parsav  pgsql.t at [59e1d7d56a]

File backend/pgsql.t artifact 0360541ecf part of check-in 59e1d7d56a


-- vim: ft=terra
local queries = {
	conf_get = {
		params = {rawstring}, sql = [[
			select value from parsav_config
				where key = $1::text limit 1
		]];
	};

	conf_set = {
		params = {rawstring,rawstring}, sql = [[
			insert into parsav_config (key, value)
				values ($1::text, $2::text)
				on conflict (key) do update set value = $2::text
		]];
	};

	conf_reset = {
		params = {rawstring}, sql = [[
			delete from parsav_config where
				key = $1::text 
		]];
	};

	actor_fetch_uid = {
		params = {uint64}, sql = [[
			select
				id, nym, handle, origin,
				bio, rank, quota, key
			from parsav_actors
				where id = $1::bigint
		]];
	};

	actor_fetch_xid = {
		params = {rawstring}, sql = [[
			select a.id, a.nym, a.handle, a.origin,
			       a.bio, a.rank, a.quota, a.key,

				coalesce(s.domain,
				        (select value from parsav_config
							where key='domain' limit 1)) as domain

			from      parsav_actors  as a
			left join parsav_servers as s
				on a.origin = s.id

			where $1::text = (a.handle || '@' || domain) or
			      $1::text = ('@' || a.handle || '@' || domain) or
				  (a.origin is null and $1::text = ('@' || a.handle))
		]];
	};
}

local struct pqr {
	sz: intptr
	res: &lib.pq.PGresult
}
terra pqr:free() if self.sz > 0 then lib.pq.PQclear(self.res) end end
terra pqr:null(row: intptr, col: intptr)
	return (lib.pq.PQgetisnull(self.res, row, col) == 1)
end
terra pqr:string(row: intptr, col: intptr)
	var v = lib.pq.PQgetvalue(self.res, row, col)
	var r: lib.mem.ptr(int8)
	r.ct = lib.str.sz(v)
	r.ptr = lib.str.ndup(v, r.ct)
	return r
end
pqr.methods.int = macro(function(self, ty, row, col)
	return quote
		var i: ty:astype()
		var v = lib.pq.PQgetvalue(self.res, row, col)
		lib.math.netswap_ip(ty, v, &i)
	in i end
end)

local con = symbol(&lib.pq.PGconn)
local prep = {}
for k,q in pairs(queries) do
	local qt = (q.sql):gsub('%s+',' '):gsub('^%s*(.-)%s*$','%1')
	local stmt = 'parsavpg_' .. k
	prep[#prep + 1] = quote
		var res = lib.pq.PQprepare([con], stmt, qt, [#q.params], nil)
		defer lib.pq.PQclear(res)
		if res == nil or lib.pq.PQresultStatus(res) ~= lib.pq.PGRES_COMMAND_OK then
			if res == nil then
				lib.bail('grievous error occurred preparing ',k,' statement')
			end
			lib.bail('could not prepare PGSQL statement ',k,': ',lib.pq.PQresultErrorMessage(res))
		end
		lib.dbg('prepared PGSQL statement ',k) 
	end

	local args, casts, counters, fixers, ft, yield = {}, {}, {}, {}, {}, {}
	for i, ty in ipairs(q.params) do
		args[i] = symbol(ty)
		ft[i] = `1
		if ty == rawstring then
			counters[i] = `lib.trn([args[i]] == nil, 0, lib.str.sz([args[i]]))
			casts[i] = `[&int8]([args[i]])
		elseif ty:isintegral() then
			counters[i] = ty.bytes
			casts[i] = `[&int8](&[args[i]])
			fixers[#fixers + 1] = quote
				--lib.io.fmt('uid=%llu(%llx)\n',[args[i]],[args[i]])
				[args[i]] = lib.math.netswap(ty, [args[i]])
			end
		end
	end

	q.exec = terra(src: &lib.store.source, [args])
		var params = arrayof([&int8], [casts])
		var params_sz = arrayof(int, [counters])
		var params_ft = arrayof(int, [ft])
		[fixers]
		var res = lib.pq.PQexecPrepared([&lib.pq.PGconn](src.handle), stmt,
			[#args], params, params_sz, params_ft, 1)
		if res == nil then
			lib.bail(['grievous error occurred executing '..k..' against database'])
		elseif lib.pq.PQresultStatus(res) ~= lib.pq.PGRES_TUPLES_OK then
			lib.bail(['PGSQL database procedure '..k..' failed\n'],
			lib.pq.PQresultErrorMessage(res))
		end

		var ct = lib.pq.PQntuples(res)
		if ct == 0 then
			lib.pq.PQclear(res)
			return pqr {0, nil}
		else
			return pqr {ct, res}
		end
	end
end

local terra row_to_actor(r: &pqr, row: intptr): lib.store.actor
	var a = lib.store.actor {
		id = r:int(uint64, row, 0);
		nym = r:string(row, 1);
		handle = r:string(row, 2);
		bio = r:string(row, 4);
		key = r:string(row, 7);
		rights = lib.store.rights_default();
	}
	a.rights.rank = r:int(uint16, 0, 5);
	a.rights.quota = r:int(uint32, 0, 6);
	if r:null(0,3) then a.origin = 0
	else a.origin = r:int(uint64,0,3) end
	return a
end

local b = `lib.store.backend {
	id = "pgsql";
	open = [terra(src: &lib.store.source): &opaque
		lib.report('connecting to postgres database: ', src.string.ptr)
		var [con] = lib.pq.PQconnectdb(src.string.ptr)
		if lib.pq.PQstatus(con) ~= lib.pq.CONNECTION_OK then
			lib.warn('postgres backend connection failed')
			lib.pq.PQfinish(con)
			return nil
		end
		var res = lib.pq.PQexec(con, [[
			select pg_catalog.set_config('search_path', 'public', false)
		]])
		if res ~= nil then defer lib.pq.PQclear(res) end
		if res == nil or lib.pq.PQresultStatus(res) ~= lib.pq.PGRES_TUPLES_OK then
			lib.warn('failed to secure postgres connection')
			lib.pq.PQfinish(con)
			return nil
		end

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

	conf_get = [terra(src: &lib.store.source, key: rawstring)
		var r = queries.conf_get.exec(src, key)
		if r.sz == 0 then return [lib.mem.ptr(int8)] { ptr = nil, ct = 0 } else
			defer r:free()
			return r:string(0,0)
		end
	end];
	conf_set = [terra(src: &lib.store.source, key: rawstring, val: rawstring)
		queries.conf_set.exec(src, key, val):free() end];
	conf_reset = [terra(src: &lib.store.source, key: rawstring)
		queries.conf_reset.exec(src, key):free() end];
	
	actor_fetch_uid = [terra(src: &lib.store.source, uid: uint64)
		var r = queries.actor_fetch_uid.exec(src, uid)
		if r.sz == 0 then
			return [lib.stat(lib.store.actor)] { ok = false, error = 1}
		else
			defer r:free()
			var a = [lib.stat(lib.store.actor)] { ok = true }
			a.val = row_to_actor(&r, 0)
			a.val.source = src
			return a
		end
	end];
}

return b