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 a79b6741e fix(c/driver/postgresql): encode Arrow JSON for JSONB COPY 
(#4505)
a79b6741e is described below

commit a79b6741e6ff8dadef547fa0f92732a81af28a5c
Author: 복준수 <[email protected]>
AuthorDate: Wed Jul 15 07:34:28 2026 +0900

    fix(c/driver/postgresql): encode Arrow JSON for JSONB COPY (#4505)
    
    ### Summary
    
    PostgreSQL binary COPY represents a `jsonb` field as a version
    byte followed by the JSON text. The PostgreSQL driver currently
    sends the physical string storage of `extension<arrow.json>`
    unchanged, so PostgreSQL interprets the first JSON byte as the
    version (for example, `{` becomes version 123) and rejects the
    row. This adds a target-aware JSONB COPY field writer.
    
    Closes #3293
    
    Co-authored-by: BOKJUNSOO <[email protected]>
---
 c/driver/postgresql/copy/writer.h      | 39 ++++++++++++++++++++++++++++++++++
 c/driver/postgresql/postgresql_test.cc | 20 ++++++++++++-----
 2 files changed, 54 insertions(+), 5 deletions(-)

diff --git a/c/driver/postgresql/copy/writer.h 
b/c/driver/postgresql/copy/writer.h
index 0853983ff..88db6023b 100644
--- a/c/driver/postgresql/copy/writer.h
+++ b/c/driver/postgresql/copy/writer.h
@@ -818,6 +818,26 @@ class PostgresCopyBinaryFieldWriter : public 
PostgresCopyFieldWriter {
   }
 };
 
+class PostgresCopyJsonbFieldWriter : public PostgresCopyFieldWriter {
+ public:
+  ArrowErrorCode Write(ArrowBuffer* buffer, int64_t index, ArrowError* error) 
override {
+    struct ArrowBufferView buffer_view = 
ArrowArrayViewGetBytesUnsafe(array_view_, index);
+    if (buffer_view.size_bytes >= (std::numeric_limits<int32_t>::max)()) {
+      ArrowErrorSet(error, "[libpq] JSON value at row %" PRId64 " is too 
large", index);
+      return EOVERFLOW;
+    }
+
+    const int32_t field_size_bytes = 
static_cast<int32_t>(buffer_view.size_bytes) + 1;
+    constexpr int8_t kJsonbVersion = 1;
+    NANOARROW_RETURN_NOT_OK(WriteChecked<int32_t>(buffer, field_size_bytes, 
error));
+    NANOARROW_RETURN_NOT_OK(WriteChecked<int8_t>(buffer, kJsonbVersion, 
error));
+    NANOARROW_RETURN_NOT_OK(
+        ArrowBufferAppend(buffer, buffer_view.data.as_uint8, 
buffer_view.size_bytes));
+
+    return ADBC_STATUS_OK;
+  }
+};
+
 class PostgresCopyBinaryDictFieldWriter : public PostgresCopyFieldWriter {
  public:
   ArrowErrorCode Write(ArrowBuffer* buffer, int64_t index, ArrowError* error) 
override {
@@ -968,6 +988,25 @@ static inline ArrowErrorCode MakeCopyFieldWriter(
   struct ArrowSchemaView schema_view;
   NANOARROW_RETURN_NOT_OK(ArrowSchemaViewInit(&schema_view, schema, error));
 
+  const bool is_arrow_json =
+      schema_view.extension_name.data != nullptr &&
+      std::string_view(schema_view.extension_name.data,
+                       schema_view.extension_name.size_bytes) == "arrow.json";
+  if (target_type != nullptr && target_type->type_id() == 
PostgresTypeId::kJsonb &&
+      is_arrow_json) {
+    switch (schema_view.type) {
+      case NANOARROW_TYPE_STRING:
+      case NANOARROW_TYPE_LARGE_STRING:
+      case NANOARROW_TYPE_STRING_VIEW: {
+        using T = PostgresCopyJsonbFieldWriter;
+        *out = T::Create<T>(array_view);
+        return NANOARROW_OK;
+      }
+      default:
+        break;
+    }
+  }
+
   if (target_type != nullptr && target_type->type_id() == 
PostgresTypeId::kNumeric) {
     switch (schema_view.type) {
       case NANOARROW_TYPE_INT8:
diff --git a/c/driver/postgresql/postgresql_test.cc 
b/c/driver/postgresql/postgresql_test.cc
index 20f19264a..3bc4db35e 100644
--- a/c/driver/postgresql/postgresql_test.cc
+++ b/c/driver/postgresql/postgresql_test.cc
@@ -1802,12 +1802,22 @@ TEST_F(PostgresStatementTest, SqlIngestJsonb) {
                 IsOkStatus(&error));
     ASSERT_THAT(AdbcStatementBind(&statement, &batch.value, &schema.value, 
&error),
                 IsOkStatus(&error));
-    // TODO(https://github.com/apache/arrow-adbc/issues/3293): we need a
-    // different extension type for JSONB so the driver can know to generate
-    // the appropriate COPY representation
-    // (JSON-representation-version-prefixed JSON string).
     ASSERT_THAT(AdbcStatementExecuteQuery(&statement, nullptr, nullptr, 
&error),
-                IsStatus(ADBC_STATUS_INVALID_ARGUMENT, &error));
+                IsOkStatus(&error));
+
+    ASSERT_THAT(AdbcStatementSetSqlQuery(
+                    &statement, "SELECT j::text FROM jsontable ORDER BY ctid", 
&error),
+                IsOkStatus(&error));
+
+    adbc_validation::StreamReader reader;
+    ASSERT_THAT(AdbcStatementExecuteQuery(&statement, &reader.stream.value,
+                                          &reader.rows_affected, &error),
+                IsOkStatus(&error));
+    ASSERT_NO_FATAL_FAILURE(reader.GetSchema());
+    ASSERT_NO_FATAL_FAILURE(reader.Next());
+    ASSERT_NE(nullptr, reader.array->release);
+    ASSERT_NO_FATAL_FAILURE(adbc_validation::CompareArray<std::string>(
+        reader.array_view->children[0], {R"({"a": 1, "b": [1, 2, 3]})", 
std::nullopt}));
   }
 }
 

Reply via email to