brandur.org

Published fragment: Easy, alternative soft deletion: deleted_record_insert.

We’ve switched away from traditional soft deletion using a deleted_at column which is wound into every query like deleted_at IS NULL to one that uses a schemaless deleted_record table that’s still useful for debugging, but doesn’t need to be threaded in throughout production code, and razes a whole class of bugs involving forgotten deleted_at IS NULL predicates or foreign key problems (see Soft deletion probably isn’t worth it).

I recommend the use of a generic insertion function:

CREATE FUNCTION deleted_record_insert() RETURNS trigger
    LANGUAGE plpgsql
AS $$
    BEGIN
        EXECUTE 'INSERT INTO deleted_record
          (data, object_id, table_name)
        VALUES
          ($1, $2, $3)'
        USING to_jsonb(OLD.*), OLD.id, TG_TABLE_NAME;

        RETURN OLD;
    END;
$$;

Which can then be used as an AFTER DELETE trigger on any table:

CREATE TRIGGER deleted_record_insert AFTER DELETE ON invoice
    FOR EACH ROW EXECUTE FUNCTION deleted_record_insert();

Our results have been shockingly good. No bugs to speak of, no reduction in operational visibility, and less friction in writing new code and analytics.