diff --git a/src/main/java/net/sf/jsqlparser/expression/StringValue.java b/src/main/java/net/sf/jsqlparser/expression/StringValue.java
index a16536fab..f02bff523 100644
--- a/src/main/java/net/sf/jsqlparser/expression/StringValue.java
+++ b/src/main/java/net/sf/jsqlparser/expression/StringValue.java
@@ -36,6 +36,12 @@ public StringValue(String escapedValue) {
&& escapedValue.endsWith("'")) {
value = escapedValue.substring(1, escapedValue.length() - 1);
return;
+ } else if (escapedValue.length() >= 2 && escapedValue.startsWith("\"")
+ && escapedValue.endsWith("\"")) {
+ // double quoted String Literals (Feature.allowDoubleQuotedStrings)
+ value = escapedValue.substring(1, escapedValue.length() - 1);
+ quoteStr = "\"";
+ return;
} else if (escapedValue.length() >= 4 && escapedValue.startsWith("$$")
&& escapedValue.endsWith("$$")) {
value = escapedValue.substring(2, escapedValue.length() - 2);
diff --git a/src/main/java/net/sf/jsqlparser/parser/AbstractJSqlParser.java b/src/main/java/net/sf/jsqlparser/parser/AbstractJSqlParser.java
index 8d4e72ea4..2cb803396 100644
--- a/src/main/java/net/sf/jsqlparser/parser/AbstractJSqlParser.java
+++ b/src/main/java/net/sf/jsqlparser/parser/AbstractJSqlParser.java
@@ -26,8 +26,10 @@ public abstract class AbstractJSqlParser
{
public enum Dialect {
ANSI_SQL, ORACLE, MYSQL(Feature.allowBackslashEscapeCharacter,
- Feature.allowHashLineComments), MARIADB(Feature.allowBackslashEscapeCharacter,
- Feature.allowHashLineComments), SQLSERVER(
+ Feature.allowHashLineComments,
+ Feature.allowDoubleQuotedStrings), MARIADB(Feature.allowBackslashEscapeCharacter,
+ Feature.allowHashLineComments,
+ Feature.allowDoubleQuotedStrings), SQLSERVER(
Feature.allowSquareBracketQuotation), POSTGRESQL, H2, EXASOL;
private final Set lexerFeatures;
@@ -98,6 +100,14 @@ public P withBackslashEscapeCharacter(boolean allowBackslashEscapeCharacter) {
return withFeature(Feature.allowBackslashEscapeCharacter, allowBackslashEscapeCharacter);
}
+ public P withDoubleQuotedStrings() {
+ return withFeature(Feature.allowDoubleQuotedStrings, true);
+ }
+
+ public P withDoubleQuotedStrings(boolean allowDoubleQuotedStrings) {
+ return withFeature(Feature.allowDoubleQuotedStrings, allowDoubleQuotedStrings);
+ }
+
public P withHashLineComments() {
return withFeature(Feature.allowHashLineComments, true);
}
diff --git a/src/main/java/net/sf/jsqlparser/parser/feature/Feature.java b/src/main/java/net/sf/jsqlparser/parser/feature/Feature.java
index 5daa74eca..de5a492f2 100644
--- a/src/main/java/net/sf/jsqlparser/parser/feature/Feature.java
+++ b/src/main/java/net/sf/jsqlparser/parser/feature/Feature.java
@@ -795,6 +795,12 @@ public enum Feature {
*/
allowBackslashEscapeCharacter(false),
+ /**
+ * allows double quoted String Literals (BigQuery, Spark/Databricks, MySQL default sql_mode);
+ * disabled by default, where double quotes stay quoted identifiers (ANSI SQL)
+ */
+ allowDoubleQuotedStrings(false),
+
/**
* allows MySQL `#` line comments; disabled by default, where a lone `#` stays the binary
* operator (#2507: PostgreSQL bitwise XOR / geometric intersection)
diff --git a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt
index 9ae702d48..4533078c9 100644
--- a/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt
+++ b/src/main/jjtree/net/sf/jsqlparser/parser/JSqlParserCC.jjt
@@ -2077,6 +2077,11 @@ TOKEN:
matchedToken.kind = squaredBracketOpenIndex;
input_stream.backup(image.length() - 1);
}
+ if ( configuration.getAsBoolean(Feature.allowDoubleQuotedStrings)
+ && matchedToken.image.charAt(0) == '"' ) {
+ // `charLiteralIndex` defined in TokenManagerDeclaration above
+ matchedToken.kind = charLiteralIndex;
+ }
}
}
diff --git a/src/site/sphinx/usage.rst b/src/site/sphinx/usage.rst
index d989b010b..4a9696b17 100644
--- a/src/site/sphinx/usage.rst
+++ b/src/site/sphinx/usage.rst
@@ -279,7 +279,7 @@ Define the Parser Features
JSQLParser interprets Squared Brackets ``[..]`` as Arrays, which does not work with MS SQL Server and T-SQL. Please use the Parser Features to instruct JSQLParser to read Squared Brackets as Quotes instead.
-JSQLParser allows for standard compliant Single Quote ``'..`` Escaping. Additional Back-slash ``\..`` Escaping needs to be activated by setting the ``BackSlashEscapeCharacter`` parser feature.
+JSQLParser allows for standard compliant Single Quote ``'..`` Escaping. Additional Back-slash ``\..`` Escaping needs to be activated by setting the ``BackSlashEscapeCharacter`` parser feature. JSQLParser reads Double Quotes ``".."`` as quoted identifiers (ANSI SQL); reading them as String Literals (BigQuery, Spark/Databricks, MySQL default sql_mode) needs the ``DoubleQuotedStrings`` parser feature.
Additionally there are Features to control the Parser's effort at the cost of the performance.
@@ -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`` and ``withHashLineComments`` (both MySQL and MariaDB syntax), ``withDialect(Dialect.SQLSERVER)`` turns on ``withSquareBracketQuotation``. 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``. Features set explicitly after the dialect preset win over the preset.
.. code-block:: java
diff --git a/src/test/java/net/sf/jsqlparser/parser/CCJSqlParserUtilTest.java b/src/test/java/net/sf/jsqlparser/parser/CCJSqlParserUtilTest.java
index 6addd7f83..54c5e4402 100644
--- a/src/test/java/net/sf/jsqlparser/parser/CCJSqlParserUtilTest.java
+++ b/src/test/java/net/sf/jsqlparser/parser/CCJSqlParserUtilTest.java
@@ -12,6 +12,7 @@
import net.sf.jsqlparser.JSQLParserException;
import net.sf.jsqlparser.expression.Expression;
import net.sf.jsqlparser.expression.LongValue;
+import net.sf.jsqlparser.expression.StringValue;
import net.sf.jsqlparser.expression.operators.arithmetic.Addition;
import net.sf.jsqlparser.expression.operators.arithmetic.Multiplication;
import net.sf.jsqlparser.expression.operators.relational.ParenthesedExpressionList;
@@ -20,6 +21,7 @@
import net.sf.jsqlparser.statement.Statements;
import net.sf.jsqlparser.statement.UnsupportedStatement;
import net.sf.jsqlparser.statement.select.PlainSelect;
+import net.sf.jsqlparser.statement.select.Select;
import net.sf.jsqlparser.statement.select.TableStatement;
import net.sf.jsqlparser.test.MemoryLeakVerifier;
import net.sf.jsqlparser.test.TestUtils;
@@ -44,6 +46,7 @@
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
public class CCJSqlParserUtilTest {
@@ -611,4 +614,45 @@ public void testDialectPresetSwitchOverrides() throws Exception {
.withDialect(AbstractJSqlParser.Dialect.MYSQL))
.toString());
}
+
+ @Test
+ public void testDoubleQuotedStringsFeature() throws Exception {
+ // default (flag off): double quotes are quoted identifiers, unchanged
+ PlainSelect select = (PlainSelect) ((Select) CCJSqlParserUtil
+ .parse("SELECT \"not an identifier\"")).getSelectBody();
+ assertTrue(select.getSelectItems().get(0).getExpression() instanceof Column);
+ // on: the BigQuery/Spark/MySQL-default reading, double quotes are
+ // string literals, the quote kept on round-trip
+ select = (PlainSelect) ((Select) CCJSqlParserUtil.parse("SELECT \"not an identifier\"",
+ p -> p.withDoubleQuotedStrings(true))).getSelectBody();
+ Expression expression = select.getSelectItems().get(0).getExpression();
+ assertTrue(expression instanceof StringValue);
+ assertEquals("not an identifier", ((StringValue) expression).getValue());
+ assertEquals("SELECT \"not an identifier\"", select.toString());
+ // empty string and doubled quotes take the single-quote treatment
+ assertEquals("SELECT \"\"", CCJSqlParserUtil
+ .parse("SELECT \"\"", p -> p.withDoubleQuotedStrings(true)).toString());
+ assertEquals("SELECT \"a\"\"b\"", CCJSqlParserUtil
+ .parse("SELECT \"a\"\"b\"", p -> p.withDoubleQuotedStrings(true)).toString());
+ // identifier positions: a `"..."` token follows the existing
+ // string-as-table branch (`FROM 'file.csv'`), the same leniency
+ // single quotes already have; MySQL itself would error here
+ assertEquals("SELECT * FROM \"t\"", CCJSqlParserUtil
+ .parse("SELECT * FROM \"t\"", p -> p.withDoubleQuotedStrings(true)).toString());
+ }
+
+ @Test
+ public void testDoubleQuotedStringsPreset() throws Exception {
+ // MYSQL and MARIADB presets carry the switch (default sql_mode reading)
+ for (AbstractJSqlParser.Dialect dialect : new AbstractJSqlParser.Dialect[] {
+ AbstractJSqlParser.Dialect.MYSQL, AbstractJSqlParser.Dialect.MARIADB}) {
+ 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);
+ }
}