Skip to content

Commit a30c3b1

Browse files
committed
Revert "Add support for search_path in PostgreSQL DSN and TOML config for non-public schemas (#244)"
This reverts commit 8ecb7bd.
1 parent 8ecb7bd commit a30c3b1

File tree

6 files changed

+43
-242
lines changed

6 files changed

+43
-242
lines changed

dbhub.toml.example

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -32,17 +32,6 @@ dsn = "postgres://postgres:postgres@localhost:5432/myapp"
3232
# password = "p@ss:word/123"
3333
# sslmode = "disable" # Optional: "disable" or "require"
3434

35-
# PostgreSQL with non-public schema (individual parameters)
36-
# [[sources]]
37-
# id = "schema_pg"
38-
# type = "postgres"
39-
# host = "localhost"
40-
# database = "mydb"
41-
# user = "user"
42-
# password = "pass"
43-
# search_path = "myschema" # Sets session search_path and default schema for discovery
44-
# # search_path = "myschema,public" # Comma-separated: first schema is used as discovery default
45-
4635
# Development PostgreSQL (shared dev server)
4736
# [[sources]]
4837
# id = "dev_pg"

src/config/__tests__/toml-loader.test.ts

Lines changed: 0 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1048,53 +1048,6 @@ query_timeout = 120
10481048

10491049
expect(dsn).toBe('postgres://user:pass@localhost:9999/db');
10501050
});
1051-
1052-
it('should append search_path param for PostgreSQL sources', () => {
1053-
const source: SourceConfig = {
1054-
id: 'test',
1055-
type: 'postgres',
1056-
host: 'localhost',
1057-
database: 'testdb',
1058-
user: 'testuser',
1059-
password: 'testpass',
1060-
search_path: 'myschema',
1061-
};
1062-
1063-
const dsn = buildDSNFromSource(source);
1064-
1065-
expect(dsn).toBe('postgres://testuser:testpass@localhost:5432/testdb?search_path=myschema');
1066-
});
1067-
1068-
it('should URL-encode comma-separated search_path values', () => {
1069-
const source: SourceConfig = {
1070-
id: 'test',
1071-
type: 'postgres',
1072-
host: 'localhost',
1073-
database: 'testdb',
1074-
user: 'testuser',
1075-
password: 'testpass',
1076-
search_path: 'myschema,public',
1077-
};
1078-
1079-
const dsn = buildDSNFromSource(source);
1080-
1081-
expect(dsn).toBe('postgres://testuser:testpass@localhost:5432/testdb?search_path=myschema%2Cpublic');
1082-
});
1083-
1084-
it('should not add search_path param when not specified', () => {
1085-
const source: SourceConfig = {
1086-
id: 'test',
1087-
type: 'postgres',
1088-
host: 'localhost',
1089-
database: 'testdb',
1090-
user: 'testuser',
1091-
password: 'testpass',
1092-
};
1093-
1094-
const dsn = buildDSNFromSource(source);
1095-
1096-
expect(dsn).not.toContain('search_path');
1097-
});
10981051
});
10991052

11001053
describe('Integration scenarios', () => {

src/config/toml-loader.ts

Lines changed: 27 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,13 @@ import toml from "@iarna/toml";
55
import type { SourceConfig, TomlConfig, ToolConfig } from "../types/config.js";
66
import { parseCommandLineArgs } from "./env.js";
77
import { parseConnectionInfoFromDSN, getDefaultPortForType } from "../utils/dsn-obfuscate.js";
8-
import {
9-
BUILTIN_TOOLS,
10-
BUILTIN_TOOL_EXECUTE_SQL,
11-
BUILTIN_TOOL_SEARCH_OBJECTS,
12-
} from "../tools/builtin-tools.js";
8+
import { BUILTIN_TOOLS, BUILTIN_TOOL_EXECUTE_SQL, BUILTIN_TOOL_SEARCH_OBJECTS } from "../tools/builtin-tools.js";
139

1410
/**
1511
* Load and parse TOML configuration file
1612
* Returns the parsed sources array, tools array, and the source of the config file
1713
*/
18-
export function loadTomlConfig(): {
19-
sources: SourceConfig[];
20-
tools?: TomlConfig["tools"];
21-
source: string;
22-
} | null {
14+
export function loadTomlConfig(): { sources: SourceConfig[]; tools?: TomlConfig['tools']; source: string } | null {
2315
const configPath = resolveTomlConfigPath();
2416
if (!configPath) {
2517
return null;
@@ -48,7 +40,9 @@ export function loadTomlConfig(): {
4840
};
4941
} catch (error) {
5042
if (error instanceof Error) {
51-
throw new Error(`Failed to load TOML configuration from ${configPath}: ${error.message}`);
43+
throw new Error(
44+
`Failed to load TOML configuration from ${configPath}: ${error.message}`
45+
);
5246
}
5347
throw error;
5448
}
@@ -65,7 +59,9 @@ function resolveTomlConfigPath(): string | null {
6559
if (args.config) {
6660
const configPath = expandHomeDir(args.config);
6761
if (!fs.existsSync(configPath)) {
68-
throw new Error(`Configuration file specified by --config flag not found: ${configPath}`);
62+
throw new Error(
63+
`Configuration file specified by --config flag not found: ${configPath}`
64+
);
6965
}
7066
return configPath;
7167
}
@@ -150,7 +146,9 @@ function validateToolsConfig(
150146

151147
for (const tool of tools) {
152148
if (!tool.name) {
153-
throw new Error(`Configuration file ${configPath}: all tools must have a 'name' field`);
149+
throw new Error(
150+
`Configuration file ${configPath}: all tools must have a 'name' field`
151+
);
154152
}
155153

156154
if (!tool.source) {
@@ -270,7 +268,11 @@ function validateSourceConfig(source: SourceConfig, configPath: string): void {
270268

271269
// Validate SSH port if provided
272270
if (source.ssh_port !== undefined) {
273-
if (typeof source.ssh_port !== "number" || source.ssh_port <= 0 || source.ssh_port > 65535) {
271+
if (
272+
typeof source.ssh_port !== "number" ||
273+
source.ssh_port <= 0 ||
274+
source.ssh_port > 65535
275+
) {
274276
throw new Error(
275277
`Configuration file ${configPath}: source '${source.id}' has invalid ssh_port. ` +
276278
`Must be between 1 and 65535.`
@@ -365,7 +367,10 @@ function validateSourceConfig(source: SourceConfig, configPath: string): void {
365367
/**
366368
* Process source configurations (expand paths, populate fields from DSN)
367369
*/
368-
function processSourceConfigs(sources: SourceConfig[], configPath: string): SourceConfig[] {
370+
function processSourceConfigs(
371+
sources: SourceConfig[],
372+
configPath: string
373+
): SourceConfig[] {
369374
return sources.map((source) => {
370375
const processed = { ...source };
371376

@@ -427,20 +432,24 @@ function expandHomeDir(filePath: string): string {
427432
* Similar to buildDSNFromEnvParams in env.ts but for TOML sources
428433
*/
429434
export function buildDSNFromSource(source: SourceConfig): string {
430-
// If DSN is already provided, use it directly
435+
// If DSN is already provided, use it
431436
if (source.dsn) {
432437
return source.dsn;
433438
}
434439

435440
// Validate required fields
436441
if (!source.type) {
437-
throw new Error(`Source '${source.id}': 'type' field is required when 'dsn' is not provided`);
442+
throw new Error(
443+
`Source '${source.id}': 'type' field is required when 'dsn' is not provided`
444+
);
438445
}
439446

440447
// Handle SQLite
441448
if (source.type === "sqlite") {
442449
if (!source.database) {
443-
throw new Error(`Source '${source.id}': 'database' field is required for SQLite`);
450+
throw new Error(
451+
`Source '${source.id}': 'database' field is required for SQLite`
452+
);
444453
}
445454
return `sqlite:///${source.database}`;
446455
}
@@ -492,13 +501,6 @@ export function buildDSNFromSource(source: SourceConfig): string {
492501
}
493502
}
494503

495-
// Add PostgreSQL-specific parameters
496-
if (source.type === "postgres") {
497-
if (source.search_path) {
498-
queryParams.push(`search_path=${encodeURIComponent(source.search_path)}`);
499-
}
500-
}
501-
502504
// Add sslmode for network databases (not sqlite)
503505
if (source.sslmode && source.type !== "sqlite") {
504506
queryParams.push(`sslmode=${source.sslmode}`);

src/connectors/__tests__/postgres.integration.test.ts

Lines changed: 0 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -462,113 +462,6 @@ describe('PostgreSQL Connector Integration Tests', () => {
462462

463463
});
464464

465-
describe('search_path support', () => {
466-
it('should discover tables in non-public schema by default when search_path is set', async () => {
467-
// Create a non-public schema with a table
468-
await postgresTest.connector.executeSQL('CREATE SCHEMA IF NOT EXISTS notpublic', {});
469-
await postgresTest.connector.executeSQL(`
470-
CREATE TABLE IF NOT EXISTS notpublic.widgets (
471-
id SERIAL PRIMARY KEY,
472-
name VARCHAR(100) NOT NULL
473-
)
474-
`, {});
475-
476-
const baseUri = postgresTest.connectionString;
477-
const uriWithSearchPath = baseUri.includes('?')
478-
? `${baseUri}&search_path=notpublic`
479-
: `${baseUri}?search_path=notpublic`;
480-
481-
const connector = new PostgresConnector();
482-
try {
483-
await connector.connect(uriWithSearchPath);
484-
485-
// getTables() without explicit schema should use notpublic (the defaultSchema)
486-
const tables = await connector.getTables();
487-
expect(tables).toContain('widgets');
488-
// Should NOT contain tables from public schema
489-
expect(tables).not.toContain('users');
490-
expect(tables).not.toContain('orders');
491-
} finally {
492-
await connector.disconnect();
493-
}
494-
});
495-
496-
it('should use first schema from comma-separated search_path as discovery default', async () => {
497-
await postgresTest.connector.executeSQL('CREATE SCHEMA IF NOT EXISTS notpublic', {});
498-
await postgresTest.connector.executeSQL(`
499-
CREATE TABLE IF NOT EXISTS notpublic.widgets (
500-
id SERIAL PRIMARY KEY,
501-
name VARCHAR(100) NOT NULL
502-
)
503-
`, {});
504-
505-
const baseUri = postgresTest.connectionString;
506-
const uriWithSearchPath = baseUri.includes('?')
507-
? `${baseUri}&search_path=notpublic,public`
508-
: `${baseUri}?search_path=notpublic,public`;
509-
510-
const connector = new PostgresConnector();
511-
try {
512-
await connector.connect(uriWithSearchPath);
513-
514-
// First schema in the list (notpublic) should be the default
515-
const tables = await connector.getTables();
516-
expect(tables).toContain('widgets');
517-
expect(tables).not.toContain('users');
518-
} finally {
519-
await connector.disconnect();
520-
}
521-
});
522-
523-
it('should allow explicit schema parameter to override defaultSchema', async () => {
524-
await postgresTest.connector.executeSQL('CREATE SCHEMA IF NOT EXISTS notpublic', {});
525-
await postgresTest.connector.executeSQL(`
526-
CREATE TABLE IF NOT EXISTS notpublic.widgets (
527-
id SERIAL PRIMARY KEY,
528-
name VARCHAR(100) NOT NULL
529-
)
530-
`, {});
531-
532-
const baseUri = postgresTest.connectionString;
533-
const uriWithSearchPath = baseUri.includes('?')
534-
? `${baseUri}&search_path=notpublic`
535-
: `${baseUri}?search_path=notpublic`;
536-
537-
const connector = new PostgresConnector();
538-
try {
539-
await connector.connect(uriWithSearchPath);
540-
541-
// Explicit schema='public' should override defaultSchema
542-
const publicTables = await connector.getTables('public');
543-
expect(publicTables).toContain('users');
544-
expect(publicTables).toContain('orders');
545-
expect(publicTables).not.toContain('widgets');
546-
} finally {
547-
await connector.disconnect();
548-
}
549-
});
550-
551-
it('should set search_path session variable after connecting', async () => {
552-
await postgresTest.connector.executeSQL('CREATE SCHEMA IF NOT EXISTS notpublic', {});
553-
554-
const baseUri = postgresTest.connectionString;
555-
const uriWithSearchPath = baseUri.includes('?')
556-
? `${baseUri}&search_path=notpublic`
557-
: `${baseUri}?search_path=notpublic`;
558-
559-
const connector = new PostgresConnector();
560-
try {
561-
await connector.connect(uriWithSearchPath);
562-
563-
const result = await connector.executeSQL('SHOW search_path', {});
564-
expect(result.rows).toHaveLength(1);
565-
expect(result.rows[0].search_path).toContain('notpublic');
566-
} finally {
567-
await connector.disconnect();
568-
}
569-
});
570-
});
571-
572465
describe('SDK-Level Readonly Mode Tests', () => {
573466
it('should set default_transaction_read_only at connection level', async () => {
574467
const readonlyConnector = new PostgresConnector();

0 commit comments

Comments
 (0)