pg_triggers 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|