Dumped on 2015-04-16
This logs access to the database and to records. Needed for HIPAA compliance among other things.
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 |
Name | Constraint |
---|---|
non_empty_user_action | CHECK ((gm.is_null_or_blank_string(user_action) IS FALSE)) |
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.
F-Key | Name | Type | Description |
---|---|---|---|
pk | serial | PRIMARY KEY | |
schema_name | name | UNIQUE#1 NOT NULL DEFAULT 'public'::name | |
table_name | name | UNIQUE#1 NOT NULL | |
signal | name |
UNIQUE#1
The name of the signal to send via NOTIFY. The actual name of the signal will be "<signal>_mod_db:<identity_pk>" where the :<identity_pk> is only added if the notify trigger knows how. |
|
carries_identity_pk | boolean |
DEFAULT false
Whether or not the signal delivers the PK of the related identity. Set during bootstrapping. |
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
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 |
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;
Mark given table for notification trigger generator. Parameters are: (schema, table). Defaults signal to table name.
select gm.add_table_for_notifies($1, $2, $2);
Mark given table for notification trigger generator. Parameters are: (schema, table, signal name)
select gm.register_notifying_table($1, $2, $3);
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;
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 ;
declare _struct text; begin select into _struct gm.concat_table_structure_v19_and_up(); return _struct; end;
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;
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;
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;
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;
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;
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;
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;
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;
(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;
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;
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;
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;
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; _old_signal 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 is NULL then _identity_accessor_SQL := '<NULL>'; elsif _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'; end if; end if; -- drop triggers should they exist _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; _cmd := 'drop trigger if exists tr_sanity_check_enc_epi_insert on ' || _qualified_table || ' cascade;'; execute _cmd; if _drop_old_triggers is true then select signal from gm.notifying_tables where schema_name = _schema_name and table_name = _table_name limit 1 into strict _old_signal; _cmd := 'drop function if exists ' || _schema_name || '.trf_announce_' || _table_name || '_mod() cascade;'; execute _cmd; _cmd := 'drop function if exists ' || _schema_name || '.trf_announce_' || _old_signal || '_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_' || _old_signal || '_mod_no_pk() cascade;'; execute _cmd; _cmd := 'drop function if exists ' || _schema_name || '.trf_announce_' || _table_name || '_generic_mod_no_pk() cascade;'; execute _cmd; _cmd := 'drop function if exists ' || _schema_name || '.trf_announce_' || _old_signal || '_generic_mod_no_pk() cascade;'; execute _cmd; end if; -- re-create triggers _payload := 'table=' || _qualified_table || '::PK name=' || _PK_col_name; _cmd := 'create constraint trigger tr_announce_' || _schema_name || '_' || _table_name || '_ins_upd'; _cmd := _cmd || ' after insert or update'; _cmd := _cmd || ' on ' || _qualified_table; _cmd := _cmd || ' deferrable'; _cmd := _cmd || ' for each row'; if _identity_accessor_SQL is NULL then _cmd := _cmd || ' execute procedure gm.trf_announce_table_ins_upd(''' || _payload || ''', ''' || _pk_accessor_SQL || ''', NULL);'; else _cmd := _cmd || ' execute procedure gm.trf_announce_table_ins_upd(''' || _payload || ''', ''' || _pk_accessor_SQL || ''', ''' || _identity_accessor_SQL || ''');'; end if; execute _cmd; _payload := 'operation=DELETE::' || _payload; _cmd := 'create constraint trigger tr_announce_' || _schema_name || '_' || _table_name || '_del'; _cmd := _cmd || ' after delete'; _cmd := _cmd || ' on ' || _qualified_table; _cmd := _cmd || ' deferrable'; _cmd := _cmd || ' for each row'; if _identity_accessor_SQL is NULL then _cmd := _cmd || ' execute procedure gm.trf_announce_table_del(''' || _payload || ''', ''' || _pk_accessor_SQL || ''', NULL);'; else _cmd := _cmd || ' execute procedure gm.trf_announce_table_del(''' || _payload || ''', ''' || _pk_accessor_SQL || ''', ''' || _identity_accessor_SQL || ''');'; end if; execute _cmd; -- encounter vs episode patient link sanity check trigger if exists ( select 1 from information_schema.columns where table_schema = _schema_name and table_name = _table_name and column_name = 'fk_encounter' ) then if exists ( select 1 from information_schema.columns where table_schema = _schema_name and table_name = _table_name and column_name = 'fk_episode' ) then _cmd := 'create trigger tr_sanity_check_enc_epi_insert before insert'; _cmd := _cmd || ' on ' || _qualified_table; _cmd := _cmd || ' for each row execute procedure clin.trf_sanity_check_enc_epi_insert();'; execute _cmd; end if; end if; return True; END;
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;
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;
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;
select gm.get_users(current_database());
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;
input is either NULL or empty string -> True; input is not NULL and not empty -> FALSE
select (coalesce(trim($1), '') = '');
select (coalesce(trim($1), 'NULL') != '');
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;
This logs access to the database.
insert into gm.access_log (user_action) values ($1);
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;
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;
Mark given table for notification trigger generator. Parameters are: (schema, table). Defaults signal to table name.
select gm.register_notifying_table($1, $2, $2);
Register given table for notification trigger generator. Parameters are: (schema, table, signal name)
DECLARE _namespace alias for $1; _table alias for $2; _signal alias for $3; 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 and signal = _signal; insert into gm.notifying_tables ( schema_name, table_name, signal ) values ( _namespace, _table, _signal ); return true; END;
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;
select gm.transfer_users($1, current_database());
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;
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=' || _pk_col_val; _identity_accessor_SQL := TG_ARGV[2]; if _identity_accessor_SQL <> '<NULL>' then EXECUTE _identity_accessor_SQL INTO STRICT _pk_identity USING OLD; _payload := _payload || '::person PK=' || _pk_identity; end if; perform pg_notify('gm_table_mod', _payload); return NULL; end;
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=' || _pk_col_val; _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=' || _pk_identity; end if; perform pg_notify('gm_table_mod', _payload); return NULL; end;
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;
select $1::text::integer;
Generated by PostgreSQL Autodoc