UNPKG

jeep-sqlite

Version:
549 lines (548 loc) 21.1 kB
import { UtilsDrop } from "../utils/utils-drop"; import { UtilsJSON } from "../utils/utils-json"; import { UtilsDelete } from "./utils-delete"; import { UtilsSQLStatement } from "./utils-sqlstatement"; export class UtilsSQLite { static async beginTransaction(db, isOpen) { const msg = 'BeginTransaction: '; if (!isOpen) { return Promise.reject(new Error(`${msg}database not opened`)); } try { db.exec('BEGIN TRANSACTION'); return Promise.resolve(); } catch (err) { const msge = err.message ? err.message : err; return Promise.reject(new Error(`${msg}${msge}`)); } } static async rollbackTransaction(db, isOpen) { const msg = 'RollbackTransaction: '; if (!isOpen) { return Promise.reject(new Error(`${msg}database not opened`)); } try { db.exec('ROLLBACK TRANSACTION'); return Promise.resolve(); } catch (err) { const msge = err.message ? err.message : err; return Promise.reject(new Error(`${msg}${msge}`)); } } static commitTransaction(db, isOpen) { const msg = 'CommitTransaction: '; if (!isOpen) { return Promise.reject(new Error(`${msg}database not opened`)); } const sql = 'COMMIT TRANSACTION'; try { db.exec(sql); return Promise.resolve(); } catch (err) { const msge = err.message ? err.message : err; return Promise.reject(new Error(`${msg}${msge}`)); } } static async dbChanges(db) { const SELECT_CHANGE = 'SELECT total_changes()'; let changes = 0; try { const res = db.exec(SELECT_CHANGE); // process the row here changes = res[0].values[0][0]; return Promise.resolve(changes); } catch (err) { const msg = err.message ? err.message : err; return Promise.reject(new Error(`DbChanges failed: ${msg}`)); } } static async getLastId(db) { const SELECT_LAST_ID = 'SELECT last_insert_rowid()'; let lastId = -1; try { const res = db.exec(SELECT_LAST_ID); // process the row here lastId = res[0].values[0][0]; return Promise.resolve(lastId); } catch (err) { const msg = err.message ? err.message : err; return Promise.reject(new Error(`GetLastId failed: ${msg}`)); } } static async setForeignKeyConstraintsEnabled(db, toggle) { let stmt = 'PRAGMA foreign_keys=OFF'; if (toggle) { stmt = 'PRAGMA foreign_keys=ON'; } try { db.run(stmt); return Promise.resolve(); } catch (err) { const msg = err.message ? err.message : err; return Promise.reject(new Error(`SetForeignKey: ${msg}`)); } } static async getVersion(db) { let version = 0; try { const res = db.exec('PRAGMA user_version;'); console.log(`#### getVersion new res: ${JSON.stringify(res)}`); if (res && res.length > 0 && res[0].values && res[0].values.length > 0 && res[0].values[0].length > 0) { version = res[0].values[0][0]; } else { const msg = "Cannot return the version from the database"; return Promise.reject(new Error(`GetVersion: ${msg}`)); } return Promise.resolve(version); } catch (err) { const msg = err.message ? err.message : err; return Promise.reject(new Error(`GetVersion: ${msg}`)); } } static async setVersion(db, version) { try { db.exec(`PRAGMA user_version = ${version}`); return Promise.resolve(); } catch (err) { const msg = err.message ? err.message : err; return Promise.reject(new Error(`SetVersion: ${msg}`)); } } static async execute(db, sql, fromJson) { try { var sqlStmt = sql; // Check for DELETE FROM in sql string if (!fromJson && sql.toLowerCase().includes('DELETE FROM'.toLowerCase())) { sqlStmt = sql.replace(/\n/g, ''); let sqlStmts = sqlStmt.split(';'); var resArr = []; for (const stmt of sqlStmts) { const trimStmt = stmt.trim().substring(0, 11).toUpperCase(); if (trimStmt === 'DELETE FROM' && stmt.toLowerCase().includes('WHERE'.toLowerCase())) { const whereStmt = stmt.trim(); const rStmt = await UtilsSQLite.deleteSQL(db, whereStmt, []); resArr.push(rStmt); } else { resArr.push(stmt); } } sqlStmt = resArr.join(';'); } db.exec(sqlStmt); const changes = await UtilsSQLite.dbChanges(db); return Promise.resolve(changes); } catch (err) { const msg = err.message ? err.message : err; return Promise.reject(new Error(`Execute: ${msg}`)); } } static async executeSet(db, set, fromJson, returnMode) { const retValues = []; let lastId = -1; let retObj = {}; for (let i = 0; i < set.length; i++) { const statement = 'statement' in set[i] ? set[i].statement : null; const values = 'values' in set[i] && set[i].values.length > 0 ? set[i].values : []; if (statement == null) { let msg = 'ExecuteSet: Error No statement'; msg += ` for index ${i}`; return Promise.reject(new Error(msg)); } try { if (Array.isArray(values[0])) { for (const val of values) { const mVal = await UtilsSQLite.replaceUndefinedByNull(val); retObj = await UtilsSQLite.run(db, statement, mVal, fromJson, returnMode); lastId = retObj["lastId"]; if (Object.keys(retObj).includes("values") && retObj["values"].length > 0) { retValues.push(retObj["values"]); } } } else { const mVal = await UtilsSQLite.replaceUndefinedByNull(values); retObj = await UtilsSQLite.run(db, statement, mVal, fromJson, returnMode); lastId = retObj["lastId"]; if (Object.keys(retObj).includes("values") && retObj["values"].length > 0) { retValues.push(retObj["values"]); } } } catch (err) { const msg = err.message ? err.message : err; return Promise.reject(new Error(`ExecuteSet: ${msg}`)); } } retObj["lastId"] = lastId; retObj["values"] = returnMode === 'all' ? retValues : returnMode === 'one' ? retValues[0] : []; return Promise.resolve(retObj); } static async queryAll(db, sql, values) { try { let retArr = []; if (values != null && values.length > 0) { retArr = db.exec(sql, values); } else { retArr = db.exec(sql); } if (retArr.length == 0) return Promise.resolve([]); const result = retArr[0].values.map(entry => { const obj = {}; retArr[0].columns.forEach((column, index) => { obj[column] = entry[index]; }); return obj; }); return Promise.resolve(result); } catch (err) { const msg = err.message ? err.message : err; return Promise.reject(new Error(`queryAll: ${msg}`)); } } static async run(db, statement, values, fromJson, returnMode) { let stmtType = statement.replace(/\n/g, "").trim().substring(0, 6).toUpperCase(); let sqlStmt = statement; let retValues = []; let retObj = {}; try { if (!fromJson && stmtType === "DELETE") { sqlStmt = await UtilsSQLite.deleteSQL(db, statement, values); } const mValues = values ? values : []; let res; if (mValues.length > 0) { const mVal = await UtilsSQLite.replaceUndefinedByNull(mValues); res = db.exec(sqlStmt, mVal); } else { res = db.exec(sqlStmt); } if (returnMode === "all" || returnMode === "one") { if (res && res.length > 0) { retValues = UtilsSQLite.getReturnedValues(res[0], returnMode); } } let lastId = await UtilsSQLite.getLastId(db); retObj["lastId"] = lastId; if (retValues != null && retValues.length > 0) retObj["values"] = retValues; return Promise.resolve(retObj); } catch (err) { const msg = err.message ? err.message : err; return Promise.reject(new Error(`run: ${msg}`)); } } static getReturnedValues(result, returnMode) { const retValues = []; for (let i = 0; i < result.values.length; i++) { let row = {}; for (let j = 0; j < result.columns.length; j++) { row[result.columns[j]] = result.values[i][j]; } retValues.push(row); if (returnMode === 'one') break; } return retValues; } static async deleteSQL(db, statement, values) { let sqlStmt = statement; try { const isLast = await UtilsSQLite.isLastModified(db, true); const isDel = await UtilsSQLite.isSqlDeleted(db, true); if (!isLast || !isDel) { return sqlStmt; } // Replace DELETE by UPDATE // set sql_deleted to 1 and the last_modified to // timenow const whereClause = UtilsSQLStatement.extractWhereClause(sqlStmt); if (!whereClause) { const msg = 'deleteSQL: cannot find a WHERE clause'; return Promise.reject(new Error(`${msg}`)); } const tableName = UtilsSQLStatement.extractTableName(sqlStmt); if (!tableName) { const msg = 'deleteSQL: cannot find a WHERE clause'; return Promise.reject(new Error(`${msg}`)); } const colNames = UtilsSQLStatement.extractColumnNames(whereClause); if (colNames.length === 0) { const msg = 'deleteSQL: Did not find column names in the WHERE Statement'; return Promise.reject(new Error(`${msg}`)); } const setStmt = 'sql_deleted = 1'; // Find REFERENCES if any and update the sql_deleted // column const hasToUpdate = await UtilsDelete.findReferencesAndUpdate(db, tableName, whereClause, colNames, values); if (hasToUpdate) { const whereStmt = whereClause.endsWith(';') ? whereClause.slice(0, -1) : whereClause; sqlStmt = `UPDATE ${tableName} SET ${setStmt} WHERE ${whereStmt} AND sql_deleted = 0;`; } else { sqlStmt = ''; } return Promise.resolve(sqlStmt); } catch (err) { let msg = err.message ? err.message : err; return Promise.reject(new Error(`deleteSQL: ${msg}`)); } } static async getTableList(db) { try { const result = await UtilsDrop.getTablesNames(db); return Promise.resolve(result); } catch (err) { const msg = err.message ? err.message : err; return Promise.reject(new Error(`getTableList: ${msg}`)); } } static async isTableExists(db, tableName) { try { let statement = 'SELECT name FROM sqlite_master WHERE '; statement += `type='table' AND name='${tableName}';`; const res = await UtilsSQLite.queryAll(db, statement, []); const ret = res.length > 0 ? true : false; return Promise.resolve(ret); } catch (err) { const msg = err.message ? err.message : err; return Promise.reject(new Error(`isTableExists: ${msg}`)); } } /** * isLastModified * @param db * @param isOpen */ static async isLastModified(db, isOpen) { if (!isOpen) { return Promise.reject('isLastModified: database not opened'); } try { const tableList = await UtilsDrop.getTablesNames(db); for (const table of tableList) { const tableNamesTypes = await UtilsJSON .getTableColumnNamesTypes(db, table); const tableColumnNames = tableNamesTypes.names; if (tableColumnNames.includes("last_modified")) { return Promise.resolve(true); } } } catch (err) { const msg = err.message ? err.message : err; return Promise.reject(new Error(`isLastModified: ${msg}`)); } } /** * isSqlDeleted * @param db * @param isOpen */ static async isSqlDeleted(db, isOpen) { if (!isOpen) { return Promise.reject('isSqlDeleted: database not opened'); } try { const tableList = await UtilsDrop.getTablesNames(db); for (const table of tableList) { const tableNamesTypes = await UtilsJSON .getTableColumnNamesTypes(db, table); const tableColumnNames = tableNamesTypes.names; if (tableColumnNames.includes("sql_deleted")) { return Promise.resolve(true); } } } catch (err) { const msg = err.message ? err.message : err; return Promise.reject(new Error(`isSqlDeleted: ${msg}`)); } } static async replaceUndefinedByNull(values) { const retValues = []; for (const val of values) { let mVal = val; if (typeof val === 'undefined') mVal = null; retValues.push(mVal); } return Promise.resolve(retValues); } static async backupTables(db) { const msg = 'BackupTables: '; let alterTables = {}; try { const tables = await UtilsDrop.getTablesNames(db); for (const table of tables) { try { const colNames = await UtilsSQLite.backupTable(db, table); alterTables[`${table}`] = colNames; } catch (err) { const msge = err.message ? err.message : err; return Promise.reject(new Error(`${msg}table ${table}: ` + `${msge}`)); } } return Promise.resolve(alterTables); } catch (err) { const msge = err.message ? err.message : err; return Promise.reject(new Error(`BackupTables: ${msge}`)); } } static async backupTable(db, table) { try { // start a transaction await UtilsSQLite.beginTransaction(db, true); // get the table's column names const colNames = await UtilsSQLite.getTableColumnNames(db, table); const tmpTable = `_temp_${table}`; // Drop the tmpTable if exists const delStmt = `DROP TABLE IF EXISTS ${tmpTable};`; await UtilsSQLite.run(db, delStmt, [], false, 'no'); // prefix the table with _temp_ let stmt = `ALTER TABLE ${table} RENAME `; stmt += `TO ${tmpTable};`; const lastId = await UtilsSQLite.run(db, stmt, [], false, 'no'); if (lastId < 0) { let msg = 'BackupTable: lastId < 0'; try { await UtilsSQLite.rollbackTransaction(db, true); } catch (err) { msg += `: ${err.message ? err.message : err}`; } return Promise.reject(new Error(`${msg}`)); } else { try { await UtilsSQLite.commitTransaction(db, true); return Promise.resolve(colNames); } catch (err) { const msge = err.message ? err.message : err; return Promise.reject(new Error('BackupTable: ' + `${msge}`)); } } } catch (err) { const msge = err.message ? err.message : err; return Promise.reject(new Error(`BackupTable: ${msge}`)); } } static async getTableColumnNames(db, tableName) { let resQuery = []; const retNames = []; const query = `PRAGMA table_info('${tableName}');`; try { resQuery = await UtilsSQLite.queryAll(db, query, []); if (resQuery.length > 0) { for (const query of resQuery) { retNames.push(query.name); } } return Promise.resolve(retNames); } catch (err) { const msg = err.message ? err.message : err; return Promise.reject(new Error('GetTableColumnNames: ' + `${msg}`)); } } static async findCommonColumns(db, alterTables) { let commonColumns = {}; try { // Get new table list const tables = await UtilsDrop.getTablesNames(db); if (tables.length === 0) { return Promise.reject(new Error('FindCommonColumns: get ' + "table's names failed")); } for (const table of tables) { // get the column's name const tableNames = await UtilsSQLite.getTableColumnNames(db, table); // find the common columns const keys = Object.keys(alterTables); if (keys.includes(table)) { commonColumns[table] = UtilsSQLite.arraysIntersection(alterTables[table], tableNames); } } return Promise.resolve(commonColumns); } catch (err) { const msg = err.message ? err.message : err; return Promise.reject(new Error(`FindCommonColumns: ${msg}`)); } } static arraysIntersection(a1, a2) { if (a1 != null && a2 != null) { const first = new Set(a1); const second = new Set(a2); return [...first].filter(item => second.has(item)); } else { return []; } } static async updateNewTablesData(db, commonColumns) { try { // start a transaction await UtilsSQLite.beginTransaction(db, true); const statements = []; const keys = Object.keys(commonColumns); keys.forEach(key => { const columns = commonColumns[key].join(','); let stmt = `INSERT INTO ${key} `; stmt += `(${columns}) `; stmt += `SELECT ${columns} FROM _temp_${key};`; statements.push(stmt); }); const changes = await UtilsSQLite.execute(db, statements.join('\n'), false); if (changes < 0) { let msg = 'updateNewTablesData: ' + 'changes < 0'; try { await UtilsSQLite.rollbackTransaction(db, true); } catch (err) { msg += `: ${err.message ? err.message : err}`; } return Promise.reject(new Error(`${msg}`)); } else { try { await UtilsSQLite.commitTransaction(db, true); return Promise.resolve(); } catch (err) { const msg = err.message ? err.message : err; return Promise.reject(new Error('updateNewTablesData: ' + `${msg}`)); } } } catch (err) { const msg = err.message ? err.message : err; return Promise.reject(new Error('updateNewTablesData: ' + `${msg}`)); } } } //# sourceMappingURL=utils-sqlite.js.map