CREATE OR REPLACE FUNCTION logidze_logger() RETURNS TRIGGER AS $body$
-- version: 4 DECLARE changes jsonb; version jsonb; full_snapshot boolean; log_data jsonb; new_v integer; size integer; history_limit integer; debounce_time integer; current_version integer; k text; iterator integer; item record; columns text[]; include_columns boolean; ts timestamp with time zone; ts_column text; err_sqlstate text; err_message text; err_detail text; err_hint text; err_context text; err_table_name text; err_schema_name text; err_jsonb jsonb; err_captured boolean; BEGIN ts_column := NULLIF(TG_ARGV[1], 'null'); columns := NULLIF(TG_ARGV[2], 'null'); include_columns := NULLIF(TG_ARGV[3], 'null'); IF NEW.log_data is NULL OR NEW.log_data = '{}'::jsonb THEN IF columns IS NOT NULL THEN log_data = logidze_snapshot(to_jsonb(NEW.*), ts_column, columns, include_columns); ELSE log_data = logidze_snapshot(to_jsonb(NEW.*), ts_column); END IF; IF log_data#>>'{h, -1, c}' != '{}' THEN NEW.log_data := log_data; END IF; ELSE IF TG_OP = 'UPDATE' AND (to_jsonb(NEW.*) = to_jsonb(OLD.*)) THEN RETURN NEW; -- pass END IF; history_limit := NULLIF(TG_ARGV[0], 'null'); debounce_time := NULLIF(TG_ARGV[4], 'null'); log_data := NEW.log_data; current_version := (log_data->>'v')::int; IF ts_column IS NULL THEN ts := statement_timestamp(); ELSEIF TG_OP = 'UPDATE' THEN ts := (to_jsonb(NEW.*) ->> ts_column)::timestamp with time zone; IF ts IS NULL OR ts = (to_jsonb(OLD.*) ->> ts_column)::timestamp with time zone THEN ts := statement_timestamp(); END IF; ELSEIF TG_OP = 'INSERT' THEN ts := (to_jsonb(NEW.*) ->> ts_column)::timestamp with time zone; IF ts IS NULL OR (extract(epoch from ts) * 1000)::bigint = (NEW.log_data #>> '{h,-1,ts}')::bigint THEN ts := statement_timestamp(); END IF; END IF; full_snapshot := (coalesce(current_setting('logidze.full_snapshot', true), '') = 'on') OR (TG_OP = 'INSERT'); IF current_version < (log_data#>>'{h,-1,v}')::int THEN iterator := 0; FOR item in SELECT * FROM jsonb_array_elements(log_data->'h') LOOP IF (item.value->>'v')::int > current_version THEN log_data := jsonb_set( log_data, '{h}', (log_data->'h') - iterator ); END IF; iterator := iterator + 1; END LOOP; END IF; changes := '{}'; IF full_snapshot THEN BEGIN changes = hstore_to_jsonb_loose(hstore(NEW.*)); EXCEPTION WHEN NUMERIC_VALUE_OUT_OF_RANGE THEN changes = row_to_json(NEW.*)::jsonb; FOR k IN (SELECT key FROM jsonb_each(changes)) LOOP IF jsonb_typeof(changes->k) = 'object' THEN changes = jsonb_set(changes, ARRAY[k], to_jsonb(changes->>k)); END IF; END LOOP; END; ELSE BEGIN changes = hstore_to_jsonb_loose( hstore(NEW.*) - hstore(OLD.*) ); EXCEPTION WHEN NUMERIC_VALUE_OUT_OF_RANGE THEN changes = (SELECT COALESCE(json_object_agg(key, value), '{}')::jsonb FROM jsonb_each(row_to_json(NEW.*)::jsonb) WHERE NOT jsonb_build_object(key, value) <@ row_to_json(OLD.*)::jsonb); FOR k IN (SELECT key FROM jsonb_each(changes)) LOOP IF jsonb_typeof(changes->k) = 'object' THEN changes = jsonb_set(changes, ARRAY[k], to_jsonb(changes->>k)); END IF; END LOOP; END; END IF; changes = changes - 'log_data'; IF columns IS NOT NULL THEN changes = logidze_filter_keys(changes, columns, include_columns); END IF; IF changes = '{}' THEN RETURN NEW; -- pass END IF; new_v := (log_data#>>'{h,-1,v}')::int + 1; size := jsonb_array_length(log_data->'h'); version := logidze_version(new_v, changes, ts); IF ( debounce_time IS NOT NULL AND (version->>'ts')::bigint - (log_data#>'{h,-1,ts}')::text::bigint <= debounce_time ) THEN -- merge new version with the previous one new_v := (log_data#>>'{h,-1,v}')::int; version := logidze_version(new_v, (log_data#>'{h,-1,c}')::jsonb || changes, ts); -- remove the previous version from log log_data := jsonb_set( log_data, '{h}', (log_data->'h') - (size - 1) ); END IF; log_data := jsonb_set( log_data, ARRAY['h', size::text], version, true ); log_data := jsonb_set( log_data, '{v}', to_jsonb(new_v) ); IF history_limit IS NOT NULL AND history_limit <= size THEN log_data := logidze_compact_history(log_data, size - history_limit + 1); END IF; NEW.log_data := log_data; END IF; RETURN NEW; -- result EXCEPTION WHEN OTHERS THEN GET STACKED DIAGNOSTICS err_sqlstate = RETURNED_SQLSTATE, err_message = MESSAGE_TEXT, err_detail = PG_EXCEPTION_DETAIL, err_hint = PG_EXCEPTION_HINT, err_context = PG_EXCEPTION_CONTEXT, err_schema_name = SCHEMA_NAME, err_table_name = TABLE_NAME; err_jsonb := jsonb_build_object( 'returned_sqlstate', err_sqlstate, 'message_text', err_message, 'pg_exception_detail', err_detail, 'pg_exception_hint', err_hint, 'pg_exception_context', err_context, 'schema_name', err_schema_name, 'table_name', err_table_name ); err_captured = logidze_capture_exception(err_jsonb); IF err_captured THEN return NEW; ELSE RAISE; END IF; END;
$body$ LANGUAGE plpgsql;