jeep-sqlite
Version:
Browser SQLite Stencil Component
549 lines (548 loc) • 21.1 kB
JavaScript
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