-- vim: ft=terra
local queries = {
conf_get = {
params = {rawstring}, sql = [[
select value from parsav_config
where key = $1::text limit 1
]];
};
conf_set = {
params = {rawstring,rawstring}, sql = [[
insert into parsav_config (key, value)
values ($1::text, $2::text)
on conflict (key) do update set value = $2::text
]];
};
conf_reset = {
params = {rawstring}, sql = [[
delete from parsav_config where
key = $1::text
]];
};
actor_fetch_uid = {
params = {uint64}, sql = [[
select
id, nym, handle, origin,
bio, rank, quota, key
from parsav_actors
where id = $1::bigint
]];
};
actor_fetch_xid = {
params = {rawstring}, sql = [[
select a.id, a.nym, a.handle, a.origin,
a.bio, a.rank, a.quota, a.key,
coalesce(s.domain,
(select value from parsav_config
where key='domain' limit 1)) as domain
from parsav_actors as a
left join parsav_servers as s
on a.origin = s.id
where $1::text = (a.handle || '@' || domain) or
$1::text = ('@' || a.handle || '@' || domain) or
(a.origin is null and $1::text = ('@' || a.handle))
]];
};
}
local struct pqr {
sz: intptr
res: &lib.pq.PGresult
}
terra pqr:free() if self.sz > 0 then lib.pq.PQclear(self.res) end end
terra pqr:null(row: intptr, col: intptr)
return (lib.pq.PQgetisnull(self.res, row, col) == 1)
end
terra pqr:string(row: intptr, col: intptr)
var v = lib.pq.PQgetvalue(self.res, row, col)
var r: lib.mem.ptr(int8)
r.ct = lib.str.sz(v)
r.ptr = lib.str.ndup(v, r.ct)
return r
end
pqr.methods.int = macro(function(self, ty, row, col)
return quote
var i: ty:astype()
var v = lib.pq.PQgetvalue(self.res, row, col)
lib.math.netswap_ip(ty, v, &i)
in i end
end)
local con = symbol(&lib.pq.PGconn)
local prep = {}
for k,q in pairs(queries) do
local qt = (q.sql):gsub('%s+',' '):gsub('^%s*(.-)%s*$','%1')
local stmt = 'parsavpg_' .. k
prep[#prep + 1] = quote
var res = lib.pq.PQprepare([con], stmt, qt, [#q.params], nil)
defer lib.pq.PQclear(res)
if res == nil or lib.pq.PQresultStatus(res) ~= lib.pq.PGRES_COMMAND_OK then
if res == nil then
lib.bail('grievous error occurred preparing ',k,' statement')
end
lib.bail('could not prepare PGSQL statement ',k,': ',lib.pq.PQresultErrorMessage(res))
end
lib.dbg('prepared PGSQL statement ',k)
end
local args, casts, counters, fixers, ft, yield = {}, {}, {}, {}, {}, {}
for i, ty in ipairs(q.params) do
args[i] = symbol(ty)
ft[i] = `1
if ty == rawstring then
counters[i] = `lib.trn([args[i]] == nil, 0, lib.str.sz([args[i]]))
casts[i] = `[&int8]([args[i]])
elseif ty:isintegral() then
counters[i] = ty.bytes
casts[i] = `[&int8](&[args[i]])
fixers[#fixers + 1] = quote
--lib.io.fmt('uid=%llu(%llx)\n',[args[i]],[args[i]])
[args[i]] = lib.math.netswap(ty, [args[i]])
end
end
end
q.exec = terra(src: &lib.store.source, [args])
var params = arrayof([&int8], [casts])
var params_sz = arrayof(int, [counters])
var params_ft = arrayof(int, [ft])
[fixers]
var res = lib.pq.PQexecPrepared([&lib.pq.PGconn](src.handle), stmt,
[#args], params, params_sz, params_ft, 1)
if res == nil then
lib.bail(['grievous error occurred executing '..k..' against database'])
elseif lib.pq.PQresultStatus(res) ~= lib.pq.PGRES_TUPLES_OK then
lib.bail(['PGSQL database procedure '..k..' failed\n'],
lib.pq.PQresultErrorMessage(res))
end
var ct = lib.pq.PQntuples(res)
if ct == 0 then
lib.pq.PQclear(res)
return pqr {0, nil}
else
return pqr {ct, res}
end
end
end
local terra row_to_actor(r: &pqr, row: intptr): lib.store.actor
var a = lib.store.actor {
id = r:int(uint64, row, 0);
nym = r:string(row, 1);
handle = r:string(row, 2);
bio = r:string(row, 4);
key = r:string(row, 7);
rights = lib.store.rights_default();
}
a.rights.rank = r:int(uint16, 0, 5);
a.rights.quota = r:int(uint32, 0, 6);
if r:null(0,3) then a.origin = 0
else a.origin = r:int(uint64,0,3) end
return a
end
local b = `lib.store.backend {
id = "pgsql";
open = [terra(src: &lib.store.source): &opaque
lib.report('connecting to postgres database: ', src.string.ptr)
var [con] = lib.pq.PQconnectdb(src.string.ptr)
if lib.pq.PQstatus(con) ~= lib.pq.CONNECTION_OK then
lib.warn('postgres backend connection failed')
lib.pq.PQfinish(con)
return nil
end
var res = lib.pq.PQexec(con, [[
select pg_catalog.set_config('search_path', 'public', false)
]])
if res ~= nil then defer lib.pq.PQclear(res) end
if res == nil or lib.pq.PQresultStatus(res) ~= lib.pq.PGRES_TUPLES_OK then
lib.warn('failed to secure postgres connection')
lib.pq.PQfinish(con)
return nil
end
[prep]
return con
end];
close = [terra(src: &lib.store.source) lib.pq.PQfinish([&lib.pq.PGconn](src.handle)) end];
conf_get = [terra(src: &lib.store.source, key: rawstring)
var r = queries.conf_get.exec(src, key)
if r.sz == 0 then return [lib.mem.ptr(int8)] { ptr = nil, ct = 0 } else
defer r:free()
return r:string(0,0)
end
end];
conf_set = [terra(src: &lib.store.source, key: rawstring, val: rawstring)
queries.conf_set.exec(src, key, val):free() end];
conf_reset = [terra(src: &lib.store.source, key: rawstring)
queries.conf_reset.exec(src, key):free() end];
actor_fetch_uid = [terra(src: &lib.store.source, uid: uint64)
var r = queries.actor_fetch_uid.exec(src, uid)
if r.sz == 0 then
return [lib.stat(lib.store.actor)] { ok = false, error = 1}
else
defer r:free()
var a = [lib.stat(lib.store.actor)] { ok = true }
a.val = row_to_actor(&r, 0)
a.val.source = src
return a
end
end];
}
return b