From 742b9332076a724f1293246ad24e63026d8677ea Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Tue, 20 Feb 2018 15:23:38 +0530 Subject: [PATCH] foreign keys --- backends/database.js | 64 ++++++++++++++++++++++++++++++++++---------- backends/mysql.js | 6 ++--- backends/sqlite.js | 64 ++++++++++++++++++++++++++++++++++++++------ client/view/form.js | 14 +++++++--- model/document.js | 2 +- model/index.js | 6 ++--- 6 files changed, 124 insertions(+), 32 deletions(-) diff --git a/backends/database.js b/backends/database.js index 08b6269a..9048c08c 100644 --- a/backends/database.js +++ b/backends/database.js @@ -30,54 +30,90 @@ module.exports = class Database extends Observable { await this.commit(); } - async createTable(doctype) { + async createTable(doctype, newName=null) { let meta = frappe.getMeta(doctype); let columns = []; - let values = []; + let indexes = []; for (let field of meta.getValidFields({ withChildren: false })) { if (this.type_map[field.fieldtype]) { - columns.push(this.getColumnDefinition(field)); + this.updateColumnDefinition(field, columns, indexes); } } - return await this.runCreateTableQuery(doctype, columns, values); + return await this.runCreateTableQuery(newName || doctype, columns, indexes); } async tableExists(table) { // return true if table exists } - async runCreateTableQuery(doctype, columns, values) { + async runCreateTableQuery(doctype, columns, indexes) { // override } - getColumnDefinition(df) { + updateColumnDefinition(field, columns, indexes) { // return `${df.fieldname} ${this.type_map[df.fieldtype]} ${ ? "PRIMARY KEY" : ""} ${df.required && !df.default ? "NOT NULL" : ""} ${df.default ? `DEFAULT ${df.default}` : ""}` } async alterTable(doctype) { // get columns + let newColumns = await this.getNewColumns(doctype); + let newForeignKeys = await this.getNewForeignKeys(doctype); + + if (newColumns.length) { + await this.addColumns(doctype, newColumns); + } + if (newForeignKeys.length) { + await this.addForeignKeys(doctype); + } + } + + async getNewColumns(doctype) { let tableColumns = await this.getTableColumns(doctype); let meta = frappe.getMeta(doctype); - let values = []; - + let newColumns = []; for (let field of meta.getValidFields({ withChildren: false })) { if (!tableColumns.includes(field.fieldname) && this.type_map[field.fieldtype]) { - values = [] - if (field.default) { - values.push(field.default); - } - await this.runAlterTableQuery(doctype, field, values); + newColumns.push(field); } } + return newColumns; + } + + async addColumns(doctype, newColumns) { + for (let field of newColumns) { + await this.runAddColumnQuery(doctype, field); + } + } + + async getNewForeignKeys(doctype) { + let foreignKeys = await this.getForeignKeys(doctype); + let newForeignKeys = []; + let meta = frappe.getMeta(doctype); + for (let field of meta.getValidFields({ withChildren: false})) { + if (field.fieldtype==='Link' && !foreignKeys.includes(field.fieldname)) { + newForeignKeys.push(field); + } + } + return newForeignKeys; + } + + async addForeignKeys(doctype, newForeignKeys) { + for (let field of newForeignKeys) { + this.addForeignKey(doctype, field); + } + } + + async getForeignKey(doctype, field) { + } async getTableColumns(doctype) { return []; } - async runAlterTableQuery(doctype, field, values) { + async runAddColumnQuery(doctype, field) { // alter table {doctype} add column ({column_def}); } diff --git a/backends/mysql.js b/backends/mysql.js index 9f032e44..f1a1b4a5 100644 --- a/backends/mysql.js +++ b/backends/mysql.js @@ -52,8 +52,8 @@ module.exports = class mysqlDatabase extends Database{ } - getColumnDefinition(df) { - return `${df.fieldname} ${this.type_map[df.fieldtype]} ${df.reqd && !df.default ? "not null" : ""} ${df.default ? `default '${df.default}'` : ""}` + updateColumnDefinition(df, columns, indexes) { + columns.push(`${df.fieldname} ${this.type_map[df.fieldtype]} ${df.reqd && !df.default ? "not null" : ""} ${df.default ? `default '${df.default}'` : ""}`); } async getTableColumns(doctype) { @@ -61,7 +61,7 @@ module.exports = class mysqlDatabase extends Database{ } - async runAlterTableQuery(doctype) { + async runAddColumnQuery(doctype) { await this.run(`ALTER TABLE ${doctype} ADD COLUMN ${this.get_column_definition(df)}`, values); } diff --git a/backends/sqlite.js b/backends/sqlite.js index 32a0dcb9..cad2863c 100644 --- a/backends/sqlite.js +++ b/backends/sqlite.js @@ -18,7 +18,7 @@ module.exports = class sqliteDatabase extends Database { if (debug) { this.conn.on('trace', (trace) => console.log(trace)); } - resolve(); + this.run('PRAGMA foreign_keys=ON').then(resolve); }); }); } @@ -28,22 +28,70 @@ module.exports = class sqliteDatabase extends Database { return (name && name.length) ? true : false; } - async runCreateTableQuery(doctype, columns, values) { - const query = `CREATE TABLE IF NOT EXISTS ${doctype} ( - ${columns.join(", ")})`; + async alterTable(doctype) { + let newColumns = await this.getNewColumns(doctype); + let newForeignKeys = await this.getNewForeignKeys(doctype); - return await this.run(query, values); + if (newColumns.length || newForeignKeys.length) { + await this.migrateToNewTable(doctype); + } } - getColumnDefinition(df) { - return `${df.fieldname} ${this.type_map[df.fieldtype]} ${ df.fieldname==="name" ? "PRIMARY KEY" : ""} ${df.required && !df.default ? "NOT NULL" : ""} ${df.default ? `DEFAULT ${df.default}` : ""}` + async migrateToNewTable(doctype) { + await this.run('PRAGMA foreign_keys=OFF'); + await this.run('BEGIN TRANSACTION'); + + // create temp table + await this.createTable(doctype, 'TEMP' + doctype); + + // copy from old to new table + await this.run(`INSERT INTO TEMP${doctype} SELECT * from ${doctype}`); + + // drop old table + await this.run(`DROP TABLE ${doctype}`); + + // rename new table + await this.run(`ALTER TABLE TEMP${doctype} RENAME TO ${doctype}`); + + await this.run('COMMIT'); + await this.run('PRAGMA foreign_keys=ON'); + } + + async runCreateTableQuery(doctype, columns, indexes) { + const query = `CREATE TABLE IF NOT EXISTS ${doctype} ( + ${columns.join(", ")} ${indexes.length ? (", " + indexes.join(", ")) : ''})`; + + return await this.run(query); + } + + updateColumnDefinition(field, columns, indexes) { + let def = `${field.fieldname} ${this.type_map[field.fieldtype]}`; + if (field.fieldname==='name') { + def += ' PRIMARY KEY NOT NULL'; + } + else if (field.reqd) { + def += ' NOT NULL'; + } + if (field.default) { + def += `DEFAULT ${field.default}`; + } + + columns.push(def); + + if (field.fieldtype==='Link' && field.target) { + indexes.push(`FOREIGN KEY (${field.fieldname}) REFERENCES ${field.target} ON UPDATE RESTRICT ON DELETE RESTRICT`); + } } async getTableColumns(doctype) { return (await this.sql(`PRAGMA table_info(${doctype})`)).map(d => d.name); } - async runAlterTableQuery(doctype, field, values) { + async getForeignKeys(doctype) { + return (await this.sql(`PRAGMA foreign_key_list(${doctype})`)).map(d => d.from); + } + + async runAddColumnQuery(doctype, field, values) { await this.run(`ALTER TABLE ${doctype} ADD COLUMN ${this.getColumnDefinition(field)}`, values); } diff --git a/client/view/form.js b/client/view/form.js index ea06f0d6..1d8b3b92 100644 --- a/client/view/form.js +++ b/client/view/form.js @@ -52,9 +52,7 @@ module.exports = class BaseForm extends Observable { if (!this.meta.isSingle && this.actions.includes('delete')) { let menu = this.container.getDropdown(frappe._('Menu')); menu.addItem(frappe._("Delete"), async (e) => { - await this.doc.delete(); - this.showAlert('Deleted', 'success'); - this.trigger('delete'); + await this.delete(); }); } @@ -178,6 +176,16 @@ module.exports = class BaseForm extends Observable { await this.trigger('submit'); } + async delete() { + try { + await this.doc.delete(); + this.showAlert('Deleted', 'success'); + this.trigger('delete'); + } catch (e) { + this.showAlert(e, 'danger'); + } + } + refresh() { for(let control of this.controlList) { control.refresh(); diff --git a/model/document.js b/model/document.js index 030d633b..86a95a48 100644 --- a/model/document.js +++ b/model/document.js @@ -133,7 +133,7 @@ module.exports = class BaseDocument extends Observable { this.owner = frappe.session.user; this.creation = now; } - this.modified_by = frappe.session.user; + this.modifieldBy = frappe.session.user; this.modified = now; } diff --git a/model/index.js b/model/index.js index 8bd01a39..37e9aef4 100644 --- a/model/index.js +++ b/model/index.js @@ -22,10 +22,10 @@ module.exports = { ], parent_fields: [ { - fieldname: 'owner', fieldtype: 'Link', required: 1, options: 'User' + fieldname: 'owner', fieldtype: 'Text', required: 1 }, { - fieldname: 'modified_by', fieldtype: 'Link', required: 1, options: 'User' + fieldname: 'modifieldBy', fieldtype: 'Text', required: 1 }, { fieldname: 'creation', fieldtype: 'Datetime', required: 1 @@ -48,7 +48,7 @@ module.exports = { fieldname: 'parent', fieldtype: 'Data', required: 1 }, { - fieldname: 'parenttype', fieldtype: 'Link', required: 1, options: 'DocType' + fieldname: 'parenttype', fieldtype: 'Text', required: 1 }, { fieldname: 'parentfield', fieldtype: 'Data', required: 1