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,13 @@ 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
+ -- show an error in the same format as accessor_space does
28
+ if type (err ) == ' table' and type (err .error ) == ' string' and
29
+ err .error :find (' :' ) then
30
+ error (err .error :gsub (' ^[^:]+: *' , ' ' ))
31
+ end
32
+
25
33
error ((' %s: %s' ):format (func_name , json .encode (err )))
26
34
end
27
35
@@ -215,6 +223,12 @@ local function space_operation(collection_name, nodes, operation, ...)
215
223
return master_result
216
224
end
217
225
226
+ local function get_shard_key_hash (key )
227
+ local shards_n = # shard .shards
228
+ local num = type (key ) == ' number' and key or digest .crc32 (key )
229
+ return 1 + digest .guava (num , shards_n )
230
+ end
231
+
218
232
-- }}}
219
233
220
234
--- Check whether a collection (it is sharded space for that accessor) exists.
395
409
396
410
--- Update a tuple with an update statements.
397
411
---
412
+ --- In case when the update should change the storage where the tuple stored
413
+ --- we perform insert to the new storage and delete from the old one. The
414
+ --- order must be 'first insert, then delete', because insert can report an
415
+ --- error in case of unique index constraints violation and we must not
416
+ --- perform delete in the case.
417
+ ---
418
+ --- This function emulates (more or less preciselly, see below) behaviour of
419
+ --- update as if it would be performed on a local tarantool instance. In case
420
+ --- when the tuple resides on the same storage the update operation performs
421
+ --- an unique index constraints check within the storage, but not on the
422
+ --- overall cluster. In case when the tuple changes its storage the insert
423
+ --- operation performs the check within the target storage.
424
+ ---
425
+ --- We can consider this as relaxing of the constraints: the function can
426
+ --- silently violate cluster-wide uniqueness constraints or report a
427
+ --- violation that was introduced by some previous operation, but cannot
428
+ --- report a violation when a local tarantool space would not.
429
+ ---
430
+ --- 'Insert, then update' approach is applicable and do not lead to a false
431
+ --- positive unique index constraint violation when storage nodes are different
432
+ --- and do not contain same tuples. We check the first condition in the
433
+ --- function and the second is guaranteed by the shard module.
434
+ ---
435
+ --- Note: if one want to use this function as basis for a similar one, but
436
+ --- allowing update of a primary key the following details should be noticed. A
437
+ --- primary key update that changes a storage where the tuple saved can be
438
+ --- performed with the 'insert, then delete' approach. An update within one
439
+ --- storage cannot be performed as update (because tarantool forbids update of
440
+ --- a primary key) and cannot be done with 'insert, then delete' way (because
441
+ --- insert can report an unique index constraint violation due to values in the
442
+ --- old version of the tuple). One can use 'delete, then insert' way and
443
+ --- perform the rollback action (insert old tuple) in case when insert of the
444
+ --- new tuple reports an error.
445
+ ---
398
446
--- @tparam table self accessor_general instance
399
447
---
400
448
--- @tparam string collection_name
@@ -420,16 +468,6 @@ local function update_tuple(self, collection_name, key, statements, opts)
420
468
421
469
shard_check_status (func_name )
422
470
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
471
-- We follow tarantool convention and disallow update of primary key parts.
434
472
local primary_index_info = get_index_info (collection_name , 0 )
435
473
for _ , statement in ipairs (statements ) do
@@ -443,14 +481,37 @@ local function update_tuple(self, collection_name, key, statements, opts)
443
481
end
444
482
end
445
483
484
+ local is_shard_key_to_be_updated = false
485
+ local new_shard_key_value
486
+ for _ , statement in ipairs (statements ) do
487
+ -- statement is {operator, field_no, value}
488
+ local field_no = statement [2 ]
489
+ if field_no == SHARD_KEY_FIELD_NO then
490
+ is_shard_key_to_be_updated = true
491
+ new_shard_key_value = statement [3 ]
492
+ break
493
+ end
494
+ end
495
+
496
+ local tuple = opts .tuple or get_tuple (self , collection_name , key )
497
+
498
+ local is_storage_to_be_changed = false
446
499
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 )
500
+ local old_shard_key_value = tuple [1 ]
501
+ local old_shard_key_hash = get_shard_key_hash (old_shard_key_value )
502
+ local new_shard_key_hash = get_shard_key_hash (new_shard_key_value )
503
+ is_storage_to_be_changed = old_shard_key_hash ~= new_shard_key_hash
504
+ end
505
+
506
+ if is_storage_to_be_changed then
507
+ -- different storages case
508
+ local old_tuple = opts .tuple or get_tuple (self , collection_name , key )
509
+ local new_tuple = old_tuple :update (statements )
510
+ self .funcs .insert_tuple (self , collection_name , new_tuple )
511
+ self .funcs .delete_tuple (self , collection_name , key , {tuple = old_tuple })
512
+ return new_tuple
451
513
else
452
- local tuple = opts .tuple or get_tuple (self , collection_name ,
453
- key )
514
+ -- one storage case
454
515
local nodes = shard .shard (tuple [SHARD_KEY_FIELD_NO ])
455
516
local tuple = space_operation (collection_name , nodes , ' update' , key ,
456
517
statements )
0 commit comments