@@ -359,7 +359,7 @@ local function separate_args_instance(args_instance, connection_args,
359
359
else
360
360
error ((' cannot found "%s" field ("%s" value) ' ..
361
361
' within allowed fields' ):format (tostring (k ),
362
- tostring (v )))
362
+ json . encode (v )))
363
363
end
364
364
end
365
365
return object_args_instance , list_args_instance
@@ -388,23 +388,44 @@ local function convert_simple_connection(state, connection, collection_name)
388
388
-- gql type of connection field
389
389
local destination_type =
390
390
state .nullable_collection_types [c .destination_collection ]
391
-
392
391
assert (destination_type ~= nil ,
393
392
(' destination_type (named %s) must not be nil' ):format (
394
393
c .destination_collection ))
395
-
394
+ local raw_destination_type = destination_type
396
395
397
396
local c_args = args_from_destination_collection (state ,
398
- c .destination_collection , c .type )
397
+ c .destination_collection , c .type )
399
398
destination_type = specify_destination_type (destination_type , c .type )
400
399
401
400
local c_list_args = state .list_arguments [c .destination_collection ]
402
401
402
+ -- capture `raw_destination_type`
403
+ local function genResolveField (info )
404
+ return function (field_name , object , filter , opts )
405
+ assert (raw_destination_type .fields [field_name ],
406
+ (' performing a subrequest by the non-existent ' ..
407
+ ' field "%s" of the collection "%s"' ):format (field_name ,
408
+ c .destination_collection ))
409
+ return raw_destination_type .fields [field_name ].resolve (
410
+ object , filter , info , opts )
411
+ end
412
+ end
413
+
403
414
local field = {
404
415
name = c .name ,
405
416
kind = destination_type ,
406
417
arguments = c_args ,
407
- resolve = function (parent , args_instance , info )
418
+ resolve = function (parent , args_instance , info , opts )
419
+ local opts = opts or {}
420
+ assert (type (opts ) == ' table' ,
421
+ ' opts must be nil or a table, got ' .. type (opts ))
422
+ local dont_force_nullability =
423
+ opts .dont_force_nullability or false
424
+ assert (type (dont_force_nullability ) == ' boolean' ,
425
+ ' opts.dont_force_nullability ' ..
426
+ ' must be nil or a boolean, got ' ..
427
+ type (dont_force_nullability ))
428
+
408
429
local destination_args_names , destination_args_values =
409
430
parent_args_values (parent , c .parts )
410
431
@@ -432,8 +453,10 @@ local function convert_simple_connection(state, connection, collection_name)
432
453
destination_args_names = destination_args_names ,
433
454
destination_args_values = destination_args_values ,
434
455
}
456
+ local resolveField = genResolveField (info )
435
457
local extra = {
436
- qcontext = info .qcontext
458
+ qcontext = info .qcontext ,
459
+ resolveField = resolveField , -- for subrequests
437
460
}
438
461
439
462
-- object_args_instance will be passed to 'filter'
@@ -451,7 +474,8 @@ local function convert_simple_connection(state, connection, collection_name)
451
474
-- we expect here exactly one object even for 1:1*
452
475
-- connections because we processed all-parts-are-null
453
476
-- situation above
454
- assert (# objs == 1 , ' expect one matching object, got ' ..
477
+ assert (# objs == 1 or dont_force_nullability ,
478
+ ' expect one matching object, got ' ..
455
479
tostring (# objs ))
456
480
return objs [1 ]
457
481
else -- c.type == '1:N'
@@ -778,6 +802,84 @@ local function create_root_collection(state)
778
802
})
779
803
end
780
804
805
+ --- Execute a function for each 1:1 or 1:1* connection of each collection.
806
+ ---
807
+ --- @tparam table state tarantool_graphql instance
808
+ ---
809
+ --- @tparam function func a function with the following parameters:
810
+ ---
811
+ --- * source collection name (string);
812
+ --- * connection (table).
813
+ local function for_each_1_1_connection (state , func )
814
+ for collection_name , collection in pairs (state .collections ) do
815
+ for _ , c in ipairs (collection .connections or {}) do
816
+ if c .type == ' 1:1' or c .type == ' 1:1*' then
817
+ func (collection_name , c )
818
+ end
819
+ end
820
+ end
821
+ end
822
+
823
+ --- Add arguments corresponding to 1:1 and 1:1* connections (nested filters).
824
+ ---
825
+ --- @tparam table state graphql_tarantool instance
826
+ local function add_connection_arguments (state )
827
+ -- map destination collection to list of input objects
828
+ local input_objects = {}
829
+ -- map source collection and connection name to an input object
830
+ local lookup_input_objects = {}
831
+
832
+ -- create InputObjects for each 1:1 or 1:1* connection of each collection
833
+ for_each_1_1_connection (state , function (collection_name , c )
834
+ -- XXX: support union collections
835
+ if c .variants ~= nil then return end
836
+
837
+ local object = types .inputObject ({
838
+ name = c .name ,
839
+ description = (' generated from the connection "%s" ' ..
840
+ ' of collection "%s" using collection "%s"' ):format (
841
+ c .name , collection_name , c .destination_collection ),
842
+ fields = state .object_arguments [c .destination_collection ],
843
+ })
844
+
845
+ if input_objects [c .destination_collection ] == nil then
846
+ input_objects [c .destination_collection ] = {}
847
+ end
848
+ table.insert (input_objects [c .destination_collection ], object )
849
+
850
+ if lookup_input_objects [collection_name ] == nil then
851
+ lookup_input_objects [collection_name ] = {}
852
+ end
853
+ lookup_input_objects [collection_name ][c .name ] = object
854
+ end )
855
+
856
+ -- update fields of collection arguments and input objects with other input
857
+ -- objects
858
+ for_each_1_1_connection (state , function (collection_name , c )
859
+ -- XXX: support union collections
860
+ if c .variants ~= nil then return end
861
+
862
+ local new_object = lookup_input_objects [collection_name ][c .name ]
863
+ -- collection arguments
864
+ local fields = state .object_arguments [collection_name ]
865
+ assert (fields [c .name ] == nil ,
866
+ ' we must not add an input object twice to the same collection ' ..
867
+ ' arguments list' )
868
+ fields [c .name ] = new_object
869
+ -- input objects
870
+ for _ , input_object in ipairs (input_objects [collection_name ] or {}) do
871
+ local fields = input_object .fields
872
+ assert (fields [c .name ] == nil ,
873
+ ' we must not add an input object twice to the same input ' ..
874
+ ' object' )
875
+ fields [c .name ] = {
876
+ name = c .name ,
877
+ kind = new_object ,
878
+ }
879
+ end
880
+ end )
881
+ end
882
+
781
883
local function parse_cfg (cfg )
782
884
local state = {}
783
885
@@ -839,14 +941,25 @@ local function parse_cfg(cfg)
839
941
{skip_compound = true })
840
942
local list_args = convert_record_fields_to_args (
841
943
accessor :list_args (collection_name ))
842
- local args = utils .merge_tables (object_args , list_args )
843
944
844
945
state .object_arguments [collection_name ] = object_args
845
946
state .list_arguments [collection_name ] = list_args
947
+ end
948
+
949
+ add_connection_arguments (state )
950
+
951
+ -- fill all_arguments with object_arguments + list_arguments
952
+ for collection_name , collection in pairs (state .collections ) do
953
+ local object_args = state .object_arguments [collection_name ]
954
+ local list_args = state .list_arguments [collection_name ]
955
+
956
+ local args = utils .merge_tables (object_args , list_args )
846
957
state .all_arguments [collection_name ] = args
847
958
end
959
+
848
960
-- create fake root `Query` collection
849
961
create_root_collection (state )
962
+
850
963
return state
851
964
end
852
965
@@ -967,10 +1080,14 @@ end
967
1080
--- -- destination_args_values = <...>,
968
1081
--- -- }
969
1082
--- --
970
- --- -- extra is a table which contains additional data for the
971
- --- -- query; by now it consists of a single qcontext table,
972
- --- -- which can be used by accessor to store any query-related
973
- --- -- data
1083
+ --- -- `extra` is a table which contains additional data for
1084
+ --- -- the query:
1085
+ --- --
1086
+ --- -- * `qcontext` (table) can be used by an accessor to store
1087
+ --- -- any query-related data;
1088
+ --- -- * `resolveField(field_name, object, filter, opts)`
1089
+ --- -- (function) for performing a subrequest on a fields
1090
+ --- -- connected using a 1:1 or 1:1* connection.
974
1091
--- --
975
1092
--- return ...
976
1093
--- end,
0 commit comments