pg_triggers 0.2.1 → 0.3.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 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