-- vim: ft=terra
local pstring = lib.mem.ptr(int8)
local binblob = lib.mem.ptr(uint8)
local queries = {
	conf_get = {
		params = {rawstring}, sql = [[
			select value from parsav_config
				where key = $1::text limit 1
		]];
	};

	conf_set = {
		params = {rawstring,rawstring}, cmd=true, 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}, cmd=true, sql = [[
			delete from parsav_config where
				key = $1::text 
		]];
	};

	actor_fetch_uid = {
		params = {uint64}, sql = [[
			select a.id, a.nym, a.handle, a.origin, a.bio,
			       a.avataruri, a.rank, a.quota, a.key, a.epithet,
			       extract(epoch from a.knownsince)::bigint,
				   coalesce(a.handle || '@' || s.domain,
				            '@' || a.handle) as xid,
			       a.invites

			from      parsav_actors  as a
			left join parsav_servers as s
				on a.origin = s.id
			where a.id = $1::bigint
		]];
	};

	actor_fetch_xid = {
		params = {pstring}, sql = [[
			select a.id, a.nym, a.handle, a.origin, a.bio,
			       a.avataruri, a.rank, a.quota, a.key, a.epithet,
			       extract(epoch from a.knownsince)::bigint,
				   coalesce(a.handle || '@' || s.domain,
				            '@' || a.handle) as xid,
			       a.invites,

				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 or
					  $1::text = ('@' || a.handle))
		]];
	};

	actor_purge_uid = {
		params = {uint64}, cmd = true, sql = [[
			with d as ( -- cheating
				delete from parsav_sanctions where victim = $1::bigint
			)
			delete from parsav_actors where id = $1::bigint
		]];
	};

	actor_save = {
		params = {
			uint64, --id
			rawstring, --nym
			rawstring, --handle
			rawstring, --bio 
			rawstring, --epithet
			rawstring, --avataruri
			uint64, --avatarid
			uint16, --rank
			uint32, --quota
			uint32 --invites
		}, cmd = true, sql = [[
			update parsav_actors set
				nym = $2::text,
				handle = $3::text,
				bio = $4::text,
				epithet = $5::text,
				avataruri = $6::text,
				avatarid = $7::bigint,
				rank = $8::smallint,
				quota = $9::integer,
				invites = $10::integer
			where id = $1::bigint
		]];
	};

	actor_create = {
		params = {
			rawstring, rawstring, uint64, lib.store.timepoint,
			rawstring, rawstring, lib.mem.ptr(uint8),
			rawstring, uint16, uint32, uint32
		};
		sql = [[
			insert into parsav_actors (
				nym,handle,
				origin,knownsince,
				bio,avataruri,key,
				epithet,rank,quota,
				invites
			) values ($1::text, $2::text,
				case when $3::bigint = 0 then null
				     else $3::bigint end,
				to_timestamp($4::bigint),
				$5::bigint, $6::bigint, $7::bytea,
				$8::text, $9::smallint, $10::integer,
				$11::integer
			) returning id
		]];
	};

	actor_auth_pw = {
		params = {pstring,rawstring,pstring,lib.store.inet}, sql = [[
			select a.aid, a.uid, a.name from parsav_auth as a
				left join parsav_actors as u on u.id = a.uid
			where (a.uid is null or u.handle = $1::text or (
					a.uid = 0 and a.name = $1::text
				)) and
				(a.kind = 'trust' or (a.kind = $2::text and a.cred = $3::bytea)) and
				(a.netmask is null or a.netmask >> $4::inet)
			order by blacklist desc limit 1
		]];
	};

	actor_enum_local = {
		params = {}, sql = [[
			select id, nym, handle, origin, bio,
			       null::text, rank, quota, key, epithet,
			       extract(epoch from knownsince)::bigint,
					'@' || handle,
				   invites
			from parsav_actors where origin is null
			order by nullif(rank,0) nulls last, handle
		]];
	};

	actor_enum = {
		params = {}, sql = [[
			select a.id, a.nym, a.handle, a.origin, a.bio,
			       a.avataruri, a.rank, a.quota, a.key, a.epithet,
			       extract(epoch from a.knownsince)::bigint,
				   coalesce(a.handle || '@' || s.domain,
				            '@' || a.handle) as xid,
				   invites
			from parsav_actors a
			left join parsav_servers s on s.id = a.origin
			order by nullif(a.rank,0) nulls last, a.handle, a.origin
		]];
	};

	actor_stats = {
		params = {uint64}, sql = ([[
			with tweets as (
				select from parsav_posts where author = $1::bigint
			),
			follows as (
				select relatee as user from parsav_rels
					where relator = $1::bigint and kind = <follow>
			),
			followers as (
				select relator as user from parsav_rels
					where relatee = $1::bigint and kind = <follow>
			),
			mutuals as (
				select * from follows  intersect  select * from followers
			)

			values (
				(select count(tweets.*)::bigint from tweets),
				(select count(follows.*)::bigint from follows),
				(select count(followers.*)::bigint from followers),
				(select count(mutuals.*)::bigint from mutuals)
			)
		]]):gsub('<(%w+)>',function(r) return tostring(lib.store.relation.idvmap[r]) end)
	};

	actor_auth_how = {
		params = {rawstring, lib.store.inet}, sql = [[
		with mts as (select a.kind from parsav_auth as a
			left join parsav_actors as u on u.id = a.uid
			where (a.uid is null or u.handle = $1::text or (
					a.uid = 0 and a.name = $1::text
				)) and
				(a.netmask is null or a.netmask >> $2::inet) and
				blacklist = false)

			select
				(select count(*) from mts where kind like 'pw-%') > 0,
				(select count(*) from mts where kind like 'otp-%') > 0,
				(select count(*) from mts where kind like 'challenge-%') > 0,
				(select count(*) from mts where kind = 'trust') > 0
		]]; -- cheat
	};

	actor_session_fetch = {
		params = {uint64, lib.store.inet, int64}, sql = [[
			select a.id, a.nym, a.handle, a.origin, a.bio,
			       a.avataruri, a.rank, a.quota, a.key, a.epithet,
			       extract(epoch from a.knownsince)::bigint,
				   coalesce(a.handle || '@' || s.domain,
				            '@' || a.handle) as xid,

			       au.restrict,
						array['post'  ] <@ au.restrict as can_post,
						array['edit'  ] <@ au.restrict as can_edit,
						array['acct'  ] <@ au.restrict as can_acct,
						array['upload'] <@ au.restrict as can_upload,
						array['censor'] <@ au.restrict as can_censor,
						array['admin' ] <@ au.restrict as can_admin

			from      parsav_auth au
			left join parsav_actors a     on au.uid = a.id
			left join parsav_servers s    on a.origin = s.id

			where au.aid = $1::bigint and au.blacklist = false and
				(au.netmask is null or au.netmask >> $2::inet) and
				($3::bigint = 0 or --slightly abusing the epoch time fmt here, but
					((a.authtime   is null or a.authtime   <= to_timestamp($3::bigint)) and
					 (au.valperiod is null or au.valperiod <= to_timestamp($3::bigint))))
		]];
	};

	actor_powers_fetch = {
		params = {uint64}, sql = [[
			select key, allow from parsav_rights where actor = $1::bigint
		]]
	};

	actor_power_insert = {
		params = {uint64,lib.mem.ptr(int8),uint16}, cmd = true, sql = [[
			insert into parsav_rights (actor, key, allow) values (
				$1::bigint, $2::text, ($3::smallint)::integer::bool
			)
		]]
	};

	actor_power_delete = {
		params = {uint64,lib.mem.ptr(int8)}, cmd = true, sql = [[
			delete from parsav_rights where
				actor = $1::bigint and
				key = $2::text
		]]
	};

	auth_sigtime_user_fetch = {
		params = {uint64}, sql = [[
			select extract(epoch from authtime)::bigint
			from parsav_actors where id = $1::bigint
		]];
	};

	auth_sigtime_user_alter = {
		params = {uint64,int64}, cmd = true, sql = [[
			update parsav_actors set
				authtime = to_timestamp($2::bigint)
				where id = $1::bigint
		]];
	};

	auth_create_pw = {
		params = {uint64, binblob, pstring}, cmd = true, sql = [[
			insert into parsav_auth (uid, name, kind, cred, comment) values (
				$1::bigint,
				(select handle from parsav_actors where id = $1::bigint),
				'pw-sha256', $2::bytea,
				$3::text
			)
		]]
	};

	auth_purge_type = {
		params = {rawstring, uint64, rawstring}, cmd = true, sql = [[
			delete from parsav_auth where
				((uid = 0 and name = $1::text) or uid = $2::bigint) and
				kind like $3::text
		]]
	};

	auth_enum_uid = {
		params = {uint64}, sql = [[
			select aid, kind, comment, netmask, blacklist from parsav_auth where uid = $1::bigint
		]];
	};

	auth_enum_handle = {
		params = {rawstring}, sql = [[
			select aid, kind, comment, netmask, blacklist from parsav_auth where name = $1::text
		]];
	};

	post_save = {
		params = {
			uint64, uint32, int64;
			rawstring, rawstring, rawstring;
		}, cmd = true, sql = [[
			update parsav_posts set
				subject = $4::text,
				acl = $5::text,
				body = $6::text,
				chgcount = $2::integer,
				edited = to_timestamp($3::bigint)
			where id = $1::bigint
		]]
	};

	post_create = {
		params = {
			uint64, rawstring, rawstring, rawstring,
			uint64, uint64, rawstring
		}, sql = [[
			insert into parsav_posts (
				author, subject, acl, body,
				parent, posted, discovered,
				circles, mentions, convoheaduri
			) values (
				$1::bigint, case when $2::text = '' then null else $2::text end,
				$3::text, $4::text, 
				$5::bigint, to_timestamp($6::bigint), now(),
				array[]::bigint[], array[]::bigint[], $7::text
			) returning id
		]]; -- TODO array handling
	};

	post_destroy_prepare = {
		params = {uint64}, cmd = true, sql = [[
			update parsav_posts set
				parent = (select parent from parsav_posts where id = $1::bigint limit 1)
			where parent = $1::bigint
		]]
	};

	post_destroy = {
		params = {uint64}, cmd = true, sql = [[
			delete from parsav_posts where id = $1::bigint
		]]
	};
	
	post_fetch = {
		params = {uint64}, sql = [[
			select a.origin is null,
				p.id, p.author, p.subject, p.acl, p.body,
				extract(epoch from p.posted    )::bigint,
				extract(epoch from p.discovered)::bigint,
				extract(epoch from p.edited    )::bigint,
				p.parent, p.convoheaduri, p.chgcount,
				coalesce(c.value, -1)::smallint

			from parsav_posts as p
				inner join parsav_actors          as a on p.author = a.id
				left join  parsav_actor_conf_ints as c on c.uid    = a.id and c.key = 'ui-accent'
			where p.id = $1::bigint
		]];
	};

	post_enum_parent = {
		params = {uint64}, sql = [[
			select a.origin is null,
				p.id, p.author, p.subject, p.acl, p.body,
				extract(epoch from p.posted    )::bigint,
				extract(epoch from p.discovered)::bigint,
				extract(epoch from p.edited    )::bigint,
				p.parent, p.convoheaduri, p.chgcount,
				coalesce(c.value, -1)::smallint

			from parsav_posts as p
				inner join parsav_actors as a on a.id = p.author
				left join  parsav_actor_conf_ints as c on c.uid = a.id and c.key = 'ui-accent'
			where p.parent = $1::bigint
			order by p.posted, p.discovered asc
		]]
	};

	thread_latest_arrival_calc = {
		params = {uint64}, sql = [[
			with recursive posts(id) as (
				select id from parsav_posts where parent = $1::bigint
			union
				select p.id from parsav_posts as p
					inner join posts on posts.id = p.parent
			), 

			maxes as (
				select unnest(array[max(p.posted), max(p.discovered), max(p.edited)]) as m
					from posts
					inner join parsav_posts as p
						on p.id = posts.id
			)

			select extract(epoch from max(m))::bigint from maxes
		]];
	};

	post_enum_author_uid = {
		params = {uint64,uint64,uint64,uint64, uint64}, sql = [[
			select a.origin is null,
				p.id, p.author, p.subject, p.acl, p.body,
				extract(epoch from p.posted    )::bigint,
				extract(epoch from p.discovered)::bigint,
				extract(epoch from p.edited    )::bigint,
				p.parent, p.convoheaduri, p.chgcount,
				coalesce((select value from parsav_actor_conf_ints as c where
					c.uid = $1::bigint and c.key = 'ui-accent'),-1)::smallint

			from parsav_posts as p
				inner join parsav_actors as a on p.author = a.id
			where p.author = $5::bigint and
				($1::bigint = 0 or p.posted <= to_timestamp($1::bigint)) and
				($2::bigint = 0 or to_timestamp($2::bigint) < p.posted)
			order by (p.posted, p.discovered) desc
			limit case when $3::bigint = 0 then null
			           else $3::bigint end
			offset $4::bigint
		]]
	};

	-- maybe there's some way to unify these two, idk, im tired

	timeline_instance_fetch = {
		params = {uint64, uint64, uint64, uint64}, sql = [[
			select true,
				p.id, p.author, p.subject, p.acl, p.body,
				extract(epoch from p.posted    )::bigint,
				extract(epoch from p.discovered)::bigint,
				extract(epoch from p.edited    )::bigint,
				p.parent, null::text, p.chgcount,
				coalesce(c.value, -1)::smallint

			from parsav_posts as p
				inner join parsav_actors          as a on p.author = a.id
				left join  parsav_actor_conf_ints as c on c.uid    = a.id and c.key = 'ui-accent'
			where
				($1::bigint = 0 or p.posted <= to_timestamp($1::bigint)) and
				($2::bigint = 0 or to_timestamp($2::bigint) < p.posted) and
				(a.origin is null)
			order by (p.posted, p.discovered) desc
			limit case when $3::bigint = 0 then null
			           else $3::bigint end
			offset $4::bigint
		]]
	};

	artifact_instantiate = {
		params = {binblob, binblob, pstring}, sql = [[
			insert into parsav_artifacts (content,hash,mime) values (
				$1::bytea, $2::bytea, $3::text
			) on conflict do nothing returning id
		]];
	};
	artifact_expropriate = {
		params = {uint64, uint64, pstring}, cmd = true, sql = [[
			insert into parsav_artifact_claims (uid,rid,description,folder) values (
				$1::bigint, $2::bigint, $3::text, 'new'
			) on conflict do nothing
		]];
	};
	artifact_quicksearch = {
		params = {binblob}, sql = [[
			select id, (content is null) from parsav_artifacts where hash = $1::bytea
				limit 1
		]];
	};
	artifact_disclaim = {
		params = {uint64, uint64}, cmd = true, sql = [[
			delete from parsav_artifact_claims where
				uid = $1::bigint and
				rid = $2::bigint
		]];
	};
	artifact_excise_forget = {
		-- delete the blasted thing and pretend it never existed
		params = {uint64}, cmd=true, sql = [[
			delete from parsav_artifacts where id = $1::bigint
		]];
	};
	artifact_excise_suppress_nullify = {
		-- banish the thing into the outer darkness, preventing
		-- it from ever being admitted into our databases, and
		-- tabulate a -- list of the degenerates who befouled
		-- their accounts with such wanton and execrable filth,
		-- the better to ensure their long-overdue punishment
		params = {uint64}, cmd=true, sql = [[
			update parsav_artifacts
				set content = null
				where id = $1::bigint;
		]];
	};
	artifact_excise_suppress_breaklinks = {
	-- "ERROR:  cannot insert multiple commands into a prepared
	--  statement" are you fucking shitting me with this shit
		params = {uint64}, sql = [[
			delete from parsav_artifact_claims where
				rid = $1::bigint
			returning uid, description, birth, folder;
		]];
	};
	post_attach_ctl_ins = {
		params = {uint64, uint64}, cmd=true, sql = [[
			update parsav_posts set
				artifacts = artifacts || $2::bigint
			where id = $1::bigint and not
				artifacts @> array[$2::bigint] -- prevent duplication
		]];
	};
	post_attach_ctl_del = {
		params = {uint64, uint64}, cmd=true, sql = [[
			update parsav_posts set
				artifacts = array_remove(artifacts, $2::bigint)
			where id = $1::bigint and
				artifacts @> array[$2::bigint]
		]];
	};

	actor_conf_str_get = {
		params = {uint64, rawstring}, sql = [[
			select value from parsav_actor_conf_strs where
				uid = $1::bigint and
				key = $2::text
			limit 1
		]];
	};
	actor_conf_str_set = {
		params = {uint64, rawstring, rawstring}, cmd = true, sql = [[
			insert into parsav_actor_conf_strs (uid,key,value)
				values ($1::bigint, $2::text, $3::text)
			on conflict (uid,key) do update set value = $3::text
		]];
	};
	actor_conf_str_enum = {
		params = {uint64}, sql = [[
			select value from parsav_actor_conf_strs where uid = $1::bigint
		]];
	};
	actor_conf_str_reset = {
		params = {uint64, rawstring}, cmd = true, sql = [[
			delete from parsav_actor_conf_strs where
				uid = $1::bigint and ($2::text is null or key = $2::text)
		]]
	};

	actor_conf_int_get = {
		params = {uint64, rawstring}, sql = [[
			select value from parsav_actor_conf_ints where
				uid = $1::bigint and
				key = $2::text
			limit 1
		]];
	};
	actor_conf_int_set = {
		params = {uint64, rawstring, uint64}, cmd = true, sql = [[
			insert into parsav_actor_conf_ints (uid,key,value)
				values ($1::bigint, $2::text, $3::bigint)
			on conflict (uid,key) do update set value = $3::bigint
		]];
	};
	actor_conf_int_enum = {
		params = {uint64}, sql = [[
			select value from parsav_actor_conf_ints where uid = $1::bigint
		]];
	};
	actor_conf_int_reset = {
		params = {uint64, rawstring}, cmd = true, sql = [[
			delete from parsav_actor_conf_ints where
				uid = $1::bigint and ($2::text is null or key = $2::text)
		]]
	};
}

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:len(row: intptr, col: intptr)
	return lib.pq.PQgetlength(self.res, row, col)
end
terra pqr:cols() return lib.pq.PQnfields(self.res) end
terra pqr:string(row: intptr, col: intptr) -- not to be exported!!
	if self:null(row,col) then return nil end
	var v = lib.pq.PQgetvalue(self.res, row, col)
	return v
end
terra pqr:_string(row: intptr, col: intptr) -- not to be exported!!
	if self:null(row,col) then return pstring.null() end
	return pstring {
		ptr = lib.pq.PQgetvalue (self.res, row, col);
		ct  = lib.pq.PQgetlength(self.res, row, col);
	}
end
terra pqr:bin(row: intptr, col: intptr) -- not to be exported!! DO NOT FREE
	if self:null(row,col) then return [lib.mem.ptr(uint8)].null() end
	return [lib.mem.ptr(uint8)] {
		ptr = [&uint8](lib.pq.PQgetvalue(self.res, row, col));
		ct = lib.pq.PQgetlength(self.res, row, col);
	}
end
terra pqr:String(row: intptr, col: intptr) -- suitable to be exported
	if self:null(row,col) then return pstring.null() end
	var s = pstring {
		ptr = lib.str.dup(self:string(row,col));
		ct = lib.pq.PQgetlength(self.res, row, col);
	}
	return s
end
terra pqr:bool(row: intptr, col: intptr)
	var v = lib.pq.PQgetvalue(self.res, row, col)
	if @v == 0x01 then return true else return false end
end
terra pqr:cidr(row: intptr, col: intptr)
	var v = lib.pq.PQgetvalue(self.res, row, col)
	var i: lib.store.inet
	if v[0] == 0x02 then i.pv = 4
	elseif v[0] == 0x03 then i.pv = 6
	else lib.bail('invalid CIDR type in stream') end
	i.fixbits = v[1]
	if v[2] ~= 0x1 then lib.bail('expected CIDR but got inet from stream') end
	if i.pv == 4 and v[3] ~= 0x04 or i.pv == 6 and v[3] ~= 0x10 then
		lib.bail('CIDR failed length sanity check') end
	
	var sz: intptr if i.pv == 4 then sz = 4 else sz = 16 end
	for j=0,sz do i.v6[j] = v[4 + j] end -- 😬
	return i
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)
		--i = @[&uint64](v)
		lib.math.netswap_ip(ty, v, &i)
	in i end
end)

local pqt = {
	[lib.store.inet] = function(cidr)
		local tycode = cidr and 0x01 or 0x00
		return terra(i: lib.store.inet, buf: &uint8)
			var sz: intptr
			if i.pv == 4 then sz = 4 else sz = 16 end
			if buf == nil then buf = [&uint8](lib.mem.heapa_raw(sz + 4)) end
			if     i.pv == 4 then buf[0] = 0x02
			elseif i.pv == 6 then buf[0] = 0x03 end
			if cidr then -- our local 'inet' is not quite orthogonal to the
			             -- postgres inet type; tweak it to match (ignore port)
				buf[1] = i.fixbits
			elseif i.pv == 6 then buf[1] = 128
			else buf[1] = 32 end
			buf[2] = tycode
			buf[3] = sz
			for j=0,sz do buf[4 + j] = i.v6[j] end -- 😬
			return buf
		end
	end;
}

local con = symbol(&lib.pq.PGconn)
local prep = {}
local function sqlsquash(s) return s
	:gsub('%%include (.-)%%',function(f)
		return sqlsquash(lib.util.ingest('backend/schema/' .. f))
	end) -- include dependencies
	:gsub('%-%-.-\n','') -- remove disruptive line comments
	:gsub('%-%-.-$','') -- remove unnecessary terminal comments
	:gsub('%s+',' ') -- remove whitespace
	:gsub('^%s*(.-)%s*$','%1') -- chomp
end

for k,q in pairs(queries) do
	local qt = sqlsquash(q.sql)
	local stmt = 'parsavpg_' .. k
	terra q.prep([con])
		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
	prep[#prep + 1] = quote q.prep([con]) end

	local args, casts, counters, fixers, ft, yield = {}, {}, {}, {}, {}, {}
	local dumpers = {}
	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]])
			dumpers[#dumpers+1] = `lib.io.fmt([tostring(i)..'. got rawstr %s\n'], [args[i]])
		elseif ty == lib.store.inet then -- assume not CIDR
			counters[i] = `lib.trn([args[i]].pv == 4,4,16)+4
			casts[i] = quote
				var ipbuf: int8[20]
				;[pqt[lib.store.inet](false)]([args[i]], [&uint8](&ipbuf))
			in &ipbuf[0] end
			dumpers[#dumpers+1] = `lib.io.fmt([tostring(i)..'. got inet\n'])
		elseif ty.ptr_basetype == int8 or ty.ptr_basetype == uint8 then
			counters[i] = `[args[i]].ct
			casts[i] = `[&int8]([args[i]].ptr)
			dumpers[#dumpers+1] = `lib.io.fmt([tostring(i)..'. got ptr %llu %.*s\n'], [args[i]].ct, [args[i]].ct, [args[i]].ptr)
		elseif ty.ptr_basetype == bool then
			counters[i] = `1
			casts[i] = `[&int8]([args[i]].ptr)
			-- dumpers[#dumpers+1] = `lib.io.fmt([tostring(i)..'. got bool = %hhu\n'], @[args[i]].ptr)
		elseif ty:isintegral() then
			counters[i] = ty.bytes
			casts[i] = `[&int8](&[args[i]])
			dumpers[#dumpers+1] = `lib.io.fmt([tostring(i)..'. got int %llu\n'], [args[i]])
			fixers[#fixers + 1] = quote
				[args[i]] = lib.math.netswap(ty, [args[i]])
			end
		end
	end

	local okconst = lib.pq.PGRES_TUPLES_OK
	if q.cmd then okconst = lib.pq.PGRES_COMMAND_OK end
	terra q.exec(src: &lib.store.source, [args])
		var params = arrayof([&int8], [casts])
		var params_sz = arrayof(int, [counters])
		var params_ft = arrayof(int, [ft])
		[fixers]
		--[dumpers]
		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) ~= okconst 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_post(r: &pqr, row: intptr): lib.mem.ptr(lib.store.post)
	var subj: rawstring, sblen: intptr
	var cvhu: rawstring, cvhlen: intptr
	if r:null(row,3)
		then subj = nil sblen = 0
		else subj = r:string(row,3) sblen = r:len(row,3)+1
	end
	if r:null(row,10)
		then cvhu = nil cvhlen = 0
		else cvhu = r:string(row,10) cvhlen = r:len(row,10)+1
	end
	var p = [ lib.str.encapsulate(lib.store.post, {
		subject = { `subj, `sblen };
		acl = {`r:string(row,4), `r:len(row,4)+1};
		body = {`r:string(row,5), `r:len(row,5)+1};
		convoheaduri = { `cvhu, `cvhlen }; --FIXME
	}) ]
	p.ptr.id = r:int(uint64,row,1)
	p.ptr.author = r:int(uint64,row,2)
	if r:null(row,6)
		then p.ptr.posted = 0
		else p.ptr.posted = r:int(uint64,row,6)
	end
	if r:null(row,7)
		then p.ptr.discovered = 0
		else p.ptr.discovered = r:int(uint64,row,7)
	end
	if r:null(row,8)
		then p.ptr.edited = 0
		else p.ptr.edited = r:int(uint64,row,8)
	end
	p.ptr.parent = r:int(uint64,row,9)
	if r:null(row,11)
		then p.ptr.chgcount = 0
		else p.ptr.chgcount = r:int(uint32,row,11)
	end 
	p.ptr.accent = r:int(int16,row,12)
	p.ptr.localpost = r:bool(row,0)

	return p
end
local terra row_to_actor(r: &pqr, row: intptr): lib.mem.ptr(lib.store.actor)
	var a: lib.mem.ptr(lib.store.actor)
	var av: rawstring, avlen: intptr
	var nym: rawstring, nymlen: intptr
	var bio: rawstring, biolen: intptr
	var epi: rawstring, epilen: intptr
	var origin: uint64 = 0
	var handle = r:_string(row, 2)
	if not r:null(row,3) then origin = r:int(uint64,row,3) end

	var avia = lib.str.acc {buf=nil}
	if origin == 0 then
		avia:compose('/avi/',handle)
		av = avia.buf
		avlen = avia.sz+1
	elseif r:null(row,5) then
		av = r:string(row,5)
		avlen = r:len(row,5)+1
	else
		av = '/s/default-avatar.webp'
		avlen = 22
	end

	if r:null(row,1) then nymlen = 0 nym = nil else
		nym = r:string(row,1)
		nymlen = r:len(row,1)+1
	end
	if r:null(row,4) then biolen = 0 bio = nil else
		bio = r:string(row,4)
		biolen = r:len(row,4)+1
	end
	if r:null(row,9) then epilen = 0 epi = nil else
		epi = r:string(row,9)
		epilen = r:len(row,9)+1
	end
	a = [ lib.str.encapsulate(lib.store.actor, {
		nym = {`nym, `nymlen};
		bio = {`bio, `biolen};
		epithet = {`epi, `epilen};
		avatar = {`av,`avlen};
		handle = {`handle.ptr, `handle.ct + 1};
		xid = {`r:string(row, 11); `r:len(row,11) + 1};
	}) ]
	a.ptr.id = r:int(uint64, row, 0);
	a.ptr.rights = lib.store.rights_default();
	a.ptr.rights.rank = r:int(uint16, row, 6);
	a.ptr.rights.quota = r:int(uint32, row, 7);
	a.ptr.rights.invites = r:int(uint32, row, 12);
	a.ptr.knownsince = r:int(int64,row, 10);
	if r:null(row,8) then
		a.ptr.key.ct = 0 a.ptr.key.ptr = nil
	else
		a.ptr.key = r:bin(row,8)
	end
	a.ptr.origin = origin
	if avia.buf ~= nil then avia:free() end
	return a
end

local privmap = lib.store.privmap

local checksha = function(src, hash, origin, username, pw)
	local validate = function(kind, cred, credlen)
		return quote 
			var r = queries.actor_auth_pw.exec(
				[&lib.store.source](src),
				username,
				kind,
				[lib.mem.ptr(int8)] {ptr=[&int8](cred), ct=credlen},
				origin)
			if r.sz > 0 then -- found a record! stop here
				var aid = r:int(uint64, 0,0)
				var uid = r:int(uint64, 0,1)
				var name = r:String(0,2)
				r:free()
				return aid, uid, name
			end
		end
	end
	
	local out = symbol(uint8[64])
	local vdrs = {}

		local alg = lib.md['MBEDTLS_MD_SHA' .. tostring(hash)]
		vdrs[#vdrs+1] = quote
			if lib.md.mbedtls_md(lib.md.mbedtls_md_info_from_type(alg),
				[&uint8](pw.ptr), pw.ct, out) ~= 0 then
				lib.bail('hashing failure!')
			end
			[ validate(string.format('pw-sha%u', hash), `&out[0], hash / 8) ]
		end

	return quote
		lib.dbg(['searching for hashed password credentials in format SHA' .. tostring(hash)])
		var [out]
		[vdrs]
		lib.dbg(['could not find password hash'])
	end
end

local schema = sqlsquash(lib.util.ingest('backend/schema/pgsql.sql'))
local obliterator = sqlsquash(lib.util.ingest('backend/schema/pgsql-drop.sql'))

local privupdate = terra(
	src: &lib.store.source,
	ac: &lib.store.actor
): {}
	var pdef: lib.store.powerset pdef:clear()
	var map = array([privmap])
	for i=0, [map.type.N] do
		var d = pdef and map[i].priv
		var u = ac.rights.powers and map[i].priv
		queries.actor_power_delete.exec(src, ac.id, map[i].name)
		if d:sz() > 0 and u:sz() == 0 then
			lib.dbg('blocking power ', {map[i].name.ptr, map[i].name.ct})
			queries.actor_power_insert.exec(src, ac.id, map[i].name, 0)
		elseif d:sz() == 0 and u:sz() > 0 then
			lib.dbg('granting power ', {map[i].name.ptr, map[i].name.ct})
			queries.actor_power_insert.exec(src, ac.id, map[i].name, 1)
		end
	end
end

local getpow = terra(
	src: &lib.store.source,
	uid: uint64
): lib.store.powerset
	var powers = lib.store.rights_default().powers
	var map = array([privmap])
	var r = queries.actor_powers_fetch.exec(src, uid)

	for i=0, r.sz do
		for j=0, [map.type.N] do
			var pn = r:_string(i,0)
			if map[j].name:cmp(pn) then
				if r:bool(i,1)
					then powers = powers + map[j].priv
					else powers = powers - map[j].priv
				end
			end
		end
	end

	return powers
end


local txdo = terra(src: &lib.store.source)
	var res = lib.pq.PQexec([&lib.pq.PGconn](src.handle), 'begin')
	if lib.pq.PQresultStatus(res) == lib.pq.PGRES_COMMAND_OK then
		lib.dbg('beginning pgsql transaction')
		return true
	else
		lib.warn('backend pgsql - failed to begin transaction: \n', lib.pq.PQresultErrorMessage(res))
		return false
	end
end

local txdone = terra(src: &lib.store.source)
	var res = lib.pq.PQexec([&lib.pq.PGconn](src.handle), 'end')
	if lib.pq.PQresultStatus(res) == lib.pq.PGRES_COMMAND_OK then
		lib.dbg('completing pgsql transaction')
		return true
	else
		lib.warn('backend pgsql - failed to complete transaction: \n', lib.pq.PQresultErrorMessage(res))
		return false
	end
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
			lib.warn('critical failure to secure postgres connection')
			lib.pq.PQfinish(con)
			return nil
		end

		defer lib.pq.PQclear(res)
		if lib.pq.PQresultStatus(res) ~= lib.pq.PGRES_TUPLES_OK then
			lib.warn('failed to secure postgres connection')
			lib.pq.PQfinish(con)
			return nil
		end

		return con
	end];

	close = [terra(src: &lib.store.source) lib.pq.PQfinish([&lib.pq.PGconn](src.handle)) end];

	tx_enter = txdo, tx_complete = txdone;

	conprep = [terra(src: &lib.store.source, mode: lib.store.prepmode.t)
		var [con] = [&lib.pq.PGconn](src.handle)
		if mode == lib.store.prepmode.full then [prep]
		elseif mode == lib.store.prepmode.conf or
		       mode == lib.store.prepmode.admin then 
			queries.conf_get.prep(con)
			queries.conf_set.prep(con)
			queries.conf_reset.prep(con)
			if mode == lib.store.prepmode.admin then 
			end
		else lib.bail('unsupported connection preparation mode') end
	end];

	dbsetup = [terra(src: &lib.store.source)
		var res = lib.pq.PQexec([&lib.pq.PGconn](src.handle), schema)
		if lib.pq.PQresultStatus(res) == lib.pq.PGRES_COMMAND_OK then
			lib.report('successfully instantiated schema in database')
			return true
		else
			lib.warn('backend pgsql - failed to initialize database: \n', lib.pq.PQresultErrorMessage(res))
			return false
		end
	end];

	obliterate_everything = [terra(src: &lib.store.source)
		var res = lib.pq.PQexec([&lib.pq.PGconn](src.handle), obliterator)
		if lib.pq.PQresultStatus(res) == lib.pq.PGRES_COMMAND_OK then
			lib.report('successfully wiped out everything parsav-related in database')
			return true
		else
			lib.warn('backend pgsql - failed to obliterate database: \n', lib.pq.PQresultErrorMessage(res))
			return false
		end
	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.mem.ptr(lib.store.actor)] { ct = 0, ptr = nil }
		else defer r:free()
			var a = row_to_actor(&r, 0)
			a.ptr.rights.powers = getpow(src, uid)
			a.ptr.source = src
			return a
		end
	end];

	actor_fetch_xid = [terra(src: &lib.store.source, xid: lib.mem.ptr(int8))
		var r = queries.actor_fetch_xid.exec(src, xid)
		if r.sz == 0 then
			return [lib.mem.ptr(lib.store.actor)] { ct = 0, ptr = nil }
		else defer r:free()
			var a = row_to_actor(&r, 0)
			a.ptr.rights.powers = getpow(src, a.ptr.id)
			a.ptr.source = src
			return a
		end
	end];

	actor_enum = [terra(src: &lib.store.source)
		var r = queries.actor_enum.exec(src)
		if r.sz == 0 then
			return [lib.mem.lstptr(lib.store.actor)].null()
		else defer r:free()
			var mem = lib.mem.heapa([lib.mem.ptr(lib.store.actor)], r.sz)
			for i=0,r.sz do
				mem.ptr[i] = row_to_actor(&r, i)
				mem(i).ptr.source = src
			end
			return [lib.mem.lstptr(lib.store.actor)] { ct = r.sz, ptr = mem.ptr }
		end
	end];

	actor_enum_local = [terra(src: &lib.store.source)
		var r = queries.actor_enum_local.exec(src)
		if r.sz == 0 then
			return [lib.mem.lstptr(lib.store.actor)].null()
		else defer r:free()
			var mem = lib.mem.heapa([lib.mem.ptr(lib.store.actor)], r.sz)
			for i=0,r.sz do
				mem.ptr[i] = row_to_actor(&r, i)
				mem(i).ptr.source = src
			end
			return [lib.mem.lstptr(lib.store.actor)] { ct = r.sz, ptr = mem.ptr }
		end
	end];

	actor_auth_how = [terra(
			src: &lib.store.source,
			ip: lib.store.inet,
			username: rawstring
		): {lib.store.credset, bool}
		var cs: lib.store.credset cs:clear();
		var r = queries.actor_auth_how.exec(src, username, ip) 
		if r.sz == 0 then return cs, false end -- just in case
		defer r:free()
		(cs.pw << r:bool(0,0))
		(cs.otp << r:bool(0,1))
		(cs.challenge << r:bool(0,2))
		(cs.trust << r:bool(0,3))
		return cs, true
	end];
	 
	actor_auth_pw = [terra(
			src: &lib.store.source,
			ip: lib.store.inet,
			username: lib.mem.ptr(int8),
			cred: lib.mem.ptr(int8)
		): {uint64, uint64, pstring}

		[ checksha(`src, 256, ip, username, cred) ] -- most common
		[ checksha(`src, 512, ip, username, cred) ] -- most secure
		[ checksha(`src, 384, ip, username, cred) ] -- weird
		[ checksha(`src, 224, ip, username, cred) ] -- weirdest

		-- TODO: check pbkdf2-hmac
		-- TODO: check OTP
		return 0, 0, pstring.null()
	end];

	actor_stats = [terra(src: &lib.store.source, uid: uint64)
		var r = queries.actor_stats.exec(src, uid)
		if r.sz == 0 then lib.bail('error fetching actor stats!') end
		var s: lib.store.actor_stats
		s.posts = r:int(uint64, 0, 0)
		s.follows = r:int(uint64, 0, 1)
		s.followers = r:int(uint64, 0, 2)
		s.mutuals = r:int(uint64, 0, 3)
		return s
	end];

	actor_session_fetch = [terra(
		src: &lib.store.source,
		aid: uint64,
		ip : lib.store.inet,
		issuetime: lib.store.timepoint
	): { lib.stat(lib.store.auth), lib.mem.ptr(lib.store.actor) }
		var r = queries.actor_session_fetch.exec(src, aid, ip, issuetime)
		if r.sz == 0 then goto fail end
		do defer r:free()

			if r:null(0,0) then goto fail end

			var a = row_to_actor(&r, 0)
			a.ptr.source = src

			var au = [lib.stat(lib.store.auth)] { ok = true }
			au.val.aid = aid
			au.val.uid = a.ptr.id
			if not r:null(0,14) then -- restricted?
				au.val.privs:clear()
				(au.val.privs.post   << r:bool(0,15)) 
				(au.val.privs.edit   << r:bool(0,16))
				(au.val.privs.acct   << r:bool(0,17))
				(au.val.privs.upload << r:bool(0,18))
				(au.val.privs.censor << r:bool(0,19))
				(au.val.privs.admin  << r:bool(0,20))
			else au.val.privs:fill() end

			return au, a
		end

		::fail:: return [lib.stat   (lib.store.auth) ] { ok = false        },
			            [lib.mem.ptr(lib.store.actor)] { ptr = nil, ct = 0 }
	end];

	post_create = [terra(
		src: &lib.store.source,
		post: &lib.store.post
	): uint64
		var r = queries.post_create.exec(src,
			post.author,post.subject,post.acl,post.body,
			post.parent,post.posted,post.convoheaduri
		) 
		if r.sz == 0 then return 0 end
		defer r:free()
		var id = r:int(uint64,0,0)
		post.source = src
		return id
	end];

	post_destroy = [terra(
		src: &lib.store.source,
		post: uint64
	): {}
		txdo(src)
			queries.post_destroy_prepare.exec(src, post)
			queries.post_destroy.exec(src, post)
		txdone(src)
	end];

	post_fetch = [terra(
		src: &lib.store.source,
		post: uint64
	): lib.mem.ptr(lib.store.post)
		var r = queries.post_fetch.exec(src, post)
		if r.sz == 0 then return [lib.mem.ptr(lib.store.post)].null() end
		var p = row_to_post(&r, 0)
		p.ptr.source = src
		return p
	end];

	timeline_instance_fetch = [terra(src: &lib.store.source, rg: lib.store.range)
		var r = pqr { sz = 0 }
		var A,B,C,D = rg:matrix() -- :/
		r = queries.timeline_instance_fetch.exec(src,A,B,C,D)
		
		var ret: lib.mem.ptr(lib.mem.ptr(lib.store.post)) ret:init(r.sz)
		for i=0,r.sz do
			ret.ptr[i] = row_to_post(&r, i) -- MUST FREE ALL
			ret.ptr[i].ptr.source = src
		end

		return ret
	end];

	post_enum_author_uid = [terra(
		src: &lib.store.source,
		uid: uint64,
		rg: lib.store.range
	): lib.mem.ptr(lib.mem.ptr(lib.store.post))
		var r = pqr { sz = 0 }
		var A,B,C,D = rg:matrix() -- :/
		r = queries.post_enum_author_uid.exec(src,A,B,C,D,uid)
		
		var ret: lib.mem.ptr(lib.mem.ptr(lib.store.post)) ret:init(r.sz)
		for i=0,r.sz do
			ret.ptr[i] = row_to_post(&r, i) -- MUST FREE ALL
			ret.ptr[i].ptr.source = src
		end

		return ret
	end];

	actor_powers_fetch = getpow;
	actor_save = [terra(
		src: &lib.store.source,
		ac: &lib.store.actor
	): {}
		var avatar = ac.avatar
		if ac.origin == 0 then avatar = nil end
		queries.actor_save.exec(src,
			ac.id, ac.nym, ac.handle,
			ac.bio, ac.epithet, avatar,
			ac.avatarid, ac.rights.rank, ac.rights.quota, ac.rights.invites)
	end];

	actor_save_privs = privupdate;

	actor_create = [terra(
		src: &lib.store.source,
		ac: &lib.store.actor
	): uint64
		var r = queries.actor_create.exec(src,ac.nym, ac.handle, ac.origin, ac.knownsince, ac.bio, ac.avatar, ac.key, ac.epithet, ac.rights.rank, ac.rights.quota,ac.rights.invites)
		if r.sz == 0 then lib.bail('failed to create actor!') end
		ac.id = r:int(uint64,0,0)

		-- check against default rights, insert records for wherever powers differ
		lib.dbg('created new actor, establishing powers')
		privupdate(src,ac)

		lib.dbg('powers established')
		return ac.id
	end];

	actor_purge_uid = [terra(
		src: &lib.store.source,
		uid: uint64
	) queries.actor_purge_uid.exec(src,uid) end];

	auth_enum_uid = [terra(
		src: &lib.store.source,
		uid: uint64
	): lib.mem.ptr(lib.mem.ptr(lib.store.auth))
		var r = queries.auth_enum_uid.exec(src,uid)
		if r.sz == 0 then return [lib.mem.ptr(lib.mem.ptr(lib.store.auth))].null() end
		var ret = lib.mem.heapa([lib.mem.ptr(lib.store.auth)], r.sz)
		for i=0, r.sz do
			var kind = r:_string(i, 1)
			var comment = r:_string(i, 2)
			var a = [ lib.str.encapsulate(lib.store.auth, {
				kind = {`kind.ptr, `kind.ct};
				comment = {`comment.ptr, `comment.ct};
			}) ]
			a.ptr.aid = r:int(uint64, i, 0)
			a.ptr.netmask = r:cidr(i, 3)
			a.ptr.blacklist = r:bool(i, 4)
			ret.ptr[i] = a
		end
		return ret
	end];

	auth_attach_pw = [terra(
		src: &lib.store.source,
		uid: uint64,
		reset: bool,
		pw: pstring,
		comment: pstring
	): {}
		var hash: uint8[lib.crypt.algsz.sha256]
		if lib.md.mbedtls_md(lib.md.mbedtls_md_info_from_type(lib.crypt.alg.sha256.id),
			[&uint8](pw.ptr), pw.ct, &hash[0]) ~= 0 then
			lib.bail('cannot hash password')
		end
		if reset then queries.auth_purge_type.exec(src, nil, uid, 'pw-%') end
		queries.auth_create_pw.exec(src, uid, binblob {ptr = &hash[0], ct = [hash.type.N]}, comment)
	end];

	auth_purge_pw = [terra(src: &lib.store.source, uid: uint64, handle: rawstring): {}
		queries.auth_purge_type.exec(src, handle, uid, 'pw-%')
	end];

	auth_purge_otp = [terra(src: &lib.store.source, uid: uint64, handle: rawstring): {}
		queries.auth_purge_type.exec(src, handle, uid, 'otp-%')
	end];

	auth_purge_trust = [terra(src: &lib.store.source, uid: uint64, handle: rawstring): {}
		queries.auth_purge_type.exec(src, handle, uid, 'trust')
	end];

	artifact_quicksearch = [terra(
		src: &lib.store.source,
		hash: binblob
	): {uint64, bool}
		var srec = queries.artifact_quicksearch.exec(src, hash)
		if srec.sz > 0 then
			defer srec:free()
			var id = srec:int(uint64,0,0)
			var ban = srec:bool(0,1)
			return id, ban
		else return 0, false end
	end];

	artifact_instantiate = [terra(
		src: &lib.store.source,
		artifact: binblob,
		mime: pstring
	): uint64
		var arthash: uint8[lib.crypt.algsz.sha256]
		if lib.md.mbedtls_md(lib.md.mbedtls_md_info_from_type(lib.crypt.alg.sha256.id),
			artifact.ptr, artifact.ct, &arthash[0]) ~= 0 then
			lib.bail('could not hash artifact to be instantiated')
		end
		var hashb = binblob{ptr=&arthash[0],ct=[arthash.type.N]}

		var srec = queries.artifact_quicksearch.exec(src, hashb)
		if srec.sz > 0 then
			defer srec:free()
			var ban = srec:bool(0,1)
			if ban then
				lib.report('user attempted to instantiate forsaken artifact')
				return 0
			end
			var oldid = srec:int(uint64,0,0)
			return oldid
		else -- not in db, insert
			var nrec = queries.artifact_instantiate.exec(src, artifact, hashb, mime)
			if nrec.sz == 0 then
				lib.warn('failed to instantiate artifact -- are you running out of storage?')
				return 0
			else defer nrec:free()
				var newid = nrec:int(uint64,0,0)
				return newid
			end
		end
	end];

	post_attach_ctl = [terra(
		src: &lib.store.source,
		post: uint64,
		artifact: uint64,
		detach: bool
	): {}
		if detach
			then queries.post_attach_ctl_del.exec(src,post,artifact)
			else queries.post_attach_ctl_ins.exec(src,post,artifact)
		end
	end];
	
	post_save = [terra(
		src: &lib.store.source,
		post: &lib.store.post
	): {}
		queries.post_save.exec(src,
			post.id, post.chgcount, post.edited,
			post.subject, post.acl, post.body)
	end];

	post_enum_parent = [terra(
		src: &lib.store.source,
		post: uint64
	): lib.mem.ptr(lib.mem.ptr(lib.store.post))
		var r = queries.post_enum_parent.exec(src,post)
		if r.sz == 0 then
			return [lib.mem.ptr(lib.mem.ptr(lib.store.post))].null()
		end
		defer r:free()
		var lst = lib.mem.heapa([lib.mem.ptr(lib.store.post)], r.sz)

		for i=0, r.sz do lst.ptr[i] = row_to_post(&r, i) end

		return lst
	end];

	thread_latest_arrival_calc = [terra(
		src: &lib.store.source,
		post: uint64
	): lib.store.timepoint
		var r = queries.thread_latest_arrival_calc.exec(src,post)
		if r.sz == 0 or r:null(0,0) then return 0 end
		var tp: lib.store.timepoint = r:int(int64,0,0)
		r:free()
		return tp
	end];

	auth_sigtime_user_fetch = [terra(
		src: &lib.store.source,
		uid: uint64
	): lib.store.timepoint
		var r = queries.auth_sigtime_user_fetch.exec(src, uid)
		if r.sz > 0 then defer r:free()
			var t = r:int(int64,0,0)
			return t
		else return 0 end
	end];

	auth_sigtime_user_alter = [terra(
		src: &lib.store.source,
		uid: uint64,
		time: lib.store.timepoint
	): {} queries.auth_sigtime_user_alter.exec(src, uid, time) end];

	actor_conf_str_enum = nil;
	actor_conf_str_get = [terra(src: &lib.store.source, uid: uint64, key: rawstring): pstring
			var r = queries.actor_conf_str_get.exec(src, uid, key)
			if r.sz > 0 then
				var ret = r:String(0,0)
				r:free()
				return ret
			else return pstring.null() end
		end];
	actor_conf_str_set = [terra(src: &lib.store.source, uid: uint64, key: rawstring, value: rawstring): {}
			queries.actor_conf_str_set.exec(src,uid,key,value) end];
	actor_conf_str_reset = [terra(src: &lib.store.source, uid: uint64, key: rawstring): {}
			queries.actor_conf_str_reset.exec(src,uid,key) end];

	actor_conf_int_enum = nil;
	actor_conf_int_get = [terra(src: &lib.store.source, uid: uint64, key: rawstring)
			var r = queries.actor_conf_int_get.exec(src, uid, key)
			if r.sz > 0 then
				var ret = r:int(uint64,0,0)
				r:free()
				return ret, true
			end
			return 0, false
		end];
	actor_conf_int_set = [terra(src: &lib.store.source, uid: uint64, key: rawstring, value: uint64): {}
			queries.actor_conf_int_set.exec(src,uid,key,value) end];
	actor_conf_int_reset = [terra(src: &lib.store.source, uid: uint64, key: rawstring): {}
			queries.actor_conf_int_reset.exec(src,uid,key) end];

	actor_auth_register_uid = nil; -- TODO better support non-view based auth
}

return b
