From 815548d747a3f8db476f6ea84cb5bee4b9218112 Mon Sep 17 00:00:00 2001
From: David Li
Date: Thu, 3 Aug 2023 09:58:41 -0400
Subject: [PATCH] feat(c,format,python): implement error details (#954)
---
.gitattributes | 2 +
adbc.h | 119 ++++++++++++-
c/driver/common/utils.c | 154 +++++++++++++++-
c/driver/common/utils.h | 13 +-
c/driver/common/utils_test.cc | 95 ++++++++++
c/driver/postgresql/CMakeLists.txt | 1 +
c/driver/postgresql/connection.cc | 31 ++--
c/driver/postgresql/database.cc | 10 +-
c/driver/postgresql/database.h | 7 +
c/driver/postgresql/error.cc | 97 +++++++++++
c/driver/postgresql/error.h | 42 +++++
c/driver/postgresql/postgresql.cc | 16 +-
c/driver/postgresql/postgresql_test.cc | 61 ++++++-
c/driver/postgresql/statement.cc | 104 ++++++-----
c/driver_manager/adbc_driver_manager.cc | 78 +++++++++
c/driver_manager/adbc_version_100.h | 7 +
.../adbc_version_100_compatibility_test.cc | 54 +++---
c/symbols.map | 9 +
.../recipe/postgresql_create_append_table.py | 2 +-
go/adbc/drivermgr/adbc.h | 119 ++++++++++++-
go/adbc/drivermgr/adbc_driver_manager.cc | 78 +++++++++
.../adbc_driver_manager/_lib.pyi | 49 ++++--
.../adbc_driver_manager/_lib.pyx | 164 ++++++++++++++----
python/adbc_driver_manager/setup.py | 5 +
.../tests/test_dbapi.py | 64 ++++++-
25 files changed, 1227 insertions(+), 154 deletions(-)
create mode 100644 c/driver/postgresql/error.cc
create mode 100644 c/driver/postgresql/error.h
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 a44721a09f..07fdfb2e77 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 9b5a3927ee..02c2a28bdc 100644
--- a/c/driver/postgresql/statement.cc
+++ b/c/driver/postgresql/statement.cc
@@ -29,6 +29,7 @@
#include
#include
+#include
#include
#include "common/utils.h"
@@ -272,20 +273,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));
@@ -293,10 +297,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;
@@ -306,10 +311,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;
@@ -476,10 +482,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);
@@ -490,18 +497,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);
}
@@ -794,10 +804,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;
@@ -864,10 +875,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;
@@ -963,11 +975,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
}
@@ -1036,10 +1049,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);
@@ -1142,6 +1156,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;
@@ -1155,6 +1170,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) {
@@ -1202,22 +1218,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 1be644093b..500fa1a119 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):