diff --git a/.github/instructions/testing.guidelines.instructions.md b/.github/instructions/testing.guidelines.instructions.md
index ec62811..5b1b477 100644
--- a/.github/instructions/testing.guidelines.instructions.md
+++ b/.github/instructions/testing.guidelines.instructions.md
@@ -321,6 +321,25 @@ Expected: 'SELECT JSON_ARRAY('value1', 'value2');'
**Solution**: Copy the "Actual" output to your baseline file (note spacing differences).
+**CRITICAL CHECK**: After copying baseline, compare it against your test script:
+```sql
+-- Input script (TestScripts/MyTest.sql)
+SELECT * FROM FUNC() WITH (HINT);
+
+-- Generated baseline (Baselines170/MyTest.sql)
+SELECT *
+FROM FUNC();
+-- ⚠️ WHERE IS 'WITH (HINT)'?
+```
+
+**If baseline is missing syntax from input:**
+1. **This is likely a BUG** - not just formatting difference
+2. Check if AST has member to store the missing syntax
+3. Verify grammar stores value: `vResult.PropertyName = vValue;`
+4. Check script generator outputs the value: `GenerateFragmentIfNotNull(node.Property)`
+5. If syntax should be preserved, add AST storage and script generation
+6. Document in spec if intentional omission (e.g., query optimizer hints)
+
#### 2. Error Count Mismatch
```
TestYourFeature.sql: number of errors after parsing is different from expected.
@@ -800,6 +819,38 @@ new ParserTest160("RegressionBugFix12345Tests160.sql", nErrors150: 1), // Bug e
new ParserTest170("RegressionBugFix12345Tests170.sql", nErrors160: 1), // Bug existed in SQL 2022
```
+## Round-Trip Fidelity Validation Checklist
+
+**CRITICAL: After generating baseline files, ALWAYS verify:**
+
+✅ **Input Preservation Check**:
+1. Open test script side-by-side with baseline file
+2. For each SQL statement, verify baseline preserves all syntax from input
+3. Check optional clauses: `WITH`, `WHERE`, `HAVING`, `ORDER BY`, etc.
+4. Check hints: Table hints, query hints, join hints
+5. Check keywords: All keywords from input should appear in baseline (unless documented normalization)
+
+✅ **Missing Syntax Investigation**:
+If baseline omits syntax from input:
+- [ ] Is this intentional keyword normalization? (e.g., APPROX → APPROXIMATE)
+- [ ] Is this a query optimizer hint that doesn't need preservation?
+- [ ] Is this a BUG where AST doesn't store the value?
+
+✅ **Bug Indicators**:
+- ❌ Input: `FUNCTION() WITH (HINT)` → Baseline: `FUNCTION()` = **LIKELY BUG**
+- ❌ Input: `SELECT ... ORDER BY col` → Baseline: `SELECT ...` = **BUG**
+- ✅ Input: `FETCH APPROX` → Baseline: `FETCH APPROXIMATE` = Acceptable normalization
+- ✅ Input: `SELECT /*+ HINT */` → Baseline: `SELECT` = Query hint (document in spec)
+
+✅ **Resolution Steps**:
+1. Check AST definition in `Ast.xml` for member to store value
+2. Verify grammar assigns value: `vResult.Property = vValue;`
+3. Check script generator outputs value: `if (node.Property != null) { ... }`
+4. If missing: Add AST member, update grammar, update script generator, rebuild
+5. Document decision in spec if intentional omission
+
+---
+
## Summary
The SqlScriptDOM testing framework provides comprehensive validation of parser functionality through:
@@ -808,7 +859,11 @@ The SqlScriptDOM testing framework provides comprehensive validation of parser f
- **Cross-version validation** (Test syntax across SQL Server versions)
- **Error condition testing** (Invalid syntax produces expected errors)
- **Exact syntax verification** (Exact T-SQL from user requests is tested precisely)
+- **Round-trip fidelity validation** (Baseline preserves all input syntax unless documented)
Following these guidelines ensures robust test coverage for parser functionality and prevents regressions when adding new features or fixing bugs.
-**Key Principle**: Always test the exact T-SQL syntax provided in user prompts or requests to verify that the specific syntax works as expected, rather than testing generalized or simplified versions of the syntax.
\ No newline at end of file
+**Key Principles**:
+1. Always test the exact T-SQL syntax provided in user prompts or requests
+2. Always verify baseline output preserves input syntax (missing syntax may indicate bugs)
+3. Document any intentional omissions (normalization, query hints) in spec
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
index 997c172..adb0b54 100644
--- a/.gitignore
+++ b/.gitignore
@@ -359,4 +359,15 @@ out/
.packages/
# Temporary build artifacts
-tmp/
\ No newline at end of file
+tmp/
+
+# Speckit files
+speckit.files
+.github/.specify/
+.github/agents/speckit.*.agent.md
+.github/prompts/speckit.*
+
+# Specs directory - ignore all files except spec.md
+specs/**/*
+!specs/**/
+!specs/**/spec.md
diff --git a/SqlScriptDom/Parser/TSql/Ast.xml b/SqlScriptDom/Parser/TSql/Ast.xml
index d17d34e..1976cb1 100644
--- a/SqlScriptDom/Parser/TSql/Ast.xml
+++ b/SqlScriptDom/Parser/TSql/Ast.xml
@@ -4808,5 +4808,6 @@
+
diff --git a/SqlScriptDom/Parser/TSql/CodeGenerationSupporter.cs b/SqlScriptDom/Parser/TSql/CodeGenerationSupporter.cs
index 4f7e5be..4c8a97d 100644
--- a/SqlScriptDom/Parser/TSql/CodeGenerationSupporter.cs
+++ b/SqlScriptDom/Parser/TSql/CodeGenerationSupporter.cs
@@ -75,6 +75,7 @@ internal static class CodeGenerationSupporter
internal const string AnsiWarnings = "ANSI_WARNINGS";
internal const string ForcePlan = "FORCEPLAN";
internal const string ForAppend = "FOR_APPEND";
+ internal const string ForceAnnOnly = "FORCE_ANN_ONLY";
internal const string ShowPlanAll = "SHOWPLAN_ALL";
internal const string ShowPlanText = "SHOWPLAN_TEXT";
internal const string IO = "IO";
diff --git a/SqlScriptDom/Parser/TSql/TSql170.g b/SqlScriptDom/Parser/TSql/TSql170.g
index 85ec9cf..a8a1b49 100644
--- a/SqlScriptDom/Parser/TSql/TSql170.g
+++ b/SqlScriptDom/Parser/TSql/TSql170.g
@@ -19321,19 +19321,35 @@ vectorSearchTableReference returns [VectorSearchTableReference vResult = Fragmen
MatchString(vMetric, CodeGenerationSupporter.Cosine, CodeGenerationSupporter.Dot, CodeGenerationSupporter.Euclidean);
vResult.Metric = vMetric;
}
- Comma tTopN:Identifier EqualsSign vTopN = signedIntegerOrVariableOrColumnReference
- {
- Match(tTopN, CodeGenerationSupporter.TopN);
-
- // Validate that TOP_N is not a negative number
- if (vTopN is UnaryExpression unaryExpr && unaryExpr.UnaryExpressionType == UnaryExpressionType.Negative)
+ // TOP_N is optional per SQL Server 2025 (commit 12d3e8fc)
+ (
+ Comma tTopN:Identifier EqualsSign vTopN = signedIntegerOrVariableOrColumnReference
{
- ThrowParseErrorException("SQL46010", unaryExpr, TSqlParserResource.SQL46010Message, "-");
+ Match(tTopN, CodeGenerationSupporter.TopN);
+
+ // Validate that TOP_N is not a negative number
+ if (vTopN is UnaryExpression unaryExpr && unaryExpr.UnaryExpressionType == UnaryExpressionType.Negative)
+ {
+ ThrowParseErrorException("SQL46010", unaryExpr, TSqlParserResource.SQL46010Message, "-");
+ }
+
+ vResult.TopN = vTopN;
}
-
- vResult.TopN = vTopN;
+ )?
+ tRParen:RightParenthesis
+ {
+ UpdateTokenInfo(vResult, tRParen);
}
- RightParenthesis simpleTableReferenceAliasOpt[vResult]
+ // WITH clause per SQL Server 2025 (commit 12d3e8fc)
+ (
+ With LeftParenthesis tForceAnnOnly:Identifier tRParen2:RightParenthesis
+ {
+ Match(tForceAnnOnly, CodeGenerationSupporter.ForceAnnOnly);
+ UpdateTokenInfo(vResult, tRParen2);
+ vResult.ForceAnnOnly = true;
+ }
+ )?
+ simpleTableReferenceAliasOpt[vResult]
;
predictTableReference[SubDmlFlags subDmlFlags] returns [PredictTableReference vResult]
@@ -33167,6 +33183,7 @@ jsonObjectBuiltInFunctionCall [FunctionCall vParent]
jsonObjectAggBuiltInFunctionCall [FunctionCall vParent]
{
+ OverClause vOverClause;
}
: (
jsonObjectAggExpressionList[vParent]
@@ -33177,6 +33194,12 @@ jsonObjectAggBuiltInFunctionCall [FunctionCall vParent]
{
UpdateTokenInfo(vParent, tRParen);
}
+ (
+ vOverClause=overClauseNoOrderBy
+ {
+ vParent.OverClause = vOverClause;
+ }
+ )?
;
jsonQueryBuiltInFunctionCall [FunctionCall vParent]
diff --git a/SqlScriptDom/Parser/TSql/TSqlFabricDW.g b/SqlScriptDom/Parser/TSql/TSqlFabricDW.g
index 77019ed..21fd33d 100644
--- a/SqlScriptDom/Parser/TSql/TSqlFabricDW.g
+++ b/SqlScriptDom/Parser/TSql/TSqlFabricDW.g
@@ -23761,8 +23761,6 @@ dropSecurityPolicyStatement returns [DropSecurityPolicyStatement vResult = Fragm
createExternalDataSourceStatement returns [CreateExternalDataSourceStatement vResult = FragmentFactory.CreateFragment()]
{
Identifier vName;
- ExternalDataSourceOption vExternalDataSourceOption;
- long encounteredOptions = 0;
vResult.DataSourceType = ExternalDataSourceType.EXTERNAL_GENERICS;
}
: tData:Identifier tSource:Identifier vName = identifier
@@ -23774,42 +23772,8 @@ createExternalDataSourceStatement returns [CreateExternalDataSourceStatement vRe
}
tWith:With LeftParenthesis
- (
- {NextTokenMatches(CodeGenerationSupporter.Type)}?
- externalDataSourceType[vResult]
- |
- {NextTokenMatches(CodeGenerationSupporter.Location)}?
- externalDataSourceLocation[vResult]
- |
- {NextTokenMatches(CodeGenerationSupporter.PushdownOption)}?
- externalDataSourcePushdownOption[vResult]
- |
- vExternalDataSourceOption = externalDataSourceLiteralOrIdentifierOption
- {
- CheckOptionDuplication(ref encounteredOptions, (int)vExternalDataSourceOption.OptionKind, vExternalDataSourceOption);
- AddAndUpdateTokenInfo(vResult, vResult.ExternalDataSourceOptions, vExternalDataSourceOption);
- }
- )
- (
- tComma:Comma
- (
- {NextTokenMatches(CodeGenerationSupporter.Type)}?
- externalDataSourceType[vResult]
- |
- {NextTokenMatches(CodeGenerationSupporter.Location)}?
- externalDataSourceLocation[vResult]
- |
- {NextTokenMatches(CodeGenerationSupporter.PushdownOption)}?
- externalDataSourcePushdownOption[vResult]
- |
- vExternalDataSourceOption = externalDataSourceLiteralOrIdentifierOption
- {
- CheckOptionDuplication(ref encounteredOptions, (int)vExternalDataSourceOption.OptionKind, vExternalDataSourceOption);
- AddAndUpdateTokenInfo(vResult, vResult.ExternalDataSourceOptions, vExternalDataSourceOption);
- }
- )
- )*
+ externalDataSourceLocation[vResult]
tRParen:RightParenthesis
{
@@ -23817,45 +23781,6 @@ createExternalDataSourceStatement returns [CreateExternalDataSourceStatement vRe
}
;
-externalDataSourceType[CreateExternalDataSourceStatement vParent]
- :
- tDataSourceType:Identifier EqualsSign
- {
- Match(tDataSourceType, CodeGenerationSupporter.Type);
- UpdateTokenInfo(vParent, tDataSourceType);
- }
- (
- tExternalDataSourceType:Identifier
- {
- if (TryMatch(tExternalDataSourceType, CodeGenerationSupporter.Hadoop))
- {
- UpdateTokenInfo(vParent, tExternalDataSourceType);
- vParent.DataSourceType = ExternalDataSourceType.HADOOP;
- }
- else if (TryMatch(tExternalDataSourceType, CodeGenerationSupporter.Rdbms))
- {
- UpdateTokenInfo(vParent, tExternalDataSourceType);
- vParent.DataSourceType = ExternalDataSourceType.RDBMS;
- }
- else if (TryMatch(tExternalDataSourceType, CodeGenerationSupporter.ShardMapManager))
- {
- UpdateTokenInfo(vParent, tExternalDataSourceType);
- vParent.DataSourceType = ExternalDataSourceType.SHARD_MAP_MANAGER;
- }
- else if (TryMatch(tExternalDataSourceType, CodeGenerationSupporter.BlobStorage))
- {
- UpdateTokenInfo(vParent, tExternalDataSourceType);
- vParent.DataSourceType = ExternalDataSourceType.BLOB_STORAGE;
- }
- else
- {
- UpdateTokenInfo(vParent, tExternalDataSourceType);
- vParent.DataSourceType = ExternalDataSourceType.EXTERNAL_GENERICS;
- }
- }
- )
- ;
-
externalDataSourceLocation[ExternalDataSourceStatement vParent]
{
Literal vLocation;
@@ -23868,68 +23793,9 @@ externalDataSourceLocation[ExternalDataSourceStatement vParent]
}
;
-externalDataSourcePushdownOption[ExternalDataSourceStatement vParent]
- :
- tPushdownOption:Identifier
- {
- Match(tPushdownOption, CodeGenerationSupporter.PushdownOption);
- UpdateTokenInfo(vParent, tPushdownOption);
- }
- EqualsSign
- (
- tOn:On
- {
- vParent.PushdownOption = ExternalDataSourcePushdownOption.ON;
- UpdateTokenInfo(vParent, tOn);
- }
- | tOff:Off
- {
- vParent.PushdownOption = ExternalDataSourcePushdownOption.OFF;
- UpdateTokenInfo(vParent, tOff);
- }
- )
- ;
-
-externalDataSourceLiteralOrIdentifierOption returns [ExternalDataSourceLiteralOrIdentifierOption vResult = this.FragmentFactory.CreateFragment()]
-{
- Literal vLiteral;
- Identifier vIdentifier;
-}
- :
- tOption:Identifier
- {
- vResult.OptionKind = ExternalDataSourceOptionHelper.Instance.ParseOption(tOption);
- }
- EqualsSign
- (
- vIdentifier = identifier
- {
- if (vResult.OptionKind != ExternalDataSourceOptionKind.Credential)
- {
- throw GetUnexpectedTokenErrorException(tOption);
- }
- vResult.Value = IdentifierOrValueExpression(vIdentifier);
- }
- |
- vLiteral = stringLiteral
- {
- if (vResult.OptionKind != ExternalDataSourceOptionKind.ResourceManagerLocation &&
- vResult.OptionKind != ExternalDataSourceOptionKind.DatabaseName &&
- vResult.OptionKind != ExternalDataSourceOptionKind.ShardMapName &&
- vResult.OptionKind != ExternalDataSourceOptionKind.ConnectionOptions)
- {
- throw GetUnexpectedTokenErrorException(tOption);
- }
- vResult.Value = IdentifierOrValueExpression(vLiteral);
- }
- )
- ;
-
alterExternalDataSourceStatement returns [AlterExternalDataSourceStatement vResult = FragmentFactory.CreateFragment()]
{
Identifier vName;
- ExternalDataSourceOption vExternalDataSourceOption;
- long encounteredOptions = 0;
}
: tData:Identifier tSource:Identifier vName = identifier
{
@@ -23940,36 +23806,8 @@ alterExternalDataSourceStatement returns [AlterExternalDataSourceStatement vResu
}
tSet:Set
- (
- {NextTokenMatches(CodeGenerationSupporter.Location)}?
- externalDataSourceLocation[vResult]
- |
- {NextTokenMatches(CodeGenerationSupporter.PushdownOption)}?
- externalDataSourcePushdownOption[vResult]
- |
- vExternalDataSourceOption = externalDataSourceLiteralOrIdentifierOption
- {
- CheckOptionDuplication(ref encounteredOptions, (int)vExternalDataSourceOption.OptionKind, vExternalDataSourceOption);
- AddAndUpdateTokenInfo(vResult, vResult.ExternalDataSourceOptions, vExternalDataSourceOption);
- }
- )
- (
- tComma:Comma
- (
- {NextTokenMatches(CodeGenerationSupporter.Location)}?
- externalDataSourceLocation[vResult]
- |
- {NextTokenMatches(CodeGenerationSupporter.PushdownOption)}?
- externalDataSourcePushdownOption[vResult]
- |
- vExternalDataSourceOption = externalDataSourceLiteralOrIdentifierOption
- {
- CheckOptionDuplication(ref encounteredOptions, (int)vExternalDataSourceOption.OptionKind, vExternalDataSourceOption);
- AddAndUpdateTokenInfo(vResult, vResult.ExternalDataSourceOptions, vExternalDataSourceOption);
- }
- )
- )*
+ externalDataSourceLocation[vResult]
;
dropExternalDataSourceStatement returns [DropExternalDataSourceStatement vResult = FragmentFactory.CreateFragment()]
diff --git a/SqlScriptDom/ScriptDom/SqlServer/ScriptGenerator/SqlScriptGeneratorVisitor.FunctionCall.cs b/SqlScriptDom/ScriptDom/SqlServer/ScriptGenerator/SqlScriptGeneratorVisitor.FunctionCall.cs
index 70d7111..ffa5099 100644
--- a/SqlScriptDom/ScriptDom/SqlServer/ScriptGenerator/SqlScriptGeneratorVisitor.FunctionCall.cs
+++ b/SqlScriptDom/ScriptDom/SqlServer/ScriptGenerator/SqlScriptGeneratorVisitor.FunctionCall.cs
@@ -70,6 +70,8 @@ public override void ExplicitVisit(FunctionCall node)
GenerateSpace();
GenerateReturnType(node?.ReturnType);
GenerateSymbol(TSqlTokenType.RightParenthesis);
+ // Generate OVER clause for windowed json_objectagg
+ GenerateSpaceAndFragmentIfNotNull(node.OverClause);
}
else if (node.FunctionName.Value.ToUpper(CultureInfo.InvariantCulture) == CodeGenerationSupporter.JsonArray)
{
diff --git a/SqlScriptDom/ScriptDom/SqlServer/ScriptGenerator/SqlScriptGeneratorVisitor.VectorSearchTableReference.cs b/SqlScriptDom/ScriptDom/SqlServer/ScriptGenerator/SqlScriptGeneratorVisitor.VectorSearchTableReference.cs
index 55e4730..09b9aa4 100644
--- a/SqlScriptDom/ScriptDom/SqlServer/ScriptGenerator/SqlScriptGeneratorVisitor.VectorSearchTableReference.cs
+++ b/SqlScriptDom/ScriptDom/SqlServer/ScriptGenerator/SqlScriptGeneratorVisitor.VectorSearchTableReference.cs
@@ -15,9 +15,9 @@ partial class SqlScriptGeneratorVisitor
/// TABLE = object[AS source_table_alias],
/// COLUMN = vector_column,
/// SIMILAR_TO = query_vector,
- /// METRIC = { 'cosine' | 'dot' | 'euclidean' },
- /// TOP_N = k
- /// ) [AS result_table_alias]
+ /// METRIC = { 'cosine' | 'dot' | 'euclidean' }
+ /// [, TOP_N = k]
+ /// ) [WITH (FORCE_ANN_ONLY)] [AS result_table_alias]
///
public override void ExplicitVisit(VectorSearchTableReference node)
{
@@ -41,13 +41,28 @@ public override void ExplicitVisit(VectorSearchTableReference node)
NewLineAndIndent();
GenerateNameEqualsValue(CodeGenerationSupporter.Metric, node.Metric);
- GenerateSymbol(TSqlTokenType.Comma);
-
- NewLineAndIndent();
- GenerateNameEqualsValue(CodeGenerationSupporter.TopN, node.TopN);
+
+ // TOP_N is optional per SQL Server 2025 (commit 12d3e8fc)
+ if (node.TopN != null)
+ {
+ GenerateSymbol(TSqlTokenType.Comma);
+ NewLineAndIndent();
+ GenerateNameEqualsValue(CodeGenerationSupporter.TopN, node.TopN);
+ }
NewLine();
GenerateSymbol(TSqlTokenType.RightParenthesis);
+
+ // WITH (FORCE_ANN_ONLY) hint per SQL Server 2025 (commit 12d3e8fc)
+ if (node.ForceAnnOnly)
+ {
+ GenerateSpaceAndKeyword(TSqlTokenType.With);
+ GenerateSpace();
+ GenerateSymbol(TSqlTokenType.LeftParenthesis);
+ GenerateIdentifier(CodeGenerationSupporter.ForceAnnOnly);
+ GenerateSymbol(TSqlTokenType.RightParenthesis);
+ }
+
GenerateSpaceAndAlias(node.Alias);
PopAlignmentPoint();
diff --git a/Test/SqlDom/Baselines170/JsonObjectAggOverClause170.sql b/Test/SqlDom/Baselines170/JsonObjectAggOverClause170.sql
new file mode 100644
index 0000000..8aa7b2e
--- /dev/null
+++ b/Test/SqlDom/Baselines170/JsonObjectAggOverClause170.sql
@@ -0,0 +1,11 @@
+SELECT JSON_OBJECTAGG(CustomerName:OrderDate) OVER (PARTITION BY OrderDate)
+FROM Customers;
+
+SELECT JSON_OBJECTAGG(CustomerName:OrderDate ABSENT ON NULL) OVER (PARTITION BY OrderDate)
+FROM Customers;
+
+SELECT JSON_OBJECTAGG(CustomerName:OrderDate NULL ON NULL) OVER (PARTITION BY OrderDate)
+FROM Customers;
+
+SELECT JSON_OBJECTAGG(CustomerName:OrderDate NULL ON NULL RETURNING JSON) OVER (PARTITION BY OrderDate)
+FROM Customers;
\ No newline at end of file
diff --git a/Test/SqlDom/Baselines170/VectorSearchOptionalTopNTests170.sql b/Test/SqlDom/Baselines170/VectorSearchOptionalTopNTests170.sql
new file mode 100644
index 0000000..6d31f39
--- /dev/null
+++ b/Test/SqlDom/Baselines170/VectorSearchOptionalTopNTests170.sql
@@ -0,0 +1,33 @@
+SELECT *
+FROM VECTOR_SEARCH(
+ TABLE = graphnode,
+ COLUMN = embedding,
+ SIMILAR_TO = @qembedding,
+ METRIC = 'euclidean'
+ );
+
+SELECT *
+FROM VECTOR_SEARCH(
+ TABLE = graphnode,
+ COLUMN = embedding,
+ SIMILAR_TO = @qembedding,
+ METRIC = 'euclidean'
+ ) WITH (FORCE_ANN_ONLY);
+
+SELECT *
+FROM VECTOR_SEARCH(
+ TABLE = graphnode,
+ COLUMN = embedding,
+ SIMILAR_TO = @qembedding,
+ METRIC = 'euclidean',
+ TOP_N = 20
+ );
+
+SELECT *
+FROM VECTOR_SEARCH(
+ TABLE = graphnode,
+ COLUMN = embedding,
+ SIMILAR_TO = @qembedding,
+ METRIC = 'euclidean',
+ TOP_N = 20
+ ) WITH (FORCE_ANN_ONLY);
diff --git a/Test/SqlDom/BaselinesFabricDW/CreateAlterExternalDataSourceTestsFabricDW.sql b/Test/SqlDom/BaselinesFabricDW/CreateAlterExternalDataSourceTestsFabricDW.sql
new file mode 100644
index 0000000..fb3aef4
--- /dev/null
+++ b/Test/SqlDom/BaselinesFabricDW/CreateAlterExternalDataSourceTestsFabricDW.sql
@@ -0,0 +1,11 @@
+CREATE EXTERNAL DATA SOURCE MyDataSource
+ WITH (
+ LOCATION = 'sqlserver://10.10.10.10:1433'
+ );
+
+CREATE EXTERNAL DATA SOURCE BlobSource
+ WITH (
+ LOCATION = 'wasbs://container@account.blob.core.windows.net'
+ );
+
+ALTER EXTERNAL DATA SOURCE MyDataSource SET LOCATION = 'sqlserver://192.168.1.100:1433';
\ No newline at end of file
diff --git a/Test/SqlDom/Only170SyntaxTests.cs b/Test/SqlDom/Only170SyntaxTests.cs
index a881c8e..12478fd 100644
--- a/Test/SqlDom/Only170SyntaxTests.cs
+++ b/Test/SqlDom/Only170SyntaxTests.cs
@@ -15,12 +15,14 @@ public partial class SqlDomTests
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: 10, nErrors100: 10, nErrors110: 10, nErrors120: 10, nErrors130: 10, nErrors140: 10, nErrors150: 10, nErrors160: 10),
new ParserTest170("VectorIndexTests170.sql", nErrors80: 2, nErrors90: 12, nErrors100: 12, nErrors110: 12, nErrors120: 12, nErrors130: 12, nErrors140: 12, nErrors150: 12, nErrors160: 12),
+ new ParserTest170("VectorSearchOptionalTopNTests170.sql"),
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),
new ParserTest170("RegexpTests170.sql", nErrors80: 0, nErrors90: 0, nErrors100: 0, nErrors110: 0, nErrors120: 0, nErrors130: 0, nErrors140: 0, nErrors150: 0, nErrors160: 0),
new ParserTest170("AiGenerateChunksTests170.sql", nErrors80: 19, nErrors90: 16, nErrors100: 15, nErrors110: 15, nErrors120: 15, nErrors130: 15, nErrors140: 15, nErrors150: 15, nErrors160: 15),
new ParserTest170("JsonFunctionTests170.sql", nErrors80: 29, nErrors90: 8, nErrors100: 54, nErrors110: 54, nErrors120: 54, nErrors130: 54, nErrors140: 54, nErrors150: 54, nErrors160: 54),
new ParserTest170("JsonArrayAggOrderBy170.sql", nErrors80: 10, nErrors90: 9, nErrors100: 9, nErrors110: 9, nErrors120: 9, nErrors130: 9, nErrors140: 9, nErrors150: 9, nErrors160: 9),
+ new ParserTest170("JsonObjectAggOverClause170.sql", nErrors80: 4, nErrors90: 4, nErrors100: 4, nErrors110: 4, nErrors120: 4, nErrors130: 4, nErrors140: 4, nErrors150: 4, nErrors160: 4),
new ParserTest170("ComplexJsonObjectFunctionTests170.sql"),
new ParserTest170("AiGenerateEmbeddingsTests170.sql", nErrors80: 14, nErrors90: 11, nErrors100: 11, nErrors110: 11, nErrors120: 11, nErrors130: 11, nErrors140: 11, nErrors150: 11, nErrors160: 11),
new ParserTest170("CreateExternalModelStatementTests170.sql", nErrors80: 2, nErrors90: 2, nErrors100: 2, nErrors110: 2, nErrors120: 2, nErrors130: 4, nErrors140: 4, nErrors150: 4, nErrors160: 4),
diff --git a/Test/SqlDom/OnlyFabricDWSyntaxTests.cs b/Test/SqlDom/OnlyFabricDWSyntaxTests.cs
index 5bee5e2..a2eaab8 100644
--- a/Test/SqlDom/OnlyFabricDWSyntaxTests.cs
+++ b/Test/SqlDom/OnlyFabricDWSyntaxTests.cs
@@ -10,6 +10,7 @@ public partial class SqlDomTests
private static readonly ParserTest[] OnlyFabricDWTestInfos =
{
new ParserTestFabricDW("CloneTableTestsFabricDW.sql", nErrors80: 4, nErrors90: 4, nErrors100: 4, nErrors110: 4, nErrors120: 4, nErrors130: 4, nErrors140: 4, nErrors150: 4, nErrors160: 4, nErrors170: 4),
+ new ParserTestFabricDW("CreateAlterExternalDataSourceTestsFabricDW.sql", nErrors80: 2, nErrors90: 2, nErrors100: 2, nErrors110: 2, nErrors120: 2, nErrors130: 0, nErrors140: 0, nErrors150: 0, nErrors160: 0, nErrors170: 0),
new ParserTestFabricDW("CreateAlterTableClusterByTestsFabricDW.sql", nErrors80: 6, nErrors90: 6, nErrors100: 6, nErrors110: 6, nErrors120: 6, nErrors130: 6, nErrors140: 6, nErrors150: 6, nErrors160: 6, nErrors170: 6),
new ParserTestFabricDW("CreateExternalTableStatementTestsFabricDW.sql", nErrors80: 2, nErrors90: 2, nErrors100: 2, nErrors110: 2, nErrors120: 2, nErrors130: 2, nErrors140: 2, nErrors150: 2, nErrors160: 0, nErrors170: 0),
new ParserTestFabricDW("CreateProcedureCloneTableTestsFabricDW.sql", nErrors80: 4, nErrors90: 4, nErrors100: 4, nErrors110: 4, nErrors120: 4, nErrors130: 4, nErrors140: 4, nErrors150: 4, nErrors160: 4, nErrors170: 4),
@@ -23,7 +24,7 @@ public partial class SqlDomTests
new ParserTestFabricDW("AiFixGrammarTestsFabricDW.sql", nErrors80: 2, nErrors90: 1, nErrors100: 2, nErrors110: 2, nErrors120: 2, nErrors130: 0, nErrors140: 0, nErrors150: 0, nErrors160: 0, nErrors170: 0),
new ParserTestFabricDW("AiGenerateResponseTestsFabricDW.sql", nErrors80: 3, nErrors90: 1, nErrors100: 2, nErrors110: 2, nErrors120: 2, nErrors130: 0, nErrors140: 0, nErrors150: 0, nErrors160: 0, nErrors170: 0),
new ParserTestFabricDW("AiSummarizeTestsFabricDW.sql", nErrors80: 2, nErrors90: 1, nErrors100: 2, nErrors110: 2, nErrors120: 2, nErrors130: 0, nErrors140: 0, nErrors150: 0, nErrors160: 0, nErrors170: 0),
- new ParserTestFabricDW("AiTranslateTestsFabricDW.sql", nErrors80: 3, nErrors90: 1, nErrors100: 2, nErrors110: 2, nErrors120: 2, nErrors130: 0, nErrors140: 0, nErrors150: 0, nErrors160: 0, nErrors170: 0),
+ new ParserTestFabricDW("AiTranslateTestsFabricDW.sql", nErrors80: 3, nErrors90: 1, nErrors100: 2, nErrors110: 2, nErrors120: 2, nErrors130: 0, nErrors140: 0, nErrors150: 0, nErrors160: 0, nErrors170: 0)
};
[TestMethod]
diff --git a/Test/SqlDom/ParserErrorsTests.cs b/Test/SqlDom/ParserErrorsTests.cs
index 5a89afe..34b0a92 100644
--- a/Test/SqlDom/ParserErrorsTests.cs
+++ b/Test/SqlDom/ParserErrorsTests.cs
@@ -5236,6 +5236,30 @@ public void CreateExternalDataSourceNegativeTest()
ParserTestUtils.ErrorTest130("CREATE EXTERNAL DATA SOURCE eds1 WITH (TYPE = RDBMS, LOCATION = someServer, DATABASE_NAME = 'someDatabase', CREDENTIAL = someCred)", new ParserErrorInfo(64, "SQL46010", "someServer"));
ParserTestUtils.ErrorTest130("CREATE EXTERNAL DATA SOURCE eds1 WITH (TYPE = RDBMS, LOCATION = 'someServer', DATABASE_NAME = someDatabase, CREDENTIAL = someCred)", new ParserErrorInfo(78, "SQL46010", "DATABASE_NAME"));
ParserTestUtils.ErrorTest130("CREATE EXTERNAL DATA SOURCE eds1 WITH (TYPE = RDBMS, LOCATION = 'someServer', DATABASE_NAME = 'someDatabase', CREDENTIAL = 'someCred')", new ParserErrorInfo(110, "SQL46010", "CREDENTIAL"));
+
+ // CREATE with TYPE parameter (no longer supported — parser expects LOCATION)
+ //
+ ParserTestUtils.ErrorTestFabricDW("CREATE EXTERNAL DATA SOURCE eds1 WITH (TYPE = HADOOP, LOCATION = 'protocol://ip_address:port')", new ParserErrorInfo(46, "SQL46010", "HADOOP"));
+
+ // CREATE with extra CREDENTIAL option after LOCATION (only LOCATION allowed, no additional options)
+ //
+ ParserTestUtils.ErrorTestFabricDW("CREATE EXTERNAL DATA SOURCE eds1 WITH (LOCATION = 'protocol://ip_address:port', CREDENTIAL = cred1)", new ParserErrorInfo(78, "SQL46010", ","));
+
+ // CREATE with extra PUSHDOWN option after LOCATION
+ //
+ ParserTestUtils.ErrorTestFabricDW("CREATE EXTERNAL DATA SOURCE eds1 WITH (LOCATION = 'protocol://ip_address:port', PUSHDOWN = ON)", new ParserErrorInfo(78, "SQL46010", ","));
+
+ // CREATE with extra RESOURCE_MANAGER_LOCATION option after LOCATION
+ //
+ ParserTestUtils.ErrorTestFabricDW("CREATE EXTERNAL DATA SOURCE eds1 WITH (LOCATION = 'protocol://ip_address:port', RESOURCE_MANAGER_LOCATION = 'ip_address:port')", new ParserErrorInfo(78, "SQL46010", ","));
+
+ // CREATE with extra DATABASE_NAME option after LOCATION
+ //
+ ParserTestUtils.ErrorTestFabricDW("CREATE EXTERNAL DATA SOURCE eds1 WITH (LOCATION = 'someServer', DATABASE_NAME = 'someDatabase')", new ParserErrorInfo(62, "SQL46010", ","));
+
+ // CREATE with extra CONNECTION_OPTIONS after LOCATION
+ //
+ ParserTestUtils.ErrorTestFabricDW("CREATE EXTERNAL DATA SOURCE eds1 WITH (LOCATION = 'protocol://ip_address:port', CONNECTION_OPTIONS = 'some_options')", new ParserErrorInfo(78, "SQL46010", ","));
}
@@ -5295,6 +5319,24 @@ public void AlterExternalDataSourceNegativeTest()
ParserTestUtils.ErrorTest130("ALTER EXTERNAL DATA SOURCE eds1 SET LOCATION = 'someServer', DATABASE_NAME = someDatabase, SHARD_MAP_NAME = 'someShardMap', CREDENTIAL = someCred", new ParserErrorInfo(61, "SQL46010", "DATABASE_NAME"));
ParserTestUtils.ErrorTest130("ALTER EXTERNAL DATA SOURCE eds1 SET LOCATION = 'someServer', DATABASE_NAME = 'someDatabase', SHARD_MAP_NAME = someShardMap, CREDENTIAL = someCred", new ParserErrorInfo(93, "SQL46010", "SHARD_MAP_NAME"));
ParserTestUtils.ErrorTest130("ALTER EXTERNAL DATA SOURCE eds1 SET LOCATION = 'someServer', DATABASE_NAME = 'someDatabase', SHARD_MAP_NAME = 'someShardMap', CREDENTIAL = 'someCred'", new ParserErrorInfo(126, "SQL46010", "CREDENTIAL"));
+
+ // ALTER with unsupported option instead of LOCATION
+ //
+ ParserTestUtils.ErrorTestFabricDW("ALTER EXTERNAL DATA SOURCE eds1 SET RESOURCE_MANAGER_LOCATION = 'ip_address:port'", new ParserErrorInfo(36, "SQL46005", "LOCATION", "RESOURCE_MANAGER_LOCATION"));
+ ParserTestUtils.ErrorTestFabricDW("ALTER EXTERNAL DATA SOURCE eds1 SET DATABASE_NAME = 'someDb'", new ParserErrorInfo(36, "SQL46005", "LOCATION", "DATABASE_NAME"));
+ ParserTestUtils.ErrorTestFabricDW("ALTER EXTERNAL DATA SOURCE eds1 SET CONNECTION_OPTIONS = 'some_options'", new ParserErrorInfo(36, "SQL46005", "LOCATION", "CONNECTION_OPTIONS"));
+ ParserTestUtils.ErrorTestFabricDW("ALTER EXTERNAL DATA SOURCE eds1 SET SHARD_MAP_NAME = 'someShardMap'", new ParserErrorInfo(36, "SQL46005", "LOCATION", "SHARD_MAP_NAME"));
+
+ // ALTER with unsupported option where the value is not a string literal
+ //
+ ParserTestUtils.ErrorTestFabricDW("ALTER EXTERNAL DATA SOURCE eds1 SET PUSHDOWN = ON", new ParserErrorInfo(47, "SQL46010", "ON"));
+ ParserTestUtils.ErrorTestFabricDW("ALTER EXTERNAL DATA SOURCE eds1 SET CREDENTIAL = cred1", new ParserErrorInfo(49, "SQL46010", "cred1"));
+
+ // ALTER with LOCATION followed by extra unsupported options (comma not expected after LOCATION value)
+ //
+ ParserTestUtils.ErrorTestFabricDW("ALTER EXTERNAL DATA SOURCE eds1 SET LOCATION = 'sqlserver://10.10.10.10:1433', PUSHDOWN = ON", new ParserErrorInfo(77, "SQL46010", ","));
+ ParserTestUtils.ErrorTestFabricDW("ALTER EXTERNAL DATA SOURCE eds1 SET LOCATION = 'sqlserver://10.10.10.10:1433', CREDENTIAL = cred1", new ParserErrorInfo(77, "SQL46010", ","));
+ ParserTestUtils.ErrorTestFabricDW("ALTER EXTERNAL DATA SOURCE eds1 SET LOCATION = 'sqlserver://10.10.10.10:1433', DATABASE_NAME = 'someDb'", new ParserErrorInfo(77, "SQL46010", ","));
}
@@ -7059,6 +7101,7 @@ RETURNS NVARCHAR(101)
RETURN @first + ' ' + @last
END;";
ParserTestUtils.ErrorTestFabricDW(scalarFunctionSyntax2, new ParserErrorInfo(scalarFunctionSyntax2.IndexOf("INLINE"), "SQL46010", "INLINE"));
+
string scalarFunctionSyntax3 = @"CREATE OR ALTER FUNCTION dbo.CountProducts
(
@ProductTable AS dbo.ProductType READONLY
@@ -7097,11 +7140,22 @@ FROM @SalesData
[SqlStudioTestCategory(Category.UnitTest)]
public void IdentityColumnNegativeTestsFabricDW()
{
- string identityColumnSyntax = @"CREATE TABLE TestTable1 (ID INT IDENTITY(1,1), Name VARCHAR(50));";
- ParserTestUtils.ErrorTestFabricDW(identityColumnSyntax, new ParserErrorInfo(40, "SQL46010", "("));
-
- string identityColumnSyntax2 = @"CREATE TABLE TestTable2 (RecordID BIGINT IDENTITY(100,5), Description NVARCHAR(200));";
- ParserTestUtils.ErrorTestFabricDW(identityColumnSyntax2, new ParserErrorInfo(49, "SQL46010", "("));
+ string identityColumnSyntax = @"CREATE TABLE TestTable1 (
+ ID INT IDENTITY(1,1),
+ Name VARCHAR(50)
+ );
+ ";
+ string token = "IDENTITY";
+ int errorOffSet = identityColumnSyntax.IndexOf(token) + token.Length;
+ ParserTestUtils.ErrorTestFabricDW(identityColumnSyntax, new ParserErrorInfo(errorOffSet, "SQL46010", "("));
+
+ string identityColumnSyntax2 = @"CREATE TABLE TestTable2 (
+ RecordID BIGINT IDENTITY(100,5),
+ Description NVARCHAR(200)
+ );
+ ";
+ errorOffSet = identityColumnSyntax2.IndexOf(token) + token.Length;
+ ParserTestUtils.ErrorTestFabricDW(identityColumnSyntax2, new ParserErrorInfo(errorOffSet, "SQL46010", "("));
}
///
@@ -7445,10 +7499,9 @@ public void VectorSearchErrorTest170()
"SELECT * FROM VECTOR_SEARCH(TABLE = tbl1, COLUMN = col1, SIMILAR_TO = query_vector)",
new ParserErrorInfo(82, "SQL46010", ")"));
- // Missing required parameters: TOP_N
- ParserTestUtils.ErrorTest170(
- "SELECT * FROM VECTOR_SEARCH(TABLE = tbl1, COLUMN = col1, SIMILAR_TO = query_vector, METRIC = 'dot')",
- new ParserErrorInfo(98, "SQL46010", ")"));
+ // TOP_N is now OPTIONAL per SQL Server 2025 (commit 12d3e8fc)
+ // The following test case has been removed as it tested for TOP_N being mandatory
+ // which is no longer the case. VECTOR_SEARCH now accepts queries without TOP_N.
// Invalid order: COLUMN before TABLE
ParserTestUtils.ErrorTest170(
diff --git a/Test/SqlDom/TestScripts/CreateAlterExternalDataSourceTestsFabricDW.sql b/Test/SqlDom/TestScripts/CreateAlterExternalDataSourceTestsFabricDW.sql
new file mode 100644
index 0000000..fb3aef4
--- /dev/null
+++ b/Test/SqlDom/TestScripts/CreateAlterExternalDataSourceTestsFabricDW.sql
@@ -0,0 +1,11 @@
+CREATE EXTERNAL DATA SOURCE MyDataSource
+ WITH (
+ LOCATION = 'sqlserver://10.10.10.10:1433'
+ );
+
+CREATE EXTERNAL DATA SOURCE BlobSource
+ WITH (
+ LOCATION = 'wasbs://container@account.blob.core.windows.net'
+ );
+
+ALTER EXTERNAL DATA SOURCE MyDataSource SET LOCATION = 'sqlserver://192.168.1.100:1433';
\ No newline at end of file
diff --git a/Test/SqlDom/TestScripts/JsonObjectAggOverClause170.sql b/Test/SqlDom/TestScripts/JsonObjectAggOverClause170.sql
new file mode 100644
index 0000000..16edf83
--- /dev/null
+++ b/Test/SqlDom/TestScripts/JsonObjectAggOverClause170.sql
@@ -0,0 +1,11 @@
+-- JSON_OBJECTAGG with OVER clause (PARTITION BY)
+SELECT JSON_OBJECTAGG(CustomerName:OrderDate) OVER (PARTITION BY OrderDate) FROM Customers;
+
+-- JSON_OBJECTAGG with ABSENT ON NULL and OVER clause
+SELECT JSON_OBJECTAGG(CustomerName:OrderDate ABSENT ON NULL) OVER (PARTITION BY OrderDate) FROM Customers;
+
+-- JSON_OBJECTAGG with NULL ON NULL and OVER clause
+SELECT JSON_OBJECTAGG(CustomerName:OrderDate NULL ON NULL) OVER (PARTITION BY OrderDate) FROM Customers;
+
+-- JSON_OBJECTAGG with NULL ON NULL, RETURNING JSON, and OVER clause
+SELECT JSON_OBJECTAGG(CustomerName:OrderDate NULL ON NULL RETURNING JSON) OVER (PARTITION BY OrderDate) FROM Customers;
\ No newline at end of file
diff --git a/Test/SqlDom/TestScripts/VectorSearchOptionalTopNTests170.sql b/Test/SqlDom/TestScripts/VectorSearchOptionalTopNTests170.sql
new file mode 100644
index 0000000..e6ebb71
--- /dev/null
+++ b/Test/SqlDom/TestScripts/VectorSearchOptionalTopNTests170.sql
@@ -0,0 +1,33 @@
+-- Test 1: VECTOR_SEARCH without TOP_N (validates optional parameter)
+SELECT * FROM VECTOR_SEARCH(
+ TABLE = graphnode,
+ COLUMN = embedding,
+ SIMILAR_TO = @qembedding,
+ METRIC = 'euclidean'
+);
+
+-- Test 2: VECTOR_SEARCH without TOP_N + WITH (FORCE_ANN_ONLY) (validates both changes)
+SELECT * FROM VECTOR_SEARCH(
+ TABLE = graphnode,
+ COLUMN = embedding,
+ SIMILAR_TO = @qembedding,
+ METRIC = 'euclidean'
+) WITH (FORCE_ANN_ONLY);
+
+-- Test 3: VECTOR_SEARCH with TOP_N (validates backward compatibility)
+SELECT * FROM VECTOR_SEARCH(
+ TABLE = graphnode,
+ COLUMN = embedding,
+ SIMILAR_TO = @qembedding,
+ METRIC = 'euclidean',
+ TOP_N = 20
+);
+
+-- Test 4: VECTOR_SEARCH with TOP_N + WITH (FORCE_ANN_ONLY) (validates both features together)
+SELECT * FROM VECTOR_SEARCH(
+ TABLE = graphnode,
+ COLUMN = embedding,
+ SIMILAR_TO = @qembedding,
+ METRIC = 'euclidean',
+ TOP_N = 20
+) WITH (FORCE_ANN_ONLY);
diff --git a/global.json b/global.json
index b2e90b5..f64b7fb 100644
--- a/global.json
+++ b/global.json
@@ -1,6 +1,6 @@
{
"sdk": {
- "version": "8.0.415",
+ "version": "8.0.418",
"rollForward": "latestMajor"
},
"msbuild-sdks": {
diff --git a/release-notes/170/170.179.0.md b/release-notes/170/170.179.0.md
new file mode 100644
index 0000000..42788f0
--- /dev/null
+++ b/release-notes/170/170.179.0.md
@@ -0,0 +1,28 @@
+# Release Notes
+
+## Microsoft.SqlServer.TransactSql.ScriptDom 170.179.0
+This update brings the following changes over the previous release:
+
+### Target Platform Support
+
+* .NET Framework 4.7.2 (Windows x86, Windows x64)
+* .NET 8 (Windows x86, Windows x64, Linux, macOS)
+* .NET Standard 2.0+ (Windows x86, Windows x64, Linux, macOS)
+
+### Dependencies
+* Updates .NET SDK to latest patch version 8.0.418
+
+#### .NET Framework
+#### .NET Core
+
+### New Features
+* Adds JSON_OBJECTAGG support for OVER clause (windowed aggregate usage).
+* Simplifies CREATE/ALTER EXTERNAL DATA SOURCE syntax for Fabric DW to only support the LOCATION parameter, removes support for TYPE, PUSHDOWN, and other literal/identifier options in the Fabric DW parser.
+
+### Fixed
+* Fixes VECTOR_SEARCH TOP_N parameter to be optional.
+* Fixes VECTOR_SEARCH to support WITH (FORCE_ANN_ONLY) query hint.
+
+### Changes
+
+### Known Issues