UNPKG

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