smr/src/lua/db.lua

135 lines
3.6 KiB
Lua

--[[
Does most of the database interaction.
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 = {}
--[[
Runs an sql query and receives the 3 arguments back, prints a nice error
message on fail, and returns true on success.
]]
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
--[[
Continuously tries to perform an sql statement until it goes through
]]
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
--[[
Provides an iterator that loops over results in an sql statement
or throws an error, then resets the statement after the loop is done.
]]
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
--[[
Binds an argument to as statement with nice error reporting on failure
stmnt :: sql.stmnt - the prepared sql statemnet
call :: string - a string "bind" or "bind_blob"
position :: number - the argument position to bind to
data :: string - The data to bind
]]
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
error(string.format("Failed call %s(%d,%q): %s", call, position, data, db.conn:errmsg()),2)
end
end
local oldconfigure = configure
db.conn = db.sqlassert(sql.open(config.db))
function configure(...)
--Create sql tables
assert(db.conn:exec(queries.create_table_authors))
--Create a fake "anonymous" user, so we don't run into trouble
--so that no one runs into trouble being able to paste under this account.
assert(db.conn:exec(queries.insert_anon_author))
--If/when an author deletes their account, all posts
--and comments by that author are also deleted (on
--delete cascade) this is intentional. This also
--means that all comments by other users on a post
--an author makes will also be deleted.
--
--Post text uses zlib compression
assert(db.conn:exec(queries.create_table_posts))
--Store the raw text so people can download it later, maybe
--we can use it for "download as image" or "download as pdf"
--in the future too. Stil stored zlib compressed
assert(db.conn:exec(queries.create_table_raw_text))
--Maybe we want to store images one day?
assert(db.conn:exec(queries.create_table_images))
--Comments on a post
assert(db.conn:exec(queries.create_table_comments))
--Tags for a post
assert(db.conn:exec(queries.create_table_tags))
--Index for tags
assert(db.conn:exec(queries.create_index_tags))
--Store a cookie for logged in users. Logged in users can edit
--their own posts, and edit their biographies.
assert(db.conn:exec(queries.create_table_session))
return oldconfigure(...)
end
configure()
function db.close()
db.conn:close()
end
return db