From f0dfd2ef96344efcacb76c5e8793a82c29534b4f Mon Sep 17 00:00:00 2001 From: Aasim Khan Date: Tue, 21 Apr 2026 11:19:56 -0700 Subject: [PATCH] Add support for DATEADD, DATEDIFF, and related functions; normalize parameters in function calls --- .../Parser/TSql/CodeGenerationSupporter.cs | 7 +++ SqlScriptDom/Parser/TSql/TSql100.g | 3 ++ SqlScriptDom/Parser/TSql/TSql110.g | 3 ++ SqlScriptDom/Parser/TSql/TSql120.g | 3 ++ SqlScriptDom/Parser/TSql/TSql130.g | 3 ++ SqlScriptDom/Parser/TSql/TSql140.g | 3 ++ SqlScriptDom/Parser/TSql/TSql150.g | 3 ++ SqlScriptDom/Parser/TSql/TSql160.g | 3 ++ SqlScriptDom/Parser/TSql/TSql170.g | 3 ++ SqlScriptDom/Parser/TSql/TSql180.g | 3 ++ SqlScriptDom/Parser/TSql/TSql80.g | 3 ++ .../Parser/TSql/TSql80ParserBaseInternal.cs | 47 ++++++++++++++++++- SqlScriptDom/Parser/TSql/TSql90.g | 3 ++ SqlScriptDom/Parser/TSql/TSqlFabricDW.g | 3 ++ Test/SqlDom/Only160SyntaxTests.cs | 31 ++++++++++++ 15 files changed, 120 insertions(+), 1 deletion(-) diff --git a/SqlScriptDom/Parser/TSql/CodeGenerationSupporter.cs b/SqlScriptDom/Parser/TSql/CodeGenerationSupporter.cs index 0ea6731..a7e52ae 100644 --- a/SqlScriptDom/Parser/TSql/CodeGenerationSupporter.cs +++ b/SqlScriptDom/Parser/TSql/CodeGenerationSupporter.cs @@ -293,13 +293,20 @@ internal static class CodeGenerationSupporter internal const string DataRetention = "DATA_RETENTION"; internal const string DataSource = "DATA_SOURCE"; internal const string Date = "DATE"; + internal const string DateAdd = "DATEADD"; + internal const string DateBucket = "DATE_BUCKET"; internal const string DateCorrelationOptimization = "DATE_CORRELATION_OPTIMIZATION"; + internal const string DateDiff = "DATEDIFF"; + internal const string DateDiffBig = "DATEDIFF_BIG"; internal const string DateFirst = "DATEFIRST"; internal const string DateFormat = "DATEFORMAT"; internal const string DateFormat2 = "DATE_FORMAT"; + internal const string DateName = "DATENAME"; + internal const string DatePart = "DATEPART"; internal const string DateTime = "DATETIME"; internal const string DateTime2 = "DATETIME2"; internal const string DateTimeOffset = "DATETIMEOFFSET"; + internal const string DateTrunc = "DATETRUNC"; internal const string Deterministic = "DETERMINISTIC"; internal const string DboOnly = "DBO_ONLY"; internal const string DbChaining = "DB_CHAINING"; diff --git a/SqlScriptDom/Parser/TSql/TSql100.g b/SqlScriptDom/Parser/TSql/TSql100.g index c0cbb0e..c17c2f9 100644 --- a/SqlScriptDom/Parser/TSql/TSql100.g +++ b/SqlScriptDom/Parser/TSql/TSql100.g @@ -20874,6 +20874,9 @@ regularBuiltInFunctionCall [FunctionCall vParent] : ( expressionList[vParent, vParent.Parameters] + { + NormalizeDatePartFunctionFirstParameter(vParent); + } | vColumn=starColumnReferenceExpression { diff --git a/SqlScriptDom/Parser/TSql/TSql110.g b/SqlScriptDom/Parser/TSql/TSql110.g index 508e6b7..a4974e1 100644 --- a/SqlScriptDom/Parser/TSql/TSql110.g +++ b/SqlScriptDom/Parser/TSql/TSql110.g @@ -23849,6 +23849,9 @@ regularBuiltInFunctionCall [FunctionCall vParent] : ( expressionList[vParent, vParent.Parameters] + { + NormalizeDatePartFunctionFirstParameter(vParent); + } | vColumn=starColumnReferenceExpression { diff --git a/SqlScriptDom/Parser/TSql/TSql120.g b/SqlScriptDom/Parser/TSql/TSql120.g index 78126da..ca52e50 100644 --- a/SqlScriptDom/Parser/TSql/TSql120.g +++ b/SqlScriptDom/Parser/TSql/TSql120.g @@ -24598,6 +24598,9 @@ regularBuiltInFunctionCall [FunctionCall vParent] : ( expressionList[vParent, vParent.Parameters] + { + NormalizeDatePartFunctionFirstParameter(vParent); + } | vColumn=starColumnReferenceExpression { diff --git a/SqlScriptDom/Parser/TSql/TSql130.g b/SqlScriptDom/Parser/TSql/TSql130.g index a51aeb9..1212d2b 100644 --- a/SqlScriptDom/Parser/TSql/TSql130.g +++ b/SqlScriptDom/Parser/TSql/TSql130.g @@ -29420,6 +29420,9 @@ regularBuiltInFunctionCall [FunctionCall vParent] : ( expressionList[vParent, vParent.Parameters] + { + NormalizeDatePartFunctionFirstParameter(vParent); + } | vColumn=starColumnReferenceExpression { diff --git a/SqlScriptDom/Parser/TSql/TSql140.g b/SqlScriptDom/Parser/TSql/TSql140.g index 3139191..28a12af 100644 --- a/SqlScriptDom/Parser/TSql/TSql140.g +++ b/SqlScriptDom/Parser/TSql/TSql140.g @@ -30237,6 +30237,9 @@ regularBuiltInFunctionCall [FunctionCall vParent] : ( expressionList[vParent, vParent.Parameters] + { + NormalizeDatePartFunctionFirstParameter(vParent); + } | vColumn=starColumnReferenceExpression { diff --git a/SqlScriptDom/Parser/TSql/TSql150.g b/SqlScriptDom/Parser/TSql/TSql150.g index 35a14f1..c34de80 100644 --- a/SqlScriptDom/Parser/TSql/TSql150.g +++ b/SqlScriptDom/Parser/TSql/TSql150.g @@ -31390,6 +31390,9 @@ regularBuiltInFunctionCall [FunctionCall vParent] : ( expressionList[vParent, vParent.Parameters] + { + NormalizeDatePartFunctionFirstParameter(vParent); + } | vColumn=starColumnReferenceExpression { diff --git a/SqlScriptDom/Parser/TSql/TSql160.g b/SqlScriptDom/Parser/TSql/TSql160.g index 4d794ca..d080006 100644 --- a/SqlScriptDom/Parser/TSql/TSql160.g +++ b/SqlScriptDom/Parser/TSql/TSql160.g @@ -32215,6 +32215,9 @@ regularBuiltInFunctionCall [FunctionCall vParent] : ( expressionList[vParent, vParent.Parameters] + { + NormalizeDatePartFunctionFirstParameter(vParent); + } | vColumn=starColumnReferenceExpression { diff --git a/SqlScriptDom/Parser/TSql/TSql170.g b/SqlScriptDom/Parser/TSql/TSql170.g index 88ce660..6739f8f 100644 --- a/SqlScriptDom/Parser/TSql/TSql170.g +++ b/SqlScriptDom/Parser/TSql/TSql170.g @@ -33343,6 +33343,9 @@ regularBuiltInFunctionCall [FunctionCall vParent] : ( expressionList[vParent, vParent.Parameters] + { + NormalizeDatePartFunctionFirstParameter(vParent); + } | vColumn=starColumnReferenceExpression { diff --git a/SqlScriptDom/Parser/TSql/TSql180.g b/SqlScriptDom/Parser/TSql/TSql180.g index 19e66ad..ea84bc2 100644 --- a/SqlScriptDom/Parser/TSql/TSql180.g +++ b/SqlScriptDom/Parser/TSql/TSql180.g @@ -33343,6 +33343,9 @@ regularBuiltInFunctionCall [FunctionCall vParent] : ( expressionList[vParent, vParent.Parameters] + { + NormalizeDatePartFunctionFirstParameter(vParent); + } | vColumn=starColumnReferenceExpression { diff --git a/SqlScriptDom/Parser/TSql/TSql80.g b/SqlScriptDom/Parser/TSql/TSql80.g index b97ddc0..87fc761 100644 --- a/SqlScriptDom/Parser/TSql/TSql80.g +++ b/SqlScriptDom/Parser/TSql/TSql80.g @@ -9103,6 +9103,9 @@ identifierBuiltInFunctionCallDefaultParams [FunctionCall vParent] } : expressionList[vParent, vParent.Parameters] + { + NormalizeDatePartFunctionFirstParameter(vParent); + } | vColumn=starColumnReferenceExpression { diff --git a/SqlScriptDom/Parser/TSql/TSql80ParserBaseInternal.cs b/SqlScriptDom/Parser/TSql/TSql80ParserBaseInternal.cs index 4f47429..5607c18 100644 --- a/SqlScriptDom/Parser/TSql/TSql80ParserBaseInternal.cs +++ b/SqlScriptDom/Parser/TSql/TSql80ParserBaseInternal.cs @@ -1979,6 +1979,51 @@ protected void PutIdentifiersIntoFunctionCall(FunctionCall functionCall, MultiPa } } + protected void NormalizeDatePartFunctionFirstParameter(FunctionCall functionCall) + { + if (functionCall == null || + functionCall.FunctionName == null || + String.IsNullOrEmpty(functionCall.FunctionName.Value) || + functionCall.Parameters == null || + functionCall.Parameters.Count == 0 || + !IsDatePartFunction(functionCall.FunctionName.Value)) + { + return; + } + + ColumnReferenceExpression columnReference = functionCall.Parameters[0] as ColumnReferenceExpression; + if (columnReference == null || + columnReference.MultiPartIdentifier == null || + columnReference.MultiPartIdentifier.Count != 1) + { + return; + } + + Identifier identifier = columnReference.MultiPartIdentifier[0]; + IdentifierLiteral literal = FragmentFactory.CreateFragment(); + literal.Value = identifier.Value; + literal.QuoteType = identifier.QuoteType; + literal.UpdateTokenInfo(columnReference); + functionCall.Parameters[0] = literal; + } + + private static bool IsDatePartFunction(string functionName) + { + switch (functionName.ToUpper(CultureInfo.InvariantCulture)) + { + case CodeGenerationSupporter.DateAdd: + case CodeGenerationSupporter.DateBucket: + case CodeGenerationSupporter.DateDiff: + case CodeGenerationSupporter.DateDiffBig: + case CodeGenerationSupporter.DateName: + case CodeGenerationSupporter.DatePart: + case CodeGenerationSupporter.DateTrunc: + return true; + default: + return false; + } + } + protected void VerifyColumnDataType(ColumnDefinition column) { // If the scalarDataType is not parsed, the ColumnIdentifier has to be a timestamp. @@ -2426,4 +2471,4 @@ protected static TSqlParseErrorException GetUnexpectedTokenErrorException(Identi #endregion } -} \ No newline at end of file +} diff --git a/SqlScriptDom/Parser/TSql/TSql90.g b/SqlScriptDom/Parser/TSql/TSql90.g index 77f27a1..9b6d0ad 100644 --- a/SqlScriptDom/Parser/TSql/TSql90.g +++ b/SqlScriptDom/Parser/TSql/TSql90.g @@ -16550,6 +16550,9 @@ regularBuiltInFunctionCall [FunctionCall vParent] : ( expressionList[vParent, vParent.Parameters] + { + NormalizeDatePartFunctionFirstParameter(vParent); + } | vColumn=starColumnReferenceExpression { diff --git a/SqlScriptDom/Parser/TSql/TSqlFabricDW.g b/SqlScriptDom/Parser/TSql/TSqlFabricDW.g index 21fd33d..05cdd61 100644 --- a/SqlScriptDom/Parser/TSql/TSqlFabricDW.g +++ b/SqlScriptDom/Parser/TSql/TSqlFabricDW.g @@ -32381,6 +32381,9 @@ regularBuiltInFunctionCall [FunctionCall vParent] : ( expressionList[vParent, vParent.Parameters] + { + NormalizeDatePartFunctionFirstParameter(vParent); + } | vColumn=starColumnReferenceExpression { diff --git a/Test/SqlDom/Only160SyntaxTests.cs b/Test/SqlDom/Only160SyntaxTests.cs index 552152b..1c504d9 100644 --- a/Test/SqlDom/Only160SyntaxTests.cs +++ b/Test/SqlDom/Only160SyntaxTests.cs @@ -154,6 +154,37 @@ public void TSql160Azure_SyntaxIn160ParserTest() } } + [TestMethod] + [Priority(0)] + [SqlStudioTestCategory(Category.UnitTest)] + public void DatePartFunctionParametersAreIdentifierLiteralsTest() + { + TSql160Parser parser = new TSql160Parser(true); + + VerifyDatePartParameter(parser, "SELECT DATEDIFF(mm, ColA, ColB) FROM my_table;", "mm"); + VerifyDatePartParameter(parser, "SELECT DATEADD(day, 1, ColA) FROM my_table;", "day"); + VerifyDatePartParameter(parser, "SELECT DATEDIFF_BIG(second, ColA, ColB) FROM my_table;", "second"); + VerifyDatePartParameter(parser, "SELECT DATENAME(month, ColA) FROM my_table;", "month"); + VerifyDatePartParameter(parser, "SELECT DATEPART(wk, ColA) FROM my_table;", "wk"); + VerifyDatePartParameter(parser, "SELECT DATE_BUCKET(WEEK, 10, ColA) FROM my_table;", "WEEK"); + VerifyDatePartParameter(parser, "SELECT DATETRUNC(year, ColA) FROM my_table;", "year"); + } + + private static void VerifyDatePartParameter(TSqlParser parser, string sql, string expectedDatePart) + { + TSqlFragment fragment = parser.Parse(new System.IO.StringReader(sql), out System.Collections.Generic.IList errors); + + Assert.AreEqual(0, errors.Count, sql); + TSqlScript script = (TSqlScript)fragment; + SelectStatement select = (SelectStatement)script.Batches[0].Statements[0]; + QuerySpecification query = (QuerySpecification)select.QueryExpression; + SelectScalarExpression selectExpression = (SelectScalarExpression)query.SelectElements[0]; + FunctionCall functionCall = (FunctionCall)selectExpression.Expression; + + Assert.IsInstanceOfType(functionCall.Parameters[0], typeof(IdentifierLiteral), sql); + Assert.AreEqual(expectedDatePart, ((IdentifierLiteral)functionCall.Parameters[0]).Value, sql); + } + [TestMethod] [Priority(0)] [SqlStudioTestCategory(Category.UnitTest)]