#include "sql.h" #include "log.h" #include #include #include #include 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 " ");", *INSERT_SQL = "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; } }