commit 280ede938294bba45b4157add8ea8b08a29ab373 from: Andrey Saranchin via: Serge Petrenko <35663196+sergepetrenko@users.noreply.github.com> date: Mon Aug 26 06:43:28 2024 UTC memtx: skip excluded tuples in index count with MVCC enabled Excluded tuples actually have their own history chains in MVCC - such chains consist of only one `memtx_story` containing excluded tuple itself. Such chains should be skipped when counting invisible tuples because they are not inserted to the index - that's what the commit does. Closes #10396 NO_DOC=bugfix (cherry picked from commit 8947cb04f59423e2944d48b8a1effec2fb11b1db) commit - 774b84d7e6f3d61000d64bbf91dd9b0784f30f5c commit + 280ede938294bba45b4157add8ea8b08a29ab373 blob - /dev/null blob + 65c81c27a0c2fa023348db25103b81f981f243d4 (mode 644) --- /dev/null +++ changelogs/unreleased/gh-10396-memtx-mvcc-exclude-null-count.md @@ -0,0 +1,5 @@ +## bugfix/memtx + +* Fixed a bug when `index:count()` could return a wrong number, raise the + last error, or fail with the `IllegalParams` error if the index has + the `exclude_null` attribute and MVCC is enabled (gh-10396). blob - e3bce440c5287c1432d1df2c13dc517d8b2e064f blob + af94b66b67f960dcabbfcaef25180f51de8681d5 --- src/box/memtx_tx.c +++ src/box/memtx_tx.c @@ -2902,6 +2902,17 @@ memtx_tx_index_invisible_count_slow(struct txn *txn, } assert(link->newer_story == NULL); + /* + * Excluded tuples have their own chains consisting of the only + * excluded story. Such stories must be skipped since they are + * not actually inserted to index. + */ + if (tuple_key_is_excluded(story->tuple, index->def->key_def, + MULTIKEY_NONE)) { + assert(link->older_story == NULL); + continue; + } + struct tuple *visible = NULL; bool is_prepared_ok = detect_whether_prepared_ok(txn); bool unused; blob - cdb98285daf6f6afafee5ef162293db3c9224ce6 blob + 94c8ee66cde9dba2b145218c30b071854fe7d4e2 --- test/box-luatest/gh_9954_mvcc_with_exclude_null_test.lua +++ test/box-luatest/gh_9954_mvcc_with_exclude_null_test.lua @@ -107,3 +107,53 @@ g.test_mvcc_with_exclude_null_space_drop = function() box.space.test:drop() end) end + +-- gh-10396 +g.test_mvcc_with_exclude_null_count = function() + g.server:exec(function() + local space = box.schema.space.create("TEST", { + format = { + {name = "ID", type = "unsigned", is_nullable = false}, + {name = "FLAG", type = "boolean", is_nullable = true}, + }, + }) + space:create_index("ID", { + unique = true, + type = "TREE", + parts = {{field = "ID", type = "unsigned"}}, + }) + space:create_index("FLAG", { + unique = false, + type = "TREE", + parts = {{ + field = "FLAG", + type = "boolean", + exclude_null = true, + is_nullable = true, + }}, + }) + + -- Wrap into transaction so that stories won't be deleted + box.begin() + -- Insert excluded tuples and then replace half of them + -- with non-excluded ones + for i = 1, 100 do + space:replace{i, box.NULL} + end + for i = 1, 100, 2 do + space:replace{i, true} + end + + -- Insert non-excluded tuples and then replace half of them + -- with excluded ones + for i = 101, 150 do + space:replace{i, false} + end + for i = 101, 150, 2 do + space:replace{i, box.NULL} + end + -- Check if count works correctly + t.assert_equals(space.index.FLAG:count({}, {iterator = 'ALL'}), 75) + box.commit() + end) +end