/** * Columns from USING expression are unqualified. In case of INNER join, it doesn't matter we can * pick the first table which contains the input column. In case of OUTER joins, we must the OUTER * table - if it's a null-able column the outer join must return them. * * @param columnName * @return table name this column belongs to */ protected void resolveUsingColumns(VoltXMLElement columns, RangeVariable[] rvs) throws HSQLParseException { // Only one OUTER join for a whole select is supported so far for (VoltXMLElement columnElmt : columns.children) { boolean innerJoin = true; String table = null; if (columnElmt.attributes.get("table") == null) { for (RangeVariable rv : rvs) { if (rv.isLeftJoin || rv.isRightJoin) { if (innerJoin == false) { throw new HSQLParseException( "VoltDB does not support outer joins with more than two tables involved"); } innerJoin = false; } if (!rv.getTable().columnList.containsKey(columnElmt.attributes.get("column"))) { // The column is not from this table. Skip it continue; } // If there is an OUTER join we need to pick the outer table if (rv.isRightJoin == true) { // this is the outer table. no need to search further. table = rv.getTable().getName().name; break; } else if (rv.isLeftJoin == false) { // it's the inner join. we found the table but still need to iterate // just in case there is an outer table we haven't seen yet. table = rv.getTable().getName().name; } } if (table != null) { columnElmt.attributes.put("table", table); } } } }
VoltXMLElement voltGetXMLSpecification(QuerySpecification select, Session session) throws HSQLParseException { // select VoltXMLElement query = new VoltXMLElement("select"); if (select.isDistinctSelect) query.attributes.put("distinct", "true"); // limit if ((select.sortAndSlice != null) && (select.sortAndSlice.limitCondition != null)) { Expression limitCondition = select.sortAndSlice.limitCondition; if (limitCondition.nodes.length != 2) { throw new HSQLParseException( "Parser did not create limit and offset expression for LIMIT."); } try { // read offset. it may be a parameter token. if (limitCondition.nodes[0].isParam() == false) { Integer offset = (Integer) limitCondition.nodes[0].getValue(session); if (offset > 0) { query.attributes.put("offset", offset.toString()); } } else { query.attributes.put("offset_paramid", limitCondition.nodes[0].getUniqueId(session)); } // read limit. it may be a parameter token. if (limitCondition.nodes[1].isParam() == false) { Integer limit = (Integer) limitCondition.nodes[1].getValue(session); query.attributes.put("limit", limit.toString()); } else { query.attributes.put("limit_paramid", limitCondition.nodes[1].getUniqueId(session)); } } catch (HsqlException ex) { // XXX really? ex.printStackTrace(); } } // columns that need to be output by the scans VoltXMLElement scanCols = new VoltXMLElement("scan_columns"); query.children.add(scanCols); assert (scanCols != null); // Just gather a mish-mash of every possible relevant expression // and uniq them later HsqlList col_list = new HsqlArrayList(); select.collectAllExpressions( col_list, Expression.columnExpressionSet, Expression.emptyExpressionSet); if (select.queryCondition != null) { Expression.collectAllExpressions( col_list, select.queryCondition, Expression.columnExpressionSet, Expression.emptyExpressionSet); } for (int i = 0; i < select.exprColumns.length; i++) { Expression.collectAllExpressions( col_list, select.exprColumns[i], Expression.columnExpressionSet, Expression.emptyExpressionSet); } for (RangeVariable rv : select.rangeVariables) { if (rv.indexCondition != null) { Expression.collectAllExpressions( col_list, rv.indexCondition, Expression.columnExpressionSet, Expression.emptyExpressionSet); } if (rv.indexEndCondition != null) { Expression.collectAllExpressions( col_list, rv.indexEndCondition, Expression.columnExpressionSet, Expression.emptyExpressionSet); } if (rv.nonIndexJoinCondition != null) { Expression.collectAllExpressions( col_list, rv.nonIndexJoinCondition, Expression.columnExpressionSet, Expression.emptyExpressionSet); } } HsqlList uniq_col_list = new HsqlArrayList(); for (int i = 0; i < col_list.size(); i++) { Expression orig = (Expression) col_list.get(i); if (!uniq_col_list.contains(orig)) { uniq_col_list.add(orig); } } for (int i = 0; i < uniq_col_list.size(); i++) { VoltXMLElement xml = ((Expression) uniq_col_list.get(i)).voltGetXML(session); scanCols.children.add(xml); assert (xml != null); } // columns VoltXMLElement cols = new VoltXMLElement("columns"); query.children.add(cols); ArrayList<Expression> orderByCols = new ArrayList<Expression>(); ArrayList<Expression> groupByCols = new ArrayList<Expression>(); ArrayList<Expression> displayCols = new ArrayList<Expression>(); ArrayList<Pair<Integer, SimpleName>> aliases = new ArrayList<Pair<Integer, SimpleName>>(); /* * select.exprColumn stores all of the columns needed by HSQL to * calculate the query's result set. It contains more than just the * columns in the output; for example, it contains columns representing * aliases, columns for groups, etc. * * Volt uses multiple collections to organize these columns. * * Observing this loop in a debugger, the following seems true: * * 1. Columns in exprColumns that appear in the output schema, appear in * exprColumns in the same order that they occur in the output schema. * * 2. expr.columnIndex is an index back in to the select.exprColumns * array. This allows multiple exprColumn entries to refer to each * other; for example, an OpType.SIMPLE_COLUMN type storing an alias * will have its columnIndex set to the offset of the expr it aliases. */ for (int i = 0; i < select.exprColumns.length; i++) { final Expression expr = select.exprColumns[i]; if (expr.alias != null) { /* * Remember how aliases relate to columns. Will iterate again later * and mutate the exprColumn entries setting the alias string on the aliased * column entry. */ if (expr instanceof ExpressionColumn) { ExpressionColumn exprColumn = (ExpressionColumn) expr; if (exprColumn.alias != null && exprColumn.columnName == null) { aliases.add(Pair.of(expr.columnIndex, expr.alias)); } } else if (expr.columnIndex > -1) { /* * Only add it to the list of aliases that need to be * propagated to columns if the column index is valid. * ExpressionArithmetic will have an alias but not * necessarily a column index. */ aliases.add(Pair.of(expr.columnIndex, expr.alias)); } } // If the column doesn't refer to another exprColumn entry, set its // column index to itself. If all columns have a valid column index, // it's easier to patch up display column ordering later. if (expr.columnIndex == -1) { expr.columnIndex = i; } if (isGroupByColumn(select, i)) { groupByCols.add(expr); } else if (expr.opType == OpTypes.ORDER_BY) { orderByCols.add(expr); } else if (expr.opType != OpTypes.SIMPLE_COLUMN || (expr.isAggregate && expr.alias != null)) { // Add aggregate aliases to the display columns to maintain // the output schema column ordering. displayCols.add(expr); } // else, other simple columns are ignored. If others exist, maybe // volt infers a display column from another column collection? } for (Pair<Integer, SimpleName> alias : aliases) { // set the alias data into the expression being aliased. select.exprColumns[alias.getFirst()].alias = alias.getSecond(); } /* * The columns chosen above as display columns aren't always the same * expr objects HSQL would use as display columns - some data were * unified (namely, SIMPLE_COLUMN aliases were pushed into COLUMNS). * * However, the correct output schema ordering was correct in exprColumns. * This order was maintained by adding SIMPLE_COLUMNs to displayCols. * * Now need to serialize the displayCols, serializing the non-simple-columns * corresponding to simple_columns for any simple_columns that woodchucks * could chuck. * * Serialize the display columns in the exprColumn order. */ for (int jj = 0; jj < displayCols.size(); ++jj) { Expression expr = displayCols.get(jj); if (expr == null) { continue; } else if (expr.opType == OpTypes.SIMPLE_COLUMN) { // simple columns are not serialized as display columns // but they are place holders for another column // in the output schema. Go find that corresponding column // and serialize it in this place. for (int ii = jj; ii < displayCols.size(); ++ii) { Expression otherCol = displayCols.get(ii); if (otherCol == null) { continue; } else if ((otherCol.opType != OpTypes.SIMPLE_COLUMN) && (otherCol.columnIndex == expr.columnIndex)) { // serialize the column this simple column stands-in for VoltXMLElement xml = otherCol.voltGetXML(session); cols.children.add(xml); assert (xml != null); // null-out otherCol to not serialize it twice displayCols.set(ii, null); // quit seeking simple_column's replacement break; } } } else { VoltXMLElement xml = expr.voltGetXML(session); cols.children.add(xml); assert (xml != null); } } // parameters voltAppendParameters(session, query); // scans VoltXMLElement scans = new VoltXMLElement("tablescans"); query.children.add(scans); assert (scans != null); for (RangeVariable rangeVariable : select.rangeVariables) { scans.children.add(rangeVariable.voltGetRangeVariableXML(session)); } // Columns from USING expression in join are not qualified. // if join is INNER then the column from USING expression can be from any table // participating in join. In case of OUTER join, it must be the outer column resolveUsingColumns(cols, select.rangeVariables); // having if (select.havingCondition != null) { throw new HSQLParseException("VoltDB does not support the HAVING clause"); } // groupby if (select.isGrouped) { VoltXMLElement groupCols = new VoltXMLElement("groupcolumns"); query.children.add(groupCols); for (Expression groupByCol : groupByCols) { groupCols.children.add(groupByCol.voltGetXML(session)); } } // orderby if (orderByCols.size() > 0) { VoltXMLElement orderCols = new VoltXMLElement("ordercolumns"); query.children.add(orderCols); for (Expression orderByCol : orderByCols) { orderCols.children.add(orderByCol.voltGetXML(session)); } } // query condition should be covered by table scans, but can un-comment // this to see if anything is missed /*if (select.queryCondition != null) { VoltXMLElement queryCond = new VoltXMLElement("querycondition"); query.children.add(queryCond); queryCond.children.add(select.queryCondition.voltGetXML(session)); }*/ return query; }