diff --git a/adbc.h b/adbc.h index ab066483de..7708d6dbd4 100644 --- a/adbc.h +++ b/adbc.h @@ -334,8 +334,9 @@ struct ADBC_EXPORT AdbcError { /// Buffers) that can be optionally parsed by clients, beyond the /// standard AdbcError fields, without having to encode it in the /// error message. The encoding of the data is driver-defined. +/// Drivers may provide multiple error details. /// -/// This can be called immediately after any API call that returns an +/// This can be used immediately after any API call that returns an /// error. Additionally, if an ArrowArrayStream returned from an /// AdbcConnection or an AdbcStatement returns an error, this can be /// immediately called from the associated AdbcConnection or @@ -343,16 +344,25 @@ struct ADBC_EXPORT AdbcError { /// other API calls with that connection or statement may clear this /// error value. /// -/// Drivers may provide multiple error details. Each call to -/// GetOptionBytes will return the next error detail. The driver -/// should return ADBC_STATUS_NOT_FOUND if there are no (more) error -/// details. +/// To use, call GetOptionInt with this option to get the number of +/// available details. Then, call GetOption with the option key +/// ADBC_OPTION_ERROR_DETAILS_PREFIX + (zero-indexed index) to get the +/// name of the error detail (for example, drivers that use gRPC +/// underneath may provide the name of the gRPC trailer corresponding +/// to the error detail). GetOptionBytes with that option name will +/// retrieve the value of the error detail (for example, a serialized +/// Any-wrapped Protobuf). /// -/// The type is uint8_t*. +/// \since ADBC API revision 1.1.0 +/// \addtogroup adbc-1.1.0 +#define ADBC_OPTION_ERROR_DETAILS "adbc.error_details" + +/// \brief Canonical option name for error details. /// +/// \see ADBC_OPTION_ERROR_DETAILS /// \since ADBC API revision 1.1.0 /// \addtogroup adbc-1.1.0 -#define ADBC_OPTION_ERROR_DETAILS "error_details" +#define ADBC_OPTION_ERROR_DETAILS_PREFIX "adbc.error_details." /// \brief The database vendor/product name (e.g. the server name). /// (type: utf8). @@ -888,26 +898,26 @@ struct ADBC_EXPORT AdbcDriver { struct AdbcError*); AdbcStatusCode (*DatabaseGetOptionBytes)(struct AdbcDatabase*, const char*, uint8_t*, size_t*, struct AdbcError*); - AdbcStatusCode (*DatabaseGetOptionInt)(struct AdbcDatabase*, const char*, int64_t*, - struct AdbcError*); AdbcStatusCode (*DatabaseGetOptionDouble)(struct AdbcDatabase*, const char*, double*, struct AdbcError*); + AdbcStatusCode (*DatabaseGetOptionInt)(struct AdbcDatabase*, const char*, int64_t*, + struct AdbcError*); AdbcStatusCode (*DatabaseSetOptionBytes)(struct AdbcDatabase*, const char*, const uint8_t*, size_t, struct AdbcError*); - AdbcStatusCode (*DatabaseSetOptionInt)(struct AdbcDatabase*, const char*, int64_t, - struct AdbcError*); AdbcStatusCode (*DatabaseSetOptionDouble)(struct AdbcDatabase*, const char*, double, struct AdbcError*); + AdbcStatusCode (*DatabaseSetOptionInt)(struct AdbcDatabase*, const char*, int64_t, + struct AdbcError*); AdbcStatusCode (*ConnectionCancel)(struct AdbcConnection*, struct AdbcError*); AdbcStatusCode (*ConnectionGetOption)(struct AdbcConnection*, const char*, char*, size_t*, struct AdbcError*); AdbcStatusCode (*ConnectionGetOptionBytes)(struct AdbcConnection*, const char*, uint8_t*, size_t*, struct AdbcError*); - AdbcStatusCode (*ConnectionGetOptionInt)(struct AdbcConnection*, const char*, int64_t*, - struct AdbcError*); AdbcStatusCode (*ConnectionGetOptionDouble)(struct AdbcConnection*, const char*, double*, struct AdbcError*); + AdbcStatusCode (*ConnectionGetOptionInt)(struct AdbcConnection*, const char*, int64_t*, + struct AdbcError*); AdbcStatusCode (*ConnectionGetStatistics)(struct AdbcConnection*, const char*, const char*, const char*, char, struct ArrowArrayStream*, struct AdbcError*); @@ -916,10 +926,10 @@ struct ADBC_EXPORT AdbcDriver { struct AdbcError*); AdbcStatusCode (*ConnectionSetOptionBytes)(struct AdbcConnection*, const char*, const uint8_t*, size_t, struct AdbcError*); - AdbcStatusCode (*ConnectionSetOptionInt)(struct AdbcConnection*, const char*, int64_t, - struct AdbcError*); AdbcStatusCode (*ConnectionSetOptionDouble)(struct AdbcConnection*, const char*, double, struct AdbcError*); + AdbcStatusCode (*ConnectionSetOptionInt)(struct AdbcConnection*, const char*, int64_t, + struct AdbcError*); AdbcStatusCode (*StatementCancel)(struct AdbcStatement*, struct AdbcError*); AdbcStatusCode (*StatementExecuteSchema)(struct AdbcStatement*, struct ArrowSchema*, @@ -928,16 +938,16 @@ struct ADBC_EXPORT AdbcDriver { struct AdbcError*); AdbcStatusCode (*StatementGetOptionBytes)(struct AdbcStatement*, const char*, uint8_t*, size_t*, struct AdbcError*); - AdbcStatusCode (*StatementGetOptionInt)(struct AdbcStatement*, const char*, int64_t*, - struct AdbcError*); AdbcStatusCode (*StatementGetOptionDouble)(struct AdbcStatement*, const char*, double*, struct AdbcError*); + AdbcStatusCode (*StatementGetOptionInt)(struct AdbcStatement*, const char*, int64_t*, + struct AdbcError*); AdbcStatusCode (*StatementSetOptionBytes)(struct AdbcStatement*, const char*, const uint8_t*, size_t, struct AdbcError*); - AdbcStatusCode (*StatementSetOptionInt)(struct AdbcStatement*, const char*, int64_t, - struct AdbcError*); AdbcStatusCode (*StatementSetOptionDouble)(struct AdbcStatement*, const char*, double, struct AdbcError*); + AdbcStatusCode (*StatementSetOptionInt)(struct AdbcStatement*, const char*, int64_t, + struct AdbcError*); /// @} }; @@ -1639,7 +1649,6 @@ AdbcStatusCode AdbcConnectionGetOptionDouble(struct AdbcConnection* connection, /// | int64 | int64 | /// | uint64 | uint64 | /// | float64 | float64 | -/// | decimal256 | decimal256 | /// | binary | binary | /// /// This AdbcConnection must outlive the returned ArrowArrayStream. diff --git a/c/driver/flightsql/sqlite_flightsql_test.cc b/c/driver/flightsql/sqlite_flightsql_test.cc index 2bf0441e3a..f31dc63130 100644 --- a/c/driver/flightsql/sqlite_flightsql_test.cc +++ b/c/driver/flightsql/sqlite_flightsql_test.cc @@ -21,6 +21,8 @@ #include #include #include +#include +#include #include "validation/adbc_validation.h" #include "validation/adbc_validation_util.h" @@ -86,6 +88,25 @@ class SqliteFlightSqlQuirks : public adbc_validation::DriverQuirks { bool supports_concurrent_statements() const override { return true; } bool supports_transactions() const override { return false; } bool supports_get_sql_info() const override { return true; } + std::optional supports_get_sql_info( + uint32_t info_code) const override { + switch (info_code) { + case ADBC_INFO_DRIVER_NAME: + return "ADBC Flight SQL Driver - Go"; + case ADBC_INFO_DRIVER_VERSION: + return "(unknown or development build)"; + case ADBC_INFO_DRIVER_ADBC_VERSION: + return ADBC_VERSION_1_1_0; + case ADBC_INFO_VENDOR_NAME: + return "db_name"; + case ADBC_INFO_VENDOR_VERSION: + return "sqlite 3"; + case ADBC_INFO_VENDOR_ARROW_VERSION: + return "12.0.0"; + default: + return std::nullopt; + } + } bool supports_get_objects() const override { return true; } bool supports_bulk_ingest() const override { return false; } bool supports_partitioned_data() const override { return true; } diff --git a/c/validation/adbc_validation.cc b/c/validation/adbc_validation.cc index 803f14c023..f977de8721 100644 --- a/c/validation/adbc_validation.cc +++ b/c/validation/adbc_validation.cc @@ -26,6 +26,7 @@ #include #include #include +#include #include #include @@ -255,79 +256,99 @@ void ConnectionTest::TestMetadataGetInfo() { GTEST_SKIP(); } - StreamReader reader; - std::vector info = { - ADBC_INFO_DRIVER_NAME, - ADBC_INFO_DRIVER_VERSION, - ADBC_INFO_VENDOR_NAME, - ADBC_INFO_VENDOR_VERSION, - }; + for (uint32_t info_code : { + ADBC_INFO_DRIVER_NAME, + ADBC_INFO_DRIVER_VERSION, + ADBC_INFO_DRIVER_ADBC_VERSION, + ADBC_INFO_VENDOR_NAME, + ADBC_INFO_VENDOR_VERSION, + }) { + uint32_t info[] = {info_code}; - ASSERT_THAT(AdbcConnectionGetInfo(&connection, info.data(), info.size(), - &reader.stream.value, &error), - IsOkStatus(&error)); - ASSERT_NO_FATAL_FAILURE(reader.GetSchema()); - ASSERT_NO_FATAL_FAILURE(CompareSchema( - &reader.schema.value, { - {"info_name", NANOARROW_TYPE_UINT32, NOT_NULL}, - {"info_value", NANOARROW_TYPE_DENSE_UNION, NULLABLE}, - })); - ASSERT_NO_FATAL_FAILURE( - CompareSchema(reader.schema->children[1], - { - {"string_value", NANOARROW_TYPE_STRING, NULLABLE}, - {"bool_value", NANOARROW_TYPE_BOOL, NULLABLE}, - {"int64_value", NANOARROW_TYPE_INT64, NULLABLE}, - {"int32_bitmask", NANOARROW_TYPE_INT32, NULLABLE}, - {"string_list", NANOARROW_TYPE_LIST, NULLABLE}, - {"int32_to_int32_list_map", NANOARROW_TYPE_MAP, NULLABLE}, - })); - ASSERT_NO_FATAL_FAILURE(CompareSchema(reader.schema->children[1]->children[4], - { - {"item", NANOARROW_TYPE_STRING, NULLABLE}, - })); - ASSERT_NO_FATAL_FAILURE(CompareSchema(reader.schema->children[1]->children[5], - { - {"entries", NANOARROW_TYPE_STRUCT, NOT_NULL}, - })); - ASSERT_NO_FATAL_FAILURE( - CompareSchema(reader.schema->children[1]->children[5]->children[0], - { - {"key", NANOARROW_TYPE_INT32, NOT_NULL}, - {"value", NANOARROW_TYPE_LIST, NULLABLE}, - })); - ASSERT_NO_FATAL_FAILURE( - CompareSchema(reader.schema->children[1]->children[5]->children[0]->children[1], - { - {"item", NANOARROW_TYPE_INT32, NULLABLE}, - })); + StreamReader reader; + ASSERT_THAT(AdbcConnectionGetInfo(&connection, info, 1, &reader.stream.value, &error), + IsOkStatus(&error)); + ASSERT_NO_FATAL_FAILURE(reader.GetSchema()); - std::vector seen; - while (true) { - ASSERT_NO_FATAL_FAILURE(reader.Next()); - if (!reader.array->release) break; - - for (int64_t row = 0; row < reader.array->length; row++) { - ASSERT_FALSE(ArrowArrayViewIsNull(reader.array_view->children[0], row)); - const uint32_t code = - reader.array_view->children[0]->buffer_views[1].data.as_uint32[row]; - seen.push_back(code); - - switch (code) { - case ADBC_INFO_DRIVER_NAME: - case ADBC_INFO_DRIVER_VERSION: - case ADBC_INFO_VENDOR_NAME: - case ADBC_INFO_VENDOR_VERSION: - // UTF8 - ASSERT_EQ(uint8_t(0), - reader.array_view->children[1]->buffer_views[0].data.as_uint8[row]); - default: - // Ignored - break; + ASSERT_NO_FATAL_FAILURE(CompareSchema( + &reader.schema.value, { + {"info_name", NANOARROW_TYPE_UINT32, NOT_NULL}, + {"info_value", NANOARROW_TYPE_DENSE_UNION, NULLABLE}, + })); + ASSERT_NO_FATAL_FAILURE( + CompareSchema(reader.schema->children[1], + { + {"string_value", NANOARROW_TYPE_STRING, NULLABLE}, + {"bool_value", NANOARROW_TYPE_BOOL, NULLABLE}, + {"int64_value", NANOARROW_TYPE_INT64, NULLABLE}, + {"int32_bitmask", NANOARROW_TYPE_INT32, NULLABLE}, + {"string_list", NANOARROW_TYPE_LIST, NULLABLE}, + {"int32_to_int32_list_map", NANOARROW_TYPE_MAP, NULLABLE}, + })); + ASSERT_NO_FATAL_FAILURE(CompareSchema(reader.schema->children[1]->children[4], + { + {"item", NANOARROW_TYPE_STRING, NULLABLE}, + })); + ASSERT_NO_FATAL_FAILURE( + CompareSchema(reader.schema->children[1]->children[5], + { + {"entries", NANOARROW_TYPE_STRUCT, NOT_NULL}, + })); + ASSERT_NO_FATAL_FAILURE( + CompareSchema(reader.schema->children[1]->children[5]->children[0], + { + {"key", NANOARROW_TYPE_INT32, NOT_NULL}, + {"value", NANOARROW_TYPE_LIST, NULLABLE}, + })); + ASSERT_NO_FATAL_FAILURE( + CompareSchema(reader.schema->children[1]->children[5]->children[0]->children[1], + { + {"item", NANOARROW_TYPE_INT32, NULLABLE}, + })); + + std::vector seen; + while (true) { + ASSERT_NO_FATAL_FAILURE(reader.Next()); + if (!reader.array->release) break; + + for (int64_t row = 0; row < reader.array->length; row++) { + ASSERT_FALSE(ArrowArrayViewIsNull(reader.array_view->children[0], row)); + const uint32_t code = + reader.array_view->children[0]->buffer_views[1].data.as_uint32[row]; + seen.push_back(code); + + std::optional expected = quirks()->supports_get_sql_info(code); + ASSERT_TRUE(expected.has_value()) << "Got unexpected info code " << code; + + uint8_t type_code = + reader.array_view->children[1]->buffer_views[0].data.as_uint8[row]; + int32_t offset = + reader.array_view->children[1]->buffer_views[1].data.as_int32[row]; + ASSERT_NO_FATAL_FAILURE(std::visit( + [&](auto&& expected_value) { + using T = std::decay_t; + if constexpr (std::is_same_v) { + ASSERT_EQ(uint8_t(2), type_code); + ASSERT_EQ(expected_value, + ArrowArrayViewGetIntUnsafe( + reader.array_view->children[1]->children[2], offset)); + } else if constexpr (std::is_same_v) { + ASSERT_EQ(uint8_t(0), type_code); + struct ArrowStringView view = ArrowArrayViewGetStringUnsafe( + reader.array_view->children[1]->children[0], offset); + ASSERT_EQ(expected_value, + std::string_view(static_cast(view.data), + view.size_bytes)); + } else { + static_assert(!sizeof(T), "not yet implemented"); + } + }, + *expected)) + << "code: " << type_code; } } + ASSERT_THAT(seen, ::testing::IsSupersetOf(info)); } - ASSERT_THAT(seen, ::testing::UnorderedElementsAreArray(info)); } void ConnectionTest::TestMetadataGetTableSchema() { diff --git a/c/validation/adbc_validation.h b/c/validation/adbc_validation.h index 2a5883c00f..26295030b5 100644 --- a/c/validation/adbc_validation.h +++ b/c/validation/adbc_validation.h @@ -20,6 +20,7 @@ #include #include +#include #include #include @@ -31,6 +32,8 @@ namespace adbc_validation { #define ADBCV_STRINGIFY(s) #s #define ADBCV_STRINGIFY_VALUE(s) ADBCV_STRINGIFY(s) +using SqlInfoValue = std::variant; + /// \brief Configuration for driver-specific behavior. class DriverQuirks { public: @@ -101,6 +104,11 @@ class DriverQuirks { /// \brief Whether GetSqlInfo is implemented virtual bool supports_get_sql_info() const { return true; } + /// \brief The expected value for a given info code + virtual std::optional supports_get_sql_info(uint32_t info_code) const { + return std::nullopt; + } + /// \brief Whether GetObjects is implemented virtual bool supports_get_objects() const { return true; } diff --git a/c/validation/adbc_validation_util.h b/c/validation/adbc_validation_util.h index fec5e7589d..d238b0a23b 100644 --- a/c/validation/adbc_validation_util.h +++ b/c/validation/adbc_validation_util.h @@ -31,6 +31,7 @@ #include #include #include + #include "common/utils.h" namespace adbc_validation { diff --git a/go/adbc/adbc.go b/go/adbc/adbc.go index 44964340ad..9936f78691 100644 --- a/go/adbc/adbc.go +++ b/go/adbc/adbc.go @@ -753,7 +753,7 @@ type ConnectionGetStatistics interface { // statistic_name | utf8 not null // statistic_key | int16 not null // - GetStatisticNames() (array.RecordReader, error) + GetStatisticNames(ctx context.Context) (array.RecordReader, error) } // StatementExecuteSchema is a Statement that also supports ExecuteSchema. diff --git a/go/adbc/driver/flightsql/flightsql_adbc.go b/go/adbc/driver/flightsql/flightsql_adbc.go index f92e316b77..221604fee7 100644 --- a/go/adbc/driver/flightsql/flightsql_adbc.go +++ b/go/adbc/driver/flightsql/flightsql_adbc.go @@ -427,11 +427,7 @@ func (d *database) GetOptionDouble(key string) (float64, error) { func (d *database) SetOption(key, value string) error { // We can't change most options post-init switch key { - case OptionTimeoutFetch: - fallthrough - case OptionTimeoutQuery: - fallthrough - case OptionTimeoutUpdate: + case OptionTimeoutFetch, OptionTimeoutQuery, OptionTimeoutUpdate: return d.timeout.setTimeoutString(key, value) } if strings.HasPrefix(key, OptionRPCCallHeaderPrefix) { @@ -1154,6 +1150,8 @@ func (c *cnxn) GetInfo(ctx context.Context, infoCodes []adbc.InfoCode) (array.Re infoNameBldr.Append(uint32(adbc.InfoVendorVersion)) case flightsql.SqlInfoFlightSqlServerArrowVersion: infoNameBldr.Append(uint32(adbc.InfoVendorArrowVersion)) + default: + continue } infoValueBldr.Append(info.TypeCode(i)) diff --git a/go/adbc/drivermgr/adbc.h b/go/adbc/drivermgr/adbc.h index ab066483de..7708d6dbd4 100644 --- a/go/adbc/drivermgr/adbc.h +++ b/go/adbc/drivermgr/adbc.h @@ -334,8 +334,9 @@ struct ADBC_EXPORT AdbcError { /// Buffers) that can be optionally parsed by clients, beyond the /// standard AdbcError fields, without having to encode it in the /// error message. The encoding of the data is driver-defined. +/// Drivers may provide multiple error details. /// -/// This can be called immediately after any API call that returns an +/// This can be used immediately after any API call that returns an /// error. Additionally, if an ArrowArrayStream returned from an /// AdbcConnection or an AdbcStatement returns an error, this can be /// immediately called from the associated AdbcConnection or @@ -343,16 +344,25 @@ struct ADBC_EXPORT AdbcError { /// other API calls with that connection or statement may clear this /// error value. /// -/// Drivers may provide multiple error details. Each call to -/// GetOptionBytes will return the next error detail. The driver -/// should return ADBC_STATUS_NOT_FOUND if there are no (more) error -/// details. +/// To use, call GetOptionInt with this option to get the number of +/// available details. Then, call GetOption with the option key +/// ADBC_OPTION_ERROR_DETAILS_PREFIX + (zero-indexed index) to get the +/// name of the error detail (for example, drivers that use gRPC +/// underneath may provide the name of the gRPC trailer corresponding +/// to the error detail). GetOptionBytes with that option name will +/// retrieve the value of the error detail (for example, a serialized +/// Any-wrapped Protobuf). /// -/// The type is uint8_t*. +/// \since ADBC API revision 1.1.0 +/// \addtogroup adbc-1.1.0 +#define ADBC_OPTION_ERROR_DETAILS "adbc.error_details" + +/// \brief Canonical option name for error details. /// +/// \see ADBC_OPTION_ERROR_DETAILS /// \since ADBC API revision 1.1.0 /// \addtogroup adbc-1.1.0 -#define ADBC_OPTION_ERROR_DETAILS "error_details" +#define ADBC_OPTION_ERROR_DETAILS_PREFIX "adbc.error_details." /// \brief The database vendor/product name (e.g. the server name). /// (type: utf8). @@ -888,26 +898,26 @@ struct ADBC_EXPORT AdbcDriver { struct AdbcError*); AdbcStatusCode (*DatabaseGetOptionBytes)(struct AdbcDatabase*, const char*, uint8_t*, size_t*, struct AdbcError*); - AdbcStatusCode (*DatabaseGetOptionInt)(struct AdbcDatabase*, const char*, int64_t*, - struct AdbcError*); AdbcStatusCode (*DatabaseGetOptionDouble)(struct AdbcDatabase*, const char*, double*, struct AdbcError*); + AdbcStatusCode (*DatabaseGetOptionInt)(struct AdbcDatabase*, const char*, int64_t*, + struct AdbcError*); AdbcStatusCode (*DatabaseSetOptionBytes)(struct AdbcDatabase*, const char*, const uint8_t*, size_t, struct AdbcError*); - AdbcStatusCode (*DatabaseSetOptionInt)(struct AdbcDatabase*, const char*, int64_t, - struct AdbcError*); AdbcStatusCode (*DatabaseSetOptionDouble)(struct AdbcDatabase*, const char*, double, struct AdbcError*); + AdbcStatusCode (*DatabaseSetOptionInt)(struct AdbcDatabase*, const char*, int64_t, + struct AdbcError*); AdbcStatusCode (*ConnectionCancel)(struct AdbcConnection*, struct AdbcError*); AdbcStatusCode (*ConnectionGetOption)(struct AdbcConnection*, const char*, char*, size_t*, struct AdbcError*); AdbcStatusCode (*ConnectionGetOptionBytes)(struct AdbcConnection*, const char*, uint8_t*, size_t*, struct AdbcError*); - AdbcStatusCode (*ConnectionGetOptionInt)(struct AdbcConnection*, const char*, int64_t*, - struct AdbcError*); AdbcStatusCode (*ConnectionGetOptionDouble)(struct AdbcConnection*, const char*, double*, struct AdbcError*); + AdbcStatusCode (*ConnectionGetOptionInt)(struct AdbcConnection*, const char*, int64_t*, + struct AdbcError*); AdbcStatusCode (*ConnectionGetStatistics)(struct AdbcConnection*, const char*, const char*, const char*, char, struct ArrowArrayStream*, struct AdbcError*); @@ -916,10 +926,10 @@ struct ADBC_EXPORT AdbcDriver { struct AdbcError*); AdbcStatusCode (*ConnectionSetOptionBytes)(struct AdbcConnection*, const char*, const uint8_t*, size_t, struct AdbcError*); - AdbcStatusCode (*ConnectionSetOptionInt)(struct AdbcConnection*, const char*, int64_t, - struct AdbcError*); AdbcStatusCode (*ConnectionSetOptionDouble)(struct AdbcConnection*, const char*, double, struct AdbcError*); + AdbcStatusCode (*ConnectionSetOptionInt)(struct AdbcConnection*, const char*, int64_t, + struct AdbcError*); AdbcStatusCode (*StatementCancel)(struct AdbcStatement*, struct AdbcError*); AdbcStatusCode (*StatementExecuteSchema)(struct AdbcStatement*, struct ArrowSchema*, @@ -928,16 +938,16 @@ struct ADBC_EXPORT AdbcDriver { struct AdbcError*); AdbcStatusCode (*StatementGetOptionBytes)(struct AdbcStatement*, const char*, uint8_t*, size_t*, struct AdbcError*); - AdbcStatusCode (*StatementGetOptionInt)(struct AdbcStatement*, const char*, int64_t*, - struct AdbcError*); AdbcStatusCode (*StatementGetOptionDouble)(struct AdbcStatement*, const char*, double*, struct AdbcError*); + AdbcStatusCode (*StatementGetOptionInt)(struct AdbcStatement*, const char*, int64_t*, + struct AdbcError*); AdbcStatusCode (*StatementSetOptionBytes)(struct AdbcStatement*, const char*, const uint8_t*, size_t, struct AdbcError*); - AdbcStatusCode (*StatementSetOptionInt)(struct AdbcStatement*, const char*, int64_t, - struct AdbcError*); AdbcStatusCode (*StatementSetOptionDouble)(struct AdbcStatement*, const char*, double, struct AdbcError*); + AdbcStatusCode (*StatementSetOptionInt)(struct AdbcStatement*, const char*, int64_t, + struct AdbcError*); /// @} }; @@ -1639,7 +1649,6 @@ AdbcStatusCode AdbcConnectionGetOptionDouble(struct AdbcConnection* connection, /// | int64 | int64 | /// | uint64 | uint64 | /// | float64 | float64 | -/// | decimal256 | decimal256 | /// | binary | binary | /// /// This AdbcConnection must outlive the returned ArrowArrayStream. diff --git a/go/adbc/pkg/_tmpl/driver.go.tmpl b/go/adbc/pkg/_tmpl/driver.go.tmpl index 7ae325a181..f476c136aa 100644 --- a/go/adbc/pkg/_tmpl/driver.go.tmpl +++ b/go/adbc/pkg/_tmpl/driver.go.tmpl @@ -38,12 +38,15 @@ import ( "os" "runtime" "runtime/cgo" + "strconv" + "strings" "sync/atomic" "unsafe" "github.com/apache/arrow-adbc/go/adbc" "github.com/apache/arrow/go/v13/arrow/array" "github.com/apache/arrow/go/v13/arrow/cdata" + "github.com/apache/arrow/go/v13/arrow/memory" "github.com/apache/arrow/go/v13/arrow/memory/mallocator" ) @@ -71,7 +74,7 @@ func setErr(err *C.struct_AdbcError, format string, vals ...interface{}) { } func errToAdbcErr(adbcerr *C.struct_AdbcError, err error) adbc.Status { - if adbcerr == nil || err == nil { + if err == nil { return adbc.StatusOK } @@ -85,6 +88,87 @@ func errToAdbcErr(adbcerr *C.struct_AdbcError, err error) adbc.Status { return adbc.StatusUnknown } +// errorDetails handles the standard error details feature in ADBC. +type errorDetails struct { + errorDetails []adbc.ErrorDetail +} + +func (ed *errorDetails) errToAdbcErr(adbcerr *C.struct_AdbcError, err error) adbc.Status { + ed.errorDetails = nil + if err == nil { + return adbc.StatusOK + } + + var adbcError adbc.Error + if errors.As(err, &adbcError) { + setErr(adbcerr, adbcError.Msg) + ed.errorDetails = adbcError.Details + return adbcError.Code + } + + setErr(adbcerr, err.Error()) + return adbc.StatusUnknown +} + +func (ed *errorDetails) getErrorDetailsOptionInt(key *C.cchar_t, value *C.int64_t) bool { + if C.GoString(key) == C.ADBC_OPTION_ERROR_DETAILS { + *value = C.int64_t(len(ed.errorDetails)) + return true + } + return false +} + +func (ed *errorDetails) getErrorDetailsOptionString(key *C.cchar_t, value *C.char, length *C.size_t) (bool, error) { + gokey := C.GoString(key) + if strings.HasPrefix(gokey, C.ADBC_OPTION_ERROR_DETAILS_PREFIX) { + indexStr := strings.TrimPrefix(gokey, C.ADBC_OPTION_ERROR_DETAILS_PREFIX) + index, err := strconv.ParseInt(indexStr, 10, 32) + if err != nil { + return false, adbc.Error{ + Code: adbc.StatusInvalidArgument, + Msg: fmt.Sprintf("[{{.Prefix}}] Invalid error details option key '%s': %s", index, err.Error()), + } + } else if index < 0 || index >= int64(len(ed.errorDetails)) { + return false, adbc.Error{ + Code: adbc.StatusInvalidArgument, + Msg: fmt.Sprintf("[{{.Prefix}}] Invalid error details option key '%s': out of range", index), + } + } + exportStringOption(ed.errorDetails[int(index)].Key(), value, length) + return true, nil + } + return false, nil +} + +func (ed *errorDetails) getErrorDetailsOptionBytes(key *C.cchar_t, value *C.uint8_t, length *C.size_t) (bool, error) { + gokey := C.GoString(key) + if strings.HasPrefix(gokey, C.ADBC_OPTION_ERROR_DETAILS_PREFIX) { + indexStr := strings.TrimPrefix(gokey, C.ADBC_OPTION_ERROR_DETAILS_PREFIX) + index, err := strconv.ParseInt(indexStr, 10, 32) + if err != nil { + return false, adbc.Error{ + Code: adbc.StatusInvalidArgument, + Msg: fmt.Sprintf("[{{.Prefix}}] Invalid error details option key '%s': %s", index, err.Error()), + } + } else if index < 0 || index >= int64(len(ed.errorDetails)) { + return false, adbc.Error{ + Code: adbc.StatusInvalidArgument, + Msg: fmt.Sprintf("[{{.Prefix}}] Invalid error details option key '%s': out of range", index), + } + } + serialized, err := ed.errorDetails[int(index)].Serialize() + if err != nil { + return false, adbc.Error{ + Code: adbc.StatusInternal, + Msg: fmt.Sprintf("[{{.Prefix}}] Could not serialize error details: %s", err.Error()), + } + } + exportBytesOption(serialized, value, length) + return true, nil + } + return false, nil +} + // We panicked; make all API functions error and dump stack traces func poison(err *C.struct_AdbcError, fname string, e interface{}) C.AdbcStatusCode { if atomic.SwapInt32(&globalPoison, 1) == 0 { @@ -115,6 +199,45 @@ func getFromHandle[T any](ptr unsafe.Pointer) *T { return cgo.Handle((uintptr)(*hptr)).Value().(*T) } +func exportStringOption(val string, out *C.char, length *C.size_t) C.AdbcStatusCode { + lenWithTerminator := C.size_t(len(val) + 1) + if lenWithTerminator <= *length { + sink := fromCArr[byte]((*byte)(unsafe.Pointer(out)), int(*length)) + copy(sink, val) + sink[lenWithTerminator] = 0 + } + *length = lenWithTerminator + return C.ADBC_STATUS_OK +} + +func exportBytesOption(val []byte, out *C.uint8_t, length *C.size_t) C.AdbcStatusCode { + if C.size_t(len(val)) <= *length { + sink := fromCArr[byte]((*byte)(out), int(*length)) + copy(sink, val) + } + *length = C.size_t(len(val)) + return C.ADBC_STATUS_OK +} + +type cancellableContext struct { + ctx context.Context + cancel context.CancelFunc +} + +func (c *cancellableContext) newContext() context.Context { + c.cancelContext() + c.ctx, c.cancel = context.WithCancel(context.Background()) + return c.ctx +} + +func (c *cancellableContext) cancelContext() { + if c.cancel != nil { + c.cancel() + } + c.ctx = nil + c.cancel = nil +} + func checkDBAlloc(db *C.struct_AdbcDatabase, err *C.struct_AdbcError, fname string) bool { if atomic.LoadInt32(&globalPoison) != 0 { setErr(err, "%s: Go panicked, driver is in unknown state", fname) @@ -145,47 +268,122 @@ func checkDBInit(db *C.struct_AdbcDatabase, err *C.struct_AdbcError, fname strin } type cDatabase struct { + errorDetails + opts map[string]string db adbc.Database } -//export {{.Prefix}}DatabaseNew -func {{.Prefix}}DatabaseNew(db *C.struct_AdbcDatabase, err *C.struct_AdbcError) (code C.AdbcStatusCode) { +//export {{.Prefix}}DatabaseGetOption +func {{.Prefix}}DatabaseGetOption(db *C.struct_AdbcDatabase, key *C.cchar_t, value *C.char, length *C.size_t, err *C.struct_AdbcError) (code C.AdbcStatusCode) { defer func() { if e := recover(); e != nil { - code = poison(err, "AdbcDatabaseNew", e) + code = poison(err, "AdbcDatabaseGetOption", e) } }() - if atomic.LoadInt32(&globalPoison) != 0 { - setErr(err, "AdbcDatabaseNew: Go panicked, driver is in unknown state") - return C.ADBC_STATUS_INTERNAL + cdb := checkDBInit(db, err, "AdbcDatabaseGetOption") + if cdb == nil { + return C.ADBC_STATUS_INVALID_STATE } - if db.private_data != nil { - setErr(err, "AdbcDatabaseNew: database already allocated") + + if handled, e := cdb.getErrorDetailsOptionString(key, value, length); e != nil { + return C.AdbcStatusCode(errToAdbcErr(err, e)) + } else if handled { + return C.ADBC_STATUS_OK + } + + opts, ok := cdb.db.(adbc.GetSetOptions) + if !ok { + setErr(err, "AdbcDatabaseGetOption: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + val, e := opts.GetOption(C.GoString(key)) + if e != nil { + return C.AdbcStatusCode(cdb.errToAdbcErr(err, e)) + } + + return exportStringOption(val, value, length) +} + +//export {{.Prefix}}DatabaseGetOptionBytes +func {{.Prefix}}DatabaseGetOptionBytes(db *C.struct_AdbcDatabase, key *C.cchar_t, value *C.uint8_t, length *C.size_t, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcDatabaseGetOptionBytes", e) + } + }() + cdb := checkDBInit(db, err, "AdbcDatabaseGetOptionBytes") + if cdb == nil { return C.ADBC_STATUS_INVALID_STATE } - dbobj := &cDatabase{opts: make(map[string]string)} - hndl := cgo.NewHandle(dbobj) - db.private_data = createHandle(hndl) - return C.ADBC_STATUS_OK + + if handled, e := cdb.getErrorDetailsOptionBytes(key, value, length); e != nil { + return C.AdbcStatusCode(errToAdbcErr(err, e)) + } else if handled { + return C.ADBC_STATUS_OK + } + + opts, ok := cdb.db.(adbc.GetSetOptions) + if !ok { + setErr(err, "AdbcDatabaseGetOptionBytes: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + val, e := opts.GetOptionBytes(C.GoString(key)) + if e != nil { + return C.AdbcStatusCode(cdb.errToAdbcErr(err, e)) + } + + return exportBytesOption(val, value, length) } -//export {{.Prefix}}DatabaseSetOption -func {{.Prefix}}DatabaseSetOption(db *C.struct_AdbcDatabase, key, value *C.cchar_t, err *C.struct_AdbcError) (code C.AdbcStatusCode) { +//export {{.Prefix}}DatabaseGetOptionDouble +func {{.Prefix}}DatabaseGetOptionDouble(db *C.struct_AdbcDatabase, key *C.cchar_t, value *C.double, err *C.struct_AdbcError) (code C.AdbcStatusCode) { defer func() { if e := recover(); e != nil { - code = poison(err, "AdbcDatabaseSetOption", e) + code = poison(err, "AdbcDatabaseGetOptionDouble", e) } }() - if !checkDBAlloc(db, err, "AdbcDatabaseSetOption") { + cdb := checkDBInit(db, err, "AdbcDatabaseGetOptionDouble") + if cdb == nil { return C.ADBC_STATUS_INVALID_STATE } - cdb := getFromHandle[cDatabase](db.private_data) - k, v := C.GoString(key), C.GoString(value) - cdb.opts[k] = v + opts, ok := cdb.db.(adbc.GetSetOptions) + if !ok { + setErr(err, "AdbcDatabaseGetOptionDouble: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } - return C.ADBC_STATUS_OK + val, e := opts.GetOptionDouble(C.GoString(key)) + *value = C.double(val) + return C.AdbcStatusCode(cdb.errToAdbcErr(err, e)) +} + +//export {{.Prefix}}DatabaseGetOptionInt +func {{.Prefix}}DatabaseGetOptionInt(db *C.struct_AdbcDatabase, key *C.cchar_t, value *C.int64_t, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcDatabaseGetOptionInt", e) + } + }() + cdb := checkDBInit(db, err, "AdbcDatabaseGetOptionInt") + if cdb == nil { + return C.ADBC_STATUS_INVALID_STATE + } + + if cdb.getErrorDetailsOptionInt(key, value) { + return C.ADBC_STATUS_OK + } + + opts, ok := cdb.db.(adbc.GetSetOptions) + if !ok { + setErr(err, "AdbcDatabaseGetOptionInt: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + + val, e := opts.GetOptionInt(C.GoString(key)) + *value = C.int64_t(val) + return C.AdbcStatusCode(cdb.errToAdbcErr(err, e)) } //export {{.Prefix}}DatabaseInit @@ -214,6 +412,27 @@ func {{.Prefix}}DatabaseInit(db *C.struct_AdbcDatabase, err *C.struct_AdbcError) return C.ADBC_STATUS_OK } +//export {{.Prefix}}DatabaseNew +func {{.Prefix}}DatabaseNew(db *C.struct_AdbcDatabase, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcDatabaseNew", e) + } + }() + if atomic.LoadInt32(&globalPoison) != 0 { + setErr(err, "AdbcDatabaseNew: Go panicked, driver is in unknown state") + return C.ADBC_STATUS_INTERNAL + } + if db.private_data != nil { + setErr(err, "AdbcDatabaseNew: database already allocated") + return C.ADBC_STATUS_INVALID_STATE + } + dbobj := &cDatabase{opts: make(map[string]string)} + hndl := cgo.NewHandle(dbobj) + db.private_data = createHandle(hndl) + return C.ADBC_STATUS_OK +} + //export {{.Prefix}}DatabaseRelease func {{.Prefix}}DatabaseRelease(db *C.struct_AdbcDatabase, err *C.struct_AdbcError) (code C.AdbcStatusCode) { defer func() { @@ -242,7 +461,100 @@ func {{.Prefix}}DatabaseRelease(db *C.struct_AdbcDatabase, err *C.struct_AdbcErr return C.ADBC_STATUS_OK } +//export {{.Prefix}}DatabaseSetOption +func {{.Prefix}}DatabaseSetOption(db *C.struct_AdbcDatabase, key, value *C.cchar_t, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcDatabaseSetOption", e) + } + }() + if !checkDBAlloc(db, err, "AdbcDatabaseSetOption") { + return C.ADBC_STATUS_INVALID_STATE + } + cdb := getFromHandle[cDatabase](db.private_data) + + k, v := C.GoString(key), C.GoString(value) + if cdb.db != nil { + opts, ok := cdb.db.(adbc.PostInitOptions) + if !ok { + setErr(err, "AdbcDatabaseSetOption: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + return C.AdbcStatusCode(cdb.errToAdbcErr(err, opts.SetOption(k, v))) + } else { + cdb.opts[k] = v + } + + return C.ADBC_STATUS_OK +} + +//export {{.Prefix}}DatabaseSetOptionBytes +func {{.Prefix}}DatabaseSetOptionBytes(db *C.struct_AdbcDatabase, key *C.cchar_t, value *C.cuint8_t, length C.size_t, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcDatabaseSetOptionBytes", e) + } + }() + cdb := checkDBInit(db, err, "AdbcDatabaseSetOptionBytes") + if cdb == nil { + return C.ADBC_STATUS_INVALID_STATE + } + + opts, ok := cdb.db.(adbc.GetSetOptions) + if !ok { + setErr(err, "AdbcDatabaseSetOptionBytes: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + + return C.AdbcStatusCode(cdb.errToAdbcErr(err, opts.SetOptionBytes(C.GoString(key), fromCArr[byte](value, int(length))))) +} + +//export {{.Prefix}}DatabaseSetOptionDouble +func {{.Prefix}}DatabaseSetOptionDouble(db *C.struct_AdbcDatabase, key *C.cchar_t, value C.double, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcDatabaseSetOptionDouble", e) + } + }() + cdb := checkDBInit(db, err, "AdbcDatabaseSetOptionDouble") + if cdb == nil { + return C.ADBC_STATUS_INVALID_STATE + } + + opts, ok := cdb.db.(adbc.GetSetOptions) + if !ok { + setErr(err, "AdbcDatabaseSetOptionDouble: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + + return C.AdbcStatusCode(cdb.errToAdbcErr(err, opts.SetOptionDouble(C.GoString(key), float64(value)))) +} + +//export {{.Prefix}}DatabaseSetOptionInt +func {{.Prefix}}DatabaseSetOptionInt(db *C.struct_AdbcDatabase, key *C.cchar_t, value C.int64_t, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcDatabaseSetOptionInt", e) + } + }() + cdb := checkDBInit(db, err, "AdbcDatabaseSetOptionInt") + if cdb == nil { + return C.ADBC_STATUS_INVALID_STATE + } + + opts, ok := cdb.db.(adbc.GetSetOptions) + if !ok { + setErr(err, "AdbcDatabaseSetOptionInt: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + + return C.AdbcStatusCode(cdb.errToAdbcErr(err, opts.SetOptionInt(C.GoString(key), int64(value)))) +} + type cConn struct { + cancellableContext + errorDetails + cnxn adbc.Connection initArgs map[string]string } @@ -276,6 +588,118 @@ func checkConnInit(cnxn *C.struct_AdbcConnection, err *C.struct_AdbcError, fname return conn } +//export {{.Prefix}}ConnectionGetOption +func {{.Prefix}}ConnectionGetOption(db *C.struct_AdbcConnection, key *C.cchar_t, value *C.char, length *C.size_t, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcConnectionGetOption", e) + } + }() + conn := checkConnInit(db, err, "AdbcConnectionGetOption") + if conn == nil { + return C.ADBC_STATUS_INVALID_STATE + } + + if handled, e := conn.getErrorDetailsOptionString(key, value, length); e != nil { + return C.AdbcStatusCode(errToAdbcErr(err, e)) + } else if handled { + return C.ADBC_STATUS_OK + } + + opts, ok := conn.cnxn.(adbc.GetSetOptions) + if !ok { + setErr(err, "AdbcConnectionGetOption: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + val, e := opts.GetOption(C.GoString(key)) + if e != nil { + return C.AdbcStatusCode(conn.errToAdbcErr(err, e)) + } + + return exportStringOption(val, value, length) +} + +//export {{.Prefix}}ConnectionGetOptionBytes +func {{.Prefix}}ConnectionGetOptionBytes(db *C.struct_AdbcConnection, key *C.cchar_t, value *C.uint8_t, length *C.size_t, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcConnectionGetOptionBytes", e) + } + }() + conn := checkConnInit(db, err, "AdbcConnectionGetOptionBytes") + if conn == nil { + return C.ADBC_STATUS_INVALID_STATE + } + + if handled, e := conn.getErrorDetailsOptionBytes(key, value, length); e != nil { + return C.AdbcStatusCode(errToAdbcErr(err, e)) + } else if handled { + return C.ADBC_STATUS_OK + } + + opts, ok := conn.cnxn.(adbc.GetSetOptions) + if !ok { + setErr(err, "AdbcConnectionGetOptionBytes: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + val, e := opts.GetOptionBytes(C.GoString(key)) + if e != nil { + return C.AdbcStatusCode(conn.errToAdbcErr(err, e)) + } + + return exportBytesOption(val, value, length) +} + +//export {{.Prefix}}ConnectionGetOptionDouble +func {{.Prefix}}ConnectionGetOptionDouble(db *C.struct_AdbcConnection, key *C.cchar_t, value *C.double, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcConnectionGetOptionDouble", e) + } + }() + conn := checkConnInit(db, err, "AdbcConnectionGetOptionDouble") + if conn == nil { + return C.ADBC_STATUS_INVALID_STATE + } + + opts, ok := conn.cnxn.(adbc.GetSetOptions) + if !ok { + setErr(err, "AdbcConnectionGetOptionDouble: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + + val, e := opts.GetOptionDouble(C.GoString(key)) + *value = C.double(val) + return C.AdbcStatusCode(conn.errToAdbcErr(err, e)) +} + +//export {{.Prefix}}ConnectionGetOptionInt +func {{.Prefix}}ConnectionGetOptionInt(db *C.struct_AdbcConnection, key *C.cchar_t, value *C.int64_t, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcConnectionGetOptionInt", e) + } + }() + conn := checkConnInit(db, err, "AdbcConnectionGetOptionInt") + if conn == nil { + return C.ADBC_STATUS_INVALID_STATE + } + + if conn.getErrorDetailsOptionInt(key, value) { + return C.ADBC_STATUS_OK + } + + opts, ok := conn.cnxn.(adbc.GetSetOptions) + if !ok { + setErr(err, "AdbcConnectionGetOptionInt: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + + val, e := opts.GetOptionInt(C.GoString(key)) + *value = C.int64_t(val) + return C.AdbcStatusCode(conn.errToAdbcErr(err, e)) +} + //export {{.Prefix}}ConnectionNew func {{.Prefix}}ConnectionNew(cnxn *C.struct_AdbcConnection, err *C.struct_AdbcError) (code C.AdbcStatusCode) { defer func() { @@ -324,8 +748,70 @@ func {{.Prefix}}ConnectionSetOption(cnxn *C.struct_AdbcConnection, key, val *C.c setErr(err, "AdbcConnectionSetOption: not supported post-init") return C.ADBC_STATUS_NOT_IMPLEMENTED } - rawCode := errToAdbcErr(err, opts.SetOption(C.GoString(key), C.GoString(val))) - return C.AdbcStatusCode(rawCode) + return C.AdbcStatusCode(conn.errToAdbcErr(err, opts.SetOption(C.GoString(key), C.GoString(val)))) +} + +//export {{.Prefix}}ConnectionSetOptionBytes +func {{.Prefix}}ConnectionSetOptionBytes(db *C.struct_AdbcConnection, key *C.cchar_t, value *C.cuint8_t, length C.size_t, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcConnectionSetOptionBytes", e) + } + }() + conn := checkConnInit(db, err, "AdbcConnectionSetOptionBytes") + if conn == nil { + return C.ADBC_STATUS_INVALID_STATE + } + + opts, ok := conn.cnxn.(adbc.GetSetOptions) + if !ok { + setErr(err, "AdbcConnectionSetOptionBytes: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + + return C.AdbcStatusCode(conn.errToAdbcErr(err, opts.SetOptionBytes(C.GoString(key), fromCArr[byte](value, int(length))))) +} + +//export {{.Prefix}}ConnectionSetOptionDouble +func {{.Prefix}}ConnectionSetOptionDouble(db *C.struct_AdbcConnection, key *C.cchar_t, value C.double, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcConnectionSetOptionDouble", e) + } + }() + conn := checkConnInit(db, err, "AdbcConnectionSetOptionDouble") + if conn == nil { + return C.ADBC_STATUS_INVALID_STATE + } + + opts, ok := conn.cnxn.(adbc.GetSetOptions) + if !ok { + setErr(err, "AdbcConnectionSetOptionDouble: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + + return C.AdbcStatusCode(conn.errToAdbcErr(err, opts.SetOptionDouble(C.GoString(key), float64(value)))) +} + +//export {{.Prefix}}ConnectionSetOptionInt +func {{.Prefix}}ConnectionSetOptionInt(db *C.struct_AdbcConnection, key *C.cchar_t, value C.int64_t, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcConnectionSetOptionInt", e) + } + }() + conn := checkConnInit(db, err, "AdbcConnectionSetOptionInt") + if conn == nil { + return C.ADBC_STATUS_INVALID_STATE + } + + opts, ok := conn.cnxn.(adbc.GetSetOptions) + if !ok { + setErr(err, "AdbcConnectionSetOptionInt: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + + return C.AdbcStatusCode(conn.errToAdbcErr(err, opts.SetOptionInt(C.GoString(key), int64(value)))) } //export {{.Prefix}}ConnectionInit @@ -350,7 +836,7 @@ func {{.Prefix}}ConnectionInit(cnxn *C.struct_AdbcConnection, db *C.struct_AdbcD } c, e := cdb.db.Open(context.Background()) if e != nil { - return C.AdbcStatusCode(errToAdbcErr(err, e)) + return C.AdbcStatusCode(cdb.errToAdbcErr(err, e)) } conn.cnxn = c @@ -363,7 +849,7 @@ func {{.Prefix}}ConnectionInit(cnxn *C.struct_AdbcConnection, db *C.struct_AdbcD } for k, v := range conn.initArgs { - rawCode := errToAdbcErr(err, opts.SetOption(k, v)) + rawCode := conn.errToAdbcErr(err, opts.SetOption(k, v)) if rawCode != adbc.StatusOK { return C.AdbcStatusCode(rawCode) } @@ -388,8 +874,9 @@ func {{.Prefix}}ConnectionRelease(cnxn *C.struct_AdbcConnection, err *C.struct_A conn := h.Value().(*cConn) defer func() { + conn.cancelContext() conn.cnxn = nil - C.free(unsafe.Pointer(cnxn.private_data)) + C.free(cnxn.private_data) cnxn.private_data = nil h.Delete() // manually trigger GC for two reasons: @@ -426,6 +913,46 @@ func toCdataArray(ptr *C.struct_ArrowArray) *cdata.CArrowArray { return (*cdata.CArrowArray)(unsafe.Pointer(ptr)) } +//export {{.Prefix}}ConnectionCancel +func {{.Prefix}}ConnectionCancel(cnxn *C.struct_AdbcConnection, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcConnectionCancel", e) + } + }() + conn := checkConnInit(cnxn, err, "AdbcConnectionCancel") + if conn == nil { + return C.ADBC_STATUS_INVALID_STATE + } + + conn.cancelContext() + return C.ADBC_STATUS_OK +} + +func toStrPtr(in *C.cchar_t) *string { + if in == nil { + return nil + } + + out := C.GoString((*C.char)(in)) + return &out +} + +func toStrSlice(in **C.cchar_t) []string { + if in == nil { + return nil + } + + sz := unsafe.Sizeof(*in) + + out := make([]string, 0, 1) + for *in != nil { + out = append(out, C.GoString(*in)) + in = (**C.cchar_t)(unsafe.Add(unsafe.Pointer(in), sz)) + } + return out +} + //export {{.Prefix}}ConnectionGetInfo func {{.Prefix}}ConnectionGetInfo(cnxn *C.struct_AdbcConnection, codes *C.uint32_t, len C.size_t, out *C.struct_ArrowArrayStream, err *C.struct_AdbcError) (code C.AdbcStatusCode) { defer func() { @@ -439,56 +966,86 @@ func {{.Prefix}}ConnectionGetInfo(cnxn *C.struct_AdbcConnection, codes *C.uint32 } infoCodes := fromCArr[adbc.InfoCode](codes, int(len)) - rdr, e := conn.cnxn.GetInfo(context.Background(), infoCodes) + rdr, e := conn.cnxn.GetInfo(conn.newContext(), infoCodes) if e != nil { - return C.AdbcStatusCode(errToAdbcErr(err, e)) + return C.AdbcStatusCode(conn.errToAdbcErr(err, e)) } cdata.ExportRecordReader(rdr, toCdataStream(out)) return C.ADBC_STATUS_OK } -func toStrPtr(in *C.cchar_t) *string { - if in == nil { - return nil +//export {{.Prefix}}ConnectionGetObjects +func {{.Prefix}}ConnectionGetObjects(cnxn *C.struct_AdbcConnection, depth C.int, catalog, dbSchema, tableName *C.cchar_t, tableType **C.cchar_t, columnName *C.cchar_t, + out *C.struct_ArrowArrayStream, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcConnectionGetObjects", e) + } + }() + conn := checkConnInit(cnxn, err, "AdbcConnectionGetObjects") + if conn == nil { + return C.ADBC_STATUS_INVALID_STATE } - out := C.GoString((*C.char)(in)) - return &out + rdr, e := conn.cnxn.GetObjects(conn.newContext(), adbc.ObjectDepth(depth), toStrPtr(catalog), toStrPtr(dbSchema), toStrPtr(tableName), toStrPtr(columnName), toStrSlice(tableType)) + if e != nil { + return C.AdbcStatusCode(conn.errToAdbcErr(err, e)) + } + cdata.ExportRecordReader(rdr, toCdataStream(out)) + return C.ADBC_STATUS_OK } -func toStrSlice(in **C.cchar_t) []string { - if in == nil { - return nil +//export {{.Prefix}}ConnectionGetStatistics +func {{.Prefix}}ConnectionGetStatistics(cnxn *C.struct_AdbcConnection, catalog, dbSchema, tableName *C.cchar_t, approximate C.char, out *C.struct_ArrowArrayStream, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcConnectionGetStatistics", e) + } + }() + conn := checkConnInit(cnxn, err, "AdbcConnectionGetStatistics") + if conn == nil { + return C.ADBC_STATUS_INVALID_STATE } - sz := unsafe.Sizeof(*in) + gs, ok := conn.cnxn.(adbc.ConnectionGetStatistics) + if !ok { + setErr(err, "AdbcConnectionGetStatistics: not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } - out := make([]string, 0, 1) - for *in != nil { - out = append(out, C.GoString(*in)) - in = (**C.cchar_t)(unsafe.Add(unsafe.Pointer(in), sz)) + rdr, e := gs.GetStatistics(conn.newContext(), toStrPtr(catalog), toStrPtr(dbSchema), toStrPtr(tableName), int(approximate) != 0) + if e != nil { + return C.AdbcStatusCode(conn.errToAdbcErr(err, e)) } - return out + + cdata.ExportRecordReader(rdr, toCdataStream(out)) + return C.ADBC_STATUS_OK } -//export {{.Prefix}}ConnectionGetObjects -func {{.Prefix}}ConnectionGetObjects(cnxn *C.struct_AdbcConnection, depth C.int, catalog, dbSchema, tableName *C.cchar_t, tableType **C.cchar_t, columnName *C.cchar_t, - out *C.struct_ArrowArrayStream, err *C.struct_AdbcError) (code C.AdbcStatusCode) { +//export {{.Prefix}}ConnectionGetStatisticNames +func {{.Prefix}}ConnectionGetStatisticNames(cnxn *C.struct_AdbcConnection, out *C.struct_ArrowArrayStream, err *C.struct_AdbcError) (code C.AdbcStatusCode) { defer func() { if e := recover(); e != nil { - code = poison(err, "AdbcConnectionGetObjects", e) + code = poison(err, "AdbcConnectionGetStatistics", e) } }() - conn := checkConnInit(cnxn, err, "AdbcConnectionGetObjects") + conn := checkConnInit(cnxn, err, "AdbcConnectionGetStatistics") if conn == nil { return C.ADBC_STATUS_INVALID_STATE } - rdr, e := conn.cnxn.GetObjects(context.Background(), adbc.ObjectDepth(depth), toStrPtr(catalog), toStrPtr(dbSchema), toStrPtr(tableName), toStrPtr(columnName), toStrSlice(tableType)) + gs, ok := conn.cnxn.(adbc.ConnectionGetStatistics) + if !ok { + setErr(err, "AdbcConnectionGetStatistics: not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + + rdr, e := gs.GetStatisticNames(conn.newContext()) if e != nil { - return C.AdbcStatusCode(errToAdbcErr(err, e)) + return C.AdbcStatusCode(conn.errToAdbcErr(err, e)) } + cdata.ExportRecordReader(rdr, toCdataStream(out)) return C.ADBC_STATUS_OK } @@ -505,9 +1062,9 @@ func {{.Prefix}}ConnectionGetTableSchema(cnxn *C.struct_AdbcConnection, catalog, return C.ADBC_STATUS_INVALID_STATE } - sc, e := conn.cnxn.GetTableSchema(context.Background(), toStrPtr(catalog), toStrPtr(dbSchema), C.GoString(tableName)) + sc, e := conn.cnxn.GetTableSchema(conn.newContext(), toStrPtr(catalog), toStrPtr(dbSchema), C.GoString(tableName)) if e != nil { - return C.AdbcStatusCode(errToAdbcErr(err, e)) + return C.AdbcStatusCode(conn.errToAdbcErr(err, e)) } cdata.ExportArrowSchema(sc, toCdataSchema(schema)) return C.ADBC_STATUS_OK @@ -525,9 +1082,9 @@ func {{.Prefix}}ConnectionGetTableTypes(cnxn *C.struct_AdbcConnection, out *C.st return C.ADBC_STATUS_INVALID_STATE } - rdr, e := conn.cnxn.GetTableTypes(context.Background()) + rdr, e := conn.cnxn.GetTableTypes(conn.newContext()) if e != nil { - return C.AdbcStatusCode(errToAdbcErr(err, e)) + return C.AdbcStatusCode(conn.errToAdbcErr(err, e)) } cdata.ExportRecordReader(rdr, toCdataStream(out)) return C.ADBC_STATUS_OK @@ -545,9 +1102,9 @@ func {{.Prefix}}ConnectionReadPartition(cnxn *C.struct_AdbcConnection, serialize return C.ADBC_STATUS_INVALID_STATE } - rdr, e := conn.cnxn.ReadPartition(context.Background(), fromCArr[byte](serialized, int(serializedLen))) + rdr, e := conn.cnxn.ReadPartition(conn.newContext(), fromCArr[byte](serialized, int(serializedLen))) if e != nil { - return C.AdbcStatusCode(errToAdbcErr(err, e)) + return C.AdbcStatusCode(conn.errToAdbcErr(err, e)) } cdata.ExportRecordReader(rdr, toCdataStream(out)) return C.ADBC_STATUS_OK @@ -565,7 +1122,7 @@ func {{.Prefix}}ConnectionCommit(cnxn *C.struct_AdbcConnection, err *C.struct_Ad return C.ADBC_STATUS_INVALID_STATE } - return C.AdbcStatusCode(errToAdbcErr(err, conn.cnxn.Commit(context.Background()))) + return C.AdbcStatusCode(conn.errToAdbcErr(err, conn.cnxn.Commit(conn.newContext()))) } //export {{.Prefix}}ConnectionRollback @@ -580,25 +1137,154 @@ func {{.Prefix}}ConnectionRollback(cnxn *C.struct_AdbcConnection, err *C.struct_ return C.ADBC_STATUS_INVALID_STATE } - return C.AdbcStatusCode(errToAdbcErr(err, conn.cnxn.Rollback(context.Background()))) + return C.AdbcStatusCode(conn.errToAdbcErr(err, conn.cnxn.Rollback(conn.newContext()))) +} + +type cStmt struct { + cancellableContext + errorDetails + + stmt adbc.Statement } -func checkStmtInit(stmt *C.struct_AdbcStatement, err *C.struct_AdbcError, fname string) adbc.Statement { +func checkStmtAlloc(stmt *C.struct_AdbcStatement, err *C.struct_AdbcError, fname string) bool { if atomic.LoadInt32(&globalPoison) != 0 { setErr(err, "%s: Go panicked, driver is in unknown state", fname) - return nil + return false } if stmt == nil { setErr(err, "%s: statement not allocated", fname) - return nil + return false } - if stmt.private_data == nil { - setErr(err, "%s: statement not initialized", fname) + setErr(err, "%s: statement not allocated", fname) + return false + } + return true +} + +func checkStmtInit(stmt *C.struct_AdbcStatement, err *C.struct_AdbcError, fname string) *cStmt { + if !checkStmtAlloc(stmt, err, fname) { + return nil + } + cStmt := getFromHandle[cStmt](stmt.private_data) + if cStmt.stmt == nil { + setErr(err, "%s: statement not allocated", fname) return nil } + return cStmt +} + +//export {{.Prefix}}StatementGetOption +func {{.Prefix}}StatementGetOption(db *C.struct_AdbcStatement, key *C.cchar_t, value *C.char, length *C.size_t, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcStatementGetOption", e) + } + }() + st := checkStmtInit(db, err, "AdbcStatementGetOption") + if st == nil { + return C.ADBC_STATUS_INVALID_STATE + } + + if handled, e := st.getErrorDetailsOptionString(key, value, length); e != nil { + return C.AdbcStatusCode(errToAdbcErr(err, e)) + } else if handled { + return C.ADBC_STATUS_OK + } + + opts, ok := st.stmt.(adbc.GetSetOptions) + if !ok { + setErr(err, "AdbcStatementGetOption: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + val, e := opts.GetOption(C.GoString(key)) + if e != nil { + return C.AdbcStatusCode(st.errToAdbcErr(err, e)) + } + + return exportStringOption(val, value, length) +} + +//export {{.Prefix}}StatementGetOptionBytes +func {{.Prefix}}StatementGetOptionBytes(db *C.struct_AdbcStatement, key *C.cchar_t, value *C.uint8_t, length *C.size_t, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcStatementGetOptionBytes", e) + } + }() + st := checkStmtInit(db, err, "AdbcStatementGetOptionBytes") + if st == nil { + return C.ADBC_STATUS_INVALID_STATE + } + + if handled, e := st.getErrorDetailsOptionBytes(key, value, length); e != nil { + return C.AdbcStatusCode(errToAdbcErr(err, e)) + } else if handled { + return C.ADBC_STATUS_OK + } + + opts, ok := st.stmt.(adbc.GetSetOptions) + if !ok { + setErr(err, "AdbcStatementGetOptionBytes: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + val, e := opts.GetOptionBytes(C.GoString(key)) + if e != nil { + return C.AdbcStatusCode(st.errToAdbcErr(err, e)) + } + + return exportBytesOption(val, value, length) +} + +//export {{.Prefix}}StatementGetOptionDouble +func {{.Prefix}}StatementGetOptionDouble(db *C.struct_AdbcStatement, key *C.cchar_t, value *C.double, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcStatementGetOptionDouble", e) + } + }() + st := checkStmtInit(db, err, "AdbcStatementGetOptionDouble") + if st == nil { + return C.ADBC_STATUS_INVALID_STATE + } + + opts, ok := st.stmt.(adbc.GetSetOptions) + if !ok { + setErr(err, "AdbcStatementGetOptionDouble: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + + val, e := opts.GetOptionDouble(C.GoString(key)) + *value = C.double(val) + return C.AdbcStatusCode(st.errToAdbcErr(err, e)) +} + +//export {{.Prefix}}StatementGetOptionInt +func {{.Prefix}}StatementGetOptionInt(db *C.struct_AdbcStatement, key *C.cchar_t, value *C.int64_t, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcStatementGetOptionInt", e) + } + }() + st := checkStmtInit(db, err, "AdbcStatementGetOptionInt") + if st == nil { + return C.ADBC_STATUS_INVALID_STATE + } + + if st.getErrorDetailsOptionInt(key, value) { + return C.ADBC_STATUS_OK + } - return (*(*cgo.Handle)(stmt.private_data)).Value().(adbc.Statement) + opts, ok := st.stmt.(adbc.GetSetOptions) + if !ok { + setErr(err, "AdbcStatementGetOptionInt: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + + val, e := opts.GetOptionInt(C.GoString(key)) + *value = C.int64_t(val) + return C.AdbcStatusCode(st.errToAdbcErr(err, e)) } //export {{.Prefix}}StatementNew @@ -619,11 +1305,11 @@ func {{.Prefix}}StatementNew(cnxn *C.struct_AdbcConnection, stmt *C.struct_AdbcS st, e := conn.cnxn.NewStatement() if e != nil { - return C.AdbcStatusCode(errToAdbcErr(err, e)) + return C.AdbcStatusCode(conn.errToAdbcErr(err, e)) } - h := cgo.NewHandle(st) - stmt.private_data = createHandle(h) + hndl := cgo.NewHandle(&cStmt{stmt: st}) + stmt.private_data = createHandle(hndl) return C.ADBC_STATUS_OK } @@ -638,31 +1324,46 @@ func {{.Prefix}}StatementRelease(stmt *C.struct_AdbcStatement, err *C.struct_Adb setErr(err, "AdbcStatementRelease: Go panicked, driver is in unknown state") return C.ADBC_STATUS_INTERNAL } - if stmt == nil { - setErr(err, "AdbcStatementRelease: statement not allocated") + if !checkStmtAlloc(stmt, err, "AdbcStatementRelease") { return C.ADBC_STATUS_INVALID_STATE } + h := (*(*cgo.Handle)(stmt.private_data)) - if stmt.private_data == nil { - setErr(err, "AdbcStatementRelease: statement not initialized") - return C.ADBC_STATUS_INVALID_STATE + st := h.Value().(*cStmt) + defer func() { + st.cancelContext() + st.stmt = nil + C.free(stmt.private_data) + stmt.private_data = nil + h.Delete() + // manually trigger GC for two reasons: + // 1. ASAN expects the release callback to be called before + // the process ends, but GC is not deterministic. So by manually + // triggering the GC we ensure the release callback gets called. + // 2. Creates deterministic GC behavior by all Release functions + // triggering a garbage collection + runtime.GC() + }() + if st.stmt == nil { + return C.ADBC_STATUS_OK } + return C.AdbcStatusCode(errToAdbcErr(err, st.stmt.Close())) +} - h := (*(*cgo.Handle)(stmt.private_data)) - st := h.Value().(adbc.Statement) - C.free(stmt.private_data) - stmt.private_data = nil +//export {{.Prefix}}StatementCancel +func {{.Prefix}}StatementCancel(stmt *C.struct_AdbcStatement, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcStatementPrepare", e) + } + }() + st := checkStmtInit(stmt, err, "AdbcStatementPrepare") + if st == nil { + return C.ADBC_STATUS_INVALID_STATE + } - e := st.Close() - h.Delete() - // manually trigger GC for two reasons: - // 1. ASAN expects the release callback to be called before - // the process ends, but GC is not deterministic. So by manually - // triggering the GC we ensure the release callback gets called. - // 2. Creates deterministic GC behavior by all Release functions - // triggering a garbage collection - runtime.GC() - return C.AdbcStatusCode(errToAdbcErr(err, e)) + st.cancelContext() + return C.ADBC_STATUS_OK } //export {{.Prefix}}StatementPrepare @@ -677,7 +1378,7 @@ func {{.Prefix}}StatementPrepare(stmt *C.struct_AdbcStatement, err *C.struct_Adb return C.ADBC_STATUS_INVALID_STATE } - return C.AdbcStatusCode(errToAdbcErr(err, st.Prepare(context.Background()))) + return C.AdbcStatusCode(st.errToAdbcErr(err, st.stmt.Prepare(st.newContext()))) } //export {{.Prefix}}StatementExecuteQuery @@ -693,29 +1394,57 @@ func {{.Prefix}}StatementExecuteQuery(stmt *C.struct_AdbcStatement, out *C.struc } if out == nil { - n, e := st.ExecuteUpdate(context.Background()) + n, e := st.stmt.ExecuteUpdate(st.newContext()) if e != nil { - return C.AdbcStatusCode(errToAdbcErr(err, e)) + return C.AdbcStatusCode(st.errToAdbcErr(err, e)) } if affected != nil { *affected = C.int64_t(n) } } else { - rdr, n, e := st.ExecuteQuery(context.Background()) + rdr, n, e := st.stmt.ExecuteQuery(st.newContext()) if e != nil { - return C.AdbcStatusCode(errToAdbcErr(err, e)) + return C.AdbcStatusCode(st.errToAdbcErr(err, e)) } if affected != nil { *affected = C.int64_t(n) } + // TODO: proxy the reader and record error details (and do this elsewhere, too) cdata.ExportRecordReader(rdr, toCdataStream(out)) } return C.ADBC_STATUS_OK } +//export {{.Prefix}}StatementExecuteSchema +func {{.Prefix}}StatementExecuteSchema(stmt *C.struct_AdbcStatement, schema *C.struct_ArrowSchema, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcStatementExecuteQuery", e) + } + }() + st := checkStmtInit(stmt, err, "AdbcStatementExecuteQuery") + if st == nil { + return C.ADBC_STATUS_INVALID_STATE + } + + es, ok := st.stmt.(adbc.StatementExecuteSchema) + if !ok { + setErr(err, "AdbcStatementExecuteSchema: not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + + sc, e := es.ExecuteSchema(st.newContext()) + if e != nil { + return C.AdbcStatusCode(st.errToAdbcErr(err, e)) + } + + cdata.ExportArrowSchema(sc, toCdataSchema(schema)) + return C.ADBC_STATUS_OK +} + //export {{.Prefix}}StatementSetSqlQuery func {{.Prefix}}StatementSetSqlQuery(stmt *C.struct_AdbcStatement, query *C.cchar_t, err *C.struct_AdbcError) (code C.AdbcStatusCode) { defer func() { @@ -728,7 +1457,7 @@ func {{.Prefix}}StatementSetSqlQuery(stmt *C.struct_AdbcStatement, query *C.ccha return C.ADBC_STATUS_INVALID_STATE } - return C.AdbcStatusCode(errToAdbcErr(err, st.SetSqlQuery(C.GoString(query)))) + return C.AdbcStatusCode(st.errToAdbcErr(err, st.stmt.SetSqlQuery(C.GoString(query)))) } //export {{.Prefix}}StatementSetSubstraitPlan @@ -743,7 +1472,7 @@ func {{.Prefix}}StatementSetSubstraitPlan(stmt *C.struct_AdbcStatement, plan *C. return C.ADBC_STATUS_INVALID_STATE } - return C.AdbcStatusCode(errToAdbcErr(err, st.SetSubstraitPlan(fromCArr[byte](plan, int(length))))) + return C.AdbcStatusCode(st.errToAdbcErr(err, st.stmt.SetSubstraitPlan(fromCArr[byte](plan, int(length))))) } //export {{.Prefix}}StatementBind @@ -762,11 +1491,11 @@ func {{.Prefix}}StatementBind(stmt *C.struct_AdbcStatement, values *C.struct_Arr if e != nil { // if there was an error, we need to manually release the input cdata.ReleaseCArrowArray(toCdataArray(values)) - return C.AdbcStatusCode(errToAdbcErr(err, e)) + return C.AdbcStatusCode(st.errToAdbcErr(err, e)) } defer rec.Release() - return C.AdbcStatusCode(errToAdbcErr(err, st.Bind(context.Background(), rec))) + return C.AdbcStatusCode(st.errToAdbcErr(err, st.stmt.Bind(st.newContext(), rec))) } //export {{.Prefix}}StatementBindStream @@ -783,9 +1512,9 @@ func {{.Prefix}}StatementBindStream(stmt *C.struct_AdbcStatement, stream *C.stru rdr, e := cdata.ImportCRecordReader(toCdataStream(stream), nil) if e != nil { - return C.AdbcStatusCode(errToAdbcErr(err, e)) + return C.AdbcStatusCode(st.errToAdbcErr(err, e)) } - return C.AdbcStatusCode(errToAdbcErr(err, st.BindStream(context.Background(), rdr.(array.RecordReader)))) + return C.AdbcStatusCode(st.errToAdbcErr(err, st.stmt.BindStream(st.newContext(), rdr.(array.RecordReader)))) } //export {{.Prefix}}StatementGetParameterSchema @@ -800,9 +1529,9 @@ func {{.Prefix}}StatementGetParameterSchema(stmt *C.struct_AdbcStatement, schema return C.ADBC_STATUS_INVALID_STATE } - sc, e := st.GetParameterSchema() + sc, e := st.stmt.GetParameterSchema() if e != nil { - return C.AdbcStatusCode(errToAdbcErr(err, e)) + return C.AdbcStatusCode(st.errToAdbcErr(err, e)) } cdata.ExportArrowSchema(sc, toCdataSchema(schema)) @@ -821,7 +1550,70 @@ func {{.Prefix}}StatementSetOption(stmt *C.struct_AdbcStatement, key, value *C.c return C.ADBC_STATUS_INVALID_STATE } - return C.AdbcStatusCode(errToAdbcErr(err, st.SetOption(C.GoString(key), C.GoString(value)))) + return C.AdbcStatusCode(st.errToAdbcErr(err, st.stmt.SetOption(C.GoString(key), C.GoString(value)))) +} + +//export {{.Prefix}}StatementSetOptionBytes +func {{.Prefix}}StatementSetOptionBytes(db *C.struct_AdbcStatement, key *C.cchar_t, value *C.cuint8_t, length C.size_t, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcStatementSetOptionBytes", e) + } + }() + st := checkStmtInit(db, err, "AdbcStatementSetOptionBytes") + if st == nil { + return C.ADBC_STATUS_INVALID_STATE + } + + opts, ok := st.stmt.(adbc.GetSetOptions) + if !ok { + setErr(err, "AdbcStatementSetOptionBytes: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + + return C.AdbcStatusCode(st.errToAdbcErr(err, opts.SetOptionBytes(C.GoString(key), fromCArr[byte](value, int(length))))) +} + +//export {{.Prefix}}StatementSetOptionDouble +func {{.Prefix}}StatementSetOptionDouble(db *C.struct_AdbcStatement, key *C.cchar_t, value C.double, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcStatementSetOptionDouble", e) + } + }() + st := checkStmtInit(db, err, "AdbcStatementSetOptionDouble") + if st == nil { + return C.ADBC_STATUS_INVALID_STATE + } + + opts, ok := st.stmt.(adbc.GetSetOptions) + if !ok { + setErr(err, "AdbcStatementSetOptionDouble: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + + return C.AdbcStatusCode(st.errToAdbcErr(err, opts.SetOptionDouble(C.GoString(key), float64(value)))) +} + +//export {{.Prefix}}StatementSetOptionInt +func {{.Prefix}}StatementSetOptionInt(db *C.struct_AdbcStatement, key *C.cchar_t, value C.int64_t, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcStatementSetOptionInt", e) + } + }() + st := checkStmtInit(db, err, "AdbcStatementSetOptionInt") + if st == nil { + return C.ADBC_STATUS_INVALID_STATE + } + + opts, ok := st.stmt.(adbc.GetSetOptions) + if !ok { + setErr(err, "AdbcStatementSetOptionInt: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + + return C.AdbcStatusCode(st.errToAdbcErr(err, opts.SetOptionInt(C.GoString(key), int64(value)))) } //export releasePartitions @@ -850,9 +1642,9 @@ func {{.Prefix}}StatementExecutePartitions(stmt *C.struct_AdbcStatement, schema return C.ADBC_STATUS_INVALID_STATE } - sc, part, n, e := st.ExecutePartitions(context.Background()) + sc, part, n, e := st.stmt.ExecutePartitions(st.newContext()) if e != nil { - return C.AdbcStatusCode(errToAdbcErr(err, e)) + return C.AdbcStatusCode(st.errToAdbcErr(err, e)) } if partitions == nil { @@ -895,13 +1687,20 @@ func {{.Prefix}}StatementExecutePartitions(stmt *C.struct_AdbcStatement, schema //export {{.Prefix}}DriverInit func {{.Prefix}}DriverInit(version C.int, rawDriver *C.void, err *C.struct_AdbcError) C.AdbcStatusCode { - if version != C.ADBC_VERSION_1_0_0 { - setErr(err, "Only version %d supported, got %d", int(C.ADBC_VERSION_1_0_0), int(version)) + driver := (*C.struct_AdbcDriver)(unsafe.Pointer(rawDriver)) + + switch version { + case C.ADBC_VERSION_1_0_0: + sink := fromCArr[byte]((*byte)(unsafe.Pointer(driver)), C.ADBC_DRIVER_1_0_0_SIZE) + memory.Set(sink, 0) + case C.ADBC_VERSION_1_1_0: + sink := fromCArr[byte]((*byte)(unsafe.Pointer(driver)), C.ADBC_DRIVER_1_1_0_SIZE) + memory.Set(sink, 0) + default: + setErr(err, "Only version 1.0.0/1.1.0 supported, got %d", int(version)) return C.ADBC_STATUS_NOT_IMPLEMENTED } - driver := (*C.struct_AdbcDriver)(unsafe.Pointer(rawDriver)) - C.memset(unsafe.Pointer(driver), 0, C.ADBC_DRIVER_1_0_0_SIZE) driver.DatabaseInit = (*[0]byte)(C.{{.Prefix}}DatabaseInit) driver.DatabaseNew = (*[0]byte)(C.{{.Prefix}}DatabaseNew) driver.DatabaseRelease = (*[0]byte)(C.{{.Prefix}}DatabaseRelease) @@ -931,6 +1730,37 @@ func {{.Prefix}}DriverInit(version C.int, rawDriver *C.void, err *C.struct_AdbcE driver.StatementGetParameterSchema = (*[0]byte)(C.{{.Prefix}}StatementGetParameterSchema) driver.StatementPrepare = (*[0]byte)(C.{{.Prefix}}StatementPrepare) + if version == C.ADBC_VERSION_1_1_0 { + driver.DatabaseGetOption = (*[0]byte)(C.{{.Prefix}}DatabaseGetOption) + driver.DatabaseGetOptionBytes = (*[0]byte)(C.{{.Prefix}}DatabaseGetOptionBytes) + driver.DatabaseGetOptionDouble = (*[0]byte)(C.{{.Prefix}}DatabaseGetOptionDouble) + driver.DatabaseGetOptionInt = (*[0]byte)(C.{{.Prefix}}DatabaseGetOptionInt) + driver.DatabaseSetOptionBytes = (*[0]byte)(C.{{.Prefix}}DatabaseSetOptionBytes) + driver.DatabaseSetOptionDouble = (*[0]byte)(C.{{.Prefix}}DatabaseSetOptionDouble) + driver.DatabaseSetOptionInt = (*[0]byte)(C.{{.Prefix}}DatabaseSetOptionInt) + + driver.ConnectionCancel = (*[0]byte)(C.{{.Prefix}}ConnectionCancel) + driver.ConnectionGetOption = (*[0]byte)(C.{{.Prefix}}ConnectionGetOption) + driver.ConnectionGetOptionBytes = (*[0]byte)(C.{{.Prefix}}ConnectionGetOptionBytes) + driver.ConnectionGetOptionDouble = (*[0]byte)(C.{{.Prefix}}ConnectionGetOptionDouble) + driver.ConnectionGetOptionInt = (*[0]byte)(C.{{.Prefix}}ConnectionGetOptionInt) + driver.ConnectionGetStatistics = (*[0]byte)(C.{{.Prefix}}ConnectionGetStatistics) + driver.ConnectionGetStatisticNames = (*[0]byte)(C.{{.Prefix}}ConnectionGetStatisticNames) + driver.ConnectionSetOptionBytes = (*[0]byte)(C.{{.Prefix}}ConnectionSetOptionBytes) + driver.ConnectionSetOptionDouble = (*[0]byte)(C.{{.Prefix}}ConnectionSetOptionDouble) + driver.ConnectionSetOptionInt = (*[0]byte)(C.{{.Prefix}}ConnectionSetOptionInt) + + driver.StatementCancel = (*[0]byte)(C.{{.Prefix}}StatementCancel) + driver.StatementExecuteSchema = (*[0]byte)(C.{{.Prefix}}StatementExecuteSchema) + driver.StatementGetOption = (*[0]byte)(C.{{.Prefix}}StatementGetOption) + driver.StatementGetOptionBytes = (*[0]byte)(C.{{.Prefix}}StatementGetOptionBytes) + driver.StatementGetOptionDouble = (*[0]byte)(C.{{.Prefix}}StatementGetOptionDouble) + driver.StatementGetOptionInt = (*[0]byte)(C.{{.Prefix}}StatementGetOptionInt) + driver.StatementSetOptionBytes = (*[0]byte)(C.{{.Prefix}}StatementSetOptionBytes) + driver.StatementSetOptionDouble = (*[0]byte)(C.{{.Prefix}}StatementSetOptionDouble) + driver.StatementSetOptionInt = (*[0]byte)(C.{{.Prefix}}StatementSetOptionInt) + } + return C.ADBC_STATUS_OK } diff --git a/go/adbc/pkg/_tmpl/utils.h.tmpl b/go/adbc/pkg/_tmpl/utils.h.tmpl index a8c7d973f6..235a21631f 100644 --- a/go/adbc/pkg/_tmpl/utils.h.tmpl +++ b/go/adbc/pkg/_tmpl/utils.h.tmpl @@ -24,32 +24,61 @@ #include "../../drivermgr/adbc.h" #include -AdbcStatusCode {{.Prefix}}DatabaseNew(struct AdbcDatabase* db, struct AdbcError* err); -AdbcStatusCode {{.Prefix}}DatabaseSetOption(struct AdbcDatabase* db, const char* key, const char* value, struct AdbcError* err); +AdbcStatusCode {{.Prefix}}DatabaseGetOption(struct AdbcDatabase*, const char*, char*, size_t*, struct AdbcError*); +AdbcStatusCode {{.Prefix}}DatabaseGetOptionBytes(struct AdbcDatabase*, const char*, uint8_t*, size_t*, struct AdbcError*); +AdbcStatusCode {{.Prefix}}DatabaseGetOptionDouble(struct AdbcDatabase*, const char*, double*, struct AdbcError*); +AdbcStatusCode {{.Prefix}}DatabaseGetOptionInt(struct AdbcDatabase*, const char*, int64_t*, struct AdbcError*); AdbcStatusCode {{.Prefix}}DatabaseInit(struct AdbcDatabase* db, struct AdbcError* err); +AdbcStatusCode {{.Prefix}}DatabaseNew(struct AdbcDatabase* db, struct AdbcError* err); AdbcStatusCode {{.Prefix}}DatabaseRelease(struct AdbcDatabase* db, struct AdbcError* err); -AdbcStatusCode {{.Prefix}}ConnectionNew(struct AdbcConnection* cnxn, struct AdbcError* err); -AdbcStatusCode {{.Prefix}}ConnectionSetOption(struct AdbcConnection* cnxn, const char* key, const char* val, struct AdbcError* err); -AdbcStatusCode {{.Prefix}}ConnectionInit(struct AdbcConnection* cnxn, struct AdbcDatabase* db, struct AdbcError* err); -AdbcStatusCode {{.Prefix}}ConnectionRelease(struct AdbcConnection* cnxn, struct AdbcError* err); +AdbcStatusCode {{.Prefix}}DatabaseSetOption(struct AdbcDatabase* db, const char* key, const char* value, struct AdbcError* err); +AdbcStatusCode {{.Prefix}}DatabaseSetOptionBytes(struct AdbcDatabase*, const char*, const uint8_t*, size_t, struct AdbcError*); +AdbcStatusCode {{.Prefix}}DatabaseSetOptionDouble(struct AdbcDatabase*, const char*, double, struct AdbcError*); +AdbcStatusCode {{.Prefix}}DatabaseSetOptionInt(struct AdbcDatabase*, const char*, int64_t, struct AdbcError*); + +AdbcStatusCode {{.Prefix}}ConnectionCancel(struct AdbcConnection*, struct AdbcError*); +AdbcStatusCode {{.Prefix}}ConnectionCommit(struct AdbcConnection* cnxn, struct AdbcError* err); AdbcStatusCode {{.Prefix}}ConnectionGetInfo(struct AdbcConnection* cnxn, uint32_t* codes, size_t len, struct ArrowArrayStream* out, struct AdbcError* err); AdbcStatusCode {{.Prefix}}ConnectionGetObjects(struct AdbcConnection* cnxn, int depth, const char* catalog, const char* dbSchema, const char* tableName, const char** tableType, const char* columnName, struct ArrowArrayStream* out, struct AdbcError* err); +AdbcStatusCode {{.Prefix}}ConnectionGetOption(struct AdbcConnection*, const char*, char*, size_t*, struct AdbcError*); +AdbcStatusCode {{.Prefix}}ConnectionGetOptionBytes(struct AdbcConnection*, const char*, uint8_t*, size_t*, struct AdbcError*); +AdbcStatusCode {{.Prefix}}ConnectionGetOptionDouble(struct AdbcConnection*, const char*, double*, struct AdbcError*); +AdbcStatusCode {{.Prefix}}ConnectionGetOptionInt(struct AdbcConnection*, const char*, int64_t*, struct AdbcError*); +AdbcStatusCode {{.Prefix}}ConnectionGetStatistics(struct AdbcConnection*, const char*, const char*, const char*, char, struct ArrowArrayStream*, struct AdbcError*); +AdbcStatusCode {{.Prefix}}ConnectionGetStatisticNames(struct AdbcConnection*, struct ArrowArrayStream*, struct AdbcError*); AdbcStatusCode {{.Prefix}}ConnectionGetTableSchema(struct AdbcConnection* cnxn, const char* catalog, const char* dbSchema, const char* tableName, struct ArrowSchema* schema, struct AdbcError* err); AdbcStatusCode {{.Prefix}}ConnectionGetTableTypes(struct AdbcConnection* cnxn, struct ArrowArrayStream* out, struct AdbcError* err); +AdbcStatusCode {{.Prefix}}ConnectionInit(struct AdbcConnection* cnxn, struct AdbcDatabase* db, struct AdbcError* err); +AdbcStatusCode {{.Prefix}}ConnectionNew(struct AdbcConnection* cnxn, struct AdbcError* err); AdbcStatusCode {{.Prefix}}ConnectionReadPartition(struct AdbcConnection* cnxn, const uint8_t* serialized, size_t serializedLen, struct ArrowArrayStream* out, struct AdbcError* err); -AdbcStatusCode {{.Prefix}}ConnectionCommit(struct AdbcConnection* cnxn, struct AdbcError* err); +AdbcStatusCode {{.Prefix}}ConnectionRelease(struct AdbcConnection* cnxn, struct AdbcError* err); AdbcStatusCode {{.Prefix}}ConnectionRollback(struct AdbcConnection* cnxn, struct AdbcError* err); -AdbcStatusCode {{.Prefix}}StatementNew(struct AdbcConnection* cnxn, struct AdbcStatement* stmt, struct AdbcError* err); -AdbcStatusCode {{.Prefix}}StatementRelease(struct AdbcStatement* stmt, struct AdbcError* err); -AdbcStatusCode {{.Prefix}}StatementPrepare(struct AdbcStatement* stmt, struct AdbcError* err); -AdbcStatusCode {{.Prefix}}StatementExecuteQuery(struct AdbcStatement* stmt, struct ArrowArrayStream* out, int64_t* affected, struct AdbcError* err); -AdbcStatusCode {{.Prefix}}StatementSetSqlQuery(struct AdbcStatement* stmt, const char* query, struct AdbcError* err); -AdbcStatusCode {{.Prefix}}StatementSetSubstraitPlan(struct AdbcStatement* stmt, const uint8_t* plan, size_t length, struct AdbcError* err); +AdbcStatusCode {{.Prefix}}ConnectionSetOption(struct AdbcConnection* cnxn, const char* key, const char* val, struct AdbcError* err); +AdbcStatusCode {{.Prefix}}ConnectionSetOptionBytes(struct AdbcConnection*, const char*, const uint8_t*, size_t, struct AdbcError*); +AdbcStatusCode {{.Prefix}}ConnectionSetOptionDouble(struct AdbcConnection*, const char*, double, struct AdbcError*); +AdbcStatusCode {{.Prefix}}ConnectionSetOptionInt(struct AdbcConnection*, const char*, int64_t, struct AdbcError*); + AdbcStatusCode {{.Prefix}}StatementBind(struct AdbcStatement* stmt, struct ArrowArray* values, struct ArrowSchema* schema, struct AdbcError* err); AdbcStatusCode {{.Prefix}}StatementBindStream(struct AdbcStatement* stmt, struct ArrowArrayStream* stream, struct AdbcError* err); +AdbcStatusCode {{.Prefix}}StatementCancel(struct AdbcStatement*, struct AdbcError*); +AdbcStatusCode {{.Prefix}}StatementExecuteQuery(struct AdbcStatement* stmt, struct ArrowArrayStream* out, int64_t* affected, struct AdbcError* err); +AdbcStatusCode {{.Prefix}}StatementExecutePartitions(struct AdbcStatement* stmt, struct ArrowSchema* schema, struct AdbcPartitions* partitions, int64_t* affected, struct AdbcError* err); +AdbcStatusCode {{.Prefix}}StatementExecuteSchema(struct AdbcStatement*, struct ArrowSchema*, struct AdbcError*); +AdbcStatusCode {{.Prefix}}StatementGetOption(struct AdbcStatement*, const char*, char*, size_t*, struct AdbcError*); +AdbcStatusCode {{.Prefix}}StatementGetOptionBytes(struct AdbcStatement*, const char*, uint8_t*, size_t*, struct AdbcError*); +AdbcStatusCode {{.Prefix}}StatementGetOptionDouble(struct AdbcStatement*, const char*, double*, struct AdbcError*); +AdbcStatusCode {{.Prefix}}StatementGetOptionInt(struct AdbcStatement*, const char*, int64_t*, struct AdbcError*); AdbcStatusCode {{.Prefix}}StatementGetParameterSchema(struct AdbcStatement* stmt, struct ArrowSchema* schema, struct AdbcError* err); +AdbcStatusCode {{.Prefix}}StatementNew(struct AdbcConnection* cnxn, struct AdbcStatement* stmt, struct AdbcError* err); +AdbcStatusCode {{.Prefix}}StatementPrepare(struct AdbcStatement* stmt, struct AdbcError* err); +AdbcStatusCode {{.Prefix}}StatementRelease(struct AdbcStatement* stmt, struct AdbcError* err); AdbcStatusCode {{.Prefix}}StatementSetOption(struct AdbcStatement* stmt, const char* key, const char* value, struct AdbcError* err); -AdbcStatusCode {{.Prefix}}StatementExecutePartitions(struct AdbcStatement* stmt, struct ArrowSchema* schema, struct AdbcPartitions* partitions, int64_t* affected, struct AdbcError* err); +AdbcStatusCode {{.Prefix}}StatementSetOptionBytes(struct AdbcStatement*, const char*, const uint8_t*, size_t, struct AdbcError*); +AdbcStatusCode {{.Prefix}}StatementSetOptionDouble(struct AdbcStatement*, const char*, double, struct AdbcError*); +AdbcStatusCode {{.Prefix}}StatementSetOptionInt(struct AdbcStatement*, const char*, int64_t, struct AdbcError*); +AdbcStatusCode {{.Prefix}}StatementSetSqlQuery(struct AdbcStatement* stmt, const char* query, struct AdbcError* err); +AdbcStatusCode {{.Prefix}}StatementSetSubstraitPlan(struct AdbcStatement* stmt, const uint8_t* plan, size_t length, struct AdbcError* err); + AdbcStatusCode {{.Prefix}}DriverInit(int version, void* rawDriver, struct AdbcError* err); static inline void {{.Prefix}}errRelease(struct AdbcError* error) { diff --git a/go/adbc/pkg/flightsql/driver.go b/go/adbc/pkg/flightsql/driver.go index 7cbec3987e..0910cdaca0 100644 --- a/go/adbc/pkg/flightsql/driver.go +++ b/go/adbc/pkg/flightsql/driver.go @@ -40,6 +40,8 @@ import ( "os" "runtime" "runtime/cgo" + "strconv" + "strings" "sync/atomic" "unsafe" @@ -47,6 +49,7 @@ import ( "github.com/apache/arrow-adbc/go/adbc/driver/flightsql" "github.com/apache/arrow/go/v13/arrow/array" "github.com/apache/arrow/go/v13/arrow/cdata" + "github.com/apache/arrow/go/v13/arrow/memory" "github.com/apache/arrow/go/v13/arrow/memory/mallocator" ) @@ -75,7 +78,7 @@ func setErr(err *C.struct_AdbcError, format string, vals ...interface{}) { } func errToAdbcErr(adbcerr *C.struct_AdbcError, err error) adbc.Status { - if adbcerr == nil || err == nil { + if err == nil { return adbc.StatusOK } @@ -89,6 +92,87 @@ func errToAdbcErr(adbcerr *C.struct_AdbcError, err error) adbc.Status { return adbc.StatusUnknown } +// errorDetails handles the standard error details feature in ADBC. +type errorDetails struct { + errorDetails []adbc.ErrorDetail +} + +func (ed *errorDetails) errToAdbcErr(adbcerr *C.struct_AdbcError, err error) adbc.Status { + ed.errorDetails = nil + if err == nil { + return adbc.StatusOK + } + + var adbcError adbc.Error + if errors.As(err, &adbcError) { + setErr(adbcerr, adbcError.Msg) + ed.errorDetails = adbcError.Details + return adbcError.Code + } + + setErr(adbcerr, err.Error()) + return adbc.StatusUnknown +} + +func (ed *errorDetails) getErrorDetailsOptionInt(key *C.cchar_t, value *C.int64_t) bool { + if C.GoString(key) == C.ADBC_OPTION_ERROR_DETAILS { + *value = C.int64_t(len(ed.errorDetails)) + return true + } + return false +} + +func (ed *errorDetails) getErrorDetailsOptionString(key *C.cchar_t, value *C.char, length *C.size_t) (bool, error) { + gokey := C.GoString(key) + if strings.HasPrefix(gokey, C.ADBC_OPTION_ERROR_DETAILS_PREFIX) { + indexStr := strings.TrimPrefix(gokey, C.ADBC_OPTION_ERROR_DETAILS_PREFIX) + index, err := strconv.ParseInt(indexStr, 10, 32) + if err != nil { + return false, adbc.Error{ + Code: adbc.StatusInvalidArgument, + Msg: fmt.Sprintf("[FlightSQL] Invalid error details option key '%s': %s", index, err.Error()), + } + } else if index < 0 || index >= int64(len(ed.errorDetails)) { + return false, adbc.Error{ + Code: adbc.StatusInvalidArgument, + Msg: fmt.Sprintf("[FlightSQL] Invalid error details option key '%s': out of range", index), + } + } + exportStringOption(ed.errorDetails[int(index)].Key(), value, length) + return true, nil + } + return false, nil +} + +func (ed *errorDetails) getErrorDetailsOptionBytes(key *C.cchar_t, value *C.uint8_t, length *C.size_t) (bool, error) { + gokey := C.GoString(key) + if strings.HasPrefix(gokey, C.ADBC_OPTION_ERROR_DETAILS_PREFIX) { + indexStr := strings.TrimPrefix(gokey, C.ADBC_OPTION_ERROR_DETAILS_PREFIX) + index, err := strconv.ParseInt(indexStr, 10, 32) + if err != nil { + return false, adbc.Error{ + Code: adbc.StatusInvalidArgument, + Msg: fmt.Sprintf("[FlightSQL] Invalid error details option key '%s': %s", index, err.Error()), + } + } else if index < 0 || index >= int64(len(ed.errorDetails)) { + return false, adbc.Error{ + Code: adbc.StatusInvalidArgument, + Msg: fmt.Sprintf("[FlightSQL] Invalid error details option key '%s': out of range", index), + } + } + serialized, err := ed.errorDetails[int(index)].Serialize() + if err != nil { + return false, adbc.Error{ + Code: adbc.StatusInternal, + Msg: fmt.Sprintf("[FlightSQL] Could not serialize error details: %s", err.Error()), + } + } + exportBytesOption(serialized, value, length) + return true, nil + } + return false, nil +} + // We panicked; make all API functions error and dump stack traces func poison(err *C.struct_AdbcError, fname string, e interface{}) C.AdbcStatusCode { if atomic.SwapInt32(&globalPoison, 1) == 0 { @@ -119,6 +203,45 @@ func getFromHandle[T any](ptr unsafe.Pointer) *T { return cgo.Handle((uintptr)(*hptr)).Value().(*T) } +func exportStringOption(val string, out *C.char, length *C.size_t) C.AdbcStatusCode { + lenWithTerminator := C.size_t(len(val) + 1) + if lenWithTerminator <= *length { + sink := fromCArr[byte]((*byte)(unsafe.Pointer(out)), int(*length)) + copy(sink, val) + sink[lenWithTerminator] = 0 + } + *length = lenWithTerminator + return C.ADBC_STATUS_OK +} + +func exportBytesOption(val []byte, out *C.uint8_t, length *C.size_t) C.AdbcStatusCode { + if C.size_t(len(val)) <= *length { + sink := fromCArr[byte]((*byte)(out), int(*length)) + copy(sink, val) + } + *length = C.size_t(len(val)) + return C.ADBC_STATUS_OK +} + +type cancellableContext struct { + ctx context.Context + cancel context.CancelFunc +} + +func (c *cancellableContext) newContext() context.Context { + c.cancelContext() + c.ctx, c.cancel = context.WithCancel(context.Background()) + return c.ctx +} + +func (c *cancellableContext) cancelContext() { + if c.cancel != nil { + c.cancel() + } + c.ctx = nil + c.cancel = nil +} + func checkDBAlloc(db *C.struct_AdbcDatabase, err *C.struct_AdbcError, fname string) bool { if atomic.LoadInt32(&globalPoison) != 0 { setErr(err, "%s: Go panicked, driver is in unknown state", fname) @@ -149,47 +272,122 @@ func checkDBInit(db *C.struct_AdbcDatabase, err *C.struct_AdbcError, fname strin } type cDatabase struct { + errorDetails + opts map[string]string db adbc.Database } -//export FlightSQLDatabaseNew -func FlightSQLDatabaseNew(db *C.struct_AdbcDatabase, err *C.struct_AdbcError) (code C.AdbcStatusCode) { +//export FlightSQLDatabaseGetOption +func FlightSQLDatabaseGetOption(db *C.struct_AdbcDatabase, key *C.cchar_t, value *C.char, length *C.size_t, err *C.struct_AdbcError) (code C.AdbcStatusCode) { defer func() { if e := recover(); e != nil { - code = poison(err, "AdbcDatabaseNew", e) + code = poison(err, "AdbcDatabaseGetOption", e) } }() - if atomic.LoadInt32(&globalPoison) != 0 { - setErr(err, "AdbcDatabaseNew: Go panicked, driver is in unknown state") - return C.ADBC_STATUS_INTERNAL + cdb := checkDBInit(db, err, "AdbcDatabaseGetOption") + if cdb == nil { + return C.ADBC_STATUS_INVALID_STATE } - if db.private_data != nil { - setErr(err, "AdbcDatabaseNew: database already allocated") + + if handled, e := cdb.getErrorDetailsOptionString(key, value, length); e != nil { + return C.AdbcStatusCode(errToAdbcErr(err, e)) + } else if handled { + return C.ADBC_STATUS_OK + } + + opts, ok := cdb.db.(adbc.GetSetOptions) + if !ok { + setErr(err, "AdbcDatabaseGetOption: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + val, e := opts.GetOption(C.GoString(key)) + if e != nil { + return C.AdbcStatusCode(cdb.errToAdbcErr(err, e)) + } + + return exportStringOption(val, value, length) +} + +//export FlightSQLDatabaseGetOptionBytes +func FlightSQLDatabaseGetOptionBytes(db *C.struct_AdbcDatabase, key *C.cchar_t, value *C.uint8_t, length *C.size_t, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcDatabaseGetOptionBytes", e) + } + }() + cdb := checkDBInit(db, err, "AdbcDatabaseGetOptionBytes") + if cdb == nil { return C.ADBC_STATUS_INVALID_STATE } - dbobj := &cDatabase{opts: make(map[string]string)} - hndl := cgo.NewHandle(dbobj) - db.private_data = createHandle(hndl) - return C.ADBC_STATUS_OK + + if handled, e := cdb.getErrorDetailsOptionBytes(key, value, length); e != nil { + return C.AdbcStatusCode(errToAdbcErr(err, e)) + } else if handled { + return C.ADBC_STATUS_OK + } + + opts, ok := cdb.db.(adbc.GetSetOptions) + if !ok { + setErr(err, "AdbcDatabaseGetOptionBytes: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + val, e := opts.GetOptionBytes(C.GoString(key)) + if e != nil { + return C.AdbcStatusCode(cdb.errToAdbcErr(err, e)) + } + + return exportBytesOption(val, value, length) } -//export FlightSQLDatabaseSetOption -func FlightSQLDatabaseSetOption(db *C.struct_AdbcDatabase, key, value *C.cchar_t, err *C.struct_AdbcError) (code C.AdbcStatusCode) { +//export FlightSQLDatabaseGetOptionDouble +func FlightSQLDatabaseGetOptionDouble(db *C.struct_AdbcDatabase, key *C.cchar_t, value *C.double, err *C.struct_AdbcError) (code C.AdbcStatusCode) { defer func() { if e := recover(); e != nil { - code = poison(err, "AdbcDatabaseSetOption", e) + code = poison(err, "AdbcDatabaseGetOptionDouble", e) } }() - if !checkDBAlloc(db, err, "AdbcDatabaseSetOption") { + cdb := checkDBInit(db, err, "AdbcDatabaseGetOptionDouble") + if cdb == nil { return C.ADBC_STATUS_INVALID_STATE } - cdb := getFromHandle[cDatabase](db.private_data) - k, v := C.GoString(key), C.GoString(value) - cdb.opts[k] = v + opts, ok := cdb.db.(adbc.GetSetOptions) + if !ok { + setErr(err, "AdbcDatabaseGetOptionDouble: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } - return C.ADBC_STATUS_OK + val, e := opts.GetOptionDouble(C.GoString(key)) + *value = C.double(val) + return C.AdbcStatusCode(cdb.errToAdbcErr(err, e)) +} + +//export FlightSQLDatabaseGetOptionInt +func FlightSQLDatabaseGetOptionInt(db *C.struct_AdbcDatabase, key *C.cchar_t, value *C.int64_t, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcDatabaseGetOptionInt", e) + } + }() + cdb := checkDBInit(db, err, "AdbcDatabaseGetOptionInt") + if cdb == nil { + return C.ADBC_STATUS_INVALID_STATE + } + + if cdb.getErrorDetailsOptionInt(key, value) { + return C.ADBC_STATUS_OK + } + + opts, ok := cdb.db.(adbc.GetSetOptions) + if !ok { + setErr(err, "AdbcDatabaseGetOptionInt: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + + val, e := opts.GetOptionInt(C.GoString(key)) + *value = C.int64_t(val) + return C.AdbcStatusCode(cdb.errToAdbcErr(err, e)) } //export FlightSQLDatabaseInit @@ -218,6 +416,27 @@ func FlightSQLDatabaseInit(db *C.struct_AdbcDatabase, err *C.struct_AdbcError) ( return C.ADBC_STATUS_OK } +//export FlightSQLDatabaseNew +func FlightSQLDatabaseNew(db *C.struct_AdbcDatabase, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcDatabaseNew", e) + } + }() + if atomic.LoadInt32(&globalPoison) != 0 { + setErr(err, "AdbcDatabaseNew: Go panicked, driver is in unknown state") + return C.ADBC_STATUS_INTERNAL + } + if db.private_data != nil { + setErr(err, "AdbcDatabaseNew: database already allocated") + return C.ADBC_STATUS_INVALID_STATE + } + dbobj := &cDatabase{opts: make(map[string]string)} + hndl := cgo.NewHandle(dbobj) + db.private_data = createHandle(hndl) + return C.ADBC_STATUS_OK +} + //export FlightSQLDatabaseRelease func FlightSQLDatabaseRelease(db *C.struct_AdbcDatabase, err *C.struct_AdbcError) (code C.AdbcStatusCode) { defer func() { @@ -246,7 +465,100 @@ func FlightSQLDatabaseRelease(db *C.struct_AdbcDatabase, err *C.struct_AdbcError return C.ADBC_STATUS_OK } +//export FlightSQLDatabaseSetOption +func FlightSQLDatabaseSetOption(db *C.struct_AdbcDatabase, key, value *C.cchar_t, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcDatabaseSetOption", e) + } + }() + if !checkDBAlloc(db, err, "AdbcDatabaseSetOption") { + return C.ADBC_STATUS_INVALID_STATE + } + cdb := getFromHandle[cDatabase](db.private_data) + + k, v := C.GoString(key), C.GoString(value) + if cdb.db != nil { + opts, ok := cdb.db.(adbc.PostInitOptions) + if !ok { + setErr(err, "AdbcDatabaseSetOption: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + return C.AdbcStatusCode(cdb.errToAdbcErr(err, opts.SetOption(k, v))) + } else { + cdb.opts[k] = v + } + + return C.ADBC_STATUS_OK +} + +//export FlightSQLDatabaseSetOptionBytes +func FlightSQLDatabaseSetOptionBytes(db *C.struct_AdbcDatabase, key *C.cchar_t, value *C.cuint8_t, length C.size_t, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcDatabaseSetOptionBytes", e) + } + }() + cdb := checkDBInit(db, err, "AdbcDatabaseSetOptionBytes") + if cdb == nil { + return C.ADBC_STATUS_INVALID_STATE + } + + opts, ok := cdb.db.(adbc.GetSetOptions) + if !ok { + setErr(err, "AdbcDatabaseSetOptionBytes: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + + return C.AdbcStatusCode(cdb.errToAdbcErr(err, opts.SetOptionBytes(C.GoString(key), fromCArr[byte](value, int(length))))) +} + +//export FlightSQLDatabaseSetOptionDouble +func FlightSQLDatabaseSetOptionDouble(db *C.struct_AdbcDatabase, key *C.cchar_t, value C.double, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcDatabaseSetOptionDouble", e) + } + }() + cdb := checkDBInit(db, err, "AdbcDatabaseSetOptionDouble") + if cdb == nil { + return C.ADBC_STATUS_INVALID_STATE + } + + opts, ok := cdb.db.(adbc.GetSetOptions) + if !ok { + setErr(err, "AdbcDatabaseSetOptionDouble: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + + return C.AdbcStatusCode(cdb.errToAdbcErr(err, opts.SetOptionDouble(C.GoString(key), float64(value)))) +} + +//export FlightSQLDatabaseSetOptionInt +func FlightSQLDatabaseSetOptionInt(db *C.struct_AdbcDatabase, key *C.cchar_t, value C.int64_t, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcDatabaseSetOptionInt", e) + } + }() + cdb := checkDBInit(db, err, "AdbcDatabaseSetOptionInt") + if cdb == nil { + return C.ADBC_STATUS_INVALID_STATE + } + + opts, ok := cdb.db.(adbc.GetSetOptions) + if !ok { + setErr(err, "AdbcDatabaseSetOptionInt: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + + return C.AdbcStatusCode(cdb.errToAdbcErr(err, opts.SetOptionInt(C.GoString(key), int64(value)))) +} + type cConn struct { + cancellableContext + errorDetails + cnxn adbc.Connection initArgs map[string]string } @@ -280,6 +592,118 @@ func checkConnInit(cnxn *C.struct_AdbcConnection, err *C.struct_AdbcError, fname return conn } +//export FlightSQLConnectionGetOption +func FlightSQLConnectionGetOption(db *C.struct_AdbcConnection, key *C.cchar_t, value *C.char, length *C.size_t, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcConnectionGetOption", e) + } + }() + conn := checkConnInit(db, err, "AdbcConnectionGetOption") + if conn == nil { + return C.ADBC_STATUS_INVALID_STATE + } + + if handled, e := conn.getErrorDetailsOptionString(key, value, length); e != nil { + return C.AdbcStatusCode(errToAdbcErr(err, e)) + } else if handled { + return C.ADBC_STATUS_OK + } + + opts, ok := conn.cnxn.(adbc.GetSetOptions) + if !ok { + setErr(err, "AdbcConnectionGetOption: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + val, e := opts.GetOption(C.GoString(key)) + if e != nil { + return C.AdbcStatusCode(conn.errToAdbcErr(err, e)) + } + + return exportStringOption(val, value, length) +} + +//export FlightSQLConnectionGetOptionBytes +func FlightSQLConnectionGetOptionBytes(db *C.struct_AdbcConnection, key *C.cchar_t, value *C.uint8_t, length *C.size_t, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcConnectionGetOptionBytes", e) + } + }() + conn := checkConnInit(db, err, "AdbcConnectionGetOptionBytes") + if conn == nil { + return C.ADBC_STATUS_INVALID_STATE + } + + if handled, e := conn.getErrorDetailsOptionBytes(key, value, length); e != nil { + return C.AdbcStatusCode(errToAdbcErr(err, e)) + } else if handled { + return C.ADBC_STATUS_OK + } + + opts, ok := conn.cnxn.(adbc.GetSetOptions) + if !ok { + setErr(err, "AdbcConnectionGetOptionBytes: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + val, e := opts.GetOptionBytes(C.GoString(key)) + if e != nil { + return C.AdbcStatusCode(conn.errToAdbcErr(err, e)) + } + + return exportBytesOption(val, value, length) +} + +//export FlightSQLConnectionGetOptionDouble +func FlightSQLConnectionGetOptionDouble(db *C.struct_AdbcConnection, key *C.cchar_t, value *C.double, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcConnectionGetOptionDouble", e) + } + }() + conn := checkConnInit(db, err, "AdbcConnectionGetOptionDouble") + if conn == nil { + return C.ADBC_STATUS_INVALID_STATE + } + + opts, ok := conn.cnxn.(adbc.GetSetOptions) + if !ok { + setErr(err, "AdbcConnectionGetOptionDouble: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + + val, e := opts.GetOptionDouble(C.GoString(key)) + *value = C.double(val) + return C.AdbcStatusCode(conn.errToAdbcErr(err, e)) +} + +//export FlightSQLConnectionGetOptionInt +func FlightSQLConnectionGetOptionInt(db *C.struct_AdbcConnection, key *C.cchar_t, value *C.int64_t, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcConnectionGetOptionInt", e) + } + }() + conn := checkConnInit(db, err, "AdbcConnectionGetOptionInt") + if conn == nil { + return C.ADBC_STATUS_INVALID_STATE + } + + if conn.getErrorDetailsOptionInt(key, value) { + return C.ADBC_STATUS_OK + } + + opts, ok := conn.cnxn.(adbc.GetSetOptions) + if !ok { + setErr(err, "AdbcConnectionGetOptionInt: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + + val, e := opts.GetOptionInt(C.GoString(key)) + *value = C.int64_t(val) + return C.AdbcStatusCode(conn.errToAdbcErr(err, e)) +} + //export FlightSQLConnectionNew func FlightSQLConnectionNew(cnxn *C.struct_AdbcConnection, err *C.struct_AdbcError) (code C.AdbcStatusCode) { defer func() { @@ -328,8 +752,70 @@ func FlightSQLConnectionSetOption(cnxn *C.struct_AdbcConnection, key, val *C.cch setErr(err, "AdbcConnectionSetOption: not supported post-init") return C.ADBC_STATUS_NOT_IMPLEMENTED } - rawCode := errToAdbcErr(err, opts.SetOption(C.GoString(key), C.GoString(val))) - return C.AdbcStatusCode(rawCode) + return C.AdbcStatusCode(conn.errToAdbcErr(err, opts.SetOption(C.GoString(key), C.GoString(val)))) +} + +//export FlightSQLConnectionSetOptionBytes +func FlightSQLConnectionSetOptionBytes(db *C.struct_AdbcConnection, key *C.cchar_t, value *C.cuint8_t, length C.size_t, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcConnectionSetOptionBytes", e) + } + }() + conn := checkConnInit(db, err, "AdbcConnectionSetOptionBytes") + if conn == nil { + return C.ADBC_STATUS_INVALID_STATE + } + + opts, ok := conn.cnxn.(adbc.GetSetOptions) + if !ok { + setErr(err, "AdbcConnectionSetOptionBytes: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + + return C.AdbcStatusCode(conn.errToAdbcErr(err, opts.SetOptionBytes(C.GoString(key), fromCArr[byte](value, int(length))))) +} + +//export FlightSQLConnectionSetOptionDouble +func FlightSQLConnectionSetOptionDouble(db *C.struct_AdbcConnection, key *C.cchar_t, value C.double, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcConnectionSetOptionDouble", e) + } + }() + conn := checkConnInit(db, err, "AdbcConnectionSetOptionDouble") + if conn == nil { + return C.ADBC_STATUS_INVALID_STATE + } + + opts, ok := conn.cnxn.(adbc.GetSetOptions) + if !ok { + setErr(err, "AdbcConnectionSetOptionDouble: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + + return C.AdbcStatusCode(conn.errToAdbcErr(err, opts.SetOptionDouble(C.GoString(key), float64(value)))) +} + +//export FlightSQLConnectionSetOptionInt +func FlightSQLConnectionSetOptionInt(db *C.struct_AdbcConnection, key *C.cchar_t, value C.int64_t, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcConnectionSetOptionInt", e) + } + }() + conn := checkConnInit(db, err, "AdbcConnectionSetOptionInt") + if conn == nil { + return C.ADBC_STATUS_INVALID_STATE + } + + opts, ok := conn.cnxn.(adbc.GetSetOptions) + if !ok { + setErr(err, "AdbcConnectionSetOptionInt: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + + return C.AdbcStatusCode(conn.errToAdbcErr(err, opts.SetOptionInt(C.GoString(key), int64(value)))) } //export FlightSQLConnectionInit @@ -354,7 +840,7 @@ func FlightSQLConnectionInit(cnxn *C.struct_AdbcConnection, db *C.struct_AdbcDat } c, e := cdb.db.Open(context.Background()) if e != nil { - return C.AdbcStatusCode(errToAdbcErr(err, e)) + return C.AdbcStatusCode(cdb.errToAdbcErr(err, e)) } conn.cnxn = c @@ -367,7 +853,7 @@ func FlightSQLConnectionInit(cnxn *C.struct_AdbcConnection, db *C.struct_AdbcDat } for k, v := range conn.initArgs { - rawCode := errToAdbcErr(err, opts.SetOption(k, v)) + rawCode := conn.errToAdbcErr(err, opts.SetOption(k, v)) if rawCode != adbc.StatusOK { return C.AdbcStatusCode(rawCode) } @@ -392,8 +878,9 @@ func FlightSQLConnectionRelease(cnxn *C.struct_AdbcConnection, err *C.struct_Adb conn := h.Value().(*cConn) defer func() { + conn.cancelContext() conn.cnxn = nil - C.free(unsafe.Pointer(cnxn.private_data)) + C.free(cnxn.private_data) cnxn.private_data = nil h.Delete() // manually trigger GC for two reasons: @@ -430,6 +917,46 @@ func toCdataArray(ptr *C.struct_ArrowArray) *cdata.CArrowArray { return (*cdata.CArrowArray)(unsafe.Pointer(ptr)) } +//export FlightSQLConnectionCancel +func FlightSQLConnectionCancel(cnxn *C.struct_AdbcConnection, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcConnectionCancel", e) + } + }() + conn := checkConnInit(cnxn, err, "AdbcConnectionCancel") + if conn == nil { + return C.ADBC_STATUS_INVALID_STATE + } + + conn.cancelContext() + return C.ADBC_STATUS_OK +} + +func toStrPtr(in *C.cchar_t) *string { + if in == nil { + return nil + } + + out := C.GoString((*C.char)(in)) + return &out +} + +func toStrSlice(in **C.cchar_t) []string { + if in == nil { + return nil + } + + sz := unsafe.Sizeof(*in) + + out := make([]string, 0, 1) + for *in != nil { + out = append(out, C.GoString(*in)) + in = (**C.cchar_t)(unsafe.Add(unsafe.Pointer(in), sz)) + } + return out +} + //export FlightSQLConnectionGetInfo func FlightSQLConnectionGetInfo(cnxn *C.struct_AdbcConnection, codes *C.uint32_t, len C.size_t, out *C.struct_ArrowArrayStream, err *C.struct_AdbcError) (code C.AdbcStatusCode) { defer func() { @@ -443,56 +970,86 @@ func FlightSQLConnectionGetInfo(cnxn *C.struct_AdbcConnection, codes *C.uint32_t } infoCodes := fromCArr[adbc.InfoCode](codes, int(len)) - rdr, e := conn.cnxn.GetInfo(context.Background(), infoCodes) + rdr, e := conn.cnxn.GetInfo(conn.newContext(), infoCodes) if e != nil { - return C.AdbcStatusCode(errToAdbcErr(err, e)) + return C.AdbcStatusCode(conn.errToAdbcErr(err, e)) } cdata.ExportRecordReader(rdr, toCdataStream(out)) return C.ADBC_STATUS_OK } -func toStrPtr(in *C.cchar_t) *string { - if in == nil { - return nil +//export FlightSQLConnectionGetObjects +func FlightSQLConnectionGetObjects(cnxn *C.struct_AdbcConnection, depth C.int, catalog, dbSchema, tableName *C.cchar_t, tableType **C.cchar_t, columnName *C.cchar_t, + out *C.struct_ArrowArrayStream, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcConnectionGetObjects", e) + } + }() + conn := checkConnInit(cnxn, err, "AdbcConnectionGetObjects") + if conn == nil { + return C.ADBC_STATUS_INVALID_STATE } - out := C.GoString((*C.char)(in)) - return &out + rdr, e := conn.cnxn.GetObjects(conn.newContext(), adbc.ObjectDepth(depth), toStrPtr(catalog), toStrPtr(dbSchema), toStrPtr(tableName), toStrPtr(columnName), toStrSlice(tableType)) + if e != nil { + return C.AdbcStatusCode(conn.errToAdbcErr(err, e)) + } + cdata.ExportRecordReader(rdr, toCdataStream(out)) + return C.ADBC_STATUS_OK } -func toStrSlice(in **C.cchar_t) []string { - if in == nil { - return nil +//export FlightSQLConnectionGetStatistics +func FlightSQLConnectionGetStatistics(cnxn *C.struct_AdbcConnection, catalog, dbSchema, tableName *C.cchar_t, approximate C.char, out *C.struct_ArrowArrayStream, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcConnectionGetStatistics", e) + } + }() + conn := checkConnInit(cnxn, err, "AdbcConnectionGetStatistics") + if conn == nil { + return C.ADBC_STATUS_INVALID_STATE } - sz := unsafe.Sizeof(*in) + gs, ok := conn.cnxn.(adbc.ConnectionGetStatistics) + if !ok { + setErr(err, "AdbcConnectionGetStatistics: not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } - out := make([]string, 0, 1) - for *in != nil { - out = append(out, C.GoString(*in)) - in = (**C.cchar_t)(unsafe.Add(unsafe.Pointer(in), sz)) + rdr, e := gs.GetStatistics(conn.newContext(), toStrPtr(catalog), toStrPtr(dbSchema), toStrPtr(tableName), int(approximate) != 0) + if e != nil { + return C.AdbcStatusCode(conn.errToAdbcErr(err, e)) } - return out + + cdata.ExportRecordReader(rdr, toCdataStream(out)) + return C.ADBC_STATUS_OK } -//export FlightSQLConnectionGetObjects -func FlightSQLConnectionGetObjects(cnxn *C.struct_AdbcConnection, depth C.int, catalog, dbSchema, tableName *C.cchar_t, tableType **C.cchar_t, columnName *C.cchar_t, - out *C.struct_ArrowArrayStream, err *C.struct_AdbcError) (code C.AdbcStatusCode) { +//export FlightSQLConnectionGetStatisticNames +func FlightSQLConnectionGetStatisticNames(cnxn *C.struct_AdbcConnection, out *C.struct_ArrowArrayStream, err *C.struct_AdbcError) (code C.AdbcStatusCode) { defer func() { if e := recover(); e != nil { - code = poison(err, "AdbcConnectionGetObjects", e) + code = poison(err, "AdbcConnectionGetStatistics", e) } }() - conn := checkConnInit(cnxn, err, "AdbcConnectionGetObjects") + conn := checkConnInit(cnxn, err, "AdbcConnectionGetStatistics") if conn == nil { return C.ADBC_STATUS_INVALID_STATE } - rdr, e := conn.cnxn.GetObjects(context.Background(), adbc.ObjectDepth(depth), toStrPtr(catalog), toStrPtr(dbSchema), toStrPtr(tableName), toStrPtr(columnName), toStrSlice(tableType)) + gs, ok := conn.cnxn.(adbc.ConnectionGetStatistics) + if !ok { + setErr(err, "AdbcConnectionGetStatistics: not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + + rdr, e := gs.GetStatisticNames(conn.newContext()) if e != nil { - return C.AdbcStatusCode(errToAdbcErr(err, e)) + return C.AdbcStatusCode(conn.errToAdbcErr(err, e)) } + cdata.ExportRecordReader(rdr, toCdataStream(out)) return C.ADBC_STATUS_OK } @@ -509,9 +1066,9 @@ func FlightSQLConnectionGetTableSchema(cnxn *C.struct_AdbcConnection, catalog, d return C.ADBC_STATUS_INVALID_STATE } - sc, e := conn.cnxn.GetTableSchema(context.Background(), toStrPtr(catalog), toStrPtr(dbSchema), C.GoString(tableName)) + sc, e := conn.cnxn.GetTableSchema(conn.newContext(), toStrPtr(catalog), toStrPtr(dbSchema), C.GoString(tableName)) if e != nil { - return C.AdbcStatusCode(errToAdbcErr(err, e)) + return C.AdbcStatusCode(conn.errToAdbcErr(err, e)) } cdata.ExportArrowSchema(sc, toCdataSchema(schema)) return C.ADBC_STATUS_OK @@ -529,9 +1086,9 @@ func FlightSQLConnectionGetTableTypes(cnxn *C.struct_AdbcConnection, out *C.stru return C.ADBC_STATUS_INVALID_STATE } - rdr, e := conn.cnxn.GetTableTypes(context.Background()) + rdr, e := conn.cnxn.GetTableTypes(conn.newContext()) if e != nil { - return C.AdbcStatusCode(errToAdbcErr(err, e)) + return C.AdbcStatusCode(conn.errToAdbcErr(err, e)) } cdata.ExportRecordReader(rdr, toCdataStream(out)) return C.ADBC_STATUS_OK @@ -549,9 +1106,9 @@ func FlightSQLConnectionReadPartition(cnxn *C.struct_AdbcConnection, serialized return C.ADBC_STATUS_INVALID_STATE } - rdr, e := conn.cnxn.ReadPartition(context.Background(), fromCArr[byte](serialized, int(serializedLen))) + rdr, e := conn.cnxn.ReadPartition(conn.newContext(), fromCArr[byte](serialized, int(serializedLen))) if e != nil { - return C.AdbcStatusCode(errToAdbcErr(err, e)) + return C.AdbcStatusCode(conn.errToAdbcErr(err, e)) } cdata.ExportRecordReader(rdr, toCdataStream(out)) return C.ADBC_STATUS_OK @@ -569,7 +1126,7 @@ func FlightSQLConnectionCommit(cnxn *C.struct_AdbcConnection, err *C.struct_Adbc return C.ADBC_STATUS_INVALID_STATE } - return C.AdbcStatusCode(errToAdbcErr(err, conn.cnxn.Commit(context.Background()))) + return C.AdbcStatusCode(conn.errToAdbcErr(err, conn.cnxn.Commit(conn.newContext()))) } //export FlightSQLConnectionRollback @@ -584,25 +1141,154 @@ func FlightSQLConnectionRollback(cnxn *C.struct_AdbcConnection, err *C.struct_Ad return C.ADBC_STATUS_INVALID_STATE } - return C.AdbcStatusCode(errToAdbcErr(err, conn.cnxn.Rollback(context.Background()))) + return C.AdbcStatusCode(conn.errToAdbcErr(err, conn.cnxn.Rollback(conn.newContext()))) +} + +type cStmt struct { + cancellableContext + errorDetails + + stmt adbc.Statement } -func checkStmtInit(stmt *C.struct_AdbcStatement, err *C.struct_AdbcError, fname string) adbc.Statement { +func checkStmtAlloc(stmt *C.struct_AdbcStatement, err *C.struct_AdbcError, fname string) bool { if atomic.LoadInt32(&globalPoison) != 0 { setErr(err, "%s: Go panicked, driver is in unknown state", fname) - return nil + return false } if stmt == nil { setErr(err, "%s: statement not allocated", fname) - return nil + return false } - if stmt.private_data == nil { - setErr(err, "%s: statement not initialized", fname) + setErr(err, "%s: statement not allocated", fname) + return false + } + return true +} + +func checkStmtInit(stmt *C.struct_AdbcStatement, err *C.struct_AdbcError, fname string) *cStmt { + if !checkStmtAlloc(stmt, err, fname) { + return nil + } + cStmt := getFromHandle[cStmt](stmt.private_data) + if cStmt.stmt == nil { + setErr(err, "%s: statement not allocated", fname) return nil } + return cStmt +} + +//export FlightSQLStatementGetOption +func FlightSQLStatementGetOption(db *C.struct_AdbcStatement, key *C.cchar_t, value *C.char, length *C.size_t, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcStatementGetOption", e) + } + }() + st := checkStmtInit(db, err, "AdbcStatementGetOption") + if st == nil { + return C.ADBC_STATUS_INVALID_STATE + } + + if handled, e := st.getErrorDetailsOptionString(key, value, length); e != nil { + return C.AdbcStatusCode(errToAdbcErr(err, e)) + } else if handled { + return C.ADBC_STATUS_OK + } + + opts, ok := st.stmt.(adbc.GetSetOptions) + if !ok { + setErr(err, "AdbcStatementGetOption: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + val, e := opts.GetOption(C.GoString(key)) + if e != nil { + return C.AdbcStatusCode(st.errToAdbcErr(err, e)) + } + + return exportStringOption(val, value, length) +} + +//export FlightSQLStatementGetOptionBytes +func FlightSQLStatementGetOptionBytes(db *C.struct_AdbcStatement, key *C.cchar_t, value *C.uint8_t, length *C.size_t, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcStatementGetOptionBytes", e) + } + }() + st := checkStmtInit(db, err, "AdbcStatementGetOptionBytes") + if st == nil { + return C.ADBC_STATUS_INVALID_STATE + } + + if handled, e := st.getErrorDetailsOptionBytes(key, value, length); e != nil { + return C.AdbcStatusCode(errToAdbcErr(err, e)) + } else if handled { + return C.ADBC_STATUS_OK + } + + opts, ok := st.stmt.(adbc.GetSetOptions) + if !ok { + setErr(err, "AdbcStatementGetOptionBytes: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + val, e := opts.GetOptionBytes(C.GoString(key)) + if e != nil { + return C.AdbcStatusCode(st.errToAdbcErr(err, e)) + } + + return exportBytesOption(val, value, length) +} + +//export FlightSQLStatementGetOptionDouble +func FlightSQLStatementGetOptionDouble(db *C.struct_AdbcStatement, key *C.cchar_t, value *C.double, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcStatementGetOptionDouble", e) + } + }() + st := checkStmtInit(db, err, "AdbcStatementGetOptionDouble") + if st == nil { + return C.ADBC_STATUS_INVALID_STATE + } + + opts, ok := st.stmt.(adbc.GetSetOptions) + if !ok { + setErr(err, "AdbcStatementGetOptionDouble: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + + val, e := opts.GetOptionDouble(C.GoString(key)) + *value = C.double(val) + return C.AdbcStatusCode(st.errToAdbcErr(err, e)) +} + +//export FlightSQLStatementGetOptionInt +func FlightSQLStatementGetOptionInt(db *C.struct_AdbcStatement, key *C.cchar_t, value *C.int64_t, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcStatementGetOptionInt", e) + } + }() + st := checkStmtInit(db, err, "AdbcStatementGetOptionInt") + if st == nil { + return C.ADBC_STATUS_INVALID_STATE + } + + if st.getErrorDetailsOptionInt(key, value) { + return C.ADBC_STATUS_OK + } - return (*(*cgo.Handle)(stmt.private_data)).Value().(adbc.Statement) + opts, ok := st.stmt.(adbc.GetSetOptions) + if !ok { + setErr(err, "AdbcStatementGetOptionInt: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + + val, e := opts.GetOptionInt(C.GoString(key)) + *value = C.int64_t(val) + return C.AdbcStatusCode(st.errToAdbcErr(err, e)) } //export FlightSQLStatementNew @@ -623,11 +1309,11 @@ func FlightSQLStatementNew(cnxn *C.struct_AdbcConnection, stmt *C.struct_AdbcSta st, e := conn.cnxn.NewStatement() if e != nil { - return C.AdbcStatusCode(errToAdbcErr(err, e)) + return C.AdbcStatusCode(conn.errToAdbcErr(err, e)) } - h := cgo.NewHandle(st) - stmt.private_data = createHandle(h) + hndl := cgo.NewHandle(&cStmt{stmt: st}) + stmt.private_data = createHandle(hndl) return C.ADBC_STATUS_OK } @@ -642,31 +1328,46 @@ func FlightSQLStatementRelease(stmt *C.struct_AdbcStatement, err *C.struct_AdbcE setErr(err, "AdbcStatementRelease: Go panicked, driver is in unknown state") return C.ADBC_STATUS_INTERNAL } - if stmt == nil { - setErr(err, "AdbcStatementRelease: statement not allocated") + if !checkStmtAlloc(stmt, err, "AdbcStatementRelease") { return C.ADBC_STATUS_INVALID_STATE } + h := (*(*cgo.Handle)(stmt.private_data)) - if stmt.private_data == nil { - setErr(err, "AdbcStatementRelease: statement not initialized") - return C.ADBC_STATUS_INVALID_STATE + st := h.Value().(*cStmt) + defer func() { + st.cancelContext() + st.stmt = nil + C.free(stmt.private_data) + stmt.private_data = nil + h.Delete() + // manually trigger GC for two reasons: + // 1. ASAN expects the release callback to be called before + // the process ends, but GC is not deterministic. So by manually + // triggering the GC we ensure the release callback gets called. + // 2. Creates deterministic GC behavior by all Release functions + // triggering a garbage collection + runtime.GC() + }() + if st.stmt == nil { + return C.ADBC_STATUS_OK } + return C.AdbcStatusCode(errToAdbcErr(err, st.stmt.Close())) +} - h := (*(*cgo.Handle)(stmt.private_data)) - st := h.Value().(adbc.Statement) - C.free(stmt.private_data) - stmt.private_data = nil +//export FlightSQLStatementCancel +func FlightSQLStatementCancel(stmt *C.struct_AdbcStatement, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcStatementPrepare", e) + } + }() + st := checkStmtInit(stmt, err, "AdbcStatementPrepare") + if st == nil { + return C.ADBC_STATUS_INVALID_STATE + } - e := st.Close() - h.Delete() - // manually trigger GC for two reasons: - // 1. ASAN expects the release callback to be called before - // the process ends, but GC is not deterministic. So by manually - // triggering the GC we ensure the release callback gets called. - // 2. Creates deterministic GC behavior by all Release functions - // triggering a garbage collection - runtime.GC() - return C.AdbcStatusCode(errToAdbcErr(err, e)) + st.cancelContext() + return C.ADBC_STATUS_OK } //export FlightSQLStatementPrepare @@ -681,7 +1382,7 @@ func FlightSQLStatementPrepare(stmt *C.struct_AdbcStatement, err *C.struct_AdbcE return C.ADBC_STATUS_INVALID_STATE } - return C.AdbcStatusCode(errToAdbcErr(err, st.Prepare(context.Background()))) + return C.AdbcStatusCode(st.errToAdbcErr(err, st.stmt.Prepare(st.newContext()))) } //export FlightSQLStatementExecuteQuery @@ -697,29 +1398,57 @@ func FlightSQLStatementExecuteQuery(stmt *C.struct_AdbcStatement, out *C.struct_ } if out == nil { - n, e := st.ExecuteUpdate(context.Background()) + n, e := st.stmt.ExecuteUpdate(st.newContext()) if e != nil { - return C.AdbcStatusCode(errToAdbcErr(err, e)) + return C.AdbcStatusCode(st.errToAdbcErr(err, e)) } if affected != nil { *affected = C.int64_t(n) } } else { - rdr, n, e := st.ExecuteQuery(context.Background()) + rdr, n, e := st.stmt.ExecuteQuery(st.newContext()) if e != nil { - return C.AdbcStatusCode(errToAdbcErr(err, e)) + return C.AdbcStatusCode(st.errToAdbcErr(err, e)) } if affected != nil { *affected = C.int64_t(n) } + // TODO: proxy the reader and record error details (and do this elsewhere, too) cdata.ExportRecordReader(rdr, toCdataStream(out)) } return C.ADBC_STATUS_OK } +//export FlightSQLStatementExecuteSchema +func FlightSQLStatementExecuteSchema(stmt *C.struct_AdbcStatement, schema *C.struct_ArrowSchema, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcStatementExecuteQuery", e) + } + }() + st := checkStmtInit(stmt, err, "AdbcStatementExecuteQuery") + if st == nil { + return C.ADBC_STATUS_INVALID_STATE + } + + es, ok := st.stmt.(adbc.StatementExecuteSchema) + if !ok { + setErr(err, "AdbcStatementExecuteSchema: not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + + sc, e := es.ExecuteSchema(st.newContext()) + if e != nil { + return C.AdbcStatusCode(st.errToAdbcErr(err, e)) + } + + cdata.ExportArrowSchema(sc, toCdataSchema(schema)) + return C.ADBC_STATUS_OK +} + //export FlightSQLStatementSetSqlQuery func FlightSQLStatementSetSqlQuery(stmt *C.struct_AdbcStatement, query *C.cchar_t, err *C.struct_AdbcError) (code C.AdbcStatusCode) { defer func() { @@ -732,7 +1461,7 @@ func FlightSQLStatementSetSqlQuery(stmt *C.struct_AdbcStatement, query *C.cchar_ return C.ADBC_STATUS_INVALID_STATE } - return C.AdbcStatusCode(errToAdbcErr(err, st.SetSqlQuery(C.GoString(query)))) + return C.AdbcStatusCode(st.errToAdbcErr(err, st.stmt.SetSqlQuery(C.GoString(query)))) } //export FlightSQLStatementSetSubstraitPlan @@ -747,7 +1476,7 @@ func FlightSQLStatementSetSubstraitPlan(stmt *C.struct_AdbcStatement, plan *C.cu return C.ADBC_STATUS_INVALID_STATE } - return C.AdbcStatusCode(errToAdbcErr(err, st.SetSubstraitPlan(fromCArr[byte](plan, int(length))))) + return C.AdbcStatusCode(st.errToAdbcErr(err, st.stmt.SetSubstraitPlan(fromCArr[byte](plan, int(length))))) } //export FlightSQLStatementBind @@ -766,11 +1495,11 @@ func FlightSQLStatementBind(stmt *C.struct_AdbcStatement, values *C.struct_Arrow if e != nil { // if there was an error, we need to manually release the input cdata.ReleaseCArrowArray(toCdataArray(values)) - return C.AdbcStatusCode(errToAdbcErr(err, e)) + return C.AdbcStatusCode(st.errToAdbcErr(err, e)) } defer rec.Release() - return C.AdbcStatusCode(errToAdbcErr(err, st.Bind(context.Background(), rec))) + return C.AdbcStatusCode(st.errToAdbcErr(err, st.stmt.Bind(st.newContext(), rec))) } //export FlightSQLStatementBindStream @@ -787,9 +1516,9 @@ func FlightSQLStatementBindStream(stmt *C.struct_AdbcStatement, stream *C.struct rdr, e := cdata.ImportCRecordReader(toCdataStream(stream), nil) if e != nil { - return C.AdbcStatusCode(errToAdbcErr(err, e)) + return C.AdbcStatusCode(st.errToAdbcErr(err, e)) } - return C.AdbcStatusCode(errToAdbcErr(err, st.BindStream(context.Background(), rdr.(array.RecordReader)))) + return C.AdbcStatusCode(st.errToAdbcErr(err, st.stmt.BindStream(st.newContext(), rdr.(array.RecordReader)))) } //export FlightSQLStatementGetParameterSchema @@ -804,9 +1533,9 @@ func FlightSQLStatementGetParameterSchema(stmt *C.struct_AdbcStatement, schema * return C.ADBC_STATUS_INVALID_STATE } - sc, e := st.GetParameterSchema() + sc, e := st.stmt.GetParameterSchema() if e != nil { - return C.AdbcStatusCode(errToAdbcErr(err, e)) + return C.AdbcStatusCode(st.errToAdbcErr(err, e)) } cdata.ExportArrowSchema(sc, toCdataSchema(schema)) @@ -825,7 +1554,70 @@ func FlightSQLStatementSetOption(stmt *C.struct_AdbcStatement, key, value *C.cch return C.ADBC_STATUS_INVALID_STATE } - return C.AdbcStatusCode(errToAdbcErr(err, st.SetOption(C.GoString(key), C.GoString(value)))) + return C.AdbcStatusCode(st.errToAdbcErr(err, st.stmt.SetOption(C.GoString(key), C.GoString(value)))) +} + +//export FlightSQLStatementSetOptionBytes +func FlightSQLStatementSetOptionBytes(db *C.struct_AdbcStatement, key *C.cchar_t, value *C.cuint8_t, length C.size_t, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcStatementSetOptionBytes", e) + } + }() + st := checkStmtInit(db, err, "AdbcStatementSetOptionBytes") + if st == nil { + return C.ADBC_STATUS_INVALID_STATE + } + + opts, ok := st.stmt.(adbc.GetSetOptions) + if !ok { + setErr(err, "AdbcStatementSetOptionBytes: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + + return C.AdbcStatusCode(st.errToAdbcErr(err, opts.SetOptionBytes(C.GoString(key), fromCArr[byte](value, int(length))))) +} + +//export FlightSQLStatementSetOptionDouble +func FlightSQLStatementSetOptionDouble(db *C.struct_AdbcStatement, key *C.cchar_t, value C.double, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcStatementSetOptionDouble", e) + } + }() + st := checkStmtInit(db, err, "AdbcStatementSetOptionDouble") + if st == nil { + return C.ADBC_STATUS_INVALID_STATE + } + + opts, ok := st.stmt.(adbc.GetSetOptions) + if !ok { + setErr(err, "AdbcStatementSetOptionDouble: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + + return C.AdbcStatusCode(st.errToAdbcErr(err, opts.SetOptionDouble(C.GoString(key), float64(value)))) +} + +//export FlightSQLStatementSetOptionInt +func FlightSQLStatementSetOptionInt(db *C.struct_AdbcStatement, key *C.cchar_t, value C.int64_t, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcStatementSetOptionInt", e) + } + }() + st := checkStmtInit(db, err, "AdbcStatementSetOptionInt") + if st == nil { + return C.ADBC_STATUS_INVALID_STATE + } + + opts, ok := st.stmt.(adbc.GetSetOptions) + if !ok { + setErr(err, "AdbcStatementSetOptionInt: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + + return C.AdbcStatusCode(st.errToAdbcErr(err, opts.SetOptionInt(C.GoString(key), int64(value)))) } //export releasePartitions @@ -854,9 +1646,9 @@ func FlightSQLStatementExecutePartitions(stmt *C.struct_AdbcStatement, schema *C return C.ADBC_STATUS_INVALID_STATE } - sc, part, n, e := st.ExecutePartitions(context.Background()) + sc, part, n, e := st.stmt.ExecutePartitions(st.newContext()) if e != nil { - return C.AdbcStatusCode(errToAdbcErr(err, e)) + return C.AdbcStatusCode(st.errToAdbcErr(err, e)) } if partitions == nil { @@ -899,13 +1691,20 @@ func FlightSQLStatementExecutePartitions(stmt *C.struct_AdbcStatement, schema *C //export FlightSQLDriverInit func FlightSQLDriverInit(version C.int, rawDriver *C.void, err *C.struct_AdbcError) C.AdbcStatusCode { - if version != C.ADBC_VERSION_1_0_0 { - setErr(err, "Only version %d supported, got %d", int(C.ADBC_VERSION_1_0_0), int(version)) + driver := (*C.struct_AdbcDriver)(unsafe.Pointer(rawDriver)) + + switch version { + case C.ADBC_VERSION_1_0_0: + sink := fromCArr[byte]((*byte)(unsafe.Pointer(driver)), C.ADBC_DRIVER_1_0_0_SIZE) + memory.Set(sink, 0) + case C.ADBC_VERSION_1_1_0: + sink := fromCArr[byte]((*byte)(unsafe.Pointer(driver)), C.ADBC_DRIVER_1_1_0_SIZE) + memory.Set(sink, 0) + default: + setErr(err, "Only version 1.0.0/1.1.0 supported, got %d", int(version)) return C.ADBC_STATUS_NOT_IMPLEMENTED } - driver := (*C.struct_AdbcDriver)(unsafe.Pointer(rawDriver)) - C.memset(unsafe.Pointer(driver), 0, C.ADBC_DRIVER_1_0_0_SIZE) driver.DatabaseInit = (*[0]byte)(C.FlightSQLDatabaseInit) driver.DatabaseNew = (*[0]byte)(C.FlightSQLDatabaseNew) driver.DatabaseRelease = (*[0]byte)(C.FlightSQLDatabaseRelease) @@ -935,6 +1734,37 @@ func FlightSQLDriverInit(version C.int, rawDriver *C.void, err *C.struct_AdbcErr driver.StatementGetParameterSchema = (*[0]byte)(C.FlightSQLStatementGetParameterSchema) driver.StatementPrepare = (*[0]byte)(C.FlightSQLStatementPrepare) + if version == C.ADBC_VERSION_1_1_0 { + driver.DatabaseGetOption = (*[0]byte)(C.FlightSQLDatabaseGetOption) + driver.DatabaseGetOptionBytes = (*[0]byte)(C.FlightSQLDatabaseGetOptionBytes) + driver.DatabaseGetOptionDouble = (*[0]byte)(C.FlightSQLDatabaseGetOptionDouble) + driver.DatabaseGetOptionInt = (*[0]byte)(C.FlightSQLDatabaseGetOptionInt) + driver.DatabaseSetOptionBytes = (*[0]byte)(C.FlightSQLDatabaseSetOptionBytes) + driver.DatabaseSetOptionDouble = (*[0]byte)(C.FlightSQLDatabaseSetOptionDouble) + driver.DatabaseSetOptionInt = (*[0]byte)(C.FlightSQLDatabaseSetOptionInt) + + driver.ConnectionCancel = (*[0]byte)(C.FlightSQLConnectionCancel) + driver.ConnectionGetOption = (*[0]byte)(C.FlightSQLConnectionGetOption) + driver.ConnectionGetOptionBytes = (*[0]byte)(C.FlightSQLConnectionGetOptionBytes) + driver.ConnectionGetOptionDouble = (*[0]byte)(C.FlightSQLConnectionGetOptionDouble) + driver.ConnectionGetOptionInt = (*[0]byte)(C.FlightSQLConnectionGetOptionInt) + driver.ConnectionGetStatistics = (*[0]byte)(C.FlightSQLConnectionGetStatistics) + driver.ConnectionGetStatisticNames = (*[0]byte)(C.FlightSQLConnectionGetStatisticNames) + driver.ConnectionSetOptionBytes = (*[0]byte)(C.FlightSQLConnectionSetOptionBytes) + driver.ConnectionSetOptionDouble = (*[0]byte)(C.FlightSQLConnectionSetOptionDouble) + driver.ConnectionSetOptionInt = (*[0]byte)(C.FlightSQLConnectionSetOptionInt) + + driver.StatementCancel = (*[0]byte)(C.FlightSQLStatementCancel) + driver.StatementExecuteSchema = (*[0]byte)(C.FlightSQLStatementExecuteSchema) + driver.StatementGetOption = (*[0]byte)(C.FlightSQLStatementGetOption) + driver.StatementGetOptionBytes = (*[0]byte)(C.FlightSQLStatementGetOptionBytes) + driver.StatementGetOptionDouble = (*[0]byte)(C.FlightSQLStatementGetOptionDouble) + driver.StatementGetOptionInt = (*[0]byte)(C.FlightSQLStatementGetOptionInt) + driver.StatementSetOptionBytes = (*[0]byte)(C.FlightSQLStatementSetOptionBytes) + driver.StatementSetOptionDouble = (*[0]byte)(C.FlightSQLStatementSetOptionDouble) + driver.StatementSetOptionInt = (*[0]byte)(C.FlightSQLStatementSetOptionInt) + } + return C.ADBC_STATUS_OK } diff --git a/go/adbc/pkg/flightsql/utils.h b/go/adbc/pkg/flightsql/utils.h index 51a67f240a..897f361a46 100644 --- a/go/adbc/pkg/flightsql/utils.h +++ b/go/adbc/pkg/flightsql/utils.h @@ -26,18 +26,29 @@ #include #include "../../drivermgr/adbc.h" +AdbcStatusCode FlightSQLDatabaseGetOption(struct AdbcDatabase*, const char*, char*, + size_t*, struct AdbcError*); +AdbcStatusCode FlightSQLDatabaseGetOptionBytes(struct AdbcDatabase*, const char*, + uint8_t*, size_t*, struct AdbcError*); +AdbcStatusCode FlightSQLDatabaseGetOptionDouble(struct AdbcDatabase*, const char*, + double*, struct AdbcError*); +AdbcStatusCode FlightSQLDatabaseGetOptionInt(struct AdbcDatabase*, const char*, int64_t*, + struct AdbcError*); +AdbcStatusCode FlightSQLDatabaseInit(struct AdbcDatabase* db, struct AdbcError* err); AdbcStatusCode FlightSQLDatabaseNew(struct AdbcDatabase* db, struct AdbcError* err); +AdbcStatusCode FlightSQLDatabaseRelease(struct AdbcDatabase* db, struct AdbcError* err); AdbcStatusCode FlightSQLDatabaseSetOption(struct AdbcDatabase* db, const char* key, const char* value, struct AdbcError* err); -AdbcStatusCode FlightSQLDatabaseInit(struct AdbcDatabase* db, struct AdbcError* err); -AdbcStatusCode FlightSQLDatabaseRelease(struct AdbcDatabase* db, struct AdbcError* err); -AdbcStatusCode FlightSQLConnectionNew(struct AdbcConnection* cnxn, struct AdbcError* err); -AdbcStatusCode FlightSQLConnectionSetOption(struct AdbcConnection* cnxn, const char* key, - const char* val, struct AdbcError* err); -AdbcStatusCode FlightSQLConnectionInit(struct AdbcConnection* cnxn, - struct AdbcDatabase* db, struct AdbcError* err); -AdbcStatusCode FlightSQLConnectionRelease(struct AdbcConnection* cnxn, - struct AdbcError* err); +AdbcStatusCode FlightSQLDatabaseSetOptionBytes(struct AdbcDatabase*, const char*, + const uint8_t*, size_t, struct AdbcError*); +AdbcStatusCode FlightSQLDatabaseSetOptionDouble(struct AdbcDatabase*, const char*, double, + struct AdbcError*); +AdbcStatusCode FlightSQLDatabaseSetOptionInt(struct AdbcDatabase*, const char*, int64_t, + struct AdbcError*); + +AdbcStatusCode FlightSQLConnectionCancel(struct AdbcConnection*, struct AdbcError*); +AdbcStatusCode FlightSQLConnectionCommit(struct AdbcConnection* cnxn, + struct AdbcError* err); AdbcStatusCode FlightSQLConnectionGetInfo(struct AdbcConnection* cnxn, uint32_t* codes, size_t len, struct ArrowArrayStream* out, struct AdbcError* err); @@ -45,51 +56,98 @@ AdbcStatusCode FlightSQLConnectionGetObjects( struct AdbcConnection* cnxn, int depth, const char* catalog, const char* dbSchema, const char* tableName, const char** tableType, const char* columnName, struct ArrowArrayStream* out, struct AdbcError* err); +AdbcStatusCode FlightSQLConnectionGetOption(struct AdbcConnection*, const char*, char*, + size_t*, struct AdbcError*); +AdbcStatusCode FlightSQLConnectionGetOptionBytes(struct AdbcConnection*, const char*, + uint8_t*, size_t*, struct AdbcError*); +AdbcStatusCode FlightSQLConnectionGetOptionDouble(struct AdbcConnection*, const char*, + double*, struct AdbcError*); +AdbcStatusCode FlightSQLConnectionGetOptionInt(struct AdbcConnection*, const char*, + int64_t*, struct AdbcError*); +AdbcStatusCode FlightSQLConnectionGetStatistics(struct AdbcConnection*, const char*, + const char*, const char*, char, + struct ArrowArrayStream*, + struct AdbcError*); +AdbcStatusCode FlightSQLConnectionGetStatisticNames(struct AdbcConnection*, + struct ArrowArrayStream*, + struct AdbcError*); AdbcStatusCode FlightSQLConnectionGetTableSchema( struct AdbcConnection* cnxn, const char* catalog, const char* dbSchema, const char* tableName, struct ArrowSchema* schema, struct AdbcError* err); AdbcStatusCode FlightSQLConnectionGetTableTypes(struct AdbcConnection* cnxn, struct ArrowArrayStream* out, struct AdbcError* err); +AdbcStatusCode FlightSQLConnectionInit(struct AdbcConnection* cnxn, + struct AdbcDatabase* db, struct AdbcError* err); +AdbcStatusCode FlightSQLConnectionNew(struct AdbcConnection* cnxn, struct AdbcError* err); AdbcStatusCode FlightSQLConnectionReadPartition(struct AdbcConnection* cnxn, const uint8_t* serialized, size_t serializedLen, struct ArrowArrayStream* out, struct AdbcError* err); -AdbcStatusCode FlightSQLConnectionCommit(struct AdbcConnection* cnxn, - struct AdbcError* err); +AdbcStatusCode FlightSQLConnectionRelease(struct AdbcConnection* cnxn, + struct AdbcError* err); AdbcStatusCode FlightSQLConnectionRollback(struct AdbcConnection* cnxn, struct AdbcError* err); -AdbcStatusCode FlightSQLStatementNew(struct AdbcConnection* cnxn, - struct AdbcStatement* stmt, struct AdbcError* err); -AdbcStatusCode FlightSQLStatementRelease(struct AdbcStatement* stmt, - struct AdbcError* err); -AdbcStatusCode FlightSQLStatementPrepare(struct AdbcStatement* stmt, - struct AdbcError* err); -AdbcStatusCode FlightSQLStatementExecuteQuery(struct AdbcStatement* stmt, - struct ArrowArrayStream* out, - int64_t* affected, struct AdbcError* err); -AdbcStatusCode FlightSQLStatementSetSqlQuery(struct AdbcStatement* stmt, - const char* query, struct AdbcError* err); -AdbcStatusCode FlightSQLStatementSetSubstraitPlan(struct AdbcStatement* stmt, - const uint8_t* plan, size_t length, - struct AdbcError* err); +AdbcStatusCode FlightSQLConnectionSetOption(struct AdbcConnection* cnxn, const char* key, + const char* val, struct AdbcError* err); +AdbcStatusCode FlightSQLConnectionSetOptionBytes(struct AdbcConnection*, const char*, + const uint8_t*, size_t, + struct AdbcError*); +AdbcStatusCode FlightSQLConnectionSetOptionDouble(struct AdbcConnection*, const char*, + double, struct AdbcError*); +AdbcStatusCode FlightSQLConnectionSetOptionInt(struct AdbcConnection*, const char*, + int64_t, struct AdbcError*); + AdbcStatusCode FlightSQLStatementBind(struct AdbcStatement* stmt, struct ArrowArray* values, struct ArrowSchema* schema, struct AdbcError* err); AdbcStatusCode FlightSQLStatementBindStream(struct AdbcStatement* stmt, struct ArrowArrayStream* stream, struct AdbcError* err); -AdbcStatusCode FlightSQLStatementGetParameterSchema(struct AdbcStatement* stmt, - struct ArrowSchema* schema, - struct AdbcError* err); -AdbcStatusCode FlightSQLStatementSetOption(struct AdbcStatement* stmt, const char* key, - const char* value, struct AdbcError* err); +AdbcStatusCode FlightSQLStatementCancel(struct AdbcStatement*, struct AdbcError*); +AdbcStatusCode FlightSQLStatementExecuteQuery(struct AdbcStatement* stmt, + struct ArrowArrayStream* out, + int64_t* affected, struct AdbcError* err); AdbcStatusCode FlightSQLStatementExecutePartitions(struct AdbcStatement* stmt, struct ArrowSchema* schema, struct AdbcPartitions* partitions, int64_t* affected, struct AdbcError* err); +AdbcStatusCode FlightSQLStatementExecuteSchema(struct AdbcStatement*, struct ArrowSchema*, + struct AdbcError*); +AdbcStatusCode FlightSQLStatementGetOption(struct AdbcStatement*, const char*, char*, + size_t*, struct AdbcError*); +AdbcStatusCode FlightSQLStatementGetOptionBytes(struct AdbcStatement*, const char*, + uint8_t*, size_t*, struct AdbcError*); +AdbcStatusCode FlightSQLStatementGetOptionDouble(struct AdbcStatement*, const char*, + double*, struct AdbcError*); +AdbcStatusCode FlightSQLStatementGetOptionInt(struct AdbcStatement*, const char*, + int64_t*, struct AdbcError*); +AdbcStatusCode FlightSQLStatementGetParameterSchema(struct AdbcStatement* stmt, + struct ArrowSchema* schema, + struct AdbcError* err); +AdbcStatusCode FlightSQLStatementNew(struct AdbcConnection* cnxn, + struct AdbcStatement* stmt, struct AdbcError* err); +AdbcStatusCode FlightSQLStatementPrepare(struct AdbcStatement* stmt, + struct AdbcError* err); +AdbcStatusCode FlightSQLStatementRelease(struct AdbcStatement* stmt, + struct AdbcError* err); +AdbcStatusCode FlightSQLStatementSetOption(struct AdbcStatement* stmt, const char* key, + const char* value, struct AdbcError* err); +AdbcStatusCode FlightSQLStatementSetOptionBytes(struct AdbcStatement*, const char*, + const uint8_t*, size_t, + struct AdbcError*); +AdbcStatusCode FlightSQLStatementSetOptionDouble(struct AdbcStatement*, const char*, + double, struct AdbcError*); +AdbcStatusCode FlightSQLStatementSetOptionInt(struct AdbcStatement*, const char*, int64_t, + struct AdbcError*); +AdbcStatusCode FlightSQLStatementSetSqlQuery(struct AdbcStatement* stmt, + const char* query, struct AdbcError* err); +AdbcStatusCode FlightSQLStatementSetSubstraitPlan(struct AdbcStatement* stmt, + const uint8_t* plan, size_t length, + struct AdbcError* err); + AdbcStatusCode FlightSQLDriverInit(int version, void* rawDriver, struct AdbcError* err); static inline void FlightSQLerrRelease(struct AdbcError* error) { error->release(error); } diff --git a/go/adbc/pkg/panicdummy/driver.go b/go/adbc/pkg/panicdummy/driver.go index 374c3cb828..9e79edcdea 100644 --- a/go/adbc/pkg/panicdummy/driver.go +++ b/go/adbc/pkg/panicdummy/driver.go @@ -40,6 +40,8 @@ import ( "os" "runtime" "runtime/cgo" + "strconv" + "strings" "sync/atomic" "unsafe" @@ -47,6 +49,7 @@ import ( "github.com/apache/arrow-adbc/go/adbc/driver/panicdummy" "github.com/apache/arrow/go/v13/arrow/array" "github.com/apache/arrow/go/v13/arrow/cdata" + "github.com/apache/arrow/go/v13/arrow/memory" "github.com/apache/arrow/go/v13/arrow/memory/mallocator" ) @@ -75,7 +78,7 @@ func setErr(err *C.struct_AdbcError, format string, vals ...interface{}) { } func errToAdbcErr(adbcerr *C.struct_AdbcError, err error) adbc.Status { - if adbcerr == nil || err == nil { + if err == nil { return adbc.StatusOK } @@ -89,6 +92,87 @@ func errToAdbcErr(adbcerr *C.struct_AdbcError, err error) adbc.Status { return adbc.StatusUnknown } +// errorDetails handles the standard error details feature in ADBC. +type errorDetails struct { + errorDetails []adbc.ErrorDetail +} + +func (ed *errorDetails) errToAdbcErr(adbcerr *C.struct_AdbcError, err error) adbc.Status { + ed.errorDetails = nil + if err == nil { + return adbc.StatusOK + } + + var adbcError adbc.Error + if errors.As(err, &adbcError) { + setErr(adbcerr, adbcError.Msg) + ed.errorDetails = adbcError.Details + return adbcError.Code + } + + setErr(adbcerr, err.Error()) + return adbc.StatusUnknown +} + +func (ed *errorDetails) getErrorDetailsOptionInt(key *C.cchar_t, value *C.int64_t) bool { + if C.GoString(key) == C.ADBC_OPTION_ERROR_DETAILS { + *value = C.int64_t(len(ed.errorDetails)) + return true + } + return false +} + +func (ed *errorDetails) getErrorDetailsOptionString(key *C.cchar_t, value *C.char, length *C.size_t) (bool, error) { + gokey := C.GoString(key) + if strings.HasPrefix(gokey, C.ADBC_OPTION_ERROR_DETAILS_PREFIX) { + indexStr := strings.TrimPrefix(gokey, C.ADBC_OPTION_ERROR_DETAILS_PREFIX) + index, err := strconv.ParseInt(indexStr, 10, 32) + if err != nil { + return false, adbc.Error{ + Code: adbc.StatusInvalidArgument, + Msg: fmt.Sprintf("[PanicDummy] Invalid error details option key '%s': %s", index, err.Error()), + } + } else if index < 0 || index >= int64(len(ed.errorDetails)) { + return false, adbc.Error{ + Code: adbc.StatusInvalidArgument, + Msg: fmt.Sprintf("[PanicDummy] Invalid error details option key '%s': out of range", index), + } + } + exportStringOption(ed.errorDetails[int(index)].Key(), value, length) + return true, nil + } + return false, nil +} + +func (ed *errorDetails) getErrorDetailsOptionBytes(key *C.cchar_t, value *C.uint8_t, length *C.size_t) (bool, error) { + gokey := C.GoString(key) + if strings.HasPrefix(gokey, C.ADBC_OPTION_ERROR_DETAILS_PREFIX) { + indexStr := strings.TrimPrefix(gokey, C.ADBC_OPTION_ERROR_DETAILS_PREFIX) + index, err := strconv.ParseInt(indexStr, 10, 32) + if err != nil { + return false, adbc.Error{ + Code: adbc.StatusInvalidArgument, + Msg: fmt.Sprintf("[PanicDummy] Invalid error details option key '%s': %s", index, err.Error()), + } + } else if index < 0 || index >= int64(len(ed.errorDetails)) { + return false, adbc.Error{ + Code: adbc.StatusInvalidArgument, + Msg: fmt.Sprintf("[PanicDummy] Invalid error details option key '%s': out of range", index), + } + } + serialized, err := ed.errorDetails[int(index)].Serialize() + if err != nil { + return false, adbc.Error{ + Code: adbc.StatusInternal, + Msg: fmt.Sprintf("[PanicDummy] Could not serialize error details: %s", err.Error()), + } + } + exportBytesOption(serialized, value, length) + return true, nil + } + return false, nil +} + // We panicked; make all API functions error and dump stack traces func poison(err *C.struct_AdbcError, fname string, e interface{}) C.AdbcStatusCode { if atomic.SwapInt32(&globalPoison, 1) == 0 { @@ -119,6 +203,45 @@ func getFromHandle[T any](ptr unsafe.Pointer) *T { return cgo.Handle((uintptr)(*hptr)).Value().(*T) } +func exportStringOption(val string, out *C.char, length *C.size_t) C.AdbcStatusCode { + lenWithTerminator := C.size_t(len(val) + 1) + if lenWithTerminator <= *length { + sink := fromCArr[byte]((*byte)(unsafe.Pointer(out)), int(*length)) + copy(sink, val) + sink[lenWithTerminator] = 0 + } + *length = lenWithTerminator + return C.ADBC_STATUS_OK +} + +func exportBytesOption(val []byte, out *C.uint8_t, length *C.size_t) C.AdbcStatusCode { + if C.size_t(len(val)) <= *length { + sink := fromCArr[byte]((*byte)(out), int(*length)) + copy(sink, val) + } + *length = C.size_t(len(val)) + return C.ADBC_STATUS_OK +} + +type cancellableContext struct { + ctx context.Context + cancel context.CancelFunc +} + +func (c *cancellableContext) newContext() context.Context { + c.cancelContext() + c.ctx, c.cancel = context.WithCancel(context.Background()) + return c.ctx +} + +func (c *cancellableContext) cancelContext() { + if c.cancel != nil { + c.cancel() + } + c.ctx = nil + c.cancel = nil +} + func checkDBAlloc(db *C.struct_AdbcDatabase, err *C.struct_AdbcError, fname string) bool { if atomic.LoadInt32(&globalPoison) != 0 { setErr(err, "%s: Go panicked, driver is in unknown state", fname) @@ -149,47 +272,122 @@ func checkDBInit(db *C.struct_AdbcDatabase, err *C.struct_AdbcError, fname strin } type cDatabase struct { + errorDetails + opts map[string]string db adbc.Database } -//export PanicDummyDatabaseNew -func PanicDummyDatabaseNew(db *C.struct_AdbcDatabase, err *C.struct_AdbcError) (code C.AdbcStatusCode) { +//export PanicDummyDatabaseGetOption +func PanicDummyDatabaseGetOption(db *C.struct_AdbcDatabase, key *C.cchar_t, value *C.char, length *C.size_t, err *C.struct_AdbcError) (code C.AdbcStatusCode) { defer func() { if e := recover(); e != nil { - code = poison(err, "AdbcDatabaseNew", e) + code = poison(err, "AdbcDatabaseGetOption", e) } }() - if atomic.LoadInt32(&globalPoison) != 0 { - setErr(err, "AdbcDatabaseNew: Go panicked, driver is in unknown state") - return C.ADBC_STATUS_INTERNAL + cdb := checkDBInit(db, err, "AdbcDatabaseGetOption") + if cdb == nil { + return C.ADBC_STATUS_INVALID_STATE } - if db.private_data != nil { - setErr(err, "AdbcDatabaseNew: database already allocated") + + if handled, e := cdb.getErrorDetailsOptionString(key, value, length); e != nil { + return C.AdbcStatusCode(errToAdbcErr(err, e)) + } else if handled { + return C.ADBC_STATUS_OK + } + + opts, ok := cdb.db.(adbc.GetSetOptions) + if !ok { + setErr(err, "AdbcDatabaseGetOption: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + val, e := opts.GetOption(C.GoString(key)) + if e != nil { + return C.AdbcStatusCode(cdb.errToAdbcErr(err, e)) + } + + return exportStringOption(val, value, length) +} + +//export PanicDummyDatabaseGetOptionBytes +func PanicDummyDatabaseGetOptionBytes(db *C.struct_AdbcDatabase, key *C.cchar_t, value *C.uint8_t, length *C.size_t, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcDatabaseGetOptionBytes", e) + } + }() + cdb := checkDBInit(db, err, "AdbcDatabaseGetOptionBytes") + if cdb == nil { return C.ADBC_STATUS_INVALID_STATE } - dbobj := &cDatabase{opts: make(map[string]string)} - hndl := cgo.NewHandle(dbobj) - db.private_data = createHandle(hndl) - return C.ADBC_STATUS_OK + + if handled, e := cdb.getErrorDetailsOptionBytes(key, value, length); e != nil { + return C.AdbcStatusCode(errToAdbcErr(err, e)) + } else if handled { + return C.ADBC_STATUS_OK + } + + opts, ok := cdb.db.(adbc.GetSetOptions) + if !ok { + setErr(err, "AdbcDatabaseGetOptionBytes: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + val, e := opts.GetOptionBytes(C.GoString(key)) + if e != nil { + return C.AdbcStatusCode(cdb.errToAdbcErr(err, e)) + } + + return exportBytesOption(val, value, length) } -//export PanicDummyDatabaseSetOption -func PanicDummyDatabaseSetOption(db *C.struct_AdbcDatabase, key, value *C.cchar_t, err *C.struct_AdbcError) (code C.AdbcStatusCode) { +//export PanicDummyDatabaseGetOptionDouble +func PanicDummyDatabaseGetOptionDouble(db *C.struct_AdbcDatabase, key *C.cchar_t, value *C.double, err *C.struct_AdbcError) (code C.AdbcStatusCode) { defer func() { if e := recover(); e != nil { - code = poison(err, "AdbcDatabaseSetOption", e) + code = poison(err, "AdbcDatabaseGetOptionDouble", e) } }() - if !checkDBAlloc(db, err, "AdbcDatabaseSetOption") { + cdb := checkDBInit(db, err, "AdbcDatabaseGetOptionDouble") + if cdb == nil { return C.ADBC_STATUS_INVALID_STATE } - cdb := getFromHandle[cDatabase](db.private_data) - k, v := C.GoString(key), C.GoString(value) - cdb.opts[k] = v + opts, ok := cdb.db.(adbc.GetSetOptions) + if !ok { + setErr(err, "AdbcDatabaseGetOptionDouble: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } - return C.ADBC_STATUS_OK + val, e := opts.GetOptionDouble(C.GoString(key)) + *value = C.double(val) + return C.AdbcStatusCode(cdb.errToAdbcErr(err, e)) +} + +//export PanicDummyDatabaseGetOptionInt +func PanicDummyDatabaseGetOptionInt(db *C.struct_AdbcDatabase, key *C.cchar_t, value *C.int64_t, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcDatabaseGetOptionInt", e) + } + }() + cdb := checkDBInit(db, err, "AdbcDatabaseGetOptionInt") + if cdb == nil { + return C.ADBC_STATUS_INVALID_STATE + } + + if cdb.getErrorDetailsOptionInt(key, value) { + return C.ADBC_STATUS_OK + } + + opts, ok := cdb.db.(adbc.GetSetOptions) + if !ok { + setErr(err, "AdbcDatabaseGetOptionInt: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + + val, e := opts.GetOptionInt(C.GoString(key)) + *value = C.int64_t(val) + return C.AdbcStatusCode(cdb.errToAdbcErr(err, e)) } //export PanicDummyDatabaseInit @@ -218,6 +416,27 @@ func PanicDummyDatabaseInit(db *C.struct_AdbcDatabase, err *C.struct_AdbcError) return C.ADBC_STATUS_OK } +//export PanicDummyDatabaseNew +func PanicDummyDatabaseNew(db *C.struct_AdbcDatabase, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcDatabaseNew", e) + } + }() + if atomic.LoadInt32(&globalPoison) != 0 { + setErr(err, "AdbcDatabaseNew: Go panicked, driver is in unknown state") + return C.ADBC_STATUS_INTERNAL + } + if db.private_data != nil { + setErr(err, "AdbcDatabaseNew: database already allocated") + return C.ADBC_STATUS_INVALID_STATE + } + dbobj := &cDatabase{opts: make(map[string]string)} + hndl := cgo.NewHandle(dbobj) + db.private_data = createHandle(hndl) + return C.ADBC_STATUS_OK +} + //export PanicDummyDatabaseRelease func PanicDummyDatabaseRelease(db *C.struct_AdbcDatabase, err *C.struct_AdbcError) (code C.AdbcStatusCode) { defer func() { @@ -246,7 +465,100 @@ func PanicDummyDatabaseRelease(db *C.struct_AdbcDatabase, err *C.struct_AdbcErro return C.ADBC_STATUS_OK } +//export PanicDummyDatabaseSetOption +func PanicDummyDatabaseSetOption(db *C.struct_AdbcDatabase, key, value *C.cchar_t, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcDatabaseSetOption", e) + } + }() + if !checkDBAlloc(db, err, "AdbcDatabaseSetOption") { + return C.ADBC_STATUS_INVALID_STATE + } + cdb := getFromHandle[cDatabase](db.private_data) + + k, v := C.GoString(key), C.GoString(value) + if cdb.db != nil { + opts, ok := cdb.db.(adbc.PostInitOptions) + if !ok { + setErr(err, "AdbcDatabaseSetOption: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + return C.AdbcStatusCode(cdb.errToAdbcErr(err, opts.SetOption(k, v))) + } else { + cdb.opts[k] = v + } + + return C.ADBC_STATUS_OK +} + +//export PanicDummyDatabaseSetOptionBytes +func PanicDummyDatabaseSetOptionBytes(db *C.struct_AdbcDatabase, key *C.cchar_t, value *C.cuint8_t, length C.size_t, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcDatabaseSetOptionBytes", e) + } + }() + cdb := checkDBInit(db, err, "AdbcDatabaseSetOptionBytes") + if cdb == nil { + return C.ADBC_STATUS_INVALID_STATE + } + + opts, ok := cdb.db.(adbc.GetSetOptions) + if !ok { + setErr(err, "AdbcDatabaseSetOptionBytes: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + + return C.AdbcStatusCode(cdb.errToAdbcErr(err, opts.SetOptionBytes(C.GoString(key), fromCArr[byte](value, int(length))))) +} + +//export PanicDummyDatabaseSetOptionDouble +func PanicDummyDatabaseSetOptionDouble(db *C.struct_AdbcDatabase, key *C.cchar_t, value C.double, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcDatabaseSetOptionDouble", e) + } + }() + cdb := checkDBInit(db, err, "AdbcDatabaseSetOptionDouble") + if cdb == nil { + return C.ADBC_STATUS_INVALID_STATE + } + + opts, ok := cdb.db.(adbc.GetSetOptions) + if !ok { + setErr(err, "AdbcDatabaseSetOptionDouble: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + + return C.AdbcStatusCode(cdb.errToAdbcErr(err, opts.SetOptionDouble(C.GoString(key), float64(value)))) +} + +//export PanicDummyDatabaseSetOptionInt +func PanicDummyDatabaseSetOptionInt(db *C.struct_AdbcDatabase, key *C.cchar_t, value C.int64_t, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcDatabaseSetOptionInt", e) + } + }() + cdb := checkDBInit(db, err, "AdbcDatabaseSetOptionInt") + if cdb == nil { + return C.ADBC_STATUS_INVALID_STATE + } + + opts, ok := cdb.db.(adbc.GetSetOptions) + if !ok { + setErr(err, "AdbcDatabaseSetOptionInt: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + + return C.AdbcStatusCode(cdb.errToAdbcErr(err, opts.SetOptionInt(C.GoString(key), int64(value)))) +} + type cConn struct { + cancellableContext + errorDetails + cnxn adbc.Connection initArgs map[string]string } @@ -280,6 +592,118 @@ func checkConnInit(cnxn *C.struct_AdbcConnection, err *C.struct_AdbcError, fname return conn } +//export PanicDummyConnectionGetOption +func PanicDummyConnectionGetOption(db *C.struct_AdbcConnection, key *C.cchar_t, value *C.char, length *C.size_t, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcConnectionGetOption", e) + } + }() + conn := checkConnInit(db, err, "AdbcConnectionGetOption") + if conn == nil { + return C.ADBC_STATUS_INVALID_STATE + } + + if handled, e := conn.getErrorDetailsOptionString(key, value, length); e != nil { + return C.AdbcStatusCode(errToAdbcErr(err, e)) + } else if handled { + return C.ADBC_STATUS_OK + } + + opts, ok := conn.cnxn.(adbc.GetSetOptions) + if !ok { + setErr(err, "AdbcConnectionGetOption: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + val, e := opts.GetOption(C.GoString(key)) + if e != nil { + return C.AdbcStatusCode(conn.errToAdbcErr(err, e)) + } + + return exportStringOption(val, value, length) +} + +//export PanicDummyConnectionGetOptionBytes +func PanicDummyConnectionGetOptionBytes(db *C.struct_AdbcConnection, key *C.cchar_t, value *C.uint8_t, length *C.size_t, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcConnectionGetOptionBytes", e) + } + }() + conn := checkConnInit(db, err, "AdbcConnectionGetOptionBytes") + if conn == nil { + return C.ADBC_STATUS_INVALID_STATE + } + + if handled, e := conn.getErrorDetailsOptionBytes(key, value, length); e != nil { + return C.AdbcStatusCode(errToAdbcErr(err, e)) + } else if handled { + return C.ADBC_STATUS_OK + } + + opts, ok := conn.cnxn.(adbc.GetSetOptions) + if !ok { + setErr(err, "AdbcConnectionGetOptionBytes: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + val, e := opts.GetOptionBytes(C.GoString(key)) + if e != nil { + return C.AdbcStatusCode(conn.errToAdbcErr(err, e)) + } + + return exportBytesOption(val, value, length) +} + +//export PanicDummyConnectionGetOptionDouble +func PanicDummyConnectionGetOptionDouble(db *C.struct_AdbcConnection, key *C.cchar_t, value *C.double, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcConnectionGetOptionDouble", e) + } + }() + conn := checkConnInit(db, err, "AdbcConnectionGetOptionDouble") + if conn == nil { + return C.ADBC_STATUS_INVALID_STATE + } + + opts, ok := conn.cnxn.(adbc.GetSetOptions) + if !ok { + setErr(err, "AdbcConnectionGetOptionDouble: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + + val, e := opts.GetOptionDouble(C.GoString(key)) + *value = C.double(val) + return C.AdbcStatusCode(conn.errToAdbcErr(err, e)) +} + +//export PanicDummyConnectionGetOptionInt +func PanicDummyConnectionGetOptionInt(db *C.struct_AdbcConnection, key *C.cchar_t, value *C.int64_t, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcConnectionGetOptionInt", e) + } + }() + conn := checkConnInit(db, err, "AdbcConnectionGetOptionInt") + if conn == nil { + return C.ADBC_STATUS_INVALID_STATE + } + + if conn.getErrorDetailsOptionInt(key, value) { + return C.ADBC_STATUS_OK + } + + opts, ok := conn.cnxn.(adbc.GetSetOptions) + if !ok { + setErr(err, "AdbcConnectionGetOptionInt: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + + val, e := opts.GetOptionInt(C.GoString(key)) + *value = C.int64_t(val) + return C.AdbcStatusCode(conn.errToAdbcErr(err, e)) +} + //export PanicDummyConnectionNew func PanicDummyConnectionNew(cnxn *C.struct_AdbcConnection, err *C.struct_AdbcError) (code C.AdbcStatusCode) { defer func() { @@ -328,8 +752,70 @@ func PanicDummyConnectionSetOption(cnxn *C.struct_AdbcConnection, key, val *C.cc setErr(err, "AdbcConnectionSetOption: not supported post-init") return C.ADBC_STATUS_NOT_IMPLEMENTED } - rawCode := errToAdbcErr(err, opts.SetOption(C.GoString(key), C.GoString(val))) - return C.AdbcStatusCode(rawCode) + return C.AdbcStatusCode(conn.errToAdbcErr(err, opts.SetOption(C.GoString(key), C.GoString(val)))) +} + +//export PanicDummyConnectionSetOptionBytes +func PanicDummyConnectionSetOptionBytes(db *C.struct_AdbcConnection, key *C.cchar_t, value *C.cuint8_t, length C.size_t, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcConnectionSetOptionBytes", e) + } + }() + conn := checkConnInit(db, err, "AdbcConnectionSetOptionBytes") + if conn == nil { + return C.ADBC_STATUS_INVALID_STATE + } + + opts, ok := conn.cnxn.(adbc.GetSetOptions) + if !ok { + setErr(err, "AdbcConnectionSetOptionBytes: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + + return C.AdbcStatusCode(conn.errToAdbcErr(err, opts.SetOptionBytes(C.GoString(key), fromCArr[byte](value, int(length))))) +} + +//export PanicDummyConnectionSetOptionDouble +func PanicDummyConnectionSetOptionDouble(db *C.struct_AdbcConnection, key *C.cchar_t, value C.double, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcConnectionSetOptionDouble", e) + } + }() + conn := checkConnInit(db, err, "AdbcConnectionSetOptionDouble") + if conn == nil { + return C.ADBC_STATUS_INVALID_STATE + } + + opts, ok := conn.cnxn.(adbc.GetSetOptions) + if !ok { + setErr(err, "AdbcConnectionSetOptionDouble: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + + return C.AdbcStatusCode(conn.errToAdbcErr(err, opts.SetOptionDouble(C.GoString(key), float64(value)))) +} + +//export PanicDummyConnectionSetOptionInt +func PanicDummyConnectionSetOptionInt(db *C.struct_AdbcConnection, key *C.cchar_t, value C.int64_t, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcConnectionSetOptionInt", e) + } + }() + conn := checkConnInit(db, err, "AdbcConnectionSetOptionInt") + if conn == nil { + return C.ADBC_STATUS_INVALID_STATE + } + + opts, ok := conn.cnxn.(adbc.GetSetOptions) + if !ok { + setErr(err, "AdbcConnectionSetOptionInt: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + + return C.AdbcStatusCode(conn.errToAdbcErr(err, opts.SetOptionInt(C.GoString(key), int64(value)))) } //export PanicDummyConnectionInit @@ -354,7 +840,7 @@ func PanicDummyConnectionInit(cnxn *C.struct_AdbcConnection, db *C.struct_AdbcDa } c, e := cdb.db.Open(context.Background()) if e != nil { - return C.AdbcStatusCode(errToAdbcErr(err, e)) + return C.AdbcStatusCode(cdb.errToAdbcErr(err, e)) } conn.cnxn = c @@ -367,7 +853,7 @@ func PanicDummyConnectionInit(cnxn *C.struct_AdbcConnection, db *C.struct_AdbcDa } for k, v := range conn.initArgs { - rawCode := errToAdbcErr(err, opts.SetOption(k, v)) + rawCode := conn.errToAdbcErr(err, opts.SetOption(k, v)) if rawCode != adbc.StatusOK { return C.AdbcStatusCode(rawCode) } @@ -392,8 +878,9 @@ func PanicDummyConnectionRelease(cnxn *C.struct_AdbcConnection, err *C.struct_Ad conn := h.Value().(*cConn) defer func() { + conn.cancelContext() conn.cnxn = nil - C.free(unsafe.Pointer(cnxn.private_data)) + C.free(cnxn.private_data) cnxn.private_data = nil h.Delete() // manually trigger GC for two reasons: @@ -430,6 +917,46 @@ func toCdataArray(ptr *C.struct_ArrowArray) *cdata.CArrowArray { return (*cdata.CArrowArray)(unsafe.Pointer(ptr)) } +//export PanicDummyConnectionCancel +func PanicDummyConnectionCancel(cnxn *C.struct_AdbcConnection, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcConnectionCancel", e) + } + }() + conn := checkConnInit(cnxn, err, "AdbcConnectionCancel") + if conn == nil { + return C.ADBC_STATUS_INVALID_STATE + } + + conn.cancelContext() + return C.ADBC_STATUS_OK +} + +func toStrPtr(in *C.cchar_t) *string { + if in == nil { + return nil + } + + out := C.GoString((*C.char)(in)) + return &out +} + +func toStrSlice(in **C.cchar_t) []string { + if in == nil { + return nil + } + + sz := unsafe.Sizeof(*in) + + out := make([]string, 0, 1) + for *in != nil { + out = append(out, C.GoString(*in)) + in = (**C.cchar_t)(unsafe.Add(unsafe.Pointer(in), sz)) + } + return out +} + //export PanicDummyConnectionGetInfo func PanicDummyConnectionGetInfo(cnxn *C.struct_AdbcConnection, codes *C.uint32_t, len C.size_t, out *C.struct_ArrowArrayStream, err *C.struct_AdbcError) (code C.AdbcStatusCode) { defer func() { @@ -443,56 +970,86 @@ func PanicDummyConnectionGetInfo(cnxn *C.struct_AdbcConnection, codes *C.uint32_ } infoCodes := fromCArr[adbc.InfoCode](codes, int(len)) - rdr, e := conn.cnxn.GetInfo(context.Background(), infoCodes) + rdr, e := conn.cnxn.GetInfo(conn.newContext(), infoCodes) if e != nil { - return C.AdbcStatusCode(errToAdbcErr(err, e)) + return C.AdbcStatusCode(conn.errToAdbcErr(err, e)) } cdata.ExportRecordReader(rdr, toCdataStream(out)) return C.ADBC_STATUS_OK } -func toStrPtr(in *C.cchar_t) *string { - if in == nil { - return nil +//export PanicDummyConnectionGetObjects +func PanicDummyConnectionGetObjects(cnxn *C.struct_AdbcConnection, depth C.int, catalog, dbSchema, tableName *C.cchar_t, tableType **C.cchar_t, columnName *C.cchar_t, + out *C.struct_ArrowArrayStream, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcConnectionGetObjects", e) + } + }() + conn := checkConnInit(cnxn, err, "AdbcConnectionGetObjects") + if conn == nil { + return C.ADBC_STATUS_INVALID_STATE } - out := C.GoString((*C.char)(in)) - return &out + rdr, e := conn.cnxn.GetObjects(conn.newContext(), adbc.ObjectDepth(depth), toStrPtr(catalog), toStrPtr(dbSchema), toStrPtr(tableName), toStrPtr(columnName), toStrSlice(tableType)) + if e != nil { + return C.AdbcStatusCode(conn.errToAdbcErr(err, e)) + } + cdata.ExportRecordReader(rdr, toCdataStream(out)) + return C.ADBC_STATUS_OK } -func toStrSlice(in **C.cchar_t) []string { - if in == nil { - return nil +//export PanicDummyConnectionGetStatistics +func PanicDummyConnectionGetStatistics(cnxn *C.struct_AdbcConnection, catalog, dbSchema, tableName *C.cchar_t, approximate C.char, out *C.struct_ArrowArrayStream, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcConnectionGetStatistics", e) + } + }() + conn := checkConnInit(cnxn, err, "AdbcConnectionGetStatistics") + if conn == nil { + return C.ADBC_STATUS_INVALID_STATE } - sz := unsafe.Sizeof(*in) + gs, ok := conn.cnxn.(adbc.ConnectionGetStatistics) + if !ok { + setErr(err, "AdbcConnectionGetStatistics: not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } - out := make([]string, 0, 1) - for *in != nil { - out = append(out, C.GoString(*in)) - in = (**C.cchar_t)(unsafe.Add(unsafe.Pointer(in), sz)) + rdr, e := gs.GetStatistics(conn.newContext(), toStrPtr(catalog), toStrPtr(dbSchema), toStrPtr(tableName), int(approximate) != 0) + if e != nil { + return C.AdbcStatusCode(conn.errToAdbcErr(err, e)) } - return out + + cdata.ExportRecordReader(rdr, toCdataStream(out)) + return C.ADBC_STATUS_OK } -//export PanicDummyConnectionGetObjects -func PanicDummyConnectionGetObjects(cnxn *C.struct_AdbcConnection, depth C.int, catalog, dbSchema, tableName *C.cchar_t, tableType **C.cchar_t, columnName *C.cchar_t, - out *C.struct_ArrowArrayStream, err *C.struct_AdbcError) (code C.AdbcStatusCode) { +//export PanicDummyConnectionGetStatisticNames +func PanicDummyConnectionGetStatisticNames(cnxn *C.struct_AdbcConnection, out *C.struct_ArrowArrayStream, err *C.struct_AdbcError) (code C.AdbcStatusCode) { defer func() { if e := recover(); e != nil { - code = poison(err, "AdbcConnectionGetObjects", e) + code = poison(err, "AdbcConnectionGetStatistics", e) } }() - conn := checkConnInit(cnxn, err, "AdbcConnectionGetObjects") + conn := checkConnInit(cnxn, err, "AdbcConnectionGetStatistics") if conn == nil { return C.ADBC_STATUS_INVALID_STATE } - rdr, e := conn.cnxn.GetObjects(context.Background(), adbc.ObjectDepth(depth), toStrPtr(catalog), toStrPtr(dbSchema), toStrPtr(tableName), toStrPtr(columnName), toStrSlice(tableType)) + gs, ok := conn.cnxn.(adbc.ConnectionGetStatistics) + if !ok { + setErr(err, "AdbcConnectionGetStatistics: not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + + rdr, e := gs.GetStatisticNames(conn.newContext()) if e != nil { - return C.AdbcStatusCode(errToAdbcErr(err, e)) + return C.AdbcStatusCode(conn.errToAdbcErr(err, e)) } + cdata.ExportRecordReader(rdr, toCdataStream(out)) return C.ADBC_STATUS_OK } @@ -509,9 +1066,9 @@ func PanicDummyConnectionGetTableSchema(cnxn *C.struct_AdbcConnection, catalog, return C.ADBC_STATUS_INVALID_STATE } - sc, e := conn.cnxn.GetTableSchema(context.Background(), toStrPtr(catalog), toStrPtr(dbSchema), C.GoString(tableName)) + sc, e := conn.cnxn.GetTableSchema(conn.newContext(), toStrPtr(catalog), toStrPtr(dbSchema), C.GoString(tableName)) if e != nil { - return C.AdbcStatusCode(errToAdbcErr(err, e)) + return C.AdbcStatusCode(conn.errToAdbcErr(err, e)) } cdata.ExportArrowSchema(sc, toCdataSchema(schema)) return C.ADBC_STATUS_OK @@ -529,9 +1086,9 @@ func PanicDummyConnectionGetTableTypes(cnxn *C.struct_AdbcConnection, out *C.str return C.ADBC_STATUS_INVALID_STATE } - rdr, e := conn.cnxn.GetTableTypes(context.Background()) + rdr, e := conn.cnxn.GetTableTypes(conn.newContext()) if e != nil { - return C.AdbcStatusCode(errToAdbcErr(err, e)) + return C.AdbcStatusCode(conn.errToAdbcErr(err, e)) } cdata.ExportRecordReader(rdr, toCdataStream(out)) return C.ADBC_STATUS_OK @@ -549,9 +1106,9 @@ func PanicDummyConnectionReadPartition(cnxn *C.struct_AdbcConnection, serialized return C.ADBC_STATUS_INVALID_STATE } - rdr, e := conn.cnxn.ReadPartition(context.Background(), fromCArr[byte](serialized, int(serializedLen))) + rdr, e := conn.cnxn.ReadPartition(conn.newContext(), fromCArr[byte](serialized, int(serializedLen))) if e != nil { - return C.AdbcStatusCode(errToAdbcErr(err, e)) + return C.AdbcStatusCode(conn.errToAdbcErr(err, e)) } cdata.ExportRecordReader(rdr, toCdataStream(out)) return C.ADBC_STATUS_OK @@ -569,7 +1126,7 @@ func PanicDummyConnectionCommit(cnxn *C.struct_AdbcConnection, err *C.struct_Adb return C.ADBC_STATUS_INVALID_STATE } - return C.AdbcStatusCode(errToAdbcErr(err, conn.cnxn.Commit(context.Background()))) + return C.AdbcStatusCode(conn.errToAdbcErr(err, conn.cnxn.Commit(conn.newContext()))) } //export PanicDummyConnectionRollback @@ -584,25 +1141,154 @@ func PanicDummyConnectionRollback(cnxn *C.struct_AdbcConnection, err *C.struct_A return C.ADBC_STATUS_INVALID_STATE } - return C.AdbcStatusCode(errToAdbcErr(err, conn.cnxn.Rollback(context.Background()))) + return C.AdbcStatusCode(conn.errToAdbcErr(err, conn.cnxn.Rollback(conn.newContext()))) +} + +type cStmt struct { + cancellableContext + errorDetails + + stmt adbc.Statement } -func checkStmtInit(stmt *C.struct_AdbcStatement, err *C.struct_AdbcError, fname string) adbc.Statement { +func checkStmtAlloc(stmt *C.struct_AdbcStatement, err *C.struct_AdbcError, fname string) bool { if atomic.LoadInt32(&globalPoison) != 0 { setErr(err, "%s: Go panicked, driver is in unknown state", fname) - return nil + return false } if stmt == nil { setErr(err, "%s: statement not allocated", fname) - return nil + return false } - if stmt.private_data == nil { - setErr(err, "%s: statement not initialized", fname) + setErr(err, "%s: statement not allocated", fname) + return false + } + return true +} + +func checkStmtInit(stmt *C.struct_AdbcStatement, err *C.struct_AdbcError, fname string) *cStmt { + if !checkStmtAlloc(stmt, err, fname) { + return nil + } + cStmt := getFromHandle[cStmt](stmt.private_data) + if cStmt.stmt == nil { + setErr(err, "%s: statement not allocated", fname) return nil } + return cStmt +} + +//export PanicDummyStatementGetOption +func PanicDummyStatementGetOption(db *C.struct_AdbcStatement, key *C.cchar_t, value *C.char, length *C.size_t, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcStatementGetOption", e) + } + }() + st := checkStmtInit(db, err, "AdbcStatementGetOption") + if st == nil { + return C.ADBC_STATUS_INVALID_STATE + } + + if handled, e := st.getErrorDetailsOptionString(key, value, length); e != nil { + return C.AdbcStatusCode(errToAdbcErr(err, e)) + } else if handled { + return C.ADBC_STATUS_OK + } + + opts, ok := st.stmt.(adbc.GetSetOptions) + if !ok { + setErr(err, "AdbcStatementGetOption: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + val, e := opts.GetOption(C.GoString(key)) + if e != nil { + return C.AdbcStatusCode(st.errToAdbcErr(err, e)) + } + + return exportStringOption(val, value, length) +} + +//export PanicDummyStatementGetOptionBytes +func PanicDummyStatementGetOptionBytes(db *C.struct_AdbcStatement, key *C.cchar_t, value *C.uint8_t, length *C.size_t, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcStatementGetOptionBytes", e) + } + }() + st := checkStmtInit(db, err, "AdbcStatementGetOptionBytes") + if st == nil { + return C.ADBC_STATUS_INVALID_STATE + } + + if handled, e := st.getErrorDetailsOptionBytes(key, value, length); e != nil { + return C.AdbcStatusCode(errToAdbcErr(err, e)) + } else if handled { + return C.ADBC_STATUS_OK + } + + opts, ok := st.stmt.(adbc.GetSetOptions) + if !ok { + setErr(err, "AdbcStatementGetOptionBytes: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + val, e := opts.GetOptionBytes(C.GoString(key)) + if e != nil { + return C.AdbcStatusCode(st.errToAdbcErr(err, e)) + } + + return exportBytesOption(val, value, length) +} + +//export PanicDummyStatementGetOptionDouble +func PanicDummyStatementGetOptionDouble(db *C.struct_AdbcStatement, key *C.cchar_t, value *C.double, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcStatementGetOptionDouble", e) + } + }() + st := checkStmtInit(db, err, "AdbcStatementGetOptionDouble") + if st == nil { + return C.ADBC_STATUS_INVALID_STATE + } + + opts, ok := st.stmt.(adbc.GetSetOptions) + if !ok { + setErr(err, "AdbcStatementGetOptionDouble: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + + val, e := opts.GetOptionDouble(C.GoString(key)) + *value = C.double(val) + return C.AdbcStatusCode(st.errToAdbcErr(err, e)) +} + +//export PanicDummyStatementGetOptionInt +func PanicDummyStatementGetOptionInt(db *C.struct_AdbcStatement, key *C.cchar_t, value *C.int64_t, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcStatementGetOptionInt", e) + } + }() + st := checkStmtInit(db, err, "AdbcStatementGetOptionInt") + if st == nil { + return C.ADBC_STATUS_INVALID_STATE + } + + if st.getErrorDetailsOptionInt(key, value) { + return C.ADBC_STATUS_OK + } - return (*(*cgo.Handle)(stmt.private_data)).Value().(adbc.Statement) + opts, ok := st.stmt.(adbc.GetSetOptions) + if !ok { + setErr(err, "AdbcStatementGetOptionInt: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + + val, e := opts.GetOptionInt(C.GoString(key)) + *value = C.int64_t(val) + return C.AdbcStatusCode(st.errToAdbcErr(err, e)) } //export PanicDummyStatementNew @@ -623,11 +1309,11 @@ func PanicDummyStatementNew(cnxn *C.struct_AdbcConnection, stmt *C.struct_AdbcSt st, e := conn.cnxn.NewStatement() if e != nil { - return C.AdbcStatusCode(errToAdbcErr(err, e)) + return C.AdbcStatusCode(conn.errToAdbcErr(err, e)) } - h := cgo.NewHandle(st) - stmt.private_data = createHandle(h) + hndl := cgo.NewHandle(&cStmt{stmt: st}) + stmt.private_data = createHandle(hndl) return C.ADBC_STATUS_OK } @@ -642,31 +1328,46 @@ func PanicDummyStatementRelease(stmt *C.struct_AdbcStatement, err *C.struct_Adbc setErr(err, "AdbcStatementRelease: Go panicked, driver is in unknown state") return C.ADBC_STATUS_INTERNAL } - if stmt == nil { - setErr(err, "AdbcStatementRelease: statement not allocated") + if !checkStmtAlloc(stmt, err, "AdbcStatementRelease") { return C.ADBC_STATUS_INVALID_STATE } + h := (*(*cgo.Handle)(stmt.private_data)) - if stmt.private_data == nil { - setErr(err, "AdbcStatementRelease: statement not initialized") - return C.ADBC_STATUS_INVALID_STATE + st := h.Value().(*cStmt) + defer func() { + st.cancelContext() + st.stmt = nil + C.free(stmt.private_data) + stmt.private_data = nil + h.Delete() + // manually trigger GC for two reasons: + // 1. ASAN expects the release callback to be called before + // the process ends, but GC is not deterministic. So by manually + // triggering the GC we ensure the release callback gets called. + // 2. Creates deterministic GC behavior by all Release functions + // triggering a garbage collection + runtime.GC() + }() + if st.stmt == nil { + return C.ADBC_STATUS_OK } + return C.AdbcStatusCode(errToAdbcErr(err, st.stmt.Close())) +} - h := (*(*cgo.Handle)(stmt.private_data)) - st := h.Value().(adbc.Statement) - C.free(stmt.private_data) - stmt.private_data = nil +//export PanicDummyStatementCancel +func PanicDummyStatementCancel(stmt *C.struct_AdbcStatement, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcStatementPrepare", e) + } + }() + st := checkStmtInit(stmt, err, "AdbcStatementPrepare") + if st == nil { + return C.ADBC_STATUS_INVALID_STATE + } - e := st.Close() - h.Delete() - // manually trigger GC for two reasons: - // 1. ASAN expects the release callback to be called before - // the process ends, but GC is not deterministic. So by manually - // triggering the GC we ensure the release callback gets called. - // 2. Creates deterministic GC behavior by all Release functions - // triggering a garbage collection - runtime.GC() - return C.AdbcStatusCode(errToAdbcErr(err, e)) + st.cancelContext() + return C.ADBC_STATUS_OK } //export PanicDummyStatementPrepare @@ -681,7 +1382,7 @@ func PanicDummyStatementPrepare(stmt *C.struct_AdbcStatement, err *C.struct_Adbc return C.ADBC_STATUS_INVALID_STATE } - return C.AdbcStatusCode(errToAdbcErr(err, st.Prepare(context.Background()))) + return C.AdbcStatusCode(st.errToAdbcErr(err, st.stmt.Prepare(st.newContext()))) } //export PanicDummyStatementExecuteQuery @@ -697,29 +1398,57 @@ func PanicDummyStatementExecuteQuery(stmt *C.struct_AdbcStatement, out *C.struct } if out == nil { - n, e := st.ExecuteUpdate(context.Background()) + n, e := st.stmt.ExecuteUpdate(st.newContext()) if e != nil { - return C.AdbcStatusCode(errToAdbcErr(err, e)) + return C.AdbcStatusCode(st.errToAdbcErr(err, e)) } if affected != nil { *affected = C.int64_t(n) } } else { - rdr, n, e := st.ExecuteQuery(context.Background()) + rdr, n, e := st.stmt.ExecuteQuery(st.newContext()) if e != nil { - return C.AdbcStatusCode(errToAdbcErr(err, e)) + return C.AdbcStatusCode(st.errToAdbcErr(err, e)) } if affected != nil { *affected = C.int64_t(n) } + // TODO: proxy the reader and record error details (and do this elsewhere, too) cdata.ExportRecordReader(rdr, toCdataStream(out)) } return C.ADBC_STATUS_OK } +//export PanicDummyStatementExecuteSchema +func PanicDummyStatementExecuteSchema(stmt *C.struct_AdbcStatement, schema *C.struct_ArrowSchema, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcStatementExecuteQuery", e) + } + }() + st := checkStmtInit(stmt, err, "AdbcStatementExecuteQuery") + if st == nil { + return C.ADBC_STATUS_INVALID_STATE + } + + es, ok := st.stmt.(adbc.StatementExecuteSchema) + if !ok { + setErr(err, "AdbcStatementExecuteSchema: not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + + sc, e := es.ExecuteSchema(st.newContext()) + if e != nil { + return C.AdbcStatusCode(st.errToAdbcErr(err, e)) + } + + cdata.ExportArrowSchema(sc, toCdataSchema(schema)) + return C.ADBC_STATUS_OK +} + //export PanicDummyStatementSetSqlQuery func PanicDummyStatementSetSqlQuery(stmt *C.struct_AdbcStatement, query *C.cchar_t, err *C.struct_AdbcError) (code C.AdbcStatusCode) { defer func() { @@ -732,7 +1461,7 @@ func PanicDummyStatementSetSqlQuery(stmt *C.struct_AdbcStatement, query *C.cchar return C.ADBC_STATUS_INVALID_STATE } - return C.AdbcStatusCode(errToAdbcErr(err, st.SetSqlQuery(C.GoString(query)))) + return C.AdbcStatusCode(st.errToAdbcErr(err, st.stmt.SetSqlQuery(C.GoString(query)))) } //export PanicDummyStatementSetSubstraitPlan @@ -747,7 +1476,7 @@ func PanicDummyStatementSetSubstraitPlan(stmt *C.struct_AdbcStatement, plan *C.c return C.ADBC_STATUS_INVALID_STATE } - return C.AdbcStatusCode(errToAdbcErr(err, st.SetSubstraitPlan(fromCArr[byte](plan, int(length))))) + return C.AdbcStatusCode(st.errToAdbcErr(err, st.stmt.SetSubstraitPlan(fromCArr[byte](plan, int(length))))) } //export PanicDummyStatementBind @@ -766,11 +1495,11 @@ func PanicDummyStatementBind(stmt *C.struct_AdbcStatement, values *C.struct_Arro if e != nil { // if there was an error, we need to manually release the input cdata.ReleaseCArrowArray(toCdataArray(values)) - return C.AdbcStatusCode(errToAdbcErr(err, e)) + return C.AdbcStatusCode(st.errToAdbcErr(err, e)) } defer rec.Release() - return C.AdbcStatusCode(errToAdbcErr(err, st.Bind(context.Background(), rec))) + return C.AdbcStatusCode(st.errToAdbcErr(err, st.stmt.Bind(st.newContext(), rec))) } //export PanicDummyStatementBindStream @@ -787,9 +1516,9 @@ func PanicDummyStatementBindStream(stmt *C.struct_AdbcStatement, stream *C.struc rdr, e := cdata.ImportCRecordReader(toCdataStream(stream), nil) if e != nil { - return C.AdbcStatusCode(errToAdbcErr(err, e)) + return C.AdbcStatusCode(st.errToAdbcErr(err, e)) } - return C.AdbcStatusCode(errToAdbcErr(err, st.BindStream(context.Background(), rdr.(array.RecordReader)))) + return C.AdbcStatusCode(st.errToAdbcErr(err, st.stmt.BindStream(st.newContext(), rdr.(array.RecordReader)))) } //export PanicDummyStatementGetParameterSchema @@ -804,9 +1533,9 @@ func PanicDummyStatementGetParameterSchema(stmt *C.struct_AdbcStatement, schema return C.ADBC_STATUS_INVALID_STATE } - sc, e := st.GetParameterSchema() + sc, e := st.stmt.GetParameterSchema() if e != nil { - return C.AdbcStatusCode(errToAdbcErr(err, e)) + return C.AdbcStatusCode(st.errToAdbcErr(err, e)) } cdata.ExportArrowSchema(sc, toCdataSchema(schema)) @@ -825,7 +1554,70 @@ func PanicDummyStatementSetOption(stmt *C.struct_AdbcStatement, key, value *C.cc return C.ADBC_STATUS_INVALID_STATE } - return C.AdbcStatusCode(errToAdbcErr(err, st.SetOption(C.GoString(key), C.GoString(value)))) + return C.AdbcStatusCode(st.errToAdbcErr(err, st.stmt.SetOption(C.GoString(key), C.GoString(value)))) +} + +//export PanicDummyStatementSetOptionBytes +func PanicDummyStatementSetOptionBytes(db *C.struct_AdbcStatement, key *C.cchar_t, value *C.cuint8_t, length C.size_t, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcStatementSetOptionBytes", e) + } + }() + st := checkStmtInit(db, err, "AdbcStatementSetOptionBytes") + if st == nil { + return C.ADBC_STATUS_INVALID_STATE + } + + opts, ok := st.stmt.(adbc.GetSetOptions) + if !ok { + setErr(err, "AdbcStatementSetOptionBytes: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + + return C.AdbcStatusCode(st.errToAdbcErr(err, opts.SetOptionBytes(C.GoString(key), fromCArr[byte](value, int(length))))) +} + +//export PanicDummyStatementSetOptionDouble +func PanicDummyStatementSetOptionDouble(db *C.struct_AdbcStatement, key *C.cchar_t, value C.double, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcStatementSetOptionDouble", e) + } + }() + st := checkStmtInit(db, err, "AdbcStatementSetOptionDouble") + if st == nil { + return C.ADBC_STATUS_INVALID_STATE + } + + opts, ok := st.stmt.(adbc.GetSetOptions) + if !ok { + setErr(err, "AdbcStatementSetOptionDouble: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + + return C.AdbcStatusCode(st.errToAdbcErr(err, opts.SetOptionDouble(C.GoString(key), float64(value)))) +} + +//export PanicDummyStatementSetOptionInt +func PanicDummyStatementSetOptionInt(db *C.struct_AdbcStatement, key *C.cchar_t, value C.int64_t, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcStatementSetOptionInt", e) + } + }() + st := checkStmtInit(db, err, "AdbcStatementSetOptionInt") + if st == nil { + return C.ADBC_STATUS_INVALID_STATE + } + + opts, ok := st.stmt.(adbc.GetSetOptions) + if !ok { + setErr(err, "AdbcStatementSetOptionInt: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + + return C.AdbcStatusCode(st.errToAdbcErr(err, opts.SetOptionInt(C.GoString(key), int64(value)))) } //export releasePartitions @@ -854,9 +1646,9 @@ func PanicDummyStatementExecutePartitions(stmt *C.struct_AdbcStatement, schema * return C.ADBC_STATUS_INVALID_STATE } - sc, part, n, e := st.ExecutePartitions(context.Background()) + sc, part, n, e := st.stmt.ExecutePartitions(st.newContext()) if e != nil { - return C.AdbcStatusCode(errToAdbcErr(err, e)) + return C.AdbcStatusCode(st.errToAdbcErr(err, e)) } if partitions == nil { @@ -899,13 +1691,20 @@ func PanicDummyStatementExecutePartitions(stmt *C.struct_AdbcStatement, schema * //export PanicDummyDriverInit func PanicDummyDriverInit(version C.int, rawDriver *C.void, err *C.struct_AdbcError) C.AdbcStatusCode { - if version != C.ADBC_VERSION_1_0_0 { - setErr(err, "Only version %d supported, got %d", int(C.ADBC_VERSION_1_0_0), int(version)) + driver := (*C.struct_AdbcDriver)(unsafe.Pointer(rawDriver)) + + switch version { + case C.ADBC_VERSION_1_0_0: + sink := fromCArr[byte]((*byte)(unsafe.Pointer(driver)), C.ADBC_DRIVER_1_0_0_SIZE) + memory.Set(sink, 0) + case C.ADBC_VERSION_1_1_0: + sink := fromCArr[byte]((*byte)(unsafe.Pointer(driver)), C.ADBC_DRIVER_1_1_0_SIZE) + memory.Set(sink, 0) + default: + setErr(err, "Only version 1.0.0/1.1.0 supported, got %d", int(version)) return C.ADBC_STATUS_NOT_IMPLEMENTED } - driver := (*C.struct_AdbcDriver)(unsafe.Pointer(rawDriver)) - C.memset(unsafe.Pointer(driver), 0, C.sizeof_struct_AdbcDriver) driver.DatabaseInit = (*[0]byte)(C.PanicDummyDatabaseInit) driver.DatabaseNew = (*[0]byte)(C.PanicDummyDatabaseNew) driver.DatabaseRelease = (*[0]byte)(C.PanicDummyDatabaseRelease) @@ -935,6 +1734,37 @@ func PanicDummyDriverInit(version C.int, rawDriver *C.void, err *C.struct_AdbcEr driver.StatementGetParameterSchema = (*[0]byte)(C.PanicDummyStatementGetParameterSchema) driver.StatementPrepare = (*[0]byte)(C.PanicDummyStatementPrepare) + if version == C.ADBC_VERSION_1_1_0 { + driver.DatabaseGetOption = (*[0]byte)(C.PanicDummyDatabaseGetOption) + driver.DatabaseGetOptionBytes = (*[0]byte)(C.PanicDummyDatabaseGetOptionBytes) + driver.DatabaseGetOptionDouble = (*[0]byte)(C.PanicDummyDatabaseGetOptionDouble) + driver.DatabaseGetOptionInt = (*[0]byte)(C.PanicDummyDatabaseGetOptionInt) + driver.DatabaseSetOptionBytes = (*[0]byte)(C.PanicDummyDatabaseSetOptionBytes) + driver.DatabaseSetOptionDouble = (*[0]byte)(C.PanicDummyDatabaseSetOptionDouble) + driver.DatabaseSetOptionInt = (*[0]byte)(C.PanicDummyDatabaseSetOptionInt) + + driver.ConnectionCancel = (*[0]byte)(C.PanicDummyConnectionCancel) + driver.ConnectionGetOption = (*[0]byte)(C.PanicDummyConnectionGetOption) + driver.ConnectionGetOptionBytes = (*[0]byte)(C.PanicDummyConnectionGetOptionBytes) + driver.ConnectionGetOptionDouble = (*[0]byte)(C.PanicDummyConnectionGetOptionDouble) + driver.ConnectionGetOptionInt = (*[0]byte)(C.PanicDummyConnectionGetOptionInt) + driver.ConnectionGetStatistics = (*[0]byte)(C.PanicDummyConnectionGetStatistics) + driver.ConnectionGetStatisticNames = (*[0]byte)(C.PanicDummyConnectionGetStatisticNames) + driver.ConnectionSetOptionBytes = (*[0]byte)(C.PanicDummyConnectionSetOptionBytes) + driver.ConnectionSetOptionDouble = (*[0]byte)(C.PanicDummyConnectionSetOptionDouble) + driver.ConnectionSetOptionInt = (*[0]byte)(C.PanicDummyConnectionSetOptionInt) + + driver.StatementCancel = (*[0]byte)(C.PanicDummyStatementCancel) + driver.StatementExecuteSchema = (*[0]byte)(C.PanicDummyStatementExecuteSchema) + driver.StatementGetOption = (*[0]byte)(C.PanicDummyStatementGetOption) + driver.StatementGetOptionBytes = (*[0]byte)(C.PanicDummyStatementGetOptionBytes) + driver.StatementGetOptionDouble = (*[0]byte)(C.PanicDummyStatementGetOptionDouble) + driver.StatementGetOptionInt = (*[0]byte)(C.PanicDummyStatementGetOptionInt) + driver.StatementSetOptionBytes = (*[0]byte)(C.PanicDummyStatementSetOptionBytes) + driver.StatementSetOptionDouble = (*[0]byte)(C.PanicDummyStatementSetOptionDouble) + driver.StatementSetOptionInt = (*[0]byte)(C.PanicDummyStatementSetOptionInt) + } + return C.ADBC_STATUS_OK } diff --git a/go/adbc/pkg/panicdummy/utils.h b/go/adbc/pkg/panicdummy/utils.h index f3b3aae13e..094000360d 100644 --- a/go/adbc/pkg/panicdummy/utils.h +++ b/go/adbc/pkg/panicdummy/utils.h @@ -26,19 +26,30 @@ #include #include "../../drivermgr/adbc.h" +AdbcStatusCode PanicDummyDatabaseGetOption(struct AdbcDatabase*, const char*, char*, + size_t*, struct AdbcError*); +AdbcStatusCode PanicDummyDatabaseGetOptionBytes(struct AdbcDatabase*, const char*, + uint8_t*, size_t*, struct AdbcError*); +AdbcStatusCode PanicDummyDatabaseGetOptionDouble(struct AdbcDatabase*, const char*, + double*, struct AdbcError*); +AdbcStatusCode PanicDummyDatabaseGetOptionInt(struct AdbcDatabase*, const char*, int64_t*, + struct AdbcError*); +AdbcStatusCode PanicDummyDatabaseInit(struct AdbcDatabase* db, struct AdbcError* err); AdbcStatusCode PanicDummyDatabaseNew(struct AdbcDatabase* db, struct AdbcError* err); +AdbcStatusCode PanicDummyDatabaseRelease(struct AdbcDatabase* db, struct AdbcError* err); AdbcStatusCode PanicDummyDatabaseSetOption(struct AdbcDatabase* db, const char* key, const char* value, struct AdbcError* err); -AdbcStatusCode PanicDummyDatabaseInit(struct AdbcDatabase* db, struct AdbcError* err); -AdbcStatusCode PanicDummyDatabaseRelease(struct AdbcDatabase* db, struct AdbcError* err); -AdbcStatusCode PanicDummyConnectionNew(struct AdbcConnection* cnxn, - struct AdbcError* err); -AdbcStatusCode PanicDummyConnectionSetOption(struct AdbcConnection* cnxn, const char* key, - const char* val, struct AdbcError* err); -AdbcStatusCode PanicDummyConnectionInit(struct AdbcConnection* cnxn, - struct AdbcDatabase* db, struct AdbcError* err); -AdbcStatusCode PanicDummyConnectionRelease(struct AdbcConnection* cnxn, - struct AdbcError* err); +AdbcStatusCode PanicDummyDatabaseSetOptionBytes(struct AdbcDatabase*, const char*, + const uint8_t*, size_t, + struct AdbcError*); +AdbcStatusCode PanicDummyDatabaseSetOptionDouble(struct AdbcDatabase*, const char*, + double, struct AdbcError*); +AdbcStatusCode PanicDummyDatabaseSetOptionInt(struct AdbcDatabase*, const char*, int64_t, + struct AdbcError*); + +AdbcStatusCode PanicDummyConnectionCancel(struct AdbcConnection*, struct AdbcError*); +AdbcStatusCode PanicDummyConnectionCommit(struct AdbcConnection* cnxn, + struct AdbcError* err); AdbcStatusCode PanicDummyConnectionGetInfo(struct AdbcConnection* cnxn, uint32_t* codes, size_t len, struct ArrowArrayStream* out, struct AdbcError* err); @@ -46,51 +57,99 @@ AdbcStatusCode PanicDummyConnectionGetObjects( struct AdbcConnection* cnxn, int depth, const char* catalog, const char* dbSchema, const char* tableName, const char** tableType, const char* columnName, struct ArrowArrayStream* out, struct AdbcError* err); +AdbcStatusCode PanicDummyConnectionGetOption(struct AdbcConnection*, const char*, char*, + size_t*, struct AdbcError*); +AdbcStatusCode PanicDummyConnectionGetOptionBytes(struct AdbcConnection*, const char*, + uint8_t*, size_t*, struct AdbcError*); +AdbcStatusCode PanicDummyConnectionGetOptionDouble(struct AdbcConnection*, const char*, + double*, struct AdbcError*); +AdbcStatusCode PanicDummyConnectionGetOptionInt(struct AdbcConnection*, const char*, + int64_t*, struct AdbcError*); +AdbcStatusCode PanicDummyConnectionGetStatistics(struct AdbcConnection*, const char*, + const char*, const char*, char, + struct ArrowArrayStream*, + struct AdbcError*); +AdbcStatusCode PanicDummyConnectionGetStatisticNames(struct AdbcConnection*, + struct ArrowArrayStream*, + struct AdbcError*); AdbcStatusCode PanicDummyConnectionGetTableSchema( struct AdbcConnection* cnxn, const char* catalog, const char* dbSchema, const char* tableName, struct ArrowSchema* schema, struct AdbcError* err); AdbcStatusCode PanicDummyConnectionGetTableTypes(struct AdbcConnection* cnxn, struct ArrowArrayStream* out, struct AdbcError* err); +AdbcStatusCode PanicDummyConnectionInit(struct AdbcConnection* cnxn, + struct AdbcDatabase* db, struct AdbcError* err); +AdbcStatusCode PanicDummyConnectionNew(struct AdbcConnection* cnxn, + struct AdbcError* err); AdbcStatusCode PanicDummyConnectionReadPartition(struct AdbcConnection* cnxn, const uint8_t* serialized, size_t serializedLen, struct ArrowArrayStream* out, struct AdbcError* err); -AdbcStatusCode PanicDummyConnectionCommit(struct AdbcConnection* cnxn, - struct AdbcError* err); +AdbcStatusCode PanicDummyConnectionRelease(struct AdbcConnection* cnxn, + struct AdbcError* err); AdbcStatusCode PanicDummyConnectionRollback(struct AdbcConnection* cnxn, struct AdbcError* err); -AdbcStatusCode PanicDummyStatementNew(struct AdbcConnection* cnxn, - struct AdbcStatement* stmt, struct AdbcError* err); -AdbcStatusCode PanicDummyStatementRelease(struct AdbcStatement* stmt, - struct AdbcError* err); -AdbcStatusCode PanicDummyStatementPrepare(struct AdbcStatement* stmt, - struct AdbcError* err); -AdbcStatusCode PanicDummyStatementExecuteQuery(struct AdbcStatement* stmt, - struct ArrowArrayStream* out, - int64_t* affected, struct AdbcError* err); -AdbcStatusCode PanicDummyStatementSetSqlQuery(struct AdbcStatement* stmt, - const char* query, struct AdbcError* err); -AdbcStatusCode PanicDummyStatementSetSubstraitPlan(struct AdbcStatement* stmt, - const uint8_t* plan, size_t length, - struct AdbcError* err); +AdbcStatusCode PanicDummyConnectionSetOption(struct AdbcConnection* cnxn, const char* key, + const char* val, struct AdbcError* err); +AdbcStatusCode PanicDummyConnectionSetOptionBytes(struct AdbcConnection*, const char*, + const uint8_t*, size_t, + struct AdbcError*); +AdbcStatusCode PanicDummyConnectionSetOptionDouble(struct AdbcConnection*, const char*, + double, struct AdbcError*); +AdbcStatusCode PanicDummyConnectionSetOptionInt(struct AdbcConnection*, const char*, + int64_t, struct AdbcError*); + AdbcStatusCode PanicDummyStatementBind(struct AdbcStatement* stmt, struct ArrowArray* values, struct ArrowSchema* schema, struct AdbcError* err); AdbcStatusCode PanicDummyStatementBindStream(struct AdbcStatement* stmt, struct ArrowArrayStream* stream, struct AdbcError* err); -AdbcStatusCode PanicDummyStatementGetParameterSchema(struct AdbcStatement* stmt, - struct ArrowSchema* schema, - struct AdbcError* err); -AdbcStatusCode PanicDummyStatementSetOption(struct AdbcStatement* stmt, const char* key, - const char* value, struct AdbcError* err); +AdbcStatusCode PanicDummyStatementCancel(struct AdbcStatement*, struct AdbcError*); +AdbcStatusCode PanicDummyStatementExecuteQuery(struct AdbcStatement* stmt, + struct ArrowArrayStream* out, + int64_t* affected, struct AdbcError* err); AdbcStatusCode PanicDummyStatementExecutePartitions(struct AdbcStatement* stmt, struct ArrowSchema* schema, struct AdbcPartitions* partitions, int64_t* affected, struct AdbcError* err); +AdbcStatusCode PanicDummyStatementExecuteSchema(struct AdbcStatement*, + struct ArrowSchema*, struct AdbcError*); +AdbcStatusCode PanicDummyStatementGetOption(struct AdbcStatement*, const char*, char*, + size_t*, struct AdbcError*); +AdbcStatusCode PanicDummyStatementGetOptionBytes(struct AdbcStatement*, const char*, + uint8_t*, size_t*, struct AdbcError*); +AdbcStatusCode PanicDummyStatementGetOptionDouble(struct AdbcStatement*, const char*, + double*, struct AdbcError*); +AdbcStatusCode PanicDummyStatementGetOptionInt(struct AdbcStatement*, const char*, + int64_t*, struct AdbcError*); +AdbcStatusCode PanicDummyStatementGetParameterSchema(struct AdbcStatement* stmt, + struct ArrowSchema* schema, + struct AdbcError* err); +AdbcStatusCode PanicDummyStatementNew(struct AdbcConnection* cnxn, + struct AdbcStatement* stmt, struct AdbcError* err); +AdbcStatusCode PanicDummyStatementPrepare(struct AdbcStatement* stmt, + struct AdbcError* err); +AdbcStatusCode PanicDummyStatementRelease(struct AdbcStatement* stmt, + struct AdbcError* err); +AdbcStatusCode PanicDummyStatementSetOption(struct AdbcStatement* stmt, const char* key, + const char* value, struct AdbcError* err); +AdbcStatusCode PanicDummyStatementSetOptionBytes(struct AdbcStatement*, const char*, + const uint8_t*, size_t, + struct AdbcError*); +AdbcStatusCode PanicDummyStatementSetOptionDouble(struct AdbcStatement*, const char*, + double, struct AdbcError*); +AdbcStatusCode PanicDummyStatementSetOptionInt(struct AdbcStatement*, const char*, + int64_t, struct AdbcError*); +AdbcStatusCode PanicDummyStatementSetSqlQuery(struct AdbcStatement* stmt, + const char* query, struct AdbcError* err); +AdbcStatusCode PanicDummyStatementSetSubstraitPlan(struct AdbcStatement* stmt, + const uint8_t* plan, size_t length, + struct AdbcError* err); + AdbcStatusCode PanicDummyDriverInit(int version, void* rawDriver, struct AdbcError* err); static inline void PanicDummyerrRelease(struct AdbcError* error) { diff --git a/go/adbc/pkg/snowflake/driver.go b/go/adbc/pkg/snowflake/driver.go index 35dc0ce883..1ae432d977 100644 --- a/go/adbc/pkg/snowflake/driver.go +++ b/go/adbc/pkg/snowflake/driver.go @@ -40,6 +40,8 @@ import ( "os" "runtime" "runtime/cgo" + "strconv" + "strings" "sync/atomic" "unsafe" @@ -47,6 +49,7 @@ import ( "github.com/apache/arrow-adbc/go/adbc/driver/snowflake" "github.com/apache/arrow/go/v13/arrow/array" "github.com/apache/arrow/go/v13/arrow/cdata" + "github.com/apache/arrow/go/v13/arrow/memory" "github.com/apache/arrow/go/v13/arrow/memory/mallocator" ) @@ -75,7 +78,7 @@ func setErr(err *C.struct_AdbcError, format string, vals ...interface{}) { } func errToAdbcErr(adbcerr *C.struct_AdbcError, err error) adbc.Status { - if adbcerr == nil || err == nil { + if err == nil { return adbc.StatusOK } @@ -89,6 +92,87 @@ func errToAdbcErr(adbcerr *C.struct_AdbcError, err error) adbc.Status { return adbc.StatusUnknown } +// errorDetails handles the standard error details feature in ADBC. +type errorDetails struct { + errorDetails []adbc.ErrorDetail +} + +func (ed *errorDetails) errToAdbcErr(adbcerr *C.struct_AdbcError, err error) adbc.Status { + ed.errorDetails = nil + if err == nil { + return adbc.StatusOK + } + + var adbcError adbc.Error + if errors.As(err, &adbcError) { + setErr(adbcerr, adbcError.Msg) + ed.errorDetails = adbcError.Details + return adbcError.Code + } + + setErr(adbcerr, err.Error()) + return adbc.StatusUnknown +} + +func (ed *errorDetails) getErrorDetailsOptionInt(key *C.cchar_t, value *C.int64_t) bool { + if C.GoString(key) == C.ADBC_OPTION_ERROR_DETAILS { + *value = C.int64_t(len(ed.errorDetails)) + return true + } + return false +} + +func (ed *errorDetails) getErrorDetailsOptionString(key *C.cchar_t, value *C.char, length *C.size_t) (bool, error) { + gokey := C.GoString(key) + if strings.HasPrefix(gokey, C.ADBC_OPTION_ERROR_DETAILS_PREFIX) { + indexStr := strings.TrimPrefix(gokey, C.ADBC_OPTION_ERROR_DETAILS_PREFIX) + index, err := strconv.ParseInt(indexStr, 10, 32) + if err != nil { + return false, adbc.Error{ + Code: adbc.StatusInvalidArgument, + Msg: fmt.Sprintf("[Snowflake] Invalid error details option key '%s': %s", index, err.Error()), + } + } else if index < 0 || index >= int64(len(ed.errorDetails)) { + return false, adbc.Error{ + Code: adbc.StatusInvalidArgument, + Msg: fmt.Sprintf("[Snowflake] Invalid error details option key '%s': out of range", index), + } + } + exportStringOption(ed.errorDetails[int(index)].Key(), value, length) + return true, nil + } + return false, nil +} + +func (ed *errorDetails) getErrorDetailsOptionBytes(key *C.cchar_t, value *C.uint8_t, length *C.size_t) (bool, error) { + gokey := C.GoString(key) + if strings.HasPrefix(gokey, C.ADBC_OPTION_ERROR_DETAILS_PREFIX) { + indexStr := strings.TrimPrefix(gokey, C.ADBC_OPTION_ERROR_DETAILS_PREFIX) + index, err := strconv.ParseInt(indexStr, 10, 32) + if err != nil { + return false, adbc.Error{ + Code: adbc.StatusInvalidArgument, + Msg: fmt.Sprintf("[Snowflake] Invalid error details option key '%s': %s", index, err.Error()), + } + } else if index < 0 || index >= int64(len(ed.errorDetails)) { + return false, adbc.Error{ + Code: adbc.StatusInvalidArgument, + Msg: fmt.Sprintf("[Snowflake] Invalid error details option key '%s': out of range", index), + } + } + serialized, err := ed.errorDetails[int(index)].Serialize() + if err != nil { + return false, adbc.Error{ + Code: adbc.StatusInternal, + Msg: fmt.Sprintf("[Snowflake] Could not serialize error details: %s", err.Error()), + } + } + exportBytesOption(serialized, value, length) + return true, nil + } + return false, nil +} + // We panicked; make all API functions error and dump stack traces func poison(err *C.struct_AdbcError, fname string, e interface{}) C.AdbcStatusCode { if atomic.SwapInt32(&globalPoison, 1) == 0 { @@ -119,6 +203,45 @@ func getFromHandle[T any](ptr unsafe.Pointer) *T { return cgo.Handle((uintptr)(*hptr)).Value().(*T) } +func exportStringOption(val string, out *C.char, length *C.size_t) C.AdbcStatusCode { + lenWithTerminator := C.size_t(len(val) + 1) + if lenWithTerminator <= *length { + sink := fromCArr[byte]((*byte)(unsafe.Pointer(out)), int(*length)) + copy(sink, val) + sink[lenWithTerminator] = 0 + } + *length = lenWithTerminator + return C.ADBC_STATUS_OK +} + +func exportBytesOption(val []byte, out *C.uint8_t, length *C.size_t) C.AdbcStatusCode { + if C.size_t(len(val)) <= *length { + sink := fromCArr[byte]((*byte)(out), int(*length)) + copy(sink, val) + } + *length = C.size_t(len(val)) + return C.ADBC_STATUS_OK +} + +type cancellableContext struct { + ctx context.Context + cancel context.CancelFunc +} + +func (c *cancellableContext) newContext() context.Context { + c.cancelContext() + c.ctx, c.cancel = context.WithCancel(context.Background()) + return c.ctx +} + +func (c *cancellableContext) cancelContext() { + if c.cancel != nil { + c.cancel() + } + c.ctx = nil + c.cancel = nil +} + func checkDBAlloc(db *C.struct_AdbcDatabase, err *C.struct_AdbcError, fname string) bool { if atomic.LoadInt32(&globalPoison) != 0 { setErr(err, "%s: Go panicked, driver is in unknown state", fname) @@ -149,47 +272,122 @@ func checkDBInit(db *C.struct_AdbcDatabase, err *C.struct_AdbcError, fname strin } type cDatabase struct { + errorDetails + opts map[string]string db adbc.Database } -//export SnowflakeDatabaseNew -func SnowflakeDatabaseNew(db *C.struct_AdbcDatabase, err *C.struct_AdbcError) (code C.AdbcStatusCode) { +//export SnowflakeDatabaseGetOption +func SnowflakeDatabaseGetOption(db *C.struct_AdbcDatabase, key *C.cchar_t, value *C.char, length *C.size_t, err *C.struct_AdbcError) (code C.AdbcStatusCode) { defer func() { if e := recover(); e != nil { - code = poison(err, "AdbcDatabaseNew", e) + code = poison(err, "AdbcDatabaseGetOption", e) } }() - if atomic.LoadInt32(&globalPoison) != 0 { - setErr(err, "AdbcDatabaseNew: Go panicked, driver is in unknown state") - return C.ADBC_STATUS_INTERNAL + cdb := checkDBInit(db, err, "AdbcDatabaseGetOption") + if cdb == nil { + return C.ADBC_STATUS_INVALID_STATE } - if db.private_data != nil { - setErr(err, "AdbcDatabaseNew: database already allocated") + + if handled, e := cdb.getErrorDetailsOptionString(key, value, length); e != nil { + return C.AdbcStatusCode(errToAdbcErr(err, e)) + } else if handled { + return C.ADBC_STATUS_OK + } + + opts, ok := cdb.db.(adbc.GetSetOptions) + if !ok { + setErr(err, "AdbcDatabaseGetOption: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + val, e := opts.GetOption(C.GoString(key)) + if e != nil { + return C.AdbcStatusCode(cdb.errToAdbcErr(err, e)) + } + + return exportStringOption(val, value, length) +} + +//export SnowflakeDatabaseGetOptionBytes +func SnowflakeDatabaseGetOptionBytes(db *C.struct_AdbcDatabase, key *C.cchar_t, value *C.uint8_t, length *C.size_t, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcDatabaseGetOptionBytes", e) + } + }() + cdb := checkDBInit(db, err, "AdbcDatabaseGetOptionBytes") + if cdb == nil { return C.ADBC_STATUS_INVALID_STATE } - dbobj := &cDatabase{opts: make(map[string]string)} - hndl := cgo.NewHandle(dbobj) - db.private_data = createHandle(hndl) - return C.ADBC_STATUS_OK + + if handled, e := cdb.getErrorDetailsOptionBytes(key, value, length); e != nil { + return C.AdbcStatusCode(errToAdbcErr(err, e)) + } else if handled { + return C.ADBC_STATUS_OK + } + + opts, ok := cdb.db.(adbc.GetSetOptions) + if !ok { + setErr(err, "AdbcDatabaseGetOptionBytes: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + val, e := opts.GetOptionBytes(C.GoString(key)) + if e != nil { + return C.AdbcStatusCode(cdb.errToAdbcErr(err, e)) + } + + return exportBytesOption(val, value, length) } -//export SnowflakeDatabaseSetOption -func SnowflakeDatabaseSetOption(db *C.struct_AdbcDatabase, key, value *C.cchar_t, err *C.struct_AdbcError) (code C.AdbcStatusCode) { +//export SnowflakeDatabaseGetOptionDouble +func SnowflakeDatabaseGetOptionDouble(db *C.struct_AdbcDatabase, key *C.cchar_t, value *C.double, err *C.struct_AdbcError) (code C.AdbcStatusCode) { defer func() { if e := recover(); e != nil { - code = poison(err, "AdbcDatabaseSetOption", e) + code = poison(err, "AdbcDatabaseGetOptionDouble", e) } }() - if !checkDBAlloc(db, err, "AdbcDatabaseSetOption") { + cdb := checkDBInit(db, err, "AdbcDatabaseGetOptionDouble") + if cdb == nil { return C.ADBC_STATUS_INVALID_STATE } - cdb := getFromHandle[cDatabase](db.private_data) - k, v := C.GoString(key), C.GoString(value) - cdb.opts[k] = v + opts, ok := cdb.db.(adbc.GetSetOptions) + if !ok { + setErr(err, "AdbcDatabaseGetOptionDouble: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } - return C.ADBC_STATUS_OK + val, e := opts.GetOptionDouble(C.GoString(key)) + *value = C.double(val) + return C.AdbcStatusCode(cdb.errToAdbcErr(err, e)) +} + +//export SnowflakeDatabaseGetOptionInt +func SnowflakeDatabaseGetOptionInt(db *C.struct_AdbcDatabase, key *C.cchar_t, value *C.int64_t, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcDatabaseGetOptionInt", e) + } + }() + cdb := checkDBInit(db, err, "AdbcDatabaseGetOptionInt") + if cdb == nil { + return C.ADBC_STATUS_INVALID_STATE + } + + if cdb.getErrorDetailsOptionInt(key, value) { + return C.ADBC_STATUS_OK + } + + opts, ok := cdb.db.(adbc.GetSetOptions) + if !ok { + setErr(err, "AdbcDatabaseGetOptionInt: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + + val, e := opts.GetOptionInt(C.GoString(key)) + *value = C.int64_t(val) + return C.AdbcStatusCode(cdb.errToAdbcErr(err, e)) } //export SnowflakeDatabaseInit @@ -218,6 +416,27 @@ func SnowflakeDatabaseInit(db *C.struct_AdbcDatabase, err *C.struct_AdbcError) ( return C.ADBC_STATUS_OK } +//export SnowflakeDatabaseNew +func SnowflakeDatabaseNew(db *C.struct_AdbcDatabase, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcDatabaseNew", e) + } + }() + if atomic.LoadInt32(&globalPoison) != 0 { + setErr(err, "AdbcDatabaseNew: Go panicked, driver is in unknown state") + return C.ADBC_STATUS_INTERNAL + } + if db.private_data != nil { + setErr(err, "AdbcDatabaseNew: database already allocated") + return C.ADBC_STATUS_INVALID_STATE + } + dbobj := &cDatabase{opts: make(map[string]string)} + hndl := cgo.NewHandle(dbobj) + db.private_data = createHandle(hndl) + return C.ADBC_STATUS_OK +} + //export SnowflakeDatabaseRelease func SnowflakeDatabaseRelease(db *C.struct_AdbcDatabase, err *C.struct_AdbcError) (code C.AdbcStatusCode) { defer func() { @@ -246,7 +465,100 @@ func SnowflakeDatabaseRelease(db *C.struct_AdbcDatabase, err *C.struct_AdbcError return C.ADBC_STATUS_OK } +//export SnowflakeDatabaseSetOption +func SnowflakeDatabaseSetOption(db *C.struct_AdbcDatabase, key, value *C.cchar_t, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcDatabaseSetOption", e) + } + }() + if !checkDBAlloc(db, err, "AdbcDatabaseSetOption") { + return C.ADBC_STATUS_INVALID_STATE + } + cdb := getFromHandle[cDatabase](db.private_data) + + k, v := C.GoString(key), C.GoString(value) + if cdb.db != nil { + opts, ok := cdb.db.(adbc.PostInitOptions) + if !ok { + setErr(err, "AdbcDatabaseSetOption: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + return C.AdbcStatusCode(cdb.errToAdbcErr(err, opts.SetOption(k, v))) + } else { + cdb.opts[k] = v + } + + return C.ADBC_STATUS_OK +} + +//export SnowflakeDatabaseSetOptionBytes +func SnowflakeDatabaseSetOptionBytes(db *C.struct_AdbcDatabase, key *C.cchar_t, value *C.cuint8_t, length C.size_t, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcDatabaseSetOptionBytes", e) + } + }() + cdb := checkDBInit(db, err, "AdbcDatabaseSetOptionBytes") + if cdb == nil { + return C.ADBC_STATUS_INVALID_STATE + } + + opts, ok := cdb.db.(adbc.GetSetOptions) + if !ok { + setErr(err, "AdbcDatabaseSetOptionBytes: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + + return C.AdbcStatusCode(cdb.errToAdbcErr(err, opts.SetOptionBytes(C.GoString(key), fromCArr[byte](value, int(length))))) +} + +//export SnowflakeDatabaseSetOptionDouble +func SnowflakeDatabaseSetOptionDouble(db *C.struct_AdbcDatabase, key *C.cchar_t, value C.double, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcDatabaseSetOptionDouble", e) + } + }() + cdb := checkDBInit(db, err, "AdbcDatabaseSetOptionDouble") + if cdb == nil { + return C.ADBC_STATUS_INVALID_STATE + } + + opts, ok := cdb.db.(adbc.GetSetOptions) + if !ok { + setErr(err, "AdbcDatabaseSetOptionDouble: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + + return C.AdbcStatusCode(cdb.errToAdbcErr(err, opts.SetOptionDouble(C.GoString(key), float64(value)))) +} + +//export SnowflakeDatabaseSetOptionInt +func SnowflakeDatabaseSetOptionInt(db *C.struct_AdbcDatabase, key *C.cchar_t, value C.int64_t, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcDatabaseSetOptionInt", e) + } + }() + cdb := checkDBInit(db, err, "AdbcDatabaseSetOptionInt") + if cdb == nil { + return C.ADBC_STATUS_INVALID_STATE + } + + opts, ok := cdb.db.(adbc.GetSetOptions) + if !ok { + setErr(err, "AdbcDatabaseSetOptionInt: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + + return C.AdbcStatusCode(cdb.errToAdbcErr(err, opts.SetOptionInt(C.GoString(key), int64(value)))) +} + type cConn struct { + cancellableContext + errorDetails + cnxn adbc.Connection initArgs map[string]string } @@ -280,6 +592,118 @@ func checkConnInit(cnxn *C.struct_AdbcConnection, err *C.struct_AdbcError, fname return conn } +//export SnowflakeConnectionGetOption +func SnowflakeConnectionGetOption(db *C.struct_AdbcConnection, key *C.cchar_t, value *C.char, length *C.size_t, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcConnectionGetOption", e) + } + }() + conn := checkConnInit(db, err, "AdbcConnectionGetOption") + if conn == nil { + return C.ADBC_STATUS_INVALID_STATE + } + + if handled, e := conn.getErrorDetailsOptionString(key, value, length); e != nil { + return C.AdbcStatusCode(errToAdbcErr(err, e)) + } else if handled { + return C.ADBC_STATUS_OK + } + + opts, ok := conn.cnxn.(adbc.GetSetOptions) + if !ok { + setErr(err, "AdbcConnectionGetOption: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + val, e := opts.GetOption(C.GoString(key)) + if e != nil { + return C.AdbcStatusCode(conn.errToAdbcErr(err, e)) + } + + return exportStringOption(val, value, length) +} + +//export SnowflakeConnectionGetOptionBytes +func SnowflakeConnectionGetOptionBytes(db *C.struct_AdbcConnection, key *C.cchar_t, value *C.uint8_t, length *C.size_t, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcConnectionGetOptionBytes", e) + } + }() + conn := checkConnInit(db, err, "AdbcConnectionGetOptionBytes") + if conn == nil { + return C.ADBC_STATUS_INVALID_STATE + } + + if handled, e := conn.getErrorDetailsOptionBytes(key, value, length); e != nil { + return C.AdbcStatusCode(errToAdbcErr(err, e)) + } else if handled { + return C.ADBC_STATUS_OK + } + + opts, ok := conn.cnxn.(adbc.GetSetOptions) + if !ok { + setErr(err, "AdbcConnectionGetOptionBytes: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + val, e := opts.GetOptionBytes(C.GoString(key)) + if e != nil { + return C.AdbcStatusCode(conn.errToAdbcErr(err, e)) + } + + return exportBytesOption(val, value, length) +} + +//export SnowflakeConnectionGetOptionDouble +func SnowflakeConnectionGetOptionDouble(db *C.struct_AdbcConnection, key *C.cchar_t, value *C.double, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcConnectionGetOptionDouble", e) + } + }() + conn := checkConnInit(db, err, "AdbcConnectionGetOptionDouble") + if conn == nil { + return C.ADBC_STATUS_INVALID_STATE + } + + opts, ok := conn.cnxn.(adbc.GetSetOptions) + if !ok { + setErr(err, "AdbcConnectionGetOptionDouble: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + + val, e := opts.GetOptionDouble(C.GoString(key)) + *value = C.double(val) + return C.AdbcStatusCode(conn.errToAdbcErr(err, e)) +} + +//export SnowflakeConnectionGetOptionInt +func SnowflakeConnectionGetOptionInt(db *C.struct_AdbcConnection, key *C.cchar_t, value *C.int64_t, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcConnectionGetOptionInt", e) + } + }() + conn := checkConnInit(db, err, "AdbcConnectionGetOptionInt") + if conn == nil { + return C.ADBC_STATUS_INVALID_STATE + } + + if conn.getErrorDetailsOptionInt(key, value) { + return C.ADBC_STATUS_OK + } + + opts, ok := conn.cnxn.(adbc.GetSetOptions) + if !ok { + setErr(err, "AdbcConnectionGetOptionInt: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + + val, e := opts.GetOptionInt(C.GoString(key)) + *value = C.int64_t(val) + return C.AdbcStatusCode(conn.errToAdbcErr(err, e)) +} + //export SnowflakeConnectionNew func SnowflakeConnectionNew(cnxn *C.struct_AdbcConnection, err *C.struct_AdbcError) (code C.AdbcStatusCode) { defer func() { @@ -328,8 +752,70 @@ func SnowflakeConnectionSetOption(cnxn *C.struct_AdbcConnection, key, val *C.cch setErr(err, "AdbcConnectionSetOption: not supported post-init") return C.ADBC_STATUS_NOT_IMPLEMENTED } - rawCode := errToAdbcErr(err, opts.SetOption(C.GoString(key), C.GoString(val))) - return C.AdbcStatusCode(rawCode) + return C.AdbcStatusCode(conn.errToAdbcErr(err, opts.SetOption(C.GoString(key), C.GoString(val)))) +} + +//export SnowflakeConnectionSetOptionBytes +func SnowflakeConnectionSetOptionBytes(db *C.struct_AdbcConnection, key *C.cchar_t, value *C.cuint8_t, length C.size_t, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcConnectionSetOptionBytes", e) + } + }() + conn := checkConnInit(db, err, "AdbcConnectionSetOptionBytes") + if conn == nil { + return C.ADBC_STATUS_INVALID_STATE + } + + opts, ok := conn.cnxn.(adbc.GetSetOptions) + if !ok { + setErr(err, "AdbcConnectionSetOptionBytes: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + + return C.AdbcStatusCode(conn.errToAdbcErr(err, opts.SetOptionBytes(C.GoString(key), fromCArr[byte](value, int(length))))) +} + +//export SnowflakeConnectionSetOptionDouble +func SnowflakeConnectionSetOptionDouble(db *C.struct_AdbcConnection, key *C.cchar_t, value C.double, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcConnectionSetOptionDouble", e) + } + }() + conn := checkConnInit(db, err, "AdbcConnectionSetOptionDouble") + if conn == nil { + return C.ADBC_STATUS_INVALID_STATE + } + + opts, ok := conn.cnxn.(adbc.GetSetOptions) + if !ok { + setErr(err, "AdbcConnectionSetOptionDouble: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + + return C.AdbcStatusCode(conn.errToAdbcErr(err, opts.SetOptionDouble(C.GoString(key), float64(value)))) +} + +//export SnowflakeConnectionSetOptionInt +func SnowflakeConnectionSetOptionInt(db *C.struct_AdbcConnection, key *C.cchar_t, value C.int64_t, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcConnectionSetOptionInt", e) + } + }() + conn := checkConnInit(db, err, "AdbcConnectionSetOptionInt") + if conn == nil { + return C.ADBC_STATUS_INVALID_STATE + } + + opts, ok := conn.cnxn.(adbc.GetSetOptions) + if !ok { + setErr(err, "AdbcConnectionSetOptionInt: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + + return C.AdbcStatusCode(conn.errToAdbcErr(err, opts.SetOptionInt(C.GoString(key), int64(value)))) } //export SnowflakeConnectionInit @@ -354,7 +840,7 @@ func SnowflakeConnectionInit(cnxn *C.struct_AdbcConnection, db *C.struct_AdbcDat } c, e := cdb.db.Open(context.Background()) if e != nil { - return C.AdbcStatusCode(errToAdbcErr(err, e)) + return C.AdbcStatusCode(cdb.errToAdbcErr(err, e)) } conn.cnxn = c @@ -367,7 +853,7 @@ func SnowflakeConnectionInit(cnxn *C.struct_AdbcConnection, db *C.struct_AdbcDat } for k, v := range conn.initArgs { - rawCode := errToAdbcErr(err, opts.SetOption(k, v)) + rawCode := conn.errToAdbcErr(err, opts.SetOption(k, v)) if rawCode != adbc.StatusOK { return C.AdbcStatusCode(rawCode) } @@ -392,8 +878,9 @@ func SnowflakeConnectionRelease(cnxn *C.struct_AdbcConnection, err *C.struct_Adb conn := h.Value().(*cConn) defer func() { + conn.cancelContext() conn.cnxn = nil - C.free(unsafe.Pointer(cnxn.private_data)) + C.free(cnxn.private_data) cnxn.private_data = nil h.Delete() // manually trigger GC for two reasons: @@ -430,6 +917,46 @@ func toCdataArray(ptr *C.struct_ArrowArray) *cdata.CArrowArray { return (*cdata.CArrowArray)(unsafe.Pointer(ptr)) } +//export SnowflakeConnectionCancel +func SnowflakeConnectionCancel(cnxn *C.struct_AdbcConnection, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcConnectionCancel", e) + } + }() + conn := checkConnInit(cnxn, err, "AdbcConnectionCancel") + if conn == nil { + return C.ADBC_STATUS_INVALID_STATE + } + + conn.cancelContext() + return C.ADBC_STATUS_OK +} + +func toStrPtr(in *C.cchar_t) *string { + if in == nil { + return nil + } + + out := C.GoString((*C.char)(in)) + return &out +} + +func toStrSlice(in **C.cchar_t) []string { + if in == nil { + return nil + } + + sz := unsafe.Sizeof(*in) + + out := make([]string, 0, 1) + for *in != nil { + out = append(out, C.GoString(*in)) + in = (**C.cchar_t)(unsafe.Add(unsafe.Pointer(in), sz)) + } + return out +} + //export SnowflakeConnectionGetInfo func SnowflakeConnectionGetInfo(cnxn *C.struct_AdbcConnection, codes *C.uint32_t, len C.size_t, out *C.struct_ArrowArrayStream, err *C.struct_AdbcError) (code C.AdbcStatusCode) { defer func() { @@ -443,56 +970,86 @@ func SnowflakeConnectionGetInfo(cnxn *C.struct_AdbcConnection, codes *C.uint32_t } infoCodes := fromCArr[adbc.InfoCode](codes, int(len)) - rdr, e := conn.cnxn.GetInfo(context.Background(), infoCodes) + rdr, e := conn.cnxn.GetInfo(conn.newContext(), infoCodes) if e != nil { - return C.AdbcStatusCode(errToAdbcErr(err, e)) + return C.AdbcStatusCode(conn.errToAdbcErr(err, e)) } cdata.ExportRecordReader(rdr, toCdataStream(out)) return C.ADBC_STATUS_OK } -func toStrPtr(in *C.cchar_t) *string { - if in == nil { - return nil +//export SnowflakeConnectionGetObjects +func SnowflakeConnectionGetObjects(cnxn *C.struct_AdbcConnection, depth C.int, catalog, dbSchema, tableName *C.cchar_t, tableType **C.cchar_t, columnName *C.cchar_t, + out *C.struct_ArrowArrayStream, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcConnectionGetObjects", e) + } + }() + conn := checkConnInit(cnxn, err, "AdbcConnectionGetObjects") + if conn == nil { + return C.ADBC_STATUS_INVALID_STATE } - out := C.GoString((*C.char)(in)) - return &out + rdr, e := conn.cnxn.GetObjects(conn.newContext(), adbc.ObjectDepth(depth), toStrPtr(catalog), toStrPtr(dbSchema), toStrPtr(tableName), toStrPtr(columnName), toStrSlice(tableType)) + if e != nil { + return C.AdbcStatusCode(conn.errToAdbcErr(err, e)) + } + cdata.ExportRecordReader(rdr, toCdataStream(out)) + return C.ADBC_STATUS_OK } -func toStrSlice(in **C.cchar_t) []string { - if in == nil { - return nil +//export SnowflakeConnectionGetStatistics +func SnowflakeConnectionGetStatistics(cnxn *C.struct_AdbcConnection, catalog, dbSchema, tableName *C.cchar_t, approximate C.char, out *C.struct_ArrowArrayStream, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcConnectionGetStatistics", e) + } + }() + conn := checkConnInit(cnxn, err, "AdbcConnectionGetStatistics") + if conn == nil { + return C.ADBC_STATUS_INVALID_STATE } - sz := unsafe.Sizeof(*in) + gs, ok := conn.cnxn.(adbc.ConnectionGetStatistics) + if !ok { + setErr(err, "AdbcConnectionGetStatistics: not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } - out := make([]string, 0, 1) - for *in != nil { - out = append(out, C.GoString(*in)) - in = (**C.cchar_t)(unsafe.Add(unsafe.Pointer(in), sz)) + rdr, e := gs.GetStatistics(conn.newContext(), toStrPtr(catalog), toStrPtr(dbSchema), toStrPtr(tableName), int(approximate) != 0) + if e != nil { + return C.AdbcStatusCode(conn.errToAdbcErr(err, e)) } - return out + + cdata.ExportRecordReader(rdr, toCdataStream(out)) + return C.ADBC_STATUS_OK } -//export SnowflakeConnectionGetObjects -func SnowflakeConnectionGetObjects(cnxn *C.struct_AdbcConnection, depth C.int, catalog, dbSchema, tableName *C.cchar_t, tableType **C.cchar_t, columnName *C.cchar_t, - out *C.struct_ArrowArrayStream, err *C.struct_AdbcError) (code C.AdbcStatusCode) { +//export SnowflakeConnectionGetStatisticNames +func SnowflakeConnectionGetStatisticNames(cnxn *C.struct_AdbcConnection, out *C.struct_ArrowArrayStream, err *C.struct_AdbcError) (code C.AdbcStatusCode) { defer func() { if e := recover(); e != nil { - code = poison(err, "AdbcConnectionGetObjects", e) + code = poison(err, "AdbcConnectionGetStatistics", e) } }() - conn := checkConnInit(cnxn, err, "AdbcConnectionGetObjects") + conn := checkConnInit(cnxn, err, "AdbcConnectionGetStatistics") if conn == nil { return C.ADBC_STATUS_INVALID_STATE } - rdr, e := conn.cnxn.GetObjects(context.Background(), adbc.ObjectDepth(depth), toStrPtr(catalog), toStrPtr(dbSchema), toStrPtr(tableName), toStrPtr(columnName), toStrSlice(tableType)) + gs, ok := conn.cnxn.(adbc.ConnectionGetStatistics) + if !ok { + setErr(err, "AdbcConnectionGetStatistics: not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + + rdr, e := gs.GetStatisticNames(conn.newContext()) if e != nil { - return C.AdbcStatusCode(errToAdbcErr(err, e)) + return C.AdbcStatusCode(conn.errToAdbcErr(err, e)) } + cdata.ExportRecordReader(rdr, toCdataStream(out)) return C.ADBC_STATUS_OK } @@ -509,9 +1066,9 @@ func SnowflakeConnectionGetTableSchema(cnxn *C.struct_AdbcConnection, catalog, d return C.ADBC_STATUS_INVALID_STATE } - sc, e := conn.cnxn.GetTableSchema(context.Background(), toStrPtr(catalog), toStrPtr(dbSchema), C.GoString(tableName)) + sc, e := conn.cnxn.GetTableSchema(conn.newContext(), toStrPtr(catalog), toStrPtr(dbSchema), C.GoString(tableName)) if e != nil { - return C.AdbcStatusCode(errToAdbcErr(err, e)) + return C.AdbcStatusCode(conn.errToAdbcErr(err, e)) } cdata.ExportArrowSchema(sc, toCdataSchema(schema)) return C.ADBC_STATUS_OK @@ -529,9 +1086,9 @@ func SnowflakeConnectionGetTableTypes(cnxn *C.struct_AdbcConnection, out *C.stru return C.ADBC_STATUS_INVALID_STATE } - rdr, e := conn.cnxn.GetTableTypes(context.Background()) + rdr, e := conn.cnxn.GetTableTypes(conn.newContext()) if e != nil { - return C.AdbcStatusCode(errToAdbcErr(err, e)) + return C.AdbcStatusCode(conn.errToAdbcErr(err, e)) } cdata.ExportRecordReader(rdr, toCdataStream(out)) return C.ADBC_STATUS_OK @@ -549,9 +1106,9 @@ func SnowflakeConnectionReadPartition(cnxn *C.struct_AdbcConnection, serialized return C.ADBC_STATUS_INVALID_STATE } - rdr, e := conn.cnxn.ReadPartition(context.Background(), fromCArr[byte](serialized, int(serializedLen))) + rdr, e := conn.cnxn.ReadPartition(conn.newContext(), fromCArr[byte](serialized, int(serializedLen))) if e != nil { - return C.AdbcStatusCode(errToAdbcErr(err, e)) + return C.AdbcStatusCode(conn.errToAdbcErr(err, e)) } cdata.ExportRecordReader(rdr, toCdataStream(out)) return C.ADBC_STATUS_OK @@ -569,7 +1126,7 @@ func SnowflakeConnectionCommit(cnxn *C.struct_AdbcConnection, err *C.struct_Adbc return C.ADBC_STATUS_INVALID_STATE } - return C.AdbcStatusCode(errToAdbcErr(err, conn.cnxn.Commit(context.Background()))) + return C.AdbcStatusCode(conn.errToAdbcErr(err, conn.cnxn.Commit(conn.newContext()))) } //export SnowflakeConnectionRollback @@ -584,25 +1141,154 @@ func SnowflakeConnectionRollback(cnxn *C.struct_AdbcConnection, err *C.struct_Ad return C.ADBC_STATUS_INVALID_STATE } - return C.AdbcStatusCode(errToAdbcErr(err, conn.cnxn.Rollback(context.Background()))) + return C.AdbcStatusCode(conn.errToAdbcErr(err, conn.cnxn.Rollback(conn.newContext()))) +} + +type cStmt struct { + cancellableContext + errorDetails + + stmt adbc.Statement } -func checkStmtInit(stmt *C.struct_AdbcStatement, err *C.struct_AdbcError, fname string) adbc.Statement { +func checkStmtAlloc(stmt *C.struct_AdbcStatement, err *C.struct_AdbcError, fname string) bool { if atomic.LoadInt32(&globalPoison) != 0 { setErr(err, "%s: Go panicked, driver is in unknown state", fname) - return nil + return false } if stmt == nil { setErr(err, "%s: statement not allocated", fname) - return nil + return false } - if stmt.private_data == nil { - setErr(err, "%s: statement not initialized", fname) + setErr(err, "%s: statement not allocated", fname) + return false + } + return true +} + +func checkStmtInit(stmt *C.struct_AdbcStatement, err *C.struct_AdbcError, fname string) *cStmt { + if !checkStmtAlloc(stmt, err, fname) { + return nil + } + cStmt := getFromHandle[cStmt](stmt.private_data) + if cStmt.stmt == nil { + setErr(err, "%s: statement not allocated", fname) return nil } + return cStmt +} + +//export SnowflakeStatementGetOption +func SnowflakeStatementGetOption(db *C.struct_AdbcStatement, key *C.cchar_t, value *C.char, length *C.size_t, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcStatementGetOption", e) + } + }() + st := checkStmtInit(db, err, "AdbcStatementGetOption") + if st == nil { + return C.ADBC_STATUS_INVALID_STATE + } + + if handled, e := st.getErrorDetailsOptionString(key, value, length); e != nil { + return C.AdbcStatusCode(errToAdbcErr(err, e)) + } else if handled { + return C.ADBC_STATUS_OK + } + + opts, ok := st.stmt.(adbc.GetSetOptions) + if !ok { + setErr(err, "AdbcStatementGetOption: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + val, e := opts.GetOption(C.GoString(key)) + if e != nil { + return C.AdbcStatusCode(st.errToAdbcErr(err, e)) + } + + return exportStringOption(val, value, length) +} + +//export SnowflakeStatementGetOptionBytes +func SnowflakeStatementGetOptionBytes(db *C.struct_AdbcStatement, key *C.cchar_t, value *C.uint8_t, length *C.size_t, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcStatementGetOptionBytes", e) + } + }() + st := checkStmtInit(db, err, "AdbcStatementGetOptionBytes") + if st == nil { + return C.ADBC_STATUS_INVALID_STATE + } + + if handled, e := st.getErrorDetailsOptionBytes(key, value, length); e != nil { + return C.AdbcStatusCode(errToAdbcErr(err, e)) + } else if handled { + return C.ADBC_STATUS_OK + } + + opts, ok := st.stmt.(adbc.GetSetOptions) + if !ok { + setErr(err, "AdbcStatementGetOptionBytes: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + val, e := opts.GetOptionBytes(C.GoString(key)) + if e != nil { + return C.AdbcStatusCode(st.errToAdbcErr(err, e)) + } + + return exportBytesOption(val, value, length) +} + +//export SnowflakeStatementGetOptionDouble +func SnowflakeStatementGetOptionDouble(db *C.struct_AdbcStatement, key *C.cchar_t, value *C.double, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcStatementGetOptionDouble", e) + } + }() + st := checkStmtInit(db, err, "AdbcStatementGetOptionDouble") + if st == nil { + return C.ADBC_STATUS_INVALID_STATE + } + + opts, ok := st.stmt.(adbc.GetSetOptions) + if !ok { + setErr(err, "AdbcStatementGetOptionDouble: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + + val, e := opts.GetOptionDouble(C.GoString(key)) + *value = C.double(val) + return C.AdbcStatusCode(st.errToAdbcErr(err, e)) +} + +//export SnowflakeStatementGetOptionInt +func SnowflakeStatementGetOptionInt(db *C.struct_AdbcStatement, key *C.cchar_t, value *C.int64_t, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcStatementGetOptionInt", e) + } + }() + st := checkStmtInit(db, err, "AdbcStatementGetOptionInt") + if st == nil { + return C.ADBC_STATUS_INVALID_STATE + } + + if st.getErrorDetailsOptionInt(key, value) { + return C.ADBC_STATUS_OK + } - return (*(*cgo.Handle)(stmt.private_data)).Value().(adbc.Statement) + opts, ok := st.stmt.(adbc.GetSetOptions) + if !ok { + setErr(err, "AdbcStatementGetOptionInt: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + + val, e := opts.GetOptionInt(C.GoString(key)) + *value = C.int64_t(val) + return C.AdbcStatusCode(st.errToAdbcErr(err, e)) } //export SnowflakeStatementNew @@ -623,11 +1309,11 @@ func SnowflakeStatementNew(cnxn *C.struct_AdbcConnection, stmt *C.struct_AdbcSta st, e := conn.cnxn.NewStatement() if e != nil { - return C.AdbcStatusCode(errToAdbcErr(err, e)) + return C.AdbcStatusCode(conn.errToAdbcErr(err, e)) } - h := cgo.NewHandle(st) - stmt.private_data = createHandle(h) + hndl := cgo.NewHandle(&cStmt{stmt: st}) + stmt.private_data = createHandle(hndl) return C.ADBC_STATUS_OK } @@ -642,31 +1328,46 @@ func SnowflakeStatementRelease(stmt *C.struct_AdbcStatement, err *C.struct_AdbcE setErr(err, "AdbcStatementRelease: Go panicked, driver is in unknown state") return C.ADBC_STATUS_INTERNAL } - if stmt == nil { - setErr(err, "AdbcStatementRelease: statement not allocated") + if !checkStmtAlloc(stmt, err, "AdbcStatementRelease") { return C.ADBC_STATUS_INVALID_STATE } + h := (*(*cgo.Handle)(stmt.private_data)) - if stmt.private_data == nil { - setErr(err, "AdbcStatementRelease: statement not initialized") - return C.ADBC_STATUS_INVALID_STATE + st := h.Value().(*cStmt) + defer func() { + st.cancelContext() + st.stmt = nil + C.free(stmt.private_data) + stmt.private_data = nil + h.Delete() + // manually trigger GC for two reasons: + // 1. ASAN expects the release callback to be called before + // the process ends, but GC is not deterministic. So by manually + // triggering the GC we ensure the release callback gets called. + // 2. Creates deterministic GC behavior by all Release functions + // triggering a garbage collection + runtime.GC() + }() + if st.stmt == nil { + return C.ADBC_STATUS_OK } + return C.AdbcStatusCode(errToAdbcErr(err, st.stmt.Close())) +} - h := (*(*cgo.Handle)(stmt.private_data)) - st := h.Value().(adbc.Statement) - C.free(stmt.private_data) - stmt.private_data = nil +//export SnowflakeStatementCancel +func SnowflakeStatementCancel(stmt *C.struct_AdbcStatement, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcStatementPrepare", e) + } + }() + st := checkStmtInit(stmt, err, "AdbcStatementPrepare") + if st == nil { + return C.ADBC_STATUS_INVALID_STATE + } - e := st.Close() - h.Delete() - // manually trigger GC for two reasons: - // 1. ASAN expects the release callback to be called before - // the process ends, but GC is not deterministic. So by manually - // triggering the GC we ensure the release callback gets called. - // 2. Creates deterministic GC behavior by all Release functions - // triggering a garbage collection - runtime.GC() - return C.AdbcStatusCode(errToAdbcErr(err, e)) + st.cancelContext() + return C.ADBC_STATUS_OK } //export SnowflakeStatementPrepare @@ -681,7 +1382,7 @@ func SnowflakeStatementPrepare(stmt *C.struct_AdbcStatement, err *C.struct_AdbcE return C.ADBC_STATUS_INVALID_STATE } - return C.AdbcStatusCode(errToAdbcErr(err, st.Prepare(context.Background()))) + return C.AdbcStatusCode(st.errToAdbcErr(err, st.stmt.Prepare(st.newContext()))) } //export SnowflakeStatementExecuteQuery @@ -697,29 +1398,57 @@ func SnowflakeStatementExecuteQuery(stmt *C.struct_AdbcStatement, out *C.struct_ } if out == nil { - n, e := st.ExecuteUpdate(context.Background()) + n, e := st.stmt.ExecuteUpdate(st.newContext()) if e != nil { - return C.AdbcStatusCode(errToAdbcErr(err, e)) + return C.AdbcStatusCode(st.errToAdbcErr(err, e)) } if affected != nil { *affected = C.int64_t(n) } } else { - rdr, n, e := st.ExecuteQuery(context.Background()) + rdr, n, e := st.stmt.ExecuteQuery(st.newContext()) if e != nil { - return C.AdbcStatusCode(errToAdbcErr(err, e)) + return C.AdbcStatusCode(st.errToAdbcErr(err, e)) } if affected != nil { *affected = C.int64_t(n) } + // TODO: proxy the reader and record error details (and do this elsewhere, too) cdata.ExportRecordReader(rdr, toCdataStream(out)) } return C.ADBC_STATUS_OK } +//export SnowflakeStatementExecuteSchema +func SnowflakeStatementExecuteSchema(stmt *C.struct_AdbcStatement, schema *C.struct_ArrowSchema, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcStatementExecuteQuery", e) + } + }() + st := checkStmtInit(stmt, err, "AdbcStatementExecuteQuery") + if st == nil { + return C.ADBC_STATUS_INVALID_STATE + } + + es, ok := st.stmt.(adbc.StatementExecuteSchema) + if !ok { + setErr(err, "AdbcStatementExecuteSchema: not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + + sc, e := es.ExecuteSchema(st.newContext()) + if e != nil { + return C.AdbcStatusCode(st.errToAdbcErr(err, e)) + } + + cdata.ExportArrowSchema(sc, toCdataSchema(schema)) + return C.ADBC_STATUS_OK +} + //export SnowflakeStatementSetSqlQuery func SnowflakeStatementSetSqlQuery(stmt *C.struct_AdbcStatement, query *C.cchar_t, err *C.struct_AdbcError) (code C.AdbcStatusCode) { defer func() { @@ -732,7 +1461,7 @@ func SnowflakeStatementSetSqlQuery(stmt *C.struct_AdbcStatement, query *C.cchar_ return C.ADBC_STATUS_INVALID_STATE } - return C.AdbcStatusCode(errToAdbcErr(err, st.SetSqlQuery(C.GoString(query)))) + return C.AdbcStatusCode(st.errToAdbcErr(err, st.stmt.SetSqlQuery(C.GoString(query)))) } //export SnowflakeStatementSetSubstraitPlan @@ -747,7 +1476,7 @@ func SnowflakeStatementSetSubstraitPlan(stmt *C.struct_AdbcStatement, plan *C.cu return C.ADBC_STATUS_INVALID_STATE } - return C.AdbcStatusCode(errToAdbcErr(err, st.SetSubstraitPlan(fromCArr[byte](plan, int(length))))) + return C.AdbcStatusCode(st.errToAdbcErr(err, st.stmt.SetSubstraitPlan(fromCArr[byte](plan, int(length))))) } //export SnowflakeStatementBind @@ -766,11 +1495,11 @@ func SnowflakeStatementBind(stmt *C.struct_AdbcStatement, values *C.struct_Arrow if e != nil { // if there was an error, we need to manually release the input cdata.ReleaseCArrowArray(toCdataArray(values)) - return C.AdbcStatusCode(errToAdbcErr(err, e)) + return C.AdbcStatusCode(st.errToAdbcErr(err, e)) } defer rec.Release() - return C.AdbcStatusCode(errToAdbcErr(err, st.Bind(context.Background(), rec))) + return C.AdbcStatusCode(st.errToAdbcErr(err, st.stmt.Bind(st.newContext(), rec))) } //export SnowflakeStatementBindStream @@ -787,9 +1516,9 @@ func SnowflakeStatementBindStream(stmt *C.struct_AdbcStatement, stream *C.struct rdr, e := cdata.ImportCRecordReader(toCdataStream(stream), nil) if e != nil { - return C.AdbcStatusCode(errToAdbcErr(err, e)) + return C.AdbcStatusCode(st.errToAdbcErr(err, e)) } - return C.AdbcStatusCode(errToAdbcErr(err, st.BindStream(context.Background(), rdr.(array.RecordReader)))) + return C.AdbcStatusCode(st.errToAdbcErr(err, st.stmt.BindStream(st.newContext(), rdr.(array.RecordReader)))) } //export SnowflakeStatementGetParameterSchema @@ -804,9 +1533,9 @@ func SnowflakeStatementGetParameterSchema(stmt *C.struct_AdbcStatement, schema * return C.ADBC_STATUS_INVALID_STATE } - sc, e := st.GetParameterSchema() + sc, e := st.stmt.GetParameterSchema() if e != nil { - return C.AdbcStatusCode(errToAdbcErr(err, e)) + return C.AdbcStatusCode(st.errToAdbcErr(err, e)) } cdata.ExportArrowSchema(sc, toCdataSchema(schema)) @@ -825,7 +1554,70 @@ func SnowflakeStatementSetOption(stmt *C.struct_AdbcStatement, key, value *C.cch return C.ADBC_STATUS_INVALID_STATE } - return C.AdbcStatusCode(errToAdbcErr(err, st.SetOption(C.GoString(key), C.GoString(value)))) + return C.AdbcStatusCode(st.errToAdbcErr(err, st.stmt.SetOption(C.GoString(key), C.GoString(value)))) +} + +//export SnowflakeStatementSetOptionBytes +func SnowflakeStatementSetOptionBytes(db *C.struct_AdbcStatement, key *C.cchar_t, value *C.cuint8_t, length C.size_t, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcStatementSetOptionBytes", e) + } + }() + st := checkStmtInit(db, err, "AdbcStatementSetOptionBytes") + if st == nil { + return C.ADBC_STATUS_INVALID_STATE + } + + opts, ok := st.stmt.(adbc.GetSetOptions) + if !ok { + setErr(err, "AdbcStatementSetOptionBytes: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + + return C.AdbcStatusCode(st.errToAdbcErr(err, opts.SetOptionBytes(C.GoString(key), fromCArr[byte](value, int(length))))) +} + +//export SnowflakeStatementSetOptionDouble +func SnowflakeStatementSetOptionDouble(db *C.struct_AdbcStatement, key *C.cchar_t, value C.double, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcStatementSetOptionDouble", e) + } + }() + st := checkStmtInit(db, err, "AdbcStatementSetOptionDouble") + if st == nil { + return C.ADBC_STATUS_INVALID_STATE + } + + opts, ok := st.stmt.(adbc.GetSetOptions) + if !ok { + setErr(err, "AdbcStatementSetOptionDouble: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + + return C.AdbcStatusCode(st.errToAdbcErr(err, opts.SetOptionDouble(C.GoString(key), float64(value)))) +} + +//export SnowflakeStatementSetOptionInt +func SnowflakeStatementSetOptionInt(db *C.struct_AdbcStatement, key *C.cchar_t, value C.int64_t, err *C.struct_AdbcError) (code C.AdbcStatusCode) { + defer func() { + if e := recover(); e != nil { + code = poison(err, "AdbcStatementSetOptionInt", e) + } + }() + st := checkStmtInit(db, err, "AdbcStatementSetOptionInt") + if st == nil { + return C.ADBC_STATUS_INVALID_STATE + } + + opts, ok := st.stmt.(adbc.GetSetOptions) + if !ok { + setErr(err, "AdbcStatementSetOptionInt: options are not supported") + return C.ADBC_STATUS_NOT_IMPLEMENTED + } + + return C.AdbcStatusCode(st.errToAdbcErr(err, opts.SetOptionInt(C.GoString(key), int64(value)))) } //export releasePartitions @@ -854,9 +1646,9 @@ func SnowflakeStatementExecutePartitions(stmt *C.struct_AdbcStatement, schema *C return C.ADBC_STATUS_INVALID_STATE } - sc, part, n, e := st.ExecutePartitions(context.Background()) + sc, part, n, e := st.stmt.ExecutePartitions(st.newContext()) if e != nil { - return C.AdbcStatusCode(errToAdbcErr(err, e)) + return C.AdbcStatusCode(st.errToAdbcErr(err, e)) } if partitions == nil { @@ -899,13 +1691,20 @@ func SnowflakeStatementExecutePartitions(stmt *C.struct_AdbcStatement, schema *C //export SnowflakeDriverInit func SnowflakeDriverInit(version C.int, rawDriver *C.void, err *C.struct_AdbcError) C.AdbcStatusCode { - if version != C.ADBC_VERSION_1_0_0 { - setErr(err, "Only version %d supported, got %d", int(C.ADBC_VERSION_1_0_0), int(version)) + driver := (*C.struct_AdbcDriver)(unsafe.Pointer(rawDriver)) + + switch version { + case C.ADBC_VERSION_1_0_0: + sink := fromCArr[byte]((*byte)(unsafe.Pointer(driver)), C.ADBC_DRIVER_1_0_0_SIZE) + memory.Set(sink, 0) + case C.ADBC_VERSION_1_1_0: + sink := fromCArr[byte]((*byte)(unsafe.Pointer(driver)), C.ADBC_DRIVER_1_1_0_SIZE) + memory.Set(sink, 0) + default: + setErr(err, "Only version 1.0.0/1.1.0 supported, got %d", int(version)) return C.ADBC_STATUS_NOT_IMPLEMENTED } - driver := (*C.struct_AdbcDriver)(unsafe.Pointer(rawDriver)) - C.memset(unsafe.Pointer(driver), 0, C.ADBC_DRIVER_1_0_0_SIZE) driver.DatabaseInit = (*[0]byte)(C.SnowflakeDatabaseInit) driver.DatabaseNew = (*[0]byte)(C.SnowflakeDatabaseNew) driver.DatabaseRelease = (*[0]byte)(C.SnowflakeDatabaseRelease) @@ -935,6 +1734,37 @@ func SnowflakeDriverInit(version C.int, rawDriver *C.void, err *C.struct_AdbcErr driver.StatementGetParameterSchema = (*[0]byte)(C.SnowflakeStatementGetParameterSchema) driver.StatementPrepare = (*[0]byte)(C.SnowflakeStatementPrepare) + if version == C.ADBC_VERSION_1_1_0 { + driver.DatabaseGetOption = (*[0]byte)(C.SnowflakeDatabaseGetOption) + driver.DatabaseGetOptionBytes = (*[0]byte)(C.SnowflakeDatabaseGetOptionBytes) + driver.DatabaseGetOptionDouble = (*[0]byte)(C.SnowflakeDatabaseGetOptionDouble) + driver.DatabaseGetOptionInt = (*[0]byte)(C.SnowflakeDatabaseGetOptionInt) + driver.DatabaseSetOptionBytes = (*[0]byte)(C.SnowflakeDatabaseSetOptionBytes) + driver.DatabaseSetOptionDouble = (*[0]byte)(C.SnowflakeDatabaseSetOptionDouble) + driver.DatabaseSetOptionInt = (*[0]byte)(C.SnowflakeDatabaseSetOptionInt) + + driver.ConnectionCancel = (*[0]byte)(C.SnowflakeConnectionCancel) + driver.ConnectionGetOption = (*[0]byte)(C.SnowflakeConnectionGetOption) + driver.ConnectionGetOptionBytes = (*[0]byte)(C.SnowflakeConnectionGetOptionBytes) + driver.ConnectionGetOptionDouble = (*[0]byte)(C.SnowflakeConnectionGetOptionDouble) + driver.ConnectionGetOptionInt = (*[0]byte)(C.SnowflakeConnectionGetOptionInt) + driver.ConnectionGetStatistics = (*[0]byte)(C.SnowflakeConnectionGetStatistics) + driver.ConnectionGetStatisticNames = (*[0]byte)(C.SnowflakeConnectionGetStatisticNames) + driver.ConnectionSetOptionBytes = (*[0]byte)(C.SnowflakeConnectionSetOptionBytes) + driver.ConnectionSetOptionDouble = (*[0]byte)(C.SnowflakeConnectionSetOptionDouble) + driver.ConnectionSetOptionInt = (*[0]byte)(C.SnowflakeConnectionSetOptionInt) + + driver.StatementCancel = (*[0]byte)(C.SnowflakeStatementCancel) + driver.StatementExecuteSchema = (*[0]byte)(C.SnowflakeStatementExecuteSchema) + driver.StatementGetOption = (*[0]byte)(C.SnowflakeStatementGetOption) + driver.StatementGetOptionBytes = (*[0]byte)(C.SnowflakeStatementGetOptionBytes) + driver.StatementGetOptionDouble = (*[0]byte)(C.SnowflakeStatementGetOptionDouble) + driver.StatementGetOptionInt = (*[0]byte)(C.SnowflakeStatementGetOptionInt) + driver.StatementSetOptionBytes = (*[0]byte)(C.SnowflakeStatementSetOptionBytes) + driver.StatementSetOptionDouble = (*[0]byte)(C.SnowflakeStatementSetOptionDouble) + driver.StatementSetOptionInt = (*[0]byte)(C.SnowflakeStatementSetOptionInt) + } + return C.ADBC_STATUS_OK } diff --git a/go/adbc/pkg/snowflake/utils.h b/go/adbc/pkg/snowflake/utils.h index 453d7a099a..f661eb03d5 100644 --- a/go/adbc/pkg/snowflake/utils.h +++ b/go/adbc/pkg/snowflake/utils.h @@ -26,18 +26,29 @@ #include #include "../../drivermgr/adbc.h" +AdbcStatusCode SnowflakeDatabaseGetOption(struct AdbcDatabase*, const char*, char*, + size_t*, struct AdbcError*); +AdbcStatusCode SnowflakeDatabaseGetOptionBytes(struct AdbcDatabase*, const char*, + uint8_t*, size_t*, struct AdbcError*); +AdbcStatusCode SnowflakeDatabaseGetOptionDouble(struct AdbcDatabase*, const char*, + double*, struct AdbcError*); +AdbcStatusCode SnowflakeDatabaseGetOptionInt(struct AdbcDatabase*, const char*, int64_t*, + struct AdbcError*); +AdbcStatusCode SnowflakeDatabaseInit(struct AdbcDatabase* db, struct AdbcError* err); AdbcStatusCode SnowflakeDatabaseNew(struct AdbcDatabase* db, struct AdbcError* err); +AdbcStatusCode SnowflakeDatabaseRelease(struct AdbcDatabase* db, struct AdbcError* err); AdbcStatusCode SnowflakeDatabaseSetOption(struct AdbcDatabase* db, const char* key, const char* value, struct AdbcError* err); -AdbcStatusCode SnowflakeDatabaseInit(struct AdbcDatabase* db, struct AdbcError* err); -AdbcStatusCode SnowflakeDatabaseRelease(struct AdbcDatabase* db, struct AdbcError* err); -AdbcStatusCode SnowflakeConnectionNew(struct AdbcConnection* cnxn, struct AdbcError* err); -AdbcStatusCode SnowflakeConnectionSetOption(struct AdbcConnection* cnxn, const char* key, - const char* val, struct AdbcError* err); -AdbcStatusCode SnowflakeConnectionInit(struct AdbcConnection* cnxn, - struct AdbcDatabase* db, struct AdbcError* err); -AdbcStatusCode SnowflakeConnectionRelease(struct AdbcConnection* cnxn, - struct AdbcError* err); +AdbcStatusCode SnowflakeDatabaseSetOptionBytes(struct AdbcDatabase*, const char*, + const uint8_t*, size_t, struct AdbcError*); +AdbcStatusCode SnowflakeDatabaseSetOptionDouble(struct AdbcDatabase*, const char*, double, + struct AdbcError*); +AdbcStatusCode SnowflakeDatabaseSetOptionInt(struct AdbcDatabase*, const char*, int64_t, + struct AdbcError*); + +AdbcStatusCode SnowflakeConnectionCancel(struct AdbcConnection*, struct AdbcError*); +AdbcStatusCode SnowflakeConnectionCommit(struct AdbcConnection* cnxn, + struct AdbcError* err); AdbcStatusCode SnowflakeConnectionGetInfo(struct AdbcConnection* cnxn, uint32_t* codes, size_t len, struct ArrowArrayStream* out, struct AdbcError* err); @@ -45,51 +56,98 @@ AdbcStatusCode SnowflakeConnectionGetObjects( struct AdbcConnection* cnxn, int depth, const char* catalog, const char* dbSchema, const char* tableName, const char** tableType, const char* columnName, struct ArrowArrayStream* out, struct AdbcError* err); +AdbcStatusCode SnowflakeConnectionGetOption(struct AdbcConnection*, const char*, char*, + size_t*, struct AdbcError*); +AdbcStatusCode SnowflakeConnectionGetOptionBytes(struct AdbcConnection*, const char*, + uint8_t*, size_t*, struct AdbcError*); +AdbcStatusCode SnowflakeConnectionGetOptionDouble(struct AdbcConnection*, const char*, + double*, struct AdbcError*); +AdbcStatusCode SnowflakeConnectionGetOptionInt(struct AdbcConnection*, const char*, + int64_t*, struct AdbcError*); +AdbcStatusCode SnowflakeConnectionGetStatistics(struct AdbcConnection*, const char*, + const char*, const char*, char, + struct ArrowArrayStream*, + struct AdbcError*); +AdbcStatusCode SnowflakeConnectionGetStatisticNames(struct AdbcConnection*, + struct ArrowArrayStream*, + struct AdbcError*); AdbcStatusCode SnowflakeConnectionGetTableSchema( struct AdbcConnection* cnxn, const char* catalog, const char* dbSchema, const char* tableName, struct ArrowSchema* schema, struct AdbcError* err); AdbcStatusCode SnowflakeConnectionGetTableTypes(struct AdbcConnection* cnxn, struct ArrowArrayStream* out, struct AdbcError* err); +AdbcStatusCode SnowflakeConnectionInit(struct AdbcConnection* cnxn, + struct AdbcDatabase* db, struct AdbcError* err); +AdbcStatusCode SnowflakeConnectionNew(struct AdbcConnection* cnxn, struct AdbcError* err); AdbcStatusCode SnowflakeConnectionReadPartition(struct AdbcConnection* cnxn, const uint8_t* serialized, size_t serializedLen, struct ArrowArrayStream* out, struct AdbcError* err); -AdbcStatusCode SnowflakeConnectionCommit(struct AdbcConnection* cnxn, - struct AdbcError* err); +AdbcStatusCode SnowflakeConnectionRelease(struct AdbcConnection* cnxn, + struct AdbcError* err); AdbcStatusCode SnowflakeConnectionRollback(struct AdbcConnection* cnxn, struct AdbcError* err); -AdbcStatusCode SnowflakeStatementNew(struct AdbcConnection* cnxn, - struct AdbcStatement* stmt, struct AdbcError* err); -AdbcStatusCode SnowflakeStatementRelease(struct AdbcStatement* stmt, - struct AdbcError* err); -AdbcStatusCode SnowflakeStatementPrepare(struct AdbcStatement* stmt, - struct AdbcError* err); -AdbcStatusCode SnowflakeStatementExecuteQuery(struct AdbcStatement* stmt, - struct ArrowArrayStream* out, - int64_t* affected, struct AdbcError* err); -AdbcStatusCode SnowflakeStatementSetSqlQuery(struct AdbcStatement* stmt, - const char* query, struct AdbcError* err); -AdbcStatusCode SnowflakeStatementSetSubstraitPlan(struct AdbcStatement* stmt, - const uint8_t* plan, size_t length, - struct AdbcError* err); +AdbcStatusCode SnowflakeConnectionSetOption(struct AdbcConnection* cnxn, const char* key, + const char* val, struct AdbcError* err); +AdbcStatusCode SnowflakeConnectionSetOptionBytes(struct AdbcConnection*, const char*, + const uint8_t*, size_t, + struct AdbcError*); +AdbcStatusCode SnowflakeConnectionSetOptionDouble(struct AdbcConnection*, const char*, + double, struct AdbcError*); +AdbcStatusCode SnowflakeConnectionSetOptionInt(struct AdbcConnection*, const char*, + int64_t, struct AdbcError*); + AdbcStatusCode SnowflakeStatementBind(struct AdbcStatement* stmt, struct ArrowArray* values, struct ArrowSchema* schema, struct AdbcError* err); AdbcStatusCode SnowflakeStatementBindStream(struct AdbcStatement* stmt, struct ArrowArrayStream* stream, struct AdbcError* err); -AdbcStatusCode SnowflakeStatementGetParameterSchema(struct AdbcStatement* stmt, - struct ArrowSchema* schema, - struct AdbcError* err); -AdbcStatusCode SnowflakeStatementSetOption(struct AdbcStatement* stmt, const char* key, - const char* value, struct AdbcError* err); +AdbcStatusCode SnowflakeStatementCancel(struct AdbcStatement*, struct AdbcError*); +AdbcStatusCode SnowflakeStatementExecuteQuery(struct AdbcStatement* stmt, + struct ArrowArrayStream* out, + int64_t* affected, struct AdbcError* err); AdbcStatusCode SnowflakeStatementExecutePartitions(struct AdbcStatement* stmt, struct ArrowSchema* schema, struct AdbcPartitions* partitions, int64_t* affected, struct AdbcError* err); +AdbcStatusCode SnowflakeStatementExecuteSchema(struct AdbcStatement*, struct ArrowSchema*, + struct AdbcError*); +AdbcStatusCode SnowflakeStatementGetOption(struct AdbcStatement*, const char*, char*, + size_t*, struct AdbcError*); +AdbcStatusCode SnowflakeStatementGetOptionBytes(struct AdbcStatement*, const char*, + uint8_t*, size_t*, struct AdbcError*); +AdbcStatusCode SnowflakeStatementGetOptionDouble(struct AdbcStatement*, const char*, + double*, struct AdbcError*); +AdbcStatusCode SnowflakeStatementGetOptionInt(struct AdbcStatement*, const char*, + int64_t*, struct AdbcError*); +AdbcStatusCode SnowflakeStatementGetParameterSchema(struct AdbcStatement* stmt, + struct ArrowSchema* schema, + struct AdbcError* err); +AdbcStatusCode SnowflakeStatementNew(struct AdbcConnection* cnxn, + struct AdbcStatement* stmt, struct AdbcError* err); +AdbcStatusCode SnowflakeStatementPrepare(struct AdbcStatement* stmt, + struct AdbcError* err); +AdbcStatusCode SnowflakeStatementRelease(struct AdbcStatement* stmt, + struct AdbcError* err); +AdbcStatusCode SnowflakeStatementSetOption(struct AdbcStatement* stmt, const char* key, + const char* value, struct AdbcError* err); +AdbcStatusCode SnowflakeStatementSetOptionBytes(struct AdbcStatement*, const char*, + const uint8_t*, size_t, + struct AdbcError*); +AdbcStatusCode SnowflakeStatementSetOptionDouble(struct AdbcStatement*, const char*, + double, struct AdbcError*); +AdbcStatusCode SnowflakeStatementSetOptionInt(struct AdbcStatement*, const char*, int64_t, + struct AdbcError*); +AdbcStatusCode SnowflakeStatementSetSqlQuery(struct AdbcStatement* stmt, + const char* query, struct AdbcError* err); +AdbcStatusCode SnowflakeStatementSetSubstraitPlan(struct AdbcStatement* stmt, + const uint8_t* plan, size_t length, + struct AdbcError* err); + AdbcStatusCode SnowflakeDriverInit(int version, void* rawDriver, struct AdbcError* err); static inline void SnowflakeerrRelease(struct AdbcError* error) { error->release(error); }