--[[ md @name lua/db ## Overview Does most of the database interaction. Creates default empty database during configure() Notably, holds a connection to the open sqlite3 database in .conn ]] local sql = require("lsqlite3") local queries = require("queries") local config = require("config") local db = {} --[[ md @name lua/db ### db.sqlassert Runs an sql query and receives the 3 arguments back, prints a nice error message on fail, and returns true on success. Parameters: 0. r - {{lsqlite/stmnt}} | {{lua/nil}} - The userdata returned from {{lsqlite/db/prepare}} 0. errcode - {{lua/nil}} | {{lua/number}} - If the first argument back from {{lsqlite/db/prepare}} is nil, this second argument is a numeric errorcode, see {{lsqlite/errcodes}} 0. err - {{lua/nil}} | {{lua/string}} - The string error returned from {{lsqlite/db/prepare}}. Only non-nil if the first return value was nil. A string message describing what went wrong in the statment. If this argument is also {{lua/nil}}, this function retrives the error mssage from {{lsqlite/db/errmsg}}. Returns: 0. r - {{lua/userdata}} | {{lua/nil}} - The first argument passed in. Used so that error checking and assignment can all be done on a single line. Example: db = require("db") query = db.sqlassert(db.conn:parepare("SELECT 'Hello, world!'")) ]] function db.sqlassert(r, errcode, err) if not r then if err then error(string.format("%d: %s",errcode, err)) elseif errcode then error(string.format("%d: %s",errcode, db.conn:errmsg())) end end return r end --[[ md @name lua/db ### db.do_sql Continuously tries to perform an sql statement until it goes through. This function may call {{lua/coroutine/yield}} Parameters: 0. stmnt - {{lsqlite/stmnt}} - The userdata returned form {{lsqlite/db/prepare}} Returns: 0. err - {{lua/number}} - The error code returned from running the statement. Will be `lsqlite.OK` on success, see {{lsqlite/errcodes}} Example: sql = require("lsqlite3") configure = function(...) end -- Mock smr environment db = require("db") configure() query = db.conn:prepare("SELECT 'Hello, world!';") assert(db.do_sql(query)) ]] function db.do_sql(stmnt) if not stmnt then error("No statement",2) end local err local i = 0 repeat err = stmnt:step() if err == sql.BUSY then i = i + 1 coroutine.yield() end until(err ~= sql.BUSY or i > 10) assert(i < 10, "Database busy") return err end --[[ md @name lua/db ### db.sql_rows Provides an iterator that loops over results in an sql statement or throws an error, then resets the statement after the loop is done. Returned iterator returns varargs, so the values can be unpacked in-line in the for loop. This statement is approximately the same as {{sqlite/stmt/rows}}, but may yield when the db connection is busy, and continue execution when the connection is free again. Parameters: 0. stmnt - {{lsqlite/stmnt}} - The userdata returned from {{sqlite/db/prepare}} Returns: 0. iterator - {{lua/iterator}} - The iterator function that returns varargs of the returns from the sql statement. Example: db = require("db") query = db.conn:prepare("SELECT 'Hello, world!';") for row in db.sql_rows(query) do print(row) -- prints 'Hello, world!' end ]] function db.sql_rows(stmnt) if not stmnt then error("No statement",2) end local err return function() err = stmnt:step() if err == sql.BUSY then coroutine.yield() elseif err == sql.ROW then return unpack(stmnt:get_values()) elseif err == sql.DONE then stmnt:reset() return nil else stmnt:reset() local msg = string.format( "SQL Iteration failed: %s : %s\n%s", tostring(err), db.conn:errmsg(), debug.traceback() ) log(LOG_CRIT,msg) error(msg) end end end --[[ md @name lua/db ### db.sqlbind Binds an argument to a prepared statement, with nice error reporting on failure. Wraps {{lsqlite/stmnt/bind_name}} with better error reporting. Parameters: 0. stmnt - {{lsqlite/stmnt}} - The prepared statement from {{sqlite/db/prepare}} 0. call - {{lua/string}} - Literal string, options are `bind` for most types, or `bind_blob` for strings that may contain embedded nulls 0. position - {{lua/number}} - The argument position to bind to, does not support named parameters 0. data - Any - the data to bind Returns nothing ]] function db.sqlbind(stmnt,call,position,data) assert(call == "bind" or call == "bind_blob","Bad bind call, call was:" .. call) local f = stmnt[call](stmnt,position,data) if f ~= sql.OK then local errs = string.format( "Failed call %s(%d,%q): %s", call, position, data, db.conn:errmsg() ) log(LOG_ERR,errs) error(errs,2) end end local oldconfigure = configure db.conn = db.sqlassert(sql.open(config.db)) function configure(...) local statements = { "create_table_authors", "insert_anon_author", "create_table_posts", "create_table_raw_text", "create_table_images", "create_table_comments", "create_table_tags", "create_index_tags", "create_table_session" } -- ipairs() needed, "create table authors" must be executed before -- "insert anon author" for _, statement in ipairs(statements) do db.sqlassert(db.conn:exec(queries[statement])) end return oldconfigure(...) end --[[ md @name lua/db ### db.close() Closes the database connection. Not called during normal operation, used to assist in unit testing. No parameters No returns ]] function db.close() db.conn:close() end return db