/* * ModeShape (http://www.modeshape.org) * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.modeshape.jcr.query.index; import static org.hamcrest.core.Is.is; import static org.hamcrest.core.IsNull.notNullValue; import static org.junit.Assert.assertThat; import static org.junit.Assert.fail; import java.io.IOException; import java.io.InputStream; import java.net.URL; import javax.jcr.ImportUUIDBehavior; import javax.jcr.Node; import javax.jcr.NodeIterator; import javax.jcr.RepositoryException; import javax.jcr.Value; import javax.jcr.query.Query; import javax.jcr.query.QueryResult; import javax.jcr.query.Row; import javax.jcr.query.RowIterator; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Ignore; import org.junit.Test; import org.modeshape.common.util.StringUtil; import org.modeshape.jcr.MultiUseAbstractTest; import org.modeshape.jcr.RepositoryConfiguration; import org.modeshape.jcr.api.Session; /** * @author Randall Hauch (rhauch@redhat.com) */ public class QueryEngineTest extends MultiUseAbstractTest { @BeforeClass public static void beforeAll() throws Exception { URL url = QueryEngineTest.class.getClassLoader().getResource("config/repo-config-no-indexes.json"); RepositoryConfiguration config = RepositoryConfiguration.read(url); startRepository(config); try { // Use a session to load the contents ... Session session = repository.login(); try { registerNodeTypes(session, "cnd/fincayra.cnd"); registerNodeTypes(session, "cnd/magnolia.cnd"); registerNodeTypes(session, "cnd/notionalTypes.cnd"); registerNodeTypes(session, "cnd/cars.cnd"); registerNodeTypes(session, "cnd/validType.cnd"); InputStream stream = resourceStream("io/cars-system-view.xml"); try { session.getWorkspace().importXML("/", stream, ImportUUIDBehavior.IMPORT_UUID_CREATE_NEW); } catch (Throwable t) { t.printStackTrace(); } finally { stream.close(); } // Create a branch that contains some same-name-siblings ... Node other = session.getRootNode().addNode("Other", "nt:unstructured"); Node a = other.addNode("NodeA", "nt:unstructured"); a.addMixin("mix:referenceable"); a.setProperty("something", "value3 quick brown fox"); a.setProperty("somethingElse", "value2"); a.setProperty("propA", "value1"); Node other2 = other.addNode("NodeA", "nt:unstructured"); other2.addMixin("mix:referenceable"); other2.setProperty("something", "value2 quick brown cat wearing hat"); other2.setProperty("propB", "value1"); other2.setProperty("propC", "value2"); Node other3 = other.addNode("NodeA", "nt:unstructured"); other3.addMixin("mix:referenceable"); other3.setProperty("something", new String[] {"black dog", "white dog"}); other3.setProperty("propB", "value1"); other3.setProperty("propC", "value3"); Value[] refValues = new Value[2]; refValues[0] = session.getValueFactory().createValue(other2); refValues[1] = session.getValueFactory().createValue(other3); other3.setProperty("otherNode", a); other3.setProperty("otherNodes", refValues); Node c = other.addNode("NodeC", "notion:typed"); c.setProperty("notion:booleanProperty", true); c.setProperty("notion:booleanProperty2", false); c.setProperty("propD", "value4"); c.setProperty("propC", "value1"); c.setProperty("notion:singleReference", a); c.setProperty("notion:multipleReferences", refValues); Node b = session.getRootNode().addNode("NodeB", "nt:unstructured"); b.setProperty("myUrl", "http://www.acme.com/foo/bar"); b.setProperty("pathProperty", a.getPath()); session.save(); // // Initialize the nodes count // initNodesCount(); // // // Prime creating a first XPath query and SQL query ... // session.getWorkspace().getQueryManager().createQuery("//element(*,nt:unstructured)", Query.XPATH); // session.getWorkspace().getQueryManager().createQuery("SELECT * FROM [nt:base]", Query.JCR_SQL2); } finally { session.logout(); } // Prime creating the schemata ... // repository.nodeTypeManager().getRepositorySchemata(); } catch (RuntimeException e) { e.printStackTrace(); throw e; } catch (Exception e) { e.printStackTrace(); throw e; } } @AfterClass public static void afterAll() throws Exception { MultiUseAbstractTest.afterAll(); } @Test public void shouldQueryForAllNodes() throws Exception { String sql = "SELECT * FROM [nt:base]"; // print = true; query(sql); } @Test public void shouldQueryForAllDescendantNodesUnderCars() throws Exception { String sql = "SELECT * FROM [nt:base] AS nodes WHERE ISDESCENDANTNODE(nodes,'/Cars')"; // print = true; query(sql, 17, pathStartsWith("/Cars/")); } @Test public void shouldQueryForAllChildrenUnderCars() throws Exception { String sql = "SELECT * FROM [nt:base] AS nodes WHERE ISCHILDNODE(nodes,'/Cars')"; // print = true; query(sql, 4, pathStartsWith("/Cars/")); } @Test public void shouldQueryForNodeAtPath() throws Exception { String sql = "SELECT * FROM [nt:base] AS nodes WHERE PATH(nodes) = '/Cars'"; // print = true; query(sql, 1, pathStartsWith("/Cars")); } @Test public void shouldQueryForNodeAtPathAndUnnecessaryCriteria() throws Exception { String sql = "SELECT * FROM [nt:base] AS nodes WHERE PATH(nodes) = '/Cars' AND ISCHILDNODE(nodes,'/')"; // print = true; query(sql, 1, pathStartsWith("/Cars")); } @Test public void shouldQueryForNodeUsingJoinWithChildNodesWithOnlyJoinCriteria() throws Exception { String sql = "SELECT * FROM [nt:unstructured] AS category JOIN [car:Car] AS car ON ISCHILDNODE(car,category)"; // print = true; query(sql, 13); } @Test public void shouldQueryForCarNodesAtPathAndUnnecessaryCriteria() throws Exception { String sql = "SELECT * FROM [car:Car] AS nodes WHERE PATH(nodes) = '/Cars/Utility/Toyota Land Cruiser'"; // print = true; query(sql, 1, pathStartsWith("/Cars/Utility/Toyota Land Cruiser")); } @Ignore @Test public void shouldQueryNodesWithMultipleCriteria() throws Exception { String sql = "SELECT * FROM [nt:base] WHERE ([acme:something] = 'foo' AND [acme:prop2] = 3.55 ) OR [acme:prop3] > 2"; // print = true; query(sql); } protected static interface Predicate { boolean accept( Node node ) throws RepositoryException; } protected Predicate pathStartsWith( final String prefix ) { return new Predicate() { @Override public boolean accept( Node node ) throws RepositoryException { return node != null && node.getPath().startsWith(prefix); } }; } protected void query( String sql ) throws Exception { query(sql, -1, null); } protected void query( String sql, Predicate predicate ) throws Exception { query(sql, -1, predicate); } protected void query( String sql, long numberOfResults ) throws Exception { query(sql, Query.JCR_SQL2, numberOfResults, null); } protected void query( String sql, long numberOfResults, Predicate predicate ) throws Exception { query(sql, Query.JCR_SQL2, numberOfResults, predicate); } protected void query( String sql, String language, long numberOfResults, Predicate predicate ) throws Exception { Query query = ((Session)session).getWorkspace().getQueryManager().createQuery(sql, language); QueryResult result = query.execute(); assertResults(query, result, numberOfResults, predicate); } protected void assertResults( Query query, QueryResult result, long numberOfResults, Predicate predicate ) throws RepositoryException { assertThat(query, is(notNullValue())); assertThat(result, is(notNullValue())); if (print /*|| result.getNodes().getSize() != numberOfResults || result.getRows().getSize() != numberOfResults*/) { System.out.println(); System.out.println(query); System.out.println(((org.modeshape.jcr.api.query.QueryResult)result).getPlan()); System.out.println(result); } if (result.getSelectorNames().length == 1) { NodeIterator iter = result.getNodes(); if (numberOfResults >= 0 && iter.getSize() != -1) { assertThat(iter.getSize(), is(numberOfResults)); } int i = 0; while (iter.hasNext()) { Node node = iter.nextNode(); if (print) System.out.println(" " + StringUtil.justifyRight(Integer.toString(++i), 4, ' ') + ". " + node.getPath()); if (predicate != null) assertThat(predicate.accept(node), is(true)); } if (print) System.out.println(); // add a blank separator line } else { try { result.getNodes(); fail("should not be able to call this method when the query has multiple selectors"); } catch (RepositoryException e) { // expected; can't call this when the query uses multiple selectors ... } String[] selectorNames = result.getSelectorNames(); RowIterator rows = result.getRows(); int rowCount = 0; while (rows.hasNext()) { Row row = rows.nextRow(); if (print) System.out.print(" " + StringUtil.justifyRight(Integer.toString(++rowCount), 4, ' ') + ". "); for (int i = 0; i != selectorNames.length; ++i) { Node node = row.getNode(selectorNames[i]); if (print) { if (i != 0) System.out.print(", "); System.out.print(node.getPath()); } } if (print) System.out.println(); // complete the line } if (print) System.out.println(); // add a blank separator line } if (numberOfResults >= 0 && result.getRows().getSize() != -1) { assertThat(result.getRows().getSize(), is(numberOfResults)); } } protected static void registerNodeTypes( Session session, String pathToClasspathResource ) throws RepositoryException, IOException { URL url = resourceUrl(pathToClasspathResource); session.getWorkspace().getNodeTypeManager().registerNodeTypes(url, true); } protected static URL resourceUrl( String name ) { return QueryEngineTest.class.getClassLoader().getResource(name); } }