pg_triggers 0.2.1 → 0.3.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
2
  SHA1:
3
- metadata.gz: 7cf39423a3020619969aa1b391430c208a7c4f51
4
- data.tar.gz: 3bd05b9d325a80182451ea7e48d854dc019f358b
3
+ metadata.gz: 5595adfac3ae67d7262d42108d3c0eb0c22f0fe2
4
+ data.tar.gz: ba76d00baf5284c9e1b87d49caae8cfd8b16e5d0
5
5
  SHA512:
6
- metadata.gz: 83754258a0258ad61666dc1e1efcda3139dd7b1a06b9d443a1b68748d34e60aa3fe7ac07a721292d210f29ed7d98933c2cfa42ebc7d79d85d82e3e10c0dff760
7
- data.tar.gz: ea9522861e095a447c9b3ce1352e26920a960ed4e9b6e7657bb76c81e6123289db1fc8e2c85e3bf66c3d0023818871bad000c7c5c3a2e7af4e284e6aec96b468
6
+ metadata.gz: fd7455f77c46996d2950859a56896d3a94afcef07cbab6756c554c1c543efb9a2d7ae6a2677e95c284ebef9752be038e4e16c1131afbee8459759b448e9d3615
7
+ data.tar.gz: b599f05a7f96e8954abe0c2a72e4860cb116c7d2c3b8ae725f37e0bbdd1f2cd03f7ef3ffba944c765fafa1c15f99cfdc2d91a23541a2f7c26e95cdd81a5819dd
data/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ ### 0.3.0 (2014-11-07)
2
+
3
+ * Add sum_cache trigger.
4
+
1
5
  ### 0.2.1 (2014-10-31)
2
6
 
3
7
  * Add :name option to counter_cache to support custom names for the function and trigger.
@@ -1,3 +1,3 @@
1
1
  module PgTriggers
2
- VERSION = '0.2.1'
2
+ VERSION = '0.3.0'
3
3
  end
data/lib/pg_triggers.rb CHANGED
@@ -53,6 +53,57 @@ module PgTriggers
53
53
  SQL
54
54
  end
55
55
 
56
+ def sum_cache(main_table, sum_column, summed_table, summed_column, relationship, options = {})
57
+ where = proc { |source| relationship.map{|k, v| "#{k} = #{source}.#{v}"}.join(' AND ') }
58
+ columns = relationship.values
59
+ changed = columns.map{|c| "((OLD.#{c} <> NEW.#{c}) OR (OLD.#{c} IS NULL <> NEW.#{c} IS NULL))"}.join(' OR ')
60
+ multiplier = (options[:multiplier] || 1).to_i
61
+ name = options[:name] || "pt_sc_#{main_table}_#{sum_column}"
62
+
63
+ condition = proc do |source|
64
+ a = []
65
+ a << columns.map{|c| "#{source}.#{c} IS NOT NULL"}.join(' AND ')
66
+ a << options[:where].gsub('ROW.', "#{source}.") if options[:where]
67
+ a.join(' AND ')
68
+ end
69
+
70
+ <<-SQL
71
+ CREATE OR REPLACE FUNCTION #{name}() RETURNS trigger
72
+ LANGUAGE plpgsql
73
+ AS $$
74
+ BEGIN
75
+ IF (TG_OP = 'INSERT') THEN
76
+ IF (#{condition['NEW']}) THEN
77
+ UPDATE #{main_table} SET #{sum_column} = #{sum_column} + (NEW.#{summed_column} * #{multiplier}) WHERE #{where['NEW']};
78
+ END IF;
79
+ RETURN NEW;
80
+ ELSIF (TG_OP = 'UPDATE') THEN
81
+ IF (#{changed}) OR ((#{condition['OLD']}) <> (#{condition['NEW']})) OR (OLD.#{summed_column} <> NEW.#{summed_column}) THEN
82
+ IF (#{condition['OLD']}) THEN
83
+ UPDATE #{main_table} SET #{sum_column} = #{sum_column} - (OLD.#{summed_column} * #{multiplier}) WHERE #{where['OLD']};
84
+ END IF;
85
+ IF (#{condition['NEW']}) THEN
86
+ UPDATE #{main_table} SET #{sum_column} = #{sum_column} + (NEW.#{summed_column} * #{multiplier}) WHERE #{where['NEW']};
87
+ END IF;
88
+ END IF;
89
+ RETURN NEW;
90
+ ELSIF (TG_OP = 'DELETE') THEN
91
+ IF (#{condition['OLD']}) THEN
92
+ UPDATE #{main_table} SET #{sum_column} = #{sum_column} - (OLD.#{summed_column} * #{multiplier}) WHERE #{where['OLD']};
93
+ END IF;
94
+ RETURN OLD;
95
+ END IF;
96
+ END;
97
+ $$;
98
+
99
+ DROP TRIGGER IF EXISTS #{name} ON #{summed_table};
100
+
101
+ CREATE TRIGGER #{name}
102
+ AFTER INSERT OR UPDATE OR DELETE ON #{summed_table}
103
+ FOR EACH ROW EXECUTE PROCEDURE #{name}();
104
+ SQL
105
+ end
106
+
56
107
  def updated_at(table, column)
57
108
  <<-SQL
58
109
  CREATE OR REPLACE FUNCTION pt_u_#{table}_#{column}() RETURNS trigger
@@ -0,0 +1,265 @@
1
+ require 'spec_helper'
2
+
3
+ describe PgTriggers, 'sum_cache' do
4
+ before do
5
+ DB.drop_table?(:summer_table)
6
+ DB.drop_table?(:summed_table)
7
+ end
8
+
9
+ it "should increment and decrement the count of related items in the associated table as rows are inserted and updated and deleted" do
10
+ DB.create_table :summer_table do
11
+ integer :id, null: false
12
+ integer :summer_sum, null: false, default: 0
13
+ end
14
+
15
+ DB.create_table :summed_table do
16
+ integer :id, null: false
17
+ integer :summer_id, null: false
18
+ integer :summed_count, null: false, default: 0
19
+ end
20
+
21
+ DB.run PgTriggers.sum_cache :summer_table, :summer_sum, :summed_table, :summed_count, {id: :summer_id}
22
+
23
+ DB[:summer_table].insert(id: 1)
24
+ DB[:summer_table].insert(id: 2)
25
+ DB[:summer_table].where(id: 1).get(:summer_sum).should == 0
26
+
27
+ DB[:summed_table].insert(id: 1, summer_id: 1)
28
+ DB[:summer_table].where(id: 1).get(:summer_sum).should == 0
29
+
30
+ DB[:summed_table].where(id: 1).update(:summed_count => 3).should == 1
31
+ DB[:summer_table].where(id: 1).get(:summer_sum).should == 3
32
+
33
+ DB[:summed_table].insert(id: 2, summer_id: 1)
34
+ DB[:summer_table].where(id: 1).get(:summer_sum).should == 3
35
+
36
+ DB[:summed_table].where(id: 2).update(:summed_count => 4).should == 1
37
+ DB[:summer_table].where(id: 1).get(:summer_sum).should == 7
38
+
39
+ DB[:summed_table].where(id: 1).delete.should == 1
40
+ DB[:summer_table].where(id: 1).get(:summer_sum).should == 4
41
+
42
+ DB[:summed_table].insert(id: 3, summer_id: 1, summed_count: 5)
43
+ DB[:summer_table].where(id: 1).get(:summer_sum).should == 9
44
+ end
45
+
46
+ it "should work when the tables are related by multiple columns" do
47
+ DB.create_table :summer_table do
48
+ integer :id1, null: false
49
+ integer :id2, null: false
50
+ integer :summer_sum, null: false, default: 0
51
+ end
52
+
53
+ DB.create_table :summed_table do
54
+ integer :id, null: false
55
+ integer :summer_id1, null: false
56
+ integer :summer_id2, null: false
57
+ integer :summed_count, null: false, default: 0
58
+ end
59
+
60
+ DB.run PgTriggers.sum_cache :summer_table, :summer_sum, :summed_table, :summed_count, {id1: :summer_id1, id2: :summer_id2}
61
+
62
+ DB[:summer_table].insert(id1: 1, id2: 1)
63
+ DB[:summer_table].insert(id1: 2, id2: 1)
64
+ DB[:summer_table].where(id1: 1).get(:summer_sum).should == 0
65
+
66
+ DB[:summed_table].insert(id: 1, summer_id1: 1, summer_id2: 1, summed_count: 2)
67
+ DB[:summer_table].where(id1: 1).get(:summer_sum).should == 2
68
+
69
+ DB[:summed_table].insert(id: 2, summer_id1: 1, summer_id2: 1, summed_count: 3)
70
+ DB[:summer_table].where(id1: 1).get(:summer_sum).should == 5
71
+
72
+ DB[:summed_table].insert(id: 3, summer_id1: 2, summer_id2: 1, summed_count: 4)
73
+ DB[:summer_table].where(id1: 1).get(:summer_sum).should == 5
74
+ DB[:summer_table].where(id1: 2).get(:summer_sum).should == 4
75
+
76
+ DB[:summed_table].insert(id: 4, summer_id1: 1, summer_id2: 1, summed_count: 5)
77
+ DB[:summer_table].where(id1: 1).get(:summer_sum).should == 10
78
+
79
+ DB[:summed_table].where(id: 4).update(summer_id1: 2).should == 1
80
+ DB[:summer_table].where(id1: 1).get(:summer_sum).should == 5
81
+ DB[:summer_table].where(id1: 2).get(:summer_sum).should == 9
82
+
83
+ DB[:summed_table].where(summer_id1: 1).delete.should == 2
84
+ DB[:summer_table].where(id1: 1).get(:summer_sum).should == 0
85
+ end
86
+
87
+ it "should work as expected when any of the columns are set to null or an unknown value" do
88
+ DB.create_table :summer_table do
89
+ integer :id1, null: false
90
+ integer :id2, null: false
91
+ integer :summed1_sum, null: false, default: 0
92
+ integer :summed2_sum, null: false, default: 0
93
+ end
94
+
95
+ DB.create_table :summed_table do
96
+ integer :id, null: false
97
+ integer :summer_id1
98
+ integer :summer_id2
99
+ integer :summed_count, null: false, default: 0
100
+ end
101
+
102
+ DB.run PgTriggers.sum_cache :summer_table, :summed1_sum, :summed_table, :summed_count, {id1: :summer_id1}
103
+ DB.run PgTriggers.sum_cache :summer_table, :summed2_sum, :summed_table, :summed_count, {id1: :summer_id1, id2: :summer_id2}
104
+
105
+ DB[:summer_table].insert(id1: 1, id2: 1)
106
+ DB[:summed_table].insert(id: 1, summer_id1: 1, summer_id2: 1, summed_count: 1)
107
+ DB[:summed_table].insert(id: 2, summer_id1: 1, summer_id2: 1, summed_count: 2)
108
+ DB[:summed_table].insert(id: 3, summer_id1: 1, summer_id2: 1, summed_count: 4)
109
+ DB[:summed_table].insert(id: 4, summer_id1: 1, summer_id2: 1, summed_count: 8)
110
+ DB[:summer_table].where(id1: 1, id2: 1).get([:summed1_sum, :summed2_sum]).should == [15, 15]
111
+
112
+ DB[:summed_table].where(id: 1).update(summer_id1: nil).should == 1
113
+ DB[:summer_table].where(id1: 1, id2: 1).get([:summed1_sum, :summed2_sum]).should == [14, 14]
114
+
115
+ DB[:summed_table].where(id: 2).update(summer_id2: nil).should == 1
116
+ DB[:summer_table].where(id1: 1, id2: 1).get([:summed1_sum, :summed2_sum]).should == [14, 12]
117
+
118
+ DB[:summed_table].where(id: 3).update(summer_id1: 90000000).should == 1
119
+ DB[:summer_table].where(id1: 1, id2: 1).get([:summed1_sum, :summed2_sum]).should == [10, 8]
120
+
121
+ DB[:summed_table].where(id: 4).update(summer_id2: 90000000).should == 1
122
+ DB[:summer_table].where(id1: 1, id2: 1).get([:summed1_sum, :summed2_sum]).should == [10, 0]
123
+
124
+ DB[:summed_table].insert(id: 5, summer_id1: 1, summer_id2: nil, summed_count: 16)
125
+ DB[:summer_table].where(id1: 1, id2: 1).get([:summed1_sum, :summed2_sum]).should == [26, 0]
126
+
127
+ DB[:summed_table].insert(id: 6, summer_id1: nil, summer_id2: nil, summed_count: 32)
128
+ DB[:summer_table].where(id1: 1, id2: 1).get([:summed1_sum, :summed2_sum]).should == [26, 0]
129
+ end
130
+
131
+ it "should accept a :where clause to filter the rows that are counted" do
132
+ DB.create_table :summer_table do
133
+ integer :id, null: false
134
+
135
+ integer :condition_sum, null: false, default: 0
136
+ integer :value_sum, null: false, default: 0
137
+ integer :condition_value_sum, null: false, default: 0
138
+ end
139
+
140
+ DB.create_table :summed_table do
141
+ integer :id, null: false
142
+ integer :summer_id, null: false
143
+ integer :summed_count, null: false, default: 0
144
+
145
+ boolean :condition
146
+ integer :value
147
+ end
148
+
149
+ DB.run PgTriggers.sum_cache :summer_table, :condition_sum, :summed_table, :summed_count, {id: :summer_id}, where: "ROW.condition"
150
+ DB.run PgTriggers.sum_cache :summer_table, :value_sum, :summed_table, :summed_count, {id: :summer_id}, where: "ROW.value > 5"
151
+ DB.run PgTriggers.sum_cache :summer_table, :condition_value_sum, :summed_table, :summed_count, {id: :summer_id}, where: "ROW.condition AND ROW.value > 5"
152
+
153
+ def values
154
+ DB[:summer_table].where(id: 1).get([:condition_sum, :value_sum, :condition_value_sum])
155
+ end
156
+
157
+ DB[:summer_table].insert(id: 1)
158
+
159
+ values.should == [0, 0, 0]
160
+ DB[:summed_table].insert(id: 1, summer_id: 1, condition: true, value: 4, summed_count: 1)
161
+ values.should == [1, 0, 0]
162
+ DB[:summed_table].insert(id: 2, summer_id: 1, condition: false, value: 4, summed_count: 2)
163
+ values.should == [1, 0, 0]
164
+ DB[:summed_table].insert(id: 3, summer_id: 1, condition: true, value: 6, summed_count: 4)
165
+ values.should == [5, 4, 4]
166
+ DB[:summed_table].insert(id: 4, summer_id: 1, condition: false, value: 6, summed_count: 8)
167
+ values.should == [5, 12, 4]
168
+ DB[:summed_table].insert(id: 5, summer_id: 1, condition: nil, value: 4, summed_count: 16)
169
+ values.should == [5, 12, 4]
170
+ DB[:summed_table].insert(id: 6, summer_id: 1, condition: false, value: nil, summed_count: 32)
171
+ values.should == [5, 12, 4]
172
+ DB[:summed_table].insert(id: 7, summer_id: 1, condition: true, value: nil, summed_count: 64)
173
+ values.should == [69, 12, 4]
174
+ DB[:summed_table].insert(id: 8, summer_id: 1, condition: nil, value: 6, summed_count: 128)
175
+ values.should == [69, 140, 4]
176
+
177
+ DB[:summed_table].where(id: 3).update(summer_id: 2).should == 1
178
+ values.should == [65, 136, 0]
179
+ DB[:summed_table].where(id: [4, 7]).delete.should == 2
180
+ values.should == [1, 128, 0]
181
+ DB[:summed_table].where(id: 3).update(summer_id: 1).should == 1
182
+ values.should == [5, 132, 4]
183
+ DB[:summed_table].where(id: 1).update(value: 6).should == 1
184
+ values.should == [5, 133, 5]
185
+ end
186
+
187
+ it "should silently replace another counter cache trigger on the same set of columns" do
188
+ DB.create_table :summer_table do
189
+ integer :id, null: false
190
+ integer :condition_sum, null: false, default: 0
191
+ end
192
+
193
+ DB.create_table :summed_table do
194
+ integer :id, null: false
195
+ integer :summer_id, null: false
196
+ integer :summed_count, null: false, default: 0
197
+ boolean :condition
198
+ end
199
+
200
+ def value
201
+ DB[:summer_table].where(id: 1).get(:condition_sum)
202
+ end
203
+
204
+ DB.run PgTriggers.sum_cache :summer_table, :condition_sum, :summed_table, :summed_count, {id: :summer_id}
205
+
206
+ DB[:summer_table].insert(id: 1)
207
+
208
+ DB[:summed_table].insert(id: 1, summer_id: 1, summed_count: 1, condition: true)
209
+ value.should == 1
210
+ DB[:summed_table].insert(id: 2, summer_id: 1, summed_count: 2, condition: false)
211
+ value.should == 3
212
+ DB[:summed_table].insert(id: 3, summer_id: 1, summed_count: 4, condition: true)
213
+ value.should == 7
214
+ DB[:summed_table].insert(id: 4, summer_id: 1, summed_count: 8, condition: false)
215
+ value.should == 15
216
+
217
+ DB.run PgTriggers.sum_cache :summer_table, :condition_sum, :summed_table, :summed_count, {id: :summer_id}, where: "ROW.condition"
218
+
219
+ DB[:summed_table].insert(id: 5, summer_id: 1, summed_count: 16, condition: nil)
220
+ value.should == 15
221
+ DB[:summed_table].insert(id: 6, summer_id: 1, summed_count: 32, condition: false)
222
+ value.should == 15
223
+ DB[:summed_table].insert(id: 7, summer_id: 1, summed_count: 64, condition: true)
224
+ value.should == 79
225
+ DB[:summed_table].insert(id: 8, summer_id: 1, summed_count: 128, condition: nil)
226
+ value.should == 79
227
+ end
228
+
229
+ it "should support a custom multiplier to apply to the sum" do
230
+ DB.create_table :summer_table do
231
+ integer :id, null: false
232
+ integer :summer_sum, null: false, default: 0
233
+ end
234
+
235
+ DB.create_table :summed_table do
236
+ integer :id, null: false
237
+ integer :summer_id, null: false
238
+ integer :summed_count, null: false, default: 0
239
+ end
240
+
241
+ DB.run PgTriggers.sum_cache :summer_table, :summer_sum, :summed_table, :summed_count, {id: :summer_id}, multiplier: 4
242
+
243
+ DB[:summer_table].insert(id: 1)
244
+ DB[:summer_table].insert(id: 2)
245
+ DB[:summer_table].where(id: 1).get(:summer_sum).should == 0
246
+
247
+ DB[:summed_table].insert(id: 1, summer_id: 1)
248
+ DB[:summer_table].where(id: 1).get(:summer_sum).should == 0
249
+
250
+ DB[:summed_table].where(id: 1).update(:summed_count => 3).should == 1
251
+ DB[:summer_table].where(id: 1).get(:summer_sum).should == 12
252
+
253
+ DB[:summed_table].insert(id: 2, summer_id: 1)
254
+ DB[:summer_table].where(id: 1).get(:summer_sum).should == 12
255
+
256
+ DB[:summed_table].where(id: 2).update(:summed_count => 4).should == 1
257
+ DB[:summer_table].where(id: 1).get(:summer_sum).should == 28
258
+
259
+ DB[:summed_table].where(id: 1).delete.should == 1
260
+ DB[:summer_table].where(id: 1).get(:summer_sum).should == 16
261
+
262
+ DB[:summed_table].insert(id: 3, summer_id: 1, summed_count: 5)
263
+ DB[:summer_table].where(id: 1).get(:summer_sum).should == 36
264
+ end
265
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pg_triggers
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.1
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chris Hanks
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-10-31 00:00:00.000000000 Z
11
+ date: 2014-11-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -113,6 +113,7 @@ files:
113
113
  - spec/audit_table_spec.rb
114
114
  - spec/counter_cache_spec.rb
115
115
  - spec/spec_helper.rb
116
+ - spec/sum_cache_spec.rb
116
117
  - spec/updated_at_spec.rb
117
118
  homepage: ''
118
119
  licenses:
@@ -142,4 +143,5 @@ test_files:
142
143
  - spec/audit_table_spec.rb
143
144
  - spec/counter_cache_spec.rb
144
145
  - spec/spec_helper.rb
146
+ - spec/sum_cache_spec.rb
145
147
  - spec/updated_at_spec.rb