pg_triggers 0.1.0 → 0.2.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/CHANGELOG.md +6 -0
- data/lib/pg_triggers.rb +42 -13
- data/lib/pg_triggers/version.rb +1 -1
- data/spec/counter_cache_spec.rb +69 -0
- data/spec/updated_at_spec.rb +79 -0
- metadata +4 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4c8e5d7162ea64f110cae96cb207f761a3a03f5f
|
4
|
+
data.tar.gz: 9a47381f9bba876a570abe52c81bb30e66d3182a
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6020076643bec765f6815d43fb0559120e8585f4cbc94dcaa23e86c641b835eea248e5112407168b2cbcc26168c32ec8fa6f4b9467c9395029e5f5257ce23f3f
|
7
|
+
data.tar.gz: 730068add4d00c160ce2fbc634a0c8e2b7ce93a57f8a67a3449fee746922cc220bb16a74c517058c982f6bc3eb2cedf2e89c48239c8db230a9f312d3baded8d6
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,9 @@
|
|
1
|
+
### 0.2.0 (2014-09-19)
|
2
|
+
|
3
|
+
* Add :value option to counter_cache to support incrementing/decrementing counters by a specific amount.
|
4
|
+
|
5
|
+
* Add updated_at trigger to track update times, while still allowing its values to be overridden for testing purposes.
|
6
|
+
|
1
7
|
### 0.1.0 (2014-08-25)
|
2
8
|
|
3
9
|
* Add audit_table trigger to track changes to a table.
|
data/lib/pg_triggers.rb
CHANGED
@@ -6,6 +6,7 @@ module PgTriggers
|
|
6
6
|
where = proc { |source| relationship.map{|k, v| "#{k} = #{source}.#{v}"}.join(' AND ') }
|
7
7
|
columns = relationship.values
|
8
8
|
changed = columns.map{|c| "((OLD.#{c} <> NEW.#{c}) OR (OLD.#{c} IS NULL <> NEW.#{c} IS NULL))"}.join(' OR ')
|
9
|
+
value = (options[:value] || 1).to_i
|
9
10
|
|
10
11
|
condition = proc do |source|
|
11
12
|
a = []
|
@@ -15,39 +16,67 @@ module PgTriggers
|
|
15
16
|
end
|
16
17
|
|
17
18
|
<<-SQL
|
18
|
-
CREATE FUNCTION
|
19
|
+
CREATE OR REPLACE FUNCTION pt_cc_#{main_table}_#{counter_column}() RETURNS trigger
|
19
20
|
LANGUAGE plpgsql
|
20
21
|
AS $$
|
21
22
|
BEGIN
|
22
23
|
IF (TG_OP = 'INSERT') THEN
|
23
24
|
IF (#{condition['NEW']}) THEN
|
24
|
-
UPDATE #{main_table} SET #{counter_column} = #{counter_column} +
|
25
|
+
UPDATE #{main_table} SET #{counter_column} = #{counter_column} + #{value} WHERE #{where['NEW']};
|
25
26
|
END IF;
|
26
27
|
RETURN NEW;
|
27
28
|
ELSIF (TG_OP = 'UPDATE') THEN
|
28
29
|
IF (#{changed}) OR ((#{condition['OLD']}) <> (#{condition['NEW']})) THEN
|
29
30
|
IF (#{condition['OLD']}) THEN
|
30
|
-
UPDATE #{main_table} SET #{counter_column} = #{counter_column} -
|
31
|
+
UPDATE #{main_table} SET #{counter_column} = #{counter_column} - #{value} WHERE #{where['OLD']};
|
31
32
|
END IF;
|
32
33
|
IF (#{condition['NEW']}) THEN
|
33
|
-
UPDATE #{main_table} SET #{counter_column} = #{counter_column} +
|
34
|
+
UPDATE #{main_table} SET #{counter_column} = #{counter_column} + #{value} WHERE #{where['NEW']};
|
34
35
|
END IF;
|
35
|
-
ELSE
|
36
|
-
|
37
36
|
END IF;
|
38
37
|
RETURN NEW;
|
39
38
|
ELSIF (TG_OP = 'DELETE') THEN
|
40
39
|
IF (#{condition['OLD']}) THEN
|
41
|
-
UPDATE #{main_table} SET #{counter_column} = #{counter_column} -
|
40
|
+
UPDATE #{main_table} SET #{counter_column} = #{counter_column} - #{value} WHERE #{where['OLD']};
|
42
41
|
END IF;
|
43
42
|
RETURN OLD;
|
44
43
|
END IF;
|
45
44
|
END;
|
46
45
|
$$;
|
47
46
|
|
48
|
-
|
47
|
+
DROP TRIGGER IF EXISTS pt_cc_#{main_table}_#{counter_column} ON #{counted_table};
|
48
|
+
|
49
|
+
CREATE TRIGGER pt_cc_#{main_table}_#{counter_column}
|
49
50
|
AFTER INSERT OR UPDATE OR DELETE ON #{counted_table}
|
50
|
-
FOR EACH ROW EXECUTE PROCEDURE
|
51
|
+
FOR EACH ROW EXECUTE PROCEDURE pt_cc_#{main_table}_#{counter_column}();
|
52
|
+
SQL
|
53
|
+
end
|
54
|
+
|
55
|
+
def updated_at(table, column)
|
56
|
+
<<-SQL
|
57
|
+
CREATE OR REPLACE FUNCTION pt_u_#{table}_#{column}() RETURNS trigger
|
58
|
+
LANGUAGE plpgsql
|
59
|
+
AS $$
|
60
|
+
BEGIN
|
61
|
+
IF (TG_OP = 'INSERT') THEN
|
62
|
+
IF NEW.updated_at IS NULL THEN
|
63
|
+
NEW.updated_at := CURRENT_TIMESTAMP;
|
64
|
+
END IF;
|
65
|
+
ELSIF (TG_OP = 'UPDATE') THEN
|
66
|
+
IF NEW.updated_at = OLD.updated_at THEN
|
67
|
+
NEW.updated_at := CURRENT_TIMESTAMP;
|
68
|
+
END IF;
|
69
|
+
END IF;
|
70
|
+
|
71
|
+
RETURN NEW;
|
72
|
+
END;
|
73
|
+
$$;
|
74
|
+
|
75
|
+
DROP TRIGGER IF EXISTS pt_u_#{table}_#{column} ON #{table};
|
76
|
+
|
77
|
+
CREATE TRIGGER pt_u_#{table}_#{column}
|
78
|
+
BEFORE INSERT OR UPDATE ON #{table}
|
79
|
+
FOR EACH ROW EXECUTE PROCEDURE pt_u_#{table}_#{column}();
|
51
80
|
SQL
|
52
81
|
end
|
53
82
|
|
@@ -67,7 +96,7 @@ module PgTriggers
|
|
67
96
|
ignore = options[:ignore].map{|a| "'#{a}'"}.join(', ') if options[:ignore]
|
68
97
|
|
69
98
|
<<-SQL
|
70
|
-
CREATE OR REPLACE FUNCTION
|
99
|
+
CREATE OR REPLACE FUNCTION pt_a_#{table_name}() RETURNS TRIGGER
|
71
100
|
AS $body$
|
72
101
|
DECLARE
|
73
102
|
changed_keys text[];
|
@@ -100,11 +129,11 @@ module PgTriggers
|
|
100
129
|
$body$
|
101
130
|
LANGUAGE plpgsql;
|
102
131
|
|
103
|
-
DROP TRIGGER IF EXISTS
|
132
|
+
DROP TRIGGER IF EXISTS pt_a_#{table_name} ON #{table_name};
|
104
133
|
|
105
|
-
CREATE TRIGGER
|
134
|
+
CREATE TRIGGER pt_a_#{table_name}
|
106
135
|
AFTER UPDATE OR DELETE ON #{table_name}
|
107
|
-
FOR EACH ROW EXECUTE PROCEDURE
|
136
|
+
FOR EACH ROW EXECUTE PROCEDURE pt_a_#{table_name}();
|
108
137
|
SQL
|
109
138
|
end
|
110
139
|
end
|
data/lib/pg_triggers/version.rb
CHANGED
data/spec/counter_cache_spec.rb
CHANGED
@@ -181,4 +181,73 @@ describe PgTriggers, 'counter_cache' do
|
|
181
181
|
DB[:counted_table].where(id: 1).update(value: 6).should == 1
|
182
182
|
values.should == [2, 3, 2]
|
183
183
|
end
|
184
|
+
|
185
|
+
it "should silently replace another counter cache trigger on the same set of columns" do
|
186
|
+
DB.create_table :counter_table do
|
187
|
+
integer :id, null: false
|
188
|
+
|
189
|
+
integer :condition_count, null: false, default: 0
|
190
|
+
end
|
191
|
+
|
192
|
+
DB.create_table :counted_table do
|
193
|
+
integer :id, null: false
|
194
|
+
integer :counter_id, null: false
|
195
|
+
|
196
|
+
boolean :condition
|
197
|
+
end
|
198
|
+
|
199
|
+
def value
|
200
|
+
DB[:counter_table].where(id: 1).get(:condition_count)
|
201
|
+
end
|
202
|
+
|
203
|
+
DB.run PgTriggers.counter_cache :counter_table, :condition_count, :counted_table, {id: :counter_id}
|
204
|
+
|
205
|
+
DB[:counter_table].insert(id: 1)
|
206
|
+
|
207
|
+
DB[:counted_table].insert(id: 1, counter_id: 1, condition: true)
|
208
|
+
value.should == 1
|
209
|
+
DB[:counted_table].insert(id: 2, counter_id: 1, condition: false)
|
210
|
+
value.should == 2
|
211
|
+
DB[:counted_table].insert(id: 3, counter_id: 1, condition: true)
|
212
|
+
value.should == 3
|
213
|
+
DB[:counted_table].insert(id: 4, counter_id: 1, condition: false)
|
214
|
+
value.should == 4
|
215
|
+
|
216
|
+
DB.run PgTriggers.counter_cache :counter_table, :condition_count, :counted_table, {id: :counter_id}, where: "ROW.condition"
|
217
|
+
|
218
|
+
DB[:counted_table].insert(id: 5, counter_id: 1, condition: nil)
|
219
|
+
value.should == 4
|
220
|
+
DB[:counted_table].insert(id: 6, counter_id: 1, condition: false)
|
221
|
+
value.should == 4
|
222
|
+
DB[:counted_table].insert(id: 7, counter_id: 1, condition: true)
|
223
|
+
value.should == 5
|
224
|
+
DB[:counted_table].insert(id: 8, counter_id: 1, condition: nil)
|
225
|
+
value.should == 5
|
226
|
+
end
|
227
|
+
|
228
|
+
it "should support a custom value to increment and decrement the count by" do
|
229
|
+
DB.create_table :counter_table do
|
230
|
+
integer :id, null: false
|
231
|
+
integer :counted_count, null: false, default: 0
|
232
|
+
end
|
233
|
+
|
234
|
+
DB.create_table :counted_table do
|
235
|
+
integer :id, null: false
|
236
|
+
integer :counter_id, null: false
|
237
|
+
end
|
238
|
+
|
239
|
+
DB.run PgTriggers.counter_cache :counter_table, :counted_count, :counted_table, {id: :counter_id}, value: 5
|
240
|
+
|
241
|
+
DB[:counter_table].insert(id: 1)
|
242
|
+
DB[:counter_table].where(id: 1).get(:counted_count).should == 0
|
243
|
+
|
244
|
+
DB[:counted_table].insert(id: 1, counter_id: 1)
|
245
|
+
DB[:counter_table].where(id: 1).get(:counted_count).should == 5
|
246
|
+
|
247
|
+
DB[:counted_table].insert(id: 2, counter_id: 1)
|
248
|
+
DB[:counter_table].where(id: 1).get(:counted_count).should == 10
|
249
|
+
|
250
|
+
DB[:counted_table].where(id: 1).delete.should == 1
|
251
|
+
DB[:counter_table].where(id: 1).get(:counted_count).should == 5
|
252
|
+
end
|
184
253
|
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe PgTriggers, 'updated_at' do
|
4
|
+
before do
|
5
|
+
DB.drop_table? :updated_at_table
|
6
|
+
|
7
|
+
DB.create_table :updated_at_table do
|
8
|
+
primary_key :id
|
9
|
+
|
10
|
+
integer :integer_column
|
11
|
+
|
12
|
+
timestamptz :updated_at
|
13
|
+
end
|
14
|
+
|
15
|
+
DB.run PgTriggers.updated_at :updated_at_table, :updated_at
|
16
|
+
end
|
17
|
+
|
18
|
+
it "should set the updated_at time to now() when the row is inserted" do
|
19
|
+
# The result of now() is the time the transaction began.
|
20
|
+
t = nil
|
21
|
+
DB.transaction do
|
22
|
+
t = DB.get{now{}}
|
23
|
+
DB[:updated_at_table].insert(integer_column: 1)
|
24
|
+
DB[:updated_at_table].get(:updated_at).should == t
|
25
|
+
end
|
26
|
+
DB[:updated_at_table].get(:updated_at).should == t
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should set the updated_at time to now() when the row is updated" do
|
30
|
+
# The result of now() is the time the transaction began.
|
31
|
+
t = nil
|
32
|
+
id = DB[:updated_at_table].insert(integer_column: 1)
|
33
|
+
DB.transaction do
|
34
|
+
t = DB.get{now{}}
|
35
|
+
DB[:updated_at_table].update integer_column: 2
|
36
|
+
DB[:updated_at_table].get(:updated_at).should == t
|
37
|
+
end
|
38
|
+
DB[:updated_at_table].get(:updated_at).should == t
|
39
|
+
end
|
40
|
+
|
41
|
+
it "should set the updated_at time to now() when the row is updated, even if no values in the row change" do
|
42
|
+
# The result of now() is the time the transaction began.
|
43
|
+
DB[:updated_at_table].insert(integer_column: 1)
|
44
|
+
t = nil
|
45
|
+
DB.transaction do
|
46
|
+
t = DB.get{now{}}
|
47
|
+
DB[:updated_at_table].update integer_column: 1
|
48
|
+
DB[:updated_at_table].get(:updated_at).should == t
|
49
|
+
end
|
50
|
+
DB[:updated_at_table].get(:updated_at).should == t
|
51
|
+
end
|
52
|
+
|
53
|
+
it "on insert should not overwrite a time the column is specifically set to" do
|
54
|
+
# Handle loss of timestamp precision in DB roundtrip.
|
55
|
+
t = DB.get Sequel.cast(Time.now - 30, :timestamptz)
|
56
|
+
|
57
|
+
DB.transaction do
|
58
|
+
DB[:updated_at_table].insert integer_column: 1, updated_at: t
|
59
|
+
DB[:updated_at_table].get(:updated_at).should == t
|
60
|
+
end
|
61
|
+
|
62
|
+
DB[:updated_at_table].get(:updated_at).should == t
|
63
|
+
end
|
64
|
+
|
65
|
+
it "on update should not overwrite a time the column is specifically set to" do
|
66
|
+
id = DB[:updated_at_table].insert(integer_column: 1)
|
67
|
+
DB[:updated_at_table].get(:updated_at)
|
68
|
+
|
69
|
+
# Handle loss of timestamp precision in DB roundtrip.
|
70
|
+
t = DB.get Sequel.cast(Time.now - 30, :timestamptz)
|
71
|
+
|
72
|
+
DB.transaction do
|
73
|
+
DB[:updated_at_table].update integer_column: 1, updated_at: t
|
74
|
+
DB[:updated_at_table].get(:updated_at).should == t
|
75
|
+
end
|
76
|
+
|
77
|
+
DB[:updated_at_table].get(:updated_at).should == t
|
78
|
+
end
|
79
|
+
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.
|
4
|
+
version: 0.2.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-
|
11
|
+
date: 2014-09-19 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/updated_at_spec.rb
|
116
117
|
homepage: ''
|
117
118
|
licenses:
|
118
119
|
- MIT
|
@@ -141,3 +142,4 @@ test_files:
|
|
141
142
|
- spec/audit_table_spec.rb
|
142
143
|
- spec/counter_cache_spec.rb
|
143
144
|
- spec/spec_helper.rb
|
145
|
+
- spec/updated_at_spec.rb
|