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 381531a82bc..5375ee6748a 100644 --- a/core/src/main/java/org/apache/calcite/sql/SqlKind.java +++ b/core/src/main/java/org/apache/calcite/sql/SqlKind.java @@ -806,6 +806,9 @@ public enum SqlKind { /** {@code SUBSTR} function (PostgreSQL semantics). */ SUBSTR_POSTGRESQL, + /** {@code CHAR_LENGTH} function. */ + CHAR_LENGTH, + /** {@code ENDS_WITH} function. */ ENDS_WITH, diff --git a/core/src/main/java/org/apache/calcite/sql/dialect/SnowflakeSqlDialect.java b/core/src/main/java/org/apache/calcite/sql/dialect/SnowflakeSqlDialect.java index 32c2874298c..f7d8741491f 100644 --- a/core/src/main/java/org/apache/calcite/sql/dialect/SnowflakeSqlDialect.java +++ b/core/src/main/java/org/apache/calcite/sql/dialect/SnowflakeSqlDialect.java @@ -43,6 +43,11 @@ public SnowflakeSqlDialect(Context context) { @Override public void unparseCall(final SqlWriter writer, final SqlCall call, final int leftPrec, final int rightPrec) { switch (call.getKind()) { + case CHAR_LENGTH: + SqlCall lengthCall = SqlLibraryOperators.LENGTH + .createCall(SqlParserPos.ZERO, call.getOperandList()); + super.unparseCall(writer, lengthCall, leftPrec, rightPrec); + break; case ENDS_WITH: SqlCall endsWithCall = SqlLibraryOperators.ENDSWITH .createCall(SqlParserPos.ZERO, call.getOperandList()); 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 878e7e0ab3b..d4c8b9d1ea9 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 @@ -274,8 +274,13 @@ private static SqlCall transformConvert(SqlValidator validator, SqlCall call) { @LibraryOperator(libraries = {BIG_QUERY}) public static final SqlFunction IFNULL = NVL.withName("IFNULL"); + /** The "LEN(string)" function. */ + @LibraryOperator(libraries = {SNOWFLAKE}) + public static final SqlFunction LEN = + SqlStdOperatorTable.CHAR_LENGTH.withName("LEN"); + /** The "LENGTH(string)" function. */ - @LibraryOperator(libraries = {BIG_QUERY}) + @LibraryOperator(libraries = {BIG_QUERY, SNOWFLAKE}) public static final SqlFunction LENGTH = SqlStdOperatorTable.CHAR_LENGTH.withName("LENGTH"); 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 40ec61d6244..4b07e5ad01c 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 @@ -1613,10 +1613,9 @@ public class SqlStdOperatorTable extends ReflectiveSqlOperatorTable { public static final SqlFunction POSITION = new SqlPositionFunction("POSITION"); public static final SqlBasicFunction CHAR_LENGTH = - SqlBasicFunction.create("CHAR_LENGTH", + SqlBasicFunction.create(SqlKind.CHAR_LENGTH, ReturnTypes.INTEGER_NULLABLE, - OperandTypes.CHARACTER, - SqlFunctionCategory.NUMERIC); + OperandTypes.CHARACTER); /** Alias for {@link #CHAR_LENGTH}. */ public static final SqlFunction CHARACTER_LENGTH = 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 69666ffd34d..d9bc1d27d5c 100644 --- a/core/src/main/java/org/apache/calcite/sql2rel/StandardConvertletTable.java +++ b/core/src/main/java/org/apache/calcite/sql2rel/StandardConvertletTable.java @@ -120,6 +120,8 @@ private StandardConvertletTable() { // Register aliases (operators which have a different name but // identical behavior to other operators). + addAlias(SqlLibraryOperators.LEN, + SqlStdOperatorTable.CHAR_LENGTH); addAlias(SqlLibraryOperators.LENGTH, SqlStdOperatorTable.CHAR_LENGTH); addAlias(SqlStdOperatorTable.CHARACTER_LENGTH, 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 3a4c480aa38..7a5bc0c4993 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 @@ -6696,6 +6696,23 @@ private void checkLiteral2(String expression, String expected) { sql(query).withLibrary(SqlLibrary.SNOWFLAKE).withSnowflake().ok(expectedSnowflake); } + /** Test case for + * [CALCITE-6182] + * Add LENGTH/LEN functions (enabled in Snowflake library). */ + @Test void testSnowflakeLength() { + final String query = "select CHAR_LENGTH(\"brand_name\")\n" + + "from \"product\""; + final String expectedBigQuery = "SELECT CHAR_LENGTH(brand_name)\n" + + "FROM foodmart.product"; + // Snowflake would accept either LEN or LENGTH, but we currently unparse into "LENGTH" + // since it seems to be used across more dialects. + final String expectedSnowflake = "SELECT LENGTH(\"brand_name\")\n" + + "FROM \"foodmart\".\"product\""; + Sql sql = sql(query).withLibrary(SqlLibrary.BIG_QUERY); + sql.withBigQuery().ok(expectedBigQuery); + sql.withSnowflake().ok(expectedSnowflake); + } + @Test void testSubstringInSpark() { final String query = "select substring(\"brand_name\" from 2) " + "from \"product\"\n"; diff --git a/site/_docs/reference.md b/site/_docs/reference.md index 841c42650cb..3f974485439 100644 --- a/site/_docs/reference.md +++ b/site/_docs/reference.md @@ -2769,7 +2769,8 @@ In the following: | m | JSON_STORAGE_SIZE(jsonValue) | Returns the number of bytes used to store the binary representation of *jsonValue* | b o | LEAST(expr [, expr ]* ) | Returns the least of the expressions | b m p | LEFT(string, length) | Returns the leftmost *length* characters from the *string* -| b | LENGTH(string) | Equivalent to `CHAR_LENGTH(string)` +| f | LEN(string) | Equivalent to `CHAR_LENGTH(string)` +| b f | LENGTH(string) | Equivalent to `CHAR_LENGTH(string)` | h s | LEVENSHTEIN(string1, string2) | Returns the Levenshtein distance between *string1* and *string2* | b | LOG(numeric1 [, numeric2 ]) | Returns the logarithm of *numeric1* to base *numeric2*, or base e if *numeric2* is not present | b o | LPAD(string, length [, pattern ]) | Returns a string or bytes value that consists of *string* prepended to *length* with *pattern* diff --git a/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java b/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java index 583fd06882b..0e270b7280a 100644 --- a/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java +++ b/testkit/src/main/java/org/apache/calcite/test/SqlOperatorTest.java @@ -4111,6 +4111,23 @@ static void checkRlikeFails(SqlOperatorFixture f) { f.checkNull("CHARACTER_LENGTH(cast(null as varchar(1)))"); } + /** Tests {@code LEN} function from Snowflake. {@code LEN} is a + * Snowflake-specific alias for {@code CHAR_LENGTH}. */ + @Test void testLenFunc() { + final SqlOperatorFixture f0 = fixture().setFor(SqlLibraryOperators.LEN); + f0.checkFails("^len('hello')^", + "No match found for function signature LEN\\(\\)", + false); + final SqlOperatorFixture f = f0.withLibrary(SqlLibrary.SNOWFLAKE); + f.checkScalar("len('hello')", "5", "INTEGER NOT NULL"); + f.checkScalar("len('')", "0", "INTEGER NOT NULL"); + f.checkScalar("len(CAST('x' as CHAR(3)))", "3", "INTEGER NOT NULL"); + f.checkScalar("len(CAST('x' as VARCHAR(4)))", "1", "INTEGER NOT NULL"); + f.checkNull("len(CAST(NULL as CHAR(5)))"); + } + + /** Tests {@code LENGTH} function from Big Query/Snowflake. {@code LENGTH} is a + * BQ/Snowflake-specific alias for {@code CHAR_LENGTH}. */ @Test void testLengthFunc() { final SqlOperatorFixture f0 = fixture().setFor(SqlLibraryOperators.LENGTH); f0.checkFails("^length('hello')^", @@ -4125,7 +4142,7 @@ static void checkRlikeFails(SqlOperatorFixture f) { f.checkNull("length(CAST(NULL as CHAR(5)))"); }; - f0.forEachLibrary(list(SqlLibrary.BIG_QUERY), consumer); + f0.forEachLibrary(list(SqlLibrary.BIG_QUERY, SqlLibrary.SNOWFLAKE), consumer); } @Test void testOctetLengthFunc() {