2020-12-23 07:07:00 +01:00
|
|
|
--[[
|
|
|
|
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 util = require("util")
|
2021-08-27 03:06:55 +02:00
|
|
|
local config = require("config")
|
2020-12-23 07:07:00 +01:00
|
|
|
|
|
|
|
local db = {}
|
2022-11-23 22:44:46 +01:00
|
|
|
|
|
|
|
--[[
|
|
|
|
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
|
|
|
|
error(string.format("%d: %s",errcode, err))
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
|
|
|
2020-12-23 07:07:00 +01:00
|
|
|
local oldconfigure = configure
|
2022-11-23 22:44:46 +01:00
|
|
|
db.conn = db.sqlassert(sql.open(config.db))
|
2020-12-23 07:07:00 +01:00
|
|
|
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))
|
2020-12-20 09:16:23 +01:00
|
|
|
|
2020-12-23 07:07:00 +01:00
|
|
|
return oldconfigure(...)
|
|
|
|
end
|
2020-12-21 05:22:22 +01:00
|
|
|
configure()
|
2020-12-23 07:07:00 +01:00
|
|
|
|
|
|
|
function db.close()
|
|
|
|
db.conn:close()
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
|
|
return db
|