Reorganize code and clean up Makefile

This commit is contained in:
2025-08-09 08:47:47 +02:00
parent e200f52f40
commit 890a5c48e4
22 changed files with 331 additions and 179 deletions
+915
View File
@@ -0,0 +1,915 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <limits.h>
#include <stdbool.h>
#include "sqlite3.h"
#include "chttp.h"
#include "WL.h"
#include "sqlite3utils.h"
sqlite3 *db;
int load_file(char *file, char **data, long *size)
{
FILE *f = fopen(file, "rb");
if (f == NULL) return -1;
fseek(f, 0, SEEK_END);
*size = ftell(f);
fseek(f, 0, SEEK_SET);
*data = malloc(*size + 1);
fread(*data, 1, *size, f);
(*data)[*size] = '\0';
fclose(f);
return 0;
}
static int query_routine(WL_State *state)
{
long long num_args;
if (!WL_popint(state, &num_args)) {
assert(0); // TODO
}
if (num_args == 0) {
assert(0); // TODO
}
WL_String format;
if (!WL_peekstr(state, -num_args, &format)) {
assert(0); // TODO
}
sqlite3_stmt *res;
int ret = sqlite3_prepare_v2(db, format.ptr, format.len, &res, 0);
if (ret != SQLITE_OK) {
fprintf(stderr, "Failed to prepare statement: %s\n", sqlite3_errmsg(db));
assert(0); // TODO
}
for (int i = 0; i < num_args-1; i++) {
if (ret != SQLITE_OK) {
fprintf(stderr, "Failed to prepare statement: %s\n", sqlite3_errmsg(db));
assert(0); // TODO
}
if (WL_peeknone(state, -num_args + i + 1)) {
ret = sqlite3_bind_null(res, i+1);
continue;
}
long long ival;
if (WL_peekint(state, -num_args + i + 1, &ival)) {
ret = sqlite3_bind_int64(res, i+1, ival);
continue;
}
float fval;
if (WL_peekfloat(state, -num_args + i + 1, &fval)) {
ret = sqlite3_bind_double(res, i+1, fval);
continue;
}
WL_String str;
if (WL_peekstr(state, -num_args + i + 1, &str)) {
ret = sqlite3_bind_text(res, i+1, str.ptr, str.len, NULL);
continue;
}
break;
}
ret = sqlite3_step(res);
if (ret == SQLITE_DONE) {
WL_pushnone(state);
} else if (ret != SQLITE_ROW) {
WL_pushnone(state);
} else {
WL_pusharray(state, 0);
do {
int num_cols = sqlite3_column_count(res);
if (num_cols < 0) {
assert(0); // TODO
}
WL_pushmap(state, 0);
for (int i = 0; i < num_cols; i++) {
ret = sqlite3_column_type(res, i);
switch (ret) {
case SQLITE_INTEGER:
{
int64_t x = sqlite3_column_int64(res, i);
WL_pushint(state, x);
}
break;
case SQLITE_FLOAT:
{
double x = sqlite3_column_double(res, i);
WL_pushfloat(state, x);
}
break;
case SQLITE_TEXT:
{
const void *x = sqlite3_column_text(res, i);
int n = sqlite3_column_bytes(res, i);
WL_pushstr(state, (WL_String) { (char*) x, n });
}
break;
case SQLITE_BLOB:
{
const void *x = sqlite3_column_blob(res, i);
int n = sqlite3_column_bytes(res, i);
WL_pushstr(state, (WL_String) { (char*) x, n });
}
break;
case SQLITE_NULL:
{
WL_pushnone(state);
}
break;
}
const char *name = sqlite3_column_name(res, i);
WL_pushstr(state, (WL_String) { (char*) name, strlen(name) });
WL_insert(state);
}
WL_append(state);
} while (sqlite3_step(res) == SQLITE_ROW);
}
WL_pushint(state, 1);
sqlite3_finalize(res);
return 0;
}
int evaluate_template(HTTP_ResponseBuilder builder,
WL_Program program, WL_Arena arena,
char *err, int errmax, int user_id, int post_id)
{
//WL_dump_program(program);
WL_State *state = WL_State_init(&arena, program, err, errmax);
if (state == NULL)
return -1;
WL_State_trace(state, 0);
for (;;) {
WL_Result result = WL_eval(state);
switch (result.type) {
case WL_DONE:
WL_State_free(state);
return 0;
case WL_ERROR:
WL_State_free(state);
return -1;
case WL_VAR:
if (WL_streq(result.str, "login_user_id", -1)) {
if (user_id < 0)
WL_pushnone(state);
else
WL_pushint(state, user_id);
} else if (WL_streq(result.str, "post_id", -1)) {
if (post_id < 0)
WL_pushnone(state);
else
WL_pushint(state, post_id);
}
break;
case WL_CALL:
if (WL_streq(result.str, "query", -1)) {
query_routine(state);
break;
}
break;
case WL_OUTPUT:
http_response_builder_body(builder, (HTTP_String) { result.str.ptr, result.str.len });
break;
}
}
return 0;
}
void evaluate_template_2(HTTP_ResponseBuilder builder, WL_Arena arena, char *file, int user_id, int post_id)
{
http_response_builder_status(builder, 200);
http_response_builder_header(builder, HTTP_STR("Content-Type: text/html"));
WL_Compiler *compiler = WL_Compiler_init(&arena);
if (compiler == NULL) {
assert(0); // TODO
}
char *loaded_files[128];
int num_loaded_files = 0;
WL_CompileResult result;
WL_String path = { file, strlen(file) };
for (int i = 0;; i++) {
char buf[1<<10];
if (path.len >= (int) sizeof(buf)) {
assert(0); // TODO
}
memcpy(buf, path.ptr, path.len);
buf[path.len] = '\0';
FILE *f = fopen(buf, "rb");
if (f == NULL) {
http_response_builder_undo(builder);
http_response_builder_status(builder, 500);
http_response_builder_body(builder, HTTP_STR("Couldn't find file '"));
http_response_builder_body(builder, (HTTP_String) { path.ptr, path.len });
http_response_builder_body(builder, HTTP_STR("'"));
http_response_builder_done(builder);
return;
}
fseek(f, 0, SEEK_END);
long file_size = ftell(f);
fseek(f, 0, SEEK_SET);
char *file_data = malloc(file_size);
fread(file_data, 1, file_size, f);
fclose(f);
result = WL_compile(compiler, path, (WL_String) { file_data, file_size });
loaded_files[num_loaded_files++] = file_data;
if (result.type == WL_COMPILE_RESULT_ERROR) {
printf("Compilation of '%.*s' failed\n", path.len, path.ptr);
break;
}
if (result.type == WL_COMPILE_RESULT_DONE)
break;
assert(result.type == WL_COMPILE_RESULT_FILE);
path = result.path;
}
for (int i = 0; i < num_loaded_files; i++)
free(loaded_files[i]);
WL_Compiler_free(compiler);
if (result.type == WL_COMPILE_RESULT_ERROR) {
http_response_builder_undo(builder);
http_response_builder_status(builder, 500);
http_response_builder_body(builder, HTTP_STR("Couldn't compile the template"));
http_response_builder_done(builder);
return;
}
WL_Program program = result.program;
char err[1<<9];
if (evaluate_template(builder, program, arena, err, (int) sizeof(err), user_id, post_id) < 0) {
http_response_builder_undo(builder);
http_response_builder_status(builder, 500);
http_response_builder_body(builder, (HTTP_String) { err, (int) strlen(err) });
http_response_builder_done(builder);
return;
}
http_response_builder_done(builder);
}
int create_user(HTTP_String name, HTTP_String email, HTTP_String pass)
{
sqlite3_stmt *stmt;
int ret = sqlite3utils_prepare(db, &stmt,
"INSERT INTO Users(username, email, password) VALUES (?, ?, ?)", name, email, pass);
if (ret != SQLITE_OK)
return -500;
ret = sqlite3_step(stmt);
if (ret != SQLITE_DONE) {
fprintf(stderr, "Failed to prepare statement: %s\n", sqlite3_errmsg(db));
// TODO: What if the user exists?
sqlite3_finalize(stmt);
return -500;
}
int64_t tmp = sqlite3_last_insert_rowid(db);
if (tmp < 0 || tmp > INT_MAX) {
sqlite3_finalize(stmt);
return -500;
}
int user_id = (int) tmp;
ret = sqlite3_finalize(stmt);
if (ret != SQLITE_OK) {
fprintf(stderr, "Failed to prepare statement: %s\n", sqlite3_errmsg(db));
sqlite3_finalize(stmt);
return -500;
}
return user_id;
}
int user_exists(HTTP_String name, HTTP_String pass)
{
sqlite3_stmt *stmt;
int ret = sqlite3utils_prepare(db, &stmt,
"SELECT id FROM Users WHERE username=? AND password=?", name, pass);
if (ret != SQLITE_OK)
return -500;
ret = sqlite3_step(stmt);
if (ret == SQLITE_DONE) {
sqlite3_finalize(stmt);
return -404;
}
if (ret != SQLITE_ROW) {
sqlite3_finalize(stmt);
return -500;
}
int user_id = sqlite3_column_int(stmt, 0);
if (user_id < 0) {
sqlite3_finalize(stmt);
return -500;
}
ret = sqlite3_finalize(stmt);
if (ret != SQLITE_OK)
return -500;
return user_id;
}
HTTP_String http_getcookie(HTTP_Request *req, HTTP_String name)
{
// TODO: best-effort implementation
for (int i = 0; i < req->num_headers; i++) {
if (!http_streqcase(req->headers[i].name, HTTP_STR("Cookie")))
continue;
char *src = req->headers[i].value.ptr;
int len = req->headers[i].value.len;
int cur = 0;
// Cookie: name1=value1; name2=value2; name3=value3
for (;;) {
while (cur < len && src[cur] == ' ')
cur++;
int off = cur;
while (cur < len && src[cur] != '=')
cur++;
HTTP_String cookie_name = { src + off, cur - off };
if (cur == len)
break;
cur++;
off = cur;
while (cur < len && src[cur] != ';')
cur++;
HTTP_String cookie_value = { src + off, cur - off };
if (http_streq(name, cookie_name))
return cookie_value;
if (cur == len)
break;
cur++;
}
}
return HTTP_STR("");
}
HTTP_String http_getparam(HTTP_String body, HTTP_String str)
{
// This is just a best-effort implementation
char *src = body.ptr;
int len = body.len;
int cur = 0;
if (cur < len && src[cur] == '?')
cur++;
while (cur < len) {
HTTP_String name;
{
int off = cur;
while (cur < len && src[cur] != '=' && src[cur] != '&')
cur++;
name = (HTTP_String) { src + off, cur - off };
}
HTTP_String body = HTTP_STR("");
if (cur < len) {
cur++;
if (src[cur-1] == '=') {
int off = cur;
while (cur < len && src[cur] != '&')
cur++;
body = (HTTP_String) { src + off, cur - off };
if (cur < len)
cur++;
}
}
if (http_streq(str, name))
return body;
}
return HTTP_STR("");
}
#define SESSION_LIMIT 1024
#define USERNAME_LIMIT 64
typedef struct {
int sess_id;
int user_id;
} Session;
typedef struct {
Session sessions[SESSION_LIMIT];
int count;
int next_sess_id; // TODO: this should be choosen randomly and on 64 bits
} SessionSet;
void session_set_init(SessionSet *set)
{
set->count = 0;
set->next_sess_id = 1;
}
int session_set_add(SessionSet *set, int user_id)
{
if (set->count == SESSION_LIMIT)
return -1;
int sess_id = set->next_sess_id++;
set->sessions[set->count++] = (Session) {
.sess_id=sess_id,
.user_id=user_id,
};
return sess_id;
}
void session_set_remove(SessionSet *set, int sess_id)
{
int i = 0;
while (i < set->count && set->sessions[i].sess_id != sess_id)
i++;
if (i == set->count)
return;
set->sessions[i] = set->sessions[--set->count];
}
int session_set_find(SessionSet *set, int sess_id)
{
int i = 0;
while (i < set->count && set->sessions[i].sess_id != sess_id)
i++;
if (i == set->count)
return -1;
return set->sessions[i].user_id;
}
bool valid_name(HTTP_String str)
{
(void) str; // TODO
return true;
}
bool valid_email(HTTP_String str)
{
(void) str; // TODO
return true;
}
bool valid_pass(HTTP_String str)
{
(void) str; // TODO
return true;
}
bool valid_post_title(HTTP_String str)
{
(void) str; // TODO
return true;
}
bool valid_post_content(HTTP_String str)
{
(void) str; // TODO
return true;
}
bool valid_link(HTTP_String str)
{
(void) str;
return true;
}
int main(void)
{
http_global_init();
printf("%s\n", sqlite3_libversion());
int ret = sqlite3_open(":memory:", &db);
if (ret != SQLITE_OK) {
fprintf(stderr, "Cannot open database: %s\n", sqlite3_errmsg(db));
sqlite3_close(db);
return -1;
}
char *schema_data;
long schema_size;
ret = load_file("misc/schema.sql", &schema_data, &schema_size);
if (ret < 0) {
printf("Couldn't load schema\n");
return -1;
}
ret = sqlite3_exec(db, schema_data, NULL, NULL, NULL);
if (ret != SQLITE_OK) {
fprintf(stderr, "Cannot run schema: %s\n", sqlite3_errmsg(db));
sqlite3_close(db);
return -1;
}
free(schema_data);
HTTP_String addr = HTTP_STR("127.0.0.1");
uint16_t port = 8080;
HTTP_Server *server = http_server_init(addr, port);
if (server == NULL) return -1;
http_server_set_trace(server, false);
int pool_cap = 1<<20;
char *pool = malloc(pool_cap);
WL_Arena arena = { pool, pool_cap, 0 };
SessionSet sessions;
session_set_init(&sessions);
for (;;) {
HTTP_Request *req;
HTTP_ResponseBuilder builder;
int ret = http_server_wait(server, &req, &builder);
if (ret < 0) return -1;
//printf("%.*s\n", req->raw.len, req->raw.ptr); // TODO
// If logged in, these are set to non-negative values
int sess_id = -1;
int user_id = -1;
{
HTTP_String str = http_getcookie(req, HTTP_STR("sess_id"));
if (str.len > 0) {
char tmp[1<<9]; // TODO
memcpy(tmp, str.ptr, str.len);
tmp[str.len] = '\0';
sess_id = atoi(tmp);
if (sess_id == 0)
sess_id = -1;
}
user_id = session_set_find(&sessions, sess_id);
}
HTTP_String path = req->url.path;
if (http_streq(path, HTTP_STR("/api/login"))) {
if (req->method != HTTP_METHOD_POST) {
http_response_builder_status(builder, 405);
http_response_builder_done(builder);
continue;
}
if (user_id != -1) {
// Already logged in
http_response_builder_status(builder, 303);
http_response_builder_header(builder, HTTP_STR("Location: /index"));
http_response_builder_done(builder);
continue;
}
HTTP_String name = http_getparam(req->body, HTTP_STR("username"));
HTTP_String pass = http_getparam(req->body, HTTP_STR("password"));
name = http_trim(name);
pass = http_trim(pass);
if (!valid_name(name) || !valid_pass(pass)) {
http_response_builder_status(builder, 400);
http_response_builder_done(builder);
continue;
}
int ret = user_exists(name, pass);
if (ret < 0) {
http_response_builder_status(builder, -ret);
http_response_builder_done(builder);
continue;
}
int user_id = ret;
int sess_id = session_set_add(&sessions, user_id);
if (sess_id < 0) {
http_response_builder_status(builder, 500);
http_response_builder_done(builder);
continue;
}
char cookie[1<<9];
int cookie_len = snprintf(cookie, sizeof(cookie), "Set-Cookie: sess_id=%d; Path=/; HttpOnly", sess_id);
if (cookie_len < 0 || cookie_len >= (int) sizeof(cookie)) {
http_response_builder_status(builder, 500);
http_response_builder_done(builder);
continue;
}
http_response_builder_status(builder, 303); // TODO: Whats the correct code here?
http_response_builder_header(builder, (HTTP_String) { cookie, cookie_len });
http_response_builder_header(builder, HTTP_STR("Location: /index"));
http_response_builder_done(builder);
} else if (http_streq(path, HTTP_STR("/api/signup"))) {
if (req->method != HTTP_METHOD_POST) {
http_response_builder_status(builder, 405);
http_response_builder_done(builder);
continue;
}
if (user_id != -1) {
// Already logged in
http_response_builder_status(builder, 303);
http_response_builder_header(builder, HTTP_STR("Location: /index"));
http_response_builder_done(builder);
continue;
}
HTTP_String name = http_getparam(req->body, HTTP_STR("username"));
HTTP_String email = http_getparam(req->body, HTTP_STR("email"));
HTTP_String pass1 = http_getparam(req->body, HTTP_STR("password1"));
HTTP_String pass2 = http_getparam(req->body, HTTP_STR("password2"));
if (!valid_name(name) || !valid_email(email) || !valid_pass(pass1)) {
http_response_builder_status(builder, 400);
http_response_builder_done(builder);
continue;
}
if (!http_streq(pass1, pass2)) {
http_response_builder_status(builder, 400);
http_response_builder_done(builder);
continue;
}
int ret = create_user(name, email, pass1);
if (ret < 0) {
http_response_builder_status(builder, -ret);
http_response_builder_done(builder);
continue;
}
user_id = ret;
int sess_id = session_set_add(&sessions, user_id);
if (sess_id < 0) {
http_response_builder_status(builder, 500);
http_response_builder_done(builder);
continue;
}
char cookie[1<<9];
int cookie_len = snprintf(cookie, sizeof(cookie), "Set-Cookie: sess_id=%d; Path=/; HttpOnly", sess_id);
if (cookie_len < 0 || cookie_len >= (int) sizeof(cookie)) {
http_response_builder_status(builder, 500);
http_response_builder_done(builder);
continue;
}
http_response_builder_status(builder, 303); // TODO: Whats the correct code here?
http_response_builder_header(builder, (HTTP_String) { cookie, cookie_len });
http_response_builder_header(builder, HTTP_STR("Location: /index"));
http_response_builder_done(builder);
} else if (http_streq(path, HTTP_STR("/api/logout"))) {
if (sess_id != -1)
session_set_remove(&sessions, sess_id);
http_response_builder_status(builder, 303); // TODO: Whats the correct code here?
http_response_builder_header(builder, HTTP_STR("Set-Cookie: sess_id=; Path=/; HttpOnly"));
http_response_builder_header(builder, HTTP_STR("Location: /index"));
http_response_builder_done(builder);
} else if (http_streq(path, HTTP_STR("/api/post"))) {
if (req->method != HTTP_METHOD_POST) {
http_response_builder_status(builder, 405);
http_response_builder_done(builder);
continue;
}
if (user_id == -1) {
http_response_builder_status(builder, 303);
http_response_builder_header(builder, HTTP_STR("Location: /index"));
http_response_builder_done(builder);
continue;
}
HTTP_String title = http_getparam(req->body, HTTP_STR("title"));
HTTP_String link = http_getparam(req->body, HTTP_STR("link"));
HTTP_String content = http_getparam(req->body, HTTP_STR("content"));
title = http_trim(title);
link = http_trim(link);
content = http_trim(content);
if (!valid_post_title(title) || !valid_link(link) || !valid_post_content(content)) {
assert(0); // TODO
}
if (content.len == 0 && link.len == 0) {
assert(0); // TODO
}
bool is_link = false;
if (link.len > 0) {
is_link = true;
content = link;
}
sqlite3_stmt *stmt;
int ret = sqlite3utils_prepare(db, &stmt, "INSERT INTO Posts(author, title, is_link, content) VALUES (?, ?, ?, ?)", user_id, title, is_link, content);
if (ret != SQLITE_OK) {
assert(0); // TODO
}
ret = sqlite3_step(stmt);
if (ret != SQLITE_DONE) {
assert(0); // TODO
}
ret = sqlite3_finalize(stmt);
if (ret != SQLITE_OK) {
assert(0); // TODO
}
int64_t tmp = sqlite3_last_insert_rowid(db);
if (tmp < 0 || tmp > INT_MAX) {
assert(0); // TODO
}
int post_id = (int) tmp;
char location[1<<9];
ret = snprintf(location, sizeof(location), "Location: /post?id=%d", post_id);
if (ret < 0 || ret >= (int) sizeof(location)) {
assert(0); // TODO
}
http_response_builder_status(builder, 303);
http_response_builder_header(builder, (HTTP_String) { location, ret });
http_response_builder_done(builder);
} else if (http_streq(path, HTTP_STR("/index"))) {
evaluate_template_2(builder, arena, "pages/index.wl", user_id, -1);
} else if (http_streq(path, HTTP_STR("/write"))) {
if (user_id == -1) {
// Not logged in
http_response_builder_status(builder, 303);
http_response_builder_header(builder, HTTP_STR("Location: /index"));
http_response_builder_done(builder);
continue;
}
evaluate_template_2(builder, arena, "pages/write.wl", user_id, -1);
} else if (http_streq(path, HTTP_STR("/login"))) {
if (user_id != -1) {
// Already logged in
http_response_builder_status(builder, 303);
http_response_builder_header(builder, HTTP_STR("Location: /index"));
http_response_builder_done(builder);
continue;
}
evaluate_template_2(builder, arena, "pages/login.wl", user_id, -1);
} else if (http_streq(path, HTTP_STR("/signup"))) {
if (user_id != -1) {
// Already logged in
http_response_builder_status(builder, 303);
http_response_builder_header(builder, HTTP_STR("Location: /index"));
http_response_builder_done(builder);
continue;
}
evaluate_template_2(builder, arena, "pages/signup.wl", user_id, -1);
} else if (http_streq(path, HTTP_STR("/post"))) {
HTTP_String idstr = http_getparam(req->url.query, HTTP_STR("id"));
char buf[32];
if (idstr.len == 0 || idstr.len >= (int) sizeof(buf)) {
evaluate_template_2(builder, arena, "pages/notfound.wl", user_id, -1);
continue;
}
memcpy(buf, idstr.ptr, idstr.len);
buf[idstr.len] = '\0';
int post_id = atoi(buf);
if (post_id == 0) {
evaluate_template_2(builder, arena, "pages/notfound.wl", user_id, -1);
continue;
}
sqlite3_stmt *stmt;
int ret = sqlite3utils_prepare(db, &stmt, "SELECT COUNT(*) FROM Posts WHERE id=?", post_id);
if (ret != SQLITE_OK) {
assert(0); // TODO
}
ret = sqlite3_step(stmt);
if (ret != SQLITE_ROW) {
assert(0); // TODO
}
int64_t num = sqlite3_column_int64(stmt, 0);
ret = sqlite3_finalize(stmt);
if (ret != SQLITE_OK) {
assert(0); // TODO
}
if (num < 0) {
assert(0); // TODO
}
if (num == 0)
evaluate_template_2(builder, arena, "pages/notfound.wl", user_id, -1);
else
evaluate_template_2(builder, arena, "pages/post.wl", user_id, post_id);
} else {
evaluate_template_2(builder, arena, "pages/notfound.wl", user_id, -1);
}
}
sqlite3_close(db);
http_server_free(server);
http_global_free();
return 0;
}
+46
View File
@@ -0,0 +1,46 @@
#include <stdio.h>
#include <stddef.h>
#include "sqlite3utils.h"
int sqlite3utils_prepare_impl(sqlite3 *db, sqlite3_stmt **pstmt, const char *fmt, VArgs args)
{
sqlite3_stmt *stmt;
int ret = sqlite3_prepare_v2(db, fmt, -1, &stmt, NULL);
if (ret != SQLITE_OK) {
fprintf(stderr, "Failed to prepare statement: %s (%s:%d)\n", sqlite3_errmsg(db), __FILE__, __LINE__);
return ret;
}
for (int i = 0; i < args.len; i++) {
VArg arg = args.ptr[i];
switch (arg.type) {
case VARG_TYPE_C : ret = sqlite3_bind_text(stmt, i+1, &arg.c, 1, NULL); break;
case VARG_TYPE_S : ret = sqlite3_bind_int(stmt, i+1, arg.s); break;
case VARG_TYPE_I : ret = sqlite3_bind_int(stmt, i+1, arg.i); break;
case VARG_TYPE_L : ret = sqlite3_bind_int64(stmt, i+1, arg.l); break;
case VARG_TYPE_LL : ret = sqlite3_bind_int64(stmt, i+1, arg.ll); break;
case VARG_TYPE_SC : ret = sqlite3_bind_int(stmt, i+1, arg.sc); break;
case VARG_TYPE_SS : ret = sqlite3_bind_int(stmt, i+1, arg.ss); break;
case VARG_TYPE_SI : ret = sqlite3_bind_int(stmt, i+1, arg.si); break;
case VARG_TYPE_SL : ret = sqlite3_bind_int64(stmt, i+1, arg.sl); break;
case VARG_TYPE_SLL: ret = sqlite3_bind_int(stmt, i+1, arg.sll); break;
case VARG_TYPE_UC : ret = sqlite3_bind_int(stmt, i+1, arg.uc); break;
case VARG_TYPE_US : ret = sqlite3_bind_int(stmt, i+1, arg.us); break;
case VARG_TYPE_UI : ret = sqlite3_bind_int64(stmt, i+1, arg.ui); break;
case VARG_TYPE_UL : ret = sqlite3_bind_int64(stmt, i+1, arg.ul); break;
case VARG_TYPE_ULL: ret = sqlite3_bind_int64(stmt, i+1, arg.ull); break;
case VARG_TYPE_F : ret = sqlite3_bind_double(stmt, i+1, arg.f); break;
case VARG_TYPE_D : ret = sqlite3_bind_double(stmt, i+1, arg.d); break;
case VARG_TYPE_B : ret = sqlite3_bind_int(stmt, i+1, arg.b); break;
case VARG_TYPE_STR: ret = sqlite3_bind_text(stmt, i+1, arg.str.ptr, arg.str.len, NULL); break;
}
if (ret != SQLITE_OK) {
fprintf(stderr, "Failed to prepare statement: %s (%s:%d)\n", sqlite3_errmsg(db), __FILE__, __LINE__);
sqlite3_finalize(stmt);
return ret;
}
}
*pstmt = stmt;
return SQLITE_OK;
}
+5
View File
@@ -0,0 +1,5 @@
#include "sqlite3.h"
#include "variadic.h"
int sqlite3utils_prepare_impl(sqlite3 *db, sqlite3_stmt **pstmt, const char *fmt, VArgs args);
#define sqlite3utils_prepare(db, pstmt, fmt, ...) sqlite3utils_prepare_impl((db), (pstmt), (fmt), VARGS(__VA_ARGS__))
+21
View File
@@ -0,0 +1,21 @@
#include "variadic.h"
VArg varg_from_c (char c) { return (VArg) { VARG_TYPE_C, .c=c }; }
VArg varg_from_s (short s) { return (VArg) { VARG_TYPE_S, .s=s }; }
VArg varg_from_i (int i) { return (VArg) { VARG_TYPE_I, .i=i }; }
VArg varg_from_l (long l) { return (VArg) { VARG_TYPE_L, .l=l }; }
VArg varg_from_ll (long long ll) { return (VArg) { VARG_TYPE_LL, .ll=ll }; }
VArg varg_from_sc (char sc) { return (VArg) { VARG_TYPE_SC, .sc=sc }; }
VArg varg_from_ss (short ss) { return (VArg) { VARG_TYPE_SS, .ss=ss }; }
VArg varg_from_si (int si) { return (VArg) { VARG_TYPE_SI, .si=si }; }
VArg varg_from_sl (long sl) { return (VArg) { VARG_TYPE_SL, .sl=sl }; }
VArg varg_from_sll (long long sll) { return (VArg) { VARG_TYPE_SLL, .sll=sll }; }
VArg varg_from_uc (char uc) { return (VArg) { VARG_TYPE_UC, .uc=uc }; }
VArg varg_from_us (short us) { return (VArg) { VARG_TYPE_US, .us=us }; }
VArg varg_from_ui (int ui) { return (VArg) { VARG_TYPE_UI, .ui=ui }; }
VArg varg_from_ul (long ul) { return (VArg) { VARG_TYPE_UL, .ul=ul }; }
VArg varg_from_ull (long long ull) { return (VArg) { VARG_TYPE_ULL, .ull=ull }; }
VArg varg_from_f (float f) { return (VArg) { VARG_TYPE_F, .f=f }; }
VArg varg_from_d (double d) { return (VArg) { VARG_TYPE_D, .d=d }; }
VArg varg_from_b (bool b) { return (VArg) { VARG_TYPE_B, .b=b }; }
VArg varg_from_str (HTTP_String str) { return (VArg) { VARG_TYPE_STR, .str=str }; }
+105
View File
@@ -0,0 +1,105 @@
#include <stdbool.h>
#include "chttp.h"
typedef enum {
VARG_TYPE_C,
VARG_TYPE_S,
VARG_TYPE_I,
VARG_TYPE_L,
VARG_TYPE_LL,
VARG_TYPE_SC,
VARG_TYPE_SS,
VARG_TYPE_SI,
VARG_TYPE_SL,
VARG_TYPE_SLL,
VARG_TYPE_UC,
VARG_TYPE_US,
VARG_TYPE_UI,
VARG_TYPE_UL,
VARG_TYPE_ULL,
VARG_TYPE_F,
VARG_TYPE_D,
VARG_TYPE_B,
VARG_TYPE_STR,
} VArgType;
typedef struct {
VArgType type;
union {
char c;
short s;
int i;
long l;
long long ll;
signed char sc;
signed short ss;
signed int si;
signed long sl;
signed long long sll;
unsigned char uc;
unsigned short us;
unsigned int ui;
unsigned long ul;
unsigned long long ull;
float f;
double d;
bool b;
HTTP_String str;
};
} VArg;
VArg varg_from_c (char c);
VArg varg_from_s (short s);
VArg varg_from_i (int i);
VArg varg_from_l (long l);
VArg varg_from_ll (long long ll);
VArg varg_from_sc (char sc);
VArg varg_from_ss (short ss);
VArg varg_from_si (int si);
VArg varg_from_sl (long sl);
VArg varg_from_sll (long long sll);
VArg varg_from_uc (char uc);
VArg varg_from_us (short us);
VArg varg_from_ui (int ui);
VArg varg_from_ul (long ul);
VArg varg_from_ull (long long ull);
VArg varg_from_f (float f);
VArg varg_from_d (double d);
VArg varg_from_b (bool b);
VArg varg_from_str (HTTP_String str);
#define VARG(X) (_Generic((X), \
char : varg_from_c, \
short : varg_from_s, \
int : varg_from_i, \
long : varg_from_l, \
long long : varg_from_ll, \
signed char : varg_from_sc, \
/*signed short : varg_from_ss,*/ \
/*signed int : varg_from_si,*/ \
/*signed long : varg_from_sl,*/ \
/*signed long long : varg_from_sll,*/ \
unsigned char : varg_from_uc, \
unsigned short : varg_from_us, \
unsigned int : varg_from_ui, \
unsigned long : varg_from_ul, \
unsigned long long: varg_from_ull, \
float : varg_from_f, \
double : varg_from_d, \
bool : varg_from_b, \
HTTP_String : varg_from_str \
))(X)
typedef struct {
int len;
VArg *ptr;
} VArgs;
#define VARGS_1(a) (VArgs) {1, (VArg[]) { VARG(a) } }
#define VARGS_2(a, b) (VArgs) {2, (VArg[]) { VARG(a), VARG(b) } }
#define VARGS_3(a, b, c) (VArgs) {3, (VArg[]) { VARG(a), VARG(b), VARG(c) } }
#define VARGS_4(a, b, c, d) (VArgs) {4, (VArg[]) { VARG(a), VARG(b), VARG(c), VARG(d) } }
#define VARGS_5(a, b, c, d, e) (VArgs) {5, (VArg[]) { VARG(a), VARG(b), VARG(c), VARG(d), VARG(e) } }
#define DISPATCH__(_1, _2, _3, _4, _5, NAME, ...) NAME
#define VARGS(...) DISPATCH__(__VA_ARGS__, VARGS_5, VARGS_4, VARGS_3, VARGS_2, VARGS_1)(__VA_ARGS__)