diff --git a/SqlScriptDom/Parser/TSql/TSql170.g b/SqlScriptDom/Parser/TSql/TSql170.g index b793bf5..85ec9cf 100644 --- a/SqlScriptDom/Parser/TSql/TSql170.g +++ b/SqlScriptDom/Parser/TSql/TSql170.g @@ -19306,6 +19306,13 @@ vectorSearchTableReference returns [VectorSearchTableReference vResult = Fragmen Comma tSimilarTo:Identifier EqualsSign vSimilarTo = expression { Match(tSimilarTo, CodeGenerationSupporter.SimilarTo); + + // Validate that SIMILAR_TO does not contain a subquery + if (vSimilarTo is ScalarSubquery) + { + ThrowParseErrorException("SQL46098", vSimilarTo, TSqlParserResource.SQL46098Message); + } + vResult.SimilarTo = vSimilarTo; } Comma tMetric:Identifier EqualsSign vMetric = stringLiteral @@ -33104,6 +33111,7 @@ jsonArrayAggBuiltInFunctionCall [FunctionCall vParent] { ScalarExpression vExpression; OrderByClause vOrderByClause; + OverClause vOverClause; } : ( vExpression=expression @@ -33133,6 +33141,12 @@ jsonArrayAggBuiltInFunctionCall [FunctionCall vParent] { UpdateTokenInfo(vParent, tRParen); } + ( + vOverClause=overClauseNoOrderBy + { + vParent.OverClause = vOverClause; + } + )? ; jsonObjectBuiltInFunctionCall [FunctionCall vParent] @@ -33980,7 +33994,7 @@ vectorSearchColumnReferenceExpression returns [ColumnReferenceExpression vResult MultiPartIdentifier vMultiPartIdentifier; } : - vMultiPartIdentifier=multiPartIdentifier[2] + vMultiPartIdentifier=multiPartIdentifier[4] { vResult.ColumnType = ColumnType.Regular; vResult.MultiPartIdentifier = vMultiPartIdentifier; diff --git a/SqlScriptDom/Parser/TSql/TSql80ParserBaseInternal.cs b/SqlScriptDom/Parser/TSql/TSql80ParserBaseInternal.cs index a17fe59..4f47429 100644 --- a/SqlScriptDom/Parser/TSql/TSql80ParserBaseInternal.cs +++ b/SqlScriptDom/Parser/TSql/TSql80ParserBaseInternal.cs @@ -292,8 +292,9 @@ protected static void AddAndUpdateTokenInfo(TSqlFragment node, IL protected static void AddAndUpdateTokenInfo(TSqlFragment node, IList collection, IList otherCollection) where TFragmentType : TSqlFragment { - foreach (TFragmentType item in otherCollection) + for (int i = 0; i < otherCollection.Count; i++) { + TFragmentType item = otherCollection[i]; AddAndUpdateTokenInfo(node, collection, item); } } diff --git a/SqlScriptDom/ScriptDom/SqlServer/ScriptGenerator/SqlScriptGeneratorVisitor.FunctionCall.cs b/SqlScriptDom/ScriptDom/SqlServer/ScriptGenerator/SqlScriptGeneratorVisitor.FunctionCall.cs index 20de09c..70d7111 100644 --- a/SqlScriptDom/ScriptDom/SqlServer/ScriptGenerator/SqlScriptGeneratorVisitor.FunctionCall.cs +++ b/SqlScriptDom/ScriptDom/SqlServer/ScriptGenerator/SqlScriptGeneratorVisitor.FunctionCall.cs @@ -94,6 +94,8 @@ public override void ExplicitVisit(FunctionCall node) GenerateSpace(); GenerateReturnType(node?.ReturnType); GenerateSymbol(TSqlTokenType.RightParenthesis); + // Generate OVER clause for windowed json_arrayagg + GenerateSpaceAndFragmentIfNotNull(node.OverClause); } else if (node.FunctionName.Value.ToUpper(CultureInfo.InvariantCulture) == CodeGenerationSupporter.JsonQuery) { @@ -115,10 +117,10 @@ public override void ExplicitVisit(FunctionCall node) else if (node.FunctionName.Value.ToUpper(CultureInfo.InvariantCulture) == CodeGenerationSupporter.JsonValue) { GenerateCommaSeparatedList(node.Parameters); - if (node.ReturnType?.Count > 0) //If there are return types then generate space and return type clause - { - GenerateSpace(); - GenerateReturnType(node?.ReturnType); + if (node.ReturnType?.Count > 0) //If there are return types then generate space and return type clause + { + GenerateSpace(); + GenerateReturnType(node?.ReturnType); } GenerateSymbol(TSqlTokenType.RightParenthesis); } diff --git a/Test/SqlDom/Baselines170/JsonArrayAggOrderBy170.sql b/Test/SqlDom/Baselines170/JsonArrayAggOrderBy170.sql index d2ab25f..8d8beef 100644 --- a/Test/SqlDom/Baselines170/JsonArrayAggOrderBy170.sql +++ b/Test/SqlDom/Baselines170/JsonArrayAggOrderBy170.sql @@ -16,4 +16,16 @@ FROM data; SELECT TOP (5) c.object_id, JSON_ARRAYAGG(c.name ORDER BY c.column_id) AS column_list FROM sys.columns AS c -GROUP BY c.object_id; \ No newline at end of file +GROUP BY c.object_id; + +SELECT JSON_ARRAYAGG(name) OVER (PARTITION BY dept) +FROM employees; + +SELECT JSON_ARRAYAGG(name ABSENT ON NULL) OVER (PARTITION BY dept) +FROM employees; + +SELECT JSON_ARRAYAGG(name NULL ON NULL) OVER (PARTITION BY dept) +FROM employees; + +SELECT JSON_ARRAYAGG(name ORDER BY name NULL ON NULL RETURNING JSON) OVER (PARTITION BY dept) +FROM employees; \ No newline at end of file diff --git a/Test/SqlDom/Baselines170/VectorFunctionTests170.sql b/Test/SqlDom/Baselines170/VectorFunctionTests170.sql index 9ab5f85..95e076d 100644 --- a/Test/SqlDom/Baselines170/VectorFunctionTests170.sql +++ b/Test/SqlDom/Baselines170/VectorFunctionTests170.sql @@ -45,4 +45,4 @@ WHERE outerref.id IN (SELECT src.id SIMILAR_TO = @qv, METRIC = 'cosine', TOP_N = outerref.max_results - ) AS ann); \ No newline at end of file + ) AS ann); diff --git a/Test/SqlDom/Baselines170/VectorSearchCrossApplyTests170.sql b/Test/SqlDom/Baselines170/VectorSearchCrossApplyTests170.sql new file mode 100644 index 0000000..3e715b6 --- /dev/null +++ b/Test/SqlDom/Baselines170/VectorSearchCrossApplyTests170.sql @@ -0,0 +1,12 @@ +DECLARE @qv AS VECTOR(1536); + +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 = dbo.qt.top_n + ) AS ann; diff --git a/Test/SqlDom/Only170SyntaxTests.cs b/Test/SqlDom/Only170SyntaxTests.cs index b4c1062..a881c8e 100644 --- a/Test/SqlDom/Only170SyntaxTests.cs +++ b/Test/SqlDom/Only170SyntaxTests.cs @@ -20,7 +20,7 @@ public partial class SqlDomTests 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: 6, nErrors90: 6, nErrors100: 6, nErrors110: 6, nErrors120: 6, nErrors130: 6, nErrors140: 6, nErrors150: 6, nErrors160: 6), + new ParserTest170("JsonArrayAggOrderBy170.sql", nErrors80: 10, nErrors90: 9, nErrors100: 9, nErrors110: 9, nErrors120: 9, nErrors130: 9, nErrors140: 9, nErrors150: 9, nErrors160: 9), 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), @@ -32,6 +32,7 @@ public partial class SqlDomTests new ParserTest170("RegexpLikeTests170.sql", nErrors80: 15, nErrors90: 15, nErrors100: 15, nErrors110: 18, nErrors120: 18, nErrors130: 18, nErrors140: 18, nErrors150: 18, nErrors160: 18), new ParserTest170("OptimizedLockingTests170.sql", nErrors80: 2, nErrors90: 2, nErrors100: 2, nErrors110: 2, nErrors120: 2, nErrors130: 2, nErrors140: 2, nErrors150: 2, nErrors160: 2), new ParserTest170("CreateEventSessionNotLikePredicate.sql", nErrors80: 2, nErrors90: 1, nErrors100: 1, nErrors110: 1, nErrors120: 1, nErrors130: 0, nErrors140: 0, nErrors150: 0, nErrors160: 0), + new ParserTest170("VectorSearchCrossApplyTests170.sql", nErrors80: 1, nErrors90: 1, nErrors100: 1, nErrors110: 1, nErrors120: 1, nErrors130: 1, nErrors140: 1, nErrors150: 1, nErrors160: 1), // Complex query with VECTOR types - parses syntactically in all versions (optimization fix), but VECTOR type only valid in TSql170 new ParserTest170("ComplexQueryTests170.sql"), // Comment preservation tests - basic SQL syntax works in all versions diff --git a/Test/SqlDom/ParserErrorsTests.cs b/Test/SqlDom/ParserErrorsTests.cs index 2204c30..5a89afe 100644 --- a/Test/SqlDom/ParserErrorsTests.cs +++ b/Test/SqlDom/ParserErrorsTests.cs @@ -7544,6 +7544,11 @@ public void VectorSearchErrorTest170() ParserTestUtils.ErrorTest170( "SELECT * FROM VECTOR_SEARCH('tbl1', 'col1', 'query_vector', 'dot', 5)", new ParserErrorInfo(28, "SQL46010", "'tbl1'")); + + // Subquery not allowed in SIMILAR_TO parameter + ParserTestUtils.ErrorTest170( + "SELECT * FROM VECTOR_SEARCH(TABLE = graphnode, COLUMN = embedding, SIMILAR_TO = (SELECT TOP 1 embedding FROM GTQuery), METRIC = 'euclidean', TOP_N = 20) AS ann", + new ParserErrorInfo(80, "SQL46098")); } /// diff --git a/Test/SqlDom/TestScripts/JsonArrayAggOrderBy170.sql b/Test/SqlDom/TestScripts/JsonArrayAggOrderBy170.sql index 094fb21..5062e67 100644 --- a/Test/SqlDom/TestScripts/JsonArrayAggOrderBy170.sql +++ b/Test/SqlDom/TestScripts/JsonArrayAggOrderBy170.sql @@ -18,4 +18,16 @@ SELECT JSON_ARRAYAGG(value ORDER BY value NULL ON NULL) FROM data; -- Real-world example with GROUP BY and system tables SELECT TOP(5) c.object_id, JSON_ARRAYAGG(c.name ORDER BY c.column_id) AS column_list FROM sys.columns AS c -GROUP BY c.object_id; \ No newline at end of file +GROUP BY c.object_id; + +-- JSON_ARRAYAGG with OVER clause (PARTITION BY) +SELECT JSON_ARRAYAGG(name) OVER (PARTITION BY dept) FROM employees; + +-- JSON_ARRAYAGG with ABSENT ON NULL and OVER clause +SELECT JSON_ARRAYAGG(name ABSENT ON NULL) OVER (PARTITION BY dept) FROM employees; + +-- JSON_ARRAYAGG with NULL ON NULL and OVER clause +SELECT JSON_ARRAYAGG(name NULL ON NULL) OVER (PARTITION BY dept) FROM employees; + +-- JSON_ARRAYAGG with ORDER BY, NULL ON NULL, RETURNING JSON, and OVER clause +SELECT JSON_ARRAYAGG(name ORDER BY name NULL ON NULL RETURNING JSON) OVER (PARTITION BY dept) FROM employees; \ No newline at end of file diff --git a/Test/SqlDom/TestScripts/VectorFunctionTests170.sql b/Test/SqlDom/TestScripts/VectorFunctionTests170.sql index a06f083..76795ae 100644 --- a/Test/SqlDom/TestScripts/VectorFunctionTests170.sql +++ b/Test/SqlDom/TestScripts/VectorFunctionTests170.sql @@ -52,4 +52,4 @@ WHERE outerref.id IN ( METRIC = 'cosine', TOP_N = outerref.max_results ) AS ann -) \ No newline at end of file +) diff --git a/Test/SqlDom/TestScripts/VectorSearchCrossApplyTests170.sql b/Test/SqlDom/TestScripts/VectorSearchCrossApplyTests170.sql new file mode 100644 index 0000000..71dfcc5 --- /dev/null +++ b/Test/SqlDom/TestScripts/VectorSearchCrossApplyTests170.sql @@ -0,0 +1,12 @@ +DECLARE @qv VECTOR(1536); + +SELECT qt.qid, src.id, ann.distance +FROM QueryTable qt +CROSS APPLY + VECTOR_SEARCH( + TABLE = graphnode AS src, + COLUMN = embedding, + SIMILAR_TO = qt.qembedding, + METRIC = 'euclidean', + TOP_N = dbo.qt.top_n + ) AS ann diff --git a/release-notes/170/170.168.0.md b/release-notes/170/170.168.0.md new file mode 100644 index 0000000..1f4253b --- /dev/null +++ b/release-notes/170/170.168.0.md @@ -0,0 +1,26 @@ +# Release Notes + +## Microsoft.SqlServer.TransactSql.ScriptDom 170.168.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.415 + +#### .NET Framework +#### .NET Core + +### New Features +* Adds formatter option for preserving comments + +### Fixed +* Fixes parsing VECTOR_SEARCH SIMILAR_TO parameter to reject subquery. +* Fixes parsing VECTOR_SEARCH TOP_N parameter to support multi-part indentifier. +### Changes + +### Known Issues