SelectListColumnResolver(Select select) { this.select = select; int columnCount = select.getColumnCount(); columns = new Column[columnCount]; expressions = new Expression[columnCount]; ArrayList<Expression> columnList = select.getExpressions(); for (int i = 0; i < columnCount; i++) { Expression expr = columnList.get(i); Column column = new Column(expr.getAlias(), Value.NULL); column.setTable(null, i); columns[i] = column; expressions[i] = expr.getNonAliasExpression(); } }
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); } } }
/** * Initialize the order by list. This call may extend the expressions list. * * @param session the session * @param expressions the select list expressions * @param expressionSQL the select list SQL snippets * @param orderList the order by list * @param visible the number of visible columns in the select list * @param mustBeInResult all order by expressions must be in the select list * @param filters the table filters */ static void initOrder( Session session, ArrayList<Expression> expressions, ArrayList<String> expressionSQL, ArrayList<SelectOrderBy> orderList, int visible, boolean mustBeInResult, ArrayList<TableFilter> filters) { Database db = session.getDatabase(); for (SelectOrderBy o : orderList) { Expression e = o.expression; if (e == null) { continue; } // special case: SELECT 1 AS A FROM DUAL ORDER BY A // (oracle supports it, but only in order by, not in group by and // not in having): // SELECT 1 AS A FROM DUAL ORDER BY -A boolean isAlias = false; int idx = expressions.size(); if (e instanceof ExpressionColumn) { // order by expression ExpressionColumn exprCol = (ExpressionColumn) e; String tableAlias = exprCol.getOriginalTableAliasName(); String col = exprCol.getOriginalColumnName(); for (int j = 0; j < visible; j++) { boolean found = false; Expression ec = expressions.get(j); if (ec instanceof ExpressionColumn) { // select expression ExpressionColumn c = (ExpressionColumn) ec; found = db.equalsIdentifiers(col, c.getColumnName()); if (found && tableAlias != null) { String ca = c.getOriginalTableAliasName(); if (ca == null) { found = false; if (filters != null) { // select id from test order by test.id for (int i = 0, size = filters.size(); i < size; i++) { TableFilter f = filters.get(i); if (db.equalsIdentifiers(f.getTableAlias(), tableAlias)) { found = true; break; } } } } else { found = db.equalsIdentifiers(ca, tableAlias); } } } else if (!(ec instanceof Alias)) { continue; } else if (tableAlias == null && db.equalsIdentifiers(col, ec.getAlias())) { found = true; } else { Expression ec2 = ec.getNonAliasExpression(); if (ec2 instanceof ExpressionColumn) { ExpressionColumn c2 = (ExpressionColumn) ec2; String ta = exprCol.getSQL(); String tb = c2.getSQL(); String s2 = c2.getColumnName(); found = db.equalsIdentifiers(col, s2); if (!db.equalsIdentifiers(ta, tb)) { found = false; } } } if (found) { idx = j; isAlias = true; break; } } } else { String s = e.getSQL(); if (expressionSQL != null) { for (int j = 0, size = expressionSQL.size(); j < size; j++) { String s2 = expressionSQL.get(j); if (db.equalsIdentifiers(s2, s)) { idx = j; isAlias = true; break; } } } } if (!isAlias) { if (mustBeInResult) { throw DbException.get(ErrorCode.ORDER_BY_NOT_IN_RESULT, e.getSQL()); } expressions.add(e); String sql = e.getSQL(); expressionSQL.add(sql); } o.columnIndexExpr = ValueExpression.get(ValueInt.get(idx + 1)); Expression expr = expressions.get(idx).getNonAliasExpression(); o.expression = expr; } }
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 (sampleSize != 0) { buff.append("\nSAMPLE_SIZE ").append(sampleSize); } if (isForUpdate) { buff.append("\nFOR UPDATE"); } if (isQuickAggregateQuery) { buff.append("\n/* direct lookup */"); } if (isDistinctQuery) { buff.append("\n/* distinct */"); } if (sortUsingIndex) { buff.append("\n/* index sorted */"); } if (isGroupQuery) { if (isGroupSortedQuery) { buff.append("\n/* group sorted */"); } } // buff.append("\n/* cost: " + cost + " */"); return buff.toString(); }
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 && filters.size() == 1) { if (condition == null) { Table t = filters.get(0).getTable(); ExpressionVisitor optimizable = ExpressionVisitor.getOptimizableVisitor(t); isQuickAggregateQuery = isEverything(optimizable); } } cost = preparePlan(); if (distinct && session.getDatabase().getSettings().optimizeDistinct && !isGroupQuery && filters.size() == 1 && expressions.size() == 1 && condition == null) { Expression expr = expressions.get(0); expr = expr.getNonAliasExpression(); if (expr instanceof ExpressionColumn) { Column column = ((ExpressionColumn) expr).getColumn(); int selectivity = column.getSelectivity(); Index columnIndex = topTableFilter.getTable().getIndexForColumn(column); if (columnIndex != null && selectivity != Constants.SELECTIVITY_DEFAULT && selectivity < 20) { // the first column must be ascending boolean ascending = columnIndex.getIndexColumns()[0].sortType == SortOrder.ASCENDING; Index current = topTableFilter.getIndex(); // if another index is faster if (columnIndex.canFindNext() && ascending && (current == null || current.getIndexType().isScan() || columnIndex == current)) { IndexType type = columnIndex.getIndexType(); // hash indexes don't work, and unique single column indexes don't work if (!type.isHash() && (!type.isUnique() || columnIndex.getColumns().length > 1)) { topTableFilter.setIndex(columnIndex); isDistinctQuery = true; } } } } } if (sort != null && !isQuickAggregateQuery && !isGroupQuery) { Index index = getSortIndex(); if (index != null) { Index current = topTableFilter.getIndex(); if (current.getIndexType().isScan() || current == index) { topTableFilter.setIndex(index); if (!topTableFilter.hasInComparisons()) { // in(select ...) and in(1,2,3) my return the key in another order sortUsingIndex = true; } } else if (index.getIndexColumns().length >= current.getIndexColumns().length) { IndexColumn[] sortColumns = index.getIndexColumns(); IndexColumn[] currentColumns = current.getIndexColumns(); boolean swapIndex = false; for (int i = 0; i < currentColumns.length; i++) { if (sortColumns[i].column != currentColumns[i].column) { swapIndex = false; break; } if (sortColumns[i].sortType != currentColumns[i].sortType) { swapIndex = true; } } if (swapIndex) { topTableFilter.setIndex(index); sortUsingIndex = true; } } } } if (!isQuickAggregateQuery && isGroupQuery && getGroupByExpressionCount() > 0) { Index index = getGroupSortedIndex(); Index current = topTableFilter.getIndex(); if (index != null && (current.getIndexType().isScan() || current == index)) { topTableFilter.setIndex(index); isGroupSortedQuery = true; } } expressionArray = new Expression[expressions.size()]; expressions.toArray(expressionArray); isPrepared = true; }
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; } } } 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) { for (Expression expr : expressions) { expr.mapColumns(f, 0); } if (condition != null) { condition.mapColumns(f, 0); } } if (havingIndex >= 0) { Expression expr = expressions.get(havingIndex); SelectListColumnResolver res = new SelectListColumnResolver(this); expr.mapColumns(res, 0); } checkInit = true; }
/** * Get the index that matches the ORDER BY list, if one exists. This is to avoid running a * separate ORDER BY if an index can be used. This is specially important for large result sets, * if only the first few rows are important (LIMIT is used) * * @return the index if one is found */ private Index getSortIndex() { if (sort == null) { return null; } ArrayList<Column> sortColumns = New.arrayList(); for (int idx : sort.getIndexes()) { if (idx < 0 || idx >= expressions.size()) { throw DbException.getInvalidValueException("ORDER BY", idx + 1); } Expression expr = expressions.get(idx); expr = expr.getNonAliasExpression(); if (expr.isConstant()) { continue; } if (!(expr instanceof ExpressionColumn)) { return null; } ExpressionColumn exprCol = (ExpressionColumn) expr; if (exprCol.getTableFilter() != topTableFilter) { return null; } sortColumns.add(exprCol.getColumn()); } Column[] sortCols = sortColumns.toArray(new Column[sortColumns.size()]); int[] sortTypes = sort.getSortTypes(); if (sortCols.length == 0) { // sort just on constants - can use scan index return topTableFilter.getTable().getScanIndex(session); } ArrayList<Index> list = topTableFilter.getTable().getIndexes(); if (list != null) { for (int i = 0, size = list.size(); i < size; i++) { Index index = list.get(i); if (index.getCreateSQL() == null) { // can't use the scan index continue; } if (index.getIndexType().isHash()) { continue; } IndexColumn[] indexCols = index.getIndexColumns(); if (indexCols.length < sortCols.length) { continue; } boolean ok = true; for (int j = 0; j < sortCols.length; j++) { // the index and the sort order must start // with the exact same columns IndexColumn idxCol = indexCols[j]; Column sortCol = sortCols[j]; if (idxCol.column != sortCol) { ok = false; break; } if (idxCol.sortType != sortTypes[j]) { // NULL FIRST for ascending and NULLS LAST // for descending would actually match the default ok = false; break; } } if (ok) { return index; } } } if (sortCols.length == 1 && sortCols[0].getColumnId() == -1) { // special case: order by _ROWID_ Index index = topTableFilter.getTable().getScanIndex(session); if (index.isRowIdIndex()) { return index; } } return null; }