hairtrigger 0.2.8 → 0.2.9
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +5 -0
- data/lib/hair_trigger/builder.rb +39 -23
- data/lib/hair_trigger/version.rb +1 -1
- data/spec/builder_spec.rb +19 -1
- metadata +2 -2
data/README.md
CHANGED
@@ -83,6 +83,11 @@ Required (but may be satisified by `before`/`after`). Possible values are `:befo
|
|
83
83
|
#### events(*events)
|
84
84
|
Required (but may be satisified by `before`/`after`). Possible values are `:insert`/`:update`/`:delete`/`:truncate`. MySQL/SQLite only support one action per trigger, and don't support `:truncate`.
|
85
85
|
|
86
|
+
#### nowrap(flag = true)
|
87
|
+
PostgreSQL specific option to prevent the trigger action from being wrapped in a `CREATE FUNCTION`. This is useful for executing existing triggers/functions directly, but is not compatible with the `security` setting nor can it be used with pre-9.0 PostgreSQL when supplying a `where` condition.
|
88
|
+
|
89
|
+
Example: `trigger.after(:update).nowrap { "tsvector_update_trigger(...)" }`
|
90
|
+
|
86
91
|
#### all
|
87
92
|
Noop, useful for trigger groups (see below).
|
88
93
|
|
data/lib/hair_trigger/builder.rb
CHANGED
@@ -4,7 +4,7 @@ module HairTrigger
|
|
4
4
|
class Builder
|
5
5
|
class DeclarationError < StandardError; end
|
6
6
|
class GenerationError < StandardError; end
|
7
|
-
|
7
|
+
|
8
8
|
attr_accessor :options
|
9
9
|
attr_reader :triggers # nil unless this is a trigger group
|
10
10
|
attr_reader :prepared_actions, :prepared_where # after delayed interpolation
|
@@ -73,6 +73,10 @@ module HairTrigger
|
|
73
73
|
options[:where] = where
|
74
74
|
end
|
75
75
|
|
76
|
+
def nowrap(flag = true)
|
77
|
+
options[:nowrap] = flag
|
78
|
+
end
|
79
|
+
|
76
80
|
# noop, just a way you can pass a block within a trigger group
|
77
81
|
def all
|
78
82
|
end
|
@@ -146,7 +150,7 @@ module HairTrigger
|
|
146
150
|
METHOD
|
147
151
|
end
|
148
152
|
end
|
149
|
-
chainable_methods :name, :on, :for_each, :before, :after, :where, :security, :timing, :events, :all
|
153
|
+
chainable_methods :name, :on, :for_each, :before, :after, :where, :security, :timing, :events, :all, :nowrap
|
150
154
|
|
151
155
|
def create_grouped_trigger?
|
152
156
|
adapter_name == :mysql
|
@@ -366,36 +370,48 @@ END;
|
|
366
370
|
def generate_trigger_postgresql
|
367
371
|
raise GenerationError, "truncate triggers are only supported on postgres 8.4 and greater" if db_version < 80400 && options[:events].include?('TRUNCATE')
|
368
372
|
raise GenerationError, "FOR EACH ROW triggers may not be triggered by truncate events" if options[:for_each] == 'ROW' && options[:events].include?('TRUNCATE')
|
369
|
-
|
370
|
-
|
373
|
+
raise GenerationError, "security cannot be used in conjunction with nowrap" if options[:nowrap] && options[:security]
|
374
|
+
raise GenerationError, "where can only be used in conjunction with nowrap on postgres 9.0 and greater" if options[:nowrap] && prepared_where && db_version < 90000
|
375
|
+
|
376
|
+
sql = ''
|
377
|
+
|
378
|
+
if options[:nowrap]
|
379
|
+
trigger_action = raw_actions
|
380
|
+
else
|
381
|
+
security = options[:security] if options[:security] && options[:security] != :invoker
|
382
|
+
sql << <<-SQL
|
371
383
|
CREATE FUNCTION #{prepared_name}()
|
372
384
|
RETURNS TRIGGER AS $$
|
373
385
|
BEGIN
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
else
|
380
|
-
sql << normalize(raw_actions, 1)
|
381
|
-
end
|
382
|
-
# if no return is specified at the end, be sure we set a sane one
|
383
|
-
unless raw_actions =~ /return [^;]+;\s*\z/i
|
384
|
-
if options[:timing] == "AFTER" || options[:for_each] == 'STATEMENT'
|
385
|
-
sql << normalize("RETURN NULL;", 1)
|
386
|
-
elsif options[:events].include?('DELETE')
|
387
|
-
sql << normalize("RETURN OLD;", 1)
|
386
|
+
SQL
|
387
|
+
if prepared_where && db_version < 90000
|
388
|
+
sql << normalize("IF #{prepared_where} THEN", 1)
|
389
|
+
sql << normalize(raw_actions, 2)
|
390
|
+
sql << normalize("END IF;", 1)
|
388
391
|
else
|
389
|
-
sql << normalize(
|
392
|
+
sql << normalize(raw_actions, 1)
|
390
393
|
end
|
391
|
-
|
392
|
-
|
394
|
+
# if no return is specified at the end, be sure we set a sane one
|
395
|
+
unless raw_actions =~ /return [^;]+;\s*\z/i
|
396
|
+
if options[:timing] == "AFTER" || options[:for_each] == 'STATEMENT'
|
397
|
+
sql << normalize("RETURN NULL;", 1)
|
398
|
+
elsif options[:events].include?('DELETE')
|
399
|
+
sql << normalize("RETURN OLD;", 1)
|
400
|
+
else
|
401
|
+
sql << normalize("RETURN NEW;", 1)
|
402
|
+
end
|
403
|
+
end
|
404
|
+
sql << <<-SQL
|
393
405
|
END;
|
394
406
|
$$ LANGUAGE plpgsql#{security ? " SECURITY #{security.to_s.upcase}" : ""};
|
395
|
-
|
407
|
+
SQL
|
408
|
+
|
409
|
+
trigger_action = "#{prepared_name}()"
|
410
|
+
end
|
411
|
+
|
396
412
|
[sql, <<-SQL]
|
397
413
|
CREATE TRIGGER #{prepared_name} #{options[:timing]} #{options[:events].join(" OR ")} ON #{options[:table]}
|
398
|
-
FOR EACH #{options[:for_each]}#{prepared_where && db_version >= 90000 ? " WHEN (" + prepared_where + ')': ''} EXECUTE PROCEDURE #{
|
414
|
+
FOR EACH #{options[:for_each]}#{prepared_where && db_version >= 90000 ? " WHEN (" + prepared_where + ')': ''} EXECUTE PROCEDURE #{trigger_action};
|
399
415
|
SQL
|
400
416
|
end
|
401
417
|
|
data/lib/hair_trigger/version.rb
CHANGED
data/spec/builder_spec.rb
CHANGED
@@ -100,7 +100,7 @@ describe "builder" do
|
|
100
100
|
}.should raise_error
|
101
101
|
end
|
102
102
|
end
|
103
|
-
|
103
|
+
|
104
104
|
context "mysql" do
|
105
105
|
before(:each) do
|
106
106
|
@adapter = MockAdapter.new("mysql")
|
@@ -242,6 +242,17 @@ describe "builder" do
|
|
242
242
|
grep(/RETURN NULL;/).size.should eql(1)
|
243
243
|
end
|
244
244
|
|
245
|
+
it "should not wrap the action in a function" do
|
246
|
+
builder.on(:foos).after(:update).nowrap{ 'existing_procedure()' }.generate.
|
247
|
+
grep(/CREATE FUNCTION/).size.should eql(0)
|
248
|
+
end
|
249
|
+
|
250
|
+
it "should reject combined use of security and nowrap" do
|
251
|
+
lambda {
|
252
|
+
builder.on(:foos).after(:update).security("'user'@'host'").nowrap{ "FOO" }.generate
|
253
|
+
}.should raise_error
|
254
|
+
end
|
255
|
+
|
245
256
|
context "legacy" do
|
246
257
|
it "should reject truncate pre-8.4" do
|
247
258
|
@adapter = MockAdapter.new("postgresql", :postgresql_version => 80300)
|
@@ -255,6 +266,13 @@ describe "builder" do
|
|
255
266
|
builder.on(:foos).after(:insert).where("BAR"){ "FOO" }.generate.
|
256
267
|
grep(/IF BAR/).size.should eql(1)
|
257
268
|
end
|
269
|
+
|
270
|
+
it "should reject combined use of where and nowrap pre-9.0" do
|
271
|
+
@adapter = MockAdapter.new("postgresql", :postgresql_version => 80400)
|
272
|
+
lambda {
|
273
|
+
builder.on(:foos).after(:insert).where("BAR").nowrap{ "FOO" }.generate
|
274
|
+
}.should raise_error
|
275
|
+
end
|
258
276
|
end
|
259
277
|
end
|
260
278
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: hairtrigger
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.
|
4
|
+
version: 0.2.9
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2014-
|
12
|
+
date: 2014-05-21 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: activerecord
|