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 2964c540f fix(c/driver/postgresql): handle empty strings correctly in
parameter binding (#3601)
2964c540f is described below
commit 2964c540f70e8887683f17cd649af6f94b1ecc5c
Author: Mandukhai Alimaa <[email protected]>
AuthorDate: Mon Oct 20 23:28:04 2025 -0500
fix(c/driver/postgresql): handle empty strings correctly in parameter
binding (#3601)
This PR fixes an issue in the PostgreSQL driver’s parameter binding
logic where empty strings were incorrectly treated as NULL values.
Null detection was inferred from param_lengths[col] == 0 and empty
strings (valid zero-length values) were misclassified as NULL.
Closes #3585.
---
c/driver/postgresql/bind_stream.h | 6 ++-
c/driver/postgresql/postgresql_test.cc | 98 ++++++++++++++++++++++++++++++++++
2 files changed, 102 insertions(+), 2 deletions(-)
diff --git a/c/driver/postgresql/bind_stream.h
b/c/driver/postgresql/bind_stream.h
index 07a36e6c2..25c55eec7 100644
--- a/c/driver/postgresql/bind_stream.h
+++ b/c/driver/postgresql/bind_stream.h
@@ -201,9 +201,11 @@ struct BindStream {
int result_format) {
param_buffer->size_bytes = 0;
int64_t last_offset = 0;
+ std::vector<bool> is_null_param(array_view->n_children);
for (int64_t col = 0; col < array_view->n_children; col++) {
- if (!ArrowArrayViewIsNull(array_view->children[col], current_row)) {
+ is_null_param[col] = ArrowArrayViewIsNull(array_view->children[col],
current_row);
+ if (!is_null_param[col]) {
// Note that this Write() call currently writes the (int32_t) byte
size of the
// field in addition to the serialized value.
UNWRAP_NANOARROW(
@@ -225,7 +227,7 @@ struct BindStream {
last_offset = 0;
for (int64_t col = 0; col < array_view->n_children; col++) {
last_offset += sizeof(int32_t);
- if (param_lengths[col] == 0) {
+ if (is_null_param[col]) {
param_values[col] = nullptr;
} else {
param_values[col] = reinterpret_cast<char*>(param_buffer->data) +
last_offset;
diff --git a/c/driver/postgresql/postgresql_test.cc
b/c/driver/postgresql/postgresql_test.cc
index c8d204bd5..2a80f9287 100644
--- a/c/driver/postgresql/postgresql_test.cc
+++ b/c/driver/postgresql/postgresql_test.cc
@@ -1712,6 +1712,104 @@ TEST_F(PostgresStatementTest,
ExecuteParameterizedQueryWithRowsAffected) {
}
}
+// Test for making sure empty string/binary parameters are inserted correct
+TEST_F(PostgresStatementTest, EmptyStringAndBinaryParameter) {
+ ASSERT_THAT(quirks()->DropTable(&connection, "adbc_test", &error),
IsOkStatus(&error));
+ ASSERT_THAT(AdbcStatementNew(&connection, &statement, &error),
IsOkStatus(&error));
+
+ // Create test table with both TEXT and BYTEA columns
+ {
+ ASSERT_THAT(AdbcStatementSetSqlQuery(
+ &statement,
+ "CREATE TABLE adbc_test (text_data TEXT, binary_data
BYTEA)", &error),
+ IsOkStatus(&error));
+ adbc_validation::StreamReader reader;
+ ASSERT_THAT(
+ AdbcStatementExecuteQuery(&statement, &reader.stream.value, nullptr,
&error),
+ IsOkStatus(&error));
+ ASSERT_NO_FATAL_FAILURE(reader.GetSchema());
+ ASSERT_NO_FATAL_FAILURE(reader.Next());
+ ASSERT_EQ(reader.array->release, nullptr);
+ }
+
+ // Insert empty string and binary via parameters
+ {
+ nanoarrow::UniqueSchema schema_bind;
+ ArrowSchemaInit(schema_bind.get());
+ ASSERT_THAT(ArrowSchemaSetTypeStruct(schema_bind.get(), 2),
+ adbc_validation::IsOkErrno());
+ ASSERT_THAT(ArrowSchemaSetType(schema_bind->children[0],
NANOARROW_TYPE_STRING),
+ adbc_validation::IsOkErrno());
+ ASSERT_THAT(ArrowSchemaSetType(schema_bind->children[1],
NANOARROW_TYPE_BINARY),
+ adbc_validation::IsOkErrno());
+
+ nanoarrow::UniqueArray bind;
+ ASSERT_THAT(ArrowArrayInitFromSchema(bind.get(), schema_bind.get(),
nullptr),
+ adbc_validation::IsOkErrno());
+ ASSERT_THAT(ArrowArrayStartAppending(bind.get()),
adbc_validation::IsOkErrno());
+
+ // Add one row with empty string and empty binary parameters
+ ASSERT_THAT(ArrowArrayAppendString(bind->children[0], ArrowCharView("")),
+ adbc_validation::IsOkErrno());
+ ArrowBufferView empty_buffer = {{nullptr}, 0};
+ ASSERT_THAT(ArrowArrayAppendBytes(bind->children[1], empty_buffer),
+ adbc_validation::IsOkErrno());
+ ASSERT_THAT(ArrowArrayFinishElement(bind.get()),
adbc_validation::IsOkErrno());
+ ASSERT_THAT(ArrowArrayFinishBuildingDefault(bind.get(), nullptr),
+ adbc_validation::IsOkErrno());
+
+ ASSERT_THAT(AdbcStatementSetSqlQuery(&statement,
+ "INSERT INTO adbc_test VALUES ($1,
$2)", &error),
+ IsOkStatus(&error));
+ ASSERT_THAT(AdbcStatementBind(&statement, bind.get(), schema_bind.get(),
&error),
+ IsOkStatus(&error));
+
+ adbc_validation::StreamReader reader;
+ ASSERT_THAT(
+ AdbcStatementExecuteQuery(&statement, &reader.stream.value, nullptr,
&error),
+ IsOkStatus(&error));
+ ASSERT_NO_FATAL_FAILURE(reader.GetSchema());
+ ASSERT_NO_FATAL_FAILURE(reader.Next());
+ ASSERT_EQ(reader.array->release, nullptr);
+ }
+
+ // Verify empty values were inserted correctly (not as NULL)
+ {
+ ASSERT_THAT(AdbcStatementSetSqlQuery(
+ &statement, "SELECT text_data, binary_data FROM
adbc_test", &error),
+ IsOkStatus(&error));
+ adbc_validation::StreamReader reader;
+ ASSERT_THAT(
+ AdbcStatementExecuteQuery(&statement, &reader.stream.value, nullptr,
&error),
+ IsOkStatus(&error));
+ ASSERT_NO_FATAL_FAILURE(reader.GetSchema());
+ ASSERT_NO_FATAL_FAILURE(reader.Next());
+ ASSERT_NE(reader.array->release, nullptr);
+ ASSERT_EQ(reader.array->length, 1);
+
+ // Row should contain empty values, not NULL
+ ASSERT_EQ(reader.array->children[0]->null_count, 0); // text_data
+ ASSERT_EQ(reader.array->children[1]->null_count, 0); // binary_data
+
+ // Check that both values are empty (string and binary)
+ // Check the single row
+ ASSERT_FALSE(ArrowArrayViewIsNull(reader.array_view->children[0], 0));
+ struct ArrowBufferView string_view =
+ ArrowArrayViewGetBytesUnsafe(reader.array_view->children[0], 0);
+ ASSERT_EQ(string_view.size_bytes, 0); // Empty string should have size 0
+
+ ASSERT_FALSE(ArrowArrayViewIsNull(reader.array_view->children[1], 0));
+ struct ArrowBufferView binary_view =
+ ArrowArrayViewGetBytesUnsafe(reader.array_view->children[1], 0);
+ ASSERT_EQ(binary_view.size_bytes, 0); // Empty binary should have size 0
+
+ ASSERT_NO_FATAL_FAILURE(reader.Next());
+ ASSERT_EQ(reader.array->release, nullptr);
+ }
+
+ ASSERT_THAT(AdbcStatementRelease(&statement, &error), IsOkStatus(&error));
+}
+
TEST_F(PostgresStatementTest, SqlExecuteCopyZeroRowOutputError) {
ASSERT_THAT(quirks()->DropTable(&connection, "adbc_test", &error),
IsOkStatus(&error));
ASSERT_THAT(AdbcStatementNew(&connection, &statement, &error),
IsOkStatus(&error));