feat: option to insert table rows

This commit is contained in:
Fabio 2020-08-12 18:12:30 +02:00
parent 128a6cd9e8
commit 2f1dfdc654
8 changed files with 356 additions and 7 deletions

View File

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

View File

@ -32,7 +32,7 @@ export class AntaresConnector {
limit: [],
join: [],
update: [],
insert: [],
insert: {},
delete: false
};
this._query = Object.assign({}, this._queryDefaults);
@ -108,6 +108,11 @@ export class AntaresConnector {
return this;
}
into (table) {
this._query.from = table;
return this;
}
delete (table) {
this._query.delete = true;
this.from(table);
@ -162,6 +167,16 @@ export class AntaresConnector {
return this;
}
/**
* @param {Object} obj field: value
* @returns
* @memberof AntaresConnector
*/
insert (obj) {
this._query.insert = { ...this._query.insert, ...obj };
return this;
}
/**
* @returns {string} SQL string
* @memberof AntaresConnector
@ -188,8 +203,10 @@ export class AntaresConnector {
// FROM
let fromRaw = '';
if (!this._query.update.length && !!this._query.from)
if (!this._query.update.length && !Object.keys(this._query.insert).length && !!this._query.from)
fromRaw = 'FROM';
else if (Object.keys(this._query.insert).length)
fromRaw = 'INTO';
switch (this._client) {
case 'maria':
@ -209,6 +226,21 @@ export class AntaresConnector {
const updateArray = this._query.update.reduce(this._reducer, []);
const updateRaw = updateArray.length ? `SET ${updateArray.join(', ')} ` : '';
let insertRaw = '';
if (Object.keys(this._query.insert).length) {
const fieldsList = [];
const valueList = [];
const fields = this._query.insert;
for (const key in fields) {
if (fields[key] === null) continue;
fieldsList.push(key);
valueList.push(typeof fields[key] === 'number' ? fields[key] : `"${fields[key]}"`);
}
insertRaw = ` (${fieldsList.join(',')}) VALUES (${valueList.join(',')}) `;
}
const groupByArray = this._query.groupBy.reduce(this._reducer, []);
const groupByRaw = groupByArray.length ? `GROUP BY ${groupByArray.join(', ')} ` : '';
@ -229,7 +261,7 @@ export class AntaresConnector {
break;
}
return `${selectRaw}${updateRaw ? 'UPDATE' : ''}${this._query.delete ? 'DELETE ' : ''}${fromRaw}${updateRaw}${whereRaw}${groupByRaw}${orderByRaw}${limitRaw}`;
return `${selectRaw}${updateRaw ? 'UPDATE' : ''}${insertRaw ? 'INSERT ' : ''}${this._query.delete ? 'DELETE ' : ''}${fromRaw}${updateRaw}${whereRaw}${groupByRaw}${orderByRaw}${limitRaw}${insertRaw}`;
}
/**

View File

@ -31,7 +31,10 @@ export default class {
datePrecision: field.DATETIME_PRECISION,
charLength: field.CHARACTER_MAXIMUM_LENGTH,
isNullable: field.IS_NULLABLE,
default: field.COLUMN_DEFAULT
default: field.COLUMN_DEFAULT,
charset: field.CHARACTER_SET_NAME,
collation: field.COLLATION_NAME,
autoIncrement: field.EXTRA.includes('auto_increment')
};
});
}

View File

@ -50,4 +50,14 @@ export default class {
.where({ [params.primary]: `IN (${params.rows.join(',')})` })
.run();
}
static async insertTableRows (connection, params) { // Prepare every field like updateTableCell method
for (let i = 0; i < params.repeat; i++) {
await connection
.schema(params.schema)
.into(params.table)
.insert(params.row)
.run();
}
}
}

View File

@ -0,0 +1,278 @@
<template>
<div class="modal active">
<a class="modal-overlay" @click.stop="closeModal" />
<div class="modal-container p-0">
<div class="modal-header pl-2">
<div class="modal-title h6">
<div class="d-flex">
<i class="mdi mdi-24px mdi-playlist-plus mr-1" /> {{ $t('message.addNewRow') }}
</div>
</div>
<a class="btn btn-clear c-hand" @click.stop="closeModal" />
</div>
<div class="modal-body">
<div class="content">
<form class="form-horizontal">
<fieldset :disabled="isInserting">
<div
v-for="(field, key) in fields"
:key="field.name"
class="form-group"
>
<div class="col-4 col-sm-12">
<label class="form-label" :title="field.name">{{ field.name }}</label>
</div>
<div class="input-group col-8 col-sm-12">
<input
v-if="inputProps(field).mask"
v-model="localRow[field.name]"
v-mask="inputProps(field).mask"
class="form-input"
:type="inputProps(field).type"
:disabled="fieldsToExclude.includes(field.name)"
:tabindex="key+1"
>
<input
v-else
v-model="localRow[field.name]"
class="form-input"
:type="inputProps(field).type"
:disabled="fieldsToExclude.includes(field.name)"
:tabindex="key+1"
>
<span class="input-group-addon" :class="`type-${field.type}`">
{{ field.type }} {{ fieldLength(field) | wrapNumber }}
</span>
<label class="form-checkbox ml-3" :title="$t('word.insert')">
<input
type="checkbox"
:checked="!field.autoIncrement"
@change.prevent="toggleFields($event, field)"
><i class="form-icon" />
</label>
</div>
</div>
</fieldset>
</form>
</div>
</div>
<div class="modal-footer text-light">
<div class="input-group col-3 tooltip tooltip-right" :data-tooltip="$t('message.numberOfInserts')">
<input
v-model="nInserts"
type="number"
class="form-input"
min="1"
:disabled="isInserting"
>
<span class="input-group-addon">
<i class="mdi mdi-24px mdi-repeat" />
</span>
</div>
<div>
<button
class="btn btn-primary mr-2"
:class="{'loading': isInserting}"
@click.stop="insertRows"
>
{{ $t('word.insert') }}
</button>
<button class="btn btn-link" @click.stop="closeModal">
{{ $t('word.close') }}
</button>
</div>
</div>
</div>
</div>
</template>
<script>
import moment from 'moment';
import { TEXT, LONG_TEXT, NUMBER, DATE, TIME, DATETIME, BLOB, BIT } from 'common/fieldTypes';
import { mask } from 'vue-the-mask';
import { mapGetters, mapActions } from 'vuex';
import Tables from '@/ipc-api/Tables';
export default {
name: 'ModalNewTableRow',
directives: {
mask
},
filters: {
wrapNumber (num) {
if (!num) return '';
return `(${num})`;
}
},
props: {
fields: Array,
connection: Object
},
data () {
return {
localRow: {},
fieldsToExclude: [],
nInserts: 1,
isInserting: false
};
},
computed: {
...mapGetters({
getWorkspace: 'workspaces/getWorkspace'
}),
workspace () {
return this.getWorkspace(this.connection.uid);
}
},
watch: {
nInserts (val) {
if (!val || val < 1)
this.nInserts = 1;
}
},
mounted () {
const rowObj = {};
for (const field of this.fields) {
let fieldDefault;
if (field.default === 'NULL') fieldDefault = null;
else {
if (NUMBER.includes(field.type))
fieldDefault = +field.default;
if ([...TEXT, ...LONG_TEXT].includes(field.type))
fieldDefault = field.default ? field.default.substring(1, field.default.length - 1) : '';
if ([...TIME, ...DATE].includes(field.type))
fieldDefault = field.default;
if (DATETIME.includes(field.type)) {
if (field.default && field.default.includes('current_timestamp')) {
let datePrecision = '';
for (let i = 0; i < field.datePrecision; i++)
datePrecision += i === 0 ? '.S' : 'S';
fieldDefault = moment().format(`YYYY-MM-DD HH:mm:ss${datePrecision}`);
}
}
}
rowObj[field.name] = fieldDefault;
if (field.autoIncrement)// Disable by default auto increment fields
this.fieldsToExclude = [...this.fieldsToExclude, field.name];
}
this.localRow = { ...rowObj };
},
methods: {
...mapActions({
addNotification: 'notifications/addNotification'
}),
async insertRows () {
this.isInserting = true;
const rowToInsert = this.localRow;
Object.keys(rowToInsert).forEach(key => {
if (this.fieldsToExclude.includes(key))
delete rowToInsert[key];
});
try {
const { status, response } = await Tables.insertTableRows({
uid: this.connection.uid,
schema: this.workspace.breadcrumbs.schema,
table: this.workspace.breadcrumbs.table,
row: rowToInsert,
repeat: this.nInserts
});
if (status === 'success') {
this.closeModal();
this.$emit('reload');
}
else
this.addNotification({ status: 'error', message: response });
}
catch (err) {
this.addNotification({ status: 'error', message: err.stack });
}
this.isInserting = false;
},
closeModal () {
this.$emit('hide');
},
fieldLength (field) {
if ([...BLOB, ...LONG_TEXT].includes(field.type)) return null;
return field.numPrecision || field.datePrecision || field.charLength || 0;
},
inputProps (field) {
if ([...TEXT, ...LONG_TEXT].includes(field.type))
return { type: 'text', mask: false };
if (NUMBER.includes(field.type))
return { type: 'number', mask: false };
if (TIME.includes(field.type)) {
let timeMask = '##:##:##';
const precision = this.fieldLength(field);
for (let i = 0; i < precision; i++)
timeMask += i === 0 ? '.#' : '#';
return { type: 'text', mask: timeMask };
}
if (DATE.includes(field.type))
return { type: 'text', mask: '####-##-##' };
if (DATETIME.includes(field.type)) {
let datetimeMask = '####-##-## ##:##:##';
const precision = this.fieldLength(field);
for (let i = 0; i < precision; i++)
datetimeMask += i === 0 ? '.#' : '#';
return { type: 'text', mask: datetimeMask };
}
if (BLOB.includes(field.type))
return { type: 'file', mask: false };
if (BIT.includes(field.type))
return { type: 'text', mask: false };
return { type: 'text', mask: false };
},
toggleFields (event, field) {
if (event.target.checked)
this.fieldsToExclude = this.fieldsToExclude.filter(f => f !== field.name);
else
this.fieldsToExclude = [...this.fieldsToExclude, field.name];
}
}
};
</script>
<style scoped>
.modal-container {
max-width: 500px;
}
.form-label {
overflow: hidden;
white-space: normal;
text-overflow: ellipsis;
}
.input-group-addon {
display: flex;
align-items: center;
}
.modal-footer {
display: flex;
justify-content: space-between;
align-items: center;
}
</style>

View File

@ -40,19 +40,28 @@
@deleteSelected="deleteSelected"
/>
</div>
<ModalNewTableRow
v-if="isAddModal"
:fields="fields"
:connection="connection"
@hide="hideAddModal"
@reload="reloadTable"
/>
</div>
</template>
<script>
import Tables from '@/ipc-api/Tables';
import WorkspaceQueryTable from '@/components/WorkspaceQueryTable';
import ModalNewTableRow from '@/components/ModalNewTableRow';
import { mapGetters, mapActions } from 'vuex';
import tableTabs from '@/mixins/tableTabs';
export default {
name: 'WorkspaceTableTab',
components: {
WorkspaceQueryTable
WorkspaceQueryTable,
ModalNewTableRow
},
mixins: [tableTabs],
props: {

View File

@ -37,7 +37,8 @@ module.exports = {
download: 'Download',
add: 'Add',
data: 'Data',
properties: 'Properties'
properties: 'Properties',
insert: 'Insert'
},
message: {
appWelcome: 'Welcome to Antares SQL Client!',
@ -65,7 +66,9 @@ module.exports = {
deleteRows: 'Delete row | Delete {count} rows',
confirmToDeleteRows: 'Do you confirm to delete one row? | Do you confirm to delete {count} rows?',
notificationsTimeout: 'Notifications timeout',
uploadFile: 'Upload file'
uploadFile: 'Upload file',
addNewRow: 'Add new row',
numberOfInserts: 'Number of inserts'
},
// Date and Time
short: {

View File

@ -17,4 +17,8 @@ export default class {
static deleteTableRows (params) {
return ipcRenderer.invoke('deleteTableRows', params);
}
static insertTableRows (params) {
return ipcRenderer.invoke('insertTableRows', params);
}
}