Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 2 additions & 0 deletions SqlScriptDom/Parser/TSql/Ast.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3630,10 +3630,12 @@
<Member Name="Expression" Type="ScalarExpression" Summary="The expression."/>
<Member Name="Percent" Type="bool" Summary="True if PERCENT keyword was used."/>
<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."/>
<Member Name="WithApproximate" Type="bool" Summary="True if WITH APPROXIMATE (or APPROX) keyword was used. Introduced in SQL Server 2025 for approximate query processing."/>
</Class>
<Class Name="OffsetClause" Summary="This class represents an offset/fetch filter, that can be used in select statements for paging the result set.">
<Member Name="OffsetExpression" Type="ScalarExpression" Summary="Expression for number fo rows to skip."/>
<Member Name="FetchExpression" Type="ScalarExpression" Summary="Expression for number fo rows to return."/>
<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."/>
</Class>
<Class Name="UnaryExpression" Base="ScalarExpression" Summary="An expression that has a single expression as child.">
<Member Name="UnaryExpressionType" Type="UnaryExpressionType" GenerateUpdatePositionInfoCall="false" Summary="The type of the expression."/>
Expand Down
2 changes: 2 additions & 0 deletions SqlScriptDom/Parser/TSql/CodeGenerationSupporter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,8 @@ internal static class CodeGenerationSupporter
internal const string Atomic = "ATOMIC";
internal const string Append = "APPEND";
internal const string AppendOnly = "APPEND_ONLY";
internal const string Approximate = "APPROXIMATE";
internal const string Approx = "APPROX";
internal const string Avg = "AVG";
internal const string Attested = "ATTESTED";
internal const string AuditGuid = "AUDIT_GUID";
Expand Down
123 changes: 99 additions & 24 deletions SqlScriptDom/Parser/TSql/TSql170.g
Original file line number Diff line number Diff line change
Expand Up @@ -18389,33 +18389,99 @@ offsetClause returns [OffsetClause vResult = this.FragmentFactory.CreateFragment
ScalarExpression vExpression;
}
:
tOffset:Identifier vExpression = expression
{
Match(tOffset, CodeGenerationSupporter.Offset);
UpdateTokenInfo(vResult,tOffset);
vResult.OffsetExpression = vExpression;
}
tOffsetRowOrRows:Identifier
{
Match(tOffsetRowOrRows, CodeGenerationSupporter.Row, CodeGenerationSupporter.Rows);
UpdateTokenInfo(vResult,tOffsetRowOrRows);
}
(
options {greedy=true;} : //Greedy due to conflict with FETCH statements
tFetch:Fetch tFirstOrNext:Identifier vExpression = expression
// Pattern 1: OFFSET with optional FETCH
tOffset:Identifier vExpression = expression
{
Match(tOffset, CodeGenerationSupporter.Offset);
UpdateTokenInfo(vResult,tOffset);
vResult.OffsetExpression = vExpression;
}
tOffsetRowOrRows:Identifier
{
Match(tOffsetRowOrRows, CodeGenerationSupporter.Row, CodeGenerationSupporter.Rows);
UpdateTokenInfo(vResult,tOffsetRowOrRows);
}
(
options {greedy=true;} : //Greedy due to conflict with FETCH statements
tFetch:Fetch
tFirstOrNext:Identifier
{
// Check if this is APPROXIMATE/APPROX before FIRST/NEXT
if (TryMatch(tFirstOrNext, CodeGenerationSupporter.Approximate) ||
TryMatch(tFirstOrNext, CodeGenerationSupporter.Approx))
{
vResult.WithApproximate = true;
UpdateTokenInfo(vResult,tFirstOrNext);

// Consume the actual FIRST/NEXT token
tFirstOrNext = LT(1);
consume();
Match(tFirstOrNext, CodeGenerationSupporter.First, CodeGenerationSupporter.Next);
UpdateTokenInfo(vResult,tFirstOrNext);
}
else
{
// This is the FIRST/NEXT token directly
Match(tFirstOrNext, CodeGenerationSupporter.First, CodeGenerationSupporter.Next);
UpdateTokenInfo(vResult,tFirstOrNext);
}
}
vExpression = expression
{
vResult.FetchExpression = vExpression;
}
tFetchRowOrRows:Identifier tOnly:Identifier
{
Match(tFetchRowOrRows, CodeGenerationSupporter.Row, CodeGenerationSupporter.Rows);
Match(tOnly, CodeGenerationSupporter.Only);
UpdateTokenInfo(vResult, tOnly);

// Validate: OFFSET cannot be used with FETCH APPROXIMATE
ValidateFetchApproximate(vResult);
}
)?
|
// Pattern 2: Standalone FETCH (APPROXIMATE only in TSql170+)
tFetch2:Fetch
{
UpdateTokenInfo(vResult,tFetch2);
}
tFirstOrNext2:Identifier
{
// Check if this is APPROXIMATE/APPROX before FIRST/NEXT
if (TryMatch(tFirstOrNext2, CodeGenerationSupporter.Approximate) ||
TryMatch(tFirstOrNext2, CodeGenerationSupporter.Approx))
{
vResult.WithApproximate = true;
UpdateTokenInfo(vResult,tFirstOrNext2);

// Consume the actual FIRST/NEXT token
tFirstOrNext2 = LT(1);
consume();
Match(tFirstOrNext2, CodeGenerationSupporter.First, CodeGenerationSupporter.Next);
UpdateTokenInfo(vResult,tFirstOrNext2);
}
else
{
// This is the FIRST/NEXT token directly
Match(tFirstOrNext2, CodeGenerationSupporter.First, CodeGenerationSupporter.Next);
UpdateTokenInfo(vResult,tFirstOrNext2);
}
}
vExpression = expression
{
UpdateTokenInfo(vResult,tFetch);
Match(tFirstOrNext, CodeGenerationSupporter.First, CodeGenerationSupporter.Next);
UpdateTokenInfo(vResult,tFirstOrNext);
vResult.FetchExpression = vExpression;
}
tFetchRowOrRows:Identifier tOnly:Identifier
tFetchRowOrRows2:Identifier tOnly2:Identifier
{
Match(tFetchRowOrRows, CodeGenerationSupporter.Row, CodeGenerationSupporter.Rows);
Match(tOnly, CodeGenerationSupporter.Only);
UpdateTokenInfo(vResult, tOnly);
Match(tFetchRowOrRows2, CodeGenerationSupporter.Row, CodeGenerationSupporter.Rows);
Match(tOnly2, CodeGenerationSupporter.Only);
UpdateTokenInfo(vResult, tOnly2);

// No validation needed for standalone FETCH APPROXIMATE (no OFFSET present)
}
)?
)
;

// This rule corresponds to topn in Sql Server grammar.
Expand Down Expand Up @@ -18447,9 +18513,18 @@ topRowFilter returns [TopRowFilter vResult = this.FragmentFactory.CreateFragment
(
With tId:Identifier
{
Match(tId,CodeGenerationSupporter.Ties);
UpdateTokenInfo(vResult,tId);
vResult.WithTies = true;
if (TryMatch(tId, CodeGenerationSupporter.Approximate) ||
TryMatch(tId, CodeGenerationSupporter.Approx))
{
UpdateTokenInfo(vResult,tId);
vResult.WithApproximate = true;
}
else
{
Match(tId,CodeGenerationSupporter.Ties);
UpdateTokenInfo(vResult,tId);
vResult.WithTies = true;
}
}
)?
;
Expand Down
16 changes: 16 additions & 0 deletions SqlScriptDom/Parser/TSql/TSql170ParserBaseInternal.cs
Original file line number Diff line number Diff line change
Expand Up @@ -109,5 +109,21 @@ protected bool ContainsVectorInLookahead()

return false;
}

/// <summary>
/// Validates that OFFSET is not used with FETCH APPROXIMATE.
/// SQL Server does not support combining OFFSET with FETCH APPROXIMATE.
/// </summary>
/// <param name="offsetClause">The offset clause to validate.</param>
protected void ValidateFetchApproximate(OffsetClause offsetClause)
{
if (offsetClause != null &&
offsetClause.WithApproximate &&
offsetClause.OffsetExpression != null)
{
ThrowParseErrorException("SQL46145", offsetClause,
TSqlParserResource.SQL46145Message);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -321,7 +321,7 @@ private List<TSqlParserToken> Align()
}

// generate aligned token stream
List<TSqlParserToken> tokens = new List<TSqlParserToken>();
List<TSqlParserToken> tokens = new List<TSqlParserToken>(_scriptWriterElements.Count);

Int32 offset = 0;
for (Int32 index = 0; index < _scriptWriterElements.Count; ++index)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,27 @@ public override void ExplicitVisit(OffsetClause node)
AlignmentPoint start = new AlignmentPoint();
MarkAndPushAlignmentPoint(start);

GenerateIdentifier(CodeGenerationSupporter.Offset);
GenerateSpaceAndFragmentIfNotNull(node.OffsetExpression);
GenerateSpaceAndIdentifier(CodeGenerationSupporter.Rows);
if (node.OffsetExpression != null)
{
GenerateIdentifier(CodeGenerationSupporter.Offset);
GenerateSpaceAndFragmentIfNotNull(node.OffsetExpression);
GenerateSpaceAndIdentifier(CodeGenerationSupporter.Rows);
}

if (node.FetchExpression != null)
{
GenerateSpaceAndKeyword(TSqlTokenType.Fetch);
if (node.OffsetExpression != null)
{
GenerateSpace();
}

GenerateKeyword(TSqlTokenType.Fetch);

if (node.WithApproximate)
{
GenerateSpaceAndIdentifier(CodeGenerationSupporter.Approximate);
}

GenerateSpaceAndIdentifier(CodeGenerationSupporter.Next);
GenerateSpaceAndFragmentIfNotNull(node.FetchExpression);
GenerateSpaceAndIdentifier(CodeGenerationSupporter.Rows);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,19 @@ public override void ExplicitVisit(TopRowFilter node)
GenerateSpaceAndKeyword(TSqlTokenType.Percent);
}

if (node.WithTies)
if (node.WithTies || node.WithApproximate)
{
GenerateSpaceAndKeyword(TSqlTokenType.With);
GenerateSpaceAndIdentifier(CodeGenerationSupporter.Ties);

if (node.WithApproximate)
{
GenerateSpaceAndIdentifier(CodeGenerationSupporter.Approximate);
}

if (node.WithTies)
{
GenerateSpaceAndIdentifier(CodeGenerationSupporter.Ties);
}
}
}
}
Expand Down
9 changes: 9 additions & 0 deletions SqlScriptDom/TSqlParserResource.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions SqlScriptDom/TSqlParserResource.resx
Original file line number Diff line number Diff line change
Expand Up @@ -631,4 +631,7 @@
<data name="SQL46144Message" xml:space="preserve">
<value>Required parameter {0} must be provided.</value>
</data>
<data name="SQL46145Message" xml:space="preserve">
<value>OFFSET clause cannot be used with FETCH APPROXIMATE. Use FETCH APPROXIMATE without OFFSET.</value>
</data>
</root>
34 changes: 34 additions & 0 deletions Test/SqlDom/Baselines170/FetchApproximateTests170.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
SELECT OrderID,
CustomerName
FROM Orders
ORDER BY OrderDate
FETCH APPROXIMATE NEXT 20 ROWS ONLY;

SELECT OrderID,
CustomerName
FROM Orders
ORDER BY OrderDate
FETCH APPROXIMATE NEXT 20 ROWS ONLY;

SELECT ProductID,
ProductName
FROM Products
ORDER BY Price
FETCH APPROXIMATE NEXT 15 ROWS ONLY;

DECLARE @take AS INT = 50;

SELECT OrderID,
CustomerName,
OrderDate
FROM Orders
ORDER BY OrderDate
FETCH APPROXIMATE NEXT @take ROWS ONLY;

SELECT TOP 100 *
FROM (SELECT OrderID,
CustomerName
FROM Orders
ORDER BY OrderDate
FETCH APPROXIMATE NEXT 1000 ROWS ONLY) AS RecentOrders
ORDER BY OrderID;
27 changes: 27 additions & 0 deletions Test/SqlDom/Baselines170/TopWithApproximateTests170.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
SELECT TOP 10 WITH APPROXIMATE *
FROM Orders
ORDER BY OrderDate;

SELECT TOP 10 WITH APPROXIMATE *
FROM Orders
ORDER BY OrderDate;

SELECT TOP (10) WITH APPROXIMATE *
FROM Orders
ORDER BY OrderDate;

SELECT TOP (LEN(DB_NAME())) WITH APPROXIMATE *
FROM Orders
ORDER BY OrderDate;

SELECT TOP 5 WITH APPROXIMATE OrderID,
CustomerName,
OrderDate
FROM Orders
ORDER BY OrderDate DESC;

DECLARE @top AS INT = 20;

SELECT TOP (@top) WITH APPROXIMATE *
FROM Products
ORDER BY Price;
55 changes: 55 additions & 0 deletions Test/SqlDom/Baselines170/VectorSearchApproximateTests170.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
SELECT TOP 10 WITH APPROXIMATE qt.qid,
src.id,
ann.distance
FROM QueryTable AS qt CROSS APPLY VECTOR_SEARCH(
TABLE = graphnode AS src,
COLUMN = embedding,
SIMILAR_TO = qt.qembedding,
METRIC = 'euclidean',
TOP_N = 10
) AS ann
ORDER BY ann.distance;


GO
SELECT TOP 10 WITH APPROXIMATE qt.qid,
src.id,
ann.distance
FROM QueryTable AS qt CROSS APPLY VECTOR_SEARCH(
TABLE = graphnode AS src,
COLUMN = embedding,
SIMILAR_TO = qt.qembedding,
METRIC = 'euclidean',
TOP_N = 10
) AS ann
ORDER BY ann.distance;


GO
SELECT qt.qid,
src.id,
ann.distance
FROM QueryTable AS qt CROSS APPLY VECTOR_SEARCH(
TABLE = graphnode AS src,
COLUMN = embedding,
SIMILAR_TO = qt.qembedding,
METRIC = 'euclidean',
TOP_N = 10
) AS ann
ORDER BY ann.distance
FETCH APPROXIMATE NEXT 10 ROWS ONLY;


GO
SELECT qt.qid,
src.id,
ann.distance
FROM QueryTable AS qt CROSS APPLY VECTOR_SEARCH(
TABLE = graphnode AS src,
COLUMN = embedding,
SIMILAR_TO = qt.qembedding,
METRIC = 'euclidean',
TOP_N = 10
) AS ann
ORDER BY ann.distance
FETCH APPROXIMATE NEXT 10 ROWS ONLY;
Loading
Loading