/** * extracts a variable name from the provided expression * @param e * @return */ private String getVariableName(Expression e){ if(e instanceof DereferenceExpression){ // parse columns like 'reference.field' return SelectParser.visitDereferenceExpression((DereferenceExpression)e); }else if (e instanceof QualifiedNameReference){ return ((QualifiedNameReference)e).getName().toString(); } else return e.toString(); }
@Override protected Void visitDereferenceExpression(DereferenceExpression node, Void context) { if (columnReferences.contains(node)) { throw new SemanticException(EXPRESSION_NOT_CONSTANT, expression, "Constant expression cannot contain column references"); } process(node.getBase(), context); return null; }
@Override protected Void visitDereferenceExpression(DereferenceExpression node, ImmutableSet.Builder<QualifiedName> builder) { if (columnReferences.contains(node)) { builder.add(DereferenceExpression.getQualifiedName(node)); } else { process(node.getBase(), builder); } return null; }
@Override protected Boolean visitDereferenceExpression(DereferenceExpression node, Void context) { if (columnReferences.contains(node)) { return isField(DereferenceExpression.getQualifiedName(node)); } // Allow SELECT col1.f1 FROM table1 GROUP BY col1 return process(node.getBase(), context); }
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()); }
@Override protected String visitDereferenceExpression(DereferenceExpression node, StackableAstVisitorContext<Integer> indent) { String baseString = process(node.getBase(), indent); return baseString + "." + formatIdentifier(node.getFieldName()); }
/** * Parses predicats of types =, >, >=, <, <= and <> * @param compareExp * @param state * @return */ private QueryWrapper processComparison(ComparisonExpression compareExp, QueryState state) { String field = getVariableName(compareExp.getLeft()); FieldAndType fat = getFieldAndType(field, state); field = fat.getFieldName(); if(compareExp.getRight() instanceof QualifiedNameReference || compareExp.getRight() instanceof DereferenceExpression){ state.addException("Matching two columns is not supported : "+compareExp); return null; } // get value of the expression Object value = getLiteralValue(compareExp.getRight(), state); if(state.hasException()) return null; QueryBuilder comparison = null; String[] types = new String[state.getSources().size()]; for(int i=0; i<types.length; i++) types[i] = state.getSources().get(i).getSource(); if(compareExp.getType() == ComparisonExpression.Type.EQUAL){ if(field.equals(Heading.ID)) comparison = QueryBuilders.idsQuery(types).ids((String)value); else if(field.equals(Heading.SEARCH)) comparison = QueryBuilders.queryStringQuery((String)value); else if(value instanceof String) comparison = queryForString(field, (String)value); else comparison = QueryBuilders.termQuery(field, value); }else if(compareExp.getType() == ComparisonExpression.Type.GREATER_THAN_OR_EQUAL){ comparison = QueryBuilders.rangeQuery(field).from(value); }else if(compareExp.getType() == ComparisonExpression.Type.LESS_THAN_OR_EQUAL){ comparison = QueryBuilders.rangeQuery(field).to(value); }else if(compareExp.getType() == ComparisonExpression.Type.GREATER_THAN){ comparison = QueryBuilders.rangeQuery(field).gt(value); }else if(compareExp.getType() == ComparisonExpression.Type.LESS_THAN){ comparison = QueryBuilders.rangeQuery(field).lt(value); }else if(compareExp.getType() == ComparisonExpression.Type.NOT_EQUAL){ if(field.equals(Heading.ID)){ state.addException("Matching document _id using '<>' is not supported"); return null; } comparison = QueryBuilders.notQuery(QueryBuilders.termQuery(field, value)); }; if(fat.getFieldType() == Types.REF) return new QueryWrapper( comparison, field.split("\\.")[0]); return new QueryWrapper(comparison); }
public static String visitDereferenceExpression(DereferenceExpression node){ if(node.getBase() instanceof QualifiedNameReference) { return ((QualifiedNameReference)node.getBase()).getName().toString()+"."+node.getFieldName(); }else return visitDereferenceExpression((DereferenceExpression)node.getBase())+"."+node.getFieldName(); }
@Override protected IComparison visitExpression(Expression node, QueryState state) { if( node instanceof LogicalBinaryExpression){ LogicalBinaryExpression boolExp = (LogicalBinaryExpression)node; IComparison left = boolExp.getLeft().accept(this, state); IComparison right = boolExp.getRight().accept(this, state); return new BooleanComparison(left, right, boolExp.getType() == Type.AND); }else if( node instanceof ComparisonExpression){ ComparisonExpression compareExp = (ComparisonExpression)node; Column column = new SelectParser().visitExpression(compareExp.getLeft(), state); Column leftCol = state.getHeading().getColumnByLabel(column.getLabel()); if(leftCol == null){ state.addException("Having reference "+column+" not found in SELECT clause"); return null; } // right hand side is a concrete literal to compare with if(compareExp.getRight() instanceof Literal){ Object value; if(compareExp.getRight() instanceof LongLiteral) value = ((LongLiteral)compareExp.getRight()).getValue(); else if(compareExp.getRight() instanceof BooleanLiteral) value = ((BooleanLiteral)compareExp.getRight()).getValue(); else if(compareExp.getRight() instanceof DoubleLiteral) value = ((DoubleLiteral)compareExp.getRight()).getValue(); else if(compareExp.getRight() instanceof StringLiteral) value = ((StringLiteral)compareExp.getRight()).getValue(); else { state.addException("Unable to get value from "+compareExp.getRight()); return null; } return new SimpleComparison(leftCol, compareExp.getType(), (Number)value); // right hand side refers to another column } else if(compareExp.getRight() instanceof DereferenceExpression || compareExp.getRight() instanceof QualifiedNameReference){ String col2; if(compareExp.getLeft() instanceof DereferenceExpression){ // parse columns like 'reference.field' col2 = SelectParser.visitDereferenceExpression((DereferenceExpression)compareExp.getRight()); }else{ col2 = ((QualifiedNameReference)compareExp.getRight()).getName().toString(); } col2 = Heading.findOriginal(state.originalSql(), col2, "having.+", "\\W"); Column rightCol = state.getHeading().getColumnByLabel(col2); if(rightCol == null){ state.addException("column "+col2+" not found in SELECT clause"); return null; } return new SimpleComparison(leftCol, compareExp.getType(), rightCol); }else { // unknown right hand side so state.addException("Unable to get value from "+compareExp.getRight()); return null; } }else if( node instanceof NotExpression){ state.addException("NOT is currently not supported, use '<>' instead"); }else{ state.addException("Unable to parse "+node+" ("+node.getClass().getName()+") is not a supported expression"); } return null; }
@Override protected OrderBy visitSortItem(SortItem si, QueryState state){ String orderKey = null; if(si.getSortKey() instanceof DereferenceExpression){ orderKey = SelectParser.visitDereferenceExpression((DereferenceExpression)si.getSortKey()); }else if (si.getSortKey() instanceof FunctionCall){ orderKey = si.getSortKey().toString().replaceAll("\"",""); }else if(si.getSortKey() instanceof SearchedCaseExpression){ //... order by CASE WHEN field IS NULL THEN 1 ELSE 0 END // TODO: improve this quick and dirty implementation SearchedCaseExpression sce = (SearchedCaseExpression)si.getSortKey(); for(WhenClause when : sce.getWhenClauses()){ orderKey = SelectParser.visitDereferenceExpression( (DereferenceExpression)((IsNullPredicate)when.getOperand()).getValue()); } }else if(si.getSortKey() instanceof QualifiedNameReference){ orderKey = ((QualifiedNameReference)si.getSortKey()).getName().toString(); }else { state.addException("Order statement with type '"+si.getSortKey().getClass().getName()+"' is not supported"); return null; } // fix case orderKey = Heading.findOriginal(state.originalSql()+";", orderKey, "order by.+", "\\W"); // remove any table reference or alias if(orderKey.contains(".")){ String prefix = orderKey.split("\\.")[0]; for(QuerySource tr : state.getSources()){ if(tr.getAlias() != null){ if(prefix.equals(tr.getAlias())) orderKey = orderKey.substring(orderKey.indexOf('.')+1); }else if (tr.getSource() != null && prefix.equals(tr.getSource())) orderKey = orderKey.substring(orderKey.indexOf('.')+1); } } // select column to order on Column column = state.getHeading().getColumnByLabel(orderKey); if(column != null){ if(si.getOrdering().toString().startsWith("ASC")){ return new OrderBy(column.getColumn(), SortOrder.ASC, column.getIndex()); }else{ return new OrderBy(column.getColumn(), SortOrder.DESC, column.getIndex()); } }else{ state.addException("Order key '"+orderKey+"' is not specified in SELECT clause"); return null; } }
@Override protected Object visitDereferenceExpression(DereferenceExpression node, Object context) { // Dereference is never a Symbol return node; }
public AggregationAnalyzer(List<FieldOrExpression> groupByExpressions, Metadata metadata, RelationType tupleDescriptor, Set<Expression> columnReferences) { requireNonNull(groupByExpressions, "groupByExpressions is null"); requireNonNull(metadata, "metadata is null"); requireNonNull(tupleDescriptor, "tupleDescriptor is null"); requireNonNull(columnReferences, "columnReferences is null"); this.tupleDescriptor = tupleDescriptor; this.metadata = metadata; this.columnReferences = ImmutableSet.copyOf(columnReferences); this.expressions = groupByExpressions.stream() .filter(FieldOrExpression::isExpression) .map(FieldOrExpression::getExpression) .collect(toImmutableList()); ImmutableList.Builder<Integer> fieldIndexes = ImmutableList.builder(); fieldIndexes.addAll(groupByExpressions.stream() .filter(FieldOrExpression::isFieldReference) .map(FieldOrExpression::getFieldIndex) .iterator()); // For a query like "SELECT * FROM T GROUP BY a", groupByExpressions will contain "a", // and the '*' will be expanded to Field references. Therefore we translate all simple name expressions // in the group by clause to fields they reference so that the expansion from '*' can be matched against them for (Expression expression : Iterables.filter(expressions, columnReferences::contains)) { QualifiedName name; if (expression instanceof QualifiedNameReference) { name = ((QualifiedNameReference) expression).getName(); } else { name = DereferenceExpression.getQualifiedName(checkType(expression, DereferenceExpression.class, "expression")); } List<Field> fields = tupleDescriptor.resolveFields(name); checkState(fields.size() <= 1, "Found more than one field for name '%s': %s", name, fields); if (fields.size() == 1) { Field field = Iterables.getOnlyElement(fields); fieldIndexes.add(tupleDescriptor.indexOf(field)); } } this.fieldIndexes = fieldIndexes.build(); }
@Override protected Type visitDereferenceExpression(DereferenceExpression node, StackableAstVisitorContext<AnalysisContext> context) { QualifiedName qualifiedName = DereferenceExpression.getQualifiedName(node); // If this Dereference looks like column reference, try match it to column first. if (qualifiedName != null) { List<Field> matches = tupleDescriptor.resolveFields(qualifiedName); if (matches.size() > 1) { throw new SemanticException(AMBIGUOUS_ATTRIBUTE, node, "Column '%s' is ambiguous", node); } if (matches.size() == 1) { Field field = Iterables.getOnlyElement(matches); int fieldIndex = tupleDescriptor.indexOf(field); resolvedNames.put(node, fieldIndex); expressionTypes.put(node, field.getType()); return field.getType(); } assertColumnPrefix(qualifiedName, node); } Type baseType = process(node.getBase(), context); if (!(baseType instanceof RowType)) { throw new SemanticException(TYPE_MISMATCH, node.getBase(), "Expression %s is not of type ROW", node.getBase()); } RowType rowType = (RowType) baseType; Type rowFieldType = null; for (RowField rowField : rowType.getFields()) { if (rowField.getName().equals(Optional.of(node.getFieldName()))) { rowFieldType = rowField.getType(); break; } } if (rowFieldType == null) { throw createMissingAttributeException(node); } expressionTypes.put(node, rowFieldType); return rowFieldType; }
@Override public Node visitDereference(SqlBaseParser.DereferenceContext context) { return new DereferenceExpression(getLocation(context), (Expression) visit(context.base), context.fieldName.getText()); }
@Override protected String visitDereferenceExpression(DereferenceExpression node, Boolean unmangleNames) { String baseString = process(node.getBase(), unmangleNames); return baseString + "." + formatIdentifier(node.getFieldName()); }
@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())); }