From 37f8ecca0625a314346b9c5afdf7c804ec9724e6 Mon Sep 17 00:00:00 2001 From: Will Ayd Date: Fri, 7 Apr 2023 12:27:59 -0700 Subject: [PATCH 1/9] feat(c/driver/postgres): Implement GetTableSchema --- c/driver/postgresql/connection.cc | 93 +++++++++++++++++++++++++- c/driver/postgresql/postgresql_test.cc | 1 - 2 files changed, 92 insertions(+), 2 deletions(-) diff --git a/c/driver/postgresql/connection.cc b/c/driver/postgresql/connection.cc index 38cba57f17..3b628b195e 100644 --- a/c/driver/postgresql/connection.cc +++ b/c/driver/postgresql/connection.cc @@ -19,6 +19,7 @@ #include #include +#include #include @@ -47,7 +48,97 @@ AdbcStatusCode PostgresConnection::GetTableSchema(const char* catalog, const char* table_name, struct ArrowSchema* schema, struct AdbcError* error) { - return ADBC_STATUS_NOT_IMPLEMENTED; + // TODO: sqlite uses a StringBuilder class that seems roughly equivalent + // to a similar tool in arrow/util/string_builder.h but wasn't clear on + // structure and how to use, so relying on simplistic appends for now + AdbcStatusCode final_status; + + std::string query = + "SELECT attname, atttypid " + "FROM pg_catalog.pg_class AS cls " + "INNER JOIN pg_catalog.pg_attribute AS attr ON cls.oid = attr.attrelid " + "INNER JOIN pg_catalog.pg_type AS typ ON attr.atttypid = typ.oid " + "WHERE attr.attnum >= 0 AND cls.oid = '"; + if (db_schema != nullptr) { + query.append(db_schema); + query.append("."); + } + query.append(table_name); + query.append("'::regclass::oid"); + + // char* stmt = PQescapeLiteral(conn_, query.c_str(), query.length()); + // if (stmt == nullptr) { + // SetError(error, "Failed to get table schema: ", PQerrorMessage(conn_)); + // return ADBC_STATUS_INVALID_ARGUMENT; + // } + + pg_result* result = PQexec(conn_, query.c_str()); + // PQfreemem(stmt); + + ExecStatusType pq_status = PQresultStatus(result); + if (pq_status == PGRES_TUPLES_OK) { + int num_rows = PQntuples(result); + ArrowSchemaInit(schema); + CHECK_NA_ADBC(ArrowSchemaSetTypeStruct(schema, num_rows), error); + + // TODO: much of this code is copied from statement.cc InferSchema + for (int row = 0; row < num_rows; row++) { + ArrowType field_type = NANOARROW_TYPE_NA; + const char* colname = PQgetvalue(result, row, 0); + const uint32_t oid = static_cast( + std::strtol(PQgetvalue(result, row, 1), /*str_end=*/nullptr, /*base=*/10)); + + auto it = type_mapping_->type_mapping.find(oid); + if (it == type_mapping_->type_mapping.end()) { + SetError(error, "Column #", row + 1, " (\"", colname, + "\") has unknown type code ", oid); + return ADBC_STATUS_NOT_IMPLEMENTED; + } + + switch (it->second) { + // TODO: this mapping will eventually have to become dynamic, + // because of complex types like arrays/records + case PgType::kBool: + field_type = NANOARROW_TYPE_BOOL; + break; + case PgType::kFloat4: + field_type = NANOARROW_TYPE_FLOAT; + break; + case PgType::kFloat8: + field_type = NANOARROW_TYPE_DOUBLE; + break; + case PgType::kInt2: + field_type = NANOARROW_TYPE_INT16; + break; + case PgType::kInt4: + field_type = NANOARROW_TYPE_INT32; + break; + case PgType::kInt8: + field_type = NANOARROW_TYPE_INT64; + break; + case PgType::kVarBinary: + field_type = NANOARROW_TYPE_BINARY; + break; + case PgType::kText: + case PgType::kVarChar: + field_type = NANOARROW_TYPE_STRING; + break; + default: + SetError(error, "Column #", row + 1, " (\"", colname, + "\") has unimplemented type code ", oid); + return ADBC_STATUS_NOT_IMPLEMENTED; + } + CHECK_NA_ADBC(ArrowSchemaSetType(schema->children[row], field_type), error); + CHECK_NA_ADBC(ArrowSchemaSetName(schema->children[row], colname), error); + } + } else { + SetError(error, "Failed to get table schema: ", PQerrorMessage(conn_)); + final_status = ADBC_STATUS_IO; + } + PQclear(result); + + // TODO: Should we disconnect here? + return final_status; } AdbcStatusCode PostgresConnection::Init(struct AdbcDatabase* database, diff --git a/c/driver/postgresql/postgresql_test.cc b/c/driver/postgresql/postgresql_test.cc index 26d6f6d173..7148d6f267 100644 --- a/c/driver/postgresql/postgresql_test.cc +++ b/c/driver/postgresql/postgresql_test.cc @@ -86,7 +86,6 @@ class PostgresConnectionTest : public ::testing::Test, void TearDown() override { ASSERT_NO_FATAL_FAILURE(TearDownTest()); } void TestMetadataGetInfo() { GTEST_SKIP() << "Not yet implemented"; } - void TestMetadataGetTableSchema() { GTEST_SKIP() << "Not yet implemented"; } void TestMetadataGetTableTypes() { GTEST_SKIP() << "Not yet implemented"; } void TestMetadataGetObjectsCatalogs() { GTEST_SKIP() << "Not yet implemented"; } From f0fd0df5fe99bebe59a6b46d08fb9ec60f57c347 Mon Sep 17 00:00:00 2001 From: Will Ayd Date: Fri, 7 Apr 2023 12:35:06 -0700 Subject: [PATCH 2/9] Added database disconnect --- c/driver/postgresql/connection.cc | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/c/driver/postgresql/connection.cc b/c/driver/postgresql/connection.cc index 3b628b195e..ba48df1dec 100644 --- a/c/driver/postgresql/connection.cc +++ b/c/driver/postgresql/connection.cc @@ -137,7 +137,11 @@ AdbcStatusCode PostgresConnection::GetTableSchema(const char* catalog, } PQclear(result); - // TODO: Should we disconnect here? + // Disconnect since PostgreSQL connections can be heavy. + { + AdbcStatusCode status = database_->Disconnect(&conn_, error); + if (status != ADBC_STATUS_OK) final_status = status; + } return final_status; } From 4fe354d4b4d9053d49cf22acc5aca3a415e5433b Mon Sep 17 00:00:00 2001 From: Will Ayd Date: Thu, 27 Apr 2023 16:53:25 -0700 Subject: [PATCH 3/9] Updates with new PGType mapping --- c/driver/postgresql/connection.cc | 51 ++++++------------------------- 1 file changed, 9 insertions(+), 42 deletions(-) diff --git a/c/driver/postgresql/connection.cc b/c/driver/postgresql/connection.cc index 3f5ef7fb00..4ad1d1da99 100644 --- a/c/driver/postgresql/connection.cc +++ b/c/driver/postgresql/connection.cc @@ -51,7 +51,7 @@ AdbcStatusCode PostgresConnection::GetTableSchema(const char* catalog, // TODO: sqlite uses a StringBuilder class that seems roughly equivalent // to a similar tool in arrow/util/string_builder.h but wasn't clear on // structure and how to use, so relying on simplistic appends for now - AdbcStatusCode final_status; + AdbcStatusCode final_status = ADBC_STATUS_OK; std::string query = "SELECT attname, atttypid " @@ -81,55 +81,22 @@ AdbcStatusCode PostgresConnection::GetTableSchema(const char* catalog, ArrowSchemaInit(schema); CHECK_NA_ADBC(ArrowSchemaSetTypeStruct(schema, num_rows), error); - // TODO: much of this code is copied from statement.cc InferSchema + ArrowError na_error; for (int row = 0; row < num_rows; row++) { - ArrowType field_type = NANOARROW_TYPE_NA; const char* colname = PQgetvalue(result, row, 0); - const uint32_t oid = static_cast( + const Oid pg_oid = static_cast( std::strtol(PQgetvalue(result, row, 1), /*str_end=*/nullptr, /*base=*/10)); - auto it = type_mapping_->type_mapping.find(oid); - if (it == type_mapping_->type_mapping.end()) { + PostgresType pg_type; + if (type_resolver_->Find(pg_oid, &pg_type, &na_error) != NANOARROW_OK) { SetError(error, "Column #", row + 1, " (\"", colname, - "\") has unknown type code ", oid); + "\") has unknown type code ", pg_oid); return ADBC_STATUS_NOT_IMPLEMENTED; } - switch (it->second) { - // TODO: this mapping will eventually have to become dynamic, - // because of complex types like arrays/records - case PgType::kBool: - field_type = NANOARROW_TYPE_BOOL; - break; - case PgType::kFloat4: - field_type = NANOARROW_TYPE_FLOAT; - break; - case PgType::kFloat8: - field_type = NANOARROW_TYPE_DOUBLE; - break; - case PgType::kInt2: - field_type = NANOARROW_TYPE_INT16; - break; - case PgType::kInt4: - field_type = NANOARROW_TYPE_INT32; - break; - case PgType::kInt8: - field_type = NANOARROW_TYPE_INT64; - break; - case PgType::kVarBinary: - field_type = NANOARROW_TYPE_BINARY; - break; - case PgType::kText: - case PgType::kVarChar: - field_type = NANOARROW_TYPE_STRING; - break; - default: - SetError(error, "Column #", row + 1, " (\"", colname, - "\") has unimplemented type code ", oid); - return ADBC_STATUS_NOT_IMPLEMENTED; - } - CHECK_NA_ADBC(ArrowSchemaSetType(schema->children[row], field_type), error); - CHECK_NA_ADBC(ArrowSchemaSetName(schema->children[row], colname), error); + CHECK_NA_ADBC( + pg_type.WithFieldName(PQfname(result, row)).SetSchema(schema->children[row]), + error); } } else { SetError(error, "Failed to get table schema: ", PQerrorMessage(conn_)); From 9bc70a35c176af6fadf609f0bd2db8167a4d90d7 Mon Sep 17 00:00:00 2001 From: Will Ayd Date: Fri, 28 Apr 2023 10:54:46 -0700 Subject: [PATCH 4/9] Fix name issue --- c/driver/postgresql/connection.cc | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/c/driver/postgresql/connection.cc b/c/driver/postgresql/connection.cc index 4ad1d1da99..c3177a4717 100644 --- a/c/driver/postgresql/connection.cc +++ b/c/driver/postgresql/connection.cc @@ -94,9 +94,8 @@ AdbcStatusCode PostgresConnection::GetTableSchema(const char* catalog, return ADBC_STATUS_NOT_IMPLEMENTED; } - CHECK_NA_ADBC( - pg_type.WithFieldName(PQfname(result, row)).SetSchema(schema->children[row]), - error); + CHECK_NA_ADBC(pg_type.WithFieldName(colname).SetSchema(schema->children[row]), + error); } } else { SetError(error, "Failed to get table schema: ", PQerrorMessage(conn_)); From 3d3c2d9c655a17c7aff138c5fb8acd7dd0c84d6f Mon Sep 17 00:00:00 2001 From: Will Ayd Date: Tue, 2 May 2023 12:33:25 -0700 Subject: [PATCH 5/9] Updates after merge with main --- c/driver/postgresql/connection.cc | 44 +++++++++++++++++-------------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/c/driver/postgresql/connection.cc b/c/driver/postgresql/connection.cc index a5977824e4..53e47c62e7 100644 --- a/c/driver/postgresql/connection.cc +++ b/c/driver/postgresql/connection.cc @@ -17,9 +17,9 @@ #include "connection.h" +#include #include #include -#include #include @@ -48,23 +48,26 @@ AdbcStatusCode PostgresConnection::GetTableSchema(const char* catalog, const char* table_name, struct ArrowSchema* schema, struct AdbcError* error) { - // TODO: sqlite uses a StringBuilder class that seems roughly equivalent - // to a similar tool in arrow/util/string_builder.h but wasn't clear on - // structure and how to use, so relying on simplistic appends for now AdbcStatusCode final_status = ADBC_STATUS_OK; + struct StringBuilder query = {0}; + if (!StringBuilderInit(&query, /*initial_size=*/256)) return ADBC_STATUS_INTERNAL; + + if (!StringBuilderAppend( + &query, "%s", + "SELECT attname, atttypid " + "FROM pg_catalog.pg_class AS cls " + "INNER JOIN pg_catalog.pg_attribute AS attr ON cls.oid = attr.attrelid " + "INNER JOIN pg_catalog.pg_type AS typ ON attr.atttypid = typ.oid " + "WHERE attr.attnum >= 0 AND cls.oid = '")) + return ADBC_STATUS_INTERNAL; - std::string query = - "SELECT attname, atttypid " - "FROM pg_catalog.pg_class AS cls " - "INNER JOIN pg_catalog.pg_attribute AS attr ON cls.oid = attr.attrelid " - "INNER JOIN pg_catalog.pg_type AS typ ON attr.atttypid = typ.oid " - "WHERE attr.attnum >= 0 AND cls.oid = '"; if (db_schema != nullptr) { - query.append(db_schema); - query.append("."); + if (!StringBuilderAppend(&query, "%s%s", db_schema, ".")) + return ADBC_STATUS_INVALID_ARGUMENT; } - query.append(table_name); - query.append("'::regclass::oid"); + + if (!StringBuilderAppend(&query, "%s%s", table_name, "'::regclass::oid")) + return ADBC_STATUS_INVALID_ARGUMENT; // char* stmt = PQescapeLiteral(conn_, query.c_str(), query.length()); // if (stmt == nullptr) { @@ -72,14 +75,15 @@ AdbcStatusCode PostgresConnection::GetTableSchema(const char* catalog, // return ADBC_STATUS_INVALID_ARGUMENT; // } - pg_result* result = PQexec(conn_, query.c_str()); + pg_result* result = PQexec(conn_, query.buffer); + StringBuilderReset(&query); // PQfreemem(stmt); ExecStatusType pq_status = PQresultStatus(result); if (pq_status == PGRES_TUPLES_OK) { int num_rows = PQntuples(result); ArrowSchemaInit(schema); - CHECK_NA_ADBC(ArrowSchemaSetTypeStruct(schema, num_rows), error); + CHECK_NA(INTERNAL, ArrowSchemaSetTypeStruct(schema, num_rows), error); ArrowError na_error; for (int row = 0; row < num_rows; row++) { @@ -89,16 +93,16 @@ AdbcStatusCode PostgresConnection::GetTableSchema(const char* catalog, PostgresType pg_type; if (type_resolver_->Find(pg_oid, &pg_type, &na_error) != NANOARROW_OK) { - SetError(error, "Column #", row + 1, " (\"", colname, + SetError(error, "%s%d%s%s%s%" PRIu32, "Column #", row + 1, " (\"", colname, "\") has unknown type code ", pg_oid); return ADBC_STATUS_NOT_IMPLEMENTED; } - CHECK_NA_ADBC(pg_type.WithFieldName(colname).SetSchema(schema->children[row]), - error); + CHECK_NA(INTERNAL, pg_type.WithFieldName(colname).SetSchema(schema->children[row]), + error); } } else { - SetError(error, "Failed to get table schema: ", PQerrorMessage(conn_)); + SetError(error, "%s%s", "Failed to get table schema: ", PQerrorMessage(conn_)); final_status = ADBC_STATUS_IO; } PQclear(result); From 0eb06016b38d61608306acd816bbf26cf20c332c Mon Sep 17 00:00:00 2001 From: Will Ayd Date: Tue, 2 May 2023 13:41:15 -0700 Subject: [PATCH 6/9] Working tests with escaped identifiers --- c/driver/postgresql/connection.cc | 32 ++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/c/driver/postgresql/connection.cc b/c/driver/postgresql/connection.cc index 53e47c62e7..e127202cc7 100644 --- a/c/driver/postgresql/connection.cc +++ b/c/driver/postgresql/connection.cc @@ -22,6 +22,7 @@ #include #include +#include #include "database.h" #include "utils.h" @@ -50,34 +51,43 @@ AdbcStatusCode PostgresConnection::GetTableSchema(const char* catalog, struct AdbcError* error) { AdbcStatusCode final_status = ADBC_STATUS_OK; struct StringBuilder query = {0}; - if (!StringBuilderInit(&query, /*initial_size=*/256)) return ADBC_STATUS_INTERNAL; + if (StringBuilderInit(&query, /*initial_size=*/256) != 0) return ADBC_STATUS_INTERNAL; - if (!StringBuilderAppend( + if (StringBuilderAppend( &query, "%s", "SELECT attname, atttypid " "FROM pg_catalog.pg_class AS cls " "INNER JOIN pg_catalog.pg_attribute AS attr ON cls.oid = attr.attrelid " "INNER JOIN pg_catalog.pg_type AS typ ON attr.atttypid = typ.oid " - "WHERE attr.attnum >= 0 AND cls.oid = '")) + "WHERE attr.attnum >= 0 AND cls.oid = '") != 0) return ADBC_STATUS_INTERNAL; if (db_schema != nullptr) { - if (!StringBuilderAppend(&query, "%s%s", db_schema, ".")) + char* schema = PQescapeIdentifier(conn_, db_schema, strlen(db_schema)); + if (schema == NULL) { + SetError(error, "%s%s", "Faled to escape schema: ", PQerrorMessage(conn_)); return ADBC_STATUS_INVALID_ARGUMENT; + } + + int ret = StringBuilderAppend(&query, "%s%s", schema, "."); + PQfreemem(schema); + + if (ret != 0) return ADBC_STATUS_INTERNAL; } - if (!StringBuilderAppend(&query, "%s%s", table_name, "'::regclass::oid")) + char* table = PQescapeIdentifier(conn_, table_name, strlen(table_name)); + if (table == NULL) { + SetError(error, "%s%s", "Failed to escape table: ", PQerrorMessage(conn_)); return ADBC_STATUS_INVALID_ARGUMENT; + } + + int ret = StringBuilderAppend(&query, "%s%s", table_name, "'::regclass::oid"); + PQfreemem(table); - // char* stmt = PQescapeLiteral(conn_, query.c_str(), query.length()); - // if (stmt == nullptr) { - // SetError(error, "Failed to get table schema: ", PQerrorMessage(conn_)); - // return ADBC_STATUS_INVALID_ARGUMENT; - // } + if (ret != 0) return ADBC_STATUS_INTERNAL; pg_result* result = PQexec(conn_, query.buffer); StringBuilderReset(&query); - // PQfreemem(stmt); ExecStatusType pq_status = PQresultStatus(result); if (pq_status == PGRES_TUPLES_OK) { From 48952736261c090b0921cb6fa300b1c00f0ff9d0 Mon Sep 17 00:00:00 2001 From: Will Ayd Date: Wed, 3 May 2023 08:48:25 -0700 Subject: [PATCH 7/9] Remove disconnect, ensure PQClear on result --- c/driver/postgresql/connection.cc | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/c/driver/postgresql/connection.cc b/c/driver/postgresql/connection.cc index e127202cc7..f0ae324e6c 100644 --- a/c/driver/postgresql/connection.cc +++ b/c/driver/postgresql/connection.cc @@ -105,7 +105,8 @@ AdbcStatusCode PostgresConnection::GetTableSchema(const char* catalog, if (type_resolver_->Find(pg_oid, &pg_type, &na_error) != NANOARROW_OK) { SetError(error, "%s%d%s%s%s%" PRIu32, "Column #", row + 1, " (\"", colname, "\") has unknown type code ", pg_oid); - return ADBC_STATUS_NOT_IMPLEMENTED; + final_status = ADBC_STATUS_NOT_IMPLEMENTED; + break; } CHECK_NA(INTERNAL, pg_type.WithFieldName(colname).SetSchema(schema->children[row]), @@ -117,11 +118,6 @@ AdbcStatusCode PostgresConnection::GetTableSchema(const char* catalog, } PQclear(result); - // Disconnect since PostgreSQL connections can be heavy. - { - AdbcStatusCode status = database_->Disconnect(&conn_, error); - if (status != ADBC_STATUS_OK) final_status = status; - } return final_status; } From a25058d248f7d82a5a2dd2558fb9316082548a72 Mon Sep 17 00:00:00 2001 From: Will Ayd Date: Wed, 3 May 2023 09:25:08 -0700 Subject: [PATCH 8/9] Add PgResultHelper class --- c/driver/postgresql/connection.cc | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/c/driver/postgresql/connection.cc b/c/driver/postgresql/connection.cc index f0ae324e6c..cc8cccbf8b 100644 --- a/c/driver/postgresql/connection.cc +++ b/c/driver/postgresql/connection.cc @@ -20,6 +20,7 @@ #include #include #include +#include #include #include @@ -28,6 +29,27 @@ #include "utils.h" namespace adbcpq { + +class PqResultHelper { + public: + PqResultHelper(PGconn* conn, const char* query) : conn_(conn) { + query_ = std::string(query); + } + pg_result* Execute() { + result_ = PQexec(conn_, query_.c_str()); + return result_; + } + + ~PqResultHelper() { + if (result_ != nullptr) PQclear(result_); + } + + private: + pg_result* result_ = nullptr; + PGconn* conn_; + std::string query_; +}; + AdbcStatusCode PostgresConnection::Commit(struct AdbcError* error) { if (autocommit_) { SetError(error, "%s", "[libpq] Cannot commit when autocommit is enabled"); @@ -86,8 +108,9 @@ AdbcStatusCode PostgresConnection::GetTableSchema(const char* catalog, if (ret != 0) return ADBC_STATUS_INTERNAL; - pg_result* result = PQexec(conn_, query.buffer); + PqResultHelper result_helper = PqResultHelper{conn_, query.buffer}; StringBuilderReset(&query); + pg_result* result = result_helper.Execute(); ExecStatusType pq_status = PQresultStatus(result); if (pq_status == PGRES_TUPLES_OK) { @@ -116,7 +139,6 @@ AdbcStatusCode PostgresConnection::GetTableSchema(const char* catalog, SetError(error, "%s%s", "Failed to get table schema: ", PQerrorMessage(conn_)); final_status = ADBC_STATUS_IO; } - PQclear(result); return final_status; } From 202862fa8959b038a284597f7e1dc20ec2c14bf4 Mon Sep 17 00:00:00 2001 From: Will Ayd Date: Thu, 4 May 2023 14:15:45 -0700 Subject: [PATCH 9/9] implement feedback --- c/driver/postgresql/connection.cc | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/c/driver/postgresql/connection.cc b/c/driver/postgresql/connection.cc index cc8cccbf8b..227c7cb482 100644 --- a/c/driver/postgresql/connection.cc +++ b/c/driver/postgresql/connection.cc @@ -28,8 +28,7 @@ #include "database.h" #include "utils.h" -namespace adbcpq { - +namespace { class PqResultHelper { public: PqResultHelper(PGconn* conn, const char* query) : conn_(conn) { @@ -49,6 +48,9 @@ class PqResultHelper { PGconn* conn_; std::string query_; }; +} // namespace + +namespace adbcpq { AdbcStatusCode PostgresConnection::Commit(struct AdbcError* error) { if (autocommit_) { @@ -113,10 +115,12 @@ AdbcStatusCode PostgresConnection::GetTableSchema(const char* catalog, pg_result* result = result_helper.Execute(); ExecStatusType pq_status = PQresultStatus(result); + auto uschema = nanoarrow::UniqueSchema(); + if (pq_status == PGRES_TUPLES_OK) { int num_rows = PQntuples(result); - ArrowSchemaInit(schema); - CHECK_NA(INTERNAL, ArrowSchemaSetTypeStruct(schema, num_rows), error); + ArrowSchemaInit(uschema.get()); + CHECK_NA(INTERNAL, ArrowSchemaSetTypeStruct(uschema.get(), num_rows), error); ArrowError na_error; for (int row = 0; row < num_rows; row++) { @@ -132,7 +136,7 @@ AdbcStatusCode PostgresConnection::GetTableSchema(const char* catalog, break; } - CHECK_NA(INTERNAL, pg_type.WithFieldName(colname).SetSchema(schema->children[row]), + CHECK_NA(INTERNAL, pg_type.WithFieldName(colname).SetSchema(uschema->children[row]), error); } } else { @@ -140,6 +144,7 @@ AdbcStatusCode PostgresConnection::GetTableSchema(const char* catalog, final_status = ADBC_STATUS_IO; } + uschema.move(schema); return final_status; }