/*
* Copyright 2014-2015 the original author or authors
*
* 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 com.wplatform.ddal.command.dml;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import com.wplatform.ddal.command.CommandInterface;
import com.wplatform.ddal.command.expression.Comparison;
import com.wplatform.ddal.command.expression.ConditionAndOr;
import com.wplatform.ddal.command.expression.Expression;
import com.wplatform.ddal.command.expression.ExpressionColumn;
import com.wplatform.ddal.command.expression.ExpressionVisitor;
import com.wplatform.ddal.command.expression.Parameter;
import com.wplatform.ddal.command.expression.Wildcard;
import com.wplatform.ddal.dbobject.table.*;
import com.wplatform.ddal.dispatch.rule.TableNode;
import com.wplatform.ddal.engine.Database;
import com.wplatform.ddal.engine.Session;
import com.wplatform.ddal.engine.SysProperties;
import com.wplatform.ddal.excutor.PreparedExecutor;
import com.wplatform.ddal.excutor.PreparedExecutorFactory;
import com.wplatform.ddal.message.DbException;
import com.wplatform.ddal.message.ErrorCode;
import com.wplatform.ddal.result.LocalResult;
import com.wplatform.ddal.result.ResultInterface;
import com.wplatform.ddal.result.ResultTarget;
import com.wplatform.ddal.result.SortOrder;
import com.wplatform.ddal.util.New;
import com.wplatform.ddal.util.StatementBuilder;
import com.wplatform.ddal.util.StringUtils;
/**
* This class represents a simple SELECT statement.
* <p>
* For each select statement,
* visibleColumnCount <= distinctColumnCount <= expressionCount.
* The expression list count could include ORDER BY and GROUP BY expressions
* that are not in the select list.
* <p>
* The call sequence is init(), mapColumns() if it's a subquery, prepare().
*
* @author Thomas Mueller
* @author Joel Turkel (Group sorted query)
*/
public class Select extends Query {
private final ArrayList<TableFilter> filters = New.arrayList();
private final ArrayList<TableFilter> topFilters = New.arrayList();
private TableFilter topTableFilter;
private ArrayList<Expression> expressions;
private Expression[] expressionArray;
private Expression having;
private Expression condition;
private int visibleColumnCount, distinctColumnCount;
private ArrayList<SelectOrderBy> orderList;
private ArrayList<Expression> group;
private int[] groupIndex;
private boolean[] groupByExpression;
private HashMap<Expression, Object> currentGroup;
private int havingIndex;
private boolean isGroupQuery;
private boolean isForUpdate;
private double cost;
private boolean isPrepared, checkInit;
private SortOrder sort;
private int currentGroupRowId;
private boolean isAccordantQuery;
private boolean isQuickAggregateQuery;
public Select(Session session) {
super(session);
}
/**
* Add a table to the query.
*
* @param filter the table to add
* @param isTop if the table can be the first table in the query plan
*/
public void addTableFilter(TableFilter filter, boolean isTop) {
// Oracle doesn't check on duplicate aliases
// String alias = filter.getAlias();
// if (filterNames.contains(alias)) {
// throw Message.getSQLException(
// ErrorCode.DUPLICATE_TABLE_ALIAS, alias);
// }
// filterNames.add(alias);
filters.add(filter);
if (isTop) {
topFilters.add(filter);
}
}
public ArrayList<TableFilter> getTopFilters() {
return topFilters;
}
/**
* Called if this query contains aggregate functions.
*/
public void setGroupQuery() {
isGroupQuery = true;
}
public ArrayList<Expression> getGroupBy() {
return group;
}
public void setGroupBy(ArrayList<Expression> group) {
this.group = group;
}
public HashMap<Expression, Object> getCurrentGroup() {
return currentGroup;
}
public int getCurrentGroupRowId() {
return currentGroupRowId;
}
public void increaseCurrentGroupRowId() {
++ this.currentGroupRowId;
}
public void setCurrentGroup(HashMap<Expression, Object> currentGroup) {
this.currentGroup = currentGroup;
}
@Override
public void setOrder(ArrayList<SelectOrderBy> order) {
orderList = order;
}
/**
* Add a condition to the list of conditions.
*
* @param cond the condition to add
*/
public void addCondition(Expression cond) {
if (condition == null) {
condition = cond;
} else {
condition = new ConditionAndOr(ConditionAndOr.AND, cond, condition);
}
}
private int getGroupByExpressionCount() {
if (groupByExpression == null) {
return 0;
}
int count = 0;
for (boolean b : groupByExpression) {
if (b) {
++count;
}
}
return count;
}
@Override
public ResultInterface queryMeta() {
LocalResult result = new LocalResult(session, expressionArray,
visibleColumnCount);
result.done();
return result;
}
@Override
protected LocalResult queryWithoutCache(int maxRows, ResultTarget target) {
session.checkCanceled();
PreparedExecutorFactory pef = session.getPreparedExecutorFactory();
PreparedExecutor executor = pef.newExecutor(this);
if(executor == null) {
throw DbException.get(ErrorCode.METHOD_ONLY_ALLOWED_FOR_QUERY);
}
return executor.executeQuery(maxRows);
}
private void expandColumnList() {
Database db = session.getDatabase();
// the expressions may change within the loop
for (int i = 0; i < expressions.size(); i++) {
Expression expr = expressions.get(i);
if (!expr.isWildcard()) {
continue;
}
String schemaName = expr.getSchemaName();
String tableAlias = expr.getTableAlias();
if (tableAlias == null) {
int temp = i;
expressions.remove(i);
for (TableFilter filter : filters) {
Wildcard c2 = new Wildcard(filter.getTable().getSchema()
.getName(), filter.getTableAlias());
expressions.add(i++, c2);
}
i = temp - 1;
} else {
TableFilter filter = null;
for (TableFilter f : filters) {
if (db.equalsIdentifiers(tableAlias, f.getTableAlias())) {
if (schemaName == null ||
db.equalsIdentifiers(schemaName,
f.getSchemaName())) {
filter = f;
break;
}
}
}
if (filter == null) {
throw DbException.get(ErrorCode.TABLE_OR_VIEW_NOT_FOUND_1,
tableAlias);
}
Table t = filter.getTable();
String alias = filter.getTableAlias();
expressions.remove(i);
Column[] columns = t.getColumns();
for (Column c : columns) {
if (filter.isNaturalJoinColumn(c)) {
continue;
}
ExpressionColumn ec = new ExpressionColumn(
session.getDatabase(), null, alias, c.getName());
expressions.add(i++, ec);
}
i--;
}
}
}
@Override
public void init() {
if (SysProperties.CHECK && checkInit) {
DbException.throwInternalError();
}
expandColumnList();
visibleColumnCount = expressions.size();
ArrayList<String> expressionSQL;
if (orderList != null || group != null) {
expressionSQL = New.arrayList();
for (int i = 0; i < visibleColumnCount; i++) {
Expression expr = expressions.get(i);
expr = expr.getNonAliasExpression();
String sql = expr.getSQL();
expressionSQL.add(sql);
}
} else {
expressionSQL = null;
}
if (orderList != null) {
initOrder(session, expressions, expressionSQL, orderList,
visibleColumnCount, distinct, filters);
}
distinctColumnCount = expressions.size();
if (having != null) {
expressions.add(having);
havingIndex = expressions.size() - 1;
having = null;
} else {
havingIndex = -1;
}
Database db = session.getDatabase();
// first the select list (visible columns),
// then 'ORDER BY' expressions,
// then 'HAVING' expressions,
// and 'GROUP BY' expressions at the end
if (group != null) {
int size = group.size();
int expSize = expressionSQL.size();
groupIndex = new int[size];
for (int i = 0; i < size; i++) {
Expression expr = group.get(i);
String sql = expr.getSQL();
int found = -1;
for (int j = 0; j < expSize; j++) {
String s2 = expressionSQL.get(j);
if (db.equalsIdentifiers(s2, sql)) {
found = j;
break;
}
}
if (found < 0) {
// special case: GROUP BY a column alias
for (int j = 0; j < expSize; j++) {
Expression e = expressions.get(j);
if (db.equalsIdentifiers(sql, e.getAlias())) {
found = j;
break;
}
sql = expr.getAlias();
if (db.equalsIdentifiers(sql, e.getAlias())) {
found = j;
break;
}
}
}
if (found < 0) {
int index = expressions.size();
groupIndex[i] = index;
expressions.add(expr);
} else {
groupIndex[i] = found;
}
}
groupByExpression = new boolean[expressions.size()];
for (int gi : groupIndex) {
groupByExpression[gi] = true;
}
group = null;
}
// map columns in select list and condition
for (TableFilter f : filters) {
mapColumns(f, 0);
}
if (havingIndex >= 0) {
Expression expr = expressions.get(havingIndex);
SelectListColumnResolver res = new SelectListColumnResolver(this);
expr.mapColumns(res, 0);
}
checkInit = true;
}
@Override
public void prepare() {
if (isPrepared) {
// sometimes a subquery is prepared twice (CREATE TABLE AS SELECT)
return;
}
if (SysProperties.CHECK && !checkInit) {
DbException.throwInternalError("not initialized");
}
if (orderList != null) {
sort = prepareOrder(orderList, expressions.size());
orderList = null;
}
for (int i = 0; i < expressions.size(); i++) {
Expression e = expressions.get(i);
expressions.set(i, e.optimize(session));
}
if (condition != null) {
condition = condition.optimize(session);
for (TableFilter f : filters) {
// outer joins: must not add index conditions such as
// "c is null" - example:
// create table parent(p int primary key) as select 1;
// create table child(c int primary key, pc int);
// insert into child values(2, 1);
// select p, c from parent
// left outer join child on p = pc where c is null;
if (!f.isJoinOuter() && !f.isJoinOuterIndirect()) {
condition.createIndexConditions(session, f);
}
}
}
if (isGroupQuery && groupIndex == null && havingIndex < 0) {
isQuickAggregateQuery = true;
}
cost = preparePlan();
TableMate last = null;
for (TableFilter filter : filters) {
if (!filter.isFromTableMate()) {
break;
}
if(filters.size() == 1) {
isAccordantQuery = true;
break;
}
TableMate table = (TableMate)filter.getTable();
if(last != null && !last.isRelationSymmetry(table)) {
break;
}last = table;
}
if (distinct && session.getDatabase().getSettings().optimizeDistinct &&
!isGroupQuery && filters.size() == 1 &&
expressions.size() == 1 && condition == null) {
//分布式的查询不合适
}
if (sort != null && !isGroupQuery) {
}
if (isGroupQuery && getGroupByExpressionCount() > 0) {
}
expressionArray = new Expression[expressions.size()];
expressions.toArray(expressionArray);
isPrepared = true;
}
@Override
public double getCost() {
return cost;
}
@Override
public HashSet<Table> getTables() {
HashSet<Table> set = New.hashSet();
for (TableFilter filter : filters) {
set.add(filter.getTable());
}
return set;
}
private double preparePlan() {
TableFilter[] topArray = topFilters.toArray(
new TableFilter[topFilters.size()]);
for (TableFilter t : topArray) {
t.setFullCondition(condition);
}
Optimizer optimizer = new Optimizer(topArray, condition, session);
optimizer.optimize();
topTableFilter = optimizer.getTopFilter();
double planCost = optimizer.getCost();
setEvaluatableRecursive(topTableFilter);
topTableFilter.prepare();
return planCost;
}
private void setEvaluatableRecursive(TableFilter f) {
for (; f != null; f = f.getJoin()) {
f.setEvaluatable(f, true);
if (condition != null) {
condition.setEvaluatable(f, true);
}
TableFilter n = f.getNestedJoin();
if (n != null) {
setEvaluatableRecursive(n);
}
Expression on = f.getJoinCondition();
if (on != null) {
if (!on.isEverything(ExpressionVisitor.EVALUATABLE_VISITOR)) {
if (session.getDatabase().getSettings().nestedJoins) {
// need to check that all added are bound to a table
on = on.optimize(session);
if (!f.isJoinOuter() && !f.isJoinOuterIndirect()) {
f.removeJoinCondition();
addCondition(on);
}
} else {
if (f.isJoinOuter()) {
// this will check if all columns exist - it may or
// may not throw an exception
on = on.optimize(session);
// it is not supported even if the columns exist
throw DbException.get(
ErrorCode.UNSUPPORTED_OUTER_JOIN_CONDITION_1,
on.getSQL());
}
f.removeJoinCondition();
// need to check that all added are bound to a table
on = on.optimize(session);
addCondition(on);
}
}
}
on = f.getFilterCondition();
if (on != null) {
if (!on.isEverything(ExpressionVisitor.EVALUATABLE_VISITOR)) {
f.removeFilterCondition();
addCondition(on);
}
}
// this is only important for subqueries, so they know
// the result columns are evaluatable
for (Expression e : expressions) {
e.setEvaluatable(f, true);
}
}
}
@Override
public String getPlanSQL() {
// can not use the field sqlStatement because the parameter
// indexes may be incorrect: ? may be in fact ?2 for a subquery
// but indexes may be set manually as well
Expression[] exprList = expressions.toArray(
new Expression[expressions.size()]);
StatementBuilder buff = new StatementBuilder("SELECT");
if (distinct) {
buff.append(" DISTINCT");
}
for (int i = 0; i < visibleColumnCount; i++) {
buff.appendExceptFirst(",");
buff.append('\n');
buff.append(StringUtils.indent(exprList[i].getSQL(), 4, false));
}
buff.append("\nFROM ");
TableFilter filter = topTableFilter;
if (filter != null) {
buff.resetCount();
int i = 0;
do {
buff.appendExceptFirst("\n");
buff.append(filter.getPlanSQL(i++ > 0));
filter = filter.getJoin();
} while (filter != null);
} else {
buff.resetCount();
int i = 0;
for (TableFilter f : topFilters) {
do {
buff.appendExceptFirst("\n");
buff.append(f.getPlanSQL(i++ > 0));
f = f.getJoin();
} while (f != null);
}
}
if (condition != null) {
buff.append("\nWHERE ").append(
StringUtils.unEnclose(condition.getSQL()));
}
if (groupIndex != null) {
buff.append("\nGROUP BY ");
buff.resetCount();
for (int gi : groupIndex) {
Expression g = exprList[gi];
g = g.getNonAliasExpression();
buff.appendExceptFirst(", ");
buff.append(StringUtils.unEnclose(g.getSQL()));
}
}
if (group != null) {
buff.append("\nGROUP BY ");
buff.resetCount();
for (Expression g : group) {
buff.appendExceptFirst(", ");
buff.append(StringUtils.unEnclose(g.getSQL()));
}
}
if (having != null) {
// could be set in addGlobalCondition
// in this case the query is not run directly, just getPlanSQL is
// called
Expression h = having;
buff.append("\nHAVING ").append(
StringUtils.unEnclose(h.getSQL()));
} else if (havingIndex >= 0) {
Expression h = exprList[havingIndex];
buff.append("\nHAVING ").append(
StringUtils.unEnclose(h.getSQL()));
}
if (sort != null) {
buff.append("\nORDER BY ").append(
sort.getSQL(exprList, visibleColumnCount));
}
if (orderList != null) {
buff.append("\nORDER BY ");
buff.resetCount();
for (SelectOrderBy o : orderList) {
buff.appendExceptFirst(", ");
buff.append(StringUtils.unEnclose(o.getSQL()));
}
}
if (limitExpr != null) {
buff.append("\nLIMIT ").append(
StringUtils.unEnclose(limitExpr.getSQL()));
if (offsetExpr != null) {
buff.append(" OFFSET ").append(
StringUtils.unEnclose(offsetExpr.getSQL()));
}
}
if (sampleSizeExpr != null) {
buff.append("\nSAMPLE_SIZE ").append(
StringUtils.unEnclose(sampleSizeExpr.getSQL()));
}
if (isForUpdate) {
buff.append("\nFOR UPDATE");
}
buff.append("\n/* cost: " + cost + " */");
return buff.toString();
}
public Expression getHaving() {
return having;
}
public void setHaving(Expression having) {
this.having = having;
}
@Override
public int getColumnCount() {
return visibleColumnCount;
}
public TableFilter getTopTableFilter() {
return topTableFilter;
}
@Override
public ArrayList<Expression> getExpressions() {
return expressions;
}
public void setExpressions(ArrayList<Expression> expressions) {
this.expressions = expressions;
}
@Override
public void setForUpdate(boolean b) {
this.isForUpdate = b;
}
@Override
public void mapColumns(ColumnResolver resolver, int level) {
for (Expression e : expressions) {
e.mapColumns(resolver, level);
}
if (condition != null) {
condition.mapColumns(resolver, level);
}
}
@Override
public void setEvaluatable(TableFilter tableFilter, boolean b) {
for (Expression e : expressions) {
e.setEvaluatable(tableFilter, b);
}
if (condition != null) {
condition.setEvaluatable(tableFilter, b);
}
}
@Override
public void addGlobalCondition(Parameter param, int columnId,
int comparisonType) {
addParameter(param);
Expression comp;
Expression col = expressions.get(columnId);
col = col.getNonAliasExpression();
if (col.isEverything(ExpressionVisitor.QUERY_COMPARABLE_VISITOR)) {
comp = new Comparison(session, comparisonType, col, param);
} else {
// this condition will always evaluate to true, but need to
// add the parameter, so it can be set later
comp = new Comparison(session, Comparison.EQUAL_NULL_SAFE, param, param);
}
comp = comp.optimize(session);
boolean addToCondition = true;
if (isGroupQuery) {
addToCondition = false;
for (int i = 0; groupIndex != null && i < groupIndex.length; i++) {
if (groupIndex[i] == columnId) {
addToCondition = true;
break;
}
}
if (!addToCondition) {
if (havingIndex >= 0) {
having = expressions.get(havingIndex);
}
if (having == null) {
having = comp;
} else {
having = new ConditionAndOr(ConditionAndOr.AND, having, comp);
}
}
}
if (addToCondition) {
if (condition == null) {
condition = comp;
} else {
condition = new ConditionAndOr(ConditionAndOr.AND, condition, comp);
}
}
}
@Override
public void updateAggregate(Session s) {
for (Expression e : expressions) {
e.updateAggregate(s);
}
if (condition != null) {
condition.updateAggregate(s);
}
if (having != null) {
having.updateAggregate(s);
}
}
@Override
public boolean isEverything(ExpressionVisitor visitor) {
switch (visitor.getType()) {
case ExpressionVisitor.DETERMINISTIC: {
if (isForUpdate) {
return false;
}
for (int i = 0, size = filters.size(); i < size; i++) {
TableFilter f = filters.get(i);
if (!f.getTable().isDeterministic()) {
return false;
}
}
break;
}
case ExpressionVisitor.EVALUATABLE: {
if (!session.getDatabase().getSettings().optimizeEvaluatableSubqueries) {
return false;
}
break;
}
case ExpressionVisitor.GET_DEPENDENCIES: {
for (int i = 0, size = filters.size(); i < size; i++) {
TableFilter f = filters.get(i);
Table table = f.getTable();
visitor.addDependency(table);
table.addDependencies(visitor.getDependencies());
}
break;
}
default:
}
ExpressionVisitor v2 = visitor.incrementQueryLevel(1);
boolean result = true;
for (int i = 0, size = expressions.size(); i < size; i++) {
Expression e = expressions.get(i);
if (!e.isEverything(v2)) {
result = false;
break;
}
}
if (result && condition != null && !condition.isEverything(v2)) {
result = false;
}
if (result && having != null && !having.isEverything(v2)) {
result = false;
}
return result;
}
@Override
public boolean isReadOnly() {
return isEverything(ExpressionVisitor.READONLY_VISITOR);
}
@Override
public boolean isCacheable() {
return !isForUpdate;
}
@Override
public int getType() {
return CommandInterface.SELECT;
}
@Override
public boolean allowGlobalConditions() {
return offsetExpr == null && (limitExpr == null || sort == null);
}
public SortOrder getSortOrder() {
return sort;
}
//getters
public SortOrder getSort() {
return sort;
}
public ArrayList<TableFilter> getFilters() {
return filters;
}
public Expression[] getExpressionArray() {
return expressionArray;
}
public Expression getCondition() {
return condition;
}
public int getVisibleColumnCount() {
return visibleColumnCount;
}
public int getDistinctColumnCount() {
return distinctColumnCount;
}
public ArrayList<SelectOrderBy> getOrderList() {
return orderList;
}
public ArrayList<Expression> getGroup() {
return group;
}
public int[] getGroupIndex() {
return groupIndex;
}
public boolean[] getGroupByExpression() {
return groupByExpression;
}
public int getHavingIndex() {
return havingIndex;
}
public boolean isGroupQuery() {
return isGroupQuery;
}
public boolean isForUpdate() {
return isForUpdate;
}
public boolean isPrepared() {
return isPrepared;
}
public boolean isQuickAggregateQuery() {
return isQuickAggregateQuery;
}
public boolean isAccordantQuery() {
return isAccordantQuery;
}
}