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));

Reply via email to