@@ -20,9 +20,9 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
2020SOFTWARE.
2121*/
2222
23+ using System . Runtime . InteropServices ;
2324using Apache . Arrow ;
2425using Apache . Arrow . Adbc ;
25- using Apache . Arrow . Adbc . Drivers . Interop . FlightSql ;
2626using Apache . Arrow . Ipc ;
2727using Apache . Arrow . Types ;
2828using Polly . Retry ;
@@ -114,23 +114,36 @@ private void InitializeIfNeeded()
114114 return ;
115115 }
116116
117- // Format the URI for ADBC FlightSQL Go driver
118- // The Go-based driver handles grpc/ grpc+tls schemes natively
117+ // Format the URI for ADBC FlightSQL driver
118+ // The driver expects grpc:// or grpc+tls:// schemes
119119 var uri = _flightAddress ;
120120
121- // Ensure proper scheme if not already present
122- if ( ! uri . StartsWith ( "grpc://" , StringComparison . OrdinalIgnoreCase ) &&
123- ! uri . StartsWith ( "grpc+tls://" , StringComparison . OrdinalIgnoreCase ) &&
124- ! uri . StartsWith ( "grpc+tcp://" , StringComparison . OrdinalIgnoreCase ) &&
125- ! uri . StartsWith ( "http://" , StringComparison . OrdinalIgnoreCase ) &&
126- ! uri . StartsWith ( "https://" , StringComparison . OrdinalIgnoreCase ) )
121+ // Convert http/https schemes to grpc/grpc+tls
122+ if ( uri . StartsWith ( "https://" , StringComparison . OrdinalIgnoreCase ) )
123+ {
124+ #if NETSTANDARD2_0
125+ uri = "grpc+tls://" + uri . Substring ( 8 ) ;
126+ #else
127+ uri = string . Concat ( "grpc+tls://" , uri . AsSpan ( 8 ) ) ;
128+ #endif
129+ }
130+ else if ( uri . StartsWith ( "http://" , StringComparison . OrdinalIgnoreCase ) )
131+ {
132+ #if NETSTANDARD2_0
133+ uri = "grpc://" + uri . Substring ( 7 ) ;
134+ #else
135+ uri = string . Concat ( "grpc://" , uri . AsSpan ( 7 ) ) ;
136+ #endif
137+ }
138+ else if ( ! uri . StartsWith ( "grpc://" , StringComparison . OrdinalIgnoreCase ) &&
139+ ! uri . StartsWith ( "grpc+tls://" , StringComparison . OrdinalIgnoreCase ) &&
140+ ! uri . StartsWith ( "grpc+tcp://" , StringComparison . OrdinalIgnoreCase ) )
127141 {
128142 // No scheme provided - add grpc or grpc+tls based on TLS setting
129143 uri = _useTls ? string . Concat ( "grpc+tls://" , uri ) : string . Concat ( "grpc://" , uri ) ;
130144 }
131145
132- // Build database parameters for the Go driver
133- // The Go driver uses "uri" as the connection parameter
146+ // Build database parameters for the ADBC FlightSQL driver
134147 var databaseParams = new Dictionary < string , string >
135148 {
136149 { "uri" , uri }
@@ -143,11 +156,12 @@ private void InitializeIfNeeded()
143156 databaseParams [ "password" ] = _apiKey ! ;
144157 }
145158
146- // Add user agent header using the Go driver's header prefix
159+ // Add user agent header
147160 databaseParams [ "adbc.flight.sql.rpc.call_header.user-agent" ] = UserAgentHelper . BuildUserAgent ( _userAgent ) ;
148161
149- // Create the Go-based interop driver and database
150- var driver = FlightSqlDriverLoader . LoadDriver ( ) ;
162+ // Load the ADBC FlightSQL driver from the application base directory
163+ var driverPath = ResolveNativeDriverPath ( ) ;
164+ var driver = AdbcDriverLoader . LoadDriver ( driverPath , "AdbcDriverFlightsqlInit" ) ;
151165 _database = driver . Open ( databaseParams ) ;
152166 _connection = _database . Connect ( new Dictionary < string , string > ( ) ) ;
153167 }
@@ -170,22 +184,34 @@ private void InitializeIfNeeded()
170184 {
171185 InitializeIfNeeded ( ) ;
172186
173- using var statement = _connection ! . CreateStatement ( ) ;
174- statement . SqlQuery = sql ;
187+ var statement = _connection ! . CreateStatement ( ) ;
188+ try
189+ {
190+ statement . SqlQuery = sql ;
191+
192+ // Prepare the statement
193+ statement . Prepare ( ) ;
175194
176- // Prepare the statement
177- statement . Prepare ( ) ;
195+ // Bind parameters if provided
196+ if ( parameters . Length > 0 )
197+ {
198+ var parameterBatch = CreateParameterBatch ( parameters ) ;
199+ statement . Bind ( parameterBatch , parameterBatch . Schema ) ;
200+ }
178201
179- // Bind parameters if provided
180- if ( parameters . Length > 0 )
202+ // Execute the query
203+ var result = statement . ExecuteQuery ( ) ;
204+
205+ // Wrap the stream to keep the statement alive for the stream's lifetime
206+ var wrappedStream = new StatementBoundArrowArrayStream ( result . Stream ! , statement ) ;
207+ return Task . FromResult < IArrowArrayStream ? > ( wrappedStream ) ;
208+ }
209+ catch
181210 {
182- var parameterBatch = CreateParameterBatch ( parameters ) ;
183- statement . Bind ( parameterBatch , parameterBatch . Schema ) ;
211+ // If anything fails before we wrap the stream, dispose the statement
212+ statement . Dispose ( ) ;
213+ throw ;
184214 }
185-
186- // Execute the query
187- var result = statement . ExecuteQuery ( ) ;
188- return Task . FromResult ( result . Stream ) ;
189215 } ) ;
190216 }
191217
@@ -513,6 +539,61 @@ private static Decimal256Array CreateDecimal256Array(object? value, Decimal256Ty
513539
514540 // ============ Disposal ============
515541
542+ /// <summary>
543+ /// Resolves the path to the native ADBC FlightSQL driver based on the current platform.
544+ /// Uses standard .NET conventions: AppContext.BaseDirectory and runtimes/{rid}/native/ layout.
545+ /// </summary>
546+ private static string ResolveNativeDriverPath ( )
547+ {
548+ var rid = GetRuntimeIdentifier ( ) ;
549+ var driverFileName = GetDriverFileName ( ) ;
550+ var driverPath = Path . Combine ( AppContext . BaseDirectory , "runtimes" , rid , "native" , driverFileName ) ;
551+
552+ if ( ! File . Exists ( driverPath ) )
553+ {
554+ throw new FileNotFoundException (
555+ $ "Could not find native ADBC FlightSQL driver at { driverPath } . " +
556+ $ "Ensure the SpiceAI NuGet package is properly installed.",
557+ driverPath ) ;
558+ }
559+
560+ return driverPath ;
561+ }
562+
563+ /// <summary>
564+ /// Gets the runtime identifier for the current platform.
565+ /// </summary>
566+ private static string GetRuntimeIdentifier ( )
567+ {
568+ var arch = RuntimeInformation . OSArchitecture . ToString ( ) . ToLowerInvariant ( ) ;
569+
570+ if ( RuntimeInformation . IsOSPlatform ( OSPlatform . Windows ) )
571+ return $ "win-{ arch } ";
572+ if ( RuntimeInformation . IsOSPlatform ( OSPlatform . Linux ) )
573+ return $ "linux-{ arch } ";
574+ if ( RuntimeInformation . IsOSPlatform ( OSPlatform . OSX ) )
575+ return $ "osx-{ arch } ";
576+
577+ throw new PlatformNotSupportedException (
578+ $ "Unsupported platform: { RuntimeInformation . OSDescription } ") ;
579+ }
580+
581+ /// <summary>
582+ /// Gets the platform-specific driver filename.
583+ /// </summary>
584+ private static string GetDriverFileName ( )
585+ {
586+ if ( RuntimeInformation . IsOSPlatform ( OSPlatform . Windows ) )
587+ return "libadbc_driver_flightsql.dll" ;
588+ if ( RuntimeInformation . IsOSPlatform ( OSPlatform . Linux ) )
589+ return "libadbc_driver_flightsql.so" ;
590+ if ( RuntimeInformation . IsOSPlatform ( OSPlatform . OSX ) )
591+ return "libadbc_driver_flightsql.dylib" ;
592+
593+ throw new PlatformNotSupportedException (
594+ $ "Unsupported platform: { RuntimeInformation . OSDescription } ") ;
595+ }
596+
516597 private bool _disposed ;
517598
518599 public void Dispose ( )
0 commit comments