Skip to main content
Glama
connection-manager.js10.5 kB
import { logger } from '../utils/logger.js'; export class ConnectionManager { constructor() { this.connections = new Map(); this.fallbackMode = true; // Use Metabase as fallback } async getConnection(metabaseClient, databaseId) { const cacheKey = `db_${databaseId}`; if (this.connections.has(cacheKey)) { return this.connections.get(cacheKey); } try { // Önce direct connection dene const directConnection = await this.createDirectConnection(metabaseClient, databaseId); this.connections.set(cacheKey, directConnection); return directConnection; } catch (error) { logger.warn(`Direct connection failed for DB ${databaseId}:`, error.message); if (this.fallbackMode) { // Fallback: Metabase proxy connection const proxyConnection = this.createProxyConnection(metabaseClient, databaseId); this.connections.set(cacheKey, proxyConnection); return proxyConnection; } throw error; } } async createDirectConnection(metabaseClient, databaseId) { const { DirectDatabaseClient } = await import('./direct-client.js'); // Connection info al (şifre sorunu olabilir) const connectionInfo = await metabaseClient.getDatabaseConnectionInfo(databaseId); // Şifre masked ise hata fırlat if (connectionInfo.password === '**MetabasePass**' || !connectionInfo.password) { throw new Error('Password not available from Metabase API'); } const client = new DirectDatabaseClient(connectionInfo, { prefix: 'claude_ai_', requireApproval: true, dryRun: false }); await client.connect(); return { type: 'direct', client: client, databaseId: databaseId }; } createProxyConnection(metabaseClient, databaseId) { return { type: 'proxy', client: metabaseClient, databaseId: databaseId }; } async executeOperation(connection, operation, ...args) { switch (connection.type) { case 'direct': return await this.executeDirectOperation(connection, operation, ...args); case 'proxy': return await this.executeProxyOperation(connection, operation, ...args); default: throw new Error(`Unknown connection type: ${connection.type}`); } } async executeDirectOperation(connection, operation, ...args) { const client = connection.client; switch (operation) { case 'createTable': return await client.createTable(...args); case 'createView': return await client.createView(...args); case 'createMaterializedView': return await client.createMaterializedView(...args); case 'createIndex': return await client.createIndex(...args); case 'getTableDDL': return await client.getTableDDL(...args); case 'getViewDDL': return await client.getViewDDL(...args); case 'listOwnObjects': return await client.listOwnObjects(); case 'executeDDL': return await client.executeDDL(...args); default: throw new Error(`Unsupported direct operation: ${operation}`); } } async executeProxyOperation(connection, operation, ...args) { const client = connection.client; const databaseId = connection.databaseId; switch (operation) { case 'createTable': return await this.createTableViaProxy(client, databaseId, ...args); case 'createView': return await this.createViewViaProxy(client, databaseId, ...args); case 'createMaterializedView': return await this.createMaterializedViewViaProxy(client, databaseId, ...args); case 'createIndex': return await this.createIndexViaProxy(client, databaseId, ...args); case 'getTableDDL': return await this.getTableDDLViaProxy(client, databaseId, ...args); case 'getViewDDL': return await this.getViewDDLViaProxy(client, databaseId, ...args); case 'listOwnObjects': return await this.listOwnObjectsViaProxy(client, databaseId); case 'executeDDL': return await this.executeDDLViaProxy(client, databaseId, ...args); default: throw new Error(`Unsupported proxy operation: ${operation}`); } } // Proxy implementations using Metabase SQL async createTableViaProxy(client, databaseId, tableName, columns, options = {}) { if (!tableName.startsWith('claude_ai_')) { tableName = 'claude_ai_' + tableName; } const columnsSQL = columns.map(col => `${col.name} ${col.type}${col.constraints ? ' ' + col.constraints : ''}` ).join(', '); const sql = `CREATE TABLE ${tableName} (${columnsSQL})`; const result = await client.executeNativeQuery(databaseId, sql); return { success: true, tableName: tableName, sql: sql, message: 'Table created via Metabase proxy', result: result }; } async createViewViaProxy(client, databaseId, viewName, selectSQL, options = {}) { if (!viewName.startsWith('claude_ai_')) { viewName = 'claude_ai_' + viewName; } const sql = `CREATE VIEW ${viewName} AS ${selectSQL}`; const result = await client.executeNativeQuery(databaseId, sql); return { success: true, viewName: viewName, sql: sql, message: 'View created via Metabase proxy', result: result }; } async createMaterializedViewViaProxy(client, databaseId, viewName, selectSQL, options = {}) { if (!viewName.startsWith('claude_ai_')) { viewName = 'claude_ai_' + viewName; } const sql = `CREATE MATERIALIZED VIEW ${viewName} AS ${selectSQL}`; const result = await client.executeNativeQuery(databaseId, sql); return { success: true, viewName: viewName, sql: sql, message: 'Materialized view created via Metabase proxy', result: result }; } async createIndexViaProxy(client, databaseId, indexName, tableName, columns, options = {}) { if (!indexName.startsWith('claude_ai_')) { indexName = 'claude_ai_' + indexName; } const unique = options.unique ? 'UNIQUE ' : ''; const columnsStr = Array.isArray(columns) ? columns.join(', ') : columns; const sql = `CREATE ${unique}INDEX ${indexName} ON ${tableName} (${columnsStr})`; const result = await client.executeNativeQuery(databaseId, sql); return { success: true, indexName: indexName, sql: sql, message: 'Index created via Metabase proxy', result: result }; } async getTableDDLViaProxy(client, databaseId, tableName) { // PostgreSQL specific query const sql = ` SELECT 'CREATE TABLE ' || schemaname || '.' || tablename || ' (' || string_agg( column_name || ' ' || data_type || CASE WHEN character_maximum_length IS NOT NULL THEN '(' || character_maximum_length || ')' ELSE '' END || CASE WHEN is_nullable = 'NO' THEN ' NOT NULL' ELSE '' END, ', ' ) || ');' as ddl FROM information_schema.columns WHERE table_name = '${tableName}' GROUP BY schemaname, tablename `; const result = await client.executeNativeQuery(databaseId, sql); return result.data.rows[0]?.[0] || null; } async getViewDDLViaProxy(client, databaseId, viewName) { const sql = ` SELECT 'CREATE VIEW ' || schemaname || '.' || viewname || ' AS ' || definition as ddl FROM pg_views WHERE viewname = '${viewName}' `; const result = await client.executeNativeQuery(databaseId, sql); return result.data.rows[0]?.[0] || null; } async listOwnObjectsViaProxy(client, databaseId) { const objects = { tables: [], views: [], materialized_views: [], indexes: [] }; // Tables const tablesSQL = ` SELECT table_name, table_schema FROM information_schema.tables WHERE table_name LIKE 'claude_ai_%' `; const tablesResult = await client.executeNativeQuery(databaseId, tablesSQL); objects.tables = tablesResult.data.rows.map(row => ({ table_name: row[0], table_schema: row[1] })); // Views const viewsSQL = ` SELECT table_name as view_name, table_schema FROM information_schema.views WHERE table_name LIKE 'claude_ai_%' `; const viewsResult = await client.executeNativeQuery(databaseId, viewsSQL); objects.views = viewsResult.data.rows.map(row => ({ view_name: row[0], table_schema: row[1] })); // Materialized Views const matViewsSQL = ` SELECT matviewname, schemaname FROM pg_matviews WHERE matviewname LIKE 'claude_ai_%' `; try { const matViewsResult = await client.executeNativeQuery(databaseId, matViewsSQL); objects.materialized_views = matViewsResult.data.rows.map(row => ({ matviewname: row[0], schemaname: row[1] })); } catch (error) { // pg_matviews may not be accessible logger.warn('Could not query materialized views:', error.message); } // Indexes const indexesSQL = ` SELECT indexname, schemaname, tablename FROM pg_indexes WHERE indexname LIKE 'claude_ai_%' `; try { const indexesResult = await client.executeNativeQuery(databaseId, indexesSQL); objects.indexes = indexesResult.data.rows.map(row => ({ indexname: row[0], schemaname: row[1], tablename: row[2] })); } catch (error) { // pg_indexes may not be accessible logger.warn('Could not query indexes:', error.message); } return objects; } async executeDDLViaProxy(client, databaseId, sql, options = {}) { const result = await client.executeNativeQuery(databaseId, sql); return { success: true, sql: sql, message: 'DDL executed via Metabase proxy', result: result }; } async disconnect(connection) { if (connection.type === 'direct' && connection.client) { await connection.client.disconnect(); } // Remove from cache this.connections.delete(`db_${connection.databaseId}`); } async disconnectAll() { for (const [key, connection] of this.connections) { await this.disconnect(connection); } this.connections.clear(); } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/enessari/metabase-ai-assistant'

If you have feedback or need assistance with the MCP directory API, please join our Discord server