/*
* 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.optimize;
import static org.hamcrest.core.Is.is;
import static org.hamcrest.core.IsNull.nullValue;
import static org.hamcrest.core.IsSame.sameInstance;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.mock;
import java.util.Collections;
import java.util.LinkedList;
import org.junit.Before;
import org.junit.Test;
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.AbstractQueryTest;
import org.modeshape.jcr.query.BufferManager;
import org.modeshape.jcr.query.QueryContext;
import org.modeshape.jcr.query.model.Between;
import org.modeshape.jcr.query.model.Comparison;
import org.modeshape.jcr.query.model.Literal;
import org.modeshape.jcr.query.model.PropertyValue;
import org.modeshape.jcr.query.plan.PlanNode;
import org.modeshape.jcr.query.plan.PlanNode.Property;
import org.modeshape.jcr.query.plan.PlanNode.Type;
import org.modeshape.jcr.query.validate.Schemata;
/**
*
*/
public class RewriteAsRangeCriteriaTest extends AbstractQueryTest {
private RewriteAsRangeCriteria rule;
private LinkedList<OptimizerRule> rules;
private QueryContext context;
private boolean print = false;
@Before
public void beforeEach() {
rule = RewriteAsRangeCriteria.INSTANCE;
rules = new LinkedList<OptimizerRule>();
rules.add(rule);
context = new QueryContext(new ExecutionContext(), mock(RepositoryCache.class), Collections.singleton("workspace"),
mock(Schemata.class), mock(RepositoryIndexes.class), mock(NodeTypes.class),
mock(BufferManager.class));
print = false;
}
protected void print( PlanNode node ) {
if (print) System.out.println(node);
}
/**
* Before:
*
* <pre>
* Access [t1]
* Project [t1]
* Select [t1] <SELECT_CRITERIA=t1.c2 = 100>
* Select [t1] <SELECT_CRITERIA=t1.c1 < 3>
* Select [t1] <SELECT_CRITERIA=t1.c1 > 1>
* Source [t1] <SOURCE_NAME=t1>
* </pre>
*
* And after:
*
* <pre>
* Access [t1]
* Project [t1]
* Select [t1] <SELECT_CRITERIA=t1.c1 BETWEEN 1 EXCLUSIVE AND 3 EXCLUSIVE>
* Select [t1] <SELECT_CRITERIA=t1.c2 = 100>
* Source [t1] <SOURCE_NAME=t1>
* </pre>
*/
@Test
public void shouldReplaceComparisonsSpecifyingExclusiveRangeWithBetweenConstraint() {
// Each of the PROJECT, SELECT, and SELECT nodes must have the names of the selectors that they apply to ...
PlanNode access = new PlanNode(Type.ACCESS, selector("t1"));
PlanNode project = new PlanNode(Type.PROJECT, access, selector("t1"));
PlanNode select1 = new PlanNode(Type.SELECT, project, selector("t1"));
PlanNode select2 = new PlanNode(Type.SELECT, select1, selector("t1"));
PlanNode select3 = new PlanNode(Type.SELECT, select2, selector("t1"));
PlanNode source = new PlanNode(Type.SOURCE, select3, selector("t1"));
source.setProperty(Property.SOURCE_NAME, selector("t1"));
select1.setProperty(Property.SELECT_CRITERIA, new Comparison(new PropertyValue(selector("t1"), "c2"), Operator.EQUAL_TO,
new Literal(100L)));
select2.setProperty(Property.SELECT_CRITERIA, new Comparison(new PropertyValue(selector("t1"), "c1"), Operator.LESS_THAN,
new Literal(3L)));
select3.setProperty(Property.SELECT_CRITERIA, new Comparison(new PropertyValue(selector("t1"), "c1"),
Operator.GREATER_THAN, new Literal(1L)));
// Execute the rule ...
print(access);
PlanNode result = executeRules(access);
print(result);
// Compare results ...
assertThat(result, is(sameInstance(access)));
assertChildren(access, project);
PlanNode newSelect = project.getFirstChild();
assertThat(newSelect.getType(), is(Type.SELECT));
assertThat(newSelect.getSelectors(), is(access.getSelectors()));
assertThat(newSelect.getParent(), is(sameInstance(project)));
Between between = newSelect.getProperty(Property.SELECT_CRITERIA, Between.class);
assertThat(between.getOperand(), is(select2.getProperty(Property.SELECT_CRITERIA, Comparison.class).getOperand1()));
assertThat(between.getLowerBound(), is(select3.getProperty(Property.SELECT_CRITERIA, Comparison.class).getOperand2()));
assertThat(between.getUpperBound(), is(select2.getProperty(Property.SELECT_CRITERIA, Comparison.class).getOperand2()));
assertThat(between.isLowerBoundIncluded(), is(false));
assertThat(between.isUpperBoundIncluded(), is(false));
assertChildren(newSelect, select1);
assertChildren(select1, source);
}
/**
* Before:
*
* <pre>
* Access [t1]
* Project [t1]
* Select [t1] <SELECT_CRITERIA=t1.c2 = 100>
* Select [t1] <SELECT_CRITERIA=t1.c1 <= 3>
* Select [t1] <SELECT_CRITERIA=t1.c1 >= 1>
* Source [t1] <SOURCE_NAME=t1>
* </pre>
*
* And after:
*
* <pre>
* Access [t1]
* Project [t1]
* Select [t1] <SELECT_CRITERIA=t1.c1 BETWEEN 1 AND 3>
* Select [t1] <SELECT_CRITERIA=t1.c2 = 100>
* Source [t1] <SOURCE_NAME=t1>
* </pre>
*/
@Test
public void shouldReplaceComparisonsSpecifyingInclusiveRangeWithBetweenConstraint() {
// Each of the PROJECT, SELECT, and SELECT nodes must have the names of the selectors that they apply to ...
PlanNode access = new PlanNode(Type.ACCESS, selector("t1"));
PlanNode project = new PlanNode(Type.PROJECT, access, selector("t1"));
PlanNode select1 = new PlanNode(Type.SELECT, project, selector("t1"));
PlanNode select2 = new PlanNode(Type.SELECT, select1, selector("t1"));
PlanNode select3 = new PlanNode(Type.SELECT, select2, selector("t1"));
PlanNode source = new PlanNode(Type.SOURCE, select3, selector("t1"));
source.setProperty(Property.SOURCE_NAME, selector("t1"));
select1.setProperty(Property.SELECT_CRITERIA, new Comparison(new PropertyValue(selector("t1"), "c2"), Operator.EQUAL_TO,
new Literal(100L)));
select2.setProperty(Property.SELECT_CRITERIA, new Comparison(new PropertyValue(selector("t1"), "c1"),
Operator.LESS_THAN_OR_EQUAL_TO, new Literal(3L)));
select3.setProperty(Property.SELECT_CRITERIA, new Comparison(new PropertyValue(selector("t1"), "c1"),
Operator.GREATER_THAN_OR_EQUAL_TO, new Literal(1L)));
// Execute the rule ...
print(access);
PlanNode result = executeRules(access);
print(result);
// Compare results ...
assertThat(result, is(sameInstance(access)));
assertChildren(access, project);
PlanNode newSelect = project.getFirstChild();
assertThat(newSelect.getType(), is(Type.SELECT));
assertThat(newSelect.getSelectors(), is(access.getSelectors()));
assertThat(newSelect.getParent(), is(sameInstance(project)));
Between between = newSelect.getProperty(Property.SELECT_CRITERIA, Between.class);
assertThat(between.getOperand(), is(select2.getProperty(Property.SELECT_CRITERIA, Comparison.class).getOperand1()));
assertThat(between.getLowerBound(), is(select3.getProperty(Property.SELECT_CRITERIA, Comparison.class).getOperand2()));
assertThat(between.getUpperBound(), is(select2.getProperty(Property.SELECT_CRITERIA, Comparison.class).getOperand2()));
assertThat(between.isLowerBoundIncluded(), is(true));
assertThat(between.isUpperBoundIncluded(), is(true));
assertChildren(newSelect, select1);
assertChildren(select1, source);
}
/**
* Before:
*
* <pre>
* Access [t1]
* Project [t1]
* Select [t1] <SELECT_CRITERIA=t1.c2 = 100>
* Select [t1] <SELECT_CRITERIA=t1.c1 > 3>
* Select [t1] <SELECT_CRITERIA=t1.c1 < 1>
* Source [t1] <SOURCE_NAME=t1>
* </pre>
*
* And after:
*
* <pre>
* Access [t1] <ACCESS_NO_RESULTS=true>
* Project [t1]
* Select [t1] <SELECT_CRITERIA=t1.c2 = 100>
* Select [t1] <SELECT_CRITERIA=t1.c1 > 3>
* Select [t1] <SELECT_CRITERIA=t1.c1 < 1>
* Source [t1] <SOURCE_NAME=t1>
* </pre>
*/
@Test
public void shouldReplaceComparisonsSpecifyingExclusiveRangeWithNotBetweenConstraint() {
// Each of the PROJECT, SELECT, and SELECT nodes must have the names of the selectors that they apply to ...
PlanNode access = new PlanNode(Type.ACCESS, selector("t1"));
PlanNode project = new PlanNode(Type.PROJECT, access, selector("t1"));
PlanNode select1 = new PlanNode(Type.SELECT, project, selector("t1"));
PlanNode select2 = new PlanNode(Type.SELECT, select1, selector("t1"));
PlanNode select3 = new PlanNode(Type.SELECT, select2, selector("t1"));
PlanNode source = new PlanNode(Type.SOURCE, select3, selector("t1"));
source.setProperty(Property.SOURCE_NAME, selector("t1"));
select1.setProperty(Property.SELECT_CRITERIA, new Comparison(new PropertyValue(selector("t1"), "c2"), Operator.EQUAL_TO,
new Literal(100L)));
select2.setProperty(Property.SELECT_CRITERIA, new Comparison(new PropertyValue(selector("t1"), "c1"),
Operator.GREATER_THAN, new Literal(3L)));
select3.setProperty(Property.SELECT_CRITERIA, new Comparison(new PropertyValue(selector("t1"), "c1"), Operator.LESS_THAN,
new Literal(1L)));
// Execute the rule ...
print(access);
PlanNode result = executeRules(access);
print(result);
// Compare results ...
assertThat(result, is(sameInstance(access)));
assertChildren(access, project);
assertThat(access.getProperty(Property.ACCESS_NO_RESULTS, Boolean.class), is(true));
}
/**
* Before:
*
* <pre>
* Access [t1]
* Project [t1]
* Select [t1] <SELECT_CRITERIA=t1.c2 = 100>
* Select [t1] <SELECT_CRITERIA=t1.c1 >= 3>
* Select [t1] <SELECT_CRITERIA=t1.c1 <= 1>
* Source [t1] <SOURCE_NAME=t1>
* </pre>
*
* And after:
*
* <pre>
* Access [t1] <ACCESS_NO_RESULTS=true>
* Project [t1]
* Select [t1] <SELECT_CRITERIA=t1.c2 = 100>
* Select [t1] <SELECT_CRITERIA=t1.c1 >= 3>
* Select [t1] <SELECT_CRITERIA=t1.c1 <= 1>
* Source [t1] <SOURCE_NAME=t1>
* </pre>
*/
@Test
public void shouldReplaceComparisonsSpecifyingInclusiveRangeWithNotBetweenConstraint() {
// Each of the PROJECT, SELECT, and SELECT nodes must have the names of the selectors that they apply to ...
PlanNode access = new PlanNode(Type.ACCESS, selector("t1"));
PlanNode project = new PlanNode(Type.PROJECT, access, selector("t1"));
PlanNode select1 = new PlanNode(Type.SELECT, project, selector("t1"));
PlanNode select2 = new PlanNode(Type.SELECT, select1, selector("t1"));
PlanNode select3 = new PlanNode(Type.SELECT, select2, selector("t1"));
PlanNode source = new PlanNode(Type.SOURCE, select3, selector("t1"));
source.setProperty(Property.SOURCE_NAME, selector("t1"));
select1.setProperty(Property.SELECT_CRITERIA, new Comparison(new PropertyValue(selector("t1"), "c2"), Operator.EQUAL_TO,
new Literal(100L)));
select2.setProperty(Property.SELECT_CRITERIA, new Comparison(new PropertyValue(selector("t1"), "c1"),
Operator.GREATER_THAN_OR_EQUAL_TO, new Literal(3L)));
select3.setProperty(Property.SELECT_CRITERIA, new Comparison(new PropertyValue(selector("t1"), "c1"),
Operator.LESS_THAN_OR_EQUAL_TO, new Literal(1L)));
// Execute the rule ...
print(access);
PlanNode result = executeRules(access);
print(result);
// Compare results ...
assertThat(result, is(sameInstance(access)));
assertChildren(access, project);
assertThat(access.getProperty(Property.ACCESS_NO_RESULTS, Boolean.class), is(true));
}
/**
* Before:
*
* <pre>
* Access [t1]
* Project [t1]
* Select [t1] <SELECT_CRITERIA=t1.c2 = 100>
* Select [t1] <SELECT_CRITERIA=t1.c1 <= 3>
* Select [t1] <SELECT_CRITERIA=t1.c1 >= 3>
* Source [t1] <SOURCE_NAME=t1>
* </pre>
*
* And after:
*
* <pre>
* Access [t1]
* Project [t1]
* Select [t1] <SELECT_CRITERIA=t1.c1 = 3>
* Select [t1] <SELECT_CRITERIA=t1.c2 = 100>
* Source [t1] <SOURCE_NAME=t1>
* </pre>
*/
@Test
public void shouldReplaceComparisonsSpecifyingInclusiveRangeWithOverlappingBoundaryEqualityComparison() {
// Each of the PROJECT, SELECT, and SELECT nodes must have the names of the selectors that they apply to ...
PlanNode access = new PlanNode(Type.ACCESS, selector("t1"));
PlanNode project = new PlanNode(Type.PROJECT, access, selector("t1"));
PlanNode select1 = new PlanNode(Type.SELECT, project, selector("t1"));
PlanNode select2 = new PlanNode(Type.SELECT, select1, selector("t1"));
PlanNode select3 = new PlanNode(Type.SELECT, select2, selector("t1"));
PlanNode source = new PlanNode(Type.SOURCE, select3, selector("t1"));
source.setProperty(Property.SOURCE_NAME, selector("t1"));
select1.setProperty(Property.SELECT_CRITERIA, new Comparison(new PropertyValue(selector("t1"), "c2"), Operator.EQUAL_TO,
new Literal(100L)));
select2.setProperty(Property.SELECT_CRITERIA, new Comparison(new PropertyValue(selector("t1"), "c1"),
Operator.LESS_THAN_OR_EQUAL_TO, new Literal(3L)));
select3.setProperty(Property.SELECT_CRITERIA, new Comparison(new PropertyValue(selector("t1"), "c1"),
Operator.GREATER_THAN_OR_EQUAL_TO, new Literal(3L)));
// Execute the rule ...
print(access);
PlanNode result = executeRules(access);
print(result);
// Compare results ...
assertThat(result, is(sameInstance(access)));
assertThat(access.getProperty(Property.ACCESS_NO_RESULTS, Boolean.class), is(nullValue()));
assertChildren(access, project);
PlanNode newSelect = project.getFirstChild();
assertThat(newSelect.getType(), is(Type.SELECT));
assertThat(newSelect.getSelectors(), is(access.getSelectors()));
assertThat(newSelect.getParent(), is(sameInstance(project)));
Comparison equality = newSelect.getProperty(Property.SELECT_CRITERIA, Comparison.class);
assertThat(equality.getOperand1(), is(select2.getProperty(Property.SELECT_CRITERIA, Comparison.class).getOperand1()));
assertThat(equality.operator(), is(Operator.EQUAL_TO));
assertThat(equality.getOperand2(), is(select2.getProperty(Property.SELECT_CRITERIA, Comparison.class).getOperand2()));
assertChildren(newSelect, select1);
assertChildren(select1, source);
}
/**
* Before:
*
* <pre>
* Access [t1]
* Project [t1]
* Select [t1] <SELECT_CRITERIA=t1.c2 = 100>
* Select [t1] <SELECT_CRITERIA=t1.c1 < 3>
* Select [t1] <SELECT_CRITERIA=t1.c1 > 3>
* Source [t1] <SOURCE_NAME=t1>
* </pre>
*
* And after:
*
* <pre>
* Access [t1] <ACCESS_NO_RESULTS=true>
* Project [t1]
* Select [t1] <SELECT_CRITERIA=t1.c2 = 100>
* Select [t1] <SELECT_CRITERIA=t1.c1 < 3>
* Select [t1] <SELECT_CRITERIA=t1.c1 > 3>
* Source [t1] <SOURCE_NAME=t1>
* </pre>
*/
@Test
public void shouldMarkAsHavingNoResultsWhenComparisonsSpecifyRangeWithNonOverlappingBoundary() {
// Each of the PROJECT, SELECT, and SELECT nodes must have the names of the selectors that they apply to ...
PlanNode access = new PlanNode(Type.ACCESS, selector("t1"));
PlanNode project = new PlanNode(Type.PROJECT, access, selector("t1"));
PlanNode select1 = new PlanNode(Type.SELECT, project, selector("t1"));
PlanNode select2 = new PlanNode(Type.SELECT, select1, selector("t1"));
PlanNode select3 = new PlanNode(Type.SELECT, select2, selector("t1"));
PlanNode source = new PlanNode(Type.SOURCE, select3, selector("t1"));
source.setProperty(Property.SOURCE_NAME, selector("t1"));
select1.setProperty(Property.SELECT_CRITERIA, new Comparison(new PropertyValue(selector("t1"), "c2"), Operator.EQUAL_TO,
new Literal(100L)));
select2.setProperty(Property.SELECT_CRITERIA, new Comparison(new PropertyValue(selector("t1"), "c1"), Operator.LESS_THAN,
new Literal(3L)));
select3.setProperty(Property.SELECT_CRITERIA, new Comparison(new PropertyValue(selector("t1"), "c1"),
Operator.GREATER_THAN, new Literal(3L)));
// Execute the rule ...
print(access);
PlanNode result = executeRules(access);
print(result);
// Compare results ...
assertThat(result, is(sameInstance(access)));
assertChildren(access, project);
assertThat(access.getProperty(Property.ACCESS_NO_RESULTS, Boolean.class), is(true));
}
protected PlanNode executeRules( PlanNode node ) {
while (!rules.isEmpty()) {
OptimizerRule rule = rules.poll();
assert rule != null;
node = rule.execute(context, node, rules);
}
return node;
}
}