aboutsummaryrefslogtreecommitdiffstats
path: root/src/sql.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/sql.c')
-rw-r--r--src/sql.c208
1 files changed, 201 insertions, 7 deletions
diff --git a/src/sql.c b/src/sql.c
index 6054745..6b66ce7 100644
--- a/src/sql.c
+++ b/src/sql.c
@@ -1,13 +1,207 @@
#include "sql.h"
+#include "log.h"
+#include <ctype.h>
+#include <limits.h>
+#include <microhttpd.h>
+#include <string.h>
+
+struct sqlite3_stmt *statements[STMTIDX_SIZE] = {NULL};
+
+struct sql_state state = {NULL};
const char
*INIT_SQL =
- "CREATE TABLE IF NOT EXISTS links ("
- "id INTEGER PRIMARY KEY,"
- "url STRING NOT NULL,"
- "created INTEGER NOT NULL"
+ "CREATE TABLE IF NOT EXISTS links ( "
+ "id INTEGER PRIMARY KEY, "
+ "url STRING NOT NULL, "
+ "created INTEGER NOT NULL "
");",
*INSERT_SQL =
- "INSERT INTO links (url,created)"
- "VALUES (?1, ?2);",
-*DELETE_SQL = "DELETE FROM links WHERE id = ?1;";
+ "INSERT INTO links (id,url,created) "
+ "VALUES (?1, ?2, unixepoch());",
+*DELETE_SQL =
+ "DELETE FROM links "
+ "WHERE id = ?1;",
+*SELECT_SQL =
+ "SELECT url,created FROM links "
+ "WHERE id = ?1;",
+*RESET_SQL = "DROP TABLE links;",
+*GETN_SQL = "SELECT leastunused(id) FROM links;";
+
+
+/* sqlite leastunused implementation */
+void step_leastunused(sqlite3_context* ctx, int argc, sqlite3_value** argv) {
+ struct slidid *idx = sqlite3_aggregate_context(ctx, sizeof *idx);
+ if (idx->res) return;
+ idx->in = sqlite3_value_int64(argv[0]);
+ if (idx->in <= 0) return;
+ if (idx->il + 1 != idx->in) idx->res = idx->il + 1;
+ idx->il = idx->in;
+}
+void final_leastunused(sqlite3_context* ctx) {
+ struct slidid *idx = sqlite3_aggregate_context(ctx, 0);
+ if (idx == NULL) {
+ sqlite3_result_int64(ctx, 1);
+ return;
+ }
+ if (idx->res == 0) {
+ sqlite3_result_int64(ctx, idx->in + 1);
+ return;
+ }
+ sqlite3_result_int64(ctx, idx->res);
+}
+
+void sqlite_init(const char *db) {
+ sqlite3 *ret;
+ sqlite3_open_v2(db, &ret, SQLITE_OPEN_CREATE | SQLITE_OPEN_READWRITE, NULL);
+ if (ret == NULL) LCRIT(1, "could not open sqlite3 database");
+
+ sqlite3_prepare(ret, INIT_SQL, -1, statements +STMTIDX_INIT, NULL);
+ if (SQLITE_DONE != sqlite3_step(statements[STMTIDX_INIT])) LERRVF("Error initializing database: %s", sqlite3_errmsg(ret));
+ sqlite3_reset(statements[STMTIDX_INIT]);
+
+ sqlite3_create_function(ret, "leastunused", 1, SQLITE_UTF8, NULL, NULL, step_leastunused, final_leastunused);
+
+ const char *queries[STMTIDX_SIZE] = {INIT_SQL, INSERT_SQL, DELETE_SQL, SELECT_SQL, RESET_SQL, GETN_SQL};
+ for (int i = 1; i < STMTIDX_SIZE; ++i) sqlite3_prepare(ret, queries[i], -1, statements + i, NULL);
+ state.db = ret;
+}
+
+// alphabet: 0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ
+// returns 0 if no conversion is available
+static inline char itoc(register unsigned long l) {
+#ifdef __GNUC__
+ char add[256] = {
+ [0 ... 9] = '0',
+ [10 ... 35] = 'a' - 10,
+ [36 ... 61] = 'A' - 10 - 26,
+ };
+ return (l < 62) * (l + add[l]);
+#else
+ if (l < 10) return '0' + l;
+ if (l < 10 + 26) return 'a' + l - 10;
+ if (l < 10 + 2 * 26) return 'A' + l - 10 - 26;
+ return '\0';
+#endif
+}
+// returns negative number if conversion not available
+static inline unsigned long ctoi(register char c) {
+#ifdef __GNUC__
+ char sub[127] = {
+ [0 ... '0'-1] = 128,
+ ['0' ... '9'] = '0',
+ ['A' ... 'Z'] = 'A' - 26 - 10,
+ ['a' ... 'z'] = 'a' - 10,
+ };
+ return c - sub[c];
+#else
+ TODO;
+#endif
+}
+// buf must be at least 12 characters (11 for strnig and 1 for termination byte)
+// returns a pointer from which the string starts
+static char *itou(register unsigned long i, register char *buf) {
+ register ssize_t ind = 11;
+ buf[ind] = '\0';
+ buf[ind - 1] = '0';
+ while (ind >= 0 && i) {
+ buf[--ind] = itoc(i % 62);
+ i /= 62;
+ }
+ return buf + ind;
+}
+static unsigned long utoi(const char *s) {
+ register unsigned long result = 0;
+ while (*s) {
+ unsigned long ctoires = ctoi(*s);
+ // overflow check
+ if ((ULONG_MAX - ctoires) / 62 <= result) return 0;
+ result *= 62;
+ result += ctoires;
+ ++s;
+ }
+ return result;
+}
+
+void sqlite_deinit(void) {
+ for (size_t i = 0; i < STMTIDX_SIZE; ++i)
+ sqlite3_finalize(statements[i]);
+ sqlite3_close(state.db);
+}
+
+enum dberror db_add_url(const char *url, const char *path, char **out) {
+ sqlite3_int64 index;
+ if (path) {
+ for(const char *i = path; *i; ++i)
+ if (!isalpha(*i) && !isdigit(*i)) return DBERROR_INVAL;
+ index = utoi(path);
+ if (index == 0) return DBERROR_TOOBIG;
+ } else {
+ if (SQLITE_ROW != sqlite3_step(statements[STMTIDX_GETN])) {
+ sqlite3_reset(statements[STMTIDX_GETN]);
+ return DBERROR_SERVER;
+ }
+ index = sqlite3_column_int64(statements[STMTIDX_GETN], 0);
+ sqlite3_reset(statements[STMTIDX_GETN]);
+ }
+ if (SQLITE_OK != sqlite3_bind_int64(statements[STMTIDX_INSERT], 1, index)) return DBERROR_SERVER;
+ if (SQLITE_OK != sqlite3_bind_text(statements[STMTIDX_INSERT], 2, url, -1, SQLITE_TRANSIENT)) return DBERROR_SERVER;
+
+ *out = itou(index, *out);
+ int res;
+ if (SQLITE_DONE != (res = sqlite3_step(statements[STMTIDX_INSERT]))) {
+ LWARNVF("sqlite insert failed with error %i", res);
+ *out = NULL;
+ sqlite3_reset(statements[STMTIDX_INSERT]);
+ return DBERROR_OCCUPIED;
+ }
+ sqlite3_reset(statements[STMTIDX_INSERT]);
+ return DBERROR_SUCCESS;
+}
+
+bool db_del_url(const char *path) {
+ return false; // lick my balls
+}
+
+char *db_get_info(const char *path, long *dong) {
+ *dong = 0;
+ return NULL;
+}
+
+char *db_get_url(const char *path) {
+ char *ret = NULL;
+ sqlite3_int64 index = utoi(path);
+ if (SQLITE_OK != sqlite3_bind_int64(statements[STMTIDX_SELECT], 1, index)) return NULL;
+ if (SQLITE_ROW == sqlite3_step(statements[STMTIDX_SELECT])) {
+ const unsigned char *c = sqlite3_column_text(statements[STMTIDX_SELECT], 0);
+ size_t sz = sqlite3_column_bytes(statements[STMTIDX_SELECT], 0);
+ ret = malloc(sz + 1);
+ memcpy(ret, c, sz);
+ ret[sz] = '\0';
+ }
+ sqlite3_reset(statements[STMTIDX_SELECT]);
+ return ret;
+}
+
+const char *db_error(enum dberror e) {
+ switch (e) {
+ case DBERROR_SUCCESS: return "success";
+ case DBERROR_ARGS: return "insufficient arguments";
+ case DBERROR_INVAL: return "invalid arguments";
+ case DBERROR_OCCUPIED: return "occupied";
+ case DBERROR_SERVER: return "server error";
+ case DBERROR_TOOBIG: return "path too long";
+ case DBERROR_UNKNOWN: return "unknown";
+ }
+}
+int db_error_status(enum dberror e) {
+ switch (e) {
+ case DBERROR_SUCCESS: return MHD_HTTP_OK;
+ case DBERROR_ARGS: return MHD_HTTP_BAD_REQUEST;
+ case DBERROR_INVAL: return MHD_HTTP_BAD_REQUEST;
+ case DBERROR_OCCUPIED: return MHD_HTTP_BAD_REQUEST;
+ case DBERROR_SERVER: return MHD_HTTP_INTERNAL_SERVER_ERROR;
+ case DBERROR_TOOBIG: return MHD_HTTP_URI_TOO_LONG;
+ case DBERROR_UNKNOWN: return MHD_HTTP_INTERNAL_SERVER_ERROR;
+ }
+}