From 43b074d93751bb7f59b6b7f32b0a4d2dd3d70ca0 Mon Sep 17 00:00:00 2001 From: Ema Paunovic Date: Wed, 3 Jun 2026 12:54:50 +0000 Subject: [PATCH 1/3] Merged PR 2076477: adding CREATE SEMANTIC INDEX feature Syntax CREATE SEMANTIC INDEX index_name ON table_name (column_definition [,...n] ) WITH (index_option [, ...n]) [ON {FILEGROUP | DEFAULT}] [;] column_definition :: = column_name [SEARCH_TYPE {vector | fulltext | hybrid}] [TYPE COLUMN type_column_name] [LANGUAGE language_term] [CHUNK_USING(chunk_option [,...n] )] chunk_option ::= { TYPE = {fixed | sentence | paragraph chapter | SIZE = character_count_per_chunk | OVERLAP = character_overlap_percentage } index_option ::= { EXTERNAL_MODEL = model_name [ (model_option) ] [VECTOR_INDEX ( vector_index_option [,...n] )] [FULLTEXT_STOPLIST = {OFF | SYSTEM | stoplist_name}] [MAXDOP = degree_of_parallelism] [DROP_EXISTING = { OFF | ON }] } model_option ::= { PARAMETERS = json_config } vector_index_option ::= { METRIC = {'cosine' | 'dot' | 'euclidean'} | TYPE = 'diskann' } - [X] The [Common checklist](https://msdata.visualstudio.com/SQLToolsAndLibraries/_git/Common?path=/Templates/PR%20Checklist%20for%20SQLToolsAndLibraries.md&version=GBmain&_a=preview) has been reviewed and followed - [X] Code changes are accompanied by appropriate unit tests - [ ] Identified and included SMEs needed to review code changes - [ ] Follow the [steps](https://msdata.visualstudio.com/SQLToolsAndLibraries/_wiki/wikis/SQLToolsAndLibraries.wiki/33838/Adding-or-Extending-TSql-support-in-Sql-Dom?anchor=make-the-changes-in) here to make changes in the code - [X] Follow the [steps](https://msdata.visualstudio.com/SQLToolsAndLibraries/_wiki/wikis/SQLToolsAndLibraries.wiki/33838/Adding-or-Extending-TSql-support-in-Sql-Dom?anchor=to-extend-the-tests-do-the-following%3A) here to add new tests for your feature - [ ] Update relevant documentation in the [wiki](https://dev.azure.com/msdata/SQLToolsAndLibraries/_wiki/wikis/SQLToolsAndLibraries.wiki) and the README.md file Please provide any additional information that might be helpful for the reviewers adding create semanic index feature ---- New feature implementation adding support for CREATE SEMANTIC INDEX statement in SQL Server's T-SQL parser (TSql180). This PR implements comprehensive parsing and script generation support for the new CREATE SEMANTIC INDEX statement, which enables semantic indexing with vector search, full-text search, and hybrid search capabilities. The feature includes column-level options for search types, chunk... --- SqlScriptDom/Parser/TSql/Ast.xml | 22 ++ .../Parser/TSql/CodeGenerationSupporter.cs | 10 + .../TSql/SemanticIndexChunkOptionKind.cs | 24 ++ .../Parser/TSql/SemanticIndexSearchType.cs | 25 ++ SqlScriptDom/Parser/TSql/TSql180.g | 238 ++++++++++++++++++ .../Parser/TSql/TSql180ParserBaseInternal.cs | 23 +- ...torVisitor.CreateSemanticIndexStatement.cs | 208 +++++++++++++++ .../CreateSemanticIndexTests180.sql | 78 ++++++ Test/SqlDom/Only180SyntaxTests.cs | 3 +- Test/SqlDom/ParserErrorsTests.cs | 77 ++++++ .../CreateSemanticIndexTests180.sql | 56 +++++ 11 files changed, 762 insertions(+), 2 deletions(-) create mode 100644 SqlScriptDom/Parser/TSql/SemanticIndexChunkOptionKind.cs create mode 100644 SqlScriptDom/Parser/TSql/SemanticIndexSearchType.cs create mode 100644 SqlScriptDom/ScriptDom/SqlServer/ScriptGenerator/SqlScriptGeneratorVisitor.CreateSemanticIndexStatement.cs create mode 100644 Test/SqlDom/Baselines180/CreateSemanticIndexTests180.sql create mode 100644 Test/SqlDom/TestScripts/CreateSemanticIndexTests180.sql diff --git a/SqlScriptDom/Parser/TSql/Ast.xml b/SqlScriptDom/Parser/TSql/Ast.xml index bad570b2..7715e87a 100644 --- a/SqlScriptDom/Parser/TSql/Ast.xml +++ b/SqlScriptDom/Parser/TSql/Ast.xml @@ -4658,6 +4658,28 @@ + + + + + + + + + + + + + + + + + + + + + + diff --git a/SqlScriptDom/Parser/TSql/CodeGenerationSupporter.cs b/SqlScriptDom/Parser/TSql/CodeGenerationSupporter.cs index e2712855..51b35d5d 100644 --- a/SqlScriptDom/Parser/TSql/CodeGenerationSupporter.cs +++ b/SqlScriptDom/Parser/TSql/CodeGenerationSupporter.cs @@ -204,6 +204,7 @@ internal static class CodeGenerationSupporter internal const string ChangeTable = "CHANGETABLE"; internal const string ChangeTracking = "CHANGE_TRACKING"; internal const string ChangeTrackingContext = "CHANGE_TRACKING_CONTEXT"; + internal const string Chapter = "CHAPTER"; internal const string Char = "CHAR"; internal const string CharacterSet = "CHARACTER_SET"; internal const string CheckConstraints = "CHECK_CONSTRAINTS"; @@ -214,6 +215,7 @@ internal static class CodeGenerationSupporter internal const string ChecksumAgg = "CHECKSUM_AGG"; internal const string ChunkSize = "CHUNK_SIZE"; internal const string ChunkType = "CHUNK_TYPE"; + internal const string ChunkUsing = "CHUNK_USING"; internal const string ModularSum = "MODULAR_SUM"; internal const string Classifier = "CLASSIFIER"; internal const string Classification = "CLASSIFICATION"; @@ -394,6 +396,7 @@ internal static class CodeGenerationSupporter internal const string Extension = "EXTENSION"; internal const string External = "EXTERNAL"; internal const string ExternalAccess = "EXTERNAL_ACCESS"; + internal const string ExternalModel = "EXTERNAL_MODEL"; internal const string ExternalMonitor = "EXTERNAL_MONITOR"; internal const string Extract = "EXTRACT"; internal const string FailOperation = "FAIL_OPERATION"; @@ -465,6 +468,7 @@ internal static class CodeGenerationSupporter internal const string Full = "FULL"; internal const string FullScan = "FULLSCAN"; internal const string Fulltext = "FULLTEXT"; + internal const string FulltextStopList = "FULLTEXT_STOPLIST"; internal const string General = "GENERAL"; internal const string GenerateSeries = "GENERATE_SERIES"; internal const string Generated = "GENERATED"; @@ -499,6 +503,7 @@ internal static class CodeGenerationSupporter internal const string Hidden = "HIDDEN"; internal const string High = "HIGH"; internal const string Hint = "HINT"; + internal const string Hybrid = "HYBRID"; internal const string Histogram = "HISTOGRAM"; internal const string HistogramSteps = "HISTOGRAM_STEPS"; internal const string HistoryRetentionPeriod = "HISTORY_RETENTION_PERIOD"; @@ -755,6 +760,7 @@ internal static class CodeGenerationSupporter internal const string PageCount = "PAGECOUNT"; internal const string PageVerify = "PAGE_VERIFY"; internal const string PagLock = "PAGLOCK"; + internal const string Paragraph = "PARAGRAPH"; internal const string Param = "PARAM"; internal const string Parameter = "PARAMETER"; internal const string Parameters = "PARAMETERS"; @@ -926,6 +932,7 @@ internal static class CodeGenerationSupporter internal const string Scroll = "SCROLL"; internal const string ScrollLocks = "SCROLL_LOCKS"; internal const string Search = "SEARCH"; + internal const string SearchType = "SEARCH_TYPE"; internal const string Secondary = "SECONDARY"; internal const string SecondaryRole = "SECONDARY_ROLE"; internal const string Seconds = "SECONDS"; @@ -935,6 +942,8 @@ internal static class CodeGenerationSupporter internal const string SecurityLog = "SECURITY_LOG"; internal const string Selective = "SELECTIVE"; internal const string Self = "SELF"; + internal const string Semantic = "SEMANTIC"; + internal const string Sentence = "SENTENCE"; internal const string SemiColon = ";"; internal const string Send = "SEND"; internal const string Sensitivity = "SENSITIVITY"; @@ -1123,6 +1132,7 @@ internal static class CodeGenerationSupporter internal const string Varp = "VARP"; internal const string VDevNo = "VDEVNO"; internal const string Vector = "Vector"; + internal const string VectorIndex = "VECTOR_INDEX"; internal const string VectorSearch = "VECTOR_SEARCH"; internal const string Verbose = "VERBOSE"; internal const string VerboseLogging = "VerboseLogging"; diff --git a/SqlScriptDom/Parser/TSql/SemanticIndexChunkOptionKind.cs b/SqlScriptDom/Parser/TSql/SemanticIndexChunkOptionKind.cs new file mode 100644 index 00000000..c3a634ac --- /dev/null +++ b/SqlScriptDom/Parser/TSql/SemanticIndexChunkOptionKind.cs @@ -0,0 +1,24 @@ +//------------------------------------------------------------------------------ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// +//------------------------------------------------------------------------------ + + + +namespace Microsoft.SqlServer.TransactSql.ScriptDom +{ +#pragma warning disable 1591 + + /// + /// The possible values for semantic index chunk options. + /// + public enum SemanticIndexChunkOptionKind + { + Type = 0, + Size = 1, + Overlap = 2 + } + +#pragma warning restore 1591 +} diff --git a/SqlScriptDom/Parser/TSql/SemanticIndexSearchType.cs b/SqlScriptDom/Parser/TSql/SemanticIndexSearchType.cs new file mode 100644 index 00000000..d3d956fc --- /dev/null +++ b/SqlScriptDom/Parser/TSql/SemanticIndexSearchType.cs @@ -0,0 +1,25 @@ +//------------------------------------------------------------------------------ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// +//------------------------------------------------------------------------------ + + + +namespace Microsoft.SqlServer.TransactSql.ScriptDom +{ +#pragma warning disable 1591 + + /// + /// The possible values for semantic index search type. + /// + public enum SemanticIndexSearchType + { + NotSpecified = 0, + Vector = 1, + Fulltext = 2, + Hybrid = 3 + } + +#pragma warning restore 1591 +} diff --git a/SqlScriptDom/Parser/TSql/TSql180.g b/SqlScriptDom/Parser/TSql/TSql180.g index f52ab337..ae426b05 100644 --- a/SqlScriptDom/Parser/TSql/TSql180.g +++ b/SqlScriptDom/Parser/TSql/TSql180.g @@ -889,6 +889,9 @@ create2005Statements returns [TSqlStatement vResult = null] | {NextTokenMatches(CodeGenerationSupporter.Vector)}? vResult=createVectorIndexStatement[null, null] + | + {NextTokenMatches(CodeGenerationSupporter.Semantic)}? + vResult=createSemanticIndexStatement | {NextTokenMatches(CodeGenerationSupporter.Contract)}? vResult=createContractStatement @@ -17132,6 +17135,232 @@ createVectorIndexStatement [IToken tUnique, bool? isClustered] returns [CreateVe )? ; +createSemanticIndexStatement returns [CreateSemanticIndexStatement vResult = FragmentFactory.CreateFragment()] +{ + Identifier vIdentifier; + SchemaObjectName vSchemaObjectName; + SemanticIndexColumn vColumn; + FileGroupOrPartitionScheme vFileGroupOrPartitionScheme; +} + : tSemantic:Identifier tIndex:Index vIdentifier=identifier + { + Match(tSemantic, CodeGenerationSupporter.Semantic); + vResult.Name = vIdentifier; + } + tOn:On vSchemaObjectName=schemaObjectThreePartName + { + vResult.OnName = vSchemaObjectName; + } + LeftParenthesis + vColumn=semanticIndexColumnDefinition + { + AddAndUpdateTokenInfo(vResult, vResult.Columns, vColumn); + } + ( + Comma vColumn=semanticIndexColumnDefinition + { + AddAndUpdateTokenInfo(vResult, vResult.Columns, vColumn); + } + )* + tRParen:RightParenthesis + { + UpdateTokenInfo(vResult, tRParen); + } + ( + // Greedy due to conflict with withCommonTableExpressionsAndXmlNamespaces + options {greedy = true; } : + With LeftParenthesis + semanticIndexWithOptions[vResult] + RightParenthesis + )? + ( + On vFileGroupOrPartitionScheme=filegroupOrPartitionScheme + { + vResult.OnFileGroupOrPartitionScheme = vFileGroupOrPartitionScheme; + } + )? + { + // Validate that EXTERNAL_MODEL is specified for vector/hybrid columns + ValidateSemanticIndexExternalModel(vResult); + } + ; + +semanticIndexColumnDefinition returns [SemanticIndexColumn vResult = FragmentFactory.CreateFragment()] +{ + Identifier vIdentifier; + IdentifierOrValueExpression vValue; +} + : vIdentifier=identifier + { + vResult.ColumnName = vIdentifier; + } + ( + {NextTokenMatches(CodeGenerationSupporter.SearchType)}? + tSearchType:Identifier EqualsSign tSearchTypeValue:Identifier + { + Match(tSearchType, CodeGenerationSupporter.SearchType); + if (TryMatch(tSearchTypeValue, CodeGenerationSupporter.Vector)) + vResult.SearchType = SemanticIndexSearchType.Vector; + else if (TryMatch(tSearchTypeValue, CodeGenerationSupporter.Fulltext)) + vResult.SearchType = SemanticIndexSearchType.Fulltext; + else + { + Match(tSearchTypeValue, CodeGenerationSupporter.Hybrid); + vResult.SearchType = SemanticIndexSearchType.Hybrid; + } + } + )? + ( + {NextTokenMatches(CodeGenerationSupporter.Type)}? + Identifier Column vIdentifier=identifier + { + vResult.TypeColumnName = vIdentifier; + } + )? + ( + {NextTokenMatches(CodeGenerationSupporter.Language)}? + tLang:Identifier vValue=stringOrIdentifier + { + Match(tLang, CodeGenerationSupporter.Language); + vResult.Language = vValue; + } + )? + ( + {NextTokenMatches(CodeGenerationSupporter.ChunkUsing)}? + tChunkUsing:Identifier LeftParenthesis + { + Match(tChunkUsing, CodeGenerationSupporter.ChunkUsing); + } + semanticIndexChunkOptions[vResult] + RightParenthesis + )? + ; + +semanticIndexChunkOptions [SemanticIndexColumn vParent] +{ + SemanticIndexChunkOption vOption; +} + : vOption=semanticIndexChunkOption + { + AddAndUpdateTokenInfo(vParent, vParent.ChunkOptions, vOption); + } + ( + Comma vOption=semanticIndexChunkOption + { + AddAndUpdateTokenInfo(vParent, vParent.ChunkOptions, vOption); + } + )* + ; + +semanticIndexChunkOption returns [SemanticIndexChunkOption vResult = FragmentFactory.CreateFragment()] +{ + IdentifierOrValueExpression vValue; +} + : tOption:Identifier EqualsSign vValue=identifierOrInteger + { + if (TryMatch(tOption, CodeGenerationSupporter.Type)) + vResult.OptionKind = SemanticIndexChunkOptionKind.Type; + else if (TryMatch(tOption, CodeGenerationSupporter.Size)) + vResult.OptionKind = SemanticIndexChunkOptionKind.Size; + else + { + Match(tOption, CodeGenerationSupporter.Overlap); + vResult.OptionKind = SemanticIndexChunkOptionKind.Overlap; + } + vResult.Value = vValue; + } + ; + +semanticIndexWithOptions [CreateSemanticIndexStatement vParent] + : semanticIndexWithOption[vParent] + ( + Comma semanticIndexWithOption[vParent] + )* + ; + +semanticIndexWithOption [CreateSemanticIndexStatement vParent] +{ + IndexOption vIndexOption; + Identifier vIdentifier; + StringLiteral vStringLiteral; +} + : + ( + {NextTokenMatches(CodeGenerationSupporter.ExternalModel)}? + tExtModel:Identifier EqualsSign + { + Match(tExtModel, CodeGenerationSupporter.ExternalModel); + } + ( + LeftParenthesis vIdentifier=identifier RightParenthesis + { + vParent.ExternalModelName = vIdentifier; + } + | + vIdentifier=identifier + { + vParent.ExternalModelName = vIdentifier; + } + ) + ( + LeftParenthesis tParams:Identifier EqualsSign vStringLiteral=stringLiteral RightParenthesis + { + Match(tParams, CodeGenerationSupporter.Parameters); + vParent.ExternalModelParameters = vStringLiteral; + } + )? + | + {NextTokenMatches(CodeGenerationSupporter.VectorIndex)}? + tVecIdx:Identifier LeftParenthesis + { + Match(tVecIdx, CodeGenerationSupporter.VectorIndex); + } + vIndexOption=semanticIndexVectorOption + { + AddAndUpdateTokenInfo(vParent, vParent.VectorIndexOptions, vIndexOption); + } + ( + Comma vIndexOption=semanticIndexVectorOption + { + AddAndUpdateTokenInfo(vParent, vParent.VectorIndexOptions, vIndexOption); + } + )* + RightParenthesis + | + {NextTokenMatches(CodeGenerationSupporter.FulltextStopList)}? + tStoplist:Identifier EqualsSign + { + Match(tStoplist, CodeGenerationSupporter.FulltextStopList); + } + ( + tOff:Off + { + StopListFullTextIndexOption vStopOpt = FragmentFactory.CreateFragment(); + vStopOpt.OptionKind = FullTextIndexOptionKind.StopList; + vStopOpt.IsOff = true; + UpdateTokenInfo(vStopOpt, tStoplist); + UpdateTokenInfo(vStopOpt, tOff); + vParent.FulltextStoplistOption = vStopOpt; + } + | + vIdentifier=identifier + { + StopListFullTextIndexOption vStopOpt = FragmentFactory.CreateFragment(); + vStopOpt.OptionKind = FullTextIndexOptionKind.StopList; + vStopOpt.IsOff = false; + vStopOpt.StopListName = vIdentifier; + UpdateTokenInfo(vStopOpt, tStoplist); + vParent.FulltextStoplistOption = vStopOpt; + } + ) + | + vIndexOption=indexOption + { + AddAndUpdateTokenInfo(vParent, vParent.IndexOptions, vIndexOption); + } + ) + ; + indexKeyColumnList[CreateIndexStatement vParent] { ColumnWithSortOrder vColumnWithSortOrder; @@ -28283,6 +28512,15 @@ xmlCompressionOption returns [XmlCompressionOption vResult = FragmentFactory.Cre )? ; +semanticIndexVectorOption returns [IndexOption vResult = null] + : + {NextTokenMatches(CodeGenerationSupporter.Metric)}? + vResult=vectorMetricOption + | + {NextTokenMatches(CodeGenerationSupporter.Type)}? + vResult=vectorTypeOption + ; + vectorMetricOption returns [VectorMetricIndexOption vResult = FragmentFactory.CreateFragment()] : tMetric:Identifier EqualsSign tMetricValue:AsciiStringLiteral { diff --git a/SqlScriptDom/Parser/TSql/TSql180ParserBaseInternal.cs b/SqlScriptDom/Parser/TSql/TSql180ParserBaseInternal.cs index 89674e5d..5f3d827e 100644 --- a/SqlScriptDom/Parser/TSql/TSql180ParserBaseInternal.cs +++ b/SqlScriptDom/Parser/TSql/TSql180ParserBaseInternal.cs @@ -70,6 +70,27 @@ protected SecurityObjectKind ParseSecurityObjectKindTSql180(Identifier identifie return TSql170ParserBaseInternal.ParseSecurityObjectKind(identifier1, identifier2); } } + + /// + /// Validates that EXTERNAL_MODEL is specified when CREATE SEMANTIC INDEX contains vector or hybrid columns. + /// Vector and hybrid search types require an external model for embeddings. + /// NotSpecified defaults to Vector, so EXTERNAL_MODEL is required unless ALL columns are explicitly Fulltext. + /// + /// The CREATE SEMANTIC INDEX statement to validate. + protected static void ValidateSemanticIndexExternalModel(CreateSemanticIndexStatement statement) + { + // EXTERNAL_MODEL is NOT required only when ALL columns are explicitly FULLTEXT + // If any column is Vector, Hybrid, or NotSpecified (defaults to Vector), EXTERNAL_MODEL is required + bool allColumnsAreFulltext = statement.Columns.All(col => + col.SearchType == SemanticIndexSearchType.Fulltext); + + // EXTERNAL_MODEL is required unless all columns are explicitly fulltext + if (!allColumnsAreFulltext && statement.ExternalModelName == null) + { + ThrowParseErrorException("SQL46144", statement, + TSqlParserResource.SQL46144Message, "EXTERNAL_MODEL"); + } + } } } - + diff --git a/SqlScriptDom/ScriptDom/SqlServer/ScriptGenerator/SqlScriptGeneratorVisitor.CreateSemanticIndexStatement.cs b/SqlScriptDom/ScriptDom/SqlServer/ScriptGenerator/SqlScriptGeneratorVisitor.CreateSemanticIndexStatement.cs new file mode 100644 index 00000000..c3333222 --- /dev/null +++ b/SqlScriptDom/ScriptDom/SqlServer/ScriptGenerator/SqlScriptGeneratorVisitor.CreateSemanticIndexStatement.cs @@ -0,0 +1,208 @@ +//------------------------------------------------------------------------------ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// +//------------------------------------------------------------------------------ +using System.Collections.Generic; +using Microsoft.SqlServer.TransactSql.ScriptDom; + +namespace Microsoft.SqlServer.TransactSql.ScriptDom.ScriptGenerator +{ + partial class SqlScriptGeneratorVisitor + { + public override void ExplicitVisit(CreateSemanticIndexStatement node) + { + GenerateKeyword(TSqlTokenType.Create); + + GenerateSpaceAndIdentifier(CodeGenerationSupporter.Semantic); + + GenerateSpaceAndKeyword(TSqlTokenType.Index); + + // name + GenerateSpaceAndFragmentIfNotNull(node.Name); + + NewLineAndIndent(); + GenerateKeyword(TSqlTokenType.On); + GenerateSpaceAndFragmentIfNotNull(node.OnName); + + // Column definitions + GenerateSpace(); + GenerateSymbol(TSqlTokenType.LeftParenthesis); + + for (int i = 0; i < node.Columns.Count; i++) + { + if (i > 0) + { + GenerateSymbol(TSqlTokenType.Comma); + GenerateSpace(); + } + GenerateFragmentIfNotNull(node.Columns[i]); + } + + GenerateSymbol(TSqlTokenType.RightParenthesis); + + // WITH clause + bool hasWithOption = node.ExternalModelName != null || + (node.VectorIndexOptions != null && node.VectorIndexOptions.Count > 0) || + node.FulltextStoplistOption != null || + (node.IndexOptions != null && node.IndexOptions.Count > 0); + + if (hasWithOption) + { + NewLineAndIndent(); + GenerateKeyword(TSqlTokenType.With); + GenerateSpace(); + GenerateSymbol(TSqlTokenType.LeftParenthesis); + + bool first = true; + + if (node.ExternalModelName != null) + { + GenerateIdentifier(CodeGenerationSupporter.ExternalModel); + GenerateSpaceAndSymbol(TSqlTokenType.EqualsSign); + GenerateSpace(); + GenerateFragmentIfNotNull(node.ExternalModelName); + if (node.ExternalModelParameters != null) + { + GenerateSpace(); + GenerateSymbol(TSqlTokenType.LeftParenthesis); + GenerateIdentifier(CodeGenerationSupporter.Parameters); + GenerateSpaceAndSymbol(TSqlTokenType.EqualsSign); + GenerateSpace(); + GenerateFragmentIfNotNull(node.ExternalModelParameters); + GenerateSymbol(TSqlTokenType.RightParenthesis); + } + first = false; + } + + if (node.VectorIndexOptions != null && node.VectorIndexOptions.Count > 0) + { + if (!first) + { + GenerateSymbol(TSqlTokenType.Comma); + GenerateSpace(); + } + GenerateIdentifier(CodeGenerationSupporter.VectorIndex); + GenerateSpace(); + GenerateSymbol(TSqlTokenType.LeftParenthesis); + GenerateCommaSeparatedList(node.VectorIndexOptions); + GenerateSymbol(TSqlTokenType.RightParenthesis); + first = false; + } + + if (node.FulltextStoplistOption != null) + { + if (!first) + { + GenerateSymbol(TSqlTokenType.Comma); + GenerateSpace(); + } + GenerateIdentifier(CodeGenerationSupporter.FulltextStopList); + GenerateSpaceAndSymbol(TSqlTokenType.EqualsSign); + GenerateSpace(); + if (node.FulltextStoplistOption.IsOff) + { + GenerateKeyword(TSqlTokenType.Off); + } + else + { + GenerateFragmentIfNotNull(node.FulltextStoplistOption.StopListName); + } + first = false; + } + + if (node.IndexOptions != null && node.IndexOptions.Count > 0) + { + foreach (var option in node.IndexOptions) + { + if (!first) + { + GenerateSymbol(TSqlTokenType.Comma); + GenerateSpace(); + } + GenerateFragmentIfNotNull(option); + first = false; + } + } + + GenerateSymbol(TSqlTokenType.RightParenthesis); + } + + if (node.OnFileGroupOrPartitionScheme != null) + { + NewLineAndIndent(); + GenerateKeyword(TSqlTokenType.On); + + GenerateSpaceAndFragmentIfNotNull(node.OnFileGroupOrPartitionScheme); + } + } + + public override void ExplicitVisit(SemanticIndexColumn node) + { + GenerateFragmentIfNotNull(node.ColumnName); + + if (node.SearchType != SemanticIndexSearchType.NotSpecified) + { + GenerateSpace(); + GenerateIdentifier(CodeGenerationSupporter.SearchType); + GenerateSpaceAndSymbol(TSqlTokenType.EqualsSign); + GenerateSpace(); + switch (node.SearchType) + { + case SemanticIndexSearchType.Vector: + GenerateIdentifier(CodeGenerationSupporter.Vector); + break; + case SemanticIndexSearchType.Fulltext: + GenerateIdentifier(CodeGenerationSupporter.Fulltext); + break; + case SemanticIndexSearchType.Hybrid: + GenerateIdentifier(CodeGenerationSupporter.Hybrid); + break; + } + } + + if (node.TypeColumnName != null) + { + GenerateSpace(); + GenerateIdentifier(CodeGenerationSupporter.Type); + GenerateSpaceAndKeyword(TSqlTokenType.Column); + GenerateSpaceAndFragmentIfNotNull(node.TypeColumnName); + } + + if (node.Language != null) + { + GenerateSpace(); + GenerateIdentifier(CodeGenerationSupporter.Language); + GenerateSpaceAndFragmentIfNotNull(node.Language); + } + + if (node.ChunkOptions != null && node.ChunkOptions.Count > 0) + { + GenerateSpace(); + GenerateIdentifier(CodeGenerationSupporter.ChunkUsing); + GenerateSymbol(TSqlTokenType.LeftParenthesis); + GenerateCommaSeparatedList(node.ChunkOptions); + GenerateSymbol(TSqlTokenType.RightParenthesis); + } + } + + public override void ExplicitVisit(SemanticIndexChunkOption node) + { + switch (node.OptionKind) + { + case SemanticIndexChunkOptionKind.Type: + GenerateIdentifier(CodeGenerationSupporter.Type); + break; + case SemanticIndexChunkOptionKind.Size: + GenerateIdentifier(CodeGenerationSupporter.Size); + break; + case SemanticIndexChunkOptionKind.Overlap: + GenerateIdentifier(CodeGenerationSupporter.Overlap); + break; + } + GenerateSpaceAndSymbol(TSqlTokenType.EqualsSign); + GenerateSpace(); + GenerateFragmentIfNotNull(node.Value); + } + } +} diff --git a/Test/SqlDom/Baselines180/CreateSemanticIndexTests180.sql b/Test/SqlDom/Baselines180/CreateSemanticIndexTests180.sql new file mode 100644 index 00000000..b853da18 --- /dev/null +++ b/Test/SqlDom/Baselines180/CreateSemanticIndexTests180.sql @@ -0,0 +1,78 @@ +CREATE SEMANTIC INDEX SI_Basic + ON dbo.Documents (content) + WITH (EXTERNAL_MODEL = MyModel); + +CREATE SEMANTIC INDEX SI_Vector + ON dbo.books (summary SEARCH_TYPE = VECTOR) + WITH (EXTERNAL_MODEL = MyModel); + +CREATE SEMANTIC INDEX SI_Multi + ON dbo.books (summary SEARCH_TYPE = VECTOR, title SEARCH_TYPE = VECTOR) + WITH (EXTERNAL_MODEL = MyModel); + +CREATE SEMANTIC INDEX SI_Hybrid + ON dbo.books (content SEARCH_TYPE = HYBRID, title SEARCH_TYPE = FULLTEXT) + WITH (EXTERNAL_MODEL = MyModel); + +CREATE SEMANTIC INDEX SI_TypeCol + ON dbo.Documents (content TYPE COLUMN content_type) + WITH (EXTERNAL_MODEL = MyModel); + +CREATE SEMANTIC INDEX SI_Lang + ON dbo.Documents (content LANGUAGE English) + WITH (EXTERNAL_MODEL = MyModel); + +CREATE SEMANTIC INDEX SI_Chunk + ON dbo.Documents (content CHUNK_USING(TYPE = paragraph, SIZE = 500, OVERLAP = 50)) + WITH (EXTERNAL_MODEL = MyModel); + +CREATE SEMANTIC INDEX SI_Full + ON dbo.Documents (content SEARCH_TYPE = VECTOR TYPE COLUMN content_type LANGUAGE English CHUNK_USING(TYPE = sentence, SIZE = 1000)) + WITH (EXTERNAL_MODEL = MyModel); + +CREATE SEMANTIC INDEX SI_Model + ON dbo.books (summary SEARCH_TYPE = VECTOR) + WITH (EXTERNAL_MODEL = OpenAIModel); + +CREATE SEMANTIC INDEX SI_ModelParen + ON dbo.books (summary SEARCH_TYPE = VECTOR) + WITH (EXTERNAL_MODEL = MyModel); + +CREATE SEMANTIC INDEX SI_ModelParams + ON dbo.books (summary SEARCH_TYPE = VECTOR) + WITH (EXTERNAL_MODEL = MyModel (PARAMETERS = '{"dimension": 1536}')); + +CREATE SEMANTIC INDEX SI_VecOpts + ON dbo.books (summary SEARCH_TYPE = VECTOR) + WITH (EXTERNAL_MODEL = OpenAIModel, VECTOR_INDEX (METRIC = 'COSINE')); + +CREATE SEMANTIC INDEX SI_StopOff + ON dbo.books (content SEARCH_TYPE = FULLTEXT) + WITH (FULLTEXT_STOPLIST = OFF); + +CREATE SEMANTIC INDEX SI_StopSys + ON dbo.books (content SEARCH_TYPE = FULLTEXT) + WITH (FULLTEXT_STOPLIST = SYSTEM); + +CREATE SEMANTIC INDEX SI_StopName + ON dbo.books (content SEARCH_TYPE = FULLTEXT) + WITH (FULLTEXT_STOPLIST = MyStoplist); + +CREATE SEMANTIC INDEX SI_MaxDop + ON dbo.books (summary SEARCH_TYPE = VECTOR) + WITH (EXTERNAL_MODEL = OpenAIModel, MAXDOP = 4); + +CREATE SEMANTIC INDEX SI_DropEx + ON dbo.books (summary SEARCH_TYPE = VECTOR) + WITH (EXTERNAL_MODEL = OpenAIModel, DROP_EXISTING = ON); + +CREATE SEMANTIC INDEX SI_FG + ON dbo.books (summary SEARCH_TYPE = VECTOR) + WITH (EXTERNAL_MODEL = OpenAIModel) + ON [PRIMARY]; + +CREATE SEMANTIC INDEX SI_Complete + ON dbo.books (summary SEARCH_TYPE = VECTOR, title SEARCH_TYPE = FULLTEXT CHUNK_USING(TYPE = fixed, SIZE = 200, OVERLAP = 25)) + WITH (EXTERNAL_MODEL = OpenAIModel (PARAMETERS = '{"api_key": "test"}'), VECTOR_INDEX (METRIC = 'COSINE', TYPE = 'DISKANN'), FULLTEXT_STOPLIST = SYSTEM, MAXDOP = 8, DROP_EXISTING = OFF) + ON [PRIMARY]; + diff --git a/Test/SqlDom/Only180SyntaxTests.cs b/Test/SqlDom/Only180SyntaxTests.cs index cd8746db..9be98076 100644 --- a/Test/SqlDom/Only180SyntaxTests.cs +++ b/Test/SqlDom/Only180SyntaxTests.cs @@ -14,7 +14,8 @@ public partial class SqlDomTests { new ParserTest180("AutomaticIndexCompactionTests180.sql", nErrors80: 2, nErrors90: 2, nErrors100: 2, nErrors110: 2, nErrors120: 2, nErrors130: 2, nErrors140: 2, nErrors150: 2, nErrors160: 2, nErrors170: 2), new ParserTest180("TrimFromReturnTests160.sql"), - new ParserTest180("PersistSamplePercentStatisticsTests130.sql") + new ParserTest180("PersistSamplePercentStatisticsTests130.sql"), + new ParserTest180("CreateSemanticIndexTests180.sql"), }; private static readonly ParserTest[] SqlAzure180_TestInfos = diff --git a/Test/SqlDom/ParserErrorsTests.cs b/Test/SqlDom/ParserErrorsTests.cs index ce5ab88a..01c1b317 100644 --- a/Test/SqlDom/ParserErrorsTests.cs +++ b/Test/SqlDom/ParserErrorsTests.cs @@ -6,6 +6,7 @@ using System; using System.Collections.Generic; using System.Globalization; +using System.IO; using System.Linq; using Microsoft.SqlServer.TransactSql.ScriptDom; using Microsoft.VisualStudio.TestTools.UnitTesting; @@ -4632,6 +4633,82 @@ public void CreateJsonIndexStatementErrorTest() new ParserErrorInfo(26, "SQL46010", "(")); } + /// + /// Negative tests for CREATE SEMANTIC INDEX statement + /// + [TestMethod] + [Priority(0)] + [SqlStudioTestCategory(Category.UnitTest)] + public void CreateSemanticIndexStatementErrorTest() + { + // Semantic Index syntax should not be supported in SQL Server versions prior to 180 + ParserTestUtils.ErrorTest170("CREATE SEMANTIC INDEX idx1 ON dbo.t1 (col1)", + new ParserErrorInfo(7, "SQL46010", "SEMANTIC")); + ParserTestUtils.ErrorTest160("CREATE SEMANTIC INDEX idx1 ON dbo.t1 (col1)", + new ParserErrorInfo(7, "SQL46010", "SEMANTIC")); + ParserTestUtils.ErrorTest150("CREATE SEMANTIC INDEX idx1 ON dbo.t1 (col1)", + new ParserErrorInfo(7, "SQL46010", "SEMANTIC")); + ParserTestUtils.ErrorTest140("CREATE SEMANTIC INDEX idx1 ON dbo.t1 (col1)", + new ParserErrorInfo(7, "SQL46010", "SEMANTIC")); + ParserTestUtils.ErrorTest130("CREATE SEMANTIC INDEX idx1 ON dbo.t1 (col1)", + new ParserErrorInfo(7, "SQL46010", "SEMANTIC")); + ParserTestUtils.ErrorTest120("CREATE SEMANTIC INDEX idx1 ON dbo.t1 (col1)", + new ParserErrorInfo(7, "SQL46010", "SEMANTIC")); + ParserTestUtils.ErrorTest110("CREATE SEMANTIC INDEX idx1 ON dbo.t1 (col1)", + new ParserErrorInfo(7, "SQL46010", "SEMANTIC")); + + // Test malformed syntax in TSql180 + TSql180Parser parser180 = new TSql180Parser(true); + + // Missing index name + ParserTestUtils.ErrorTest(parser180, "CREATE SEMANTIC INDEX ON dbo.t1 (col1)", + new ParserErrorInfo(22, "SQL46010", "ON")); + + // Missing table name + ParserTestUtils.ErrorTest(parser180, "CREATE SEMANTIC INDEX idx1 ON (col1)", + new ParserErrorInfo(30, "SQL46010", "(")); + + // Missing column list + ParserTestUtils.ErrorTest(parser180, "CREATE SEMANTIC INDEX idx1 ON dbo.t1", + new ParserErrorInfo(36, "SQL46029")); + + // Empty column list + ParserTestUtils.ErrorTest(parser180, "CREATE SEMANTIC INDEX idx1 ON dbo.t1 ()", + new ParserErrorInfo(38, "SQL46010", ")")); + + // Invalid SEARCH_TYPE value + ParserTestUtils.ErrorTest(parser180, "CREATE SEMANTIC INDEX idx1 ON dbo.t1 (col1 SEARCH_TYPE=invalid)", + new ParserErrorInfo(55, "SQL46005", "HYBRID", "invalid")); + + // Invalid CHUNK_USING option + ParserTestUtils.ErrorTest(parser180, "CREATE SEMANTIC INDEX idx1 ON dbo.t1 (col1 CHUNK_USING(BADOPT = 100))", + new ParserErrorInfo(55, "SQL46005", "OVERLAP", "BADOPT")); + + // EXTERNAL_MODEL is required when SEARCH_TYPE is not specified (defaults to vector) + ParserTestUtils.ErrorTest(parser180, "CREATE SEMANTIC INDEX idx1 ON dbo.t1 (col1)", + new ParserErrorInfo(22, "SQL46144", "EXTERNAL_MODEL")); + + // EXTERNAL_MODEL is required for vector columns + ParserTestUtils.ErrorTest(parser180, "CREATE SEMANTIC INDEX idx1 ON dbo.t1 (col1 SEARCH_TYPE = VECTOR)", + new ParserErrorInfo(22, "SQL46144", "EXTERNAL_MODEL")); + + // EXTERNAL_MODEL is required for hybrid columns + ParserTestUtils.ErrorTest(parser180, "CREATE SEMANTIC INDEX idx1 ON dbo.t1 (col1 SEARCH_TYPE = HYBRID)", + new ParserErrorInfo(22, "SQL46144", "EXTERNAL_MODEL")); + + // EXTERNAL_MODEL is required when at least one column is vector + ParserTestUtils.ErrorTest(parser180, "CREATE SEMANTIC INDEX idx1 ON dbo.t1 (col1 SEARCH_TYPE = FULLTEXT, col2 SEARCH_TYPE = VECTOR)", + new ParserErrorInfo(22, "SQL46144", "EXTERNAL_MODEL")); + + // EXTERNAL_MODEL is required when at least one column has no SEARCH_TYPE (defaults to vector) + ParserTestUtils.ErrorTest(parser180, "CREATE SEMANTIC INDEX idx1 ON dbo.t1 (col1 SEARCH_TYPE = FULLTEXT, col2)", + new ParserErrorInfo(22, "SQL46144", "EXTERNAL_MODEL")); + + // Multiple vector columns without EXTERNAL_MODEL + ParserTestUtils.ErrorTest(parser180, "CREATE SEMANTIC INDEX idx1 ON dbo.t1 (col1 SEARCH_TYPE = VECTOR, col2 SEARCH_TYPE = VECTOR)", + new ParserErrorInfo(22, "SQL46144", "EXTERNAL_MODEL")); + } + /// /// Check that the value of MAXDOP index option is within range /// diff --git a/Test/SqlDom/TestScripts/CreateSemanticIndexTests180.sql b/Test/SqlDom/TestScripts/CreateSemanticIndexTests180.sql new file mode 100644 index 00000000..677e3487 --- /dev/null +++ b/Test/SqlDom/TestScripts/CreateSemanticIndexTests180.sql @@ -0,0 +1,56 @@ +-- Basic semantic index (defaults to SEARCH_TYPE=vector, requires EXTERNAL_MODEL) +CREATE SEMANTIC INDEX SI_Basic ON dbo.Documents (content) WITH (EXTERNAL_MODEL = MyModel); + +-- Semantic index with SEARCH_TYPE and EXTERNAL_MODEL (required for vector) +CREATE SEMANTIC INDEX SI_Vector ON dbo.books (summary SEARCH_TYPE=vector) WITH (EXTERNAL_MODEL = MyModel); + +-- Multiple columns with SEARCH_TYPE and EXTERNAL_MODEL (required for vector) +CREATE SEMANTIC INDEX SI_Multi ON dbo.books (summary SEARCH_TYPE=vector, title SEARCH_TYPE=vector) WITH (EXTERNAL_MODEL = MyModel); + +-- All search types (EXTERNAL_MODEL required for hybrid) +CREATE SEMANTIC INDEX SI_Hybrid ON dbo.books (content SEARCH_TYPE=hybrid, title SEARCH_TYPE=fulltext) WITH (EXTERNAL_MODEL = MyModel); + +-- With TYPE COLUMN (defaults to vector, requires EXTERNAL_MODEL) +CREATE SEMANTIC INDEX SI_TypeCol ON dbo.Documents (content TYPE COLUMN content_type) WITH (EXTERNAL_MODEL = MyModel); + +-- With LANGUAGE (defaults to vector, requires EXTERNAL_MODEL) +CREATE SEMANTIC INDEX SI_Lang ON dbo.Documents (content LANGUAGE English) WITH (EXTERNAL_MODEL = MyModel); + +-- With CHUNK_USING options (defaults to vector, requires EXTERNAL_MODEL) +CREATE SEMANTIC INDEX SI_Chunk ON dbo.Documents (content CHUNK_USING(TYPE = paragraph, SIZE = 500, OVERLAP = 50)) WITH (EXTERNAL_MODEL = MyModel); + +-- Full column definition with EXTERNAL_MODEL (required for vector) +CREATE SEMANTIC INDEX SI_Full ON dbo.Documents (content SEARCH_TYPE=vector TYPE COLUMN content_type LANGUAGE English CHUNK_USING(TYPE = sentence, SIZE = 1000)) WITH (EXTERNAL_MODEL = MyModel); + +-- WITH EXTERNAL_MODEL +CREATE SEMANTIC INDEX SI_Model ON dbo.books (summary SEARCH_TYPE=vector) WITH (EXTERNAL_MODEL = OpenAIModel); + +-- WITH EXTERNAL_MODEL in parentheses +CREATE SEMANTIC INDEX SI_ModelParen ON dbo.books (summary SEARCH_TYPE=vector) WITH (EXTERNAL_MODEL = (MyModel)); + +-- WITH EXTERNAL_MODEL and PARAMETERS +CREATE SEMANTIC INDEX SI_ModelParams ON dbo.books (summary SEARCH_TYPE=vector) WITH (EXTERNAL_MODEL = MyModel (PARAMETERS = '{"dimension": 1536}')); + +-- WITH VECTOR_INDEX options +CREATE SEMANTIC INDEX SI_VecOpts ON dbo.books (summary SEARCH_TYPE=vector) WITH (EXTERNAL_MODEL = OpenAIModel, VECTOR_INDEX (METRIC = 'cosine')); + +-- WITH FULLTEXT_STOPLIST OFF +CREATE SEMANTIC INDEX SI_StopOff ON dbo.books (content SEARCH_TYPE=fulltext) WITH (FULLTEXT_STOPLIST = OFF); + +-- WITH FULLTEXT_STOPLIST SYSTEM +CREATE SEMANTIC INDEX SI_StopSys ON dbo.books (content SEARCH_TYPE=fulltext) WITH (FULLTEXT_STOPLIST = SYSTEM); + +-- WITH FULLTEXT_STOPLIST name +CREATE SEMANTIC INDEX SI_StopName ON dbo.books (content SEARCH_TYPE=fulltext) WITH (FULLTEXT_STOPLIST = MyStoplist); + +-- WITH MAXDOP +CREATE SEMANTIC INDEX SI_MaxDop ON dbo.books (summary SEARCH_TYPE=vector) WITH (EXTERNAL_MODEL = OpenAIModel, MAXDOP = 4); + +-- WITH DROP_EXISTING +CREATE SEMANTIC INDEX SI_DropEx ON dbo.books (summary SEARCH_TYPE=vector) WITH (EXTERNAL_MODEL = OpenAIModel, DROP_EXISTING = ON); + +-- With ON filegroup +CREATE SEMANTIC INDEX SI_FG ON dbo.books (summary SEARCH_TYPE=vector) WITH (EXTERNAL_MODEL = OpenAIModel) ON [PRIMARY]; + +-- Complex example with all options +CREATE SEMANTIC INDEX SI_Complete ON dbo.books (summary SEARCH_TYPE=vector, title SEARCH_TYPE=fulltext CHUNK_USING(TYPE = fixed, SIZE = 200, OVERLAP = 25)) WITH (EXTERNAL_MODEL = OpenAIModel (PARAMETERS = '{"api_key": "test"}'), VECTOR_INDEX (METRIC = 'cosine', TYPE = 'DiskANN'), FULLTEXT_STOPLIST = SYSTEM, MAXDOP = 8, DROP_EXISTING = OFF) ON [PRIMARY]; From 1024e11b8188a19e4091ae198de268f20d36fe60 Mon Sep 17 00:00:00 2001 From: Maksim Vlasov Date: Wed, 10 Jun 2026 20:41:01 +0000 Subject: [PATCH 2/3] Merged PR 2111576: [Trident DW] Support invoke_external_api intrinsic and its' UDF syntax # Pull Request Template for ScriptDom ## Description This PR adds support for a new intrinsic that we've been implementing in Fabric DataWarehouse: `invoke_external_api` and its' UDF syntax sugar which later must become the only supported syntax - `EXTERNAL FUNCTION` ## Code Change - [x] The [Common checklist](https://msdata.visualstudio.com/SQLToolsAndLibraries/_git/Common?path=/Templates/PR%20Checklist%20for%20SQLToolsAndLibraries.md&version=GBmain&_a=preview) has been reviewed and followed - [x] Code changes are accompanied by appropriate unit tests - [x] Identified and included SMEs needed to review code changes - [x] Follow the [steps](https://msdata.visualstudio.com/SQLToolsAndLibraries/_wiki/wikis/SQLToolsAndLibraries.wiki/33838/Adding-or-Extending-TSql-support-in-Sql-Dom?anchor=make-the-changes-in) here to make changes in the code ## Testing - [x] Follow the [steps](https://msdata.visualstudio.com/SQLToolsAndLibraries/_wiki/wikis/SQLToolsAndLibraries.wiki/33838/Adding-or-Extending-TSql-support-in-Sql-Dom?anchor=to-extend-the-tests-do-the-following%3A) here to add new tests for your feature ## Documentation - [x] Update relevant documentation in the [wiki](https://dev.azure.com/msdata/SQLToolsAndLibraries/_wiki/wikis/SQLToolsAndLibraries.wiki) and the README.md file - no changes requried ## Additional Information [AI and Extensibility.docx](https://microsoft.sharepoint.com/:w:/r/teams/AzurePolaris/_layouts/15/doc2.aspx?sourcedoc=%7B75BAC667-A870-4482-8A37-F80E6EC8FCE0%7D&file=AI%20and%20Extensibiliy.docx) `VARCHAR(MAX) invoke_external_api('functionSetName', 'functionName'[, ... args])` - invokes Fabric Function `functionName` in `functionSetName` where Function Set is `User Defined Functions` item in PowerBI Workspace. `CREATEA FUNCTION [schema].[localName] [RETURNS ] AS EXTERNAL FUNCTION functionSetName.functionName` is a DDL syntax used to create SQL UDF based on function from Function Set. The DML would be: `SELECT [schema].[localName](col1) FROM tbl`, which during expression tree transformation will become `invoke_external_api` ---- #### AI description (iteration 3) #### PR Classification New feature: Adding support for external function definitions and the `INVOKE_EXTERNAL_API` intrinsic function in Trident Data Warehouse (Fabric DW). #### PR Summary This PR introduces syntax support for creating external functions and invoking external APIs in SQL ScriptDom for Fabric DW. It enables defining function aliases to external function sets and calling them via a new intrinsic. - **`TSqlFabricDW.g`**: Added grammar rules for `CREATE/ALTER/CREATE OR ALTER FUNCTION ... AS EXTERNAL FUNCTION` statements and `INVOKE_EXTERNAL_API` function call syntax - **`Ast.xml`**: Defined new AST node types `ExternalFunctionStatement` (and variants) and `InvokeExternalApiFunctionCall` with their properties - **`SqlScriptGeneratorVisitor.ExternalFunctionStatement.cs`** and **`SqlScriptGeneratorVisitor.InvokeExternalApiFunction.cs`**: Implemen... --- SqlScriptDom/Parser/TSql/Ast.xml | 30 +++++ .../Parser/TSql/CodeGenerationSupporter.cs | 1 + SqlScriptDom/Parser/TSql/TSqlFabricDW.g | 122 +++++++++++++++++- ...eratorVisitor.ExternalFunctionStatement.cs | 52 ++++++++ ...eratorVisitor.InvokeExternalApiFunction.cs | 56 ++++++++ .../ExternalFunctionTestsFabricDW.sql | 49 +++++++ .../InvokeExternalApiTestsFabricDW.sql | 27 ++++ Test/SqlDom/OnlyFabricDWSyntaxTests.cs | 2 + Test/SqlDom/ParserErrorsTests.cs | 39 ++++++ .../ExternalFunctionTestsFabricDW.sql | 62 +++++++++ .../InvokeExternalApiTestsFabricDW.sql | 27 ++++ 11 files changed, 466 insertions(+), 1 deletion(-) create mode 100644 SqlScriptDom/ScriptDom/SqlServer/ScriptGenerator/SqlScriptGeneratorVisitor.ExternalFunctionStatement.cs create mode 100644 SqlScriptDom/ScriptDom/SqlServer/ScriptGenerator/SqlScriptGeneratorVisitor.InvokeExternalApiFunction.cs create mode 100644 Test/SqlDom/BaselinesFabricDW/ExternalFunctionTestsFabricDW.sql create mode 100644 Test/SqlDom/BaselinesFabricDW/InvokeExternalApiTestsFabricDW.sql create mode 100644 Test/SqlDom/TestScripts/ExternalFunctionTestsFabricDW.sql create mode 100644 Test/SqlDom/TestScripts/InvokeExternalApiTestsFabricDW.sql diff --git a/SqlScriptDom/Parser/TSql/Ast.xml b/SqlScriptDom/Parser/TSql/Ast.xml index 7715e87a..d90c664a 100644 --- a/SqlScriptDom/Parser/TSql/Ast.xml +++ b/SqlScriptDom/Parser/TSql/Ast.xml @@ -794,6 +794,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + @@ -4828,6 +4852,12 @@ + + + + + + diff --git a/SqlScriptDom/Parser/TSql/CodeGenerationSupporter.cs b/SqlScriptDom/Parser/TSql/CodeGenerationSupporter.cs index 51b35d5d..7bd0e484 100644 --- a/SqlScriptDom/Parser/TSql/CodeGenerationSupporter.cs +++ b/SqlScriptDom/Parser/TSql/CodeGenerationSupporter.cs @@ -541,6 +541,7 @@ internal static class CodeGenerationSupporter internal const string Intermediate = "INTERMEDIATE"; internal const string IntervalLengthMinutes = "INTERVAL_LENGTH_MINUTES"; internal const string Insensitive = "INSENSITIVE"; + internal const string InvokeExternalApi = "INVOKE_EXTERNAL_API"; internal const string IRowset = "IROWSET"; internal const string Isolation = "ISOLATION"; internal const string Job = "JOB"; diff --git a/SqlScriptDom/Parser/TSql/TSqlFabricDW.g b/SqlScriptDom/Parser/TSql/TSqlFabricDW.g index e4a7f24d..4ad40b85 100644 --- a/SqlScriptDom/Parser/TSql/TSqlFabricDW.g +++ b/SqlScriptDom/Parser/TSql/TSqlFabricDW.g @@ -730,7 +730,9 @@ lastStatement returns [TSqlStatement vResult = null] | vResult=createRuleStatement | vResult=createViewStatement | vResult=alterViewStatement + | (Create Function schemaObjectNonEmptyTwoPartName (LeftParenthesis (functionParameter (Comma functionParameter)*)? RightParenthesis)? (Identifier scalarDataType)? As External) => vResult=createExternalFunctionStatement | vResult=createFunctionStatement + | (Alter Function schemaObjectNonEmptyTwoPartName (LeftParenthesis (functionParameter (Comma functionParameter)*)? RightParenthesis)? (Identifier scalarDataType)? As External) => vResult=alterExternalFunctionStatement | vResult=alterFunctionStatement | vResult=createSchemaStatement | vResult=createIdentifierStatement @@ -770,7 +772,8 @@ alterIdentifierStatement returns [TSqlStatement vResult] createOrAlterStatements returns [TSqlStatement vResult] : tCreate:Create Or Alter ( - vResult = createOrAlterFunctionStatement + (Function schemaObjectNonEmptyTwoPartName (LeftParenthesis (functionParameter (Comma functionParameter)*)? RightParenthesis)? (Identifier scalarDataType)? As External) => vResult = createOrAlterExternalFunctionStatement + | vResult = createOrAlterFunctionStatement | vResult = createOrAlterProcedureStatement | vResult = createOrAlterTriggerStatement | vResult = createOrAlterViewStatement @@ -12996,6 +12999,83 @@ createOrAlterFunctionStatement returns [CreateOrAlterFunctionStatement vResult = } ; +createExternalFunctionStatement returns [CreateExternalFunctionStatement vResult = this.FragmentFactory.CreateFragment()] + : tCreate:Create + { + UpdateTokenInfo(vResult, tCreate); + } + externalFunctionStatementBody[vResult] + ; + +alterExternalFunctionStatement returns [AlterExternalFunctionStatement vResult = this.FragmentFactory.CreateFragment()] + : tAlter:Alter + { + UpdateTokenInfo(vResult, tAlter); + } + externalFunctionStatementBody[vResult] + ; + +createOrAlterExternalFunctionStatement returns [CreateOrAlterExternalFunctionStatement vResult = this.FragmentFactory.CreateFragment()] + : externalFunctionStatementBody[vResult] + ; + +externalFunctionStatementBody[ExternalFunctionStatement vResult] +{ + SchemaObjectName vName; + SchemaObjectName vExternalName; + DataTypeReference vReturnType; + ProcedureParameter vParameter; +} + : tFunction:Function + { + UpdateTokenInfo(vResult, tFunction); + } + vName=schemaObjectNonEmptyTwoPartName + { + vResult.Name = vName; + } + ( + LeftParenthesis + ( + vParameter=functionParameter + { + AddAndUpdateTokenInfo(vResult, vResult.Parameters, vParameter); + } + ( Comma vParameter=functionParameter + { + AddAndUpdateTokenInfo(vResult, vResult.Parameters, vParameter); + } + )* + )? + RightParenthesis + )? + ( + {NextTokenMatches(CodeGenerationSupporter.Returns)}? + tReturns:Identifier vReturnType=scalarDataType + { + Match(tReturns, CodeGenerationSupporter.Returns); + UpdateTokenInfo(vResult, tReturns); + vResult.ReturnType = vReturnType; + } + )? + tAs:As + { + UpdateTokenInfo(vResult, tAs); + } + tExternal:External + { + UpdateTokenInfo(vResult, tExternal); + } + tExternalFunction:Function + { + UpdateTokenInfo(vResult, tExternalFunction); + } + vExternalName=schemaObjectNonEmptyTwoPartName + { + vResult.ExternalName = vExternalName; + } + ; + functionStatementBody[FunctionStatementBody vResult, out bool vParseErrorOccurred] { SchemaObjectName vSchemaObjectName; @@ -31491,6 +31571,9 @@ expressionPrimary [ExpressionFlags expressionFlags] returns [PrimaryExpression v | {NextTokenMatches(CodeGenerationSupporter.AITranslate) && (LA(2) == LeftParenthesis)}? vResult=aiTranslateFunctionCall + | + {NextTokenMatches(CodeGenerationSupporter.InvokeExternalApi) && (LA(2) == LeftParenthesis)}? + vResult=invokeExternalApiFunctionCall | (Identifier LeftParenthesis)=> vResult=builtInFunctionCall @@ -32306,6 +32389,43 @@ aiTranslateFunctionCall returns [AITranslateFunctionCall vResult = this.Fragment RightParenthesis ; +// INVOKE_EXTERNAL_API('', '' [, [, ... ]]) +// functionSetName and functionName must be string literals. +// Remaining arguments are optional scalar expressions. +invokeExternalApiFunctionCall + returns [InvokeExternalApiFunctionCall vResult = this.FragmentFactory.CreateFragment()] +{ + StringLiteral vFunctionSetName; + StringLiteral vFunctionName; + ScalarExpression vArg; +} + : + tFunc:Identifier LeftParenthesis + { + Match(tFunc, CodeGenerationSupporter.InvokeExternalApi); + UpdateTokenInfo(vResult, tFunc); + } + vFunctionSetName=stringLiteral + { + vResult.FunctionSetName = vFunctionSetName; + } + Comma + vFunctionName=stringLiteral + { + vResult.FunctionName = vFunctionName; + } + ( + Comma vArg=expression + { + AddAndUpdateTokenInfo(vResult, vResult.Arguments, vArg); + } + )* + tRParen:RightParenthesis + { + UpdateTokenInfo(vResult, tRParen); + } + ; + // TODO, olegr: Add more checks for allowed functions here - there are quite some in SQL Server parser builtInFunctionCall returns [FunctionCall vResult = FragmentFactory.CreateFragment()] { diff --git a/SqlScriptDom/ScriptDom/SqlServer/ScriptGenerator/SqlScriptGeneratorVisitor.ExternalFunctionStatement.cs b/SqlScriptDom/ScriptDom/SqlServer/ScriptGenerator/SqlScriptGeneratorVisitor.ExternalFunctionStatement.cs new file mode 100644 index 00000000..8ab01c63 --- /dev/null +++ b/SqlScriptDom/ScriptDom/SqlServer/ScriptGenerator/SqlScriptGeneratorVisitor.ExternalFunctionStatement.cs @@ -0,0 +1,52 @@ +//------------------------------------------------------------------------------ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// +//------------------------------------------------------------------------------ +using Microsoft.SqlServer.TransactSql.ScriptDom; + +namespace Microsoft.SqlServer.TransactSql.ScriptDom.ScriptGenerator +{ + partial class SqlScriptGeneratorVisitor + { + public override void ExplicitVisit(CreateExternalFunctionStatement node) + { + GenerateKeyword(TSqlTokenType.Create); + GenerateExternalFunctionStatementBody(node); + } + + public override void ExplicitVisit(AlterExternalFunctionStatement node) + { + GenerateKeyword(TSqlTokenType.Alter); + GenerateExternalFunctionStatementBody(node); + } + + public override void ExplicitVisit(CreateOrAlterExternalFunctionStatement node) + { + GenerateKeyword(TSqlTokenType.Create); + GenerateSpaceAndKeyword(TSqlTokenType.Or); + GenerateSpaceAndKeyword(TSqlTokenType.Alter); + GenerateExternalFunctionStatementBody(node); + } + + private void GenerateExternalFunctionStatementBody(ExternalFunctionStatement node) + { + GenerateSpaceAndKeyword(TSqlTokenType.Function); + GenerateSpaceAndFragmentIfNotNull(node.Name); + if (node.Parameters != null && node.Parameters.Count > 0) + { + GenerateSpace(); + GenerateParenthesisedCommaSeparatedList(node.Parameters); + } + if (node.ReturnType != null) + { + GenerateSpaceAndIdentifier(CodeGenerationSupporter.Returns); + GenerateSpaceAndFragmentIfNotNull(node.ReturnType); + } + GenerateSpaceAndKeyword(TSqlTokenType.As); + GenerateSpaceAndIdentifier(CodeGenerationSupporter.External); + GenerateSpaceAndKeyword(TSqlTokenType.Function); + GenerateSpaceAndFragmentIfNotNull(node.ExternalName); + } + } +} diff --git a/SqlScriptDom/ScriptDom/SqlServer/ScriptGenerator/SqlScriptGeneratorVisitor.InvokeExternalApiFunction.cs b/SqlScriptDom/ScriptDom/SqlServer/ScriptGenerator/SqlScriptGeneratorVisitor.InvokeExternalApiFunction.cs new file mode 100644 index 00000000..8b6e8188 --- /dev/null +++ b/SqlScriptDom/ScriptDom/SqlServer/ScriptGenerator/SqlScriptGeneratorVisitor.InvokeExternalApiFunction.cs @@ -0,0 +1,56 @@ +//------------------------------------------------------------------------------ +// +// Copyright (c) Microsoft Corporation. All rights reserved. +// +//------------------------------------------------------------------------------ + +using System; + +using Microsoft.SqlServer.TransactSql.ScriptDom; + +namespace Microsoft.SqlServer.TransactSql.ScriptDom.ScriptGenerator +{ + partial class SqlScriptGeneratorVisitor + { + /// + /// Emits an INVOKE_EXTERNAL_API function call like + /// INVOKE_EXTERNAL_API('FunctionSetName', 'FunctionName' [, arg1 [, arg2 ...]]). + /// FunctionSetName and FunctionName are required string literals; remaining + /// arguments are optional scalar expressions. + /// + /// Expression node to generate + public override void ExplicitVisit(InvokeExternalApiFunctionCall node) + { + if (node.FunctionSetName == null) + { + throw new InvalidOperationException("InvokeExternalApiFunctionCall.FunctionSetName is required."); + } + + if (node.FunctionName == null) + { + throw new InvalidOperationException("InvokeExternalApiFunctionCall.FunctionName is required."); + } + + GenerateIdentifier(CodeGenerationSupporter.InvokeExternalApi); + GenerateSymbol(TSqlTokenType.LeftParenthesis); + + GenerateFragmentIfNotNull(node.FunctionSetName); + + GenerateSymbol(TSqlTokenType.Comma); + GenerateSpace(); + GenerateFragmentIfNotNull(node.FunctionName); + + if (node.Arguments != null) + { + for (int i = 0; i < node.Arguments.Count; i++) + { + GenerateSymbol(TSqlTokenType.Comma); + GenerateSpace(); + GenerateFragmentIfNotNull(node.Arguments[i]); + } + } + + GenerateSymbol(TSqlTokenType.RightParenthesis); + } + } +} diff --git a/Test/SqlDom/BaselinesFabricDW/ExternalFunctionTestsFabricDW.sql b/Test/SqlDom/BaselinesFabricDW/ExternalFunctionTestsFabricDW.sql new file mode 100644 index 00000000..6920cc20 --- /dev/null +++ b/Test/SqlDom/BaselinesFabricDW/ExternalFunctionTestsFabricDW.sql @@ -0,0 +1,49 @@ +CREATE FUNCTION dbo.MyExternalFn AS EXTERNAL FUNCTION mySet.myFn; + +GO +ALTER FUNCTION dbo.MyExternalFn AS EXTERNAL FUNCTION mySet.anotherFn; + +GO +CREATE OR ALTER FUNCTION dbo.MyExternalFn AS EXTERNAL FUNCTION mySet.myFn; + +GO +CREATE FUNCTION MyFn AS EXTERNAL FUNCTION myFn; + +GO +CREATE FUNCTION dbo.MyExternalFn RETURNS MONEY AS EXTERNAL FUNCTION mySet.myFn; + +GO +ALTER FUNCTION dbo.MyExternalFn RETURNS NVARCHAR (100) AS EXTERNAL FUNCTION mySet.myFn; + +GO +CREATE OR ALTER FUNCTION dbo.MyExternalFn RETURNS INT AS EXTERNAL FUNCTION mySet.myFn; + +GO +CREATE FUNCTION dbo.MyExternalFn (@x INT) AS EXTERNAL FUNCTION mySet.myFn; + +GO +CREATE FUNCTION dbo.MyExternalFn (@x INT, @y NVARCHAR (50)) RETURNS INT AS EXTERNAL FUNCTION mySet.myFn; + +GO +ALTER FUNCTION dbo.MyExternalFn (@x INT) RETURNS BIGINT AS EXTERNAL FUNCTION mySet.myFn; + +GO +CREATE OR ALTER FUNCTION dbo.MyExternalFn (@x INT) RETURNS INT AS EXTERNAL FUNCTION mySet.myFn; + +GO +DROP FUNCTION dbo.MyExternalFn; + +GO +SELECT dbo.MyExternalFn(); + +GO +SELECT dbo.MyExternalFn(42, N'hello'); + +GO +DECLARE @v AS INT = 1; + +SELECT dbo.MyExternalFn(@v, 'const'); + +GO +SELECT dbo.MyExternalFn(t.col1, t.col2) AS r +FROM dbo.t AS t; diff --git a/Test/SqlDom/BaselinesFabricDW/InvokeExternalApiTestsFabricDW.sql b/Test/SqlDom/BaselinesFabricDW/InvokeExternalApiTestsFabricDW.sql new file mode 100644 index 00000000..68422135 --- /dev/null +++ b/Test/SqlDom/BaselinesFabricDW/InvokeExternalApiTestsFabricDW.sql @@ -0,0 +1,27 @@ +SELECT INVOKE_EXTERNAL_API('myFunctionSet', 'myFunction'); + + +GO +SELECT INVOKE_EXTERNAL_API('myFunctionSet', 'myFunction', 42); + + +GO +DECLARE @v AS INT = 7; + +SELECT INVOKE_EXTERNAL_API(N'mySet', N'doStuff', @v, 'literal', 1 + 2, NULL); + + +GO +ALTER FUNCTION dbo.TestInvokeExternalApi +( ) +RETURNS NVARCHAR (MAX) +AS +BEGIN + RETURN (INVOKE_EXTERNAL_API('mySet', 'myFn', 'arg1')); +END + + +GO +SELECT 1 +FROM dbo.t +WHERE INVOKE_EXTERNAL_API('mySet', 'isAllowed', col1) = 1; diff --git a/Test/SqlDom/OnlyFabricDWSyntaxTests.cs b/Test/SqlDom/OnlyFabricDWSyntaxTests.cs index 8898e811..483e08ef 100644 --- a/Test/SqlDom/OnlyFabricDWSyntaxTests.cs +++ b/Test/SqlDom/OnlyFabricDWSyntaxTests.cs @@ -25,6 +25,8 @@ public partial class SqlDomTests new ParserTestFabricDW("AiGenerateResponseTestsFabricDW.sql", nErrors80: 3, nErrors90: 1, nErrors100: 2, nErrors110: 2, nErrors120: 2, nErrors130: 0, nErrors140: 0, nErrors150: 0, nErrors160: 0, nErrors170: 0, nErrors180: 0), new ParserTestFabricDW("AiSummarizeTestsFabricDW.sql", nErrors80: 2, nErrors90: 1, nErrors100: 2, nErrors110: 2, nErrors120: 2, nErrors130: 0, nErrors140: 0, nErrors150: 0, nErrors160: 0, nErrors170: 0, nErrors180: 0), new ParserTestFabricDW("AiTranslateTestsFabricDW.sql", nErrors80: 3, nErrors90: 1, nErrors100: 2, nErrors110: 2, nErrors120: 2, nErrors130: 0, nErrors140: 0, nErrors150: 0, nErrors160: 0, nErrors170: 0, nErrors180: 0), + new ParserTestFabricDW("InvokeExternalApiTestsFabricDW.sql", nErrors80: 2, nErrors90: 1, nErrors100: 0, nErrors110: 0, nErrors120: 0, nErrors130: 0, nErrors140: 0, nErrors150: 0, nErrors160: 0, nErrors170: 0, nErrors180: 0), + new ParserTestFabricDW("ExternalFunctionTestsFabricDW.sql", nErrors80: 12, nErrors90: 4, nErrors100: 4, nErrors110: 4, nErrors120: 4, nErrors130: 11, nErrors140: 11, nErrors150: 11, nErrors160: 11, nErrors170: 11, nErrors180: 0), }; [TestMethod] diff --git a/Test/SqlDom/ParserErrorsTests.cs b/Test/SqlDom/ParserErrorsTests.cs index 01c1b317..faeb5a68 100644 --- a/Test/SqlDom/ParserErrorsTests.cs +++ b/Test/SqlDom/ParserErrorsTests.cs @@ -7945,6 +7945,45 @@ public void AiTranslateNegativeTestsFabricDw() new ParserErrorInfo(28, "SQL46010", ",")); } + /// + /// Negative tests for CREATE/ALTER FUNCTION ... AS EXTERNAL FUNCTION syntax in Fabric DW. + /// + [TestMethod] + [Priority(0)] + [SqlStudioTestCategory(Category.UnitTest)] + public void ExternalFunctionNegativeTestsFabricDW() + { + // Missing external name after EXTERNAL FUNCTION + ParserTestUtils.ErrorTestFabricDW( + "CREATE FUNCTION dbo.f AS EXTERNAL FUNCTION", + new ParserErrorInfo(42, "SQL46029")); + + // Missing FUNCTION keyword after EXTERNAL + ParserTestUtils.ErrorTestFabricDW( + "CREATE FUNCTION dbo.f AS EXTERNAL mySet.myFn", + new ParserErrorInfo(34, "SQL46010", "mySet")); + + // Three-part external name not allowed + ParserTestUtils.ErrorTestFabricDW( + "CREATE FUNCTION dbo.f AS EXTERNAL FUNCTION db.mySet.myFn", + new ParserErrorInfo(51, "SQL46010", ".")); + + // RETURNS without a data type + ParserTestUtils.ErrorTestFabricDW( + "CREATE FUNCTION dbo.f RETURNS AS EXTERNAL FUNCTION mySet.myFn", + new ParserErrorInfo(22, "SQL46010", "RETURNS")); + + // ALTER: missing external name + ParserTestUtils.ErrorTestFabricDW( + "ALTER FUNCTION dbo.f AS EXTERNAL FUNCTION", + new ParserErrorInfo(41, "SQL46029")); + + // CREATE OR ALTER: missing FUNCTION keyword after EXTERNAL + ParserTestUtils.ErrorTestFabricDW( + "CREATE OR ALTER FUNCTION dbo.f AS EXTERNAL mySet.myFn", + new ParserErrorInfo(43, "SQL46010", "mySet")); + } + /// /// Negative test for OFFSET with FETCH APPROXIMATE - SQL46145 error /// OFFSET clause cannot be used with FETCH APPROXIMATE (introduced in SQL Server 2025) diff --git a/Test/SqlDom/TestScripts/ExternalFunctionTestsFabricDW.sql b/Test/SqlDom/TestScripts/ExternalFunctionTestsFabricDW.sql new file mode 100644 index 00000000..43d8dc1c --- /dev/null +++ b/Test/SqlDom/TestScripts/ExternalFunctionTestsFabricDW.sql @@ -0,0 +1,62 @@ +-- Basic CREATE FUNCTION ... AS EXTERNAL FUNCTION +CREATE FUNCTION dbo.MyExternalFn AS EXTERNAL FUNCTION mySet.myFn; +GO + +-- ALTER FUNCTION ... AS EXTERNAL FUNCTION +ALTER FUNCTION dbo.MyExternalFn AS EXTERNAL FUNCTION mySet.anotherFn; +GO + +-- CREATE OR ALTER FUNCTION ... AS EXTERNAL FUNCTION +CREATE OR ALTER FUNCTION dbo.MyExternalFn AS EXTERNAL FUNCTION mySet.myFn; +GO + +-- Single-part external name +CREATE FUNCTION MyFn AS EXTERNAL FUNCTION myFn; +GO + +-- CREATE with RETURNS clause specifying explicit return type +CREATE FUNCTION dbo.MyExternalFn RETURNS MONEY AS EXTERNAL FUNCTION mySet.myFn; +GO + +-- ALTER with RETURNS clause +ALTER FUNCTION dbo.MyExternalFn RETURNS NVARCHAR(100) AS EXTERNAL FUNCTION mySet.myFn; +GO + +-- CREATE OR ALTER with RETURNS clause +CREATE OR ALTER FUNCTION dbo.MyExternalFn RETURNS INT AS EXTERNAL FUNCTION mySet.myFn; +GO + +-- CREATE with single parameter +CREATE FUNCTION dbo.MyExternalFn (@x INT) AS EXTERNAL FUNCTION mySet.myFn; +GO + +-- CREATE with multiple parameters and RETURNS +CREATE FUNCTION dbo.MyExternalFn (@x INT, @y NVARCHAR(50)) RETURNS INT AS EXTERNAL FUNCTION mySet.myFn; +GO + +-- ALTER with parameters and RETURNS +ALTER FUNCTION dbo.MyExternalFn (@x INT) RETURNS BIGINT AS EXTERNAL FUNCTION mySet.myFn; +GO + +-- CREATE OR ALTER with parameters and RETURNS +CREATE OR ALTER FUNCTION dbo.MyExternalFn (@x INT) RETURNS INT AS EXTERNAL FUNCTION mySet.myFn; +GO + +-- DROP FUNCTION (existing syntax, no change needed) +DROP FUNCTION dbo.MyExternalFn; +GO + +-- DML: invoking the external function like a regular UDF +SELECT dbo.MyExternalFn(); +GO + +SELECT dbo.MyExternalFn(42, N'hello'); +GO + +DECLARE @v INT = 1; +SELECT dbo.MyExternalFn(@v, 'const'); +GO + +SELECT dbo.MyExternalFn(t.col1, t.col2) AS r +FROM dbo.t AS t; +GO diff --git a/Test/SqlDom/TestScripts/InvokeExternalApiTestsFabricDW.sql b/Test/SqlDom/TestScripts/InvokeExternalApiTestsFabricDW.sql new file mode 100644 index 00000000..ff0d704b --- /dev/null +++ b/Test/SqlDom/TestScripts/InvokeExternalApiTestsFabricDW.sql @@ -0,0 +1,27 @@ +-- Basic call with only required arguments. +SELECT INVOKE_EXTERNAL_API('myFunctionSet', 'myFunction'); +GO + +-- With a single optional argument. +SELECT INVOKE_EXTERNAL_API('myFunctionSet', 'myFunction', 42); +GO + +-- With multiple optional arguments of various expression kinds. +DECLARE @v INT = 7; +SELECT INVOKE_EXTERNAL_API(N'mySet', N'doStuff', @v, 'literal', 1 + 2, NULL); +GO + +-- Inside an ALTER FUNCTION RETURN statement (critical RETURN-context coverage). +ALTER FUNCTION dbo.TestInvokeExternalApi() +RETURNS NVARCHAR(MAX) +AS +BEGIN + RETURN (INVOKE_EXTERNAL_API('mySet', 'myFn', 'arg1')); +END; +GO + +-- Inside a WHERE clause. +SELECT 1 +FROM dbo.t +WHERE INVOKE_EXTERNAL_API('mySet', 'isAllowed', col1) = 1; +GO From 5c7197c5c0cc227e6951e1f3dfce8c28ce60fbeb Mon Sep 17 00:00:00 2001 From: Leila Lali Date: Wed, 17 Jun 2026 15:28:51 +0000 Subject: [PATCH 3/3] Merged PR 2150081: Adding release notes for 180.37.3 adding release notes for 180.37.3 ---- #### AI description (iteration 2) #### PR Classification This is a documentation update adding release notes for version 180.35.0 of Microsoft.SqlServer.TransactSql.ScriptDom. #### PR Summary This pull request adds comprehensive release notes documenting new features, bug fixes, and improvements in version 180.35.0 of the SQL Script DOM library. - `/release-notes/180/180.37.3.md`: Documents support for CREATE FUNCTION AS EXTERNAL FUNCTION in Microsoft Fabric Warehouse - `/release-notes/180/180.37.3.md`: Lists bug fixes for PERSIST_SAMPLE_PERCENT parsing, AlterTableAddTableElementStatement separator issues, and WindowDefinition whitespace handling - `/release-notes/180/180.37.3.md`: Notes improvements to comment preservation in SQL Server script generator and .NET SDK update to version 8.0.420 --- release-notes/180/180.37.3.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 release-notes/180/180.37.3.md diff --git a/release-notes/180/180.37.3.md b/release-notes/180/180.37.3.md new file mode 100644 index 00000000..b7e7bac0 --- /dev/null +++ b/release-notes/180/180.37.3.md @@ -0,0 +1,30 @@ +# Release Notes + +## Microsoft.SqlServer.TransactSql.ScriptDom 180.37.3 +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.420 + +#### .NET Framework +#### .NET Core + +### New Features +* Adds support for CREATE FUNCTION AS EXTERNAL FUNCTION in Warehouse in Microsoft Fabric. + +### Fixed +* Fix PERSIST_SAMPLE_PERCENT statistics option parsing [#207](https://github.com/microsoft/SqlScriptDOM/pull/207) +* Fixes the issue with AlterTableAddTableElementStatement omitted the separator between column-definitions/constraints and table indexes, producing 'NOT NULLINDEX ix_...' +* Fixes the issue with WindowDefinition omitted whitespace between the inherited window-name reference (RefWindowName) and a following PARTITION BY or ORDER BY, producing 'win2PARTITION ...' + +### Changes +* Improvements to how comments are preserved and emitted in the SQL Server script generator. + +### Known Issues +* None \ No newline at end of file