2022-04-14 18:25:13 +02:00
import * as antares from 'common/interfaces/antares' ;
import * as sqlite from 'better-sqlite3' ;
2021-11-13 23:00:53 +01:00
import { AntaresCore } from '../AntaresCore' ;
2022-04-14 18:25:13 +02:00
import * as dataTypes from 'common/data-types/sqlite' ;
2021-11-18 19:43:08 +01:00
import { NUMBER , FLOAT , TIME , DATETIME } from 'common/fieldTypes' ;
2021-11-13 23:00:53 +01:00
export class SQLiteClient extends AntaresCore {
2022-04-14 18:25:13 +02:00
private _schema? : string ;
private _connectionsToCommit : Map < string , sqlite.Database > ;
protected _connection? : sqlite.Database ;
protected _params : { databasePath : string ; readonly : boolean } ;
constructor ( args : antares.ClientParams ) {
2021-11-13 23:00:53 +01:00
super ( args ) ;
this . _schema = null ;
2022-02-14 18:53:55 +01:00
this . _connectionsToCommit = new Map ( ) ;
2021-11-13 23:00:53 +01:00
}
2022-04-14 18:25:13 +02:00
_getTypeInfo ( type : string ) {
2021-11-13 23:00:53 +01:00
return dataTypes
. reduce ( ( acc , group ) = > [ . . . acc , . . . group . types ] , [ ] )
. filter ( _type = > _type . name === type . toUpperCase ( ) ) [ 0 ] ;
}
async connect ( ) {
2022-02-14 18:53:55 +01:00
this . _connection = this . getConnection ( ) ;
}
getConnection ( ) {
return sqlite ( this . _params . databasePath , {
2021-11-16 13:21:33 +01:00
fileMustExist : true ,
readonly : this . _params . readonly
2021-11-13 23:00:53 +01:00
} ) ;
}
2022-04-14 18:25:13 +02:00
destroy ( ) : void {
return null ;
}
2021-11-13 23:00:53 +01:00
2022-04-14 18:25:13 +02:00
use ( ) : void {
return null ;
}
2021-11-13 23:00:53 +01:00
2022-04-14 18:25:13 +02:00
async getStructure ( schemas : Set < string > ) {
/* eslint-disable camelcase */
interface ShowTableResult {
Db? : string ;
type : string ;
name : string ;
tbl_name : string ;
rootpage :4 ;
sql : string ;
}
type ShowTriggersResult = ShowTableResult
/* eslint-enable camelcase */
const { rows : databases } = await this . raw < antares.QueryResult < { name : string } > > ( 'SELECT * FROM pragma_database_list' ) ;
2021-11-13 23:00:53 +01:00
const filteredDatabases = databases ;
2022-04-14 18:25:13 +02:00
const tablesArr : ShowTableResult [ ] = [ ] ;
const triggersArr : ShowTriggersResult [ ] = [ ] ;
2021-11-13 23:00:53 +01:00
let schemaSize = 0 ;
for ( const db of filteredDatabases ) {
if ( ! schemas . has ( db . name ) ) continue ;
2022-04-14 18:25:13 +02:00
let { rows : tables } = await this . raw < antares.QueryResult < ShowTableResult > > ( `
2021-11-19 13:13:35 +01:00
SELECT *
FROM "${db.name}" . sqlite_master
WHERE type IN ( 'table' , 'view' )
AND name NOT LIKE 'sqlite_%'
ORDER BY name
` );
2021-11-13 23:00:53 +01:00
if ( tables . length ) {
tables = tables . map ( table = > {
table . Db = db . name ;
return table ;
} ) ;
tablesArr . push ( . . . tables ) ;
}
2022-04-14 18:25:13 +02:00
let { rows : triggers } = await this . raw < antares.QueryResult < ShowTriggersResult > > ( ` SELECT * FROM " ${ db . name } ".sqlite_master WHERE type='trigger' ` ) ;
2021-11-13 23:00:53 +01:00
if ( triggers . length ) {
triggers = triggers . map ( trigger = > {
trigger . Db = db . name ;
return trigger ;
} ) ;
triggersArr . push ( . . . triggers ) ;
}
}
return filteredDatabases . map ( db = > {
if ( schemas . has ( db . name ) ) {
// TABLES
const remappedTables = tablesArr . filter ( table = > table . Db === db . name ) . map ( table = > {
2021-11-15 18:09:34 +01:00
const tableSize = 0 ;
2021-11-13 23:00:53 +01:00
schemaSize += tableSize ;
return {
name : table.name ,
type : table . type ,
rows : false ,
size : false
} ;
} ) ;
// TRIGGERS
const remappedTriggers = triggersArr . filter ( trigger = > trigger . Db === db . name ) . map ( trigger = > {
return {
2021-11-19 15:36:07 +01:00
name : trigger.name ,
table : trigger.tbl_name
2021-11-13 23:00:53 +01:00
} ;
} ) ;
return {
name : db.name ,
size : schemaSize ,
tables : remappedTables ,
functions : [ ] ,
procedures : [ ] ,
triggers : remappedTriggers ,
schedulers : [ ]
} ;
}
else {
return {
name : db.name ,
size : 0 ,
tables : [ ] ,
functions : [ ] ,
procedures : [ ] ,
triggers : [ ] ,
schedulers : [ ]
} ;
}
} ) ;
}
2022-04-14 18:25:13 +02:00
async getTableColumns ( { schema , table } : { schema : string ; table : string } ) {
interface TableColumnsResult {
cid : number ;
name : string ;
type : string ;
notnull : 0 | 1 ;
// eslint-disable-next-line camelcase
dflt_value : string ;
pk : 0 | 1 ;
}
const { rows : fields } = await this . raw < antares.QueryResult < TableColumnsResult > > ( ` SELECT * FROM " ${ schema } ".pragma_table_info(' ${ table } ') ` ) ;
2021-11-13 23:00:53 +01:00
2021-11-18 11:36:46 +01:00
return fields . map ( field = > {
2022-04-14 18:25:13 +02:00
const [ type , length ] : [ string , number ? ] = field . type . includes ( '(' )
? field . type . replace ( ')' , '' ) . split ( '(' ) . map ( ( el : string | number ) = > {
if ( ! isNaN ( Number ( el ) ) ) el = Number ( el ) ;
2021-11-18 11:36:46 +01:00
return el ;
2022-04-14 18:25:13 +02:00
} ) as [ string , number ? ]
2021-11-18 11:36:46 +01:00
: [ field . type , null ] ;
2021-11-13 23:00:53 +01:00
return {
2021-11-18 11:36:46 +01:00
name : field.name ,
key : null ,
2021-11-18 19:43:08 +01:00
type : type . trim ( ) ,
2021-11-18 11:36:46 +01:00
schema : schema ,
table : table ,
numPrecision : [ . . . NUMBER , . . . FLOAT ] . includes ( type ) ? length : null ,
datePrecision : null ,
charLength : ! [ . . . NUMBER , . . . FLOAT ] . includes ( type ) ? length : null ,
nullable : ! field . notnull ,
unsigned : null ,
zerofill : null ,
2022-02-26 10:02:23 +01:00
order : typeof field . cid === 'string' ? + field . cid + 1 : field.cid + 1 ,
2021-11-18 11:36:46 +01:00
default : field . dflt_value ,
charset : null ,
collation : null ,
autoIncrement : false ,
onUpdate : null ,
comment : ''
2021-11-13 23:00:53 +01:00
} ;
} ) ;
}
2022-04-14 18:25:13 +02:00
async getTableApproximateCount ( { schema , table } : { schema : string ; table : string } ) : Promise < number > {
2021-11-15 18:09:34 +01:00
const { rows } = await this . raw ( ` SELECT COUNT(*) AS count FROM " ${ schema } "." ${ table } " ` ) ;
2021-11-13 23:00:53 +01:00
return rows . length ? rows [ 0 ] . count : 0 ;
}
2022-04-14 18:25:13 +02:00
async getTableOptions ( { table } : { table : string } ) {
2021-11-18 11:36:46 +01:00
return { name : table } ;
2021-11-13 23:00:53 +01:00
}
2022-04-14 18:25:13 +02:00
async getTableIndexes ( { schema , table } : { schema : string ; table : string } ) {
interface TableColumnsResult {
type : string ;
name : string ;
// eslint-disable-next-line camelcase
tbl_name : string ;
rootpage :4 ;
sql : string ;
}
interface ShowIndexesResult {
seq : number ;
name : string ;
unique : 0 | 1 ;
origin : string ;
partial : 0 | 1 ;
}
2021-11-16 12:27:51 +01:00
const remappedIndexes = [ ] ;
2022-04-14 18:25:13 +02:00
const { rows : primaryKeys } = await this . raw < antares.QueryResult < TableColumnsResult > > ( ` SELECT * FROM " ${ schema } ".pragma_table_info(' ${ table } ') WHERE pk != 0 ` ) ;
2021-11-16 12:27:51 +01:00
for ( const key of primaryKeys ) {
remappedIndexes . push ( {
name : 'PRIMARY' ,
column : key.name ,
2022-04-14 18:25:13 +02:00
indexType : null as never ,
2021-11-16 12:27:51 +01:00
type : 'PRIMARY' ,
2022-04-14 18:25:13 +02:00
cardinality : null as never ,
2021-11-16 12:27:51 +01:00
comment : '' ,
indexComment : ''
} ) ;
}
2021-11-13 23:00:53 +01:00
2022-04-14 18:25:13 +02:00
const { rows : indexes } = await this . raw < antares.QueryResult < ShowIndexesResult > > ( ` SELECT * FROM " ${ schema } ".pragma_index_list(' ${ table } '); ` ) ;
2021-11-16 12:27:51 +01:00
for ( const index of indexes ) {
const { rows : details } = await this . raw ( ` SELECT * FROM " ${ schema } ".pragma_index_info(' ${ index . name } '); ` ) ;
for ( const detail of details ) {
remappedIndexes . push ( {
name : index.name ,
column : detail.name ,
2022-04-14 18:25:13 +02:00
indexType : null as never ,
2021-11-16 12:27:51 +01:00
type : index . unique === 1 ? 'UNIQUE' : 'INDEX' ,
2022-04-14 18:25:13 +02:00
cardinality : null as never ,
2021-11-16 12:27:51 +01:00
comment : '' ,
indexComment : ''
} ) ;
}
}
return remappedIndexes ;
2021-11-13 23:00:53 +01:00
}
2022-04-14 18:25:13 +02:00
async getKeyUsage ( { schema , table } : { schema : string ; table : string } ) {
/* eslint-disable camelcase */
interface KeyResult {
from : string ;
id : number ;
table : string ;
to : string ;
on_update : string ;
on_delete : string ;
}
/* eslint-enable camelcase */
const { rows } = await this . raw < antares.QueryResult < KeyResult > > ( ` SELECT * FROM " ${ schema } ".pragma_foreign_key_list(' ${ table } '); ` ) ;
2021-11-13 23:00:53 +01:00
return rows . map ( field = > {
return {
2021-11-18 11:36:46 +01:00
schema : schema ,
table : table ,
field : field.from ,
position : field.id + 1 ,
constraintPosition : null ,
constraintName : field.id ,
refSchema : schema ,
refTable : field.table ,
refField : field.to ,
onUpdate : field.on_update ,
onDelete : field.on_delete
2021-11-13 23:00:53 +01:00
} ;
} ) ;
}
2022-04-14 18:25:13 +02:00
async getUsers ( ) : Promise < void > {
return null ;
}
async createTable ( params : antares.CreateTableParams ) {
const {
schema ,
fields ,
foreigns ,
indexes ,
options
} = params ;
const newColumns : string [ ] = [ ] ;
const newIndexes : string [ ] = [ ] ;
const manageIndexes : string [ ] = [ ] ;
const newForeigns : string [ ] = [ ] ;
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 ;
newColumns . push ( ` " ${ field . name } "
$ { field . type . toUpperCase ( ) } $ { length ? ` ( ${ length } ) ` : '' }
$ { field . unsigned ? 'UNSIGNED' : '' }
$ { field . nullable ? 'NULL' : 'NOT NULL' }
$ { field . autoIncrement ? 'AUTO_INCREMENT' : '' }
$ { 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
manageIndexes . push ( ` CREATE ${ type === 'UNIQUE' ? type : '' } INDEX " ${ index . name } " ON " ${ options . name } " ( ${ fields } ) ` ) ;
} ) ;
// ADD FOREIGN KEYS
foreigns . forEach ( foreign = > {
newForeigns . push ( ` CONSTRAINT " ${ foreign . constraintName } " FOREIGN KEY (" ${ foreign . field } ") REFERENCES " ${ foreign . refTable } " (" ${ foreign . refField } ") ON UPDATE ${ foreign . onUpdate } ON DELETE ${ foreign . onDelete } ` ) ;
} ) ;
sql = ` ${ sql } ( ${ [ . . . newColumns , . . . newIndexes , . . . newForeigns ] . join ( ', ' ) } ) ` ;
if ( manageIndexes . length ) sql = ` ${ sql } ; ${ manageIndexes . join ( ';' ) } ` ;
return await this . raw ( sql ) ;
}
async alterTable ( params : antares.AlterTableParams ) {
const {
table ,
schema ,
additions ,
deletions ,
changes ,
tableStructure
} = params ;
try {
await this . raw ( 'BEGIN TRANSACTION' ) ;
await this . raw ( 'PRAGMA foreign_keys = 0' ) ;
const tmpName = ` Antares_ ${ table } _tmp ` ;
await this . raw ( ` CREATE TABLE " ${ tmpName } " AS SELECT * FROM " ${ table } " ` ) ;
await this . dropTable ( params ) ;
const createTableParams = {
schema : schema ,
fields : tableStructure.fields ,
foreigns : tableStructure.foreigns ,
indexes : tableStructure.indexes.filter ( index = > ! index . name . includes ( 'sqlite_autoindex' ) ) ,
options : { name : tableStructure.name }
} ;
await this . createTable ( createTableParams ) ;
const insertFields = createTableParams . fields
. filter ( field = > {
return (
additions . every ( add = > add . name !== field . name ) &&
deletions . every ( del = > del . name !== field . name )
) ;
} )
. reduce ( ( acc , curr ) = > {
acc . push ( ` " ${ curr . name } " ` ) ;
return acc ;
} , [ ] ) ;
const selectFields = insertFields . map ( field = > {
const renamedField = changes . find ( change = > ` " ${ change . name } " ` === field ) ;
if ( renamedField )
return ` " ${ renamedField . orgName } " ` ;
return field ;
} ) ;
await this . raw ( ` INSERT INTO " ${ createTableParams . options . name } " ( ${ insertFields . join ( ',' ) } ) SELECT ${ selectFields . join ( ',' ) } FROM " ${ tmpName } " ` ) ;
await this . dropTable ( { schema : schema , table : tmpName } ) ;
await this . raw ( 'PRAGMA foreign_keys = 1' ) ;
await this . raw ( 'COMMIT' ) ;
}
catch ( err ) {
await this . raw ( 'ROLLBACK' ) ;
return Promise . reject ( err ) ;
}
}
async duplicateTable ( params : { schema : string ; table : string } ) { // TODO: retrive table informations and create a copy
const sql = ` CREATE TABLE " ${ params . schema } "." ${ params . table } _copy" AS SELECT * FROM " ${ params . schema } "." ${ params . table } " ` ;
return await this . raw ( sql ) ;
}
async truncateTable ( params : { schema : string ; table : string } ) {
const sql = ` DELETE FROM " ${ 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 ) ;
}
2021-11-13 23:00:53 +01:00
2022-04-14 18:25:13 +02:00
async getViewInformations ( { schema , view } : { schema : string ; view : string } ) {
2021-11-19 13:13:35 +01:00
const sql = ` SELECT "sql" FROM " ${ schema } ".sqlite_master WHERE "type"='view' AND name=' ${ view } ' ` ;
2021-11-13 23:00:53 +01:00
const results = await this . raw ( sql ) ;
return results . rows . map ( row = > {
return {
2021-11-19 13:13:35 +01:00
sql : row.sql.match ( / ( ? < = A S ) . * ? $ / g s ) [ 0 ] ,
name : view
2021-11-13 23:00:53 +01:00
} ;
} ) [ 0 ] ;
}
2022-04-14 18:25:13 +02:00
async dropView ( params : { schema : string ; view : string } ) {
2021-11-19 13:13:35 +01:00
const sql = ` DROP VIEW " ${ params . schema } "." ${ params . view } " ` ;
2021-11-13 23:00:53 +01:00
return await this . raw ( sql ) ;
}
2022-04-14 18:25:13 +02:00
async alterView ( { view } : { view : antares.AlterViewParams } ) {
2021-11-19 15:36:07 +01:00
try {
await this . dropView ( { schema : view.schema , view : view.oldName } ) ;
await this . createView ( view ) ;
}
catch ( err ) {
return Promise . reject ( err ) ;
}
2021-11-13 23:00:53 +01:00
}
2022-04-14 18:25:13 +02:00
async createView ( params : antares.CreateViewParams ) {
2021-11-19 13:13:35 +01:00
const sql = ` CREATE VIEW " ${ params . schema } "." ${ params . name } " AS ${ params . sql } ` ;
2021-11-13 23:00:53 +01:00
return await this . raw ( sql ) ;
}
2022-04-14 18:25:13 +02:00
async getTriggerInformations ( { schema , trigger } : { schema : string ; trigger : string } ) {
2021-11-19 15:36:07 +01:00
const sql = ` SELECT "sql" FROM " ${ schema } ".sqlite_master WHERE "type"='trigger' AND name=' ${ trigger } ' ` ;
2021-11-13 23:00:53 +01:00
const results = await this . raw ( sql ) ;
return results . rows . map ( row = > {
return {
2021-11-19 15:36:07 +01:00
sql : row.sql.match ( / ( B E G I N | b e g i n ) ( . * ) ( E N D | e n d ) / g s ) [ 0 ] ,
name : trigger ,
table : row.sql.match ( / ( ? < = O N ` ) . * ? ( ? = ` ) / g s ) [ 0 ] ,
activation : row.sql.match ( / ( B E F O R E | A F T E R ) / g s ) [ 0 ] ,
event : row.sql.match ( / ( I N S E R T | U P D A T E | D E L E T E ) / g s ) [ 0 ]
2021-11-13 23:00:53 +01:00
} ;
} ) [ 0 ] ;
}
2022-04-14 18:25:13 +02:00
async dropTrigger ( params : { schema : string ; trigger : string } ) {
2021-11-13 23:00:53 +01:00
const sql = ` DROP TRIGGER \` ${ params . schema } \` . \` ${ params . trigger } \` ` ;
return await this . raw ( sql ) ;
}
2022-04-14 18:25:13 +02:00
async alterTrigger ( { trigger } : { trigger : antares.AlterTriggerParams } ) {
2021-11-13 23:00:53 +01:00
const tempTrigger = Object . assign ( { } , trigger ) ;
tempTrigger . name = ` Antares_ ${ tempTrigger . name } _tmp ` ;
try {
await this . createTrigger ( tempTrigger ) ;
await this . dropTrigger ( { schema : trigger.schema , trigger : tempTrigger.name } ) ;
await this . dropTrigger ( { schema : trigger.schema , trigger : trigger.oldName } ) ;
await this . createTrigger ( trigger ) ;
}
catch ( err ) {
return Promise . reject ( err ) ;
}
}
2022-04-14 18:25:13 +02:00
async createTrigger ( params : antares.CreateTriggerParams ) {
2021-11-13 23:00:53 +01:00
const sql = ` CREATE ${ params . definer ? ` DEFINER= ${ params . definer } ` : '' } TRIGGER \` ${ params . schema } \` . \` ${ params . name } \` ${ params . activation } ${ params . event } ON \` ${ params . table } \` FOR EACH ROW ${ params . sql } ` ;
return await this . raw ( sql , { split : false } ) ;
}
2022-04-14 18:25:13 +02:00
async getCollations ( ) : Promise < undefined [ ] > {
2021-11-13 23:00:53 +01:00
return [ ] ;
}
2022-04-14 18:25:13 +02:00
async getVariables ( ) : Promise < undefined [ ] > {
2021-11-13 23:00:53 +01:00
return [ ] ;
}
async getEngines ( ) {
return {
name : 'SQLite' ,
support : 'YES' ,
comment : '' ,
isDefault : true
} ;
}
async getVersion ( ) {
const os = require ( 'os' ) ;
const sql = 'SELECT sqlite_version() AS version' ;
const { rows } = await this . raw ( sql ) ;
return {
number : rows [ 0 ] . version ,
name : 'SQLite' ,
arch : process.arch ,
os : ` ${ os . type ( ) } ${ os . release ( ) } `
} ;
}
2022-04-14 18:25:13 +02:00
async getProcesses ( ) : Promise < void > {
return null ;
}
2021-11-13 23:00:53 +01:00
2022-04-14 18:25:13 +02:00
async killProcess ( ) : Promise < void > {
return null ;
}
2021-11-13 23:00:53 +01:00
2022-04-14 18:25:13 +02:00
async commitTab ( tabUid : string ) {
2022-02-14 18:53:55 +01:00
const connection = this . _connectionsToCommit . get ( tabUid ) ;
if ( connection ) {
connection . prepare ( 'COMMIT' ) . run ( ) ;
return this . destroyConnectionToCommit ( tabUid ) ;
}
}
2022-04-14 18:25:13 +02:00
async rollbackTab ( tabUid : string ) {
2022-02-14 18:53:55 +01:00
const connection = this . _connectionsToCommit . get ( tabUid ) ;
if ( connection ) {
connection . prepare ( 'ROLLBACK' ) . run ( ) ;
return this . destroyConnectionToCommit ( tabUid ) ;
}
}
2022-04-14 18:25:13 +02:00
destroyConnectionToCommit ( tabUid : string ) {
2022-02-14 18:53:55 +01:00
const connection = this . _connectionsToCommit . get ( tabUid ) ;
if ( connection ) {
connection . close ( ) ;
this . _connectionsToCommit . delete ( tabUid ) ;
}
}
2021-11-13 23:00:53 +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-11-15 18:09:34 +01:00
fromRaw += this . _query . from ? ` ${ this . _query . schema ? ` " ${ this . _query . schema } ". ` : '' } " ${ this . _query . from } " ` : '' ;
2021-11-13 23:00:53 +01:00
// WHERE
2021-11-15 18:09:34 +01:00
const whereArray = this . _query . where
. reduce ( this . _reducer , [ ] )
? . map ( clausole = > clausole . replace ( '= null' , 'IS NULL' ) ) ;
2021-11-13 23:00:53 +01:00
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 ] ) ;
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
const limitRaw = this . _query . limit . length ? ` LIMIT ${ this . _query . limit . join ( ', ' ) } ` : '' ;
// OFFSET
const offsetRaw = this . _query . offset . length ? ` OFFSET ${ this . _query . offset . join ( ', ' ) } ` : '' ;
return ` ${ selectRaw } ${ updateRaw ? 'UPDATE' : '' } ${ insertRaw ? 'INSERT ' : '' } ${ this . _query . delete ? 'DELETE ' : '' } ${ fromRaw } ${ updateRaw } ${ whereRaw } ${ groupByRaw } ${ orderByRaw } ${ limitRaw } ${ offsetRaw } ${ insertRaw } ` ;
}
2022-04-14 18:25:13 +02:00
async raw < T = antares.QueryResult > ( sql : string , args? : antares.QueryParams ) {
2021-11-13 23:00:53 +01:00
if ( process . env . NODE_ENV === 'development' ) this . _logger ( sql ) ; // TODO: replace BLOB content with a placeholder
args = {
nest : false ,
details : false ,
split : true ,
comments : true ,
2022-02-14 18:53:55 +01:00
autocommit : true ,
2021-11-13 23:00:53 +01:00
. . . args
} ;
if ( ! args . comments )
sql = sql . replace ( /(\/\*(.|[\r\n])*?\*\/)|(--(.*|[\r\n]))/gm , '' ) ; // Remove comments
const resultsArr = [ ] ;
2021-11-16 12:27:51 +01:00
let paramsArr = [ ] ;
2021-11-13 23:00:53 +01:00
const queries = args . split
? sql . split ( /((?:[^;'"]*(?:"(?:\\.|[^"])*"|'(?:\\.|[^'])*')[^;'"]*)+)|;/gm )
. filter ( Boolean )
. map ( q = > q . trim ( ) )
: [ sql ] ;
2022-02-14 18:53:55 +01:00
2022-04-14 18:25:13 +02:00
let connection : sqlite.Database ;
2022-02-14 18:53:55 +01:00
if ( ! args . autocommit && args . tabUid ) { // autocommit OFF
if ( this . _connectionsToCommit . has ( args . tabUid ) )
connection = this . _connectionsToCommit . get ( args . tabUid ) ;
else {
connection = this . getConnection ( ) ;
connection . prepare ( 'BEGIN TRANSACTION' ) . run ( ) ;
this . _connectionsToCommit . set ( args . tabUid , connection ) ;
}
}
else // autocommit ON
connection = this . _connection ;
2021-11-13 23:00:53 +01:00
for ( const query of queries ) {
if ( ! query ) continue ;
const timeStart = new Date ( ) ;
let timeStop ;
2022-04-14 18:25:13 +02:00
const keysArr : antares.QueryForeign [ ] = [ ] ;
2021-11-13 23:00:53 +01:00
const { rows , report , fields , keys , duration } = await new Promise ( ( resolve , reject ) = > {
2021-11-16 12:27:51 +01:00
( async ( ) = > {
2022-04-14 18:25:13 +02:00
let queryRunResult : sqlite.RunResult ;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let queryAllResult : any [ ] ;
2021-11-16 12:27:51 +01:00
let affectedRows ;
let fields ;
2022-04-14 18:25:13 +02:00
const detectedTypes : { [ key : string ] : string } = { } ;
2021-11-16 12:27:51 +01:00
2021-11-16 12:56:03 +01:00
try {
const stmt = connection . prepare ( query ) ;
if ( stmt . reader ) {
2022-04-14 18:25:13 +02:00
queryAllResult = stmt . all ( ) ;
2021-11-16 12:56:03 +01:00
fields = stmt . columns ( ) ;
2022-04-14 18:25:13 +02:00
if ( queryAllResult . length ) {
2021-11-16 12:56:03 +01:00
fields . forEach ( field = > {
2022-04-14 18:25:13 +02:00
detectedTypes [ field . name ] = typeof queryAllResult [ 0 ] [ field . name ] ;
2021-11-16 12:56:03 +01:00
} ) ;
}
}
else {
2022-04-14 18:25:13 +02:00
queryRunResult = stmt . run ( ) ;
affectedRows = queryRunResult . changes ;
2021-11-16 12:27:51 +01:00
}
}
2021-11-16 12:56:03 +01:00
catch ( err ) {
reject ( err ) ;
2021-11-15 18:09:34 +01:00
}
2021-11-13 23:00:53 +01:00
2021-11-16 12:27:51 +01:00
timeStop = new Date ( ) ;
let remappedFields = fields
? fields . map ( field = > {
2022-04-14 18:25:13 +02:00
let [ parsedType , length ] : [ string , number ? ] = field . type ? . includes ( '(' )
? field . type . replace ( ')' , '' ) . split ( '(' ) . map ( ( el : string | number ) = > {
if ( ! isNaN ( Number ( el ) ) )
el = Number ( el ) ;
2021-11-18 19:43:08 +01:00
else
2022-04-14 18:25:13 +02:00
el = ( el as string ) . trim ( ) ;
2021-11-18 19:43:08 +01:00
return el ;
2022-04-14 18:25:13 +02:00
} ) as [ string , number ? ]
2021-11-18 19:43:08 +01:00
: [ field . type , null ] ;
if ( [ . . . TIME , . . . DATETIME ] . includes ( parsedType ) ) {
2022-04-14 18:25:13 +02:00
const firstNotNull = queryAllResult . find ( res = > res [ field . name ] !== null ) ;
2021-12-16 09:16:15 +01:00
if ( firstNotNull && firstNotNull [ field . name ] . includes ( '.' ) )
2021-11-18 19:43:08 +01:00
length = firstNotNull [ field . name ] . split ( '.' ) . pop ( ) . length ;
}
2021-11-18 11:36:46 +01:00
2021-11-16 12:27:51 +01:00
return {
name : field.name ,
alias : field.name ,
orgName : field.column ,
schema : field.database ,
table : field.table ,
tableAlias : field.table ,
orgTable : field.table ,
2021-11-18 19:43:08 +01:00
type : field . type !== null ? parsedType : detectedTypes [ field . name ] ,
2022-04-14 18:25:13 +02:00
length ,
key : undefined as string
2021-11-16 12:27:51 +01:00
} ;
} ) . filter ( Boolean )
: [ ] ;
if ( args . details ) {
paramsArr = remappedFields . map ( field = > {
return {
table : field.table ,
schema : field.schema
} ;
} ) . 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 {
const indexes = await this . getTableIndexes ( paramObj ) ;
remappedFields = remappedFields . map ( field = > {
const fieldIndex = indexes . find ( i = > i . column === field . name ) ;
if ( field . table === paramObj . table && field . schema === paramObj . schema ) {
if ( fieldIndex ) {
const key = fieldIndex . type === 'PRIMARY' ? 'pri' : fieldIndex . type === 'UNIQUE' ? 'uni' : 'mul' ;
field = { . . . field , key } ;
2022-04-14 18:25:13 +02:00
}
2021-11-16 12:27:51 +01:00
}
return field ;
} ) ;
}
catch ( err ) {
reject ( err ) ;
}
}
}
2021-11-13 23:00:53 +01:00
2021-11-16 12:27:51 +01:00
resolve ( {
2022-04-14 18:25:13 +02:00
duration : timeStop.getTime ( ) - timeStart . getTime ( ) ,
rows : Array.isArray ( queryAllResult ) ? queryAllResult . some ( el = > Array . isArray ( el ) ) ? [ ] : queryAllResult : false ,
2021-11-16 12:27:51 +01:00
report : affectedRows !== undefined ? { affectedRows } : null ,
fields : remappedFields ,
keys : keysArr
} ) ;
} ) ( ) ;
2021-11-13 23:00:53 +01:00
} ) ;
resultsArr . push ( { rows , report , fields , keys , duration } ) ;
}
2022-04-14 18:25:13 +02:00
const result = resultsArr . length === 1 ? resultsArr [ 0 ] : resultsArr ;
return result as unknown as T ;
2021-11-13 23:00:53 +01:00
}
}