4
4
5
5
local json = require (' json' )
6
6
local yaml = require (' yaml' )
7
+ local digest = require (' digest' )
7
8
local utils = require (' graphql.utils' )
8
9
local shard = utils .optional_require (' shard' )
9
10
local accessor_general = require (' graphql.accessor_general' )
@@ -22,6 +23,18 @@ local index_info_cache = {}
22
23
23
24
local function shard_check_error (func_name , result , err )
24
25
if result ~= nil then return end
26
+
27
+ -- avoid json encoding of an error message (when the error is in the known
28
+ -- format)
29
+ if type (err ) == ' table' and type (err .error ) == ' string' then
30
+ error ({
31
+ message = err .error ,
32
+ extensions = {
33
+ shard_error = err ,
34
+ }
35
+ })
36
+ end
37
+
25
38
error ((' %s: %s' ):format (func_name , json .encode (err )))
26
39
end
27
40
@@ -215,6 +228,12 @@ local function space_operation(collection_name, nodes, operation, ...)
215
228
return master_result
216
229
end
217
230
231
+ local function get_shard_key_hash (key )
232
+ local shards_n = # shard .shards
233
+ local num = type (key ) == ' number' and key or digest .crc32 (key )
234
+ return 1 + digest .guava (num , shards_n )
235
+ end
236
+
218
237
-- }}}
219
238
220
239
--- Check whether a collection (it is sharded space for that accessor) exists.
395
414
396
415
--- Update a tuple with an update statements.
397
416
---
417
+ --- In case when the update should change the storage where the tuple stored
418
+ --- we perform insert to the new storage and delete from the old one. The
419
+ --- order must be 'first insert, then delete', because insert can report an
420
+ --- error in case of unique index constraints violation and we must not
421
+ --- perform delete in the case.
422
+ ---
423
+ --- This function emulates (more or less preciselly, see below) behaviour of
424
+ --- update as if it would be performed on a local tarantool instance. In case
425
+ --- when the tuple resides on the same storage the update operation performs
426
+ --- a unique index constraints check within the storage, but not on the overall
427
+ --- cluster. In case when the tuple changes its storage the insert operation
428
+ --- performs the check within the target storage.
429
+ ---
430
+ --- We can consider this as relaxing of the constraints: the function can
431
+ --- silently violate cluster-wide uniqueness constraints or report a
432
+ --- violation that was introduced by some previous operation, but cannot
433
+ --- report a violation when a local tarantool space would not.
434
+ ---
435
+ --- 'Insert, then delete' approach is applicable and do not lead to a false
436
+ --- positive unique index constraint violation when storage nodes are different
437
+ --- and do not contain same tuples. We check the first condition in the
438
+ --- function and the second is guaranteed by the shard module.
439
+ ---
440
+ --- Note: if one want to use this function as basis for a similar one, but
441
+ --- allowing update of a primary key the following details should be noticed. A
442
+ --- primary key update that **changes a storage** where the tuple saved can be
443
+ --- performed with the 'insert, then delete' approach. An update **within one
444
+ --- storage** cannot be performed in the following ways:
445
+ ---
446
+ --- * as update (because tarantool forbids update of a primary key),
447
+ --- * 'insert, then delete' way (because insert can report a unique index
448
+ --- constraint violation due to values in the old version of the tuple),
449
+ --- * 'tuple:update(), then replace' (at least because old tuple resides in the
450
+ --- storage and because an other tuple can be silently rewritten).
451
+ ---
452
+ --- To support primary key update for **one storage** case one can use 'delete,
453
+ --- then insert' way and perform the rollback action (insert old tuple) in case
454
+ --- when insert of the new tuple reports an error. There are other ways, e.g.
455
+ --- manual unique constraints check.
456
+ ---
398
457
--- @tparam table self accessor_general instance
399
458
---
400
459
--- @tparam string collection_name
@@ -420,16 +479,6 @@ local function update_tuple(self, collection_name, key, statements, opts)
420
479
421
480
shard_check_status (func_name )
422
481
423
- local is_shard_key_to_be_updated = false
424
- for _ , statement in ipairs (statements ) do
425
- -- statement is {operator, field_no, value}
426
- local field_no = statement [2 ]
427
- if field_no == SHARD_KEY_FIELD_NO then
428
- is_shard_key_to_be_updated = true
429
- break
430
- end
431
- end
432
-
433
482
-- We follow tarantool convention and disallow update of primary key parts.
434
483
local primary_index_info = get_index_info (collection_name , 0 )
435
484
for _ , statement in ipairs (statements ) do
@@ -443,14 +492,37 @@ local function update_tuple(self, collection_name, key, statements, opts)
443
492
end
444
493
end
445
494
495
+ local is_shard_key_to_be_updated = false
496
+ local new_shard_key_value
497
+ for _ , statement in ipairs (statements ) do
498
+ -- statement is {operator, field_no, value}
499
+ local field_no = statement [2 ]
500
+ if field_no == SHARD_KEY_FIELD_NO then
501
+ is_shard_key_to_be_updated = true
502
+ new_shard_key_value = statement [3 ]
503
+ break
504
+ end
505
+ end
506
+
507
+ local tuple = opts .tuple or get_tuple (self , collection_name , key )
508
+
509
+ local is_storage_to_be_changed = false
446
510
if is_shard_key_to_be_updated then
447
- local tuple = self .funcs .delete_tuple (self , collection_name , key ,
448
- {tuple = opts .tuple })
449
- tuple = tuple :update (statements )
450
- return self .funcs .insert_tuple (self , collection_name , tuple )
511
+ local old_shard_key_value = tuple [1 ]
512
+ local old_shard_key_hash = get_shard_key_hash (old_shard_key_value )
513
+ local new_shard_key_hash = get_shard_key_hash (new_shard_key_value )
514
+ is_storage_to_be_changed = old_shard_key_hash ~= new_shard_key_hash
515
+ end
516
+
517
+ if is_storage_to_be_changed then
518
+ -- different storages case
519
+ local old_tuple = opts .tuple or get_tuple (self , collection_name , key )
520
+ local new_tuple = old_tuple :update (statements )
521
+ self .funcs .insert_tuple (self , collection_name , new_tuple )
522
+ self .funcs .delete_tuple (self , collection_name , key , {tuple = old_tuple })
523
+ return new_tuple
451
524
else
452
- local tuple = opts .tuple or get_tuple (self , collection_name ,
453
- key )
525
+ -- one storage case
454
526
local nodes = shard .shard (tuple [SHARD_KEY_FIELD_NO ])
455
527
local tuple = space_operation (collection_name , nodes , ' update' , key ,
456
528
statements )
0 commit comments