You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@arrow.apache.org by li...@apache.org on 2024/01/31 14:28:58 UTC

(arrow-adbc) branch main updated: feat(c/driver/postgresql): Add enum type support (#1485)

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 12ea8b6f feat(c/driver/postgresql): Add enum type support (#1485)
12ea8b6f is described below

commit 12ea8b6f44dcf5f6ef9a939acdaad31efaf490d8
Author: Dewey Dunnington <de...@voltrondata.com>
AuthorDate: Wed Jan 31 10:28:53 2024 -0400

    feat(c/driver/postgresql): Add enum type support (#1485)
    
    Closes #1483.
    
    This was pretty straightforward because the COPY representation is the
    same as a string.
    
    I also moved all the COPY examples to the common header (since only a
    few were missing and it seemed odd that some of them were in different
    spots).
    
    From R:
    
    ``` r
    library(adbcdrivermanager)
    
    db <- adbc_database_init(
      adbcpostgresql::adbcpostgresql(),
      uri = "postgresql://localhost:5432/postgres?user=postgres&password=password")
    
    db |>
      execute_adbc("CREATE TYPE mood AS ENUM ('sad', 'ok', 'happy')")
    
    db |>
      adbc_database_release()
    
    # Have to recreate the database after because that's where we cache the types
    con <- db <- adbc_database_init(
        adbcpostgresql::adbcpostgresql(),
        uri = "postgresql://localhost:5432/postgres?user=postgres&password=password"
      ) |>
      adbc_connection_init()
    
    con |>
      execute_adbc("CREATE TABLE x (a mood)") |>
      execute_adbc("INSERT INTO x VALUES ('sad')") |>
      execute_adbc("INSERT INTO x VALUES ('ok')")
    
    # Returns as string because that's how the driver sees it
    con |>
      read_adbc("SELECT * from x") |>
      as.data.frame()
    #>     a
    #> 1 sad
    #> 2  ok
    ```
    
    <sup>Created on 2024-01-23 with [reprex
    v2.0.2](https://reprex.tidyverse.org)</sup>
---
 .../postgresql/copy/postgres_copy_reader_test.cc   | 88 ++++++++++------------
 .../postgresql/copy/postgres_copy_test_common.h    | 80 +++++++++++++++++---
 c/driver/postgresql/copy/reader.h                  |  1 +
 c/driver/postgresql/postgres_type.h                |  7 ++
 4 files changed, 116 insertions(+), 60 deletions(-)

diff --git a/c/driver/postgresql/copy/postgres_copy_reader_test.cc b/c/driver/postgresql/copy/postgres_copy_reader_test.cc
index 366ad7dd..ccc52bce 100644
--- a/c/driver/postgresql/copy/postgres_copy_reader_test.cc
+++ b/c/driver/postgresql/copy/postgres_copy_reader_test.cc
@@ -314,29 +314,6 @@ TEST(PostgresCopyUtilsTest, PostgresCopyReadDate) {
   ASSERT_EQ(data_buffer[1], 47482);
 }
 
-// For full coverage, ensure that this contains NUMERIC examples that:
-// - Have >= four zeroes to the left of the decimal point
-// - Have >= four zeroes to the right of the decimal point
-// - Include special values (nan, -inf, inf, NULL)
-// - Have >= four trailing zeroes to the right of the decimal point
-// - Have >= four leading zeroes before the first digit to the right of the decimal point
-// - Is < 0 (negative)
-// COPY (SELECT CAST(col AS NUMERIC) AS col FROM (  VALUES (1000000), ('0.00001234'),
-// ('1.0000'), (-123.456), (123.456), ('nan'), ('-inf'), ('inf'), (NULL)) AS drvd(col)) TO
-// STDOUT WITH (FORMAT binary);
-static uint8_t kTestPgCopyNumeric[] = {
-    0x50, 0x47, 0x43, 0x4f, 0x50, 0x59, 0x0a, 0xff, 0x0d, 0x0a, 0x00, 0x00, 0x00, 0x00,
-    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x01, 0x00,
-    0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0a, 0x00,
-    0x01, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x08, 0x04, 0xd2, 0x00, 0x01, 0x00, 0x00, 0x00,
-    0x0a, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x01, 0x00, 0x01, 0x00,
-    0x00, 0x00, 0x0c, 0x00, 0x02, 0x00, 0x00, 0x40, 0x00, 0x00, 0x03, 0x00, 0x7b, 0x11,
-    0xd0, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
-    0x03, 0x00, 0x7b, 0x11, 0xd0, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
-    0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
-    0x00, 0xf0, 0x00, 0x00, 0x20, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
-    0x00, 0xd0, 0x00, 0x00, 0x20, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
-
 TEST(PostgresCopyUtilsTest, PostgresCopyReadNumeric) {
   ArrowBufferView data;
   data.data.as_uint8 = kTestPgCopyNumeric;
@@ -515,6 +492,45 @@ TEST(PostgresCopyUtilsTest, PostgresCopyReadText) {
   ASSERT_EQ(std::string(data_buffer + 3, 4), "1234");
 }
 
+TEST(PostgresCopyUtilsTest, PostgresCopyReadEnum) {
+  ArrowBufferView data;
+  data.data.as_uint8 = kTestPgCopyEnum;
+  data.size_bytes = sizeof(kTestPgCopyEnum);
+
+  auto col_type = PostgresType(PostgresTypeId::kEnum);
+  PostgresType input_type(PostgresTypeId::kRecord);
+  input_type.AppendChild("col", col_type);
+
+  PostgresCopyStreamTester tester;
+  ASSERT_EQ(tester.Init(input_type), NANOARROW_OK);
+  ASSERT_EQ(tester.ReadAll(&data), ENODATA);
+  ASSERT_EQ(data.data.as_uint8 - kTestPgCopyEnum, sizeof(kTestPgCopyEnum));
+  ASSERT_EQ(data.size_bytes, 0);
+
+  nanoarrow::UniqueArray array;
+  ASSERT_EQ(tester.GetArray(array.get()), NANOARROW_OK);
+  ASSERT_EQ(array->length, 3);
+  ASSERT_EQ(array->n_children, 1);
+
+  auto validity = reinterpret_cast<const uint8_t*>(array->children[0]->buffers[0]);
+  auto offsets = reinterpret_cast<const int32_t*>(array->children[0]->buffers[1]);
+  auto data_buffer = reinterpret_cast<const char*>(array->children[0]->buffers[2]);
+  ASSERT_NE(validity, nullptr);
+  ASSERT_NE(data_buffer, nullptr);
+
+  ASSERT_TRUE(ArrowBitGet(validity, 0));
+  ASSERT_TRUE(ArrowBitGet(validity, 1));
+  ASSERT_FALSE(ArrowBitGet(validity, 2));
+
+  ASSERT_EQ(offsets[0], 0);
+  ASSERT_EQ(offsets[1], 2);
+  ASSERT_EQ(offsets[2], 5);
+  ASSERT_EQ(offsets[3], 5);
+
+  ASSERT_EQ(std::string(data_buffer + 0, 2), "ok");
+  ASSERT_EQ(std::string(data_buffer + 2, 3), "sad");
+}
+
 TEST(PostgresCopyUtilsTest, PostgresCopyReadBinary) {
   ArrowBufferView data;
   data.data.as_uint8 = kTestPgCopyBinary;
@@ -564,19 +580,6 @@ TEST(PostgresCopyUtilsTest, PostgresCopyReadBinary) {
   ASSERT_EQ(data_buffer[7], 0xff);
 }
 
-// COPY (SELECT CAST("col" AS INTEGER ARRAY) AS "col" FROM (  VALUES ('{-123, -1}'), ('{0,
-// 1, 123}'), (NULL)) AS drvd("col")) TO STDOUT WITH (FORMAT binary);
-static uint8_t kTestPgCopyIntegerArray[] = {
-    0x50, 0x47, 0x43, 0x4f, 0x50, 0x59, 0x0a, 0xff, 0x0d, 0x0a, 0x00, 0x00, 0x00, 0x00,
-    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00,
-    0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x02, 0x00,
-    0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0xff, 0xff, 0xff, 0x85, 0x00, 0x00, 0x00,
-    0x04, 0xff, 0xff, 0xff, 0xff, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00,
-    0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x03, 0x00,
-    0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
-    0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x7b, 0x00,
-    0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
-
 TEST(PostgresCopyUtilsTest, PostgresCopyReadArray) {
   ArrowBufferView data;
   data.data.as_uint8 = kTestPgCopyIntegerArray;
@@ -623,19 +626,6 @@ TEST(PostgresCopyUtilsTest, PostgresCopyReadArray) {
   ASSERT_EQ(data_buffer[4], 123);
 }
 
-// CREATE TYPE custom_record AS (nested1 integer, nested2 double precision);
-// COPY (SELECT CAST("col" AS custom_record) AS "col" FROM (  VALUES ('(123, 456.789)'),
-// ('(12, 345.678)'), (NULL)) AS drvd("col")) TO STDOUT WITH (FORMAT binary);
-static uint8_t kTestPgCopyCustomRecord[] = {
-    0x50, 0x47, 0x43, 0x4f, 0x50, 0x59, 0x0a, 0xff, 0x0d, 0x0a, 0x00, 0x00, 0x00,
-    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x20, 0x00,
-    0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00,
-    0x00, 0x7b, 0x00, 0x00, 0x02, 0xbd, 0x00, 0x00, 0x00, 0x08, 0x40, 0x7c, 0x8c,
-    0x9f, 0xbe, 0x76, 0xc8, 0xb4, 0x00, 0x01, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00,
-    0x00, 0x02, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
-    0x0c, 0x00, 0x00, 0x02, 0xbd, 0x00, 0x00, 0x00, 0x08, 0x40, 0x75, 0x9a, 0xd9,
-    0x16, 0x87, 0x2b, 0x02, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
-
 TEST(PostgresCopyUtilsTest, PostgresCopyReadCustomRecord) {
   ArrowBufferView data;
   data.data.as_uint8 = kTestPgCopyCustomRecord;
diff --git a/c/driver/postgresql/copy/postgres_copy_test_common.h b/c/driver/postgresql/copy/postgres_copy_test_common.h
index 7f1e1487..55a96004 100644
--- a/c/driver/postgresql/copy/postgres_copy_test_common.h
+++ b/c/driver/postgresql/copy/postgres_copy_test_common.h
@@ -23,14 +23,14 @@ namespace adbcpq {
 
 // COPY (SELECT CAST("col" AS BOOLEAN) AS "col" FROM (  VALUES (TRUE), (FALSE), (NULL)) AS
 // drvd("col")) TO STDOUT;
-static uint8_t kTestPgCopyBoolean[] = {
+static const uint8_t kTestPgCopyBoolean[] = {
     0x50, 0x47, 0x43, 0x4f, 0x50, 0x59, 0x0a, 0xff, 0x0d, 0x0a, 0x00, 0x00, 0x00, 0x00,
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x01,
     0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
 
 // COPY (SELECT CAST("col" AS SMALLINT) AS "col" FROM (  VALUES (-123), (-1), (1), (123),
 // (NULL)) AS drvd("col")) TO STDOUT WITH (FORMAT binary);
-static uint8_t kTestPgCopySmallInt[] = {
+static const uint8_t kTestPgCopySmallInt[] = {
     0x50, 0x47, 0x43, 0x4f, 0x50, 0x59, 0x0a, 0xff, 0x0d, 0x0a, 0x00, 0x00,
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
     0x02, 0xff, 0x85, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0xff, 0xff, 0x00,
@@ -39,7 +39,7 @@ static uint8_t kTestPgCopySmallInt[] = {
 
 // COPY (SELECT CAST("col" AS INTEGER) AS "col" FROM (  VALUES (-123), (-1), (1), (123),
 // (NULL)) AS drvd("col")) TO STDOUT WITH (FORMAT binary);
-static uint8_t kTestPgCopyInteger[] = {
+static const uint8_t kTestPgCopyInteger[] = {
     0x50, 0x47, 0x43, 0x4f, 0x50, 0x59, 0x0a, 0xff, 0x0d, 0x0a, 0x00, 0x00, 0x00, 0x00,
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0xff, 0xff, 0xff,
     0x85, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0xff, 0xff, 0xff, 0xff, 0x00, 0x01, 0x00,
@@ -48,7 +48,7 @@ static uint8_t kTestPgCopyInteger[] = {
 
 // COPY (SELECT CAST("col" AS BIGINT) AS "col" FROM (  VALUES (-123), (-1), (1), (123),
 // (NULL)) AS drvd("col")) TO STDOUT WITH (FORMAT binary);
-static uint8_t kTestPgCopyBigInt[] = {
+static const uint8_t kTestPgCopyBigInt[] = {
     0x50, 0x47, 0x43, 0x4f, 0x50, 0x59, 0x0a, 0xff, 0x0d, 0x0a, 0x00, 0x00, 0x00, 0x00,
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0xff, 0xff, 0xff,
     0xff, 0xff, 0xff, 0xff, 0x85, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0xff, 0xff, 0xff,
@@ -58,7 +58,7 @@ static uint8_t kTestPgCopyBigInt[] = {
 
 // COPY (SELECT CAST("col" AS REAL) AS "col" FROM (  VALUES (-123.456), (-1), (1),
 // (123.456), (NULL)) AS drvd("col")) TO STDOUT WITH (FORMAT binary);
-static uint8_t kTestPgCopyReal[] = {
+static const uint8_t kTestPgCopyReal[] = {
     0x50, 0x47, 0x43, 0x4f, 0x50, 0x59, 0x0a, 0xff, 0x0d, 0x0a, 0x00, 0x00, 0x00, 0x00,
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0xc2, 0xf6, 0xe9,
     0x79, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0xbf, 0x80, 0x00, 0x00, 0x00, 0x01, 0x00,
@@ -67,7 +67,7 @@ static uint8_t kTestPgCopyReal[] = {
 
 // COPY (SELECT CAST("col" AS DOUBLE PRECISION) AS "col" FROM (  VALUES (-123.456), (-1),
 // (1), (123.456), (NULL)) AS drvd("col")) TO STDOUT WITH (FORMAT binary);
-static uint8_t kTestPgCopyDoublePrecision[] = {
+static const uint8_t kTestPgCopyDoublePrecision[] = {
     0x50, 0x47, 0x43, 0x4f, 0x50, 0x59, 0x0a, 0xff, 0x0d, 0x0a, 0x00, 0x00, 0x00, 0x00,
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0xc0, 0x5e, 0xdd,
     0x2f, 0x1a, 0x9f, 0xbe, 0x77, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0xbf, 0xf0, 0x00,
@@ -75,7 +75,7 @@ static uint8_t kTestPgCopyDoublePrecision[] = {
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x40, 0x5e, 0xdd,
     0x2f, 0x1a, 0x9f, 0xbe, 0x77, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
 
-static uint8_t kTestPgCopyDate[] = {
+static const uint8_t kTestPgCopyDate[] = {
     0x50, 0x47, 0x43, 0x4f, 0x50, 0x59, 0x0a, 0xff, 0x0d, 0x0a, 0x00, 0x00,
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
     0x04, 0xff, 0xff, 0x71, 0x54, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00,
@@ -83,7 +83,7 @@ static uint8_t kTestPgCopyDate[] = {
 
 // COPY (SELECT CAST(col AS TIMESTAMP) FROM (  VALUES ('1900-01-01 12:34:56'),
 // ('2100-01-01 12:34:56'), (NULL)) AS drvd("col")) TO STDOUT WITH (FORMAT BINARY);
-static uint8_t kTestPgCopyTimestamp[] = {
+static const uint8_t kTestPgCopyTimestamp[] = {
     0x50, 0x47, 0x43, 0x4f, 0x50, 0x59, 0x0a, 0xff, 0x0d, 0x0a, 0x00, 0x00, 0x00, 0x00,
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0xff, 0xf4, 0xc9,
     0xf9, 0x07, 0xe5, 0x9c, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x0b, 0x36,
@@ -91,7 +91,7 @@ static uint8_t kTestPgCopyTimestamp[] = {
 
 // COPY (SELECT CAST(col AS INTERVAL) FROM (  VALUES ('-1 months -2 days -4 seconds'),
 // ('1 months 2 days 4 seconds'), (NULL)) AS drvd("col")) TO STDOUT WITH (FORMAT BINARY);
-static uint8_t kTestPgCopyInterval[] = {
+static const uint8_t kTestPgCopyInterval[] = {
     0x50, 0x47, 0x43, 0x4f, 0x50, 0x59, 0x0a, 0xff, 0x0d, 0x0a, 0x00, 0x00,
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
     0x10, 0xff, 0xff, 0xff, 0xff, 0xff, 0xc2, 0xf7, 0x00, 0xff, 0xff, 0xff,
@@ -101,7 +101,7 @@ static uint8_t kTestPgCopyInterval[] = {
 
 // COPY (SELECT CAST("col" AS TEXT) AS "col" FROM (  VALUES ('abc'), ('1234'),
 // (NULL::text)) AS drvd("col")) TO STDOUT WITH (FORMAT binary);
-static uint8_t kTestPgCopyText[] = {
+static const uint8_t kTestPgCopyText[] = {
     0x50, 0x47, 0x43, 0x4f, 0x50, 0x59, 0x0a, 0xff, 0x0d, 0x0a, 0x00, 0x00,
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
     0x03, 0x61, 0x62, 0x63, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x31, 0x32,
@@ -110,11 +110,69 @@ static uint8_t kTestPgCopyText[] = {
 // COPY (SELECT CAST("col" AS BYTEA) AS "col" FROM (  VALUES (''), ('\x0001'),
 // ('\x01020304'), ('\xFEFF'), (NULL)) AS drvd("col")) TO STDOUT
 // WITH (FORMAT binary);
-static uint8_t kTestPgCopyBinary[] = {
+static const uint8_t kTestPgCopyBinary[] = {
     0x50, 0x47, 0x43, 0x4f, 0x50, 0x59, 0x0a, 0xff, 0x0d, 0x0a, 0x00, 0x00,
     0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00,
     0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 0x01, 0x00, 0x01, 0x00,
     0x00, 0x00, 0x04, 0x01, 0x02, 0x03, 0x04, 0x00, 0x01, 0x00, 0x00, 0x00,
     0x02, 0xfe, 0xff, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
 
+// CREATE TYPE mood AS ENUM ('sad', 'ok', 'happy');
+// COPY (SELECT CAST("col" AS TEXT) AS "col" FROM (  VALUES ('ok'), ('sad'), (NULL::text))
+// AS drvd("col")) TO STDOUT WITH (FORMAT binary);
+static const uint8_t kTestPgCopyEnum[] = {
+    0x50, 0x47, 0x43, 0x4f, 0x50, 0x59, 0x0a, 0xff, 0x0d, 0x0a, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
+    0x00, 0x00, 0x02, 0x6f, 0x6b, 0x00, 0x01, 0x00, 0x00, 0x00, 0x03,
+    0x73, 0x61, 0x64, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
+
+// COPY (SELECT CAST("col" AS INTEGER ARRAY) AS "col" FROM (  VALUES ('{-123, -1}'), ('{0,
+// 1, 123}'), (NULL)) AS drvd("col")) TO STDOUT WITH (FORMAT binary);
+static const uint8_t kTestPgCopyIntegerArray[] = {
+    0x50, 0x47, 0x43, 0x4f, 0x50, 0x59, 0x0a, 0xff, 0x0d, 0x0a, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x24, 0x00, 0x00, 0x00,
+    0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x02, 0x00,
+    0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0xff, 0xff, 0xff, 0x85, 0x00, 0x00, 0x00,
+    0x04, 0xff, 0xff, 0xff, 0xff, 0x00, 0x01, 0x00, 0x00, 0x00, 0x2c, 0x00, 0x00, 0x00,
+    0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x03, 0x00,
+    0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x04, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00, 0x7b, 0x00,
+    0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
+
+// CREATE TYPE custom_record AS (nested1 integer, nested2 double precision);
+// COPY (SELECT CAST("col" AS custom_record) AS "col" FROM (  VALUES ('(123, 456.789)'),
+// ('(12, 345.678)'), (NULL)) AS drvd("col")) TO STDOUT WITH (FORMAT binary);
+static const uint8_t kTestPgCopyCustomRecord[] = {
+    0x50, 0x47, 0x43, 0x4f, 0x50, 0x59, 0x0a, 0xff, 0x0d, 0x0a, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x20, 0x00,
+    0x00, 0x00, 0x02, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00,
+    0x00, 0x7b, 0x00, 0x00, 0x02, 0xbd, 0x00, 0x00, 0x00, 0x08, 0x40, 0x7c, 0x8c,
+    0x9f, 0xbe, 0x76, 0xc8, 0xb4, 0x00, 0x01, 0x00, 0x00, 0x00, 0x20, 0x00, 0x00,
+    0x00, 0x02, 0x00, 0x00, 0x00, 0x17, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+    0x0c, 0x00, 0x00, 0x02, 0xbd, 0x00, 0x00, 0x00, 0x08, 0x40, 0x75, 0x9a, 0xd9,
+    0x16, 0x87, 0x2b, 0x02, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
+
+// For full coverage, ensure that this contains NUMERIC examples that:
+// - Have >= four zeroes to the left of the decimal point
+// - Have >= four zeroes to the right of the decimal point
+// - Include special values (nan, -inf, inf, NULL)
+// - Have >= four trailing zeroes to the right of the decimal point
+// - Have >= four leading zeroes before the first digit to the right of the decimal point
+// - Is < 0 (negative)
+// COPY (SELECT CAST(col AS NUMERIC) AS col FROM (  VALUES (1000000), ('0.00001234'),
+// ('1.0000'), (-123.456), (123.456), ('nan'), ('-inf'), ('inf'), (NULL)) AS drvd(col)) TO
+// STDOUT WITH (FORMAT binary);
+static const uint8_t kTestPgCopyNumeric[] = {
+    0x50, 0x47, 0x43, 0x4f, 0x50, 0x59, 0x0a, 0xff, 0x0d, 0x0a, 0x00, 0x00, 0x00, 0x00,
+    0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0a, 0x00, 0x01, 0x00,
+    0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x64, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0a, 0x00,
+    0x01, 0xff, 0xfe, 0x00, 0x00, 0x00, 0x08, 0x04, 0xd2, 0x00, 0x01, 0x00, 0x00, 0x00,
+    0x0a, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x01, 0x00, 0x01, 0x00,
+    0x00, 0x00, 0x0c, 0x00, 0x02, 0x00, 0x00, 0x40, 0x00, 0x00, 0x03, 0x00, 0x7b, 0x11,
+    0xd0, 0x00, 0x01, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00,
+    0x03, 0x00, 0x7b, 0x11, 0xd0, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
+    0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
+    0x00, 0xf0, 0x00, 0x00, 0x20, 0x00, 0x01, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,
+    0x00, 0xd0, 0x00, 0x00, 0x20, 0x00, 0x01, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff};
+
 }  // namespace adbcpq
diff --git a/c/driver/postgresql/copy/reader.h b/c/driver/postgresql/copy/reader.h
index 0ab3f95e..49eb3089 100644
--- a/c/driver/postgresql/copy/reader.h
+++ b/c/driver/postgresql/copy/reader.h
@@ -769,6 +769,7 @@ static inline ArrowErrorCode MakeCopyFieldReader(const PostgresType& pg_type,
         case PostgresTypeId::kText:
         case PostgresTypeId::kBpchar:
         case PostgresTypeId::kName:
+        case PostgresTypeId::kEnum:
           *out = new PostgresCopyBinaryFieldReader();
           return NANOARROW_OK;
         case PostgresTypeId::kNumeric:
diff --git a/c/driver/postgresql/postgres_type.h b/c/driver/postgresql/postgres_type.h
index 9d5f2e64..e8de6e1b 100644
--- a/c/driver/postgresql/postgres_type.h
+++ b/c/driver/postgresql/postgres_type.h
@@ -50,6 +50,7 @@ enum class PostgresTypeId {
   kCstring,
   kDate,
   kDomain,
+  kEnum,
   kFloat4,
   kFloat8,
   kInet,
@@ -225,6 +226,7 @@ class PostgresType {
       case PostgresTypeId::kVarchar:
       case PostgresTypeId::kText:
       case PostgresTypeId::kName:
+      case PostgresTypeId::kEnum:
         NANOARROW_RETURN_NOT_OK(ArrowSchemaSetType(schema, NANOARROW_TYPE_STRING));
         break;
       case PostgresTypeId::kBytea:
@@ -572,6 +574,8 @@ static inline const char* PostgresTyprecv(PostgresTypeId type_id) {
       return "date_recv";
     case PostgresTypeId::kDomain:
       return "domain_recv";
+    case PostgresTypeId::kEnum:
+      return "enum_recv";
     case PostgresTypeId::kFloat4:
       return "float4recv";
     case PostgresTypeId::kFloat8:
@@ -737,6 +741,8 @@ static inline const char* PostgresTypname(PostgresTypeId type_id) {
       return "date";
     case PostgresTypeId::kDomain:
       return "domain";
+    case PostgresTypeId::kEnum:
+      return "enum";
     case PostgresTypeId::kFloat4:
       return "float4";
     case PostgresTypeId::kFloat8:
@@ -880,6 +886,7 @@ static inline std::vector<PostgresTypeId> PostgresTypeIdAll(bool nested) {
                                       PostgresTypeId::kCircle,
                                       PostgresTypeId::kCstring,
                                       PostgresTypeId::kDate,
+                                      PostgresTypeId::kEnum,
                                       PostgresTypeId::kFloat4,
                                       PostgresTypeId::kFloat8,
                                       PostgresTypeId::kInet,