Skip to content
This repository has been archived by the owner on Aug 2, 2022. It is now read-only.

Support to parse backticks quoted identifiers #240

Merged
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
920830a
Added an unquoter utils class to extract string text from the back-ticks
Oct 21, 2019
3e3a91e
Modified the semantic analyzer to visit the index names and field nam…
Oct 21, 2019
5ef2ba8
Added an UnquoteIdentifierRule rewrite the AST that are constructed f…
Oct 21, 2019
a91696b
Moved the rule into "identifier" package under the rewriter
Oct 21, 2019
eef1203
Added unittest for the backticksUnquoter utils class
Oct 21, 2019
f851098
Added integtest for the unquote rule
Oct 21, 2019
b325d44
Modified the unquoter to make it reusable, and moved into StringUtils…
Oct 21, 2019
272f2c7
Modified unittest
Oct 21, 2019
614c78e
Added proper condition to modify the SQLSelectItem in visiting method
Oct 21, 2019
8a0c4c6
Added comments to explain the major methods in the rewriter rule.
Oct 21, 2019
5968924
Moved pretty formatter test under utils test class
Oct 21, 2019
d9796c8
Modified the unquoter IT
Oct 21, 2019
157ed37
Added UT for the updated ANTLR parser
Oct 21, 2019
76b1eb7
Used Strings.isNullOrEmpty()
Oct 21, 2019
9ca3688
Reused unquoteSingleField method; modified the substring generating code
Oct 21, 2019
8201a8d
Updated
Oct 21, 2019
bc876ef
Updated
Oct 21, 2019
97eeec8
Added IT to test the field alias in JDBC response; seperated backtick…
Oct 21, 2019
bf8445c
Updated intro of rewriter rule
Oct 21, 2019
aa29c0f
Added unittest for UnquoteIdentifierRule
Oct 22, 2019
c7b4216
Updated
Oct 22, 2019
4332813
Updated
Oct 22, 2019
d66f233
Updated
Oct 22, 2019
afa4684
Updated
Oct 22, 2019
44b4c1a
Updated
Oct 22, 2019
ba1235b
Added UT and IT for quoted <table>.<column> case
Oct 22, 2019
72f92de
Added one UT case
Oct 22, 2019
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@

import com.amazon.opendistroforelasticsearch.sql.antlr.semantic.types.Type;
import com.amazon.opendistroforelasticsearch.sql.antlr.visitor.GenericSqlParseTreeVisitor;
import com.amazon.opendistroforelasticsearch.sql.utils.BackticksUnquoter;

import java.util.List;

Expand Down Expand Up @@ -60,20 +61,20 @@ public Type visitSelect(List<Type> itemTypes) {

@Override
public void visitAs(String alias, Type type) {
mappingLoader.visitAs(alias, type);
typeChecker.visitAs(alias, type);
mappingLoader.visitAs(new BackticksUnquoter().unquoteSingleField(alias), type);
typeChecker.visitAs(new BackticksUnquoter().unquoteSingleField(alias), type);
}

@Override
public Type visitIndexName(String indexName) {
mappingLoader.visitIndexName(indexName);
return typeChecker.visitIndexName(indexName);
mappingLoader.visitIndexName(new BackticksUnquoter().unquoteSingleField(indexName));
return typeChecker.visitIndexName(new BackticksUnquoter().unquoteSingleField(indexName));
}

@Override
public Type visitFieldName(String fieldName) {
mappingLoader.visitFieldName(fieldName);
return typeChecker.visitFieldName(fieldName);
mappingLoader.visitFieldName(new BackticksUnquoter().unquoteFullColumn(fieldName));
return typeChecker.visitFieldName(new BackticksUnquoter().unquoteFullColumn(fieldName));
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
import com.amazon.opendistroforelasticsearch.sql.query.multi.MultiQueryAction;
import com.amazon.opendistroforelasticsearch.sql.query.multi.MultiQuerySelect;
import com.amazon.opendistroforelasticsearch.sql.rewriter.RewriteRuleExecutor;
import com.amazon.opendistroforelasticsearch.sql.rewriter.identifier.UnquoteIdentifierRule;
import com.amazon.opendistroforelasticsearch.sql.rewriter.alias.TableAliasPrefixRemoveRule;
import com.amazon.opendistroforelasticsearch.sql.rewriter.matchtoterm.TermFieldRewriter;
import com.amazon.opendistroforelasticsearch.sql.rewriter.matchtoterm.TermFieldRewriter.TermRewriterFilter;
Expand Down Expand Up @@ -78,6 +79,7 @@ public static QueryAction create(Client client, String sql) throws SqlParseExcep

RewriteRuleExecutor<SQLQueryExpr> ruleExecutor = RewriteRuleExecutor.builder()
.withRule(new SQLExprParentSetterRule())
.withRule(new UnquoteIdentifierRule())
.withRule(new TableAliasPrefixRemoveRule())
.withRule(new SubQueryRewriteRule())
.build();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* or in the "license" file accompanying this file. This file 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.
*/

package com.amazon.opendistroforelasticsearch.sql.rewriter.identifier;

import com.alibaba.druid.sql.ast.expr.SQLIdentifierExpr;
import com.alibaba.druid.sql.ast.expr.SQLPropertyExpr;
import com.alibaba.druid.sql.ast.expr.SQLQueryExpr;
import com.alibaba.druid.sql.ast.statement.SQLExprTableSource;
import com.alibaba.druid.sql.ast.statement.SQLSelectItem;
import com.alibaba.druid.sql.dialect.mysql.visitor.MySqlASTVisitorAdapter;
import com.amazon.opendistroforelasticsearch.sql.rewriter.RewriteRule;
import com.amazon.opendistroforelasticsearch.sql.utils.BackticksUnquoter;

public class UnquoteIdentifierRule extends MySqlASTVisitorAdapter implements RewriteRule<SQLQueryExpr> {
chloe-zh marked this conversation as resolved.
Show resolved Hide resolved

String identifier = null;
chloe-zh marked this conversation as resolved.
Show resolved Hide resolved

@Override
public boolean visit(SQLPropertyExpr propertyExpr) {
String identifier = ((SQLIdentifierExpr) propertyExpr.getOwner()).getName();
chloe-zh marked this conversation as resolved.
Show resolved Hide resolved
if (identifier.startsWith("`") && identifier.endsWith("`")) {
chloe-zh marked this conversation as resolved.
Show resolved Hide resolved
this.identifier = new BackticksUnquoter().unquoteSingleField(identifier) + "."
+ new BackticksUnquoter().unquoteSingleField(propertyExpr.getName());
SQLSelectItem selectItem = (SQLSelectItem) propertyExpr.getParent();
selectItem.setExpr(new SQLIdentifierExpr(this.identifier));
this.identifier = null;
return false;
}
return true;
}

@Override
public boolean visit(SQLSelectItem selectItem) {
try {
String identifier = ((SQLIdentifierExpr) selectItem.getExpr()).getName();
chloe-zh marked this conversation as resolved.
Show resolved Hide resolved
if (identifier.endsWith(".")) {
this.identifier = identifier + new BackticksUnquoter().unquoteSingleField(selectItem.getAlias());
selectItem.setExpr(new SQLIdentifierExpr(this.identifier));
selectItem.setAlias(null);
}
} finally {
return true;
}
}

@Override
public boolean visit(SQLIdentifierExpr identifierExpr) {
if (identifier != null) {
return false;
}
return true;
}

@Override
public void endVisit(SQLIdentifierExpr identifierExpr) {
if (identifier != null) {
chloe-zh marked this conversation as resolved.
Show resolved Hide resolved
identifierExpr.setName(identifier);
identifier = null;
} else {
identifierExpr.setName(new BackticksUnquoter().unquoteFullColumn(identifierExpr.getName()));
}
}

@Override
public void endVisit(SQLExprTableSource tableSource) {
tableSource.setAlias(new BackticksUnquoter().unquoteSingleField(tableSource.getAlias()));
}

@Override
public boolean match(SQLQueryExpr root) {
chloe-zh marked this conversation as resolved.
Show resolved Hide resolved
return true;
}

@Override
public void rewrite(SQLQueryExpr root) {
chloe-zh marked this conversation as resolved.
Show resolved Hide resolved
root.accept(new UnquoteIdentifierRule());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* or in the "license" file accompanying this file. This file 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.
*/

package com.amazon.opendistroforelasticsearch.sql.utils;

/**
* Utils Class for remove the back-ticks of identifiers
*/
public class BackticksUnquoter {
chloe-zh marked this conversation as resolved.
Show resolved Hide resolved

/**
*
* @param text
* @return An unquoted string whose outer pair of back-ticks (if any) has been removed
*/
public static String unquoteSingleField(String text) {
chloe-zh marked this conversation as resolved.
Show resolved Hide resolved
if (text != null && text.startsWith("`") && text.endsWith("`")) {
chloe-zh marked this conversation as resolved.
Show resolved Hide resolved
return text.substring(1, text.length() - 1);
}
return text;
}

/**
*
* @param text
* @return A string whose each dot-seperated field has been unquoted from back-ticks (if any)
*/
public static String unquoteFullColumn(String text) {
chloe-zh marked this conversation as resolved.
Show resolved Hide resolved
if (text == null) {
return null;
}
String[] strs = text.split("\\.");
for (int i = 0; i < strs.length; i++) {
String unquotedSubstr = strs[i];
if (unquotedSubstr != null && unquotedSubstr.startsWith("`") && unquotedSubstr.endsWith("`")) {
unquotedSubstr = strs[i].substring(1, strs[i].length() - 1);
}
strs[i] = unquotedSubstr;
}
StringBuilder unquotedStrBuilder = new StringBuilder(strs[0]);
for (int i = 1; i < strs.length; i++) {
unquotedStrBuilder.append(".").append(strs[i]);
}
return unquotedStrBuilder.toString();
}

public BackticksUnquoter() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you don't need to instantiate this class, make constructor private

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -1671,6 +1671,40 @@ public void fieldCollapsingTest() throws IOException {
Assert.assertEquals(21, hits.length());
}

@Test
public void backticksQuotedIndexNameTest() throws Exception {
AdminClient adminClient = this.admin();
TestUtils.createTestIndex(adminClient, "bank_two", "bank_two", null);
TestUtils.loadBulk(ESIntegTestCase.client(), "/src/test/resources/bank_two.json", "bank");

final String query = "SELECT lastname FROM `bank` ORDER BY age LIMIT 3";
JSONObject response = executeQuery(query);
JSONArray hits = getHits(response);

assertThat("bank", equalTo(((JSONObject) hits.get(0)).query("/_index")));
}

@Test
public void backticksQuotedFieldNamesTest() {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we test so many queries within one method?

dai-chen marked this conversation as resolved.
Show resolved Hide resolved
final String basicQuery = StringUtils.format("SELECT b.lastname FROM %s " +
"AS b ORDER BY age LIMIT 3", TestsConstants.TEST_INDEX_BANK);
final String queryWithQuotedAlias = StringUtils.format("SELECT `b`.lastname FROM %s" +
" AS `b` ORDER BY age LIMIT 3", TestsConstants.TEST_INDEX_BANK);
final String queryWithQuotedFieldName = StringUtils.format("SELECT b.`lastname` FROM %s " +
"AS b ORDER BY age LIMIT 3", TestsConstants.TEST_INDEX_BANK);
final String queryWithQuotedAliasAndFieldName = StringUtils.format("SELECT `b`.`lastname` FROM %s " +
"AS `b` ORDER BY age LIMIT 3", TestsConstants.TEST_INDEX_BANK);
final String queryWithQuotedAliasContainingSpecialChars = StringUtils.format("SELECT `b k`.lastname FROM %s " +
"AS `b k` ORDER BY age LIMIT 3", TestsConstants.TEST_INDEX_BANK);

final String expectedResponse = executeQuery(basicQuery, "jdbc");

assertThat(expectedResponse, equalTo(executeQuery(queryWithQuotedAlias, "jdbc")));
assertThat(expectedResponse, equalTo(executeQuery(queryWithQuotedFieldName, "jdbc")));
assertThat(expectedResponse, equalTo(executeQuery(queryWithQuotedAliasAndFieldName, "jdbc")));
assertThat(expectedResponse, equalTo(executeQuery(queryWithQuotedAliasContainingSpecialChars, "jdbc")));
}

private String getScrollId(JSONObject response) {
return response.getString("_scroll_id");
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Copyright 2019 Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* or in the "license" file accompanying this file. This file 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.
*/

package com.amazon.opendistroforelasticsearch.sql.unittest.utils;

import com.amazon.opendistroforelasticsearch.sql.utils.BackticksUnquoter;
import org.junit.Test;

import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.IsEqual.equalTo;

public class BackticksUnquoterTest {
dai-chen marked this conversation as resolved.
Show resolved Hide resolved

@Test
public void assertNotQuotedStringShouldKeepTheSame() {
final String originalText = "identifier";
chloe-zh marked this conversation as resolved.
Show resolved Hide resolved
final String resultForUnquotingSingleField = new BackticksUnquoter().unquoteSingleField(originalText);
final String resultForUnquotingFullColumn = new BackticksUnquoter().unquoteFullColumn(originalText);

assertThat(resultForUnquotingSingleField, equalTo(originalText));
assertThat(resultForUnquotingFullColumn, equalTo(originalText));
}

@Test
public void assertStringWithOneBackTickShouldKeepTheSame() {
final String originalText = "`identifier";
final String result1 = new BackticksUnquoter().unquoteSingleField(originalText);
final String result2 = new BackticksUnquoter().unquoteFullColumn(originalText);

assertThat(result1, equalTo(originalText));
assertThat(result2, equalTo(originalText));
}

@Test
public void assertBackticksQuotedStringShouldBeUnquoted() {
final String originalText1 = "`identifier`";
final String originalText2 = "`identifier1`.`identifier2`";

final String expectedResult1 = "identifier";
final String expectedResult2 = "identifier1.identifier2";
chloe-zh marked this conversation as resolved.
Show resolved Hide resolved

final String result1 = new BackticksUnquoter().unquoteSingleField(originalText1);
final String result2 = new BackticksUnquoter().unquoteFullColumn(originalText2);

assertThat(expectedResult1, equalTo(result1));
assertThat(expectedResult2, equalTo(result2));
}
}