/* * 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.excutor.dml; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import com.wplatform.ddal.command.dml.Select; import com.wplatform.ddal.command.expression.Expression; import com.wplatform.ddal.command.expression.ExpressionColumn; import com.wplatform.ddal.config.TableConfig; import com.wplatform.ddal.dbobject.index.Index; import com.wplatform.ddal.dbobject.table.Column; import com.wplatform.ddal.dbobject.table.Table; import com.wplatform.ddal.dbobject.table.TableFilter; import com.wplatform.ddal.dbobject.table.TableMate; import com.wplatform.ddal.dispatch.rule.TableNode; import com.wplatform.ddal.engine.Session; 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.SearchRow; import com.wplatform.ddal.result.SortOrder; import com.wplatform.ddal.util.New; import com.wplatform.ddal.util.StatementBuilder; import com.wplatform.ddal.util.StringUtils; import com.wplatform.ddal.util.ValueHashMap; import com.wplatform.ddal.value.Value; import com.wplatform.ddal.value.ValueArray; import com.wplatform.ddal.value.ValueNull; /** * @author <a href="mailto:jorgie.mail@gmail.com">jorgie li</a> */ public class SelectExecutor extends PreparedRoutingExecutor<Select> { protected Expression limitExpr; protected Expression offsetExpr; protected Expression sampleSizeExpr; protected boolean distinct; protected boolean randomAccessResult; private TableFilter topTableFilter; private final ArrayList<TableFilter> filters; private final ArrayList<TableFilter> topFilters ; private ArrayList<Expression> expressions; private Expression[] expressionArray; private Expression having; private Expression condition; private int visibleColumnCount, distinctColumnCount; private ArrayList<Expression> group; private int[] groupIndex; private boolean[] groupByExpression; private int havingIndex; private boolean isGroupQuery; private boolean isForUpdate; private boolean isQuickAggregateQuery; private boolean isAccordantQuery; private SortOrder sort; /** * @param prepared */ public SelectExecutor(Select prepared) { super(prepared); limitExpr = prepared.getLimit(); expressions = prepared.getExpressions(); sort = prepared.getSort(); distinct = prepared.isDistinct(); randomAccessResult = prepared.isRandomAccessResult(); isGroupQuery = prepared.isGroupQuery(); isAccordantQuery = prepared.isAccordantQuery(); isQuickAggregateQuery = prepared.isQuickAggregateQuery(); isForUpdate = prepared.isForUpdate(); offsetExpr = prepared.getOffset(); topTableFilter = prepared.getTopTableFilter(); group = prepared.getGroup(); groupIndex = prepared.getGroupIndex(); groupByExpression = prepared.getGroupByExpression(); distinctColumnCount = prepared.getDistinctColumnCount(); condition = prepared.getCondition(); expressionArray = prepared.getExpressionArray(); having = prepared.getHaving(); filters = prepared.getFilters(); topFilters = prepared.getTopFilters(); havingIndex = prepared.getHavingIndex(); visibleColumnCount = expressions.size(); } @Override public LocalResult executeQuery(int maxRows, ResultTarget target) { int limitRows = maxRows == 0 ? -1 : maxRows; if (limitExpr != null) { Value v = limitExpr.getValue(session); int l = v == ValueNull.INSTANCE ? -1 : v.getInt(); if (limitRows < 0) { limitRows = l; } else if (l >= 0) { limitRows = Math.min(l, limitRows); } } int columnCount = expressions.size(); LocalResult result = null; if (target == null || !session.getDatabase().getSettings().optimizeInsertFromSelect) { result = createLocalResult(result); } if (sort != null) { result = createLocalResult(result); result.setSortOrder(sort); } if (distinct) { result = createLocalResult(result); result.setDistinct(); } if (randomAccessResult) { result = createLocalResult(result); } if (isGroupQuery) { result = createLocalResult(result); } if (limitRows >= 0 || offsetExpr != null) { result = createLocalResult(result); } topTableFilter.startQuery(session); topTableFilter.reset(); topTableFilter.lock(session, isForUpdate, isForUpdate); ResultTarget to = result != null ? result : target; if (limitRows != 0) { if(isAccordantQuery) { if (isGroupQuery) { queryGroupAccordant(columnCount, to); } else { queryFlatAccordant(columnCount, to, limitRows); } } else { if (isGroupQuery) { queryGroup(columnCount, result); } else { queryFlat(columnCount, to, limitRows); } } } if (offsetExpr != null) { result.setOffset(offsetExpr.getValue(session).getInt()); } if (result != null) { result.done(); if (target != null) { while (result.next()) { target.addRow(result.currentRow()); } result.close(); return null; } return result; } return null; } private void scanLevelValidation(TableFilter filter) { Table test = filter.getTable(); if (!(test instanceof Table)) { return; } TableMate table = castTableMate(test); table.check(); Index index = filter.getIndex(); int scanLevel = table.getScanLevel(); switch (scanLevel) { case TableConfig.SCANLEVEL_UNLIMITED: break; case TableConfig.SCANLEVEL_FILTER: if (filter.getFilterCondition() == null) { throw DbException.get(ErrorCode.NOT_ALLOWED_TO_SCAN_TABLE, table.getSQL(), "filter", "filter"); } break; case TableConfig.SCANLEVEL_ANYINDEX: if (index.getIndexType().isScan()) { throw DbException.get(ErrorCode.NOT_ALLOWED_TO_SCAN_TABLE, table.getSQL(), "anyIndex", "index"); } break; case TableConfig.SCANLEVEL_UNIQUEINDEX: if (!index.getIndexType().isUnique()) { throw DbException.get(ErrorCode.NOT_ALLOWED_TO_SCAN_TABLE, table.getSQL(), "uniqueIndex", "unique index"); } break; case TableConfig.SCANLEVEL_SHARDINGKEY: if (!index.getIndexType().isShardingKey()) { throw DbException.get(ErrorCode.NOT_ALLOWED_TO_SCAN_TABLE, table.getSQL(), "shardingKey", "sharding key"); } break; default: throw DbException.throwInternalError("error table scan level " + scanLevel); } } @Override protected List<Value> doTranslate(TableNode node, SearchRow row, StatementBuilder buff) { return null; } private void queryGroupSorted(int columnCount, ResultTarget result) { int rowNumber = 0; prepared.setCurrentRowNumber(0); prepared.setCurrentGroup(null); Value[] previousKeyValues = null; while (topTableFilter.next()) { prepared.setCurrentRowNumber(rowNumber + 1);//for rownum expression if (condition == null || Boolean.TRUE.equals(condition.getBooleanValue(session))) { rowNumber++; Value[] keyValues = new Value[groupIndex.length]; // update group for (int i = 0; i < groupIndex.length; i++) { int idx = groupIndex[i]; Expression expr = expressions.get(idx); keyValues[i] = expr.getValue(session); } if (previousKeyValues == null) { previousKeyValues = keyValues; prepared.setCurrentGroup(New.<Expression, Object>hashMap()); } else if (!Arrays.equals(previousKeyValues, keyValues)) { addGroupSortedRow(previousKeyValues, columnCount, result); previousKeyValues = keyValues; prepared.setCurrentGroup(New.<Expression, Object>hashMap()); } prepared.increaseCurrentGroupRowId(); for (int i = 0; i < columnCount; i++) { if (groupByExpression == null || !groupByExpression[i]) { Expression expr = expressions.get(i); expr.updateAggregate(session); } } } } if (previousKeyValues != null) { addGroupSortedRow(previousKeyValues, columnCount, result); } } private void addGroupSortedRow(Value[] keyValues, int columnCount, ResultTarget result) { Value[] row = new Value[columnCount]; for (int j = 0; groupIndex != null && j < groupIndex.length; j++) { row[groupIndex[j]] = keyValues[j]; } for (int j = 0; j < columnCount; j++) { if (groupByExpression != null && groupByExpression[j]) { continue; } Expression expr = expressions.get(j); row[j] = expr.getValue(session); } if (isHavingNullOrFalse(row)) { return; } row = keepOnlyDistinct(row, columnCount); result.addRow(row); } private Value[] keepOnlyDistinct(Value[] row, int columnCount) { if (columnCount == distinctColumnCount) { return row; } // remove columns so that 'distinct' can filter duplicate rows Value[] r2 = new Value[distinctColumnCount]; System.arraycopy(row, 0, r2, 0, distinctColumnCount); return r2; } private boolean isHavingNullOrFalse(Value[] row) { if (havingIndex >= 0) { Value v = row[havingIndex]; if (v == ValueNull.INSTANCE) { return true; } if (!Boolean.TRUE.equals(v.getBoolean())) { return true; } } return false; } private Index getGroupSortedIndex() { if (groupIndex == null || groupByExpression == null) { return null; } ArrayList<Index> indexes = topTableFilter.getTable().getIndexes(); if (indexes != null) { for (int i = 0, size = indexes.size(); i < size; i++) { Index index = indexes.get(i); if (index.getIndexType().isScan()) { continue; } if (index.getIndexType().isHash()) { // does not allow scanning entries continue; } if (isGroupSortedIndex(topTableFilter, index)) { return index; } } } return null; } private boolean isGroupSortedIndex(TableFilter tableFilter, Index index) { // check that all the GROUP BY expressions are part of the index Column[] indexColumns = index.getColumns(); // also check that the first columns in the index are grouped boolean[] grouped = new boolean[indexColumns.length]; outerLoop: for (int i = 0, size = expressions.size(); i < size; i++) { if (!groupByExpression[i]) { continue; } Expression expr = expressions.get(i).getNonAliasExpression(); if (!(expr instanceof ExpressionColumn)) { return false; } ExpressionColumn exprCol = (ExpressionColumn) expr; for (int j = 0; j < indexColumns.length; ++j) { if (tableFilter == exprCol.getTableFilter()) { if (indexColumns[j].equals(exprCol.getColumn())) { grouped[j] = true; continue outerLoop; } } } // We didn't find a matching index column // for one group by expression return false; } // check that the first columns in the index are grouped // good: index(a, b, c); group by b, a // bad: index(a, b, c); group by a, c for (int i = 1; i < grouped.length; i++) { if (!grouped[i - 1] && grouped[i]) { return false; } } return true; } private void queryGroup(int columnCount, LocalResult result) { ValueHashMap<HashMap<Expression, Object>> groups = ValueHashMap.newInstance(); int rowNumber = 0; prepared.setCurrentRowNumber(0); prepared.setCurrentGroup(null); ValueArray defaultGroup = ValueArray.get(new Value[0]); int sampleSize = getSampleSizeValue(session); while (topTableFilter.next()) { prepared.setCurrentRowNumber(rowNumber + 1); if (condition == null || Boolean.TRUE.equals(condition.getBooleanValue(session))) { Value key; rowNumber++; if (groupIndex == null) { key = defaultGroup; } else { Value[] keyValues = new Value[groupIndex.length]; // update group for (int i = 0; i < groupIndex.length; i++) { int idx = groupIndex[i]; Expression expr = expressions.get(idx); keyValues[i] = expr.getValue(session); } key = ValueArray.get(keyValues); } HashMap<Expression, Object> values = groups.get(key); if (values == null) { values = new HashMap<Expression, Object>(); groups.put(key, values); } prepared.setCurrentGroup(values); prepared.increaseCurrentGroupRowId(); int len = columnCount; for (int i = 0; i < len; i++) { if (groupByExpression == null || !groupByExpression[i]) { Expression expr = expressions.get(i); expr.updateAggregate(session); } } if (sampleSize > 0 && rowNumber >= sampleSize) { break; } } } if (groupIndex == null && groups.size() == 0) { groups.put(defaultGroup, new HashMap<Expression, Object>()); } ArrayList<Value> keys = groups.keys(); for (Value v : keys) { ValueArray key = (ValueArray) v; prepared.setCurrentGroup(groups.get(key)); Value[] keyValues = key.getList(); Value[] row = new Value[columnCount]; for (int j = 0; groupIndex != null && j < groupIndex.length; j++) { row[groupIndex[j]] = keyValues[j]; } for (int j = 0; j < columnCount; j++) { if (groupByExpression != null && groupByExpression[j]) { continue; } Expression expr = expressions.get(j); row[j] = expr.getValue(session); } if (isHavingNullOrFalse(row)) { continue; } row = keepOnlyDistinct(row, columnCount); result.addRow(row); } } private void queryFlatAccordant(int columnCount, ResultTarget result, long limitRows) { } private void queryFlat(int columnCount, ResultTarget result, long limitRows) { // limitRows must be long, otherwise we get an int overflow // if limitRows is at or near Integer.MAX_VALUE // limitRows is never 0 here if (limitRows > 0 && offsetExpr != null) { int offset = offsetExpr.getValue(session).getInt(); if (offset > 0) { limitRows += offset; } } int rowNumber = 0; prepared.setCurrentRowNumber(0); int sampleSize = getSampleSizeValue(session); while (topTableFilter.next()) { prepared.setCurrentRowNumber(rowNumber + 1); if (condition == null || Boolean.TRUE.equals(condition.getBooleanValue(session))) { Value[] row = new Value[columnCount]; for (int i = 0; i < columnCount; i++) { Expression expr = expressions.get(i); row[i] = expr.getValue(session); } result.addRow(row); rowNumber++; if ((sort == null) && limitRows > 0 && result.getRowCount() >= limitRows) { break; } if (sampleSize > 0 && rowNumber >= sampleSize) { break; } } } } private void queryGroupAccordant(int columnCount, ResultTarget result) { Value[] row = new Value[columnCount]; for (int i = 0; i < columnCount; i++) { Expression expr = expressions.get(i); row[i] = expr.getValue(session); } result.addRow(row); } public ResultInterface queryMeta() { LocalResult result = new LocalResult(session, expressionArray, visibleColumnCount); result.done(); return result; } private LocalResult createLocalResult(LocalResult old) { return old != null ? old : new LocalResult(session, expressionArray, visibleColumnCount); } 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 (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"); } return buff.toString(); } /** * Get the sample size, if set. * * @param session the session * @return the sample size */ int getSampleSizeValue(Session session) { if (sampleSizeExpr == null) { return 0; } Value v = sampleSizeExpr.optimize(session).getValue(session); if (v == ValueNull.INSTANCE) { return 0; } return v.getInt(); } }