import { ListModel } from '../models/list-model' ; import { RecordModel } from '../models/record-model' ; import { Q , Condition , Connector } from './q' ; import { Where } from './where' ; import { Lookup } from './lookups' ; import { BaseField , BaseDbField } from '../fields/base' ; import { ForeignKeyVirtualField } from '../fields/foreign-key' ; import { Aggregate , Count } from './aggregates' ; import { Record } from '../datatypes/record' ; import { Col , Expression , ColExpression } from './expressions' ; var ORDER_DIR = { 'ASC' : [ 'ASC' , 'DESC' ] , 'DESC' : [ 'DESC' , 'ASC' ] , } ; function getOrderDir ( field , def = 'ASC' ) { var dirn = ORDER_DIR [ def ] ; if ( field [ 0 ] == '-' ) return [ field . substr ( 1 ) , dirn [ 1 ] ] ; return [ field , dirn [ 0 ] ] ; } export class Query { model : ListModel ; where : Where = new Where ( ) ; used_aliases : Array < any > = [ ] ; lists : string [ ] = [ ] ; lowMark = 0 ; highMark = null ; annotations = new Map < string , Aggregate > ( ) ; defaultCols : boolean = true ; orderBy : string [ ] = [ ] ; constructor ( model ) { this . model = model ; this . lists . push ( model . name ) ; } clone ( klass ? ) { klass = klass ? klass : Object . getPrototypeOf ( this ) . constructor ; var query = new klass ( this . model ) ; query . lists = [ ] ; query . lists . push ( ... this . lists ) ; query . where = this . where . clone ( ) ; query . lowMark = this . lowMark ; query . highMark = this . highMark ; query . defaultCols = this . defaultCols ; query . orderBy = [ ... this . orderBy ] ; this . annotations = new Map ( this . annotations ) ; return query ; } add_q ( q_object ) { var clause , _ ; [ clause , _ ] = this . _add_q ( q_object , this . used_aliases ) ; if ( clause ) this . where . add ( clause , Connector . AND ) ; } _add_q ( q_object , used_aliases = [ ] , branch_negated = false , current_negated = false , allow_joins = true , split_subq = true ) { var connector = q_object . connector ; var current_negated = current_negated !== q_object . negated ; var branch_negated = branch_negated || q_object . negated ; var target_clause = new Where ( null , connector , q_object . negated ) ; var needed_inner = false ; for ( let child of q_object . children ) { var child_clause ; if ( child instanceof Q ) { [ child_clause , needed_inner ] = this . _add_q ( child , used_aliases , branch_negated , current_negated , allow_joins , split_subq ) ; if ( child_clause ) target_clause . add ( child_clause , connector ) ; } else { for ( let arg in child ) { var value = child [ arg ] ; [ child_clause , needed_inner ] = this . build_filter ( [ arg , value ] , branch_negated , current_negated , used_aliases , connector , allow_joins , split_subq ) ; if ( child_clause ) target_clause . add ( child_clause , connector ) ; } } } return [ target_clause , needed_inner ] ; } build_filter ( filter_expr , branch_negated = false , current_negated = false , can_reuse = null , connector = Connector . AND , allow_joins = true , split_subq = true ) { var [ arg , value ] = filter_expr ; var [ lookups , parts , reffed_expression ] = this . solve_lookup_type ( arg ) ; if ( ! allow_joins && parts . length > 1 ) throw new Error ( "Joined field references are not permitted in this query" ) ; var used_joins ; [ value , lookups , used_joins ] = this . prepare_lookup_value ( value , lookups , can_reuse , allow_joins ) ; var clause = new Where ( ) ; if ( reffed_expression ) { } var opts = { } ; var alias = this . get_initial_alias ( ) ; var allow_many = ! branch_negated || ! split_subq ; var field = this . model . getDbField ( parts [ 0 ] ) ; if ( false ) { } else { var col = new Col ( alias , field ) ; var condition = this . build_lookup ( lookups , col , value ) ; } clause . add ( condition , Connector . AND ) ; return [ clause , false ] ; } setLimits ( low , high = null ) { if ( high !== null ) { if ( this . highMark !== null ) this . highMark = Math . min ( this . highMark , this . lowMark + high ) ; else this . highMark = this . lowMark + high ; } if ( low !== null ) { if ( this . highMark !== null ) this . lowMark = Math . min ( this . highMark , this . lowMark + low ) ; else this . lowMark = this . lowMark + low ; } if ( this . lowMark == this . highMark ) this . setEmpty ( ) ; } setEmpty ( ) { } getCount ( alias = '__count' ) { var obj = this . clone ( ) ; obj . addAnnotation ( new Count ( '*' ) , alias , true ) ; return obj ; } addAnnotation ( aggregate , alias , isSummary = false ) { this . defaultCols = false ; this . annotations . set ( alias , aggregate ) ; } solve_lookup_type ( lookup ) { var LOOKUP_SEP = '__' ; var lookupSplitted = lookup . split ( LOOKUP_SEP ) ; var _ , field , __ , lookupParts ; [ _ , field , __ , lookupParts ] = this . namesToPath ( lookupSplitted , this . model ) ; var fieldParts = lookupSplitted . slice ( 0 , lookupSplitted . length - lookupParts . length ) ; if ( lookupParts . length == 0 ) { lookupParts = [ 'exact' ] ; } else if ( lookupParts . length > 1 ) { if ( ! fieldParts ) { throw new Error ( 'Invalid lookup "' + lookup + '" for model ' + this . model . name + '".' ) ; } } return [ lookupParts , fieldParts , false ] ; } namesToPath ( names , model , allowMany = true , failOnMissing = false ) { var path , names_with_path ; [ path , names_with_path ] = [ [ ] , [ ] ] ; var pos = - 1 ; for ( let name of names ) { pos ++ ; var field = null ; try { field = model . getDbField ( name ) ; } catch ( e ) { } if ( field !== null ) { } else { } if ( false ) { } else { var finalField = field ; var targets = [ field , ] ; if ( failOnMissing && pos + 1 !== names . length ) { throw new Error ( 'Cannot resolve keyword "' + names [ pos + 1 ] + '" into field. Join on "' + name + '" not permitted.' ) ; } break ; } } ; return [ path , finalField , targets , names . slice ( pos + 1 ) ] ; } prepare_lookup_value ( value , lookups , can_reuse , allow_joins ) { return [ value , lookups , [ ] ] ; } get_initial_alias ( ) { if ( this . lists . length ) return this . lists [ 0 ] ; else return this . model . name ; } setup_joins ( names , opts , alias , can_reuse = null , allow_many = true ) { } build_lookup ( lookups , lhs , rhs ) { lookups = [ ... lookups ] ; for ( let lookup of lookups ) { var name = lookup ; if ( lookups . length == 1 ) { var finalLookup = lhs . target . getLookup ( name ) ; if ( ! finalLookup ) { finalLookup = lhs . target . getLookup ( 'exact' ) ; } return new finalLookup ( lhs , rhs ) ; } } } clearOrdering ( forceEmpty ) { this . orderBy = [ ] ; } addOrdering ( ... ordering ) { if ( ordering && ordering . length ) this . orderBy . push ( ... ordering ) ; } getOrdering ( ) { var result = { } ; var asc = 'ASC' ; this . orderBy . forEach ( ( field , pos ) => { var [ col , order ] = getOrderDir ( field , asc ) ; result [ col ] = order ; } ) ; return result ; } } export interface ChildQueryDescriber { handle : string ; query : Query ; } export class SaveQuery extends Query { childSaveQueries : Map < BaseField , ChildQueryDescriber [ ] > = new Map ( ) ; addChildQuery ( field , describer ) { this . childSaveQueries . set ( field , describer ) ; } toSelectQuery ( ) { return this ; } } export class InsertQuery extends SaveQuery { records : Record [ ] = [ ] ; recordModel : RecordModel ; fields : BaseDbField [ ] = [ ] ; constructor ( recordModel ) { super ( recordModel . backend . getListModel ( recordModel . listName ) ) ; this . recordModel = recordModel ; } getLookupData ( ) { var condition = { } ; for ( let field of this . records [ 0 ] . __director__ . model . getLookupFields ( ) ) { if ( this . records [ 0 ] [ field . name ] ) condition [ field . name ] = this . records [ 0 ] [ field . name ] ; } return condition ; } toSelectQuery ( ) { if ( this . records . length !== 1 ) throw new Error ( 'Can\'t transform to select query if number of records is not equal 1.' ) ; var query = new Query ( this . model ) ; var record = this . records [ 0 ] ; query . add_q ( new Q ( this . getLookupData ( ) ) ) ; return query ; } updateWithParentRecord ( linkThrough , parentRecordRawData ) { var linkField = this . model . getField ( linkThrough ) ; var condition = { } ; if ( linkField instanceof ForeignKeyVirtualField ) { for ( let tableFieldName in linkField . keys ) { let parentRecordFieldName = linkField . keys [ tableFieldName ] ; if ( parentRecordRawData [ parentRecordFieldName ] === undefined ) return ; condition [ tableFieldName ] = parentRecordRawData [ parentRecordFieldName ] ; } } else { condition [ linkThrough ] = parentRecordRawData ; } for ( let record of this . records ) { for ( let key in condition ) { record [ key ] = condition [ key ] ; } } } insertValues ( fields , records ) { this . fields = fields ; this . records = records ; } } export class UpdateQuery extends SaveQuery { values : [ BaseDbField , any ] [ ] = [ ] ; initiator : Record ; addUpdateValues ( values ) { var valuesSeq = [ ] ; for ( let name in values ) { let val = values [ name ] ; let field = this . model . getField ( name ) ; valuesSeq . push ( [ field , val ] ) ; } return this . addUpdateFields ( valuesSeq ) ; } addUpdateFields ( valuesSeq ) { for ( let f of valuesSeq ) { let field = f [ 0 ] ; let val = f [ 1 ] ; this . values . push ( [ field , val ] ) ; } } getLookupData ( oldLookupData = false ) { var condition = { } ; this . where . children . forEach ( ( lookup , idx ) => { if ( lookup instanceof Lookup ) { condition [ lookup . name ] = lookup . rhs ; } } ) ; if ( ! oldLookupData ) { for ( let f of this . values ) { let field = f [ 0 ] ; let val = f [ 1 ] ; if ( field . name in condition ) condition [ field . name ] = val ; } } return condition ; } toSelectQuery ( ) { var query = new Query ( this . model ) ; query . where = this . where . clone ( ) ; var condition = { } ; for ( let f of this . values ) { let field = f [ 0 ] ; let val = f [ 1 ] ; query . where . children . forEach ( ( lookup , idx ) => { if ( lookup instanceof Lookup && lookup . name == field . name ) { query . where . children . splice ( idx , 1 ) ; condition [ field . name ] = val ; } } ) ; } query . add_q ( new Q ( condition ) ) ; return query ; } setInitiator ( record ) { this . initiator = record ; } } export class DeleteQuery extends SaveQuery { initiator : Record ; setInitiator ( initiator ) { this . initiator = initiator ; } getLookupData ( ) { var condition = { } ; this . where . children . forEach ( ( lookup , idx ) => { if ( lookup instanceof Lookup ) { condition [ lookup . name ] = lookup . rhs ; } } ) ; return condition ; } }