From 4f42fb7c95b76d30ac9369959233036cafb6253d Mon Sep 17 00:00:00 2001 From: Sergey Nuyanzin Date: Sun, 3 Aug 2025 00:26:39 +0200 Subject: [PATCH 1/2] [CALCITE-7121] Allow to use hyphens in unquoted Table Names in dialects with backticks quoting --- .../sql/parser/SqlAbstractParserImpl.java | 6 ++++-- .../calcite/sql/parser/SqlParserTest.java | 19 +++++++++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/core/src/main/java/org/apache/calcite/sql/parser/SqlAbstractParserImpl.java b/core/src/main/java/org/apache/calcite/sql/parser/SqlAbstractParserImpl.java index 389a6fa56152..8315a4dac1fb 100644 --- a/core/src/main/java/org/apache/calcite/sql/parser/SqlAbstractParserImpl.java +++ b/core/src/main/java/org/apache/calcite/sql/parser/SqlAbstractParserImpl.java @@ -619,9 +619,11 @@ public static LexicalState forConfig(SqlParser.Config config) { return BQID; case BACK_TICK: if (config.conformance().allowHyphenInUnquotedTableName() - && config.charLiteralStyles().equals( + && (config.charLiteralStyles().equals( EnumSet.of(CharLiteralStyle.BQ_SINGLE, - CharLiteralStyle.BQ_DOUBLE))) { + CharLiteralStyle.BQ_DOUBLE)) + || config.charLiteralStyles().equals( + EnumSet.of(CharLiteralStyle.STANDARD)))) { return BQID; } if (!config.conformance().allowHyphenInUnquotedTableName() diff --git a/testkit/src/main/java/org/apache/calcite/sql/parser/SqlParserTest.java b/testkit/src/main/java/org/apache/calcite/sql/parser/SqlParserTest.java index f7704aa842bb..cf3d557ed2eb 100644 --- a/testkit/src/main/java/org/apache/calcite/sql/parser/SqlParserTest.java +++ b/testkit/src/main/java/org/apache/calcite/sql/parser/SqlParserTest.java @@ -987,6 +987,25 @@ private void checkLarge(int n) { .fails("(?s)Encountered \"-\" at .*") .withDialect(BIG_QUERY) .fails("(?s)Encountered \"-\" at .*"); + + final SqlConformance nonBigQueryConformance = new SqlAbstractConformance() { + @Override public boolean allowHyphenInUnquotedTableName() { + return true; + } + }; + + sql("select * from foo-bar.baz cross join (select alpha-omega from t) as t") + .withDialect(MYSQL) + .withConformance(nonBigQueryConformance) + .ok("SELECT *\n" + + "FROM `foo-bar`.`baz`\n" + + "CROSS JOIN (SELECT (`alpha` - `omega`)\n" + + "FROM `t`) AS `t`"); + + sql("select * from foo ^-^ bar.baz cross join (select alpha-omega from t) as t") + .withDialect(MYSQL) + .withConformance(nonBigQueryConformance) + .fails("(?s)Encountered \"-\" at .*"); } @Test void testHyphenatedColumnName() { From d105292ab30fea6fa3b66b63dc9395fe247aea5d Mon Sep 17 00:00:00 2001 From: Sergey Nuyanzin Date: Sun, 3 Aug 2025 08:46:51 +0200 Subject: [PATCH 2/2] Add tests similar to the ones for BigQuery --- .../calcite/sql/parser/SqlParserFixture.java | 4 + .../calcite/sql/parser/SqlParserTest.java | 87 +++++++++++++------ 2 files changed, 66 insertions(+), 25 deletions(-) diff --git a/testkit/src/main/java/org/apache/calcite/sql/parser/SqlParserFixture.java b/testkit/src/main/java/org/apache/calcite/sql/parser/SqlParserFixture.java index 34fc7ac1140e..8a016ea29494 100644 --- a/testkit/src/main/java/org/apache/calcite/sql/parser/SqlParserFixture.java +++ b/testkit/src/main/java/org/apache/calcite/sql/parser/SqlParserFixture.java @@ -189,6 +189,10 @@ public SqlParserFixture withTester(SqlParserTest.Tester tester) { convertToLinux, parserChecker); } + public SqlParserFixture withQuoting(Quoting quoting) { + return withConfig(c -> c.withQuoting(quoting)); + } + /** * Sets whether to convert actual strings to Linux (converting Windows * CR-LF line endings to Linux LF) before comparing them to expected. diff --git a/testkit/src/main/java/org/apache/calcite/sql/parser/SqlParserTest.java b/testkit/src/main/java/org/apache/calcite/sql/parser/SqlParserTest.java index cf3d557ed2eb..da6adfdce411 100644 --- a/testkit/src/main/java/org/apache/calcite/sql/parser/SqlParserTest.java +++ b/testkit/src/main/java/org/apache/calcite/sql/parser/SqlParserTest.java @@ -892,6 +892,12 @@ private void checkLarge(int n) { } @Test void testHyphenatedTableName() { + final SqlConformance nonBigQueryConformance = new SqlAbstractConformance() { + @Override public boolean allowHyphenInUnquotedTableName() { + return true; + } + }; + sql("select * from bigquery^-^foo-bar.baz") .fails("(?s)Encountered \"-\" at .*") .withDialect(BIG_QUERY) @@ -913,7 +919,12 @@ private void checkLarge(int n) { .ok("SELECT baz.buzz\n" + "FROM `foo-bar`.baz") .withDialect(MYSQL) - .fails("(?s)Encountered \"-\" at .*"); + .fails("(?s)Encountered \"-\" at .*") + // It should allow if enabled via SqlConformance + .withConformance(nonBigQueryConformance) + .withQuoting(Quoting.BACK_TICK) + .ok("SELECT `baz`.`buzz`\n" + + "FROM `foo-bar`.`baz`"); // No hyphenated identifiers as table aliases. sql("select * from foo.baz as hyphenated^-^alias-not-allowed") @@ -923,7 +934,12 @@ private void checkLarge(int n) { sql("select * from foo.baz as `hyphenated-alias-allowed-if-quoted`") .withDialect(BIG_QUERY) .ok("SELECT *\n" - + "FROM foo.baz AS `hyphenated-alias-allowed-if-quoted`"); + + "FROM foo.baz AS `hyphenated-alias-allowed-if-quoted`") + .withDialect(MYSQL) + .withQuoting(Quoting.BACK_TICK) + .withConformance(nonBigQueryConformance) + .ok("SELECT *\n" + + "FROM `foo`.`baz` AS `hyphenated-alias-allowed-if-quoted`"); // No hyphenated identifiers as column names. sql("select * from foo-bar.baz cross join (select alpha-omega from t) as t") @@ -931,28 +947,53 @@ private void checkLarge(int n) { .ok("SELECT *\n" + "FROM `foo-bar`.baz\n" + "CROSS JOIN (SELECT (alpha - omega)\n" - + "FROM t) AS t"); + + "FROM t) AS t") + .withDialect(MYSQL) + .withQuoting(Quoting.BACK_TICK) + .withConformance(nonBigQueryConformance) + .ok("SELECT *\n" + + "FROM `foo-bar`.`baz`\n" + + "CROSS JOIN (SELECT (`alpha` - `omega`)\n" + + "FROM `t`) AS `t`"); sql("select * from bigquery-foo-bar.baz as hyphenated^-^alias-not-allowed") .withDialect(BIG_QUERY) + .fails("(?s)Encountered \"-\" at .*") + .withDialect(MYSQL) + .withQuoting(Quoting.BACK_TICK) + .withConformance(nonBigQueryConformance) .fails("(?s)Encountered \"-\" at .*"); sql("insert into bigquery^-^public-data.foo values (1)") .fails("Non-query expression encountered in illegal context") .withDialect(BIG_QUERY) .ok("INSERT INTO `bigquery-public-data`.foo\n" + + "VALUES (1)") + .withDialect(MYSQL) + .withQuoting(Quoting.BACK_TICK) + .withConformance(nonBigQueryConformance) + .ok("INSERT INTO `bigquery-public-data`.`foo`\n" + "VALUES (1)"); sql("update bigquery^-^public-data.foo set a = b") .fails("(?s)Encountered \"-\" at .*") .withDialect(BIG_QUERY) - .ok("UPDATE `bigquery-public-data`.foo SET a = b"); + .ok("UPDATE `bigquery-public-data`.foo SET a = b") + .withDialect(MYSQL) + .withQuoting(Quoting.BACK_TICK) + .withConformance(nonBigQueryConformance) + .ok("UPDATE `bigquery-public-data`.`foo` SET `a` = `b`"); sql("delete from bigquery^-^public-data.foo where a = 5") .fails("(?s)Encountered \"-\" at .*") .withDialect(BIG_QUERY) .ok("DELETE FROM `bigquery-public-data`.foo\n" - + "WHERE (a = 5)"); + + "WHERE (a = 5)") + .withDialect(MYSQL) + .withQuoting(Quoting.BACK_TICK) + .withConformance(nonBigQueryConformance) + .ok("DELETE FROM `bigquery-public-data`.`foo`\n" + + "WHERE (`a` = 5)"); final String mergeSql = "merge into bigquery^-^public-data.emps e\n" + "using (\n" @@ -977,35 +1018,31 @@ private void checkLarge(int n) { + "WHEN NOT MATCHED THEN" + " INSERT (name, dept, salary)" + " VALUES (t.name, 10, (t.salary * 0.15))"; + final String nonBigQueryMergeExpected = "MERGE INTO `bigquery-public-data`.`emps` AS `e`\n" + + "USING (SELECT *\n" + + "FROM `bigquery-public-data`.`tempemps`\n" + + "WHERE (`deptno` IS NULL)) AS `t`\n" + + "ON (`e`.`empno` = `t`.`empno`)\n" + + "WHEN MATCHED THEN" + + " UPDATE SET `name` = `t`.`name`, `deptno` = `t`.`deptno`," + + " `salary` = (`t`.`salary` * 0.1)\n" + + "WHEN NOT MATCHED THEN" + + " INSERT (`name`, `dept`, `salary`)" + + " VALUES (`t`.`name`, 10, (`t`.`salary` * 0.15))"; sql(mergeSql) .fails("(?s)Encountered \"-\" at .*") .withDialect(BIG_QUERY) - .ok(mergeExpected); + .ok(mergeExpected) + .withDialect(MYSQL) + .withQuoting(Quoting.BACK_TICK) + .withConformance(nonBigQueryConformance) + .ok(nonBigQueryMergeExpected); // Hyphenated identifiers may not contain spaces, even in BigQuery. sql("select * from bigquery ^-^ foo - bar as t where x < y") .fails("(?s)Encountered \"-\" at .*") .withDialect(BIG_QUERY) .fails("(?s)Encountered \"-\" at .*"); - - final SqlConformance nonBigQueryConformance = new SqlAbstractConformance() { - @Override public boolean allowHyphenInUnquotedTableName() { - return true; - } - }; - - sql("select * from foo-bar.baz cross join (select alpha-omega from t) as t") - .withDialect(MYSQL) - .withConformance(nonBigQueryConformance) - .ok("SELECT *\n" - + "FROM `foo-bar`.`baz`\n" - + "CROSS JOIN (SELECT (`alpha` - `omega`)\n" - + "FROM `t`) AS `t`"); - - sql("select * from foo ^-^ bar.baz cross join (select alpha-omega from t) as t") - .withDialect(MYSQL) - .withConformance(nonBigQueryConformance) - .fails("(?s)Encountered \"-\" at .*"); } @Test void testHyphenatedColumnName() {