parsav  Artifact [18ea7fb9d0]

Artifact 18ea7fb9d053ec0f0b251d21f644225bd04ee68b5ad3a11c3002845eee907108:


-- vim: ft=terra
local pstring = lib.mem.ptr(int8)
local binblob = lib.mem.ptr(uint8)
local queries = {
	server_setup_self = {
		params = {rawstring,binblob,int64}, cmd = true, sql = [[
			insert into parsav_servers (id, domain, key, parsav, knownsince)
				values (0, $1::text, $2::bytea, true, $3::bigint)
		]];
	};

	server_fetch_sid = {
		params = {uint64}, sql = [[
			select domain, key, knownsince, parsav from parsav_servers
				where id = $1::bigint
		]];
	};

	conf_get = {
		params = {pstring}, sql = [[
			select value from parsav_config
				where key = $1::text limit 1
		]];
	};

	conf_set = {
		params = {pstring,pstring}, 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 (pg_temp.parsavpg_translate_actor(a)).*
				from parsav_actors as a
				where a.id = $1::bigint
		]];
	};

	actor_fetch_xid = {
		params = {pstring}, sql = [[
			with txd as (
				select (pg_temp.parsavpg_translate_actor(a)).* from parsav_actors as a
			)
			select * from txd as a where $1::text = xid or
				  (a.origin is null and
					  $1::text = a.handle or
					  $1::text = (a.handle ||'@'||
				        (select value from parsav_config where key='domain')))
		]];
	};

	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,authtime
			) values ($1::text, $2::text,
				case when $3::bigint = 0 then null
				     else $3::bigint end,
				$4::bigint,
				$5::bigint, $6::bigint, $7::bytea,
				$8::text, $9::smallint, $10::integer,
				$11::integer,$4::bigint
			) returning id
		]];
	};

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

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

	actor_enum = {
		params = {}, sql = [[
			select (pg_temp.parsavpg_translate_actor(a)).*
				from parsav_actors as a

			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 = <rel:follow>
			),
			followers as (
				select relator as user from parsav_rels
					where relatee = $1::bigint and kind = <rel: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)
			)
		]]
	};

	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 (pg_temp.parsavpg_translate_actor(a)).*,

			       au.restrict,
						array['post'    ] <@ au.restrict,
						array['edit'    ] <@ au.restrict,
						array['account' ] <@ au.restrict,
						array['upload'  ] <@ au.restrict,
						array['artifact'] <@ au.restrict,
						array['moderate'] <@ au.restrict,
						array['admin'   ] <@ au.restrict,
						array['invite'  ] <@ au.restrict

			from      parsav_auth au
			left join parsav_actors a     on au.uid = a.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   <= $3::bigint) and
					 (au.valperiod is null or au.valperiod <= $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
		]]
	};

	actor_rel_create = {
		params = {uint16,uint64, uint64, int64}, cmd = true, sql = [[
			insert into parsav_rels (kind,relator,relatee,since)
				values($1::smallint, $2::bigint, $3::bigint, $4::bigint)
			on conflict do nothing
		]];
	};

	actor_rel_destroy = {
		params = {uint16,uint64, uint64}, cmd = true, sql = [[
			delete from parsav_rels where
				kind  = $1::smallint and
				relator = $2::bigint and
				relatee = $3::bigint
		]];
	};

	actor_rel_enum = {
		params = {uint64, uint64}, sql = [[
			select kind from parsav_rels where
				relator = $1::bigint and
				relatee = $2::bigint
		]];
	};

	actor_notice_enum = {
		params = {uint64}, sql = [[
			select (notice).* from pg_temp.parsavpg_notices
			where rcpt = $1::bigint
		]];
	};

	circle_search = {
		params = {uint64,uint64}, sql = [[
			select name, id, owner, array_length(members,1) from parsav_circles where
				($1::bigint = 0 or $1::bigint = owner) and
				($2::bigint = 0 or $2::bigint = id)
		]];
	};

	circle_create = {
		params = {uint64,pstring}, sql = [[
			insert into parsav_circles (owner,name)
				values ($1::bigint, $2::text)
			returning id
		]];
	};

	circle_destroy = {
		params = {uint64,uint64}, cmd = true, sql = [[
			delete from parsav_circles where
				owner = $1::bigint and
				id = $2::bigint
		]];
	};

	circle_memberships_uid = {
		params = {uint64, uint64}, sql = [[
			select name, id, owner, array_length(members,1) from parsav_circles where
				owner   =  $1::bigint and
				members @> array[$2::bigint]
		]];
	};

	circle_members_fetch_cid = {
		params = {uint64}, sql = [[
			select unnest(members) from parsav_circles where
				id = $1::bigint
		]];
	};

	circle_members_fetch_name = {
		params = {uint64, pstring}, sql = [[
			select unnest(members) from parsav_circles where
				($1::bigint = 0 or owner = $1::bigint) and
				name = $2::text
		]];
	};

	circle_members_add_uid = {
		params = {uint64, uint64}, cmd = true, sql = [[
			update parsav_circles set
				members = members || $2::bigint
			where id = $1::bigint
		]];
	};

	circle_members_del_uid = {
		params = {uint64, uint64}, cmd = true, sql = [[
			update parsav_circles set
				members = array_remove(members, $2::bigint)
			where id = $1::bigint
		]];
	};

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

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

	auth_create_pw = {
		params = {uint64, binblob, int64, pstring}, sql = [[
			insert into parsav_auth (uid, name, kind, cred, valperiod, comment) values (
				$1::bigint,
				(select handle from parsav_actors where id = $1::bigint),
				'pw-sha256', $2::bytea,
				$3::bigint, $4::text
			) on conflict (name,kind,cred) do update set comment = $4::text returning aid
		]]
	};

	auth_create_rsa = {
		params = {uint64, binblob, int64, pstring}, sql = [[
			insert into parsav_auth (uid, name, kind, cred, valperiod, comment) values (
				$1::bigint,
				(select handle from parsav_actors where id = $1::bigint),
				'challenge-rsa', $2::bytea,
				$3::bigint, $4::text
			) on conflict (name,kind,cred) do update set comment = $4::text returning aid
		]]
	};

	auth_destroy_aid = {
		params = {uint64}, cmd = true, sql = [[
			delete from parsav_auth where aid = $1::bigint
		]];
	};

	auth_destroy_aid_uid = {
		params = {uint64,uint64}, cmd = true, sql = [[
			delete from parsav_auth where aid = $1::bigint and uid = $2::bigint
		]];
	};

	auth_privs_clear = {
		params = {uint64}, cmd = true, sql = [[
			update parsav_auth set restrict = array[]::text[] where aid = $1::bigint
		]];
	};

	auth_priv_install = {
		params = {uint64,pstring}, cmd = true, sql = [[
			update parsav_auth set restrict = restrict || $2::text where aid = $1::bigint
		]];
	};

	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_purge_aid = {
		params = {uint64}, cmd = true, sql = [[
			delete from parsav_auth where aid = $1::bigint
		]]
	};

	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
		]];
	};

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

	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 = $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, $6::bigint, $6::bigint,
				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 (p.post).*
			from pg_temp.parsavpg_known_content as p
				where (p.post).id = $1::bigint and (p.post).rtdby = 0
		]]
	};

	post_enum_parent = {
		params = {uint64}, sql = [[
			select (p.post).*
			from pg_temp.parsavpg_known_content as p
				where (p.post).parent = $1::bigint and (p.post).rtdby = 0
				order by (p.post).posted, (p.post).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 max(m)::bigint from maxes
		]];
	};

	post_react_simple = {
		params = {uint64, uint64, pstring, int64}, sql = [[
			insert into parsav_acts (kind,actor,subject,time) values (
				$3::text, $1::bigint, $2::bigint, $4::bigint
			) returning id
		]];
	};

	post_react_cancel = {
		params = {uint64, uint64, pstring}, cmd = true, sql = [[
			delete from parsav_acts where
				actor = $1::bigint and
				subject = $2::bigint and
				kind = $3::text
		]];
	};

	post_react_delete = {
		params = {uint64}, cmd = true, sql = [[
			delete from parsav_acts where id = $1::bigint
		]];
	};

	post_reacts_fetch_uid = {
		params = {uint64, uint64, pstring}, sql = [[
			select id, actor, subject, kind, body, time from parsav_acts where
				($1::bigint = 0 or actor   = $1::bigint) and
				($2::bigint = 0 or subject = $2::bigint) and
				($3::text is null or kind  = $3::text  )
		]];
	};

	post_act_fetch_notice = {
		params = {uint64}, sql = [[
			select (pg_temp.parsavpg_translate_act(a)).*
			from parsav_acts as a
				where id = $1::bigint
		]];
	};

	post_enum_author_uid = {
		params = {uint64,uint64,uint64,uint64, uint64}, sql = [[
			select (c.post).*
			from pg_temp.parsavpg_known_content as c

			where c.promoter = $5::bigint and
				($1::bigint = 0 or c.tltime   <= $1::bigint) and
				($2::bigint = 0 or $2::bigint <  c.tltime)
			order by c.tltime 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 (c.post).*
			from pg_temp.parsavpg_known_content as c

			where (c.post).localpost = true and
				($1::bigint = 0 or c.tltime   <= $1::bigint) and
				($2::bigint = 0 or $2::bigint <  c.tltime)
			order by c.tltime desc

			limit case when $3::bigint = 0 then null
					   else $3::bigint end
			offset $4::bigint
		]];
	};

	timeline_circle_fetch = {
		params = {uint64, uint64, uint64, uint64, uint64}, sql = [[
			with circle as (
				select unnest(members) from parsav_circles where id = $1::bigint
			)

			select (c.post).*
			from pg_temp.parsavpg_known_content as c

			where ($2::bigint = 0 or c.tltime   <= $2::bigint) and
				  ($3::bigint = 0 or $3::bigint <  c.tltime) and
				  (c.promoter in (table circle) or
				   c.promoter = (select owner from parsav_circles where id = $1::bigint))

			order by c.tltime desc

			limit case when $4::bigint = 0 then null
					   else $4::bigint end
			offset $5::bigint
		]];
	};

	timeline_actor_fetch = {
		params = {uint64, uint64, uint64, uint64, uint64}, sql = [[
			with followed as (
				select relatee from parsav_rels where
					kind = <rel:follow> and
					relator = $1::bigint
			), avoided as ( -- not strictly necessary but lets us minimize how much data needs to be sent back to parsav for filtering
				select relatee as avoidee from parsav_rels where
					kind = <rel:avoid> or kind = <rel:mute> and
					relator = $1::bigint
				union select relator as avoidee from parsav_rels where
					kind = <rel:exclude> and
					relatee = $1::bigint
			)

			select (c.post).*
			from pg_temp.parsavpg_known_content as c

			where ($2::bigint = 0 or c.tltime   <= $2::bigint) and
				  ($3::bigint = 0 or $3::bigint <  c.tltime) and
				  (c.promoter in (table followed) or
				   c.promoter = $1::bigint) and
				  not ((c.post).author in (table avoided)) and
				  not (c.promoter      in (table avoided))
			order by c.tltime desc

			limit case when $4::bigint = 0 then null
					   else $4::bigint end
			offset $5::bigint
		]];
	};

	artifact_instantiate = {
		params = {binblob, binblob, pstring, int64}, sql = [[
			insert into parsav_artifacts (content,hash,mime,birth) values (
				$1::bytea, $2::bytea, $3::text,$4::bigint
			) on conflict do nothing returning id
		]];
	};
	artifact_expropriate = {
		params = {uint64, uint64, pstring, pstring, int64}, cmd = true, sql = [[
			insert into parsav_artifact_claims (uid,rid,description,folder,birth) values (
				$1::bigint, $2::bigint, $3::text, $4::text, $5::bigint
			) 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_collect_garbage = {
		params = {}, cmd = true, sql = [[
			delete from parsav_artifacts where
				id not in (select rid from parsav_artifact_claims) and
				content is not null -- avoid stepping on toes of ban mech
		]];
	};
	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;
		]];
	};
	artifact_enum_uid = {
		params = {uint64, pstring}, sql = [[
			select (pg_temp.parsavpg_translate_artifact(a)).*
			from parsav_artifact_claims as a where uid = $1::bigint and
				($2::text is null or
				 ($2::text = '' and folder is null) or
				 $2::text = folder)
			order by birth desc
		]];
	};
	artifact_fetch = {
		params = {uint64, uint64}, sql = [[
			select (pg_temp.parsavpg_translate_artifact(a)).*
			from parsav_artifact_claims as a where uid = $1::bigint and rid = $2::bigint
		]];
	};
	artifact_load = {
		params = {uint64}, sql = [[
			select content, mime from parsav_artifacts where id = $1::bigint
		]];
	};
	artifact_folder_enum = {
		params = {uint64}, sql = [[
			select distinct folder from parsav_artifact_claims where
				uid = $1::bigint and folder is not null
				order by 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, pstring}, sql = [[
			select value from parsav_actor_conf_strs where
				uid = $1::bigint and
				key = $2::text
			limit 1
		]];
	};
	actor_conf_str_set = {
		params = {uint64, pstring, pstring}, 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, pstring}, 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, pstring}, sql = [[
			select value from parsav_actor_conf_ints where
				uid = $1::bigint and
				key = $2::text
			limit 1
		]];
	};
	actor_conf_int_set = {
		params = {uint64, pstring, 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, pstring}, 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 sqlvars = {
 -- unforunately necessary to generate IDs that fill the whole
 -- address space (due to floating-point shenanigans); there's
 -- just no good way of doing this with SQL
	['def:uniq'] = [[bigint primary key default (
		 (random() * ((1::bigint << 32) - 1)    )::bigint << 32 | 
		 (random() * ((1::bigint << 32) - 2) + 1)::bigint
	)]]
}
for i, n in ipairs(lib.store.noticetype.members) do
	sqlvars['notice:' .. n] = lib.store.noticetype[n]:asvalue()
end

for i, n in ipairs(lib.store.relation.members) do
	sqlvars['rel:' .. n] = lib.store.relation.idvmap[n]
end

local con = symbol(&lib.pq.PGconn)
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('<(%g%g-)>',function(r) return tostring(sqlvars[r]) end)
	:gsub('%s+',' ') -- remove whitespace
	:gsub('^%s*(.-)%s*$','%1') -- chomp
end

-- to simplify queries and reduce development headaches in general, we
-- offload as much logic as possible into views. to avoid versioning
-- difficulties, these views are not part of the schema, but are rather
-- uploaded to the database at the start of a parsav connection, visible
-- only to the connecting parsav instance, stored in memory, and dropped
-- as soon as the connection session ends.

local tempviews = sqlsquash(lib.util.ingest 'backend/schema/pgsql-views.sql')
local prep = { quote
	var res = lib.pq.PQexec([con], tempviews)
	if lib.pq.PQresultStatus(res) == lib.pq.PGRES_COMMAND_OK then
		lib.dbg('uploading pgsql session views')
	else
		lib.bail('backend pgsql - failed to upload session views: \n', lib.pq.PQresultErrorMessage(res))
	end
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 %p %.*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_artifact(res: &pqr, i: intptr): lib.mem.ptr(lib.store.artifact)
	var id = res:int(uint64,i,0)
	var idbuf: int8[lib.math.shorthand.maxlen]
	var idlen = lib.math.shorthand.gen(id, &idbuf[0])
	var desc = res:_string(i,2)
	var folder = res:_string(i,3)
	var mime = res:_string(i,4)
	var m = [ lib.str.encapsulate(lib.store.artifact, {
		desc =  {`desc.ptr, `desc.ct + 1};
		folder = {`folder.ptr, `folder.ct + 1};
		mime = {`mime.ptr, `mime.ct + 1};
		url = {`&idbuf[0], `idlen + 1};
	}) ]
	m.ptr.rid = id
	return m
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.rtdby = r:int(uint64,row,13)
	p.ptr.rtdat = r:int(uint64,row,14)
	p.ptr.rtact = r:int(uint64,row,15)
	p.ptr.likes = r:int(uint32,row,16)
	p.ptr.rts = r:int(uint32,row,17)
	p.ptr.isreply = r:bool(row,18)
	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);
	a.ptr.avatarid = r:int(uint64,row, 13);
	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.powmap

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].val
		var u = ac.rights.powers and map[i].val
		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].val
					else powers = powers - map[j].val
				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.server_setup_self.prep(con)
			queries.conf_get.prep(con)
			queries.conf_set.prep(con)
			queries.conf_reset.prep(con)
			if mode == lib.store.prepmode.admin then 
				queries.server_fetch_sid.prep(con)
				queries.actor_fetch_uid.prep(con)
				queries.actor_fetch_xid.prep(con)
				queries.actor_enum.prep(con)
				queries.actor_enum_local.prep(con)
				queries.actor_stats.prep(con)
				queries.actor_powers_fetch.prep(con)
				queries.actor_save.prep(con)
				queries.actor_create.prep(con)
				queries.actor_purge_uid.prep(con)
			end
		else lib.bail('unsupported connection preparation mode') end
	end];

	server_setup_self = [terra(
		src: &lib.store.source,
		domain: rawstring,
		key: binblob
	): {}
		queries.server_setup_self.exec(src,domain,key,lib.osclock.time(nil))
	end];

	dbsetup = [terra(src: &lib.store.source): bool
		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): bool
		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: pstring)
		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: pstring, val: pstring)
		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: pstring,
			cred: pstring
		): {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_auth_challenge = [terra(
		src: &lib.store.source,
		ip: lib.store.inet,
		username: pstring,
		sig: binblob,
		token: pstring
	): {uint64, uint64, pstring}
		-- we need to iterate through all the challenge types. right now that's just RSA
		lib.dbg('checking against token ', {token.ptr,token.ct})
		var rsakeys = queries.actor_auth_challenge.exec(src, 'rsa', username, ip)
		var toprops = [terra(res: &pqr, i: intptr)
			return {
				aid = res:int(uint64, i, 0);
				uid = res:int(uint64, i, 1);
				name = res:_string(i, 2);
				blacklist = res:bool(i, 3);
				pubkey = res:bin(i, 4);
			}
		end]
		if rsakeys.sz > 0 then defer rsakeys:free()
			for i=0, rsakeys.sz do var props = toprops(&rsakeys, i)
				lib.dbg('loading next RSA pubkey')
				var pub = lib.crypt.loadpub(props.pubkey.ptr, props.pubkey.ct)
				if pub.ok then defer pub.val:free()
					lib.dbg('checking pubkey against response')
					var vfy, secl = lib.crypt.verify(&pub.val, token.ptr, token.ct, sig.ptr, sig.ct)
					if vfy then
						lib.dbg('signature verified')
						if props.blacklist then lib.dbg('key blacklisted!') goto fail end
						var dupname = lib.str.dup(props.name.ptr)
						return props.aid, props.uid, pstring {dupname, props.name.ct}
					end
				else lib.warn('invalid pubkey in authentication table for user ',{props.name.ptr, props.name.ct}) end
			end
		end

		-- and so on

		lib.dbg('no challenges were successful')
		::fail::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.account << r:bool(0,17))
				(au.val.privs.upload  << r:bool(0,18))
				(au.val.privs.artifact<< r:bool(0,19))
				(au.val.privs.moderate<< r:bool(0,20))
				(au.val.privs.admin   << r:bool(0,21))
				(au.val.privs.invite  << r:bool(0,22))
			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];

	post_act_cancel = [terra(
		src: &lib.store.source,
		act: uint64
	): {} queries.post_react_delete.exec(src, act) end];

	post_retweet = [terra(
		src: &lib.store.source,
		uid: uint64,
		post: uint64,
		undo: bool
	): {}
		if not undo then
			var time = lib.osclock.time(nil)
			queries.post_react_simple.exec(src,uid,post,"rt",time)
		else
			queries.post_react_cancel.exec(src,uid,post,"rt")
		end
	end];
	post_like = [terra(
		src: &lib.store.source,
		uid: uint64,
		post: uint64,
		undo: bool
	): {}
		var time = lib.osclock.time(nil)
		if not undo then
			queries.post_react_simple.exec(src,uid,post,"like",time)
		else
			queries.post_react_cancel.exec(src,uid,post,"like")
		end
	end];
	post_liked_uid = [terra(
		src: &lib.store.source,
		uid: uint64,
		post: uint64
	): bool
		var q = queries.post_reacts_fetch_uid.exec(src,uid,post,'like')
		if q.sz > 0 then q:free() return true end
		return false
	end];

	timeline_instance_fetch = [terra(
		src: &lib.store.source,
		rg: lib.store.range
	): lib.mem.lstptr(lib.store.post)
		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];

	timeline_circle_fetch = [terra(
		src: &lib.store.source,
		cid: uint64,
		rg: lib.store.range
	): lib.mem.lstptr(lib.store.post)
		var r = pqr { sz = 0 }
		var A,B,C,D = rg:matrix() -- :/
		r = queries.timeline_circle_fetch.exec(src,cid,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];

	timeline_actor_fetch_uid = [terra(
		src: &lib.store.source,
		uid: uint64,
		rg: lib.store.range
	): lib.mem.lstptr(lib.store.post)
		var r = pqr { sz = 0 }
		var A,B,C,D = rg:matrix() -- :/
		r = queries.timeline_actor_fetch.exec(src,uid,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_rel_create = [terra(
		src: &lib.store.source,
		kind:    uint16,
		relator: uint64,
		relatee: uint64
	): {} queries.actor_rel_create.exec(src,kind,relator,relatee,lib.osclock.time(nil)) end];

	actor_rel_destroy = [terra(
		src: &lib.store.source,
		kind:    uint16,
		relator: uint64,
		relatee: uint64
	): {} queries.actor_rel_destroy.exec(src,kind,relator,relatee) end];

	actor_rel_calc = [terra(
		src: &lib.store.source,
		relator: uint64,
		relatee: uint64
	): lib.store.relationship
		var r = lib.store.relationship {
			agent = relator, patient = relatee
		} r.rel:clear()
		  r.recip:clear()

		var res = queries.actor_rel_enum.exec(src,relator,relatee)
		var recip = queries.actor_rel_enum.exec(src,relatee,relator)

		if res.sz > 0 then defer res:free()
			for i = 0, res.sz do
				var bit = res:int(uint16, i, 0)-1
				if bit < [#lib.store.relation.members] then r.rel:setbit(bit,true)
					else lib.warn('unknown relationship type in database') end
			end
		end

		if recip.sz > 0 then defer recip:free()
			for i = 0, recip.sz do
				var bit = recip:int(uint16, i, 0)-1
				if bit < [#lib.store.relation.members] then r.recip:setbit(bit,true)
					else lib.warn('unknown relationship type in database') end
			end
		end

		return r
	end];

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

	actor_notice_enum = [terra(
		src: &lib.store.source,
		uid: uint64
	): lib.mem.ptr(lib.store.notice)
		var r = queries.actor_notice_enum.exec(src,uid);
		if r.sz == 0 then return [lib.mem.ptr(lib.store.notice)].null() end
		defer r:free()

		var notes = lib.mem.heapa(lib.store.notice, r.sz)
		for i=0, r.sz do
			var n = notes.ptr + i
			n.kind = r:int(uint16,i,0)
			n.when = r:int(int64,i,1)
			n.who = r:int(int64,i,2)
			n.what = r:int(uint64,i,3)
			if n.kind == lib.store.noticetype.reply then
				n.reply = r:int(uint64,i,4)
			elseif n.kind == lib.store.noticetype.react then
				var react = r:_string(i,5)
				lib.str.ncpy(n.reaction, react.ptr, lib.math.smallest(react.ct,[(`n.reaction).tree.type.N]))
			end
		end

		return notes
	end];

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

	auth_enum_uid = [terra(
		src: &lib.store.source,
		uid: uint64
	): lib.mem.lstptr(lib.store.auth)
		var r = queries.auth_enum_uid.exec(src,uid)
		if r.sz == 0 then return [lib.mem.lstptr(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+1};
				comment = {`comment.ptr, `comment.ct+1};
			}) ]
			a.ptr.aid = r:int(uint64, i, 0)
			if r:null(i,3)
				then a.ptr.netmask.pv = 0
				else a.ptr.netmask = r:cidr(i, 3)
			end
			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
	): uint64
		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
		var r = queries.auth_create_pw.exec(src, uid, binblob {ptr = &hash[0], ct = [hash.type.N]}, lib.osclock.time(nil), comment)
		if r.sz == 0 then return 0 end
		var aid = r:int(uint64,0,0)
		r:free()
		return aid
	end];

	auth_attach_rsa = [terra(
		src: &lib.store.source,
		uid: uint64,
		reset: bool,
		pub: binblob,
		comment: pstring
	): uint64
		if reset then queries.auth_purge_type.exec(src, nil, uid, 'challenge-%') end
		var r = queries.auth_create_rsa.exec(src, uid, pub, lib.osclock.time(nil), comment)
		if r.sz == 0 then return 0 end
		var aid = r:int(uint64,0,0)
		r:free()
		return aid
	end];

	auth_privs_set = [terra(
		src: &lib.store.source,
		aid: uint64,
		set: lib.store.privset
	): {}
		var map = array([lib.store.privmap])
		queries.auth_privs_clear.exec(src,aid)
		if set:sz() == 0 then return end
		for i=0, [map.type.N] do
			if (set and map[i].val):sz() > 0 then
				queries.auth_priv_install.exec(src,aid,map[i].name)
			end
		end
	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];

	auth_destroy_aid = [terra(
		src: &lib.store.source,
		aid: uint64
	): {} queries.auth_destroy_aid.exec(src,aid) end];

	auth_destroy_aid_uid = [terra(
		src: &lib.store.source,
		aid: uint64,
		uid: uint64
	): {} queries.auth_destroy_aid_uid.exec(src,aid,uid) 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, lib.osclock.time(nil))
			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];

	artifact_expropriate = [terra(
		src: &lib.store.source,
		uid: uint64,
		artifact: uint64,
		desc: pstring,
		folder: pstring
	): {}
		queries.artifact_expropriate.exec(src,uid,artifact,desc,folder, lib.osclock.time(nil))
	end];

	artifact_disclaim = [terra(
		src: &lib.store.source,
		uid: uint64,
		artifact: uint64
	)
		queries.artifact_disclaim.exec(src,uid,artifact)
		queries.artifact_collect_garbage.exec(src) -- TODO add a config option to change GC strategies, instead of just always running a cycle after an artifact is disclaimed, which is not very efficient
	end];

	artifact_enum_uid = [terra(
		src: &lib.store.source,
		uid: uint64,
		folder: pstring
	)
		var res = queries.artifact_enum_uid.exec(src,uid,folder)
		if res.sz > 0 then
			var m = lib.mem.heapa([lib.mem.ptr(lib.store.artifact)], res.sz)
			for i=0,res.sz do
				m.ptr[i] = row_to_artifact(&res, i)
				m(i).ptr.owner = uid
			end
			return m
		else return [lib.mem.lstptr(lib.store.artifact)].null() end
	end];

	artifact_fetch = [terra(
		src: &lib.store.source,
		uid: uint64,
		rid: uint64
	)
		var res = queries.artifact_fetch.exec(src,uid,rid)
		if res.sz > 0 then
			var a = row_to_artifact(&res, 0)
			a.ptr.owner = uid
			res:free()
			return a
		end
		return [lib.mem.ptr(lib.store.artifact)].null()
	end];

	artifact_load = [terra(
		src: &lib.store.source,
		rid: uint64
	): {binblob, pstring}
		var r = queries.artifact_load.exec(src,rid)
		if r.sz == 0 then return binblob.null(), pstring.null() end

		var mime = r:String(0,1)
		var mbin = r:bin(0,0)
		var bin = lib.mem.heapa(uint8,mbin.ct)
		lib.mem.cpy(bin.ptr, mbin.ptr, bin.ct)

		r:free()
		return bin, mime
	end];

	artifact_folder_enum = [terra(
		src: &lib.store.source,
		uid: uint64
	)
		var r = queries.artifact_folder_enum.exec(src,uid)
		if r.sz == 0 then return [lib.mem.ptr(pstring)].null() end
		defer r:free()
		var lst = lib.mem.heapa(pstring, r.sz)
		for i=0,r.sz do lst.ptr[i] = r:String(i,0) end
		return lst
	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.lstptr(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];

	post_act_fetch_notice = [terra(
		src: &lib.store.source,
		act: uint64
	): lib.store.notice
		var r = queries.post_act_fetch_notice.exec(src,act)
		if r.sz == 0 then return lib.store.notice { kind = lib.store.noticetype.none } end
		defer r:free()
		
		var n: lib.store.notice
		n.kind = r:int(uint16,0,0)
		n.when = r:int(int64,0,1)
		n.who = r:int(int64,0,2)
		n.what = r:int(uint64,0,3)
		if n.kind == lib.store.noticetype.react then
			var react = r:_string(0,5)
			lib.str.ncpy(n.reaction, react.ptr, lib.math.smallest(react.ct,[(`n.reaction).tree.type.N]))
		end

		return n
	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,
		pool: &lib.mem.pool,
		uid: uint64,
		key: pstring
	): pstring
			var r = queries.actor_conf_str_get.exec(src, uid, key)
			if r.sz > 0 then
				return r:_string(0,0):pdup(pool)
			else return pstring.null() end
		end];
	actor_conf_str_set = [terra(src: &lib.store.source, uid: uint64, key: pstring, value: pstring): {}
			queries.actor_conf_str_set.exec(src,uid,key,value) end];
	actor_conf_str_reset = [terra(src: &lib.store.source, uid: uint64, key: pstring): {}
			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: pstring)
			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: pstring, 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: pstring): {}
			queries.actor_conf_int_reset.exec(src,uid,key) end];
	
	circle_search = [terra(
		src: &lib.store.source,
		pool:&lib.mem.pool,
		uid: uint64,
		cid: uint64
	): lib.mem.ptr(lib.store.circle)
		var res = queries.circle_search.exec(src, uid, cid)
		if res.sz == 0 then return [lib.mem.ptr(lib.store.circle)].null() end
		defer res:free()

		var rt = pool:alloc(lib.store.circle, res.sz)
		for i = 0, res.sz do
			var name = res:_string(i,0)
			rt(i) = lib.store.circle {
				name = name:pdup(pool);      cid = res:int(uint64,i,1);
				owner = res:int(uint64,i,2); memcount = res:int(uint64,i,3);
			}
		end

		return rt
	end];

	circle_create = [terra(
		src: &lib.store.source,
		owner: uint64,
		name: pstring
	): uint64
		var r = queries.circle_create.exec(src, owner, name)
		if r.sz > 0 then defer r:free() return r:int(uint64,0,0) end
		return 0
	end];

	circle_destroy = [terra(
		src: &lib.store.source,
		owner: uint64,
		cid: uint64
	): {} queries.circle_destroy.exec(src, owner, cid) end];

	circle_memberships_uid = [terra(
		src: &lib.store.source,
		pool:&lib.mem.pool,
		owner: uint64,
		subject: uint64
	): lib.mem.ptr(lib.store.circle)
		var res = queries.circle_memberships_uid.exec(src, owner, subject)
		if res.sz == 0 then return [lib.mem.ptr(lib.store.circle)].null() end
		defer res:free()

		var rt = pool:alloc(lib.store.circle, res.sz)
		for i = 0, res.sz do
			var name = res:_string(i,0)
			rt(i) = lib.store.circle {
				name = name:pdup(pool);      cid = res:int(uint64,i,1);
				owner = res:int(uint64,i,2); memcount = res:int(uint64,i,3);
			}
		end

		return rt
	end];

	circle_members_fetch_cid = [terra(
		src: &lib.store.source,
		pool:&lib.mem.pool,
		cid: uint64
	): lib.mem.ptr(uint64)
		var res = queries.circle_members_fetch_cid.exec(src,cid)
		if res.sz == 0 then return [lib.mem.ptr(uint64)].null() end
		defer res:free()

		var rt = pool:alloc(uint64, res.sz)
		for i = 0, res.sz do rt(i) = res:int(uint64,i,0) end

		return rt
	end];

	circle_members_add_uid = [terra(
		src: &lib.store.source, owner: uint64, subject: uint64
	): {} queries.circle_members_add_uid.exec(src,owner,subject) end];

	circle_members_del_uid = [terra(
		src: &lib.store.source, owner: uint64, subject: uint64
	): {} queries.circle_members_del_uid.exec(src,owner,subject) end];

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

return b