@@ -4,6 +4,10 @@ import {MemberExpression, MethodCallExpression, QueryEntity, QueryExpression, Qu
44import { SqliteFormatter } from '../src' ;
55import SimpleOrderSchema from './config/models/SimpleOrder.json' ;
66import { TestApplication } from './TestApplication' ;
7+ import { TraceUtils } from '@themost/common' ;
8+ import { DataPermissionEventListener } from '@themost/data' ;
9+ import { promisify } from 'util' ;
10+ const beforeExecuteAsync = promisify ( DataPermissionEventListener . prototype . beforeExecute ) ;
711
812/**
913 * @param { import('../src').SqliteAdapter } db
@@ -328,9 +332,7 @@ describe('SqlFormatter', () => {
328332 select . push ( {
329333 customer : {
330334 $jsonObject : [
331- 'familyName' ,
332335 new QueryField ( 'familyName' ) . from ( 'customer' ) ,
333- 'givenName' ,
334336 new QueryField ( 'givenName' ) . from ( 'customer' ) ,
335337 ]
336338 }
@@ -369,18 +371,14 @@ describe('SqlFormatter', () => {
369371 select . push ( {
370372 customer : {
371373 $jsonObject : [
372- 'familyName' ,
373374 new QueryField ( 'familyName' ) . from ( 'customers' ) ,
374- 'givenName' ,
375375 new QueryField ( 'givenName' ) . from ( 'customers' ) ,
376376 ]
377377 }
378378 } , {
379379 orderStatus : {
380380 $jsonObject : [
381- 'name' ,
382381 new QueryField ( 'name' ) . from ( 'orderStatusTypes' ) ,
383- 'alternateName' ,
384382 new QueryField ( 'alternateName' ) . from ( 'orderStatusTypes' ) ,
385383 ]
386384 }
@@ -401,4 +399,206 @@ describe('SqlFormatter', () => {
401399 } ) ;
402400 } ) ;
403401
402+ it ( 'should use json queries for expand entities' , async ( ) => {
403+ // set context user
404+ context . user = {
405+ name : 'james.may@example.com'
406+ } ;
407+ let start = new Date ( ) . getTime ( ) ;
408+ const items = await context . model ( 'Order' ) . asQueryable ( ) . select (
409+ 'id' , 'orderDate' , 'orderStatus' , 'customer' , 'orderedItem'
410+ ) . expand ( 'customer' , 'orderStatus' , 'orderedItem' ) . getItems ( ) ;
411+ let end = new Date ( ) . getTime ( ) ;
412+ TraceUtils . log ( 'Elapsed time: ' + ( end - start ) + 'ms' ) ;
413+ expect ( items . length ) . toBeTruthy ( ) ;
414+ // create ad-hoc query
415+ const { viewAdapter : Orders } = context . model ( 'Order' ) ;
416+ const { viewAdapter : People } = context . model ( 'Person' ) ;
417+ const { viewAdapter : Products } = context . model ( 'Product' ) ;
418+ const { viewAdapter : OrderStatusTypes } = context . model ( 'OrderStatusType' ) ;
419+ const personAttributes = context . model ( 'Person' ) . select ( ) . query . $select [ People ] . map ( ( x ) => {
420+ return x . from ( 'customer' ) ;
421+ } ) ;
422+ const productAttributes = context . model ( 'Product' ) . select ( ) . query . $select [ Products ] . map ( ( x ) => {
423+ return x . from ( 'orderedItem' ) ;
424+ } ) ;
425+ const orderStatusAttributes = context . model ( 'OrderStatusType' ) . select ( ) . query . $select [ OrderStatusTypes ] . map ( ( x ) => {
426+ return x . from ( 'orderStatus' ) ;
427+ } ) ;
428+ const q = new QueryExpression ( ) . select (
429+ new QueryField ( 'id' ) . from ( Orders ) ,
430+ new QueryField ( 'orderDate' ) . from ( Orders ) ,
431+ new QueryField ( {
432+ customer : {
433+ $jsonObject : personAttributes
434+ }
435+ } ) ,
436+ new QueryField ( {
437+ product : {
438+ $jsonObject : productAttributes
439+ }
440+ } ) ,
441+ new QueryField ( {
442+ orderStatus : {
443+ $jsonObject : orderStatusAttributes
444+ }
445+ } )
446+ ) . from ( Orders ) . join ( new QueryEntity ( People ) . as ( 'customer' ) ) . with (
447+ new QueryExpression ( ) . where (
448+ new QueryField ( 'customer' ) . from ( Orders )
449+ ) . equal (
450+ new QueryField ( 'id' ) . from ( 'customer' )
451+ )
452+ ) . join ( new QueryEntity ( Products ) . as ( 'orderedItem' ) ) . with (
453+ new QueryExpression ( ) . where (
454+ new QueryField ( 'orderedItem' ) . from ( Orders )
455+ ) . equal (
456+ new QueryField ( 'id' ) . from ( 'orderedItem' )
457+ )
458+ ) . join ( new QueryEntity ( OrderStatusTypes ) . as ( 'orderStatus' ) ) . with (
459+ new QueryExpression ( ) . where (
460+ new QueryField ( 'orderStatus' ) . from ( Orders )
461+ ) . equal (
462+ new QueryField ( 'id' ) . from ( 'orderStatus' )
463+ )
464+ ) . where ( new QueryField ( 'email' ) . from ( 'customer' ) ) . equal ( context . user . name ) ;
465+
466+ start = new Date ( ) . getTime ( ) ;
467+ const customerOrders = await context . db . executeAsync ( q , [ ] ) ;
468+ end = new Date ( ) . getTime ( ) ;
469+ TraceUtils . log ( 'Elapsed time: ' + ( end - start ) + 'ms' ) ;
470+ expect ( customerOrders . length ) . toBeTruthy ( ) ;
471+ expect ( items . length ) . toEqual ( customerOrders . length ) ;
472+ } ) ;
473+
474+ it ( 'should use json queries and validate permission' , async ( ) => {
475+ // set context user
476+ context . user = {
477+ name : 'james.may@example.com'
478+ } ;
479+ const queryOrders = context . model ( 'Order' ) . asQueryable ( ) . select ( ) . flatten ( ) ;
480+ const { viewAdapter : Orders } = queryOrders . model ;
481+ expect ( queryOrders ) . toBeTruthy ( ) ;
482+ // prepare query for customer
483+ const queryPeople = context . model ( 'Person' ) . asQueryable ( ) . select ( ) . flatten ( ) ;
484+ await beforeExecuteAsync ( {
485+ model : queryPeople . model ,
486+ emitter : queryPeople ,
487+ query : queryPeople . query ,
488+ } ) ;
489+ expect ( queryPeople ) . toBeTruthy ( ) ;
490+ // prepare query for order status
491+ const queryOrderStatus = context . model ( 'OrderStatusType' ) . asQueryable ( ) . select ( ) . flatten ( ) ;
492+ await beforeExecuteAsync ( {
493+ model : queryOrderStatus . model ,
494+ emitter : queryOrderStatus ,
495+ query : queryOrderStatus . query ,
496+ } ) ;
497+ // prepare query for ordered item
498+ const queryProducts = context . model ( 'Product' ) . asQueryable ( ) . select ( ) . flatten ( ) ;
499+ await beforeExecuteAsync ( {
500+ model : queryProducts . model ,
501+ emitter : queryProducts ,
502+ query : queryProducts . query ,
503+ } ) ;
504+
505+ // phase 1: join customers in order to get customer as json object
506+ const { viewAdapter : People } = queryPeople . model ;
507+ // select customer as json object
508+ const selectCustomer = new QueryField ( {
509+ customer : {
510+ $jsonObject : queryPeople . query . $select [ People ] . map ( ( x ) => {
511+ return x . from ( 'customer' ) ;
512+ } )
513+ }
514+ } ) ;
515+ // remove select arguments from nested query and push a wildcard select
516+ // important note: this operation reduces the size of the subquery used for join entity
517+ queryPeople . query . $select [ People ] = [ new QueryField ( `${ People } .*` ) ] ;
518+ // join entity
519+ queryOrders . query . join ( queryPeople . query . as ( 'customer' ) ) . with (
520+ new QueryExpression ( ) . where (
521+ new QueryField ( 'customer' ) . from ( Orders )
522+ ) . equal (
523+ new QueryField ( 'id' ) . from ( 'customer' )
524+ )
525+ )
526+ // append customer json object
527+
528+ const selectOrders = queryOrders . query . $select [ Orders ] ;
529+ // remove previoulsy selected customer field
530+ let removeIndex = selectOrders . findIndex ( ( x ) => x instanceof QueryField && x . $name === `${ Orders } .customer` ) ;
531+ if ( removeIndex >= 0 ) {
532+ selectOrders . splice ( removeIndex , 1 ) ;
533+ }
534+ // add customer json object
535+ selectOrders . push ( selectCustomer ) ;
536+
537+ // phase 2: join ordered items in order to get ordered item as json object
538+ const { viewAdapter : Products } = queryProducts . model ;
539+ // select ordered item as json object
540+ const selectOrderedItem = new QueryField ( {
541+ orderedItem : {
542+ $jsonObject : queryProducts . query . $select [ Products ] . map ( ( x ) => {
543+ return x . from ( 'orderedItem' ) ;
544+ } )
545+ }
546+ } ) ;
547+ // remove select arguments from nested query and push a wildcard select
548+ // important note: this operation reduces the size of the subquery used for join entity
549+ queryProducts . query . $select [ Products ] = [ new QueryField ( `${ Products } .*` ) ] ;
550+ // join entity
551+ queryOrders . query . join ( queryProducts . query . as ( 'orderedItem' ) ) . with (
552+ new QueryExpression ( ) . where (
553+ new QueryField ( 'orderedItem' ) . from ( Orders )
554+ ) . equal (
555+ new QueryField ( 'id' ) . from ( 'orderedItem' )
556+ )
557+ )
558+ removeIndex = selectOrders . findIndex ( ( x ) => x instanceof QueryField && x . $name === `${ Orders } .orderedItem` ) ;
559+ if ( removeIndex >= 0 ) {
560+ selectOrders . splice ( removeIndex , 1 ) ;
561+ }
562+ // add ordered json object
563+ selectOrders . push ( selectOrderedItem ) ;
564+
565+ // phase 3: join order status in order to get order status as json object
566+ const { viewAdapter : OrderStatusTypes } = queryOrderStatus . model ;
567+ // select order status as json object
568+ const selectOrderStatus = new QueryField ( {
569+ orderStatus : {
570+ $jsonObject : queryOrderStatus . query . $select [ OrderStatusTypes ] . map ( ( x ) => {
571+ return x . from ( 'orderStatus' ) ;
572+ } )
573+ }
574+ } ) ;
575+ // remove select arguments from nested query and push a wildcard select
576+ // important note: this operation reduces the size of the subquery used for join entity
577+ queryOrderStatus . query . $select [ OrderStatusTypes ] = [ new QueryField ( `${ OrderStatusTypes } .*` ) ] ;
578+ // join entity
579+ queryOrders . query . join ( queryOrderStatus . query . as ( 'orderStatus' ) ) . with (
580+ new QueryExpression ( ) . where (
581+ new QueryField ( 'orderStatus' ) . from ( Orders )
582+ ) . equal (
583+ new QueryField ( 'id' ) . from ( 'orderStatus' )
584+ )
585+ ) ;
586+ removeIndex = selectOrders . findIndex ( ( x ) => x instanceof QueryField && x . $name === `${ Orders } .orderStatus` ) ;
587+ if ( removeIndex >= 0 ) {
588+ selectOrders . splice ( removeIndex , 1 ) ;
589+ }
590+ // add order status json object
591+ selectOrders . push ( selectOrderStatus ) ;
592+
593+ let start = new Date ( ) . getTime ( ) ;
594+ const items = await queryOrders . getItems ( ) ;
595+ let end = new Date ( ) . getTime ( ) ;
596+ TraceUtils . log ( 'Elapsed time: ' + ( end - start ) + 'ms' ) ;
597+ expect ( items . length ) . toBeTruthy ( ) ;
598+ for ( const item of items ) {
599+ expect ( item . customer ) . toBeInstanceOf ( Object ) ;
600+ expect ( item . orderedItem ) . toBeInstanceOf ( Object ) ;
601+ }
602+ } ) ;
603+
404604} ) ;
0 commit comments