@bhagat-surya-dev/dashchat-database-manager
Version:
AI-powered database schema analysis and management library
307 lines • 12.3 kB
JavaScript
"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