feat: foreign key support in add/edit row

This commit is contained in:
Fabio 2020-08-17 15:10:19 +02:00
parent dca625fe5a
commit 0b6a188d19
10 changed files with 169 additions and 24 deletions

2
.github/FUNDING.yml vendored
View File

@ -1,6 +1,6 @@
# These are supported funding model platforms
github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
github: [fabio286]
patreon: fabio286
open_collective: # Replace with a single Open Collective username
ko_fi: # Replace with a single Ko-fi username

View File

@ -3,7 +3,6 @@
import { app, BrowserWindow, nativeImage } from 'electron';
import * as path from 'path';
import { format as formatUrl } from 'url';
import ipcHandlers from './ipc-handlers';
const isDevelopment = process.env.NODE_ENV !== 'production';

View File

@ -64,4 +64,14 @@ export default (connections) => {
return { status: 'error', response: err.toString() };
}
});
ipcMain.handle('get-foreign-list', async (event, params) => {
try {
const results = await Tables.getForeignList(connections[params.uid], params);
return { status: 'success', response: results };
}
catch (err) {
return { status: 'error', response: err.toString() };
}
});
};

View File

@ -40,8 +40,6 @@ 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')

View File

@ -54,12 +54,13 @@ export default class {
static async insertTableRows (connection, params) {
const insertObj = {};
console.log(params);
for (const key in params.row) {
const type = params.fields[key];
let escapedParam;
if (NUMBER.includes(type))
if (params.row[key] === null)
escapedParam = 'NULL';
else if (NUMBER.includes(type))
escapedParam = params.row[key];
else if ([...TEXT, ...LONG_TEXT].includes(type))
escapedParam = `"${sqlEscaper(params.row[key])}"`;
@ -85,4 +86,17 @@ export default class {
.run();
}
}
static async getForeignList (connection, params) {
const query = connection
.select(`${params.column} AS foreignColumn`)
.schema(params.schema)
.from(params.table)
.orderBy('foreignColumn ASC');
if (params.description)
query.select(`LEFT(${params.description}, 20) AS foreignDescription`);
return query.run();
}
}

View File

@ -0,0 +1,89 @@
<template>
<select
ref="editField"
class="px-1"
@change="onChange"
@blur="$emit('blur')"
>
<option
v-for="row in foreignList"
:key="row.foreignColumn"
:value="row.foreignColumn"
:selected="row.foreignColumn === value"
>
{{ row.foreignColumn }} {{ 'foreignDescription' in row ? ` - ${row.foreignDescription}` : '' | cutText }}
</option>
</select>
</template>
<script>
import Tables from '@/ipc-api/Tables';
import { mapGetters, mapActions } from 'vuex';
import { TEXT, LONG_TEXT } from 'common/fieldTypes';
export default {
name: 'ForeignKeySelect',
filters: {
cutText (val) {
if (typeof val !== 'string') return val;
return val.length > 15 ? `${val.substring(0, 15)}...` : val;
}
},
props: {
value: [String, Number],
keyUsage: Object
},
data () {
return {
foreignList: []
};
},
computed: {
...mapGetters({
selectedWorkspace: 'workspaces/getSelected'
})
},
async created () {
let firstTextField;
const params = {
uid: this.selectedWorkspace,
schema: this.keyUsage.refSchema,
table: this.keyUsage.refTable
};
try { // Field data
const { status, response } = await Tables.getTableColumns(params);
if (status === 'success')
firstTextField = response.find(field => [...TEXT, ...LONG_TEXT].includes(field.type)).name || false;
else
this.addNotification({ status: 'error', message: response });
}
catch (err) {
this.addNotification({ status: 'error', message: err.stack });
}
try { // Foregn list
const { status, response } = await Tables.getForeignList({
...params,
column: this.keyUsage.refColumn,
description: firstTextField
});
if (status === 'success')
this.foreignList = response.rows;
else
this.addNotification({ status: 'error', message: response });
}
catch (err) {
this.addNotification({ status: 'error', message: err.stack });
}
},
methods: {
...mapActions({
addNotification: 'notifications/addNotification'
}),
onChange () {
this.$emit('update:value', this.$refs.editField.value);
}
}
};
</script>

View File

@ -23,8 +23,15 @@
<label class="form-label" :title="field.name">{{ field.name }}</label>
</div>
<div class="input-group col-8 col-sm-12">
<ForeignKeySelect
v-if="foreignKeys.includes(field.name)"
class="form-select"
:value.sync="localRow[field.name]"
:key-usage="getKeyUsage(field.name)"
:disabled="fieldsToExclude.includes(field.name)"
/>
<input
v-if="inputProps(field).mask"
v-else-if="inputProps(field).mask"
v-model="localRow[field.name]"
v-mask="inputProps(field).mask"
class="form-input"
@ -100,9 +107,13 @@ import { TEXT, LONG_TEXT, NUMBER, DATE, TIME, DATETIME, BLOB, BIT } from 'common
import { mask } from 'vue-the-mask';
import { mapGetters, mapActions } from 'vuex';
import Tables from '@/ipc-api/Tables';
import ForeignKeySelect from '@/components/ForeignKeySelect';
export default {
name: 'ModalNewTableRow',
components: {
ForeignKeySelect
},
directives: {
mask
},
@ -113,8 +124,8 @@ export default {
}
},
props: {
fields: Array,
connection: Object
connection: Object,
tabUid: [String, Number]
},
data () {
return {
@ -126,10 +137,21 @@ export default {
},
computed: {
...mapGetters({
getWorkspace: 'workspaces/getWorkspace'
selectedWorkspace: 'workspaces/getSelected',
getWorkspace: 'workspaces/getWorkspace',
getWorkspaceTab: 'workspaces/getWorkspaceTab'
}),
workspace () {
return this.getWorkspace(this.connection.uid);
return this.getWorkspace(this.selectedWorkspace);
},
foreignKeys () {
return this.keyUsage.map(key => key.column);
},
fields () {
return this.getWorkspaceTab(this.tabUid) ? this.getWorkspaceTab(this.tabUid).fields : [];
},
keyUsage () {
return this.getWorkspaceTab(this.tabUid) ? this.getWorkspaceTab(this.tabUid).keyUsage : [];
}
},
watch: {
@ -194,7 +216,7 @@ export default {
try {
const { status, response } = await Tables.insertTableRows({
uid: this.connection.uid,
uid: this.selectedWorkspace,
schema: this.workspace.breadcrumbs.schema,
table: this.workspace.breadcrumbs.table,
row: rowToInsert,
@ -271,6 +293,10 @@ export default {
if (!files.length) return;
this.localRow[field] = files[0].path;
},
getKeyUsage (keyName) {
return this.keyUsage.find(key => key.column === keyName);
}
}
};

View File

@ -16,6 +16,13 @@
:class="`${isNull(col)} type-${fieldType(cKey)}`"
@dblclick="editON($event, col, cKey)"
>{{ col | typeFormat(fieldType(cKey), fieldPrecision(cKey)) | cutText }}</span>
<ForeignKeySelect
v-else-if="foreignKeys.includes(cKey)"
class="editable-field"
:value.sync="editingContent"
:key-usage="getKeyUsage(cKey)"
@blur="editOFF"
/>
<template v-else>
<input
v-if="inputProps.mask"
@ -131,11 +138,13 @@ import hexToBinary from 'common/libs/hexToBinary';
import { TEXT, LONG_TEXT, NUMBER, DATE, TIME, DATETIME, BLOB, BIT } from 'common/fieldTypes';
import { mask } from 'vue-the-mask';
import ConfirmModal from '@/components/BaseConfirmModal';
import ForeignKeySelect from '@/components/ForeignKeySelect';
export default {
name: 'WorkspaceQueryTableRow',
components: {
ConfirmModal
ConfirmModal,
ForeignKeySelect
},
directives: {
mask
@ -240,6 +249,9 @@ export default {
},
isImage () {
return ['gif', 'jpg', 'png', 'bmp', 'ico', 'tif'].includes(this.contentInfo.ext);
},
foreignKeys () {
return this.keyUsage.map(key => key.column);
}
},
created () {
@ -381,6 +393,9 @@ export default {
},
selectRow (event, row) {
this.$emit('selectRow', event, row);
},
getKeyUsage (keyName) {
return this.keyUsage.find(key => key.column === keyName);
}
}
};

View File

@ -33,7 +33,6 @@
<div class="workspace-query-results column col-12">
<WorkspaceQueryTable
v-if="results"
v-show="!isQuering"
ref="queryTable"
:results="results"
:tab-uid="tabUid"
@ -43,8 +42,7 @@
</div>
<ModalNewTableRow
v-if="isAddModal"
:fields="fields"
:connection="connection"
:tab-uid="tabUid"
@hide="hideAddModal"
@reload="reloadTable"
/>
@ -57,7 +55,6 @@ 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',
@ -90,13 +87,6 @@ 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: {

View File

@ -25,4 +25,8 @@ export default class {
static insertTableRows (params) {
return ipcRenderer.invoke('insertTableRows', params);
}
static getForeignList (params) {
return ipcRenderer.invoke('get-foreign-list', params);
}
}