/* * 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.plan; import static org.hamcrest.core.Is.is; import static org.junit.Assert.assertThat; import static org.mockito.Mockito.mock; import java.util.Collection; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import org.junit.Before; import org.junit.Test; import org.modeshape.common.FixFor; import org.modeshape.common.collection.Problems; import org.modeshape.common.collection.SimpleProblems; import org.modeshape.jcr.ExecutionContext; import org.modeshape.jcr.NodeTypes; import org.modeshape.jcr.RepositoryIndexes; import org.modeshape.jcr.api.query.qom.Operator; import org.modeshape.jcr.cache.RepositoryCache; import org.modeshape.jcr.query.BufferManager; import org.modeshape.jcr.query.QueryBuilder; import org.modeshape.jcr.query.QueryContext; import org.modeshape.jcr.query.model.And; import org.modeshape.jcr.query.model.BindVariableName; import org.modeshape.jcr.query.model.Column; import org.modeshape.jcr.query.model.Comparison; import org.modeshape.jcr.query.model.Constraint; import org.modeshape.jcr.query.model.DynamicOperand; import org.modeshape.jcr.query.model.Literal; import org.modeshape.jcr.query.model.NodePath; import org.modeshape.jcr.query.model.PropertyValue; import org.modeshape.jcr.query.model.QueryCommand; import org.modeshape.jcr.query.model.SelectorName; import org.modeshape.jcr.query.model.SetCriteria; import org.modeshape.jcr.query.model.StaticOperand; import org.modeshape.jcr.query.model.Subquery; import org.modeshape.jcr.query.model.TypeSystem; import org.modeshape.jcr.query.plan.PlanNode.Property; import org.modeshape.jcr.query.plan.PlanNode.Type; import org.modeshape.jcr.query.validate.ImmutableSchemata; import org.modeshape.jcr.query.validate.Schemata; /** * */ public class CanonicalPlannerTest { private ExecutionContext executionContext; private CanonicalPlanner planner; private TypeSystem typeSystem; private QueryBuilder builder; private PlanHints hints; private QueryCommand query; private PlanNode plan; private Problems problems; private Schemata schemata; private RepositoryIndexes indexDefns; private NodeTypes nodeTypes; private ImmutableSchemata.Builder schemataBuilder; private QueryContext queryContext; private boolean print; private RepositoryCache repoCache; private Set<String> workspaces; private BufferManager bufferMgr; @Before public void beforeEach() { planner = new CanonicalPlanner(); executionContext = new ExecutionContext(); typeSystem = executionContext.getValueFactories().getTypeSystem(); repoCache = mock(RepositoryCache.class); workspaces = Collections.singleton("workspace"); hints = new PlanHints(); builder = new QueryBuilder(typeSystem); problems = new SimpleProblems(); nodeTypes = mock(NodeTypes.class); indexDefns = mock(RepositoryIndexes.class); schemataBuilder = ImmutableSchemata.createBuilder(executionContext, nodeTypes); bufferMgr = new BufferManager(executionContext); print = false; } protected void print( PlanNode plan ) { if (print) System.out.println(plan); } protected SelectorName selector( String name ) { return new SelectorName(name); } protected Set<SelectorName> selectors( String... names ) { Set<SelectorName> selectors = new HashSet<SelectorName>(); for (String name : names) { selectors.add(selector(name)); } return selectors; } @SuppressWarnings( "unchecked" ) protected void assertProjectNode( PlanNode node, String... columnNames ) { assertThat(node.getType(), is(Type.PROJECT)); if (columnNames.length != 0) { assertThat(node.hasCollectionProperty(Property.PROJECT_COLUMNS), is(true)); } List<Column> columns = node.getProperty(Property.PROJECT_COLUMNS, List.class); assertThat(columns.size(), is(columnNames.length)); for (int i = 0; i != columns.size(); ++i) { Column column = columns.get(i); assertThat(column.getColumnName(), is(columnNames[i])); } } @SuppressWarnings( "unchecked" ) protected void assertSourceNode( PlanNode node, String sourceName, String sourceAlias, String... availableColumns ) { assertThat(node.getType(), is(Type.SOURCE)); assertThat(node.getProperty(Property.SOURCE_NAME, SelectorName.class), is(selector(sourceName))); if (sourceAlias != null) { assertThat(node.getProperty(Property.SOURCE_ALIAS, SelectorName.class), is(selector(sourceAlias))); } else { assertThat(node.hasProperty(Property.SOURCE_ALIAS), is(false)); } Collection<Schemata.Column> columns = (Collection<Schemata.Column>)node.getProperty(Property.SOURCE_COLUMNS); assertThat(columns.size(), is(availableColumns.length)); int i = 0; for (Schemata.Column column : columns) { String expectedName = availableColumns[i++]; assertThat(column.getName(), is(expectedName)); } } protected QueryContext initQueryContext() { queryContext = new QueryContext(executionContext, repoCache, workspaces, schemata, indexDefns, nodeTypes, bufferMgr, hints, problems); return queryContext; } @Test public void shouldProducePlanForSelectStarFromTable() { schemata = schemataBuilder.addTable("__ALLNODES__", "column1", "column2", "column3").build(); query = builder.selectStar().fromAllNodes().query(); initQueryContext(); plan = planner.createPlan(queryContext, query); assertThat(problems.isEmpty(), is(true)); assertProjectNode(plan, "column1", "column2", "column3"); assertThat(plan.getType(), is(PlanNode.Type.PROJECT)); assertThat(plan.getChildCount(), is(1)); PlanNode source = plan.getFirstChild(); assertSourceNode(source, "__ALLNODES__", null, "column1", "column2", "column3"); assertThat(source.getChildCount(), is(0)); print(plan); } @Test public void shouldProduceErrorWhenSelectingNonExistantTable() { schemata = schemataBuilder.addTable("someTable", "column1", "column2", "column3").build(); query = builder.selectStar().fromAllNodes().query(); initQueryContext(); plan = planner.createPlan(queryContext, query); assertThat(problems.hasErrors(), is(true)); } @Test public void shouldProduceErrorWhenSelectingNonExistantColumnOnExistingTable() { schemata = schemataBuilder.addTable("someTable", "column1", "column2", "column3").build(); query = builder.select("column1", "column4").from("someTable").query(); initQueryContext(); plan = planner.createPlan(queryContext, query); assertThat(problems.hasErrors(), is(true)); } @Test public void shouldProducePlanWhenSelectingAllColumnsOnExistingTable() { schemata = schemataBuilder.addTable("someTable", "column1", "column2", "column3").build(); query = builder.selectStar().from("someTable").query(); initQueryContext(); plan = planner.createPlan(queryContext, query); print(plan); assertThat(problems.hasErrors(), is(false)); assertThat(problems.isEmpty(), is(true)); assertProjectNode(plan, "column1", "column2", "column3"); assertThat(plan.getType(), is(PlanNode.Type.PROJECT)); assertThat(plan.getChildCount(), is(1)); PlanNode source = plan.getFirstChild(); assertSourceNode(source, "someTable", null, "column1", "column2", "column3"); assertThat(source.getChildCount(), is(0)); } @Test public void shouldProducePlanWhenSelectingColumnsFromTableWithoutAlias() { schemata = schemataBuilder.addTable("someTable", "column1", "column2", "column3").build(); query = builder.select("column1", "column2").from("someTable").where().path("someTable").isEqualTo(1L).end().query(); initQueryContext(); plan = planner.createPlan(queryContext, query); assertThat(problems.hasErrors(), is(false)); assertThat(plan.getType(), is(PlanNode.Type.PROJECT)); assertThat(plan.getSelectors(), is(selectors("someTable"))); } @Test public void shouldProducePlanWhenSelectingColumnsFromTableWithAlias() { schemata = schemataBuilder.addTable("dna:someTable", "column1", "column2", "column3").build(); query = builder.select("column1", "column2").from("dna:someTable AS t1").where().path("t1").isEqualTo(1L).end().query(); initQueryContext(); plan = planner.createPlan(queryContext, query); assertThat(problems.hasErrors(), is(false)); print(plan); assertThat(plan.getType(), is(PlanNode.Type.PROJECT)); assertThat(plan.getSelectors(), is(selectors("t1"))); } @Test public void shouldProducePlanWhenSelectingAllColumnsFromTableWithAlias() { schemata = schemataBuilder.addTable("dna:someTable", "column1", "column2", "column3").build(); query = builder.selectStar().from("dna:someTable AS t1").where().path("t1").isEqualTo(1L).end().query(); initQueryContext(); plan = planner.createPlan(queryContext, query); assertThat(problems.hasErrors(), is(false)); print(plan); assertThat(plan.getType(), is(PlanNode.Type.PROJECT)); assertThat(plan.getSelectors(), is(selectors("t1"))); } @Test public void shouldProduceErrorWhenFullTextSearchingTableWithNoSearchableColumns() { schemata = schemataBuilder.addTable("someTable", "column1", "column2", "column3").build(); // Make sure the query without the search criteria does not have an error query = builder.select("column1", "column2").from("someTable").query(); initQueryContext(); plan = planner.createPlan(queryContext, query); assertThat(problems.hasErrors(), is(false)); query = builder.select("column1", "column2").from("someTable").where().search("someTable", "term1").end().query(); initQueryContext(); plan = planner.createPlan(queryContext, query); assertThat(problems.hasErrors(), is(true)); } @Test public void shouldProducePlanWhenFullTextSearchingTableWithAtLeastOneSearchableColumn() { schemata = schemataBuilder.addTable("someTable", "column1", "column2", "column3").makeSearchable("someTable", "column1") .build(); query = builder.select("column1", "column4").from("someTable").where().search("someTable", "term1").end().query(); initQueryContext(); plan = planner.createPlan(queryContext, query); assertThat(problems.hasErrors(), is(true)); } @Test public void shouldProduceErrorWhenFullTextSearchingColumnThatIsNotSearchable() { schemata = schemataBuilder.addTable("someTable", "column1", "column2", "column3").build(); // Make sure the query without the search criteria does not have an error query = builder.select("column1", "column2").from("someTable").query(); initQueryContext(); plan = planner.createPlan(queryContext, query); assertThat(problems.hasErrors(), is(false)); query = builder.select("column1", "column2").from("someTable").where().search("someTable", "column2", "term1").end() .query(); initQueryContext(); plan = planner.createPlan(queryContext, query); assertThat(problems.hasErrors(), is(true)); } @Test public void shouldProducePlanWhenFullTextSearchingColumnThatIsSearchable() { schemata = schemataBuilder.addTable("someTable", "column1", "column2", "column3").makeSearchable("someTable", "column1") .build(); query = builder.select("column1", "column4").from("someTable").where().search("someTable", "column1", "term1").end() .query(); initQueryContext(); plan = planner.createPlan(queryContext, query); assertThat(problems.hasErrors(), is(true)); } @Test public void shouldProducePlanWhenOrderByClauseIsUsed() { schemata = schemataBuilder.addTable("dna:someTable", "column1", "column2", "column3").build(); query = builder.selectStar().from("dna:someTable AS t1").where().path("t1").isEqualTo(1L).end().orderBy().ascending() .propertyValue("t1", "column1").end().query(); initQueryContext(); plan = planner.createPlan(queryContext, query); assertThat(problems.hasErrors(), is(false)); print(plan); assertThat(plan.getType(), is(PlanNode.Type.SORT)); assertThat(plan.getSelectors(), is(selectors("t1"))); } @Test public void shouldProducePlanWhenOrderByClauseWithScoreIsUsed() { schemata = schemataBuilder.addTable("dna:someTable", "column1", "column2", "column3").build(); query = builder.selectStar().from("dna:someTable AS t1").where().path("t1").isEqualTo(1L).end().orderBy().ascending() .fullTextSearchScore("t1").end().query(); initQueryContext(); plan = planner.createPlan(queryContext, query); assertThat(problems.hasErrors(), is(false)); print(plan); assertThat(plan.getType(), is(PlanNode.Type.SORT)); assertThat(plan.getSelectors(), is(selectors("t1"))); } @FixFor( "MODE-869" ) @Test public void shouldProducePlanWhenUsingSubquery() { // Define the schemata ... schemata = schemataBuilder.addTable("someTable", "column1", "column2", "column3") .addTable("otherTable", "columnA", "columnB").build(); // Define the subquery command ... QueryCommand subquery = builder.select("columnA").from("otherTable").query(); builder = new QueryBuilder(typeSystem); // Define the query command (which uses the subquery) ... query = builder.selectStar().from("someTable").where().path("someTable").isLike(subquery).end().query(); initQueryContext(); plan = planner.createPlan(queryContext, query); // print = true; print(plan); assertThat(problems.hasErrors(), is(false)); assertThat(problems.isEmpty(), is(true)); // The top node should be the dependent query ... assertThat(plan.getType(), is(Type.DEPENDENT_QUERY)); assertThat(plan.getChildCount(), is(2)); // The first child should be the plan for the subquery ... PlanNode subqueryPlan = plan.getFirstChild(); assertProjectNode(subqueryPlan, "columnA"); assertThat(subqueryPlan.getProperty(Property.VARIABLE_NAME, String.class), is(Subquery.VARIABLE_PREFIX + "1")); assertThat(subqueryPlan.getChildCount(), is(1)); assertThat(subqueryPlan.getSelectors(), is(selectors("otherTable"))); PlanNode subquerySource = subqueryPlan.getFirstChild(); assertSourceNode(subquerySource, "otherTable", null, "columnA", "columnB"); assertThat(subquerySource.getChildCount(), is(0)); // The second child should be the plan for the regular query ... PlanNode queryPlan = plan.getLastChild(); assertProjectNode(queryPlan, "column1", "column2", "column3"); assertThat(queryPlan.getType(), is(PlanNode.Type.PROJECT)); assertThat(queryPlan.getChildCount(), is(1)); assertThat(queryPlan.getSelectors(), is(selectors("someTable"))); PlanNode criteriaNode = queryPlan.getFirstChild(); assertThat(criteriaNode.getType(), is(PlanNode.Type.SELECT)); assertThat(criteriaNode.getChildCount(), is(1)); assertThat(criteriaNode.getSelectors(), is(selectors("someTable"))); assertThat(criteriaNode.getProperty(Property.SELECT_CRITERIA), is((Object)like(nodePath("someTable"), var(Subquery.VARIABLE_PREFIX + "1")))); PlanNode source = criteriaNode.getFirstChild(); assertSourceNode(source, "someTable", null, "column1", "column2", "column3"); assertThat(source.getChildCount(), is(0)); } @FixFor( "MODE-869" ) @Test public void shouldProducePlanWhenUsingSubqueryInSubquery() { // Define the schemata ... schemata = schemataBuilder.addTable("someTable", "column1", "column2", "column3") .addTable("otherTable", "columnA", "columnB").addTable("stillOther", "columnX", "columnY") .build(); // Define the innermost subquery command ... QueryCommand subquery2 = builder.select("columnY").from("stillOther").where().propertyValue("stillOther", "columnX") .isLessThan().cast(3).asLong().end().query(); builder = new QueryBuilder(typeSystem); // Define the outer subquery command ... QueryCommand subquery1 = builder.select("columnA").from("otherTable").where().propertyValue("otherTable", "columnB") .isEqualTo(subquery2).end().query(); builder = new QueryBuilder(typeSystem); // Define the query command (which uses the subquery) ... query = builder.selectStar().from("someTable").where().path("someTable").isLike(subquery1).end().query(); initQueryContext(); plan = planner.createPlan(queryContext, query); // print = true; print(plan); assertThat(problems.hasErrors(), is(false)); assertThat(problems.isEmpty(), is(true)); // The top node should be the dependent query ... assertThat(plan.getType(), is(Type.DEPENDENT_QUERY)); assertThat(plan.getChildCount(), is(2)); // The first child of the top node should be a dependent query ... PlanNode depQuery1 = plan.getFirstChild(); assertThat(depQuery1.getType(), is(PlanNode.Type.DEPENDENT_QUERY)); assertThat(depQuery1.getChildCount(), is(2)); // The first child should be the plan for the 2nd subquery (since it has to be executed first) ... PlanNode subqueryPlan2 = depQuery1.getFirstChild(); assertProjectNode(subqueryPlan2, "columnY"); assertThat(subqueryPlan2.getProperty(Property.VARIABLE_NAME, String.class), is(Subquery.VARIABLE_PREFIX + "2")); assertThat(subqueryPlan2.getChildCount(), is(1)); assertThat(subqueryPlan2.getSelectors(), is(selectors("stillOther"))); PlanNode criteriaNode2 = subqueryPlan2.getFirstChild(); assertThat(criteriaNode2.getType(), is(PlanNode.Type.SELECT)); assertThat(criteriaNode2.getChildCount(), is(1)); assertThat(criteriaNode2.getSelectors(), is(selectors("stillOther"))); assertThat(criteriaNode2.getProperty(Property.SELECT_CRITERIA), is((Object)lessThan(property("stillOther", "columnX"), literal(3L)))); PlanNode subquerySource2 = criteriaNode2.getFirstChild(); assertSourceNode(subquerySource2, "stillOther", null, "columnX", "columnY"); assertThat(subquerySource2.getChildCount(), is(0)); // The second child of the dependent query should be the plan for the subquery ... PlanNode subqueryPlan1 = depQuery1.getLastChild(); assertProjectNode(subqueryPlan1, "columnA"); assertThat(subqueryPlan1.getProperty(Property.VARIABLE_NAME, String.class), is(Subquery.VARIABLE_PREFIX + "1")); assertThat(subqueryPlan1.getChildCount(), is(1)); assertThat(subqueryPlan1.getSelectors(), is(selectors("otherTable"))); PlanNode criteriaNode1 = subqueryPlan1.getFirstChild(); assertThat(criteriaNode1.getType(), is(PlanNode.Type.SELECT)); assertThat(criteriaNode1.getChildCount(), is(1)); assertThat(criteriaNode1.getSelectors(), is(selectors("otherTable"))); assertThat(criteriaNode1.getProperty(Property.SELECT_CRITERIA), is((Object)equals(property("otherTable", "columnB"), var(Subquery.VARIABLE_PREFIX + "2")))); PlanNode subquerySource1 = criteriaNode1.getFirstChild(); assertSourceNode(subquerySource1, "otherTable", null, "columnA", "columnB"); assertThat(subquerySource1.getChildCount(), is(0)); // The second child of the top node should be the plan for the regular query ... PlanNode queryPlan = plan.getLastChild(); assertProjectNode(queryPlan, "column1", "column2", "column3"); assertThat(queryPlan.getType(), is(PlanNode.Type.PROJECT)); assertThat(queryPlan.getChildCount(), is(1)); assertThat(queryPlan.getSelectors(), is(selectors("someTable"))); PlanNode criteriaNode = queryPlan.getFirstChild(); assertThat(criteriaNode.getType(), is(PlanNode.Type.SELECT)); assertThat(criteriaNode.getChildCount(), is(1)); assertThat(criteriaNode.getSelectors(), is(selectors("someTable"))); assertThat(criteriaNode.getProperty(Property.SELECT_CRITERIA), is((Object)like(nodePath("someTable"), var(Subquery.VARIABLE_PREFIX + "1")))); PlanNode source = criteriaNode.getFirstChild(); assertSourceNode(source, "someTable", null, "column1", "column2", "column3"); assertThat(source.getChildCount(), is(0)); } @FixFor( "MODE-869" ) @Test public void shouldProducePlanWhenUsingTwoSubqueries() { // Define the schemata ... schemata = schemataBuilder.addTable("someTable", "column1", "column2", "column3") .addTable("otherTable", "columnA", "columnB").addTable("stillOther", "columnX", "columnY") .build(); // Define the first subquery command ... QueryCommand subquery1 = builder.select("columnA").from("otherTable").where().propertyValue("otherTable", "columnB") .isEqualTo("winner").end().query(); builder = new QueryBuilder(typeSystem); // Define the second subquery command ... QueryCommand subquery2 = builder.select("columnY").from("stillOther").where().propertyValue("stillOther", "columnX") .isLessThan().cast(3).asLong().end().query(); builder = new QueryBuilder(typeSystem); // Define the query command (which uses the subquery) ... query = builder.selectStar().from("someTable").where().path("someTable").isLike(subquery2).and() .propertyValue("someTable", "column3").isInSubquery(subquery1).end().query(); initQueryContext(); plan = planner.createPlan(queryContext, query); // print = true; print(plan); assertThat(problems.hasErrors(), is(false)); assertThat(problems.isEmpty(), is(true)); // The top node should be the dependent query ... assertThat(plan.getType(), is(Type.DEPENDENT_QUERY)); assertThat(plan.getChildCount(), is(2)); // The first child of the top node should be the plan for subquery1 ... PlanNode subqueryPlan1 = plan.getFirstChild(); assertProjectNode(subqueryPlan1, "columnA"); assertThat(subqueryPlan1.getProperty(Property.VARIABLE_NAME, String.class), is(Subquery.VARIABLE_PREFIX + "1")); assertThat(subqueryPlan1.getChildCount(), is(1)); assertThat(subqueryPlan1.getSelectors(), is(selectors("otherTable"))); PlanNode criteriaNode1 = subqueryPlan1.getFirstChild(); assertThat(criteriaNode1.getType(), is(PlanNode.Type.SELECT)); assertThat(criteriaNode1.getChildCount(), is(1)); assertThat(criteriaNode1.getSelectors(), is(selectors("otherTable"))); assertThat(criteriaNode1.getProperty(Property.SELECT_CRITERIA), is((Object)equals(property("otherTable", "columnB"), literal("winner")))); PlanNode subquerySource1 = criteriaNode1.getFirstChild(); assertSourceNode(subquerySource1, "otherTable", null, "columnA", "columnB"); assertThat(subquerySource1.getChildCount(), is(0)); // The second child of the top node should be a dependent query ... PlanNode depQuery2 = plan.getLastChild(); assertThat(depQuery2.getType(), is(PlanNode.Type.DEPENDENT_QUERY)); assertThat(depQuery2.getChildCount(), is(2)); // The first child of the second dependent should be the plan for the 2nd subquery (since it has to be executed first) ... PlanNode subqueryPlan2 = depQuery2.getFirstChild(); assertProjectNode(subqueryPlan2, "columnY"); assertThat(subqueryPlan2.getProperty(Property.VARIABLE_NAME, String.class), is(Subquery.VARIABLE_PREFIX + "2")); assertThat(subqueryPlan2.getChildCount(), is(1)); assertThat(subqueryPlan2.getSelectors(), is(selectors("stillOther"))); PlanNode criteriaNode2 = subqueryPlan2.getFirstChild(); assertThat(criteriaNode2.getType(), is(PlanNode.Type.SELECT)); assertThat(criteriaNode2.getChildCount(), is(1)); assertThat(criteriaNode2.getSelectors(), is(selectors("stillOther"))); assertThat(criteriaNode2.getProperty(Property.SELECT_CRITERIA), is((Object)lessThan(property("stillOther", "columnX"), literal(3L)))); PlanNode subquerySource2 = criteriaNode2.getFirstChild(); assertSourceNode(subquerySource2, "stillOther", null, "columnX", "columnY"); assertThat(subquerySource2.getChildCount(), is(0)); // The second child of the second dependent node should be the plan for the regular query ... PlanNode queryPlan = depQuery2.getLastChild(); assertProjectNode(queryPlan, "column1", "column2", "column3"); assertThat(queryPlan.getType(), is(PlanNode.Type.PROJECT)); assertThat(queryPlan.getChildCount(), is(1)); assertThat(queryPlan.getSelectors(), is(selectors("someTable"))); PlanNode criteriaNode3 = queryPlan.getFirstChild(); assertThat(criteriaNode3.getType(), is(PlanNode.Type.SELECT)); assertThat(criteriaNode3.getChildCount(), is(1)); assertThat(criteriaNode3.getSelectors(), is(selectors("someTable"))); assertThat(criteriaNode3.getProperty(Property.SELECT_CRITERIA), is((Object)like(nodePath("someTable"), var(Subquery.VARIABLE_PREFIX + "2")))); PlanNode criteriaNode4 = criteriaNode3.getFirstChild(); assertThat(criteriaNode4.getProperty(Property.SELECT_CRITERIA), is((Object)in(property("someTable", "column3"), var(Subquery.VARIABLE_PREFIX + "1")))); PlanNode source = criteriaNode4.getFirstChild(); assertSourceNode(source, "someTable", null, "column1", "column2", "column3"); assertThat(source.getChildCount(), is(0)); } protected NodePath nodePath( String selectorName ) { return nodePath(selector(selectorName)); } protected NodePath nodePath( SelectorName selectorName ) { return new NodePath(selectorName); } protected PropertyValue property( String selectorName, String columnName ) { return property(selector(selectorName), columnName); } protected PropertyValue property( SelectorName selectorName, String columnName ) { return new PropertyValue(selectorName, columnName); } protected BindVariableName var( String variableName ) { return new BindVariableName(variableName); } protected Literal literal( Object value ) { return new Literal(value); } protected And and( Constraint left, Constraint right ) { return new And(left, right); } protected Comparison like( DynamicOperand left, StaticOperand right ) { return new Comparison(left, Operator.LIKE, right); } protected Comparison lessThan( DynamicOperand left, StaticOperand right ) { return new Comparison(left, Operator.LESS_THAN, right); } protected Comparison equals( DynamicOperand left, StaticOperand right ) { return new Comparison(left, Operator.EQUAL_TO, right); } protected SetCriteria in( DynamicOperand left, StaticOperand... right ) { return new SetCriteria(left, right); } }