@04l3x4ndr3/microbase-orm
Version:
Um micro ORM em JavaScript ES6 inspirado no CodeIgniter 3 Query Builder com suporte para MySQL/MariaDB e PostgreSQL
255 lines (212 loc) • 8.74 kB
JavaScript
class MySQLDriver {
constructor(connection, config = {}) {
this.connection = connection;
this.config = config;
this.isPool = !!config.max;
this.DEBUG = config.debug || false;
// ✅ Adicionar proteção contra recursão
this.errorDepth = 0;
this.maxErrorDepth = 10;
}
escapeIdentifier(identifier) {
return `\`${identifier.replace(/`/g, '``')}\``;
}
escapeValue(value) {
if (value === null) return 'NULL';
if (typeof value === 'string') {
return `'${value.replace(/'/g, "\\'")}'`;
}
if (typeof value === 'number') return value.toString();
if (typeof value === 'boolean') return value ? '1' : '0';
if (value instanceof Date) {
return `'${value.toISOString().slice(0, 19).replace('T', ' ')}'`;
}
return `'${String(value).replace(/'/g, "\\'")}'`;
}
async execute(sql, params = []) {
// ✅ Proteção contra stack overflow
if (this.errorDepth > this.maxErrorDepth) {
throw new Error('Stack overflow detectado - muitos erros aninhados');
}
try {
if (this.DEBUG) {
console.log('🔍 MySQL SQL Debug:', sql);
console.log('📝 MySQL Params:', params);
console.log('🏊♂️ Using Pool:', this.isPool);
}
return await this.connection.execute(sql, params);
} catch (error) {
throw this.handleMySQLError(error, sql);
}
}
handleMySQLError(error, sql) {
const errorCode = error.code || 'UNKNOWN';
const errno = error.errno || 0;
const errorMessage = String(error.message || error.toString() || 'Erro desconhecido');
switch (errorCode) {
case 'ER_NO_SUCH_TABLE':
case 'ER_BAD_TABLE_ERROR':
return new Error(`Tabela não encontrada: ${errorMessage}`);
case 'ER_BAD_FIELD_ERROR':
return new Error(`Coluna não encontrada: ${errorMessage}`);
case 'ER_DUP_ENTRY':
return new Error(`Violação de chave única: ${errorMessage}`);
case 'ER_NO_REFERENCED_ROW_2':
return new Error(`Violação de chave estrangeira: ${errorMessage}`);
case 'ER_BAD_NULL_ERROR':
return new Error(`Violação de NOT NULL: ${errorMessage}`);
case 'ER_ACCESS_DENIED_ERROR':
return new Error(`Acesso negado: Verifique usuário e senha: ${errorMessage}`);
case 'ER_BAD_DB_ERROR':
return new Error(`Banco de dados não encontrado: ${errorMessage}`);
case 'ER_CON_COUNT_ERROR':
return new Error(`Muitas conexões ativas: ${errorMessage}`);
case 'ER_PARSE_ERROR':
return new Error(`Erro de sintaxe SQL: ${errorMessage}\nSQL: ${sql}`);
case 'ECONNREFUSED':
return new Error(`Conexão recusada: Verifique se o MySQL está rodando: ${errorMessage}`);
case 'ENOTFOUND':
return new Error(`Host não encontrado: ${errorMessage}`);
case 'PROTOCOL_CONNECTION_LOST':
return new Error(`Conexão perdida com o MySQL: ${errorMessage}`);
case 'PROTOCOL_ENQUEUE_AFTER_QUIT':
return new Error(`Tentativa de usar conexão após desconectar: ${errorMessage}`);
case 'ER_LOCK_WAIT_TIMEOUT':
return new Error(`Timeout de lock: ${errorMessage}`);
case 'ER_LOCK_DEADLOCK':
return new Error(`Deadlock detectado: ${errorMessage}`);
// Erros específicos do Pool
case 'ER_POOL_CLOSED':
return new Error(`Pool de conexões foi fechado: ${errorMessage}`);
case 'ER_GET_CONNECTION_TIMEOUT':
return new Error(`Timeout ao obter conexão do pool: ${errorMessage}`);
default:
return new Error(`MySQL Error [${errorCode}/${errno}]: ${errorMessage}\nSQL: ${sql}`);
}
}
getLimitSyntax(limit, offset = 0) {
if (offset > 0) {
return `LIMIT ${offset}, ${limit}`;
}
return `LIMIT ${limit}`;
}
getRandomFunction() {
return 'RAND()';
}
// Método para verificar se uma tabela existe
async tableExists(tableName) {
try {
const result = await this.execute(`
SELECT COUNT(*) as count
FROM information_schema.tables
WHERE table_schema = DATABASE()
AND table_name = ?`, [tableName]);
return result[0].count > 0;
} catch (error) {
return false;
}
}
// Método para listar todas as tabelas
async listTables() {
try {
const result = await this.execute(
`SELECT table_name
FROM information_schema.tables
WHERE table_schema = DATABASE()
ORDER BY table_name`);
return result.map(row => row.table_name);
} catch (error) {
throw new Error(`Erro ao listar tabelas: ${error.message}`);
}
}
// Método para descrever uma tabela
async describeTable(tableName) {
try {
return await this.execute(`DESCRIBE ??`, [tableName]);
} catch (error) {
throw new Error(`Erro ao descrever tabela ${tableName}: ${error.message}`);
}
}
// Método para obter informações do banco
async getDatabaseInfo() {
try {
const [version] = await this.execute('SELECT VERSION() as version');
const [charset] = await this.execute('SELECT @@character_set_database as charset');
const [collation] = await this.execute('SELECT @@collation_database as collation');
return {
version: version.version,
charset: charset.charset,
collation: collation.collation
};
} catch (error) {
throw new Error(`Erro ao obter informações do banco: ${error.message}`);
}
}
// Método para obter estatísticas de performance
async getPerformanceStats() {
try {
const stats = await this.execute(`
SHOW STATUS WHERE Variable_name IN (
'Connections',
'Threads_connected',
'Threads_running',
'Questions',
'Slow_queries',
'Uptime'
)
`);
const result = {};
stats.forEach(stat => {
result[stat.Variable_name.toLowerCase()] = stat.Value;
});
return result;
} catch (error) {
throw new Error(`Erro ao obter estatísticas: ${error.message}`);
}
}
// Método para verificar status da conexão
async ping() {
try {
await this.execute('SELECT 1 as ping');
return true;
} catch (error) {
return false;
}
}
// Método para backup de tabela
async backupTable(tableName) {
try {
const backupTableName = `${tableName}_backup_${Date.now()}`;
// ✅ Usar escape manual em vez de placeholder para DDL
const escapedBackupName = this.escapeIdentifier(backupTableName);
const escapedTableName = this.escapeIdentifier(tableName);
await this.execute(`CREATE TABLE ${escapedBackupName} AS
SELECT *
FROM ${escapedTableName}`);
return backupTableName;
} catch (error) {
// ✅ Evitar recursão - não referenciar error.message diretamente
const errorMsg = error instanceof Error ? error.message : String(error);
throw new Error(`Erro ao criar backup da tabela ${tableName}: ${errorMsg}`);
}
}
// Método para otimizar tabela
async optimizeTable(tableName) {
try {
const result = await this.execute(`OPTIMIZE TABLE ??`, [tableName]);
return result[0];
} catch (error) {
throw new Error(`Erro ao otimizar tabela ${tableName}: ${error.message}`);
}
}
// Método para analisar tabela
async analyzeTable(tableName) {
try {
const result = await this.execute(`ANALYZE TABLE ??`, [tableName]);
return result[0];
} catch (error) {
throw new Error(`Erro ao analisar tabela ${tableName}: ${error.message}`);
}
}
}
export default MySQLDriver;