/*
* 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.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.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.JoinType;
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 PushSelectCriteriaTest extends AbstractQueryTest {
private PushSelectCriteria rule;
private QueryContext context;
@Before
public void beforeEach() {
context = new QueryContext(new ExecutionContext(), mock(RepositoryCache.class), Collections.singleton("workspace"),
mock(Schemata.class), mock(RepositoryIndexes.class), mock(NodeTypes.class),
mock(BufferManager.class));
rule = PushSelectCriteria.INSTANCE;
}
/**
* Before:
*
* <pre>
* ...
* |
* PROJECT with the list of columns being SELECTed
* |
* SELECT1
* | One or more SELECT plan nodes that each have
* SELECT2 a single non-join constraint that are then all AND-ed
* | together
* SELECTn
* |
* ACCESS
* |
* SOURCE
* </pre>
*
* And after:
*
* <pre>
* ...
* |
* PROJECT with the list of columns being SELECTed
* |
* ACCESS
* |
* SELECT1
* | One or more SELECT plan nodes that each have
* SELECT2 a single non-join constraint that are then all AND-ed
* | together
* SELECTn
* |
* SOURCE
* </pre>
*/
@Test
public void shouldPushDownAllSelectNodesThatApplyToSelectorBelowAccessNodeButAboveSourceNodeUsingSameSelector() {
// Each of the PROJECT, SELECT, and SELECT nodes must have the names of the selectors that they apply to ...
PlanNode project = new PlanNode(Type.PROJECT, selector("Selector1"));
PlanNode select1 = new PlanNode(Type.SELECT, project, selector("Selector1"));
PlanNode select2 = new PlanNode(Type.SELECT, select1, selector("Selector1"));
PlanNode select3 = new PlanNode(Type.SELECT, select2, selector("Selector1"));
PlanNode select4 = new PlanNode(Type.SELECT, select3, selector("Selector1"));
PlanNode access = new PlanNode(Type.ACCESS, select4, selector("Selector1"));
PlanNode source = new PlanNode(Type.SOURCE, access, selector("Selector1"));
// Execute the rule ...
PlanNode result = rule.execute(context, project, new LinkedList<OptimizerRule>());
assertThat(result, is(sameInstance(project)));
assertChildren(project, access);
assertChildren(access, select1);
assertChildren(select1, select2);
assertChildren(select2, select3);
assertChildren(select3, select4);
assertChildren(select4, source);
assertChildren(source);
}
@Test
public void shouldNotPushDownSelectNodesThatUseDifferentSelectorNamesThanSourceNode() {
// Each of the PROJECT, SELECT, and SELECT nodes must have the names of the selectors that they apply to ...
PlanNode project = new PlanNode(Type.PROJECT, selector("Selector1"));
PlanNode select1 = new PlanNode(Type.SELECT, project, selector("Selector2"));
PlanNode select2 = new PlanNode(Type.SELECT, select1, selector("Selector1"));
PlanNode select3 = new PlanNode(Type.SELECT, select2, selector("Selector2"));
PlanNode select4 = new PlanNode(Type.SELECT, select3, selector("Selector1"));
PlanNode access = new PlanNode(Type.ACCESS, select4, selector("Selector1"));
PlanNode source = new PlanNode(Type.SOURCE, access, selector("Selector1"));
// Execute the rule ...
PlanNode result = rule.execute(context, project, new LinkedList<OptimizerRule>());
assertThat(result, is(sameInstance(project)));
assertChildren(project, select1);
assertChildren(select1, select3);
assertChildren(select3, access);
assertChildren(access, select2);
assertChildren(select2, select4);
assertChildren(select4, source);
assertChildren(source);
}
/**
* Before:
*
* <pre>
* ...
* |
* PROJECT ('s1','s2') with the list of columns being SELECTed (from 's1' and 's2' selectors)
* |
* SELECT1 ('s1')
* | One or more SELECT plan nodes that each have
* SELECT2 ('s2') a single non-join constraint that are then all AND-ed
* | together, and that each have the selector(s) they apply to
* SELECT3 ('s1','s2')
* |
* SELECT4 ('s1')
* |
* JOIN ('s1','s2')
* / \
* / \
* ACCESS ACCESS
* ('s1') ('s2')
* | |
* SOURCE SOURCE
* ('s1') ('s2')
* </pre>
*
* And after:
*
* <pre>
* ...
* |
* PROJECT ('s1','s2') with the list of columns being SELECTed (from 's1' and 's2' selectors)
* |
* SELECT3 ('s1','s2') Any SELECT plan nodes that apply to multiple selectors are left above
* | the ACCESS nodes.
* JOIN ('s1','s2')
* / \
* / \
* ACCESS ACCESS
* ('s1') ('s2')
* | |
* SELECT1 SELECT2
* ('s1') ('s2')
* | |
* SELECT4 SOURCE
* ('s1') ('s2')
* |
* SOURCE
* ('s1')
* </pre>
*/
@Test
public void shouldPushDownAllSelectNodesThatApplyToOneSelectorToBelowAccessNodeForThatSelector() {
// Each of the PROJECT, SELECT, and SELECT nodes must have the names of the selectors that they apply to ...
PlanNode project = new PlanNode(Type.PROJECT, selector("Selector1"), selector("Selector2"));
PlanNode select1 = new PlanNode(Type.SELECT, project, selector("Selector1"));
PlanNode select2 = new PlanNode(Type.SELECT, select1, selector("Selector2"));
PlanNode select3 = new PlanNode(Type.SELECT, select2, selector("Selector1"), selector("Selector2"));
PlanNode select4 = new PlanNode(Type.SELECT, select3, selector("Selector1"));
PlanNode join = new PlanNode(Type.JOIN, select4, selector("Selector1"), selector("Selector2"));
PlanNode s1Access = new PlanNode(Type.ACCESS, join, selector("Selector1"));
PlanNode s1Source = new PlanNode(Type.SOURCE, s1Access, selector("Selector1"));
PlanNode s2Access = new PlanNode(Type.ACCESS, join, selector("Selector2"));
PlanNode s2Source = new PlanNode(Type.SOURCE, s2Access, selector("Selector2"));
// Set the join type ...
join.setProperty(Property.JOIN_TYPE, JoinType.INNER);
// Execute the rule ...
PlanNode result = rule.execute(context, project, new LinkedList<OptimizerRule>());
// System.out.println(result);
assertThat(result, is(sameInstance(project)));
assertChildren(project, select3);
assertChildren(select3, join);
assertChildren(join, s1Access, s2Access);
assertChildren(s1Access, select1);
assertChildren(select1, select4);
assertChildren(select4, s1Source);
assertChildren(s2Access, select2);
assertChildren(select2, s2Source);
assertChildren(s2Source);
assertChildren(s1Source);
}
}