UNPKG

tuain-dbpool-generic

Version:

Database pool interface to perform standard database operations as part of the Tuain Application Development Framework

319 lines (300 loc) 12.4 kB
const knex = require('knex'); const { attachPaginate } = require('knex-paginate'); const queryUtils = require('./query-utils'); attachPaginate(); 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 label = 'DatabasePool'; const SELECTONE = 'SELECTONE'; class DatabasePool { constructor(logger, errors) { this.logger = logger; this.errors = errors; this.errors.addModuleSet('dbpool-lib', modErrs); this.databasePool = {}; this.engineName = ''; this.knex = knex; 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 message = ''; const action = 'launchQuery'; try { const methodName = this.mappingFunctions[queryType]; if (!this[methodName]) { message = `Tipo de query ${queryType} no soportado en libreria`; this.logger.log({ level: 'error', label, action, message }); errorObj = this.errors.get(modErrs.common.queryType, message); return [errorObj, null]; } const queryResult = await this[methodName](queryObject, recordData, dataConstraints); if (!queryResult || !Array.isArray(queryResult) || queryResult.length !== 2) { errorObj = this.errors.get(modErrs.common.wrongQueryResult); return [errorObj, queryResult]; } return queryResult; } catch (err) { message = `Error preparando sentencia ${queryType}: ${err.message}/${err.stack}`; this.logger.log({ level: 'error', label, action, message }); errorObj = this.errors.get(modErrs.common.queryProcessing, message); return [errorObj, null]; } } async executeRawQuery() { const message = 'Método virtual puro que requiere de implementación en la subclase'; this.logger.log({ level: 'warn', label, action: 'executeRawQuery', message }); } async executeProcedure() { const message = 'Método virtual puro que requiere de implementación en la subclase'; this.logger.log({ level: 'warn', label, action: 'executeProcedure', message }); } async executeQuery(queryObject, recordData, inputConstraints) { let joinArgs = ''; let message; const action = 'executeQuery'; 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 }); message = `Ejecución consulta paginada: ${knexQuery.toString()}`; this.logger.log({ level: 'silly', label, action, message }); 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) { message = `Ejecución consulta sin paginación: ${knexQuery.toString()}`; this.logger.log({ level: 'silly', label, action, message }); queryData = await this.executeRawQuery(knexQuery.toString()); } return this.processResponse(queryObject, response, queryData); } async executeInsert(queryObject, recordData) { const action = 'executeInsert'; 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(); const message = `DBPOOL: Ejecución insert: ${sql}`; this.logger.log({ level: 'silly', label, action, message }); 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 action = 'executeUpdate'; let message; 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) { message = '¡Sentencia update sin where!'; this.logger.log({ level: 'error', label, action, message }); } 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(); } message = `Ejecución update: ${sql}`; this.logger.log({ level: 'silly', label, action, message }); const update = await this.knexPool.raw(sql); return [null, { changedRows: update[0].changedRows }]; } async executeDelete(queryObject, recordData, dataConstraints) { const action = 'executeDelete'; let message; const table = queryObject.dataSource; const whereFields = queryObject.requiredFields; const filters = dataConstraints && dataConstraints.filtersByValue ? dataConstraints.filtersByValue : queryUtils.completeDeleteFilters(recordData, whereFields); if (!whereFields && filters === null) { message = '¡Sentencia delete sin where!'; this.logger.log({ level: 'warn', label, action, message }); } let sql; if (!whereFields) { sql = this.knexPool(table).del().toString(); } else { sql = this.knexPool(table) .where((builder) => { queryUtils.processWhereSentence(whereFields, filters, builder); }) .del() .toString(); } message = `Ejecución delete: ${sql}`; this.logger.log({ level: 'silly', label, action, message }); const del = await this.knexPool.raw(sql); return [null, { affectedRows: del[0].affectedRows }]; } processResponse(queryObject, response, data) { const action = 'processResponse'; let message; const containerName = queryObject.container || 'recordSet'; if (!data || data.length === 0) { message = 'La consulta No retorno información'; this.logger.log({ level: 'silly', label, action, message }); const errorObj = this.errors.get(modErrs.common.notInfoQuery, message); 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]; } message = `Respuesta de la consulta: ${JSON.stringify(response)}`; this.logger.log({ level: 'silly', label, action, message }); return [null, response]; } } module.exports = DatabasePool;