/*
* Copyright (C) 2010 eXo Platform SAS.
*
* This is free software; you can redistribute it and/or modify it
* under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this software; if not, write to the Free
* Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
* 02110-1301 USA, or see the FSF site: http://www.fsf.org.
*/
package org.xcmis.search.query.plan;
import static org.hamcrest.core.Is.is;
import static org.hamcrest.core.IsNull.notNullValue;
import static org.hamcrest.core.IsNull.nullValue;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.mock;
import org.junit.Before;
import org.junit.Test;
import org.xcmis.search.InvalidQueryException;
import org.xcmis.search.content.InMemorySchema;
import org.xcmis.search.content.Schema;
import org.xcmis.search.model.Query;
import org.xcmis.search.model.column.Column;
import org.xcmis.search.model.source.SelectorName;
import org.xcmis.search.query.QueryBuilder;
import org.xcmis.search.query.QueryExecutionContext;
import org.xcmis.search.query.QueryExecutionExceptions;
import org.xcmis.search.query.plan.QueryExecutionPlan.LimitExecutionPlan;
import org.xcmis.search.query.plan.QueryExecutionPlan.ProjectExecutionPlan;
import org.xcmis.search.query.plan.QueryExecutionPlan.SelectorExecutionPlan;
import org.xcmis.search.query.plan.QueryExecutionPlan.Type;
import org.xcmis.search.value.CastSystem;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
* Test simple planer.
*
*/
public class SimpleQueryPlanerTest
{
private SimplePlaner planner;
// private TypeSystem typeSystem;
private QueryBuilder builder;
private Query query;
private QueryExecutionPlan plan;
private QueryExecutionExceptions problems;
private Schema schema;
private InMemorySchema.Builder schemataBuilder;
private QueryExecutionContext queryContext;
private boolean print;
@Before
public void beforeEach()
{
planner = new SimplePlaner();
// typeSystem = new ExecutionContext().getValueFactories().getTypeSystem();
// hints = new PlanHints();
builder = new QueryBuilder(mock(CastSystem.class));
problems = new QueryExecutionExceptions();
schemataBuilder = InMemorySchema.createBuilder();
print = false;
}
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(QueryExecutionPlan executionPlan, String... columnNames)
{
assertThat(plan.getType(), is(Type.PROJECT));
ProjectExecutionPlan node = (ProjectExecutionPlan)executionPlan;
if (columnNames.length != 0)
{
assertThat(node.getColumns(), notNullValue());
}
List<Column> columns = node.getColumns();
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(QueryExecutionPlan executionPlan, String sourceName, String sourceAlias,
String... availableColumns)
{
assertThat(executionPlan.getType(), is(Type.SELECTOR));
SelectorExecutionPlan node = (SelectorExecutionPlan)executionPlan;
assertThat(node.getName(), is(selector(sourceName)));
if (sourceAlias != null)
{
assertThat(node.getAlias(), is(selector(sourceAlias)));
}
else
{
assertThat(node.getAlias(), nullValue());
}
Collection<Schema.Column> columns = node.getColumns();
assertThat(columns.size(), is(availableColumns.length));
int i = 0;
for (Schema.Column column : columns)
{
String expectedName = availableColumns[i++];
assertThat(column.getName(), is(expectedName));
}
}
@Test
public void testShouldProducePlanForSelectStarFromTable() throws InvalidQueryException
{
schema = schemataBuilder.addTable("my:mytype", "column1", "column2", "column3").build();
query = builder.selectStar().from("my:mytype").query();
queryContext = new QueryExecutionContext(schema, problems, null);
plan = planner.createPlan(queryContext, query);
assertThat(problems.hasProblems(), is(false));
assertProjectNode(plan.findPlanByType(Type.PROJECT), "column1", "column2", "column3");
assertThat(plan.getSize(), is(2));
assertSourceNode(plan.findPlanByType(Type.SELECTOR), "my:mytype", null, "column1", "column2", "column3");
}
@Test
public void testShouldProduceErrorWhenSelectingNonExistantTable() throws InvalidQueryException
{
schema = schemataBuilder.addTable("someTable", "column1", "column2", "column3").build();
query = builder.selectStar().from("otheTable").query();
queryContext = new QueryExecutionContext(schema, problems, null);
plan = planner.createPlan(queryContext, query);
assertThat(problems.hasProblems(), is(true));
}
@Test
public void testShouldProduceErrorWhenSelectingNonExistantColumnOnExistingTable() throws InvalidQueryException
{
schema = schemataBuilder.addTable("someTable", "column1", "column2", "column3").build();
query = builder.select("column1", "column4").from("someTable").query();
queryContext = new QueryExecutionContext(schema, problems, null);
plan = planner.createPlan(queryContext, query);
assertThat(problems.hasProblems(), is(true));
}
@Test
public void testShouldProducePlanWhenSelectingAllColumnsOnExistingTable() throws InvalidQueryException
{
schema = schemataBuilder.addTable("someTable", "column1", "column2", "column3").build();
query = builder.selectStar().from("someTable").query();
queryContext = new QueryExecutionContext(schema, problems, null);
plan = planner.createPlan(queryContext, query);
assertThat(problems.hasProblems(), is(false));
assertProjectNode(plan.findPlanByType(Type.PROJECT), "column1", "column2", "column3");
assertThat(plan.getSize(), is(2));
assertSourceNode(plan.findPlanByType(Type.SELECTOR), "someTable", null, "column1", "column2", "column3");
}
@Test
public void testShouldProducePlanWhenSelectingColumnsFromTableWithoutAlias() throws InvalidQueryException
{
schema = schemataBuilder.addTable("someTable", "column1", "column2", "column3").build();
query =
builder.select("column1", "column2").from("someTable").where().nodeName("someTable").isEqualTo("nodeTestName")
.end().query();
queryContext = new QueryExecutionContext(schema, problems, null);
plan = planner.createPlan(queryContext, query);
assertThat(problems.hasProblems(), is(false));
assertThat(plan.findPlanByType(Type.PROJECT), notNullValue());
QueryExecutionPlan executionStep = plan.findPlanByType(Type.PROJECT);
assertThat(executionStep.getType(), is(Type.PROJECT));
assertThat(executionStep.getSelectors(), is(selectors("someTable")));
}
@Test
public void testShouldProducePlanWhenSelectingColumnsFromTableWithAlias() throws InvalidQueryException
{
schema = schemataBuilder.addTable("test:someTable", "column1", "column2", "column3").build();
query =
builder.select("column1", "column2").from("test:someTable AS t1").where().nodeName("t1").isEqualTo(
"nodeTestName").end().query();
queryContext = new QueryExecutionContext(schema, problems, null);
plan = planner.createPlan(queryContext, query);
assertThat(problems.hasProblems(), is(false));
assertThat(plan.findPlanByType(Type.PROJECT), notNullValue());
QueryExecutionPlan executionStep = plan.findPlanByType(Type.PROJECT);
assertThat(executionStep.getSelectors(), is(selectors("t1")));
}
@Test
public void testShouldProducePlanWhenSelectingAllColumnsFromTableWithAlias() throws InvalidQueryException
{
schema = schemataBuilder.addTable("test:someTable", "column1", "column2", "column3").build();
query =
builder.selectStar().from("test:someTable AS t1").where().nodeName("t1").isEqualTo("node name").end().query();
queryContext = new QueryExecutionContext(schema, problems, null);
plan = planner.createPlan(queryContext, query);
assertThat(problems.hasProblems(), is(false));
assertThat(plan.findPlanByType(Type.PROJECT), notNullValue());
QueryExecutionPlan executionStep = plan.findPlanByType(Type.PROJECT);
assertThat(executionStep.getSelectors(), is(selectors("t1")));
}
@Test
public void testShouldProduceErrorWhenFullTextSearchingTableWithNoSearchableColumns() throws InvalidQueryException
{
schema = 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();
queryContext = new QueryExecutionContext(schema, problems, null);
plan = planner.createPlan(queryContext, query);
assertThat(problems.hasProblems(), is(false));
query = builder.select("column1", "column2").from("someTable").where().search("someTable", "term1").end().query();
queryContext = new QueryExecutionContext(schema, problems, null);
plan = planner.createPlan(queryContext, query);
assertThat(problems.hasProblems(), is(true));
}
@Test
public void testShouldProducePlanWhenFullTextSearchingTableWithAtLeastOneSearchableColumn()
throws InvalidQueryException
{
schema =
schemataBuilder.addTable("someTable", "column1", "column2", "column3").makeSearchable("someTable", "column1")
.build();
query = builder.select("column1", "column4").from("someTable").where().search("someTable", "term1").end().query();
queryContext = new QueryExecutionContext(schema, problems, null);
plan = planner.createPlan(queryContext, query);
assertThat(problems.hasProblems(), is(true));
}
@Test
public void testShouldProduceErrorWhenFullTextSearchingColumnThatIsNotSearchable() throws InvalidQueryException
{
schema = 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();
queryContext = new QueryExecutionContext(schema, problems, null);
plan = planner.createPlan(queryContext, query);
assertThat(problems.hasProblems(), is(false));
query =
builder.select("column1", "column2").from("someTable").where().search("someTable", "column2", "term1").end()
.query();
queryContext = new QueryExecutionContext(schema, problems, null);
plan = planner.createPlan(queryContext, query);
assertThat(problems.hasProblems(), is(true));
}
@Test
public void testShouldProducePlanWhenFullTextSearchingColumnThatIsSearchable() throws InvalidQueryException
{
schema =
schemataBuilder.addTable("someTable", "column1", "column2", "column3").makeSearchable("someTable", "column1")
.build();
query =
builder.select("column1", "column4").from("someTable").where().search("someTable", "column1", "term1").end()
.query();
queryContext = new QueryExecutionContext(schema, problems, null);
plan = planner.createPlan(queryContext, query);
assertThat(problems.hasProblems(), is(true));
}
@Test
public void testShouldProducePlanWhenOrderByClauseIsUsed() throws InvalidQueryException
{
schema =
schemataBuilder.addTable("test:someTable", "column1", "column2", "column3").makeSearchable("test:someTable",
"column1").build();
query =
builder.selectStar().from("test:someTable AS t1").where().search("t1", "column1", "term1").end().orderBy()
.ascending().propertyValue("t1", "column1").end().query();
queryContext = new QueryExecutionContext(schema, problems, null);
plan = planner.createPlan(queryContext, query);
assertThat(problems.hasProblems(), is(false));
assertThat(plan.findPlanByType(Type.SORT), notNullValue());
QueryExecutionPlan executionStep = plan.findPlanByType(Type.SORT);
assertThat(executionStep.getSelectors(), is(selectors("t1")));
//TODO ASC
}
@Test
public void testShouldProducePlanWhenOrderByClauseWithScoreIsUsed() throws InvalidQueryException
{
schema =
schemataBuilder.addTable("test:someTable", "column1", "column2", "column3").makeSearchable("test:someTable",
"column1").build();
query =
builder.selectStar().from("test:someTable AS t1").where().search("t1", "column1", "term1").end().orderBy()
.ascending().fullTextSearchScore("t1").end().query();
queryContext = new QueryExecutionContext(schema, problems, null);
plan = planner.createPlan(queryContext, query);
assertThat(problems.hasProblems(), is(false));
assertThat(plan.findPlanByType(Type.SORT), notNullValue());
QueryExecutionPlan executionStep = plan.findPlanByType(Type.SORT);
assertThat(executionStep.getSelectors(), is(selectors("t1")));
//TODO ASC
}
@Test
public void testShouldProducePlanWhenLimitClauseIsUsed() throws InvalidQueryException
{
schema =
schemataBuilder.addTable("test:someTable", "column1", "column2", "column3").makeSearchable("test:someTable",
"column1").build();
query =
builder.selectStar().from("test:someTable AS t1").where().search("t1", "column1", "term1").end().limit(10)
.query();
queryContext = new QueryExecutionContext(schema, problems, null);
plan = planner.createPlan(queryContext, query);
assertThat(problems.hasProblems(), is(false));
assertThat(plan.findPlanByType(Type.LIMIT), notNullValue());
LimitExecutionPlan executionStep = (LimitExecutionPlan)plan.findPlanByType(Type.LIMIT);
assertThat(executionStep.getLimit().getRowLimit(), is(10));
assertThat(executionStep.getLimit().getOffset(), is(0));
}
@Test
public void testShouldProducePlanWhenOffsetClauseIsUsed() throws InvalidQueryException
{
schema =
schemataBuilder.addTable("test:someTable", "column1", "column2", "column3").makeSearchable("test:someTable",
"column1").build();
query =
builder.selectStar().from("test:someTable AS t1").where().search("t1", "column1", "term1").end().limit(20)
.offset(10).query();
queryContext = new QueryExecutionContext(schema, problems, null);
plan = planner.createPlan(queryContext, query);
assertThat(problems.hasProblems(), is(false));
assertThat(plan.findPlanByType(Type.LIMIT), notNullValue());
LimitExecutionPlan executionStep = (LimitExecutionPlan)plan.findPlanByType(Type.LIMIT);
assertThat(executionStep.getLimit().getRowLimit(), is(20));
assertThat(executionStep.getLimit().getOffset(), is(10));
}
}