sequel_postgresql_triggers 1.0.3 → 1.0.4
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 +7 -0
- data/README +6 -0
- data/lib/sequel_postgresql_triggers.rb +29 -2
- data/spec/sequel_postgresql_triggers_spec.rb +210 -153
- metadata +41 -48
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 125a5f23b1a05c7b989bd9bac3b9e4569f85af55
|
4
|
+
data.tar.gz: 24a4466e9bb4c91ec25238d56a3027e3091c684d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 7507b30166920a252b7c583828bb41e0a55531d9a58a9fcbea7e5aca24daad079b80d2e8fe2f9e65d146cf3c720f6b62ec6a2c373e494939ea4d3e9685a73cf4
|
7
|
+
data.tar.gz: 390410edef60b9930572664d41e94e1356b4959f205ad7389067bbf7d92f6dada7a89b1b475c403abdc228dc4b997c7ec8e2277994289fff64d5f4679ae8defa
|
data/README
CHANGED
@@ -51,6 +51,12 @@ This takes a table name and one or more column names, and adds
|
|
51
51
|
an update trigger that raises an exception if you try to modify
|
52
52
|
the value of any of the columns.
|
53
53
|
|
54
|
+
=== Touch Propagation - pgt_touch
|
55
|
+
|
56
|
+
This takes several arguments (again, see the RDoc) and sets up a
|
57
|
+
trigger that watches one table for changes, and touches timestamps
|
58
|
+
of related rows in a separate table.
|
59
|
+
|
54
60
|
== License
|
55
61
|
|
56
62
|
This library is released under the MIT License. See the MIT-LICENSE
|
@@ -110,6 +110,33 @@ module Sequel
|
|
110
110
|
END;
|
111
111
|
SQL
|
112
112
|
end
|
113
|
+
|
114
|
+
# When rows in a table are updated, touches a timestamp of related rows
|
115
|
+
# in another table.
|
116
|
+
# Arguments:
|
117
|
+
# * main_table : name of table that is being watched for changes
|
118
|
+
# * touch_table : name of table that needs to be touched
|
119
|
+
# * column : name of timestamp column to be touched
|
120
|
+
# * expr : hash or array that represents the columns that define the relationship
|
121
|
+
# * opts : option hash, see module documentation
|
122
|
+
def pgt_touch(main_table, touch_table, column, expr, opts={})
|
123
|
+
trigger_name = opts[:trigger_name] || "pgt_t_#{main_table}__#{touch_table}"
|
124
|
+
function_name = opts[:function_name] || "pgt_t_#{main_table}__#{touch_table}"
|
125
|
+
cond = proc{|source| expr.map{|k,v| "#{quote_identifier(k)} = #{source}.#{quote_identifier(v)}"}.join(" AND ")}
|
126
|
+
sql = <<-SQL
|
127
|
+
BEGIN
|
128
|
+
IF (TG_OP = 'INSERT') THEN
|
129
|
+
UPDATE #{quote_schema_table(touch_table)} SET #{quote_identifier(column)} = CURRENT_TIMESTAMP WHERE #{cond['NEW']} AND #{quote_identifier(column)} <> CURRENT_TIMESTAMP;
|
130
|
+
ELSIF (TG_OP = 'UPDATE') THEN
|
131
|
+
UPDATE #{quote_schema_table(touch_table)} SET #{quote_identifier(column)} = CURRENT_TIMESTAMP WHERE #{cond['NEW']} AND #{quote_identifier(column)} <> CURRENT_TIMESTAMP;
|
132
|
+
ELSIF (TG_OP = 'DELETE') THEN
|
133
|
+
UPDATE #{quote_schema_table(touch_table)} SET #{quote_identifier(column)} = CURRENT_TIMESTAMP WHERE #{cond['OLD']} AND #{quote_identifier(column)} <> CURRENT_TIMESTAMP;
|
134
|
+
END IF;
|
135
|
+
RETURN NULL;
|
136
|
+
END;
|
137
|
+
SQL
|
138
|
+
pgt_trigger(main_table, trigger_name, function_name, [:insert, :delete, :update], sql, :after=>true)
|
139
|
+
end
|
113
140
|
|
114
141
|
# Turns a column in the table into a updated at timestamp column, which
|
115
142
|
# always contains the timestamp the record was inserted or last updated.
|
@@ -132,9 +159,9 @@ module Sequel
|
|
132
159
|
|
133
160
|
# Add or replace a function that returns trigger to handle the action,
|
134
161
|
# and add a trigger that calls the function.
|
135
|
-
def pgt_trigger(table, trigger_name, function_name, events, definition)
|
162
|
+
def pgt_trigger(table, trigger_name, function_name, events, definition, opts={})
|
136
163
|
create_function(function_name, definition, :language=>:plpgsql, :returns=>:trigger, :replace=>true)
|
137
|
-
create_trigger(table, trigger_name, function_name, :events=>events, :each_row=>true)
|
164
|
+
create_trigger(table, trigger_name, function_name, :events=>events, :each_row=>true, :after=>opts[:after])
|
138
165
|
end
|
139
166
|
end
|
140
167
|
end
|
@@ -7,161 +7,218 @@ DB = Sequel.connect(ENV['PGT_SPEC_DB']||'postgres:///spgt_test?user=postgres')
|
|
7
7
|
$:.unshift(File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), 'lib'))
|
8
8
|
require 'sequel_postgresql_triggers'
|
9
9
|
|
10
|
-
|
11
|
-
before do
|
12
|
-
DB.create_language(:plpgsql) if DB.server_version < 90000
|
13
|
-
end
|
14
|
-
after do
|
15
|
-
DB.drop_language(:plpgsql, :cascade=>true) if DB.server_version < 90000
|
16
|
-
end
|
17
|
-
|
18
|
-
context "PostgreSQL Counter Cache Trigger" do
|
10
|
+
describe "PostgreSQL Triggers" do
|
19
11
|
before do
|
20
|
-
DB.
|
21
|
-
DB.create_table(:entries){integer :id; integer :account_id}
|
22
|
-
DB.pgt_counter_cache(:accounts, :id, :num_entries, :entries, :account_id)
|
23
|
-
DB[:accounts] << {:id=>1}
|
24
|
-
DB[:accounts] << {:id=>2}
|
12
|
+
DB.create_language(:plpgsql) if DB.server_version < 90000
|
25
13
|
end
|
26
|
-
|
27
|
-
after do
|
28
|
-
DB.drop_table(:entries, :accounts)
|
29
|
-
end
|
30
|
-
|
31
|
-
specify "Should modify counter cache when adding or removing records" do
|
32
|
-
DB[:accounts].filter(:id=>1).get(:num_entries).should == 0
|
33
|
-
DB[:accounts].filter(:id=>2).get(:num_entries).should == 0
|
34
|
-
DB[:entries] << {:id=>1, :account_id=>1}
|
35
|
-
DB[:accounts].filter(:id=>1).get(:num_entries).should == 1
|
36
|
-
DB[:accounts].filter(:id=>2).get(:num_entries).should == 0
|
37
|
-
DB[:entries] << {:id=>2, :account_id=>1}
|
38
|
-
DB[:accounts].filter(:id=>1).get(:num_entries).should == 2
|
39
|
-
DB[:accounts].filter(:id=>2).get(:num_entries).should == 0
|
40
|
-
DB[:entries] << {:id=>3, :account_id=>2}
|
41
|
-
DB[:accounts].filter(:id=>1).get(:num_entries).should == 2
|
42
|
-
DB[:accounts].filter(:id=>2).get(:num_entries).should == 1
|
43
|
-
DB[:entries].filter(:id=>2).delete
|
44
|
-
DB[:accounts].filter(:id=>1).get(:num_entries).should == 1
|
45
|
-
DB[:accounts].filter(:id=>2).get(:num_entries).should == 1
|
46
|
-
DB[:entries].delete
|
47
|
-
DB[:accounts].filter(:id=>1).get(:num_entries).should == 0
|
48
|
-
DB[:accounts].filter(:id=>2).get(:num_entries).should == 0
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
context "PostgreSQL Created At Trigger" do
|
53
|
-
before do
|
54
|
-
DB.create_table(:accounts){integer :id; timestamp :added_on}
|
55
|
-
DB.pgt_created_at(:accounts, :added_on)
|
56
|
-
end
|
57
|
-
|
58
14
|
after do
|
59
|
-
DB.
|
15
|
+
DB.drop_language(:plpgsql, :cascade=>true) if DB.server_version < 90000
|
16
|
+
end
|
17
|
+
|
18
|
+
context "PostgreSQL Counter Cache Trigger" do
|
19
|
+
before do
|
20
|
+
DB.create_table(:accounts){integer :id; integer :num_entries, :default=>0}
|
21
|
+
DB.create_table(:entries){integer :id; integer :account_id}
|
22
|
+
DB.pgt_counter_cache(:accounts, :id, :num_entries, :entries, :account_id)
|
23
|
+
DB[:accounts] << {:id=>1}
|
24
|
+
DB[:accounts] << {:id=>2}
|
25
|
+
end
|
26
|
+
|
27
|
+
after do
|
28
|
+
DB.drop_table(:entries, :accounts)
|
29
|
+
end
|
30
|
+
|
31
|
+
specify "Should modify counter cache when adding or removing records" do
|
32
|
+
DB[:accounts].filter(:id=>1).get(:num_entries).should == 0
|
33
|
+
DB[:accounts].filter(:id=>2).get(:num_entries).should == 0
|
34
|
+
DB[:entries] << {:id=>1, :account_id=>1}
|
35
|
+
DB[:accounts].filter(:id=>1).get(:num_entries).should == 1
|
36
|
+
DB[:accounts].filter(:id=>2).get(:num_entries).should == 0
|
37
|
+
DB[:entries] << {:id=>2, :account_id=>1}
|
38
|
+
DB[:accounts].filter(:id=>1).get(:num_entries).should == 2
|
39
|
+
DB[:accounts].filter(:id=>2).get(:num_entries).should == 0
|
40
|
+
DB[:entries] << {:id=>3, :account_id=>2}
|
41
|
+
DB[:accounts].filter(:id=>1).get(:num_entries).should == 2
|
42
|
+
DB[:accounts].filter(:id=>2).get(:num_entries).should == 1
|
43
|
+
DB[:entries].filter(:id=>2).delete
|
44
|
+
DB[:accounts].filter(:id=>1).get(:num_entries).should == 1
|
45
|
+
DB[:accounts].filter(:id=>2).get(:num_entries).should == 1
|
46
|
+
DB[:entries].delete
|
47
|
+
DB[:accounts].filter(:id=>1).get(:num_entries).should == 0
|
48
|
+
DB[:accounts].filter(:id=>2).get(:num_entries).should == 0
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
context "PostgreSQL Created At Trigger" do
|
53
|
+
before do
|
54
|
+
DB.create_table(:accounts){integer :id; timestamp :added_on}
|
55
|
+
DB.pgt_created_at(:accounts, :added_on)
|
56
|
+
end
|
57
|
+
|
58
|
+
after do
|
59
|
+
DB.drop_table(:accounts)
|
60
|
+
end
|
61
|
+
|
62
|
+
specify "Should set the column upon insertion and ignore modifications afterward" do
|
63
|
+
DB[:accounts] << {:id=>1}
|
64
|
+
t = DB[:accounts].get(:added_on)
|
65
|
+
t.strftime('%F').should == Date.today.strftime('%F')
|
66
|
+
DB[:accounts].update(:added_on=>Date.today - 60)
|
67
|
+
DB[:accounts].get(:added_on).should == t
|
68
|
+
DB[:accounts] << {:id=>2}
|
69
|
+
ds = DB[:accounts].select(:added_on)
|
70
|
+
DB[:accounts].select((Sequel::SQL::NumericExpression.new(:NOOP, ds.filter(:id=>2)) > ds.filter(:id=>1)).as(:x)).first[:x].should == true
|
71
|
+
DB[:accounts].filter(:id=>1).update(:id=>3)
|
72
|
+
DB[:accounts].select((Sequel::SQL::NumericExpression.new(:NOOP, ds.filter(:id=>2)) > ds.filter(:id=>3)).as(:x)).first[:x].should == true
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
context "PostgreSQL Immutable Trigger" do
|
77
|
+
before do
|
78
|
+
DB.create_table(:accounts){integer :id; integer :balance, :default=>0}
|
79
|
+
DB.pgt_immutable(:accounts, :balance)
|
80
|
+
DB[:accounts] << {:id=>1}
|
81
|
+
end
|
82
|
+
|
83
|
+
after do
|
84
|
+
DB.drop_table(:accounts)
|
85
|
+
end
|
86
|
+
|
87
|
+
specify "Should allow modifying columns not marked as immutable" do
|
88
|
+
proc{DB[:accounts].update(:id=>2)}.should_not raise_error
|
89
|
+
end
|
90
|
+
|
91
|
+
specify "Should allow updating a column to its existing value" do
|
92
|
+
proc{DB[:accounts].update(:balance=>0)}.should_not raise_error
|
93
|
+
proc{DB[:accounts].update(:balance=>Sequel.*(:balance, :balance))}.should_not raise_error
|
94
|
+
end
|
95
|
+
|
96
|
+
specify "Should not allow modifying a column's value" do
|
97
|
+
proc{DB[:accounts].update(:balance=>1)}.should raise_error(Sequel::DatabaseError)
|
98
|
+
end
|
99
|
+
|
100
|
+
specify "Should handle NULL values correctly" do
|
101
|
+
proc{DB[:accounts].update(:balance=>nil)}.should raise_error(Sequel::DatabaseError)
|
102
|
+
DB[:accounts].delete
|
103
|
+
DB[:accounts] << {:id=>1, :balance=>nil}
|
104
|
+
proc{DB[:accounts].update(:balance=>nil)}.should_not raise_error
|
105
|
+
proc{DB[:accounts].update(:balance=>0)}.should raise_error(Sequel::DatabaseError)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
context "PostgreSQL Sum Cache Trigger" do
|
110
|
+
before do
|
111
|
+
DB.create_table(:accounts){integer :id; integer :balance, :default=>0}
|
112
|
+
DB.create_table(:entries){integer :id; integer :account_id; integer :amount}
|
113
|
+
DB.pgt_sum_cache(:accounts, :id, :balance, :entries, :account_id, :amount)
|
114
|
+
DB[:accounts] << {:id=>1}
|
115
|
+
DB[:accounts] << {:id=>2}
|
116
|
+
end
|
117
|
+
|
118
|
+
after do
|
119
|
+
DB.drop_table(:entries, :accounts)
|
120
|
+
end
|
121
|
+
|
122
|
+
specify "Should modify sum cache when adding, updating, or removing records" do
|
123
|
+
DB[:accounts].filter(:id=>1).get(:balance).should == 0
|
124
|
+
DB[:accounts].filter(:id=>2).get(:balance).should == 0
|
125
|
+
DB[:entries] << {:id=>1, :account_id=>1, :amount=>100}
|
126
|
+
DB[:accounts].filter(:id=>1).get(:balance).should == 100
|
127
|
+
DB[:accounts].filter(:id=>2).get(:balance).should == 0
|
128
|
+
DB[:entries] << {:id=>2, :account_id=>1, :amount=>200}
|
129
|
+
DB[:accounts].filter(:id=>1).get(:balance).should == 300
|
130
|
+
DB[:accounts].filter(:id=>2).get(:balance).should == 0
|
131
|
+
DB[:entries] << {:id=>3, :account_id=>2, :amount=>500}
|
132
|
+
DB[:accounts].filter(:id=>1).get(:balance).should == 300
|
133
|
+
DB[:accounts].filter(:id=>2).get(:balance).should == 500
|
134
|
+
DB[:entries].exclude(:id=>2).update(:amount=>Sequel.*(:amount, 2))
|
135
|
+
DB[:accounts].filter(:id=>1).get(:balance).should == 400
|
136
|
+
DB[:accounts].filter(:id=>2).get(:balance).should == 1000
|
137
|
+
DB[:entries].filter(:id=>2).delete
|
138
|
+
DB[:accounts].filter(:id=>1).get(:balance).should == 200
|
139
|
+
DB[:accounts].filter(:id=>2).get(:balance).should == 1000
|
140
|
+
DB[:entries].delete
|
141
|
+
DB[:accounts].filter(:id=>1).get(:balance).should == 0
|
142
|
+
DB[:accounts].filter(:id=>2).get(:balance).should == 0
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
context "PostgreSQL Updated At Trigger" do
|
147
|
+
before do
|
148
|
+
DB.create_table(:accounts){integer :id; timestamp :changed_on}
|
149
|
+
DB.pgt_updated_at(:accounts, :changed_on)
|
150
|
+
end
|
151
|
+
|
152
|
+
after do
|
153
|
+
DB.drop_table(:accounts)
|
154
|
+
end
|
155
|
+
|
156
|
+
specify "Should set the column always to the current timestamp" do
|
157
|
+
DB[:accounts] << {:id=>1}
|
158
|
+
t = DB[:accounts].get(:changed_on)
|
159
|
+
t.strftime('%F').should == Date.today.strftime('%F')
|
160
|
+
DB[:accounts] << {:id=>2}
|
161
|
+
ds = DB[:accounts].select(:changed_on)
|
162
|
+
DB[:accounts].select((Sequel::SQL::NumericExpression.new(:NOOP, ds.filter(:id=>2)) > ds.filter(:id=>1)).as(:x)).first[:x].should == true
|
163
|
+
DB[:accounts].filter(:id=>1).update(:id=>3)
|
164
|
+
DB[:accounts].select((Sequel::SQL::NumericExpression.new(:NOOP, ds.filter(:id=>3)) > ds.filter(:id=>2)).as(:x)).first[:x].should == true
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
context "PostgreSQL Touch Trigger" do
|
169
|
+
before do
|
170
|
+
DB.create_table(:parents){integer :id1; integer :id2; integer :child_id; timestamp :changed_on}
|
171
|
+
DB.create_table(:children){integer :id; integer :parent_id1; integer :parent_id2; timestamp :changed_on}
|
172
|
+
end
|
173
|
+
|
174
|
+
after do
|
175
|
+
DB.drop_table(:children, :parents)
|
176
|
+
end
|
177
|
+
|
178
|
+
specify "Should update the timestamp column of the related table when adding, updating or removing records" do
|
179
|
+
DB.pgt_touch(:children, :parents, :changed_on, :id1=>:parent_id1)
|
180
|
+
DB[:parents] << {:id1=>1, :changed_on=>Date.today - 30}
|
181
|
+
DB[:children] << {:id=>1, :parent_id1=>1}
|
182
|
+
DB[:parents].get(:changed_on).strftime('%F').should == Date.today.strftime('%F')
|
183
|
+
DB[:parents].update(:changed_on=>Date.today - 30)
|
184
|
+
DB[:children].update(:id=>2)
|
185
|
+
DB[:parents].get(:changed_on).strftime('%F').should == Date.today.strftime('%F')
|
186
|
+
DB[:parents].update(:changed_on=>Date.today - 30)
|
187
|
+
DB[:children].delete
|
188
|
+
DB[:parents].get(:changed_on).strftime('%F').should == Date.today.strftime('%F')
|
189
|
+
end
|
190
|
+
|
191
|
+
specify "Should update the timestamp column of the related table when there is a composite foreign key" do
|
192
|
+
DB.pgt_touch(:children, :parents, :changed_on, :id1=>:parent_id1, :id2=>:parent_id2)
|
193
|
+
DB[:parents] << {:id1=>1, :id2=>2, :changed_on=>Date.today - 30}
|
194
|
+
DB[:children] << {:id=>1, :parent_id1=>1, :parent_id2=>2}
|
195
|
+
DB[:parents].get(:changed_on).strftime('%F').should == Date.today.strftime('%F')
|
196
|
+
DB[:parents].update(:changed_on=>Date.today - 30)
|
197
|
+
DB[:children].update(:id=>2)
|
198
|
+
DB[:parents].get(:changed_on).strftime('%F').should == Date.today.strftime('%F')
|
199
|
+
DB[:parents].update(:changed_on=>Date.today - 30)
|
200
|
+
DB[:children].delete
|
201
|
+
DB[:parents].get(:changed_on).strftime('%F').should == Date.today.strftime('%F')
|
202
|
+
end
|
203
|
+
|
204
|
+
specify "Should update timestamps correctly when two tables touch each other" do
|
205
|
+
DB.pgt_touch(:children, :parents, :changed_on, :id1=>:parent_id1)
|
206
|
+
DB.pgt_touch(:parents, :children, :changed_on, :id=>:child_id)
|
207
|
+
DB[:parents] << {:id1=>1, :child_id=>1, :changed_on=>Date.today - 30}
|
208
|
+
DB[:children] << {:id=>1, :parent_id1=>1, :changed_on=>Date.today - 30}
|
209
|
+
DB[:parents].get(:changed_on).strftime('%F').should == Date.today.strftime('%F')
|
210
|
+
DB[:children].get(:changed_on).strftime('%F').should == Date.today.strftime('%F')
|
211
|
+
time = DB[:parents].get(:changed_on)
|
212
|
+
DB[:parents].update(:id2=>4)
|
213
|
+
DB[:parents].get(:changed_on).should > time
|
214
|
+
DB[:children].get(:changed_on).should > time
|
215
|
+
time = DB[:parents].get(:changed_on)
|
216
|
+
DB[:children].update(:id=>1)
|
217
|
+
DB[:parents].get(:changed_on).should > time
|
218
|
+
DB[:children].get(:changed_on).should > time
|
219
|
+
time = DB[:parents].get(:changed_on)
|
220
|
+
DB[:children].delete
|
221
|
+
DB[:parents].get(:changed_on).should > time
|
222
|
+
end
|
60
223
|
end
|
61
|
-
|
62
|
-
specify "Should set the column upon insertion and ignore modifications afterward" do
|
63
|
-
DB[:accounts] << {:id=>1}
|
64
|
-
t = DB[:accounts].get(:added_on)
|
65
|
-
t.strftime('%F').should == Date.today.strftime('%F')
|
66
|
-
DB[:accounts].update(:added_on=>Date.today - 60)
|
67
|
-
DB[:accounts].get(:added_on).should == t
|
68
|
-
DB[:accounts] << {:id=>2}
|
69
|
-
ds = DB[:accounts].select(:added_on)
|
70
|
-
DB[:accounts].select((Sequel::SQL::NumericExpression.new(:NOOP, ds.filter(:id=>2)) > ds.filter(:id=>1)).as(:x)).first[:x].should == true
|
71
|
-
DB[:accounts].filter(:id=>1).update(:id=>3)
|
72
|
-
DB[:accounts].select((Sequel::SQL::NumericExpression.new(:NOOP, ds.filter(:id=>2)) > ds.filter(:id=>3)).as(:x)).first[:x].should == true
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
|
-
context "PostgreSQL Immutable Trigger" do
|
77
|
-
before do
|
78
|
-
DB.create_table(:accounts){integer :id; integer :balance, :default=>0}
|
79
|
-
DB.pgt_immutable(:accounts, :balance)
|
80
|
-
DB[:accounts] << {:id=>1}
|
81
|
-
end
|
82
|
-
|
83
|
-
after do
|
84
|
-
DB.drop_table(:accounts)
|
85
|
-
end
|
86
|
-
|
87
|
-
specify "Should allow modifying columns not marked as immutable" do
|
88
|
-
proc{DB[:accounts].update(:id=>2)}.should_not raise_error
|
89
|
-
end
|
90
|
-
|
91
|
-
specify "Should allow updating a column to its existing value" do
|
92
|
-
proc{DB[:accounts].update(:balance=>0)}.should_not raise_error
|
93
|
-
proc{DB[:accounts].update(:balance=>:balance * :balance)}.should_not raise_error
|
94
|
-
end
|
95
|
-
|
96
|
-
specify "Should not allow modifying a column's value" do
|
97
|
-
proc{DB[:accounts].update(:balance=>1)}.should raise_error(Sequel::DatabaseError)
|
98
|
-
end
|
99
|
-
|
100
|
-
specify "Should handle NULL values correctly" do
|
101
|
-
proc{DB[:accounts].update(:balance=>nil)}.should raise_error(Sequel::DatabaseError)
|
102
|
-
DB[:accounts].delete
|
103
|
-
DB[:accounts] << {:id=>1, :balance=>nil}
|
104
|
-
proc{DB[:accounts].update(:balance=>nil)}.should_not raise_error
|
105
|
-
proc{DB[:accounts].update(:balance=>0)}.should raise_error(Sequel::DatabaseError)
|
106
|
-
end
|
107
|
-
end
|
108
|
-
|
109
|
-
context "PostgreSQL Sum Cache Trigger" do
|
110
|
-
before do
|
111
|
-
DB.create_table(:accounts){integer :id; integer :balance, :default=>0}
|
112
|
-
DB.create_table(:entries){integer :id; integer :account_id; integer :amount}
|
113
|
-
DB.pgt_sum_cache(:accounts, :id, :balance, :entries, :account_id, :amount)
|
114
|
-
DB[:accounts] << {:id=>1}
|
115
|
-
DB[:accounts] << {:id=>2}
|
116
|
-
end
|
117
|
-
|
118
|
-
after do
|
119
|
-
DB.drop_table(:entries, :accounts)
|
120
|
-
end
|
121
|
-
|
122
|
-
specify "Should modify sum cache when adding, updating, or removing records" do
|
123
|
-
DB[:accounts].filter(:id=>1).get(:balance).should == 0
|
124
|
-
DB[:accounts].filter(:id=>2).get(:balance).should == 0
|
125
|
-
DB[:entries] << {:id=>1, :account_id=>1, :amount=>100}
|
126
|
-
DB[:accounts].filter(:id=>1).get(:balance).should == 100
|
127
|
-
DB[:accounts].filter(:id=>2).get(:balance).should == 0
|
128
|
-
DB[:entries] << {:id=>2, :account_id=>1, :amount=>200}
|
129
|
-
DB[:accounts].filter(:id=>1).get(:balance).should == 300
|
130
|
-
DB[:accounts].filter(:id=>2).get(:balance).should == 0
|
131
|
-
DB[:entries] << {:id=>3, :account_id=>2, :amount=>500}
|
132
|
-
DB[:accounts].filter(:id=>1).get(:balance).should == 300
|
133
|
-
DB[:accounts].filter(:id=>2).get(:balance).should == 500
|
134
|
-
DB[:entries].exclude(:id=>2).update(:amount=>:amount * 2)
|
135
|
-
DB[:accounts].filter(:id=>1).get(:balance).should == 400
|
136
|
-
DB[:accounts].filter(:id=>2).get(:balance).should == 1000
|
137
|
-
DB[:entries].filter(:id=>2).delete
|
138
|
-
DB[:accounts].filter(:id=>1).get(:balance).should == 200
|
139
|
-
DB[:accounts].filter(:id=>2).get(:balance).should == 1000
|
140
|
-
DB[:entries].delete
|
141
|
-
DB[:accounts].filter(:id=>1).get(:balance).should == 0
|
142
|
-
DB[:accounts].filter(:id=>2).get(:balance).should == 0
|
143
|
-
end
|
144
|
-
end
|
145
|
-
|
146
|
-
context "PostgreSQL Updated At Trigger" do
|
147
|
-
before do
|
148
|
-
DB.create_table(:accounts){integer :id; timestamp :changed_on}
|
149
|
-
DB.pgt_updated_at(:accounts, :changed_on)
|
150
|
-
end
|
151
|
-
|
152
|
-
after do
|
153
|
-
DB.drop_table(:accounts)
|
154
|
-
end
|
155
|
-
|
156
|
-
specify "Should set the column always to the current timestamp" do
|
157
|
-
DB[:accounts] << {:id=>1}
|
158
|
-
t = DB[:accounts].get(:changed_on)
|
159
|
-
t.strftime('%F').should == Date.today.strftime('%F')
|
160
|
-
DB[:accounts] << {:id=>2}
|
161
|
-
ds = DB[:accounts].select(:changed_on)
|
162
|
-
DB[:accounts].select((Sequel::SQL::NumericExpression.new(:NOOP, ds.filter(:id=>2)) > ds.filter(:id=>1)).as(:x)).first[:x].should == true
|
163
|
-
DB[:accounts].filter(:id=>1).update(:id=>3)
|
164
|
-
DB[:accounts].select((Sequel::SQL::NumericExpression.new(:NOOP, ds.filter(:id=>3)) > ds.filter(:id=>2)).as(:x)).first[:x].should == true
|
165
|
-
end
|
166
|
-
end
|
167
224
|
end
|
metadata
CHANGED
@@ -1,76 +1,69 @@
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
2
|
name: sequel_postgresql_triggers
|
3
|
-
version: !ruby/object:Gem::Version
|
4
|
-
|
5
|
-
prerelease: false
|
6
|
-
segments:
|
7
|
-
- 1
|
8
|
-
- 0
|
9
|
-
- 3
|
10
|
-
version: 1.0.3
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.4
|
11
5
|
platform: ruby
|
12
|
-
authors:
|
6
|
+
authors:
|
13
7
|
- Jeremy Evans
|
14
8
|
autorequire:
|
15
9
|
bindir: bin
|
16
10
|
cert_chain: []
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
11
|
+
date: 2013-12-06 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: sequel
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - '>='
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - '>='
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
22
27
|
description:
|
23
28
|
email: code@jeremyevans.net
|
24
29
|
executables: []
|
25
|
-
|
26
30
|
extensions: []
|
27
|
-
|
28
31
|
extra_rdoc_files: []
|
29
|
-
|
30
|
-
files:
|
32
|
+
files:
|
31
33
|
- README
|
32
34
|
- MIT-LICENSE
|
33
35
|
- lib/sequel_postgresql_triggers.rb
|
34
36
|
- spec/sequel_postgresql_triggers_spec.rb
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
37
|
+
homepage: https://github.com/jeremyevans/sequel_postgresql_triggers
|
38
|
+
licenses:
|
39
|
+
- MIT
|
40
|
+
metadata: {}
|
39
41
|
post_install_message:
|
40
|
-
rdoc_options:
|
42
|
+
rdoc_options:
|
41
43
|
- --inline-source
|
42
44
|
- --line-numbers
|
43
45
|
- --title
|
44
|
-
-
|
46
|
+
- 'Sequel PostgreSQL Triggers: Database enforced timestamps, immutable columns, and
|
47
|
+
counter/sum caches'
|
45
48
|
- README
|
46
49
|
- MIT-LICENSE
|
47
50
|
- lib
|
48
|
-
require_paths:
|
51
|
+
require_paths:
|
49
52
|
- lib
|
50
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
none: false
|
61
|
-
requirements:
|
62
|
-
- - ">="
|
63
|
-
- !ruby/object:Gem::Version
|
64
|
-
hash: 3
|
65
|
-
segments:
|
66
|
-
- 0
|
67
|
-
version: "0"
|
53
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
54
|
+
requirements:
|
55
|
+
- - '>='
|
56
|
+
- !ruby/object:Gem::Version
|
57
|
+
version: '0'
|
58
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
59
|
+
requirements:
|
60
|
+
- - '>='
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
version: '0'
|
68
63
|
requirements: []
|
69
|
-
|
70
64
|
rubyforge_project:
|
71
|
-
rubygems_version:
|
65
|
+
rubygems_version: 2.0.3
|
72
66
|
signing_key:
|
73
|
-
specification_version:
|
67
|
+
specification_version: 4
|
74
68
|
summary: Database enforced timestamps, immutable columns, and counter/sum caches
|
75
69
|
test_files: []
|
76
|
-
|