fluxen 1.1.1
Single-header embedded key-value store for C++20
Loading...
Searching...
No Matches
fluxen::DB Class Reference

A persistent key-value database backed by a single file. More...

#include <fluxen.hpp>

Public Member Functions

 DB (std::string_view path)
 Opens or creates a database at the given path.
 ~DB ()=default
 Closes the database and releases all file locks.
void put (std::string_view key, std::string_view value)
 Stores a string value under the given key.
template<typename T>
requires (std::is_trivially_copyable_v<T> && !std::is_convertible_v<T, std::string_view>)
void put (std::string_view key, const T &value)
 Stores a trivially copyable value under the given key.
template<typename T = std::string>
auto get (std::string_view key) const -> std::optional< T >
 Retrieves the value stored under the given key.
void remove (std::string_view key)
 Deletes the value stored under the given key.
auto has (std::string_view key) const -> bool
 Returns true if the given key exists in the database.
void each (const std::function< void(std::string_view, Bytes)> &fn) const
 Iterates over all live key-value pairs.
void prefix (std::string_view pfx, const std::function< void(std::string_view, Bytes)> &fn) const
 Iterates over all keys that begin with the given prefix.
void transaction (const std::function< TxResult(Tx &)> &fn)
 Executes a batch of operations atomically.
auto compact () -> bool
 Rewrites the database file retaining only live entries.
auto key_count () const -> size_t
 Returns the number of live keys currently stored.
auto file_size () const -> size_t
 Returns the current size of the database file in bytes.

Detailed Description

A persistent key-value database backed by a single file.

Keys are UTF-8 strings up to 255 bytes. Values can be std::string, string literals, or any trivially copyable type (int, float, struct, ...).

The database is safe to use from multiple threads concurrently. Reads proceed in parallel, and writes acquire an exclusive lock and are given priority over waiting readers to prevent starvation. See the Thread safety section in the main page for full details.

Poisoned state
If a write operation fails at the OS level and the subsequent attempt to truncate the partial write also fails, the DB enters a permanent poisoned state. Every subsequent call to any public method will throw std::runtime_error. The only recovery is to destroy the DB object. Normal I/O errors that are successfully rolled back do not poison the database.
Note
DB is non-copyable. Create one instance per file.
Example
fluxen::DB db("app.db");
db.put("hits", int64_t{0});
if (auto val = db.get<int64_t>("hits")) {
std::cout << *val << std::endl;
}
A persistent key-value database backed by a single file.
Definition fluxen.hpp:665

Constructor & Destructor Documentation

◆ DB()

fluxen::DB::DB ( std::string_view path)
inlineexplicit

Opens or creates a database at the given path.

If the file does not exist, it is created and initialised with the magic header. If it exists, it is scanned to rebuild the in-memory index.

Crash recovery
If the file contains a partial entry at the tail (e.g. from a crash mid-write), the partial bytes are automatically truncated and the database opens successfully with all fully committed entries intact.
Parameters
pathFilesystem path to the database file.
Exceptions
std::runtime_errorIf the file cannot be opened or created.
std::runtime_errorIf the file exists but has an invalid magic header (i.e. was not created by fluxen).
std::runtime_errorIf a partial tail entry is detected but truncation fails.

◆ ~DB()

fluxen::DB::~DB ( )
default

Closes the database and releases all file locks.

Note
On Windows, the underlying file is locked as long as this object exists. Ensure this object is destroyed before calling std::filesystem::remove() on the database file.

Member Function Documentation

◆ put() [1/2]

void fluxen::DB::put ( std::string_view key,
std::string_view value )
inline

Stores a string value under the given key.

If the key already exists its value is overwritten. The write is appended to the on-disk log immediately but is not explicitly fsynced. Use transaction() if you require durability guarantees.

Acquires an exclusive lock, blocking until all active readers have finished.

Parameters
keyKey to write. Must be between 1 and 255 bytes.
valueString value to store.
Exceptions
std::runtime_errorIf the database is poisoned.
std::runtime_errorIf the append fails. The partial write is rolled back via truncation before throwing. If truncation also fails, the database is poisoned and the error message will say so.
std::runtime_errorIf the key is empty or longer than 255 bytes.

◆ put() [2/2]

template<typename T>
requires (std::is_trivially_copyable_v<T> && !std::is_convertible_v<T, std::string_view>)
void fluxen::DB::put ( std::string_view key,
const T & value )
inline

Stores a trivially copyable value under the given key.

The value is stored as its raw bytes via memcpy. Overwriting a key with a value of a different size is valid. A subsequent get<T>() call will return nullopt if the stored size does not match sizeof(T).

Acquires an exclusive lock, blocking until all active readers have finished.

Template Parameters
TAny trivially copyable type: int, float, bool, struct, etc. Must not be implicitly convertible to std::string_view.
Parameters
keyKey to write. Must be between 1 and 255 bytes.
valueValue to store.
Exceptions
std::runtime_errorIf the database is poisoned.
std::runtime_errorIf the append fails. The partial write is rolled back via truncation before throwing. If truncation also fails, the database is poisoned and the error message will say so.
std::runtime_errorIf the key is empty or longer than 255 bytes.
Example
struct Config { int port; bool debug; };
db.put("cfg", Config{8080, false});
db.put("score", int32_t{100});

◆ get()

template<typename T = std::string>
auto fluxen::DB::get ( std::string_view key) const->std::optional< T >
inline

Retrieves the value stored under the given key.

Returns std::nullopt in two cases:

  • The key does not exist.
  • For non-string T: the stored byte size differs from sizeof(T).

The default template parameter is std::string, so db.get("key") and db.get<std::string>("key") are equivalent.

Acquires a shared lock, allowing concurrent calls from other readers. If the memory-mapped file is stale from a recent write, this thread will remap it before reading (see ensure_mapped()).

Template Parameters
TThe type to deserialise into. Defaults to std::string. Must be std::string or a trivially copyable type.
Parameters
keyThe key to look up.
Returns
std::optional<T> containing the value, or std::nullopt.
Exceptions
std::runtime_errorIf the database is poisoned.
Example
auto name = db.get("username"); // std::optional<std::string>
auto score = db.get<int32_t>("score"); // std::optional<int32_t>
auto cfg = db.get<Config>("cfg"); // std::optional<Config>
// Idiomatic usage
if (auto v = db.get<int32_t>("score")) {
process(*v);
}

◆ remove()

void fluxen::DB::remove ( std::string_view key)
inline

Deletes the value stored under the given key.

Appends a tombstone entry to the log and removes the key from the index. If the key does not exist this is a no-op. Space used by the old value is reclaimed the next time compact() is called.

Acquires an exclusive lock, blocking until all active readers have finished. Waiting writers take priority over new readers.

Parameters
keyThe key to delete.
Exceptions
std::runtime_errorIf the database is poisoned.
std::runtime_errorIf the append fails. The partial write is rolled back via truncation before throwing. If truncation also fails, the database is poisoned and the error message will say so.
std::runtime_errorIf the key is empty or longer than 255 bytes.

◆ has()

auto fluxen::DB::has ( std::string_view key) const->bool
inlinenodiscard

Returns true if the given key exists in the database.

Acquires a shared lock, allowing concurrent calls from other readers.

Parameters
keyThe key to look up.
Returns
true if the key exists, false otherwise.
Exceptions
std::runtime_errorIf the database is poisoned.

◆ each()

void fluxen::DB::each ( const std::function< void(std::string_view, Bytes)> & fn) const
inline

Iterates over all live key-value pairs.

The callback is invoked once per key with a string_view of the key and a Bytes span pointing directly into the memory-mapped file. The iteration order is unspecified.

Acquires a shared lock for the duration of iteration, allowing concurrent readers. Write methods called from another thread will block until iteration completes.

Parameters
fnCallback of the form void(std::string_view key, Bytes val).
Warning
Do not call any write method from inside fn — the shared lock is not reentrant with an exclusive lock and will deadlock. Copy keys out first if you need to write during iteration. The Bytes span is only valid for the duration of the callback; copy the data if you need it later.
Exceptions
std::runtime_errorIf the database is poisoned.
Example
db.each([](std::string_view key, fluxen::Bytes val) {
std::string v(reinterpret_cast<const char*>(val.data()), val.size());
std::cout << key << " = " << v << "\n";
});
std::span< const std::byte > Bytes
A non-owning view of raw bytes in the memory-mapped file.
Definition fluxen.hpp:117

◆ prefix()

void fluxen::DB::prefix ( std::string_view pfx,
const std::function< void(std::string_view, Bytes)> & fn ) const
inline

Iterates over all keys that begin with the given prefix.

A convenient way to implement namespaced key sets. For example, all keys stored as "user:1001", "user:1002", etc. can be iterated with db.prefix("user:", fn).

The callback and locking behaviour are identical to each(). The same warnings about write methods and Bytes span lifetime apply.

Parameters
pfxThe prefix string to filter by.
fnCallback of the form void(std::string_view key, Bytes val).
Note
Iteration is O(total keys), not O(matching keys), because the underlying index is a hash map with no sorted order.
Exceptions
std::runtime_errorIf the database is poisoned.
Example
db.put("user:james", "admin");
db.put("user:bryce", "viewer");
db.put("config:x", "value");
db.prefix("user:", [](std::string_view key, fluxen::Bytes val) {
// called for user:james and user:bryce only
});

◆ transaction()

void fluxen::DB::transaction ( const std::function< TxResult(Tx &)> & fn)
inline

Executes a batch of operations atomically.

The callback receives a Tx object on which any number of put() and remove() calls can be staged. Returning fluxen::commit applies all operations atomically and flushes them to disk. Returning fluxen::rollback discards all staged operations and the database is left completely unchanged.

If the callback throws, the exception propagates to the caller and all staged operations are discarded (equivalent to rollback).

Parameters
fnA callable of the form TxResult(Tx&).
Exceptions
std::runtime_errorIf the database is poisoned.
std::runtime_errorIf the batch append fails, or if the append succeeds but the subsequent fsync fails. In both cases the partial write is truncated before throwing, leaving the database unchanged. If truncation itself fails after an fsync error, the database is poisoned and every subsequent call will throw.
Note
All staged operations are serialized into a single buffer and written with one syscall and one fsync. This makes transaction() significantly faster than the equivalent number of individual put() calls when writing many keys at once. The callback runs without holding the database mutex, so other threads may write between callback return and commit; the transaction's own operations are always applied atomically.
Example
db.transaction([](fluxen::Tx& tx) {
tx.put("balance", int32_t{500});
tx.put("currency", std::string("USD"));
tx.remove("old_session");
return fluxen::commit;
});
A staged batch of write operations, used inside DB::transaction().
Definition fluxen.hpp:553
void put(std::string_view key, std::string_view value)
Stage a string value to be written.
Definition fluxen.hpp:572
void remove(std::string_view key)
Stage a key deletion.
Definition fluxen.hpp:623
Rolling back
db.transaction([&](fluxen::Tx& tx) {
tx.put("x", new_value);
if (!validate(new_value)) {
return fluxen::rollback; // nothing is written
}
return fluxen::commit;
});

◆ compact()

auto fluxen::DB::compact ( ) ->bool
inlinenodiscard

Rewrites the database file retaining only live entries.

The append-only log accumulates dead entries over time as keys are overwritten or deleted. compact() rewrites the file with only the current live values, reclaiming that space.

The rewrite is crash-safe. Compacted data is first written to a temporary file ( <path>.tmp), fsynced, and then atomically renamed over the original. A crash at any point leaves either the original or the fully written new file intact.

Acquires an exclusive lock, blocking all reads and writes for the duration of the rewrite. The database remains fully consistent. If the process is interrupted mid-compact the old file is still intact.

There is no automatic compaction. Call this periodically if your workload involves many overwrites or deletions.

Returns
true if compaction succeeded. false if the atomic rename failed. In that case the database is fully intact and usable, just not compacted. The caller may retry later.
Exceptions
std::runtime_errorIf the database is poisoned.
std::runtime_errorIf compaction failed and the original file could not be reopened. The DB object must be destroyed; any further use is undefined.
std::runtime_errorIf the file mapping could not be refreshed before or after the rewrite. The DB object must be destroyed due to any further use being undefined.
Warning
On success, invalidates all previously returned Bytes spans and pointers into the mapped region. Reacquire any needed data after a successful compaction. Spans remain valid if false is returned.

◆ key_count()

auto fluxen::DB::key_count ( ) const->size_t
inlinenodiscard

Returns the number of live keys currently stored.

Acquires a shared lock, allowing concurrent calls from other readers.

Returns
Number of keys in the index.
Exceptions
std::runtime_errorIf the database is poisoned.

◆ file_size()

auto fluxen::DB::file_size ( ) const->size_t
inlinenodiscard

Returns the current size of the database file in bytes.

This includes space used by dead entries (overwritten or deleted values) that have not yet been reclaimed by compact(). It is an upper bound on the live data size.

Acquires a shared lock, allowing concurrent calls from other readers.

Returns
File size in bytes.
Exceptions
std::runtime_errorIf the database is poisoned.

The documentation for this class was generated from the following file: