blob: 786c347de598ae0ea6fb4b162511ab43ead1c46b [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using Apache.Arrow.Adbc.Extensions;
using Apache.Arrow.C;
using Apache.Arrow.Ipc;
namespace Apache.Arrow.Adbc.C
{
internal delegate AdbcStatusCode AdbcDriverInit(int version, ref CAdbcDriver driver, ref CAdbcError error);
/// <summary>
/// Class for working with imported drivers from files
/// </summary>
public static class CAdbcDriverImporter
{
private const string driverInit = "AdbcDriverInit";
private const int ADBC_VERSION_1_0_0 = 1000000;
/// <summary>
/// Loads an <see cref="AdbcDriver"/> from the file system.
/// </summary>
/// <param name="file">The path to the driver to load</param>
/// <param name="entryPoint">The name of the entry point. If not provided, the name AdbcDriverInit will be used.</param>
public static AdbcDriver Load(string file, string? entryPoint = null)
{
if (file == null)
{
throw new ArgumentNullException(nameof(file));
}
if (!File.Exists(file))
{
throw new ArgumentException("file does not exist", nameof(file));
}
IntPtr library = NativeLibrary.Load(file);
if (library == IntPtr.Zero)
{
throw new ArgumentException("unable to load library", nameof(file));
}
try
{
entryPoint = entryPoint ?? driverInit;
IntPtr export = NativeLibrary.GetExport(library, entryPoint);
if (export == IntPtr.Zero)
{
NativeLibrary.Free(library);
throw new ArgumentException($"Unable to find {entryPoint} export", nameof(file));
}
AdbcDriverInit init = Marshal.GetDelegateForFunctionPointer<AdbcDriverInit>(export);
CAdbcDriver driver = new CAdbcDriver();
using (CallHelper caller = new CallHelper())
{
caller.Call(init, ADBC_VERSION_1_0_0, ref driver);
ValidateDriver(ref driver, ADBC_VERSION_1_0_0);
ImportedAdbcDriver result = new ImportedAdbcDriver(library, driver);
library = IntPtr.Zero;
return result;
}
}
finally
{
if (library != IntPtr.Zero) { NativeLibrary.Free(library); }
}
}
private static unsafe void ValidateDriver(ref CAdbcDriver driver, int version)
{
#if NET5_0_OR_GREATER
void* empty = null;
#else
IntPtr empty = IntPtr.Zero;
#endif
if (driver.DatabaseNew == empty) { throw AdbcException.Missing(nameof(driver.DatabaseNew)); }
if (driver.DatabaseInit == empty) { throw AdbcException.Missing(nameof(driver.DatabaseInit)); }
if (driver.DatabaseRelease == empty) { throw AdbcException.Missing(nameof(driver.DatabaseRelease)); }
if (driver.DatabaseSetOption == empty) { driver.DatabaseSetOption = DatabaseSetOptionDefault; }
if (driver.ConnectionNew == empty) { throw AdbcException.Missing(nameof(driver.ConnectionNew)); }
if (driver.ConnectionInit == empty) { throw AdbcException.Missing(nameof(driver.ConnectionInit)); }
if (driver.ConnectionRelease == empty) { throw AdbcException.Missing(nameof(driver.ConnectionRelease)); }
if (driver.ConnectionCommit == empty) { driver.ConnectionCommit = ConnectionCommitDefault; }
if (driver.ConnectionGetInfo == empty) { driver.ConnectionGetInfo = ConnectionGetInfoDefault; }
if (driver.ConnectionGetObjects == empty) { driver.ConnectionGetObjects = ConnectionGetObjectsDefault; }
if (driver.ConnectionGetTableSchema == empty) { driver.ConnectionGetTableSchema = ConnectionGetTableSchemaDefault; }
if (driver.ConnectionGetTableTypes == empty) { driver.ConnectionGetTableTypes = ConnectionGetTableTypesDefault; }
if (driver.ConnectionReadPartition == empty) { driver.ConnectionReadPartition = ConnectionReadPartitionDefault; }
if (driver.ConnectionRollback == empty) { driver.ConnectionRollback = ConnectionRollbackDefault; }
if (driver.ConnectionSetOption == empty) { driver.ConnectionSetOption = ConnectionSetOptionDefault; }
if (driver.StatementExecutePartitions == empty) { driver.StatementExecutePartitions = StatementExecutePartitionsDefault; }
if (driver.StatementExecuteQuery == empty) { throw AdbcException.Missing(nameof(driver.StatementExecuteQuery)); }
if (driver.StatementNew == empty) { throw AdbcException.Missing(nameof(driver.StatementNew)); }
if (driver.StatementRelease == empty) { throw AdbcException.Missing(nameof(driver.StatementRelease)); }
if (driver.StatementBind == empty) { driver.StatementBind = StatementBindDefault; }
if (driver.StatementBindStream == empty) { driver.StatementBindStream = StatementBindStreamDefault; }
if (driver.StatementGetParameterSchema == empty) { driver.StatementGetParameterSchema = StatementGetParameterSchemaDefault; }
if (driver.StatementPrepare == empty) { driver.StatementPrepare = StatementPrepareDefault; }
if (driver.StatementSetOption == empty) { driver.StatementSetOption = StatementSetOptionDefault; }
if (driver.StatementSetSqlQuery == empty) { driver.StatementSetSqlQuery = StatementSetSqlQueryDefault; }
if (driver.StatementSetSubstraitPlan == empty) { driver.StatementSetSubstraitPlan = StatementSetSubstraitPlanDefault; }
}
#if !NET5_0_OR_GREATER
private static unsafe IntPtr DatabaseSetOptionDefault = NativeDelegate<DatabaseSetOption>.AsNativePointer(DatabaseSetOptionDefaultImpl);
#else
private static unsafe delegate* unmanaged<CAdbcDatabase*, byte*, byte*, CAdbcError*, AdbcStatusCode> DatabaseSetOptionDefault => &DatabaseSetOptionDefaultImpl;
[UnmanagedCallersOnly]
#endif
private static unsafe AdbcStatusCode DatabaseSetOptionDefaultImpl(CAdbcDatabase* database, byte* key, byte* value, CAdbcError* error)
{
return NotImplemented(error, nameof(CAdbcDriver.DatabaseSetOption));
}
#if !NET5_0_OR_GREATER
private static unsafe IntPtr ConnectionCommitDefault = NativeDelegate<ConnectionCommit>.AsNativePointer(ConnectionCommitDefaultImpl);
#else
private static unsafe delegate* unmanaged<CAdbcConnection*, CAdbcError*, AdbcStatusCode> ConnectionCommitDefault => &ConnectionCommitDefaultImpl;
[UnmanagedCallersOnly]
#endif
private static unsafe AdbcStatusCode ConnectionCommitDefaultImpl(CAdbcConnection* connection, CAdbcError* error)
{
return NotImplemented(error, nameof(CAdbcDriver.ConnectionCommit));
}
#if !NET5_0_OR_GREATER
private static unsafe IntPtr ConnectionGetInfoDefault = NativeDelegate<ConnectionGetInfo>.AsNativePointer(ConnectionGetInfoDefaultImpl);
#else
private static unsafe delegate* unmanaged<CAdbcConnection*, int*, int, CArrowArrayStream*, CAdbcError*, AdbcStatusCode> ConnectionGetInfoDefault => &ConnectionGetInfoDefaultImpl;
[UnmanagedCallersOnly]
#endif
private static unsafe AdbcStatusCode ConnectionGetInfoDefaultImpl(CAdbcConnection* connection, int* info_codes, int info_codes_length, CArrowArrayStream* stream, CAdbcError* error)
{
return NotImplemented(error, nameof(CAdbcDriver.ConnectionGetInfo));
}
#if !NET5_0_OR_GREATER
private static unsafe IntPtr ConnectionGetObjectsDefault = NativeDelegate<ConnectionGetObjects>.AsNativePointer(ConnectionGetObjectsDefaultImpl);
#else
private static unsafe delegate* unmanaged<CAdbcConnection*, int, byte*, byte*, byte*, byte**, byte*, CArrowArrayStream*, CAdbcError*, AdbcStatusCode> ConnectionGetObjectsDefault => &ConnectionGetObjectsDefaultImpl;
[UnmanagedCallersOnly]
#endif
private static unsafe AdbcStatusCode ConnectionGetObjectsDefaultImpl(CAdbcConnection* connection, int depth, byte* catalog, byte* db_schema, byte* table_name, byte** table_type, byte* column_name, CArrowArrayStream* stream, CAdbcError* error)
{
return NotImplemented(error, nameof(CAdbcDriver.ConnectionGetObjects));
}
#if !NET5_0_OR_GREATER
private static unsafe IntPtr ConnectionGetTableSchemaDefault = NativeDelegate<ConnectionGetTableSchema>.AsNativePointer(ConnectionGetTableSchemaDefaultImpl);
#else
private static unsafe delegate* unmanaged<CAdbcConnection*, byte*, byte*, byte*, CArrowSchema*, CAdbcError*, AdbcStatusCode> ConnectionGetTableSchemaDefault => &ConnectionGetTableSchemaDefaultImpl;
[UnmanagedCallersOnly]
#endif
private static unsafe AdbcStatusCode ConnectionGetTableSchemaDefaultImpl(CAdbcConnection* connection, byte* catalog, byte* db_schema, byte* table_name, CArrowSchema* schema, CAdbcError* error)
{
return NotImplemented(error, nameof(CAdbcDriver.ConnectionGetTableSchema));
}
#if !NET5_0_OR_GREATER
private static unsafe IntPtr ConnectionGetTableTypesDefault = NativeDelegate<ConnectionGetTableTypes>.AsNativePointer(ConnectionGetTableTypesDefaultImpl);
#else
private static unsafe delegate* unmanaged<CAdbcConnection*, CArrowArrayStream*, CAdbcError*, AdbcStatusCode> ConnectionGetTableTypesDefault => &ConnectionGetTableTypesDefaultImpl;
[UnmanagedCallersOnly]
#endif
private static unsafe AdbcStatusCode ConnectionGetTableTypesDefaultImpl(CAdbcConnection* connection, CArrowArrayStream* stream, CAdbcError* error)
{
return NotImplemented(error, nameof(CAdbcDriver.ConnectionGetTableTypes));
}
#if !NET5_0_OR_GREATER
private static unsafe IntPtr ConnectionReadPartitionDefault = NativeDelegate<ConnectionReadPartition>.AsNativePointer(ConnectionReadPartitionDefaultImpl);
#else
private static unsafe delegate* unmanaged<CAdbcConnection*, byte*, int, CArrowArrayStream*, CAdbcError*, AdbcStatusCode> ConnectionReadPartitionDefault => &ConnectionReadPartitionDefaultImpl;
[UnmanagedCallersOnly]
#endif
private static unsafe AdbcStatusCode ConnectionReadPartitionDefaultImpl(CAdbcConnection* connection, byte* serialized_partition, int serialized_length, CArrowArrayStream* stream, CAdbcError* error)
{
return NotImplemented(error, nameof(CAdbcDriver.ConnectionReadPartition));
}
#if !NET5_0_OR_GREATER
private static unsafe IntPtr ConnectionRollbackDefault = NativeDelegate<ConnectionRollback>.AsNativePointer(ConnectionRollbackDefaultImpl);
#else
private static unsafe delegate* unmanaged<CAdbcConnection*, CAdbcError*, AdbcStatusCode> ConnectionRollbackDefault => &ConnectionRollbackDefaultImpl;
[UnmanagedCallersOnly]
#endif
private static unsafe AdbcStatusCode ConnectionRollbackDefaultImpl(CAdbcConnection* connection, CAdbcError* error)
{
return NotImplemented(error, nameof(CAdbcDriver.ConnectionRollback));
}
#if !NET5_0_OR_GREATER
private static unsafe IntPtr ConnectionSetOptionDefault = NativeDelegate<ConnectionSetOption>.AsNativePointer(ConnectionSetOptionDefaultImpl);
#else
private static unsafe delegate* unmanaged<CAdbcConnection*, byte*, byte*, CAdbcError*, AdbcStatusCode> ConnectionSetOptionDefault => &ConnectionSetOptionDefaultImpl;
[UnmanagedCallersOnly]
#endif
private static unsafe AdbcStatusCode ConnectionSetOptionDefaultImpl(CAdbcConnection* connection, byte* name, byte* value, CAdbcError* error)
{
return NotImplemented(error, nameof(CAdbcDriver.ConnectionSetOption));
}
#if !NET5_0_OR_GREATER
private static unsafe IntPtr StatementExecutePartitionsDefault = NativeDelegate<StatementExecutePartitions>.AsNativePointer(StatementExecutePartitionsDefaultImpl);
#else
private static unsafe delegate* unmanaged<CAdbcStatement*, CArrowSchema*, CAdbcPartitions*, long*, CAdbcError*, AdbcStatusCode> StatementExecutePartitionsDefault => &StatementExecutePartitionsDefaultImpl;
[UnmanagedCallersOnly]
#endif
private static unsafe AdbcStatusCode StatementExecutePartitionsDefaultImpl(CAdbcStatement* statement, CArrowSchema* schema, CAdbcPartitions* partitions, long* rows, CAdbcError* error)
{
return NotImplemented(error, nameof(CAdbcDriver.StatementExecutePartitions));
}
#if !NET5_0_OR_GREATER
private static unsafe IntPtr StatementBindDefault = NativeDelegate<StatementBind>.AsNativePointer(StatementBindDefaultImpl);
#else
private static unsafe delegate* unmanaged<CAdbcStatement*, CArrowArray*, CArrowSchema*, CAdbcError*, AdbcStatusCode> StatementBindDefault => &StatementBindDefaultImpl;
[UnmanagedCallersOnly]
#endif
private static unsafe AdbcStatusCode StatementBindDefaultImpl(CAdbcStatement* statement, CArrowArray* array, CArrowSchema* schema, CAdbcError* error)
{
return NotImplemented(error, nameof(CAdbcDriver.StatementBind));
}
#if !NET5_0_OR_GREATER
private static unsafe IntPtr StatementBindStreamDefault = NativeDelegate<StatementBindStream>.AsNativePointer(StatementBindStreamDefaultImpl);
#else
private static unsafe delegate* unmanaged<CAdbcStatement*, CArrowArrayStream*, CAdbcError*, AdbcStatusCode> StatementBindStreamDefault => &StatementBindStreamDefaultImpl;
[UnmanagedCallersOnly]
#endif
private static unsafe AdbcStatusCode StatementBindStreamDefaultImpl(CAdbcStatement* statement, CArrowArrayStream* stream, CAdbcError* error)
{
return NotImplemented(error, nameof(CAdbcDriver.StatementBindStream));
}
#if !NET5_0_OR_GREATER
private static unsafe IntPtr StatementGetParameterSchemaDefault = NativeDelegate<StatementGetParameterSchema>.AsNativePointer(StatementGetParameterSchemaDefaultImpl);
#else
private static unsafe delegate* unmanaged<CAdbcStatement*, CArrowSchema*, CAdbcError*, AdbcStatusCode> StatementGetParameterSchemaDefault => &StatementGetParameterSchemaDefaultImpl;
[UnmanagedCallersOnly]
#endif
private static unsafe AdbcStatusCode StatementGetParameterSchemaDefaultImpl(CAdbcStatement* statement, CArrowSchema* schema, CAdbcError* error)
{
return NotImplemented(error, nameof(CAdbcDriver.StatementGetParameterSchema));
}
#if !NET5_0_OR_GREATER
private static unsafe IntPtr StatementPrepareDefault = NativeDelegate<StatementPrepare>.AsNativePointer(StatementPrepareDefaultImpl);
#else
private static unsafe delegate* unmanaged<CAdbcStatement*, CAdbcError*, AdbcStatusCode> StatementPrepareDefault => &StatementPrepareDefaultImpl;
[UnmanagedCallersOnly]
#endif
private static unsafe AdbcStatusCode StatementPrepareDefaultImpl(CAdbcStatement* statement, CAdbcError* error)
{
return NotImplemented(error, nameof(CAdbcDriver.StatementPrepare));
}
#if !NET5_0_OR_GREATER
private static unsafe IntPtr StatementSetOptionDefault = NativeDelegate<StatementSetOption>.AsNativePointer(StatementSetOptionDefaultImpl);
#else
private static unsafe delegate* unmanaged<CAdbcStatement*, byte*, byte*, CAdbcError*, AdbcStatusCode> StatementSetOptionDefault => &StatementSetOptionDefaultImpl;
[UnmanagedCallersOnly]
#endif
private static unsafe AdbcStatusCode StatementSetOptionDefaultImpl(CAdbcStatement* statement, byte* name, byte* value, CAdbcError* error)
{
return NotImplemented(error, nameof(CAdbcDriver.StatementSetOption));
}
#if !NET5_0_OR_GREATER
private static unsafe IntPtr StatementSetSqlQueryDefault = NativeDelegate<StatementSetSqlQuery>.AsNativePointer(StatementSetSqlQueryDefaultImpl);
#else
private static unsafe delegate* unmanaged<CAdbcStatement*, byte*, CAdbcError*, AdbcStatusCode> StatementSetSqlQueryDefault => &StatementSetSqlQueryDefaultImpl;
[UnmanagedCallersOnly]
#endif
private static unsafe AdbcStatusCode StatementSetSqlQueryDefaultImpl(CAdbcStatement* statement, byte* text, CAdbcError* error)
{
return NotImplemented(error, nameof(CAdbcDriver.StatementSetSqlQuery));
}
#if !NET5_0_OR_GREATER
private static unsafe IntPtr StatementSetSubstraitPlanDefault = NativeDelegate<StatementSetSubstraitPlan>.AsNativePointer(StatementSetSubstraitPlanDefaultImpl);
#else
private static unsafe delegate* unmanaged<CAdbcStatement*, byte*, int, CAdbcError*, AdbcStatusCode> StatementSetSubstraitPlanDefault => &StatementSetSubstraitPlanDefaultImpl;
[UnmanagedCallersOnly]
#endif
private static unsafe AdbcStatusCode StatementSetSubstraitPlanDefaultImpl(CAdbcStatement* statement, byte* plan, int length, CAdbcError* error)
{
return NotImplemented(error, nameof(CAdbcDriver.StatementSetSubstraitPlan));
}
private static unsafe AdbcStatusCode NotImplemented(CAdbcError* error, string name)
{
if (error != null)
{
CAdbcDriverExporter.ReleaseError(error);
error->message = (byte*)MarshalExtensions.StringToCoTaskMemUTF8($"Adbc{name} not implemented");
error->sqlstate0 = (byte)0;
error->sqlstate1 = (byte)0;
error->sqlstate2 = (byte)0;
error->sqlstate3 = (byte)0;
error->sqlstate4 = (byte)0;
error->vendor_code = 0;
error->release = CAdbcDriverExporter.ReleaseErrorPtr;
}
return AdbcStatusCode.NotImplemented;
}
/// <summary>
/// Native implementation of <see cref="AdbcDriver"/>
/// </summary>
internal sealed class ImportedAdbcDriver : AdbcDriver
{
private IntPtr _library;
private CAdbcDriver _nativeDriver;
private int _references;
private bool _disposed;
public ImportedAdbcDriver(IntPtr library, CAdbcDriver nativeDriver)
{
_library = library;
_nativeDriver = nativeDriver;
_references = 1;
}
~ImportedAdbcDriver()
{
Dispose(false);
}
internal unsafe ref CAdbcDriver Driver
{
get
{
if (_disposed) { throw new ObjectDisposedException(nameof(ImportedAdbcDriver)); }
return ref _nativeDriver;
}
}
internal ImportedAdbcDriver AddReference()
{
Interlocked.Increment(ref _references);
return this;
}
internal unsafe void RemoveReference()
{
if (Interlocked.Decrement(ref _references) == 0)
{
if (_nativeDriver.release != default)
{
using (CallHelper caller = new CallHelper())
{
caller.Call(_nativeDriver.release, ref _nativeDriver);
}
NativeLibrary.Free(_library);
_library = IntPtr.Zero;
}
}
}
/// <summary>
/// Opens a database
/// </summary>
/// <param name="parameters">
/// Parameters to use when calling DatabaseNew.
/// </param>
public unsafe override AdbcDatabase Open(IReadOnlyDictionary<string, string> parameters)
{
if (parameters == null) throw new ArgumentNullException(nameof(parameters));
CAdbcDatabase nativeDatabase = new CAdbcDatabase();
ImportedAdbcDatabase? result = null;
try
{
using (CallHelper caller = new CallHelper())
{
caller.Call(Driver.DatabaseNew, ref nativeDatabase);
foreach (KeyValuePair<string, string> pair in parameters)
{
caller.Call(Driver.DatabaseSetOption, ref nativeDatabase, pair.Key, pair.Value);
}
caller.Call(Driver.DatabaseInit, ref nativeDatabase);
}
result = new ImportedAdbcDatabase(this, nativeDatabase);
}
finally
{
if (result == null && nativeDatabase.private_data != null)
{
using (CallHelper caller = new CallHelper())
{
caller.Call(Driver.DatabaseRelease, ref nativeDatabase);
}
}
}
return result;
}
public unsafe override void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (!_disposed)
{
_disposed = true;
RemoveReference();
if (disposing)
{
base.Dispose();
}
}
}
}
/// <summary>
/// A native implementation of <see cref="AdbcDatabase"/>
/// </summary>
internal sealed class ImportedAdbcDatabase : AdbcDatabase
{
private readonly ImportedAdbcDriver _driver;
private CAdbcDatabase _nativeDatabase;
private bool _disposed;
internal ImportedAdbcDatabase(ImportedAdbcDriver driver, CAdbcDatabase nativeDatabase)
{
_driver = driver.AddReference();
_nativeDatabase = nativeDatabase;
}
~ImportedAdbcDatabase()
{
Dispose(false);
}
private unsafe ref CAdbcDriver Driver
{
get
{
if (_disposed) { throw new ObjectDisposedException(nameof(ImportedAdbcDatabase)); }
return ref _driver.Driver;
}
}
public unsafe override AdbcConnection Connect(IReadOnlyDictionary<string, string>? options)
{
CAdbcConnection nativeConnection = new CAdbcConnection();
ImportedAdbcConnection? result = null;
try
{
using (CallHelper caller = new CallHelper())
{
caller.Call(Driver.ConnectionNew, ref nativeConnection);
if (options != null)
{
foreach (KeyValuePair<string, string> pair in options)
{
caller.Call(Driver.ConnectionSetOption, ref nativeConnection, pair.Key, pair.Value);
}
}
caller.Call(Driver.ConnectionInit, ref nativeConnection, ref _nativeDatabase);
result = new ImportedAdbcConnection(_driver, nativeConnection);
}
}
finally
{
if (result == null && nativeConnection.private_data != null)
{
using (CallHelper caller = new CallHelper())
{
caller.Call(Driver.ConnectionRelease, ref nativeConnection);
}
}
}
return result;
}
public unsafe override void SetOption(string key, string value)
{
using (CallHelper caller = new CallHelper())
{
caller.Call(Driver.DatabaseSetOption, ref _nativeDatabase, key, value);
}
}
public override void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private unsafe void Dispose(bool disposing)
{
if (!_disposed)
{
_disposed = true;
try
{
if (_nativeDatabase.private_data != default)
{
using (CallHelper caller = new CallHelper())
{
caller.Call(_driver.Driver.DatabaseRelease, ref _nativeDatabase);
}
}
}
finally
{
_nativeDatabase.private_data = default;
_driver.RemoveReference();
}
if (disposing)
{
base.Dispose();
}
}
}
}
/// <summary>
/// A native implementation of <see cref="AdbcConnection"/>
/// </summary>
internal sealed class ImportedAdbcConnection : AdbcConnection
{
private readonly ImportedAdbcDriver _driver;
private CAdbcConnection _nativeConnection;
private bool _disposed;
private bool? _autoCommit;
private IsolationLevel? _isolationLevel;
private bool? _readOnly;
internal ImportedAdbcConnection(ImportedAdbcDriver driver, CAdbcConnection nativeConnection)
{
_driver = driver.AddReference();
_nativeConnection = nativeConnection;
}
~ImportedAdbcConnection()
{
Dispose(false);
}
private unsafe ref CAdbcDriver Driver
{
get
{
if (_disposed) { throw new ObjectDisposedException(nameof(ImportedAdbcConnection)); }
return ref _driver.Driver;
}
}
public override bool AutoCommit
{
get => _autoCommit ?? throw AdbcException.NotImplemented("no value has been set for AutoCommit");
set
{
SetOption(AdbcOptions.Autocommit, AdbcOptions.GetEnabled(value));
_autoCommit = value;
}
}
public override IsolationLevel IsolationLevel
{
get => _isolationLevel ?? IsolationLevel.Default;
set
{
SetOption(AdbcOptions.IsolationLevel, AdbcOptions.GetIsolationLevel(value));
_isolationLevel = value;
}
}
public override bool ReadOnly
{
get => _readOnly ?? throw AdbcException.NotImplemented("no value has been set for ReadOnly");
set
{
SetOption(AdbcOptions.ReadOnly, AdbcOptions.GetEnabled(value));
_readOnly = value;
}
}
public unsafe override AdbcStatement CreateStatement()
{
CAdbcStatement nativeStatement = new CAdbcStatement();
ImportedAdbcStatement? result = null;
try
{
using (CallHelper caller = new CallHelper())
{
fixed (CAdbcConnection* connection = &_nativeConnection)
{
caller.TranslateCode(
#if NET5_0_OR_GREATER
Driver.StatementNew
#else
Marshal.GetDelegateForFunctionPointer<StatementNew>(Driver.StatementNew)
#endif
(connection, &nativeStatement, &caller._error));
}
result = new ImportedAdbcStatement(_driver, nativeStatement);
}
}
finally
{
if (result == null && nativeStatement.private_data != null)
{
using (CallHelper caller = new CallHelper())
{
caller.Call(Driver.StatementRelease, ref nativeStatement);
}
}
}
return result;
}
public unsafe override IArrowArrayStream GetInfo(IReadOnlyList<AdbcInfoCode> codes)
{
using (CallHelper caller = new CallHelper())
{
Span<AdbcInfoCode> span = codes.AsSpan();
fixed (CAdbcConnection* connection = &_nativeConnection)
fixed (AdbcInfoCode* spanPtr = span)
{
caller.TranslateCode(
#if NET5_0_OR_GREATER
Driver.ConnectionGetInfo
#else
Marshal.GetDelegateForFunctionPointer<ConnectionGetInfo>(Driver.ConnectionGetInfo)
#endif
(connection, (int*)spanPtr, codes.Count, caller.CreateStream(), &caller._error));
return caller.ImportStream();
}
}
}
public unsafe override IArrowArrayStream GetObjects(GetObjectsDepth depth, string? catalogPattern, string? dbSchemaPattern, string? tableNamePattern, IReadOnlyList<string>? tableTypes, string? columnNamePattern)
{
tableTypes = tableTypes ?? [];
byte** utf8TableTypes = null;
try
{
// need to terminate with a null entry per https://github.com/apache/arrow-adbc/blob/b97e22c4d6524b60bf261e1970155500645be510/adbc.h#L909-L911
utf8TableTypes = (byte**)Marshal.AllocHGlobal(IntPtr.Size * (tableTypes.Count + 1));
utf8TableTypes[tableTypes.Count] = null;
for (int i = 0; i < tableTypes.Count; i++)
{
string tableType = tableTypes[i];
utf8TableTypes[i] = (byte*)MarshalExtensions.StringToCoTaskMemUTF8(tableType);
}
using (Utf8Helper utf8Catalog = new Utf8Helper(catalogPattern))
using (Utf8Helper utf8Schema = new Utf8Helper(dbSchemaPattern))
using (Utf8Helper utf8Table = new Utf8Helper(tableNamePattern))
using (Utf8Helper utf8Column = new Utf8Helper(columnNamePattern))
using (CallHelper caller = new CallHelper())
{
fixed (CAdbcConnection* connection = &_nativeConnection)
{
caller.TranslateCode(
#if NET5_0_OR_GREATER
Driver.ConnectionGetObjects
#else
Marshal.GetDelegateForFunctionPointer<ConnectionGetObjects>(Driver.ConnectionGetObjects)
#endif
(connection, (int)depth, utf8Catalog, utf8Schema, utf8Table, utf8TableTypes, utf8Column, caller.CreateStream(), &caller._error));
return caller.ImportStream();
}
}
}
finally
{
for (int i = 0; i < tableTypes.Count; i++)
{
Marshal.FreeCoTaskMem((IntPtr)utf8TableTypes[i]);
}
Marshal.FreeHGlobal((IntPtr)utf8TableTypes);
}
}
public unsafe override IArrowArrayStream GetTableTypes()
{
using (CallHelper caller = new CallHelper())
{
fixed (CAdbcConnection* connection = &_nativeConnection)
{
caller.TranslateCode(
#if NET5_0_OR_GREATER
Driver.ConnectionGetTableTypes
#else
Marshal.GetDelegateForFunctionPointer<ConnectionGetTableTypes>(Driver.ConnectionGetTableTypes)
#endif
(connection, caller.CreateStream(), &caller._error));
return caller.ImportStream();
}
}
}
public unsafe override Schema GetTableSchema(string? catalog, string? db_schema, string table_name)
{
using (Utf8Helper utf8Catalog = new Utf8Helper(catalog))
using (Utf8Helper utf8Schema = new Utf8Helper(db_schema))
using (Utf8Helper utf8Table = new Utf8Helper(table_name))
using (CallHelper caller = new CallHelper())
{
fixed (CAdbcConnection* connection = &_nativeConnection)
{
caller.TranslateCode(
#if NET5_0_OR_GREATER
Driver.ConnectionGetTableSchema
#else
Marshal.GetDelegateForFunctionPointer<ConnectionGetTableSchema>(Driver.ConnectionGetTableSchema)
#endif
(connection, utf8Catalog, utf8Schema, utf8Table, caller.CreateSchema(), &caller._error));
return caller.ImportSchema();
}
}
}
public override AdbcStatement BulkIngest(string targetTableName, BulkIngestMode mode)
{
AdbcStatement statement = CreateStatement();
bool succeeded = false;
try
{
statement.SetOption(AdbcOptions.Ingest.TargetTable, targetTableName);
statement.SetOption(AdbcOptions.Ingest.Mode, AdbcOptions.GetIngestMode(mode));
succeeded = true;
return statement;
}
finally
{
if (!succeeded) { statement.Dispose(); }
}
}
public unsafe override void Commit()
{
using (CallHelper caller = new CallHelper())
{
caller.Call(Driver.ConnectionCommit, ref _nativeConnection);
}
}
public unsafe override void Rollback()
{
using (CallHelper caller = new CallHelper())
{
caller.Call(Driver.ConnectionRollback, ref _nativeConnection);
}
}
public unsafe override void SetOption(string key, string value)
{
using (CallHelper caller = new CallHelper())
{
caller.Call(Driver.ConnectionSetOption, ref _nativeConnection, key, value);
}
}
public override void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private unsafe void Dispose(bool disposing)
{
if (!_disposed)
{
_disposed = true;
try
{
if (_nativeConnection.private_data != default)
{
using (CallHelper caller = new CallHelper())
{
caller.Call(_driver.Driver.ConnectionRelease, ref _nativeConnection);
}
}
}
finally
{
_nativeConnection.private_data = default;
_driver.RemoveReference();
}
if (disposing)
{
base.Dispose();
}
}
}
}
/// <summary>
/// A native implementation of <see cref="AdbcStatement"/>
/// </summary>
internal sealed class ImportedAdbcStatement : AdbcStatement
{
private ImportedAdbcDriver _driver;
private CAdbcStatement _nativeStatement;
private byte[]? _substraitPlan;
private bool _disposed;
internal ImportedAdbcStatement(ImportedAdbcDriver driver, CAdbcStatement nativeStatement)
{
_driver = driver.AddReference();
_nativeStatement = nativeStatement;
}
~ImportedAdbcStatement()
{
Dispose(false);
}
private unsafe ref CAdbcDriver Driver
{
get
{
if (_disposed) { throw new ObjectDisposedException(nameof(ImportedAdbcStatement)); }
return ref _driver.Driver;
}
}
public unsafe override byte[]? SubstraitPlan
{
get => _substraitPlan;
set
{
using (CallHelper caller = new CallHelper())
{
fixed (CAdbcStatement* statement = &_nativeStatement)
fixed (byte* substraitPlan = value)
{
caller.TranslateCode(
#if NET5_0_OR_GREATER
Driver.StatementSetSubstraitPlan
#else
Marshal.GetDelegateForFunctionPointer<StatementSetSubstraitPlan>(Driver.StatementSetSubstraitPlan)
#endif
(statement, substraitPlan, value?.Length ?? 0, &caller._error));
}
_substraitPlan = value;
}
}
}
public unsafe override void Bind(RecordBatch batch, Schema schema)
{
using (CallHelper caller = new CallHelper())
{
fixed (CAdbcStatement* statement = &_nativeStatement)
{
CArrowArrayExporter.ExportRecordBatch(batch, caller.CreateArray());
CArrowSchemaExporter.ExportSchema(schema, caller.CreateSchema());
caller.TranslateCode(
#if NET5_0_OR_GREATER
Driver.StatementBind
#else
Marshal.GetDelegateForFunctionPointer<StatementBind>(Driver.StatementBind)
#endif
(statement, caller.Array, caller.Schema, &caller._error));
// On success, ownership passes to the driver
caller.ForgetArray();
caller.ForgetSchema();
}
}
}
public unsafe override void BindStream(IArrowArrayStream stream)
{
using (CallHelper caller = new CallHelper())
{
fixed (CAdbcStatement* statement = &_nativeStatement)
{
CArrowArrayStreamExporter.ExportArrayStream(stream, caller.CreateStream());
caller.TranslateCode(
#if NET5_0_OR_GREATER
Driver.StatementBindStream
#else
Marshal.GetDelegateForFunctionPointer<StatementBindStream>(Driver.StatementBindStream)
#endif
(statement, caller.ArrayStream, &caller._error));
// On success, ownership passes to the driver
caller.ForgetStream();
}
}
}
public unsafe override QueryResult ExecuteQuery()
{
if (SqlQuery != null)
{
// TODO: Consider moving this to the setter
SetSqlQuery(SqlQuery);
}
using (CallHelper caller = new CallHelper())
{
fixed (CAdbcStatement* statement = &_nativeStatement)
{
long rows = 0;
caller.TranslateCode(
#if NET5_0_OR_GREATER
Driver.StatementExecuteQuery
#else
Marshal.GetDelegateForFunctionPointer<StatementExecuteQuery>(Driver.StatementExecuteQuery)
#endif
(statement, caller.CreateStream(), &rows, &caller._error));
return new QueryResult(rows, caller.ImportStream());
}
}
}
public unsafe override UpdateResult ExecuteUpdate()
{
if (SqlQuery != null)
{
// TODO: Consider moving this to the setter
SetSqlQuery(SqlQuery);
}
using (CallHelper caller = new CallHelper())
{
fixed (CAdbcStatement* statement = &_nativeStatement)
{
long rows = 0;
caller.TranslateCode(
#if NET5_0_OR_GREATER
Driver.StatementExecuteQuery
#else
Marshal.GetDelegateForFunctionPointer<StatementExecuteQuery>(Driver.StatementExecuteQuery)
#endif
(statement, null, &rows, &caller._error));
return new UpdateResult(rows);
}
}
}
public unsafe override PartitionedResult ExecutePartitioned()
{
if (SqlQuery != null)
{
// TODO: Consider moving this to the setter
SetSqlQuery(SqlQuery);
}
using (CallHelper caller = new CallHelper())
{
fixed (CAdbcStatement* statement = &_nativeStatement)
{
CAdbcPartitions* nativePartitions = null;
long rowsAffected = 0;
try
{
nativePartitions = (CAdbcPartitions*)Marshal.AllocHGlobal(sizeof(CAdbcPartitions));
caller.TranslateCode(
#if NET5_0_OR_GREATER
Driver.StatementExecutePartitions
#else
Marshal.GetDelegateForFunctionPointer<StatementExecutePartitions>(Driver.StatementExecutePartitions)
#endif
(statement, caller.CreateSchema(), nativePartitions, &rowsAffected, &caller._error));
PartitionDescriptor[] partitions = new PartitionDescriptor[nativePartitions->num_partitions];
for (int i = 0; i < partitions.Length; i++)
{
partitions[i] = new PartitionDescriptor(MarshalExtensions.MarshalBuffer(nativePartitions->partitions[i], checked((int)nativePartitions->partition_lengths[i]))!);
}
return new PartitionedResult(caller.ImportSchema(), rowsAffected, partitions);
}
finally
{
if (nativePartitions->release != null)
{
#if NET5_0_OR_GREATER
nativePartitions->release(nativePartitions);
#else
Marshal.GetDelegateForFunctionPointer<PartitionsRelease>(nativePartitions->release)(nativePartitions);
#endif
}
if (nativePartitions != null)
{
Marshal.FreeHGlobal((IntPtr)nativePartitions);
}
}
}
}
}
public unsafe override void SetOption(string key, string value)
{
using (CallHelper caller = new CallHelper())
{
caller.Call(Driver.StatementSetOption, ref _nativeStatement, key, value);
}
}
public override void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private unsafe void Dispose(bool disposing)
{
if (!_disposed)
{
_disposed = true;
try
{
if (_nativeStatement.private_data != default)
{
using (CallHelper caller = new CallHelper())
{
caller.Call(_driver.Driver.StatementRelease, ref _nativeStatement);
}
}
}
finally
{
_nativeStatement.private_data = default;
_driver.RemoveReference();
}
if (disposing)
{
base.Dispose();
}
}
}
private unsafe void SetSqlQuery(string sqlQuery)
{
fixed (CAdbcStatement* statement = &_nativeStatement)
{
using (Utf8Helper query = new Utf8Helper(sqlQuery))
using (CallHelper caller = new CallHelper())
{
caller.TranslateCode(
#if NET5_0_OR_GREATER
_driver.Driver.StatementSetSqlQuery
#else
Marshal.GetDelegateForFunctionPointer<StatementSetSqlQuery>(_driver.Driver.StatementSetSqlQuery)
#endif
(statement, query, &caller._error));
}
}
}
}
internal class ImportedAdbcException : AdbcException
{
private readonly string? _sqlState;
private readonly int _nativeError;
public unsafe ImportedAdbcException(ref CAdbcError error, AdbcStatusCode statusCode)
: base(MarshalExtensions.PtrToStringUTF8(error.message) ?? "Undefined error", statusCode)
{
if (error.sqlstate0 != 0)
{
fixed (CAdbcError* fixedError = &error)
{
_sqlState = Encoding.ASCII.GetString(&fixedError->sqlstate0, 5);
}
_nativeError = error.vendor_code;
}
}
public override string? SqlState => _sqlState;
public override int NativeError => _nativeError;
}
/// <summary>
/// Assists with UTF8/string marshalling
/// </summary>
private struct Utf8Helper : IDisposable
{
private IntPtr _s;
public Utf8Helper(string? s)
{
if (s != null) _s = MarshalExtensions.StringToCoTaskMemUTF8(s);
}
public static implicit operator IntPtr(Utf8Helper s) { return s._s; }
public static unsafe implicit operator byte*(Utf8Helper s) { return (byte*)s._s; }
public void Dispose()
{
if (_s != IntPtr.Zero)
{
Marshal.FreeCoTaskMem(_s);
_s = IntPtr.Zero;
}
}
}
/// <summary>
/// Assists with delegate calls and handling error codes
/// </summary>
private unsafe struct CallHelper : IDisposable
{
public CAdbcError _error;
private CArrowSchema* _schema;
private CArrowArray* _array;
private CArrowArrayStream* _arrayStream;
public CArrowSchema* Schema => _schema;
public CArrowArray* Array => _array;
public CArrowArrayStream* ArrayStream => _arrayStream;
public CArrowSchema* CreateSchema()
{
Debug.Assert(_schema == null);
_schema = CArrowSchema.Create();
return _schema;
}
public void ForgetSchema()
{
Debug.Assert(_schema != null);
_schema = null;
}
public Schema ImportSchema()
{
Debug.Assert(_schema != null);
Schema schema = CArrowSchemaImporter.ImportSchema(_schema);
// ImportSchema makes a copy so we need to free the original
CArrowSchema.Free(_schema);
_schema = null;
return schema;
}
public CArrowArray* CreateArray()
{
Debug.Assert(_array == null);
_array = CArrowArray.Create();
return _array;
}
public void ForgetArray()
{
Debug.Assert(_array != null);
_array = null;
}
public CArrowArrayStream* CreateStream()
{
Debug.Assert(_arrayStream == null);
_arrayStream = CArrowArrayStream.Create();
return _arrayStream;
}
public void ForgetStream()
{
Debug.Assert(_arrayStream != null);
_arrayStream = null;
}
public IArrowArrayStream ImportStream()
{
Debug.Assert(_arrayStream != null);
IArrowArrayStream arrayStream = CArrowArrayStreamImporter.ImportArrayStream(_arrayStream);
_arrayStream = null;
return arrayStream;
}
public unsafe void Call(AdbcDriverInit init, int version, ref CAdbcDriver driver)
{
TranslateCode(init(version, ref driver, ref this._error));
}
#if NET5_0_OR_GREATER
public unsafe void Call(delegate* unmanaged<CAdbcDriver*, CAdbcError*, AdbcStatusCode> fn, ref CAdbcDriver nativeDriver)
{
fixed (CAdbcDriver* driver = &nativeDriver)
fixed (CAdbcError* e = &_error)
{
TranslateCode(fn(driver, e));
}
}
#else
public unsafe void Call(IntPtr fn, ref CAdbcDriver nativeDriver)
{
fixed (CAdbcDriver* driver = &nativeDriver)
fixed (CAdbcError* e = &_error)
{
TranslateCode(Marshal.GetDelegateForFunctionPointer<DriverRelease>(fn)(driver, e));
}
}
#endif
#if NET5_0_OR_GREATER
public unsafe void Call(delegate* unmanaged<CAdbcDatabase*, CAdbcError*, AdbcStatusCode> fn, ref CAdbcDatabase nativeDatabase)
{
fixed (CAdbcDatabase* db = &nativeDatabase)
fixed (CAdbcError* e = &_error)
{
TranslateCode(fn(db, e));
}
}
#else
public unsafe void Call(IntPtr fn, ref CAdbcDatabase nativeDatabase)
{
fixed (CAdbcDatabase* db = &nativeDatabase)
fixed (CAdbcError* e = &_error)
{
TranslateCode(Marshal.GetDelegateForFunctionPointer<DatabaseFn>(fn)(db, e));
}
}
#endif
#if NET5_0_OR_GREATER
public unsafe void Call(delegate* unmanaged<CAdbcDatabase*, byte*, byte*, CAdbcError*, AdbcStatusCode> fn, ref CAdbcDatabase nativeDatabase, string key, string? value)
{
fixed (CAdbcDatabase* db = &nativeDatabase)
fixed (CAdbcError* e = &_error)
{
using (Utf8Helper utf8Key = new Utf8Helper(key))
using (Utf8Helper utf8Value = new Utf8Helper(value))
{
TranslateCode(fn(db, utf8Key, utf8Value, e));
}
}
}
#else
public unsafe void Call(IntPtr fn, ref CAdbcDatabase nativeDatabase, string key, string? value)
{
fixed (CAdbcDatabase* db = &nativeDatabase)
fixed (CAdbcError* e = &_error)
{
using (Utf8Helper utf8Key = new Utf8Helper(key))
using (Utf8Helper utf8Value = new Utf8Helper(value))
{
TranslateCode(Marshal.GetDelegateForFunctionPointer<DatabaseSetOption>(fn)(db, utf8Key, utf8Value, e));
}
}
}
#endif
#if NET5_0_OR_GREATER
public unsafe void Call(delegate* unmanaged<CAdbcConnection*, CAdbcError*, AdbcStatusCode> fn, ref CAdbcConnection nativeConnection)
{
fixed (CAdbcConnection* cn = &nativeConnection)
fixed (CAdbcError* e = &_error)
{
TranslateCode(fn(cn, e));
}
}
#else
public unsafe void Call(IntPtr fn, ref CAdbcConnection nativeConnection)
{
fixed (CAdbcConnection* cn = &nativeConnection)
fixed (CAdbcError* e = &_error)
{
TranslateCode(Marshal.GetDelegateForFunctionPointer<ConnectionFn>(fn)(cn, e));
}
}
#endif
#if NET5_0_OR_GREATER
public unsafe void Call(delegate* unmanaged<CAdbcStatement*, CAdbcError*, AdbcStatusCode> fn, ref CAdbcStatement nativeStatement)
{
fixed (CAdbcStatement* stmt = &nativeStatement)
fixed (CAdbcError* e = &_error)
{
TranslateCode(fn(stmt, e));
}
}
#else
public unsafe void Call(IntPtr fn, ref CAdbcStatement nativeStatement)
{
fixed (CAdbcStatement* stmt = &nativeStatement)
fixed (CAdbcError* e = &_error)
{
TranslateCode(Marshal.GetDelegateForFunctionPointer<StatementPrepare>(fn)(stmt, e));
}
}
#endif
#if NET5_0_OR_GREATER
public unsafe void Call(delegate* unmanaged<CAdbcConnection*, byte*, byte*, CAdbcError*, AdbcStatusCode> fn, ref CAdbcConnection nativeConnection, string key, string? value)
{
fixed (CAdbcConnection* cn = &nativeConnection)
fixed (CAdbcError* e = &_error)
{
using (Utf8Helper utf8Key = new Utf8Helper(key))
using (Utf8Helper utf8Value = new Utf8Helper(value))
{
TranslateCode(fn(cn, utf8Key, utf8Value, e));
}
}
}
#else
public unsafe void Call(IntPtr fn, ref CAdbcConnection nativeConnection, string key, string? value)
{
fixed (CAdbcConnection* cn = &nativeConnection)
fixed (CAdbcError* e = &_error)
{
using (Utf8Helper utf8Key = new Utf8Helper(key))
using (Utf8Helper utf8Value = new Utf8Helper(value))
{
TranslateCode(Marshal.GetDelegateForFunctionPointer<ConnectionSetOption>(fn)(cn, utf8Key, utf8Value, e));
}
}
}
#endif
#if NET5_0_OR_GREATER
public unsafe void Call(delegate* unmanaged<CAdbcConnection*, CAdbcDatabase*, CAdbcError*, AdbcStatusCode> fn, ref CAdbcConnection nativeConnection, ref CAdbcDatabase database)
{
fixed (CAdbcConnection* cn = &nativeConnection)
fixed (CAdbcDatabase* db = &database)
fixed (CAdbcError* e = &_error)
{
TranslateCode(fn(cn, db, e));
}
}
#else
public unsafe void Call(IntPtr fn, ref CAdbcConnection nativeConnection, ref CAdbcDatabase database)
{
fixed (CAdbcConnection* cn = &nativeConnection)
fixed (CAdbcDatabase* db = &database)
fixed (CAdbcError* e = &_error)
{
TranslateCode(Marshal.GetDelegateForFunctionPointer<ConnectionInit>(fn)(cn, db, e));
}
}
#endif
#if NET5_0_OR_GREATER
public unsafe void Call(delegate* unmanaged<CAdbcStatement*, byte*, byte*, CAdbcError*, AdbcStatusCode> fn, ref CAdbcStatement nativeStatement, string key, string? value)
{
fixed (CAdbcStatement* stmt = &nativeStatement)
fixed (CAdbcError* e = &_error)
{
using (Utf8Helper utf8Key = new Utf8Helper(key))
using (Utf8Helper utf8Value = new Utf8Helper(value))
{
TranslateCode(fn(stmt, utf8Key, utf8Value, e));
}
}
}
#else
public unsafe void Call(IntPtr fn, ref CAdbcStatement nativeStatement, string key, string? value)
{
fixed (CAdbcStatement* stmt = &nativeStatement)
fixed (CAdbcError* e = &_error)
{
using (Utf8Helper utf8Key = new Utf8Helper(key))
using (Utf8Helper utf8Value = new Utf8Helper(value))
{
TranslateCode(Marshal.GetDelegateForFunctionPointer<StatementSetOption>(fn)(stmt, utf8Key, utf8Value, e));
}
}
}
#endif
public unsafe void Dispose()
{
if (_error.release != default)
{
fixed (CAdbcError* err = &_error)
{
#if NET5_0_OR_GREATER
_error.release(err);
#else
Marshal.GetDelegateForFunctionPointer<ErrorRelease>(err->release)(err);
#endif
_error.release = default;
}
}
if (_array != null)
{
CArrowArray.Free(_array);
_array = null;
}
if (_schema != null)
{
CArrowSchema.Free(_schema);
_schema = null;
}
if (_arrayStream != null)
{
CArrowArrayStream.Free(_arrayStream);
_arrayStream = null;
}
}
public unsafe void TranslateCode(AdbcStatusCode statusCode)
{
if (statusCode != AdbcStatusCode.Success)
{
ImportedAdbcException exception = new ImportedAdbcException(ref _error, statusCode);
Dispose();
throw exception;
}
}
}
}
}