aboutsummaryrefslogtreecommitdiff
path: root/lib/ix.c
diff options
context:
space:
mode:
Diffstat (limited to 'lib/ix.c')
-rw-r--r--lib/ix.c215
1 files changed, 215 insertions, 0 deletions
diff --git a/lib/ix.c b/lib/ix.c
new file mode 100644
index 0000000..0a6af19
--- /dev/null
+++ b/lib/ix.c
@@ -0,0 +1,215 @@
+// SPDX-License-Identifier: LGPL-3.0-or-later
+
+#include "impl.h"
+
+static bool hkvs_ix_add_fast(char *ix, size_t id, const char *key, size_t key_size, uint8_t bits, const char *hk);
+
+static int hkvs_ix_build_try(hkvs_table *t, hkvs_io *io, uint8_t ix_bits) {
+ if(ix_bits > 59) return -EFBIG;
+ uint64_t ix_size_64 = HKVS_IX_SIZE(ix_bits);
+ if(ix_size_64 > SIZE_MAX) return -EFBIG;
+ size_t ix_size = (size_t)ix_size_64;
+
+ char ix_key[HKVS_IX_KEY_SIZE];
+
+ int r = io->random(io, ix_key, HKVS_IX_KEY_SIZE);
+ if(r < 0) return r;
+
+ char *ix;
+ uint64_t fi;
+ r = io->create(io, &fi, &ix, ix_size);
+ if(r < 0) return r;
+
+ size_t key_size = hkvs_read_u8(t->slab.param + HKVS_PARAM_TABLE_KEYSZ);
+ size_t n = hkvs_slab_size(&t->slab);
+ for(size_t i = 0; i < n; i++) {
+ if(hkvs_slab_mark(&t->slab, i) == 0) continue;
+ const char *key = hkvs_slab_at(&t->slab, i);
+ if(!hkvs_ix_add_fast(ix, i, key, key_size, ix_bits, ix_key)) {
+ io->close(io, fi, ix, ix_size);
+ io->unlink(io, fi);
+ return 0;
+ }
+ }
+
+ uint64_t old_fi = hkvs_read_u64(t->slab.param + HKVS_PARAM_TABLE_IXFI);
+ if(old_fi) {
+ if(t->ix) {
+ io->close(io, old_fi, t->ix, t->ix_size);
+ }
+ io->unlink(io, old_fi);
+ }
+
+ t->ix = ix;
+ t->ix_size = ix_size;
+
+ hkvs_write_u8(t->slab.param + HKVS_PARAM_TABLE_IXBIT, ix_bits);
+ hkvs_write_u64(t->slab.param + HKVS_PARAM_TABLE_IXFI, fi);
+ memcpy(t->slab.param + HKVS_PARAM_TABLE_IXKEY, ix_key, HKVS_IX_KEY_SIZE);
+
+ return 1;
+}
+
+int hkvs_ix_build(hkvs_table *t, hkvs_io *io) {
+ size_t count = 0;
+ {
+ size_t n = hkvs_slab_size(&t->slab);
+ for(size_t i = 0; i < n; i++) {
+ if(hkvs_slab_mark(&t->slab, i)) count++;
+ }
+ }
+
+ uint8_t bits = 4;
+ if(count > 0) {
+ size_t load = count - 1;
+ while(load >= 13) {
+ load /= 2;
+ bits++;
+ }
+ }
+
+ size_t attempts = 0;
+ while(true) {
+ int r = hkvs_ix_build_try(t, io, bits);
+ if(r < 0) return r;
+ if(r) return 0;
+
+ attempts++;
+ if(attempts == 8 || attempts == 64) {
+ bits++;
+ }
+ }
+}
+
+int hkvs_ix_add(hkvs *c, hkvs_table *t, size_t id) {
+ const char *key = hkvs_slab_at(&t->slab, id);
+
+ char *ix = t->ix;
+ uint8_t bits = hkvs_table__ix_bits(t);
+ size_t key_size = hkvs_table__key_size(t);
+ const char *hk = hkvs_table__ix_key(t);
+
+ if(hkvs_ix_add_fast(ix, id, key, key_size, bits, hk)) return 0;
+
+ return hkvs_ix_build(t, c->io);
+}
+
+hkvs_rid hkvs_record_find(hkvs *c, hkvs_table *t, const char *key) {
+ (void)c;
+
+ const char *hk = hkvs_table__ix_key(t);
+ size_t key_size = hkvs_table__key_size(t);
+ uint64_t hv = hkvs_hash(hk, key, key_size);
+
+ uint8_t bits = hkvs_table__ix_bits(t);
+ size_t entries = hkvs_slab_size(&t->slab);
+ char *ix = t->ix;
+
+ size_t size = (size_t)1 << bits;
+ size_t mask = size - 1;
+
+ for(size_t disp = 0; disp < HKVS_IX_MAX_DISP; disp++) {
+ size_t at = ((size_t)hv + disp) & mask;
+ char *ent = ix + at * HKVS_IX_ENTRY;
+ uint64_t v = hkvs_read_u64(ent);
+ if(v == 0) return HKVS_INVALID_RID;
+ if(v > entries) goto fail;
+ uint64_t h = hkvs_read_u64(ent + 8);
+ if(h != hv) {
+ if(((at - h) & mask) < disp) break;
+ continue;
+ }
+
+ size_t id = (size_t)(v - 1);
+ char *found = hkvs_slab_at(&t->slab, id);
+ if(!memcmp(key, found, key_size)) {
+ return (hkvs_rid){id};
+ }
+ }
+
+fail:
+ return HKVS_INVALID_RID;
+}
+
+void hkvs_ix_remove(hkvs_table *t, size_t id) {
+ const char *key = hkvs_slab_at(&t->slab, id);
+
+ uint8_t bits = hkvs_table__ix_bits(t);
+ const char *hk = hkvs_table__ix_key(t);
+ size_t key_size = hkvs_table__key_size(t);
+ char *ix = t->ix;
+
+ uint64_t hv = hkvs_hash(hk, key, key_size);
+ size_t size = (size_t)1 << bits;
+ size_t mask = size - 1;
+
+ uint64_t vv = (uint64_t)id + 1;
+
+ char *ent;
+ for(size_t disp = 0; disp < HKVS_IX_MAX_DISP; disp++) {
+ size_t at = ((size_t)hv + disp) & mask;
+ ent = ix + at * HKVS_IX_ENTRY;
+ uint64_t v = hkvs_read_u64(ent);
+ if(v == vv) {
+ if(hkvs_read_u64(ent + 8) != hv) goto corrupted;
+ while(true) {
+ size_t next = (at + 1) & mask;
+ char *e2 = ix + next * HKVS_IX_ENTRY;
+ v = hkvs_read_u64(e2);
+ if(v == 0) break;
+ uint64_t h = hkvs_read_u64(e2 + 8);
+ if((h & mask) == next) break;
+ hkvs_write_u64(ent, v);
+ hkvs_write_u64(ent + 8, h);
+ at = next;
+ ent = e2;
+ }
+ hkvs_write_u64(ent, 0);
+ hkvs_write_u64(ent + 8, 0);
+ return;
+ }
+ if(v == 0) goto corrupted;
+ }
+
+corrupted:
+ hkvs_die("database corrupted");
+}
+
+bool hkvs_ix_add_fast(char *ix, size_t id, const char *key, size_t key_size, uint8_t bits, const char *hk) {
+ uint64_t h_new = hkvs_hash(hk, key, key_size);
+ size_t size = (size_t)1 << bits;
+ size_t mask = size - 1;
+
+ uint64_t v_new = (uint64_t)id + 1;
+
+ for(size_t disp = 0; disp < HKVS_IX_MAX_DISP; disp++) {
+ size_t at = ((size_t)h_new + disp) & mask;
+ char *ent = ix + at * HKVS_IX_ENTRY;
+ uint64_t v_disp = hkvs_read_u64(ent);
+ if(v_disp == 0) {
+ hkvs_write_u64(ent, v_new);
+ hkvs_write_u64(ent + 8, h_new);
+ return true;
+ }
+ uint64_t h_disp = hkvs_read_u64(ent + 8);
+ if(((at - h_disp) & mask) < disp) {
+ hkvs_write_u64(ent, v_new);
+ hkvs_write_u64(ent + 8, h_new);
+ while(true) {
+ at = (at + 1) & mask;
+ ent = ix + at * HKVS_IX_ENTRY;
+ uint64_t v2 = hkvs_read_u64(ent);
+ uint64_t h2 = hkvs_read_u64(ent + 8);
+ hkvs_write_u64(ent, v_disp);
+ hkvs_write_u64(ent + 8, h_disp);
+ if(v2 == 0) return true;
+ v_disp = v2;
+ h_disp = h2;
+ if(((at - h_disp) & mask) >= HKVS_IX_MAX_DISP - 1) return false;
+ }
+ return true;
+ }
+ }
+
+ return false;
+}