396 lines
12 KiB
JavaScript
396 lines
12 KiB
JavaScript
/**
|
|
* CAMADA DE BANCO DE DADOS (arquivo único)
|
|
* ========================================
|
|
* Concentra tudo que diz respeito a banco de dados em UM lugar:
|
|
*
|
|
* 1) CONFIGURAÇÃO — aliases, drivers, conexões estáticas e customizadas
|
|
* 2) DRIVER FIREBIRD
|
|
* 3) DISPATCHER — API pública usada por todo o sistema
|
|
*
|
|
* Suporte ao banco Firebird. Cada conexão declara o campo `driver`.
|
|
* Conexões adicionadas em runtime (via API /api/databases) são persistidas
|
|
* em databases_custom.json (ao lado deste arquivo).
|
|
*/
|
|
const path = require('path');
|
|
const fs = require('fs');
|
|
|
|
// ============================================================
|
|
// 1) CONFIGURAÇÃO
|
|
// ============================================================
|
|
const CUSTOM_DB_PATH = path.resolve(__dirname, 'databases_custom.json');
|
|
|
|
// Conexões dinâmicas salvas em arquivo JSON
|
|
var customDatabases = {};
|
|
try {
|
|
if (fs.existsSync(CUSTOM_DB_PATH)) {
|
|
customDatabases = JSON.parse(fs.readFileSync(CUSTOM_DB_PATH, 'utf8'));
|
|
}
|
|
} catch (e) {
|
|
console.error('[Databases] Erro ao carregar databases_custom.json:', e.message);
|
|
}
|
|
|
|
const DRIVERS = ['firebird'];
|
|
const DEFAULT_DRIVER = 'firebird';
|
|
|
|
// Valores padrão para Firebird
|
|
const DRIVER_DEFAULTS = {
|
|
firebird: {
|
|
host: 'localhost',
|
|
port: 3050,
|
|
user: 'SYSDBA',
|
|
password: 'masterkey',
|
|
encoding: 'UTF-8',
|
|
lowercase_keys: false,
|
|
pageSize: 4096,
|
|
wireCrypt: 1, // WIRE_CRYPT_ENABLE
|
|
},
|
|
};
|
|
|
|
/**
|
|
* Conexões estáticas.
|
|
* - `novo_local` → Firebird (informe o CAMINHO do arquivo .FDB em `database`)
|
|
* Sobrescrevível pelo .env (DB_*).
|
|
*/
|
|
const databases = {
|
|
novo_local: {
|
|
driver: 'firebird',
|
|
host: process.env.DB_HOST || 'localhost',
|
|
port: parseInt(process.env.DB_PORT, 10) || 3050,
|
|
// CAMINHO do arquivo .FDB: defina DB_DATABASE no .env (absoluto) ou ajuste
|
|
// o path.resolve abaixo. Ex.: path.resolve(__dirname, '../db/NOVO.FDB').
|
|
database: process.env.DB_DATABASE || path.resolve(__dirname, '../db/NOVO.FDB'),
|
|
user: process.env.DB_USER || 'SYSDBA',
|
|
password: process.env.DB_PASSWORD || 'masterkey',
|
|
encoding: process.env.DB_ENCODING || 'UTF-8',
|
|
lowercase_keys: false,
|
|
pageSize: 4096,
|
|
wireCrypt: 1, // WIRE_CRYPT_ENABLE
|
|
},
|
|
};
|
|
|
|
function salvarCustomDatabases() {
|
|
try {
|
|
fs.writeFileSync(CUSTOM_DB_PATH, JSON.stringify(customDatabases, null, 2), 'utf8');
|
|
return true;
|
|
} catch (e) {
|
|
console.error('[Databases] Erro ao salvar databases_custom.json:', e.message);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/** Normaliza uma config bruta aplicando o driver e seus defaults. */
|
|
function normalize(raw) {
|
|
const d = DRIVER_DEFAULTS['firebird'];
|
|
|
|
return {
|
|
driver: 'firebird',
|
|
host: raw.host || d.host,
|
|
port: raw.port || d.port,
|
|
database: raw.database,
|
|
user: raw.user || d.user,
|
|
password: raw.password || d.password,
|
|
encoding: raw.encoding || d.encoding,
|
|
lowercase_keys: raw.lowercase_keys || d.lowercase_keys,
|
|
role: raw.role || null,
|
|
pageSize: raw.pageSize || d.pageSize,
|
|
wireCrypt: raw.wireCrypt !== undefined ? raw.wireCrypt : d.wireCrypt,
|
|
};
|
|
}
|
|
|
|
/** Configuração normalizada (inclui `driver`) de um alias. */
|
|
function getConfig(alias) {
|
|
if (!alias) throw new Error('Alias do banco não informado.');
|
|
const aliasLower = alias.toLowerCase();
|
|
const raw = customDatabases[aliasLower] || databases[aliasLower];
|
|
if (!raw) {
|
|
throw new Error(`Alias "${alias}" não encontrado. Aliases disponíveis: ${listAliases().join(', ')}`);
|
|
}
|
|
return normalize(raw);
|
|
}
|
|
|
|
/** Lista de aliases disponíveis. */
|
|
function listAliases() {
|
|
return Object.keys(databases).concat(
|
|
Object.keys(customDatabases).filter((a) => !databases[a])
|
|
);
|
|
}
|
|
|
|
/** Todos os aliases com suas configs (custom + estáticas). */
|
|
function listAllDatabases() {
|
|
var result = {};
|
|
Object.keys(databases).forEach(function (alias) {
|
|
result[alias] = Object.assign({}, databases[alias], { _tipo: 'estatico' });
|
|
});
|
|
Object.keys(customDatabases).forEach(function (alias) {
|
|
result[alias] = Object.assign({}, customDatabases[alias], { _tipo: 'custom' });
|
|
});
|
|
return result;
|
|
}
|
|
|
|
/** Adiciona/atualiza uma conexão customizada. */
|
|
function addDatabase(alias, config) {
|
|
if (!alias || !config || !config.database) {
|
|
throw new Error('Alias e database são obrigatórios.');
|
|
}
|
|
var aliasLower = alias.toLowerCase().replace(/[^a-z0-9_]/g, '_');
|
|
|
|
var stored = { driver: 'firebird' };
|
|
['host', 'port', 'database', 'user', 'password',
|
|
'encoding', 'lowercase_keys', 'role', 'pageSize', 'wireCrypt']
|
|
.forEach(function (k) {
|
|
if (config[k] !== undefined && config[k] !== null && config[k] !== '') {
|
|
stored[k] = config[k];
|
|
}
|
|
});
|
|
|
|
customDatabases[aliasLower] = stored;
|
|
return salvarCustomDatabases();
|
|
}
|
|
|
|
/** Remove uma conexão customizada. */
|
|
function removeDatabase(alias) {
|
|
if (!alias) return false;
|
|
var aliasLower = alias.toLowerCase();
|
|
if (!customDatabases[aliasLower]) return false;
|
|
delete customDatabases[aliasLower];
|
|
return salvarCustomDatabases();
|
|
}
|
|
|
|
// ============================================================
|
|
// 2) DRIVER FIREBIRD
|
|
// ============================================================
|
|
const Firebird = require('node-firebird');
|
|
|
|
function readBlob(blobFunc) {
|
|
return new Promise(function (resolve, reject) {
|
|
if (typeof blobFunc !== 'function') return resolve(blobFunc);
|
|
blobFunc(function (err, name, emitter) {
|
|
if (err) return reject(err);
|
|
if (!emitter || typeof emitter.on !== 'function') return resolve(null);
|
|
var chunks = [];
|
|
var total = 0;
|
|
emitter.on('data', function (c) { chunks.push(c); total += c.length; });
|
|
emitter.on('end', function () { resolve(Buffer.concat(chunks, total)); });
|
|
emitter.on('error', reject);
|
|
});
|
|
});
|
|
}
|
|
|
|
function firebirdQuery(config, sql, params = []) {
|
|
return new Promise((resolve, reject) => {
|
|
Firebird.attach(config, (err, db) => {
|
|
if (err) return reject(new Error(`Erro ao conectar (firebird): ${err.message}`));
|
|
var allRows = [];
|
|
db.sequentially(sql, params, function (row) {
|
|
var keys = Object.keys(row);
|
|
var blobPromises = keys.map(function (key) {
|
|
var val = row[key];
|
|
if (typeof val === 'function') {
|
|
return readBlob(val).then(function (data) { row[key] = data; });
|
|
}
|
|
return Promise.resolve();
|
|
});
|
|
return Promise.all(blobPromises).then(function () { allRows.push(row); });
|
|
}, function (queryErr) {
|
|
db.detach();
|
|
if (queryErr) return reject(new Error(`Erro na consulta (firebird): ${queryErr.message}`));
|
|
resolve(allRows);
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
function firebirdExecute(config, sql, params = []) {
|
|
return new Promise((resolve, reject) => {
|
|
Firebird.attach(config, (err, db) => {
|
|
if (err) return reject(new Error(`Erro ao conectar (firebird): ${err.message}`));
|
|
db.transaction(Firebird.ISOLATION_READ_COMMITTED, (transErr, transaction) => {
|
|
if (transErr) {
|
|
db.detach();
|
|
return reject(new Error(`Erro ao iniciar transação (firebird): ${transErr.message}`));
|
|
}
|
|
transaction.query(sql, params, (queryErr, result) => {
|
|
if (queryErr) {
|
|
transaction.rollback();
|
|
db.detach();
|
|
return reject(new Error(`Erro na execução (firebird): ${queryErr.message}`));
|
|
}
|
|
transaction.commit((commitErr) => {
|
|
db.detach();
|
|
if (commitErr) return reject(new Error(`Erro ao commitar (firebird): ${commitErr.message}`));
|
|
resolve({ affectedRows: result ? result.length : 0, result });
|
|
});
|
|
});
|
|
});
|
|
});
|
|
});
|
|
}
|
|
|
|
async function firebirdTestConnection(config) {
|
|
await firebirdQuery(config, 'SELECT 1 FROM RDB$DATABASE');
|
|
return true;
|
|
}
|
|
|
|
async function firebirdListTables(config) {
|
|
const rows = await firebirdQuery(config, `
|
|
SELECT TRIM(RDB$RELATION_NAME) AS TABLE_NAME
|
|
FROM RDB$RELATIONS
|
|
WHERE RDB$SYSTEM_FLAG = 0 AND RDB$RELATION_TYPE = 0
|
|
ORDER BY RDB$RELATION_NAME
|
|
`);
|
|
return rows.map((r) => (r.TABLE_NAME || '').trim()).filter(Boolean);
|
|
}
|
|
|
|
async function firebirdTableInfo(config, tableName) {
|
|
const rows = await firebirdQuery(config, `
|
|
SELECT
|
|
rf.RDB$FIELD_NAME AS COLUMN_NAME,
|
|
rf.RDB$FIELD_POSITION AS ORDINAL_POSITION,
|
|
CASE f.RDB$FIELD_TYPE
|
|
WHEN 7 THEN 'SMALLINT' WHEN 8 THEN 'INTEGER' WHEN 16 THEN 'BIGINT'
|
|
WHEN 9 THEN 'QUAD' WHEN 10 THEN 'FLOAT' WHEN 27 THEN 'DOUBLE PRECISION'
|
|
WHEN 12 THEN 'DATE' WHEN 13 THEN 'TIME' WHEN 35 THEN 'TIMESTAMP'
|
|
WHEN 37 THEN 'VARCHAR' WHEN 40 THEN 'CSTRING' WHEN 45 THEN 'BLOB_ID'
|
|
WHEN 261 THEN 'BLOB' WHEN 14 THEN 'CHAR' WHEN 41 THEN 'NUMERIC'
|
|
ELSE 'UNKNOWN'
|
|
END AS DATA_TYPE,
|
|
f.RDB$FIELD_LENGTH AS FIELD_LENGTH,
|
|
f.RDB$FIELD_SCALE AS FIELD_SCALE,
|
|
f.RDB$FIELD_PRECISION AS FIELD_PRECISION,
|
|
rf.RDB$NULL_FLAG AS NULL_FLAG
|
|
FROM RDB$RELATION_FIELDS rf
|
|
INNER JOIN RDB$FIELDS f ON rf.RDB$FIELD_SOURCE = f.RDB$FIELD_NAME
|
|
WHERE rf.RDB$RELATION_NAME = ?
|
|
ORDER BY rf.RDB$FIELD_POSITION
|
|
`, [String(tableName).toUpperCase()]);
|
|
|
|
return rows.map((row) => ({
|
|
name: (row.COLUMN_NAME || '').trim(),
|
|
position: row.ORDINAL_POSITION,
|
|
type: (row.DATA_TYPE || '').trim(),
|
|
length: row.FIELD_LENGTH,
|
|
precision: row.FIELD_PRECISION,
|
|
scale: row.FIELD_SCALE,
|
|
nullable: row.NULL_FLAG !== 1,
|
|
}));
|
|
}
|
|
|
|
async function firebirdTableExists(config, tableName) {
|
|
const r = await firebirdQuery(config,
|
|
"SELECT COUNT(*) AS CT FROM RDB$RELATIONS WHERE RDB$RELATION_NAME = ?",
|
|
[String(tableName).toUpperCase()]);
|
|
return (r[0] && r[0].CT > 0) || false;
|
|
}
|
|
|
|
async function firebirdColumnExists(config, tableName, columnName) {
|
|
const r = await firebirdQuery(config,
|
|
"SELECT COUNT(*) AS CT FROM RDB$RELATION_FIELDS WHERE RDB$RELATION_NAME = ? AND RDB$FIELD_NAME = ?",
|
|
[String(tableName).toUpperCase(), String(columnName).toUpperCase()]);
|
|
return (r[0] && r[0].CT > 0) || false;
|
|
}
|
|
|
|
async function firebirdClose() { /* Firebird abre conexão por consulta; nada a encerrar */ }
|
|
|
|
// ============================================================
|
|
// 3) DISPATCHER — API pública
|
|
// ============================================================
|
|
const driver = {
|
|
query: firebirdQuery,
|
|
execute: firebirdExecute,
|
|
testConnection: firebirdTestConnection,
|
|
listTables: firebirdListTables,
|
|
tableInfo: firebirdTableInfo,
|
|
tableExists: firebirdTableExists,
|
|
columnExists: firebirdColumnExists,
|
|
close: firebirdClose,
|
|
};
|
|
|
|
function getConn(alias) {
|
|
const config = getConfig(alias);
|
|
return { config, driver };
|
|
}
|
|
|
|
function query(alias, sql, params = []) {
|
|
let d;
|
|
try { d = getConn(alias); } catch (err) { return Promise.reject(err); }
|
|
return d.driver.query(d.config, sql, params).catch((err) => { throw new Error(`[${alias}] ${err.message}`); });
|
|
}
|
|
|
|
function execute(alias, sql, params = []) {
|
|
let d;
|
|
try { d = getConn(alias); } catch (err) { return Promise.reject(err); }
|
|
return d.driver.execute(d.config, sql, params).catch((err) => { throw new Error(`[${alias}] ${err.message}`); });
|
|
}
|
|
|
|
async function testConnection(alias) {
|
|
try {
|
|
const { config } = getConn(alias);
|
|
await driver.testConnection(config);
|
|
return true;
|
|
} catch (err) {
|
|
console.error(`[${alias}] Falha na conexão:`, err.message);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
async function testAllConnections() {
|
|
const results = {};
|
|
for (const alias of listAliases()) {
|
|
results[alias] = await testConnection(alias);
|
|
}
|
|
return results;
|
|
}
|
|
|
|
function listTables(alias) {
|
|
const { config } = getConn(alias);
|
|
return driver.listTables(config);
|
|
}
|
|
|
|
function tableInfo(alias, tableName) {
|
|
const { config } = getConn(alias);
|
|
return driver.tableInfo(config, tableName);
|
|
}
|
|
|
|
function tableExists(alias, tableName) {
|
|
const { config } = getConn(alias);
|
|
return driver.tableExists(config, tableName);
|
|
}
|
|
|
|
function columnExists(alias, tableName, columnName) {
|
|
const { config } = getConn(alias);
|
|
return driver.columnExists(config, tableName, columnName);
|
|
}
|
|
|
|
/** Nome do driver de um alias (sempre 'firebird'). */
|
|
function driverOf(alias) {
|
|
return 'firebird';
|
|
}
|
|
|
|
/** Encerra recursos (noop para Firebird). */
|
|
async function closeAll() {
|
|
await driver.close();
|
|
}
|
|
|
|
module.exports = {
|
|
// Acesso a dados
|
|
query,
|
|
execute,
|
|
testConnection,
|
|
testAllConnections,
|
|
listTables,
|
|
tableInfo,
|
|
tableExists,
|
|
columnExists,
|
|
driverOf,
|
|
closeAll,
|
|
// Configuração / aliases
|
|
databases,
|
|
DRIVERS,
|
|
DEFAULT_DRIVER,
|
|
getConfig,
|
|
listAliases,
|
|
listAllDatabases,
|
|
addDatabase,
|
|
removeDatabase,
|
|
};
|