Skip to content
This repository was archived by the owner on Apr 14, 2022. It is now read-only.

Commit 100a2ec

Browse files
committed
Remove 1:1* connection, change 1:1 behaviour
Changes in 1:1 connection behaviour: * The case when all connection parts are null is allowed (gives null child object instead of an error). * The case when no child object were matched is allowed. - FULL MATCH contraint does not verified entirely (further work is needed). - The following bug was fixed: usage of filtering arguments now cannot trigger 'expect one matching object' error if data are consistent. Other changes: * GraphQL auto configuration behaviour was changed in case when a connection parts count is the same with underlying index, the index is unique and some index parts are nullable and some are not. Now it generates 1:1 connection (and lean on runtime checks) instead of giving an error. Part of #135.
1 parent 08704ed commit 100a2ec

File tree

8 files changed

+31
-102
lines changed

8 files changed

+31
-102
lines changed

graphql/accessor_general.lua

+1-2
Original file line numberDiff line numberDiff line change
@@ -883,8 +883,7 @@ local function process_tuple(self, state, tuple, opts)
883883
if obj[k] == nil then
884884
local field_name = k
885885
local sub_filter = v
886-
local sub_opts = {dont_force_nullability = true}
887-
local field = resolveField(field_name, obj, sub_filter, sub_opts)
886+
local field = resolveField(field_name, obj, sub_filter)
888887
if field == nil then return true end
889888
obj[k] = field
890889
-- XXX: Remove the value from a filter? But then we need to copy

graphql/config_complement.lua

+11-55
Original file line numberDiff line numberDiff line change
@@ -9,64 +9,25 @@
99
local json = require('json')
1010
local yaml = require('yaml')
1111
local log = require('log')
12+
1213
local utils = require('graphql.utils')
1314
local check = utils.check
14-
local get_spaces_formats = require('graphql.simple_config').get_spaces_formats
1515

1616
local config_complement = {}
1717

18-
--- The function determines connection type by connection.parts
19-
--- and source collection space format.
20-
---
21-
--- XXX Currently there are two possible situations when connection_parts form
22-
--- unique index - all source_fields are nullable (1:1*) or all source_fields
23-
--- are non nullable (1:1). In case of partially nullable connection_parts (which
24-
--- form unique index) the error is raised. There is an alternative: relax
25-
--- this requirement and deduce non-null connection type in the case.
26-
local function determine_connection_type(connection_parts, index, source_space_format)
27-
local type
28-
29-
if #connection_parts < #(index.fields) then
30-
type = '1:N'
31-
end
32-
33-
if #connection_parts == #(index.fields) then
34-
if index.unique then
35-
type = '1:1'
36-
else
37-
type = '1:N'
38-
end
39-
end
40-
41-
local is_all_nullable = true
42-
local is_all_not_nullable = true
43-
44-
for _, connection_part in pairs(connection_parts) do
45-
for _,field_format in ipairs(source_space_format) do
46-
if connection_part.source_field == field_format.name then
47-
if field_format.is_nullable == true then
48-
is_all_not_nullable = false
49-
else
50-
is_all_nullable = false
51-
end
52-
end
53-
end
54-
end
55-
56-
if is_all_nullable == is_all_not_nullable and type == '1:1' then
57-
error('source_fields in connection_parts must be all nullable or ' ..
58-
'not nullable at the same time')
18+
--- Determine connection type by connection.parts and index uniqueness.
19+
local function determine_connection_type(connection_parts, index)
20+
if #connection_parts < #index.fields then
21+
return '1:N'
22+
elseif #connection_parts == #index.fields then
23+
return index.unique and '1:1' or '1:N'
5924
end
6025

61-
if is_all_nullable and type == '1:1' then
62-
type = '1:1*'
63-
end
64-
65-
return type
26+
error(('Connection parts count is more then index parts count: %d > %d')
27+
:format(#connection_parts, #index.fields))
6628
end
6729

68-
-- The function returns connection_parts sorted by destination_fields as
69-
-- index_fields prefix.
30+
-- Return connection_parts sorted by destination_fields as index_fields prefix.
7031
local function sort_parts(connection_parts, index_fields)
7132
local sorted_parts = {}
7233

@@ -211,8 +172,6 @@ local function complement_connections(collections, connections, indexes)
211172
check(collections, 'collections', 'table')
212173
check(connections, 'connections', 'table')
213174

214-
local spaces_formats = get_spaces_formats()
215-
216175
for _, c in pairs(connections) do
217176
check(c.name, 'connection.name', 'string')
218177
check(c.source_collection, 'connection.source_collection', 'string')
@@ -230,10 +189,7 @@ local function complement_connections(collections, connections, indexes)
230189
result_c.destination_collection = c.destination_collection
231190
result_c.parts = determine_connection_parts(c.parts, index)
232191

233-
local source_space_format = spaces_formats[result_c.source_collection]
234-
235-
result_c.type = determine_connection_type(result_c.parts, index,
236-
source_space_format)
192+
result_c.type = determine_connection_type(result_c.parts, index)
237193
result_c.index_name = c.index_name
238194
result_c.name = c.name
239195

graphql/convert_schema/resolve.lua

+4-23
Original file line numberDiff line numberDiff line change
@@ -106,29 +106,16 @@ function resolve.gen_resolve_function(collection_name, connection,
106106
local opts = opts or {}
107107
assert(type(opts) == 'table',
108108
'opts must be nil or a table, got ' .. type(opts))
109-
local dont_force_nullability =
110-
opts.dont_force_nullability or false
111-
assert(type(dont_force_nullability) == 'boolean',
112-
'opts.dont_force_nullability ' ..
113-
'must be nil or a boolean, got ' ..
114-
type(dont_force_nullability))
109+
-- no opts for now
115110

116111
local from = gen_from_parameter(collection_name, parent, c)
117112

118113
-- Avoid non-needed index lookup on a destination collection when
119114
-- all connection parts are null:
120-
-- * return null for 1:1* connection;
115+
-- * return null for 1:1 connection;
121116
-- * return {} for 1:N connection (except the case when source
122117
-- collection is the query or the mutation pseudo-collection).
123118
if collection_name ~= nil and are_all_parts_null(parent, c.parts) then
124-
if c.type ~= '1:1*' and c.type ~= '1:N' then
125-
-- `if` is to avoid extra json.encode
126-
assert(c.type == '1:1*' or c.type == '1:N',
127-
('only 1:1* or 1:N connections can have ' ..
128-
'all key parts null; parent is %s from ' ..
129-
'collection "%s"'):format(json.encode(parent),
130-
tostring(collection_name)))
131-
end
132119
return c.type == '1:N' and {} or nil
133120
end
134121

@@ -152,14 +139,8 @@ function resolve.gen_resolve_function(collection_name, connection,
152139
assert(type(objs) == 'table',
153140
'objs list received from an accessor ' ..
154141
'must be a table, got ' .. type(objs))
155-
if c.type == '1:1' or c.type == '1:1*' then
156-
-- we expect here exactly one object even for 1:1*
157-
-- connections because we processed all-parts-are-null
158-
-- situation above
159-
assert(#objs == 1 or dont_force_nullability,
160-
'expect one matching object, got ' ..
161-
tostring(#objs))
162-
return objs[1]
142+
if c.type == '1:1' then
143+
return objs[1] -- nil for empty list of matching objects
163144
else -- c.type == '1:N'
164145
return objs
165146
end

graphql/convert_schema/schema.lua

+7-6
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ local schema = {}
2525
--- * DONE: Move avro-schema -> GraphQL arguments translating into its own
2626
--- module.
2727
--- * DONE: Support a sub-record arguments and others (union, array, ...).
28-
--- * TBD: Generate arguments for cartesian product of {1:1, 1:1*, 1:N, all} x
28+
--- * TBD: Generate arguments for cartesian product of {1:1, 1:N, all} x
2929
--- {query, mutation, all} x {top-level, nested, all} x {collections}.
3030
--- * TBD: Use generated arguments in GraphQL types (schema) generation.
3131
---
@@ -138,7 +138,8 @@ local function create_root_collection(state)
138138
})
139139
end
140140

141-
--- Execute a function for each 1:1 or 1:1* connection of each collection.
141+
--- Execute a function for each connection of one of specified types in each
142+
--- collection.
142143
---
143144
--- @tparam table state tarantool_graphql instance
144145
---
@@ -160,7 +161,7 @@ local function for_each_connection(state, connection_types, func)
160161
end
161162
end
162163

163-
--- Add arguments corresponding to 1:1 and 1:1* connections (nested filters).
164+
--- Add arguments corresponding to 1:1 connections (nested filters).
164165
---
165166
--- @tparam table state graphql_tarantool instance
166167
local function add_connection_arguments(state)
@@ -169,8 +170,8 @@ local function add_connection_arguments(state)
169170
-- map source collection and connection name to an input object
170171
local lookup_input_objects = {}
171172

172-
-- create InputObjects for each 1:1 or 1:1* connection of each collection
173-
for_each_connection(state, {'1:1', '1:1*'}, function(collection_name, c)
173+
-- create InputObjects for each 1:1 connection of each collection
174+
for_each_connection(state, {'1:1'}, function(collection_name, c)
174175
-- XXX: support multihead connections
175176
if c.variants ~= nil then return end
176177

@@ -195,7 +196,7 @@ local function add_connection_arguments(state)
195196

196197
-- update fields of collection arguments and input objects with other input
197198
-- objects
198-
for_each_connection(state, {'1:1', '1:1*'}, function(collection_name, c)
199+
for_each_connection(state, {'1:1'}, function(collection_name, c)
199200
-- XXX: support multihead connections
200201
if c.variants ~= nil then return end
201202

graphql/convert_schema/types.lua

+3-11
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,6 @@ local function args_from_destination_collection(state, collection,
4949
connection_type)
5050
if connection_type == '1:1' then
5151
return state.object_arguments[collection]
52-
elseif connection_type == '1:1*' then
53-
return state.object_arguments[collection]
5452
elseif connection_type == '1:N' then
5553
return state.all_arguments[collection]
5654
else
@@ -60,8 +58,6 @@ end
6058

6159
local function specify_destination_type(destination_type, connection_type)
6260
if connection_type == '1:1' then
63-
return core_types.nonNull(destination_type)
64-
elseif connection_type == '1:1*' then
6561
return destination_type
6662
elseif connection_type == '1:N' then
6763
return core_types.nonNull(core_types.list(core_types.nonNull(
@@ -77,7 +73,7 @@ end
7773
--- described in comments to @{convert_multihead_connection}.
7874
---
7975
--- @tparam table type_to_box GraphQL Object type (which represents a collection)
80-
--- @tparam string connection_type of given collection (1:1, 1:1* or 1:N)
76+
--- @tparam string connection_type of given collection (1:1, 1:N)
8177
--- @tparam string type_to_box_name name of given 'type_to_box' (It can not
8278
--- be taken from 'type_to_box' because at the time of function execution
8379
--- 'type_to_box' refers to an empty table, which later will be filled with
@@ -96,9 +92,6 @@ local function box_collection_type(type_to_box, connection_type,
9692
if connection_type == '1:1' then
9793
box_type_name = 'box_' .. type_to_box_name
9894
box_type_description = 'Box around 1:1 multi-head variant'
99-
elseif connection_type == '1:1*' then
100-
box_type_name = 'box_' .. type_to_box_name
101-
box_type_description = 'Box around 1:1* multi-head variant'
10295
elseif connection_type == '1:N' then
10396
box_type_name = 'box_array_' .. type_to_box_name
10497
box_type_description = 'Box around 1:N multi-head variant'
@@ -317,9 +310,8 @@ end
317310
local convert_connection_to_field = function(state, connection, collection_name,
318311
context)
319312
check(connection.type, 'connection.type', 'string')
320-
assert(connection.type == '1:1' or connection.type == '1:1*' or
321-
connection.type == '1:N', 'connection.type must be 1:1, 1:1* or 1:N, '..
322-
'got ' .. connection.type)
313+
assert(connection.type == '1:1' or connection.type == '1:N',
314+
'connection.type must be 1:1 or 1:N, got ' .. connection.type)
323315
check(connection.name, 'connection.name', 'string')
324316
assert(connection.destination_collection or connection.variants,
325317
'connection must either destination_collection or variants field')

graphql/impl.lua

+1-1
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,7 @@ end
244244
--- -- any query-related data;
245245
--- -- * `resolveField(field_name, object, filter, opts)`
246246
--- -- (function) for performing a subrequest on a fields
247-
--- -- connected using a 1:1 or 1:1* connection.
247+
--- -- connected using a 1:1 connection.
248248
--- --
249249
--- return ...
250250
--- end,

test/extra/to_avro_huge.test.lua

+3-3
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ fields:
114114
type: int
115115
- name: order_item__item
116116
type:
117-
type: record
117+
type: record*
118118
fields:
119119
- name: id
120120
type: int
@@ -144,7 +144,7 @@ fields:
144144
type: string
145145
- name: user_connection
146146
type:
147-
type: record
147+
type: record*
148148
fields:
149149
- name: id
150150
type: int
@@ -168,7 +168,7 @@ fields:
168168
fields:
169169
- name: order_item__item
170170
type:
171-
type: record
171+
type: record*
172172
fields:
173173
- name: name
174174
type: string

test/testdata/nullable_1_1_conn_testdata.lua

+1-1
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ function nullable_1_1_conn_testdata.get_test_metadata()
5151
"index_name": "in_reply_to"
5252
},
5353
{
54-
"type": "1:1*",
54+
"type": "1:1",
5555
"name": "in_reply_to",
5656
"destination_collection": "email",
5757
"parts": [

0 commit comments

Comments
 (0)