@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 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()); }
private RepositoryOperation reindexingExternalContentOperation() { return repository -> { JcrSession session = repository.login(); // sleep a bit to make sure reindexing completes Thread.sleep(300); try { AbstractJcrNode node = session.getNode("/fs2/file.txt"); String createdBy = node.getProperty("jcr:createdBy").getString(); assertNotNull(createdBy); JcrQueryManager jcrQueryManager = session.getWorkspace().getQueryManager(); Query query = jcrQueryManager.createQuery("select file.[jcr:path] from [nt:file] as file where file.[jcr:createdBy]='" + createdBy + "'", JcrQuery.JCR_SQL2); ValidateQuery.validateQuery() .useIndex("nodesByAuthor") .hasNodesAtPaths("/fs2/file.txt") .validate(query, query.execute()); session.getWorkspace().reindex(); ValidateQuery.validateQuery() .useIndex("nodesByAuthor") .hasNodesAtPaths("/fs2/file.txt") .validate(query, query.execute()); } finally { session.logout(); } }; }
@Test public void shouldUseSingleColumnNodePathIndexInQueryAgainstSameNodeType() throws Exception { registerValueIndex("pathIndex", "nt:unstructured", "Node path index", "*", "jcr:path", PropertyType.PATH); // 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 a query that should NOT use this index because direct lookup by path is lower cost ... Query query = jcrSql2Query("SELECT * FROM [nt:unstructured] WHERE [jcr:path] = '/myFirstBook'"); validateQuery().rowCount(1L).useIndex("NodeByPath").considerIndex("pathIndex").validate(query, query.execute()); // Issues a query that should NOT use this index ... query = jcrSql2Query("SELECT * FROM [nt:unstructured] WHERE [jcr:path] LIKE '/my%Book'"); validateQuery().rowCount(2L).useNoIndexes().validate(query, query.execute()); // Issues some queries that should use this index ... query = jcrSql2Query("SELECT * FROM [nt:unstructured] WHERE [jcr:path] > '/mySecondBook'"); validateQuery().rowCount(1L).useIndex("pathIndex").validate(query, query.execute()); query = jcrSql2Query("SELECT * FROM [nt:unstructured] WHERE PATH() > '/mySecondBook'"); validateQuery().rowCount(1L).useIndex("pathIndex").validate(query, query.execute()); }
validateQuery() .rowCount(1L) .useIndex("typesIndex") .hasNodesAtPaths("/node101") .validate(query1, query1.execute()); validateQuery() .rowCount(1L) .useIndex("typesIndex") .hasNodesAtPaths("/node51") .validate(query2, query2.execute()); validateQuery() .rowCount(1L) .useIndex("typesIndex") .hasNodesAtPaths("/node1") .validate(query3, query3.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()); }
String sql = "select [jcr:path] from [nt:unstructured] where testProp = 'test'"; Query query = session.getWorkspace().getQueryManager().createQuery(sql, Query.JCR_SQL2); ValidateQuery.validateQuery().rowCount(1).useIndex("testProp").validate(query, query.execute()); session.logout();
@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()); }
@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()); }
@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 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()); }
@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()); }
@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()); }
@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()); }
@Test @FixFor( "MODE-2393 ") public void reindexingLocalProviderShouldRemoveExistingDataFirst() throws Exception { // clean the indexes TestingUtil.waitUntilFolderCleanedUp("target/startup_test_indexes"); startRunStop(this::addNodeAndAssertIndexUsed, "config/repo-config-persistent-local-indexes.json"); startRunStop(repository -> { JcrSession session = repository.login(); // force a re-index of the entire workspace - this should clear the existing indexes first session.getWorkspace().reindex(); // then force a reindex of a certain path session.getWorkspace().reindex("/testRoot"); //then check that still only 1 node is returned String sql = "select [jcr:path] from [nt:unstructured] where [jcr:name] = 'testRoot'"; Query query = session.getWorkspace().getQueryManager().createQuery(sql, Query.JCR_SQL2); ValidateQuery.validateQuery().rowCount(1).useIndex("nodesByName").validate(query, query.execute()); }, "config/repo-config-persistent-local-indexes.json"); }
@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-2292" ) @Test public void shouldUseIndexesAfterRestarting() throws Exception { // clean the indexes TestingUtil.waitUntilFolderCleanedUp("target/startup_test_indexes"); startRunStop(this::addNodeAndAssertIndexUsed, "config/repo-config-persistent-local-indexes.json"); startRunStop(repository -> { JcrSession session = repository.login(); String sql = "select [jcr:path] from [nt:unstructured] where [jcr:name] = 'testRoot'"; Query query = session.getWorkspace().getQueryManager().createQuery(sql, Query.JCR_SQL2); ValidateQuery.validateQuery().rowCount(1).useIndex("nodesByName").validate(query, query.execute()); session.logout(); }, "config/repo-config-persistent-local-indexes.json"); }
@Test @FixFor("MODE-2644") public void shouldUseDynamicallyRegisteredIndexes() throws Exception { // clean the indexes TestingUtil.waitUntilFolderCleanedUp("target/startup_test_indexes"); startRunStop(this::registerIndexDefinitionAndCheckUsage, "config/repo-config-persistent-local-indexes.json"); startRunStop(repository -> { JcrSession session = repository.login(); String sql = "select [jcr:path] from [nt:unstructured] where testProp = 'test'"; Query query = session.getWorkspace().getQueryManager().createQuery(sql, Query.JCR_SQL2); ValidateQuery.validateQuery().rowCount(1).useIndex("testProp").validate(query, query.execute()); session.logout(); }, "config/repo-config-persistent-local-indexes.json"); }
private void addNodeAndAssertIndexUsed(JcrRepository repository) throws RepositoryException, InterruptedException { JcrSession session = repository.login(); session.getRootNode().addNode("testRoot"); session.save(); Thread.sleep(100); String sql = "select [jcr:path] from [nt:unstructured] where [jcr:name] = 'testRoot'"; Query query = session.getWorkspace().getQueryManager().createQuery(sql, Query.JCR_SQL2); ValidateQuery.validateQuery().rowCount(1).useIndex("nodesByName").validate(query, query.execute()); session.logout(); }