PMEMKV(3)

πŸ“¦ version 1.5.0, in ubuntu jammy

πŸ’– pmemkv(3) β€” PMEMKV Programmer's Manual πŸ’–

okay bestie, welcome to pmemkv(3) πŸ’… this is the page for pmemkv, the Key/Value Datastore for Persistent Memory. no cap, it's giving main character energy.


✨ NAME ✨

pmemkv β€” Key/Value Datastore for Persistent Memory πŸ¦‹


🌸 SYNOPSIS 🌸

okay so here's the whole cast, fr fr:

              #include <libpmemkv.h>

              typedef int pmemkv_get_kv_callback(const char *key, size_t keybytes, const char *value,
                          size_t valuebytes, void *arg);
              typedef void pmemkv_get_v_callback(const char *value, size_t valuebytes, void *arg);

              int pmemkv_open(const char *engine, pmemkv_config *config, pmemkv_db **db);
              void pmemkv_close(pmemkv_db *kv);

              int pmemkv_count_all(pmemkv_db *db, size_t *cnt);
              int pmemkv_count_above(pmemkv_db *db, const char *k, size_t kb, size_t *cnt);
              int pmemkv_count_below(pmemkv_db *db, const char *k, size_t kb, size_t *cnt);
              int pmemkv_count_between(pmemkv_db *db, const char *k1, size_t kb1, const char *k2,
                          size_t kb2, size_t *cnt);

              int pmemkv_get_all(pmemkv_db *db, pmemkv_get_kv_callback *c, void *arg);
              int pmemkv_get_above(pmemkv_db *db, const char *k, size_t kb, pmemkv_get_kv_callback *c,
                          void *arg);
              int pmemkv_get_below(pmemkv_db *db, const char *k, size_t kb, pmemkv_get_kv_callback *c,
                          void *arg);
              int pmemkv_get_between(pmemkv_db *db, const char *k1, size_t kb1, const char *k2,
                          size_t kb2, pmemkv_get_kv_callback *c, void *arg);

              int pmemkv_exists(pmemkv_db *db, const char *k, size_t kb);

              int pmemkv_get(pmemkv_db *db, const char *k, size_t kb, pmemkv_get_v_callback *c,
                          void *arg);
              int pmemkv_get_copy(pmemkv_db *db, const char *k, size_t kb, char *buffer,
                          size_t buffer_size, size_t *value_size);
              int pmemkv_put(pmemkv_db *db, const char *k, size_t kb, const char *v, size_t vb);

              int pmemkv_remove(pmemkv_db *db, const char *k, size_t kb);

              int pmemkv_defrag(pmemkv_db *db, double start_percent, double amount_percent);

              const char *pmemkv_errormsg(void);

Wanna know about the pmemkv configuration API? go read libpmemkv_config(3) πŸ’–. Wanna iterate? libpmemkv_iterator(3) has the deets ✨. And for general pmemkv lore, engine descriptions, and bindings details? that's libpmemkv(7) πŸ‘‘.

🌸 DESCRIPTION 🌸

Okay bestie, here's the tea on how pmemkv actually stores your stuff πŸ‘‘ Keys and values living inside a pmemkv database can be arbitrary binary data and can totally contain multiple null characters β€” no judgment, no trimming, the database does not care πŸ’… Every function that accepts a key wants a const char *k pointer to the data and its size as a size_t. Both. Every time. Don't forget the size, I'm begging πŸ™ˆ

Some of these functions β€” mainly the range-query API girlies β€” are not guaranteed to be implemented by all engines. So if an engine just doesn't vibe with a certain function, it will hand you back PMEMKV_STATUS_NOT_SUPPORTED. Not a you problem, it's an engine problem πŸ’€

Note, and this is important: there are no explicit upper_bound/lower_bound functions. None. If you want to snag the element(s) above or below the key you picked, you use pmemkv_get_above() or pmemkv_get_below(). Scroll down for the details, they're both iconic ✨

int pmemkv_open(const char *engine, pmemkv_config *config, pmemkv_db **db);

Opens the pmemkv database and tucks a pointer to a pmemkv_db instance into *db. The engine parameter is where you name your engine (peep libpmemkv(7) for the full list of available engines). The config parameter is your configuration setup (see libpmemkv_config(3) for all the deets).

Pmemkv takes ownership of the config parameter β€” meaning that after open (whether it succeeded or failed, either way) you must NOT call pmemkv_config_delete(). It's not yours anymore, bestie, let it go 🫢

void pmemkv_close(pmemkv_db *kv);

Closes the pmemkv database. That's it, that's the whole thing. Bye bye πŸ‘‹

int pmemkv_count_all(pmemkv_db *db, size_t *cnt);

Stores in *cnt the number of records in db. Simple. Clean. Ate ✨

int pmemkv_count_above(pmemkv_db *db, const char *k, size_t kb, size_t *cnt);

Stores in *cnt the number of records in db whose keys are greater than the key k of length kb. The order of the elements is specified by a comparator (see libpmemkv(7)).

int pmemkv_count_below(pmemkv_db *db, const char *k, size_t kb, size_t *cnt);

Stores in *cnt the number of records in db whose keys are less than the key k of length kb. Order of the elements? Specified by a comparator (see libpmemkv(7)). Yes, same as above, we love consistency πŸ’–

int pmemkv_count_between(pmemkv_db *db, const char *k1, size_t kb1, const char *k2, size_t kb2, size_t *cnt);

Stores in *cnt the number of records in db whose keys are greater than key k1 (of length kb1) and less than key k2 (of length kb2). Order of the elements is specified by a comparator (see libpmemkv(7)). A little range sandwich, love that for you πŸ₯ͺ

int pmemkv_get_all(pmemkv_db *db, pmemkv_get_kv_callback *c, void *arg);

Executes function c for every record stored in db. The arguments passed to that function are: pointer to a key, size of the key, pointer to a value, size of the value, and the arg you specified yourself.

Function c can stop the iteration by returning a non-zero value β€” and in that case pmemkv_get_all() returns PMEMKV_STATUS_STOPPED_BY_CB. Returning 0 keeps the iteration going. Order of the elements is specified by a comparator (see libpmemkv(7)).

int pmemkv_get_above(pmemkv_db *db, const char *k, size_t kb, pmemkv_get_kv_callback *c, void *arg);

Executes function c for every record stored in db whose keys are greater than key k (of length kb). Arguments passed to c are: pointer to a key, size of the key, pointer to a value, size of the value, and the arg you specified. Function c can stop the iteration by returning a non-zero value β€” in that case pmemkv_get_above() returns PMEMKV_STATUS_STOPPED_BY_CB. Returning 0 continues the iteration. Order of the elements is specified by a comparator (see libpmemkv(7)).

int pmemkv_get_below(pmemkv_db *db, const char *k, size_t kb, pmemkv_get_kv_callback *c, void *arg);

Executes function c for every record stored in db whose keys are less than key k (of length kb). Arguments passed to c are: pointer to a key, size of the key, pointer to a value, size of the value, and the arg you specified. Function c can stop the iteration by returning a non-zero value β€” in that case pmemkv_get_below() returns PMEMKV_STATUS_STOPPED_BY_CB. Returning 0 continues the iteration. Order of the elements is specified by a comparator (see libpmemkv(7)).

int pmemkv_get_between(pmemkv_db *db, const char *k1, size_t kb1, const char *k2, size_t kb2, pmemkv_get_kv_callback *c, void *arg);

Executes function c for every record stored in db whose keys are greater than key k1 (of length kb1) and less than key k2 (of length kb2). Arguments passed to c are: pointer to a key, size of the key, pointer to a value, size of the value, and the arg you specified. Function c can stop the iteration by returning a non-zero value β€” in that case pmemkv_get_between() returns PMEMKV_STATUS_STOPPED_BY_CB. Returning 0 continues the iteration. Order of the elements is specified by a comparator (see libpmemkv(7)).

int pmemkv_exists(pmemkv_db *db, const char *k, size_t kb);

Checks existence of the record with key k of length kb. If the record is present you get PMEMKV_STATUS_OK back, otherwise it's PMEMKV_STATUS_NOT_FOUND. Other possible return values are described in the ERRORS section. It's giving "are you there? πŸ‘€"

int pmemkv_get(pmemkv_db *db, const char *k, size_t kb, pmemkv_get_v_callback *c, void *arg);

Executes function c on the record with key k (of length kb). If the record is present and no error happened, the function returns PMEMKV_STATUS_OK. If the record does not exist, PMEMKV_STATUS_NOT_FOUND is returned. Other possible return values are described in the ERRORS section.

Function c gets called with the following parameters: pointer to a value, size of the value, and the arg you specified. And here's the kicker β€” the value points to the location where data is actually stored (so no copy occurs, it's direct, it's raw, it's real 🌈). This function is guaranteed to be implemented by all engines.

int pmemkv_get_copy(pmemkv_db *db, const char *k, size_t kb, char *buffer, size_t buffer_size, size_t *value_size);

Copies the value of the record with key k of length kb into a user provided buffer. buffer points to the value buffer, buffer_size specifies its size, and *value_size is filled in by this function.

If the value doesn't fit in the provided buffer then this function returns PMEMKV_STATUS_UNKNOWN_ERROR. Rude, but that's the rule 😭

Otherwise, in the absence of any errors, PMEMKV_STATUS_OK is returned. Other possible return values are described in the ERRORS section. This function is guaranteed to be implemented by all engines.

int pmemkv_put(pmemkv_db *db, const char *k, size_t kb, const char *v, size_t vb);

Inserts a key-value pair into the pmemkv database 🫢 Here kb is the length of key k and vb is the length of value v. Once this function returns, you the caller are free to reuse both buffers β€” pmemkv already took what it needed. This function is guaranteed to be implemented by all engines.

int pmemkv_remove(pmemkv_db *db, const char *k, size_t kb);

Removes the record with key k of length kb. Gone, deleted, not coming back πŸ’€ This function is guaranteed to be implemented by all engines.

int pmemkv_defrag(pmemkv_db *db, double start_percent, double amount_percent);

Defragments approximately amount_percent percent of elements in the database, starting from start_percent percent of elements. Tidy up that storage, bestie, your database deserves it ✨

const char *pmemkv_errormsg(void);

Returns a human readable string describing the last error. Actual words, for actual humans πŸ’–


🚨 ERRORS 🚨

Every function β€” except for pmemkv_close() and pmemkv_errormsg() β€” returns one of the following status codes:

  • PMEMKV_STATUS_OK – no error πŸŽ‰
  • PMEMKV_STATUS_UNKNOWN_ERROR – unknown error
  • PMEMKV_STATUS_NOT_FOUND – record not found
  • PMEMKV_STATUS_NOT_SUPPORTED – function is not implemented by current engine
  • PMEMKV_STATUS_INVALID_ARGUMENT – argument to function has wrong value
  • PMEMKV_STATUS_CONFIG_PARSING_ERROR – parsing data to config failed
  • PMEMKV_STATUS_CONFIG_TYPE_ERROR – config item has different type than expected
  • PMEMKV_STATUS_STOPPED_BY_CB – iteration was stopped by user's callback
  • PMEMKV_STATUS_OUT_OF_MEMORY – operation failed because there is not enough memory (or space on the device)
  • PMEMKV_STATUS_WRONG_ENGINE_NAME – engine name does not match any available engine
  • PMEMKV_STATUS_TRANSACTION_SCOPE_ERROR – an error with the scope of the libpmemobj transaction
  • PMEMKV_STATUS_DEFRAG_ERROR – the defragmentation process failed (possibly in the middle of a run)

Heads up bestie, plot twist: the status returned from a function can change in a future version of a library to a more specific one. Like, if a function returns PMEMKV_STATUS_UNKNOWN_ERROR today, it is possible that in future versions it will return PMEMKV_STATUS_INVALID_ARGUMENT instead. So the recommended way to check for an error is to compare the status with PMEMKV_STATUS_OK. Don't hard-code your expectations on the specific error, okay? Protect your peace 🧚

✨ EXAMPLE ✨

Okay bestie, this example is literally pulled straight out of the examples/pmemkv_basic_c directory, so you know it's the real deal πŸ’…

Here's what basic pmemkv usage looks like in C:

#include <assert.h>
#include <libpmemkv.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)
#define MAX_VAL_LEN 64

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

int get_kv_callback(const char *k, size_t kb, const char *value, size_t value_bytes,
            void *arg)
{
    printf("   visited: %s\n", k);

    return 0;
}

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

    /* 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);

    /* Alternatively create_or_error_if_exists flag can be set, to fail if file exists
     * For differences between the two flags, see manpage libpmemkv(7). */
    /* s = pmemkv_config_put_create_or_error_if_exists(cfg, true); */

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

    LOG("Putting new key");
    const char *key1 = "key1";
    const char *value1 = "value1";
    s = pmemkv_put(db, key1, strlen(key1), value1, strlen(value1));
    ASSERT(s == PMEMKV_STATUS_OK);

    size_t cnt;
    s = pmemkv_count_all(db, &cnt);
    ASSERT(s == PMEMKV_STATUS_OK);
    ASSERT(cnt == 1);

    LOG("Reading key back");
    char val[MAX_VAL_LEN];
    s = pmemkv_get_copy(db, key1, strlen(key1), val, MAX_VAL_LEN, NULL);
    ASSERT(s == PMEMKV_STATUS_OK);
    ASSERT(!strcmp(val, "value1"));

    LOG("Iterating existing keys");
    const char *key2 = "key2";
    const char *value2 = "value2";
    const char *key3 = "key3";
    const char *value3 = "value3";
    pmemkv_put(db, key2, strlen(key2), value2, strlen(value2));
    pmemkv_put(db, key3, strlen(key3), value3, strlen(value3));
    pmemkv_get_all(db, &get_kv_callback, NULL);

    LOG("Removing existing key");
    s = pmemkv_remove(db, key1, strlen(key1));
    ASSERT(s == PMEMKV_STATUS_OK);
    ASSERT(pmemkv_exists(db, key1, strlen(key1)) == PMEMKV_STATUS_NOT_FOUND);

    LOG("Defragmenting the database");
    s = pmemkv_defrag(db, 0, 100);
    ASSERT(s == PMEMKV_STATUS_OK);

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

    return 0;
}

🫢 Common usage mistake

Okay so this is THE classic oopsie in pmemkv API usage (and honestly it hits especially hard when you're in the C++ API): dereferencing a pointer to data that lives inside pmemkv from outside the scope of a callback function. It's giving "I kept the receipt but the store is closed" 😭

std::string value;
const char* ptr;
size_t sz;
kv->get("key1", [&](string_view v) {
    /* Save pointer to the data to use it later outside of a callback scope */
    ptr = v.data();
    sz = v.size();
});
kv->remove("key");
/* ERROR!
 * Using this pointer outside of a callback function may cause access to some random data
 * or a segmentation fault. At that point, ptr should be considered as invalid.
 */
value.append(ptr, sz);
No cap, this one bites. Once you're outside the callback, ptr is invalid β€” using it may land you on some random data or a segfault. Not the slay you were hoping for πŸ’€

🌈 SEE ALSO 🌈

Go peek at libpmemkv(7), libpmemkv_config(3), libpmemkv_iterator(3) and <https://pmem.io> β€” that's the whole squad, bestie πŸ’–


And that's a wrap on the whole page, fr fr πŸŽ€ This was PMEMKV, the pmemkv version 1.5.0 era, dated 2022-03-09 β€” iconic, timeless, lowkey a legend πŸ‘‘ You understood all of it, I just know it. Go build something gorgeous with it πŸ’…βœ¨

πŸ“¦ where this page comes from