sequel_postgresql_triggers 1.4.0 → 1.5.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 +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
|