PMEMKV_ITERATOR(3)

πŸ“¦ version 1.5.0, in ubuntu jammy

πŸ’– NAME πŸ’–

pmemkv_iterator β€” it's the Iterator API for libpmemkv, bestie. That's the whole vibe. (You'll also see it written as PMEMKV_ITERATOR(3), from the PMEMKV Programmer's Manual, iykyk.)

⚠️ Heads up gorgeous: This API is EXPERIMENTAL and might change. So like... don't build your whole personality on it. 😭

✨ SYNOPSIS ✨

#include <libpmemkv.h>

int pmemkv_iterator_new(pmemkv_db *db, pmemkv_iterator **it);
int pmemkv_write_iterator_new(pmemkv_db *db, pmemkv_write_iterator **it);

void pmemkv_iterator_delete(pmemkv_iterator *it);
void pmemkv_write_iterator_delete(pmemkv_write_iterator *it);

int pmemkv_iterator_seek(pmemkv_iterator *it, const char *k, size_t kb);
int pmemkv_iterator_seek_lower(pmemkv_iterator *it, const char *k, size_t kb);
int pmemkv_iterator_seek_lower_eq(pmemkv_iterator *it, const char *k, size_t kb);
int pmemkv_iterator_seek_higher(pmemkv_iterator *it, const char *k, size_t kb);
int pmemkv_iterator_seek_higher_eq(pmemkv_iterator *it, const char *k, size_t kb);

int pmemkv_iterator_seek_to_first(pmemkv_iterator *it);
int pmemkv_iterator_seek_to_last(pmemkv_iterator *it);

int pmemkv_iterator_is_next(pmemkv_iterator *it);
int pmemkv_iterator_next(pmemkv_iterator *it);
int pmemkv_iterator_prev(pmemkv_iterator *it);

int pmemkv_iterator_key(pmemkv_iterator *it, const char **k, size_t *kb);

int pmemkv_iterator_read_range(pmemkv_iterator *it, size_t pos, size_t n,
                    const char **data, size_t *rb);
int pmemkv_write_iterator_write_range(pmemkv_write_iterator *it, size_t pos, size_t n,
                    char **data, size_t *wb);

int pmemkv_write_iterator_commit(pmemkv_write_iterator *it);
void pmemkv_write_iterator_abort(pmemkv_write_iterator *it);

Fr fr, for the general rundown of pmemkv and all the engines you can pick from, go peek at libpmemkv(7). And for the tea on the pmemkv core API? That's libpmemkv(3). 🫢


🌸 DESCRIPTION 🌸

Iterators are literally just the little helpers that let you walk over the records inside db. Slay.

So there's two flavors of iterator: pmemkv_iterator (the read one) and pmemkv_write_iterator. Both of them let you read a record's key and value. πŸŽ€

The cute little trick: if you wanna use a pmemkv_write_iterator as a pmemkv_iterator, you just grab its member called "iter" β€” so like write_it->iter. That's it, that's the whole hack. πŸ’…

Here's what calling pmemkv_iterator_seek_to_first() looks like with both iterator types:

/* read_it is already created, by pmemkv_iterator */
int status = pmemkv_iterator_seek_to_first(read_it);

/* write_it is already created, by pmemkv_write_iterator */
int status = pmemkv_iterator_seek_to_first(write_it->iter);

And the extra sparkle on pmemkv_write_iterator? It can also modify a record's value transactionally. Main character energy honestly. ✨

🫧 Lowkey important: not every engine is guaranteed to implement all of these functions. If an engine doesn't support a certain function, it'll hand you back PMEMKV_STATUS_NOT_SUPPORTED. No cap.
πŸ’€ Also β€” holding more than one iterator at the same time in the same thread is undefined behavior. Don't do it, bestie, I'm begging.

πŸ” The Functions, One By One πŸ”

  • int pmemkv_iterator_new(pmemkv_db *db, pmemkv_iterator **it); β€” Makes a brand new pmemkv_iterator and drops a pointer to it into *it. 🌈
  • int pmemkv_write_iterator_new(pmemkv_db *db, pmemkv_write_iterator **it); β€” Makes a brand new pmemkv_write_iterator and drops a pointer to it into *it. πŸ¦‹
  • void pmemkv_iterator_delete(pmemkv_iterator *it); β€” Deletes the pmemkv_iterator. Bye bestie. πŸ‘‹
  • void pmemkv_write_iterator_delete(pmemkv_write_iterator *it); β€” Deletes the pmemkv_write_iterator. Also bye. πŸ‘‹
  • int pmemkv_iterator_seek(pmemkv_iterator *it, const char *k, size_t kb); β€” Moves the iterator to the record with the given key k, which is kb long. If that record is there and nothing went wrong, you get PMEMKV_STATUS_OK back. If the record doesn't exist, you get PMEMKV_STATUS_NOT_FOUND and the iterator position is undefined. Sneaky detail: it internally aborts all changes made to an element the iterator was pointing at before. πŸ™ˆ
  • int pmemkv_iterator_seek_lower(pmemkv_iterator *it, const char *k, size_t kb); β€” Moves the iterator to the record with a key lower than the given key k of length kb. If that record is present and no errors occurred, you get PMEMKV_STATUS_OK. If it doesn't exist, PMEMKV_STATUS_NOT_FOUND comes back and the iterator position is undefined. It also internally aborts all changes made to an element previously pointed at by the iterator. πŸ’«
  • int pmemkv_iterator_seek_lower_eq(pmemkv_iterator *it, const char *k, size_t kb); β€” Moves the iterator to the record with a key equal to or lower than the given key k of length kb. Present + no errors = PMEMKV_STATUS_OK. Doesn't exist = PMEMKV_STATUS_NOT_FOUND and undefined position. And yes, it internally aborts all changes made to an element previously pointed at by the iterator. 🌸
  • int pmemkv_iterator_seek_higher(pmemkv_iterator *it, const char *k, size_t kb); β€” Moves the iterator to the record with a key higher than the given key k of length kb. Record present and no errors β†’ PMEMKV_STATUS_OK. Record missing β†’ PMEMKV_STATUS_NOT_FOUND, iterator position undefined. It internally aborts all changes made to an element previously pointed at by the iterator. ⭐
  • int pmemkv_iterator_seek_higher_eq(pmemkv_iterator *it, const char *k, size_t kb); β€” Moves the iterator to the record with a key equal to or higher than the given key k of length kb. Present and error-free β†’ PMEMKV_STATUS_OK. Not present β†’ PMEMKV_STATUS_NOT_FOUND plus an undefined position. And (say it with me) it internally aborts all changes made to an element previously pointed at by the iterator. 🧚
  • int pmemkv_iterator_seek_to_first(pmemkv_iterator *it); β€” Moves the iterator to the first record. If db isn't empty and no errors happened, you get PMEMKV_STATUS_OK. If db is empty, you get PMEMKV_STATUS_NOT_FOUND and the iterator position is undefined. It internally aborts all changes made to an element previously pointed at by the iterator. πŸ₯‡
  • int pmemkv_iterator_seek_to_last(pmemkv_iterator *it); β€” Moves the iterator to the last record. db not empty + no errors = PMEMKV_STATUS_OK. db empty = PMEMKV_STATUS_NOT_FOUND, position undefined. Yep, it internally aborts all changes made to an element previously pointed at by the iterator. 🏁
  • int pmemkv_iterator_is_next(pmemkv_iterator *it); β€” Checks whether there's a next record available. If it comes back true, it's guaranteed that pmemkv_iterator_next(it) will return PMEMKV_STATUS_OK. Otherwise the iterator is already sitting on the last element, and pmemkv_iterator_next(it) will return PMEMKV_STATUS_NOT_FOUND. A little peek, no commitment. πŸ‘€
  • int pmemkv_iterator_next(pmemkv_iterator *it); β€” Moves the iterator to the next record. If the next record exists you get PMEMKV_STATUS_OK; otherwise PMEMKV_STATUS_NOT_FOUND and the iterator position is undefined. And once again: it internally aborts all changes made to an element previously pointed at by the iterator. ➑️
  • int pmemkv_iterator_prev(pmemkv_iterator *it); β€” Moves the iterator to the previous record. Previous record exists β†’ PMEMKV_STATUS_OK. Doesn't exist β†’ PMEMKV_STATUS_NOT_FOUND and undefined position. Also aborts all changes made to an element previously pointed at by the iterator, internally. ⬅️
  • int pmemkv_iterator_key(pmemkv_iterator *it, const char **k, size_t *kb); β€” Assigns the record's key's address to k, and the key's length to kb. If the iterator is on an undefined position, calling this is undefined behavior. πŸ’€
  • int pmemkv_iterator_read_range(pmemkv_iterator *it, size_t pos, size_t n, const char **data, size_t *rb); β€” Lets you grab a record's value's range, and it's read-only. You can ask for the full value or just a subrange of it (n elements starting from pos). It assigns a pointer to the beginning of the requested range to data, and the number of elements in the range to rb. If n is bigger than the length of the value, it gets automatically shrunk. And if the iterator is on an undefined position, calling this method is undefined behavior. πŸ“–
  • int pmemkv_write_iterator_write_range(pmemkv_write_iterator *it, size_t pos, size_t n, char **data, size_t *wb); β€” Lets you grab a record's value's range that you can actually modify. Again, full value or just a subrange (n elements starting from pos). Assigns a pointer to the beginning of the requested range to data, and the number of elements in the range to wb. If n is bigger than the value's length, it's automatically shrunk. Changes made on a requested range are not persistent until pmemkv_write_iterator_commit() is called. And if the iterator is on an undefined position, calling this method is undefined behavior. ✍️
  • int pmemkv_write_iterator_commit(pmemkv_write_iterator *it); β€” Commits the modifications made on the current record. Calling this method is the ONLY way to save the modifications the iterator made on the current record. You have to call it before changing the iterator position, otherwise your modifications get automatically aborted. It's giving... use it or lose it. 😀
  • void pmemkv_write_iterator_abort(pmemkv_write_iterator *it); β€” Aborts uncommitted modifications made on the current record. The "nvm, forget I said anything" button. πŸ™…

πŸ’₯ ERRORS πŸ’₯

Every single one of these functions returns one of the pmemkv status codes β€” except for pmemkv_iterator_delete(), pmemkv_write_iterator_delete() and pmemkv_write_iterator_abort(). Wanna see all the possible status code options? That's libpmemkv(3), go look. πŸ‘‘


✨ EXAMPLE ✨

Okay bestie, this next bit is the part where it all clicks, I promise. 🫢 The example below is taken straight from the examples/pmemkv_iterator_c directory, no cap.

It shows off the basic iterator functions in action: iterating over all the records, and then modifying one of them. Iconic behaviour, honestly. πŸ‘‘

#include "libpmemkv.h"

#include <assert.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define ASSERT(expr)                                                                     \
    do {                                                                             \
        if (!(expr))                                                             \
            puts(pmemkv_errormsg());                                         \
        assert(expr);                                                            \
    } while (0)

#define LOG(msg) puts(msg)

static const uint64_t SIZE = 1024UL * 1024UL * 1024UL;

int main(int argc, char *argv[])
{
    if (argc < 2) {
        fprintf(stderr, "Usage: %s file\n", argv[0]);
        exit(1);
    }

    const size_t n_elements = 10;
    char buffer[64];

    /* See libpmemkv_config(3) for more detailed example of config creation */
    LOG("Creating config");
    pmemkv_config *cfg = pmemkv_config_new();
    ASSERT(cfg != NULL);

    int s = pmemkv_config_put_path(cfg, argv[1]);
    ASSERT(s == PMEMKV_STATUS_OK);
    s = pmemkv_config_put_size(cfg, SIZE);
    ASSERT(s == PMEMKV_STATUS_OK);
    s = pmemkv_config_put_create_if_missing(cfg, true);
    ASSERT(s == PMEMKV_STATUS_OK);

    LOG("Opening pmemkv database with 'radix' engine");
    pmemkv_db *db = NULL;
    s = pmemkv_open("radix", cfg, &db);
    ASSERT(s == PMEMKV_STATUS_OK);
    ASSERT(db != NULL);

    LOG("Putting new keys");
    for (size_t i = 0; i < n_elements; ++i) {
        char key[10];
        const char *value = "value";
        sprintf(key, "key%zu", i);
        s = pmemkv_put(db, key, strlen(key), value, strlen(value));
        ASSERT(s == PMEMKV_STATUS_OK);
    }

    /* get a new read iterator */
    pmemkv_iterator *it;
    s = pmemkv_iterator_new(db, &it);
    ASSERT(s == PMEMKV_STATUS_OK);

    LOG("Iterate from first to last element");
    s = pmemkv_iterator_seek_to_first(it);
    ASSERT(s == PMEMKV_STATUS_OK);

    size_t element_number = 0;
    do {
        const char *str;
        size_t cnt;
        /* read a key */
        s = pmemkv_iterator_key(it, &str, &cnt);
        ASSERT(s == PMEMKV_STATUS_OK);
        sprintf(buffer, "Key %zu = %s", element_number++, str);
        LOG(buffer);
    } while (pmemkv_iterator_next(it) != PMEMKV_STATUS_NOT_FOUND);

    /* iterator must be deleted manually */
    pmemkv_iterator_delete(it);

    /* get a new write_iterator */
    pmemkv_write_iterator *w_it;
    s = pmemkv_write_iterator_new(db, &w_it);
    ASSERT(s == PMEMKV_STATUS_OK);

    /* if you want to get a pmemkv_iterator (read iterator) from a
     * pmemkv_write_iterator, you should do: write_it->iter */
    s = pmemkv_iterator_seek_to_last(w_it->iter);
    ASSERT(s == PMEMKV_STATUS_OK);

    /* get a write range, to modify last element's value */
    char *data;
    size_t cnt;
    s = pmemkv_write_iterator_write_range(w_it, 0, 5, &data, &cnt);
    ASSERT(s == PMEMKV_STATUS_OK);

    for (size_t i = 0; i < cnt; ++i)
        data[i] = 'x';

    /* commit changes */
    s = pmemkv_write_iterator_commit(w_it);
    ASSERT(s == PMEMKV_STATUS_OK);

    /* get a read range, to read modified value */
    const char *str;
    s = pmemkv_iterator_read_range(w_it->iter, 0, 5, &str, &cnt);
    ASSERT(s == PMEMKV_STATUS_OK);

    /* verify a modified value */
    ASSERT(strcmp(str, "xxxxx") == 0);
    sprintf(buffer, "Modified value = %s", str);
    LOG(buffer);

    /* iterator must be deleted manually */
    pmemkv_write_iterator_delete(w_it);

    LOG("Closing database");
    pmemkv_close(db);

    return 0;
}

🌈 SEE ALSO 🌈

Go get your extra reading in, bestie, it's giving study era πŸ“šβœ¨:


And that's a wrap on this whole page, fr fr. πŸ’– This was PMEMKV β€” pmemkv version 1.5.0, dated 2022-03-09, aka the PMEMKV_ITERATOR(3) glow-up. Go iterate on something beautiful, you absolute icon. πŸ’…πŸŽ€πŸ¦‹

πŸ“¦ where this page comes from