Skip to content

Commit

Permalink
SQL: double quotes escaping bug fix (elastic#43829)
Browse files Browse the repository at this point in the history
  • Loading branch information
astefan authored Jul 10, 2019
1 parent 593e163 commit d589dca
Show file tree
Hide file tree
Showing 3 changed files with 31 additions and 2 deletions.
3 changes: 3 additions & 0 deletions docs/reference/sql/language/syntax/lexic/index.asciidoc
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,12 @@ public TableIdentifier visitTableIdentifier(TableIdentifierContext ctx) {
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
Expand All @@ -41,4 +41,8 @@ public String visitQualifiedName(QualifiedNameContext ctx) {

return Strings.collectionToDelimitedString(visitList(ctx.identifier(), String.class), ".");
}

private static String unquoteIdentifier(String identifier) {
return identifier.replace("\"\"", "\"");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -21,6 +24,7 @@
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;
Expand All @@ -46,6 +50,24 @@ private <T> T singleProjection(Project project, Class<T> type) {
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());
Expand Down

0 comments on commit d589dca

Please sign in to comment.