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