Files
scoreodonto.com/backend/postgres-adapter.js

144 lines
6.3 KiB
JavaScript

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)
};