From 3454fb76180d83d1cb47d257dd8e8509bfba1121 Mon Sep 17 00:00:00 2001 From: aloima Date: Sun, 13 Oct 2024 15:47:42 +0300 Subject: [PATCH] feat(hashtable): add deleting hash table fix(commands): invalid writing "unknown command" fix(commands): invalid argument count checking for HSET command feat(hashtable): change grow_hashtable method as resize_hashtable method feat(commands): add HDEL command chore: remove unused headers from some files chore(hashtable): move add_fv_to_hashtable to memory file perf(hashtable): remove strlen method from add_fv_to_hashtable method --- headers/commands.h | 3 +- headers/hashtable.h | 9 ++- src/commands/hashtable/hdel.c | 59 ++++++++++++++++++++ src/commands/hashtable/hset.c | 15 ++--- src/hashtable/grow.c | 88 ----------------------------- src/hashtable/hashtable.c | 29 +++++++++- src/hashtable/memory.c | 102 ++++++++++++++++++++++++++++++++++ src/server/commands.c | 36 ++++++------ src/server/transactions.c | 1 - 9 files changed, 222 insertions(+), 120 deletions(-) create mode 100644 src/commands/hashtable/hdel.c delete mode 100644 src/hashtable/grow.c create mode 100644 src/hashtable/memory.c diff --git a/headers/commands.h b/headers/commands.h index f55512f..8113301 100644 --- a/headers/commands.h +++ b/headers/commands.h @@ -4,8 +4,6 @@ #include -#include - #define WRONG_ARGUMENT_ERROR(client, name, len) (_write((client), "-Wrong argument count for '" name "' command\r\n", 38 + (len))) struct Subcommand { @@ -52,6 +50,7 @@ extern struct Command cmd_time; /* /GENERIC COMMANDS */ /* HASHTABLE COMMANDS */ +extern struct Command cmd_hdel; extern struct Command cmd_hget; extern struct Command cmd_hlen; extern struct Command cmd_hset; diff --git a/headers/hashtable.h b/headers/hashtable.h index 4545c9a..41d19e6 100644 --- a/headers/hashtable.h +++ b/headers/hashtable.h @@ -1,5 +1,6 @@ #pragma once +#include "telly.h" #include "utils.h" #include @@ -9,6 +10,7 @@ struct FVPair { value_t value; enum TellyTypes type; struct FVPair *next; + uint64_t hash; }; struct HashTableSize { @@ -21,14 +23,17 @@ struct HashTable { struct FVPair **fvs; struct HashTableSize size; double grow_factor; + double shrink_factor; }; uint64_t hash(char *key); -struct HashTable *create_hashtable(uint32_t default_size, double grow_factor); +struct HashTable *create_hashtable(uint32_t default_size, const double grow_factor, const double shrink_factor); +void resize_hashtable(struct HashTable *table, const uint32_t size); struct FVPair *get_fv_from_hashtable(struct HashTable *table, char *name); void free_hashtable(struct HashTable *table); void set_fv_value(struct FVPair *fv, void *value); void free_fv(struct FVPair *fv); -void add_fv_to_hashtable(struct HashTable *table, char *name, void *value, enum TellyTypes type); +void add_fv_to_hashtable(struct HashTable *table, const string_t name, void *value, const enum TellyTypes type); +bool del_fv_to_hashtable(struct HashTable *table, const string_t name); diff --git a/src/commands/hashtable/hdel.c b/src/commands/hashtable/hdel.c new file mode 100644 index 0000000..4e8f148 --- /dev/null +++ b/src/commands/hashtable/hdel.c @@ -0,0 +1,59 @@ +#include "../../../headers/telly.h" +#include "../../../headers/server.h" +#include "../../../headers/database.h" +#include "../../../headers/commands.h" +#include "../../../headers/hashtable.h" + +#include +#include +#include +#include + +static void run(struct Client *client, respdata_t *data) { + if (data->count < 3) { + if (client) WRONG_ARGUMENT_ERROR(client, "HDEL", 4); + return; + } + + const string_t key = data->value.array[1]->value.string; + struct KVPair *kv = get_data(key.value); + struct HashTable *table; + + if (kv) { + if (kv->type == TELLY_HASHTABLE) { + table = kv->value->hashtable; + } else if (client) { + _write(client, "-Invalid type for 'HDEL' command\r\n", 34); + return; + } + } else if (client) { + _write(client, ":0\r\n", 4); + return; + } + + uint32_t deleted = 0; + + for (uint32_t i = 1; i < data->count; ++i) { + const string_t name = data->value.array[i]->value.string; + + if (del_fv_to_hashtable(table, name)) deleted += 1; + } + + if (client) { + const uint32_t buf_len = 3 + get_digit_count(deleted); + char buf[buf_len + 1]; + sprintf(buf, ":%d\r\n", deleted); + + _write(client, buf, buf_len); + } +} + +struct Command cmd_hdel = { + .name = "HDEL", + .summary = "Deletes field(s) of the hash table for the key. If hash table does not exist, creates it.", + .since = "0.1.5", + .complexity = "O(N) where N is field name count", + .subcommands = NULL, + .subcommand_count = 0, + .run = run +}; diff --git a/src/commands/hashtable/hset.c b/src/commands/hashtable/hset.c index 1f2f726..90ef913 100644 --- a/src/commands/hashtable/hset.c +++ b/src/commands/hashtable/hset.c @@ -10,8 +10,8 @@ #include static void run(struct Client *client, respdata_t *data) { - if (client && (data->count == 2 || data->count % 2 != 0)) { - WRONG_ARGUMENT_ERROR(client, "HSET", 4); + if (data->count == 2 || data->count % 2 != 0) { + if (client) WRONG_ARGUMENT_ERROR(client, "HSET", 4); return; } @@ -22,16 +22,17 @@ static void run(struct Client *client, respdata_t *data) { if (kv && kv->type == TELLY_HASHTABLE) { table = kv->value->hashtable; } else { - table = create_hashtable(16, 0.5); + table = create_hashtable(16, 0.5, 0.75); + set_data(kv, key, (value_t) { .hashtable = table }, TELLY_HASHTABLE); } - const uint64_t fv_count = (data->count / 2) - 1; + const uint32_t fv_count = (data->count / 2) - 1; for (uint32_t i = 1; i <= fv_count; ++i) { - char *name = data->value.array[i * 2]->value.string.value; + const string_t name = data->value.array[i * 2]->value.string; char *value = data->value.array[i * 2 + 1]->value.string.value; bool is_true = streq(value, "true"); @@ -51,7 +52,7 @@ static void run(struct Client *client, respdata_t *data) { if (client) { const uint32_t buf_len = 3 + get_digit_count(fv_count); char buf[buf_len + 1]; - sprintf(buf, ":%ld\r\n", fv_count); + sprintf(buf, ":%d\r\n", fv_count); _write(client, buf, buf_len); } @@ -61,7 +62,7 @@ struct Command cmd_hset = { .name = "HSET", .summary = "Sets field(s) of the hash table for the key. If hash table does not exist, creates it.", .since = "0.1.3", - .complexity = "O(1)", + .complexity = "O(N) where N is field name-value pair count", .subcommands = NULL, .subcommand_count = 0, .run = run diff --git a/src/hashtable/grow.c b/src/hashtable/grow.c deleted file mode 100644 index 6103a6e..0000000 --- a/src/hashtable/grow.c +++ /dev/null @@ -1,88 +0,0 @@ -#include "../../headers/hashtable.h" -#include "../../headers/utils.h" - -#include -#include -#include -#include - -void grow_hashtable(struct HashTable *table) { - const uint32_t allocated_size = (table->size.allocated * (1 + table->grow_factor)); - struct FVPair **fvs = calloc(allocated_size, sizeof(struct FVPair *)); - table->size.filled = 0; - - for (uint32_t i = 0; i < table->size.allocated; ++i) { - struct FVPair *fv = table->fvs[i]; - - while (fv) { - struct FVPair *next = fv->next; - fv->next = NULL; - const uint32_t index = hash(fv->name.value) % allocated_size; - struct FVPair **area = &fvs[index]; - - if (!*area) table->size.filled += 1; - while (*area) area = &(*area)->next; - - *area = fv; - fv = next; - } - } - - free(table->fvs); - table->size.allocated = allocated_size; - table->fvs = fvs; -} - -void add_fv_to_hashtable(struct HashTable *table, char *name, void *value, enum TellyTypes type) { - if (table->size.filled == table->size.allocated) grow_hashtable(table); - - const uint32_t index = hash(name) % table->size.allocated; - table->size.all += 1; - table->size.filled += 1; - - struct FVPair *fv; - bool found = false; - - if ((fv = table->fvs[index])) { - do { - if (streq(fv->name.value, name)) { - found = true; - break; - } else if (fv->next) fv = fv->next; - else break; - } while (fv); - } - - if (fv) { - if (found) { - if (fv->type == TELLY_STR && type != TELLY_STR) free(fv->value.string.value); - fv->type = type; - set_fv_value(fv, value); - } else { - fv->next = malloc(sizeof(struct FVPair)); - fv->next->type = type; - fv->next->next = NULL; - - const uint32_t len = strlen(name); - const uint32_t size = len + 1; - fv->next->name.len = len; - fv->next->name.value = malloc(size); - memcpy(fv->next->name.value, name, size); - - set_fv_value(fv->next, value); - } - } else { - fv = malloc(sizeof(struct FVPair)); - fv->type = type; - fv->next = NULL; - - const uint32_t len = strlen(name); - const uint32_t size = len + 1; - fv->name.len = len; - fv->name.value = malloc(size); - memcpy(fv->name.value, name, size); - set_fv_value(fv, value); - - table->fvs[index] = fv; - } -} diff --git a/src/hashtable/hashtable.c b/src/hashtable/hashtable.c index 79c67f9..7b82548 100644 --- a/src/hashtable/hashtable.c +++ b/src/hashtable/hashtable.c @@ -12,17 +12,44 @@ uint64_t hash(char *key) { return hash; } -struct HashTable *create_hashtable(const uint32_t default_size, const double grow_factor) { +struct HashTable *create_hashtable(const uint32_t default_size, const double grow_factor, const double shrink_factor) { struct HashTable *table = malloc(sizeof(struct HashTable)); table->fvs = calloc(default_size, sizeof(struct FVPair *)); table->size.allocated = default_size; table->size.all = 0; table->size.filled = 0; table->grow_factor = grow_factor; + table->shrink_factor = shrink_factor; return table; } +void resize_hashtable(struct HashTable *table, const uint32_t size) { + struct FVPair **fvs = calloc(size, sizeof(struct FVPair *)); + table->size.filled = 0; + + for (uint32_t i = 0; i < table->size.allocated; ++i) { + struct FVPair *fv = table->fvs[i]; + + while (fv) { + struct FVPair *next = fv->next; + fv->next = NULL; + const uint32_t index = fv->hash % size; + struct FVPair **area = &fvs[index]; + + if (!*area) table->size.filled += 1; + while (*area) area = &(*area)->next; + + *area = fv; + fv = next; + } + } + + free(table->fvs); + table->size.allocated = size; + table->fvs = fvs; +} + struct FVPair *get_fv_from_hashtable(struct HashTable *table, char *name) { const uint32_t index = hash(name) % table->size.allocated; struct FVPair *fv = table->fvs[index]; diff --git a/src/hashtable/memory.c b/src/hashtable/memory.c new file mode 100644 index 0000000..7999b1e --- /dev/null +++ b/src/hashtable/memory.c @@ -0,0 +1,102 @@ +#include "../../headers/hashtable.h" +#include "../../headers/utils.h" + +#include +#include +#include +#include + +void add_fv_to_hashtable(struct HashTable *table, const string_t name, void *value, const enum TellyTypes type) { + const uint64_t hashed = hash(name.value); + uint32_t index = hashed % table->size.allocated; + + table->size.all += 1; + + struct FVPair *fv; + bool found = false; + + if ((fv = table->fvs[index])) { + do { + if (streq(fv->name.value, name.value)) { + found = true; + break; + } else if (fv->next) fv = fv->next; + else break; + } while (fv); + } else { + table->size.filled += 1; + } + + if (table->size.allocated == table->size.filled) { + resize_hashtable(table, (table->size.allocated * (1 + table->grow_factor))); + index = hashed % table->size.allocated; + + if (!table->fvs[index]) table->size.filled += 1; + } + + if (fv) { + if (found) { + if (fv->type == TELLY_STR && type != TELLY_STR) free(fv->value.string.value); + fv->type = type; + set_fv_value(fv, value); + } else { + fv->next = malloc(sizeof(struct FVPair)); + fv = fv->next; + } + } else { + fv = malloc(sizeof(struct FVPair)); + table->fvs[index] = fv; + } + + fv->type = type; + fv->hash = hashed; + fv->next = NULL; + + const uint32_t size = name.len + 1; + fv->name.len = name.len; + fv->name.value = malloc(size); + memcpy(fv->name.value, name.value, size); + + set_fv_value(fv, value); +} + +bool del_fv_to_hashtable(struct HashTable *table, const string_t name) { + const uint64_t hashed = hash(name.value); + uint32_t index = hashed % table->size.allocated; + + struct FVPair *fv; + struct FVPair *prev = NULL; + + if ((fv = table->fvs[index])) { + do { + if (streq(fv->name.value, name.value)) { + // b is element will be deleted + if (prev) { // a b a or a a b + prev->next = fv->next; + } else if (!fv->next) { // b or ~~a a b~~ + table->size.filled -= 1; + table->fvs[index] = NULL; + + const uint32_t size = (table->size.allocated * table->shrink_factor); + + if (size == table->size.filled) { + resize_hashtable(table, size); + } + } else { // b a a + table->fvs[index] = fv->next; + } + + table->size.all -= 1; + free_fv(fv); + return true; + } else if (fv->next) { + prev = fv; + fv = fv->next; + } else { + return false; + } + } while (fv); + } + + return false; +} diff --git a/src/server/commands.c b/src/server/commands.c index 222edb0..5b8305f 100644 --- a/src/server/commands.c +++ b/src/server/commands.c @@ -8,19 +8,18 @@ #include #include -#include -#include - static struct Command *commands = NULL; -static uint32_t command_count = 22; +static uint32_t command_count = 23; void load_commands() { struct Command _commands[] = { - // Hashtable commands - cmd_hget, - cmd_hlen, - cmd_hset, - cmd_htype, + // Data commands + cmd_decr, + cmd_exists, + cmd_get, + cmd_incr, + cmd_set, + cmd_type, // Generic commands cmd_client, @@ -30,21 +29,20 @@ void load_commands() { cmd_ping, cmd_time, + // Hashtable commands + cmd_hdel, + cmd_hget, + cmd_hlen, + cmd_hset, + cmd_htype, + // List commands cmd_lindex, cmd_llen, cmd_lpop, cmd_lpush, cmd_rpop, - cmd_rpush, - - // Uncategorized commands - cmd_decr, - cmd_exists, - cmd_get, - cmd_incr, - cmd_set, - cmd_type + cmd_rpush }; commands = malloc(sizeof(_commands)); @@ -87,7 +85,7 @@ void execute_command(struct Client *client, respdata_t *data) { char res[len + 1]; sprintf(res, "-unknown command '%s'\r\n", input); - write(client->connfd, res, len); + _write(client, res, len); } } else { write_log(LOG_ERR, "Received data from Client #%d is not RDT_ARRAY, so it is not readable as a command.", client->id); diff --git a/src/server/transactions.c b/src/server/transactions.c index 9e9d313..4c1c96d 100644 --- a/src/server/transactions.c +++ b/src/server/transactions.c @@ -8,7 +8,6 @@ #include #include -#include #include struct Transaction **transactions;