/*
* 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.content.interceptors;
import org.apache.commons.lang.NotImplementedException;
import org.apache.commons.lang.Validate;
import org.xcmis.search.content.command.InvocationContext;
import org.xcmis.search.content.command.query.ExecuteSelectorCommand;
import org.xcmis.search.content.command.query.ProcessQueryCommand;
import org.xcmis.search.model.Limit;
import org.xcmis.search.model.Query;
import org.xcmis.search.model.constraint.Constraint;
import org.xcmis.search.model.ordering.Ordering;
import org.xcmis.search.query.QueryExecutionContext;
import org.xcmis.search.query.QueryExecutionExceptions;
import org.xcmis.search.query.Statistics;
import org.xcmis.search.query.plan.Optimizer;
import org.xcmis.search.query.plan.QueryExecutionPlan;
import org.xcmis.search.query.plan.QueryExecutionPlaner;
import org.xcmis.search.query.plan.QueryExecutionPlan.JoinExecutionPlan;
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.SortExecutionPlan;
import org.xcmis.search.query.plan.QueryExecutionPlan.WhereExecutionPlan;
import org.xcmis.search.result.ScoredRow;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* A query engine that is able to execute formal queries expressed in the
* Abstract Query Model.
*/
public class QueryProcessorInterceptor extends CommandInterceptor
{
private final QueryExecutionPlaner planner;
private final Optimizer optimizer;
/**
* Create a new query engine given the {@link QueryExecutionPlaner planner},
* {@link Optimizer optimizer}, {@link QueryProcessor processor}.
*
* @param planner
* the planner that should be used to generate canonical query
* plans for the queries;
* @param optimizer
* the optimizer that should be used to optimize the canonical
* query plan
* @throws IllegalArgumentException
* if the processor reference is null
*/
public QueryProcessorInterceptor(QueryExecutionPlaner planner, Optimizer optimizer)
{
super();
this.planner = planner;
this.optimizer = optimizer;
}
/**
* @see org.xcmis.search.content.interceptors.AbstractVisitor#visitProcessQueryCommand(org.xcmis.search.content.command.InvocationContext,
* org.xcmis.search.content.command.query.ProcessQueryCommand)
*/
@Override
public Object visitProcessQueryCommand(InvocationContext ctx, ProcessQueryCommand command) throws Throwable
{
QueryExecutionExceptions executionExceptions = new QueryExecutionExceptions();
try
{
return execute(ctx, new QueryExecutionContext(ctx.getSchema(), executionExceptions, command
.getBindVariablesValues()), command.getQuery());
}
finally
{
if (executionExceptions.hasProblems())
{
throw executionExceptions.getTopException();
}
}
}
/**
* Execute the supplied query by planning, optimizing, and then processing it.
* @param ctx
*
* @param context the context in which the query should be executed
* @param query the query that is to be executed
* @return the query results; never null
* @throws IllegalArgumentException if the context or query references are null
*/
public List<ScoredRow> execute(InvocationContext ctx, QueryExecutionContext context, Query query)
{
Validate.notNull(context, "The context argument may not be null");
Validate.notNull(query, "The query argument may not be null");
// Create the plan ...
long start = System.currentTimeMillis();
QueryExecutionPlan executionPlan = planner.createPlan(context, query);
long duration = System.currentTimeMillis() - start;
Statistics stats = new Statistics(duration);
if (!context.getExecutionExceptions().hasProblems())
{
// Optimize the plan ...
start = System.currentTimeMillis();
QueryExecutionPlan optimizedPlan = optimizer.optimize(context, executionPlan);
duration = System.currentTimeMillis() - start;
stats = stats.withOptimizationTime(duration);
if (!context.getExecutionExceptions().hasProblems())
{
// Execute the plan ...
try
{
start = System.currentTimeMillis();
return execute(ctx, context, query, stats, optimizedPlan);
}
finally
{
duration = System.currentTimeMillis() - start;
stats = stats.withOptimizationTime(duration);
}
}
}
return Collections.emptyList();
}
/**
* Execute the supplied query by plan.
* @param ctx
* @param context
* @param query
* @param stats
* @param queryPlan
*/
private List<ScoredRow> execute(InvocationContext ctx, QueryExecutionContext context, Query query, Statistics stats,
QueryExecutionPlan queryPlan)
{
QueryExecuteableComponent component = createQueryExecuteableComponent(queryPlan);
return component.executeComponent(ctx, context);
};
/**
* Create component for execution.
* @param queryExecutionPlan
* @return
*/
private QueryExecuteableComponent createQueryExecuteableComponent(QueryExecutionPlan queryExecutionPlan)
{
LimitExecutionPlan limitPlan = null;
SortExecutionPlan sortPlan = null;
ProjectExecutionPlan projectPlan = null;
List<WhereExecutionPlan> constraintsPlan = new ArrayList<WhereExecutionPlan>();
QueryExecutionPlan nextPlan = queryExecutionPlan;
do
{
switch (nextPlan.getType())
{
case LIMIT :
limitPlan = (LimitExecutionPlan)nextPlan;
break;
case SORT :
sortPlan = (SortExecutionPlan)nextPlan;
break;
case PROJECT :
projectPlan = (ProjectExecutionPlan)nextPlan;
break;
case WHERE :
constraintsPlan.add((WhereExecutionPlan)nextPlan);
break;
case SELECTOR :
return new SelectorExecuteableComponent(this, ((SelectorExecutionPlan)nextPlan), projectPlan,
constraintsPlan, sortPlan, limitPlan);
case JOIN :
JoinExecutionPlan joinPlan = (JoinExecutionPlan)nextPlan;
QueryExecuteableComponent left = createQueryExecuteableComponent(joinPlan.getLeftPlan());
QueryExecuteableComponent right = createQueryExecuteableComponent(joinPlan.getRightPlan());
return new JoinExecutionComponent(this, joinPlan, left, right, projectPlan, constraintsPlan, sortPlan,
limitPlan);
default :
throw new NotImplementedException("Execution for plan " + queryExecutionPlan.getType().toString()
+ " not implemented");
}
nextPlan = nextPlan.next();
}
while (nextPlan != null);
return null;
}
/**
* Generic cluss of query execution
*
*/
private abstract class QueryExecuteableComponent
{
private final LimitExecutionPlan limitPlan;
private final SortExecutionPlan sortPlan;
private final ProjectExecutionPlan projectPlan;
private final List<WhereExecutionPlan> constraintsPlan;
private final CommandInterceptor interceptor;
/**
* @param projectPlan
* @param constraintsPlan
* @param sortPlan
* @param limitPlan
*/
public QueryExecuteableComponent(CommandInterceptor interceptor, ProjectExecutionPlan projectPlan,
List<WhereExecutionPlan> constraintsPlan, SortExecutionPlan sortPlan, LimitExecutionPlan limitPlan)
{
super();
this.interceptor = interceptor;
this.projectPlan = projectPlan;
this.constraintsPlan = constraintsPlan;
this.sortPlan = sortPlan;
this.limitPlan = limitPlan;
}
/**
* @return the interceptor
*/
public CommandInterceptor getInterceptor()
{
return interceptor;
}
/**
* @return the limitPlan
*/
public LimitExecutionPlan getLimitPlan()
{
return limitPlan;
}
/**
* @return the limit
*/
public Limit getLimit()
{
return limitPlan == null ? Limit.NONE : limitPlan.getLimit();
}
/**
* @return the sortPlan
*/
public SortExecutionPlan getSortPlan()
{
return sortPlan;
}
/**
* @return the sortPlan
*/
public List<Ordering> getOrder()
{
return sortPlan == null ? new ArrayList<Ordering>() : sortPlan.getOrderings();
}
/**
* @return the projectPlan
*/
public ProjectExecutionPlan getProjectPlan()
{
return projectPlan;
}
/**
* @return the constraintsPlan
*/
public List<WhereExecutionPlan> getConstraintsPlan()
{
return constraintsPlan;
}
/**
* @return the constraintsPlan
*/
public List<Constraint> getConstraints()
{
List<Constraint> constraints = new ArrayList<Constraint>(constraintsPlan.size());
for (WhereExecutionPlan constrain : constraintsPlan)
{
constraints.add(constrain.getConstraint());
}
return constraints;
}
public abstract List<ScoredRow> executeComponent(InvocationContext ctx, QueryExecutionContext context);
}
/**
* Execution component for source plan.
*
*/
private class SelectorExecuteableComponent extends QueryExecuteableComponent
{
private final SelectorExecutionPlan selectorExecutionPlan;
/**
* @param projectPlan
* @param constraintsPlan
* @param sortPlan
* @param limitPlan
*/
public SelectorExecuteableComponent(CommandInterceptor interceptor, SelectorExecutionPlan selectorExecutionPlan,
ProjectExecutionPlan projectPlan, List<WhereExecutionPlan> constraintsPlan, SortExecutionPlan sortPlan,
LimitExecutionPlan limitPlan)
{
super(interceptor, projectPlan, constraintsPlan, sortPlan, limitPlan);
this.selectorExecutionPlan = selectorExecutionPlan;
}
/**
* @return the sourceExecutionPlan
*/
public SelectorExecutionPlan getSelectorExecutionPlan()
{
return selectorExecutionPlan;
}
/**
*
* @see org.xcmis.search.content.interceptors.QueryProcessorInterceptor.QueryExecuteableComponent#executeComponent(org.xcmis.search.query.QueryExecutionContext)
*/
@Override
public List<ScoredRow> executeComponent(InvocationContext ctx, QueryExecutionContext context)
{
try
{
ExecuteSelectorCommand command =
new ExecuteSelectorCommand(selectorExecutionPlan.getName(), selectorExecutionPlan.getAlias(),
getConstraints(), getLimit(), getOrder(), context.getVariables());
return (List<ScoredRow>)getInterceptor().invokeNextInterceptor(ctx, command);
}
catch (Throwable e)
{
context.getExecutionExceptions().addException(e);
}
return null;
}
}
private class JoinExecutionComponent extends QueryExecuteableComponent
{
private final JoinExecutionPlan joinPlan;
/**
* @param projectPlan
* @param constraintsPlan
* @param sortPlan
* @param limitPlan
*/
public JoinExecutionComponent(CommandInterceptor interceptor, JoinExecutionPlan joinPlan,
QueryExecuteableComponent left, QueryExecuteableComponent right, ProjectExecutionPlan projectPlan,
List<WhereExecutionPlan> constraintsPlan, SortExecutionPlan sortPlan, LimitExecutionPlan limitPlan)
{
super(interceptor, projectPlan, constraintsPlan, sortPlan, limitPlan);
this.joinPlan = joinPlan;
}
/**
*
* @see org.xcmis.search.content.interceptors.QueryProcessorInterceptor.QueryExecuteableComponent#executeComponent(org.xcmis.search.query.QueryExecutionContext)
*/
@Override
public List<ScoredRow> executeComponent(InvocationContext ctx, QueryExecutionContext context)
{
throw new NotImplementedException("Method not implemented");
}
/**
* @return the joinPlan
*/
public JoinExecutionPlan getJoinPlan()
{
return joinPlan;
}
}
}