import pg from 'pg'; const { Pool } = pg; // Simple query translator from MySQL dialect to PostgreSQL function translateQuery(query, params) { if (typeof query !== 'string') return { sql: query, pgParams: params }; let sql = query; let pgParams = params ? [...params] : []; // Remove or convert backticks sql = sql.replace(/`([^`]+)`/g, '"$1"'); // INSERT INTO table SET ? if (sql.match(/INSERT\s+INTO\s+([^\s]+)\s+SET\s+\?/i)) { const table = sql.match(/INSERT\s+INTO\s+([^\s]+)\s+SET\s+\?/i)[1].replace(/"/g, ''); const dataObj = pgParams[0]; if (dataObj && typeof dataObj === 'object') { const keys = Object.keys(dataObj); const cols = keys.map(k => `"${k}"`).join(', '); const valuesPlaceholder = keys.map((_, idx) => `$${idx + 1}`).join(', '); sql = `INSERT INTO "${table}" (${cols}) VALUES (${valuesPlaceholder})`; pgParams = keys.map(k => dataObj[k]); } } // UPDATE table SET ? WHERE ... if (sql.match(/UPDATE\s+([^\s]+)\s+SET\s+\?\s+WHERE\s+(.+)/i)) { const match = sql.match(/UPDATE\s+([^\s]+)\s+SET\s+\?\s+WHERE\s+(.+)/i); const table = match[1].replace(/"/g, ''); const whereClause = match[2]; const dataObj = pgParams[0]; const extraParams = pgParams.slice(1); if (dataObj && typeof dataObj === 'object') { const keys = Object.keys(dataObj); let paramIdx = 1; const setClause = keys.map(k => `"${k}" = $${paramIdx++}`).join(', '); let postgresWhere = whereClause; let tempIdx = paramIdx; postgresWhere = postgresWhere.replace(/\?/g, () => `$${tempIdx++}`); sql = `UPDATE "${table}" SET ${setClause} WHERE ${postgresWhere}`; pgParams = [...keys.map(k => dataObj[k]), ...extraParams]; } } // Translate standard '?' to '$1', '$2', etc. let count = 1; sql = sql.replace(/\?/g, () => `$${count++}`); // ON DUPLICATE KEY UPDATE -> ON CONFLICT if (sql.match(/ON\s+DUPLICATE\s+KEY\s+UPDATE/i)) { if (sql.toLowerCase().includes('settings')) { sql = sql.replace(/ON\s+DUPLICATE\s+KEY\s+UPDATE\s+data\s*=\s*VALUES\(data\)|ON\s+DUPLICATE\s+KEY\s+UPDATE\s+data\s*=\s*\$4|ON\s+DUPLICATE\s+KEY\s+UPDATE\s+data\s*=\s*EXCLUDED\.data/i, 'ON CONFLICT (id) DO UPDATE SET data = EXCLUDED.data'); } else if (sql.toLowerCase().includes('google_tokens')) { sql = sql.replace(/ON\s+DUPLICATE\s+KEY\s+UPDATE[\s\S]+/i, 'ON CONFLICT (owner_id) DO UPDATE SET refresh_token = EXCLUDED.refresh_token, access_token = EXCLUDED.access_token, expiry_date = EXCLUDED.expiry_date'); } else if (sql.toLowerCase().includes('usuarios')) { sql = sql.replace(/ON\s+DUPLICATE\s+KEY\s+UPDATE[\s\S]+/i, 'ON CONFLICT (id) DO UPDATE SET nome = EXCLUDED.nome, cro = EXCLUDED.cro, cro_uf = EXCLUDED.cro_uf, celular = EXCLUDED.celular, especialidade = EXCLUDED.especialidade'); } else if (sql.toLowerCase().includes('vinculos')) { sql = sql.replace(/ON\s+DUPLICATE\s+KEY\s+UPDATE[\s\S]+/i, 'ON CONFLICT (usuario_id, clinica_id) DO UPDATE SET role = EXCLUDED.role'); } else if (sql.toLowerCase().includes('dentistas_horarios')) { sql = sql.replace(/ON\s+DUPLICATE\s+KEY\s+UPDATE[\s\S]+/i, 'ON CONFLICT (id) DO UPDATE SET dia_semana = EXCLUDED.dia_semana, hora_inicio = EXCLUDED.hora_inicio, hora_fim = EXCLUDED.hora_fim, ativo = EXCLUDED.ativo'); } else if (sql.toLowerCase().includes('dentistas')) { sql = sql.replace(/ON\s+DUPLICATE\s+KEY\s+UPDATE[\s\S]+/i, 'ON CONFLICT (id) DO UPDATE SET nome = EXCLUDED.nome, especialidade = EXCLUDED.especialidade'); } else if (sql.toLowerCase().includes('procedimentos_valores_planos')) { sql = sql.replace(/ON\s+DUPLICATE\s+KEY\s+UPDATE[\s\S]+/i, 'ON CONFLICT (id) DO UPDATE SET valor = EXCLUDED.valor'); } else if (sql.toLowerCase().includes('procedimentos')) { sql = sql.replace(/ON\s+DUPLICATE\s+KEY\s+UPDATE[\s\S]+/i, 'ON CONFLICT (id) DO UPDATE SET nome = EXCLUDED.nome, "especialidadeId" = EXCLUDED."especialidadeId", "especialidadeNome" = EXCLUDED."especialidadeNome", "valorParticular" = EXCLUDED."valorParticular", "codigoInterno" = EXCLUDED."codigoInterno", categoria = EXCLUDED.categoria, tipo_regiao = EXCLUDED.tipo_regiao, exige_face = EXCLUDED.exige_face, codigo = EXCLUDED.codigo'); } } // DATE_FORMAT -> TO_CHAR sql = sql.replace(/DATE_FORMAT\(([^,]+),\s*['"]%Y-%m['"]\)/gi, "TO_CHAR($1, 'YYYY-MM')"); // DATE_SUB -> CURRENT_DATE - INTERVAL sql = sql.replace(/DATE_SUB\(CURDATE\(\),\s*INTERVAL\s+(\d+)\s+MONTH\)/gi, "CURRENT_DATE - INTERVAL '$1 MONTH'"); // SET FOREIGN_KEY_CHECKS if (sql.match(/SET\s+FOREIGN_KEY_CHECKS\s*=\s*\d+/i)) { sql = "SELECT 1"; } return { sql, pgParams }; } class PostgresConnection { constructor(client) { this.client = client; } async query(query, params) { const { sql, pgParams } = translateQuery(query, params); const res = await this.client.query(sql, pgParams); return [res.rows, res.fields]; } async beginTransaction() { await this.client.query('BEGIN'); } async commit() { await this.client.query('COMMIT'); } async rollback() { await this.client.query('ROLLBACK'); } release() { this.client.release(); } } class PostgresPool { constructor(config) { // Convert connection string from postgresql:// to pool config const connectionString = process.env.DATABASE_URL; this.pool = new Pool({ connectionString: connectionString || `postgresql://${config.user}:${config.password}@${config.host}:5432/${config.database}`, ssl: connectionString && connectionString.includes('localhost') ? false : { rejectUnauthorized: false } }); } async query(query, params) { const { sql, pgParams } = translateQuery(query, params); const res = await this.pool.query(sql, pgParams); return [res.rows, res.fields]; } async getConnection() { const client = await this.pool.connect(); return new PostgresConnection(client); } async end() { await this.pool.end(); } } export default { createPool: (config) => new PostgresPool(config) };