refactor: mysql client ts refactor

This commit is contained in:
Fabio Di Stasio 2022-04-12 17:08:05 +02:00
parent cbef7489b2
commit fc1d6fba7f
10 changed files with 1159 additions and 1024 deletions

View File

@ -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<T = any> {
rows: T[];
report: { affectedRows: number };
fields: QueryField[];
keys: QueryForeign[];
duration: number;
}

View File

@ -1,16 +1,29 @@
import * as antares from 'common/interfaces/antares';
import fs from 'fs'; import fs from 'fs';
import { ipcMain } from 'electron'; import { ipcMain } from 'electron';
import { ClientsFactory } from '../libs/ClientsFactory'; import { ClientsFactory } from '../libs/ClientsFactory';
import { SslOptions } from 'mysql2';
export default connections => { export default (connections: {[key: string]: antares.Client}) => {
ipcMain.handle('test-connection', async (event, conn) => { ipcMain.handle('test-connection', async (event, conn: antares.ConnectionParams) => {
const params = { const params = {
host: conn.host, host: conn.host,
port: +conn.port, port: +conn.port,
user: conn.user, user: conn.user,
password: conn.password, 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) if (conn.database)
@ -21,9 +34,9 @@ export default connections => {
if (conn.ssl) { if (conn.ssl) {
params.ssl = { params.ssl = {
key: conn.key ? fs.readFileSync(conn.key) : null, key: conn.key ? fs.readFileSync(conn.key).toString() : null,
cert: conn.cert ? fs.readFileSync(conn.cert) : null, cert: conn.cert ? fs.readFileSync(conn.cert).toString() : null,
ca: conn.ca ? fs.readFileSync(conn.ca) : null, ca: conn.ca ? fs.readFileSync(conn.ca).toString() : null,
ciphers: conn.ciphers, ciphers: conn.ciphers,
rejectUnauthorized: !conn.untrustedConnection rejectUnauthorized: !conn.untrustedConnection
}; };
@ -35,7 +48,7 @@ export default connections => {
username: conn.sshUser, username: conn.sshUser,
password: conn.sshPass, password: conn.sshPass,
port: conn.sshPort ? conn.sshPort : 22, 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 passphrase: conn.sshPassphrase
}; };
} }
@ -61,14 +74,26 @@ export default connections => {
return uid in connections; return uid in connections;
}); });
ipcMain.handle('connect', async (event, conn) => { ipcMain.handle('connect', async (event, conn: antares.ConnectionParams) => {
const params = { const params = {
host: conn.host, host: conn.host,
port: +conn.port, port: +conn.port,
user: conn.user, user: conn.user,
password: conn.password, password: conn.password,
application_name: 'Antares SQL', 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) if (conn.database)
@ -82,9 +107,9 @@ export default connections => {
if (conn.ssl) { if (conn.ssl) {
params.ssl = { params.ssl = {
key: conn.key ? fs.readFileSync(conn.key) : null, key: conn.key ? fs.readFileSync(conn.key).toString() : null,
cert: conn.cert ? fs.readFileSync(conn.cert) : null, cert: conn.cert ? fs.readFileSync(conn.cert).toString() : null,
ca: conn.ca ? fs.readFileSync(conn.ca) : null, ca: conn.ca ? fs.readFileSync(conn.ca).toString() : null,
ciphers: conn.ciphers, ciphers: conn.ciphers,
rejectUnauthorized: !conn.untrustedConnection rejectUnauthorized: !conn.untrustedConnection
}; };
@ -96,7 +121,7 @@ export default connections => {
username: conn.sshUser, username: conn.sshUser,
password: conn.sshPass, password: conn.sshPass,
port: conn.sshPort ? conn.sshPort : 22, 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 passphrase: conn.sshPassphrase
}; };
} }
@ -110,6 +135,9 @@ export default connections => {
await connection.connect(); await connection.connect();
// TODO: temporary
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
const structure = await connection.getStructure(new Set()); const structure = await connection.getStructure(new Set());
connections[conn.uid] = connection; connections[conn.uid] = connection;

View File

@ -1,3 +1,5 @@
import * as antares from 'common/interfaces/antares';
import connection from './connection'; import connection from './connection';
import tables from './tables'; import tables from './tables';
import views from './views'; import views from './views';
@ -10,7 +12,7 @@ import application from './application';
import schema from './schema'; import schema from './schema';
import users from './users'; import users from './users';
const connections = {}; const connections: {[key: string]: antares.Client} = {};
export default () => { export default () => {
connection(connections); connection(connections);

View File

@ -1,5 +1,10 @@
'use strict'; // import BetterSqlite3 from 'better-sqlite3';
const queryLogger = sql => { 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 // Remove comments, newlines and multiple spaces
const escapedSql = sql.replace(/(\/\*(.|[\r\n])*?\*\/)|(--(.*|[\r\n]))/gm, '').replace(/\s\s+/g, ' '); const escapedSql = sql.replace(/(\/\*(.|[\r\n])*?\*\/)|(--(.*|[\r\n]))/gm, '').replace(/\s\s+/g, ' ');
console.log(escapedSql); console.log(escapedSql);
@ -7,22 +12,21 @@ const queryLogger = sql => {
/** /**
* As Simple As Possible Query Builder Core * As Simple As Possible Query Builder Core
*
* @class AntaresCore
*/ */
export class AntaresCore { export class AntaresCore {
/** protected _client: string;
* Creates an instance of AntaresCore. protected _params: mysql.ConnectionOptions | pg.ClientConfig | { databasePath: string; readonly: boolean};
* protected _poolSize: number;
* @param {Object} args connection params // protected _connection?: mysql.Connection | mysql.Pool | pg.Connection | BetterSqlite3.Database
* @memberof AntaresCore protected _ssh?: SSH2Promise;
*/ protected _logger: (sql: string) => void;
constructor (args) { protected _queryDefaults: antares.QueryBuilderObject;
protected _query: antares.QueryBuilderObject;
constructor (args: antares.ClientParams) {
this._client = args.client; this._client = args.client;
this._params = args.params; this._params = args.params;
this._poolSize = args.poolSize || false; this._poolSize = args.poolSize || undefined;
this._connection = null;
this._ssh = null;
this._logger = args.logger || queryLogger; this._logger = args.logger || queryLogger;
this._queryDefaults = { this._queryDefaults = {
@ -42,7 +46,8 @@ export class AntaresCore {
this._query = Object.assign({}, this._queryDefaults); 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; const type = typeof curr;
switch (type) { switch (type) {
@ -62,94 +67,87 @@ export class AntaresCore {
} }
} }
/** private _resetQuery () {
* Resets the query object after a query
*
* @memberof AntaresCore
*/
_resetQuery () {
this._query = Object.assign({}, this._queryDefaults); this._query = Object.assign({}, this._queryDefaults);
} }
schema (schema) { schema (schema: string) {
this._query.schema = schema; this._query.schema = schema;
return this; return this;
} }
select (...args) { select (...args: string[]) {
this._query.select = [...this._query.select, ...args]; this._query.select = [...this._query.select, ...args];
return this; return this;
} }
from (table) { from (table: string) {
this._query.from = table; this._query.from = table;
return this; return this;
} }
into (table) { into (table: string) {
this._query.from = table; this._query.from = table;
return this; return this;
} }
delete (table) { delete (table: string) {
this._query.delete = true; this._query.delete = true;
this.from(table); this.from(table);
return this; return this;
} }
where (...args) { // eslint-disable-next-line @typescript-eslint/no-explicit-any
where (...args: any) {
this._query.where = [...this._query.where, ...args]; this._query.where = [...this._query.where, ...args];
return this; return this;
} }
groupBy (...args) { // eslint-disable-next-line @typescript-eslint/no-explicit-any
groupBy (...args: any) {
this._query.groupBy = [...this._query.groupBy, ...args]; this._query.groupBy = [...this._query.groupBy, ...args];
return this; return this;
} }
orderBy (...args) { // eslint-disable-next-line @typescript-eslint/no-explicit-any
orderBy (...args: any) {
this._query.orderBy = [...this._query.orderBy, ...args]; this._query.orderBy = [...this._query.orderBy, ...args];
return this; return this;
} }
limit (...args) { limit (...args: string[]) {
this._query.limit = args; this._query.limit = args;
return this; return this;
} }
offset (...args) { offset (...args: string[]) {
this._query.offset = args; this._query.offset = args;
return this; return this;
} }
/** // eslint-disable-next-line @typescript-eslint/no-explicit-any
* @param {String | Array} args field = value update (...args: any) {
* @returns
* @memberof AntaresCore
*/
update (...args) {
this._query.update = [...this._query.update, ...args]; this._query.update = [...this._query.update, ...args];
return this; return this;
} }
/** insert (arr: string[]) {
* @param {Array} arr Array of row objects
* @returns
* @memberof AntaresCore
*/
insert (arr) {
this._query.insert = [...this._query.insert, ...arr]; this._query.insert = [...this._query.insert, ...arr];
return this; return this;
} }
/** getSQL (): string {
* @param {Object} args throw new Error('Client must implement the "getSQL" method');
* @returns {Promise} }
* @memberof AntaresCore
*/ // eslint-disable-next-line @typescript-eslint/no-unused-vars
run (args) { raw<T = antares.QueryResult> (_sql: string, _args?: antares.QueryParams): Promise<T> {
throw new Error('Client must implement the "raw" method');
}
run<RowType> (args?: antares.QueryParams) {
const rawQuery = this.getSQL(); const rawQuery = this.getSQL();
this._resetQuery(); this._resetQuery();
return this.raw(rawQuery, args); return this.raw<antares.QueryResult<RowType>>(rawQuery, args);
} }
} }

View File

@ -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}`);
}
}
}

View File

@ -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}`);
}
}
}

View File

@ -76,6 +76,8 @@ export class PostgreSQLClient extends AntaresCore {
* @memberof PostgreSQLClient * @memberof PostgreSQLClient
*/ */
async getDbConfig () { async getDbConfig () {
this._params.application_name = 'Antares SQL';
const dbConfig = { const dbConfig = {
host: this._params.host, host: this._params.host,
port: this._params.port, port: this._params.port,

View File

@ -1,5 +1,3 @@
'use strict';
import { app, BrowserWindow, /* session, */ nativeImage, Menu } from 'electron'; import { app, BrowserWindow, /* session, */ nativeImage, Menu } from 'electron';
import * as path from 'path'; import * as path from 'path';
import Store from 'electron-store'; import Store from 'electron-store';
@ -8,7 +6,6 @@ import * as remoteMain from '@electron/remote/main';
import ipcHandlers from './ipc-handlers'; import ipcHandlers from './ipc-handlers';
// remoteMain.initialize();
Store.initRenderer(); Store.initRenderer();
const isDevelopment = process.env.NODE_ENV !== 'production'; const isDevelopment = process.env.NODE_ENV !== 'production';
@ -18,8 +15,8 @@ const gotTheLock = app.requestSingleInstanceLock();
process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = 'true'; process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = 'true';
// global reference to mainWindow (necessary to prevent window from being garbage collected) // global reference to mainWindow (necessary to prevent window from being garbage collected)
let mainWindow; let mainWindow: BrowserWindow;
let mainWindowState; let mainWindowState: windowStateKeeper.State;
async function createMainWindow () { async function createMainWindow () {
const icon = require('../renderer/images/logo-32.png'); const icon = require('../renderer/images/logo-32.png');
@ -36,7 +33,6 @@ async function createMainWindow () {
webPreferences: { webPreferences: {
nodeIntegration: true, nodeIntegration: true,
contextIsolation: false, contextIsolation: false,
'web-security': false,
spellcheck: false spellcheck: false
}, },
frame: false, frame: false,
@ -127,7 +123,7 @@ else {
} }
function createAppMenu () { function createAppMenu () {
let menu = null; let menu: Electron.Menu = null;
if (isMacOS) { if (isMacOS) {
menu = Menu.buildFromTemplate([ menu = Menu.buildFromTemplate([

View File

@ -253,14 +253,6 @@
> >
</div> </div>
</div> </div>
<div class="form-group columns">
<div class="column col-4 col-sm-12" />
<div class="column col-8 col-sm-12">
<label class="form-checkbox form-inline">
<input v-model="localConnection.untrustedConnection" type="checkbox"><i class="form-icon" /> {{ $t('message.untrustedConnection') }}
</label>
</div>
</div>
</fieldset> </fieldset>
</form> </form>
</div> </div>