feat(PostgreSQL): table fields edit

This commit is contained in:
Fabio Di Stasio 2021-03-25 18:33:29 +01:00
parent e7401cc96e
commit e3f259c6e8
11 changed files with 135 additions and 77 deletions

View File

@ -21,7 +21,7 @@ I'm actively working on it, hoping to provide cool features and fixes as soon as
Why am I developing an SQL client when there are a lot of them on the market? Why am I developing an SQL client when there are a lot of them on the market?
The main goal is to develop a totally free, full featured, cross platform and open source alternative, empowered by JavaScript's ecosystem. The main goal is to develop a totally free, full featured, cross platform and open source alternative, empowered by JavaScript's ecosystem.
An application created with minimalism and semplicity in mind, with features in the right places, not hundreds of tiny buttons or submenu. A modern application created with minimalism and semplicity in mind, with features in the right places, not hundreds of tiny buttons, tabs or submenu.
## How to contribute ## How to contribute

View File

@ -37,5 +37,12 @@ module.exports = {
indexes: false, indexes: false,
foreigns: false, foreigns: false,
sortableFields: false, sortableFields: false,
zerofill: false unsigned: false,
nullable: false,
zerofill: false,
autoIncrement: false,
comment: false,
collation: false,
arrays: false,
onUpdate: false
}; };

View File

@ -36,5 +36,11 @@ module.exports = {
indexes: true, indexes: true,
foreigns: true, foreigns: true,
sortableFields: true, sortableFields: true,
zerofill: true unsigned: true,
nullable: true,
zerofill: true,
autoIncrement: true,
comment: true,
collation: true,
onUpdate: true
}; };

View File

@ -19,7 +19,7 @@ module.exports = {
schedulers: false, schedulers: false,
// Settings // Settings
databaseEdit: false, databaseEdit: false,
tableSettings: false, tableSettings: true,
viewSettings: false, viewSettings: false,
triggerSettings: false, triggerSettings: false,
routineSettings: false, routineSettings: false,
@ -27,5 +27,7 @@ module.exports = {
schedulerSettings: false, schedulerSettings: false,
indexes: true, indexes: true,
foreigns: true, foreigns: true,
sortableFields: false sortableFields: false,
nullable: true,
arrays: true
}; };

View File

@ -4,7 +4,7 @@ module.exports = [
types: [ types: [
{ {
name: 'TINYINT', name: 'TINYINT',
length: true, length: 4,
collation: false, collation: false,
unsigned: true, unsigned: true,
zerofill: true zerofill: true

View File

@ -4,22 +4,22 @@ module.exports = [
types: [ types: [
{ {
name: 'SMALLINT', name: 'SMALLINT',
length: true, length: false,
unsigned: true unsigned: true
}, },
{ {
name: 'INTEGER', name: 'INTEGER',
length: true, length: false,
unsigned: true unsigned: true
}, },
{ {
name: 'BIGINT', name: 'BIGINT',
length: true, length: false,
unsigned: true unsigned: true
}, },
{ {
name: 'DECIMAL', name: 'DECIMAL',
length: true, length: false,
unsigned: true unsigned: true
}, },
{ {
@ -29,17 +29,17 @@ module.exports = [
}, },
{ {
name: 'SMALLSERIAL', name: 'SMALLSERIAL',
length: true, length: false,
unsigned: true unsigned: true
}, },
{ {
name: 'SERIAL', name: 'SERIAL',
length: true, length: false,
unsigned: true unsigned: true
}, },
{ {
name: 'BIGSERIAL', name: 'BIGSERIAL',
length: true, length: false,
unsigned: true unsigned: true
} }
] ]
@ -49,12 +49,12 @@ module.exports = [
types: [ types: [
{ {
name: 'REAL', name: 'REAL',
length: true, length: false,
unsigned: true unsigned: true
}, },
{ {
name: 'DOUBLE PRECISION', name: 'DOUBLE PRECISION',
length: true, length: false,
unsigned: true unsigned: true
} }
] ]
@ -64,7 +64,7 @@ module.exports = [
types: [ types: [
{ {
name: 'money', name: 'money',
length: true, length: false,
unsigned: true unsigned: true
} }
] ]
@ -77,14 +77,9 @@ module.exports = [
length: true, length: true,
unsigned: false unsigned: false
}, },
{
name: 'CHAR',
length: false,
unsigned: false
},
{ {
name: 'CHARACTER', name: 'CHARACTER',
length: false, length: true,
unsigned: false unsigned: false
}, },
{ {
@ -109,7 +104,7 @@ module.exports = [
types: [ types: [
{ {
name: 'BYTEA', name: 'BYTEA',
length: true, length: false,
unsigned: false unsigned: false
} }
] ]
@ -129,17 +124,17 @@ module.exports = [
}, },
{ {
name: 'DATE', name: 'DATE',
length: true, length: false,
unsigned: false unsigned: false
}, },
{ {
name: 'TIME', name: 'TIME WITHOUT TIME ZONE',
length: true, length: false,
unsigned: false unsigned: false
}, },
{ {
name: 'TIME WITH TIME ZONE', name: 'TIME WITH TIME ZONE',
length: true, length: false,
unsigned: false unsigned: false
}, },
{ {
@ -229,12 +224,12 @@ module.exports = [
types: [ types: [
{ {
name: 'BIT', name: 'BIT',
length: false, length: true,
unsigned: false unsigned: false
}, },
{ {
name: 'BIT VARYING', name: 'BIT VARYING',
length: false, length: true,
unsigned: false unsigned: false
} }
] ]

View File

@ -1,5 +1,5 @@
'use strict'; 'use strict';
import pg, { Pool, Client, types } from 'pg'; import { Pool, Client, types } from 'pg';
import { parse } from 'pgsql-ast-parser'; import { parse } from 'pgsql-ast-parser';
import { AntaresCore } from '../AntaresCore'; import { AntaresCore } from '../AntaresCore';
import dataTypes from 'common/data-types/postgresql'; import dataTypes from 'common/data-types/postgresql';
@ -8,11 +8,11 @@ function pgToString (value) {
return value.toString(); return value.toString();
} }
pg.types.setTypeParser(1082, pgToString); // date types.setTypeParser(1082, pgToString); // date
pg.types.setTypeParser(1083, pgToString); // time types.setTypeParser(1083, pgToString); // time
pg.types.setTypeParser(1114, pgToString); // timestamp types.setTypeParser(1114, pgToString); // timestamp
pg.types.setTypeParser(1184, pgToString); // timestamptz types.setTypeParser(1184, pgToString); // timestamptz
pg.types.setTypeParser(1266, pgToString); // timetz types.setTypeParser(1266, pgToString); // timetz
export class PostgreSQLClient extends AntaresCore { export class PostgreSQLClient extends AntaresCore {
constructor (args) { constructor (args) {
@ -282,7 +282,7 @@ export class PostgreSQLClient extends AntaresCore {
default: field.column_default, default: field.column_default,
charset: field.character_set_name, charset: field.character_set_name,
collation: field.collation_name, collation: field.collation_name,
autoIncrement: null, autoIncrement: false,
onUpdate: null, onUpdate: null,
comment: '' comment: ''
}; };
@ -317,7 +317,10 @@ export class PostgreSQLClient extends AntaresCore {
name: row.constraint_name, name: row.constraint_name,
column: row.column_name, column: row.column_name,
indexType: null, indexType: null,
type: row.constraint_type type: row.constraint_type,
cardinality: null,
comment: '',
indexComment: ''
}; };
}); });
} }
@ -359,6 +362,8 @@ export class PostgreSQLClient extends AntaresCore {
tc.constraint_name, tc.constraint_name,
tc.table_name, tc.table_name,
kcu.column_name, kcu.column_name,
kcu.position_in_unique_constraint,
kcu.ordinal_position,
ccu.table_schema AS foreign_table_schema, ccu.table_schema AS foreign_table_schema,
ccu.table_name AS foreign_table_name, ccu.table_name AS foreign_table_name,
ccu.column_name AS foreign_column_name, ccu.column_name AS foreign_column_name,
@ -1020,8 +1025,10 @@ export class PostgreSQLClient extends AntaresCore {
options options
} = params; } = params;
let sql = `ALTER TABLE \`${table}\` `; let sql = '';
const alterColumns = []; const alterColumns = [];
const renameColumns = [];
const createSequences = [];
// OPTIONS // OPTIONS
if ('comment' in options) alterColumns.push(`COMMENT='${options.comment}'`); if ('comment' in options) alterColumns.push(`COMMENT='${options.comment}'`);
@ -1034,7 +1041,7 @@ export class PostgreSQLClient extends AntaresCore {
const typeInfo = this._getTypeInfo(addition.type); const typeInfo = this._getTypeInfo(addition.type);
const length = typeInfo.length ? addition.numLength || addition.charLength || addition.datePrecision : false; const length = typeInfo.length ? addition.numLength || addition.charLength || addition.datePrecision : false;
alterColumns.push(`ADD COLUMN \`${addition.name}\` alterColumns.push(`ADD COLUMN ${addition.name}
${addition.type.toUpperCase()}${length ? `(${length})` : ''} ${addition.type.toUpperCase()}${length ? `(${length})` : ''}
${addition.unsigned ? 'UNSIGNED' : ''} ${addition.unsigned ? 'UNSIGNED' : ''}
${addition.zerofill ? 'ZEROFILL' : ''} ${addition.zerofill ? 'ZEROFILL' : ''}
@ -1043,8 +1050,7 @@ export class PostgreSQLClient extends AntaresCore {
${addition.default ? `DEFAULT ${addition.default}` : ''} ${addition.default ? `DEFAULT ${addition.default}` : ''}
${addition.comment ? `COMMENT '${addition.comment}'` : ''} ${addition.comment ? `COMMENT '${addition.comment}'` : ''}
${addition.collation ? `COLLATE ${addition.collation}` : ''} ${addition.collation ? `COLLATE ${addition.collation}` : ''}
${addition.onUpdate ? `ON UPDATE ${addition.onUpdate}` : ''} ${addition.onUpdate ? `ON UPDATE ${addition.onUpdate}` : ''}`);
${addition.after ? `AFTER \`${addition.after}\`` : 'FIRST'}`);
}); });
// ADD INDEX // ADD INDEX
@ -1071,18 +1077,33 @@ export class PostgreSQLClient extends AntaresCore {
changes.forEach(change => { changes.forEach(change => {
const typeInfo = this._getTypeInfo(change.type); const typeInfo = this._getTypeInfo(change.type);
const length = typeInfo.length ? change.numLength || change.charLength || change.datePrecision : false; const length = typeInfo.length ? change.numLength || change.charLength || change.datePrecision : false;
let localType;
alterColumns.push(`CHANGE COLUMN \`${change.orgName}\` \`${change.name}\` switch (change.type) {
${change.type.toUpperCase()}${length ? `(${length})` : ''} case 'SERIAL':
${change.unsigned ? 'UNSIGNED' : ''} localType = 'integer';
${change.zerofill ? 'ZEROFILL' : ''} break;
${change.nullable ? 'NULL' : 'NOT NULL'} case 'SMALLSERIAL':
${change.autoIncrement ? 'AUTO_INCREMENT' : ''} localType = 'smallint';
${change.default ? `DEFAULT ${change.default}` : ''} break;
${change.comment ? `COMMENT '${change.comment}'` : ''} case 'BIGSERIAL':
${change.collation ? `COLLATE ${change.collation}` : ''} localType = 'bigint';
${change.onUpdate ? `ON UPDATE ${change.onUpdate}` : ''} break;
${change.after ? `AFTER \`${change.after}\`` : 'FIRST'}`); default:
localType = change.type.toLowerCase();
}
alterColumns.push(`ALTER COLUMN "${change.orgName}" TYPE ${localType}${length ? `(${length})` : ''} USING "${change.orgName}"::${localType}`);
alterColumns.push(`ALTER COLUMN "${change.orgName}" ${change.nullable ? 'DROP NOT NULL' : 'SET NOT NULL'}`);
alterColumns.push(`ALTER COLUMN "${change.orgName}" ${change.default ? `SET DEFAULT ${change.default}` : 'DROP DEFAULT'}`);
if (['SERIAL', 'SMALLSERIAL', 'BIGSERIAL'].includes(change.type)) {
const sequenceName = `${table}_${change.name}_seq`.replace(' ', '_');
createSequences.push(`CREATE SEQUENCE IF NOT EXISTS ${sequenceName} OWNED BY "${table}"."${change.orgName}"`);
alterColumns.push(`ALTER COLUMN "${change.orgName}" SET DEFAULT nextval('${sequenceName}')`);
}
if (change.orgName !== change.name)
renameColumns.push(`ALTER TABLE "${table}" RENAME COLUMN "${change.orgName}" TO "${change.name}"`);
}); });
// CHANGE INDEX // CHANGE INDEX
@ -1113,7 +1134,7 @@ export class PostgreSQLClient extends AntaresCore {
// DROP FIELDS // DROP FIELDS
deletions.forEach(deletion => { deletions.forEach(deletion => {
alterColumns.push(`DROP COLUMN \`${deletion.name}\``); alterColumns.push(`DROP COLUMN ${deletion.name}`);
}); });
// DROP INDEX // DROP INDEX
@ -1129,10 +1150,12 @@ export class PostgreSQLClient extends AntaresCore {
alterColumns.push(`DROP FOREIGN KEY \`${deletion.constraintName}\``); alterColumns.push(`DROP FOREIGN KEY \`${deletion.constraintName}\``);
}); });
sql += alterColumns.join(', '); if (alterColumns.length) sql += `ALTER TABLE "${table}" ${alterColumns.join(', ')}; `;
// RENAME // RENAME
if (options.name) sql += `; RENAME TABLE \`${table}\` TO \`${options.name}\``; if (renameColumns.length) sql += `${renameColumns.join(';')}; `;
if (createSequences.length) sql = `${createSequences.join(';')}; ${sql}`;
if (options.name) sql += `ALTER TABLE "${table}" RENAME TO "${options.name}"; `;
return await this.raw(sql); return await this.raw(sql);
} }

View File

@ -26,7 +26,7 @@
> >
</div> </div>
</div> </div>
<div class="form-group"> <div v-if="workspace.customizations.comment" class="form-group">
<label class="form-label col-4"> <label class="form-label col-4">
{{ $t('word.comment') }} {{ $t('word.comment') }}
</label> </label>
@ -38,7 +38,7 @@
> >
</div> </div>
</div> </div>
<div class="form-group"> <div v-if="workspace.customizations.autoIncrement" class="form-group">
<label class="form-label col-4"> <label class="form-label col-4">
{{ $t('word.autoIncrement') }} {{ $t('word.autoIncrement') }}
</label> </label>
@ -50,7 +50,7 @@
> >
</div> </div>
</div> </div>
<div class="form-group"> <div v-if="workspace.customizations.collations" class="form-group">
<label class="form-label col-4"> <label class="form-label col-4">
{{ $t('word.collation') }} {{ $t('word.collation') }}
</label> </label>
@ -66,7 +66,7 @@
</select> </select>
</div> </div>
</div> </div>
<div class="form-group"> <div v-if="workspace.customizations.engines" class="form-group">
<label class="form-label col-4"> <label class="form-label col-4">
{{ $t('word.engine') }} {{ $t('word.engine') }}
</label> </label>

View File

@ -433,11 +433,11 @@ export default {
_id: uidGen(), _id: uidGen(),
name: `${this.$tc('word.field', 1)}_${++this.newFieldsCounter}`, name: `${this.$tc('word.field', 1)}_${++this.newFieldsCounter}`,
key: '', key: '',
type: 'int', type: this.workspace.dataTypes[0].types[0].name,
schema: this.schema, schema: this.schema,
table: this.table, table: this.table,
numPrecision: null, numPrecision: null,
numLength: 11, numLength: this.workspace.dataTypes[0].types[0].length,
datePrecision: null, datePrecision: null,
charLength: null, charLength: null,
nullable: false, nullable: false,

View File

@ -49,21 +49,21 @@
</div> </div>
</div> </div>
</div> </div>
<div class="th"> <div v-if="customizations.unsigned" class="th">
<div class="column-resizable"> <div class="column-resizable">
<div class="table-column-title"> <div class="table-column-title">
{{ $t('word.unsigned') }} {{ $t('word.unsigned') }}
</div> </div>
</div> </div>
</div> </div>
<div class="th"> <div v-if="customizations.nullable" class="th">
<div class="column-resizable"> <div class="column-resizable">
<div class="table-column-title"> <div class="table-column-title">
{{ $t('message.allowNull') }} {{ $t('message.allowNull') }}
</div> </div>
</div> </div>
</div> </div>
<div class="th"> <div v-if="customizations.zerofill" class="th">
<div class="column-resizable"> <div class="column-resizable">
<div class="table-column-title"> <div class="table-column-title">
{{ $t('message.zeroFill') }} {{ $t('message.zeroFill') }}
@ -77,14 +77,14 @@
</div> </div>
</div> </div>
</div> </div>
<div class="th"> <div v-if="customizations.comment" class="th">
<div class="column-resizable"> <div class="column-resizable">
<div class="table-column-title"> <div class="table-column-title">
{{ $t('word.comment') }} {{ $t('word.comment') }}
</div> </div>
</div> </div>
</div> </div>
<div class="th"> <div v-if="customizations.collation" class="th">
<div class="column-resizable min-100"> <div class="column-resizable min-100">
<div class="table-column-title"> <div class="table-column-title">
{{ $t('word.collation') }} {{ $t('word.collation') }}
@ -106,6 +106,7 @@
:indexes="getIndexes(row.name)" :indexes="getIndexes(row.name)"
:foreigns="getForeigns(row.name)" :foreigns="getForeigns(row.name)"
:data-types="dataTypes" :data-types="dataTypes"
:customizations="customizations"
@contextmenu="contextMenu" @contextmenu="contextMenu"
/> />
</draggable> </draggable>
@ -154,6 +155,9 @@ export default {
workspaceSchema () { workspaceSchema () {
return this.getWorkspace(this.connUid).breadcrumbs.schema; return this.getWorkspace(this.connUid).breadcrumbs.schema;
}, },
customizations () {
return this.getWorkspace(this.connUid).customizations;
},
dataTypes () { dataTypes () {
return this.getWorkspace(this.connUid).dataTypes; return this.getWorkspace(this.connUid).dataTypes;
}, },

View File

@ -1,8 +1,8 @@
<template> <template>
<div class="tr" @contextmenu.prevent="$emit('contextmenu', $event, localRow._id)"> <div class="tr" @contextmenu.prevent="$emit('contextmenu', $event, localRow._id)">
<div class="td" tabindex="0"> <div class="td" tabindex="0">
<div class="row-draggable"> <div :class="customizations.sortableFields ? 'row-draggable' : 'text-center'">
<i class="mdi mdi-drag-horizontal row-draggable-icon" /> <i v-if="customizations.sortableFields" class="mdi mdi-drag-horizontal row-draggable-icon" />
{{ localRow.order }} {{ localRow.order }}
</div> </div>
</div> </div>
@ -96,7 +96,11 @@
> >
</template> </template>
</div> </div>
<div class="td" tabindex="0"> <div
v-if="customizations.unsigned"
class="td"
tabindex="0"
>
<label class="form-checkbox"> <label class="form-checkbox">
<input <input
v-model="localRow.unsigned" v-model="localRow.unsigned"
@ -106,7 +110,11 @@
<i class="form-icon" /> <i class="form-icon" />
</label> </label>
</div> </div>
<div class="td" tabindex="0"> <div
v-if="customizations.nullable"
class="td"
tabindex="0"
>
<label class="form-checkbox"> <label class="form-checkbox">
<input <input
v-model="localRow.nullable" v-model="localRow.nullable"
@ -116,7 +124,11 @@
<i class="form-icon" /> <i class="form-icon" />
</label> </label>
</div> </div>
<div class="td" tabindex="0"> <div
v-if="customizations.zerofill"
class="td"
tabindex="0"
>
<label class="form-checkbox"> <label class="form-checkbox">
<input <input
v-model="localRow.zerofill" v-model="localRow.zerofill"
@ -131,7 +143,11 @@
{{ fieldDefault }} {{ fieldDefault }}
</span> </span>
</div> </div>
<div class="td type-varchar" tabindex="0"> <div
v-if="customizations.comment"
class="td type-varchar"
tabindex="0"
>
<span <span
v-if="!isInlineEditor.comment" v-if="!isInlineEditor.comment"
class="cell-content" class="cell-content"
@ -149,7 +165,11 @@
@blur="editOFF" @blur="editOFF"
> >
</div> </div>
<div class="td" tabindex="0"> <div
v-if="customizations.collation"
class="td"
tabindex="0"
>
<template v-if="fieldType.collation"> <template v-if="fieldType.collation">
<span <span
v-if="!isInlineEditor.collation" v-if="!isInlineEditor.collation"
@ -220,7 +240,7 @@
</div> </div>
</div> </div>
</div> </div>
<div class="mb-2"> <div v-if="customizations.nullable" class="mb-2">
<label class="form-radio form-inline"> <label class="form-radio form-inline">
<input <input
v-model="defaultValue.type" v-model="defaultValue.type"
@ -230,7 +250,7 @@
><i class="form-icon" /> NULL ><i class="form-icon" /> NULL
</label> </label>
</div> </div>
<div class="mb-2"> <div v-if="customizations.autoIncrement" class="mb-2">
<label class="form-radio form-inline"> <label class="form-radio form-inline">
<input <input
v-model="defaultValue.type" v-model="defaultValue.type"
@ -261,7 +281,7 @@
</div> </div>
</div> </div>
</div> </div>
<div> <div v-if="customizations.onUpdate">
<div class="form-group"> <div class="form-group">
<label class="form-label col-4"> <label class="form-label col-4">
{{ $t('message.onUpdate') }} {{ $t('message.onUpdate') }}
@ -294,7 +314,8 @@ export default {
row: Object, row: Object,
dataTypes: Array, dataTypes: Array,
indexes: Array, indexes: Array,
foreigns: Array foreigns: Array,
customizations: Object
}, },
data () { data () {
return { return {