mirror of
https://github.com/Fabio286/antares.git
synced 2025-06-05 21:59:22 +02:00
Compare commits
35 Commits
Author | SHA1 | Date | |
---|---|---|---|
1df57dc705 | |||
86240fb53c | |||
1d363f755e | |||
0d77aee3eb | |||
a41cf1ab56 | |||
5ceddb8e00 | |||
16e17b39b6 | |||
20cba6ee9b | |||
9ffd443a66 | |||
f82dbd24dc | |||
6eb2977568 | |||
cafb65560a | |||
532d963019 | |||
1b0a63ff31 | |||
c22187c305 | |||
dcccb544f9 | |||
7d2ace9456 | |||
2584c9b9c2 | |||
a6b75ad0dc | |||
90fd9db917 | |||
c0f54b9514 | |||
cd31413256 | |||
b33199ea59 | |||
dea5ec7513 | |||
be816e8588 | |||
1e938adc5d | |||
8735a0c5f9 | |||
3dde1c109e | |||
d0b3e1b1b8 | |||
c20bff7bcb | |||
9f5ec0276c | |||
55932fe115 | |||
d374372e20 | |||
bb5f44681f | |||
49a4e1cb7b |
2
.github/FUNDING.yml
vendored
2
.github/FUNDING.yml
vendored
@@ -1,7 +1,7 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: [fabio286]
|
||||
patreon: fabio286
|
||||
patreon: #fabio286
|
||||
open_collective: # Replace with a single Open Collective username
|
||||
ko_fi: # Replace with a single Ko-fi username
|
||||
tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
|
||||
|
52
.travis.yml
52
.travis.yml
@@ -1,52 +0,0 @@
|
||||
language: node_js
|
||||
node_js: 12
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- node_modules
|
||||
- app/node_modules
|
||||
- $HOME/.cache/electron
|
||||
- $HOME/.cache/electron-builder
|
||||
- $HOME/.npm/_prebuilds
|
||||
|
||||
env:
|
||||
global:
|
||||
- ELECTRON_CACHE=$HOME/.cache/electron
|
||||
- ELECTRON_BUILDER_CACHE=$HOME/.cache/electron-builder
|
||||
|
||||
jobs:
|
||||
include:
|
||||
- stage: Test
|
||||
before_install:
|
||||
- sudo apt-get install libsecret-1-dev
|
||||
- npm install
|
||||
script:
|
||||
- npm test
|
||||
|
||||
- stage: Deploy Linux & Windows
|
||||
if: tag IS present
|
||||
os: linux
|
||||
services: docker
|
||||
before_install:
|
||||
- sudo apt-get install libsecret-1-dev
|
||||
- npm install
|
||||
script:
|
||||
- docker run --rm --env-file <(env | grep -iE 'DEBUG|NODE_|ELECTRON_|NPM_|CI|CIRCLE|TRAVIS|APPVEYOR_|CSC_|_TOKEN|_KEY|AWS_|STRIP|BUILD_') -v ${PWD}:/project -v ~/.cache/electron:/root/.cache/electron -v ~/.cache/electron-builder:/root/.cache/electron-builder electronuserland/builder:wine /bin/bash -c "npm run build -- --linux --win -p always"
|
||||
before_cache:
|
||||
- rm -rf $HOME/.cache/electron-builder/wine
|
||||
|
||||
- stage: Deploy Mac
|
||||
if: tag IS present
|
||||
os: osx
|
||||
before_install:
|
||||
- npm install
|
||||
osx_image: xcode10.2
|
||||
script:
|
||||
- npm run build -- -p always
|
||||
|
||||
# - stage: Deploy ARM Linux
|
||||
# if: tag IS present
|
||||
# os: linux
|
||||
# arch: arm64
|
||||
# script:
|
||||
# - npm run build -- --linux AppImage -p always
|
68
CHANGELOG.md
68
CHANGELOG.md
@@ -2,6 +2,74 @@
|
||||
|
||||
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
||||
|
||||
### [0.1.4](https://github.com/Fabio286/antares/compare/v0.1.3...v0.1.4) (2021-04-22)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* query results export ([1d363f7](https://github.com/Fabio286/antares/commit/1d363f755e025d0fc6fec61cbd47ff87a8f25728))
|
||||
* **UI:** canc press to delete selected rows ([20cba6e](https://github.com/Fabio286/antares/commit/20cba6ee9bc1daa902b04d6e2ddcb31d04fbf805))
|
||||
* **UI:** ctrl+s shortcut to save changes ([16e17b3](https://github.com/Fabio286/antares/commit/16e17b39b6c8b561cc018d02afee2276190ce304))
|
||||
* **UI:** format and clear queries ([9ffd443](https://github.com/Fabio286/antares/commit/9ffd443a66303f88fc4529896f6d1d7917454f7a))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* launch from shortcut of procedures or functions with parameters without name dont works ([f82dbd2](https://github.com/Fabio286/antares/commit/f82dbd24dcef7b4d8d127a604e256b3f79a6c617))
|
||||
* wrong changelog in some cases ([a41cf1a](https://github.com/Fabio286/antares/commit/a41cf1ab5662f5f5fdedff4a9e1c626c23071377))
|
||||
* **UI:** data type not listed in selection if not present in global types ([6eb2977](https://github.com/Fabio286/antares/commit/6eb2977568987b9440b62ae7dbd7183338bfcc9b))
|
||||
|
||||
|
||||
### Improvements
|
||||
|
||||
* **UI:** improved connection status indicator ([5ceddb8](https://github.com/Fabio286/antares/commit/5ceddb8e00f3bc1984b8e47de270dc39b367903f))
|
||||
|
||||
### [0.1.3](https://github.com/Fabio286/antares/compare/v0.1.2...v0.1.3) (2021-04-17)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* **PostgreSQL:** functions management ([cd31413](https://github.com/Fabio286/antares/commit/cd3141325681ea572c06b8998dd7bd334ceb3236))
|
||||
* **PostgreSQL:** procedure language select ([b33199e](https://github.com/Fabio286/antares/commit/b33199ea59c60b467601f333857494aa40adf4e8))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **MySQL:** invalid JavaScript datetime values not shown ([dcccb54](https://github.com/Fabio286/antares/commit/dcccb544f9ec24ad693c9e81fb4bcfbdbb7cc4e1))
|
||||
* approximate row count shown for results less than 1000 ([a6b75ad](https://github.com/Fabio286/antares/commit/a6b75ad0dc0d884332464c277e8542b2698630b9))
|
||||
* field apparently loses index or foreign key on rename in table editor ([7d2ace9](https://github.com/Fabio286/antares/commit/7d2ace94562f8da307b15b83c89d919727d800c8))
|
||||
|
||||
|
||||
### Improvements
|
||||
|
||||
* **MySQL:** improved the way to get routine and functions parameters ([90fd9db](https://github.com/Fabio286/antares/commit/90fd9db917c40262f2bc2501ab86f5feba3d8db4))
|
||||
* **UI:** improved table fields suggestion in query editor ([c22187c](https://github.com/Fabio286/antares/commit/c22187c3053aef368a351cc35e2f1d407ecde209))
|
||||
|
||||
### [0.1.2](https://github.com/Fabio286/antares/compare/v0.1.1...v0.1.2) (2021-04-11)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* in-app last release changelog ([1e938ad](https://github.com/Fabio286/antares/commit/1e938adc5d8eb5ad16ab16342375eecd88f68d20))
|
||||
* **PostgreSQL:** edit timezone in cell editor ([8735a0c](https://github.com/Fabio286/antares/commit/8735a0c5f9e5b6b3bcaadf37ce158aa7beae2c48))
|
||||
* **PostgreSQL:** procedures management ([3dde1c1](https://github.com/Fabio286/antares/commit/3dde1c109e23342d94362626ef7350dc123ea859))
|
||||
* **PostgreSQL:** support of arrays in table settings ([d0b3e1b](https://github.com/Fabio286/antares/commit/d0b3e1b1b8be9d2c038d70e16d4478671315de8f))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* cell edit doesn't properly use primary or unique index to update if both present, closes [#51](https://github.com/Fabio286/antares/issues/51) ([55932fe](https://github.com/Fabio286/antares/commit/55932fe11583bd5ff48f82b8408965adba4f5071))
|
||||
* deletion of rows from query results ([c20bff7](https://github.com/Fabio286/antares/commit/c20bff7bcbe340ac99ebcacaba3359edd61c068a))
|
||||
* no foreign key select when cell value is NULL, closes [#50](https://github.com/Fabio286/antares/issues/50) ([9f5ec02](https://github.com/Fabio286/antares/commit/9f5ec0276c92904975fdaea34b4c845c92bfe8d4))
|
||||
* wrong datetime conversion when updating a row without an unique key ([d374372](https://github.com/Fabio286/antares/commit/d374372e208318d7e50b258a8041145bdf7992c5))
|
||||
* **PostgreSQL:** issue with selected schema different than public ([49a4e1c](https://github.com/Fabio286/antares/commit/49a4e1cb7b24642641265d5830d3fee370cceeb4))
|
||||
* **UI:** white readonly inputs with dark theme ([bb5f446](https://github.com/Fabio286/antares/commit/bb5f44681f87aacf2cd2f60a6d958c5732289790))
|
||||
|
||||
|
||||
### Improvements
|
||||
|
||||
* **UI:** improved setting modal rendering ([be816e8](https://github.com/Fabio286/antares/commit/be816e85888b4f3d26cbb9caac0adbc4dde0ea94))
|
||||
|
||||
### [0.1.1](https://github.com/Fabio286/antares/compare/v0.1.0...v0.1.1) (2021-04-03)
|
||||
|
||||
|
||||
|
@@ -4,7 +4,7 @@
|
||||
|
||||
# Antares SQL Client
|
||||
|
||||
 [](https://travis-ci.com/Fabio286/antares)   [](https://snapcraft.io/antares) [](https://snapcraft.io/antares)
|
||||
   [](https://snapcraft.io/antares) [](https://snapcraft.io/antares) [](https://www.treedom.net/en/user/fabio-di-stasio/event/antares-for-the-planet)
|
||||
|
||||
Antares is an SQL client based on [Electron.js](https://github.com/electron/electron) and [Vue.js](https://github.com/vuejs/vue) that aims to become a useful tool, especially for developers.
|
||||
My target is to support as many databases as possible, and all major operating systems, including the ARM versions.
|
||||
|
19
package.json
19
package.json
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "antares",
|
||||
"productName": "Antares",
|
||||
"version": "0.1.1",
|
||||
"version": "0.1.4",
|
||||
"description": "A cross-platform easy to use SQL client.",
|
||||
"license": "MIT",
|
||||
"repository": "https://github.com/Fabio286/antares.git",
|
||||
@@ -73,7 +73,7 @@
|
||||
"electron-store": "^7.0.0",
|
||||
"electron-updater": "^4.3.5",
|
||||
"faker": "^5.3.1",
|
||||
"keytar": "^7.3.0",
|
||||
"marked": "^2.0.2",
|
||||
"moment": "^2.29.1",
|
||||
"mssql": "^6.2.3",
|
||||
"mysql2": "^2.2.5",
|
||||
@@ -82,6 +82,7 @@
|
||||
"pgsql-ast-parser": "^7.0.2",
|
||||
"source-map-support": "^0.5.16",
|
||||
"spectre.css": "^0.5.9",
|
||||
"sql-formatter": "^4.0.2",
|
||||
"v-mask": "^2.2.4",
|
||||
"vue-i18n": "^8.22.4",
|
||||
"vuedraggable": "^2.24.3",
|
||||
@@ -90,22 +91,22 @@
|
||||
"devDependencies": {
|
||||
"babel-eslint": "^10.1.0",
|
||||
"cross-env": "^7.0.2",
|
||||
"electron": "^11.3.0",
|
||||
"electron": "^11.4.3",
|
||||
"electron-builder": "^22.9.1",
|
||||
"electron-devtools-installer": "^3.1.1",
|
||||
"electron-webpack": "^2.8.2",
|
||||
"electron-webpack-vue": "^2.4.0",
|
||||
"eslint": "^7.20.0",
|
||||
"eslint": "^7.24.0",
|
||||
"eslint-config-standard": "^16.0.2",
|
||||
"eslint-plugin-import": "^2.22.1",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-promise": "^4.3.1",
|
||||
"eslint-plugin-vue": "^7.6.0",
|
||||
"eslint-plugin-promise": "^5.1.0",
|
||||
"eslint-plugin-vue": "^7.9.0",
|
||||
"node-sass": "^5.0.0",
|
||||
"sass-loader": "^10.1.1",
|
||||
"standard-version": "^9.1.0",
|
||||
"stylelint": "^13.9.0",
|
||||
"stylelint-config-standard": "^20.0.0",
|
||||
"standard-version": "^9.2.0",
|
||||
"stylelint": "^13.12.0",
|
||||
"stylelint-config-standard": "^21.0.0",
|
||||
"stylelint-scss": "^3.19.0",
|
||||
"vue": "^2.6.12",
|
||||
"vue-template-compiler": "^2.6.12",
|
||||
|
@@ -16,6 +16,7 @@ module.exports = {
|
||||
tables: false,
|
||||
views: false,
|
||||
triggers: false,
|
||||
triggerFunctions: false,
|
||||
routines: false,
|
||||
functions: false,
|
||||
schedulers: false,
|
||||
@@ -23,6 +24,7 @@ module.exports = {
|
||||
tableAdd: false,
|
||||
viewAdd: false,
|
||||
triggerAdd: false,
|
||||
triggerFunctionAdd: false,
|
||||
routineAdd: false,
|
||||
functionAdd: false,
|
||||
schedulerAdd: false,
|
||||
@@ -31,6 +33,7 @@ module.exports = {
|
||||
tableSettings: false,
|
||||
viewSettings: false,
|
||||
triggerSettings: false,
|
||||
triggerFunctionSettings: false,
|
||||
routineSettings: false,
|
||||
functionSettings: false,
|
||||
schedulerSettings: false,
|
||||
@@ -44,9 +47,23 @@ module.exports = {
|
||||
comment: false,
|
||||
collation: false,
|
||||
definer: false,
|
||||
arrays: false,
|
||||
onUpdate: false,
|
||||
tableArray: false,
|
||||
viewAlgorithm: false,
|
||||
viewSqlSecurity: false,
|
||||
viewUpdateOption: false
|
||||
viewUpdateOption: false,
|
||||
procedureDeterministic: false,
|
||||
procedureDataAccess: false,
|
||||
procedureSql: false,
|
||||
procedureContext: false,
|
||||
procedureLanguage: false,
|
||||
functionDeterministic: false,
|
||||
functionDataAccess: false,
|
||||
functionSql: false,
|
||||
functionContext: false,
|
||||
functionLanguage: false,
|
||||
triggerMiltipleEvents: false,
|
||||
triggerUpdateColumns: false,
|
||||
parametersLength: false,
|
||||
languages: false
|
||||
};
|
||||
|
@@ -46,5 +46,13 @@ module.exports = {
|
||||
onUpdate: true,
|
||||
viewAlgorithm: true,
|
||||
viewSqlSecurity: true,
|
||||
viewUpdateOption: true
|
||||
viewUpdateOption: true,
|
||||
procedureDeterministic: true,
|
||||
procedureDataAccess: true,
|
||||
procedureSql: 'BEGIN\r\n\r\nEND',
|
||||
procedureContext: true,
|
||||
functionDeterministic: true,
|
||||
functionDataAccess: true,
|
||||
functionSql: 'BEGIN\r\n\r\nEND',
|
||||
parametersLength: true
|
||||
};
|
||||
|
@@ -14,22 +14,29 @@ module.exports = {
|
||||
tables: true,
|
||||
views: true,
|
||||
triggers: false,
|
||||
routines: false,
|
||||
functions: false,
|
||||
schedulers: false,
|
||||
routines: true,
|
||||
functions: true,
|
||||
// Settings
|
||||
tableAdd: true,
|
||||
viewAdd: true,
|
||||
triggerAdd: false,
|
||||
routineAdd: true,
|
||||
functionAdd: true,
|
||||
databaseEdit: false,
|
||||
tableSettings: true,
|
||||
viewSettings: true,
|
||||
triggerSettings: false,
|
||||
routineSettings: false,
|
||||
functionSettings: false,
|
||||
schedulerSettings: false,
|
||||
routineSettings: true,
|
||||
functionSettings: true,
|
||||
indexes: true,
|
||||
foreigns: true,
|
||||
sortableFields: false,
|
||||
nullable: true,
|
||||
arrays: true
|
||||
tableArray: true,
|
||||
procedureSql: '$BODY$\r\n\r\n$BODY$',
|
||||
procedureContext: true,
|
||||
procedureLanguage: true,
|
||||
functionSql: '$BODY$\r\n\r\n$BODY$',
|
||||
functionContext: true,
|
||||
functionLanguage: true,
|
||||
languages: ['sql', 'plpgsql', 'c', 'internal']
|
||||
};
|
||||
|
@@ -54,6 +54,7 @@ export const TIME = [
|
||||
'TIME',
|
||||
'TIME WITH TIME ZONE'
|
||||
];
|
||||
|
||||
export const DATETIME = [
|
||||
'DATETIME',
|
||||
'TIMESTAMP',
|
||||
@@ -61,6 +62,12 @@ export const DATETIME = [
|
||||
'TIMESTAMP WITH TIME ZONE'
|
||||
];
|
||||
|
||||
// Used to check datetime fields only
|
||||
export const HAS_TIMEZONE = [
|
||||
'TIMESTAMP WITH TIME ZONE',
|
||||
'TIME WITH TIME ZONE'
|
||||
];
|
||||
|
||||
export const BLOB = [
|
||||
'BLOB',
|
||||
'TINYBLOB',
|
||||
|
@@ -2,9 +2,7 @@
|
||||
|
||||
import { app, BrowserWindow, nativeImage } from 'electron';
|
||||
import * as path from 'path';
|
||||
import crypto from 'crypto';
|
||||
import { format as formatUrl } from 'url';
|
||||
import keytar from 'keytar';
|
||||
import Store from 'electron-store';
|
||||
|
||||
import ipcHandlers from './ipc-handlers';
|
||||
@@ -96,18 +94,6 @@ else {
|
||||
|
||||
// create main BrowserWindow when electron is ready
|
||||
app.on('ready', async () => {
|
||||
try {
|
||||
let key = await keytar.getPassword('antares', 'user');
|
||||
|
||||
if (!key) {
|
||||
key = crypto.randomBytes(16).toString('hex');
|
||||
keytar.setPassword('antares', 'user', key);
|
||||
}
|
||||
}
|
||||
catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
|
||||
mainWindow = createMainWindow();
|
||||
});
|
||||
}
|
||||
|
@@ -1,4 +1,3 @@
|
||||
import keytar from 'keytar';
|
||||
import { app, ipcMain } from 'electron';
|
||||
|
||||
export default () => {
|
||||
@@ -7,14 +6,7 @@ export default () => {
|
||||
});
|
||||
|
||||
ipcMain.on('get-key', async event => {
|
||||
let key = false;
|
||||
|
||||
try {
|
||||
key = await keytar.getPassword('antares', 'user');
|
||||
}
|
||||
catch (err) {
|
||||
console.log(err);
|
||||
}
|
||||
const key = false;
|
||||
event.returnValue = key;
|
||||
});
|
||||
};
|
||||
|
@@ -8,7 +8,8 @@ export default connections => {
|
||||
host: conn.host,
|
||||
port: +conn.port,
|
||||
user: conn.user,
|
||||
password: conn.password
|
||||
password: conn.password,
|
||||
application_name: 'Antares SQL'
|
||||
};
|
||||
|
||||
if (conn.database)
|
||||
@@ -50,7 +51,8 @@ export default connections => {
|
||||
host: conn.host,
|
||||
port: +conn.port,
|
||||
user: conn.user,
|
||||
password: conn.password
|
||||
password: conn.password,
|
||||
application_name: 'Antares SQL'
|
||||
};
|
||||
|
||||
if (conn.database)
|
||||
|
@@ -157,9 +157,13 @@ export default (connections) => {
|
||||
|
||||
ipcMain.handle('delete-table-rows', async (event, params) => {
|
||||
if (params.primary) {
|
||||
const idString = params.rows.map(row => typeof row[params.primary] === 'string'
|
||||
? `"${row[params.primary]}"`
|
||||
: row[params.primary]).join(',');
|
||||
const idString = params.rows.map(row => {
|
||||
const fieldName = Object.keys(row)[0].includes('.') ? `${params.table}.${params.primary}` : params.primary;
|
||||
|
||||
return typeof row[fieldName] === 'string'
|
||||
? `"${row[fieldName]}"`
|
||||
: row[fieldName];
|
||||
}).join(',');
|
||||
|
||||
try {
|
||||
const result = await connections[params.uid]
|
||||
|
@@ -7,6 +7,8 @@ export class MySQLClient extends AntaresCore {
|
||||
constructor (args) {
|
||||
super(args);
|
||||
|
||||
this._schema = null;
|
||||
|
||||
this.types = {
|
||||
0: 'DECIMAL',
|
||||
1: 'TINYINT',
|
||||
@@ -102,8 +104,18 @@ export class MySQLClient extends AntaresCore {
|
||||
async connect () {
|
||||
if (!this._poolSize)
|
||||
this._connection = mysql.createConnection(this._params);
|
||||
else {
|
||||
this._connection = mysql.createPool({
|
||||
...this._params,
|
||||
connectionLimit: this._poolSize,
|
||||
typeCast: (field, next) => {
|
||||
if (field.type === 'DATETIME')
|
||||
return field.string();
|
||||
else
|
||||
this._connection = mysql.createPool({ ...this._params, connectionLimit: this._poolSize });
|
||||
return next();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -120,6 +132,7 @@ export class MySQLClient extends AntaresCore {
|
||||
* @memberof MySQLClient
|
||||
*/
|
||||
use (schema) {
|
||||
this._schema = schema;
|
||||
return this.raw(`USE \`${schema}\``);
|
||||
}
|
||||
|
||||
@@ -578,7 +591,7 @@ export class MySQLClient extends AntaresCore {
|
||||
const sql = `SHOW CREATE PROCEDURE \`${schema}\`.\`${routine}\``;
|
||||
const results = await this.raw(sql);
|
||||
|
||||
return results.rows.map(row => {
|
||||
return results.rows.map(async row => {
|
||||
if (!row['Create Procedure']) {
|
||||
return {
|
||||
definer: null,
|
||||
@@ -592,22 +605,23 @@ export class MySQLClient extends AntaresCore {
|
||||
};
|
||||
}
|
||||
|
||||
const parameters = row['Create Procedure']
|
||||
.match(/(\([^()]*(?:(?:\([^()]*\))[^()]*)*\)\s*)/s)[0]
|
||||
.replaceAll('\r', '')
|
||||
.replaceAll('\t', '')
|
||||
.slice(1, -1)
|
||||
.split(',')
|
||||
.map(el => {
|
||||
const param = el.split(' ');
|
||||
const type = param[2] ? param[2].replace(')', '').split('(') : ['', null];
|
||||
const sql = `SELECT *
|
||||
FROM information_schema.parameters
|
||||
WHERE SPECIFIC_NAME = '${routine}'
|
||||
AND SPECIFIC_SCHEMA = '${schema}'
|
||||
ORDER BY ORDINAL_POSITION
|
||||
`;
|
||||
|
||||
const results = await this.raw(sql);
|
||||
|
||||
const parameters = results.rows.map(row => {
|
||||
return {
|
||||
name: param[1] ? param[1].replaceAll('`', '') : '',
|
||||
type: type[0].replaceAll('\n', ''),
|
||||
length: +type[1] ? +type[1].replace(/\D/g, '') : '',
|
||||
context: param[0] ? param[0].replace('\n', '') : ''
|
||||
name: row.PARAMETER_NAME,
|
||||
type: row.DATA_TYPE.toUpperCase(),
|
||||
length: row.NUMERIC_PRECISION || row.DATETIME_PRECISION || row.CHARACTER_MAXIMUM_LENGTH || '',
|
||||
context: row.PARAMETER_MODE
|
||||
};
|
||||
}).filter(el => el.name);
|
||||
});
|
||||
|
||||
let dataAccess = 'CONTAINS SQL';
|
||||
if (row['Create Procedure'].includes('NO SQL'))
|
||||
@@ -698,7 +712,7 @@ export class MySQLClient extends AntaresCore {
|
||||
const sql = `SHOW CREATE FUNCTION \`${schema}\`.\`${func}\``;
|
||||
const results = await this.raw(sql);
|
||||
|
||||
return results.rows.map(row => {
|
||||
return results.rows.map(async row => {
|
||||
if (!row['Create Function']) {
|
||||
return {
|
||||
definer: null,
|
||||
@@ -714,22 +728,23 @@ export class MySQLClient extends AntaresCore {
|
||||
};
|
||||
}
|
||||
|
||||
const parameters = row['Create Function']
|
||||
.match(/(\([^()]*(?:(?:\([^()]*\))[^()]*)*\)\s*)/s)[0]
|
||||
.replaceAll('\r', '')
|
||||
.replaceAll('\t', '')
|
||||
.slice(1, -1)
|
||||
.split(',')
|
||||
.map(el => {
|
||||
const param = el.split(' ');
|
||||
const type = param[1] ? param[1].replace(')', '').split('(') : ['', null];
|
||||
const sql = `SELECT *
|
||||
FROM information_schema.parameters
|
||||
WHERE SPECIFIC_NAME = '${func}'
|
||||
AND SPECIFIC_SCHEMA = '${schema}'
|
||||
ORDER BY ORDINAL_POSITION
|
||||
`;
|
||||
|
||||
const results = await this.raw(sql);
|
||||
|
||||
const parameters = results.rows.filter(row => row.PARAMETER_MODE).map(row => {
|
||||
return {
|
||||
name: param[0] ? param[0].replaceAll('`', '') : '',
|
||||
type: type[0],
|
||||
length: +type[1] ? +type[1].replace(/\D/g, '') : ''
|
||||
name: row.PARAMETER_NAME,
|
||||
type: row.DATA_TYPE.toUpperCase(),
|
||||
length: row.NUMERIC_PRECISION || row.DATETIME_PRECISION || row.CHARACTER_MAXIMUM_LENGTH || '',
|
||||
context: row.PARAMETER_MODE
|
||||
};
|
||||
}).filter(el => el.name);
|
||||
});
|
||||
|
||||
let dataAccess = 'CONTAINS SQL';
|
||||
if (row['Create Function'].includes('NO SQL'))
|
||||
@@ -801,13 +816,15 @@ export class MySQLClient extends AntaresCore {
|
||||
return acc;
|
||||
}, []).join(',');
|
||||
|
||||
const sql = `CREATE ${func.definer ? `DEFINER=${func.definer} ` : ''}FUNCTION \`${func.name}\`(${parameters}) RETURNS ${func.returns}${func.returnsLength ? `(${func.returnsLength})` : ''}
|
||||
const body = func.returns ? func.sql : 'BEGIN\n RETURN 0;\nEND';
|
||||
|
||||
const sql = `CREATE ${func.definer ? `DEFINER=${func.definer} ` : ''}FUNCTION \`${func.name}\`(${parameters}) RETURNS ${func.returns || 'SMALLINT'}${func.returnsLength ? `(${func.returnsLength})` : ''}
|
||||
LANGUAGE SQL
|
||||
${func.deterministic ? 'DETERMINISTIC' : 'NOT DETERMINISTIC'}
|
||||
${func.dataAccess}
|
||||
SQL SECURITY ${func.security}
|
||||
COMMENT '${func.comment}'
|
||||
${func.sql}`;
|
||||
${body}`;
|
||||
|
||||
return await this.raw(sql, { split: false });
|
||||
}
|
||||
@@ -1049,7 +1066,7 @@ export class MySQLClient extends AntaresCore {
|
||||
options
|
||||
} = params;
|
||||
|
||||
let sql = `ALTER TABLE \`${table}\` `;
|
||||
let sql = `ALTER TABLE \`${this._schema}\`.\`${table}\` `;
|
||||
const alterColumns = [];
|
||||
|
||||
// OPTIONS
|
||||
|
@@ -23,54 +23,16 @@ export class PostgreSQLClient extends AntaresCore {
|
||||
this.types = {};
|
||||
for (const key in types.builtins)
|
||||
this.types[types.builtins[key]] = key;
|
||||
}
|
||||
|
||||
_getType (field) {
|
||||
let name = this.types[field.columnType];
|
||||
let length = field.columnLength;
|
||||
|
||||
if (['DATE', 'TIME', 'YEAR', 'DATETIME'].includes(name))
|
||||
length = field.decimals;
|
||||
|
||||
if (name === 'TIMESTAMP')
|
||||
length = 0;
|
||||
|
||||
if (field.charsetNr === 63) { // if binary
|
||||
if (name === 'CHAR')
|
||||
name = 'BINARY';
|
||||
else if (name === 'VARCHAR')
|
||||
name = 'VARBINARY';
|
||||
}
|
||||
|
||||
if (name === 'BLOB') {
|
||||
switch (length) {
|
||||
case 765:
|
||||
name = 'TYNITEXT';
|
||||
break;
|
||||
case 196605:
|
||||
name = 'TEXT';
|
||||
break;
|
||||
case 50331645:
|
||||
name = 'MEDIUMTEXT';
|
||||
break;
|
||||
case 4294967295:
|
||||
name = field.charsetNr === 63 ? 'LONGBLOB' : 'LONGTEXT';
|
||||
break;
|
||||
case 255:
|
||||
name = 'TINYBLOB';
|
||||
break;
|
||||
case 65535:
|
||||
name = 'BLOB';
|
||||
break;
|
||||
case 16777215:
|
||||
name = 'MEDIUMBLOB';
|
||||
break;
|
||||
default:
|
||||
name = field.charsetNr === 63 ? 'BLOB' : 'TEXT';
|
||||
}
|
||||
}
|
||||
|
||||
return { name, length };
|
||||
this._arrayTypes = {
|
||||
_int2: 'SMALLINT',
|
||||
_int4: 'INTEGER',
|
||||
_int8: 'BIGINT',
|
||||
_float4: 'REAL',
|
||||
_float8: 'DOUBLE PRECISION',
|
||||
_char: '"CHAR"',
|
||||
_varchar: 'CHARACTER VARYING'
|
||||
};
|
||||
}
|
||||
|
||||
_getTypeInfo (type) {
|
||||
@@ -79,6 +41,12 @@ export class PostgreSQLClient extends AntaresCore {
|
||||
.filter(_type => _type.name === type.toUpperCase())[0];
|
||||
}
|
||||
|
||||
_getArrayType (type) {
|
||||
if (Object.keys(this._arrayTypes).includes(type))
|
||||
return this._arrayTypes[type];
|
||||
return type.replace('_', '');
|
||||
}
|
||||
|
||||
/**
|
||||
* @memberof PostgreSQLClient
|
||||
*/
|
||||
@@ -109,7 +77,8 @@ export class PostgreSQLClient extends AntaresCore {
|
||||
*/
|
||||
use (schema) {
|
||||
this._schema = schema;
|
||||
return this.raw(`SET search_path TO '${schema}', '$user'`);
|
||||
if (schema)
|
||||
return this.raw(`SET search_path TO ${schema}`);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -189,41 +158,43 @@ export class PostgreSQLClient extends AntaresCore {
|
||||
});
|
||||
|
||||
// PROCEDURES
|
||||
const remappedProcedures = procedures.filter(procedure => procedure.Db === db.database).map(procedure => {
|
||||
const remappedProcedures = procedures.filter(procedure => procedure.routine_schema === db.database).map(procedure => {
|
||||
return {
|
||||
name: procedure.Name,
|
||||
type: procedure.Type,
|
||||
definer: procedure.Definer,
|
||||
created: procedure.Created,
|
||||
updated: procedure.Modified,
|
||||
comment: procedure.Comment,
|
||||
charset: procedure.character_set_client,
|
||||
security: procedure.Security_type
|
||||
name: procedure.routine_name,
|
||||
type: procedure.routine_type,
|
||||
security: procedure.security_type
|
||||
};
|
||||
});
|
||||
|
||||
// FUNCTIONS
|
||||
const remappedFunctions = functions.filter(func => func.Db === db.database).map(func => {
|
||||
const remappedFunctions = functions.filter(func => func.routine_schema === db.database && func.data_type !== 'trigger').map(func => {
|
||||
return {
|
||||
name: func.routine_name,
|
||||
type: func.routine_type,
|
||||
definer: null, // func.Definer,
|
||||
created: null, // func.Created,
|
||||
updated: null, // func.Modified,
|
||||
comment: null, // func.Comment,
|
||||
charset: null, // func.character_set_client,
|
||||
security: func.security_type
|
||||
};
|
||||
});
|
||||
|
||||
// TRIGGER FUNCTIONS
|
||||
const remappedTriggerFunctions = functions.filter(func => func.routine_schema === db.database && func.data_type === 'trigger').map(func => {
|
||||
return {
|
||||
name: func.routine_name,
|
||||
type: func.routine_type,
|
||||
security: func.security_type
|
||||
};
|
||||
});
|
||||
|
||||
// TRIGGERS
|
||||
const remappedTriggers = triggersArr.filter(trigger => trigger.Db === db.database).map(trigger => {
|
||||
return {
|
||||
name: trigger.trigger_name,
|
||||
name: `${trigger.table_name}.${trigger.trigger_name}`,
|
||||
orgName: trigger.trigger_name,
|
||||
timing: trigger.activation,
|
||||
definer: trigger.definition, // ???
|
||||
definer: '',
|
||||
definition: trigger.definition,
|
||||
event: trigger.event,
|
||||
table: trigger.table_trigger,
|
||||
sqlMode: trigger.sql_mode
|
||||
table: trigger.table_name,
|
||||
sqlMode: ''
|
||||
};
|
||||
});
|
||||
|
||||
@@ -233,6 +204,7 @@ export class PostgreSQLClient extends AntaresCore {
|
||||
functions: remappedFunctions,
|
||||
procedures: remappedProcedures,
|
||||
triggers: remappedTriggers,
|
||||
triggerFunctions: remappedTriggerFunctions,
|
||||
schedulers: []
|
||||
};
|
||||
}
|
||||
@@ -256,7 +228,7 @@ export class PostgreSQLClient extends AntaresCore {
|
||||
* @returns {Object} table scructure
|
||||
* @memberof PostgreSQLClient
|
||||
*/
|
||||
async getTableColumns ({ schema, table }) {
|
||||
async getTableColumns ({ schema, table }, arrayRemap = true) {
|
||||
const { rows } = await this
|
||||
.select('*')
|
||||
.schema('information_schema')
|
||||
@@ -266,10 +238,17 @@ export class PostgreSQLClient extends AntaresCore {
|
||||
.run();
|
||||
|
||||
return rows.map(field => {
|
||||
let type = field.data_type;
|
||||
const isArray = type === 'ARRAY';
|
||||
|
||||
if (isArray && arrayRemap)
|
||||
type = this._getArrayType(field.udt_name);
|
||||
|
||||
return {
|
||||
name: field.column_name,
|
||||
key: null,
|
||||
type: field.data_type.toUpperCase(),
|
||||
type: type.toUpperCase(),
|
||||
isArray,
|
||||
schema: field.table_schema,
|
||||
table: field.table_name,
|
||||
numPrecision: field.numeric_precision,
|
||||
@@ -297,6 +276,9 @@ export class PostgreSQLClient extends AntaresCore {
|
||||
* @memberof PostgreSQLClient
|
||||
*/
|
||||
async getTableIndexes ({ schema, table }) {
|
||||
if (schema !== 'public')
|
||||
await this.use(schema);
|
||||
|
||||
const { rows } = await this.raw(`WITH ndx_list AS (
|
||||
SELECT pg_index.indexrelid, pg_class.oid
|
||||
FROM pg_index, pg_class
|
||||
@@ -476,7 +458,7 @@ export class PostgreSQLClient extends AntaresCore {
|
||||
* @memberof PostgreSQLClient
|
||||
*/
|
||||
async dropView (params) {
|
||||
const sql = `DROP VIEW ${params.view}`;
|
||||
const sql = `DROP VIEW ${this._schema}.${params.view}`;
|
||||
return await this.raw(sql);
|
||||
}
|
||||
|
||||
@@ -488,10 +470,10 @@ export class PostgreSQLClient extends AntaresCore {
|
||||
*/
|
||||
async alterView (params) {
|
||||
const { view } = params;
|
||||
let sql = `CREATE OR REPLACE VIEW ${view.oldName} AS ${view.sql}`;
|
||||
let sql = `CREATE OR REPLACE VIEW ${this._schema}.${view.oldName} AS ${view.sql}`;
|
||||
|
||||
if (view.name !== view.oldName)
|
||||
sql += `; ALTER VIEW ${view.oldName} RENAME TO ${view.name}`;
|
||||
sql += `; ALTER VIEW ${this._schema}.${view.oldName} RENAME TO ${view.name}`;
|
||||
|
||||
return await this.raw(sql);
|
||||
}
|
||||
@@ -503,7 +485,7 @@ export class PostgreSQLClient extends AntaresCore {
|
||||
* @memberof PostgreSQLClient
|
||||
*/
|
||||
async createView (view) {
|
||||
const sql = `CREATE VIEW ${view.name} AS ${view.sql}`;
|
||||
const sql = `CREATE VIEW ${this._schema}.${view.name} AS ${view.sql}`;
|
||||
return await this.raw(sql);
|
||||
}
|
||||
|
||||
@@ -536,7 +518,8 @@ export class PostgreSQLClient extends AntaresCore {
|
||||
* @memberof PostgreSQLClient
|
||||
*/
|
||||
async dropTrigger (params) {
|
||||
const sql = `DROP TRIGGER \`${params.trigger}\``;
|
||||
const triggerParts = params.trigger.split('.');
|
||||
const sql = `DROP TRIGGER ${triggerParts[1]} ON ${triggerParts[0]}`;
|
||||
return await this.raw(sql);
|
||||
}
|
||||
|
||||
@@ -580,16 +563,16 @@ export class PostgreSQLClient extends AntaresCore {
|
||||
* @memberof PostgreSQLClient
|
||||
*/
|
||||
async getRoutineInformations ({ schema, routine }) {
|
||||
const sql = `SHOW CREATE PROCEDURE \`${schema}\`.\`${routine}\``;
|
||||
const sql = `SELECT pg_get_functiondef((SELECT oid FROM pg_proc WHERE proname = '${routine}'));`;
|
||||
const results = await this.raw(sql);
|
||||
|
||||
return results.rows.map(row => {
|
||||
if (!row['Create Procedure']) {
|
||||
return results.rows.map(async row => {
|
||||
if (!row.pg_get_functiondef) {
|
||||
return {
|
||||
definer: null,
|
||||
sql: '',
|
||||
parameters: [],
|
||||
name: row.Procedure,
|
||||
name: routine,
|
||||
comment: '',
|
||||
security: 'DEFINER',
|
||||
deterministic: false,
|
||||
@@ -597,40 +580,48 @@ export class PostgreSQLClient extends AntaresCore {
|
||||
};
|
||||
}
|
||||
|
||||
const parameters = row['Create Procedure']
|
||||
.match(/(\([^()]*(?:(?:\([^()]*\))[^()]*)*\)\s*)/s)[0]
|
||||
.replaceAll('\r', '')
|
||||
.replaceAll('\t', '')
|
||||
.slice(1, -1)
|
||||
.split(',')
|
||||
.map(el => {
|
||||
const param = el.split(' ');
|
||||
const type = param[2] ? param[2].replace(')', '').split('(') : ['', null];
|
||||
const sql = `SELECT proc.specific_schema AS procedure_schema,
|
||||
proc.specific_name,
|
||||
proc.routine_name AS procedure_name,
|
||||
proc.external_language,
|
||||
args.parameter_name,
|
||||
args.parameter_mode,
|
||||
args.data_type
|
||||
FROM information_schema.routines proc
|
||||
LEFT JOIN information_schema.parameters args
|
||||
ON proc.specific_schema = args.specific_schema
|
||||
AND proc.specific_name = args.specific_name
|
||||
WHERE proc.routine_schema not in ('pg_catalog', 'information_schema')
|
||||
AND proc.routine_type = 'PROCEDURE'
|
||||
AND proc.routine_name = '${routine}'
|
||||
AND proc.specific_schema = '${schema}'
|
||||
ORDER BY procedure_schema,
|
||||
specific_name,
|
||||
procedure_name,
|
||||
args.ordinal_position
|
||||
`;
|
||||
|
||||
const results = await this.raw(sql);
|
||||
|
||||
const parameters = results.rows.map(row => {
|
||||
return {
|
||||
name: param[1] ? param[1].replaceAll('`', '') : '',
|
||||
type: type[0].replaceAll('\n', ''),
|
||||
length: +type[1] ? +type[1].replace(/\D/g, '') : '',
|
||||
context: param[0] ? param[0].replace('\n', '') : ''
|
||||
name: row.parameter_name,
|
||||
type: row.data_type.toUpperCase(),
|
||||
length: '',
|
||||
context: row.parameter_mode
|
||||
};
|
||||
}).filter(el => el.name);
|
||||
|
||||
let dataAccess = 'CONTAINS SQL';
|
||||
if (row['Create Procedure'].includes('NO SQL'))
|
||||
dataAccess = 'NO SQL';
|
||||
if (row['Create Procedure'].includes('READS SQL DATA'))
|
||||
dataAccess = 'READS SQL DATA';
|
||||
if (row['Create Procedure'].includes('MODIFIES SQL DATA'))
|
||||
dataAccess = 'MODIFIES SQL DATA';
|
||||
});
|
||||
|
||||
return {
|
||||
definer: row['Create Procedure'].match(/(?<=DEFINER=).*?(?=\s)/gs)[0],
|
||||
sql: row['Create Procedure'].match(/(BEGIN|begin)(.*)(END|end)/gs)[0],
|
||||
definer: '',
|
||||
sql: row.pg_get_functiondef.match(/(\$(.*)\$)(.*)(\$(.*)\$)/gs)[0],
|
||||
parameters: parameters || [],
|
||||
name: row.Procedure,
|
||||
comment: row['Create Procedure'].match(/(?<=COMMENT ').*?(?=')/gs) ? row['Create Procedure'].match(/(?<=COMMENT ').*?(?=')/gs)[0] : '',
|
||||
security: row['Create Procedure'].includes('SQL SECURITY INVOKER') ? 'INVOKER' : 'DEFINER',
|
||||
deterministic: row['Create Procedure'].includes('DETERMINISTIC'),
|
||||
dataAccess
|
||||
name: routine,
|
||||
comment: '',
|
||||
security: row.pg_get_functiondef.includes('SECURITY DEFINER') ? 'DEFINER' : 'INVOKER',
|
||||
deterministic: null,
|
||||
dataAccess: null,
|
||||
language: row.pg_get_functiondef.match(/(?<=LANGUAGE )(.*)(?<=[\S+\n\r\s])/gm)[0]
|
||||
};
|
||||
})[0];
|
||||
}
|
||||
@@ -642,7 +633,7 @@ export class PostgreSQLClient extends AntaresCore {
|
||||
* @memberof PostgreSQLClient
|
||||
*/
|
||||
async dropRoutine (params) {
|
||||
const sql = `DROP PROCEDURE \`${params.routine}\``;
|
||||
const sql = `DROP PROCEDURE ${this._schema}.${params.routine}`;
|
||||
return await this.raw(sql);
|
||||
}
|
||||
|
||||
@@ -677,18 +668,18 @@ export class PostgreSQLClient extends AntaresCore {
|
||||
async createRoutine (routine) {
|
||||
const parameters = 'parameters' in routine
|
||||
? routine.parameters.reduce((acc, curr) => {
|
||||
acc.push(`${curr.context} \`${curr.name}\` ${curr.type}${curr.length ? `(${curr.length})` : ''}`);
|
||||
acc.push(`${curr.context} ${curr.name} ${curr.type}${curr.length ? `(${curr.length})` : ''}`);
|
||||
return acc;
|
||||
}, []).join(',')
|
||||
: '';
|
||||
|
||||
const sql = `CREATE ${routine.definer ? `DEFINER=${routine.definer} ` : ''}PROCEDURE \`${routine.name}\`(${parameters})
|
||||
LANGUAGE SQL
|
||||
${routine.deterministic ? 'DETERMINISTIC' : 'NOT DETERMINISTIC'}
|
||||
${routine.dataAccess}
|
||||
SQL SECURITY ${routine.security}
|
||||
COMMENT '${routine.comment}'
|
||||
${routine.sql}`;
|
||||
if (this._schema !== 'public')
|
||||
await this.use(this._schema);
|
||||
|
||||
const sql = `CREATE PROCEDURE ${this._schema}.${routine.name}(${parameters})
|
||||
LANGUAGE ${routine.language}
|
||||
SECURITY ${routine.security}
|
||||
AS ${routine.sql}`;
|
||||
|
||||
return await this.raw(sql, { split: false });
|
||||
}
|
||||
@@ -700,63 +691,66 @@ export class PostgreSQLClient extends AntaresCore {
|
||||
* @memberof PostgreSQLClient
|
||||
*/
|
||||
async getFunctionInformations ({ schema, func }) {
|
||||
const sql = `SHOW CREATE FUNCTION \`${schema}\`.\`${func}\``;
|
||||
const sql = `SELECT pg_get_functiondef((SELECT oid FROM pg_proc WHERE proname = '${func}'));`;
|
||||
const results = await this.raw(sql);
|
||||
|
||||
return results.rows.map(row => {
|
||||
if (!row['Create Function']) {
|
||||
return results.rows.map(async row => {
|
||||
if (!row.pg_get_functiondef) {
|
||||
return {
|
||||
definer: null,
|
||||
sql: '',
|
||||
parameters: [],
|
||||
name: row.Procedure,
|
||||
name: func,
|
||||
comment: '',
|
||||
security: 'DEFINER',
|
||||
deterministic: false,
|
||||
dataAccess: 'CONTAINS SQL',
|
||||
returns: 'INT',
|
||||
returnsLength: null
|
||||
dataAccess: 'CONTAINS SQL'
|
||||
};
|
||||
}
|
||||
|
||||
const parameters = row['Create Function']
|
||||
.match(/(\([^()]*(?:(?:\([^()]*\))[^()]*)*\)\s*)/s)[0]
|
||||
.replaceAll('\r', '')
|
||||
.replaceAll('\t', '')
|
||||
.slice(1, -1)
|
||||
.split(',')
|
||||
.map(el => {
|
||||
const param = el.split(' ');
|
||||
const type = param[1] ? param[1].replace(')', '').split('(') : ['', null];
|
||||
const sql = `SELECT proc.specific_schema AS procedure_schema,
|
||||
proc.specific_name,
|
||||
proc.routine_name AS procedure_name,
|
||||
proc.external_language,
|
||||
args.parameter_name,
|
||||
args.parameter_mode,
|
||||
args.data_type
|
||||
FROM information_schema.routines proc
|
||||
LEFT JOIN information_schema.parameters args
|
||||
ON proc.specific_schema = args.specific_schema
|
||||
AND proc.specific_name = args.specific_name
|
||||
WHERE proc.routine_schema not in ('pg_catalog', 'information_schema')
|
||||
AND proc.routine_type = 'FUNCTION'
|
||||
AND proc.routine_name = '${func}'
|
||||
AND proc.specific_schema = '${schema}'
|
||||
ORDER BY procedure_schema,
|
||||
specific_name,
|
||||
procedure_name,
|
||||
args.ordinal_position
|
||||
`;
|
||||
|
||||
const results = await this.raw(sql);
|
||||
|
||||
const parameters = results.rows.filter(row => row.parameter_mode).map(row => {
|
||||
return {
|
||||
name: param[0] ? param[0].replaceAll('`', '') : '',
|
||||
type: type[0],
|
||||
length: +type[1] ? +type[1].replace(/\D/g, '') : ''
|
||||
name: row.parameter_name,
|
||||
type: row.data_type.toUpperCase(),
|
||||
length: '',
|
||||
context: row.parameter_mode
|
||||
};
|
||||
}).filter(el => el.name);
|
||||
|
||||
let dataAccess = 'CONTAINS SQL';
|
||||
if (row['Create Function'].includes('NO SQL'))
|
||||
dataAccess = 'NO SQL';
|
||||
if (row['Create Function'].includes('READS SQL DATA'))
|
||||
dataAccess = 'READS SQL DATA';
|
||||
if (row['Create Function'].includes('MODIFIES SQL DATA'))
|
||||
dataAccess = 'MODIFIES SQL DATA';
|
||||
|
||||
const output = row['Create Function'].match(/(?<=RETURNS ).*?(?=\s)/gs).length ? row['Create Function'].match(/(?<=RETURNS ).*?(?=\s)/gs)[0].replace(')', '').split('(') : ['', null];
|
||||
});
|
||||
|
||||
return {
|
||||
definer: row['Create Function'].match(/(?<=DEFINER=).*?(?=\s)/gs)[0],
|
||||
sql: row['Create Function'].match(/(BEGIN|begin)(.*)(END|end)/gs)[0],
|
||||
definer: '',
|
||||
sql: row.pg_get_functiondef.match(/(\$(.*)\$)(.*)(\$(.*)\$)/gs)[0],
|
||||
parameters: parameters || [],
|
||||
name: row.Function,
|
||||
comment: row['Create Function'].match(/(?<=COMMENT ').*?(?=')/gs) ? row['Create Function'].match(/(?<=COMMENT ').*?(?=')/gs)[0] : '',
|
||||
security: row['Create Function'].includes('SQL SECURITY INVOKER') ? 'INVOKER' : 'DEFINER',
|
||||
deterministic: row['Create Function'].includes('DETERMINISTIC'),
|
||||
dataAccess,
|
||||
returns: output[0].toUpperCase(),
|
||||
returnsLength: +output[1]
|
||||
name: func,
|
||||
comment: '',
|
||||
security: row.pg_get_functiondef.includes('SECURITY DEFINER') ? 'DEFINER' : 'INVOKER',
|
||||
deterministic: null,
|
||||
dataAccess: null,
|
||||
language: row.pg_get_functiondef.match(/(?<=LANGUAGE )(.*)(?<=[\S+\n\r\s])/gm)[0],
|
||||
returns: row.pg_get_functiondef.match(/(?<=RETURNS )(.*)(?<=[\S+\n\r\s])/gm)[0].replace('SETOF ', '').toUpperCase()
|
||||
};
|
||||
})[0];
|
||||
}
|
||||
@@ -768,7 +762,7 @@ export class PostgreSQLClient extends AntaresCore {
|
||||
* @memberof PostgreSQLClient
|
||||
*/
|
||||
async dropFunction (params) {
|
||||
const sql = `DROP FUNCTION \`${params.func}\``;
|
||||
const sql = `DROP FUNCTION ${this._schema}.${params.func}`;
|
||||
return await this.raw(sql);
|
||||
}
|
||||
|
||||
@@ -801,18 +795,23 @@ export class PostgreSQLClient extends AntaresCore {
|
||||
* @memberof PostgreSQLClient
|
||||
*/
|
||||
async createFunction (func) {
|
||||
const parameters = func.parameters.reduce((acc, curr) => {
|
||||
acc.push(`\`${curr.name}\` ${curr.type}${curr.length ? `(${curr.length})` : ''}`);
|
||||
const parameters = 'parameters' in func
|
||||
? func.parameters.reduce((acc, curr) => {
|
||||
acc.push(`${curr.context} ${curr.name} ${curr.type}${curr.length ? `(${curr.length})` : ''}`);
|
||||
return acc;
|
||||
}, []).join(',');
|
||||
}, []).join(',')
|
||||
: '';
|
||||
|
||||
const sql = `CREATE ${func.definer ? `DEFINER=${func.definer} ` : ''}FUNCTION \`${func.name}\`(${parameters}) RETURNS ${func.returns}${func.returnsLength ? `(${func.returnsLength})` : ''}
|
||||
LANGUAGE SQL
|
||||
${func.deterministic ? 'DETERMINISTIC' : 'NOT DETERMINISTIC'}
|
||||
${func.dataAccess}
|
||||
SQL SECURITY ${func.security}
|
||||
COMMENT '${func.comment}'
|
||||
${func.sql}`;
|
||||
if (this._schema !== 'public')
|
||||
await this.use(this._schema);
|
||||
|
||||
const body = func.returns ? func.sql : '$BODY$\n$BODY$';
|
||||
|
||||
const sql = `CREATE FUNCTION ${this._schema}.${func.name}(${parameters})
|
||||
RETURNS ${func.returns || 'void'}
|
||||
LANGUAGE ${func.language}
|
||||
SECURITY ${func.security}
|
||||
AS ${body}`;
|
||||
|
||||
return await this.raw(sql, { split: false });
|
||||
}
|
||||
@@ -1000,7 +999,7 @@ export class PostgreSQLClient extends AntaresCore {
|
||||
name
|
||||
} = params;
|
||||
|
||||
const sql = `CREATE TABLE ${name} (${name}_id INTEGER NULL); ALTER TABLE ${name} DROP COLUMN ${name}_id`;
|
||||
const sql = `CREATE TABLE ${this._schema}.${name} (${name}_id INTEGER NULL); ALTER TABLE ${this._schema}.${name} DROP COLUMN ${name}_id`;
|
||||
|
||||
return await this.raw(sql);
|
||||
}
|
||||
@@ -1022,6 +1021,9 @@ export class PostgreSQLClient extends AntaresCore {
|
||||
options
|
||||
} = params;
|
||||
|
||||
if (this._schema !== 'public')
|
||||
await this.use(this._schema);
|
||||
|
||||
let sql = '';
|
||||
const alterColumns = [];
|
||||
const renameColumns = [];
|
||||
@@ -1040,7 +1042,7 @@ export class PostgreSQLClient extends AntaresCore {
|
||||
const length = typeInfo.length ? addition.numLength || addition.charLength || addition.datePrecision : false;
|
||||
|
||||
alterColumns.push(`ADD COLUMN ${addition.name}
|
||||
${addition.type.toUpperCase()}${length ? `(${length})` : ''}
|
||||
${addition.type.toUpperCase()}${length ? `(${length})` : ''}${addition.isArray ? '[]' : ''}
|
||||
${addition.unsigned ? 'UNSIGNED' : ''}
|
||||
${addition.zerofill ? 'ZEROFILL' : ''}
|
||||
${addition.nullable ? 'NULL' : 'NOT NULL'}
|
||||
@@ -1061,7 +1063,7 @@ export class PostgreSQLClient extends AntaresCore {
|
||||
else if (type === 'UNIQUE')
|
||||
alterColumns.push(`ADD CONSTRAINT ${addition.name} UNIQUE (${fields})`);
|
||||
else
|
||||
manageIndexes.push(`CREATE INDEX ${addition.name} ON ${table}(${fields})`);
|
||||
manageIndexes.push(`CREATE INDEX ${addition.name} ON ${this._schema}.${table}(${fields})`);
|
||||
});
|
||||
|
||||
// ADD FOREIGN KEYS
|
||||
@@ -1089,24 +1091,22 @@ export class PostgreSQLClient extends AntaresCore {
|
||||
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'}`);
|
||||
alterColumns.push(`ALTER COLUMN "${change.name}" TYPE ${localType}${length ? `(${length})` : ''}${change.isArray ? '[]' : ''} USING "${change.name}"::${localType}`);
|
||||
alterColumns.push(`ALTER COLUMN "${change.name}" ${change.nullable ? 'DROP NOT NULL' : 'SET NOT NULL'}`);
|
||||
alterColumns.push(`ALTER COLUMN "${change.name}" ${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}')`);
|
||||
createSequences.push(`CREATE SEQUENCE IF NOT EXISTS ${sequenceName} OWNED BY "${table}"."${change.name}"`);
|
||||
alterColumns.push(`ALTER COLUMN "${change.name}" SET DEFAULT nextval('${sequenceName}')`);
|
||||
}
|
||||
|
||||
if (change.orgName !== change.name)
|
||||
renameColumns.push(`ALTER TABLE "${table}" RENAME COLUMN "${change.orgName}" TO "${change.name}"`);
|
||||
renameColumns.push(`ALTER TABLE "${this._schema}"."${table}" RENAME COLUMN "${change.orgName}" TO "${change.name}"`);
|
||||
});
|
||||
|
||||
// CHANGE INDEX
|
||||
indexChanges.changes.forEach(change => {
|
||||
if (change.oldType === 'PRIMARY')
|
||||
alterColumns.push('DROP PRIMARY KEY');
|
||||
else if (change.oldType === 'UNIQUE')
|
||||
if (['PRIMARY', 'UNIQUE'].includes(change.oldType))
|
||||
alterColumns.push(`DROP CONSTRAINT ${change.oldName}`);
|
||||
else
|
||||
manageIndexes.push(`DROP INDEX ${change.oldName}`);
|
||||
@@ -1119,7 +1119,7 @@ export class PostgreSQLClient extends AntaresCore {
|
||||
else if (type === 'UNIQUE')
|
||||
alterColumns.push(`ADD CONSTRAINT ${change.name} UNIQUE (${fields})`);
|
||||
else
|
||||
manageIndexes.push(`CREATE INDEX ${change.name} ON ${table}(${fields})`);
|
||||
manageIndexes.push(`CREATE INDEX ${change.name} ON ${this._schema}.${table}(${fields})`);
|
||||
});
|
||||
|
||||
// CHANGE FOREIGN KEYS
|
||||
@@ -1146,13 +1146,13 @@ export class PostgreSQLClient extends AntaresCore {
|
||||
alterColumns.push(`DROP CONSTRAINT ${deletion.constraintName}`);
|
||||
});
|
||||
|
||||
if (alterColumns.length) sql += `ALTER TABLE "${table}" ${alterColumns.join(', ')}; `;
|
||||
|
||||
// RENAME
|
||||
if (renameColumns.length) sql += `${renameColumns.join(';')}; `;
|
||||
if (alterColumns.length) sql += `ALTER TABLE "${this._schema}"."${table}" ${alterColumns.join(', ')}; `;
|
||||
if (createSequences.length) sql = `${createSequences.join(';')}; ${sql}`;
|
||||
if (manageIndexes.length) sql = `${manageIndexes.join(';')}; ${sql}`;
|
||||
if (options.name) sql += `ALTER TABLE "${table}" RENAME TO "${options.name}"; `;
|
||||
if (options.name) sql += `ALTER TABLE "${this._schema}"."${table}" RENAME TO "${options.name}"; `;
|
||||
|
||||
// RENAME
|
||||
if (renameColumns.length) sql = `${renameColumns.join(';')}; ${sql}`;
|
||||
|
||||
return await this.raw(sql);
|
||||
}
|
||||
@@ -1164,7 +1164,7 @@ export class PostgreSQLClient extends AntaresCore {
|
||||
* @memberof PostgreSQLClient
|
||||
*/
|
||||
async truncateTable (params) {
|
||||
const sql = `TRUNCATE TABLE ${params.table}`;
|
||||
const sql = `TRUNCATE TABLE ${this._schema}.${params.table}`;
|
||||
return await this.raw(sql);
|
||||
}
|
||||
|
||||
@@ -1175,7 +1175,7 @@ export class PostgreSQLClient extends AntaresCore {
|
||||
* @memberof PostgreSQLClient
|
||||
*/
|
||||
async dropTable (params) {
|
||||
const sql = `DROP TABLE ${params.table}`;
|
||||
const sql = `DROP TABLE ${this._schema}.${params.table}`;
|
||||
return await this.raw(sql);
|
||||
}
|
||||
|
||||
@@ -1249,6 +1249,10 @@ export class PostgreSQLClient extends AntaresCore {
|
||||
split: true,
|
||||
...args
|
||||
};
|
||||
|
||||
if (args.nest && this._schema !== 'public')
|
||||
await this.use(this._schema);
|
||||
|
||||
const resultsArr = [];
|
||||
let paramsArr = [];
|
||||
const queries = args.split ? sql.split(';') : [sql];
|
||||
@@ -1338,7 +1342,7 @@ export class PostgreSQLClient extends AntaresCore {
|
||||
if (!paramObj.table || !paramObj.schema) continue;
|
||||
|
||||
try { // Column details
|
||||
const columns = await this.getTableColumns(paramObj);
|
||||
const columns = await this.getTableColumns(paramObj, false);
|
||||
const indexes = await this.getTableIndexes(paramObj);
|
||||
|
||||
remappedFields = remappedFields.map(field => {
|
||||
|
@@ -58,6 +58,7 @@ export default {
|
||||
},
|
||||
mounted () {
|
||||
ipcRenderer.send('check-for-updates');
|
||||
this.checkVersionUpdate();
|
||||
|
||||
const Menu = remote.Menu;
|
||||
|
||||
@@ -100,7 +101,8 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
showNewConnModal: 'application/showNewConnModal'
|
||||
showNewConnModal: 'application/showNewConnModal',
|
||||
checkVersionUpdate: 'application/checkVersionUpdate'
|
||||
})
|
||||
}
|
||||
};
|
||||
|
@@ -69,7 +69,7 @@ export default {
|
||||
this.editor = ace.edit(`editor-${this.id}`, {
|
||||
mode: `ace/mode/${this.mode}`,
|
||||
theme: `ace/theme/${this.editorTheme}`,
|
||||
value: this.value,
|
||||
value: this.value || '',
|
||||
fontSize: '14px',
|
||||
printMargin: false,
|
||||
readOnly: this.readOnly,
|
||||
|
@@ -51,7 +51,7 @@ export default {
|
||||
}),
|
||||
isValidDefault () {
|
||||
if (!this.foreignList.length) return true;
|
||||
return this.foreignList.some(foreign => foreign.foreign_column.toString() === this.value.toString());
|
||||
return this.value === null || this.foreignList.some(foreign => foreign.foreign_column.toString() === this.value.toString());
|
||||
}
|
||||
},
|
||||
async created () {
|
||||
|
@@ -15,7 +15,7 @@
|
||||
<div class="content">
|
||||
<form class="form-horizontal">
|
||||
<div
|
||||
v-for="(parameter, i) in localRoutine.parameters"
|
||||
v-for="(parameter, i) in inParameters"
|
||||
:key="parameter._id"
|
||||
class="form-group"
|
||||
>
|
||||
@@ -26,7 +26,7 @@
|
||||
<div class="input-group">
|
||||
<input
|
||||
:ref="i === 0 ? 'firstInput' : ''"
|
||||
v-model="values[parameter.name]"
|
||||
v-model="values[`${i}-${parameter.name}`]"
|
||||
class="form-input"
|
||||
type="text"
|
||||
>
|
||||
@@ -43,6 +43,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { NUMBER, FLOAT } from 'common/fieldTypes';
|
||||
import ConfirmModal from '@/components/BaseConfirmModal';
|
||||
|
||||
export default {
|
||||
@@ -57,13 +58,19 @@ export default {
|
||||
}
|
||||
},
|
||||
props: {
|
||||
localRoutine: Object
|
||||
localRoutine: Object,
|
||||
client: String
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
values: {}
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
inParameters () {
|
||||
return this.localRoutine.parameters.filter(param => param.context === 'IN');
|
||||
}
|
||||
},
|
||||
created () {
|
||||
window.addEventListener('keydown', this.onKey);
|
||||
|
||||
@@ -81,8 +88,23 @@ export default {
|
||||
return '';
|
||||
},
|
||||
runRoutine () {
|
||||
const valArr = Object.keys(this.values).reduce((acc, curr) => {
|
||||
const value = isNaN(this.values[curr]) ? `"${this.values[curr]}"` : this.values[curr];
|
||||
const valArr = Object.keys(this.values).reduce((acc, curr, i) => {
|
||||
let qc;
|
||||
switch (this.client) {
|
||||
case 'maria':
|
||||
case 'mysql':
|
||||
qc = '"';
|
||||
break;
|
||||
case 'pg':
|
||||
qc = '\'';
|
||||
break;
|
||||
default:
|
||||
qc = '"';
|
||||
}
|
||||
|
||||
const param = this.localRoutine.parameters.find(param => `${i}-${param.name}` === curr);
|
||||
|
||||
const value = [...NUMBER, ...FLOAT].includes(param.type) ? this.values[curr] : `${qc}${this.values[curr]}${qc}`;
|
||||
acc.push(value);
|
||||
return acc;
|
||||
}, []);
|
||||
|
@@ -7,7 +7,7 @@
|
||||
>
|
||||
<template :slot="'header'">
|
||||
<div class="d-flex">
|
||||
<i class="mdi mdi-24px mdi-plus mr-1" /> {{ $t('message.createNewRoutine') }}
|
||||
<i class="mdi mdi-24px mdi-plus mr-1" /> {{ $t('message.createNewFunction') }}
|
||||
</div>
|
||||
</template>
|
||||
<div :slot="'body'">
|
||||
@@ -25,7 +25,19 @@
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div v-if="customizations.languages" class="form-group">
|
||||
<label class="form-label col-4">
|
||||
{{ $t('word.language') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<select v-model="localFunction.language" class="form-select">
|
||||
<option v-for="language in customizations.languages" :key="language">
|
||||
{{ language }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="customizations.definer" class="form-group">
|
||||
<label class="form-label col-4">
|
||||
{{ $t('word.definer') }}
|
||||
</label>
|
||||
@@ -53,42 +65,7 @@
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label col-4">
|
||||
{{ $t('word.returns') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<div class="input-group">
|
||||
<select
|
||||
v-model="localFunction.returns"
|
||||
class="form-select text-uppercase"
|
||||
style="width: 0;"
|
||||
>
|
||||
<optgroup
|
||||
v-for="group in workspace.dataTypes"
|
||||
:key="group.group"
|
||||
:label="group.group"
|
||||
>
|
||||
<option
|
||||
v-for="type in group.types"
|
||||
:key="type.name"
|
||||
:selected="localFunction.returns === type.name"
|
||||
:value="type.name"
|
||||
>
|
||||
{{ type.name }}
|
||||
</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
<input
|
||||
v-model="localFunction.returnsLength"
|
||||
class="form-input"
|
||||
type="number"
|
||||
min="0"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div v-if="customizations.comment" class="form-group">
|
||||
<label class="form-label col-4">
|
||||
{{ $t('word.comment') }}
|
||||
</label>
|
||||
@@ -111,7 +88,7 @@
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div v-if="customizations.functionDataAccess" class="form-group">
|
||||
<label class="form-label col-4">
|
||||
{{ $t('message.dataAccess') }}
|
||||
</label>
|
||||
@@ -124,7 +101,7 @@
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div v-if="customizations.functionDeterministic" class="form-group">
|
||||
<div class="col-4" />
|
||||
<div class="column">
|
||||
<label class="form-checkbox form-inline">
|
||||
@@ -152,11 +129,12 @@ export default {
|
||||
return {
|
||||
localFunction: {
|
||||
definer: '',
|
||||
sql: 'BEGIN\r\n RETURN NULL;\r\nEND',
|
||||
sql: '',
|
||||
parameters: [],
|
||||
name: '',
|
||||
comment: '',
|
||||
returns: 'INT',
|
||||
language: null,
|
||||
returns: null,
|
||||
returnsLength: 10,
|
||||
security: 'DEFINER',
|
||||
deterministic: false,
|
||||
@@ -168,9 +146,17 @@ export default {
|
||||
computed: {
|
||||
schema () {
|
||||
return this.workspace.breadcrumbs.schema;
|
||||
},
|
||||
customizations () {
|
||||
return this.workspace.customizations;
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
if (this.customizations.languages)
|
||||
this.localFunction.language = this.customizations.languages[0];
|
||||
|
||||
if (this.customizations.procedureSql)
|
||||
this.localFunction.sql = this.customizations.procedureSql;
|
||||
setTimeout(() => {
|
||||
this.$refs.firstInput.focus();
|
||||
}, 20);
|
||||
|
@@ -25,7 +25,19 @@
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div v-if="customizations.languages" class="form-group">
|
||||
<label class="form-label col-4">
|
||||
{{ $t('word.language') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<select v-model="localRoutine.language" class="form-select">
|
||||
<option v-for="language in customizations.languages" :key="language">
|
||||
{{ language }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="customizations.definer" class="form-group">
|
||||
<label class="form-label col-4">
|
||||
{{ $t('word.definer') }}
|
||||
</label>
|
||||
@@ -53,7 +65,7 @@
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div v-if="customizations.comment" class="form-group">
|
||||
<label class="form-label col-4">
|
||||
{{ $t('word.comment') }}
|
||||
</label>
|
||||
@@ -76,7 +88,7 @@
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div v-if="customizations.comment" class="form-group">
|
||||
<label class="form-label col-4">
|
||||
{{ $t('message.dataAccess') }}
|
||||
</label>
|
||||
@@ -89,7 +101,7 @@
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div v-if="customizations.procedureDeterministic" class="form-group">
|
||||
<div class="col-4" />
|
||||
<div class="column">
|
||||
<label class="form-checkbox form-inline">
|
||||
@@ -117,10 +129,11 @@ export default {
|
||||
return {
|
||||
localRoutine: {
|
||||
definer: '',
|
||||
sql: 'BEGIN\r\n\r\nEND',
|
||||
sql: '',
|
||||
parameters: [],
|
||||
name: '',
|
||||
comment: '',
|
||||
language: null,
|
||||
security: 'DEFINER',
|
||||
deterministic: false,
|
||||
dataAccess: 'CONTAINS SQL'
|
||||
@@ -131,9 +144,17 @@ export default {
|
||||
computed: {
|
||||
schema () {
|
||||
return this.workspace.breadcrumbs.schema;
|
||||
},
|
||||
customizations () {
|
||||
return this.workspace.customizations;
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
if (this.customizations.languages)
|
||||
this.localRoutine.language = this.customizations.languages[0];
|
||||
|
||||
if (this.customizations.procedureSql)
|
||||
this.localRoutine.sql = this.customizations.procedureSql;
|
||||
setTimeout(() => {
|
||||
this.$refs.firstInput.focus();
|
||||
}, 20);
|
||||
|
@@ -37,6 +37,13 @@
|
||||
>
|
||||
<a class="c-hand" :class="{'badge badge-update': hasUpdates}">{{ $t('word.update') }}</a>
|
||||
</li>
|
||||
<li
|
||||
class="tab-item"
|
||||
:class="{'active': selectedTab === 'changelog'}"
|
||||
@click="selectTab('changelog')"
|
||||
>
|
||||
<a class="c-hand">{{ $t('word.changelog') }}</a>
|
||||
</li>
|
||||
<li
|
||||
class="tab-item"
|
||||
:class="{'active': selectedTab === 'about'}"
|
||||
@@ -46,7 +53,7 @@
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div v-if="selectedTab === 'general'" class="panel-body py-4">
|
||||
<div v-show="selectedTab === 'general'" class="panel-body py-4">
|
||||
<div class="container">
|
||||
<form class="form-horizontal columns">
|
||||
<div class="column col-12 h6 text-uppercase mb-1">
|
||||
@@ -134,7 +141,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="selectedTab === 'themes'" class="panel-body py-4">
|
||||
<div v-show="selectedTab === 'themes'" class="panel-body py-4">
|
||||
<div class="container">
|
||||
<div class="columns">
|
||||
<div class="column col-12 h6 text-uppercase mb-2">
|
||||
@@ -207,17 +214,20 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="selectedTab === 'update'" class="panel-body py-4">
|
||||
<div v-show="selectedTab === 'update'" class="panel-body py-4">
|
||||
<ModalSettingsUpdate />
|
||||
</div>
|
||||
<div v-show="selectedTab === 'changelog'" class="panel-body py-4">
|
||||
<ModalSettingsChangelog />
|
||||
</div>
|
||||
|
||||
<div v-if="selectedTab === 'about'" class="panel-body py-4">
|
||||
<div v-show="selectedTab === 'about'" class="panel-body py-4">
|
||||
<div class="text-center">
|
||||
<img :src="require('@/images/logo.svg').default" width="128">
|
||||
<h4>{{ appName }}</h4>
|
||||
<p>
|
||||
{{ $t('word.version') }} {{ appVersion }}<br>
|
||||
<a class="c-hand" @click="openOutside('https://github.com/Fabio286/antares')">GitHub</a> | <a class="c-hand" @click="openOutside('https://github.com/Fabio286/antares/blob/master/CHANGELOG.md')">CHANGELOG</a><br>
|
||||
<a class="c-hand" @click="openOutside('https://github.com/Fabio286/antares')">GitHub</a> | <a class="c-hand" @click="openOutside('https://antares-sql.app/')">Website</a><br>
|
||||
<small>{{ $t('word.author') }} <a class="c-hand" @click="openOutside('https://github.com/Fabio286')">Fabio Di Stasio</a></small><br>
|
||||
<small>{{ $t('message.madeWithJS') }}</small>
|
||||
</p>
|
||||
@@ -233,6 +243,7 @@
|
||||
import { mapActions, mapGetters } from 'vuex';
|
||||
import localesNames from '@/i18n/supported-locales';
|
||||
import ModalSettingsUpdate from '@/components/ModalSettingsUpdate';
|
||||
import ModalSettingsChangelog from '@/components/ModalSettingsChangelog';
|
||||
import BaseTextEditor from '@/components/BaseTextEditor';
|
||||
const { shell } = require('electron');
|
||||
|
||||
@@ -240,6 +251,7 @@ export default {
|
||||
name: 'ModalSettings',
|
||||
components: {
|
||||
ModalSettingsUpdate,
|
||||
ModalSettingsChangelog,
|
||||
BaseTextEditor
|
||||
},
|
||||
data () {
|
||||
@@ -394,11 +406,16 @@ ORDER BY
|
||||
|
||||
<style lang="scss">
|
||||
#settings {
|
||||
.modal-container {
|
||||
position: absolute;
|
||||
top: 17.5vh;
|
||||
|
||||
.modal-body {
|
||||
overflow: hidden;
|
||||
|
||||
.panel-body {
|
||||
height: calc(70vh - 70px);
|
||||
min-height: calc(25vh - 70px);
|
||||
max-height: 65vh;
|
||||
overflow: auto;
|
||||
|
||||
.theme-block {
|
||||
@@ -443,5 +460,6 @@ ORDER BY
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
82
src/renderer/components/ModalSettingsChangelog.vue
Normal file
82
src/renderer/components/ModalSettingsChangelog.vue
Normal file
@@ -0,0 +1,82 @@
|
||||
<template>
|
||||
<div class="p-relative">
|
||||
<BaseLoader v-if="isLoading" />
|
||||
<div
|
||||
id="changelog"
|
||||
class="container"
|
||||
v-html="changelog"
|
||||
/>
|
||||
<div v-if="isError" class="empty">
|
||||
<div class="empty-icon">
|
||||
<i class="mdi mdi-48px mdi-alert-outline" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { mapGetters } from 'vuex';
|
||||
import marked from 'marked';
|
||||
import BaseLoader from '@/components/BaseLoader';
|
||||
|
||||
export default {
|
||||
name: 'ModalSettingsChangelog',
|
||||
components: {
|
||||
BaseLoader
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
changelog: '',
|
||||
isLoading: true,
|
||||
error: '',
|
||||
isError: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({ appVersion: 'application/appVersion' })
|
||||
},
|
||||
created () {
|
||||
this.getChangelog();
|
||||
},
|
||||
methods: {
|
||||
async getChangelog () {
|
||||
try {
|
||||
const apiRes = await fetch(`https://api.github.com/repos/Fabio286/antares/releases/tags/v${this.appVersion}`, {
|
||||
method: 'GET'
|
||||
});
|
||||
|
||||
const { body } = await apiRes.json();
|
||||
const markdown = body.substr(0, body.indexOf('### Download'));
|
||||
const renderer = {
|
||||
link (href, title, text) {
|
||||
return text;
|
||||
},
|
||||
listitem (text) {
|
||||
return `<li>${text.replace(/ *\([^)]*\) */g, '')}</li>`;
|
||||
}
|
||||
};
|
||||
|
||||
marked.use({ renderer });
|
||||
|
||||
this.changelog = marked(markdown);
|
||||
}
|
||||
catch (err) {
|
||||
this.error = err.message;
|
||||
this.isError = true;
|
||||
}
|
||||
this.isLoading = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
</script>
|
||||
<style lang="scss">
|
||||
#changelog {
|
||||
h3 {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
li {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
@@ -126,9 +126,13 @@ export default {
|
||||
return 'sql';
|
||||
}
|
||||
},
|
||||
cursorPosition () {
|
||||
return this.editor.session.doc.positionToIndex(this.editor.getCursorPosition());
|
||||
},
|
||||
lastWord () {
|
||||
const words = this.value.split(' ');
|
||||
return words[words.length - 1];
|
||||
const charsBefore = this.value.slice(0, this.cursorPosition);
|
||||
const words = charsBefore.replaceAll('\n', ' ').split(' ').filter(Boolean);
|
||||
return words.pop();
|
||||
},
|
||||
isLastWordATable () {
|
||||
return /\w+\.\w*/gm.test(this.lastWord);
|
||||
@@ -209,7 +213,7 @@ export default {
|
||||
if (['insertstring', 'backspace', 'del'].includes(e.command.name)) {
|
||||
if (this.isLastWordATable || e.args === '.') {
|
||||
if (e.args !== ' ') {
|
||||
const table = this.tables.find(t => t.name === this.lastWord.split('.').pop());
|
||||
const table = this.tables.find(t => t.name === this.lastWord.split('.').pop().trim());
|
||||
|
||||
if (table) {
|
||||
const params = {
|
||||
|
@@ -11,9 +11,9 @@
|
||||
|
||||
<div class="footer-right-elements">
|
||||
<ul class="footer-elements">
|
||||
<li class="footer-element footer-link" @click="openOutside('https://github.com/sponsors/Fabio286')">
|
||||
<i class="mdi mdi-18px mdi-coffee mr-1" />
|
||||
<small>{{ $t('word.donate') }}</small>
|
||||
<li class="footer-element footer-link" @click="openOutside('https://www.treedom.net/en/user/fabio-di-stasio/event/antares-for-the-planet')">
|
||||
<i class="mdi mdi-18px mdi-tree mr-1" />
|
||||
<small>{{ $t('message.plantATree') }}</small>
|
||||
</li>
|
||||
<li class="footer-element footer-link" @click="openOutside('https://github.com/Fabio286/antares/issues')">
|
||||
<i class="mdi mdi-18px mdi-bug" />
|
||||
|
@@ -19,7 +19,7 @@
|
||||
@contextmenu.prevent="contextMenu($event, connection)"
|
||||
@mouseover.self="tooltipPosition"
|
||||
>
|
||||
<i class="settingbar-element-icon dbi" :class="`dbi-${connection.client} ${connected.includes(connection.uid) ? 'badge' : ''}`" />
|
||||
<i class="settingbar-element-icon dbi" :class="`dbi-${connection.client} ${getStatusBadge(connection.uid)}`" />
|
||||
<span class="ex-tooltip-content">{{ getConnectionName(connection.uid) }}</span>
|
||||
</li>
|
||||
</draggable>
|
||||
@@ -73,7 +73,7 @@ export default {
|
||||
...mapGetters({
|
||||
getConnections: 'connections/getConnections',
|
||||
getConnectionName: 'connections/getConnectionName',
|
||||
connected: 'workspaces/getConnected',
|
||||
getWorkspace: 'workspaces/getWorkspace',
|
||||
selectedWorkspace: 'workspaces/getSelected',
|
||||
updateStatus: 'application/getUpdateStatus'
|
||||
}),
|
||||
@@ -109,6 +109,22 @@ export default {
|
||||
const el = e.target;
|
||||
const fromTop = window.pageYOffset + el.getBoundingClientRect().top - (el.offsetHeight / 4);
|
||||
el.querySelector('.ex-tooltip-content').style.top = `${fromTop}px`;
|
||||
},
|
||||
getStatusBadge (uid) {
|
||||
if (this.getWorkspace(uid)) {
|
||||
const status = this.getWorkspace(uid).connection_status;
|
||||
|
||||
switch (status) {
|
||||
case 'connected':
|
||||
return 'badge badge-connected';
|
||||
case 'connecting':
|
||||
return 'badge badge-connecting';
|
||||
case 'failed':
|
||||
return 'badge badge-failed';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@@ -1,7 +1,7 @@
|
||||
<template>
|
||||
<div v-show="isSelected" class="workspace column columns col-gapless">
|
||||
<WorkspaceExploreBar :connection="connection" :is-selected="isSelected" />
|
||||
<div v-if="workspace.connected" class="workspace-tabs column columns col-gapless">
|
||||
<div v-if="workspace.connection_status === 'connected'" class="workspace-tabs column columns col-gapless">
|
||||
<ul
|
||||
id="tabWrap"
|
||||
ref="tabWrap"
|
||||
|
@@ -8,7 +8,7 @@
|
||||
>
|
||||
<div class="workspace-explorebar-header">
|
||||
<span class="workspace-explorebar-title">{{ connectionName }}</span>
|
||||
<span v-if="workspace.connected" class="workspace-explorebar-tools">
|
||||
<span v-if="workspace.connection_status === 'connected'" class="workspace-explorebar-tools">
|
||||
<i
|
||||
class="mdi mdi-18px mdi-database-plus c-hand mr-2"
|
||||
:title="$t('message.createNewSchema')"
|
||||
@@ -28,7 +28,7 @@
|
||||
</span>
|
||||
</div>
|
||||
<div class="workspace-explorebar-search">
|
||||
<div v-if="workspace.connected" class="has-icon-right">
|
||||
<div v-if="workspace.connection_status === 'connected'" class="has-icon-right">
|
||||
<input
|
||||
v-model="searchTerm"
|
||||
class="form-input input-sm"
|
||||
@@ -39,7 +39,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<WorkspaceConnectPanel
|
||||
v-if="!workspace.connected"
|
||||
v-if="workspace.connection_status !== 'connected'"
|
||||
class="workspace-explorebar-body"
|
||||
:connection="connection"
|
||||
/>
|
||||
|
@@ -32,6 +32,7 @@
|
||||
<ModalAskParameters
|
||||
v-if="isAskingParameters"
|
||||
:local-routine="localElement"
|
||||
:client="workspace.client"
|
||||
@confirm="runElement"
|
||||
@close="hideAskParamsModal"
|
||||
/>
|
||||
@@ -205,13 +206,13 @@ export default {
|
||||
case 'maria':
|
||||
case 'mysql':
|
||||
case 'pg':
|
||||
sql = `CALL \`${this.localElement.name}\` (${params.join(',')})`;
|
||||
sql = `CALL ${this.localElement.name}(${params.join(',')})`;
|
||||
break;
|
||||
case 'mssql':
|
||||
sql = `EXEC ${this.localElement.name} ${params.join(',')}`;
|
||||
break;
|
||||
default:
|
||||
sql = `CALL \`${this.localElement.name}\` (${params.join(',')})`;
|
||||
sql = `CALL \`${this.localElement.name}\`(${params.join(',')})`;
|
||||
}
|
||||
|
||||
this.newTab({ uid: this.workspace.uid, content: sql, autorun: true });
|
||||
@@ -247,9 +248,11 @@ export default {
|
||||
switch (this.workspace.client) { // TODO: move in a better place
|
||||
case 'maria':
|
||||
case 'mysql':
|
||||
case 'pg':
|
||||
sql = `SELECT \`${this.localElement.name}\` (${params.join(',')})`;
|
||||
break;
|
||||
case 'pg':
|
||||
sql = `SELECT ${this.localElement.name}(${params.join(',')})`;
|
||||
break;
|
||||
case 'mssql':
|
||||
sql = `SELECT ${this.localElement.name} ${params.join(',')}`;
|
||||
break;
|
||||
|
@@ -75,8 +75,8 @@
|
||||
<div>
|
||||
<ul class="menu menu-nav pt-0">
|
||||
<li
|
||||
v-for="procedure of filteredProcedures"
|
||||
:key="procedure.name"
|
||||
v-for="(procedure, i) of filteredProcedures"
|
||||
:key="`${procedure.name}-${i}`"
|
||||
class="menu-item"
|
||||
:class="{'text-bold': breadcrumbs.schema === database.name && breadcrumbs.procedure === procedure.name}"
|
||||
@click="setBreadcrumbs({schema: database.name, procedure: procedure.name})"
|
||||
@@ -103,8 +103,8 @@
|
||||
<div>
|
||||
<ul class="menu menu-nav pt-0">
|
||||
<li
|
||||
v-for="func of filteredFunctions"
|
||||
:key="func.name"
|
||||
v-for="(func, i) of filteredFunctions"
|
||||
:key="`${func.name}-${i}`"
|
||||
class="menu-item"
|
||||
:class="{'text-bold': breadcrumbs.schema === database.name && breadcrumbs.function === func.name}"
|
||||
@click="setBreadcrumbs({schema: database.name, function: func.name})"
|
||||
|
@@ -26,7 +26,19 @@
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div v-if="customizations.languages" class="form-group">
|
||||
<label class="form-label col-4">
|
||||
{{ $t('word.language') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<select v-model="optionsProxy.language" class="form-select">
|
||||
<option v-for="language in customizations.languages" :key="language">
|
||||
{{ language }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="customizations.definer" class="form-group">
|
||||
<label class="form-label col-4">
|
||||
{{ $t('word.definer') }}
|
||||
</label>
|
||||
@@ -65,6 +77,12 @@
|
||||
class="form-select text-uppercase"
|
||||
style="width: 0;"
|
||||
>
|
||||
<option v-if="localOptions.returns === 'VOID'">
|
||||
VOID
|
||||
</option>
|
||||
<option v-if="!isInDataTypes">
|
||||
{{ localOptions.returns }}
|
||||
</option>
|
||||
<optgroup
|
||||
v-for="group in workspace.dataTypes"
|
||||
:key="group.group"
|
||||
@@ -81,6 +99,7 @@
|
||||
</optgroup>
|
||||
</select>
|
||||
<input
|
||||
v-if="customizations.parametersLength"
|
||||
v-model="optionsProxy.returnsLength"
|
||||
class="form-input"
|
||||
type="number"
|
||||
@@ -89,7 +108,7 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div v-if="customizations.comment" class="form-group">
|
||||
<label class="form-label col-4">
|
||||
{{ $t('word.comment') }}
|
||||
</label>
|
||||
@@ -112,7 +131,7 @@
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div v-if="customizations.functionDataAccess" class="form-group">
|
||||
<label class="form-label col-4">
|
||||
{{ $t('message.dataAccess') }}
|
||||
</label>
|
||||
@@ -125,7 +144,7 @@
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div v-if="customizations.functionDeterministic" class="form-group">
|
||||
<div class="col-4" />
|
||||
<div class="column">
|
||||
<label class="form-checkbox form-inline">
|
||||
@@ -159,6 +178,19 @@ export default {
|
||||
computed: {
|
||||
isTableNameValid () {
|
||||
return this.optionsProxy.name !== '';
|
||||
},
|
||||
customizations () {
|
||||
return this.workspace.customizations;
|
||||
},
|
||||
isInDataTypes () {
|
||||
let typeNames = [];
|
||||
for (const group of this.workspace.dataTypes) {
|
||||
typeNames = group.types.reduce((acc, curr) => {
|
||||
acc.push(curr.name);
|
||||
return acc;
|
||||
}, []);
|
||||
}
|
||||
return typeNames.includes(this.localOptions.returns);
|
||||
}
|
||||
},
|
||||
created () {
|
||||
|
@@ -49,7 +49,7 @@
|
||||
<div class="tile-title">
|
||||
{{ param.name }}
|
||||
</div>
|
||||
<small class="tile-subtitle text-gray">{{ param.type }}{{ param.length ? `(${param.length})` : '' }}</small>
|
||||
<small class="tile-subtitle text-gray">{{ param.type }}{{ param.length ? `(${param.length})` : '' }} · {{ param.context }}</small>
|
||||
</div>
|
||||
<div class="tile-action">
|
||||
<button
|
||||
@@ -106,7 +106,7 @@
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div v-if="customizations.parametersLength" class="form-group">
|
||||
<label class="form-label col-3">
|
||||
{{ $t('word.length') }}
|
||||
</label>
|
||||
@@ -119,6 +119,37 @@
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="customizations.functionContext" class="form-group">
|
||||
<label class="form-label col-3">
|
||||
{{ $t('word.context') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<label class="form-radio">
|
||||
<input
|
||||
v-model="selectedParamObj.context"
|
||||
type="radio"
|
||||
name="context"
|
||||
value="IN"
|
||||
> <i class="form-icon" /> IN
|
||||
</label>
|
||||
<label class="form-radio">
|
||||
<input
|
||||
v-model="selectedParamObj.context"
|
||||
type="radio"
|
||||
value="OUT"
|
||||
name="context"
|
||||
> <i class="form-icon" /> OUT
|
||||
</label>
|
||||
<label class="form-radio">
|
||||
<input
|
||||
v-model="selectedParamObj.context"
|
||||
type="radio"
|
||||
value="INOUT"
|
||||
name="context"
|
||||
> <i class="form-icon" /> INOUT
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<div v-if="!parametersProxy.length" class="empty">
|
||||
<div class="empty-icon">
|
||||
@@ -168,6 +199,9 @@ export default {
|
||||
},
|
||||
isChanged () {
|
||||
return JSON.stringify(this.localParameters) !== JSON.stringify(this.parametersProxy);
|
||||
},
|
||||
customizations () {
|
||||
return this.workspace.customizations;
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
|
@@ -26,7 +26,19 @@
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div v-if="customizations.languages" class="form-group">
|
||||
<label class="form-label col-4">
|
||||
{{ $t('word.language') }}
|
||||
</label>
|
||||
<div class="column">
|
||||
<select v-model="optionsProxy.language" class="form-select">
|
||||
<option v-for="language in customizations.languages" :key="language">
|
||||
{{ language }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="customizations.definer" class="form-group">
|
||||
<label class="form-label col-4">
|
||||
{{ $t('word.definer') }}
|
||||
</label>
|
||||
@@ -54,7 +66,7 @@
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div v-if="customizations.comment" class="form-group">
|
||||
<label class="form-label col-4">
|
||||
{{ $t('word.comment') }}
|
||||
</label>
|
||||
@@ -77,7 +89,7 @@
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div v-if="customizations.procedureDataAccess" class="form-group">
|
||||
<label class="form-label col-4">
|
||||
{{ $t('message.dataAccess') }}
|
||||
</label>
|
||||
@@ -90,7 +102,7 @@
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div v-if="customizations.procedureDeterministic" class="form-group">
|
||||
<div class="col-4" />
|
||||
<div class="column">
|
||||
<label class="form-checkbox form-inline">
|
||||
@@ -124,6 +136,9 @@ export default {
|
||||
computed: {
|
||||
isTableNameValid () {
|
||||
return this.optionsProxy.name !== '';
|
||||
},
|
||||
customizations () {
|
||||
return this.workspace.customizations;
|
||||
}
|
||||
},
|
||||
created () {
|
||||
|
@@ -106,7 +106,7 @@
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div v-if="customizations.parametersLength" class="form-group">
|
||||
<label class="form-label col-3">
|
||||
{{ $t('word.length') }}
|
||||
</label>
|
||||
@@ -119,7 +119,7 @@
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div v-if="customizations.procedureContext" class="form-group">
|
||||
<label class="form-label col-3">
|
||||
{{ $t('word.context') }}
|
||||
</label>
|
||||
@@ -199,6 +199,9 @@ export default {
|
||||
},
|
||||
isChanged () {
|
||||
return JSON.stringify(this.localParameters) !== JSON.stringify(this.parametersProxy);
|
||||
},
|
||||
customizations () {
|
||||
return this.workspace.customizations;
|
||||
}
|
||||
},
|
||||
mounted () {
|
||||
@@ -235,10 +238,10 @@ export default {
|
||||
addParameter () {
|
||||
this.parametersProxy = [...this.parametersProxy, {
|
||||
_id: uidGen(),
|
||||
name: `Param${this.i++}`,
|
||||
type: 'INT',
|
||||
name: `param${this.i++}`,
|
||||
type: this.workspace.dataTypes[0].types[0].name,
|
||||
context: 'IN',
|
||||
length: 10
|
||||
length: ''
|
||||
}];
|
||||
|
||||
if (this.parametersProxy.length === 1)
|
||||
|
@@ -7,6 +7,7 @@
|
||||
class="btn btn-primary btn-sm"
|
||||
:disabled="!isChanged"
|
||||
:class="{'loading':isSaving}"
|
||||
title="CTRL+S"
|
||||
@click="saveChanges"
|
||||
>
|
||||
<span>{{ $t('word.save') }}</span>
|
||||
@@ -68,6 +69,7 @@
|
||||
@remove-field="removeField"
|
||||
@add-new-index="addNewIndex"
|
||||
@add-to-index="addToIndex"
|
||||
@rename-field="renameField"
|
||||
/>
|
||||
</div>
|
||||
<WorkspacePropsOptionsModal
|
||||
@@ -149,6 +151,7 @@ export default {
|
||||
computed: {
|
||||
...mapGetters({
|
||||
getWorkspace: 'workspaces/getWorkspace',
|
||||
selectedWorkspace: 'workspaces/getSelected',
|
||||
getDatabaseVariable: 'workspaces/getDatabaseVariable'
|
||||
}),
|
||||
workspace () {
|
||||
@@ -162,7 +165,7 @@ export default {
|
||||
return this.getDatabaseVariable(this.connection.uid, 'default_storage_engine').value || '';
|
||||
},
|
||||
isSelected () {
|
||||
return this.workspace.selected_tab === 'prop';
|
||||
return this.workspace.selected_tab === 'prop' && this.selectedWorkspace === this.workspace.uid && this.table;
|
||||
},
|
||||
schema () {
|
||||
return this.workspace.breadcrumbs.schema;
|
||||
@@ -199,6 +202,12 @@ export default {
|
||||
this.setUnsavedChanges(val);
|
||||
}
|
||||
},
|
||||
created () {
|
||||
window.addEventListener('keydown', this.onKey);
|
||||
},
|
||||
beforeDestroy () {
|
||||
window.removeEventListener('keydown', this.onKey);
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
addNotification: 'notifications/addNotification',
|
||||
@@ -457,6 +466,20 @@ export default {
|
||||
scrollable.scrollTop = scrollable.scrollHeight + 30;
|
||||
}, 20);
|
||||
},
|
||||
renameField (payload) {
|
||||
this.localIndexes = this.localIndexes.map(index => {
|
||||
const fi = index.fields.findIndex(field => field === payload.old);
|
||||
if (fi !== -1)
|
||||
index.fields[fi] = payload.new;
|
||||
return index;
|
||||
});
|
||||
|
||||
this.localKeyUsage = this.localKeyUsage.map(key => {
|
||||
if (key.field === payload.old)
|
||||
key.field = payload.new;
|
||||
return key;
|
||||
});
|
||||
},
|
||||
removeField (uid) {
|
||||
this.localFields = this.localFields.filter(field => field._id !== uid);
|
||||
},
|
||||
@@ -504,6 +527,15 @@ export default {
|
||||
},
|
||||
foreignsUpdate (foreigns) {
|
||||
this.localKeyUsage = foreigns;
|
||||
},
|
||||
onKey (e) {
|
||||
if (this.isSelected) {
|
||||
e.stopPropagation();
|
||||
if (e.ctrlKey && e.keyCode === 83) { // CTRL + S
|
||||
if (this.isChanged)
|
||||
this.saveChanges();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@@ -7,6 +7,7 @@
|
||||
class="btn btn-primary btn-sm"
|
||||
:disabled="!isChanged"
|
||||
:class="{'loading':isSaving}"
|
||||
title="CTRL+S"
|
||||
@click="saveChanges"
|
||||
>
|
||||
<span>{{ $t('word.save') }}</span>
|
||||
@@ -47,7 +48,7 @@
|
||||
<BaseLoader v-if="isLoading" />
|
||||
<label class="form-label ml-2">{{ $t('message.functionBody') }}</label>
|
||||
<QueryEditor
|
||||
v-if="isSelected"
|
||||
v-show="isSelected"
|
||||
ref="queryEditor"
|
||||
:value.sync="localFunction.sql"
|
||||
:workspace="workspace"
|
||||
@@ -73,6 +74,7 @@
|
||||
<ModalAskParameters
|
||||
v-if="isAskingParameters"
|
||||
:local-routine="localFunction"
|
||||
:client="workspace.client"
|
||||
@confirm="runFunction"
|
||||
@close="hideAskParamsModal"
|
||||
/>
|
||||
@@ -119,13 +121,14 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
selectedWorkspace: 'workspaces/getSelected',
|
||||
getWorkspace: 'workspaces/getWorkspace'
|
||||
}),
|
||||
workspace () {
|
||||
return this.getWorkspace(this.connection.uid);
|
||||
},
|
||||
isSelected () {
|
||||
return this.workspace.selected_tab === 'prop';
|
||||
return this.workspace.selected_tab === 'prop' && this.selectedWorkspace === this.workspace.uid && this.function;
|
||||
},
|
||||
schema () {
|
||||
return this.workspace.breadcrumbs.schema;
|
||||
@@ -170,6 +173,12 @@ export default {
|
||||
destroyed () {
|
||||
window.removeEventListener('resize', this.resizeQueryEditor);
|
||||
},
|
||||
created () {
|
||||
window.addEventListener('keydown', this.onKey);
|
||||
},
|
||||
beforeDestroy () {
|
||||
window.removeEventListener('keydown', this.onKey);
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
addNotification: 'notifications/addNotification',
|
||||
@@ -280,9 +289,11 @@ export default {
|
||||
switch (this.connection.client) { // TODO: move in a better place
|
||||
case 'maria':
|
||||
case 'mysql':
|
||||
case 'pg':
|
||||
sql = `SELECT \`${this.originalFunction.name}\` (${params.join(',')})`;
|
||||
break;
|
||||
case 'pg':
|
||||
sql = `SELECT ${this.originalFunction.name}(${params.join(',')})`;
|
||||
break;
|
||||
case 'mssql':
|
||||
sql = `SELECT ${this.originalFunction.name} ${params.join(',')}`;
|
||||
break;
|
||||
@@ -309,6 +320,15 @@ export default {
|
||||
},
|
||||
hideAskParamsModal () {
|
||||
this.isAskingParameters = false;
|
||||
},
|
||||
onKey (e) {
|
||||
if (this.isSelected) {
|
||||
e.stopPropagation();
|
||||
if (e.ctrlKey && e.keyCode === 83) { // CTRL + S
|
||||
if (this.isChanged)
|
||||
this.saveChanges();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@@ -7,6 +7,7 @@
|
||||
class="btn btn-primary btn-sm"
|
||||
:disabled="!isChanged"
|
||||
:class="{'loading':isSaving}"
|
||||
title="CTRL+S"
|
||||
@click="saveChanges"
|
||||
>
|
||||
<span>{{ $t('word.save') }}</span>
|
||||
@@ -74,6 +75,7 @@
|
||||
<ModalAskParameters
|
||||
v-if="isAskingParameters"
|
||||
:local-routine="localRoutine"
|
||||
:client="workspace.client"
|
||||
@confirm="runRoutine"
|
||||
@close="hideAskParamsModal"
|
||||
/>
|
||||
@@ -120,13 +122,14 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
selectedWorkspace: 'workspaces/getSelected',
|
||||
getWorkspace: 'workspaces/getWorkspace'
|
||||
}),
|
||||
workspace () {
|
||||
return this.getWorkspace(this.connection.uid);
|
||||
},
|
||||
isSelected () {
|
||||
return this.workspace.selected_tab === 'prop';
|
||||
return this.workspace.selected_tab === 'prop' && this.selectedWorkspace === this.workspace.uid && this.routine;
|
||||
},
|
||||
schema () {
|
||||
return this.workspace.breadcrumbs.schema;
|
||||
@@ -171,6 +174,12 @@ export default {
|
||||
destroyed () {
|
||||
window.removeEventListener('resize', this.resizeQueryEditor);
|
||||
},
|
||||
created () {
|
||||
window.addEventListener('keydown', this.onKey);
|
||||
},
|
||||
beforeDestroy () {
|
||||
window.removeEventListener('keydown', this.onKey);
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
addNotification: 'notifications/addNotification',
|
||||
@@ -281,13 +290,13 @@ export default {
|
||||
case 'maria':
|
||||
case 'mysql':
|
||||
case 'pg':
|
||||
sql = `CALL \`${this.originalRoutine.name}\` (${params.join(',')})`;
|
||||
sql = `CALL ${this.originalRoutine.name}(${params.join(',')})`;
|
||||
break;
|
||||
case 'mssql':
|
||||
sql = `EXEC ${this.originalRoutine.name} ${params.join(',')}`;
|
||||
break;
|
||||
default:
|
||||
sql = `CALL \`${this.originalRoutine.name}\` (${params.join(',')})`;
|
||||
sql = `CALL \`${this.originalRoutine.name}\`(${params.join(',')})`;
|
||||
}
|
||||
|
||||
this.newTab({ uid: this.connection.uid, content: sql, autorun: true });
|
||||
@@ -309,6 +318,15 @@ export default {
|
||||
},
|
||||
hideAskParamsModal () {
|
||||
this.isAskingParameters = false;
|
||||
},
|
||||
onKey (e) {
|
||||
if (this.isSelected) {
|
||||
e.stopPropagation();
|
||||
if (e.ctrlKey && e.keyCode === 83) { // CTRL + S
|
||||
if (this.isChanged)
|
||||
this.saveChanges();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@@ -7,6 +7,7 @@
|
||||
class="btn btn-primary btn-sm"
|
||||
:disabled="!isChanged"
|
||||
:class="{'loading':isSaving}"
|
||||
title="CTRL+S"
|
||||
@click="saveChanges"
|
||||
>
|
||||
<span>{{ $t('word.save') }}</span>
|
||||
@@ -169,13 +170,14 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
selectedWorkspace: 'workspaces/getSelected',
|
||||
getWorkspace: 'workspaces/getWorkspace'
|
||||
}),
|
||||
workspace () {
|
||||
return this.getWorkspace(this.connection.uid);
|
||||
},
|
||||
isSelected () {
|
||||
return this.workspace.selected_tab === 'prop';
|
||||
return this.workspace.selected_tab === 'prop' && this.selectedWorkspace === this.workspace.uid && this.scheduler;
|
||||
},
|
||||
schema () {
|
||||
return this.workspace.breadcrumbs.schema;
|
||||
@@ -220,6 +222,12 @@ export default {
|
||||
destroyed () {
|
||||
window.removeEventListener('resize', this.resizeQueryEditor);
|
||||
},
|
||||
created () {
|
||||
window.addEventListener('keydown', this.onKey);
|
||||
},
|
||||
beforeDestroy () {
|
||||
window.removeEventListener('keydown', this.onKey);
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
addNotification: 'notifications/addNotification',
|
||||
@@ -310,6 +318,15 @@ export default {
|
||||
},
|
||||
timingUpdate (options) {
|
||||
this.localScheduler = options;
|
||||
},
|
||||
onKey (e) {
|
||||
if (this.isSelected) {
|
||||
e.stopPropagation();
|
||||
if (e.ctrlKey && e.keyCode === 83) { // CTRL + S
|
||||
if (this.isChanged)
|
||||
this.saveChanges();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@@ -7,6 +7,7 @@
|
||||
class="btn btn-primary btn-sm"
|
||||
:disabled="!isChanged"
|
||||
:class="{'loading':isSaving}"
|
||||
title="CTRL+S"
|
||||
@click="saveChanges"
|
||||
>
|
||||
<span>{{ $t('word.save') }}</span>
|
||||
@@ -140,13 +141,14 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
selectedWorkspace: 'workspaces/getSelected',
|
||||
getWorkspace: 'workspaces/getWorkspace'
|
||||
}),
|
||||
workspace () {
|
||||
return this.getWorkspace(this.connection.uid);
|
||||
},
|
||||
isSelected () {
|
||||
return this.workspace.selected_tab === 'prop';
|
||||
return this.workspace.selected_tab === 'prop' && this.selectedWorkspace === this.workspace.uid && this.trigger;
|
||||
},
|
||||
schema () {
|
||||
return this.workspace.breadcrumbs.schema;
|
||||
@@ -191,6 +193,12 @@ export default {
|
||||
destroyed () {
|
||||
window.removeEventListener('resize', this.resizeQueryEditor);
|
||||
},
|
||||
created () {
|
||||
window.addEventListener('keydown', this.onKey);
|
||||
},
|
||||
beforeDestroy () {
|
||||
window.removeEventListener('keydown', this.onKey);
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
addNotification: 'notifications/addNotification',
|
||||
@@ -274,6 +282,15 @@ export default {
|
||||
this.editorHeight = size;
|
||||
this.$refs.queryEditor.editor.resize();
|
||||
}
|
||||
},
|
||||
onKey (e) {
|
||||
if (this.isSelected) {
|
||||
e.stopPropagation();
|
||||
if (e.ctrlKey && e.keyCode === 83) { // CTRL + S
|
||||
if (this.isChanged)
|
||||
this.saveChanges();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@@ -7,6 +7,7 @@
|
||||
class="btn btn-primary btn-sm"
|
||||
:disabled="!isChanged"
|
||||
:class="{'loading':isSaving}"
|
||||
title="CTRL+S"
|
||||
@click="saveChanges"
|
||||
>
|
||||
<span>{{ $t('word.save') }}</span>
|
||||
@@ -201,13 +202,14 @@ export default {
|
||||
},
|
||||
computed: {
|
||||
...mapGetters({
|
||||
selectedWorkspace: 'workspaces/getSelected',
|
||||
getWorkspace: 'workspaces/getWorkspace'
|
||||
}),
|
||||
workspace () {
|
||||
return this.getWorkspace(this.connection.uid);
|
||||
},
|
||||
isSelected () {
|
||||
return this.workspace.selected_tab === 'prop';
|
||||
return this.workspace.selected_tab === 'prop' && this.selectedWorkspace === this.workspace.uid && this.view;
|
||||
},
|
||||
schema () {
|
||||
return this.workspace.breadcrumbs.schema;
|
||||
@@ -245,6 +247,12 @@ export default {
|
||||
destroyed () {
|
||||
window.removeEventListener('resize', this.resizeQueryEditor);
|
||||
},
|
||||
created () {
|
||||
window.addEventListener('keydown', this.onKey);
|
||||
},
|
||||
beforeDestroy () {
|
||||
window.removeEventListener('keydown', this.onKey);
|
||||
},
|
||||
methods: {
|
||||
...mapActions({
|
||||
addNotification: 'notifications/addNotification',
|
||||
@@ -327,6 +335,15 @@ export default {
|
||||
this.editorHeight = size;
|
||||
this.$refs.queryEditor.editor.resize();
|
||||
}
|
||||
},
|
||||
onKey (e) {
|
||||
if (this.isSelected) {
|
||||
e.stopPropagation();
|
||||
if (e.ctrlKey && e.keyCode === 83) { // CTRL + S
|
||||
if (this.isChanged)
|
||||
this.saveChanges();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@@ -42,6 +42,13 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="customizations.tableArray" class="th">
|
||||
<div class="column-resizable">
|
||||
<div class="table-column-title">
|
||||
{{ $t('word.array') }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="th">
|
||||
<div class="column-resizable">
|
||||
<div class="table-column-title">
|
||||
@@ -108,6 +115,7 @@
|
||||
:data-types="dataTypes"
|
||||
:customizations="customizations"
|
||||
@contextmenu="contextMenu"
|
||||
@rename-field="$emit('rename-field', $event)"
|
||||
/>
|
||||
</draggable>
|
||||
</div>
|
||||
|
@@ -60,6 +60,9 @@
|
||||
class="form-select editable-field small-select text-uppercase"
|
||||
@blur="editOFF"
|
||||
>
|
||||
<option v-if="!isInDataTypes">
|
||||
{{ row.type }}
|
||||
</option>
|
||||
<optgroup
|
||||
v-for="group in dataTypes"
|
||||
:key="group.group"
|
||||
@@ -68,7 +71,7 @@
|
||||
<option
|
||||
v-for="type in group.types"
|
||||
:key="type.name"
|
||||
:selected="localRow.type.toUpperCase() === type.name"
|
||||
:selected="localRow.type === type.name"
|
||||
:value="type.name"
|
||||
>
|
||||
{{ type.name }}
|
||||
@@ -76,6 +79,16 @@
|
||||
</optgroup>
|
||||
</select>
|
||||
</div>
|
||||
<div
|
||||
v-if="customizations.tableArray"
|
||||
class="td"
|
||||
tabindex="0"
|
||||
>
|
||||
<label class="form-checkbox">
|
||||
<input v-model="localRow.isArray" type="checkbox">
|
||||
<i class="form-icon" />
|
||||
</label>
|
||||
</div>
|
||||
<div class="td type-int" tabindex="0">
|
||||
<template v-if="fieldType.length">
|
||||
<span
|
||||
@@ -364,6 +377,16 @@ export default {
|
||||
},
|
||||
isNullable () {
|
||||
return !this.indexes.some(index => ['PRIMARY'].includes(index.type));
|
||||
},
|
||||
isInDataTypes () {
|
||||
let typeNames = [];
|
||||
for (const group of this.dataTypes) {
|
||||
typeNames = group.types.reduce((acc, curr) => {
|
||||
acc.push(curr.name);
|
||||
return acc;
|
||||
}, []);
|
||||
}
|
||||
return typeNames.includes(this.row.type);
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@@ -430,9 +453,6 @@ export default {
|
||||
this.defaultValue.expression = this.localRow.default;
|
||||
}
|
||||
},
|
||||
updateRow () {
|
||||
this.$emit('input', this.localRow);
|
||||
},
|
||||
editON (event, content, field) {
|
||||
if (field === 'length') {
|
||||
if (['integer', 'float', 'binary', 'spatial'].includes(this.fieldType.group)) this.editingField = 'numLength';
|
||||
@@ -459,6 +479,9 @@ export default {
|
||||
}
|
||||
},
|
||||
editOFF () {
|
||||
if (this.editingField === 'name')
|
||||
this.$emit('rename-field', { old: this.localRow[this.editingField], new: this.editingContent });
|
||||
|
||||
this.localRow[this.editingField] = this.editingContent;
|
||||
|
||||
if (this.editingField === 'type' && this.editingContent !== this.originalContent) {
|
||||
@@ -481,8 +504,7 @@ export default {
|
||||
if (!this.fieldType.zerofill)
|
||||
this.localRow.zerofill = false;
|
||||
}
|
||||
|
||||
if (this.editingField === 'default') {
|
||||
else if (this.editingField === 'default') {
|
||||
switch (this.defaultValue.type) {
|
||||
case 'autoincrement':
|
||||
this.localRow.autoIncrement = true;
|
||||
|
@@ -1,5 +1,12 @@
|
||||
<template>
|
||||
<div v-show="isSelected" class="workspace-query-tab column col-12 columns col-gapless">
|
||||
<div
|
||||
v-show="isSelected"
|
||||
class="workspace-query-tab column col-12 columns col-gapless no-outline"
|
||||
tabindex="0"
|
||||
@keydown.116="runQuery(query)"
|
||||
@keydown.ctrl.87="clear"
|
||||
@keydown.ctrl.119="beautify"
|
||||
>
|
||||
<div class="workspace-query-runner column col-12">
|
||||
<QueryEditor
|
||||
v-show="isSelected"
|
||||
@@ -24,6 +31,43 @@
|
||||
<span>{{ $t('word.run') }}</span>
|
||||
<i class="mdi mdi-24px mdi-play" />
|
||||
</button>
|
||||
<div class="dropdown export-dropdown pr-2">
|
||||
<button
|
||||
:disabled="!results.length || isQuering"
|
||||
class="btn btn-dark btn-sm dropdown-toggle mr-0 pr-0"
|
||||
tabindex="0"
|
||||
>
|
||||
<span>{{ $t('word.export') }}</span>
|
||||
<i class="mdi mdi-24px mdi-file-export ml-1" />
|
||||
<i class="mdi mdi-24px mdi-menu-down" />
|
||||
</button>
|
||||
<ul class="menu text-left">
|
||||
<li class="menu-item">
|
||||
<a class="c-hand" @click="downloadTable('json')">JSON</a>
|
||||
</li>
|
||||
<li class="menu-item">
|
||||
<a class="c-hand" @click="downloadTable('csv')">CSV</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
<button
|
||||
class="btn btn-dark btn-sm"
|
||||
:disabled="!query || isQuering"
|
||||
title="CTRL+F8"
|
||||
@click="beautify()"
|
||||
>
|
||||
<span>{{ $t('word.format') }}</span>
|
||||
<i class="mdi mdi-24px mdi-brush pl-1" />
|
||||
</button>
|
||||
<button
|
||||
class="btn btn-link btn-sm"
|
||||
:disabled="!query || isQuering"
|
||||
title="CTRL+W"
|
||||
@click="clear()"
|
||||
>
|
||||
<span>{{ $t('word.clear') }}</span>
|
||||
<i class="mdi mdi-24px mdi-delete-sweep pl-1" />
|
||||
</button>
|
||||
</div>
|
||||
<div class="workspace-query-info">
|
||||
<div
|
||||
@@ -68,6 +112,7 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { format } from 'sql-formatter';
|
||||
import Schema from '@/ipc-api/Schema';
|
||||
import QueryEditor from '@/components/QueryEditor';
|
||||
import BaseLoader from '@/components/BaseLoader';
|
||||
@@ -192,12 +237,36 @@ export default {
|
||||
if (this.$refs.queryEditor)
|
||||
this.$refs.queryEditor.editor.resize();
|
||||
},
|
||||
onKey (e) {
|
||||
if (this.isSelected && this.isWorkspaceSelected) {
|
||||
e.stopPropagation();
|
||||
if (e.key === 'F5')
|
||||
this.runQuery(this.query);
|
||||
beautify () {
|
||||
if (this.$refs.queryEditor) {
|
||||
let language = 'sql';
|
||||
|
||||
switch (this.workspace.client) {
|
||||
case 'mysql':
|
||||
language = 'mysql';
|
||||
break;
|
||||
case 'maria':
|
||||
language = 'mariadb';
|
||||
break;
|
||||
case 'pg':
|
||||
language = 'postgresql';
|
||||
break;
|
||||
}
|
||||
|
||||
const formattedQuery = format(this.query, {
|
||||
language,
|
||||
uppercase: true
|
||||
});
|
||||
this.$refs.queryEditor.editor.session.setValue(formattedQuery);
|
||||
}
|
||||
},
|
||||
clear () {
|
||||
if (this.$refs.queryEditor)
|
||||
this.$refs.queryEditor.editor.session.setValue('');
|
||||
this.clearTabData();
|
||||
},
|
||||
downloadTable (format) {
|
||||
this.$refs.queryTable.downloadTable(format, `${this.tab.type}-${this.tab.index}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -251,4 +320,9 @@ export default {
|
||||
}
|
||||
}
|
||||
|
||||
.export-dropdown {
|
||||
.menu {
|
||||
min-width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@@ -1,16 +1,18 @@
|
||||
<template>
|
||||
<div
|
||||
ref="tableWrapper"
|
||||
class="vscroll"
|
||||
class="vscroll no-outline"
|
||||
tabindex="0"
|
||||
:style="{'height': resultsSize+'px'}"
|
||||
@keyup.46="showDeleteConfirmModal"
|
||||
>
|
||||
<TableContext
|
||||
v-if="isContext"
|
||||
:context-event="contextEvent"
|
||||
:selected-rows="selectedRows"
|
||||
@delete-selected="deleteSelected"
|
||||
@show-delete-modal="showDeleteConfirmModal"
|
||||
@set-null="setNull"
|
||||
@close-context="isContext = false"
|
||||
@close-context="closeContext"
|
||||
/>
|
||||
<ul v-if="resultsWithRows.length > 1" class="tab tab-block result-tabs">
|
||||
<li
|
||||
@@ -75,6 +77,23 @@
|
||||
</template>
|
||||
</BaseVirtualScroll>
|
||||
</div>
|
||||
|
||||
<ConfirmModal
|
||||
v-if="isDeleteConfirmModal"
|
||||
@confirm="deleteSelected"
|
||||
@hide="hideDeleteConfirmModal"
|
||||
>
|
||||
<template :slot="'header'">
|
||||
<div class="d-flex">
|
||||
<i class="mdi mdi-24px mdi-delete mr-1" /> {{ $tc('message.deleteRows', selectedRows.length) }}
|
||||
</div>
|
||||
</template>
|
||||
<div :slot="'body'">
|
||||
<div class="mb-2">
|
||||
{{ $tc('message.confirmToDeleteRows', selectedRows.length) }}
|
||||
</div>
|
||||
</div>
|
||||
</ConfirmModal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -85,14 +104,17 @@ import { TEXT, LONG_TEXT, BLOB } from 'common/fieldTypes';
|
||||
import BaseVirtualScroll from '@/components/BaseVirtualScroll';
|
||||
import WorkspaceQueryTableRow from '@/components/WorkspaceQueryTableRow';
|
||||
import TableContext from '@/components/WorkspaceQueryTableContext';
|
||||
import ConfirmModal from '@/components/BaseConfirmModal';
|
||||
import { mapActions, mapGetters } from 'vuex';
|
||||
import moment from 'moment';
|
||||
|
||||
export default {
|
||||
name: 'WorkspaceQueryTable',
|
||||
components: {
|
||||
BaseVirtualScroll,
|
||||
WorkspaceQueryTableRow,
|
||||
TableContext
|
||||
TableContext,
|
||||
ConfirmModal
|
||||
},
|
||||
props: {
|
||||
results: Array,
|
||||
@@ -105,6 +127,7 @@ export default {
|
||||
resultsSize: 1000,
|
||||
localResults: [],
|
||||
isContext: false,
|
||||
isDeleteConfirmModal: false,
|
||||
contextEvent: null,
|
||||
selectedCell: null,
|
||||
selectedRows: [],
|
||||
@@ -122,12 +145,13 @@ export default {
|
||||
return this.getWorkspace(this.connUid).breadcrumbs.schema;
|
||||
},
|
||||
primaryField () {
|
||||
const primaryFields = this.fields.filter(field => ['pri', 'uni'].includes(field.key));
|
||||
const primaryFields = this.fields.filter(field => field.key === 'pri');
|
||||
const uniqueFields = this.fields.filter(field => field.key === 'uni');
|
||||
|
||||
if (primaryFields.length > 1 || !primaryFields.length)
|
||||
if ((primaryFields.length > 1 || !primaryFields.length) && (uniqueFields.length > 1 || !uniqueFields.length))
|
||||
return false;
|
||||
|
||||
return primaryFields[0];
|
||||
return primaryFields[0] || uniqueFields[0];
|
||||
},
|
||||
isSortable () {
|
||||
return this.fields.every(field => field.name);
|
||||
@@ -298,6 +322,17 @@ export default {
|
||||
delete row._id;
|
||||
delete orgRow._id;
|
||||
|
||||
Object.keys(orgRow).forEach(key => { // remap the row
|
||||
if (orgRow[key] instanceof Date && moment(orgRow[key]).isValid()) { // if datetime
|
||||
let datePrecision = '';
|
||||
const precision = this.fields.find(field => field.name === key).datePrecision;
|
||||
for (let i = 0; i < precision; i++)
|
||||
datePrecision += i === 0 ? '.S' : 'S';
|
||||
|
||||
orgRow[key] = moment(orgRow[key]).format(`YYYY-MM-DD HH:mm:ss${datePrecision}`);
|
||||
}
|
||||
});
|
||||
|
||||
const params = {
|
||||
primary: this.primaryField.name,
|
||||
schema: this.getSchema(this.resultsetIndex),
|
||||
@@ -309,7 +344,17 @@ export default {
|
||||
};
|
||||
this.$emit('update-field', params);
|
||||
},
|
||||
closeContext () {
|
||||
this.isContext = false;
|
||||
},
|
||||
showDeleteConfirmModal () {
|
||||
this.isDeleteConfirmModal = true;
|
||||
},
|
||||
hideDeleteConfirmModal () {
|
||||
this.isDeleteConfirmModal = false;
|
||||
},
|
||||
deleteSelected () {
|
||||
this.closeContext();
|
||||
const rows = this.localResults.filter(row => this.selectedRows.includes(row._id)).map(row => {
|
||||
delete row._id;
|
||||
return row;
|
||||
|
@@ -17,61 +17,30 @@
|
||||
<i class="mdi mdi-18px mdi-delete text-light pr-1" /> {{ $tc('message.deleteRows', selectedRows.length) }}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<ConfirmModal
|
||||
v-if="isConfirmModal"
|
||||
@confirm="deleteRows"
|
||||
@hide="hideConfirmModal"
|
||||
>
|
||||
<template :slot="'header'">
|
||||
<div class="d-flex">
|
||||
<i class="mdi mdi-24px mdi-delete mr-1" /> {{ $tc('message.deleteRows', selectedRows.length) }}
|
||||
</div>
|
||||
</template>
|
||||
<div :slot="'body'">
|
||||
<div class="mb-2">
|
||||
{{ $tc('message.confirmToDeleteRows', selectedRows.length) }}
|
||||
</div>
|
||||
</div>
|
||||
</ConfirmModal>
|
||||
</BaseContextMenu>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BaseContextMenu from '@/components/BaseContextMenu';
|
||||
import ConfirmModal from '@/components/BaseConfirmModal';
|
||||
|
||||
export default {
|
||||
name: 'WorkspaceQueryTableContext',
|
||||
components: {
|
||||
BaseContextMenu,
|
||||
ConfirmModal
|
||||
BaseContextMenu
|
||||
},
|
||||
props: {
|
||||
contextEvent: MouseEvent,
|
||||
selectedRows: Array
|
||||
},
|
||||
data () {
|
||||
return {
|
||||
isConfirmModal: false
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
},
|
||||
methods: {
|
||||
showConfirmModal () {
|
||||
this.isConfirmModal = true;
|
||||
},
|
||||
hideConfirmModal () {
|
||||
this.isConfirmModal = false;
|
||||
this.$emit('show-delete-modal');
|
||||
},
|
||||
closeContext () {
|
||||
this.$emit('close-context');
|
||||
},
|
||||
deleteRows () {
|
||||
this.$emit('delete-selected');
|
||||
this.closeContext();
|
||||
},
|
||||
setNull () {
|
||||
this.$emit('set-null');
|
||||
this.closeContext();
|
||||
|
@@ -182,7 +182,7 @@ import { mimeFromHex } from 'common/libs/mimeFromHex';
|
||||
import { formatBytes } from 'common/libs/formatBytes';
|
||||
import { bufferToBase64 } from 'common/libs/bufferToBase64';
|
||||
import hexToBinary from 'common/libs/hexToBinary';
|
||||
import { TEXT, LONG_TEXT, ARRAY, TEXT_SEARCH, NUMBER, FLOAT, BOOLEAN, DATE, TIME, DATETIME, BLOB, BIT } from 'common/fieldTypes';
|
||||
import { TEXT, LONG_TEXT, ARRAY, TEXT_SEARCH, NUMBER, FLOAT, BOOLEAN, DATE, TIME, DATETIME, BLOB, BIT, HAS_TIMEZONE } from 'common/fieldTypes';
|
||||
import { VueMaskDirective } from 'v-mask';
|
||||
import ConfirmModal from '@/components/BaseConfirmModal';
|
||||
import TextEditor from '@/components/BaseTextEditor';
|
||||
@@ -286,6 +286,9 @@ export default {
|
||||
for (let i = 0; i < precision; i++)
|
||||
timeMask += i === 0 ? '.#' : '#';
|
||||
|
||||
if (HAS_TIMEZONE.includes(this.editingType))
|
||||
timeMask += 'X##';
|
||||
|
||||
return { type: 'text', mask: timeMask };
|
||||
}
|
||||
|
||||
@@ -299,6 +302,9 @@ export default {
|
||||
for (let i = 0; i < precision; i++)
|
||||
datetimeMask += i === 0 ? '.#' : '#';
|
||||
|
||||
if (HAS_TIMEZONE.includes(this.editingType))
|
||||
datetimeMask += 'X##';
|
||||
|
||||
return { type: 'text', mask: datetimeMask };
|
||||
}
|
||||
|
||||
|
@@ -74,7 +74,7 @@
|
||||
<div v-if="results.length && results[0].rows">
|
||||
{{ $t('word.results') }}: <b>{{ results[0].rows.length.toLocaleString() }}</b>
|
||||
</div>
|
||||
<div v-if="results.length && results[0].rows && tableInfo && results[0].rows.length < tableInfo.rows">
|
||||
<div v-if="hasApproximately">
|
||||
{{ $t('word.total') }}: <b>{{ tableInfo.rows.toLocaleString() }}</b> <small>({{ $t('word.approximately') }})</small>
|
||||
</div>
|
||||
<div v-if="workspace.breadcrumbs.database">
|
||||
@@ -179,6 +179,13 @@ export default {
|
||||
catch (err) {
|
||||
return { rows: 0 };
|
||||
}
|
||||
},
|
||||
hasApproximately () {
|
||||
return this.results.length &&
|
||||
this.results[0].rows &&
|
||||
this.tableInfo &&
|
||||
this.results[0].rows.length === 1000 &&
|
||||
this.results[0].rows.length < this.tableInfo.rows;
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
|
@@ -102,7 +102,10 @@ module.exports = {
|
||||
variables: 'Variables',
|
||||
processes: 'Processes',
|
||||
database: 'Database',
|
||||
scratchpad: 'Scratchpad'
|
||||
scratchpad: 'Scratchpad',
|
||||
array: 'Array',
|
||||
changelog: 'Changelog',
|
||||
format: 'Format'
|
||||
},
|
||||
message: {
|
||||
appWelcome: 'Welcome to Antares SQL Client!',
|
||||
@@ -206,7 +209,8 @@ module.exports = {
|
||||
schemaName: 'Schema name',
|
||||
editSchema: 'Edit schema',
|
||||
deleteSchema: 'Delete schema',
|
||||
markdownSupported: 'Markdown supported'
|
||||
markdownSupported: 'Markdown supported',
|
||||
plantATree: 'Plant a Tree'
|
||||
},
|
||||
faker: {
|
||||
address: 'Address',
|
||||
|
@@ -42,6 +42,7 @@ export default {
|
||||
|
||||
try {
|
||||
const { status, response } = await Tables.deleteTableRows(params);
|
||||
this.isQuering = false;
|
||||
|
||||
if (status === 'success')
|
||||
this.reloadTable();
|
||||
@@ -50,9 +51,8 @@ export default {
|
||||
}
|
||||
catch (err) {
|
||||
this.addNotification({ status: 'error', message: err.stack });
|
||||
}
|
||||
|
||||
this.isQuering = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@@ -30,6 +30,7 @@
|
||||
"macaddr": $string-color,
|
||||
"macaddr8": $string-color,
|
||||
"uuid": $string-color,
|
||||
"regproc": $string-color,
|
||||
"int": $number-color,
|
||||
"tinyint": $number-color,
|
||||
"smallint": $number-color,
|
||||
@@ -76,6 +77,7 @@
|
||||
"tsvector": $array-color,
|
||||
"tsquery": $array-color,
|
||||
"pg_node_tree": $array-color,
|
||||
"aclitem": $array-color,
|
||||
"unknown": $unknown-color,
|
||||
)
|
||||
);
|
||||
|
@@ -30,6 +30,12 @@
|
||||
animation: jump-down-in 0.2s reverse;
|
||||
}
|
||||
|
||||
.pulse {
|
||||
animation-name: pulse;
|
||||
animation-duration: 2s;
|
||||
animation-iteration-count: infinite;
|
||||
}
|
||||
|
||||
@keyframes jump-down-in {
|
||||
0% {
|
||||
transform: scale(0);
|
||||
@@ -39,3 +45,17 @@
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes pulse {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
50% {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
100% {
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
@@ -30,6 +30,10 @@ body {
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
.no-outline {
|
||||
outline: none !important;
|
||||
}
|
||||
|
||||
.no-border {
|
||||
outline: none !important;
|
||||
border: none !important;
|
||||
@@ -99,6 +103,7 @@ body {
|
||||
.modal-container,
|
||||
.modal-sm .modal-container {
|
||||
padding: 0;
|
||||
border-radius: 3px;
|
||||
|
||||
.modal-header {
|
||||
padding: 0.4rem 0.8rem;
|
||||
@@ -106,6 +111,7 @@ body {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
border-radius: 3px 3px 0 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -130,6 +136,21 @@ body {
|
||||
box-shadow: none;
|
||||
}
|
||||
}
|
||||
|
||||
&.badge-connected::after {
|
||||
background: $success-color;
|
||||
}
|
||||
|
||||
&.badge-connecting::after {
|
||||
background: $warning-color;
|
||||
animation-name: pulse;
|
||||
animation-duration: 2s;
|
||||
animation-iteration-count: infinite;
|
||||
}
|
||||
|
||||
&.badge-failed::after {
|
||||
background: $error-color;
|
||||
}
|
||||
}
|
||||
|
||||
.form-select {
|
||||
|
@@ -106,6 +106,11 @@
|
||||
border-color: $primary-color;
|
||||
}
|
||||
|
||||
.form-input[readonly] {
|
||||
background-color: $bg-color-dark;
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
.input-group .input-group-addon {
|
||||
border-color: #3f3f3f;
|
||||
background: $bg-color-dark;
|
||||
@@ -381,7 +386,6 @@
|
||||
bottom: -10px;
|
||||
right: 0;
|
||||
position: absolute;
|
||||
background: $success-color;
|
||||
}
|
||||
|
||||
&.badge-update::after {
|
||||
|
@@ -168,7 +168,6 @@
|
||||
bottom: -10px;
|
||||
right: 0;
|
||||
position: absolute;
|
||||
background: $success-color;
|
||||
}
|
||||
|
||||
&.badge-update::after {
|
||||
|
@@ -1,10 +1,14 @@
|
||||
'use strict';
|
||||
import Store from 'electron-store';
|
||||
const persistentStore = new Store({ name: 'settings' });
|
||||
|
||||
export default {
|
||||
namespaced: true,
|
||||
strict: true,
|
||||
state: {
|
||||
app_name: 'Antares - SQL Client',
|
||||
app_version: process.env.PACKAGE_VERSION || 0,
|
||||
cached_version: persistentStore.get('cached_version', 0),
|
||||
is_loading: false,
|
||||
is_new_modal: false,
|
||||
is_setting_modal: false,
|
||||
@@ -19,6 +23,7 @@ export default {
|
||||
isLoading: state => state.is_loading,
|
||||
appName: state => state.app_name,
|
||||
appVersion: state => state.app_version,
|
||||
cachedVersion: state => state.cached_version,
|
||||
getBaseCompleter: state => state.base_completer,
|
||||
getSelectedConnection: state => state.selected_conection,
|
||||
isNewModal: state => state.is_new_modal,
|
||||
@@ -54,6 +59,10 @@ export default {
|
||||
HIDE_SCRATCHPAD (state) {
|
||||
state.is_scratchpad = false;
|
||||
},
|
||||
CHANGE_CACHED_VERSION (state) {
|
||||
state.cached_version = state.app_version;
|
||||
persistentStore.set('cached_version', state.cached_version);
|
||||
},
|
||||
CHANGE_UPDATE_STATUS (state, status) {
|
||||
state.update_status = status;
|
||||
},
|
||||
@@ -62,6 +71,12 @@ export default {
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
checkVersionUpdate ({ getters, commit, dispatch }) {
|
||||
if (getters.appVersion !== getters.cachedVersion) {
|
||||
dispatch('showSettingModal', 'changelog');
|
||||
commit('CHANGE_CACHED_VERSION');
|
||||
}
|
||||
},
|
||||
setLoadingStatus ({ commit }, payload) {
|
||||
commit('SET_LOADING_STATUS', payload);
|
||||
},
|
||||
|
@@ -1,8 +1,7 @@
|
||||
'use strict';
|
||||
import Store from 'electron-store';
|
||||
import crypto from 'crypto';
|
||||
import Application from '../../ipc-api/Application';
|
||||
const key = Application.getKey() || localStorage.getItem('key');
|
||||
const key = localStorage.getItem('key');
|
||||
|
||||
if (!key)
|
||||
localStorage.setItem('key', crypto.randomBytes(16).toString('hex'));
|
||||
|
@@ -37,7 +37,7 @@ export default {
|
||||
},
|
||||
getConnected: state => {
|
||||
return state.workspaces
|
||||
.filter(workspace => workspace.connected)
|
||||
.filter(workspace => workspace.connection_status === 'connected')
|
||||
.map(workspace => workspace.uid);
|
||||
},
|
||||
getLoadedSchemas: state => uid => {
|
||||
@@ -54,7 +54,7 @@ export default {
|
||||
SELECT_WORKSPACE (state, uid) {
|
||||
state.selected_workspace = uid;
|
||||
},
|
||||
ADD_CONNECTED (state, payload) {
|
||||
SET_CONNECTED (state, payload) {
|
||||
const { uid, client, dataTypes, indexTypes, customizations, structure, version } = payload;
|
||||
|
||||
state.workspaces = state.workspaces.map(workspace => workspace.uid === uid
|
||||
@@ -65,19 +65,41 @@ export default {
|
||||
indexTypes,
|
||||
customizations,
|
||||
structure,
|
||||
connected: true,
|
||||
connection_status: 'connected',
|
||||
version
|
||||
}
|
||||
: workspace);
|
||||
},
|
||||
REMOVE_CONNECTED (state, uid) {
|
||||
SET_CONNECTING (state, uid) {
|
||||
state.workspaces = state.workspaces.map(workspace => workspace.uid === uid
|
||||
? {
|
||||
...workspace,
|
||||
structure: {},
|
||||
breadcrumbs: {},
|
||||
loaded_schemas: new Set(),
|
||||
connected: false
|
||||
connection_status: 'connecting'
|
||||
}
|
||||
: workspace);
|
||||
},
|
||||
SET_FAILED (state, uid) {
|
||||
state.workspaces = state.workspaces.map(workspace => workspace.uid === uid
|
||||
? {
|
||||
...workspace,
|
||||
structure: {},
|
||||
breadcrumbs: {},
|
||||
loaded_schemas: new Set(),
|
||||
connection_status: 'failed'
|
||||
}
|
||||
: workspace);
|
||||
},
|
||||
SET_DISCONNECTED (state, uid) {
|
||||
state.workspaces = state.workspaces.map(workspace => workspace.uid === uid
|
||||
? {
|
||||
...workspace,
|
||||
structure: {},
|
||||
breadcrumbs: {},
|
||||
loaded_schemas: new Set(),
|
||||
connection_status: 'disconnected'
|
||||
}
|
||||
: workspace);
|
||||
},
|
||||
@@ -247,10 +269,14 @@ export default {
|
||||
commit('SELECT_WORKSPACE', uid);
|
||||
},
|
||||
async connectWorkspace ({ dispatch, commit }, connection) {
|
||||
commit('SET_CONNECTING', connection.uid);
|
||||
|
||||
try {
|
||||
const { status, response } = await Connection.connect(connection);
|
||||
if (status === 'error')
|
||||
if (status === 'error') {
|
||||
dispatch('notifications/addNotification', { status, message: response }, { root: true });
|
||||
commit('SET_FAILED', connection.uid);
|
||||
}
|
||||
else {
|
||||
let dataTypes = [];
|
||||
let indexTypes = [];
|
||||
@@ -288,7 +314,7 @@ export default {
|
||||
dispatch('connections/editConnection', connProxy, { root: true });
|
||||
}
|
||||
|
||||
commit('ADD_CONNECTED', {
|
||||
commit('SET_CONNECTED', {
|
||||
uid: connection.uid,
|
||||
client: connection.client,
|
||||
dataTypes,
|
||||
@@ -382,13 +408,13 @@ export default {
|
||||
},
|
||||
removeConnected ({ commit }, uid) {
|
||||
Connection.disconnect(uid);
|
||||
commit('REMOVE_CONNECTED', uid);
|
||||
commit('SET_DISCONNECTED', uid);
|
||||
commit('SELECT_TAB', { uid, tab: 0 });
|
||||
},
|
||||
addWorkspace ({ commit, dispatch, getters }, uid) {
|
||||
const workspace = {
|
||||
uid,
|
||||
connected: false,
|
||||
connection_status: 'disconnected',
|
||||
selected_tab: 0,
|
||||
search_term: '',
|
||||
tabs: [],
|
||||
|
Reference in New Issue
Block a user