diff --git a/src/common/libs/uidGen.js b/src/common/libs/uidGen.js index f109f023..f8def6b1 100644 --- a/src/common/libs/uidGen.js +++ b/src/common/libs/uidGen.js @@ -1,4 +1,8 @@ -'use strict'; -export function uidGen () { - return Math.random().toString(36).substr(2, 9).toUpperCase(); +/** + * @export + * @param {String} [prefix] + * @returns {String} Unique ID + */ +export function uidGen (prefix) { + return (prefix ? `${prefix}:` : '') + Math.random().toString(36).substr(2, 9).toUpperCase(); }; diff --git a/src/main/ipc-handlers/tables.js b/src/main/ipc-handlers/tables.js index d66de0b4..6e9a7361 100644 --- a/src/main/ipc-handlers/tables.js +++ b/src/main/ipc-handlers/tables.js @@ -25,6 +25,16 @@ export default (connections) => { } }); + ipcMain.handle('get-key-usage', async (event, { uid, schema, table }) => { + try { + const result = await InformationSchema.getKeyUsage(connections[uid], schema, table); + return { status: 'success', response: result }; + } + catch (err) { + return { status: 'error', response: err.toString() }; + } + }); + ipcMain.handle('updateTableCell', async (event, params) => { try { const result = await Tables.updateTableCell(connections[params.uid], params); diff --git a/src/main/models/InformationSchema.js b/src/main/models/InformationSchema.js index 130e95bd..2729611f 100644 --- a/src/main/models/InformationSchema.js +++ b/src/main/models/InformationSchema.js @@ -38,4 +38,29 @@ export default class { }; }); } + + static async getKeyUsage (connection, schema, table) { + // SELECT * FROM information_schema.KEY_COLUMN_USAGE WHERE TABLE_SCHEMA='fep-gprs' AND TABLE_NAME='struttura_macchine' AND REFERENCED_TABLE_NAME IS NOT NULL; + + const { rows } = await connection + .select('*') + .schema('information_schema') + .from('KEY_COLUMN_USAGE') + .where({ TABLE_SCHEMA: `= '${schema}'`, TABLE_NAME: `= '${table}'`, REFERENCED_TABLE_NAME: 'IS NOT NULL' }) + .run(); + + return rows.map(field => { + return { + schema: field.TABLE_SCHEMA, + table: field.TABLE_NAME, + column: field.COLUMN_NAME, + position: field.ORDINAL_POSITION, + constraintPosition: field.POSITION_IN_UNIQUE_CONSTRAINT, + constraintName: field.CONSTRAINT_NAME, + refSchema: field.REFERENCED_TABLE_SCHEMA, + refTable: field.REFERENCED_TABLE_NAME, + refColumn: field.REFERENCED_COLUMN_NAME + }; + }); + } } diff --git a/src/renderer/components/ModalNewConnection.vue b/src/renderer/components/ModalNewConnection.vue index 6ff3e998..91ac9169 100644 --- a/src/renderer/components/ModalNewConnection.vue +++ b/src/renderer/components/ModalNewConnection.vue @@ -170,7 +170,7 @@ export default { user: 'root', password: '', ask: false, - uid: uidGen() + uid: uidGen('C') }, toast: { status: '', diff --git a/src/renderer/components/Workspace.vue b/src/renderer/components/Workspace.vue index dea186f4..1db9d182 100644 --- a/src/renderer/components/Workspace.vue +++ b/src/renderer/components/Workspace.vue @@ -42,6 +42,7 @@ v-for="tab of queryTabs" v-show="selectedTab === tab.uid" :key="tab.uid" + :tab-uid="tab.uid" :connection="connection" /> diff --git a/src/renderer/components/WorkspaceQueryTab.vue b/src/renderer/components/WorkspaceQueryTab.vue index a9bce245..72abc614 100644 --- a/src/renderer/components/WorkspaceQueryTab.vue +++ b/src/renderer/components/WorkspaceQueryTab.vue @@ -27,9 +27,10 @@
@@ -53,7 +54,8 @@ export default { }, mixins: [tableTabs], props: { - connection: Object + connection: Object, + tabUid: String }, data () { return { @@ -61,7 +63,7 @@ export default { lastQuery: '', isQuering: false, results: {}, - fields: [] + selectedFields: [] }; }, computed: { @@ -72,37 +74,39 @@ export default { return this.getWorkspace(this.connection.uid); }, table () { - if (this.results.fields.length) + if ('fields' in this.results && this.results.fields.length) return this.results.fields[0].orgTable; return ''; }, schema () { - if (this.results.fields.length) + if ('fields' in this.results && this.results.fields.length) return this.results.fields[0].db; - return ''; + return this.workspace.breadcrumbs.schema; } }, methods: { ...mapActions({ - addNotification: 'notifications/addNotification' + addNotification: 'notifications/addNotification', + setTabFields: 'workspaces/setTabFields', + setTabKeyUsage: 'workspaces/setTabKeyUsage' }), async runQuery (query) { if (!query) return; this.isQuering = true; this.results = {}; - this.fields = []; - let selectedFields = []; + this.setTabFields({ cUid: this.connection.uid, tUid: this.tabUid, fields: [] }); - try { + try { // Query Data const params = { uid: this.connection.uid, + schema: this.schema, query }; const { status, response } = await Connection.rawQuery(params); if (status === 'success') { this.results = response; - selectedFields = response.fields.map(field => field.orgName); + this.selectedFields = response.fields.map(field => field.orgName); } else this.addNotification({ status: 'error', message: response }); @@ -111,7 +115,7 @@ export default { this.addNotification({ status: 'error', message: err.stack }); } - try { + try { // Table data const params = { uid: this.connection.uid, schema: this.schema, @@ -119,8 +123,27 @@ export default { }; const { status, response } = await Tables.getTableColumns(params); + if (status === 'success') { + const fields = response.filter(field => this.selectedFields.includes(field.name)); + this.setTabFields({ cUid: this.connection.uid, tUid: this.tabUid, fields }); + } + else + this.addNotification({ status: 'error', message: response }); + } + catch (err) { + this.addNotification({ status: 'error', message: err.stack }); + } + + try { // Key usage (foreign keys) + const params = { + uid: this.connection.uid, + schema: this.schema, + table: this.table + }; + + const { status, response } = await Tables.getKeyUsage(params); if (status === 'success') - this.fields = response.filter(field => selectedFields.includes(field.name)); + this.setTabKeyUsage({ cUid: this.connection.uid, tUid: this.tabUid, keyUsage: response }); else this.addNotification({ status: 'error', message: response }); } diff --git a/src/renderer/components/WorkspaceQueryTable.vue b/src/renderer/components/WorkspaceQueryTable.vue index 63f03bdc..df5fb791 100644 --- a/src/renderer/components/WorkspaceQueryTable.vue +++ b/src/renderer/components/WorkspaceQueryTable.vue @@ -53,6 +53,7 @@ :key="row._id" :row="row" :fields="fields" + :key-usage="keyUsage" class="tr" :class="{'selected': selectedRows.includes(row._id)}" @selectRow="selectRow($event, row._id)" @@ -70,7 +71,7 @@ import { uidGen } from 'common/libs/uidGen'; import BaseVirtualScroll from '@/components/BaseVirtualScroll'; import WorkspaceQueryTableRow from '@/components/WorkspaceQueryTableRow'; import TableContext from '@/components/WorkspaceQueryTableContext'; -import { mapActions } from 'vuex'; +import { mapActions, mapGetters } from 'vuex'; export default { name: 'WorkspaceQueryTable', @@ -81,7 +82,7 @@ export default { }, props: { results: Object, - fields: Array + tabUid: [String, Number] }, data () { return { @@ -96,6 +97,9 @@ export default { }; }, computed: { + ...mapGetters({ + getWorkspaceTab: 'workspaces/getWorkspaceTab' + }), primaryField () { return this.fields.filter(field => ['pri', 'uni'].includes(field.key))[0] || false; }, @@ -114,6 +118,12 @@ export default { else return this.localResults; }, + fields () { + return this.getWorkspaceTab(this.tabUid) ? this.getWorkspaceTab(this.tabUid).fields : []; + }, + keyUsage () { + return this.getWorkspaceTab(this.tabUid) ? this.getWorkspaceTab(this.tabUid).keyUsage : []; + }, scrollElement () { return this.$refs.tableWrapper; } diff --git a/src/renderer/components/WorkspaceQueryTableRow.vue b/src/renderer/components/WorkspaceQueryTableRow.vue index e44766ac..fece3791 100644 --- a/src/renderer/components/WorkspaceQueryTableRow.vue +++ b/src/renderer/components/WorkspaceQueryTableRow.vue @@ -178,7 +178,8 @@ export default { }, props: { row: Object, - fields: Array + fields: Array, + keyUsage: Array }, data () { return { @@ -318,7 +319,7 @@ export default { editOFF () { this.isInlineEditor[this.editingField] = false; let content; - if (!['blob', 'mediumblob', 'longblob'].includes(this.editingType)) { + if (!BLOB.includes(this.editingType)) { if (this.editingContent === this.$options.filters.typeFormat(this.originalContent, this.editingType)) return;// If not changed content = this.editingContent; } diff --git a/src/renderer/components/WorkspaceTableTab.vue b/src/renderer/components/WorkspaceTableTab.vue index da1e593e..d408a6fd 100644 --- a/src/renderer/components/WorkspaceTableTab.vue +++ b/src/renderer/components/WorkspaceTableTab.vue @@ -33,9 +33,10 @@
@@ -56,6 +57,7 @@ import WorkspaceQueryTable from '@/components/WorkspaceQueryTable'; import ModalNewTableRow from '@/components/ModalNewTableRow'; import { mapGetters, mapActions } from 'vuex'; import tableTabs from '@/mixins/tableTabs'; +// import { TEXT, LONG_TEXT } from 'common/fieldTypes'; export default { name: 'WorkspaceTableTab', @@ -70,9 +72,11 @@ export default { }, data () { return { + tabUid: 1, isQuering: false, results: {}, fields: [], + keyUsage: [], lastTable: null, isAddModal: false }; @@ -86,6 +90,13 @@ export default { }, isSelected () { return this.workspace.selected_tab === 1; + }, + firstTextField () { // TODO: move inside new row modal and row components + if (this.fields.length) { + const textField = this.fields.find(field => [...TEXT, ...LONG_TEXT].includes(field.type)); + return textField ? textField.name : ''; + } + return ''; } }, watch: { @@ -107,12 +118,15 @@ export default { }, methods: { ...mapActions({ - addNotification: 'notifications/addNotification' + addNotification: 'notifications/addNotification', + setTabFields: 'workspaces/setTabFields', + setTabKeyUsage: 'workspaces/setTabKeyUsage' }), async getTableData () { if (!this.table) return; this.isQuering = true; this.results = {}; + this.setTabFields({ cUid: this.connection.uid, tUid: this.tabUid, fields: [] }); const params = { uid: this.connection.uid, @@ -120,10 +134,12 @@ export default { table: this.workspace.breadcrumbs.table }; - try { + try { // Columns data const { status, response } = await Tables.getTableColumns(params); - if (status === 'success') - this.fields = response; + if (status === 'success') { + this.fields = response;// Needed to add new rows + this.setTabFields({ cUid: this.connection.uid, tUid: this.tabUid, fields: response }); + } else this.addNotification({ status: 'error', message: response }); } @@ -131,7 +147,7 @@ export default { this.addNotification({ status: 'error', message: err.stack }); } - try { + try { // Table data const { status, response } = await Tables.getTableData(params); if (status === 'success') @@ -143,6 +159,19 @@ export default { this.addNotification({ status: 'error', message: err.stack }); } + try { // Key usage (foreign keys) + const { status, response } = await Tables.getKeyUsage(params); + if (status === 'success') { + this.keyUsage = response;// Needed to add new rows + this.setTabKeyUsage({ cUid: this.connection.uid, tUid: this.tabUid, keyUsage: response }); + } + else + this.addNotification({ status: 'error', message: response }); + } + catch (err) { + this.addNotification({ status: 'error', message: err.stack }); + } + this.isQuering = false; }, reloadTable () { diff --git a/src/renderer/ipc-api/Tables.js b/src/renderer/ipc-api/Tables.js index c4975188..1b95d38e 100644 --- a/src/renderer/ipc-api/Tables.js +++ b/src/renderer/ipc-api/Tables.js @@ -10,6 +10,10 @@ export default class { return ipcRenderer.invoke('getTableData', params); } + static getKeyUsage (params) { + return ipcRenderer.invoke('get-key-usage', params); + } + static updateTableCell (params) { return ipcRenderer.invoke('updateTableCell', params); } diff --git a/src/renderer/store/modules/notifications.store.js b/src/renderer/store/modules/notifications.store.js index 6cfd1308..861a3bd7 100644 --- a/src/renderer/store/modules/notifications.store.js +++ b/src/renderer/store/modules/notifications.store.js @@ -20,7 +20,7 @@ export default { }, actions: { addNotification ({ commit }, payload) { - payload.uid = uidGen(); + payload.uid = uidGen('N'); commit('ADD_NOTIFICATION', payload); }, removeNotification ({ commit }, uid) { diff --git a/src/renderer/store/modules/workspaces.store.js b/src/renderer/store/modules/workspaces.store.js index 32a32855..4ba77fd3 100644 --- a/src/renderer/store/modules/workspaces.store.js +++ b/src/renderer/store/modules/workspaces.store.js @@ -28,8 +28,14 @@ export default { return null; }, getWorkspace: state => uid => { - const workspace = state.workspaces.filter(workspace => workspace.uid === uid); - return workspace.length ? workspace[0] : {}; + return state.workspaces.find(workspace => workspace.uid === uid); + }, + getWorkspaceTab: (state, getters) => tUid => { + if (!getters.getSelected) return; + const workspace = state.workspaces.find(workspace => workspace.uid === getters.getSelected); + if ('tabs' in workspace) + return workspace.tabs.find(tab => tab.uid === tUid); + return {}; }, getConnected: state => { return state.workspaces @@ -58,9 +64,11 @@ export default { }, NEW_TAB (state, uid) { const newTab = { - uid: uidGen(), + uid: uidGen('T'), selected: false, - type: 'query' + type: 'query', + fields: [], + keyUsage: [] }; state.workspaces = state.workspaces.map(workspace => { if (workspace.uid === uid) { @@ -75,6 +83,40 @@ export default { }, SELECT_TAB (state, { uid, tab }) { state.workspaces = state.workspaces.map(workspace => workspace.uid === uid ? { ...workspace, selected_tab: tab } : workspace); + }, + SET_TAB_FIELDS (state, { cUid, tUid, fields }) { + state.workspaces = state.workspaces.map(workspace => { + if (workspace.uid === cUid) { + return { + ...workspace, + tabs: workspace.tabs.map(tab => { + if (tab.uid === tUid) + return { ...tab, fields }; + else + return tab; + }) + }; + } + else + return workspace; + }); + }, + SET_TAB_KEY_USAGE (state, { cUid, tUid, keyUsage }) { + state.workspaces = state.workspaces.map(workspace => { + if (workspace.uid === cUid) { + return { + ...workspace, + tabs: workspace.tabs.map(tab => { + if (tab.uid === tUid) + return { ...tab, keyUsage }; + else + return tab; + }) + }; + } + else + return workspace; + }); } }, actions: { @@ -115,7 +157,12 @@ export default { uid, connected: false, selected_tab: 0, - tabs: [{ uid: 1, type: 'table' }], + tabs: [{ + uid: 1, + type: 'table', + fields: [], + keyUsage: [] + }], structure: {}, breadcrumbs: {} }; @@ -133,6 +180,12 @@ export default { }, selectTab ({ commit }, payload) { commit('SELECT_TAB', payload); + }, + setTabFields ({ commit }, payload) { + commit('SET_TAB_FIELDS', payload); + }, + setTabKeyUsage ({ commit }, payload) { + commit('SET_TAB_KEY_USAGE', payload); } } };