@@ -42,6 +42,12 @@ public class SqlServerCollection<TKey, TRecord>
4242 /// <summary>The database schema.</summary>
4343 private readonly string ? _schema ;
4444
45+ /// <summary>Whether the model contains any DiskAnn vector properties, requiring Azure SQL.</summary>
46+ private readonly bool _requiresAzureSql ;
47+
48+ /// <summary>Cached result of the Azure SQL engine edition check (null = not yet checked).</summary>
49+ private bool ? _isAzureSql ;
50+
4551 /// <summary>
4652 /// Initializes a new instance of the <see cref="SqlServerCollection{TKey, TRecord}"/> class.
4753 /// </summary>
@@ -78,6 +84,16 @@ internal SqlServerCollection(string connectionString, string name, Func<SqlServe
7884
7985 this . _mapper = new SqlServerMapper < TRecord > ( this . _model ) ;
8086
87+ // Check if any vector property uses DiskAnn, which requires Azure SQL.
88+ foreach ( var vp in this . _model . VectorProperties )
89+ {
90+ if ( vp . IndexKind == IndexKind . DiskAnn )
91+ {
92+ this . _requiresAzureSql = true ;
93+ break ;
94+ }
95+ }
96+
8197 var connectionStringBuilder = new SqlConnectionStringBuilder ( connectionString ) ;
8298
8399 this . _collectionMetadata = new ( )
@@ -116,6 +132,12 @@ public override Task EnsureCollectionExistsAsync(CancellationToken cancellationT
116132 private async Task CreateCollectionAsync ( bool ifNotExists , CancellationToken cancellationToken )
117133 {
118134 using SqlConnection connection = new ( this . _connectionString ) ;
135+
136+ if ( this . _requiresAzureSql )
137+ {
138+ await this . EnsureAzureSqlForDiskAnnAsync ( connection , cancellationToken ) . ConfigureAwait ( false ) ;
139+ }
140+
119141 List < SqlCommand > commands = SqlServerCommandBuilder . CreateTable (
120142 connection ,
121143 this . _schema ,
@@ -604,6 +626,12 @@ _ when vectorProperty.EmbeddingGenerationDispatcher is not null
604626 // Connection and command are going to be disposed by the ReadVectorSearchResultsAsync,
605627 // when the user is done with the results.
606628 SqlConnection connection = new ( this . _connectionString ) ;
629+
630+ if ( vectorProperty . IndexKind == IndexKind . DiskAnn )
631+ {
632+ await this . EnsureAzureSqlForDiskAnnAsync ( connection , cancellationToken ) . ConfigureAwait ( false ) ;
633+ }
634+
607635 SqlCommand command = SqlServerCommandBuilder . SelectVector (
608636 connection ,
609637 this . _schema ,
@@ -664,6 +692,12 @@ _ when vectorProperty.EmbeddingGenerationDispatcher is not null
664692 // Connection and command are going to be disposed by the ReadVectorSearchResultsAsync,
665693 // when the user is done with the results.
666694 SqlConnection connection = new ( this . _connectionString ) ;
695+
696+ if ( vectorProperty . IndexKind == IndexKind . DiskAnn )
697+ {
698+ await this . EnsureAzureSqlForDiskAnnAsync ( connection , cancellationToken ) . ConfigureAwait ( false ) ;
699+ }
700+
667701 SqlCommand command = SqlServerCommandBuilder . SelectHybrid (
668702 connection ,
669703 this . _schema ,
@@ -807,4 +841,48 @@ public override async IAsyncEnumerable<TRecord> GetAsync(Expression<Func<TRecord
807841 yield return this . _mapper . MapFromStorageToDataModel ( reader , options . IncludeVectors ) ;
808842 }
809843 }
844+
845+ /// <summary>
846+ /// Validates that the connection is to Azure SQL Database or SQL database in Microsoft Fabric,
847+ /// which is required for DiskAnn vector indexes and the VECTOR_SEARCH function.
848+ /// </summary>
849+ private async Task EnsureAzureSqlForDiskAnnAsync ( SqlConnection connection , CancellationToken cancellationToken )
850+ {
851+ if ( this . _isAzureSql is true )
852+ {
853+ return ;
854+ }
855+
856+ if ( this . _isAzureSql is false )
857+ {
858+ connection . Dispose ( ) ;
859+ throw new NotSupportedException (
860+ "DiskAnn vector indexes and the VECTOR_SEARCH function require Azure SQL Database or SQL database in Microsoft Fabric. " +
861+ "They are not supported on SQL Server. Use a Flat index kind with VECTOR_DISTANCE instead." ) ;
862+ }
863+
864+ if ( connection . State != System . Data . ConnectionState . Open )
865+ {
866+ await connection . OpenAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
867+ }
868+
869+ using var command = connection . CreateCommand ( ) ;
870+ command . CommandText = "SELECT SERVERPROPERTY('EngineEdition')" ;
871+ var result = await command . ExecuteScalarAsync ( cancellationToken ) . ConfigureAwait ( false ) ;
872+ var engineEdition = Convert . ToInt32 ( result ) ;
873+
874+ // 5 = Azure SQL Database, 11 = SQL database in Microsoft Fabric
875+ this . _isAzureSql = engineEdition is 5 or 11 ;
876+
877+ if ( ! this . _isAzureSql . Value )
878+ {
879+ // Dispose the connection before throwing; in SearchAsync/HybridSearchAsync the connection
880+ // is not in a using block (it's normally disposed by ReadVectorSearchResultsAsync).
881+ connection . Dispose ( ) ;
882+
883+ throw new NotSupportedException (
884+ "DiskAnn vector indexes and the VECTOR_SEARCH function require Azure SQL Database or SQL database in Microsoft Fabric. " +
885+ "They are not supported on SQL Server. Use a Flat index kind with VECTOR_DISTANCE instead." ) ;
886+ }
887+ }
810888}
0 commit comments