Skip to content

Commit d503232

Browse files
authored
Sync Repos: Release 170.191 (#193)
* Fix allocation performance issue in ScriptWriter token list initialization * Add SQL Server 2025 TOP WITH APPROXIMATE and FETCH APPROXIMATE support * Adding release notes for 170.191.0
1 parent a54bcd5 commit d503232

18 files changed

+474
-31
lines changed

SqlScriptDom/Parser/TSql/Ast.xml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3630,10 +3630,12 @@
36303630
<Member Name="Expression" Type="ScalarExpression" Summary="The expression."/>
36313631
<Member Name="Percent" Type="bool" Summary="True if PERCENT keyword was used."/>
36323632
<Member Name="WithTies" Type="bool" Summary="True is WITH TIES keywords were used. Important: The interpreter has to check there is an order by clause."/>
3633+
<Member Name="WithApproximate" Type="bool" Summary="True if WITH APPROXIMATE (or APPROX) keyword was used. Introduced in SQL Server 2025 for approximate query processing."/>
36333634
</Class>
36343635
<Class Name="OffsetClause" Summary="This class represents an offset/fetch filter, that can be used in select statements for paging the result set.">
36353636
<Member Name="OffsetExpression" Type="ScalarExpression" Summary="Expression for number fo rows to skip."/>
36363637
<Member Name="FetchExpression" Type="ScalarExpression" Summary="Expression for number fo rows to return."/>
3638+
<Member Name="WithApproximate" Type="bool" Summary="True if FETCH APPROXIMATE (or APPROX) keyword was used. Only valid in SQL Server 2025 (170) and above. Note: APPROXIMATE is not valid with OFFSET; only FETCH APPROXIMATE is supported."/>
36373639
</Class>
36383640
<Class Name="UnaryExpression" Base="ScalarExpression" Summary="An expression that has a single expression as child.">
36393641
<Member Name="UnaryExpressionType" Type="UnaryExpressionType" GenerateUpdatePositionInfoCall="false" Summary="The type of the expression."/>

SqlScriptDom/Parser/TSql/CodeGenerationSupporter.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,8 @@ internal static class CodeGenerationSupporter
143143
internal const string Atomic = "ATOMIC";
144144
internal const string Append = "APPEND";
145145
internal const string AppendOnly = "APPEND_ONLY";
146+
internal const string Approximate = "APPROXIMATE";
147+
internal const string Approx = "APPROX";
146148
internal const string Avg = "AVG";
147149
internal const string Attested = "ATTESTED";
148150
internal const string AuditGuid = "AUDIT_GUID";

SqlScriptDom/Parser/TSql/TSql170.g

Lines changed: 99 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -18389,33 +18389,99 @@ offsetClause returns [OffsetClause vResult = this.FragmentFactory.CreateFragment
1838918389
ScalarExpression vExpression;
1839018390
}
1839118391
:
18392-
tOffset:Identifier vExpression = expression
18393-
{
18394-
Match(tOffset, CodeGenerationSupporter.Offset);
18395-
UpdateTokenInfo(vResult,tOffset);
18396-
vResult.OffsetExpression = vExpression;
18397-
}
18398-
tOffsetRowOrRows:Identifier
18399-
{
18400-
Match(tOffsetRowOrRows, CodeGenerationSupporter.Row, CodeGenerationSupporter.Rows);
18401-
UpdateTokenInfo(vResult,tOffsetRowOrRows);
18402-
}
1840318392
(
18404-
options {greedy=true;} : //Greedy due to conflict with FETCH statements
18405-
tFetch:Fetch tFirstOrNext:Identifier vExpression = expression
18393+
// Pattern 1: OFFSET with optional FETCH
18394+
tOffset:Identifier vExpression = expression
18395+
{
18396+
Match(tOffset, CodeGenerationSupporter.Offset);
18397+
UpdateTokenInfo(vResult,tOffset);
18398+
vResult.OffsetExpression = vExpression;
18399+
}
18400+
tOffsetRowOrRows:Identifier
18401+
{
18402+
Match(tOffsetRowOrRows, CodeGenerationSupporter.Row, CodeGenerationSupporter.Rows);
18403+
UpdateTokenInfo(vResult,tOffsetRowOrRows);
18404+
}
18405+
(
18406+
options {greedy=true;} : //Greedy due to conflict with FETCH statements
18407+
tFetch:Fetch
18408+
tFirstOrNext:Identifier
18409+
{
18410+
// Check if this is APPROXIMATE/APPROX before FIRST/NEXT
18411+
if (TryMatch(tFirstOrNext, CodeGenerationSupporter.Approximate) ||
18412+
TryMatch(tFirstOrNext, CodeGenerationSupporter.Approx))
18413+
{
18414+
vResult.WithApproximate = true;
18415+
UpdateTokenInfo(vResult,tFirstOrNext);
18416+
18417+
// Consume the actual FIRST/NEXT token
18418+
tFirstOrNext = LT(1);
18419+
consume();
18420+
Match(tFirstOrNext, CodeGenerationSupporter.First, CodeGenerationSupporter.Next);
18421+
UpdateTokenInfo(vResult,tFirstOrNext);
18422+
}
18423+
else
18424+
{
18425+
// This is the FIRST/NEXT token directly
18426+
Match(tFirstOrNext, CodeGenerationSupporter.First, CodeGenerationSupporter.Next);
18427+
UpdateTokenInfo(vResult,tFirstOrNext);
18428+
}
18429+
}
18430+
vExpression = expression
18431+
{
18432+
vResult.FetchExpression = vExpression;
18433+
}
18434+
tFetchRowOrRows:Identifier tOnly:Identifier
18435+
{
18436+
Match(tFetchRowOrRows, CodeGenerationSupporter.Row, CodeGenerationSupporter.Rows);
18437+
Match(tOnly, CodeGenerationSupporter.Only);
18438+
UpdateTokenInfo(vResult, tOnly);
18439+
18440+
// Validate: OFFSET cannot be used with FETCH APPROXIMATE
18441+
ValidateFetchApproximate(vResult);
18442+
}
18443+
)?
18444+
|
18445+
// Pattern 2: Standalone FETCH (APPROXIMATE only in TSql170+)
18446+
tFetch2:Fetch
18447+
{
18448+
UpdateTokenInfo(vResult,tFetch2);
18449+
}
18450+
tFirstOrNext2:Identifier
18451+
{
18452+
// Check if this is APPROXIMATE/APPROX before FIRST/NEXT
18453+
if (TryMatch(tFirstOrNext2, CodeGenerationSupporter.Approximate) ||
18454+
TryMatch(tFirstOrNext2, CodeGenerationSupporter.Approx))
18455+
{
18456+
vResult.WithApproximate = true;
18457+
UpdateTokenInfo(vResult,tFirstOrNext2);
18458+
18459+
// Consume the actual FIRST/NEXT token
18460+
tFirstOrNext2 = LT(1);
18461+
consume();
18462+
Match(tFirstOrNext2, CodeGenerationSupporter.First, CodeGenerationSupporter.Next);
18463+
UpdateTokenInfo(vResult,tFirstOrNext2);
18464+
}
18465+
else
18466+
{
18467+
// This is the FIRST/NEXT token directly
18468+
Match(tFirstOrNext2, CodeGenerationSupporter.First, CodeGenerationSupporter.Next);
18469+
UpdateTokenInfo(vResult,tFirstOrNext2);
18470+
}
18471+
}
18472+
vExpression = expression
1840618473
{
18407-
UpdateTokenInfo(vResult,tFetch);
18408-
Match(tFirstOrNext, CodeGenerationSupporter.First, CodeGenerationSupporter.Next);
18409-
UpdateTokenInfo(vResult,tFirstOrNext);
1841018474
vResult.FetchExpression = vExpression;
1841118475
}
18412-
tFetchRowOrRows:Identifier tOnly:Identifier
18476+
tFetchRowOrRows2:Identifier tOnly2:Identifier
1841318477
{
18414-
Match(tFetchRowOrRows, CodeGenerationSupporter.Row, CodeGenerationSupporter.Rows);
18415-
Match(tOnly, CodeGenerationSupporter.Only);
18416-
UpdateTokenInfo(vResult, tOnly);
18478+
Match(tFetchRowOrRows2, CodeGenerationSupporter.Row, CodeGenerationSupporter.Rows);
18479+
Match(tOnly2, CodeGenerationSupporter.Only);
18480+
UpdateTokenInfo(vResult, tOnly2);
18481+
18482+
// No validation needed for standalone FETCH APPROXIMATE (no OFFSET present)
1841718483
}
18418-
)?
18484+
)
1841918485
;
1842018486

1842118487
// This rule corresponds to topn in Sql Server grammar.
@@ -18447,9 +18513,18 @@ topRowFilter returns [TopRowFilter vResult = this.FragmentFactory.CreateFragment
1844718513
(
1844818514
With tId:Identifier
1844918515
{
18450-
Match(tId,CodeGenerationSupporter.Ties);
18451-
UpdateTokenInfo(vResult,tId);
18452-
vResult.WithTies = true;
18516+
if (TryMatch(tId, CodeGenerationSupporter.Approximate) ||
18517+
TryMatch(tId, CodeGenerationSupporter.Approx))
18518+
{
18519+
UpdateTokenInfo(vResult,tId);
18520+
vResult.WithApproximate = true;
18521+
}
18522+
else
18523+
{
18524+
Match(tId,CodeGenerationSupporter.Ties);
18525+
UpdateTokenInfo(vResult,tId);
18526+
vResult.WithTies = true;
18527+
}
1845318528
}
1845418529
)?
1845518530
;

SqlScriptDom/Parser/TSql/TSql170ParserBaseInternal.cs

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,5 +109,21 @@ protected bool ContainsVectorInLookahead()
109109

110110
return false;
111111
}
112+
113+
/// <summary>
114+
/// Validates that OFFSET is not used with FETCH APPROXIMATE.
115+
/// SQL Server does not support combining OFFSET with FETCH APPROXIMATE.
116+
/// </summary>
117+
/// <param name="offsetClause">The offset clause to validate.</param>
118+
protected void ValidateFetchApproximate(OffsetClause offsetClause)
119+
{
120+
if (offsetClause != null &&
121+
offsetClause.WithApproximate &&
122+
offsetClause.OffsetExpression != null)
123+
{
124+
ThrowParseErrorException("SQL46145", offsetClause,
125+
TSqlParserResource.SQL46145Message);
126+
}
127+
}
112128
}
113129
}

SqlScriptDom/ScriptDom/SqlServer/ScriptGenerator/ScriptWriter.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,7 @@ private List<TSqlParserToken> Align()
321321
}
322322

323323
// generate aligned token stream
324-
List<TSqlParserToken> tokens = new List<TSqlParserToken>();
324+
List<TSqlParserToken> tokens = new List<TSqlParserToken>(_scriptWriterElements.Count);
325325

326326
Int32 offset = 0;
327327
for (Int32 index = 0; index < _scriptWriterElements.Count; ++index)

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

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,27 @@ public override void ExplicitVisit(OffsetClause node)
1414
AlignmentPoint start = new AlignmentPoint();
1515
MarkAndPushAlignmentPoint(start);
1616

17-
GenerateIdentifier(CodeGenerationSupporter.Offset);
18-
GenerateSpaceAndFragmentIfNotNull(node.OffsetExpression);
19-
GenerateSpaceAndIdentifier(CodeGenerationSupporter.Rows);
17+
if (node.OffsetExpression != null)
18+
{
19+
GenerateIdentifier(CodeGenerationSupporter.Offset);
20+
GenerateSpaceAndFragmentIfNotNull(node.OffsetExpression);
21+
GenerateSpaceAndIdentifier(CodeGenerationSupporter.Rows);
22+
}
2023

2124
if (node.FetchExpression != null)
2225
{
23-
GenerateSpaceAndKeyword(TSqlTokenType.Fetch);
26+
if (node.OffsetExpression != null)
27+
{
28+
GenerateSpace();
29+
}
30+
31+
GenerateKeyword(TSqlTokenType.Fetch);
32+
33+
if (node.WithApproximate)
34+
{
35+
GenerateSpaceAndIdentifier(CodeGenerationSupporter.Approximate);
36+
}
37+
2438
GenerateSpaceAndIdentifier(CodeGenerationSupporter.Next);
2539
GenerateSpaceAndFragmentIfNotNull(node.FetchExpression);
2640
GenerateSpaceAndIdentifier(CodeGenerationSupporter.Rows);

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

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,19 @@ public override void ExplicitVisit(TopRowFilter node)
1919
GenerateSpaceAndKeyword(TSqlTokenType.Percent);
2020
}
2121

22-
if (node.WithTies)
22+
if (node.WithTies || node.WithApproximate)
2323
{
2424
GenerateSpaceAndKeyword(TSqlTokenType.With);
25-
GenerateSpaceAndIdentifier(CodeGenerationSupporter.Ties);
25+
26+
if (node.WithApproximate)
27+
{
28+
GenerateSpaceAndIdentifier(CodeGenerationSupporter.Approximate);
29+
}
30+
31+
if (node.WithTies)
32+
{
33+
GenerateSpaceAndIdentifier(CodeGenerationSupporter.Ties);
34+
}
2635
}
2736
}
2837
}

SqlScriptDom/TSqlParserResource.Designer.cs

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

SqlScriptDom/TSqlParserResource.resx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -631,4 +631,7 @@
631631
<data name="SQL46144Message" xml:space="preserve">
632632
<value>Required parameter {0} must be provided.</value>
633633
</data>
634+
<data name="SQL46145Message" xml:space="preserve">
635+
<value>OFFSET clause cannot be used with FETCH APPROXIMATE. Use FETCH APPROXIMATE without OFFSET.</value>
636+
</data>
634637
</root>
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
SELECT OrderID,
2+
CustomerName
3+
FROM Orders
4+
ORDER BY OrderDate
5+
FETCH APPROXIMATE NEXT 20 ROWS ONLY;
6+
7+
SELECT OrderID,
8+
CustomerName
9+
FROM Orders
10+
ORDER BY OrderDate
11+
FETCH APPROXIMATE NEXT 20 ROWS ONLY;
12+
13+
SELECT ProductID,
14+
ProductName
15+
FROM Products
16+
ORDER BY Price
17+
FETCH APPROXIMATE NEXT 15 ROWS ONLY;
18+
19+
DECLARE @take AS INT = 50;
20+
21+
SELECT OrderID,
22+
CustomerName,
23+
OrderDate
24+
FROM Orders
25+
ORDER BY OrderDate
26+
FETCH APPROXIMATE NEXT @take ROWS ONLY;
27+
28+
SELECT TOP 100 *
29+
FROM (SELECT OrderID,
30+
CustomerName
31+
FROM Orders
32+
ORDER BY OrderDate
33+
FETCH APPROXIMATE NEXT 1000 ROWS ONLY) AS RecentOrders
34+
ORDER BY OrderID;

0 commit comments

Comments
 (0)