em-pg-client-helper 1.3.0 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f507ec3c80d6ed2c58575898e2132bfa3e05ac36
4
- data.tar.gz: 5c311e9a44d3274689c4c6f12dfc028a3e721328
3
+ metadata.gz: 54910da339d3b8be844ddddb6b373dbec7ca04b5
4
+ data.tar.gz: efb8474eb8d13f6020116b4923dfea5f4793059c
5
5
  SHA512:
6
- metadata.gz: 6b39ee8f0cdce4de503b1793ca26f13b0bd493cd53b80390a95a65018252af5d30c837f733013fb07678a8aab32caa62fceab8d0f6a26b8c70494df3185c2173
7
- data.tar.gz: 0af1627ff7f3bac097818c5a91f5c71467f32aadfb18fef17dd939db9e360e1a20a8559a68421b5ae24f0d74153907ca9af62c3d3d5347ebf18fda5662239cf0
6
+ metadata.gz: c39f44d77f9238e333d22fd9ed3a6970f46c45a783d3414f733baa61a912479524032c962fdacd0e085e56f38c0615d3dba3e30fcbe66198168ab657a1c113ba
7
+ data.tar.gz: 012405e9483038a357080c994b20ce3ce0793379e557b28f04331a34d70a07a2871afeadc98f1c6303d5d3397073fb61db0ff7b4a4c8826f8fff2cb7aee82055
@@ -76,10 +76,10 @@ module PG::EM::Client::Helper
76
76
  sqls = sqldb.sqls
77
77
 
78
78
  if sqls.empty?
79
- sqls = [ret.sql] rescue []
79
+ sqls = [ret.sql] rescue ret
80
80
  end
81
81
 
82
- if sqls.empty?
82
+ if sqls.nil? or sqls.empty?
83
83
  raise PG::EM::Client::Helper::BadSequelError,
84
84
  "Your block did not generate an SQL statement"
85
85
  end
@@ -195,6 +195,58 @@ module PG::EM::Client::Helper
195
195
  db.exec_defer(*insert_sql(tbl, params))
196
196
  end
197
197
 
198
+ # Efficiently perform a "bulk" insert of multiple rows.
199
+ #
200
+ # When you have a large quantity of data to insert into a table, you don't
201
+ # want to do it one row at a time -- that's *really* inefficient. On the
202
+ # other hand, if you do one giant multi-row insert statement, the insert
203
+ # will fail if *any* of the rows causes a constraint failure. What to do?
204
+ #
205
+ # Well, here's our answer: try to insert all the records at once. If that
206
+ # fails with a constraint violation, then split the set of records in half
207
+ # and try to bulk insert each of those halves. Recurse in this fashion until
208
+ # you only have one record to insert.
209
+ #
210
+ # @param db [PG::EM::Client, PG::EM::ConnectionPool] the connection against
211
+ # which the insert wil be run.
212
+ #
213
+ # @param tbl [#to_sym] the name of the table into which you wish to insert
214
+ # your data.
215
+ #
216
+ # @param columns [Array<#to_sym>] the columns into which each record of data
217
+ # will be inserted.
218
+ #
219
+ # @param rows [Array<Array<Object>>] the values to insert. Each entry in
220
+ # the outermost array is a row of data; the elements of each of these inner
221
+ # arrays corresponds to the column in the same position in the `columns`
222
+ # array. **NOTE**: we don't do any checking to make sure you're giving
223
+ # us the correct list of values for each row. Thus, if you give us a
224
+ # row array that has too few, or too many, entries, the database will puke.
225
+ #
226
+ # @return [EM::Deferrable] the deferrable in which the query is being called;
227
+ # once the bulk insert completes successfully, the deferrable will succeed
228
+ # with the number of rows that were successfully inserted. If the insert
229
+ # could not be completed, the deferrable will fail (`#errback`) with the
230
+ # exception.
231
+ #
232
+ # @since 2.0.0
233
+ #
234
+ def db_bulk_insert(db, tbl, columns, rows, &blk)
235
+ EM::Completion.new.tap do |df|
236
+ df.callback(&blk) if blk
237
+
238
+ db_transaction(db) do |txn|
239
+ txn.bulk_insert(tbl, columns, rows) do |count|
240
+ txn.commit do
241
+ df.succeed(count)
242
+ end
243
+ end
244
+ end.errback do |ex|
245
+ df.fail(ex)
246
+ end
247
+ end
248
+ end
249
+
198
250
  # @!macro upsert_params
199
251
  #
200
252
  # @param tbl [#to_s] The name of the table on which to operate.
@@ -7,6 +7,13 @@
7
7
  class PG::EM::Client::Helper::DeferrableGroup
8
8
  include ::EventMachine::Deferrable
9
9
 
10
+ # Raised when an attempt is made to add a deferrable to a group on
11
+ # which {#close} has already been called.
12
+ #
13
+ # @since 2.0.0
14
+ #
15
+ class ClosedError < StandardError; end
16
+
10
17
  # Create a new deferrable group.
11
18
  #
12
19
  def initialize
@@ -23,15 +30,15 @@ class PG::EM::Client::Helper::DeferrableGroup
23
30
  #
24
31
  # @return [EM::Deferrable] the same deferrable.
25
32
  #
26
- # @raise [RuntimeError] if you attempt to add a deferrable after the
27
- # group has been closed (that is, the `#close` method has been called),
28
- # indicating that the deferrable group doesn't have any more deferrables
29
- # to add.
33
+ # @raise [PG::EM::Client::Helper::DeferrableGroup::ClosedError] if you
34
+ # attempt to add a deferrable after the group has been closed (that is,
35
+ # the `#close` method has been called), indicating that the deferrable
36
+ # group doesn't have any more deferrables to add.
30
37
  #
31
38
  def add(df)
32
39
  if @closed
33
- raise RuntimeError,
34
- "This deferrable group is closed."
40
+ raise ClosedError,
41
+ "This deferrable group is closed"
35
42
  end
36
43
 
37
44
  @outstanding << df
@@ -1,3 +1,5 @@
1
+ require 'securerandom'
2
+
1
3
  # Represents a database transaction, and contains all of the methods which
2
4
  # can be used to execute queries within the transaction connection.
3
5
  #
@@ -5,6 +7,13 @@ class PG::EM::Client::Helper::Transaction
5
7
  include ::PG::EM::Client::Helper
6
8
  include ::EventMachine::Deferrable
7
9
 
10
+ # Raised when you attempt to execute a query in a transaction which has
11
+ # been finished (either by COMMIT or ROLLBACK).
12
+ #
13
+ # @since 2.0.0
14
+ #
15
+ class ClosedError < StandardError; end
16
+
8
17
  # Create a new transaction. You shouldn't have to call this yourself;
9
18
  # `db_transaction` should create one and pass it to your block.
10
19
  #
@@ -21,12 +30,12 @@ class PG::EM::Client::Helper::Transaction
21
30
  # @raise [ArgumentError] If an unknown isolation level is specified.
22
31
  #
23
32
  def initialize(conn, opts = {}, &blk)
24
- @conn = conn
25
- @opts = opts
26
- # This can be `nil` if the txn is in progress, or it will be
27
- # true or false to indicate success/failure of the txn
28
- @committed = nil
29
- @retryable = opts[:retry]
33
+ @conn = conn
34
+ @opts = opts
35
+ @finished = nil
36
+ @retryable = opts[:retry]
37
+ @autorollback_on_error = true
38
+ @savepoint_stack = []
30
39
 
31
40
  DeferrableGroup.new do |dg|
32
41
  @dg = dg
@@ -59,7 +68,7 @@ class PG::EM::Client::Helper::Transaction
59
68
  end
60
69
  end
61
70
  end.callback do
62
- rollback(RuntimeError.new("txn.commit was not called")) unless @committed
71
+ rollback(RuntimeError.new("txn.commit was not called")) unless @finished
63
72
  self.succeed
64
73
  end.errback do |ex|
65
74
  if @retryable and [PG::TRSerializationFailure].include?(ex.class)
@@ -81,16 +90,16 @@ class PG::EM::Client::Helper::Transaction
81
90
  # exception will be raised.
82
91
  #
83
92
  def commit
84
- if @committed.nil?
93
+ unless @finished
85
94
  trace_query("COMMIT")
86
95
  @conn.exec_defer("COMMIT", []).tap do |df|
87
96
  @dg.add(df)
88
97
  end.callback do
89
- @committed = true
98
+ @finished = true
90
99
  @dg.close
91
- end.errback do |ex|
92
- @committed = false
93
- @dg.fail(ex)
100
+ yield if block_given?
101
+ end.errback do
102
+ @finished = true
94
103
  @dg.close
95
104
  end
96
105
  end
@@ -102,15 +111,81 @@ class PG::EM::Client::Helper::Transaction
102
111
  # event of a database error or other exception.
103
112
  #
104
113
  def rollback(ex)
105
- if @committed.nil?
106
- exec("ROLLBACK") do
107
- @committed = false
108
- @dg.fail(ex)
114
+ unless @finished
115
+ if @savepoint_stack.empty?
116
+ exec("ROLLBACK") do
117
+ @finished = true
118
+ @dg.fail(ex)
119
+ @dg.close
120
+ end
121
+ else
122
+ sp = @savepoint_stack.pop
123
+ exec("ROLLBACK TO \"#{sp[:savepoint]}\"")
124
+ sp[:deferrable].fail(ex)
109
125
  @dg.close
126
+ @dg = sp[:parent_deferrable_group]
110
127
  end
111
128
  end
112
129
  end
113
130
 
131
+ # Manage the "rollback on the failure of a single query" behaviour.
132
+ #
133
+ # The default behaviour of a transaction, when a query fails, is for
134
+ # the transaction to automatically be rolled back and the rest of the
135
+ # statements to not be executed. In **ALMOST** every case, this is the
136
+ # correct behaviour. However, there are some corner cases in which you
137
+ # want to be able to avoid this behaviour, and will manually react to
138
+ # the transaction failure in some way. In that case, you can set this
139
+ # to `false` and the transaction will not automatically fail.
140
+ #
141
+ # Given that pretty much the only thing you can do when a query fails,
142
+ # other than abort the transaction, is to rollback to a savepoint, you
143
+ # might want to look at {#savepoint} before you try using this.
144
+ #
145
+ # @since 2.0.0
146
+ #
147
+ attr_accessor :autorollback_on_error
148
+
149
+ # Setup a "savepoint" within the transaction.
150
+ #
151
+ # A savepoint is, as the name suggests, kinda like a "saved game", in an
152
+ # SQL transaction. If a query fails within a transaction, normally all
153
+ # you can do is rollback and abort the entire transaction. Savepoints
154
+ # give you another option: roll back to the savepoint, and try again.
155
+ #
156
+ # So, that's what this method does. Inside of the block passed to
157
+ # `#savepoint`, if any query fails, instead of rolling back the entire
158
+ # transaction, we instead only rollback to the savepoint, and execution
159
+ # continues by executing the `errback` callbacks defined on the savepoint
160
+ # deferrable.
161
+ #
162
+ # @return [EM::Deferrable]
163
+ #
164
+ # @since 2.0.0
165
+ #
166
+ def savepoint(&blk)
167
+ savepoint = SecureRandom.uuid
168
+ parent_dg = @dg
169
+ DeferrableGroup.new do |dg|
170
+ @dg = dg
171
+
172
+ dg.callback do
173
+ @dg = parent_dg
174
+ @dg.close
175
+ end
176
+
177
+ exec("SAVEPOINT \"#{savepoint}\"").tap do |df|
178
+ @savepoint_stack << { :savepoint => savepoint,
179
+ :deferrable => df,
180
+ :parent_deferrable_group => parent_dg
181
+ }
182
+
183
+ df.callback(&blk) if blk
184
+ end
185
+ end
186
+ end
187
+
188
+
114
189
  # Generate SQL statements via Sequel, and run the result against the
115
190
  # database. Very chic.
116
191
  #
@@ -131,6 +206,67 @@ class PG::EM::Client::Helper::Transaction
131
206
  exec(*insert_sql(tbl, params), &blk)
132
207
  end
133
208
 
209
+ # Efficiently perform a "bulk" insert of multiple rows.
210
+ #
211
+ # When you have a large quantity of data to insert into a table, you don't
212
+ # want to do it one row at a time -- that's *really* inefficient. On the
213
+ # other hand, if you do one giant multi-row insert statement, the insert
214
+ # will fail if *any* of the rows causes a constraint failure. What to do?
215
+ #
216
+ # Well, here's our answer: try to insert all the records at once. If that
217
+ # fails with a constraint violation, then split the set of records in half
218
+ # and try to bulk insert each of those halves. Recurse in this fashion until
219
+ # you only have one record to insert.
220
+ #
221
+ # @param tbl [#to_sym] the name of the table into which you wish to insert
222
+ # your data.
223
+ #
224
+ # @param columns [Array<#to_sym>] the columns into which each record of data
225
+ # will be inserted.
226
+ #
227
+ # @param rows [Array<Array<Object>>] the values to insert. Each entry in
228
+ # the outermost array is a row of data; the elements of each of these inner
229
+ # arrays corresponds to the column in the same position in the `columns`
230
+ # array. **NOTE**: we don't do any checking to make sure you're giving
231
+ # us the correct list of values for each row. Thus, if you give us a
232
+ # row array that has too few, or too many, entries, the database will puke.
233
+ #
234
+ # @yield [Integer] Once the insert has completed, the number of rows that
235
+ # were successfully inserted (that may be less than `rows.length` if
236
+ # there were any constraint failures) will be yielded to the block.
237
+ #
238
+ # @since 2.0.0
239
+ #
240
+ def bulk_insert(tbl, columns, rows, &blk)
241
+ db = Sequel.connect("mock://postgres")
242
+
243
+ # Guh hand-hacked SQL is fugly... but what I'm doing is so utterly
244
+ # niche that Sequel doesn't support it.
245
+ q_tbl = db.literal(tbl.to_sym)
246
+ q_cols = columns.map { |c| db.literal(c.to_sym) }
247
+
248
+ subselect = "SELECT 1 FROM #{q_tbl} AS dst WHERE " +
249
+ q_cols.map { |c| "src.#{c}=dst.#{c}" }.join(" AND ")
250
+
251
+ total_rows_inserted = 0
252
+ DeferrableGroup.new.tap do |dg|
253
+ rows.each_slice(100) do |slice|
254
+ vals = slice.map do |row|
255
+ "(" + row.map { |v| db.literal(v) }.join(", ") + ")"
256
+ end.join(", ")
257
+ q = "INSERT INTO #{q_tbl} (SELECT * FROM (VALUES #{vals}) " +
258
+ "AS src (#{q_cols.join(", ")}) WHERE NOT EXISTS (#{subselect}))"
259
+ df = exec(q) do |res|
260
+ total_rows_inserted += res.cmd_tuples
261
+ end
262
+ dg.add(df)
263
+ end
264
+ dg.callback { dg.succeed(total_rows_inserted) }
265
+ dg.callback(&blk) if blk
266
+ dg.close
267
+ end
268
+ end
269
+
134
270
  # Run an upsert inside a transaction.
135
271
  #
136
272
  # @see {PG::EM::Client::Helper#upsert_sql} for all the parameters.
@@ -157,8 +293,8 @@ class PG::EM::Client::Helper::Transaction
157
293
  # specific query finishes.
158
294
  #
159
295
  def exec(sql, values=[], &blk)
160
- unless @committed.nil?
161
- raise RuntimeError,
296
+ if @finished
297
+ raise ClosedError,
162
298
  "Cannot execute a query in a transaction that has been closed"
163
299
  end
164
300
 
@@ -167,11 +303,13 @@ class PG::EM::Client::Helper::Transaction
167
303
  @dg.add(df)
168
304
  df.callback(&blk) if blk
169
305
  end.errback do |ex|
170
- rollback(ex)
306
+ rollback(ex) if @autorollback_on_error
171
307
  end
172
308
  end
173
309
  alias_method :exec_defer, :exec
174
310
 
311
+ private
312
+
175
313
  # Trace queries as they happen, if `ENV['EM_PG_TXN_TRACE']` is set.
176
314
  #
177
315
  def trace_query(q, v=nil)
@@ -0,0 +1,27 @@
1
+ require_relative './spec_helper'
2
+
3
+ describe "PG::EM::Client::Helper#db_bulk_insert" do
4
+ let(:mock_conn) { double(PG::EM::Client) }
5
+
6
+ it "inserts multiple records" do
7
+ expect(dbl = double).to receive(:results).with(2)
8
+
9
+ in_em do
10
+ expect_query("BEGIN")
11
+ expect_query('INSERT INTO "foo" ' +
12
+ '(SELECT * FROM (VALUES (1, \'x\'), (3, \'y\')) ' +
13
+ 'AS src ("bar", "baz") ' +
14
+ 'WHERE NOT EXISTS ' +
15
+ '(SELECT 1 FROM "foo" AS dst ' +
16
+ 'WHERE src."bar"=dst."bar" AND src."baz"=dst."baz"))',
17
+ [], 0.001, :succeed, Struct.new(:cmd_tuples).new(2)
18
+ )
19
+ expect_query("COMMIT")
20
+
21
+ db_bulk_insert(mock_conn, "foo", [:bar, :baz], [[1, "x"], [3, "y"]]) do |count|
22
+ dbl.results(count)
23
+ EM.stop
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,118 @@
1
+ require_relative './spec_helper'
2
+
3
+ describe "PG::EM::Client::Helper::Transaction#savepoint" do
4
+ let(:mock_conn) { double(PG::EM::Client) }
5
+
6
+ it "executes through the savepoint in normal operation" do
7
+ in_em do
8
+ expect(SecureRandom).to receive(:uuid).and_return("faff")
9
+ expect_query("BEGIN")
10
+ expect_query('INSERT INTO "foo" ("bar") VALUES ($1)', ["wombat"])
11
+ expect_query('SAVEPOINT "faff"')
12
+ expect_query('INSERT INTO "foo" ("bar") VALUES ($1)', ["baz"])
13
+ expect_query("COMMIT")
14
+ in_transaction do |txn|
15
+ txn.insert("foo", :bar => 'wombat') do
16
+ txn.savepoint do
17
+ txn.insert("foo", :bar => 'baz') do
18
+ txn.commit
19
+ end
20
+ end.errback do
21
+ txn.rollback # Just to show this *doesn't* happen
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+
28
+ it "rolls back to the savepoint after a failed INSERT" do
29
+ in_em do
30
+ expect(SecureRandom).to receive(:uuid).and_return("faff")
31
+ expect_query("BEGIN")
32
+ expect_query('INSERT INTO "foo" ("bar") VALUES ($1)', ["wombat"])
33
+ expect_query('SAVEPOINT "faff"')
34
+ expect_query_failure('INSERT INTO "foo" ("bar") VALUES ($1)', ["baz"])
35
+ expect_query('ROLLBACK TO "faff"')
36
+ expect_query('INSERT INTO "foo" ("bar") VALUES ($1)', ["wibble"])
37
+ expect_query("COMMIT")
38
+ in_transaction do |txn|
39
+ txn.insert("foo", :bar => 'wombat') do
40
+ txn.savepoint do
41
+ txn.insert("foo", :bar => 'baz') do
42
+ txn.rollback # Just to show this *doesn't* happen
43
+ end
44
+ end.errback do
45
+ txn.insert("foo", :bar => 'wibble') do
46
+ txn.commit
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+
54
+ it "issues ROLLBACK if query fails outside of savepoint" do
55
+ in_em do
56
+ expect(SecureRandom).to receive(:uuid).and_return("faff")
57
+ expect_query("BEGIN")
58
+ expect_query('INSERT INTO "foo" ("bar") VALUES ($1)', ["wombat"])
59
+ expect_query('SAVEPOINT "faff"')
60
+ expect_query_failure('INSERT INTO "foo" ("bar") VALUES ($1)', ["baz"])
61
+ # I'd like to be able to verify that the savepoint IDs are the same
62
+ # in both queries, but I'm not sure how. Cross fingers!
63
+ expect_query('ROLLBACK TO "faff"')
64
+ expect_query_failure('INSERT INTO "foo" ("bar") VALUES ($1)', ["wibble"])
65
+ expect_query("ROLLBACK")
66
+ in_transaction do |txn|
67
+ txn.insert("foo", :bar => 'wombat') do
68
+ txn.savepoint do
69
+ txn.insert("foo", :bar => 'baz') do
70
+ txn.rollback # Just to show this *doesn't* happen
71
+ end
72
+ end.errback do
73
+ txn.insert("foo", :bar => 'wibble') do
74
+ txn.commit
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
81
+
82
+ it "handles nested SAVEPOINTs correctly" do
83
+ in_em do
84
+ expect(SecureRandom).to receive(:uuid).and_return("faff1")
85
+ expect(SecureRandom).to receive(:uuid).and_return("faff2")
86
+ expect_query("BEGIN")
87
+ expect_query('INSERT INTO "foo" ("bar") VALUES ($1)', ["wombat"])
88
+ expect_query('SAVEPOINT "faff1"')
89
+ expect_query_failure('INSERT INTO "foo" ("bar") VALUES ($1)', ["baz"])
90
+ # I'd like to be able to verify that the savepoint IDs are the same
91
+ # in both queries, but I'm not sure how. Cross fingers!
92
+ expect_query('ROLLBACK TO "faff1"')
93
+
94
+ expect_query('SAVEPOINT "faff2"')
95
+ expect_query_failure('INSERT INTO "foo" ("bar") VALUES ($1)', ["wibble"])
96
+ expect_query('ROLLBACK TO "faff2"')
97
+ expect_query("COMMIT")
98
+
99
+ in_transaction do |txn|
100
+ txn.insert("foo", :bar => 'wombat') do
101
+ txn.savepoint do
102
+ txn.insert("foo", :bar => 'baz') do
103
+ txn.rollback # Just to show this *doesn't* happen
104
+ end
105
+ end.errback do
106
+ txn.savepoint do
107
+ txn.insert("foo", :bar => 'wibble') do
108
+ txn.rollback # Another "shouldn't happen"
109
+ end.errback do
110
+ txn.commit
111
+ end
112
+ end
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end
118
+ end
@@ -33,6 +33,20 @@ describe "PG::EM::Client::Helper#db_transaction" do
33
33
  end
34
34
  end
35
35
 
36
+ it "fails the transaction if COMMIT fails" do
37
+ dbl = double
38
+ expect(dbl).to receive(:foo)
39
+ expect(dbl).to_not receive(:bar)
40
+
41
+ in_em do
42
+ expect_query("BEGIN")
43
+ expect_query_failure("COMMIT")
44
+ in_transaction do |txn|
45
+ txn.commit
46
+ end.callback { dbl.bar }.errback { dbl.foo }
47
+ end
48
+ end
49
+
36
50
  it "runs a simple INSERT correctly" do
37
51
  in_em do
38
52
  expect_query("BEGIN")
@@ -219,4 +233,21 @@ describe "PG::EM::Client::Helper#db_transaction" do
219
233
  end
220
234
  end
221
235
  end
236
+
237
+ it "doesn't rollback back after a failed INSERT with autorollback = false" do
238
+ in_em do
239
+ expect_query("BEGIN")
240
+ expect_query_failure('INSERT INTO "foo" ("bar") VALUES ($1)', ["baz"])
241
+ expect_query('INSERT INTO "foo" ("bar") VALUES ($1)', ["wombat"])
242
+ expect_query("COMMIT")
243
+ in_transaction do |txn|
244
+ txn.autorollback_on_error = false
245
+ txn.insert("foo", :bar => 'baz').errback do
246
+ txn.insert("foo", :bar => 'wombat') do
247
+ txn.commit
248
+ end
249
+ end
250
+ end
251
+ end
252
+ end
222
253
  end
data/spec/txn_helper.rb CHANGED
@@ -23,9 +23,16 @@ module TxnHelper
23
23
  end
24
24
 
25
25
  def in_em
26
- EM.run do
27
- EM.add_timer(0.5) { EM.stop; raise "test timeout" }
28
- yield
26
+ begin
27
+ Timeout.timeout(0.5) do
28
+ EM.run do
29
+ yield
30
+ end
31
+ end
32
+ rescue Timeout::Error
33
+ EM.stop if EM.reactor_running?
34
+ raise RuntimeError,
35
+ "EM test time exceeded"
29
36
  end
30
37
  end
31
38
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: em-pg-client-helper
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.3.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Matt Palmer
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-03-03 00:00:00.000000000 Z
11
+ date: 2015-03-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: em-pg-client
@@ -230,7 +230,9 @@ files:
230
230
  - lib/em-pg-client-helper.rb
231
231
  - lib/em-pg-client-helper/deferrable_group.rb
232
232
  - lib/em-pg-client-helper/transaction.rb
233
+ - spec/db_bulk_insert_spec.rb
233
234
  - spec/db_insert_spec.rb
235
+ - spec/db_transaction_savepoint_spec.rb
234
236
  - spec/db_transaction_sequel_spec.rb
235
237
  - spec/db_transaction_spec.rb
236
238
  - spec/db_transaction_upsert_spec.rb