UNPKG

@bhagat-surya-dev/dashchat-database-manager

Version:

AI-powered database schema analysis and management library

307 lines 12.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.PostgresHandler = void 0; // PostgreSQL specific handler with Supabase pooler fix const typeorm_1 = require("typeorm"); const sql_db_1 = require("langchain/sql_db"); const perf_hooks_1 = require("perf_hooks"); const pg_1 = require("pg"); const base_handler_1 = require("./base-handler"); const supabase_handler_1 = require("./supabase-handler"); class PostgresHandler extends base_handler_1.BaseDatabaseHandler { getDatabaseType() { return 'postgres'; } // Create TypeORM DataSource options for Postgres createDataSourceOptions(databaseUrl) { const url = new URL(databaseUrl); const isSupabasePooler = url.hostname.includes('pooler.supabase.com'); // Use 'type' as 'postgres' to help type narrowing const baseOptions = { type: 'postgres', url: databaseUrl, entities: [], synchronize: false, logging: false, }; if (isSupabasePooler) { return { ...baseOptions, ssl: { rejectUnauthorized: false, }, extra: { ssl: { rejectUnauthorized: false, }, connectionTimeoutMillis: 30000, query_timeout: 30000, statement_timeout: 30000, idle_in_transaction_session_timeout: 30000, // Add pool settings for better connection management max: 1, // Limit to 1 connection to avoid pooling issues min: 0, acquire: 30000, idle: 10000, }, }; } else { return { ...baseOptions, ssl: { rejectUnauthorized: false }, extra: { connectionTimeoutMillis: 30000, query_timeout: 30000, }, }; } } // Quotes identifiers safely for Postgres getQuotedIdentifier(identifier) { return `"${identifier.replace(/"/g, '""')}"`; } // Get table column metadata and sample data using pg directly for Supabase async getTableInfoWithPg(connectionString, tableName, tableSchema) { const method = 'getTableInfoWithPg'; // Create a new client using the imported Client const client = new pg_1.Client({ connectionString, ssl: { rejectUnauthorized: false, }, connectionTimeoutMillis: 30000, query_timeout: 30000, statement_timeout: 30000, }); try { await client.connect(); const columnsQuery = ` SELECT column_name, data_type, is_nullable FROM information_schema.columns WHERE table_name = $1 AND table_schema = $2 ORDER BY ordinal_position; `; const queryParams = [tableName, tableSchema || 'public']; const safeTableNameForFrom = this.getQuotedIdentifier(tableName); const safeSchemaPrefix = tableSchema ? `${this.getQuotedIdentifier(tableSchema)}.` : ''; const sampleDataQuery = `SELECT * FROM ${safeSchemaPrefix}${safeTableNameForFrom} LIMIT ${this.maxSampleSize}`; const [columnsResult, sampleDataResult] = await Promise.all([ client.query(columnsQuery, queryParams), client.query(sampleDataQuery) ]); return { name: tableName, columns: columnsResult.rows.map(col => ({ column_name: col.column_name, data_type: col.data_type, is_nullable: col.is_nullable })), sampleData: sampleDataResult.rows }; } catch (error) { this.logError(method, error, { tableName, tableSchema }); throw error; } finally { await client.end(); } } // Get table column metadata and sample data using TypeORM async getTableInfo(datasource, tableName, tableSchema) { const method = 'getTableInfo'; const columnsQuery = ` SELECT column_name, data_type, is_nullable FROM information_schema.columns WHERE table_name = $1 AND table_schema = $2 ORDER BY ordinal_position; `; const queryParams = [tableName, tableSchema || 'public']; const safeTableNameForFrom = this.getQuotedIdentifier(tableName); const safeSchemaPrefix = tableSchema ? `${this.getQuotedIdentifier(tableSchema)}.` : ''; const sampleDataQuery = `SELECT * FROM ${safeSchemaPrefix}${safeTableNameForFrom} LIMIT ${this.maxSampleSize}`; try { const [columnsResult, sampleData] = await Promise.all([ datasource.query(columnsQuery, queryParams), datasource.query(sampleDataQuery) ]); return { name: tableName, columns: columnsResult.map(col => ({ column_name: col.column_name, data_type: col.data_type, is_nullable: col.is_nullable })), sampleData }; } catch (error) { this.logError(method, error, { tableName, tableSchema }); throw error; } } // Get list of tables using pg directly for Supabase async getTablesWithPg(connectionString) { const method = 'getTablesWithPg'; const client = new pg_1.Client({ connectionString, ssl: { rejectUnauthorized: false, }, connectionTimeoutMillis: 30000, query_timeout: 30000, statement_timeout: 30000, }); try { await client.connect(); // Query to get all user tables from the public schema const tablesQuery = ` SELECT table_name, table_schema FROM information_schema.tables WHERE table_schema = 'public' AND table_type = 'BASE TABLE' ORDER BY table_name; `; const result = await client.query(tablesQuery); return result.rows.map((row) => ({ tableName: row.table_name, schema: row.table_schema })); } catch (error) { this.logError(method, error); throw error; } finally { await client.end(); } } // Test if connection is valid async testConnection(databaseUrl) { const method = 'testConnection'; const start = perf_hooks_1.performance.now(); if (!databaseUrl) { this.logError(method, new Error('Database URL is required')); return false; } // Detect if this is a Supabase URL and delegate to SupabaseHandler if (supabase_handler_1.SupabaseHandler.isSupabaseUrl(databaseUrl)) { this.logInfo(method, 'Detected Supabase URL, delegating to SupabaseHandler'); const success = await supabase_handler_1.supabaseHandler.testConnection(databaseUrl); const duration = perf_hooks_1.performance.now() - start; this.log(base_handler_1.LogLevel.DEBUG, { method, action: 'performance_measurement', duration, metadata: { durationMs: duration.toFixed(2) } }, 'Connection test completed'); return success; } const datasource = new typeorm_1.DataSource(this.createDataSourceOptions(databaseUrl)); try { await this.withTimeout(datasource.initialize(), this.timeout, 'Connection timed out'); this.logInfo(method, 'Successfully connected to PostgreSQL database'); return true; } catch (error) { this.logError(method, error, { databaseUrl }); return false; } finally { if (datasource.isInitialized) { await datasource.destroy(); } const duration = perf_hooks_1.performance.now() - start; this.log(base_handler_1.LogLevel.DEBUG, { method, action: 'performance_measurement', duration, metadata: { durationMs: duration.toFixed(2) } }, 'Connection test completed'); } } // TypeORM Version Compatibility // Use pg library directly for Supabase connections async testSupabaseConnection(databaseUrl) { const client = new pg_1.Client({ connectionString: databaseUrl, ssl: { rejectUnauthorized: false, }, connectionTimeoutMillis: 30000, }); try { await client.connect(); await client.query('SELECT 1'); await client.end(); return true; } catch (error) { console.error('Supabase connection test failed:', error); return false; } } // Main method to get schema info async getSchemaInfo(databaseUrl) { const method = 'getSchemaInfo'; const start = perf_hooks_1.performance.now(); if (!databaseUrl) { this.log(base_handler_1.LogLevel.ERROR, { method, action: 'validation' }, 'Database URL is required.'); throw new Error('Please provide a valid database URL'); } this.logInfo(method, 'Starting schema extraction for PostgreSQL', { databaseUrl: databaseUrl.replace(/:[^:@]*@/, ':***@') // Hide password in logs }); // Check if this is a Supabase URL and delegate to SupabaseHandler if (supabase_handler_1.SupabaseHandler.isSupabaseUrl(databaseUrl)) { this.logInfo(method, 'Detected Supabase URL, delegating to SupabaseHandler'); return await supabase_handler_1.supabaseHandler.getSchemaInfo(databaseUrl); } // Use TypeORM for non-Supabase connections const datasource = new typeorm_1.DataSource(this.createDataSourceOptions(databaseUrl)); try { await datasource.initialize(); try { const sqlDb = await sql_db_1.SqlDatabase.fromDataSourceParams({ appDataSource: datasource }); // Update type handling for LangChain tables const tablesFromLangchain = sqlDb.allTables.map(table => ({ tableName: table.tableName, schema: 'public' })); const tableInfoPromises = tablesFromLangchain.map(table => this.getTableInfo(datasource, table.tableName, table.schema)); const rawTablesInfo = await Promise.all(tableInfoPromises); return { databaseType: this.getDatabaseType(), tables: rawTablesInfo.map(table => ({ name: table.name, columns: table.columns.map(col => ({ name: col.column_name, type: col.data_type, nullable: col.is_nullable === 'YES' })), sampleData: table.sampleData })) }; } finally { if (datasource.isInitialized) await datasource.destroy(); } } catch (error) { this.logError(method, error, { databaseUrl }); throw error; } finally { const duration = perf_hooks_1.performance.now() - start; this.log(base_handler_1.LogLevel.DEBUG, { method, action: 'performance_measurement', duration, metadata: { durationMs: duration.toFixed(2) } }, 'Schema extraction completed'); } } } exports.PostgresHandler = PostgresHandler; //# sourceMappingURL=postgres-handler.js.map