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
+ --- a unique index constraints check within the storage, but not on the overall
422
+ --- cluster. In case when the tuple changes its storage the insert operation
423
+ --- 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 delete' 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 in the following ways:
440
+ ---
441
+ --- * as update (because tarantool forbids update of a primary key),
442
+ --- * 'insert, then delete' way (because insert can report a unique index
443
+ --- constraint violation due to values in the old version of the tuple),
444
+ --- * 'tuple:update(), then replace' (at least because old tuple resides in the
445
+ --- storage and because an other tuple can be silently rewritten).
446
+ ---
447
+ --- To support primary key update for **one storage** case one can use 'delete,
448
+ --- then insert' way and perform the rollback action (insert old tuple) in case
449
+ --- when insert of the new tuple reports an error. There are other ways, e.g.
450
+ --- manual unique constraints check.
451
+ ---
398
452
--- @tparam table self accessor_general instance
399
453
---
400
454
--- @tparam string collection_name
@@ -420,16 +474,6 @@ local function update_tuple(self, collection_name, key, statements, opts)
420
474
421
475
shard_check_status (func_name )
422
476
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
477
-- We follow tarantool convention and disallow update of primary key parts.
434
478
local primary_index_info = get_index_info (collection_name , 0 )
435
479
for _ , statement in ipairs (statements ) do
@@ -443,14 +487,37 @@ local function update_tuple(self, collection_name, key, statements, opts)
443
487
end
444
488
end
445
489
490
+ local is_shard_key_to_be_updated = false
491
+ local new_shard_key_value
492
+ for _ , statement in ipairs (statements ) do
493
+ -- statement is {operator, field_no, value}
494
+ local field_no = statement [2 ]
495
+ if field_no == SHARD_KEY_FIELD_NO then
496
+ is_shard_key_to_be_updated = true
497
+ new_shard_key_value = statement [3 ]
498
+ break
499
+ end
500
+ end
501
+
502
+ local tuple = opts .tuple or get_tuple (self , collection_name , key )
503
+
504
+ local is_storage_to_be_changed = false
446
505
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 )
506
+ local old_shard_key_value = tuple [1 ]
507
+ local old_shard_key_hash = get_shard_key_hash (old_shard_key_value )
508
+ local new_shard_key_hash = get_shard_key_hash (new_shard_key_value )
509
+ is_storage_to_be_changed = old_shard_key_hash ~= new_shard_key_hash
510
+ end
511
+
512
+ if is_storage_to_be_changed then
513
+ -- different storages case
514
+ local old_tuple = opts .tuple or get_tuple (self , collection_name , key )
515
+ local new_tuple = old_tuple :update (statements )
516
+ self .funcs .insert_tuple (self , collection_name , new_tuple )
517
+ self .funcs .delete_tuple (self , collection_name , key , {tuple = old_tuple })
518
+ return new_tuple
451
519
else
452
- local tuple = opts .tuple or get_tuple (self , collection_name ,
453
- key )
520
+ -- one storage case
454
521
local nodes = shard .shard (tuple [SHARD_KEY_FIELD_NO ])
455
522
local tuple = space_operation (collection_name , nodes , ' update' , key ,
456
523
statements )
0 commit comments