Skip to content

Commit a54bcd5

Browse files
authored
Sync Repos: Release 170.179.0 (#192)
* JSON_OBJECTAGG support for OVER clause ## Description JSON_OBJECTAGG supports windowed aggregate usage via the OVER (PARTITION BY ...) clause in SQL Server, but ScriptDOM was missing support for parsing and generating it. This PR adds OVER clause support for JSON_OBJECTAGG in the TSql170 parser and script generator. SQL syntax now supported: `sql 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; ` ## Changes - Updated JSON_OBJECTAGG production in TSql170.g to optionally parse an OVER clause - Enhanced SqlScriptGeneratorVisitor.FunctionCall.cs to generate the OVER clause in the output - Added new baseline and test scripts (JsonObjectAggOverClause170.sql in both Baselines170 and TestScripts) ## Additional Information No AST changes were needed -- FunctionCall.OverClause already exists in Ast.xml. The grammar uses overClauseNoOrderBy (not overClause) since, like regular aggregates (e.g., SUM() OVER (...)), the ORDER BY within the OVER clause is not applicable for JSON_OBJECTAGG as a windowed aggregate. All older parsers (80-160) produce 4 errors each (one per test statement) since the JSON_OBJECTAGG key:value syntax is not recognized pre-TSql170. * [SECURITY] Bump dotnet-sdk from 8.0.415 to 8.0.418 * Fix: Make VECTOR_SEARCH TOP_N parameter optional and add WITH (FORCE_ANN_ONLY) support ## Summary Fixes ScriptDOM parser to correctly handle optional `TOP_N` parameter and `WITH (FORCE_ANN_ONLY)` query hint in `VECTOR_SEARCH` syntax, aligning with SQL Server 2025 behavior. ## Changes **Grammar (TSql170.g)** - Made `TOP_N` parameter optional - Added `WITH (FORCE_ANN_ONLY)` clause parsing **AST & Script Generator** - Added `ForceAnnOnly` boolean to `VectorSearchTableReference` - Updated script generator for nullable `TOP_N` and `WITH` clause output - Full round-trip fidelity maintained **Tests** - Added `VectorSearchOptionalTopNTests170.sql` (4 test scenarios) - Removed obsolete error test expecting mandatory `TOP_N` ## Example Syntax Now Supported ```sql -- Without TOP_N SELECT * FROM VECTOR_SEARCH(...) AS ann -- With query hint SELECT * FROM VECTOR_SEARCH(...) WITH (FORCE_ANN_ONLY) AS ann -- With TOP_N (backward compatible) SELECT * FROM VECTOR_SEARCH(..., TOP_N = 10) AS ann ``` Backward compatible - No breaking changes. * Simplifies CREATE/ALTER EXTERNAL DATA SOURCE syntax for Fabric DW Simplifies CREATE/ALTER EXTERNAL DATA SOURCE syntax for Fabric DW to only support the LOCATION parameter, removing support for TYPE, PUSHDOWN, and other literal/ identifier options in the Fabric DW parser. Added positive and negative tests for this based on the specification. * Adding release notes for 170.179.0
1 parent c6e10b2 commit a54bcd5

19 files changed

+334
-194
lines changed

.github/instructions/testing.guidelines.instructions.md

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -321,6 +321,25 @@ Expected: 'SELECT JSON_ARRAY('value1', 'value2');'
321321

322322
**Solution**: Copy the "Actual" output to your baseline file (note spacing differences).
323323

324+
**CRITICAL CHECK**: After copying baseline, compare it against your test script:
325+
```sql
326+
-- Input script (TestScripts/MyTest.sql)
327+
SELECT * FROM FUNC() WITH (HINT);
328+
329+
-- Generated baseline (Baselines170/MyTest.sql)
330+
SELECT *
331+
FROM FUNC();
332+
-- ⚠️ WHERE IS 'WITH (HINT)'?
333+
```
334+
335+
**If baseline is missing syntax from input:**
336+
1. **This is likely a BUG** - not just formatting difference
337+
2. Check if AST has member to store the missing syntax
338+
3. Verify grammar stores value: `vResult.PropertyName = vValue;`
339+
4. Check script generator outputs the value: `GenerateFragmentIfNotNull(node.Property)`
340+
5. If syntax should be preserved, add AST storage and script generation
341+
6. Document in spec if intentional omission (e.g., query optimizer hints)
342+
324343
#### 2. Error Count Mismatch
325344
```
326345
TestYourFeature.sql: number of errors after parsing is different from expected.
@@ -800,6 +819,38 @@ new ParserTest160("RegressionBugFix12345Tests160.sql", nErrors150: 1), // Bug e
800819
new ParserTest170("RegressionBugFix12345Tests170.sql", nErrors160: 1), // Bug existed in SQL 2022
801820
```
802821

822+
## Round-Trip Fidelity Validation Checklist
823+
824+
**CRITICAL: After generating baseline files, ALWAYS verify:**
825+
826+
**Input Preservation Check**:
827+
1. Open test script side-by-side with baseline file
828+
2. For each SQL statement, verify baseline preserves all syntax from input
829+
3. Check optional clauses: `WITH`, `WHERE`, `HAVING`, `ORDER BY`, etc.
830+
4. Check hints: Table hints, query hints, join hints
831+
5. Check keywords: All keywords from input should appear in baseline (unless documented normalization)
832+
833+
**Missing Syntax Investigation**:
834+
If baseline omits syntax from input:
835+
- [ ] Is this intentional keyword normalization? (e.g., APPROX → APPROXIMATE)
836+
- [ ] Is this a query optimizer hint that doesn't need preservation?
837+
- [ ] Is this a BUG where AST doesn't store the value?
838+
839+
**Bug Indicators**:
840+
- ❌ Input: `FUNCTION() WITH (HINT)` → Baseline: `FUNCTION()` = **LIKELY BUG**
841+
- ❌ Input: `SELECT ... ORDER BY col` → Baseline: `SELECT ...` = **BUG**
842+
- ✅ Input: `FETCH APPROX` → Baseline: `FETCH APPROXIMATE` = Acceptable normalization
843+
- ✅ Input: `SELECT /*+ HINT */` → Baseline: `SELECT` = Query hint (document in spec)
844+
845+
**Resolution Steps**:
846+
1. Check AST definition in `Ast.xml` for member to store value
847+
2. Verify grammar assigns value: `vResult.Property = vValue;`
848+
3. Check script generator outputs value: `if (node.Property != null) { ... }`
849+
4. If missing: Add AST member, update grammar, update script generator, rebuild
850+
5. Document decision in spec if intentional omission
851+
852+
---
853+
803854
## Summary
804855

805856
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
808859
- **Cross-version validation** (Test syntax across SQL Server versions)
809860
- **Error condition testing** (Invalid syntax produces expected errors)
810861
- **Exact syntax verification** (Exact T-SQL from user requests is tested precisely)
862+
- **Round-trip fidelity validation** (Baseline preserves all input syntax unless documented)
811863

812864
Following these guidelines ensures robust test coverage for parser functionality and prevents regressions when adding new features or fixing bugs.
813865

814-
**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.
866+
**Key Principles**:
867+
1. Always test the exact T-SQL syntax provided in user prompts or requests
868+
2. Always verify baseline output preserves input syntax (missing syntax may indicate bugs)
869+
3. Document any intentional omissions (normalization, query hints) in spec

.gitignore

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -359,4 +359,15 @@ out/
359359
.packages/
360360

361361
# Temporary build artifacts
362-
tmp/
362+
tmp/
363+
364+
# Speckit files
365+
speckit.files
366+
.github/.specify/
367+
.github/agents/speckit.*.agent.md
368+
.github/prompts/speckit.*
369+
370+
# Specs directory - ignore all files except spec.md
371+
specs/**/*
372+
!specs/**/
373+
!specs/**/spec.md

SqlScriptDom/Parser/TSql/Ast.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4808,5 +4808,6 @@
48084808
<Member Name="SimilarTo" Type="ScalarExpression" Summary="The vector used for search." />
48094809
<Member Name="Metric" Type="StringLiteral" Summary="The distance metric to use for the search." />
48104810
<Member Name="TopN" Type="ScalarExpression" Summary="The maximum number of similar vectors that must be returned." />
4811+
<Member Name="ForceAnnOnly" Type="bool" Summary="Whether the WITH (FORCE_ANN_ONLY) hint is specified." />
48114812
</Class>
48124813
</Types>

SqlScriptDom/Parser/TSql/CodeGenerationSupporter.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ internal static class CodeGenerationSupporter
7575
internal const string AnsiWarnings = "ANSI_WARNINGS";
7676
internal const string ForcePlan = "FORCEPLAN";
7777
internal const string ForAppend = "FOR_APPEND";
78+
internal const string ForceAnnOnly = "FORCE_ANN_ONLY";
7879
internal const string ShowPlanAll = "SHOWPLAN_ALL";
7980
internal const string ShowPlanText = "SHOWPLAN_TEXT";
8081
internal const string IO = "IO";

SqlScriptDom/Parser/TSql/TSql170.g

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -19321,19 +19321,35 @@ vectorSearchTableReference returns [VectorSearchTableReference vResult = Fragmen
1932119321
MatchString(vMetric, CodeGenerationSupporter.Cosine, CodeGenerationSupporter.Dot, CodeGenerationSupporter.Euclidean);
1932219322
vResult.Metric = vMetric;
1932319323
}
19324-
Comma tTopN:Identifier EqualsSign vTopN = signedIntegerOrVariableOrColumnReference
19325-
{
19326-
Match(tTopN, CodeGenerationSupporter.TopN);
19327-
19328-
// Validate that TOP_N is not a negative number
19329-
if (vTopN is UnaryExpression unaryExpr && unaryExpr.UnaryExpressionType == UnaryExpressionType.Negative)
19324+
// TOP_N is optional per SQL Server 2025 (commit 12d3e8fc)
19325+
(
19326+
Comma tTopN:Identifier EqualsSign vTopN = signedIntegerOrVariableOrColumnReference
1933019327
{
19331-
ThrowParseErrorException("SQL46010", unaryExpr, TSqlParserResource.SQL46010Message, "-");
19328+
Match(tTopN, CodeGenerationSupporter.TopN);
19329+
19330+
// Validate that TOP_N is not a negative number
19331+
if (vTopN is UnaryExpression unaryExpr && unaryExpr.UnaryExpressionType == UnaryExpressionType.Negative)
19332+
{
19333+
ThrowParseErrorException("SQL46010", unaryExpr, TSqlParserResource.SQL46010Message, "-");
19334+
}
19335+
19336+
vResult.TopN = vTopN;
1933219337
}
19333-
19334-
vResult.TopN = vTopN;
19338+
)?
19339+
tRParen:RightParenthesis
19340+
{
19341+
UpdateTokenInfo(vResult, tRParen);
1933519342
}
19336-
RightParenthesis simpleTableReferenceAliasOpt[vResult]
19343+
// WITH clause per SQL Server 2025 (commit 12d3e8fc)
19344+
(
19345+
With LeftParenthesis tForceAnnOnly:Identifier tRParen2:RightParenthesis
19346+
{
19347+
Match(tForceAnnOnly, CodeGenerationSupporter.ForceAnnOnly);
19348+
UpdateTokenInfo(vResult, tRParen2);
19349+
vResult.ForceAnnOnly = true;
19350+
}
19351+
)?
19352+
simpleTableReferenceAliasOpt[vResult]
1933719353
;
1933819354

1933919355
predictTableReference[SubDmlFlags subDmlFlags] returns [PredictTableReference vResult]
@@ -33167,6 +33183,7 @@ jsonObjectBuiltInFunctionCall [FunctionCall vParent]
3316733183

3316833184
jsonObjectAggBuiltInFunctionCall [FunctionCall vParent]
3316933185
{
33186+
OverClause vOverClause;
3317033187
}
3317133188
: (
3317233189
jsonObjectAggExpressionList[vParent]
@@ -33177,6 +33194,12 @@ jsonObjectAggBuiltInFunctionCall [FunctionCall vParent]
3317733194
{
3317833195
UpdateTokenInfo(vParent, tRParen);
3317933196
}
33197+
(
33198+
vOverClause=overClauseNoOrderBy
33199+
{
33200+
vParent.OverClause = vOverClause;
33201+
}
33202+
)?
3318033203
;
3318133204

3318233205
jsonQueryBuiltInFunctionCall [FunctionCall vParent]

SqlScriptDom/Parser/TSql/TSqlFabricDW.g

Lines changed: 2 additions & 164 deletions
Original file line numberDiff line numberDiff line change
@@ -23761,8 +23761,6 @@ dropSecurityPolicyStatement returns [DropSecurityPolicyStatement vResult = Fragm
2376123761
createExternalDataSourceStatement returns [CreateExternalDataSourceStatement vResult = FragmentFactory.CreateFragment<CreateExternalDataSourceStatement>()]
2376223762
{
2376323763
Identifier vName;
23764-
ExternalDataSourceOption vExternalDataSourceOption;
23765-
long encounteredOptions = 0;
2376623764
vResult.DataSourceType = ExternalDataSourceType.EXTERNAL_GENERICS;
2376723765
}
2376823766
: tData:Identifier tSource:Identifier vName = identifier
@@ -23774,88 +23772,15 @@ createExternalDataSourceStatement returns [CreateExternalDataSourceStatement vRe
2377423772
}
2377523773

2377623774
tWith:With LeftParenthesis
23777-
(
23778-
{NextTokenMatches(CodeGenerationSupporter.Type)}?
23779-
externalDataSourceType[vResult]
23780-
|
23781-
{NextTokenMatches(CodeGenerationSupporter.Location)}?
23782-
externalDataSourceLocation[vResult]
23783-
|
23784-
{NextTokenMatches(CodeGenerationSupporter.PushdownOption)}?
23785-
externalDataSourcePushdownOption[vResult]
23786-
|
23787-
vExternalDataSourceOption = externalDataSourceLiteralOrIdentifierOption
23788-
{
23789-
CheckOptionDuplication(ref encounteredOptions, (int)vExternalDataSourceOption.OptionKind, vExternalDataSourceOption);
23790-
AddAndUpdateTokenInfo(vResult, vResult.ExternalDataSourceOptions, vExternalDataSourceOption);
23791-
}
23792-
)
2379323775

23794-
(
23795-
tComma:Comma
23796-
(
23797-
{NextTokenMatches(CodeGenerationSupporter.Type)}?
23798-
externalDataSourceType[vResult]
23799-
|
23800-
{NextTokenMatches(CodeGenerationSupporter.Location)}?
23801-
externalDataSourceLocation[vResult]
23802-
|
23803-
{NextTokenMatches(CodeGenerationSupporter.PushdownOption)}?
23804-
externalDataSourcePushdownOption[vResult]
23805-
|
23806-
vExternalDataSourceOption = externalDataSourceLiteralOrIdentifierOption
23807-
{
23808-
CheckOptionDuplication(ref encounteredOptions, (int)vExternalDataSourceOption.OptionKind, vExternalDataSourceOption);
23809-
AddAndUpdateTokenInfo(vResult, vResult.ExternalDataSourceOptions, vExternalDataSourceOption);
23810-
}
23811-
)
23812-
)*
23776+
externalDataSourceLocation[vResult]
2381323777

2381423778
tRParen:RightParenthesis
2381523779
{
2381623780
UpdateTokenInfo(vResult,tRParen);
2381723781
}
2381823782
;
2381923783

23820-
externalDataSourceType[CreateExternalDataSourceStatement vParent]
23821-
:
23822-
tDataSourceType:Identifier EqualsSign
23823-
{
23824-
Match(tDataSourceType, CodeGenerationSupporter.Type);
23825-
UpdateTokenInfo(vParent, tDataSourceType);
23826-
}
23827-
(
23828-
tExternalDataSourceType:Identifier
23829-
{
23830-
if (TryMatch(tExternalDataSourceType, CodeGenerationSupporter.Hadoop))
23831-
{
23832-
UpdateTokenInfo(vParent, tExternalDataSourceType);
23833-
vParent.DataSourceType = ExternalDataSourceType.HADOOP;
23834-
}
23835-
else if (TryMatch(tExternalDataSourceType, CodeGenerationSupporter.Rdbms))
23836-
{
23837-
UpdateTokenInfo(vParent, tExternalDataSourceType);
23838-
vParent.DataSourceType = ExternalDataSourceType.RDBMS;
23839-
}
23840-
else if (TryMatch(tExternalDataSourceType, CodeGenerationSupporter.ShardMapManager))
23841-
{
23842-
UpdateTokenInfo(vParent, tExternalDataSourceType);
23843-
vParent.DataSourceType = ExternalDataSourceType.SHARD_MAP_MANAGER;
23844-
}
23845-
else if (TryMatch(tExternalDataSourceType, CodeGenerationSupporter.BlobStorage))
23846-
{
23847-
UpdateTokenInfo(vParent, tExternalDataSourceType);
23848-
vParent.DataSourceType = ExternalDataSourceType.BLOB_STORAGE;
23849-
}
23850-
else
23851-
{
23852-
UpdateTokenInfo(vParent, tExternalDataSourceType);
23853-
vParent.DataSourceType = ExternalDataSourceType.EXTERNAL_GENERICS;
23854-
}
23855-
}
23856-
)
23857-
;
23858-
2385923784
externalDataSourceLocation[ExternalDataSourceStatement vParent]
2386023785
{
2386123786
Literal vLocation;
@@ -23868,68 +23793,9 @@ externalDataSourceLocation[ExternalDataSourceStatement vParent]
2386823793
}
2386923794
;
2387023795

23871-
externalDataSourcePushdownOption[ExternalDataSourceStatement vParent]
23872-
:
23873-
tPushdownOption:Identifier
23874-
{
23875-
Match(tPushdownOption, CodeGenerationSupporter.PushdownOption);
23876-
UpdateTokenInfo(vParent, tPushdownOption);
23877-
}
23878-
EqualsSign
23879-
(
23880-
tOn:On
23881-
{
23882-
vParent.PushdownOption = ExternalDataSourcePushdownOption.ON;
23883-
UpdateTokenInfo(vParent, tOn);
23884-
}
23885-
| tOff:Off
23886-
{
23887-
vParent.PushdownOption = ExternalDataSourcePushdownOption.OFF;
23888-
UpdateTokenInfo(vParent, tOff);
23889-
}
23890-
)
23891-
;
23892-
23893-
externalDataSourceLiteralOrIdentifierOption returns [ExternalDataSourceLiteralOrIdentifierOption vResult = this.FragmentFactory.CreateFragment<ExternalDataSourceLiteralOrIdentifierOption>()]
23894-
{
23895-
Literal vLiteral;
23896-
Identifier vIdentifier;
23897-
}
23898-
:
23899-
tOption:Identifier
23900-
{
23901-
vResult.OptionKind = ExternalDataSourceOptionHelper.Instance.ParseOption(tOption);
23902-
}
23903-
EqualsSign
23904-
(
23905-
vIdentifier = identifier
23906-
{
23907-
if (vResult.OptionKind != ExternalDataSourceOptionKind.Credential)
23908-
{
23909-
throw GetUnexpectedTokenErrorException(tOption);
23910-
}
23911-
vResult.Value = IdentifierOrValueExpression(vIdentifier);
23912-
}
23913-
|
23914-
vLiteral = stringLiteral
23915-
{
23916-
if (vResult.OptionKind != ExternalDataSourceOptionKind.ResourceManagerLocation &&
23917-
vResult.OptionKind != ExternalDataSourceOptionKind.DatabaseName &&
23918-
vResult.OptionKind != ExternalDataSourceOptionKind.ShardMapName &&
23919-
vResult.OptionKind != ExternalDataSourceOptionKind.ConnectionOptions)
23920-
{
23921-
throw GetUnexpectedTokenErrorException(tOption);
23922-
}
23923-
vResult.Value = IdentifierOrValueExpression(vLiteral);
23924-
}
23925-
)
23926-
;
23927-
2392823796
alterExternalDataSourceStatement returns [AlterExternalDataSourceStatement vResult = FragmentFactory.CreateFragment<AlterExternalDataSourceStatement>()]
2392923797
{
2393023798
Identifier vName;
23931-
ExternalDataSourceOption vExternalDataSourceOption;
23932-
long encounteredOptions = 0;
2393323799
}
2393423800
: tData:Identifier tSource:Identifier vName = identifier
2393523801
{
@@ -23940,36 +23806,8 @@ alterExternalDataSourceStatement returns [AlterExternalDataSourceStatement vResu
2394023806
}
2394123807

2394223808
tSet:Set
23943-
(
23944-
{NextTokenMatches(CodeGenerationSupporter.Location)}?
23945-
externalDataSourceLocation[vResult]
23946-
|
23947-
{NextTokenMatches(CodeGenerationSupporter.PushdownOption)}?
23948-
externalDataSourcePushdownOption[vResult]
23949-
|
23950-
vExternalDataSourceOption = externalDataSourceLiteralOrIdentifierOption
23951-
{
23952-
CheckOptionDuplication(ref encounteredOptions, (int)vExternalDataSourceOption.OptionKind, vExternalDataSourceOption);
23953-
AddAndUpdateTokenInfo(vResult, vResult.ExternalDataSourceOptions, vExternalDataSourceOption);
23954-
}
23955-
)
2395623809

23957-
(
23958-
tComma:Comma
23959-
(
23960-
{NextTokenMatches(CodeGenerationSupporter.Location)}?
23961-
externalDataSourceLocation[vResult]
23962-
|
23963-
{NextTokenMatches(CodeGenerationSupporter.PushdownOption)}?
23964-
externalDataSourcePushdownOption[vResult]
23965-
|
23966-
vExternalDataSourceOption = externalDataSourceLiteralOrIdentifierOption
23967-
{
23968-
CheckOptionDuplication(ref encounteredOptions, (int)vExternalDataSourceOption.OptionKind, vExternalDataSourceOption);
23969-
AddAndUpdateTokenInfo(vResult, vResult.ExternalDataSourceOptions, vExternalDataSourceOption);
23970-
}
23971-
)
23972-
)*
23810+
externalDataSourceLocation[vResult]
2397323811
;
2397423812

2397523813
dropExternalDataSourceStatement returns [DropExternalDataSourceStatement vResult = FragmentFactory.CreateFragment<DropExternalDataSourceStatement>()]

SqlScriptDom/ScriptDom/SqlServer/ScriptGenerator/SqlScriptGeneratorVisitor.FunctionCall.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,8 @@ public override void ExplicitVisit(FunctionCall node)
7070
GenerateSpace();
7171
GenerateReturnType(node?.ReturnType);
7272
GenerateSymbol(TSqlTokenType.RightParenthesis);
73+
// Generate OVER clause for windowed json_objectagg
74+
GenerateSpaceAndFragmentIfNotNull(node.OverClause);
7375
}
7476
else if (node.FunctionName.Value.ToUpper(CultureInfo.InvariantCulture) == CodeGenerationSupporter.JsonArray)
7577
{

0 commit comments

Comments
 (0)