private static void projectTableColumnFamily( StatementContext context, String cfName, TableRef tableRef, List<Expression> projectedExpressions, List<ExpressionProjector> projectedColumns) throws SQLException { PTable table = tableRef.getTable(); PColumnFamily pfamily = table.getColumnFamily(cfName); for (PColumn column : pfamily.getColumns()) { ColumnRef ref = new ColumnRef(tableRef, column.getPosition()); Expression expression = ref.newColumnExpression(); projectedExpressions.add(expression); projectedColumns.add( new ExpressionProjector( column.getName().toString(), table.getName().getString(), expression, false)); } }
private static void projectIndexColumnFamily( StatementContext context, String cfName, TableRef tableRef, List<Expression> projectedExpressions, List<ExpressionProjector> projectedColumns) throws SQLException { PTable index = tableRef.getTable(); PTable table = context.getConnection().getPMetaData().getTable(index.getParentName().getString()); PColumnFamily pfamily = table.getColumnFamily(cfName); for (PColumn column : pfamily.getColumns()) { PColumn indexColumn = index.getColumn(IndexUtil.getIndexColumnName(column)); ColumnRef ref = new ColumnRef(tableRef, indexColumn.getPosition()); Expression expression = ref.newColumnExpression(); projectedExpressions.add(expression); projectedColumns.add( new ExpressionProjector( column.getName().toString(), table.getName().getString(), expression, false)); } }
/** * Builds the projection for the scan * * @param context query context kept between compilation of different query clauses * @param statement TODO * @param groupBy compiled GROUP BY clause * @param targetColumns list of columns, parallel to aliasedNodes, that are being set for an * UPSERT SELECT statement. Used to coerce expression types to the expected target type. * @return projector used to access row values during scan * @throws SQLException */ public static RowProjector compile( StatementContext context, SelectStatement statement, GroupBy groupBy, List<? extends PDatum> targetColumns) throws SQLException { List<AliasedNode> aliasedNodes = statement.getSelect(); // Setup projected columns in Scan SelectClauseVisitor selectVisitor = new SelectClauseVisitor(context, groupBy); List<ExpressionProjector> projectedColumns = new ArrayList<ExpressionProjector>(); TableRef tableRef = context.getResolver().getTables().get(0); PTable table = tableRef.getTable(); boolean isWildcard = false; Scan scan = context.getScan(); int index = 0; List<Expression> projectedExpressions = Lists.newArrayListWithExpectedSize(aliasedNodes.size()); List<byte[]> projectedFamilies = Lists.newArrayListWithExpectedSize(aliasedNodes.size()); for (AliasedNode aliasedNode : aliasedNodes) { ParseNode node = aliasedNode.getNode(); // TODO: visitor? if (node instanceof WildcardParseNode) { if (statement.isAggregate()) { ExpressionCompiler.throwNonAggExpressionInAggException(node.toString()); } isWildcard = true; if (tableRef.getTable().getType() == PTableType.INDEX && ((WildcardParseNode) node).isRewrite()) { projectAllIndexColumns(context, tableRef, projectedExpressions, projectedColumns); } else { projectAllTableColumns(context, tableRef, projectedExpressions, projectedColumns); } } else if (node instanceof FamilyWildcardParseNode) { // Project everything for SELECT cf.* // TODO: support cf.* expressions for multiple tables the same way with *. String cfName = ((FamilyWildcardParseNode) node).getName(); // Delay projecting to scan, as when any other column in the column family gets // added to the scan, it overwrites that we want to project the entire column // family. Instead, we do the projection at the end. // TODO: consider having a ScanUtil.addColumn and ScanUtil.addFamily to work // around this, as this code depends on this function being the last place where // columns are projected (which is currently true, but could change). projectedFamilies.add(Bytes.toBytes(cfName)); if (tableRef.getTable().getType() == PTableType.INDEX && ((FamilyWildcardParseNode) node).isRewrite()) { projectIndexColumnFamily( context, cfName, tableRef, projectedExpressions, projectedColumns); } else { projectTableColumnFamily( context, cfName, tableRef, projectedExpressions, projectedColumns); } } else { Expression expression = node.accept(selectVisitor); projectedExpressions.add(expression); if (index < targetColumns.size()) { PDatum targetColumn = targetColumns.get(index); if (targetColumn.getDataType() != expression.getDataType()) { PDataType targetType = targetColumn.getDataType(); // Check if coerce allowed using more relaxed isCastableTo check, since we promote // INTEGER to LONG // during expression evaluation and then convert back to INTEGER on UPSERT SELECT (and // we don't have // (an actual value we can specifically check against). if (expression.getDataType() != null && !expression.getDataType().isCastableTo(targetType)) { throw new ArgumentTypeMismatchException( targetType, expression.getDataType(), "column: " + targetColumn); } expression = CoerceExpression.create(expression, targetType); } } if (node instanceof BindParseNode) { context.getBindManager().addParamMetaData((BindParseNode) node, expression); } if (!node.isStateless()) { if (!selectVisitor.isAggregate() && statement.isAggregate()) { ExpressionCompiler.throwNonAggExpressionInAggException(expression.toString()); } } String columnAlias = aliasedNode.getAlias() != null ? aliasedNode.getAlias() : SchemaUtil.normalizeIdentifier(aliasedNode.getNode().getAlias()); boolean isCaseSensitive = (columnAlias != null && (aliasedNode.isCaseSensitve() || SchemaUtil.isCaseSensitive(columnAlias))) || selectVisitor.isCaseSensitive; String name = columnAlias == null ? expression.toString() : columnAlias; projectedColumns.add( new ExpressionProjector( name, table.getName().getString(), expression, isCaseSensitive)); } selectVisitor.reset(); index++; } table = context.getCurrentTable().getTable(); // switch to current table for scan projection // TODO make estimatedByteSize more accurate by counting the joined columns. int estimatedKeySize = table.getRowKeySchema().getEstimatedValueLength(); int estimatedByteSize = 0; for (Map.Entry<byte[], NavigableSet<byte[]>> entry : scan.getFamilyMap().entrySet()) { PColumnFamily family = table.getColumnFamily(entry.getKey()); if (entry.getValue() == null) { for (PColumn column : family.getColumns()) { Integer byteSize = column.getByteSize(); estimatedByteSize += SizedUtil.KEY_VALUE_SIZE + estimatedKeySize + (byteSize == null ? RowKeySchema.ESTIMATED_VARIABLE_LENGTH_SIZE : byteSize); } } else { for (byte[] cq : entry.getValue()) { PColumn column = family.getColumn(cq); Integer byteSize = column.getByteSize(); estimatedByteSize += SizedUtil.KEY_VALUE_SIZE + estimatedKeySize + (byteSize == null ? RowKeySchema.ESTIMATED_VARIABLE_LENGTH_SIZE : byteSize); } } } selectVisitor.compile(); // Since we don't have the empty key value in read-only tables, // we must project everything. boolean isProjectEmptyKeyValue = table.getType() != PTableType.VIEW && table.getViewType() != ViewType.MAPPED && !isWildcard; if (isProjectEmptyKeyValue) { for (byte[] family : projectedFamilies) { projectColumnFamily(table, scan, family); } } else { /* * TODO: this could be optimized by detecting: * - if a column is projected that's not in the where clause * - if a column is grouped by that's not in the where clause * - if we're not using IS NULL or CASE WHEN expressions */ projectAllColumnFamilies(table, scan); } return new RowProjector(projectedColumns, estimatedByteSize, isProjectEmptyKeyValue); }