From 7baba9657d42041bef5aaef22a4f641c3858be96 Mon Sep 17 00:00:00 2001 From: Francesco Cozzuto Date: Sat, 16 Aug 2025 17:28:38 +0200 Subject: [PATCH] Template and prepared statement caching --- 3p/WL.c | 3 - 3p/WL.h | 1 + Makefile | 2 +- pages/index.wl | 13 +- pages/login.wl | 5 +- pages/login_and_signup_style.wl | 12 + pages/page.wl | 9 +- pages/post.wl | 36 +- pages/signup.wl | 5 +- src/main.c | 697 +++++++++++++++----------------- src/sqlite3utils.c | 165 ++++++-- src/sqlite3utils.h | 20 +- src/template.c | 407 +++++++++++++++++++ src/template.h | 16 + src/variadic.h | 5 + 15 files changed, 963 insertions(+), 433 deletions(-) create mode 100644 src/template.c create mode 100644 src/template.h diff --git a/3p/WL.c b/3p/WL.c index 46a21df..c18c4ff 100644 --- a/3p/WL.c +++ b/3p/WL.c @@ -2684,9 +2684,6 @@ static void walk_expr_node(Codegen *cg, Node *node, bool one) case NODE_OPER_SHOVEL: { - Node *dst = node->left; - Node *src = node->right; - walk_expr_node(cg, node->left, true); cg_push_scope(cg, SCOPE_ASSIGNMENT); diff --git a/3p/WL.h b/3p/WL.h index d2657eb..ceebb50 100644 --- a/3p/WL.h +++ b/3p/WL.h @@ -1,4 +1,5 @@ #include +#include typedef struct WL_Runtime WL_Runtime; typedef struct WL_Compiler WL_Compiler; diff --git a/Makefile b/Makefile index fa67d14..69ceaca 100644 --- a/Makefile +++ b/Makefile @@ -20,4 +20,4 @@ sqlite3.o: 3p/sqlite3.c gcc -o $@ -c $< clean: - rm *.o *.out *.exe \ No newline at end of file + rm *.o *.out *.exe diff --git a/pages/index.wl b/pages/index.wl index 5f9e830..1476628 100644 --- a/pages/index.wl +++ b/pages/index.wl @@ -1,6 +1,6 @@ include "pages/page.wl" -let posts = $query("SELECT id, title, is_link, content, 0 as num_comments, CURRENT_TIMESTAMP as date FROM Posts") +let posts = $query("SELECT P.id, P.title, P.is_link, P.content, (SELECT COUNT(*) FROM Comments as C WHERE c.parent_post=P.id) as num_comments, CURRENT_TIMESTAMP as date FROM Posts as P") if posts == none: posts = [] @@ -30,13 +30,20 @@ let style = width: 250px; text-align: right; } + + #no-posts { + margin: 60px auto; + width: 100%; + text-align: center; + color: #7A5F2A; + } let main =
\if len(posts) == 0: -
There are no posts yet!
+
There are no posts yet!
\for post in posts: { @@ -51,7 +58,7 @@ let main = \post.title
- \post.date | \post.num_comments comments + \post.date | \post.num_comments comments
} diff --git a/pages/login.wl b/pages/login.wl index 459cb29..6a5913b 100644 --- a/pages/login.wl +++ b/pages/login.wl @@ -4,7 +4,10 @@ include "pages/login_and_signup_style.wl" let main =
Welcome back! -
+ +
+ + diff --git a/pages/login_and_signup_style.wl b/pages/login_and_signup_style.wl index 2834852..5a20fd6 100644 --- a/pages/login_and_signup_style.wl +++ b/pages/login_and_signup_style.wl @@ -59,4 +59,16 @@ let style = .form-links a:hover { color: #1D2B42; } + + #response { + max-width: 300px; + margin: auto; + } + #response .error { + margin-top: 30px; + border-radius: 3px; + border: 1px solid #E44C82; + background: #F295B5; + padding: 5px 10px; + } \ No newline at end of file diff --git a/pages/page.wl b/pages/page.wl index fee84e9..38599dd 100644 --- a/pages/page.wl +++ b/pages/page.wl @@ -43,15 +43,16 @@ procedure page(title, login_user_id, style, main) } \(style) +
diff --git a/pages/signup.wl b/pages/signup.wl index c08c6a1..d8e6569 100644 --- a/pages/signup.wl +++ b/pages/signup.wl @@ -4,7 +4,10 @@ include "pages/login_and_signup_style.wl" let main =
Welcome! - + +
+ + diff --git a/src/main.c b/src/main.c index 186efbe..372f293 100644 --- a/src/main.c +++ b/src/main.c @@ -6,10 +6,24 @@ #include #include "sqlite3.h" #include "chttp.h" -#include "wl.h" #include "sqlite3utils.h" +#include "template.h" -sqlite3 *db; +#define WL_STR(X) ((WL_String) { (X), (int) sizeof(X)-1}) + +#define HTML_STR(X) html_str(HTTP_STR(#X)) + +static HTTP_String html_str(HTTP_String str) +{ + str = http_trim(str); + if (str.len > 0 && str.ptr[0] == '(') { + str.ptr++; + str.len--; + } + if (str.len > 0 && str.ptr[str.len-1] == ')') + str.len--; + return str; +} int load_file(char *file, char **data, long *size) { @@ -29,309 +43,64 @@ int load_file(char *file, char **data, long *size) return 0; } -static int query_routine(WL_Runtime *rt) -{ - int num_args = wl_arg_count(rt); - if (num_args == 0) - return 0; - - WL_String format; - if (!wl_arg_str(rt, 0, &format)) - return -1; - - sqlite3_stmt *stmt; - int ret = sqlite3_prepare_v2(db, format.ptr, format.len, &stmt, 0); - if (ret != SQLITE_OK) { - fprintf(stderr, "Failed to prepare statement: %s\n", sqlite3_errmsg(db)); - return -1; - } - - for (int i = 1; i < num_args; i++) { - - int64_t ival; - double fval; - WL_String str; - - if (0) {} - else if (wl_arg_none(rt, i)) - ret = sqlite3_bind_null (stmt, i); - else if (wl_arg_s64(rt, i, &ival)) - ret = sqlite3_bind_int64 (stmt, i, ival); - else if (wl_arg_f64(rt, i, &fval)) - ret = sqlite3_bind_double(stmt, i, fval); - else if (wl_arg_str(rt, i, &str)) - ret = sqlite3_bind_text (stmt, i, str.ptr, str.len, NULL); - else assert(0); - - if (ret != SQLITE_OK) { - fprintf(stderr, "Failed to prepare statement: %s\n", sqlite3_errmsg(db)); - sqlite3_finalize(stmt); - return -1; - } - } - - wl_push_array(rt, 0); - - while (sqlite3_step(stmt) == SQLITE_ROW) { - - int num_cols = sqlite3_column_count(stmt); - if (num_cols < 0) { - assert(0); // TODO - } - - wl_push_map(rt, num_cols); - - for (int i = 0; i < num_cols; i++) { - ret = sqlite3_column_type(stmt, i); - switch (ret) { - - case SQLITE_INTEGER: - { - int64_t x = sqlite3_column_int64(stmt, i); - wl_push_s64(rt, x); - } - break; - - case SQLITE_FLOAT: - { - double x = sqlite3_column_double(stmt, i); - wl_push_f64(rt, x); - } - break; - - case SQLITE_TEXT: - { - const void *x = sqlite3_column_text(stmt, i); - int n = sqlite3_column_bytes(stmt, i); - wl_push_str(rt, (WL_String) { (char*) x, n }); - } - break; - - case SQLITE_BLOB: - { - const void *x = sqlite3_column_blob(stmt, i); - int n = sqlite3_column_bytes(stmt, i); - wl_push_str(rt, (WL_String) { (char*) x, n }); - } - break; - - case SQLITE_NULL: - { - wl_push_none(rt); - } - break; - } - - const char *name = sqlite3_column_name(stmt, i); - - wl_push_str(rt, (WL_String) { (char*) name, strlen(name) }); - wl_insert(rt); - } - - wl_append(rt); - } - - sqlite3_finalize(stmt); - return 0; -} - -int evaluate_template(HTTP_ResponseBuilder builder, - WL_Program program, WL_Arena arena, int user_id, int post_id) -{ - //wl_dump_program(program); - - WL_Runtime *rt = wl_runtime_init(&arena, program); - if (rt == NULL) - return -1; - - for (;;) { - - WL_EvalResult result = wl_runtime_eval(rt); - switch (result.type) { - - case WL_EVAL_DONE: - return 0; - - case WL_EVAL_ERROR: - printf("Error: %s\n", wl_runtime_error(rt).ptr); // TODO - return -1; - - case WL_EVAL_SYSVAR: - if (wl_streq(result.str, "login_user_id", -1)) { - - if (user_id < 0) - wl_push_none(rt); - else - wl_push_s64(rt, user_id); - - } else if (wl_streq(result.str, "post_id", -1)) { - - if (post_id < 0) - wl_push_none(rt); - else - wl_push_s64(rt, post_id); - } - break; - - case WL_EVAL_SYSCALL: - if (wl_streq(result.str, "query", -1)) { - query_routine(rt); - break; - } - break; - - case WL_EVAL_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_AddResult 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_compiler_add(compiler, (WL_String) { file_data, file_size }); - - loaded_files[num_loaded_files++] = file_data; - - if (result.type == WL_ADD_ERROR) { - printf("Compilation of '%.*s' failed\n", path.len, path.ptr); - break; - } - - if (result.type == WL_ADD_LINK) - break; - - assert(result.type == WL_ADD_AGAIN); - path = result.path; - } - - WL_Program program; - int ret = wl_compiler_link(compiler, &program); - - for (int i = 0; i < num_loaded_files; i++) - free(loaded_files[i]); - - if (ret < 0) { - WL_String err = wl_compiler_error(compiler); - http_response_builder_undo(builder); - http_response_builder_status(builder, 500); - http_response_builder_body(builder, (HTTP_String) { err.ptr, err.len }); - http_response_builder_done(builder); - return; - } - - if (evaluate_template(builder, program, arena, user_id, post_id) < 0) { - http_response_builder_undo(builder); - http_response_builder_status(builder, 500); - http_response_builder_done(builder); - return; - } - - http_response_builder_done(builder); -} - -int create_user(HTTP_String name, HTTP_String email, HTTP_String pass) +int create_user(SQLiteCache *dbcache, HTTP_String name, HTTP_String email, HTTP_String pass) { sqlite3_stmt *stmt; - int ret = sqlite3utils_prepare(db, &stmt, + int ret = sqlite3utils_prepare_and_bind(dbcache, &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)); + fprintf(stderr, "Failed to prepare statement: %s\n", sqlite3_errmsg(sqlite_cache_getdb(dbcache))); // TODO: What if the user exists? - sqlite3_finalize(stmt); + sqlite3_reset(stmt); return -500; } - int64_t tmp = sqlite3_last_insert_rowid(db); + int64_t tmp = sqlite3_last_insert_rowid(sqlite_cache_getdb(dbcache)); if (tmp < 0 || tmp > INT_MAX) { - sqlite3_finalize(stmt); + sqlite3_reset(stmt); return -500; } int user_id = (int) tmp; - ret = sqlite3_finalize(stmt); + ret = sqlite3_reset(stmt); if (ret != SQLITE_OK) { - fprintf(stderr, "Failed to prepare statement: %s\n", sqlite3_errmsg(db)); - sqlite3_finalize(stmt); + fprintf(stderr, "Failed to prepare statement: %s\n", sqlite3_errmsg(sqlite_cache_getdb(dbcache))); + sqlite3_reset(stmt); return -500; } return user_id; } -int user_exists(HTTP_String name, HTTP_String pass) +int user_exists(SQLiteCache *dbcache, HTTP_String name, HTTP_String pass) { sqlite3_stmt *stmt; - int ret = sqlite3utils_prepare(db, &stmt, + int ret = sqlite3utils_prepare_and_bind(dbcache, &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); + sqlite3_reset(stmt); return -404; } if (ret != SQLITE_ROW) { - sqlite3_finalize(stmt); + sqlite3_reset(stmt); return -500; } int user_id = sqlite3_column_int(stmt, 0); if (user_id < 0) { - sqlite3_finalize(stmt); + sqlite3_reset(stmt); return -500; } - ret = sqlite3_finalize(stmt); + ret = sqlite3_reset(stmt); if (ret != SQLITE_OK) return -500; @@ -386,7 +155,7 @@ HTTP_String http_getcookie(HTTP_Request *req, HTTP_String name) return HTTP_STR(""); } -void *alloc(WL_Arena *arena, int num, int align) +static void *alloc(WL_Arena *arena, int num, int align) { int pad = -(uintptr_t) (arena->ptr + arena->cur) & (align-1); if (arena->len - arena->cur < num + pad) @@ -493,6 +262,28 @@ HTTP_String http_getparam(HTTP_String body, HTTP_String str, WL_Arena *arena) return HTTP_STR(""); } +static bool is_digit(char c) +{ + return c >= '0' && c <= '9'; +} + +int http_getparami(HTTP_String body, HTTP_String str, WL_Arena *arena) +{ + HTTP_String out = http_getparam(body, str, arena); + if (out.len == 0 || !is_digit(out.ptr[0])) + return -1; + int cur = 0; + int buf = 0; + do { + int d = out.ptr[cur++] - '0'; + if (buf > (INT_MAX - d) / 10) + return -1; + buf = buf * 10 + d; + } while (cur < out.len && is_digit(out.ptr[cur])); + + return buf; +} + #define SESSION_LIMIT 1024 #define USERNAME_LIMIT 64 @@ -593,6 +384,7 @@ int main(void) printf("%s\n", sqlite3_libversion()); + sqlite3 *db; int ret = sqlite3_open(":memory:", &db); if (ret != SQLITE_OK) { fprintf(stderr, "Cannot open database: %s\n", sqlite3_errmsg(db)); @@ -617,6 +409,10 @@ int main(void) free(schema_data); + TemplateCache *tpcache = template_cache_init(4); + SQLiteCache *dbcache = sqlite_cache_init(db, 5); + + HTTP_String addr = HTTP_STR("127.0.0.1"); uint16_t port = 8080; @@ -667,15 +463,25 @@ int main(void) if (http_streq(path, HTTP_STR("/api/login"))) { if (req->method != HTTP_METHOD_POST) { - http_response_builder_status(builder, 405); + http_response_builder_status(builder, 200); + http_response_builder_body(builder, HTML_STR(( +
+ Invalid request method +
+ ))); 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_status(builder, 200); + http_response_builder_header(builder, HTTP_STR("HX-Redirect: /index")); + http_response_builder_body(builder, HTML_STR(( +
+ You are already logged in +
+ ))); http_response_builder_done(builder); continue; } @@ -687,14 +493,24 @@ int main(void) pass = http_trim(pass); if (!valid_name(name) || !valid_pass(pass)) { - http_response_builder_status(builder, 400); + http_response_builder_status(builder, 200); + http_response_builder_body(builder, HTML_STR(( +
+ Invalid credentials +
+ ))); http_response_builder_done(builder); continue; } - int ret = user_exists(name, pass); + int ret = user_exists(dbcache, name, pass); if (ret < 0) { - http_response_builder_status(builder, -ret); + http_response_builder_status(builder, 200); + http_response_builder_body(builder, HTML_STR(( +
+ Invalid credentials +
+ ))); http_response_builder_done(builder); continue; } @@ -702,7 +518,12 @@ int main(void) int sess_id = session_set_add(&sessions, user_id); if (sess_id < 0) { - http_response_builder_status(builder, 500); + http_response_builder_status(builder, 200); + http_response_builder_body(builder, HTML_STR(( +
+ Internal error +
+ ))); http_response_builder_done(builder); continue; } @@ -710,28 +531,48 @@ int main(void) 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_status(builder, 200); + http_response_builder_body(builder, HTML_STR(( +
+ Internal error +
+ ))); http_response_builder_done(builder); continue; } - http_response_builder_status(builder, 303); // TODO: Whats the correct code here? + http_response_builder_status(builder, 200); // 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_header(builder, HTTP_STR("HX-Redirect: /index")); + http_response_builder_body(builder, HTML_STR(( +
+ Welcome back! +
+ ))); 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_status(builder, 200); + http_response_builder_body(builder, HTML_STR(( +
+ Invalid request method +
+ ))); 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_status(builder, 200); + http_response_builder_header(builder, HTTP_STR("HX-Redirect: /index")); + http_response_builder_body(builder, HTML_STR(( +
+ You are already logged in +
+ ))); http_response_builder_done(builder); continue; } @@ -742,20 +583,35 @@ int main(void) HTTP_String pass2 = http_getparam(req->body, HTTP_STR("password2"), &arena); if (!valid_name(name) || !valid_email(email) || !valid_pass(pass1)) { - http_response_builder_status(builder, 400); + http_response_builder_status(builder, 200); + http_response_builder_body(builder, HTML_STR(( +
+ Invalid credentials +
+ ))); http_response_builder_done(builder); continue; } if (!http_streq(pass1, pass2)) { - http_response_builder_status(builder, 400); + http_response_builder_status(builder, 200); + http_response_builder_body(builder, HTML_STR(( +
+ The password was repeated incorrectly +
+ ))); http_response_builder_done(builder); continue; } - int ret = create_user(name, email, pass1); + int ret = create_user(dbcache, name, email, pass1); if (ret < 0) { - http_response_builder_status(builder, -ret); + http_response_builder_status(builder, 200); + http_response_builder_body(builder, HTML_STR(( +
+ Internal error +
+ ))); http_response_builder_done(builder); continue; } @@ -763,7 +619,12 @@ int main(void) int sess_id = session_set_add(&sessions, user_id); if (sess_id < 0) { - http_response_builder_status(builder, 500); + http_response_builder_status(builder, 200); + http_response_builder_body(builder, HTML_STR(( +
+ Internal error +
+ ))); http_response_builder_done(builder); continue; } @@ -771,14 +632,24 @@ int main(void) 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_status(builder, 200); + http_response_builder_body(builder, HTML_STR(( +
+ Internal error +
+ ))); http_response_builder_done(builder); continue; } - http_response_builder_status(builder, 303); // TODO: Whats the correct code here? + http_response_builder_status(builder, 200); // 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_header(builder, HTTP_STR("HX-Redirect: /index")); + http_response_builder_body(builder, HTML_STR(( +
+ Welcome! +
+ ))); http_response_builder_done(builder); } else if (http_streq(path, HTTP_STR("/api/logout"))) { @@ -814,12 +685,37 @@ int main(void) link = http_trim(link); content = http_trim(content); - if (!valid_post_title(title) || !valid_link(link) || !valid_post_content(content)) { - assert(0); // TODO + if (!valid_post_title(title)) { + http_response_builder_status(builder, 200); + http_response_builder_body(builder, HTML_STR(( +
+ Invalid title +
+ ))); + http_response_builder_done(builder); + continue; } - if (content.len == 0 && link.len == 0) { - assert(0); // TODO + if (!valid_link(link)) { + http_response_builder_status(builder, 200); + http_response_builder_body(builder, HTML_STR(( +
+ Invalid link +
+ ))); + http_response_builder_done(builder); + continue; + } + + if (!valid_post_content(content)) { + http_response_builder_status(builder, 200); + http_response_builder_body(builder, HTML_STR(( +
+ Invalid content +
+ ))); + http_response_builder_done(builder); + continue; } bool is_link = false; @@ -829,31 +725,68 @@ int main(void) } sqlite3_stmt *stmt; - int ret = sqlite3utils_prepare(db, &stmt, "INSERT INTO Posts(author, title, is_link, content) VALUES (?, ?, ?, ?)", user_id, title, is_link, content); + int ret = sqlite3utils_prepare_and_bind(dbcache, &stmt, "INSERT INTO Posts(author, title, is_link, content) VALUES (?, ?, ?, ?)", user_id, title, is_link, content); if (ret != SQLITE_OK) { - assert(0); // TODO + http_response_builder_status(builder, 200); + http_response_builder_body(builder, HTML_STR(( +
+ Internal error +
+ ))); + http_response_builder_done(builder); + continue; } ret = sqlite3_step(stmt); if (ret != SQLITE_DONE) { - assert(0); // TODO - } - - ret = sqlite3_finalize(stmt); - if (ret != SQLITE_OK) { - assert(0); // TODO + sqlite3_reset(stmt); + http_response_builder_status(builder, 200); + http_response_builder_body(builder, HTML_STR(( +
+ Internal error +
+ ))); + http_response_builder_done(builder); + continue; } int64_t tmp = sqlite3_last_insert_rowid(db); if (tmp < 0 || tmp > INT_MAX) { - assert(0); // TODO + sqlite3_reset(stmt); + http_response_builder_status(builder, 200); + http_response_builder_body(builder, HTML_STR(( +
+ Internal error +
+ ))); + http_response_builder_done(builder); + continue; } int post_id = (int) tmp; + ret = sqlite3_reset(stmt); + if (ret != SQLITE_OK) { + http_response_builder_status(builder, 200); + http_response_builder_body(builder, HTML_STR(( +
+ Internal error +
+ ))); + http_response_builder_done(builder); + continue; + } + 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, 200); + http_response_builder_body(builder, HTML_STR(( +
+ Internal error +
+ ))); + http_response_builder_done(builder); + continue; } http_response_builder_status(builder, 303); @@ -863,85 +796,96 @@ int main(void) } else if (http_streq(path, HTTP_STR("/api/comment"))) { if (req->method != HTTP_METHOD_POST) { - http_response_builder_status(builder, 405); + http_response_builder_status(builder, 200); + http_response_builder_body(builder, HTML_STR(( +
+ Invalid request method +
+ ))); 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_status(builder, 200); + http_response_builder_body(builder, HTML_STR(( +
+ You are not logged in +
+ ))); http_response_builder_done(builder); continue; } - HTTP_String parent_post_str = http_getparam(req->body, HTTP_STR("parent_post"), &arena); - HTTP_String parent_comment_str = http_getparam(req->body, HTTP_STR("parent_comment"), &arena); - HTTP_String content = http_getparam(req->body, HTTP_STR("content"), &arena); - - int parent_post; - { - char buf[32]; - if (parent_post_str.len >= (int) sizeof(buf)) { - assert(0); // TODO - } - memcpy(buf, parent_post_str.ptr, parent_post_str.len); - buf[parent_post_str.len] = '\0'; - - parent_post = atoi(buf); - if (parent_post == 0) { - assert(0); // TODO - } - } - - int parent_comment; - { - char buf[32]; - if (parent_comment_str.len >= (int) sizeof(buf)) - parent_comment = -1; - else { - memcpy(buf, parent_comment_str.ptr, parent_comment_str.len); - buf[parent_comment_str.len] = '\0'; - - parent_comment = atoi(buf); - if (parent_comment == 0) - parent_comment = -1; - } - } + int parent_post = http_getparami(req->body, HTTP_STR("parent_post"), &arena); + int parent_comment = http_getparami(req->body, HTTP_STR("parent_comment"), &arena); + HTTP_String content = http_getparam (req->body, HTTP_STR("content"), &arena); content = http_trim(content); if (!valid_comment_content(content)) { - assert(0); // TODO - } - - if (content.len == 0) { - assert(0); // TODO + http_response_builder_status(builder, 200); + http_response_builder_body(builder, HTML_STR(( +
+ Invalid content +
+ ))); + http_response_builder_done(builder); + continue; } int ret; sqlite3_stmt *stmt; if (parent_comment == -1) - ret = sqlite3utils_prepare(db, &stmt, "INSERT INTO Comments(author, content, parent_post) VALUES (?, ?, ?)", user_id, content, parent_post); + ret = sqlite3utils_prepare_and_bind(dbcache, &stmt, "INSERT INTO Comments(author, content, parent_post) VALUES (?, ?, ?)", user_id, content, parent_post); else - ret = sqlite3utils_prepare(db, &stmt, "INSERT INTO Comments(author, content, parent_post, parent_comment) VALUES (?, ?, ?, ?)", user_id, content, parent_post, parent_comment); + ret = sqlite3utils_prepare_and_bind(dbcache, &stmt, "INSERT INTO Comments(author, content, parent_post, parent_comment) VALUES (?, ?, ?, ?)", user_id, content, parent_post, parent_comment); if (ret != SQLITE_OK) { - assert(0); // TODO + http_response_builder_status(builder, 200); + http_response_builder_body(builder, HTML_STR(( +
+ Internal error +
+ ))); + http_response_builder_done(builder); + continue; } ret = sqlite3_step(stmt); if (ret != SQLITE_DONE) { - assert(0); // TODO + sqlite3_reset(stmt); + http_response_builder_status(builder, 200); + http_response_builder_body(builder, HTML_STR(( +
+ Internal error +
+ ))); + http_response_builder_done(builder); + continue; } - ret = sqlite3_finalize(stmt); + ret = sqlite3_reset(stmt); if (ret != SQLITE_OK) { - assert(0); // TODO + http_response_builder_status(builder, 200); + http_response_builder_body(builder, HTML_STR(( +
+ Internal error +
+ ))); + http_response_builder_done(builder); + continue; } char location[1<<9]; ret = snprintf(location, sizeof(location), "Location: /post?id=%d", parent_post); if (ret < 0 || ret >= (int) sizeof(location)) { - assert(0); // TODO + http_response_builder_status(builder, 200); + http_response_builder_body(builder, HTML_STR(( +
+ Internal error +
+ ))); + http_response_builder_done(builder); + continue; } http_response_builder_status(builder, 303); @@ -950,7 +894,7 @@ int main(void) } else if (http_streq(path, HTTP_STR("/index"))) { - evaluate_template_2(builder, arena, "pages/index.wl", user_id, -1); + template_eval(builder, 200, WL_STR("pages/index.wl"), tpcache, &arena, dbcache, user_id, -1); } else if (http_streq(path, HTTP_STR("/write"))) { @@ -962,7 +906,7 @@ int main(void) continue; } - evaluate_template_2(builder, arena, "pages/write.wl", user_id, -1); + template_eval(builder, 200, WL_STR("pages/write.wl"), tpcache, &arena, dbcache, user_id, -1); } else if (http_streq(path, HTTP_STR("/login"))) { @@ -974,7 +918,7 @@ int main(void) continue; } - evaluate_template_2(builder, arena, "pages/login.wl", user_id, -1); + template_eval(builder, 200, WL_STR("pages/login.wl"), tpcache, &arena, dbcache, user_id, -1); } else if (http_streq(path, HTTP_STR("/signup"))) { @@ -986,60 +930,61 @@ int main(void) continue; } - evaluate_template_2(builder, arena, "pages/signup.wl", user_id, -1); + template_eval(builder, 200, WL_STR("pages/signup.wl"), tpcache, &arena, dbcache, user_id, -1); } else if (http_streq(path, HTTP_STR("/post"))) { - HTTP_String idstr = http_getparam(req->url.query, HTTP_STR("id"), &arena); - - char buf[32]; - if (idstr.len == 0 || idstr.len >= (int) sizeof(buf)) { - printf("post id [%.*s] is not defined\n", idstr.len, idstr.ptr); // TODO - 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) { - printf("Invalid post id [%s]\n", buf); - evaluate_template_2(builder, arena, "pages/notfound.wl", user_id, -1); + int post_id = http_getparami(req->url.query, HTTP_STR("id"), &arena); + if (post_id < 0) { + template_eval(builder, 404, WL_STR("pages/notfound.wl"), tpcache, &arena, dbcache, user_id, -1); continue; } sqlite3_stmt *stmt; - int ret = sqlite3utils_prepare(db, &stmt, "SELECT COUNT(*) FROM Posts WHERE id=?", post_id); + int ret = sqlite3utils_prepare_and_bind(dbcache, &stmt, "SELECT COUNT(*) FROM Posts WHERE id=?", post_id); if (ret != SQLITE_OK) { - assert(0); // TODO + http_response_builder_status(builder, 500); + http_response_builder_done(builder); + continue; } ret = sqlite3_step(stmt); if (ret != SQLITE_ROW) { - assert(0); // TODO + sqlite3_reset(stmt); + http_response_builder_status(builder, 500); + http_response_builder_done(builder); + continue; } int64_t num = sqlite3_column_int64(stmt, 0); - ret = sqlite3_finalize(stmt); + ret = sqlite3_reset(stmt); if (ret != SQLITE_OK) { - assert(0); // TODO + http_response_builder_status(builder, 500); + http_response_builder_done(builder); + continue; } if (num < 0) { - assert(0); // TODO + http_response_builder_status(builder, 500); + http_response_builder_done(builder); + continue; } - 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); + if (num == 0) { + template_eval(builder, 404, WL_STR("pages/notfound.wl"), tpcache, &arena, dbcache, user_id, -1); + continue; + } + + template_eval(builder, 200, WL_STR("pages/post.wl"), tpcache, &arena, dbcache, user_id, post_id); } else { - evaluate_template_2(builder, arena, "pages/notfound.wl", user_id, -1); + template_eval(builder, 404, WL_STR("pages/notfound.wl"), tpcache, &arena, dbcache, user_id, -1); } } + sqlite_cache_free(dbcache); + template_cache_free(tpcache); sqlite3_close(db); http_server_free(server); http_global_free(); diff --git a/src/sqlite3utils.c b/src/sqlite3utils.c index 050de67..cad2b40 100644 --- a/src/sqlite3utils.c +++ b/src/sqlite3utils.c @@ -1,42 +1,155 @@ #include #include +#include +#include #include "sqlite3utils.h" -int sqlite3utils_prepare_impl(sqlite3 *db, sqlite3_stmt **pstmt, const char *fmt, VArgs args) +typedef struct { + char *str; + int len; + sqlite3_stmt *stmt; +} Prepped; + +struct SQLiteCache { + sqlite3 *db; + int count; + int capacity_log2; + Prepped items[]; +}; + +SQLiteCache *sqlite_cache_init(sqlite3 *db, int capacity_log2) +{ + SQLiteCache *cache = malloc(sizeof(SQLiteCache) + (1 << capacity_log2) * sizeof(Prepped)); + if (cache == NULL) + return NULL; + + cache->db = db; + cache->count = 0; + cache->capacity_log2 = capacity_log2; + + for (int i = 0; i < (1 << capacity_log2); i++) + cache->items[i].stmt = NULL; + + return cache; +} + +void sqlite_cache_free(SQLiteCache *cache) +{ + for (int i = 0; i < (1 << cache->capacity_log2); i++) { + sqlite3_stmt *stmt = cache->items[i].stmt; + if (stmt) { + free(cache->items[i].str); + sqlite3_finalize(stmt); + } + } + free(cache); +} + +sqlite3 *sqlite_cache_getdb(SQLiteCache *cache) +{ + return cache->db; +} + +static unsigned long djb2(char *src, int len) +{ + char *ptr = src; + char *end = src + len; + + unsigned long hash = 5381; + int c; + while (ptr < end && (c = *ptr++)) + hash = ((hash << 5) + hash) + c; // hash * 33 + c + return hash; +} + +static int lookup(SQLiteCache *cache, char *fmt, int fmtlen) +{ + int mask = (1 << cache->capacity_log2) - 1; + int hash = djb2(fmt, fmtlen); + int i = hash & mask; + int perturb = hash; + for (;;) { + + if (cache->items[i].stmt == NULL) + return i; + + if (cache->items[i].len == fmtlen && !memcmp(cache->items[i].str, fmt, fmtlen)) + return i; + + perturb >>= 5; + i = (i * 5 + 1 + perturb) & mask; + } + + return -1; +} + +int sqlite3utils_prepare(SQLiteCache *cache, sqlite3_stmt **pstmt, const char *fmt, int fmtlen) +{ + if (fmtlen < 0) + fmtlen = strlen(fmt); + + int i = lookup(cache, fmt, fmtlen); + if (cache->items[i].stmt == NULL) { + + printf("Preparing statement [%.*s]\n", fmtlen, fmt); // TODO + + sqlite3_stmt *stmt; + int ret = sqlite3_prepare_v2(cache->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; + } + + char *cpy = malloc(fmtlen); + if (cpy == NULL) { + sqlite3_finalize(stmt); + return SQLITE_NOMEM; + } + memcpy(cpy, fmt, fmtlen); + + cache->items[i].str = cpy; + cache->items[i].len = fmtlen; + cache->items[i].stmt = stmt; + } + sqlite3_stmt *stmt = cache->items[i].stmt; + + *pstmt = stmt; + return SQLITE_OK; +} + +int sqlite3utils_prepare_and_bind_impl(SQLiteCache *cache, + 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__); + int ret = sqlite3utils_prepare(cache, &stmt, fmt, strlen(fmt)); + if (ret != SQLITE_OK) 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; + 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); + sqlite3_reset(stmt); return ret; } } diff --git a/src/sqlite3utils.h b/src/sqlite3utils.h index 15988cc..321ef55 100644 --- a/src/sqlite3utils.h +++ b/src/sqlite3utils.h @@ -1,5 +1,21 @@ +#ifndef SQLITE3UTILS_INCLUDED +#define SQLITE3UTILS_INCLUDED + #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__)) +#define sqlite3utils_prepare_and_bind(cache, pstmt, fmt, ...) sqlite3utils_prepare_and_bind_impl((cache), (pstmt), (fmt), VARGS(__VA_ARGS__)) + +typedef struct SQLiteCache SQLiteCache; + +SQLiteCache* sqlite_cache_init(sqlite3 *db, int capacity_log2); +void sqlite_cache_free(SQLiteCache *cache); +sqlite3* sqlite_cache_getdb(SQLiteCache *cache); + +int sqlite3utils_prepare(SQLiteCache *cache, + sqlite3_stmt **pstmt, const char *fmt, int fmtlen); + +int sqlite3utils_prepare_and_bind_impl(SQLiteCache *cache, + sqlite3_stmt **pstmt, const char *fmt, VArgs args); + +#endif // SQLITE3UTILS_INCLUDED \ No newline at end of file diff --git a/src/template.c b/src/template.c new file mode 100644 index 0000000..d3b6cce --- /dev/null +++ b/src/template.c @@ -0,0 +1,407 @@ +#include +#include +#include +#include +#include +#include "template.h" +#include "sqlite3utils.h" + +#define TRACE(...) {} +//#define TRACE(fmt, ...) printf((fmt "\n"), ## __VA_ARGS__); + +typedef struct CachedProgram CachedProgram; +struct CachedProgram { + char path[1<<8]; + int pathlen; + WL_Program program; +}; + +struct TemplateCache { + int count; + int capacity_log2; + CachedProgram pool[]; +}; + +TemplateCache *template_cache_init(int capacity_log2) +{ + TemplateCache *cache = malloc(sizeof(TemplateCache) + (1 << capacity_log2) * sizeof(CachedProgram)); + if (cache == NULL) + return NULL; + + cache->count = 0; + cache->capacity_log2 = capacity_log2; + + for (int i = 0; i < (1 << capacity_log2); i++) + cache->pool[i].pathlen = -1; + return cache; +} + +void template_cache_free(TemplateCache *cache) +{ + free(cache); +} + +static unsigned long djb2(WL_String str) +{ + char *ptr = str.ptr; + char *end = str.ptr + str.len; + + unsigned long hash = 5381; + int c; + while (ptr < end && (c = *ptr++)) + hash = ((hash << 5) + hash) + c; // hash * 33 + c + return hash; +} + +static int lookup(TemplateCache *cache, WL_String path) +{ + int mask = (1 << cache->capacity_log2) - 1; + int hash = djb2(path); + int i = hash & mask; + int perturb = hash; + for (;;) { + + if (cache->pool[i].pathlen == -1) + return i; + + if (wl_streq(path, cache->pool[i].path, cache->pool[i].pathlen)) + return i; + + perturb >>= 5; + i = (i * 5 + 1 + perturb) & mask; + } + + return -1; +} + +typedef struct LoadedFile LoadedFile; +struct LoadedFile { + LoadedFile* next; + int len; + char data[]; +}; + +static LoadedFile *load_file(WL_String path, WL_Arena *arena) +{ + char buf[1<<10]; + if (path.len >= (int) sizeof(buf)) + return NULL; + memcpy(buf, path.ptr, path.len); + buf[path.len] = '\0'; + + FILE *stream = fopen(buf, "rb"); + if (stream == NULL) + return NULL; + + int ret = fseek(stream, 0, SEEK_END); + if (ret) { + fclose(stream); + return NULL; + } + + long tmp = ftell(stream); + if (tmp < 0 || tmp > INT_MAX) { + fclose(stream); + return NULL; + } + int len = (int) tmp; + + ret = fseek(stream, 0, SEEK_SET); + if (ret) { + fclose(stream); + return NULL; + } + + LoadedFile *result = malloc(sizeof(LoadedFile) + len + 1); + if (result == NULL) { + fclose(stream); + return NULL; + } + result->next = NULL; + result->len = len; + + int read_len = fread(result->data, 1, len+1, stream); + if (read_len != len || ferror(stream) || !feof(stream)) { + fclose(stream); + free(result); + return NULL; + } + + fclose(stream); + return result; +} + +static void free_loaded_files(LoadedFile *loaded_file) +{ + while (loaded_file) { + LoadedFile *next = loaded_file->next; + free(loaded_file); + loaded_file = next; + } +} + +static int compile(WL_String path, WL_Program *program, WL_Arena *arena) +{ + WL_Compiler *compiler = wl_compiler_init(arena); + if (compiler == NULL) { + TRACE("Couldn't initialize WL compiler object"); + return -1; + } + + LoadedFile *loaded_file_head = NULL; + LoadedFile **loaded_file_tail = &loaded_file_head; + + for (int i = 0;; i++) { + + LoadedFile *loaded_file = load_file(path, arena); + if (loaded_file == NULL) { + TRACE("Couldn't load file '%.*s'", path.len, path.ptr); + free_loaded_files(loaded_file_head); + return -1; + } + + *loaded_file_tail = loaded_file; + loaded_file_tail = &loaded_file->next; + + WL_String content = { loaded_file->data, loaded_file->len }; + WL_AddResult result = wl_compiler_add(compiler, content); + + if (result.type == WL_ADD_ERROR) { + TRACE("Compilation failed (%s)", wl_compiler_error(compiler).ptr); + free_loaded_files(loaded_file_head); + return -1; + } + + if (result.type == WL_ADD_LINK) break; + + assert(result.type == WL_ADD_AGAIN); + path = result.path; + } + + int ret = wl_compiler_link(compiler, program); + if (ret < 0) { + TRACE("Compilation failed (%s)", wl_compiler_error(compiler).ptr); + return -1; + } + + free_loaded_files(loaded_file_head); + + TRACE("Compilation succeded"); + return 0; +} + +static int query_routine(WL_Runtime *rt, SQLiteCache *dbcache) +{ + int num_args = wl_arg_count(rt); + if (num_args == 0) + return 0; + + WL_String format; + if (!wl_arg_str(rt, 0, &format)) + return -1; + + sqlite3_stmt *stmt; + int ret = sqlite3utils_prepare(dbcache, &stmt, format.ptr, format.len); + if (ret != SQLITE_OK) + return -1; + + for (int i = 1; i < num_args; i++) { + + int64_t ival; + double fval; + WL_String str; + + if (wl_arg_none(rt, i)) + ret = sqlite3_bind_null (stmt, i); + else if (wl_arg_s64(rt, i, &ival)) + ret = sqlite3_bind_int64 (stmt, i, ival); + else if (wl_arg_f64(rt, i, &fval)) + ret = sqlite3_bind_double(stmt, i, fval); + else if (wl_arg_str(rt, i, &str)) + ret = sqlite3_bind_text (stmt, i, str.ptr, str.len, NULL); + else assert(0); + + if (ret != SQLITE_OK) { + sqlite3_reset(stmt); + return -1; + } + } + + wl_push_array(rt, 0); + + while (sqlite3_step(stmt) == SQLITE_ROW) { + + int num_cols = sqlite3_column_count(stmt); + if (num_cols < 0) { + sqlite3_reset(stmt); + return -1; + } + + wl_push_map(rt, num_cols); + + for (int i = 0; i < num_cols; i++) { + ret = sqlite3_column_type(stmt, i); + switch (ret) { + + case SQLITE_INTEGER: + { + int64_t x = sqlite3_column_int64(stmt, i); + wl_push_s64(rt, x); + } + break; + + case SQLITE_FLOAT: + { + double x = sqlite3_column_double(stmt, i); + wl_push_f64(rt, x); + } + break; + + case SQLITE_TEXT: + { + const void *x = sqlite3_column_text(stmt, i); + int n = sqlite3_column_bytes(stmt, i); + wl_push_str(rt, (WL_String) { (char*) x, n }); + } + break; + + case SQLITE_BLOB: + { + const void *x = sqlite3_column_blob(stmt, i); + int n = sqlite3_column_bytes(stmt, i); + wl_push_str(rt, (WL_String) { (char*) x, n }); + } + break; + + case SQLITE_NULL: + { + wl_push_none(rt); + } + break; + } + + const char *name = sqlite3_column_name(stmt, i); + + wl_push_str(rt, (WL_String) { (char*) name, strlen(name) }); + wl_insert(rt); + } + + wl_append(rt); + } + + sqlite3_reset(stmt); + return 0; +} + +static void push_sysvar(WL_Runtime *rt, WL_String name, SQLiteCache *dbcache, int user_id, int post_id) +{ + if (wl_streq(name, "login_user_id", -1)) { + + if (user_id < 0) + wl_push_none(rt); + else + wl_push_s64(rt, user_id); + + } else if (wl_streq(name, "post_id", -1)) { + + if (post_id < 0) + wl_push_none(rt); + else + wl_push_s64(rt, post_id); + } +} + +static void push_syscall(WL_Runtime *rt, WL_String name, SQLiteCache *dbcache) +{ + if (wl_streq(name, "query", -1)) { + query_routine(rt, dbcache); + return; + } +} + +static int get_or_create_program(TemplateCache *cache, WL_String path, WL_Arena *arena, WL_Program *program) +{ + if (cache == NULL) + return -1; + + int i = lookup(cache, path); + if (cache->pool[i].pathlen == -1) { + + WL_Program program; + int ret = compile(path, &program, arena); + if (ret < 0) return -1; + + void *p = malloc(program.len); + if (p == NULL) + return -1; + memcpy(p, program.ptr, program.len); + program.ptr = p; + + if (sizeof(cache->pool->path) <= path.len) + return -1; + memcpy(cache->pool[i].path, path.ptr, path.len); + cache->pool[i].path[path.len] = '\0'; + cache->pool[i].pathlen = path.len; + cache->pool[i].program = program; + } + + *program = cache->pool[i].program; + return 0; +} + +void template_eval(HTTP_ResponseBuilder builder, int status, + WL_String path, TemplateCache *cache, WL_Arena *arena, + SQLiteCache *dbcache, int user_id, int post_id) +{ + http_response_builder_status(builder, status); + + WL_Program program; + int ret = get_or_create_program(cache, path, arena, &program); + if (ret < 0) { + http_response_builder_undo(builder); + http_response_builder_status(builder, 500); + http_response_builder_done(builder); + return; + } + + //wl_dump_program(program); + + WL_Runtime *rt = wl_runtime_init(arena, program); + if (rt == NULL) { + http_response_builder_undo(builder); + http_response_builder_status(builder, 500); + http_response_builder_done(builder); + return; + } + + for (bool done = false; !done; ) { + + WL_EvalResult result = wl_runtime_eval(rt); + switch (result.type) { + + case WL_EVAL_DONE: + http_response_builder_done(builder); + done = true; + break; + + case WL_EVAL_ERROR: + // wl_runtime_error(rt) + http_response_builder_undo(builder); + http_response_builder_status(builder, 500); + http_response_builder_done(builder); + return; + + case WL_EVAL_SYSVAR: + push_sysvar(rt, result.str, dbcache, user_id, post_id); + break; + + case WL_EVAL_SYSCALL: + push_syscall(rt, result.str, dbcache); + break; + + case WL_EVAL_OUTPUT: + http_response_builder_body(builder, (HTTP_String) { result.str.ptr, result.str.len }); + break; + } + } +} diff --git a/src/template.h b/src/template.h new file mode 100644 index 0000000..ddd085c --- /dev/null +++ b/src/template.h @@ -0,0 +1,16 @@ +#ifndef TEMPLATE_INCLUDED +#define TEMPLATE_INCLUDED + +#include "wl.h" +#include "chttp.h" +#include "sqlite3utils.h" + +typedef struct TemplateCache TemplateCache; +TemplateCache *template_cache_init(int capacity_log2); +void template_cache_free(TemplateCache *cache); + +void template_eval(HTTP_ResponseBuilder builder, int status, + WL_String path, TemplateCache *cache, WL_Arena *arena, + SQLiteCache *dbcache, int user_id, int post_id); + +#endif // TEMPLATE_INCLUDED \ No newline at end of file diff --git a/src/variadic.h b/src/variadic.h index 12d4a40..65ba096 100644 --- a/src/variadic.h +++ b/src/variadic.h @@ -1,3 +1,6 @@ +#ifndef VARIADIC_INCLUDED +#define VARIADIC_INCLUDED + #include #include "chttp.h" @@ -103,3 +106,5 @@ typedef struct { #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__) + +#endif // VARIADIC_INCLUDED \ No newline at end of file