Skip to content

Commit bf6a0da

Browse files
committed
Add JSON Audit Logging feature
1 parent 485ff3b commit bf6a0da

File tree

3 files changed

+93
-0
lines changed

3 files changed

+93
-0
lines changed

README.rdoc

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,37 @@ table :: The name of the table
213213
defaults :: A hash of default values to enforce, where keys are column names
214214
and values are the default values to enforce
215215

216+
=== JSON Audit Logging - pgt_json_audit_log_setup and pg_json_audit_log
217+
218+
These methods setup an auditing function where updates and deletes log
219+
the previous values to a central auditing table in JSON format.
220+
221+
==== pgt_json_audit_log_setup
222+
223+
This creates an audit table and a trigger function that will log
224+
previous values to the audit table. This returns the name of the
225+
trigger function created, which should be passed to
226+
+pgt_json_audit_log+.
227+
228+
Arguments:
229+
table :: The name of the table storing the audit logs.
230+
231+
Options:
232+
function_opts :: Options to pass to +create_function+ when creating
233+
the trigger function.
234+
235+
==== pgt_json_audit_log
236+
237+
This adds a trigger to the table that will log previous values to the
238+
audting table for updates and deletes.
239+
240+
Arguments:
241+
table :: The name of the table to audit
242+
function :: The name of the trigger function to call to log changes
243+
244+
Note that it is probably a bad idea to use the same table argument
245+
to both +pgt_json_audit_log_setup+ and +pgt_json_audit_log+.
246+
216247
== License
217248

218249
This library is released under the MIT License. See the MIT-LICENSE

lib/sequel/extensions/pg_triggers.rb

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,33 @@ def pgt_immutable(table, *columns)
8282
pgt_trigger(table, trigger_name, function_name, :update, "BEGIN #{ifs} RETURN NEW; END;")
8383
end
8484

85+
def pgt_json_audit_log_setup(table, opts={})
86+
function_name = opts[:function_name] || "pgt_jal_#{table}"
87+
create_table(table) do
88+
DateTime :at, :default=>Sequel::CURRENT_TIMESTAMP, :null=>false
89+
String :user
90+
String :schema
91+
String :table
92+
String :action
93+
jsonb :prior
94+
end
95+
create_function(function_name, (<<-SQL), {:language=>:plpgsql, :returns=>:trigger, :replace=>true}.merge(opts[:function_opts]||{}))
96+
BEGIN
97+
INSERT INTO #{quote_identifier(table)} (at, "user", "schema", "table", action, prior) VALUES
98+
(CURRENT_TIMESTAMP, CURRENT_USER, TG_TABLE_SCHEMA, TG_TABLE_NAME, TG_OP, to_jsonb(OLD));
99+
IF (TG_OP = 'DELETE') THEN
100+
RETURN OLD;
101+
END IF;
102+
RETURN NEW;
103+
END;
104+
SQL
105+
function_name
106+
end
107+
108+
def pgt_json_audit_log(table, function, opts={})
109+
create_trigger(table, (opts[:trigger_name] || "pgt_jal_#{table}"), function, :events=>[:update, :delete], :each_row=>true, :after=>true)
110+
end
111+
85112
def pgt_sum_cache(main_table, main_table_id_column, sum_column, summed_table, summed_table_id_column, summed_column, opts={})
86113
trigger_name = opts[:trigger_name] || "pgt_sc_#{main_table}__#{main_table_id_column}__#{sum_column}__#{summed_table_id_column}"
87114
function_name = opts[:function_name] || "pgt_sc_#{main_table}__#{main_table_id_column}__#{sum_column}__#{summed_table}__#{summed_table_id_column}__#{summed_column}"

spec/sequel_postgresql_triggers_spec.rb

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -607,3 +607,38 @@
607607
end
608608
end
609609

610+
611+
describe "PostgreSQL JSON Audit Logging" do
612+
before do
613+
DB.extension :pg_json
614+
DB.create_table(:accounts){integer :id; integer :a}
615+
DB.pgt_json_audit_log_setup(:table_audit_logs, :function_name=>:spgt_audit_log)
616+
DB.pgt_json_audit_log(:accounts, :spgt_audit_log)
617+
@ds = DB[:accounts]
618+
@ds.insert(:id=>1)
619+
@logs = DB[:table_audit_logs].reverse(:at)
620+
end
621+
622+
after do
623+
DB.drop_table(:accounts, :table_audit_logs)
624+
DB.drop_function(:spgt_audit_log)
625+
end
626+
627+
it "should previous values in JSON format for inserts and updates" do
628+
@logs.first.must_be_nil
629+
630+
@ds.update(:id=>2, :a=>3)
631+
@ds.all.must_equal [{:id=>2, :a=>3}]
632+
h = @logs.first
633+
h.delete(:at).to_i.must_be_within_delta(10, DB.get(Sequel::CURRENT_TIMESTAMP).to_i)
634+
h.delete(:user).must_be_kind_of(String)
635+
h.must_equal(:schema=>"public", :table=>"accounts", :action=>"UPDATE", :prior=>{"a"=>nil, "id"=>1})
636+
637+
@ds.delete
638+
@ds.all.must_equal []
639+
h = @logs.first
640+
h.delete(:at).to_i.must_be_within_delta(10, DB.get(Sequel::CURRENT_TIMESTAMP).to_i)
641+
h.delete(:user).must_be_kind_of(String)
642+
h.must_equal(:schema=>"public", :table=>"accounts", :action=>"DELETE", :prior=>{"a"=>3, "id"=>2})
643+
end
644+
end if DB.server_version >= 90400

0 commit comments

Comments
 (0)