This is an automated email from the ASF dual-hosted git repository.
jhyde pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/calcite.git
The following commit(s) were added to refs/heads/master by this push:
new 174a707 [CALCITE-4443] Add ILIKE operator (as LIKE, but
case-insensitive and PostgreSQL-specific) (Ondřej Štumpf)
174a707 is described below
commit 174a707e1c199c97d7cc3531f0cd2e94745f4366
Author: Ondřej Štumpf <[email protected]>
AuthorDate: Mon Dec 21 13:09:18 2020 +0100
[CALCITE-4443] Add ILIKE operator (as LIKE, but case-insensitive and
PostgreSQL-specific) (Ondřej Štumpf)
Add method SqlOperator.not(), and remove
SqlImplementor.NOT_KIND_OPERATORS map. (Julian Hyde)
In RelToSqlConverterTest, allow Set<SqlLibrary> as part of
test configuration. (Julian Hyde)
Close apache/calcite#2317
---
babel/src/main/codegen/config.fmpp | 1 +
core/src/main/codegen/default_config.fmpp | 1 +
core/src/main/codegen/templates/Parser.jj | 5 ++
.../calcite/adapter/enumerable/RexImpTable.java | 7 +-
.../apache/calcite/rel/rel2sql/SqlImplementor.java | 31 +++++----
.../java/org/apache/calcite/rex/RexSimplify.java | 55 ++++++++-------
.../org/apache/calcite/runtime/SqlFunctions.java | 12 ++++
.../main/java/org/apache/calcite/sql/SqlKind.java | 8 +++
.../java/org/apache/calcite/sql/SqlOperator.java | 15 ++++
.../org/apache/calcite/sql/SqlSpecialOperator.java | 4 +-
.../calcite/sql/dialect/PostgresqlSqlDialect.java | 14 ++++
.../calcite/sql/dialect/VerticaSqlDialect.java | 15 ++++
.../apache/calcite/sql/fun/SqlBetweenOperator.java | 21 ++++++
.../org/apache/calcite/sql/fun/SqlInOperator.java | 20 ++++++
.../calcite/sql/fun/SqlLibraryOperators.java | 11 +++
.../apache/calcite/sql/fun/SqlLikeOperator.java | 70 ++++++++++++++++---
.../calcite/sql/fun/SqlPosixRegexOperator.java | 27 ++++++++
.../calcite/sql/fun/SqlStdOperatorTable.java | 36 +++++++---
.../apache/calcite/sql/parser/SqlParserUtil.java | 18 ++---
.../apache/calcite/sql/validate/SqlValidator.java | 5 +-
.../calcite/sql/validate/SqlValidatorImpl.java | 18 +++--
.../calcite/sql2rel/StandardConvertletTable.java | 7 ++
.../java/org/apache/calcite/tools/RelBuilder.java | 10 ++-
.../org/apache/calcite/util/BuiltInMethod.java | 1 +
.../calcite/util/PrecedenceClimbingParser.java | 4 +-
.../rel/rel2sql/RelToSqlConverterStructsTest.java | 2 +-
.../calcite/rel/rel2sql/RelToSqlConverterTest.java | 80 +++++++++++++++++-----
.../apache/calcite/sql/parser/SqlParserTest.java | 16 ++++-
.../apache/calcite/sql/test/SqlAdvisorTest.java | 1 +
.../calcite/sql/test/SqlOperatorBaseTest.java | 64 +++++++++++++++++
.../org/apache/calcite/test/RelBuilderTest.java | 19 ++++-
.../org/apache/calcite/test/SqlValidatorTest.java | 13 ++++
site/_docs/reference.md | 3 +
33 files changed, 507 insertions(+), 107 deletions(-)
diff --git a/babel/src/main/codegen/config.fmpp
b/babel/src/main/codegen/config.fmpp
index bb31a72..20cb864 100644
--- a/babel/src/main/codegen/config.fmpp
+++ b/babel/src/main/codegen/config.fmpp
@@ -237,6 +237,7 @@ data: {
"HOUR"
"IDENTITY"
# "IF" # not a keyword in Calcite
+ "ILIKE"
"IMMEDIATE"
"IMMEDIATELY"
"IMPORT"
diff --git a/core/src/main/codegen/default_config.fmpp
b/core/src/main/codegen/default_config.fmpp
index 00f89ed..d683329 100644
--- a/core/src/main/codegen/default_config.fmpp
+++ b/core/src/main/codegen/default_config.fmpp
@@ -130,6 +130,7 @@ parser: {
"HOP"
"HOURS"
"IGNORE"
+ "ILIKE"
"IMMEDIATE"
"IMMEDIATELY"
"IMPLEMENTATION"
diff --git a/core/src/main/codegen/templates/Parser.jj
b/core/src/main/codegen/templates/Parser.jj
index 3857911..9ea92ea 100644
--- a/core/src/main/codegen/templates/Parser.jj
+++ b/core/src/main/codegen/templates/Parser.jj
@@ -3588,11 +3588,15 @@ List<Object> Expression2(ExprContext exprContext) :
(
<LIKE> { op = SqlStdOperatorTable.NOT_LIKE; }
|
+ <ILIKE> { op = SqlLibraryOperators.NOT_ILIKE; }
+ |
<SIMILAR> <TO> { op =
SqlStdOperatorTable.NOT_SIMILAR_TO; }
)
|
<LIKE> { op = SqlStdOperatorTable.LIKE; }
|
+ <ILIKE> { op = SqlLibraryOperators.ILIKE; }
+ |
<SIMILAR> <TO> { op = SqlStdOperatorTable.SIMILAR_TO; }
)
<#if
(parser.includePosixOperators!default.parser.includePosixOperators)>
@@ -7447,6 +7451,7 @@ SqlPostfixOperator PostfixRowOperator() :
| < HOURS: "HOURS" >
| < IDENTITY: "IDENTITY" >
| < IGNORE: "IGNORE" >
+| < ILIKE: "ILIKE" >
| < IMMEDIATE: "IMMEDIATE" >
| < IMMEDIATELY: "IMMEDIATELY" >
| < IMPLEMENTATION: "IMPLEMENTATION" >
diff --git
a/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java
b/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java
index eebac14..a39357a 100644
--- a/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java
+++ b/core/src/main/java/org/apache/calcite/adapter/enumerable/RexImpTable.java
@@ -120,6 +120,7 @@ import static
org.apache.calcite.sql.fun.SqlLibraryOperators.EXISTS_NODE;
import static org.apache.calcite.sql.fun.SqlLibraryOperators.EXTRACT_VALUE;
import static org.apache.calcite.sql.fun.SqlLibraryOperators.EXTRACT_XML;
import static org.apache.calcite.sql.fun.SqlLibraryOperators.FROM_BASE64;
+import static org.apache.calcite.sql.fun.SqlLibraryOperators.ILIKE;
import static org.apache.calcite.sql.fun.SqlLibraryOperators.JSON_DEPTH;
import static org.apache.calcite.sql.fun.SqlLibraryOperators.JSON_KEYS;
import static org.apache.calcite.sql.fun.SqlLibraryOperators.JSON_LENGTH;
@@ -469,11 +470,15 @@ public class RexImpTable {
map.put(IS_FALSE, new IsFalseImplementor());
map.put(IS_NOT_FALSE, new IsNotFalseImplementor());
- // LIKE and SIMILAR
+ // LIKE, ILIKE and SIMILAR
final MethodImplementor likeImplementor =
new MethodImplementor(BuiltInMethod.LIKE.method, NullPolicy.STRICT,
false);
map.put(LIKE, likeImplementor);
+ final MethodImplementor ilikeImplementor =
+ new MethodImplementor(BuiltInMethod.ILIKE.method, NullPolicy.STRICT,
+ false);
+ map.put(ILIKE, ilikeImplementor);
final MethodImplementor similarImplementor =
new MethodImplementor(BuiltInMethod.SIMILAR.method, NullPolicy.STRICT,
false);
diff --git
a/core/src/main/java/org/apache/calcite/rel/rel2sql/SqlImplementor.java
b/core/src/main/java/org/apache/calcite/rel/rel2sql/SqlImplementor.java
index ff00cf7..8ccbbe2 100644
--- a/core/src/main/java/org/apache/calcite/rel/rel2sql/SqlImplementor.java
+++ b/core/src/main/java/org/apache/calcite/rel/rel2sql/SqlImplementor.java
@@ -141,16 +141,6 @@ public abstract class SqlImplementor {
final RexBuilder rexBuilder =
new RexBuilder(new SqlTypeFactoryImpl(RelDataTypeSystemImpl.DEFAULT));
- /** Maps a {@link SqlKind} to a {@link SqlOperator} that implements NOT
- * applied to that kind. */
- private static final Map<SqlKind, SqlOperator> NOT_KIND_OPERATORS =
- ImmutableMap.<SqlKind, SqlOperator>builder()
- .put(SqlKind.IN, SqlStdOperatorTable.NOT_IN)
- .put(SqlKind.NOT_IN, SqlStdOperatorTable.IN)
- .put(SqlKind.LIKE, SqlStdOperatorTable.NOT_LIKE)
- .put(SqlKind.SIMILAR, SqlStdOperatorTable.NOT_SIMILAR_TO)
- .build();
-
protected SqlImplementor(SqlDialect dialect) {
this.dialect = requireNonNull(dialect);
}
@@ -798,7 +788,7 @@ public abstract class SqlImplementor {
case NOT:
RexNode operand = ((RexCall) rex).operands.get(0);
final SqlNode node = toSql(program, operand);
- final SqlOperator inverseOperator =
NOT_KIND_OPERATORS.get(operand.getKind());
+ final SqlOperator inverseOperator = getInverseOperator(operand);
if (inverseOperator != null) {
switch (operand.getKind()) {
case IN:
@@ -832,7 +822,7 @@ public abstract class SqlImplementor {
break;
case NOT:
RexNode operand = call.operands.get(0);
- if (NOT_KIND_OPERATORS.containsKey(operand.getKind())) {
+ if (getInverseOperator(operand) != null) {
return callToSql(program, (RexCall) operand, !not);
}
break;
@@ -840,9 +830,8 @@ public abstract class SqlImplementor {
break;
}
if (not) {
- SqlKind kind = op.getKind();
- op = requireNonNull(NOT_KIND_OPERATORS.get(kind),
- () -> "unable to negate " + kind);
+ op = requireNonNull(getInverseOperator(call),
+ () -> "unable to negate " + call.getKind());
}
final List<SqlNode> nodeList = toSql(program, call.getOperands());
switch (call.getKind()) {
@@ -870,6 +859,18 @@ public abstract class SqlImplementor {
return SqlUtil.createCall(op, POS, nodeList);
}
+ /** If {@code node} is a {@link RexCall}, extracts the operator and
+ * finds the corresponding inverse operator using {@link
SqlOperator#not()}.
+ * Returns null if {@code node} is not a {@link RexCall},
+ * or if the operator has no logical inverse. */
+ private static @Nullable SqlOperator getInverseOperator(RexNode node) {
+ if (node instanceof RexCall) {
+ return ((RexCall) node).getOperator().not();
+ } else {
+ return null;
+ }
+ }
+
/** Converts a Sarg to SQL, generating "operand IN (c1, c2, ...)" if the
* ranges are all points. */
@SuppressWarnings({"BetaApi", "UnstableApiUsage"})
diff --git a/core/src/main/java/org/apache/calcite/rex/RexSimplify.java
b/core/src/main/java/org/apache/calcite/rex/RexSimplify.java
index 9e05265..15c3d0d 100644
--- a/core/src/main/java/org/apache/calcite/rex/RexSimplify.java
+++ b/core/src/main/java/org/apache/calcite/rex/RexSimplify.java
@@ -582,10 +582,12 @@ public class RexSimplify {
private RexNode simplifyNot(RexCall call, RexUnknownAs unknownAs) {
final RexNode a = call.getOperands().get(0);
+ final List<RexNode> newOperands;
switch (a.getKind()) {
case NOT:
// NOT NOT x ==> x
return simplify(((RexCall) a).getOperands().get(0), unknownAs);
+
case SEARCH:
// NOT SEARCH(x, Sarg[(-inf, 10) OR NULL) ==> SEARCH(x, Sarg[[10, +inf)])
final RexCall call2 = (RexCall) a;
@@ -598,30 +600,17 @@ public class RexSimplify {
rexBuilder.makeLiteral(requireNonNull(sarg,
"sarg").negate(), literal.getType(),
literal.getTypeName()))),
unknownAs.negate());
+
case LITERAL:
if (a.getType().getSqlTypeName() == SqlTypeName.BOOLEAN
&& !RexLiteral.isNullLiteral(a)) {
return rexBuilder.makeLiteral(!RexLiteral.booleanValue(a));
}
break;
- default:
- break;
- }
- final SqlKind negateKind = a.getKind().negate();
- if (a.getKind() != negateKind) {
- return simplify(
- rexBuilder.makeCall(RexUtil.op(negateKind),
- ((RexCall) a).getOperands()), unknownAs);
- }
- final SqlKind negateKind2 = a.getKind().negateNullSafe();
- if (a.getKind() != negateKind2) {
- return simplify(
- rexBuilder.makeCall(RexUtil.op(negateKind2),
- ((RexCall) a).getOperands()), unknownAs);
- }
- if (a.getKind() == SqlKind.AND) {
+
+ case AND:
// NOT distributivity for AND
- final List<RexNode> newOperands = new ArrayList<>();
+ newOperands = new ArrayList<>();
for (RexNode operand : ((RexCall) a).getOperands()) {
newOperands.add(
simplify(rexBuilder.makeCall(SqlStdOperatorTable.NOT, operand),
@@ -629,10 +618,10 @@ public class RexSimplify {
}
return simplify(
rexBuilder.makeCall(SqlStdOperatorTable.OR, newOperands), unknownAs);
- }
- if (a.getKind() == SqlKind.OR) {
+
+ case OR:
// NOT distributivity for OR
- final List<RexNode> newOperands = new ArrayList<>();
+ newOperands = new ArrayList<>();
for (RexNode operand : ((RexCall) a).getOperands()) {
newOperands.add(
simplify(rexBuilder.makeCall(SqlStdOperatorTable.NOT, operand),
@@ -640,9 +629,9 @@ public class RexSimplify {
}
return simplify(
rexBuilder.makeCall(SqlStdOperatorTable.AND, newOperands),
unknownAs);
- }
- if (a.getKind() == SqlKind.CASE) {
- final List<RexNode> newOperands = new ArrayList<>();
+
+ case CASE:
+ newOperands = new ArrayList<>();
List<RexNode> operands = ((RexCall) a).getOperands();
for (int i = 0; i < operands.size(); i += 2) {
if (i + 1 == operands.size()) {
@@ -654,7 +643,27 @@ public class RexSimplify {
}
return simplify(
rexBuilder.makeCall(SqlStdOperatorTable.CASE, newOperands),
unknownAs);
+
+ case IN:
+ case NOT_IN:
+ // do not try to negate
+ break;
+
+ default:
+ final SqlKind negateKind = a.getKind().negate();
+ if (a.getKind() != negateKind) {
+ return simplify(
+ rexBuilder.makeCall(RexUtil.op(negateKind),
+ ((RexCall) a).getOperands()), unknownAs);
+ }
+ final SqlKind negateKind2 = a.getKind().negateNullSafe();
+ if (a.getKind() != negateKind2) {
+ return simplify(
+ rexBuilder.makeCall(RexUtil.op(negateKind2),
+ ((RexCall) a).getOperands()), unknownAs);
+ }
}
+
RexNode a2 = simplify(a, unknownAs.negate());
if (a == a2) {
return call;
diff --git a/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
b/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
index eaab70b..afcff87 100644
--- a/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
+++ b/core/src/main/java/org/apache/calcite/runtime/SqlFunctions.java
@@ -607,6 +607,18 @@ public class SqlFunctions {
return Pattern.matches(regex, s);
}
+ /** SQL {@code ILIKE} function. */
+ public static boolean ilike(String s, String pattern) {
+ final String regex = Like.sqlToRegexLike(pattern, null);
+ return Pattern.compile(regex,
Pattern.CASE_INSENSITIVE).matcher(s).matches();
+ }
+
+ /** SQL {@code ILIKE} function with escape. */
+ public static boolean ilike(String s, String pattern, String escape) {
+ final String regex = Like.sqlToRegexLike(pattern, escape);
+ return Pattern.compile(regex,
Pattern.CASE_INSENSITIVE).matcher(s).matches();
+ }
+
/** SQL {@code SIMILAR} function. */
public static boolean similar(String s, String pattern) {
final String regex = Like.sqlToRegexSimilar(pattern, null);
diff --git a/core/src/main/java/org/apache/calcite/sql/SqlKind.java
b/core/src/main/java/org/apache/calcite/sql/SqlKind.java
index aa18bfe..d7a6493 100644
--- a/core/src/main/java/org/apache/calcite/sql/SqlKind.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlKind.java
@@ -1385,6 +1385,14 @@ public enum SqlKind {
return GREATER_THAN;
case GREATER_THAN_OR_EQUAL:
return LESS_THAN;
+ case IN:
+ return NOT_IN;
+ case NOT_IN:
+ return IN;
+ case DRUID_IN:
+ return DRUID_NOT_IN;
+ case DRUID_NOT_IN:
+ return DRUID_IN;
case IS_TRUE:
return IS_FALSE;
case IS_FALSE:
diff --git a/core/src/main/java/org/apache/calcite/sql/SqlOperator.java
b/core/src/main/java/org/apache/calcite/sql/SqlOperator.java
index 3534282..b7e00d8 100644
--- a/core/src/main/java/org/apache/calcite/sql/SqlOperator.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlOperator.java
@@ -595,6 +595,10 @@ public abstract class SqlOperator {
argTypes, null, null, getSyntax(), getKind(),
validator.getCatalogReader().nameMatcher(), false);
+ if (sqlOperator == null) {
+ throw validator.handleUnresolvedFunction(call, this, argTypes, null);
+ }
+
((SqlBasicCall) call).setOperator(castNonNull(sqlOperator));
RelDataType type = call.getOperator().validateOperands(validator, scope,
call);
@@ -955,6 +959,17 @@ public abstract class SqlOperator {
return returnTypeInference;
}
+ /** Returns the operator that is the logical inverse of this operator.
+ *
+ * <p>For example, {@code SqlStdOperatorTable.LIKE.not()} returns
+ * {@code SqlStdOperatorTable.NOT_LIKE}, and vice versa.
+ *
+ * <p>By default, returns {@code null}, which means there is no inverse
+ * operator. */
+ public @Nullable SqlOperator not() {
+ return null;
+ }
+
/**
* Returns the {@link Strong.Policy} strategy for this operator, or null if
* there is no particular strategy, in which case this policy will be
deducted
diff --git a/core/src/main/java/org/apache/calcite/sql/SqlSpecialOperator.java
b/core/src/main/java/org/apache/calcite/sql/SqlSpecialOperator.java
index 4f9b9db..a37ec2b 100644
--- a/core/src/main/java/org/apache/calcite/sql/SqlSpecialOperator.java
+++ b/core/src/main/java/org/apache/calcite/sql/SqlSpecialOperator.java
@@ -99,8 +99,8 @@ public class SqlSpecialOperator extends SqlOperator {
SqlOperator op(int i);
SqlParserPos pos(int i);
boolean isOp(int i);
- @Nullable SqlNode node(int i);
- void replaceSublist(int start, int end, @Nullable SqlNode e);
+ SqlNode node(int i);
+ void replaceSublist(int start, int end, SqlNode e);
/** Creates a parser whose token sequence is a copy of a subset of this
* token sequence. */
diff --git
a/core/src/main/java/org/apache/calcite/sql/dialect/PostgresqlSqlDialect.java
b/core/src/main/java/org/apache/calcite/sql/dialect/PostgresqlSqlDialect.java
index cb87373..87137c8 100644
---
a/core/src/main/java/org/apache/calcite/sql/dialect/PostgresqlSqlDialect.java
+++
b/core/src/main/java/org/apache/calcite/sql/dialect/PostgresqlSqlDialect.java
@@ -27,6 +27,7 @@ import org.apache.calcite.sql.SqlDataTypeSpec;
import org.apache.calcite.sql.SqlDialect;
import org.apache.calcite.sql.SqlLiteral;
import org.apache.calcite.sql.SqlNode;
+import org.apache.calcite.sql.SqlOperator;
import org.apache.calcite.sql.SqlWriter;
import org.apache.calcite.sql.fun.SqlFloorFunction;
import org.apache.calcite.sql.parser.SqlParserPos;
@@ -34,6 +35,8 @@ import org.apache.calcite.sql.type.SqlTypeName;
import org.checkerframework.checker.nullness.qual.Nullable;
+import java.util.List;
+
/**
* A <code>SqlDialect</code> implementation for the PostgreSQL database.
*/
@@ -94,6 +97,17 @@ public class PostgresqlSqlDialect extends SqlDialect {
SqlParserPos.ZERO);
}
+ @Override public boolean supportsFunction(SqlOperator operator,
+ RelDataType type, final List<RelDataType> paramTypes) {
+ switch (operator.kind) {
+ case LIKE:
+ // introduces support for ILIKE as well
+ return true;
+ default:
+ return super.supportsFunction(operator, type, paramTypes);
+ }
+ }
+
@Override public boolean requiresAliasForFromItems() {
return true;
}
diff --git
a/core/src/main/java/org/apache/calcite/sql/dialect/VerticaSqlDialect.java
b/core/src/main/java/org/apache/calcite/sql/dialect/VerticaSqlDialect.java
index d1cc8f8..3c31d7f 100644
--- a/core/src/main/java/org/apache/calcite/sql/dialect/VerticaSqlDialect.java
+++ b/core/src/main/java/org/apache/calcite/sql/dialect/VerticaSqlDialect.java
@@ -17,7 +17,11 @@
package org.apache.calcite.sql.dialect;
import org.apache.calcite.avatica.util.Casing;
+import org.apache.calcite.rel.type.RelDataType;
import org.apache.calcite.sql.SqlDialect;
+import org.apache.calcite.sql.SqlOperator;
+
+import java.util.List;
/**
* A <code>SqlDialect</code> implementation for the Vertica database.
@@ -38,4 +42,15 @@ public class VerticaSqlDialect extends SqlDialect {
@Override public boolean supportsNestedAggregations() {
return false;
}
+
+ @Override public boolean supportsFunction(SqlOperator operator,
+ RelDataType type, final List<RelDataType> paramTypes) {
+ switch (operator.kind) {
+ case LIKE:
+ // introduces support for ILIKE as well
+ return true;
+ default:
+ return super.supportsFunction(operator, type, paramTypes);
+ }
+ }
}
diff --git
a/core/src/main/java/org/apache/calcite/sql/fun/SqlBetweenOperator.java
b/core/src/main/java/org/apache/calcite/sql/fun/SqlBetweenOperator.java
index 685a12d..1030eca 100644
--- a/core/src/main/java/org/apache/calcite/sql/fun/SqlBetweenOperator.java
+++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlBetweenOperator.java
@@ -117,10 +117,31 @@ public class SqlBetweenOperator extends SqlInfixOperator {
return litmus.fail("not a rex operator");
}
+ /**
+ * Returns whether this is 'NOT' variant of an operator.
+ *
+ * @see #not()
+ */
public boolean isNegated() {
return negated;
}
+ @Override public SqlOperator not() {
+ return of(negated, flag == Flag.SYMMETRIC);
+ }
+
+ private static SqlBetweenOperator of(boolean negated, boolean symmetric) {
+ if (symmetric) {
+ return negated
+ ? SqlStdOperatorTable.SYMMETRIC_BETWEEN
+ : SqlStdOperatorTable.SYMMETRIC_NOT_BETWEEN;
+ } else {
+ return negated
+ ? SqlStdOperatorTable.NOT_BETWEEN
+ : SqlStdOperatorTable.BETWEEN;
+ }
+ }
+
@Override public RelDataType inferReturnType(
SqlOperatorBinding opBinding) {
ExplicitOperatorBinding newOpBinding =
diff --git a/core/src/main/java/org/apache/calcite/sql/fun/SqlInOperator.java
b/core/src/main/java/org/apache/calcite/sql/fun/SqlInOperator.java
index d4d2819..8a43e70 100644
--- a/core/src/main/java/org/apache/calcite/sql/fun/SqlInOperator.java
+++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlInOperator.java
@@ -26,6 +26,7 @@ import org.apache.calcite.sql.SqlCallBinding;
import org.apache.calcite.sql.SqlKind;
import org.apache.calcite.sql.SqlNode;
import org.apache.calcite.sql.SqlNodeList;
+import org.apache.calcite.sql.SqlOperator;
import org.apache.calcite.sql.type.ComparableOperandTypeChecker;
import org.apache.calcite.sql.type.InferTypes;
import org.apache.calcite.sql.type.OperandTypes;
@@ -80,6 +81,25 @@ public class SqlInOperator extends SqlBinaryOperator {
return kind == SqlKind.NOT_IN;
}
+ @Override public SqlOperator not() {
+ return of(kind.negateNullSafe());
+ }
+
+ private static SqlBinaryOperator of(SqlKind kind) {
+ switch (kind) {
+ case IN:
+ return SqlStdOperatorTable.IN;
+ case NOT_IN:
+ return SqlStdOperatorTable.NOT_IN;
+ case DRUID_IN:
+ return SqlInternalOperators.DRUID_IN;
+ case DRUID_NOT_IN:
+ return SqlInternalOperators.DRUID_NOT_IN;
+ default:
+ throw new AssertionError("unexpected " + kind);
+ }
+ }
+
@Override public boolean validRexOperands(int count, Litmus litmus) {
if (count == 0) {
return litmus.fail("wrong operand count {} for {}", count, this);
diff --git
a/core/src/main/java/org/apache/calcite/sql/fun/SqlLibraryOperators.java
b/core/src/main/java/org/apache/calcite/sql/fun/SqlLibraryOperators.java
index d5cdecc..911fc21 100644
--- a/core/src/main/java/org/apache/calcite/sql/fun/SqlLibraryOperators.java
+++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlLibraryOperators.java
@@ -25,6 +25,7 @@ import org.apache.calcite.sql.SqlKind;
import org.apache.calcite.sql.SqlOperator;
import org.apache.calcite.sql.SqlOperatorBinding;
import org.apache.calcite.sql.SqlOperatorTable;
+import org.apache.calcite.sql.SqlSpecialOperator;
import org.apache.calcite.sql.SqlSyntax;
import org.apache.calcite.sql.type.InferTypes;
import org.apache.calcite.sql.type.OperandTypes;
@@ -460,6 +461,16 @@ public abstract class SqlLibraryOperators {
OperandTypes.STRING_STRING,
SqlFunctionCategory.STRING);
+ /** The case-insensitive variant of the LIKE operator. */
+ @LibraryOperator(libraries = {POSTGRESQL})
+ public static final SqlSpecialOperator ILIKE =
+ new SqlLikeOperator("ILIKE", SqlKind.LIKE, false, false);
+
+ /** The case-insensitive variant of the NOT LIKE operator. */
+ @LibraryOperator(libraries = {POSTGRESQL})
+ public static final SqlSpecialOperator NOT_ILIKE =
+ new SqlLikeOperator("NOT ILIKE", SqlKind.LIKE, true, false);
+
/** The "CONCAT(arg, ...)" function that concatenates strings.
* For example, "CONCAT('a', 'bc', 'd')" returns "abcd". */
@LibraryOperator(libraries = {MYSQL, POSTGRESQL})
diff --git a/core/src/main/java/org/apache/calcite/sql/fun/SqlLikeOperator.java
b/core/src/main/java/org/apache/calcite/sql/fun/SqlLikeOperator.java
index 754db45..ce5cf82 100644
--- a/core/src/main/java/org/apache/calcite/sql/fun/SqlLikeOperator.java
+++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlLikeOperator.java
@@ -31,10 +31,10 @@ import org.apache.calcite.sql.type.OperandTypes;
import org.apache.calcite.sql.type.ReturnTypes;
import org.apache.calcite.sql.type.SqlOperandCountRanges;
import org.apache.calcite.sql.type.SqlTypeUtil;
+import org.apache.calcite.sql.validate.SqlValidator;
+import org.apache.calcite.sql.validate.SqlValidatorScope;
import org.apache.calcite.util.Litmus;
-import org.checkerframework.checker.nullness.qual.Nullable;
-
/**
* An operator describing the <code>LIKE</code> and <code>SIMILAR</code>
* operators.
@@ -56,20 +56,23 @@ public class SqlLikeOperator extends SqlSpecialOperator {
//~ Instance fields --------------------------------------------------------
private final boolean negated;
+ private final boolean caseSensitive;
//~ Constructors -----------------------------------------------------------
/**
* Creates a SqlLikeOperator.
*
- * @param name Operator name
- * @param kind Kind
- * @param negated Whether this is 'NOT LIKE'
+ * @param name Operator name
+ * @param kind Kind
+ * @param negated Whether this is 'NOT LIKE'
+ * @param caseSensitive Whether this operator ignores the case of its
operands
*/
SqlLikeOperator(
String name,
SqlKind kind,
- boolean negated) {
+ boolean negated,
+ boolean caseSensitive) {
// LIKE is right-associative, because that makes it easier to capture
// dangling ESCAPE clauses: "a like b like c escape d" becomes
// "a like (b like c escape d)".
@@ -81,7 +84,13 @@ public class SqlLikeOperator extends SqlSpecialOperator {
ReturnTypes.BOOLEAN_NULLABLE,
InferTypes.FIRST_KNOWN,
OperandTypes.STRING_SAME_SAME_SAME);
+ if (!caseSensitive && kind != SqlKind.LIKE) {
+ throw new IllegalArgumentException("Only (possibly negated) "
+ + SqlKind.LIKE + " can be made case-insensitive, not " + kind);
+ }
+
this.negated = negated;
+ this.caseSensitive = caseSensitive;
}
//~ Methods ----------------------------------------------------------------
@@ -90,11 +99,49 @@ public class SqlLikeOperator extends SqlSpecialOperator {
* Returns whether this is the 'NOT LIKE' operator.
*
* @return whether this is 'NOT LIKE'
+ *
+ * @see #not()
*/
public boolean isNegated() {
return negated;
}
+ /**
+ * Returns whether this operator matches the case of its operands.
+ * For example, returns true for {@code LIKE} and false for {@code ILIKE}.
+ *
+ * @return whether this operator matches the case of its operands
+ */
+ public boolean isCaseSensitive() {
+ return caseSensitive;
+ }
+
+ @Override public SqlOperator not() {
+ return of(kind, !negated, caseSensitive);
+ }
+
+ private static SqlOperator of(SqlKind kind, boolean negated,
+ boolean caseSensitive) {
+ switch (kind) {
+ case SIMILAR:
+ return negated
+ ? SqlStdOperatorTable.NOT_SIMILAR_TO
+ : SqlStdOperatorTable.SIMILAR_TO;
+ case LIKE:
+ if (caseSensitive) {
+ return negated
+ ? SqlStdOperatorTable.NOT_LIKE
+ : SqlStdOperatorTable.LIKE;
+ } else {
+ return negated
+ ? SqlLibraryOperators.NOT_ILIKE
+ : SqlLibraryOperators.ILIKE;
+ }
+ default:
+ throw new AssertionError("unexpected " + kind);
+ }
+ }
+
@Override public SqlOperandCountRange getOperandCountRange() {
return SqlOperandCountRanges.between(2, 3);
}
@@ -131,6 +178,11 @@ public class SqlLikeOperator extends SqlSpecialOperator {
throwOnFailure);
}
+ @Override public void validateCall(SqlCall call, SqlValidator validator,
+ SqlValidatorScope scope, SqlValidatorScope operandScope) {
+ super.validateCall(call, validator, scope, operandScope);
+ }
+
@Override public boolean validRexOperands(int count, Litmus litmus) {
if (negated) {
litmus.fail("unsupported negated operator {}", this);
@@ -185,8 +237,8 @@ public class SqlLikeOperator extends SqlSpecialOperator {
}
}
}
- final @Nullable SqlNode[] operands;
- int end;
+ final SqlNode[] operands;
+ final int end;
if (exp2 != null) {
operands = new SqlNode[]{exp0, exp1, exp2};
end = opOrdinal + 4;
@@ -194,7 +246,7 @@ public class SqlLikeOperator extends SqlSpecialOperator {
operands = new SqlNode[]{exp0, exp1};
end = opOrdinal + 2;
}
- SqlCall call = createCall(SqlParserPos.ZERO, operands);
+ SqlCall call = createCall(SqlParserPos.sum(operands), operands);
return new ReduceResult(opOrdinal - 1, end, call);
}
}
diff --git
a/core/src/main/java/org/apache/calcite/sql/fun/SqlPosixRegexOperator.java
b/core/src/main/java/org/apache/calcite/sql/fun/SqlPosixRegexOperator.java
index fe6c204..c3c519b 100644
--- a/core/src/main/java/org/apache/calcite/sql/fun/SqlPosixRegexOperator.java
+++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlPosixRegexOperator.java
@@ -22,6 +22,7 @@ import org.apache.calcite.sql.SqlCall;
import org.apache.calcite.sql.SqlCallBinding;
import org.apache.calcite.sql.SqlKind;
import org.apache.calcite.sql.SqlOperandCountRange;
+import org.apache.calcite.sql.SqlOperator;
import org.apache.calcite.sql.SqlWriter;
import org.apache.calcite.sql.type.InferTypes;
import org.apache.calcite.sql.type.OperandTypes;
@@ -78,6 +79,22 @@ public class SqlPosixRegexOperator extends SqlBinaryOperator
{
// ~ Methods ----------------------------------------------------------------
+ @Override public SqlOperator not() {
+ return of(!negated, caseSensitive);
+ }
+
+ private static SqlOperator of(boolean negated, boolean ignoreCase) {
+ if (ignoreCase) {
+ return negated
+ ? SqlStdOperatorTable.NEGATED_POSIX_REGEX_CASE_SENSITIVE
+ : SqlStdOperatorTable.POSIX_REGEX_CASE_SENSITIVE;
+ } else {
+ return negated
+ ? SqlStdOperatorTable.NEGATED_POSIX_REGEX_CASE_INSENSITIVE
+ : SqlStdOperatorTable.POSIX_REGEX_CASE_INSENSITIVE;
+ }
+ }
+
@Override public SqlOperandCountRange getOperandCountRange() {
return SqlOperandCountRanges.of(2);
}
@@ -120,10 +137,20 @@ public class SqlPosixRegexOperator extends
SqlBinaryOperator {
writer.endList(frame);
}
+ /**
+ * Returns whether this operator matches the case of its operands.
+ *
+ * @return whether this operator matches the case of its operands
+ */
public boolean isCaseSensitive() {
return caseSensitive;
}
+ /**
+ * Returns whether this is 'NOT' variant of an operator.
+ *
+ * @see #not()
+ */
public boolean isNegated() {
return negated;
}
diff --git
a/core/src/main/java/org/apache/calcite/sql/fun/SqlStdOperatorTable.java
b/core/src/main/java/org/apache/calcite/sql/fun/SqlStdOperatorTable.java
index a34098e..8521225 100644
--- a/core/src/main/java/org/apache/calcite/sql/fun/SqlStdOperatorTable.java
+++ b/core/src/main/java/org/apache/calcite/sql/fun/SqlStdOperatorTable.java
@@ -1399,22 +1399,24 @@ public class SqlStdOperatorTable extends
ReflectiveSqlOperatorTable {
true);
public static final SqlSpecialOperator NOT_LIKE =
- new SqlLikeOperator("NOT LIKE", SqlKind.LIKE, true);
+ new SqlLikeOperator("NOT LIKE", SqlKind.LIKE, true, true);
public static final SqlSpecialOperator LIKE =
- new SqlLikeOperator("LIKE", SqlKind.LIKE, false);
+ new SqlLikeOperator("LIKE", SqlKind.LIKE, false, true);
public static final SqlSpecialOperator NOT_SIMILAR_TO =
- new SqlLikeOperator("NOT SIMILAR TO", SqlKind.SIMILAR, true);
+ new SqlLikeOperator("NOT SIMILAR TO", SqlKind.SIMILAR, true, true);
public static final SqlSpecialOperator SIMILAR_TO =
- new SqlLikeOperator("SIMILAR TO", SqlKind.SIMILAR, false);
+ new SqlLikeOperator("SIMILAR TO", SqlKind.SIMILAR, false, true);
- public static final SqlBinaryOperator POSIX_REGEX_CASE_SENSITIVE = new
SqlPosixRegexOperator(
- "POSIX REGEX CASE SENSITIVE", SqlKind.POSIX_REGEX_CASE_SENSITIVE, true,
false);
+ public static final SqlBinaryOperator POSIX_REGEX_CASE_SENSITIVE =
+ new SqlPosixRegexOperator("POSIX REGEX CASE SENSITIVE",
+ SqlKind.POSIX_REGEX_CASE_SENSITIVE, true, false);
- public static final SqlBinaryOperator POSIX_REGEX_CASE_INSENSITIVE = new
SqlPosixRegexOperator(
- "POSIX REGEX CASE INSENSITIVE", SqlKind.POSIX_REGEX_CASE_INSENSITIVE,
false, false);
+ public static final SqlBinaryOperator POSIX_REGEX_CASE_INSENSITIVE =
+ new SqlPosixRegexOperator("POSIX REGEX CASE INSENSITIVE",
+ SqlKind.POSIX_REGEX_CASE_INSENSITIVE, false, false);
public static final SqlBinaryOperator NEGATED_POSIX_REGEX_CASE_SENSITIVE =
new SqlPosixRegexOperator("NEGATED POSIX REGEX CASE SENSITIVE",
@@ -2619,4 +2621,22 @@ public class SqlStdOperatorTable extends
ReflectiveSqlOperatorTable {
}
}
+ /** Returns the operator for {@code LIKE} with given case-sensitivity,
+ * optionally negated. */
+ public static SqlOperator like(boolean negated, boolean caseSensitive) {
+ if (negated) {
+ if (caseSensitive) {
+ return NOT_LIKE;
+ } else {
+ return SqlLibraryOperators.NOT_ILIKE;
+ }
+ } else {
+ if (caseSensitive) {
+ return LIKE;
+ } else {
+ return SqlLibraryOperators.ILIKE;
+ }
+ }
+ }
+
}
diff --git
a/core/src/main/java/org/apache/calcite/sql/parser/SqlParserUtil.java
b/core/src/main/java/org/apache/calcite/sql/parser/SqlParserUtil.java
index 87d6cd2..f697205 100644
--- a/core/src/main/java/org/apache/calcite/sql/parser/SqlParserUtil.java
+++ b/core/src/main/java/org/apache/calcite/sql/parser/SqlParserUtil.java
@@ -653,7 +653,7 @@ public final class SqlParserUtil {
* we encounter a token of this kind.
* @return the root node of the tree which the list condenses into
*/
- public static @Nullable SqlNode toTreeEx(SqlSpecialOperator.TokenSequence
list,
+ public static SqlNode toTreeEx(SqlSpecialOperator.TokenSequence list,
int start, final int minPrec, final SqlKind stopperKind) {
PrecedenceClimbingParser parser = list.parser(start,
token -> {
@@ -676,10 +676,10 @@ public final class SqlParserUtil {
return node;
}
- private static @Nullable SqlNode convert(PrecedenceClimbingParser.Token
token) {
+ private static SqlNode convert(PrecedenceClimbingParser.Token token) {
switch (token.type) {
case ATOM:
- return (SqlNode) token.o;
+ return requireNonNull((SqlNode) token.o);
case CALL:
final PrecedenceClimbingParser.Call call =
(PrecedenceClimbingParser.Call) token;
@@ -865,11 +865,11 @@ public final class SqlParserUtil {
return list.get(i).o instanceof ToTreeListItem;
}
- @Override public @Nullable SqlNode node(int i) {
+ @Override public SqlNode node(int i) {
return convert(list.get(i));
}
- @Override public void replaceSublist(int start, int end, @Nullable SqlNode
e) {
+ @Override public void replaceSublist(int start, int end, SqlNode e) {
SqlParserUtil.replaceSublist(list, start, end, parser.atom(e));
}
}
@@ -918,7 +918,7 @@ public final class SqlParserUtil {
throw new AssertionError();
}
} else {
- builder.atom(o);
+ builder.atom(requireNonNull(o));
}
}
return builder.build();
@@ -946,11 +946,11 @@ public final class SqlParserUtil {
return list.get(i) instanceof ToTreeListItem;
}
- @Override public @Nullable SqlNode node(int i) {
- return (@Nullable SqlNode) list.get(i);
+ @Override public SqlNode node(int i) {
+ return requireNonNull((SqlNode) list.get(i));
}
- @Override public void replaceSublist(int start, int end, @Nullable SqlNode
e) {
+ @Override public void replaceSublist(int start, int end, SqlNode e) {
SqlParserUtil.replaceSublist(list, start, end, e);
}
}
diff --git
a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidator.java
b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidator.java
index 17dd0c6..95e07be 100644
--- a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidator.java
+++ b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidator.java
@@ -36,6 +36,7 @@ import org.apache.calcite.sql.SqlMatchRecognize;
import org.apache.calcite.sql.SqlMerge;
import org.apache.calcite.sql.SqlNode;
import org.apache.calcite.sql.SqlNodeList;
+import org.apache.calcite.sql.SqlOperator;
import org.apache.calcite.sql.SqlOperatorTable;
import org.apache.calcite.sql.SqlSelect;
import org.apache.calcite.sql.SqlUpdate;
@@ -655,7 +656,7 @@ public interface SqlValidator {
List<RelDataType> argTypes);
/**
- * Handles a call to a function which cannot be resolved. Returns a an
+ * Handles a call to a function which cannot be resolved. Returns an
* appropriately descriptive error, which caller must throw.
*
* @param call Call
@@ -665,7 +666,7 @@ public interface SqlValidator {
* @param argNames Names of arguments, or null if call by position
*/
CalciteException handleUnresolvedFunction(SqlCall call,
- SqlFunction unresolvedFunction, List<RelDataType> argTypes,
+ SqlOperator unresolvedFunction, List<RelDataType> argTypes,
@Nullable List<String> argNames);
/**
diff --git
a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java
b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java
index eb9af34..7419bf1 100644
--- a/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java
+++ b/core/src/main/java/org/apache/calcite/sql/validate/SqlValidatorImpl.java
@@ -1919,7 +1919,7 @@ public class SqlValidatorImpl implements
SqlValidatorWithHints {
}
@Override public CalciteException handleUnresolvedFunction(SqlCall call,
- SqlFunction unresolvedFunction, List<RelDataType> argTypes,
+ SqlOperator unresolvedFunction, List<RelDataType> argTypes,
@Nullable List<String> argNames) {
// For builtins, we can give a better error message
final List<SqlOperator> overloads = new ArrayList<>();
@@ -1937,12 +1937,16 @@ public class SqlValidatorImpl implements
SqlValidatorWithHints {
}
}
- AssignableOperandTypeChecker typeChecking =
- new AssignableOperandTypeChecker(argTypes, argNames);
- String signature =
- typeChecking.getAllowedSignatures(
- unresolvedFunction,
- unresolvedFunction.getName());
+ final String signature;
+ if (unresolvedFunction instanceof SqlFunction) {
+ final SqlOperandTypeChecker typeChecking =
+ new AssignableOperandTypeChecker(argTypes, argNames);
+ signature =
+ typeChecking.getAllowedSignatures(unresolvedFunction,
+ unresolvedFunction.getName());
+ } else {
+ signature = unresolvedFunction.getName();
+ }
throw newValidationError(call,
RESOURCE.validatorUnknownFunction(signature));
}
diff --git
a/core/src/main/java/org/apache/calcite/sql2rel/StandardConvertletTable.java
b/core/src/main/java/org/apache/calcite/sql2rel/StandardConvertletTable.java
index 640b37f..6d062bd 100644
--- a/core/src/main/java/org/apache/calcite/sql2rel/StandardConvertletTable.java
+++ b/core/src/main/java/org/apache/calcite/sql2rel/StandardConvertletTable.java
@@ -172,6 +172,13 @@ public class StandardConvertletTable extends
ReflectiveConvertletTable {
SqlStdOperatorTable.LIKE.createCall(SqlParserPos.ZERO,
call.getOperandList()))));
+ // Expand "x NOT ILIKE y" into "NOT (x ILIKE y)"
+ registerOp(SqlLibraryOperators.NOT_ILIKE,
+ (cx, call) -> cx.convertExpression(
+ SqlStdOperatorTable.NOT.createCall(SqlParserPos.ZERO,
+ SqlLibraryOperators.ILIKE.createCall(SqlParserPos.ZERO,
+ call.getOperandList()))));
+
// Expand "x NOT SIMILAR y" into "NOT (x SIMILAR y)"
registerOp(SqlStdOperatorTable.NOT_SIMILAR_TO,
(cx, call) -> cx.convertExpression(
diff --git a/core/src/main/java/org/apache/calcite/tools/RelBuilder.java
b/core/src/main/java/org/apache/calcite/tools/RelBuilder.java
index 50b6bdf..de89160 100644
--- a/core/src/main/java/org/apache/calcite/tools/RelBuilder.java
+++ b/core/src/main/java/org/apache/calcite/tools/RelBuilder.java
@@ -640,13 +640,11 @@ public class RelBuilder {
private RexCall call(SqlOperator operator, List<RexNode> operandList) {
switch (operator.getKind()) {
case LIKE:
- if (((SqlLikeOperator) operator).isNegated()) {
- return (RexCall) not(call(SqlStdOperatorTable.LIKE, operandList));
- }
- break;
case SIMILAR:
- if (((SqlLikeOperator) operator).isNegated()) {
- return (RexCall) not(call(SqlStdOperatorTable.SIMILAR_TO,
operandList));
+ final SqlLikeOperator likeOperator = (SqlLikeOperator) operator;
+ if (likeOperator.isNegated()) {
+ final SqlOperator notLikeOperator = likeOperator.not();
+ return (RexCall) not(call(notLikeOperator, operandList));
}
break;
case BETWEEN:
diff --git a/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
b/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
index 38f42cd..84008a6 100644
--- a/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
+++ b/core/src/main/java/org/apache/calcite/util/BuiltInMethod.java
@@ -419,6 +419,7 @@ public enum BuiltInMethod {
LTRIM(SqlFunctions.class, "ltrim", String.class),
RTRIM(SqlFunctions.class, "rtrim", String.class),
LIKE(SqlFunctions.class, "like", String.class, String.class),
+ ILIKE(SqlFunctions.class, "ilike", String.class, String.class),
SIMILAR(SqlFunctions.class, "similar", String.class, String.class),
POSIX_REGEX(SqlFunctions.class, "posixRegex", String.class, String.class,
boolean.class),
REGEXP_REPLACE3(SqlFunctions.class, "regexpReplace", String.class,
diff --git
a/core/src/main/java/org/apache/calcite/util/PrecedenceClimbingParser.java
b/core/src/main/java/org/apache/calcite/util/PrecedenceClimbingParser.java
index 41f451a..6da09f6 100644
--- a/core/src/main/java/org/apache/calcite/util/PrecedenceClimbingParser.java
+++ b/core/src/main/java/org/apache/calcite/util/PrecedenceClimbingParser.java
@@ -55,7 +55,7 @@ public class PrecedenceClimbingParser {
last = p;
}
- public Token atom(@Nullable Object o) {
+ public Token atom(Object o) {
return new Token(Type.ATOM, o, -1, -1);
}
@@ -384,7 +384,7 @@ public class PrecedenceClimbingParser {
return this;
}
- public Builder atom(@Nullable Object o) {
+ public Builder atom(Object o) {
return add(dummy.atom(o));
}
diff --git
a/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterStructsTest.java
b/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterStructsTest.java
index b6a2109..bbbadb8 100644
---
a/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterStructsTest.java
+++
b/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterStructsTest.java
@@ -164,7 +164,7 @@ class RelToSqlConverterStructsTest {
private RelToSqlConverterTest.Sql sql(String sql) {
return new RelToSqlConverterTest.Sql(ROOT_SCHEMA, sql,
- CalciteSqlDialect.DEFAULT, SqlParser.Config.DEFAULT,
+ CalciteSqlDialect.DEFAULT, SqlParser.Config.DEFAULT, ImmutableSet.of(),
UnaryOperator.identity(), null, ImmutableList.of());
}
diff --git
a/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java
b/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java
index d94d7c4..2defcaf 100644
---
a/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java
+++
b/core/src/test/java/org/apache/calcite/rel/rel2sql/RelToSqlConverterTest.java
@@ -54,10 +54,13 @@ import org.apache.calcite.sql.dialect.MssqlSqlDialect;
import org.apache.calcite.sql.dialect.MysqlSqlDialect;
import org.apache.calcite.sql.dialect.OracleSqlDialect;
import org.apache.calcite.sql.dialect.PostgresqlSqlDialect;
+import org.apache.calcite.sql.fun.SqlLibrary;
+import org.apache.calcite.sql.fun.SqlLibraryOperatorTableFactory;
import org.apache.calcite.sql.fun.SqlStdOperatorTable;
import org.apache.calcite.sql.parser.SqlParser;
import org.apache.calcite.sql.type.SqlTypeFactoryImpl;
import org.apache.calcite.sql.type.SqlTypeName;
+import org.apache.calcite.sql.util.SqlOperatorTables;
import org.apache.calcite.sql.util.SqlShuttle;
import org.apache.calcite.sql.validate.SqlConformance;
import org.apache.calcite.sql2rel.SqlToRelConverter;
@@ -77,11 +80,14 @@ import org.apache.calcite.util.Util;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.ImmutableSet;
import org.junit.jupiter.api.Test;
+import java.util.Collection;
import java.util.List;
import java.util.Map;
+import java.util.Set;
import java.util.function.Function;
import java.util.function.UnaryOperator;
import java.util.stream.Collectors;
@@ -103,7 +109,7 @@ class RelToSqlConverterTest {
/** Initiates a test case with a given SQL query. */
private Sql sql(String sql) {
return new Sql(CalciteAssert.SchemaSpec.JDBC_FOODMART, sql,
- CalciteSqlDialect.DEFAULT, SqlParser.Config.DEFAULT,
+ CalciteSqlDialect.DEFAULT, SqlParser.Config.DEFAULT, ImmutableSet.of(),
UnaryOperator.identity(), null, ImmutableList.of());
}
@@ -114,9 +120,13 @@ class RelToSqlConverterTest {
private static Planner getPlanner(List<RelTraitDef> traitDefs,
SqlParser.Config parserConfig, SchemaPlus schema,
- SqlToRelConverter.Config sqlToRelConf, Program... programs) {
+ SqlToRelConverter.Config sqlToRelConf, Collection<SqlLibrary> librarySet,
+ Program... programs) {
final MockSqlOperatorTable operatorTable =
- new MockSqlOperatorTable(SqlStdOperatorTable.instance());
+ new MockSqlOperatorTable(
+ SqlOperatorTables.chain(SqlStdOperatorTable.instance(),
+ SqlLibraryOperatorTableFactory.INSTANCE
+ .getOperatorTable(librarySet)));
MockSqlOperatorTable.addRamp(operatorTable);
final FrameworkConfig config = Frameworks.newConfigBuilder()
.parserConfig(parserConfig)
@@ -3599,6 +3609,24 @@ class RelToSqlConverterTest {
sql(query).ok(expected);
}
+ @Test void testIlike() {
+ String query = "select \"product_name\" from \"product\" a "
+ + "where \"product_name\" ilike 'abC'";
+ String expected = "SELECT \"product_name\"\n"
+ + "FROM \"foodmart\".\"product\"\n"
+ + "WHERE \"product_name\" ILIKE 'abC'";
+ sql(query).withLibrary(SqlLibrary.POSTGRESQL).ok(expected);
+ }
+
+ @Test void testNotIlike() {
+ String query = "select \"product_name\" from \"product\" a "
+ + "where \"product_name\" not ilike 'abC'";
+ String expected = "SELECT \"product_name\"\n"
+ + "FROM \"foodmart\".\"product\"\n"
+ + "WHERE \"product_name\" NOT ILIKE 'abC'";
+ sql(query).withLibrary(SqlLibrary.POSTGRESQL).ok(expected);
+ }
+
@Test void testMatchRecognizePatternExpression() {
String sql = "select *\n"
+ " from \"product\" match_recognize\n"
@@ -5592,13 +5620,14 @@ class RelToSqlConverterTest {
private final SchemaPlus schema;
private final String sql;
private final SqlDialect dialect;
+ private final Set<SqlLibrary> librarySet;
private final Function<RelBuilder, RelNode> relFn;
private final List<Function<RelNode, RelNode>> transforms;
private final SqlParser.Config parserConfig;
private final UnaryOperator<SqlToRelConverter.Config> config;
Sql(CalciteAssert.SchemaSpec schemaSpec, String sql, SqlDialect dialect,
- SqlParser.Config parserConfig,
+ SqlParser.Config parserConfig, Set<SqlLibrary> librarySet,
UnaryOperator<SqlToRelConverter.Config> config,
Function<RelBuilder, RelNode> relFn,
List<Function<RelNode, RelNode>> transforms) {
@@ -5606,6 +5635,7 @@ class RelToSqlConverterTest {
this.schema = CalciteAssert.addSchema(rootSchema, schemaSpec);
this.sql = sql;
this.dialect = dialect;
+ this.librarySet = librarySet;
this.relFn = relFn;
this.transforms = ImmutableList.copyOf(transforms);
this.parserConfig = parserConfig;
@@ -5613,13 +5643,14 @@ class RelToSqlConverterTest {
}
Sql(SchemaPlus schema, String sql, SqlDialect dialect,
- SqlParser.Config parserConfig,
+ SqlParser.Config parserConfig, Set<SqlLibrary> librarySet,
UnaryOperator<SqlToRelConverter.Config> config,
Function<RelBuilder, RelNode> relFn,
List<Function<RelNode, RelNode>> transforms) {
this.schema = schema;
this.sql = sql;
this.dialect = dialect;
+ this.librarySet = librarySet;
this.relFn = relFn;
this.transforms = ImmutableList.copyOf(transforms);
this.parserConfig = parserConfig;
@@ -5627,13 +5658,13 @@ class RelToSqlConverterTest {
}
Sql dialect(SqlDialect dialect) {
- return new Sql(schema, sql, dialect, parserConfig, config, relFn,
- transforms);
+ return new Sql(schema, sql, dialect, parserConfig, librarySet, config,
+ relFn, transforms);
}
Sql relFn(Function<RelBuilder, RelNode> relFn) {
- return new Sql(schema, sql, dialect, parserConfig, config, relFn,
- transforms);
+ return new Sql(schema, sql, dialect, parserConfig, librarySet, config,
+ relFn, transforms);
}
Sql withCalcite() {
@@ -5755,18 +5786,27 @@ class RelToSqlConverterTest {
}
Sql parserConfig(SqlParser.Config parserConfig) {
- return new Sql(schema, sql, dialect, parserConfig, config, relFn,
- transforms);
+ return new Sql(schema, sql, dialect, parserConfig, librarySet, config,
+ relFn, transforms);
}
Sql withConfig(UnaryOperator<SqlToRelConverter.Config> config) {
- return new Sql(schema, sql, dialect, parserConfig, config, relFn,
- transforms);
+ return new Sql(schema, sql, dialect, parserConfig, librarySet, config,
+ relFn, transforms);
+ }
+
+ final Sql withLibrary(SqlLibrary library) {
+ return withLibrarySet(ImmutableSet.of(library));
+ }
+
+ Sql withLibrarySet(Iterable<? extends SqlLibrary> librarySet) {
+ return new Sql(schema, sql, dialect, parserConfig,
+ ImmutableSet.copyOf(librarySet), config, relFn, transforms);
}
Sql optimize(final RuleSet ruleSet, final RelOptPlanner relOptPlanner) {
- return new Sql(schema, sql, dialect, parserConfig, config, relFn,
- FlatLists.append(transforms, r -> {
+ final List<Function<RelNode, RelNode>> transforms =
+ FlatLists.append(this.transforms, r -> {
Program program = Programs.of(ruleSet);
final RelOptPlanner p =
Util.first(relOptPlanner,
@@ -5775,7 +5815,9 @@ class RelToSqlConverterTest {
.build()));
return program.run(p, r, r.getTraitSet(),
ImmutableList.of(), ImmutableList.of());
- }));
+ });
+ return new Sql(schema, sql, dialect, parserConfig, librarySet, config,
+ relFn, transforms);
}
Sql ok(String expectedQuery) {
@@ -5803,7 +5845,7 @@ class RelToSqlConverterTest {
final SqlToRelConverter.Config config =
this.config.apply(SqlToRelConverter.config()
.withTrimUnusedFields(false));
final Planner planner =
- getPlanner(null, parserConfig, schema, config);
+ getPlanner(null, parserConfig, schema, config, librarySet);
SqlNode parse = planner.parse(sql);
SqlNode validate = planner.validate(parse);
rel = planner.rel(validate).rel;
@@ -5818,8 +5860,8 @@ class RelToSqlConverterTest {
}
public Sql schema(CalciteAssert.SchemaSpec schemaSpec) {
- return new Sql(schemaSpec, sql, dialect, parserConfig, config, relFn,
- transforms);
+ return new Sql(schemaSpec, sql, dialect, parserConfig, librarySet,
config,
+ relFn, transforms);
}
}
}
diff --git
a/core/src/test/java/org/apache/calcite/sql/parser/SqlParserTest.java
b/core/src/test/java/org/apache/calcite/sql/parser/SqlParserTest.java
index c0a97c5..df07eb2 100644
--- a/core/src/test/java/org/apache/calcite/sql/parser/SqlParserTest.java
+++ b/core/src/test/java/org/apache/calcite/sql/parser/SqlParserTest.java
@@ -285,6 +285,7 @@ public class SqlParserTest {
"HOURS", "2011",
"IDENTITY", "92", "99", "2003", "2011", "2014", "c",
"IF", "92", "99", "2003",
+ "ILIKE",
"IMMEDIATE", "92", "99", "2003",
"IMMEDIATELY",
"IMPORT", "c",
@@ -1806,7 +1807,20 @@ public class SqlParserTest {
+ "WHERE (`A` LIKE `B` ESCAPE `C`)) ESCAPE `D`)))");
}
- @Test void testFoo() {
+ @Test void testIlike() {
+ // The ILIKE operator is only valid when the PostgreSQL function library is
+ // enabled ('fun=postgresql'). But the parser can always parse it.
+ final String expected = "SELECT *\n"
+ + "FROM `T`\n"
+ + "WHERE (`X` NOT ILIKE '%abc%')";
+ final String sql = "select * from t where x not ilike '%abc%'";
+ sql(sql).ok(expected);
+
+ final String sql1 = "select * from t where x ilike '%abc%'";
+ final String expected1 = "SELECT *\n"
+ + "FROM `T`\n"
+ + "WHERE (`X` ILIKE '%abc%')";
+ sql(sql1).ok(expected1);
}
@Test void testArithmeticOperators() {
diff --git a/core/src/test/java/org/apache/calcite/sql/test/SqlAdvisorTest.java
b/core/src/test/java/org/apache/calcite/sql/test/SqlAdvisorTest.java
index 04edf7e..ba70768 100644
--- a/core/src/test/java/org/apache/calcite/sql/test/SqlAdvisorTest.java
+++ b/core/src/test/java/org/apache/calcite/sql/test/SqlAdvisorTest.java
@@ -296,6 +296,7 @@ class SqlAdvisorTest extends SqlValidatorTestCase {
"KEYWORD(CONTAINS)",
"KEYWORD(EQUALS)",
"KEYWORD(FORMAT)",
+ "KEYWORD(ILIKE)",
"KEYWORD(IMMEDIATELY)",
"KEYWORD(IN)",
"KEYWORD(IS)",
diff --git
a/core/src/test/java/org/apache/calcite/sql/test/SqlOperatorBaseTest.java
b/core/src/test/java/org/apache/calcite/sql/test/SqlOperatorBaseTest.java
index aeb5cff..0b29fcc 100644
--- a/core/src/test/java/org/apache/calcite/sql/test/SqlOperatorBaseTest.java
+++ b/core/src/test/java/org/apache/calcite/sql/test/SqlOperatorBaseTest.java
@@ -3864,6 +3864,19 @@ public abstract class SqlOperatorBaseTest {
tester.checkBoolean("'abbc' like 'a\\%c' escape '\\'", Boolean.FALSE);
}
+ @Test void testIlikeEscape() {
+ tester.setFor(SqlLibraryOperators.ILIKE);
+ final SqlTester tester1 = libraryTester(SqlLibrary.POSTGRESQL);
+ tester1.checkBoolean("'a_c' ilike 'a#_C' escape '#'", Boolean.TRUE);
+ tester1.checkBoolean("'axc' ilike 'a#_C' escape '#'", Boolean.FALSE);
+ tester1.checkBoolean("'a_c' ilike 'a\\_C' escape '\\'", Boolean.TRUE);
+ tester1.checkBoolean("'axc' ilike 'a\\_C' escape '\\'", Boolean.FALSE);
+ tester1.checkBoolean("'a%c' ilike 'a\\%C' escape '\\'", Boolean.TRUE);
+ tester1.checkBoolean("'a%cde' ilike 'a\\%C_e' escape '\\'", Boolean.TRUE);
+ tester1.checkBoolean("'abbc' ilike 'a%C' escape '\\'", Boolean.TRUE);
+ tester1.checkBoolean("'abbc' ilike 'a\\%C' escape '\\'", Boolean.FALSE);
+ }
+
@Disabled("[CALCITE-525] Exception-handling in built-in functions")
@Test void testLikeEscape2() {
tester.checkBoolean("'x' not like 'x' escape 'x'", Boolean.TRUE);
@@ -3894,6 +3907,48 @@ public abstract class SqlOperatorBaseTest {
tester.checkBoolean("'ab\ncd\nef' like '%cde%'", Boolean.FALSE);
}
+ @Test void testIlikeOperator() {
+ tester.setFor(SqlLibraryOperators.ILIKE);
+ final String noLike = "No match found for function signature ILIKE";
+ tester.checkFails("^'a' ilike 'b'^", noLike, false);
+ tester.checkFails("^'a' ilike 'b' escape 'c'^", noLike, false);
+ final String noNotLike = "No match found for function signature NOT ILIKE";
+ tester.checkFails("^'a' not ilike 'b'^", noNotLike, false);
+ tester.checkFails("^'a' not ilike 'b' escape 'c'^", noNotLike, false);
+
+ final SqlTester tester1 = libraryTester(SqlLibrary.POSTGRESQL);
+ tester1.checkBoolean("'' ilike ''", Boolean.TRUE);
+ tester1.checkBoolean("'a' ilike 'a'", Boolean.TRUE);
+ tester1.checkBoolean("'a' ilike 'b'", Boolean.FALSE);
+ tester1.checkBoolean("'a' ilike 'A'", Boolean.TRUE);
+ tester1.checkBoolean("'a' ilike 'a_'", Boolean.FALSE);
+ tester1.checkBoolean("'a' ilike '_a'", Boolean.FALSE);
+ tester1.checkBoolean("'a' ilike '%a'", Boolean.TRUE);
+ tester1.checkBoolean("'a' ilike '%A'", Boolean.TRUE);
+ tester1.checkBoolean("'a' ilike '%a%'", Boolean.TRUE);
+ tester1.checkBoolean("'a' ilike '%A%'", Boolean.TRUE);
+ tester1.checkBoolean("'a' ilike 'a%'", Boolean.TRUE);
+ tester1.checkBoolean("'a' ilike 'A%'", Boolean.TRUE);
+ tester1.checkBoolean("'ab' ilike 'a_'", Boolean.TRUE);
+ tester1.checkBoolean("'ab' ilike 'A_'", Boolean.TRUE);
+ tester1.checkBoolean("'abc' ilike 'a_'", Boolean.FALSE);
+ tester1.checkBoolean("'abcd' ilike 'a%'", Boolean.TRUE);
+ tester1.checkBoolean("'abcd' ilike 'A%'", Boolean.TRUE);
+ tester1.checkBoolean("'ab' ilike '_b'", Boolean.TRUE);
+ tester1.checkBoolean("'ab' ilike '_B'", Boolean.TRUE);
+ tester1.checkBoolean("'abcd' ilike '_d'", Boolean.FALSE);
+ tester1.checkBoolean("'abcd' ilike '%d'", Boolean.TRUE);
+ tester1.checkBoolean("'abcd' ilike '%D'", Boolean.TRUE);
+ tester1.checkBoolean("'ab\ncd' ilike 'ab%'", Boolean.TRUE);
+ tester1.checkBoolean("'ab\ncd' ilike 'aB%'", Boolean.TRUE);
+ tester1.checkBoolean("'abc\ncd' ilike 'ab%'", Boolean.TRUE);
+ tester1.checkBoolean("'abc\ncd' ilike 'Ab%'", Boolean.TRUE);
+ tester1.checkBoolean("'123\n\n45\n' ilike '%'", Boolean.TRUE);
+ tester1.checkBoolean("'ab\ncd\nef' ilike '%cd%'", Boolean.TRUE);
+ tester1.checkBoolean("'ab\ncd\nef' ilike '%CD%'", Boolean.TRUE);
+ tester1.checkBoolean("'ab\ncd\nef' ilike '%cde%'", Boolean.FALSE);
+ }
+
/** Test case for
* <a
href="https://issues.apache.org/jira/browse/CALCITE-1898">[CALCITE-1898]
* LIKE must match '.' (period) literally</a>. */
@@ -3903,6 +3958,15 @@ public abstract class SqlOperatorBaseTest {
tester.checkBoolean("'abc.e' like '%c.e'", Boolean.TRUE);
}
+ @Test void testIlikeDot() {
+ tester.setFor(SqlLibraryOperators.ILIKE);
+ final SqlTester tester1 = libraryTester(SqlLibrary.POSTGRESQL);
+ tester1.checkBoolean("'abc' ilike 'a.c'", Boolean.FALSE);
+ tester1.checkBoolean("'abcde' ilike '%c.e'", Boolean.FALSE);
+ tester1.checkBoolean("'abc.e' ilike '%c.e'", Boolean.TRUE);
+ tester1.checkBoolean("'abc.e' ilike '%c.E'", Boolean.TRUE);
+ }
+
@Test void testNotSimilarToOperator() {
tester.setFor(SqlStdOperatorTable.NOT_SIMILAR_TO, VM_EXPAND);
tester.checkBoolean("'ab' not similar to 'a_'", false);
diff --git a/core/src/test/java/org/apache/calcite/test/RelBuilderTest.java
b/core/src/test/java/org/apache/calcite/test/RelBuilderTest.java
index 6bb8e96..5a4423e 100644
--- a/core/src/test/java/org/apache/calcite/test/RelBuilderTest.java
+++ b/core/src/test/java/org/apache/calcite/test/RelBuilderTest.java
@@ -53,6 +53,7 @@ import org.apache.calcite.schema.impl.ViewTable;
import org.apache.calcite.schema.impl.ViewTableMacro;
import org.apache.calcite.sql.SqlMatchRecognize;
import org.apache.calcite.sql.SqlOperator;
+import org.apache.calcite.sql.fun.SqlLibraryOperators;
import org.apache.calcite.sql.fun.SqlStdOperatorTable;
import org.apache.calcite.sql.parser.SqlParser;
import org.apache.calcite.sql.type.SqlTypeName;
@@ -3963,8 +3964,7 @@ public class RelBuilderTest {
RelNode root =
builder.scan("EMP")
.filter(
- builder.call(
- SqlStdOperatorTable.NOT_LIKE,
+ builder.call(SqlStdOperatorTable.NOT_LIKE,
builder.field("ENAME"),
builder.literal("a%b%c")))
.build();
@@ -3974,6 +3974,21 @@ public class RelBuilderTest {
assertThat(root, hasTree(expected));
}
+ @Test void testNotIlike() {
+ final RelBuilder builder = RelBuilder.create(config().build());
+ RelNode root =
+ builder.scan("EMP")
+ .filter(
+ builder.call(SqlLibraryOperators.NOT_ILIKE,
+ builder.field("ENAME"),
+ builder.literal("a%b%c")))
+ .build();
+ final String expected = ""
+ + "LogicalFilter(condition=[NOT(ILIKE($1, 'a%b%c'))])\n"
+ + " LogicalTableScan(table=[[scott, EMP]])\n";
+ assertThat(root, hasTree(expected));
+ }
+
/** Test case for
* <a
href="https://issues.apache.org/jira/browse/CALCITE-4415">[CALCITE-4415]
* SqlStdOperatorTable.NOT_LIKE has a wrong implementor</a>. */
diff --git a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
index a79d8a3..e2ca616 100644
--- a/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
+++ b/core/src/test/java/org/apache/calcite/test/SqlValidatorTest.java
@@ -1005,6 +1005,19 @@ public class SqlValidatorTest extends
SqlValidatorTestCase {
expr("'a' similar to 'b' escape 'c'").ok();
}
+ @Test void testIlike() {
+ final Sql s = sql("?")
+ .withOperatorTable(operatorTableFor(SqlLibrary.POSTGRESQL));
+ s.expr("'a' ilike 'b'").columnType("BOOLEAN NOT NULL");
+ s.expr("'a' ilike cast(null as varchar(99))").columnType("BOOLEAN");
+ s.expr("cast(null as varchar(99)) not ilike 'b'").columnType("BOOLEAN");
+ s.expr("'a' not ilike 'b' || 'c'").columnType("BOOLEAN NOT NULL");
+
+ // ILIKE is only available in the PostgreSQL function library
+ expr("^'a' ilike 'b'^")
+ .fails("No match found for function signature ILIKE");
+ }
+
public void _testLikeAndSimilarFails() {
expr("'a' like _UTF16'b' escape 'c'")
.fails("(?s).*Operands _ISO-8859-1.a. COLLATE
ISO-8859-1.en_US.primary,"
diff --git a/site/_docs/reference.md b/site/_docs/reference.md
index bba8db9..96d9a50 100644
--- a/site/_docs/reference.md
+++ b/site/_docs/reference.md
@@ -632,6 +632,7 @@ HOP,
HOURS,
**IDENTITY**,
IGNORE,
+ILIKE,
IMMEDIATE,
IMMEDIATELY,
IMPLEMENTATION,
@@ -2505,6 +2506,8 @@ semantics.
| m | EXTRACTVALUE(xml, xpathExpr)) | Returns the text of the
first text node which is a child of the element or elements matched by the
XPath expression.
| o | GREATEST(expr [, expr ]*) | Returns the greatest of
the expressions
| b h s | IF(condition, value1, value2) | Returns *value1* if
*condition* is TRUE, *value2* otherwise
+| p | string1 ILIKE string2 [ ESCAPE string3 ] | Whether *string1*
matches pattern *string2*, ignoring case (similar to `LIKE`)
+| p | string1 NOT ILIKE string2 [ ESCAPE string3 ] | Whether *string1* does
not match pattern *string2*, ignoring case (similar to `NOT LIKE`)
| m | JSON_TYPE(jsonValue) | Returns a string value
indicating the type of *jsonValue*
| m | JSON_DEPTH(jsonValue) | Returns an integer
value indicating the depth of *jsonValue*
| m | JSON_PRETTY(jsonValue) | Returns a
pretty-printing of *jsonValue*