Skip to content

Commit ed692fd

Browse files
fib-seqjulianhyde
authored andcommitted
[CALCITE-3323] Add mode to SqlValidator that treats statements as valid if they contain unknown functions (Ryan Fu)
The mode is controlled by new method SqlValidator.isLenientOperatorLookup(). If it encounters an unknown function, or a known function with invalid number or types of arguments, the validator now creates a SqlUnresolvedFunction. The return type is inferred to be UNKNOWN, so that an enclosing function call can be validated. Removed .factorypath files and updated gitignore files to reject these. Close apache/calcite#1471
1 parent 0c8cbac commit ed692fd

File tree

8 files changed

+92
-11
lines changed

8 files changed

+92
-11
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ settings.xml
2525
.project
2626
.buildpath
2727
.classpath
28+
.factorypath
2829
.settings
2930
.checkstyle
3031

core/src/main/java/org/apache/calcite/sql/SqlFunction.java

Lines changed: 24 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818

1919
import org.apache.calcite.linq4j.function.Functions;
2020
import org.apache.calcite.rel.type.RelDataType;
21+
import org.apache.calcite.sql.parser.SqlParserPos;
22+
import org.apache.calcite.sql.type.OperandTypes;
2123
import org.apache.calcite.sql.type.SqlOperandTypeChecker;
2224
import org.apache.calcite.sql.type.SqlOperandTypeInference;
2325
import org.apache.calcite.sql.type.SqlReturnTypeInference;
@@ -271,25 +273,37 @@ SqlSyntax.FUNCTION, getKind(),
271273
return validator.deriveConstructorType(scope, call, this, function,
272274
argTypes);
273275
}
276+
277+
validCoercionType:
274278
if (function == null) {
275-
boolean changed = false;
276279
if (validator.isTypeCoercionEnabled()) {
277280
// try again if implicit type coercion is allowed.
278-
function = (SqlFunction) SqlUtil.lookupRoutine(validator.getOperatorTable(),
279-
getNameAsId(), argTypes, argNames, getFunctionType(), SqlSyntax.FUNCTION, getKind(),
280-
validator.getCatalogReader().nameMatcher(),
281-
true);
281+
function = (SqlFunction)
282+
SqlUtil.lookupRoutine(validator.getOperatorTable(), getNameAsId(),
283+
argTypes, argNames, getFunctionType(), SqlSyntax.FUNCTION,
284+
getKind(), validator.getCatalogReader().nameMatcher(), true);
282285
// try to coerce the function arguments to the declared sql type name.
283286
// if we succeed, the arguments would be wrapped with CAST operator.
284287
if (function != null) {
285288
TypeCoercion typeCoercion = validator.getTypeCoercion();
286-
changed = typeCoercion.userDefinedFunctionCoercion(scope, call, function);
289+
if (typeCoercion.userDefinedFunctionCoercion(scope, call, function)) {
290+
break validCoercionType;
291+
}
292+
}
293+
// if function doesn't exist within operator table and known function
294+
// handling is turned off then create a more permissive function
295+
if (function == null && validator.isLenientOperatorLookup()) {
296+
final SqlFunction x = (SqlFunction) call.getOperator();
297+
final SqlIdentifier identifier =
298+
Util.first(x.getSqlIdentifier(),
299+
new SqlIdentifier(x.getName(), SqlParserPos.ZERO));
300+
function = new SqlUnresolvedFunction(identifier, null,
301+
null, OperandTypes.VARIADIC, null, x.getFunctionType());
302+
break validCoercionType;
287303
}
288304
}
289-
if (!changed) {
290-
throw validator.handleUnresolvedFunction(call, this, argTypes,
291-
argNames);
292-
}
305+
throw validator.handleUnresolvedFunction(call, this, argTypes,
306+
argNames);
293307
}
294308

295309
// REVIEW jvs 25-Mar-2005: This is, in a sense, expanding

core/src/main/java/org/apache/calcite/sql/validate/SqlValidator.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -770,6 +770,27 @@ boolean validateModality(SqlSelect select, SqlModality modality,
770770

771771
SqlValidatorScope getWithScope(SqlNode withItem);
772772

773+
/**
774+
* Sets whether this validator should be lenient upon encountering an unknown
775+
* function.
776+
*
777+
* @param lenient Whether to be lenient when encountering an unknown function
778+
*/
779+
SqlValidator setLenientOperatorLookup(boolean lenient);
780+
781+
/** Returns whether this validator should be lenient upon encountering an
782+
* unknown function.
783+
*
784+
* <p>If true, if a statement contains a call to a function that is not
785+
* present in the operator table, or if the call does not have the required
786+
* number or types of operands, the validator nevertheless regards the
787+
* statement as valid. The type of the function call will be
788+
* {@link #getUnknownType() UNKNOWN}.
789+
*
790+
* <p>If false (the default behavior), an unknown function call causes a
791+
* validation error to be thrown. */
792+
boolean isLenientOperatorLookup();
793+
773794
/**
774795
* Set if implicit type coercion is allowed when the validator does validation.
775796
* See {@link org.apache.calcite.sql.validate.implicit.TypeCoercionImpl} for the details.

core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,8 @@ public class SqlValidatorImpl implements SqlValidatorWithHints {
273273

274274
protected boolean expandColumnReferences;
275275

276+
protected boolean lenientOperatorLookup;
277+
276278
private boolean rewriteCalls;
277279

278280
private NullCollation nullCollation = NullCollation.HIGH;
@@ -326,6 +328,7 @@ protected SqlValidatorImpl(
326328
groupFinder = new AggFinder(opTab, false, false, true, null, nameMatcher);
327329
aggOrOverOrGroupFinder = new AggFinder(opTab, true, true, true, null,
328330
nameMatcher);
331+
this.lenientOperatorLookup = false;
329332
this.enableTypeCoercion = catalogReader.getConfig() == null
330333
|| catalogReader.getConfig().typeCoercion();
331334
this.typeCoercion = TypeCoercions.getTypeCoercion(this, conformance);
@@ -3832,6 +3835,15 @@ public SqlValidatorScope getWithScope(SqlNode withItem) {
38323835
return scopes.get(withItem);
38333836
}
38343837

3838+
public SqlValidator setLenientOperatorLookup(boolean lenient) {
3839+
this.lenientOperatorLookup = lenient;
3840+
return this;
3841+
}
3842+
3843+
public boolean isLenientOperatorLookup() {
3844+
return this.lenientOperatorLookup;
3845+
}
3846+
38353847
public SqlValidator setEnableTypeCoercion(boolean enabled) {
38363848
this.enableTypeCoercion = enabled;
38373849
return this;

core/src/test/java/org/apache/calcite/sql/test/AbstractSqlTester.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,10 @@ public SqlTester withCaseSensitive(boolean sensitive) {
294294
return with("caseSensitive", sensitive);
295295
}
296296

297+
public SqlTester withLenientOperatorLookup(boolean lenient) {
298+
return with("lenientOperatorLookup", lenient);
299+
}
300+
297301
public SqlTester withLex(Lex lex) {
298302
return withQuoting(lex.quoting)
299303
.withCaseSensitive(lex.caseSensitive)

core/src/test/java/org/apache/calcite/sql/test/SqlTestFactory.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ public class SqlTestFactory {
6060
.put("quotedCasing", Casing.UNCHANGED)
6161
.put("unquotedCasing", Casing.TO_UPPER)
6262
.put("caseSensitive", true)
63+
.put("lenientOperatorLookup", false)
6364
.put("enableTypeCoercion", true)
6465
.put("conformance", SqlConformanceEnum.DEFAULT)
6566
.put("operatorTable", SqlStdOperatorTable.instance())
@@ -130,12 +131,15 @@ public static SqlParser.Config createParserConfig(ImmutableMap<String, Object> o
130131
public SqlValidator getValidator() {
131132
final SqlConformance conformance =
132133
(SqlConformance) options.get("conformance");
134+
final boolean lenientOperatorLookup =
135+
(boolean) options.get("lenientOperatorLookup");
133136
final boolean enableTypeCoercion = (boolean) options.get("enableTypeCoercion");
134137
return validatorFactory.create(operatorTable.get(),
135138
catalogReader.get(),
136139
typeFactory.get(),
137140
conformance)
138-
.setEnableTypeCoercion(enableTypeCoercion);
141+
.setEnableTypeCoercion(enableTypeCoercion)
142+
.setLenientOperatorLookup(lenientOperatorLookup);
139143
}
140144

141145
public SqlAdvisor createAdvisor() {

core/src/test/java/org/apache/calcite/sql/test/SqlTester.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,10 @@ enum VmName {
8383
/** Returns a tester that tests with implicit type coercion on/off. */
8484
SqlTester enableTypeCoercion(boolean enabled);
8585

86+
/** Returns a tester that does not fail validation if it encounters an
87+
* unknown function. */
88+
SqlTester withLenientOperatorLookup(boolean lenient);
89+
8690
/** Returns a tester that gets connections from a given factory. */
8791
SqlTester withConnectionFactory(
8892
CalciteAssert.ConnectionFactory connectionFactory);

core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1480,6 +1480,25 @@ public void _testLikeAndSimilarFails() {
14801480
.fails("No match found for function signature FOO..");
14811481
}
14821482

1483+
@Test public void testUnknownFunctionHandling() {
1484+
final Sql s = sql("?").withTester(t -> t.withLenientOperatorLookup(true));
1485+
s.expr("concat('a', 2)").ok();
1486+
s.expr("foo('2001-12-21')").ok();
1487+
s.expr("\"foo\"('b')").ok();
1488+
s.expr("foo()").ok();
1489+
s.expr("'a' || foo(bar('2001-12-21'))").ok();
1490+
s.expr("cast(foo(5, 2) as DECIMAL)").ok();
1491+
s.expr("select ascii('xyz')").ok();
1492+
s.expr("select get_bit(CAST('FFFF' as BINARY), 1)").ok();
1493+
s.expr("select now()").ok();
1494+
s.expr("^TIMESTAMP_CMP_TIMESTAMPTZ^").fails("(?s).*");
1495+
s.expr("atan(0)").ok();
1496+
s.expr("select row_number() over () from emp").ok();
1497+
s.expr("select coalesce(1, 2, 3)").ok();
1498+
s.sql("select count() from emp").ok(); // too few args
1499+
s.sql("select sum(1, 2) from emp").ok(); // too many args
1500+
}
1501+
14831502
@Test public void testJdbcFunctionCall() {
14841503
expr("{fn log10(1)}").ok();
14851504
expr("{fn locate('','')}").ok();
@@ -9893,6 +9912,8 @@ private static int prec(SqlOperator op) {
98939912

98949913
@Test public void testDummy() {
98959914
// (To debug individual statements, paste them into this method.)
9915+
final Sql s = sql("?").withTester(t -> t.withLenientOperatorLookup(true));
9916+
s.sql("select count() from emp").ok();
98969917
}
98979918

98989919
@Test public void testCustomColumnResolving() {

0 commit comments

Comments
 (0)