diff --git a/.gitattributes b/.gitattributes index 7f39f6a39c..c4d02d4c4f 100644 --- a/.gitattributes +++ b/.gitattributes @@ -16,6 +16,8 @@ # under the License. c/vendor/* linguist-vendored +go/adbc/drivermgr/adbc.h linguist-vendored +go/adbc/drivermgr/adbc_driver_manager.cc linguist-vendored python/adbc_driver_flightsql/adbc_driver_flightsql/_static_version.py export-subst python/adbc_driver_manager/adbc_driver_manager/_static_version.py export-subst python/adbc_driver_postgresql/adbc_driver_postgresql/_static_version.py export-subst diff --git a/adbc.h b/adbc.h index 9d9fa92347..32eaf8b284 100644 --- a/adbc.h +++ b/adbc.h @@ -248,7 +248,25 @@ typedef uint8_t AdbcStatusCode; /// May indicate a database-side error only. #define ADBC_STATUS_UNAUTHORIZED 14 +/// \brief Inform the driver/driver manager that we are using the extended +/// AdbcError struct from ADBC 1.1.0. +/// +/// See the AdbcError documentation for usage. +/// +/// \since ADBC API revision 1.1.0 +/// \addtogroup adbc-1.1.0 +#define ADBC_ERROR_VENDOR_CODE_PRIVATE_DATA INT32_MIN + /// \brief A detailed error message for an operation. +/// +/// The caller must zero-initialize this struct (clarified in ADBC 1.1.0). +/// +/// The structure was extended in ADBC 1.1.0. Drivers and clients using ADBC +/// 1.0.0 will not have the private_data or private_driver fields. Drivers +/// should read/write these fields if and only if vendor_code is equal to +/// ADBC_ERROR_VENDOR_CODE_PRIVATE_DATA. Clients are required to initialize +/// this struct to avoid the possibility of uninitialized values confusing the +/// driver. struct ADBC_EXPORT AdbcError { /// \brief The error message. char* message; @@ -266,8 +284,103 @@ struct ADBC_EXPORT AdbcError { /// Unlike other structures, this is an embedded callback to make it /// easier for the driver manager and driver to cooperate. void (*release)(struct AdbcError* error); + + /// \brief Opaque implementation-defined state. + /// + /// This field may not be used unless vendor_code is + /// ADBC_ERROR_VENDOR_CODE_PRIVATE_DATA. If present, this field is NULLPTR + /// iff the error is unintialized/freed. + /// + /// \since ADBC API revision 1.1.0 + /// \addtogroup adbc-1.1.0 + void* private_data; + + /// \brief The associated driver (used by the driver manager to help + /// track state). + /// + /// This field may not be used unless vendor_code is + /// ADBC_ERROR_VENDOR_CODE_PRIVATE_DATA. + /// + /// \since ADBC API revision 1.1.0 + /// \addtogroup adbc-1.1.0 + struct AdbcDriver* private_driver; +}; + +#ifdef __cplusplus +/// \brief A helper to initialize the full AdbcError structure. +/// +/// \since ADBC API revision 1.1.0 +/// \addtogroup adbc-1.1.0 +#define ADBC_ERROR_INIT \ + (AdbcError{nullptr, \ + ADBC_ERROR_VENDOR_CODE_PRIVATE_DATA, \ + {0, 0, 0, 0, 0}, \ + nullptr, \ + nullptr, \ + nullptr}) +#else +/// \brief A helper to initialize the full AdbcError structure. +/// +/// \since ADBC API revision 1.1.0 +/// \addtogroup adbc-1.1.0 +#define ADBC_ERROR_INIT \ + ((struct AdbcError){ \ + NULL, ADBC_ERROR_VENDOR_CODE_PRIVATE_DATA, {0, 0, 0, 0, 0}, NULL, NULL, NULL}) +#endif + +/// \brief The size of the AdbcError structure in ADBC 1.0.0. +/// +/// Drivers written for ADBC 1.1.0 and later should never touch more than this +/// portion of an AdbcDriver struct when vendor_code is not +/// ADBC_ERROR_VENDOR_CODE_PRIVATE_DATA. +/// +/// \since ADBC API revision 1.1.0 +/// \addtogroup adbc-1.1.0 +#define ADBC_ERROR_1_0_0_SIZE (offsetof(struct AdbcError, private_data)) +/// \brief The size of the AdbcError structure in ADBC 1.1.0. +/// +/// Drivers written for ADBC 1.1.0 and later should never touch more than this +/// portion of an AdbcDriver struct when vendor_code is +/// ADBC_ERROR_VENDOR_CODE_PRIVATE_DATA. +/// +/// \since ADBC API revision 1.1.0 +/// \addtogroup adbc-1.1.0 +#define ADBC_ERROR_1_1_0_SIZE (sizeof(struct AdbcError)) + +/// \brief Extra key-value metadata for an error. +/// +/// The fields here are owned by the driver and should not be freed. The +/// fields here are invalidated when the release callback in AdbcError is +/// called. +/// +/// \since ADBC API revision 1.1.0 +/// \addtogroup adbc-1.1.0 +struct ADBC_EXPORT AdbcErrorDetail { + /// \brief The metadata key. + const char* key; + /// \brief The binary metadata value. + const uint8_t* value; + /// \brief The length of the metadata value. + size_t value_length; }; +/// \brief Get the number of metadata values available in an error. +/// +/// \since ADBC API revision 1.1.0 +/// \addtogroup adbc-1.1.0 +ADBC_EXPORT +int AdbcErrorGetDetailCount(struct AdbcError* error); + +/// \brief Get a metadata value in an error by index. +/// +/// If index is invalid, returns an AdbcErrorDetail initialized with NULL/0 +/// fields. +/// +/// \since ADBC API revision 1.1.0 +/// \addtogroup adbc-1.1.0 +ADBC_EXPORT +struct AdbcErrorDetail AdbcErrorGetDetail(struct AdbcError* error, int index); + /// @} /// \defgroup adbc-constants Constants @@ -284,6 +397,7 @@ struct ADBC_EXPORT AdbcError { /// When passed to an AdbcDriverInitFunc(), the driver parameter must /// point to an AdbcDriver. /// +/// \since ADBC API revision 1.1.0 /// \addtogroup adbc-1.1.0 #define ADBC_VERSION_1_1_0 1001000 @@ -894,6 +1008,9 @@ struct ADBC_EXPORT AdbcDriver { /// /// @{ + int (*ErrorGetDetailCount)(struct AdbcError* error); + struct AdbcErrorDetail (*ErrorGetDetail)(struct AdbcError* error, int index); + AdbcStatusCode (*DatabaseGetOption)(struct AdbcDatabase*, const char*, char*, size_t*, struct AdbcError*); AdbcStatusCode (*DatabaseGetOptionBytes)(struct AdbcDatabase*, const char*, uint8_t*, @@ -959,7 +1076,7 @@ struct ADBC_EXPORT AdbcDriver { /// /// \since ADBC API revision 1.1.0 /// \addtogroup adbc-1.1.0 -#define ADBC_DRIVER_1_0_0_SIZE (offsetof(struct AdbcDriver, DatabaseGetOption)) +#define ADBC_DRIVER_1_0_0_SIZE (offsetof(struct AdbcDriver, ErrorGetDetailCount)) /// \brief The size of the AdbcDriver structure in ADBC 1.1.0. /// Drivers written for ADBC 1.1.0 and later should never touch more diff --git a/c/driver/common/utils.c b/c/driver/common/utils.c index eb01bc18a4..abd41f8c82 100644 --- a/c/driver/common/utils.c +++ b/c/driver/common/utils.c @@ -18,14 +18,46 @@ #include "utils.h" #include -#include #include #include #include #include +#include "adbc.h" + +static size_t kErrorBufferSize = 1024; + +/// For ADBC 1.1.0, the structure held in private_data. +struct AdbcErrorDetails { + char* message; + + // The metadata keys (may be NULL). + char** keys; + // The metadata values (may be NULL). + uint8_t** values; + // The metadata value lengths (may be NULL). + size_t* lengths; + // The number of initialized metadata. + int count; + // The length of the keys/values/lengths arrays above. + int capacity; +}; + +static void ReleaseErrorWithDetails(struct AdbcError* error) { + struct AdbcErrorDetails* details = (struct AdbcErrorDetails*)error->private_data; + free(details->message); + + for (int i = 0; i < details->count; i++) { + free(details->keys[i]); + free(details->values[i]); + } -static size_t kErrorBufferSize = 256; + free(details->keys); + free(details->values); + free(details->lengths); + free(error->private_data); + *error = ADBC_ERROR_INIT; +} static void ReleaseError(struct AdbcError* error) { free(error->message); @@ -34,20 +66,126 @@ static void ReleaseError(struct AdbcError* error) { } void SetError(struct AdbcError* error, const char* format, ...) { + va_list args; + va_start(args, format); + SetErrorVariadic(error, format, args); + va_end(args); +} + +void SetErrorVariadic(struct AdbcError* error, const char* format, va_list args) { if (!error) return; if (error->release) { // TODO: combine the errors if possible error->release(error); } - error->message = malloc(kErrorBufferSize); - if (!error->message) return; - error->release = &ReleaseError; + if (error->vendor_code == ADBC_ERROR_VENDOR_CODE_PRIVATE_DATA) { + error->private_data = malloc(sizeof(struct AdbcErrorDetails)); + if (!error->private_data) return; + + struct AdbcErrorDetails* details = (struct AdbcErrorDetails*)error->private_data; + + details->message = malloc(kErrorBufferSize); + if (!details->message) { + free(details); + return; + } + details->keys = NULL; + details->values = NULL; + details->lengths = NULL; + details->count = 0; + details->capacity = 0; + + error->message = details->message; + error->release = &ReleaseErrorWithDetails; + } else { + error->message = malloc(kErrorBufferSize); + if (!error->message) return; + + error->release = &ReleaseError; + } - va_list args; - va_start(args, format); vsnprintf(error->message, kErrorBufferSize, format, args); - va_end(args); +} + +void AppendErrorDetail(struct AdbcError* error, const char* key, const uint8_t* detail, + size_t detail_length) { + if (error->release != ReleaseErrorWithDetails) return; + + struct AdbcErrorDetails* details = (struct AdbcErrorDetails*)error->private_data; + if (details->count >= details->capacity) { + int new_capacity = (details->capacity == 0) ? 4 : (2 * details->capacity); + char** new_keys = calloc(new_capacity, sizeof(char*)); + if (!new_keys) { + return; + } + + uint8_t** new_values = calloc(new_capacity, sizeof(uint8_t*)); + if (!new_values) { + free(new_keys); + return; + } + + size_t* new_lengths = calloc(new_capacity, sizeof(size_t*)); + if (!new_lengths) { + free(new_keys); + free(new_values); + return; + } + + memcpy(new_keys, details->keys, sizeof(char*) * details->count); + free(details->keys); + details->keys = new_keys; + + memcpy(new_values, details->values, sizeof(uint8_t*) * details->count); + free(details->values); + details->values = new_values; + + memcpy(new_lengths, details->lengths, sizeof(size_t) * details->count); + free(details->lengths); + details->lengths = new_lengths; + + details->capacity = new_capacity; + } + + char* key_data = strdup(key); + if (!key_data) return; + uint8_t* value_data = malloc(detail_length); + if (!value_data) { + free(key_data); + return; + } + memcpy(value_data, detail, detail_length); + + int index = details->count; + details->keys[index] = key_data; + details->values[index] = value_data; + details->lengths[index] = detail_length; + + details->count++; +} + +int CommonErrorGetDetailCount(struct AdbcError* error) { + if (error->release != ReleaseErrorWithDetails) { + return 0; + } + struct AdbcErrorDetails* details = (struct AdbcErrorDetails*)error->private_data; + return details->count; +} + +struct AdbcErrorDetail CommonErrorGetDetail(struct AdbcError* error, int index) { + if (error->release != ReleaseErrorWithDetails) { + return (struct AdbcErrorDetail){NULL, NULL, 0}; + } + struct AdbcErrorDetails* details = (struct AdbcErrorDetails*)error->private_data; + if (index < 0 || index >= details->count) { + return (struct AdbcErrorDetail){NULL, NULL, 0}; + } + return (struct AdbcErrorDetail){ + .key = details->keys[index], + .value = details->values[index], + .value_length = details->lengths[index], + }; } struct SingleBatchArrayStream { diff --git a/c/driver/common/utils.h b/c/driver/common/utils.h index 381c7b05ee..8818555775 100644 --- a/c/driver/common/utils.h +++ b/c/driver/common/utils.h @@ -17,6 +17,7 @@ #pragma once +#include #include #include @@ -35,10 +36,20 @@ extern "C" { #define ADBC_CHECK_PRINTF_ATTRIBUTE #endif -/// Set error details using a format string. +/// Set error message using a format string. void SetError(struct AdbcError* error, const char* format, ...) ADBC_CHECK_PRINTF_ATTRIBUTE; +/// Set error message using a format string. +void SetErrorVariadic(struct AdbcError* error, const char* format, va_list args); + +/// Add an error detail. +void AppendErrorDetail(struct AdbcError* error, const char* key, const uint8_t* detail, + size_t detail_length); + +int CommonErrorGetDetailCount(struct AdbcError* error); +struct AdbcErrorDetail CommonErrorGetDetail(struct AdbcError* error, int index); + struct StringBuilder { char* buffer; // Not including null terminator diff --git a/c/driver/common/utils_test.cc b/c/driver/common/utils_test.cc index 6fa7e254df..d5c202bf2e 100644 --- a/c/driver/common/utils_test.cc +++ b/c/driver/common/utils_test.cc @@ -15,6 +15,12 @@ // specific language governing permissions and limitations // under the License. +#include +#include +#include +#include + +#include #include #include "utils.h" @@ -72,3 +78,92 @@ TEST(TestStringBuilder, TestMultipleAppends) { StringBuilderReset(&str); } + +TEST(ErrorDetails, Adbc100) { + struct AdbcError error; + std::memset(&error, 0, ADBC_ERROR_1_1_0_SIZE); + + SetError(&error, "My message"); + + ASSERT_EQ(nullptr, error.private_data); + ASSERT_EQ(nullptr, error.private_driver); + + { + std::string detail = "detail"; + AppendErrorDetail(&error, "key", reinterpret_cast(detail.data()), + detail.size()); + } + + ASSERT_EQ(0, CommonErrorGetDetailCount(&error)); + struct AdbcErrorDetail detail = CommonErrorGetDetail(&error, 0); + ASSERT_EQ(nullptr, detail.key); + ASSERT_EQ(nullptr, detail.value); + ASSERT_EQ(0, detail.value_length); + + error.release(&error); +} + +TEST(ErrorDetails, Adbc110) { + struct AdbcError error = ADBC_ERROR_INIT; + SetError(&error, "My message"); + + ASSERT_NE(nullptr, error.private_data); + ASSERT_EQ(nullptr, error.private_driver); + + { + std::string detail = "detail"; + AppendErrorDetail(&error, "key", reinterpret_cast(detail.data()), + detail.size()); + } + + ASSERT_EQ(1, CommonErrorGetDetailCount(&error)); + struct AdbcErrorDetail detail = CommonErrorGetDetail(&error, 0); + ASSERT_STREQ("key", detail.key); + ASSERT_EQ("detail", std::string_view(reinterpret_cast(detail.value), + detail.value_length)); + + detail = CommonErrorGetDetail(&error, -1); + ASSERT_EQ(nullptr, detail.key); + ASSERT_EQ(nullptr, detail.value); + ASSERT_EQ(0, detail.value_length); + + detail = CommonErrorGetDetail(&error, 2); + ASSERT_EQ(nullptr, detail.key); + ASSERT_EQ(nullptr, detail.value); + ASSERT_EQ(0, detail.value_length); + + error.release(&error); + ASSERT_EQ(nullptr, error.private_data); + ASSERT_EQ(nullptr, error.private_driver); +} + +TEST(ErrorDetails, RoundTripValues) { + struct AdbcError error = ADBC_ERROR_INIT; + SetError(&error, "My message"); + + struct Detail { + std::string key; + std::vector value; + }; + + std::vector details = { + {"x-key-1", {0, 1, 2, 3}}, {"x-key-2", {1, 1}}, {"x-key-3", {128, 129, 200, 0, 1}}, + {"x-key-4", {97, 98, 99}}, {"x-key-5", {42}}, + }; + + for (const auto& detail : details) { + AppendErrorDetail(&error, detail.key.c_str(), detail.value.data(), + detail.value.size()); + } + + ASSERT_EQ(details.size(), CommonErrorGetDetailCount(&error)); + for (int i = 0; i < static_cast(details.size()); i++) { + struct AdbcErrorDetail detail = CommonErrorGetDetail(&error, i); + ASSERT_EQ(details[i].key, detail.key); + ASSERT_EQ(details[i].value.size(), detail.value_length); + ASSERT_THAT(std::vector(detail.value, detail.value + detail.value_length), + ::testing::ElementsAreArray(details[i].value)); + } + + error.release(&error); +} diff --git a/c/driver/postgresql/CMakeLists.txt b/c/driver/postgresql/CMakeLists.txt index d16979419a..9cf595a386 100644 --- a/c/driver/postgresql/CMakeLists.txt +++ b/c/driver/postgresql/CMakeLists.txt @@ -29,6 +29,7 @@ endif() add_arrow_lib(adbc_driver_postgresql SOURCES connection.cc + error.cc database.cc postgresql.cc statement.cc diff --git a/c/driver/postgresql/connection.cc b/c/driver/postgresql/connection.cc index 1c1b0507e5..df612ea879 100644 --- a/c/driver/postgresql/connection.cc +++ b/c/driver/postgresql/connection.cc @@ -32,7 +32,9 @@ #include "common/utils.h" #include "database.h" +#include "error.h" +namespace adbcpq { namespace { static const uint32_t kSupportedInfoCodes[] = { @@ -52,10 +54,10 @@ struct PqRecord { }; // Used by PqResultHelper to provide index-based access to the records within each -// row of a pg_result +// row of a PGresult class PqResultRow { public: - PqResultRow(pg_result* result, int row_num) : result_(result), row_num_(row_num) { + PqResultRow(PGresult* result, int row_num) : result_(result), row_num_(row_num) { ncols_ = PQnfields(result); } @@ -69,7 +71,7 @@ class PqResultRow { } private: - pg_result* result_ = nullptr; + PGresult* result_ = nullptr; int row_num_; int ncols_; }; @@ -95,10 +97,11 @@ class PqResultHelper { PGresult* result = PQprepare(conn_, /*stmtName=*/"", query_.c_str(), param_values_.size(), NULL); if (PQresultStatus(result) != PGRES_COMMAND_OK) { - SetError(error_, "[libpq] Failed to prepare query: %s\nQuery was:%s", - PQerrorMessage(conn_), query_.c_str()); + AdbcStatusCode code = + SetError(error_, result, "[libpq] Failed to prepare query: %s\nQuery was:%s", + PQerrorMessage(conn_), query_.c_str()); PQclear(result); - return ADBC_STATUS_IO; + return code; } PQclear(result); @@ -117,9 +120,10 @@ class PqResultHelper { ExecStatusType status = PQresultStatus(result_); if (status != PGRES_TUPLES_OK && status != PGRES_COMMAND_OK) { - SetError(error_, "[libpq] Failed to execute query '%s': %s", query_.c_str(), - PQerrorMessage(conn_)); - return ADBC_STATUS_IO; + AdbcStatusCode error = + SetError(error_, result_, "[libpq] Failed to execute query '%s': %s", + query_.c_str(), PQerrorMessage(conn_)); + return error; } return ADBC_STATUS_OK; @@ -167,7 +171,7 @@ class PqResultHelper { iterator end() { return iterator(*this, NumRows()); } private: - pg_result* result_ = nullptr; + PGresult* result_ = nullptr; PGconn* conn_; std::string query_; std::vector param_values_; @@ -730,8 +734,6 @@ class PqGetObjectsHelper { } // namespace -namespace adbcpq { - AdbcStatusCode PostgresConnection::Cancel(struct AdbcError* error) { // > errbuf must be a char array of size errbufsize (the recommended size is // > 256 bytes). @@ -754,9 +756,10 @@ AdbcStatusCode PostgresConnection::Commit(struct AdbcError* error) { PGresult* result = PQexec(conn_, "COMMIT"); if (PQresultStatus(result) != PGRES_COMMAND_OK) { - SetError(error, "%s%s", "[libpq] Failed to commit: ", PQerrorMessage(conn_)); + AdbcStatusCode code = SetError(error, result, "%s%s", + "[libpq] Failed to commit: ", PQerrorMessage(conn_)); PQclear(result); - return ADBC_STATUS_IO; + return code; } PQclear(result); return ADBC_STATUS_OK; diff --git a/c/driver/postgresql/database.cc b/c/driver/postgresql/database.cc index 2e18b5bf30..5de8628095 100644 --- a/c/driver/postgresql/database.cc +++ b/c/driver/postgresql/database.cc @@ -125,10 +125,10 @@ AdbcStatusCode PostgresDatabase::Disconnect(PGconn** conn, struct AdbcError* err // Helpers for building the type resolver from queries static inline int32_t InsertPgAttributeResult( - pg_result* result, const std::shared_ptr& resolver); + PGresult* result, const std::shared_ptr& resolver); static inline int32_t InsertPgTypeResult( - pg_result* result, const std::shared_ptr& resolver); + PGresult* result, const std::shared_ptr& resolver); AdbcStatusCode PostgresDatabase::RebuildTypeResolver(struct AdbcError* error) { PGconn* conn = nullptr; @@ -177,7 +177,7 @@ ORDER BY auto resolver = std::make_shared(); // Insert record type definitions (this includes table schemas) - pg_result* result = PQexec(conn, kColumnsQuery.c_str()); + PGresult* result = PQexec(conn, kColumnsQuery.c_str()); ExecStatusType pq_status = PQresultStatus(result); if (pq_status == PGRES_TUPLES_OK) { InsertPgAttributeResult(result, resolver); @@ -222,7 +222,7 @@ ORDER BY } static inline int32_t InsertPgAttributeResult( - pg_result* result, const std::shared_ptr& resolver) { + PGresult* result, const std::shared_ptr& resolver) { int num_rows = PQntuples(result); std::vector> columns; uint32_t current_type_oid = 0; @@ -254,7 +254,7 @@ static inline int32_t InsertPgAttributeResult( } static inline int32_t InsertPgTypeResult( - pg_result* result, const std::shared_ptr& resolver) { + PGresult* result, const std::shared_ptr& resolver) { int num_rows = PQntuples(result); PostgresTypeResolver::Item item; int32_t n_added = 0; diff --git a/c/driver/postgresql/database.h b/c/driver/postgresql/database.h index 9269b1958b..6c3da58daa 100644 --- a/c/driver/postgresql/database.h +++ b/c/driver/postgresql/database.h @@ -66,3 +66,10 @@ class PostgresDatabase { std::shared_ptr type_resolver_; }; } // namespace adbcpq + +extern "C" { +/// For applications that want to use the driver struct directly, this gives +/// them access to the Init routine. +ADBC_EXPORT +AdbcStatusCode PostgresqlDriverInit(int, void*, struct AdbcError*); +} diff --git a/c/driver/postgresql/error.cc b/c/driver/postgresql/error.cc new file mode 100644 index 0000000000..47e04496ba --- /dev/null +++ b/c/driver/postgresql/error.cc @@ -0,0 +1,97 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#include "error.h" + +#include +#include +#include +#include +#include + +#include + +#include "common/utils.h" + +namespace adbcpq { + +namespace { +struct DetailField { + int code; + std::string key; +}; + +static const std::vector kDetailFields = { + {PG_DIAG_COLUMN_NAME, "PG_DIAG_COLUMN_NAME"}, + {PG_DIAG_CONTEXT, "PG_DIAG_CONTEXT"}, + {PG_DIAG_CONSTRAINT_NAME, "PG_DIAG_CONSTRAINT_NAME"}, + {PG_DIAG_DATATYPE_NAME, "PG_DIAG_DATATYPE_NAME"}, + {PG_DIAG_INTERNAL_POSITION, "PG_DIAG_INTERNAL_POSITION"}, + {PG_DIAG_INTERNAL_QUERY, "PG_DIAG_INTERNAL_QUERY"}, + {PG_DIAG_MESSAGE_PRIMARY, "PG_DIAG_MESSAGE_PRIMARY"}, + {PG_DIAG_MESSAGE_DETAIL, "PG_DIAG_MESSAGE_DETAIL"}, + {PG_DIAG_MESSAGE_HINT, "PG_DIAG_MESSAGE_HINT"}, + {PG_DIAG_SEVERITY_NONLOCALIZED, "PG_DIAG_SEVERITY_NONLOCALIZED"}, + {PG_DIAG_SQLSTATE, "PG_DIAG_SQLSTATE"}, + {PG_DIAG_STATEMENT_POSITION, "PG_DIAG_STATEMENT_POSITION"}, + {PG_DIAG_SCHEMA_NAME, "PG_DIAG_SCHEMA_NAME"}, + {PG_DIAG_TABLE_NAME, "PG_DIAG_TABLE_NAME"}, +}; +} // namespace + +AdbcStatusCode SetError(struct AdbcError* error, PGresult* result, const char* format, + ...) { + va_list args; + va_start(args, format); + SetErrorVariadic(error, format, args); + va_end(args); + + AdbcStatusCode code = ADBC_STATUS_IO; + + const char* sqlstate = PQresultErrorField(result, PG_DIAG_SQLSTATE); + if (sqlstate) { + // https://www.postgresql.org/docs/current/errcodes-appendix.html + // This can be extended in the future + if (std::strcmp(sqlstate, "57014") == 0) { + code = ADBC_STATUS_CANCELLED; + } else if (std::strncmp(sqlstate, "42", 0) == 0) { + // Class 42 — Syntax Error or Access Rule Violation + code = ADBC_STATUS_INVALID_ARGUMENT; + } + + static_assert(sizeof(error->sqlstate) == 5, ""); + // N.B. strncpy generates warnings when used for this purpose + int i = 0; + for (; sqlstate[i] != '\0' && i < 5; i++) { + error->sqlstate[i] = sqlstate[i]; + } + for (; i < 5; i++) { + error->sqlstate[i] = '\0'; + } + } + + for (const auto& field : kDetailFields) { + const char* value = PQresultErrorField(result, field.code); + if (value) { + AppendErrorDetail(error, field.key.c_str(), reinterpret_cast(value), + std::strlen(value)); + } + } + return code; +} + +} // namespace adbcpq diff --git a/c/driver/postgresql/error.h b/c/driver/postgresql/error.h new file mode 100644 index 0000000000..75c52b46c3 --- /dev/null +++ b/c/driver/postgresql/error.h @@ -0,0 +1,42 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +// Error handling utilities. + +#pragma once + +#include +#include + +namespace adbcpq { + +// The printf checking attribute doesn't work properly on gcc 4.8 +// and results in spurious compiler warnings +#if defined(__clang__) || (defined(__GNUC__) && __GNUC__ >= 5) +#define ADBC_CHECK_PRINTF_ATTRIBUTE(x, y) __attribute__((format(printf, x, y))) +#else +#define ADBC_CHECK_PRINTF_ATTRIBUTE(x, y) +#endif + +/// \brief Set an error based on a PGresult, inferring the proper ADBC status +/// code from the PGresult. +AdbcStatusCode SetError(struct AdbcError* error, PGresult* result, const char* format, + ...) ADBC_CHECK_PRINTF_ATTRIBUTE(3, 4); + +#undef ADBC_CHECK_PRINTF_ATTRIBUTE + +} // namespace adbcpq diff --git a/c/driver/postgresql/postgresql.cc b/c/driver/postgresql/postgresql.cc index 9f6730eb4f..d88a0b28e9 100644 --- a/c/driver/postgresql/postgresql.cc +++ b/c/driver/postgresql/postgresql.cc @@ -34,7 +34,7 @@ using adbcpq::PostgresStatement; // --------------------------------------------------------------------- // ADBC interface implementation - as private functions so that these // don't get replaced by the dynamic linker. If we implemented these -// under the Adbc* names, then DriverInit, the linker may resolve +// under the Adbc* names, then in DriverInit, the linker may resolve // functions to the address of the functions provided by the driver // manager instead of our functions. // @@ -47,6 +47,17 @@ using adbcpq::PostgresStatement; // // So in the end some manual effort here was chosen. +// --------------------------------------------------------------------- +// AdbcError + +int AdbcErrorGetDetailCount(struct AdbcError* error) { + return CommonErrorGetDetailCount(error); +} + +struct AdbcErrorDetail AdbcErrorGetDetail(struct AdbcError* error, int index) { + return CommonErrorGetDetail(error, index); +} + // --------------------------------------------------------------------- // AdbcDatabase @@ -799,6 +810,9 @@ AdbcStatusCode PostgresqlDriverInit(int version, void* raw_driver, if (version >= ADBC_VERSION_1_1_0) { std::memset(driver, 0, ADBC_DRIVER_1_1_0_SIZE); + driver->ErrorGetDetailCount = CommonErrorGetDetailCount; + driver->ErrorGetDetail = CommonErrorGetDetail; + driver->DatabaseGetOption = PostgresDatabaseGetOption; driver->DatabaseGetOptionBytes = PostgresDatabaseGetOptionBytes; driver->DatabaseGetOptionDouble = PostgresDatabaseGetOptionDouble; diff --git a/c/driver/postgresql/postgresql_test.cc b/c/driver/postgresql/postgresql_test.cc index 9033a22f5c..44b434a12b 100644 --- a/c/driver/postgresql/postgresql_test.cc +++ b/c/driver/postgresql/postgresql_test.cc @@ -25,8 +25,9 @@ #include #include #include -#include "common/utils.h" +#include "common/utils.h" +#include "database.h" #include "validation/adbc_validation.h" #include "validation/adbc_validation_util.h" @@ -140,6 +141,20 @@ class PostgresDatabaseTest : public ::testing::Test, }; ADBCV_TEST_DATABASE(PostgresDatabaseTest) +TEST_F(PostgresDatabaseTest, AdbcDriverBackwardsCompatibility) { + // XXX: sketchy cast + auto* driver = static_cast(malloc(ADBC_DRIVER_1_0_0_SIZE)); + std::memset(driver, 0, ADBC_DRIVER_1_0_0_SIZE); + + ASSERT_THAT(::PostgresqlDriverInit(ADBC_VERSION_1_0_0, driver, &error), + IsOkStatus(&error)); + + ASSERT_THAT(::PostgresqlDriverInit(424242, driver, &error), + IsStatus(ADBC_STATUS_NOT_IMPLEMENTED, &error)); + + free(driver); +} + class PostgresConnectionTest : public ::testing::Test, public adbc_validation::ConnectionTest { public: @@ -537,13 +552,13 @@ TEST_F(PostgresConnectionTest, MetadataGetTableSchemaInjection) { /*db_schema=*/nullptr, "0'::int; DROP TABLE bulk_ingest;--", &schema.value, &error), - IsStatus(ADBC_STATUS_IO, &error)); + IsStatus(ADBC_STATUS_INVALID_ARGUMENT, &error)); ASSERT_THAT( AdbcConnectionGetTableSchema(&connection, /*catalog=*/nullptr, /*db_schema=*/"0'::int; DROP TABLE bulk_ingest;--", "DROP TABLE bulk_ingest;", &schema.value, &error), - IsStatus(ADBC_STATUS_IO, &error)); + IsStatus(ADBC_STATUS_INVALID_ARGUMENT, &error)); ASSERT_THAT(AdbcConnectionGetTableSchema(&connection, /*catalog=*/nullptr, /*db_schema=*/nullptr, "bulk_ingest", @@ -586,11 +601,27 @@ TEST_F(PostgresConnectionTest, MetadataSetCurrentDbSchema) { IsOkStatus(&error)); // Table does not exist in this schema + error.vendor_code = ADBC_ERROR_VENDOR_CODE_PRIVATE_DATA; ASSERT_THAT( AdbcStatementSetSqlQuery(&statement.value, "SELECT * FROM schematable", &error), IsOkStatus(&error)); ASSERT_THAT(AdbcStatementExecuteQuery(&statement.value, nullptr, nullptr, &error), - IsStatus(ADBC_STATUS_IO, &error)); + IsStatus(ADBC_STATUS_INVALID_ARGUMENT, &error)); + // 42P01 = table not found + ASSERT_EQ("42P01", std::string_view(error.sqlstate, 5)); + ASSERT_NE(0, AdbcErrorGetDetailCount(&error)); + bool found = false; + for (int i = 0; i < AdbcErrorGetDetailCount(&error); i++) { + struct AdbcErrorDetail detail = AdbcErrorGetDetail(&error, i); + if (std::strcmp(detail.key, "PG_DIAG_MESSAGE_PRIMARY") == 0) { + found = true; + std::string_view message(reinterpret_cast(detail.value), + detail.value_length); + ASSERT_THAT(message, ::testing::HasSubstr("schematable")); + } + } + error.release(&error); + ASSERT_TRUE(found) << "Did not find expected error detail"; ASSERT_THAT( AdbcConnectionSetOption(&connection, ADBC_CONNECTION_OPTION_CURRENT_DB_SCHEMA, @@ -761,6 +792,28 @@ TEST_F(PostgresStatementTest, BatchSizeHint) { } } +// Test that an ADBC 1.0.0-sized error still works +TEST_F(PostgresStatementTest, AdbcErrorBackwardsCompatibility) { + // XXX: sketchy cast + auto* error = static_cast(malloc(ADBC_ERROR_1_0_0_SIZE)); + std::memset(error, 0, ADBC_ERROR_1_0_0_SIZE); + + ASSERT_THAT(AdbcStatementNew(&connection, &statement, error), IsOkStatus(error)); + ASSERT_THAT( + AdbcStatementSetSqlQuery(&statement, "SELECT * FROM thistabledoesnotexist", error), + IsOkStatus(error)); + adbc_validation::StreamReader reader; + ASSERT_THAT(AdbcStatementExecuteQuery(&statement, &reader.stream.value, + &reader.rows_affected, error), + IsStatus(ADBC_STATUS_INVALID_ARGUMENT, error)); + + ASSERT_EQ("42P01", std::string_view(error->sqlstate, 5)); + ASSERT_EQ(0, AdbcErrorGetDetailCount(error)); + + error->release(error); + free(error); +} + struct TypeTestCase { std::string name; std::string sql_type; diff --git a/c/driver/postgresql/statement.cc b/c/driver/postgresql/statement.cc index b60cc43b23..076dca8480 100644 --- a/c/driver/postgresql/statement.cc +++ b/c/driver/postgresql/statement.cc @@ -28,6 +28,7 @@ #include #include +#include #include #include "common/utils.h" @@ -262,20 +263,23 @@ struct BindStream { if (autocommit) { PGresult* begin_result = PQexec(conn, "BEGIN"); if (PQresultStatus(begin_result) != PGRES_COMMAND_OK) { - SetError(error, "[libpq] Failed to begin transaction for timezone data: %s", - PQerrorMessage(conn)); + AdbcStatusCode code = + SetError(error, begin_result, + "[libpq] Failed to begin transaction for timezone data: %s", + PQerrorMessage(conn)); PQclear(begin_result); - return ADBC_STATUS_IO; + return code; } PQclear(begin_result); } PGresult* get_tz_result = PQexec(conn, "SELECT current_setting('TIMEZONE')"); if (PQresultStatus(get_tz_result) != PGRES_TUPLES_OK) { - SetError(error, "[libpq] Could not query current timezone: %s", - PQerrorMessage(conn)); + AdbcStatusCode code = SetError(error, get_tz_result, + "[libpq] Could not query current timezone: %s", + PQerrorMessage(conn)); PQclear(get_tz_result); - return ADBC_STATUS_IO; + return code; } tz_setting = std::string(PQgetvalue(get_tz_result, 0, 0)); @@ -283,10 +287,11 @@ struct BindStream { PGresult* set_utc_result = PQexec(conn, "SET TIME ZONE 'UTC'"); if (PQresultStatus(set_utc_result) != PGRES_COMMAND_OK) { - SetError(error, "[libpq] Failed to set time zone to UTC: %s", - PQerrorMessage(conn)); + AdbcStatusCode code = SetError(error, set_utc_result, + "[libpq] Failed to set time zone to UTC: %s", + PQerrorMessage(conn)); PQclear(set_utc_result); - return ADBC_STATUS_IO; + return code; } PQclear(set_utc_result); break; @@ -296,10 +301,11 @@ struct BindStream { PGresult* result = PQprepare(conn, /*stmtName=*/"", query.c_str(), /*nParams=*/bind_schema->n_children, param_types.data()); if (PQresultStatus(result) != PGRES_COMMAND_OK) { - SetError(error, "[libpq] Failed to prepare query: %s\nQuery was:%s", - PQerrorMessage(conn), query.c_str()); + AdbcStatusCode code = + SetError(error, result, "[libpq] Failed to prepare query: %s\nQuery was:%s", + PQerrorMessage(conn), query.c_str()); PQclear(result); - return ADBC_STATUS_IO; + return code; } PQclear(result); return ADBC_STATUS_OK; @@ -441,10 +447,11 @@ struct BindStream { /*resultFormat=*/0 /*text*/); if (PQresultStatus(result) != PGRES_COMMAND_OK) { - SetError(error, "%s%s", "[libpq] Failed to execute prepared statement: ", - PQerrorMessage(conn)); + AdbcStatusCode code = SetError( + error, result, "%s%s", + "[libpq] Failed to execute prepared statement: ", PQerrorMessage(conn)); PQclear(result); - return ADBC_STATUS_IO; + return code; } PQclear(result); @@ -455,18 +462,21 @@ struct BindStream { std::string reset_query = "SET TIME ZONE '" + tz_setting + "'"; PGresult* reset_tz_result = PQexec(conn, reset_query.c_str()); if (PQresultStatus(reset_tz_result) != PGRES_COMMAND_OK) { - SetError(error, "[libpq] Failed to reset time zone: %s", PQerrorMessage(conn)); + AdbcStatusCode code = + SetError(error, reset_tz_result, "[libpq] Failed to reset time zone: %s", + PQerrorMessage(conn)); PQclear(reset_tz_result); - return ADBC_STATUS_IO; + return code; } PQclear(reset_tz_result); PGresult* commit_result = PQexec(conn, "COMMIT"); if (PQresultStatus(commit_result) != PGRES_COMMAND_OK) { - SetError(error, "[libpq] Failed to commit transaction: %s", - PQerrorMessage(conn)); + AdbcStatusCode code = + SetError(error, commit_result, "[libpq] Failed to commit transaction: %s", + PQerrorMessage(conn)); PQclear(commit_result); - return ADBC_STATUS_IO; + return code; } PQclear(commit_result); } @@ -773,10 +783,11 @@ AdbcStatusCode PostgresStatement::CreateBulkTable( /*paramLengths=*/nullptr, /*paramFormats=*/nullptr, /*resultFormat=*/1 /*(binary)*/); if (PQresultStatus(result) != PGRES_COMMAND_OK) { - SetError(error, "[libpq] Failed to drop table: %s\nQuery was: %s", - PQerrorMessage(connection_->conn()), drop.c_str()); + AdbcStatusCode code = + SetError(error, result, "[libpq] Failed to drop table: %s\nQuery was: %s", + PQerrorMessage(connection_->conn()), drop.c_str()); PQclear(result); - return ADBC_STATUS_IO; + return code; } PQclear(result); break; @@ -837,10 +848,11 @@ AdbcStatusCode PostgresStatement::CreateBulkTable( /*paramLengths=*/nullptr, /*paramFormats=*/nullptr, /*resultFormat=*/1 /*(binary)*/); if (PQresultStatus(result) != PGRES_COMMAND_OK) { - SetError(error, "[libpq] Failed to create table: %s\nQuery was: %s", - PQerrorMessage(connection_->conn()), create.c_str()); + AdbcStatusCode code = + SetError(error, result, "[libpq] Failed to create table: %s\nQuery was: %s", + PQerrorMessage(connection_->conn()), create.c_str()); PQclear(result); - return ADBC_STATUS_IO; + return code; } PQclear(result); return ADBC_STATUS_OK; @@ -936,11 +948,12 @@ AdbcStatusCode PostgresStatement::ExecuteQuery(struct ArrowArrayStream* stream, /*paramTypes=*/nullptr, /*paramValues=*/nullptr, /*paramLengths=*/nullptr, /*paramFormats=*/nullptr, kPgBinaryFormat); if (PQresultStatus(reader_.result_) != PGRES_COPY_OUT) { - SetError(error, - "[libpq] Failed to execute query: could not begin COPY: %s\nQuery was: %s", - PQerrorMessage(connection_->conn()), copy_query.c_str()); + AdbcStatusCode code = SetError( + error, reader_.result_, + "[libpq] Failed to execute query: could not begin COPY: %s\nQuery was: %s", + PQerrorMessage(connection_->conn()), copy_query.c_str()); ClearResult(); - return ADBC_STATUS_IO; + return code; } // Result is read from the connection, not the result, but we won't clear it here } @@ -1009,10 +1022,11 @@ AdbcStatusCode PostgresStatement::ExecuteUpdateQuery(int64_t* rows_affected, /*paramFormats=*/nullptr, /*resultFormat=*/kPgBinaryFormat); ExecStatusType status = PQresultStatus(result); if (status != PGRES_COMMAND_OK && status != PGRES_TUPLES_OK) { - SetError(error, "[libpq] Failed to execute query: %s\nQuery was:%s", - PQerrorMessage(connection_->conn()), query_.c_str()); + AdbcStatusCode code = + SetError(error, result, "[libpq] Failed to execute query: %s\nQuery was:%s", + PQerrorMessage(connection_->conn()), query_.c_str()); PQclear(result); - return ADBC_STATUS_IO; + return code; } if (rows_affected) *rows_affected = PQntuples(reader_.result_); PQclear(result); @@ -1115,6 +1129,7 @@ AdbcStatusCode PostgresStatement::SetOption(const char* key, const char* value, if (std::strcmp(key, ADBC_INGEST_OPTION_TARGET_TABLE) == 0) { query_.clear(); ingest_.target = value; + prepared_ = false; } else if (std::strcmp(key, ADBC_INGEST_OPTION_MODE) == 0) { if (std::strcmp(value, ADBC_INGEST_OPTION_MODE_CREATE) == 0) { ingest_.mode = IngestMode::kCreate; @@ -1128,6 +1143,7 @@ AdbcStatusCode PostgresStatement::SetOption(const char* key, const char* value, SetError(error, "[libpq] Invalid value '%s' for option '%s'", value, key); return ADBC_STATUS_INVALID_ARGUMENT; } + prepared_ = false; } else if (std::strcmp(key, ADBC_POSTGRESQL_OPTION_BATCH_SIZE_HINT_BYTES) == 0) { int64_t int_value = std::atol(value); if (int_value <= 0) { @@ -1175,22 +1191,24 @@ AdbcStatusCode PostgresStatement::SetupReader(struct AdbcError* error) { PGresult* result = PQprepare(connection_->conn(), /*stmtName=*/"", query_.c_str(), /*nParams=*/0, nullptr); if (PQresultStatus(result) != PGRES_COMMAND_OK) { - SetError(error, - "[libpq] Failed to execute query: could not infer schema: failed to " - "prepare query: %s\nQuery was:%s", - PQerrorMessage(connection_->conn()), query_.c_str()); + AdbcStatusCode code = + SetError(error, result, + "[libpq] Failed to execute query: could not infer schema: failed to " + "prepare query: %s\nQuery was:%s", + PQerrorMessage(connection_->conn()), query_.c_str()); PQclear(result); - return ADBC_STATUS_IO; + return code; } PQclear(result); result = PQdescribePrepared(connection_->conn(), /*stmtName=*/""); if (PQresultStatus(result) != PGRES_COMMAND_OK) { - SetError(error, - "[libpq] Failed to execute query: could not infer schema: failed to " - "describe prepared statement: %s\nQuery was:%s", - PQerrorMessage(connection_->conn()), query_.c_str()); + AdbcStatusCode code = + SetError(error, result, + "[libpq] Failed to execute query: could not infer schema: failed to " + "describe prepared statement: %s\nQuery was:%s", + PQerrorMessage(connection_->conn()), query_.c_str()); PQclear(result); - return ADBC_STATUS_IO; + return code; } // Resolve the information from the PGresult into a PostgresType diff --git a/c/driver_manager/adbc_driver_manager.cc b/c/driver_manager/adbc_driver_manager.cc index ea4ddc4c55..370b3eead1 100644 --- a/c/driver_manager/adbc_driver_manager.cc +++ b/c/driver_manager/adbc_driver_manager.cc @@ -130,6 +130,12 @@ static AdbcStatusCode ReleaseDriver(struct AdbcDriver* driver, struct AdbcError* // Default stubs +int ErrorGetDetailCount(struct AdbcError* error) { return 0; } + +struct AdbcErrorDetail ErrorGetDetail(struct AdbcError* error, int index) { + return {nullptr, nullptr, 0}; +} + AdbcStatusCode DatabaseGetOption(struct AdbcDatabase* database, const char* key, char* value, size_t* length, struct AdbcError* error) { return ADBC_STATUS_NOT_FOUND; @@ -366,6 +372,27 @@ struct TempConnection { // Direct implementations of API methods +int AdbcErrorGetDetailCount(struct AdbcError* error) { + if (error->vendor_code == ADBC_ERROR_VENDOR_CODE_PRIVATE_DATA && error->private_data && + error->private_driver) { + return error->private_driver->ErrorGetDetailCount(error); + } + return 0; +} + +struct AdbcErrorDetail AdbcErrorGetDetail(struct AdbcError* error, int index) { + if (error->vendor_code == ADBC_ERROR_VENDOR_CODE_PRIVATE_DATA && error->private_data && + error->private_driver) { + return error->private_driver->ErrorGetDetail(error, index); + } + return {nullptr, nullptr, 0}; +} + +#define INIT_ERROR(ERROR, SOURCE) \ + if ((ERROR)->vendor_code == ADBC_ERROR_VENDOR_CODE_PRIVATE_DATA) { \ + (ERROR)->private_driver = (SOURCE)->private_driver; \ + } + AdbcStatusCode AdbcDatabaseNew(struct AdbcDatabase* database, struct AdbcError* error) { // Allocate a temporary structure to store options pre-Init database->private_data = new TempDatabase(); @@ -377,6 +404,7 @@ AdbcStatusCode AdbcDatabaseGetOption(struct AdbcDatabase* database, const char* char* value, size_t* length, struct AdbcError* error) { if (database->private_driver) { + INIT_ERROR(error, database); return database->private_driver->DatabaseGetOption(database, key, value, length, error); } @@ -406,6 +434,7 @@ AdbcStatusCode AdbcDatabaseGetOptionBytes(struct AdbcDatabase* database, const c uint8_t* value, size_t* length, struct AdbcError* error) { if (database->private_driver) { + INIT_ERROR(error, database); return database->private_driver->DatabaseGetOptionBytes(database, key, value, length, error); } @@ -427,6 +456,7 @@ AdbcStatusCode AdbcDatabaseGetOptionBytes(struct AdbcDatabase* database, const c AdbcStatusCode AdbcDatabaseGetOptionInt(struct AdbcDatabase* database, const char* key, int64_t* value, struct AdbcError* error) { if (database->private_driver) { + INIT_ERROR(error, database); return database->private_driver->DatabaseGetOptionInt(database, key, value, error); } const auto* args = reinterpret_cast(database->private_data); @@ -441,6 +471,7 @@ AdbcStatusCode AdbcDatabaseGetOptionInt(struct AdbcDatabase* database, const cha AdbcStatusCode AdbcDatabaseGetOptionDouble(struct AdbcDatabase* database, const char* key, double* value, struct AdbcError* error) { if (database->private_driver) { + INIT_ERROR(error, database); return database->private_driver->DatabaseGetOptionDouble(database, key, value, error); } const auto* args = reinterpret_cast(database->private_data); @@ -455,6 +486,7 @@ AdbcStatusCode AdbcDatabaseGetOptionDouble(struct AdbcDatabase* database, const AdbcStatusCode AdbcDatabaseSetOption(struct AdbcDatabase* database, const char* key, const char* value, struct AdbcError* error) { if (database->private_driver) { + INIT_ERROR(error, database); return database->private_driver->DatabaseSetOption(database, key, value, error); } @@ -473,6 +505,7 @@ AdbcStatusCode AdbcDatabaseSetOptionBytes(struct AdbcDatabase* database, const c const uint8_t* value, size_t length, struct AdbcError* error) { if (database->private_driver) { + INIT_ERROR(error, database); return database->private_driver->DatabaseSetOptionBytes(database, key, value, length, error); } @@ -485,6 +518,7 @@ AdbcStatusCode AdbcDatabaseSetOptionBytes(struct AdbcDatabase* database, const c AdbcStatusCode AdbcDatabaseSetOptionInt(struct AdbcDatabase* database, const char* key, int64_t value, struct AdbcError* error) { if (database->private_driver) { + INIT_ERROR(error, database); return database->private_driver->DatabaseSetOptionInt(database, key, value, error); } @@ -496,6 +530,7 @@ AdbcStatusCode AdbcDatabaseSetOptionInt(struct AdbcDatabase* database, const cha AdbcStatusCode AdbcDatabaseSetOptionDouble(struct AdbcDatabase* database, const char* key, double value, struct AdbcError* error) { if (database->private_driver) { + INIT_ERROR(error, database); return database->private_driver->DatabaseSetOptionDouble(database, key, value, error); } @@ -566,6 +601,7 @@ AdbcStatusCode AdbcDatabaseInit(struct AdbcDatabase* database, struct AdbcError* auto double_options = std::move(args->double_options); delete args; + INIT_ERROR(error, database); for (const auto& option : options) { status = database->private_driver->DatabaseSetOption(database, option.first.c_str(), option.second.c_str(), error); @@ -616,6 +652,7 @@ AdbcStatusCode AdbcDatabaseRelease(struct AdbcDatabase* database, } return ADBC_STATUS_INVALID_STATE; } + INIT_ERROR(error, database); auto status = database->private_driver->DatabaseRelease(database, error); if (database->private_driver->release) { database->private_driver->release(database->private_driver, error); @@ -631,6 +668,7 @@ AdbcStatusCode AdbcConnectionCancel(struct AdbcConnection* connection, if (!connection->private_driver) { return ADBC_STATUS_INVALID_STATE; } + INIT_ERROR(error, connection); return connection->private_driver->ConnectionCancel(connection, error); } @@ -639,6 +677,7 @@ AdbcStatusCode AdbcConnectionCommit(struct AdbcConnection* connection, if (!connection->private_driver) { return ADBC_STATUS_INVALID_STATE; } + INIT_ERROR(error, connection); return connection->private_driver->ConnectionCommit(connection, error); } @@ -649,6 +688,7 @@ AdbcStatusCode AdbcConnectionGetInfo(struct AdbcConnection* connection, if (!connection->private_driver) { return ADBC_STATUS_INVALID_STATE; } + INIT_ERROR(error, connection); return connection->private_driver->ConnectionGetInfo(connection, info_codes, info_codes_length, out, error); } @@ -662,6 +702,7 @@ AdbcStatusCode AdbcConnectionGetObjects(struct AdbcConnection* connection, int d if (!connection->private_driver) { return ADBC_STATUS_INVALID_STATE; } + INIT_ERROR(error, connection); return connection->private_driver->ConnectionGetObjects( connection, depth, catalog, db_schema, table_name, table_types, column_name, stream, error); @@ -687,6 +728,7 @@ AdbcStatusCode AdbcConnectionGetOption(struct AdbcConnection* connection, const *length = it->second.size() + 1; return ADBC_STATUS_OK; } + INIT_ERROR(error, connection); return connection->private_driver->ConnectionGetOption(connection, key, value, length, error); } @@ -711,6 +753,7 @@ AdbcStatusCode AdbcConnectionGetOptionBytes(struct AdbcConnection* connection, *length = it->second.size() + 1; return ADBC_STATUS_OK; } + INIT_ERROR(error, connection); return connection->private_driver->ConnectionGetOptionBytes(connection, key, value, length, error); } @@ -732,6 +775,7 @@ AdbcStatusCode AdbcConnectionGetOptionInt(struct AdbcConnection* connection, *value = it->second; return ADBC_STATUS_OK; } + INIT_ERROR(error, connection); return connection->private_driver->ConnectionGetOptionInt(connection, key, value, error); } @@ -753,6 +797,7 @@ AdbcStatusCode AdbcConnectionGetOptionDouble(struct AdbcConnection* connection, *value = it->second; return ADBC_STATUS_OK; } + INIT_ERROR(error, connection); return connection->private_driver->ConnectionGetOptionDouble(connection, key, value, error); } @@ -765,6 +810,7 @@ AdbcStatusCode AdbcConnectionGetTableSchema(struct AdbcConnection* connection, if (!connection->private_driver) { return ADBC_STATUS_INVALID_STATE; } + INIT_ERROR(error, connection); return connection->private_driver->ConnectionGetTableSchema( connection, catalog, db_schema, table_name, schema, error); } @@ -775,6 +821,7 @@ AdbcStatusCode AdbcConnectionGetTableTypes(struct AdbcConnection* connection, if (!connection->private_driver) { return ADBC_STATUS_INVALID_STATE; } + INIT_ERROR(error, connection); return connection->private_driver->ConnectionGetTableTypes(connection, stream, error); } @@ -824,6 +871,7 @@ AdbcStatusCode AdbcConnectionInit(struct AdbcConnection* connection, connection, option.first.c_str(), option.second, error); if (status != ADBC_STATUS_OK) return status; } + INIT_ERROR(error, connection); return connection->private_driver->ConnectionInit(connection, database, error); } @@ -845,6 +893,7 @@ AdbcStatusCode AdbcConnectionReadPartition(struct AdbcConnection* connection, if (!connection->private_driver) { return ADBC_STATUS_INVALID_STATE; } + INIT_ERROR(error, connection); return connection->private_driver->ConnectionReadPartition( connection, serialized_partition, serialized_length, out, error); } @@ -860,6 +909,7 @@ AdbcStatusCode AdbcConnectionRelease(struct AdbcConnection* connection, } return ADBC_STATUS_INVALID_STATE; } + INIT_ERROR(error, connection); auto status = connection->private_driver->ConnectionRelease(connection, error); connection->private_driver = nullptr; return status; @@ -870,6 +920,7 @@ AdbcStatusCode AdbcConnectionRollback(struct AdbcConnection* connection, if (!connection->private_driver) { return ADBC_STATUS_INVALID_STATE; } + INIT_ERROR(error, connection); return connection->private_driver->ConnectionRollback(connection, error); } @@ -885,6 +936,7 @@ AdbcStatusCode AdbcConnectionSetOption(struct AdbcConnection* connection, const args->options[key] = value; return ADBC_STATUS_OK; } + INIT_ERROR(error, connection); return connection->private_driver->ConnectionSetOption(connection, key, value, error); } @@ -901,6 +953,7 @@ AdbcStatusCode AdbcConnectionSetOptionBytes(struct AdbcConnection* connection, args->bytes_options[key] = std::string(reinterpret_cast(value), length); return ADBC_STATUS_OK; } + INIT_ERROR(error, connection); return connection->private_driver->ConnectionSetOptionBytes(connection, key, value, length, error); } @@ -918,6 +971,7 @@ AdbcStatusCode AdbcConnectionSetOptionInt(struct AdbcConnection* connection, args->int_options[key] = value; return ADBC_STATUS_OK; } + INIT_ERROR(error, connection); return connection->private_driver->ConnectionSetOptionInt(connection, key, value, error); } @@ -935,6 +989,7 @@ AdbcStatusCode AdbcConnectionSetOptionDouble(struct AdbcConnection* connection, args->double_options[key] = value; return ADBC_STATUS_OK; } + INIT_ERROR(error, connection); return connection->private_driver->ConnectionSetOptionDouble(connection, key, value, error); } @@ -945,6 +1000,7 @@ AdbcStatusCode AdbcStatementBind(struct AdbcStatement* statement, if (!statement->private_driver) { return ADBC_STATUS_INVALID_STATE; } + INIT_ERROR(error, statement); return statement->private_driver->StatementBind(statement, values, schema, error); } @@ -954,6 +1010,7 @@ AdbcStatusCode AdbcStatementBindStream(struct AdbcStatement* statement, if (!statement->private_driver) { return ADBC_STATUS_INVALID_STATE; } + INIT_ERROR(error, statement); return statement->private_driver->StatementBindStream(statement, stream, error); } @@ -962,6 +1019,7 @@ AdbcStatusCode AdbcStatementCancel(struct AdbcStatement* statement, if (!statement->private_driver) { return ADBC_STATUS_INVALID_STATE; } + INIT_ERROR(error, statement); return statement->private_driver->StatementCancel(statement, error); } @@ -974,6 +1032,7 @@ AdbcStatusCode AdbcStatementExecutePartitions(struct AdbcStatement* statement, if (!statement->private_driver) { return ADBC_STATUS_INVALID_STATE; } + INIT_ERROR(error, statement); return statement->private_driver->StatementExecutePartitions( statement, schema, partitions, rows_affected, error); } @@ -985,6 +1044,7 @@ AdbcStatusCode AdbcStatementExecuteQuery(struct AdbcStatement* statement, if (!statement->private_driver) { return ADBC_STATUS_INVALID_STATE; } + INIT_ERROR(error, statement); return statement->private_driver->StatementExecuteQuery(statement, out, rows_affected, error); } @@ -995,6 +1055,7 @@ AdbcStatusCode AdbcStatementExecuteSchema(struct AdbcStatement* statement, if (!statement->private_driver) { return ADBC_STATUS_INVALID_STATE; } + INIT_ERROR(error, statement); return statement->private_driver->StatementExecuteSchema(statement, schema, error); } @@ -1004,6 +1065,7 @@ AdbcStatusCode AdbcStatementGetOption(struct AdbcStatement* statement, const cha if (!statement->private_driver) { return ADBC_STATUS_INVALID_STATE; } + INIT_ERROR(error, statement); return statement->private_driver->StatementGetOption(statement, key, value, length, error); } @@ -1014,6 +1076,7 @@ AdbcStatusCode AdbcStatementGetOptionBytes(struct AdbcStatement* statement, if (!statement->private_driver) { return ADBC_STATUS_INVALID_STATE; } + INIT_ERROR(error, statement); return statement->private_driver->StatementGetOptionBytes(statement, key, value, length, error); } @@ -1023,6 +1086,7 @@ AdbcStatusCode AdbcStatementGetOptionInt(struct AdbcStatement* statement, const if (!statement->private_driver) { return ADBC_STATUS_INVALID_STATE; } + INIT_ERROR(error, statement); return statement->private_driver->StatementGetOptionInt(statement, key, value, error); } @@ -1032,6 +1096,7 @@ AdbcStatusCode AdbcStatementGetOptionDouble(struct AdbcStatement* statement, if (!statement->private_driver) { return ADBC_STATUS_INVALID_STATE; } + INIT_ERROR(error, statement); return statement->private_driver->StatementGetOptionDouble(statement, key, value, error); } @@ -1042,6 +1107,7 @@ AdbcStatusCode AdbcStatementGetParameterSchema(struct AdbcStatement* statement, if (!statement->private_driver) { return ADBC_STATUS_INVALID_STATE; } + INIT_ERROR(error, statement); return statement->private_driver->StatementGetParameterSchema(statement, schema, error); } @@ -1051,6 +1117,7 @@ AdbcStatusCode AdbcStatementNew(struct AdbcConnection* connection, if (!connection->private_driver) { return ADBC_STATUS_INVALID_STATE; } + INIT_ERROR(error, connection); auto status = connection->private_driver->StatementNew(connection, statement, error); statement->private_driver = connection->private_driver; return status; @@ -1061,6 +1128,7 @@ AdbcStatusCode AdbcStatementPrepare(struct AdbcStatement* statement, if (!statement->private_driver) { return ADBC_STATUS_INVALID_STATE; } + INIT_ERROR(error, statement); return statement->private_driver->StatementPrepare(statement, error); } @@ -1069,6 +1137,7 @@ AdbcStatusCode AdbcStatementRelease(struct AdbcStatement* statement, if (!statement->private_driver) { return ADBC_STATUS_INVALID_STATE; } + INIT_ERROR(error, statement); auto status = statement->private_driver->StatementRelease(statement, error); statement->private_driver = nullptr; return status; @@ -1079,6 +1148,7 @@ AdbcStatusCode AdbcStatementSetOption(struct AdbcStatement* statement, const cha if (!statement->private_driver) { return ADBC_STATUS_INVALID_STATE; } + INIT_ERROR(error, statement); return statement->private_driver->StatementSetOption(statement, key, value, error); } @@ -1088,6 +1158,7 @@ AdbcStatusCode AdbcStatementSetOptionBytes(struct AdbcStatement* statement, if (!statement->private_driver) { return ADBC_STATUS_INVALID_STATE; } + INIT_ERROR(error, statement); return statement->private_driver->StatementSetOptionBytes(statement, key, value, length, error); } @@ -1097,6 +1168,7 @@ AdbcStatusCode AdbcStatementSetOptionInt(struct AdbcStatement* statement, const if (!statement->private_driver) { return ADBC_STATUS_INVALID_STATE; } + INIT_ERROR(error, statement); return statement->private_driver->StatementSetOptionInt(statement, key, value, error); } @@ -1106,6 +1178,7 @@ AdbcStatusCode AdbcStatementSetOptionDouble(struct AdbcStatement* statement, if (!statement->private_driver) { return ADBC_STATUS_INVALID_STATE; } + INIT_ERROR(error, statement); return statement->private_driver->StatementSetOptionDouble(statement, key, value, error); } @@ -1115,6 +1188,7 @@ AdbcStatusCode AdbcStatementSetSqlQuery(struct AdbcStatement* statement, if (!statement->private_driver) { return ADBC_STATUS_INVALID_STATE; } + INIT_ERROR(error, statement); return statement->private_driver->StatementSetSqlQuery(statement, query, error); } @@ -1124,6 +1198,7 @@ AdbcStatusCode AdbcStatementSetSubstraitPlan(struct AdbcStatement* statement, if (!statement->private_driver) { return ADBC_STATUS_INVALID_STATE; } + INIT_ERROR(error, statement); return statement->private_driver->StatementSetSubstraitPlan(statement, plan, length, error); } @@ -1373,6 +1448,9 @@ AdbcStatusCode AdbcLoadDriverFromInitFunc(AdbcDriverInitFunc init_func, int vers } if (version >= ADBC_VERSION_1_1_0) { auto* driver = reinterpret_cast(raw_driver); + FILL_DEFAULT(driver, ErrorGetDetailCount); + FILL_DEFAULT(driver, ErrorGetDetail); + FILL_DEFAULT(driver, DatabaseGetOption); FILL_DEFAULT(driver, DatabaseGetOptionBytes); FILL_DEFAULT(driver, DatabaseGetOptionDouble); diff --git a/c/driver_manager/adbc_version_100.h b/c/driver_manager/adbc_version_100.h index b4a8b949f8..b349f86f73 100644 --- a/c/driver_manager/adbc_version_100.h +++ b/c/driver_manager/adbc_version_100.h @@ -23,6 +23,13 @@ extern "C" { #endif +struct AdbcErrorVersion100 { + char* message; + int32_t vendor_code; + char sqlstate[5]; + void (*release)(struct AdbcError* error); +}; + struct AdbcDriverVersion100 { void* private_data; void* private_manager; diff --git a/c/driver_manager/adbc_version_100_compatibility_test.cc b/c/driver_manager/adbc_version_100_compatibility_test.cc index 634c126c55..27e5f5d997 100644 --- a/c/driver_manager/adbc_version_100_compatibility_test.cc +++ b/c/driver_manager/adbc_version_100_compatibility_test.cc @@ -55,6 +55,9 @@ class AdbcVersion : public ::testing::Test { }; TEST_F(AdbcVersion, StructSize) { + ASSERT_EQ(sizeof(AdbcErrorVersion100), ADBC_ERROR_1_0_0_SIZE); + ASSERT_EQ(sizeof(AdbcError), ADBC_ERROR_1_1_0_SIZE); + ASSERT_EQ(sizeof(AdbcDriverVersion100), ADBC_DRIVER_1_0_0_SIZE); ASSERT_EQ(sizeof(AdbcDriver), ADBC_DRIVER_1_1_0_SIZE); } @@ -74,30 +77,35 @@ TEST_F(AdbcVersion, OldDriverNewManager) { &driver, &error), IsOkStatus(&error)); - ASSERT_NE(driver.DatabaseGetOption, nullptr); - ASSERT_NE(driver.DatabaseGetOptionInt, nullptr); - ASSERT_NE(driver.DatabaseGetOptionDouble, nullptr); - ASSERT_NE(driver.DatabaseSetOptionInt, nullptr); - ASSERT_NE(driver.DatabaseSetOptionDouble, nullptr); - - ASSERT_NE(driver.ConnectionGetOption, nullptr); - ASSERT_NE(driver.ConnectionGetOptionInt, nullptr); - ASSERT_NE(driver.ConnectionGetOptionDouble, nullptr); - ASSERT_NE(driver.ConnectionSetOptionInt, nullptr); - ASSERT_NE(driver.ConnectionSetOptionDouble, nullptr); - - ASSERT_NE(driver.StatementCancel, nullptr); - ASSERT_NE(driver.StatementExecuteSchema, nullptr); - ASSERT_NE(driver.StatementGetOption, nullptr); - ASSERT_NE(driver.StatementGetOptionInt, nullptr); - ASSERT_NE(driver.StatementGetOptionDouble, nullptr); - ASSERT_NE(driver.StatementSetOptionInt, nullptr); - ASSERT_NE(driver.StatementSetOptionDouble, nullptr); + EXPECT_NE(driver.ErrorGetDetailCount, nullptr); + EXPECT_NE(driver.ErrorGetDetail, nullptr); + + EXPECT_NE(driver.DatabaseGetOption, nullptr); + EXPECT_NE(driver.DatabaseGetOptionBytes, nullptr); + EXPECT_NE(driver.DatabaseGetOptionDouble, nullptr); + EXPECT_NE(driver.DatabaseGetOptionInt, nullptr); + EXPECT_NE(driver.DatabaseSetOptionInt, nullptr); + EXPECT_NE(driver.DatabaseSetOptionDouble, nullptr); + + EXPECT_NE(driver.ConnectionCancel, nullptr); + EXPECT_NE(driver.ConnectionGetOption, nullptr); + EXPECT_NE(driver.ConnectionGetOptionBytes, nullptr); + EXPECT_NE(driver.ConnectionGetOptionDouble, nullptr); + EXPECT_NE(driver.ConnectionGetOptionInt, nullptr); + EXPECT_NE(driver.ConnectionSetOptionInt, nullptr); + EXPECT_NE(driver.ConnectionSetOptionDouble, nullptr); + + EXPECT_NE(driver.StatementCancel, nullptr); + EXPECT_NE(driver.StatementExecuteSchema, nullptr); + EXPECT_NE(driver.StatementGetOption, nullptr); + EXPECT_NE(driver.StatementGetOptionBytes, nullptr); + EXPECT_NE(driver.StatementGetOptionDouble, nullptr); + EXPECT_NE(driver.StatementGetOptionInt, nullptr); + EXPECT_NE(driver.StatementSetOptionInt, nullptr); + EXPECT_NE(driver.StatementSetOptionDouble, nullptr); } -// Initialize a version 1.1.0 driver with the version 1.0.0 driver struct. -TEST_F(AdbcVersion, NewDriverOldLayout) { - // TODO: no new drivers yet. -} +// N.B. see postgresql_test.cc for backwards compatibility test of AdbcError +// N.B. see postgresql_test.cc for backwards compatibility test of AdbcDriver } // namespace adbc diff --git a/c/symbols.map b/c/symbols.map index 5e965b355e..110b4d1e43 100644 --- a/c/symbols.map +++ b/c/symbols.map @@ -20,6 +20,15 @@ # Only expose symbols from the ADBC API Adbc*; + # Expose driver-specific initialization routines + PostgresqlDriverInit; + SqliteDriverInit; + + # Export Postgres API for testing + extern "C++" { + *adbcpq::*; + }; + local: *; }; diff --git a/docs/source/python/recipe/postgresql_create_append_table.py b/docs/source/python/recipe/postgresql_create_append_table.py index 9b0c66f989..a2f6258ce5 100644 --- a/docs/source/python/recipe/postgresql_create_append_table.py +++ b/docs/source/python/recipe/postgresql_create_append_table.py @@ -63,7 +63,7 @@ with conn.cursor() as cur: try: cur.adbc_ingest("example", data, mode="create") - except conn.OperationalError: + except conn.ProgrammingError: pass else: raise RuntimeError("Should have failed!") diff --git a/go/adbc/drivermgr/adbc.h b/go/adbc/drivermgr/adbc.h index 9d9fa92347..32eaf8b284 100644 --- a/go/adbc/drivermgr/adbc.h +++ b/go/adbc/drivermgr/adbc.h @@ -248,7 +248,25 @@ typedef uint8_t AdbcStatusCode; /// May indicate a database-side error only. #define ADBC_STATUS_UNAUTHORIZED 14 +/// \brief Inform the driver/driver manager that we are using the extended +/// AdbcError struct from ADBC 1.1.0. +/// +/// See the AdbcError documentation for usage. +/// +/// \since ADBC API revision 1.1.0 +/// \addtogroup adbc-1.1.0 +#define ADBC_ERROR_VENDOR_CODE_PRIVATE_DATA INT32_MIN + /// \brief A detailed error message for an operation. +/// +/// The caller must zero-initialize this struct (clarified in ADBC 1.1.0). +/// +/// The structure was extended in ADBC 1.1.0. Drivers and clients using ADBC +/// 1.0.0 will not have the private_data or private_driver fields. Drivers +/// should read/write these fields if and only if vendor_code is equal to +/// ADBC_ERROR_VENDOR_CODE_PRIVATE_DATA. Clients are required to initialize +/// this struct to avoid the possibility of uninitialized values confusing the +/// driver. struct ADBC_EXPORT AdbcError { /// \brief The error message. char* message; @@ -266,8 +284,103 @@ struct ADBC_EXPORT AdbcError { /// Unlike other structures, this is an embedded callback to make it /// easier for the driver manager and driver to cooperate. void (*release)(struct AdbcError* error); + + /// \brief Opaque implementation-defined state. + /// + /// This field may not be used unless vendor_code is + /// ADBC_ERROR_VENDOR_CODE_PRIVATE_DATA. If present, this field is NULLPTR + /// iff the error is unintialized/freed. + /// + /// \since ADBC API revision 1.1.0 + /// \addtogroup adbc-1.1.0 + void* private_data; + + /// \brief The associated driver (used by the driver manager to help + /// track state). + /// + /// This field may not be used unless vendor_code is + /// ADBC_ERROR_VENDOR_CODE_PRIVATE_DATA. + /// + /// \since ADBC API revision 1.1.0 + /// \addtogroup adbc-1.1.0 + struct AdbcDriver* private_driver; +}; + +#ifdef __cplusplus +/// \brief A helper to initialize the full AdbcError structure. +/// +/// \since ADBC API revision 1.1.0 +/// \addtogroup adbc-1.1.0 +#define ADBC_ERROR_INIT \ + (AdbcError{nullptr, \ + ADBC_ERROR_VENDOR_CODE_PRIVATE_DATA, \ + {0, 0, 0, 0, 0}, \ + nullptr, \ + nullptr, \ + nullptr}) +#else +/// \brief A helper to initialize the full AdbcError structure. +/// +/// \since ADBC API revision 1.1.0 +/// \addtogroup adbc-1.1.0 +#define ADBC_ERROR_INIT \ + ((struct AdbcError){ \ + NULL, ADBC_ERROR_VENDOR_CODE_PRIVATE_DATA, {0, 0, 0, 0, 0}, NULL, NULL, NULL}) +#endif + +/// \brief The size of the AdbcError structure in ADBC 1.0.0. +/// +/// Drivers written for ADBC 1.1.0 and later should never touch more than this +/// portion of an AdbcDriver struct when vendor_code is not +/// ADBC_ERROR_VENDOR_CODE_PRIVATE_DATA. +/// +/// \since ADBC API revision 1.1.0 +/// \addtogroup adbc-1.1.0 +#define ADBC_ERROR_1_0_0_SIZE (offsetof(struct AdbcError, private_data)) +/// \brief The size of the AdbcError structure in ADBC 1.1.0. +/// +/// Drivers written for ADBC 1.1.0 and later should never touch more than this +/// portion of an AdbcDriver struct when vendor_code is +/// ADBC_ERROR_VENDOR_CODE_PRIVATE_DATA. +/// +/// \since ADBC API revision 1.1.0 +/// \addtogroup adbc-1.1.0 +#define ADBC_ERROR_1_1_0_SIZE (sizeof(struct AdbcError)) + +/// \brief Extra key-value metadata for an error. +/// +/// The fields here are owned by the driver and should not be freed. The +/// fields here are invalidated when the release callback in AdbcError is +/// called. +/// +/// \since ADBC API revision 1.1.0 +/// \addtogroup adbc-1.1.0 +struct ADBC_EXPORT AdbcErrorDetail { + /// \brief The metadata key. + const char* key; + /// \brief The binary metadata value. + const uint8_t* value; + /// \brief The length of the metadata value. + size_t value_length; }; +/// \brief Get the number of metadata values available in an error. +/// +/// \since ADBC API revision 1.1.0 +/// \addtogroup adbc-1.1.0 +ADBC_EXPORT +int AdbcErrorGetDetailCount(struct AdbcError* error); + +/// \brief Get a metadata value in an error by index. +/// +/// If index is invalid, returns an AdbcErrorDetail initialized with NULL/0 +/// fields. +/// +/// \since ADBC API revision 1.1.0 +/// \addtogroup adbc-1.1.0 +ADBC_EXPORT +struct AdbcErrorDetail AdbcErrorGetDetail(struct AdbcError* error, int index); + /// @} /// \defgroup adbc-constants Constants @@ -284,6 +397,7 @@ struct ADBC_EXPORT AdbcError { /// When passed to an AdbcDriverInitFunc(), the driver parameter must /// point to an AdbcDriver. /// +/// \since ADBC API revision 1.1.0 /// \addtogroup adbc-1.1.0 #define ADBC_VERSION_1_1_0 1001000 @@ -894,6 +1008,9 @@ struct ADBC_EXPORT AdbcDriver { /// /// @{ + int (*ErrorGetDetailCount)(struct AdbcError* error); + struct AdbcErrorDetail (*ErrorGetDetail)(struct AdbcError* error, int index); + AdbcStatusCode (*DatabaseGetOption)(struct AdbcDatabase*, const char*, char*, size_t*, struct AdbcError*); AdbcStatusCode (*DatabaseGetOptionBytes)(struct AdbcDatabase*, const char*, uint8_t*, @@ -959,7 +1076,7 @@ struct ADBC_EXPORT AdbcDriver { /// /// \since ADBC API revision 1.1.0 /// \addtogroup adbc-1.1.0 -#define ADBC_DRIVER_1_0_0_SIZE (offsetof(struct AdbcDriver, DatabaseGetOption)) +#define ADBC_DRIVER_1_0_0_SIZE (offsetof(struct AdbcDriver, ErrorGetDetailCount)) /// \brief The size of the AdbcDriver structure in ADBC 1.1.0. /// Drivers written for ADBC 1.1.0 and later should never touch more diff --git a/go/adbc/drivermgr/adbc_driver_manager.cc b/go/adbc/drivermgr/adbc_driver_manager.cc index ea4ddc4c55..370b3eead1 100644 --- a/go/adbc/drivermgr/adbc_driver_manager.cc +++ b/go/adbc/drivermgr/adbc_driver_manager.cc @@ -130,6 +130,12 @@ static AdbcStatusCode ReleaseDriver(struct AdbcDriver* driver, struct AdbcError* // Default stubs +int ErrorGetDetailCount(struct AdbcError* error) { return 0; } + +struct AdbcErrorDetail ErrorGetDetail(struct AdbcError* error, int index) { + return {nullptr, nullptr, 0}; +} + AdbcStatusCode DatabaseGetOption(struct AdbcDatabase* database, const char* key, char* value, size_t* length, struct AdbcError* error) { return ADBC_STATUS_NOT_FOUND; @@ -366,6 +372,27 @@ struct TempConnection { // Direct implementations of API methods +int AdbcErrorGetDetailCount(struct AdbcError* error) { + if (error->vendor_code == ADBC_ERROR_VENDOR_CODE_PRIVATE_DATA && error->private_data && + error->private_driver) { + return error->private_driver->ErrorGetDetailCount(error); + } + return 0; +} + +struct AdbcErrorDetail AdbcErrorGetDetail(struct AdbcError* error, int index) { + if (error->vendor_code == ADBC_ERROR_VENDOR_CODE_PRIVATE_DATA && error->private_data && + error->private_driver) { + return error->private_driver->ErrorGetDetail(error, index); + } + return {nullptr, nullptr, 0}; +} + +#define INIT_ERROR(ERROR, SOURCE) \ + if ((ERROR)->vendor_code == ADBC_ERROR_VENDOR_CODE_PRIVATE_DATA) { \ + (ERROR)->private_driver = (SOURCE)->private_driver; \ + } + AdbcStatusCode AdbcDatabaseNew(struct AdbcDatabase* database, struct AdbcError* error) { // Allocate a temporary structure to store options pre-Init database->private_data = new TempDatabase(); @@ -377,6 +404,7 @@ AdbcStatusCode AdbcDatabaseGetOption(struct AdbcDatabase* database, const char* char* value, size_t* length, struct AdbcError* error) { if (database->private_driver) { + INIT_ERROR(error, database); return database->private_driver->DatabaseGetOption(database, key, value, length, error); } @@ -406,6 +434,7 @@ AdbcStatusCode AdbcDatabaseGetOptionBytes(struct AdbcDatabase* database, const c uint8_t* value, size_t* length, struct AdbcError* error) { if (database->private_driver) { + INIT_ERROR(error, database); return database->private_driver->DatabaseGetOptionBytes(database, key, value, length, error); } @@ -427,6 +456,7 @@ AdbcStatusCode AdbcDatabaseGetOptionBytes(struct AdbcDatabase* database, const c AdbcStatusCode AdbcDatabaseGetOptionInt(struct AdbcDatabase* database, const char* key, int64_t* value, struct AdbcError* error) { if (database->private_driver) { + INIT_ERROR(error, database); return database->private_driver->DatabaseGetOptionInt(database, key, value, error); } const auto* args = reinterpret_cast(database->private_data); @@ -441,6 +471,7 @@ AdbcStatusCode AdbcDatabaseGetOptionInt(struct AdbcDatabase* database, const cha AdbcStatusCode AdbcDatabaseGetOptionDouble(struct AdbcDatabase* database, const char* key, double* value, struct AdbcError* error) { if (database->private_driver) { + INIT_ERROR(error, database); return database->private_driver->DatabaseGetOptionDouble(database, key, value, error); } const auto* args = reinterpret_cast(database->private_data); @@ -455,6 +486,7 @@ AdbcStatusCode AdbcDatabaseGetOptionDouble(struct AdbcDatabase* database, const AdbcStatusCode AdbcDatabaseSetOption(struct AdbcDatabase* database, const char* key, const char* value, struct AdbcError* error) { if (database->private_driver) { + INIT_ERROR(error, database); return database->private_driver->DatabaseSetOption(database, key, value, error); } @@ -473,6 +505,7 @@ AdbcStatusCode AdbcDatabaseSetOptionBytes(struct AdbcDatabase* database, const c const uint8_t* value, size_t length, struct AdbcError* error) { if (database->private_driver) { + INIT_ERROR(error, database); return database->private_driver->DatabaseSetOptionBytes(database, key, value, length, error); } @@ -485,6 +518,7 @@ AdbcStatusCode AdbcDatabaseSetOptionBytes(struct AdbcDatabase* database, const c AdbcStatusCode AdbcDatabaseSetOptionInt(struct AdbcDatabase* database, const char* key, int64_t value, struct AdbcError* error) { if (database->private_driver) { + INIT_ERROR(error, database); return database->private_driver->DatabaseSetOptionInt(database, key, value, error); } @@ -496,6 +530,7 @@ AdbcStatusCode AdbcDatabaseSetOptionInt(struct AdbcDatabase* database, const cha AdbcStatusCode AdbcDatabaseSetOptionDouble(struct AdbcDatabase* database, const char* key, double value, struct AdbcError* error) { if (database->private_driver) { + INIT_ERROR(error, database); return database->private_driver->DatabaseSetOptionDouble(database, key, value, error); } @@ -566,6 +601,7 @@ AdbcStatusCode AdbcDatabaseInit(struct AdbcDatabase* database, struct AdbcError* auto double_options = std::move(args->double_options); delete args; + INIT_ERROR(error, database); for (const auto& option : options) { status = database->private_driver->DatabaseSetOption(database, option.first.c_str(), option.second.c_str(), error); @@ -616,6 +652,7 @@ AdbcStatusCode AdbcDatabaseRelease(struct AdbcDatabase* database, } return ADBC_STATUS_INVALID_STATE; } + INIT_ERROR(error, database); auto status = database->private_driver->DatabaseRelease(database, error); if (database->private_driver->release) { database->private_driver->release(database->private_driver, error); @@ -631,6 +668,7 @@ AdbcStatusCode AdbcConnectionCancel(struct AdbcConnection* connection, if (!connection->private_driver) { return ADBC_STATUS_INVALID_STATE; } + INIT_ERROR(error, connection); return connection->private_driver->ConnectionCancel(connection, error); } @@ -639,6 +677,7 @@ AdbcStatusCode AdbcConnectionCommit(struct AdbcConnection* connection, if (!connection->private_driver) { return ADBC_STATUS_INVALID_STATE; } + INIT_ERROR(error, connection); return connection->private_driver->ConnectionCommit(connection, error); } @@ -649,6 +688,7 @@ AdbcStatusCode AdbcConnectionGetInfo(struct AdbcConnection* connection, if (!connection->private_driver) { return ADBC_STATUS_INVALID_STATE; } + INIT_ERROR(error, connection); return connection->private_driver->ConnectionGetInfo(connection, info_codes, info_codes_length, out, error); } @@ -662,6 +702,7 @@ AdbcStatusCode AdbcConnectionGetObjects(struct AdbcConnection* connection, int d if (!connection->private_driver) { return ADBC_STATUS_INVALID_STATE; } + INIT_ERROR(error, connection); return connection->private_driver->ConnectionGetObjects( connection, depth, catalog, db_schema, table_name, table_types, column_name, stream, error); @@ -687,6 +728,7 @@ AdbcStatusCode AdbcConnectionGetOption(struct AdbcConnection* connection, const *length = it->second.size() + 1; return ADBC_STATUS_OK; } + INIT_ERROR(error, connection); return connection->private_driver->ConnectionGetOption(connection, key, value, length, error); } @@ -711,6 +753,7 @@ AdbcStatusCode AdbcConnectionGetOptionBytes(struct AdbcConnection* connection, *length = it->second.size() + 1; return ADBC_STATUS_OK; } + INIT_ERROR(error, connection); return connection->private_driver->ConnectionGetOptionBytes(connection, key, value, length, error); } @@ -732,6 +775,7 @@ AdbcStatusCode AdbcConnectionGetOptionInt(struct AdbcConnection* connection, *value = it->second; return ADBC_STATUS_OK; } + INIT_ERROR(error, connection); return connection->private_driver->ConnectionGetOptionInt(connection, key, value, error); } @@ -753,6 +797,7 @@ AdbcStatusCode AdbcConnectionGetOptionDouble(struct AdbcConnection* connection, *value = it->second; return ADBC_STATUS_OK; } + INIT_ERROR(error, connection); return connection->private_driver->ConnectionGetOptionDouble(connection, key, value, error); } @@ -765,6 +810,7 @@ AdbcStatusCode AdbcConnectionGetTableSchema(struct AdbcConnection* connection, if (!connection->private_driver) { return ADBC_STATUS_INVALID_STATE; } + INIT_ERROR(error, connection); return connection->private_driver->ConnectionGetTableSchema( connection, catalog, db_schema, table_name, schema, error); } @@ -775,6 +821,7 @@ AdbcStatusCode AdbcConnectionGetTableTypes(struct AdbcConnection* connection, if (!connection->private_driver) { return ADBC_STATUS_INVALID_STATE; } + INIT_ERROR(error, connection); return connection->private_driver->ConnectionGetTableTypes(connection, stream, error); } @@ -824,6 +871,7 @@ AdbcStatusCode AdbcConnectionInit(struct AdbcConnection* connection, connection, option.first.c_str(), option.second, error); if (status != ADBC_STATUS_OK) return status; } + INIT_ERROR(error, connection); return connection->private_driver->ConnectionInit(connection, database, error); } @@ -845,6 +893,7 @@ AdbcStatusCode AdbcConnectionReadPartition(struct AdbcConnection* connection, if (!connection->private_driver) { return ADBC_STATUS_INVALID_STATE; } + INIT_ERROR(error, connection); return connection->private_driver->ConnectionReadPartition( connection, serialized_partition, serialized_length, out, error); } @@ -860,6 +909,7 @@ AdbcStatusCode AdbcConnectionRelease(struct AdbcConnection* connection, } return ADBC_STATUS_INVALID_STATE; } + INIT_ERROR(error, connection); auto status = connection->private_driver->ConnectionRelease(connection, error); connection->private_driver = nullptr; return status; @@ -870,6 +920,7 @@ AdbcStatusCode AdbcConnectionRollback(struct AdbcConnection* connection, if (!connection->private_driver) { return ADBC_STATUS_INVALID_STATE; } + INIT_ERROR(error, connection); return connection->private_driver->ConnectionRollback(connection, error); } @@ -885,6 +936,7 @@ AdbcStatusCode AdbcConnectionSetOption(struct AdbcConnection* connection, const args->options[key] = value; return ADBC_STATUS_OK; } + INIT_ERROR(error, connection); return connection->private_driver->ConnectionSetOption(connection, key, value, error); } @@ -901,6 +953,7 @@ AdbcStatusCode AdbcConnectionSetOptionBytes(struct AdbcConnection* connection, args->bytes_options[key] = std::string(reinterpret_cast(value), length); return ADBC_STATUS_OK; } + INIT_ERROR(error, connection); return connection->private_driver->ConnectionSetOptionBytes(connection, key, value, length, error); } @@ -918,6 +971,7 @@ AdbcStatusCode AdbcConnectionSetOptionInt(struct AdbcConnection* connection, args->int_options[key] = value; return ADBC_STATUS_OK; } + INIT_ERROR(error, connection); return connection->private_driver->ConnectionSetOptionInt(connection, key, value, error); } @@ -935,6 +989,7 @@ AdbcStatusCode AdbcConnectionSetOptionDouble(struct AdbcConnection* connection, args->double_options[key] = value; return ADBC_STATUS_OK; } + INIT_ERROR(error, connection); return connection->private_driver->ConnectionSetOptionDouble(connection, key, value, error); } @@ -945,6 +1000,7 @@ AdbcStatusCode AdbcStatementBind(struct AdbcStatement* statement, if (!statement->private_driver) { return ADBC_STATUS_INVALID_STATE; } + INIT_ERROR(error, statement); return statement->private_driver->StatementBind(statement, values, schema, error); } @@ -954,6 +1010,7 @@ AdbcStatusCode AdbcStatementBindStream(struct AdbcStatement* statement, if (!statement->private_driver) { return ADBC_STATUS_INVALID_STATE; } + INIT_ERROR(error, statement); return statement->private_driver->StatementBindStream(statement, stream, error); } @@ -962,6 +1019,7 @@ AdbcStatusCode AdbcStatementCancel(struct AdbcStatement* statement, if (!statement->private_driver) { return ADBC_STATUS_INVALID_STATE; } + INIT_ERROR(error, statement); return statement->private_driver->StatementCancel(statement, error); } @@ -974,6 +1032,7 @@ AdbcStatusCode AdbcStatementExecutePartitions(struct AdbcStatement* statement, if (!statement->private_driver) { return ADBC_STATUS_INVALID_STATE; } + INIT_ERROR(error, statement); return statement->private_driver->StatementExecutePartitions( statement, schema, partitions, rows_affected, error); } @@ -985,6 +1044,7 @@ AdbcStatusCode AdbcStatementExecuteQuery(struct AdbcStatement* statement, if (!statement->private_driver) { return ADBC_STATUS_INVALID_STATE; } + INIT_ERROR(error, statement); return statement->private_driver->StatementExecuteQuery(statement, out, rows_affected, error); } @@ -995,6 +1055,7 @@ AdbcStatusCode AdbcStatementExecuteSchema(struct AdbcStatement* statement, if (!statement->private_driver) { return ADBC_STATUS_INVALID_STATE; } + INIT_ERROR(error, statement); return statement->private_driver->StatementExecuteSchema(statement, schema, error); } @@ -1004,6 +1065,7 @@ AdbcStatusCode AdbcStatementGetOption(struct AdbcStatement* statement, const cha if (!statement->private_driver) { return ADBC_STATUS_INVALID_STATE; } + INIT_ERROR(error, statement); return statement->private_driver->StatementGetOption(statement, key, value, length, error); } @@ -1014,6 +1076,7 @@ AdbcStatusCode AdbcStatementGetOptionBytes(struct AdbcStatement* statement, if (!statement->private_driver) { return ADBC_STATUS_INVALID_STATE; } + INIT_ERROR(error, statement); return statement->private_driver->StatementGetOptionBytes(statement, key, value, length, error); } @@ -1023,6 +1086,7 @@ AdbcStatusCode AdbcStatementGetOptionInt(struct AdbcStatement* statement, const if (!statement->private_driver) { return ADBC_STATUS_INVALID_STATE; } + INIT_ERROR(error, statement); return statement->private_driver->StatementGetOptionInt(statement, key, value, error); } @@ -1032,6 +1096,7 @@ AdbcStatusCode AdbcStatementGetOptionDouble(struct AdbcStatement* statement, if (!statement->private_driver) { return ADBC_STATUS_INVALID_STATE; } + INIT_ERROR(error, statement); return statement->private_driver->StatementGetOptionDouble(statement, key, value, error); } @@ -1042,6 +1107,7 @@ AdbcStatusCode AdbcStatementGetParameterSchema(struct AdbcStatement* statement, if (!statement->private_driver) { return ADBC_STATUS_INVALID_STATE; } + INIT_ERROR(error, statement); return statement->private_driver->StatementGetParameterSchema(statement, schema, error); } @@ -1051,6 +1117,7 @@ AdbcStatusCode AdbcStatementNew(struct AdbcConnection* connection, if (!connection->private_driver) { return ADBC_STATUS_INVALID_STATE; } + INIT_ERROR(error, connection); auto status = connection->private_driver->StatementNew(connection, statement, error); statement->private_driver = connection->private_driver; return status; @@ -1061,6 +1128,7 @@ AdbcStatusCode AdbcStatementPrepare(struct AdbcStatement* statement, if (!statement->private_driver) { return ADBC_STATUS_INVALID_STATE; } + INIT_ERROR(error, statement); return statement->private_driver->StatementPrepare(statement, error); } @@ -1069,6 +1137,7 @@ AdbcStatusCode AdbcStatementRelease(struct AdbcStatement* statement, if (!statement->private_driver) { return ADBC_STATUS_INVALID_STATE; } + INIT_ERROR(error, statement); auto status = statement->private_driver->StatementRelease(statement, error); statement->private_driver = nullptr; return status; @@ -1079,6 +1148,7 @@ AdbcStatusCode AdbcStatementSetOption(struct AdbcStatement* statement, const cha if (!statement->private_driver) { return ADBC_STATUS_INVALID_STATE; } + INIT_ERROR(error, statement); return statement->private_driver->StatementSetOption(statement, key, value, error); } @@ -1088,6 +1158,7 @@ AdbcStatusCode AdbcStatementSetOptionBytes(struct AdbcStatement* statement, if (!statement->private_driver) { return ADBC_STATUS_INVALID_STATE; } + INIT_ERROR(error, statement); return statement->private_driver->StatementSetOptionBytes(statement, key, value, length, error); } @@ -1097,6 +1168,7 @@ AdbcStatusCode AdbcStatementSetOptionInt(struct AdbcStatement* statement, const if (!statement->private_driver) { return ADBC_STATUS_INVALID_STATE; } + INIT_ERROR(error, statement); return statement->private_driver->StatementSetOptionInt(statement, key, value, error); } @@ -1106,6 +1178,7 @@ AdbcStatusCode AdbcStatementSetOptionDouble(struct AdbcStatement* statement, if (!statement->private_driver) { return ADBC_STATUS_INVALID_STATE; } + INIT_ERROR(error, statement); return statement->private_driver->StatementSetOptionDouble(statement, key, value, error); } @@ -1115,6 +1188,7 @@ AdbcStatusCode AdbcStatementSetSqlQuery(struct AdbcStatement* statement, if (!statement->private_driver) { return ADBC_STATUS_INVALID_STATE; } + INIT_ERROR(error, statement); return statement->private_driver->StatementSetSqlQuery(statement, query, error); } @@ -1124,6 +1198,7 @@ AdbcStatusCode AdbcStatementSetSubstraitPlan(struct AdbcStatement* statement, if (!statement->private_driver) { return ADBC_STATUS_INVALID_STATE; } + INIT_ERROR(error, statement); return statement->private_driver->StatementSetSubstraitPlan(statement, plan, length, error); } @@ -1373,6 +1448,9 @@ AdbcStatusCode AdbcLoadDriverFromInitFunc(AdbcDriverInitFunc init_func, int vers } if (version >= ADBC_VERSION_1_1_0) { auto* driver = reinterpret_cast(raw_driver); + FILL_DEFAULT(driver, ErrorGetDetailCount); + FILL_DEFAULT(driver, ErrorGetDetail); + FILL_DEFAULT(driver, DatabaseGetOption); FILL_DEFAULT(driver, DatabaseGetOptionBytes); FILL_DEFAULT(driver, DatabaseGetOptionDouble); diff --git a/python/adbc_driver_manager/adbc_driver_manager/_lib.pyi b/python/adbc_driver_manager/adbc_driver_manager/_lib.pyi index 7723df1772..28e6be16af 100644 --- a/python/adbc_driver_manager/adbc_driver_manager/_lib.pyi +++ b/python/adbc_driver_manager/adbc_driver_manager/_lib.pyi @@ -38,10 +38,16 @@ class AdbcConnection(_AdbcHandle): def get_info( self, info_codes: Optional[List[Union[int, "AdbcInfoCode"]]] = None ) -> "ArrowArrayStreamHandle": ... - def get_option(self, key: str) -> str: ... - def get_option_bytes(self, key: str) -> bytes: ... - def get_option_float(self, key: str) -> float: ... - def get_option_int(self, key: str) -> int: ... + def get_option( + self, + key: Union[bytes, str], + *, + encoding: str = "utf-8", + errors: str = "strict", + ) -> str: ... + def get_option_bytes(self, key: Union[bytes, str]) -> bytes: ... + def get_option_float(self, key: Union[bytes, str]) -> float: ... + def get_option_int(self, key: Union[bytes, str]) -> int: ... def get_objects( self, depth: "GetObjectsDepth", @@ -66,10 +72,16 @@ class AdbcConnection(_AdbcHandle): class AdbcDatabase(_AdbcHandle): def __init__(self, **kwargs: str) -> None: ... def close(self) -> None: ... - def get_option(self, key: str) -> str: ... - def get_option_bytes(self, key: str) -> bytes: ... - def get_option_float(self, key: str) -> float: ... - def get_option_int(self, key: str) -> int: ... + def get_option( + self, + key: Union[bytes, str], + *, + encoding: str = "utf-8", + errors: str = "strict", + ) -> str: ... + def get_option_bytes(self, key: Union[bytes, str]) -> bytes: ... + def get_option_float(self, key: Union[bytes, str]) -> float: ... + def get_option_int(self, key: Union[bytes, str]) -> int: ... def set_options(self, **kwargs: Union[bytes, float, int, str]) -> None: ... class AdbcInfoCode(enum.IntEnum): @@ -90,10 +102,16 @@ class AdbcStatement(_AdbcHandle): def execute_query(self, *args, **kwargs) -> Any: ... def execute_schema(self) -> "ArrowSchemaHandle": ... def execute_update(self, *args, **kwargs) -> Any: ... - def get_option(self, key: str) -> str: ... - def get_option_bytes(self, key: str) -> bytes: ... - def get_option_float(self, key: str) -> float: ... - def get_option_int(self, key: str) -> int: ... + def get_option( + self, + key: Union[bytes, str], + *, + encoding: str = "utf-8", + errors: str = "strict", + ) -> str: ... + def get_option_bytes(self, key: Union[bytes, str]) -> bytes: ... + def get_option_float(self, key: Union[bytes, str]) -> float: ... + def get_option_int(self, key: Union[bytes, str]) -> int: ... def get_parameter_schema(self, *args, **kwargs) -> Any: ... def prepare(self, *args, **kwargs) -> Any: ... def set_options(self, **kwargs: Union[bytes, float, int, str]) -> None: ... @@ -135,6 +153,7 @@ class Error(Exception): status_code: AdbcStatusCode vendor_code: Optional[int] sqlstate: Optional[str] + details: List[Tuple[str, bytes]] def __init__( self, @@ -142,7 +161,8 @@ class Error(Exception): *, status_code: Union[int, AdbcStatusCode], vendor_code: Optional[str] = None, - sqlstate: Optional[str] = None + sqlstate: Optional[str] = None, + details: Optional[List[Tuple[str, bytes]]] = None, ) -> None: ... class GetObjectsDepth(enum.IntEnum): @@ -162,7 +182,8 @@ class NotSupportedError(DatabaseError): message: str, *, vendor_code: Optional[str] = None, - sqlstate: Optional[str] = None + sqlstate: Optional[str] = None, + details: Optional[List[Tuple[str, bytes]]] = None, ) -> None: ... class OperationalError(DatabaseError): ... diff --git a/python/adbc_driver_manager/adbc_driver_manager/_lib.pyx b/python/adbc_driver_manager/adbc_driver_manager/_lib.pyx index a5ccc23be6..2acf0083e9 100644 --- a/python/adbc_driver_manager/adbc_driver_manager/_lib.pyx +++ b/python/adbc_driver_manager/adbc_driver_manager/_lib.pyx @@ -94,6 +94,16 @@ cdef extern from "adbc.h" nogil: char[5] sqlstate CAdbcErrorRelease release + cdef struct CAdbcErrorDetail"AdbcErrorDetail": + char* key + uint8_t* value + size_t value_length + + int AdbcErrorGetDetailCount(CAdbcError* error) + CAdbcErrorDetail AdbcErrorGetDetail(CAdbcError* error, int index) + + cdef int ADBC_ERROR_VENDOR_CODE_PRIVATE_DATA + cdef struct CAdbcDriver"AdbcDriver": pass @@ -326,13 +336,16 @@ class Error(Exception): A vendor-specific status code if present. sqlstate : str, optional The SQLSTATE code if present. + details : list[tuple[str, bytes]], optional + Additional error details, if present. """ - def __init__(self, message, *, status_code, vendor_code=None, sqlstate=None): + def __init__(self, message, *, status_code, vendor_code=None, sqlstate=None, details=None): super().__init__(message) self.status_code = AdbcStatusCode(status_code) self.vendor_code = vendor_code self.sqlstate = sqlstate + self.details = details or [] class InterfaceError(Error): @@ -366,12 +379,13 @@ class ProgrammingError(DatabaseError): class NotSupportedError(DatabaseError): """An operation or some functionality is not supported.""" - def __init__(self, message, *, vendor_code=None, sqlstate=None): + def __init__(self, message, *, vendor_code=None, sqlstate=None, details=None): super().__init__( message, status_code=AdbcStatusCode.NOT_IMPLEMENTED, vendor_code=vendor_code, sqlstate=sqlstate, + details=details, ) @@ -398,6 +412,8 @@ INGEST_OPTION_TARGET_TABLE = ADBC_INGEST_OPTION_TARGET_TABLE.decode("utf-8") cdef void check_error(CAdbcStatusCode status, CAdbcError* error) except *: + cdef CAdbcErrorDetail c_detail + if status == ADBC_STATUS_OK: return @@ -408,14 +424,26 @@ cdef void check_error(CAdbcStatusCode status, CAdbcError* error) except *: if error != NULL: if error.message != NULL: message += ": " - message += error.message.decode("utf-8") - if error.vendor_code: + message += error.message.decode("utf-8", "replace") + if error.vendor_code and error.vendor_code != ADBC_ERROR_VENDOR_CODE_PRIVATE_DATA: vendor_code = error.vendor_code message += f". Vendor code: {vendor_code}" if error.sqlstate[0] != 0: sqlstate = bytes(error.sqlstate[i] for i in range(5)) - sqlstate = sqlstate.decode("ascii") + sqlstate = sqlstate.decode("ascii", "replace") message += f". SQLSTATE: {sqlstate}" + + num_details = AdbcErrorGetDetailCount(error) + details = [] + for index in range(num_details): + c_detail = AdbcErrorGetDetail(error, index) + if c_detail.key == NULL or c_detail.value == NULL: + # Shouldn't happen... + break + details.append( + (c_detail.key, + PyBytes_FromStringAndSize( c_detail.value, c_detail.value_length))) + if error.release: error.release(error) @@ -441,13 +469,15 @@ cdef void check_error(CAdbcStatusCode status, CAdbcError* error) except *: ADBC_STATUS_UNAUTHORIZED): klass = ProgrammingError elif status == ADBC_STATUS_NOT_IMPLEMENTED: - raise NotSupportedError(message, vendor_code=vendor_code, sqlstate=sqlstate) - raise klass(message, status_code=status, vendor_code=vendor_code, sqlstate=sqlstate) + raise NotSupportedError(message, vendor_code=vendor_code, sqlstate=sqlstate, details=details) + raise klass(message, status_code=status, vendor_code=vendor_code, sqlstate=sqlstate, details=details) cdef CAdbcError empty_error(): cdef CAdbcError error memset(&error, 0, cython.sizeof(error)) + # We always want the extended error info + error.vendor_code = ADBC_ERROR_VENDOR_CODE_PRIVATE_DATA return error @@ -607,8 +637,8 @@ cdef class AdbcDatabase(_AdbcHandle): elif value is None: raise ValueError(f"value for key '{key}' cannot be None") else: - key = key.encode("utf-8") - value = value.encode("utf-8") + key = _to_bytes(key, "key") + value = _to_bytes(value, "value") c_key = key c_value = value status = AdbcDatabaseSetOption( @@ -632,10 +662,29 @@ cdef class AdbcDatabase(_AdbcHandle): status = AdbcDatabaseRelease(&self.database, &c_error) check_error(status, &c_error) - def get_option(self, key: str) -> str: - """Get the value of a string option.""" + def get_option( + self, + key: str | bytes, + *, + encoding="utf-8", + errors="strict", + ) -> str: + """ + Get the value of a string option. + + Parameters + ---------- + key : str or bytes + The option to get. + encoding : str + The encoding of the option value. This should almost + always be UTF-8. + errors : str + What to do about errors when decoding the option value + (see bytes.decode). + """ cdef CAdbcError c_error = empty_error() - key_bytes = key.encode("utf-8") + key_bytes = _to_bytes(key, "key") cdef char* c_key = key_bytes cdef uint8_t* c_value = NULL cdef size_t c_len = 0 @@ -663,12 +712,12 @@ cdef class AdbcDatabase(_AdbcHandle): # Remove trailing null terminator if c_len > 0: c_len -= 1 - return buf[:c_len].decode("utf-8") + return buf[:c_len].decode(encoding, errors) def get_option_bytes(self, key: str) -> bytes: """Get the value of a binary option.""" cdef CAdbcError c_error = empty_error() - key_bytes = key.encode("utf-8") + key_bytes = _to_bytes(key, "key") cdef char* c_key = key_bytes cdef uint8_t* c_value = NULL cdef size_t c_len = 0 @@ -698,7 +747,7 @@ cdef class AdbcDatabase(_AdbcHandle): def get_option_float(self, key: str) -> float: """Get the value of a floating-point option.""" cdef CAdbcError c_error = empty_error() - key_bytes = key.encode("utf-8") + key_bytes = _to_bytes(key, "key") cdef char* c_key = key_bytes cdef double c_value = 0.0 check_error( @@ -710,7 +759,7 @@ cdef class AdbcDatabase(_AdbcHandle): def get_option_int(self, key: str) -> int: """Get the value of an integer option.""" cdef CAdbcError c_error = empty_error() - key_bytes = key.encode("utf-8") + key_bytes = _to_bytes(key, "key") cdef char* c_key = key_bytes cdef int64_t c_value = 0 check_error( @@ -733,7 +782,7 @@ cdef class AdbcDatabase(_AdbcHandle): cdef char* c_key = NULL cdef char* c_value = NULL for key, value in kwargs.items(): - key = key.encode("utf-8") + key = _to_bytes(key, "option key") c_key = key if value is None: @@ -741,7 +790,7 @@ cdef class AdbcDatabase(_AdbcHandle): status = AdbcDatabaseSetOption( &self.database, c_key, c_value, &c_error) elif isinstance(value, str): - value = value.encode("utf-8") + value = _to_bytes(value, "option value") c_value = value status = AdbcDatabaseSetOption( &self.database, c_key, c_value, &c_error) @@ -909,10 +958,29 @@ cdef class AdbcConnection(_AdbcHandle): return stream - def get_option(self, key: str) -> str: - """Get the value of a string option.""" + def get_option( + self, + key: str | bytes, + *, + encoding="utf-8", + errors="strict", + ) -> str: + """ + Get the value of a string option. + + Parameters + ---------- + key : str or bytes + The option to get. + encoding : str + The encoding of the option value. This should almost + always be UTF-8. + errors : str + What to do about errors when decoding the option value + (see bytes.decode). + """ cdef CAdbcError c_error = empty_error() - key_bytes = key.encode("utf-8") + key_bytes = _to_bytes(key, "key") cdef char* c_key = key_bytes cdef uint8_t* c_value = NULL cdef size_t c_len = 0 @@ -940,12 +1008,12 @@ cdef class AdbcConnection(_AdbcHandle): # Remove trailing null terminator if c_len > 0: c_len -= 1 - return buf[:c_len].decode("utf-8") + return buf[:c_len].decode(encoding, errors) def get_option_bytes(self, key: str) -> bytes: """Get the value of a binary option.""" cdef CAdbcError c_error = empty_error() - key_bytes = key.encode("utf-8") + key_bytes = _to_bytes(key, "key") cdef char* c_key = key_bytes cdef uint8_t* c_value = NULL cdef size_t c_len = 0 @@ -975,7 +1043,7 @@ cdef class AdbcConnection(_AdbcHandle): def get_option_float(self, key: str) -> float: """Get the value of a floating-point option.""" cdef CAdbcError c_error = empty_error() - key_bytes = key.encode("utf-8") + key_bytes = _to_bytes(key, "key") cdef char* c_key = key_bytes cdef double c_value = 0.0 check_error( @@ -987,7 +1055,7 @@ cdef class AdbcConnection(_AdbcHandle): def get_option_int(self, key: str) -> int: """Get the value of an integer option.""" cdef CAdbcError c_error = empty_error() - key_bytes = key.encode("utf-8") + key_bytes = _to_bytes(key, "key") cdef char* c_key = key_bytes cdef int64_t c_value = 0 check_error( @@ -1086,7 +1154,8 @@ cdef class AdbcConnection(_AdbcHandle): check_error(status, &c_error) def set_options(self, **kwargs) -> None: - """Set arbitrary key-value options. + """ + Set arbitrary key-value options. Note, not all drivers support setting options after creation. @@ -1098,7 +1167,7 @@ cdef class AdbcConnection(_AdbcHandle): cdef char* c_key = NULL cdef char* c_value = NULL for key, value in kwargs.items(): - key = key.encode("utf-8") + key = _to_bytes(key, "option key") c_key = key if value is None: @@ -1106,7 +1175,7 @@ cdef class AdbcConnection(_AdbcHandle): status = AdbcConnectionSetOption( &self.connection, c_key, c_value, &c_error) elif isinstance(value, str): - value = value.encode("utf-8") + value = _to_bytes(value, "option value") c_value = value status = AdbcConnectionSetOption( &self.connection, c_key, c_value, &c_error) @@ -1357,10 +1426,29 @@ cdef class AdbcStatement(_AdbcHandle): check_error(status, &c_error) return rows_affected - def get_option(self, key: str) -> str: - """Get the value of a string option.""" + def get_option( + self, + key: str | bytes, + *, + encoding="utf-8", + errors="strict", + ) -> str: + """ + Get the value of a string option. + + Parameters + ---------- + key : str or bytes + The option to get. + encoding : str + The encoding of the option value. This should almost + always be UTF-8. + errors : str + What to do about errors when decoding the option value + (see bytes.decode). + """ cdef CAdbcError c_error = empty_error() - key_bytes = key.encode("utf-8") + key_bytes = _to_bytes(key, "key") cdef char* c_key = key_bytes cdef uint8_t* c_value = NULL cdef size_t c_len = 0 @@ -1388,12 +1476,12 @@ cdef class AdbcStatement(_AdbcHandle): # Remove trailing null terminator if c_len > 0: c_len -= 1 - return buf[:c_len].decode("utf-8") + return buf[:c_len].decode(encoding, errors) def get_option_bytes(self, key: str) -> bytes: """Get the value of a binary option.""" cdef CAdbcError c_error = empty_error() - key_bytes = key.encode("utf-8") + key_bytes = _to_bytes(key, "key") cdef char* c_key = key_bytes cdef uint8_t* c_value = NULL cdef size_t c_len = 0 @@ -1423,7 +1511,7 @@ cdef class AdbcStatement(_AdbcHandle): def get_option_float(self, key: str) -> float: """Get the value of a floating-point option.""" cdef CAdbcError c_error = empty_error() - key_bytes = key.encode("utf-8") + key_bytes = _to_bytes(key, "key") cdef char* c_key = key_bytes cdef double c_value = 0.0 check_error( @@ -1435,7 +1523,7 @@ cdef class AdbcStatement(_AdbcHandle): def get_option_int(self, key: str) -> int: """Get the value of an integer option.""" cdef CAdbcError c_error = empty_error() - key_bytes = key.encode("utf-8") + key_bytes = _to_bytes(key, "key") cdef char* c_key = key_bytes cdef int64_t c_value = 0 check_error( @@ -1487,6 +1575,8 @@ cdef class AdbcStatement(_AdbcHandle): """ Set arbitrary key-value options. + Note, not all drivers support setting options after creation. + See Also -------- adbc_driver_manager.StatementOptions : Standard option names. @@ -1495,7 +1585,7 @@ cdef class AdbcStatement(_AdbcHandle): cdef char* c_key = NULL cdef char* c_value = NULL for key, value in kwargs.items(): - key = key.encode("utf-8") + key = _to_bytes(key, "option key") c_key = key if value is None: @@ -1503,7 +1593,7 @@ cdef class AdbcStatement(_AdbcHandle): status = AdbcStatementSetOption( &self.statement, c_key, c_value, &c_error) elif isinstance(value, str): - value = value.encode("utf-8") + value = _to_bytes(value, "option value") c_value = value status = AdbcStatementSetOption( &self.statement, c_key, c_value, &c_error) diff --git a/python/adbc_driver_manager/setup.py b/python/adbc_driver_manager/setup.py index dde05a08e3..9584b78be0 100644 --- a/python/adbc_driver_manager/setup.py +++ b/python/adbc_driver_manager/setup.py @@ -72,10 +72,15 @@ def get_version_and_cmdclass(pkg_path): # ------------------------------------------------------------ # Resolve compiler flags +build_type = os.environ.get("ADBC_BUILD_TYPE", "release") + if sys.platform == "win32": extra_compile_args = ["/std:c++17", "/DADBC_EXPORTING"] else: extra_compile_args = ["-std=c++17"] + if build_type == "debug": + # Useful to step through driver manager code in GDB + extra_compile_args.extend(["-ggdb", "-Og"]) # ------------------------------------------------------------ # Setup diff --git a/python/adbc_driver_postgresql/tests/test_dbapi.py b/python/adbc_driver_postgresql/tests/test_dbapi.py index e3f86a4447..ca6908b66f 100644 --- a/python/adbc_driver_postgresql/tests/test_dbapi.py +++ b/python/adbc_driver_postgresql/tests/test_dbapi.py @@ -132,8 +132,70 @@ def test_query_trivial(postgres: dbapi.Connection): assert cur.fetchone() == (1,) +def test_query_invalid(postgres: dbapi.Connection) -> None: + with postgres.cursor() as cur: + with pytest.raises( + postgres.ProgrammingError, match="failed to prepare query" + ) as excinfo: + cur.execute("SELECT * FROM tabledoesnotexist") + + assert excinfo.value.sqlstate == "42P01" + assert len(excinfo.value.details) > 0 + + def test_stmt_ingest(postgres: dbapi.Connection) -> None: - pass + table = pyarrow.table( + [ + [1, 2, 3], + ["a", None, "b"], + ], + names=["ints", "strs"], + ) + double_table = pyarrow.table( + [ + [1, 1, 2, 2, 3, 3], + ["a", "a", None, None, "b", "b"], + ], + names=["ints", "strs"], + ) + + with postgres.cursor() as cur: + cur.execute("DROP TABLE IF EXISTS test_ingest") + + with pytest.raises( + postgres.ProgrammingError, match='"test_ingest" does not exist' + ): + cur.adbc_ingest("test_ingest", table, mode="append") + postgres.rollback() + + cur.adbc_ingest("test_ingest", table, mode="replace") + cur.execute("SELECT * FROM test_ingest ORDER BY ints") + assert cur.fetch_arrow_table() == table + + with pytest.raises( + postgres.ProgrammingError, match='"test_ingest" already exists' + ): + cur.adbc_ingest("test_ingest", table, mode="create") + + cur.adbc_ingest("test_ingest", table, mode="create_append") + cur.execute("SELECT * FROM test_ingest ORDER BY ints") + assert cur.fetch_arrow_table() == double_table + + cur.adbc_ingest("test_ingest", table, mode="replace") + cur.execute("SELECT * FROM test_ingest ORDER BY ints") + assert cur.fetch_arrow_table() == table + + cur.execute("DROP TABLE IF EXISTS test_ingest") + + cur.adbc_ingest("test_ingest", table, mode="create_append") + cur.execute("SELECT * FROM test_ingest ORDER BY ints") + assert cur.fetch_arrow_table() == table + + cur.execute("DROP TABLE IF EXISTS test_ingest") + + cur.adbc_ingest("test_ingest", table, mode="create") + cur.execute("SELECT * FROM test_ingest ORDER BY ints") + assert cur.fetch_arrow_table() == table def test_ddl(postgres: dbapi.Connection):