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 a88614f4f fix(c/driver/postgresql): honor GetObjects schema filter
(#3855)
a88614f4f is described below
commit a88614f4fa9515cc44240fcaf1ba3d75132b242d
Author: Matt Corley <[email protected]>
AuthorDate: Mon Jan 5 18:52:51 2026 -0800
fix(c/driver/postgresql): honor GetObjects schema filter (#3855)
## Summary
Fix the PostgreSQL driver’s `GetObjects` table enumeration so
`db_schema` / `db_schema_filter` does not depend on `search_path`.
## Details
The tables query used `pg_catalog.pg_table_is_visible(c.oid)`, which is
`search_path`-dependent and can hide tables in non-current schemas even
when a schema filter is provided. Remove that predicate and rely on the
explicit schema predicate.
## Tests
- `pre-commit run`
- `ctest -L driver-postgresql` (against `postgres-test` from `docker
compose`)
Closes #3854
---------
Co-authored-by: Matthew Corley <[email protected]>
Co-authored-by: David Li <[email protected]>
---
c/driver/postgresql/connection.cc | 4 +-
c/driver/postgresql/postgresql_test.cc | 55 +++++++++++++++++++++++
python/adbc_driver_postgresql/tests/test_dbapi.py | 42 +++++++++++++++++
3 files changed, 100 insertions(+), 1 deletion(-)
diff --git a/c/driver/postgresql/connection.cc
b/c/driver/postgresql/connection.cc
index cf4dea157..7738a44fa 100644
--- a/c/driver/postgresql/connection.cc
+++ b/c/driver/postgresql/connection.cc
@@ -72,13 +72,15 @@ static const char* kSchemaQueryAll =
// Parameterized on schema_name, relkind
// Note that when binding relkind as a string it must look like {"r", "v", ...}
// (i.e., double quotes). Binding a binary list<string> element also works.
+// Don't use pg_table_is_visible(): it is search_path-dependent and would hide
tables
+// in non-current schemas even when GetObjects is called with a schema filter.
static const char* kTablesQueryAll =
"SELECT c.relname, CASE c.relkind WHEN 'r' THEN 'table' WHEN 'v' THEN
'view' "
"WHEN 'm' THEN 'materialized view' WHEN 't' THEN 'TOAST table' "
"WHEN 'f' THEN 'foreign table' WHEN 'p' THEN 'partitioned table' END "
"AS reltype FROM pg_catalog.pg_class c "
"LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace "
- "WHERE pg_catalog.pg_table_is_visible(c.oid) AND n.nspname = $1 AND
c.relkind = "
+ "WHERE n.nspname = $1 AND c.relkind = "
"ANY($2)";
// Parameterized on schema_name, table_name
diff --git a/c/driver/postgresql/postgresql_test.cc
b/c/driver/postgresql/postgresql_test.cc
index 5eca504da..d457a14a0 100644
--- a/c/driver/postgresql/postgresql_test.cc
+++ b/c/driver/postgresql/postgresql_test.cc
@@ -372,6 +372,61 @@ TEST_F(PostgresConnectionTest, GetObjectsGetDbSchemas) {
ASSERT_NE(schema, nullptr) << "schema public not found";
}
+TEST_F(PostgresConnectionTest,
GetObjectsSchemaFilterFindsTablesOutsideSearchPath) {
+ ASSERT_THAT(AdbcConnectionNew(&connection, &error), IsOkStatus(&error));
+ ASSERT_THAT(AdbcConnectionInit(&connection, &database, &error),
IsOkStatus(&error));
+
+ const std::string schema_name = "adbc_get_objects_test";
+ const std::string table_name = "schema_filter_table";
+
+ // Ensure the schema is not part of the current search_path.
+ ASSERT_THAT(
+ AdbcConnectionSetOption(&connection,
ADBC_CONNECTION_OPTION_CURRENT_DB_SCHEMA,
+ "public", &error),
+ IsOkStatus(&error));
+
+ ASSERT_THAT(quirks()->EnsureDbSchema(&connection, schema_name, &error),
+ IsOkStatus(&error));
+ ASSERT_THAT(quirks()->DropTable(&connection, table_name, schema_name,
&error),
+ IsOkStatus(&error));
+
+ {
+ adbc_validation::Handle<struct AdbcStatement> statement;
+ ASSERT_THAT(AdbcStatementNew(&connection, &statement.value, &error),
+ IsOkStatus(&error));
+
+ std::string create =
+ "CREATE TABLE \"" + schema_name + "\".\"" + table_name + "\" (ints
INT)";
+ ASSERT_THAT(AdbcStatementSetSqlQuery(&statement.value, create.c_str(),
&error),
+ IsOkStatus(&error));
+ ASSERT_THAT(AdbcStatementExecuteQuery(&statement.value, nullptr, nullptr,
&error),
+ IsOkStatus(&error));
+ }
+
+ adbc_validation::StreamReader reader;
+ ASSERT_THAT(AdbcConnectionGetObjects(&connection, ADBC_OBJECT_DEPTH_TABLES,
nullptr,
+ schema_name.c_str(), nullptr, nullptr,
nullptr,
+ &reader.stream.value, &error),
+ IsOkStatus(&error));
+ ASSERT_NO_FATAL_FAILURE(reader.GetSchema());
+ ASSERT_NO_FATAL_FAILURE(reader.Next());
+ ASSERT_NE(nullptr, reader.array->release);
+ ASSERT_GT(reader.array->length, 0);
+
+ auto get_objects_data =
adbc_validation::GetObjectsReader{&reader.array_view.value};
+ ASSERT_NE(*get_objects_data, nullptr)
+ << "could not initialize the AdbcGetObjectsData object";
+
+ const auto catalog = adbc_validation::ConnectionGetOption(
+ &connection, ADBC_CONNECTION_OPTION_CURRENT_CATALOG, &error);
+ ASSERT_TRUE(catalog.has_value());
+
+ struct AdbcGetObjectsTable* table = InternalAdbcGetObjectsDataGetTableByName(
+ *get_objects_data, catalog->c_str(), schema_name.c_str(),
table_name.c_str());
+ ASSERT_NE(table, nullptr) << "could not find " << schema_name << "." <<
table_name
+ << " via GetObjects";
+}
+
TEST_F(PostgresConnectionTest, GetObjectsGetAllFindsPrimaryKey) {
ASSERT_THAT(AdbcConnectionNew(&connection, &error), IsOkStatus(&error));
ASSERT_THAT(AdbcConnectionInit(&connection, &database, &error),
IsOkStatus(&error));
diff --git a/python/adbc_driver_postgresql/tests/test_dbapi.py
b/python/adbc_driver_postgresql/tests/test_dbapi.py
index 0b25ef923..5fe72cfa3 100644
--- a/python/adbc_driver_postgresql/tests/test_dbapi.py
+++ b/python/adbc_driver_postgresql/tests/test_dbapi.py
@@ -53,6 +53,48 @@ def test_conn_change_db_schema(postgres: dbapi.Connection)
-> None:
assert postgres.adbc_current_db_schema == "dbapischema"
+def test_get_objects_schema_filter_outside_search_path(
+ postgres: dbapi.Connection,
+) -> None:
+ schema_name = "dbapi_get_objects_test"
+ table_name = "schema_filter_table"
+
+ # Regression test: adbc_get_objects(db_schema_filter=...) should not
depend on the
+ # connection's current schema/search_path.
+ assert postgres.adbc_current_db_schema == "public"
+
+ with postgres.cursor() as cur:
+ cur.execute(f'CREATE SCHEMA IF NOT EXISTS "{schema_name}"')
+ cur.execute(f'DROP TABLE IF EXISTS "{schema_name}"."{table_name}"')
+ cur.execute(f'CREATE TABLE "{schema_name}"."{table_name}" (ints INT)')
+ postgres.commit()
+
+ assert postgres.adbc_current_db_schema == "public"
+
+ metadata = (
+ postgres.adbc_get_objects(
+ depth="tables",
+ db_schema_filter=schema_name,
+ table_name_filter=table_name,
+ )
+ .read_all()
+ .to_pylist()
+ )
+
+ catalog_name = postgres.adbc_current_catalog
+ catalog = next(
+ (row for row in metadata if row["catalog_name"] == catalog_name), None
+ )
+ assert catalog is not None
+
+ schemas = catalog["catalog_db_schemas"]
+ assert len(schemas) == 1
+ assert schemas[0]["db_schema_name"] == schema_name
+ tables = schemas[0]["db_schema_tables"]
+ assert len(tables) == 1
+ assert tables[0]["table_name"] == table_name
+
+
def test_conn_get_info(postgres: dbapi.Connection) -> None:
info = postgres.adbc_get_info()
assert info["driver_name"] == "ADBC PostgreSQL Driver"