sequel_postgresql_triggers 1.4.0 → 1.5.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 +58 -2
- data/lib/sequel/extensions/pg_triggers.rb +62 -14
- data/spec/sequel_postgresql_triggers_spec.rb +598 -542
- metadata +4 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d0c46791ed8af6edc2e71f5e7964625fe66811eb76d7a99c085b5157336f2d43
|
4
|
+
data.tar.gz: 0d3cae20446b2f4d334746077959d8415cb815c9afcb4212a57c35ff2bc8ab88
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 19e85c00deff747bd3d89bad0d3dfd5d64105cd9191d4a336e004f5a862b097734fa5fe81793ac5bb8f32c8b849f5d28e1c9ef0975f038679f66357689702408
|
7
|
+
data.tar.gz: 1a59b39455c23b6e41af7825de4e6a9c53ba0c394ddb48296e4636f31b5a34bb86f13524562edb104050791d47fa4ac39a7ec665234dcb110ced6bf5c862798f
|
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
|
@@ -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,62 @@ 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
|
+
|
201
257
|
== License
|
202
258
|
|
203
259
|
This library is released under the MIT License. See the MIT-LICENSE
|
@@ -6,8 +6,8 @@ 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)
|
@@ -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)
|
@@ -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
|
@@ -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
|
@@ -1,7 +1,9 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
require 'rubygems'
|
3
3
|
require 'sequel'
|
4
|
-
|
4
|
+
|
5
|
+
ENV['MT_NO_PLUGINS'] = '1' # Work around stupid autoloading of plugins
|
6
|
+
gem 'minitest'
|
5
7
|
require 'minitest/autorun'
|
6
8
|
|
7
9
|
DB = Sequel.connect(ENV['PGT_SPEC_DB']||'postgres:///spgt_test?user=postgres')
|
@@ -16,578 +18,632 @@ else
|
|
16
18
|
end
|
17
19
|
DB.extension :pg_array
|
18
20
|
|
19
|
-
describe "PostgreSQL
|
21
|
+
describe "PostgreSQL Counter Cache Trigger" do
|
22
|
+
before do
|
23
|
+
DB.create_table(:accounts){integer :id; integer :num_entries, :default=>0}
|
24
|
+
DB.create_table(:entries){integer :id; integer :account_id}
|
25
|
+
DB.pgt_counter_cache(:accounts, :id, :num_entries, :entries, :account_id, :function_name=>:spgt_counter_cache)
|
26
|
+
DB[:accounts].insert(:id=>1)
|
27
|
+
DB[:accounts].insert(:id=>2)
|
28
|
+
end
|
29
|
+
|
30
|
+
after do
|
31
|
+
DB.drop_table(:entries, :accounts)
|
32
|
+
DB.drop_function(:spgt_counter_cache)
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should modify counter cache when adding or removing records" do
|
36
|
+
DB[:accounts].order(:id).select_map(:num_entries).must_equal [0, 0]
|
37
|
+
|
38
|
+
DB[:entries].insert(:id=>1, :account_id=>1)
|
39
|
+
DB[:accounts].order(:id).select_map(:num_entries).must_equal [1, 0]
|
40
|
+
|
41
|
+
DB[:entries].insert(:id=>2, :account_id=>1)
|
42
|
+
DB[:accounts].order(:id).select_map(:num_entries).must_equal [2, 0]
|
43
|
+
|
44
|
+
DB[:entries].insert(:id=>3, :account_id=>nil)
|
45
|
+
DB[:accounts].order(:id).select_map(:num_entries).must_equal [2, 0]
|
46
|
+
|
47
|
+
DB[:entries].where(:id=>3).update(:account_id=>2)
|
48
|
+
DB[:accounts].order(:id).select_map(:num_entries).must_equal [2, 1]
|
49
|
+
|
50
|
+
DB[:entries].where(:id=>2).update(:account_id=>2)
|
51
|
+
DB[:accounts].order(:id).select_map(:num_entries).must_equal [1, 2]
|
52
|
+
|
53
|
+
DB[:entries].where(:id=>2).update(:account_id=>nil)
|
54
|
+
DB[:accounts].order(:id).select_map(:num_entries).must_equal [1, 1]
|
55
|
+
|
56
|
+
DB[:entries].where(:id=>2).update(:id=>4)
|
57
|
+
DB[:accounts].order(:id).select_map(:num_entries).must_equal [1, 1]
|
58
|
+
|
59
|
+
DB[:entries].where(:id=>4).update(:account_id=>2)
|
60
|
+
DB[:accounts].order(:id).select_map(:num_entries).must_equal [1, 2]
|
61
|
+
|
62
|
+
DB[:entries].where(:id=>4).update(:account_id=>nil)
|
63
|
+
DB[:accounts].order(:id).select_map(:num_entries).must_equal [1, 1]
|
64
|
+
|
65
|
+
DB[:entries].filter(:id=>4).delete
|
66
|
+
DB[:accounts].order(:id).select_map(:num_entries).must_equal [1, 1]
|
67
|
+
|
68
|
+
DB[:entries].delete
|
69
|
+
DB[:accounts].order(:id).select_map(:num_entries).must_equal [0, 0]
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
describe "PostgreSQL Created At Trigger" do
|
74
|
+
before do
|
75
|
+
DB.create_table(:accounts){integer :id; timestamp :added_on}
|
76
|
+
DB.pgt_created_at(:accounts, :added_on, :function_name=>:spgt_created_at)
|
77
|
+
end
|
78
|
+
|
79
|
+
after do
|
80
|
+
DB.drop_table(:accounts)
|
81
|
+
DB.drop_function(:spgt_created_at)
|
82
|
+
end
|
83
|
+
|
84
|
+
it "should set the column upon insertion and ignore modifications afterward" do
|
85
|
+
DB[:accounts].insert(:id=>1)
|
86
|
+
t = DB[:accounts].get(:added_on)
|
87
|
+
t.strftime('%F').must_equal Date.today.strftime('%F')
|
88
|
+
DB[:accounts].update(:added_on=>Date.today - 60)
|
89
|
+
DB[:accounts].get(:added_on).must_equal t
|
90
|
+
DB[:accounts].insert(:id=>2)
|
91
|
+
ds = DB[:accounts].select(:added_on)
|
92
|
+
DB[:accounts].select((Sequel::SQL::NumericExpression.new(:NOOP, ds.filter(:id=>2)) > ds.filter(:id=>1)).as(:x)).first[:x].must_equal true
|
93
|
+
DB[:accounts].filter(:id=>1).update(:id=>3)
|
94
|
+
DB[:accounts].select((Sequel::SQL::NumericExpression.new(:NOOP, ds.filter(:id=>2)) > ds.filter(:id=>3)).as(:x)).first[:x].must_equal true
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
describe "PostgreSQL Immutable Trigger" do
|
99
|
+
before do
|
100
|
+
DB.create_table(:accounts){integer :id; integer :balance, :default=>0}
|
101
|
+
DB.pgt_immutable(:accounts, :balance, :function_name=>:spgt_immutable)
|
102
|
+
DB[:accounts].insert(:id=>1)
|
103
|
+
end
|
104
|
+
|
105
|
+
after do
|
106
|
+
DB.drop_table(:accounts)
|
107
|
+
DB.drop_function(:spgt_immutable)
|
108
|
+
end
|
109
|
+
|
110
|
+
it "should allow modifying columns not marked as immutable" do
|
111
|
+
DB[:accounts].update(:id=>2)
|
112
|
+
end
|
113
|
+
|
114
|
+
it "should allow updating a column to its existing value" do
|
115
|
+
DB[:accounts].update(:balance=>0)
|
116
|
+
DB[:accounts].update(:balance=>Sequel.*(:balance, :balance))
|
117
|
+
end
|
118
|
+
|
119
|
+
it "should not allow modifying a column's value" do
|
120
|
+
proc{DB[:accounts].update(:balance=>1)}.must_raise(Sequel::DatabaseError)
|
121
|
+
end
|
122
|
+
|
123
|
+
it "should handle NULL values correctly" do
|
124
|
+
proc{DB[:accounts].update(:balance=>nil)}.must_raise(Sequel::DatabaseError)
|
125
|
+
DB[:accounts].delete
|
126
|
+
DB[:accounts].insert(:id=>1, :balance=>nil)
|
127
|
+
DB[:accounts].update(:balance=>nil)
|
128
|
+
proc{DB[:accounts].update(:balance=>0)}.must_raise(Sequel::DatabaseError)
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
describe "PostgreSQL Sum Cache Trigger" do
|
133
|
+
before do
|
134
|
+
DB.create_table(:accounts){integer :id; integer :balance, :default=>0}
|
135
|
+
DB.create_table(:entries){integer :id; integer :account_id; integer :amount}
|
136
|
+
DB.pgt_sum_cache(:accounts, :id, :balance, :entries, :account_id, :amount, :function_name=>:spgt_sum_cache)
|
137
|
+
DB[:accounts].insert(:id=>1)
|
138
|
+
DB[:accounts].insert(:id=>2)
|
139
|
+
end
|
140
|
+
|
141
|
+
after do
|
142
|
+
DB.drop_table(:entries, :accounts)
|
143
|
+
DB.drop_function(:spgt_sum_cache)
|
144
|
+
end
|
145
|
+
|
146
|
+
it "should modify sum cache when adding, updating, or removing records" do
|
147
|
+
DB[:accounts].order(:id).select_map(:balance).must_equal [0, 0]
|
148
|
+
|
149
|
+
DB[:entries].insert(:id=>1, :account_id=>1, :amount=>100)
|
150
|
+
DB[:accounts].order(:id).select_map(:balance).must_equal [100, 0]
|
151
|
+
|
152
|
+
DB[:entries].insert(:id=>2, :account_id=>1, :amount=>200)
|
153
|
+
DB[:accounts].order(:id).select_map(:balance).must_equal [300, 0]
|
154
|
+
|
155
|
+
DB[:entries].insert(:id=>3, :account_id=>nil, :amount=>500)
|
156
|
+
DB[:accounts].order(:id).select_map(:balance).must_equal [300, 0]
|
157
|
+
|
158
|
+
DB[:entries].where(:id=>3).update(:account_id=>2)
|
159
|
+
DB[:accounts].order(:id).select_map(:balance).must_equal [300, 500]
|
160
|
+
|
161
|
+
DB[:entries].exclude(:id=>2).update(:amount=>Sequel.*(:amount, 2))
|
162
|
+
DB[:accounts].order(:id).select_map(:balance).must_equal [400, 1000]
|
163
|
+
|
164
|
+
DB[:entries].where(:id=>2).update(:account_id=>2)
|
165
|
+
DB[:accounts].order(:id).select_map(:balance).must_equal [200, 1200]
|
166
|
+
|
167
|
+
DB[:entries].where(:id=>2).update(:account_id=>nil)
|
168
|
+
DB[:accounts].order(:id).select_map(:balance).must_equal [200, 1000]
|
169
|
+
|
170
|
+
DB[:entries].where(:id=>2).update(:id=>4)
|
171
|
+
DB[:accounts].order(:id).select_map(:balance).must_equal [200, 1000]
|
172
|
+
|
173
|
+
DB[:entries].where(:id=>4).update(:account_id=>2)
|
174
|
+
DB[:accounts].order(:id).select_map(:balance).must_equal [200, 1200]
|
175
|
+
|
176
|
+
DB[:entries].where(:id=>4).update(:account_id=>nil)
|
177
|
+
DB[:accounts].order(:id).select_map(:balance).must_equal [200, 1000]
|
178
|
+
|
179
|
+
DB[:entries].filter(:id=>4).delete
|
180
|
+
DB[:accounts].order(:id).select_map(:balance).must_equal [200, 1000]
|
181
|
+
|
182
|
+
DB[:entries].delete
|
183
|
+
DB[:accounts].order(:id).select_map(:balance).must_equal [0, 0]
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
describe "PostgreSQL Sum Cache Trigger with arbitrary expression" do
|
188
|
+
before do
|
189
|
+
DB.create_table(:accounts){integer :id; integer :nonzero_entries_count, :default=>0}
|
190
|
+
DB.create_table(:entries){integer :id; integer :account_id; integer :amount}
|
191
|
+
DB.pgt_sum_cache(:accounts, :id, :nonzero_entries_count, :entries, :account_id, Sequel.case({0=>0}, 1, :amount), :function_name=>:spgt_sum_cache)
|
192
|
+
DB[:accounts].insert(:id=>1)
|
193
|
+
DB[:accounts].insert(:id=>2)
|
194
|
+
end
|
195
|
+
|
196
|
+
after do
|
197
|
+
DB.drop_table(:entries, :accounts)
|
198
|
+
DB.drop_function(:spgt_sum_cache)
|
199
|
+
end
|
200
|
+
|
201
|
+
it "should modify sum cache when adding, updating, or removing records" do
|
202
|
+
DB[:accounts].order(:id).select_map(:nonzero_entries_count).must_equal [0, 0]
|
203
|
+
|
204
|
+
DB[:entries].insert(:id=>1, :account_id=>1, :amount=>100)
|
205
|
+
DB[:accounts].order(:id).select_map(:nonzero_entries_count).must_equal [1, 0]
|
206
|
+
|
207
|
+
DB[:entries].insert(:id=>2, :account_id=>1, :amount=>200)
|
208
|
+
DB[:accounts].order(:id).select_map(:nonzero_entries_count).must_equal [2, 0]
|
209
|
+
|
210
|
+
DB[:entries].insert(:id=>3, :account_id=>nil, :amount=>500)
|
211
|
+
DB[:accounts].order(:id).select_map(:nonzero_entries_count).must_equal [2, 0]
|
212
|
+
|
213
|
+
DB[:entries].where(:id=>3).update(:account_id=>2)
|
214
|
+
DB[:accounts].order(:id).select_map(:nonzero_entries_count).must_equal [2, 1]
|
215
|
+
|
216
|
+
DB[:entries].exclude(:id=>2).update(:amount=>Sequel.*(:amount, 2))
|
217
|
+
DB[:accounts].order(:id).select_map(:nonzero_entries_count).must_equal [2, 1]
|
218
|
+
|
219
|
+
DB[:entries].where(:id=>2).update(:account_id=>2)
|
220
|
+
DB[:accounts].order(:id).select_map(:nonzero_entries_count).must_equal [1, 2]
|
221
|
+
|
222
|
+
DB[:entries].where(:id=>2).update(:account_id=>nil)
|
223
|
+
DB[:accounts].order(:id).select_map(:nonzero_entries_count).must_equal [1, 1]
|
224
|
+
|
225
|
+
DB[:entries].where(:id=>2).update(:id=>4)
|
226
|
+
DB[:accounts].order(:id).select_map(:nonzero_entries_count).must_equal [1, 1]
|
227
|
+
|
228
|
+
DB[:entries].where(:id=>4).update(:account_id=>2)
|
229
|
+
DB[:accounts].order(:id).select_map(:nonzero_entries_count).must_equal [1, 2]
|
230
|
+
|
231
|
+
DB[:entries].where(:id=>4).update(:account_id=>nil)
|
232
|
+
DB[:accounts].order(:id).select_map(:nonzero_entries_count).must_equal [1, 1]
|
233
|
+
|
234
|
+
DB[:entries].filter(:id=>4).delete
|
235
|
+
DB[:accounts].order(:id).select_map(:nonzero_entries_count).must_equal [1, 1]
|
236
|
+
|
237
|
+
DB[:entries].delete
|
238
|
+
DB[:accounts].order(:id).select_map(:nonzero_entries_count).must_equal [0, 0]
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
|
243
|
+
describe "PostgreSQL Sum Through Many Cache Trigger" do
|
244
|
+
before do
|
245
|
+
DB.create_table(:parents){primary_key :id; integer :balance, :default=>0, :null=>false}
|
246
|
+
DB.create_table(:children){primary_key :id; integer :amount, :null=>false}
|
247
|
+
DB.create_table(:links){integer :parent_id, :null=>false; integer :child_id, :null=>false; unique [:parent_id, :child_id]}
|
248
|
+
DB.pgt_sum_through_many_cache(
|
249
|
+
:main_table=>:parents,
|
250
|
+
:sum_column=>:balance,
|
251
|
+
:summed_table=>:children,
|
252
|
+
:summed_column=>:amount,
|
253
|
+
:join_table=>:links,
|
254
|
+
:main_table_fk_column=>:parent_id,
|
255
|
+
:summed_table_fk_column=>:child_id,
|
256
|
+
:function_name=>:spgt_stm_cache,
|
257
|
+
:join_function_name=>:spgt_stm_cache_join
|
258
|
+
)
|
259
|
+
DB[:parents].insert(:id=>1)
|
260
|
+
DB[:parents].insert(:id=>2)
|
261
|
+
end
|
262
|
+
|
263
|
+
after do
|
264
|
+
DB.drop_table(:links, :parents, :children)
|
265
|
+
DB.drop_function(:spgt_stm_cache)
|
266
|
+
DB.drop_function(:spgt_stm_cache_join)
|
267
|
+
end
|
268
|
+
|
269
|
+
it "should modify sum cache when adding, updating, or removing records" do
|
270
|
+
DB[:parents].order(:id).select_map(:balance).must_equal [0, 0]
|
271
|
+
|
272
|
+
DB[:children].insert(:id=>1, :amount=>100)
|
273
|
+
DB[:links].insert(:parent_id=>1, :child_id=>1)
|
274
|
+
DB[:parents].order(:id).select_map(:balance).must_equal [100, 0]
|
275
|
+
|
276
|
+
DB[:children].insert(:id=>2, :amount=>200)
|
277
|
+
DB[:links].insert(:parent_id=>1, :child_id=>2)
|
278
|
+
DB[:parents].order(:id).select_map(:balance).must_equal [300, 0]
|
279
|
+
|
280
|
+
DB[:children].insert(:id=>3, :amount=>500)
|
281
|
+
DB[:parents].order(:id).select_map(:balance).must_equal [300, 0]
|
282
|
+
DB[:links].insert(:parent_id=>2, :child_id=>3)
|
283
|
+
DB[:parents].order(:id).select_map(:balance).must_equal [300, 500]
|
284
|
+
|
285
|
+
DB[:links].where(:parent_id=>2, :child_id=>3).update(:parent_id=>1)
|
286
|
+
DB[:parents].order(:id).select_map(:balance).must_equal [800, 0]
|
287
|
+
|
288
|
+
DB[:children].insert(:id=>4, :amount=>400)
|
289
|
+
DB[:links].where(:parent_id=>1, :child_id=>3).update(:child_id=>4)
|
290
|
+
DB[:parents].order(:id).select_map(:balance).must_equal [700, 0]
|
291
|
+
|
292
|
+
DB[:links].where(:parent_id=>1, :child_id=>4).update(:parent_id=>2, :child_id=>3)
|
293
|
+
DB[:parents].order(:id).select_map(:balance).must_equal [300, 500]
|
294
|
+
|
295
|
+
DB[:children].exclude(:id=>2).update(:amount=>Sequel.*(:amount, 2))
|
296
|
+
DB[:parents].order(:id).select_map(:balance).must_equal [400, 1000]
|
297
|
+
|
298
|
+
DB[:links].where(:parent_id=>1, :child_id=>2).update(:parent_id=>2)
|
299
|
+
DB[:parents].order(:id).select_map(:balance).must_equal [200, 1200]
|
300
|
+
|
301
|
+
DB[:links].where(:parent_id=>2, :child_id=>2).update(:parent_id=>1)
|
302
|
+
DB[:parents].order(:id).select_map(:balance).must_equal [400, 1000]
|
303
|
+
|
304
|
+
DB[:links].where(:parent_id=>1, :child_id=>2).update(:child_id=>3)
|
305
|
+
DB[:parents].order(:id).select_map(:balance).must_equal [1200, 1000]
|
306
|
+
|
307
|
+
DB[:links].insert(:parent_id=>2, :child_id=>4)
|
308
|
+
DB[:parents].order(:id).select_map(:balance).must_equal [1200, 1800]
|
309
|
+
|
310
|
+
DB[:children].filter(:id=>4).delete
|
311
|
+
DB[:parents].order(:id).select_map(:balance).must_equal [1200, 1000]
|
312
|
+
|
313
|
+
DB[:links].filter(:parent_id=>1, :child_id=>1).delete
|
314
|
+
DB[:parents].order(:id).select_map(:balance).must_equal [1000, 1000]
|
315
|
+
|
316
|
+
DB[:children].insert(:id=>4, :amount=>400)
|
317
|
+
DB[:parents].order(:id).select_map(:balance).must_equal [1000, 1400]
|
318
|
+
|
319
|
+
DB[:children].delete
|
320
|
+
DB[:parents].order(:id).select_map(:balance).must_equal [0, 0]
|
321
|
+
|
322
|
+
DB[:children].multi_insert([{:id=>2, :amount=>200}, {:id=>1, :amount=>200}, {:id=>3, :amount=>1000}, {:id=>4, :amount=>400}])
|
323
|
+
DB[:parents].order(:id).select_map(:balance).must_equal [1000, 1400]
|
324
|
+
|
325
|
+
DB[:links].where(:child_id=>3).update(:child_id=>2)
|
326
|
+
DB[:parents].order(:id).select_map(:balance).must_equal [200, 600]
|
327
|
+
|
328
|
+
DB[:children].update(:amount=>10)
|
329
|
+
DB[:parents].order(:id).select_map(:balance).must_equal [10, 20]
|
330
|
+
|
331
|
+
DB[:links].delete
|
332
|
+
DB[:parents].order(:id).select_map(:balance).must_equal [0, 0]
|
333
|
+
end
|
334
|
+
end
|
335
|
+
|
336
|
+
describe "PostgreSQL Sum Through Many Cache Trigger with arbitrary expression" do
|
20
337
|
before do
|
21
|
-
DB.
|
338
|
+
DB.create_table(:parents){primary_key :id; integer :nonzero_entries_count, :default=>0, :null=>false}
|
339
|
+
DB.create_table(:children){primary_key :id; integer :amount, :null=>false}
|
340
|
+
DB.create_table(:links){integer :parent_id, :null=>false; integer :child_id, :null=>false; unique [:parent_id, :child_id]}
|
341
|
+
DB.pgt_sum_through_many_cache(
|
342
|
+
:main_table=>:parents,
|
343
|
+
:sum_column=>:nonzero_entries_count,
|
344
|
+
:summed_table=>:children,
|
345
|
+
:summed_column=>Sequel.case({0=>0}, 1, :amount),
|
346
|
+
:join_table=>:links,
|
347
|
+
:main_table_fk_column=>:parent_id,
|
348
|
+
:summed_table_fk_column=>:child_id,
|
349
|
+
:function_name=>:spgt_stm_cache,
|
350
|
+
:join_function_name=>:spgt_stm_cache_join
|
351
|
+
)
|
352
|
+
DB[:parents].insert(:id=>1)
|
353
|
+
DB[:parents].insert(:id=>2)
|
22
354
|
end
|
355
|
+
|
23
356
|
after do
|
24
|
-
DB.
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
DB.pgt_counter_cache(:accounts, :id, :num_entries, :entries, :account_id, :function_name=>:spgt_counter_cache)
|
32
|
-
DB[:accounts].insert(:id=>1)
|
33
|
-
DB[:accounts].insert(:id=>2)
|
34
|
-
end
|
35
|
-
|
36
|
-
after do
|
37
|
-
DB.drop_table(:entries, :accounts)
|
38
|
-
DB.drop_function(:spgt_counter_cache)
|
39
|
-
end
|
40
|
-
|
41
|
-
it "Should modify counter cache when adding or removing records" do
|
42
|
-
DB[:accounts].order(:id).select_map(:num_entries).must_equal [0, 0]
|
43
|
-
|
44
|
-
DB[:entries].insert(:id=>1, :account_id=>1)
|
45
|
-
DB[:accounts].order(:id).select_map(:num_entries).must_equal [1, 0]
|
46
|
-
|
47
|
-
DB[:entries].insert(:id=>2, :account_id=>1)
|
48
|
-
DB[:accounts].order(:id).select_map(:num_entries).must_equal [2, 0]
|
49
|
-
|
50
|
-
DB[:entries].insert(:id=>3, :account_id=>nil)
|
51
|
-
DB[:accounts].order(:id).select_map(:num_entries).must_equal [2, 0]
|
52
|
-
|
53
|
-
DB[:entries].where(:id=>3).update(:account_id=>2)
|
54
|
-
DB[:accounts].order(:id).select_map(:num_entries).must_equal [2, 1]
|
55
|
-
|
56
|
-
DB[:entries].where(:id=>2).update(:account_id=>2)
|
57
|
-
DB[:accounts].order(:id).select_map(:num_entries).must_equal [1, 2]
|
58
|
-
|
59
|
-
DB[:entries].where(:id=>2).update(:account_id=>nil)
|
60
|
-
DB[:accounts].order(:id).select_map(:num_entries).must_equal [1, 1]
|
61
|
-
|
62
|
-
DB[:entries].where(:id=>2).update(:id=>4)
|
63
|
-
DB[:accounts].order(:id).select_map(:num_entries).must_equal [1, 1]
|
64
|
-
|
65
|
-
DB[:entries].where(:id=>4).update(:account_id=>2)
|
66
|
-
DB[:accounts].order(:id).select_map(:num_entries).must_equal [1, 2]
|
67
|
-
|
68
|
-
DB[:entries].where(:id=>4).update(:account_id=>nil)
|
69
|
-
DB[:accounts].order(:id).select_map(:num_entries).must_equal [1, 1]
|
70
|
-
|
71
|
-
DB[:entries].filter(:id=>4).delete
|
72
|
-
DB[:accounts].order(:id).select_map(:num_entries).must_equal [1, 1]
|
73
|
-
|
74
|
-
DB[:entries].delete
|
75
|
-
DB[:accounts].order(:id).select_map(:num_entries).must_equal [0, 0]
|
76
|
-
end
|
77
|
-
end
|
78
|
-
|
79
|
-
describe "PostgreSQL Created At Trigger" do
|
80
|
-
before do
|
81
|
-
DB.create_table(:accounts){integer :id; timestamp :added_on}
|
82
|
-
DB.pgt_created_at(:accounts, :added_on, :function_name=>:spgt_created_at)
|
83
|
-
end
|
84
|
-
|
85
|
-
after do
|
86
|
-
DB.drop_table(:accounts)
|
87
|
-
DB.drop_function(:spgt_created_at)
|
88
|
-
end
|
89
|
-
|
90
|
-
it "Should set the column upon insertion and ignore modifications afterward" do
|
91
|
-
DB[:accounts].insert(:id=>1)
|
92
|
-
t = DB[:accounts].get(:added_on)
|
93
|
-
t.strftime('%F').must_equal Date.today.strftime('%F')
|
94
|
-
DB[:accounts].update(:added_on=>Date.today - 60)
|
95
|
-
DB[:accounts].get(:added_on).must_equal t
|
96
|
-
DB[:accounts].insert(:id=>2)
|
97
|
-
ds = DB[:accounts].select(:added_on)
|
98
|
-
DB[:accounts].select((Sequel::SQL::NumericExpression.new(:NOOP, ds.filter(:id=>2)) > ds.filter(:id=>1)).as(:x)).first[:x].must_equal true
|
99
|
-
DB[:accounts].filter(:id=>1).update(:id=>3)
|
100
|
-
DB[:accounts].select((Sequel::SQL::NumericExpression.new(:NOOP, ds.filter(:id=>2)) > ds.filter(:id=>3)).as(:x)).first[:x].must_equal true
|
101
|
-
end
|
102
|
-
end
|
103
|
-
|
104
|
-
describe "PostgreSQL Immutable Trigger" do
|
105
|
-
before do
|
106
|
-
DB.create_table(:accounts){integer :id; integer :balance, :default=>0}
|
107
|
-
DB.pgt_immutable(:accounts, :balance, :function_name=>:spgt_immutable)
|
108
|
-
DB[:accounts].insert(:id=>1)
|
109
|
-
end
|
110
|
-
|
111
|
-
after do
|
112
|
-
DB.drop_table(:accounts)
|
113
|
-
DB.drop_function(:spgt_immutable)
|
114
|
-
end
|
115
|
-
|
116
|
-
it "Should allow modifying columns not marked as immutable" do
|
117
|
-
DB[:accounts].update(:id=>2)
|
118
|
-
end
|
119
|
-
|
120
|
-
it "Should allow updating a column to its existing value" do
|
121
|
-
DB[:accounts].update(:balance=>0)
|
122
|
-
DB[:accounts].update(:balance=>Sequel.*(:balance, :balance))
|
123
|
-
end
|
124
|
-
|
125
|
-
it "Should not allow modifying a column's value" do
|
126
|
-
proc{DB[:accounts].update(:balance=>1)}.must_raise(Sequel::DatabaseError)
|
127
|
-
end
|
128
|
-
|
129
|
-
it "Should handle NULL values correctly" do
|
130
|
-
proc{DB[:accounts].update(:balance=>nil)}.must_raise(Sequel::DatabaseError)
|
131
|
-
DB[:accounts].delete
|
132
|
-
DB[:accounts].insert(:id=>1, :balance=>nil)
|
133
|
-
DB[:accounts].update(:balance=>nil)
|
134
|
-
proc{DB[:accounts].update(:balance=>0)}.must_raise(Sequel::DatabaseError)
|
135
|
-
end
|
136
|
-
end
|
137
|
-
|
138
|
-
describe "PostgreSQL Sum Cache Trigger" do
|
139
|
-
before do
|
140
|
-
DB.create_table(:accounts){integer :id; integer :balance, :default=>0}
|
141
|
-
DB.create_table(:entries){integer :id; integer :account_id; integer :amount}
|
142
|
-
DB.pgt_sum_cache(:accounts, :id, :balance, :entries, :account_id, :amount, :function_name=>:spgt_sum_cache)
|
143
|
-
DB[:accounts].insert(:id=>1)
|
144
|
-
DB[:accounts].insert(:id=>2)
|
145
|
-
end
|
146
|
-
|
147
|
-
after do
|
148
|
-
DB.drop_table(:entries, :accounts)
|
149
|
-
DB.drop_function(:spgt_sum_cache)
|
150
|
-
end
|
151
|
-
|
152
|
-
it "Should modify sum cache when adding, updating, or removing records" do
|
153
|
-
DB[:accounts].order(:id).select_map(:balance).must_equal [0, 0]
|
154
|
-
|
155
|
-
DB[:entries].insert(:id=>1, :account_id=>1, :amount=>100)
|
156
|
-
DB[:accounts].order(:id).select_map(:balance).must_equal [100, 0]
|
157
|
-
|
158
|
-
DB[:entries].insert(:id=>2, :account_id=>1, :amount=>200)
|
159
|
-
DB[:accounts].order(:id).select_map(:balance).must_equal [300, 0]
|
160
|
-
|
161
|
-
DB[:entries].insert(:id=>3, :account_id=>nil, :amount=>500)
|
162
|
-
DB[:accounts].order(:id).select_map(:balance).must_equal [300, 0]
|
163
|
-
|
164
|
-
DB[:entries].where(:id=>3).update(:account_id=>2)
|
165
|
-
DB[:accounts].order(:id).select_map(:balance).must_equal [300, 500]
|
166
|
-
|
167
|
-
DB[:entries].exclude(:id=>2).update(:amount=>Sequel.*(:amount, 2))
|
168
|
-
DB[:accounts].order(:id).select_map(:balance).must_equal [400, 1000]
|
169
|
-
|
170
|
-
DB[:entries].where(:id=>2).update(:account_id=>2)
|
171
|
-
DB[:accounts].order(:id).select_map(:balance).must_equal [200, 1200]
|
172
|
-
|
173
|
-
DB[:entries].where(:id=>2).update(:account_id=>nil)
|
174
|
-
DB[:accounts].order(:id).select_map(:balance).must_equal [200, 1000]
|
175
|
-
|
176
|
-
DB[:entries].where(:id=>2).update(:id=>4)
|
177
|
-
DB[:accounts].order(:id).select_map(:balance).must_equal [200, 1000]
|
178
|
-
|
179
|
-
DB[:entries].where(:id=>4).update(:account_id=>2)
|
180
|
-
DB[:accounts].order(:id).select_map(:balance).must_equal [200, 1200]
|
181
|
-
|
182
|
-
DB[:entries].where(:id=>4).update(:account_id=>nil)
|
183
|
-
DB[:accounts].order(:id).select_map(:balance).must_equal [200, 1000]
|
184
|
-
|
185
|
-
DB[:entries].filter(:id=>4).delete
|
186
|
-
DB[:accounts].order(:id).select_map(:balance).must_equal [200, 1000]
|
187
|
-
|
188
|
-
DB[:entries].delete
|
189
|
-
DB[:accounts].order(:id).select_map(:balance).must_equal [0, 0]
|
190
|
-
end
|
191
|
-
end
|
192
|
-
|
193
|
-
describe "PostgreSQL Sum Cache Trigger with arbitrary expression" do
|
194
|
-
before do
|
195
|
-
DB.create_table(:accounts){integer :id; integer :nonzero_entries_count, :default=>0}
|
196
|
-
DB.create_table(:entries){integer :id; integer :account_id; integer :amount}
|
197
|
-
DB.pgt_sum_cache(:accounts, :id, :nonzero_entries_count, :entries, :account_id, Sequel.case({0=>0}, 1, :amount), :function_name=>:spgt_sum_cache)
|
198
|
-
DB[:accounts].insert(:id=>1)
|
199
|
-
DB[:accounts].insert(:id=>2)
|
200
|
-
end
|
201
|
-
|
202
|
-
after do
|
203
|
-
DB.drop_table(:entries, :accounts)
|
204
|
-
DB.drop_function(:spgt_sum_cache)
|
205
|
-
end
|
206
|
-
|
207
|
-
it "Should modify sum cache when adding, updating, or removing records" do
|
208
|
-
DB[:accounts].order(:id).select_map(:nonzero_entries_count).must_equal [0, 0]
|
209
|
-
|
210
|
-
DB[:entries].insert(:id=>1, :account_id=>1, :amount=>100)
|
211
|
-
DB[:accounts].order(:id).select_map(:nonzero_entries_count).must_equal [1, 0]
|
212
|
-
|
213
|
-
DB[:entries].insert(:id=>2, :account_id=>1, :amount=>200)
|
214
|
-
DB[:accounts].order(:id).select_map(:nonzero_entries_count).must_equal [2, 0]
|
215
|
-
|
216
|
-
DB[:entries].insert(:id=>3, :account_id=>nil, :amount=>500)
|
217
|
-
DB[:accounts].order(:id).select_map(:nonzero_entries_count).must_equal [2, 0]
|
218
|
-
|
219
|
-
DB[:entries].where(:id=>3).update(:account_id=>2)
|
220
|
-
DB[:accounts].order(:id).select_map(:nonzero_entries_count).must_equal [2, 1]
|
221
|
-
|
222
|
-
DB[:entries].exclude(:id=>2).update(:amount=>Sequel.*(:amount, 2))
|
223
|
-
DB[:accounts].order(:id).select_map(:nonzero_entries_count).must_equal [2, 1]
|
224
|
-
|
225
|
-
DB[:entries].where(:id=>2).update(:account_id=>2)
|
226
|
-
DB[:accounts].order(:id).select_map(:nonzero_entries_count).must_equal [1, 2]
|
227
|
-
|
228
|
-
DB[:entries].where(:id=>2).update(:account_id=>nil)
|
229
|
-
DB[:accounts].order(:id).select_map(:nonzero_entries_count).must_equal [1, 1]
|
230
|
-
|
231
|
-
DB[:entries].where(:id=>2).update(:id=>4)
|
232
|
-
DB[:accounts].order(:id).select_map(:nonzero_entries_count).must_equal [1, 1]
|
233
|
-
|
234
|
-
DB[:entries].where(:id=>4).update(:account_id=>2)
|
235
|
-
DB[:accounts].order(:id).select_map(:nonzero_entries_count).must_equal [1, 2]
|
236
|
-
|
237
|
-
DB[:entries].where(:id=>4).update(:account_id=>nil)
|
238
|
-
DB[:accounts].order(:id).select_map(:nonzero_entries_count).must_equal [1, 1]
|
239
|
-
|
240
|
-
DB[:entries].filter(:id=>4).delete
|
241
|
-
DB[:accounts].order(:id).select_map(:nonzero_entries_count).must_equal [1, 1]
|
242
|
-
|
243
|
-
DB[:entries].delete
|
244
|
-
DB[:accounts].order(:id).select_map(:nonzero_entries_count).must_equal [0, 0]
|
245
|
-
end
|
246
|
-
end
|
247
|
-
|
248
|
-
|
249
|
-
describe "PostgreSQL Sum Through Many Cache Trigger" do
|
250
|
-
before do
|
251
|
-
DB.create_table(:parents){primary_key :id; integer :balance, :default=>0, :null=>false}
|
252
|
-
DB.create_table(:children){primary_key :id; integer :amount, :null=>false}
|
253
|
-
DB.create_table(:links){integer :parent_id, :null=>false; integer :child_id, :null=>false; unique [:parent_id, :child_id]}
|
254
|
-
DB.pgt_sum_through_many_cache(
|
255
|
-
:main_table=>:parents,
|
256
|
-
:sum_column=>:balance,
|
257
|
-
:summed_table=>:children,
|
258
|
-
:summed_column=>:amount,
|
259
|
-
:join_table=>:links,
|
260
|
-
:main_table_fk_column=>:parent_id,
|
261
|
-
:summed_table_fk_column=>:child_id,
|
262
|
-
:function_name=>:spgt_stm_cache,
|
263
|
-
:join_function_name=>:spgt_stm_cache_join
|
264
|
-
)
|
265
|
-
DB[:parents].insert(:id=>1)
|
266
|
-
DB[:parents].insert(:id=>2)
|
267
|
-
end
|
268
|
-
|
269
|
-
after do
|
270
|
-
DB.drop_table(:links, :parents, :children)
|
271
|
-
DB.drop_function(:spgt_stm_cache)
|
272
|
-
DB.drop_function(:spgt_stm_cache_join)
|
273
|
-
end
|
274
|
-
|
275
|
-
it "Should modify sum cache when adding, updating, or removing records" do
|
276
|
-
DB[:parents].order(:id).select_map(:balance).must_equal [0, 0]
|
277
|
-
|
278
|
-
DB[:children].insert(:id=>1, :amount=>100)
|
279
|
-
DB[:links].insert(:parent_id=>1, :child_id=>1)
|
280
|
-
DB[:parents].order(:id).select_map(:balance).must_equal [100, 0]
|
281
|
-
|
282
|
-
DB[:children].insert(:id=>2, :amount=>200)
|
283
|
-
DB[:links].insert(:parent_id=>1, :child_id=>2)
|
284
|
-
DB[:parents].order(:id).select_map(:balance).must_equal [300, 0]
|
285
|
-
|
286
|
-
DB[:children].insert(:id=>3, :amount=>500)
|
287
|
-
DB[:parents].order(:id).select_map(:balance).must_equal [300, 0]
|
288
|
-
DB[:links].insert(:parent_id=>2, :child_id=>3)
|
289
|
-
DB[:parents].order(:id).select_map(:balance).must_equal [300, 500]
|
357
|
+
DB.drop_table(:links, :parents, :children)
|
358
|
+
DB.drop_function(:spgt_stm_cache)
|
359
|
+
DB.drop_function(:spgt_stm_cache_join)
|
360
|
+
end
|
361
|
+
|
362
|
+
it "should modify sum cache when adding, updating, or removing records" do
|
363
|
+
DB[:parents].order(:id).select_map(:nonzero_entries_count).must_equal [0, 0]
|
290
364
|
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
DB[:children].insert(:id=>4, :amount=>400)
|
295
|
-
DB[:links].where(:parent_id=>1, :child_id=>3).update(:child_id=>4)
|
296
|
-
DB[:parents].order(:id).select_map(:balance).must_equal [700, 0]
|
365
|
+
DB[:children].insert(:id=>1, :amount=>100)
|
366
|
+
DB[:links].insert(:parent_id=>1, :child_id=>1)
|
367
|
+
DB[:parents].order(:id).select_map(:nonzero_entries_count).must_equal [1, 0]
|
297
368
|
|
298
|
-
|
299
|
-
|
369
|
+
DB[:children].insert(:id=>2, :amount=>200)
|
370
|
+
DB[:links].insert(:parent_id=>1, :child_id=>2)
|
371
|
+
DB[:parents].order(:id).select_map(:nonzero_entries_count).must_equal [2, 0]
|
300
372
|
|
301
|
-
|
302
|
-
|
373
|
+
DB[:children].insert(:id=>3, :amount=>500)
|
374
|
+
DB[:parents].order(:id).select_map(:nonzero_entries_count).must_equal [2, 0]
|
375
|
+
DB[:links].insert(:parent_id=>2, :child_id=>3)
|
376
|
+
DB[:parents].order(:id).select_map(:nonzero_entries_count).must_equal [2, 1]
|
303
377
|
|
304
|
-
|
305
|
-
|
378
|
+
DB[:links].where(:parent_id=>2, :child_id=>3).update(:parent_id=>1)
|
379
|
+
DB[:parents].order(:id).select_map(:nonzero_entries_count).must_equal [3, 0]
|
306
380
|
|
307
|
-
|
308
|
-
|
381
|
+
DB[:children].insert(:id=>4, :amount=>400)
|
382
|
+
DB[:links].where(:parent_id=>1, :child_id=>3).update(:child_id=>4)
|
383
|
+
DB[:parents].order(:id).select_map(:nonzero_entries_count).must_equal [3, 0]
|
309
384
|
|
310
|
-
|
311
|
-
|
385
|
+
DB[:links].where(:parent_id=>1, :child_id=>4).update(:parent_id=>2, :child_id=>3)
|
386
|
+
DB[:parents].order(:id).select_map(:nonzero_entries_count).must_equal [2, 1]
|
312
387
|
|
313
|
-
|
314
|
-
|
388
|
+
DB[:children].exclude(:id=>2).update(:amount=>Sequel.*(:amount, 2))
|
389
|
+
DB[:parents].order(:id).select_map(:nonzero_entries_count).must_equal [2, 1]
|
315
390
|
|
316
|
-
|
317
|
-
|
391
|
+
DB[:links].where(:parent_id=>1, :child_id=>2).update(:parent_id=>2)
|
392
|
+
DB[:parents].order(:id).select_map(:nonzero_entries_count).must_equal [1, 2]
|
318
393
|
|
319
|
-
|
320
|
-
|
394
|
+
DB[:links].where(:parent_id=>2, :child_id=>2).update(:parent_id=>1)
|
395
|
+
DB[:parents].order(:id).select_map(:nonzero_entries_count).must_equal [2, 1]
|
321
396
|
|
322
|
-
|
323
|
-
|
397
|
+
DB[:links].where(:parent_id=>1, :child_id=>2).update(:child_id=>3)
|
398
|
+
DB[:parents].order(:id).select_map(:nonzero_entries_count).must_equal [2, 1]
|
324
399
|
|
325
|
-
|
326
|
-
|
400
|
+
DB[:links].insert(:parent_id=>2, :child_id=>4)
|
401
|
+
DB[:parents].order(:id).select_map(:nonzero_entries_count).must_equal [2, 2]
|
327
402
|
|
328
|
-
|
329
|
-
|
403
|
+
DB[:children].filter(:id=>4).delete
|
404
|
+
DB[:parents].order(:id).select_map(:nonzero_entries_count).must_equal [2, 1]
|
330
405
|
|
331
|
-
|
332
|
-
|
406
|
+
DB[:links].filter(:parent_id=>1, :child_id=>1).delete
|
407
|
+
DB[:parents].order(:id).select_map(:nonzero_entries_count).must_equal [1, 1]
|
333
408
|
|
334
|
-
|
335
|
-
|
409
|
+
DB[:children].insert(:id=>4, :amount=>400)
|
410
|
+
DB[:parents].order(:id).select_map(:nonzero_entries_count).must_equal [1, 2]
|
336
411
|
|
337
|
-
|
338
|
-
|
339
|
-
|
412
|
+
DB[:children].delete
|
413
|
+
DB[:parents].order(:id).select_map(:nonzero_entries_count).must_equal [0, 0]
|
414
|
+
|
415
|
+
DB[:children].multi_insert([{:id=>2, :amount=>200}, {:id=>1, :amount=>200}, {:id=>3, :amount=>1000}, {:id=>4, :amount=>400}])
|
416
|
+
DB[:parents].order(:id).select_map(:nonzero_entries_count).must_equal [1, 2]
|
417
|
+
|
418
|
+
DB[:links].where(:child_id=>3).update(:child_id=>2)
|
419
|
+
DB[:parents].order(:id).select_map(:nonzero_entries_count).must_equal [1, 2]
|
420
|
+
|
421
|
+
DB[:children].update(:amount=>10)
|
422
|
+
DB[:parents].order(:id).select_map(:nonzero_entries_count).must_equal [1, 2]
|
423
|
+
|
424
|
+
DB[:links].delete
|
425
|
+
DB[:parents].order(:id).select_map(:nonzero_entries_count).must_equal [0, 0]
|
340
426
|
end
|
427
|
+
end
|
341
428
|
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
DB.pgt_sum_through_many_cache(
|
348
|
-
:main_table=>:parents,
|
349
|
-
:sum_column=>:nonzero_entries_count,
|
350
|
-
:summed_table=>:children,
|
351
|
-
:summed_column=>Sequel.case({0=>0}, 1, :amount),
|
352
|
-
:join_table=>:links,
|
353
|
-
:main_table_fk_column=>:parent_id,
|
354
|
-
:summed_table_fk_column=>:child_id,
|
355
|
-
:function_name=>:spgt_stm_cache,
|
356
|
-
:join_function_name=>:spgt_stm_cache_join
|
357
|
-
)
|
358
|
-
DB[:parents].insert(:id=>1)
|
359
|
-
DB[:parents].insert(:id=>2)
|
360
|
-
end
|
429
|
+
describe "PostgreSQL Updated At Trigger" do
|
430
|
+
before do
|
431
|
+
DB.create_table(:accounts){integer :id; timestamp :changed_on}
|
432
|
+
DB.pgt_updated_at(:accounts, :changed_on, :function_name=>:spgt_updated_at)
|
433
|
+
end
|
361
434
|
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
end
|
435
|
+
after do
|
436
|
+
DB.drop_table(:accounts)
|
437
|
+
DB.drop_function(:spgt_updated_at)
|
438
|
+
end
|
367
439
|
|
368
|
-
|
369
|
-
|
440
|
+
it "should set the column always to the current timestamp" do
|
441
|
+
DB[:accounts].insert(:id=>1)
|
442
|
+
t = DB[:accounts].get(:changed_on)
|
443
|
+
t.strftime('%F').must_equal Date.today.strftime('%F')
|
444
|
+
DB[:accounts].insert(:id=>2)
|
445
|
+
ds = DB[:accounts].select(:changed_on)
|
446
|
+
DB[:accounts].select((Sequel::SQL::NumericExpression.new(:NOOP, ds.filter(:id=>2)) > ds.filter(:id=>1)).as(:x)).first[:x].must_equal true
|
447
|
+
DB[:accounts].filter(:id=>1).update(:id=>3)
|
448
|
+
DB[:accounts].select((Sequel::SQL::NumericExpression.new(:NOOP, ds.filter(:id=>3)) > ds.filter(:id=>2)).as(:x)).first[:x].must_equal true
|
449
|
+
end
|
450
|
+
end
|
370
451
|
|
371
|
-
|
372
|
-
|
373
|
-
|
452
|
+
describe "PostgreSQL Touch Trigger" do
|
453
|
+
before do
|
454
|
+
DB.create_table(:parents){integer :id1; integer :id2; integer :child_id; timestamp :changed_on}
|
455
|
+
DB.create_table(:children){integer :id; integer :parent_id1; integer :parent_id2; timestamp :changed_on}
|
456
|
+
end
|
374
457
|
|
375
|
-
|
376
|
-
|
377
|
-
|
458
|
+
after do
|
459
|
+
DB.drop_table(:children, :parents)
|
460
|
+
DB.drop_function(:spgt_touch)
|
461
|
+
DB.drop_function(:spgt_touch2) if @spgt_touch2
|
462
|
+
end
|
378
463
|
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
464
|
+
it "should update the timestamp column of the related table when adding, updating or removing records" do
|
465
|
+
DB.pgt_touch(:children, :parents, :changed_on, {:id1=>:parent_id1}, :function_name=>:spgt_touch)
|
466
|
+
d = Date.today
|
467
|
+
d30 = Date.today - 30
|
468
|
+
DB[:parents].insert(:id1=>1, :changed_on=>d30)
|
469
|
+
DB[:parents].insert(:id1=>2, :changed_on=>d30)
|
470
|
+
DB[:children].insert(:id=>1, :parent_id1=>1)
|
471
|
+
DB[:parents].order(:id1).select_map(:changed_on).map{|t| t.strftime('%F')}.must_equal [d.strftime('%F'), d30.strftime('%F')]
|
472
|
+
|
473
|
+
DB[:parents].update(:changed_on=>d30)
|
474
|
+
DB[:children].update(:id=>2)
|
475
|
+
DB[:parents].order(:id1).select_map(:changed_on).map{|t| t.strftime('%F')}.must_equal [d.strftime('%F'), d30.strftime('%F')]
|
476
|
+
|
477
|
+
DB[:parents].update(:changed_on=>d30)
|
478
|
+
DB[:children].update(:parent_id1=>2)
|
479
|
+
DB[:parents].order(:id1).select_map(:changed_on).map{|t| t.strftime('%F')}.must_equal [d.strftime('%F'), d.strftime('%F')]
|
480
|
+
|
481
|
+
DB[:parents].update(:changed_on=>d30)
|
482
|
+
DB[:children].update(:parent_id1=>nil)
|
483
|
+
DB[:parents].order(:id1).select_map(:changed_on).map{|t| t.strftime('%F')}.must_equal [d30.strftime('%F'), d.strftime('%F')]
|
484
|
+
|
485
|
+
DB[:parents].update(:changed_on=>d30)
|
486
|
+
DB[:children].update(:parent_id2=>1)
|
487
|
+
DB[:parents].order(:id1).select_map(:changed_on).map{|t| t.strftime('%F')}.must_equal [d30.strftime('%F'), d30.strftime('%F')]
|
488
|
+
|
489
|
+
DB[:parents].update(:changed_on=>d30)
|
490
|
+
DB[:children].update(:parent_id1=>2)
|
491
|
+
DB[:parents].order(:id1).select_map(:changed_on).map{|t| t.strftime('%F')}.must_equal [d30.strftime('%F'), d.strftime('%F')]
|
492
|
+
|
493
|
+
DB[:parents].update(:changed_on=>d30)
|
494
|
+
DB[:children].delete
|
495
|
+
DB[:parents].order(:id1).select_map(:changed_on).map{|t| t.strftime('%F')}.must_equal [d30.strftime('%F'), d.strftime('%F')]
|
496
|
+
|
497
|
+
DB[:parents].update(:changed_on=>d30)
|
498
|
+
DB[:children].insert(:id=>2, :parent_id1=>nil)
|
499
|
+
DB[:parents].order(:id1).select_map(:changed_on).map{|t| t.strftime('%F')}.must_equal [d30.strftime('%F'), d30.strftime('%F')]
|
500
|
+
DB[:children].where(:id=>2).delete
|
501
|
+
DB[:parents].order(:id1).select_map(:changed_on).map{|t| t.strftime('%F')}.must_equal [d30.strftime('%F'), d30.strftime('%F')]
|
502
|
+
end
|
383
503
|
|
384
|
-
|
385
|
-
|
504
|
+
it "should update the timestamp column of the related table when there is a composite foreign key" do
|
505
|
+
DB.pgt_touch(:children, :parents, :changed_on, {:id1=>:parent_id1, :id2=>:parent_id2}, :function_name=>:spgt_touch)
|
506
|
+
DB[:parents].insert(:id1=>1, :id2=>2, :changed_on=>Date.today - 30)
|
507
|
+
DB[:children].insert(:id=>1, :parent_id1=>1, :parent_id2=>2)
|
508
|
+
DB[:parents].get(:changed_on).strftime('%F').must_equal Date.today.strftime('%F')
|
509
|
+
DB[:parents].update(:changed_on=>Date.today - 30)
|
510
|
+
DB[:children].update(:id=>2)
|
511
|
+
DB[:parents].get(:changed_on).strftime('%F').must_equal Date.today.strftime('%F')
|
512
|
+
DB[:parents].update(:changed_on=>Date.today - 30)
|
513
|
+
DB[:children].delete
|
514
|
+
DB[:parents].get(:changed_on).strftime('%F').must_equal Date.today.strftime('%F')
|
515
|
+
end
|
516
|
+
|
517
|
+
it "should update timestamps correctly when two tables touch each other" do
|
518
|
+
DB.pgt_touch(:children, :parents, :changed_on, {:id1=>:parent_id1}, :function_name=>:spgt_touch)
|
519
|
+
@spgt_touch2 = true
|
520
|
+
DB.pgt_touch(:parents, :children, :changed_on, {:id=>:child_id}, :function_name=>:spgt_touch2)
|
521
|
+
DB[:parents].insert(:id1=>1, :child_id=>1, :changed_on=>Date.today - 30)
|
522
|
+
DB[:children].insert(:id=>1, :parent_id1=>1, :changed_on=>Date.today - 30)
|
523
|
+
DB[:parents].get(:changed_on).strftime('%F').must_equal Date.today.strftime('%F')
|
524
|
+
DB[:children].get(:changed_on).strftime('%F').must_equal Date.today.strftime('%F')
|
525
|
+
time = DB[:parents].get(:changed_on)
|
526
|
+
DB[:parents].update(:id2=>4)
|
527
|
+
DB[:parents].get(:changed_on).must_be :>, time
|
528
|
+
DB[:children].get(:changed_on).must_be :>, time
|
529
|
+
time = DB[:parents].get(:changed_on)
|
530
|
+
DB[:children].update(:id=>1)
|
531
|
+
DB[:parents].get(:changed_on).must_be :>, time
|
532
|
+
DB[:children].get(:changed_on).must_be :>, time
|
533
|
+
time = DB[:parents].get(:changed_on)
|
534
|
+
DB[:children].delete
|
535
|
+
DB[:parents].get(:changed_on).must_be :>, time
|
536
|
+
end
|
537
|
+
|
538
|
+
it "should update the timestamp on the related table if that timestamp is initially NULL" do
|
539
|
+
DB.pgt_touch(:children, :parents, :changed_on, {:id1=>:parent_id1}, :function_name=>:spgt_touch)
|
540
|
+
DB[:parents].insert(:id1=>1, :changed_on=>nil)
|
541
|
+
DB[:children].insert(:id=>1, :parent_id1=>1)
|
542
|
+
changed_on = DB[:parents].get(:changed_on)
|
543
|
+
changed_on.wont_equal nil
|
544
|
+
changed_on.strftime('%F').must_equal Date.today.strftime('%F')
|
545
|
+
end
|
546
|
+
end
|
547
|
+
|
548
|
+
describe "PostgreSQL Array Foreign Key Trigger" do
|
549
|
+
before do
|
550
|
+
DB.create_table(:accounts){Integer :id, :primary_key=>true}
|
551
|
+
DB.create_table(:entries){Integer :id, :primary_key=>true; column :account_ids, 'integer[]'}
|
552
|
+
DB.pgt_foreign_key_array(:table=>:entries, :column=>:account_ids, :referenced_table=>:accounts, :referenced_column=>:id, :function_name=>:spgt_foreign_key_array, :referenced_function_name=>:spgt_referenced_foreign_key_array)
|
553
|
+
end
|
386
554
|
|
387
|
-
|
388
|
-
|
389
|
-
|
555
|
+
after do
|
556
|
+
DB.drop_table(:entries, :accounts)
|
557
|
+
DB.drop_function(:spgt_foreign_key_array)
|
558
|
+
DB.drop_function(:spgt_referenced_foreign_key_array)
|
559
|
+
end
|
390
560
|
|
391
|
-
|
392
|
-
|
561
|
+
it "should raise error for queries that violate referential integrity, and allow other queries" do
|
562
|
+
proc{DB[:entries].insert(:id=>10, :account_ids=>Sequel.pg_array([1]))}.must_raise Sequel::DatabaseError
|
563
|
+
DB[:entries].insert(:id=>10, :account_ids=>nil)
|
564
|
+
DB[:entries].update(:account_ids=>Sequel.pg_array([], :integer))
|
565
|
+
DB[:accounts].insert(:id=>1)
|
566
|
+
proc{DB[:entries].insert(:id=>10, :account_ids=>Sequel.pg_array([1, 1]))}.must_raise Sequel::DatabaseError
|
567
|
+
DB[:entries].update(:account_ids=>Sequel.pg_array([1]))
|
568
|
+
proc{DB[:entries].update(:account_ids=>Sequel.pg_array([2]))}.must_raise Sequel::DatabaseError
|
569
|
+
DB[:accounts].insert(:id=>2)
|
570
|
+
proc{DB[:entries].insert(:id=>10, :account_ids=>Sequel.pg_array([[1], [2]]))}.must_raise Sequel::DatabaseError
|
571
|
+
DB[:entries].update(:account_ids=>Sequel.pg_array([2]))
|
572
|
+
DB[:entries].all.must_equal [{:id=>10, :account_ids=>[2]}]
|
573
|
+
DB[:entries].update(:account_ids=>Sequel.pg_array([1, 2]))
|
574
|
+
DB[:entries].all.must_equal [{:id=>10, :account_ids=>[1, 2]}]
|
575
|
+
DB[:entries].update(:account_ids=>Sequel.pg_array([1]))
|
576
|
+
DB[:accounts].where(:id=>1).update(:id=>1)
|
577
|
+
DB[:accounts].where(:id=>2).update(:id=>3)
|
578
|
+
proc{DB[:accounts].where(:id=>1).update(:id=>2)}.must_raise Sequel::DatabaseError
|
579
|
+
proc{DB[:accounts].where(:id=>1).delete}.must_raise Sequel::DatabaseError
|
580
|
+
DB[:accounts].where(:id=>3).count.must_equal 1
|
581
|
+
DB[:accounts].where(:id=>3).delete
|
582
|
+
proc{DB[:accounts].delete}.must_raise Sequel::DatabaseError
|
583
|
+
DB[:entries].delete
|
584
|
+
DB[:accounts].delete
|
585
|
+
end
|
586
|
+
end
|
393
587
|
|
394
|
-
|
395
|
-
|
588
|
+
describe "PostgreSQL Force Defaults Trigger" do
|
589
|
+
before do
|
590
|
+
DB.create_table(:accounts){integer :id; integer :a, :default=>0; String :b; integer :c; integer :d, :default=>4}
|
591
|
+
DB.pgt_force_defaults(:accounts, {:a=>1, :b=>"'\\a", :c=>nil}, :function_name=>:spgt_force_defaults)
|
592
|
+
@ds = DB[:accounts]
|
593
|
+
end
|
396
594
|
|
397
|
-
|
398
|
-
|
595
|
+
after do
|
596
|
+
DB.drop_table(:accounts)
|
597
|
+
DB.drop_function(:spgt_force_defaults)
|
598
|
+
end
|
399
599
|
|
400
|
-
|
401
|
-
|
600
|
+
it "should override default values when inserting" do
|
601
|
+
@ds.insert
|
602
|
+
DB[:accounts].first.must_equal(:id=>nil, :a=>1, :b=>"'\\a", :c=>nil, :d=>4)
|
402
603
|
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
DB[:links].insert(:parent_id=>2, :child_id=>4)
|
407
|
-
DB[:parents].order(:id).select_map(:nonzero_entries_count).must_equal [2, 2]
|
408
|
-
|
409
|
-
DB[:children].filter(:id=>4).delete
|
410
|
-
DB[:parents].order(:id).select_map(:nonzero_entries_count).must_equal [2, 1]
|
411
|
-
|
412
|
-
DB[:links].filter(:parent_id=>1, :child_id=>1).delete
|
413
|
-
DB[:parents].order(:id).select_map(:nonzero_entries_count).must_equal [1, 1]
|
414
|
-
|
415
|
-
DB[:children].insert(:id=>4, :amount=>400)
|
416
|
-
DB[:parents].order(:id).select_map(:nonzero_entries_count).must_equal [1, 2]
|
417
|
-
|
418
|
-
DB[:children].delete
|
419
|
-
DB[:parents].order(:id).select_map(:nonzero_entries_count).must_equal [0, 0]
|
420
|
-
|
421
|
-
DB[:children].multi_insert([{:id=>2, :amount=>200}, {:id=>1, :amount=>200}, {:id=>3, :amount=>1000}, {:id=>4, :amount=>400}])
|
422
|
-
DB[:parents].order(:id).select_map(:nonzero_entries_count).must_equal [1, 2]
|
423
|
-
|
424
|
-
DB[:links].where(:child_id=>3).update(:child_id=>2)
|
425
|
-
DB[:parents].order(:id).select_map(:nonzero_entries_count).must_equal [1, 2]
|
426
|
-
|
427
|
-
DB[:children].update(:amount=>10)
|
428
|
-
DB[:parents].order(:id).select_map(:nonzero_entries_count).must_equal [1, 2]
|
429
|
-
|
430
|
-
DB[:links].delete
|
431
|
-
DB[:parents].order(:id).select_map(:nonzero_entries_count).must_equal [0, 0]
|
432
|
-
end
|
433
|
-
end
|
434
|
-
|
435
|
-
describe "PostgreSQL Updated At Trigger" do
|
436
|
-
before do
|
437
|
-
DB.create_table(:accounts){integer :id; timestamp :changed_on}
|
438
|
-
DB.pgt_updated_at(:accounts, :changed_on, :function_name=>:spgt_updated_at)
|
439
|
-
end
|
440
|
-
|
441
|
-
after do
|
442
|
-
DB.drop_table(:accounts)
|
443
|
-
DB.drop_function(:spgt_updated_at)
|
444
|
-
end
|
445
|
-
|
446
|
-
it "Should set the column always to the current timestamp" do
|
447
|
-
DB[:accounts].insert(:id=>1)
|
448
|
-
t = DB[:accounts].get(:changed_on)
|
449
|
-
t.strftime('%F').must_equal Date.today.strftime('%F')
|
450
|
-
DB[:accounts].insert(:id=>2)
|
451
|
-
ds = DB[:accounts].select(:changed_on)
|
452
|
-
DB[:accounts].select((Sequel::SQL::NumericExpression.new(:NOOP, ds.filter(:id=>2)) > ds.filter(:id=>1)).as(:x)).first[:x].must_equal true
|
453
|
-
DB[:accounts].filter(:id=>1).update(:id=>3)
|
454
|
-
DB[:accounts].select((Sequel::SQL::NumericExpression.new(:NOOP, ds.filter(:id=>3)) > ds.filter(:id=>2)).as(:x)).first[:x].must_equal true
|
455
|
-
end
|
456
|
-
end
|
457
|
-
|
458
|
-
describe "PostgreSQL Touch Trigger" do
|
459
|
-
before do
|
460
|
-
DB.create_table(:parents){integer :id1; integer :id2; integer :child_id; timestamp :changed_on}
|
461
|
-
DB.create_table(:children){integer :id; integer :parent_id1; integer :parent_id2; timestamp :changed_on}
|
462
|
-
end
|
463
|
-
|
464
|
-
after do
|
465
|
-
DB.drop_table(:children, :parents)
|
466
|
-
DB.drop_function(:spgt_touch)
|
467
|
-
DB.drop_function(:spgt_touch2) if @spgt_touch2
|
468
|
-
end
|
469
|
-
|
470
|
-
it "Should update the timestamp column of the related table when adding, updating or removing records" do
|
471
|
-
DB.pgt_touch(:children, :parents, :changed_on, {:id1=>:parent_id1}, :function_name=>:spgt_touch)
|
472
|
-
d = Date.today
|
473
|
-
d30 = Date.today - 30
|
474
|
-
DB[:parents].insert(:id1=>1, :changed_on=>d30)
|
475
|
-
DB[:parents].insert(:id1=>2, :changed_on=>d30)
|
476
|
-
DB[:children].insert(:id=>1, :parent_id1=>1)
|
477
|
-
DB[:parents].order(:id1).select_map(:changed_on).map{|t| t.strftime('%F')}.must_equal [d.strftime('%F'), d30.strftime('%F')]
|
478
|
-
|
479
|
-
DB[:parents].update(:changed_on=>d30)
|
480
|
-
DB[:children].update(:id=>2)
|
481
|
-
DB[:parents].order(:id1).select_map(:changed_on).map{|t| t.strftime('%F')}.must_equal [d.strftime('%F'), d30.strftime('%F')]
|
482
|
-
|
483
|
-
DB[:parents].update(:changed_on=>d30)
|
484
|
-
DB[:children].update(:parent_id1=>2)
|
485
|
-
DB[:parents].order(:id1).select_map(:changed_on).map{|t| t.strftime('%F')}.must_equal [d.strftime('%F'), d.strftime('%F')]
|
486
|
-
|
487
|
-
DB[:parents].update(:changed_on=>d30)
|
488
|
-
DB[:children].update(:parent_id1=>nil)
|
489
|
-
DB[:parents].order(:id1).select_map(:changed_on).map{|t| t.strftime('%F')}.must_equal [d30.strftime('%F'), d.strftime('%F')]
|
490
|
-
|
491
|
-
DB[:parents].update(:changed_on=>d30)
|
492
|
-
DB[:children].update(:parent_id2=>1)
|
493
|
-
DB[:parents].order(:id1).select_map(:changed_on).map{|t| t.strftime('%F')}.must_equal [d30.strftime('%F'), d30.strftime('%F')]
|
494
|
-
|
495
|
-
DB[:parents].update(:changed_on=>d30)
|
496
|
-
DB[:children].update(:parent_id1=>2)
|
497
|
-
DB[:parents].order(:id1).select_map(:changed_on).map{|t| t.strftime('%F')}.must_equal [d30.strftime('%F'), d.strftime('%F')]
|
498
|
-
|
499
|
-
DB[:parents].update(:changed_on=>d30)
|
500
|
-
DB[:children].delete
|
501
|
-
DB[:parents].order(:id1).select_map(:changed_on).map{|t| t.strftime('%F')}.must_equal [d30.strftime('%F'), d.strftime('%F')]
|
502
|
-
|
503
|
-
DB[:parents].update(:changed_on=>d30)
|
504
|
-
DB[:children].insert(:id=>2, :parent_id1=>nil)
|
505
|
-
DB[:parents].order(:id1).select_map(:changed_on).map{|t| t.strftime('%F')}.must_equal [d30.strftime('%F'), d30.strftime('%F')]
|
506
|
-
DB[:children].where(:id=>2).delete
|
507
|
-
DB[:parents].order(:id1).select_map(:changed_on).map{|t| t.strftime('%F')}.must_equal [d30.strftime('%F'), d30.strftime('%F')]
|
508
|
-
end
|
509
|
-
|
510
|
-
it "Should update the timestamp column of the related table when there is a composite foreign key" do
|
511
|
-
DB.pgt_touch(:children, :parents, :changed_on, {:id1=>:parent_id1, :id2=>:parent_id2}, :function_name=>:spgt_touch)
|
512
|
-
DB[:parents].insert(:id1=>1, :id2=>2, :changed_on=>Date.today - 30)
|
513
|
-
DB[:children].insert(:id=>1, :parent_id1=>1, :parent_id2=>2)
|
514
|
-
DB[:parents].get(:changed_on).strftime('%F').must_equal Date.today.strftime('%F')
|
515
|
-
DB[:parents].update(:changed_on=>Date.today - 30)
|
516
|
-
DB[:children].update(:id=>2)
|
517
|
-
DB[:parents].get(:changed_on).strftime('%F').must_equal Date.today.strftime('%F')
|
518
|
-
DB[:parents].update(:changed_on=>Date.today - 30)
|
519
|
-
DB[:children].delete
|
520
|
-
DB[:parents].get(:changed_on).strftime('%F').must_equal Date.today.strftime('%F')
|
521
|
-
end
|
522
|
-
|
523
|
-
it "Should update timestamps correctly when two tables touch each other" do
|
524
|
-
DB.pgt_touch(:children, :parents, :changed_on, {:id1=>:parent_id1}, :function_name=>:spgt_touch)
|
525
|
-
@spgt_touch2 = true
|
526
|
-
DB.pgt_touch(:parents, :children, :changed_on, {:id=>:child_id}, :function_name=>:spgt_touch2)
|
527
|
-
DB[:parents].insert(:id1=>1, :child_id=>1, :changed_on=>Date.today - 30)
|
528
|
-
DB[:children].insert(:id=>1, :parent_id1=>1, :changed_on=>Date.today - 30)
|
529
|
-
DB[:parents].get(:changed_on).strftime('%F').must_equal Date.today.strftime('%F')
|
530
|
-
DB[:children].get(:changed_on).strftime('%F').must_equal Date.today.strftime('%F')
|
531
|
-
time = DB[:parents].get(:changed_on)
|
532
|
-
DB[:parents].update(:id2=>4)
|
533
|
-
DB[:parents].get(:changed_on).must_be :>, time
|
534
|
-
DB[:children].get(:changed_on).must_be :>, time
|
535
|
-
time = DB[:parents].get(:changed_on)
|
536
|
-
DB[:children].update(:id=>1)
|
537
|
-
DB[:parents].get(:changed_on).must_be :>, time
|
538
|
-
DB[:children].get(:changed_on).must_be :>, time
|
539
|
-
time = DB[:parents].get(:changed_on)
|
540
|
-
DB[:children].delete
|
541
|
-
DB[:parents].get(:changed_on).must_be :>, time
|
542
|
-
end
|
543
|
-
|
544
|
-
it "Should update the timestamp on the related table if that timestamp is initially NULL" do
|
545
|
-
DB.pgt_touch(:children, :parents, :changed_on, {:id1=>:parent_id1}, :function_name=>:spgt_touch)
|
546
|
-
DB[:parents].insert(:id1=>1, :changed_on=>nil)
|
547
|
-
DB[:children].insert(:id=>1, :parent_id1=>1)
|
548
|
-
changed_on = DB[:parents].get(:changed_on)
|
549
|
-
changed_on.wont_equal nil
|
550
|
-
changed_on.strftime('%F').must_equal Date.today.strftime('%F')
|
551
|
-
end
|
552
|
-
end
|
553
|
-
|
554
|
-
describe "PostgreSQL Array Foreign Key Trigger" do
|
555
|
-
before do
|
556
|
-
DB.create_table(:accounts){Integer :id, :primary_key=>true}
|
557
|
-
DB.create_table(:entries){Integer :id, :primary_key=>true; column :account_ids, 'integer[]'}
|
558
|
-
DB.pgt_foreign_key_array(:table=>:entries, :column=>:account_ids, :referenced_table=>:accounts, :referenced_column=>:id, :function_name=>:spgt_foreign_key_array, :referenced_function_name=>:spgt_referenced_foreign_key_array)
|
559
|
-
end
|
560
|
-
|
561
|
-
after do
|
562
|
-
DB.drop_table(:entries, :accounts)
|
563
|
-
DB.drop_function(:spgt_foreign_key_array)
|
564
|
-
DB.drop_function(:spgt_referenced_foreign_key_array)
|
565
|
-
end
|
566
|
-
|
567
|
-
it "should raise error for queries that violate referential integrity, and allow other queries" do
|
568
|
-
proc{DB[:entries].insert(:id=>10, :account_ids=>Sequel.pg_array([1]))}.must_raise Sequel::DatabaseError
|
569
|
-
DB[:entries].insert(:id=>10, :account_ids=>nil)
|
570
|
-
DB[:entries].update(:account_ids=>Sequel.pg_array([], :integer))
|
571
|
-
DB[:accounts].insert(:id=>1)
|
572
|
-
proc{DB[:entries].insert(:id=>10, :account_ids=>Sequel.pg_array([1, 1]))}.must_raise Sequel::DatabaseError
|
573
|
-
DB[:entries].update(:account_ids=>Sequel.pg_array([1]))
|
574
|
-
proc{DB[:entries].update(:account_ids=>Sequel.pg_array([2]))}.must_raise Sequel::DatabaseError
|
575
|
-
DB[:accounts].insert(:id=>2)
|
576
|
-
proc{DB[:entries].insert(:id=>10, :account_ids=>Sequel.pg_array([[1], [2]]))}.must_raise Sequel::DatabaseError
|
577
|
-
DB[:entries].update(:account_ids=>Sequel.pg_array([2]))
|
578
|
-
DB[:entries].all.must_equal [{:id=>10, :account_ids=>[2]}]
|
579
|
-
DB[:entries].update(:account_ids=>Sequel.pg_array([1, 2]))
|
580
|
-
DB[:entries].all.must_equal [{:id=>10, :account_ids=>[1, 2]}]
|
581
|
-
DB[:entries].update(:account_ids=>Sequel.pg_array([1]))
|
582
|
-
DB[:accounts].where(:id=>1).update(:id=>1)
|
583
|
-
DB[:accounts].where(:id=>2).update(:id=>3)
|
584
|
-
proc{DB[:accounts].where(:id=>1).update(:id=>2)}.must_raise Sequel::DatabaseError
|
585
|
-
proc{DB[:accounts].where(:id=>1).delete}.must_raise Sequel::DatabaseError
|
586
|
-
DB[:accounts].where(:id=>3).count.must_equal 1
|
587
|
-
DB[:accounts].where(:id=>3).delete
|
588
|
-
proc{DB[:accounts].delete}.must_raise Sequel::DatabaseError
|
589
|
-
DB[:entries].delete
|
590
|
-
DB[:accounts].delete
|
591
|
-
end
|
604
|
+
@ds.delete
|
605
|
+
@ds.insert(:id=>10, :a=>11, :b=>12, :c=>13, :d=>14)
|
606
|
+
DB[:accounts].first.must_equal(:id=>10, :a=>1, :b=>"'\\a", :c=>nil, :d=>14)
|
592
607
|
end
|
593
608
|
end
|
609
|
+
|
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
|
+
txid1 = h.delete(:txid)
|
636
|
+
txid1.must_be_kind_of(Integer)
|
637
|
+
h.must_equal(:schema=>"public", :table=>"accounts", :action=>"UPDATE", :prior=>{"a"=>nil, "id"=>1})
|
638
|
+
|
639
|
+
@ds.delete
|
640
|
+
@ds.all.must_equal []
|
641
|
+
h = @logs.first
|
642
|
+
h.delete(:at).to_i.must_be_within_delta(10, DB.get(Sequel::CURRENT_TIMESTAMP).to_i)
|
643
|
+
h.delete(:user).must_be_kind_of(String)
|
644
|
+
txid2 = h.delete(:txid)
|
645
|
+
txid2.must_be_kind_of(Integer)
|
646
|
+
txid2.must_be :>, txid1
|
647
|
+
h.must_equal(:schema=>"public", :table=>"accounts", :action=>"DELETE", :prior=>{"a"=>3, "id"=>2})
|
648
|
+
end
|
649
|
+
end if DB.server_version >= 90400
|