1
1
mirror of https://github.com/Fabio286/antares.git synced 2025-06-05 21:59:22 +02:00

feat(PostgreSQL): partial postgre implementation

This commit is contained in:
2021-03-16 18:42:03 +01:00
parent 8c9e4f6e96
commit d892fa6fb3
32 changed files with 2003 additions and 73 deletions

View File

@ -71,6 +71,7 @@
"moment": "^2.29.1", "moment": "^2.29.1",
"mssql": "^6.2.3", "mssql": "^6.2.3",
"mysql2": "^2.2.5", "mysql2": "^2.2.5",
"node-sql-parser": "^3.1.0",
"pg": "^8.5.1", "pg": "^8.5.1",
"source-map-support": "^0.5.16", "source-map-support": "^0.5.16",
"spectre.css": "^0.5.9", "spectre.css": "^0.5.9",

View File

@ -0,0 +1,36 @@
module.exports = {
// Core
collations: false,
engines: false,
// Tools
processesList: false,
usersManagement: false,
variables: false,
// Structure
databases: true,
tables: false,
views: false,
triggers: false,
routines: false,
functions: false,
schedulers: false,
// Settings
tableAdd: false,
viewAdd: false,
triggerAdd: false,
routineAdd: false,
functionAdd: false,
schedulerAdd: false,
databaseEdit: false,
schemaEdit: false,
tableSettings: false,
viewSettings: false,
triggerSettings: false,
routineSettings: false,
functionSettings: false,
schedulerSettings: false,
indexes: false,
foreigns: false,
sortableFields: false,
zerofill: false
};

View File

@ -0,0 +1,36 @@
const defaults = require('./defaults');
module.exports = {
...defaults,
// Core
collations: true,
engines: true,
// Tools
processesList: true,
// Structure
schemas: true,
tables: true,
views: true,
triggers: true,
routines: true,
functions: true,
schedulers: true,
// Settings
tableAdd: true,
viewAdd: true,
triggerAdd: true,
routineAdd: true,
functionAdd: true,
schedulerAdd: true,
schemaEdit: true,
tableSettings: true,
viewSettings: true,
triggerSettings: true,
routineSettings: true,
functionSettings: true,
schedulerSettings: true,
indexes: true,
foreigns: true,
sortableFields: true,
zerofill: true
};

View File

@ -0,0 +1,28 @@
const defaults = require('./defaults');
module.exports = {
...defaults,
// Core
collations: false,
engines: false,
// Tools
processesList: true,
// Structure
tables: true,
views: true,
triggers: true,
routines: true,
functions: true,
schedulers: false,
// Settings
databaseEdit: false,
tableSettings: false,
viewSettings: false,
triggerSettings: false,
routineSettings: false,
functionSettings: false,
schedulerSettings: false,
indexes: true,
foreigns: true,
sortableFields: false
};

View File

@ -0,0 +1,297 @@
module.exports = [
{
group: 'integer',
types: [
{
name: 'SMALLINT',
length: true,
unsigned: true
},
{
name: 'INTEGER',
length: true,
unsigned: true
},
{
name: 'BIGINT',
length: true,
unsigned: true
},
{
name: 'DECIMAL',
length: true,
unsigned: true
},
{
name: 'NUMERIC',
length: true,
unsigned: true
},
{
name: 'SMALLSERIAL',
length: true,
unsigned: true
},
{
name: 'SERIAL',
length: true,
unsigned: true
},
{
name: 'BIGSERIAL',
length: true,
unsigned: true
}
]
},
{
group: 'float',
types: [
{
name: 'REAL',
length: true,
unsigned: true
},
{
name: 'DOUBLE PRECISION',
length: true,
unsigned: true
}
]
},
{
group: 'monetary',
types: [
{
name: 'money',
length: true,
unsigned: true
}
]
},
{
group: 'string',
types: [
{
name: 'CHARACTER VARYING',
length: true,
unsigned: false
},
{
name: 'CHAR',
length: false,
unsigned: false
},
{
name: 'CHARACTER',
length: false,
unsigned: false
},
{
name: 'TEXT',
length: false,
unsigned: false
},
{
name: '"CHAR"',
length: false,
unsigned: false
},
{
name: 'NAME',
length: false,
unsigned: false
}
]
},
{
group: 'binary',
types: [
{
name: 'BYTEA',
length: true,
unsigned: false
}
]
},
{
group: 'time',
types: [
{
name: 'TIMESTAMP',
length: false,
unsigned: false
},
{
name: 'TIMESTAMP WITH TIME ZONE',
length: false,
unsigned: false
},
{
name: 'DATE',
length: true,
unsigned: false
},
{
name: 'TIME',
length: true,
unsigned: false
},
{
name: 'TIME WITH TIME ZONE',
length: true,
unsigned: false
},
{
name: 'INTERVAL',
length: false,
unsigned: false
}
]
},
{
group: 'boolean',
types: [
{
name: 'BOOLEAN',
length: false,
unsigned: false
}
]
},
{
group: 'geometric',
types: [
{
name: 'POINT',
length: false,
unsigned: false
},
{
name: 'LINE',
length: false,
unsigned: false
},
{
name: 'LSEG',
length: false,
unsigned: false
},
{
name: 'BOX',
length: false,
unsigned: false
},
{
name: 'PATH',
length: false,
unsigned: false
},
{
name: 'POLYGON',
length: false,
unsigned: false
},
{
name: 'CIRCLE',
length: false,
unsigned: false
}
]
},
{
group: 'network',
types: [
{
name: 'CIDR',
length: false,
unsigned: false
},
{
name: 'INET',
length: false,
unsigned: false
},
{
name: 'MACADDR',
length: false,
unsigned: false
},
{
name: 'MACADDR8',
length: false,
unsigned: false
}
]
},
{
group: 'bit',
types: [
{
name: 'BIT',
length: false,
unsigned: false
},
{
name: 'BIT VARYING',
length: false,
unsigned: false
}
]
},
{
group: 'text search',
types: [
{
name: 'TSVECTOR',
length: false,
unsigned: false
},
{
name: 'TSQUERY',
length: false,
unsigned: false
}
]
},
{
group: 'uuid',
types: [
{
name: 'UUID',
length: false,
unsigned: false
}
]
},
{
group: 'xml',
types: [
{
name: 'XML',
length: false,
unsigned: false
}
]
},
{
group: 'json',
types: [
{
name: 'JSON',
length: false,
unsigned: false
},
{
name: 'JSONB',
length: false,
unsigned: false
},
{
name: 'JSONPATH',
length: false,
unsigned: false
}
]
}
];

View File

@ -1 +0,0 @@
module.exports = [];

View File

@ -1,13 +1,66 @@
export const TEXT = ['CHAR', 'VARCHAR']; export const TEXT = [
export const LONG_TEXT = ['TEXT', 'MEDIUMTEXT', 'LONGTEXT']; 'CHAR',
'VARCHAR',
'CHARACTER',
'CHARACTER VARYING'
];
export const LONG_TEXT = [
'TEXT',
'MEDIUMTEXT',
'LONGTEXT',
'ARRAY',
'ANYARRAY'
];
export const NUMBER = ['INT', 'TINYINT', 'SMALLINT', 'MEDIUMINT', 'BIGINT', 'DECIMAL', 'NEWDECIMAL', 'BOOL']; export const NUMBER = [
export const FLOAT = ['FLOAT', 'DOUBLE']; 'INT',
'TINYINT',
'SMALLINT',
'MEDIUMINT',
'BIGINT',
'DECIMAL',
'NUMERIC',
'INTEGER',
'SMALLSERIAL',
'SERIAL',
'BIGSERIAL',
'OID',
'XID'
];
export const FLOAT = [
'FLOAT',
'DOUBLE',
'REAL',
'DOUBLE PRECISION',
'MONEY'
];
export const BOOLEAN = [
'BOOL',
'BOOLEAN'
];
export const DATE = ['DATE']; export const DATE = ['DATE'];
export const TIME = ['TIME']; export const TIME = [
export const DATETIME = ['DATETIME', 'TIMESTAMP']; 'TIME',
'TIME WITH TIME ZONE'
];
export const DATETIME = [
'DATETIME',
'TIMESTAMP',
'TIMESTAMP WITH TIME ZONE'
];
export const BLOB = ['BLOB', 'TINYBLOB', 'MEDIUMBLOB', 'LONGBLOB']; export const BLOB = [
'BLOB',
'TINYBLOB',
'MEDIUMBLOB',
'LONGBLOB',
'BYTEA'
];
export const BIT = ['BIT']; export const BIT = [
'BIT',
'BIT VARYING'
];

View File

@ -0,0 +1,5 @@
module.exports = [
'PRIMARY',
'INDEX',
'UNIQUE'
];

View File

@ -59,13 +59,13 @@ export default connections => {
}; };
} }
try {
const connection = ClientsFactory.getConnection({ const connection = ClientsFactory.getConnection({
client: conn.client, client: conn.client,
params, params,
poolSize: 1 poolSize: 1
}); });
try {
await connection.connect(); await connection.connect();
const structure = await connection.getStructure(new Set()); const structure = await connection.getStructure(new Set());

View File

@ -4,8 +4,7 @@ import { ipcMain } from 'electron';
export default connections => { export default connections => {
ipcMain.handle('create-database', async (event, params) => { ipcMain.handle('create-database', async (event, params) => {
try { try {
const query = `CREATE DATABASE \`${params.name}\` COLLATE ${params.collation}`; await connections[params.uid].createDatabase(params);
await connections[params.uid].raw(query);
return { status: 'success' }; return { status: 'success' };
} }
@ -16,8 +15,7 @@ export default connections => {
ipcMain.handle('update-database', async (event, params) => { ipcMain.handle('update-database', async (event, params) => {
try { try {
const query = `ALTER DATABASE \`${params.name}\` COLLATE ${params.collation}`; await connections[params.uid].alterDatabase(params);
await connections[params.uid].raw(query);
return { status: 'success' }; return { status: 'success' };
} }
@ -28,8 +26,7 @@ export default connections => {
ipcMain.handle('delete-database', async (event, params) => { ipcMain.handle('delete-database', async (event, params) => {
try { try {
const query = `DROP DATABASE \`${params.database}\``; await connections[params.uid].dropDatabase(params);
await connections[params.uid].raw(query);
return { status: 'success' }; return { status: 'success' };
} }
@ -38,10 +35,9 @@ export default connections => {
} }
}); });
ipcMain.handle('get-database-collation', async (event, params) => { // TODO: move to mysql class ipcMain.handle('get-database-collation', async (event, params) => {
try { try {
const query = `SELECT \`DEFAULT_COLLATION_NAME\` FROM \`information_schema\`.\`SCHEMATA\` WHERE \`SCHEMA_NAME\`='${params.database}'`; const collation = await connections[params.uid].getDatabaseCollation(params);
const collation = await connections[params.uid].raw(query);
return { status: 'success', response: collation.rows.length ? collation.rows[0].DEFAULT_COLLATION_NAME : '' }; return { status: 'success', response: collation.rows.length ? collation.rows[0].DEFAULT_COLLATION_NAME : '' };
} }

View File

@ -176,19 +176,19 @@ export default (connections) => {
if (params.row[key] === null) if (params.row[key] === null)
escapedParam = 'NULL'; escapedParam = 'NULL';
else if ([...NUMBER, ...FLOAT].includes(type)) else if ([...NUMBER, ...FLOAT].includes(type))
escapedParam = params.row[key]; escapedParam = +params.row[key];
else if ([...TEXT, ...LONG_TEXT].includes(type)) else if ([...TEXT, ...LONG_TEXT].includes(type))
escapedParam = `"${sqlEscaper(params.row[key])}"`; escapedParam = `'${sqlEscaper(params.row[key])}'`;
else if (BLOB.includes(type)) { else if (BLOB.includes(type)) {
if (params.row[key]) { if (params.row[key]) {
const fileBlob = fs.readFileSync(params.row[key]); const fileBlob = fs.readFileSync(params.row[key]);
escapedParam = `0x${fileBlob.toString('hex')}`; escapedParam = `0x${fileBlob.toString('hex')}`;
} }
else else
escapedParam = '""'; escapedParam = '\'\'';
} }
else else
escapedParam = `"${sqlEscaper(params.row[key])}"`; escapedParam = `'${sqlEscaper(params.row[key])}'`;
insertObj[key] = escapedParam; insertObj[key] = escapedParam;
} }
@ -225,19 +225,19 @@ export default (connections) => {
else if ([...NUMBER, ...FLOAT].includes(type)) else if ([...NUMBER, ...FLOAT].includes(type))
escapedParam = params.row[key].value; escapedParam = params.row[key].value;
else if ([...TEXT, ...LONG_TEXT].includes(type)) else if ([...TEXT, ...LONG_TEXT].includes(type))
escapedParam = `"${sqlEscaper(params.row[key].value)}"`; escapedParam = `'${sqlEscaper(params.row[key].value)}'`;
else if (BLOB.includes(type)) { else if (BLOB.includes(type)) {
if (params.row[key].value) { if (params.row[key].value) {
const fileBlob = fs.readFileSync(params.row[key].value); const fileBlob = fs.readFileSync(params.row[key].value);
escapedParam = `0x${fileBlob.toString('hex')}`; escapedParam = `0x${fileBlob.toString('hex')}`;
} }
else else
escapedParam = '""'; escapedParam = '\'\'';
} }
else if (BIT.includes(type)) else if (BIT.includes(type))
escapedParam = `b'${sqlEscaper(params.row[key].value)}'`; escapedParam = `b'${sqlEscaper(params.row[key].value)}'`;
else else
escapedParam = `"${sqlEscaper(params.row[key].value)}"`; escapedParam = `'${sqlEscaper(params.row[key].value)}'`;
insertObj[key] = escapedParam; insertObj[key] = escapedParam;
} }
@ -261,10 +261,10 @@ export default (connections) => {
if (typeof fakeValue === 'string') { if (typeof fakeValue === 'string') {
if (params.row[key].length) if (params.row[key].length)
fakeValue = fakeValue.substr(0, params.row[key].length); fakeValue = fakeValue.substr(0, params.row[key].length);
fakeValue = `"${sqlEscaper(fakeValue)}"`; fakeValue = `'${sqlEscaper(fakeValue)}'`;
} }
else if ([...DATE, ...DATETIME].includes(type)) else if ([...DATE, ...DATETIME].includes(type))
fakeValue = `"${moment(fakeValue).format('YYYY-MM-DD HH:mm:ss.SSSSSS')}"`; fakeValue = `'${moment(fakeValue).format('YYYY-MM-DD HH:mm:ss.SSSSSS')}'`;
insertObj[key] = fakeValue; insertObj[key] = fakeValue;
} }

View File

@ -1,5 +1,6 @@
'use strict'; 'use strict';
import { MySQLClient } from './clients/MySQLClient'; import { MySQLClient } from './clients/MySQLClient';
import { PostgreSQLClient } from './clients/PostgreSQLClient';
export class ClientsFactory { export class ClientsFactory {
/** /**
@ -20,8 +21,10 @@ export class ClientsFactory {
case 'mysql': case 'mysql':
case 'maria': case 'maria':
return new MySQLClient(args); return new MySQLClient(args);
case 'pg':
return new PostgreSQLClient(args);
default: default:
return new Error(`Unknown database client: ${args.client}`); throw new Error(`Unknown database client: ${args.client}`);
} }
} }
} }

View File

@ -404,6 +404,44 @@ export class MySQLClient extends AntaresCore {
}); });
} }
/**
* CREATE DATABASE
*
* @returns {Array.<Object>} parameters
* @memberof MySQLClient
*/
async createDatabase (params) {
return await this.raw(`CREATE DATABASE \`${params.name}\` COLLATE ${params.collation}`);
}
/**
* ALTER DATABASE
*
* @returns {Array.<Object>} parameters
* @memberof MySQLClient
*/
async alterDatabase (params) {
return await this.raw(`ALTER DATABASE \`${params.name}\` COLLATE ${params.collation}`);
}
/**
* DROP DATABASE
*
* @returns {Array.<Object>} parameters
* @memberof MySQLClient
*/
async dropDatabase (params) {
return await this.raw(`DROP DATABASE \`${params.database}\``);
}
/**
* @returns {Array.<Object>} parameters
* @memberof MySQLClient
*/
async getDatabaseCollation (params) {
return await this.raw(`SELECT \`DEFAULT_COLLATION_NAME\` FROM \`information_schema\`.\`SCHEMATA\` WHERE \`SCHEMA_NAME\`='${params.database}'`);
}
/** /**
* SHOW CREATE VIEW * SHOW CREATE VIEW
* *
@ -1281,7 +1319,7 @@ export class MySQLClient extends AntaresCore {
const response = await this.getTableColumns(paramObj); const response = await this.getTableColumns(paramObj);
remappedFields = remappedFields.map(field => { remappedFields = remappedFields.map(field => {
const detailedField = response.find(f => f.name === field.name); const detailedField = response.find(f => f.name === field.name);
if (detailedField && field.orgTable === paramObj.table && field.schema === paramObj.schema && detailedField.name === field.orgName) if (detailedField && field.orgTable === paramObj.table && field.schema === paramObj.schema)
field = { ...detailedField, ...field }; field = { ...detailedField, ...field };
return field; return field;
}); });

File diff suppressed because it is too large Load Diff

View File

@ -30,7 +30,7 @@
class="form-input" class="form-input"
type="text" type="text"
> >
<span class="input-group-addon field-type" :class="`type-${parameter.type.toLowerCase()}`"> <span class="input-group-addon field-type" :class="typeClass(parameter.type)">
{{ parameter.type }} {{ parameter.length | wrapNumber }} {{ parameter.type }} {{ parameter.length | wrapNumber }}
</span> </span>
</div> </div>
@ -75,6 +75,11 @@ export default {
window.removeEventListener('keydown', this.onKey); window.removeEventListener('keydown', this.onKey);
}, },
methods: { methods: {
typeClass (type) {
if (type)
return `type-${type.toLowerCase().replaceAll(' ', '_').replaceAll('"', '')}`;
return '';
},
runRoutine () { runRoutine () {
const valArr = Object.keys(this.values).reduce((acc, curr) => { const valArr = Object.keys(this.values).reduce((acc, curr) => {
const value = isNaN(this.values[curr]) ? `"${this.values[curr]}"` : this.values[curr]; const value = isNaN(this.values[curr]) ? `"${this.values[curr]}"` : this.values[curr];

View File

@ -59,12 +59,12 @@
<option value="maria"> <option value="maria">
MariaDB MariaDB
</option> </option>
<!-- <option value="mssql">
Microsoft SQL
</option>
<option value="pg"> <option value="pg">
PostgreSQL PostgreSQL
</option> </option>
<!-- <option value="mssql">
Microsoft SQL
</option>
<option value="oracledb"> <option value="oracledb">
Oracle DB Oracle DB
</option> --> </option> -->

View File

@ -5,7 +5,7 @@
<div class="modal-header pl-2"> <div class="modal-header pl-2">
<div class="modal-title h6"> <div class="modal-title h6">
<div class="d-flex"> <div class="d-flex">
<i class="mdi mdi-24px mdi-database-edit mr-1" /> {{ $t('message.editDatabase') }} <i class="mdi mdi-24px mdi-database-edit mr-1" /> {{ $t('message.editSchema') }}
</div> </div>
</div> </div>
<a class="btn btn-clear c-hand" @click.stop="closeModal" /> <a class="btn btn-clear c-hand" @click.stop="closeModal" />
@ -23,7 +23,7 @@
class="form-input" class="form-input"
type="text" type="text"
required required
:placeholder="$t('message.databaseName')" :placeholder="$t('message.schemaName')"
readonly readonly
> >
</div> </div>

View File

@ -34,7 +34,7 @@
:field-obj="localRow[field.name]" :field-obj="localRow[field.name]"
:value.sync="localRow[field.name]" :value.sync="localRow[field.name]"
> >
<span class="input-group-addon field-type" :class="`type-${field.type.toLowerCase()}`"> <span class="input-group-addon field-type" :class="typeClass(field.type)">
{{ field.type }} {{ fieldLength(field) | wrapNumber }} {{ field.type }} {{ fieldLength(field) | wrapNumber }}
</span> </span>
<label class="form-checkbox ml-3" :title="$t('word.insert')"> <label class="form-checkbox ml-3" :title="$t('word.insert')">
@ -286,6 +286,11 @@ export default {
...mapActions({ ...mapActions({
addNotification: 'notifications/addNotification' addNotification: 'notifications/addNotification'
}), }),
typeClass (type) {
if (type)
return `type-${type.toLowerCase().replaceAll(' ', '_').replaceAll('"', '')}`;
return '';
},
async insertRows () { async insertRows () {
this.isInserting = true; this.isInserting = true;
const rowToInsert = this.localRow; const rowToInsert = this.localRow;

View File

@ -63,12 +63,12 @@
<option value="maria"> <option value="maria">
MariaDB MariaDB
</option> </option>
<!-- <option value="mssql">
Microsoft SQL
</option>
<option value="pg"> <option value="pg">
PostgreSQL PostgreSQL
</option> </option>
<!-- <option value="mssql">
Microsoft SQL
</option>
<option value="oracledb"> <option value="oracledb">
Oracle DB Oracle DB
</option> --> </option> -->
@ -315,6 +315,7 @@ export default {
this.connection.port = '1433'; this.connection.port = '1433';
break; break;
case 'pg': case 'pg':
this.connection.user = 'postgres';
this.connection.port = '5432'; this.connection.port = '5432';
break; break;
case 'oracledb': case 'oracledb':

View File

@ -5,7 +5,7 @@
<div class="modal-header pl-2"> <div class="modal-header pl-2">
<div class="modal-title h6"> <div class="modal-title h6">
<div class="d-flex"> <div class="d-flex">
<i class="mdi mdi-24px mdi-database-plus mr-1" /> {{ $t('message.createNewDatabase') }} <i class="mdi mdi-24px mdi-database-plus mr-1" /> {{ $t('message.createNewSchema') }}
</div> </div>
</div> </div>
<a class="btn btn-clear c-hand" @click.stop="closeModal" /> <a class="btn btn-clear c-hand" @click.stop="closeModal" />
@ -24,11 +24,11 @@
class="form-input" class="form-input"
type="text" type="text"
required required
:placeholder="$t('message.databaseName')" :placeholder="$t('message.schemaName')"
> >
</div> </div>
</div> </div>
<div class="form-group"> <div v-if="customizations.collations" class="form-group">
<div class="col-3"> <div class="col-3">
<label class="form-label">{{ $t('word.collation') }}</label> <label class="form-label">{{ $t('word.collation') }}</label>
</div> </div>
@ -49,7 +49,11 @@
</div> </div>
</div> </div>
<div class="modal-footer text-light"> <div class="modal-footer text-light">
<button class="btn btn-primary mr-2" @click.stop="createDatabase"> <button
class="btn btn-primary mr-2"
:class="{'loading': isLoading}"
@click.stop="createDatabase"
>
{{ $t('word.add') }} {{ $t('word.add') }}
</button> </button>
<button class="btn btn-link" @click.stop="closeModal"> <button class="btn btn-link" @click.stop="closeModal">
@ -68,6 +72,7 @@ export default {
name: 'ModalNewDatabase', name: 'ModalNewDatabase',
data () { data () {
return { return {
isLoading: false,
database: { database: {
name: '', name: '',
collation: '' collation: ''
@ -83,8 +88,11 @@ export default {
collations () { collations () {
return this.getWorkspace(this.selectedWorkspace).collations; return this.getWorkspace(this.selectedWorkspace).collations;
}, },
customizations () {
return this.getWorkspace(this.selectedWorkspace).customizations;
},
defaultCollation () { defaultCollation () {
return this.getDatabaseVariable(this.selectedWorkspace, 'collation_server').value || ''; return this.getDatabaseVariable(this.selectedWorkspace, 'collation_server') ? this.getDatabaseVariable(this.selectedWorkspace, 'collation_server').value : '';
} }
}, },
created () { created () {
@ -102,6 +110,7 @@ export default {
addNotification: 'notifications/addNotification' addNotification: 'notifications/addNotification'
}), }),
async createDatabase () { async createDatabase () {
this.isLoading = true;
try { try {
const { status, response } = await Database.createDatabase({ const { status, response } = await Database.createDatabase({
uid: this.selectedWorkspace, uid: this.selectedWorkspace,
@ -118,6 +127,7 @@ export default {
catch (err) { catch (err) {
this.addNotification({ status: 'error', message: err.stack }); this.addNotification({ status: 'error', message: err.stack });
} }
this.isLoading = false;
}, },
closeModal () { closeModal () {
this.$emit('close'); this.$emit('close');

View File

@ -69,7 +69,7 @@
:disabled="fieldsToExclude.includes(field.name)" :disabled="fieldsToExclude.includes(field.name)"
:tabindex="key+1" :tabindex="key+1"
> >
<span class="input-group-addon" :class="`type-${field.type.toLowerCase()}`"> <span class="input-group-addon" :class="typeCLass(field.type)">
{{ field.type }} {{ fieldLength(field) | wrapNumber }} {{ field.type }} {{ fieldLength(field) | wrapNumber }}
</span> </span>
<label class="form-checkbox ml-3" :title="$t('word.insert')"> <label class="form-checkbox ml-3" :title="$t('word.insert')">
@ -222,6 +222,11 @@ export default {
...mapActions({ ...mapActions({
addNotification: 'notifications/addNotification' addNotification: 'notifications/addNotification'
}), }),
typeClass (type) {
if (type)
return `type-${type.toLowerCase().replaceAll(' ', '_').replaceAll('"', '')}`;
return '';
},
async insertRows () { async insertRows () {
this.isInserting = true; this.isInserting = true;
const rowToInsert = this.localRow; const rowToInsert = this.localRow;

View File

@ -37,7 +37,7 @@
</ul> </ul>
</li> </li>
<li <li
v-if="schemaChild" v-if="schemaChild && isSettingSupported"
class="tab-item" class="tab-item"
:class="{'active': selectedTab === 'prop'}" :class="{'active': selectedTab === 'prop'}"
@click="selectTab({uid: workspace.uid, tab: 'prop'})" @click="selectTab({uid: workspace.uid, tab: 'prop'})"
@ -194,6 +194,15 @@ export default {
isSelected () { isSelected () {
return this.selectedWorkspace === this.connection.uid; return this.selectedWorkspace === this.connection.uid;
}, },
isSettingSupported () {
if (this.workspace.breadcrumbs.table && this.workspace.customizations.tableSettings) return true;
if (this.workspace.breadcrumbs.view && this.workspace.customizations.viewSettings) return true;
if (this.workspace.breadcrumbs.trigger && this.workspace.customizations.triggerSettings) return true;
if (this.workspace.breadcrumbs.procedure && this.workspace.customizations.routineSettings) return true;
if (this.workspace.breadcrumbs.function && this.workspace.customizations.functionSettings) return true;
if (this.workspace.breadcrumbs.scheduler && this.workspace.customizations.schedulerSettings) return true;
return false;
},
selectedTab () { selectedTab () {
if ( if (
( (

View File

@ -11,7 +11,7 @@
<span v-if="workspace.connected" class="workspace-explorebar-tools"> <span v-if="workspace.connected" class="workspace-explorebar-tools">
<i <i
class="mdi mdi-18px mdi-database-plus c-hand mr-2" class="mdi mdi-18px mdi-database-plus c-hand mr-2"
:title="$t('message.createNewDatabase')" :title="$t('message.createNewSchema')"
@click="showNewDBModal" @click="showNewDBModal"
/> />
<i <i

View File

@ -7,27 +7,55 @@
<span class="d-flex"><i class="mdi mdi-18px mdi-plus text-light pr-1" /> {{ $t('word.add') }}</span> <span class="d-flex"><i class="mdi mdi-18px mdi-plus text-light pr-1" /> {{ $t('word.add') }}</span>
<i class="mdi mdi-18px mdi-chevron-right text-light pl-1" /> <i class="mdi mdi-18px mdi-chevron-right text-light pl-1" />
<div class="context-submenu"> <div class="context-submenu">
<div class="context-element" @click="showCreateTableModal"> <div
v-if="workspace.customizations.tableAdd"
class="context-element"
@click="showCreateTableModal"
>
<span class="d-flex"><i class="mdi mdi-18px mdi-table text-light pr-1" /> {{ $t('word.table') }}</span> <span class="d-flex"><i class="mdi mdi-18px mdi-table text-light pr-1" /> {{ $t('word.table') }}</span>
</div> </div>
<div class="context-element" @click="showCreateViewModal"> <div
v-if="workspace.customizations.viewAdd"
class="context-element"
@click="showCreateViewModal"
>
<span class="d-flex"><i class="mdi mdi-18px mdi-table-eye text-light pr-1" /> {{ $t('word.view') }}</span> <span class="d-flex"><i class="mdi mdi-18px mdi-table-eye text-light pr-1" /> {{ $t('word.view') }}</span>
</div> </div>
<div class="context-element" @click="showCreateTriggerModal"> <div
v-if="workspace.customizations.triggerAdd"
class="context-element"
@click="showCreateTriggerModal"
>
<span class="d-flex"><i class="mdi mdi-18px mdi-table-cog text-light pr-1" /> {{ $tc('word.trigger', 1) }}</span> <span class="d-flex"><i class="mdi mdi-18px mdi-table-cog text-light pr-1" /> {{ $tc('word.trigger', 1) }}</span>
</div> </div>
<div class="context-element" @click="showCreateRoutineModal"> <div
v-if="workspace.customizations.routineAdd"
class="context-element"
@click="showCreateRoutineModal"
>
<span class="d-flex"><i class="mdi mdi-18px mdi-sync-circle pr-1" /> {{ $tc('word.storedRoutine', 1) }}</span> <span class="d-flex"><i class="mdi mdi-18px mdi-sync-circle pr-1" /> {{ $tc('word.storedRoutine', 1) }}</span>
</div> </div>
<div class="context-element" @click="showCreateFunctionModal"> <div
v-if="workspace.customizations.functionAdd"
class="context-element"
@click="showCreateFunctionModal"
>
<span class="d-flex"><i class="mdi mdi-18px mdi-arrow-right-bold-box pr-1" /> {{ $tc('word.function', 1) }}</span> <span class="d-flex"><i class="mdi mdi-18px mdi-arrow-right-bold-box pr-1" /> {{ $tc('word.function', 1) }}</span>
</div> </div>
<div class="context-element" @click="showCreateSchedulerModal"> <div
v-if="workspace.customizations.schedulerAdd"
class="context-element"
@click="showCreateSchedulerModal"
>
<span class="d-flex"><i class="mdi mdi-18px mdi-calendar-clock text-light pr-1" /> {{ $tc('word.scheduler', 1) }}</span> <span class="d-flex"><i class="mdi mdi-18px mdi-calendar-clock text-light pr-1" /> {{ $tc('word.scheduler', 1) }}</span>
</div> </div>
</div> </div>
</div> </div>
<div class="context-element" @click="showEditModal"> <div
v-if="workspace.customizations.schemaEdit"
class="context-element"
@click="showEditModal"
>
<span class="d-flex"><i class="mdi mdi-18px mdi-database-edit text-light pr-1" /> {{ $t('word.edit') }}</span> <span class="d-flex"><i class="mdi mdi-18px mdi-database-edit text-light pr-1" /> {{ $t('word.edit') }}</span>
</div> </div>
<div class="context-element" @click="showDeleteModal"> <div class="context-element" @click="showDeleteModal">
@ -36,12 +64,12 @@
<ConfirmModal <ConfirmModal
v-if="isDeleteModal" v-if="isDeleteModal"
@confirm="deleteDatabase" @confirm="deleteSchema"
@hide="hideDeleteModal" @hide="hideDeleteModal"
> >
<template slot="header"> <template slot="header">
<div class="d-flex"> <div class="d-flex">
<i class="mdi mdi-24px mdi-database-remove mr-1" /> {{ $t('message.deleteDatabase') }} <i class="mdi mdi-24px mdi-database-remove mr-1" /> {{ $t('message.deleteSchema') }}
</div> </div>
</template> </template>
<div slot="body"> <div slot="body">
@ -130,7 +158,7 @@ export default {
closeContext () { closeContext () {
this.$emit('close-context'); this.$emit('close-context');
}, },
async deleteDatabase () { async deleteSchema () {
try { try {
const { status, response } = await Database.deleteDatabase({ const { status, response } = await Database.deleteDatabase({
uid: this.selectedWorkspace, uid: this.selectedWorkspace,

View File

@ -41,7 +41,7 @@
> >
<div class="tile-icon"> <div class="tile-icon">
<div> <div>
<i class="mdi mdi-hexagon mdi-24px" :class="`type-${param.type.toLowerCase()}`" /> <i class="mdi mdi-hexagon mdi-24px" :class="typeClass(param.type)" />
</div> </div>
</div> </div>
<div class="tile-content"> <div class="tile-content">
@ -183,6 +183,11 @@ export default {
window.removeEventListener('resize', this.getModalInnerHeight); window.removeEventListener('resize', this.getModalInnerHeight);
}, },
methods: { methods: {
typeClass (type) {
if (type)
return `type-${type.toLowerCase().replaceAll(' ', '_').replaceAll('"', '')}`;
return '';
},
confirmParametersChange () { confirmParametersChange () {
this.$emit('parameters-update', this.parametersProxy); this.$emit('parameters-update', this.parametersProxy);
}, },

View File

@ -41,7 +41,7 @@
> >
<div class="tile-icon"> <div class="tile-icon">
<div> <div>
<i class="mdi mdi-hexagon mdi-24px" :class="`type-${param.type.toLowerCase()}`" /> <i class="mdi mdi-hexagon mdi-24px" :class="typeClass(param.type)" />
</div> </div>
</div> </div>
<div class="tile-content"> <div class="tile-content">
@ -214,6 +214,11 @@ export default {
window.removeEventListener('resize', this.getModalInnerHeight); window.removeEventListener('resize', this.getModalInnerHeight);
}, },
methods: { methods: {
typeClass (type) {
if (type)
return `type-${type.toLowerCase().replaceAll(' ', '_').replaceAll('"', '')}`;
return '';
},
confirmParametersChange () { confirmParametersChange () {
this.$emit('parameters-update', this.parametersProxy); this.$emit('parameters-update', this.parametersProxy);
}, },

View File

@ -48,7 +48,7 @@
<span <span
v-if="!isInlineEditor.type" v-if="!isInlineEditor.type"
class="cell-content text-left" class="cell-content text-left"
:class="`type-${lowerCase(localRow.type)}`" :class="typeClass(localRow.type)"
@click="editON($event, localRow.type.toUpperCase(), 'type')" @click="editON($event, localRow.type.toUpperCase(), 'type')"
> >
{{ localRow.type }} {{ localRow.type }}
@ -378,10 +378,10 @@ export default {
return 'UNKNOWN ' + key; return 'UNKNOWN ' + key;
} }
}, },
lowerCase (val) { typeClass (type) {
if (val) if (type)
return val.toLowerCase(); return `type-${type.toLowerCase().replaceAll(' ', '_').replaceAll('"', '')}`;
return val; return '';
}, },
initLocalRow () { initLocalRow () {
Object.keys(this.localRow).forEach(key => { Object.keys(this.localRow).forEach(key => {

View File

@ -12,7 +12,7 @@
<span <span
v-if="!isInlineEditor[cKey]" v-if="!isInlineEditor[cKey]"
class="cell-content px-2" class="cell-content px-2"
:class="`${isNull(col)} type-${fields[cKey].type.toLowerCase()}`" :class="`${isNull(col)} ${typeClass(fields[cKey].type)}`"
@dblclick="editON($event, col, cKey)" @dblclick="editON($event, col, cKey)"
>{{ col | typeFormat(fields[cKey].type.toLowerCase(), fields[cKey].length) | cutText }}</span> >{{ col | typeFormat(fields[cKey].type.toLowerCase(), fields[cKey].length) | cutText }}</span>
<ForeignKeySelect <ForeignKeySelect
@ -331,6 +331,11 @@ export default {
isNull (value) { isNull (value) {
return value === null ? ' is-null' : ''; return value === null ? ' is-null' : '';
}, },
typeClass (type) {
if (type)
return `type-${type.toLowerCase().replaceAll(' ', '_').replaceAll('"', '')}`;
return '';
},
bufferToBase64 (val) { bufferToBase64 (val) {
return bufferToBase64(val); return bufferToBase64(val);
}, },

View File

@ -199,7 +199,11 @@ module.exports = {
setNull: 'Set NULL', setNull: 'Set NULL',
processesList: 'Processes list', processesList: 'Processes list',
processInfo: 'Process info', processInfo: 'Process info',
manageUsers: 'Manage users' manageUsers: 'Manage users',
createNewSchema: 'Create new schema',
schemaName: 'Schema name',
editSchema: 'Edit schema',
deleteSchema: 'Delete schema'
}, },
faker: { faker: {
address: 'Address', address: 'Address',

View File

@ -22,6 +22,14 @@
"mediumtext": $string-color, "mediumtext": $string-color,
"longtext": $string-color, "longtext": $string-color,
"json": $string-color, "json": $string-color,
"name": $string-color,
"character": $string-color,
"character_varying": $string-color,
"cidr": $string-color,
"inet": $string-color,
"macaddr": $string-color,
"macaddr8": $string-color,
"uuid": $string-color,
"int": $number-color, "int": $number-color,
"tinyint": $number-color, "tinyint": $number-color,
"smallint": $number-color, "smallint": $number-color,
@ -31,12 +39,25 @@
"decimal": $number-color, "decimal": $number-color,
"bigint": $number-color, "bigint": $number-color,
"newdecimal": $number-color, "newdecimal": $number-color,
"integer": $number-color,
"numeric": $number-color,
"smallserial": $number-color,
"serial": $number-color,
"bigserial": $number-color,
"real": $number-color,
"double_precision": $number-color,
"oid": $number-color,
"xid": $number-color,
"money": $number-color,
"datetime": $date-color, "datetime": $date-color,
"date": $date-color, "date": $date-color,
"time": $date-color, "time": $date-color,
"time_with_time_zone": $date-color,
"year": $date-color, "year": $date-color,
"timestamp": $date-color, "timestamp": $date-color,
"timestamp_with_time_zone": $date-color,
"bit": $bit-color, "bit": $bit-color,
"bit_varying": $bit-color,
"binary": $blob-color, "binary": $blob-color,
"varbinary": $blob-color, "varbinary": $blob-color,
"blob": $blob-color, "blob": $blob-color,
@ -44,8 +65,14 @@
"mediumblob": $blob-color, "mediumblob": $blob-color,
"medium_blob": $blob-color, "medium_blob": $blob-color,
"longblob": $blob-color, "longblob": $blob-color,
"bytea": $blob-color,
"enum": $enum-color, "enum": $enum-color,
"set": $enum-color, "set": $enum-color,
"boolean": $enum-color,
"interval": $array-color,
"array": $array-color,
"anyarray": $array-color,
"pg_node_tree": $array-color,
"unknown": $unknown-color, "unknown": $unknown-color,
) )
); );

View File

@ -14,6 +14,7 @@ $number-color: cornflowerblue;
$date-color: coral; $date-color: coral;
$bit-color: lightskyblue; $bit-color: lightskyblue;
$blob-color: darkorchid; $blob-color: darkorchid;
$array-color: greenyellow;
$enum-color: gold; $enum-color: gold;
$unknown-color: gray; $unknown-color: gray;

View File

@ -55,7 +55,7 @@ export default {
state.selected_workspace = uid; state.selected_workspace = uid;
}, },
ADD_CONNECTED (state, payload) { ADD_CONNECTED (state, payload) {
const { uid, client, dataTypes, indexTypes, structure, version } = payload; const { uid, client, dataTypes, indexTypes, customizations, structure, version } = payload;
state.workspaces = state.workspaces.map(workspace => workspace.uid === uid state.workspaces = state.workspaces.map(workspace => workspace.uid === uid
? { ? {
@ -63,6 +63,7 @@ export default {
client, client,
dataTypes, dataTypes,
indexTypes, indexTypes,
customizations,
structure, structure,
connected: true, connected: true,
version version
@ -253,12 +254,19 @@ export default {
else { else {
let dataTypes = []; let dataTypes = [];
let indexTypes = []; let indexTypes = [];
let customizations = {};
switch (connection.client) { switch (connection.client) {
case 'mysql': case 'mysql':
case 'maria': case 'maria':
dataTypes = require('common/data-types/mysql'); dataTypes = require('common/data-types/mysql');
indexTypes = require('common/index-types/mysql'); indexTypes = require('common/index-types/mysql');
customizations = require('common/customizations/mysql');
break;
case 'pg':
dataTypes = require('common/data-types/postgresql');
indexTypes = require('common/index-types/postgresql');
customizations = require('common/customizations/postgresql');
break; break;
} }
@ -285,6 +293,7 @@ export default {
client: connection.client, client: connection.client,
dataTypes, dataTypes,
indexTypes, indexTypes,
customizations,
structure: response, structure: response,
version version
}); });