sequel_postgresql_triggers 1.3.0 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: e4f87e2b711aa431fa2c58e7775e2f6f7263f86a
4
- data.tar.gz: 3053bc8ad102ad616fdb1aa05240e2564553366d
2
+ SHA256:
3
+ metadata.gz: 3b5d81f0071528eb70130d8ebe3093a365c0d024e250583d8ea7e35d5b7af263
4
+ data.tar.gz: 18bb2a79d2642636f34fce1f0f4be315e90592c3be8c8a8d5f587d753061d360
5
5
  SHA512:
6
- metadata.gz: 139573dfcf3b39b056a844c91f8b397b0a72d38365601624717a60d292a3f92c9213fc981402f62b7dccacedbe50ef35f6ebb2c292e1427c6312538545889b75
7
- data.tar.gz: 0b75cb7a59a2f274e988274bf7a5d999bf39eef2878b6685b7a68f8bbfe4296e81c86366a42d9f705da7cc15d9c7a34a93ab083c45d550113f49616ad8ef1117
6
+ metadata.gz: 615949150a244df98c696da8472ebeb40ae5c09812457f5ecca61197a10754e489c431a68d2a44df91eb5f17e229e6e39b4d3b4f4cf6ca28be520b2145345e3c
7
+ data.tar.gz: ca67361ebd569731121ad3f482e47d0320d0658144fd4d7bd21734fcf3542ae84d3fb592483813980fbb4f7e8b52866575c4f0c7efb8566d34760d884f66207c
@@ -1,4 +1,4 @@
1
- Copyright (c) 2008-2016 Jeremy Evans
1
+ Copyright (c) 2008-2017 Jeremy Evans
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining a copy
4
4
  of this software and associated documentation files (the "Software"), to
@@ -7,6 +7,7 @@ a user to easily handle the following types of columns:
7
7
  * Counter/Sum Caches
8
8
  * Immutable Columns
9
9
  * Touch Propogation
10
+ * Foreign Key Arrays (Referential Integrity Checks)
10
11
 
11
12
  It handles these internally to the database via triggers, so even if
12
13
  other applications access the database (without using Sequel), things
@@ -177,6 +178,26 @@ column :: name of timestamp column to be touched
177
178
  expr :: hash or array that represents the columns that define the relationship
178
179
  opts :: options hash
179
180
 
181
+ === Foreign Key Arrays - pgt_foreign_key_array
182
+
183
+ This takes a single options hash, and sets up triggers on both
184
+ tables involved. The table with the foreign key array has insert/update
185
+ triggers to make sure newly inserted/updated rows reference valid rows
186
+ in the referenced table. The table being referenced has update/delete
187
+ triggers to make sure the value before update or delete is not still
188
+ being referenced.
189
+
190
+ Note that this will not catch all referential integrity violations, but
191
+ it should catch the most common ones.
192
+
193
+ Options:
194
+ :table :: table with foreign key array
195
+ :column :: foreign key array column
196
+ :referenced_table :: table referenced by foreign key array
197
+ :referenced_column :: column referenced by foreign key array (generally primary key)
198
+ :referenced_function_name :: function name for trigger function on referenced table
199
+ :referenced_trigger_name :: trigger name for referenced table
200
+
180
201
  == License
181
202
 
182
203
  This library is released under the MIT License. See the MIT-LICENSE
@@ -213,6 +213,66 @@ module Sequel
213
213
  SQL
214
214
  end
215
215
 
216
+ def pgt_foreign_key_array(opts={})
217
+ table, column, rtable, rcolumn = opts.values_at(:table, :column, :referenced_table, :referenced_column)
218
+ trigger_name = opts[:trigger_name] || "pgt_fka_#{column}"
219
+ function_name = opts[:function_name] || "pgt_fka_#{table}__#{column}"
220
+ rtrigger_name = opts[:referenced_trigger_name] || "pgt_rfka_#{column}"
221
+ rfunction_name = opts[:referenced_function_name] || "pgt_rfka_#{table}__#{column}"
222
+ col = quote_identifier(column)
223
+ tab = quote_identifier(table)
224
+ rcol = quote_identifier(rcolumn)
225
+ rtab = quote_identifier(rtable)
226
+
227
+ pgt_trigger(table, trigger_name, function_name, [:insert, :update], <<-SQL)
228
+ DECLARE
229
+ arr #{tab}.#{col}%TYPE;
230
+ temp_count1 int;
231
+ temp_count2 int;
232
+ BEGIN
233
+ arr := NEW.#{col};
234
+ temp_count1 := array_ndims(arr);
235
+ IF arr IS NULL OR temp_count1 IS NULL THEN
236
+ RETURN NEW;
237
+ END IF;
238
+
239
+ IF temp_count1 IS DISTINCT FROM 1 THEN
240
+ RAISE EXCEPTION 'Foreign key array #{tab}.#{col} has more than 1 dimension: %, dimensions: %', arr, temp_count1;
241
+ END IF;
242
+
243
+ SELECT count(*) INTO temp_count1 FROM unnest(arr);
244
+ SELECT count(*) INTO temp_count2 FROM (SELECT DISTINCT * FROM unnest(arr)) AS t;
245
+ IF temp_count1 IS DISTINCT FROM temp_count2 THEN
246
+ RAISE EXCEPTION 'Duplicate entry in foreign key array #{tab}.#{col}: %', arr;
247
+ END IF;
248
+
249
+ SELECT COUNT(*) INTO temp_count1 FROM #{rtab} WHERE #{rcol} = ANY(arr);
250
+ temp_count2 := array_length(arr, 1);
251
+ IF temp_count1 IS DISTINCT FROM temp_count2 THEN
252
+ RAISE EXCEPTION 'Entry in foreign key array #{tab}.#{col} not in referenced column #{rtab}.#{rcol}: %', arr;
253
+ END IF;
254
+
255
+ RETURN NEW;
256
+ END;
257
+ SQL
258
+
259
+ pgt_trigger(rtable, rtrigger_name, rfunction_name, [:delete, :update], <<-SQL)
260
+ DECLARE
261
+ val #{rtab}.#{rcol}%TYPE;
262
+ temp_count int;
263
+ BEGIN
264
+ val := OLD.#{rcol};
265
+ IF (TG_OP = 'DELETE') OR val IS DISTINCT FROM NEW.#{rcol} THEN
266
+ SELECT COUNT(*) INTO temp_count FROM #{tab} WHERE #{col} @> ARRAY[val];
267
+ IF temp_count IS DISTINCT FROM 0 THEN
268
+ RAISE EXCEPTION 'Entry in referenced column #{rtab}.#{rcol} still in foreign key array #{tab}.#{col}: %, count: %', val, temp_count;
269
+ END IF;
270
+ END IF;
271
+ RETURN NEW;
272
+ END;
273
+ SQL
274
+ end
275
+
216
276
  private
217
277
 
218
278
  # Add or replace a function that returns trigger to handle the action,
@@ -14,6 +14,7 @@ else
14
14
  puts "Running specs with extension"
15
15
  DB.extension :pg_triggers
16
16
  end
17
+ DB.extension :pg_array
17
18
 
18
19
  describe "PostgreSQL Triggers" do
19
20
  before do
@@ -28,8 +29,8 @@ describe "PostgreSQL Triggers" do
28
29
  DB.create_table(:accounts){integer :id; integer :num_entries, :default=>0}
29
30
  DB.create_table(:entries){integer :id; integer :account_id}
30
31
  DB.pgt_counter_cache(:accounts, :id, :num_entries, :entries, :account_id, :function_name=>:spgt_counter_cache)
31
- DB[:accounts] << {:id=>1}
32
- DB[:accounts] << {:id=>2}
32
+ DB[:accounts].insert(:id=>1)
33
+ DB[:accounts].insert(:id=>2)
33
34
  end
34
35
 
35
36
  after do
@@ -40,13 +41,13 @@ describe "PostgreSQL Triggers" do
40
41
  it "Should modify counter cache when adding or removing records" do
41
42
  DB[:accounts].order(:id).select_map(:num_entries).must_equal [0, 0]
42
43
 
43
- DB[:entries] << {:id=>1, :account_id=>1}
44
+ DB[:entries].insert(:id=>1, :account_id=>1)
44
45
  DB[:accounts].order(:id).select_map(:num_entries).must_equal [1, 0]
45
46
 
46
- DB[:entries] << {:id=>2, :account_id=>1}
47
+ DB[:entries].insert(:id=>2, :account_id=>1)
47
48
  DB[:accounts].order(:id).select_map(:num_entries).must_equal [2, 0]
48
49
 
49
- DB[:entries] << {:id=>3, :account_id=>nil}
50
+ DB[:entries].insert(:id=>3, :account_id=>nil)
50
51
  DB[:accounts].order(:id).select_map(:num_entries).must_equal [2, 0]
51
52
 
52
53
  DB[:entries].where(:id=>3).update(:account_id=>2)
@@ -87,12 +88,12 @@ describe "PostgreSQL Triggers" do
87
88
  end
88
89
 
89
90
  it "Should set the column upon insertion and ignore modifications afterward" do
90
- DB[:accounts] << {:id=>1}
91
+ DB[:accounts].insert(:id=>1)
91
92
  t = DB[:accounts].get(:added_on)
92
93
  t.strftime('%F').must_equal Date.today.strftime('%F')
93
94
  DB[:accounts].update(:added_on=>Date.today - 60)
94
95
  DB[:accounts].get(:added_on).must_equal t
95
- DB[:accounts] << {:id=>2}
96
+ DB[:accounts].insert(:id=>2)
96
97
  ds = DB[:accounts].select(:added_on)
97
98
  DB[:accounts].select((Sequel::SQL::NumericExpression.new(:NOOP, ds.filter(:id=>2)) > ds.filter(:id=>1)).as(:x)).first[:x].must_equal true
98
99
  DB[:accounts].filter(:id=>1).update(:id=>3)
@@ -104,7 +105,7 @@ describe "PostgreSQL Triggers" do
104
105
  before do
105
106
  DB.create_table(:accounts){integer :id; integer :balance, :default=>0}
106
107
  DB.pgt_immutable(:accounts, :balance, :function_name=>:spgt_immutable)
107
- DB[:accounts] << {:id=>1}
108
+ DB[:accounts].insert(:id=>1)
108
109
  end
109
110
 
110
111
  after do
@@ -128,7 +129,7 @@ describe "PostgreSQL Triggers" do
128
129
  it "Should handle NULL values correctly" do
129
130
  proc{DB[:accounts].update(:balance=>nil)}.must_raise(Sequel::DatabaseError)
130
131
  DB[:accounts].delete
131
- DB[:accounts] << {:id=>1, :balance=>nil}
132
+ DB[:accounts].insert(:id=>1, :balance=>nil)
132
133
  DB[:accounts].update(:balance=>nil)
133
134
  proc{DB[:accounts].update(:balance=>0)}.must_raise(Sequel::DatabaseError)
134
135
  end
@@ -139,8 +140,8 @@ describe "PostgreSQL Triggers" do
139
140
  DB.create_table(:accounts){integer :id; integer :balance, :default=>0}
140
141
  DB.create_table(:entries){integer :id; integer :account_id; integer :amount}
141
142
  DB.pgt_sum_cache(:accounts, :id, :balance, :entries, :account_id, :amount, :function_name=>:spgt_sum_cache)
142
- DB[:accounts] << {:id=>1}
143
- DB[:accounts] << {:id=>2}
143
+ DB[:accounts].insert(:id=>1)
144
+ DB[:accounts].insert(:id=>2)
144
145
  end
145
146
 
146
147
  after do
@@ -151,13 +152,13 @@ describe "PostgreSQL Triggers" do
151
152
  it "Should modify sum cache when adding, updating, or removing records" do
152
153
  DB[:accounts].order(:id).select_map(:balance).must_equal [0, 0]
153
154
 
154
- DB[:entries] << {:id=>1, :account_id=>1, :amount=>100}
155
+ DB[:entries].insert(:id=>1, :account_id=>1, :amount=>100)
155
156
  DB[:accounts].order(:id).select_map(:balance).must_equal [100, 0]
156
157
 
157
- DB[:entries] << {:id=>2, :account_id=>1, :amount=>200}
158
+ DB[:entries].insert(:id=>2, :account_id=>1, :amount=>200)
158
159
  DB[:accounts].order(:id).select_map(:balance).must_equal [300, 0]
159
160
 
160
- DB[:entries] << {:id=>3, :account_id=>nil, :amount=>500}
161
+ DB[:entries].insert(:id=>3, :account_id=>nil, :amount=>500)
161
162
  DB[:accounts].order(:id).select_map(:balance).must_equal [300, 0]
162
163
 
163
164
  DB[:entries].where(:id=>3).update(:account_id=>2)
@@ -194,8 +195,8 @@ describe "PostgreSQL Triggers" do
194
195
  DB.create_table(:accounts){integer :id; integer :nonzero_entries_count, :default=>0}
195
196
  DB.create_table(:entries){integer :id; integer :account_id; integer :amount}
196
197
  DB.pgt_sum_cache(:accounts, :id, :nonzero_entries_count, :entries, :account_id, Sequel.case({0=>0}, 1, :amount), :function_name=>:spgt_sum_cache)
197
- DB[:accounts] << {:id=>1}
198
- DB[:accounts] << {:id=>2}
198
+ DB[:accounts].insert(:id=>1)
199
+ DB[:accounts].insert(:id=>2)
199
200
  end
200
201
 
201
202
  after do
@@ -206,13 +207,13 @@ describe "PostgreSQL Triggers" do
206
207
  it "Should modify sum cache when adding, updating, or removing records" do
207
208
  DB[:accounts].order(:id).select_map(:nonzero_entries_count).must_equal [0, 0]
208
209
 
209
- DB[:entries] << {:id=>1, :account_id=>1, :amount=>100}
210
+ DB[:entries].insert(:id=>1, :account_id=>1, :amount=>100)
210
211
  DB[:accounts].order(:id).select_map(:nonzero_entries_count).must_equal [1, 0]
211
212
 
212
- DB[:entries] << {:id=>2, :account_id=>1, :amount=>200}
213
+ DB[:entries].insert(:id=>2, :account_id=>1, :amount=>200)
213
214
  DB[:accounts].order(:id).select_map(:nonzero_entries_count).must_equal [2, 0]
214
215
 
215
- DB[:entries] << {:id=>3, :account_id=>nil, :amount=>500}
216
+ DB[:entries].insert(:id=>3, :account_id=>nil, :amount=>500)
216
217
  DB[:accounts].order(:id).select_map(:nonzero_entries_count).must_equal [2, 0]
217
218
 
218
219
  DB[:entries].where(:id=>3).update(:account_id=>2)
@@ -261,8 +262,8 @@ describe "PostgreSQL Triggers" do
261
262
  :function_name=>:spgt_stm_cache,
262
263
  :join_function_name=>:spgt_stm_cache_join
263
264
  )
264
- DB[:parents] << {:id=>1}
265
- DB[:parents] << {:id=>2}
265
+ DB[:parents].insert(:id=>1)
266
+ DB[:parents].insert(:id=>2)
266
267
  end
267
268
 
268
269
  after do
@@ -274,23 +275,23 @@ describe "PostgreSQL Triggers" do
274
275
  it "Should modify sum cache when adding, updating, or removing records" do
275
276
  DB[:parents].order(:id).select_map(:balance).must_equal [0, 0]
276
277
 
277
- DB[:children] << {:id=>1, :amount=>100}
278
- DB[:links] << {:parent_id=>1, :child_id=>1}
278
+ DB[:children].insert(:id=>1, :amount=>100)
279
+ DB[:links].insert(:parent_id=>1, :child_id=>1)
279
280
  DB[:parents].order(:id).select_map(:balance).must_equal [100, 0]
280
281
 
281
- DB[:children] << {:id=>2, :amount=>200}
282
- DB[:links] << {:parent_id=>1, :child_id=>2}
282
+ DB[:children].insert(:id=>2, :amount=>200)
283
+ DB[:links].insert(:parent_id=>1, :child_id=>2)
283
284
  DB[:parents].order(:id).select_map(:balance).must_equal [300, 0]
284
285
 
285
- DB[:children] << {:id=>3, :amount=>500}
286
+ DB[:children].insert(:id=>3, :amount=>500)
286
287
  DB[:parents].order(:id).select_map(:balance).must_equal [300, 0]
287
- DB[:links] << {:parent_id=>2, :child_id=>3}
288
+ DB[:links].insert(:parent_id=>2, :child_id=>3)
288
289
  DB[:parents].order(:id).select_map(:balance).must_equal [300, 500]
289
290
 
290
291
  DB[:links].where(:parent_id=>2, :child_id=>3).update(:parent_id=>1)
291
292
  DB[:parents].order(:id).select_map(:balance).must_equal [800, 0]
292
293
 
293
- DB[:children] << {:id=>4, :amount=>400}
294
+ DB[:children].insert(:id=>4, :amount=>400)
294
295
  DB[:links].where(:parent_id=>1, :child_id=>3).update(:child_id=>4)
295
296
  DB[:parents].order(:id).select_map(:balance).must_equal [700, 0]
296
297
 
@@ -309,7 +310,7 @@ describe "PostgreSQL Triggers" do
309
310
  DB[:links].where(:parent_id=>1, :child_id=>2).update(:child_id=>3)
310
311
  DB[:parents].order(:id).select_map(:balance).must_equal [1200, 1000]
311
312
 
312
- DB[:links] << {:parent_id=>2, :child_id=>4}
313
+ DB[:links].insert(:parent_id=>2, :child_id=>4)
313
314
  DB[:parents].order(:id).select_map(:balance).must_equal [1200, 1800]
314
315
 
315
316
  DB[:children].filter(:id=>4).delete
@@ -318,7 +319,7 @@ describe "PostgreSQL Triggers" do
318
319
  DB[:links].filter(:parent_id=>1, :child_id=>1).delete
319
320
  DB[:parents].order(:id).select_map(:balance).must_equal [1000, 1000]
320
321
 
321
- DB[:children] << {:id=>4, :amount=>400}
322
+ DB[:children].insert(:id=>4, :amount=>400)
322
323
  DB[:parents].order(:id).select_map(:balance).must_equal [1000, 1400]
323
324
 
324
325
  DB[:children].delete
@@ -354,8 +355,8 @@ describe "PostgreSQL Triggers" do
354
355
  :function_name=>:spgt_stm_cache,
355
356
  :join_function_name=>:spgt_stm_cache_join
356
357
  )
357
- DB[:parents] << {:id=>1}
358
- DB[:parents] << {:id=>2}
358
+ DB[:parents].insert(:id=>1)
359
+ DB[:parents].insert(:id=>2)
359
360
  end
360
361
 
361
362
  after do
@@ -367,23 +368,23 @@ describe "PostgreSQL Triggers" do
367
368
  it "Should modify sum cache when adding, updating, or removing records" do
368
369
  DB[:parents].order(:id).select_map(:nonzero_entries_count).must_equal [0, 0]
369
370
 
370
- DB[:children] << {:id=>1, :amount=>100}
371
- DB[:links] << {:parent_id=>1, :child_id=>1}
371
+ DB[:children].insert(:id=>1, :amount=>100)
372
+ DB[:links].insert(:parent_id=>1, :child_id=>1)
372
373
  DB[:parents].order(:id).select_map(:nonzero_entries_count).must_equal [1, 0]
373
374
 
374
- DB[:children] << {:id=>2, :amount=>200}
375
- DB[:links] << {:parent_id=>1, :child_id=>2}
375
+ DB[:children].insert(:id=>2, :amount=>200)
376
+ DB[:links].insert(:parent_id=>1, :child_id=>2)
376
377
  DB[:parents].order(:id).select_map(:nonzero_entries_count).must_equal [2, 0]
377
378
 
378
- DB[:children] << {:id=>3, :amount=>500}
379
+ DB[:children].insert(:id=>3, :amount=>500)
379
380
  DB[:parents].order(:id).select_map(:nonzero_entries_count).must_equal [2, 0]
380
- DB[:links] << {:parent_id=>2, :child_id=>3}
381
+ DB[:links].insert(:parent_id=>2, :child_id=>3)
381
382
  DB[:parents].order(:id).select_map(:nonzero_entries_count).must_equal [2, 1]
382
383
 
383
384
  DB[:links].where(:parent_id=>2, :child_id=>3).update(:parent_id=>1)
384
385
  DB[:parents].order(:id).select_map(:nonzero_entries_count).must_equal [3, 0]
385
386
 
386
- DB[:children] << {:id=>4, :amount=>400}
387
+ DB[:children].insert(:id=>4, :amount=>400)
387
388
  DB[:links].where(:parent_id=>1, :child_id=>3).update(:child_id=>4)
388
389
  DB[:parents].order(:id).select_map(:nonzero_entries_count).must_equal [3, 0]
389
390
 
@@ -402,7 +403,7 @@ describe "PostgreSQL Triggers" do
402
403
  DB[:links].where(:parent_id=>1, :child_id=>2).update(:child_id=>3)
403
404
  DB[:parents].order(:id).select_map(:nonzero_entries_count).must_equal [2, 1]
404
405
 
405
- DB[:links] << {:parent_id=>2, :child_id=>4}
406
+ DB[:links].insert(:parent_id=>2, :child_id=>4)
406
407
  DB[:parents].order(:id).select_map(:nonzero_entries_count).must_equal [2, 2]
407
408
 
408
409
  DB[:children].filter(:id=>4).delete
@@ -411,7 +412,7 @@ describe "PostgreSQL Triggers" do
411
412
  DB[:links].filter(:parent_id=>1, :child_id=>1).delete
412
413
  DB[:parents].order(:id).select_map(:nonzero_entries_count).must_equal [1, 1]
413
414
 
414
- DB[:children] << {:id=>4, :amount=>400}
415
+ DB[:children].insert(:id=>4, :amount=>400)
415
416
  DB[:parents].order(:id).select_map(:nonzero_entries_count).must_equal [1, 2]
416
417
 
417
418
  DB[:children].delete
@@ -443,10 +444,10 @@ describe "PostgreSQL Triggers" do
443
444
  end
444
445
 
445
446
  it "Should set the column always to the current timestamp" do
446
- DB[:accounts] << {:id=>1}
447
+ DB[:accounts].insert(:id=>1)
447
448
  t = DB[:accounts].get(:changed_on)
448
449
  t.strftime('%F').must_equal Date.today.strftime('%F')
449
- DB[:accounts] << {:id=>2}
450
+ DB[:accounts].insert(:id=>2)
450
451
  ds = DB[:accounts].select(:changed_on)
451
452
  DB[:accounts].select((Sequel::SQL::NumericExpression.new(:NOOP, ds.filter(:id=>2)) > ds.filter(:id=>1)).as(:x)).first[:x].must_equal true
452
453
  DB[:accounts].filter(:id=>1).update(:id=>3)
@@ -470,9 +471,9 @@ describe "PostgreSQL Triggers" do
470
471
  DB.pgt_touch(:children, :parents, :changed_on, {:id1=>:parent_id1}, :function_name=>:spgt_touch)
471
472
  d = Date.today
472
473
  d30 = Date.today - 30
473
- DB[:parents] << {:id1=>1, :changed_on=>d30}
474
- DB[:parents] << {:id1=>2, :changed_on=>d30}
475
- DB[:children] << {:id=>1, :parent_id1=>1}
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)
476
477
  DB[:parents].order(:id1).select_map(:changed_on).map{|t| t.strftime('%F')}.must_equal [d.strftime('%F'), d30.strftime('%F')]
477
478
 
478
479
  DB[:parents].update(:changed_on=>d30)
@@ -500,7 +501,7 @@ describe "PostgreSQL Triggers" do
500
501
  DB[:parents].order(:id1).select_map(:changed_on).map{|t| t.strftime('%F')}.must_equal [d30.strftime('%F'), d.strftime('%F')]
501
502
 
502
503
  DB[:parents].update(:changed_on=>d30)
503
- DB[:children] << {:id=>2, :parent_id1=>nil}
504
+ DB[:children].insert(:id=>2, :parent_id1=>nil)
504
505
  DB[:parents].order(:id1).select_map(:changed_on).map{|t| t.strftime('%F')}.must_equal [d30.strftime('%F'), d30.strftime('%F')]
505
506
  DB[:children].where(:id=>2).delete
506
507
  DB[:parents].order(:id1).select_map(:changed_on).map{|t| t.strftime('%F')}.must_equal [d30.strftime('%F'), d30.strftime('%F')]
@@ -508,8 +509,8 @@ describe "PostgreSQL Triggers" do
508
509
 
509
510
  it "Should update the timestamp column of the related table when there is a composite foreign key" do
510
511
  DB.pgt_touch(:children, :parents, :changed_on, {:id1=>:parent_id1, :id2=>:parent_id2}, :function_name=>:spgt_touch)
511
- DB[:parents] << {:id1=>1, :id2=>2, :changed_on=>Date.today - 30}
512
- DB[:children] << {:id=>1, :parent_id1=>1, :parent_id2=>2}
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)
513
514
  DB[:parents].get(:changed_on).strftime('%F').must_equal Date.today.strftime('%F')
514
515
  DB[:parents].update(:changed_on=>Date.today - 30)
515
516
  DB[:children].update(:id=>2)
@@ -523,8 +524,8 @@ describe "PostgreSQL Triggers" do
523
524
  DB.pgt_touch(:children, :parents, :changed_on, {:id1=>:parent_id1}, :function_name=>:spgt_touch)
524
525
  @spgt_touch2 = true
525
526
  DB.pgt_touch(:parents, :children, :changed_on, {:id=>:child_id}, :function_name=>:spgt_touch2)
526
- DB[:parents] << {:id1=>1, :child_id=>1, :changed_on=>Date.today - 30}
527
- DB[:children] << {:id=>1, :parent_id1=>1, :changed_on=>Date.today - 30}
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)
528
529
  DB[:parents].get(:changed_on).strftime('%F').must_equal Date.today.strftime('%F')
529
530
  DB[:children].get(:changed_on).strftime('%F').must_equal Date.today.strftime('%F')
530
531
  time = DB[:parents].get(:changed_on)
@@ -542,11 +543,51 @@ describe "PostgreSQL Triggers" do
542
543
 
543
544
  it "Should update the timestamp on the related table if that timestamp is initially NULL" do
544
545
  DB.pgt_touch(:children, :parents, :changed_on, {:id1=>:parent_id1}, :function_name=>:spgt_touch)
545
- DB[:parents] << {:id1=>1, :changed_on=>nil}
546
- DB[:children] << {:id=>1, :parent_id1=>1}
546
+ DB[:parents].insert(:id1=>1, :changed_on=>nil)
547
+ DB[:children].insert(:id=>1, :parent_id1=>1)
547
548
  changed_on = DB[:parents].get(:changed_on)
548
549
  changed_on.wont_equal nil
549
550
  changed_on.strftime('%F').must_equal Date.today.strftime('%F')
550
551
  end
551
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
592
+ end
552
593
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sequel_postgresql_triggers
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.0
4
+ version: 1.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeremy Evans
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-05-08 00:00:00.000000000 Z
11
+ date: 2018-01-05 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sequel
@@ -63,7 +63,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
63
63
  version: '0'
64
64
  requirements: []
65
65
  rubyforge_project:
66
- rubygems_version: 2.6.11
66
+ rubygems_version: 2.7.3
67
67
  signing_key:
68
68
  specification_version: 4
69
69
  summary: Database enforced timestamps, immutable columns, counter/sum caches, and