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-07-05 09:30:52 +02:00
import * as SSH2Promise from 'ssh2-promise' ;
2021-03-16 18:42:03 +01:00
2021-03-18 15:56:52 +01:00
function pgToString ( value ) {
return value . toString ( ) ;
}
2021-11-05 11:41:12 +01:00
types . setTypeParser ( 20 , a => parseInt ( a ) ) ; // bigint string to number
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 ;
2021-04-09 19:31:41 +02:00
this . _arrayTypes = {
_int2 : 'SMALLINT' ,
_int4 : 'INTEGER' ,
_int8 : 'BIGINT' ,
_float4 : 'REAL' ,
_float8 : 'DOUBLE PRECISION' ,
_char : '"CHAR"' ,
_varchar : 'CHARACTER VARYING'
} ;
2021-03-16 18:42:03 +01:00
}
2021-10-19 17:42:31 +02:00
_reducer ( acc , curr ) {
const type = typeof curr ;
switch ( type ) {
case 'number' :
case 'string' :
return [ ... acc , curr ] ;
case 'object' :
if ( Array . isArray ( curr ) )
return [ ... acc , ... curr ] ;
else {
const clausoles = [ ] ;
for ( const key in curr )
clausoles . push ( ` " ${ key } " ${ curr [ key ] } ` ) ;
return clausoles ;
}
}
}
2021-03-16 18:42:03 +01:00
_getTypeInfo ( type ) {
return dataTypes
. reduce ( ( acc , group ) => [ ... acc , ... group . types ] , [ ] )
. filter ( _type => _type . name === type . toUpperCase ( ) ) [ 0 ] ;
}
2021-04-09 19:31:41 +02:00
_getArrayType ( type ) {
if ( Object . keys ( this . _arrayTypes ) . includes ( type ) )
return this . _arrayTypes [ type ] ;
return type . replace ( '_' , '' ) ;
}
2021-03-16 18:42:03 +01:00
/ * *
* @ memberof PostgreSQLClient
* /
async connect ( ) {
2021-07-05 09:30:52 +02:00
const dbConfig = {
host : this . _params . host ,
port : this . _params . port ,
user : this . _params . user ,
password : this . _params . password ,
ssl : null
} ;
if ( this . _params . database ? . length ) dbConfig . database = this . _params . database ;
if ( this . _params . ssl ) dbConfig . ssl = { ... this . _params . ssl } ;
if ( this . _params . ssh ) {
2021-10-07 14:58:31 +02:00
try {
this . _ssh = new SSH2Promise ( { ... this . _params . ssh } ) ;
2021-07-05 09:30:52 +02:00
2021-10-07 14:58:31 +02:00
const tunnel = await this . _ssh . addTunnel ( {
remoteAddr : this . _params . host ,
remotePort : this . _params . port
} ) ;
dbConfig . port = tunnel . localPort ;
}
catch ( err ) {
if ( this . _ssh ) this . _ssh . close ( ) ;
throw err ;
}
2021-07-05 09:30:52 +02:00
}
2021-03-16 18:42:03 +01:00
if ( ! this . _poolSize ) {
2021-07-05 09:30:52 +02:00
const client = new Client ( dbConfig ) ;
2021-03-17 16:51:26 +01:00
await client . connect ( ) ;
this . _connection = client ;
2021-03-16 18:42:03 +01:00
}
else {
2021-07-05 09:30:52 +02:00
const pool = new Pool ( { ... dbConfig , max : this . _poolSize } ) ;
2021-03-16 18:42:03 +01:00
this . _connection = pool ;
}
}
/ * *
* @ memberof PostgreSQLClient
* /
destroy ( ) {
this . _connection . end ( ) ;
2021-07-05 09:30:52 +02:00
if ( this . _ssh ) this . _ssh . close ( ) ;
2021-03-16 18:42:03 +01:00
}
/ * *
2021-08-13 16:50:59 +02:00
* Executes an "USE" query
2021-03-16 18:42:03 +01:00
*
* @ param { String } schema
* @ memberof PostgreSQLClient
* /
use ( schema ) {
this . _schema = schema ;
2021-04-19 19:15:06 +02:00
if ( schema )
2021-10-06 12:08:37 +02:00
return this . raw ( ` SET search_path TO " ${ schema } " ` ) ;
2021-03-16 18:42:03 +01:00
}
/ * *
* @ 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 = [ ] ;
2021-11-06 16:36:54 +01:00
let schemaSize = 0 ;
2021-03-16 18:42:03 +01:00
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 ( `
2021-11-04 21:54:42 +01:00
SELECT
pg _class . relname AS table _name ,
pg _trigger . tgname AS trigger _name ,
pg _namespace . nspname AS trigger _schema ,
( pg _trigger . tgenabled != 'D' ) : : bool AS enabled
FROM pg _trigger
JOIN pg _class ON pg _trigger . tgrelid = pg _class . oid
JOIN pg _namespace ON pg _namespace . oid = pg _class . relnamespace
JOIN information _schema . triggers ON information _schema . triggers . trigger _schema = pg _namespace . nspname
AND information _schema . triggers . event _object _table = pg _class . relname
AND information _schema . triggers . trigger _name = pg _trigger . tgname
2021-03-16 18:42:03 +01:00
WHERE trigger _schema = '${db.database}'
2021-11-04 21:54:42 +01:00
GROUP BY 1 , 2 , 3 , 4
ORDER BY table _name
2021-03-16 18:42:03 +01:00
` );
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 => {
2021-11-06 16:36:54 +01:00
const tableSize = + table . data _length + table . index _length ;
schemaSize += tableSize ;
2021-03-16 18:42:03 +01:00
return {
name : table . table _name ,
type : table . table _type === 'VIEW' ? 'view' : 'table' ,
rows : table . reltuples ,
2021-11-06 16:36:54 +01:00
size : tableSize ,
2021-03-16 18:42:03 +01:00
collation : table . Collation ,
comment : table . comment ,
engine : ''
} ;
} ) ;
// PROCEDURES
2021-04-09 19:31:41 +02:00
const remappedProcedures = procedures . filter ( procedure => procedure . routine _schema === db . database ) . map ( procedure => {
2021-03-16 18:42:03 +01:00
return {
2021-04-09 19:31:41 +02:00
name : procedure . routine _name ,
type : procedure . routine _type ,
security : procedure . security _type
2021-03-16 18:42:03 +01:00
} ;
} ) ;
// FUNCTIONS
2021-04-12 18:46:35 +02:00
const remappedFunctions = functions . filter ( func => func . routine _schema === db . database && func . data _type !== 'trigger' ) . map ( func => {
2021-03-16 18:42:03 +01:00
return {
name : func . routine _name ,
type : func . routine _type ,
security : func . security _type
} ;
} ) ;
2021-04-12 18:46:35 +02:00
// TRIGGER FUNCTIONS
const remappedTriggerFunctions = functions . filter ( func => func . routine _schema === db . database && func . data _type === 'trigger' ) . map ( func => {
return {
name : func . routine _name ,
type : func . routine _type ,
security : func . security _type
} ;
} ) ;
2021-03-16 18:42:03 +01:00
// TRIGGERS
const remappedTriggers = triggersArr . filter ( trigger => trigger . Db === db . database ) . map ( trigger => {
return {
2021-04-22 15:15:08 +02:00
name : ` ${ trigger . table _name } . ${ trigger . trigger _name } ` ,
orgName : trigger . trigger _name ,
definer : '' ,
table : trigger . table _name ,
2021-11-04 21:54:42 +01:00
sqlMode : '' ,
enabled : trigger . enabled
2021-03-16 18:42:03 +01:00
} ;
} ) ;
return {
name : db . database ,
2021-11-06 16:36:54 +01:00
size : schemaSize ,
2021-03-16 18:42:03 +01:00
tables : remappedTables ,
functions : remappedFunctions ,
procedures : remappedProcedures ,
triggers : remappedTriggers ,
2021-04-12 18:46:35 +02:00
triggerFunctions : remappedTriggerFunctions ,
2021-03-16 18:42:03 +01:00
schedulers : [ ]
} ;
}
else {
return {
name : db . database ,
2021-11-06 16:36:54 +01:00
size : 0 ,
2021-03-16 18:42:03 +01:00
tables : [ ] ,
functions : [ ] ,
procedures : [ ] ,
triggers : [ ] ,
2021-07-03 11:29:14 +02:00
triggerFunctions : [ ] ,
2021-03-16 18:42:03 +01:00
schedulers : [ ]
} ;
}
} ) ;
}
/ * *
* @ param { Object } params
* @ param { String } params . schema
* @ param { String } params . table
* @ returns { Object } table scructure
* @ memberof PostgreSQLClient
* /
2021-04-09 19:31:41 +02:00
async getTableColumns ( { schema , table } , arrayRemap = true ) {
2021-03-16 18:42:03 +01:00
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 => {
2021-04-09 19:31:41 +02:00
let type = field . data _type ;
const isArray = type === 'ARRAY' ;
if ( isArray && arrayRemap )
type = this . _getArrayType ( field . udt _name ) ;
2021-03-16 18:42:03 +01:00
return {
name : field . column _name ,
key : null ,
2021-04-09 19:31:41 +02:00
type : type . toUpperCase ( ) ,
isArray ,
2021-03-16 18:42:03 +01:00
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 : ''
} ;
} ) ;
}
2021-08-04 15:52:26 +02:00
/ * *
* @ param { Object } params
* @ param { String } params . schema
* @ param { String } params . table
* @ returns { Object } table row count
* @ memberof PostgreSQLClient
* /
async getTableApproximateCount ( { schema , table } ) {
const { rows } = await this . raw ( ` SELECT reltuples AS count FROM pg_class WHERE relname = ' ${ table } ' ` ) ;
return rows . length ? rows [ 0 ] . count : 0 ;
}
2021-08-11 16:16:58 +02:00
/ * *
* @ param { Object } params
* @ param { String } params . schema
* @ param { String } params . table
* @ returns { Object } table options
* @ memberof MySQLClient
* /
async getTableOptions ( { schema , table } ) {
const { rows } = await this . raw ( `
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" = '${schema}'
AND table _name = '${table}'
` );
if ( rows . length ) {
return {
name : rows [ 0 ] . table _name ,
type : rows [ 0 ] . table _type === 'VIEW' ? 'view' : 'table' ,
rows : rows [ 0 ] . reltuples ,
size : + rows [ 0 ] . data _length + + rows [ 0 ] . index _length ,
collation : rows [ 0 ] . Collation ,
comment : rows [ 0 ] . comment ,
engine : ''
} ;
} ;
return { } ;
}
2021-03-16 18:42:03 +01:00
/ * *
* @ param { Object } params
* @ param { String } params . schema
* @ param { String } params . table
* @ returns { Object } table indexes
* @ memberof PostgreSQLClient
* /
async getTableIndexes ( { schema , table } ) {
2021-04-06 12:48:40 +02:00
if ( schema !== 'public' )
2021-04-19 19:15:06 +02:00
await this . use ( schema ) ;
2021-04-06 12:48:40 +02:00
2021-03-16 18:42:03 +01:00
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
*
2021-11-18 11:36:46 +01:00
* @ returns { Promise < null > }
2021-03-16 18:42:03 +01:00
* @ 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
*
2021-11-18 11:36:46 +01:00
* @ returns { Promise < null > }
2021-03-16 18:42:03 +01:00
* @ 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
*
2021-11-18 11:36:46 +01:00
* @ returns { Promise < null > }
2021-03-16 18:42:03 +01:00
* @ 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 } ) {
2021-03-31 16:54:06 +02:00
const sql = ` SELECT "definition" FROM "pg_views" WHERE "viewname"=' ${ view } ' AND "schemaname"=' ${ schema } ' ` ;
2021-03-16 18:42:03 +01:00
const results = await this . raw ( sql ) ;
return results . rows . map ( row => {
return {
2021-03-31 16:54:06 +02:00
algorithm : '' ,
definer : '' ,
security : '' ,
updateOption : '' ,
sql : row . definition ,
name : view
2021-03-16 18:42:03 +01:00
} ;
} ) [ 0 ] ;
}
/ * *
* DROP VIEW
*
2021-11-18 11:36:46 +01:00
* @ returns { Promise < null > }
2021-03-16 18:42:03 +01:00
* @ memberof PostgreSQLClient
* /
async dropView ( params ) {
2021-10-06 12:08:37 +02:00
const sql = ` DROP VIEW " ${ params . schema } "." ${ params . view } " ` ;
2021-03-16 18:42:03 +01:00
return await this . raw ( sql ) ;
}
/ * *
* ALTER VIEW
*
2021-11-18 11:36:46 +01:00
* @ returns { Promise < null > }
2021-03-16 18:42:03 +01:00
* @ memberof PostgreSQLClient
* /
async alterView ( params ) {
const { view } = params ;
2021-10-06 12:08:37 +02:00
let sql = ` CREATE OR REPLACE VIEW " ${ view . schema } "." ${ view . oldName } " AS ${ view . sql } ` ;
2021-03-16 18:42:03 +01:00
if ( view . name !== view . oldName )
2021-10-06 12:08:37 +02:00
sql += ` ; ALTER VIEW " ${ view . schema } "." ${ view . oldName } " RENAME TO " ${ view . name } " ` ;
2021-03-16 18:42:03 +01:00
return await this . raw ( sql ) ;
}
/ * *
* CREATE VIEW
*
2021-11-18 11:36:46 +01:00
* @ returns { Promise < null > }
2021-03-16 18:42:03 +01:00
* @ memberof PostgreSQLClient
* /
2021-07-21 14:40:29 +02:00
async createView ( params ) {
2021-10-06 12:08:37 +02:00
const sql = ` CREATE VIEW " ${ params . schema } "." ${ params . name } " AS ${ params . sql } ` ;
2021-03-16 18:42:03 +01:00
return await this . raw ( sql ) ;
}
/ * *
* SHOW CREATE TRIGGER
*
* @ returns { Array . < Object > } view informations
* @ memberof PostgreSQLClient
* /
async getTriggerInformations ( { schema , trigger } ) {
2021-06-08 09:12:43 +02:00
const [ table , triggerName ] = trigger . split ( '.' ) ;
const results = await this . raw ( `
2021-11-04 21:54:42 +01:00
SELECT
information _schema . triggers . event _object _schema AS table _schema ,
information _schema . triggers . event _object _table AS table _name ,
information _schema . triggers . trigger _schema ,
information _schema . triggers . trigger _name ,
string _agg ( event _manipulation , ',' ) AS EVENT ,
2021-06-08 09:12:43 +02:00
action _timing AS activation ,
action _condition AS condition ,
2021-11-04 21:54:42 +01:00
action _statement AS definition ,
( pg _trigger . tgenabled != 'D' ) : : bool AS enabled
FROM pg _trigger
JOIN pg _class ON pg _trigger . tgrelid = pg _class . oid
JOIN pg _namespace ON pg _namespace . oid = pg _class . relnamespace
JOIN information _schema . triggers ON pg _namespace . nspname = information _schema . triggers . trigger _schema
AND pg _class . relname = information _schema . triggers . event _object _table
2021-06-08 09:12:43 +02:00
WHERE trigger _schema = '${schema}'
AND trigger _name = '${triggerName}'
AND event _object _table = '${table}'
2021-11-04 21:54:42 +01:00
GROUP BY 1 , 2 , 3 , 4 , 6 , 7 , 8 , 9
2021-06-08 09:12:43 +02:00
ORDER BY table _schema ,
table _name
` );
2021-03-16 18:42:03 +01:00
return results . rows . map ( row => {
return {
2021-06-08 09:12:43 +02:00
sql : row . definition ,
name : row . trigger _name ,
table : row . table _name ,
2021-11-04 21:54:42 +01:00
event : [ ... new Set ( row . event . split ( ',' ) ) ] ,
2021-06-08 09:12:43 +02:00
activation : row . activation
2021-03-16 18:42:03 +01:00
} ;
} ) [ 0 ] ;
}
/ * *
* DROP TRIGGER
*
2021-11-18 11:36:46 +01:00
* @ returns { Promise < null > }
2021-03-16 18:42:03 +01:00
* @ memberof PostgreSQLClient
* /
async dropTrigger ( params ) {
2021-04-22 15:15:08 +02:00
const triggerParts = params . trigger . split ( '.' ) ;
2021-07-21 14:40:29 +02:00
const sql = ` DROP TRIGGER " ${ triggerParts [ 1 ] } " ON " ${ params . schema } "." ${ triggerParts [ 0 ] } " ` ;
2021-03-16 18:42:03 +01:00
return await this . raw ( sql ) ;
}
/ * *
* ALTER TRIGGER
*
2021-11-18 11:36:46 +01:00
* @ returns { Promise < null > }
2021-03-16 18:42:03 +01:00
* @ memberof PostgreSQLClient
* /
async alterTrigger ( params ) {
const { trigger } = params ;
2021-06-17 22:01:18 +02:00
const tempTrigger = Object . assign ( { } , trigger ) ;
tempTrigger . name = ` Antares_ ${ tempTrigger . name } _tmp ` ;
try {
await this . createTrigger ( tempTrigger ) ;
2021-07-21 14:40:29 +02:00
await this . dropTrigger ( { schema : trigger . schema , trigger : ` ${ tempTrigger . table } . ${ tempTrigger . name } ` } ) ;
await this . dropTrigger ( { schema : trigger . schema , trigger : ` ${ trigger . table } . ${ trigger . oldName } ` } ) ;
2021-06-17 22:01:18 +02:00
await this . createTrigger ( trigger ) ;
}
catch ( err ) {
return Promise . reject ( err ) ;
}
2021-03-16 18:42:03 +01:00
}
/ * *
* CREATE TRIGGER
*
2021-11-18 11:36:46 +01:00
* @ returns { Promise < null > }
2021-03-16 18:42:03 +01:00
* @ memberof PostgreSQLClient
* /
2021-07-21 14:40:29 +02:00
async createTrigger ( params ) {
const eventsString = Array . isArray ( params . event ) ? params . event . join ( ' OR ' ) : params . event ;
const sql = ` CREATE TRIGGER " ${ params . name } " ${ params . activation } ${ eventsString } ON " ${ params . schema } "." ${ params . table } " FOR EACH ROW ${ params . sql } ` ;
2021-03-16 18:42:03 +01:00
return await this . raw ( sql , { split : false } ) ;
}
2021-11-04 21:54:42 +01:00
async enableTrigger ( { schema , trigger } ) {
const [ table , triggerName ] = trigger . split ( '.' ) ;
const sql = ` ALTER TABLE " ${ schema } "." ${ table } " ENABLE TRIGGER " ${ triggerName } " ` ;
return await this . raw ( sql , { split : false } ) ;
}
async disableTrigger ( { schema , trigger } ) {
const [ table , triggerName ] = trigger . split ( '.' ) ;
const sql = ` ALTER TABLE " ${ schema } "." ${ table } " DISABLE TRIGGER " ${ triggerName } " ` ;
return await this . raw ( sql , { split : false } ) ;
}
2021-03-16 18:42:03 +01:00
/ * *
* SHOW CREATE PROCEDURE
*
* @ returns { Array . < Object > } view informations
* @ memberof PostgreSQLClient
* /
async getRoutineInformations ( { schema , routine } ) {
2021-04-09 19:31:41 +02:00
const sql = ` SELECT pg_get_functiondef((SELECT oid FROM pg_proc WHERE proname = ' ${ routine } ')); ` ;
2021-03-16 18:42:03 +01:00
const results = await this . raw ( sql ) ;
2021-04-10 20:38:46 +02:00
return results . rows . map ( async row => {
2021-04-09 19:31:41 +02:00
if ( ! row . pg _get _functiondef ) {
2021-03-16 18:42:03 +01:00
return {
definer : null ,
sql : '' ,
parameters : [ ] ,
2021-04-09 19:31:41 +02:00
name : routine ,
2021-03-16 18:42:03 +01:00
comment : '' ,
security : 'DEFINER' ,
deterministic : false ,
dataAccess : 'CONTAINS SQL'
} ;
}
2021-04-10 20:38:46 +02:00
const sql = ` SELECT proc.specific_schema AS procedure_schema,
2021-04-14 18:06:20 +02:00
proc . specific _name ,
proc . routine _name AS procedure _name ,
proc . external _language ,
args . parameter _name ,
args . parameter _mode ,
args . data _type
FROM information _schema . routines proc
LEFT JOIN information _schema . parameters args
ON proc . specific _schema = args . specific _schema
AND proc . specific _name = args . specific _name
WHERE proc . routine _schema not in ( 'pg_catalog' , 'information_schema' )
AND proc . routine _type = 'PROCEDURE'
AND proc . routine _name = '${routine}'
AND proc . specific _schema = '${schema}'
2021-07-21 14:40:29 +02:00
AND args . data _type != NULL
2021-04-14 18:06:20 +02:00
ORDER BY procedure _schema ,
specific _name ,
procedure _name ,
args . ordinal _position
` ;
2021-04-10 20:38:46 +02:00
const results = await this . raw ( sql ) ;
const parameters = results . rows . map ( row => {
return {
name : row . parameter _name ,
2021-07-03 11:39:57 +02:00
type : row . data _type ? row . data _type . toUpperCase ( ) : '' ,
2021-04-10 20:38:46 +02:00
length : '' ,
context : row . parameter _mode
} ;
} ) ;
2021-03-16 18:42:03 +01:00
return {
2021-04-09 19:31:41 +02:00
definer : '' ,
sql : row . pg _get _functiondef . match ( / ( \ $ ( . * ) \ $ ) ( . * ) ( \ $ ( . * ) \ $ ) / g s ) [ 0 ] ,
2021-03-16 18:42:03 +01:00
parameters : parameters || [ ] ,
2021-04-09 19:31:41 +02:00
name : routine ,
comment : '' ,
2021-04-10 20:38:46 +02:00
security : row . pg _get _functiondef . includes ( 'SECURITY DEFINER' ) ? 'DEFINER' : 'INVOKER' ,
2021-04-09 19:31:41 +02:00
deterministic : null ,
2021-04-10 20:38:46 +02:00
dataAccess : null ,
2021-04-09 19:31:41 +02:00
language : row . pg _get _functiondef . match ( /(?<=LANGUAGE )(.*)(?<=[\S+\n\r\s])/gm ) [ 0 ]
2021-03-16 18:42:03 +01:00
} ;
} ) [ 0 ] ;
}
/ * *
* DROP PROCEDURE
*
* @ returns { Array . < Object > } parameters
* @ memberof PostgreSQLClient
* /
async dropRoutine ( params ) {
2021-07-21 14:40:29 +02:00
const sql = ` DROP PROCEDURE " ${ params . schema } "." ${ params . routine } " ` ;
2021-03-16 18:42:03 +01:00
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 ) ;
2021-07-21 14:40:29 +02:00
await this . dropRoutine ( { schema : routine . schema , routine : tempProcedure . name } ) ;
await this . dropRoutine ( { schema : routine . schema , routine : routine . oldName } ) ;
2021-03-16 18:42:03 +01:00
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 ) => {
2021-09-07 18:20:45 +02:00
acc . push ( ` ${ curr . context } ${ curr . name } ${ curr . type } ` ) ;
2021-03-16 18:42:03 +01:00
return acc ;
} , [ ] ) . join ( ',' )
: '' ;
2021-07-21 14:40:29 +02:00
if ( routine . schema !== 'public' )
await this . use ( routine . schema ) ;
2021-04-10 20:38:46 +02:00
2021-07-21 14:40:29 +02:00
const sql = ` CREATE PROCEDURE " ${ routine . schema } "." ${ routine . name } "( ${ parameters } )
2021-04-12 18:46:35 +02:00
LANGUAGE $ { routine . language }
2021-04-09 19:31:41 +02:00
SECURITY $ { routine . security }
AS $ { routine . sql } ` ;
2021-03-16 18:42:03 +01:00
return await this . raw ( sql , { split : false } ) ;
}
/ * *
* SHOW CREATE FUNCTION
*
* @ returns { Array . < Object > } view informations
* @ memberof PostgreSQLClient
* /
async getFunctionInformations ( { schema , func } ) {
2021-04-12 18:46:35 +02:00
const sql = ` SELECT pg_get_functiondef((SELECT oid FROM pg_proc WHERE proname = ' ${ func } ')); ` ;
2021-03-16 18:42:03 +01:00
const results = await this . raw ( sql ) ;
2021-04-12 18:46:35 +02:00
return results . rows . map ( async row => {
if ( ! row . pg _get _functiondef ) {
2021-03-16 18:42:03 +01:00
return {
definer : null ,
sql : '' ,
parameters : [ ] ,
2021-04-12 18:46:35 +02:00
name : func ,
2021-03-16 18:42:03 +01:00
comment : '' ,
security : 'DEFINER' ,
deterministic : false ,
2021-04-12 18:46:35 +02:00
dataAccess : 'CONTAINS SQL'
2021-03-16 18:42:03 +01:00
} ;
}
2021-04-12 18:46:35 +02:00
const sql = ` SELECT proc.specific_schema AS procedure_schema,
2021-04-14 18:06:20 +02:00
proc . specific _name ,
proc . routine _name AS procedure _name ,
proc . external _language ,
args . parameter _name ,
args . parameter _mode ,
args . data _type
FROM information _schema . routines proc
LEFT JOIN information _schema . parameters args
ON proc . specific _schema = args . specific _schema
AND proc . specific _name = args . specific _name
WHERE proc . routine _schema not in ( 'pg_catalog' , 'information_schema' )
AND proc . routine _type = 'FUNCTION'
AND proc . routine _name = '${func}'
AND proc . specific _schema = '${schema}'
ORDER BY procedure _schema ,
specific _name ,
procedure _name ,
args . ordinal _position
` ;
2021-03-16 18:42:03 +01:00
2021-04-12 18:46:35 +02:00
const results = await this . raw ( sql ) ;
2021-03-16 18:42:03 +01:00
2021-04-13 18:05:03 +02:00
const parameters = results . rows . filter ( row => row . parameter _mode ) . map ( row => {
2021-04-12 18:46:35 +02:00
return {
name : row . parameter _name ,
type : row . data _type . toUpperCase ( ) ,
length : '' ,
context : row . parameter _mode
} ;
} ) ;
2021-03-16 18:42:03 +01:00
return {
2021-04-12 18:46:35 +02:00
definer : '' ,
sql : row . pg _get _functiondef . match ( / ( \ $ ( . * ) \ $ ) ( . * ) ( \ $ ( . * ) \ $ ) / g s ) [ 0 ] ,
2021-03-16 18:42:03 +01:00
parameters : parameters || [ ] ,
2021-04-12 18:46:35 +02:00
name : func ,
comment : '' ,
security : row . pg _get _functiondef . includes ( 'SECURITY DEFINER' ) ? 'DEFINER' : 'INVOKER' ,
deterministic : null ,
dataAccess : null ,
language : row . pg _get _functiondef . match ( /(?<=LANGUAGE )(.*)(?<=[\S+\n\r\s])/gm ) [ 0 ] ,
2021-04-13 18:05:03 +02:00
returns : row . pg _get _functiondef . match ( /(?<=RETURNS )(.*)(?<=[\S+\n\r\s])/gm ) [ 0 ] . replace ( 'SETOF ' , '' ) . toUpperCase ( )
2021-03-16 18:42:03 +01:00
} ;
} ) [ 0 ] ;
}
/ * *
* DROP FUNCTION
*
* @ returns { Array . < Object > } parameters
* @ memberof PostgreSQLClient
* /
async dropFunction ( params ) {
2021-07-21 14:40:29 +02:00
const sql = ` DROP FUNCTION " ${ params . schema } "." ${ params . func } " ` ;
2021-03-16 18:42:03 +01:00
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 ) ;
2021-07-21 14:40:29 +02:00
await this . dropFunction ( { schema : func . schema , func : tempProcedure . name } ) ;
await this . dropFunction ( { schema : func . schema , func : func . oldName } ) ;
2021-03-16 18:42:03 +01:00
await this . createFunction ( func ) ;
}
catch ( err ) {
return Promise . reject ( err ) ;
}
}
/ * *
* CREATE FUNCTION
*
* @ returns { Array . < Object > } parameters
* @ memberof PostgreSQLClient
* /
async createFunction ( func ) {
2021-04-13 18:05:03 +02:00
const parameters = 'parameters' in func
? func . parameters . reduce ( ( acc , curr ) => {
2021-09-07 18:20:45 +02:00
acc . push ( ` ${ curr . context } ${ curr . name || '' } ${ curr . type } ` ) ;
2021-04-13 18:05:03 +02:00
return acc ;
} , [ ] ) . join ( ',' )
: '' ;
2021-07-21 14:40:29 +02:00
if ( func . schema !== 'public' )
await this . use ( func . schema ) ;
2021-04-13 18:05:03 +02:00
2021-07-03 11:29:14 +02:00
const body = func . returns ? func . sql : '$function$\n$function$' ;
2021-03-16 18:42:03 +01:00
2021-07-21 14:40:29 +02:00
const sql = ` CREATE FUNCTION " ${ func . schema } "." ${ func . name } " ( ${ parameters } )
2021-04-13 18:05:03 +02:00
RETURNS $ { func . returns || 'void' }
2021-04-12 18:46:35 +02:00
LANGUAGE $ { func . language }
2021-04-13 18:05:03 +02:00
SECURITY $ { func . security }
AS $ { body } ` ;
2021-03-16 18:42:03 +01:00
return await this . raw ( sql , { split : false } ) ;
}
2021-07-03 11:29:14 +02:00
/ * *
* ALTER TRIGGER FUNCTION
*
* @ returns { Array . < Object > } parameters
* @ memberof PostgreSQLClient
* /
async alterTriggerFunction ( params ) {
const { func } = params ;
2021-07-21 14:40:29 +02:00
if ( func . schema !== 'public' )
await this . use ( func . schema ) ;
2021-07-03 11:29:14 +02:00
const body = func . returns ? func . sql : '$function$\n$function$' ;
2021-07-21 14:40:29 +02:00
const sql = ` CREATE OR REPLACE FUNCTION " ${ func . schema } "." ${ func . name } " ()
2021-07-03 11:29:14 +02:00
RETURNS TRIGGER
LANGUAGE $ { func . language }
AS $ { body } ` ;
return await this . raw ( sql , { split : false } ) ;
}
/ * *
* CREATE TRIGGER FUNCTION
*
* @ returns { Array . < Object > } parameters
* @ memberof PostgreSQLClient
* /
async createTriggerFunction ( func ) {
2021-07-21 14:40:29 +02:00
if ( func . schema !== 'public' )
await this . use ( func . schema ) ;
2021-07-03 11:29:14 +02:00
const body = func . returns ? func . sql : '$function$\r\nBEGIN\r\n\r\nEND\r\n$function$' ;
2021-07-21 14:40:29 +02:00
const sql = ` CREATE FUNCTION " ${ func . schema } "." ${ func . name } " ()
2021-07-03 11:29:14 +02:00
RETURNS TRIGGER
LANGUAGE $ { func . language }
AS $ { body } ` ;
return await this . raw ( sql , { split : false } ) ;
}
2021-03-16 18:42:03 +01:00
/ * *
* 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
} ;
} ) ;
}
2021-09-26 11:19:48 +02:00
async killProcess ( id ) {
return await this . raw ( ` SELECT pg_terminate_backend( ${ id } ) ` ) ;
}
2021-03-16 18:42:03 +01:00
/ * *
* CREATE TABLE
*
2021-11-18 11:36:46 +01:00
* @ returns { Promise < null > }
2021-03-16 18:42:03 +01:00
* @ memberof PostgreSQLClient
* /
async createTable ( params ) {
2021-08-13 16:50:59 +02:00
const {
schema ,
fields ,
foreigns ,
indexes ,
options
} = params ;
const newColumns = [ ] ;
const newIndexes = [ ] ;
const manageIndexes = [ ] ;
const newForeigns = [ ] ;
let sql = ` CREATE TABLE " ${ schema } "." ${ options . name } " ` ;
// ADD FIELDS
fields . forEach ( field => {
const typeInfo = this . _getTypeInfo ( field . type ) ;
const length = typeInfo . length ? field . enumValues || field . numLength || field . charLength || field . datePrecision : false ;
2021-10-19 17:42:31 +02:00
newColumns . push ( ` " ${ field . name } "
2021-08-13 16:50:59 +02:00
$ { field . type . toUpperCase ( ) } $ { length ? ` ( ${ length } ) ` : '' }
$ { field . unsigned ? 'UNSIGNED' : '' }
$ { field . zerofill ? 'ZEROFILL' : '' }
$ { field . nullable ? 'NULL' : 'NOT NULL' }
$ { field . default ? ` DEFAULT ${ field . default } ` : '' }
$ { field . onUpdate ? ` ON UPDATE ${ field . onUpdate } ` : '' } ` );
} ) ;
// ADD INDEX
indexes . forEach ( index => {
const fields = index . fields . map ( field => ` ${ field } ` ) . join ( ',' ) ;
const type = index . type ;
if ( type === 'PRIMARY' )
newIndexes . push ( ` PRIMARY KEY ( ${ fields } ) ` ) ;
else if ( type === 'UNIQUE' )
2021-10-06 12:08:37 +02:00
newIndexes . push ( ` CONSTRAINT " ${ index . name } " UNIQUE ( ${ fields } ) ` ) ;
2021-08-13 16:50:59 +02:00
else
2021-10-06 12:08:37 +02:00
manageIndexes . push ( ` CREATE INDEX " ${ index . name } " ON " ${ schema } "." ${ options . name } " ( ${ fields } ) ` ) ;
2021-08-13 16:50:59 +02:00
} ) ;
2021-03-16 18:42:03 +01:00
2021-08-13 16:50:59 +02:00
// ADD FOREIGN KEYS
foreigns . forEach ( foreign => {
2021-10-06 12:08:37 +02:00
newForeigns . push ( ` CONSTRAINT " ${ foreign . constraintName } " FOREIGN KEY (" ${ foreign . field } ") REFERENCES " ${ schema } "." ${ foreign . refTable } " (" ${ foreign . refField } ") ON UPDATE ${ foreign . onUpdate } ON DELETE ${ foreign . onDelete } ` ) ;
2021-08-13 16:50:59 +02:00
} ) ;
sql = ` ${ sql } ( ${ [ ... newColumns , ... newIndexes , ... newForeigns ] . join ( ', ' ) } ) ` ;
if ( manageIndexes . length ) sql = ` ${ sql } ; ${ manageIndexes . join ( ';' ) } ` ;
2021-03-16 18:42:03 +01:00
return await this . raw ( sql ) ;
}
/ * *
* ALTER TABLE
*
2021-11-18 11:36:46 +01:00
* @ returns { Promise < null > }
2021-03-16 18:42:03 +01:00
* @ memberof PostgreSQLClient
* /
async alterTable ( params ) {
const {
table ,
2021-07-21 14:40:29 +02:00
schema ,
2021-03-16 18:42:03 +01:00
additions ,
deletions ,
changes ,
indexChanges ,
foreignChanges ,
options
} = params ;
2021-07-21 14:40:29 +02:00
if ( schema !== 'public' )
await this . use ( schema ) ;
2021-04-16 17:42:16 +02:00
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-30 19:07:04 +02:00
const manageIndexes = [ ] ;
2021-03-16 18:42:03 +01:00
// ADD FIELDS
additions . forEach ( addition => {
const typeInfo = this . _getTypeInfo ( addition . type ) ;
const length = typeInfo . length ? addition . numLength || addition . charLength || addition . datePrecision : false ;
2021-10-06 12:08:37 +02:00
alterColumns . push ( ` ADD COLUMN " ${ addition . name } "
2021-04-09 19:31:41 +02:00
$ { addition . type . toUpperCase ( ) } $ { length ? ` ( ${ length } ) ` : '' } $ { addition . isArray ? '[]' : '' }
2021-03-16 18:42:03 +01:00
$ { addition . unsigned ? 'UNSIGNED' : '' }
$ { addition . zerofill ? 'ZEROFILL' : '' }
$ { addition . nullable ? 'NULL' : 'NOT NULL' }
2021-05-06 22:21:42 +02:00
$ { addition . default ? ` DEFAULT ${ addition . default } ` : '' }
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-10-06 12:08:37 +02:00
const fields = addition . fields . map ( field => ` " ${ field } " ` ) . join ( ',' ) ;
2021-03-29 20:18:44 +02:00
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' )
2021-10-06 12:08:37 +02:00
alterColumns . push ( ` ADD CONSTRAINT " ${ addition . name } " UNIQUE ( ${ fields } ) ` ) ;
2021-03-30 19:07:04 +02:00
else
2021-10-06 12:08:37 +02:00
manageIndexes . push ( ` CREATE INDEX " ${ addition . name } " ON " ${ schema } "." ${ table } " ( ${ fields } ) ` ) ;
2021-03-16 18:42:03 +01:00
} ) ;
// ADD FOREIGN KEYS
foreignChanges . additions . forEach ( addition => {
2021-10-06 12:08:37 +02:00
alterColumns . push ( ` ADD CONSTRAINT " ${ addition . constraintName } " FOREIGN KEY (" ${ addition . field } ") REFERENCES " ${ schema } "." ${ addition . refTable } " ( ${ addition . refField } ) ON UPDATE ${ addition . onUpdate } ON DELETE ${ addition . onDelete } ` ) ;
2021-03-16 18:42:03 +01:00
} ) ;
// 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 ( ) ;
}
2021-04-16 17:42:16 +02:00
alterColumns . push ( ` ALTER COLUMN " ${ change . name } " TYPE ${ localType } ${ length ? ` ( ${ length } ) ` : '' } ${ change . isArray ? '[]' : '' } USING " ${ change . name } ":: ${ localType } ` ) ;
alterColumns . push ( ` ALTER COLUMN " ${ change . name } " ${ change . nullable ? 'DROP NOT NULL' : 'SET NOT NULL' } ` ) ;
2021-05-06 22:21:42 +02:00
alterColumns . push ( ` ALTER COLUMN " ${ change . name } " ${ change . default ? ` SET DEFAULT ${ change . default } ` : 'DROP DEFAULT' } ` ) ;
2021-10-06 12:08:37 +02:00
2021-03-25 18:33:29 +01:00
if ( [ 'SERIAL' , 'SMALLSERIAL' , 'BIGSERIAL' ] . includes ( change . type ) ) {
const sequenceName = ` ${ table } _ ${ change . name } _seq ` . replace ( ' ' , '_' ) ;
2021-04-16 17:42:16 +02:00
createSequences . push ( ` CREATE SEQUENCE IF NOT EXISTS ${ sequenceName } OWNED BY " ${ table } "." ${ change . name } " ` ) ;
alterColumns . push ( ` ALTER COLUMN " ${ change . name } " SET DEFAULT nextval(' ${ sequenceName } ') ` ) ;
2021-03-25 18:33:29 +01:00
}
if ( change . orgName !== change . name )
2021-07-21 14:40:29 +02:00
renameColumns . push ( ` ALTER TABLE " ${ schema } "." ${ table } " RENAME COLUMN " ${ change . orgName } " TO " ${ change . name } " ` ) ;
2021-03-16 18:42:03 +01:00
} ) ;
// CHANGE INDEX
indexChanges . changes . forEach ( change => {
2021-04-16 17:42:16 +02:00
if ( [ 'PRIMARY' , 'UNIQUE' ] . includes ( change . oldType ) )
2021-03-29 20:18:44 +02:00
alterColumns . push ( ` DROP CONSTRAINT ${ change . oldName } ` ) ;
2021-03-30 19:07:04 +02:00
else
manageIndexes . push ( ` DROP INDEX ${ change . oldName } ` ) ;
2021-03-16 18:42:03 +01:00
2021-10-06 12:08:37 +02:00
const fields = change . fields . map ( field => ` " ${ field } " ` ) . join ( ',' ) ;
2021-03-29 20:18:44 +02:00
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' )
2021-10-06 12:08:37 +02:00
alterColumns . push ( ` ADD CONSTRAINT " ${ change . name } " UNIQUE ( ${ fields } ) ` ) ;
2021-03-30 19:07:04 +02:00
else
2021-10-06 12:08:37 +02:00
manageIndexes . push ( ` CREATE INDEX " ${ change . name } " ON " ${ schema } "." ${ table } " ( ${ fields } ) ` ) ;
2021-03-16 18:42:03 +01:00
} ) ;
// CHANGE FOREIGN KEYS
foreignChanges . changes . forEach ( change => {
2021-10-06 12:08:37 +02:00
alterColumns . push ( ` DROP CONSTRAINT " ${ change . oldName } " ` ) ;
alterColumns . push ( ` ADD CONSTRAINT " ${ change . constraintName } " FOREIGN KEY ( ${ change . field } ) REFERENCES " ${ schema } "." ${ change . refTable } " (" ${ change . refField } ") ON UPDATE ${ change . onUpdate } ON DELETE ${ change . onDelete } ` ) ;
2021-03-16 18:42:03 +01:00
} ) ;
// DROP FIELDS
deletions . forEach ( deletion => {
2021-10-06 12:08:37 +02:00
alterColumns . push ( ` DROP COLUMN " ${ deletion . name } " ` ) ;
2021-03-16 18:42:03 +01:00
} ) ;
// DROP INDEX
indexChanges . deletions . forEach ( deletion => {
2021-03-30 19:07:04 +02:00
if ( [ 'PRIMARY' , 'UNIQUE' ] . includes ( deletion . type ) )
2021-10-06 12:08:37 +02:00
alterColumns . push ( ` DROP CONSTRAINT " ${ deletion . name } " ` ) ;
2021-03-30 19:07:04 +02:00
else
2021-10-06 12:08:37 +02:00
manageIndexes . push ( ` DROP INDEX " ${ deletion . name } " ` ) ;
2021-03-16 18:42:03 +01:00
} ) ;
// DROP FOREIGN KEYS
foreignChanges . deletions . forEach ( deletion => {
2021-10-06 12:08:37 +02:00
alterColumns . push ( ` DROP CONSTRAINT " ${ deletion . constraintName } " ` ) ;
2021-03-16 18:42:03 +01:00
} ) ;
2021-07-21 14:40:29 +02:00
if ( alterColumns . length ) sql += ` ALTER TABLE " ${ schema } "." ${ table } " ${ alterColumns . join ( ', ' ) } ; ` ;
2021-03-25 18:33:29 +01:00
if ( createSequences . length ) sql = ` ${ createSequences . join ( ';' ) } ; ${ sql } ` ;
2021-03-30 19:07:04 +02:00
if ( manageIndexes . length ) sql = ` ${ manageIndexes . join ( ';' ) } ; ${ sql } ` ;
2021-07-21 14:40:29 +02:00
if ( options . name ) sql += ` ALTER TABLE " ${ schema } "." ${ table } " RENAME TO " ${ options . name } "; ` ;
2021-03-16 18:42:03 +01:00
2021-04-16 17:42:16 +02:00
// RENAME
if ( renameColumns . length ) sql = ` ${ renameColumns . join ( ';' ) } ; ${ sql } ` ;
2021-03-16 18:42:03 +01:00
return await this . raw ( sql ) ;
}
2021-07-03 12:27:50 +02:00
/ * *
* DUPLICATE TABLE
*
2021-11-18 11:36:46 +01:00
* @ returns { Promise < null > }
2021-07-03 12:27:50 +02:00
* @ memberof PostgreSQLClient
* /
async duplicateTable ( params ) {
2021-10-06 12:08:37 +02:00
const sql = ` CREATE TABLE " ${ params . schema } "." ${ params . table } _copy" (LIKE " ${ params . schema } "." ${ params . table } " INCLUDING ALL) ` ;
2021-07-03 12:27:50 +02:00
return await this . raw ( sql ) ;
}
2021-03-16 18:42:03 +01:00
/ * *
* TRUNCATE TABLE
*
2021-11-18 11:36:46 +01:00
* @ returns { Promise < null > }
2021-03-16 18:42:03 +01:00
* @ memberof PostgreSQLClient
* /
async truncateTable ( params ) {
2021-10-06 12:08:37 +02:00
const sql = ` TRUNCATE TABLE " ${ params . schema } "." ${ params . table } " ` ;
2021-03-16 18:42:03 +01:00
return await this . raw ( sql ) ;
}
/ * *
* DROP TABLE
*
2021-11-18 11:36:46 +01:00
* @ returns { Promise < null > }
2021-03-16 18:42:03 +01:00
* @ memberof PostgreSQLClient
* /
async dropTable ( params ) {
2021-10-06 12:08:37 +02:00
const sql = ` DROP TABLE " ${ params . schema } "." ${ params . table } " ` ;
2021-03-16 18:42:03 +01:00
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' ;
2021-10-06 12:08:37 +02:00
fromRaw += this . _query . from ? ` ${ this . _query . schema ? ` " ${ this . _query . schema } ". ` : '' } " ${ this . _query . from } " ` : '' ;
2021-03-16 18:42:03 +01:00
// 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
2021-05-27 22:13:59 +02:00
// OFFSET
2021-05-31 14:27:02 +02:00
const offsetRaw = selectArray . length && this . _query . offset . length ? ` OFFSET ${ this . _query . offset . join ( ', ' ) } ` : '' ;
2021-05-27 22:13:59 +02:00
return ` ${ selectRaw } ${ updateRaw ? 'UPDATE' : '' } ${ insertRaw ? 'INSERT ' : '' } ${ this . _query . delete ? 'DELETE ' : '' } ${ fromRaw } ${ updateRaw } ${ whereRaw } ${ groupByRaw } ${ orderByRaw } ${ limitRaw } ${ offsetRaw } ${ insertRaw } ` ;
2021-03-16 18:42:03 +01:00
}
/ * *
* @ 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 ,
2021-07-21 14:40:29 +02:00
comments : true ,
2021-03-16 18:42:03 +01:00
... args
} ;
2021-04-06 12:48:40 +02:00
2021-05-14 17:02:27 +02:00
if ( args . schema && args . schema !== 'public' )
await this . use ( args . schema ) ;
2021-04-06 12:48:40 +02:00
2021-07-21 14:40:29 +02:00
if ( ! args . comments )
sql = sql . replace ( /(\/\*(.|[\r\n])*?\*\/)|(--(.*|[\r\n]))/gm , '' ) ; // Remove comments
2021-03-16 18:42:03 +01:00
const resultsArr = [ ] ;
let paramsArr = [ ] ;
2021-06-11 14:32:51 +02:00
const queries = args . split
? sql . split ( /(?!\B'[^']*);(?![^']*'\B)/gm )
. filter ( Boolean )
. map ( q => q . trim ( ) )
: [ sql ] ;
2021-03-16 18:42:03 +01:00
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
2021-04-09 19:31:41 +02:00
const columns = await this . getTableColumns ( paramObj , false ) ;
2021-03-16 18:42:03 +01:00
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 ;
}
}