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 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