[NO ISSUE][SQLPP] Alternative escaping of quotes in StringLiteral
- user model changes: no
- storage format changes: no
- interface changes: no
Details:
- Allow single/double quotes to be escaped in
StringLiterals by doubling them up
E.g.: 'Asterix''DB' is parsed as: Asterix'DB
- Allow 'E' prefix in StringLiterals
E.g.: E'AsterixDB' is parsed as: AsterixDB
- Add testcases
Change-Id: Ieba3b525162fc4602f5edeabe96ab50d28453860
Reviewed-on: https://asterix-gerrit.ics.uci.edu/c/asterixdb/+/13687
Integration-Tests: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Tested-by: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Reviewed-by: Dmitry Lychagin <dmitry.lychagin@couchbase.com>
Reviewed-by: Ali Alsuliman <ali.al.solaiman@gmail.com>
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/string-literal1/string-literal1.1.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/string-literal1/string-literal1.1.query.sqlpp
new file mode 100644
index 0000000..6c0c952
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/string/string-literal1/string-literal1.1.query.sqlpp
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+with strings as [
+ 'xs0',
+ 'xs1''ys1',
+ 'xs2''ys2''zs2',
+ E'xs3',
+
+ "xd0",
+ "xd1""yd1",
+ "xd2""yd2""zd2",
+ E"xd3"
+]
+
+select i, s
+from range(0, len(strings)-1) i
+let s = strings[i]
+order by i;
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/string/string-literal1/string-literal1.1.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/string/string-literal1/string-literal1.1.adm
new file mode 100644
index 0000000..1804cf8
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/string/string-literal1/string-literal1.1.adm
@@ -0,0 +1,8 @@
+{ "i": 0, "s": "xs0" }
+{ "i": 1, "s": "xs1'ys1" }
+{ "i": 2, "s": "xs2'ys2'zs2" }
+{ "i": 3, "s": "xs3" }
+{ "i": 4, "s": "xd0" }
+{ "i": 5, "s": "xd1\"yd1" }
+{ "i": 6, "s": "xd2\"yd2\"zd2" }
+{ "i": 7, "s": "xd3" }
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/testsuite_sqlpp.xml b/asterixdb/asterix-app/src/test/resources/runtimets/testsuite_sqlpp.xml
index facf00f..d1f19ea 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/testsuite_sqlpp.xml
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/testsuite_sqlpp.xml
@@ -10835,6 +10835,11 @@
</compilation-unit>
</test-case>
<test-case FilePath="string">
+ <compilation-unit name="string-literal1">
+ <output-dir compare="Text">string-literal1</output-dir>
+ </compilation-unit>
+ </test-case>
+ <test-case FilePath="string">
<compilation-unit name="string-to-codepoint">
<output-dir compare="Text">string-to-codepoint</output-dir>
</compilation-unit>
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/javacc/SQLPP.jj b/asterixdb/asterix-lang-sqlpp/src/main/javacc/SQLPP.jj
index 969d579..facdfa7 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/javacc/SQLPP.jj
+++ b/asterixdb/asterix-lang-sqlpp/src/main/javacc/SQLPP.jj
@@ -3157,12 +3157,48 @@
String StringLiteral() throws ParseException:
{
+ StringBuilder str = null;
+ char quote = 0;
+ boolean ext = false;
+ Token litToken = null;
+ String lit = null;
}
{
- <STRING_LITERAL>
+ ( <STRING_LITERAL>
{
- return removeQuotesAndEscapes(token.image);
+ String quoted = token.image;
+ char q = quoted.charAt(0);
+ boolean e = q == 'E';
+ if (e) {
+ q = quoted.charAt(1);
+ quoted = quoted.substring(1);
+ }
+ if (lit == null) {
+ quote = q;
+ ext = e;
+ } else {
+ boolean isAdjacent = litToken.endLine == token.beginLine && litToken.endColumn + 1 == token.beginColumn;
+ if (!isAdjacent || ext || e || (q != quote)) {
+ throw new SqlppParseException(getSourceLocation(token), "Invalid string literal");
+ }
+ if (str == null) {
+ str = new StringBuilder();
+ }
+ str.append(lit);
+ str.append(quote);
+ }
+ lit = removeQuotesAndEscapes(quoted);
+ litToken = token;
}
+ )+
+ {
+ if (str == null) {
+ return lit;
+ } else {
+ str.append(lit);
+ return str.toString();
+ }
+ }
}
Triple<List<String>, Token, Token> MultipartIdentifier() throws ParseException:
@@ -5472,7 +5508,7 @@
| <EscapeCr>
| <EscapeTab>
| ~["`","\\"])* "`">
- | <STRING_LITERAL : ("\"" (
+ | <STRING_LITERAL : ( ("E")? "\"" (
<EscapeQuot>
| <EscapeBslash>
| <EscapeSlash>
@@ -5482,7 +5518,7 @@
| <EscapeCr>
| <EscapeTab>
| ~["\"","\\"])* "\"")
- | ("\'"(
+ | ( ("E")? "\'" (
<EscapeApos>
| <EscapeBslash>
| <EscapeSlash>
diff --git a/asterixdb/asterix-lang-sqlpp/src/test/java/org/apache/asterix/lang/sqlpp/parser/ParserTest.java b/asterixdb/asterix-lang-sqlpp/src/test/java/org/apache/asterix/lang/sqlpp/parser/ParserTest.java
index 5b7ef16..8413c38 100644
--- a/asterixdb/asterix-lang-sqlpp/src/test/java/org/apache/asterix/lang/sqlpp/parser/ParserTest.java
+++ b/asterixdb/asterix-lang-sqlpp/src/test/java/org/apache/asterix/lang/sqlpp/parser/ParserTest.java
@@ -19,8 +19,12 @@
package org.apache.asterix.lang.sqlpp.parser;
import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.lang.common.base.Expression;
import org.apache.asterix.lang.common.base.IParser;
import org.apache.asterix.lang.common.base.IParserFactory;
+import org.apache.asterix.lang.common.base.Literal;
+import org.apache.asterix.lang.common.expression.LiteralExpr;
+import org.junit.Assert;
import org.junit.Test;
public class ParserTest {
@@ -105,4 +109,87 @@
parser = factory.createParser(query);
parser.parse();
}
+
+ @Test
+ public void testStringLiterals() {
+ // test different quote combinations
+ String v1 = "a1";
+ String[] prefixes = new String[] { "", "E" };
+ String[] quotes = new String[] { "'", "\"" };
+
+ IParserFactory factory = new SqlppParserFactory();
+ StringBuilder qb = new StringBuilder();
+
+ for (String p : prefixes) {
+ for (String q1 : quotes) {
+ for (String q2 : quotes) {
+ qb.setLength(0);
+ qb.append(p).append(q1).append(v1).append(q2).append(';');
+
+ boolean expectSuccess = q1.equals(q2);
+ String expectedValue = expectSuccess ? v1 : "Lexical error";
+ testParseStringLiteral(qb.toString(), expectSuccess, expectedValue, factory);
+ }
+ }
+ }
+ }
+
+ @Test
+ public void testStringLiteralsMulti() {
+ // test different quote combinations
+ String v1 = "a1", v2 = "b2", v3 = "c3";
+ String[] prefixesStart = new String[] { "", "E" };
+ String[] prefixesRest = new String[] { "", "E", " ", "\n" };
+ String[] quotes = new String[] { "'", "\"" };
+
+ IParserFactory factory = new SqlppParserFactory();
+ StringBuilder qb = new StringBuilder();
+ for (String p1 : prefixesStart) {
+ for (String q1 : quotes) {
+ for (String p2 : prefixesRest) {
+ for (String q2 : quotes) {
+ for (String p3 : prefixesRest) {
+ for (String q3 : quotes) {
+ qb.setLength(0);
+ qb.append(p1).append(q1).append(v1).append(q1);
+ qb.append(p2).append(q2).append(v2).append(q2);
+ qb.append(p3).append(q3).append(v3).append(q3);
+ qb.append(';');
+
+ boolean expectSuccess =
+ p1.isEmpty() && p2.isEmpty() && p3.isEmpty() && q1.equals(q2) && q1.equals(q3);
+ String expectedValue =
+ expectSuccess ? v1 + q1 + v2 + q1 + v3 : "Invalid string literal";
+ testParseStringLiteral(qb.toString(), expectSuccess, expectedValue, factory);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ private void testParseStringLiteral(String query, boolean expectSuccess, String expectedValueOrError,
+ IParserFactory factory) {
+ try {
+ IParser parser = factory.createParser(query);
+ Expression expr = parser.parseExpression();
+ if (!expectSuccess) {
+ Assert.fail("Unexpected parsing success for: " + query);
+ }
+ Assert.assertEquals(Expression.Kind.LITERAL_EXPRESSION, expr.getKind());
+ LiteralExpr litExpr = (LiteralExpr) expr;
+ Literal lit = litExpr.getValue();
+ Assert.assertEquals(Literal.Type.STRING, lit.getLiteralType());
+ String value = lit.getStringValue();
+ Assert.assertEquals(expectedValueOrError, value);
+ } catch (CompilationException e) {
+ if (expectSuccess) {
+ Assert.fail("Unexpected parsing failure for: " + query + " : " + e);
+ } else {
+ Assert.assertTrue(e.getMessage() + " does not contain: " + expectedValueOrError,
+ e.getMessage().contains(expectedValueOrError));
+ }
+ }
+ }
}