@Override protected PlanBuilder visitQuerySpecification(QuerySpecification node, Void context) { PlanBuilder builder = planFrom(node); builder = appendSemiJoins(builder, analysis.getInPredicates(node)); builder = appendScalarSubqueryJoins(builder, analysis.getScalarSubqueries(node)); builder = filter(builder, analysis.getWhere(node)); builder = aggregate(builder, node); builder = filter(builder, analysis.getHaving(node)); builder = window(builder, node); List<FieldOrExpression> orderBy = analysis.getOrderByExpressions(node); List<FieldOrExpression> outputs = analysis.getOutputExpressions(node); builder = project(builder, Iterables.concat(orderBy, outputs)); builder = distinct(builder, node, outputs, orderBy); builder = sort(builder, node); builder = project(builder, analysis.getOutputExpressions(node)); builder = limit(builder, node); return builder; }
private PlanBuilder planFrom(QuerySpecification node) { RelationPlan relationPlan; if (node.getFrom().isPresent()) { relationPlan = new RelationPlanner(analysis, symbolAllocator, idAllocator, metadata, session) .process(node.getFrom().get(), null); } else { relationPlan = planImplicitTable(); } TranslationMap translations = new TranslationMap(relationPlan, analysis); // Make field->symbol mapping from underlying relation plan available for translations // This makes it possible to rewrite FieldOrExpressions that reference fields from the FROM clause directly translations.setFieldMappings(relationPlan.getOutputSymbols()); return new PlanBuilder(translations, relationPlan.getRoot(), relationPlan.getSampleWeight()); }
private PlanBuilder distinct(PlanBuilder subPlan, QuerySpecification node, List<FieldOrExpression> outputs, List<FieldOrExpression> orderBy) { if (node.getSelect().isDistinct()) { checkState(outputs.containsAll(orderBy), "Expected ORDER BY terms to be in SELECT. Broken analysis"); AggregationNode aggregation = new AggregationNode(idAllocator.getNextId(), subPlan.getRoot(), subPlan.getRoot().getOutputSymbols(), ImmutableMap.<Symbol, FunctionCall>of(), ImmutableMap.<Symbol, Signature>of(), ImmutableMap.<Symbol, Symbol>of(), AggregationNode.Step.SINGLE, Optional.empty(), 1.0, Optional.empty()); return new PlanBuilder(subPlan.getTranslations(), aggregation, subPlan.getSampleWeight()); } return subPlan; }
@Override protected RelationType visitQuerySpecification(QuerySpecification node, AnalysisContext parentContext) { // TODO: extract candidate names from SELECT, WHERE, HAVING, GROUP BY and ORDER BY expressions // to pass down to analyzeFrom AnalysisContext context = new AnalysisContext(parentContext); RelationType tupleDescriptor = analyzeFrom(node, context); node.getWhere().ifPresent(where -> analyzeWhere(node, tupleDescriptor, context, where)); List<FieldOrExpression> outputExpressions = analyzeSelect(node, tupleDescriptor, context); List<List<FieldOrExpression>> groupByExpressions = analyzeGroupBy(node, tupleDescriptor, context, outputExpressions); List<FieldOrExpression> orderByExpressions = analyzeOrderBy(node, tupleDescriptor, context, outputExpressions); analyzeHaving(node, tupleDescriptor, context); analyzeAggregations(node, tupleDescriptor, groupByExpressions, outputExpressions, orderByExpressions, context, analysis.getColumnReferences()); analyzeWindowFunctions(node, outputExpressions, orderByExpressions); RelationType descriptor = computeOutputDescriptor(node, tupleDescriptor); analysis.setOutputDescriptor(node, descriptor); return descriptor; }
private void analyzeHaving(QuerySpecification node, RelationType tupleDescriptor, AnalysisContext context) { if (node.getHaving().isPresent()) { Expression predicate = node.getHaving().get(); ExpressionAnalysis expressionAnalysis = analyzeExpression(predicate, tupleDescriptor, context); analysis.recordSubqueries(node, expressionAnalysis); Type predicateType = expressionAnalysis.getType(predicate); if (!predicateType.equals(BOOLEAN) && !predicateType.equals(UNKNOWN)) { throw new SemanticException(TYPE_MISMATCH, predicate, "HAVING clause must evaluate to a boolean: actual type %s", predicateType); } analysis.setHaving(node, predicate); } }
@Override protected Void visitQuerySpecification(QuerySpecification node, Integer indent) { process(node.getSelect(), indent); if (node.getFrom().isPresent()) { append(indent, "FROM"); builder.append('\n'); append(indent, " "); process(node.getFrom().get(), indent); } builder.append('\n'); if (node.getWhere().isPresent()) { append(indent, "WHERE " + formatExpression(node.getWhere().get(), parameters, indent)) .append('\n'); } if (node.getGroupBy().isPresent()) { append(indent, "GROUP BY " + (node.getGroupBy().get().isDistinct() ? " DISTINCT " : "") + formatGroupBy(node.getGroupBy().get() .getGroupingElements(), indent)).append('\n'); } if (node.getHaving().isPresent()) { append(indent, "HAVING " + formatExpression(node.getHaving().get(), parameters, indent)) .append('\n'); } if (node.getOrderBy().isPresent()) { append(indent, "ORDER BY " + formatSortItems(node.getOrderBy().get().getSortItems(), parameters, indent)) .append('\n'); } if (node.getLimit().isPresent()) { append(indent, "LIMIT " + node.getLimit().get()) .append('\n'); } return null; }
/** * Builds the provided {@link SearchRequestBuilder} by parsing the {@link Query} using the properties provided. * @param sql the original sql statement * @param queryBody the Query parsed from the sql * @param searchReq the request to build * @param props a set of properties to use in certain cases * @param tableColumnInfo mapping from available tables to columns and their types * @return an array containing [ {@link Heading}, {@link IComparison} having, List<{@link OrderBy}> orderings, Integer limit] * @throws SQLException */ public ParseResult parse(String sql, QueryBody queryBody, int maxRows, Properties props, Map<String, Map<String, Integer>> tableColumnInfo) throws SQLException{ this.sql = sql.replace("\r", " ").replace("\n", " ");// TODO: this removes linefeeds from string literals as well! this.props = props; this.maxRows = maxRows; this.tableColumnInfo = tableColumnInfo; if(queryBody instanceof QuerySpecification){ ParseResult result = queryBody.accept(this, null); if(result.getException() != null) throw result.getException(); return result; } throw new SQLException("The provided query does not contain a QueryBody"); }
/** * Parses and executes the provided insert statement and returns 1 if execution was successful * @param sql * @param insert * @param index * @return the number of executed inserts * @throws SQLException */ public int execute(String sql, Insert insert, String index) throws SQLException{ if(insert.getQuery().getQueryBody() instanceof Values){ // parse one or multiple value sets (... VALUES (1,2,'a'), (2,4,'b'), ...) return this.insertFromValues(sql, insert, index, Utils.getIntProp(props, Utils.PROP_FETCH_SIZE, 2500)); }else if(insert.getQuery().getQueryBody() instanceof QuerySpecification){ // insert data based on a SELECT statement return this.insertFromSelect(sql, insert, index, Utils.getIntProp(props, Utils.PROP_FETCH_SIZE, 2500)); }else throw new SQLException("Unknown set of values to insert ("+insert.getQuery().getQueryBody()+")"); }
/** * Creates a view (elasticsearch alias) with given name and query * @param sql * @param create * @param index * @return * @throws SQLException */ public int execute(String sql, CreateView create, String index) throws SQLException{ String alias = create.getName().toString(); alias = Heading.findOriginal(sql, alias, "\\s+view\\s+", "\\s+as\\s+"); QueryBody queryBody = create.getQuery().getQueryBody(); if(!(queryBody instanceof QuerySpecification)) throw new SQLException("Statement does not contain expected query specifiction"); QuerySpecification querySpec = (QuerySpecification)queryBody; if(!querySpec.getFrom().isPresent()) throw new SQLException("Add atleast one INDEX to the query to create the view from"); QueryState state = new BasicQueryState(sql, new Heading(), props); List<QuerySource> relations = new RelationParser().process(querySpec.getFrom().get(), null); String[] indices = new String[relations.size()]; for(int i=0; i<relations.size(); i++) indices[i] = relations.get(i).getSource(); new SelectParser().process(querySpec.getSelect(), state); IndicesAliasesResponse response; if(querySpec.getWhere().isPresent()){ QueryBuilder query = new WhereParser().process(querySpec.getWhere().get(), state).getQuery(); response = client.admin().indices().prepareAliases().addAlias(indices, alias, query).execute().actionGet(); }else{ response = client.admin().indices().prepareAliases().addAlias(indices, alias).execute().actionGet(); } if(!response.isAcknowledged()) throw new SQLException("Elasticsearch failed to create the specified alias"); this.statement.getConnection().getTypeMap(); // trigger a reload of the table&column set for the connection return 0; // the number of altered rows }
private PlanBuilder aggregate(PlanBuilder subPlan, QuerySpecification node) { List<List<FieldOrExpression>> groupingSets = analysis.getGroupingSets(node); if (groupingSets.isEmpty()) { return subPlan; } return aggregateGroupingSet(getOnlyElement(groupingSets), subPlan, node); }
@Override protected RelationPlan visitQuerySpecification(QuerySpecification node, Void context) { PlanBuilder subPlan = new QueryPlanner(analysis, symbolAllocator, idAllocator, metadata, session).process(node, null); ImmutableList.Builder<Symbol> outputSymbols = ImmutableList.builder(); for (FieldOrExpression fieldOrExpression : analysis.getOutputExpressions(node)) { outputSymbols.add(subPlan.translate(fieldOrExpression)); } return new RelationPlan(subPlan.getRoot(), analysis.getOutputDescriptor(node), outputSymbols.build(), subPlan.getSampleWeight()); }
private List<List<FieldOrExpression>> analyzeGroupBy(QuerySpecification node, RelationType tupleDescriptor, AnalysisContext context, List<FieldOrExpression> outputExpressions) { List<Set<Set<Expression>>> enumeratedGroupingSets = node.getGroupBy().stream() .map(GroupingElement::enumerateGroupingSets) .distinct() .collect(toImmutableList()); // compute cross product of enumerated grouping sets, if there are any List<List<Expression>> computedGroupingSets = ImmutableList.of(); if (!enumeratedGroupingSets.isEmpty()) { computedGroupingSets = Sets.cartesianProduct(enumeratedGroupingSets).stream() .map(groupingSetList -> groupingSetList.stream() .flatMap(Collection::stream) .distinct() .collect(toImmutableList())) .distinct() .collect(toImmutableList()); } // if there are aggregates, but no grouping columns, create a grand total grouping set if (computedGroupingSets.isEmpty() && !extractAggregates(node).isEmpty()) { computedGroupingSets = ImmutableList.of(ImmutableList.of()); } if (computedGroupingSets.size() > 1) { throw new SemanticException(NOT_SUPPORTED, node, "Grouping by multiple sets of columns is not yet supported"); } List<List<FieldOrExpression>> analyzedGroupingSets = computedGroupingSets.stream() .map(groupingSet -> analyzeGroupingColumns(groupingSet, node, tupleDescriptor, context, outputExpressions)) .collect(toImmutableList()); analysis.setGroupingSets(node, analyzedGroupingSets); return analyzedGroupingSets; }
private List<FieldOrExpression> analyzeGroupingColumns(List<Expression> groupingColumns, QuerySpecification node, RelationType tupleDescriptor, AnalysisContext context, List<FieldOrExpression> outputExpressions) { ImmutableList.Builder<FieldOrExpression> groupingColumnsBuilder = ImmutableList.builder(); for (Expression groupingColumn : groupingColumns) { // first, see if this is an ordinal FieldOrExpression groupByExpression; if (groupingColumn instanceof LongLiteral) { long ordinal = ((LongLiteral) groupingColumn).getValue(); if (ordinal < 1 || ordinal > outputExpressions.size()) { throw new SemanticException(INVALID_ORDINAL, groupingColumn, "GROUP BY position %s is not in select list", ordinal); } groupByExpression = outputExpressions.get((int) (ordinal - 1)); } else { ExpressionAnalysis expressionAnalysis = analyzeExpression(groupingColumn, tupleDescriptor, context); analysis.recordSubqueries(node, expressionAnalysis); groupByExpression = new FieldOrExpression(groupingColumn); } Type type; if (groupByExpression.isExpression()) { Analyzer.verifyNoAggregatesOrWindowFunctions(metadata, groupByExpression.getExpression(), "GROUP BY"); type = analysis.getType(groupByExpression.getExpression()); } else { type = tupleDescriptor.getFieldByIndex(groupByExpression.getFieldIndex()).getType(); } if (!type.isComparable()) { throw new SemanticException(TYPE_MISMATCH, node, "%s is not comparable, and therefore cannot be used in GROUP BY", type); } groupingColumnsBuilder.add(groupByExpression); } return groupingColumnsBuilder.build(); }
private RelationType computeOutputDescriptor(QuerySpecification node, RelationType inputTupleDescriptor) { ImmutableList.Builder<Field> outputFields = ImmutableList.builder(); for (SelectItem item : node.getSelect().getSelectItems()) { if (item instanceof AllColumns) { // expand * and T.* Optional<QualifiedName> starPrefix = ((AllColumns) item).getPrefix(); for (Field field : inputTupleDescriptor.resolveFieldsWithPrefix(starPrefix)) { outputFields.add(Field.newUnqualified(field.getName(), field.getType())); } } else if (item instanceof SingleColumn) { SingleColumn column = (SingleColumn) item; Expression expression = column.getExpression(); Optional<String> alias = column.getAlias(); if (!alias.isPresent()) { QualifiedName name = null; if (expression instanceof QualifiedNameReference) { name = ((QualifiedNameReference) expression).getName(); } else if (expression instanceof DereferenceExpression) { name = DereferenceExpression.getQualifiedName((DereferenceExpression) expression); } if (name != null) { alias = Optional.of(getLast(name.getOriginalParts())); } } outputFields.add(Field.newUnqualified(alias, analysis.getType(expression))); // TODO don't use analysis as a side-channel. Use outputExpressions to look up the type } else { throw new IllegalArgumentException("Unsupported SelectItem type: " + item.getClass().getName()); } } return new RelationType(outputFields.build()); }
private RelationType analyzeFrom(QuerySpecification node, AnalysisContext context) { RelationType fromDescriptor = new RelationType(); if (node.getFrom().isPresent()) { fromDescriptor = process(node.getFrom().get(), context); } return fromDescriptor; }
private void analyzeAggregations(QuerySpecification node, RelationType tupleDescriptor, List<List<FieldOrExpression>> groupingSets, List<FieldOrExpression> outputExpressions, List<FieldOrExpression> orderByExpressions, AnalysisContext context, Set<Expression> columnReferences) { List<FunctionCall> aggregates = extractAggregates(node); if (context.isApproximate()) { if (aggregates.stream().anyMatch(FunctionCall::isDistinct)) { throw new SemanticException(NOT_SUPPORTED, node, "DISTINCT aggregations not supported for approximate queries"); } } ImmutableList<FieldOrExpression> allGroupingColumns = groupingSets.stream() .flatMap(Collection::stream) .distinct() .collect(toImmutableList()); // is this an aggregation query? if (!groupingSets.isEmpty()) { // ensure SELECT, ORDER BY and HAVING are constant with respect to group // e.g, these are all valid expressions: // SELECT f(a) GROUP BY a // SELECT f(a + 1) GROUP BY a + 1 // SELECT a + sum(b) GROUP BY a for (FieldOrExpression fieldOrExpression : Iterables.concat(outputExpressions, orderByExpressions)) { verifyAggregations(node, allGroupingColumns, tupleDescriptor, fieldOrExpression, columnReferences); } if (node.getHaving().isPresent()) { verifyAggregations(node, allGroupingColumns, tupleDescriptor, new FieldOrExpression(node.getHaving().get()), columnReferences); } } }
private void verifyAggregations( QuerySpecification node, List<FieldOrExpression> groupByExpressions, RelationType tupleDescriptor, FieldOrExpression fieldOrExpression, Set<Expression> columnReferences) { AggregationAnalyzer analyzer = new AggregationAnalyzer(groupByExpressions, metadata, tupleDescriptor, columnReferences); if (fieldOrExpression.isExpression()) { analyzer.analyze(fieldOrExpression.getExpression()); } else { int fieldIndex = fieldOrExpression.getFieldIndex(); if (!analyzer.analyze(fieldIndex)) { Field field = tupleDescriptor.getFieldByIndex(fieldIndex); if (field.getRelationAlias().isPresent()) { if (field.getName().isPresent()) { throw new SemanticException(MUST_BE_AGGREGATE_OR_GROUP_BY, node, "Column '%s.%s' not in GROUP BY clause", field.getRelationAlias().get(), field.getName().get()); } else { throw new SemanticException(MUST_BE_AGGREGATE_OR_GROUP_BY, node, "Columns from '%s' not in GROUP BY clause", field.getRelationAlias().get()); } } else { if (field.getName().isPresent()) { throw new SemanticException(MUST_BE_AGGREGATE_OR_GROUP_BY, node, "Column '%s' not in GROUP BY clause", field.getName().get()); } else { throw new SemanticException(MUST_BE_AGGREGATE_OR_GROUP_BY, node, "Some columns from FROM clause not in GROUP BY clause"); } } } } }
@Override protected Void visitQuerySpecification(QuerySpecification node, Integer indent) { process(node.getSelect(), indent); if (node.getFrom().isPresent()) { append(indent, "FROM"); builder.append('\n'); append(indent, " "); process(node.getFrom().get(), indent); } builder.append('\n'); if (node.getWhere().isPresent()) { append(indent, "WHERE " + formatExpression(node.getWhere().get())) .append('\n'); } if (!node.getGroupBy().isEmpty()) { append(indent, "GROUP BY " + formatGroupBy(node.getGroupBy())).append('\n'); } if (node.getHaving().isPresent()) { append(indent, "HAVING " + formatExpression(node.getHaving().get())) .append('\n'); } if (!node.getOrderBy().isEmpty()) { append(indent, "ORDER BY " + formatSortItems(node.getOrderBy())) .append('\n'); } if (node.getLimit().isPresent()) { append(indent, "LIMIT " + node.getLimit().get()) .append('\n'); } return null; }
@Override public Node visitQueryNoWith(SqlBaseParser.QueryNoWithContext context) { QueryBody term = (QueryBody) visit(context.queryTerm()); if (term instanceof QuerySpecification) { // When we have a simple query specification // followed by order by limit, fold the order by and limit // clauses into the query specification (analyzer/planner // expects this structure to resolve references with respect // to columns defined in the query specification) QuerySpecification query = (QuerySpecification) term; return new Query( getLocation(context), Optional.<With>empty(), new QuerySpecification( getLocation(context), query.getSelect(), query.getFrom(), query.getWhere(), query.getGroupBy(), query.getHaving(), visit(context.sortItem(), SortItem.class), getTextIfPresent(context.limit)), ImmutableList.of(), Optional.<String>empty(), getTextIfPresent(context.confidence) .map(confidence -> new Approximate(getLocation(context), confidence))); } return new Query( getLocation(context), Optional.<With>empty(), term, visit(context.sortItem(), SortItem.class), getTextIfPresent(context.limit), getTextIfPresent(context.confidence) .map(confidence -> new Approximate(getLocation(context), confidence))); }
public static Query simpleQuery(Select select, Relation from, Optional<Expression> where, List<GroupingElement> groupBy, Optional<Expression> having, List<SortItem> ordering, Optional<String> limit) { return query(new QuerySpecification( select, Optional.of(from), where, groupBy, having, ordering, limit)); }
private static QuerySpecification createSelect123() { return new QuerySpecification( selectList(new LongLiteral("123")), Optional.empty(), Optional.empty(), ImmutableList.of(), Optional.empty(), ImmutableList.of(), Optional.empty() ); }
private PlanBuilder sort(PlanBuilder subPlan, QuerySpecification node) { return sort(subPlan, node.getOrderBy(), node.getLimit(), analysis.getOrderByExpressions(node)); }
private PlanBuilder limit(PlanBuilder subPlan, QuerySpecification node) { return limit(subPlan, node.getOrderBy(), node.getLimit()); }
public void setAggregates(QuerySpecification node, List<FunctionCall> aggregates) { this.aggregates.put(node, aggregates); }
public List<FunctionCall> getAggregates(QuerySpecification query) { return aggregates.get(query); }
public void setGroupingSets(QuerySpecification node, List<List<FieldOrExpression>> expressions) { groupByExpressions.put(node, expressions); }
public List<List<FieldOrExpression>> getGroupingSets(QuerySpecification node) { return groupByExpressions.get(node); }
public Expression getWhere(QuerySpecification node) { return where.get(node); }
public void setHaving(QuerySpecification node, Expression expression) { having.put(node, expression); }
public void setWindowFunctions(QuerySpecification node, List<FunctionCall> functions) { windowFunctions.put(node, functions); }
public Map<QuerySpecification, List<FunctionCall>> getWindowFunctions() { return windowFunctions; }
public List<FunctionCall> getWindowFunctions(QuerySpecification query) { return windowFunctions.get(query); }
public Expression getHaving(QuerySpecification query) { return having.get(query); }
private void analyzeWindowFunctions(QuerySpecification node, List<FieldOrExpression> outputExpressions, List<FieldOrExpression> orderByExpressions) { WindowFunctionExtractor extractor = new WindowFunctionExtractor(); for (FieldOrExpression fieldOrExpression : Iterables.concat(outputExpressions, orderByExpressions)) { if (fieldOrExpression.isExpression()) { extractor.process(fieldOrExpression.getExpression(), null); new WindowFunctionValidator().process(fieldOrExpression.getExpression(), analysis); } } List<FunctionCall> windowFunctions = extractor.getWindowFunctions(); for (FunctionCall windowFunction : windowFunctions) { Window window = windowFunction.getWindow().get(); WindowFunctionExtractor nestedExtractor = new WindowFunctionExtractor(); for (Expression argument : windowFunction.getArguments()) { nestedExtractor.process(argument, null); } for (Expression expression : window.getPartitionBy()) { nestedExtractor.process(expression, null); } for (SortItem sortItem : window.getOrderBy()) { nestedExtractor.process(sortItem.getSortKey(), null); } if (window.getFrame().isPresent()) { nestedExtractor.process(window.getFrame().get(), null); } if (!nestedExtractor.getWindowFunctions().isEmpty()) { throw new SemanticException(NESTED_WINDOW, node, "Cannot nest window functions inside window function '%s': %s", windowFunction, extractor.getWindowFunctions()); } if (windowFunction.isDistinct()) { throw new SemanticException(NOT_SUPPORTED, node, "DISTINCT in window function parameters not yet supported: %s", windowFunction); } if (window.getFrame().isPresent()) { analyzeWindowFrame(window.getFrame().get()); } List<TypeSignature> argumentTypes = Lists.transform(windowFunction.getArguments(), expression -> analysis.getType(expression).getTypeSignature()); FunctionKind kind = metadata.getFunctionRegistry().resolveFunction(windowFunction.getName(), argumentTypes, false).getKind(); if (kind != AGGREGATE && kind != APPROXIMATE_AGGREGATE && kind != WINDOW) { throw new SemanticException(MUST_BE_WINDOW_FUNCTION, node, "Not a window function: %s", windowFunction.getName()); } } analysis.setWindowFunctions(node, windowFunctions); }
private List<FieldOrExpression> analyzeSelect(QuerySpecification node, RelationType tupleDescriptor, AnalysisContext context) { ImmutableList.Builder<FieldOrExpression> outputExpressionBuilder = ImmutableList.builder(); for (SelectItem item : node.getSelect().getSelectItems()) { if (item instanceof AllColumns) { // expand * and T.* Optional<QualifiedName> starPrefix = ((AllColumns) item).getPrefix(); List<Field> fields = tupleDescriptor.resolveFieldsWithPrefix(starPrefix); if (fields.isEmpty()) { if (starPrefix.isPresent()) { throw new SemanticException(MISSING_TABLE, item, "Table '%s' not found", starPrefix.get()); } else { throw new SemanticException(WILDCARD_WITHOUT_FROM, item, "SELECT * not allowed in queries without FROM clause"); } } for (Field field : fields) { int fieldIndex = tupleDescriptor.indexOf(field); outputExpressionBuilder.add(new FieldOrExpression(fieldIndex)); if (node.getSelect().isDistinct() && !field.getType().isComparable()) { throw new SemanticException(TYPE_MISMATCH, node.getSelect(), "DISTINCT can only be applied to comparable types (actual: %s)", field.getType()); } } } else if (item instanceof SingleColumn) { SingleColumn column = (SingleColumn) item; ExpressionAnalysis expressionAnalysis = analyzeExpression(column.getExpression(), tupleDescriptor, context); analysis.recordSubqueries(node, expressionAnalysis); outputExpressionBuilder.add(new FieldOrExpression(column.getExpression())); Type type = expressionAnalysis.getType(column.getExpression()); if (node.getSelect().isDistinct() && !type.isComparable()) { throw new SemanticException(TYPE_MISMATCH, node.getSelect(), "DISTINCT can only be applied to comparable types (actual: %s): %s", type, column.getExpression()); } } else { throw new IllegalArgumentException("Unsupported SelectItem type: " + item.getClass().getName()); } } ImmutableList<FieldOrExpression> result = outputExpressionBuilder.build(); analysis.setOutputExpressions(node, result); return result; }
@Test public void testSelectWithRowType() throws Exception { assertStatement("SELECT col1.f1, col2, col3.f1.f2.f3 FROM table1", new Query( Optional.empty(), new QuerySpecification( selectList( new DereferenceExpression(new QualifiedNameReference(QualifiedName.of("col1")), "f1"), new QualifiedNameReference(QualifiedName.of("col2")), new DereferenceExpression( new DereferenceExpression(new DereferenceExpression(new QualifiedNameReference(QualifiedName.of("col3")), "f1"), "f2"), "f3")), Optional.of(new Table(QualifiedName.of("table1"))), Optional.empty(), ImmutableList.of(), Optional.empty(), ImmutableList.of(), Optional.empty()), ImmutableList.<SortItem>of(), Optional.empty(), Optional.empty())); assertStatement("SELECT col1.f1[0], col2, col3[2].f2.f3, col4[4] FROM table1", new Query( Optional.empty(), new QuerySpecification( selectList( new SubscriptExpression(new DereferenceExpression(new QualifiedNameReference(QualifiedName.of("col1")), "f1"), new LongLiteral("0")), new QualifiedNameReference(QualifiedName.of("col2")), new DereferenceExpression(new DereferenceExpression(new SubscriptExpression(new QualifiedNameReference(QualifiedName.of("col3")), new LongLiteral("2")), "f2"), "f3"), new SubscriptExpression(new QualifiedNameReference(QualifiedName.of("col4")), new LongLiteral("4")) ), Optional.of(new Table(QualifiedName.of("table1"))), Optional.empty(), ImmutableList.of(), Optional.empty(), ImmutableList.of(), Optional.empty()), ImmutableList.<SortItem>of(), Optional.empty(), Optional.empty())); assertStatement("SELECT test_row(11, 12).col0", new Query( Optional.empty(), new QuerySpecification( selectList( new DereferenceExpression(new FunctionCall(QualifiedName.of("test_row"), Lists.newArrayList(new LongLiteral("11"), new LongLiteral("12"))), "col0") ), Optional.empty(), Optional.empty(), ImmutableList.of(), Optional.empty(), ImmutableList.of(), Optional.empty()), ImmutableList.<SortItem>of(), Optional.empty(), Optional.empty())); }