Skip to main content
The engine module parses and executes RadishDB commands against a HashTable. It is the layer between the REPL/server and the storage layer — all user-facing commands (SET, GET, DEL, TTL, INFO, SAVE, LOAD, BENCH, etc.) are dispatched here.

Data structures

ResultType

Discriminant tag for the Result union.
typedef enum {
  RES_STRING,
  RES_OK,
  RES_ERROR,
  RES_INTEGER,
  RES_NIL,
  RES_CLEAN,
  RES_EXIT
} ResultType;
VariantMeaning
RES_STRINGA heap-allocated string payload is present in result.value.string.
RES_OKThe command succeeded with no value to return (e.g. SET). No payload.
RES_ERRORA heap-allocated error message is present in result.value.string.
RES_INTEGERA long result is present in result.value.integer (e.g. TTL, DEL, COUNT).
RES_NILThe command returned a null result (e.g. GET on a missing key). No payload.
RES_CLEANSignals the REPL to clear the terminal screen. No payload.
RES_EXITSignals the REPL or server to shut down cleanly. No payload.

Result

The return value of execute_command. Carries both the result type and, for some variants, a payload.
typedef struct Result {
  ResultType type;
  union {
    char *string;  // used for RES_STRING and RES_ERROR — heap-allocated, must be freed
    long integer;  // used for RES_INTEGER
  } value;
} Result;
type
ResultType
required
Discriminant tag. Determines which field of value is valid.
value
union
Always call free_result after you are done with a Result. Skipping it for RES_STRING or RES_ERROR results is a memory leak.

engine_start_time

extern time_t engine_start_time;
A module-level variable set once at process startup. The INFO command reads this to compute uptime. Set it before calling execute_command for the first time:
engine_start_time = time(NULL);

Functions

execute_command

Parses and executes a single RadishDB command string.
Result execute_command(HashTable *ht, char *line);
ht
HashTable *
required
The hash table the command operates on.
line
char *
required
A mutable, null-terminated command string such as "SET name radish". The function modifies this buffer in-place: it calls trim_newline to strip trailing whitespace and strtok (via split_tokens) to tokenize it. Do not pass a string literal.
Returns a Result struct. The caller must call free_result on it after use.
line is modified in-place. Always pass a writable buffer, never a string literal. Declare with char cmd[] = "..." or strdup a literal before passing.

Handling each result type

Result r = execute_command(ht, line);

switch (r.type) {
    case RES_OK:
        printf("OK\n");
        break;
    case RES_STRING:
        printf("%s\n", r.value.string);
        break;
    case RES_INTEGER:
        printf("(integer) %ld\n", r.value.integer);
        break;
    case RES_NIL:
        printf("(nil)\n");
        break;
    case RES_ERROR:
        fprintf(stderr, "%s\n", r.value.string);
        break;
    case RES_EXIT:
        // signal shutdown
        break;
    case RES_CLEAN:
        // clear terminal
        break;
}

free_result(&r);

free_result

Frees the heap-allocated payload of a Result, if any.
void free_result(Result *r);
r
Result *
required
Pointer to the result to free. For RES_STRING and RES_ERROR, frees r->value.string. For all other types, this is a no-op.
The implementation:
void free_result(Result *r) {
    if ((r->type == RES_STRING || r->type == RES_ERROR) && r->value.string) {
        free(r->value.string);
    }
}
It is safe to call free_result on any result type — it only frees memory when a payload is present.

Formats and writes a Result to a FILE * output stream.
void print_result(FILE *out, Result *r);
out
FILE *
required
Output file pointer — stdout for the REPL, a client socket file pointer for the TCP server.
r
Result *
required
The result to print. Each type is formatted as follows:
ResultTypeOutput
RES_STRINGThe string followed by \n
RES_ERRORThe error message followed by \n
RES_OKOK\n
RES_INTEGERThe integer as %ld\n
RES_NIL(nil)\n
RES_CLEANNo output (REPL clears screen separately)
RES_EXITNo output
print_result does not call free_result. You must call free_result separately after printing.
Result r = execute_command(ht, line);
print_result(stdout, &r);
free_result(&r);

Full example

#include "engine.h"
#include "hashtable.h"
#include <stdio.h>
#include <time.h>

int main(void) {
    HashTable *ht = ht_create(8);
    engine_start_time = time(NULL);

    // SET
    char cmd1[] = "SET name radish";
    Result r1 = execute_command(ht, cmd1);
    // r1.type == RES_OK
    free_result(&r1);

    // GET
    char cmd2[] = "GET name";
    Result r2 = execute_command(ht, cmd2);
    if (r2.type == RES_STRING) {
        printf("Value: %s\n", r2.value.string);  // "radish"
    }
    free_result(&r2);

    // TTL (no expiry set → -1)
    char cmd3[] = "TTL name";
    Result r3 = execute_command(ht, cmd3);
    // r3.type == RES_INTEGER, r3.value.integer == -1
    printf("TTL: %ld\n", r3.value.integer);
    free_result(&r3);

    ht_free(ht);
    return 0;
}

Server frontend

start_server

Starts the TCP server, binds to port 6379, and enters the command processing loop.
void start_server(HashTable *ht);
ht
HashTable *
required
The hash table the server operates on. All commands received over TCP are executed against this table.
The server:
  • Binds to 0.0.0.0:6379
  • Accepts one client connection at a time (listen backlog of 1)
  • Reads commands line by line from the client socket
  • Calls execute_command and print_result for each line
  • Calls expire_sweep(ht, 10) on every iteration
  • Exits when the client disconnects
This function does not return until the client disconnects. Declared in server.h.
#include "server.h"
#include "hashtable.h"

int main(void) {
    HashTable *ht = ht_create(8);
    // ... open AOF, replay, etc.
    start_server(ht);  // blocks until client disconnects
    ht_free(ht);
    return 0;
}

Build docs developers (and LLMs) love