Skip to content

Commit

Permalink
[CALCITE-6645] Support user-defined function without parentheses when…
Browse files Browse the repository at this point in the history
… db dialect's allowNiladicParentheses property is false
  • Loading branch information
strongduanmu authored and mihaibudiu committed Nov 13, 2024
1 parent 215577d commit af4391d
Show file tree
Hide file tree
Showing 9 changed files with 94 additions and 13 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@ private static SqlMonikerImpl moniker(CalciteSchema schema, @Nullable String nam
getFunctionsFrom(opName.names)
.stream()
.filter(predicate)
.map(function -> toOp(opName, function))
.map(function -> toOp(opName, function, config))
.forEachOrdered(operatorList::add);
}

Expand All @@ -296,15 +296,15 @@ public static SqlOperatorTable operatorTable(String... classNames) {
for (String name : schema.getFunctionNames()) {
schema.getFunctions(name, true).forEach(function -> {
final SqlIdentifier id = new SqlIdentifier(name, SqlParserPos.ZERO);
list.add(toOp(id, function));
list.add(toOp(id, function, CalciteConnectionConfig.DEFAULT));
});
}
return SqlOperatorTables.of(list);
}

/** Converts a function to a {@link org.apache.calcite.sql.SqlOperator}. */
private static SqlOperator toOp(SqlIdentifier name,
final org.apache.calcite.schema.Function function) {
final org.apache.calcite.schema.Function function, CalciteConnectionConfig config) {
final Function<RelDataTypeFactory, List<RelDataType>> argTypesFactory =
typeFactory -> function.getParameters()
.stream()
Expand Down Expand Up @@ -344,8 +344,12 @@ private static SqlOperator toOp(SqlIdentifier name,
if (function instanceof ScalarFunction) {
final SqlReturnTypeInference returnTypeInference =
infer((ScalarFunction) function);
SqlSyntax syntax = function.getParameters().isEmpty()
&& !config.conformance().allowNiladicParentheses()
? SqlSyntax.FUNCTION_ID
: SqlSyntax.FUNCTION;
return new SqlUserDefinedFunction(name, kind, returnTypeInference,
operandTypeInference, operandMetadata, function);
operandTypeInference, operandMetadata, function, syntax);
} else if (function instanceof AggregateFunction) {
final SqlReturnTypeInference returnTypeInference =
infer((AggregateFunction) function);
Expand Down Expand Up @@ -423,7 +427,7 @@ private static RelDataType toSql(RelDataTypeFactory typeFactory,
if (schema != null) {
for (String name : schema.getFunctionNames()) {
schema.getFunctions(name, true).forEach(f ->
builder.add(toOp(new SqlIdentifier(name, SqlParserPos.ZERO), f)));
builder.add(toOp(new SqlIdentifier(name, SqlParserPos.ZERO), f, config)));
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
import org.apache.calcite.sql.SqlFunctionCategory;
import org.apache.calcite.sql.SqlIdentifier;
import org.apache.calcite.sql.SqlKind;
import org.apache.calcite.sql.SqlSyntax;
import org.apache.calcite.sql.type.SqlOperandMetadata;
import org.apache.calcite.sql.type.SqlOperandTypeChecker;
import org.apache.calcite.sql.type.SqlOperandTypeInference;
Expand All @@ -42,6 +43,8 @@
public class SqlUserDefinedFunction extends SqlFunction {
public final Function function;

public final SqlSyntax syntax;

@Deprecated // to be removed before 2.0
public SqlUserDefinedFunction(SqlIdentifier opName,
SqlReturnTypeInference returnTypeInference,
Expand All @@ -63,7 +66,17 @@ public SqlUserDefinedFunction(SqlIdentifier opName, SqlKind kind,
@Nullable SqlOperandMetadata operandMetadata,
Function function) {
this(opName, kind, returnTypeInference, operandTypeInference,
operandMetadata, function, SqlFunctionCategory.USER_DEFINED_FUNCTION);
operandMetadata, function, SqlFunctionCategory.USER_DEFINED_FUNCTION, SqlSyntax.FUNCTION);
}

/** Creates a {@link SqlUserDefinedFunction} with sql syntax. */
public SqlUserDefinedFunction(SqlIdentifier opName, SqlKind kind,
SqlReturnTypeInference returnTypeInference,
SqlOperandTypeInference operandTypeInference,
@Nullable SqlOperandMetadata operandMetadata,
Function function, SqlSyntax syntax) {
this(opName, kind, returnTypeInference, operandTypeInference,
operandMetadata, function, SqlFunctionCategory.USER_DEFINED_FUNCTION, syntax);
}

/** Constructor used internally and by derived classes. */
Expand All @@ -72,16 +85,21 @@ protected SqlUserDefinedFunction(SqlIdentifier opName, SqlKind kind,
SqlOperandTypeInference operandTypeInference,
@Nullable SqlOperandMetadata operandMetadata,
Function function,
SqlFunctionCategory category) {
SqlFunctionCategory category, SqlSyntax syntax) {
super(Util.last(opName.names), opName, kind, returnTypeInference,
operandTypeInference, operandMetadata, category);
this.function = function;
this.syntax = syntax;
}

@Override public @Nullable SqlOperandMetadata getOperandTypeChecker() {
return (@Nullable SqlOperandMetadata) super.getOperandTypeChecker();
}

@Override public SqlSyntax getSyntax() {
return syntax;
}

/**
* Returns function that implements given operator call.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import org.apache.calcite.sql.SqlIdentifier;
import org.apache.calcite.sql.SqlKind;
import org.apache.calcite.sql.SqlOperatorBinding;
import org.apache.calcite.sql.SqlSyntax;
import org.apache.calcite.sql.SqlTableFunction;
import org.apache.calcite.sql.type.SqlOperandMetadata;
import org.apache.calcite.sql.type.SqlOperandTypeChecker;
Expand Down Expand Up @@ -64,7 +65,7 @@ public SqlUserDefinedTableFunction(SqlIdentifier opName, SqlKind kind,
TableFunction function) {
super(opName, kind, returnTypeInference, operandTypeInference,
operandMetadata, function,
SqlFunctionCategory.USER_DEFINED_TABLE_FUNCTION);
SqlFunctionCategory.USER_DEFINED_TABLE_FUNCTION, SqlSyntax.FUNCTION);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1959,7 +1959,7 @@ protected SqlSelect createSourceSelectForDelete(SqlDelete call) {
}

@Override public @Nullable SqlCall makeNullaryCall(SqlIdentifier id) {
if (id.names.size() == 1 && !id.isComponentQuoted(0)) {
if (!id.isComponentQuoted(id.names.size() - 1)) {
final List<SqlOperator> list = new ArrayList<>();
opTab.lookupOperatorOverloads(id, null, SqlSyntax.FUNCTION, list,
catalogReader.nameMatcher());
Expand Down
44 changes: 41 additions & 3 deletions core/src/test/java/org/apache/calcite/test/UdfTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
import org.apache.calcite.schema.impl.ScalarFunctionImpl;
import org.apache.calcite.schema.impl.ViewTable;
import org.apache.calcite.sql.type.SqlTypeName;
import org.apache.calcite.sql.validate.SqlConformanceEnum;
import org.apache.calcite.test.schemata.hr.HrSchema;
import org.apache.calcite.util.Smalls;

Expand Down Expand Up @@ -122,6 +123,12 @@ private CalciteAssert.AssertThat withUdf() {
+ "'\n"
+ " },\n"
+ " {\n"
+ " name: 'MY_NILADIC_PARENTHESES',\n"
+ " className: '"
+ Smalls.MyNiladicParenthesesFunction.class.getName()
+ "'\n"
+ " },\n"
+ " {\n"
+ " name: 'COUNT_ARGS',\n"
+ " className: '"
+ Smalls.CountArgs0Function.class.getName()
Expand Down Expand Up @@ -407,14 +414,43 @@ private CalciteAssert.AssertThat withUdf() {
.returns("P=null\n");
}

/** Test case for
* <a href="https://issues.apache.org/jira/browse/CALCITE-6645">[CALCITE-6645]
* User-defined function with niladic parentheses</a>. */
@Test void testUserDefinedFunctionWithNiladicParentheses() {
final CalciteAssert.AssertThat with = withUdf();
with.with(SqlConformanceEnum.MYSQL_5)
.query("select \"adhoc\".my_niladic_parentheses() as p\n"
+ "from \"adhoc\".EMPLOYEES limit 1")
.returns("P=foo\n");
with.with(SqlConformanceEnum.ORACLE_10)
.query("select \"adhoc\".my_niladic_parentheses as p\n"
+ "from \"adhoc\".EMPLOYEES limit 1")
.returns("P=foo\n");
// wrong niladic function with mysql_5 conformance
with.with(SqlConformanceEnum.MYSQL_5)
.query("select \"adhoc\".my_niladic_parentheses as p\n"
+ "from \"adhoc\".EMPLOYEES limit 1")
.throws_("Table 'adhoc' not found");
// wrong niladic function with oracle_10 conformance
with.with(SqlConformanceEnum.ORACLE_10)
.query("select \"adhoc\".my_niladic_parentheses() as p\n"
+ "from \"adhoc\".EMPLOYEES limit 1")
.throws_("No match found for function signature MY_NILADIC_PARENTHESES()");
}

/** Tests a user-defined function that has multiple overloads. */
@Test void testUdfOverloaded() {
final CalciteAssert.AssertThat with = withUdf();
with.query("values (\"adhoc\".count_args(),\n"
// MYSQL_5 conformance support niladic function with parentheses
with.with(SqlConformanceEnum.MYSQL_5)
.query("values (\"adhoc\".count_args(),\n"
+ " \"adhoc\".count_args(0),\n"
+ " \"adhoc\".count_args(0, 0))")
.returns("EXPR$0=0; EXPR$1=1; EXPR$2=2\n");
with.query("select max(\"adhoc\".count_args()) as p0,\n"
// MYSQL_5 conformance support niladic function with parentheses
with.with(SqlConformanceEnum.MYSQL_5)
.query("select max(\"adhoc\".count_args()) as p0,\n"
+ " min(\"adhoc\".count_args(0)) as p1,\n"
+ " max(\"adhoc\".count_args(0, 0)) as p2\n"
+ "from \"adhoc\".EMPLOYEES limit 1")
Expand All @@ -423,7 +459,9 @@ private CalciteAssert.AssertThat withUdf() {

@Test void testUdfOverloadedNullable() {
final CalciteAssert.AssertThat with = withUdf();
with.query("values (\"adhoc\".count_args(),\n"
// MYSQL_5 conformance support niladic function with parentheses
with.with(SqlConformanceEnum.MYSQL_5)
.query("values (\"adhoc\".count_args(),\n"
+ " \"adhoc\".count_args(cast(null as smallint)),\n"
+ " \"adhoc\".count_args(0, 0))")
.returns("EXPR$0=0; EXPR$1=-1; EXPR$2=2\n");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import org.apache.calcite.sql.SqlFunctionCategory;
import org.apache.calcite.sql.SqlIdentifier;
import org.apache.calcite.sql.SqlKind;
import org.apache.calcite.sql.SqlSyntax;
import org.apache.calcite.sql.parser.SqlParserPos;
import org.apache.calcite.sql.type.SqlOperandMetadata;
import org.apache.calcite.sql.type.SqlOperandTypeInference;
Expand All @@ -42,7 +43,7 @@ private PigUserDefinedFunction(SqlIdentifier opName,
FuncSpec funcSpec) {
super(opName, SqlKind.OTHER_FUNCTION, returnTypeInference,
operandTypeInference, operandMetadata, function,
SqlFunctionCategory.USER_DEFINED_CONSTRUCTOR);
SqlFunctionCategory.USER_DEFINED_CONSTRUCTOR, SqlSyntax.FUNCTION);
this.funcSpec = funcSpec;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ public Properties provideConnectionInfo() throws IOException {
Properties info = new Properties();
info.setProperty("lex", "MYSQL");
info.setProperty("model", "inline:" + provideSchema());
info.setProperty("conformance", "MYSQL_5");
return info;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,10 @@ private CalciteAssert() {}
return this;
}

@Override public AssertThat with(SqlConformanceEnum conformance) {
return this;
}

@Override public AssertThat with(
ConnectionPostProcessor postProcessor) {
return this;
Expand Down Expand Up @@ -1214,6 +1218,11 @@ public AssertThat with(Lex lex) {
return with(CalciteConnectionProperty.LEX, lex);
}

/** Sets the conformance property. */
public AssertThat with(SqlConformanceEnum conformance) {
return with(CalciteConnectionProperty.CONFORMANCE, conformance);
}

/** Sets the default schema to a given schema. */
public AssertThat withSchema(String name, Schema schema) {
return with(ConnectionFactories.add(name, schema));
Expand Down
9 changes: 9 additions & 0 deletions testkit/src/main/java/org/apache/calcite/util/Smalls.java
Original file line number Diff line number Diff line change
Expand Up @@ -683,6 +683,15 @@ public static int eval(int x) throws IllegalArgumentException, IOException {
}
}

/** User-defined function with niladic parentheses. */
public static class MyNiladicParenthesesFunction {
// This is a constant function that tests for niladic parentheses,
// and it returns the constant value foo
public String eval() {
return "foo";
}
}

/** Example of a UDF that has overloaded UDFs (same name, different args). */
public abstract static class CountArgs0Function {
private CountArgs0Function() {}
Expand Down

0 comments on commit af4391d

Please sign in to comment.