From 0b861d962d68e0f1b805e67b9d9072935b483bda Mon Sep 17 00:00:00 2001 From: Fabio Di Stasio Date: Sun, 10 Apr 2022 10:55:23 +0200 Subject: [PATCH 01/14] chore(release): 0.5.2 --- CHANGELOG.md | 26 ++++++++++++++++++++++++++ package.json | 2 +- 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a7165fd..cfa0b9c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,32 @@ 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.5.2](https://github.com/antares-sql/antares/compare/v0.5.1...v0.5.2) (2022-04-10) + + +### Features + +* **core:** option to allow untrusted SSL connections ([6cc098c](https://github.com/antares-sql/antares/commit/6cc098c6f02fb52cc71c0141431ab75f12744a1c)) +* french translation updated, closes [#222](https://github.com/antares-sql/antares/issues/222) ([796f61b](https://github.com/antares-sql/antares/commit/796f61bf2feab0da515901e2137dc7bf04371d7d)) +* **PostgreSQL:** export functions and procedures ([a8ca8f2](https://github.com/antares-sql/antares/commit/a8ca8f2f76ab36c4afe84d602709386315f4b7d1)) +* **PostgreSQL:** export tables ([a67071e](https://github.com/antares-sql/antares/commit/a67071e28470bcbd0ec26780bb86f3c65750ded8)) +* **PostgreSQL:** export triggers ([42376b4](https://github.com/antares-sql/antares/commit/42376b4bc6dd8b630402d09b026d9fbc0b8646bb)) +* **PostgreSQL:** export user-defined types before tables ([bb02479](https://github.com/antares-sql/antares/commit/bb02479b71bf75a6e69e28af57c5fe213d3f30bc)) +* **PostgreSQL:** export views ([86f011f](https://github.com/antares-sql/antares/commit/86f011f34fec9d6829bce324493fea888a863ffc)) +* **PostgreSQL:** sql dump importer ([6086ca4](https://github.com/antares-sql/antares/commit/6086ca4a80b9ad6a07086446253d781f052d3abc)) + + +### Bug Fixes + +* **PostgreSQL:** wrong values exporting table content ([0f9c991](https://github.com/antares-sql/antares/commit/0f9c991f539560913fa0e9361a16e6448a066a27)) +* ssh tunnel not properly working, closes [#220](https://github.com/antares-sql/antares/issues/220) ([026d74c](https://github.com/antares-sql/antares/commit/026d74c8c88c605a3c8c963c211078f5b3dcfda1)) + + +### Improvements + +* **PostgreSQL:** improved dump file ([408dded](https://github.com/antares-sql/antares/commit/408ddeda5634ab6bf41eff760271669170b60eb6)) +* **PostgreSQL:** improved views exportation ([638a88a](https://github.com/antares-sql/antares/commit/638a88a1fb35c048ff4c6d120aaaef831c846f58)) + ### [0.5.1](https://github.com/Fabio286/antares/compare/v0.5.0...v0.5.1) (2022-03-25) diff --git a/package.json b/package.json index 68bbe711..705b9a88 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "antares", "productName": "Antares", - "version": "0.5.1", + "version": "0.5.2", "description": "A modern, fast and productivity driven SQL client with a focus in UX.", "license": "MIT", "repository": "https://github.com/antares-sql/antares.git", From 744c62391425865fb5b49d7c80e8945b058e10d9 Mon Sep 17 00:00:00 2001 From: Cleverson Date: Mon, 11 Apr 2022 09:22:24 -0300 Subject: [PATCH 02/14] Update pt-BR.js --- src/renderer/i18n/pt-BR.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/renderer/i18n/pt-BR.js b/src/renderer/i18n/pt-BR.js index 257b1b43..8f94712a 100644 --- a/src/renderer/i18n/pt-BR.js +++ b/src/renderer/i18n/pt-BR.js @@ -28,7 +28,7 @@ module.exports = { version: 'Versão', donate: 'Doação', run: 'Executar', - schema: 'Schema', + schema: 'Esquema', results: 'Resultados', size: 'Tamanho', seconds: 'Segundos', @@ -41,7 +41,7 @@ module.exports = { insert: 'Inserção', connecting: 'Connectando', name: 'Nome', - collation: 'Collation', + collation: 'Agrupamento', clear: 'Limpar', options: 'Opções', autoRefresh: 'Atualização Automática', @@ -55,13 +55,13 @@ module.exports = { order: 'Ordem', expression: 'Expressão', autoIncrement: 'Auto Incremental', - engine: 'Engine', + engine: 'Motor', field: 'Campo | Campos', approximately: 'Aproximadamente', total: 'Total', table: 'Tabela', discard: 'Descartar', - stay: 'Stay', + stay: 'Fica', author: 'Autor', light: 'Claro', dark: 'Escuro', @@ -71,10 +71,10 @@ module.exports = { view: 'Visão', definer: 'Definidor', algorithm: 'Algoritmo', - trigger: 'Trigger | Triggers', - storedRoutine: 'Stored routine | Stored routines', - scheduler: 'Scheduler | Schedulers', - event: 'Event', + trigger: 'Gatilho | Gatilhos', + storedRoutine: 'Procedimento Armazenado | Procedimentos Armazenados', + scheduler: 'Agendador | Agendadores', + event: 'Evento', parameters: 'Parametros', function: 'Função | Funções', deterministic: 'Deterministico', @@ -204,8 +204,8 @@ module.exports = { applicationTheme: 'Tema da aplicação', editorTheme: 'Editor de tema', wrapLongLines: 'Quebrar linhas longas', - selectStatement: 'Select statement', - triggerStatement: 'Trigger statement', + selectStatement: 'Declaração de Select', + triggerStatement: 'Declaração de Gatilho', sqlSecurity: 'Segurança SQL', updateOption: 'Opção de atualização', deleteView: 'Apagar view', From 3975359292d5d290c556b37fd7c82fb0c0acccd0 Mon Sep 17 00:00:00 2001 From: Fabio Di Stasio Date: Tue, 12 Apr 2022 17:04:11 +0200 Subject: [PATCH 03/14] build: typescript config --- .eslintrc | 25 +++++++++++++++++++++++-- package.json | 9 +++++++++ tsconfig.json | 19 +++++++++++++++++++ webpack.main.config.js | 9 +++++++-- 4 files changed, 58 insertions(+), 4 deletions(-) create mode 100644 tsconfig.json diff --git a/.eslintrc b/.eslintrc index 54031e45..5fb449d3 100644 --- a/.eslintrc +++ b/.eslintrc @@ -6,15 +6,22 @@ }, "extends": [ "standard", + "plugin:@typescript-eslint/recommended", "plugin:vue/recommended" ], + "parser": "vue-eslint-parser", "parserOptions": { - "parser": "@babel/eslint-parser", + "parser": "@typescript-eslint/parser", "ecmaVersion": 9, "sourceType": "module", "requireConfigFile": false }, + "plugins": [ + "@typescript-eslint" + ], "rules": { + "space-infix-ops": "off", + "object-curly-newline": "off", "indent": [ "error", 3, @@ -69,6 +76,20 @@ "max": 1 } } - ] + ], + "@typescript-eslint/member-delimiter-style": [ + "warn", + { + "multiline": { + "delimiter": "semi", + "requireLast": true + }, + "singleline": { + "delimiter": "semi", + "requireLast": false + } + } + ], + "@typescript-eslint/no-var-requires": "off" } } \ No newline at end of file diff --git a/package.json b/package.json index 705b9a88..78af2abe 100644 --- a/package.json +++ b/package.json @@ -134,6 +134,12 @@ "devDependencies": { "@babel/eslint-parser": "^7.15.7", "@babel/preset-env": "^7.15.8", + "@babel/preset-typescript": "^7.16.7", + "@types/better-sqlite3": "^7.5.0", + "@types/node": "^17.0.23", + "@types/pg": "^8.6.5", + "@typescript-eslint/eslint-plugin": "^5.18.0", + "@typescript-eslint/parser": "^5.18.0", "all-contributors-cli": "^6.20.0", "babel-loader": "^8.2.3", "chalk": "^4.1.2", @@ -163,7 +169,10 @@ "stylelint-config-standard": "^22.0.0", "stylelint-scss": "^3.21.0", "tree-kill": "^1.2.2", + "ts-loader": "^9.2.8", + "typescript": "^4.6.3", "vue": "^2.6.14", + "vue-eslint-parser": "^8.3.0", "vue-loader": "^15.9.8", "vue-template-compiler": "^2.6.14", "webpack": "^5.60.0", diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..df9a8d8c --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,19 @@ +{ + "include": ["./src/main/**/*", "src/common/interfaces/antares.ts"], + "compilerOptions": { + "baseUrl": "./", + "target": "es2021", + "allowJs": true, + "moduleResolution": "node12", + "noImplicitAny": true, + "types": [ + "node" + ], + "allowSyntheticDefaultImports": true, + "resolveJsonModule": true, + "removeComments": true, + "paths": { + "common/*": ["./src/common/*"] + } + } +} \ No newline at end of file diff --git a/webpack.main.config.js b/webpack.main.config.js index dca9bb05..88bacb36 100644 --- a/webpack.main.config.js +++ b/webpack.main.config.js @@ -13,7 +13,7 @@ module.exports = { // Main mode: process.env.NODE_ENV, devtool: isDevMode ? 'eval-source-map' : false, entry: { - main: path.join(__dirname, './src/main/main.js') + main: path.join(__dirname, './src/main/main.ts') }, target: 'electron-main', output: { @@ -28,7 +28,7 @@ module.exports = { // Main }, externals: externals.filter((d) => !whiteListedModules.includes(d)), resolve: { - extensions: ['.js', '.json'], + extensions: ['.js', '.json', '.ts'], alias: { src: path.join(__dirname, 'src/'), common: path.resolve(__dirname, 'src/common') @@ -56,6 +56,11 @@ module.exports = { // Main name: '[path][name].[ext]' } }, + { + test: /\.ts$/, + exclude: /node_modules/, + loader: 'ts-loader' + }, { test: /\.js$/, exclude: /node_modules/, From c6c14fbf2b8f1a9db3c029475cc8b4801024534d Mon Sep 17 00:00:00 2001 From: Fabio Di Stasio Date: Tue, 12 Apr 2022 17:08:05 +0200 Subject: [PATCH 04/14] refactor: mysql client ts refactor --- src/common/interfaces/antares.ts | 296 +++ .../{connection.js => connection.ts} | 56 +- src/main/ipc-handlers/{index.js => index.ts} | 4 +- .../libs/{AntaresCore.js => AntaresCore.ts} | 98 +- src/main/libs/ClientsFactory.js | 38 - src/main/libs/ClientsFactory.ts | 20 + .../{MySQLClient.js => MySQLClient.ts} | 1651 ++++++++--------- src/main/libs/clients/PostgreSQLClient.js | 2 + src/main/{main.js => main.ts} | 10 +- .../WorkspaceEditConnectionPanel.vue | 8 - 10 files changed, 1159 insertions(+), 1024 deletions(-) create mode 100644 src/common/interfaces/antares.ts rename src/main/ipc-handlers/{connection.js => connection.ts} (63%) rename src/main/ipc-handlers/{index.js => index.ts} (85%) rename src/main/libs/{AntaresCore.js => AntaresCore.ts} (52%) delete mode 100644 src/main/libs/ClientsFactory.js create mode 100644 src/main/libs/ClientsFactory.ts rename src/main/libs/clients/{MySQLClient.js => MySQLClient.ts} (77%) rename src/main/{main.js => main.ts} (97%) diff --git a/src/common/interfaces/antares.ts b/src/common/interfaces/antares.ts new file mode 100644 index 00000000..9ef4172f --- /dev/null +++ b/src/common/interfaces/antares.ts @@ -0,0 +1,296 @@ +import mysql from 'mysql2/promise'; +import * as pg from 'pg'; +import SSHConfig from 'ssh2-promise/lib/sshConfig'; +import { MySQLClient } from '../../main/libs/clients/MySQLClient'; +import { PostgreSQLClient } from '../../main/libs/clients/PostgreSQLClient'; +import { SQLiteClient } from '../../main/libs/clients/SQLiteClient'; + +export type Client = MySQLClient | PostgreSQLClient | SQLiteClient +export type ClientCode = 'mysql' | 'maria' | 'pg' | 'sqlite' + +/** + * Pasameters needed to create a new Antares connection to a database + */ +export interface ClientParams { + client: ClientCode; + params: + mysql.ConnectionOptions & {schema: string; ssl?: mysql.SslOptions; ssh?: SSHConfig; readonly: boolean} + | pg.ClientConfig & {schema: string; ssl?: mysql.SslOptions; ssh?: SSHConfig; readonly: boolean} + | { databasePath: string; readonly: boolean }; + poolSize?: number; + logger?: () => void; +} + +/** + * Paramenets insered by user in connection mask + */ +export interface ConnectionParams { + uid: string; + name?: string; + client: ClientCode; + host: string; + database?: string; + schema?: string; + databasePath?: string; + port: number; + user: string; + password: string; + ask: boolean; + readonly: boolean; + ssl: boolean; + cert?: string; + key?: string; + ca?: string; + untrustedConnection: boolean; + ciphers?: string; + ssh: boolean; + sshHost?: string; + sshUser?: string; + sshPass?: string; + sshKey?: string; + sshPort?: number; + sshPassphrase?: string; +} + +// Tables +export interface TableField { + name: string; + key: string; + type: string; + schema: string; + numPrecision?: number; + numLength?: number; + datePrecision?: number; + charLength?: number; + numScale?: number; + nullable?: boolean; + unsigned?: boolean; + zerofill?: boolean; + order?: number; + default?: number | string; + enumValues?: string; + charset?: string; + collation?: string; + autoIncrement?: boolean; + onUpdate?: string; + comment?: string; + after?: string; + orgName?: string; +} + +export interface TableIndex { + name: string; + fields: string[]; + type: string; + comment?: string; + indexType?: string; + indexComment?: string; + cardinality?: number; + oldType?: string; + oldName?: string; +} + +export interface TableForeign { + constraintName: string; + refSchema: string; + table: string; + refTable: string; + field: string; + refField: string; + onUpdate: string; + onDelete: string; + oldName?: string; +} + +export interface TableOptions { + name: string; + type: 'table' | 'view'; + engine?: string; + comment?: string; + collation?: string; + autoIncrement?: number; +} + +export interface CreateTableParams { + /** Connection UID */ + uid: string; + schema: string; + fields: TableField[]; + foreigns: TableForeign[]; + indexes: TableIndex[]; + options: TableOptions; +} + +export interface AlterTableParams { + /** Connection UID */ + uid: string; + schema: string; + table: string; + additions: TableField[]; + changes: TableField[]; + deletions: TableField[]; + indexChanges: { + additions: TableIndex[]; + changes: TableIndex[]; + deletions: TableIndex[]; + }; + foreignChanges: { + additions: TableForeign[]; + changes: TableForeign[]; + deletions: TableForeign[]; + }; + options: TableOptions; +} + +// Views +export interface CreateViewParams { + schema: string; + name: string; + algorithm: string; + definer: string; + security: string; + sql: string; + updateOption: string; +} + +export interface AlterViewParams extends CreateViewParams { + oldName?: string; +} + +// Triggers +export interface CreateTriggerParams { + definer?: string; + schema: string; + name: string; + activation: string; + event: string; + table: string; + sql: string; +} + +export interface AlterTriggerParams extends CreateTriggerParams { + oldName?: string; +} + +// Routines & Functions +export interface FunctionParam { + context: string; + name: string; + type: string; + length: number; +} + +export interface CreateRoutineParams { + name: string; + parameters?: FunctionParam[]; + definer: string; + schema: string; + deterministic: boolean; + dataAccess: string; + security: string; + comment?: string; + sql: string; +} + +export interface AlterRoutineParams extends CreateRoutineParams { + oldName?: string; +} + +export interface CreateFunctionParams { + name: string; + parameters?: FunctionParam[]; + definer: string; + schema: string; + deterministic: boolean; + dataAccess: string; + security: string; + comment?: string; + sql: string; + returns: string; + returnsLength: number; +} + +export interface AlterFunctionParams extends CreateFunctionParams { + oldName?: string; +} + +// Events +export interface CreateEventParams { + definer?: string; + schema: string; + name: string; + execution: string; + every: string[]; + starts: string; + ends: string; + at: string; + preserve: string; + state: string; + comment: string; + sql: string; +} + +export interface AlterEventParams extends CreateEventParams { + oldName?: string; +} + +// Query +export interface QueryBuilderObject { + schema: string; + select: string[]; + from: string; + where: string[]; + groupBy: string[]; + orderBy: string[]; + limit: string[]; + offset: string[]; + join: string[]; + update: string[]; + insert: string[]; + delete: boolean; +} + +export interface QueryParams { + nest?: boolean; + details?: boolean; + split?: boolean; + comments?: boolean; + autocommit?: boolean; + schema?: string; + tabUid?: string; +} + +export interface QueryField { + name: string; + alias: string; + orgName: string; + schema: string; + table: string; + tableAlias: string; + orgTable: string; + type: string; + length: number; +} + +export interface QueryForeign { + schema: string; + table: string; + field: string; + position: number; + constraintPosition: number; + constraintName: string; + refSchema: string; + refTable: string; + refField: string; + onUpdate: string; + onDelete: string; +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export interface QueryResult { + rows: T[]; + report: { affectedRows: number }; + fields: QueryField[]; + keys: QueryForeign[]; + duration: number; +} diff --git a/src/main/ipc-handlers/connection.js b/src/main/ipc-handlers/connection.ts similarity index 63% rename from src/main/ipc-handlers/connection.js rename to src/main/ipc-handlers/connection.ts index 3a0019aa..50f406ba 100644 --- a/src/main/ipc-handlers/connection.js +++ b/src/main/ipc-handlers/connection.ts @@ -1,16 +1,29 @@ +import * as antares from 'common/interfaces/antares'; import fs from 'fs'; import { ipcMain } from 'electron'; import { ClientsFactory } from '../libs/ClientsFactory'; +import { SslOptions } from 'mysql2'; -export default connections => { - ipcMain.handle('test-connection', async (event, conn) => { +export default (connections: {[key: string]: antares.Client}) => { + ipcMain.handle('test-connection', async (event, conn: antares.ConnectionParams) => { const params = { host: conn.host, port: +conn.port, user: conn.user, password: conn.password, - application_name: 'Antares SQL', - readonly: conn.readonly + readonly: conn.readonly, + database: '', + schema: '', + databasePath: '', + ssl: undefined as SslOptions, + ssh: undefined as { + host: string; + username: string; + password: string; + port: number; + privateKey: string; + passphrase: string; + } }; if (conn.database) @@ -21,9 +34,9 @@ export default connections => { if (conn.ssl) { params.ssl = { - key: conn.key ? fs.readFileSync(conn.key) : null, - cert: conn.cert ? fs.readFileSync(conn.cert) : null, - ca: conn.ca ? fs.readFileSync(conn.ca) : null, + key: conn.key ? fs.readFileSync(conn.key).toString() : null, + cert: conn.cert ? fs.readFileSync(conn.cert).toString() : null, + ca: conn.ca ? fs.readFileSync(conn.ca).toString() : null, ciphers: conn.ciphers, rejectUnauthorized: !conn.untrustedConnection }; @@ -35,7 +48,7 @@ export default connections => { username: conn.sshUser, password: conn.sshPass, port: conn.sshPort ? conn.sshPort : 22, - privateKey: conn.sshKey ? fs.readFileSync(conn.sshKey) : null, + privateKey: conn.sshKey ? fs.readFileSync(conn.sshKey).toString() : null, passphrase: conn.sshPassphrase }; } @@ -61,14 +74,26 @@ export default connections => { return uid in connections; }); - ipcMain.handle('connect', async (event, conn) => { + ipcMain.handle('connect', async (event, conn: antares.ConnectionParams) => { const params = { host: conn.host, port: +conn.port, user: conn.user, password: conn.password, application_name: 'Antares SQL', - readonly: conn.readonly + readonly: conn.readonly, + database: '', + schema: '', + databasePath: '', + ssl: undefined as SslOptions, + ssh: undefined as { + host: string; + username: string; + password: string; + port: number; + privateKey: string; + passphrase: string; + } }; if (conn.database) @@ -82,9 +107,9 @@ export default connections => { if (conn.ssl) { params.ssl = { - key: conn.key ? fs.readFileSync(conn.key) : null, - cert: conn.cert ? fs.readFileSync(conn.cert) : null, - ca: conn.ca ? fs.readFileSync(conn.ca) : null, + key: conn.key ? fs.readFileSync(conn.key).toString() : null, + cert: conn.cert ? fs.readFileSync(conn.cert).toString() : null, + ca: conn.ca ? fs.readFileSync(conn.ca).toString() : null, ciphers: conn.ciphers, rejectUnauthorized: !conn.untrustedConnection }; @@ -96,7 +121,7 @@ export default connections => { username: conn.sshUser, password: conn.sshPass, port: conn.sshPort ? conn.sshPort : 22, - privateKey: conn.sshKey ? fs.readFileSync(conn.sshKey) : null, + privateKey: conn.sshKey ? fs.readFileSync(conn.sshKey).toString() : null, passphrase: conn.sshPassphrase }; } @@ -110,6 +135,9 @@ export default connections => { await connection.connect(); + // TODO: temporary + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore const structure = await connection.getStructure(new Set()); connections[conn.uid] = connection; diff --git a/src/main/ipc-handlers/index.js b/src/main/ipc-handlers/index.ts similarity index 85% rename from src/main/ipc-handlers/index.js rename to src/main/ipc-handlers/index.ts index 503a79b2..2ee53db7 100644 --- a/src/main/ipc-handlers/index.js +++ b/src/main/ipc-handlers/index.ts @@ -1,3 +1,5 @@ +import * as antares from 'common/interfaces/antares'; + import connection from './connection'; import tables from './tables'; import views from './views'; @@ -10,7 +12,7 @@ import application from './application'; import schema from './schema'; import users from './users'; -const connections = {}; +const connections: {[key: string]: antares.Client} = {}; export default () => { connection(connections); diff --git a/src/main/libs/AntaresCore.js b/src/main/libs/AntaresCore.ts similarity index 52% rename from src/main/libs/AntaresCore.js rename to src/main/libs/AntaresCore.ts index 7f5cd0ae..042510dd 100644 --- a/src/main/libs/AntaresCore.js +++ b/src/main/libs/AntaresCore.ts @@ -1,5 +1,10 @@ -'use strict'; -const queryLogger = sql => { +// import BetterSqlite3 from 'better-sqlite3'; +import * as antares from 'common/interfaces/antares'; +import mysql from 'mysql2/promise'; +import * as pg from 'pg'; +import SSH2Promise from 'ssh2-promise'; + +const queryLogger = (sql: string) => { // Remove comments, newlines and multiple spaces const escapedSql = sql.replace(/(\/\*(.|[\r\n])*?\*\/)|(--(.*|[\r\n]))/gm, '').replace(/\s\s+/g, ' '); console.log(escapedSql); @@ -7,22 +12,21 @@ const queryLogger = sql => { /** * As Simple As Possible Query Builder Core - * - * @class AntaresCore */ export class AntaresCore { - /** - * Creates an instance of AntaresCore. - * - * @param {Object} args connection params - * @memberof AntaresCore - */ - constructor (args) { + protected _client: string; + protected _params: mysql.ConnectionOptions | pg.ClientConfig | { databasePath: string; readonly: boolean}; + protected _poolSize: number; + // protected _connection?: mysql.Connection | mysql.Pool | pg.Connection | BetterSqlite3.Database + protected _ssh?: SSH2Promise; + protected _logger: (sql: string) => void; + protected _queryDefaults: antares.QueryBuilderObject; + protected _query: antares.QueryBuilderObject; + + constructor (args: antares.ClientParams) { this._client = args.client; this._params = args.params; - this._poolSize = args.poolSize || false; - this._connection = null; - this._ssh = null; + this._poolSize = args.poolSize || undefined; this._logger = args.logger || queryLogger; this._queryDefaults = { @@ -42,7 +46,8 @@ export class AntaresCore { this._query = Object.assign({}, this._queryDefaults); } - _reducer (acc, curr) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + protected _reducer (acc: string[], curr: any) { const type = typeof curr; switch (type) { @@ -62,94 +67,87 @@ export class AntaresCore { } } - /** - * Resets the query object after a query - * - * @memberof AntaresCore - */ - _resetQuery () { + private _resetQuery () { this._query = Object.assign({}, this._queryDefaults); } - schema (schema) { + schema (schema: string) { this._query.schema = schema; return this; } - select (...args) { + select (...args: string[]) { this._query.select = [...this._query.select, ...args]; return this; } - from (table) { + from (table: string) { this._query.from = table; return this; } - into (table) { + into (table: string) { this._query.from = table; return this; } - delete (table) { + delete (table: string) { this._query.delete = true; this.from(table); return this; } - where (...args) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + where (...args: any) { this._query.where = [...this._query.where, ...args]; return this; } - groupBy (...args) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + groupBy (...args: any) { this._query.groupBy = [...this._query.groupBy, ...args]; return this; } - orderBy (...args) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + orderBy (...args: any) { this._query.orderBy = [...this._query.orderBy, ...args]; return this; } - limit (...args) { + limit (...args: string[]) { this._query.limit = args; return this; } - offset (...args) { + offset (...args: string[]) { this._query.offset = args; return this; } - /** - * @param {String | Array} args field = value - * @returns - * @memberof AntaresCore - */ - update (...args) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + update (...args: any) { this._query.update = [...this._query.update, ...args]; return this; } - /** - * @param {Array} arr Array of row objects - * @returns - * @memberof AntaresCore - */ - insert (arr) { + insert (arr: string[]) { this._query.insert = [...this._query.insert, ...arr]; return this; } - /** - * @param {Object} args - * @returns {Promise} - * @memberof AntaresCore - */ - run (args) { + getSQL (): string { + throw new Error('Client must implement the "getSQL" method'); + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + raw (_sql: string, _args?: antares.QueryParams): Promise { + throw new Error('Client must implement the "raw" method'); + } + + run (args?: antares.QueryParams) { const rawQuery = this.getSQL(); this._resetQuery(); - return this.raw(rawQuery, args); + return this.raw>(rawQuery, args); } } diff --git a/src/main/libs/ClientsFactory.js b/src/main/libs/ClientsFactory.js deleted file mode 100644 index 0ab6d5ba..00000000 --- a/src/main/libs/ClientsFactory.js +++ /dev/null @@ -1,38 +0,0 @@ -'use strict'; -import { MySQLClient } from './clients/MySQLClient'; -import { PostgreSQLClient } from './clients/PostgreSQLClient'; -import { SQLiteClient } from './clients/SQLiteClient'; -export class ClientsFactory { - /** - * Returns a database connection based on received args. - * - * @param {Object} args - * @param {String} args.client - * @param {Object} args.params - * @param {String} args.params.host - * @param {Number} args.params.port - * @param {String} args.params.password - * @param {String=} args.params.database - * @param {String=} args.params.schema - * @param {String} args.params.ssh.host - * @param {String} args.params.ssh.username - * @param {String} args.params.ssh.password - * @param {Number} args.params.ssh.port - * @param {Number=} args.poolSize - * @returns Database Connection - * @memberof ClientsFactory - */ - static getClient (args) { - switch (args.client) { - case 'mysql': - case 'maria': - return new MySQLClient(args); - case 'pg': - return new PostgreSQLClient(args); - case 'sqlite': - return new SQLiteClient(args); - default: - throw new Error(`Unknown database client: ${args.client}`); - } - } -} diff --git a/src/main/libs/ClientsFactory.ts b/src/main/libs/ClientsFactory.ts new file mode 100644 index 00000000..73c29920 --- /dev/null +++ b/src/main/libs/ClientsFactory.ts @@ -0,0 +1,20 @@ +import * as antares from 'common/interfaces/antares'; +import { MySQLClient } from './clients/MySQLClient'; +import { PostgreSQLClient } from './clients/PostgreSQLClient'; +import { SQLiteClient } from './clients/SQLiteClient'; + +export class ClientsFactory { + static getClient (args: antares.ClientParams) { + switch (args.client) { + case 'mysql': + case 'maria': + return new MySQLClient(args); + case 'pg': + return new PostgreSQLClient(args); + case 'sqlite': + return new SQLiteClient(args); + default: + throw new Error(`Unknown database client: ${args.client}`); + } + } +} diff --git a/src/main/libs/clients/MySQLClient.js b/src/main/libs/clients/MySQLClient.ts similarity index 77% rename from src/main/libs/clients/MySQLClient.js rename to src/main/libs/clients/MySQLClient.ts index 5f514774..8332e439 100644 --- a/src/main/libs/clients/MySQLClient.js +++ b/src/main/libs/clients/MySQLClient.ts @@ -1,53 +1,60 @@ -'use strict'; +import * as antares from 'common/interfaces/antares'; import mysql from 'mysql2/promise'; import { AntaresCore } from '../AntaresCore'; import dataTypes from 'common/data-types/mysql'; -import * as SSH2Promise from 'ssh2-promise'; +import SSH2Promise from 'ssh2-promise'; +import SSHConfig from 'ssh2-promise/lib/sshConfig'; export class MySQLClient extends AntaresCore { - constructor (args) { + private _schema?: string; + private _runningConnections: Map; + private _connectionsToCommit: Map; + protected _connection?: mysql.Connection | mysql.Pool; + protected _params: mysql.ConnectionOptions & {schema: string; ssl?: mysql.SslOptions; ssh?: SSHConfig; readonly: boolean}; + + private types: {[key: number]: string} = { + 0: 'DECIMAL', + 1: 'TINYINT', + 2: 'SMALLINT', + 3: 'INT', + 4: 'FLOAT', + 5: 'DOUBLE', + 6: 'NULL', + 7: 'TIMESTAMP', + 8: 'BIGINT', + 9: 'MEDIUMINT', + 10: 'DATE', + 11: 'TIME', + 12: 'DATETIME', + 13: 'YEAR', + 14: 'NEWDATE', + 15: 'VARCHAR', + 16: 'BIT', + 17: 'TIMESTAMP2', + 18: 'DATETIME2', + 19: 'TIME2', + 245: 'JSON', + 246: 'NEWDECIMAL', + 247: 'ENUM', + 248: 'SET', + 249: 'TINY_BLOB', + 250: 'MEDIUM_BLOB', + 251: 'LONG_BLOB', + 252: 'BLOB', + 253: 'VARCHAR', + 254: 'CHAR', + 255: 'GEOMETRY' + } + + constructor (args: antares.ClientParams) { super(args); this._schema = null; this._runningConnections = new Map(); this._connectionsToCommit = new Map(); - - this.types = { - 0: 'DECIMAL', - 1: 'TINYINT', - 2: 'SMALLINT', - 3: 'INT', - 4: 'FLOAT', - 5: 'DOUBLE', - 6: 'NULL', - 7: 'TIMESTAMP', - 8: 'BIGINT', - 9: 'MEDIUMINT', - 10: 'DATE', - 11: 'TIME', - 12: 'DATETIME', - 13: 'YEAR', - 14: 'NEWDATE', - 15: 'VARCHAR', - 16: 'BIT', - 17: 'TIMESTAMP2', - 18: 'DATETIME2', - 19: 'TIME2', - 245: 'JSON', - 246: 'NEWDECIMAL', - 247: 'ENUM', - 248: 'SET', - 249: 'TINY_BLOB', - 250: 'MEDIUM_BLOB', - 251: 'LONG_BLOB', - 252: 'BLOB', - 253: 'VARCHAR', - 254: 'CHAR', - 255: 'GEOMETRY' - }; } - _getType (field) { + private _getType (field: mysql.FieldPacket & { columnType?: number; columnLength?: number }) { let name = this.types[field.columnType]; let length = field.columnLength; @@ -95,13 +102,14 @@ export class MySQLClient extends AntaresCore { return { name, length }; } - _getTypeInfo (type) { + private _getTypeInfo (type: string) { return dataTypes .reduce((acc, group) => [...acc, ...group.types], []) - .filter(_type => _type.name === type.toUpperCase())[0]; + .filter((_type) => _type.name === type.toUpperCase())[0]; } - _reducer (acc, curr) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + protected _reducer (acc: string[], curr: any) { const type = typeof curr; switch (type) { @@ -121,27 +129,21 @@ export class MySQLClient extends AntaresCore { } } - /** - * - * @returns dbConfig - * @memberof MySQLClient - */ - async getDbConfig () { - delete this._params.application_name; - + async getDbConfig (): Promise { const dbConfig = { host: this._params.host, port: this._params.port, user: this._params.user, password: this._params.password, - ssl: null, + database: undefined as string | undefined, + ssl: null as mysql.SslOptions, supportBigNumbers: true, bigNumberStrings: true }; if (this._params.schema?.length) dbConfig.database = this._params.schema; - if (this._params.ssl) dbConfig.ssl = { ...this._params.ssl }; + if (this._params.ssl) dbConfig.ssl = this._params.ssl; if (this._params.ssh) { try { @@ -152,7 +154,7 @@ export class MySQLClient extends AntaresCore { remotePort: this._params.port }); - dbConfig.host = this._ssh.config.host; + dbConfig.host = (this._ssh.config as SSHConfig[] & { host: string }).host; dbConfig.port = tunnel.localPort; } catch (err) { @@ -164,9 +166,6 @@ export class MySQLClient extends AntaresCore { return dbConfig; } - /** - * @memberof MySQLClient - */ async connect () { if (!this._poolSize) this._connection = await this.getConnection(); @@ -174,9 +173,6 @@ export class MySQLClient extends AntaresCore { this._connection = await this.getConnectionPool(); } - /** - * @memberof MySQLClient - */ destroy () { this._connection.end(); if (this._ssh) this._ssh.close(); @@ -195,15 +191,15 @@ export class MySQLClient extends AntaresCore { }); // ANSI_QUOTES check - const [res] = await connection.query('SHOW GLOBAL VARIABLES LIKE \'%sql_mode%\''); - const sqlMode = res[0]?.Variable_name?.split(','); + const [response] = await connection.query('SHOW GLOBAL VARIABLES LIKE \'%sql_mode%\''); + const sqlMode = response[0]?.Variable_name?.split(','); const hasAnsiQuotes = sqlMode.includes('ANSI_QUOTES'); if (this._params.readonly) await connection.query('SET SESSION TRANSACTION READ ONLY'); if (hasAnsiQuotes) - await connection.query(`SET SESSION sql_mode = "${sqlMode.filter(m => m !== 'ANSI_QUOTES').join(',')}"`); + await connection.query(`SET SESSION sql_mode = "${sqlMode.filter((m: string) => m !== 'ANSI_QUOTES').join(',')}"`); return connection; } @@ -222,42 +218,70 @@ export class MySQLClient extends AntaresCore { }); // ANSI_QUOTES check - const [res] = await connection.query('SHOW GLOBAL VARIABLES LIKE \'%sql_mode%\''); + const [res] = await connection.query('SHOW GLOBAL VARIABLES LIKE \'%sql_mode%\''); const sqlMode = res[0]?.Variable_name?.split(','); const hasAnsiQuotes = sqlMode.includes('ANSI_QUOTES'); if (hasAnsiQuotes) - await connection.query(`SET SESSION sql_mode = "${sqlMode.filter(m => m !== 'ANSI_QUOTES').join(',')}"`); + await connection.query(`SET SESSION sql_mode = "${sqlMode.filter((m: string) => m !== 'ANSI_QUOTES').join(',')}"`); connection.on('connection', conn => { if (this._params.readonly) conn.query('SET SESSION TRANSACTION READ ONLY'); if (hasAnsiQuotes) - conn.query(`SET SESSION sql_mode = "${sqlMode.filter(m => m !== 'ANSI_QUOTES').join(',')}"`); + conn.query(`SET SESSION sql_mode = "${sqlMode.filter((m: string) => m !== 'ANSI_QUOTES').join(',')}"`); }); return connection; } - /** - * Executes an USE query - * - * @param {String} schema - * @memberof MySQLClient - */ - use (schema) { + use (schema: string) { this._schema = schema; return this.raw(`USE \`${schema}\``); } - /** - * @param {Array} schemas list - * @returns {Array.} databases scructure - * @memberof MySQLClient - */ - async getStructure (schemas) { - const { rows: databases } = await this.raw('SHOW DATABASES'); + async getStructure (schemas: Set) { + /* eslint-disable camelcase */ + interface ShowTableResult { + Db?: string; + Name: string; + Engine: string; + Version: number; + Row_format: string; + Rows: number; + Avg_row_length: number; + Data_length: number; + Max_data_length: number; + Index_length: number; + Data_free: number; + Auto_increment: number; + Create_time: Date; + Update_time: Date; + Check_time?: number; + Collation: string; + Checksum?: number; + Create_options: string; + Comment: string; + } + + interface ShowTriggersResult { + Db?: string; + Trigger: string; + Event: string; + Table: string; + Statement: string; + Timing: string; + Created: Date; + sql_mode: string; + Definer: string; + character_set_client: string; + collation_connection: string; + 'Database Collation': string; + } + /* eslint-enable camelcase */ + + const { rows: databases } = await this.raw>('SHOW DATABASES'); let filteredDatabases = databases; @@ -268,14 +292,14 @@ export class MySQLClient extends AntaresCore { const { rows: procedures } = await this.raw('SHOW PROCEDURE STATUS'); const { rows: schedulers } = await this.raw('SELECT *, EVENT_SCHEMA AS `Db`, EVENT_NAME AS `Name` FROM information_schema.`EVENTS`'); - const tablesArr = []; - const triggersArr = []; + const tablesArr: ShowTableResult[] = []; + const triggersArr: ShowTriggersResult[] = []; let schemaSize = 0; for (const db of filteredDatabases) { if (!schemas.has(db.Database)) continue; - let { rows: tables } = await this.raw(`SHOW TABLE STATUS FROM \`${db.Database}\``); + let { rows: tables } = await this.raw>(`SHOW TABLE STATUS FROM \`${db.Database}\``); if (tables.length) { tables = tables.map(table => { table.Db = db.Database; @@ -284,7 +308,7 @@ export class MySQLClient extends AntaresCore { tablesArr.push(...tables); } - let { rows: triggers } = await this.raw(`SHOW TRIGGERS FROM \`${db.Database}\``); + let { rows: triggers } = await this.raw>(`SHOW TRIGGERS FROM \`${db.Database}\``); if (triggers.length) { triggers = triggers.map(trigger => { trigger.Db = db.Database; @@ -418,23 +442,41 @@ export class MySQLClient extends AntaresCore { }); } - /** - * @param {Object} params - * @param {String} params.schema - * @param {String} params.table - * @returns {Object} table scructure - * @memberof MySQLClient - */ - async getTableColumns ({ schema, table }) { + async getTableColumns ({ schema, table }: { schema: string; table: string }) { + interface TableColumnsResult { + COLUMN_TYPE: string; + NUMERIC_PRECISION: string; + COLUMN_NAME: string; + COLUMN_DEFAULT: string; + COLUMN_KEY: string; + DATA_TYPE: string; + TABLE_SCHEMA: string; + TABLE_NAME: string; + NUMERIC_SCALE: string; + DATETIME_PRECISION: string; + CHARACTER_MAXIMUM_LENGTH: string; + IS_NULLABLE: string; + ORDINAL_POSITION: string; + CHARACTER_SET_NAME: string; + COLLATION_NAME: string; + EXTRA: string; + COLUMN_COMMENT: string; + } + + interface CreateTableResult { + 'Create Table'?: string; + Table: string; + } + const { rows } = await this .select('*') .schema('information_schema') .from('COLUMNS') .where({ TABLE_SCHEMA: `= '${schema}'`, TABLE_NAME: `= '${table}'` }) .orderBy({ ORDINAL_POSITION: 'ASC' }) - .run(); + .run(); - const { rows: fields } = await this.raw(`SHOW CREATE TABLE \`${schema}\`.\`${table}\``); + const { rows: fields } = await this.raw>(`SHOW CREATE TABLE \`${schema}\`.\`${table}\``); const remappedFields = fields.map(row => { if (!row['Create Table']) return false; @@ -442,7 +484,7 @@ export class MySQLClient extends AntaresCore { let n = 0; return row['Create Table'] .split('') - .reduce((acc, curr) => { + .reduce((acc: string, curr: string) => { if (curr === ')') n--; if (n !== 0) acc += curr; if (curr === '(') n++; @@ -450,23 +492,16 @@ export class MySQLClient extends AntaresCore { }, '') .replaceAll('\n', '') .split(/,\s?(?![^(]*\))/) - .map(f => { + .map((f: string) => { try { const fieldArr = f.trim().split(' '); const nameAndType = fieldArr.slice(0, 2); - if (nameAndType[0].charAt(0) !== '`') return false; + if (nameAndType[0].charAt(0) !== '`') return null; const details = fieldArr.slice(2).join(' '); let defaultValue = null; if (details.includes('DEFAULT')) defaultValue = details.match(/(?<=DEFAULT ).*?$/gs)[0].split(' COMMENT')[0]; - // const defaultValueArr = defaultValue.split(''); - // if (defaultValueArr[0] === '\'') { - // defaultValueArr.shift(); - // defaultValueArr.pop(); - // defaultValue = defaultValueArr.join(''); - // } - const typeAndLength = nameAndType[1].replace(')', '').split('('); return { @@ -477,19 +512,19 @@ export class MySQLClient extends AntaresCore { }; } catch (err) { - return false; + return null; } }) .filter(Boolean) - .reduce((acc, curr) => { + .reduce((acc: {[key: string]: { name: string; type: string; length: string; default: string}}, curr) => { acc[curr.name] = curr; return acc; }, {}); })[0]; - return rows.map(field => { - let numLength = field.COLUMN_TYPE.match(/int\(([^)]+)\)/); - numLength = numLength ? +numLength.pop() : field.NUMERIC_PRECISION || null; + return rows.map((field) => { + const numLengthMatch = field.COLUMN_TYPE.match(/int\(([^)]+)\)/); + const numLength = numLengthMatch ? +numLengthMatch.pop() : field.NUMERIC_PRECISION || null; const enumValues = /(enum|set)/.test(field.COLUMN_TYPE) ? field.COLUMN_TYPE.match(/\(([^)]+)\)/)[0].slice(1, -1) : null; @@ -529,27 +564,13 @@ export class MySQLClient extends AntaresCore { }); } - /** - * @param {Object} params - * @param {String} params.schema - * @param {String} params.table - * @returns {Object} table row count - * @memberof MySQLClient - */ - async getTableApproximateCount ({ schema, table }) { + async getTableApproximateCount ({ schema, table }: { schema: string; table: string }) { const { rows } = await this.raw(`SELECT table_rows "count" FROM information_schema.tables WHERE table_name = "${table}" AND table_schema = "${schema}"`); return rows.length ? rows[0].count : 0; } - /** - * @param {Object} params - * @param {String} params.schema - * @param {String} params.table - * @returns {Object} table options - * @memberof MySQLClient - */ - async getTableOptions ({ schema, table }) { + async getTableOptions ({ schema, table }: { schema: string; table: string }) { const { rows } = await this.raw(`SHOW TABLE STATUS FROM \`${schema}\` WHERE Name = '${table}'`); if (rows.length) { @@ -575,18 +596,11 @@ export class MySQLClient extends AntaresCore { autoIncrement: rows[0].Auto_increment, collation: rows[0].Collation }; - }; + } return {}; } - /** - * @param {Object} params - * @param {String} params.schema - * @param {String} params.table - * @returns {Object} table indexes - * @memberof MySQLClient - */ - async getTableIndexes ({ schema, table }) { + async getTableIndexes ({ schema, table }: { schema: string; table: string }) { const { rows } = await this.raw(`SHOW INDEXES FROM \`${table}\` FROM \`${schema}\``); return rows.map(row => { @@ -603,27 +617,38 @@ export class MySQLClient extends AntaresCore { }); } - /** - * @param {Object} params - * @param {String} params.schema - * @param {String} params.table - * @returns {Object} table key usage - * @memberof MySQLClient - */ - async getKeyUsage ({ schema, table }) { + async getKeyUsage ({ schema, table }: { schema: string; table: string }) { + interface KeyResult { + TABLE_SCHEMA: string; + TABLE_NAME: string; + COLUMN_NAME: string; + ORDINAL_POSITION: number; + POSITION_IN_UNIQUE_CONSTRAINT: number; + CONSTRAINT_NAME: string; + REFERENCED_TABLE_SCHEMA: string; + REFERENCED_TABLE_NAME: string; + REFERENCED_COLUMN_NAME: string; + } + + interface KeyExtraResult { + CONSTRAINT_NAME: string; + UPDATE_RULE: string; + DELETE_RULE: string; + } + const { rows } = await this .select('*') .schema('information_schema') .from('KEY_COLUMN_USAGE') .where({ TABLE_SCHEMA: `= '${schema}'`, TABLE_NAME: `= '${table}'`, REFERENCED_TABLE_NAME: 'IS NOT NULL' }) - .run(); + .run(); const { rows: extras } = await this .select('*') .schema('information_schema') .from('REFERENTIAL_CONSTRAINTS') .where({ CONSTRAINT_SCHEMA: `= '${schema}'`, TABLE_NAME: `= '${table}'`, REFERENCED_TABLE_NAME: 'IS NOT NULL' }) - .run(); + .run(); return rows.map(field => { const extra = extras.find(x => x.CONSTRAINT_NAME === field.CONSTRAINT_NAME); @@ -639,16 +664,10 @@ export class MySQLClient extends AntaresCore { refField: field.REFERENCED_COLUMN_NAME, onUpdate: extra.UPDATE_RULE, onDelete: extra.DELETE_RULE - }; + } as antares.QueryForeign; }); } - /** - * SELECT `user`, `host`, authentication_string) AS `password` FROM `mysql`.`user` - * - * @returns {Array.} users list - * @memberof MySQLClient - */ async getUsers () { const { rows } = await this.raw('SELECT `user`, `host`, authentication_string AS `password` FROM `mysql`.`user`'); @@ -661,713 +680,23 @@ export class MySQLClient extends AntaresCore { }); } - /** - * CREATE DATABASE - * - * @returns {Promise} - * @memberof MySQLClient - */ - async createSchema (params) { + async createSchema (params: { name: string; collation: string }) { return await this.raw(`CREATE DATABASE \`${params.name}\` COLLATE ${params.collation}`); } - /** - * ALTER DATABASE - * - * @returns {Promise} - * @memberof MySQLClient - */ - async alterSchema (params) { + async alterSchema (params: { name: string; collation: string }) { return await this.raw(`ALTER DATABASE \`${params.name}\` COLLATE ${params.collation}`); } - /** - * DROP DATABASE - * - * @returns {Promise} - * @memberof MySQLClient - */ - async dropSchema (params) { + async dropSchema (params: { database: string }) { return await this.raw(`DROP DATABASE \`${params.database}\``); } - /** - * @returns {Array.} parameters - * @memberof MySQLClient - */ - async getDatabaseCollation (params) { + async getDatabaseCollation (params: { database: string }) { return await this.raw(`SELECT \`DEFAULT_COLLATION_NAME\` FROM \`information_schema\`.\`SCHEMATA\` WHERE \`SCHEMA_NAME\`='${params.database}'`); } - /** - * SHOW CREATE VIEW - * - * @returns {Array.} view informations - * @memberof MySQLClient - */ - async getViewInformations ({ schema, view }) { - const sql = `SHOW CREATE VIEW \`${schema}\`.\`${view}\``; - const results = await this.raw(sql); - - return results.rows.map(row => { - return { - algorithm: row['Create View'].match(/(?<=CREATE ALGORITHM=).*?(?=\s)/gs)[0], - definer: row['Create View'].match(/(?<=DEFINER=).*?(?=\s)/gs)[0], - security: row['Create View'].match(/(?<=SQL SECURITY ).*?(?=\s)/gs)[0], - updateOption: row['Create View'].match(/(?<=WITH ).*?(?=\s)/gs) ? row['Create View'].match(/(?<=WITH ).*?(?=\s)/gs)[0] : '', - sql: row['Create View'].match(/(?<=AS ).*?$/gs)[0], - name: row.View - }; - })[0]; - } - - /** - * DROP VIEW - * - * @returns {Promise} - * @memberof MySQLClient - */ - async dropView (params) { - const sql = `DROP VIEW \`${params.schema}\`.\`${params.view}\``; - return await this.raw(sql); - } - - /** - * ALTER VIEW - * - * @returns {Promise} - * @memberof MySQLClient - */ - async alterView (params) { - const { view } = params; - let sql = ` - USE \`${view.schema}\`; - ALTER ALGORITHM = ${view.algorithm}${view.definer ? ` DEFINER=${view.definer}` : ''} - SQL SECURITY ${view.security} - VIEW \`${view.schema}\`.\`${view.oldName}\` AS ${view.sql} ${view.updateOption ? `WITH ${view.updateOption} CHECK OPTION` : ''} - `; - - if (view.name !== view.oldName) - sql += `; RENAME TABLE \`${view.schema}\`.\`${view.oldName}\` TO \`${view.schema}\`.\`${view.name}\``; - - return await this.raw(sql); - } - - /** - * CREATE VIEW - * - * @returns {Promise} - * @memberof MySQLClient - */ - async createView (params) { - const sql = `CREATE ALGORITHM = ${params.algorithm} ${params.definer ? `DEFINER=${params.definer} ` : ''}SQL SECURITY ${params.security} VIEW \`${params.schema}\`.\`${params.name}\` AS ${params.sql} ${params.updateOption ? `WITH ${params.updateOption} CHECK OPTION` : ''}`; - return await this.raw(sql); - } - - /** - * SHOW CREATE TRIGGER - * - * @returns {Array.} view informations - * @memberof MySQLClient - */ - async getTriggerInformations ({ schema, trigger }) { - const sql = `SHOW CREATE TRIGGER \`${schema}\`.\`${trigger}\``; - const results = await this.raw(sql); - - return results.rows.map(row => { - return { - definer: row['SQL Original Statement'].match(/(?<=DEFINER=).*?(?=\s)/gs)[0], - sql: row['SQL Original Statement'].match(/(BEGIN|begin)(.*)(END|end)/gs)[0], - name: row.Trigger, - table: row['SQL Original Statement'].match(/(?<=ON `).*?(?=`)/gs)[0], - activation: row['SQL Original Statement'].match(/(BEFORE|AFTER)/gs)[0], - event: row['SQL Original Statement'].match(/(INSERT|UPDATE|DELETE)/gs)[0] - }; - })[0]; - } - - /** - * DROP TRIGGER - * - * @returns {Promise} - * @memberof MySQLClient - */ - async dropTrigger (params) { - const sql = `DROP TRIGGER \`${params.schema}\`.\`${params.trigger}\``; - return await this.raw(sql); - } - - /** - * ALTER TRIGGER - * - * @returns {Promise} - * @memberof MySQLClient - */ - async alterTrigger (params) { - const { trigger } = params; - const tempTrigger = Object.assign({}, trigger); - tempTrigger.name = `Antares_${tempTrigger.name}_tmp`; - - try { - await this.createTrigger(tempTrigger); - await this.dropTrigger({ schema: trigger.schema, trigger: tempTrigger.name }); - await this.dropTrigger({ schema: trigger.schema, trigger: trigger.oldName }); - await this.createTrigger(trigger); - } - catch (err) { - return Promise.reject(err); - } - } - - /** - * CREATE TRIGGER - * - * @returns {Promise} - * @memberof MySQLClient - */ - async createTrigger (params) { - const sql = `CREATE ${params.definer ? `DEFINER=${params.definer} ` : ''}TRIGGER \`${params.schema}\`.\`${params.name}\` ${params.activation} ${params.event} ON \`${params.table}\` FOR EACH ROW ${params.sql}`; - return await this.raw(sql, { split: false }); - } - - /** - * SHOW CREATE PROCEDURE - * - * @returns {Array.} view informations - * @memberof MySQLClient - */ - async getRoutineInformations ({ schema, routine }) { - const sql = `SHOW CREATE PROCEDURE \`${schema}\`.\`${routine}\``; - const results = await this.raw(sql); - - return results.rows.map(async row => { - if (!row['Create Procedure']) { - return { - definer: null, - sql: '', - parameters: [], - name: row.Procedure, - comment: '', - security: 'DEFINER', - deterministic: false, - dataAccess: 'CONTAINS SQL' - }; - } - - 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: row.PARAMETER_NAME, - type: row.DATA_TYPE.toUpperCase(), - length: row.NUMERIC_PRECISION || row.DATETIME_PRECISION || row.CHARACTER_MAXIMUM_LENGTH || '', - context: row.PARAMETER_MODE - }; - }); - - 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], - 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 - }; - })[0]; - } - - /** - * DROP PROCEDURE - * - * @returns {Promise} - * @memberof MySQLClient - */ - async dropRoutine (params) { - const sql = `DROP PROCEDURE \`${params.schema}\`.\`${params.routine}\``; - return await this.raw(sql); - } - - /** - * ALTER PROCEDURE - * - * @returns {Promise} - * @memberof MySQLClient - */ - async alterRoutine (params) { - const { routine } = params; - const tempProcedure = Object.assign({}, routine); - tempProcedure.name = `Antares_${tempProcedure.name}_tmp`; - - try { - await this.createRoutine(tempProcedure); - await this.dropRoutine({ schema: routine.schema, routine: tempProcedure.name }); - await this.dropRoutine({ schema: routine.schema, routine: routine.oldName }); - await this.createRoutine(routine); - } - catch (err) { - return Promise.reject(err); - } - } - - /** - * CREATE PROCEDURE - * - * @returns {Promise} - * @memberof MySQLClient - */ - async createRoutine (params) { - const parameters = 'parameters' in params - ? params.parameters.reduce((acc, curr) => { - acc.push(`${curr.context} \`${curr.name}\` ${curr.type}${curr.length ? `(${curr.length})` : ''}`); - return acc; - }, []).join(',') - : ''; - - const sql = `CREATE ${params.definer ? `DEFINER=${params.definer} ` : ''}PROCEDURE \`${params.schema}\`.\`${params.name}\`(${parameters}) - LANGUAGE SQL - ${params.deterministic ? 'DETERMINISTIC' : 'NOT DETERMINISTIC'} - ${params.dataAccess} - SQL SECURITY ${params.security} - COMMENT '${params.comment}' - ${params.sql}`; - - return await this.raw(sql, { split: false }); - } - - /** - * SHOW CREATE FUNCTION - * - * @returns {Array.} view informations - * @memberof MySQLClient - */ - async getFunctionInformations ({ schema, func }) { - const sql = `SHOW CREATE FUNCTION \`${schema}\`.\`${func}\``; - const results = await this.raw(sql); - - return results.rows.map(async row => { - if (!row['Create Function']) { - return { - definer: null, - sql: '', - parameters: [], - name: row.Procedure, - comment: '', - security: 'DEFINER', - deterministic: false, - dataAccess: 'CONTAINS SQL', - returns: 'INT', - returnsLength: 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: row.PARAMETER_NAME, - type: row.DATA_TYPE.toUpperCase(), - length: row.NUMERIC_PRECISION || row.DATETIME_PRECISION || row.CHARACTER_MAXIMUM_LENGTH || '', - context: row.PARAMETER_MODE - }; - }); - - 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], - 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] - }; - })[0]; - } - - /** - * DROP FUNCTION - * - * @returns {Promise} - * @memberof MySQLClient - */ - async dropFunction (params) { - const sql = `DROP FUNCTION \`${params.schema}\`.\`${params.func}\``; - return await this.raw(sql); - } - - /** - * ALTER FUNCTION - * - * @returns {Promise} - * @memberof MySQLClient - */ - async alterFunction (params) { - const { func } = params; - const tempProcedure = Object.assign({}, func); - tempProcedure.name = `Antares_${tempProcedure.name}_tmp`; - - try { - await this.createFunction(tempProcedure); - await this.dropFunction({ schema: func.schema, func: tempProcedure.name }); - await this.dropFunction({ schema: func.schema, func: func.oldName }); - await this.createFunction(func); - } - catch (err) { - return Promise.reject(err); - } - } - - /** - * CREATE FUNCTION - * - * @returns {Promise} - * @memberof MySQLClient - */ - async createFunction (params) { - const parameters = 'parameters' in params - ? params.parameters.reduce((acc, curr) => { - acc.push(`\`${curr.name}\` ${curr.type}${curr.length ? `(${curr.length})` : ''}`); - return acc; - }, []).join(',') - : ''; - - const body = params.returns ? params.sql : 'BEGIN\n RETURN 0;\nEND'; - - const sql = `CREATE ${params.definer ? `DEFINER=${params.definer} ` : ''}FUNCTION \`${params.schema}\`.\`${params.name}\`(${parameters}) RETURNS ${params.returns || 'SMALLINT'}${params.returnsLength ? `(${params.returnsLength})` : ''} - LANGUAGE SQL - ${params.deterministic ? 'DETERMINISTIC' : 'NOT DETERMINISTIC'} - ${params.dataAccess} - SQL SECURITY ${params.security} - COMMENT '${params.comment}' - ${body}`; - - return await this.raw(sql, { split: false }); - } - - /** - * SHOW CREATE EVENT - * - * @returns {Array.} view informations - * @memberof MySQLClient - */ - async getEventInformations ({ schema, scheduler }) { - const sql = `SHOW CREATE EVENT \`${schema}\`.\`${scheduler}\``; - const results = await this.raw(sql); - - return results.rows.map(row => { - const schedule = row['Create Event']; - const execution = schedule.includes('EVERY') ? 'EVERY' : 'ONCE'; - const every = execution === 'EVERY' ? row['Create Event'].match(/(?<=EVERY )(\s*([^\s]+)){0,2}/gs)[0].replaceAll('\'', '').split(' ') : []; - const starts = execution === 'EVERY' && schedule.includes('STARTS') ? schedule.match(/(?<=STARTS ').*?(?='\s)/gs)[0] : ''; - const ends = execution === 'EVERY' && schedule.includes('ENDS') ? schedule.match(/(?<=ENDS ').*?(?='\s)/gs)[0] : ''; - const at = execution === 'ONCE' && schedule.includes('AT') ? schedule.match(/(?<=AT ').*?(?='\s)/gs)[0] : ''; - - return { - definer: row['Create Event'].match(/(?<=DEFINER=).*?(?=\s)/gs)[0], - sql: row['Create Event'].match(/(?<=DO )(.*)/gs)[0], - name: row.Event, - comment: row['Create Event'].match(/(?<=COMMENT ').*?(?=')/gs) ? row['Create Event'].match(/(?<=COMMENT ').*?(?=')/gs)[0] : '', - state: row['Create Event'].includes('ENABLE') ? 'ENABLE' : row['Create Event'].includes('DISABLE ON SLAVE') ? 'DISABLE ON SLAVE' : 'DISABLE', - preserve: row['Create Event'].includes('ON COMPLETION PRESERVE'), - execution, - every, - starts, - ends, - at - }; - })[0]; - } - - /** - * DROP EVENT - * - * @returns {Promise} - * @memberof MySQLClient - */ - async dropEvent (params) { - const sql = `DROP EVENT \`${params.schema}\`.\`${params.scheduler}\``; - return await this.raw(sql); - } - - /** - * ALTER EVENT - * - * @returns {Promise} - * @memberof MySQLClient - */ - async alterEvent (params) { - const { scheduler } = params; - - if (scheduler.execution === 'EVERY' && scheduler.every[0].includes('-')) - scheduler.every[0] = `'${scheduler.every[0]}'`; - - const sql = `ALTER ${scheduler.definer ? ` DEFINER=${scheduler.definer}` : ''} EVENT \`${scheduler.schema}\`.\`${scheduler.oldName}\` - ON SCHEDULE - ${scheduler.execution === 'EVERY' - ? `EVERY ${scheduler.every.join(' ')}${scheduler.starts ? ` STARTS '${scheduler.starts}'` : ''}${scheduler.ends ? ` ENDS '${scheduler.ends}'` : ''}` - : `AT '${scheduler.at}'`} - ON COMPLETION${!scheduler.preserve ? ' NOT' : ''} PRESERVE - ${scheduler.name !== scheduler.oldName ? `RENAME TO \`${scheduler.schema}\`.\`${scheduler.name}\`` : ''} - ${scheduler.state} - COMMENT '${scheduler.comment}' - DO ${scheduler.sql}`; - - return await this.raw(sql, { split: false }); - } - - /** - * CREATE EVENT - * - * @returns {Promise} - * @memberof MySQLClient - */ - async createEvent (params) { - const sql = `CREATE ${params.definer ? ` DEFINER=${params.definer}` : ''} EVENT \`${params.schema}\`.\`${params.name}\` - ON SCHEDULE - ${params.execution === 'EVERY' - ? `EVERY ${params.every.join(' ')}${params.starts ? ` STARTS '${params.starts}'` : ''}${params.ends ? ` ENDS '${params.ends}'` : ''}` - : `AT '${params.at}'`} - ON COMPLETION${!params.preserve ? ' NOT' : ''} PRESERVE - ${params.state} - COMMENT '${params.comment}' - DO ${params.sql}`; - - return await this.raw(sql, { split: false }); - } - - async enableEvent ({ schema, scheduler }) { - const sql = `ALTER EVENT \`${schema}\`.\`${scheduler}\` ENABLE`; - return await this.raw(sql, { split: false }); - } - - async disableEvent ({ schema, scheduler }) { - const sql = `ALTER EVENT \`${schema}\`.\`${scheduler}\` DISABLE`; - return await this.raw(sql, { split: false }); - } - - /** - * SHOW COLLATION - * - * @returns {Array.} collations list - * @memberof MySQLClient - */ - async getCollations () { - const results = await this.raw('SHOW COLLATION'); - - return results.rows.map(row => { - return { - charset: row.Charset, - collation: row.Collation, - compiled: row.Compiled.includes('Yes'), - default: row.Default.includes('Yes'), - id: row.Id, - sortLen: row.Sortlen - }; - }); - } - - /** - * SHOW VARIABLES - * - * @returns {Array.} variables list - * @memberof MySQLClient - */ - async getVariables () { - const sql = 'SHOW VARIABLES'; - const results = await this.raw(sql); - - return results.rows.map(row => { - return { - name: row.Variable_name, - value: row.Value - }; - }); - } - - /** - * SHOW VARIABLES LIKE %variable% - * - * @param {String} variable - * @param {'global'|'session'|null} level - * @returns {Object} variable - * @memberof MySQLClient - */ - async getVariable (variable, level) { - const sql = `SHOW${level ? ' ' + level.toUpperCase() : ''} VARIABLES LIKE '%${variable}%'`; - const results = await this.raw(sql); - - if (results.rows.length) { - return { - name: results.rows[0].Variable_name, - value: results.rows[0].Value - }; - } - } - - /** - * SHOW ENGINES - * - * @returns {Array.} engines list - * @memberof MySQLClient - */ - async getEngines () { - const sql = 'SHOW ENGINES'; - const results = await this.raw(sql); - - return results.rows.map(row => { - return { - name: row.Engine, - support: row.Support, - comment: row.Comment, - transactions: row.Transactions, - xa: row.XA, - savepoints: row.Savepoints, - isDefault: row.Support.includes('DEFAULT') - }; - }); - } - - /** - * SHOW VARIABLES LIKE '%vers%' - * - * @returns {Array.} version parameters - * @memberof MySQLClient - */ - async getVersion () { - const sql = 'SHOW VARIABLES LIKE "%vers%"'; - const { rows } = await this.raw(sql); - - return rows.reduce((acc, curr) => { - switch (curr.Variable_name) { - case 'version': - acc.number = curr.Value.split('-')[0]; - break; - case 'version_comment': - acc.name = curr.Value.replace('(GPL)', ''); - break; - case 'version_compile_machine': - acc.arch = curr.Value; - break; - case 'version_compile_os': - acc.os = curr.Value; - break; - } - return acc; - }, {}); - } - - async getProcesses () { - const sql = 'SELECT `ID`, `USER`, `HOST`, `DB`, `COMMAND`, `TIME`, `STATE`, LEFT(`INFO`, 51200) AS `INFO` FROM `information_schema`.`PROCESSLIST`'; - - const { rows } = await this.raw(sql); - - return rows.map(row => { - return { - id: row.ID, - user: row.USER, - host: row.HOST, - db: row.DB, - command: row.COMMAND, - time: row.TIME, - state: row.STATE, - info: row.INFO - }; - }); - } - - /** - * - * @param {number} id - * @returns {Promise} - */ - async killProcess (id) { - return await this.raw(`KILL ${id}`); - } - - /** - * - * @param {string} tabUid - * @returns {Promise} - */ - async killTabQuery (tabUid) { - const id = this._runningConnections.get(tabUid); - if (id) - return await this.killProcess(id); - } - - /** - * - * @param {string} tabUid - * @returns {Promise} - */ - async commitTab (tabUid) { - const connection = this._connectionsToCommit.get(tabUid); - if (connection) - return await connection.query('COMMIT'); - } - - /** - * - * @param {string} tabUid - * @returns {Promise} - */ - async rollbackTab (tabUid) { - const connection = this._connectionsToCommit.get(tabUid); - if (connection) - return await connection.query('ROLLBACK'); - } - - destroyConnectionToCommit (tabUid) { - const connection = this._connectionsToCommit.get(tabUid); - if (connection) { - connection.destroy(); - this._connectionsToCommit.delete(tabUid); - } - } - - /** - * CREATE TABLE - * - * @returns {Promise} - * @memberof MySQLClient - */ - async createTable (params) { + async createTable (params: antares.CreateTableParams) { const { schema, fields, @@ -1375,9 +704,9 @@ export class MySQLClient extends AntaresCore { indexes, options } = params; - const newColumns = []; - const newIndexes = []; - const newForeigns = []; + const newColumns: string[] = []; + const newIndexes: string[] = []; + const newForeigns: string[] = []; let sql = `CREATE TABLE \`${schema}\`.\`${options.name}\``; @@ -1423,13 +752,7 @@ export class MySQLClient extends AntaresCore { return await this.raw(sql); } - /** - * ALTER TABLE - * - * @returns {Promise} - * @memberof MySQLClient - */ - async alterTable (params) { + async alterTable (params: antares.AlterTableParams) { const { table, schema, @@ -1558,43 +881,554 @@ export class MySQLClient extends AntaresCore { return await this.raw(sql); } - /** - * DUPLICATE TABLE - * - * @returns {Promise} - * @memberof MySQLClient - */ - async duplicateTable (params) { + async duplicateTable (params: { schema: string; table: string }) { const sql = `CREATE TABLE \`${params.schema}\`.\`${params.table}_copy\` LIKE \`${params.schema}\`.\`${params.table}\``; return await this.raw(sql); } - /** - * TRUNCATE TABLE - * - * @returns {Promise} - * @memberof MySQLClient - */ - async truncateTable (params) { + async truncateTable (params: { schema: string; table: string }) { const sql = `TRUNCATE TABLE \`${params.schema}\`.\`${params.table}\``; return await this.raw(sql); } - /** - * DROP TABLE - * - * @returns {Promise} - * @memberof MySQLClient - */ - async dropTable (params) { + async dropTable (params: { schema: string; table: string }) { const sql = `DROP TABLE \`${params.schema}\`.\`${params.table}\``; return await this.raw(sql); } + async getViewInformations ({ schema, view }: { schema: string; view: string }) { + const sql = `SHOW CREATE VIEW \`${schema}\`.\`${view}\``; + const results = await this.raw(sql); + + return results.rows.map(row => { + return { + algorithm: row['Create View'].match(/(?<=CREATE ALGORITHM=).*?(?=\s)/gs)[0], + definer: row['Create View'].match(/(?<=DEFINER=).*?(?=\s)/gs)[0], + security: row['Create View'].match(/(?<=SQL SECURITY ).*?(?=\s)/gs)[0], + updateOption: row['Create View'].match(/(?<=WITH ).*?(?=\s)/gs) ? row['Create View'].match(/(?<=WITH ).*?(?=\s)/gs)[0] : '', + sql: row['Create View'].match(/(?<=AS ).*?$/gs)[0], + name: row.View + }; + })[0]; + } + + async dropView (params: { schema: string; view: string }) { + const sql = `DROP VIEW \`${params.schema}\`.\`${params.view}\``; + return await this.raw(sql); + } + + async alterView (params: antares.AlterViewParams) { + let sql = ` + USE \`${params.schema}\`; + ALTER ALGORITHM = ${params.algorithm}${params.definer ? ` DEFINER=${params.definer}` : ''} + SQL SECURITY ${params.security} + params \`${params.schema}\`.\`${params.oldName}\` AS ${params.sql} ${params.updateOption ? `WITH ${params.updateOption} CHECK OPTION` : ''} + `; + + if (params.name !== params.oldName) + sql += `; RENAME TABLE \`${params.schema}\`.\`${params.oldName}\` TO \`${params.schema}\`.\`${params.name}\``; + + return await this.raw(sql); + } + + async createView (params: antares.CreateViewParams) { + const sql = `CREATE ALGORITHM = ${params.algorithm} ${params.definer ? `DEFINER=${params.definer} ` : ''}SQL SECURITY ${params.security} VIEW \`${params.schema}\`.\`${params.name}\` AS ${params.sql} ${params.updateOption ? `WITH ${params.updateOption} CHECK OPTION` : ''}`; + return await this.raw(sql); + } + + async getTriggerInformations ({ schema, trigger }: { schema: string; trigger: string }) { + const sql = `SHOW CREATE TRIGGER \`${schema}\`.\`${trigger}\``; + const results = await this.raw(sql); + + return results.rows.map(row => { + return { + definer: row['SQL Original Statement'].match(/(?<=DEFINER=).*?(?=\s)/gs)[0], + sql: row['SQL Original Statement'].match(/(BEGIN|begin)(.*)(END|end)/gs)[0], + name: row.Trigger, + table: row['SQL Original Statement'].match(/(?<=ON `).*?(?=`)/gs)[0], + activation: row['SQL Original Statement'].match(/(BEFORE|AFTER)/gs)[0], + event: row['SQL Original Statement'].match(/(INSERT|UPDATE|DELETE)/gs)[0] + }; + })[0]; + } + + async dropTrigger (params: { schema: string; trigger: string }) { + const sql = `DROP TRIGGER \`${params.schema}\`.\`${params.trigger}\``; + return await this.raw(sql); + } + + async alterTrigger ({ trigger } : {trigger: antares.AlterTriggerParams}) { + const tempTrigger = Object.assign({}, trigger); + tempTrigger.name = `Antares_${tempTrigger.name}_tmp`; + + try { + await this.createTrigger(tempTrigger); + await this.dropTrigger({ schema: trigger.schema, trigger: tempTrigger.name }); + await this.dropTrigger({ schema: trigger.schema, trigger: trigger.oldName }); + await this.createTrigger(trigger); + } + catch (err) { + return Promise.reject(err); + } + } + + async createTrigger (params: antares.CreateTriggerParams) { + const sql = `CREATE ${params.definer ? `DEFINER=${params.definer} ` : ''}TRIGGER \`${params.schema}\`.\`${params.name}\` ${params.activation} ${params.event} ON \`${params.table}\` FOR EACH ROW ${params.sql}`; + return await this.raw(sql, { split: false }); + } + + async getRoutineInformations ({ schema, routine }: { schema: string; routine: string }) { + interface CreateProcedureResult { + 'Create Procedure'?: string; + Procedure: string; + } + + interface ProcedureParamsResult { + PARAMETER_NAME: string; + DATA_TYPE: string; + NUMERIC_PRECISION: string; + DATETIME_PRECISION: string; + CHARACTER_MAXIMUM_LENGTH: string; + PARAMETER_MODE: string; + } + + const results = await this.raw>(`SHOW CREATE PROCEDURE \`${schema}\`.\`${routine}\``); + + return results.rows.map(async row => { + if (!row['Create Procedure']) { + return { + definer: null, + sql: '', + parameters: [], + name: row.Procedure, + comment: '', + security: 'DEFINER', + deterministic: false, + dataAccess: 'CONTAINS SQL' + }; + } + + 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: row.PARAMETER_NAME, + type: row.DATA_TYPE.toUpperCase(), + length: row.NUMERIC_PRECISION || row.DATETIME_PRECISION || row.CHARACTER_MAXIMUM_LENGTH || '', + context: row.PARAMETER_MODE + }; + }); + + 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], + 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 + }; + })[0]; + } + + async dropRoutine (params: { schema: string; routine: string }) { + const sql = `DROP PROCEDURE \`${params.schema}\`.\`${params.routine}\``; + return await this.raw(sql); + } + + async alterRoutine ({ routine }: {routine: antares.AlterRoutineParams}) { + const tempProcedure = Object.assign({}, routine); + tempProcedure.name = `Antares_${tempProcedure.name}_tmp`; + + try { + await this.createRoutine(tempProcedure); + await this.dropRoutine({ schema: routine.schema, routine: tempProcedure.name }); + await this.dropRoutine({ schema: routine.schema, routine: routine.oldName }); + await this.createRoutine(routine); + } + catch (err) { + return Promise.reject(err); + } + } + + async createRoutine (params: antares.CreateRoutineParams) { + const parameters = 'parameters' in params + ? params.parameters.reduce((acc: string[], curr) => { + acc.push(`${curr.context} \`${curr.name}\` ${curr.type}${curr.length ? `(${curr.length})` : ''}`); + return acc; + }, []).join(',') + : ''; + + const sql = `CREATE ${params.definer ? `DEFINER=${params.definer} ` : ''}PROCEDURE \`${params.schema}\`.\`${params.name}\`(${parameters}) + LANGUAGE SQL + ${params.deterministic ? 'DETERMINISTIC' : 'NOT DETERMINISTIC'} + ${params.dataAccess} + SQL SECURITY ${params.security} + COMMENT '${params.comment}' + ${params.sql}`; + + return await this.raw(sql, { split: false }); + } + + async getFunctionInformations ({ schema, func }: { schema: string; func: string }) { + interface CreateFunctionResult { + 'Create Function'?: string; + Function: string; + } + + interface FunctionParamsResult { + PARAMETER_NAME: string; + DATA_TYPE: string; + NUMERIC_PRECISION: string; + DATETIME_PRECISION: string; + CHARACTER_MAXIMUM_LENGTH: string; + PARAMETER_MODE: string; + } + + const results = await this.raw>(`SHOW CREATE FUNCTION \`${schema}\`.\`${func}\``); + + return results.rows.map(async row => { + if (!row['Create Function']) { + return { + definer: null, + sql: '', + parameters: [], + name: row.Function, + comment: '', + security: 'DEFINER', + deterministic: false, + dataAccess: 'CONTAINS SQL', + returns: 'INT', + returnsLength: 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: row.PARAMETER_NAME, + type: row.DATA_TYPE.toUpperCase(), + length: row.NUMERIC_PRECISION || row.DATETIME_PRECISION || row.CHARACTER_MAXIMUM_LENGTH || '', + context: row.PARAMETER_MODE + }; + }); + + 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], + 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] + }; + })[0]; + } + + async dropFunction (params: { schema: string; func: string }) { + const sql = `DROP FUNCTION \`${params.schema}\`.\`${params.func}\``; + return await this.raw(sql); + } + + async alterFunction ({ func }: { func: antares.AlterFunctionParams }) { + const tempProcedure = Object.assign({}, func); + tempProcedure.name = `Antares_${tempProcedure.name}_tmp`; + + try { + await this.createFunction(tempProcedure); + await this.dropFunction({ schema: func.schema, func: tempProcedure.name }); + await this.dropFunction({ schema: func.schema, func: func.oldName }); + await this.createFunction(func); + } + catch (err) { + return Promise.reject(err); + } + } + + async createFunction (params: antares.CreateFunctionParams) { + const parameters = 'parameters' in params + ? params.parameters.reduce((acc: string[], curr) => { + acc.push(`\`${curr.name}\` ${curr.type}${curr.length ? `(${curr.length})` : ''}`); + return acc; + }, []).join(',') + : ''; + + const body = params.returns ? params.sql : 'BEGIN\n RETURN 0;\nEND'; + + const sql = `CREATE ${params.definer ? `DEFINER=${params.definer} ` : ''}FUNCTION \`${params.schema}\`.\`${params.name}\`(${parameters}) RETURNS ${params.returns || 'SMALLINT'}${params.returnsLength ? `(${params.returnsLength})` : ''} + LANGUAGE SQL + ${params.deterministic ? 'DETERMINISTIC' : 'NOT DETERMINISTIC'} + ${params.dataAccess} + SQL SECURITY ${params.security} + COMMENT '${params.comment}' + ${body}`; + + return await this.raw(sql, { split: false }); + } + + async getEventInformations ({ schema, scheduler }: { schema: string; scheduler: string }) { + interface CreateFunctionResult { + 'Create Event'?: string; + Event: string; + } + + const results = await this.raw>(`SHOW CREATE EVENT \`${schema}\`.\`${scheduler}\``); + + return results.rows.map(row => { + const schedule = row['Create Event']; + const execution = schedule.includes('EVERY') ? 'EVERY' : 'ONCE'; + const every = execution === 'EVERY' ? row['Create Event'].match(/(?<=EVERY )(\s*([^\s]+)){0,2}/gs)[0].replaceAll('\'', '').split(' ') : []; + const starts = execution === 'EVERY' && schedule.includes('STARTS') ? schedule.match(/(?<=STARTS ').*?(?='\s)/gs)[0] : ''; + const ends = execution === 'EVERY' && schedule.includes('ENDS') ? schedule.match(/(?<=ENDS ').*?(?='\s)/gs)[0] : ''; + const at = execution === 'ONCE' && schedule.includes('AT') ? schedule.match(/(?<=AT ').*?(?='\s)/gs)[0] : ''; + + return { + definer: row['Create Event'].match(/(?<=DEFINER=).*?(?=\s)/gs)[0], + sql: row['Create Event'].match(/(?<=DO )(.*)/gs)[0], + name: row.Event, + comment: row['Create Event'].match(/(?<=COMMENT ').*?(?=')/gs) ? row['Create Event'].match(/(?<=COMMENT ').*?(?=')/gs)[0] : '', + state: row['Create Event'].includes('ENABLE') ? 'ENABLE' : row['Create Event'].includes('DISABLE ON SLAVE') ? 'DISABLE ON SLAVE' : 'DISABLE', + preserve: row['Create Event'].includes('ON COMPLETION PRESERVE'), + execution, + every, + starts, + ends, + at + }; + })[0]; + } + + async dropEvent (params: { schema: string; scheduler: string }) { + const sql = `DROP EVENT \`${params.schema}\`.\`${params.scheduler}\``; + return await this.raw(sql); + } + + async alterEvent ({ scheduler }: { scheduler: antares.AlterEventParams }) { + if (scheduler.execution === 'EVERY' && scheduler.every[0].includes('-')) + scheduler.every[0] = `'${scheduler.every[0]}'`; + + const sql = `ALTER ${scheduler.definer ? ` DEFINER=${scheduler.definer}` : ''} EVENT \`${scheduler.schema}\`.\`${scheduler.oldName}\` + ON SCHEDULE + ${scheduler.execution === 'EVERY' + ? `EVERY ${scheduler.every.join(' ')}${scheduler.starts ? ` STARTS '${scheduler.starts}'` : ''}${scheduler.ends ? ` ENDS '${scheduler.ends}'` : ''}` + : `AT '${scheduler.at}'`} + ON COMPLETION${!scheduler.preserve ? ' NOT' : ''} PRESERVE + ${scheduler.name !== scheduler.oldName ? `RENAME TO \`${scheduler.schema}\`.\`${scheduler.name}\`` : ''} + ${scheduler.state} + COMMENT '${scheduler.comment}' + DO ${scheduler.sql}`; + + return await this.raw(sql, { split: false }); + } + + async createEvent (params: antares.CreateEventParams) { + const sql = `CREATE ${params.definer ? ` DEFINER=${params.definer}` : ''} EVENT \`${params.schema}\`.\`${params.name}\` + ON SCHEDULE + ${params.execution === 'EVERY' + ? `EVERY ${params.every.join(' ')}${params.starts ? ` STARTS '${params.starts}'` : ''}${params.ends ? ` ENDS '${params.ends}'` : ''}` + : `AT '${params.at}'`} + ON COMPLETION${!params.preserve ? ' NOT' : ''} PRESERVE + ${params.state} + COMMENT '${params.comment}' + DO ${params.sql}`; + + return await this.raw(sql, { split: false }); + } + + async enableEvent ({ schema, scheduler }: { schema: string; scheduler: string }) { + const sql = `ALTER EVENT \`${schema}\`.\`${scheduler}\` ENABLE`; + return await this.raw(sql, { split: false }); + } + + async disableEvent ({ schema, scheduler }: { schema: string; scheduler: string }) { + const sql = `ALTER EVENT \`${schema}\`.\`${scheduler}\` DISABLE`; + return await this.raw(sql, { split: false }); + } + + async getCollations () { + interface ShowCollationResult { + Charset: string; + Collation: string; + Compiled: string; + Default: string; + Id: number; + Sortlen: number; + } + + const { rows } = await this.raw>('SHOW COLLATION'); + + return rows.map(row => { + return { + charset: row.Charset, + collation: row.Collation, + compiled: row.Compiled.includes('Yes'), + default: row.Default.includes('Yes'), + id: row.Id, + sortLen: row.Sortlen + }; + }); + } + + async getVariables () { + interface ShowVariablesResult { + // eslint-disable-next-line camelcase + Variable_name: string; + Value: string; + } + + const { rows } = await this.raw>('SHOW VARIABLES'); + + return rows.map(row => { + return { + name: row.Variable_name, + value: row.Value + }; + }); + } + + async getVariable (variable: string, level?: 'global' | 'session') { + const sql = `SHOW${level ? ' ' + level.toUpperCase() : ''} VARIABLES LIKE '%${variable}%'`; + const { rows } = await this.raw(sql); + + if (rows.length) { + return { + name: rows[0].Variable_name, + value: rows[0].Value + }; + } + } + + async getEngines () { + const sql = 'SHOW ENGINES'; + const { rows } = await this.raw(sql); + + return rows.map(row => { + return { + name: row.Engine, + support: row.Support, + comment: row.Comment, + transactions: row.Transactions, + xa: row.XA, + savepoints: row.Savepoints, + isDefault: row.Support.includes('DEFAULT') + }; + }); + } + + async getVersion () { + const sql = 'SHOW VARIABLES LIKE "%vers%"'; + const { rows } = await this.raw(sql); + + return rows.reduce((acc, curr) => { + switch (curr.Variable_name) { + case 'version': + acc.number = curr.Value.split('-')[0]; + break; + case 'version_comment': + acc.name = curr.Value.replace('(GPL)', ''); + break; + case 'version_compile_machine': + acc.arch = curr.Value; + break; + case 'version_compile_os': + acc.os = curr.Value; + break; + } + return acc; + }, {}); + } + + async getProcesses () { + const sql = 'SELECT `ID`, `USER`, `HOST`, `DB`, `COMMAND`, `TIME`, `STATE`, LEFT(`INFO`, 51200) AS `INFO` FROM `information_schema`.`PROCESSLIST`'; + + const { rows } = await this.raw(sql); + + return rows.map(row => { + return { + id: row.ID, + user: row.USER, + host: row.HOST, + db: row.DB, + command: row.COMMAND, + time: row.TIME, + state: row.STATE, + info: row.INFO + }; + }); + } + + async killProcess (id: number) { + return await this.raw(`KILL ${id}`); + } + + async killTabQuery (tabUid: string) { + const id = this._runningConnections.get(tabUid); + if (id) + return await this.killProcess(id); + } + + async commitTab (tabUid: string) { + const connection = this._connectionsToCommit.get(tabUid); + if (connection) + await connection.query('COMMIT'); + } + /** - * @returns {String} SQL string - * @memberof MySQLClient + * + * @param {string} tabUid + * @returns {Promise} */ + async rollbackTab (tabUid: string) { + const connection = this._connectionsToCommit.get(tabUid); + if (connection) + await connection.query('ROLLBACK'); + } + + destroyConnectionToCommit (tabUid: string) { + const connection = this._connectionsToCommit.get(tabUid); + if (connection) { + (connection as mysql.Connection).destroy(); + this._connectionsToCommit.delete(tabUid); + } + } + getSQL () { // SELECT const selectArray = this._query.select.reduce(this._reducer, []); @@ -1648,17 +1482,8 @@ export class MySQLClient extends AntaresCore { return `${selectRaw}${updateRaw ? 'UPDATE' : ''}${insertRaw ? 'INSERT ' : ''}${this._query.delete ? 'DELETE ' : ''}${fromRaw}${updateRaw}${whereRaw}${groupByRaw}${orderByRaw}${limitRaw}${offsetRaw}${insertRaw}`; } - /** - * @param {string} sql raw SQL query - * @param {object} args - * @param {boolean} args.nest - * @param {boolean} args.details - * @param {boolean} args.split - * @returns {Promise} - * @memberof MySQLClient - */ - async raw (sql, args) { - if (process.env.NODE_ENV === 'development') this._logger(sql);// TODO: replace BLOB content with a placeholder + async raw (sql: string, args?: antares.QueryParams) { + if (process.env.NODE_ENV === 'development') this._logger(sql); args = { nest: false, @@ -1673,7 +1498,7 @@ export class MySQLClient extends AntaresCore { sql = sql.replace(/(\/\*(.|[\r\n])*?\*\/)|(--(.*|[\r\n]))/gm, '');// Remove comments const nestTables = args.nest ? '.' : false; - const resultsArr = []; + const resultsArr: antares.QueryResult[] = []; let paramsArr = []; const queries = args.split ? sql.split(/((?:[^;'"]*(?:"(?:\\.|[^"])*"|'(?:\\.|[^'])*')[^;'"]*)+)|;/gm) @@ -1681,8 +1506,8 @@ export class MySQLClient extends AntaresCore { .map(q => q.trim()) : [sql]; - let connection; - const isPool = typeof this._connection.getConnection === 'function'; + let connection: mysql.Connection | mysql.Pool; + const isPool = 'getConnection' in this._connection; if (!args.autocommit && args.tabUid) { // autocommit OFF if (this._connectionsToCommit.has(args.tabUid)) @@ -1694,10 +1519,12 @@ export class MySQLClient extends AntaresCore { } } else// autocommit ON - connection = isPool ? await this._connection.getConnection() : this._connection; + connection = isPool ? await (this._connection as mysql.Pool).getConnection() : this._connection; - if (args.tabUid && isPool) - this._runningConnections.set(args.tabUid, connection.connection.connectionId); + if (args.tabUid && isPool) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + this._runningConnections.set(args.tabUid, (connection as any).connection.connectionId); + } if (args.schema) await connection.query(`USE \`${args.schema}\``); @@ -1705,8 +1532,8 @@ export class MySQLClient extends AntaresCore { for (const query of queries) { if (!query) continue; const timeStart = new Date(); - let timeStop; - let keysArr = []; + let timeStop: Date; + let keysArr: antares.QueryForeign[] = []; const { rows, report, fields, keys, duration } = await new Promise((resolve, reject) => { connection.query({ sql: query, nestTables }).then(async ([response, fields]) => { @@ -1716,7 +1543,7 @@ export class MySQLClient extends AntaresCore { let remappedFields = fields ? fields.map(field => { if (!field || Array.isArray(field)) - return false; + return undefined; const type = this._getType(field); @@ -1724,7 +1551,7 @@ export class MySQLClient extends AntaresCore { name: field.orgName, alias: field.name, orgName: field.orgName, - schema: args.schema || field.schema, + schema: args.schema || (field as mysql.FieldPacket & {schema: string}).schema, table: field.table, tableAlias: field.table, orgTable: field.orgTable, @@ -1735,7 +1562,7 @@ export class MySQLClient extends AntaresCore { : []; if (args.details) { - let cachedTable; + let cachedTable: string; if (remappedFields.length) { paramsArr = remappedFields.map(field => { @@ -1760,7 +1587,8 @@ export class MySQLClient extends AntaresCore { } catch (err) { if (isPool && args.autocommit) { - connection.release(); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (connection as any).release(); this._runningConnections.delete(args.tabUid); } reject(err); @@ -1772,7 +1600,8 @@ export class MySQLClient extends AntaresCore { } catch (err) { if (isPool && args.autocommit) { - connection.release(); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (connection as any).release(); this._runningConnections.delete(args.tabUid); } reject(err); @@ -1782,7 +1611,7 @@ export class MySQLClient extends AntaresCore { } resolve({ - duration: timeStop - timeStart, + duration: timeStop.getTime() - timeStart.getTime(), rows: Array.isArray(queryResult) ? queryResult.some(el => Array.isArray(el)) ? [] : queryResult : false, report: !Array.isArray(queryResult) ? queryResult : false, fields: remappedFields, @@ -1790,21 +1619,31 @@ export class MySQLClient extends AntaresCore { }); }).catch((err) => { if (isPool && args.autocommit) { - connection.release(); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (connection as any).release(); this._runningConnections.delete(args.tabUid); } reject(err); }); }); - resultsArr.push({ rows, report, fields, keys, duration }); + resultsArr.push({ + rows, + report, + fields, + keys, + duration + }); } if (isPool && args.autocommit) { - connection.release(); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (connection as any).release(); this._runningConnections.delete(args.tabUid); } - return resultsArr.length === 1 ? resultsArr[0] : resultsArr; + const result = resultsArr.length === 1 ? resultsArr[0] : resultsArr; + + return result as unknown as T; } } diff --git a/src/main/libs/clients/PostgreSQLClient.js b/src/main/libs/clients/PostgreSQLClient.js index 78e1a34a..68279366 100644 --- a/src/main/libs/clients/PostgreSQLClient.js +++ b/src/main/libs/clients/PostgreSQLClient.js @@ -76,6 +76,8 @@ export class PostgreSQLClient extends AntaresCore { * @memberof PostgreSQLClient */ async getDbConfig () { + this._params.application_name = 'Antares SQL'; + const dbConfig = { host: this._params.host, port: this._params.port, diff --git a/src/main/main.js b/src/main/main.ts similarity index 97% rename from src/main/main.js rename to src/main/main.ts index 87c8ca67..0915e8d7 100644 --- a/src/main/main.js +++ b/src/main/main.ts @@ -1,5 +1,3 @@ -'use strict'; - import { app, BrowserWindow, /* session, */ nativeImage, Menu } from 'electron'; import * as path from 'path'; import Store from 'electron-store'; @@ -8,7 +6,6 @@ import * as remoteMain from '@electron/remote/main'; import ipcHandlers from './ipc-handlers'; -// remoteMain.initialize(); Store.initRenderer(); const isDevelopment = process.env.NODE_ENV !== 'production'; @@ -18,8 +15,8 @@ const gotTheLock = app.requestSingleInstanceLock(); process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = 'true'; // global reference to mainWindow (necessary to prevent window from being garbage collected) -let mainWindow; -let mainWindowState; +let mainWindow: BrowserWindow; +let mainWindowState: windowStateKeeper.State; async function createMainWindow () { const icon = require('../renderer/images/logo-32.png'); @@ -36,7 +33,6 @@ async function createMainWindow () { webPreferences: { nodeIntegration: true, contextIsolation: false, - 'web-security': false, spellcheck: false }, frame: false, @@ -127,7 +123,7 @@ else { } function createAppMenu () { - let menu = null; + let menu: Electron.Menu = null; if (isMacOS) { menu = Menu.buildFromTemplate([ diff --git a/src/renderer/components/WorkspaceEditConnectionPanel.vue b/src/renderer/components/WorkspaceEditConnectionPanel.vue index de8eb97c..27c920da 100644 --- a/src/renderer/components/WorkspaceEditConnectionPanel.vue +++ b/src/renderer/components/WorkspaceEditConnectionPanel.vue @@ -253,14 +253,6 @@ > -
-
-
- -
-
From e3907914f29b4c4f9f51d49003e70aa57af64df3 Mon Sep 17 00:00:00 2001 From: Fabio Di Stasio Date: Tue, 12 Apr 2022 17:59:22 +0200 Subject: [PATCH 05/14] build: fixed module resolution and workers webpack config --- src/common/interfaces/antares.ts | 2 +- src/main/libs/clients/MySQLClient.ts | 4 ++-- src/main/main.ts | 2 +- tsconfig.json | 2 +- webpack.workers.config.js | 7 ++++++- 5 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/common/interfaces/antares.ts b/src/common/interfaces/antares.ts index 9ef4172f..81179f64 100644 --- a/src/common/interfaces/antares.ts +++ b/src/common/interfaces/antares.ts @@ -1,4 +1,4 @@ -import mysql from 'mysql2/promise'; +import * as mysql from 'mysql2/promise'; import * as pg from 'pg'; import SSHConfig from 'ssh2-promise/lib/sshConfig'; import { MySQLClient } from '../../main/libs/clients/MySQLClient'; diff --git a/src/main/libs/clients/MySQLClient.ts b/src/main/libs/clients/MySQLClient.ts index 8332e439..e62bc424 100644 --- a/src/main/libs/clients/MySQLClient.ts +++ b/src/main/libs/clients/MySQLClient.ts @@ -1,7 +1,7 @@ import * as antares from 'common/interfaces/antares'; -import mysql from 'mysql2/promise'; +import * as mysql from 'mysql2/promise'; import { AntaresCore } from '../AntaresCore'; -import dataTypes from 'common/data-types/mysql'; +import * as dataTypes from 'common/data-types/mysql'; import SSH2Promise from 'ssh2-promise'; import SSHConfig from 'ssh2-promise/lib/sshConfig'; diff --git a/src/main/main.ts b/src/main/main.ts index 0915e8d7..563b66c6 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -1,6 +1,6 @@ import { app, BrowserWindow, /* session, */ nativeImage, Menu } from 'electron'; import * as path from 'path'; -import Store from 'electron-store'; +import * as Store from 'electron-store'; import * as windowStateKeeper from 'electron-window-state'; import * as remoteMain from '@electron/remote/main'; diff --git a/tsconfig.json b/tsconfig.json index df9a8d8c..5b3d9663 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,7 +4,7 @@ "baseUrl": "./", "target": "es2021", "allowJs": true, - "moduleResolution": "node12", + "module": "CommonJS", "noImplicitAny": true, "types": [ "node" diff --git a/webpack.workers.config.js b/webpack.workers.config.js index 0a71d2ef..29397557 100644 --- a/webpack.workers.config.js +++ b/webpack.workers.config.js @@ -30,6 +30,11 @@ const config = { externals: externals.filter((d) => !whiteListedModules.includes(d)), module: { rules: [ + { + test: /\.ts$/, + exclude: /node_modules/, + loader: 'ts-loader' + }, { test: /\.js$/, use: 'babel-loader', @@ -42,7 +47,7 @@ const config = { ] }, resolve: { - extensions: ['.js', '.json'], + extensions: ['.js', '.json', '.ts'], alias: { src: path.join(__dirname, 'src/'), common: path.resolve(__dirname, 'src/common') From d85662cb7ddf9bdf30649ea9415e864403622f59 Mon Sep 17 00:00:00 2001 From: Fabio Di Stasio Date: Thu, 14 Apr 2022 09:15:16 +0200 Subject: [PATCH 06/14] refactor: posgtre client ts refactor --- src/common/interfaces/antares.ts | 3 + src/main/libs/clients/MySQLClient.ts | 72 +- ...ostgreSQLClient.js => PostgreSQLClient.ts} | 1471 ++++++++--------- tsconfig.json | 1 + 4 files changed, 704 insertions(+), 843 deletions(-) rename src/main/libs/clients/{PostgreSQLClient.js => PostgreSQLClient.ts} (80%) diff --git a/src/common/interfaces/antares.ts b/src/common/interfaces/antares.ts index 81179f64..98a90764 100644 --- a/src/common/interfaces/antares.ts +++ b/src/common/interfaces/antares.ts @@ -72,6 +72,7 @@ export interface TableField { charset?: string; collation?: string; autoIncrement?: boolean; + isArray?: boolean; onUpdate?: string; comment?: string; after?: string; @@ -189,6 +190,7 @@ export interface CreateRoutineParams { dataAccess: string; security: string; comment?: string; + language?: string; sql: string; } @@ -208,6 +210,7 @@ export interface CreateFunctionParams { sql: string; returns: string; returnsLength: number; + language?: string; } export interface AlterFunctionParams extends CreateFunctionParams { diff --git a/src/main/libs/clients/MySQLClient.ts b/src/main/libs/clients/MySQLClient.ts index e62bc424..20dbc5a4 100644 --- a/src/main/libs/clients/MySQLClient.ts +++ b/src/main/libs/clients/MySQLClient.ts @@ -8,7 +8,7 @@ import SSHConfig from 'ssh2-promise/lib/sshConfig'; export class MySQLClient extends AntaresCore { private _schema?: string; private _runningConnections: Map; - private _connectionsToCommit: Map; + private _connectionsToCommit: Map; protected _connection?: mysql.Connection | mysql.Pool; protected _params: mysql.ConnectionOptions & {schema: string; ssl?: mysql.SslOptions; ssh?: SSHConfig; readonly: boolean}; @@ -564,14 +564,29 @@ export class MySQLClient extends AntaresCore { }); } - async getTableApproximateCount ({ schema, table }: { schema: string; table: string }) { + async getTableApproximateCount ({ schema, table }: { schema: string; table: string }): Promise { const { rows } = await this.raw(`SELECT table_rows "count" FROM information_schema.tables WHERE table_name = "${table}" AND table_schema = "${schema}"`); return rows.length ? rows[0].count : 0; } async getTableOptions ({ schema, table }: { schema: string; table: string }) { - const { rows } = await this.raw(`SHOW TABLE STATUS FROM \`${schema}\` WHERE Name = '${table}'`); + /* eslint-disable camelcase */ + interface TableOptionsResult { + Name: string; + Rows: string; + Create_time: string; + Update_time: string; + Engine: string; + Data_length: number; + Index_length: number; + Auto_increment: string; + Collation: string; + Comment: string; + } + /* eslint-enable camelcase */ + + const { rows } = await this.raw>(`SHOW TABLE STATUS FROM \`${schema}\` WHERE Name = '${table}'`); if (rows.length) { let tableType; @@ -601,7 +616,19 @@ export class MySQLClient extends AntaresCore { } async getTableIndexes ({ schema, table }: { schema: string; table: string }) { - const { rows } = await this.raw(`SHOW INDEXES FROM \`${table}\` FROM \`${schema}\``); + /* eslint-disable camelcase */ + interface ShowIntexesResult { + Non_unique: number; + Column_name: string; + Index_type: string; + Key_name: string; + Cardinality: number; + Comment: string; + Index_comment: string; + } + /* eslint-enable camelcase */ + + const { rows } = await this.raw>(`SHOW INDEXES FROM \`${table}\` FROM \`${schema}\``); return rows.map(row => { return { @@ -676,7 +703,7 @@ export class MySQLClient extends AntaresCore { name: row.user, host: row.host, password: row.password - }; + } as {name: string; host: string; password: string}; }); } @@ -765,7 +792,7 @@ export class MySQLClient extends AntaresCore { } = params; let sql = `ALTER TABLE \`${schema}\`.\`${table}\` `; - const alterColumns = []; + const alterColumns: string[] = []; // OPTIONS if ('comment' in options) alterColumns.push(`COMMENT='${options.comment}'`); @@ -917,16 +944,16 @@ export class MySQLClient extends AntaresCore { return await this.raw(sql); } - async alterView (params: antares.AlterViewParams) { + async alterView ({ view }: { view: antares.AlterViewParams }) { let sql = ` - USE \`${params.schema}\`; - ALTER ALGORITHM = ${params.algorithm}${params.definer ? ` DEFINER=${params.definer}` : ''} - SQL SECURITY ${params.security} - params \`${params.schema}\`.\`${params.oldName}\` AS ${params.sql} ${params.updateOption ? `WITH ${params.updateOption} CHECK OPTION` : ''} + USE \`${view.schema}\`; + ALTER ALGORITHM = ${view.algorithm}${view.definer ? ` DEFINER=${view.definer}` : ''} + SQL SECURITY ${view.security} + params \`${view.schema}\`.\`${view.oldName}\` AS ${view.sql} ${view.updateOption ? `WITH ${view.updateOption} CHECK OPTION` : ''} `; - if (params.name !== params.oldName) - sql += `; RENAME TABLE \`${params.schema}\`.\`${params.oldName}\` TO \`${params.schema}\`.\`${params.name}\``; + if (view.name !== view.oldName) + sql += `; RENAME TABLE \`${view.schema}\`.\`${view.oldName}\` TO \`${view.schema}\`.\`${view.name}\``; return await this.raw(sql); } @@ -1410,11 +1437,6 @@ export class MySQLClient extends AntaresCore { await connection.query('COMMIT'); } - /** - * - * @param {string} tabUid - * @returns {Promise} - */ async rollbackTab (tabUid: string) { const connection = this._connectionsToCommit.get(tabUid); if (connection) @@ -1506,7 +1528,7 @@ export class MySQLClient extends AntaresCore { .map(q => q.trim()) : [sql]; - let connection: mysql.Connection | mysql.Pool; + let connection: mysql.Connection | mysql.Pool | mysql.PoolConnection; const isPool = 'getConnection' in this._connection; if (!args.autocommit && args.tabUid) { // autocommit OFF @@ -1587,8 +1609,7 @@ export class MySQLClient extends AntaresCore { } catch (err) { if (isPool && args.autocommit) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (connection as any).release(); + (connection as mysql.PoolConnection).release(); this._runningConnections.delete(args.tabUid); } reject(err); @@ -1600,8 +1621,7 @@ export class MySQLClient extends AntaresCore { } catch (err) { if (isPool && args.autocommit) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (connection as any).release(); + (connection as mysql.PoolConnection).release(); this._runningConnections.delete(args.tabUid); } reject(err); @@ -1619,8 +1639,7 @@ export class MySQLClient extends AntaresCore { }); }).catch((err) => { if (isPool && args.autocommit) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (connection as any).release(); + (connection as mysql.PoolConnection).release(); this._runningConnections.delete(args.tabUid); } reject(err); @@ -1637,8 +1656,7 @@ export class MySQLClient extends AntaresCore { } if (isPool && args.autocommit) { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - (connection as any).release(); + (connection as mysql.PoolConnection).release(); this._runningConnections.delete(args.tabUid); } diff --git a/src/main/libs/clients/PostgreSQLClient.js b/src/main/libs/clients/PostgreSQLClient.ts similarity index 80% rename from src/main/libs/clients/PostgreSQLClient.js rename to src/main/libs/clients/PostgreSQLClient.ts index 68279366..67e1b076 100644 --- a/src/main/libs/clients/PostgreSQLClient.js +++ b/src/main/libs/clients/PostgreSQLClient.ts @@ -1,44 +1,56 @@ -'use strict'; -import { Pool, Client, types } from 'pg'; -import { parse } from 'pgsql-ast-parser'; +import * as antares from 'common/interfaces/antares'; +import * as mysql from 'mysql2'; +import { builtinsTypes } from 'pg-types'; +import * as pg /* { Pool, Client, types } */ from 'pg'; +import * as pgAst from 'pgsql-ast-parser'; import { AntaresCore } from '../AntaresCore'; -import dataTypes from 'common/data-types/postgresql'; -import * as SSH2Promise from 'ssh2-promise'; +import * as dataTypes from 'common/data-types/postgresql'; +import SSH2Promise from 'ssh2-promise'; +import SSHConfig from 'ssh2-promise/lib/sshConfig'; -function pgToString (value) { +// eslint-disable-next-line @typescript-eslint/no-explicit-any +function pgToString (value: any) { return value.toString(); } -types.setTypeParser(1082, pgToString); // date -types.setTypeParser(1083, pgToString); // time -types.setTypeParser(1114, pgToString); // timestamp -types.setTypeParser(1184, pgToString); // timestamptz -types.setTypeParser(1266, pgToString); // timetz +pg.types.setTypeParser(1082, pgToString); // date +pg.types.setTypeParser(1083, pgToString); // time +pg.types.setTypeParser(1114, pgToString); // timestamp +pg.types.setTypeParser(1184, pgToString); // timestamptz +pg.types.setTypeParser(1266, pgToString); // timetz export class PostgreSQLClient extends AntaresCore { - constructor (args) { + private _schema?: string; + private _runningConnections: Map; + private _connectionsToCommit: Map; + protected _connection?: pg.Client | pg.Pool; + protected _params: pg.ClientConfig & {schema: string; ssl?: mysql.SslOptions; ssh?: SSHConfig; readonly: boolean}; + private types: {[key: string]: string} = {}; + private _arrayTypes: {[key: string]: string} = { + _int2: 'SMALLINT', + _int4: 'INTEGER', + _int8: 'BIGINT', + _float4: 'REAL', + _float8: 'DOUBLE PRECISION', + _char: '"CHAR"', + _varchar: 'CHARACTER VARYING' + } + + constructor (args: antares.ClientParams) { super(args); this._schema = null; this._runningConnections = new Map(); this._connectionsToCommit = new Map(); - this.types = {}; - for (const key in types.builtins) - this.types[types.builtins[key]] = key; - - this._arrayTypes = { - _int2: 'SMALLINT', - _int4: 'INTEGER', - _int8: 'BIGINT', - _float4: 'REAL', - _float8: 'DOUBLE PRECISION', - _char: '"CHAR"', - _varchar: 'CHARACTER VARYING' - }; + for (const key in pg.types.builtins) { + const builtinKey = key as builtinsTypes; + this.types[pg.types.builtins[builtinKey]] = key; + } } - _reducer (acc, curr) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + _reducer (acc: string[], curr: any) { const type = typeof curr; switch (type) { @@ -58,13 +70,13 @@ export class PostgreSQLClient extends AntaresCore { } } - _getTypeInfo (type) { + _getTypeInfo (type: string) { return dataTypes .reduce((acc, group) => [...acc, ...group.types], []) .filter(_type => _type.name === type.toUpperCase())[0]; } - _getArrayType (type) { + _getArrayType (type: string) { if (Object.keys(this._arrayTypes).includes(type)) return this._arrayTypes[type]; return type.replace('_', ''); @@ -82,13 +94,14 @@ export class PostgreSQLClient extends AntaresCore { host: this._params.host, port: this._params.port, user: this._params.user, + database: undefined as string | undefined, password: this._params.password, - ssl: null + ssl: null as mysql.SslOptions }; if (this._params.database?.length) dbConfig.database = this._params.database; - if (this._params.ssl) dbConfig.ssl = { ...this._params.ssl }; + if (this._params.ssl) dbConfig.ssl = this._params.ssl; if (this._params.ssh) { try { @@ -99,7 +112,7 @@ export class PostgreSQLClient extends AntaresCore { remotePort: this._params.port }); - dbConfig.host = this._ssh.config.host; + dbConfig.host = (this._ssh.config as SSHConfig[] & { host: string }).host; dbConfig.port = tunnel.localPort; } catch (err) { @@ -123,7 +136,7 @@ export class PostgreSQLClient extends AntaresCore { async getConnection () { const dbConfig = await this.getDbConfig(); - const client = new Client(dbConfig); + const client = new pg.Client(dbConfig); await client.connect(); const connection = client; @@ -135,7 +148,7 @@ export class PostgreSQLClient extends AntaresCore { async getConnectionPool () { const dbConfig = await this.getDbConfig(); - const pool = new Pool({ ...dbConfig, max: this._poolSize }); + const pool = new pg.Pool({ ...dbConfig, max: this._poolSize }); const connection = pool; if (this._params.readonly) { @@ -147,22 +160,12 @@ export class PostgreSQLClient extends AntaresCore { return connection; } - /** - * @memberof PostgreSQLClient - */ destroy () { this._connection.end(); if (this._ssh) this._ssh.close(); } - /** - * Executes an 'SET search_path TO "${schema}"' query - * - * @param {String} schema - * @param {Object?} connection optional - * @memberof PostgreSQLClient - */ - use (schema, connection) { + use (schema: string, connection?: pg.Client | pg.PoolClient) { this._schema = schema; if (schema) { @@ -175,24 +178,39 @@ export class PostgreSQLClient extends AntaresCore { } } - /** - * @param {Array} schemas list - * @returns {Array.} databases scructure - * @memberof PostgreSQLClient - */ - async getStructure (schemas) { - const { rows: databases } = await this.raw('SELECT schema_name AS database FROM information_schema.schemata ORDER BY schema_name'); + async getStructure (schemas: Set) { + /* eslint-disable camelcase */ + interface ShowTableResult { + Db?: string; + data_length: number; + index_length: number; + table_name: string; + table_type: string; + reltuples: number; + Collation: string; + comment: string; + } + + interface ShowTriggersResult { + Db?: string; + table_name: string; + trigger_name: string; + enabled: boolean; + } + /* eslint-enable camelcase */ + + const { rows: databases } = await this.raw>('SELECT schema_name AS database FROM information_schema.schemata ORDER BY schema_name'); const { rows: functions } = await this.raw('SELECT * FROM information_schema.routines WHERE routine_type = \'FUNCTION\''); const { rows: procedures } = await this.raw('SELECT * FROM information_schema.routines WHERE routine_type = \'PROCEDURE\''); - const tablesArr = []; - const triggersArr = []; + const tablesArr: ShowTableResult[] = []; + const triggersArr: ShowTriggersResult[] = []; let schemaSize = 0; for (const db of databases) { if (!schemas.has(db.database)) continue; - let { rows: tables } = await this.raw(` + let { rows: tables } = await this.raw>(` SELECT *, pg_table_size(QUOTE_IDENT(t.TABLE_SCHEMA) || '.' || QUOTE_IDENT(t.TABLE_NAME))::bigint AS data_length, pg_relation_size(QUOTE_IDENT(t.TABLE_SCHEMA) || '.' || QUOTE_IDENT(t.TABLE_NAME))::bigint AS index_length, @@ -212,7 +230,7 @@ export class PostgreSQLClient extends AntaresCore { tablesArr.push(...tables); } - let { rows: triggers } = await this.raw(` + let { rows: triggers } = await this.raw>(` SELECT pg_class.relname AS table_name, pg_trigger.tgname AS trigger_name, @@ -321,21 +339,33 @@ export class PostgreSQLClient extends AntaresCore { }); } - /** - * @param {Object} params - * @param {String} params.schema - * @param {String} params.table - * @returns {Object} table scructure - * @memberof PostgreSQLClient - */ - async getTableColumns ({ schema, table }, arrayRemap = true) { + async getTableColumns ({ schema, table }: { schema: string; table: string }, arrayRemap = true) { + /* eslint-disable camelcase */ + interface TableColumnsResult { + data_type: string; + udt_name: string; + column_name: string; + table_schema: string; + table_name: string; + numeric_scale: number; + numeric_precision: number; + datetime_precision: number; + character_maximum_length: number; + is_nullable: string; + ordinal_position: number; + column_default: string; + character_set_name: string; + collation_name: string; + } + /* eslint-enable camelcase */ + const { rows } = await this .select('*') .schema('information_schema') .from('columns') .where({ table_schema: `= '${schema}'`, table_name: `= '${table}'` }) .orderBy({ ordinal_position: 'ASC' }) - .run(); + .run(); return rows.map(field => { let type = field.data_type; @@ -369,28 +399,26 @@ export class PostgreSQLClient extends AntaresCore { }); } - /** - * @param {Object} params - * @param {String} params.schema - * @param {String} params.table - * @returns {Object} table row count - * @memberof PostgreSQLClient - */ - async getTableApproximateCount ({ schema, table }) { + async getTableApproximateCount ({ table }: { table: string }): Promise { const { rows } = await this.raw(`SELECT reltuples AS count FROM pg_class WHERE relname = '${table}'`); return rows.length ? rows[0].count : 0; } - /** - * @param {Object} params - * @param {String} params.schema - * @param {String} params.table - * @returns {Object} table options - * @memberof MySQLClient - */ - async getTableOptions ({ schema, table }) { - const { rows } = await this.raw(` + async getTableOptions ({ schema, table }: { schema: string; table: string }) { + /* eslint-disable camelcase */ + interface TableOptionsResult { + table_name: string; + table_type: string; + reltuples: string; + data_length: number; + index_length: number; + Collation: string; + comment: string; + } + /* eslint-enable camelcase */ + + const { rows } = await this.raw>(` SELECT *, pg_table_size(QUOTE_IDENT(t.TABLE_SCHEMA) || '.' || QUOTE_IDENT(t.TABLE_NAME))::bigint AS data_length, pg_relation_size(QUOTE_IDENT(t.TABLE_SCHEMA) || '.' || QUOTE_IDENT(t.TABLE_NAME))::bigint AS index_length, @@ -412,22 +440,23 @@ export class PostgreSQLClient extends AntaresCore { comment: rows[0].comment, engine: '' }; - }; + } return {}; } - /** - * @param {Object} params - * @param {String} params.schema - * @param {String} params.table - * @returns {Object} table indexes - * @memberof PostgreSQLClient - */ - async getTableIndexes ({ schema, table }) { + async getTableIndexes ({ schema, table }: { schema: string; table: string }) { + /* eslint-disable camelcase */ + interface ShowIntexesResult { + constraint_name: string; + column_name: string; + constraint_type: string; + } + /* eslint-enable camelcase */ + if (schema !== 'public') await this.use(schema); - const { rows } = await this.raw(`WITH ndx_list AS ( + const { rows } = await this.raw>(`WITH ndx_list AS ( SELECT pg_index.indexrelid, pg_class.oid FROM pg_index, pg_class WHERE pg_class.relname = '${table}' AND pg_class.oid = pg_index.indrelid), ndx_cols AS ( @@ -446,24 +475,19 @@ export class PostgreSQLClient extends AntaresCore { return { name: row.constraint_name, column: row.column_name, - indexType: null, + indexType: null as null, type: row.constraint_type, - cardinality: null, + cardinality: null as null, comment: '', indexComment: '' }; }); } - /** - * - * @param {Number} id - * @returns {Array} - */ - async getTableByIDs (ids) { + async getTableByIDs (ids: string) { if (!ids) return; - const { rows } = await this.raw(` + const { rows } = await this.raw>(` SELECT relid AS tableid, relname, schemaname FROM pg_statio_all_tables WHERE relid IN (${ids}) UNION SELECT pg_class.oid AS tableid,relname, nspname AS schemaname FROM pg_class JOIN pg_namespace ON pg_namespace.oid = pg_class.relnamespace WHERE pg_class.oid IN (${ids}) @@ -475,18 +499,27 @@ export class PostgreSQLClient extends AntaresCore { schema: curr.schemaname }; return acc; - }, {}); + }, {} as {table: string; schema: string}[]); } - /** - * @param {Object} params - * @param {String} params.schema - * @param {String} params.table - * @returns {Object} table key usage - * @memberof PostgreSQLClient - */ - async getKeyUsage ({ schema, table }) { - const { rows } = await this.raw(` + async getKeyUsage ({ schema, table }: { schema: string; table: string }) { + /* eslint-disable camelcase */ + interface KeyResult { + table_schema: string; + table_name: string; + column_name: string; + ordinal_position: number; + position_in_unique_constraint: number; + constraint_name: string; + foreign_table_schema: string; + foreign_table_name: string; + foreign_column_name: string; + update_rule: string; + delete_rule: string; + } + /* eslint-enable camelcase */ + + const { rows } = await this.raw>(` SELECT tc.table_schema, tc.constraint_name, @@ -529,12 +562,6 @@ export class PostgreSQLClient extends AntaresCore { }); } - /** - * SELECT * FROM pg_catalog.pg_user - * - * @returns {Array.} users list - * @memberof PostgreSQLClient - */ async getUsers () { const { rows } = await this.raw('SELECT * FROM pg_catalog.pg_user'); @@ -543,647 +570,24 @@ export class PostgreSQLClient extends AntaresCore { name: row.username, host: row.host, password: row.passwd - }; + } as {name: string; host: string; password: string}; }); } - /** - * CREATE SCHEMA - * - * @returns {Promise} - * @memberof MySQLClient - */ - async createSchema (params) { + async createSchema (params: {name: string}) { return await this.raw(`CREATE SCHEMA "${params.name}"`); } - /** - * ALTER DATABASE - * - * @returns {Promise} - * @memberof MySQLClient - */ - async alterSchema (params) { - return await this.raw(`ALTER SCHEMA "${params.name}"`); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + async alterSchema (params: {name: string}): Promise { + return null; } - /** - * DROP DATABASE - * - * @returns {Promise} - * @memberof MySQLClient - */ - async dropSchema (params) { + async dropSchema (params: { database: string }) { return await this.raw(`DROP SCHEMA "${params.database}" CASCADE`); } - /** - * SHOW CREATE VIEW - * - * @returns {Array.} view informations - * @memberof PostgreSQLClient - */ - async getViewInformations ({ schema, view }) { - const sql = `SELECT "definition" FROM "pg_views" WHERE "viewname"='${view}' AND "schemaname"='${schema}'`; - const results = await this.raw(sql); - - return results.rows.map(row => { - return { - algorithm: '', - definer: '', - security: '', - updateOption: '', - sql: row.definition, - name: view - }; - })[0]; - } - - /** - * DROP VIEW - * - * @returns {Promise} - * @memberof PostgreSQLClient - */ - async dropView (params) { - const sql = `DROP VIEW "${params.schema}"."${params.view}"`; - return await this.raw(sql); - } - - /** - * ALTER VIEW - * - * @returns {Promise} - * @memberof PostgreSQLClient - */ - async alterView (params) { - const { view } = params; - let sql = `CREATE OR REPLACE VIEW "${view.schema}"."${view.oldName}" AS ${view.sql}`; - - if (view.name !== view.oldName) - sql += `; ALTER VIEW "${view.schema}"."${view.oldName}" RENAME TO "${view.name}"`; - - return await this.raw(sql); - } - - /** - * CREATE VIEW - * - * @returns {Promise} - * @memberof PostgreSQLClient - */ - async createView (params) { - const sql = `CREATE VIEW "${params.schema}"."${params.name}" AS ${params.sql}`; - return await this.raw(sql); - } - - /** - * SHOW CREATE TRIGGER - * - * @returns {Array.} view informations - * @memberof PostgreSQLClient - */ - async getTriggerInformations ({ schema, trigger }) { - const [table, triggerName] = trigger.split('.'); - - const results = await this.raw(` - SELECT - information_schema.triggers.event_object_schema AS table_schema, - information_schema.triggers.event_object_table AS table_name, - information_schema.triggers.trigger_schema, - information_schema.triggers.trigger_name, - string_agg(event_manipulation, ',') AS EVENT, - action_timing AS activation, - action_condition AS condition, - action_statement AS definition, - (pg_trigger.tgenabled != 'D')::bool AS enabled - FROM pg_trigger - JOIN pg_class ON pg_trigger.tgrelid = pg_class.oid - JOIN pg_namespace ON pg_namespace.oid = pg_class.relnamespace - JOIN information_schema.triggers ON pg_namespace.nspname = information_schema.triggers.trigger_schema - AND pg_class.relname = information_schema.triggers.event_object_table - WHERE trigger_schema = '${schema}' - AND trigger_name = '${triggerName}' - AND event_object_table = '${table}' - GROUP BY 1,2,3,4,6,7,8,9 - ORDER BY table_schema, - table_name - `); - - return results.rows.map(row => { - return { - sql: row.definition, - name: row.trigger_name, - table: row.table_name, - event: [...new Set(row.event.split(','))], - activation: row.activation - }; - })[0]; - } - - /** - * DROP TRIGGER - * - * @returns {Promise} - * @memberof PostgreSQLClient - */ - async dropTrigger (params) { - const triggerParts = params.trigger.split('.'); - const sql = `DROP TRIGGER "${triggerParts[1]}" ON "${params.schema}"."${triggerParts[0]}"`; - return await this.raw(sql); - } - - /** - * ALTER TRIGGER - * - * @returns {Promise} - * @memberof PostgreSQLClient - */ - async alterTrigger (params) { - const { trigger } = params; - const tempTrigger = Object.assign({}, trigger); - tempTrigger.name = `Antares_${tempTrigger.name}_tmp`; - - try { - await this.createTrigger(tempTrigger); - await this.dropTrigger({ schema: trigger.schema, trigger: `${tempTrigger.table}.${tempTrigger.name}` }); - await this.dropTrigger({ schema: trigger.schema, trigger: `${trigger.table}.${trigger.oldName}` }); - await this.createTrigger(trigger); - } - catch (err) { - return Promise.reject(err); - } - } - - /** - * CREATE TRIGGER - * - * @returns {Promise} - * @memberof PostgreSQLClient - */ - async createTrigger (params) { - const eventsString = Array.isArray(params.event) ? params.event.join(' OR ') : params.event; - const sql = `CREATE TRIGGER "${params.name}" ${params.activation} ${eventsString} ON "${params.schema}"."${params.table}" FOR EACH ROW ${params.sql}`; - return await this.raw(sql, { split: false }); - } - - async enableTrigger ({ schema, trigger }) { - const [table, triggerName] = trigger.split('.'); - const sql = `ALTER TABLE "${schema}"."${table}" ENABLE TRIGGER "${triggerName}"`; - return await this.raw(sql, { split: false }); - } - - async disableTrigger ({ schema, trigger }) { - const [table, triggerName] = trigger.split('.'); - const sql = `ALTER TABLE "${schema}"."${table}" DISABLE TRIGGER "${triggerName}"`; - return await this.raw(sql, { split: false }); - } - - /** - * SHOW CREATE PROCEDURE - * - * @returns {Array.} view informations - * @memberof PostgreSQLClient - */ - async getRoutineInformations ({ 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(async row => { - if (!row.pg_get_functiondef) { - return { - definer: null, - sql: '', - parameters: [], - name: routine, - comment: '', - security: 'DEFINER', - deterministic: false, - dataAccess: 'CONTAINS SQL' - }; - } - - 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}' - AND args.data_type != NULL - 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: row.parameter_name, - type: row.data_type ? row.data_type.toUpperCase() : '', - length: '', - context: row.parameter_mode - }; - }); - - return { - definer: '', - sql: row.pg_get_functiondef.match(/(\$(.*)\$)(.*)(\$(.*)\$)/gs)[0], - parameters: parameters || [], - 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]; - } - - /** - * DROP PROCEDURE - * - * @returns {Array.} parameters - * @memberof PostgreSQLClient - */ - async dropRoutine (params) { - const sql = `DROP PROCEDURE "${params.schema}"."${params.routine}"`; - return await this.raw(sql); - } - - /** - * ALTER PROCEDURE - * - * @returns {Array.} parameters - * @memberof PostgreSQLClient - */ - async alterRoutine (params) { - const { routine } = params; - const tempProcedure = Object.assign({}, routine); - tempProcedure.name = `Antares_${tempProcedure.name}_tmp`; - - try { - await this.createRoutine(tempProcedure); - await this.dropRoutine({ schema: routine.schema, routine: tempProcedure.name }); - await this.dropRoutine({ schema: routine.schema, routine: routine.oldName }); - await this.createRoutine(routine); - } - catch (err) { - return Promise.reject(err); - } - } - - /** - * CREATE PROCEDURE - * - * @returns {Array.} parameters - * @memberof PostgreSQLClient - */ - async createRoutine (routine) { - const parameters = 'parameters' in routine - ? routine.parameters.reduce((acc, curr) => { - acc.push(`${curr.context} ${curr.name} ${curr.type}`); - return acc; - }, []).join(',') - : ''; - - if (routine.schema !== 'public') - await this.use(routine.schema); - - const sql = `CREATE PROCEDURE "${routine.schema}"."${routine.name}"(${parameters}) - LANGUAGE ${routine.language} - SECURITY ${routine.security} - AS ${routine.sql}`; - - return await this.raw(sql, { split: false }); - } - - /** - * SHOW CREATE FUNCTION - * - * @returns {Array.} view informations - * @memberof PostgreSQLClient - */ - async getFunctionInformations ({ 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(async row => { - if (!row.pg_get_functiondef) { - return { - definer: null, - sql: '', - parameters: [], - name: func, - comment: '', - security: 'DEFINER', - deterministic: false, - dataAccess: 'CONTAINS SQL' - }; - } - - 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: row.parameter_name, - type: row.data_type.toUpperCase(), - length: '', - context: row.parameter_mode - }; - }); - - return { - definer: '', - sql: row.pg_get_functiondef.match(/(\$(.*)\$)(.*)(\$(.*)\$)/gs)[0], - parameters: parameters || [], - 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]; - } - - /** - * DROP FUNCTION - * - * @returns {Array.} parameters - * @memberof PostgreSQLClient - */ - async dropFunction (params) { - const sql = `DROP FUNCTION "${params.schema}"."${params.func}"`; - return await this.raw(sql); - } - - /** - * ALTER FUNCTION - * - * @returns {Array.} parameters - * @memberof PostgreSQLClient - */ - async alterFunction (params) { - const { func } = params; - const tempProcedure = Object.assign({}, func); - tempProcedure.name = `Antares_${tempProcedure.name}_tmp`; - - try { - await this.createFunction(tempProcedure); - await this.dropFunction({ schema: func.schema, func: tempProcedure.name }); - await this.dropFunction({ schema: func.schema, func: func.oldName }); - await this.createFunction(func); - } - catch (err) { - return Promise.reject(err); - } - } - - /** - * CREATE FUNCTION - * - * @returns {Array.} parameters - * @memberof PostgreSQLClient - */ - async createFunction (func) { - const parameters = 'parameters' in func - ? func.parameters.reduce((acc, curr) => { - acc.push(`${curr.context} ${curr.name || ''} ${curr.type}`); - return acc; - }, []).join(',') - : ''; - - if (func.schema !== 'public') - await this.use(func.schema); - - const body = func.returns ? func.sql : '$function$\n$function$'; - - const sql = `CREATE FUNCTION "${func.schema}"."${func.name}" (${parameters}) - RETURNS ${func.returns || 'void'} - LANGUAGE ${func.language} - SECURITY ${func.security} - AS ${body}`; - - return await this.raw(sql, { split: false }); - } - - /** - * ALTER TRIGGER FUNCTION - * - * @returns {Array.} parameters - * @memberof PostgreSQLClient - */ - async alterTriggerFunction (params) { - const { func } = params; - - if (func.schema !== 'public') - await this.use(func.schema); - - const body = func.returns ? func.sql : '$function$\n$function$'; - - const sql = `CREATE OR REPLACE FUNCTION "${func.schema}"."${func.name}" () - RETURNS TRIGGER - LANGUAGE ${func.language} - AS ${body}`; - - return await this.raw(sql, { split: false }); - } - - /** - * CREATE TRIGGER FUNCTION - * - * @returns {Array.} parameters - * @memberof PostgreSQLClient - */ - async createTriggerFunction (func) { - if (func.schema !== 'public') - await this.use(func.schema); - - const body = func.returns ? func.sql : '$function$\r\nBEGIN\r\n\r\nEND\r\n$function$'; - - const sql = `CREATE FUNCTION "${func.schema}"."${func.name}" () - RETURNS TRIGGER - LANGUAGE ${func.language} - AS ${body}`; - - return await this.raw(sql, { split: false }); - } - - /** - * SELECT * FROM pg_collation - * - * @returns {Array.} collations list - * @memberof PostgreSQLClient - */ - async getCollations () { - return []; - } - - /** - * SHOW ALL - * - * @returns {Array.} variables list - * @memberof PostgreSQLClient - */ - async getVariables () { - const sql = 'SHOW ALL'; - const results = await this.raw(sql); - - return results.rows.map(row => { - return { - name: row.name, - value: row.setting - }; - }); - } - - /** - * SHOW ENGINES - * - * @returns {Array.} engines list - * @memberof PostgreSQLClient - */ - async getEngines () { - return { - name: 'PostgreSQL', - support: 'YES', - comment: '', - isDefault: true - }; - } - - /** - * SHOW VARIABLES LIKE '%vers%' - * - * @returns {Array.} version parameters - * @memberof PostgreSQLClient - */ - async getVersion () { - const sql = 'SELECT version()'; - const { rows } = await this.raw(sql); - const infos = rows[0].version.split(','); - - return { - number: infos[0].split(' ')[1], - name: infos[0].split(' ')[0], - arch: infos[1], - os: infos[2] - }; - } - - async getProcesses () { - const sql = 'SELECT "pid", "usename", "client_addr", "datname", application_name , EXTRACT(EPOCH FROM CURRENT_TIMESTAMP - "query_start")::INTEGER, "state", "query" FROM "pg_stat_activity"'; - - const { rows } = await this.raw(sql); - - return rows.map(row => { - return { - id: row.pid, - user: row.usename, - host: row.client_addr, - database: row.datname, - application: row.application_name, - time: row.date_part, - state: row.state, - info: row.query - }; - }); - } - - /** - * - * @param {number} id - * @returns {Promise} - */ - async killProcess (id) { - return await this.raw(`SELECT pg_terminate_backend(${id})`); - } - - /** - * - * @param {string} tabUid - * @returns {Promise} - */ - async killTabQuery (tabUid) { - const id = this._runningConnections.get(tabUid); - if (id) - return await this.raw(`SELECT pg_cancel_backend(${id})`); - } - - /** - * - * @param {string} tabUid - * @returns {Promise} - */ - async commitTab (tabUid) { - const connection = this._connectionsToCommit.get(tabUid); - if (connection) { - await connection.query('COMMIT'); - return this.destroyConnectionToCommit(tabUid); - } - } - - /** - * - * @param {string} tabUid - * @returns {Promise} - */ - async rollbackTab (tabUid) { - const connection = this._connectionsToCommit.get(tabUid); - if (connection) { - await connection.query('ROLLBACK'); - return this.destroyConnectionToCommit(tabUid); - } - } - - destroyConnectionToCommit (tabUid) { - const connection = this._connectionsToCommit.get(tabUid); - if (connection) { - connection.end(); - this._connectionsToCommit.delete(tabUid); - } - } - - /** - * CREATE TABLE - * - * @returns {Promise} - * @memberof PostgreSQLClient - */ - async createTable (params) { + async createTable (params: antares.CreateTableParams) { const { schema, fields, @@ -1191,10 +595,10 @@ export class PostgreSQLClient extends AntaresCore { indexes, options } = params; - const newColumns = []; - const newIndexes = []; - const manageIndexes = []; - const newForeigns = []; + const newColumns: string[] = []; + const newIndexes: string[] = []; + const manageIndexes: string[] = []; + const newForeigns: string[] = []; let sql = `CREATE TABLE "${schema}"."${options.name}"`; @@ -1232,16 +636,11 @@ export class PostgreSQLClient extends AntaresCore { sql = `${sql} (${[...newColumns, ...newIndexes, ...newForeigns].join(', ')})`; if (manageIndexes.length) sql = `${sql}; ${manageIndexes.join(';')}`; + return await this.raw(sql); } - /** - * ALTER TABLE - * - * @returns {Promise} - * @memberof PostgreSQLClient - */ - async alterTable (params) { + async alterTable (params: antares.AlterTableParams) { const { table, schema, @@ -1257,10 +656,10 @@ export class PostgreSQLClient extends AntaresCore { await this.use(schema); let sql = ''; - const alterColumns = []; - const renameColumns = []; - const createSequences = []; - const manageIndexes = []; + const alterColumns: string[] = []; + const renameColumns: string[] = []; + const createSequences: string[] = []; + const manageIndexes: string[] = []; // ADD FIELDS additions.forEach(addition => { @@ -1381,43 +780,486 @@ export class PostgreSQLClient extends AntaresCore { return await this.raw(sql); } - /** - * DUPLICATE TABLE - * - * @returns {Promise} - * @memberof PostgreSQLClient - */ - async duplicateTable (params) { + async duplicateTable (params: { schema: string; table: string }) { const sql = `CREATE TABLE "${params.schema}"."${params.table}_copy" (LIKE "${params.schema}"."${params.table}" INCLUDING ALL)`; return await this.raw(sql); } - /** - * TRUNCATE TABLE - * - * @returns {Promise} - * @memberof PostgreSQLClient - */ - async truncateTable (params) { + async truncateTable (params: { schema: string; table: string }) { const sql = `TRUNCATE TABLE "${params.schema}"."${params.table}"`; return await this.raw(sql); } - /** - * DROP TABLE - * - * @returns {Promise} - * @memberof PostgreSQLClient - */ - async dropTable (params) { + async dropTable (params: { schema: string; table: string }) { const sql = `DROP TABLE "${params.schema}"."${params.table}"`; return await this.raw(sql); } + async getViewInformations ({ schema, view }: { schema: string; view: string }) { + const sql = `SELECT "definition" FROM "pg_views" WHERE "viewname"='${view}' AND "schemaname"='${schema}'`; + const results = await this.raw(sql); + + return results.rows.map(row => { + return { + algorithm: '', + definer: '', + security: '', + updateOption: '', + sql: row.definition, + name: view + }; + })[0]; + } + + async dropView (params: { schema: string; view: string }) { + const sql = `DROP VIEW "${params.schema}"."${params.view}"`; + return await this.raw(sql); + } + + async alterView ({ view }: { view: antares.AlterViewParams }) { + let sql = `CREATE OR REPLACE VIEW "${view.schema}"."${view.oldName}" AS ${view.sql}`; + + if (view.name !== view.oldName) + sql += `; ALTER VIEW "${view.schema}"."${view.oldName}" RENAME TO "${view.name}"`; + + return await this.raw(sql); + } + + async createView (params: antares.CreateViewParams) { + const sql = `CREATE VIEW "${params.schema}"."${params.name}" AS ${params.sql}`; + return await this.raw(sql); + } + + async getTriggerInformations ({ schema, trigger }: { schema: string; trigger: string }) { + const [table, triggerName] = trigger.split('.'); + + const results = await this.raw(` + SELECT + information_schema.triggers.event_object_schema AS table_schema, + information_schema.triggers.event_object_table AS table_name, + information_schema.triggers.trigger_schema, + information_schema.triggers.trigger_name, + string_agg(event_manipulation, ',') AS EVENT, + action_timing AS activation, + action_condition AS condition, + action_statement AS definition, + (pg_trigger.tgenabled != 'D')::bool AS enabled + FROM pg_trigger + JOIN pg_class ON pg_trigger.tgrelid = pg_class.oid + JOIN pg_namespace ON pg_namespace.oid = pg_class.relnamespace + JOIN information_schema.triggers ON pg_namespace.nspname = information_schema.triggers.trigger_schema + AND pg_class.relname = information_schema.triggers.event_object_table + WHERE trigger_schema = '${schema}' + AND trigger_name = '${triggerName}' + AND event_object_table = '${table}' + GROUP BY 1,2,3,4,6,7,8,9 + ORDER BY table_schema, + table_name + `); + + return results.rows.map(row => { + return { + sql: row.definition, + name: row.trigger_name, + table: row.table_name, + event: [...new Set(row.event.split(','))], + activation: row.activation + }; + })[0]; + } + + async dropTrigger (params: { schema: string; trigger: string }) { + const triggerParts = params.trigger.split('.'); + const sql = `DROP TRIGGER "${triggerParts[1]}" ON "${params.schema}"."${triggerParts[0]}"`; + return await this.raw(sql); + } + + async alterTrigger ({ trigger } : {trigger: antares.AlterTriggerParams}) { + const tempTrigger = Object.assign({}, trigger); + tempTrigger.name = `Antares_${tempTrigger.name}_tmp`; + + try { + await this.createTrigger(tempTrigger); + await this.dropTrigger({ schema: trigger.schema, trigger: `${tempTrigger.table}.${tempTrigger.name}` }); + await this.dropTrigger({ schema: trigger.schema, trigger: `${trigger.table}.${trigger.oldName}` }); + await this.createTrigger(trigger); + } + catch (err) { + return Promise.reject(err); + } + } + + async createTrigger (params: antares.CreateTriggerParams) { + const eventsString = Array.isArray(params.event) ? params.event.join(' OR ') : params.event; + const sql = `CREATE TRIGGER "${params.name}" ${params.activation} ${eventsString} ON "${params.schema}"."${params.table}" FOR EACH ROW ${params.sql}`; + return await this.raw(sql, { split: false }); + } + + async enableTrigger ({ schema, trigger }: { schema: string; trigger: string }) { + const [table, triggerName] = trigger.split('.'); + const sql = `ALTER TABLE "${schema}"."${table}" ENABLE TRIGGER "${triggerName}"`; + return await this.raw(sql, { split: false }); + } + + async disableTrigger ({ schema, trigger }: { schema: string; trigger: string }) { + const [table, triggerName] = trigger.split('.'); + const sql = `ALTER TABLE "${schema}"."${table}" DISABLE TRIGGER "${triggerName}"`; + return await this.raw(sql, { split: false }); + } + + async getRoutineInformations ({ schema, routine }: { schema: string; routine: string }) { + const sql = `SELECT pg_get_functiondef((SELECT oid FROM pg_proc WHERE proname = '${routine}'));`; + const results = await this.raw(sql); + + return results.rows.map(async row => { + if (!row.pg_get_functiondef) { + return { + definer: null, + sql: '', + parameters: [], + name: routine, + comment: '', + security: 'DEFINER', + deterministic: false, + dataAccess: 'CONTAINS SQL' + }; + } + + 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}' + AND args.data_type != NULL + 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: row.parameter_name, + type: row.data_type ? row.data_type.toUpperCase() : '', + length: '', + context: row.parameter_mode + }; + }); + + return { + definer: '', + sql: row.pg_get_functiondef.match(/(\$(.*)\$)(.*)(\$(.*)\$)/gs)[0], + parameters: parameters || [], + 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]; + } + + async dropRoutine (params: { schema: string; routine: string }) { + const sql = `DROP PROCEDURE "${params.schema}"."${params.routine}"`; + return await this.raw(sql); + } + + async alterRoutine ({ routine }: {routine: antares.AlterRoutineParams}) { + const tempProcedure = Object.assign({}, routine); + tempProcedure.name = `Antares_${tempProcedure.name}_tmp`; + + try { + await this.createRoutine(tempProcedure); + await this.dropRoutine({ schema: routine.schema, routine: tempProcedure.name }); + await this.dropRoutine({ schema: routine.schema, routine: routine.oldName }); + await this.createRoutine(routine); + } + catch (err) { + return Promise.reject(err); + } + } + + async createRoutine (routine: antares.CreateRoutineParams) { + const parameters = 'parameters' in routine + ? routine.parameters.reduce((acc, curr) => { + acc.push(`${curr.context} ${curr.name} ${curr.type}`); + return acc; + }, []).join(',') + : ''; + + if (routine.schema !== 'public') + await this.use(routine.schema); + + const sql = `CREATE PROCEDURE "${routine.schema}"."${routine.name}"(${parameters}) + LANGUAGE ${routine.language} + SECURITY ${routine.security} + AS ${routine.sql}`; + + return await this.raw(sql, { split: false }); + } + + async getFunctionInformations ({ schema, func }: { schema: string; func: string }) { + /* eslint-disable camelcase */ + interface CreateFunctionResult { + pg_get_functiondef: string; + } + + interface FunctionParamsResult { + parameter_mode: string; + parameter_name: string; + data_type: string; + } + /* eslint-enable camelcase */ + + const sql = `SELECT pg_get_functiondef((SELECT oid FROM pg_proc WHERE proname = '${func}'));`; + const results = await this.raw>(sql); + + return results.rows.map(async row => { + if (!row.pg_get_functiondef) { + return { + definer: null, + sql: '', + parameters: [], + name: func, + comment: '', + security: 'DEFINER', + deterministic: false, + dataAccess: 'CONTAINS SQL' + }; + } + + 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: row.parameter_name, + type: row.data_type.toUpperCase(), + length: '', + context: row.parameter_mode + }; + }); + + return { + definer: '', + sql: row.pg_get_functiondef.match(/(\$(.*)\$)(.*)(\$(.*)\$)/gs)[0], + parameters: parameters || [], + 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]; + } + + async dropFunction (params: { schema: string; func: string }) { + const sql = `DROP FUNCTION "${params.schema}"."${params.func}"`; + return await this.raw(sql); + } + /** - * @returns {String} SQL string + * ALTER FUNCTION + * + * @returns {Array.} parameters * @memberof PostgreSQLClient */ + async alterFunction ({ func }: { func: antares.AlterFunctionParams }) { + const tempProcedure = Object.assign({}, func); + tempProcedure.name = `Antares_${tempProcedure.name}_tmp`; + + try { + await this.createFunction(tempProcedure); + await this.dropFunction({ schema: func.schema, func: tempProcedure.name }); + await this.dropFunction({ schema: func.schema, func: func.oldName }); + await this.createFunction(func); + } + catch (err) { + return Promise.reject(err); + } + } + + async createFunction (func: antares.CreateFunctionParams) { + const parameters = 'parameters' in func + ? func.parameters.reduce((acc, curr) => { + acc.push(`${curr.context} ${curr.name || ''} ${curr.type}`); + return acc; + }, []).join(',') + : ''; + + if (func.schema !== 'public') + await this.use(func.schema); + + const body = func.returns ? func.sql : '$function$\n$function$'; + + const sql = `CREATE FUNCTION "${func.schema}"."${func.name}" (${parameters}) + RETURNS ${func.returns || 'void'} + LANGUAGE ${func.language} + SECURITY ${func.security} + AS ${body}`; + + return await this.raw(sql, { split: false }); + } + + async alterTriggerFunction ({ func }: { func: antares.CreateFunctionParams}) { + if (func.schema !== 'public') + await this.use(func.schema); + + const body = func.returns ? func.sql : '$function$\n$function$'; + + const sql = `CREATE OR REPLACE FUNCTION "${func.schema}"."${func.name}" () + RETURNS TRIGGER + LANGUAGE ${func.language} + AS ${body}`; + + return await this.raw(sql, { split: false }); + } + + async createTriggerFunction (func: antares.CreateFunctionParams) { + if (func.schema !== 'public') + await this.use(func.schema); + + const body = func.returns ? func.sql : '$function$\r\nBEGIN\r\n\r\nEND\r\n$function$'; + + const sql = `CREATE FUNCTION "${func.schema}"."${func.name}" () + RETURNS TRIGGER + LANGUAGE ${func.language} + AS ${body}`; + + return await this.raw(sql, { split: false }); + } + + async getCollations (): Promise { + return []; + } + + async getVariables () { + interface ShowVariablesResult { + name: string; + setting: string; + } + + const sql = 'SHOW ALL'; + const results = await this.raw>(sql); + + return results.rows.map(row => { + return { + name: row.name, + value: row.setting + }; + }); + } + + async getEngines () { + return { + name: 'PostgreSQL', + support: 'YES', + comment: '', + isDefault: true + }; + } + + async getVersion () { + const sql = 'SELECT version()'; + const { rows } = await this.raw(sql); + const infos = rows[0].version.split(','); + + return { + number: infos[0].split(' ')[1], + name: infos[0].split(' ')[0], + arch: infos[1], + os: infos[2] + }; + } + + async getProcesses () { + const sql = 'SELECT "pid", "usename", "client_addr", "datname", application_name , EXTRACT(EPOCH FROM CURRENT_TIMESTAMP - "query_start")::INTEGER, "state", "query" FROM "pg_stat_activity"'; + + const { rows } = await this.raw(sql); + + return rows.map(row => { + return { + id: row.pid, + user: row.usename, + host: row.client_addr, + database: row.datname, + application: row.application_name, + time: row.date_part, + state: row.state, + info: row.query + }; + }); + } + + async killProcess (id: number) { + return await this.raw(`SELECT pg_terminate_backend(${id})`); + } + + async killTabQuery (tabUid: string) { + const id = this._runningConnections.get(tabUid); + if (id) + return await this.raw(`SELECT pg_cancel_backend(${id})`); + } + + async commitTab (tabUid: string) { + const connection = this._connectionsToCommit.get(tabUid); + if (connection) { + await connection.query('COMMIT'); + return this.destroyConnectionToCommit(tabUid); + } + } + + async rollbackTab (tabUid: string) { + const connection = this._connectionsToCommit.get(tabUid); + if (connection) { + await connection.query('ROLLBACK'); + return this.destroyConnectionToCommit(tabUid); + } + } + + destroyConnectionToCommit (tabUid: string) { + const connection = this._connectionsToCommit.get(tabUid); + if (connection) { + (connection as pg.Client).end(); + this._connectionsToCommit.delete(tabUid); + } + } + getSQL () { // SELECT const selectArray = this._query.select.reduce(this._reducer, []); @@ -1471,17 +1313,8 @@ export class PostgreSQLClient extends AntaresCore { return `${selectRaw}${updateRaw ? 'UPDATE' : ''}${insertRaw ? 'INSERT ' : ''}${this._query.delete ? 'DELETE ' : ''}${fromRaw}${updateRaw}${whereRaw}${groupByRaw}${orderByRaw}${limitRaw}${offsetRaw}${insertRaw}`; } - /** - * @param {string} sql raw SQL query - * @param {object} args - * @param {boolean} args.nest - * @param {boolean} args.details - * @param {boolean} args.split - * @returns {Promise} - * @memberof PostgreSQLClient - */ - async raw (sql, args) { - if (process.env.NODE_ENV === 'development') this._logger(sql);// TODO: replace BLOB content with a placeholder + async raw (sql: string, args?: antares.QueryParams) { + if (process.env.NODE_ENV === 'development') this._logger(sql); args = { nest: false, @@ -1495,7 +1328,7 @@ export class PostgreSQLClient extends AntaresCore { if (!args.comments) sql = sql.replace(/(\/\*(.|[\r\n])*?\*\/)|(--(.*|[\r\n]))/gm, '');// Remove comments - const resultsArr = []; + const resultsArr: antares.QueryResult[] = []; let paramsArr = []; const queries = args.split ? sql.split(/(?!\B'[^']*);(?![^']*'\B)/gm) @@ -1503,8 +1336,8 @@ export class PostgreSQLClient extends AntaresCore { .map(q => q.trim()) : [sql]; - let connection; - const isPool = this._connection instanceof Pool; + let connection: pg.Client | pg.PoolClient; + const isPool = this._connection instanceof pg.Pool; if (!args.autocommit && args.tabUid) { // autocommit OFF if (this._connectionsToCommit.has(args.tabUid)) @@ -1515,11 +1348,12 @@ export class PostgreSQLClient extends AntaresCore { this._connectionsToCommit.set(args.tabUid, connection); } } - else// autocommit ON - connection = isPool ? await this._connection.connect() : this._connection; + else { // autocommit ON + connection = isPool ? await this._connection.connect() as pg.PoolClient : this._connection as pg.Client; + } if (args.tabUid && isPool) - this._runningConnections.set(args.tabUid, connection.processID); + this._runningConnections.set(args.tabUid, (connection as pg.PoolClient & { processID: number }).processID); if (args.schema && args.schema !== 'public') await this.use(args.schema, connection); @@ -1528,8 +1362,8 @@ export class PostgreSQLClient extends AntaresCore { if (!query) continue; const timeStart = new Date(); - let timeStop; - let keysArr = []; + let timeStop: Date; + let keysArr: antares.QueryForeign[] = []; const { rows, report, fields, keys, duration } = await new Promise((resolve, reject) => { (async () => { @@ -1538,16 +1372,17 @@ export class PostgreSQLClient extends AntaresCore { timeStop = new Date(); - let ast; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let ast: any; try { - [ast] = parse(query); + [ast] = pgAst.parse(query);// TODO: maybe refactor } catch (err) {} const { rows, fields } = res; let queryResult; - let tablesInfo; + let tablesInfo: { table: string; schema: string }[]; if (args.nest) { const tablesID = [...new Set(fields.map(field => field.tableID))].toString(); @@ -1567,10 +1402,10 @@ export class PostgreSQLClient extends AntaresCore { let remappedFields = fields ? fields.map(field => { if (!field || Array.isArray(field)) - return false; + return undefined; - let schema = ast && ast.from && 'schema' in ast.from[0] ? ast.from[0].schema : this._schema; - let table = ast && ast.from ? ast.from[0].name : null; + let schema: string = ast && ast.from && 'schema' in ast.from[0] ? ast.from[0].schema : this._schema; + let table: string = ast && ast.from ? ast.from[0].name : null; if (args.nest) { schema = tablesInfo[field.tableID] ? tablesInfo[field.tableID].schema : this._schema; @@ -1586,7 +1421,9 @@ export class PostgreSQLClient extends AntaresCore { // TODO: pick ast.from index if multiple tableAlias: ast && ast.from ? ast.from[0].as : null, orgTable: ast && ast.from ? ast.from[0].name : null, - type: this.types[field.dataTypeID] || field.format + type: this.types[field.dataTypeID] || field.format, + length: undefined as number, + key: undefined as string }; }).filter(Boolean) : []; @@ -1619,7 +1456,7 @@ export class PostgreSQLClient extends AntaresCore { if (fieldIndex) { const key = fieldIndex.type === 'PRIMARY' ? 'pri' : fieldIndex.type === 'UNIQUE' ? 'uni' : 'mul'; field = { ...field, key }; - }; + } } return field; @@ -1627,7 +1464,7 @@ export class PostgreSQLClient extends AntaresCore { } catch (err) { if (isPool && args.autocommit) { - connection.release(); + (connection as pg.PoolClient).release(); this._runningConnections.delete(args.tabUid); } reject(err); @@ -1639,7 +1476,7 @@ export class PostgreSQLClient extends AntaresCore { } catch (err) { if (isPool && args.autocommit) { - connection.release(); + (connection as pg.PoolClient).release(); this._runningConnections.delete(args.tabUid); } reject(err); @@ -1649,7 +1486,7 @@ export class PostgreSQLClient extends AntaresCore { } resolve({ - duration: timeStop - timeStart, + duration: timeStop.getTime() - timeStart.getTime(), rows: Array.isArray(queryResult) ? queryResult.some(el => Array.isArray(el)) ? [] : queryResult : false, report: !Array.isArray(queryResult) ? queryResult : false, fields: remappedFields, @@ -1658,7 +1495,7 @@ export class PostgreSQLClient extends AntaresCore { } catch (err) { if (isPool && args.autocommit) { - connection.release(); + (connection as pg.PoolClient).release(); this._runningConnections.delete(args.tabUid); } reject(err); @@ -1670,10 +1507,12 @@ export class PostgreSQLClient extends AntaresCore { } if (isPool && args.autocommit) { - connection.release(); + (connection as pg.PoolClient).release(); this._runningConnections.delete(args.tabUid); } - return resultsArr.length === 1 ? resultsArr[0] : resultsArr; + const result = resultsArr.length === 1 ? resultsArr[0] : resultsArr; + + return result as unknown as T; } } diff --git a/tsconfig.json b/tsconfig.json index 5b3d9663..9e768354 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -9,6 +9,7 @@ "types": [ "node" ], + "sourceMap": true, "allowSyntheticDefaultImports": true, "resolveJsonModule": true, "removeComments": true, From 5e2ad8c3773e4ae4972d60a8f7bef5f1402a4fa6 Mon Sep 17 00:00:00 2001 From: Fabio Di Stasio Date: Thu, 14 Apr 2022 18:25:13 +0200 Subject: [PATCH 07/14] refactor: sqlite client ts refactor --- src/common/interfaces/antares.ts | 12 +- src/main/libs/AntaresCore.ts | 2 - src/main/libs/clients/PostgreSQLClient.ts | 2 +- .../{SQLiteClient.js => SQLiteClient.ts} | 669 ++++++++---------- 4 files changed, 292 insertions(+), 393 deletions(-) rename src/main/libs/clients/{SQLiteClient.js => SQLiteClient.ts} (68%) diff --git a/src/common/interfaces/antares.ts b/src/common/interfaces/antares.ts index 98a90764..2a33d5ea 100644 --- a/src/common/interfaces/antares.ts +++ b/src/common/interfaces/antares.ts @@ -105,7 +105,7 @@ export interface TableForeign { export interface TableOptions { name: string; - type: 'table' | 'view'; + type?: 'table' | 'view'; engine?: string; comment?: string; collation?: string; @@ -114,7 +114,7 @@ export interface TableOptions { export interface CreateTableParams { /** Connection UID */ - uid: string; + uid?: string; schema: string; fields: TableField[]; foreigns: TableForeign[]; @@ -124,12 +124,18 @@ export interface CreateTableParams { export interface AlterTableParams { /** Connection UID */ - uid: string; + uid?: string; schema: string; table: string; additions: TableField[]; changes: TableField[]; deletions: TableField[]; + tableStructure: { + name: string; + fields: TableField[]; + foreigns: TableForeign[]; + indexes: TableIndex[]; + }; indexChanges: { additions: TableIndex[]; changes: TableIndex[]; diff --git a/src/main/libs/AntaresCore.ts b/src/main/libs/AntaresCore.ts index 042510dd..c1e06e48 100644 --- a/src/main/libs/AntaresCore.ts +++ b/src/main/libs/AntaresCore.ts @@ -1,4 +1,3 @@ -// import BetterSqlite3 from 'better-sqlite3'; import * as antares from 'common/interfaces/antares'; import mysql from 'mysql2/promise'; import * as pg from 'pg'; @@ -17,7 +16,6 @@ export class AntaresCore { protected _client: string; protected _params: mysql.ConnectionOptions | pg.ClientConfig | { databasePath: string; readonly: boolean}; protected _poolSize: number; - // protected _connection?: mysql.Connection | mysql.Pool | pg.Connection | BetterSqlite3.Database protected _ssh?: SSH2Promise; protected _logger: (sql: string) => void; protected _queryDefaults: antares.QueryBuilderObject; diff --git a/src/main/libs/clients/PostgreSQLClient.ts b/src/main/libs/clients/PostgreSQLClient.ts index 67e1b076..d3ff3d12 100644 --- a/src/main/libs/clients/PostgreSQLClient.ts +++ b/src/main/libs/clients/PostgreSQLClient.ts @@ -1,7 +1,7 @@ import * as antares from 'common/interfaces/antares'; import * as mysql from 'mysql2'; import { builtinsTypes } from 'pg-types'; -import * as pg /* { Pool, Client, types } */ from 'pg'; +import * as pg from 'pg'; import * as pgAst from 'pgsql-ast-parser'; import { AntaresCore } from '../AntaresCore'; import * as dataTypes from 'common/data-types/postgresql'; diff --git a/src/main/libs/clients/SQLiteClient.js b/src/main/libs/clients/SQLiteClient.ts similarity index 68% rename from src/main/libs/clients/SQLiteClient.js rename to src/main/libs/clients/SQLiteClient.ts index 920aa95a..15755d58 100644 --- a/src/main/libs/clients/SQLiteClient.js +++ b/src/main/libs/clients/SQLiteClient.ts @@ -1,26 +1,28 @@ -'use strict'; -import sqlite from 'better-sqlite3'; +import * as antares from 'common/interfaces/antares'; +import * as sqlite from 'better-sqlite3'; import { AntaresCore } from '../AntaresCore'; -import dataTypes from 'common/data-types/sqlite'; +import * as dataTypes from 'common/data-types/sqlite'; import { NUMBER, FLOAT, TIME, DATETIME } from 'common/fieldTypes'; export class SQLiteClient extends AntaresCore { - constructor (args) { + private _schema?: string; + private _connectionsToCommit: Map; + protected _connection?: sqlite.Database; + protected _params: { databasePath: string; readonly: boolean}; + + constructor (args: antares.ClientParams) { super(args); this._schema = null; this._connectionsToCommit = new Map(); } - _getTypeInfo (type) { + _getTypeInfo (type: string) { return dataTypes .reduce((acc, group) => [...acc, ...group.types], []) .filter(_type => _type.name === type.toUpperCase())[0]; } - /** - * @memberof SQLiteClient - */ async connect () { this._connection = this.getConnection(); } @@ -32,36 +34,40 @@ export class SQLiteClient extends AntaresCore { }); } - /** - * @memberof SQLiteClient - */ - destroy () {} + destroy (): void { + return null; + } - /** - * Executes an USE query - * - * @memberof SQLiteClient - */ - use () {} + use (): void { + return null; + } - /** - * @param {Array} schemas list - * @returns {Array.} databases scructure - * @memberof SQLiteClient - */ - async getStructure (schemas) { - const { rows: databases } = await this.raw('SELECT * FROM pragma_database_list'); + async getStructure (schemas: Set) { + /* eslint-disable camelcase */ + interface ShowTableResult { + Db?: string; + type: string; + name: string; + tbl_name: string; + rootpage:4; + sql: string; + } + + type ShowTriggersResult = ShowTableResult + /* eslint-enable camelcase */ + + const { rows: databases } = await this.raw>('SELECT * FROM pragma_database_list'); const filteredDatabases = databases; - const tablesArr = []; - const triggersArr = []; + const tablesArr: ShowTableResult[] = []; + const triggersArr: ShowTriggersResult[] = []; let schemaSize = 0; for (const db of filteredDatabases) { if (!schemas.has(db.name)) continue; - let { rows: tables } = await this.raw(` + let { rows: tables } = await this.raw>(` SELECT * FROM "${db.name}".sqlite_master WHERE type IN ('table', 'view') @@ -76,7 +82,7 @@ export class SQLiteClient extends AntaresCore { tablesArr.push(...tables); } - let { rows: triggers } = await this.raw(`SELECT * FROM "${db.name}".sqlite_master WHERE type='trigger'`); + let { rows: triggers } = await this.raw>(`SELECT * FROM "${db.name}".sqlite_master WHERE type='trigger'`); if (triggers.length) { triggers = triggers.map(trigger => { trigger.Db = db.name; @@ -133,22 +139,24 @@ export class SQLiteClient extends AntaresCore { }); } - /** - * @param {Object} params - * @param {String} params.schema - * @param {String} params.table - * @returns {Object} table scructure - * @memberof SQLiteClient - */ - async getTableColumns ({ schema, table }) { - const { rows: fields } = await this.raw(`SELECT * FROM "${schema}".pragma_table_info('${table}')`); + async getTableColumns ({ schema, table }: { schema: string; table: string }) { + interface TableColumnsResult { + cid: number; + name: string; + type: string; + notnull: 0 | 1; + // eslint-disable-next-line camelcase + dflt_value: string; + pk: 0 | 1; + } + const { rows: fields } = await this.raw>(`SELECT * FROM "${schema}".pragma_table_info('${table}')`); return fields.map(field => { - const [type, length] = field.type.includes('(') - ? field.type.replace(')', '').split('(').map(el => { - if (!isNaN(el)) el = +el; + const [type, length]: [string, number?] = field.type.includes('(') + ? field.type.replace(')', '').split('(').map((el: string | number) => { + if (!isNaN(Number(el))) el = Number(el); return el; - }) + }) as [string, number?] : [field.type, null]; return { @@ -174,54 +182,50 @@ export class SQLiteClient extends AntaresCore { }); } - /** - * @param {Object} params - * @param {String} params.schema - * @param {String} params.table - * @returns {Object} table row count - * @memberof SQLiteClient - */ - async getTableApproximateCount ({ schema, table }) { + async getTableApproximateCount ({ schema, table }: { schema: string; table: string }): Promise { const { rows } = await this.raw(`SELECT COUNT(*) AS count FROM "${schema}"."${table}"`); return rows.length ? rows[0].count : 0; } - /** - * @param {Object} params - * @param {String} params.schema - * @param {String} params.table - * @returns {Object} table options - * @memberof SQLiteClient - */ - async getTableOptions ({ schema, table }) { + async getTableOptions ({ table }: { table: string }) { return { name: table }; } - /** - * @param {Object} params - * @param {String} params.schema - * @param {String} params.table - * @returns {Object} table indexes - * @memberof SQLiteClient - */ - async getTableIndexes ({ schema, table }) { + async getTableIndexes ({ schema, table }: { schema: string; table: string }) { + interface TableColumnsResult { + type: string; + name: string; + // eslint-disable-next-line camelcase + tbl_name: string; + rootpage:4; + sql: string; + } + + interface ShowIndexesResult { + seq: number; + name: string; + unique: 0 | 1; + origin: string; + partial: 0 | 1; + } + const remappedIndexes = []; - const { rows: primaryKeys } = await this.raw(`SELECT * FROM "${schema}".pragma_table_info('${table}') WHERE pk != 0`); + const { rows: primaryKeys } = await this.raw>(`SELECT * FROM "${schema}".pragma_table_info('${table}') WHERE pk != 0`); for (const key of primaryKeys) { remappedIndexes.push({ name: 'PRIMARY', column: key.name, - indexType: null, + indexType: null as never, type: 'PRIMARY', - cardinality: null, + cardinality: null as never, comment: '', indexComment: '' }); } - const { rows: indexes } = await this.raw(`SELECT * FROM "${schema}".pragma_index_list('${table}');`); + const { rows: indexes } = await this.raw>(`SELECT * FROM "${schema}".pragma_index_list('${table}');`); for (const index of indexes) { const { rows: details } = await this.raw(`SELECT * FROM "${schema}".pragma_index_info('${index.name}');`); @@ -230,9 +234,9 @@ export class SQLiteClient extends AntaresCore { remappedIndexes.push({ name: index.name, column: detail.name, - indexType: null, + indexType: null as never, type: index.unique === 1 ? 'UNIQUE' : 'INDEX', - cardinality: null, + cardinality: null as never, comment: '', indexComment: '' }); @@ -242,15 +246,19 @@ export class SQLiteClient extends AntaresCore { return remappedIndexes; } - /** - * @param {Object} params - * @param {String} params.schema - * @param {String} params.table - * @returns {Object} table key usage - * @memberof SQLiteClient - */ - async getKeyUsage ({ schema, table }) { - const { rows } = await this.raw(`SELECT * FROM "${schema}".pragma_foreign_key_list('${table}');`); + async getKeyUsage ({ schema, table }: { schema: string; table: string }) { + /* eslint-disable camelcase */ + interface KeyResult { + from: string; + id: number; + table: string; + to: string; + on_update: string; + on_delete: string; + } + /* eslint-enable camelcase */ + + const { rows } = await this.raw>(`SELECT * FROM "${schema}".pragma_foreign_key_list('${table}');`); return rows.map(field => { return { @@ -269,229 +277,11 @@ export class SQLiteClient extends AntaresCore { }); } - async getUsers () {} - - /** - * SHOW CREATE VIEW - * - * @returns {Array.} view informations - * @memberof SQLiteClient - */ - async getViewInformations ({ schema, view }) { - const sql = `SELECT "sql" FROM "${schema}".sqlite_master WHERE "type"='view' AND name='${view}'`; - const results = await this.raw(sql); - - return results.rows.map(row => { - return { - sql: row.sql.match(/(?<=AS ).*?$/gs)[0], - name: view - }; - })[0]; + async getUsers (): Promise { + return null; } - /** - * DROP VIEW - * - * @returns {Array.} parameters - * @memberof SQLiteClient - */ - async dropView (params) { - const sql = `DROP VIEW "${params.schema}"."${params.view}"`; - return await this.raw(sql); - } - - /** - * ALTER VIEW - * - * @returns {Array.} parameters - * @memberof SQLiteClient - */ - async alterView (params) { - const { view } = params; - try { - await this.dropView({ schema: view.schema, view: view.oldName }); - await this.createView(view); - } - catch (err) { - return Promise.reject(err); - } - } - - /** - * CREATE VIEW - * - * @returns {Array.} parameters - * @memberof SQLiteClient - */ - async createView (params) { - const sql = `CREATE VIEW "${params.schema}"."${params.name}" AS ${params.sql}`; - return await this.raw(sql); - } - - /** - * SHOW CREATE TRIGGER - * - * @returns {Array.} view informations - * @memberof SQLiteClient - */ - async getTriggerInformations ({ schema, trigger }) { - const sql = `SELECT "sql" FROM "${schema}".sqlite_master WHERE "type"='trigger' AND name='${trigger}'`; - const results = await this.raw(sql); - - return results.rows.map(row => { - return { - sql: row.sql.match(/(BEGIN|begin)(.*)(END|end)/gs)[0], - name: trigger, - table: row.sql.match(/(?<=ON `).*?(?=`)/gs)[0], - activation: row.sql.match(/(BEFORE|AFTER)/gs)[0], - event: row.sql.match(/(INSERT|UPDATE|DELETE)/gs)[0] - }; - })[0]; - } - - /** - * DROP TRIGGER - * - * @returns {Array.} parameters - * @memberof SQLiteClient - */ - async dropTrigger (params) { - const sql = `DROP TRIGGER \`${params.schema}\`.\`${params.trigger}\``; - return await this.raw(sql); - } - - /** - * ALTER TRIGGER - * - * @returns {Array.} parameters - * @memberof SQLiteClient - */ - async alterTrigger (params) { - const { trigger } = params; - const tempTrigger = Object.assign({}, trigger); - tempTrigger.name = `Antares_${tempTrigger.name}_tmp`; - - try { - await this.createTrigger(tempTrigger); - await this.dropTrigger({ schema: trigger.schema, trigger: tempTrigger.name }); - await this.dropTrigger({ schema: trigger.schema, trigger: trigger.oldName }); - await this.createTrigger(trigger); - } - catch (err) { - return Promise.reject(err); - } - } - - /** - * CREATE TRIGGER - * - * @returns {Array.} parameters - * @memberof SQLiteClient - */ - async createTrigger (params) { - const sql = `CREATE ${params.definer ? `DEFINER=${params.definer} ` : ''}TRIGGER \`${params.schema}\`.\`${params.name}\` ${params.activation} ${params.event} ON \`${params.table}\` FOR EACH ROW ${params.sql}`; - return await this.raw(sql, { split: false }); - } - - /** - * SHOW COLLATION - * - * @returns {Array.} collations list - * @memberof SQLiteClient - */ - async getCollations () { - return []; - } - - /** - * SHOW VARIABLES - * - * @returns {Array.} variables list - * @memberof SQLiteClient - */ - async getVariables () { - return []; - } - - /** - * SHOW ENGINES - * - * @returns {Array.} engines list - * @memberof SQLiteClient - */ - async getEngines () { - return { - name: 'SQLite', - support: 'YES', - comment: '', - isDefault: true - }; - } - - /** - * SHOW VARIABLES LIKE '%vers%' - * - * @returns {Array.} version parameters - * @memberof SQLiteClient - */ - async getVersion () { - const os = require('os'); - const sql = 'SELECT sqlite_version() AS version'; - const { rows } = await this.raw(sql); - - return { - number: rows[0].version, - name: 'SQLite', - arch: process.arch, - os: `${os.type()} ${os.release()}` - }; - } - - async getProcesses () {} - - async killProcess () {} - - /** - * - * @param {string} tabUid - * @returns {Promise} - */ - async commitTab (tabUid) { - const connection = this._connectionsToCommit.get(tabUid); - if (connection) { - connection.prepare('COMMIT').run(); - return this.destroyConnectionToCommit(tabUid); - } - } - - /** - * - * @param {string} tabUid - * @returns {Promise} - */ - async rollbackTab (tabUid) { - const connection = this._connectionsToCommit.get(tabUid); - if (connection) { - connection.prepare('ROLLBACK').run(); - return this.destroyConnectionToCommit(tabUid); - } - } - - destroyConnectionToCommit (tabUid) { - const connection = this._connectionsToCommit.get(tabUid); - if (connection) { - connection.close(); - this._connectionsToCommit.delete(tabUid); - } - } - - /** - * CREATE TABLE - * - * @returns {Promise} - * @memberof SQLiteClient - */ - async createTable (params) { + async createTable (params: antares.CreateTableParams) { const { schema, fields, @@ -499,10 +289,10 @@ export class SQLiteClient extends AntaresCore { indexes, options } = params; - const newColumns = []; - const newIndexes = []; - const manageIndexes = []; - const newForeigns = []; + const newColumns: string[] = []; + const newIndexes: string[] = []; + const manageIndexes: string[] = []; + const newForeigns: string[] = []; let sql = `CREATE TABLE "${schema}"."${options.name}"`; @@ -512,7 +302,7 @@ export class SQLiteClient extends AntaresCore { const length = typeInfo?.length ? field.enumValues || field.numLength || field.charLength || field.datePrecision : false; newColumns.push(`"${field.name}" - ${field.type.toUpperCase()}${length && length !== true ? `(${length})` : ''} + ${field.type.toUpperCase()}${length ? `(${length})` : ''} ${field.unsigned ? 'UNSIGNED' : ''} ${field.nullable ? 'NULL' : 'NOT NULL'} ${field.autoIncrement ? 'AUTO_INCREMENT' : ''} @@ -542,34 +332,37 @@ export class SQLiteClient extends AntaresCore { return await this.raw(sql); } - /** - * ALTER TABLE - * - * @returns {Promise} - * @memberof SQLiteClient - */ - async alterTable (params) { + async alterTable (params: antares.AlterTableParams) { + const { + table, + schema, + additions, + deletions, + changes, + tableStructure + } = params; + try { await this.raw('BEGIN TRANSACTION'); await this.raw('PRAGMA foreign_keys = 0'); - const tmpName = `Antares_${params.table}_tmp`; - await this.raw(`CREATE TABLE "${tmpName}" AS SELECT * FROM "${params.table}"`); + const tmpName = `Antares_${table}_tmp`; + await this.raw(`CREATE TABLE "${tmpName}" AS SELECT * FROM "${table}"`); await this.dropTable(params); const createTableParams = { - schema: params.schema, - fields: params.tableStructure.fields, - foreigns: params.tableStructure.foreigns, - indexes: params.tableStructure.indexes.filter(index => !index.name.includes('sqlite_autoindex')), - options: { name: params.tableStructure.name } + schema: schema, + fields: tableStructure.fields, + foreigns: tableStructure.foreigns, + indexes: tableStructure.indexes.filter(index => !index.name.includes('sqlite_autoindex')), + options: { name: tableStructure.name } }; await this.createTable(createTableParams); const insertFields = createTableParams.fields .filter(field => { return ( - params.additions.every(add => add.name !== field.name) && - params.deletions.every(del => del.name !== field.name) + additions.every(add => add.name !== field.name) && + deletions.every(del => del.name !== field.name) ); }) .reduce((acc, curr) => { @@ -578,7 +371,7 @@ export class SQLiteClient extends AntaresCore { }, []); const selectFields = insertFields.map(field => { - const renamedField = params.changes.find(change => `"${change.name}"` === field); + const renamedField = changes.find(change => `"${change.name}"` === field); if (renamedField) return `"${renamedField.orgName}"`; return field; @@ -586,7 +379,7 @@ export class SQLiteClient extends AntaresCore { await this.raw(`INSERT INTO "${createTableParams.options.name}" (${insertFields.join(',')}) SELECT ${selectFields.join(',')} FROM "${tmpName}"`); - await this.dropTable({ schema: params.schema, table: tmpName }); + await this.dropTable({ schema: schema, table: tmpName }); await this.raw('PRAGMA foreign_keys = 1'); await this.raw('COMMIT'); } @@ -596,43 +389,155 @@ export class SQLiteClient extends AntaresCore { } } - /** - * DUPLICATE TABLE - * - * @returns {Promise} - * @memberof SQLiteClient - */ - async duplicateTable (params) { // TODO: retrive table informations and create a copy + async duplicateTable (params: { schema: string; table: string }) { // TODO: retrive table informations and create a copy const sql = `CREATE TABLE "${params.schema}"."${params.table}_copy" AS SELECT * FROM "${params.schema}"."${params.table}"`; return await this.raw(sql); } - /** - * TRUNCATE TABLE - * - * @returns {Promise} - * @memberof SQLiteClient - */ - async truncateTable (params) { + async truncateTable (params: { schema: string; table: string }) { const sql = `DELETE FROM "${params.schema}"."${params.table}"`; return await this.raw(sql); } - /** - * DROP TABLE - * - * @returns {Promise} - * @memberof SQLiteClient - */ - async dropTable (params) { + async dropTable (params: { schema: string; table: string }) { const sql = `DROP TABLE "${params.schema}"."${params.table}"`; return await this.raw(sql); } - /** - * @returns {String} SQL string - * @memberof SQLiteClient - */ + async getViewInformations ({ schema, view }: { schema: string; view: string }) { + const sql = `SELECT "sql" FROM "${schema}".sqlite_master WHERE "type"='view' AND name='${view}'`; + const results = await this.raw(sql); + + return results.rows.map(row => { + return { + sql: row.sql.match(/(?<=AS ).*?$/gs)[0], + name: view + }; + })[0]; + } + + async dropView (params: { schema: string; view: string }) { + const sql = `DROP VIEW "${params.schema}"."${params.view}"`; + return await this.raw(sql); + } + + async alterView ({ view }: { view: antares.AlterViewParams }) { + try { + await this.dropView({ schema: view.schema, view: view.oldName }); + await this.createView(view); + } + catch (err) { + return Promise.reject(err); + } + } + + async createView (params: antares.CreateViewParams) { + const sql = `CREATE VIEW "${params.schema}"."${params.name}" AS ${params.sql}`; + return await this.raw(sql); + } + + async getTriggerInformations ({ schema, trigger }: { schema: string; trigger: string }) { + const sql = `SELECT "sql" FROM "${schema}".sqlite_master WHERE "type"='trigger' AND name='${trigger}'`; + const results = await this.raw(sql); + + return results.rows.map(row => { + return { + sql: row.sql.match(/(BEGIN|begin)(.*)(END|end)/gs)[0], + name: trigger, + table: row.sql.match(/(?<=ON `).*?(?=`)/gs)[0], + activation: row.sql.match(/(BEFORE|AFTER)/gs)[0], + event: row.sql.match(/(INSERT|UPDATE|DELETE)/gs)[0] + }; + })[0]; + } + + async dropTrigger (params: { schema: string; trigger: string }) { + const sql = `DROP TRIGGER \`${params.schema}\`.\`${params.trigger}\``; + return await this.raw(sql); + } + + async alterTrigger ({ trigger } : {trigger: antares.AlterTriggerParams}) { + const tempTrigger = Object.assign({}, trigger); + tempTrigger.name = `Antares_${tempTrigger.name}_tmp`; + + try { + await this.createTrigger(tempTrigger); + await this.dropTrigger({ schema: trigger.schema, trigger: tempTrigger.name }); + await this.dropTrigger({ schema: trigger.schema, trigger: trigger.oldName }); + await this.createTrigger(trigger); + } + catch (err) { + return Promise.reject(err); + } + } + + async createTrigger (params: antares.CreateTriggerParams) { + const sql = `CREATE ${params.definer ? `DEFINER=${params.definer} ` : ''}TRIGGER \`${params.schema}\`.\`${params.name}\` ${params.activation} ${params.event} ON \`${params.table}\` FOR EACH ROW ${params.sql}`; + return await this.raw(sql, { split: false }); + } + + async getCollations (): Promise { + return []; + } + + async getVariables (): Promise { + return []; + } + + async getEngines () { + return { + name: 'SQLite', + support: 'YES', + comment: '', + isDefault: true + }; + } + + async getVersion () { + const os = require('os'); + const sql = 'SELECT sqlite_version() AS version'; + const { rows } = await this.raw(sql); + + return { + number: rows[0].version, + name: 'SQLite', + arch: process.arch, + os: `${os.type()} ${os.release()}` + }; + } + + async getProcesses (): Promise { + return null; + } + + async killProcess (): Promise { + return null; + } + + async commitTab (tabUid: string) { + const connection = this._connectionsToCommit.get(tabUid); + if (connection) { + connection.prepare('COMMIT').run(); + return this.destroyConnectionToCommit(tabUid); + } + } + + async rollbackTab (tabUid: string) { + const connection = this._connectionsToCommit.get(tabUid); + if (connection) { + connection.prepare('ROLLBACK').run(); + return this.destroyConnectionToCommit(tabUid); + } + } + + destroyConnectionToCommit (tabUid: string) { + const connection = this._connectionsToCommit.get(tabUid); + if (connection) { + connection.close(); + this._connectionsToCommit.delete(tabUid); + } + } + getSQL () { // SELECT const selectArray = this._query.select.reduce(this._reducer, []); @@ -688,16 +593,7 @@ export class SQLiteClient extends AntaresCore { return `${selectRaw}${updateRaw ? 'UPDATE' : ''}${insertRaw ? 'INSERT ' : ''}${this._query.delete ? 'DELETE ' : ''}${fromRaw}${updateRaw}${whereRaw}${groupByRaw}${orderByRaw}${limitRaw}${offsetRaw}${insertRaw}`; } - /** - * @param {string} sql raw SQL query - * @param {object} args - * @param {boolean} args.nest - * @param {boolean} args.details - * @param {boolean} args.split - * @returns {Promise} - * @memberof SQLiteClient - */ - async raw (sql, args) { + async raw (sql: string, args?: antares.QueryParams) { if (process.env.NODE_ENV === 'development') this._logger(sql);// TODO: replace BLOB content with a placeholder args = { @@ -720,7 +616,7 @@ export class SQLiteClient extends AntaresCore { .map(q => q.trim()) : [sql]; - let connection; + let connection: sqlite.Database; if (!args.autocommit && args.tabUid) { // autocommit OFF if (this._connectionsToCommit.has(args.tabUid)) @@ -738,31 +634,33 @@ export class SQLiteClient extends AntaresCore { if (!query) continue; const timeStart = new Date(); let timeStop; - const keysArr = []; + const keysArr: antares.QueryForeign[] = []; const { rows, report, fields, keys, duration } = await new Promise((resolve, reject) => { (async () => { - let queryResult; + let queryRunResult: sqlite.RunResult; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + let queryAllResult: any[]; let affectedRows; let fields; - const detectedTypes = {}; + const detectedTypes: {[key: string]: string} = {}; try { const stmt = connection.prepare(query); if (stmt.reader) { - queryResult = stmt.all(); + queryAllResult = stmt.all(); fields = stmt.columns(); - if (queryResult.length) { + if (queryAllResult.length) { fields.forEach(field => { - detectedTypes[field.name] = typeof queryResult[0][field.name]; + detectedTypes[field.name] = typeof queryAllResult[0][field.name]; }); } } else { - const info = queryResult = stmt.run(); - affectedRows = info.changes; + queryRunResult = stmt.run(); + affectedRows = queryRunResult.changes; } } catch (err) { @@ -773,18 +671,18 @@ export class SQLiteClient extends AntaresCore { let remappedFields = fields ? fields.map(field => { - let [parsedType, length] = field.type?.includes('(') - ? field.type.replace(')', '').split('(').map(el => { - if (!isNaN(el)) - el = +el; + let [parsedType, length]: [string, number?] = field.type?.includes('(') + ? field.type.replace(')', '').split('(').map((el: string | number) => { + if (!isNaN(Number(el))) + el = Number(el); else - el = el.trim(); + el = (el as string).trim(); return el; - }) + }) as [string, number?] : [field.type, null]; if ([...TIME, ...DATETIME].includes(parsedType)) { - const firstNotNull = queryResult.find(res => res[field.name] !== null); + const firstNotNull = queryAllResult.find(res => res[field.name] !== null); if (firstNotNull && firstNotNull[field.name].includes('.')) length = firstNotNull[field.name].split('.').pop().length; } @@ -798,7 +696,8 @@ export class SQLiteClient extends AntaresCore { tableAlias: field.table, orgTable: field.table, type: field.type !== null ? parsedType : detectedTypes[field.name], - length + length, + key: undefined as string }; }).filter(Boolean) : []; @@ -818,18 +717,12 @@ export class SQLiteClient extends AntaresCore { const indexes = await this.getTableIndexes(paramObj); remappedFields = remappedFields.map(field => { - // const detailedField = columns.find(f => f.name === field.name); const fieldIndex = indexes.find(i => i.column === field.name); if (field.table === paramObj.table && field.schema === paramObj.schema) { - // if (detailedField) { - // const length = detailedField.numPrecision || detailedField.charLength || detailedField.datePrecision || null; - // field = { ...field, ...detailedField, length }; - // } - if (fieldIndex) { const key = fieldIndex.type === 'PRIMARY' ? 'pri' : fieldIndex.type === 'UNIQUE' ? 'uni' : 'mul'; field = { ...field, key }; - }; + } } return field; @@ -842,8 +735,8 @@ export class SQLiteClient extends AntaresCore { } resolve({ - duration: timeStop - timeStart, - rows: Array.isArray(queryResult) ? queryResult.some(el => Array.isArray(el)) ? [] : queryResult : false, + duration: timeStop.getTime() - timeStart.getTime(), + rows: Array.isArray(queryAllResult) ? queryAllResult.some(el => Array.isArray(el)) ? [] : queryAllResult : false, report: affectedRows !== undefined ? { affectedRows } : null, fields: remappedFields, keys: keysArr @@ -854,6 +747,8 @@ export class SQLiteClient extends AntaresCore { resultsArr.push({ rows, report, fields, keys, duration }); } - return resultsArr.length === 1 ? resultsArr[0] : resultsArr; + const result = resultsArr.length === 1 ? resultsArr[0] : resultsArr; + + return result as unknown as T; } } From 6adc93e1cdfda5b6f204722db47dc489909cfee6 Mon Sep 17 00:00:00 2001 From: Fabio Di Stasio Date: Fri, 15 Apr 2022 14:56:13 +0200 Subject: [PATCH 08/14] refactor: ipc handlers ts refactor --- package.json | 1 + src/common/FakerMethods.js | 4 +- src/common/interfaces/antares.ts | 7 +- src/common/interfaces/tableApis.ts | 20 +++ src/common/interfaces/workers.ts | 7 + .../{application.js => application.ts} | 0 .../{functions.js => functions.ts} | 3 +- .../ipc-handlers/{routines.js => routines.ts} | 3 +- .../{schedulers.js => schedulers.ts} | 3 +- .../ipc-handlers/{schema.js => schema.ts} | 27 ++-- .../ipc-handlers/{tables.js => tables.ts} | 29 ++-- .../ipc-handlers/{triggers.js => triggers.ts} | 3 +- .../ipc-handlers/{updates.js => updates.ts} | 10 +- src/main/ipc-handlers/{users.js => users.ts} | 3 +- src/main/ipc-handlers/{views.js => views.ts} | 3 +- src/main/libs/AntaresCore.ts | 125 ++++++++++++++++-- src/main/libs/clients/MySQLClient.ts | 12 +- src/main/libs/clients/PostgreSQLClient.ts | 13 +- src/main/libs/clients/SQLiteClient.ts | 12 +- src/main/libs/exporters/sql/SqlExporter.js | 9 +- 20 files changed, 219 insertions(+), 75 deletions(-) create mode 100644 src/common/interfaces/tableApis.ts create mode 100644 src/common/interfaces/workers.ts rename src/main/ipc-handlers/{application.js => application.ts} (100%) rename src/main/ipc-handlers/{functions.js => functions.ts} (93%) rename src/main/ipc-handlers/{routines.js => routines.ts} (90%) rename src/main/ipc-handlers/{schedulers.js => schedulers.ts} (93%) rename src/main/ipc-handlers/{schema.js => schema.ts} (93%) rename src/main/ipc-handlers/{tables.js => tables.ts} (92%) rename src/main/ipc-handlers/{triggers.js => triggers.ts} (93%) rename src/main/ipc-handlers/{updates.js => updates.ts} (85%) rename src/main/ipc-handlers/{users.js => users.ts} (78%) rename src/main/ipc-handlers/{views.js => views.ts} (90%) diff --git a/package.json b/package.json index 78af2abe..944d32e4 100644 --- a/package.json +++ b/package.json @@ -105,6 +105,7 @@ }, "dependencies": { "@electron/remote": "^2.0.1", + "@faker-js/faker": "^6.1.2", "@mdi/font": "^6.1.95", "@turf/helpers": "^6.5.0", "@vscode/vscode-languagedetection": "^1.0.21", diff --git a/src/common/FakerMethods.js b/src/common/FakerMethods.js index e401fa11..3d43ecb9 100644 --- a/src/common/FakerMethods.js +++ b/src/common/FakerMethods.js @@ -134,7 +134,7 @@ export default class { { name: 'phoneNumberFormat', group: 'phone', types: ['string'] }, { name: 'phoneFormats', group: 'phone', types: ['string'] }, - { name: 'number', group: 'datatype', types: ['string', 'number'], params: ['min', 'max'] }, + { name: 'number', group: 'random', types: ['string', 'number'], params: ['min', 'max'] }, { name: 'float', group: 'random', types: ['string', 'float'], params: ['min', 'max'] }, { name: 'arrayElement', group: 'random', types: ['string'] }, { name: 'arrayElements', group: 'random', types: ['string'] }, @@ -195,7 +195,7 @@ export default class { return 1; return 0; - }); ; + }); } static getGroupsByType (type) { diff --git a/src/common/interfaces/antares.ts b/src/common/interfaces/antares.ts index 2a33d5ea..64fbeb75 100644 --- a/src/common/interfaces/antares.ts +++ b/src/common/interfaces/antares.ts @@ -251,11 +251,12 @@ export interface QueryBuilderObject { where: string[]; groupBy: string[]; orderBy: string[]; - limit: string[]; - offset: string[]; + limit: number; + offset: number; join: string[]; update: string[]; - insert: string[]; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + insert: {[key: string]: any}[]; delete: boolean; } diff --git a/src/common/interfaces/tableApis.ts b/src/common/interfaces/tableApis.ts new file mode 100644 index 00000000..f3aacd22 --- /dev/null +++ b/src/common/interfaces/tableApis.ts @@ -0,0 +1,20 @@ +import { UsableLocale } from '@faker-js/faker'; + +export interface InsertRowsParams { + uid: string; + schema: string; + table: string; + row: {[key: string]: { + group: string; + method: string; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + params: any; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + value: any; + length: number; + }; + }; + repeat: number; + fields: {[key: string]: string}; + locale: UsableLocale; +} diff --git a/src/common/interfaces/workers.ts b/src/common/interfaces/workers.ts new file mode 100644 index 00000000..2120b9c9 --- /dev/null +++ b/src/common/interfaces/workers.ts @@ -0,0 +1,7 @@ +export type WorkerEvent = 'export-progress' | 'import-progress' | 'query-error' | 'end' | 'cancel' | 'error' + +export interface WorkerIpcMessage { + type: WorkerEvent; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + payload: any; +} diff --git a/src/main/ipc-handlers/application.js b/src/main/ipc-handlers/application.ts similarity index 100% rename from src/main/ipc-handlers/application.js rename to src/main/ipc-handlers/application.ts diff --git a/src/main/ipc-handlers/functions.js b/src/main/ipc-handlers/functions.ts similarity index 93% rename from src/main/ipc-handlers/functions.js rename to src/main/ipc-handlers/functions.ts index 0d793a61..c54d9e77 100644 --- a/src/main/ipc-handlers/functions.js +++ b/src/main/ipc-handlers/functions.ts @@ -1,6 +1,7 @@ +import * as antares from 'common/interfaces/antares'; import { ipcMain } from 'electron'; -export default (connections) => { +export default (connections: {[key: string]: antares.Client}) => { ipcMain.handle('get-function-informations', async (event, params) => { try { const result = await connections[params.uid].getFunctionInformations(params); diff --git a/src/main/ipc-handlers/routines.js b/src/main/ipc-handlers/routines.ts similarity index 90% rename from src/main/ipc-handlers/routines.js rename to src/main/ipc-handlers/routines.ts index b1b232e7..b293116e 100644 --- a/src/main/ipc-handlers/routines.js +++ b/src/main/ipc-handlers/routines.ts @@ -1,6 +1,7 @@ +import * as antares from 'common/interfaces/antares'; import { ipcMain } from 'electron'; -export default (connections) => { +export default (connections: {[key: string]: antares.Client}) => { ipcMain.handle('get-routine-informations', async (event, params) => { try { const result = await connections[params.uid].getRoutineInformations(params); diff --git a/src/main/ipc-handlers/schedulers.js b/src/main/ipc-handlers/schedulers.ts similarity index 93% rename from src/main/ipc-handlers/schedulers.js rename to src/main/ipc-handlers/schedulers.ts index f270beb3..97e54b1e 100644 --- a/src/main/ipc-handlers/schedulers.js +++ b/src/main/ipc-handlers/schedulers.ts @@ -1,6 +1,7 @@ +import * as antares from 'common/interfaces/antares'; import { ipcMain } from 'electron'; -export default (connections) => { +export default (connections: {[key: string]: antares.Client}) => { ipcMain.handle('get-scheduler-informations', async (event, params) => { try { const result = await connections[params.uid].getEventInformations(params); diff --git a/src/main/ipc-handlers/schema.js b/src/main/ipc-handlers/schema.ts similarity index 93% rename from src/main/ipc-handlers/schema.js rename to src/main/ipc-handlers/schema.ts index b8149be2..475fa53c 100644 --- a/src/main/ipc-handlers/schema.js +++ b/src/main/ipc-handlers/schema.ts @@ -1,14 +1,15 @@ +import * as antares from 'common/interfaces/antares'; +import * as workers from 'common/interfaces/workers'; import fs from 'fs'; import path from 'path'; -import { fork } from 'child_process'; +import { ChildProcess, fork } from 'child_process'; import { ipcMain, dialog } from 'electron'; -// @TODO: need some factories const isDevelopment = process.env.NODE_ENV !== 'production'; -export default connections => { - let exporter = null; - let importer = null; +export default (connections: {[key: string]: antares.Client}) => { + let exporter: ChildProcess = null; + let importer: ChildProcess = null; ipcMain.handle('create-schema', async (event, params) => { try { @@ -51,9 +52,7 @@ export default connections => { return { status: 'success', - response: collation.rows.length - ? collation.rows[0].DEFAULT_COLLATION_NAME - : '' + response: collation }; } catch (err) { @@ -175,7 +174,7 @@ export default connections => { ipcMain.handle('export', (event, { uid, type, tables, ...rest }) => { if (exporter !== null) return; - return new Promise((resolve, reject) => { + return new Promise((resolve/*, reject */) => { (async () => { if (fs.existsSync(rest.outputFile)) { // If file exists ask for replace const result = await dialog.showMessageBox({ @@ -211,7 +210,7 @@ export default connections => { }); // Exporter message listener - exporter.on('message', ({ type, payload }) => { + exporter.on('message', ({ type, payload }: workers.WorkerIpcMessage) => { switch (type) { case 'export-progress': event.sender.send('export-progress', payload); @@ -244,7 +243,7 @@ export default connections => { }); }); - ipcMain.handle('abort-export', async event => { + ipcMain.handle('abort-export', async () => { let willAbort = false; if (exporter) { @@ -268,7 +267,7 @@ export default connections => { ipcMain.handle('import-sql', async (event, options) => { if (importer !== null) return; - return new Promise((resolve, reject) => { + return new Promise((resolve/*, reject */) => { (async () => { const dbConfig = await connections[options.uid].getDbConfig(); @@ -283,7 +282,7 @@ export default connections => { }); // Importer message listener - importer.on('message', ({ type, payload }) => { + importer.on('message', ({ type, payload }: workers.WorkerIpcMessage) => { switch (type) { case 'import-progress': event.sender.send('import-progress', payload); @@ -314,7 +313,7 @@ export default connections => { }); }); - ipcMain.handle('abort-import-sql', async event => { + ipcMain.handle('abort-import-sql', async () => { let willAbort = false; if (importer) { diff --git a/src/main/ipc-handlers/tables.js b/src/main/ipc-handlers/tables.ts similarity index 92% rename from src/main/ipc-handlers/tables.js rename to src/main/ipc-handlers/tables.ts index 79e0034f..466fc10d 100644 --- a/src/main/ipc-handlers/tables.js +++ b/src/main/ipc-handlers/tables.ts @@ -1,12 +1,14 @@ +import * as antares from 'common/interfaces/antares'; +import { InsertRowsParams } from 'common/interfaces/tableApis'; import { ipcMain } from 'electron'; -import faker from 'faker'; +import { faker } from '@faker-js/faker'; import moment from 'moment'; import { sqlEscaper } from 'common/libs/sqlEscaper'; import { TEXT, LONG_TEXT, ARRAY, TEXT_SEARCH, NUMBER, FLOAT, BLOB, BIT, DATE, DATETIME } from 'common/fieldTypes'; import * as customizations from 'common/customizations'; import fs from 'fs'; -export default (connections) => { +export default (connections: {[key: string]: antares.Client}) => { ipcMain.handle('get-table-columns', async (event, params) => { try { const result = await connections[params.uid].getTableColumns(params); @@ -196,7 +198,8 @@ export default (connections) => { ipcMain.handle('delete-table-rows', async (event, params) => { if (params.primary) { - const idString = params.rows.map(row => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const idString = params.rows.map((row: {[key: string]: any}) => { const fieldName = Object.keys(row)[0].includes('.') ? `${params.table}.${params.primary}` : params.primary; return typeof row[fieldName] === 'string' @@ -245,7 +248,8 @@ export default (connections) => { ipcMain.handle('insert-table-rows', async (event, params) => { try { // TODO: move to client classes - const insertObj = {}; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const insertObj: {[key: string]: any} = {}; for (const key in params.row) { const type = params.fields[key]; let escapedParam; @@ -312,12 +316,14 @@ export default (connections) => { } }); - ipcMain.handle('insert-table-fake-rows', async (event, params) => { + ipcMain.handle('insert-table-fake-rows', async (event, params: InsertRowsParams) => { try { // TODO: move to client classes - const rows = []; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const rows: {[key: string]: any}[] = []; for (let i = 0; i < +params.repeat; i++) { - const insertObj = {}; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const insertObj: {[key: string]: any} = {}; for (const key in params.row) { const type = params.fields[key]; @@ -375,7 +381,8 @@ export default (connections) => { insertObj[key] = escapedParam; } else { // Faker value - const parsedParams = {}; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const parsedParams: {[key: string]: any} = {}; let fakeValue; if (params.locale) @@ -386,10 +393,12 @@ export default (connections) => { if (!isNaN(params.row[key].params[param])) parsedParams[param] = +params.row[key].params[param]; }); - fakeValue = faker[params.row[key].group][params.row[key].method](parsedParams); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + fakeValue = (faker as any)[params.row[key].group][params.row[key].method](parsedParams); } else - fakeValue = faker[params.row[key].group][params.row[key].method](); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + fakeValue = (faker as any)[params.row[key].group][params.row[key].method](); if (typeof fakeValue === 'string') { if (params.row[key].length) diff --git a/src/main/ipc-handlers/triggers.js b/src/main/ipc-handlers/triggers.ts similarity index 93% rename from src/main/ipc-handlers/triggers.js rename to src/main/ipc-handlers/triggers.ts index 89df9a15..b54786be 100644 --- a/src/main/ipc-handlers/triggers.js +++ b/src/main/ipc-handlers/triggers.ts @@ -1,6 +1,7 @@ +import * as antares from 'common/interfaces/antares'; import { ipcMain } from 'electron'; -export default (connections) => { +export default (connections: {[key: string]: antares.Client}) => { ipcMain.handle('get-trigger-informations', async (event, params) => { try { const result = await connections[params.uid].getTriggerInformations(params); diff --git a/src/main/ipc-handlers/updates.js b/src/main/ipc-handlers/updates.ts similarity index 85% rename from src/main/ipc-handlers/updates.js rename to src/main/ipc-handlers/updates.ts index 19636b52..b509d549 100644 --- a/src/main/ipc-handlers/updates.js +++ b/src/main/ipc-handlers/updates.ts @@ -4,8 +4,8 @@ import Store from 'electron-store'; const persistentStore = new Store({ name: 'settings' }); const isMacOS = process.platform === 'darwin'; -let mainWindow; -autoUpdater.allowPrerelease = persistentStore.get('allow_prerelease', true); +let mainWindow: Electron.IpcMainEvent; +autoUpdater.allowPrerelease = persistentStore.get('allow_prerelease', true) as boolean; export default () => { ipcMain.on('check-for-updates', event => { @@ -50,7 +50,7 @@ export default () => { mainWindow.reply('update-downloaded'); }); - autoUpdater.logger = require('electron-log'); - autoUpdater.logger.transports.console.format = '{h}:{i}:{s} {text}'; - autoUpdater.logger.transports.file.level = 'info'; + // autoUpdater.logger = require('electron-log'); + // autoUpdater.logger.transports.console.format = '{h}:{i}:{s} {text}'; + // autoUpdater.logger.transports.file.level = 'info'; }; diff --git a/src/main/ipc-handlers/users.js b/src/main/ipc-handlers/users.ts similarity index 78% rename from src/main/ipc-handlers/users.js rename to src/main/ipc-handlers/users.ts index 9631ee3c..8a1ff309 100644 --- a/src/main/ipc-handlers/users.js +++ b/src/main/ipc-handlers/users.ts @@ -1,6 +1,7 @@ +import * as antares from 'common/interfaces/antares'; import { ipcMain } from 'electron'; -export default (connections) => { +export default (connections: {[key: string]: antares.Client}) => { ipcMain.handle('get-users', async (event, uid) => { try { const result = await connections[uid].getUsers(); diff --git a/src/main/ipc-handlers/views.js b/src/main/ipc-handlers/views.ts similarity index 90% rename from src/main/ipc-handlers/views.js rename to src/main/ipc-handlers/views.ts index 7d48218e..63825497 100644 --- a/src/main/ipc-handlers/views.js +++ b/src/main/ipc-handlers/views.ts @@ -1,6 +1,7 @@ +import * as antares from 'common/interfaces/antares'; import { ipcMain } from 'electron'; -export default (connections) => { +export default (connections: {[key: string]: antares.Client}) => { ipcMain.handle('get-view-informations', async (event, params) => { try { const result = await connections[params.uid].getViewInformations(params); diff --git a/src/main/libs/AntaresCore.ts b/src/main/libs/AntaresCore.ts index c1e06e48..9803f016 100644 --- a/src/main/libs/AntaresCore.ts +++ b/src/main/libs/AntaresCore.ts @@ -13,7 +13,7 @@ const queryLogger = (sql: string) => { * As Simple As Possible Query Builder Core */ export class AntaresCore { - protected _client: string; + _client: antares.ClientCode; protected _params: mysql.ConnectionOptions | pg.ClientConfig | { databasePath: string; readonly: boolean}; protected _poolSize: number; protected _ssh?: SSH2Promise; @@ -34,8 +34,8 @@ export class AntaresCore { where: [], groupBy: [], orderBy: [], - limit: [], - offset: [], + limit: null, + offset: null, join: [], update: [], insert: [], @@ -113,13 +113,13 @@ export class AntaresCore { return this; } - limit (...args: string[]) { - this._query.limit = args; + limit (limit: number) { + this._query.limit = limit; return this; } - offset (...args: string[]) { - this._query.offset = args; + offset (offset: number) { + this._query.offset = offset; return this; } @@ -129,7 +129,8 @@ export class AntaresCore { return this; } - insert (arr: string[]) { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + insert (arr: {[key: string]: any}[]) { this._query.insert = [...this._query.insert, ...arr]; return this; } @@ -148,4 +149,112 @@ export class AntaresCore { this._resetQuery(); return this.raw>(rawQuery, args); } + + /* eslint-disable @typescript-eslint/no-unused-vars */ + /* eslint-disable @typescript-eslint/no-explicit-any */ + getDbConfig () { + throw new Error('Method "getDbConfig" not implemented'); + } + + createSchema (...args: any) { + throw new Error('Method "createSchema" not implemented'); + } + + alterSchema (...args: any) { + throw new Error('Method "alterSchema" not implemented'); + } + + dropSchema (...args: any) { + throw new Error('Method "dropSchema" not implemented'); + } + + getDatabaseCollation (...args: any) { + throw new Error('Method "getDatabaseCollation" not implemented'); + } + + getFunctionInformations (...args: any) { + throw new Error('Method "getFunctionInformations" not implemented'); + } + + alterFunction (...args: any) { + throw new Error('Method "alterFunction" not implemented'); + } + + createTriggerFunction (...args: any) { + throw new Error('Method "createTriggerFunction" not implemented'); + } + + alterTriggerFunction (...args: any) { + throw new Error('Method "alterTriggerFunction" not implemented'); + } + + createFunction (...args: any) { + throw new Error('Method "createFunction" not implemented'); + } + + dropFunction (...args: any) { + throw new Error('Method "dropFunction" not implemented'); + } + + getCollations () { + throw new Error('Method "getCollations" not implemented'); + } + + getRoutineInformations (...args: any) { + throw new Error('Method "getRoutineInformations" not implemented'); + } + + dropRoutine (...args: any) { + throw new Error('Method "dropRoutine" not implemented'); + } + + alterRoutine (...args: any) { + throw new Error('Method "alterRoutine" not implemented'); + } + + createRoutine (...args: any) { + throw new Error('Method "createRoutine" not implemented'); + } + + getVariables () { + throw new Error('Method "getVariables" not implemented'); + } + + getEventInformations (...args: any) { + throw new Error('Method "getEventInformations" not implemented'); + } + + dropEvent (...args: any) { + throw new Error('Method "dropEvent" not implemented'); + } + + alterEvent (...args: any) { + throw new Error('Method "alterEvent" not implemented'); + } + + createEvent (...args: any) { + throw new Error('Method "createEvent" not implemented'); + } + + enableEvent (...args: any) { + throw new Error('Method "enableEvent" not implemented'); + } + + disableEvent (...args: any) { + throw new Error('Method "disableEvent" not implemented'); + } + + enableTrigger (...args: any) { + throw new Error('Method "enableTrigger" not implemented'); + } + + disableTrigger (...args: any) { + throw new Error('Method "disableTrigger" not implemented'); + } + + killTabQuery (...args: any) { + throw new Error('Method "killTabQuery" not implemented'); + } + /* eslint-enable @typescript-eslint/no-unused-vars */ + /* eslint-enable @typescript-eslint/no-explicit-any */ } diff --git a/src/main/libs/clients/MySQLClient.ts b/src/main/libs/clients/MySQLClient.ts index 20dbc5a4..fe4df0ff 100644 --- a/src/main/libs/clients/MySQLClient.ts +++ b/src/main/libs/clients/MySQLClient.ts @@ -720,7 +720,13 @@ export class MySQLClient extends AntaresCore { } async getDatabaseCollation (params: { database: string }) { - return await this.raw(`SELECT \`DEFAULT_COLLATION_NAME\` FROM \`information_schema\`.\`SCHEMATA\` WHERE \`SCHEMA_NAME\`='${params.database}'`); + let collation: string; + const { rows: collaitons } = await this.raw>(`SELECT \`DEFAULT_COLLATION_NAME\` FROM \`information_schema\`.\`SCHEMATA\` WHERE \`SCHEMA_NAME\`='${params.database}'`); + + if (collaitons.length) + collation = collaitons[0].DEFAULT_COLLATION_NAME; + + return collation; } async createTable (params: antares.CreateTableParams) { @@ -1496,10 +1502,10 @@ export class MySQLClient extends AntaresCore { const orderByRaw = orderByArray.length ? `ORDER BY ${orderByArray.join(', ')} ` : ''; // LIMIT - const limitRaw = this._query.limit.length ? `LIMIT ${this._query.limit.join(', ')} ` : ''; + const limitRaw = this._query.limit ? `LIMIT ${this._query.limit} ` : ''; // OFFSET - const offsetRaw = this._query.offset.length ? `OFFSET ${this._query.offset.join(', ')} ` : ''; + const offsetRaw = this._query.offset ? `OFFSET ${this._query.offset} ` : ''; return `${selectRaw}${updateRaw ? 'UPDATE' : ''}${insertRaw ? 'INSERT ' : ''}${this._query.delete ? 'DELETE ' : ''}${fromRaw}${updateRaw}${whereRaw}${groupByRaw}${orderByRaw}${limitRaw}${offsetRaw}${insertRaw}`; } diff --git a/src/main/libs/clients/PostgreSQLClient.ts b/src/main/libs/clients/PostgreSQLClient.ts index d3ff3d12..f41a59df 100644 --- a/src/main/libs/clients/PostgreSQLClient.ts +++ b/src/main/libs/clients/PostgreSQLClient.ts @@ -82,11 +82,6 @@ export class PostgreSQLClient extends AntaresCore { return type.replace('_', ''); } - /** - * - * @returns dbConfig - * @memberof PostgreSQLClient - */ async getDbConfig () { this._params.application_name = 'Antares SQL'; @@ -1164,10 +1159,6 @@ export class PostgreSQLClient extends AntaresCore { return await this.raw(sql, { split: false }); } - async getCollations (): Promise { - return []; - } - async getVariables () { interface ShowVariablesResult { name: string; @@ -1305,10 +1296,10 @@ export class PostgreSQLClient extends AntaresCore { const orderByRaw = orderByArray.length ? `ORDER BY ${orderByArray.join(', ')} ` : ''; // LIMIT - const limitRaw = selectArray.length && this._query.limit.length ? `LIMIT ${this._query.limit.join(', ')} ` : ''; + const limitRaw = selectArray.length && this._query.limit ? `LIMIT ${this._query.limit} ` : ''; // OFFSET - const offsetRaw = selectArray.length && this._query.offset.length ? `OFFSET ${this._query.offset.join(', ')} ` : ''; + const offsetRaw = selectArray.length && this._query.offset ? `OFFSET ${this._query.offset} ` : ''; return `${selectRaw}${updateRaw ? 'UPDATE' : ''}${insertRaw ? 'INSERT ' : ''}${this._query.delete ? 'DELETE ' : ''}${fromRaw}${updateRaw}${whereRaw}${groupByRaw}${orderByRaw}${limitRaw}${offsetRaw}${insertRaw}`; } diff --git a/src/main/libs/clients/SQLiteClient.ts b/src/main/libs/clients/SQLiteClient.ts index 15755d58..4a11eba3 100644 --- a/src/main/libs/clients/SQLiteClient.ts +++ b/src/main/libs/clients/SQLiteClient.ts @@ -476,14 +476,6 @@ export class SQLiteClient extends AntaresCore { return await this.raw(sql, { split: false }); } - async getCollations (): Promise { - return []; - } - - async getVariables (): Promise { - return []; - } - async getEngines () { return { name: 'SQLite', @@ -585,10 +577,10 @@ export class SQLiteClient extends AntaresCore { const orderByRaw = orderByArray.length ? `ORDER BY ${orderByArray.join(', ')} ` : ''; // LIMIT - const limitRaw = this._query.limit.length ? `LIMIT ${this._query.limit.join(', ')} ` : ''; + const limitRaw = this._query.limit ? `LIMIT ${this._query.limit} ` : ''; // OFFSET - const offsetRaw = this._query.offset.length ? `OFFSET ${this._query.offset.join(', ')} ` : ''; + const offsetRaw = this._query.offset ? `OFFSET ${this._query.offset} ` : ''; return `${selectRaw}${updateRaw ? 'UPDATE' : ''}${insertRaw ? 'INSERT ' : ''}${this._query.delete ? 'DELETE ' : ''}${fromRaw}${updateRaw}${whereRaw}${groupByRaw}${orderByRaw}${limitRaw}${offsetRaw}${insertRaw}`; } diff --git a/src/main/libs/exporters/sql/SqlExporter.js b/src/main/libs/exporters/sql/SqlExporter.js index b5361e54..6c7f65f0 100644 --- a/src/main/libs/exporters/sql/SqlExporter.js +++ b/src/main/libs/exporters/sql/SqlExporter.js @@ -151,17 +151,20 @@ Generation time: ${moment().format()} return this.buildComment(`Dump completed on ${moment().format()}`); } - getCreateTable (tableName) { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + getCreateTable (_tableName) { throw new Error( 'Sql Exporter must implement the "getCreateTable" method' ); } - getDropTable (tableName) { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + getDropTable (_tableName) { throw new Error('Sql Exporter must implement the "getDropTable" method'); } - getTableInsert (tableName) { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + getTableInsert (_tableName) { throw new Error( 'Sql Exporter must implement the "getTableInsert" method' ); From ce0f278caf669192b6aa149811377619bbde63fe Mon Sep 17 00:00:00 2001 From: Fabio Di Stasio Date: Fri, 15 Apr 2022 23:13:23 +0200 Subject: [PATCH 09/14] refactor: db exporter ts refactor --- src/common/interfaces/antares.ts | 6 ++ src/common/interfaces/exporter.ts | 28 ++++++ src/main/ipc-handlers/schema.ts | 2 +- src/main/libs/clients/MySQLClient.ts | 14 +-- src/main/libs/clients/PostgreSQLClient.ts | 7 +- src/main/libs/clients/SQLiteClient.ts | 2 +- .../{BaseExporter.js => BaseExporter.ts} | 22 +++-- .../{MysqlExporter.js => MysqlExporter.ts} | 49 ++++++---- ...reSQLExporter.js => PostgreSQLExporter.ts} | 92 +++++++++++-------- .../sql/{SqlExporter.js => SqlExporter.ts} | 61 +++++++----- src/main/workers/{exporter.js => exporter.ts} | 17 ++-- webpack.workers.config.js | 2 +- 12 files changed, 198 insertions(+), 104 deletions(-) create mode 100644 src/common/interfaces/exporter.ts rename src/main/libs/exporters/{BaseExporter.js => BaseExporter.ts} (77%) rename src/main/libs/exporters/sql/{MysqlExporter.js => MysqlExporter.ts} (89%) rename src/main/libs/exporters/sql/{PostgreSQLExporter.js => PostgreSQLExporter.ts} (88%) rename src/main/libs/exporters/sql/{SqlExporter.js => SqlExporter.ts} (73%) rename src/main/workers/{exporter.js => exporter.ts} (72%) diff --git a/src/common/interfaces/antares.ts b/src/common/interfaces/antares.ts index 64fbeb75..20e11a61 100644 --- a/src/common/interfaces/antares.ts +++ b/src/common/interfaces/antares.ts @@ -1,5 +1,9 @@ import * as mysql from 'mysql2/promise'; import * as pg from 'pg'; +import MysqlExporter from 'src/main/libs/exporters/sql/MysqlExporter'; +import PostgreSQLExporter from 'src/main/libs/exporters/sql/PostgreSQLExporter'; +import MySQLImporter from 'src/main/libs/importers/sql/MysqlImporter'; +import PostgreSQLImporter from 'src/main/libs/importers/sql/PostgreSQLImporter'; import SSHConfig from 'ssh2-promise/lib/sshConfig'; import { MySQLClient } from '../../main/libs/clients/MySQLClient'; import { PostgreSQLClient } from '../../main/libs/clients/PostgreSQLClient'; @@ -7,6 +11,8 @@ import { SQLiteClient } from '../../main/libs/clients/SQLiteClient'; export type Client = MySQLClient | PostgreSQLClient | SQLiteClient export type ClientCode = 'mysql' | 'maria' | 'pg' | 'sqlite' +export type Exporter = MysqlExporter | PostgreSQLExporter +export type Importer = MySQLImporter | PostgreSQLImporter /** * Pasameters needed to create a new Antares connection to a database diff --git a/src/common/interfaces/exporter.ts b/src/common/interfaces/exporter.ts new file mode 100644 index 00000000..72592051 --- /dev/null +++ b/src/common/interfaces/exporter.ts @@ -0,0 +1,28 @@ +export interface TableParams { + table: string; + includeStructure: boolean; + includeContent: boolean; + includeDropStatement: boolean; +} + +export interface ExportOptions { + schema: string; + includes: { + functions: boolean; + views: boolean; + triggers: boolean; + routines: boolean; + schedulers: boolean; + }; + outputFormat: 'sql' | 'sql.zip'; + outputFile: string; + sqlInsertAfter: number; + sqlInsertDivider: 'bytes' | 'rows'; +} + +export interface ExportState { + totalItems?: number; + currentItemIndex?: number; + currentItem?: string; + op?: string; +} diff --git a/src/main/ipc-handlers/schema.ts b/src/main/ipc-handlers/schema.ts index 475fa53c..81bfd328 100644 --- a/src/main/ipc-handlers/schema.ts +++ b/src/main/ipc-handlers/schema.ts @@ -1,6 +1,6 @@ import * as antares from 'common/interfaces/antares'; import * as workers from 'common/interfaces/workers'; -import fs from 'fs'; +import * as fs from 'fs'; import path from 'path'; import { ChildProcess, fork } from 'child_process'; import { ipcMain, dialog } from 'electron'; diff --git a/src/main/libs/clients/MySQLClient.ts b/src/main/libs/clients/MySQLClient.ts index fe4df0ff..42aa59f0 100644 --- a/src/main/libs/clients/MySQLClient.ts +++ b/src/main/libs/clients/MySQLClient.ts @@ -9,8 +9,8 @@ export class MySQLClient extends AntaresCore { private _schema?: string; private _runningConnections: Map; private _connectionsToCommit: Map; - protected _connection?: mysql.Connection | mysql.Pool; - protected _params: mysql.ConnectionOptions & {schema: string; ssl?: mysql.SslOptions; ssh?: SSHConfig; readonly: boolean}; + _connection?: mysql.Connection | mysql.Pool; + _params: mysql.ConnectionOptions & {schema: string; ssl?: mysql.SslOptions; ssh?: SSHConfig; readonly: boolean}; private types: {[key: number]: string} = { 0: 'DECIMAL', @@ -445,18 +445,18 @@ export class MySQLClient extends AntaresCore { async getTableColumns ({ schema, table }: { schema: string; table: string }) { interface TableColumnsResult { COLUMN_TYPE: string; - NUMERIC_PRECISION: string; + NUMERIC_PRECISION: number; COLUMN_NAME: string; COLUMN_DEFAULT: string; COLUMN_KEY: string; DATA_TYPE: string; TABLE_SCHEMA: string; TABLE_NAME: string; - NUMERIC_SCALE: string; - DATETIME_PRECISION: string; - CHARACTER_MAXIMUM_LENGTH: string; + NUMERIC_SCALE: number; + DATETIME_PRECISION: number; + CHARACTER_MAXIMUM_LENGTH: number; IS_NULLABLE: string; - ORDINAL_POSITION: string; + ORDINAL_POSITION: number; CHARACTER_SET_NAME: string; COLLATION_NAME: string; EXTRA: string; diff --git a/src/main/libs/clients/PostgreSQLClient.ts b/src/main/libs/clients/PostgreSQLClient.ts index f41a59df..d84dd2cb 100644 --- a/src/main/libs/clients/PostgreSQLClient.ts +++ b/src/main/libs/clients/PostgreSQLClient.ts @@ -24,7 +24,6 @@ export class PostgreSQLClient extends AntaresCore { private _runningConnections: Map; private _connectionsToCommit: Map; protected _connection?: pg.Client | pg.Pool; - protected _params: pg.ClientConfig & {schema: string; ssl?: mysql.SslOptions; ssh?: SSHConfig; readonly: boolean}; private types: {[key: string]: string} = {}; private _arrayTypes: {[key: string]: string} = { _int2: 'SMALLINT', @@ -36,6 +35,8 @@ export class PostgreSQLClient extends AntaresCore { _varchar: 'CHARACTER VARYING' } + _params: pg.ClientConfig & {schema: string; ssl?: mysql.SslOptions; ssh?: SSHConfig; readonly: boolean}; + constructor (args: antares.ClientParams) { super(args); @@ -173,6 +174,10 @@ export class PostgreSQLClient extends AntaresCore { } } + getCollations (): null[] { + return []; + } + async getStructure (schemas: Set) { /* eslint-disable camelcase */ interface ShowTableResult { diff --git a/src/main/libs/clients/SQLiteClient.ts b/src/main/libs/clients/SQLiteClient.ts index 4a11eba3..de7893f9 100644 --- a/src/main/libs/clients/SQLiteClient.ts +++ b/src/main/libs/clients/SQLiteClient.ts @@ -8,7 +8,7 @@ export class SQLiteClient extends AntaresCore { private _schema?: string; private _connectionsToCommit: Map; protected _connection?: sqlite.Database; - protected _params: { databasePath: string; readonly: boolean}; + _params: { databasePath: string; readonly: boolean}; constructor (args: antares.ClientParams) { super(args); diff --git a/src/main/libs/exporters/BaseExporter.js b/src/main/libs/exporters/BaseExporter.ts similarity index 77% rename from src/main/libs/exporters/BaseExporter.js rename to src/main/libs/exporters/BaseExporter.ts index 2870f96f..98b19157 100644 --- a/src/main/libs/exporters/BaseExporter.js +++ b/src/main/libs/exporters/BaseExporter.ts @@ -1,10 +1,18 @@ -import fs from 'fs'; -import { createGzip } from 'zlib'; -import path from 'path'; -import EventEmitter from 'events'; +import * as exporter from 'common/interfaces/exporter'; +import * as fs from 'fs'; +import { createGzip, Gzip } from 'zlib'; +import * as path from 'path'; +import * as EventEmitter from 'events'; export class BaseExporter extends EventEmitter { - constructor (tables, options) { + protected _tables; + protected _options; + protected _isCancelled; + protected _outputFileStream: fs.WriteStream; + protected _processedStream: fs.WriteStream | Gzip; + protected _state; + + constructor (tables: exporter.TableParams[], options: exporter.ExportOptions) { super(); this._tables = tables; this._options = options; @@ -60,11 +68,11 @@ export class BaseExporter extends EventEmitter { this.emitUpdate({ op: 'cancelling' }); } - emitUpdate (state) { + emitUpdate (state: exporter.ExportState) { this.emit('progress', { ...this._state, ...state }); } - writeString (data) { + writeString (data: string) { if (this._isCancelled) return; try { diff --git a/src/main/libs/exporters/sql/MysqlExporter.js b/src/main/libs/exporters/sql/MysqlExporter.ts similarity index 89% rename from src/main/libs/exporters/sql/MysqlExporter.js rename to src/main/libs/exporters/sql/MysqlExporter.ts index c5460ba4..ce87db0b 100644 --- a/src/main/libs/exporters/sql/MysqlExporter.js +++ b/src/main/libs/exporters/sql/MysqlExporter.ts @@ -1,14 +1,20 @@ +import * as exporter from 'common/interfaces/exporter'; +import * as mysql from 'mysql2/promise'; import { SqlExporter } from './SqlExporter'; import { BLOB, BIT, DATE, DATETIME, FLOAT, SPATIAL, IS_MULTI_SPATIAL, NUMBER } from 'common/fieldTypes'; import hexToBinary from 'common/libs/hexToBinary'; import { getArrayDepth } from 'common/libs/getArrayDepth'; -import moment from 'moment'; +import * as moment from 'moment'; import { lineString, point, polygon } from '@turf/helpers'; +import { MySQLClient } from '../../clients/MySQLClient'; export default class MysqlExporter extends SqlExporter { - constructor (...args) { - super(...args); + protected _client: MySQLClient; + constructor (client: MySQLClient, tables: exporter.TableParams[], options: exporter.ExportOptions) { + super(tables, options); + + this._client = client; this._commentChar = '#'; } @@ -42,7 +48,7 @@ ${footer} `; } - async getCreateTable (tableName) { + async getCreateTable (tableName: string) { const { rows } = await this._client.raw( `SHOW CREATE TABLE \`${this.schemaName}\`.\`${tableName}\`` ); @@ -54,11 +60,11 @@ ${footer} return rows[0][col] + ';'; } - getDropTable (tableName) { + getDropTable (tableName: string) { return `DROP TABLE IF EXISTS \`${tableName}\`;`; } - async * getTableInsert (tableName) { + async * getTableInsert (tableName: string) { let rowCount = 0; let sqlStr = ''; @@ -109,7 +115,7 @@ ${footer} queryLength = 0; rowsWritten = 0; } - else if (parseInt(rowIndex) === 0) sqlInsertString += '\n\t('; + else if (rowIndex === 0) sqlInsertString += '\n\t('; else sqlInsertString += ',\n\t('; for (const i in notGeneratedColumns) { @@ -124,7 +130,7 @@ ${footer} } else if (DATETIME.includes(column.type)) { let datePrecision = ''; - for (let i = 0; i < column.precision; i++) + for (let i = 0; i < column.datePrecision; i++) datePrecision += i === 0 ? '.S' : 'S'; sqlInsertString += moment(val).isValid() @@ -144,7 +150,7 @@ ${footer} if (IS_MULTI_SPATIAL.includes(column.type)) { const features = []; for (const element of val) - features.push(this.getMarkers(element)); + features.push(this._getGeoJSON(element)); geoJson = { type: 'FeatureCollection', @@ -323,7 +329,7 @@ ${footer} return sqlString; } - async getRoutineSyntax (name, type, definer) { + async getRoutineSyntax (name: string, type: string, definer: string) { const { rows: routines } = await this._client.raw( `SHOW CREATE ${type} \`${this.schemaName}\`.\`${name}\`` ); @@ -353,12 +359,13 @@ ${footer} return sqlString; } - async _queryStream (sql) { + async _queryStream (sql: string) { if (process.env.NODE_ENV === 'development') console.log('EXPORTER:', sql); - const isPool = typeof this._client._connection.getConnection === 'function'; - const connection = isPool ? await this._client._connection.getConnection() : this._client._connection; - const stream = connection.connection.query(sql).stream(); - const dispose = () => connection.destroy(); + const isPool = 'getConnection' in this._client._connection; + const connection = isPool ? await (this._client._connection as mysql.Pool).getConnection() : this._client._connection; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const stream = (connection as any).connection.query(sql).stream(); + const dispose = () => (connection as mysql.PoolConnection).release(); stream.on('end', dispose); stream.on('error', dispose); @@ -366,17 +373,17 @@ ${footer} return stream; } - getEscapedDefiner (definer) { + getEscapedDefiner (definer: string) { return definer .split('@') .map(part => '`' + part + '`') .join('@'); } - escapeAndQuote (val) { + escapeAndQuote (val: string) { // eslint-disable-next-line no-control-regex const CHARS_TO_ESCAPE = /[\0\b\t\n\r\x1a"'\\]/g; - const CHARS_ESCAPE_MAP = { + const CHARS_ESCAPE_MAP: {[key: string]: string} = { '\0': '\\0', '\b': '\\b', '\t': '\\t', @@ -405,14 +412,16 @@ ${footer} return `'${escapedVal}'`; } - _getGeoJSON (val) { + /* eslint-disable @typescript-eslint/no-explicit-any */ + _getGeoJSON (val: any) { if (Array.isArray(val)) { if (getArrayDepth(val) === 1) return lineString(val.reduce((acc, curr) => [...acc, [curr.x, curr.y]], [])); else - return polygon(val.map(arr => arr.reduce((acc, curr) => [...acc, [curr.x, curr.y]], []))); + return polygon(val.map(arr => arr.reduce((acc: any, curr: any) => [...acc, [curr.x, curr.y]], []))); } else return point([val.x, val.y]); } + /* eslint-enable @typescript-eslint/no-explicit-any */ } diff --git a/src/main/libs/exporters/sql/PostgreSQLExporter.js b/src/main/libs/exporters/sql/PostgreSQLExporter.ts similarity index 88% rename from src/main/libs/exporters/sql/PostgreSQLExporter.js rename to src/main/libs/exporters/sql/PostgreSQLExporter.ts index c4815ad9..b2feb460 100644 --- a/src/main/libs/exporters/sql/PostgreSQLExporter.js +++ b/src/main/libs/exporters/sql/PostgreSQLExporter.ts @@ -1,12 +1,21 @@ +import * as antares from 'common/interfaces/antares'; +import * as exporter from 'common/interfaces/exporter'; import { SqlExporter } from './SqlExporter'; import { BLOB, BIT, DATE, DATETIME, FLOAT, NUMBER, TEXT_SEARCH } from 'common/fieldTypes'; import hexToBinary from 'common/libs/hexToBinary'; -import { getArrayDepth } from 'common/libs/getArrayDepth'; -import moment from 'moment'; -import { lineString, point, polygon } from '@turf/helpers'; -import QueryStream from 'pg-query-stream'; +import * as moment from 'moment'; +// eslint-disable-next-line @typescript-eslint/ban-ts-comment +// @ts-ignore +import * as QueryStream from 'pg-query-stream'; +import { PostgreSQLClient } from '../../clients/PostgreSQLClient'; export default class PostgreSQLExporter extends SqlExporter { + constructor (client: PostgreSQLClient, tables: exporter.TableParams[], options: exporter.ExportOptions) { + super(tables, options); + + this._client = client; + } + async getSqlHeader () { let dump = await super.getSqlHeader(); dump += ` @@ -30,11 +39,28 @@ SET row_security = off;\n\n\n`; return dump; } - async getCreateTable (tableName) { + async getCreateTable (tableName: string) { + /* eslint-disable camelcase */ + interface SequenceRecord { + sequence_catalog: string; + sequence_schema: string; + sequence_name: string; + data_type: string; + numeric_precision: number; + numeric_precision_radix: number; + numeric_scale: number; + start_value: string; + minimum_value: string; + maximum_value: string; + increment: string; + cycle_option: string; + } + /* eslint-enable camelcase */ + let createSql = ''; const sequences = []; const columnsSql = []; - const arrayTypes = { + const arrayTypes: {[key: string]: string} = { _int2: 'smallint', _int4: 'integer', _int8: 'bigint', @@ -60,7 +86,7 @@ SET row_security = off;\n\n\n`; if (fieldType === 'USER-DEFINED') fieldType = `"${this.schemaName}".${column.udt_name}`; else if (fieldType === 'ARRAY') { if (Object.keys(arrayTypes).includes(fieldType)) - fieldType = arrayTypes[type] + '[]'; + fieldType = arrayTypes[column.udt_name] + '[]'; else fieldType = column.udt_name.replaceAll('_', '') + '[]'; } @@ -91,7 +117,7 @@ SET row_security = off;\n\n\n`; .schema('information_schema') .from('sequences') .where({ sequence_schema: `= '${this.schemaName}'`, sequence_name: `= '${sequence}'` }) - .run(); + .run(); if (rows.length) { createSql += `CREATE SEQUENCE "${this.schemaName}"."${sequence}" @@ -119,7 +145,7 @@ SET row_security = off;\n\n\n`; .schema('pg_catalog') .from('pg_indexes') .where({ schemaname: `= '${this.schemaName}'`, tablename: `= '${tableName}'` }) - .run(); + .run<{indexdef: string}>(); for (const index of indexes) createSql += `${index.indexdef};\n`; @@ -157,11 +183,11 @@ SET row_security = off;\n\n\n`; return createSql; } - getDropTable (tableName) { + getDropTable (tableName: string) { return `DROP TABLE IF EXISTS "${this.schemaName}"."${tableName}";`; } - async * getTableInsert (tableName) { + async * getTableInsert (tableName: string) { let rowCount = 0; const sqlStr = ''; @@ -205,14 +231,14 @@ SET row_security = off;\n\n\n`; } else if (DATETIME.includes(column.type)) { let datePrecision = ''; - for (let i = 0; i < column.precision; i++) + for (let i = 0; i < column.datePrecision; i++) datePrecision += i === 0 ? '.S' : 'S'; sqlInsertString += moment(val).isValid() ? this.escapeAndQuote(moment(val).format(`YYYY-MM-DD HH:mm:ss${datePrecision}`)) : this.escapeAndQuote(val); } - else if (column.isArray) { + else if ('isArray' in column) { let parsedVal; if (Array.isArray(val)) parsedVal = JSON.stringify(val).replaceAll('[', '{').replaceAll(']', '}'); @@ -254,7 +280,7 @@ SET row_security = off;\n\n\n`; async getCreateTypes () { let sqlString = ''; - const { rows: types } = await this._client.raw(` + const { rows: types } = await this._client.raw>(` SELECT pg_type.typname, pg_enum.enumlabel FROM pg_type JOIN pg_enum ON pg_enum.enumtypid = pg_type.oid; @@ -360,6 +386,15 @@ SET row_security = off;\n\n\n`; } async getTriggers () { + /* eslint-disable camelcase */ + interface TriggersResult { + event_object_table: string; + table_name: string; + trigger_name: string; + events: string[]; + event_manipulation: string; + } + /* eslint-enable camelcase */ let sqlString = ''; // Trigger functions @@ -374,7 +409,7 @@ SET row_security = off;\n\n\n`; sqlString += `\n${functionDef[0].definition};\n`; } - const { rows: triggers } = await this._client.raw( + const { rows: triggers } = await this._client.raw>( `SELECT * FROM "information_schema"."triggers" WHERE "trigger_schema"='${this.schemaName}'` ); @@ -430,11 +465,12 @@ SET row_security = off;\n\n\n`; return sqlString; } - async _queryStream (sql) { + async _queryStream (sql: string) { if (process.env.NODE_ENV === 'development') console.log('EXPORTER:', sql); const connection = await this._client.getConnection(); const query = new QueryStream(sql, null); - const stream = connection.query(query); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const stream = (connection as any).query(query); const dispose = () => connection.end(); stream.on('end', dispose); @@ -443,17 +479,10 @@ SET row_security = off;\n\n\n`; return stream; } - getEscapedDefiner (definer) { - return definer - .split('@') - .map(part => '`' + part + '`') - .join('@'); - } - - escapeAndQuote (val) { + escapeAndQuote (val: string) { // eslint-disable-next-line no-control-regex const CHARS_TO_ESCAPE = /[\0\b\t\n\r\x1a"'\\]/g; - const CHARS_ESCAPE_MAP = { + const CHARS_ESCAPE_MAP: {[key: string]: string} = { '\0': '\\0', '\b': '\\b', '\t': '\\t', @@ -481,15 +510,4 @@ SET row_security = off;\n\n\n`; return `'${escapedVal}'`; } - - _getGeoJSON (val) { - if (Array.isArray(val)) { - if (getArrayDepth(val) === 1) - return lineString(val.reduce((acc, curr) => [...acc, [curr.x, curr.y]], [])); - else - return polygon(val.map(arr => arr.reduce((acc, curr) => [...acc, [curr.x, curr.y]], []))); - } - else - return point([val.x, val.y]); - } } diff --git a/src/main/libs/exporters/sql/SqlExporter.js b/src/main/libs/exporters/sql/SqlExporter.ts similarity index 73% rename from src/main/libs/exporters/sql/SqlExporter.js rename to src/main/libs/exporters/sql/SqlExporter.ts index 6c7f65f0..cd42f501 100644 --- a/src/main/libs/exporters/sql/SqlExporter.js +++ b/src/main/libs/exporters/sql/SqlExporter.ts @@ -1,13 +1,12 @@ -import moment from 'moment'; +import * as moment from 'moment'; +import { MySQLClient } from '../../clients/MySQLClient'; +import { PostgreSQLClient } from '../../clients/PostgreSQLClient'; import { BaseExporter } from '../BaseExporter'; export class SqlExporter extends BaseExporter { - constructor (client, tables, options) { - super(tables, options); - this._client = client; - this._commentChar = '--'; - this._postTablesSql = ''; - } + protected _client: MySQLClient | PostgreSQLClient; + protected _commentChar = '--' + protected _postTablesSql = '' get schemaName () { return this._options.schema; @@ -24,7 +23,7 @@ export class SqlExporter extends BaseExporter { async dump () { const { includes } = this._options; - const extraItems = Object.keys(includes).filter(key => includes[key]); + const extraItems = Object.keys(includes).filter((key: 'functions' | 'views' | 'triggers' | 'routines' | 'schedulers') => includes[key]); const totalTableToProcess = this._tables.filter( t => t.includeStructure || t.includeContent || t.includeDropStatement ).length; @@ -98,14 +97,15 @@ export class SqlExporter extends BaseExporter { } for (const item of extraItems) { - const processingMethod = `get${item.charAt(0).toUpperCase() + item.slice(1)}`; + type exporterMethods = 'getViews' | 'getTriggers' | 'getSchedulers' | 'getFunctions' | 'getRoutines' + const processingMethod = `get${item.charAt(0).toUpperCase() + item.slice(1)}` as exporterMethods; exportState.currentItemIndex++; exportState.currentItem = item; exportState.op = 'PROCESSING'; this.emitUpdate(exportState); if (this[processingMethod]) { - const data = await this[processingMethod](); + const data = await this[processingMethod]() as unknown as string; if (data !== '') { const header = this.buildComment( @@ -123,7 +123,7 @@ export class SqlExporter extends BaseExporter { this.writeString(footer); } - buildComment (text) { + buildComment (text: string) { return text .split('\n') .map(txt => `${this._commentChar} ${txt}`) @@ -151,22 +151,37 @@ Generation time: ${moment().format()} return this.buildComment(`Dump completed on ${moment().format()}`); } - // eslint-disable-next-line @typescript-eslint/no-unused-vars - getCreateTable (_tableName) { - throw new Error( - 'Sql Exporter must implement the "getCreateTable" method' - ); + /* eslint-disable @typescript-eslint/no-unused-vars */ + getCreateTable (_tableName: string): Promise { + throw new Error('Sql Exporter must implement the "getCreateTable" method'); } - // eslint-disable-next-line @typescript-eslint/no-unused-vars - getDropTable (_tableName) { + getDropTable (_tableName: string): string { throw new Error('Sql Exporter must implement the "getDropTable" method'); } - // eslint-disable-next-line @typescript-eslint/no-unused-vars - getTableInsert (_tableName) { - throw new Error( - 'Sql Exporter must implement the "getTableInsert" method' - ); + getTableInsert (_tableName: string): AsyncGenerator { + throw new Error('Sql Exporter must implement the "getTableInsert" method'); } + + getViews () { + throw new Error('Method "getViews" not implemented'); + } + + getTriggers () { + throw new Error('Method "getTriggers" not implemented'); + } + + getSchedulers () { + throw new Error('Method "getSchedulers" not implemented'); + } + + getFunctions () { + throw new Error('Method "getFunctions" not implemented'); + } + + getRoutines () { + throw new Error('Method "getRoutines" not implemented'); + } + /* eslint-enable @typescript-eslint/no-unused-vars */ } diff --git a/src/main/workers/exporter.js b/src/main/workers/exporter.ts similarity index 72% rename from src/main/workers/exporter.js rename to src/main/workers/exporter.ts index 7e96c78f..9f03826b 100644 --- a/src/main/workers/exporter.js +++ b/src/main/workers/exporter.ts @@ -1,8 +1,11 @@ -import fs from 'fs'; +import * as antares from 'common/interfaces/antares'; +import * as fs from 'fs'; +import { MySQLClient } from '../libs/clients/MySQLClient'; +import { PostgreSQLClient } from '../libs/clients/PostgreSQLClient'; import { ClientsFactory } from '../libs/ClientsFactory'; -import MysqlExporter from '../libs/exporters/sql/MysqlExporter.js'; +import MysqlExporter from '../libs/exporters/sql/MysqlExporter'; import PostgreSQLExporter from '../libs/exporters/sql/PostgreSQLExporter'; -let exporter; +let exporter: antares.Exporter; process.on('message', async ({ type, client, tables, options }) => { if (type === 'init') { @@ -10,16 +13,16 @@ process.on('message', async ({ type, client, tables, options }) => { client: client.name, params: client.config, poolSize: 5 - }); + }) as MySQLClient | PostgreSQLClient; await connection.connect(); switch (client.name) { case 'mysql': case 'maria': - exporter = new MysqlExporter(connection, tables, options); + exporter = new MysqlExporter(connection as MySQLClient, tables, options); break; case 'pg': - exporter = new PostgreSQLExporter(connection, tables, options); + exporter = new PostgreSQLExporter(connection as PostgreSQLClient, tables, options); break; default: process.send({ @@ -62,3 +65,5 @@ process.on('message', async ({ type, client, tables, options }) => { else if (type === 'cancel') exporter.cancel(); }); + +process.on('beforeExit', console.log); diff --git a/webpack.workers.config.js b/webpack.workers.config.js index 29397557..ce354055 100644 --- a/webpack.workers.config.js +++ b/webpack.workers.config.js @@ -13,7 +13,7 @@ const config = { mode: process.env.NODE_ENV, devtool: isDevMode ? 'eval-source-map' : false, entry: { - exporter: path.join(__dirname, './src/main/workers/exporter.js'), + exporter: path.join(__dirname, './src/main/workers/exporter.ts'), importer: path.join(__dirname, './src/main/workers/importer.js') }, target: 'node', From 79f32ca442b0f6597caf52391b2db80926ce5781 Mon Sep 17 00:00:00 2001 From: Fabio Di Stasio Date: Sun, 17 Apr 2022 12:01:07 +0200 Subject: [PATCH 10/14] refactor: db import ts refactor --- src/common/interfaces/antares.ts | 2 +- src/common/interfaces/importer.ts | 16 +++++++++++ src/common/interfaces/parser.ts | 0 .../{BaseImporter.js => BaseImporter.ts} | 14 +++++++--- .../{MysqlImporter.js => MySQLlImporter.ts} | 13 ++++++--- ...reSQLImporter.js => PostgreSQLImporter.ts} | 11 ++++++-- .../{MySQLParser.js => MySQLParser.ts} | 22 +++++++++++---- ...ostgreSQLParser.js => PostgreSQLParser.ts} | 28 +++++++++++++++---- src/main/workers/{importer.js => importer.ts} | 15 ++++++---- 9 files changed, 92 insertions(+), 29 deletions(-) create mode 100644 src/common/interfaces/importer.ts create mode 100644 src/common/interfaces/parser.ts rename src/main/libs/importers/{BaseImporter.js => BaseImporter.ts} (76%) rename src/main/libs/importers/sql/{MysqlImporter.js => MySQLlImporter.ts} (83%) rename src/main/libs/importers/sql/{PostgreSQLImporter.js => PostgreSQLImporter.ts} (85%) rename src/main/libs/parsers/{MySQLParser.js => MySQLParser.ts} (80%) rename src/main/libs/parsers/{PostgreSQLParser.js => PostgreSQLParser.ts} (83%) rename src/main/workers/{importer.js => importer.ts} (74%) diff --git a/src/common/interfaces/antares.ts b/src/common/interfaces/antares.ts index 20e11a61..9e75a34d 100644 --- a/src/common/interfaces/antares.ts +++ b/src/common/interfaces/antares.ts @@ -2,7 +2,7 @@ import * as mysql from 'mysql2/promise'; import * as pg from 'pg'; import MysqlExporter from 'src/main/libs/exporters/sql/MysqlExporter'; import PostgreSQLExporter from 'src/main/libs/exporters/sql/PostgreSQLExporter'; -import MySQLImporter from 'src/main/libs/importers/sql/MysqlImporter'; +import MySQLImporter from 'src/main/libs/importers/sql/MySQLlImporter'; import PostgreSQLImporter from 'src/main/libs/importers/sql/PostgreSQLImporter'; import SSHConfig from 'ssh2-promise/lib/sshConfig'; import { MySQLClient } from '../../main/libs/clients/MySQLClient'; diff --git a/src/common/interfaces/importer.ts b/src/common/interfaces/importer.ts new file mode 100644 index 00000000..d0aa985a --- /dev/null +++ b/src/common/interfaces/importer.ts @@ -0,0 +1,16 @@ +import * as antares from './antares'; + +export interface ImportOptions { + uid: string; + schema: string; + type: antares.ClientCode; + file: string; +} + +export interface ImportState { + fileSize?: number; + readPosition?: number; + percentage?: number; + queryCount?: number; + op?: string; +} diff --git a/src/common/interfaces/parser.ts b/src/common/interfaces/parser.ts new file mode 100644 index 00000000..e69de29b diff --git a/src/main/libs/importers/BaseImporter.js b/src/main/libs/importers/BaseImporter.ts similarity index 76% rename from src/main/libs/importers/BaseImporter.js rename to src/main/libs/importers/BaseImporter.ts index 901a5d98..807aedea 100644 --- a/src/main/libs/importers/BaseImporter.js +++ b/src/main/libs/importers/BaseImporter.ts @@ -1,8 +1,14 @@ -import fs from 'fs'; -import EventEmitter from 'events'; +import * as importer from 'common/interfaces/importer'; +import * as fs from 'fs'; +import * as EventEmitter from 'events'; export class BaseImporter extends EventEmitter { - constructor (options) { + protected _options; + protected _isCancelled; + protected _fileHandler; + protected _state; + + constructor (options: importer.ImportOptions) { super(); this._options = options; this._isCancelled = false; @@ -43,7 +49,7 @@ export class BaseImporter extends EventEmitter { this.emitUpdate({ op: 'cancelling' }); } - emitUpdate (state) { + emitUpdate (state: importer.ImportState) { this.emit('progress', { ...this._state, ...state }); } diff --git a/src/main/libs/importers/sql/MysqlImporter.js b/src/main/libs/importers/sql/MySQLlImporter.ts similarity index 83% rename from src/main/libs/importers/sql/MysqlImporter.js rename to src/main/libs/importers/sql/MySQLlImporter.ts index dc9349da..51c8e418 100644 --- a/src/main/libs/importers/sql/MysqlImporter.js +++ b/src/main/libs/importers/sql/MySQLlImporter.ts @@ -1,14 +1,18 @@ -import fs from 'fs/promises'; +import * as mysql from 'mysql2'; +import * as importer from 'common/interfaces/importer'; +import * as fs from 'fs/promises'; import MySQLParser from '../../parsers/MySQLParser'; import { BaseImporter } from '../BaseImporter'; export default class MySQLImporter extends BaseImporter { - constructor (client, options) { + protected _client: mysql.Pool + + constructor (client: mysql.Pool, options: importer.ImportOptions) { super(options); this._client = client; } - async import () { + async import (): Promise { try { const { size: totalFileSize } = await fs.stat(this._options.file); const parser = new MySQLParser(); @@ -60,7 +64,8 @@ export default class MySQLImporter extends BaseImporter { parser.on('pause', () => { this._fileHandler.unpipe(parser); - this._fileHandler.readableFlowing = false; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (this._fileHandler as any).readableFlowing = false; }); this._fileHandler.on('data', (chunk) => { diff --git a/src/main/libs/importers/sql/PostgreSQLImporter.js b/src/main/libs/importers/sql/PostgreSQLImporter.ts similarity index 85% rename from src/main/libs/importers/sql/PostgreSQLImporter.js rename to src/main/libs/importers/sql/PostgreSQLImporter.ts index a18f22c9..eb6a79fd 100644 --- a/src/main/libs/importers/sql/PostgreSQLImporter.js +++ b/src/main/libs/importers/sql/PostgreSQLImporter.ts @@ -1,14 +1,18 @@ +import * as pg from 'pg'; +import * as importer from 'common/interfaces/importer'; import fs from 'fs/promises'; import PostgreSQLParser from '../../parsers/PostgreSQLParser'; import { BaseImporter } from '../BaseImporter'; export default class PostgreSQLImporter extends BaseImporter { - constructor (client, options) { + protected _client: pg.PoolClient; + + constructor (client: pg.PoolClient, options: importer.ImportOptions) { super(options); this._client = client; } - async import () { + async import (): Promise { try { const { size: totalFileSize } = await fs.stat(this._options.file); const parser = new PostgreSQLParser(); @@ -60,7 +64,8 @@ export default class PostgreSQLImporter extends BaseImporter { parser.on('pause', () => { this._fileHandler.unpipe(parser); - this._fileHandler.readableFlowing = false; + // eslint-disable-next-line @typescript-eslint/no-explicit-any + (this._fileHandler as any).readableFlowing = false; }); this._fileHandler.on('data', (chunk) => { diff --git a/src/main/libs/parsers/MySQLParser.js b/src/main/libs/parsers/MySQLParser.ts similarity index 80% rename from src/main/libs/parsers/MySQLParser.js rename to src/main/libs/parsers/MySQLParser.ts index 3157a5e3..66bdd997 100644 --- a/src/main/libs/parsers/MySQLParser.js +++ b/src/main/libs/parsers/MySQLParser.ts @@ -1,7 +1,17 @@ -import { Transform } from 'stream'; +import { Transform, TransformCallback, TransformOptions } from 'stream'; export default class MySQLParser extends Transform { - constructor (opts) { + private _buffer: string; + private _lastChar: string; + private _last9Chars: string; + + encoding: BufferEncoding; + delimiter: string; + isEscape: boolean; + currentQuote: string; + isDelimiter: boolean; + + constructor (opts?: TransformOptions & { delimiter: string }) { opts = { delimiter: ';', encoding: 'utf8', @@ -21,7 +31,7 @@ export default class MySQLParser extends Transform { this.isDelimiter = false; } - _transform (chunk, encoding, next) { + _transform (chunk: Buffer, encoding: BufferEncoding, next: TransformCallback) { for (const char of chunk.toString(this.encoding)) { this.checkEscape(); this._buffer += char; @@ -49,7 +59,7 @@ export default class MySQLParser extends Transform { } } - checkNewDelimiter (char) { + checkNewDelimiter (char: string) { if (this.currentQuote === null && this._last9Chars === 'delimiter') { this.isDelimiter = true; this._buffer = ''; @@ -64,7 +74,7 @@ export default class MySQLParser extends Transform { } } - checkQuote (char) { + checkQuote (char: string) { const isQuote = !this.isEscape && (char === '\'' || char === '"'); if (isQuote && this.currentQuote === char) this.currentQuote = null; @@ -77,7 +87,7 @@ export default class MySQLParser extends Transform { if (this.isDelimiter) return false; - let query = false; + let query: false | string = false; let demiliterFound = false; if (this.currentQuote === null && this._buffer.length >= this.delimiter.length) demiliterFound = this._last9Chars.slice(-this.delimiter.length) === this.delimiter; diff --git a/src/main/libs/parsers/PostgreSQLParser.js b/src/main/libs/parsers/PostgreSQLParser.ts similarity index 83% rename from src/main/libs/parsers/PostgreSQLParser.js rename to src/main/libs/parsers/PostgreSQLParser.ts index 883a07ac..a4a35cfa 100644 --- a/src/main/libs/parsers/PostgreSQLParser.js +++ b/src/main/libs/parsers/PostgreSQLParser.ts @@ -1,7 +1,23 @@ -import { Transform } from 'stream'; +import { Transform, TransformCallback, TransformOptions } from 'stream'; export default class PostgreSQLParser extends Transform { - constructor (opts) { + private _buffer: string; + private _lastChar: string; + private _lastChars: string; + private _bodyWrapper: string; + private _bodyWrapperBuffer: string; + private _firstDollarFound: boolean; + private _isBody: boolean; + private _isSingleLineComment: boolean; + private _isMultiLineComment: boolean; + + encoding: BufferEncoding; + delimiter: string; + isEscape: boolean; + currentQuote: string; + isDelimiter: boolean; + + constructor (opts?: TransformOptions & { delimiter: string }) { opts = { delimiter: ';', encoding: 'utf8', @@ -30,7 +46,7 @@ export default class PostgreSQLParser extends Transform { return this._isSingleLineComment || this._isMultiLineComment; } - _transform (chunk, encoding, next) { + _transform (chunk: Buffer, encoding: BufferEncoding, next: TransformCallback) { for (const char of chunk.toString(this.encoding)) { this.checkEscape(); this._buffer += char; @@ -82,7 +98,7 @@ export default class PostgreSQLParser extends Transform { } } - checkBodyWrapper (char) { + checkBodyWrapper (char: string) { if (this._isBody) this._isBody = this._lastChars !== this._bodyWrapper; @@ -111,7 +127,7 @@ export default class PostgreSQLParser extends Transform { } } - checkQuote (char) { + checkQuote (char: string) { const isQuote = !this.isEscape && (char === '\'' || char === '"'); if (isQuote && this.currentQuote === char) this.currentQuote = null; @@ -124,7 +140,7 @@ export default class PostgreSQLParser extends Transform { if (this._isBody || this._isComment) return false; - let query = false; + let query: false | string = false; let demiliterFound = false; if (this.currentQuote === null && this._buffer.length >= this.delimiter.length) diff --git a/src/main/workers/importer.js b/src/main/workers/importer.ts similarity index 74% rename from src/main/workers/importer.js rename to src/main/workers/importer.ts index f1c27636..67d56031 100644 --- a/src/main/workers/importer.js +++ b/src/main/workers/importer.ts @@ -1,7 +1,12 @@ +import * as antares from 'common/interfaces/antares'; +import * as pg from 'pg'; +import * as mysql from 'mysql2'; +import { MySQLClient } from '../libs/clients/MySQLClient'; +import { PostgreSQLClient } from '../libs/clients/PostgreSQLClient'; import { ClientsFactory } from '../libs/ClientsFactory'; -import MySQLImporter from '../libs/importers/sql/MysqlImporter'; +import MySQLImporter from '../libs/importers/sql/MySQLlImporter'; import PostgreSQLImporter from '../libs/importers/sql/PostgreSQLImporter'; -let importer; +let importer: antares.Importer; process.on('message', async ({ type, dbConfig, options }) => { if (type === 'init') { @@ -12,17 +17,17 @@ process.on('message', async ({ type, dbConfig, options }) => { schema: options.schema }, poolSize: 1 - }); + }) as MySQLClient | PostgreSQLClient; const pool = await connection.getConnectionPool(); switch (options.type) { case 'mysql': case 'maria': - importer = new MySQLImporter(pool, options); + importer = new MySQLImporter(pool as unknown as mysql.Pool, options); break; case 'pg': - importer = new PostgreSQLImporter(pool, options); + importer = new PostgreSQLImporter(pool as unknown as pg.PoolClient, options); break; default: process.send({ From f82db96f34579c5f5f0c025ba82aff13047eb045 Mon Sep 17 00:00:00 2001 From: Fabio Di Stasio Date: Sun, 17 Apr 2022 12:11:00 +0200 Subject: [PATCH 11/14] fix: importer webpack config --- webpack.workers.config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/webpack.workers.config.js b/webpack.workers.config.js index ce354055..c410072c 100644 --- a/webpack.workers.config.js +++ b/webpack.workers.config.js @@ -14,7 +14,7 @@ const config = { devtool: isDevMode ? 'eval-source-map' : false, entry: { exporter: path.join(__dirname, './src/main/workers/exporter.ts'), - importer: path.join(__dirname, './src/main/workers/importer.js') + importer: path.join(__dirname, './src/main/workers/importer.ts') }, target: 'node', output: { From 472fa6f4300b7e3fbf8c8079a548ca34d941f5fb Mon Sep 17 00:00:00 2001 From: Fabio Di Stasio Date: Sun, 17 Apr 2022 12:27:46 +0200 Subject: [PATCH 12/14] fix: wrong path module importation --- src/main/ipc-handlers/schema.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/ipc-handlers/schema.ts b/src/main/ipc-handlers/schema.ts index 81bfd328..bb744c20 100644 --- a/src/main/ipc-handlers/schema.ts +++ b/src/main/ipc-handlers/schema.ts @@ -1,7 +1,7 @@ import * as antares from 'common/interfaces/antares'; import * as workers from 'common/interfaces/workers'; import * as fs from 'fs'; -import path from 'path'; +import * as path from 'path'; import { ChildProcess, fork } from 'child_process'; import { ipcMain, dialog } from 'electron'; From a9f88e57848bada7f794159b9e0b41261134ee83 Mon Sep 17 00:00:00 2001 From: Fabio Di Stasio Date: Tue, 19 Apr 2022 11:47:50 +0200 Subject: [PATCH 13/14] chore: update bug_report.md [skip ci] --- .github/ISSUE_TEMPLATE/bug_report.md | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 013b7c0a..ece57867 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -26,14 +26,16 @@ If applicable, add screenshots to help explain your problem. **Application (please complete the following information):** -- Client [e.g. MySQL] -- Version [e.g. 0.14.0] -- Distribution: [e.g. exe, Linux Store, AppImage, dmg] +- App client [e.g. MySQL] +- App version [e.g. 0.5.2] +- Installation source: [e.g. exe, Linux Store, AppImage, dmg] -**Desktop (please complete the following information):** +**Environment (please complete the following information):** -- OS: [e.g. Windows 11] -- Version [e.g. 21H2] +- OS name: [e.g. Windows 11] +- OS version [e.g. 21H2] +- DB name [e.g. MariaDB] +- DB version [e.g. 10.3.34] **Additional context** Add any other context about the problem here. From eaf38df1b7e4675621cbf026afa7a70493592a71 Mon Sep 17 00:00:00 2001 From: Cleverson Date: Wed, 20 Apr 2022 16:14:14 -0300 Subject: [PATCH 14/14] Fixed a typo Fixed a typo --- src/renderer/i18n/pt-BR.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/renderer/i18n/pt-BR.js b/src/renderer/i18n/pt-BR.js index 8f94712a..b9abe1be 100644 --- a/src/renderer/i18n/pt-BR.js +++ b/src/renderer/i18n/pt-BR.js @@ -163,7 +163,7 @@ module.exports = { restartToInstall: 'Reinicie o Antares para instalar', unableEditFieldWithoutPrimary: 'Indisponível a edição de um campo sem a chave primária na tabela de resultados', editCell: 'Editar celula', - deleteRows: 'Apgar linha | Apagar {count} linhas', + deleteRows: 'Apagar linha | Apagar {count} linhas', confirmToDeleteRows: 'Você confirma a exclusão de uma linha? | Você confirma a exclusão de {count} linhas?', notificationsTimeout: 'Notificações de timeout', uploadFile: 'Upload de arquivo',