Last active
July 11, 2024 20:57
-
-
Save jweinst1/d7be819d15f98b1072907c4ce27080d6 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <stdio.h> | |
#include <stdint.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#include <limits.h> | |
#include <sys/mman.h> | |
#include <sys/stat.h> | |
#include <sys/socket.h> | |
#include <sys/stat.h> | |
#include <arpa/inet.h> | |
#include <netinet/in.h> | |
#include <assert.h> | |
#include <errno.h> | |
#include <fcntl.h> | |
#include <unistd.h> | |
static int file_exists(const char* path) { | |
struct stat buffer; | |
return stat(path, &buffer) == 0; | |
} | |
static ssize_t file_size(const char* path) { | |
struct stat sb; | |
if (stat(path, &sb) == 0) { | |
return sb.st_size; | |
} | |
return -1; | |
} | |
static void create_init_file(const char* path, size_t size) { | |
int fd = open(path, O_RDWR | O_CREAT, (mode_t)0600); | |
lseek(fd, size-1, SEEK_SET); | |
write(fd, "", 1); | |
lseek(fd, 0, SEEK_SET); | |
close(fd); | |
} | |
static size_t get_page_size(void) { | |
return sysconf(_SC_PAGE_SIZE); | |
} | |
#define DJB2_HASH_BASE 5381 | |
size_t hash_djb2(const char *str) { | |
size_t hash = DJB2_HASH_BASE; | |
int c; | |
while ((c = *str++)) | |
hash = ((hash << 5) + hash) + c; | |
return hash; | |
} | |
int hash_djb2_n(const char *str, size_t n, size_t* hash) { | |
int c; | |
while(n-- && (c = *str++)) | |
*hash = ((*hash << 5) + *hash) + c; | |
return c == 0; | |
} | |
static char* str_dupl(const char* src) { | |
size_t src_size = strlen(src) + 1; | |
char* newstr = malloc(src_size); | |
memcpy(newstr, src, src_size); | |
return newstr; | |
} | |
enum dbstore_type { | |
DBSTORE_MEM_MAP, | |
//DBSTORE_FILE, todo, in future | |
DBSTORE_IN_MEM | |
}; | |
struct dbfile { | |
size_t page_size; | |
char* filepath; | |
size_t file_size; | |
size_t page_count; | |
size_t page_cap; | |
char** pages; | |
int fd; | |
}; | |
struct dbcfg { | |
size_t page_size; | |
enum dbstore_type ftype; | |
}; | |
int dbcfg_validate(const struct dbcfg* cfg) { | |
if (cfg == NULL) { | |
return 1; | |
} | |
if (cfg->ftype == DBSTORE_MEM_MAP) { | |
// If using mmap, the offsets must be a multiple of the os page size | |
if (cfg->page_size > 0 && cfg->page_size % get_page_size() != 0) { | |
return 0; | |
} | |
} | |
return 1; | |
} | |
int dbfile_open(struct dbfile* dbf, const char* path, struct dbcfg* cfg) { | |
if(!dbcfg_validate(cfg)) { | |
return 0; | |
} | |
dbf->page_size = cfg != NULL ? cfg->page_size : get_page_size(); | |
if (!file_exists(path)) { | |
create_init_file(path, dbf->page_size * 1); //todo | |
} | |
ssize_t dbsize = file_size(path); | |
int fd = open(path, O_RDWR | O_CREAT, (mode_t)0600); | |
if (fd == -1) { | |
return 0; | |
} | |
dbf->filepath = str_dupl(path); | |
dbf->page_cap = 10; | |
dbf->file_size = dbsize; | |
dbf->page_count = dbf->file_size / dbf->page_size; | |
dbf->page_cap += dbf->page_count; | |
dbf->fd = fd; | |
dbf->pages = calloc(1, sizeof(char*) * dbf->page_cap); | |
for (size_t i = 0; i < dbf->page_count; ++i){ | |
char* pagemap = mmap(0, dbf->page_size, PROT_READ | PROT_WRITE, MAP_SHARED, dbf->fd, i * dbf->page_size); | |
dbf->pages[i] = pagemap; | |
} | |
return 1; | |
} | |
size_t dbfile_grow(struct dbfile* dbf , size_t n_pages) { | |
if ((dbf->page_count + n_pages) > dbf->page_cap) { | |
dbf->page_cap += n_pages * 5; | |
dbf->pages = realloc(dbf->pages, dbf->page_cap); | |
} | |
size_t size_increase = n_pages * dbf->page_size; | |
dbf->file_size += size_increase; | |
lseek(dbf->fd, dbf->file_size-1, SEEK_SET); | |
write(dbf->fd, "", 1); | |
lseek(dbf->fd, 0, SEEK_SET); | |
size_t prev_page_count = dbf->page_count; | |
dbf->page_count += n_pages; | |
return prev_page_count; | |
// wait for pages to need to be mapped into memory lazily | |
} | |
// todo, allow non linear get of page | |
char* dbfile_get_page(struct dbfile* dbf, size_t n) { | |
if (n >= dbf->page_count) { | |
dbfile_grow(dbf, (n - dbf->page_count) + 1); | |
} | |
if (dbf->pages[n] == NULL) { | |
char* pagemap = mmap(0, dbf->page_size, PROT_READ | PROT_WRITE, MAP_SHARED, dbf->fd, n * dbf->page_size); | |
dbf->pages[n] = pagemap; | |
} | |
return dbf->pages[n]; | |
} | |
int dbfile_get_place(struct dbfile* dbf, size_t offset, size_t* result) { | |
if (offset >= dbf->file_size) { | |
return 0; | |
} | |
result[0] = offset / dbf->page_size; | |
result[1] = offset % dbf->page_size; | |
return 1; | |
} | |
int dbfile_write(struct dbfile* dbf, size_t offset, const char* data, size_t size) { | |
size_t place[2] = {0}; | |
if (!dbfile_get_place(dbf, offset, place)) { | |
return 0; | |
} | |
size_t cur_page = place[0]; | |
size_t cur_off = place[1]; | |
//printf("cur_page %zu cur off %zu\n", cur_page, cur_off); | |
while (size) { | |
char* page = dbfile_get_page(dbf, cur_page); | |
size_t to_write = dbf->page_size - cur_off; | |
//printf("to write %zu\n", to_write); | |
memcpy(page + cur_off, data, to_write > size ? size : to_write ); | |
size = to_write > size ? 0 : size - to_write; | |
data += to_write; | |
cur_off = 0; | |
++cur_page; | |
} | |
return 1; | |
} | |
int dbfile_write_po(struct dbfile* dbf, size_t page, size_t offset, const char* data, size_t size) { | |
size_t cur_page = page; | |
size_t cur_off = offset; | |
while (size) { | |
char* page = dbfile_get_page(dbf, cur_page); | |
size_t to_write = dbf->page_size - cur_off; | |
memcpy(page + cur_off, data, to_write > size ? size : to_write ); | |
size = to_write > size ? 0 : size - to_write; | |
data += to_write; | |
cur_off = 0; | |
++cur_page; | |
} | |
return 1; | |
} | |
int dbfile_read_po(struct dbfile* dbf, size_t page, size_t offset, char* data, size_t size) { | |
size_t cur_page = page; | |
size_t cur_off = offset; | |
while (size) { | |
char* page = dbfile_get_page(dbf, cur_page); | |
size_t to_read = dbf->page_size - cur_off; | |
memcpy(data, page + cur_off, to_read > size ? size : to_read ); | |
size = to_read > size ? 0 : size - to_read; | |
data += to_read; | |
cur_off = 0; | |
++cur_page; | |
} | |
return 1; | |
} | |
size_t dbfile_hash_null(struct dbfile* dbf, size_t page, size_t offset) { | |
size_t cur_page = page; | |
size_t cur_off = offset; | |
size_t hashbase = DJB2_HASH_BASE; | |
int found_null = 0; | |
while (!found_null) { | |
char* page = dbfile_get_page(dbf, cur_page); | |
size_t to_read = dbf->page_size - cur_off; | |
found_null = hash_djb2_n(page + cur_off, to_read, &hashbase); | |
cur_off = 0; | |
++cur_page; | |
} | |
return hashbase; | |
} | |
int dbfile_read(struct dbfile* dbf, size_t offset, char* data, size_t size) { | |
size_t place[2] = {0}; | |
if (!dbfile_get_place(dbf, offset, place)) { | |
return 0; | |
} | |
size_t cur_page = place[0]; | |
size_t cur_off = place[1]; | |
while (size) { | |
char* page = dbfile_get_page(dbf, cur_page); | |
size_t to_read = dbf->page_size - cur_off; | |
memcpy(data, page + cur_off, to_read > size ? size : to_read ); | |
size = to_read > size ? 0 : size - to_read; | |
data += to_read; | |
cur_off = 0; | |
++cur_page; | |
} | |
return 1; | |
} | |
int dbfile_cmp_null(struct dbfile* dbf, size_t page, size_t offset, const char* data, size_t size) { | |
size_t cur_page = page; | |
size_t cur_off = offset; | |
//printf("cur_page %zu cur off %zu\n", cur_page, cur_off); | |
while (size) { | |
char* page = dbfile_get_page(dbf, cur_page); | |
size_t to_read = dbf->page_size - cur_off; | |
//printf("to read %zu\n", to_read); | |
if (strncmp(data, page + cur_off, to_read > size ? size : to_read ) != 0) { | |
return 0; | |
} | |
size = to_read > size ? 0 : size - to_read; | |
data += to_read; | |
cur_off = 0; | |
++cur_page; | |
} | |
return 1; | |
} | |
void dbfile_close(struct dbfile* dbf) { | |
for (size_t i = 0; i < dbf->page_count; ++i){ | |
if(munmap(dbf->pages[i], dbf->page_size) == -1) { | |
fprintf(stderr, "Failed to unmap page %zu\n", i); | |
} | |
} | |
close(dbf->fd); | |
dbf->fd = -1; | |
} | |
void dbfile_path_free(struct dbfile* dbf) { | |
if (dbf->filepath != NULL) { | |
free(dbf->filepath); | |
dbf->filepath = NULL; | |
} | |
} | |
void dbfile_remove(struct dbfile* dbf) { | |
if (dbf->filepath != NULL) { | |
remove(dbf->filepath); | |
} | |
} | |
// storage pointer form | |
// [page number][offset in page][size] | |
// number = 4 bytes | |
// offset in page 4 bytes | |
// size = 4 bytes | |
// hash storage pointer form | |
// [page number][offset in page][size] | |
// number = 4 bytes | |
// offset in page 4 bytes | |
// size = 4 bytes | |
// hash len = 4 bytes <--- used for rehashes | |
// | |
// hash blocks | |
// * dependent on page size | |
// [next page][hashslot] | |
// hashslot = storage pointer | |
// | |
// storage blocks (optional , can just be free blocks) | |
// [-header-][-list-] | |
// [next page][len][page number...][page number....] | |
// | |
// free blocks | |
// [-header-][-list-] | |
// [next page][len][total free space][storageslot...] | |
// storageslot = storage pointer | |
// | |
// db header | |
// magic string, 4 bytes | |
// [hashblocks root] = 4 bytes | |
// [storageblocks root] = 4 bytes (optional) | |
// [freeblocks root] = 4 bytes | |
// hash block count (for hash modulo) | |
static const char MAGIC_SEQ[] = {'k', 'h', 'o', 'm'}; | |
static const size_t STORAGE_PTR_SIZE = sizeof(int32_t) * 3; | |
static const size_t HASHSTORAGE_PTR_SIZE = sizeof(int32_t) * 3; | |
static const size_t HASHSTORAGE_PTR_SIZE_INT = HASHSTORAGE_PTR_SIZE / sizeof(int32_t); | |
static const size_t LEN_BLOCK_HEADER_SIZE = sizeof(int32_t) * 2; | |
static const size_t HASH_BLOCK_HEADER_SIZE = sizeof(int32_t); | |
static const size_t DB_HEADER_PAGE_SIZE_OFF = sizeof(MAGIC_SEQ) + (sizeof(int32_t) * 2); | |
static const size_t DB_HEADER_HASH_LEN_OFF = sizeof(MAGIC_SEQ) + (sizeof(int32_t) * 3); | |
static const size_t DB_HEADER_ITEM_COUNT_OFF = sizeof(MAGIC_SEQ) + (sizeof(int32_t) * 4); | |
size_t items_per_block(size_t page_size) { | |
size_t slot_port = page_size - LEN_BLOCK_HEADER_SIZE; | |
return (slot_port - (slot_port % STORAGE_PTR_SIZE)) / STORAGE_PTR_SIZE; | |
} | |
size_t hashes_per_block(size_t page_size) { | |
size_t slot_port = page_size - HASH_BLOCK_HEADER_SIZE; | |
return (slot_port - (slot_port % HASHSTORAGE_PTR_SIZE)) / HASHSTORAGE_PTR_SIZE; | |
} | |
struct database { | |
struct dbfile dbf; | |
}; | |
int _has_magic_seq(const char* page) { | |
return page[0] == MAGIC_SEQ[0] && | |
page[1] == MAGIC_SEQ[1] && | |
page[2] == MAGIC_SEQ[2] && | |
page[3] == MAGIC_SEQ[3]; | |
} | |
void _erase_region(char* ptr, size_t off, size_t size, size_t end) { | |
char* temp = malloc(end - (off + size)); | |
memcpy(temp, ptr + off + size, end - (off + size)); | |
memcpy(ptr + off, temp, end - (off + size)); | |
free(temp); | |
} | |
void _write_storage_ptr(char* ptr, int32_t page, int32_t off, int32_t size) { | |
int32_t* writer = (int32_t*)ptr; | |
writer[0] = page; | |
writer[1] = off; | |
writer[2] = size; | |
} | |
void _read_storage_ptr(const char* ptr, int32_t* page, int32_t* off, int32_t* size) { | |
int32_t* writer = (int32_t*)ptr; | |
*page = writer[0]; | |
*off = writer[1]; | |
*size = writer[2]; | |
} | |
void _write_storage_ptr_w(char* ptr, int32_t* input) { | |
int32_t* writer = (int32_t*)ptr; | |
writer[0] = input[0]; | |
writer[1] = input[1]; | |
writer[2] = input[2]; | |
} | |
void _write_storage_ptr_hash(int32_t* writer, const int32_t* input) { | |
writer[0] = input[0]; | |
writer[1] = input[1]; | |
writer[2] = input[2]; | |
} | |
void _read_storage_ptr_w(const char* ptr, int32_t* output) { | |
int32_t* writer = (int32_t*)ptr; | |
output[0] = writer[0]; | |
output[1] = writer[1]; | |
output[2] = writer[2]; | |
} | |
void _read_storage_ptr_po(const char* ptr, int32_t size, int32_t* output) { | |
int32_t* writer = (int32_t*)ptr; | |
output[0] = writer[0]; | |
output[1] = writer[1]; | |
output[2] = size; | |
} | |
void _shift_storage_ptr(char* ptr, int32_t amount, size_t page_size) { | |
int32_t* writer = (int32_t*)ptr; | |
writer[0] += (amount / page_size); | |
writer[1] += amount; | |
writer[2] -= amount; | |
} | |
int _has_size_storage_ptr(const char* ptr, int32_t size) { | |
int32_t* reader = (int32_t*)ptr; | |
return reader[2] >= size; | |
} | |
int _is_empty_ins_storage_ptr(const int32_t* reader) { | |
return reader[0] == 0; | |
} | |
int _is_del_storage_ptr(const int32_t* reader) { | |
return reader[0] == -1; | |
} | |
void _mark_del_storage_ptr(int32_t* reader) { | |
reader[0] = -1; | |
} | |
int _is_size_zero_storage_ptr(const char* ptr) { | |
int32_t* reader = (int32_t*)ptr; | |
return reader[2] == 0; | |
} | |
int32_t database_len_get(const char* block) { | |
const int32_t* ptr = (const int32_t*)block; | |
return ptr[1]; | |
} | |
void database_len_dec(char* block) { | |
int32_t* ptr = (int32_t*)block; | |
ptr[1] -= 1; | |
} | |
void database_len_init(char* block) { | |
int32_t* ptr = (int32_t*)block; | |
ptr[0] = -1; | |
ptr[1] = 0; | |
} | |
void database_hash_init(char* block) { | |
int32_t* ptr = (int32_t*)block; | |
ptr[0] = -1; | |
} | |
int32_t database_get_hashroot(struct database* db) { | |
char* header = dbfile_get_page(&db->dbf, 0); | |
return *(int32_t*)(header + sizeof(MAGIC_SEQ)); | |
} | |
void database_set_hashroot(struct database* db, int32_t new_root) { | |
char* header = dbfile_get_page(&db->dbf, 0); | |
*(int32_t*)(header + sizeof(MAGIC_SEQ)) = new_root; | |
} | |
int32_t database_get_spaceroot(struct database* db) { | |
char* header = dbfile_get_page(&db->dbf, 0); | |
return *(int32_t*)(header + sizeof(MAGIC_SEQ) + sizeof(int32_t)); | |
} | |
size_t database_get_hash_len(struct database* db) { | |
char* header = dbfile_get_page(&db->dbf, 0); | |
int32_t hash_block_len = *(int32_t*)(header + DB_HEADER_HASH_LEN_OFF); | |
return hash_block_len * hashes_per_block(db->dbf.page_size); | |
} | |
int32_t database_get_hash_count(struct database* db) { | |
char* header = dbfile_get_page(&db->dbf, 0); | |
int32_t hash_block_len = *(int32_t*)(header + DB_HEADER_HASH_LEN_OFF); | |
return hash_block_len; | |
} | |
int32_t database_get_page_size(struct database* db) { | |
char* header = dbfile_get_page(&db->dbf, 0); | |
int32_t page_size = *(int32_t*)(header + DB_HEADER_PAGE_SIZE_OFF); | |
return page_size; | |
} | |
int64_t database_get_item_count(struct database* db) { | |
char* header = dbfile_get_page(&db->dbf, 0); | |
int64_t item_len = *(int64_t*)(header + DB_HEADER_ITEM_COUNT_OFF); | |
return item_len; | |
} | |
int64_t database_inc_item_count(struct database* db, int64_t amount) { | |
char* header = dbfile_get_page(&db->dbf, 0); | |
int64_t* item_len = (int64_t*)(header + DB_HEADER_ITEM_COUNT_OFF); | |
*item_len += amount; | |
return *item_len; | |
} | |
int64_t database_dec_item_count(struct database* db, int64_t amount) { | |
char* header = dbfile_get_page(&db->dbf, 0); | |
int64_t* item_len = (int64_t*)(header + DB_HEADER_ITEM_COUNT_OFF); | |
*item_len -= amount; | |
return *item_len; | |
} | |
double database_get_load_factor(struct database* db) { | |
return (double)database_get_item_count(db) / (double)database_get_hash_len(db); | |
} | |
void database_set_hash_count(struct database* db, int32_t new_count) { | |
char* header = dbfile_get_page(&db->dbf, 0); | |
*(int32_t*)(header + DB_HEADER_HASH_LEN_OFF) = new_count; | |
} | |
size_t database_get_hash_block_count(struct database* db) { | |
int32_t hash_iter = database_get_hashroot(db); | |
size_t total = 0; | |
while (hash_iter != -1) { | |
char* hash_page = dbfile_get_page(&db->dbf, hash_iter); | |
int32_t* header = (int32_t*)hash_page; | |
hash_iter = header[0]; | |
++total; | |
} | |
return total; | |
} | |
int32_t database_get_hash_block(struct database* db, size_t n) { | |
int32_t hash_iter = database_get_hashroot(db); | |
while (n-- && hash_iter != -1) { | |
char* hash_page = dbfile_get_page(&db->dbf, hash_iter); | |
int32_t* header = (int32_t*)hash_page; | |
hash_iter = header[0]; | |
} | |
return hash_iter; | |
} | |
size_t database_get_space_block_count(struct database* db) { | |
int32_t space_iter = database_get_spaceroot(db); | |
size_t total = 0; | |
while (space_iter != -1) { | |
char* space_page = dbfile_get_page(&db->dbf, space_iter); | |
int32_t* header = (int32_t*)space_page; | |
space_iter = header[0]; | |
++total; | |
} | |
return total; | |
} | |
void database_inc_hash_len(struct database* db) { | |
char* header = dbfile_get_page(&db->dbf, 0); | |
int32_t* hash_block_len = (int32_t*)(header + DB_HEADER_HASH_LEN_OFF); | |
*hash_block_len += 1; | |
} | |
void database_recomp_hash_len(struct database* db) { | |
size_t new_hash_block_count = database_get_hash_block_count(db); | |
char* header = dbfile_get_page(&db->dbf, 0); | |
int32_t* hash_block_len = (int32_t*)(header + DB_HEADER_HASH_LEN_OFF); | |
*hash_block_len = new_hash_block_count; | |
} | |
int32_t database_find_space_ptr(char* block, int32_t min_size, size_t page_size, int32_t* result) { | |
char* read_location = block + LEN_BLOCK_HEADER_SIZE; | |
int32_t len = ((int32_t*)block)[1]; // todo safe function for this | |
for (int32_t i = 0; i < len; ++i) | |
{ | |
if (_has_size_storage_ptr(read_location, min_size)) { | |
_read_storage_ptr_po(read_location, min_size, result); | |
_shift_storage_ptr(read_location, min_size, page_size); | |
if (_is_size_zero_storage_ptr(read_location)) { | |
_erase_region(block, read_location - block, STORAGE_PTR_SIZE, page_size); | |
database_len_dec(block); | |
} | |
return i; | |
} | |
read_location += STORAGE_PTR_SIZE; | |
} | |
return -1; | |
} | |
int32_t database_find_space_block(struct database* db) { | |
int32_t space_iter = database_get_spaceroot(db); | |
while (space_iter != -1) { | |
char* space_page = dbfile_get_page(&db->dbf, space_iter); | |
int32_t* header = (int32_t*)space_page; | |
if(header[1] < items_per_block(db->dbf.page_size)) { | |
return space_iter; | |
} | |
space_iter = header[0]; | |
} | |
return -1; | |
} | |
int32_t database_find_space_storage(struct database* db, int32_t min_size, int32_t* result) { | |
int32_t space_iter = database_get_spaceroot(db); | |
while (space_iter != -1) { | |
char* space_page = dbfile_get_page(&db->dbf, space_iter); | |
int32_t* header = (int32_t*)space_page; | |
if (database_find_space_ptr(space_page, min_size, db->dbf.page_size, result) != -1) { | |
return 0; | |
} | |
space_iter = header[0]; | |
} | |
return -1; | |
} | |
// access into a len block | |
int32_t* _len_block_read(char* block, int32_t n, int32_t* result) { | |
int32_t* header = (int32_t*)block; | |
if (n >= header[1]) { | |
return NULL; | |
} | |
char* read_location = block + LEN_BLOCK_HEADER_SIZE + (n * STORAGE_PTR_SIZE); | |
_read_storage_ptr_w(read_location, result); | |
return (int32_t*)read_location; | |
} | |
int32_t* _hash_block_at(char* block, int32_t n) { | |
char* read_location = block + HASH_BLOCK_HEADER_SIZE + (n * HASHSTORAGE_PTR_SIZE); | |
return (int32_t*)read_location; | |
} | |
int32_t* _hash_block_begin(char* block) { | |
return (int32_t*)(block + HASH_BLOCK_HEADER_SIZE); | |
} | |
int32_t* _hash_block_end(char* block, size_t page_size) { | |
return (int32_t*)(block + HASH_BLOCK_HEADER_SIZE + (hashes_per_block(page_size) * HASHSTORAGE_PTR_SIZE)); | |
} | |
void database_place_ptr_in_len_block(char* block, int32_t page, int32_t off, int32_t size) { | |
int32_t* header = (int32_t*)block; | |
char* write_location = block + LEN_BLOCK_HEADER_SIZE + (header[1] * STORAGE_PTR_SIZE); | |
_write_storage_ptr(write_location, page, off, size); | |
header[1] += 1; | |
} | |
int32_t database_add_space_block(struct database* db) { | |
int32_t new_block = dbfile_grow(&db->dbf, 1); | |
database_len_init(dbfile_get_page(&db->dbf, new_block)); | |
int32_t space_iter = database_get_spaceroot(db); | |
char* space_page = dbfile_get_page(&db->dbf, space_iter); | |
int32_t* reader = (int32_t*)space_page; | |
while (reader[0] != -1) { | |
space_iter = reader[0]; | |
space_page = dbfile_get_page(&db->dbf, space_iter); | |
reader = (int32_t*)space_page; | |
} | |
reader[0] = new_block; | |
return new_block; | |
} | |
int32_t database_add_hash_block(struct database* db, size_t n_blocks) { | |
int32_t hash_iter = database_get_hashroot(db); | |
char* hash_page = dbfile_get_page(&db->dbf, hash_iter); | |
int32_t* reader = (int32_t*)hash_page; | |
while (reader[0] != -1) { | |
hash_iter = reader[0]; | |
hash_page = dbfile_get_page(&db->dbf, hash_iter); | |
reader = (int32_t*)hash_page; | |
} | |
// now at end of list, begin adding | |
while(n_blocks--) { | |
int32_t new_block = dbfile_grow(&db->dbf, 1); | |
database_hash_init(dbfile_get_page(&db->dbf, new_block)); | |
reader[0] = new_block; | |
hash_iter = reader[0]; | |
hash_page = dbfile_get_page(&db->dbf, hash_iter); | |
reader = (int32_t*)hash_page; | |
} | |
return 1; | |
} | |
// makes a linked list of new hash blocks | |
int32_t database_make_hash_blocks(struct database* db, size_t n_blocks) { | |
int32_t hash_block_first = dbfile_grow(&db->dbf, 1); | |
database_hash_init(dbfile_get_page(&db->dbf, hash_block_first)); | |
int32_t hash_iter = hash_block_first; | |
char* hash_page = dbfile_get_page(&db->dbf, hash_iter); | |
int32_t* reader = (int32_t*)hash_page; | |
// now at end of list, begin adding | |
while(--n_blocks) { | |
int32_t new_block = dbfile_grow(&db->dbf, 1); | |
database_hash_init(dbfile_get_page(&db->dbf, new_block)); | |
reader[0] = new_block; | |
hash_iter = reader[0]; | |
hash_page = dbfile_get_page(&db->dbf, hash_iter); | |
reader = (int32_t*)hash_page; | |
} | |
return hash_block_first; | |
} | |
// The number of blocks to increase by must be at least 1 | |
int32_t database_add_storage_blocks(struct database* db, int32_t size) { | |
int32_t block_count = (size / db->dbf.page_size) + 1; | |
int32_t new_block = dbfile_grow(&db->dbf, block_count); | |
int32_t toadd_to = database_find_space_block(db); | |
char* adding_space = dbfile_get_page(&db->dbf, toadd_to); | |
database_place_ptr_in_len_block(adding_space, new_block, 0, size); | |
return new_block; | |
} | |
int database_allocate_storage(struct database* db, int32_t size, int32_t* result) { | |
int did_inc = 0; | |
while (database_find_space_storage(db, size, result) == -1) { | |
did_inc = 1; | |
printf("added\n"); | |
database_add_storage_blocks(db, size); | |
} | |
return did_inc; | |
} | |
int database_deallocate_storage(struct database* db, const int32_t* result) { | |
if (result[0] <= 0) { | |
return 0; | |
} | |
int32_t toadd_to = -1; | |
toadd_to = database_find_space_block(db); | |
if (toadd_to == -1) { | |
toadd_to = database_add_space_block(db); | |
} | |
char* adding_space = dbfile_get_page(&db->dbf, toadd_to); | |
database_place_ptr_in_len_block(adding_space, result[0], result[1], result[2]); | |
return 1; | |
} | |
int database_compare_key(struct database* db, const char* key, size_t key_size, const int32_t* store_ptr) { | |
return dbfile_cmp_null(&db->dbf, store_ptr[0], store_ptr[1], key, key_size); | |
} | |
int32_t* database_hash_and_probe(struct database* db, const char* key, size_t key_size, | |
int32_t sblock, int32_t* slot, int put) { | |
char* page = dbfile_get_page(&db->dbf, sblock); | |
int32_t* place = _hash_block_at(page, slot == NULL ? 0 : *slot); | |
if (_is_empty_ins_storage_ptr(place)) { | |
return place; | |
} else if(_is_del_storage_ptr(place)) { | |
if (put) { | |
return place; | |
} | |
} else if (database_compare_key(db, key, key_size, place)) { | |
return place; | |
} | |
int32_t* iter = place; | |
int32_t* begin = _hash_block_begin(page); | |
int32_t* end = _hash_block_end(page, db->dbf.page_size); | |
while (iter != end) { | |
if (_is_empty_ins_storage_ptr(iter)) { | |
return iter; | |
} else if(_is_del_storage_ptr(iter)) { | |
if (put) { | |
return iter; | |
} | |
} else if (database_compare_key(db, key, key_size, iter)) { | |
return iter; | |
} | |
iter += HASHSTORAGE_PTR_SIZE_INT; | |
} | |
iter = begin; | |
while (iter != place) { | |
if (_is_empty_ins_storage_ptr(iter)) { | |
return iter; | |
} else if(_is_del_storage_ptr(iter)) { | |
if (put) { | |
return iter; | |
} | |
} else if (database_compare_key(db, key, key_size, iter)) { | |
return iter; | |
} | |
iter += HASHSTORAGE_PTR_SIZE_INT; | |
} | |
return NULL; | |
} | |
/** | |
* This function just compares if its empty or not because we know there cannot be a duplicate | |
* */ | |
int32_t* database_rehash_and_probe(struct database* db, int32_t sblock, int32_t* slot) { | |
char* page = dbfile_get_page(&db->dbf, sblock); | |
int32_t* place = _hash_block_at(page, slot == NULL ? 0 : *slot); | |
if (_is_empty_ins_storage_ptr(place)) { | |
return place; | |
} | |
int32_t* iter = place; | |
int32_t* begin = _hash_block_begin(page); | |
int32_t* end = _hash_block_end(page, db->dbf.page_size); | |
while (iter != end) { | |
if (_is_empty_ins_storage_ptr(iter)) { | |
return iter; | |
} | |
iter += HASHSTORAGE_PTR_SIZE_INT; | |
} | |
iter = begin; | |
while (iter != place) { | |
if (_is_empty_ins_storage_ptr(iter)) { | |
return iter; | |
} | |
iter += HASHSTORAGE_PTR_SIZE_INT; | |
} | |
return NULL; | |
} | |
int32_t database_hashlist_get_n(struct database* db, int32_t hash_list, size_t n) { | |
//printf("hash n %zu, hl %d, pc, %zu\n", n, hash_list, db->dbf.page_count); | |
while (n--) { | |
char* page = dbfile_get_page(&db->dbf, hash_list); | |
int32_t* reader = (int32_t*)page; | |
hash_list = reader[0]; | |
} | |
return hash_list; | |
} | |
int database_rehash_into(struct database* db, const int32_t* store_ptr, int32_t hash_list, size_t hash_size) { | |
if (store_ptr[0] < 1) { | |
return 0; | |
} | |
size_t rehash = dbfile_hash_null(&db->dbf, store_ptr[0], store_ptr[1]); | |
size_t hash_slot = rehash % hash_size; | |
size_t hash_each_block = hashes_per_block(db->dbf.page_size); | |
int32_t hash_place = hash_slot % hash_each_block; | |
int32_t into_block = database_hashlist_get_n(db, hash_list, hash_slot / hash_each_block); | |
int32_t* cur_spot = database_rehash_and_probe(db, into_block, &hash_place); | |
if (cur_spot != NULL) { | |
_write_storage_ptr_hash(cur_spot, store_ptr); | |
return 1; | |
} | |
// iterate over other blocks | |
int32_t list_place = hash_list; | |
while (list_place != -1) { | |
int32_t* list_spot = database_rehash_and_probe(db, list_place, NULL); | |
if (list_spot != NULL) { | |
_write_storage_ptr_hash(list_spot, store_ptr); | |
return 1; | |
} | |
char* list_block = dbfile_get_page(&db->dbf, list_place); | |
int32_t* reader = (int32_t*)list_block; | |
list_place = reader[0]; | |
} | |
return 0; | |
} | |
char* database_adv_to_val(struct database* db, const int32_t* store_ptr, size_t key_size) { | |
int32_t adv_ptr[3]; | |
if (!store_ptr[0]) | |
return NULL; | |
adv_ptr[0] = store_ptr[0] + (key_size / db->dbf.page_size); | |
adv_ptr[1] = store_ptr[1] + (key_size % db->dbf.page_size); | |
adv_ptr[2] = store_ptr[2] - key_size; | |
char* strbuf = malloc(adv_ptr[2]); | |
dbfile_read_po(&db->dbf, adv_ptr[0], adv_ptr[1], strbuf, adv_ptr[2]); | |
return strbuf; | |
} | |
int database_expand(struct database* db, size_t n_blocks) { | |
size_t cur_block_count = database_get_hash_block_count(db); | |
size_t next_count = (cur_block_count + n_blocks); | |
size_t next_len = next_count * hashes_per_block(db->dbf.page_size); | |
int32_t new_hash_lists = database_make_hash_blocks(db, next_count); | |
int32_t old_hash_root = database_get_hashroot(db); | |
int32_t hashiter = old_hash_root; | |
while (hashiter != -1) { | |
char* page = dbfile_get_page(&db->dbf, hashiter); | |
int32_t* begin = _hash_block_begin(page); | |
int32_t* end = _hash_block_end(page, db->dbf.page_size); | |
int32_t* iter = begin; | |
// to do | |
while (iter != end) { | |
database_rehash_into(db, iter, new_hash_lists, next_len); | |
iter += HASHSTORAGE_PTR_SIZE_INT; | |
} | |
hashiter = ((int32_t*)(page))[0]; | |
} | |
// reset the hash list, | |
//database_set_hash_count(db, (int32_t)next_count); | |
database_set_hashroot(db, new_hash_lists); | |
// turn previous hash list into free blocks | |
int32_t old_hash_iter = old_hash_root; | |
while (old_hash_iter != -1) { | |
int32_t to_free = old_hash_iter; | |
char* page = dbfile_get_page(&db->dbf, old_hash_iter); | |
old_hash_iter = ((int32_t*)(page))[0]; | |
int32_t freed_storage[3] = {to_free, 0, db->dbf.page_size}; | |
database_deallocate_storage(db, freed_storage); | |
} | |
// now recompute the length | |
database_recomp_hash_len(db); | |
return 1; | |
} | |
int database_put(struct database* db, const char* key, const char* val) { | |
int32_t storage_place[3]; | |
size_t key_hash = hash_djb2(key); | |
size_t key_size = strlen(key) + 1; | |
size_t val_size = strlen(val) + 1; | |
size_t total_size = key_size + val_size; | |
char* buff = calloc(1, total_size); | |
memcpy(buff, key, key_size); | |
memcpy(buff + key_size, val, val_size); | |
database_allocate_storage(db, total_size, storage_place); | |
dbfile_write_po(&db->dbf, storage_place[0], storage_place[1], buff, total_size); | |
free(buff); | |
size_t hash_size = database_get_hash_len(db); | |
size_t hash_slot = key_hash % hash_size; | |
size_t hash_each_block = hashes_per_block(db->dbf.page_size); | |
int32_t hash_place = hash_slot % hash_each_block; | |
int32_t into_block = database_get_hash_block(db, hash_slot / hash_each_block); | |
int32_t* found = database_hash_and_probe(db, key, key_size, into_block, &hash_place, 1); | |
if(found != NULL) { | |
database_deallocate_storage(db, found); | |
_write_storage_ptr_hash(found, storage_place); | |
database_inc_item_count(db, 1); | |
return 1; | |
} | |
int32_t block_iter = database_get_hashroot(db); | |
while (block_iter != -1) { | |
found = database_hash_and_probe(db, key, key_size, into_block, NULL, 1); | |
if(found != NULL) { | |
database_deallocate_storage(db, found); | |
_write_storage_ptr_hash(found, storage_place); | |
database_inc_item_count(db, 1); | |
return 1; | |
} | |
char* hash_page = dbfile_get_page(&db->dbf, block_iter); | |
int32_t* header = (int32_t*)hash_page; | |
block_iter = header[0]; | |
} | |
return 0; | |
} | |
char* database_get(struct database* db, const char* key) { | |
size_t key_hash = hash_djb2(key); | |
size_t key_size = strlen(key) + 1; | |
size_t hash_size = database_get_hash_len(db); | |
size_t hash_slot = key_hash % hash_size; | |
size_t hash_each_block = hashes_per_block(db->dbf.page_size); | |
int32_t hash_place = hash_slot % hash_each_block; | |
int32_t into_block = database_get_hash_block(db, hash_slot / hash_each_block); | |
int32_t* found = database_hash_and_probe(db, key, key_size, into_block, &hash_place, 0); | |
if(found != NULL) { | |
return database_adv_to_val(db, found, key_size); | |
} | |
int32_t block_iter = database_get_hashroot(db); | |
while (block_iter != -1) { | |
found = database_hash_and_probe(db, key, key_size, into_block, NULL, 0); | |
if(found != NULL) { | |
return database_adv_to_val(db, found, key_size); | |
} | |
char* hash_page = dbfile_get_page(&db->dbf, block_iter); | |
int32_t* header = (int32_t*)hash_page; | |
block_iter = header[0]; | |
} | |
return NULL; | |
} | |
int database_del(struct database* db, const char* key) { | |
size_t key_hash = hash_djb2(key); | |
size_t key_size = strlen(key) + 1; | |
size_t hash_size = database_get_hash_len(db); | |
size_t hash_slot = key_hash % hash_size; | |
size_t hash_each_block = hashes_per_block(db->dbf.page_size); | |
int32_t hash_place = hash_slot % hash_each_block; | |
int32_t into_block = database_get_hash_block(db, hash_slot / hash_each_block); | |
int32_t* found = database_hash_and_probe(db, key, key_size, into_block, &hash_place, 0); | |
if(found != NULL) { | |
database_deallocate_storage(db, found); | |
_mark_del_storage_ptr(found); | |
database_dec_item_count(db, 1); | |
return 1; | |
} | |
int32_t block_iter = database_get_hashroot(db); | |
while (block_iter != -1) { | |
found = database_hash_and_probe(db, key, key_size, into_block, NULL, 0); | |
if(found != NULL) { | |
database_deallocate_storage(db, found); | |
_mark_del_storage_ptr(found); | |
database_dec_item_count(db, 1); | |
return 1; | |
} | |
char* hash_page = dbfile_get_page(&db->dbf, block_iter); | |
int32_t* header = (int32_t*)hash_page; | |
block_iter = header[0]; | |
} | |
return 0; | |
} | |
void database_init(struct database* db) { | |
int32_t roots[2]; | |
int32_t hash_len = 1; | |
int64_t item_count = 0; | |
struct dbfile* dbf = &db->dbf; | |
char* header = dbfile_get_page(dbf, 0); | |
header[0] = MAGIC_SEQ[0]; | |
header[1] = MAGIC_SEQ[1]; | |
header[2] = MAGIC_SEQ[2]; | |
header[3] = MAGIC_SEQ[3]; | |
header += sizeof(MAGIC_SEQ); | |
int32_t hash_root = dbfile_grow(dbf, 1); | |
int32_t space_root = dbfile_grow(dbf, 1); | |
roots[0] = hash_root; | |
roots[1] = space_root; | |
memcpy(header, roots, sizeof(roots)); | |
header += sizeof(roots); | |
int32_t page_size = dbf->page_size; | |
memcpy(header, &page_size, sizeof(page_size)); | |
header += sizeof(page_size); | |
memcpy(header, &hash_len, sizeof(hash_len)); | |
header += sizeof(hash_len); | |
memcpy(header, &item_count, sizeof(item_count)); // used for load factor | |
// init roots | |
char* hash_page = dbfile_get_page(dbf, hash_root); | |
char* space_page = dbfile_get_page(dbf, space_root); | |
database_hash_init(hash_page); | |
database_len_init(space_page); | |
database_add_storage_blocks(db, page_size); // todo beginning allocation strategy | |
} | |
int database_open(struct database* db, const char* pathfile, struct dbcfg* cfg) { | |
if (!dbfile_open(&db->dbf, pathfile, cfg)) | |
return 0; | |
char* header = dbfile_get_page(&db->dbf, 0); | |
if (!_has_magic_seq(header)) { | |
database_init(db); | |
} | |
db->dbf.page_size = database_get_page_size(db); | |
return 1; | |
} | |
void database_close(struct database* db) { | |
dbfile_close(&db->dbf); | |
dbfile_path_free(&db->dbf); | |
} | |
void database_close_and_remove(struct database* db) { | |
dbfile_close(&db->dbf); | |
dbfile_remove(&db->dbf); | |
dbfile_path_free(&db->dbf); | |
} | |
//------- tests --------- | |
static void check_cond(int cond, const char* condstr, unsigned line) { | |
if (!cond) { | |
fprintf(stderr, "Failed cond '%s' at line %u\n", condstr, line); | |
} | |
} | |
#define CHECKIT(cnd) check_cond(cnd, #cnd, __LINE__) | |
static void test_djb2_n(void) { | |
const char* test1 = "foobarfoobar"; | |
size_t hash1 = hash_djb2(test1); | |
size_t base = DJB2_HASH_BASE; | |
hash_djb2_n(test1, 6, &base); | |
hash_djb2_n(test1 + 6, 6, &base); | |
CHECKIT(hash1 == base); | |
} | |
static void test_dbfile_rw(void) { | |
struct dbfile foo; | |
dbfile_open(&foo, "boof", NULL); | |
unsigned char buf[3000]; | |
memset(buf, 255, sizeof(buf)); | |
dbfile_write(&foo, foo.page_size - 1000, (char*)buf, sizeof(buf)); | |
memset(buf, 0, sizeof(buf)); | |
dbfile_read(&foo, foo.page_size - 1000, (char*)buf, sizeof(buf)); | |
for (int i = 0; i < sizeof(buf); ++i) | |
{ | |
CHECKIT(buf[i] == 255); | |
} | |
dbfile_close(&foo); | |
dbfile_remove(&foo); | |
dbfile_path_free(&foo); | |
} | |
static void test_dbfile_cmp(void) { | |
struct dbfile foo; | |
const char* str1 = "abcd"; | |
const char* str2 = "efgh"; | |
size_t str1_size = strlen(str1) + 1; | |
size_t str2_size = strlen(str2) + 1; | |
dbfile_open(&foo, "boof", NULL); | |
dbfile_write(&foo, 4, str1, str1_size); | |
dbfile_write(&foo, 4 + str1_size, str2, str2_size); | |
CHECKIT(dbfile_cmp_null(&foo, 0, 4, "abcd", 5) == 1); | |
CHECKIT(dbfile_cmp_null(&foo, 0, 4, "abcd", 8) == 1); // null stops | |
CHECKIT(dbfile_cmp_null(&foo, 0, 4, "efgh", 5) == 0); | |
CHECKIT(dbfile_cmp_null(&foo, 0, 4 + str1_size, "efgh", 5) == 1); | |
// different size compare | |
CHECKIT(dbfile_cmp_null(&foo, 0, 4, "abcde", 6) == 0); | |
CHECKIT(dbfile_cmp_null(&foo, 0, 4, "abc", 4) == 0); | |
unsigned char* big_str = calloc(1, 4000); | |
memset(big_str, 'd', 3999); | |
dbfile_write(&foo, foo.page_size - 1000, (char*)big_str, 4000); | |
CHECKIT(dbfile_cmp_null(&foo, 0, foo.page_size - 1000, (char*)big_str, 4000) == 1); | |
free(big_str); | |
dbfile_close(&foo); | |
dbfile_remove(&foo); | |
dbfile_path_free(&foo); | |
} | |
static void test_dbfile_hash_null(void) { | |
struct dbfile foo; | |
const char* str1 = "abcddd"; | |
const char* str2 = "efgh"; | |
size_t str1_size = strlen(str1) + 1; | |
size_t str2_size = strlen(str2) + 1; | |
size_t str1_hash = hash_djb2(str1); | |
size_t str2_hash = hash_djb2(str2); | |
dbfile_open(&foo, "boof", NULL); | |
dbfile_write(&foo, 4, str1, str1_size); | |
dbfile_write(&foo, 4 + str1_size, str2, str2_size); | |
CHECKIT(dbfile_hash_null(&foo, 0, 4) == str1_hash); | |
CHECKIT(dbfile_hash_null(&foo, 0, 4 + str1_size) == str2_hash); | |
dbfile_close(&foo); | |
dbfile_remove(&foo); | |
dbfile_path_free(&foo); | |
} | |
static void test_dbfile_grow(void) { | |
struct dbfile foo; | |
dbfile_open(&foo, "boof", NULL); | |
CHECKIT(foo.page_count == 1); | |
int32_t new_block = dbfile_grow(&foo, 1); | |
CHECKIT(foo.page_count == 2); | |
CHECKIT(new_block == (foo.page_count - 1)); | |
dbfile_close(&foo); | |
dbfile_remove(&foo); | |
dbfile_path_free(&foo); | |
} | |
static void test_database_open_close(void) { | |
struct database db; | |
CHECKIT(database_open(&db, "boof", NULL)); | |
CHECKIT(database_get_hashroot(&db) != -1); | |
CHECKIT(database_get_spaceroot(&db) != -1); | |
CHECKIT(database_get_hash_len(&db) == (1 * hashes_per_block(db.dbf.page_size))); | |
database_close_and_remove(&db); | |
} | |
static void test_database_add_space(void) { | |
struct database db; | |
CHECKIT(database_open(&db, "boof", NULL)); | |
CHECKIT(database_get_hashroot(&db) != -1); | |
CHECKIT(database_get_spaceroot(&db) != -1); | |
CHECKIT(database_get_space_block_count(&db) == 1); | |
CHECKIT(database_add_space_block(&db) != -1); | |
CHECKIT(database_get_space_block_count(&db) == 2); | |
CHECKIT(database_add_space_block(&db) != -1); | |
CHECKIT(database_get_space_block_count(&db) == 3); | |
database_close_and_remove(&db); | |
} | |
static void test_database_find_space_ptr(void) { | |
int32_t result[3]; | |
size_t page_size = 4096; | |
char* page = calloc(1, page_size); | |
int32_t* writer = (int32_t*)page; | |
writer[0] = -1; | |
writer[1] = 1; | |
writer[2] = 66; | |
writer[3] = 102; | |
writer[4] = 200; // size | |
CHECKIT(database_find_space_ptr(page, 32, page_size, result) != -1); | |
CHECKIT(result[0] == 66); | |
CHECKIT(result[1] == 102); | |
CHECKIT(result[2] == 32); | |
CHECKIT(writer[4] == (200-32)); | |
free(page); | |
} | |
static void test_database_find_space_ptr_zero(void) { | |
int32_t result[3]; | |
size_t page_size = 4096; | |
char* page = calloc(1, page_size); | |
int32_t* writer = (int32_t*)page; | |
writer[0] = -1; | |
writer[1] = 1; | |
writer[2] = 66; | |
writer[3] = 102; | |
writer[4] = 32; // size | |
writer[5] = 201; // extra byte | |
CHECKIT(database_find_space_ptr(page, 32, page_size, result) != -1); | |
CHECKIT(result[0] == 66); | |
CHECKIT(result[1] == 102); | |
CHECKIT(result[2] == 32); | |
CHECKIT(writer[1] == 0); // todo len function | |
CHECKIT(writer[2] == 201); // extra byte moved | |
free(page); | |
} | |
static void test_database_allocate(void) { | |
struct database db; | |
int32_t result1[3]; | |
int32_t result2[3]; | |
CHECKIT(database_open(&db, "boof", NULL)); | |
CHECKIT(database_get_hashroot(&db) != -1); | |
CHECKIT(database_get_spaceroot(&db) != -1); | |
CHECKIT(database_allocate_storage(&db, 32, result1) == 0); | |
CHECKIT(database_allocate_storage(&db, 32, result2) == 0); | |
CHECKIT(result1[0] == result2[0]); | |
CHECKIT(result1[1] != result2[1]); | |
CHECKIT(result1[2] == result2[2]); | |
CHECKIT(result1[2] == 32); | |
database_close_and_remove(&db); | |
} | |
static void test_database_allocate_large(void) { | |
struct database db; | |
int32_t result1[3]; | |
int32_t result2[3]; | |
CHECKIT(database_open(&db, "boof", NULL)); | |
size_t page_size = db.dbf.page_size; | |
CHECKIT(database_get_hashroot(&db) != -1); | |
CHECKIT(database_get_spaceroot(&db) != -1); | |
CHECKIT(database_allocate_storage(&db, 32, result1) == 0); | |
CHECKIT(database_allocate_storage(&db, page_size * 2, result2) == 1); | |
CHECKIT(result1[0] != result2[0]); | |
CHECKIT(result1[1] == result2[1]); // both will begin in a new block | |
CHECKIT(result1[2] != result2[2]); | |
CHECKIT(result1[2] == 32); | |
database_close_and_remove(&db); | |
} | |
static void test_database_deallocate(void) { | |
struct database db; | |
int32_t result1[3]; | |
int32_t result2[3]; | |
CHECKIT(database_open(&db, "boof", NULL)); | |
int32_t space = database_get_spaceroot(&db); | |
char* space_page = dbfile_get_page(&db.dbf, space); | |
CHECKIT(space != -1); | |
CHECKIT(database_allocate_storage(&db, 32, result1) == 0); | |
CHECKIT(database_allocate_storage(&db, 32, result2) == 0); | |
int32_t before_len = database_len_get(space_page); // todo length function | |
database_deallocate_storage(&db, result1); | |
CHECKIT(database_len_get(space_page) == before_len + 1); | |
database_deallocate_storage(&db, result2); | |
CHECKIT(database_len_get(space_page) == before_len + 2); | |
database_close_and_remove(&db); | |
} | |
static void test_database_add_hash_block(void) { | |
struct database db; | |
CHECKIT(database_open(&db, "boof", NULL)); | |
CHECKIT(database_get_hashroot(&db) != -1); | |
CHECKIT(database_get_spaceroot(&db) != -1); | |
size_t hash_len1 = database_get_hash_block_count(&db); | |
database_add_hash_block(&db, 1); | |
database_recomp_hash_len(&db); | |
size_t hash_len2 = database_get_hash_block_count(&db); | |
CHECKIT(hash_len1 == (hash_len2 - 1)); | |
database_add_hash_block(&db, 4); | |
size_t hash_len3 = database_get_hash_block_count(&db); | |
CHECKIT(hash_len2 == (hash_len3 - 4)); | |
database_close_and_remove(&db); | |
} | |
static void test_database_key_cmp(void) { | |
struct database db; | |
int32_t result1[3]; | |
const char* keystr = "abcdef"; | |
size_t keystr_size = strlen(keystr) + 1; | |
CHECKIT(database_open(&db, "boof", NULL)); | |
CHECKIT(database_allocate_storage(&db, keystr_size, result1) == 0); | |
dbfile_write_po(&db.dbf, result1[0], result1[1], keystr, keystr_size); | |
CHECKIT(database_compare_key(&db, keystr, keystr_size, result1)); | |
database_close_and_remove(&db); | |
} | |
static void test_database_hash_and_probe(void) { | |
struct database db; | |
int32_t store_ptr1[3]; | |
int32_t store_ptr2[3]; | |
const char* keystr1 = "abcdef"; | |
const char* keystr2 = "abcdefg"; | |
size_t keystr1_size = strlen(keystr1) + 1; | |
size_t keystr2_size = strlen(keystr2) + 1; | |
CHECKIT(database_open(&db, "boof", NULL)); | |
CHECKIT(database_allocate_storage(&db, keystr1_size, store_ptr1) == 0); | |
CHECKIT(database_allocate_storage(&db, keystr2_size, store_ptr2) == 0); | |
dbfile_write_po(&db.dbf, store_ptr1[0], store_ptr1[1], keystr1, keystr1_size); | |
dbfile_write_po(&db.dbf, store_ptr2[0], store_ptr2[1], keystr2, keystr2_size); | |
int32_t hashroot = database_get_hashroot(&db); | |
int32_t* found1 = database_hash_and_probe(&db, keystr1, keystr1_size, hashroot, NULL, 1); | |
CHECKIT(found1 != NULL); | |
_write_storage_ptr_hash(found1, store_ptr1); | |
int32_t* found2 = database_hash_and_probe(&db, keystr2, keystr2_size, hashroot, NULL, 1); | |
CHECKIT(found2 != NULL); | |
CHECKIT(found1 != found2); | |
_write_storage_ptr_hash(found2, store_ptr2); | |
char* hashpage = dbfile_get_page(&db.dbf, hashroot) + HASH_BLOCK_HEADER_SIZE; | |
int32_t* reader = (int32_t*)hashpage; | |
CHECKIT(reader[0] == store_ptr1[0]); | |
CHECKIT(reader[1] == store_ptr1[1]); | |
CHECKIT(reader[2] == store_ptr1[2]); | |
hashpage += HASHSTORAGE_PTR_SIZE; | |
reader = (int32_t*)hashpage; | |
CHECKIT(reader[0] == store_ptr2[0]); | |
CHECKIT(reader[1] == store_ptr2[1]); | |
CHECKIT(reader[2] == store_ptr2[2]); | |
database_close_and_remove(&db); | |
} | |
static void test_database_put_get_del(void) { | |
struct database db; | |
const char* key1 = "abcdef"; | |
const char* key2 = "abc5ef"; | |
const char* val1 = "abcdefg"; | |
const char* val2 = "bbbbbb"; | |
char* key1res = NULL; | |
char* key2res = NULL; | |
CHECKIT(database_open(&db, "boof", NULL)); | |
CHECKIT(database_put(&db, key1, val1)); | |
key1res = database_get(&db, key1); | |
CHECKIT(strcmp(key1res, val1) == 0); | |
CHECKIT(database_put(&db, key1, val2)); | |
free(key1res); | |
key1res = database_get(&db, key1); | |
CHECKIT(strcmp(key1res, val2) == 0); | |
CHECKIT(database_put(&db, key2, val2)); | |
key2res = database_get(&db, key2); | |
CHECKIT(strcmp(key2res, key1res) == 0); | |
CHECKIT(database_del(&db, key1)); | |
CHECKIT(database_del(&db, key2)); | |
CHECKIT(database_get(&db, key1) == NULL); | |
CHECKIT(database_get(&db, key2) == NULL); | |
free(key1res); | |
free(key2res); | |
database_close_and_remove(&db); | |
} | |
static void test_database_load_factor(void) { | |
struct database db; | |
const char* key1 = "abcdef"; | |
const char* key2 = "abc5ef"; | |
const char* val1 = "abcdefg"; | |
const char* val2 = "bbbbbb"; | |
CHECKIT(database_open(&db, "boof", NULL)); | |
double lf1 = database_get_load_factor(&db); | |
CHECKIT(database_put(&db, key1, val1)); | |
double lf2 = database_get_load_factor(&db); | |
CHECKIT(database_put(&db, key2, val2)); | |
double lf3 = database_get_load_factor(&db); | |
CHECKIT(lf2 > lf1); | |
CHECKIT(lf3 > lf2); | |
database_close_and_remove(&db); | |
} | |
static void test_database_expand(void) { | |
struct database db; | |
const char* key1 = "abcdef"; | |
const char* key2 = "abc5ef"; | |
const char* key3 = "foobar"; | |
const char* val1 = "abcdefg"; | |
const char* val2 = "bbbbbb"; | |
const char* val3 = "bbbcbbb"; | |
char* key1res = NULL; | |
char* key2res = NULL; | |
char* key3res = NULL; | |
CHECKIT(database_open(&db, "boof", NULL)); | |
int32_t hashroot1 = database_get_hashroot(&db); | |
CHECKIT(database_put(&db, key1, val1)); | |
CHECKIT(database_put(&db, key2, val2)); | |
key1res = database_get(&db, key1); | |
CHECKIT(strcmp(key1res, val1) == 0); | |
free(key1res); | |
key2res = database_get(&db, key2); | |
CHECKIT(strcmp(key2res, val2) == 0); | |
free(key2res); | |
database_expand(&db, 1); | |
CHECKIT(database_put(&db, key3, val3)); | |
int32_t hashroot2 = database_get_hashroot(&db); | |
CHECKIT(hashroot1 != hashroot2); | |
key1res = database_get(&db, key1); | |
key2res = database_get(&db, key2); | |
CHECKIT(strcmp(key1res, val1) == 0); | |
CHECKIT(strcmp(key2res, val2) == 0); | |
free(key1res); | |
free(key2res); | |
database_expand(&db, 2); | |
key1res = database_get(&db, key1); | |
key2res = database_get(&db, key2); | |
key3res = database_get(&db, key3); | |
CHECKIT(strcmp(key1res, val1) == 0); | |
CHECKIT(strcmp(key2res, val2) == 0); | |
CHECKIT(strcmp(key3res, val3) == 0); | |
free(key1res); | |
free(key2res); | |
free(key3res); | |
database_close_and_remove(&db); | |
} | |
int main(int argc, char const *argv[]) | |
{ | |
test_djb2_n(); | |
test_dbfile_rw(); | |
test_dbfile_cmp(); | |
test_dbfile_hash_null(); | |
test_dbfile_grow(); | |
test_database_open_close(); | |
test_database_add_hash_block(); | |
test_database_add_space(); | |
test_database_find_space_ptr(); | |
test_database_find_space_ptr_zero(); | |
test_database_allocate(); | |
test_database_allocate_large(); | |
test_database_key_cmp(); | |
test_database_deallocate(); | |
test_database_hash_and_probe(); | |
test_database_put_get_del(); | |
test_database_load_factor(); | |
test_database_expand(); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment