sequel_postgresql_triggers 1.4.0 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3b5d81f0071528eb70130d8ebe3093a365c0d024e250583d8ea7e35d5b7af263
4
- data.tar.gz: 18bb2a79d2642636f34fce1f0f4be315e90592c3be8c8a8d5f587d753061d360
3
+ metadata.gz: 2a985c677d8d3b87630378feee9b80dda40ef2ba95147ec525f2df8feff18b38
4
+ data.tar.gz: 8c6b9526bbedb13a72fd3edc44c2da56041fc985c0e41187613914bae9163230
5
5
  SHA512:
6
- metadata.gz: 615949150a244df98c696da8472ebeb40ae5c09812457f5ecca61197a10754e489c431a68d2a44df91eb5f17e229e6e39b4d3b4f4cf6ca28be520b2145345e3c
7
- data.tar.gz: ca67361ebd569731121ad3f482e47d0320d0658144fd4d7bd21734fcf3542ae84d3fb592483813980fbb4f7e8b52866575c4f0c7efb8566d34760d884f66207c
6
+ metadata.gz: 39bd5f9c742aeed9331e59b521d571e5631c1b3dcd0804dd959e010a2a6c2bcd110ad8752eac92b1f9d0a768d0a9925f67cf59cdc5a34b718bc4e15e07ba996e
7
+ data.tar.gz: e5304e315766b8059adc6d29e48b6eb60af4ead1662a70d0eb5d7a87daa541a3b5068385a0b3a302bddaf23fd35f88637c6c3cf762b8308d5f2b5c27f9db682e
data/MIT-LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2008-2017 Jeremy Evans
1
+ Copyright (c) 2008-2018 Jeremy Evans
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining a copy
4
4
  of this software and associated documentation files (the "Software"), to
data/README.rdoc CHANGED
@@ -95,7 +95,7 @@ opts :: options hash
95
95
 
96
96
  === Counter Cache - pgt_counter_cache
97
97
 
98
- This takes quite a few arguments (see the RDoc) and sets up a
98
+ This takes many arguments and sets up a
99
99
  counter cache so that when the counted table is inserted to
100
100
  or deleted from, records in the main table are updated with the
101
101
  count of the corresponding records in the counted table. The counter
@@ -126,7 +126,7 @@ sum cache that sums the length of a string column.
126
126
 
127
127
  Arguments:
128
128
  main_table :: name of table holding counter cache column
129
- main_table_id_column :: column in main table matching counted_table_id_column in counted_table
129
+ main_table_id_column :: column in main table matching summed_table_id_column in summed_table
130
130
  sum_column :: column in main table containing the sum cache
131
131
  summed_table :: name of table being summed
132
132
  summed_table_id_column :: column in summed_table matching main_table_id_column in main_table
@@ -167,7 +167,7 @@ table :: name of table
167
167
 
168
168
  === Touch Propagation - pgt_touch
169
169
 
170
- This takes several arguments (again, see the RDoc) and sets up a
170
+ This takes several arguments and sets up a
171
171
  trigger that watches one table for changes, and touches timestamps
172
172
  of related rows in a separate table.
173
173
 
@@ -198,6 +198,74 @@ Options:
198
198
  :referenced_function_name :: function name for trigger function on referenced table
199
199
  :referenced_trigger_name :: trigger name for referenced table
200
200
 
201
+ === Force Defaults - pgt_force_defaults
202
+
203
+ This takes 2 arguments, a table and a hash of column default values, and sets
204
+ up an insert trigger that will override user submitted or database
205
+ default values and use the values given when setting up the trigger.
206
+ This is mostly useful in situations where multiple database accounts
207
+ are used where one account has insert permissions but not update
208
+ permissions, and you want to ensure that inserted rows have specific
209
+ column values to enforce security requirements.
210
+
211
+ Arguments:
212
+ table :: The name of the table
213
+ defaults :: A hash of default values to enforce, where keys are column names
214
+ and values are the default values to enforce
215
+
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
+ The audit log table will store the following columns:
236
+
237
+ txid :: The 64-bit transaction ID for the transaction that made the modification (txid_current())
238
+ at :: The timestamp of the transaction that made the modification (CURRENT_TIMESTAMP)
239
+ user :: The database user name that made the modification (CURRENT_USER)
240
+ schema :: The schema containing the table that was modified (TG_TABLE_SCHEMA)
241
+ table :: The table that was modified (TG_TABLE_NAME)
242
+ action :: The type of modification, either DELETE or UPDATE (TG_OP)
243
+ prior :: A jsonb column with the contents of the row before the modification (to_jsonb(OLD))
244
+
245
+ ==== pgt_json_audit_log
246
+
247
+ This adds a trigger to the table that will log previous values to the
248
+ audting table for updates and deletes.
249
+
250
+ Arguments:
251
+ table :: The name of the table to audit
252
+ function :: The name of the trigger function to call to log changes
253
+
254
+ Note that it is probably a bad idea to use the same table argument
255
+ to both +pgt_json_audit_log_setup+ and +pgt_json_audit_log+.
256
+
257
+ == Caveats
258
+
259
+ If you have defined counter or sum cache triggers using this library
260
+ before version 1.6.0, you should drop them and regenerate them if
261
+ you want the triggers to work correctly with queries that use
262
+ <tt>INSERT ... ON CONFLICT DO NOTHING</tt>.
263
+
264
+ When restoring a data-only migration with +pg_dump+, you may need to
265
+ use <tt>--disable-triggers</tt> for it to restore correctly, and you
266
+ will need to manually enforce data integrity if you are doing
267
+ partial restores and not full restores.
268
+
201
269
  == License
202
270
 
203
271
  This library is released under the MIT License. See the MIT-LICENSE
@@ -6,15 +6,15 @@ module Sequel
6
6
  module Postgres
7
7
  PGT_DEFINE = proc do
8
8
  def pgt_counter_cache(main_table, main_table_id_column, counter_column, counted_table, counted_table_id_column, opts={})
9
- trigger_name = opts[:trigger_name] || "pgt_cc_#{main_table}__#{main_table_id_column}__#{counter_column}__#{counted_table_id_column}"
10
- function_name = opts[:function_name] || "pgt_cc_#{main_table}__#{main_table_id_column}__#{counter_column}__#{counted_table}__#{counted_table_id_column}"
9
+ trigger_name = opts[:trigger_name] || "pgt_cc_#{pgt_mangled_table_name(main_table)}__#{main_table_id_column}__#{counter_column}__#{counted_table_id_column}"
10
+ function_name = opts[:function_name] || "pgt_cc_#{pgt_mangled_table_name(main_table)}__#{main_table_id_column}__#{counter_column}__#{pgt_mangled_table_name(counted_table)}__#{counted_table_id_column}"
11
11
 
12
12
  table = quote_schema_table(main_table)
13
13
  id_column = quote_identifier(counted_table_id_column)
14
14
  main_column = quote_identifier(main_table_id_column)
15
15
  count_column = quote_identifier(counter_column)
16
16
 
17
- pgt_trigger(counted_table, trigger_name, function_name, [:insert, :update, :delete], <<-SQL)
17
+ pgt_trigger(counted_table, trigger_name, function_name, [:insert, :update, :delete], <<-SQL, :after=>true)
18
18
  BEGIN
19
19
  IF (TG_OP = 'UPDATE' AND (NEW.#{id_column} = OLD.#{id_column} OR (OLD.#{id_column} IS NULL AND NEW.#{id_column} IS NULL))) THEN
20
20
  RETURN NEW;
@@ -37,7 +37,7 @@ module Sequel
37
37
 
38
38
  def pgt_created_at(table, column, opts={})
39
39
  trigger_name = opts[:trigger_name] || "pgt_ca_#{column}"
40
- function_name = opts[:function_name] || "pgt_ca_#{table}__#{column}"
40
+ function_name = opts[:function_name] || "pgt_ca_#{pgt_mangled_table_name(table)}__#{column}"
41
41
  col = quote_identifier(column)
42
42
  pgt_trigger(table, trigger_name, function_name, [:insert, :update], <<-SQL)
43
43
  BEGIN
@@ -51,6 +51,21 @@ module Sequel
51
51
  SQL
52
52
  end
53
53
 
54
+ def pgt_force_defaults(table, defaults, opts={})
55
+ cols = defaults.keys.sort.join('_')
56
+ trigger_name = opts[:trigger_name] || "pgt_fd_#{cols}"
57
+ function_name = opts[:function_name] || "pgt_fd_#{pgt_mangled_table_name(table)}__#{cols}"
58
+ lines = defaults.map do |column, v|
59
+ "NEW.#{quote_identifier(column)} = #{literal(v)};"
60
+ end
61
+ pgt_trigger(table, trigger_name, function_name, [:insert], <<-SQL)
62
+ BEGIN
63
+ #{lines.join("\n")}
64
+ RETURN NEW;
65
+ END;
66
+ SQL
67
+ end
68
+
54
69
  def pgt_immutable(table, *columns)
55
70
  opts = columns.last.is_a?(Hash) ? columns.pop : {}
56
71
  trigger_name = opts[:trigger_name] || "pgt_im_#{columns.join('__')}"
@@ -67,9 +82,37 @@ module Sequel
67
82
  pgt_trigger(table, trigger_name, function_name, :update, "BEGIN #{ifs} RETURN NEW; END;")
68
83
  end
69
84
 
85
+ def pgt_json_audit_log_setup(table, opts={})
86
+ function_name = opts[:function_name] || "pgt_jal_#{pgt_mangled_table_name(table)}"
87
+ create_table(table) do
88
+ Bignum :txid, :null=>false, :index=>true
89
+ DateTime :at, :default=>Sequel::CURRENT_TIMESTAMP, :null=>false
90
+ String :user, :null=>false
91
+ String :schema, :null=>false
92
+ String :table, :null=>false
93
+ String :action, :null=>false
94
+ jsonb :prior, :null=>false
95
+ end
96
+ create_function(function_name, (<<-SQL), {:language=>:plpgsql, :returns=>:trigger, :replace=>true}.merge(opts[:function_opts]||{}))
97
+ BEGIN
98
+ INSERT INTO #{quote_schema_table(table)} (txid, at, "user", "schema", "table", action, prior) VALUES
99
+ (txid_current(), CURRENT_TIMESTAMP, CURRENT_USER, TG_TABLE_SCHEMA, TG_TABLE_NAME, TG_OP, to_jsonb(OLD));
100
+ IF (TG_OP = 'DELETE') THEN
101
+ RETURN OLD;
102
+ END IF;
103
+ RETURN NEW;
104
+ END;
105
+ SQL
106
+ function_name
107
+ end
108
+
109
+ def pgt_json_audit_log(table, function, opts={})
110
+ create_trigger(table, (opts[:trigger_name] || "pgt_jal_#{pgt_mangled_table_name(table)}"), function, :events=>[:update, :delete], :each_row=>true, :after=>true)
111
+ end
112
+
70
113
  def pgt_sum_cache(main_table, main_table_id_column, sum_column, summed_table, summed_table_id_column, summed_column, opts={})
71
- trigger_name = opts[:trigger_name] || "pgt_sc_#{main_table}__#{main_table_id_column}__#{sum_column}__#{summed_table_id_column}"
72
- function_name = opts[:function_name] || "pgt_sc_#{main_table}__#{main_table_id_column}__#{sum_column}__#{summed_table}__#{summed_table_id_column}__#{summed_column}"
114
+ trigger_name = opts[:trigger_name] || "pgt_sc_#{pgt_mangled_table_name(main_table)}__#{main_table_id_column}__#{sum_column}__#{summed_table_id_column}"
115
+ function_name = opts[:function_name] || "pgt_sc_#{pgt_mangled_table_name(main_table)}__#{main_table_id_column}__#{sum_column}__#{pgt_mangled_table_name(summed_table)}__#{summed_table_id_column}__#{summed_column}"
73
116
 
74
117
  table = quote_schema_table(main_table)
75
118
  id_column = quote_identifier(summed_table_id_column)
@@ -79,7 +122,7 @@ module Sequel
79
122
  main_column = quote_identifier(main_table_id_column)
80
123
  sum_column = quote_identifier(sum_column)
81
124
 
82
- pgt_trigger(summed_table, trigger_name, function_name, [:insert, :delete, :update], <<-SQL)
125
+ pgt_trigger(summed_table, trigger_name, function_name, [:insert, :delete, :update], <<-SQL, :after=>true)
83
126
  BEGIN
84
127
  IF (TG_OP = 'UPDATE' AND NEW.#{id_column} = OLD.#{id_column}) THEN
85
128
  UPDATE #{table} SET #{sum_column} = #{sum_column} + #{new_table_summed_column} - #{old_table_summed_column} WHERE #{main_column} = NEW.#{id_column};
@@ -111,10 +154,10 @@ module Sequel
111
154
  summed_table_fk_column = opts.fetch(:summed_table_fk_column)
112
155
 
113
156
  summed_column_slug = summed_column.is_a?(String) || summed_column.is_a?(Symbol) ? "__#{summed_column}" : ""
114
- trigger_name = opts[:trigger_name] || "pgt_stmc_#{main_table}__#{main_table_id_column}__#{sum_column}__#{summed_table_id_column}#{summed_column_slug}__#{main_table_fk_column}__#{summed_table_fk_column}"
115
- function_name = opts[:function_name] || "pgt_stmc_#{main_table}__#{main_table_id_column}__#{sum_column}__#{summed_table}__#{summed_table_id_column}#{summed_column_slug}__#{join_table}__#{main_table_fk_column}__#{summed_table_fk_column}"
116
- join_trigger_name = opts[:join_trigger_name] || "pgt_stmc_join_#{main_table}__#{main_table_id_column}__#{sum_column}__#{summed_table_id_column}#{summed_column_slug}__#{main_table_fk_column}__#{summed_table_fk_column}"
117
- join_function_name = opts[:join_function_name] || "pgt_stmc_join_#{main_table}__#{main_table_id_column}__#{sum_column}__#{summed_table}__#{summed_table_id_column}#{summed_column_slug}__#{join_table}__#{main_table_fk_column}__#{summed_table_fk_column}"
157
+ trigger_name = opts[:trigger_name] || "pgt_stmc_#{pgt_mangled_table_name(main_table)}__#{main_table_id_column}__#{sum_column}__#{summed_table_id_column}#{summed_column_slug}__#{main_table_fk_column}__#{summed_table_fk_column}"
158
+ function_name = opts[:function_name] || "pgt_stmc_#{pgt_mangled_table_name(main_table)}__#{main_table_id_column}__#{sum_column}__#{pgt_mangled_table_name(summed_table)}__#{summed_table_id_column}#{summed_column_slug}__#{pgt_mangled_table_name(join_table)}__#{main_table_fk_column}__#{summed_table_fk_column}"
159
+ join_trigger_name = opts[:join_trigger_name] || "pgt_stmc_join_#{pgt_mangled_table_name(main_table)}__#{main_table_id_column}__#{sum_column}__#{summed_table_id_column}#{summed_column_slug}__#{main_table_fk_column}__#{summed_table_fk_column}"
160
+ join_function_name = opts[:join_function_name] || "pgt_stmc_join_#{pgt_mangled_table_name(main_table)}__#{main_table_id_column}__#{sum_column}__#{pgt_mangled_table_name(summed_table)}__#{summed_table_id_column}#{summed_column_slug}__#{pgt_mangled_table_name(join_table)}__#{main_table_fk_column}__#{summed_table_fk_column}"
118
161
 
119
162
  orig_summed_table = summed_table
120
163
  orig_join_table = join_table
@@ -133,7 +176,7 @@ module Sequel
133
176
  main_table_fk_column = quote_schema_table(main_table_fk_column)
134
177
  summed_table_fk_column = quote_schema_table(summed_table_fk_column)
135
178
 
136
- pgt_trigger(orig_summed_table, trigger_name, function_name, [:insert, :delete, :update], <<-SQL)
179
+ pgt_trigger(orig_summed_table, trigger_name, function_name, [:insert, :delete, :update], <<-SQL, :after=>true)
137
180
  BEGIN
138
181
  IF (TG_OP = 'UPDATE' AND NEW.#{summed_table_id_column} = OLD.#{summed_table_id_column}) THEN
139
182
  UPDATE #{main_table} SET #{sum_column} = #{sum_column} + #{new_table_summed_column} - #{old_table_summed_column} WHERE #{main_table_id_column} IN (SELECT #{main_table_fk_column} FROM #{join_table} WHERE #{summed_table_fk_column} = NEW.#{summed_table_id_column});
@@ -152,7 +195,7 @@ module Sequel
152
195
  END;
153
196
  SQL
154
197
 
155
- pgt_trigger(orig_join_table, join_trigger_name, join_function_name, [:insert, :delete, :update], <<-SQL)
198
+ pgt_trigger(orig_join_table, join_trigger_name, join_function_name, [:insert, :delete, :update], <<-SQL, :after=>true)
156
199
  BEGIN
157
200
  IF (NOT (TG_OP = 'UPDATE' AND NEW.#{main_table_fk_column} = OLD.#{main_table_fk_column} AND NEW.#{summed_table_fk_column} = OLD.#{summed_table_fk_column})) THEN
158
201
  IF (TG_OP = 'INSERT' OR TG_OP = 'UPDATE') THEN
@@ -171,8 +214,8 @@ module Sequel
171
214
  end
172
215
 
173
216
  def pgt_touch(main_table, touch_table, column, expr, opts={})
174
- trigger_name = opts[:trigger_name] || "pgt_t_#{main_table}__#{touch_table}"
175
- function_name = opts[:function_name] || "pgt_t_#{main_table}__#{touch_table}"
217
+ trigger_name = opts[:trigger_name] || "pgt_t_#{pgt_mangled_table_name(main_table)}__#{pgt_mangled_table_name(touch_table)}"
218
+ function_name = opts[:function_name] || "pgt_t_#{pgt_mangled_table_name(main_table)}__#{pgt_mangled_table_name(touch_table)}"
176
219
  cond = lambda{|source| expr.map{|k,v| "#{quote_identifier(k)} = #{source}.#{quote_identifier(v)}"}.join(" AND ")}
177
220
  same_id = expr.map{|k,v| "NEW.#{quote_identifier(v)} = OLD.#{quote_identifier(v)}"}.join(" AND ")
178
221
 
@@ -204,7 +247,7 @@ module Sequel
204
247
 
205
248
  def pgt_updated_at(table, column, opts={})
206
249
  trigger_name = opts[:trigger_name] || "pgt_ua_#{column}"
207
- function_name = opts[:function_name] || "pgt_ua_#{table}__#{column}"
250
+ function_name = opts[:function_name] || "pgt_ua_#{pgt_mangled_table_name(table)}__#{column}"
208
251
  pgt_trigger(table, trigger_name, function_name, [:insert, :update], <<-SQL)
209
252
  BEGIN
210
253
  NEW.#{quote_identifier(column)} := CURRENT_TIMESTAMP;
@@ -216,9 +259,9 @@ module Sequel
216
259
  def pgt_foreign_key_array(opts={})
217
260
  table, column, rtable, rcolumn = opts.values_at(:table, :column, :referenced_table, :referenced_column)
218
261
  trigger_name = opts[:trigger_name] || "pgt_fka_#{column}"
219
- function_name = opts[:function_name] || "pgt_fka_#{table}__#{column}"
262
+ function_name = opts[:function_name] || "pgt_fka_#{pgt_mangled_table_name(table)}__#{column}"
220
263
  rtrigger_name = opts[:referenced_trigger_name] || "pgt_rfka_#{column}"
221
- rfunction_name = opts[:referenced_function_name] || "pgt_rfka_#{table}__#{column}"
264
+ rfunction_name = opts[:referenced_function_name] || "pgt_rfka_#{pgt_mangled_table_name(table)}__#{column}"
222
265
  col = quote_identifier(column)
223
266
  tab = quote_identifier(table)
224
267
  rcol = quote_identifier(rcolumn)
@@ -281,6 +324,11 @@ module Sequel
281
324
  create_function(function_name, definition, :language=>:plpgsql, :returns=>:trigger, :replace=>true)
282
325
  create_trigger(table, trigger_name, function_name, :events=>events, :each_row=>true, :after=>opts[:after])
283
326
  end
327
+
328
+ # Mangle the schema name so it can be used in an unquoted_identifier
329
+ def pgt_mangled_table_name(table)
330
+ quote_schema_table(table).gsub('"', '').gsub(/[^A-Za-z0-9]/, '_').gsub(/_+/, '_')
331
+ end
284
332
  end
285
333
 
286
334
  module PGTMethods