tuain-dbpool-generic
Version:
Database pool interface to perform standard database operations as part of the Tuain Application Development Framework
405 lines (386 loc) • 14.4 kB
JavaScript
const knex = require('knex');
const { attachPaginate } = require('knex-paginate');
const queryUtils = require('./query-utils');
const modErrs = {
common: {
queryType: ['01', 'Tipo de sentencia inválido'],
queryProcessing: ['02', 'Error en procesamiento de consulta'],
notInfoQuery: ['03', 'Consulta no generó resultados'],
procedureError: ['04', 'Error en ejecución de procedimiento'],
wrongQueryResult: ['05', 'El resultado de la consulta no tiene la estructura esperada'],
generalError: ['99', 'Error general de base de datos'],
},
};
const legacyModErrs = {
moduleName: 'dbpool-lib',
moduleCode: '01',
defaultLanguage: 'es',
contexts: {
common: {
contextCode: '01',
items: {
queryType: {
errorCode: '01',
defaultMessage: 'Tipo de sentencia inválido',
},
queryProcessing: {
errorCode: '02',
defaultMessage: 'Error en procesamiento de consulta',
},
notInfoQuery: {
errorCode: '03',
defaultMessage: 'Consulta no generó resultados',
},
procedureError: {
errorCode: '04',
defaultMessage: 'Error en ejecución de procedimiento',
},
wrongQueryResult: {
errorCode: '05',
defaultMessage: 'El resultado de la consulta no tiene la estructura esperada',
},
generalError: {
errorCode: '99',
defaultMessage: 'Error general de base de datos',
},
},
},
},
};
class DatabasePool {
constructor(logger, errors) {
this.logger = logger;
this.errors = errors;
this.legacyErrors = false;
if (this.errors._errorDictionary) {
this.legacyErrors = true;
this.errors.addModuleSet(legacyModErrs);
} else {
this.errors.addModuleSet('dbpool-lib', modErrs);
}
this.databasePool = {};
this.engineName = '';
this.knex = knex;
this.setupPaginator = attachPaginate;
this.mappingFunctions = {
SELECTONE: 'executeQuery',
SELECT: 'executeQuery',
INSERT: 'executeInsert',
UPDATE: 'executeUpdate',
DELETE: 'executeDelete',
PROCEDURE: 'executeProcedure',
};
}
async launchQuery(queryObject, recordData, dataConstraints) {
const { queryType } = queryObject;
let errorObj = null;
let errorDetail = '';
try {
const methodName = this.mappingFunctions[queryType];
if (!this[methodName]) {
errorDetail = `Tipo de query ${queryType} no soportado en libreria`;
this.logger.log({
level: 'error',
label: 'DatabasePool',
action: 'launchQuery',
message: errorDetail,
});
errorObj = (this.legacyErrors)
? this.errors.get(legacyModErrs.contexts.common.items.queryType, errorDetail)
: this.errors.get(modErrs.common.queryType, errorDetail);
return [errorObj, null];
}
const queryResult = await this[methodName](queryObject, recordData, dataConstraints);
if (!queryResult || !Array.isArray(queryResult) || queryResult.length !== 2) {
errorObj = (this.legacyErrors)
? this.errors.get(legacyModErrs.contexts.common.items.wrongQueryResult)
: this.errors.get(modErrs.common.wrongQueryResult);
return [errorObj, queryResult];
}
return queryResult;
} catch (err) {
errorDetail = `Error preparando sentencia ${queryType}: ${err.message}/${err.stack}`;
this.logger.log({
level: 'error',
label: 'DatabasePool',
action: 'launchQuery',
message: errorDetail,
});
errorObj = (this.legacyErrors)
? this.errors.get(legacyModErrs.contexts.common.items.queryProcessing, errorDetail)
: this.errors.get(modErrs.common.queryProcessing, errorDetail);
return [errorObj, null];
}
}
async executeRawQuery() {
this.logger.log({
level: 'warn',
label: 'DatabasePool',
action: 'executeRawQuery',
message: 'Método virtual puro que requiere de implementación en la subclase',
});
}
async executeProcedure() {
this.logger.log({
level: 'warn',
label: 'DatabasePool',
action: 'executeProcedure',
message: 'Método virtual puro que requiere de implementación en la subclase',
});
}
async executeQuery(queryObject, recordData, inputConstraints) {
let joinArgs = '';
const response = {};
const dataConstraints = queryUtils.completeFilters(queryObject, recordData, inputConstraints);
const [selectColumns, knexColArgs] = queryUtils.processColumns(this.knexPool, queryObject);
const knexQuery = this.knexPool.select(knexColArgs);
const tableObj = queryUtils.processTable(queryObject);
knexQuery.from(tableObj);
joinArgs += queryUtils.processFixedJoins(queryObject);
let simpleFilterFields = selectColumns;
if (queryObject.simpleFilterFields) {
simpleFilterFields = {};
Object.keys(selectColumns).forEach((alias) => {
if (queryObject.simpleFilterFields.includes(alias)) {
simpleFilterFields[alias] = selectColumns[alias];
}
});
}
queryUtils.processSimpleFilters(queryObject, dataConstraints, simpleFilterFields, knexQuery);
if (dataConstraints && dataConstraints.filtersByValue) {
joinArgs += queryUtils.processFiltersByValue(queryObject, dataConstraints, selectColumns, knexQuery);
}
knexQuery.joinRaw(joinArgs.trim());
queryUtils.processOrder(dataConstraints, selectColumns, knexQuery);
let queryData;
let executedPaginated = false;
if (dataConstraints) {
const { recordsNumber: perPage, pageNumber: currentPage, includeTotalRows: isLengthAware } = dataConstraints;
if (perPage && currentPage) {
executedPaginated = true;
const queryToBeExecuted = knexQuery
.paginate({ perPage, currentPage, isLengthAware: isLengthAware ?? false });
this.logger.log({
level: 'silly',
label: 'DatabasePool',
action: 'executeQuery',
message: `Ejecución consulta paginada: ${knexQuery.toString()}`,
});
const { data, pagination } = await queryToBeExecuted;
queryData = data;
response._pagination = {
pageNumber: pagination.currentPage,
rows: pagination.total ?? null,
lastPage: pagination?.lastPage ?? null,
// pagination.from, pagination.to
};
}
}
if (!executedPaginated) {
this.logger.log({
level: 'silly',
label: 'DatabasePool',
action: 'executeQuery',
message: `Ejecución consulta sin paginación: ${knexQuery.toString()}`,
});
queryData = await this.executeRawQuery(knexQuery.toString());
}
return this.processResponse(queryObject, response, queryData);
}
async executeInsert(queryObject, recordData) {
const recordsToInsert = (Array.isArray(recordData)) ? recordData : [recordData];
const table = queryObject.dataSource[0];
const columns = queryObject.queryFields;
const predefinedSets = queryObject.defaultAssign;
const fieldValues = queryObject.fieldAliases;
const insertRecords = [];
const defaultArgs = {};
if (predefinedSets) {
const preFields = Object.keys(predefinedSets);
if (preFields && Array.isArray(preFields) && preFields.length > 0) {
for (let index = 0; index < preFields.length; index++) {
const fieldName = preFields[index];
const fieldValue = predefinedSets[fieldName];
defaultArgs[fieldName] = fieldValue;
}
}
}
for (let recordIndex = 0; recordIndex < recordsToInsert.length; recordIndex++) {
const recordFields = recordsToInsert[recordIndex];
const insertArgs = { ...defaultArgs };
for (let index = 0; index < columns.length; index++) {
const column = columns[index];
const requestKey = fieldValues[index];
if (recordFields && requestKey in recordFields) {
insertArgs[column] = recordFields[requestKey];
}
}
insertRecords.push(insertArgs);
}
let keyColumn = null;
let keyFieldName = null;
if (queryObject && 'recordKey' in queryObject) {
[keyFieldName] = Object.keys(queryObject.recordKey);
keyColumn = queryObject.recordKey[keyFieldName];
}
const sql = this.knexPool(table).insert(insertRecords).toString();
this.logger.log({
level: 'silly',
label: 'DatabasePool',
action: 'executeInsert',
message: `DBPOOL: Ejecución insert: ${sql}`,
});
let fieldToRecover = '';
let insertedRecords;
if (this.engineName === 'mssql') {
fieldToRecover = (queryObject.recoverAll || !keyColumn) ? '*' : keyColumn;
insertedRecords = await this.knexPool(table).insert(insertRecords).returning(fieldToRecover);
} else {
insertedRecords = await this.knexPool(table).insert(insertRecords);
}
if (fieldToRecover !== '*' || typeof insertedRecords?.[0] === 'number') {
// No se tienen campos
return [null, { ids: insertedRecords }];
}
let insertedKeys;
if (insertedRecords && insertedRecords.length > 0) {
const mappingColumns = columns;
const mappingTable = {};
for (let index = 0; index < columns.length; index++) {
const columnName = columns[index];
const fieldName = queryObject.fieldAliases[index];
mappingTable[columnName] = fieldName;
}
if (keyColumn) {
mappingColumns.push(keyColumn);
mappingTable[keyColumn] = keyFieldName;
}
insertedKeys = insertedRecords.map(newRecord => newRecord[keyColumn]);
insertedRecords = insertedRecords.map((newRecord) => {
const externalRecord = {};
mappingColumns.forEach((columnName) => {
const fieldName = mappingTable[columnName];
if (fieldName) {
externalRecord[fieldName] = newRecord[columnName];
}
});
return externalRecord;
});
}
if (insertedKeys && insertedKeys.length > 0) {
return [null, { ids: insertedKeys, records: insertedRecords }];
}
return [null, { records: insertedRecords }];
}
async executeUpdate(queryObject, recordFields, dataConstraints) {
const table = queryObject.dataSource[0];
const setColumns = queryObject.queryFields;
const predefinedSets = queryObject.defaultAssign;
const setFieldValues = queryObject.fieldAliases;
const whereFields = queryObject.requiredFields;
const filters = dataConstraints && dataConstraints.filtersByValue ? dataConstraints.filtersByValue : null;
if (!whereFields && filters === null) {
this.logger.log({
level: 'error',
label: 'DatabasePool',
action: 'executeUpdate',
message: '¡Sentencia update sin where!',
});
}
const updateArgs = {};
for (let index = 0; index < setColumns.length; index++) {
const column = setColumns[index];
const requestKey = setFieldValues[index];
if (requestKey && recordFields && requestKey in recordFields) {
updateArgs[column] = recordFields[requestKey];
}
}
if (predefinedSets) {
const preFields = Object.keys(predefinedSets);
if (preFields && Array.isArray(preFields) && preFields.length > 0) {
for (let index = 0; index < preFields.length; index++) {
const fieldName = preFields[index];
const fieldValue = predefinedSets[fieldName];
updateArgs[fieldName] = fieldValue;
}
}
}
let sql;
if (!whereFields) {
sql = this.knexPool(table).update(updateArgs).toString();
} else {
sql = this.knexPool(table).where((builder) => {
queryUtils.processWhereSentence(whereFields, filters, builder);
}).update(updateArgs).toString();
}
this.logger.log({
level: 'silly',
label: 'DatabasePool',
action: 'executeUpdate',
message: `Ejecución update: ${sql}`,
});
const update = await this.knexPool.raw(sql);
return [null, { changedRows: update[0].changedRows }];
}
async executeDelete(queryObject, recordData, dataConstraints) {
const table = queryObject.dataSource;
const whereFields = queryObject.requiredFields;
const filters = dataConstraints && dataConstraints.filtersByValue
? dataConstraints.filtersByValue : queryUtils.completeDeleteFilters(recordData, whereFields);
if (!whereFields && filters === null) {
this.logger.log({
level: 'warn',
label: 'DatabasePool',
action: 'executeDelete',
message: '¡Sentencia delete sin where!',
});
}
let sql;
if (!whereFields) {
sql = this.knexPool(table).del().toString();
} else {
sql = this.knexPool(table).where((builder) => {
queryUtils.processWhereSentence(whereFields, filters, builder);
}).del().toString();
}
this.logger.log({
level: 'silly',
label: 'DatabasePool',
action: 'executeDelete',
message: `Ejecución delete: ${sql}`,
});
const del = await this.knexPool.raw(sql);
return [null, { affectedRows: del[0].affectedRows }];
}
processResponse(queryObject, response, data) {
const containerName = queryObject.container || 'recordSet';
if (!data || data.length === 0) {
const errorDetail = 'La consulta No retorno información';
this.logger.log({
level: 'silly',
label: 'DatabasePool',
action: 'processResponse',
message: errorDetail,
});
const errorObj = (this.legacyErrors)
? this.errors.get(legacyModErrs.contexts.common.items.notInfoQuery, errorDetail)
: this.errors.get(modErrs.common.notInfoQuery, errorDetail);
return [errorObj, null];
}
if (queryObject.queryType === 'SELECTONE') {
response[containerName] = (Array.isArray(data)) ? data[0] : data;
} else if (Array.isArray(data)) {
response[containerName] = data;
} else {
response[containerName] = [data];
}
this.logger.log({
level: 'silly',
label: 'DatabasePool',
action: 'processResponse',
message: `Respuesta de la consulta: ${JSON.stringify(response)}`,
});
return [null, response];
}
}
module.exports = DatabasePool;