@Override public void run() { JcrSession session = null; try { session = repository().login(); Query query = jcrSql2Query(session, "SELECT * FROM [mix:title]"); startLatch.await(); for (int i = 0; i != numQueriesEachThread; ++i) { // Compute a query plan that should use this index, UNLESS the index is currently undergoing rebuilding... validateQuery()./*rowCount(2L).useIndex("titleNodes")*/validate(query, query.execute()); } } catch (Exception e) { e.printStackTrace(); fail(e.getMessage()); } finally { printMessage("Completing thread"); if (session != null) session.logout(); stopLatch.countDown(); } } };
@Test public void shouldUseSingleColumnStringIndexForQueryWithNoCriteriaOtherThanMixinViaFromClause() throws Exception { registerValueIndex("titleNodes", "mix:title", null, "*", "jcr:mixinTypes", PropertyType.NAME); // print = true; // Add a node that uses this type ... Node root = session().getRootNode(); Node book1 = root.addNode("myFirstBook"); book1.addMixin("mix:title"); book1.setProperty("jcr:title", "The Title"); Node book2 = root.addNode("mySecondBook"); book2.addMixin("mix:title"); book2.setProperty("jcr:title", "A Different Title"); // Create a node that is not a 'mix:title' and therefore won't be included in the SELECT clauses ... Node other = root.addNode("somethingElse"); other.setProperty("propA", "a value for property A"); other.setProperty("jcr:title", "The Title"); session.save(); // Compute a query plan that should use this index ... Query query = jcrSql2Query("SELECT * FROM [mix:title]"); validateQuery().rowCount(2L).useIndex("titleNodes").validate(query, query.execute()); }
@Test public void shouldUseSingleColumnStringIndexForQueryWithNoCriteriaOtherThanPrimaryTypeViaFromClause() throws Exception { registerValueIndex("unstructuredNodes", "nt:unstructured", null, "*", "jcr:primaryType", PropertyType.NAME); // print = true; // Add a node that uses this type ... Node root = session().getRootNode(); Node book1 = root.addNode("myFirstBook"); book1.addMixin("mix:title"); book1.setProperty("jcr:title", "The Title"); Node book2 = root.addNode("mySecondBook"); book2.addMixin("mix:title"); book2.setProperty("jcr:title", "A Different Title"); // Create a node that is not a 'mix:title' and therefore won't be included in the SELECT clauses ... Node other = root.addNode("somethingElse"); other.setProperty("propA", "a value for property A"); other.setProperty("jcr:title", "The Title"); session.save(); // Compute a query plan that should use this index ... Query query = jcrSql2Query("SELECT * FROM [nt:unstructured]"); validateQuery().rowCount(3L).useIndex("unstructuredNodes").validate(query, query.execute()); }
@Test public void shouldNotUseSingleColumnStringIndexInQueryAgainstSuperType() throws Exception { registerValueIndex("descriptionIndex", "mix:title", "Index for the 'jcr:title' property on mix:title", "*", "jcr:title", PropertyType.STRING); // print = true; // Add a node that uses this type ... Node root = session().getRootNode(); Node book1 = root.addNode("myFirstBook"); book1.addMixin("mix:title"); book1.setProperty("jcr:title", "The Title"); Node book2 = root.addNode("mySecondBook"); book2.addMixin("mix:title"); book2.setProperty("jcr:title", "A Different Title"); // Create a node that is not a 'mix:title' and therefore won't be included in the query ... Node other = root.addNode("somethingElse"); other.setProperty("propA", "a value for property A"); other.setProperty("jcr:title", "The Title"); session.save(); // Compute a query plan that will NOT use this index, since the selector doesn't match the index's node type. // If we would use this index, the index doesn't know about non-mix:title nodes like the 'other' node ... Query query = jcrSql2Query("SELECT * FROM [nt:base] WHERE [jcr:title] = 'The Title'"); validateQuery().rowCount(2L).validate(query, query.execute()); // Compute a query plan that will NOT use this index, since the selector doesn't match the index's node type. // If we would use this index, the index doesn't know about non-mix:title nodes like the 'other' node ... query = jcrSql2Query("SELECT * FROM [nt:base] WHERE [jcr:title] LIKE '% Title'"); validateQuery().rowCount(3L).validate(query, query.execute()); }
@Test public void shouldUseSingleColumnDateIndexInQueryAgainstSameNodeType() throws Exception { registerValueIndex("dateIndex", "mix:lastModified", "Date value index", "*", "jcr:lastModified", PropertyType.DATE); // print = true; // Add a node that uses this type ... Node root = session().getRootNode(); Node obj1 = root.addNode("notionalObjectA"); obj1.addMixin("mix:lastModified"); Node obj2 = root.addNode("notionalObjectB"); obj2.addMixin("mix:lastModified"); // Create a node that is not a 'notion:typed' and therefore won't be included in the query ... Node other = root.addNode("somethingElse"); other.setProperty("jcr:lastModified", Calendar.getInstance()); session.save(); // Issues some queries that should use this index ... Query query = jcrSql2Query("SELECT * FROM [mix:lastModified] WHERE [jcr:lastModified] > CAST('2012-10-21T00:00:00.000' AS DATE)"); validateQuery().rowCount(2L).validate(query, query.execute()); query = jcrSql2Query("SELECT * FROM [mix:lastModified] WHERE [jcr:lastModified] < CAST('2999-10-21T00:00:00.000' AS DATE)"); validateQuery().rowCount(2L).validate(query, query.execute()); // Issue a query that does not use this index ... query = jcrSql2Query("SELECT * FROM [nt:unstructured] WHERE [jcr:lastModified] > CAST('2012-10-21T00:00:00.000' AS DATE)"); validateQuery().rowCount(3L).validate(query, query.execute()); }
@Test public void shouldUseSingleColumnDateAsLongIndexInQueryAgainstSameNodeType() throws Exception { registerValueIndex("dateIndex", "mix:lastModified", "Date value index", "*", "jcr:lastModified", PropertyType.LONG); // print = true; // Add a node that uses this type ... Node root = session().getRootNode(); Node obj1 = root.addNode("notionalObjectA"); obj1.addMixin("mix:lastModified"); Node obj2 = root.addNode("notionalObjectB"); obj2.addMixin("mix:lastModified"); // Create a node that is not a 'notion:typed' and therefore won't be included in the query ... Node other = root.addNode("somethingElse"); other.setProperty("jcr:lastModified", Calendar.getInstance()); session.save(); // Issues some queries that should use this index ... Query query = jcrSql2Query("SELECT * FROM [mix:lastModified] WHERE [jcr:lastModified] > CAST('2012-10-21T00:00:00.000' AS DATE)"); validateQuery().rowCount(2L).validate(query, query.execute()); query = jcrSql2Query("SELECT * FROM [mix:lastModified] WHERE [jcr:lastModified] < CAST('2999-10-21T00:00:00.000' AS DATE)"); validateQuery().rowCount(2L).validate(query, query.execute()); // Issue a query that does not use this index ... query = jcrSql2Query("SELECT * FROM [nt:unstructured] WHERE [jcr:lastModified] > CAST('2012-10-21T00:00:00.000' AS DATE)"); validateQuery().rowCount(3L).validate(query, query.execute()); }
@Test public void shouldUseSingleColumnNodeTypeIndexForQueryWithNoCriteriaOtherThanPrimaryTypeViaFromClause() throws Exception { registerNodeTypeIndex("primaryTypes", "nt:base", null, "*", "jcr:primaryType", PropertyType.STRING); registerNodeTypeIndex("mixinTypes", "nt:base", null, "*", "jcr:mixinTypes", PropertyType.STRING); // print = true; // Add a node that uses this type ... Node root = session().getRootNode(); Node book1 = root.addNode("myFirstBook"); book1.addMixin("mix:title"); book1.setProperty("jcr:title", "The Title"); Node book2 = root.addNode("mySecondBook"); book2.addMixin("mix:title"); book2.setProperty("jcr:title", "A Different Title"); // Create a node that is not a 'mix:title' and therefore won't be included in the SELECT clauses ... Node other = root.addNode("somethingElse"); other.setProperty("propA", "a value for property A"); other.setProperty("jcr:title", "The Title"); session.save(); // Compute a query plan that should use this index ... Query query = jcrSql2Query("SELECT * FROM [mix:title]"); validateQuery().rowCount(2L).useIndex("mixinTypes").validate(query, query.execute()); // Compute a query plan that should use this index ... query = jcrSql2Query("SELECT * FROM [nt:unstructured]"); validateQuery().rowCount(3L).useIndex("primaryTypes").validate(query, query.execute()); }
@FixFor( "MODE-2312" ) @Test public void shouldUseImplicitPathIndex() throws Exception { Node root = session().getRootNode(); Node newNode1 = root.addNode("nodeA"); newNode1.setProperty("foo", "X"); newNode1.addMixin("mix:referenceable"); Node newNode2 = root.addNode("nodeB"); newNode2.setProperty("foo", "Y"); session().save(); // print = true; // Compute a query plan that should use this index ... final String pathValue = newNode1.getPath(); Query query = jcrSql2Query("SELECT [jcr:path] FROM [nt:unstructured] WHERE [jcr:path] = '" + pathValue + "'"); validateQuery().rowCount(1L).useIndex(IndexPlanners.NODE_BY_PATH_INDEX_NAME).validate(query, query.execute()); query = jcrSql2Query("SELECT A.* FROM [nt:unstructured] AS A WHERE A.[jcr:path] = $pathValue"); query.bindValue("pathValue", valueFactory().createValue(pathValue)); validateQuery().rowCount(1L).useIndex(IndexPlanners.NODE_BY_PATH_INDEX_NAME).validate(query, query.execute()); }
@Test public void shouldUseSingleColumnNodeDepthIndexInQueryAgainstSameNodeType() throws Exception { registerValueIndex("depthIndex", "nt:unstructured", "Node depth index", "*", "mode:depth", PropertyType.LONG); // print = true; // Add a node that uses this type ... Node book1 = session().getRootNode().addNode("myFirstBook"); book1.addMixin("mix:title"); book1.setProperty("jcr:title", "The Title"); Node book2 = session().getRootNode().addNode("mySecondBook"); book2.addMixin("mix:title"); book2.setProperty("jcr:title", "A Different Title"); Node other = book2.addNode("chapter"); other.setProperty("propA", "a value for property A"); other.setProperty("jcr:title", "The Title"); session.save(); // Issues some queries that should use this index ... Query query = jcrSql2Query("SELECT * FROM [nt:unstructured] WHERE [mode:depth] > 0"); validateQuery().rowCount(3L).validate(query, query.execute()); query = jcrSql2Query("SELECT * FROM [nt:unstructured] WHERE DEPTH() > 0"); validateQuery().rowCount(3L).validate(query, query.execute()); query = jcrSql2Query("SELECT * FROM [nt:unstructured] WHERE DEPTH() > 1"); validateQuery().rowCount(1L).validate(query, query.execute()); query = jcrSql2Query("SELECT * FROM [nt:unstructured] WHERE DEPTH() >= 2"); validateQuery().rowCount(1L).validate(query, query.execute()); }
@Test public void shouldSelectIndexWhenMultipleAndedConstraintsApply() throws Exception { registerValueIndex("longValues", "nt:unstructured", "Long values index", "*", "value", PropertyType.LONG); Node root = session().getRootNode(); int valuesCount = 5; for (int i = 0; i < valuesCount; i++) { String name = String.valueOf(i+1); Node node = root.addNode(name); node.setProperty("value", (long) (i+1)); } session.save(); String sql1 = "SELECT number.[jcr:name] FROM [nt:unstructured] as number WHERE (number.value > 1 AND number.value < 3) OR " + "(number.value > 3 AND number.value < 5)"; String sql2 = "SELECT number.[jcr:name] FROM [nt:unstructured] as number WHERE number.value <2"; Query query = jcrSql2Query(sql1 + " UNION " + sql2); validateQuery() .rowCount(2L) .useIndex("longValues") .hasNodesAtPaths("/2", "/4", "/1") .validate(query, query.execute()); }
@Test @FixFor("MODE-2645") public void shouldNotRecreateIndexesAfterRestart() throws Exception { Node root = session().getRootNode(); Node newNode1 = root.addNode("nodeA"); newNode1.setProperty("foo", "X"); newNode1.addMixin("mix:referenceable"); Node newNode2 = root.addNode("nodeB"); newNode2.setProperty("foo", "Y"); session().save(); // print = true; // Compute a query plan that should use this index ... final String pathValue = newNode1.getPath(); Query query = jcrSql2Query("SELECT [jcr:path] FROM [nt:unstructured] WHERE [jcr:path] = '" + pathValue + "'"); validateQuery().rowCount(1L).useIndex(IndexPlanners.NODE_BY_PATH_INDEX_NAME).validate(query, query.execute()); stopRepository(); startRepositoryWithConfiguration(repositoryConfiguration()); query = jcrSql2Query("SELECT [jcr:path] FROM [nt:unstructured] WHERE [jcr:path] = '" + pathValue + "'"); validateQuery().rowCount(1L).useIndex(IndexPlanners.NODE_BY_PATH_INDEX_NAME).validate(query, query.execute()); } }
@Test @Override public void shouldSelectIndexWhenMultipleAndedConstraintsApply() throws Exception { registerValueIndex("longValues", "nt:unstructured", "Long values index", "*", "value", PropertyType.LONG); Node root = session().getRootNode(); int valuesCount = 5; for (int i = 0; i < valuesCount; i++) { String name = String.valueOf(i+1); Node node = root.addNode(name); node.setProperty("value", (long) (i+1)); } session.save(); String sql1 = "SELECT number.[jcr:name] FROM [nt:unstructured] as number WHERE (number.value > 1 AND number.value < 3) OR " + "(number.value > 3 AND number.value < 5)"; String sql2 = "SELECT number.[jcr:name] FROM [nt:unstructured] as number WHERE number.value <2"; Query query = jcrSql2Query(sql1 + " UNION " + sql2); validateQuery() .rowCount(3L) .useIndex("longValues") .hasNodesAtPaths("/2", "/4", "/1") .validate(query, query.execute()); }
@FixFor( "MODE-2312" ) @Test public void shouldUseImplicitIdIndex() throws Exception { Node root = session().getRootNode(); Node newNode1 = root.addNode("nodeA"); newNode1.setProperty("foo", "X"); newNode1.addMixin("mix:referenceable"); Node newNode2 = root.addNode("nodeB"); newNode2.setProperty("foo", "Y"); session().save(); // print = true; // Compute a query plan that should use this index ... final String uuid = newNode1.getIdentifier(); Query query = jcrSql2Query("SELECT [jcr:path] FROM [nt:unstructured] WHERE [jcr:uuid] = '" + uuid + "'"); validateQuery().rowCount(1L).useIndex(IndexPlanners.NODE_BY_ID_INDEX_NAME).validate(query, query.execute()); query = jcrSql2Query("SELECT A.* FROM [nt:unstructured] AS A WHERE A.[jcr:uuid] = $uuidValue"); query.bindValue("uuidValue", valueFactory().createValue(uuid)); validateQuery().rowCount(1L).useIndex(IndexPlanners.NODE_BY_ID_INDEX_NAME).validate(query, query.execute()); }
@FixFor( "MODE-2346" ) @Test public void shouldUseExplicitIndexesWithLowerCardinalityOverImplicitIndexes() throws Exception { String explicitIndex = "explicitIndex"; registerValueIndex(explicitIndex, "nt:unstructured", "Foo index", "*", "foo", PropertyType.STRING); // wait a bit to make sure the index definitions have been updated Node root = session().getRootNode(); Node nodeA = root.addNode("nodeA"); Node nodeB = nodeA.addNode("nodeB"); nodeB.setProperty("foo", "X"); session().save(); // print = true; // Compute a query plan that should use this index ... Query query = jcrSql2Query( "SELECT [jcr:path] FROM [nt:unstructured] AS node WHERE ISDESCENDANTNODE(node, '/nodeA') AND node.[foo]='X'"); validateQuery() .rowCount(1L) .considerIndexes(IndexPlanners.DESCENDANTS_BY_PATH_INDEX_NAME, explicitIndex) .useIndex(explicitIndex) .validate(query, query.execute()); }
@Test public void shouldUseSingleColumnNodeNameIndexInQueryAgainstSameNodeType() throws Exception { registerValueIndex("nameIndex", "nt:base", "Node name index", "*", "jcr:name", PropertyType.NAME); // print = true; // Add a node that uses this type ... Node book1 = session().getRootNode().addNode("myFirstBook"); book1.addMixin("mix:title"); book1.setProperty("jcr:title", "The Title"); Node book2 = session().getRootNode().addNode("mySecondBook"); book2.addMixin("mix:title"); book2.setProperty("jcr:title", "A Different Title"); Node other = session().getRootNode().addNode("somethingElse"); other.setProperty("propA", "a value for property A"); other.setProperty("jcr:title", "The Title"); session.save(); // Issues some queries that should use this index ... Query query = jcrSql2Query("SELECT * FROM [nt:base] WHERE [jcr:name] = 'myFirstBook'"); validateQuery().rowCount(1L).validate(query, query.execute()); query = jcrSql2Query("SELECT * FROM [nt:base] WHERE NAME() LIKE 'myFirstBook'"); validateQuery().rowCount(1L).validate(query, query.execute()); query = jcrSql2Query("SELECT * FROM [nt:unstructured] WHERE NAME() LIKE '%Book'"); validateQuery().rowCount(2L).validate(query, query.execute()); }
@Test @FixFor( "MODE-2515" ) public void shouldSupportQueryLimitWithMoreThan100Nodes() throws Exception { registerValueIndex("title", "mix:title", null, "*", "jcr:title", PropertyType.STRING); // Add a node that uses this type ... Node root = session().getRootNode(); int nodeCount = 102; for (int i = 0; i < nodeCount; i++) { Node book = root.addNode("book_" + (i+1)); book.addMixin("mix:title"); book.setProperty("jcr:title", "Title"); } session.save(); // Compute a query plan that should use this index ... Query query = jcrSql2Query("SELECT * FROM [mix:title] as book where book.[jcr:title] = 'Title'"); int limit = nodeCount - 1; query.setLimit(limit); validateQuery().rowCount(limit).useIndex("title").validate(query, query.execute()); limit = nodeCount / 2; query.setLimit(limit); validateQuery().rowCount(limit).useIndex("title").validate(query, query.execute()); }
@FixFor( "MODE-2401" ) @Test public void shouldNotConsiderNonQueryableNodeTypes() throws RepositoryException, InterruptedException { String typeName = "nt:nonQueryableFolder"; registerNodeType(typeName, false, false, "nt:folder"); registerNodeTypeIndex("typesIndex", "nt:folder", null, "*", "jcr:primaryType", PropertyType.STRING); session.getRootNode().addNode("nonQueryableFolder", typeName); session.getRootNode().addNode("regularFolder1", "nt:folder"); Node folder2 = session.getRootNode().addNode("regularFolder2", typeName); folder2.addNode("subFolder", "nt:folder"); session.save(); final List<String> expectedResults = new ArrayList<>(Arrays.asList("/regularFolder1", "/regularFolder2/subFolder")); Query query = jcrSql2Query("select folder.[jcr:name] FROM [nt:folder] as folder"); validateQuery() .rowCount(2L) .useIndex("typesIndex") .onEachRow(new ValidateQuery.Predicate() { @Override public void validate( int rowNumber, Row row ) throws RepositoryException { expectedResults.remove(row.getPath()); } }) .validate(query, query.execute()); assertTrue("Not all expected nodes found: " + expectedResults, expectedResults.isEmpty()); }
@Test @FixFor( "MODE-2498") public void shouldSelectCorrectIndexWhenMultipleIndexesUseTheSameAncestorProperty() throws Exception { registerNodeType("mix:custom", true, true, "mix:title"); registerNodeType("mix:custom2", true, true, "mix:title"); registerValueIndex("custom_names", "mix:custom", null, "*", "jcr:name", PropertyType.NAME); registerValueIndex("custom2_names", "mix:custom2", null, "*", "jcr:name", PropertyType.NAME); //print = true; // Add a node that uses this type ... Node root = session().getRootNode(); Node book1 = root.addNode("myFirstBook"); book1.addMixin("mix:custom"); book1.setProperty("jcr:title", "The Title"); session.save(); // Compute a query plan that should use this index ... Query query = jcrSql2Query("SELECT * FROM [mix:custom] as custom where custom.[jcr:name] = 'myFirstBook'"); validateQuery().rowCount(1L).useIndex("custom_names").validate(query, query.execute()); }
@FixFor( "MODE-2307" ) @Test public void shouldUseSingleColumnStringIndexForQueryWithJoin() throws Exception { registerNodeType("nt:typeWithReference"); registerNodeType("nt:typeWithSysName"); registerValueIndex("refIndex", "nt:typeWithReference", null, "*", "referenceId", PropertyType.STRING); registerValueIndex("sysIndex", "nt:typeWithSysName", null, "*", "sysName", PropertyType.STRING); registerNodeTypeIndex("typesIndex", "nt:base", null, "*", "jcr:primaryType", PropertyType.STRING); // print = true; Node root = session().getRootNode(); Node newNode1 = root.addNode("nodeWithSysName", "nt:typeWithSysName"); newNode1.setProperty("sysName", "X"); newNode1.addMixin("mix:referenceable"); Node newNode2 = root.addNode("nodeWithReference", "nt:typeWithReference"); newNode2.setProperty("referenceId", newNode1.getIdentifier()); session.save(); // Compute a query plan that should use this index ... Query query = jcrSql2Query("SELECT A.* FROM [nt:typeWithReference] AS A " + "JOIN [nt:typeWithSysName] AS B ON A.referenceId = B.[jcr:uuid] " // + "WHERE B.sysName = $sysName"); query.bindValue("sysName", valueFactory().createValue("X")); validateQuery().rowCount(1L).considerIndexes("sysIndex", "refIndex", "typesIndex").validate(query, query.execute()); }