From ff4e59a5bd5127290e9fb2ebf4f7f38078a1b64a Mon Sep 17 00:00:00 2001 From: Georgy Moshkin <gmoshkin@picodata.io> Date: Fri, 8 Sep 2023 20:15:28 +0300 Subject: [PATCH] box: introduce box_read_view_* ffi API functions Introduce API for opening a read view over the specified spaces and indexes and creating iterators over the index read views. NO_DOC=picodata patch --- changelogs/unreleased/box_read_view.md | 4 + extra/exports | 5 + src/CMakeLists.txt | 1 + src/box/read_view.c | 130 +++++++++++++ src/box/read_view.h | 99 ++++++++++ test/app-tap/module_api.c | 241 +++++++++++++++++++++++++ test/app-tap/module_api.test.lua | 2 +- 7 files changed, 481 insertions(+), 1 deletion(-) create mode 100644 changelogs/unreleased/box_read_view.md diff --git a/changelogs/unreleased/box_read_view.md b/changelogs/unreleased/box_read_view.md new file mode 100644 index 0000000000..f487d7c906 --- /dev/null +++ b/changelogs/unreleased/box_read_view.md @@ -0,0 +1,4 @@ +## feature/box + +Introduce box_read_view_* ffi API for opening/closing read views and iterating +over index read views. diff --git a/extra/exports b/extra/exports index 61886c79c8..c164164f28 100644 --- a/extra/exports +++ b/extra/exports @@ -95,6 +95,11 @@ box_latch_unlock box_on_shutdown box_read_ffi_disable box_read_ffi_is_disabled +box_read_view_close +box_read_view_iterator_all +box_read_view_iterator_free +box_read_view_iterator_next_raw +box_read_view_open_for_given_spaces box_region_aligned_alloc box_region_alloc box_region_truncate diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 7c9e19406f..22e4c11214 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -240,6 +240,7 @@ set(api_headers ${PROJECT_SOURCE_DIR}/src/lib/core/port.h ${PROJECT_SOURCE_DIR}/src/box/port.h ${PROJECT_SOURCE_DIR}/src/box/module_cache.h + ${PROJECT_SOURCE_DIR}/src/box/read_view.h ${EXTRA_API_HEADERS} ) rebuild_module_api(${api_headers}) diff --git a/src/box/read_view.c b/src/box/read_view.c index f53a14cd2e..cf9ee34349 100644 --- a/src/box/read_view.c +++ b/src/box/read_view.c @@ -279,3 +279,133 @@ read_view_foreach(read_view_foreach_f cb, void *arg) } return true; } + +/** + * See read_view_opts::filter_arg. + */ +struct read_view_user_filer_arg { + /* See box_read_view_open_for_given_spaces. */ + struct space_index_id *space_index_ids; + /* See box_read_view_open_for_given_spaces. */ + uint32_t space_index_ids_count; +}; + +/** + * See read_view_opts::filter_space. + */ +static bool +read_view_user_space_filter(struct space *space, void *a) +{ + struct read_view_user_filer_arg *arg = a; + for (uint32_t i = 0; i < arg->space_index_ids_count; i++) + if (space_id(space) == arg->space_index_ids[i].space_id) + return true; + return false; +} + +/** + * See read_view_opts::filter_index. + */ +static bool +read_view_user_index_filter(struct space *space, struct index *index, void *a) +{ + struct read_view_user_filer_arg *arg = a; + for (uint32_t i = 0; i < arg->space_index_ids_count; i++) { + if (space_id(space) != arg->space_index_ids[i].space_id) + continue; + if (index->def->iid == arg->space_index_ids[i].index_id) + return true; + } + return false; +} + +API_EXPORT box_read_view_t * +box_read_view_open_for_given_spaces(const char *name, + struct space_index_id *space_index_ids, + uint32_t space_index_ids_count, + uint64_t flags) +{ + struct read_view_opts rv_opts; + read_view_opts_create(&rv_opts); + rv_opts.name = name; + + /* + * The user chooses spaces explicitly by ids, + * so they may choose to add data-temporary spaces. + */ + rv_opts.enable_data_temporary_spaces = true; + + if (flags & BOX_READ_VIEW_FIELD_NAMES) + rv_opts.enable_field_names = true; + + struct read_view_user_filer_arg arg; + arg.space_index_ids = space_index_ids; + arg.space_index_ids_count = space_index_ids_count; + rv_opts.filter_arg = &arg; + rv_opts.filter_space = read_view_user_space_filter; + rv_opts.filter_index = read_view_user_index_filter; + + box_read_view_t *rv = xcalloc(1, sizeof(box_read_view_t)); + if (read_view_open(rv, &rv_opts) != 0) { + free(rv); + return NULL; + } + + return rv; +} + +API_EXPORT void +box_read_view_close(box_read_view_t *rv) +{ + read_view_close(rv); + free(rv); +} + +API_EXPORT int +box_read_view_iterator_all(box_read_view_t *rv, + uint32_t space_id, uint32_t index_id, + box_read_view_iterator_t **iter) +{ + struct space_read_view *space_rv; + read_view_foreach_space(space_rv, rv) { + if (space_rv->id != space_id) + continue; + + struct index_read_view *index_rv = + space_read_view_index(space_rv, index_id); + if (index_rv == NULL) { + /* Index is not in the read view. */ + *iter = NULL; + return 0; + } + + box_read_view_iterator_t *it = + xcalloc(1, sizeof(box_read_view_iterator_t)); + if (index_read_view_create_iterator(index_rv, ITER_ALL, + NULL, 0, it) != 0) { + free(it); + return -1; + } + + *iter = it; + return 0; + } + + /* Space is not in the read view. */ + *iter = NULL; + return 0; +} + +API_EXPORT int +box_read_view_iterator_next_raw(box_read_view_iterator_t *iterator, + const char **data, uint32_t *size) +{ + return index_read_view_iterator_next_raw(iterator, data, size); +} + +API_EXPORT void +box_read_view_iterator_free(box_read_view_iterator_t *iterator) +{ + index_read_view_iterator_destroy(iterator); + free(iterator); +} diff --git a/src/box/read_view.h b/src/box/read_view.h index 1af5d5a94f..5d609e9533 100644 --- a/src/box/read_view.h +++ b/src/box/read_view.h @@ -214,6 +214,105 @@ read_view_foreach_f(struct read_view *rv, void *arg); bool read_view_foreach(read_view_foreach_f cb, void *arg); +/** \cond public */ + +typedef struct index_read_view_iterator box_read_view_iterator_t; +typedef struct read_view box_read_view_t; + +/** + * Flags supported by \link box_read_view_open_for_given_spaces \endlink. + */ +enum box_read_view_flags { + BOX_READ_VIEW_FIELD_NAMES = 0x0001, +}; + +struct space_index_id { + uint32_t space_id; + uint32_t index_id; +}; + +/** + * Open a read view on the spaces and indexes specified by the given parameters. + * + * \param name Read view name. Will be copied + * so memory may be reused immediately. + * + * \param space_index_ids Array of pairs (space id, index id) which + * should be added to the read view. + * \param space_index_ids_count Number of elements in \a space_index_ids + * + * \param flags Read view flags. Must be a disjunction of + * enum \link box_read_view_flags \endlink values + * or 0. + * + * \retval NULL On error (check box_error_last()). + * \retval read view Otherwise. + * + * \sa box_read_view_iterator() + * \sa box_read_view_close() + */ +box_read_view_t * +box_read_view_open_for_given_spaces(const char *name, + struct space_index_id *space_index_ids, + uint32_t space_index_ids_count, + uint64_t flags); + +/** + * Close the read view and dispose off any resources taken up by it. + * + * \sa box_read_view_open_for_given_spaces() + */ +void +box_read_view_close(box_read_view_t *rv); + +/** + * Create an iterator over all the tuples in the read view of the given index. + * + * \param rv Read view returned by box_read_view_open_for_given_spaces() + * \param space_id Space identifier + * \param index_id Index identifier + * \param[out] iter Iterator or NULL if the given index is not in the read view + * + * \retval -1 On error (check box_error_last()) + * \retval 0 Otherwise. Index not existing is not an error + * + * \sa box_read_view_iterator_next() + * \sa box_read_view_iterator_free() + */ +int +box_read_view_iterator_all(box_read_view_t *rv, + uint32_t space_id, uint32_t index_id, + box_read_view_iterator_t **iter); + +/** + * Retrieve the next item from the \a iterator. + * + * \param iterator An iterator returned by box_read_view_iterator() + * \param[out] data A pointer to the raw tuple data + * or NULL if there's no more data + * \param[out] size Size of the returned tuple data + * + * \retval -1 On error (check box_error_last() for details) + * \retval 0 On success. The end of data is not an error + * + * \sa box_read_view_iterator() + * \sa box_read_view_iterator_free() + */ +int +box_read_view_iterator_next_raw(box_read_view_iterator_t *iterator, + const char **data, uint32_t *size); + +/** + * Destroy and deallocate the read view iterator. + * + * \sa box_read_view_iterator() + * \sa box_read_view_iterator_free() + */ +void +box_read_view_iterator_free(box_read_view_iterator_t *iterator); + +/** \endcond public */ + #if defined(__cplusplus) } /* extern "C" */ #endif /* defined(__cplusplus) */ diff --git a/test/app-tap/module_api.c b/test/app-tap/module_api.c index f775f60bca..b4e55c2c91 100644 --- a/test/app-tap/module_api.c +++ b/test/app-tap/module_api.c @@ -3382,6 +3382,246 @@ test_box_auth_data_prepare(struct lua_State *L) return 1; } +#define CHECK_STREQ(expected, data, len) do { \ + fail_unless(strlen(expected) == (len)); \ + fail_unless(memcmp(expected, data, len) == 0); \ +} while (0) + +static int +test_box_read_view(struct lua_State *L) +{ + int rc; + const char *data; + uint32_t size; + box_tuple_t *t; + + /* + * Preparation. + */ + const char code[] = + "local s1 = box.schema.space.create('test_box_read_view_s1')\n" + "s1:create_index('pk')\n" + "s1:put{16}\n" + "s1:put{32}\n" + "s1:put{48}\n" + "\n" + "local s2 = box.schema.space.create('test_box_read_view_s2')\n" + "s2:format({{'id', 'unsigned'}, {'val', 'string'}})\n" + "s2:create_index('pk')\n" + "s2:create_index('val', {parts = {'val'}})\n" + "s2:put{1, 'c'}\n" + "s2:put{2, 'b'}\n" + "s2:put{3, 'a'}\n" + "\n" + "local s3 = box.schema.space.create('test_box_read_view_s3')\n" + "s3:create_index('pk')\n" + "s3:put{1}\n" + "s3:put{2}\n" + "s3:put{3}\n" + "\n" + "local s4 = box.schema.space.create(" + " 'test_box_read_view_s4', { engine = 'vinyl' }\n" + ")\n" + "s4:create_index('pk')\n" + "s4:put{16}\n" + "s4:put{32}\n" + "s4:put{48}\n" + "\n" + "return s1.id, s2.id, s3.id, s4.id\n"; + rc = luaL_loadbuffer(L, code, sizeof(code) - 1, __func__); + fail_unless(rc == 0); + rc = lua_pcall(L, 0, 4, 0); + fail_unless(rc == 0); + uint32_t s1_id = lua_tointeger(L, -4); + uint32_t s2_id = lua_tointeger(L, -3); + uint32_t s3_id = lua_tointeger(L, -2); + uint32_t s4_id = lua_tointeger(L, -1); + + /* Space doesn't exist */ + uint32_t sX_id = 69420; + { + char key[64]; + char *key_end = key; + key_end = mp_encode_array(key_end, 1); + key_end = mp_encode_uint(key_end, sX_id); + box_index_get(BOX_SPACE_ID, 0, key, key_end, &t); + fail_unless(t == NULL); + } + + /* + * Open read view. + */ + struct space_index_id space_index_ids[] = { + {s1_id, 0}, + /* Unknown index is ignored. */ + {s3_id, 1337}, + /* Unknown space is ignored. */ + {sX_id, 0}, + {s4_id, 0}, + {s2_id, 1}, + }; + box_read_view_t *rv; + rv = box_read_view_open_for_given_spaces(__func__, + space_index_ids, + lengthof(space_index_ids), 0); + fail_unless(rv != NULL); + + /* + * Space index is in the read view. + */ + box_read_view_iterator_t *it; + rc = box_read_view_iterator_all(rv, s1_id, 0, &it); + fail_unless(rc == 0); + + rc = box_read_view_iterator_next_raw(it, &data, &size); + fail_unless(rc == 0); + CHECK_STREQ("\x91\x10", data, size); + + rc = box_read_view_iterator_next_raw(it, &data, &size); + fail_unless(rc == 0); + CHECK_STREQ("\x91\x20", data, size); + + rc = box_read_view_iterator_next_raw(it, &data, &size); + fail_unless(rc == 0); + CHECK_STREQ("\x91\x30", data, size); + + rc = box_read_view_iterator_next_raw(it, &data, &size); + fail_unless(data == NULL); + + box_read_view_iterator_free(it); + + /* + * Space is in the read view, but index doesn't. + */ + rc = box_read_view_iterator_all(rv, s1_id, 1, &it); + fail_unless(rc == 0); + fail_unless(it == NULL); + + rc = box_read_view_iterator_all(rv, s2_id, 0, &it); + fail_unless(rc == 0); + fail_unless(it == NULL); + + /* + * Space index is in the read view. Non-primary index. + */ + rc = box_read_view_iterator_all(rv, s2_id, 1, &it); + fail_unless(rc == 0); + + rc = box_read_view_iterator_next_raw(it, &data, &size); + fail_unless(rc == 0); + CHECK_STREQ("\x92\x03\xa1\x61", data, size); + + rc = box_read_view_iterator_next_raw(it, &data, &size); + fail_unless(rc == 0); + CHECK_STREQ("\x92\x02\xa1\x62", data, size); + + rc = box_read_view_iterator_next_raw(it, &data, &size); + fail_unless(rc == 0); + CHECK_STREQ("\x92\x01\xa1\x63", data, size); + + rc = box_read_view_iterator_next_raw(it, &data, &size); + fail_unless(data == NULL); + + box_read_view_iterator_free(it); + + /* + * Space is not in the read view. + */ + rc = box_read_view_iterator_all(rv, s3_id, 0, &it); + fail_unless(rc == 0); + fail_unless(it == NULL); + + rc = box_read_view_iterator_all(rv, s3_id, 1337, &it); + fail_unless(rc == 0); + fail_unless(it == NULL); + + rc = box_read_view_iterator_all(rv, sX_id, 0, &it); + fail_unless(rc == 0); + fail_unless(it == NULL); + + /* + * Space is modified but read view is not. + */ + { + static const char key[] = "\x91\x20"; + box_delete(s1_id, 0, key, key + sizeof(key) - 1, &t); + fail_unless(t != NULL); + } + { + static const char key[] = "\x91\x40"; + box_insert(s1_id, key, key + sizeof(key) - 1, &t); + fail_unless(t != NULL); + } + { + static const char key[] = "\x92\x30\x45"; + box_replace(s1_id, key, key + sizeof(key) - 1, &t); + fail_unless(t != NULL); + } + { + box_iterator_t *it; + ssize_t len; + char buf[64]; + buf[0] = 0x90; + it = box_index_iterator(s1_id, 0, ITER_ALL, buf, buf + 1); + + box_iterator_next(it, &t); + len = box_tuple_to_buf(t, buf, sizeof(buf)); + CHECK_STREQ("\x91\x10", buf, len); + + box_iterator_next(it, &t); + len = box_tuple_to_buf(t, buf, sizeof(buf)); + CHECK_STREQ("\x92\x30\x45", buf, len); + + box_iterator_next(it, &t); + len = box_tuple_to_buf(t, buf, sizeof(buf)); + CHECK_STREQ("\x91\x40", buf, len); + + box_iterator_next(it, &t); + fail_unless(t == NULL); + + box_iterator_free(it); + } + + /* + * Read view is not modified. + */ + rc = box_read_view_iterator_all(rv, s1_id, 0, &it); + fail_unless(rc == 0); + + rc = box_read_view_iterator_next_raw(it, &data, &size); + fail_unless(rc == 0); + CHECK_STREQ("\x91\x10", data, size); + + rc = box_read_view_iterator_next_raw(it, &data, &size); + fail_unless(rc == 0); + CHECK_STREQ("\x91\x20", data, size); + + rc = box_read_view_iterator_next_raw(it, &data, &size); + fail_unless(rc == 0); + CHECK_STREQ("\x91\x30", data, size); + + rc = box_read_view_iterator_next_raw(it, &data, &size); + fail_unless(data == NULL); + + box_read_view_iterator_free(it); + + /* + * Vinyl spaces don't support read views. + */ + + rc = box_read_view_iterator_all(rv, s4_id, 0, &it); + fail_unless(rc == 0); + fail_unless(it == NULL); + + /* + * Close read view. + */ + box_read_view_close(rv); + + lua_pushboolean(L, true); + return 1; +} + LUA_API int luaopen_module_api(lua_State *L) { @@ -3444,6 +3684,7 @@ luaopen_module_api(lua_State *L) {"box_iproto_send", test_box_iproto_send}, {"box_iproto_override_set", test_box_iproto_override_set}, {"box_iproto_override_reset", test_box_iproto_override_reset}, + {"test_box_read_view", test_box_read_view}, {NULL, NULL} }; luaL_register(L, "module_api", lib); diff --git a/test/app-tap/module_api.test.lua b/test/app-tap/module_api.test.lua index 8dcf33c5bb..1788357c5b 100755 --- a/test/app-tap/module_api.test.lua +++ b/test/app-tap/module_api.test.lua @@ -664,7 +664,7 @@ local function test_box_iproto_override(test, module) end require('tap').test("module_api", function(test) - test:plan(55) + test:plan(56) local status, module = pcall(require, 'module_api') test:is(status, true, "module") test:ok(status, "module is loaded") -- GitLab