sqlite-plugin-red
Version:
Adding a tab to show and edit the structure of a sqlite database. Needs node-red-node-sqlite.
124 lines (116 loc) • 4.84 kB
JavaScript
const Database = require('better-sqlite3')
const getStructure = (path) => {
const db = new Database(path)
const tables = db.prepare('SELECT name, sql FROM sqlite_master WHERE type=? AND name NOT LIKE ?')
const structure = []
for (const table of tables.iterate('table', 'sqlite_%')) {
const tableObject = { name: table.name }
// autoincrement info can only received by table sql. We could also retrieve all neccessary table infos from here,
// but it must be filtered from a string... we only do this here as AUTONINCREMENT can only be set once (and then there is only one primary key)
if (table.sql.includes('PRIMARY KEY') && table.sql.includes('AUTOINCREMENT')) {
tableObject.autoincrement = table.sql.split('PRIMARY KEY(')[1].split('"')[1]
}
structure.push(tableObject)
}
// ...therefore we get the column data from pragma table_info
structure.forEach((table, index) => {
const columns = db.prepare('PRAGMA table_info( "' + table.name + '" );').all()
// and change number to bool for checkbox using later
columns.forEach(c => {
if (c.notnull === 0) c.notnull = false
else c.notnull = true
if (c.pk === 0) c.pk = false
else c.notnull = true
c.ai = false
c.unique = false
})
// ... and add the autoincrement there
if (table.autoincrement) {
const autoIncrIndex = columns.findIndex(c => c.name === table.autoincrement)
if (autoIncrIndex !== -1) {
columns[autoIncrIndex].ai = true
delete table.autoincrement
}
}
// ... as well as if the column is unique
const uniqueFields = db.prepare('SELECT DISTINCT m.name as table_name, ii.name as column_name FROM sqlite_master AS m, pragma_index_list(m.name) AS il, pragma_index_info(il.name) AS ii WHERE m.type=? AND il.[unique] = 1;').bind('table').all()
const uniqueFieldsInThisTable = uniqueFields.filter(f => f.table_name === table.name)
uniqueFieldsInThisTable.forEach(uField => {
const uFieldIndex = columns.findIndex(c => c.name === uField.column_name)
if (uFieldIndex > -1) columns[uFieldIndex].unique = true
})
structure[index].columns = columns
})
db.close()
return structure
}
const runSql = (path, sql, command) => {
const db = new Database(path)
const statement = db.prepare(sql)
let result
try {
if (command === 'run') {
result = statement.run()
} else if (command === 'getAll') {
result = statement.all()
if (typeof result === 'undefined') result = {}
} else if (command === 'get') {
result = statement.get()
if (typeof result === 'undefined') result = {}
} else {
throw new Error('No command stated!')
}
return result
} catch(e) {
throw new Error(e)
}
}
const createTableWithNewProp = (path, fields, fieldsSql, backupName) => {
const tempBackup = backupName + '_temp_' + Date.now() + Math.floor(Math.random() * Number.MAX_SAFE_INTEGER)
createCopyDb(path, fields, fieldsSql, backupName, tempBackup)
commitCopyDb(path, backupName, tempBackup)
}
const createCopyDb = (path, fields, fieldsSql, tableName, backupName) => {
const db = new Database(path)
if (!tableName || !backupName) throw new Error('Missing table name!')
try {
// Steps for altering column props as stated in https://www.sqlite.org/lang_altertable.html
// Create new table
db.prepare(`CREATE TABLE "${backupName}" ${fieldsSql}`).run()
// Copy data
db.prepare(`INSERT INTO "${backupName}" (${fields}) SELECT ${fields} FROM "${tableName}"`).run()
return 'db copy'
} catch (e) {
db.prepare('DROP TABLE IF EXISTS "' + backupName + '"').run()
throw new Error(e)
}
}
const commitCopyDb = (path, tableName, backupName) => {
// to change a column property we have to go through the following steps... an alter table column will propably never be implemented (expect name changing)
const db = new Database(path)
if (!tableName || !backupName) throw new Error('Missing table name!')
try {
// Drop old table (first check if data was copied!)
const getFirstLineOrg = db.prepare('SELECT * FROM "' + tableName + '"').get()
const getFirstLineCopy = db.prepare('SELECT * FROM "' + backupName + '"').get()
if (getFirstLineOrg && !getFirstLineCopy) {
throw new Error('Something went wrong! Database copy is missing data -> Reset all db changes.')
} else {
db.prepare('DROP TABLE "' + tableName + '"').run()
// Rename new into old
db.prepare(`ALTER TABLE "${backupName}" RENAME TO "${tableName}";`).run()
db.close()
return 'Table changed'
}
} catch (e) {
db.prepare('DROP TABLE IF EXISTS "' + backupName + '"').run()
throw new Error(e)
}
}
module.exports = {
getStructure,
runSql,
createTableWithNewProp,
createCopyDb,
commitCopyDb
}