diff --git a/.gitignore b/.gitignore
index c606ba9..997c172 100644
--- a/.gitignore
+++ b/.gitignore
@@ -356,4 +356,7 @@ MigrationBackup/
out/
# Project packages folder
-.packages/
\ No newline at end of file
+.packages/
+
+# Temporary build artifacts
+tmp/
\ No newline at end of file
diff --git a/SqlScriptDom/Parser/TSql/Ast.xml b/SqlScriptDom/Parser/TSql/Ast.xml
index ffeec7c..e5764b2 100644
--- a/SqlScriptDom/Parser/TSql/Ast.xml
+++ b/SqlScriptDom/Parser/TSql/Ast.xml
@@ -4600,6 +4600,13 @@
+
+
+
+
+
+
+
diff --git a/SqlScriptDom/Parser/TSql/TSql170.g b/SqlScriptDom/Parser/TSql/TSql170.g
index b5b4a2b..031bc61 100644
--- a/SqlScriptDom/Parser/TSql/TSql170.g
+++ b/SqlScriptDom/Parser/TSql/TSql170.g
@@ -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
@@ -16844,6 +16847,7 @@ createIndexStatement returns [TSqlStatement vResult = null]
(
vResult=createRelationalIndexStatement[tUnique, isClustered]
| vResult=createColumnStoreIndexStatement[tUnique, isClustered]
+ | vResult=createJsonIndexStatement[tUnique, isClustered]
)
)
|
@@ -16980,6 +16984,58 @@ createColumnStoreIndexStatement [IToken tUnique, bool? isClustered] returns [Cre
)?
;
+createJsonIndexStatement [IToken tUnique, bool? isClustered] returns [CreateJsonIndexStatement vResult = FragmentFactory.CreateFragment()]
+{
+ 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;
+ 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;
diff --git a/SqlScriptDom/ScriptDom/SqlServer/ScriptGenerator/SqlScriptGeneratorVisitor.CreateJsonIndexStatement.cs b/SqlScriptDom/ScriptDom/SqlServer/ScriptGenerator/SqlScriptGeneratorVisitor.CreateJsonIndexStatement.cs
new file mode 100644
index 0000000..e7665dd
--- /dev/null
+++ b/SqlScriptDom/ScriptDom/SqlServer/ScriptGenerator/SqlScriptGeneratorVisitor.CreateJsonIndexStatement.cs
@@ -0,0 +1,49 @@
+//------------------------------------------------------------------------------
+//
+// Copyright (c) Microsoft Corporation. All rights reserved.
+//
+//------------------------------------------------------------------------------
+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);
+ }
+ }
+}
\ No newline at end of file
diff --git a/Test/SqlDom/Baselines170/JsonIndexTests170.sql b/Test/SqlDom/Baselines170/JsonIndexTests170.sql
new file mode 100644
index 0000000..f6010de
--- /dev/null
+++ b/Test/SqlDom/Baselines170/JsonIndexTests170.sql
@@ -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[*]');
\ No newline at end of file
diff --git a/Test/SqlDom/Only170SyntaxTests.cs b/Test/SqlDom/Only170SyntaxTests.cs
index bd84d40..654fcdb 100644
--- a/Test/SqlDom/Only170SyntaxTests.cs
+++ b/Test/SqlDom/Only170SyntaxTests.cs
@@ -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)
};
diff --git a/Test/SqlDom/ParserErrorsTests.cs b/Test/SqlDom/ParserErrorsTests.cs
index 57f6264..f7632b5 100644
--- a/Test/SqlDom/ParserErrorsTests.cs
+++ b/Test/SqlDom/ParserErrorsTests.cs
@@ -4384,6 +4384,58 @@ public void CreateIndexStatementErrorTest()
new ParserErrorInfo(47, "SQL46010", "col1"));
}
+ ///
+ /// JSON Index error tests - ensure JSON Index syntax is rejected in older versions and malformed syntax produces appropriate errors
+ ///
+ [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", "("));
+ }
+
///
/// Check that the value of MAXDOP index option is within range
///
diff --git a/Test/SqlDom/TestScripts/JsonIndexTests170.sql b/Test/SqlDom/TestScripts/JsonIndexTests170.sql
new file mode 100644
index 0000000..2a0f1ad
--- /dev/null
+++ b/Test/SqlDom/TestScripts/JsonIndexTests170.sql
@@ -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[*]');
\ No newline at end of file
diff --git a/global.json b/global.json
index 0e56f42..457d429 100644
--- a/global.json
+++ b/global.json
@@ -1,6 +1,6 @@
{
"sdk": {
- "version": "8.0.411",
+ "version": "8.0.117",
"rollForward": "latestMajor"
},
"msbuild-sdks": {