diff --git a/SqlScriptDom/Parser/TSql/Ast.xml b/SqlScriptDom/Parser/TSql/Ast.xml
index bad570b2..d90c664a 100644
--- a/SqlScriptDom/Parser/TSql/Ast.xml
+++ b/SqlScriptDom/Parser/TSql/Ast.xml
@@ -794,6 +794,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -4658,6 +4682,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -4806,6 +4852,12 @@
+
+
+
+
+
+
diff --git a/SqlScriptDom/Parser/TSql/CodeGenerationSupporter.cs b/SqlScriptDom/Parser/TSql/CodeGenerationSupporter.cs
index e2712855..7bd0e484 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";
@@ -536,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";
@@ -755,6 +761,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 +933,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 +943,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 +1133,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/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.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/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/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/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/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/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 ce5ab88a..faeb5a68 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
///
@@ -7868,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/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];
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
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