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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 19f1fcd14466ad95643f91d2dc62934b7b6eef63
4
- data.tar.gz: eaaea66c3afaa575234c0fb2be06c3b3f265df0c
3
+ metadata.gz: 4c8e5d7162ea64f110cae96cb207f761a3a03f5f
4
+ data.tar.gz: 9a47381f9bba876a570abe52c81bb30e66d3182a
5
5
  SHA512:
6
- metadata.gz: f25370dae7dfffba8e21b20c7072b979942c7542b12857166d8473b5307bbc108e897b5726560c22166962182e2d0720503467a86694f8e3c16be0df42ede480
7
- data.tar.gz: 73b51af990c1a93f284ff1f1402378f5299e995ad39ae1452b7bbe22468b5cd772ac58f9fa4abe8fea9c6a232643ea4daaa7aa656fe85f6f09ae8c4d9e84ed74
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 pg_triggers_counter_#{main_table}_#{counter_column}() RETURNS trigger
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} + 1 WHERE #{where['NEW']};
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} - 1 WHERE #{where['OLD']};
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} + 1 WHERE #{where['NEW']};
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} - 1 WHERE #{where['OLD']};
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
- CREATE TRIGGER pg_triggers_counter_#{main_table}_#{counter_column}
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 pg_triggers_counter_#{main_table}_#{counter_column}();
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 pg_triggers_audit_#{table_name}() RETURNS TRIGGER
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 pg_triggers_audit_#{table_name} ON #{table_name};
132
+ DROP TRIGGER IF EXISTS pt_a_#{table_name} ON #{table_name};
104
133
 
105
- CREATE TRIGGER pg_triggers_audit_#{table_name}
134
+ CREATE TRIGGER pt_a_#{table_name}
106
135
  AFTER UPDATE OR DELETE ON #{table_name}
107
- FOR EACH ROW EXECUTE PROCEDURE pg_triggers_audit_#{table_name}();
136
+ FOR EACH ROW EXECUTE PROCEDURE pt_a_#{table_name}();
108
137
  SQL
109
138
  end
110
139
  end
@@ -1,3 +1,3 @@
1
1
  module PgTriggers
2
- VERSION = "0.1.0"
2
+ VERSION = '0.2.0'
3
3
  end
@@ -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.1.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-08-25 00:00:00.000000000 Z
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