Skip to content
Permalink
Browse files
Add replace statement to sql parser (#12386)
Relevant Issue: #11929

- Add custom replace statement to Druid SQL parser.
- Edit DruidPlanner to convert relevant fields to Query Context.
- Refactor common code with INSERT statements to reuse them for REPLACE where possible.
  • Loading branch information
adarshsanjeev committed May 13, 2022
1 parent 9177515 commit 39b3487aa9829d1d5b19733ef76319d8133dcb38
Showing 13 changed files with 1,759 additions and 458 deletions.
@@ -51,6 +51,7 @@ data: {
# List of additional classes and packages to import.
# Example. "org.apache.calcite.sql.*", "java.util.List".
imports: [
"java.util.List"
"org.apache.calcite.sql.SqlNode"
"org.apache.calcite.sql.SqlInsert"
"org.apache.druid.java.util.common.granularity.Granularity"
@@ -63,6 +64,7 @@ data: {
# keyword add it to 'nonReservedKeywords' section.
keywords: [
"CLUSTERED"
"OVERWRITE"
"PARTITIONED"
]

@@ -218,6 +220,7 @@ data: {
"OTHERS"
"OUTPUT"
"OVERRIDING"
"OVERWRITE"
"PAD"
"PARAMETER_MODE"
"PARAMETER_NAME"
@@ -384,6 +387,7 @@ data: {
statementParserMethods: [
"DruidSqlInsertEof()"
"DruidSqlExplain()"
"DruidSqlReplaceEof()"
]

# List of methods for parsing custom literals.
@@ -433,8 +437,10 @@ data: {
# given as part of "statementParserMethods", "literalParserMethods" or
# "dataTypeParserMethods".
implementationFiles: [
"common.ftl"
"insert.ftl"
"explain.ftl"
"replace.ftl"
]

includePosixOperators: false
@@ -0,0 +1,74 @@
/*
* 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.
*/

// Using fully qualified name for Pair class, since Calcite also has a same class name being used in the Parser.jj
org.apache.druid.java.util.common.Pair<Granularity, String> PartitionGranularity() :
{
SqlNode e;
Granularity granularity;
String unparseString;
}
{
(
<HOUR>
{
granularity = Granularities.HOUR;
unparseString = "HOUR";
}
|
<DAY>
{
granularity = Granularities.DAY;
unparseString = "DAY";
}
|
<MONTH>
{
granularity = Granularities.MONTH;
unparseString = "MONTH";
}
|
<YEAR>
{
granularity = Granularities.YEAR;
unparseString = "YEAR";
}
|
<ALL>
{
granularity = Granularities.ALL;
unparseString = "ALL";
}
[
<TIME>
{
unparseString += " TIME";
}
]
|
e = Expression(ExprContext.ACCEPT_SUB_QUERY)
{
granularity = DruidSqlParserUtils.convertSqlNodeToGranularityThrowingParseExceptions(e);
unparseString = e.toString();
}
)
{
return new org.apache.druid.java.util.common.Pair(granularity, unparseString);
}
}
@@ -62,6 +62,8 @@ SqlNode DruidQueryOrSqlQueryOrDml() :
{
(
stmt = DruidSqlInsertEof()
|
stmt = DruidSqlReplaceEof()
|
stmt = SqlQueryOrDml()
)
@@ -70,58 +70,3 @@ SqlNodeList ClusterItems() :
return new SqlNodeList(list, s.addAll(list).pos());
}
}

org.apache.druid.java.util.common.Pair<Granularity, String> PartitionGranularity() :
{
SqlNode e = null;
Granularity granularity = null;
String unparseString = null;
}
{
(
<HOUR>
{
granularity = Granularities.HOUR;
unparseString = "HOUR";
}
|
<DAY>
{
granularity = Granularities.DAY;
unparseString = "DAY";
}
|
<MONTH>
{
granularity = Granularities.MONTH;
unparseString = "MONTH";
}
|
<YEAR>
{
granularity = Granularities.YEAR;
unparseString = "YEAR";
}
|
<ALL>
{
granularity = Granularities.ALL;
unparseString = "ALL";
}
[
<TIME>
{
unparseString += " TIME";
}
]
|
e = Expression(ExprContext.ACCEPT_SUB_QUERY)
{
granularity = DruidSqlParserUtils.convertSqlNodeToGranularityThrowingParseExceptions(e);
unparseString = e.toString();
}
)
{
return new org.apache.druid.java.util.common.Pair(granularity, unparseString);
}
}
@@ -0,0 +1,78 @@
/*
* 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.
*/

// Taken from syntax of SqlInsert statement from calcite parser, edited for replace syntax
SqlNode DruidSqlReplaceEof() :
{
SqlNode table;
SqlNode source;
SqlNodeList columnList = null;
final Span s;
SqlInsert sqlInsert;
// Using fully qualified name for Pair class, since Calcite also has a same class name being used in the Parser.jj
org.apache.druid.java.util.common.Pair<Granularity, String> partitionedBy = new org.apache.druid.java.util.common.Pair(null, null);
final Pair<SqlNodeList, SqlNodeList> p;
final SqlNode replaceTimeQuery;
}
{
<REPLACE> { s = span(); }
<INTO>
table = CompoundIdentifier()
[
p = ParenthesizedCompoundIdentifierList() {
if (p.left.size() > 0) {
columnList = p.left;
}
}
]
<OVERWRITE>
replaceTimeQuery = ReplaceTimeQuery()
source = OrderedQueryOrExpr(ExprContext.ACCEPT_QUERY)
// PARTITIONED BY is necessary, but is kept optional in the grammar. It is asserted that it is not missing in the
// DruidSqlInsert constructor so that we can return a custom error message.
[
<PARTITIONED> <BY>
partitionedBy = PartitionGranularity()
]
// EOF is also present in SqlStmtEof but EOF is a special case and a single EOF can be consumed multiple times.
// The reason for adding EOF here is to ensure that we create a DruidSqlReplace node after the syntax has been
// validated and throw SQL syntax errors before performing validations in the DruidSqlReplace which can overshadow the
// actual error message.
<EOF>
{
sqlInsert = new SqlInsert(s.end(source), SqlNodeList.EMPTY, table, source, columnList);
return new DruidSqlReplace(sqlInsert, partitionedBy.lhs, partitionedBy.rhs, replaceTimeQuery);
}
}

SqlNode ReplaceTimeQuery() :
{
SqlNode replaceQuery;
}
{
(
<ALL> { replaceQuery = SqlLiteral.createCharString("ALL", getPos()); }
|
// We parse all types of conditions and throw an exception if it is not supported to keep the parsing simple
replaceQuery = WhereOpt()
)
{
return replaceQuery;
}
}
@@ -43,6 +43,8 @@ public class DruidSqlInsert extends SqlInsert
public static final SqlOperator OPERATOR = SqlInsert.OPERATOR;

private final Granularity partitionedBy;

// Used in the unparse function to generate the original query since we convert the string to an enum
private final String partitionedByStringForUnparse;

@Nullable

0 comments on commit 39b3487

Please sign in to comment.