From 439592778abb9f8755d1a5ebb4f78fafdb8e552c Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 8 Sep 2026 10:17:20 +0000 Subject: [PATCH] fix(postgres): include foreign tables in getTables so search_objects can discover them Postgres reports foreign tables (postgres_fdw, file_fdw, ...) in information_schema.tables with table_type = 'FOREIGN', so the 'BASE TABLE' filter hid them from search_objects even though every per-table detail query (columns, comment, row count, tableExists) already handled them. Widen the filter to include 'FOREIGN'. Also let getTableIndexes see partitioned tables (relkind 'p'), which carry their own index entries since PG11 but were filtered out. Adds integration coverage using a handler-less FDW so the foreign table can be enumerated and described without a remote server. Closes #418 Co-Authored-By: Claude Fable 5.1 Claude-Session: https://claude.ai/code/session_01Yc1YYa3jHCEW7FDxC66R9Z --- .../__tests__/postgres.integration.test.ts | 37 +++++++++++++++++++ .../__tests__/shared/integration-test-base.ts | 2 +- src/connectors/postgres/index.ts | 7 +++- 3 files changed, 43 insertions(+), 3 deletions(-) diff --git a/src/connectors/__tests__/postgres.integration.test.ts b/src/connectors/__tests__/postgres.integration.test.ts index d5f9fa9e..19ce37f8 100644 --- a/src/connectors/__tests__/postgres.integration.test.ts +++ b/src/connectors/__tests__/postgres.integration.test.ts @@ -173,6 +173,21 @@ class PostgreSQLIntegrationTest extends IntegrationTestBase { expect(comment).toBe('Users aged 25 or older'); }); + it('should list foreign tables as tables, not views', async () => { + const tables = await postgresTest.connector.getTables(); + expect(tables).toContain('remote_users'); + + const views = await postgresTest.connector.getViews(); + expect(views).not.toContain('remote_users'); + + expect(await postgresTest.connector.tableExists('remote_users')).toBe(true); + }); + + it('should describe foreign tables like regular tables', async () => { + const columns = await postgresTest.connector.getTableSchema('remote_users'); + expect(columns.map((c) => c.column_name)).toEqual(['id', 'name', 'email', 'age']); + + const comment = await postgresTest.connector.getTableComment!('remote_users'); + expect(comment).toBe('Users on a remote server'); + + // Foreign tables cannot have indexes; this must return empty rather than throw. + const indexes = await postgresTest.connector.getTableIndexes('remote_users'); + expect(indexes).toEqual([]); + }); + it('should report connection pool state and buffer cache hit ratio via getHealthCheck', async () => { const health = await postgresTest.connector.getHealthCheck!(); diff --git a/src/connectors/__tests__/shared/integration-test-base.ts b/src/connectors/__tests__/shared/integration-test-base.ts index bfa6461b..96c74821 100644 --- a/src/connectors/__tests__/shared/integration-test-base.ts +++ b/src/connectors/__tests__/shared/integration-test-base.ts @@ -104,7 +104,7 @@ export abstract class IntegrationTestBase { it('should list views without overlapping tables', async () => { // Verifies getViews() is wired and its query executes against the real - // database. getTables() (BASE TABLE only) and getViews() must be disjoint. + // database. getTables() (tables, never views) and getViews() must be disjoint. const tables = await this.connector.getTables(); const views = await this.connector.getViews(); expect(Array.isArray(views)).toBe(true); diff --git a/src/connectors/postgres/index.ts b/src/connectors/postgres/index.ts index 7de24dd3..13d11f22 100644 --- a/src/connectors/postgres/index.ts +++ b/src/connectors/postgres/index.ts @@ -259,12 +259,15 @@ export class PostgresConnector implements Connector { // Use the configured default schema (from search_path config, defaults to 'public') const schemaToUse = schema || this.defaultSchema; + // 'FOREIGN' covers foreign tables (postgres_fdw, file_fdw, ...). They are + // queryable like base tables and information_schema.columns already + // describes them, so they must be discoverable here too (#418). const result = await client.query( ` SELECT table_name FROM information_schema.tables WHERE table_schema = $1 - AND table_type = 'BASE TABLE' + AND table_type IN ('BASE TABLE', 'FOREIGN') ORDER BY table_name `, [schemaToUse] @@ -358,7 +361,7 @@ export class PostgresConnector implements Connector { AND i.oid = ix.indexrelid AND a.attrelid = t.oid AND a.attnum = ANY(ix.indkey) - AND t.relkind = 'r' + AND t.relkind IN ('r','p') AND t.relname = $1 AND ns.oid = t.relnamespace AND ns.nspname = $2