Skip to content
10 changes: 9 additions & 1 deletion src/main/java/net/sf/jsqlparser/parser/AbstractJSqlParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,15 @@ public enum Dialect {
Feature.allowHashLineComments,
Feature.allowDoubleQuotedStrings), SQLSERVER(AdjacentStringLiterals.OFF,
Feature.allowSquareBracketQuotation), POSTGRESQL(
AdjacentStringLiterals.NEWLINE), H2, EXASOL;
AdjacentStringLiterals.NEWLINE), H2, EXASOL, BIGQUERY(
AdjacentStringLiterals.WHITESPACE,
Feature.allowDoubleQuotedStrings,
Feature.allowHashLineComments,
Feature.allowBackslashEscapeCharacter), DATABRICKS(
AdjacentStringLiterals.WHITESPACE,
Feature.allowDoubleQuotedStrings,
Feature.allowBackslashEscapeCharacter), SNOWFLAKE(
Feature.allowBackslashEscapeCharacter);

private final Set<Feature> lexerFeatures;
private final AdjacentStringLiterals adjacentStringLiterals;
Expand Down
2 changes: 1 addition & 1 deletion src/site/sphinx/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@ Additionally there are Features to control the Parser's effort at the cost of th
.withBackslashEscapeCharacter(true)
);

Instead of turning the individual Parser Features on one by one, a ``Dialect`` preset selects the features of that database dialect: ``withDialect(Dialect.MYSQL)`` turns on ``withBackslashEscapeCharacter``, ``withHashLineComments`` and ``withDoubleQuotedStrings`` (MySQL and MariaDB syntax, the latter for the default sql_mode), ``withDialect(Dialect.SQLSERVER)`` turns on ``withSquareBracketQuotation``. ``withDialect(Dialect.POSTGRESQL)`` and ``withDialect(Dialect.ANSI_SQL)`` turn on the newline rule for adjacent String Literals. Features set explicitly after the dialect preset win over the preset.
Instead of turning the individual Parser Features on one by one, a ``Dialect`` preset selects the features of that database dialect: ``withDialect(Dialect.MYSQL)`` turns on ``withBackslashEscapeCharacter``, ``withHashLineComments`` and ``withDoubleQuotedStrings`` (MySQL and MariaDB syntax, the latter for the default sql_mode), ``withDialect(Dialect.SQLSERVER)`` turns on ``withSquareBracketQuotation``. ``withDialect(Dialect.POSTGRESQL)`` and ``withDialect(Dialect.ANSI_SQL)`` turn on the newline rule for adjacent String Literals. ``withDialect(Dialect.BIGQUERY)`` and ``withDialect(Dialect.DATABRICKS)`` turn on ``withDoubleQuotedStrings`` and ``withBackslashEscapeCharacter`` plus the any-whitespace rule for adjacent String Literals, the BigQuery preset additionally ``withHashLineComments``; ``withDialect(Dialect.SNOWFLAKE)`` turns on ``withBackslashEscapeCharacter`` only, keeping double quotes as quoted identifiers. Features set explicitly after the dialect preset win over the preset.

.. code-block:: java

Expand Down
129 changes: 124 additions & 5 deletions src/test/java/net/sf/jsqlparser/parser/CCJSqlParserUtilTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -641,23 +641,142 @@
.parse("SELECT * FROM \"t\"", p -> p.withDoubleQuotedStrings(true)).toString());
}

@Test
public void testDialectPresetsWarehouses() throws Exception {
// BIGQUERY: double quoted strings, # line comments and backslash
// escapes (GoogleSQL "Lexical structure and syntax", identifiers are
// backticked), backticks stay unconditional
assertEquals("SELECT `col` FROM t WHERE a = 'x\\'yz' AND b = 42", CCJSqlParserUtil
.parse("SELECT `col` FROM t WHERE a = 'x\\'yz' AND b = 42#24",
p -> p.withDialect(AbstractJSqlParser.Dialect.BIGQUERY))
.toString());
// DATABRICKS: double quoted strings and backslash escapes (Spark
// "Literals"), `#` stays the #2507 operator
assertEquals("SELECT \"a\" FROM t WHERE x = 'it\\'s'", CCJSqlParserUtil
.parse("SELECT \"a\" FROM t WHERE x = 'it\\'s'",
p -> p.withDialect(AbstractJSqlParser.Dialect.DATABRICKS))
.toString());
assertEquals("SELECT 42 # 24", CCJSqlParserUtil.parse("SELECT 42 # 24",
p -> p.withDialect(AbstractJSqlParser.Dialect.DATABRICKS)).toString());
// SNOWFLAKE: backslash escape sequences ("String & binary data
// types"), `#` stays the operator
assertEquals("SELECT col FROM t WHERE a = 'x\\'yz'", CCJSqlParserUtil
.parse("SELECT col FROM t WHERE a = 'x\\'yz'",
p -> p.withDialect(AbstractJSqlParser.Dialect.SNOWFLAKE))
.toString());
assertEquals("SELECT 42 # 24", CCJSqlParserUtil.parse("SELECT 42 # 24",
p -> p.withDialect(AbstractJSqlParser.Dialect.SNOWFLAKE)).toString());
}

@Test
public void testDoubleQuotedStringsPreset() throws Exception {
// MYSQL and MARIADB presets carry the switch (default sql_mode reading)
// the string-default dialects: MYSQL and MARIADB (default sql_mode),
// BIGQUERY and DATABRICKS (string literals, identifiers are backticked)
for (AbstractJSqlParser.Dialect dialect : new AbstractJSqlParser.Dialect[] {
AbstractJSqlParser.Dialect.MYSQL, AbstractJSqlParser.Dialect.MARIADB}) {
AbstractJSqlParser.Dialect.MYSQL, AbstractJSqlParser.Dialect.MARIADB,
AbstractJSqlParser.Dialect.BIGQUERY, AbstractJSqlParser.Dialect.DATABRICKS}) {
PlainSelect select = (PlainSelect) ((Select) CCJSqlParserUtil
.parse("SELECT \"abc\"", p -> p.withDialect(dialect))).getSelectBody();
assertTrue(select.getSelectItems().get(0).getExpression() instanceof StringValue);
}
// the identifier-default dialects keep the quoted identifier reading
PlainSelect select = (PlainSelect) ((Select) CCJSqlParserUtil.parse("SELECT \"abc\"",
p -> p.withDialect(AbstractJSqlParser.Dialect.SQLSERVER))).getSelectBody();
assertTrue(select.getSelectItems().get(0).getExpression() instanceof Column);
for (AbstractJSqlParser.Dialect dialect : new AbstractJSqlParser.Dialect[] {
AbstractJSqlParser.Dialect.SQLSERVER, AbstractJSqlParser.Dialect.SNOWFLAKE}) {
PlainSelect select = (PlainSelect) ((Select) CCJSqlParserUtil.parse("SELECT \"abc\"",
p -> p.withDialect(dialect))).getSelectBody();
assertTrue(select.getSelectItems().get(0).getExpression() instanceof Column);
}
}

@Test
public void testAdjacentStringLiteralsNewline() throws Exception {
// default (off): the MySQL / SQL Server alias reading, unchanged
assertEquals("SELECT 'a' 'b'", CCJSqlParserUtil.parse("SELECT 'a' 'b'").toString());
assertEquals("SELECT 'a' 'b'", CCJSqlParserUtil.parse("SELECT 'a'\n'b'").toString());
assertThrows(JSQLParserException.class,
() -> CCJSqlParserUtil.parse("SELECT * FROM t WHERE x = 'a'\n'b'"));
// newline mode: the standard / Postgres reading, "Two string constants
// that are only separated by whitespace with at least one newline are
// concatenated"
PlainSelect select = (PlainSelect) ((Select) CCJSqlParserUtil.parse("SELECT 'a'\n'b'",
p -> p.withAdjacentStringLiterals(
AbstractJSqlParser.AdjacentStringLiterals.NEWLINE)))
.getSelectBody();
Expression expression = select.getSelectItems().get(0).getExpression();
assertTrue(expression instanceof StringValue);
assertEquals("ab", ((StringValue) expression).getValue());
assertEquals("SELECT 'ab'", select.toString());
// same line keeps the alias reading under newline mode
assertEquals("SELECT 'a' 'b'",
CCJSqlParserUtil.parse("SELECT 'a' 'b'",
p -> p.withAdjacentStringLiterals(
AbstractJSqlParser.AdjacentStringLiterals.NEWLINE))
.toString());
// expression positions concatenate too
assertEquals("SELECT * FROM t WHERE x = 'ab'",
CCJSqlParserUtil.parse("SELECT * FROM t WHERE x = 'a'\n'b'",
p -> p.withAdjacentStringLiterals(
AbstractJSqlParser.AdjacentStringLiterals.NEWLINE))
.toString());
// three parts merge into one literal
assertEquals("SELECT 'abc'",
CCJSqlParserUtil.parse("SELECT 'a'\n'b'\n'c'",
p -> p.withAdjacentStringLiterals(
AbstractJSqlParser.AdjacentStringLiterals.NEWLINE))
.toString());
// the ANSI SQL and Postgres presets carry it, the MySQL preset does not
for (AbstractJSqlParser.Dialect dialect : new AbstractJSqlParser.Dialect[] {
AbstractJSqlParser.Dialect.ANSI_SQL, AbstractJSqlParser.Dialect.POSTGRESQL}) {
assertEquals("SELECT 'ab'", CCJSqlParserUtil.parse("SELECT 'a'\n'b'",
p -> p.withDialect(dialect)).toString());
}
assertEquals("SELECT 'a' 'b'", CCJSqlParserUtil.parse("SELECT 'a'\n'b'",
p -> p.withDialect(AbstractJSqlParser.Dialect.MYSQL)).toString());
}

@Test
public void testAdjacentStringLiteralsWhitespace() throws Exception {
// whitespace mode: the GoogleSQL / Spark reading, same line
// concatenates (their literal chunking)
assertEquals("SELECT 'ab'",
CCJSqlParserUtil.parse("SELECT 'a' 'b'",
p -> p.withAdjacentStringLiterals(
AbstractJSqlParser.AdjacentStringLiterals.WHITESPACE))
.toString());
// combined with allowDoubleQuotedStrings: the BigQuery chunking shape
assertEquals("SELECT \"12\"",
CCJSqlParserUtil.parse("SELECT \"1\" \"2\"",
p -> p.withDoubleQuotedStrings(true).withAdjacentStringLiterals(
AbstractJSqlParser.AdjacentStringLiterals.WHITESPACE))
.toString());
// the BigQuery and Databricks presets carry it, the Snowflake preset
// does not (no adjacent-literal concatenation documented)
for (AbstractJSqlParser.Dialect dialect : new AbstractJSqlParser.Dialect[] {
AbstractJSqlParser.Dialect.BIGQUERY, AbstractJSqlParser.Dialect.DATABRICKS}) {
assertEquals("SELECT 'ab'", CCJSqlParserUtil.parse("SELECT 'a' 'b'",
p -> p.withDialect(dialect)).toString());
}
assertEquals("SELECT 'a' 'b'", CCJSqlParserUtil.parse("SELECT 'a' 'b'",
p -> p.withDialect(AbstractJSqlParser.Dialect.SNOWFLAKE)).toString());
}

@Test
public void testAdjacentStringLiteralsBoolean() throws Exception {
// true: the standard (newline) mode, the same line keeps the alias reading
assertEquals("SELECT 'ab'", CCJSqlParserUtil.parse("SELECT 'a'\n'b'",
p -> p.withAdjacentStringLiterals(true)).toString());
assertEquals("SELECT 'a' 'b'", CCJSqlParserUtil.parse("SELECT 'a' 'b'",
p -> p.withAdjacentStringLiterals(true)).toString());
// false: off, the alias reading also across newlines
assertEquals("SELECT 'a' 'b'", CCJSqlParserUtil.parse("SELECT 'a'\n'b'",
p -> p.withAdjacentStringLiterals(false)).toString());
// the no-arg variant enables the standard mode
assertEquals("SELECT 'ab'", CCJSqlParserUtil.parse("SELECT 'a'\n'b'",
p -> p.withAdjacentStringLiterals()).toString());
}

@Test
public void testAdjacentStringLiteralsNewline() throws Exception {

Check failure on line 779 in src/test/java/net/sf/jsqlparser/parser/CCJSqlParserUtilTest.java

View workflow job for this annotation

GitHub Actions / Gradle Check (macos-latest)

method testAdjacentStringLiteralsNewline() is already defined in class CCJSqlParserUtilTest
// default (off): the MySQL / SQL Server alias reading, unchanged
assertEquals("SELECT 'a' 'b'", CCJSqlParserUtil.parse("SELECT 'a' 'b'").toString());
assertEquals("SELECT 'a' 'b'", CCJSqlParserUtil.parse("SELECT 'a'\n'b'").toString());
Expand Down Expand Up @@ -703,7 +822,7 @@
}

@Test
public void testAdjacentStringLiteralsWhitespace() throws Exception {

Check failure on line 825 in src/test/java/net/sf/jsqlparser/parser/CCJSqlParserUtilTest.java

View workflow job for this annotation

GitHub Actions / Gradle Check (macos-latest)

method testAdjacentStringLiteralsWhitespace() is already defined in class CCJSqlParserUtilTest
// whitespace mode: the GoogleSQL / Spark reading, same line
// concatenates (their literal chunking)
assertEquals("SELECT 'ab'",
Expand All @@ -720,7 +839,7 @@
}

@Test
public void testAdjacentStringLiteralsBoolean() throws Exception {

Check failure on line 842 in src/test/java/net/sf/jsqlparser/parser/CCJSqlParserUtilTest.java

View workflow job for this annotation

GitHub Actions / Gradle Check (macos-latest)

method testAdjacentStringLiteralsBoolean() is already defined in class CCJSqlParserUtilTest
// true: the standard (newline) mode, the same line keeps the alias reading
assertEquals("SELECT 'ab'", CCJSqlParserUtil.parse("SELECT 'a'\n'b'",
p -> p.withAdjacentStringLiterals(true)).toString());
Expand Down
Loading