diff --git a/SqlScriptDom/Parser/TSql/Ast.xml b/SqlScriptDom/Parser/TSql/Ast.xml index 842a787..9a1d8b5 100644 --- a/SqlScriptDom/Parser/TSql/Ast.xml +++ b/SqlScriptDom/Parser/TSql/Ast.xml @@ -236,6 +236,7 @@ + @@ -349,6 +350,11 @@ + + + + + diff --git a/SqlScriptDom/Parser/TSql/CodeGenerationSupporter.cs b/SqlScriptDom/Parser/TSql/CodeGenerationSupporter.cs index 1106636..acb3acb 100644 --- a/SqlScriptDom/Parser/TSql/CodeGenerationSupporter.cs +++ b/SqlScriptDom/Parser/TSql/CodeGenerationSupporter.cs @@ -523,6 +523,7 @@ internal static class CodeGenerationSupporter internal const string JsonArray = "JSON_ARRAY"; internal const string JsonObject = "JSON_OBJECT"; internal const string JsonObjectAgg = "JSON_OBJECTAGG"; + internal const string JsonArrayAgg = "JSON_ARRAYAGG"; internal const string Keep = "KEEP"; internal const string KeepDefaults = "KEEPDEFAULTS"; internal const string KeepFixed = "KEEPFIXED"; @@ -819,6 +820,7 @@ internal static class CodeGenerationSupporter internal const string RecursiveTriggers = "RECURSIVE_TRIGGERS"; internal const string Recovery = "RECOVERY"; internal const string Regenerate = "REGENERATE"; + internal const string RegexpLike = "REGEXP_LIKE"; internal const string RegexpMatches = "REGEXP_MATCHES"; internal const string RegexpSplitToTable = "REGEXP_SPLIT_TO_TABLE"; internal const string RejectType = "REJECT_TYPE"; diff --git a/SqlScriptDom/Parser/TSql/EventNotificationEventType.cs b/SqlScriptDom/Parser/TSql/EventNotificationEventType.cs index 6edc4f8..221ef2e 100644 --- a/SqlScriptDom/Parser/TSql/EventNotificationEventType.cs +++ b/SqlScriptDom/Parser/TSql/EventNotificationEventType.cs @@ -1091,6 +1091,11 @@ public enum EventNotificationEventType /// DropExternalLanguage = 331, + /// + /// CREATE_JSON_INDEX + /// + CreateJsonIndex = 343, + /// /// CREATE_VECTOR_INDEX /// diff --git a/SqlScriptDom/Parser/TSql/TSql160.g b/SqlScriptDom/Parser/TSql/TSql160.g index 913e2a0..d870760 100644 --- a/SqlScriptDom/Parser/TSql/TSql160.g +++ b/SqlScriptDom/Parser/TSql/TSql160.g @@ -31846,15 +31846,31 @@ jsonKeyValueExpression returns [JsonKeyValue vResult = FragmentFactory.CreateFra : ( vKey=expression - { - vResult.JsonKeyName=vKey; - } + { + vResult.JsonKeyName=vKey; + } Colon vValue=expression - { - vResult.JsonValue=vValue; + { + vResult.JsonValue=vValue; } - ) - ; + + | + + label:Label + { + var identifier = this.FragmentFactory.CreateFragment(); + var multiPartIdentifier = this.FragmentFactory.CreateFragment(); + var columnRef = this.FragmentFactory.CreateFragment(); + CreateIdentifierFromLabel(label, identifier, multiPartIdentifier); + columnRef.MultiPartIdentifier = multiPartIdentifier; + vResult.JsonKeyName=columnRef; + } + vValue=expression + { + vResult.JsonValue=vValue; + } + ) + ; windowClause returns [WindowClause vResult = FragmentFactory.CreateFragment()] { diff --git a/SqlScriptDom/Parser/TSql/TSql170.g b/SqlScriptDom/Parser/TSql/TSql170.g index a8f9bf2..8ba896b 100644 --- a/SqlScriptDom/Parser/TSql/TSql170.g +++ b/SqlScriptDom/Parser/TSql/TSql170.g @@ -19238,7 +19238,10 @@ aiGenerateFixedChunksTableReference [ScalarExpression vSource, Identifier vChunk Match(vEnableChunkSetIdParam, CodeGenerationSupporter.EnableChunkSetId); } EqualsSign - vEnableChunkSetId = expression + ( + vEnableChunkSetId = integer // constant integer + | vEnableChunkSetId = nullLiteral // NULL literal + ) { vResult.EnableChunkSetId = vEnableChunkSetId; } @@ -31094,6 +31097,9 @@ booleanExpressionPrimary [ExpressionFlags expressionFlags] returns [BooleanExpre vResult = vMatchPredicate; UpdateTokenInfo(vResult,tRParen); } + | + {NextTokenMatches(CodeGenerationSupporter.RegexpLike)}? + vResult=regexpLikePredicate | vExpressionFirst=expressionWithFlags[expressionFlags] ( @@ -31475,6 +31481,29 @@ tsEqualCall returns [TSEqualCall vResult = this.FragmentFactory.CreateFragment()] +{ + ScalarExpression vText; + ScalarExpression vPattern; + ScalarExpression vFlags = null; +} + : tRegexp:Identifier LeftParenthesis vText=expression Comma vPattern=expression + { + UpdateTokenInfo(vResult,tRegexp); + vResult.Text = vText; + vResult.Pattern = vPattern; + } + (Comma vFlags=expression + )? + { + vResult.Flags = vFlags; + } + tRParen:RightParenthesis + { + UpdateTokenInfo(vResult,tRParen); + } + ; + updateCall returns [UpdateCall vResult = this.FragmentFactory.CreateFragment()] { Identifier vIdentifier; @@ -32502,6 +32531,22 @@ jsonKeyValueExpression returns [JsonKeyValue vResult = FragmentFactory.CreateFra { vResult.JsonValue=vValue; } + + | + + label:Label + { + var identifier = this.FragmentFactory.CreateFragment(); + var multiPartIdentifier = this.FragmentFactory.CreateFragment(); + var columnRef = this.FragmentFactory.CreateFragment(); + CreateIdentifierFromLabel(label, identifier, multiPartIdentifier); + columnRef.MultiPartIdentifier = multiPartIdentifier; + vResult.JsonKeyName=columnRef; + } + vValue=expression + { + vResult.JsonValue=vValue; + } ) ; @@ -32785,6 +32830,9 @@ builtInFunctionCall returns [FunctionCall vResult = FragmentFactory.CreateFragme | {(vResult.FunctionName != null && vResult.FunctionName.Value.ToUpper(CultureInfo.InvariantCulture) == CodeGenerationSupporter.JsonObjectAgg)}? jsonObjectAggBuiltInFunctionCall[vResult] + | + {(vResult.FunctionName != null && vResult.FunctionName.Value.ToUpper(CultureInfo.InvariantCulture) == CodeGenerationSupporter.JsonArrayAgg)}? + jsonArrayAggBuiltInFunctionCall[vResult] | {(vResult.FunctionName != null && vResult.FunctionName.Value.ToUpper(CultureInfo.InvariantCulture) == CodeGenerationSupporter.Trim) && (NextTokenMatches(CodeGenerationSupporter.Leading) | NextTokenMatches(CodeGenerationSupporter.Trailing) | NextTokenMatches(CodeGenerationSupporter.Both))}? @@ -32829,6 +32877,32 @@ jsonArrayBuiltInFunctionCall [FunctionCall vParent] } ; +jsonArrayAggBuiltInFunctionCall [FunctionCall vParent] +{ + ScalarExpression vExpression; +} + : ( + vExpression=expression + { + AddAndUpdateTokenInfo(vParent, vParent.Parameters, vExpression); + } + ) + ( + jsonNullClauseFunction[vParent] + | + /* empty */ + ) + ( + jsonReturningClause[vParent] + | + /* empty */ + ) + tRParen:RightParenthesis + { + UpdateTokenInfo(vParent, tRParen); + } + ; + jsonObjectBuiltInFunctionCall [FunctionCall vParent] { } diff --git a/SqlScriptDom/Parser/TSql/TSql80ParserBaseInternal.cs b/SqlScriptDom/Parser/TSql/TSql80ParserBaseInternal.cs index 120adcc..7415c4a 100644 --- a/SqlScriptDom/Parser/TSql/TSql80ParserBaseInternal.cs +++ b/SqlScriptDom/Parser/TSql/TSql80ParserBaseInternal.cs @@ -263,6 +263,25 @@ internal static void UpdateTokenInfo(TSqlFragment fragment, antlr.IToken token) fragment.UpdateTokenInfo(tokenIndex, tokenIndex); } + /// + /// Creates an identifier from a label token and adds it to the multipart identifier. + /// + /// + /// + /// + internal static void CreateIdentifierFromLabel(antlr.IToken token, Identifier identifier, MultiPartIdentifier multiPartIdentifier) + { + var tokenText = token?.getText(); + if (string.IsNullOrEmpty(tokenText)) + { + throw GetUnexpectedTokenErrorException(token); + } + var identifierName = tokenText?.EndsWith(":") == true ? tokenText.Substring(0, tokenText.Length - 1) : tokenText; + identifier.SetIdentifier(identifierName); + UpdateTokenInfo(identifier, token); + AddAndUpdateTokenInfo(multiPartIdentifier, multiPartIdentifier.Identifiers, identifier); + } + protected static void AddAndUpdateTokenInfo(TSqlFragment node, IList collection, TFragmentType item) where TFragmentType : TSqlFragment { diff --git a/SqlScriptDom/Parser/TSql/TSqlFabricDW.g b/SqlScriptDom/Parser/TSql/TSqlFabricDW.g index 137a318..fda6c13 100644 --- a/SqlScriptDom/Parser/TSql/TSqlFabricDW.g +++ b/SqlScriptDom/Parser/TSql/TSqlFabricDW.g @@ -17746,15 +17746,29 @@ commonTableExpression returns [CommonTableExpression vResult = this.FragmentFact { Identifier vIdentifier; QueryExpression vQueryExpression; + WithCtesAndXmlNamespaces vWithCommonTableExpressionsAndXmlNamespaces; } : vIdentifier=identifier { vResult.ExpressionName = vIdentifier; } (columnNameList[vResult, vResult.Columns])? - As tLParen:LeftParenthesis vQueryExpression=subqueryExpression[SubDmlFlags.SelectNotForInsert] tRParen:RightParenthesis + As tLParen:LeftParenthesis + ( + vWithCommonTableExpressionsAndXmlNamespaces=withCommonTableExpressionsAndXmlNamespaces + vQueryExpression=subqueryExpression[SubDmlFlags.SelectNotForInsert] + { + vResult.QueryExpression = vQueryExpression; + vResult.WithCtesAndXmlNamespaces = vWithCommonTableExpressionsAndXmlNamespaces; + } + | + vQueryExpression=subqueryExpression[SubDmlFlags.SelectNotForInsert] + { + vResult.QueryExpression = vQueryExpression; + } + ) + tRParen:RightParenthesis { - vResult.QueryExpression = vQueryExpression; UpdateTokenInfo(vResult,tLParen); UpdateTokenInfo(vResult,tRParen); } diff --git a/SqlScriptDom/ScriptDom/SqlServer/ScriptGenerator/SqlScriptGeneratorVisitor.CommonTableExpression.cs b/SqlScriptDom/ScriptDom/SqlServer/ScriptGenerator/SqlScriptGeneratorVisitor.CommonTableExpression.cs index dc6d8e0..7fec1eb 100644 --- a/SqlScriptDom/ScriptDom/SqlServer/ScriptGenerator/SqlScriptGeneratorVisitor.CommonTableExpression.cs +++ b/SqlScriptDom/ScriptDom/SqlServer/ScriptGenerator/SqlScriptGeneratorVisitor.CommonTableExpression.cs @@ -31,7 +31,28 @@ public override void ExplicitVisit(CommonTableExpression node) AlignmentPoint subquery = new AlignmentPoint(); MarkAndPushAlignmentPoint(subquery); - GenerateQueryExpressionInParentheses(node.QueryExpression); + GenerateSymbol(TSqlTokenType.LeftParenthesis); + + AlignmentPoint queryBody = new AlignmentPoint(); + MarkAndPushAlignmentPoint(queryBody); + + // Generate nested WITH clause if present + if (node.WithCtesAndXmlNamespaces != null) + { + AlignmentPoint clauseBodyNested = new AlignmentPoint(ClauseBody); + GenerateFragmentWithAlignmentPointIfNotNull(node.WithCtesAndXmlNamespaces, clauseBodyNested); + NewLine(); + } + + if (node.QueryExpression != null) + { + AlignmentPoint clauseBodyQuery = new AlignmentPoint(ClauseBody); + GenerateFragmentWithAlignmentPointIfNotNull(node.QueryExpression, clauseBodyQuery); + } + + PopAlignmentPoint(); + + GenerateSymbol(TSqlTokenType.RightParenthesis); PopAlignmentPoint(); } diff --git a/SqlScriptDom/ScriptDom/SqlServer/ScriptGenerator/SqlScriptGeneratorVisitor.FunctionCall.cs b/SqlScriptDom/ScriptDom/SqlServer/ScriptGenerator/SqlScriptGeneratorVisitor.FunctionCall.cs index f4e50f9..0daaff0 100644 --- a/SqlScriptDom/ScriptDom/SqlServer/ScriptGenerator/SqlScriptGeneratorVisitor.FunctionCall.cs +++ b/SqlScriptDom/ScriptDom/SqlServer/ScriptGenerator/SqlScriptGeneratorVisitor.FunctionCall.cs @@ -72,6 +72,17 @@ public override void ExplicitVisit(FunctionCall node) GenerateSymbol(TSqlTokenType.RightParenthesis); } else if (node.FunctionName.Value.ToUpper(CultureInfo.InvariantCulture) == CodeGenerationSupporter.JsonArray) + { + GenerateCommaSeparatedList(node.Parameters); + if (node.Parameters?.Count > 0 && node?.AbsentOrNullOnNull?.Count > 0) //If there are values and null on null or absent on null present then generate space in between them + GenerateSpace(); + GenerateNullOnNullOrAbsentOnNull(node?.AbsentOrNullOnNull); + if (node.ReturnType?.Count > 0) //If there are values and null on null or absent on null present then generate space in between them + GenerateSpace(); + GenerateReturnType(node?.ReturnType); + GenerateSymbol(TSqlTokenType.RightParenthesis); + } + else if (node.FunctionName.Value.ToUpper(CultureInfo.InvariantCulture) == CodeGenerationSupporter.JsonArrayAgg) { GenerateCommaSeparatedList(node.Parameters); if (node.Parameters?.Count > 0 && node?.AbsentOrNullOnNull?.Count > 0) //If there are values and null on null or absent on null present then generate space in between them diff --git a/SqlScriptDom/ScriptDom/SqlServer/ScriptGenerator/SqlScriptGeneratorVisitor.RegexpLikePredicate.cs b/SqlScriptDom/ScriptDom/SqlServer/ScriptGenerator/SqlScriptGeneratorVisitor.RegexpLikePredicate.cs new file mode 100644 index 0000000..5ecb0e9 --- /dev/null +++ b/SqlScriptDom/ScriptDom/SqlServer/ScriptGenerator/SqlScriptGeneratorVisitor.RegexpLikePredicate.cs @@ -0,0 +1,35 @@ +//------------------------------------------------------------------------------ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// +//------------------------------------------------------------------------------ +using System; +using Microsoft.SqlServer.TransactSql.ScriptDom; + +namespace Microsoft.SqlServer.TransactSql.ScriptDom.ScriptGenerator +{ + internal partial class SqlScriptGeneratorVisitor + { + public override void ExplicitVisit(RegexpLikePredicate node) + { + GenerateIdentifier(CodeGenerationSupporter.RegexpLike); + + GenerateSpace(); + GenerateSymbol(TSqlTokenType.LeftParenthesis); + + GenerateFragmentIfNotNull(node.Text); + GenerateSymbol(TSqlTokenType.Comma); + GenerateSpace(); + GenerateFragmentIfNotNull(node.Pattern); + + if (node.Flags != null) + { + GenerateSymbol(TSqlTokenType.Comma); + GenerateSpace(); + GenerateFragmentIfNotNull(node.Flags); + } + + GenerateSymbol(TSqlTokenType.RightParenthesis); + } + } +} diff --git a/Test/SqlDom/Baselines170/AiGenerateChunksTests170.sql b/Test/SqlDom/Baselines170/AiGenerateChunksTests170.sql index 8374acc..a11724c 100644 --- a/Test/SqlDom/Baselines170/AiGenerateChunksTests170.sql +++ b/Test/SqlDom/Baselines170/AiGenerateChunksTests170.sql @@ -16,9 +16,6 @@ FROM AI_GENERATE_CHUNKS (SOURCE = 'some text', CHUNK_TYPE = fixed, CHUNK_SIZE = SELECT * FROM AI_GENERATE_CHUNKS (SOURCE = @SOURCE, CHUNK_TYPE = fixed, CHUNK_SIZE = @CHUNK_SIZE); -SELECT * -FROM AI_GENERATE_CHUNKS (SOURCE = @SOURCE, CHUNK_TYPE = fixed, CHUNK_SIZE = @CHUNK_SIZE, OVERLAP = @OVERLAP, ENABLE_CHUNK_SET_ID = @ENABLE_CHUNK_SET_ID); - SELECT * FROM AI_GENERATE_CHUNKS (SOURCE = NULL, CHUNK_TYPE = fixed, CHUNK_SIZE = 5); @@ -31,9 +28,6 @@ FROM t1 CROSS APPLY AI_GENERATE_CHUNKS (SOURCE = 'some text', CHUNK_TYPE = fixed SELECT * FROM t1 CROSS APPLY AI_GENERATE_CHUNKS (SOURCE = 'some text', CHUNK_TYPE = fixed, CHUNK_SIZE = 10, OVERLAP = t1.c1); -SELECT * -FROM t1 CROSS APPLY AI_GENERATE_CHUNKS (SOURCE = 'some text', CHUNK_TYPE = fixed, CHUNK_SIZE = 10, OVERLAP = 5, ENABLE_CHUNK_SET_ID = t1.c1); - SELECT * FROM AI_GENERATE_CHUNKS (SOURCE = N'some text', CHUNK_TYPE = fixed, CHUNK_SIZE = NULL); diff --git a/Test/SqlDom/Baselines170/JsonFunctionTests170.sql b/Test/SqlDom/Baselines170/JsonFunctionTests170.sql index 29adc38..baa161f 100644 --- a/Test/SqlDom/Baselines170/JsonFunctionTests170.sql +++ b/Test/SqlDom/Baselines170/JsonFunctionTests170.sql @@ -37,7 +37,7 @@ DECLARE @id_key AS NVARCHAR (10) = N'id', @id_value AS NVARCHAR (64) = NEWID(); SELECT JSON_OBJECT('user_name':USER_NAME(), @id_key:@id_value, 'sid':(SELECT @@SPID)); SELECT s.session_id, - JSON_OBJECT('security_id':s.security_id, 'login':s.login_name, 'status':s.status) AS info + JSON_OBJECT(security_id:s.security_id, 'login':s.login_name, 'status':s.status) AS info FROM sys.dm_exec_sessions AS s WHERE s.is_user_process = 1; @@ -92,10 +92,44 @@ SELECT JSON_OBJECTAGG(); SELECT JSON_OBJECTAGG('name':1); -SELECT JSON_OBJECTAGG('name':JSON_ARRAY(1, 2)); +SELECT JSON_OBJECTAGG('name':JSON_ARRAY(1, 2)); + SELECT JSON_OBJECTAGG('name':'b' NULL ON NULL RETURNING JSON); SELECT JSON_OBJECTAGG('name':'b' ABSENT ON NULL RETURNING JSON); SELECT JSON_OBJECTAGG('name':'b' RETURNING JSON); +SELECT JSON_ARRAYAGG('name'); + +SELECT JSON_ARRAYAGG('a'); + +SELECT JSON_OBJECTAGG(c1:c2); + +SELECT JSON_OBJECTAGG(c1:'c2'); + +SELECT JSON_ARRAYAGG('a' NULL ON NULL); + +SELECT JSON_ARRAYAGG('a' NULL ON NULL RETURNING JSON); + +SELECT s.session_id, + JSON_ARRAYAGG(s.host_name) +FROM sys.dm_exec_sessions AS s +WHERE s.is_user_process = 1; + +SELECT s.session_id, + JSON_ARRAYAGG(s.host_name NULL ON NULL) +FROM sys.dm_exec_sessions AS s +WHERE s.is_user_process = 1; + +SELECT s.session_id, + JSON_ARRAYAGG(s.host_name NULL ON NULL RETURNING JSON) +FROM sys.dm_exec_sessions AS s +WHERE s.is_user_process = 1; + + +GO +CREATE VIEW dbo.jsonfunctest +AS +SELECT JSON_OBJECTAGG(c1:c2) AS jsoncontents +FROM (VALUES ('key1', 'c'), ('key2', 'b'), ('key3', 'a')) AS t(c1, c2); \ No newline at end of file diff --git a/Test/SqlDom/Baselines170/RegexpLikeTests170.sql b/Test/SqlDom/Baselines170/RegexpLikeTests170.sql new file mode 100644 index 0000000..2faab93 --- /dev/null +++ b/Test/SqlDom/Baselines170/RegexpLikeTests170.sql @@ -0,0 +1,33 @@ +SELECT 1 +WHERE REGEXP_LIKE ('abc', '^a'); + +SELECT 1 +WHERE NOT REGEXP_LIKE ('abc', '^b'); + +SELECT IIF (REGEXP_LIKE ('abc', '^a'), 1, 0) AS is_match; + +SELECT IIF (NOT REGEXP_LIKE ('abc', '^a'), 1, 0) AS is_match; + +SELECT CASE WHEN REGEXP_LIKE ('abc', '^a') THEN 1 ELSE 0 END AS is_match; + +SELECT CASE WHEN NOT REGEXP_LIKE ('abc', '^a') THEN 1 ELSE 0 END AS is_match; + +SELECT 1 +WHERE REGEXP_LIKE ('abc', '^a', 'i'); + +SELECT 1 +WHERE NOT REGEXP_LIKE ('abc', '^b', 'i'); + +SELECT IIF (REGEXP_LIKE ('abc', '^a', 'i'), 1, 0) AS is_match; + +SELECT IIF (NOT REGEXP_LIKE ('abc', '^a', 'i'), 1, 0) AS is_match; + +SELECT CASE WHEN REGEXP_LIKE ('abc', '^a', 'i') THEN 1 ELSE 0 END AS is_match; + +SELECT CASE WHEN NOT REGEXP_LIKE ('abc', '^a', 'i') THEN 1 ELSE 0 END AS is_match; + +SELECT CASE WHEN NOT REGEXP_LIKE ('abc', '^a', NULL) THEN 1 ELSE 0 END AS is_match; + +SELECT CASE WHEN REGEXP_LIKE (NULL, '^a', 'c') THEN 1 ELSE 0 END AS is_match; + +SELECT IIF (NOT REGEXP_LIKE ('abc', NULL), 1, 0) AS is_match; \ No newline at end of file diff --git a/Test/SqlDom/BaselinesFabricDW/NestedCTETestsFabricDW.sql b/Test/SqlDom/BaselinesFabricDW/NestedCTETestsFabricDW.sql new file mode 100644 index 0000000..86e585d --- /dev/null +++ b/Test/SqlDom/BaselinesFabricDW/NestedCTETestsFabricDW.sql @@ -0,0 +1,54 @@ +WITH Orders_Summary +AS (SELECT CustomerID, + COUNT(*) AS OrderCount, + SUM(Amount) AS TotalAmount + FROM Orders + GROUP BY CustomerID) +SELECT CustomerID, + OrderCount, + TotalAmount +FROM Orders_Summary +WHERE OrderCount > 5; + +WITH Regional_Analysis +AS (WITH Sales_Data + AS (SELECT RegionID, + ProductID, + SUM(Amount) AS Sales + FROM Sales + GROUP BY RegionID, ProductID) + SELECT RegionID, + COUNT(ProductID) AS ProductCount, + SUM(Sales) AS TotalSales + FROM Sales_Data + GROUP BY RegionID) +SELECT RegionID, + ProductCount, + TotalSales +FROM Regional_Analysis +WHERE TotalSales > 10000; + +WITH Final_Report +AS (WITH Department_Summary + AS (WITH Employee_Data + AS (SELECT DepartmentID, + EmployeeID, + Salary + FROM Employees + WHERE IsActive = 1) + SELECT DepartmentID, + COUNT(EmployeeID) AS EmpCount, + AVG(Salary) AS AvgSalary + FROM Employee_Data + GROUP BY DepartmentID) + SELECT DepartmentID, + EmpCount, + AvgSalary, + CASE WHEN AvgSalary > 50000 THEN 'High' ELSE 'Low' END AS SalaryLevel + FROM Department_Summary) +SELECT DepartmentID, + EmpCount, + AvgSalary, + SalaryLevel +FROM Final_Report +ORDER BY AvgSalary DESC; \ No newline at end of file diff --git a/Test/SqlDom/Only170SyntaxTests.cs b/Test/SqlDom/Only170SyntaxTests.cs index a339cf7..736c839 100644 --- a/Test/SqlDom/Only170SyntaxTests.cs +++ b/Test/SqlDom/Only170SyntaxTests.cs @@ -1,6 +1,7 @@ using Microsoft.SqlServer.TransactSql.ScriptDom; using Microsoft.VisualStudio.TestTools.UnitTesting; using SqlStudio.Tests.AssemblyTools.TestCategory; +using System.Collections.Generic; namespace SqlStudio.Tests.UTSqlScriptDom { @@ -15,15 +16,15 @@ public partial class SqlDomTests 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: 21, nErrors90: 18, nErrors100: 17, nErrors110: 17, nErrors120: 17, nErrors130: 17, nErrors140: 17, nErrors150: 17, nErrors160: 17), - new ParserTest170("JsonFunctionTests170.sql", nErrors80: 9, nErrors90: 8, nErrors100: 30, nErrors110: 30, nErrors120: 30, nErrors130: 30, nErrors140: 30, nErrors150: 30, nErrors160: 30), + 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: 10, nErrors90: 8, nErrors100: 35, nErrors110: 35, nErrors120: 35, nErrors130: 35, nErrors140: 35, nErrors150: 35, nErrors160: 35), new ParserTest170("AiGenerateEmbeddingsTests170.sql", nErrors80: 12, nErrors90: 9, nErrors100: 9, nErrors110: 9, nErrors120: 9, nErrors130: 9, nErrors140: 9, nErrors150: 9, nErrors160: 9), new ParserTest170("CreateExternalModelStatementTests170.sql", nErrors80: 2, nErrors90: 2, nErrors100: 2, nErrors110: 2, nErrors120: 2, nErrors130: 4, nErrors140: 4, nErrors150: 4, nErrors160: 4), new ParserTest170("AlterExternalModelStatementTests170.sql", nErrors80: 2, nErrors90: 2, nErrors100: 2, nErrors110: 2, nErrors120: 2, nErrors130: 5, nErrors140: 5, nErrors150: 5, nErrors160: 5), new ParserTest170("DropExternalModelStatementTests170.sql", nErrors80: 1, nErrors90: 1, nErrors100: 1, nErrors110: 1, nErrors120: 1, nErrors130: 1, nErrors140: 1, nErrors150: 1, nErrors160: 1), new ParserTest170("VectorFunctionTests170.sql", nErrors80: 1, nErrors90: 1, nErrors100: 2, nErrors110: 2, nErrors120: 2, nErrors130: 2, nErrors140: 2, nErrors150: 2, nErrors160: 2), new ParserTest170("SecurityStatementExternalModelTests170.sql", nErrors80: 2, nErrors90: 17, nErrors100: 17, nErrors110: 17, nErrors120: 17, nErrors130: 17, nErrors140: 17, nErrors150: 17, nErrors160: 17), - + new ParserTest170("RegexpLikeTests170.sql", nErrors80: 13, nErrors90: 13, nErrors100: 13, nErrors110: 15, nErrors120: 15, nErrors130: 15, nErrors140: 15, nErrors150: 15, nErrors160: 15) }; private static readonly ParserTest[] SqlAzure170_TestInfos = diff --git a/Test/SqlDom/OnlyFabricDWSyntaxTests.cs b/Test/SqlDom/OnlyFabricDWSyntaxTests.cs index 043340e..86cc0b1 100644 --- a/Test/SqlDom/OnlyFabricDWSyntaxTests.cs +++ b/Test/SqlDom/OnlyFabricDWSyntaxTests.cs @@ -14,6 +14,7 @@ public partial class SqlDomTests 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), new ParserTestFabricDW("IdentityColumnTestsFabricDW.sql", nErrors80: 0, nErrors90: 0, nErrors100: 0, nErrors110: 0, nErrors120: 0, nErrors130: 0, nErrors140: 0, nErrors150: 0, nErrors160: 0, nErrors170: 0), + new ParserTestFabricDW("NestedCTETestsFabricDW.sql", nErrors80: 1, nErrors90: 2, nErrors100: 2, nErrors110: 2, nErrors120: 2, nErrors130: 2, nErrors140: 2, nErrors150: 2, nErrors160: 2, nErrors170: 2), new ParserTestFabricDW("ScalarFunctionTestsFabricDW.sql", nErrors80: 3, nErrors90: 2, nErrors100: 2, nErrors110: 2, nErrors120: 2, nErrors130: 0, nErrors140: 0, nErrors150: 0, nErrors160: 0, nErrors170: 0) }; diff --git a/Test/SqlDom/ParserErrorsTests.cs b/Test/SqlDom/ParserErrorsTests.cs index 6a46317..26b5e09 100644 --- a/Test/SqlDom/ParserErrorsTests.cs +++ b/Test/SqlDom/ParserErrorsTests.cs @@ -548,6 +548,35 @@ public void JsonObjectAGGSyntaxNegativeTest() new ParserErrorInfo(44, "SQL46005", "JSON", "INT")); } + /// + /// Negative tests for JSON_ARRAYAGG syntax in functions + /// + [TestMethod] + [Priority(0)] + [SqlStudioTestCategory(Category.UnitTest)] + public void JsonArrayAggSyntaxNegativeTest() + { + // Incorrect placing of colon + ParserTestUtils.ErrorTest160("SELECT JSON_ARRAYAGG('name':'value')", + new ParserErrorInfo(27, "SQL46010", ":")); + + // Incorrect placing of single quotes + ParserTestUtils.ErrorTest160("SELECT JSON_ARRAYAGG('name)", + new ParserErrorInfo(21, "SQL46030", "'name)")); + + // Cannot use incomplete absent on null clause cases + ParserTestUtils.ErrorTest160("SELECT JSON_ARRAYAGG('name', NULL ABSENT ON)", + new ParserErrorInfo(34, "SQL46010", "ABSENT")); + + // one params only + ParserTestUtils.ErrorTest160("SELECT JSON_ARRAYAGG('name', 'value', NULL ON NULL)", + new ParserErrorInfo(43, "SQL46010", "ON")); + + // Cannot use null on null incorrectly + ParserTestUtils.ErrorTest160("SELECT JSON_ARRAYAGG('name', NULL NULL ON)", + new ParserErrorInfo(34, "SQL46010", "NULL")); + } + /// /// Negative tests for Data Masking Alter Column syntax. /// @@ -7189,6 +7218,31 @@ public void GenerateChunksNegativeTest170() ParserTestUtils.ErrorTest170( "SELECT * FROM AI_GENERATE_CHUNKS (source = 'some text', chunk_type = other, chunk_size = 5)", new ParserErrorInfo(69, "SQL46010", "other")); + + // Invalid ENABLE_CHUNK_SET_ID (parameter reference not allowed) + ParserTestUtils.ErrorTest170( + "SELECT * FROM AI_GENERATE_CHUNKS (SOURCE = @SOURCE, CHUNK_TYPE = fixed, CHUNK_SIZE = @CHUNK_SIZE, OVERLAP = @OVERLAP, ENABLE_CHUNK_SET_ID = @ENABLE_CHUNK_SET_ID)", + new ParserErrorInfo(140, "SQL46010", "@ENABLE_CHUNK_SET_ID")); + + // Invalid ENABLE_CHUNK_SET_ID (column reference not allowed) + ParserTestUtils.ErrorTest170( + "SELECT * FROM t1 CROSS APPLY AI_GENERATE_CHUNKS (SOURCE = 'some text', CHUNK_TYPE = fixed, CHUNK_SIZE = 10, OVERLAP = 5, ENABLE_CHUNK_SET_ID = t1.c1)", + new ParserErrorInfo(143, "SQL46010", "t1")); + + // Invalid ENABLE_CHUNK_SET_ID (decimal literal not allowed) + ParserTestUtils.ErrorTest170( + "SELECT * FROM t1 CROSS APPLY AI_GENERATE_CHUNKS (SOURCE = 'some text', CHUNK_TYPE = fixed, CHUNK_SIZE = 10, OVERLAP = 5, ENABLE_CHUNK_SET_ID = 0.1)", + new ParserErrorInfo(143, "SQL46010", "0.1")); + + // Invalid ENABLE_CHUNK_SET_ID (string literal not allowed) + ParserTestUtils.ErrorTest170( + "SELECT * FROM t1 CROSS APPLY AI_GENERATE_CHUNKS (SOURCE = 'some text', CHUNK_TYPE = fixed, CHUNK_SIZE = 10, OVERLAP = 5, ENABLE_CHUNK_SET_ID = '1')", + new ParserErrorInfo(143, "SQL46010", "'1'")); + + // Invalid ENABLE_CHUNK_SET_ID (function call not allowed) + ParserTestUtils.ErrorTest170( + "SELECT * FROM t1 CROSS APPLY AI_GENERATE_CHUNKS (SOURCE = 'some text', CHUNK_TYPE = fixed, CHUNK_SIZE = 10, OVERLAP = 5, ENABLE_CHUNK_SET_ID = rand())", + new ParserErrorInfo(143, "SQL46010", "rand")); } /// diff --git a/Test/SqlDom/TestScripts/AiGenerateChunksTests170.sql b/Test/SqlDom/TestScripts/AiGenerateChunksTests170.sql index 8374acc..a11724c 100644 --- a/Test/SqlDom/TestScripts/AiGenerateChunksTests170.sql +++ b/Test/SqlDom/TestScripts/AiGenerateChunksTests170.sql @@ -16,9 +16,6 @@ FROM AI_GENERATE_CHUNKS (SOURCE = 'some text', CHUNK_TYPE = fixed, CHUNK_SIZE = SELECT * FROM AI_GENERATE_CHUNKS (SOURCE = @SOURCE, CHUNK_TYPE = fixed, CHUNK_SIZE = @CHUNK_SIZE); -SELECT * -FROM AI_GENERATE_CHUNKS (SOURCE = @SOURCE, CHUNK_TYPE = fixed, CHUNK_SIZE = @CHUNK_SIZE, OVERLAP = @OVERLAP, ENABLE_CHUNK_SET_ID = @ENABLE_CHUNK_SET_ID); - SELECT * FROM AI_GENERATE_CHUNKS (SOURCE = NULL, CHUNK_TYPE = fixed, CHUNK_SIZE = 5); @@ -31,9 +28,6 @@ FROM t1 CROSS APPLY AI_GENERATE_CHUNKS (SOURCE = 'some text', CHUNK_TYPE = fixed SELECT * FROM t1 CROSS APPLY AI_GENERATE_CHUNKS (SOURCE = 'some text', CHUNK_TYPE = fixed, CHUNK_SIZE = 10, OVERLAP = t1.c1); -SELECT * -FROM t1 CROSS APPLY AI_GENERATE_CHUNKS (SOURCE = 'some text', CHUNK_TYPE = fixed, CHUNK_SIZE = 10, OVERLAP = 5, ENABLE_CHUNK_SET_ID = t1.c1); - SELECT * FROM AI_GENERATE_CHUNKS (SOURCE = N'some text', CHUNK_TYPE = fixed, CHUNK_SIZE = NULL); diff --git a/Test/SqlDom/TestScripts/JsonFunctionTests170.sql b/Test/SqlDom/TestScripts/JsonFunctionTests170.sql index d15a477..d275126 100644 --- a/Test/SqlDom/TestScripts/JsonFunctionTests170.sql +++ b/Test/SqlDom/TestScripts/JsonFunctionTests170.sql @@ -35,7 +35,7 @@ DECLARE @id_key AS NVARCHAR (10) = N'id', @id_value AS NVARCHAR (64) = NEWID(); SELECT JSON_OBJECT('user_name':USER_NAME(), @id_key:@id_value, 'sid':(SELECT @@SPID)); SELECT s.session_id, - JSON_OBJECT('security_id':s.security_id, 'login':s.login_name, 'status':s.status) AS info + JSON_OBJECT(security_id:s.security_id, 'login':s.login_name, 'status':s.status) AS info FROM sys.dm_exec_sessions AS s WHERE s.is_user_process = 1; @@ -96,3 +96,35 @@ SELECT JSON_OBJECTAGG('name':'b' NULL ON NULL RETURNING JSON); SELECT JSON_OBJECTAGG('name':'b' ABSENT ON NULL RETURNING JSON); SELECT JSON_OBJECTAGG('name':'b' RETURNING JSON); + +SELECT JSON_ARRAYAGG('name'); + +SELECT JSON_ARRAYAGG('a'); +SELECT JSON_OBJECTAGG( c1:c2 ) +SELECT JSON_OBJECTAGG( c1:'c2' ) + +SELECT JSON_ARRAYAGG('a' NULL ON NULL); + +SELECT JSON_ARRAYAGG('a' NULL ON NULL RETURNING JSON); + +SELECT s.session_id, + JSON_ARRAYAGG(s.host_name) +FROM sys.dm_exec_sessions AS s +WHERE s.is_user_process = 1; + +SELECT s.session_id, + JSON_ARRAYAGG(s.host_name NULL ON NULL) +FROM sys.dm_exec_sessions AS s +WHERE s.is_user_process = 1; + +SELECT s.session_id, + JSON_ARRAYAGG(s.host_name NULL ON NULL RETURNING JSON) +FROM sys.dm_exec_sessions AS s +WHERE s.is_user_process = 1; + +GO +CREATE VIEW dbo.jsonfunctest AS + SELECT JSON_OBJECTAGG( c1:c2 ) as jsoncontents + FROM ( + VALUES('key1', 'c'), ('key2', 'b'), ('key3','a') + ) AS t(c1, c2); \ No newline at end of file diff --git a/Test/SqlDom/TestScripts/NestedCTETestsFabricDW.sql b/Test/SqlDom/TestScripts/NestedCTETestsFabricDW.sql new file mode 100644 index 0000000..86e585d --- /dev/null +++ b/Test/SqlDom/TestScripts/NestedCTETestsFabricDW.sql @@ -0,0 +1,54 @@ +WITH Orders_Summary +AS (SELECT CustomerID, + COUNT(*) AS OrderCount, + SUM(Amount) AS TotalAmount + FROM Orders + GROUP BY CustomerID) +SELECT CustomerID, + OrderCount, + TotalAmount +FROM Orders_Summary +WHERE OrderCount > 5; + +WITH Regional_Analysis +AS (WITH Sales_Data + AS (SELECT RegionID, + ProductID, + SUM(Amount) AS Sales + FROM Sales + GROUP BY RegionID, ProductID) + SELECT RegionID, + COUNT(ProductID) AS ProductCount, + SUM(Sales) AS TotalSales + FROM Sales_Data + GROUP BY RegionID) +SELECT RegionID, + ProductCount, + TotalSales +FROM Regional_Analysis +WHERE TotalSales > 10000; + +WITH Final_Report +AS (WITH Department_Summary + AS (WITH Employee_Data + AS (SELECT DepartmentID, + EmployeeID, + Salary + FROM Employees + WHERE IsActive = 1) + SELECT DepartmentID, + COUNT(EmployeeID) AS EmpCount, + AVG(Salary) AS AvgSalary + FROM Employee_Data + GROUP BY DepartmentID) + SELECT DepartmentID, + EmpCount, + AvgSalary, + CASE WHEN AvgSalary > 50000 THEN 'High' ELSE 'Low' END AS SalaryLevel + FROM Department_Summary) +SELECT DepartmentID, + EmpCount, + AvgSalary, + SalaryLevel +FROM Final_Report +ORDER BY AvgSalary DESC; \ No newline at end of file diff --git a/Test/SqlDom/TestScripts/RegexpLikeTests170.sql b/Test/SqlDom/TestScripts/RegexpLikeTests170.sql new file mode 100644 index 0000000..2faab93 --- /dev/null +++ b/Test/SqlDom/TestScripts/RegexpLikeTests170.sql @@ -0,0 +1,33 @@ +SELECT 1 +WHERE REGEXP_LIKE ('abc', '^a'); + +SELECT 1 +WHERE NOT REGEXP_LIKE ('abc', '^b'); + +SELECT IIF (REGEXP_LIKE ('abc', '^a'), 1, 0) AS is_match; + +SELECT IIF (NOT REGEXP_LIKE ('abc', '^a'), 1, 0) AS is_match; + +SELECT CASE WHEN REGEXP_LIKE ('abc', '^a') THEN 1 ELSE 0 END AS is_match; + +SELECT CASE WHEN NOT REGEXP_LIKE ('abc', '^a') THEN 1 ELSE 0 END AS is_match; + +SELECT 1 +WHERE REGEXP_LIKE ('abc', '^a', 'i'); + +SELECT 1 +WHERE NOT REGEXP_LIKE ('abc', '^b', 'i'); + +SELECT IIF (REGEXP_LIKE ('abc', '^a', 'i'), 1, 0) AS is_match; + +SELECT IIF (NOT REGEXP_LIKE ('abc', '^a', 'i'), 1, 0) AS is_match; + +SELECT CASE WHEN REGEXP_LIKE ('abc', '^a', 'i') THEN 1 ELSE 0 END AS is_match; + +SELECT CASE WHEN NOT REGEXP_LIKE ('abc', '^a', 'i') THEN 1 ELSE 0 END AS is_match; + +SELECT CASE WHEN NOT REGEXP_LIKE ('abc', '^a', NULL) THEN 1 ELSE 0 END AS is_match; + +SELECT CASE WHEN REGEXP_LIKE (NULL, '^a', 'c') THEN 1 ELSE 0 END AS is_match; + +SELECT IIF (NOT REGEXP_LIKE ('abc', NULL), 1, 0) AS is_match; \ No newline at end of file diff --git a/release-notes/170/170.100.0.md b/release-notes/170/170.100.0.md new file mode 100644 index 0000000..8f6bf9f --- /dev/null +++ b/release-notes/170/170.100.0.md @@ -0,0 +1,25 @@ +# Release Notes + +## Microsoft.SqlServer.TransactSql.ScriptDom 170.100.0 +This update brings the below 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.413 + +#### .NET Framework +#### .NET Core + +### New Features +* Adds support for `JSON_ARRAYAGG` abd `REGEXP_LIKE` Functions. + +### Fixed + +### Changes + +### Known Issues