Skip to content

Commit

Permalink
Allow simple expression in VALUES
Browse files Browse the repository at this point in the history
In SQL, a simple expression is a special case of a row expression with degree 1.

    'foo' = ROW ('foo')

This means that the following syntax is valid:

    VALUES 1, 2, 3, 4

and is equivalent to

    VALUES ROW (1), ROW (2), ROW (3), ROW (4)

This commit adds grammar and analyzer support for row expressions and implements
the functionality needed for VALUES to be able to work with them.
  • Loading branch information
martint committed Jan 8, 2015
1 parent 7447657 commit cc939dc
Show file tree
Hide file tree
Showing 19 changed files with 164 additions and 92 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@

import java.lang.invoke.MethodHandle;
import java.util.Map;
import java.util.Optional;

import static com.facebook.presto.metadata.FunctionRegistry.mangleFieldAccessor;
import static com.facebook.presto.operator.scalar.JsonExtract.BooleanJsonExtractor;
Expand Down Expand Up @@ -65,7 +66,7 @@ public RowFieldAccessor(RowType type, String fieldName)
Type returnType = null;
int index = 0;
for (RowField field : type.getFields()) {
if (field.getName().equals(fieldName)) {
if (field.getName().equals(Optional.of(fieldName))) {
returnType = field.getType();
break;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
import com.facebook.presto.sql.tree.NullLiteral;
import com.facebook.presto.sql.tree.QualifiedName;
import com.facebook.presto.sql.tree.QualifiedNameReference;
import com.facebook.presto.sql.tree.Row;
import com.facebook.presto.sql.tree.SearchedCaseExpression;
import com.facebook.presto.sql.tree.SimpleCaseExpression;
import com.facebook.presto.sql.tree.SortItem;
Expand Down Expand Up @@ -198,6 +199,19 @@ public Type process(Node node, @Nullable AnalysisContext context)
return super.process(node, context);
}

@Override
protected Type visitRow(Row node, AnalysisContext context)
{
List<Type> types = node.getItems().stream()
.map((child) -> process(child, context))
.collect(toImmutableList());

Type type = new RowType(types, Optional.empty());
expressionTypes.put(node, type);

return type;
}

@Override
protected Type visitCurrentTime(CurrentTime node, AnalysisContext context)
{
Expand Down Expand Up @@ -269,7 +283,7 @@ else if (matches.size() > 1) {
RowType rowType = checkType(field.getType(), RowType.class, "field.getType()");
Type rowFieldType = null;
for (RowField rowField : rowType.getFields()) {
if (rowField.getName().equals(node.getName().getSuffix())) {
if (rowField.getName().equals(Optional.of(node.getName().getSuffix()))) {
rowFieldType = rowField.getType();
break;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@
import com.facebook.presto.sql.tree.QualifiedName;
import com.facebook.presto.sql.tree.Query;
import com.facebook.presto.sql.tree.Relation;
import com.facebook.presto.sql.tree.Row;
import com.facebook.presto.sql.tree.SelectItem;
import com.facebook.presto.sql.tree.ShowCatalogs;
import com.facebook.presto.sql.tree.ShowColumns;
Expand Down Expand Up @@ -317,7 +316,7 @@ protected TupleDescriptor visitShowFunctions(ShowFunctions node, AnalysisContext
@Override
protected TupleDescriptor visitShowSession(ShowSession node, AnalysisContext context)
{
ImmutableList.Builder<Row> rows = ImmutableList.builder();
ImmutableList.Builder<Expression> rows = ImmutableList.builder();
for (Entry<String, String> property : new TreeMap<>(session.getSystemProperties()).entrySet()) {
rows.add(row(
new StringLiteral(property.getKey()),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@
import com.facebook.presto.sql.tree.Query;
import com.facebook.presto.sql.tree.QuerySpecification;
import com.facebook.presto.sql.tree.Relation;
import com.facebook.presto.sql.tree.Row;
import com.facebook.presto.sql.tree.SampledRelation;
import com.facebook.presto.sql.tree.SelectItem;
import com.facebook.presto.sql.tree.SingleColumn;
Expand All @@ -70,7 +69,8 @@
import com.facebook.presto.sql.tree.WindowFrame;
import com.facebook.presto.type.ArrayType;
import com.facebook.presto.type.MapType;
import com.google.common.base.Joiner;
import com.facebook.presto.type.RowType;
import com.facebook.presto.util.ImmutableCollectors;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableMultimap;
Expand Down Expand Up @@ -123,12 +123,11 @@
import static com.facebook.presto.sql.tree.FrameBound.Type.UNBOUNDED_PRECEDING;
import static com.facebook.presto.sql.tree.WindowFrame.Type.RANGE;
import static com.facebook.presto.type.UnknownType.UNKNOWN;
import static com.facebook.presto.util.ImmutableCollectors.toImmutableList;
import static com.facebook.presto.util.Types.checkType;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import static com.google.common.collect.Iterables.elementsEqual;
import static com.google.common.collect.Iterables.transform;

public class TupleAnalyzer
extends DefaultTraversalVisitor<TupleDescriptor, AnalysisContext>
Expand Down Expand Up @@ -531,35 +530,34 @@ protected TupleDescriptor visitValues(Values node, AnalysisContext context)
{
checkState(node.getRows().size() >= 1);

TupleAnalyzer analyzer = new TupleAnalyzer(analysis, session, metadata, sqlParser, experimentalSyntaxEnabled);
Set<Type> types = node.getRows().stream()
.map((row) -> analyzeExpression(row, new TupleDescriptor(), context).getType(row))
.collect(ImmutableCollectors.toImmutableSet());

// Use the first descriptor as the output descriptor for the VALUES
TupleDescriptor outputDescriptor = analyzer.process(node.getRows().get(0), context).withOnlyVisibleFields();
Iterable<Type> types = transform(outputDescriptor.getVisibleFields(), Field::getType);

for (Row row : Iterables.skip(node.getRows(), 1)) {
TupleDescriptor descriptor = analyzer.process(row, context);
Iterable<Type> rowTypes = transform(descriptor.getVisibleFields(), Field::getType);
if (!elementsEqual(types, rowTypes)) {
throw new SemanticException(MISMATCHED_SET_COLUMN_TYPES, node, "Values rows have mismatched types: " +
"Expected: (" + Joiner.on(", ").join(types) + "), " +
"Actual: (" + Joiner.on(", ").join(rowTypes) + ")");
}
if (types.size() > 1) {
throw new SemanticException(MISMATCHED_SET_COLUMN_TYPES,
node,
"Values rows have mismatched types: %s vs %s",
Iterables.get(types, 0),
Iterables.get(types, 1));
}

analysis.setOutputDescriptor(node, outputDescriptor);
return outputDescriptor;
}
Type type = Iterables.getOnlyElement(types);

@Override
protected TupleDescriptor visitRow(Row node, AnalysisContext context)
{
ImmutableList.Builder<Field> outputFields = ImmutableList.builder();
for (Expression expression : node.getItems()) {
ExpressionAnalysis expressionAnalysis = analyzeExpression(expression, new TupleDescriptor(), context);
outputFields.add(Field.newUnqualified(Optional.empty(), expressionAnalysis.getType(expression)));
List<Field> fields;
if (type instanceof RowType) {
fields = ((RowType) type).getFields().stream()
.map(RowType.RowField::getType)
.map((valueType) -> Field.newUnqualified(Optional.empty(), valueType))
.collect(toImmutableList());
}
return new TupleDescriptor(outputFields.build());
else {
fields = ImmutableList.of(Field.newUnqualified(Optional.empty(), type));
}

TupleDescriptor descriptor = new TupleDescriptor(fields);
analysis.setOutputDescriptor(node, descriptor);
return descriptor;
}

private void analyzeWindowFunctions(QuerySpecification node, List<FieldOrExpression> outputExpressions, List<FieldOrExpression> orderByExpressions)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
import com.facebook.presto.sql.tree.NullLiteral;
import com.facebook.presto.sql.tree.QualifiedName;
import com.facebook.presto.sql.tree.QualifiedNameReference;
import com.facebook.presto.sql.tree.Row;
import com.facebook.presto.sql.tree.SearchedCaseExpression;
import com.facebook.presto.sql.tree.SimpleCaseExpression;
import com.facebook.presto.sql.tree.StringLiteral;
Expand Down Expand Up @@ -773,6 +774,12 @@ protected Object visitArrayConstructor(ArrayConstructor node, Object context)
return visitFunctionCall(new FunctionCall(QualifiedName.of(ArrayConstructor.ARRAY_CONSTRUCTOR), node.getValues()), context);
}

@Override
protected Object visitRow(Row node, Object context)
{
throw new UnsupportedOperationException("Row expressions not yet supported");
}

@Override
protected Object visitSubscriptExpression(SubscriptExpression node, Object context)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -434,11 +434,18 @@ protected RelationPlan visitValues(Values node, Void context)
}

ImmutableList.Builder<List<Expression>> rows = ImmutableList.builder();
for (Row row : node.getRows()) {
for (Expression row : node.getRows()) {
ImmutableList.Builder<Expression> values = ImmutableList.builder();
for (Expression expression : row.getItems()) {
values.add(evaluateConstantExpression(expression));

if (row instanceof Row) {
for (Expression expression : ((Row) row).getItems()) {
values.add(evaluateConstantExpression(expression));
}
}
else {
values.add(row);
}

rows.add(values.build());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import com.google.common.collect.ImmutableList;

import java.util.List;
import java.util.Optional;

import static com.facebook.presto.type.RowType.RowField;
import static com.facebook.presto.util.Types.checkType;
Expand All @@ -44,20 +45,27 @@ public String getName()
public RowType createType(List<Type> types, List<Object> literals)
{
checkArgument(!types.isEmpty(), "types is empty");

if (literals.isEmpty()) {
return new RowType(types, Optional.empty());
}

checkArgument(types.size() == literals.size(), "types and literals must be matched in size");

ImmutableList.Builder<String> builder = ImmutableList.builder();
for (Object literal : literals) {
builder.add(checkType(literal, String.class, "literal"));
}
return new RowType(types, builder.build());
return new RowType(types, Optional.of(builder.build()));
}

public List<ParametricFunction> createFunctions(Type type)
{
RowType rowType = checkType(type, RowType.class, "type");
ImmutableList.Builder<ParametricFunction> builder = ImmutableList.builder();
for (RowField field : rowType.getFields()) {
builder.add(new RowFieldAccessor(rowType, field.getName()));
field.getName()
.ifPresent(name -> builder.add(new RowFieldAccessor(rowType, field.getName().get())));
}
return builder.build();
}
Expand Down
35 changes: 27 additions & 8 deletions presto-main/src/main/java/com/facebook/presto/type/RowType.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import static com.facebook.presto.spi.StandardErrorCode.INTERNAL_ERROR;
import static com.facebook.presto.type.TypeJsonUtils.createBlock;
Expand All @@ -44,19 +45,30 @@
import static com.google.common.base.Preconditions.checkNotNull;
import static java.lang.String.format;

/**
* As defined in ISO/IEC FCD 9075-2 (SQL 2011), section 4.8
*/
public class RowType
extends AbstractVariableWidthType
{
private final List<RowField> fields;
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapperProvider().get();
private static final CollectionType COLLECTION_TYPE = OBJECT_MAPPER.getTypeFactory().constructCollectionType(List.class, Object.class);

public RowType(List<Type> fieldTypes, List<String> fieldNames)
public RowType(List<Type> fieldTypes, Optional<List<String>> fieldNames)
{
super(new TypeSignature("row", Lists.transform(fieldTypes, Type::getTypeSignature), ImmutableList.<Object>copyOf(fieldNames)), Slice.class);
super(new TypeSignature(
"row",
Lists.transform(fieldTypes, Type::getTypeSignature),
fieldNames.orElse(ImmutableList.of()).stream()
.map(Object.class::cast)
.collect(toImmutableList())),
Slice.class);

ImmutableList.Builder<RowField> builder = ImmutableList.builder();
for (int i = 0; i < fieldTypes.size(); i++) {
builder.add(new RowField(fieldTypes.get(i), fieldNames.get(i)));
final int index = i;
builder.add(new RowField(fieldTypes.get(i), fieldNames.map((names) -> names.get(index))));
}
fields = builder.build();
}
Expand All @@ -67,7 +79,13 @@ public String getDisplayName()
// Convert to standard sql name
List<String> fields = new ArrayList<>();
for (int i = 0; i < this.fields.size(); i++) {
fields.add(this.fields.get(i).getName() + " " + this.fields.get(i).getType().getDisplayName());
RowField field = this.fields.get(i);
if (field.getName().isPresent()) {
fields.add(field.getName() + " " + field.getType().getDisplayName());
}
else {
fields.add(field.getType().getDisplayName());
}
}
return "ROW(" + Joiner.on(", ").join(fields) + ")";
}
Expand Down Expand Up @@ -135,9 +153,9 @@ public List<RowField> getFields()
public static class RowField
{
private final Type type;
private final String name;
private final Optional<String> name;

public RowField(Type type, String name)
public RowField(Type type, Optional<String> name)
{
this.type = checkNotNull(type, "type is null");
this.name = checkNotNull(name, "name is null");
Expand All @@ -148,7 +166,7 @@ public Type getType()
return type;
}

public String getName()
public Optional<String> getName()
{
return name;
}
Expand All @@ -157,7 +175,8 @@ public String getName()
@Override
public boolean isComparable()
{
return Iterables.all(fields, new Predicate<RowField>() {
return Iterables.all(fields, new Predicate<RowField>()
{
@Override
public boolean apply(RowField field)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@

import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;

import static com.facebook.presto.block.BlockAssertions.createBooleansBlock;
import static com.facebook.presto.block.BlockAssertions.createDoublesBlock;
Expand Down Expand Up @@ -195,7 +196,7 @@ public void testDoubleMapMap()
public void testDoubleRowMap()
throws Exception
{
RowType innerRowType = new RowType(ImmutableList.of(BIGINT, DOUBLE), ImmutableList.of("f1", "f2"));
RowType innerRowType = new RowType(ImmutableList.of(BIGINT, DOUBLE), Optional.of(ImmutableList.of("f1", "f2")));
MapType mapType = new MapType(DOUBLE, innerRowType);
InternalAggregationFunction aggFunc = metadata.getExactFunction(new Signature(NAME,
mapType.getTypeSignature().toString(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,14 +75,10 @@ queryTerm
;

queryPrimary
: querySpecification #queryPrimaryDefault
| TABLE qualifiedName #table
| VALUES rowValue (',' rowValue)* #inlineTable
| '(' queryNoWith ')' #subquery
;

rowValue
: '(' expression (',' expression )* ')'
: querySpecification #queryPrimaryDefault
| TABLE qualifiedName #table
| VALUES expression (',' expression)* #inlineTable
| '(' queryNoWith ')' #subquery
;

sortItem
Expand Down Expand Up @@ -207,6 +203,8 @@ primaryExpression
| number #numericLiteral
| booleanValue #booleanLiteral
| STRING #stringLiteral
| '(' expression (',' expression)+ ')' #rowConstructor
| ROW '(' expression (',' expression)* ')' #rowConstructor
| qualifiedName #columnReference
| qualifiedName '(' ASTERISK ')' over? #functionCall
| qualifiedName '(' (setQuantifier? expression (',' expression)*)? ')' over? #functionCall
Expand Down
Loading

0 comments on commit cc939dc

Please sign in to comment.