sequel_postgresql_triggers 1.1.0 → 1.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/README.rdoc +175 -0
- data/lib/sequel/extensions/pg_triggers.rb +225 -0
- data/lib/sequel_postgresql_triggers.rb +3 -284
- data/spec/sequel_postgresql_triggers_spec.rb +6 -11
- metadata +7 -5
- data/README +0 -75
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4d2e84d8e162f972268dc9839f8f744651ff15ca
|
4
|
+
data.tar.gz: 343a12a7c9531e694a92eeb1a037821095401bc2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 931984b649f29b90b738e00546c0c388fbea363136afc736cfcd69a0ff69b36d90ec5f429d2f5c3157b41b246eb9d9aaa13798f30c5eb12a205ed6f4bbe27c54
|
7
|
+
data.tar.gz: 7061eb0436fbe68db834aa130b503a834871bc2f554025fd851ce2411edd3bf964697d84a1feb01a979e40f644862b15ffd386d967a02849ca9b6c0ac45af61f
|
data/README.rdoc
ADDED
@@ -0,0 +1,175 @@
|
|
1
|
+
= Sequel PostgreSQL Triggers
|
2
|
+
|
3
|
+
Sequel PostgreSQL Triggers is a small enhancement to Sequel allowing
|
4
|
+
a user to easily handle the following types of columns:
|
5
|
+
|
6
|
+
* Timestamp Columns (Created At/Updated At)
|
7
|
+
* Counter/Sum Caches
|
8
|
+
* Immutable Columns
|
9
|
+
* Touch Propogation
|
10
|
+
|
11
|
+
It handles these internally to the database via triggers, so even if
|
12
|
+
other applications access the database (without using Sequel), things
|
13
|
+
will still work (unless the database superuser disables triggers).
|
14
|
+
|
15
|
+
To use this, load the +pg_triggers+ extension into the Sequel::Database
|
16
|
+
object:
|
17
|
+
|
18
|
+
DB.extension :pg_triggers
|
19
|
+
|
20
|
+
Then you can call the pgt_* methods it adds on your Sequel::Database
|
21
|
+
object:
|
22
|
+
|
23
|
+
DB.pgt_created_at(:table_name, :created_at)
|
24
|
+
|
25
|
+
Most commonly, this is used in migrations, with a structure similar
|
26
|
+
to:
|
27
|
+
|
28
|
+
Sequel.migration do
|
29
|
+
up do
|
30
|
+
extension :pg_triggers
|
31
|
+
|
32
|
+
pgt_created_at(:table_name,
|
33
|
+
:created_at,
|
34
|
+
:function_name=>:table_name_set_created_at,
|
35
|
+
:trigger_name=>:set_created_at)
|
36
|
+
end
|
37
|
+
|
38
|
+
down do
|
39
|
+
drop_trigger(:table_name, :set_created_at)
|
40
|
+
drop_function(:table_name_set_created_at)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
Note that you only need to load this extension when defining the
|
45
|
+
triggers, you don't need to load this extension when your
|
46
|
+
application is running.
|
47
|
+
|
48
|
+
To use any of these methods before PostgreSQL 9.0, you have to add
|
49
|
+
the plpgsql procedural language to PostgreSQL, which you can do with:
|
50
|
+
|
51
|
+
DB.create_language(:plpgsql)
|
52
|
+
|
53
|
+
If you want to load this extension globally for all PostgreSQL
|
54
|
+
databases, you can do:
|
55
|
+
|
56
|
+
require 'sequel_postgresql_triggers'
|
57
|
+
|
58
|
+
However, global modification is discouraged and only remains for
|
59
|
+
backwards compatibility.
|
60
|
+
|
61
|
+
== Triggers
|
62
|
+
|
63
|
+
All of the public methods this extension adds take the following options
|
64
|
+
in their opts hash:
|
65
|
+
|
66
|
+
:function_name :: The name of the function to use. This is important
|
67
|
+
to specify if you want an easy way to drop the function.
|
68
|
+
:trigger_name :: The name of the trigger to use. This is important
|
69
|
+
to specify if you want an easy way to drop the trigger.
|
70
|
+
|
71
|
+
=== Created At Columns - pgt_created_at
|
72
|
+
|
73
|
+
pgt_created_at takes the table and column given and makes it so that
|
74
|
+
upon insertion, the column is set to the CURRENT_TIMESTAMP, and that
|
75
|
+
upon update, the column's value is always set to the previous value.
|
76
|
+
This is sort of like an immutable column, but it doesn't bring up an
|
77
|
+
error if you try to change it, it just ignores it.
|
78
|
+
|
79
|
+
Arguments:
|
80
|
+
table :: name of table
|
81
|
+
column :: column in table that should be a created at timestamp column
|
82
|
+
opts :: option hash
|
83
|
+
|
84
|
+
=== Updated At Columns - pgt_updated_at
|
85
|
+
|
86
|
+
Similar to pgt_created_at, takes a table and column and makes it so
|
87
|
+
that upon insertion, the column is set to CURRENT_TIMESTAMP. It
|
88
|
+
differs that upon update, the column is also set to CURRENT_TIMESTAMP.
|
89
|
+
|
90
|
+
Arguments:
|
91
|
+
table :: name of table
|
92
|
+
column :: column in table that should be a updated at timestamp column
|
93
|
+
opts :: options hash
|
94
|
+
|
95
|
+
=== Counter Cache - pgt_counter_cache
|
96
|
+
|
97
|
+
This takes quite a few arguments (see the RDoc) and sets up a
|
98
|
+
counter cache so that when the counted table is inserted to
|
99
|
+
or deleted from, records in the main table are updated with the
|
100
|
+
count of the corresponding records in the counted table. The counter
|
101
|
+
cache column must have a default of 0 for this to work correctly.
|
102
|
+
|
103
|
+
Arguments:
|
104
|
+
main_table :: name of table holding counter cache column
|
105
|
+
main_table_id_column :: column in main table matching counted_table_id_column in counted_table
|
106
|
+
counter_column :: column in main table containing the counter cache
|
107
|
+
counted_table :: name of table being counted
|
108
|
+
counted_table_id_column :: column in counted_table matching main_table_id_column in main_table
|
109
|
+
opts :: options hash
|
110
|
+
|
111
|
+
=== Sum Cache - pgt_sum_cache
|
112
|
+
|
113
|
+
Similar to pgt_counter_cache, except instead of storing a count
|
114
|
+
of records in the main table, it stores the sum on one of the
|
115
|
+
columns in summed table. The sum cache column must have a default
|
116
|
+
of 0 for this to work correctly.
|
117
|
+
|
118
|
+
Arguments:
|
119
|
+
main_table :: name of table holding counter cache column
|
120
|
+
main_table_id_column :: column in main table matching counted_table_id_column in counted_table
|
121
|
+
sum_column :: column in main table containing the sum cache
|
122
|
+
summed_table :: name of table being summed
|
123
|
+
summed_table_id_column :: column in summed_table matching main_table_id_column in main_table
|
124
|
+
summed_column :: column in summed_table being summed
|
125
|
+
opts :: options hash
|
126
|
+
|
127
|
+
=== Sum Through Many Cache - pgt_sum_through_many_cache
|
128
|
+
|
129
|
+
Similar to pgt_sum_cache, except instead of a one-to-many relationship,
|
130
|
+
it supports a many-to-many relationship with a single join table. The
|
131
|
+
sum cache column must have a default of 0 for this to work correctly.
|
132
|
+
|
133
|
+
This takes a single options hash argument, supporting the following options
|
134
|
+
in addition to the standard options:
|
135
|
+
:main_table :: name of table holding sum cache column
|
136
|
+
:main_table_id_column :: primary key column in main table referenced by main_table_fk_column (default: :id)
|
137
|
+
:sum_column :: column in main table containing the sum cache, must be NOT NULL and default to 0
|
138
|
+
:summed_table :: name of table being summed
|
139
|
+
:summed_table_id_column :: primary key column in summed_table referenced by summed_table_fk_column (default: :id)
|
140
|
+
:summed_column :: column in summed_table being summed, must be NOT NULL
|
141
|
+
:join_table :: name of table which joins main_table with summed_table
|
142
|
+
:main_table_fk_column :: column in join_table referencing main_table_id_column, must be NOT NULL
|
143
|
+
:summed_table_fk_column :: column in join_table referencing summed_table_id_column, must be NOT NULL
|
144
|
+
|
145
|
+
=== Immutable Columns - pgt_immutable
|
146
|
+
|
147
|
+
This takes a table name and one or more column names, and adds
|
148
|
+
an update trigger that raises an exception if you try to modify
|
149
|
+
the value of any of the columns.
|
150
|
+
|
151
|
+
Arguments:
|
152
|
+
table :: name of table
|
153
|
+
*columns :: All columns in the table that should be immutable. Can end with options hash.
|
154
|
+
|
155
|
+
=== Touch Propagation - pgt_touch
|
156
|
+
|
157
|
+
This takes several arguments (again, see the RDoc) and sets up a
|
158
|
+
trigger that watches one table for changes, and touches timestamps
|
159
|
+
of related rows in a separate table.
|
160
|
+
|
161
|
+
Arguments:
|
162
|
+
main_table :: name of table that is being watched for changes
|
163
|
+
touch_table :: name of table that needs to be touched
|
164
|
+
column :: name of timestamp column to be touched
|
165
|
+
expr :: hash or array that represents the columns that define the relationship
|
166
|
+
opts :: options hash
|
167
|
+
|
168
|
+
== License
|
169
|
+
|
170
|
+
This library is released under the MIT License. See the MIT-LICENSE
|
171
|
+
file for details.
|
172
|
+
|
173
|
+
== Author
|
174
|
+
|
175
|
+
Jeremy Evans <code@jeremyevans.net>
|
@@ -0,0 +1,225 @@
|
|
1
|
+
# The pg_triggers extension adds support to the Database instance for easily
|
2
|
+
# creating triggers and trigger returning functions for common needs.
|
3
|
+
|
4
|
+
#
|
5
|
+
module Sequel
|
6
|
+
module Postgres
|
7
|
+
PGT_DEFINE = proc do
|
8
|
+
def pgt_counter_cache(main_table, main_table_id_column, counter_column, counted_table, counted_table_id_column, opts={})
|
9
|
+
trigger_name = opts[:trigger_name] || "pgt_cc_#{main_table}__#{main_table_id_column}__#{counter_column}__#{counted_table_id_column}"
|
10
|
+
function_name = opts[:function_name] || "pgt_cc_#{main_table}__#{main_table_id_column}__#{counter_column}__#{counted_table}__#{counted_table_id_column}"
|
11
|
+
|
12
|
+
table = quote_schema_table(main_table)
|
13
|
+
id_column = quote_identifier(counted_table_id_column)
|
14
|
+
main_column = quote_identifier(main_table_id_column)
|
15
|
+
count_column = quote_identifier(counter_column)
|
16
|
+
|
17
|
+
pgt_trigger(counted_table, trigger_name, function_name, [:insert, :update, :delete], <<-SQL)
|
18
|
+
BEGIN
|
19
|
+
IF (TG_OP = 'UPDATE' AND (NEW.#{id_column} = OLD.#{id_column} OR (OLD.#{id_column} IS NULL AND NEW.#{id_column} IS NULL))) THEN
|
20
|
+
RETURN NEW;
|
21
|
+
ELSE
|
22
|
+
IF ((TG_OP = 'INSERT' OR TG_OP = 'UPDATE') AND NEW.#{id_column} IS NOT NULL) THEN
|
23
|
+
UPDATE #{table} SET #{count_column} = #{count_column} + 1 WHERE #{main_column} = NEW.#{id_column};
|
24
|
+
END IF;
|
25
|
+
IF ((TG_OP = 'DELETE' OR TG_OP = 'UPDATE') AND OLD.#{id_column} IS NOT NULL) THEN
|
26
|
+
UPDATE #{table} SET #{count_column} = #{count_column} - 1 WHERE #{main_column} = OLD.#{id_column};
|
27
|
+
END IF;
|
28
|
+
END IF;
|
29
|
+
|
30
|
+
IF (TG_OP = 'DELETE') THEN
|
31
|
+
RETURN OLD;
|
32
|
+
END IF;
|
33
|
+
RETURN NEW;
|
34
|
+
END;
|
35
|
+
SQL
|
36
|
+
end
|
37
|
+
|
38
|
+
def pgt_created_at(table, column, opts={})
|
39
|
+
trigger_name = opts[:trigger_name] || "pgt_ca_#{column}"
|
40
|
+
function_name = opts[:function_name] || "pgt_ca_#{table}__#{column}"
|
41
|
+
col = quote_identifier(column)
|
42
|
+
pgt_trigger(table, trigger_name, function_name, [:insert, :update], <<-SQL)
|
43
|
+
BEGIN
|
44
|
+
IF (TG_OP = 'UPDATE') THEN
|
45
|
+
NEW.#{col} := OLD.#{col};
|
46
|
+
ELSIF (TG_OP = 'INSERT') THEN
|
47
|
+
NEW.#{col} := CURRENT_TIMESTAMP;
|
48
|
+
END IF;
|
49
|
+
RETURN NEW;
|
50
|
+
END;
|
51
|
+
SQL
|
52
|
+
end
|
53
|
+
|
54
|
+
def pgt_immutable(table, *columns)
|
55
|
+
opts = columns.last.is_a?(Hash) ? columns.pop : {}
|
56
|
+
trigger_name = opts[:trigger_name] || "pgt_im_#{columns.join('__')}"
|
57
|
+
function_name = opts[:function_name] || "pgt_im_#{columns.join('__')}"
|
58
|
+
ifs = columns.map do |c|
|
59
|
+
old = "OLD.#{quote_identifier(c)}"
|
60
|
+
new = "NEW.#{quote_identifier(c)}"
|
61
|
+
<<-END
|
62
|
+
IF #{new} IS DISTINCT FROM #{old} THEN
|
63
|
+
RAISE EXCEPTION 'Attempted #{c} update: Old: %, New: %', #{old}, #{new};
|
64
|
+
END IF;
|
65
|
+
END
|
66
|
+
end.join("\n")
|
67
|
+
pgt_trigger(table, trigger_name, function_name, :update, "BEGIN #{ifs} RETURN NEW; END;")
|
68
|
+
end
|
69
|
+
|
70
|
+
def pgt_sum_cache(main_table, main_table_id_column, sum_column, summed_table, summed_table_id_column, summed_column, opts={})
|
71
|
+
trigger_name = opts[:trigger_name] || "pgt_sc_#{main_table}__#{main_table_id_column}__#{sum_column}__#{summed_table_id_column}"
|
72
|
+
function_name = opts[:function_name] || "pgt_sc_#{main_table}__#{main_table_id_column}__#{sum_column}__#{summed_table}__#{summed_table_id_column}__#{summed_column}"
|
73
|
+
|
74
|
+
table = quote_schema_table(main_table)
|
75
|
+
id_column = quote_identifier(summed_table_id_column)
|
76
|
+
summed_column = quote_identifier(summed_column)
|
77
|
+
main_column = quote_identifier(main_table_id_column)
|
78
|
+
sum_column = quote_identifier(sum_column)
|
79
|
+
|
80
|
+
pgt_trigger(summed_table, trigger_name, function_name, [:insert, :delete, :update], <<-SQL)
|
81
|
+
BEGIN
|
82
|
+
IF (TG_OP = 'UPDATE' AND NEW.#{id_column} = OLD.#{id_column}) THEN
|
83
|
+
UPDATE #{table} SET #{sum_column} = #{sum_column} + NEW.#{summed_column} - OLD.#{summed_column} WHERE #{main_column} = NEW.#{id_column};
|
84
|
+
ELSE
|
85
|
+
IF ((TG_OP = 'INSERT' OR TG_OP = 'UPDATE') AND NEW.#{id_column} IS NOT NULL) THEN
|
86
|
+
UPDATE #{table} SET #{sum_column} = #{sum_column} + NEW.#{summed_column} WHERE #{main_column} = NEW.#{id_column};
|
87
|
+
END IF;
|
88
|
+
IF ((TG_OP = 'DELETE' OR TG_OP = 'UPDATE') AND OLD.#{id_column} IS NOT NULL) THEN
|
89
|
+
UPDATE #{table} SET #{sum_column} = #{sum_column} - OLD.#{summed_column} WHERE #{main_column} = OLD.#{id_column};
|
90
|
+
END IF;
|
91
|
+
END IF;
|
92
|
+
IF (TG_OP = 'DELETE') THEN
|
93
|
+
RETURN OLD;
|
94
|
+
END IF;
|
95
|
+
RETURN NEW;
|
96
|
+
END;
|
97
|
+
SQL
|
98
|
+
end
|
99
|
+
|
100
|
+
def pgt_sum_through_many_cache(opts={})
|
101
|
+
main_table = opts.fetch(:main_table)
|
102
|
+
main_table_id_column = opts.fetch(:main_table_id_column, :id)
|
103
|
+
sum_column = opts.fetch(:sum_column)
|
104
|
+
summed_table = opts.fetch(:summed_table)
|
105
|
+
summed_table_id_column = opts.fetch(:summed_table_id_column, :id)
|
106
|
+
summed_column = opts.fetch(:summed_column)
|
107
|
+
join_table = opts.fetch(:join_table)
|
108
|
+
main_table_fk_column = opts.fetch(:main_table_fk_column)
|
109
|
+
summed_table_fk_column = opts.fetch(:summed_table_fk_column)
|
110
|
+
|
111
|
+
trigger_name = opts[:trigger_name] || "pgt_stmc_#{main_table}__#{main_table_id_column}__#{sum_column}__#{summed_table_id_column}__#{summed_column}__#{main_table_fk_column}__#{summed_table_fk_column}"
|
112
|
+
function_name = opts[:function_name] || "pgt_stmc_#{main_table}__#{main_table_id_column}__#{sum_column}__#{summed_table}__#{summed_table_id_column}__#{summed_column}__#{join_table}__#{main_table_fk_column}__#{summed_table_fk_column}"
|
113
|
+
join_trigger_name = opts[:join_trigger_name] || "pgt_stmc_join_#{main_table}__#{main_table_id_column}__#{sum_column}__#{summed_table_id_column}__#{summed_column}__#{main_table_fk_column}__#{summed_table_fk_column}"
|
114
|
+
join_function_name = opts[:join_function_name] || "pgt_stmc_join_#{main_table}__#{main_table_id_column}__#{sum_column}__#{summed_table}__#{summed_table_id_column}__#{summed_column}__#{join_table}__#{main_table_fk_column}__#{summed_table_fk_column}"
|
115
|
+
|
116
|
+
orig_summed_table = summed_table
|
117
|
+
orig_join_table = join_table
|
118
|
+
|
119
|
+
main_table = quote_schema_table(main_table)
|
120
|
+
main_table_id_column = quote_schema_table(main_table_id_column)
|
121
|
+
sum_column = quote_schema_table(sum_column)
|
122
|
+
summed_table = quote_schema_table(summed_table)
|
123
|
+
summed_table_id_column = quote_schema_table(summed_table_id_column)
|
124
|
+
summed_column = quote_schema_table(summed_column)
|
125
|
+
join_table = quote_schema_table(join_table)
|
126
|
+
main_table_fk_column = quote_schema_table(main_table_fk_column)
|
127
|
+
summed_table_fk_column = quote_schema_table(summed_table_fk_column)
|
128
|
+
|
129
|
+
pgt_trigger(orig_summed_table, trigger_name, function_name, [:insert, :delete, :update], <<-SQL)
|
130
|
+
BEGIN
|
131
|
+
IF (TG_OP = 'UPDATE' AND NEW.#{summed_table_id_column} = OLD.#{summed_table_id_column}) THEN
|
132
|
+
UPDATE #{main_table} SET #{sum_column} = #{sum_column} + NEW.#{summed_column} - OLD.#{summed_column} WHERE #{main_table_id_column} IN (SELECT #{main_table_fk_column} FROM #{join_table} WHERE #{summed_table_fk_column} = NEW.#{summed_table_id_column});
|
133
|
+
ELSE
|
134
|
+
IF (TG_OP = 'INSERT' OR TG_OP = 'UPDATE') THEN
|
135
|
+
UPDATE #{main_table} SET #{sum_column} = #{sum_column} + NEW.#{summed_column} WHERE #{main_table_id_column} IN (SELECT #{main_table_fk_column} FROM #{join_table} WHERE #{summed_table_fk_column} = NEW.#{summed_table_id_column});
|
136
|
+
END IF;
|
137
|
+
IF (TG_OP = 'DELETE' OR TG_OP = 'UPDATE') THEN
|
138
|
+
UPDATE #{main_table} SET #{sum_column} = #{sum_column} - OLD.#{summed_column} WHERE #{main_table_id_column} IN (SELECT #{main_table_fk_column} FROM #{join_table} WHERE #{summed_table_fk_column} = OLD.#{summed_table_id_column});
|
139
|
+
END IF;
|
140
|
+
END IF;
|
141
|
+
IF (TG_OP = 'DELETE') THEN
|
142
|
+
RETURN OLD;
|
143
|
+
END IF;
|
144
|
+
RETURN NEW;
|
145
|
+
END;
|
146
|
+
SQL
|
147
|
+
|
148
|
+
pgt_trigger(orig_join_table, join_trigger_name, join_function_name, [:insert, :delete, :update], <<-SQL)
|
149
|
+
BEGIN
|
150
|
+
IF (NOT (TG_OP = 'UPDATE' AND NEW.#{main_table_fk_column} = OLD.#{main_table_fk_column} AND NEW.#{summed_table_fk_column} = OLD.#{summed_table_fk_column})) THEN
|
151
|
+
IF (TG_OP = 'INSERT' OR TG_OP = 'UPDATE') THEN
|
152
|
+
UPDATE #{main_table} SET #{sum_column} = #{sum_column} + (SELECT #{summed_column} FROM #{summed_table} WHERE #{summed_table_id_column} = NEW.#{summed_table_fk_column}) WHERE #{main_table_id_column} = NEW.#{main_table_fk_column};
|
153
|
+
END IF;
|
154
|
+
IF (TG_OP = 'DELETE' OR TG_OP = 'UPDATE') THEN
|
155
|
+
UPDATE #{main_table} SET #{sum_column} = #{sum_column} - (SELECT #{summed_column} FROM #{summed_table} WHERE #{summed_table_id_column} = OLD.#{summed_table_fk_column}) WHERE #{main_table_id_column} = OLD.#{main_table_fk_column};
|
156
|
+
END IF;
|
157
|
+
END IF;
|
158
|
+
IF (TG_OP = 'DELETE') THEN
|
159
|
+
RETURN OLD;
|
160
|
+
END IF;
|
161
|
+
RETURN NEW;
|
162
|
+
END;
|
163
|
+
SQL
|
164
|
+
end
|
165
|
+
|
166
|
+
def pgt_touch(main_table, touch_table, column, expr, opts={})
|
167
|
+
trigger_name = opts[:trigger_name] || "pgt_t_#{main_table}__#{touch_table}"
|
168
|
+
function_name = opts[:function_name] || "pgt_t_#{main_table}__#{touch_table}"
|
169
|
+
cond = lambda{|source| expr.map{|k,v| "#{quote_identifier(k)} = #{source}.#{quote_identifier(v)}"}.join(" AND ")}
|
170
|
+
same_id = expr.map{|k,v| "NEW.#{quote_identifier(v)} = OLD.#{quote_identifier(v)}"}.join(" AND ")
|
171
|
+
|
172
|
+
table = quote_schema_table(touch_table)
|
173
|
+
col = quote_identifier(column)
|
174
|
+
update = lambda{|source| " UPDATE #{table} SET #{col} = CURRENT_TIMESTAMP WHERE #{cond[source]} AND ((#{col} <> CURRENT_TIMESTAMP) OR (#{col} IS NULL));"}
|
175
|
+
|
176
|
+
sql = <<-SQL
|
177
|
+
BEGIN
|
178
|
+
IF (TG_OP = 'UPDATE' AND (#{same_id})) THEN
|
179
|
+
#{update['NEW']}
|
180
|
+
ELSE
|
181
|
+
IF (TG_OP = 'INSERT' OR TG_OP = 'UPDATE') THEN
|
182
|
+
#{update['NEW']}
|
183
|
+
END IF;
|
184
|
+
IF (TG_OP = 'DELETE' OR TG_OP = 'UPDATE') THEN
|
185
|
+
#{update['OLD']}
|
186
|
+
END IF;
|
187
|
+
END IF;
|
188
|
+
|
189
|
+
IF (TG_OP = 'DELETE') THEN
|
190
|
+
RETURN OLD;
|
191
|
+
END IF;
|
192
|
+
RETURN NEW;
|
193
|
+
END;
|
194
|
+
SQL
|
195
|
+
pgt_trigger(main_table, trigger_name, function_name, [:insert, :delete, :update], sql, :after=>true)
|
196
|
+
end
|
197
|
+
|
198
|
+
def pgt_updated_at(table, column, opts={})
|
199
|
+
trigger_name = opts[:trigger_name] || "pgt_ua_#{column}"
|
200
|
+
function_name = opts[:function_name] || "pgt_ua_#{table}__#{column}"
|
201
|
+
pgt_trigger(table, trigger_name, function_name, [:insert, :update], <<-SQL)
|
202
|
+
BEGIN
|
203
|
+
NEW.#{quote_identifier(column)} := CURRENT_TIMESTAMP;
|
204
|
+
RETURN NEW;
|
205
|
+
END;
|
206
|
+
SQL
|
207
|
+
end
|
208
|
+
|
209
|
+
private
|
210
|
+
|
211
|
+
# Add or replace a function that returns trigger to handle the action,
|
212
|
+
# and add a trigger that calls the function.
|
213
|
+
def pgt_trigger(table, trigger_name, function_name, events, definition, opts={})
|
214
|
+
create_function(function_name, definition, :language=>:plpgsql, :returns=>:trigger, :replace=>true)
|
215
|
+
create_trigger(table, trigger_name, function_name, :events=>events, :each_row=>true, :after=>opts[:after])
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
module PGTMethods
|
220
|
+
class_eval(&PGT_DEFINE)
|
221
|
+
end
|
222
|
+
end
|
223
|
+
|
224
|
+
Database.register_extension(:pg_triggers, Postgres::PGTMethods)
|
225
|
+
end
|
@@ -1,290 +1,9 @@
|
|
1
|
+
require 'sequel/extensions/pg_triggers'
|
2
|
+
|
1
3
|
module Sequel
|
2
4
|
module Postgres
|
3
|
-
# Add the pgt_* methods so that any Sequel database connecting to PostgreSQL
|
4
|
-
# can use them. All of these methods require the plpgsql procedural language
|
5
|
-
# be added to the PostgreSQL database before they can be used. On PostgreSQL
|
6
|
-
# 9.0 and later versions, it is installed by default. For older versions,
|
7
|
-
# you can install it with:
|
8
|
-
#
|
9
|
-
# DB.create_language(:plpgsql)
|
10
|
-
#
|
11
|
-
# All of the public methods take the following options in their opts hash:
|
12
|
-
#
|
13
|
-
# * :function_name: The name of the function to use. This is important
|
14
|
-
# to specify if you want an easy way to drop the function.
|
15
|
-
# * :trigger_name: The name of the trigger to use. This is important
|
16
|
-
# to specify if you want an easy way to drop the trigger.
|
17
5
|
module DatabaseMethods
|
18
|
-
|
19
|
-
# column in the main table with the number of rows in the counted table
|
20
|
-
# for the matching id. Arguments:
|
21
|
-
# * main_table : name of table holding counter cache column
|
22
|
-
# * main_table_id_column : column in main table matching counted_table_id_column in counted_table
|
23
|
-
# * counter_column : column in main table containing the counter cache
|
24
|
-
# * counted_table : name of table being counted
|
25
|
-
# * counted_table_id_column : column in counted_table matching main_table_id_column in main_table
|
26
|
-
# * opts : option hash, see module documentation
|
27
|
-
def pgt_counter_cache(main_table, main_table_id_column, counter_column, counted_table, counted_table_id_column, opts={})
|
28
|
-
trigger_name = opts[:trigger_name] || "pgt_cc_#{main_table}__#{main_table_id_column}__#{counter_column}__#{counted_table_id_column}"
|
29
|
-
function_name = opts[:function_name] || "pgt_cc_#{main_table}__#{main_table_id_column}__#{counter_column}__#{counted_table}__#{counted_table_id_column}"
|
30
|
-
|
31
|
-
table = quote_schema_table(main_table)
|
32
|
-
id_column = quote_identifier(counted_table_id_column)
|
33
|
-
main_column = quote_identifier(main_table_id_column)
|
34
|
-
count_column = quote_identifier(counter_column)
|
35
|
-
|
36
|
-
pgt_trigger(counted_table, trigger_name, function_name, [:insert, :update, :delete], <<-SQL)
|
37
|
-
BEGIN
|
38
|
-
IF (TG_OP = 'UPDATE' AND (NEW.#{id_column} = OLD.#{id_column} OR (OLD.#{id_column} IS NULL AND NEW.#{id_column} IS NULL))) THEN
|
39
|
-
RETURN NEW;
|
40
|
-
ELSE
|
41
|
-
IF ((TG_OP = 'INSERT' OR TG_OP = 'UPDATE') AND NEW.#{id_column} IS NOT NULL) THEN
|
42
|
-
UPDATE #{table} SET #{count_column} = #{count_column} + 1 WHERE #{main_column} = NEW.#{id_column};
|
43
|
-
END IF;
|
44
|
-
IF ((TG_OP = 'DELETE' OR TG_OP = 'UPDATE') AND OLD.#{id_column} IS NOT NULL) THEN
|
45
|
-
UPDATE #{table} SET #{count_column} = #{count_column} - 1 WHERE #{main_column} = OLD.#{id_column};
|
46
|
-
END IF;
|
47
|
-
END IF;
|
48
|
-
|
49
|
-
IF (TG_OP = 'DELETE') THEN
|
50
|
-
RETURN OLD;
|
51
|
-
END IF;
|
52
|
-
RETURN NEW;
|
53
|
-
END;
|
54
|
-
SQL
|
55
|
-
end
|
56
|
-
|
57
|
-
# Turns a column in the table into a created at timestamp column, which
|
58
|
-
# always contains the timestamp the record was inserted into the database.
|
59
|
-
# Arguments:
|
60
|
-
# * table : name of table
|
61
|
-
# * column : column in table that should be a created at timestamp column
|
62
|
-
# * opts : option hash, see module documentation
|
63
|
-
def pgt_created_at(table, column, opts={})
|
64
|
-
trigger_name = opts[:trigger_name] || "pgt_ca_#{column}"
|
65
|
-
function_name = opts[:function_name] || "pgt_ca_#{table}__#{column}"
|
66
|
-
col = quote_identifier(column)
|
67
|
-
pgt_trigger(table, trigger_name, function_name, [:insert, :update], <<-SQL)
|
68
|
-
BEGIN
|
69
|
-
IF (TG_OP = 'UPDATE') THEN
|
70
|
-
NEW.#{col} := OLD.#{col};
|
71
|
-
ELSIF (TG_OP = 'INSERT') THEN
|
72
|
-
NEW.#{col} := CURRENT_TIMESTAMP;
|
73
|
-
END IF;
|
74
|
-
RETURN NEW;
|
75
|
-
END;
|
76
|
-
SQL
|
77
|
-
end
|
78
|
-
|
79
|
-
# Makes all given columns in the given table immutable, so an exception
|
80
|
-
# is raised if there is an attempt to modify the value when updating the
|
81
|
-
# record. Arguments:
|
82
|
-
# * table : name of table
|
83
|
-
# * columns : All columns in the table that should be immutable. Can end with a hash of options, see module documentation.
|
84
|
-
def pgt_immutable(table, *columns)
|
85
|
-
opts = columns.last.is_a?(Hash) ? columns.pop : {}
|
86
|
-
trigger_name = opts[:trigger_name] || "pgt_im_#{columns.join('__')}"
|
87
|
-
function_name = opts[:function_name] || "pgt_im_#{columns.join('__')}"
|
88
|
-
ifs = columns.map do |c|
|
89
|
-
old = "OLD.#{quote_identifier(c)}"
|
90
|
-
new = "NEW.#{quote_identifier(c)}"
|
91
|
-
<<-END
|
92
|
-
IF #{new} IS DISTINCT FROM #{old} THEN
|
93
|
-
RAISE EXCEPTION 'Attempted #{c} update: Old: %, New: %', #{old}, #{new};
|
94
|
-
END IF;
|
95
|
-
END
|
96
|
-
end.join("\n")
|
97
|
-
pgt_trigger(table, trigger_name, function_name, :update, "BEGIN #{ifs} RETURN NEW; END;")
|
98
|
-
end
|
99
|
-
|
100
|
-
# Turns a column in the main table into a sum cache. A sum cache is a
|
101
|
-
# column in the main table with the sum of a column in the summed table
|
102
|
-
# for the matching id. Arguments:
|
103
|
-
# * main_table : name of table holding counter cache column
|
104
|
-
# * main_table_id_column : column in main table matching counted_table_id_column in counted_table
|
105
|
-
# * sum_column : column in main table containing the sum cache
|
106
|
-
# * summed_table : name of table being summed
|
107
|
-
# * summed_table_id_column : column in summed_table matching main_table_id_column in main_table
|
108
|
-
# * summed_column : column in summed_table being summed
|
109
|
-
# * opts : option hash, see module documentation
|
110
|
-
def pgt_sum_cache(main_table, main_table_id_column, sum_column, summed_table, summed_table_id_column, summed_column, opts={})
|
111
|
-
trigger_name = opts[:trigger_name] || "pgt_sc_#{main_table}__#{main_table_id_column}__#{sum_column}__#{summed_table_id_column}"
|
112
|
-
function_name = opts[:function_name] || "pgt_sc_#{main_table}__#{main_table_id_column}__#{sum_column}__#{summed_table}__#{summed_table_id_column}__#{summed_column}"
|
113
|
-
|
114
|
-
table = quote_schema_table(main_table)
|
115
|
-
id_column = quote_identifier(summed_table_id_column)
|
116
|
-
summed_column = quote_identifier(summed_column)
|
117
|
-
main_column = quote_identifier(main_table_id_column)
|
118
|
-
sum_column = quote_identifier(sum_column)
|
119
|
-
|
120
|
-
pgt_trigger(summed_table, trigger_name, function_name, [:insert, :delete, :update], <<-SQL)
|
121
|
-
BEGIN
|
122
|
-
IF (TG_OP = 'UPDATE' AND NEW.#{id_column} = OLD.#{id_column}) THEN
|
123
|
-
UPDATE #{table} SET #{sum_column} = #{sum_column} + NEW.#{summed_column} - OLD.#{summed_column} WHERE #{main_column} = NEW.#{id_column};
|
124
|
-
ELSE
|
125
|
-
IF ((TG_OP = 'INSERT' OR TG_OP = 'UPDATE') AND NEW.#{id_column} IS NOT NULL) THEN
|
126
|
-
UPDATE #{table} SET #{sum_column} = #{sum_column} + NEW.#{summed_column} WHERE #{main_column} = NEW.#{id_column};
|
127
|
-
END IF;
|
128
|
-
IF ((TG_OP = 'DELETE' OR TG_OP = 'UPDATE') AND OLD.#{id_column} IS NOT NULL) THEN
|
129
|
-
UPDATE #{table} SET #{sum_column} = #{sum_column} - OLD.#{summed_column} WHERE #{main_column} = OLD.#{id_column};
|
130
|
-
END IF;
|
131
|
-
END IF;
|
132
|
-
IF (TG_OP = 'DELETE') THEN
|
133
|
-
RETURN OLD;
|
134
|
-
END IF;
|
135
|
-
RETURN NEW;
|
136
|
-
END;
|
137
|
-
SQL
|
138
|
-
end
|
139
|
-
|
140
|
-
# Turns a column in the main table into a sum cache through a join table.
|
141
|
-
# A sum cache is a column in the main table with the sum of a column in the
|
142
|
-
# summed table for the matching id. The join table must have NOT NULL constraints
|
143
|
-
# on the foreign keys to the main table and summed table and a
|
144
|
-
# composite unique constraint on both foreign keys.
|
145
|
-
#
|
146
|
-
# Arguments:
|
147
|
-
# * opts : option hash, see module documentation, and below.
|
148
|
-
# * :main_table: name of table holding sum cache column
|
149
|
-
# * :main_table_id_column: primary key column in main table referenced by main_table_fk_column (default: :id)
|
150
|
-
# * :sum_column: column in main table containing the sum cache, must be NOT NULL and default to 0
|
151
|
-
# * :summed_table: name of table being summed
|
152
|
-
# * :summed_table_id_column: primary key column in summed_table referenced by summed_table_fk_column (default: ;id)
|
153
|
-
# * :summed_column: column in summed_table being summed, must be NOT NULL
|
154
|
-
# * :join_table: name of table which joins main_table with summed_table
|
155
|
-
# * :main_table_fk_column: column in join_table referencing main_table_id_column, must be NOT NULL
|
156
|
-
# * :summed_table_fk_column: column in join_table referencing summed_table_id_column, must be NOT NULL
|
157
|
-
def pgt_sum_through_many_cache(opts={})
|
158
|
-
main_table = opts.fetch(:main_table)
|
159
|
-
main_table_id_column = opts.fetch(:main_table_id_column, :id)
|
160
|
-
sum_column = opts.fetch(:sum_column)
|
161
|
-
summed_table = opts.fetch(:summed_table)
|
162
|
-
summed_table_id_column = opts.fetch(:summed_table_id_column, :id)
|
163
|
-
summed_column = opts.fetch(:summed_column)
|
164
|
-
join_table = opts.fetch(:join_table)
|
165
|
-
main_table_fk_column = opts.fetch(:main_table_fk_column)
|
166
|
-
summed_table_fk_column = opts.fetch(:summed_table_fk_column)
|
167
|
-
|
168
|
-
trigger_name = opts[:trigger_name] || "pgt_stmc_#{main_table}__#{main_table_id_column}__#{sum_column}__#{summed_table_id_column}__#{summed_column}__#{main_table_fk_column}__#{summed_table_fk_column}"
|
169
|
-
function_name = opts[:function_name] || "pgt_stmc_#{main_table}__#{main_table_id_column}__#{sum_column}__#{summed_table}__#{summed_table_id_column}__#{summed_column}__#{join_table}__#{main_table_fk_column}__#{summed_table_fk_column}"
|
170
|
-
join_trigger_name = opts[:join_trigger_name] || "pgt_stmc_join_#{main_table}__#{main_table_id_column}__#{sum_column}__#{summed_table_id_column}__#{summed_column}__#{main_table_fk_column}__#{summed_table_fk_column}"
|
171
|
-
join_function_name = opts[:join_function_name] || "pgt_stmc_join_#{main_table}__#{main_table_id_column}__#{sum_column}__#{summed_table}__#{summed_table_id_column}__#{summed_column}__#{join_table}__#{main_table_fk_column}__#{summed_table_fk_column}"
|
172
|
-
|
173
|
-
orig_summed_table = summed_table
|
174
|
-
orig_join_table = join_table
|
175
|
-
|
176
|
-
main_table = quote_schema_table(main_table)
|
177
|
-
main_table_id_column = quote_schema_table(main_table_id_column)
|
178
|
-
sum_column = quote_schema_table(sum_column)
|
179
|
-
summed_table = quote_schema_table(summed_table)
|
180
|
-
summed_table_id_column = quote_schema_table(summed_table_id_column)
|
181
|
-
summed_column = quote_schema_table(summed_column)
|
182
|
-
join_table = quote_schema_table(join_table)
|
183
|
-
main_table_fk_column = quote_schema_table(main_table_fk_column)
|
184
|
-
summed_table_fk_column = quote_schema_table(summed_table_fk_column)
|
185
|
-
|
186
|
-
pgt_trigger(orig_summed_table, trigger_name, function_name, [:insert, :delete, :update], <<-SQL)
|
187
|
-
BEGIN
|
188
|
-
IF (TG_OP = 'UPDATE' AND NEW.#{summed_table_id_column} = OLD.#{summed_table_id_column}) THEN
|
189
|
-
UPDATE #{main_table} SET #{sum_column} = #{sum_column} + NEW.#{summed_column} - OLD.#{summed_column} WHERE #{main_table_id_column} IN (SELECT #{main_table_fk_column} FROM #{join_table} WHERE #{summed_table_fk_column} = NEW.#{summed_table_id_column});
|
190
|
-
ELSE
|
191
|
-
IF (TG_OP = 'INSERT' OR TG_OP = 'UPDATE') THEN
|
192
|
-
UPDATE #{main_table} SET #{sum_column} = #{sum_column} + NEW.#{summed_column} WHERE #{main_table_id_column} IN (SELECT #{main_table_fk_column} FROM #{join_table} WHERE #{summed_table_fk_column} = NEW.#{summed_table_id_column});
|
193
|
-
END IF;
|
194
|
-
IF (TG_OP = 'DELETE' OR TG_OP = 'UPDATE') THEN
|
195
|
-
UPDATE #{main_table} SET #{sum_column} = #{sum_column} - OLD.#{summed_column} WHERE #{main_table_id_column} IN (SELECT #{main_table_fk_column} FROM #{join_table} WHERE #{summed_table_fk_column} = OLD.#{summed_table_id_column});
|
196
|
-
END IF;
|
197
|
-
END IF;
|
198
|
-
IF (TG_OP = 'DELETE') THEN
|
199
|
-
RETURN OLD;
|
200
|
-
END IF;
|
201
|
-
RETURN NEW;
|
202
|
-
END;
|
203
|
-
SQL
|
204
|
-
|
205
|
-
pgt_trigger(orig_join_table, join_trigger_name, join_function_name, [:insert, :delete, :update], <<-SQL)
|
206
|
-
BEGIN
|
207
|
-
IF (NOT (TG_OP = 'UPDATE' AND NEW.#{main_table_fk_column} = OLD.#{main_table_fk_column} AND NEW.#{summed_table_fk_column} = OLD.#{summed_table_fk_column})) THEN
|
208
|
-
IF (TG_OP = 'INSERT' OR TG_OP = 'UPDATE') THEN
|
209
|
-
UPDATE #{main_table} SET #{sum_column} = #{sum_column} + (SELECT #{summed_column} FROM #{summed_table} WHERE #{summed_table_id_column} = NEW.#{summed_table_fk_column}) WHERE #{main_table_id_column} = NEW.#{main_table_fk_column};
|
210
|
-
END IF;
|
211
|
-
IF (TG_OP = 'DELETE' OR TG_OP = 'UPDATE') THEN
|
212
|
-
UPDATE #{main_table} SET #{sum_column} = #{sum_column} - (SELECT #{summed_column} FROM #{summed_table} WHERE #{summed_table_id_column} = OLD.#{summed_table_fk_column}) WHERE #{main_table_id_column} = OLD.#{main_table_fk_column};
|
213
|
-
END IF;
|
214
|
-
END IF;
|
215
|
-
IF (TG_OP = 'DELETE') THEN
|
216
|
-
RETURN OLD;
|
217
|
-
END IF;
|
218
|
-
RETURN NEW;
|
219
|
-
END;
|
220
|
-
SQL
|
221
|
-
end
|
222
|
-
|
223
|
-
# When rows in a table are updated, touches a timestamp of related rows
|
224
|
-
# in another table.
|
225
|
-
# Arguments:
|
226
|
-
# * main_table : name of table that is being watched for changes
|
227
|
-
# * touch_table : name of table that needs to be touched
|
228
|
-
# * column : name of timestamp column to be touched
|
229
|
-
# * expr : hash or array that represents the columns that define the relationship
|
230
|
-
# * opts : option hash, see module documentation
|
231
|
-
def pgt_touch(main_table, touch_table, column, expr, opts={})
|
232
|
-
trigger_name = opts[:trigger_name] || "pgt_t_#{main_table}__#{touch_table}"
|
233
|
-
function_name = opts[:function_name] || "pgt_t_#{main_table}__#{touch_table}"
|
234
|
-
cond = lambda{|source| expr.map{|k,v| "#{quote_identifier(k)} = #{source}.#{quote_identifier(v)}"}.join(" AND ")}
|
235
|
-
same_id = expr.map{|k,v| "NEW.#{quote_identifier(v)} = OLD.#{quote_identifier(v)}"}.join(" AND ")
|
236
|
-
|
237
|
-
table = quote_schema_table(touch_table)
|
238
|
-
col = quote_identifier(column)
|
239
|
-
update = lambda{|source| " UPDATE #{table} SET #{col} = CURRENT_TIMESTAMP WHERE #{cond[source]} AND ((#{col} <> CURRENT_TIMESTAMP) OR (#{col} IS NULL));"}
|
240
|
-
|
241
|
-
sql = <<-SQL
|
242
|
-
BEGIN
|
243
|
-
IF (TG_OP = 'UPDATE' AND (#{same_id})) THEN
|
244
|
-
#{update['NEW']}
|
245
|
-
ELSE
|
246
|
-
IF (TG_OP = 'INSERT' OR TG_OP = 'UPDATE') THEN
|
247
|
-
#{update['NEW']}
|
248
|
-
END IF;
|
249
|
-
IF (TG_OP = 'DELETE' OR TG_OP = 'UPDATE') THEN
|
250
|
-
#{update['OLD']}
|
251
|
-
END IF;
|
252
|
-
END IF;
|
253
|
-
|
254
|
-
IF (TG_OP = 'DELETE') THEN
|
255
|
-
RETURN OLD;
|
256
|
-
END IF;
|
257
|
-
RETURN NEW;
|
258
|
-
END;
|
259
|
-
SQL
|
260
|
-
pgt_trigger(main_table, trigger_name, function_name, [:insert, :delete, :update], sql, :after=>true)
|
261
|
-
end
|
262
|
-
|
263
|
-
# Turns a column in the table into a updated at timestamp column, which
|
264
|
-
# always contains the timestamp the record was inserted or last updated.
|
265
|
-
# Arguments:
|
266
|
-
# * table : name of table
|
267
|
-
# * column : column in table that should be a updated at timestamp column
|
268
|
-
# * opts : option hash, see module documentation
|
269
|
-
def pgt_updated_at(table, column, opts={})
|
270
|
-
trigger_name = opts[:trigger_name] || "pgt_ua_#{column}"
|
271
|
-
function_name = opts[:function_name] || "pgt_ua_#{table}__#{column}"
|
272
|
-
pgt_trigger(table, trigger_name, function_name, [:insert, :update], <<-SQL)
|
273
|
-
BEGIN
|
274
|
-
NEW.#{quote_identifier(column)} := CURRENT_TIMESTAMP;
|
275
|
-
RETURN NEW;
|
276
|
-
END;
|
277
|
-
SQL
|
278
|
-
end
|
279
|
-
|
280
|
-
private
|
281
|
-
|
282
|
-
# Add or replace a function that returns trigger to handle the action,
|
283
|
-
# and add a trigger that calls the function.
|
284
|
-
def pgt_trigger(table, trigger_name, function_name, events, definition, opts={})
|
285
|
-
create_function(function_name, definition, :language=>:plpgsql, :returns=>:trigger, :replace=>true)
|
286
|
-
create_trigger(table, trigger_name, function_name, :events=>events, :each_row=>true, :after=>opts[:after])
|
287
|
-
end
|
6
|
+
class_eval(&PGT_DEFINE)
|
288
7
|
end
|
289
8
|
end
|
290
9
|
end
|
@@ -7,17 +7,12 @@ require 'minitest/autorun'
|
|
7
7
|
DB = Sequel.connect(ENV['PGT_SPEC_DB']||'postgres:///spgt_test?user=postgres')
|
8
8
|
|
9
9
|
$:.unshift(File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), 'lib'))
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
config.expect_with :rspec do |c|
|
17
|
-
c.syntax = :should
|
18
|
-
end
|
19
|
-
end
|
20
|
-
end
|
10
|
+
if ENV['PGT_GLOBAL'] == '1'
|
11
|
+
puts "Running specs with global modification"
|
12
|
+
require 'sequel_postgresql_triggers'
|
13
|
+
else
|
14
|
+
puts "Running specs with extension"
|
15
|
+
DB.extension :pg_triggers
|
21
16
|
end
|
22
17
|
|
23
18
|
describe "PostgreSQL Triggers" do
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sequel_postgresql_triggers
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Jeremy Evans
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2016-
|
11
|
+
date: 2016-09-28 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: sequel
|
@@ -31,7 +31,8 @@ extensions: []
|
|
31
31
|
extra_rdoc_files: []
|
32
32
|
files:
|
33
33
|
- MIT-LICENSE
|
34
|
-
- README
|
34
|
+
- README.rdoc
|
35
|
+
- lib/sequel/extensions/pg_triggers.rb
|
35
36
|
- lib/sequel_postgresql_triggers.rb
|
36
37
|
- spec/sequel_postgresql_triggers_spec.rb
|
37
38
|
homepage: https://github.com/jeremyevans/sequel_postgresql_triggers
|
@@ -45,7 +46,7 @@ rdoc_options:
|
|
45
46
|
- "--title"
|
46
47
|
- 'Sequel PostgreSQL Triggers: Database enforced timestamps, immutable columns, and
|
47
48
|
counter/sum caches'
|
48
|
-
- README
|
49
|
+
- README.rdoc
|
49
50
|
- MIT-LICENSE
|
50
51
|
- lib
|
51
52
|
require_paths:
|
@@ -65,5 +66,6 @@ rubyforge_project:
|
|
65
66
|
rubygems_version: 2.5.1
|
66
67
|
signing_key:
|
67
68
|
specification_version: 4
|
68
|
-
summary: Database enforced timestamps, immutable columns,
|
69
|
+
summary: Database enforced timestamps, immutable columns, counter/sum caches, and
|
70
|
+
touch propogation
|
69
71
|
test_files: []
|
data/README
DELETED
@@ -1,75 +0,0 @@
|
|
1
|
-
= Sequel PostgreSQL Triggers
|
2
|
-
|
3
|
-
Sequel PostgreSQL Triggers is a small enhancement to Sequel allowing
|
4
|
-
a user to easily handle the following types of columns:
|
5
|
-
|
6
|
-
* Timestamp Columns (Created At/Updated At)
|
7
|
-
* Counter/Sum Caches
|
8
|
-
* Immutable Columns
|
9
|
-
|
10
|
-
It handles these internally to the database via triggers, so even if
|
11
|
-
other applications access the database (without using Sequel), things
|
12
|
-
will still work (unless the database superuser disables triggers).
|
13
|
-
|
14
|
-
To use any of these methods before PostgreSQL 9.0, you have to add
|
15
|
-
the plpgsql procedural language to PostgreSQL, which you can do with:
|
16
|
-
|
17
|
-
DB.create_language(:plpgsql)
|
18
|
-
|
19
|
-
== Triggers
|
20
|
-
|
21
|
-
=== Created At Columns - pgt_created_at
|
22
|
-
|
23
|
-
pgt_created_at takes the table and column given and makes it so that
|
24
|
-
upon insertion, the column is set to the CURRENT_TIMESTAMP, and that
|
25
|
-
upon update, the column's value is always set to the previous value.
|
26
|
-
This is sort of like an immutable column, but it doesn't bring up an
|
27
|
-
error if you try to change it, it just ignores it.
|
28
|
-
|
29
|
-
=== Updated At Columns - pgt_updated_at
|
30
|
-
|
31
|
-
Similar to pgt_created_at, takes a table and column and makes it so
|
32
|
-
that upon insertion, the column is set to CURRENT_TIMESTAMP. It
|
33
|
-
differs that upon update, the column is also set to CURRENT_TIMESTAMP.
|
34
|
-
|
35
|
-
=== Counter Cache - pgt_counter_cache
|
36
|
-
|
37
|
-
This takes quite a few arguments (see the RDoc) and sets up a
|
38
|
-
counter cache so that when the counted table is inserted to
|
39
|
-
or deleted from, records in the main table are updated with the
|
40
|
-
count of the corresponding records in the counted table. The counter
|
41
|
-
cache column must have a default of 0 for this to work correctly.
|
42
|
-
|
43
|
-
=== Sum Cache - pgt_sum_cache
|
44
|
-
|
45
|
-
Similar to pgt_counter_cache, except instead of storing a count
|
46
|
-
of records in the main table, it stores the sum on one of the
|
47
|
-
columns in summed table. The sum cache column must have a default
|
48
|
-
of 0 for this to work correctly.
|
49
|
-
|
50
|
-
=== Sum Through Many Cache - pgt_sum_through_many_cache
|
51
|
-
|
52
|
-
Similar to pgt_sum_cache, except instead of a one-to-many relationship,
|
53
|
-
it supports a many-to-many relationship with a single join table. The
|
54
|
-
sum cache column must have a default of 0 for this to work correctly.
|
55
|
-
|
56
|
-
=== Immutable Columns - pgt_immutable
|
57
|
-
|
58
|
-
This takes a table name and one or more column names, and adds
|
59
|
-
an update trigger that raises an exception if you try to modify
|
60
|
-
the value of any of the columns.
|
61
|
-
|
62
|
-
=== Touch Propagation - pgt_touch
|
63
|
-
|
64
|
-
This takes several arguments (again, see the RDoc) and sets up a
|
65
|
-
trigger that watches one table for changes, and touches timestamps
|
66
|
-
of related rows in a separate table.
|
67
|
-
|
68
|
-
== License
|
69
|
-
|
70
|
-
This library is released under the MIT License. See the MIT-LICENSE
|
71
|
-
file for details.
|
72
|
-
|
73
|
-
== Author
|
74
|
-
|
75
|
-
Jeremy Evans <code@jeremyevans.net>
|