Cool Stuff in PostgreSQL 10: Auto-logging

We started off by creating a logging infrastructure, then arranging for a single table to use it.

Rather than repeat that work for each table, let's use a relatively although not completely new feature: EVENT TRIGGER. The idea here is that we fire a trigger on CREATE TABLE and see to it that the table is logged. We'll write the trigger first, even though in reality, we'd need to load the function it calls first.

  1. CREATE EVENT TRIGGER add_logger
  2. ON ddl_command_end
  3. WHEN tag IN ('create table')
  4. EXECUTE PROCEDURE add_logger();
  5. COMMENT ON EVENT TRIGGER add_logger IS 'Ensure that each table which is not a log gets logged';

The magic happens inside add_logger(), but it's magic we've already seen. First, we'll get the table's name and schema using pg_event_trigger_ddl_commands(), filtering out tables which are already log tables. The test here is crude and string-based, but we could easily go to schema-based ones.

  1. CREATE OR REPLACE FUNCTION add_logger()
  2. RETURNS event_trigger
  3. LANGUAGE plpgsql
  4. AS $$
  5. DECLARE
  6. r RECORD;
  7. cmd TEXT;
  8. BEGIN
  9. SELECT p.*, c.relname as table_name INTO STRICT r
  10. FROM
  11. pg_catalog.pg_event_trigger_ddl_commands() p
  12. JOIN
  13. pg_catalog.pg_class c
  14. ON (p.objid = c.oid)
  15. WHERE
  16. p.object_type = 'table' AND
  17. c.relname !~ '_log'; /* No recursing! */
  18. IF NOT FOUND THEN
  19. RAISE NOTICE 'Skipping log table';
  20. RETURN;
  21. END IF;

Next, we add the log table for the table in question:

  1. RAISE NOTICE 'Adding log table(s) for %.%', r.schema_name, r.table_name;
  2. cmd := format('CREATE TABLE IF NOT EXISTS %I
  3. PARTITION OF the_log
  4. FOR VALUES IN (%L)
  5. PARTITION BY LIST(table_name);',
  6. pg_catalog.concat_ws('_', r.schema_name, 'log'),
  7. r.schema_name
  8. );
  9. EXECUTE cmd;
  10. cmd := format('CREATE TABLE IF NOT EXISTS %I
  11. PARTITION OF %s
  12. FOR VALUES IN (%L);',
  13. pg_catalog.concat_ws('_', r.schema_name, r.table_name, 'log'),
  14. pg_catalog.concat_ws('_', r.schema_name, 'log'),
  15. r.table_name
  16. );
  17. EXECUTE cmd;

Finally, the triggers that populate it.

  1. cmd := format(
  2. $q$CREATE TRIGGER %I
  3. AFTER INSERT ON %I.%I
  4. REFERENCING NEW TABLE AS new_table
  5. FOR EACH STATEMENT
  6. EXECUTE PROCEDURE public.log();$q$,
  7. pg_catalog.concat_ws('_', 'log_insert', r.schema_name, r.table_name),
  8. r.schema_name,
  9. r.table_name
  10. );
  11. EXECUTE cmd;
  12. cmd := format(
  13. $q$CREATE TRIGGER %I
  14. AFTER UPDATE ON %I.%I
  15. REFERENCING OLD TABLE AS old_table NEW TABLE AS new_table
  16. FOR EACH STATEMENT
  17. EXECUTE PROCEDURE public.log();$q$,
  18. pg_catalog.concat_ws('_', 'log_update', r.schema_name, r.table_name),
  19. r.schema_name,
  20. r.table_name
  21. );
  22. EXECUTE cmd;
  23. cmd := format(
  24. $q$CREATE TRIGGER %I
  25. AFTER DELETE ON %I.%I
  26. REFERENCING OLD TABLE AS old_table
  27. FOR EACH STATEMENT
  28. EXECUTE PROCEDURE public.log();$q$,
  29. pg_catalog.concat_ws('_', 'log_delete', r.schema_name, r.table_name),
  30. r.schema_name,
  31. r.table_name
  32. );
  33. EXECUTE cmd;
  34. EXCEPTION
  35. WHEN no_data_found THEN
  36. NULL;
  37. WHEN too_many_rows THEN
  38. RAISE EXCEPTION 'This function should only fire on one table, not this list: %', r.object_identity;
  39. END;
  40. $$;

This concludes this mini-series on auto-logging. I hope you've enjoyed it, and until the next time, keep database doing!

Comments

  1. Is this related to previous articles where "the_log" table was created and hence is dependent on them?

    testing=# create table mytable (akey int not null primary key, avalue text);
    NOTICE: Adding log table(s) for public.mytable
    ERROR: relation "the_log" does not exist
    CONTEXT: SQL statement "CREATE TABLE IF NOT EXISTS public_log
    PARTITION OF the_log
    FOR VALUES IN ('public')
    PARTITION BY LIST(table_name);"
    PL/pgSQL function add_logger() line 28 at EXECUTE

    ReplyDelete

Post a Comment

Popular posts from this blog

Cool Stuff in PostgreSQL 10: Transition Table Triggers

Cool Stuff in PostgreSQL 10: Partitioned Audit Table