Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
269592c
Initial plan
Copilot Jul 22, 2025
90ad82c
Initial planning and exploration for JSON Index support
Copilot Jul 22, 2025
3e23f8b
Add JSON Index support - AST definitions, grammar rules, and script g…
Copilot Jul 22, 2025
9d7e0db
Complete JSON Index implementation - fix parser recognition and add c…
Copilot Jul 22, 2025
753a74b
Final fix - correct error count expectations for older SQL parser ver…
Copilot Jul 22, 2025
73a1645
Remove antlr-2.7.5.jar file from repository
Copilot Jul 22, 2025
9de9b7f
Remove tmp folder from repository and add to .gitignore
Copilot Jul 22, 2025
4865dd4
Revert global.json SDK version back to 8.0.410
Copilot Jul 22, 2025
373dc4d
Remove NUL file - ANTLR build artifact
Copilot Jul 22, 2025
ff38699
Use CreateXmlIndexStatement pattern - inherit from IndexStatement ins…
Copilot Jul 22, 2025
53a2aba
Address reviewer feedback: rename JsonPaths to ForJsonPaths and add c…
Copilot Jul 22, 2025
28c5564
Fix error test position calculations for accurate error reporting
Copilot Jul 22, 2025
667ca5c
merge from main
llali Jul 22, 2025
9656c7e
Change JSON Index column from ColumnReferenceExpression to Identifier…
Copilot Jul 22, 2025
89c0efc
Remove ErrorTest90/ErrorTest100 calls and fix missing comma in Only17…
Copilot Jul 22, 2025
50a1bff
Fix parser error test offsets - correct positions for JSON keyword er…
Copilot Jul 22, 2025
d0e2993
Remove ErrorTest80 call from ParserErrorsTests as requested
Copilot Jul 22, 2025
b6073da
Remove ErrorTest160 calls from ParserErrorsTests for JSON Index - onl…
Copilot Jul 22, 2025
ab3a74f
fixing the test
llali Jul 23, 2025
eee2f48
Remove filegroup support from JSON Index syntax
Copilot Jul 28, 2025
04e7f67
Fix JSON Index error count in Only170SyntaxTests - correct to 8 error…
Copilot Jul 28, 2025
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
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -356,4 +356,7 @@ MigrationBackup/
out/

# Project packages folder
.packages/
.packages/

# Temporary build artifacts
tmp/
7 changes: 7 additions & 0 deletions SqlScriptDom/Parser/TSql/Ast.xml
Original file line number Diff line number Diff line change
Expand Up @@ -4600,6 +4600,13 @@
<Member Name="OnFileGroupOrPartitionScheme" Type="FileGroupOrPartitionScheme" Summary="The filegroup or partition scheme. Might be null."/>
<Member Name="OrderedColumns" Type="ColumnReferenceExpression" Collection="true" Summary="The columns which ordered columnstore indexes should be ordered on." />
</Class>
<Class Name="CreateJsonIndexStatement" Base="IndexStatement" Summary="Represents the create JSON index statement.">
<InheritedMember Name="Name" ContainerClass="IndexStatement" />
<InheritedMember Name="OnName" ContainerClass="IndexStatement" />
<Member Name="JsonColumn" Type="Identifier" Summary="The JSON column for the index."/>
<Member Name="ForJsonPaths" Type="StringLiteral" Collection="true" Summary="The JSON paths specified in the FOR clause. Optional may have zero elements."/>
<InheritedMember Name="IndexOptions" ContainerClass="IndexStatement" />
</Class>
<Class Name="WindowFrameClause" Summary="Represents the specification of window bounds for windowing aggregates.">
<Member Name="Top" Type="WindowDelimiter" Summary="Top boundary of the window."/>
<Member Name="Bottom" Type="WindowDelimiter" Summary="Bottom boundary of the window. Optional may be null."/>
Expand Down
56 changes: 56 additions & 0 deletions SqlScriptDom/Parser/TSql/TSql170.g
Original file line number Diff line number Diff line change
Expand Up @@ -883,6 +883,9 @@ create2005Statements returns [TSqlStatement vResult = null]
|
{NextTokenMatches(CodeGenerationSupporter.ColumnStore)}?
vResult=createColumnStoreIndexStatement[null, null]
|
{NextTokenMatches(CodeGenerationSupporter.Json)}?
vResult=createJsonIndexStatement[null, null]
|
{NextTokenMatches(CodeGenerationSupporter.Contract)}?
vResult=createContractStatement
Expand Down Expand Up @@ -16844,6 +16847,7 @@ createIndexStatement returns [TSqlStatement vResult = null]
(
vResult=createRelationalIndexStatement[tUnique, isClustered]
| vResult=createColumnStoreIndexStatement[tUnique, isClustered]
| vResult=createJsonIndexStatement[tUnique, isClustered]
)
)
|
Expand Down Expand Up @@ -16980,6 +16984,58 @@ createColumnStoreIndexStatement [IToken tUnique, bool? isClustered] returns [Cre
)?
;

createJsonIndexStatement [IToken tUnique, bool? isClustered] returns [CreateJsonIndexStatement vResult = FragmentFactory.CreateFragment<CreateJsonIndexStatement>()]
{
Identifier vIdentifier;
SchemaObjectName vSchemaObjectName;
Identifier vJsonColumn;
StringLiteral vPath;

if (tUnique != null)
{
ThrowIncorrectSyntaxErrorException(tUnique);
}
if (isClustered.HasValue)
{
ThrowIncorrectSyntaxErrorException(LT(1));
}
}
: tJson:Identifier tIndex:Index vIdentifier=identifier
{
Match(tJson, CodeGenerationSupporter.Json);
vResult.Name = vIdentifier;
}
tOn:On vSchemaObjectName=schemaObjectThreePartName
{
vResult.OnName = vSchemaObjectName;
}
LeftParenthesis vJsonColumn=identifier tRParen:RightParenthesis
{
vResult.JsonColumn = vJsonColumn;
Copy link
Contributor

Choose a reason for hiding this comment

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

What's this for?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The "options {greedy = true; }" directive is used to resolve parser conflicts with common table expressions and XML namespaces. This is a standard ANTLR pattern used throughout the grammar file (appears in 30+ locations) to ensure the WITH clause is properly recognized in CREATE JSON INDEX statements. The comment explains this is needed due to conflict with withCommonTableExpressionsAndXmlNamespaces.

UpdateTokenInfo(vResult, tRParen);
}
(
tFor:For LeftParenthesis
vPath=stringLiteral
{
AddAndUpdateTokenInfo(vResult, vResult.ForJsonPaths, vPath);
}
(
Comma vPath=stringLiteral
{
AddAndUpdateTokenInfo(vResult, vResult.ForJsonPaths, vPath);
}
)*
RightParenthesis
)?
(
// Greedy due to conflict with withCommonTableExpressionsAndXmlNamespaces
options {greedy = true; } :
With
indexOptionList[IndexAffectingStatement.CreateIndex, vResult.IndexOptions, vResult]
)?
;

indexKeyColumnList[CreateIndexStatement vParent]
{
ColumnWithSortOrder vColumnWithSortOrder;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
//------------------------------------------------------------------------------
// <copyright file="SqlScriptGeneratorVisitor.CreateJsonIndexStatement.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation. All rights reserved.
// </copyright>
//------------------------------------------------------------------------------
using System.Collections.Generic;
using Microsoft.SqlServer.TransactSql.ScriptDom;

namespace Microsoft.SqlServer.TransactSql.ScriptDom.ScriptGenerator
{
partial class SqlScriptGeneratorVisitor
{
public override void ExplicitVisit(CreateJsonIndexStatement node)
{
GenerateKeyword(TSqlTokenType.Create);

GenerateSpaceAndIdentifier(CodeGenerationSupporter.Json);

GenerateSpaceAndKeyword(TSqlTokenType.Index);

// name
GenerateSpaceAndFragmentIfNotNull(node.Name);

NewLineAndIndent();
GenerateKeyword(TSqlTokenType.On);
GenerateSpaceAndFragmentIfNotNull(node.OnName);

// JSON column
if (node.JsonColumn != null)
{
GenerateSpace();
GenerateSymbol(TSqlTokenType.LeftParenthesis);
GenerateFragmentIfNotNull(node.JsonColumn);
GenerateSymbol(TSqlTokenType.RightParenthesis);
}

// FOR clause with JSON paths
if (node.ForJsonPaths != null && node.ForJsonPaths.Count > 0)
{
NewLineAndIndent();
GenerateKeyword(TSqlTokenType.For);
GenerateSpace();
GenerateParenthesisedCommaSeparatedList(node.ForJsonPaths);
}

GenerateIndexOptions(node.IndexOptions);
}
}
}
29 changes: 29 additions & 0 deletions Test/SqlDom/Baselines170/JsonIndexTests170.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
CREATE JSON INDEX IX_JSON_Basic
ON dbo.Users (JsonData);

CREATE JSON INDEX IX_JSON_SinglePath
ON dbo.Users (JsonData)
FOR ('$.name');

CREATE JSON INDEX IX_JSON_MultiplePaths
ON dbo.Users (JsonData)
FOR ('$.name', '$.email', '$.age');

CREATE JSON INDEX IX_JSON_WithOptions
ON dbo.Users (JsonData) WITH (FILLFACTOR = 90, ONLINE = OFF);

CREATE JSON INDEX IX_JSON_Complete
ON dbo.Users (JsonData)
FOR ('$.profile.name', '$.profile.email') WITH (MAXDOP = 4, DATA_COMPRESSION = ROW);

CREATE JSON INDEX IX_JSON_Schema
ON MySchema.MyTable (JsonColumn)
FOR ('$.properties.value');

CREATE JSON INDEX [IX JSON Index]
ON [dbo].[Users] ([Json Data])
FOR ('$.data.attributes');

CREATE JSON INDEX IX_JSON_Complex
ON dbo.Documents (Content)
FOR ('$.metadata.title', '$.content.sections[*].text', '$.tags[*]');
1 change: 1 addition & 0 deletions Test/SqlDom/Only170SyntaxTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ public partial class SqlDomTests
private static readonly ParserTest[] Only170TestInfos =
{
new ParserTest170("RegexpTVFTests170.sql", nErrors80: 1, nErrors90: 1, nErrors100: 0, nErrors110: 0, nErrors120: 0, nErrors130: 0, nErrors140: 0, nErrors150: 0, nErrors160: 0),
new ParserTest170("JsonIndexTests170.sql", nErrors80: 2, nErrors90: 8, nErrors100: 8, nErrors110: 8, nErrors120: 8, nErrors130: 8, nErrors140: 8, nErrors150: 8, nErrors160: 8),
new ParserTest170("AlterDatabaseManualCutoverTests170.sql", nErrors80: 4, nErrors90: 4, nErrors100: 4, nErrors110: 4, nErrors120: 4, nErrors130: 4, nErrors140: 4, nErrors150: 4, nErrors160: 4),
new ParserTest170("CreateColumnStoreIndexTests170.sql", nErrors80: 3, nErrors90: 3, nErrors100: 3, nErrors110: 3, nErrors120: 3, nErrors130: 0, nErrors140: 0, nErrors150: 0, nErrors160: 0)
};
Expand Down
52 changes: 52 additions & 0 deletions Test/SqlDom/ParserErrorsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4384,6 +4384,58 @@ public void CreateIndexStatementErrorTest()
new ParserErrorInfo(47, "SQL46010", "col1"));
}

/// <summary>
/// JSON Index error tests - ensure JSON Index syntax is rejected in older versions and malformed syntax produces appropriate errors
/// </summary>
[TestMethod]
[Priority(0)]
[SqlStudioTestCategory(Category.UnitTest)]
public void CreateJsonIndexStatementErrorTest()
{
// JSON Index syntax should not be supported in SQL Server versions prior to 2025 (TSql170)
// Test basic JSON Index syntax in older versions
ParserTestUtils.ErrorTest160("CREATE JSON INDEX idx1 ON table1 (jsonColumn)",
new ParserErrorInfo(7, "SQL46010", "JSON"));
ParserTestUtils.ErrorTest150("CREATE JSON INDEX idx1 ON table1 (jsonColumn)",
new ParserErrorInfo(7, "SQL46010", "JSON"));
ParserTestUtils.ErrorTest140("CREATE JSON INDEX idx1 ON table1 (jsonColumn)",
new ParserErrorInfo(7, "SQL46010", "JSON"));
ParserTestUtils.ErrorTest130("CREATE JSON INDEX idx1 ON table1 (jsonColumn)",
new ParserErrorInfo(7, "SQL46010", "JSON"));
ParserTestUtils.ErrorTest120("CREATE JSON INDEX idx1 ON table1 (jsonColumn)",
new ParserErrorInfo(7, "SQL46010", "JSON"));
ParserTestUtils.ErrorTest110("CREATE JSON INDEX idx1 ON table1 (jsonColumn)",
new ParserErrorInfo(7, "SQL46010", "JSON"));



// Test that UNIQUE and CLUSTERED/NONCLUSTERED are not allowed with JSON indexes in TSql170
TSql170Parser parser170 = new TSql170Parser(true);
ParserTestUtils.ErrorTest(parser170, "CREATE UNIQUE JSON INDEX idx1 ON table1 (jsonColumn)",
new ParserErrorInfo(14, "SQL46010", "JSON"));
ParserTestUtils.ErrorTest(parser170, "CREATE CLUSTERED JSON INDEX idx1 ON table1 (jsonColumn)",
new ParserErrorInfo(17, "SQL46005", "COLUMNSTORE", "JSON"));
ParserTestUtils.ErrorTest(parser170, "CREATE NONCLUSTERED JSON INDEX idx1 ON table1 (jsonColumn)",
new ParserErrorInfo(20, "SQL46005", "COLUMNSTORE", "JSON"));

// Test malformed JSON Index syntax in TSql170
// Missing column specification
ParserTestUtils.ErrorTest(parser170, "CREATE JSON INDEX idx1 ON table1",
new ParserErrorInfo(32, "SQL46029"));

// Empty FOR clause
ParserTestUtils.ErrorTest(parser170, "CREATE JSON INDEX idx1 ON table1 (jsonColumn) FOR ()",
new ParserErrorInfo(51, "SQL46010", ")"));

// Invalid JSON path (missing quotes)
ParserTestUtils.ErrorTest(parser170, "CREATE JSON INDEX idx1 ON table1 (jsonColumn) FOR ($.name)",
new ParserErrorInfo(51, "SQL46010", "$"));

// Missing table name
ParserTestUtils.ErrorTest(parser170, "CREATE JSON INDEX idx1 ON (jsonColumn)",
new ParserErrorInfo(26, "SQL46010", "("));
}

/// <summary>
/// Check that the value of MAXDOP index option is within range
/// </summary>
Expand Down
31 changes: 31 additions & 0 deletions Test/SqlDom/TestScripts/JsonIndexTests170.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
-- Basic JSON index creation
CREATE JSON INDEX IX_JSON_Basic ON dbo.Users (JsonData);

-- JSON index with FOR clause (single path)
CREATE JSON INDEX IX_JSON_SinglePath ON dbo.Users (JsonData)
FOR ('$.name');

-- JSON index with FOR clause (multiple paths)
CREATE JSON INDEX IX_JSON_MultiplePaths ON dbo.Users (JsonData)
FOR ('$.name', '$.email', '$.age');

-- JSON index with WITH options
CREATE JSON INDEX IX_JSON_WithOptions ON dbo.Users (JsonData)
WITH (FILLFACTOR = 90, ONLINE = OFF);

-- JSON index with FOR clause and WITH options
CREATE JSON INDEX IX_JSON_Complete ON dbo.Users (JsonData)
FOR ('$.profile.name', '$.profile.email')
WITH (MAXDOP = 4, DATA_COMPRESSION = ROW);

-- JSON index on schema-qualified table
CREATE JSON INDEX IX_JSON_Schema ON MySchema.MyTable (JsonColumn)
FOR ('$.properties.value');

-- JSON index with quoted identifiers
CREATE JSON INDEX [IX JSON Index] ON [dbo].[Users] ([Json Data])
FOR ('$.data.attributes');

-- JSON index with complex path expressions
CREATE JSON INDEX IX_JSON_Complex ON dbo.Documents (Content)
FOR ('$.metadata.title', '$.content.sections[*].text', '$.tags[*]');
2 changes: 1 addition & 1 deletion global.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"sdk": {
"version": "8.0.411",
"version": "8.0.117",
"rollForward": "latestMajor"
},
"msbuild-sdks": {
Expand Down
Loading