This is an automated email from the ASF dual-hosted git repository.
lidavidm pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-adbc.git
The following commit(s) were added to refs/heads/main by this push:
new 47e1a3cbd2 feat(c/driver_manager): polyfill numeric options for 1.0
drivers (#4774)
47e1a3cbd2 is described below
commit 47e1a3cbd2f1a0f2be9353473e98c4aec89f5864
Author: Yifan Chen <[email protected]>
AuthorDate: Sun Oct 4 18:33:40 2026 -0700
feat(c/driver_manager): polyfill numeric options for 1.0 drivers (#4774)
## Summary
- track the version actually accepted during driver negotiation
- for drivers negotiated down to ADBC 1.0, implement integer and double
setters through the legacy string setter
- use locale-independent numeric formatting
OpenAI Codex was used to help implement and test this change.
Closes #4115
---
c/driver_manager/adbc_driver_manager_api.cc | 64 +++++++-
.../adbc_version_100_compatibility_test.cc | 168 +++++++++++++++++++++
go/adbc/drivermgr/adbc_driver_manager_api.cc | 64 +++++++-
3 files changed, 294 insertions(+), 2 deletions(-)
diff --git a/c/driver_manager/adbc_driver_manager_api.cc
b/c/driver_manager/adbc_driver_manager_api.cc
index 19f8e7b4c5..6d4b66df7a 100644
--- a/c/driver_manager/adbc_driver_manager_api.cc
+++ b/c/driver_manager/adbc_driver_manager_api.cc
@@ -21,6 +21,10 @@
#include <cassert>
#include <cerrno>
#include <cstring>
+#include <iomanip>
+#include <limits>
+#include <locale>
+#include <sstream>
#include <string>
#include <unordered_map>
#include <utility>
@@ -404,6 +408,52 @@ AdbcStatusCode StatementSetOptionDouble(struct
AdbcStatement* statement, const c
return ADBC_STATUS_NOT_IMPLEMENTED;
}
+template <typename Object, typename Value, typename Setter>
+AdbcStatusCode SetNumericOptionAsString(Object* object, const char* key, Value
value,
+ struct AdbcError* error, Setter
setter) {
+ std::ostringstream text;
+ text.imbue(std::locale::classic());
+ text << std::setprecision(std::numeric_limits<double>::max_digits10) <<
value;
+ return setter(object, key, text.str().c_str(), error);
+}
+
+AdbcStatusCode DatabaseSetOptionIntLegacy(AdbcDatabase* database, const char*
key,
+ int64_t value, AdbcError* error) {
+ return SetNumericOptionAsString(database, key, value, error,
+ database->private_driver->DatabaseSetOption);
+}
+
+AdbcStatusCode DatabaseSetOptionDoubleLegacy(AdbcDatabase* database, const
char* key,
+ double value, AdbcError* error) {
+ return SetNumericOptionAsString(database, key, value, error,
+ database->private_driver->DatabaseSetOption);
+}
+
+AdbcStatusCode ConnectionSetOptionIntLegacy(AdbcConnection* connection, const
char* key,
+ int64_t value, AdbcError* error) {
+ return SetNumericOptionAsString(connection, key, value, error,
+
connection->private_driver->ConnectionSetOption);
+}
+
+AdbcStatusCode ConnectionSetOptionDoubleLegacy(AdbcConnection* connection,
+ const char* key, double value,
+ AdbcError* error) {
+ return SetNumericOptionAsString(connection, key, value, error,
+
connection->private_driver->ConnectionSetOption);
+}
+
+AdbcStatusCode StatementSetOptionIntLegacy(AdbcStatement* statement, const
char* key,
+ int64_t value, AdbcError* error) {
+ return SetNumericOptionAsString(statement, key, value, error,
+
statement->private_driver->StatementSetOption);
+}
+
+AdbcStatusCode StatementSetOptionDoubleLegacy(AdbcStatement* statement, const
char* key,
+ double value, AdbcError* error) {
+ return SetNumericOptionAsString(statement, key, value, error,
+
statement->private_driver->StatementSetOption);
+}
+
AdbcStatusCode StatementSetSqlQuery(struct AdbcStatement*, const char*,
struct AdbcError* error) {
SetError(error, "AdbcStatementSetSqlQuery not implemented");
@@ -1475,10 +1525,14 @@ AdbcStatusCode
AdbcLoadDriverFromInitFunc(AdbcDriverInitFunc init_func, int vers
// succession with the underlying driver until we find one that's
// accepted.
AdbcStatusCode result = ADBC_STATUS_NOT_IMPLEMENTED;
+ int negotiated_version = version;
for (const int try_version : kSupportedVersions) {
if (try_version > version) continue;
result = init_func(try_version, raw_driver, error);
- if (result != ADBC_STATUS_NOT_IMPLEMENTED) break;
+ if (result != ADBC_STATUS_NOT_IMPLEMENTED) {
+ negotiated_version = try_version;
+ break;
+ }
}
if (result != ADBC_STATUS_OK) {
return result;
@@ -1517,6 +1571,14 @@ AdbcStatusCode
AdbcLoadDriverFromInitFunc(AdbcDriverInitFunc init_func, int vers
}
if (version >= ADBC_VERSION_1_1_0) {
auto* driver = reinterpret_cast<struct AdbcDriver*>(raw_driver);
+ if (negotiated_version == ADBC_VERSION_1_0_0) {
+ driver->DatabaseSetOptionInt = DatabaseSetOptionIntLegacy;
+ driver->DatabaseSetOptionDouble = DatabaseSetOptionDoubleLegacy;
+ driver->ConnectionSetOptionInt = ConnectionSetOptionIntLegacy;
+ driver->ConnectionSetOptionDouble = ConnectionSetOptionDoubleLegacy;
+ driver->StatementSetOptionInt = StatementSetOptionIntLegacy;
+ driver->StatementSetOptionDouble = StatementSetOptionDoubleLegacy;
+ }
FILL_DEFAULT(driver, ErrorGetDetailCount);
FILL_DEFAULT(driver, ErrorGetDetail);
FILL_DEFAULT(driver, ErrorFromArrayStream);
diff --git a/c/driver_manager/adbc_version_100_compatibility_test.cc
b/c/driver_manager/adbc_version_100_compatibility_test.cc
index 43079ecb3e..8e7428d597 100644
--- a/c/driver_manager/adbc_version_100_compatibility_test.cc
+++ b/c/driver_manager/adbc_version_100_compatibility_test.cc
@@ -18,7 +18,12 @@
#include <gmock/gmock.h>
#include <gtest/gtest.h>
+#include <cmath>
+#include <limits>
+#include <locale>
#include <memory>
+#include <string>
+#include <vector>
#include "adbc_version_100.h"
#include "arrow-adbc/adbc.h"
@@ -30,11 +35,66 @@ namespace adbc {
using adbc_validation::IsOkStatus;
using adbc_validation::IsStatus;
+std::vector<std::string> option_values;
+AdbcStatusCode option_status = ADBC_STATUS_OK;
+
+template <typename Object>
+AdbcStatusCode CaptureOption(Object*, const char* key, const char* value,
AdbcError*) {
+ EXPECT_STREQ(key, "option");
+ option_values.emplace_back(value);
+ return option_status;
+}
+
+AdbcStatusCode LegacyOptionDriverInit(int version, void* raw_driver,
AdbcError* error) {
+ auto status = Version100DriverInit(version, raw_driver, error);
+ if (status != ADBC_STATUS_OK) return status;
+
+ auto* driver = static_cast<AdbcDriver*>(raw_driver);
+ driver->DatabaseSetOption = CaptureOption<AdbcDatabase>;
+ driver->ConnectionSetOption = CaptureOption<AdbcConnection>;
+ driver->StatementSetOption = CaptureOption<AdbcStatement>;
+ return ADBC_STATUS_OK;
+}
+
+AdbcStatusCode Version110OptionDriverInit(int, void* raw_driver, AdbcError*
error) {
+ return LegacyOptionDriverInit(ADBC_VERSION_1_0_0, raw_driver, error);
+}
+
+AdbcStatusCode NativeStatementSetOptionInt(AdbcStatement*, const char*,
int64_t,
+ AdbcError*) {
+ return ADBC_STATUS_CANCELLED;
+}
+
+AdbcStatusCode Version110NativeOptionDriverInit(int version, void* raw_driver,
+ AdbcError* error) {
+ auto status = Version110OptionDriverInit(version, raw_driver, error);
+ static_cast<AdbcDriver*>(raw_driver)->StatementSetOptionInt =
+ NativeStatementSetOptionInt;
+ return status;
+}
+
+struct CommaDecimal : std::numpunct<char> {
+ char do_decimal_point() const override { return ','; }
+};
+
+class ScopedCommaLocale {
+ public:
+ ScopedCommaLocale() : previous_(std::locale()) {
+ std::locale::global(std::locale(previous_, new CommaDecimal));
+ }
+ ~ScopedCommaLocale() { std::locale::global(previous_); }
+
+ private:
+ std::locale previous_;
+};
+
class AdbcVersion : public ::testing::Test {
public:
void SetUp() override {
std::memset(&driver, 0, sizeof(driver));
std::memset(&error, 0, sizeof(error));
+ option_values.clear();
+ option_status = ADBC_STATUS_OK;
}
void TearDown() override {
@@ -105,6 +165,114 @@ TEST_F(AdbcVersion, OldDriverNewManager) {
EXPECT_NE(driver.StatementSetOptionDouble, nullptr);
}
+TEST_F(AdbcVersion, OldDriverNumericSetters) {
+ ASSERT_THAT(AdbcLoadDriverFromInitFunc(&LegacyOptionDriverInit,
ADBC_VERSION_1_1_0,
+ &driver, &error),
+ IsOkStatus(&error));
+
+ AdbcDatabase database = {nullptr, &driver};
+ AdbcConnection connection = {nullptr, &driver};
+ AdbcStatement statement = {nullptr, &driver};
+ ASSERT_THAT(driver.DatabaseNew(&database, &error), IsOkStatus(&error));
+ ASSERT_THAT(driver.ConnectionNew(&connection, &error), IsOkStatus(&error));
+ ASSERT_THAT(driver.StatementNew(&connection, &statement, &error),
IsOkStatus(&error));
+
+ ScopedCommaLocale locale;
+ for (int64_t value : {std::numeric_limits<int64_t>::min(), int64_t{0},
+ std::numeric_limits<int64_t>::max()}) {
+ ASSERT_THAT(AdbcDatabaseSetOptionInt(&database, "option", value, &error),
+ IsOkStatus(&error));
+ ASSERT_THAT(AdbcConnectionSetOptionInt(&connection, "option", value,
&error),
+ IsOkStatus(&error));
+ ASSERT_THAT(AdbcStatementSetOptionInt(&statement, "option", value, &error),
+ IsOkStatus(&error));
+ for (size_t i = option_values.size() - 3; i < option_values.size(); ++i) {
+ EXPECT_EQ(std::to_string(value), option_values[i]);
+ }
+ }
+
+ for (double value : {1.2345678901234567, 1e-12, -0.0, 1e200}) {
+ ASSERT_THAT(AdbcDatabaseSetOptionDouble(&database, "option", value,
&error),
+ IsOkStatus(&error));
+ ASSERT_THAT(AdbcConnectionSetOptionDouble(&connection, "option", value,
&error),
+ IsOkStatus(&error));
+ ASSERT_THAT(AdbcStatementSetOptionDouble(&statement, "option", value,
&error),
+ IsOkStatus(&error));
+ for (size_t i = option_values.size() - 3; i < option_values.size(); ++i) {
+ EXPECT_EQ(std::string::npos, option_values[i].find(','));
+ EXPECT_EQ(value, std::stod(option_values[i]));
+ EXPECT_EQ(std::signbit(value),
std::signbit(std::stod(option_values[i])));
+ }
+ }
+
+ EXPECT_THAT(driver.StatementRelease(&statement, &error), IsOkStatus(&error));
+ EXPECT_THAT(driver.ConnectionRelease(&connection, &error),
IsOkStatus(&error));
+ EXPECT_THAT(driver.DatabaseRelease(&database, &error), IsOkStatus(&error));
+}
+
+TEST_F(AdbcVersion, OldDriverQueuedOptionsAndErrorStatus) {
+ AdbcDatabase database = {};
+ AdbcConnection connection = {};
+ AdbcStatement statement = {};
+ ASSERT_THAT(AdbcDatabaseNew(&database, &error), IsOkStatus(&error));
+ ASSERT_THAT(
+ AdbcDriverManagerDatabaseSetInitFunc(&database, LegacyOptionDriverInit,
&error),
+ IsOkStatus(&error));
+ ASSERT_THAT(AdbcDatabaseSetOptionInt(&database, "option", 42, &error),
+ IsOkStatus(&error));
+ ASSERT_THAT(AdbcDatabaseInit(&database, &error), IsOkStatus(&error));
+ ASSERT_THAT(AdbcConnectionNew(&connection, &error), IsOkStatus(&error));
+ ASSERT_THAT(AdbcConnectionSetOptionDouble(&connection, "option", 1e-12,
&error),
+ IsOkStatus(&error));
+ ASSERT_THAT(AdbcConnectionInit(&connection, &database, &error),
IsOkStatus(&error));
+ ASSERT_THAT(AdbcStatementNew(&connection, &statement, &error),
IsOkStatus(&error));
+
+ option_status = ADBC_STATUS_INVALID_ARGUMENT;
+ EXPECT_THAT(AdbcStatementSetOptionInt(&statement, "option", 1, &error),
+ IsStatus(option_status, &error));
+ ASSERT_EQ(3U, option_values.size());
+ EXPECT_EQ("42", option_values[0]);
+ EXPECT_EQ(1e-12, std::stod(option_values[1]));
+ EXPECT_EQ("1", option_values[2]);
+
+ option_status = ADBC_STATUS_OK;
+ EXPECT_THAT(AdbcStatementRelease(&statement, &error), IsOkStatus(&error));
+ EXPECT_THAT(AdbcConnectionRelease(&connection, &error), IsOkStatus(&error));
+ EXPECT_THAT(AdbcDatabaseRelease(&database, &error), IsOkStatus(&error));
+}
+
+TEST_F(AdbcVersion, Version110DriverDoesNotUseFallback) {
+ ASSERT_THAT(AdbcLoadDriverFromInitFunc(&Version110OptionDriverInit,
ADBC_VERSION_1_1_0,
+ &driver, &error),
+ IsOkStatus(&error));
+ AdbcStatement statement = {nullptr, &driver};
+ EXPECT_THAT(AdbcStatementSetOptionInt(&statement, "option", 1, &error),
+ IsStatus(ADBC_STATUS_NOT_IMPLEMENTED, &error));
+ EXPECT_TRUE(option_values.empty());
+}
+
+TEST_F(AdbcVersion, OldDriverBytesRemainUnsupported) {
+ ASSERT_THAT(AdbcLoadDriverFromInitFunc(&LegacyOptionDriverInit,
ADBC_VERSION_1_1_0,
+ &driver, &error),
+ IsOkStatus(&error));
+ AdbcStatement statement = {nullptr, &driver};
+ const uint8_t value[] = {'a', 0, 'b'};
+ EXPECT_THAT(
+ AdbcStatementSetOptionBytes(&statement, "option", value, sizeof(value),
&error),
+ IsStatus(ADBC_STATUS_NOT_IMPLEMENTED, &error));
+ EXPECT_TRUE(option_values.empty());
+}
+
+TEST_F(AdbcVersion, Version110NativeNumericSetterIsPreserved) {
+ ASSERT_THAT(AdbcLoadDriverFromInitFunc(&Version110NativeOptionDriverInit,
+ ADBC_VERSION_1_1_0, &driver, &error),
+ IsOkStatus(&error));
+ AdbcStatement statement = {nullptr, &driver};
+ EXPECT_THAT(AdbcStatementSetOptionInt(&statement, "option", 1, &error),
+ IsStatus(ADBC_STATUS_CANCELLED, &error));
+ EXPECT_TRUE(option_values.empty());
+}
+
// N.B. see postgresql_test.cc for backwards compatibility test of AdbcError
// N.B. see postgresql_test.cc for backwards compatibility test of AdbcDriver
diff --git a/go/adbc/drivermgr/adbc_driver_manager_api.cc
b/go/adbc/drivermgr/adbc_driver_manager_api.cc
index 19f8e7b4c5..6d4b66df7a 100644
--- a/go/adbc/drivermgr/adbc_driver_manager_api.cc
+++ b/go/adbc/drivermgr/adbc_driver_manager_api.cc
@@ -21,6 +21,10 @@
#include <cassert>
#include <cerrno>
#include <cstring>
+#include <iomanip>
+#include <limits>
+#include <locale>
+#include <sstream>
#include <string>
#include <unordered_map>
#include <utility>
@@ -404,6 +408,52 @@ AdbcStatusCode StatementSetOptionDouble(struct
AdbcStatement* statement, const c
return ADBC_STATUS_NOT_IMPLEMENTED;
}
+template <typename Object, typename Value, typename Setter>
+AdbcStatusCode SetNumericOptionAsString(Object* object, const char* key, Value
value,
+ struct AdbcError* error, Setter
setter) {
+ std::ostringstream text;
+ text.imbue(std::locale::classic());
+ text << std::setprecision(std::numeric_limits<double>::max_digits10) <<
value;
+ return setter(object, key, text.str().c_str(), error);
+}
+
+AdbcStatusCode DatabaseSetOptionIntLegacy(AdbcDatabase* database, const char*
key,
+ int64_t value, AdbcError* error) {
+ return SetNumericOptionAsString(database, key, value, error,
+ database->private_driver->DatabaseSetOption);
+}
+
+AdbcStatusCode DatabaseSetOptionDoubleLegacy(AdbcDatabase* database, const
char* key,
+ double value, AdbcError* error) {
+ return SetNumericOptionAsString(database, key, value, error,
+ database->private_driver->DatabaseSetOption);
+}
+
+AdbcStatusCode ConnectionSetOptionIntLegacy(AdbcConnection* connection, const
char* key,
+ int64_t value, AdbcError* error) {
+ return SetNumericOptionAsString(connection, key, value, error,
+
connection->private_driver->ConnectionSetOption);
+}
+
+AdbcStatusCode ConnectionSetOptionDoubleLegacy(AdbcConnection* connection,
+ const char* key, double value,
+ AdbcError* error) {
+ return SetNumericOptionAsString(connection, key, value, error,
+
connection->private_driver->ConnectionSetOption);
+}
+
+AdbcStatusCode StatementSetOptionIntLegacy(AdbcStatement* statement, const
char* key,
+ int64_t value, AdbcError* error) {
+ return SetNumericOptionAsString(statement, key, value, error,
+
statement->private_driver->StatementSetOption);
+}
+
+AdbcStatusCode StatementSetOptionDoubleLegacy(AdbcStatement* statement, const
char* key,
+ double value, AdbcError* error) {
+ return SetNumericOptionAsString(statement, key, value, error,
+
statement->private_driver->StatementSetOption);
+}
+
AdbcStatusCode StatementSetSqlQuery(struct AdbcStatement*, const char*,
struct AdbcError* error) {
SetError(error, "AdbcStatementSetSqlQuery not implemented");
@@ -1475,10 +1525,14 @@ AdbcStatusCode
AdbcLoadDriverFromInitFunc(AdbcDriverInitFunc init_func, int vers
// succession with the underlying driver until we find one that's
// accepted.
AdbcStatusCode result = ADBC_STATUS_NOT_IMPLEMENTED;
+ int negotiated_version = version;
for (const int try_version : kSupportedVersions) {
if (try_version > version) continue;
result = init_func(try_version, raw_driver, error);
- if (result != ADBC_STATUS_NOT_IMPLEMENTED) break;
+ if (result != ADBC_STATUS_NOT_IMPLEMENTED) {
+ negotiated_version = try_version;
+ break;
+ }
}
if (result != ADBC_STATUS_OK) {
return result;
@@ -1517,6 +1571,14 @@ AdbcStatusCode
AdbcLoadDriverFromInitFunc(AdbcDriverInitFunc init_func, int vers
}
if (version >= ADBC_VERSION_1_1_0) {
auto* driver = reinterpret_cast<struct AdbcDriver*>(raw_driver);
+ if (negotiated_version == ADBC_VERSION_1_0_0) {
+ driver->DatabaseSetOptionInt = DatabaseSetOptionIntLegacy;
+ driver->DatabaseSetOptionDouble = DatabaseSetOptionDoubleLegacy;
+ driver->ConnectionSetOptionInt = ConnectionSetOptionIntLegacy;
+ driver->ConnectionSetOptionDouble = ConnectionSetOptionDoubleLegacy;
+ driver->StatementSetOptionInt = StatementSetOptionIntLegacy;
+ driver->StatementSetOptionDouble = StatementSetOptionDoubleLegacy;
+ }
FILL_DEFAULT(driver, ErrorGetDetailCount);
FILL_DEFAULT(driver, ErrorGetDetail);
FILL_DEFAULT(driver, ErrorFromArrayStream);