Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 105 additions & 18 deletions core/src/main/codegen/templates/Parser.jj
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,7 @@ import org.apache.calcite.sql.SqlWithItem;
import org.apache.calcite.sql.fun.SqlCase;
import org.apache.calcite.sql.fun.SqlInternalOperators;
import org.apache.calcite.sql.fun.SqlLibraryOperators;
import org.apache.calcite.sql.fun.SqlRowOperator;
import org.apache.calcite.sql.fun.SqlStdOperatorTable;
import org.apache.calcite.sql.fun.SqlTrimFunction;
import org.apache.calcite.sql.parser.Span;
Expand Down Expand Up @@ -2703,12 +2704,14 @@ void AddRowConstructor(List<SqlNode> list) :

/**
* Parses a row constructor in the context of a VALUES expression.
* Supports optional field-name aliases: {@code ROW(expr AS fieldName, ...)}.
*/
SqlNode RowConstructor() :
{
final SqlNodeList valueList;
SqlNodeList valueList;
final SqlNode value;
final Span s;
final List<SqlNode> nameList = new ArrayList<SqlNode>();
}
{
// hints are necessary here due to common LPAREN prefixes
Expand All @@ -2720,15 +2723,24 @@ SqlNode RowConstructor() :
<LPAREN> { s = span(); }
<ROW>
valueList = ParenthesizedQueryOrCommaListWithDefault(ExprContext.ACCEPT_NONCURSOR)
<RPAREN> { s.add(this); }
<RPAREN>
{
s.add(this);
return buildRowCall(s.end(valueList), valueList, null);
}
|
// Standard forms: ROW(e1 [AS n1], e2, ...) or (e1 [AS n1], e2, ...)
LOOKAHEAD(3)
(
<ROW> { s = span(); }
|
{ s = Span.of(); }
)
valueList = ParenthesizedQueryOrCommaListWithDefault(ExprContext.ACCEPT_NONCURSOR)
{ nameList.clear(); }
valueList = RowArgListWithParens(ExprContext.ACCEPT_NONCURSOR, nameList)
{
return buildRowCall(s.end(valueList), valueList, nameList);
}
|
value = Expression(ExprContext.ACCEPT_NONCURSOR)
{
Expand All @@ -2741,15 +2753,9 @@ SqlNode RowConstructor() :
s = Span.of(value);
valueList = new SqlNodeList(ImmutableList.of(value),
value.getParserPosition());
return buildRowCall(s.end(valueList), valueList, null);
}
)
{
// REVIEW jvs 8-Feb-2004: Should we discriminate between scalar
// sub-queries inside of ROW and row sub-queries? The standard does,
// but the distinction seems to be purely syntactic.
return SqlStdOperatorTable.ROW.createCall(s.end(valueList),
(List<SqlNode>) valueList);
}
}

/** Parses a WHERE clause for SELECT, DELETE, and UPDATE. */
Expand Down Expand Up @@ -4173,6 +4179,27 @@ SqlKind comp() :
}
}

/**
* Builds a ROW call from parsed expressions and optional field-name aliases.
* Uses the singleton ROW operator when all names are absent, and a per-instance
* SqlRowOperator (carrying the names) when any AS alias was written.
*/
JAVACODE SqlNode buildRowCall(SqlParserPos pos, SqlNodeList exprList, List<SqlNode> nameList) {
if (nameList != null) {
for (int i = 0; i < nameList.size(); i++) {
if (nameList.get(i) != null) {
final List<String> fieldNames = new ArrayList<String>();
for (int j = 0; j < nameList.size(); j++) {
SqlNode n = nameList.get(j);
fieldNames.add(n instanceof SqlLiteral ? ((SqlLiteral) n).getValueAs(String.class) : null);
}
return new SqlRowOperator("ROW", fieldNames).createCall(pos, (List<SqlNode>) exprList.getList());
}
}
}
return SqlStdOperatorTable.ROW.createCall(pos, (List<SqlNode>) exprList.getList());
}

/**
* Parses a unary row expression, or a parenthesized expression of any
* kind.
Expand All @@ -4184,6 +4211,9 @@ SqlNode Expression3(ExprContext exprContext) :
final SqlNodeList list1;
final Span s;
final Span rowSpan;
// Populated by RowArgListWithParens: one entry per expression, either a
// SqlLiteral string for an AS-aliased field name, or null if unnamed.
final List<SqlNode> rowFieldNames = new ArrayList<SqlNode>();
}
{
LOOKAHEAD(2)
Expand All @@ -4200,7 +4230,7 @@ SqlNode Expression3(ExprContext exprContext) :
s = span();
pushRowValueStar();
}
list = ParenthesizedQueryOrCommaList(exprContext) {
list = RowArgListWithParens(exprContext, rowFieldNames) {
try {
if (exprContext != ExprContext.ACCEPT_ALL
&& exprContext != ExprContext.ACCEPT_CURSOR
Expand All @@ -4209,7 +4239,7 @@ SqlNode Expression3(ExprContext exprContext) :
throw SqlUtil.newContextException(s.end(list),
RESOURCE.illegalRowExpression());
}
return SqlStdOperatorTable.ROW.createCall(list);
return buildRowCall(list.getParserPosition(), list, rowFieldNames);
} finally {
popRowValueStar();
}
Expand All @@ -4219,12 +4249,11 @@ SqlNode Expression3(ExprContext exprContext) :
<ROW> { rowSpan = span(); pushRowValueStar(); }
| { rowSpan = null; }
)
list1 = ParenthesizedQueryOrCommaList(exprContext) {
list1 = RowArgListWithParens(exprContext, rowFieldNames) {
try {
if (rowSpan != null) {
// interpret as row constructor
return SqlStdOperatorTable.ROW.createCall(rowSpan.end(list1),
(List<SqlNode>) list1);
return buildRowCall(rowSpan.end(list1), list1, rowFieldNames);
}
} finally {
if (rowSpan != null) {
Expand Down Expand Up @@ -4280,10 +4309,68 @@ SqlNode Expression3(ExprContext exprContext) :
return list1.get(0).clone(list1.getParserPosition());
} else {
// interpret as row constructor
return SqlStdOperatorTable.ROW.createCall(span().end(list1),
(List<SqlNode>) list1);
return buildRowCall(span().end(list1), list1, rowFieldNames);
}
}
}

/**
* Parses a parenthesized comma list for ROW constructors.
* Supports optional field-name aliases: {@code (expr AS fieldName, ...)}.
* Populates {@code nameList} with field names (or null for unnamed fields).
* Returns a SqlNodeList of the value expressions.
*/
SqlNodeList RowArgListWithParens(ExprContext exprContext, List<SqlNode> nameList) :
{
SqlNode e;
SqlIdentifier alias;
final List<SqlNode> exprList = new ArrayList<SqlNode>();
ExprContext firstExprContext = exprContext;
final Span s;
}
{
<LPAREN>
{
s = span();
switch (exprContext) {
case ACCEPT_SUB_QUERY:
firstExprContext = ExprContext.ACCEPT_NONCURSOR;
break;
case ACCEPT_CURSOR:
firstExprContext = ExprContext.ACCEPT_ALL;
break;
}
}
(
e = OrderedQueryOrExpr(firstExprContext)
(
<AS> alias = SimpleIdentifier()
{ exprList.add(e); nameList.add(SqlLiteral.createCharString(alias.getSimple(), alias.getParserPosition())); }
|
{ exprList.add(e); nameList.add(null); }
)
|
e = Default() { exprList.add(e); nameList.add(null); }
)
(
<COMMA>
{
checkNonQueryExpression(exprContext);
}
(
e = Expression(exprContext)
(
<AS> alias = SimpleIdentifier()
{ exprList.add(e); nameList.add(SqlLiteral.createCharString(alias.getSimple(), alias.getParserPosition())); }
|
{ exprList.add(e); nameList.add(null); }
)
|
e = Default() { exprList.add(e); nameList.add(null); }
)
)*
<RPAREN>
{ return new SqlNodeList(exprList, s.end(this)); }
}

/**
Expand Down Expand Up @@ -5407,7 +5494,7 @@ SqlNode PeriodConstructor() :
<COMMA>
AddExpression(args, ExprContext.ACCEPT_SUB_QUERY)
<RPAREN> {
return SqlStdOperatorTable.ROW.createCall(s.end(this), args);
return buildRowCall(s.end(this), new SqlNodeList(args, s.end(this)), null);
}
}

Expand Down
89 changes: 79 additions & 10 deletions core/src/main/java/org/apache/calcite/sql/fun/SqlRowOperator.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,43 +20,80 @@
import org.apache.calcite.rel.type.RelDataTypeFactory;
import org.apache.calcite.sql.SqlCall;
import org.apache.calcite.sql.SqlKind;
import org.apache.calcite.sql.SqlNode;
import org.apache.calcite.sql.SqlOperatorBinding;
import org.apache.calcite.sql.SqlSpecialOperator;
import org.apache.calcite.sql.SqlUtil;
import org.apache.calcite.sql.SqlWriter;
import org.apache.calcite.sql.type.InferTypes;
import org.apache.calcite.sql.type.OperandTypes;
import org.apache.calcite.sql.validate.SqlValidator;
import org.apache.calcite.sql.validate.SqlValidatorScope;
import org.apache.calcite.util.ImmutableNullableList;

import org.checkerframework.checker.nullness.qual.Nullable;

import java.util.List;

/**
* SqlRowOperator represents the special ROW constructor.
* SqlRowOperator represents the special ROW constructor, {@code ROW(v1, v2, ...)}.
*
* <p>TODO: describe usage for row-value construction and row-type construction
* (SQL supports both).
* <p>Fields may be given explicit names using AS aliases:
* {@code ROW(v1 AS f1, v2 AS f2, ...)}. When aliases are present, a
* per-instance operator (rather than the singleton {@link
* org.apache.calcite.sql.fun.SqlStdOperatorTable#ROW}) is used to carry the
* field names through type inference. After type inference the resulting
* {@link org.apache.calcite.rel.type.RelDataType} carries the names, so
* downstream code does not need to inspect the operator.
*
* <p>When no aliases are given, field names are auto-generated
* ({@code EXPR$0}, {@code EXPR$1}, …).
*/
public class SqlRowOperator extends SqlSpecialOperator {

Check warning on line 52 in core/src/main/java/org/apache/calcite/sql/fun/SqlRowOperator.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Override the "equals" method in this class.

See more on https://sonarcloud.io/project/issues?id=apache_calcite&issues=AZ6_E6qk6-8DAyAuMa3Z&open=AZ6_E6qk6-8DAyAuMa3Z&pullRequest=5018
//~ Constructors -----------------------------------------------------------

/**
* Optional explicit field names. When null, field names are auto-generated
* ({@code EXPR$0}, {@code EXPR$1}, …). Individual entries may be null to
* mix named and unnamed fields.
*/
private final @Nullable List<@Nullable String> fieldNames;

/** Constructor for the singleton (no field-name aliases). */
public SqlRowOperator(String name) {
this(name, null);
}

/** Constructor for a named ROW operator with explicit field-name aliases.
* Field names may be null, in which case they are auto-generated. */
public SqlRowOperator(String name, @Nullable List<@Nullable String> fieldNames) {
super(name,
SqlKind.ROW, MDX_PRECEDENCE,
false,
null,
InferTypes.RETURN_TYPE,
OperandTypes.VARIADIC);
if (fieldNames == null) {
this.fieldNames = null;
} else {
this.fieldNames = ImmutableNullableList.copyOf(fieldNames);
}
}

//~ Methods ----------------------------------------------------------------

@Override public RelDataType inferReturnType(
final SqlOperatorBinding opBinding) {
// The type of a ROW(e1,e2) expression is a record with the types
// {e1type,e2type}. According to the standard, field names are
// implementation-defined.
// ROW(e1type,e2type). Field names come from AS aliases when present;

Check warning on line 87 in core/src/main/java/org/apache/calcite/sql/fun/SqlRowOperator.java

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

This block of commented-out lines of code should be removed.

See more on https://sonarcloud.io/project/issues?id=apache_calcite&issues=AZ6_E6qk6-8DAyAuMa3a&open=AZ6_E6qk6-8DAyAuMa3a&pullRequest=5018
// otherwise they are implementation-defined.
final RelDataTypeFactory typeFactory = opBinding.getTypeFactory();
final RelDataTypeFactory.Builder builder = typeFactory.builder();
for (int index = 0; index < opBinding.getOperandCount(); index++) {
builder.add(SqlUtil.deriveAliasFromOrdinal(index),
opBinding.getOperandType(index));
for (int i = 0; i < opBinding.getOperandCount(); i++) {
final String fieldName =
fieldNames != null && fieldNames.get(i) != null
? fieldNames.get(i)
: SqlUtil.deriveAliasFromOrdinal(i);
builder.add(fieldName, opBinding.getOperandType(i));
}
final RelDataType recordType = builder.build();

Expand All @@ -68,12 +105,44 @@
return typeFactory.createTypeWithNullability(recordType, nullable);
}

@Override public RelDataType deriveType(
SqlValidator validator,
SqlValidatorScope scope,
SqlCall call) {
if (fieldNames == null) {
return super.deriveType(validator, scope, call);
}
// For named ROW: validate operand types without replacing this operator
// via lookupRoutine (which would substitute the singleton and lose field names).
for (SqlNode operand : call.getOperandList()) {
validator.deriveType(scope, operand);
}
return validateOperands(validator, scope, call);
}

@Override public void unparse(
SqlWriter writer,
SqlCall call,
int leftPrec,
int rightPrec) {
SqlUtil.unparseFunctionSyntax(this, writer, call, false);
if (fieldNames == null) {
SqlUtil.unparseFunctionSyntax(this, writer, call, false);
return;
}
writer.print("ROW");
writer.setNeedWhitespace(false);
final SqlWriter.Frame frame =
writer.startList(SqlWriter.FrameTypeEnum.FUN_CALL, "(", ")");
for (int i = 0; i < call.operandCount(); i++) {
writer.sep(",");
call.operand(i).unparse(writer, 0, 0);
final String name = fieldNames.get(i);
if (name != null) {
writer.keyword("AS");
writer.identifier(name, true);
}
}
writer.endList(frame);
}

// override SqlOperator
Expand Down
Loading
Loading