import { CommonFieldComparisonBetweenType , FilterComparisonOperators } from '@nestjs-query/core' ; import { ObjectLiteral } from 'typeorm' ; import { randomString } from '../common' ; internal type CmpSQLType = { sql : string ; params : ObjectLiteral ; } ; internal export type EntityComparisonField < Entity , F extends keyof Entity > = Entity [ F ] | Entity [ F ] [ ] | CommonFieldComparisonBetweenType < Entity [ F ] > | true | false | null ; internal export class SQLComparisonBuilder < Entity > { static DEFAULT_COMPARISON_MAP : Record < string , string > = { eq : '=' , neq : '!=' , gt : '>' , gte : '>=' , lt : '<' , lte : '<=' , like : 'LIKE' , notlike : 'NOT LIKE' , ilike : 'ILIKE' , notilike : 'NOT ILIKE' , } ; constructor ( readonly comparisonMap = SQLComparisonBuilder . DEFAULT_COMPARISON_MAP ) { } private get paramName ( ) : string { const id = randomString ( ) ; const param = `param${ id }` ; return param ; } param field param cmp param val param alias build < F extends keyof Entity > ( field , cmp , val , alias ? ) { const col = alias ? `${ alias }.${ field as string }` : `${ field as string }` ; const normalizedCmp = ( cmp as string ) . toLowerCase ( ) ; if ( this . comparisonMap [ normalizedCmp ] ) { return this . createComparisonSQL ( normalizedCmp , col , val ) ; } if ( normalizedCmp === 'is' ) { return this . isComparisonSQL ( col , val ) ; } if ( normalizedCmp === 'isnot' ) { return this . isNotComparisonSQL ( col , val ) ; } if ( normalizedCmp === 'in' ) { return this . inComparisonSQL ( col , val ) ; } if ( normalizedCmp === 'notin' ) { return this . notInComparisonSQL ( col , val ) ; } if ( normalizedCmp === 'between' ) { return this . betweenComparisonSQL ( col , val ) ; } if ( normalizedCmp === 'notbetween' ) { return this . notBetweenComparisonSQL ( col , val ) ; } throw new Error ( `unknown operator ${ JSON . stringify ( cmp ) }` ) ; } private createComparisonSQL < F extends keyof Entity > ( cmp , col , val ) { const operator = this . comparisonMap [ cmp ] ; const { paramName } = this ; return { sql : `${ col } ${ operator } :${ paramName }` , params : { [ paramName ] : val } } ; } private isComparisonSQL < F extends keyof Entity > ( col , val ) { if ( val === null ) { return { sql : `${ col } IS NULL` , params : { } } ; } if ( val === true ) { return { sql : `${ col } IS TRUE` , params : { } } ; } if ( val === false ) { return { sql : `${ col } IS FALSE` , params : { } } ; } throw new Error ( `unexpected is operator param ${ JSON . stringify ( val ) }` ) ; } private isNotComparisonSQL < F extends keyof Entity > ( col , val ) { if ( val === null ) { return { sql : `${ col } IS NOT NULL` , params : { } } ; } if ( val === true ) { return { sql : `${ col } IS NOT TRUE` , params : { } } ; } if ( val === false ) { return { sql : `${ col } IS NOT FALSE` , params : { } } ; } throw new Error ( `unexpected isNot operator param ${ JSON . stringify ( val ) }` ) ; } private inComparisonSQL < F extends keyof Entity > ( col , val ) { this . checkNonEmptyArray ( val ) ; const { paramName } = this ; return { sql : `${ col } IN (:...${ paramName })` , params : { [ paramName ] : val } , } ; } private notInComparisonSQL < F extends keyof Entity > ( col , val ) { this . checkNonEmptyArray ( val ) ; const { paramName } = this ; return { sql : `${ col } NOT IN (:...${ paramName })` , params : { [ paramName ] : val } , } ; } private checkNonEmptyArray < F extends keyof Entity > ( val ) { if ( ! Array . isArray ( val ) ) { throw new Error ( `Invalid in value expected an array got ${ JSON . stringify ( val ) }` ) ; } if ( ! val . length ) { throw new Error ( `Invalid in value expected a non-empty array got ${ JSON . stringify ( val ) }` ) ; } } private betweenComparisonSQL < F extends keyof Entity > ( col , val ) { if ( this . isBetweenVal ( val ) ) { const { paramName : lowerParamName } = this ; const { paramName : upperParamName } = this ; return { sql : `${ col } BETWEEN :${ lowerParamName } AND :${ upperParamName }` , params : { [ lowerParamName ] : val . lower , [ upperParamName ] : val . upper , } , } ; } throw new Error ( `Invalid value for between expected {lower: val, upper: val} got ${ JSON . stringify ( val ) }` ) ; } private notBetweenComparisonSQL < F extends keyof Entity > ( col , val ) { if ( this . isBetweenVal ( val ) ) { const { paramName : lowerParamName } = this ; const { paramName : upperParamName } = this ; return { sql : `${ col } NOT BETWEEN :${ lowerParamName } AND :${ upperParamName }` , params : { [ lowerParamName ] : val . lower , [ upperParamName ] : val . upper , } , } ; } throw new Error ( `Invalid value for not between expected {lower: val, upper: val} got ${ JSON . stringify ( val ) }` ) ; } private isBetweenVal < F extends keyof Entity > ( val ) { return val !== null && typeof val === 'object' && 'lower' in val && 'upper' in val ; } }