Dumped on 2024-12-23

Index of database - gnumed_v21


Table: access_log

This logs access to the database and to records. Needed for HIPAA compliance among other things.

access_log Structure
F-Key Name Type Description
pk_audit integer NOT NULL DEFAULT nextval('audit.audit_fields_pk_audit_seq'::regclass)
row_version integer NOT NULL
modified_when timestamp with time zone NOT NULL DEFAULT now()
modified_by name NOT NULL DEFAULT "current_user"()
pk serial PRIMARY KEY
user_action text NOT NULL

 

access_log Constraints
Name Constraint
audit_audit_fields_sane_modified_when CHECK (((modified_when <= clock_timestamp()) IS TRUE))
non_empty_user_action CHECK ((gm.is_null_or_blank_string(user_action) IS FALSE))

Index - Schema gm


Table: notifying_tables

All tables that want to send standard notifications must be recorded in this table. Notification triggers will be generated automatically for all tables recorded here.

notifying_tables Structure
F-Key Name Type Description
pk serial PRIMARY KEY
schema_name name NOT NULL DEFAULT 'public'::name
table_name name NOT NULL

Index - Schema gm


Table: schema_revision

this table holds the revisions of all SQL scripts ever inserted into this database, the values are preferably provided by CVS tags in the scripts themselves, see above for a convenient way to do that

schema_revision Structure
F-Key Name Type Description
pk serial PRIMARY KEY
filename text UNIQUE#1 NOT NULL

the name of the script, handled most easily by CVS via "RCSfile"
version text UNIQUE#1 NOT NULL

the version of the script, handled most easily by CVS via "Revision"
imported timestamp with time zone NOT NULL DEFAULT now()

when this script was imported, mainly for debugging

Index - Schema gm


Function: account_is_dbowner_or_staff(_account name)

Returns: boolean

Language: PLPGSQL

DECLARE
	_is_owner boolean;
BEGIN
	-- is _account member of current db group ?
--	PERFORM 1 FROM pg_auth_members
--	WHERE
--		roleid = (SELECT oid FROM pg_roles WHERE rolname = current_database())
--			AND
--		member = (SELECT oid FROM pg_roles WHERE rolname = _account)
--	;
--	IF FOUND THEN
--		-- should catch people on staff, gm-dbo, and postgres
--		RETURN TRUE;
--	END IF;
	-- postgres
	IF _account = 'postgres' THEN
		RETURN TRUE;
	END IF;
	-- on staff list
	PERFORM 1 FROM dem.staff WHERE db_user = _account;
	IF FOUND THEN
		RETURN TRUE;
	END IF;
	-- owner
	SELECT pg_catalog.pg_get_userbyid(datdba) = _account INTO STRICT _is_owner FROM pg_catalog.pg_database WHERE datname = current_database();
	IF _is_owner IS TRUE THEN
		RETURN TRUE;
	END IF;
	RETURN FALSE;
END;

Function: add_missing_array_bits()

Returns: boolean

Language: PLPGSQL

Add array aggregate and array unnesting to PostgreSQL versions lacking this functionality (IOW < 8.4).

DECLARE
BEGIN
	-- array_agg
	perform 1 from pg_catalog.pg_aggregate where aggfnoid::oid = (select oid from pg_catalog.pg_proc where proname = 'array_agg'::name limit 1);
	if FOUND then
		raise NOTICE '[gm.add_missing_array_bits]: aggregate <array_agg> already exists';
	else
		raise NOTICE '[gm.add_missing_array_bits]: aggregate <array_agg> does not exist (probably PostgreSQL <8.4), creating';
		CREATE AGGREGATE array_agg(anyelement) (
			SFUNC = array_append,
			STYPE = anyarray,
			INITCOND = '{}'
		);
		comment on aggregate array_agg(anyelement) is
			'Missing on PG 8.3, needed for vaccination handling starting with conversion from gnumed_v13 to gnumed_v14.';
	end if;
	-- unnest()
	perform 1 from pg_catalog.pg_proc where
		proname = 'array_unnest'::name
			and
		pronamespace = (select oid from pg_namespace where nspname = 'gm'::name)
	;
	if FOUND then
		raise NOTICE '[gm.add_missing_array_bits]: function "gm.array_unnest()" already exists';
	else
		raise NOTICE '[gm.add_missing_array_bits]: function "gm.array_unnest()" does not exist, creating';
		CREATE OR REPLACE FUNCTION gm.array_unnest(anyarray)
			RETURNS SETOF anyelement
			AS '
		SELECT $1[i] FROM
			generate_series (
				array_lower($1,1),
				array_upper($1,1)
			) i
		;'
			LANGUAGE 'sql'
			IMMUTABLE
		;
		comment on function gm.array_unnest(anyarray) is
			'Missing on PG 8.3, needed for vaccination handling starting with conversion from gnumed_v13 to gnumed_v14.';
	end if;
	perform 1 from pg_catalog.pg_proc where
		proname = 'unnest'::name
			and
		pronamespace = (select oid from pg_namespace where nspname = 'pg_catalog'::name)
	;
	if FOUND then
		raise NOTICE '[gm.add_missing_array_bits]: function "pg_catalog.unnest()" exists';
		-- also exists in public ?
		perform 1 from pg_catalog.pg_proc where
			proname = 'unnest'::name
				and
			pronamespace = (select oid from pg_namespace where nspname = 'public'::name)
		;
		if FOUND then
			raise NOTICE '[gm.add_missing_array_bits]: function "unnest()" also exists in schema "public", removing';
			drop function public.unnest(anyarray) cascade;
		end if;
	else
		raise NOTICE '[gm.add_missing_array_bits]: function "pg_catalog.unnest()" does not exist (probably PostgreSQL <8.4)';
		-- exists in public ?
		perform 1 from pg_catalog.pg_proc where
			proname = 'unnest'::name
				and
			pronamespace = (select oid from pg_namespace where nspname = 'public'::name)
		;
		if FOUND then
			raise NOTICE '[gm.add_missing_array_bits]: function "public.unnest()" already exists';
		else
			raise NOTICE '[gm.add_missing_array_bits]: function "public.unnest()" does not exist either, creating';
			CREATE OR REPLACE FUNCTION public.unnest(anyarray)
				RETURNS SETOF anyelement
				AS 'SELECT gm.array_unnest($1);'
				LANGUAGE 'sql'
				IMMUTABLE
			;
			comment on function public.unnest(anyarray) is
				'Missing on PG 8.3, needed for vaccination handling starting with conversion from gnumed_v13 to gnumed_v14.';
		end if;
	end if;
	return TRUE;
END;

Function: add_table_for_notifies(name, name)

Returns: boolean

Language: SQL

Mark given table for notification trigger generator. Parameters are: (schema, table). Defaults signal to table name.

select gm.register_notifying_table($1, $2);

Function: add_user_to_permission_group(name, name)

Returns: boolean

Language: PLPGSQL

Only gm-dbo is GRANTed EXECUTE on this function. This way users need to know the gm-dbo (GNUmed admin) password to execute it.

DECLARE
	_username alias for $1;
	_target_group alias for $2;
	_valid_groups name[];
	_group name;
	_query text;
BEGIN
	-- verify user
	perform 1 from pg_user where usename = _username;
	if not FOUND then
		raise warning '[gm.add_user_to_permission_group]: user [%] does not exist', _username;
		return False;
	end if;
	-- verify logical group validity
	-- no "gm-nurse", "gm-admin" just yet
	_valid_groups := ARRAY[quote_ident('gm-public'), quote_ident('gm-staff'), quote_ident('gm-doctors')];
	if quote_ident(_target_group) <> all(_valid_groups) then
		raise warning '[gm.add_user_to_permission_group]: invalid group [%]', _target_group;
		return False;
	end if;
	-- verify group existance
	perform 1 from pg_group where groname = _target_group;
	if not FOUND then
		raise warning '[gm.add_user_to_permission_group]: group [%] does not exist', _target_group;
		return False;
	end if;
	-- drop user from all groups
	--FOREACH _group IN ARRAY _valid_groups LOOP
	FOR _group IN SELECT unnest(_valid_groups) LOOP
		_query := 'alter group ' || _group || ' drop user ' || quote_ident(_username) || ';';
		execute _query;
	END LOOP;
	-- add user to desired group
	_query := 'alter group ' || quote_ident(_target_group) || ' add user ' || quote_ident(_username) || ';';
	execute _query;
	return True;
END;

Function: array_unnest(anyarray)

Returns: SET OF anyelement

Language: SQL

Missing on PG 8.3, needed for vaccination handling starting with conversion from gnumed_v13 to gnumed_v14.

		SELECT $1[i] FROM
			generate_series (
				array_lower($1,1),
				array_upper($1,1)
			) i
		;

Function: concat_table_structure()

Returns: text

Language: PLPGSQL

declare
	_struct text;
begin
	select into _struct gm.concat_table_structure_v19_and_up();
	return _struct;
end;

Function: concat_table_structure(integer)

Returns: text

Language: PLPGSQL

declare
	_db_ver alias for $1;
	_struct text;
begin
	if _db_ver < 6 then
		select into _struct gm.concat_table_structure_v1();
		return _struct;
	end if;
	if _db_ver < 8 then
		select into _struct gm.concat_table_structure_v2();
		return _struct;
	end if;
	if _db_ver < 16 then
		select into _struct gm.concat_table_structure_v3();
		return _struct;
	end if;
	if _db_ver < 17 then
		select into _struct gm.concat_table_structure_v16_and_up();
		return _struct;
	end if;
	if _db_ver < 18 then
		select into _struct gm.concat_table_structure_v17_and_up();
		return _struct;
	end if;
	if _db_ver < 19 then
		select into _struct gm.concat_table_structure_v18_and_up();
		return _struct;
	end if;
	select into _struct gm.concat_table_structure_v19_and_up();
	return _struct;
end;

Function: concat_table_structure_v1()

Returns: text

Language: PLPGSQL

copy of gm_concat_table_structure() until gnumed_v5, works on public, dem, clin, blobs

declare
	_row record;
	_total text;
begin
	_total := '';
	-- schema.table.column.data_type
	for _row in
		select * from information_schema.columns cols
			where cols.table_name in (
				select tabs.table_name from information_schema.tables tabs where
					-- those which hold clinical data
					tabs.table_schema in ('public', 'dem', 'clin', 'blobs') and
					tabs.table_type = 'BASE TABLE'
				)
			order by
				md5(cols.table_schema || cols.table_name || cols.column_name || cols.data_type)
	loop
		_total := _total
			|| _row.table_schema || '.'
			|| _row.table_name || '.'
			|| _row.column_name || '::'
			|| _row.udt_name || '
';
	end loop;
	return _total;
end;

Function: concat_table_structure_v16_and_up()

Returns: text

Language: PLPGSQL

new concat_table_structure() starting with gnumed_v16, works on dem, clin, blobs, cfg, ref, i18n, sorts properly by bytea

declare
	_row record;
	_total text;
begin
	_total := '';
	-- schema.table.column.data_type
	for _row in
		select *
		from information_schema.columns cols
		where
			cols.table_name in (
				select tabs.table_name from information_schema.tables tabs where
					-- those which hold clinical data
					tabs.table_schema in ('dem', 'clin', 'blobs', 'cfg', 'ref', 'i18n')
						and
					tabs.table_type = 'BASE TABLE'
			) and
			cols.table_schema in ('dem', 'clin', 'blobs', 'cfg', 'ref', 'i18n')
		order by
			decode(md5(cols.table_schema || cols.table_name || cols.column_name || cols.data_type), 'hex')
	loop
		_total := _total
			|| _row.table_schema || '.'
			|| _row.table_name || '.'
			|| _row.column_name || '::'
			|| _row.udt_name || E'
';
	end loop;
	return _total;
end;

Function: concat_table_structure_v17_and_up()

Returns: text

Language: PLPGSQL

new concat_table_structure() starting with gnumed_v17, works on dem, clin, blobs, cfg, ref, i18n, bill, sorts properly by bytea

declare
	_row record;
	_total text;
begin
	_total := '';
	-- schema.table.column.data_type
	for _row in
		select *
		from information_schema.columns cols
		where
			cols.table_name in (
				select tabs.table_name from information_schema.tables tabs where
					tabs.table_schema in ('dem', 'clin', 'blobs', 'cfg', 'ref', 'i18n', 'bill')
						and
					tabs.table_type = 'BASE TABLE'
			) and
			cols.table_schema in ('dem', 'clin', 'blobs', 'cfg', 'ref', 'i18n', 'bill')
		order by
			decode(md5(cols.table_schema || cols.table_name || cols.column_name || cols.data_type), 'hex')
	loop
		_total := _total
			|| _row.table_schema || '.'
			|| _row.table_name || '.'
			|| _row.column_name || '::'
			|| _row.udt_name || E'
';
	end loop;
	return _total;
end;

Function: concat_table_structure_v18_and_up()

Returns: text

Language: PLPGSQL

new concat_table_structure() starting with gnumed_v18, works on dem, clin, blobs, cfg, ref, i18n, bill, includes primary keys, sorts properly by bytea

declare
	_table_desc record;
	_pk_desc record;
	_column_desc record;
	_total text;
begin
	_total := '';
	-- find relevant tables
	for _table_desc in
		select * from information_schema.tables tabs where
			tabs.table_schema in ('dem', 'clin', 'blobs', 'cfg', 'ref', 'i18n', 'bill')
				and
			tabs.table_type = 'BASE TABLE'
		order by
			decode(md5(tabs.table_schema || tabs.table_name), 'hex')
	-- loop over tables
	loop
		-- where are we at ?
		_total := _total || 'TABLE:' || _table_desc.table_schema || '.' || _table_desc.table_name || E'
';
		-- find PKs of that table
		for _pk_desc in
			select * from (
				select
					pg_class.oid::regclass || '.' || pg_attribute.attname || '::' || format_type(pg_attribute.atttypid, pg_attribute.atttypmod) AS primary_key_column
				from
					pg_index, pg_class, pg_attribute
				where
					--pg_class.oid = 'TABLENAME'::regclass
					pg_class.oid = (_table_desc.table_schema || '.' || _table_desc.table_name)::regclass
						AND 
					indrelid = pg_class.oid
						AND
					pg_attribute.attrelid = pg_class.oid
						AND
					pg_attribute.attnum = any(pg_index.indkey)
						AND
					indisprimary
				) AS PKs
			order by
				decode(md5(PKs.primary_key_column), 'hex')
		-- and loop over those PK columns
		loop
			_total := _total || 'PK:' || _pk_desc.primary_key_column	|| E'
';
		end loop;
		-- find columns of that table
		for _column_desc in
			select *
			from information_schema.columns cols
			where
				cols.table_name = _table_desc.table_name
					and
				cols.table_schema = _table_desc.table_schema
			order by
				decode(md5(cols.column_name || cols.data_type), 'hex')
		-- and loop over those columns
		loop
			-- add columns in the format "schema.table.column::data_type"
			_total := _total || 'COL:'
				|| _column_desc.table_schema || '.'
				|| _column_desc.table_name || '.'
				|| _column_desc.column_name || '::'
				|| _column_desc.udt_name || E'
';
		end loop;
	end loop;
	return _total;
end;

Function: concat_table_structure_v19_and_up()

Returns: text

Language: PLPGSQL

new concat_table_structure() starting with gnumed_v19, works on dem, clin, blobs, cfg, ref, i18n, bill, includes primary keys and constraints, sorts properly by bytea

declare
	_table_desc record;
	_pk_desc record;
	_column_desc record;
	_constraint_def record;
	_total text;
begin
	_total := '';
	-- find relevant tables
	for _table_desc in
		select * from information_schema.tables tabs where
			tabs.table_schema in ('dem', 'clin', 'blobs', 'cfg', 'ref', 'i18n', 'bill')
				and
			tabs.table_type = 'BASE TABLE'
		order by
			decode(md5(tabs.table_schema || tabs.table_name), 'hex')
	-- loop over tables
	loop
		-- where are we at ?
		_total := _total || 'TABLE:' || _table_desc.table_schema || '.' || _table_desc.table_name || E'\n';
		-- find PKs of that table
		for _pk_desc in
			select * from (
				select
					pg_class.oid::regclass || '.' || pg_attribute.attname || '::' || format_type(pg_attribute.atttypid, pg_attribute.atttypmod) AS primary_key_column
				from
					pg_index, pg_class, pg_attribute
				where
					--pg_class.oid = 'TABLENAME'::regclass
					pg_class.oid = (_table_desc.table_schema || '.' || _table_desc.table_name)::regclass
						AND 
					indrelid = pg_class.oid
						AND
					pg_attribute.attrelid = pg_class.oid
						AND
					pg_attribute.attnum = any(pg_index.indkey)
						AND
					indisprimary
				) AS PKs
			order by
				decode(md5(PKs.primary_key_column), 'hex')
		-- and loop over those PK columns
		loop
			_total := _total || 'PK:' || _pk_desc.primary_key_column	|| E'\n';
		end loop;
		-- find columns of that table
		for _column_desc in
			select *
			from information_schema.columns cols
			where
				cols.table_name = _table_desc.table_name
					and
				cols.table_schema = _table_desc.table_schema
			order by
				decode(md5(cols.column_name || cols.data_type), 'hex')
		-- and loop over those columns
		loop
			-- add columns in the format "schema.table.column::data_type"
			_total := _total || 'COL:'
				|| _column_desc.table_schema || '.'
				|| _column_desc.table_name || '.'
				|| _column_desc.column_name || '::'
				|| _column_desc.udt_name || E'\n';
		end loop;
		-- find and loop over CONSTRAINTs of that table
		for _constraint_def in
			select * from
				(select
					tbl.contype,
					'CONSTRAINT:type='
						|| tbl.contype || ':'
						|| replace(pg_catalog.pg_get_constraintdef(tbl.oid, true), ' ', '_')
						|| '::active='
						|| tbl.convalidated
					 as condef
				from pg_catalog.pg_constraint tbl
				where
					tbl.conrelid = (_table_desc.table_schema || '.' || _table_desc.table_name)::regclass
					-- include FKs only because we may have to add/remove
					-- other (say, check) constraints in a minor release
					-- for valid reasons which we do not want to affect
					-- the hash, if however we need to modify a foreign
					-- key that would, indeed, warrant a hash change
						AND
					tbl.contype = 'f'
				) as CONSTRAINTs
			order by
				CONSTRAINTs.contype,
				decode(md5(CONSTRAINTs.condef), 'hex')
		loop
			_total := _total || _constraint_def.condef || E'\n';
		end loop;
	end loop;		-- over tables
	return _total;
end;

Function: concat_table_structure_v2()

Returns: text

Language: PLPGSQL

new concat_table_structure() starting with gnumed_v6, works on dem, clin, blobs, cfg, ref, i18n

declare
	_row record;
	_total text;
begin
	_total := '';
	-- schema.table.column.data_type
	for _row in
		select * from information_schema.columns cols
			where cols.table_name in (
				select tabs.table_name from information_schema.tables tabs where
					-- those which hold clinical data
					tabs.table_schema in ('dem', 'clin', 'blobs', 'cfg', 'ref', 'i18n') and
					tabs.table_type = 'BASE TABLE'
				)
			order by
				md5(cols.table_schema || cols.table_name || cols.column_name || cols.data_type)
	loop
		_total := _total
			|| _row.table_schema || '.'
			|| _row.table_name || '.'
			|| _row.column_name || '::'
			|| _row.udt_name || '
';
	end loop;
	return _total;
end;

Function: concat_table_structure_v3()

Returns: text

Language: PLPGSQL

new concat_table_structure() starting with gnumed_v8, works on dem, clin, blobs, cfg, ref, i18n, sorts properly by bytea

declare
	_row record;
	_total text;
begin
	_total := '';
	-- schema.table.column.data_type
	for _row in
		select * from information_schema.columns cols
			where cols.table_name in (
				select tabs.table_name from information_schema.tables tabs where
					-- those which hold clinical data
					tabs.table_schema in ('dem', 'clin', 'blobs', 'cfg', 'ref', 'i18n') and
					tabs.table_type = 'BASE TABLE'
				)
			order by
				decode(md5(cols.table_schema || cols.table_name || cols.column_name || cols.data_type), 'hex')
	loop
		_total := _total
			|| _row.table_schema || '.'
			|| _row.table_name || '.'
			|| _row.column_name || '::'
			|| _row.udt_name || '
';
	end loop;
	return _total;
end;

Function: create_all_enc_epi_sanity_check_triggers()

Returns: boolean

Language: PLPGSQL

(Re)create sanity check triggers for all tables which have both fk_encounter and fk_episode.

DECLARE
	_schema name;
	_table name;
	_qualified_table2check name;
	_fk_encounter_col name;
	_fk_episode_col name;
	_total_success boolean;
BEGIN
	raise notice 'gm.create_all_enc_epi_sanity_check_triggers()';
	_total_success := True;
	-- loop over tables with encounter AND episode FKs
	-- (assumes there is only one of each)
	for _schema, _table in
		select
			(select pg_n.nspname from pg_catalog.pg_namespace pg_n where pg_n.oid = pg_c.relnamespace),
			pg_c.relname
		from pg_class pg_c
		where pg_c.oid in (
			select distinct fk_tbl.conrelid
			from pg_catalog.pg_constraint fk_tbl
			where
				exists (
					select 1 from pg_catalog.pg_constraint fk_tbl1
					where
						fk_tbl1.contype = 'f'
							and
						fk_tbl1.conrelid = fk_tbl.conrelid
							and
						fk_tbl1.confrelid = 'clin.encounter'::regclass
							and
						fk_tbl1.confkey[1] = (
							select attnum from pg_catalog.pg_attribute col_tbl
							where
								col_tbl.attname = 'pk'
									AND
								col_tbl.attrelid = 'clin.encounter'::regclass
						)
				)
					and
				exists (
					select 1 from pg_catalog.pg_constraint fk_tbl2
					where
						fk_tbl2.contype = 'f'
							and
						fk_tbl2.conrelid = fk_tbl.conrelid
							and
						fk_tbl2.confrelid = 'clin.episode'::regclass
							and
						fk_tbl2.confkey[1] = (
							select attnum from pg_catalog.pg_attribute col_tbl
							where
								col_tbl.attname = 'pk'
									AND
								col_tbl.attrelid = 'clin.episode'::regclass
						)
				)
		)
	loop
		_qualified_table2check := _schema || '.' || _table;
		raise notice 'gm.create_all_enc_epi_sanity_check_triggers(): processing %', _qualified_table2check;
		-- find encounter FK column name
		select col_tbl.attname into _fk_encounter_col
		from pg_catalog.pg_attribute col_tbl
		where
			col_tbl.attrelid = _qualified_table2check::regclass
				and
			col_tbl.attnum = (
				select fk_tbl.conkey[1]
				from pg_catalog.pg_constraint fk_tbl
				where
					fk_tbl.contype = 'f'
						and
					fk_tbl.conrelid = _qualified_table2check::regclass
						and
					fk_tbl.confrelid = 'clin.encounter'::regclass
						and
					fk_tbl.confkey[1] = (
						select col_tbl1.attnum
						from pg_catalog.pg_attribute col_tbl1
						where
							col_tbl1.attname = 'pk'
								AND
							col_tbl1.attrelid = 'clin.encounter'::regclass
					)
				)
			;
		-- find episode FK column name
		select col_tbl.attname into _fk_episode_col
		from pg_catalog.pg_attribute col_tbl
		where
			col_tbl.attrelid = _qualified_table2check::regclass
				and
			col_tbl.attnum = (
				select fk_tbl.conkey[1]
				from pg_catalog.pg_constraint fk_tbl
				where
					fk_tbl.contype = 'f'
						and
					fk_tbl.conrelid = _qualified_table2check::regclass
						and
					fk_tbl.confrelid = 'clin.episode'::regclass
						and
					fk_tbl.confkey[1] = (
						select col_tbl1.attnum
						from pg_catalog.pg_attribute col_tbl1
						where
							col_tbl1.attname = 'pk'
								AND
							col_tbl1.attrelid = 'clin.episode'::regclass
					)
				)
			;
		-- now create the trigger
		BEGIN
			PERFORM gm.create_enc_epi_sanity_check_trigger(_schema, _table, _fk_encounter_col, _fk_episode_col);
		EXCEPTION
			WHEN undefined_table OR invalid_foreign_key THEN
				raise warning 'gm.create_all_enc_epi_sanity_check_triggers(): error processing <%.%>, skipping', _schema, _table;
				_total_success := False;
		END;
	end loop;
	return _total_success;
END;

Function: create_all_table_mod_triggers(_drop_old_triggers boolean)

Returns: boolean

Language: PLPGSQL

(Re)create all table mod triggers for all registered tables.

DECLARE
	_notify_table record;
	_cmd text;
	_total_success boolean;
BEGIN
	_total_success := True;
	-- loop over registered tables
	for _notify_table in select * from gm.notifying_tables loop
		BEGIN
			PERFORM gm.create_table_mod_triggers(_notify_table.schema_name, _notify_table.table_name, _drop_old_triggers);
		EXCEPTION
			WHEN undefined_table OR undefined_column THEN
				raise warning 'gm.create_all_table_mod_triggers(): error processing <%.%>, skipping', _notify_table.schema_name, _notify_table.table_name;
				_total_success := False;
		END;
	end loop;
	return _total_success;
END;

Function: create_enc_epi_sanity_check_trigger(_schema_name name, _table_name name, _fk_encounter_col name, _fk_episode_col name)

Returns: boolean

Language: PLPGSQL

This function can be run on any table in order to add enccounter <-> episode sanity check triggers to that table.

DECLARE
	_qualified_table2check text;
	_msg text;
	_cmd text;
BEGIN
	_qualified_table2check := _schema_name || '.' || _table_name;
	raise notice 'gm.create_enc_epi_sanity_check_trigger(): % (.% vs .%)', _qualified_table2check, _fk_encounter_col, _fk_episode_col;
	-- verify table exists
	if not exists(select 1 from information_schema.tables where table_schema = _schema_name and table_name = _table_name) then
		raise warning 'gm.create_enc_epi_sanity_check_trigger(): table <%> does not exist', _qualified_table2check;
		raise exception undefined_table;
		return false;
	end if;
	-- verify that it points to clin.encounter.pk
	if not exists (
		select 1
		from
			pg_catalog.pg_constraint fk_tbl
		where
			fk_tbl.contype = 'f'
				AND
			fk_tbl.confrelid = 'clin.encounter'::regclass
				AND
			fk_tbl.conrelid = _qualified_table2check::regclass
				AND
			fk_tbl.confkey[1] = (
				select attnum from pg_catalog.pg_attribute col_tbl
				where
					col_tbl.attname = 'pk'
						AND
					col_tbl.attrelid = 'clin.encounter'::regclass
			)
				AND
			fk_tbl.conkey[1] = (
				select attnum from pg_catalog.pg_attribute col_tbl
				where
					col_tbl.attname = _fk_encounter_col
						AND
					col_tbl.attrelid = _qualified_table2check::regclass
			)
	) then
		raise exception
			'gm.create_enc_epi_sanity_check_trigger(): <%.%> does not point to clin.encounter.pk', _qualified_table2check, _fk_encounter_col
			USING ERRCODE = 'invalid_foreign_key'
		;
		return false;
	end if;
	-- verify that it points to clin.episode.pk
	if not exists (
		select 1
		from
			pg_catalog.pg_constraint fk_tbl
		where
			fk_tbl.contype = 'f'
				AND
			fk_tbl.confrelid = 'clin.episode'::regclass
				AND
			fk_tbl.conrelid = _qualified_table2check::regclass
				AND
			fk_tbl.confkey[1] = (
				select attnum from pg_catalog.pg_attribute col_tbl
				where
					col_tbl.attname = 'pk'
						AND
					col_tbl.attrelid = 'clin.episode'::regclass
			)
				AND
			fk_tbl.conkey[1] = (
				select attnum from pg_catalog.pg_attribute col_tbl
				where
					col_tbl.attname = _fk_episode_col
						AND
					col_tbl.attrelid = _qualified_table2check::regclass
			)
	) then
		raise exception
			'gm.create_enc_epi_sanity_check_trigger(): <%.%> does not point to clin.episode.pk', _qualified_table2check, _fk_episode_col
			USING ERRCODE = 'invalid_foreign_key'
		;
		return false;
	end if;
	-- re-create trigger
	_cmd := 'drop trigger if exists tr_sanity_check_enc_epi_ins_upd on ' || _qualified_table2check || ' cascade';
	execute _cmd;
	_cmd := 'create trigger tr_sanity_check_enc_epi_ins_upd ';
	_cmd := _cmd || 'before insert or update ';
	_cmd := _cmd || 'on ' || _qualified_table2check || ' ';
	_cmd := _cmd || 'for each row when (NEW.fk_episode is not null) ';
	_cmd := _cmd || 'execute procedure clin.trf_sanity_check_enc_epi_ins_upd(''' || _fk_encounter_col || ''', ''' || _fk_episode_col || ''')';
	execute _cmd;
	return True;
END;

Function: create_generic_combi_vaccine(text, text[], text, boolean)

Returns: boolean

Language: PLPGSQL

DECLARE
	_name alias for $1;
	_indications alias for $2;
	_atc alias for $3;
	_is_live alias for $4;
	_generic_name text;
	_pk_brand integer;
	_pk_vaccine integer;
	_indication text;
BEGIN
	_generic_name := _name || ' - generic vaccine';
	raise notice 're-creating [%] (%)', _generic_name, array_to_string(_indications, '-');
	-- retrieve or create ref.branded_drug entry for indication
	select pk into _pk_brand from ref.branded_drug
	where
		is_fake is true
			and
		description = _generic_name;
	if FOUND is false then
		insert into ref.branded_drug (
			description,
			preparation,
			is_fake,
			atc_code
		) values (
			_generic_name,
			'vaccine',		-- this is rather arbitrary
			True,
			coalesce(_atc, 'J07')
		)
		returning pk
		into _pk_brand;
	end if;
	-- retrieve or create clin.vaccine entry for generic brand
	select pk into _pk_vaccine from clin.vaccine
	where fk_brand = _pk_brand;
	if FOUND is false then
		insert into clin.vaccine (
			is_live,
			fk_brand
		) values (
			_is_live,
			_pk_brand
		)
		returning pk
		into _pk_vaccine;
	end if;
	-- link indications to vaccine
	delete from clin.lnk_vaccine2inds
	where
		fk_vaccine = _pk_vaccine;
	for _indication in select unnest(_indications) loop
		insert into clin.lnk_vaccine2inds (
			fk_vaccine,
			fk_indication
		) values (
			_pk_vaccine,
			(select id from clin.vacc_indication where description = _indication)
		);
	end loop;
	return true;
END;

Function: create_generic_combi_vaccines()

Returns: boolean

Language: SQL

select gm.create_generic_combi_vaccine (
	'Td'::text,
	ARRAY['tetanus'::text,'diphtheria'::text],
	'J07AM51',
	False
);
select gm.create_generic_combi_vaccine (
	'DT'::text,
	ARRAY['tetanus'::text,'diphtheria'::text],
	'J07AM51',
	False
);
select gm.create_generic_combi_vaccine (
	'TdaP'::text,
	ARRAY['tetanus'::text,'diphtheria'::text,'pertussis'::text],
	'J07CA01',
	False
);
select gm.create_generic_combi_vaccine (
	'TDaP'::text,
	ARRAY['tetanus'::text,'diphtheria'::text,'pertussis'::text],
	'J07CA01',
	False
);
select gm.create_generic_combi_vaccine (
	'TdaP-Pol'::text,
	ARRAY['tetanus'::text,'diphtheria'::text,'pertussis'::text,'poliomyelitis'::text],
	'J07CA02',
	False
);
select gm.create_generic_combi_vaccine (
	'TDaP-Pol'::text,
	ARRAY['tetanus'::text,'diphtheria'::text,'pertussis'::text,'poliomyelitis'::text],
	'J07CA02',
	False
);
select gm.create_generic_combi_vaccine (
	'TDaP-Pol-HiB'::text,
	ARRAY['tetanus'::text,'diphtheria'::text,'pertussis'::text,'poliomyelitis'::text,'haemophilus influenzae b'::text],
	'J07CA06',
	False
);
select gm.create_generic_combi_vaccine (
	'TDaP-Pol-HiB-HepB'::text,
	ARRAY['tetanus'::text,'diphtheria'::text,'pertussis'::text,'poliomyelitis'::text,'haemophilus influenzae b'::text,'hepatitis B'::text],
	'J07CA09',
	False
);
select gm.create_generic_combi_vaccine (
	'MMR'::text,
	ARRAY['measles'::text,'mumps'::text,'rubella'::text],
	'J07BD52',
	True
);
select gm.create_generic_combi_vaccine (
	'MMRV'::text,
	ARRAY['measles'::text,'mumps'::text,'rubella'::text,'varicella (chickenpox, shingles)'::text],
	'J07BD54',
	True
);
select gm.create_generic_combi_vaccine (
	'HepAB'::text,
	ARRAY['hepatitis A'::text,'hepatitis B'::text],
	'J07BC20',
	False
);
select True;

Function: create_generic_monovalent_vaccines()

Returns: boolean

Language: PLPGSQL

DECLARE
	_row record;
	_generic_name text;
	_pk_brand integer;
	_pk_vaccine integer;
BEGIN
	for _row in select * from clin.vacc_indication loop
		_generic_name := _row.description || ' - generic vaccine';
		raise notice 're-creating [%]', _generic_name;
		-- retrieve or create ref.branded_drug entry for indication
		select pk into _pk_brand from ref.branded_drug
		where
			is_fake is true
				and
			description = _generic_name;
		if FOUND is false then
			insert into ref.branded_drug (
				description,
				preparation,
				is_fake,
				atc_code
			) values (
				_generic_name,
				'vaccine',		-- this is rather arbitrary
				True,
				coalesce(_row.atcs_single_indication[1], 'J07')
			)
			returning pk
			into _pk_brand;
		end if;
		-- retrieve or create clin.vaccine entry for generic brand
		select pk into _pk_vaccine from clin.vaccine
		where fk_brand = _pk_brand;
		if FOUND is false then
			insert into clin.vaccine (
				is_live,
				fk_brand
			) values (
				false,
				_pk_brand
			)
			returning pk
			into _pk_vaccine;
		end if;
		-- link indication to vaccine
		delete from clin.lnk_vaccine2inds
		where
			fk_vaccine = _pk_vaccine;
		insert into clin.lnk_vaccine2inds (
			fk_vaccine,
			fk_indication
		) values (
			_pk_vaccine,
			_row.id
		);
	end loop;
	return true;
END;

Function: create_table_mod_triggers(_schema_name name, _table_name name, _drop_old_triggers boolean)

Returns: boolean

Language: PLPGSQL

This function can be run on any table in order to add notification triggers to that table.

DECLARE
	_qualified_table text;
	_msg text;
	_payload text;
	_PK_col_name text;
	_pk_accessor_SQL text;
	_accessor_col text;
	_col_candidate text;
	_identity_accessor_SQL text;
	_cmd text;
BEGIN
	_qualified_table := _schema_name || '.' || _table_name;
	raise notice 'gm.create_table_mod_triggers(): %', _qualified_table;
	-- verify table exists
	if not exists(select 1 from information_schema.tables where table_schema = _schema_name and table_name = _table_name) then
		raise warning 'gm.create_table_mod_triggers(): table <%> does not exist', _qualified_table;
		raise exception undefined_table;
		return false;
	end if;
	-- find PK column
	select
		pg_attribute.attname into _PK_col_name
	from
		pg_index, pg_class, pg_attribute
	where
		pg_class.oid = _qualified_table::regclass
			AND
		indrelid = pg_class.oid
			AND
		pg_attribute.attrelid = pg_class.oid
			AND
		pg_attribute.attnum = any(pg_index.indkey)
			AND
		indisprimary;
	if _PK_col_name is NULL then
		raise warning 'gm.create_table_mod_triggers(): table <%> lacks a primary key', _qualified_table;
		raise exception undefined_column;
		return false;
	end if;
	_pk_accessor_SQL := 'select $1.' || _PK_col_name;
	-- find identity accessor
	-- special case
	if _qualified_table = 'dem.identity' then
		_identity_accessor_SQL := 'select $1.pk';
	else
		-- look for columns by which to retrieve affected person
		_accessor_col := NULL;
		foreach _col_candidate in array array['fk_identity', 'fk_patient', 'id_identity', 'fk_encounter'] loop
			if exists (
				select 1 from pg_class, pg_attribute where
				pg_class.oid = _qualified_table::regclass
					AND
				pg_attribute.attname = _col_candidate
					AND
				pg_attribute.attrelid = pg_class.oid
			) then
				_accessor_col := _col_candidate;
				exit;
			end if;
		end loop;
		if _accessor_col = 'fk_encounter' then					-- retrieve identity PK via fk_encounter
			_identity_accessor_SQL := 'select fk_patient from clin.encounter where pk = $1.fk_encounter limit 1';
		elsif _accessor_col = 'fk_identity' then					-- retrieve identity PK via fk_identity
			_identity_accessor_SQL := 'select $1.fk_identity';
		elsif _accessor_col = 'fk_patient' then					-- retrieve identity PK via fk_patient
			_identity_accessor_SQL := 'select $1.fk_patient';
		elsif _accessor_col = 'id_identity' then					-- retrieve identity PK via id_identity
			_identity_accessor_SQL := 'select $1.id_identity';
		else
			_identity_accessor_SQL := '<NULL>';
		end if;
	end if;
	-- drop triggers should they exist
	-- old-name announcement triggers
	-- remove in v22
	_cmd := 'drop trigger if exists tr_announce_' || _schema_name || '_' || _table_name || '_ins_upd on ' || _qualified_table || ' cascade;';
	execute _cmd;
	_cmd := 'drop trigger if exists tr_announce_' || _schema_name || '_' || _table_name || '_del on ' || _qualified_table || ' cascade;';
	execute _cmd;
	-- new-name announcement triggers
	_cmd := 'drop trigger if exists zzz_tr_announce_' || _schema_name || '_' || _table_name || '_ins_upd on ' || _qualified_table || ' cascade;';
	execute _cmd;
	_cmd := 'drop trigger if exists zzz_tr_announce_' || _schema_name || '_' || _table_name || '_del on ' || _qualified_table || ' cascade;';
	execute _cmd;
	-- remove in v21
	if _drop_old_triggers is true then
		_cmd := 'drop function if exists ' || _schema_name || '.trf_announce_' || _table_name || '_mod() cascade;';
		execute _cmd;
		_cmd := 'drop function if exists ' || _schema_name || '.trf_announce_' || _table_name || '_mod_no_pk() cascade;';
		execute _cmd;
		_cmd := 'drop function if exists ' || _schema_name || '.trf_announce_' || _table_name || '_generic_mod_no_pk() cascade;';
		execute _cmd;
	end if;
	-- re-create triggers
	-- 1) INSERT/UPDATE
	_payload := 'table=' || _qualified_table || '::PK name=' || _PK_col_name;
	_cmd := 'create constraint trigger zzz_tr_announce_' || _schema_name || '_' || _table_name || '_ins_upd';
	_cmd := _cmd || ' after insert or update';
	_cmd := _cmd || ' on ' || _qualified_table;
	-- needed so a SELECT inside, say, _identity_accessor_SQL running
	-- concurrently to a "lengthy" TX does not create a serialization
	-- failure by being a rw-dependancy pivot
	_cmd := _cmd || ' deferrable initially deferred';
	_cmd := _cmd || ' for each row';
	_cmd := _cmd || ' execute procedure gm.trf_announce_table_ins_upd(''' || _payload || ''', ''' || _pk_accessor_SQL || ''', ''' || _identity_accessor_SQL || ''');';
	execute _cmd;
	-- 2) DELETE
	_payload := 'operation=DELETE::' || _payload;
	_cmd := 'create constraint trigger zzz_tr_announce_' || _schema_name || '_' || _table_name || '_del';
	_cmd := _cmd || ' after delete';
	_cmd := _cmd || ' on ' || _qualified_table;
	-- needed so a SELECT inside, say, _identity_accessor_SQL running
	-- concurrently to a "lengthy" TX does not create a serialization
	-- failure by being a rw-dependancy pivot
	_cmd := _cmd || ' deferrable initially deferred';
	_cmd := _cmd || ' for each row';
	_cmd := _cmd || ' execute procedure gm.trf_announce_table_del(''' || _payload || ''', ''' || _pk_accessor_SQL || ''', ''' || _identity_accessor_SQL || ''');';
	execute _cmd;
	return True;
END;

Function: create_user(name, text)

Returns: boolean

Language: PLPGSQL

To create users one needs to have CREATEROLE rights. Only gm-dbo is GRANTed EXECUTE. This way users need to know the gm-dbo (GNUmed admin) password to execute the function. Newly created users belong to group "gm-public" by default.

DECLARE
	_username alias for $1;
	_password alias for $2;
	_database text;
	_query text;
BEGIN
	perform 1 from pg_user where usename = _username;
	if not FOUND then
		_query := 'create user ' || quote_ident(_username)
					|| ' with password ' || quote_literal(_password)
					|| ';';
		execute _query;
		perform 1 from pg_user where usename = _username;
		if not FOUND then
			raise exception 'cannot create user [%]', _username;
			return false;
		end if;
	end if;
	_query := 'alter group "gm-logins" add user ' || quote_ident(_username) || ';';
	execute _query;
	_query := 'alter group "gm-public" add user ' || quote_ident(_username) || ';';
	execute _query;
	-- satisfy "database = samerole" in pg_hba.conf
	select into _database current_database();
	_query := 'alter group ' || quote_ident(_database) || ' add user ' || quote_ident(_username) || ';';
	execute _query;
	return true;
END;

Function: disable_user(name)

Returns: boolean

Language: PLPGSQL

To disable users one needs to have CREATEROLE rights. Only gm-dbo is GRANTed EXECUTE. This way users need to know the gm-dbo (GNUmed admin) password to execute the function.

DECLARE
	_username alias for $1;
	_query text;
BEGIN
	perform 1 from pg_user where usename = _username;
	if not FOUND then
		return true;
	end if;
	_query := 'alter group "gm-logins" drop user ' || quote_ident(_username) || ';';
	execute _query;
	return true;
END;

Function: drop_user(name)

Returns: boolean

Language: PLPGSQL

To drop users one needs to have CREATEROLE rights. Only gm-dbo is GRANTed EXECUTE. This way users need to know the gm-dbo (GNUmed admin) password to execute the function.

DECLARE
	_username alias for $1;
	_query text;
BEGIN
	perform 1 from pg_user where usename = _username;
	if not FOUND then
		return true;
	end if;
	_query := 'drop user ' || quote_ident(_username) || ';';
	execute _query;
	perform 1 from pg_user where usename = _username;
	if FOUND then
		return false;
	end if;
	return true;
END;

Function: get_users()

Returns: text[]

Language: SQL

select gm.get_users(current_database());

Function: get_users(name)

Returns: text[]

Language: PLPGSQL

Convenience function listing all PostgreSQL accounts (roles) needed for a consistent dump of the database.

DECLARE
	_db alias for $1;
	_gm_users text[];
	_user text;
BEGIN
	-- GNUmed group roles
	_gm_users := ARRAY['gm-logins', 'gm-public', 'gm-doctors', 'gm-staff', _db];
	-- add roles being *members* of groups gm-logins, gm-public, _db
	FOR _user in
		select distinct rolname from pg_roles where oid in (
			select member from pg_auth_members where roleid in (
				select oid from pg_roles where rolname in ('gm-logins', 'gm-public', _db)
			)
		)
	LOOP
		continue when _user = 'postgres';
		continue when _user = any(_gm_users);
		_gm_users := _gm_users || _user;
	END LOOP;
	-- add roles mentioned in any *.modified_by fields
	FOR _user in select distinct modified_by from audit.audit_fields LOOP
		continue when _user = 'postgres';
		continue when _user = any(_gm_users);
		_gm_users := _gm_users || _user;
	END LOOP;
	-- add roles mentioned in dem.staff.db_user
	FOR _user in select distinct db_user from dem.staff LOOP
		continue when _user = 'postgres';
		continue when _user = any(_gm_users);
		_gm_users := _gm_users || _user;
	END LOOP;
	return _gm_users;
END;

Function: is_null_or_blank_string(text)

Returns: boolean

Language: SQL

input is either NULL or empty string -> True; input is not NULL and not empty -> FALSE

select (coalesce(trim($1), '') = '');

Function: is_null_or_non_empty_string(text)

Returns: boolean

Language: SQL

select (coalesce(trim($1), 'NULL') != '');

Function: lo_chunked_md5(oid, integer)

Returns: text

Language: PLPGSQL

Function to create a chunked md5 sum on arbitrarily large LARGE OBJECTs.

DECLARE
	lo_id alias for $1;
	chunk_size alias for $2;
	_lo_fd integer;
	_lo_size integer;
	_chunk_count integer;
	_remainder integer;
	_md5_concat text;
	INV_READ constant integer := x'40000'::integer;
	SEEK_SET constant integer := 0;
	SEEK_END constant integer := 2;
BEGIN
	-- check for existence of lo_id ?
	_lo_fd := lo_open(lo_id, INV_READ);
	-- get size
	_lo_size := lo_lseek(_lo_fd, 0, SEEK_END);
	PERFORM lo_close(_lo_fd);						-- move further down if loread() proves faster
	-- calculate chunks and remainder
	_chunk_count := _lo_size / chunk_size;
	_remainder := _lo_size % chunk_size;
	-- loop over chunks
	_md5_concat := '';
	FOR _chunk_id in 1.._chunk_count LOOP
		_md5_concat := _md5_concat || md5(lo_get(lo_id, (_chunk_id - 1) * chunk_size, chunk_size));
		-- using loread() may be faster (as it directly accesses the
		-- existing lo_fd and thusly does not need to re-open the LO
		-- each round)
	END LOOP;
	-- add remainder
	_md5_concat := _md5_concat || md5(lo_get(lo_id, _chunk_count * chunk_size, _remainder));
	return md5(_md5_concat);
END;

Function: log_access2emr(text)

Returns: void

Language: PLPGSQL

This logs access to a patient EMR.

DECLARE
	_action alias for $1;
BEGIN
	if gm.is_null_or_blank_string(_action) then
		raise exception 'gm.log_access2emr(): action detail cannot be NULL or empty';
	end if;
	insert into gm.access_log (user_action) values ('EMR access: ' || _action);
	return;
END;

Function: log_other_access(text)

Returns: void

Language: SQL

This logs access to the database.

insert into gm.access_log (user_action) values ($1);

Function: log_script_insertion(text, text)

Returns: text

Language: PLPGSQL

declare
	_filename alias for $1;
	_version alias for $2;
	_hash text;
begin
	delete from gm.schema_revision where filename = _filename;
	insert into gm.schema_revision (filename, version) values (
		_filename,
		_version
	);
	perform gm.log_other_access (
		'database change script inserted: ' || _filename || ' (' || _version || ')'
	);
	select into _hash md5(gm.concat_table_structure());
	return _hash;
end;

Function: nullify_empty_string(text)

Returns: text

Language: PLPGSQL

DECLARE
	_input alias for $1;
BEGIN
	if _input is null then
		return null;
	end if;
	if trim(_input) = '' then
		return null;
	end if;
	return _input;
END;

Function: register_notifying_table(name, name)

Returns: boolean

Language: PLPGSQL

Register given table for notification trigger generator. Parameters are: (schema, table)

DECLARE
	_namespace alias for $1;
	_table alias for $2;
	dummy RECORD;
BEGIN
	-- does table exist ?
	select relname into dummy from pg_class where
		relname = _table and
		relnamespace = (select oid from pg_namespace where nspname = _namespace)
	;
	if not found then
		raise exception 'register_notifying_table(): Table [%.%] does not exist.', _namespace, _table;
	end if;
	-- make sure we can insert
	delete from gm.notifying_tables where
		table_name = _table
		and schema_name = _namespace;
	insert into gm.notifying_tables (
		schema_name,
		table_name
	) values (
		_namespace,
		_table
	);
	return true;
END;

Function: strip_allzeros_fraction(numeric)

Returns: numeric

Language: PLPGSQL

Remove fractions containing only zeros (n.000...) from NUMERICs/DECIMALs.

DECLARE
	_numeric_value alias for $1;
	_fraction numeric;
	_msg text;
BEGIN
	_fraction := _numeric_value - trunc(_numeric_value);
	if _fraction <> 0 then
		return _numeric_value;
	end if;
	BEGIN
		return _numeric_value::bigint::numeric;
	EXCEPTION
		WHEN numeric_value_out_of_range THEN
			RAISE NOTICE '[gm.strip_allzeros_fraction]: cannot strip from %', _numeric_value;
			RETURN _numeric_value;
	END;
END;

Function: transfer_users(text)

Returns: boolean

Language: SQL

select gm.transfer_users($1, current_database());

Function: transfer_users(text, text)

Returns: boolean

Language: PLPGSQL

This function transfers adds users from the group role given in the argument to the group role corresponding to the current database name. This enables group membership based authentication as used in GNUmed. This operation is typically only run on database upgrade and is only available to gm-dbo.

DECLARE
	_source_group alias for $1;
	_target_group alias for $2;
	member_ids int[];
	member_id int;
	member_name text;
	tmp text;
BEGIN
	-- source group exists ?
	perform 1 from pg_group where groname = _source_group;
	if not FOUND then
		raise exception 'gm_transfer_users(): source group [%] does not exist', _source_group;
		return false;
	end if;
	-- target group exists ?
	perform 1 from pg_group where groname = _target_group;
	if not FOUND then
		raise exception 'gm_transfer_users(): target group [%] does not exist', _target_group;
		return false;
	end if;
	-- loop over group member IDs
	select into member_ids grolist from pg_group where groname = _source_group;
	FOR idx IN coalesce(array_lower(member_ids, 1), 0) .. coalesce(array_upper(member_ids, 1), -1) LOOP
		member_id := member_ids[idx];
		select into member_name usename from pg_user where usesysid = member_id;
		tmp := 'gm_transfer_users(text): transferring "'
				|| member_name || '" ('
				|| member_id || ') from group "'
				|| _source_group || '" to group "'
				|| _target_group || '"';
		raise notice '%', tmp;
		-- satisfy "database = samegroup" in pg_hba.conf
		tmp := 'alter group ' || quote_ident(_target_group) || ' add user ' || quote_ident(member_name) || ';';
		execute tmp;
	end LOOP;
	return true;
END;

Function: trf_announce_table_del()

Returns: trigger

Language: PLPGSQL

Trigger function announcing a DELETE on a table. sends signal: gm_table_mod payload: operation=DELETE, table=the table that is updated, PK name=the name of the PK column of the table (requires single column PKs), row PK=the PK of the affected row, person PK=the PK of the affected person,

declare
	_payload text;
	_pk_accessor_SQL text;
	_pk_col_val integer;
	_identity_accessor_SQL text;
	_pk_identity integer;
begin
	_pk_accessor_SQL := TG_ARGV[1];
	EXECUTE _pk_accessor_SQL INTO STRICT _pk_col_val USING OLD;
	_payload := TG_ARGV[0] || '::row PK=' || coalesce(_pk_col_val::text, 'NULL');
	_identity_accessor_SQL := TG_ARGV[2];
	if _identity_accessor_SQL <> '<NULL>' then
		--raise notice '%.%: %', TG_TABLE_SCHEMA, TG_TABLE_NAME, _identity_accessor_SQL;
		EXECUTE _identity_accessor_SQL INTO STRICT _pk_identity USING OLD;
		_payload := _payload || '::person PK=' || coalesce(_pk_identity::text, 'NULL');
	end if;
	perform pg_notify('gm_table_mod', _payload);
	return NULL;
end;

Function: trf_announce_table_ins_upd()

Returns: trigger

Language: PLPGSQL

Trigger function announcing an INSERT or UPDATE to a table. sends signal: gm_table_mod payload: operation=INSERT/UPDATE, table=the table that is updated, PK name=the name of the PK column of the table (requires single column PKs), row PK=the PK of the affected row, person PK=the PK of the affected person,

declare
	_payload text;
	_pk_accessor_SQL text;
	_pk_col_val integer;
	_identity_accessor_SQL text;
	_pk_identity integer;
begin
	_pk_accessor_SQL := TG_ARGV[1];
	EXECUTE _pk_accessor_SQL INTO STRICT _pk_col_val USING NEW;
	_payload := 'operation=' || TG_OP || '::' || TG_ARGV[0] || '::row PK=' || coalesce(_pk_col_val::text, 'NULL');
	_identity_accessor_SQL := TG_ARGV[2];
	if _identity_accessor_SQL <> '<NULL>' then
		EXECUTE _identity_accessor_SQL INTO STRICT _pk_identity USING NEW;
		_payload := _payload || '::person PK=' || coalesce(_pk_identity::text, 'NULL');
	end if;
	perform pg_notify('gm_table_mod', _payload);
	return NULL;
end;

Function: user_exists(name)

Returns: boolean

Language: PLPGSQL

BEGIN
	perform 1 from pg_user where usename = $1;
	if not FOUND then
		raise notice 'Cannot set database language. User % does not exist.', $1;
		return false;
	end if;
	return true;
END;

Function: xid2int(xid)

Returns: integer

Language: SQL

select $1::text::integer;

Generated by PostgreSQL Autodoc

W3C HTML 4.01 Strict