aboutsummaryrefslogtreecommitdiff
path: root/include/hkvs.h
blob: 7ab706a69675bb46d0078d1e887286df7f1112f9 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
// 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);

/* ================================================================ */