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 +4 -4
- data/MIT-LICENSE +1 -1
- data/README.rdoc +71 -3
- data/lib/sequel/extensions/pg_triggers.rb +66 -18
- data/spec/sequel_postgresql_triggers_spec.rb +666 -544
- metadata +36 -9
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2a985c677d8d3b87630378feee9b80dda40ef2ba95147ec525f2df8feff18b38
|
4
|
+
data.tar.gz: 8c6b9526bbedb13a72fd3edc44c2da56041fc985c0e41187613914bae9163230
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 39bd5f9c742aeed9331e59b521d571e5631c1b3dcd0804dd959e010a2a6c2bcd110ad8752eac92b1f9d0a768d0a9925f67cf59cdc5a34b718bc4e15e07ba996e
|
7
|
+
data.tar.gz: e5304e315766b8059adc6d29e48b6eb60af4ead1662a70d0eb5d7a87daa541a3b5068385a0b3a302bddaf23fd35f88637c6c3cf762b8308d5f2b5c27f9db682e
|
data/MIT-LICENSE
CHANGED
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
|
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
|
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
|
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
|