2021-03-16 18:42:03 +01:00
'use strict' ;
2021-03-25 18:33:29 +01:00
import { Pool , Client , types } from 'pg' ;
2021-03-19 18:49:26 +01:00
import { parse } from 'pgsql-ast-parser' ;
2021-03-16 18:42:03 +01:00
import { AntaresCore } from '../AntaresCore' ;
import dataTypes from 'common/data-types/postgresql' ;
2021-03-18 15:56:52 +01:00
function pgToString ( value ) {
return value . toString ( ) ;
}
2021-03-25 18:33:29 +01:00
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
2021-03-18 15:56:52 +01:00
2021-03-16 18:42:03 +01:00
export class PostgreSQLClient extends AntaresCore {
constructor ( args ) {
super ( args ) ;
this . _schema = null ;
this . types = { } ;
for ( const key in types . builtins )
this . types [ types . builtins [ key ] ] = key ;
}
_getType ( field ) {
let name = this . types [ field . columnType ] ;
let length = field . columnLength ;
if ( [ 'DATE' , 'TIME' , 'YEAR' , 'DATETIME' ] . includes ( name ) )
length = field . decimals ;
if ( name === 'TIMESTAMP' )
length = 0 ;
if ( field . charsetNr === 63 ) { // if binary
if ( name === 'CHAR' )
name = 'BINARY' ;
else if ( name === 'VARCHAR' )
name = 'VARBINARY' ;
}
if ( name === 'BLOB' ) {
switch ( length ) {
case 765 :
name = 'TYNITEXT' ;
break ;
case 196605 :
name = 'TEXT' ;
break ;
case 50331645 :
name = 'MEDIUMTEXT' ;
break ;
case 4294967295 :
name = field . charsetNr === 63 ? 'LONGBLOB' : 'LONGTEXT' ;
break ;
case 255 :
name = 'TINYBLOB' ;
break ;
case 65535 :
name = 'BLOB' ;
break ;
case 16777215 :
name = 'MEDIUMBLOB' ;
break ;
default :
name = field . charsetNr === 63 ? 'BLOB' : 'TEXT' ;
}
}
return { name , length } ;
}
_getTypeInfo ( type ) {
return dataTypes
. reduce ( ( acc , group ) => [ ... acc , ... group . types ] , [ ] )
. filter ( _type => _type . name === type . toUpperCase ( ) ) [ 0 ] ;
}
/ * *
* @ memberof PostgreSQLClient
* /
async connect ( ) {
if ( ! this . _poolSize ) {
const client = new Client ( this . _params ) ;
2021-03-17 16:51:26 +01:00
await client . connect ( ) ;
this . _connection = client ;
2021-03-16 18:42:03 +01:00
}
else {
const pool = new Pool ( { ... this . _params , max : this . _poolSize } ) ;
this . _connection = pool ;
}
}
/ * *
* @ memberof PostgreSQLClient
* /
destroy ( ) {
this . _connection . end ( ) ;
}
/ * *
* Executes an USE query
*
* @ param { String } schema
* @ memberof PostgreSQLClient
* /
use ( schema ) {
this . _schema = schema ;
return this . raw ( ` SET search_path TO ' ${ schema } ', ' $ user' ` ) ;
}
/ * *
* @ param { Array } schemas list
* @ returns { Array . < Object > } 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' ) ;
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 = [ ] ;
for ( const db of databases ) {
if ( ! schemas . has ( db . database ) ) continue ;
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 ,
c . reltuples , obj _description ( c . oid ) AS comment
FROM "information_schema" . "tables" AS t
LEFT JOIN "pg_namespace" n ON t . table _schema = n . nspname
LEFT JOIN "pg_class" c ON n . oid = c . relnamespace AND c . relname = t . table _name
WHERE t . "table_schema" = '${db.database}'
ORDER BY table _name
` );
if ( tables . length ) {
tables = tables . map ( table => {
table . Db = db . database ;
return table ;
} ) ;
tablesArr . push ( ... tables ) ;
}
let { rows : triggers } = await this . raw ( `
SELECT event _object _schema AS table _schema ,
event _object _table AS table _name ,
trigger _schema ,
trigger _name ,
string _agg ( event _manipulation , ',' ) AS event ,
action _timing AS activation ,
action _condition AS condition ,
action _statement AS definition
FROM information _schema . triggers
WHERE trigger _schema = '${db.database}'
GROUP BY 1 , 2 , 3 , 4 , 6 , 7 , 8
ORDER BY table _schema ,
table _name
` );
if ( triggers . length ) {
triggers = triggers . map ( trigger => {
trigger . Db = db . database ;
return trigger ;
} ) ;
triggersArr . push ( ... triggers ) ;
}
}
return databases . map ( db => {
if ( schemas . has ( db . database ) ) {
// TABLES
const remappedTables = tablesArr . filter ( table => table . Db === db . database ) . map ( table => {
return {
name : table . table _name ,
type : table . table _type === 'VIEW' ? 'view' : 'table' ,
rows : table . reltuples ,
size : + table . data _length + + table . index _length ,
collation : table . Collation ,
comment : table . comment ,
engine : ''
} ;
} ) ;
// PROCEDURES
const remappedProcedures = procedures . filter ( procedure => procedure . Db === db . database ) . map ( procedure => {
return {
name : procedure . Name ,
type : procedure . Type ,
definer : procedure . Definer ,
created : procedure . Created ,
updated : procedure . Modified ,
comment : procedure . Comment ,
charset : procedure . character _set _client ,
security : procedure . Security _type
} ;
} ) ;
// FUNCTIONS
const remappedFunctions = functions . filter ( func => func . Db === db . database ) . map ( func => {
return {
name : func . routine _name ,
type : func . routine _type ,
definer : null , // func.Definer,
created : null , // func.Created,
updated : null , // func.Modified,
comment : null , // func.Comment,
charset : null , // func.character_set_client,
security : func . security _type
} ;
} ) ;
// TRIGGERS
const remappedTriggers = triggersArr . filter ( trigger => trigger . Db === db . database ) . map ( trigger => {
return {
name : trigger . trigger _name ,
timing : trigger . activation ,
definer : trigger . definition , // ???
event : trigger . event ,
table : trigger . table _trigger ,
sqlMode : trigger . sql _mode
} ;
} ) ;
return {
name : db . database ,
tables : remappedTables ,
functions : remappedFunctions ,
procedures : remappedProcedures ,
triggers : remappedTriggers ,
schedulers : [ ]
} ;
}
else {
return {
name : db . database ,
tables : [ ] ,
functions : [ ] ,
procedures : [ ] ,
triggers : [ ] ,
schedulers : [ ]
} ;
}
} ) ;
}
/ * *
* @ param { Object } params
* @ param { String } params . schema
* @ param { String } params . table
* @ returns { Object } table scructure
* @ memberof PostgreSQLClient
* /
async getTableColumns ( { schema , table } ) {
const { rows } = await this
. select ( '*' )
. schema ( 'information_schema' )
. from ( 'columns' )
. where ( { table _schema : ` = ' ${ schema } ' ` , table _name : ` = ' ${ table } ' ` } )
. orderBy ( { ordinal _position : 'ASC' } )
. run ( ) ;
return rows . map ( field => {
return {
name : field . column _name ,
key : null ,
type : field . data _type . toUpperCase ( ) ,
schema : field . table _schema ,
table : field . table _name ,
numPrecision : field . numeric _precision ,
datePrecision : field . datetime _precision ,
charLength : field . character _maximum _length ,
nullable : field . is _nullable . includes ( 'YES' ) ,
unsigned : null ,
zerofill : null ,
order : field . ordinal _position ,
default : field . column _default ,
charset : field . character _set _name ,
collation : field . collation _name ,
2021-03-25 18:33:29 +01:00
autoIncrement : false ,
2021-03-16 18:42:03 +01:00
onUpdate : null ,
comment : ''
} ;
} ) ;
}
/ * *
* @ param { Object } params
* @ param { String } params . schema
* @ param { String } params . table
* @ returns { Object } table indexes
* @ memberof PostgreSQLClient
* /
async getTableIndexes ( { schema , table } ) {
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 (
2021-03-29 20:18:44 +02:00
SELECT pg _class . relname , UNNEST ( i . indkey ) AS col _ndx , CASE i . indisprimary WHEN TRUE THEN 'PRIMARY' ELSE CASE i . indisunique WHEN TRUE THEN 'UNIQUE' ELSE 'INDEX' END END AS CONSTRAINT _TYPE , pg _class . oid
2021-03-16 18:42:03 +01:00
FROM pg _class
JOIN pg _index i ON ( pg _class . oid = i . indexrelid )
JOIN ndx _list ON ( pg _class . oid = ndx _list . indexrelid )
WHERE pg _table _is _visible ( pg _class . oid ) )
SELECT ndx _cols . relname AS CONSTRAINT _NAME , ndx _cols . CONSTRAINT _TYPE , a . attname AS COLUMN _NAME
FROM pg _attribute a
JOIN ndx _cols ON ( a . attnum = ndx _cols . col _ndx )
JOIN ndx _list ON ( ndx _list . oid = a . attrelid AND ndx _list . indexrelid = ndx _cols . oid )
` );
return rows . map ( row => {
return {
name : row . constraint _name ,
column : row . column _name ,
indexType : null ,
2021-03-25 18:33:29 +01:00
type : row . constraint _type ,
cardinality : null ,
comment : '' ,
indexComment : ''
2021-03-16 18:42:03 +01:00
} ;
} ) ;
}
2021-03-19 18:49:26 +01:00
/ * *
*
* @ param { Number } id
* @ returns { Array }
* /
async getTableByIDs ( ids ) {
2021-03-21 13:00:27 +01:00
if ( ! ids ) return ;
2021-03-19 18:49:26 +01:00
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 } )
` );
return rows . reduce ( ( acc , curr ) => {
acc [ curr . tableid ] = {
table : curr . relname ,
schema : curr . schemaname
} ;
return acc ;
} , { } ) ;
}
2021-03-16 18:42:03 +01:00
/ * *
* @ param { Object } params
* @ param { String } params . schema
* @ param { String } params . table
* @ returns { Object } table key usage
* @ memberof PostgreSQLClient
* /
async getKeyUsage ( { schema , table } ) {
2021-03-19 18:49:26 +01:00
const { rows } = await this . raw ( `
2021-03-21 11:51:22 +01:00
SELECT
tc . table _schema ,
tc . constraint _name ,
tc . table _name ,
kcu . column _name ,
2021-03-25 18:33:29 +01:00
kcu . position _in _unique _constraint ,
kcu . ordinal _position ,
2021-03-21 11:51:22 +01:00
ccu . table _schema AS foreign _table _schema ,
ccu . table _name AS foreign _table _name ,
ccu . column _name AS foreign _column _name ,
rc . update _rule ,
rc . delete _rule
FROM information _schema . table _constraints AS tc
JOIN information _schema . key _column _usage AS kcu
ON tc . constraint _name = kcu . constraint _name
AND tc . table _schema = kcu . table _schema
JOIN information _schema . constraint _column _usage AS ccu
ON ccu . constraint _name = tc . constraint _name
AND ccu . table _schema = tc . table _schema
JOIN information _schema . referential _constraints AS rc
ON rc . constraint _name = kcu . constraint _name
WHERE tc . constraint _type = 'FOREIGN KEY' AND tc . table _schema = '${schema}'
AND tc . table _name = '${table}'
2021-03-19 18:49:26 +01:00
` );
2021-03-16 18:42:03 +01:00
return rows . map ( field => {
return {
2021-03-19 18:49:26 +01:00
schema : field . table _schema ,
table : field . table _name ,
field : field . column _name ,
position : field . ordinal _position ,
2021-03-21 11:51:22 +01:00
constraintPosition : field . position _in _unique _constraint ,
2021-03-19 18:49:26 +01:00
constraintName : field . constraint _name ,
2021-03-21 11:51:22 +01:00
refSchema : field . foreign _table _schema ,
refTable : field . foreign _table _name ,
refField : field . foreign _column _name ,
2021-03-19 18:49:26 +01:00
onUpdate : field . update _rule ,
onDelete : field . delete _rule
2021-03-16 18:42:03 +01:00
} ;
} ) ;
}
/ * *
* SELECT * FROM pg _catalog . pg _user
*
* @ returns { Array . < Object > } users list
* @ memberof PostgreSQLClient
* /
async getUsers ( ) {
const { rows } = await this . raw ( 'SELECT * FROM pg_catalog.pg_user' ) ;
return rows . map ( row => {
return {
name : row . username ,
host : row . host ,
password : row . passwd
} ;
} ) ;
}
/ * *
* CREATE SCHEMA
*
* @ returns { Array . < Object > } parameters
* @ memberof MySQLClient
* /
2021-03-17 11:15:14 +01:00
async createSchema ( params ) {
2021-03-16 18:42:03 +01:00
return await this . raw ( ` CREATE SCHEMA " ${ params . name } " ` ) ;
}
/ * *
* ALTER DATABASE
*
* @ returns { Array . < Object > } parameters
* @ memberof MySQLClient
* /
2021-03-17 11:15:14 +01:00
async alterSchema ( params ) {
2021-03-16 18:42:03 +01:00
return await this . raw ( ` ALTER SCHEMA " ${ params . name } " ` ) ;
}
/ * *
* DROP DATABASE
*
* @ returns { Array . < Object > } parameters
* @ memberof MySQLClient
* /
2021-03-17 11:15:14 +01:00
async dropSchema ( params ) {
2021-03-16 18:42:03 +01:00
return await this . raw ( ` DROP SCHEMA " ${ params . database } " ` ) ;
}
/ * *
* SHOW CREATE VIEW
*
* @ returns { Array . < Object > } view informations
* @ memberof PostgreSQLClient
* /
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 ( / ( ? < = C R E A T E A L G O R I T H M = ) . * ? ( ? = \ s ) / g s ) [ 0 ] ,
definer : row [ 'Create View' ] . match ( / ( ? < = D E F I N E R = ) . * ? ( ? = \ s ) / g s ) [ 0 ] ,
security : row [ 'Create View' ] . match ( / ( ? < = S Q L S E C U R I T Y ) . * ? ( ? = \ s ) / g s ) [ 0 ] ,
updateOption : row [ 'Create View' ] . match ( / ( ? < = W I T H ) . * ? ( ? = \ s ) / g s ) ? r o w [ ' C r e a t e V i e w ' ] . m a t c h ( / ( ? < = W I T H ) . * ? ( ? = \ s ) / g s ) [ 0 ] : ' ' ,
sql : row [ 'Create View' ] . match ( / ( ? < = A S ) . * ? $ / g s ) [ 0 ] ,
name : row . View
} ;
} ) [ 0 ] ;
}
/ * *
* DROP VIEW
*
* @ returns { Array . < Object > } parameters
* @ memberof PostgreSQLClient
* /
async dropView ( params ) {
const sql = ` DROP VIEW \` ${ params . view } \` ` ;
return await this . raw ( sql ) ;
}
/ * *
* ALTER VIEW
*
* @ returns { Array . < Object > } parameters
* @ memberof PostgreSQLClient
* /
async alterView ( params ) {
const { view } = params ;
let sql = ` ALTER ALGORITHM = ${ view . algorithm } ${ view . definer ? ` DEFINER= ${ view . definer } ` : '' } SQL SECURITY ${ view . security } VIEW \` ${ view . oldName } \` AS ${ view . sql } ${ view . updateOption ? ` WITH ${ view . updateOption } CHECK OPTION ` : '' } ` ;
if ( view . name !== view . oldName )
sql += ` ; RENAME TABLE \` ${ view . oldName } \` TO \` ${ view . name } \` ` ;
return await this . raw ( sql ) ;
}
/ * *
* CREATE VIEW
*
* @ returns { Array . < Object > } parameters
* @ memberof PostgreSQLClient
* /
async createView ( view ) {
const sql = ` CREATE ALGORITHM = ${ view . algorithm } ${ view . definer ? ` DEFINER= ${ view . definer } ` : '' } SQL SECURITY ${ view . security } VIEW \` ${ view . name } \` AS ${ view . sql } ${ view . updateOption ? ` WITH ${ view . updateOption } CHECK OPTION ` : '' } ` ;
return await this . raw ( sql ) ;
}
/ * *
* SHOW CREATE TRIGGER
*
* @ returns { Array . < Object > } view informations
* @ memberof PostgreSQLClient
* /
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 ( / ( ? < = D E F I N E R = ) . * ? ( ? = \ s ) / g s ) [ 0 ] ,
sql : row [ 'SQL Original Statement' ] . match ( / ( B E G I N | b e g i n ) ( . * ) ( E N D | e n d ) / g s ) [ 0 ] ,
name : row . Trigger ,
table : row [ 'SQL Original Statement' ] . match ( / ( ? < = O N ` ) . * ? ( ? = ` ) / g s ) [ 0 ] ,
event1 : row [ 'SQL Original Statement' ] . match ( / ( B E F O R E | A F T E R ) / g s ) [ 0 ] ,
event2 : row [ 'SQL Original Statement' ] . match ( / ( I N S E R T | U P D A T E | D E L E T E ) / g s ) [ 0 ]
} ;
} ) [ 0 ] ;
}
/ * *
* DROP TRIGGER
*
* @ returns { Array . < Object > } parameters
* @ memberof PostgreSQLClient
* /
async dropTrigger ( params ) {
const sql = ` DROP TRIGGER \` ${ params . trigger } \` ` ;
return await this . raw ( sql ) ;
}
/ * *
* ALTER TRIGGER
*
* @ returns { Array . < Object > } parameters
* @ 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 ( { trigger : tempTrigger . name } ) ;
await this . dropTrigger ( { trigger : trigger . oldName } ) ;
await this . createTrigger ( trigger ) ;
}
catch ( err ) {
return Promise . reject ( err ) ;
}
}
/ * *
* CREATE TRIGGER
*
* @ returns { Array . < Object > } parameters
* @ memberof PostgreSQLClient
* /
async createTrigger ( trigger ) {
const sql = ` CREATE ${ trigger . definer ? ` DEFINER= ${ trigger . definer } ` : '' } TRIGGER \` ${ trigger . name } \` ${ trigger . event1 } ${ trigger . event2 } ON \` ${ trigger . table } \` FOR EACH ROW ${ trigger . sql } ` ;
return await this . raw ( sql , { split : false } ) ;
}
/ * *
* SHOW CREATE PROCEDURE
*
* @ returns { Array . < Object > } view informations
* @ memberof PostgreSQLClient
* /
async getRoutineInformations ( { schema , routine } ) {
const sql = ` SHOW CREATE PROCEDURE \` ${ schema } \` . \` ${ routine } \` ` ;
const results = await this . raw ( sql ) ;
return results . rows . map ( row => {
if ( ! row [ 'Create Procedure' ] ) {
return {
definer : null ,
sql : '' ,
parameters : [ ] ,
name : row . Procedure ,
comment : '' ,
security : 'DEFINER' ,
deterministic : false ,
dataAccess : 'CONTAINS SQL'
} ;
}
const parameters = row [ 'Create Procedure' ]
. match ( / ( \ ( [ ^ ( ) ] * ( ? : ( ? : \ ( [ ^ ( ) ] * \ ) ) [ ^ ( ) ] * ) * \ ) \ s * ) / s ) [ 0 ]
. replaceAll ( '\r' , '' )
. replaceAll ( '\t' , '' )
. slice ( 1 , - 1 )
. split ( ',' )
. map ( el => {
const param = el . split ( ' ' ) ;
const type = param [ 2 ] ? param [ 2 ] . replace ( ')' , '' ) . split ( '(' ) : [ '' , null ] ;
return {
name : param [ 1 ] ? param [ 1 ] . replaceAll ( '`' , '' ) : '' ,
type : type [ 0 ] . replaceAll ( '\n' , '' ) ,
length : + type [ 1 ] ? + type [ 1 ] . replace ( /\D/g , '' ) : '' ,
context : param [ 0 ] ? param [ 0 ] . replace ( '\n' , '' ) : ''
} ;
} ) . filter ( el => el . name ) ;
let dataAccess = 'CONTAINS SQL' ;
if ( row [ 'Create Procedure' ] . includes ( 'NO SQL' ) )
dataAccess = 'NO SQL' ;
if ( row [ 'Create Procedure' ] . includes ( 'READS SQL DATA' ) )
dataAccess = 'READS SQL DATA' ;
if ( row [ 'Create Procedure' ] . includes ( 'MODIFIES SQL DATA' ) )
dataAccess = 'MODIFIES SQL DATA' ;
return {
definer : row [ 'Create Procedure' ] . match ( / ( ? < = D E F I N E R = ) . * ? ( ? = \ s ) / g s ) [ 0 ] ,
sql : row [ 'Create Procedure' ] . match ( / ( B E G I N | b e g i n ) ( . * ) ( E N D | e n d ) / g s ) [ 0 ] ,
parameters : parameters || [ ] ,
name : row . Procedure ,
comment : row [ 'Create Procedure' ] . match ( / ( ? < = C O M M E N T ' ) . * ? ( ? = ' ) / g s ) ? r o w [ ' C r e a t e P r o c e d u r e ' ] . m a t c h ( / ( ? < = C O M M E N T ' ) . * ? ( ? = ' ) / g s ) [ 0 ] : ' ' ,
security : row [ 'Create Procedure' ] . includes ( 'SQL SECURITY INVOKER' ) ? 'INVOKER' : 'DEFINER' ,
deterministic : row [ 'Create Procedure' ] . includes ( 'DETERMINISTIC' ) ,
dataAccess
} ;
} ) [ 0 ] ;
}
/ * *
* DROP PROCEDURE
*
* @ returns { Array . < Object > } parameters
* @ memberof PostgreSQLClient
* /
async dropRoutine ( params ) {
const sql = ` DROP PROCEDURE \` ${ params . routine } \` ` ;
return await this . raw ( sql ) ;
}
/ * *
* ALTER PROCEDURE
*
* @ returns { Array . < Object > } 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 ( { routine : tempProcedure . name } ) ;
await this . dropRoutine ( { routine : routine . oldName } ) ;
await this . createRoutine ( routine ) ;
}
catch ( err ) {
return Promise . reject ( err ) ;
}
}
/ * *
* CREATE PROCEDURE
*
* @ returns { Array . < Object > } parameters
* @ memberof PostgreSQLClient
* /
async createRoutine ( routine ) {
const parameters = 'parameters' in routine
? routine . parameters . reduce ( ( acc , curr ) => {
acc . push ( ` ${ curr . context } \` ${ curr . name } \` ${ curr . type } ${ curr . length ? ` ( ${ curr . length } ) ` : '' } ` ) ;
return acc ;
} , [ ] ) . join ( ',' )
: '' ;
const sql = ` CREATE ${ routine . definer ? ` DEFINER= ${ routine . definer } ` : '' } PROCEDURE \` ${ routine . name } \` ( ${ parameters } )
LANGUAGE SQL
$ { routine . deterministic ? 'DETERMINISTIC' : 'NOT DETERMINISTIC' }
$ { routine . dataAccess }
SQL SECURITY $ { routine . security }
COMMENT '${routine.comment}'
$ { routine . sql } ` ;
return await this . raw ( sql , { split : false } ) ;
}
/ * *
* SHOW CREATE FUNCTION
*
* @ returns { Array . < Object > } view informations
* @ memberof PostgreSQLClient
* /
async getFunctionInformations ( { schema , func } ) {
const sql = ` SHOW CREATE FUNCTION \` ${ schema } \` . \` ${ func } \` ` ;
const results = await this . raw ( sql ) ;
return results . rows . map ( 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 parameters = row [ 'Create Function' ]
. match ( / ( \ ( [ ^ ( ) ] * ( ? : ( ? : \ ( [ ^ ( ) ] * \ ) ) [ ^ ( ) ] * ) * \ ) \ s * ) / s ) [ 0 ]
. replaceAll ( '\r' , '' )
. replaceAll ( '\t' , '' )
. slice ( 1 , - 1 )
. split ( ',' )
. map ( el => {
const param = el . split ( ' ' ) ;
const type = param [ 1 ] ? param [ 1 ] . replace ( ')' , '' ) . split ( '(' ) : [ '' , null ] ;
return {
name : param [ 0 ] ? param [ 0 ] . replaceAll ( '`' , '' ) : '' ,
type : type [ 0 ] ,
length : + type [ 1 ] ? + type [ 1 ] . replace ( /\D/g , '' ) : ''
} ;
} ) . filter ( el => el . name ) ;
let dataAccess = 'CONTAINS SQL' ;
if ( row [ 'Create Function' ] . includes ( 'NO SQL' ) )
dataAccess = 'NO SQL' ;
if ( row [ 'Create Function' ] . includes ( 'READS SQL DATA' ) )
dataAccess = 'READS SQL DATA' ;
if ( row [ 'Create Function' ] . includes ( 'MODIFIES SQL DATA' ) )
dataAccess = 'MODIFIES SQL DATA' ;
const output = row [ 'Create Function' ] . match ( / ( ? < = R E T U R N S ) . * ? ( ? = \ s ) / g s ) . l e n g t h ? r o w [ ' C r e a t e F u n c t i o n ' ] . m a t c h ( / ( ? < = R E T U R N S ) . * ? ( ? = \ s ) / g s ) [ 0 ] . r e p l a c e ( ' ) ' , ' ' ) . s p l i t ( ' ( ' ) : [ ' ' , n u l l ] ;
return {
definer : row [ 'Create Function' ] . match ( / ( ? < = D E F I N E R = ) . * ? ( ? = \ s ) / g s ) [ 0 ] ,
sql : row [ 'Create Function' ] . match ( / ( B E G I N | b e g i n ) ( . * ) ( E N D | e n d ) / g s ) [ 0 ] ,
parameters : parameters || [ ] ,
name : row . Function ,
comment : row [ 'Create Function' ] . match ( / ( ? < = C O M M E N T ' ) . * ? ( ? = ' ) / g s ) ? r o w [ ' C r e a t e F u n c t i o n ' ] . m a t c h ( / ( ? < = C O M M E N T ' ) . * ? ( ? = ' ) / g s ) [ 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 { Array . < Object > } parameters
* @ memberof PostgreSQLClient
* /
async dropFunction ( params ) {
const sql = ` DROP FUNCTION \` ${ params . func } \` ` ;
return await this . raw ( sql ) ;
}
/ * *
* ALTER FUNCTION
*
* @ returns { Array . < Object > } 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 ( { func : tempProcedure . name } ) ;
await this . dropFunction ( { func : func . oldName } ) ;
await this . createFunction ( func ) ;
}
catch ( err ) {
return Promise . reject ( err ) ;
}
}
/ * *
* CREATE FUNCTION
*
* @ returns { Array . < Object > } parameters
* @ memberof PostgreSQLClient
* /
async createFunction ( func ) {
const parameters = func . parameters . reduce ( ( acc , curr ) => {
acc . push ( ` \` ${ curr . name } \` ${ curr . type } ${ curr . length ? ` ( ${ curr . length } ) ` : '' } ` ) ;
return acc ;
} , [ ] ) . join ( ',' ) ;
const sql = ` CREATE ${ func . definer ? ` DEFINER= ${ func . definer } ` : '' } FUNCTION \` ${ func . name } \` ( ${ parameters } ) RETURNS ${ func . returns } ${ func . returnsLength ? ` ( ${ func . returnsLength } ) ` : '' }
LANGUAGE SQL
$ { func . deterministic ? 'DETERMINISTIC' : 'NOT DETERMINISTIC' }
$ { func . dataAccess }
SQL SECURITY $ { func . security }
COMMENT '${func.comment}'
$ { func . sql } ` ;
return await this . raw ( sql , { split : false } ) ;
}
/ * *
* SHOW CREATE EVENT
*
* @ returns { Array . < Object > } view informations
* @ memberof PostgreSQLClient
* /
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 ( / ( ? < = E V E R Y ) ( \ s * ( [ ^ \ s ] + ) ) { 0 , 2 } / g s ) [ 0 ] . r e p l a c e A l l ( ' \ ' ' , ' ' ) . s p l i t ( ' ' ) : [ ] ;
const starts = execution === 'EVERY' && schedule . includes ( 'STARTS' ) ? schedule . match ( / ( ? < = S T A R T S ' ) . * ? ( ? = ' \ s ) / g s ) [ 0 ] : ' ' ;
const ends = execution === 'EVERY' && schedule . includes ( 'ENDS' ) ? schedule . match ( / ( ? < = E N D S ' ) . * ? ( ? = ' \ s ) / g s ) [ 0 ] : ' ' ;
const at = execution === 'ONCE' && schedule . includes ( 'AT' ) ? schedule . match ( / ( ? < = A T ' ) . * ? ( ? = ' \ s ) / g s ) [ 0 ] : ' ' ;
return {
definer : row [ 'Create Event' ] . match ( / ( ? < = D E F I N E R = ) . * ? ( ? = \ s ) / g s ) [ 0 ] ,
sql : row [ 'Create Event' ] . match ( / ( ? < = D O ) ( . * ) / g s ) [ 0 ] ,
name : row . Event ,
comment : row [ 'Create Event' ] . match ( / ( ? < = C O M M E N T ' ) . * ? ( ? = ' ) / g s ) ? r o w [ ' C r e a t e E v e n t ' ] . m a t c h ( / ( ? < = C O M M E N T ' ) . * ? ( ? = ' ) / g s ) [ 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 { Array . < Object > } parameters
* @ memberof PostgreSQLClient
* /
async dropEvent ( params ) {
const sql = ` DROP EVENT \` ${ params . scheduler } \` ` ;
return await this . raw ( sql ) ;
}
/ * *
* ALTER EVENT
*
* @ returns { Array . < Object > } parameters
* @ memberof PostgreSQLClient
* /
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 . 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 . name } \` ` : '' }
$ { scheduler . state }
COMMENT '${scheduler.comment}'
DO $ { scheduler . sql } ` ;
return await this . raw ( sql , { split : false } ) ;
}
/ * *
* CREATE EVENT
*
* @ returns { Array . < Object > } parameters
* @ memberof PostgreSQLClient
* /
async createEvent ( scheduler ) {
const sql = ` CREATE ${ scheduler . definer ? ` DEFINER= ${ scheduler . definer } ` : '' } EVENT \` ${ scheduler . name } \`
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 . state }
COMMENT '${scheduler.comment}'
DO $ { scheduler . sql } ` ;
return await this . raw ( sql , { split : false } ) ;
}
/ * *
* SELECT * FROM pg _collation
*
* @ returns { Array . < Object > } collations list
* @ memberof PostgreSQLClient
* /
async getCollations ( ) {
return [ ] ;
}
/ * *
* SHOW ALL
*
* @ returns { Array . < Object > } 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 . < Object > } engines list
* @ memberof PostgreSQLClient
* /
async getEngines ( ) {
return {
name : 'PostgreSQL' ,
support : 'YES' ,
comment : '' ,
isDefault : true
} ;
}
/ * *
* SHOW VARIABLES LIKE '%vers%'
*
* @ returns { Array . < Object > } 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
} ;
} ) ;
}
/ * *
* CREATE TABLE
*
* @ returns { Array . < Object > } parameters
* @ memberof PostgreSQLClient
* /
async createTable ( params ) {
const {
2021-03-28 11:55:15 +02:00
name
2021-03-16 18:42:03 +01:00
} = params ;
2021-03-28 11:55:15 +02:00
const sql = ` CREATE TABLE ${ name } ( ${ name } _id INTEGER NULL) ` ;
2021-03-16 18:42:03 +01:00
return await this . raw ( sql ) ;
}
/ * *
* ALTER TABLE
*
* @ returns { Array . < Object > } parameters
* @ memberof PostgreSQLClient
* /
async alterTable ( params ) {
const {
table ,
additions ,
deletions ,
changes ,
indexChanges ,
foreignChanges ,
options
} = params ;
2021-03-25 18:33:29 +01:00
let sql = '' ;
2021-03-16 18:42:03 +01:00
const alterColumns = [ ] ;
2021-03-25 18:33:29 +01:00
const renameColumns = [ ] ;
const createSequences = [ ] ;
2021-03-16 18:42:03 +01:00
// OPTIONS
if ( 'comment' in options ) alterColumns . push ( ` COMMENT=' ${ options . comment } ' ` ) ;
if ( 'engine' in options ) alterColumns . push ( ` ENGINE= ${ options . engine } ` ) ;
if ( 'autoIncrement' in options ) alterColumns . push ( ` AUTO_INCREMENT= ${ + options . autoIncrement } ` ) ;
if ( 'collation' in options ) alterColumns . push ( ` COLLATE=' ${ options . collation } ' ` ) ;
// ADD FIELDS
additions . forEach ( addition => {
const typeInfo = this . _getTypeInfo ( addition . type ) ;
const length = typeInfo . length ? addition . numLength || addition . charLength || addition . datePrecision : false ;
2021-03-25 18:33:29 +01:00
alterColumns . push ( ` ADD COLUMN ${ addition . name }
2021-03-16 18:42:03 +01:00
$ { addition . type . toUpperCase ( ) } $ { length ? ` ( ${ length } ) ` : '' }
$ { addition . unsigned ? 'UNSIGNED' : '' }
$ { addition . zerofill ? 'ZEROFILL' : '' }
$ { addition . nullable ? 'NULL' : 'NOT NULL' }
$ { addition . autoIncrement ? 'AUTO_INCREMENT' : '' }
$ { addition . default ? ` DEFAULT ${ addition . default } ` : '' }
$ { addition . comment ? ` COMMENT ' ${ addition . comment } ' ` : '' }
$ { addition . collation ? ` COLLATE ${ addition . collation } ` : '' }
2021-03-25 18:33:29 +01:00
$ { addition . onUpdate ? ` ON UPDATE ${ addition . onUpdate } ` : '' } ` );
2021-03-16 18:42:03 +01:00
} ) ;
// ADD INDEX
indexChanges . additions . forEach ( addition => {
2021-03-29 20:18:44 +02:00
const fields = addition . fields . map ( field => ` ${ field } ` ) . join ( ',' ) ;
const type = addition . type ;
2021-03-16 18:42:03 +01:00
if ( type === 'PRIMARY' )
alterColumns . push ( ` ADD PRIMARY KEY ( ${ fields } ) ` ) ;
2021-03-29 20:18:44 +02:00
else if ( type === 'UNIQUE' )
alterColumns . push ( ` ADD CONSTRAINT ${ addition . name } UNIQUE ( ${ fields } ) ` ) ;
2021-03-16 18:42:03 +01:00
} ) ;
// ADD FOREIGN KEYS
foreignChanges . additions . forEach ( addition => {
alterColumns . push ( ` ADD CONSTRAINT \` ${ addition . constraintName } \` FOREIGN KEY ( \` ${ addition . field } \` ) REFERENCES \` ${ addition . refTable } \` ( \` ${ addition . refField } \` ) ON UPDATE ${ addition . onUpdate } ON DELETE ${ addition . onDelete } ` ) ;
} ) ;
// CHANGE FIELDS
changes . forEach ( change => {
const typeInfo = this . _getTypeInfo ( change . type ) ;
const length = typeInfo . length ? change . numLength || change . charLength || change . datePrecision : false ;
2021-03-25 18:33:29 +01:00
let localType ;
2021-03-16 18:42:03 +01:00
2021-03-25 18:33:29 +01:00
switch ( change . type ) {
case 'SERIAL' :
localType = 'integer' ;
break ;
case 'SMALLSERIAL' :
localType = 'smallint' ;
break ;
case 'BIGSERIAL' :
localType = 'bigint' ;
break ;
default :
localType = change . type . toLowerCase ( ) ;
}
alterColumns . push ( ` ALTER COLUMN " ${ change . orgName } " TYPE ${ localType } ${ length ? ` ( ${ length } ) ` : '' } USING " ${ change . orgName } ":: ${ localType } ` ) ;
alterColumns . push ( ` ALTER COLUMN " ${ change . orgName } " ${ change . nullable ? 'DROP NOT NULL' : 'SET NOT NULL' } ` ) ;
alterColumns . push ( ` ALTER COLUMN " ${ change . orgName } " ${ change . default ? ` SET DEFAULT ${ change . default } ` : 'DROP DEFAULT' } ` ) ;
if ( [ 'SERIAL' , 'SMALLSERIAL' , 'BIGSERIAL' ] . includes ( change . type ) ) {
const sequenceName = ` ${ table } _ ${ change . name } _seq ` . replace ( ' ' , '_' ) ;
createSequences . push ( ` CREATE SEQUENCE IF NOT EXISTS ${ sequenceName } OWNED BY " ${ table } "." ${ change . orgName } " ` ) ;
alterColumns . push ( ` ALTER COLUMN " ${ change . orgName } " SET DEFAULT nextval(' ${ sequenceName } ') ` ) ;
}
if ( change . orgName !== change . name )
renameColumns . push ( ` ALTER TABLE " ${ table } " RENAME COLUMN " ${ change . orgName } " TO " ${ change . name } " ` ) ;
2021-03-16 18:42:03 +01:00
} ) ;
// CHANGE INDEX
indexChanges . changes . forEach ( change => {
if ( change . oldType === 'PRIMARY' )
alterColumns . push ( 'DROP PRIMARY KEY' ) ;
else
2021-03-29 20:18:44 +02:00
alterColumns . push ( ` DROP CONSTRAINT ${ change . oldName } ` ) ;
2021-03-16 18:42:03 +01:00
2021-03-29 20:18:44 +02:00
const fields = change . fields . map ( field => ` ${ field } ` ) . join ( ',' ) ;
const type = change . type ;
2021-03-16 18:42:03 +01:00
if ( type === 'PRIMARY' )
alterColumns . push ( ` ADD PRIMARY KEY ( ${ fields } ) ` ) ;
2021-03-29 20:18:44 +02:00
else if ( type === 'UNIQUE' )
alterColumns . push ( ` ADD CONSTRAINT ${ change . name } UNIQUE ( ${ fields } ) ` ) ;
2021-03-16 18:42:03 +01:00
} ) ;
// CHANGE FOREIGN KEYS
foreignChanges . changes . forEach ( change => {
alterColumns . push ( ` DROP FOREIGN KEY \` ${ change . oldName } \` ` ) ;
alterColumns . push ( ` ADD CONSTRAINT \` ${ change . constraintName } \` FOREIGN KEY ( \` ${ change . field } \` ) REFERENCES \` ${ change . refTable } \` ( \` ${ change . refField } \` ) ON UPDATE ${ change . onUpdate } ON DELETE ${ change . onDelete } ` ) ;
} ) ;
// DROP FIELDS
deletions . forEach ( deletion => {
2021-03-25 18:33:29 +01:00
alterColumns . push ( ` DROP COLUMN ${ deletion . name } ` ) ;
2021-03-16 18:42:03 +01:00
} ) ;
// DROP INDEX
indexChanges . deletions . forEach ( deletion => {
if ( deletion . type === 'PRIMARY' )
alterColumns . push ( 'DROP PRIMARY KEY' ) ;
else
2021-03-29 20:18:44 +02:00
alterColumns . push ( ` DROP CONSTRAINT ${ deletion . name } ` ) ;
2021-03-16 18:42:03 +01:00
} ) ;
// DROP FOREIGN KEYS
foreignChanges . deletions . forEach ( deletion => {
2021-03-29 20:18:44 +02:00
alterColumns . push ( ` DROP FOREIGN KEY ${ deletion . constraintName } ` ) ;
2021-03-16 18:42:03 +01:00
} ) ;
2021-03-25 18:33:29 +01:00
if ( alterColumns . length ) sql += ` ALTER TABLE " ${ table } " ${ alterColumns . join ( ', ' ) } ; ` ;
2021-03-16 18:42:03 +01:00
// RENAME
2021-03-25 18:33:29 +01:00
if ( renameColumns . length ) sql += ` ${ renameColumns . join ( ';' ) } ; ` ;
if ( createSequences . length ) sql = ` ${ createSequences . join ( ';' ) } ; ${ sql } ` ;
if ( options . name ) sql += ` ALTER TABLE " ${ table } " RENAME TO " ${ options . name } "; ` ;
2021-03-16 18:42:03 +01:00
return await this . raw ( sql ) ;
}
/ * *
* TRUNCATE TABLE
*
* @ returns { Array . < Object > } parameters
* @ memberof PostgreSQLClient
* /
async truncateTable ( params ) {
const sql = ` TRUNCATE TABLE ${ params . table } ` ;
return await this . raw ( sql ) ;
}
/ * *
* DROP TABLE
*
* @ returns { Array . < Object > } parameters
* @ memberof PostgreSQLClient
* /
async dropTable ( params ) {
const sql = ` DROP TABLE ${ params . table } ` ;
return await this . raw ( sql ) ;
}
/ * *
* @ returns { String } SQL string
* @ memberof PostgreSQLClient
* /
getSQL ( ) {
// SELECT
const selectArray = this . _query . select . reduce ( this . _reducer , [ ] ) ;
let selectRaw = '' ;
if ( selectArray . length )
selectRaw = selectArray . length ? ` SELECT ${ selectArray . join ( ', ' ) } ` : 'SELECT * ' ;
// FROM
let fromRaw = '' ;
if ( ! this . _query . update . length && ! Object . keys ( this . _query . insert ) . length && ! ! this . _query . from )
fromRaw = 'FROM' ;
else if ( Object . keys ( this . _query . insert ) . length )
fromRaw = 'INTO' ;
fromRaw += this . _query . from ? ` ${ this . _query . schema ? ` ${ this . _query . schema } . ` : '' } ${ this . _query . from } ` : '' ;
// WHERE
const whereArray = this . _query . where . reduce ( this . _reducer , [ ] ) ;
const whereRaw = whereArray . length ? ` WHERE ${ whereArray . join ( ' AND ' ) } ` : '' ;
// UPDATE
const updateArray = this . _query . update . reduce ( this . _reducer , [ ] ) ;
const updateRaw = updateArray . length ? ` SET ${ updateArray . join ( ', ' ) } ` : '' ;
// INSERT
let insertRaw = '' ;
if ( this . _query . insert . length ) {
const fieldsList = Object . keys ( this . _query . insert [ 0 ] ) . map ( f => ` " ${ f } " ` ) ;
const rowsList = this . _query . insert . map ( el => ` ( ${ Object . values ( el ) . join ( ', ' ) } ) ` ) ;
insertRaw = ` ( ${ fieldsList . join ( ', ' ) } ) VALUES ${ rowsList . join ( ', ' ) } ` ;
}
// GROUP BY
const groupByArray = this . _query . groupBy . reduce ( this . _reducer , [ ] ) ;
const groupByRaw = groupByArray . length ? ` GROUP BY ${ groupByArray . join ( ', ' ) } ` : '' ;
// ORDER BY
const orderByArray = this . _query . orderBy . reduce ( this . _reducer , [ ] ) ;
const orderByRaw = orderByArray . length ? ` ORDER BY ${ orderByArray . join ( ', ' ) } ` : '' ;
// LIMIT
2021-03-21 13:00:27 +01:00
const limitRaw = selectArray . length && this . _query . limit . length ? ` LIMIT ${ this . _query . limit . join ( ', ' ) } ` : '' ;
2021-03-16 18:42:03 +01:00
return ` ${ selectRaw } ${ updateRaw ? 'UPDATE' : '' } ${ insertRaw ? 'INSERT ' : '' } ${ this . _query . delete ? 'DELETE ' : '' } ${ fromRaw } ${ updateRaw } ${ whereRaw } ${ groupByRaw } ${ orderByRaw } ${ limitRaw } ${ 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 ) {
args = {
nest : false ,
details : false ,
split : true ,
... args
} ;
const resultsArr = [ ] ;
let paramsArr = [ ] ;
const queries = args . split ? sql . split ( ';' ) : [ sql ] ;
if ( process . env . NODE _ENV === 'development' ) this . _logger ( sql ) ; // TODO: replace BLOB content with a placeholder
for ( const query of queries ) {
if ( ! query ) continue ;
const timeStart = new Date ( ) ;
let timeStop ;
let keysArr = [ ] ;
const { rows , report , fields , keys , duration } = await new Promise ( ( resolve , reject ) => {
2021-03-19 18:49:26 +01:00
this . _connection . query ( {
rowMode : args . nest ? 'array' : null ,
text : query
} , async ( err , res ) => {
2021-03-16 18:42:03 +01:00
timeStop = new Date ( ) ;
if ( err )
reject ( err ) ;
else {
let ast ;
2021-03-19 18:49:26 +01:00
2021-03-16 18:42:03 +01:00
try {
2021-03-19 18:49:26 +01:00
[ ast ] = parse ( query ) ;
2021-03-16 18:42:03 +01:00
}
2021-03-19 18:49:26 +01:00
catch ( err ) { }
2021-03-16 18:42:03 +01:00
2021-03-19 18:49:26 +01:00
const { rows , fields } = res ;
let queryResult ;
2021-03-21 11:51:22 +01:00
let tablesInfo ;
2021-03-19 18:49:26 +01:00
if ( args . nest ) {
const tablesID = [ ... new Set ( fields . map ( field => field . tableID ) ) ] . toString ( ) ;
2021-03-21 11:51:22 +01:00
tablesInfo = await this . getTableByIDs ( tablesID ) ;
2021-03-19 18:49:26 +01:00
queryResult = rows . map ( row => {
return row . reduce ( ( acc , curr , i ) => {
2021-03-20 16:29:56 +01:00
const table = tablesInfo [ fields [ i ] . tableID ] ? tablesInfo [ fields [ i ] . tableID ] . table : '' ;
acc [ ` ${ table ? ` ${ table } . ` : '' } ${ fields [ i ] . name } ` ] = curr ;
2021-03-19 18:49:26 +01:00
return acc ;
} , { } ) ;
} ) ;
2021-03-16 18:42:03 +01:00
}
2021-03-19 18:49:26 +01:00
else
queryResult = rows ;
2021-03-16 18:42:03 +01:00
let remappedFields = fields
? fields . map ( field => {
if ( ! field || Array . isArray ( field ) )
return false ;
2021-03-21 11:51:22 +01:00
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 ;
if ( args . nest ) {
schema = tablesInfo [ field . tableID ] ? tablesInfo [ field . tableID ] . schema : this . _schema ;
table = tablesInfo [ field . tableID ] ? tablesInfo [ field . tableID ] . table : null ;
}
2021-03-16 18:42:03 +01:00
return {
2021-03-19 18:49:26 +01:00
... field ,
2021-03-16 18:42:03 +01:00
name : field . name ,
alias : field . name ,
2021-03-21 11:51:22 +01:00
schema ,
table ,
// TODO: pick ast.from index if multiple
2021-03-16 18:42:03 +01:00
tableAlias : ast && ast . from ? ast . from [ 0 ] . as : null ,
2021-03-19 18:49:26 +01:00
orgTable : ast && ast . from ? ast . from [ 0 ] . name : null ,
type : this . types [ field . dataTypeID ] || field . format
2021-03-16 18:42:03 +01:00
} ;
} ) . filter ( Boolean )
: [ ] ;
if ( args . details ) {
if ( remappedFields . length ) {
paramsArr = remappedFields . map ( field => {
return {
2021-03-19 18:49:26 +01:00
table : field . table ,
schema : field . schema
2021-03-16 18:42:03 +01:00
} ;
} ) . filter ( ( val , i , arr ) => arr . findIndex ( el => el . schema === val . schema && el . table === val . table ) === i ) ;
for ( const paramObj of paramsArr ) {
if ( ! paramObj . table || ! paramObj . schema ) continue ;
try { // Column details
const columns = await this . getTableColumns ( paramObj ) ;
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 ) {
2021-03-18 15:56:52 +01:00
if ( detailedField ) {
const length = detailedField . numPrecision || detailedField . charLength || detailedField . datePrecision || null ;
field = { ... field , ... detailedField , length } ;
}
2021-03-16 18:42:03 +01:00
if ( fieldIndex ) {
const key = fieldIndex . type === 'PRIMARY' ? 'pri' : fieldIndex . type === 'UNIQUE' ? 'uni' : 'mul' ;
field = { ... field , key } ;
} ;
}
return field ;
} ) ;
}
catch ( err ) {
reject ( err ) ;
}
try { // Key usage (foreign keys)
const response = await this . getKeyUsage ( paramObj ) ;
keysArr = keysArr ? [ ... keysArr , ... response ] : response ;
}
catch ( err ) {
reject ( err ) ;
}
}
}
}
resolve ( {
duration : timeStop - timeStart ,
rows : Array . isArray ( queryResult ) ? queryResult . some ( el => Array . isArray ( el ) ) ? [ ] : queryResult : false ,
report : ! Array . isArray ( queryResult ) ? queryResult : false ,
fields : remappedFields ,
keys : keysArr
} ) ;
}
} ) ;
} ) ;
resultsArr . push ( { rows , report , fields , keys , duration } ) ;
}
return resultsArr . length === 1 ? resultsArr [ 0 ] : resultsArr ;
}
}