@@ -323,6 +323,7 @@ gql_type = function(state, avro_schema, collection, collection_name)
323
323
assert (destination_type ~= nil ,
324
324
(' destination_type (named %s) must not be nil' ):format (
325
325
c .destination_collection ))
326
+ local raw_destination_type = destination_type
326
327
327
328
local c_args
328
329
if c .type == ' 1:1' then
@@ -340,11 +341,33 @@ gql_type = function(state, avro_schema, collection, collection_name)
340
341
341
342
local c_list_args = state .list_arguments [c .destination_collection ]
342
343
344
+ -- capture `raw_destination_type`
345
+ local function genResolveField (info )
346
+ return function (field_name , object , filter , opts )
347
+ assert (raw_destination_type .fields [field_name ],
348
+ (' performing a subrequest by the non-existent ' ..
349
+ ' field "%s" of the collection "%s"' ):format (field_name ,
350
+ c .destination_collection ))
351
+ return raw_destination_type .fields [field_name ].resolve (
352
+ object , filter , info , opts )
353
+ end
354
+ end
355
+
343
356
fields [c .name ] = {
344
357
name = c .name ,
345
358
kind = destination_type ,
346
359
arguments = c_args ,
347
- resolve = function (parent , args_instance , info )
360
+ resolve = function (parent , args_instance , info , opts )
361
+ local opts = opts or {}
362
+ assert (type (opts ) == ' table' ,
363
+ ' opts must be nil or a table, got ' .. type (opts ))
364
+ local dont_force_nullability =
365
+ opts .dont_force_nullability or false
366
+ assert (type (dont_force_nullability ) == ' boolean' ,
367
+ ' opts.dont_force_nullability ' ..
368
+ ' must be nil or a boolean, got ' ..
369
+ type (dont_force_nullability ))
370
+
348
371
local destination_args_names = {}
349
372
local destination_args_values = {}
350
373
local are_all_parts_non_null = true
@@ -407,8 +430,10 @@ gql_type = function(state, avro_schema, collection, collection_name)
407
430
destination_args_names = destination_args_names ,
408
431
destination_args_values = destination_args_values ,
409
432
}
433
+ local resolveField = genResolveField (info )
410
434
local extra = {
411
- qcontext = info .qcontext
435
+ qcontext = info .qcontext ,
436
+ resolveField = resolveField , -- for subrequests
412
437
}
413
438
local object_args_instance = {} -- passed to 'filter'
414
439
local list_args_instance = {} -- passed to 'args'
@@ -420,7 +445,7 @@ gql_type = function(state, avro_schema, collection, collection_name)
420
445
else
421
446
error ((' cannot found "%s" field ("%s" value) ' ..
422
447
' within allowed fields' ):format (tostring (k ),
423
- tostring (v )))
448
+ json . encode (v )))
424
449
end
425
450
end
426
451
local objs = accessor :select (parent ,
@@ -433,7 +458,7 @@ gql_type = function(state, avro_schema, collection, collection_name)
433
458
-- we expect here exactly one object even for 1:1*
434
459
-- connections because we processed all-parts-are-null
435
460
-- situation above
436
- assert (# objs == 1 ,
461
+ assert (# objs == 1 or dont_force_nullability ,
437
462
' expect one matching object, got ' ..
438
463
tostring (# objs ))
439
464
return objs [1 ]
@@ -529,6 +554,78 @@ local function create_root_collection(state)
529
554
})
530
555
end
531
556
557
+ --- Execute a function for each 1:1 or 1:1* connection of each collection.
558
+ ---
559
+ --- @tparam table state tarantool_graphql instance
560
+ ---
561
+ --- @tparam function func a function with the following parameters:
562
+ ---
563
+ --- * source collection name (string);
564
+ --- * connection (table).
565
+ local function for_each_1_1_connection (state , func )
566
+ for collection_name , collection in pairs (state .collections ) do
567
+ for _ , c in ipairs (collection .connections or {}) do
568
+ if c .type == ' 1:1' or c .type == ' 1:1*' then
569
+ func (collection_name , c )
570
+ end
571
+ end
572
+ end
573
+ end
574
+
575
+ --- Add arguments corresponding to 1:1 and 1:1* connections (nested filters).
576
+ ---
577
+ --- @tparam table state graphql_tarantool instance
578
+ local function add_connection_arguments (state )
579
+ -- map destination collection to list of input objects
580
+ local input_objects = {}
581
+ -- map source collection and connection name to an input object
582
+ local lookup_input_objects = {}
583
+
584
+ -- create InputObjects for each 1:1 or 1:1* connection of each collection
585
+ for_each_1_1_connection (state , function (collection_name , c )
586
+ local object = types .inputObject ({
587
+ name = c .name ,
588
+ description = (' generated from the connection "%s" ' ..
589
+ ' of collection "%s" using collection "%s"' ):format (
590
+ c .name , collection_name , c .destination_collection ),
591
+ fields = state .object_arguments [c .destination_collection ],
592
+ })
593
+
594
+ if input_objects [c .destination_collection ] == nil then
595
+ input_objects [c .destination_collection ] = {}
596
+ end
597
+ table.insert (input_objects [c .destination_collection ], object )
598
+
599
+ if lookup_input_objects [collection_name ] == nil then
600
+ lookup_input_objects [collection_name ] = {}
601
+ end
602
+ lookup_input_objects [collection_name ][c .name ] = object
603
+ end )
604
+
605
+ -- update fields of collection arguments and input objects with other input
606
+ -- objects
607
+ for_each_1_1_connection (state , function (collection_name , c )
608
+ local new_object = lookup_input_objects [collection_name ][c .name ]
609
+ -- collection arguments
610
+ local fields = state .object_arguments [collection_name ]
611
+ assert (fields [c .name ] == nil ,
612
+ ' we must not add an input object twice to the same collection ' ..
613
+ ' arguments list' )
614
+ fields [c .name ] = new_object
615
+ -- input objects
616
+ for _ , input_object in ipairs (input_objects [collection_name ] or {}) do
617
+ local fields = input_object .fields
618
+ assert (fields [c .name ] == nil ,
619
+ ' we must not add an input object twice to the same input ' ..
620
+ ' object' )
621
+ fields [c .name ] = {
622
+ name = c .name ,
623
+ kind = new_object ,
624
+ }
625
+ end
626
+ end )
627
+ end
628
+
532
629
local function parse_cfg (cfg )
533
630
local state = {}
534
631
@@ -590,14 +687,25 @@ local function parse_cfg(cfg)
590
687
{skip_compound = true })
591
688
local list_args = convert_record_fields_to_args (
592
689
accessor :list_args (collection_name ))
593
- local args = utils .merge_tables (object_args , list_args )
594
690
595
691
state .object_arguments [collection_name ] = object_args
596
692
state .list_arguments [collection_name ] = list_args
693
+ end
694
+
695
+ add_connection_arguments (state )
696
+
697
+ -- fill all_arguments with object_arguments + list_arguments
698
+ for collection_name , collection in pairs (state .collections ) do
699
+ local object_args = state .object_arguments [collection_name ]
700
+ local list_args = state .list_arguments [collection_name ]
701
+
702
+ local args = utils .merge_tables (object_args , list_args )
597
703
state .all_arguments [collection_name ] = args
598
704
end
705
+
599
706
-- create fake root `Query` collection
600
707
create_root_collection (state )
708
+
601
709
return state
602
710
end
603
711
@@ -718,10 +826,14 @@ end
718
826
--- -- destination_args_values = <...>,
719
827
--- -- }
720
828
--- --
721
- --- -- extra is a table which contains additional data for the
722
- --- -- query; by now it consists of a single qcontext table,
723
- --- -- which can be used by accessor to store any query-related
724
- --- -- data
829
+ --- -- `extra` is a table which contains additional data for
830
+ --- -- the query:
831
+ --- --
832
+ --- -- * `qcontext` (table) can be used by an accessor to store
833
+ --- -- any query-related data;
834
+ --- -- * `resolveField(field_name, object, filter, opts)`
835
+ --- -- (function) for performing a subrequest on a fields
836
+ --- -- connected using a 1:1 or 1:1* connection.
725
837
--- --
726
838
--- return ...
727
839
--- end,
0 commit comments