Skip to content

Commit

Permalink
Hacked together a client and CLI
Browse files Browse the repository at this point in the history
  • Loading branch information
codepr committed Mar 17, 2024
1 parent 79b01e5 commit 733fa9a
Show file tree
Hide file tree
Showing 10 changed files with 292 additions and 19 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,4 +20,6 @@ logs
tags
build
roach
roach-cli
roach-server
server
16 changes: 12 additions & 4 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,26 +1,34 @@
CC = gcc
CFLAGS = -Wall -Wextra -Werror -Wunused -std=c11 -pedantic -ggdb -fsanitize=address -fsanitize=undefined -fno-omit-frame-pointer -pg -D_DEFAULT_SOURCE=200809L -Iinclude -Isrc
LDFLAGS = -L. -ltimeseries -fsanitize=address -fsanitize=undefined
LDFLAGS_CLI = -fsanitize=address -fsanitize=undefined

LIB_SOURCES = src/timeseries.c src/partition.c src/wal.c src/disk_io.c src/binary.c src/logging.c src/persistent_index.c src/commit_log.c
LIB_OBJECTS = $(LIB_SOURCES:.c=.o)
LIB_PERSISTENCE = logdata

SERVER_SOURCES = src/main.c src/parser.c src/protocol.c src/server.c src/ev.h src/ev_tcp.h
SERVER_OBJECTS = $(SERVER_SOURCES:.c=.o)
SERVER_EXECUTABLE = server
SERVER_EXECUTABLE = roach-server

all: libtimeseries.so $(SERVER_EXECUTABLE)
CLI_SOURCES = src/client.c src/roach_cli.c src/protocol.c src/ev.h src/ev_tcp.h
CLI_OBJECTS = $(CLI_SOURCES:.c=.o)
CLI_EXECUTABLE = roach-cli

all: libtimeseries.so $(SERVER_EXECUTABLE) $(CLI_EXECUTABLE)

libtimeseries.so: $(LIB_OBJECTS)
$(CC) -shared -o $@ $(LIB_OBJECTS)

$(SERVER_EXECUTABLE): $(SERVER_OBJECTS) libtimeseries.so
$(CC) -o $@ $(SERVER_OBJECTS) $(LDFLAGS)

$(CLI_EXECUTABLE): $(CLI_OBJECTS)
$(CC) -o $@ $(CLI_OBJECTS) $(LDFLAGS_CLI)

%.o: %.c
$(CC) $(CFLAGS) -fPIC -c $< -o $@

clean:
rm -f $(LIB_OBJECTS) $(SERVER_OBJECTS) libtimeseries.so $(SERVER_EXECUTABLE)
rm -rf $(LIB_PERSISTENCE) 2> /dev/null
@rm -f $(LIB_OBJECTS) $(SERVER_OBJECTS) libtimeseries.so $(SERVER_EXECUTABLE)
@rm -rf $(LIB_PERSISTENCE) 2> /dev/null
24 changes: 14 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Small timeseries database written in C, designed to efficiently store and
retrieve timestamped data records. Mostly to explore persistent log data
structure applications.

### Basics
## Basics

Still at a very early stage, the main concepts are

Expand All @@ -22,7 +22,7 @@ Still at a very early stage, the main concepts are
log, providing durability and recovery in case of crashes or failures.


### TODO
## TODO

- Duplicate points policy
- CRC32 of records for data integrity
Expand All @@ -31,7 +31,7 @@ Still at a very early stage, the main concepts are
- Schema definitions
- Server: Text based protocol, a simplified SQL-like would be cool

### Usage
## Usage

At the current stage, no server attached, just a tiny library with some crude APIs;

Expand All @@ -47,7 +47,7 @@ At the current stage, no server attached, just a tiny library with some crude AP

Plus a few other helpers.

#### As a library
### As a library

Build the `libtimeseries.so` first

Expand Down Expand Up @@ -83,7 +83,7 @@ To run it
LD_LIBRARY_PATH=/path/to/timeseries.so ./my_project
```

#### Quickstart
### Quickstart

```c
#include "timeseries.h"
Expand Down Expand Up @@ -123,18 +123,18 @@ int main() {

```

### Roach server draft
## Roach server draft

Event based server (rely on [ev](https://github.com/codepr/ev.git) at least
initially), TCP as the main transport protocol, text-based custom protocol
inspired by RESP but simpler.

#### Simple query language
### Simple query language

Definition of a simple, text-based format for clients to interact with the
server, allowing them to send commands and receive responses.

##### Basic outline
#### Basic outline

- **Text-Based Format:** Use a text-based format where each command and
response is represented as a single line of text.
Expand All @@ -145,25 +145,29 @@ server, allowing them to send commands and receive responses.
clients after processing commands. Responses should provide relevant
information or acknowledge the completion of the requested operation.

##### Core commands
#### Core commands

Define the basic operations in a SQL-like query language

- **CREATE** creates a database or a timeseries

`CREATE <database name>`
`CREATE <timeseries name> INTO <database name> [<retention period>] [<duplication policy>]`

- **INSERT** insertion of point(s) in a timeseries

`INSERT <timeseries name> INTO <database name> <timestamp | *> <value>, ...`

- **SELECT** query a timeseries, selection of point(s) and aggregations

`SELECT <timeseries name> FROM <database name> RANGE <start_timestamp> TO <end_timestamp> WHERE value [>|<|=|<=|>=|!=] <literal> AGGREGATE [AVG|MIN|MAX] BY <literal>`

- **DELETE** delete a timeseries or a database

`DELETE <database name>`
`DELETE <timeseries name> FROM <database name>`

##### Flow:
#### Flow:

1. **Client Sends Command:** Clients send commands to the server in the
specified text format.
Expand Down
116 changes: 116 additions & 0 deletions src/client.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
#include "client.h"
#include "protocol.h"
#include <ctype.h>
#include <errno.h>
#include <netdb.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>

#define BUFSIZE 2048

/*
* Create a non-blocking socket and use it to connect to the specified host and
* port
*/
static int roach_connect(const struct connect_options *opts) {

/* socket: create the socket */
int fd = socket(opts->s_family, SOCK_STREAM, 0);
if (fd < 0)
goto err;

/* Set socket timeout for read and write if present on options */
if (opts->timeout > 0) {
struct timeval tv;
tv.tv_sec = opts->timeout;
tv.tv_usec = 0;
setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, (const char *)&tv, sizeof(tv));
setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, (const char *)&tv, sizeof(tv));
}

if (opts->s_family == AF_INET) {
struct sockaddr_in addr;
struct hostent *server;

/* gethostbyname: get the server's DNS entry */
server = gethostbyname(opts->s_addr);
if (server == NULL)
goto err;

/* build the server's address */
addr.sin_family = opts->s_family;
addr.sin_port = htons(opts->s_port);
addr.sin_addr = *((struct in_addr *)server->h_addr);
bzero(&(addr.sin_zero), 8);

/* connect: create a connection with the server */
if (connect(fd, (const struct sockaddr *)&addr, sizeof(addr)) == -1)
goto err;

} else if (opts->s_family == AF_UNIX) {
struct sockaddr_un addr;
memset(&addr, 0, sizeof(addr));
addr.sun_family = AF_UNIX;
if (*opts->s_addr == '\0') {
*addr.sun_path = '\0';
strncpy(addr.sun_path + 1, opts->s_addr + 1,
sizeof(addr.sun_path) - 2);
} else {
strncpy(addr.sun_path, opts->s_addr, sizeof(addr.sun_path) - 1);
}

if (connect(fd, (struct sockaddr *)&addr, sizeof(addr)) == -1)
goto err;
}

return fd;

err:

if (errno == EINPROGRESS)
return fd;

perror("socket(2) opening socket failed");
return CLIENT_FAILURE;
}

void client_init(Client *c, const struct connect_options *opts) {
c->opts = opts;
}

int client_connect(Client *c) {
int fd = roach_connect(c->opts);
if (fd < 0)
return CLIENT_FAILURE;
c->fd = fd;
return CLIENT_SUCCESS;
}

void client_disconnect(Client *c) { close(c->fd); }

int client_send_command(Client *c, char *buf) {
uint8_t data[BUFSIZE];
Request rq = {.length = strlen(buf)};
snprintf(rq.query, sizeof(rq.query), "%s", buf);
ssize_t n = encode_request(&rq, data);
if (n < 0)
return -1;

return write(c->fd, data, n);
}

int client_recv_response(Client *c, Response *rs) {
uint8_t data[BUFSIZE];
ssize_t n = read(c->fd, data, BUFSIZE);
if (n < 0)
return -1;

n = decode_response(data, rs);
if (n < 0)
return -1;

return n;
}
45 changes: 45 additions & 0 deletions src/client.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
#ifndef CLIENT_H
#define CLIENT_H

#include <netdb.h>
#include <stdio.h>

#define CLIENT_SUCCESS 0
#define CLIENT_FAILURE -1
#define CLIENT_UNKNOWN_CMD -2

typedef struct response Response;

typedef struct client Client;

/*
* Connection options, use this structure to specify connection related opts
* like socket family, host port and timeout for communication
*/
struct connect_options {
int timeout;
int s_family;
int s_port;
char *s_addr;
};

/*
* Pretty basic connection wrapper, just a FD with a buffer tracking bytes and
* some options for connection
*/
struct client {
int fd;
const struct connect_options *opts;
};

void client_init(Client *c, const struct connect_options *opts);

int client_connect(Client *c);

void client_disconnect(Client *c);

int client_send_command(Client *c, char *buf);

int client_recv_response(Client *c, Response *rs);

#endif // CLIENT_H
2 changes: 1 addition & 1 deletion src/parser.c
Original file line number Diff line number Diff line change
Expand Up @@ -333,7 +333,7 @@ Statement_Select parse_select(Token *tokens, size_t token_count) {
}

Statement parse(Token *tokens, size_t token_count) {
Statement statement = {.type = STATEMENT_UNKNOW};
Statement statement = {.type = STATEMENT_UNKNOWN};

switch (tokens[0].type) {
case TOKEN_CREATE:
Expand Down
4 changes: 2 additions & 2 deletions src/parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -142,10 +142,10 @@ typedef struct {

// Define statement types
typedef enum {
STATEMENT_UNKNOW,
STATEMENT_CREATE,
STATEMENT_INSERT,
STATEMENT_SELECT
STATEMENT_SELECT,
STATEMENT_UNKNOWN
} Statement_Type;

// Define a generic statement
Expand Down
1 change: 1 addition & 0 deletions src/protocol.c
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "protocol.h"
#include <stdio.h>

static ssize_t encode_string(uint8_t *dst, const char *src, size_t length) {
size_t i = 0, j = 0;
Expand Down
3 changes: 1 addition & 2 deletions src/protocol.h
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
#ifndef PROTOCOL_H
#define PROTOCOL_H

#include "parser.h"
#include <math.h>
#include <stdint.h>
#include <stdlib.h>
Expand Down Expand Up @@ -42,7 +41,7 @@ typedef enum { STRING_RSP, ARRAY_RSP } Response_Type;
* Define a generic response which can either be a string response or an array
* response for the time being
*/
typedef struct {
typedef struct response {
Response_Type type;
union {
String_Response string_response;
Expand Down
Loading

0 comments on commit 733fa9a

Please sign in to comment.