Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions c/driver/postgresql/postgres_type.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

#include <cerrno>
#include <cstdint>
#include <optional>
#include <string>
#include <unordered_map>
#include <utility>
Expand Down Expand Up @@ -166,6 +167,12 @@ class PostgresType {
return out;
}

PostgresType WithTypeModifier(int32_t typmod) const {
PostgresType out(*this);
out.typmod_ = typmod;
return out;
}

PostgresType Array(uint32_t oid = 0, const std::string& typname = "") const {
PostgresType out(PostgresTypeId::kArray);
out.AppendChild("item", *this);
Expand Down Expand Up @@ -371,11 +378,13 @@ class PostgresType {
uint32_t oid_;
PostgresTypeId type_id_;
std::string typname_;
std::optional<int32_t> typmod_;
std::string field_name_;
std::vector<PostgresType> children_;

static constexpr const char* kPostgresTypeKey = "ADBC:postgresql:typname";
static constexpr const char* kTypeKey = "POSTGRESQL:type";
static constexpr const char* kTypeModifierKey = "POSTGRESQL:typmod";
static constexpr const char* kExtensionName = "ARROW:extension:name";
static constexpr const char* kOpaqueExtensionName = "arrow.opaque";
static constexpr const char* kJsonExtensionName = "arrow.json";
Expand All @@ -389,6 +398,11 @@ class PostgresType {
NANOARROW_RETURN_NOT_OK(ArrowMetadataBuilderInit(buffer.get(), schema->metadata));
NANOARROW_RETURN_NOT_OK(ArrowMetadataBuilderAppend(
buffer.get(), ArrowCharView(kTypeKey), ArrowCharView(typname)));
if (type_id_ == PostgresTypeId::kNumeric && typmod_.has_value()) {
const std::string typmod = std::to_string(*typmod_);
NANOARROW_RETURN_NOT_OK(ArrowMetadataBuilderAppend(
buffer.get(), ArrowCharView(kTypeModifierKey), ArrowCharView(typmod.c_str())));
}
NANOARROW_RETURN_NOT_OK(
ArrowSchemaSetMetadata(schema, reinterpret_cast<char*>(buffer->data)));
return NANOARROW_OK;
Expand Down
33 changes: 33 additions & 0 deletions c/driver/postgresql/postgres_type_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,39 @@

namespace adbcpq {

TEST(PostgresTypeTest, NumericTypeModifier) {
for (const int32_t typmod : {-1, 1900557, 329729, 131080}) {
SCOPED_TRACE(typmod);
nanoarrow::UniqueSchema schema;
ArrowSchemaInit(schema.get());
ASSERT_EQ(PostgresType(PostgresTypeId::kNumeric)
.WithTypeModifier(typmod)
.WithFieldName("amount")
.SetSchema(schema.get()),
NANOARROW_OK);
EXPECT_STREQ(schema->format, "u");
EXPECT_STREQ(schema->name, "amount");
ArrowStringView value = ArrowCharView("");
ASSERT_EQ(ArrowMetadataGetValue(schema->metadata, ArrowCharView("POSTGRESQL:typmod"),
&value),
NANOARROW_OK);
EXPECT_EQ(std::string(value.data, value.size_bytes), std::to_string(typmod));
ASSERT_EQ(ArrowMetadataGetValue(schema->metadata,
ArrowCharView("ARROW:extension:name"), &value),
NANOARROW_OK);
EXPECT_EQ(std::string(value.data, value.size_bytes), "arrow.opaque");
}

nanoarrow::UniqueSchema schema;
ArrowSchemaInit(schema.get());
ASSERT_EQ(PostgresType(PostgresTypeId::kNumeric).SetSchema(schema.get()), NANOARROW_OK);
ArrowStringView value = {nullptr, 0};
ASSERT_EQ(
ArrowMetadataGetValue(schema->metadata, ArrowCharView("POSTGRESQL:typmod"), &value),
NANOARROW_OK);
EXPECT_EQ(value.data, nullptr);
}

class MockTypeResolver : public PostgresTypeResolver {
public:
ArrowErrorCode Init() {
Expand Down
55 changes: 55 additions & 0 deletions c/driver/postgresql/postgresql_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2042,6 +2042,61 @@ TEST_F(PostgresStatementTest, ExecuteSchemaParameterizedQuery) {
ASSERT_THAT(AdbcStatementRelease(&statement, &error), IsOkStatus(&error));
}

TEST_F(PostgresStatementTest, NumericTypeModifier) {
ASSERT_THAT(AdbcStatementNew(&connection, &statement, &error), IsOkStatus(&error));
for (const char* use_copy : {"true", "false"}) {
SCOPED_TRACE(use_copy);
ASSERT_THAT(
AdbcStatementSetOption(&statement, "adbc.postgresql.use_copy", use_copy, &error),
IsOkStatus(&error));
for (const char* predicate : {"true", "false"}) {
SCOPED_TRACE(predicate);
const std::string query =
"SELECT 1.25::numeric(29,9), 1.25::numeric, NULL::numeric(29,9), "
"1.25::numeric(29,9) + 1.25::numeric(29,9), 1::integer WHERE "s +
predicate;
ASSERT_THAT(AdbcStatementSetSqlQuery(&statement, query.c_str(), &error),
IsOkStatus(&error));
nanoarrow::UniqueSchema described;
ASSERT_THAT(AdbcStatementExecuteSchema(&statement, described.get(), &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());
for (const ArrowSchema* schema : {described.get(), &reader.schema.value}) {
ASSERT_EQ(schema->n_children, 5);
for (int i = 0; i < 5; i++) {
ArrowStringView value = {nullptr, 0};
ASSERT_EQ(ArrowMetadataGetValue(schema->children[i]->metadata,
ArrowCharView("POSTGRESQL:typmod"), &value),
NANOARROW_OK);
if (i == 4) {
EXPECT_EQ(value.data, nullptr);
} else {
EXPECT_STREQ(schema->children[i]->format, "u");
EXPECT_EQ(std::string(value.data, value.size_bytes),
(i == 0 || i == 2) ? "1900557" : "-1");
}
}
}
ASSERT_NO_FATAL_FAILURE(reader.Next());
if (std::string(predicate) == "true") {
ASSERT_NE(reader.array->release, nullptr);
ASSERT_NO_FATAL_FAILURE(adbc_validation::CompareArray<std::string>(
reader.array_view->children[0], {"1.250000000"}));
ASSERT_NO_FATAL_FAILURE(adbc_validation::CompareArray<std::string>(
reader.array_view->children[1], {"1.25"}));
ASSERT_NO_FATAL_FAILURE(adbc_validation::CompareArray<std::string>(
reader.array_view->children[2], {std::nullopt}));
ASSERT_NO_FATAL_FAILURE(reader.Next());
}
ASSERT_EQ(reader.array->release, nullptr);
}
}
}

TEST_F(PostgresStatementTest, ExecuteParameterizedQueryWithResult) {
nanoarrow::UniqueSchema schema_bind;
ArrowSchemaInit(schema_bind.get());
Expand Down
3 changes: 2 additions & 1 deletion c/driver/postgresql/result_helper.cc
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,8 @@ Status PqResultHelper::ResolveOutputTypes(PostgresTypeResolver& type_resolver,
pg_type = PostgresType::Unnamed(pg_oid);
}

root_type.AppendChild(PQfname(result_, i), pg_type);
root_type.AppendChild(PQfname(result_, i),
pg_type.WithTypeModifier(PQfmod(result_, i)));
}

*result_types = root_type;
Expand Down
3 changes: 3 additions & 0 deletions c/driver/postgresql/result_helper.h
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,9 @@ class PqResultHelper {
return PQfname(result_, column_number);
}
Oid FieldType(int column_number) const { return PQftype(result_, column_number); }
int FieldTypeModifier(int column_number) const {
return PQfmod(result_, column_number);
}
PqResultRow Row(int i) const { return PqResultRow(result_, i); }

class iterator {
Expand Down
3 changes: 2 additions & 1 deletion c/driver/postgresql/result_reader.cc
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,8 @@ Status PqResultArrayReader::Initialize(int64_t* rows_affected) {
UNWRAP_ERRNO(Internal,
type_resolver_->FindWithDefault(helper_.FieldType(i), &child_type));

child_type = child_type.WithFieldName(helper_.FieldName(i));
child_type = child_type.WithFieldName(helper_.FieldName(i))
.WithTypeModifier(helper_.FieldTypeModifier(i));
UNWRAP_ERRNO(Internal, child_type.SetSchema(schema_->children[i], vendor_name_));

std::unique_ptr<PostgresCopyFieldReader> child_reader;
Expand Down
16 changes: 16 additions & 0 deletions docs/source/driver/postgresql.rst

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you clean up this prose? Stuff like "It does not add modifiers to parameter schemas or catalog discovery." is rather unnecessary; I don't need a full log of the AI's thought process baked into the docs.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd rather have just a section briefly listing the metadata keys we attach and describing each one.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry yeah there's a bunch of slop in here, I wasn't sure this would even come to the attention of folks so I hadn't looked over it closely yet. I'll go back through it as soon as I get some time to return to it.

Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,22 @@ being read or written.
overflow/underflow; an error will be returned if this would be
the case.

Result Field Metadata
~~~~~~~~~~~~~~~~~~~~~

The driver attaches the following PostgreSQL metadata to Arrow result fields:

``POSTGRESQL:type``
The PostgreSQL type name, such as ``int4`` or ``numeric``.

``POSTGRESQL:typmod``
The type modifier for a top-level numeric column, returned by
`PQfmod <https://www.postgresql.org/docs/18/libpq-exec.html#LIBPQ-PQFMOD>`_,
as a signed decimal string. Encodes precision and scale using
PostgreSQL's `numeric typmod encoding
<https://github.com/postgres/postgres/blob/REL_18_0/src/backend/utils/adt/numeric.c#L891-L947>`_.
Value is ``-1`` if no modifier is available.

Unknown Types
~~~~~~~~~~~~~

Expand Down
Loading