refactor: sqlite client ts refactor

This commit is contained in:
Fabio Di Stasio 2022-04-14 18:25:13 +02:00
parent d85662cb7d
commit 5e2ad8c377
4 changed files with 292 additions and 393 deletions

View File

@ -105,7 +105,7 @@ export interface TableForeign {
export interface TableOptions { export interface TableOptions {
name: string; name: string;
type: 'table' | 'view'; type?: 'table' | 'view';
engine?: string; engine?: string;
comment?: string; comment?: string;
collation?: string; collation?: string;
@ -114,7 +114,7 @@ export interface TableOptions {
export interface CreateTableParams { export interface CreateTableParams {
/** Connection UID */ /** Connection UID */
uid: string; uid?: string;
schema: string; schema: string;
fields: TableField[]; fields: TableField[];
foreigns: TableForeign[]; foreigns: TableForeign[];
@ -124,12 +124,18 @@ export interface CreateTableParams {
export interface AlterTableParams { export interface AlterTableParams {
/** Connection UID */ /** Connection UID */
uid: string; uid?: string;
schema: string; schema: string;
table: string; table: string;
additions: TableField[]; additions: TableField[];
changes: TableField[]; changes: TableField[];
deletions: TableField[]; deletions: TableField[];
tableStructure: {
name: string;
fields: TableField[];
foreigns: TableForeign[];
indexes: TableIndex[];
};
indexChanges: { indexChanges: {
additions: TableIndex[]; additions: TableIndex[];
changes: TableIndex[]; changes: TableIndex[];

View File

@ -1,4 +1,3 @@
// import BetterSqlite3 from 'better-sqlite3';
import * as antares from 'common/interfaces/antares'; import * as antares from 'common/interfaces/antares';
import mysql from 'mysql2/promise'; import mysql from 'mysql2/promise';
import * as pg from 'pg'; import * as pg from 'pg';
@ -17,7 +16,6 @@ export class AntaresCore {
protected _client: string; protected _client: string;
protected _params: mysql.ConnectionOptions | pg.ClientConfig | { databasePath: string; readonly: boolean}; protected _params: mysql.ConnectionOptions | pg.ClientConfig | { databasePath: string; readonly: boolean};
protected _poolSize: number; protected _poolSize: number;
// protected _connection?: mysql.Connection | mysql.Pool | pg.Connection | BetterSqlite3.Database
protected _ssh?: SSH2Promise; protected _ssh?: SSH2Promise;
protected _logger: (sql: string) => void; protected _logger: (sql: string) => void;
protected _queryDefaults: antares.QueryBuilderObject; protected _queryDefaults: antares.QueryBuilderObject;

View File

@ -1,7 +1,7 @@
import * as antares from 'common/interfaces/antares'; import * as antares from 'common/interfaces/antares';
import * as mysql from 'mysql2'; import * as mysql from 'mysql2';
import { builtinsTypes } from 'pg-types'; 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 * as pgAst from 'pgsql-ast-parser';
import { AntaresCore } from '../AntaresCore'; import { AntaresCore } from '../AntaresCore';
import * as dataTypes from 'common/data-types/postgresql'; import * as dataTypes from 'common/data-types/postgresql';

View File

@ -1,26 +1,28 @@
'use strict'; import * as antares from 'common/interfaces/antares';
import sqlite from 'better-sqlite3'; import * as sqlite from 'better-sqlite3';
import { AntaresCore } from '../AntaresCore'; 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'; import { NUMBER, FLOAT, TIME, DATETIME } from 'common/fieldTypes';
export class SQLiteClient extends AntaresCore { export class SQLiteClient extends AntaresCore {
constructor (args) { private _schema?: string;
private _connectionsToCommit: Map<string, sqlite.Database>;
protected _connection?: sqlite.Database;
protected _params: { databasePath: string; readonly: boolean};
constructor (args: antares.ClientParams) {
super(args); super(args);
this._schema = null; this._schema = null;
this._connectionsToCommit = new Map(); this._connectionsToCommit = new Map();
} }
_getTypeInfo (type) { _getTypeInfo (type: string) {
return dataTypes return dataTypes
.reduce((acc, group) => [...acc, ...group.types], []) .reduce((acc, group) => [...acc, ...group.types], [])
.filter(_type => _type.name === type.toUpperCase())[0]; .filter(_type => _type.name === type.toUpperCase())[0];
} }
/**
* @memberof SQLiteClient
*/
async connect () { async connect () {
this._connection = this.getConnection(); this._connection = this.getConnection();
} }
@ -32,36 +34,40 @@ export class SQLiteClient extends AntaresCore {
}); });
} }
/** destroy (): void {
* @memberof SQLiteClient return null;
*/ }
destroy () {}
/** use (): void {
* Executes an USE query return null;
* }
* @memberof SQLiteClient
*/
use () {}
/** async getStructure (schemas: Set<string>) {
* @param {Array} schemas list /* eslint-disable camelcase */
* @returns {Array.<Object>} databases scructure interface ShowTableResult {
* @memberof SQLiteClient Db?: string;
*/ type: string;
async getStructure (schemas) { name: string;
const { rows: databases } = await this.raw('SELECT * FROM pragma_database_list'); tbl_name: string;
rootpage:4;
sql: string;
}
type ShowTriggersResult = ShowTableResult
/* eslint-enable camelcase */
const { rows: databases } = await this.raw<antares.QueryResult<{ name: string}>>('SELECT * FROM pragma_database_list');
const filteredDatabases = databases; const filteredDatabases = databases;
const tablesArr = []; const tablesArr: ShowTableResult[] = [];
const triggersArr = []; const triggersArr: ShowTriggersResult[] = [];
let schemaSize = 0; let schemaSize = 0;
for (const db of filteredDatabases) { for (const db of filteredDatabases) {
if (!schemas.has(db.name)) continue; if (!schemas.has(db.name)) continue;
let { rows: tables } = await this.raw(` let { rows: tables } = await this.raw<antares.QueryResult<ShowTableResult>>(`
SELECT * SELECT *
FROM "${db.name}".sqlite_master FROM "${db.name}".sqlite_master
WHERE type IN ('table', 'view') WHERE type IN ('table', 'view')
@ -76,7 +82,7 @@ export class SQLiteClient extends AntaresCore {
tablesArr.push(...tables); tablesArr.push(...tables);
} }
let { rows: triggers } = await this.raw(`SELECT * FROM "${db.name}".sqlite_master WHERE type='trigger'`); let { rows: triggers } = await this.raw<antares.QueryResult<ShowTriggersResult>>(`SELECT * FROM "${db.name}".sqlite_master WHERE type='trigger'`);
if (triggers.length) { if (triggers.length) {
triggers = triggers.map(trigger => { triggers = triggers.map(trigger => {
trigger.Db = db.name; trigger.Db = db.name;
@ -133,22 +139,24 @@ export class SQLiteClient extends AntaresCore {
}); });
} }
/** async getTableColumns ({ schema, table }: { schema: string; table: string }) {
* @param {Object} params interface TableColumnsResult {
* @param {String} params.schema cid: number;
* @param {String} params.table name: string;
* @returns {Object} table scructure type: string;
* @memberof SQLiteClient notnull: 0 | 1;
*/ // eslint-disable-next-line camelcase
async getTableColumns ({ schema, table }) { dflt_value: string;
const { rows: fields } = await this.raw(`SELECT * FROM "${schema}".pragma_table_info('${table}')`); pk: 0 | 1;
}
const { rows: fields } = await this.raw<antares.QueryResult<TableColumnsResult>>(`SELECT * FROM "${schema}".pragma_table_info('${table}')`);
return fields.map(field => { return fields.map(field => {
const [type, length] = field.type.includes('(') const [type, length]: [string, number?] = field.type.includes('(')
? field.type.replace(')', '').split('(').map(el => { ? field.type.replace(')', '').split('(').map((el: string | number) => {
if (!isNaN(el)) el = +el; if (!isNaN(Number(el))) el = Number(el);
return el; return el;
}) }) as [string, number?]
: [field.type, null]; : [field.type, null];
return { return {
@ -174,54 +182,50 @@ export class SQLiteClient extends AntaresCore {
}); });
} }
/** async getTableApproximateCount ({ schema, table }: { schema: string; table: string }): Promise<number> {
* @param {Object} params
* @param {String} params.schema
* @param {String} params.table
* @returns {Object} table row count
* @memberof SQLiteClient
*/
async getTableApproximateCount ({ schema, table }) {
const { rows } = await this.raw(`SELECT COUNT(*) AS count FROM "${schema}"."${table}"`); const { rows } = await this.raw(`SELECT COUNT(*) AS count FROM "${schema}"."${table}"`);
return rows.length ? rows[0].count : 0; return rows.length ? rows[0].count : 0;
} }
/** async getTableOptions ({ table }: { table: string }) {
* @param {Object} params
* @param {String} params.schema
* @param {String} params.table
* @returns {Object} table options
* @memberof SQLiteClient
*/
async getTableOptions ({ schema, table }) {
return { name: table }; return { name: table };
} }
/** async getTableIndexes ({ schema, table }: { schema: string; table: string }) {
* @param {Object} params interface TableColumnsResult {
* @param {String} params.schema type: string;
* @param {String} params.table name: string;
* @returns {Object} table indexes // eslint-disable-next-line camelcase
* @memberof SQLiteClient tbl_name: string;
*/ rootpage:4;
async getTableIndexes ({ schema, table }) { sql: string;
}
interface ShowIndexesResult {
seq: number;
name: string;
unique: 0 | 1;
origin: string;
partial: 0 | 1;
}
const remappedIndexes = []; const remappedIndexes = [];
const { rows: primaryKeys } = await this.raw(`SELECT * FROM "${schema}".pragma_table_info('${table}') WHERE pk != 0`); const { rows: primaryKeys } = await this.raw<antares.QueryResult<TableColumnsResult>>(`SELECT * FROM "${schema}".pragma_table_info('${table}') WHERE pk != 0`);
for (const key of primaryKeys) { for (const key of primaryKeys) {
remappedIndexes.push({ remappedIndexes.push({
name: 'PRIMARY', name: 'PRIMARY',
column: key.name, column: key.name,
indexType: null, indexType: null as never,
type: 'PRIMARY', type: 'PRIMARY',
cardinality: null, cardinality: null as never,
comment: '', comment: '',
indexComment: '' indexComment: ''
}); });
} }
const { rows: indexes } = await this.raw(`SELECT * FROM "${schema}".pragma_index_list('${table}');`); const { rows: indexes } = await this.raw<antares.QueryResult<ShowIndexesResult>>(`SELECT * FROM "${schema}".pragma_index_list('${table}');`);
for (const index of indexes) { for (const index of indexes) {
const { rows: details } = await this.raw(`SELECT * FROM "${schema}".pragma_index_info('${index.name}');`); 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({ remappedIndexes.push({
name: index.name, name: index.name,
column: detail.name, column: detail.name,
indexType: null, indexType: null as never,
type: index.unique === 1 ? 'UNIQUE' : 'INDEX', type: index.unique === 1 ? 'UNIQUE' : 'INDEX',
cardinality: null, cardinality: null as never,
comment: '', comment: '',
indexComment: '' indexComment: ''
}); });
@ -242,15 +246,19 @@ export class SQLiteClient extends AntaresCore {
return remappedIndexes; return remappedIndexes;
} }
/** async getKeyUsage ({ schema, table }: { schema: string; table: string }) {
* @param {Object} params /* eslint-disable camelcase */
* @param {String} params.schema interface KeyResult {
* @param {String} params.table from: string;
* @returns {Object} table key usage id: number;
* @memberof SQLiteClient table: string;
*/ to: string;
async getKeyUsage ({ schema, table }) { on_update: string;
const { rows } = await this.raw(`SELECT * FROM "${schema}".pragma_foreign_key_list('${table}');`); on_delete: string;
}
/* eslint-enable camelcase */
const { rows } = await this.raw<antares.QueryResult<KeyResult>>(`SELECT * FROM "${schema}".pragma_foreign_key_list('${table}');`);
return rows.map(field => { return rows.map(field => {
return { return {
@ -269,229 +277,11 @@ export class SQLiteClient extends AntaresCore {
}); });
} }
async getUsers () {} async getUsers (): Promise<void> {
return null;
/**
* SHOW CREATE VIEW
*
* @returns {Array.<Object>} 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 createTable (params: antares.CreateTableParams) {
* DROP VIEW
*
* @returns {Array.<Object>} parameters
* @memberof SQLiteClient
*/
async dropView (params) {
const sql = `DROP VIEW "${params.schema}"."${params.view}"`;
return await this.raw(sql);
}
/**
* ALTER VIEW
*
* @returns {Array.<Object>} 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.<Object>} 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.<Object>} 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.<Object>} parameters
* @memberof SQLiteClient
*/
async dropTrigger (params) {
const sql = `DROP TRIGGER \`${params.schema}\`.\`${params.trigger}\``;
return await this.raw(sql);
}
/**
* ALTER TRIGGER
*
* @returns {Array.<Object>} 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.<Object>} 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.<Object>} collations list
* @memberof SQLiteClient
*/
async getCollations () {
return [];
}
/**
* SHOW VARIABLES
*
* @returns {Array.<Object>} variables list
* @memberof SQLiteClient
*/
async getVariables () {
return [];
}
/**
* SHOW ENGINES
*
* @returns {Array.<Object>} engines list
* @memberof SQLiteClient
*/
async getEngines () {
return {
name: 'SQLite',
support: 'YES',
comment: '',
isDefault: true
};
}
/**
* SHOW VARIABLES LIKE '%vers%'
*
* @returns {Array.<Object>} 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<null>}
*/
async commitTab (tabUid) {
const connection = this._connectionsToCommit.get(tabUid);
if (connection) {
connection.prepare('COMMIT').run();
return this.destroyConnectionToCommit(tabUid);
}
}
/**
*
* @param {string} tabUid
* @returns {Promise<null>}
*/
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<null>}
* @memberof SQLiteClient
*/
async createTable (params) {
const { const {
schema, schema,
fields, fields,
@ -499,10 +289,10 @@ export class SQLiteClient extends AntaresCore {
indexes, indexes,
options options
} = params; } = params;
const newColumns = []; const newColumns: string[] = [];
const newIndexes = []; const newIndexes: string[] = [];
const manageIndexes = []; const manageIndexes: string[] = [];
const newForeigns = []; const newForeigns: string[] = [];
let sql = `CREATE TABLE "${schema}"."${options.name}"`; 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; const length = typeInfo?.length ? field.enumValues || field.numLength || field.charLength || field.datePrecision : false;
newColumns.push(`"${field.name}" newColumns.push(`"${field.name}"
${field.type.toUpperCase()}${length && length !== true ? `(${length})` : ''} ${field.type.toUpperCase()}${length ? `(${length})` : ''}
${field.unsigned ? 'UNSIGNED' : ''} ${field.unsigned ? 'UNSIGNED' : ''}
${field.nullable ? 'NULL' : 'NOT NULL'} ${field.nullable ? 'NULL' : 'NOT NULL'}
${field.autoIncrement ? 'AUTO_INCREMENT' : ''} ${field.autoIncrement ? 'AUTO_INCREMENT' : ''}
@ -542,34 +332,37 @@ export class SQLiteClient extends AntaresCore {
return await this.raw(sql); return await this.raw(sql);
} }
/** async alterTable (params: antares.AlterTableParams) {
* ALTER TABLE const {
* table,
* @returns {Promise<null>} schema,
* @memberof SQLiteClient additions,
*/ deletions,
async alterTable (params) { changes,
tableStructure
} = params;
try { try {
await this.raw('BEGIN TRANSACTION'); await this.raw('BEGIN TRANSACTION');
await this.raw('PRAGMA foreign_keys = 0'); await this.raw('PRAGMA foreign_keys = 0');
const tmpName = `Antares_${params.table}_tmp`; const tmpName = `Antares_${table}_tmp`;
await this.raw(`CREATE TABLE "${tmpName}" AS SELECT * FROM "${params.table}"`); await this.raw(`CREATE TABLE "${tmpName}" AS SELECT * FROM "${table}"`);
await this.dropTable(params); await this.dropTable(params);
const createTableParams = { const createTableParams = {
schema: params.schema, schema: schema,
fields: params.tableStructure.fields, fields: tableStructure.fields,
foreigns: params.tableStructure.foreigns, foreigns: tableStructure.foreigns,
indexes: params.tableStructure.indexes.filter(index => !index.name.includes('sqlite_autoindex')), indexes: tableStructure.indexes.filter(index => !index.name.includes('sqlite_autoindex')),
options: { name: params.tableStructure.name } options: { name: tableStructure.name }
}; };
await this.createTable(createTableParams); await this.createTable(createTableParams);
const insertFields = createTableParams.fields const insertFields = createTableParams.fields
.filter(field => { .filter(field => {
return ( return (
params.additions.every(add => add.name !== field.name) && additions.every(add => add.name !== field.name) &&
params.deletions.every(del => del.name !== field.name) deletions.every(del => del.name !== field.name)
); );
}) })
.reduce((acc, curr) => { .reduce((acc, curr) => {
@ -578,7 +371,7 @@ export class SQLiteClient extends AntaresCore {
}, []); }, []);
const selectFields = insertFields.map(field => { const selectFields = insertFields.map(field => {
const renamedField = params.changes.find(change => `"${change.name}"` === field); const renamedField = changes.find(change => `"${change.name}"` === field);
if (renamedField) if (renamedField)
return `"${renamedField.orgName}"`; return `"${renamedField.orgName}"`;
return field; 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.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('PRAGMA foreign_keys = 1');
await this.raw('COMMIT'); await this.raw('COMMIT');
} }
@ -596,43 +389,155 @@ export class SQLiteClient extends AntaresCore {
} }
} }
/** async duplicateTable (params: { schema: string; table: string }) { // TODO: retrive table informations and create a copy
* DUPLICATE TABLE
*
* @returns {Promise<null>}
* @memberof SQLiteClient
*/
async duplicateTable (params) { // TODO: retrive table informations and create a copy
const sql = `CREATE TABLE "${params.schema}"."${params.table}_copy" AS SELECT * FROM "${params.schema}"."${params.table}"`; const sql = `CREATE TABLE "${params.schema}"."${params.table}_copy" AS SELECT * FROM "${params.schema}"."${params.table}"`;
return await this.raw(sql); return await this.raw(sql);
} }
/** async truncateTable (params: { schema: string; table: string }) {
* TRUNCATE TABLE
*
* @returns {Promise<null>}
* @memberof SQLiteClient
*/
async truncateTable (params) {
const sql = `DELETE FROM "${params.schema}"."${params.table}"`; const sql = `DELETE FROM "${params.schema}"."${params.table}"`;
return await this.raw(sql); return await this.raw(sql);
} }
/** async dropTable (params: { schema: string; table: string }) {
* DROP TABLE
*
* @returns {Promise<null>}
* @memberof SQLiteClient
*/
async dropTable (params) {
const sql = `DROP TABLE "${params.schema}"."${params.table}"`; const sql = `DROP TABLE "${params.schema}"."${params.table}"`;
return await this.raw(sql); return await this.raw(sql);
} }
/** async getViewInformations ({ schema, view }: { schema: string; view: string }) {
* @returns {String} SQL string const sql = `SELECT "sql" FROM "${schema}".sqlite_master WHERE "type"='view' AND name='${view}'`;
* @memberof SQLiteClient 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<undefined[]> {
return [];
}
async getVariables (): Promise<undefined[]> {
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<void> {
return null;
}
async killProcess (): Promise<void> {
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 () { getSQL () {
// SELECT // SELECT
const selectArray = this._query.select.reduce(this._reducer, []); 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}`; return `${selectRaw}${updateRaw ? 'UPDATE' : ''}${insertRaw ? 'INSERT ' : ''}${this._query.delete ? 'DELETE ' : ''}${fromRaw}${updateRaw}${whereRaw}${groupByRaw}${orderByRaw}${limitRaw}${offsetRaw}${insertRaw}`;
} }
/** async raw<T = antares.QueryResult> (sql: string, args?: antares.QueryParams) {
* @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) {
if (process.env.NODE_ENV === 'development') this._logger(sql);// TODO: replace BLOB content with a placeholder if (process.env.NODE_ENV === 'development') this._logger(sql);// TODO: replace BLOB content with a placeholder
args = { args = {
@ -720,7 +616,7 @@ export class SQLiteClient extends AntaresCore {
.map(q => q.trim()) .map(q => q.trim())
: [sql]; : [sql];
let connection; let connection: sqlite.Database;
if (!args.autocommit && args.tabUid) { // autocommit OFF if (!args.autocommit && args.tabUid) { // autocommit OFF
if (this._connectionsToCommit.has(args.tabUid)) if (this._connectionsToCommit.has(args.tabUid))
@ -738,31 +634,33 @@ export class SQLiteClient extends AntaresCore {
if (!query) continue; if (!query) continue;
const timeStart = new Date(); const timeStart = new Date();
let timeStop; let timeStop;
const keysArr = []; const keysArr: antares.QueryForeign[] = [];
const { rows, report, fields, keys, duration } = await new Promise((resolve, reject) => { const { rows, report, fields, keys, duration } = await new Promise((resolve, reject) => {
(async () => { (async () => {
let queryResult; let queryRunResult: sqlite.RunResult;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let queryAllResult: any[];
let affectedRows; let affectedRows;
let fields; let fields;
const detectedTypes = {}; const detectedTypes: {[key: string]: string} = {};
try { try {
const stmt = connection.prepare(query); const stmt = connection.prepare(query);
if (stmt.reader) { if (stmt.reader) {
queryResult = stmt.all(); queryAllResult = stmt.all();
fields = stmt.columns(); fields = stmt.columns();
if (queryResult.length) { if (queryAllResult.length) {
fields.forEach(field => { fields.forEach(field => {
detectedTypes[field.name] = typeof queryResult[0][field.name]; detectedTypes[field.name] = typeof queryAllResult[0][field.name];
}); });
} }
} }
else { else {
const info = queryResult = stmt.run(); queryRunResult = stmt.run();
affectedRows = info.changes; affectedRows = queryRunResult.changes;
} }
} }
catch (err) { catch (err) {
@ -773,18 +671,18 @@ export class SQLiteClient extends AntaresCore {
let remappedFields = fields let remappedFields = fields
? fields.map(field => { ? fields.map(field => {
let [parsedType, length] = field.type?.includes('(') let [parsedType, length]: [string, number?] = field.type?.includes('(')
? field.type.replace(')', '').split('(').map(el => { ? field.type.replace(')', '').split('(').map((el: string | number) => {
if (!isNaN(el)) if (!isNaN(Number(el)))
el = +el; el = Number(el);
else else
el = el.trim(); el = (el as string).trim();
return el; return el;
}) }) as [string, number?]
: [field.type, null]; : [field.type, null];
if ([...TIME, ...DATETIME].includes(parsedType)) { 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('.')) if (firstNotNull && firstNotNull[field.name].includes('.'))
length = firstNotNull[field.name].split('.').pop().length; length = firstNotNull[field.name].split('.').pop().length;
} }
@ -798,7 +696,8 @@ export class SQLiteClient extends AntaresCore {
tableAlias: field.table, tableAlias: field.table,
orgTable: field.table, orgTable: field.table,
type: field.type !== null ? parsedType : detectedTypes[field.name], type: field.type !== null ? parsedType : detectedTypes[field.name],
length length,
key: undefined as string
}; };
}).filter(Boolean) }).filter(Boolean)
: []; : [];
@ -818,18 +717,12 @@ export class SQLiteClient extends AntaresCore {
const indexes = await this.getTableIndexes(paramObj); const indexes = await this.getTableIndexes(paramObj);
remappedFields = remappedFields.map(field => { remappedFields = remappedFields.map(field => {
// const detailedField = columns.find(f => f.name === field.name);
const fieldIndex = indexes.find(i => i.column === field.name); const fieldIndex = indexes.find(i => i.column === field.name);
if (field.table === paramObj.table && field.schema === paramObj.schema) { 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) { if (fieldIndex) {
const key = fieldIndex.type === 'PRIMARY' ? 'pri' : fieldIndex.type === 'UNIQUE' ? 'uni' : 'mul'; const key = fieldIndex.type === 'PRIMARY' ? 'pri' : fieldIndex.type === 'UNIQUE' ? 'uni' : 'mul';
field = { ...field, key }; field = { ...field, key };
}; }
} }
return field; return field;
@ -842,8 +735,8 @@ export class SQLiteClient extends AntaresCore {
} }
resolve({ resolve({
duration: timeStop - timeStart, duration: timeStop.getTime() - timeStart.getTime(),
rows: Array.isArray(queryResult) ? queryResult.some(el => Array.isArray(el)) ? [] : queryResult : false, rows: Array.isArray(queryAllResult) ? queryAllResult.some(el => Array.isArray(el)) ? [] : queryAllResult : false,
report: affectedRows !== undefined ? { affectedRows } : null, report: affectedRows !== undefined ? { affectedRows } : null,
fields: remappedFields, fields: remappedFields,
keys: keysArr keys: keysArr
@ -854,6 +747,8 @@ export class SQLiteClient extends AntaresCore {
resultsArr.push({ rows, report, fields, keys, duration }); 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;
} }
} }