Refactoring and bugfixes

A few bugfixes for author biographies.
Also moved a few functions that used to be in "util" into "db"
This commit is contained in:
Robin Malley 2022-11-23 21:44:46 +00:00
parent 411bcb494d
commit 9daf7e90cd
20 changed files with 151 additions and 127 deletions

View File

@ -9,6 +9,7 @@ local sql = require("lsqlite3")
local queries = require("queries") local queries = require("queries")
local util = require("util") local util = require("util")
local db = require("db")
local ret = {} local ret = {}
@ -16,11 +17,11 @@ local stmnt_cache, stmnt_insert_cache, stmnt_dirty_cache
local oldconfigure = configure local oldconfigure = configure
function configure(...) function configure(...)
local cache = util.sqlassert(sql.open_memory()) local cache = db.sqlassert(sql.open_memory())
ret.cache = cache -- Expose db for testing ret.cache = cache -- Expose db for testing
--A cache table to store rendered pages that do not need to be --A cache table to store rendered pages that do not need to be
--rerendered. In theory this could OOM the program eventually and start --rerendered. In theory this could OOM the program eventually and start
--swapping to disk. TODO: fixme --swapping to disk. TODO
assert(cache:exec([[ assert(cache:exec([[
CREATE TABLE IF NOT EXISTS cache ( CREATE TABLE IF NOT EXISTS cache (
path TEXT PRIMARY KEY, path TEXT PRIMARY KEY,
@ -55,7 +56,7 @@ end
--Render a page, with cacheing. If you need to dirty a cache, call dirty_cache() --Render a page, with cacheing. If you need to dirty a cache, call dirty_cache()
function ret.render(pagename,callback) function ret.render(pagename,callback)
stmnt_cache:bind_names{path=pagename} stmnt_cache:bind_names{path=pagename}
local err = util.do_sql(stmnt_cache) local err = db.do_sql(stmnt_cache)
if err == sql.DONE then if err == sql.DONE then
stmnt_cache:reset() stmnt_cache:reset()
--page is not cached --page is not cached
@ -73,7 +74,7 @@ function ret.render(pagename,callback)
path=pagename, path=pagename,
data=text, data=text,
} }
err = util.do_sql(stmnt_insert_cache) err = db.do_sql(stmnt_insert_cache)
if err == sql.ERROR or err == sql.MISUSE then if err == sql.ERROR or err == sql.MISUSE then
error("Failed to update cache for page " .. pagename) error("Failed to update cache for page " .. pagename)
end end
@ -81,11 +82,13 @@ function ret.render(pagename,callback)
return text return text
end end
-- Dirty a cached page, causing it to be re-rendered the next time it's
-- requested. Doesn't actually delete it or anything, just sets it's dirty bit
function ret.dirty(url) function ret.dirty(url)
stmnt_dirty_cache:bind_names{ stmnt_dirty_cache:bind_names{
path = url path = url
} }
util.do_sql(stmnt_dirty_cache) db.do_sql(stmnt_dirty_cache)
stmnt_dirty_cache:reset() stmnt_dirty_cache:reset()
end end

View File

@ -9,8 +9,85 @@ local util = require("util")
local config = require("config") local config = require("config")
local db = {} 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
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
local oldconfigure = configure local oldconfigure = configure
db.conn = util.sqlassert(sql.open(config.db)) db.conn = db.sqlassert(sql.open(config.db))
function configure(...) function configure(...)
--Create sql tables --Create sql tables

View File

@ -8,7 +8,7 @@ local stmnt_tags_get
local oldconfigure = configure local oldconfigure = configure
function configure(...) function configure(...)
stmnt_tags_get = util.sqlassert(db.conn:prepare(queries.select_suggest_tags)) stmnt_tags_get = db.sqlassert(db.conn:prepare(queries.select_suggest_tags))
return oldconfigure(...) return oldconfigure(...)
end end

View File

@ -37,7 +37,7 @@ local function bio_edit_get(req)
stmnt_bio:bind_names{ stmnt_bio:bind_names{
authorid = authorid authorid = authorid
} }
local err = util.do_sql(stmnt_bio) local err = db.do_sql(stmnt_bio)
if err == sql.DONE then if err == sql.DONE then
--No rows, we're logged in but an author with our id doesn't --No rows, we're logged in but an author with our id doesn't
--exist? Something has gone wrong. --exist? Something has gone wrong.
@ -56,10 +56,13 @@ found, please report this error.
end end
assert(err == sql.ROW) assert(err == sql.ROW)
local data = stmnt_bio:get_values() local data = stmnt_bio:get_values()
local bio = zlib.decompress(data[1]) local bio_text = data[1]
if data[1] ~= "" then
bio_text = zlib.decompress(data[1])
end
stmnt_bio:reset() stmnt_bio:reset()
ret = pages.edit_bio{ ret = pages.edit_bio{
text = bio, text = bio_text,
user = author, user = author,
domain = config.domain, domain = config.domain,
} }

View File

@ -26,18 +26,19 @@ local function edit_bio(req)
local author, author_id = session.get(req) local author, author_id = session.get(req)
http_request_populate_post(req) http_request_populate_post(req)
local text = assert(http_argument_get_string(req,"text")) local text = http_argument_get_string(req,"text") or ""
local parsed = parsers.plain(text) -- Make sure the plain parser can deal with it, even though we don't store this result. local parsed = parsers.plain(text) -- Make sure the plain parser can deal with it, even though we don't store this result.
local compr_raw = zlib.compress(text) local compr_raw = zlib.compress(text)
local compr = zlib.compress(parsed) local compr = zlib.compress(parsed)
assert(stmnt_update_bio:bind_blob(1,compr_raw) == sql.OK) db.sqlbind(stmnt_update_bio, "bind_blob", 1,compr_raw)
assert(stmnt_update_bio:bind(2, author_id) == sql.OK) db.sqlbind(stmnt_update_bio, "bind", 2, author_id)
if util.do_sql(stmnt_update_bio) ~= sql.DONE then if db.do_sql(stmnt_update_bio) ~= sql.DONE then
stmnt_update_bio:reset() stmnt_update_bio:reset()
error("Faled to update biography") error("Faled to update biography")
end end
stmnt_update_bio:reset()
local loc = string.format("https://%s.%s",author,config.domain) local loc = string.format("https://%s.%s",author,config.domain)
-- Dirty the cache for the author's index, the only place where the bio is displayed. -- Dirty the cache for the author's index, the only place where the bio is displayed.
cache.dirty(string.format("%s.%s",author,config.domain)) cache.dirty(string.format("%s.%s",author,config.domain))

View File

@ -16,7 +16,7 @@ local stmnt_author_create
local oldconfigure = configure local oldconfigure = configure
function configure(...) function configure(...)
stmnt_author_create = util.sqlassert(db.conn:prepare(queries.insert_author)) stmnt_author_create = db.sqlassert(db.conn:prepare(queries.insert_author))
return oldconfigure(...) return oldconfigure(...)
end end
@ -46,7 +46,7 @@ local function claim_post(req)
} }
stmnt_author_create:bind_blob(2,salt) stmnt_author_create:bind_blob(2,salt)
stmnt_author_create:bind_blob(3,hash) stmnt_author_create:bind_blob(3,hash)
local err = util.do_sql(stmnt_author_create) local err = db.do_sql(stmnt_author_create)
if err == sql.DONE then if err == sql.DONE then
log(LOG_INFO,"Account creation successful:" .. name) log(LOG_INFO,"Account creation successful:" .. name)
--We sucessfully made the new author --We sucessfully made the new author

View File

@ -37,7 +37,7 @@ local function delete_post(req)
postid = storyid, postid = storyid,
authorid = authorid authorid = authorid
} }
local err = util.do_sql(stmnt_delete) local err = db.do_sql(stmnt_delete)
if err ~= sql.DONE then if err ~= sql.DONE then
log(LOG_DEBUG,string.format("Failed to delete: %d:%s",err, db.conn:errmsg())) log(LOG_DEBUG,string.format("Failed to delete: %d:%s",err, db.conn:errmsg()))
http_response(req,500,pages.error{ http_response(req,500,pages.error{

View File

@ -25,7 +25,7 @@ local function download_get(req)
stmnt_download:bind_names{ stmnt_download:bind_names{
postid = story_id postid = story_id
} }
local err = util.do_sql(stmnt_download) local err = db.do_sql(stmnt_download)
if err == sql.DONE then if err == sql.DONE then
--No rows, story not found --No rows, story not found
http_response(req,404,pages.nostory{path=story}) http_response(req,404,pages.nostory{path=story})

View File

@ -32,7 +32,7 @@ local function edit_get(req)
postid = story_id, postid = story_id,
authorid = authorid authorid = authorid
} }
local err = util.do_sql(stmnt_edit) local err = db.do_sql(stmnt_edit)
if err == sql.DONE then if err == sql.DONE then
--No rows, we're probably not the owner (it might --No rows, we're probably not the owner (it might
--also be because there's no such story) --also be because there's no such story)

View File

@ -38,7 +38,7 @@ local function edit_post(req)
stmnt_author_of:bind_names{ stmnt_author_of:bind_names{
id = storyid id = storyid
} }
local err = util.do_sql(stmnt_author_of) local err = db.do_sql(stmnt_author_of)
if err ~= sql.ROW then if err ~= sql.ROW then
stmnt_author_of:reset() stmnt_author_of:reset()
local msg = string.format("No author found for story: %d", storyid) local msg = string.format("No author found for story: %d", storyid)
@ -66,14 +66,14 @@ local function edit_post(req)
assert(stmnt_update_raw:bind_blob(1,compr_raw) == sql.OK) assert(stmnt_update_raw:bind_blob(1,compr_raw) == sql.OK)
assert(stmnt_update_raw:bind(2,markup) == sql.OK) assert(stmnt_update_raw:bind(2,markup) == sql.OK)
assert(stmnt_update_raw:bind(3,storyid) == sql.OK) assert(stmnt_update_raw:bind(3,storyid) == sql.OK)
assert(util.do_sql(stmnt_update_raw) == sql.DONE, "Failed to update raw") assert(db.do_sql(stmnt_update_raw) == sql.DONE, "Failed to update raw")
stmnt_update_raw:reset() stmnt_update_raw:reset()
assert(stmnt_update:bind(1,title) == sql.OK) assert(stmnt_update:bind(1,title) == sql.OK)
assert(stmnt_update:bind_blob(2,compr) == sql.OK) assert(stmnt_update:bind_blob(2,compr) == sql.OK)
assert(stmnt_update:bind(3,pasteas == "anonymous" and 1 or 0) == sql.OK) assert(stmnt_update:bind(3,pasteas == "anonymous" and 1 or 0) == sql.OK)
assert(stmnt_update:bind(4,unlisted) == sql.OK) assert(stmnt_update:bind(4,unlisted) == sql.OK)
assert(stmnt_update:bind(5,storyid) == sql.OK) assert(stmnt_update:bind(5,storyid) == sql.OK)
assert(util.do_sql(stmnt_update) == sql.DONE, "Failed to update text") assert(db.do_sql(stmnt_update) == sql.DONE, "Failed to update text")
stmnt_update:reset() stmnt_update:reset()
tagslib.set(storyid,tags) tagslib.set(storyid,tags)
local id_enc = util.encode_id(storyid) local id_enc = util.encode_id(storyid)
@ -81,7 +81,7 @@ local function edit_post(req)
local loc = string.format("https://%s/%s",config.domain,id_enc) local loc = string.format("https://%s/%s",config.domain,id_enc)
if unlisted then if unlisted then
stmnt_hash:bind_names{id=storyid} stmnt_hash:bind_names{id=storyid}
local err = util.do_sql(stmnt_hash) local err = db.do_sql(stmnt_hash)
if err ~= sql.ROW then if err ~= sql.ROW then
error("Failed to get a post's hash while trying to make it unlisted") error("Failed to get a post's hash while trying to make it unlisted")
end end

View File

@ -9,6 +9,7 @@ local pages = require("pages")
local libtags = require("tags") local libtags = require("tags")
local session = require("session") local session = require("session")
local parsers = require("parsers") local parsers = require("parsers")
local zlib = require("zlib")
local stmnt_index, stmnt_author, stmnt_author_bio local stmnt_index, stmnt_author, stmnt_author_bio
@ -27,7 +28,7 @@ local function get_site_home(req, loggedin)
log(LOG_DEBUG,"Cache miss, rendering site index") log(LOG_DEBUG,"Cache miss, rendering site index")
stmnt_index:bind_names{} stmnt_index:bind_names{}
local latest = {} local latest = {}
for idr, title, iar, dater, author, hits in util.sql_rows(stmnt_index) do for idr, title, iar, dater, author, hits in db.sql_rows(stmnt_index) do
table.insert(latest,{ table.insert(latest,{
url = util.encode_id(idr), url = util.encode_id(idr),
title = title, title = title,
@ -45,12 +46,11 @@ local function get_site_home(req, loggedin)
} }
end end
local function get_author_home(req, loggedin) local function get_author_home(req, loggedin)
--print("Looking at author home...")
local host = http_request_get_host(req) local host = http_request_get_host(req)
local subdomain = host:match("([^\\.]+)") local subdomain = host:match("([^\\.]+)")
stmnt_author_bio:bind_names{author=subdomain} stmnt_author_bio:bind_names{author=subdomain}
local err = util.do_sql(stmnt_author_bio)
local author, authorid = session.get(req) local author, authorid = session.get(req)
local err = db.do_sql(stmnt_author_bio)
if err == sql.DONE then if err == sql.DONE then
log(LOG_INFO,"No such author:" .. subdomain) log(LOG_INFO,"No such author:" .. subdomain)
stmnt_author_bio:reset() stmnt_author_bio:reset()
@ -63,12 +63,15 @@ local function get_author_home(req, loggedin)
error(string.format("Failed to get author %q error: %q",subdomain, tostring(err))) error(string.format("Failed to get author %q error: %q",subdomain, tostring(err)))
end end
local data = stmnt_author_bio:get_values() local data = stmnt_author_bio:get_values()
local bio = parsers.plain(zlib.decompress(data[1])) local bio_text = data[1]
if data[1] ~= "" then
bio_text = zlib.decompress(data[1])
end
local bio = parsers.plain(bio_text)
stmnt_author_bio:reset() stmnt_author_bio:reset()
stmnt_author:bind_names{author=subdomain} stmnt_author:bind_names{author=subdomain}
local stories = {} local stories = {}
for id, title, time, hits, unlisted, hash in util.sql_rows(stmnt_author) do for id, title, time, hits, unlisted, hash in db.sql_rows(stmnt_author) do
--print("Looking at:",id,title,time,hits,unlisted)
if unlisted == 1 and author == subdomain then if unlisted == 1 and author == subdomain then
local url = util.encode_id(id) .. "?pwd=" .. util.encode_unlisted(hash) local url = util.encode_id(id) .. "?pwd=" .. util.encode_unlisted(hash)
table.insert(stories,{ table.insert(stories,{

View File

@ -27,7 +27,7 @@ local function login_post(req)
name = name name = name
} }
local text local text
local err = util.do_sql(stmnt_author_acct) local err = db.do_sql(stmnt_author_acct)
if err == sql.ROW then if err == sql.ROW then
local id, salt, passhash = unpack(stmnt_author_acct:get_values()) local id, salt, passhash = unpack(stmnt_author_acct:get_values())
stmnt_author_acct:reset() stmnt_author_acct:reset()

View File

@ -44,14 +44,14 @@ local function anon_paste(req,ps)
log(LOG_DEBUG,string.format("new story: %q, length: %d",ps.title,string.len(ps.text))) log(LOG_DEBUG,string.format("new story: %q, length: %d",ps.title,string.len(ps.text)))
local textsha3 = sha3(ps.text .. get_random_bytes(32)) local textsha3 = sha3(ps.text .. get_random_bytes(32))
util.sqlbind(stmnt_paste,"bind_blob",1,ps.text) db.sqlbind(stmnt_paste,"bind_blob",1,ps.text)
util.sqlbind(stmnt_paste,"bind",2,ps.title) db.sqlbind(stmnt_paste,"bind",2,ps.title)
util.sqlbind(stmnt_paste,"bind",3,-1) db.sqlbind(stmnt_paste,"bind",3,-1)
util.sqlbind(stmnt_paste,"bind",4,true) db.sqlbind(stmnt_paste,"bind",4,true)
util.sqlbind(stmnt_paste,"bind_blob",5,"") db.sqlbind(stmnt_paste,"bind_blob",5,"")
util.sqlbind(stmnt_paste,"bind",6,ps.unlisted) db.sqlbind(stmnt_paste,"bind",6,ps.unlisted)
util.sqlbind(stmnt_paste,"bind_blob",7,textsha3) db.sqlbind(stmnt_paste,"bind_blob",7,textsha3)
local err = util.do_sql(stmnt_paste) local err = db.do_sql(stmnt_paste)
stmnt_paste:reset() stmnt_paste:reset()
if err == sql.DONE then if err == sql.DONE then
local rowid = stmnt_paste:last_insert_rowid() local rowid = stmnt_paste:last_insert_rowid()
@ -62,7 +62,7 @@ local function anon_paste(req,ps)
assert(stmnt_raw:bind(1,rowid) == sql.OK) assert(stmnt_raw:bind(1,rowid) == sql.OK)
assert(stmnt_raw:bind_blob(2,ps.raw) == sql.OK) assert(stmnt_raw:bind_blob(2,ps.raw) == sql.OK)
assert(stmnt_raw:bind(3,ps.markup) == sql.OK) assert(stmnt_raw:bind(3,ps.markup) == sql.OK)
err = util.do_sql(stmnt_raw) err = db.do_sql(stmnt_raw)
stmnt_raw:reset() stmnt_raw:reset()
if err ~= sql.DONE then if err ~= sql.DONE then
local msg = string.format( local msg = string.format(
@ -112,9 +112,9 @@ local function author_paste(req,ps)
assert(stmnt_paste:bind(3,authorid) == sql.OK) assert(stmnt_paste:bind(3,authorid) == sql.OK)
assert(stmnt_paste:bind(4,asanon == "anonymous") == sql.OK) assert(stmnt_paste:bind(4,asanon == "anonymous") == sql.OK)
assert(stmnt_paste:bind_blob(5,"") == sql.OK) assert(stmnt_paste:bind_blob(5,"") == sql.OK)
util.sqlbind(stmnt_paste,"bind",6,ps.unlisted) db.sqlbind(stmnt_paste,"bind",6,ps.unlisted)
util.sqlbind(stmnt_paste,"bind_blob",7,textsha3) db.sqlbind(stmnt_paste,"bind_blob",7,textsha3)
local err = util.do_sql(stmnt_paste) local err = db.do_sql(stmnt_paste)
stmnt_paste:reset() stmnt_paste:reset()
if err == sql.DONE then if err == sql.DONE then
local rowid = stmnt_paste:last_insert_rowid() local rowid = stmnt_paste:last_insert_rowid()
@ -125,7 +125,7 @@ local function author_paste(req,ps)
assert(stmnt_raw:bind(1,rowid) == sql.OK) assert(stmnt_raw:bind(1,rowid) == sql.OK)
assert(stmnt_raw:bind_blob(2,ps.raw) == sql.OK) assert(stmnt_raw:bind_blob(2,ps.raw) == sql.OK)
assert(stmnt_raw:bind(3,ps.markup) == sql.OK) assert(stmnt_raw:bind(3,ps.markup) == sql.OK)
err = util.do_sql(stmnt_raw) err = db.do_sql(stmnt_raw)
stmnt_raw:reset() stmnt_raw:reset()
if err ~= sql.DONE then if err ~= sql.DONE then
local msg = string.format( local msg = string.format(

View File

@ -28,7 +28,7 @@ local function add_view(storyid)
stmnt_update_views:bind_names{ stmnt_update_views:bind_names{
id = storyid id = storyid
} }
local err = util.do_sql(stmnt_update_views) local err = db.do_sql(stmnt_update_views)
assert(err == sql.DONE, "Failed to update view counter:"..tostring(err)) assert(err == sql.DONE, "Failed to update view counter:"..tostring(err))
stmnt_update_views:reset() stmnt_update_views:reset()
end end
@ -42,7 +42,7 @@ local function populate_ps_story(req,ps)
stmnt_read:bind_names{ stmnt_read:bind_names{
id = ps.storyid, id = ps.storyid,
} }
local err = util.do_sql(stmnt_read) local err = db.do_sql(stmnt_read)
if err == sql.DONE then if err == sql.DONE then
--We got no story --We got no story
stmnt_read:reset() stmnt_read:reset()
@ -81,7 +81,7 @@ local function get_comments(req,ps)
id = ps.storyid id = ps.storyid
} }
local comments = {} local comments = {}
for com_author, com_isanon, com_text in util.sql_rows(stmnt_comments) do for com_author, com_isanon, com_text in db.sql_rows(stmnt_comments) do
table.insert(comments,{ table.insert(comments,{
author = com_author, author = com_author,
isanon = com_isanon == 1, --int to boolean isanon = com_isanon == 1, --int to boolean

View File

@ -38,7 +38,7 @@ local function read_post(req)
isanon = isanon, isanon = isanon,
comment_text = comment_text, comment_text = comment_text,
} }
local err = util.do_sql(stmnt_comment_insert) local err = db.do_sql(stmnt_comment_insert)
stmnt_comment_insert:reset() stmnt_comment_insert:reset()
if err ~= sql.DONE then if err ~= sql.DONE then
http_response(req,500,"Internal error, failed to post comment. Go back and try again.") http_response(req,500,"Internal error, failed to post comment. Go back and try again.")

9
src/lua/global.lua Normal file
View File

@ -0,0 +1,9 @@
-- Various global functions to cause less typing.
function assertf(bool, fmt, ...)
fmt = fmt or "Assetion Failed"
if not bool then
error(string.format(fmt,...),2)
end
end

View File

@ -28,7 +28,7 @@ function session.get(req)
stmnt_get_session:bind_names{ stmnt_get_session:bind_names{
key = sessionid key = sessionid
} }
local err = util.do_sql(stmnt_get_session) local err = db.do_sql(stmnt_get_session)
if err ~= sql.ROW then if err ~= sql.ROW then
stmnt_get_session:reset() stmnt_get_session:reset()
return nil, "No such session by logged in users" return nil, "No such session by logged in users"
@ -57,7 +57,7 @@ function session.start(who)
sessionid = session, sessionid = session,
authorid = who authorid = who
} }
local err = util.do_sql(stmnt_insert_session) local err = db.do_sql(stmnt_insert_session)
stmnt_insert_session:reset() stmnt_insert_session:reset()
assert(err == sql.DONE) assert(err == sql.DONE)
return session return session
@ -71,7 +71,7 @@ function session.finish(who,sessionid)
authorid = who, authorid = who,
sessionid = sessionid sessionid = sessionid
} }
local err = util.do_sql(stmnt_delete_session) local err = db.do_sql(stmnt_delete_session)
stmnt_delete_session:reset() stmnt_delete_session:reset()
assert(err == sql.DONE) assert(err == sql.DONE)
return true return true

View File

@ -41,13 +41,13 @@ end
function tags.set(storyid,tags) function tags.set(storyid,tags)
assert(stmnt_drop_tags:bind_names{postid = storyid} == sql.OK) assert(stmnt_drop_tags:bind_names{postid = storyid} == sql.OK)
util.do_sql(stmnt_drop_tags) db.do_sql(stmnt_drop_tags)
stmnt_drop_tags:reset() stmnt_drop_tags:reset()
local err local err
for _,tag in pairs(tags) do for _,tag in pairs(tags) do
assert(stmnt_ins_tag:bind(1,storyid) == sql.OK) assert(stmnt_ins_tag:bind(1,storyid) == sql.OK)
assert(stmnt_ins_tag:bind(2,tag) == sql.OK) assert(stmnt_ins_tag:bind(2,tag) == sql.OK)
err = util.do_sql(stmnt_ins_tag) err = db.do_sql(stmnt_ins_tag)
stmnt_ins_tag:reset() stmnt_ins_tag:reset()
end end
if err ~= sql.DONE then if err ~= sql.DONE then

View File

@ -4,80 +4,6 @@ local config = require("config")
local types = require("types") local types = require("types")
local util = {} local util = {}
--[[
Runs an sql query and receives the 3 arguments back, prints a nice error
message on fail, and returns true on success.
]]
function util.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 util.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 util.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 util.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 to %s at %d with %q: %s", call, position, data, db.conn:errmsg()),2)
end
end
--see https://perishablepress.com/stop-using-unsafe-characters-in-urls/ --see https://perishablepress.com/stop-using-unsafe-characters-in-urls/
--no underscore because we use that for our operative pages --no underscore because we use that for our operative pages
local url_characters = local url_characters =

View File

@ -33,7 +33,9 @@
<div class="row"> <div class="row">
<textarea name="text" cols=80 rows=24 class="column"><%= text %></textarea><br/> <textarea name="text" cols=80 rows=24 class="column"><%= text %></textarea><br/>
</div> </div>
<input type="submit"> <div class="row">
<input type="submit">
</div>
</fieldset> </fieldset>
</form> </form>