/*
* Copyright (c) 2011-2015 EPFL DATA Laboratory
* Copyright (c) 2014-2015 The Squall Collaboration (see NOTICE)
*
* All rights reserved.
*
* 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 ch.epfl.data.squall.api.sql.optimizers.index;
import java.util.List;
import java.util.Map;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.schema.Table;
import net.sf.jsqlparser.statement.select.SelectItem;
import ch.epfl.data.squall.api.sql.optimizers.Optimizer;
import ch.epfl.data.squall.api.sql.schema.Schema;
import ch.epfl.data.squall.api.sql.util.ParserUtil;
import ch.epfl.data.squall.api.sql.visitors.jsql.SQLVisitor;
import ch.epfl.data.squall.api.sql.visitors.squall.IndexSelectItemsVisitor;
import ch.epfl.data.squall.api.sql.visitors.squall.IndexWhereVisitor;
import ch.epfl.data.squall.components.Component;
import ch.epfl.data.squall.components.DataSourceComponent;
import ch.epfl.data.squall.components.OperatorComponent;
import ch.epfl.data.squall.expressions.ValueExpression;
import ch.epfl.data.squall.operators.AggregateOperator;
import ch.epfl.data.squall.operators.ProjectOperator;
import ch.epfl.data.squall.operators.SelectOperator;
import ch.epfl.data.squall.query_plans.QueryBuilder;
import ch.epfl.data.squall.utilities.DeepCopy;
/*
* Generate a query plan as it was parsed from the SQL.
* SELECT and WHERE clause are attached to the final component.
*/
public class IndexSimpleOptimizer implements Optimizer {
private final SQLVisitor _pq;
private final Schema _schema;
private final Map _map;
private IndexCompGen _cg;
private final IndexTranslator _it;
public IndexSimpleOptimizer(Map map) {
_map = map;
_pq = ParserUtil.parseQuery(map);
_schema = new Schema(map);
_it = new IndexTranslator(_schema, _pq.getTan());
}
private void attachSelectClause(List<AggregateOperator> aggOps,
List<ValueExpression> groupByVEs, Component affectedComponent) {
if (aggOps.isEmpty()) {
final ProjectOperator project = new ProjectOperator(groupByVEs);
affectedComponent.add(project);
} else if (aggOps.size() == 1) {
// all the others are group by
final AggregateOperator firstAgg = aggOps.get(0);
if (ParserUtil.isAllColumnRefs(groupByVEs)) {
// plain fields in select
final List<Integer> groupByColumns = ParserUtil
.extractColumnIndexes(groupByVEs);
firstAgg.setGroupByColumns(groupByColumns);
// Setting new level of components is necessary for correctness
// only for distinct in aggregates
// but it's certainly pleasant to have the final result grouped
// on nodes by group by columns.
final boolean newLevel = !(_it.isHashedBy(affectedComponent,
groupByColumns));
if (newLevel) {
affectedComponent.setOutputPartKey(groupByColumns);
OperatorComponent oc = new OperatorComponent(
affectedComponent,
ParserUtil.generateUniqueName("OPERATOR"))
.add(firstAgg);
_cg.getQueryBuilder().add(oc);
} else
affectedComponent.add(firstAgg);
} else {
// Sometimes groupByVEs contains other functions, so we have to
// use projections instead of simple groupBy
// always new level
if (affectedComponent.getHashExpressions() != null
&& !affectedComponent.getHashExpressions().isEmpty())
throw new RuntimeException(
"Too complex: cannot have hashExpression both for joinCondition and groupBy!");
// WARNING: groupByVEs cannot be used on two places: that's why
// we do deep copy
final ProjectOperator groupByProj = new ProjectOperator(
(List<ValueExpression>) DeepCopy.copy(groupByVEs));
if (!(groupByProj.getExpressions() == null || groupByProj
.getExpressions().isEmpty()))
firstAgg.setGroupByProjection(groupByProj);
// current component
affectedComponent
.setHashExpressions((List<ValueExpression>) DeepCopy
.copy(groupByVEs));
OperatorComponent oc = new OperatorComponent(affectedComponent,
ParserUtil.generateUniqueName("OPERATOR"))
.add(firstAgg);
_cg.getQueryBuilder().add(oc);
}
} else
throw new RuntimeException(
"For now only one aggregate function supported!");
}
private void attachWhereClause(SelectOperator select,
Component affectedComponent) {
affectedComponent.add(select);
}
@Override
public QueryBuilder generate() {
_cg = generateTableJoins();
// selectItems might add OperatorComponent, this is why it goes first
processSelectClause(_pq.getSelectItems());
processWhereClause(_pq.getWhereExpr());
ParserUtil.orderOperators(_cg.getQueryBuilder());
final RuleParallelismAssigner parAssign = new RuleParallelismAssigner(
_cg.getQueryBuilder(), _pq.getTan(), _schema, _map);
parAssign.assignPar();
return _cg.getQueryBuilder();
}
private IndexCompGen generateTableJoins() {
final List<Table> tableList = _pq.getTableList();
final IndexCompGen cg = new IndexCompGen(_schema, _pq, _map);
Component firstParent = cg.generateDataSource(ParserUtil
.getComponentName(tableList.get(0)));
// a special case
if (tableList.size() == 1)
return cg;
// This generates a lefty query plan.
for (int i = 0; i < tableList.size() - 1; i++) {
final DataSourceComponent secondParent = cg
.generateDataSource(ParserUtil.getComponentName(tableList
.get(i + 1)));
firstParent = cg.generateEquiJoin(firstParent, secondParent);
}
return cg;
}
private int processSelectClause(List<SelectItem> selectItems) {
final IndexSelectItemsVisitor selectVisitor = new IndexSelectItemsVisitor(
_cg.getQueryBuilder(), _schema, _pq.getTan(), _map);
for (final SelectItem elem : selectItems)
elem.accept(selectVisitor);
final List<AggregateOperator> aggOps = selectVisitor.getAggOps();
final List<ValueExpression> groupByVEs = selectVisitor.getGroupByVEs();
final Component affectedComponent = _cg.getQueryBuilder()
.getLastComponent();
attachSelectClause(aggOps, groupByVEs, affectedComponent);
return (aggOps.isEmpty() ? IndexSelectItemsVisitor.NON_AGG
: IndexSelectItemsVisitor.AGG);
}
private void processWhereClause(Expression whereExpr) {
// all the selection are performed on the last component
final Component affectedComponent = _cg.getQueryBuilder()
.getLastComponent();
final IndexWhereVisitor whereVisitor = new IndexWhereVisitor(
affectedComponent, _schema, _pq.getTan());
if (whereExpr != null) {
whereExpr.accept(whereVisitor);
attachWhereClause(whereVisitor.getSelectOperator(),
affectedComponent);
}
}
}