diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index f4b000c..cc61129 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -106,14 +106,36 @@ Example: To run all priority 0 tests
dotnet test --filter Priority=0
```
+#### ⚠️ CRITICAL: Full Test Suite for Parser Changes
+
+**If you make ANY changes to grammar files (`.g` files) or AST definitions (`Ast.xml`), you MUST run the complete test suite** to ensure no regressions:
+
+```cmd
+dotnet test Test/SqlDom/UTSqlScriptDom.csproj -c Debug
+```
+
+**Why this is critical for parser changes:**
+- Grammar changes can have far-reaching effects on seemingly unrelated functionality
+- Shared grammar rules are used in multiple contexts throughout the parser
+- AST modifications can affect script generation and visitor patterns across the entire codebase
+- Token recognition changes can impact parsing of statements that don't even use the modified feature
+
+**Example of unexpected failures:**
+- Modifying a shared rule like `identifierColumnReferenceExpression` can cause other tests to fail because the rule now accepts syntax that should be rejected in different contexts
+- Changes to operator precedence can affect unrelated expressions
+- Adding new AST members without proper script generation support can break round-trip parsing
+
+Always verify that all ~557 tests pass before submitting your changes.
+
### Pull Request Process
Before sending a Pull Request, please do the following:
-1. Ensure builds are still successful and tests, including any added or updated tests, pass prior to submitting the pull request.
-2. Update any documentation, user and contributor, that is impacted by your changes.
-3. Include your change description in `CHANGELOG.md` file as part of pull request.
-4. You may merge the pull request in once you have the sign-off of two other developers, or if you do not have permission to do that, you may request the second reviewer to merge it for you.
+1. **For parser changes (grammar/AST modifications): Run the complete test suite** (`dotnet test Test/SqlDom/UTSqlScriptDom.csproj -c Debug`) and ensure all ~557 tests pass. Grammar changes can have unexpected side effects.
+2. Ensure builds are still successful and tests, including any added or updated tests, pass prior to submitting the pull request.
+3. Update any documentation, user and contributor, that is impacted by your changes.
+4. Include your change description in `CHANGELOG.md` file as part of pull request.
+5. You may merge the pull request in once you have the sign-off of two other developers, or if you do not have permission to do that, you may request the second reviewer to merge it for you.
### Helpful notes for SQLDOM extensions
diff --git a/SqlScriptDom/Parser/TSql/Ast.xml b/SqlScriptDom/Parser/TSql/Ast.xml
index 347befd..4e0e182 100644
--- a/SqlScriptDom/Parser/TSql/Ast.xml
+++ b/SqlScriptDom/Parser/TSql/Ast.xml
@@ -652,8 +652,9 @@
+
-
+
diff --git a/SqlScriptDom/Parser/TSql/CodeGenerationSupporter.cs b/SqlScriptDom/Parser/TSql/CodeGenerationSupporter.cs
index 0a64b28..6d285f5 100644
--- a/SqlScriptDom/Parser/TSql/CodeGenerationSupporter.cs
+++ b/SqlScriptDom/Parser/TSql/CodeGenerationSupporter.cs
@@ -527,6 +527,7 @@ internal static class CodeGenerationSupporter
internal const string JsonObjectAgg = "JSON_OBJECTAGG";
internal const string JsonArrayAgg = "JSON_ARRAYAGG";
internal const string JsonQuery = "JSON_QUERY";
+ internal const string JsonValue = "JSON_VALUE";
internal const string Array = "ARRAY";
internal const string Wrapper = "WRAPPER";
internal const string Keep = "KEEP";
diff --git a/SqlScriptDom/Parser/TSql/TSql170.g b/SqlScriptDom/Parser/TSql/TSql170.g
index d746306..1850132 100644
--- a/SqlScriptDom/Parser/TSql/TSql170.g
+++ b/SqlScriptDom/Parser/TSql/TSql170.g
@@ -32631,22 +32631,86 @@ expressionList [TSqlFragment vParent, IList expressions]
)
;
+/* jsonReturningClause is used by json_object, json_objectagg, json_array, json_arrayagg where only
+ RETURNING JSON is supported. Any other type with JSON should return in error */
jsonReturningClause [FunctionCall vParent]
{
- Identifier vJson;
+ DataTypeReference vDataType;
}
:
- tReturning:Identifier tJson:Identifier
+ tReturning:Identifier vDataType=jsonDataType
{
Match(tReturning, CodeGenerationSupporter.Returning);
- Match(tJson, CodeGenerationSupporter.Json);
- UpdateTokenInfo(vParent,tJson);
- vJson = FragmentFactory.CreateFragment();
- AddAndUpdateTokenInfo(vParent, vParent.ReturnType, vJson);
- vJson.SetUnquotedIdentifier(tJson.getText());
+ UpdateTokenInfo(vParent, tReturning);
+ vParent.ReturnType.Add(vDataType);
+ }
+;
+
+jsonDataType returns [SqlDataTypeReference vResult = null]
+{
+ SchemaObjectName vJsonTypeName;
+}
+:
+ vJsonTypeName=schemaObjectTwoPartName
+ {
+ // Only allow JSON as the data type
+ if (vJsonTypeName.BaseIdentifier.Value.ToUpper(CultureInfo.InvariantCulture) != CodeGenerationSupporter.Json)
+ {
+ ThrowParseErrorException("SQL46005", vJsonTypeName,
+ TSqlParserResource.SQL46005Message, CodeGenerationSupporter.Json, vJsonTypeName.BaseIdentifier.Value);
+ }
+
+ vResult = FragmentFactory.CreateFragment();
+ vResult.Name = vJsonTypeName;
+ vResult.SqlDataTypeOption = SqlDataTypeOption.Json;
+ vResult.UpdateTokenInfo(vJsonTypeName);
+ }
+;
+
+/* jsonValueReturningClause is used by json_value. Only json_value support RETURNING syntax*/
+jsonValueReturningClause [FunctionCall vParent]
+{
+ DataTypeReference vDataType;
+}
+:
+ tReturning:Identifier vDataType=scalarDataType
+ {
+ Match(tReturning, CodeGenerationSupporter.Returning);
+ UpdateTokenInfo(vParent, tReturning);
+
+ // JSON_VALUE only supports specific SQL data types in RETURNING clause
+ if (vDataType is SqlDataTypeReference sqlDataType)
+ {
+ bool isAllowedType = sqlDataType.SqlDataTypeOption == SqlDataTypeOption.Int ||
+ sqlDataType.SqlDataTypeOption == SqlDataTypeOption.TinyInt ||
+ sqlDataType.SqlDataTypeOption == SqlDataTypeOption.SmallInt ||
+ sqlDataType.SqlDataTypeOption == SqlDataTypeOption.BigInt ||
+ sqlDataType.SqlDataTypeOption == SqlDataTypeOption.Float ||
+ sqlDataType.SqlDataTypeOption == SqlDataTypeOption.Real ||
+ sqlDataType.SqlDataTypeOption == SqlDataTypeOption.Decimal ||
+ sqlDataType.SqlDataTypeOption == SqlDataTypeOption.Numeric ||
+ sqlDataType.SqlDataTypeOption == SqlDataTypeOption.Bit ||
+ sqlDataType.SqlDataTypeOption == SqlDataTypeOption.VarChar ||
+ sqlDataType.SqlDataTypeOption == SqlDataTypeOption.NVarChar ||
+ sqlDataType.SqlDataTypeOption == SqlDataTypeOption.Char ||
+ sqlDataType.SqlDataTypeOption == SqlDataTypeOption.NChar ||
+ sqlDataType.SqlDataTypeOption == SqlDataTypeOption.Date ||
+ sqlDataType.SqlDataTypeOption == SqlDataTypeOption.Time ||
+ sqlDataType.SqlDataTypeOption == SqlDataTypeOption.DateTime2 ||
+ sqlDataType.SqlDataTypeOption == SqlDataTypeOption.DateTimeOffset;
+
+ if (!isAllowedType)
+ {
+ ThrowParseErrorException("SQL46005", vDataType,
+ TSqlParserResource.SQL46005Message, "supported data type", sqlDataType.SqlDataTypeOption.ToString());
+ }
+ }
+
+ vParent.ReturnType.Add(vDataType);
}
;
+
jsonKeyValueExpression returns [JsonKeyValue vResult = FragmentFactory.CreateFragment()]
{
ScalarExpression vKey;
@@ -32970,6 +33034,9 @@ builtInFunctionCall returns [FunctionCall vResult = FragmentFactory.CreateFragme
|
{(vResult.FunctionName != null && vResult.FunctionName.Value.ToUpper(CultureInfo.InvariantCulture) == CodeGenerationSupporter.JsonQuery)}?
jsonQueryBuiltInFunctionCall[vResult]
+ |
+ {(vResult.FunctionName != null && vResult.FunctionName.Value.ToUpper(CultureInfo.InvariantCulture) == CodeGenerationSupporter.JsonValue)}?
+ jsonValueBuiltInFunctionCall[vResult]
|
{(vResult.FunctionName != null && vResult.FunctionName.Value.ToUpper(CultureInfo.InvariantCulture) == CodeGenerationSupporter.Trim) &&
(NextTokenMatches(CodeGenerationSupporter.Leading) | NextTokenMatches(CodeGenerationSupporter.Trailing) | NextTokenMatches(CodeGenerationSupporter.Both))}?
@@ -33017,6 +33084,7 @@ jsonArrayBuiltInFunctionCall [FunctionCall vParent]
jsonArrayAggBuiltInFunctionCall [FunctionCall vParent]
{
ScalarExpression vExpression;
+ OrderByClause vOrderByClause;
}
: (
vExpression=expression
@@ -33024,6 +33092,14 @@ jsonArrayAggBuiltInFunctionCall [FunctionCall vParent]
AddAndUpdateTokenInfo(vParent, vParent.Parameters, vExpression);
}
)
+ (
+ vOrderByClause=orderByClause
+ {
+ vParent.JsonOrderByClause = vOrderByClause;
+ }
+ |
+ /* empty */
+ )
(
jsonNullClauseFunction[vParent]
|
@@ -33085,10 +33161,6 @@ jsonQueryBuiltInFunctionCall [FunctionCall vParent]
AddAndUpdateTokenInfo(vParent, vParent.Parameters, vPath);
}
)?
- tRParen:RightParenthesis
- {
- UpdateTokenInfo(vParent, tRParen);
- }
(
With tArray:Identifier tWrapper:Identifier
{
@@ -33103,6 +33175,34 @@ jsonQueryBuiltInFunctionCall [FunctionCall vParent]
vParent.WithArrayWrapper = true;
}
)?
+ tRParen:RightParenthesis
+ {
+ UpdateTokenInfo(vParent, tRParen);
+ }
+ ;
+
+jsonValueBuiltInFunctionCall [FunctionCall vParent]
+{
+ ScalarExpression vExpression;
+ ScalarExpression vPath;
+}
+ : vExpression=expression
+ {
+ AddAndUpdateTokenInfo(vParent, vParent.Parameters, vExpression);
+ }
+ Comma vPath=expression
+ {
+ AddAndUpdateTokenInfo(vParent, vParent.Parameters, vPath);
+ }
+ (
+ jsonValueReturningClause[vParent]
+ |
+ /* empty */
+ )
+ tRParen:RightParenthesis
+ {
+ UpdateTokenInfo(vParent, tRParen);
+ }
;
regularBuiltInFunctionCall [FunctionCall vParent]
diff --git a/SqlScriptDom/ScriptDom/SqlServer/ScriptGenerator/SqlScriptGeneratorVisitor.FunctionCall.cs b/SqlScriptDom/ScriptDom/SqlServer/ScriptGenerator/SqlScriptGeneratorVisitor.FunctionCall.cs
index 25c8bd0..20de09c 100644
--- a/SqlScriptDom/ScriptDom/SqlServer/ScriptGenerator/SqlScriptGeneratorVisitor.FunctionCall.cs
+++ b/SqlScriptDom/ScriptDom/SqlServer/ScriptGenerator/SqlScriptGeneratorVisitor.FunctionCall.cs
@@ -85,6 +85,8 @@ public override void ExplicitVisit(FunctionCall node)
else if (node.FunctionName.Value.ToUpper(CultureInfo.InvariantCulture) == CodeGenerationSupporter.JsonArrayAgg)
{
GenerateCommaSeparatedList(node.Parameters);
+ // Generate ORDER BY clause if present
+ GenerateSpaceAndFragmentIfNotNull(node.JsonOrderByClause);
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);
@@ -96,9 +98,8 @@ public override void ExplicitVisit(FunctionCall node)
else if (node.FunctionName.Value.ToUpper(CultureInfo.InvariantCulture) == CodeGenerationSupporter.JsonQuery)
{
GenerateCommaSeparatedList(node.Parameters);
- GenerateSymbol(TSqlTokenType.RightParenthesis);
- // Handle WITH ARRAY WRAPPER clause
+ // Handle WITH ARRAY WRAPPER clause - inside parentheses
if (node.WithArrayWrapper)
{
GenerateSpace();
@@ -108,6 +109,18 @@ public override void ExplicitVisit(FunctionCall node)
GenerateSpace();
GenerateIdentifier(CodeGenerationSupporter.Wrapper);
}
+
+ GenerateSymbol(TSqlTokenType.RightParenthesis);
+ }
+ 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);
+ }
+ GenerateSymbol(TSqlTokenType.RightParenthesis);
}
else
{
@@ -161,13 +174,47 @@ private void GenerateNullOnNullOrAbsentOnNull(IList list)
GenerateKeyword(TSqlTokenType.Null);
}
}
- private void GenerateReturnType(IList list)
+
+ // Generate returning clause with SQLType.
+ private void GenerateReturnType(IList list)
{
- if (list?.Count > 0 && list[0].Value?.ToUpper(CultureInfo.InvariantCulture) == CodeGenerationSupporter.Json)
+ if (list?.Count > 0)
{
GenerateIdentifier("RETURNING");
GenerateSpace();
- GenerateSpaceSeparatedList(list);
+
+ // Generate each data type correctly
+ for (int i = 0; i < list.Count; i++)
+ {
+ if (i > 0)
+ GenerateSpace();
+
+ // Handle SqlDataTypeReference properly - need to generate the type name and parameters separately
+ if (list[i] is SqlDataTypeReference sqlDataType)
+ {
+ // Generate the data type name (e.g., NVARCHAR)
+ string dataTypeName = sqlDataType.SqlDataTypeOption.ToString().ToUpper(CultureInfo.InvariantCulture);
+ GenerateIdentifier(dataTypeName);
+
+ // Generate parameters if any (e.g., (50))
+ if (sqlDataType.Parameters?.Count > 0)
+ {
+ GenerateSymbol(TSqlTokenType.LeftParenthesis);
+ for (int j = 0; j < sqlDataType.Parameters.Count; j++)
+ {
+ if (j > 0)
+ GenerateSymbol(TSqlTokenType.Comma);
+ GenerateFragmentIfNotNull(sqlDataType.Parameters[j]);
+ }
+ GenerateSymbol(TSqlTokenType.RightParenthesis);
+ }
+ }
+ else
+ {
+ // For other data type references, use the default generation
+ GenerateFragmentIfNotNull(list[i]);
+ }
+ }
}
}
}
diff --git a/Test/SqlDom/Baselines170/JsonArrayAggOrderBy170.sql b/Test/SqlDom/Baselines170/JsonArrayAggOrderBy170.sql
new file mode 100644
index 0000000..d2ab25f
--- /dev/null
+++ b/Test/SqlDom/Baselines170/JsonArrayAggOrderBy170.sql
@@ -0,0 +1,19 @@
+SELECT JSON_ARRAYAGG(value ORDER BY value)
+FROM mytable;
+
+SELECT JSON_ARRAYAGG(name ORDER BY name ASC)
+FROM users;
+
+SELECT JSON_ARRAYAGG(score ORDER BY score DESC)
+FROM scores;
+
+SELECT JSON_ARRAYAGG(data ORDER BY priority DESC, created_at ASC)
+FROM records;
+
+SELECT JSON_ARRAYAGG(value ORDER BY value NULL ON NULL)
+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
diff --git a/Test/SqlDom/Baselines170/JsonFunctionTests170.sql b/Test/SqlDom/Baselines170/JsonFunctionTests170.sql
index c8042bc..75f07eb 100644
--- a/Test/SqlDom/Baselines170/JsonFunctionTests170.sql
+++ b/Test/SqlDom/Baselines170/JsonFunctionTests170.sql
@@ -140,7 +140,7 @@ SELECT JSON_QUERY('{ "a": 1 }');
SELECT JSON_QUERY('{ "a": 1 }', '$.a');
-SELECT JSON_QUERY('{ "a": [1,2,3] }', '$.a') WITH ARRAY WRAPPER;
+SELECT JSON_QUERY('{ "a": [1,2,3] }', '$.a' WITH ARRAY WRAPPER);
GO
@@ -154,4 +154,34 @@ GO
SELECT TOP (5) c.object_id,
JSON_OBJECTAGG(c.name:c.column_id) AS columns
FROM sys.columns AS c
-GROUP BY c.object_id;
\ No newline at end of file
+GROUP BY c.object_id;
+
+SELECT JSON_VALUE('a', '$');
+SELECT JSON_VALUE('c', '$' RETURNING INT);
+SELECT JSON_VALUE('c', '$' RETURNING SMALLINT);
+SELECT JSON_VALUE('c', '$' RETURNING BIGINT);
+SELECT JSON_VALUE('c', '$' RETURNING TINYINT);
+SELECT JSON_VALUE('c', '$' RETURNING NUMERIC);
+SELECT JSON_VALUE('c', '$' RETURNING FLOAT);
+SELECT JSON_VALUE('c', '$' RETURNING REAL);
+SELECT JSON_VALUE('c', '$' RETURNING DECIMAL);
+SELECT JSON_VALUE('c', '$' RETURNING CHAR);
+SELECT JSON_VALUE('c', '$' RETURNING NVARCHAR(50));
+SELECT JSON_VALUE('c', '$' RETURNING NCHAR);
+SELECT JSON_VALUE('c', '$' RETURNING DATE);
+SELECT JSON_VALUE('c', '$' RETURNING DATETIME2);
+SELECT JSON_VALUE('c', '$' RETURNING TIME);
+SELECT JSON_VALUE('c', '$' RETURNING BIT);
+
+SELECT id,
+ json_col
+FROM tab1
+WHERE JSON_CONTAINS(json_col, 'abc', '$.a') = 1;
+
+SELECT id,
+ json_col
+FROM tab1
+WHERE JSON_CONTAINS(json_col, 'abc%', '$.a', 1) = 1;
+
+SELECT JSON_MODIFY(json_col, '$.a', 30)
+FROM tab1;
diff --git a/Test/SqlDom/Baselines170/SecurityStatementExternalModelTests170.sql b/Test/SqlDom/Baselines170/SecurityStatementExternalModelTests170.sql
index a64b7b7..4800677 100644
--- a/Test/SqlDom/Baselines170/SecurityStatementExternalModelTests170.sql
+++ b/Test/SqlDom/Baselines170/SecurityStatementExternalModelTests170.sql
@@ -10,15 +10,15 @@ GRANT EXECUTE
ON EXTERNAL MODEL::MyPredictionModel TO analyst;
GRANT EXECUTE
- ON EXTERNAL MODEL::schema1.ModelName TO user1, user2
+ ON EXTERNAL MODEL::ModelName TO user1, user2
WITH GRANT OPTION;
GRANT CONTROL
- ON EXTERNAL MODEL::dbo.SalesModel TO mladmin
+ ON EXTERNAL MODEL::SalesModel TO mladmin
AS dbo;
GRANT VIEW DEFINITION
- ON EXTERNAL MODEL::MySchema.MyModel TO PUBLIC;
+ ON EXTERNAL MODEL::MyModel TO PUBLIC;
DENY ALTER ANY EXTERNAL MODEL TO restricteduser;
@@ -26,7 +26,7 @@ DENY EXECUTE
ON EXTERNAL MODEL::TestModel TO guest CASCADE;
DENY CONTROL
- ON EXTERNAL MODEL::test.model1 TO user1, user2 CASCADE
+ ON EXTERNAL MODEL::model1 TO user1, user2 CASCADE
AS admin;
REVOKE ALTER ANY EXTERNAL MODEL TO olduser;
@@ -35,22 +35,22 @@ REVOKE GRANT OPTION FOR EXECUTE
ON EXTERNAL MODEL::MyModel TO tempuser CASCADE;
REVOKE CONTROL
- ON EXTERNAL MODEL::prod.RecommendationModel TO contractor
+ ON EXTERNAL MODEL::RecommendationModel TO contractor
AS manager;
REVOKE EXECUTE
- ON EXTERNAL MODEL::dbo.CustomerModel TO PUBLIC CASCADE;
+ ON EXTERNAL MODEL::CustomerModel TO PUBLIC CASCADE;
ALTER AUTHORIZATION
ON EXTERNAL MODEL::MyModel
TO newowner;
ALTER AUTHORIZATION
- ON EXTERNAL MODEL::schema1.ModelName
+ ON EXTERNAL MODEL::ModelName
TO SCHEMA OWNER;
ALTER AUTHORIZATION
- ON EXTERNAL MODEL::test.experimentalmodel
+ ON EXTERNAL MODEL::experimentalmodel
TO datateam;
GRANT EXECUTE, VIEW DEFINITION
@@ -63,7 +63,7 @@ REVOKE EXECUTE, VIEW DEFINITION
ON EXTERNAL MODEL::OldModel TO formeranalyst;
GRANT EXECUTE
- ON EXTERNAL MODEL::MyDatabase.MySchema.ModelWithSpaces TO testuser;
+ ON EXTERNAL MODEL::ModelWithSpaces TO testuser;
DENY ALTER ANY EXTERNAL MODEL TO userwithspaces;
diff --git a/Test/SqlDom/Only170SyntaxTests.cs b/Test/SqlDom/Only170SyntaxTests.cs
index c0d7765..79e81f0 100644
--- a/Test/SqlDom/Only170SyntaxTests.cs
+++ b/Test/SqlDom/Only170SyntaxTests.cs
@@ -19,7 +19,8 @@ public partial class SqlDomTests
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: 19, nErrors90: 16, nErrors100: 15, nErrors110: 15, nErrors120: 15, nErrors130: 15, nErrors140: 15, nErrors150: 15, nErrors160: 15),
- new ParserTest170("JsonFunctionTests170.sql", nErrors80: 13, nErrors90: 8, nErrors100: 38, nErrors110: 38, nErrors120: 38, nErrors130: 38, nErrors140: 38, nErrors150: 38, nErrors160: 38),
+ new ParserTest170("JsonFunctionTests170.sql", nErrors80: 28, nErrors90: 8, nErrors100: 53, nErrors110: 53, nErrors120: 53, nErrors130: 53, nErrors140: 53, nErrors150: 53, nErrors160: 53),
+ new ParserTest170("JsonArrayAggOrderBy170.sql", nErrors80: 6, nErrors90: 6, nErrors100: 6, nErrors110: 6, nErrors120: 6, nErrors130: 6, nErrors140: 6, nErrors150: 6, nErrors160: 6),
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),
new ParserTest170("AlterExternalModelStatementTests170.sql", nErrors80: 2, nErrors90: 2, nErrors100: 2, nErrors110: 2, nErrors120: 2, nErrors130: 5, nErrors140: 5, nErrors150: 5, nErrors160: 5),
diff --git a/Test/SqlDom/ParserErrorsTests.cs b/Test/SqlDom/ParserErrorsTests.cs
index 3644819..1fb3965 100644
--- a/Test/SqlDom/ParserErrorsTests.cs
+++ b/Test/SqlDom/ParserErrorsTests.cs
@@ -472,6 +472,14 @@ public void JsonObjectSyntaxNegativeTest()
// Cannot use incomplete null on null clause cases
ParserTestUtils.ErrorTest160("SELECT JSON_OBJECT('name':'value', 'type':NULL NULL ON)",
new ParserErrorInfo(54, "SQL46010", ")"));
+
+ // Cannot use anything other than JSON in RETURNING clause
+ ParserTestUtils.ErrorTest170("SELECT JSON_OBJECT('name':'a' RETURNING VARCHAR(10))",
+ new ParserErrorInfo(40, "SQL46005", "JSON", "VARCHAR"));
+
+ // Cannot use anything other than JSON in RETURNING clause
+ ParserTestUtils.ErrorTest170("SELECT JSON_OBJECT('name':'a' RETURNING SMALLINT)",
+ new ParserErrorInfo(40, "SQL46005", "JSON", "SMALLINT"));
}
///
@@ -501,6 +509,26 @@ public void JsonArraySyntaxNegativeTest()
// Cannot use incomplete null on null clause cases
ParserTestUtils.ErrorTest160("SELECT JSON_ARRAY('name', 'value', NULL, 'type' ON NULL)",
new ParserErrorInfo(48, "SQL46010", "ON"));
+
+ // Incorrect usage of ORDER BY in JSON_ARRAY
+ ParserTestUtils.ErrorTest170("SELECT JSON_ARRAYAGG(data ORDER priority DESC, created_at ASC) FROM records;",
+ new ParserErrorInfo(32, "SQL46010", "priority"));
+
+ // Incorrect usage of ORDER BY in JSON_ARRAY
+ ParserTestUtils.ErrorTest170("SELECT JSON_ARRAYAGG(data ORDER By) FROM records;",
+ new ParserErrorInfo(34, "SQL46010", ")"));
+
+ // Cannot use anything other than JSON in RETURNING clause
+ ParserTestUtils.ErrorTest170("SELECT JSON_ARRAYAGG('name' RETURNING INT)",
+ new ParserErrorInfo(38, "SQL46005", "JSON", "INT"));
+
+ // Cannot use anything other than JSON in RETURNING clause
+ ParserTestUtils.ErrorTest170("SELECT JSON_ARRAY('name' RETURNING VARCHAR(10))",
+ new ParserErrorInfo(35, "SQL46005", "JSON", "VARCHAR"));
+
+ // Cannot use anything other than JSON in RETURNING clause
+ ParserTestUtils.ErrorTest170("SELECT JSON_ARRAY('name' RETURNING Float)",
+ new ParserErrorInfo(35, "SQL46005", "JSON", "Float"));
}
///
@@ -575,6 +603,31 @@ public void JsonArrayAggSyntaxNegativeTest()
// Cannot use null on null incorrectly
ParserTestUtils.ErrorTest160("SELECT JSON_ARRAYAGG('name', NULL NULL ON)",
new ParserErrorInfo(34, "SQL46010", "NULL"));
+
+ // Cannot use anything other than JSON in RETURNING clause
+ ParserTestUtils.ErrorTest170("SELECT JSON_ARRAYAGG('name' RETURNING INT)",
+ new ParserErrorInfo(38, "SQL46005", "JSON", "INT"));
+
+ // Cannot use anything other than JSON in RETURNING clause
+ ParserTestUtils.ErrorTest170("SELECT JSON_ARRAYAGG('name' RETURNING NVARCHAR(10))",
+ new ParserErrorInfo(38, "SQL46005", "JSON", "NVARCHAR"));
+ }
+
+ ///
+ /// Negative tests for Json_Value syntax in functions
+ ///
+ [TestMethod]
+ [Priority(0)]
+ [SqlStudioTestCategory(Category.UnitTest)]
+ public void JsonValueSyntaxNegativeTest()
+ {
+ // Cannot use anything other than JSON in RETURNING clause
+ ParserTestUtils.ErrorTest170("SELECT JSON_VALUE('', '$' RETURNING JSON)",
+ new ParserErrorInfo(36, "SQL46005", "supported data type", "Json"));
+
+ // Cannot use anything other than JSON in RETURNING clause
+ ParserTestUtils.ErrorTest170("SELECT JSON_VALUE('', '$' RETURNING Vector)",
+ new ParserErrorInfo(36, "SQL46005", "supported data type", "Vector"));
}
///
@@ -590,28 +643,24 @@ public void JsonQuerySyntaxNegativeTest()
new ParserErrorInfo(32, "SQL46010", "WITH"));
// Cannot use WITH ARRAY without WRAPPER (unexpected end of file)
- ParserTestUtils.ErrorTest170("SELECT JSON_QUERY('{ \"a\": 1 }') WITH ARRAY",
- new ParserErrorInfo(42, "SQL46029", ""));
+ ParserTestUtils.ErrorTest170("SELECT JSON_QUERY('{ \"a\": 1 }' WITH ARRAY",
+ new ParserErrorInfo(41, "SQL46029", ""));
// Cannot use WITH WRAPPER without ARRAY (unexpected end of file)
- ParserTestUtils.ErrorTest170("SELECT JSON_QUERY('{ \"a\": 1 }') WITH WRAPPER",
- new ParserErrorInfo(44, "SQL46029", ""));
+ ParserTestUtils.ErrorTest170("SELECT JSON_QUERY('{ \"a\": 1 }' WITH WRAPPER",
+ new ParserErrorInfo(43, "SQL46029", ""));
// Cannot use incorrect keyword instead of ARRAY
- ParserTestUtils.ErrorTest170("SELECT JSON_QUERY('{ \"a\": 1 }') WITH OBJECT WRAPPER",
- new ParserErrorInfo(37, "SQL46010", "OBJECT"));
+ ParserTestUtils.ErrorTest170("SELECT JSON_QUERY('{ \"a\": 1 }' WITH OBJECT WRAPPER",
+ new ParserErrorInfo(36, "SQL46010", "OBJECT"));
// Cannot use incorrect keyword instead of WRAPPER
- ParserTestUtils.ErrorTest170("SELECT JSON_QUERY('{ \"a\": 1 }') WITH ARRAY OBJECT",
- new ParserErrorInfo(43, "SQL46010", "OBJECT"));
+ ParserTestUtils.ErrorTest170("SELECT JSON_QUERY('{ \"a\": 1 }' WITH ARRAY OBJECT",
+ new ParserErrorInfo(42, "SQL46010", "OBJECT"));
// Cannot use JSON_QUERY with colon syntax (key:value pairs like JSON_OBJECT)
ParserTestUtils.ErrorTest170("SELECT JSON_QUERY('name':'value')",
new ParserErrorInfo(24, "SQL46010", ":"));
-
- // WITH ARRAY WRAPPER must come after closing parenthesis, not before
- ParserTestUtils.ErrorTest170("SELECT JSON_QUERY('{ \"a\": 1 }' WITH ARRAY WRAPPER)",
- new ParserErrorInfo(31, "SQL46010", "WITH"));
}
///
@@ -7047,19 +7096,11 @@ FROM @SalesData
[SqlStudioTestCategory(Category.UnitTest)]
public void IdentityColumnNegativeTestsFabricDW()
{
- string identityColumnSyntax = @"CREATE TABLE TestTable1 (
- ID INT IDENTITY(1,1),
- Name VARCHAR(50)
- );
- ";
- ParserTestUtils.ErrorTestFabricDW(identityColumnSyntax, new ParserErrorInfo(identityColumnSyntax.IndexOf("IDENTITY(") + 8, "SQL46010", "("));
+ string identityColumnSyntax = @"CREATE TABLE TestTable1 (ID INT IDENTITY(1,1), Name VARCHAR(50));";
+ ParserTestUtils.ErrorTestFabricDW(identityColumnSyntax, new ParserErrorInfo(40, "SQL46010", "("));
- string identityColumnSyntax2 = @"CREATE TABLE TestTable2 (
- RecordID BIGINT IDENTITY(100,5),
- Description NVARCHAR(200)
- );
- ";
- ParserTestUtils.ErrorTestFabricDW(identityColumnSyntax2, new ParserErrorInfo(identityColumnSyntax2.IndexOf("IDENTITY(") + 8, "SQL46010", "("));
+ string identityColumnSyntax2 = @"CREATE TABLE TestTable2 (RecordID BIGINT IDENTITY(100,5), Description NVARCHAR(200));";
+ ParserTestUtils.ErrorTestFabricDW(identityColumnSyntax2, new ParserErrorInfo(49, "SQL46010", "("));
}
///
diff --git a/Test/SqlDom/TestScripts/JsonArrayAggOrderBy170.sql b/Test/SqlDom/TestScripts/JsonArrayAggOrderBy170.sql
new file mode 100644
index 0000000..094fb21
--- /dev/null
+++ b/Test/SqlDom/TestScripts/JsonArrayAggOrderBy170.sql
@@ -0,0 +1,21 @@
+-- Test JSON_ARRAYAGG with ORDER BY clause
+
+-- Basic ORDER BY
+SELECT JSON_ARRAYAGG(value ORDER BY value) FROM mytable;
+
+-- ORDER BY with ASC
+SELECT JSON_ARRAYAGG(name ORDER BY name ASC) FROM users;
+
+-- ORDER BY with DESC
+SELECT JSON_ARRAYAGG(score ORDER BY score DESC) FROM scores;
+
+-- ORDER BY with multiple columns
+SELECT JSON_ARRAYAGG(data ORDER BY priority DESC, created_at ASC) FROM records;
+
+-- ORDER BY with NULL CLAUSE
+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
diff --git a/Test/SqlDom/TestScripts/JsonFunctionTests170.sql b/Test/SqlDom/TestScripts/JsonFunctionTests170.sql
index 89564cf..39b2901 100644
--- a/Test/SqlDom/TestScripts/JsonFunctionTests170.sql
+++ b/Test/SqlDom/TestScripts/JsonFunctionTests170.sql
@@ -133,7 +133,7 @@ GO
SELECT JSON_QUERY('{ "a": 1 }');
SELECT JSON_QUERY('{ "a": 1 }', '$.a');
-SELECT JSON_QUERY('{ "a": [1,2,3] }', '$.a') WITH ARRAY WRAPPER;
+SELECT JSON_QUERY('{ "a": [1,2,3] }', '$.a' WITH ARRAY WRAPPER);
GO
CREATE VIEW dbo.jsonfunctest AS
@@ -146,3 +146,36 @@ GO
SELECT TOP(5) c.object_id, JSON_OBJECTAGG(c.name:c.column_id) AS columns
FROM sys.columns AS c
GROUP BY c.object_id;
+
+SELECT JSON_VALUE('a', '$');
+SELECT JSON_VALUE('c', '$' RETURNING INT);
+SELECT JSON_VALUE('c', '$' RETURNING SMALLINT);
+SELECT JSON_VALUE('c', '$' RETURNING BIGINT);
+SELECT JSON_VALUE('c', '$' RETURNING TINYINT);
+SELECT JSON_VALUE('c', '$' RETURNING NUMERIC);
+SELECT JSON_VALUE('c', '$' RETURNING FLOAT);
+SELECT JSON_VALUE('c', '$' RETURNING REAL);
+SELECT JSON_VALUE('c', '$' RETURNING DECIMAL);
+SELECT JSON_VALUE('c', '$' RETURNING CHAR);
+SELECT JSON_VALUE('c', '$' RETURNING NVARCHAR(50));
+SELECT JSON_VALUE('c', '$' RETURNING NCHAR);
+SELECT JSON_VALUE('c', '$' RETURNING DATE);
+SELECT JSON_VALUE('c', '$' RETURNING DATETIME2);
+SELECT JSON_VALUE('c', '$' RETURNING TIME);
+SELECT JSON_VALUE('c', '$' RETURNING BIT);
+
+-- Json_Contains
+SELECT id,
+ json_col
+FROM tab1
+WHERE JSON_CONTAINS(json_col, 'abc', '$.a') = 1;
+
+-- Json_Contains as LIKE
+SELECT id,
+ json_col
+FROM tab1
+WHERE JSON_CONTAINS(json_col, 'abc%', '$.a', 1) = 1;
+
+-- Json_Modify
+SELECT JSON_MODIFY(json_col, '$.a', 30)
+FROM tab1;
diff --git a/Test/SqlDom/TestScripts/SecurityStatementExternalModelTests170.sql b/Test/SqlDom/TestScripts/SecurityStatementExternalModelTests170.sql
index 04442d6..679953a 100644
--- a/Test/SqlDom/TestScripts/SecurityStatementExternalModelTests170.sql
+++ b/Test/SqlDom/TestScripts/SecurityStatementExternalModelTests170.sql
@@ -6,19 +6,19 @@ GRANT ALTER ANY EXTERNAL MODEL TO mlrole, adminrole
GRANT EXECUTE
ON EXTERNAL MODEL::MyPredictionModel TO analyst;
GRANT EXECUTE
- ON EXTERNAL MODEL::schema1.ModelName TO user1, user2
+ ON EXTERNAL MODEL::ModelName TO user1, user2
WITH GRANT OPTION;
GRANT CONTROL
- ON EXTERNAL MODEL::dbo.SalesModel TO mladmin
+ ON EXTERNAL MODEL::SalesModel TO mladmin
AS dbo;
GRANT VIEW DEFINITION
- ON EXTERNAL MODEL::MySchema.MyModel TO PUBLIC;
+ ON EXTERNAL MODEL::MyModel TO PUBLIC;
DENY ALTER ANY EXTERNAL MODEL TO restricteduser;
DENY EXECUTE
ON EXTERNAL MODEL::TestModel TO guest
CASCADE;
DENY CONTROL
- ON EXTERNAL MODEL::test.model1 TO user1, user2
+ ON EXTERNAL MODEL::model1 TO user1, user2
CASCADE
AS admin;
REVOKE ALTER ANY EXTERNAL MODEL FROM olduser;
@@ -26,14 +26,14 @@ REVOKE GRANT OPTION FOR EXECUTE
ON EXTERNAL MODEL::MyModel FROM tempuser
CASCADE;
REVOKE CONTROL
- ON EXTERNAL MODEL::prod.RecommendationModel FROM contractor
+ ON EXTERNAL MODEL::RecommendationModel FROM contractor
AS manager;
REVOKE EXECUTE
- ON EXTERNAL MODEL::dbo.CustomerModel FROM PUBLIC
+ ON EXTERNAL MODEL::CustomerModel FROM PUBLIC
CASCADE;
ALTER AUTHORIZATION ON EXTERNAL MODEL::MyModel TO newowner;
-ALTER AUTHORIZATION ON EXTERNAL MODEL::schema1.ModelName TO SCHEMA OWNER;
-ALTER AUTHORIZATION ON EXTERNAL MODEL::test.experimentalmodel TO datateam;
+ALTER AUTHORIZATION ON EXTERNAL MODEL::ModelName TO SCHEMA OWNER;
+ALTER AUTHORIZATION ON EXTERNAL MODEL::experimentalmodel TO datateam;
GRANT EXECUTE, VIEW DEFINITION
ON EXTERNAL MODEL::MultiPermModel TO analyst;
DENY EXECUTE, CONTROL
@@ -41,7 +41,7 @@ DENY EXECUTE, CONTROL
REVOKE EXECUTE, VIEW DEFINITION
ON EXTERNAL MODEL::OldModel FROM formeranalyst;
GRANT EXECUTE
- ON EXTERNAL MODEL::MyDatabase.MySchema.ModelWithSpaces TO testuser;
+ ON EXTERNAL MODEL::ModelWithSpaces TO testuser;
DENY ALTER ANY EXTERNAL MODEL TO userwithspaces;
REVOKE CONTROL
ON EXTERNAL MODEL::QuotedModel FROM QuotedUser;
\ No newline at end of file
diff --git a/release-notes/170/170.128.0.md b/release-notes/170/170.128.0.md
new file mode 100644
index 0000000..77acacb
--- /dev/null
+++ b/release-notes/170/170.128.0.md
@@ -0,0 +1,26 @@
+# Release Notes
+
+## Microsoft.SqlServer.TransactSql.ScriptDom 170.128.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.414
+
+#### .NET Framework
+#### .NET Core
+
+### New Features
+* Adds support for RETURNING option for JSON functions
+
+### Fixed
+* Fixes https://github.com/microsoft/SqlScriptDOM/issues/159
+* Fixes https://github.com/microsoft/SqlScriptDOM/issues/170
+### Changes
+
+### Known Issues