sequel_postgresql_triggers 1.4.0 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|