diff --git a/TSQL_Parser/TSQL_Parser/Clauses/Parsers/TSQLForClauseParser.cs b/TSQL_Parser/TSQL_Parser/Clauses/Parsers/TSQLForClauseParser.cs index 91e8e7a..f103e3e 100644 --- a/TSQL_Parser/TSQL_Parser/Clauses/Parsers/TSQLForClauseParser.cs +++ b/TSQL_Parser/TSQL_Parser/Clauses/Parsers/TSQLForClauseParser.cs @@ -6,6 +6,7 @@ using TSQL.Statements; using TSQL.Statements.Parsers; using TSQL.Tokens; +using TSQL.Tokens.Parsers; namespace TSQL.Clauses.Parsers { @@ -23,24 +24,43 @@ public TSQLForClause Parse(ITSQLTokenizer tokenizer) forClause.Tokens.Add(tokenizer.Current); - while ( - tokenizer.MoveNext() && - !tokenizer.Current.IsCharacter(TSQLCharacters.Semicolon) && - ( - tokenizer.Current.Type != TSQLTokenType.Keyword || - ( - tokenizer.Current.Type == TSQLTokenType.Keyword && - !tokenizer.Current.AsKeyword.Keyword.In - ( - TSQLKeywords.OPTION - ) && - !tokenizer.Current.AsKeyword.Keyword.IsStatementStart() - ) - )) + TSQLTokenParserHelper.ReadThroughAnyCommentsOrWhitespace( + tokenizer, + forClause.Tokens); + + if (tokenizer.Current.AsIdentifier?.Text?.ToUpper() != "XML") { - forClause.Tokens.Add(tokenizer.Current); + throw new InvalidOperationException("XML expected."); } + forClause.Tokens.Add(tokenizer.Current); + + TSQLTokenParserHelper.ReadThroughAnyCommentsOrWhitespace( + tokenizer, + forClause.Tokens); + + // https://docs.microsoft.com/en-us/sql/relational-databases/xml/for-xml-sql-server?view=sql-server-ver16 + + if (!new List + { + "RAW", + "AUTO", + "EXPLICIT", + "PATH" + }.Contains(tokenizer.Current.AsIdentifier?.Text?.ToUpper())) + { + throw new InvalidOperationException("RAW, AUTO, EXPLICIT, or PATH expected."); + } + + forClause.Tokens.Add(tokenizer.Current); + + TSQLTokenParserHelper.ReadUntilStop( + tokenizer, + forClause, + new List { }, + new List { }, + lookForStatementStarts: true); + return forClause; } } diff --git a/TSQL_Parser/TSQL_Parser/Expressions/Parsers/TSQLSelectExpressionParser.cs b/TSQL_Parser/TSQL_Parser/Expressions/Parsers/TSQLSelectExpressionParser.cs index 5152d1a..18092d5 100644 --- a/TSQL_Parser/TSQL_Parser/Expressions/Parsers/TSQLSelectExpressionParser.cs +++ b/TSQL_Parser/TSQL_Parser/Expressions/Parsers/TSQLSelectExpressionParser.cs @@ -5,7 +5,6 @@ using System.Threading.Tasks; using TSQL.Tokens; -using TSQL.Tokens.Parsers; namespace TSQL.Expressions.Parsers { @@ -17,7 +16,6 @@ public TSQLExpression Parse(ITSQLTokenizer tokenizer) if ( tokenizer.Current != null && - tokenizer.Current.Text != "*" && tokenizer.Current.Type.In( TSQLTokenType.Operator) && diff --git a/TSQL_Parser/TSQL_Parser/Properties/AssemblyInfo.cs b/TSQL_Parser/TSQL_Parser/Properties/AssemblyInfo.cs index 453991e..8f79c29 100644 --- a/TSQL_Parser/TSQL_Parser/Properties/AssemblyInfo.cs +++ b/TSQL_Parser/TSQL_Parser/Properties/AssemblyInfo.cs @@ -32,7 +32,7 @@ // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("2.4.0.0")] -[assembly: AssemblyFileVersion("2.4.0.0")] +[assembly: AssemblyVersion("2.5.0.0")] +[assembly: AssemblyFileVersion("2.5.0.0")] [assembly: InternalsVisibleTo("Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100793625650b945744f8a2c57bc75da89cd4d2c551636aa180c3020b7a15b815c10e983e83c312eb02f131c6fcf18aaffd6c8d9af6c4353c91ca0e9206b0fb8fb7805fc07b510a47ff40705ae56977ae8893e2d247d166aa400926582840e8a5602df055762bc3479dd14c9621a77946b6e6b0a00a77204c78fb52c65121bd75ba")] \ No newline at end of file diff --git a/TSQL_Parser/TSQL_Parser/Push.bat b/TSQL_Parser/TSQL_Parser/Push.bat index ccdbae9..573899b 100644 --- a/TSQL_Parser/TSQL_Parser/Push.bat +++ b/TSQL_Parser/TSQL_Parser/Push.bat @@ -1,4 +1,4 @@ nuget SetApiKey %NUGET_KEY% -nuget push TSQL.Parser.2.4.0.snupkg -Source https://api.nuget.org/v3/index.json -nuget push TSQL.Parser.2.4.0.nupkg -Source https://api.nuget.org/v3/index.json +nuget push TSQL.Parser.2.5.0.snupkg -Source https://api.nuget.org/v3/index.json +nuget push TSQL.Parser.2.5.0.nupkg -Source https://api.nuget.org/v3/index.json pause \ No newline at end of file diff --git a/TSQL_Parser/TSQL_Parser/TSQL_Parser.nuspec b/TSQL_Parser/TSQL_Parser/TSQL_Parser.nuspec index 7d08247..fae6b55 100644 --- a/TSQL_Parser/TSQL_Parser/TSQL_Parser.nuspec +++ b/TSQL_Parser/TSQL_Parser/TSQL_Parser.nuspec @@ -2,7 +2,7 @@ TSQL.Parser - 2.4.0 + 2.5.0 TSQL.Parser Bruce Dunwiddie shriop @@ -10,7 +10,7 @@ https://github.com/bruce-dunwiddie/tsql-parser false Library for Parsing SQL Server T-SQL Scripts - Fixed handling of NULL in SELECT, and DISTINCT in COUNT(). + Fixed handling of FOR XML in SELECT. Copyright © 2022 sql parser sql-server tsql diff --git a/TSQL_Parser/TSQL_Parser/TSQL_Parser_NetStandard.csproj b/TSQL_Parser/TSQL_Parser/TSQL_Parser_NetStandard.csproj index 2c99c6e..78ee8d7 100644 --- a/TSQL_Parser/TSQL_Parser/TSQL_Parser_NetStandard.csproj +++ b/TSQL_Parser/TSQL_Parser/TSQL_Parser_NetStandard.csproj @@ -4,9 +4,9 @@ netstandard2.0 TSQL_Parser TSQL_Parser - 2.4.0.0 - 2.4.0.0 - 2.4.0.0 + 2.5.0.0 + 2.5.0.0 + 2.5.0.0 Library for Parsing SQL Server TSQL Scripts Copyright © 2022 diff --git a/TSQL_Parser/Tests/Statements/SelectStatementTests.cs b/TSQL_Parser/Tests/Statements/SelectStatementTests.cs index e37a5a6..0f532d6 100644 --- a/TSQL_Parser/Tests/Statements/SelectStatementTests.cs +++ b/TSQL_Parser/Tests/Statements/SelectStatementTests.cs @@ -309,6 +309,8 @@ FROM SomeTable a JOIN SomeTable b Assert.AreEqual(21, select.From.Tokens.Count); Assert.IsNull(select.Where); Assert.AreEqual(1, select.Select.Columns.Count); + Assert.AreEqual(3, select.Select.Columns[0].Tokens.Count); + Assert.AreEqual("a", select.Select.Columns[0].Expression.AsMulticolumn.TableReference.Single().Text); } [Test] @@ -889,5 +891,42 @@ public void SelectStatement_SELECT_NULL() Assert.AreEqual(TSQLExpressionType.Null, select.Select.Columns[3].Expression.Type); Assert.AreEqual(5, select.Select.Columns[4].Expression.AsConstant.Literal.AsNumericLiteral.Value); } + + [Test] + public void SelectStatement_ChainedOperators() + { + // regression test for https://github.com/bruce-dunwiddie/tsql-parser/issues/110 + List statements = TSQLStatementReader.ParseStatements( + @"select isnull(a, 0) * isnull(b, 0) / 100 as Result from myTable", + includeWhitespace: false); + + Assert.AreEqual(1, statements.Count); + TSQLSelectStatement select = statements.Single().AsSelect; + Assert.AreEqual(20, select.Tokens.Count); + Assert.AreEqual(1, select.Select.Columns.Count); + } + + [Test] + public void SelectStatement_Stuff() + { + // regression test for https://github.com/bruce-dunwiddie/tsql-parser/issues/108 + List statements = TSQLStatementReader.ParseStatements( + @"select stuff ( (select top 10 note from asset_note where note is not null order by note_id for XML path('')) , 1 , 1 , 'Note: ' );", + includeWhitespace: false); + + Assert.AreEqual(1, statements.Count); + TSQLSelectStatement select = statements.Single().AsSelect; + + TSQLSelectStatement innerSelect = select + .Select + .Columns[0] + .Expression + .AsFunction + .Arguments[0] + .AsSubquery + .Select; + + Assert.AreEqual(6, innerSelect.For.Tokens.Count); + } } }