julianhyde commented on code in PR #3502: URL: https://github.com/apache/calcite/pull/3502#discussion_r1393156745
########## core/src/main/java/org/apache/calcite/rex/RexLambdaRef.java: ########## @@ -0,0 +1,49 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.calcite.rex; + +import org.apache.calcite.rel.type.RelDataType; +import org.apache.calcite.sql.SqlKind; + +/** + * Variable which references a field of a lambda expression. Review Comment: s/which/that/ ########## core/src/main/java/org/apache/calcite/sql/fun/SqlLambdaExpressionOperator.java: ########## @@ -0,0 +1,59 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.calcite.sql.fun; + +import org.apache.calcite.rel.type.RelDataType; +import org.apache.calcite.sql.SqlCall; +import org.apache.calcite.sql.SqlKind; +import org.apache.calcite.sql.SqlLambdaExpression; +import org.apache.calcite.sql.SqlNode; +import org.apache.calcite.sql.SqlSpecialOperator; +import org.apache.calcite.sql.validate.SqlLambdaExpressionScope; +import org.apache.calcite.sql.validate.SqlValidator; +import org.apache.calcite.sql.validate.SqlValidatorScope; + +import java.util.List; + +import static com.google.common.collect.ImmutableList.toImmutableList; + +/** + * The {@code SqlLambdaExpressionOperator} represents a lambda expression. + * The syntax : + * {@code IDENTIFIER -> EXPRESSION} or {@code (IDENTIFIER, IDENTIFIER, ...) -> EXPRESSION}. + */ +public class SqlLambdaExpressionOperator extends SqlSpecialOperator { Review Comment: Make this a private inner class of `SqlLambdaExpression`. ########## core/src/main/java/org/apache/calcite/sql/type/LambdaExpressionOperandTypeChecker.java: ########## @@ -0,0 +1,146 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.calcite.sql.type; + +import org.apache.calcite.rel.type.RelDataType; +import org.apache.calcite.sql.SqlCall; +import org.apache.calcite.sql.SqlCallBinding; +import org.apache.calcite.sql.SqlIdentifier; +import org.apache.calcite.sql.SqlLambdaExpression; +import org.apache.calcite.sql.SqlNode; +import org.apache.calcite.sql.SqlOperator; +import org.apache.calcite.sql.SqlUtil; +import org.apache.calcite.sql.util.SqlBasicVisitor; +import org.apache.calcite.sql.validate.SqlLambdaExpressionScope; +import org.apache.calcite.sql.validate.SqlValidator; + +import com.google.common.collect.ImmutableList; + +import java.util.List; + +import static org.apache.calcite.util.Static.RESOURCE; + +/** + * Operand type-checking strategy where the type of the operand is a lambda + * expression with a given return type and argument types. + */ +public class LambdaExpressionOperandTypeChecker implements SqlSingleOperandTypeChecker { Review Comment: I don't think this should be public. That will prevent refactoring. Outside the package people should just use a method in `OperandTypes`. ########## core/src/main/codegen/templates/Parser.jj: ########## @@ -3912,6 +3919,29 @@ SqlNode Expression3(ExprContext exprContext) : } } +/** + * Parses a lambda expression. + */ +SqlNode LambdaExpression() : +{ + final SqlNodeList parameters; + final SqlNode expression; + final Span s; +} +{ + ( + LOOKAHEAD(2) + <LPAREN> <RPAREN> { parameters = SqlNodeList.EMPTY; } + | + parameters = SimpleIdentifierOrList() Review Comment: should there be a rule that allows `()`, `x` and `(x [, y]*)`? ########## core/src/main/codegen/templates/Parser.jj: ########## @@ -8787,6 +8817,7 @@ void NonReservedKeyWord2of3() : | < NE2: "!=" > | < PLUS: "+" > | < MINUS: "-" > +| < LAMBDA_OPERATOR: "->" > Review Comment: just `LAMBDA` ########## core/src/main/java/org/apache/calcite/rex/RexLambdaExpression.java: ########## @@ -0,0 +1,101 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.calcite.rex; + +import org.apache.calcite.rel.type.RelDataType; +import org.apache.calcite.sql.SqlKind; + +import org.apache.commons.lang3.StringUtils; + +import org.checkerframework.checker.nullness.qual.Nullable; + +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +/** + * Represents a lambda expression. + */ +public class RexLambdaExpression extends RexNode { + //~ Instance fields -------------------------------------------------------- + + private final List<RexLambdaRef> parameters; + private final RexNode expression; + + //~ Constructors ----------------------------------------------------------- + + RexLambdaExpression(List<RexLambdaRef> parameters, RexNode expression) { + this.expression = expression; + this.parameters = parameters; + } + + //~ Methods ---------------------------------------------------------------- + + @Override public RelDataType getType() { + return expression.getType(); + } + + @Override public SqlKind getKind() { + return SqlKind.LAMBDA; + } + + @Override public <R> R accept(RexVisitor<R> visitor) { + return visitor.visitLambda(this); + } + + @Override public <R, P> R accept(RexBiVisitor<R, P> visitor, P arg) { + return visitor.visitLambda(this, arg); + } + + public RexNode getExpression() { + return expression; + } + + public List<RexLambdaRef> getParameters() { + return parameters; + } + + @Override public boolean equals(@Nullable Object o) { + if (this == o) { Review Comment: can you use the compact form 'this == o || o instanceof RexLambdaExpression & ...'? no need for Objects.equals. use expression.equals because it's not-null. ########## core/src/main/java/org/apache/calcite/sql/type/LambdaExpressionOperandTypeChecker.java: ########## @@ -0,0 +1,146 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.calcite.sql.type; + +import org.apache.calcite.rel.type.RelDataType; +import org.apache.calcite.sql.SqlCall; +import org.apache.calcite.sql.SqlCallBinding; +import org.apache.calcite.sql.SqlIdentifier; +import org.apache.calcite.sql.SqlLambdaExpression; +import org.apache.calcite.sql.SqlNode; +import org.apache.calcite.sql.SqlOperator; +import org.apache.calcite.sql.SqlUtil; +import org.apache.calcite.sql.util.SqlBasicVisitor; +import org.apache.calcite.sql.validate.SqlLambdaExpressionScope; +import org.apache.calcite.sql.validate.SqlValidator; + +import com.google.common.collect.ImmutableList; + +import java.util.List; + +import static org.apache.calcite.util.Static.RESOURCE; + +/** + * Operand type-checking strategy where the type of the operand is a lambda + * expression with a given return type and argument types. + */ +public class LambdaExpressionOperandTypeChecker implements SqlSingleOperandTypeChecker { + + private final SqlTypeFamily returnTypeFamily; + private final List<SqlTypeFamily> argFamilies; + + public LambdaExpressionOperandTypeChecker( + SqlTypeFamily returnTypeFamily, + List<SqlTypeFamily> argFamilies) { + this.returnTypeFamily = returnTypeFamily; + this.argFamilies = argFamilies; + } + + @Override public String getAllowedSignatures(SqlOperator op, String opName) { + ImmutableList.Builder<SqlTypeFamily> builder = ImmutableList.builder(); + builder.addAll(argFamilies); + builder.add(returnTypeFamily); + + return SqlUtil.getAliasedSignature(op, opName, builder.build()); + } + + @Override public boolean checkOperandTypes(SqlCallBinding callBinding, + boolean throwOnFailure) { + return false; + } + + @Override public boolean checkSingleOperandType( + SqlCallBinding callBinding, SqlNode operand, int iFormalOperand, boolean throwOnFailure) { + if (!(operand instanceof SqlLambdaExpression) + || ((SqlLambdaExpression) operand).getParameters().size() != argFamilies.size()) { + if (throwOnFailure) { + throw callBinding.newValidationSignatureError(); + } + return false; + } + SqlLambdaExpression lambdaExpr = (SqlLambdaExpression) operand; + + if (lambdaExpr.getParameters().isEmpty() + || argFamilies.stream().allMatch(f -> f == SqlTypeFamily.ANY) + || returnTypeFamily == SqlTypeFamily.ANY) { + return true; + } + + if (SqlUtil.isNullLiteral(lambdaExpr.getExpression(), false)) { + if (callBinding.isTypeCoercionEnabled()) { + return true; + } else if (throwOnFailure) { + throw callBinding.getValidator().newValidationError(lambdaExpr.getExpression(), + RESOURCE.nullIllegal()); + } else { + return false; + } + } + + // Replace the parameter types in the lambda expression. + final SqlValidator validator = callBinding.getValidator(); + SqlLambdaExpressionScope scope = + (SqlLambdaExpressionScope) validator.getLambdaExpressionScope(lambdaExpr); + for (int i = 0; i < argFamilies.size(); i++) { + SqlNode param = lambdaExpr.getParameters().get(i); + RelDataType type = argFamilies.get(i).getDefaultConcreteType(callBinding.getTypeFactory()); + if (type != null) { + scope.getParameterTypes().put(param.toString(), type); + } + } + lambdaExpr.accept(new TypeRemover(validator)); + + // Given the new relDataType, re-validate the lambda expression. + validator.validateLambdaExpression(lambdaExpr); + final RelDataType newType = validator.getValidatedNodeType(lambdaExpr); + assert newType instanceof LambdaExpressionSqlType; + final SqlTypeName returnTypeName = + ((LambdaExpressionSqlType) newType).getReturnType().getSqlTypeName(); + if (returnTypeName == SqlTypeName.ANY + || returnTypeFamily.getTypeNames().contains(returnTypeName)) { + return true; + } + + if (throwOnFailure) { + throw callBinding.newValidationSignatureError(); + } + return false; + } + + /** Review Comment: can you explain why this is needed? ########## core/src/main/java/org/apache/calcite/sql/type/LambdaExpressionOperandTypeChecker.java: ########## @@ -0,0 +1,146 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.calcite.sql.type; + +import org.apache.calcite.rel.type.RelDataType; +import org.apache.calcite.sql.SqlCall; +import org.apache.calcite.sql.SqlCallBinding; +import org.apache.calcite.sql.SqlIdentifier; +import org.apache.calcite.sql.SqlLambdaExpression; +import org.apache.calcite.sql.SqlNode; +import org.apache.calcite.sql.SqlOperator; +import org.apache.calcite.sql.SqlUtil; +import org.apache.calcite.sql.util.SqlBasicVisitor; +import org.apache.calcite.sql.validate.SqlLambdaExpressionScope; +import org.apache.calcite.sql.validate.SqlValidator; + +import com.google.common.collect.ImmutableList; + +import java.util.List; + +import static org.apache.calcite.util.Static.RESOURCE; + +/** + * Operand type-checking strategy where the type of the operand is a lambda + * expression with a given return type and argument types. + */ +public class LambdaExpressionOperandTypeChecker implements SqlSingleOperandTypeChecker { + + private final SqlTypeFamily returnTypeFamily; + private final List<SqlTypeFamily> argFamilies; + + public LambdaExpressionOperandTypeChecker( + SqlTypeFamily returnTypeFamily, + List<SqlTypeFamily> argFamilies) { + this.returnTypeFamily = returnTypeFamily; Review Comment: check nulls and copy to immutable list ########## core/src/main/java/org/apache/calcite/rex/RexLambdaExpression.java: ########## @@ -0,0 +1,101 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.calcite.rex; + +import org.apache.calcite.rel.type.RelDataType; +import org.apache.calcite.sql.SqlKind; + +import org.apache.commons.lang3.StringUtils; + +import org.checkerframework.checker.nullness.qual.Nullable; + +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +/** + * Represents a lambda expression. + */ +public class RexLambdaExpression extends RexNode { + //~ Instance fields -------------------------------------------------------- + + private final List<RexLambdaRef> parameters; + private final RexNode expression; + + //~ Constructors ----------------------------------------------------------- + + RexLambdaExpression(List<RexLambdaRef> parameters, RexNode expression) { + this.expression = expression; Review Comment: ImmutbleList.copyOf(parameters) requireNonNull(expression) ########## core/src/main/java/org/apache/calcite/sql/fun/SqlLibraryOperators.java: ########## @@ -1176,6 +1176,15 @@ private static RelDataType arrayAppendPrependReturnType(SqlOperatorBinding opBin SqlLibraryOperators::arrayAppendPrependReturnType, OperandTypes.ARRAY_ELEMENT); + /** The "EXISTS(array, function1)" function (Spark); Returns whether a predicate holds Review Comment: the parameter isn't a `function1`, it's a lambda; there is no function type in SQL grammar: shouldn't have capital 'R' after ';'. ########## core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java: ########## @@ -5371,6 +5372,28 @@ public static List sortArray(List list, boolean ascending) { return list; } + /** Support the EXISTS(list, function1) function. */ Review Comment: Are you aware of the `nullableExists` function? ########## core/src/main/java/org/apache/calcite/rex/RexLambdaRef.java: ########## @@ -0,0 +1,49 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.calcite.rex; + +import org.apache.calcite.rel.type.RelDataType; +import org.apache.calcite.sql.SqlKind; + +/** + * Variable which references a field of a lambda expression. + */ +public class RexLambdaRef extends RexInputRef { + + private final String paramName; Review Comment: could this be an ordinal? ########## core/src/main/java/org/apache/calcite/rex/RexLambdaRef.java: ########## @@ -0,0 +1,49 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.calcite.rex; + +import org.apache.calcite.rel.type.RelDataType; +import org.apache.calcite.sql.SqlKind; + +/** + * Variable which references a field of a lambda expression. + */ +public class RexLambdaRef extends RexInputRef { Review Comment: Extend RexSlot instead? Use the same strategy for names as RexLocalRef does. ########## core/src/main/java/org/apache/calcite/rex/RexLambdaExpression.java: ########## @@ -0,0 +1,101 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.calcite.rex; + +import org.apache.calcite.rel.type.RelDataType; +import org.apache.calcite.sql.SqlKind; + +import org.apache.commons.lang3.StringUtils; + +import org.checkerframework.checker.nullness.qual.Nullable; + +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +/** + * Represents a lambda expression. + */ +public class RexLambdaExpression extends RexNode { Review Comment: rename RexLambdaExpression to RexLambda. give example. ########## core/src/main/java/org/apache/calcite/sql/type/SqlTypeFamily.java: ########## @@ -77,6 +77,7 @@ public enum SqlTypeFamily implements RelDataTypeFamily { CURSOR, COLUMN_LIST, GEO, + LAMBDA_EXPRESSION, Review Comment: change to FUNCTION ########## core/src/main/java/org/apache/calcite/sql/type/LambdaExpressionSqlType.java: ########## @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.calcite.sql.type; + +import org.apache.calcite.linq4j.Ord; +import org.apache.calcite.rel.type.RelDataType; +import org.apache.calcite.rel.type.RelDataTypeFamily; +import org.apache.calcite.rel.type.RelDataTypeField; + +import org.checkerframework.checker.nullness.qual.Nullable; + +/** + * SQL lambda expression type. + */ +public class LambdaExpressionSqlType extends AbstractSqlType { + private final @Nullable RelDataType parameterType; + private final RelDataType returnType; + + public LambdaExpressionSqlType(@Nullable RelDataType parameterType, RelDataType returnType) { + super(SqlTypeName.LAMBDA_EXPRESSION, true, null); + assert parameterType == null || parameterType.isStruct(); Review Comment: why allow null parameter type? a `PairList<String, RelDataType>` (which may be empty) seems about right. ########## site/_docs/reference.md: ########## @@ -1205,6 +1205,7 @@ Note: | MULTISET | Unordered collection that may contain duplicates | Example: int multiset | ARRAY | Ordered, contiguous collection that may contain duplicates | Example: varchar(10) array | CURSOR | Cursor over the result of executing a query | +| LAMBDA_EXPRESSION| A function definition that is not bound to an identifier.| Example FUNCTION(INTEGER, VARCHAR(30)) -> INTEGER Review Comment: Let's just call this a FUNCTION type. Add spaces around `|`. Remove '.' You should note that it is not fully supported e.g. in CAST or DDL ########## core/src/main/java/org/apache/calcite/sql/type/LambdaExpressionSqlType.java: ########## @@ -0,0 +1,65 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.calcite.sql.type; + +import org.apache.calcite.linq4j.Ord; +import org.apache.calcite.rel.type.RelDataType; +import org.apache.calcite.rel.type.RelDataTypeFamily; +import org.apache.calcite.rel.type.RelDataTypeField; + +import org.checkerframework.checker.nullness.qual.Nullable; + +/** + * SQL lambda expression type. + */ Review Comment: can this be private? I would call this a function type, not a lambda type. In future we may allow other kinds of function value. ########## core/src/main/java/org/apache/calcite/sql/validate/LambdaExpressionNamespace.java: ########## @@ -0,0 +1,51 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.calcite.sql.validate; + +import org.apache.calcite.rel.type.RelDataType; +import org.apache.calcite.sql.SqlLambdaExpression; +import org.apache.calcite.sql.SqlNode; + +import org.checkerframework.checker.nullness.qual.Nullable; + +import static java.util.Objects.requireNonNull; + +/** + * Namespace for {@code lambda expression}. + */ +public class LambdaExpressionNamespace extends AbstractNamespace { Review Comment: rename to LambdaNamespace ########## core/src/main/java/org/apache/calcite/sql/type/OperandTypes.java: ########## @@ -101,6 +101,25 @@ public static FamilyOperandTypeChecker family(List<SqlTypeFamily> families) { return family(families, i -> false); } + /** + * Creates a checker that passes if the operand is a lambda expression with Review Comment: change 'lambda expression' to 'function' but give lambda expression as an example. ########## core/src/main/java/org/apache/calcite/sql/validate/SqlLambdaExpressionScope.java: ########## @@ -0,0 +1,77 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.calcite.sql.validate; + +import org.apache.calcite.rel.type.RelDataType; +import org.apache.calcite.sql.SqlIdentifier; +import org.apache.calcite.sql.SqlLambdaExpression; +import org.apache.calcite.sql.SqlNode; +import org.apache.calcite.sql.type.SqlTypeName; +import org.apache.calcite.util.Litmus; + +import org.checkerframework.checker.nullness.qual.Nullable; + +import java.util.HashMap; +import java.util.Map; + +import static org.apache.calcite.util.Static.RESOURCE; + +/** + * Scope for a {@link SqlLambdaExpression LAMBDA EXPRESSION}. + */ +public class SqlLambdaExpressionScope extends ListScope { + private final SqlLambdaExpression lambdaExpr; + private final Map<String, RelDataType> parameterTypes; + + public SqlLambdaExpressionScope( + SqlValidatorScope parent, SqlLambdaExpression lambdaExpr) { + super(parent); + this.lambdaExpr = lambdaExpr; + + // default parameter type is ANY Review Comment: default type is ANY? Sounds dubious. Can you justify this with an example? SQL is a strongly typed language. ########## core/src/main/java/org/apache/calcite/sql/validate/SqlLambdaExpressionScope.java: ########## @@ -0,0 +1,77 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to you under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.calcite.sql.validate; + +import org.apache.calcite.rel.type.RelDataType; +import org.apache.calcite.sql.SqlIdentifier; +import org.apache.calcite.sql.SqlLambdaExpression; +import org.apache.calcite.sql.SqlNode; +import org.apache.calcite.sql.type.SqlTypeName; +import org.apache.calcite.util.Litmus; + +import org.checkerframework.checker.nullness.qual.Nullable; + +import java.util.HashMap; +import java.util.Map; + +import static org.apache.calcite.util.Static.RESOURCE; + +/** + * Scope for a {@link SqlLambdaExpression LAMBDA EXPRESSION}. + */ +public class SqlLambdaExpressionScope extends ListScope { + private final SqlLambdaExpression lambdaExpr; + private final Map<String, RelDataType> parameterTypes; Review Comment: why cache the parameter types? caches go out of date. better to have a method that computes them when needed. ########## testkit/src/main/java/org/apache/calcite/test/MockSqlOperatorTable.java: ########## @@ -623,4 +625,24 @@ public CompositeFunction() { return typeFactory.createSqlType(SqlTypeName.BIGINT); } } + + private static final SqlFunction HIGHER_ORDER_FUNCTION = + new SqlFunction("HIGHER_ORDER_FUNCTION", Review Comment: Use `SqlBasicFunction.create`. ########## core/src/main/codegen/templates/Parser.jj: ########## @@ -1060,6 +1064,9 @@ void AddArg(List<SqlNode> list, ExprContext exprContext) : ) ( e = Default() + | + LOOKAHEAD((SimpleIdentifierOrList() | <LPAREN> <RPAREN>) <LAMBDA_OPERATOR>) + e = LambdaExpression() Review Comment: How expensive is this LOOKAHEAD? ########## site/_docs/reference.md: ########## @@ -2614,6 +2615,22 @@ Note: | jsonValue IS JSON ARRAY | Whether *jsonValue* is a JSON array | jsonValue IS NOT JSON ARRAY | Whether *jsonValue* is not a JSON array +### Higher-order Functions + +A higher-order function takes one or more lambda expressions as arguments. + +Lambda Expression Syntax: +{% highlight sql %} +lambdaExpression: + parameters '->' expression + +parameters: + '(' [ identifier [, identifier ] ] ')' + | identifier +{% endhighlight %} + +Higher-order functions are not included in the SQL standard, so all the functions will be listed in the next chapter. + Review Comment: Is it true that higher-order functions must be built-in? I think it would be helpful to list the higher-order functions here. ########## core/src/main/codegen/templates/Parser.jj: ########## @@ -1033,6 +1034,9 @@ void AddArg0(List<SqlNode> list, ExprContext exprContext) : ) ( e = Default() + | + LOOKAHEAD((SimpleIdentifierOrList() | <LPAREN> <RPAREN>) <LAMBDA_OPERATOR>) + e = LambdaExpression() Review Comment: How expensive is this LOOKAHEAD? ########## core/src/main/java/org/apache/calcite/rel/rel2sql/SqlImplementor.java: ########## @@ -779,6 +782,17 @@ public SqlNode toSql(@Nullable RexProgram program, RexNode rex) { return SqlStdOperatorTable.NOT.createCall(POS, node); } + case LAMBDA: + final RexLambdaExpression lambda = (RexLambdaExpression) rex; + final SqlNodeList parameters = new SqlNodeList(POS); + for (RexLambdaRef parameter : lambda.getParameters()) { + parameters.add(toSql(program, parameter)); + } + final SqlNode expression = toSql(program, lambda.getExpression()); + return new SqlLambdaExpression(POS, parameters, expression); + case LAMBDA_REF: Review Comment: add blank line before ########## core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java: ########## @@ -6683,6 +6683,31 @@ void testGroupExpressionEquivalenceParams() { .fails("'PERCENTILE_DISC' requires precisely one ORDER BY key"); } + /** Test case for + * <a href="https://issues.apache.org/jira/browse/CALCITE-3679">[CALCITE-3679] + * Allow lambda expressions in SQL queries</a>. */ + @Test void testHigherOrderFunction() { + final SqlValidatorFixture s = fixture() + .withOperatorTable(MockSqlOperatorTable.standard().extend()); + s.withSql("select HIGHER_ORDER_FUNCTION(1, (x, y) -> x + 1)").ok(); + s.withSql("select HIGHER_ORDER_FUNCTION(1, (x, y) -> y)").ok(); + s.withSql("select HIGHER_ORDER_FUNCTION(1, (x, y) -> char_length(x) + 1)").ok(); + s.withSql("select HIGHER_ORDER_FUNCTION(1, (x, y) -> null)").ok(); + s.withSql("select HIGHER_ORDER_FUNCTION(1, (x, y) -> char_length(x) + 1)") + .type("RecordType(INTEGER NOT NULL EXPR$0) NOT NULL"); + + s.withSql("select HIGHER_ORDER_FUNCTION2(1, () -> 0.1)").ok(); + s.withSql("select HIGHER_ORDER_FUNCTION2(1, () -> 0.1)") + .type("RecordType(INTEGER NOT NULL EXPR$0) NOT NULL"); + + s.withSql("select ^HIGHER_ORDER_FUNCTION(1, null)^") + .fails("Cannot apply '(?s).*HIGHER_ORDER_FUNCTION' to arguments of type " + + "'HIGHER_ORDER_FUNCTION\\(<INTEGER>, <NULL>\\)'.*"); + s.withSql("select ^HIGHER_ORDER_FUNCTION(1, (x, y, z) -> x + 1)^") + .fails("Cannot apply '(?s).*HIGHER_ORDER_FUNCTION' to arguments of type " + + "'HIGHER_ORDER_FUNCTION\\(<INTEGER>, <FUNCTION\\(ANY, ANY, ANY\\) -> ANY>\\)'.*"); + } + Review Comment: need to see more tests for EXISTS. including negative tests. wrong type of arguments, wrong number of arguments, arguments of wrong name, arguments of wrong case. check type derivation (if you didn't do it in SqlOperatorTest) -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
