241 lines
5.5 KiB
Lua
241 lines
5.5 KiB
Lua
--[[ 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
|
|
]]
|
|
|
|
--[[ sh
|
|
@name sql/table
|
|
echo "digraph schema {" \
|
|
"$(cat doc/schema/*.dot)" \
|
|
"}" | dot -Tsvg
|
|
]]
|
|
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
|