2023-08-18 15:57:31 +02:00
import dataTypes from 'common/data-types/postgresql' ;
2022-04-14 09:15:16 +02:00
import * as antares from 'common/interfaces/antares' ;
2022-04-14 18:25:13 +02:00
import * as pg from 'pg' ;
2022-04-14 09:15:16 +02:00
import * as pgAst from 'pgsql-ast-parser' ;
2023-08-18 15:57:31 +02:00
2021-03-16 18:42:03 +01:00
import { AntaresCore } from '../AntaresCore' ;
2022-05-10 13:22:26 +02:00
import SSH2Promise = require ( 'ssh2-promise' ) ;
2022-04-14 09:15:16 +02:00
import SSHConfig from 'ssh2-promise/lib/sshConfig' ;
2023-08-11 16:42:34 +02:00
import { ConnectionOptions } from 'tls' ;
2021-03-16 18:42:03 +01:00
2022-04-14 09:15:16 +02:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function pgToString ( value : any ) {
2021-03-18 15:56:52 +01:00
return value . toString ( ) ;
}
2022-04-14 09:15:16 +02:00
pg . types . setTypeParser ( 1082 , pgToString ) ; // date
pg . types . setTypeParser ( 1083 , pgToString ) ; // time
pg . types . setTypeParser ( 1114 , pgToString ) ; // timestamp
pg . types . setTypeParser ( 1184 , pgToString ) ; // timestamptz
pg . types . setTypeParser ( 1266 , pgToString ) ; // timetz
2021-03-18 15:56:52 +01:00
2022-08-01 18:13:14 +02:00
// from pg-types
type builtinsTypes =
'BOOL' |
'BYTEA' |
'CHAR' |
'INT8' |
'INT2' |
'INT4' |
'REGPROC' |
'TEXT' |
'OID' |
'TID' |
'XID' |
'CID' |
'JSON' |
'XML' |
'PG_NODE_TREE' |
'SMGR' |
'PATH' |
'POLYGON' |
'CIDR' |
'FLOAT4' |
'FLOAT8' |
'ABSTIME' |
'RELTIME' |
'TINTERVAL' |
'CIRCLE' |
'MACADDR8' |
'MONEY' |
'MACADDR' |
'INET' |
'ACLITEM' |
'BPCHAR' |
'VARCHAR' |
'DATE' |
'TIME' |
'TIMESTAMP' |
'TIMESTAMPTZ' |
'INTERVAL' |
'TIMETZ' |
'BIT' |
'VARBIT' |
'NUMERIC' |
'REFCURSOR' |
'REGPROCEDURE' |
'REGOPER' |
'REGOPERATOR' |
'REGCLASS' |
'REGTYPE' |
'UUID' |
'TXID_SNAPSHOT' |
'PG_LSN' |
'PG_NDISTINCT' |
'PG_DEPENDENCIES' |
'TSVECTOR' |
'TSQUERY' |
'GTSVECTOR' |
'REGCONFIG' |
'REGDICTIONARY' |
'JSONB' |
'REGNAMESPACE' |
'REGROLE' ;
2021-03-16 18:42:03 +01:00
export class PostgreSQLClient extends AntaresCore {
2022-04-14 09:15:16 +02:00
private _schema? : string ;
private _runningConnections : Map < string , number > ;
private _connectionsToCommit : Map < string , pg.Client | pg.PoolClient > ;
2023-05-14 18:48:21 +02:00
private _keepaliveTimer : NodeJS.Timer ;
private _keepaliveMs : number ;
2022-04-14 09:15:16 +02:00
protected _connection? : pg.Client | pg . Pool ;
private types : { [ key : string ] : string } = { } ;
private _arrayTypes : { [ key : string ] : string } = {
_int2 : 'SMALLINT' ,
_int4 : 'INTEGER' ,
_int8 : 'BIGINT' ,
_float4 : 'REAL' ,
_float8 : 'DOUBLE PRECISION' ,
_char : '"CHAR"' ,
_varchar : 'CHARACTER VARYING'
}
2023-08-11 16:42:34 +02:00
_params : pg.ClientConfig & { schema : string ; ssl? : ConnectionOptions ; ssh? : SSHConfig ; readonly : boolean } ;
2022-04-15 23:13:23 +02:00
2022-04-14 09:15:16 +02:00
constructor ( args : antares.ClientParams ) {
2021-03-16 18:42:03 +01:00
super ( args ) ;
this . _schema = null ;
2021-12-26 21:13:02 +01:00
this . _runningConnections = new Map ( ) ;
2022-02-14 18:00:26 +01:00
this . _connectionsToCommit = new Map ( ) ;
2023-05-14 18:48:21 +02:00
this . _keepaliveMs = 10 * 60 * 1000 ;
2021-03-16 18:42:03 +01:00
2022-04-14 09:15:16 +02:00
for ( const key in pg . types . builtins ) {
const builtinKey = key as builtinsTypes ;
this . types [ pg . types . builtins [ builtinKey ] ] = key ;
}
2021-03-16 18:42:03 +01:00
}
2022-04-14 09:15:16 +02:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any
_reducer ( acc : string [ ] , curr : any ) {
2021-10-19 17:42:31 +02:00
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 ;
}
}
}
2022-05-07 09:46:07 +02:00
getTypeInfo ( type : string ) : antares . TypeInformations {
2021-03-16 18:42:03 +01:00
return dataTypes
. reduce ( ( acc , group ) = > [ . . . acc , . . . group . types ] , [ ] )
. filter ( _type = > _type . name === type . toUpperCase ( ) ) [ 0 ] ;
}
2022-04-14 09:15:16 +02:00
_getArrayType ( type : string ) {
2021-04-09 19:31:41 +02:00
if ( Object . keys ( this . _arrayTypes ) . includes ( type ) )
return this . _arrayTypes [ type ] ;
return type . replace ( '_' , '' ) ;
}
2022-02-14 18:00:26 +01:00
async getDbConfig ( ) {
2022-04-12 17:08:05 +02:00
this . _params . application_name = 'Antares SQL' ;
2021-07-05 09:30:52 +02:00
const dbConfig = {
host : this._params.host ,
port : this._params.port ,
user : this._params.user ,
2023-06-13 18:10:52 +02:00
database : 'postgres' as string ,
2021-07-05 09:30:52 +02:00
password : this._params.password ,
2023-08-11 16:42:34 +02:00
ssl : null as ConnectionOptions
2021-07-05 09:30:52 +02:00
} ;
if ( this . _params . database ? . length ) dbConfig . database = this . _params . database ;
2022-04-14 09:15:16 +02:00
if ( this . _params . ssl ) dbConfig . ssl = this . _params . ssl ;
2021-07-05 09:30:52 +02:00
if ( this . _params . ssh ) {
2021-10-07 14:58:31 +02:00
try {
2022-12-29 13:04:20 +01:00
this . _ssh = new SSH2Promise ( {
. . . this . _params . ssh ,
2023-01-05 17:21:22 +01:00
keepaliveInterval : 30 * 60 * 1000 ,
2022-12-29 13:04:20 +01:00
debug : process.env.NODE_ENV !== 'production' ? ( s ) = > console . log ( s ) : null
} ) ;
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
} ) ;
2022-04-01 09:51:03 +02:00
2022-04-14 09:15:16 +02:00
dbConfig . host = ( this . _ssh . config as SSHConfig [ ] & { host : string } ) . host ;
2021-10-07 14:58:31 +02:00
dbConfig . port = tunnel . localPort ;
}
catch ( err ) {
if ( this . _ssh ) this . _ssh . close ( ) ;
throw err ;
}
2021-07-05 09:30:52 +02:00
}
2022-02-14 18:00:26 +01:00
return dbConfig ;
}
2021-11-24 14:24:52 +01:00
2022-02-14 18:00:26 +01:00
/ * *
* @memberof PostgreSQLClient
* /
async connect ( ) {
if ( ! this . _poolSize )
this . _connection = await this . getConnection ( ) ;
else
this . _connection = await this . getConnectionPool ( ) ;
}
2021-11-24 14:24:52 +01:00
2022-02-14 18:00:26 +01:00
async getConnection ( ) {
const dbConfig = await this . getDbConfig ( ) ;
2022-04-14 09:15:16 +02:00
const client = new pg . Client ( dbConfig ) ;
2022-02-14 18:00:26 +01:00
await client . connect ( ) ;
const connection = client ;
if ( this . _params . readonly )
await connection . query ( 'SET SESSION CHARACTERISTICS AS TRANSACTION READ ONLY' ) ;
return connection ;
}
async getConnectionPool ( ) {
const dbConfig = await this . getDbConfig ( ) ;
2022-05-15 17:37:54 +02:00
const pool = new pg . Pool ( {
. . . dbConfig ,
max : this._poolSize ,
idleTimeoutMillis : 0
} ) ;
2022-02-14 18:00:26 +01:00
const connection = pool ;
if ( this . _params . readonly ) {
connection . on ( 'connect' , conn = > {
conn . query ( 'SET SESSION CHARACTERISTICS AS TRANSACTION READ ONLY' ) ;
} ) ;
2021-03-16 18:42:03 +01:00
}
2022-02-14 18:00:26 +01:00
2023-05-14 18:48:21 +02:00
this . _keepaliveTimer = setInterval ( async ( ) = > {
await this . keepAlive ( ) ;
} , this . _keepaliveMs ) ;
2022-02-14 18:00:26 +01:00
return connection ;
2021-03-16 18:42:03 +01:00
}
destroy ( ) {
this . _connection . end ( ) ;
2023-05-14 18:48:21 +02:00
clearInterval ( this . _keepaliveTimer ) ;
this . _keepaliveTimer = undefined ;
2021-07-05 09:30:52 +02:00
if ( this . _ssh ) this . _ssh . close ( ) ;
2021-03-16 18:42:03 +01:00
}
2023-05-14 18:48:21 +02:00
private async keepAlive ( ) {
const connection = await this . _connection . connect ( ) as pg . PoolClient ;
await connection . query ( 'SELECT 1+1' ) ;
connection . release ( ) ;
}
2022-04-14 09:15:16 +02:00
use ( schema : string , connection? : pg.Client | pg . PoolClient ) {
2021-03-16 18:42:03 +01:00
this . _schema = schema ;
2022-01-17 09:15:18 +01:00
if ( schema ) {
const sql = ` SET search_path TO " ${ schema } " ` ;
if ( connection === undefined )
return this . raw ( sql ) ;
else
return connection . query ( sql ) ;
}
2021-03-16 18:42:03 +01:00
}
2022-04-15 23:13:23 +02:00
getCollations ( ) : null [ ] {
return [ ] ;
}
2023-06-13 18:10:52 +02:00
async getDatabases ( ) {
const { rows } = await this . raw ( 'SELECT datname FROM pg_database WHERE datistemplate = false' ) ;
if ( rows ) {
return rows . reduce ( ( acc , cur ) = > {
acc . push ( cur . datname ) ;
return acc ;
} , [ ] as string [ ] ) ;
}
else
return [ ] ;
}
2022-04-14 09:15:16 +02:00
async getStructure ( schemas : Set < string > ) {
/* eslint-disable camelcase */
interface ShowTableResult {
Db? : string ;
data_length : number ;
index_length : number ;
table_name : string ;
table_type : string ;
reltuples : number ;
Collation : string ;
comment : string ;
}
interface ShowTriggersResult {
Db? : string ;
table_name : string ;
trigger_name : string ;
enabled : boolean ;
}
/* eslint-enable camelcase */
const { rows : databases } = await this . raw < antares.QueryResult < { database : string } > > ( 'SELECT schema_name AS database FROM information_schema.schemata ORDER BY schema_name' ) ;
2021-03-16 18:42:03 +01:00
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\'' ) ;
2022-04-14 09:15:16 +02:00
const tablesArr : ShowTableResult [ ] = [ ] ;
const triggersArr : ShowTriggersResult [ ] = [ ] ;
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 ;
2022-04-14 09:15:16 +02:00
let { rows : tables } = await this . raw < antares.QueryResult < ShowTableResult > > ( `
2021-03-16 18:42:03 +01:00
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 ) ;
}
2022-04-14 09:15:16 +02:00
let { rows : triggers } = await this . raw < antares.QueryResult < ShowTriggersResult > > ( `
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 = > {
2022-02-28 14:19:07 +01:00
const tableSize = Number ( table . data_length ) + Number ( table . index_length ) ;
2021-11-06 16:36:54 +01:00
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 : [ ]
} ;
}
} ) ;
}
2022-04-14 09:15:16 +02:00
async getTableColumns ( { schema , table } : { schema : string ; table : string } , arrayRemap = true ) {
/* eslint-disable camelcase */
interface TableColumnsResult {
data_type : string ;
udt_name : string ;
column_name : string ;
table_schema : string ;
table_name : string ;
numeric_scale : number ;
numeric_precision : number ;
datetime_precision : number ;
character_maximum_length : number ;
is_nullable : string ;
ordinal_position : number ;
column_default : string ;
character_set_name : string ;
collation_name : string ;
}
/* eslint-enable camelcase */
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' } )
2022-04-14 09:15:16 +02:00
. run < TableColumnsResult > ( ) ;
2021-03-16 18:42:03 +01:00
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 ,
2022-01-22 12:29:49 +01:00
numScale : field.numeric_scale ,
2021-03-16 18:42:03 +01:00
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 : ''
} ;
} ) ;
}
2022-04-14 09:15:16 +02:00
async getTableApproximateCount ( { table } : { table : string } ) : Promise < number > {
2021-08-04 15:52:26 +02:00
const { rows } = await this . raw ( ` SELECT reltuples AS count FROM pg_class WHERE relname = ' ${ table } ' ` ) ;
return rows . length ? rows [ 0 ] . count : 0 ;
}
2022-04-14 09:15:16 +02:00
async getTableOptions ( { schema , table } : { schema : string ; table : string } ) {
/* eslint-disable camelcase */
interface TableOptionsResult {
table_name : string ;
table_type : string ;
reltuples : string ;
data_length : number ;
index_length : number ;
Collation : string ;
comment : string ;
}
/* eslint-enable camelcase */
const { rows } = await this . raw < antares.QueryResult < TableOptionsResult > > ( `
2021-08-11 16:16:58 +02:00
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 : ''
} ;
2022-04-14 09:15:16 +02:00
}
2021-08-11 16:16:58 +02:00
return { } ;
}
2022-04-14 09:15:16 +02:00
async getTableIndexes ( { schema , table } : { schema : string ; table : string } ) {
/* eslint-disable camelcase */
interface ShowIntexesResult {
constraint_name : string ;
column_name : string ;
constraint_type : string ;
}
/* eslint-enable camelcase */
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
2022-04-14 09:15:16 +02:00
const { rows } = await this . raw < antares.QueryResult < ShowIntexesResult > > ( ` WITH ndx_list AS (
2021-03-16 18:42:03 +01:00
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 ,
2022-11-15 16:46:12 +01:00
type : row . constraint_type
2021-03-16 18:42:03 +01:00
} ;
} ) ;
}
2022-04-14 09:15:16 +02:00
async getTableByIDs ( ids : string ) {
2021-03-21 13:00:27 +01:00
if ( ! ids ) return ;
2022-04-14 09:15:16 +02:00
const { rows } = await this . raw < antares.QueryResult < { tableid : number ; relname : string ; schemaname : string } > > ( `
2021-03-19 18:49:26 +01:00
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 ;
2022-04-14 09:15:16 +02:00
} , { } as { table : string ; schema : string } [ ] ) ;
2021-03-19 18:49:26 +01:00
}
2023-05-25 18:51:56 +02:00
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async getTableDll ( { schema , table } : { schema : string ; table : string } ) {
// const { rows } = await this.raw<antares.QueryResult<{'ddl'?: string}>>(`
// SELECT
// 'CREATE TABLE ' || relname || E'\n(\n' ||
// array_to_string(
// array_agg(' ' || column_name || ' ' || type || ' '|| not_null)
// , E',\n'
// ) || E'\n);\n' AS ddl
// FROM (
// SELECT
// a.attname AS column_name
// , pg_catalog.format_type(a.atttypid, a.atttypmod) AS type
// , CASE WHEN a.attnotnull THEN 'NOT NULL' ELSE 'NULL' END AS not_null
// , c.relname
// FROM pg_attribute a, pg_class c, pg_type t
// WHERE a.attnum > 0
// AND a.attrelid = c.oid
// AND a.atttypid = t.oid
// AND c.relname = '${table}'
// ORDER BY a.attnum
// ) AS tabledefinition
// GROUP BY relname
// `);
// if (rows.length)
// return rows[0].ddl;
// else return '';
/* eslint-disable camelcase */
interface SequenceRecord {
sequence_catalog : string ;
sequence_schema : string ;
sequence_name : string ;
data_type : string ;
numeric_precision : number ;
numeric_precision_radix : number ;
numeric_scale : number ;
start_value : string ;
minimum_value : string ;
maximum_value : string ;
increment : string ;
cycle_option : string ;
}
/* eslint-enable camelcase */
let createSql = '' ;
const sequences = [ ] ;
const columnsSql = [ ] ;
const arrayTypes : { [ key : string ] : string } = {
_int2 : 'smallint' ,
_int4 : 'integer' ,
_int8 : 'bigint' ,
_float4 : 'real' ,
_float8 : 'double precision' ,
_char : '"char"' ,
_varchar : 'character varying'
} ;
// Table columns
const { rows } = await this . raw ( `
SELECT *
FROM "information_schema" . "columns"
WHERE "table_schema" = '${schema}'
AND "table_name" = '${table}'
ORDER BY "ordinal_position" ASC
` , { schema: 'information_schema' });
if ( ! rows . length ) return '' ;
for ( const column of rows ) {
let fieldType = column . data_type ;
if ( fieldType === 'USER-DEFINED' ) fieldType = ` " ${ schema } ". ${ column . udt_name } ` ;
else if ( fieldType === 'ARRAY' ) {
if ( Object . keys ( arrayTypes ) . includes ( fieldType ) )
fieldType = arrayTypes [ column . udt_name ] + '[]' ;
else
fieldType = column . udt_name . replaceAll ( '_' , '' ) + '[]' ;
}
const columnArr = [
` " ${ column . column_name } " ` ,
` ${ fieldType } ${ column . character_maximum_length ? ` ( ${ column . character_maximum_length } ) ` : '' } `
] ;
if ( column . column_default ) {
columnArr . push ( ` DEFAULT ${ column . column_default } ` ) ;
if ( column . column_default . includes ( 'nextval' ) ) {
const sequenceName = column . column_default . split ( '\'' ) [ 1 ] ;
sequences . push ( sequenceName ) ;
}
}
if ( column . is_nullable === 'NO' ) columnArr . push ( 'NOT NULL' ) ;
columnsSql . push ( columnArr . join ( ' ' ) ) ;
}
// Table sequences
for ( let sequence of sequences ) {
if ( sequence . includes ( '.' ) ) sequence = sequence . split ( '.' ) [ 1 ] ;
const { rows } = await this . select ( '*' )
. schema ( 'information_schema' )
. from ( 'sequences' )
. where ( { sequence_schema : ` = ' ${ schema } ' ` , sequence_name : ` = ' ${ sequence } ' ` } )
. run < SequenceRecord > ( ) ;
if ( rows . length ) {
createSql += ` CREATE SEQUENCE " ${ schema } "." ${ sequence } "
START WITH $ { rows [ 0 ] . start_value }
INCREMENT BY $ { rows [ 0 ] . increment }
MINVALUE $ { rows [ 0 ] . minimum_value }
MAXVALUE $ { rows [ 0 ] . maximum_value }
CACHE 1 ; \ n ` ;
}
}
// Table create
createSql += ` \ nCREATE TABLE " ${ schema } "." ${ table } "(
$ { columnsSql . join ( ',\n ' ) }
) ; \ n ` ;
// Table indexes
createSql += '\n' ;
const { rows : indexes } = await this . select ( '*' )
. schema ( 'pg_catalog' )
. from ( 'pg_indexes' )
. where ( { schemaname : ` = ' ${ schema } ' ` , tablename : ` = ' ${ table } ' ` } )
. run < { indexdef : string } > ( ) ;
for ( const index of indexes )
createSql += ` ${ index . indexdef } ; \ n ` ;
return createSql ;
}
2022-04-14 09:15:16 +02:00
async getKeyUsage ( { schema , table } : { schema : string ; table : string } ) {
/* eslint-disable camelcase */
interface KeyResult {
table_schema : string ;
table_name : string ;
column_name : string ;
ordinal_position : number ;
position_in_unique_constraint : number ;
constraint_name : string ;
foreign_table_schema : string ;
foreign_table_name : string ;
foreign_column_name : string ;
update_rule : string ;
delete_rule : string ;
}
/* eslint-enable camelcase */
const { rows } = await this . raw < antares.QueryResult < KeyResult > > ( `
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
} ;
} ) ;
}
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
2022-04-14 09:15:16 +02:00
} as { name : string ; host : string ; password : string } ;
2021-03-16 18:42:03 +01:00
} ) ;
}
2022-04-14 09:15:16 +02:00
async createSchema ( params : { name : string } ) {
2021-03-16 18:42:03 +01:00
return await this . raw ( ` CREATE SCHEMA " ${ params . name } " ` ) ;
}
2022-04-14 09:15:16 +02:00
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async alterSchema ( params : { name : string } ) : Promise < void > {
return null ;
2021-03-16 18:42:03 +01:00
}
2022-04-14 09:15:16 +02:00
async dropSchema ( params : { database : string } ) {
2022-03-21 18:32:45 +01:00
return await this . raw ( ` DROP SCHEMA " ${ params . database } " CASCADE ` ) ;
2021-03-16 18:42:03 +01:00
}
2022-04-14 09:15:16 +02:00
async createTable ( params : antares.CreateTableParams ) {
const {
schema ,
fields ,
foreigns ,
indexes ,
options
} = params ;
const newColumns : string [ ] = [ ] ;
const newIndexes : string [ ] = [ ] ;
const manageIndexes : string [ ] = [ ] ;
const newForeigns : string [ ] = [ ] ;
2021-03-16 18:42:03 +01:00
2022-04-14 09:15:16 +02:00
let sql = ` CREATE TABLE " ${ schema } "." ${ options . name } " ` ;
2021-03-16 18:42:03 +01:00
2022-04-14 09:15:16 +02:00
// ADD FIELDS
fields . forEach ( field = > {
2022-05-07 09:46:07 +02:00
const typeInfo = this . getTypeInfo ( field . type ) ;
2022-04-14 09:15:16 +02:00
const length = typeInfo . length ? field . enumValues || field . numLength || field . charLength || field.datePrecision : false ;
2021-03-16 18:42:03 +01:00
2022-04-14 09:15:16 +02:00
newColumns . push ( ` " ${ field . name } "
$ { field . type . toUpperCase ( ) } $ { length ? ` ( ${ length } ${ field . numScale !== null ? ` , ${ field . numScale } ` : '' } ) ` : '' }
$ { field . unsigned ? 'UNSIGNED' : '' }
$ { field . zerofill ? 'ZEROFILL' : '' }
$ { field . nullable ? 'NULL' : 'NOT NULL' }
2022-05-05 23:09:10 +02:00
$ { field . default !== null ? ` DEFAULT ${ field . default || '\'\'' } ` : '' }
2022-04-14 09:15:16 +02:00
$ { field . onUpdate ? ` ON UPDATE ${ field . onUpdate } ` : '' } ` );
} ) ;
2021-03-16 18:42:03 +01:00
2022-04-14 09:15:16 +02:00
// ADD INDEX
indexes . forEach ( index = > {
const fields = index . fields . map ( field = > ` ${ field } ` ) . join ( ',' ) ;
const type = index . type ;
2021-03-16 18:42:03 +01:00
2022-04-14 09:15:16 +02:00
if ( type === 'PRIMARY' )
newIndexes . push ( ` PRIMARY KEY ( ${ fields } ) ` ) ;
else if ( type === 'UNIQUE' )
newIndexes . push ( ` CONSTRAINT " ${ index . name } " UNIQUE ( ${ fields } ) ` ) ;
else
manageIndexes . push ( ` CREATE INDEX " ${ index . name } " ON " ${ schema } "." ${ options . name } " ( ${ fields } ) ` ) ;
} ) ;
2021-06-08 09:12:43 +02:00
2022-04-14 09:15:16 +02:00
// ADD FOREIGN KEYS
foreigns . forEach ( foreign = > {
newForeigns . push ( ` CONSTRAINT " ${ foreign . constraintName } " FOREIGN KEY (" ${ foreign . field } ") REFERENCES " ${ schema } "." ${ foreign . refTable } " (" ${ foreign . refField } ") ON UPDATE ${ foreign . onUpdate } ON DELETE ${ foreign . onDelete } ` ) ;
} ) ;
2021-03-16 18:42:03 +01:00
2022-04-14 09:15:16 +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 ) ;
}
2022-04-14 09:15:16 +02:00
async alterTable ( params : antares.AlterTableParams ) {
const {
table ,
schema ,
additions ,
deletions ,
changes ,
indexChanges ,
foreignChanges ,
options
} = params ;
2021-06-17 22:01:18 +02:00
2022-04-14 09:15:16 +02:00
if ( schema !== 'public' )
await this . use ( schema ) ;
2021-03-16 18:42:03 +01:00
2022-04-14 09:15:16 +02:00
let sql = '' ;
const alterColumns : string [ ] = [ ] ;
const renameColumns : string [ ] = [ ] ;
const createSequences : string [ ] = [ ] ;
const manageIndexes : string [ ] = [ ] ;
2021-03-16 18:42:03 +01:00
2022-04-14 09:15:16 +02:00
// ADD FIELDS
additions . forEach ( addition = > {
2022-05-07 09:46:07 +02:00
const typeInfo = this . getTypeInfo ( addition . type ) ;
2022-04-14 09:15:16 +02:00
const length = typeInfo . length ? addition . numLength || addition . charLength || addition.datePrecision : false ;
2021-11-04 21:54:42 +01:00
2022-04-14 09:15:16 +02:00
alterColumns . push ( ` ADD COLUMN " ${ addition . name } "
$ { addition . type . toUpperCase ( ) } $ { length ? ` ( ${ length } ${ addition . numScale !== null ? ` , ${ addition . numScale } ` : '' } ) ` : '' } $ { addition . isArray ? '[]' : '' }
$ { addition . unsigned ? 'UNSIGNED' : '' }
$ { addition . zerofill ? 'ZEROFILL' : '' }
$ { addition . nullable ? 'NULL' : 'NOT NULL' }
2022-05-05 23:09:10 +02:00
$ { addition . default !== null ? ` DEFAULT ${ addition . default || '\'\'' } ` : '' }
2022-04-14 09:15:16 +02:00
$ { addition . onUpdate ? ` ON UPDATE ${ addition . onUpdate } ` : '' } ` );
} ) ;
2021-11-04 21:54:42 +01:00
2022-04-14 09:15:16 +02:00
// ADD INDEX
indexChanges . additions . forEach ( addition = > {
const fields = addition . fields . map ( field = > ` " ${ field } " ` ) . join ( ',' ) ;
const type = addition . type ;
2021-03-16 18:42:03 +01:00
2022-04-14 09:15:16 +02:00
if ( type === 'PRIMARY' )
alterColumns . push ( ` ADD PRIMARY KEY ( ${ fields } ) ` ) ;
else if ( type === 'UNIQUE' )
alterColumns . push ( ` ADD CONSTRAINT " ${ addition . name } " UNIQUE ( ${ fields } ) ` ) ;
else
manageIndexes . push ( ` CREATE INDEX " ${ addition . name } " ON " ${ schema } "." ${ table } " ( ${ fields } ) ` ) ;
} ) ;
// ADD FOREIGN KEYS
foreignChanges . additions . forEach ( addition = > {
alterColumns . push ( ` ADD CONSTRAINT " ${ addition . constraintName } " FOREIGN KEY (" ${ addition . field } ") REFERENCES " ${ schema } "." ${ addition . refTable } " ( ${ addition . refField } ) ON UPDATE ${ addition . onUpdate } ON DELETE ${ addition . onDelete } ` ) ;
} ) ;
// CHANGE FIELDS
changes . forEach ( change = > {
2022-05-07 09:46:07 +02:00
const typeInfo = this . getTypeInfo ( change . type ) ;
2022-04-14 09:15:16 +02:00
const length = typeInfo . length ? change . numLength || change . charLength || change.datePrecision : false ;
let localType ;
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 . name } " TYPE ${ localType } ${ length ? ` ( ${ length } ${ change . numScale ? ` , ${ change . numScale } ` : '' } ) ` : '' } ${ change . isArray ? '[]' : '' } USING " ${ change . name } ":: ${ localType } ` ) ;
alterColumns . push ( ` ALTER COLUMN " ${ change . name } " ${ change . nullable ? 'DROP NOT NULL' : 'SET NOT NULL' } ` ) ;
2022-05-05 23:09:10 +02:00
alterColumns . push ( ` ALTER COLUMN " ${ change . name } " ${ change . default !== null ? ` SET DEFAULT ${ change . default || '\'\'' } ` : 'DROP DEFAULT' } ` ) ;
2022-04-14 09:15:16 +02:00
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 . name } " ` ) ;
alterColumns . push ( ` ALTER COLUMN " ${ change . name } " SET DEFAULT nextval(' ${ sequenceName } ') ` ) ;
}
if ( change . orgName !== change . name )
renameColumns . push ( ` ALTER TABLE " ${ schema } "." ${ table } " RENAME COLUMN " ${ change . orgName } " TO " ${ change . name } " ` ) ;
} ) ;
// CHANGE INDEX
indexChanges . changes . forEach ( change = > {
if ( [ 'PRIMARY' , 'UNIQUE' ] . includes ( change . oldType ) )
alterColumns . push ( ` DROP CONSTRAINT ${ change . oldName } ` ) ;
else
manageIndexes . push ( ` DROP INDEX ${ change . oldName } ` ) ;
const fields = change . fields . map ( field = > ` " ${ field } " ` ) . join ( ',' ) ;
const type = change . type ;
if ( type === 'PRIMARY' )
alterColumns . push ( ` ADD PRIMARY KEY ( ${ fields } ) ` ) ;
else if ( type === 'UNIQUE' )
alterColumns . push ( ` ADD CONSTRAINT " ${ change . name } " UNIQUE ( ${ fields } ) ` ) ;
else
manageIndexes . push ( ` CREATE INDEX " ${ change . name } " ON " ${ schema } "." ${ table } " ( ${ fields } ) ` ) ;
} ) ;
// CHANGE FOREIGN KEYS
foreignChanges . changes . forEach ( change = > {
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 } ` ) ;
} ) ;
// DROP FIELDS
deletions . forEach ( deletion = > {
alterColumns . push ( ` DROP COLUMN " ${ deletion . name } " ` ) ;
} ) ;
// DROP INDEX
indexChanges . deletions . forEach ( deletion = > {
if ( [ 'PRIMARY' , 'UNIQUE' ] . includes ( deletion . type ) )
alterColumns . push ( ` DROP CONSTRAINT " ${ deletion . name } " ` ) ;
else
manageIndexes . push ( ` DROP INDEX " ${ deletion . name } " ` ) ;
} ) ;
// DROP FOREIGN KEYS
foreignChanges . deletions . forEach ( deletion = > {
alterColumns . push ( ` DROP CONSTRAINT " ${ deletion . constraintName } " ` ) ;
} ) ;
if ( alterColumns . length ) sql += ` ALTER TABLE " ${ schema } "." ${ table } " ${ alterColumns . join ( ', ' ) } ; ` ;
if ( createSequences . length ) sql = ` ${ createSequences . join ( ';' ) } ; ${ sql } ` ;
if ( manageIndexes . length ) sql = ` ${ manageIndexes . join ( ';' ) } ; ${ sql } ` ;
if ( options . name ) sql += ` ALTER TABLE " ${ schema } "." ${ table } " RENAME TO " ${ options . name } "; ` ;
// RENAME
if ( renameColumns . length ) sql = ` ${ renameColumns . join ( ';' ) } ; ${ sql } ` ;
return await this . raw ( sql ) ;
}
async duplicateTable ( params : { schema : string ; table : string } ) {
const sql = ` CREATE TABLE " ${ params . schema } "." ${ params . table } _copy" (LIKE " ${ params . schema } "." ${ params . table } " INCLUDING ALL) ` ;
return await this . raw ( sql ) ;
}
async truncateTable ( params : { schema : string ; table : string } ) {
const sql = ` TRUNCATE TABLE " ${ params . schema } "." ${ params . table } " ` ;
return await this . raw ( sql ) ;
}
async dropTable ( params : { schema : string ; table : string } ) {
const sql = ` DROP TABLE " ${ params . schema } "." ${ params . table } " ` ;
return await this . raw ( sql ) ;
}
async getViewInformations ( { schema , view } : { schema : string ; view : string } ) {
const sql = ` SELECT "definition" FROM "pg_views" WHERE "viewname"=' ${ view } ' AND "schemaname"=' ${ schema } ' ` ;
const results = await this . raw ( sql ) ;
return results . rows . map ( row = > {
return {
algorithm : '' ,
definer : '' ,
security : '' ,
updateOption : '' ,
sql : row.definition ,
name : view
} ;
} ) [ 0 ] ;
}
async dropView ( params : { schema : string ; view : string } ) {
const sql = ` DROP VIEW " ${ params . schema } "." ${ params . view } " ` ;
return await this . raw ( sql ) ;
}
async alterView ( { view } : { view : antares.AlterViewParams } ) {
let sql = ` CREATE OR REPLACE VIEW " ${ view . schema } "." ${ view . oldName } " AS ${ view . sql } ` ;
if ( view . name !== view . oldName )
sql += ` ; ALTER VIEW " ${ view . schema } "." ${ view . oldName } " RENAME TO " ${ view . name } " ` ;
return await this . raw ( sql ) ;
}
async createView ( params : antares.CreateViewParams ) {
const sql = ` CREATE VIEW " ${ params . schema } "." ${ params . name } " AS ${ params . sql } ` ;
return await this . raw ( sql ) ;
}
async getTriggerInformations ( { schema , trigger } : { schema : string ; trigger : string } ) {
const [ table , triggerName ] = trigger . split ( '.' ) ;
const results = await this . raw ( `
SELECT
information_schema . triggers . event_object_schema AS table_schema ,
information_schema . triggers . event_object_table AS table_name ,
information_schema . triggers . trigger_schema ,
information_schema . triggers . trigger_name ,
string_agg ( event_manipulation , ',' ) AS EVENT ,
action_timing AS activation ,
action_condition AS condition ,
action_statement AS definition ,
( pg_trigger . tgenabled != 'D' ) : : bool AS enabled
FROM pg_trigger
JOIN pg_class ON pg_trigger . tgrelid = pg_class . oid
JOIN pg_namespace ON pg_namespace . oid = pg_class . relnamespace
JOIN information_schema . triggers ON pg_namespace . nspname = information_schema . triggers . trigger_schema
AND pg_class . relname = information_schema . triggers . event_object_table
WHERE trigger_schema = '${schema}'
AND trigger_name = '${triggerName}'
AND event_object_table = '${table}'
GROUP BY 1 , 2 , 3 , 4 , 6 , 7 , 8 , 9
ORDER BY table_schema ,
table_name
` );
return results . rows . map ( row = > {
return {
sql : row.definition ,
name : row.trigger_name ,
table : row.table_name ,
event : [ . . . new Set ( row . event . split ( ',' ) ) ] ,
activation : row.activation
} ;
} ) [ 0 ] ;
}
async dropTrigger ( params : { schema : string ; trigger : string } ) {
const triggerParts = params . trigger . split ( '.' ) ;
const sql = ` DROP TRIGGER " ${ triggerParts [ 1 ] } " ON " ${ params . schema } "." ${ triggerParts [ 0 ] } " ` ;
return await this . raw ( sql ) ;
}
async alterTrigger ( { trigger } : { trigger : antares.AlterTriggerParams } ) {
const tempTrigger = Object . assign ( { } , trigger ) ;
tempTrigger . name = ` Antares_ ${ tempTrigger . name } _tmp ` ;
try {
await this . createTrigger ( tempTrigger ) ;
await this . dropTrigger ( { schema : trigger.schema , trigger : ` ${ tempTrigger . table } . ${ tempTrigger . name } ` } ) ;
await this . dropTrigger ( { schema : trigger.schema , trigger : ` ${ trigger . table } . ${ trigger . oldName } ` } ) ;
await this . createTrigger ( trigger ) ;
}
catch ( err ) {
return Promise . reject ( err ) ;
}
}
async createTrigger ( params : antares.CreateTriggerParams ) {
const eventsString = Array . isArray ( params . event ) ? params . event . join ( ' OR ' ) : params . event ;
const sql = ` CREATE TRIGGER " ${ params . name } " ${ params . activation } ${ eventsString } ON " ${ params . schema } "." ${ params . table } " FOR EACH ROW ${ params . sql } ` ;
return await this . raw ( sql , { split : false } ) ;
}
async enableTrigger ( { schema , trigger } : { schema : string ; trigger : string } ) {
const [ table , triggerName ] = trigger . split ( '.' ) ;
const sql = ` ALTER TABLE " ${ schema } "." ${ table } " ENABLE TRIGGER " ${ triggerName } " ` ;
return await this . raw ( sql , { split : false } ) ;
}
async disableTrigger ( { schema , trigger } : { schema : string ; trigger : string } ) {
const [ table , triggerName ] = trigger . split ( '.' ) ;
const sql = ` ALTER TABLE " ${ schema } "." ${ table } " DISABLE TRIGGER " ${ triggerName } " ` ;
return await this . raw ( sql , { split : false } ) ;
}
async getRoutineInformations ( { schema , routine } : { schema : string ; routine : string } ) {
const sql = ` SELECT pg_get_functiondef((SELECT oid FROM pg_proc WHERE proname = ' ${ routine } ')); ` ;
const results = await this . raw ( sql ) ;
return results . rows . map ( async row = > {
if ( ! row . pg_get_functiondef ) {
return {
definer : null ,
sql : '' ,
parameters : [ ] ,
name : routine ,
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 ] ;
}
2022-04-14 09:15:16 +02:00
async dropRoutine ( params : { schema : string ; routine : string } ) {
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 ) ;
}
2022-04-14 09:15:16 +02:00
async alterRoutine ( { routine } : { routine : antares.AlterRoutineParams } ) {
2021-03-16 18:42:03 +01:00
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 ) ;
}
}
2022-04-14 09:15:16 +02:00
async createRoutine ( routine : antares.CreateRoutineParams ) {
2021-03-16 18:42:03 +01:00
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 } ) ;
}
2022-04-14 09:15:16 +02:00
async getFunctionInformations ( { schema , func } : { schema : string ; func : string } ) {
/* eslint-disable camelcase */
interface CreateFunctionResult {
pg_get_functiondef : string ;
}
interface FunctionParamsResult {
parameter_mode : string ;
parameter_name : string ;
data_type : string ;
}
/* eslint-enable camelcase */
2021-04-12 18:46:35 +02:00
const sql = ` SELECT pg_get_functiondef((SELECT oid FROM pg_proc WHERE proname = ' ${ func } ')); ` ;
2022-04-14 09:15:16 +02:00
const results = await this . raw < antares.QueryResult < CreateFunctionResult > > ( sql ) ;
2021-03-16 18:42:03 +01:00
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
2022-04-14 09:15:16 +02:00
const results = await this . raw < antares.QueryResult < FunctionParamsResult > > ( 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 ] ;
}
2022-04-14 09:15:16 +02:00
async dropFunction ( params : { schema : string ; func : string } ) {
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
* /
2022-04-14 09:15:16 +02:00
async alterFunction ( { func } : { func : antares.AlterFunctionParams } ) {
2021-03-16 18:42:03 +01:00
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 ) ;
}
}
2022-04-14 09:15:16 +02:00
async createFunction ( func : antares.CreateFunctionParams ) {
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 } ) ;
}
2022-04-14 09:15:16 +02:00
async alterTriggerFunction ( { func } : { func : antares.CreateFunctionParams } ) {
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 } ) ;
2022-04-14 09:15:16 +02:00
}
async createTriggerFunction ( func : antares.CreateFunctionParams ) {
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
async getVariables ( ) {
2022-04-14 09:15:16 +02:00
interface ShowVariablesResult {
name : string ;
setting : string ;
}
2021-03-16 18:42:03 +01:00
const sql = 'SHOW ALL' ;
2022-04-14 09:15:16 +02:00
const results = await this . raw < antares.QueryResult < ShowVariablesResult > > ( sql ) ;
2021-03-16 18:42:03 +01:00
return results . rows . map ( row = > {
return {
name : row.name ,
value : row.setting
} ;
} ) ;
}
async getEngines ( ) {
return {
name : 'PostgreSQL' ,
support : 'YES' ,
comment : '' ,
isDefault : true
} ;
}
async getVersion ( ) {
const sql = 'SELECT version()' ;
const { rows } = await this . raw ( sql ) ;
const infos = rows [ 0 ] . version . split ( ',' ) ;
return {
number : infos [ 0 ] . split ( ' ' ) [ 1 ] ,
name : infos [ 0 ] . split ( ' ' ) [ 0 ] ,
arch : infos [ 1 ] ,
os : infos [ 2 ]
} ;
}
async getProcesses ( ) {
const sql = 'SELECT "pid", "usename", "client_addr", "datname", application_name , EXTRACT(EPOCH FROM CURRENT_TIMESTAMP - "query_start")::INTEGER, "state", "query" FROM "pg_stat_activity"' ;
const { rows } = await this . raw ( sql ) ;
return rows . map ( row = > {
return {
id : row.pid ,
user : row.usename ,
host : row.client_addr ,
database : row.datname ,
application : row.application_name ,
time : row.date_part ,
state : row.state ,
info : row.query
} ;
} ) ;
}
2022-04-14 09:15:16 +02:00
async killProcess ( id : number ) {
2021-09-26 11:19:48 +02:00
return await this . raw ( ` SELECT pg_terminate_backend( ${ id } ) ` ) ;
}
2022-04-14 09:15:16 +02:00
async killTabQuery ( tabUid : string ) {
2021-12-26 21:13:02 +01:00
const id = this . _runningConnections . get ( tabUid ) ;
if ( id )
return await this . raw ( ` SELECT pg_cancel_backend( ${ id } ) ` ) ;
}
2022-04-14 09:15:16 +02:00
async commitTab ( tabUid : string ) {
2022-02-14 18:00:26 +01:00
const connection = this . _connectionsToCommit . get ( tabUid ) ;
2022-02-14 18:53:55 +01:00
if ( connection ) {
await connection . query ( 'COMMIT' ) ;
return this . destroyConnectionToCommit ( tabUid ) ;
}
2022-02-14 18:00:26 +01:00
}
2022-04-14 09:15:16 +02:00
async rollbackTab ( tabUid : string ) {
2022-02-14 18:00:26 +01:00
const connection = this . _connectionsToCommit . get ( tabUid ) ;
2022-02-14 18:53:55 +01:00
if ( connection ) {
await connection . query ( 'ROLLBACK' ) ;
return this . destroyConnectionToCommit ( tabUid ) ;
}
2022-02-14 18:00:26 +01:00
}
2022-04-14 09:15:16 +02:00
destroyConnectionToCommit ( tabUid : string ) {
2022-02-14 18:00:26 +01:00
const connection = this . _connectionsToCommit . get ( tabUid ) ;
if ( connection ) {
2022-04-14 09:15:16 +02:00
( connection as pg . Client ) . end ( ) ;
2022-02-14 18:00:26 +01:00
this . _connectionsToCommit . delete ( tabUid ) ;
}
}
2021-03-16 18:42:03 +01:00
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
2022-04-15 14:56:13 +02:00
const limitRaw = selectArray . length && this . _query . limit ? ` LIMIT ${ this . _query . limit } ` : '' ;
2021-03-16 18:42:03 +01:00
2021-05-27 22:13:59 +02:00
// OFFSET
2022-04-15 14:56:13 +02:00
const offsetRaw = selectArray . length && this . _query . offset ? ` OFFSET ${ this . _query . offset } ` : '' ;
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
}
2022-04-14 09:15:16 +02:00
async raw < T = antares.QueryResult > ( sql : string , args? : antares.QueryParams ) {
2022-07-18 17:21:34 +02:00
this . _logger ( { cUid : this._cUid , sql } ) ;
2021-12-26 21:13:02 +01:00
2021-03-16 18:42:03 +01:00
args = {
nest : false ,
details : false ,
split : true ,
2021-07-21 14:40:29 +02:00
comments : true ,
2022-02-14 18:00:26 +01:00
autocommit : true ,
2021-03-16 18:42:03 +01:00
. . . args
} ;
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
2022-04-14 09:15:16 +02:00
const resultsArr : antares.QueryResult [ ] = [ ] ;
2021-03-16 18:42:03 +01:00
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
2022-04-14 09:15:16 +02:00
let connection : pg.Client | pg . PoolClient ;
const isPool = this . _connection instanceof pg . Pool ;
2022-02-14 18:00:26 +01:00
if ( ! args . autocommit && args . tabUid ) { // autocommit OFF
if ( this . _connectionsToCommit . has ( args . tabUid ) )
connection = this . _connectionsToCommit . get ( args . tabUid ) ;
else {
connection = await this . getConnection ( ) ;
await connection . query ( 'START TRANSACTION' ) ;
this . _connectionsToCommit . set ( args . tabUid , connection ) ;
}
}
2022-04-14 09:15:16 +02:00
else { // autocommit ON
connection = isPool ? await this . _connection . connect ( ) as pg.PoolClient : this._connection as pg . Client ;
}
2021-12-26 21:13:02 +01:00
if ( args . tabUid && isPool )
2022-04-14 09:15:16 +02:00
this . _runningConnections . set ( args . tabUid , ( connection as pg . PoolClient & { processID : number } ) . processID ) ;
2021-12-26 21:13:02 +01:00
2023-07-07 15:22:16 +02:00
if ( args . schema )
2022-01-17 09:15:18 +01:00
await this . use ( args . schema , connection ) ;
2021-03-16 18:42:03 +01:00
for ( const query of queries ) {
if ( ! query ) continue ;
const timeStart = new Date ( ) ;
2022-04-14 09:15:16 +02:00
let timeStop : Date ;
let keysArr : antares.QueryForeign [ ] = [ ] ;
2021-03-16 18:42:03 +01:00
2022-09-21 10:33:44 +02:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const { rows , report , fields , keys , duration } : any = await new Promise ( ( resolve , reject ) = > {
2021-12-26 21:13:02 +01:00
( async ( ) = > {
try {
const res = await connection . query ( { rowMode : args.nest ? 'array' : null , text : query } ) ;
timeStop = new Date ( ) ;
2021-03-16 18:42:03 +01:00
2022-04-14 09:15:16 +02:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let ast : any ;
2021-03-19 18:49:26 +01:00
2021-03-16 18:42:03 +01:00
try {
2022-04-14 09:15:16 +02:00
[ ast ] = pgAst . parse ( query ) ; // TODO: maybe refactor
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 ;
2022-04-14 09:15:16 +02:00
let tablesInfo : { table : string ; schema : string } [ ] ;
2021-03-21 11:51:22 +01:00
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 ) )
2022-04-14 09:15:16 +02:00
return undefined ;
2021-03-16 18:42:03 +01:00
2022-04-14 09:15:16 +02:00
let schema : string = ast && ast . from && 'schema' in ast . from [ 0 ] ? ast . from [ 0 ] . schema : this._schema ;
let table : string = ast && ast . from ? ast . from [ 0 ] . name : null ;
2021-03-21 11:51:22 +01:00
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 ,
2022-04-14 09:15:16 +02:00
type : this . types [ field . dataTypeID ] || field . format ,
length : undefined as number ,
key : undefined as string
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 } ;
2022-04-14 09:15:16 +02:00
}
2021-03-16 18:42:03 +01:00
}
return field ;
} ) ;
}
catch ( err ) {
2022-02-14 18:00:26 +01:00
if ( isPool && args . autocommit ) {
2022-04-14 09:15:16 +02:00
( connection as pg . PoolClient ) . release ( ) ;
2021-12-26 21:13:02 +01:00
this . _runningConnections . delete ( args . tabUid ) ;
}
2021-03-16 18:42:03 +01:00
reject ( err ) ;
}
try { // Key usage (foreign keys)
const response = await this . getKeyUsage ( paramObj ) ;
keysArr = keysArr ? [ . . . keysArr , . . . response ] : response ;
}
catch ( err ) {
2022-02-14 18:00:26 +01:00
if ( isPool && args . autocommit ) {
2022-04-14 09:15:16 +02:00
( connection as pg . PoolClient ) . release ( ) ;
2021-12-26 21:13:02 +01:00
this . _runningConnections . delete ( args . tabUid ) ;
}
2021-03-16 18:42:03 +01:00
reject ( err ) ;
}
}
}
}
resolve ( {
2022-04-14 09:15:16 +02:00
duration : timeStop.getTime ( ) - timeStart . getTime ( ) ,
2021-03-16 18:42:03 +01:00
rows : Array.isArray ( queryResult ) ? queryResult . some ( el = > Array . isArray ( el ) ) ? [ ] : queryResult : false ,
report : ! Array . isArray ( queryResult ) ? queryResult : false ,
fields : remappedFields ,
keys : keysArr
} ) ;
}
2021-12-26 21:13:02 +01:00
catch ( err ) {
2022-02-14 18:00:26 +01:00
if ( isPool && args . autocommit ) {
2022-04-14 09:15:16 +02:00
( connection as pg . PoolClient ) . release ( ) ;
2021-12-26 21:13:02 +01:00
this . _runningConnections . delete ( args . tabUid ) ;
}
reject ( err ) ;
}
} ) ( ) ;
2021-03-16 18:42:03 +01:00
} ) ;
resultsArr . push ( { rows , report , fields , keys , duration } ) ;
}
2022-02-14 18:00:26 +01:00
if ( isPool && args . autocommit ) {
2022-04-14 09:15:16 +02:00
( connection as pg . PoolClient ) . release ( ) ;
2021-12-26 21:13:02 +01:00
this . _runningConnections . delete ( args . tabUid ) ;
}
2022-04-14 09:15:16 +02:00
const result = resultsArr . length === 1 ? resultsArr [ 0 ] : resultsArr ;
return result as unknown as T ;
2021-03-16 18:42:03 +01:00
}
}