Skip to content

Commit

Permalink
Support parsing SQL Server open json function (#29721)
Browse files Browse the repository at this point in the history
* Support parsing SQL Server open json function

* Fix spotless check

* Change the traversal object to each
  • Loading branch information
TherChenYang authored Jan 15, 2024
1 parent 453aab3 commit a9cc771
Show file tree
Hide file tree
Showing 11 changed files with 253 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ unreservedWord
| DATA_RETENTION | TEMPORAL_HISTORY_RETENTION | EDITION | MIXED_PAGE_ALLOCATION | DISABLED | ALLOWED | HADR | MULTI_USER | RESTRICTED_USER | SINGLE_USER | OFFLINE | EMERGENCY | SUSPEND | DATE_CORRELATION_OPTIMIZATION
| ELASTIC_POOL | SERVICE_OBJECTIVE | DATABASE_NAME | ALLOW_CONNECTIONS | GEO | NAMED | DATEFIRST | BACKUP_STORAGE_REDUNDANCY | FORCE_FAILOVER_ALLOW_DATA_LOSS | SECONDARY | FAILOVER | DEFAULT_FULLTEXT_LANGUAGE
| DEFAULT_LANGUAGE | INLINE | NESTED_TRIGGERS | TRANSFORM_NOISE_WORDS | TWO_DIGIT_YEAR_CUTOFF | PERSISTENT_LOG_BUFFER | DIRECTORY_NAME | DATEFORMAT | DELAYED_DURABILITY | TRANSFER | SCHEMA | PASSWORD | AUTHORIZATION
| MEMBER | SEARCH | TEXT | SECOND | PRECISION | VIEWS | PROVIDER | COLUMNS | SUBSTRING | RETURNS | SIZE
| MEMBER | SEARCH | TEXT | SECOND | PRECISION | VIEWS | PROVIDER | COLUMNS | SUBSTRING | RETURNS | SIZE | CONTAINS | MONTH
;

databaseName
Expand Down Expand Up @@ -221,12 +221,12 @@ primaryKey

// TODO comb expr
expr
: expr andOperator expr
: booleanPrimary
| expr andOperator expr
| expr orOperator expr
| expr distinctFrom expr
| notOperator expr
| LP_ expr RP_
| booleanPrimary
;

andOperator
Expand Down Expand Up @@ -311,7 +311,7 @@ distinct
;

specialFunction
: castFunction | charFunction | convertFunction
: castFunction | charFunction | convertFunction | openJsonFunction
;

castFunction
Expand All @@ -326,6 +326,18 @@ charFunction
: CHAR LP_ expr (COMMA_ expr)* (USING ignoredIdentifier)? RP_
;

openJsonFunction
: OPENJSON LP_ expr (COMMA_ expr)? RP_ openJsonWithclause?
;

openJsonWithclause
: WITH LP_ jsonColumnDefinition (COMMA_ jsonColumnDefinition)* RP_
;

jsonColumnDefinition
: columnName dataType expr? (AS JSON)?
;

regularFunction
: regularFunctionName LP_ (expr (COMMA_ expr)* | ASTERISK_)? RP_
;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1942,3 +1942,7 @@ AUTO_DROP
PERSIST_SAMPLE_PERCENT
: P E R S I S T UL_ S A M P L E UL_ P E R C E N T
;

OPENJSON
: O P E N J S O N
;
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@
import org.apache.shardingsphere.sql.parser.autogen.SQLServerStatementParser.InsertExecClauseContext;
import org.apache.shardingsphere.sql.parser.autogen.SQLServerStatementParser.ExecContext;
import org.apache.shardingsphere.sql.parser.autogen.SQLServerStatementParser.ProcedureNameContext;
import org.apache.shardingsphere.sql.parser.autogen.SQLServerStatementParser.OpenJsonFunctionContext;
import org.apache.shardingsphere.sql.parser.autogen.SQLServerStatementParser.TableHintLimitedContext;
import org.apache.shardingsphere.sql.parser.sql.common.enums.AggregationType;
import org.apache.shardingsphere.sql.parser.sql.common.enums.JoinType;
Expand Down Expand Up @@ -207,6 +208,7 @@
import org.apache.shardingsphere.sql.parser.sql.dialect.statement.sqlserver.segment.StatisticsDimension;
import org.apache.shardingsphere.sql.parser.sql.dialect.statement.sqlserver.segment.StatisticsOptionSegment;
import org.apache.shardingsphere.sql.parser.sql.dialect.statement.sqlserver.segment.StatisticsStrategySegment;

import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
Expand Down Expand Up @@ -569,6 +571,9 @@ public final ASTNode visitSimpleExpr(final SimpleExprContext ctx) {
if (null != ctx.columnName()) {
return visit(ctx.columnName());
}
if (null != ctx.LP_() && 1 == ctx.expr().size()) {
return visit(ctx.expr(0));
}
return visitRemainSimpleExpr(ctx);
}

Expand Down Expand Up @@ -653,6 +658,9 @@ public final ASTNode visitSpecialFunction(final SpecialFunctionContext ctx) {
if (null != ctx.charFunction()) {
return visit(ctx.charFunction());
}
if (null != ctx.openJsonFunction()) {
return visit(ctx.openJsonFunction());
}
return new FunctionSegment(ctx.getStart().getStartIndex(), ctx.getStop().getStopIndex(), ctx.getChild(0).getChild(0).getText(), getOriginalText(ctx));
}

Expand Down Expand Up @@ -687,6 +695,15 @@ public final ASTNode visitCharFunction(final CharFunctionContext ctx) {
return new FunctionSegment(ctx.getStart().getStartIndex(), ctx.getStop().getStopIndex(), ctx.CHAR().getText(), getOriginalText(ctx));
}

@Override
public final ASTNode visitOpenJsonFunction(final OpenJsonFunctionContext ctx) {
FunctionSegment result = new FunctionSegment(ctx.getStart().getStartIndex(), ctx.getStop().getStopIndex(), ctx.OPENJSON().getText(), getOriginalText(ctx));
for (ExprContext each : ctx.expr()) {
result.getParameters().add((ExpressionSegment) visit(each));
}
return result;
}

@Override
public final ASTNode visitRegularFunction(final RegularFunctionContext ctx) {
FunctionSegment result = new FunctionSegment(ctx.getStart().getStartIndex(), ctx.getStop().getStopIndex(), ctx.regularFunctionName().getText(), getOriginalText(ctx));
Expand Down
36 changes: 36 additions & 0 deletions test/it/parser/src/main/resources/case/dml/insert.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2952,6 +2952,42 @@
</values>
</insert>

<insert sql-case-id="insert_with_nchar_4">
<table name="@table1" start-index="7" stop-index="13"/>
<columns start-index="15" stop-index="32">
<column name="c2" start-index="16" stop-index="17"/>
<column name="is_transient" start-index="20" stop-index="31"/>
</columns>
<values>
<value>
<assignment-value>
<literal-expression value="sample durable" start-index="42" stop-index="58"/>
</assignment-value>
<assignment-value>
<literal-expression value="0" start-index="61" stop-index="61"/>
</assignment-value>
</value>
</values>
</insert>

<insert sql-case-id="insert_with_nchar_5">
<table name="@table1" start-index="7" stop-index="13"/>
<columns start-index="15" stop-index="32">
<column name="c2" start-index="16" stop-index="17"/>
<column name="is_transient" start-index="20" stop-index="31"/>
</columns>
<values>
<value>
<assignment-value>
<literal-expression value="sample non-durable" start-index="42" stop-index="62"/>
</assignment-value>
<assignment-value>
<literal-expression value="1" start-index="65" stop-index="65"/>
</assignment-value>
</value>
</values>
</insert>

<insert sql-case-id="insert_with_data_base_name">
<table name="VariableTest" start-index="12" stop-index="46">
<owner name="dbo" start-index="31" stop-index="33">
Expand Down
103 changes: 103 additions & 0 deletions test/it/parser/src/main/resources/case/dml/select-join.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1368,4 +1368,107 @@
</expr>
</where>
</select>

<select sql-case-id="select_cross_apply_join_with_open_json_function">
<projections start-index="7" stop-index="339">
<column-projection name="reason" start-index="7" stop-index="12"/>
<column-projection name="score" start-index="15" stop-index="19"/>
<expression-projection text="JSON_VALUE(details, '$.implementationDetails.script')" start-index="22" stop-index="83" alias="script">
<expr>
<function text="JSON_VALUE(details, '$.implementationDetails.script')" function-name="JSON_VALUE" start-index="31" stop-index="83">
<parameter>
<column name="details" start-index="42" stop-index="48"/>
</parameter>
<parameter>
<literal-expression value="$.implementationDetails.script" start-index="51" stop-index="82"/>
</parameter>
</function>
</expr>
</expression-projection>
<shorthand-projection name="*" start-index="86" stop-index="103">
<owner name="planForceDetails" start-index="86" stop-index="101"/>
</shorthand-projection>
<expression-projection start-index="106" stop-index="255" text="regressedPlanExecutionCount + recommendedPlanExecutionCount) * (regressedPlanCpuTimeAverage - recommendedPlanCpuTimeAverage)/100000" alias="estimated_gain">
<expr>
<binary-operation-expression start-index="123" stop-index="255">
<left>
<binary-operation-expression start-index="123" stop-index="247">
<operator>*</operator>
<left>
<binary-operation-expression start-index="124" stop-index="182">
<left>
<column name="regressedPlanExecutionCount" start-index="124" stop-index="150"/>
</left>
<right>
<column name="recommendedPlanExecutionCount" start-index="154" stop-index="182"/>
</right>
<operator>+</operator>
</binary-operation-expression>
</left>
<right>
<binary-operation-expression start-index="188" stop-index="246">
<left>
<column name="regressedPlanCpuTimeAverage" start-index="188" stop-index="214"/>
</left>
<right>
<column name="recommendedPlanCpuTimeAverage" start-index="218" stop-index="246"/>
</right>
<operator>-</operator>
</binary-operation-expression>
</right>
</binary-operation-expression>
</left>
<right>
<literal-expression value="1000000" start-index="249" stop-index="255"/>
</right>
<operator>/</operator>
</binary-operation-expression>
</expr>
</expression-projection>
<expression-projection text="IIF(regressedPlanErrorCount > recommendedPlanErrorCount, 'YES','NO')" start-index="258" stop-index="339" alias="error_prone">
<expr>
<function text="IIF(regressedPlanErrorCount > recommendedPlanErrorCount, 'YES','NO')" start-index="272" stop-index="339" function-name="IIF">
<parameter>
<binary-operation-expression start-index="276" stop-index="326">
<left>
<column name="regressedPlanErrorCount" start-index="276" stop-index="298"/>
</left>
<right>
<column name="recommendedPlanErrorCount" start-index="302" stop-index="326"/>
</right>
<operator>&gt;</operator>
</binary-operation-expression>
</parameter>
<parameter>
<literal-expression value="YES" start-index="329" stop-index="333"/>
</parameter>
<parameter>
<literal-expression value="NO" start-index="335" stop-index="338"/>
</parameter>
</function>
</expr>
</expression-projection>
</projections>
<from start-index="346" stop-index="770">
<join-table join-type="CROSS">
<left>
<simple-table name="dm_db_tuning_recommendations" start-index="346" stop-index="377">
<owner name="sys" start-index="346" stop-index="348"/>
</simple-table>
</left>
<right>
<function-table start-index="391" stop-index="750" table-alias="planForceDetails">
<table-function function-name="OPENJSON" text="OPENJSON (Details, '$.planForceDetails') WITH ([query_id] int '$.queryId', regressedPlanId int '$.regressedPlanId', recommendedPlanId int '$.recommendedPlanId', regressedPlanErrorCount int, recommendedPlanErrorCount int, regressedPlanExecutionCount int, regressedPlanCpuTimeAverage float, recommendedPlanExecutionCount int, recommendedPlanCpuTimeAverage float)">
<parameter>
<column name="Details" start-index="401" stop-index="407"/>
</parameter>
<parameter>
<literal-expression value="$.planForceDetails" start-index="410" stop-index="429"/>
</parameter>
</table-function>
</function-table>
</right>
</join-table>
</from>
</select>
</sql-parser-test-cases>
Original file line number Diff line number Diff line change
Expand Up @@ -629,4 +629,38 @@
<expression-item expression="COUNT(*)" order-direction="DESC" start-index="147" stop-index="154"/>
</order-by>
</select>

<select sql-case-id="select_from_open_json_function">
<projections start-index="7" stop-index="7">
<shorthand-projection start-index="7" stop-index="7"/>
</projections>
<from start-index="14" stop-index="101">
<function-table start-index="14" stop-index="102" table-alias="months">
<table-function text="OPENJSON(@array) WITH ( month VARCHAR(3), temp int, month_id tinyint '$.sql:identity()')" function-name="OPENJSON">
<parameter>
<column name="@array" start-index="23" stop-index="28"/>
</parameter>
</table-function>
</function-table>
</from>
</select>

<select sql-case-id="select_from_open_json_function_with_path">
<projections start-index="7" stop-index="18">
<column-projection name="key" start-index="7" stop-index="11" start-delimiter='[' end-delimiter=']'/>
<column-projection name="value" start-index="14" stop-index="18"/>
</projections>
<from start-index="25" stop-index="64">
<function-table start-index="25" stop-index="64">
<table-function text="OPENJSON(@json,'$.path.to.&quot;sub-object&quot;')" function-name="OPENJSON">
<parameter>
<column name="@json" start-index="34" stop-index="38"/>
</parameter>
<parameter>
<literal-expression value="$.path.to.&quot;sub-object&quot;" start-index="40" stop-index="63"/>
</parameter>
</table-function>
</function-table>
</from>
</select>
</sql-parser-test-cases>
37 changes: 37 additions & 0 deletions test/it/parser/src/main/resources/case/dml/select.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8137,4 +8137,41 @@
</expression-projection>
</projections>
</select>

<select sql-case-id="select_with_contains_function">
<projections start-index="7" stop-index="16">
<column-projection name="product_id" start-index="7" stop-index="16"/>
</projections>
<from start-index="23" stop-index="30">
<simple-table name="products" start-index="23" stop-index="30"/>
</from>
<where start-index="32" stop-index="157">
<expr>
<binary-operation-expression start-index="38" stop-index="157">
<left>
<function text="CONTAINS(product_description, '&quot;Snap Happy 100EZ&quot; OR FORMSOF(THESAURUS,&quot;Snap Happy&quot;) OR &quot;100EZ&quot;')" function-name="CONTAINS" start-index="38" stop-index="134">
<parameter>
<column name="product_description" start-index="47" stop-index="65"/>
</parameter>
<parameter>
<literal-expression value="&quot;Snap Happy 100EZ&quot; OR FORMSOF(THESAURUS,&quot;Snap Happy&quot;) OR &quot;100EZ&quot;" start-index="68" stop-index="133"/>
</parameter>
</function>
</left>
<operator>AND</operator>
<right>
<binary-operation-expression start-index="140" stop-index="157">
<left>
<column name="product_cost" start-index="140" stop-index="151"/>
</left>
<right>
<literal-expression value="200" start-index="155" stop-index="157"/>
</right>
<operator>&lt;</operator>
</binary-operation-expression>
</right>
</binary-operation-expression>
</expr>
</where>
</select>
</sql-parser-test-cases>
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,8 @@
<sql-case id="insert_with_nchar_1" value="INSERT INTO dbo.T1 VALUES (1, N'Natalia')" db-types="SQLServer"/>
<sql-case id="insert_with_nchar_2" value="INSERT INTO dbo.T1 VALUES (2, N'Mark')" db-types="SQLServer"/>
<sql-case id="insert_with_nchar_3" value="INSERT INTO dbo.T1 VALUES (3, N'Randolph')" db-types="SQLServer"/>
<sql-case id="insert_with_nchar_4" value="INSERT @table1 (c2, is_transient) VALUES (N'sample durable', 0)" db-types="SQLServer"/>
<sql-case id="insert_with_nchar_5" value="INSERT @table1 (c2, is_transient) VALUES (N'sample non-durable', 1)" db-types="SQLServer"/>
<sql-case id="insert_with_batch_nchar" value="INSERT INTO TestSchema.Employees (Name, Location) VALUES (N'Jared', N'Australia'), (N'Nikita', N'India'), (N'Tom', N'Germany')" db-types="SQLServer"/>
<sql-case id="insert_with_data_base_name" value="INSERT INTO AdventureWorks2022.dbo.VariableTest(Col1) VALUES('$(tablename)')" db-types="SQLServer"/>
<sql-case id="insert_with_exec" value="INSERT INTO iris_rx_data (&quot;Sepal.Length&quot;, &quot;Sepal.Width&quot;, &quot;Petal.Length&quot;, &quot;Petal.Width&quot; , &quot;Species&quot;) EXECUTE sp_execute_external_script @language = N'R' , @script = N'iris_data &lt;- iris'" db-types="SQLServer"/>
Expand Down
Loading

0 comments on commit a9cc771

Please sign in to comment.