Browse Source

SQL: double quotes escaping bug fix (#43829)

Andrei Stefan 6 years ago
parent
commit
d589dcad18

+ 3 - 0
docs/reference/sql/language/syntax/lexic/index.asciidoc

@@ -121,6 +121,9 @@ SELECT "first_name" <1>
 <1> Double quotes `"` used for column and table identifiers
 <2> Single quotes `'` used for a string literal
 
+NOTE:: to escape single or double quotes, one needs to use that specific quote one more time. For example, the literal `John's` can be escaped like
+`SELECT 'John''s' AS name`. The same goes for double quotes escaping - `SELECT 123 AS "test""number"` will display as a result a column with the name `test"number`.
+
 [[sql-syntax-special-chars]]
 ==== Special characters
 

+ 6 - 2
x-pack/plugin/sql/src/main/java/org/elasticsearch/xpack/sql/parser/IdentifierBuilder.java

@@ -25,12 +25,12 @@ abstract class IdentifierBuilder extends AbstractBuilder {
         ParseTree tree = ctx.name != null ? ctx.name : ctx.TABLE_IDENTIFIER();
         String index = tree.getText();
 
-        return new TableIdentifier(source, visitIdentifier(ctx.catalog), index);
+        return new TableIdentifier(source, visitIdentifier(ctx.catalog), unquoteIdentifier(index));
     }
 
     @Override
     public String visitIdentifier(IdentifierContext ctx) {
-        return ctx == null ? null : ctx.getText();
+        return ctx == null ? null : unquoteIdentifier(ctx.getText());
     }
 
     @Override
@@ -41,4 +41,8 @@ abstract class IdentifierBuilder extends AbstractBuilder {
 
         return Strings.collectionToDelimitedString(visitList(ctx.identifier(), String.class), ".");
     }
+    
+    private static String unquoteIdentifier(String identifier) {
+        return identifier.replace("\"\"", "\"");
+    }
 }

+ 22 - 0
x-pack/plugin/sql/src/test/java/org/elasticsearch/xpack/sql/parser/SqlParserTests.java

@@ -6,7 +6,10 @@
 package org.elasticsearch.xpack.sql.parser;
 
 import com.google.common.base.Joiner;
+
 import org.elasticsearch.test.ESTestCase;
+import org.elasticsearch.xpack.sql.expression.Alias;
+import org.elasticsearch.xpack.sql.expression.Literal;
 import org.elasticsearch.xpack.sql.expression.NamedExpression;
 import org.elasticsearch.xpack.sql.expression.Order;
 import org.elasticsearch.xpack.sql.expression.UnresolvedAttribute;
@@ -21,6 +24,7 @@ import org.elasticsearch.xpack.sql.plan.logical.Filter;
 import org.elasticsearch.xpack.sql.plan.logical.LogicalPlan;
 import org.elasticsearch.xpack.sql.plan.logical.OrderBy;
 import org.elasticsearch.xpack.sql.plan.logical.Project;
+import org.elasticsearch.xpack.sql.plan.logical.UnresolvedRelation;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -46,6 +50,24 @@ public class SqlParserTests extends ESTestCase {
         return type.cast(p);
     }
 
+    public void testEscapeDoubleQuotes() {
+        Project project = project(parseStatement("SELECT bar FROM \"fo\"\"o\""));
+        assertTrue(project.child() instanceof UnresolvedRelation);
+        assertEquals("fo\"o", ((UnresolvedRelation) project.child()).table().index());
+    }
+
+    public void testEscapeSingleQuotes() {
+        Alias a = singleProjection(project(parseStatement("SELECT '''ab''c' AS \"escaped_text\"")), Alias.class);
+        assertEquals("'ab'c", ((Literal) a.child()).value());
+        assertEquals("escaped_text", a.name());
+    }
+
+    public void testEscapeSingleAndDoubleQuotes() {
+        Alias a = singleProjection(project(parseStatement("SELECT 'ab''c' AS \"escaped\"\"text\"")), Alias.class);
+        assertEquals("ab'c", ((Literal) a.child()).value());
+        assertEquals("escaped\"text", a.name());
+    }
+
     public void testSelectField() {
         UnresolvedAttribute a = singleProjection(project(parseStatement("SELECT bar FROM foo")), UnresolvedAttribute.class);
         assertEquals("bar", a.name());