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
37 changes: 37 additions & 0 deletions src/connectors/__tests__/postgres.integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,21 @@ class PostgreSQLIntegrationTest extends IntegrationTestBase<PostgreSQLTestContai
`, {});
await connector.executeSQL(`COMMENT ON VIEW active_users IS 'Users aged 25 or older'`, {});

// Create a foreign table (for foreign table discovery tests, #418). A
// handler-less FDW is enough: the catalog entries exist and can be
// enumerated/described, the table just can't be scanned.
await connector.executeSQL('CREATE FOREIGN DATA WRAPPER dummy_fdw', {});
await connector.executeSQL('CREATE SERVER IF NOT EXISTS dummy_server FOREIGN DATA WRAPPER dummy_fdw', {});
await connector.executeSQL(`
CREATE FOREIGN TABLE IF NOT EXISTS remote_users (
id INTEGER,
name VARCHAR(100),
email VARCHAR(100),
age INTEGER
) SERVER dummy_server
`, {});
await connector.executeSQL(`COMMENT ON FOREIGN TABLE remote_users IS 'Users on a remote server'`, {});

// Create test stored procedures using SQL language to avoid dollar quoting
await connector.executeSQL(`
CREATE OR REPLACE FUNCTION get_user_count()
Expand Down Expand Up @@ -346,6 +361,28 @@ describe('PostgreSQL Connector Integration Tests', () => {
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!();

Expand Down
2 changes: 1 addition & 1 deletion src/connectors/__tests__/shared/integration-test-base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ export abstract class IntegrationTestBase<TContainer extends TestContainer> {

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);
Expand Down
7 changes: 5 additions & 2 deletions src/connectors/postgres/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -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
Expand Down
Loading