Writing idempotent administration scripts

In many environments, it’s important to execute idempotent administration scripts, i.e. scripts that are able to build or update an E-Maj environment, whatever is its initial state. An E-Maj environment can be considered as a parameters set and a tables groups set for which the tables and sequences they contain must be described and that must be managed then (groups start and stop, marks set,…).

Set of parameters

Two working approaches exist.

Global parameters configuration management

It deals with loading a parameters set in JSON format, read from a flat file or a table column, using the emaj_import_parameters_configuration() function with the second parameter set to TRUE to reset any E-Maj parameter not in the JSON description.

SELECT emaj.emaj_import_parameters_configuration (JSON.configuration, TRUE);

The JSON configuration may have been build manually or using the emaj_export_parameters_configuration() function.

Unitary management

It is also possible to execute a script that sets all E-Maj parameters in a single transaction, a NULL value been used for parameters keeping their default value:

BEGIN;
  SELECT emaj.emaj_set_param(parameter.key 1, parameter.value 1);
  SELECT emaj.emaj_set_param(parameter.key 2, parameter.value 2);      …
  ...
COMMIT;

Tables groups content

Here again, there are two working approaches.

Managing a global tables groups configuration

Alike for parameters, a global tables groups configuration can be defined into a JSON structure stored into a flat file or a table column. The emaj_import_groups_configuration() function “loads” such a configuration. Missing groups are created and groups whose content differs are automatically updated. In order to get an idempotent operation, it is necessary to:

  • import all groups from the configuration at once, with the second parameter set to NULL (or set to the exhaustive tables groups list);

  • authorize the update of in LOGGING state tables groups, with the 3rd parameter set to TRUE;

  • drop any existing group that is not in the configuration to import, with the 5th parameter set to TRUE.

SELECT emaj.emaj_import_groups_configuration (JSON.configuration, NULL, TRUE, '<marque>', TRUE);

The JSON configuration to load may have been built manualy or using the emaj_export_groups_configuration() function.

Elementary groups configuration

The alternative approach consists in writing a script containing all the elementary actions needed to create, populate, modify or even drop tables groups, into a single transaction, and taking the current state into account.

To create missing tables groups:

SELECT emaj.emaj_create_group ('myGroup1', ...)
   WHERE NOT emaj_does_exist_group('myGroup1');
SELECT emaj.emaj_create_group ('myGroup2', ...)
   WHERE NOT emaj_does_exist_group('myGroup2');
...

To drop obsolete groups, once stopped:

SELECT emaj.emaj_drop_group (group_name)
   FROM unnest (emaj.emaj_get_groups () ) AS group_name
   WHERE group_name NOT IN ('myGroup1', 'myGroup2', ...);

To assign the table sch1.tbl1 or the sequence sch1.seq1 to the tables group grp1, if it is not yet the case:

SELECT CASE
   WHEN emaj_get_assigned_group_table('sch1', 'tbl1') IS NULL
      THEN emaj.emaj_assign_table('sch1', 'tbl1', 'grp1', ...)
   WHEN emaj_get_assigned_group_table('sch1', 'tbl1') <> 'grp1'
      THEN emaj.emaj_move_table('sch1', 'tbl1', 'grp1')
   ELSE CONTINUE
   END;

SELECT CASE
   WHEN emaj_get_assigned_group_sequence('sch1', 'seq1') IS NULL
      THEN emaj.emaj_assign_sequence('sch1', 'seq1', 'grp1')
   WHEN emaj_get_assigned_group_sequence('sch1', 'seq1') <> 'grp1'
      THEN emaj.emaj_move_sequence('sch1', 'seq1', 'grp1')
   ELSE CONTINUE
   END;

By extension, to assign all tables from schema sch1 to the same group:

SELECT CASE
   WHEN emaj_get_assigned_group_table(nspname, relname) IS NULL
      THEN emaj.emaj_assign_table(nspname, relname, 'grp1', options)
   WHEN emaj_get_assigned_group_table(nspname, relname) <> 'grp1'
      THEN emaj.emaj_move_table(nspname, relname, 'grp1')
   ELSE CONTINUE
   END
   FROM pg_class
        JOIN pg_namespace ON (pg_namespace.oid = relnamespace)
   WHERE nspname = 'sch1' AND relkind = 'r';

If E-Maj properties of tables belonging to a group have non default values, it is important to check their target values, using emaj_modify_table() and/or emaj_modify_tables() functions. In the JSONB input parameter, properties keeping their default values must be explicitely set to null.

SELECT emaj.emaj_modify_tables ('sch1', '.*', null,
   '{ "priority" : null, "log_data_tablespace" : null, "log_index_tablespace" : null,
      "ignored_triggers" : null }'));

SELECT emaj.emaj_modify_table ('sch1', 'tbl1',
   '{ "priority" : 1, "ignored_triggers" : ["trg1"] }'));

Tables groups state

It is possible to set a mark on a tables group depending on its current state:

SELECT emaj.emaj_set_mark_group ('grp1', '<mark>')
   WHERE emaj.emaj_is_logging_group('grp1');

To start or stop all tables groups, whatever their current state:

SELECT emaj.emaj_start_groups (emaj.emaj_get_idle_groups(),
   '<start_mark>');

SELECT emaj.emaj_stop_groups (emaj.emaj_get_logging_groups());

Similarly, a common mark can be set on all started groups, with:

SELECT emaj.emaj_set_mark_groups (emaj.emaj_get_logging_groups(),
   '<mark>');

Let’s remind that emaj_get_groups(), emaj_get_logging_groups() and emaj_get_idle_groups() functions have parameters that filter group names.

Finaly, the emaj_protect_group() and emaj_unprotect_group() functions that respectively protect and unprotect a tables group against E-Maj rollbacks, are idempotent by nature. So they are safely callable without knowing the current group protection level.