author Jeremy Daly <jeremy@jeremydaly.com>
 *  license import { error } from './utils' ; import checkAttribute from './checkAttribute' ; import Table from '../classes/Table' ; interface FilterExpression { attr ? : string ; size ? : string ; eq ? : string | number | boolean | null ; ne ? : string | number | boolean | null ; lt ? : string | number ; lte ? : string | number ; gt ? : string | number ; gte ? : string | number ; between ? : string [ ] | number [ ] ; beginsWith ? : string ; in ? : any [ ] ; contains ? : string ; exists ? : boolean ; type ? : string ; or ? : boolean ; negate ? : boolean ; entity ? : string ; } export type FilterExpressions = FilterExpression | FilterExpression [ ] | FilterExpressions [ ] ; const buildExpression = ( exp , table , entity ? , group = 0 , level = 0 ) : any => { const clauses = Array . isArray ( exp ) ? exp : [ exp ] ; let expression = '' ; let names = { } ; let values = { } ; let logic ; clauses . forEach ( ( x , id ) => { if ( Array . isArray ( clauses [ id ] ) ) { let sub = buildExpression ( clauses [ id ] , table , entity , group , level ) ; logic = logic ? logic : sub . logic ; expression += `${ id > 0 ? ` ${ sub . logic } ` : '' }(${ sub . expression })` ; names = Object . assign ( names , sub . names ) ; values = Object . assign ( values , sub . values ) ; group = sub . group ; } else { const exp = clauses [ id ] as FilterExpression ; group ++ ; if ( entity && ! exp . entity ) exp . entity = entity ; const clause = parseClause ( exp , group , table ) ; expression += `${ id > 0 ? ` ${ clause . logic } ` : '' }${ clause . clause }` ; names = Object . assign ( names , clause . names ) ; values = Object . assign ( values , clause . values ) ; logic = logic ? logic : clause . logic ; } } ) ; return { logic , expression , names , values , group } ; } ; export default buildExpression ; const conditionError = ( op ? ) => error ( `You can only supply one filter condition per query. Already using '${ op }'` ) ; const parseClause = ( _clause , grp , table ) => { let clause = '' ; const names = { } ; const values = { } ; const { attr , size , negate , or , eq , ne , in : _in , lt , lte , gt , gte , between , exists , contains , beginsWith , type , entity , ... args } = _clause ; if ( Object . keys ( args ) . length > 0 ) error ( `Invalid expression options: ${ Object . keys ( args ) . join ( ', ' ) }` ) ; if ( entity !== undefined && ( typeof entity !== 'string' || ( ! table [ entity ] || ! table [ entity ] . schema || ! table [ entity ] . schema . attributes ) ) ) error ( `'entity' value of '${ entity }' must be a string and a valid table Entity name` ) ; names [ `#attr${ grp }` ] = typeof attr === 'string' ? checkAttribute ( attr , ( entity ? table [ entity ] . schema . attributes : table . Table . attributes ) ) : typeof size === 'string' ? checkAttribute ( size , ( entity ? table [ entity ] . schema . attributes : table . Table . attributes ) ) : error ( `A string for 'attr' or 'size' is required for condition expressions` ) ; let operator , value , f ; if ( eq !== undefined ) { value = eq ; f = 'eq' ; operator = '=' ; } if ( ne !== undefined ) { value = value ? conditionError ( f ) : ne ; f = 'ne' ; operator = '<>' ; } if ( _in ) { value = value ? conditionError ( f ) : _in ; f = 'in' ; operator = 'IN' ; } if ( lt !== undefined ) { value = value ? conditionError ( f ) : lt ; f = 'lt' ; operator = '<' ; } if ( lte !== undefined ) { value = value ? conditionError ( f ) : lte ; f = 'lte' ; operator = '<=' ; } if ( gt !== undefined ) { value = value ? conditionError ( f ) : gt ; f = 'gt' ; operator = '>' ; } if ( gte !== undefined ) { value = value ? conditionError ( f ) : gte ; f = 'gte' ; operator = '>=' ; } if ( between ) { value = value ? conditionError ( f ) : between ; f = 'between' ; operator = 'BETWEEN' ; } if ( exists !== undefined ) { value = value ? conditionError ( f ) : exists ; f = 'exists' ; operator = 'EXISTS' ; } if ( contains ) { value = value ? conditionError ( f ) : contains ; f = 'contains' ; operator = 'CONTAINS' ; } if ( beginsWith ) { value = value ? conditionError ( f ) : beginsWith ; f = 'beginsWith' ; operator = 'BEGINS_WITH' ; } if ( type ) { value = value ? conditionError ( f ) : type ; f = 'type' ; operator = 'ATTRIBUTE_TYPE' ; } if ( operator ) { if ( operator === 'BETWEEN' ) { if ( Array . isArray ( value ) && value . length === 2 ) { values [ `:attr${ grp }_0` ] = value [ 0 ] ; values [ `:attr${ grp }_1` ] = value [ 1 ] ; clause = `${ size ? `size(#attr${ grp })` : `#attr${ grp }` } between :attr${ grp }_0 and :attr${ grp }_1` ; } else { error ( `'between' conditions require an array with two values.` ) ; } } else if ( operator === 'IN' ) { if ( ! attr ) error ( `'in' conditions require an 'attr'.` ) ; if ( Array . isArray ( value ) ) { clause = `#attr${ grp } IN (${ value . map ( ( x , i ) => { values [ `:attr${ grp }_${ i }` ] = x ; return `:attr${ grp }_${ i }` ; } ) . join ( ',' ) })` ; } else { error ( `'in' conditions require an array.` ) ; } } else if ( operator === 'EXISTS' ) { if ( ! attr ) error ( `'exists' conditions require an 'attr'.` ) ; clause = value ? `attribute_exists(#attr${ grp })` : `attribute_not_exists(#attr${ grp })` ; } else { values [ `:attr${ grp }` ] = value ; if ( operator === 'BEGINS_WITH' ) { if ( ! attr ) error ( `'beginsWith' conditions require an 'attr'.` ) ; clause = `begins_with(#attr${ grp },:attr${ grp })` ; } else if ( operator === 'CONTAINS' ) { if ( ! attr ) error ( `'contains' conditions require an 'attr'.` ) ; clause = `contains(#attr${ grp },:attr${ grp })` ; } else if ( operator === 'ATTRIBUTE_TYPE' ) { if ( ! attr ) error ( `'type' conditions require an 'attr'.` ) ; clause = `attribute_type(#attr${ grp },:attr${ grp })` ; } else { clause = `${ size ? `size(#attr${ grp })` : `#attr${ grp }` } ${ operator } :attr${ grp }` ; } } if ( negate ) { clause = `(NOT ${ clause })` ; } } else { error ( 'A condition is required' ) ; } return { logic : or ? 'OR' : 'AND' , clause , names , values } ; } ;