diff options
Diffstat (limited to 'src/sql.c')
-rw-r--r-- | src/sql.c | 208 |
1 files changed, 201 insertions, 7 deletions
@@ -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; + } +} |