Keys
This guide shows how keys work in Vix KV.
Use this page when you want to organize values with simple string keys, structured keys, and prefixes.
Public header
#include <vix/kv/kv.hpp>
#include <vix/print.hpp>Key styles
Vix KV supports two common key styles.
| Style | Example | Use when |
|---|---|---|
| String key | "hello" | You want a simple key for the direct API. |
| Structured key | {"users", "1", "name"} | You want grouped data and prefix listing. |
Simple string keys
The direct API uses string keys.
#include <vix/kv/kv.hpp>
#include <vix/print.hpp>
int main()
{
auto kv = vix::kv::open("data/kv");
kv.put("hello", "world");
const auto value = kv.get("hello");
if (value.has_value())
{
vix::print("hello =", *value);
}
(void)kv.close();
return 0;
}Run:
vix run main.cppExpected output:
hello = worldStructured keys
Structured keys use vix::kv::KeyPath.
kv.set({"users", "1", "name"}, "Ada");
kv.set({"users", "1", "email"}, "ada@example.com");
kv.set({"settings", "theme"}, "dark");A structured key is made of segments.
users / 1 / nameIn code:
vix::kv::KeyPath key{"users", "1", "name"};Write with a structured key
#include <vix/kv/kv.hpp>
#include <vix/print.hpp>
int main()
{
auto opened = vix::kv::open_memory();
if (opened.is_err())
{
vix::eprint(opened.error().message());
return 1;
}
auto kv = opened.move_value();
auto written = kv.set({"users", "1", "name"}, "Ada");
if (written.is_err())
{
vix::eprint(written.error().message());
return 1;
}
auto value = kv.get({"users", "1", "name"});
if (value.is_ok())
{
vix::print("users/1/name =", value.value().to_string());
}
(void)kv.close();
return 0;
}Expected output:
users/1/name = AdaRead with the same key
The read key must match the write key.
kv.set({"users", "1", "name"}, "Ada");
auto value = kv.get({"users", "1", "name"});
if (value.is_ok())
{
vix::print(value.value().to_string());
}This is a different key:
kv.get({"users", "name", "1"});Key segment order matters.
Prefix organization
Structured keys are useful for grouping related values.
kv.set({"users", "1", "name"}, "Ada");
kv.set({"users", "1", "email"}, "ada@example.com");
kv.set({"users", "2", "name"}, "Grace");
kv.set({"settings", "theme"}, "dark");This creates a logical layout:
users/
1/
name
email
2/
name
settings/
themeList by prefix
Use list(prefix) to read all live entries under a prefix.
auto users = kv.list({"users"});
if (users.is_err())
{
vix::eprint(users.error().message());
return 1;
}Complete example:
#include <vix/kv/kv.hpp>
#include <vix/print.hpp>
static std::string key_to_string(const vix::kv::KeyPath &key)
{
std::string out;
for (std::size_t i = 0; i < key.size(); ++i)
{
if (i > 0)
{
out += "/";
}
out += key.at(i);
}
return out;
}
int main()
{
auto opened = vix::kv::open_memory();
if (opened.is_err())
{
vix::eprint(opened.error().message());
return 1;
}
auto kv = opened.move_value();
(void)kv.set({"users", "1", "name"}, "Ada");
(void)kv.set({"users", "2", "name"}, "Grace");
(void)kv.set({"settings", "theme"}, "dark");
auto users = kv.list({"users"});
if (users.is_err())
{
vix::eprint(users.error().message());
return 1;
}
for (const auto &[key, value] : users.value())
{
vix::print(key_to_string(key), "=", value.to_string());
}
(void)kv.close();
return 0;
}Example output:
users/1/name = Ada
users/2/name = GraceList all keys
Use list() without a prefix to read all live entries.
auto all = kv.list();
if (all.is_err())
{
vix::eprint(all.error().message());
return 1;
}
for (const auto &[key, value] : all.value())
{
vix::print(value.to_string());
}Check if a key exists
Use contains.
if (kv.contains({"users", "1", "name"}))
{
vix::print("user name exists");
}Complete example:
#include <vix/kv/kv.hpp>
#include <vix/print.hpp>
int main()
{
auto opened = vix::kv::open_memory();
if (opened.is_err())
{
vix::eprint(opened.error().message());
return 1;
}
auto kv = opened.move_value();
(void)kv.set({"users", "1", "name"}, "Ada");
vix::print(
"contains =",
kv.contains({"users", "1", "name"}) ? "yes" : "no");
(void)kv.close();
return 0;
}Expected output:
contains = yesDelete a key
Use erase with the same key path.
auto erased = kv.erase(vix::kv::KeyPath{"users", "1", "name"});
if (erased.is_err())
{
vix::eprint(erased.error().message());
return 1;
}Complete example:
#include <vix/kv/kv.hpp>
#include <vix/print.hpp>
int main()
{
auto opened = vix::kv::open_memory();
if (opened.is_err())
{
vix::eprint(opened.error().message());
return 1;
}
auto kv = opened.move_value();
(void)kv.set({"session", "token"}, "abc123");
vix::print(
"before erase =",
kv.contains({"session", "token"}) ? "exists" : "missing");
auto erased = kv.erase(vix::kv::KeyPath{"session", "token"});
if (erased.is_err())
{
vix::eprint(erased.error().message());
return 1;
}
vix::print(
"after erase =",
kv.contains({"session", "token"}) ? "exists" : "missing");
(void)kv.close();
return 0;
}Expected output:
before erase = exists
after erase = missingKeyPath basics
A KeyPath is a sequence of string segments.
vix::kv::KeyPath key{"users", "1", "profile", "name"};You can inspect its size:
vix::print("segments =", key.size());You can access individual segments:
vix::print("first =", key.at(0));
vix::print("last =", key.at(key.size() - 1));Example:
#include <vix/kv/kv.hpp>
#include <vix/print.hpp>
int main()
{
vix::kv::KeyPath key{"users", "1", "name"};
vix::print("size =", key.size());
vix::print("first =", key.at(0));
vix::print("second =", key.at(1));
vix::print("third =", key.at(2));
return 0;
}Expected output:
size = 3
first = users
second = 1
third = nameRecommended key shape
Use stable, predictable segments.
Good:
kv.set({"users", "1", "name"}, "Ada");
kv.set({"users", "1", "email"}, "ada@example.com");
kv.set({"orders", "1001", "status"}, "paid");
kv.set({"settings", "theme"}, "dark");Avoid putting everything in one flat string when you want prefix listing.
Less useful:
kv.put("users/1/name", "Ada");Better for structured queries:
kv.set({"users", "1", "name"}, "Ada");Prefix examples
Users
kv.set({"users", "1", "name"}, "Ada");
kv.set({"users", "1", "role"}, "admin");
kv.set({"users", "2", "name"}, "Grace");List all users-related entries:
auto users = kv.list({"users"});List a single user:
auto user_1 = kv.list({"users", "1"});Settings
kv.set({"settings", "theme"}, "dark");
kv.set({"settings", "language"}, "en");List settings:
auto settings = kv.list({"settings"});Sessions
kv.set({"sessions", "abc123", "user_id"}, "1");
kv.set({"sessions", "abc123", "expires_at"}, "2026-01-01");Delete one session value:
auto erased = kv.erase(vix::kv::KeyPath{"sessions", "abc123", "user_id"});Common workflows
Store user fields
kv.set({"users", "1", "name"}, "Ada");
kv.set({"users", "1", "email"}, "ada@example.com");
kv.set({"users", "1", "role"}, "admin");Read one field
auto name = kv.get({"users", "1", "name"});
if (name.is_ok())
{
vix::print("name =", name.value().to_string());
}Check existence
if (kv.contains({"users", "1", "email"}))
{
vix::print("email exists");
}List a prefix
auto entries = kv.list({"users", "1"});
if (entries.is_ok())
{
vix::print("fields =", entries.value().size());
}Delete one field
auto erased = kv.erase(vix::kv::KeyPath{"users", "1", "role"});
if (erased.is_err())
{
vix::eprint(erased.error().message());
return 1;
}Common mistakes
Changing segment order
Wrong:
kv.set({"users", "1", "name"}, "Ada");
auto value = kv.get({"users", "name", "1"});Correct:
auto value = kv.get({"users", "1", "name"});Mixing string keys and structured keys accidentally
This direct API key:
kv.put("users/1/name", "Ada");is not the same API style as:
kv.set({"users", "1", "name"}, "Ada");Use one style consistently in the same part of your app.
Using empty keys
Avoid empty key paths.
vix::kv::KeyPath key{};A useful key has at least one segment.
vix::kv::KeyPath key{"settings", "theme"};Forgetting that deletes use the same key path
If the value was written with:
kv.set({"session", "token"}, "abc123");Delete it with:
kv.erase(vix::kv::KeyPath{"session", "token"});Expecting deleted keys in list results
Deleted keys are not returned as live entries.
auto entries = kv.list({"users"});Use stats to inspect tombstones.
const auto stats = kv.stats();
vix::print("tombstones =", stats.tombstone_count);Best practices
Use plural resource names for grouped data.
{"users", "1", "name"}
{"orders", "1001", "status"}
{"sessions", "abc123", "user_id"}Keep keys stable. Avoid changing key shapes between versions unless you also migrate old data.
Use structured keys when you need prefix listing. Use direct string keys for very small examples or simple standalone values.
Related pages
| Page | Purpose |
|---|---|
| Opening a database | Learn how to open memory and durable stores. |
| Values | Learn how to write, read, and delete values. |
| Persistence | Learn how keys and values survive restart. |
| Recovery | Learn how deletes and WAL recovery work. |
| Stats | Learn how to inspect live keys and tombstones. |
Next step
Continue with persistence.