diff --git a/csharp/src/Apache.Arrow.Adbc/AdbcException.cs b/csharp/src/Apache.Arrow.Adbc/AdbcException.cs index 98faefa6c8..c4deada39a 100644 --- a/csharp/src/Apache.Arrow.Adbc/AdbcException.cs +++ b/csharp/src/Apache.Arrow.Adbc/AdbcException.cs @@ -56,6 +56,11 @@ public static AdbcException NotImplemented(string message) return new AdbcException(message, AdbcStatusCode.NotImplemented); } + internal static AdbcException Missing(string name) + { + return new AdbcException($"Driver does not implement required function Adbc{name}", AdbcStatusCode.InternalError); + } + /// /// For database providers which support it, contains a standard /// SQL 5-character return code indicating the success or failure diff --git a/csharp/src/Apache.Arrow.Adbc/C/CAdbcDriverExporter.cs b/csharp/src/Apache.Arrow.Adbc/C/CAdbcDriverExporter.cs index 7352e34807..2b77315f2b 100644 --- a/csharp/src/Apache.Arrow.Adbc/C/CAdbcDriverExporter.cs +++ b/csharp/src/Apache.Arrow.Adbc/C/CAdbcDriverExporter.cs @@ -30,7 +30,7 @@ public class CAdbcDriverExporter private static unsafe readonly NativeDelegate s_releaseError = new NativeDelegate(ReleaseError); #if NET5_0_OR_GREATER - private static unsafe delegate* unmanaged ReleaseErrorPtr => (delegate* unmanaged)s_releaseError.Pointer; + internal static unsafe delegate* unmanaged ReleaseErrorPtr => (delegate* unmanaged)s_releaseError.Pointer; private static unsafe delegate* unmanaged ReleaseDriverPtr => &ReleaseDriver; private static unsafe delegate* unmanaged ReleasePartitionsPtr => &ReleasePartitions; @@ -60,7 +60,7 @@ public class CAdbcDriverExporter private static unsafe delegate* unmanaged StatementSetSubstraitPlanPtr => &SetStatementSubstraitPlan; private static unsafe delegate* unmanaged StatementGetParameterSchemaPtr => &GetStatementParameterSchema; #else - private static unsafe IntPtr ReleaseErrorPtr => s_releaseError.Pointer; + internal static unsafe IntPtr ReleaseErrorPtr => s_releaseError.Pointer; private static unsafe IntPtr ReleaseDriverPtr = NativeDelegate.AsNativePointer(ReleaseDriver); private static unsafe IntPtr ReleasePartitionsPtr = NativeDelegate.AsNativePointer(ReleasePartitions); @@ -84,8 +84,8 @@ public class CAdbcDriverExporter private static unsafe IntPtr StatementExecuteQueryPtr = NativeDelegate.AsNativePointer(ExecuteStatementQuery); private static unsafe IntPtr StatementExecutePartitionsPtr = NativeDelegate.AsNativePointer(ExecuteStatementPartitions); private static unsafe IntPtr StatementNewPtr = NativeDelegate.AsNativePointer(NewStatement); - private static unsafe IntPtr StatementReleasePtr = NativeDelegate.AsNativePointer(ReleaseStatement); - private static unsafe IntPtr StatementPreparePtr = NativeDelegate.AsNativePointer(PrepareStatement); + private static unsafe IntPtr StatementReleasePtr = NativeDelegate.AsNativePointer(ReleaseStatement); + private static unsafe IntPtr StatementPreparePtr = NativeDelegate.AsNativePointer(PrepareStatement); private static unsafe IntPtr StatementSetSqlQueryPtr = NativeDelegate.AsNativePointer(SetStatementSqlQuery); private static unsafe IntPtr StatementSetSubstraitPlanPtr = NativeDelegate.AsNativePointer(SetStatementSubstraitPlan); private static unsafe IntPtr StatementGetParameterSchemaPtr = NativeDelegate.AsNativePointer(GetStatementParameterSchema); @@ -129,7 +129,7 @@ public unsafe static AdbcStatusCode AdbcDriverInit(int version, CAdbcDriver* nat return 0; } - private unsafe static void ReleaseError(CAdbcError* error) + internal unsafe static void ReleaseError(CAdbcError* error) { if (error != null && ((IntPtr)error->message) != IntPtr.Zero) { diff --git a/csharp/src/Apache.Arrow.Adbc/C/CAdbcDriverImporter.cs b/csharp/src/Apache.Arrow.Adbc/C/CAdbcDriverImporter.cs index a191775ec4..24c8206314 100644 --- a/csharp/src/Apache.Arrow.Adbc/C/CAdbcDriverImporter.cs +++ b/csharp/src/Apache.Arrow.Adbc/C/CAdbcDriverImporter.cs @@ -75,6 +75,9 @@ public static AdbcDriver Load(string file, string? entryPoint = null) 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; @@ -86,6 +89,249 @@ public static AdbcDriver Load(string file, string? entryPoint = null) } } + 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.AsNativePointer(DatabaseSetOptionDefaultImpl); +#else + private static unsafe delegate* unmanaged 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.AsNativePointer(ConnectionCommitDefaultImpl); +#else + private static unsafe delegate* unmanaged 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.AsNativePointer(ConnectionGetInfoDefaultImpl); +#else + private static unsafe delegate* unmanaged 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.AsNativePointer(ConnectionGetObjectsDefaultImpl); +#else + private static unsafe delegate* unmanaged 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.AsNativePointer(ConnectionGetTableSchemaDefaultImpl); +#else + private static unsafe delegate* unmanaged 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.AsNativePointer(ConnectionGetTableTypesDefaultImpl); +#else + private static unsafe delegate* unmanaged 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.AsNativePointer(ConnectionReadPartitionDefaultImpl); +#else + private static unsafe delegate* unmanaged 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.AsNativePointer(ConnectionRollbackDefaultImpl); +#else + private static unsafe delegate* unmanaged 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.AsNativePointer(ConnectionSetOptionDefaultImpl); +#else + private static unsafe delegate* unmanaged 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.AsNativePointer(StatementExecutePartitionsDefaultImpl); +#else + private static unsafe delegate* unmanaged 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.AsNativePointer(StatementBindDefaultImpl); +#else + private static unsafe delegate* unmanaged 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.AsNativePointer(StatementBindStreamDefaultImpl); +#else + private static unsafe delegate* unmanaged 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.AsNativePointer(StatementGetParameterSchemaDefaultImpl); +#else + private static unsafe delegate* unmanaged 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.AsNativePointer(StatementPrepareDefaultImpl); +#else + private static unsafe delegate* unmanaged 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.AsNativePointer(StatementSetOptionDefaultImpl); +#else + private static unsafe delegate* unmanaged 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.AsNativePointer(StatementSetSqlQueryDefaultImpl); +#else + private static unsafe delegate* unmanaged 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.AsNativePointer(StatementSetSubstraitPlanDefaultImpl); +#else + private static unsafe delegate* unmanaged 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; + } + /// /// Native implementation of /// @@ -1026,7 +1272,7 @@ public unsafe void Call(IntPtr fn, ref CAdbcStatement nativeStatement) fixed (CAdbcStatement* stmt = &nativeStatement) fixed (CAdbcError* e = &_error) { - TranslateCode(Marshal.GetDelegateForFunctionPointer(fn)(stmt, e)); + TranslateCode(Marshal.GetDelegateForFunctionPointer(fn)(stmt, e)); } } #endif diff --git a/csharp/src/Apache.Arrow.Adbc/C/Delegates.cs b/csharp/src/Apache.Arrow.Adbc/C/Delegates.cs index 4825801593..e1fa475ffb 100644 --- a/csharp/src/Apache.Arrow.Adbc/C/Delegates.cs +++ b/csharp/src/Apache.Arrow.Adbc/C/Delegates.cs @@ -27,21 +27,25 @@ namespace Apache.Arrow.Adbc.C internal unsafe delegate AdbcStatusCode DriverRelease(CAdbcDriver* driver, CAdbcError* error); internal unsafe delegate void PartitionsRelease(CAdbcPartitions* partitions); internal unsafe delegate AdbcStatusCode DatabaseSetOption(CAdbcDatabase* database, byte* name, byte* value, CAdbcError* error); + internal unsafe delegate AdbcStatusCode ConnectionCommit(CAdbcConnection* connection, CAdbcError* error); internal unsafe delegate AdbcStatusCode ConnectionGetObjects(CAdbcConnection* connection, int depth, byte* catalog, byte* db_schema, byte* table_name, byte** table_type, byte* column_name, CArrowArrayStream* stream, CAdbcError* error); internal unsafe delegate AdbcStatusCode ConnectionGetTableSchema(CAdbcConnection* connection, byte* catalog, byte* db_schema, byte* table_name, CArrowSchema* schema, CAdbcError* error); internal unsafe delegate AdbcStatusCode ConnectionGetTableTypes(CAdbcConnection* connection, CArrowArrayStream* stream, CAdbcError* error); internal unsafe delegate AdbcStatusCode ConnectionInit(CAdbcConnection* connection, CAdbcDatabase* database, CAdbcError* error); internal unsafe delegate AdbcStatusCode ConnectionGetInfo(CAdbcConnection* connection, int* info_codes, int info_codes_length, CArrowArrayStream* stream, CAdbcError* error); internal unsafe delegate AdbcStatusCode ConnectionReadPartition(CAdbcConnection* connection, byte* serialized_partition, int serialized_length, CArrowArrayStream* stream, CAdbcError* error); + internal unsafe delegate AdbcStatusCode ConnectionRollback(CAdbcConnection* connection, CAdbcError* error); internal unsafe delegate AdbcStatusCode ConnectionSetOption(CAdbcConnection* connection, byte* name, byte* value, CAdbcError* error); internal unsafe delegate AdbcStatusCode StatementBind(CAdbcStatement* statement, CArrowArray* array, CArrowSchema* schema, CAdbcError* error); internal unsafe delegate AdbcStatusCode StatementBindStream(CAdbcStatement* statement, CArrowArrayStream* stream, CAdbcError* error); internal unsafe delegate AdbcStatusCode StatementExecuteQuery(CAdbcStatement* statement, CArrowArrayStream* stream, long* rows, CAdbcError* error); internal unsafe delegate AdbcStatusCode StatementExecutePartitions(CAdbcStatement* statement, CArrowSchema* schema, CAdbcPartitions* partitions, long* rows, CAdbcError* error); + internal unsafe delegate AdbcStatusCode StatementGetParameterSchema(CAdbcStatement* statement, CArrowSchema* schema, CAdbcError* error); internal unsafe delegate AdbcStatusCode StatementNew(CAdbcConnection* connection, CAdbcStatement* statement, CAdbcError* error); - internal unsafe delegate AdbcStatusCode StatementFn(CAdbcStatement* statement, CAdbcError* error); + internal unsafe delegate AdbcStatusCode StatementPrepare(CAdbcStatement* statement, CAdbcError* error); + internal unsafe delegate AdbcStatusCode StatementSetOption(CAdbcStatement* statement, byte* name, byte* value, CAdbcError* error); internal unsafe delegate AdbcStatusCode StatementSetSqlQuery(CAdbcStatement* statement, byte* text, CAdbcError* error); internal unsafe delegate AdbcStatusCode StatementSetSubstraitPlan(CAdbcStatement* statement, byte* plan, int length, CAdbcError* error); - internal unsafe delegate AdbcStatusCode StatementGetParameterSchema(CAdbcStatement* statement, CArrowSchema* schema, CAdbcError* error); + internal unsafe delegate AdbcStatusCode StatementRelease(CAdbcStatement* statement, CAdbcError* error); #endif }