2020-04-22 15:52:17 +02:00
import { Connection } from "mysql" ;
2021-05-03 19:29:22 +02:00
2021-06-01 16:04:43 +02:00
import { Pagination } from "../common/Pagination.js" ;
2021-05-03 19:29:22 +02:00
import Model from "./Model.js" ;
import ModelFactory from "./ModelFactory.js" ;
import ModelRelation , { RelationDatabaseProperties } from "./ModelRelation.js" ;
import { isQueryVariable , query , QueryResult , QueryVariable } from "./MysqlConnectionManager.js" ;
2020-04-22 15:52:17 +02:00
2020-09-04 22:15:55 +02:00
export default class ModelQuery < M extends Model > implements WhereFieldConsumer < M > {
2020-10-02 12:06:18 +02:00
public static select < M extends Model > ( factory : ModelFactory < M > , . . . fields : QueryFields ) : ModelQuery < M > {
2020-09-05 15:33:51 +02:00
fields = fields . map ( v = > v === '' ? new SelectFieldValue ( 'none' , 1 , true ) : v ) ;
2020-07-24 12:13:28 +02:00
return new ModelQuery ( QueryType . SELECT , factory , fields . length > 0 ? fields : [ '*' ] ) ;
2020-04-22 15:52:17 +02:00
}
2020-10-02 12:06:18 +02:00
public static insert < M extends Model > ( factory : ModelFactory < M > , data : Pick < M , keyof M > ) : ModelQuery < M > {
2020-04-22 15:52:17 +02:00
const fields = [ ] ;
2020-09-25 23:42:15 +02:00
for ( const key of Object . keys ( data ) ) {
2020-10-02 12:06:18 +02:00
fields . push ( new FieldValue ( key , data [ key ] , false ) ) ;
}
return new ModelQuery ( QueryType . INSERT , factory , fields ) ;
}
public static update < M extends Model > ( factory : ModelFactory < M > , data : Pick < M , keyof M > ) : ModelQuery < M > {
const fields = [ ] ;
for ( const key of Object . keys ( data ) ) {
fields . push ( new FieldValue ( inputToFieldOrValue ( key , factory . table ) , data [ key ] , false ) ) ;
2020-04-22 15:52:17 +02:00
}
2020-07-24 12:13:28 +02:00
return new ModelQuery ( QueryType . UPDATE , factory , fields ) ;
2020-04-22 15:52:17 +02:00
}
2020-07-24 12:13:28 +02:00
public static delete < M extends Model > ( factory : ModelFactory < M > ) : ModelQuery < M > {
return new ModelQuery ( QueryType . DELETE , factory ) ;
2020-04-22 15:52:17 +02:00
}
private readonly type : QueryType ;
2020-07-24 12:13:28 +02:00
private readonly factory : ModelFactory < M > ;
2020-04-22 15:52:17 +02:00
private readonly table : string ;
2020-10-02 12:06:18 +02:00
private readonly fields : QueryFields ;
2020-06-14 11:43:31 +02:00
private _leftJoin? : string ;
2020-09-05 15:33:51 +02:00
private _leftJoinAlias? : string ;
2020-06-14 11:43:31 +02:00
private _leftJoinOn : WhereFieldValue [ ] = [ ] ;
2020-09-04 22:15:55 +02:00
private _where : ( WhereFieldValue | WhereFieldValueGroup ) [ ] = [ ] ;
2020-04-22 15:52:17 +02:00
private _limit? : number ;
private _offset? : number ;
private _sortBy? : string ;
private _sortDirection ? : 'ASC' | 'DESC' ;
2020-06-27 14:36:50 +02:00
private readonly relations : string [ ] = [ ] ;
2020-09-25 23:42:15 +02:00
private readonly subRelations : { [ relation : string ] : string [ ] | undefined } = { } ;
2020-06-27 14:36:50 +02:00
private _pivot? : string [ ] ;
2020-09-06 15:06:40 +02:00
private _union? : ModelQueryUnion ;
2020-09-05 14:55:19 +02:00
private _recursiveRelation? : RelationDatabaseProperties ;
2020-09-08 18:12:39 +02:00
private _reverseRecursiveRelation? : boolean ;
2020-04-22 15:52:17 +02:00
2020-10-02 12:06:18 +02:00
private constructor ( type : QueryType , factory : ModelFactory < M > , fields? : QueryFields ) {
2020-04-22 15:52:17 +02:00
this . type = type ;
2020-07-24 12:13:28 +02:00
this . factory = factory ;
this . table = factory . table ;
2020-04-22 15:52:17 +02:00
this . fields = fields || [ ] ;
}
2020-09-05 15:33:51 +02:00
public leftJoin ( table : string , alias? : string ) : this {
2020-06-14 11:43:31 +02:00
this . _leftJoin = table ;
2020-09-05 15:33:51 +02:00
this . _leftJoinAlias = alias ;
2020-04-22 15:52:17 +02:00
return this ;
}
2020-09-25 23:42:15 +02:00
public on (
field1 : string ,
field2 : string ,
test : WhereTest = WhereTest . EQ ,
operator : WhereOperator = WhereOperator . AND ,
) : this {
this . _leftJoinOn . push ( new WhereFieldValue (
inputToFieldOrValue ( field1 ) , inputToFieldOrValue ( field2 ) , true , test , operator ,
) ) ;
2020-06-14 11:43:31 +02:00
return this ;
2020-04-22 15:52:17 +02:00
}
2020-09-25 23:42:15 +02:00
public where (
field : string ,
value : ModelFieldData ,
test : WhereTest = WhereTest . EQ ,
operator : WhereOperator = WhereOperator . AND ,
) : this {
2020-06-14 11:43:31 +02:00
this . _where . push ( new WhereFieldValue ( field , value , false , test , operator ) ) ;
return this ;
2020-04-22 15:52:17 +02:00
}
2020-09-25 23:42:15 +02:00
public groupWhere (
setter : ( query : WhereFieldConsumer < M > ) = > void ,
operator : WhereOperator = WhereOperator . AND ,
) : this {
2020-09-04 22:15:55 +02:00
this . _where . push ( new WhereFieldValueGroup ( this . collectWheres ( setter ) , operator ) ) ;
return this ;
}
private collectWheres ( setter : ( query : WhereFieldConsumer < M > ) = > void ) : ( WhereFieldValue | WhereFieldValueGroup ) [ ] {
2020-09-25 23:42:15 +02:00
// eslint-disable-next-line @typescript-eslint/no-this-alias
2020-09-04 22:15:55 +02:00
const query = this ;
const wheres : ( WhereFieldValue | WhereFieldValueGroup ) [ ] = [ ] ;
setter ( {
2020-09-25 23:42:15 +02:00
where (
field : string ,
value : ModelFieldData ,
test : WhereTest = WhereTest . EQ ,
operator : WhereOperator = WhereOperator . AND ,
) {
2020-09-04 22:15:55 +02:00
wheres . push ( new WhereFieldValue ( field , value , false , test , operator ) ) ;
return this ;
} ,
2020-09-25 23:42:15 +02:00
groupWhere (
setter : ( query : WhereFieldConsumer < M > ) = > void ,
operator : WhereOperator = WhereOperator . AND ,
) {
wheres . push ( new WhereFieldValueGroup ( query . collectWheres ( setter ) , operator ) ) ;
2020-09-04 22:15:55 +02:00
return this ;
2020-09-25 23:42:15 +02:00
} ,
2020-09-04 22:15:55 +02:00
} ) ;
return wheres ;
}
2020-06-14 11:43:31 +02:00
public limit ( limit : number , offset : number = 0 ) : this {
2020-04-22 15:52:17 +02:00
this . _limit = limit ;
this . _offset = offset ;
return this ;
}
2020-09-06 15:06:40 +02:00
public sortBy ( field : string , direction : SortDirection = 'ASC' , raw : boolean = false ) : this {
2020-09-06 15:38:54 +02:00
this . _sortBy = raw ? field : inputToFieldOrValue ( field ) ;
2020-04-22 15:52:17 +02:00
this . _sortDirection = direction ;
return this ;
}
2020-06-27 14:36:50 +02:00
/ * *
2020-09-07 13:43:02 +02:00
* @param relations The relations field names to eagerload . To load nested relations , separate fields with '.'
* ( i . e . : "author.roles.permissions" loads authors , their roles , and the permissions of these roles )
2020-06-27 14:36:50 +02:00
* /
2020-09-07 13:43:02 +02:00
public with ( . . . relations : string [ ] ) : this {
relations . forEach ( relation = > {
const parts = relation . split ( '.' ) ;
2020-09-08 18:12:39 +02:00
if ( this . relations . indexOf ( parts [ 0 ] ) < 0 ) this . relations . push ( parts [ 0 ] ) ;
2020-09-07 13:43:02 +02:00
if ( parts . length > 1 ) {
if ( ! this . subRelations [ parts [ 0 ] ] ) this . subRelations [ parts [ 0 ] ] = [ ] ;
2020-09-25 23:42:15 +02:00
this . subRelations [ parts [ 0 ] ] ? . push ( parts . slice ( 1 ) . join ( '.' ) ) ;
2020-09-07 13:43:02 +02:00
}
} ) ;
2020-06-27 14:36:50 +02:00
return this ;
}
public pivot ( . . . fields : string [ ] ) : this {
this . _pivot = fields ;
2020-04-22 15:52:17 +02:00
return this ;
}
2020-09-25 23:42:15 +02:00
public union (
query : ModelQuery < Model > ,
sortBy : string , direction : SortDirection = 'ASC' ,
raw : boolean = false ,
limit? : number ,
offset? : number ,
) : this {
2020-09-06 15:06:40 +02:00
if ( this . type !== QueryType . SELECT ) throw new Error ( 'Union queries are only implemented with SELECT.' ) ;
this . _union = {
query : query ,
2020-09-06 15:38:54 +02:00
sortBy : raw ? sortBy : inputToFieldOrValue ( sortBy ) ,
2020-09-06 15:06:40 +02:00
direction : direction ,
limit : limit ,
2020-09-11 15:15:15 +02:00
offset : offset ,
2020-09-06 15:06:40 +02:00
} ;
return this ;
}
2020-09-08 18:12:39 +02:00
public recursive ( relation : RelationDatabaseProperties , reverse : boolean ) : this {
2020-09-05 14:55:19 +02:00
if ( this . type !== QueryType . SELECT ) throw new Error ( 'Recursive queries are only implemented with SELECT.' ) ;
this . _recursiveRelation = relation ;
2020-09-08 18:12:39 +02:00
this . _reverseRecursiveRelation = reverse ;
2020-09-05 14:55:19 +02:00
return this ;
}
2020-04-22 15:52:17 +02:00
public toString ( final : boolean = false ) : string {
let query = '' ;
2020-09-05 15:33:51 +02:00
if ( this . _pivot ) this . fields . push ( . . . this . _pivot ) ;
2020-09-02 16:29:02 +02:00
// Prevent wildcard and fields from conflicting
2020-09-25 23:42:15 +02:00
const fields = this . fields . map ( f = > {
2020-09-06 15:38:54 +02:00
const field = f . toString ( ) ;
2020-09-05 15:33:51 +02:00
if ( field . startsWith ( '(' ) ) return f ; // Skip sub-queries
2020-09-06 15:38:54 +02:00
return inputToFieldOrValue ( field , this . table ) ;
} ) . join ( ',' ) ;
2020-04-22 15:52:17 +02:00
2020-06-14 11:43:31 +02:00
let join = '' ;
if ( this . _leftJoin ) {
2020-09-05 16:09:30 +02:00
join = ` LEFT JOIN \` ${ this . _leftJoin } \` `
+ ( this . _leftJoinAlias ? ` AS \` ${ this . _leftJoinAlias } \` ` : '' )
+ ` ON ${ this . _leftJoinOn [ 0 ] } ` ;
2020-06-14 11:43:31 +02:00
for ( let i = 1 ; i < this . _leftJoinOn . length ; i ++ ) {
join += this . _leftJoinOn [ i ] . toString ( false ) ;
}
}
2020-04-22 15:52:17 +02:00
let where = '' ;
if ( this . _where . length > 0 ) {
2020-09-05 16:09:30 +02:00
where = ` WHERE ${ this . _where [ 0 ] } ` ;
2020-04-22 15:52:17 +02:00
for ( let i = 1 ; i < this . _where . length ; i ++ ) {
where += this . _where [ i ] . toString ( false ) ;
}
}
let limit = '' ;
if ( typeof this . _limit === 'number' ) {
2020-09-05 16:09:30 +02:00
limit = ` LIMIT ${ this . _limit } ` ;
2020-04-22 15:52:17 +02:00
if ( typeof this . _offset === 'number' && this . _offset !== 0 ) {
limit += ` OFFSET ${ this . _offset } ` ;
}
}
let orderBy = '' ;
if ( typeof this . _sortBy === 'string' ) {
2020-09-25 23:42:15 +02:00
orderBy = ` ORDER BY ${ this . _sortBy } ${ this . _sortDirection } ` ;
2020-04-22 15:52:17 +02:00
}
2020-09-05 14:55:19 +02:00
const table = ` \` ${ this . table } \` ` ;
2020-04-22 15:52:17 +02:00
switch ( this . type ) {
case QueryType . SELECT :
2020-09-05 14:55:19 +02:00
if ( this . _recursiveRelation ) {
const cteFields = fields . replace ( RegExp ( ` ${ table } ` , 'g' ) , 'o' ) ;
2020-09-25 23:42:15 +02:00
const idKey = this . _reverseRecursiveRelation ?
this . _recursiveRelation . foreignKey :
this . _recursiveRelation . localKey ;
2020-09-08 18:12:39 +02:00
const sortOrder = this . _reverseRecursiveRelation ? 'DESC' : 'ASC' ;
2020-09-05 14:55:19 +02:00
query = ` WITH RECURSIVE cte AS ( `
2020-09-08 18:12:39 +02:00
+ ` SELECT ${ fields } ,1 AS __depth, CONCAT( \` ${ idKey } \` ) AS __path FROM ${ table } ${ where } `
2020-09-05 14:55:19 +02:00
+ ` UNION `
2020-09-08 18:12:39 +02:00
+ ` SELECT ${ cteFields } ,c.__depth + 1,CONCAT(c.__path,'/',o. \` ${ idKey } \` ) AS __path FROM ${ table } AS o, cte AS c WHERE o. \` ${ this . _recursiveRelation . foreignKey } \` =c. \` ${ this . _recursiveRelation . localKey } \` `
+ ` ) SELECT * FROM cte ${ join } ${ orderBy || ` ORDER BY __path ${ sortOrder } ` } ${ limit } ` ;
2020-09-05 14:55:19 +02:00
} else {
2020-09-05 16:09:30 +02:00
query = ` SELECT ${ fields } FROM ${ table } ${ join } ${ where } ${ orderBy } ${ limit } ` ;
2020-09-05 14:55:19 +02:00
}
2020-09-06 15:06:40 +02:00
if ( this . _union ) {
const unionOrderBy = this . _union . sortBy ? ` ORDER BY ${ this . _union . sortBy } ${ this . _union . direction } ` : '' ;
const unionLimit = typeof this . _union . limit === 'number' ? ` LIMIT ${ this . _union . limit } ` : '' ;
2020-09-11 15:15:15 +02:00
const unionOffset = typeof this . _union . offset === 'number' ? ` OFFSET ${ this . _union . offset } ` : '' ;
query = ` ( ${ query } ) UNION ${ this . _union . query . toString ( false ) } ${ unionOrderBy } ${ unionLimit } ${ unionOffset } ` ;
2020-09-06 15:06:40 +02:00
}
2020-04-22 15:52:17 +02:00
break ;
2020-10-02 12:06:18 +02:00
case QueryType . INSERT : {
const insertFields = this . fields . filter ( f = > f instanceof FieldValue )
. map ( f = > f as FieldValue ) ;
const insertFieldNames = insertFields . map ( f = > f . fieldName ) . join ( ',' ) ;
const insertFieldValues = insertFields . map ( f = > f . fieldValue ) . join ( ',' ) ;
query = ` INSERT INTO ${ table } ( ${ insertFieldNames } ) VALUES( ${ insertFieldValues } ) ` ;
break ;
}
2020-04-22 15:52:17 +02:00
case QueryType . UPDATE :
2020-09-05 16:09:30 +02:00
query = ` UPDATE ${ table } SET ${ fields } ${ where } ${ orderBy } ${ limit } ` ;
2020-04-22 15:52:17 +02:00
break ;
case QueryType . DELETE :
2020-09-05 16:09:30 +02:00
query = ` DELETE FROM ${ table } ${ where } ${ orderBy } ${ limit } ` ;
2020-04-22 15:52:17 +02:00
break ;
}
return final ? query : ` ( ${ query } ) ` ;
}
public build ( ) : string {
return this . toString ( true ) ;
}
2020-09-25 23:42:15 +02:00
public get variables ( ) : QueryVariable [ ] {
const variables : QueryVariable [ ] = [ ] ;
this . fields . filter ( v = > v instanceof FieldValue )
. flatMap ( v = > ( v as FieldValue ) . variables )
2020-04-22 15:52:17 +02:00
. forEach ( v = > variables . push ( v ) ) ;
2020-09-04 22:15:55 +02:00
this . _where . flatMap ( v = > this . getVariables ( v ) )
2020-04-22 15:52:17 +02:00
. forEach ( v = > variables . push ( v ) ) ;
2020-09-06 16:02:53 +02:00
this . _union ? . query . variables . forEach ( v = > variables . push ( v ) ) ;
2020-04-22 15:52:17 +02:00
return variables ;
}
2020-09-25 23:42:15 +02:00
private getVariables ( where : WhereFieldValue | WhereFieldValueGroup ) : QueryVariable [ ] {
2020-09-04 22:15:55 +02:00
return where instanceof WhereFieldValueGroup ?
where . fields . flatMap ( v = > this . getVariables ( v ) ) :
where . variables ;
}
2020-06-27 14:36:50 +02:00
public async execute ( connection? : Connection ) : Promise < QueryResult > {
return await query ( this . build ( ) , this . variables , connection ) ;
2020-04-22 15:52:17 +02:00
}
2020-06-27 14:36:50 +02:00
public async get ( connection? : Connection ) : Promise < ModelQueryResult < M > > {
2020-09-04 15:10:11 +02:00
const queryResult = await this . execute ( connection ) ;
2020-06-27 14:36:50 +02:00
const models : ModelQueryResult < M > = [ ] ;
2020-09-04 22:44:30 +02:00
models . originalData = [ ] ;
2020-06-27 14:36:50 +02:00
if ( this . _pivot ) models . pivot = [ ] ;
// Eager loading init
2020-09-25 23:42:15 +02:00
const relationMap : { [ p : string ] : ModelRelation < Model , Model , Model | Model [ ] | null > [ ] } = { } ;
2020-06-27 14:36:50 +02:00
for ( const relation of this . relations ) {
relationMap [ relation ] = [ ] ;
}
for ( const result of queryResult . results ) {
2020-09-25 23:42:15 +02:00
const modelData : Record < string , unknown > = { } ;
2020-09-02 11:55:38 +02:00
for ( const field of Object . keys ( result ) ) {
modelData [ field . split ( '.' ) [ 1 ] || field ] = result [ field ] ;
}
2020-09-25 23:42:15 +02:00
const model = this . factory . create ( modelData as Pick < M , keyof M > , false ) ;
2020-06-27 14:36:50 +02:00
models . push ( model ) ;
2020-09-04 22:44:30 +02:00
models . originalData . push ( modelData ) ;
2020-06-27 14:36:50 +02:00
2020-09-25 23:42:15 +02:00
if ( this . _pivot && models . pivot ) {
const pivotData : Record < string , unknown > = { } ;
2020-06-27 14:36:50 +02:00
for ( const field of this . _pivot ) {
2020-09-02 11:55:38 +02:00
pivotData [ field ] = result [ field . split ( '.' ) [ 1 ] ] ;
2020-06-27 14:36:50 +02:00
}
2020-09-25 23:42:15 +02:00
models . pivot . push ( pivotData ) ;
2020-06-27 14:36:50 +02:00
}
// Eager loading init map
for ( const relation of this . relations ) {
2020-07-28 10:33:17 +02:00
if ( model [ relation ] === undefined ) throw new Error ( ` Relation ${ relation } doesn't exist on ${ model . constructor . name } . ` ) ;
2020-09-25 23:42:15 +02:00
if ( ! ( model [ relation ] instanceof ModelRelation ) ) throw new Error ( ` Field ${ relation } is not a relation on ${ model . constructor . name } . ` ) ;
relationMap [ relation ] . push ( model [ relation ] as ModelRelation < Model , Model , Model | Model [ ] | null > ) ;
2020-06-27 14:36:50 +02:00
}
}
// Eager loading execute
for ( const relationName of this . relations ) {
const relations = relationMap [ relationName ] ;
2020-06-27 17:11:31 +02:00
if ( relations . length > 0 ) {
2020-09-07 13:43:02 +02:00
const allModels = await relations [ 0 ] . eagerLoad ( relations , this . subRelations [ relationName ] ) ;
2020-06-27 17:11:31 +02:00
await Promise . all ( relations . map ( r = > r . populate ( allModels ) ) ) ;
}
2020-06-27 14:36:50 +02:00
}
return models ;
}
public async paginate ( page : number , perPage : number , connection? : Connection ) : Promise < ModelQueryResult < M > > {
this . limit ( perPage , ( page - 1 ) * perPage ) ;
const result = await this . get ( connection ) ;
2021-03-30 10:20:38 +02:00
result . pagination = new Pagination ( page , perPage , await this . count ( true , connection ) ) ;
2020-06-27 14:36:50 +02:00
return result ;
}
public async first ( ) : Promise < M | null > {
const models = await this . limit ( 1 ) . get ( ) ;
return models . length > 0 ? models [ 0 ] : null ;
}
public async count ( removeLimit : boolean = false , connection? : Connection ) : Promise < number > {
if ( removeLimit ) {
this . _limit = undefined ;
this . _offset = undefined ;
2020-04-22 15:52:17 +02:00
}
2020-06-27 14:36:50 +02:00
this . _sortBy = undefined ;
this . _sortDirection = undefined ;
2020-08-26 14:04:23 +02:00
this . fields . splice ( 0 , this . fields . length ) ;
2020-09-04 18:51:41 +02:00
this . fields . push ( new SelectFieldValue ( '_count' , 'COUNT(*)' , true ) ) ;
2020-09-25 23:42:15 +02:00
const queryResult = await this . execute ( connection ) ;
return Number ( queryResult . results [ 0 ] [ '_count' ] ) ;
2020-04-22 15:52:17 +02:00
}
}
2020-09-06 15:38:54 +02:00
function inputToFieldOrValue ( input : string , addTable? : string ) : string {
if ( input . startsWith ( '`' ) || input . startsWith ( '"' ) || input . startsWith ( "'" ) ) {
return input ;
}
let parts = input . split ( '.' ) ;
if ( addTable && parts . length === 1 ) parts = [ addTable , input ] ; // Add table disambiguation
return parts . map ( v = > v === '*' ? v : ` \` ${ v } \` ` ) . join ( '.' ) ;
}
2020-06-27 14:36:50 +02:00
export interface ModelQueryResult < M extends Model > extends Array < M > {
2020-09-25 23:42:15 +02:00
originalData? : Record < string , unknown > [ ] ;
2021-03-30 10:20:38 +02:00
pagination? : Pagination ;
2020-09-25 23:42:15 +02:00
pivot? : Record < string , unknown > [ ] ;
2020-06-27 14:36:50 +02:00
}
2020-04-22 15:52:17 +02:00
export enum QueryType {
SELECT ,
2020-10-02 12:06:18 +02:00
INSERT ,
2020-04-22 15:52:17 +02:00
UPDATE ,
DELETE ,
}
2020-06-14 11:43:31 +02:00
export enum WhereOperator {
2020-04-22 15:52:17 +02:00
AND = 'AND' ,
OR = 'OR' ,
}
2020-06-14 11:43:31 +02:00
export enum WhereTest {
EQ = '=' ,
NE = '!=' ,
GT = '>' ,
GE = '>=' ,
LT = '<' ,
LE = '<=' ,
2020-04-22 15:52:17 +02:00
IN = ' IN ' ,
}
class FieldValue {
protected readonly field : string ;
2020-09-25 23:42:15 +02:00
protected value : ModelFieldData ;
2020-06-14 11:43:31 +02:00
protected raw : boolean ;
2020-04-22 15:52:17 +02:00
2020-09-25 23:42:15 +02:00
public constructor ( field : string , value : ModelFieldData , raw : boolean ) {
2020-04-22 15:52:17 +02:00
this . field = field ;
this . value = value ;
2020-06-14 11:43:31 +02:00
this . raw = raw ;
2020-04-22 15:52:17 +02:00
}
public toString ( first : boolean = true ) : string {
2020-10-02 12:06:18 +02:00
return ` ${ first ? '' : ',' } ${ this . fieldName } ${ this . test } ${ this . fieldValue } ` ;
2020-04-22 15:52:17 +02:00
}
protected get test ( ) : string {
return '=' ;
}
2020-09-25 23:42:15 +02:00
public get variables ( ) : QueryVariable [ ] {
2020-07-28 15:01:59 +02:00
if ( this . value instanceof ModelQuery ) return this . value . variables ;
2020-10-02 12:06:18 +02:00
if ( this . raw || this . value === null || this . value === undefined ||
typeof this . value === 'boolean' ) return [ ] ;
2020-09-25 23:42:15 +02:00
if ( Array . isArray ( this . value ) ) return this . value . map ( value = > {
if ( ! isQueryVariable ( value ) ) value = value . toString ( ) ;
return value ;
} ) as QueryVariable [ ] ;
let value = this . value ;
if ( ! isQueryVariable ( value ) ) value = value . toString ( ) ;
return [ value as QueryVariable ] ;
2020-04-22 15:52:17 +02:00
}
2020-10-02 12:06:18 +02:00
public get fieldName ( ) : string {
return inputToFieldOrValue ( this . field ) ;
}
public get fieldValue ( ) : ModelFieldData {
let value : string ;
if ( this . value instanceof ModelQuery ) {
value = this . value . toString ( false ) ;
} else if ( this . value === null || this . value === undefined ) {
value = 'null' ;
} else if ( typeof this . value === 'boolean' ) {
value = String ( this . value ) ;
} else if ( this . raw ) {
value = this . value . toString ( ) ;
} else {
value = Array . isArray ( this . value ) ?
` ( ${ '?' . repeat ( this . value . length ) . split ( '' ) . join ( ',' ) } ) ` :
'?' ;
}
return value ;
}
2020-04-22 15:52:17 +02:00
}
2020-09-05 15:33:51 +02:00
export class SelectFieldValue extends FieldValue {
2020-09-25 23:42:15 +02:00
public toString ( ) : string {
let value : string ;
if ( this . value instanceof ModelQuery ) {
value = this . value . toString ( true ) ;
} else if ( this . value === null || this . value === undefined ) {
value = 'null' ;
2020-10-02 12:06:18 +02:00
} else if ( typeof this . value === 'boolean' ) {
value = String ( this . value ) ;
2020-09-25 23:42:15 +02:00
} else {
value = this . raw ?
this . value . toString ( ) :
'?' ;
}
return ` ( ${ value } ) AS \` ${ this . field } \` ` ;
2020-04-22 15:52:17 +02:00
}
}
class WhereFieldValue extends FieldValue {
private readonly _test : WhereTest ;
2020-06-14 11:43:31 +02:00
private readonly operator : WhereOperator ;
2020-04-22 15:52:17 +02:00
2020-09-25 23:42:15 +02:00
public constructor ( field : string , value : ModelFieldData , raw : boolean , test : WhereTest , operator : WhereOperator ) {
2020-06-14 11:43:31 +02:00
super ( field , value , raw ) ;
2020-04-22 15:52:17 +02:00
this . _test = test ;
2020-06-14 11:43:31 +02:00
this . operator = operator ;
2020-04-22 15:52:17 +02:00
}
public toString ( first : boolean = true ) : string {
return ( ! first ? ` ${ this . operator } ` : '' ) + super . toString ( true ) ;
}
protected get test ( ) : string {
2020-10-02 12:06:18 +02:00
if ( this . value === null || this . value === undefined ) {
2020-07-27 10:56:43 +02:00
if ( this . _test === WhereTest . EQ ) {
return ' IS ' ;
} else if ( this . _test === WhereTest . NE ) {
return ' IS NOT ' ;
}
}
2020-04-22 15:52:17 +02:00
return this . _test ;
}
2020-09-04 22:15:55 +02:00
}
class WhereFieldValueGroup {
public readonly fields : ( WhereFieldValue | WhereFieldValueGroup ) [ ] ;
public readonly operator : WhereOperator ;
public constructor ( fields : ( WhereFieldValue | WhereFieldValueGroup ) [ ] , operator : WhereOperator ) {
this . fields = fields ;
this . operator = operator ;
}
public toString ( first : boolean = true ) : string {
let str = ` ${ first ? '' : ` ${ this . operator } ` } ( ` ;
let firstField = true ;
for ( const field of this . fields ) {
str += field . toString ( firstField ) ;
firstField = false ;
}
str += ')' ;
return str ;
}
}
2020-09-11 15:15:15 +02:00
export interface WhereFieldConsumer < M extends Model > {
2020-09-25 23:42:15 +02:00
where ( field : string , value : ModelFieldData , test? : WhereTest , operator? : WhereOperator ) : this ;
2020-09-04 22:15:55 +02:00
groupWhere ( setter : ( query : WhereFieldConsumer < M > ) = > void , operator? : WhereOperator ) : this ;
}
2020-09-05 15:33:51 +02:00
2020-10-02 12:06:18 +02:00
export type QueryFields = ( string | SelectFieldValue | FieldValue ) [ ] ;
2020-09-06 15:06:40 +02:00
export type SortDirection = 'ASC' | 'DESC' ;
type ModelQueryUnion = {
2020-09-25 23:42:15 +02:00
query : ModelQuery < Model > ,
2020-09-06 15:06:40 +02:00
sortBy : string ,
direction : SortDirection ,
limit? : number ,
2020-09-11 15:15:15 +02:00
offset? : number ,
2020-09-06 15:06:40 +02:00
} ;
2020-09-25 23:42:15 +02:00
export type ModelFieldData =
| QueryVariable
| ModelQuery < Model >
| { toString ( ) : string }
| ( QueryVariable | { toString ( ) : string } ) [ ] ;