hairtrigger 0.2.8 → 0.2.9
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.
- 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
|