UNPKG

jeep-sqlite

Version:
647 lines (646 loc) 27.4 kB
import { UtilsSQLite } from "./utils-sqlite"; import { UtilsJSON } from "./utils-json"; export class UtilsExportJSON { static async createExportObject(db, sqlObj, exportProgress) { const retObj = {}; let tables = []; let views = []; let errmsg = ''; try { // get View's name views = await UtilsExportJSON.getViewsName(db); // get Table's name const resTables = await UtilsExportJSON.getTablesNameSQL(db); if (resTables.length === 0) { return Promise.reject(new Error("createExportObject: table's names failed")); } else { const isTable = await UtilsSQLite.isTableExists(db, 'sync_table'); if (!isTable && sqlObj.mode === 'partial') { return Promise.reject(new Error('No sync_table available')); } switch (sqlObj.mode) { case 'partial': { tables = await UtilsExportJSON.getTablesPartial(db, resTables, exportProgress); break; } case 'full': { tables = await UtilsExportJSON.getTablesFull(db, resTables, exportProgress); break; } default: { errmsg = 'createExportObject: expMode ' + sqlObj.mode + ' not defined'; break; } } if (errmsg.length > 0) { return Promise.reject(new Error(errmsg)); } if (tables.length > 0) { retObj.database = sqlObj.database; retObj.version = sqlObj.version; retObj.encrypted = sqlObj.encrypted; retObj.mode = sqlObj.mode; retObj.tables = tables; if (views.length > 0) { retObj.views = views; } } return Promise.resolve(retObj); } } catch (err) { return Promise.reject(new Error('createExportObject: ' + err.message)); } } static async getViewsName(mDb) { const views = []; let sql = 'SELECT name,sql FROM sqlite_master WHERE '; sql += "type='view' AND name NOT LIKE 'sqlite_%';"; let retQuery = []; try { retQuery = await UtilsSQLite.queryAll(mDb, sql, []); for (const query of retQuery) { const view = {}; view.name = query.name; view.value = query.sql.substring(query.sql.indexOf('AS ') + 3); views.push(view); } return Promise.resolve(views); } catch (err) { return Promise.reject(new Error(`getViewsName: ${err.message}`)); } } static async getTablesFull(db, resTables, exportProgress) { const tables = []; let errmsg = ''; try { // Loop through the tables for (const rTable of resTables) { let tableName; let sqlStmt; if (rTable.name) { tableName = rTable.name; } else { errmsg = 'GetTablesFull: no name'; break; } if (rTable.sql) { sqlStmt = rTable.sql; } else { errmsg = 'GetTablesFull: no sql'; break; } const table = {}; // create Table's Schema const schema = await UtilsExportJSON.getSchema(sqlStmt /*, tableName*/); if (schema.length === 0) { errmsg = 'GetTablesFull: no Schema returned'; break; } // check schema validity await UtilsJSON.checkSchemaValidity(schema); // create Table's indexes if any const indexes = await UtilsExportJSON.getIndexes(db, tableName); if (indexes.length > 0) { // check indexes validity await UtilsJSON.checkIndexesValidity(indexes); } // create Table's triggers if any const triggers = await UtilsExportJSON.getTriggers(db, tableName); if (triggers.length > 0) { // check triggers validity await UtilsJSON.checkTriggersValidity(triggers); } let msg = `Full: Table ${tableName} schema export completed ...`; exportProgress.emit({ progress: msg }); // create Table's Data const query = `SELECT * FROM ${tableName};`; const values = await UtilsJSON.getValues(db, query, tableName); table.name = tableName; if (schema.length > 0) { table.schema = schema; } else { errmsg = `GetTablesFull: must contain schema`; break; } if (indexes.length > 0) { table.indexes = indexes; } if (triggers.length > 0) { table.triggers = triggers; } if (values.length > 0) { table.values = values; } if (Object.keys(table).length <= 1) { errmsg = `GetTablesFull: table ${tableName} is not a jsonTable`; break; } msg = `Full: Table ${tableName} table data export completed ...`; exportProgress.emit({ progress: msg }); tables.push(table); } if (errmsg.length > 0) { return Promise.reject(new Error(errmsg)); } return Promise.resolve(tables); } catch (err) { return Promise.reject(new Error(`GetTablesFull: ${err.message}`)); } } static async getSchema(sqlStmt /*, tableName: string*/) { const schema = []; // take the substring between parenthesis const openPar = sqlStmt.indexOf('('); const closePar = sqlStmt.lastIndexOf(')'); let sstr = sqlStmt.substring(openPar + 1, closePar); // check if there is other parenthesis and replace the ',' by '§' try { sstr = await UtilsExportJSON.modEmbeddedParentheses(sstr); const sch = sstr.split(","); // for each element of the array split the // first word as key for (let j = 0; j < sch.length; j++) { let row = []; const scht = sch[j].replace(/\n/g, "").trim(); row[0] = scht.substring(0, scht.indexOf(" ")); row[1] = scht.substring(scht.indexOf(" ") + 1); const jsonRow = {}; if (row[0].toUpperCase() === "FOREIGN") { const oPar = scht.indexOf("("); const cPar = scht.indexOf(")"); const fk = scht.substring(oPar + 1, cPar); const fknames = fk.split('§'); row[0] = fknames.join(','); row[0] = row[0].replace(/, /g, ","); row[1] = scht.substring(cPar + 2); jsonRow['foreignkey'] = row[0]; } else if (row[0].toUpperCase() === "PRIMARY") { row = UtilsExportJSON.getRow(scht, "CPK"); jsonRow['constraint'] = row[0]; } else if (row[0].toUpperCase() === "UNIQUE") { row = UtilsExportJSON.getRow(scht, "CUN"); jsonRow['constraint'] = row[0]; } else if (row[0].toUpperCase() === "CONSTRAINT") { let tRow = []; const row1t = row[1].trim(); tRow[0] = row1t.substring(0, row1t.indexOf(" ")); tRow[1] = row1t.substring(row1t.indexOf(" ") + 1); row[0] = tRow[0]; jsonRow['constraint'] = row[0]; row[1] = tRow[1]; } else { jsonRow['column'] = row[0]; } jsonRow['value'] = row[1].replace(/§/g, ","); schema.push(jsonRow); } return Promise.resolve(schema); } catch (err) { return Promise.reject(new Error(err.message)); } } static getRow(scht, cName) { let row = []; const oPar = scht.indexOf("("); const cPar = scht.indexOf(")"); const values = scht.substring(oPar + 1, cPar); const valuesnames = values.split('§'); row[0] = `${cName}_` + valuesnames.join('_'); row[0] = row[0].replace(/_ /g, "_"); row[1] = scht; return row; } static async getIndexes(db, tableName) { const indexes = []; let errmsg = ''; try { let stmt = 'SELECT name,tbl_name,sql FROM sqlite_master WHERE '; stmt += `type = 'index' AND tbl_name = '${tableName}' `; stmt += `AND sql NOTNULL;`; const retIndexes = await UtilsSQLite.queryAll(db, stmt, []); if (retIndexes.length > 0) { for (const rIndex of retIndexes) { const keys = Object.keys(rIndex); if (keys.length === 3) { if (rIndex['tbl_name'] === tableName) { const sql = rIndex['sql']; const mode = sql.includes('UNIQUE') ? 'UNIQUE' : ''; const oPar = sql.lastIndexOf('('); const cPar = sql.lastIndexOf(')'); const index = {}; index.name = rIndex['name']; index.value = sql.slice(oPar + 1, cPar); if (mode.length > 0) index.mode = mode; indexes.push(index); } else { errmsg = `GetIndexes: Table ${tableName} doesn't match`; break; } } else { errmsg = `GetIndexes: Table ${tableName} creating indexes`; break; } } if (errmsg.length > 0) { return Promise.reject(new Error(errmsg)); } } return Promise.resolve(indexes); } catch (err) { return Promise.reject(new Error(`GetIndexes: ${err.message}`)); } } static async getTriggers(db, tableName) { const triggers = []; try { let stmt = 'SELECT name,tbl_name,sql FROM sqlite_master WHERE '; stmt += `type = 'trigger' AND tbl_name = '${tableName}' `; stmt += `AND sql NOT NULL;`; const retTriggers = await UtilsSQLite.queryAll(db, stmt, []); if (retTriggers.length > 0) { for (const rTrg of retTriggers) { const keys = Object.keys(rTrg); if (keys.length === 3) { if (rTrg['tbl_name'] === tableName) { const sql = rTrg['sql']; const name = rTrg['name']; let sqlArr = sql.split(name); if (sqlArr.length != 2) { return Promise.reject(new Error(`GetTriggers: sql split name does not return 2 values`)); } if (!sqlArr[1].includes(tableName)) { return Promise.reject(new Error(`GetTriggers: sql split does not contains ${tableName}`)); } const timeEvent = sqlArr[1].split(tableName, 1)[0].trim(); sqlArr = sqlArr[1].split(timeEvent + ' ' + tableName); if (sqlArr.length != 2) { return Promise.reject(new Error(`GetTriggers: sql split tableName does not return 2 values`)); } let condition = ''; let logic = ''; if (sqlArr[1].trim().substring(0, 5).toUpperCase() !== 'BEGIN') { sqlArr = sqlArr[1].trim().split('BEGIN'); if (sqlArr.length != 2) { return Promise.reject(new Error(`GetTriggers: sql split BEGIN does not return 2 values`)); } condition = sqlArr[0].trim(); logic = 'BEGIN' + sqlArr[1]; } else { logic = sqlArr[1].trim(); } const trigger = {}; trigger.name = name; trigger.logic = logic; if (condition.length > 0) trigger.condition = condition; trigger.timeevent = timeEvent; triggers.push(trigger); } else { return Promise.reject(new Error(`GetTriggers: Table ${tableName} doesn't match`)); } } else { return Promise.reject(new Error(`GetTriggers: Table ${tableName} creating indexes`)); } } } return Promise.resolve(triggers); } catch (err) { return Promise.reject(new Error(`GetTriggers: ${err.message}`)); } } static async getTablesPartial(db, resTables, exportProgress) { const tables = []; let modTables = {}; let syncDate = 0; let modTablesKeys = []; let errmsg = ''; try { // Get the syncDate and the Modified Tables const partialModeData = await UtilsExportJSON .getPartialModeData(db, resTables); if (Object.keys(partialModeData).includes('syncDate')) { syncDate = partialModeData.syncDate; } if (Object.keys(partialModeData).includes('modTables')) { modTables = partialModeData.modTables; modTablesKeys = Object.keys(modTables); } // Loop trough tables for (const rTable of resTables) { let tableName = ''; let sqlStmt = ''; if (rTable.name) { tableName = rTable.name; } else { errmsg = 'GetTablesFull: no name'; break; } if (rTable.sql) { sqlStmt = rTable.sql; } else { errmsg = 'GetTablesFull: no sql'; break; } if (modTablesKeys.length == 0 || modTablesKeys.indexOf(tableName) === -1 || modTables[tableName] == 'No') { continue; } const table = {}; let schema = []; let indexes = []; let triggers = []; table.name = rTable; if (modTables[table.name] === 'Create') { // create Table's Schema schema = await UtilsExportJSON.getSchema(sqlStmt /*, tableName*/); if (schema.length > 0) { // check schema validity await UtilsJSON.checkSchemaValidity(schema); } // create Table's indexes if any indexes = await UtilsExportJSON.getIndexes(db, tableName); if (indexes.length > 0) { // check indexes validity await UtilsJSON.checkIndexesValidity(indexes); } // create Table's triggers if any triggers = await UtilsExportJSON.getTriggers(db, tableName); if (triggers.length > 0) { // check triggers validity await UtilsJSON.checkTriggersValidity(triggers); } } let msg = `Partial: Table ${tableName} schema export completed ...`; exportProgress.emit({ progress: msg }); // create Table's Data let query = ''; if (modTables[tableName] === 'Create') { query = `SELECT * FROM ${tableName};`; } else { query = `SELECT * FROM ${tableName} ` + `WHERE last_modified > ${syncDate};`; } const values = await UtilsJSON.getValues(db, query, tableName); // check the table object validity table.name = tableName; if (schema.length > 0) { table.schema = schema; } if (indexes.length > 0) { table.indexes = indexes; } if (triggers.length > 0) { table.triggers = triggers; } if (values.length > 0) { table.values = values; } if (Object.keys(table).length <= 1) { errmsg = `GetTablesPartial: table ${tableName} is not a jsonTable`; break; } msg = `Partial: Table ${tableName} table data export completed ...`; exportProgress.emit({ progress: msg }); tables.push(table); } if (errmsg.length > 0) { return Promise.reject(new Error(errmsg)); } return Promise.resolve(tables); } catch (err) { return Promise.reject(new Error(`GetTablesPartial: ${err.message}`)); } } static async getPartialModeData(db, resTables) { const retData = {}; try { // get the synchronization date const syncDate = await UtilsExportJSON.getSynchroDate(db); if (syncDate <= 0) { return Promise.reject(new Error(`GetPartialModeData: no syncDate`)); } // get the tables which have been updated // since last synchronization const modTables = await UtilsExportJSON.getTablesModified(db, resTables, syncDate); if (modTables.length <= 0) { return Promise.reject(new Error(`GetPartialModeData: no modTables`)); } retData.syncDate = syncDate; retData.modTables = modTables; return Promise.resolve(retData); } catch (err) { return Promise.reject(new Error(`GetPartialModeData: ${err.message}`)); } } static async getTablesNameSQL(db) { let sql = 'SELECT name,sql FROM sqlite_master WHERE '; sql += "type='table' AND name NOT LIKE 'sync_table' "; sql += "AND name NOT LIKE '_temp_%' "; sql += "AND name NOT LIKE 'sqlite_%';"; try { const retQuery = await UtilsSQLite.queryAll(db, sql, []); return Promise.resolve(retQuery); } catch (err) { return Promise.reject(new Error(`getTablesNamesSQL: ${err.message}`)); } } static async getTablesModified(db, tables, syncDate) { let errmsg = ''; try { const retModified = {}; for (const rTable of tables) { let mode; // get total count of the table let stmt = 'SELECT count(*) AS tcount '; stmt += `FROM ${rTable.name};`; let retQuery = await UtilsSQLite.queryAll(db, stmt, []); if (retQuery.length != 1) { errmsg = 'GetTableModified: total ' + 'count not returned'; break; } const totalCount = retQuery[0]['tcount']; // get total count of modified since last sync stmt = 'SELECT count(*) AS mcount FROM '; stmt += `${rTable.name} WHERE last_modified > `; stmt += `${syncDate};`; retQuery = await UtilsSQLite.queryAll(db, stmt, []); if (retQuery.length != 1) break; const totalModifiedCount = retQuery[0]['mcount']; if (totalModifiedCount === 0) { mode = 'No'; } else if (totalCount === totalModifiedCount) { mode = 'Create'; } else { mode = 'Modified'; } const key = rTable.name; retModified[key] = mode; } if (errmsg.length > 0) { return Promise.reject(new Error(errmsg)); } return Promise.resolve(retModified); } catch (err) { return Promise.reject(new Error(`GetTableModified: ${err.message}`)); } } static async getSynchroDate(db) { try { const stmt = `SELECT sync_date FROM sync_table WHERE id = 1;`; const res = await UtilsSQLite.queryAll(db, stmt, []); return Promise.resolve(res[0]["sync_date"]); } catch (err) { const msg = `GetSynchroDate: ${err.message}`; return Promise.reject(new Error(msg)); } } static async getLastExportDate(db) { try { const stmt = `SELECT sync_date FROM sync_table WHERE id = 2;`; const res = await UtilsSQLite.queryAll(db, stmt, []); if (res.length === 0) { return Promise.resolve(-1); } else { return Promise.resolve(res[0]["sync_date"]); } } catch (err) { const msg = `getLastExport: ${err.message}`; return Promise.reject(new Error(msg)); } } static async setLastExportDate(db, lastExportedDate) { try { const isTable = await UtilsSQLite.isTableExists(db, 'sync_table'); if (!isTable) { return Promise.reject(new Error('setLastExportDate: No sync_table available')); } const sDate = Math.round(new Date(lastExportedDate).getTime() / 1000); let stmt = ""; if (await UtilsExportJSON.getLastExportDate(db) > 0) { stmt = `UPDATE sync_table SET sync_date = ${sDate} WHERE id = 2;`; } else { stmt = `INSERT INTO sync_table (sync_date) VALUES (${sDate});`; } const changes = await UtilsSQLite.execute(db, stmt, false); if (changes < 0) { return { result: false, message: 'setLastExportDate failed' }; } else { return { result: true }; } } catch (err) { return { result: false, message: `setLastExportDate failed: ${err.message}` }; } } static async delExportedRows(db) { let lastExportDate; try { // check if synchronization table exists const isTable = await UtilsSQLite.isTableExists(db, 'sync_table'); if (!isTable) { return Promise.reject(new Error('DelExportedRows: No sync_table available')); } // get the last export date lastExportDate = await UtilsExportJSON.getLastExportDate(db); if (lastExportDate < 0) { return Promise.reject(new Error("DelExportedRows: no last exported date available")); } // get the table' name list const resTables = await UtilsSQLite.getTableList(db); if (resTables.length === 0) { return Promise.reject(new Error("DelExportedRows: No table's names returned")); } // Loop through the tables for (const table of resTables) { let lastId = -1; // define the delete statement const delStmt = `DELETE FROM ${table} WHERE sql_deleted = 1 AND last_modified < ${lastExportDate};`; lastId = await UtilsSQLite.run(db, delStmt, [], true, 'no'); if (lastId < 0) { return Promise.reject(new Error('DelExportedRows: lastId < 0')); } } } catch (err) { return Promise.reject(new Error(`DelExportedRows failed: ${err.message}`)); } } static async modEmbeddedParentheses(sstr) { const oParArray = UtilsExportJSON.indexOfChar(sstr, '('); const cParArray = UtilsExportJSON.indexOfChar(sstr, ')'); if (oParArray.length != cParArray.length) { return Promise.reject("ModEmbeddedParentheses: Not same number of '(' & ')'"); } if (oParArray.length === 0) { return Promise.resolve(sstr); } let resStmt = sstr.substring(0, oParArray[0] - 1); for (let i = 0; i < oParArray.length; i++) { let str; if (i < oParArray.length - 1) { if (oParArray[i + 1] < cParArray[i]) { str = sstr.substring(oParArray[i] - 1, cParArray[i + 1]); i++; } else { str = sstr.substring(oParArray[i] - 1, cParArray[i]); } } else { str = sstr.substring(oParArray[i] - 1, cParArray[i]); } const newS = str.replace(/,/g, "§"); resStmt += newS; if (i < oParArray.length - 1) { resStmt += sstr.substring(cParArray[i], oParArray[i + 1] - 1); } } resStmt += sstr.substring(cParArray[cParArray.length - 1], sstr.length); return Promise.resolve(resStmt); } static indexOfChar(str, char) { let tmpArr = [...str]; char = char.toLowerCase(); return tmpArr.reduce((results, elem, idx) => elem.toLowerCase() === char ? [...results, idx] : results, []); } } //# sourceMappingURL=utils-exportJson.js.map