@@ -20,6 +20,63 @@ function getTable (resourceConfig) {
2020 return resourceConfig . table || underscore ( resourceConfig . name )
2121}
2222
23+ function processRelationField ( resourceConfig , query , field , criteria , options , joinedTables ) {
24+ let fieldParts = field . split ( '.' )
25+ let localResourceConfig = resourceConfig
26+ let relationPath = [ ]
27+ let relationName = null ;
28+
29+ while ( fieldParts . length >= 2 ) {
30+ relationName = fieldParts . shift ( )
31+ let [ relation ] = localResourceConfig . relationList . filter ( r => r . relation === relationName || r . localField === relationName )
32+
33+ if ( relation ) {
34+ let relationResourceConfig = resourceConfig . getResource ( relation . relation )
35+ relationPath . push ( relation . relation )
36+
37+ if ( relation . type === 'belongsTo' || relation . type === 'hasOne' ) {
38+ // Apply table join for belongsTo/hasOne property (if not done already)
39+ if ( ! joinedTables . some ( t => t === relationPath . join ( '.' ) ) ) {
40+ let table = getTable ( localResourceConfig )
41+ let localId = `${ table } .${ relation . localKey } `
42+
43+ let relationTable = getTable ( relationResourceConfig )
44+ let foreignId = `${ relationTable } .${ relationResourceConfig . idAttribute } `
45+
46+ query . join ( relationTable , localId , foreignId )
47+ joinedTables . push ( relationPath . join ( '.' ) )
48+ }
49+ } else if ( relation . type === 'hasMany' ) {
50+ // Perform `WHERE EXISTS` subquery for hasMany property
51+ let existsParams = {
52+ [ `${ relationName } .${ fieldParts . splice ( 0 ) . join ( '.' ) } ` ] : criteria // remaining field(s) handled by EXISTS subquery
53+ } ;
54+ let subQueryTable = getTable ( relationResourceConfig ) ;
55+ let subQueryOptions = deepMixIn ( { query : knex ( this . defaults ) . select ( `${ subQueryTable } .*` ) . from ( subQueryTable ) } , options )
56+ let subQuery = this . filterQuery ( relationResourceConfig , existsParams , subQueryOptions )
57+ . whereRaw ( '??.??=??.??' , [
58+ getTable ( relationResourceConfig ) ,
59+ relation . foreignKey ,
60+ getTable ( localResourceConfig ) ,
61+ localResourceConfig . idAttribute
62+ ] )
63+ if ( Object . keys ( criteria ) . some ( k => k . indexOf ( '|' ) > - 1 ) ) {
64+ query . orWhereExists ( subQuery ) ;
65+ } else {
66+ query . whereExists ( subQuery ) ;
67+ }
68+ }
69+
70+ localResourceConfig = relationResourceConfig
71+ } else {
72+ // hopefully a qualified local column
73+ }
74+ }
75+ relationName = fieldParts . shift ( ) ;
76+
77+ return relationName ? `${ getTable ( localResourceConfig ) } .${ relationName } ` : null ;
78+ }
79+
2380function loadWithRelations ( items , resourceConfig , options ) {
2481 let tasks = [ ]
2582 let instance = Array . isArray ( items ) ? null : items
@@ -297,179 +354,127 @@ class DSSqlAdapter {
297354 }
298355 }
299356
300- let processRelationField = ( field ) => {
301- let parts = field . split ( '.' )
302- let localResourceConfig = resourceConfig
303- let relationPath = [ ]
304-
305- while ( parts . length >= 2 ) {
306- let relationName = parts . shift ( )
307- let [ relation ] = localResourceConfig . relationList . filter ( r => r . relation === relationName || r . localField === relationName )
308-
309- if ( relation ) {
310- let relationResourceConfig = resourceConfig . getResource ( relation . relation )
311- relationPath . push ( relation . relation )
312-
313- if ( relation . type === 'belongsTo' || relation . type === 'hasOne' ) {
314- // Apply table join for belongsTo/hasOne property (if not done already)
315- if ( ! joinedTables . some ( t => t === relationPath . join ( '.' ) ) ) {
316- let table = getTable ( localResourceConfig )
317- let localId = `${ table } .${ relation . localKey } `
318-
319- let relationTable = getTable ( relationResourceConfig )
320- let foreignId = `${ relationTable } .${ relationResourceConfig . idAttribute } `
321-
322- query . join ( relationTable , localId , foreignId )
323- joinedTables . push ( relationPath . join ( '.' ) )
324- }
325- } else if ( relation . type === 'hasMany' ) {
326- // Perform `WHERE EXISTS` subquery for hasMany property
327- let existsParams = {
328- [ parts [ 0 ] ] : criteria
329- } ;
330- let subQuery = this . filterQuery ( relationResourceConfig , existsParams , options )
331- . whereRaw ( '??.??=??.??' , [
332- getTable ( relationResourceConfig ) ,
333- relation . foreignKey ,
334- getTable ( localResourceConfig ) ,
335- localResourceConfig . idAttribute
336- ] )
337- if ( Object . keys ( criteria ) . some ( k => k . indexOf ( '|' ) > - 1 ) ) {
338- query . orWhereExists ( subQuery ) ;
339- } else {
340- query . whereExists ( subQuery ) ;
341- }
342- criteria = null ; // criteria handled by EXISTS subquery
343- }
344-
345- localResourceConfig = relationResourceConfig
346- } else {
347- // hopefully a qualified local column
348- }
349- }
350-
351- return `${ getTable ( localResourceConfig ) } .${ parts [ 0 ] } `
352- }
353-
354357 if ( contains ( field , '.' ) ) {
355358 if ( contains ( field , ',' ) ) {
356359 let splitFields = field . split ( ',' ) . map ( c => c . trim ( ) )
357- field = splitFields . map ( splitField => processRelationField ( splitField ) ) . join ( ',' )
360+ field = splitFields . map ( splitField => processRelationField . call ( this , resourceConfig , query , splitField , criteria , options , joinedTables ) ) . join ( ',' )
358361 } else {
359- field = processRelationField ( field , query , resourceConfig , joinedTables )
362+ field = processRelationField . call ( this , resourceConfig , query , field , criteria , options , joinedTables )
360363 }
361364 }
362365
363- forOwn ( criteria , ( v , op ) => {
364- if ( op in ( this . queryOperators || { } ) ) {
365- // Custom or overridden operator
366- query = this . queryOperators [ op ] ( query , field , v )
367- } else {
368- // Builtin operators
369- if ( op === '==' || op === '===' ) {
370- if ( v === null ) {
371- query = query . whereNull ( field )
372- } else {
373- query = query . where ( field , v )
374- }
375- } else if ( op === '!=' || op === '!==' ) {
376- if ( v === null ) {
377- query = query . whereNotNull ( field )
378- } else {
379- query = query . where ( field , '!=' , v )
380- }
381- } else if ( op === '>' ) {
382- query = query . where ( field , '>' , v )
383- } else if ( op === '>=' ) {
384- query = query . where ( field , '>=' , v )
385- } else if ( op === '<' ) {
386- query = query . where ( field , '<' , v )
387- } else if ( op === '<=' ) {
388- query = query . where ( field , '<=' , v )
389- // } else if (op === 'isectEmpty') {
390- // subQuery = subQuery ? subQuery.and(row(field).default([]).setIntersection(r.expr(v).default([])).count().eq(0)) : row(field).default([]).setIntersection(r.expr(v).default([])).count().eq(0)
391- // } else if (op === 'isectNotEmpty') {
392- // subQuery = subQuery ? subQuery.and(row(field).default([]).setIntersection(r.expr(v).default([])).count().ne(0)) : row(field).default([]).setIntersection(r.expr(v).default([])).count().ne(0)
393- } else if ( op === 'in' ) {
394- query = query . where ( field , 'in' , v )
395- } else if ( op === 'notIn' ) {
396- query = query . whereNotIn ( field , v )
397- } else if ( op === 'near' ) {
398- const milesRegex = / ( \d + ( \. \d + ) ? ) \s * ( m | M ) i l e s $ /
399- const kilometersRegex = / ( \d + ( \. \d + ) ? ) \s * ( k | K ) $ /
400-
401- let radius
402- let unitsPerDegree
403- if ( typeof v . radius === 'number' || milesRegex . test ( v . radius ) ) {
404- radius = typeof v . radius === 'number' ? v . radius : v . radius . match ( milesRegex ) [ 1 ]
405- unitsPerDegree = 69.0 // miles per degree
406- } else if ( kilometersRegex . test ( v . radius ) ) {
407- radius = v . radius . match ( kilometersRegex ) [ 1 ]
408- unitsPerDegree = 111.045 // kilometers per degree;
409- } else {
410- throw new Error ( 'Unknown radius distance units' )
411- }
366+ if ( field ) {
367+ forOwn ( criteria , ( v , op ) => {
368+ if ( op in ( this . queryOperators || { } ) ) {
369+ // Custom or overridden operator
370+ query = this . queryOperators [ op ] ( query , field , v )
371+ } else {
372+ // Builtin operators
373+ if ( op === '==' || op === '===' ) {
374+ if ( v === null ) {
375+ query = query . whereNull ( field )
376+ } else {
377+ query = query . where ( field , v )
378+ }
379+ } else if ( op === '!=' || op === '!==' ) {
380+ if ( v === null ) {
381+ query = query . whereNotNull ( field )
382+ } else {
383+ query = query . where ( field , '!=' , v )
384+ }
385+ } else if ( op === '>' ) {
386+ query = query . where ( field , '>' , v )
387+ } else if ( op === '>=' ) {
388+ query = query . where ( field , '>=' , v )
389+ } else if ( op === '<' ) {
390+ query = query . where ( field , '<' , v )
391+ } else if ( op === '<=' ) {
392+ query = query . where ( field , '<=' , v )
393+ // } else if (op === 'isectEmpty') {
394+ // subQuery = subQuery ? subQuery.and(row(field).default([]).setIntersection(r.expr(v).default([])).count().eq(0)) : row(field).default([]).setIntersection(r.expr(v).default([])).count().eq(0)
395+ // } else if (op === 'isectNotEmpty') {
396+ // subQuery = subQuery ? subQuery.and(row(field).default([]).setIntersection(r.expr(v).default([])).count().ne(0)) : row(field).default([]).setIntersection(r.expr(v).default([])).count().ne(0)
397+ } else if ( op === 'in' ) {
398+ query = query . where ( field , 'in' , v )
399+ } else if ( op === 'notIn' ) {
400+ query = query . whereNotIn ( field , v )
401+ } else if ( op === 'near' ) {
402+ const milesRegex = / ( \d + ( \. \d + ) ? ) \s * ( m | M ) i l e s $ /
403+ const kilometersRegex = / ( \d + ( \. \d + ) ? ) \s * ( k | K ) $ /
404+
405+ let radius
406+ let unitsPerDegree
407+ if ( typeof v . radius === 'number' || milesRegex . test ( v . radius ) ) {
408+ radius = typeof v . radius === 'number' ? v . radius : v . radius . match ( milesRegex ) [ 1 ]
409+ unitsPerDegree = 69.0 // miles per degree
410+ } else if ( kilometersRegex . test ( v . radius ) ) {
411+ radius = v . radius . match ( kilometersRegex ) [ 1 ]
412+ unitsPerDegree = 111.045 // kilometers per degree;
413+ } else {
414+ throw new Error ( 'Unknown radius distance units' )
415+ }
412416
413- let [ latitudeColumn , longitudeColumn ] = field . split ( ',' ) . map ( c => c . trim ( ) )
414- let [ latitude , longitude ] = v . center
415-
416- // Uses indexes on `latitudeColumn` / `longitudeColumn` if available
417- query = query
418- . whereBetween ( latitudeColumn , [
419- latitude - ( radius / unitsPerDegree ) ,
420- latitude + ( radius / unitsPerDegree )
421- ] )
422- . whereBetween ( longitudeColumn , [
423- longitude - ( radius / ( unitsPerDegree * Math . cos ( latitude * ( Math . PI / 180 ) ) ) ) ,
424- longitude + ( radius / ( unitsPerDegree * Math . cos ( latitude * ( Math . PI / 180 ) ) ) )
425- ] )
426-
427- if ( v . calculateDistance ) {
428- let distanceColumn = ( typeof v . calculateDistance === 'string' ) ? v . calculateDistance : 'distance'
429- query = query . select ( knex . raw ( `
430- ${ unitsPerDegree } * DEGREES(ACOS(
431- COS(RADIANS(?)) * COS(RADIANS(${ latitudeColumn } )) *
432- COS(RADIANS(${ longitudeColumn } ) - RADIANS(?)) +
433- SIN(RADIANS(?)) * SIN(RADIANS(${ latitudeColumn } ))
434- )) AS ${ distanceColumn } ` , [ latitude , longitude , latitude ] ) )
435- }
436- } else if ( op === 'like' ) {
437- query = query . where ( field , 'like' , v )
438- } else if ( op === '|like' ) {
439- query = query . orWhere ( field , 'like' , v )
440- } else if ( op === '|==' || op === '|===' ) {
441- if ( v === null ) {
442- query = query . orWhereNull ( field )
443- } else {
444- query = query . orWhere ( field , v )
445- }
446- } else if ( op === '|!=' || op === '|!==' ) {
447- if ( v === null ) {
448- query = query . orWhereNotNull ( field )
417+ let [ latitudeColumn , longitudeColumn ] = field . split ( ',' ) . map ( c => c . trim ( ) )
418+ let [ latitude , longitude ] = v . center
419+
420+ // Uses indexes on `latitudeColumn` / `longitudeColumn` if available
421+ query = query
422+ . whereBetween ( latitudeColumn , [
423+ latitude - ( radius / unitsPerDegree ) ,
424+ latitude + ( radius / unitsPerDegree )
425+ ] )
426+ . whereBetween ( longitudeColumn , [
427+ longitude - ( radius / ( unitsPerDegree * Math . cos ( latitude * ( Math . PI / 180 ) ) ) ) ,
428+ longitude + ( radius / ( unitsPerDegree * Math . cos ( latitude * ( Math . PI / 180 ) ) ) )
429+ ] )
430+
431+ if ( v . calculateDistance ) {
432+ let distanceColumn = ( typeof v . calculateDistance === 'string' ) ? v . calculateDistance : 'distance'
433+ query = query . select ( knex . raw ( `
434+ ${ unitsPerDegree } * DEGREES(ACOS(
435+ COS(RADIANS(?)) * COS(RADIANS(${ latitudeColumn } )) *
436+ COS(RADIANS(${ longitudeColumn } ) - RADIANS(?)) +
437+ SIN(RADIANS(?)) * SIN(RADIANS(${ latitudeColumn } ))
438+ )) AS ${ distanceColumn } ` , [ latitude , longitude , latitude ] ) )
439+ }
440+ } else if ( op === 'like' ) {
441+ query = query . where ( field , 'like' , v )
442+ } else if ( op === '|like' ) {
443+ query = query . orWhere ( field , 'like' , v )
444+ } else if ( op === '|==' || op === '|===' ) {
445+ if ( v === null ) {
446+ query = query . orWhereNull ( field )
447+ } else {
448+ query = query . orWhere ( field , v )
449+ }
450+ } else if ( op === '|!=' || op === '|!==' ) {
451+ if ( v === null ) {
452+ query = query . orWhereNotNull ( field )
453+ } else {
454+ query = query . orWhere ( field , '!=' , v )
455+ }
456+ } else if ( op === '|>' ) {
457+ query = query . orWhere ( field , '>' , v )
458+ } else if ( op === '|>=' ) {
459+ query = query . orWhere ( field , '>=' , v )
460+ } else if ( op === '|<' ) {
461+ query = query . orWhere ( field , '<' , v )
462+ } else if ( op === '|<=' ) {
463+ query = query . orWhere ( field , '<=' , v )
464+ // } else if (op === '|isectEmpty') {
465+ // subQuery = subQuery ? subQuery.or(row(field).default([]).setIntersection(r.expr(v).default([])).count().eq(0)) : row(field).default([]).setIntersection(r.expr(v).default([])).count().eq(0)
466+ // } else if (op === '|isectNotEmpty') {
467+ // subQuery = subQuery ? subQuery.or(row(field).default([]).setIntersection(r.expr(v).default([])).count().ne(0)) : row(field).default([]).setIntersection(r.expr(v).default([])).count().ne(0)
468+ } else if ( op === '|in' ) {
469+ query = query . orWhere ( field , 'in' , v )
470+ } else if ( op === '|notIn' ) {
471+ query = query . orWhereNotIn ( field , v )
449472 } else {
450- query = query . orWhere ( field , '!=' , v )
473+ throw new Error ( 'Operator not found' )
451474 }
452- } else if ( op === '|>' ) {
453- query = query . orWhere ( field , '>' , v )
454- } else if ( op === '|>=' ) {
455- query = query . orWhere ( field , '>=' , v )
456- } else if ( op === '|<' ) {
457- query = query . orWhere ( field , '<' , v )
458- } else if ( op === '|<=' ) {
459- query = query . orWhere ( field , '<=' , v )
460- // } else if (op === '|isectEmpty') {
461- // subQuery = subQuery ? subQuery.or(row(field).default([]).setIntersection(r.expr(v).default([])).count().eq(0)) : row(field).default([]).setIntersection(r.expr(v).default([])).count().eq(0)
462- // } else if (op === '|isectNotEmpty') {
463- // subQuery = subQuery ? subQuery.or(row(field).default([]).setIntersection(r.expr(v).default([])).count().ne(0)) : row(field).default([]).setIntersection(r.expr(v).default([])).count().ne(0)
464- } else if ( op === '|in' ) {
465- query = query . orWhere ( field , 'in' , v )
466- } else if ( op === '|notIn' ) {
467- query = query . orWhereNotIn ( field , v )
468- } else {
469- throw new Error ( 'Operator not found' )
470475 }
471- }
472- } )
476+ } )
477+ }
473478 } )
474479 }
475480
0 commit comments