Skip to content

Commit

Permalink
feat: tellydb is now completely in-memory database
Browse files Browse the repository at this point in the history
note: only supports for primitive types for now
feat(database): add DIRECT I/O for database file
perf: reduce system calls
feat(database): renew password saving to database file
feat(database): change password deriving algorithm to HKDF on SHA384
perf(database): change password structure
fix(database): missing configuration freeing on checking lock file
docs(file): change authorization file specification
perf(commands): replace where_password with get_password in AUTH command
feat(commands): add RENAME and DEL commands
refactor(utils): add length argument to hash method
refactor(database): change get_data argument to string_t
feat(commands): add DELETE command
feat(commands): add deleting empty data to HDEL, RPOP and LPOP commands
perf(database): remove null terminator from data keys
  • Loading branch information
aloima committed Dec 2, 2024
1 parent b894b5a commit acd5068
Show file tree
Hide file tree
Showing 43 changed files with 598 additions and 848 deletions.
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# tellydb
A key-value database project for educational purposes.
An in-memory key-value database project for educational purposes.

## Features
+ Follows [RESP2/RESP3](https://redis.io/docs/latest/develop/reference/protocol-spec/) specification from redis, so all redis clients are compatible
Expand All @@ -9,7 +9,8 @@ A key-value database project for educational purposes.
+ Supports integer, string, null, boolean, list and hash table types
+ Provides atomicity when saving to the database file
+ Provides saving to the database file using a background thread
+ Provides authorization system with permissions
+ Provides authorization system with permissions using passwords
+ Uses Direct I/O for logging and database files

> Look at:
> [docs/SPECS.md](./docs/SPECS.md) for more technical information,
Expand Down
6 changes: 5 additions & 1 deletion docs/AUTH.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ This file defines authorization system concepts.
If a client use a password defined in the server with `AUTH`, this client have all permissions of the password.

## Limitations
Server can have up to `2^6-1` passwords.
* Server can have up to `2^6-1` passwords.

## Notes
* Password deriving algorithm is HKDF SHA384.
* The length of derived passwords is 48.

## Permissions
* `P_READ`, read a value from database, not included data type and data existence
Expand Down
30 changes: 30 additions & 0 deletions docs/COMMANDS.md
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,19 @@ APPEND user_name " Black"
DECR user_age
```

#### DEL
**Syntax**: `DEL key [key ...]`
**Description**: Deletes the specified keys.
**Since**: `0.1.7`
**Time complexity**: `O(N) where N is key count`
**Permissions**: `P_WRITE`
**Returns**: Deleted key count

**Example**:
```shell
DEL user_name user_id
```

---

### EXISTS
Expand Down Expand Up @@ -572,6 +585,23 @@ INCR user_age

---

#### RENAME
**Syntax**: `RENAME old new`
**Description**: Renames existing key to new key.
**Since**: `0.1.7`
**Time complexity**: `O(1)`
**Permissions**: `P_WRITE`
**Returns**: `OK` or null reply if key is ntot exist
**Behavior**:
* If new key already exists, throws an error.

**Example**:
```shell
RENAME name user_name
```

---

### SET
**Syntax**: `SET key value [NX|XX] [GET]`
**Description**: Sets value.
Expand Down
2 changes: 1 addition & 1 deletion docs/FILE.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ Authorization part consists of passwords and their permissions and as follows:
* `password count byte count (1 byte, n) + password count (n byte) + passwords`

A password is as follows:
* `string length specifier + hashed password + password salt (2 bytes) + password permissions (1 byte)`
* `derived password (48 byte) + password permissions (1 byte)`

For permissions, look at [AUTH.md](./AUTH.md).

Expand Down
2 changes: 2 additions & 0 deletions headers/commands.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,11 @@ extern const struct Command cmd_htype;
/* KV COMMANDS */
extern const struct Command cmd_append;
extern const struct Command cmd_decr;
extern const struct Command cmd_del;
extern const struct Command cmd_get;
extern const struct Command cmd_exists;
extern const struct Command cmd_incr;
extern const struct Command cmd_rename;
extern const struct Command cmd_set;
extern const struct Command cmd_type;
/* /KV COMMANDS */
Expand Down
18 changes: 7 additions & 11 deletions headers/database.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,33 +15,29 @@ struct KVPair {
string_t key;
void *value;
enum TellyTypes type;

struct {
off_t start_at;
off_t end_at;
} pos;
};

struct BTree *create_cache();
struct BTree *get_cache();
struct KVPair *get_kv_from_cache(const char *key);
struct BTreeValue **get_sorted_kvs_by_pos_as_values(uint32_t *size);
bool delete_kv_from_cache(const char *key);
struct KVPair *get_kv_from_cache(const char *key, const size_t length);
bool delete_kv_from_cache(const char *key, const size_t length);
void free_cache();

void get_all_keys(off_t from);
struct KVPair *get_data(const char *key);
void get_all_data_from_file(const int fd, const off64_t file_size, char *block, const uint16_t block_size, const uint16_t filled_block_size);
struct KVPair *get_data(const string_t key);
struct KVPair *set_data(struct KVPair *data, const string_t key, void *value, const enum TellyTypes type);
bool delete_data(const string_t key);
void save_data(const uint64_t server_age);
bool bg_save(uint64_t server_age);

void set_kv(struct KVPair *kv, const string_t key, void *value, const enum TellyTypes type, const off_t start_at, const off_t end_at);
void set_kv(struct KVPair *kv, const string_t key, void *value, const enum TellyTypes type);
void free_kv(struct KVPair *kv);
/* /DATABASE */


/* DATABASE FILE */
bool open_database_fd(const char *filename, uint64_t *server_age);
uint16_t get_block_size();
int get_database_fd();
void close_database_fd();
/* /DATABASE FILE */
Expand Down
2 changes: 1 addition & 1 deletion headers/hashtable.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ struct HashTable {

struct HashTable *create_hashtable(uint32_t default_size);
void resize_hashtable(struct HashTable *table, const uint32_t size);
struct FVPair *get_fv_from_hashtable(struct HashTable *table, char *name);
struct FVPair *get_fv_from_hashtable(struct HashTable *table, const string_t name);
void free_hashtable(struct HashTable *table);

void free_fv(struct FVPair *fv);
Expand Down
16 changes: 9 additions & 7 deletions headers/server.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,26 +63,28 @@ enum Permissions {
};

struct Password {
string_t data;
unsigned char data[48];
uint8_t permissions;
char salt[3];
};

void create_constant_passwords();
void free_constant_passwords();
struct Password *get_full_password();
struct Password *get_empty_password();

bool initialize_kdf();
void free_kdf();

struct Password **get_passwords();
uint32_t get_password_count();
off_t get_authorization_from_file(const int fd);
uint16_t get_authorization_from_file(const int fd, char *block, const uint16_t block_size);
void free_passwords();

void add_password(struct Client *client, const string_t data, const uint8_t permissions);
bool remove_password(struct Client *executor, const char *value);
int32_t where_password(const char *value);
struct Password *get_password(const char *value);
bool edit_password(const char *value, const uint32_t permissions);
bool remove_password(struct Client *executor, char *value, const size_t value_len);
int32_t where_password(char *value, const size_t value_len);
struct Password *get_password(char *value, const size_t value_len);
bool edit_password(char *value, const size_t value_len, const uint32_t permissions);
/* /AUTH */


Expand Down
2 changes: 1 addition & 1 deletion headers/utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,4 +45,4 @@ void generate_date_string(char *text, const time_t value);

int open_file(const char *file, int flags);

uint64_t hash(char *key);
uint64_t hash(char *key, uint32_t length);
10 changes: 4 additions & 6 deletions src/commands/generic/auth.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ static void run(struct Client *client, commanddata_t *command, struct Password *
return;
}

const char *value = command->args[0].value;
const int32_t at = where_password(value);
const string_t input = command->args[0];
struct Password *found = get_password(input.value, input.len);

if (at == -1) {
if (!found) {
_write(client, "-This password does not exist\r\n", 31);
} else {
if (password && password != get_empty_password()) {
Expand All @@ -33,9 +33,7 @@ static void run(struct Client *client, commanddata_t *command, struct Password *
}
}

struct Password **passwords = get_passwords();
client->password = passwords[at];

client->password = found;
WRITE_OK(client);
}
}
Expand Down
10 changes: 5 additions & 5 deletions src/commands/generic/pwd.c
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ static void run(struct Client *client, commanddata_t *command, struct Password *
return;
}

if (where_password(data.value) == -1) {
if (where_password(data.value, data.len) == -1) {
add_password(client, data, permissions);
WRITE_OK(client);
} else {
Expand All @@ -90,10 +90,10 @@ static void run(struct Client *client, commanddata_t *command, struct Password *
return;
}

const char *value = command->args[1].value;
const string_t input = command->args[1];
char *permissions_value = command->args[2].value;

struct Password *target = get_password(value);
struct Password *target = get_password(input.value, input.len);

if (target) {
const uint8_t permissions = read_permissions_value(client, permissions_value);
Expand All @@ -115,9 +115,9 @@ static void run(struct Client *client, commanddata_t *command, struct Password *
return;
}

const char *value = command->args[1].value;
const string_t input = command->args[1];

if (remove_password(client, value)) WRITE_OK(client);
if (remove_password(client, input.value, input.len)) WRITE_OK(client);
else {
_write(client, "-This password cannot be found\r\n", 32);
}
Expand Down
6 changes: 5 additions & 1 deletion src/commands/hashtable/hdel.c
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ static void run(struct Client *client, commanddata_t *command, struct Password *

if (password->permissions & P_WRITE) {
const string_t key = command->args[0];
struct KVPair *kv = get_data(key.value);
struct KVPair *kv = get_data(key);
struct HashTable *table;

if (kv) {
Expand All @@ -39,6 +39,8 @@ static void run(struct Client *client, commanddata_t *command, struct Password *
del_fv_to_hashtable(table, command->args[i]);
}

if (table->size.all == 0) delete_data(key);

char buf[14];
const size_t nbytes = sprintf(buf, ":%d\r\n", old_size - table->size.all);
_write(client, buf, nbytes);
Expand All @@ -49,6 +51,8 @@ static void run(struct Client *client, commanddata_t *command, struct Password *
for (uint32_t i = 1; i < command->arg_count; ++i) {
del_fv_to_hashtable(table, command->args[i]);
}

if (table->size.all == 0) delete_data(key);
}
} else if (client) {
_write(client, "-Not allowed to use this command, need P_WRITE\r\n", 48);
Expand Down
5 changes: 2 additions & 3 deletions src/commands/hashtable/hget.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,11 @@ static void run(struct Client *client, commanddata_t *command, struct Password *
}

if (password->permissions & P_READ) {
const struct KVPair *kv = get_data(command->args[0].value);
const struct KVPair *kv = get_data(command->args[0]);

if (kv) {
if (kv->type == TELLY_HASHTABLE) {
char *name = command->args[1].value;
const struct FVPair *field = get_fv_from_hashtable(kv->value, name);
const struct FVPair *field = get_fv_from_hashtable(kv->value, command->args[1]);

if (field) {
write_value(client, field->value, field->type);
Expand Down
2 changes: 1 addition & 1 deletion src/commands/hashtable/hlen.c
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ static void run(struct Client *client, commanddata_t *command, struct Password *
}

if (password->permissions & P_READ) {
const struct KVPair *kv = get_data(command->args[0].value);
const struct KVPair *kv = get_data(command->args[0]);

if (kv) {
if (kv->type == TELLY_HASHTABLE) {
Expand Down
2 changes: 1 addition & 1 deletion src/commands/hashtable/hset.c
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ static void run(struct Client *client, commanddata_t *command, struct Password *
}

const string_t key = command->args[0];
struct KVPair *kv = get_data(key.value);
struct KVPair *kv = get_data(key);
struct HashTable *table;

if (kv) {
Expand Down
6 changes: 2 additions & 4 deletions src/commands/hashtable/htype.c
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,11 @@ static void run(struct Client *client, commanddata_t *command, struct Password *
}

if (password->permissions & P_READ) {
const char *key = command->args[0].value;
const struct KVPair *kv = get_data(key);
const struct KVPair *kv = get_data(command->args[0]);

if (kv) {
if (kv->type == TELLY_HASHTABLE) {
char *name = command->args[1].value;
struct FVPair *fv = get_fv_from_hashtable(kv->value, name);
struct FVPair *fv = get_fv_from_hashtable(kv->value, command->args[1]);

switch (fv->type) {
case TELLY_NULL:
Expand Down
2 changes: 1 addition & 1 deletion src/commands/kv/append.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ static void run(struct Client *client, commanddata_t *command, struct Password *

if (password->permissions & (P_READ | P_WRITE)) {
const string_t key = command->args[0];
const struct KVPair *kv = get_data(key.value);
const struct KVPair *kv = get_data(key);

if (kv) {
if (kv->type == TELLY_STR) {
Expand Down
2 changes: 1 addition & 1 deletion src/commands/kv/decr.c
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ static void run(struct Client *client, commanddata_t *command, struct Password *

if (password->permissions & (P_READ | P_WRITE)) {
string_t key = command->args[0];
struct KVPair *result = get_data(key.value);
struct KVPair *result = get_data(key);

if (!result) {
long *number = calloc(1, sizeof(long));
Expand Down
40 changes: 40 additions & 0 deletions src/commands/kv/del.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
#include "../../../headers/server.h"
#include "../../../headers/database.h"
#include "../../../headers/commands.h"

#include <stdio.h>
#include <stdint.h>

static void run(struct Client *client, commanddata_t *command, struct Password *password) {
if (command->arg_count == 0) {
if (client) WRONG_ARGUMENT_ERROR(client, "DEL", 3);
return;
}

if (password->permissions & P_WRITE) {
uint32_t deleted = 0;

for (uint32_t i = 0; i < command->arg_count; ++i) {
deleted += delete_data(command->args[i]);
}

if (client) {
char res[13];
const size_t res_len = sprintf(res, ":%d\r\n", deleted);

_write(client, res, res_len);
}
} else if (client) {
_write(client, "-Not allowed to use this command, need P_WRITE\r\n", 48);
}
}

const struct Command cmd_del = {
.name = "DEL",
.summary = "Deletes the specified keys.",
.since = "0.1.7",
.complexity = "O(N) where N is key count",
.subcommands = NULL,
.subcommand_count = 0,
.run = run
};
2 changes: 1 addition & 1 deletion src/commands/kv/exists.c
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ static void run(struct Client *client, commanddata_t *command, __attribute__((un
buf[0] = '\0';

for (uint32_t i = 0; i < command->arg_count; ++i) {
const char *key = command->args[i].value;
const string_t key = command->args[i];

if (get_data(key)) {
existed += 1;
Expand Down
3 changes: 1 addition & 2 deletions src/commands/kv/get.c
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@ static void run(struct Client *client, commanddata_t *command, struct Password *
}

if (password->permissions & P_READ) {
const char *key = command->args[0].value;
const struct KVPair *kv = get_data(key);
const struct KVPair *kv = get_data(command->args[0]);

if (kv) {
write_value(client, kv->value, kv->type);
Expand Down
Loading

0 comments on commit acd5068

Please sign in to comment.