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

Commit 090eb18

Browse files
committed
Implement delete mutation
Forbid passing "insert", "update" or "delete" with each other in an one argument list. Part of #142.
1 parent e442f40 commit 090eb18

File tree

5 files changed

+340
-44
lines changed

5 files changed

+340
-44
lines changed

README.md

+33-1
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,7 @@ Consider the following details:
294294
pass to the `update` argument. This type / argument requires a user to set
295295
subset of fields of an updating object except primary key parts.
296296
* A mutation with an `update` argument always return the updated object.
297+
* The `update` argument is forbidden with `insert` or `delete` arguments.
297298
* The `update` argument is forbidden in `query` requests.
298299
* Objects are selected by filters first, then updated using a statement in the
299300
`update` argument, then connected objects are selected.
@@ -308,7 +309,38 @@ Consider the following details:
308309

309310
#### Delete
310311

311-
TBD
312+
Example:
313+
314+
```
315+
mutation delete_user_and_order(
316+
$user_id: String,
317+
$order_id: String,
318+
) {
319+
user_collection(user_id: $user_id, delete: true) {
320+
user_id
321+
first_name
322+
last_name
323+
}
324+
order_collection(order_id: $order_id, delete: true) {
325+
order_id
326+
description
327+
in_stock
328+
}
329+
}
330+
```
331+
332+
Consider the following details:
333+
334+
* There are no special type name for a `delete` argument, it is just Boolean.
335+
* A mutation with a `delete: true` argument always return the deleted object.
336+
* The `delete` argument is forbidden with `insert` or `update` arguments.
337+
* The `delete` argument is forbidden in `query` requests.
338+
* The same fields traversal order and 'select -> change -> select connected'
339+
order of operations for an one field are applied likewise for the `update`
340+
argument.
341+
* The `limit` argument can be used to define how many objects are subject to
342+
deletion and `offset` can help with adjusting start point of multi-object
343+
delete operation.
312344

313345
## GraphiQL
314346
```

graphql/accessor_general.lua

+95-31
Original file line numberDiff line numberDiff line change
@@ -950,6 +950,65 @@ local function process_tuple(state, tuple, opts)
950950
return true
951951
end
952952

953+
--- Get schema name by a collection name.
954+
---
955+
--- @tparam table self data accessor instance
956+
---
957+
--- @tparam string collection_name
958+
---
959+
--- @treturn string `schema_name`
960+
local function get_schema_name(self, collection_name)
961+
local collection = self.collections[collection_name]
962+
assert(collection ~= nil,
963+
('cannot find the collection "%s"'):format(collection_name))
964+
local schema_name = collection.schema_name
965+
assert(type(schema_name) == 'string',
966+
'schema_name must be a string, got ' .. type(schema_name))
967+
return schema_name
968+
end
969+
970+
--- Call one of accessor function: `update_tuple` or `delete_tuple` for each
971+
--- selected object.
972+
---
973+
--- @tparam table self data accessor instance
974+
---
975+
--- @tparam string collection_name
976+
---
977+
--- @tparam string schema_name
978+
---
979+
--- @tparam table selected objects to perform the operation
980+
---
981+
--- @tparam string operation 'update_tuple' or 'delete_tuple'
982+
---
983+
--- @param[opt] ... parameters to pass to the function
984+
---
985+
--- @treturn table `new_objects` list of returned objects (in the order of the
986+
--- `selected` parameter)
987+
local function perform_primary_key_operation(self, collection_name, schema_name,
988+
selected, operation, ...)
989+
check(operation, 'operation', 'string')
990+
991+
local _, primary_index_meta = get_primary_index_meta(self, collection_name)
992+
993+
local new_objects = {}
994+
995+
for _, object in ipairs(selected) do
996+
local key = {}
997+
for _, field_name in ipairs(primary_index_meta.fields) do
998+
table.insert(key, object[field_name])
999+
end
1000+
-- XXX: we can pass a tuple corresponding the object to update_tuple /
1001+
-- delete_tuple to save one get operation
1002+
local new_tuple = self.funcs[operation](collection_name, key, ...)
1003+
local new_object = self.funcs.unflatten_tuple(collection_name,
1004+
new_tuple, {use_tomap = self.collection_use_tomap[collection_name]},
1005+
self.default_unflatten_tuple[schema_name])
1006+
table.insert(new_objects, new_object)
1007+
end
1008+
1009+
return new_objects
1010+
end
1011+
9531012
--- The function is core of this module and implements logic of fetching and
9541013
--- filtering requested objects.
9551014
---
@@ -1117,17 +1176,13 @@ local function insert_internal(self, collection_name, from, filter, args, extra)
11171176
local err_msg = '"insert" must be the only argument when it is present'
11181177
assert(next(filter) == nil, err_msg)
11191178
assert(next(args) == nil, err_msg)
1179+
assert(next(extra.extra_args, next(extra.extra_args)) == nil, err_msg)
11201180
check(from, 'from', 'table')
11211181
-- allow only top level collection
11221182
check(from.collection_name, 'from.collection_name', 'nil')
11231183

11241184
-- convert object -> tuple (set default values from a schema)
1125-
local collection = self.collections[collection_name]
1126-
assert(collection ~= nil,
1127-
('cannot find the collection "%s"'):format(collection_name))
1128-
local schema_name = collection.schema_name
1129-
assert(type(schema_name) == 'string',
1130-
'schema_name must be a string, got ' .. type(schema_name))
1185+
local schema_name = get_schema_name(self, collection_name)
11311186
local default_flatten_object = self.default_flatten_object[schema_name]
11321187
assert(default_flatten_object ~= nil,
11331188
('cannot find default_flatten_object ' ..
@@ -1164,42 +1219,44 @@ local function update_internal(self, collection_name, extra, selected)
11641219
local xobject = extra.extra_args.update
11651220
if xobject == nil then return nil end
11661221

1167-
-- convert xobject -> update statements
1168-
local collection = self.collections[collection_name]
1169-
assert(collection ~= nil,
1170-
('cannot find the collection "%s"'):format(collection_name))
1171-
local schema_name = collection.schema_name
1172-
assert(type(schema_name) == 'string',
1173-
'schema_name must be a string, got ' .. type(schema_name))
1222+
local err_msg = '"update" must not be passed with "insert" or "delete" ' ..
1223+
'arguments'
1224+
assert(next(extra.extra_args, next(extra.extra_args)) == nil, err_msg)
11741225

1226+
-- convert xobject -> update statements
1227+
local schema_name = get_schema_name(self, collection_name)
11751228
local default_xflatten = self.default_xflatten[schema_name]
11761229
assert(default_xflatten ~= nil,
11771230
('cannot find default_xflatten ' ..
11781231
'for collection "%s"'):format(collection_name))
1179-
11801232
local statements = self.funcs.xflatten(collection_name, xobject, {
11811233
service_fields_defaults =
11821234
self.service_fields_defaults[schema_name],
11831235
}, default_xflatten)
11841236

1185-
local _, primary_index_meta = get_primary_index_meta(self, collection_name)
1186-
local new_objects = {}
1187-
for _, object in ipairs(selected) do
1188-
local key = {}
1189-
for _, field_name in ipairs(primary_index_meta.fields) do
1190-
table.insert(key, object[field_name])
1191-
end
1192-
-- XXX: we can pass a tuple corresponding the object to update_tuple to
1193-
-- save one get operation
1194-
local new_tuple = self.funcs.update_tuple(collection_name, key,
1195-
statements)
1196-
local new_object = self.funcs.unflatten_tuple(collection_name,
1197-
new_tuple, {use_tomap = self.collection_use_tomap[collection_name]},
1198-
self.default_unflatten_tuple[schema_name])
1199-
table.insert(new_objects, new_object)
1200-
end
1237+
return perform_primary_key_operation(self, collection_name, schema_name,
1238+
selected, 'update_tuple', statements)
1239+
end
12011240

1202-
return new_objects
1241+
--- Delete an object.
1242+
---
1243+
--- Parameters are the same as for @{select_update}.
1244+
---
1245+
--- @tparam table extra `extra.extra_args.delete` is used
1246+
---
1247+
--- @treturn table `new_objects` list of deleted objects (in the order of the
1248+
--- `selected` parameter)
1249+
local function delete_internal(self, collection_name, extra, selected)
1250+
if not extra.extra_args.delete then return nil end
1251+
1252+
local err_msg = '"delete" must not be passed with "insert" or "update" ' ..
1253+
'arguments'
1254+
assert(next(extra.extra_args, next(extra.extra_args)) == nil, err_msg)
1255+
1256+
local schema_name = get_schema_name(self, collection_name)
1257+
1258+
return perform_primary_key_operation(self, collection_name, schema_name,
1259+
selected, 'delete_tuple')
12031260
end
12041261

12051262
--- Set of asserts to check the `funcs` argument of the @{accessor_general.new}
@@ -1234,6 +1291,9 @@ local function validate_funcs(funcs)
12341291
assert(type(funcs.update_tuple) == 'function',
12351292
'funcs.update_tuple must be a function, got ' ..
12361293
type(funcs.update_tuple))
1294+
assert(type(funcs.delete_tuple) == 'function',
1295+
'funcs.delete_tuple must be a function, got ' ..
1296+
type(funcs.delete_tuple))
12371297
end
12381298

12391299
--- This function is called on first select related to a query. Its purpose is
@@ -1685,6 +1745,10 @@ function accessor_general.new(opts, funcs)
16851745
selected)
16861746
if updated ~= nil then return updated end
16871747

1748+
local deleted = delete_internal(self, collection_name, extra,
1749+
selected)
1750+
if deleted ~= nil then return deleted end
1751+
16881752
return selected
16891753
end,
16901754
list_args = list_args,

graphql/accessor_shard.lua

+1
Original file line numberDiff line numberDiff line change
@@ -470,6 +470,7 @@ function accessor_shard.new(opts, funcs)
470470
xflatten = funcs.xflatten or xflatten,
471471
insert_tuple = funcs.insert_tuple or insert_tuple,
472472
update_tuple = funcs.update_tuple or update_tuple,
473+
delete_tuple = funcs.delete_tuple or delete_tuple,
473474
}
474475

475476
return accessor_general.new(opts, res_funcs)

graphql/accessor_space.lua

+18
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,23 @@ local function update_tuple(collection_name, key, statements, opts)
117117
return box.space[collection_name]:update(key, statements)
118118
end
119119

120+
--- Delete tuple by a primary key.
121+
---
122+
--- @tparam string collection_name
123+
---
124+
--- @param key primary key
125+
---
126+
--- @tparam table opts
127+
---
128+
--- * tuple (ignored in accessor_space)
129+
---
130+
--- @treturn cdata tuple
131+
local function delete_tuple(collection_name, key, opts)
132+
local opts = opts or {}
133+
check(opts, 'opts', 'table')
134+
return box.space[collection_name]:delete(key)
135+
end
136+
120137
--- Create a new space data accessor instance.
121138
function accessor_space.new(opts, funcs)
122139
local funcs = funcs or {}
@@ -140,6 +157,7 @@ function accessor_space.new(opts, funcs)
140157
xflatten = funcs.xflatten or xflatten,
141158
insert_tuple = funcs.insert_tuple or insert_tuple,
142159
update_tuple = funcs.update_tuple or update_tuple,
160+
delete_tuple = funcs.delete_tuple or delete_tuple,
143161
}
144162

145163
return accessor_general.new(opts, res_funcs)

0 commit comments

Comments
 (0)