// SPDX-License-Identifier: LGPL-3.0-or-later
#pragma once
#include <errno.h>
#include <stdint.h>
#include <stddef.h>
#include <stdbool.h>
#ifndef HKVS_API
#define HKVS_API extern __attribute__((__nothrow__))
#endif
#ifndef HKVS_INLINE
#define HKVS_INLINE inline static __attribute__((__always_inline__, __unused__, __nothrow__))
#endif
#define HKVS_MUSTCHECK __attribute__((__warn_unused_result__))
#define HKVS_MAGIC ((uint64_t)0xfffec19153564b48ULL)
/* Error codes:
* - `-ENOMEM`: Memory allocation failed.
* - `-EFBIG`: An implementation limit on the database size has been reached.
* Note that this may happen to writes that don't change the number of
* records.
* - `-EBADMSG`: Database corruption has been detected.
*/
/* ================================================================ */
/*
* ---------------------- Core Database API -----------------------
*
* The database contains a set of tables with 32-bit integer identifiers. Each
* table contains records with fixed-size keys and variable-sized values.
*
* The database storage is provided by an implementation of `hkvs_io`. The core
* of the database does not provide transaction support. That has to be
* implemented at the I/O layer.
*/
/* `hkvs*` is the database handle type. */
typedef struct hkvs hkvs;
/* `hkvs_table*` represents a database table. */
typedef struct hkvs_table hkvs_table;
/* `hkvs_io` is an interface for the I/O methods used by the database. */
typedef struct hkvs_io hkvs_io;
/* Open a new database. The database pointer is stored in `*pc`.
*
* On failure, a negative error code is returned and `*pc` is left unchanged.
* The `hkvs_io` is destroyed.
*/
HKVS_API HKVS_MUSTCHECK int hkvs_new(hkvs **pc, hkvs_io *io);
/* Close the database and destroy its `hkvs_io`.
*
* This method does not fail. */
HKVS_API void hkvs_free(hkvs *c);
/* Find a table by index. If fewer tables exist, NULL is returned.
*
* This method does not fail. */
HKVS_API hkvs_table *hkvs_table_at(hkvs *c, size_t i);
/* Find a table by ID. If no table with the given ID exists or its key size does
* not match, NULL is returned.
*
* This method does not fail. */
HKVS_API hkvs_table *hkvs_table_open(hkvs *c, uint32_t id, size_t key_size);
/* Create a table and store its pointer in `*ptable`.
*
* Record values with size <= `val_size_hint` may be stored together with the
* key.
*
* A positive return value indicates that a table was created, and 0 means that
* the table already exists.
*
* On failure, a negative error code is returned and the database is left
* unchanged. If a table with the given ID exists but has a different key size,
* `-EEXIST` is returned. */
HKVS_API int hkvs_table_create(hkvs *c, hkvs_table **ptable, uint32_t id, size_t key_size, size_t val_size_hint);
/* Delete the table with the given ID. A positive return value indicates that a
* table was found and deleted, and 0 means that no table with the given ID
* exists.
*
* On failure, a negative error code is returned and the database is left
* unchanged. */
HKVS_API int hkvs_table_delete(hkvs *c, uint32_t id);
/* Get the ID of the given table.
*
* This method does not fail. */
HKVS_API uint32_t hkvs_table_id(hkvs *c, hkvs_table *t);
/* Get the key size of the given table.
*
* This method does not fail. */
HKVS_API size_t hkvs_table_key_size(hkvs *c, hkvs_table *t);
/* Invalidate all record key and value pointers. This may release some
* resources.
*
* This method does not fail. */
HKVS_API void hkvs_put_records(hkvs *c);
/* ================================================================ */
/*
* -------------------------- Record API --------------------------
*
* The record API provides access to the contents of tables.
*
* Record IDs are 64-bit integers. New record IDs are assigned either
* sequentially or by reusing IDs of deleted records.
*
* Record IDs are persistent, meaning that a record will keep its ID until
* deleted.
*/
/* `hkvs_rid` is a wrapper for a record ID. */
typedef struct hkvs_rid hkvs_rid;
struct hkvs_rid {
uint64_t id;
};
#define HKVS_INVALID_RID ((struct hkvs_rid){(size_t)-1})
/* Check if a record ID is valid. */
HKVS_INLINE bool hkvs_rid_ok(hkvs_rid rid) {
return rid.id <= UINT64_MAX / 2;
}
/* Return a pointer to the key of the given record, or NULL if no record with
* that ID exists. The pointer is invalidated by methods that add, remove, or
* resize records of any table.
*
* This method does not fail. */
HKVS_API HKVS_MUSTCHECK const char *hkvs_record_key(hkvs *c, hkvs_table *t, hkvs_rid rid);
/* Get the data pointer and value of the given record. If the record exists,
* a positive value is returned. If not, 0 is returned.
*
* The pointer is invalidated by methods that add, remove, or resize records
* of any table. Do not forget to call `hkvs_put_records` when done with the
* value.
*
* On failure, a negative error code is returned. */
HKVS_API HKVS_MUSTCHECK int hkvs_record_value(hkvs *c, hkvs_table *t, hkvs_rid rid, char **pvalue, size_t *psize);
/* Resize a given record. Only the first `min(keep_size, new_size, old_size)`
* bytes of the value are retained, the rest of the value is uninitialized.
*
* If `pvalue` is provided, a pointer to the record value is stored there. Do
* not forget to call `hkvs_put_records` when done with the value.
*
* It is a programmer error to call this method with an ID that does not
* correspond to an existing record.
*
* On failure, a negative error code is returned and the database is left
* unchanged. */
HKVS_API HKVS_MUSTCHECK int hkvs_record_resize(hkvs *c, hkvs_table *t, hkvs_rid rid, char **pvalue, size_t new_size, size_t keep_size);
/* Find the record with the given key. If no record was found,
* `HKVS_INVALID_RID` is returned.
*
* This method does not fail. */
HKVS_API HKVS_MUSTCHECK hkvs_rid hkvs_record_find(hkvs *c, hkvs_table *t, const char *key);
/* Delete the given record.
*
* It is a programmer error to call this method with an ID that does not
* correspond to an existing record.
*
* This method does not fail. */
HKVS_API void hkvs_record_delete(hkvs *c, hkvs_table *t, hkvs_rid rid);
/* Append a new record to the table. The ID of the new record is stored in
* `*prid`.
*
* It is a programmer error to call this method with a key that is already in
* the table.
*
* On failure, a negative error code is returned and the database is left
* unchanged. */
HKVS_API HKVS_MUSTCHECK int hkvs_record_append(hkvs *c, hkvs_table *t, hkvs_rid *prid, const char *key, char **pvalue, size_t size);
/* ================================================================ */
/*
* ---------------------- Utility Functions -----------------------
*
* These are some utility functions that can be correctly implemented using
* the rest of the public API. However, the implementations provided here may be
* slightly more efficient.
*/
/* Set the value of a record.
*
* It is a programmer error to call this method with a record ID that does not
* correspond to an existing record.
*
* On failure, a negative error code is returned and the database is left
* unchanged. */
HKVS_API HKVS_MUSTCHECK int hkvs_record_set(hkvs *c, hkvs_table *t, hkvs_rid rid, const char *value, size_t size);
/* Append or set the record with the given key.
*
* The ID of the record is stored in `*prid`. If a new record was created, 1
* is returned. If an existing record was changed, 0 is returned.
*
* On failure, a negative error code is returned and the database is left
* unchanged. */
HKVS_API HKVS_MUSTCHECK int hkvs_record_update(hkvs *c, hkvs_table *t, hkvs_rid *prid, const char *key, const char *value, size_t size);
/* ================================================================ */
/*
* ------------------------- Iterator API -------------------------
*
* An iterator provides a way to enumerate all records in a table. Iterators
* themselves do not consume any resources.
*
* The fields of `hkvs_iter` are an implementation detail.
*/
typedef struct hkvs_iter hkvs_iter;
struct hkvs_iter {
size_t priv1, priv2;
};
/* Initialize an iterator. The first call to `hkvs_iter_next` on the iterator
* will return the first record. Any change of the database invalidates the
* iterator, and it must be reinitialized.
*
* This method does not fail.
*/
HKVS_API void hkvs_iter_open(hkvs *c, hkvs_table *t, hkvs_iter *it);
/* Initialize an iterator positioned at a given record. The next call to
* `hkvs_iter_next()` will return the first record with an ID greater than
* or equal to the one given here.
*
* This method does not fail. */
HKVS_API void hkvs_iter_seek(hkvs *c, hkvs_table *t, hkvs_iter *it, hkvs_rid rid);
/* Retrieve the ID of the next record. If `pkey` is provided, a pointer to the
* record's key is stored there.
*
* If the end of the database has been reached, `HKVS_INVALID_RID` is returned.
*
* This method does not fail. */
HKVS_API HKVS_MUSTCHECK hkvs_rid hkvs_iter_next(hkvs *c, hkvs_table *t, hkvs_iter *it, const char **pkey);
/* Delete the record last returned by `hkvs_iter_next()` without invalidating
* the iterator. Key and value pointers are still invalidated.
*
* It is a programmer error to call this method without a preceding call to
* `hkvs_iter_next()`.
*
* This method does not fail. */
HKVS_API void hkvs_iter_delete(hkvs *c, hkvs_table *t, hkvs_iter *it);
/* ================================================================ */
/*
* ------------------------- hkvs_io API --------------------------
*
* The I/O interface consists of several methods for operating on memory-mapped
* files.
*
* In order to initialize the database, an empty file 0 must be created. It will
* never be deleted.
*/
struct hkvs_io {
/* Destroy the IO interface. */
void (*destroy)(hkvs_io *io);
/* Write random bytes to the given buffer. */
int (*random)(hkvs_io *io, char *buf, size_t size);
/* Open the file and memory-map it. The size is stored in `*psz` and the
* address is stored in `*pmem`. The file is not open when this method is
* called. */
int (*open)(hkvs_io *io, uint64_t fi, char **pmem, size_t *psz);
/* Create a zero-filled file with the given size and open it. */
int (*create)(hkvs_io *io, uint64_t *pfi, char **pmem, size_t sz);
/* Resize the file and update its memory-mapped address. The file is open
* and mapped at `*pmem` with size `old_sz` when this method is called. */
int (*resize)(hkvs_io *io, uint64_t fi, char **pmem, size_t old_sz, size_t new_sz);
/* Release the file's memory mapping and close it. The file is open and
* mapped at `mem` with size `sz` when this method is called. */
void (*close)(hkvs_io *io, uint64_t fi, char *mem, size_t sz);
/* Delete the file. The file is not open when this method is called. */
void (*unlink)(hkvs_io *io, uint64_t fi);
};
/* Create a new `hkvs_io` that entirely works in memory. */
HKVS_API hkvs_io *hkvs_io_new_mem(uint64_t random_seed);
/* Create a new `hkvs_io` given a directory file descriptor. This implementation
* is simple but it does no locking and is not crash-safe. */
HKVS_API hkvs_io *hkvs_io_new_unsafe_dirfd(int fd);
/* ================================================================ */