sequel 3.45.0 → 3.46.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG +34 -0
  3. data/README.rdoc +6 -0
  4. data/Rakefile +46 -33
  5. data/doc/release_notes/3.46.0.txt +122 -0
  6. data/doc/schema_modification.rdoc +42 -6
  7. data/doc/security.rdoc +379 -0
  8. data/doc/transactions.rdoc +1 -1
  9. data/lib/sequel/adapters/jdbc/as400.rb +1 -0
  10. data/lib/sequel/adapters/jdbc/h2.rb +11 -0
  11. data/lib/sequel/adapters/mysql2.rb +3 -9
  12. data/lib/sequel/adapters/postgres.rb +34 -2
  13. data/lib/sequel/adapters/shared/cubrid.rb +5 -0
  14. data/lib/sequel/adapters/shared/mssql.rb +27 -3
  15. data/lib/sequel/adapters/shared/mysql.rb +25 -4
  16. data/lib/sequel/adapters/shared/sqlite.rb +12 -1
  17. data/lib/sequel/connection_pool.rb +3 -3
  18. data/lib/sequel/connection_pool/sharded_threaded.rb +7 -8
  19. data/lib/sequel/connection_pool/threaded.rb +7 -8
  20. data/lib/sequel/core.rb +5 -2
  21. data/lib/sequel/database.rb +1 -1
  22. data/lib/sequel/database/connecting.rb +7 -7
  23. data/lib/sequel/database/features.rb +88 -0
  24. data/lib/sequel/database/misc.rb +14 -64
  25. data/lib/sequel/database/query.rb +0 -332
  26. data/lib/sequel/database/schema_generator.rb +36 -3
  27. data/lib/sequel/database/schema_methods.rb +48 -12
  28. data/lib/sequel/database/transactions.rb +344 -0
  29. data/lib/sequel/dataset/actions.rb +24 -9
  30. data/lib/sequel/dataset/mutation.rb +20 -0
  31. data/lib/sequel/dataset/query.rb +0 -17
  32. data/lib/sequel/dataset/sql.rb +7 -0
  33. data/lib/sequel/exceptions.rb +10 -6
  34. data/lib/sequel/extensions/_pretty_table.rb +2 -2
  35. data/lib/sequel/extensions/looser_typecasting.rb +10 -0
  36. data/lib/sequel/extensions/migration.rb +5 -2
  37. data/lib/sequel/model.rb +1 -1
  38. data/lib/sequel/model/associations.rb +16 -14
  39. data/lib/sequel/model/base.rb +14 -2
  40. data/lib/sequel/plugins/composition.rb +3 -3
  41. data/lib/sequel/plugins/dirty.rb +6 -6
  42. data/lib/sequel/plugins/hook_class_methods.rb +3 -0
  43. data/lib/sequel/plugins/serialization.rb +7 -17
  44. data/lib/sequel/plugins/string_stripper.rb +2 -1
  45. data/lib/sequel/plugins/validation_helpers.rb +1 -1
  46. data/lib/sequel/sql.rb +3 -0
  47. data/lib/sequel/version.rb +1 -1
  48. data/spec/adapters/mssql_spec.rb +21 -0
  49. data/spec/adapters/postgres_spec.rb +35 -8
  50. data/spec/core/database_spec.rb +4 -0
  51. data/spec/core/dataset_spec.rb +48 -2
  52. data/spec/core/schema_generator_spec.rb +10 -1
  53. data/spec/core/schema_spec.rb +69 -0
  54. data/spec/extensions/composition_spec.rb +21 -2
  55. data/spec/extensions/dirty_spec.rb +17 -10
  56. data/spec/extensions/eager_each_spec.rb +4 -1
  57. data/spec/extensions/looser_typecasting_spec.rb +16 -19
  58. data/spec/extensions/migration_spec.rb +7 -1
  59. data/spec/extensions/serialization_spec.rb +22 -0
  60. data/spec/extensions/single_table_inheritance_spec.rb +3 -2
  61. data/spec/extensions/validation_helpers_spec.rb +6 -0
  62. data/spec/integration/dataset_test.rb +5 -0
  63. data/spec/integration/schema_test.rb +16 -0
  64. data/spec/model/associations_spec.rb +40 -0
  65. data/spec/model/base_spec.rb +21 -1
  66. data/spec/model/record_spec.rb +3 -0
  67. metadata +14 -10
@@ -44,6 +44,9 @@ module Sequel
44
44
  # with that type as a constant. Types given should either already
45
45
  # be constants/classes or a capitalized string/symbol with the same name
46
46
  # as a constant/class.
47
+ #
48
+ # Do not call this method with untrusted input, as that can result in
49
+ # arbitrary code execution.
47
50
  def self.add_type_method(*types)
48
51
  types.each do |type|
49
52
  class_eval("def #{type}(name, opts={}); column(name, #{type}, opts); end", __FILE__, __LINE__)
@@ -99,10 +102,12 @@ module Sequel
99
102
  # (:restrict, :cascade, :set_null, :set_default, :no_action).
100
103
  # :primary_key :: Make the column as a single primary key column. This should only
101
104
  # be used if you have a single, nonautoincrementing primary key column.
105
+ # :primary_key_constraint_name :: The name to give the primary key constraint
102
106
  # :type :: Overrides the type given as the argument. Generally not used by column
103
107
  # itself, but can be passed as an option to other methods that call column.
104
108
  # :unique :: Mark the column as unique, generally has the same effect as
105
109
  # creating a unique index on the column.
110
+ # :unique_constraint_name :: The name to give the unique key constraint
106
111
  def column(name, type, opts = {})
107
112
  columns << {:name => name, :type => type}.merge(opts)
108
113
  if index_opts = opts[:index]
@@ -127,6 +132,10 @@ module Sequel
127
132
  # foreign_key(:artist_id, :artists, :key=>:id) # artist_id INTEGER REFERENCES artists(id)
128
133
  # foreign_key(:artist_id, :artists, :type=>String) # artist_id varchar(255) REFERENCES artists(id)
129
134
  #
135
+ # Additional Options:
136
+ #
137
+ # :foreign_key_constraint_name :: The name to give the foreign key constraint
138
+ #
130
139
  # If you want a foreign key constraint without adding a column (usually because it is a
131
140
  # composite foreign key), you can provide an array of columns as the first argument, and
132
141
  # you can provide the :name option to name the constraint:
@@ -206,7 +215,7 @@ module Sequel
206
215
  end
207
216
 
208
217
  # Adds an autoincrementing primary key column or a primary key constraint to the DDL.
209
- # To create a constraint, the first argument should be an array of column symbols
218
+ # To just create a constraint, the first argument should be an array of column symbols
210
219
  # specifying the primary key columns. To create an autoincrementing primary key
211
220
  # column, a single symbol can be used. In both cases, an options hash can be used
212
221
  # as the second argument.
@@ -214,10 +223,13 @@ module Sequel
214
223
  # If you want to create a primary key column that is not autoincrementing, you
215
224
  # should not use this method. Instead, you should use the regular +column+ method
216
225
  # with a <tt>:primary_key=>true</tt> option.
226
+ #
227
+ # If an array of column symbols is used, you can specify the :name option
228
+ # to name the constraint.
217
229
  #
218
230
  # Examples:
219
231
  # primary_key(:id)
220
- # primary_key([:street_number, :house_number])
232
+ # primary_key([:street_number, :house_number], :name=>:some constraint_name)
221
233
  def primary_key(name, *args)
222
234
  return composite_primary_key(name, *args) if name.is_a?(Array)
223
235
  @primary_key = @db.serial_primary_key_options.merge({:name => name})
@@ -246,7 +258,8 @@ module Sequel
246
258
  #
247
259
  # unique(:name) # UNIQUE (name)
248
260
  #
249
- # Supports the same :deferrable option as #column.
261
+ # Supports the same :deferrable option as #column. The :name option can be used
262
+ # to name the constraint.
250
263
  def unique(columns, opts = {})
251
264
  constraints << {:type => :unique, :columns => Array(columns)}.merge(opts)
252
265
  end
@@ -396,6 +409,21 @@ module Sequel
396
409
  @operations << {:op => :drop_constraint, :name => name}.merge(opts)
397
410
  end
398
411
 
412
+ # Remove a foreign key and the associated column from the DDL for the table. General options:
413
+ #
414
+ # :name :: The name of the constraint to drop. If not given, uses the same name
415
+ # that would be used by add_foreign_key with the same columns.
416
+ #
417
+ # NOTE: If you want to drop only the foreign key constraint but keep the column,
418
+ # use the composite key syntax even if it is only one column.
419
+ #
420
+ # drop_foreign_key(:artist_id) # DROP CONSTRAINT table_artist_id_fkey, DROP COLUMN artist_id
421
+ # drop_foreign_key([:name]) # DROP CONSTRAINT table_name_fkey
422
+ def drop_foreign_key(name, opts={})
423
+ drop_composite_foreign_key(Array(name), opts)
424
+ drop_column(name) unless name.is_a?(Array)
425
+ end
426
+
399
427
  # Remove an index from the DDL for the table. General options:
400
428
  #
401
429
  # :name :: The name of the index to drop. If not given, uses the same name
@@ -465,6 +493,11 @@ module Sequel
465
493
  def add_composite_foreign_key(columns, table, opts)
466
494
  @operations << {:op => :add_constraint, :type => :foreign_key, :columns => columns, :table => table}.merge(opts)
467
495
  end
496
+
497
+ # Drop a composite foreign key constraint
498
+ def drop_composite_foreign_key(columns, opts)
499
+ @operations << {:op => :drop_constraint, :type => :foreign_key, :columns => columns}.merge(opts)
500
+ end
468
501
  end
469
502
  end
470
503
  end
@@ -383,6 +383,9 @@ module Sequel
383
383
  when :add_constraint
384
384
  "ADD #{constraint_definition_sql(op)}"
385
385
  when :drop_constraint
386
+ if op[:type] == :foreign_key
387
+ quoted_name ||= quote_identifier(foreign_key_name(table, op[:columns]))
388
+ end
386
389
  "DROP CONSTRAINT #{quoted_name}#{' CASCADE' if op[:cascade]}"
387
390
  else
388
391
  raise Error, "Unsupported ALTER TABLE operation: #{op[:op]}"
@@ -416,7 +419,7 @@ module Sequel
416
419
  last_combinable = true
417
420
  end
418
421
  elsif sql = alter_table_sql(table, op)
419
- grouped_ops << sql
422
+ Array(sql).each{|s| grouped_ops << s}
420
423
  last_combinable = false
421
424
  end
422
425
  end
@@ -474,17 +477,32 @@ module Sequel
474
477
 
475
478
  # Add primary key SQL fragment to column creation SQL.
476
479
  def column_definition_primary_key_sql(sql, column)
477
- sql << PRIMARY_KEY if column[:primary_key]
480
+ if column[:primary_key]
481
+ if name = column[:primary_key_constraint_name]
482
+ sql << " CONSTRAINT #{quote_identifier(name)}"
483
+ end
484
+ sql << PRIMARY_KEY
485
+ end
478
486
  end
479
487
 
480
488
  # Add foreign key reference SQL fragment to column creation SQL.
481
489
  def column_definition_references_sql(sql, column)
482
- sql << column_references_column_constraint_sql(column) if column[:table]
490
+ if column[:table]
491
+ if name = column[:foreign_key_constraint_name]
492
+ sql << " CONSTRAINT #{quote_identifier(name)}"
493
+ end
494
+ sql << column_references_column_constraint_sql(column)
495
+ end
483
496
  end
484
497
 
485
498
  # Add unique constraint SQL fragment to column creation SQL.
486
499
  def column_definition_unique_sql(sql, column)
487
- sql << UNIQUE if column[:unique]
500
+ if column[:unique]
501
+ if name = column[:unique_constraint_name]
502
+ sql << " CONSTRAINT #{quote_identifier(name)}"
503
+ end
504
+ sql << UNIQUE
505
+ end
488
506
  end
489
507
 
490
508
  # SQL for all given columns, used inside a CREATE TABLE block.
@@ -528,11 +546,11 @@ module Sequel
528
546
  when :primary_key
529
547
  sql << "PRIMARY KEY #{literal(constraint[:columns])}"
530
548
  when :foreign_key
531
- sql << column_references_table_constraint_sql(constraint)
549
+ sql << column_references_table_constraint_sql(constraint.merge(:deferrable=>nil))
532
550
  when :unique
533
551
  sql << "UNIQUE #{literal(constraint[:columns])}"
534
552
  else
535
- raise Error, "Invalid constriant type #{constraint[:type]}, should be :check, :primary_key, :foreign_key, or :unique"
553
+ raise Error, "Invalid constraint type #{constraint[:type]}, should be :check, :primary_key, :foreign_key, or :unique"
536
554
  end
537
555
  constraint_deferrable_sql_append(sql, constraint[:deferrable])
538
556
  sql
@@ -575,6 +593,23 @@ module Sequel
575
593
 
576
594
  # DDL statement for creating a table with the given name, columns, and options
577
595
  def create_table_sql(name, generator, options)
596
+ unless supports_named_column_constraints?
597
+ # Split column constraints into table constraints if they have a name
598
+ generator.columns.each do |c|
599
+ if (constraint_name = c.delete(:foreign_key_constraint_name)) && (table = c.delete(:table))
600
+ opts = {}
601
+ opts[:name] = constraint_name
602
+ [:key, :on_delete, :on_update, :deferrable].each{|k| opts[k] = c[k]}
603
+ generator.foreign_key([c[:name]], table, opts)
604
+ end
605
+ if (constraint_name = c.delete(:unique_constraint_name)) && c.delete(:unique)
606
+ generator.unique(c[:name], :name=>constraint_name)
607
+ end
608
+ if (constraint_name = c.delete(:primary_key_constraint_name)) && c.delete(:primary_key)
609
+ generator.primary_key([c[:name]], :name=>constraint_name)
610
+ end
611
+ end
612
+ end
578
613
  "#{create_table_prefix_sql(name, options)} (#{column_list_sql(generator)})"
579
614
  end
580
615
 
@@ -614,6 +649,13 @@ module Sequel
614
649
  "#{"#{schema}_" if schema and schema != default_schema}#{table}_#{columns.map{|c| [String, Symbol].any?{|cl| c.is_a?(cl)} ? c : literal(c).gsub(/\W/, '_')}.join(UNDERSCORE)}_index"
615
650
  end
616
651
 
652
+ # Get foreign key name for given table and columns.
653
+ def foreign_key_name(table_name, columns)
654
+ keys = foreign_key_list(table_name).select{|key| key[:columns] == columns}
655
+ raise(Error, "#{keys.empty? ? 'Missing' : 'Ambiguous'} foreign key for #{columns.inspect}") unless keys.size == 1
656
+ keys.first[:name]
657
+ end
658
+
617
659
  # The SQL to drop an index for the table.
618
660
  def drop_index_sql(table, op)
619
661
  "DROP INDEX #{quote_identifier(op[:name] || default_index_name(table, op[:columns]))}"
@@ -736,12 +778,6 @@ module Sequel
736
778
  schema_utility_dataset.split_qualifiers(table_name)
737
779
  end
738
780
 
739
- # Whether the database supports combining multiple alter table
740
- # operations into a single query, false by default.
741
- def supports_combining_alter_table_ops?
742
- false
743
- end
744
-
745
781
  # SQL DDL fragment for temporary table
746
782
  def temporary_table_sql
747
783
  self.class.const_get(:TEMPORARY)
@@ -0,0 +1,344 @@
1
+ module Sequel
2
+ class Database
3
+ # ---------------------
4
+ # :section: 8 - Methods related to database transactions
5
+ # Database transactions make multiple queries atomic, so
6
+ # that either all of the queries take effect or none of
7
+ # them do.
8
+ # ---------------------
9
+
10
+ SQL_BEGIN = 'BEGIN'.freeze
11
+ SQL_COMMIT = 'COMMIT'.freeze
12
+ SQL_RELEASE_SAVEPOINT = 'RELEASE SAVEPOINT autopoint_%d'.freeze
13
+ SQL_ROLLBACK = 'ROLLBACK'.freeze
14
+ SQL_ROLLBACK_TO_SAVEPOINT = 'ROLLBACK TO SAVEPOINT autopoint_%d'.freeze
15
+ SQL_SAVEPOINT = 'SAVEPOINT autopoint_%d'.freeze
16
+
17
+ TRANSACTION_BEGIN = 'Transaction.begin'.freeze
18
+ TRANSACTION_COMMIT = 'Transaction.commit'.freeze
19
+ TRANSACTION_ROLLBACK = 'Transaction.rollback'.freeze
20
+
21
+ TRANSACTION_ISOLATION_LEVELS = {:uncommitted=>'READ UNCOMMITTED'.freeze,
22
+ :committed=>'READ COMMITTED'.freeze,
23
+ :repeatable=>'REPEATABLE READ'.freeze,
24
+ :serializable=>'SERIALIZABLE'.freeze}
25
+
26
+ # The default transaction isolation level for this database,
27
+ # used for all future transactions. For MSSQL, this should be set
28
+ # to something if you ever plan to use the :isolation option to
29
+ # Database#transaction, as on MSSQL if affects all future transactions
30
+ # on the same connection.
31
+ attr_accessor :transaction_isolation_level
32
+
33
+ # Starts a database transaction. When a database transaction is used,
34
+ # either all statements are successful or none of the statements are
35
+ # successful. Note that MySQL MyISAM tables do not support transactions.
36
+ #
37
+ # The following general options are respected:
38
+ #
39
+ # :disconnect :: If set to :retry, automatically sets the :retry_on option
40
+ # with a Sequel::DatabaseDisconnectError. This option is only
41
+ # present for backwards compatibility, please use the :retry_on
42
+ # option instead.
43
+ # :isolation :: The transaction isolation level to use for this transaction,
44
+ # should be :uncommitted, :committed, :repeatable, or :serializable,
45
+ # used if given and the database/adapter supports customizable
46
+ # transaction isolation levels.
47
+ # :num_retries :: The number of times to retry if the :retry_on option is used.
48
+ # The default is 5 times. Can be set to nil to retry indefinitely,
49
+ # but that is not recommended.
50
+ # :prepare :: A string to use as the transaction identifier for a
51
+ # prepared transaction (two-phase commit), if the database/adapter
52
+ # supports prepared transactions.
53
+ # :retry_on :: An exception class or array of exception classes for which to
54
+ # automatically retry the transaction. Can only be set if not inside
55
+ # an existing transaction.
56
+ # Note that this should not be used unless the entire transaction
57
+ # block is idempotent, as otherwise it can cause non-idempotent
58
+ # behavior to execute multiple times.
59
+ # :rollback :: Can the set to :reraise to reraise any Sequel::Rollback exceptions
60
+ # raised, or :always to always rollback even if no exceptions occur
61
+ # (useful for testing).
62
+ # :server :: The server to use for the transaction.
63
+ # :savepoint :: Whether to create a new savepoint for this transaction,
64
+ # only respected if the database/adapter supports savepoints. By
65
+ # default Sequel will reuse an existing transaction, so if you want to
66
+ # use a savepoint you must use this option.
67
+ #
68
+ # PostgreSQL specific options:
69
+ #
70
+ # :deferrable :: (9.1+) If present, set to DEFERRABLE if true or NOT DEFERRABLE if false.
71
+ # :read_only :: If present, set to READ ONLY if true or READ WRITE if false.
72
+ # :synchronous :: if non-nil, set synchronous_commit
73
+ # appropriately. Valid values true, :on, false, :off, :local (9.1+),
74
+ # and :remote_write (9.2+).
75
+ def transaction(opts={}, &block)
76
+ if opts[:disconnect] == :retry
77
+ raise(Error, 'cannot specify both :disconnect=>:retry and :retry_on') if opts[:retry_on]
78
+ return transaction(opts.merge(:retry_on=>Sequel::DatabaseDisconnectError, :disconnect=>nil), &block)
79
+ end
80
+
81
+ if retry_on = opts[:retry_on]
82
+ num_retries = opts.fetch(:num_retries, 5)
83
+ begin
84
+ transaction(opts.merge(:retry_on=>nil, :retrying=>true), &block)
85
+ rescue *retry_on
86
+ if num_retries
87
+ num_retries -= 1
88
+ retry if num_retries >= 0
89
+ else
90
+ retry
91
+ end
92
+ raise
93
+ end
94
+ else
95
+ synchronize(opts[:server]) do |conn|
96
+ if already_in_transaction?(conn, opts)
97
+ if opts[:retrying]
98
+ raise Sequel::Error, "cannot set :disconnect=>:retry or :retry_on options if you are already inside a transaction"
99
+ end
100
+ return yield(conn)
101
+ end
102
+ _transaction(conn, opts, &block)
103
+ end
104
+ end
105
+ end
106
+
107
+ private
108
+
109
+ # Internal generic transaction method. Any exception raised by the given
110
+ # block will cause the transaction to be rolled back. If the exception is
111
+ # not a Sequel::Rollback, the error will be reraised. If no exception occurs
112
+ # inside the block, the transaction is commited.
113
+ def _transaction(conn, opts={})
114
+ rollback = opts[:rollback]
115
+ begin
116
+ add_transaction(conn, opts)
117
+ begin_transaction(conn, opts)
118
+ if rollback == :always
119
+ begin
120
+ yield(conn)
121
+ rescue Exception => e1
122
+ raise e1
123
+ ensure
124
+ raise ::Sequel::Rollback unless e1
125
+ end
126
+ else
127
+ yield(conn)
128
+ end
129
+ rescue Exception => e
130
+ begin
131
+ rollback_transaction(conn, opts)
132
+ rescue Exception => e3
133
+ raise_error(e3, :classes=>database_error_classes, :conn=>conn)
134
+ end
135
+ transaction_error(e, :conn=>conn, :rollback=>rollback)
136
+ ensure
137
+ begin
138
+ committed = commit_or_rollback_transaction(e, conn, opts)
139
+ rescue Exception => e2
140
+ begin
141
+ raise_error(e2, :classes=>database_error_classes, :conn=>conn)
142
+ rescue Sequel::DatabaseError => e4
143
+ begin
144
+ rollback_transaction(conn, opts)
145
+ ensure
146
+ raise e4
147
+ end
148
+ end
149
+ ensure
150
+ remove_transaction(conn, committed)
151
+ end
152
+ end
153
+ end
154
+
155
+ # Synchronize access to the current transactions, returning the hash
156
+ # of options for the current transaction (if any)
157
+ def _trans(conn)
158
+ Sequel.synchronize{@transactions[conn]}
159
+ end
160
+
161
+ # Add the current thread to the list of active transactions
162
+ def add_transaction(conn, opts)
163
+ if supports_savepoints?
164
+ unless _trans(conn)
165
+ if (prep = opts[:prepare]) && supports_prepared_transactions?
166
+ Sequel.synchronize{@transactions[conn] = {:savepoint_level=>0, :prepare=>prep}}
167
+ else
168
+ Sequel.synchronize{@transactions[conn] = {:savepoint_level=>0}}
169
+ end
170
+ end
171
+ elsif (prep = opts[:prepare]) && supports_prepared_transactions?
172
+ Sequel.synchronize{@transactions[conn] = {:prepare => prep}}
173
+ else
174
+ Sequel.synchronize{@transactions[conn] = {}}
175
+ end
176
+ end
177
+
178
+ # Call all stored after_commit blocks for the given transaction
179
+ def after_transaction_commit(conn)
180
+ if ary = _trans(conn)[:after_commit]
181
+ ary.each{|b| b.call}
182
+ end
183
+ end
184
+
185
+ # Call all stored after_rollback blocks for the given transaction
186
+ def after_transaction_rollback(conn)
187
+ if ary = _trans(conn)[:after_rollback]
188
+ ary.each{|b| b.call}
189
+ end
190
+ end
191
+
192
+ # Whether the current thread/connection is already inside a transaction
193
+ def already_in_transaction?(conn, opts)
194
+ _trans(conn) && (!supports_savepoints? || !opts[:savepoint])
195
+ end
196
+
197
+ # SQL to start a new savepoint
198
+ def begin_savepoint_sql(depth)
199
+ SQL_SAVEPOINT % depth
200
+ end
201
+
202
+ # Start a new database connection on the given connection
203
+ def begin_new_transaction(conn, opts)
204
+ log_connection_execute(conn, begin_transaction_sql)
205
+ set_transaction_isolation(conn, opts)
206
+ end
207
+
208
+ # Start a new database transaction or a new savepoint on the given connection.
209
+ def begin_transaction(conn, opts={})
210
+ if supports_savepoints?
211
+ th = _trans(conn)
212
+ if (depth = th[:savepoint_level]) > 0
213
+ log_connection_execute(conn, begin_savepoint_sql(depth))
214
+ else
215
+ begin_new_transaction(conn, opts)
216
+ end
217
+ th[:savepoint_level] += 1
218
+ else
219
+ begin_new_transaction(conn, opts)
220
+ end
221
+ end
222
+
223
+ # SQL to BEGIN a transaction.
224
+ def begin_transaction_sql
225
+ SQL_BEGIN
226
+ end
227
+
228
+ if (! defined?(RUBY_ENGINE) or RUBY_ENGINE == 'ruby' or RUBY_ENGINE == 'rbx') and RUBY_VERSION < '1.9'
229
+ # :nocov:
230
+ # Whether to commit the current transaction. On ruby 1.8 and rubinius,
231
+ # Thread.current.status is checked because Thread#kill skips rescue
232
+ # blocks (so exception would be nil), but the transaction should
233
+ # still be rolled back.
234
+ def commit_or_rollback_transaction(exception, conn, opts)
235
+ if exception
236
+ false
237
+ else
238
+ if Thread.current.status == 'aborting'
239
+ rollback_transaction(conn, opts)
240
+ false
241
+ else
242
+ commit_transaction(conn, opts)
243
+ true
244
+ end
245
+ end
246
+ end
247
+ # :nocov:
248
+ else
249
+ # Whether to commit the current transaction. On ruby 1.9 and JRuby,
250
+ # transactions will be committed if Thread#kill is used on an thread
251
+ # that has a transaction open, and there isn't a work around.
252
+ def commit_or_rollback_transaction(exception, conn, opts)
253
+ if exception
254
+ false
255
+ else
256
+ commit_transaction(conn, opts)
257
+ true
258
+ end
259
+ end
260
+ end
261
+
262
+ # SQL to commit a savepoint
263
+ def commit_savepoint_sql(depth)
264
+ SQL_RELEASE_SAVEPOINT % depth
265
+ end
266
+
267
+ # Commit the active transaction on the connection
268
+ def commit_transaction(conn, opts={})
269
+ if supports_savepoints?
270
+ depth = _trans(conn)[:savepoint_level]
271
+ log_connection_execute(conn, depth > 1 ? commit_savepoint_sql(depth-1) : commit_transaction_sql)
272
+ else
273
+ log_connection_execute(conn, commit_transaction_sql)
274
+ end
275
+ end
276
+
277
+ # SQL to COMMIT a transaction.
278
+ def commit_transaction_sql
279
+ SQL_COMMIT
280
+ end
281
+
282
+ # Method called on the connection object to execute SQL on the database,
283
+ # used by the transaction code.
284
+ def connection_execute_method
285
+ :execute
286
+ end
287
+
288
+ # Remove the current thread from the list of active transactions
289
+ def remove_transaction(conn, committed)
290
+ if !supports_savepoints? || ((_trans(conn)[:savepoint_level] -= 1) <= 0)
291
+ begin
292
+ if committed
293
+ after_transaction_commit(conn)
294
+ else
295
+ after_transaction_rollback(conn)
296
+ end
297
+ ensure
298
+ Sequel.synchronize{@transactions.delete(conn)}
299
+ end
300
+ end
301
+ end
302
+
303
+ # SQL to rollback to a savepoint
304
+ def rollback_savepoint_sql(depth)
305
+ SQL_ROLLBACK_TO_SAVEPOINT % depth
306
+ end
307
+
308
+ # Rollback the active transaction on the connection
309
+ def rollback_transaction(conn, opts={})
310
+ if supports_savepoints?
311
+ depth = _trans(conn)[:savepoint_level]
312
+ log_connection_execute(conn, depth > 1 ? rollback_savepoint_sql(depth-1) : rollback_transaction_sql)
313
+ else
314
+ log_connection_execute(conn, rollback_transaction_sql)
315
+ end
316
+ end
317
+
318
+ # SQL to ROLLBACK a transaction.
319
+ def rollback_transaction_sql
320
+ SQL_ROLLBACK
321
+ end
322
+
323
+ # Set the transaction isolation level on the given connection
324
+ def set_transaction_isolation(conn, opts)
325
+ if supports_transaction_isolation_levels? and level = opts.fetch(:isolation, transaction_isolation_level)
326
+ log_connection_execute(conn, set_transaction_isolation_sql(level))
327
+ end
328
+ end
329
+
330
+ # SQL to set the transaction isolation level
331
+ def set_transaction_isolation_sql(level)
332
+ "SET TRANSACTION ISOLATION LEVEL #{TRANSACTION_ISOLATION_LEVELS[level]}"
333
+ end
334
+
335
+ # Raise a database error unless the exception is an Rollback.
336
+ def transaction_error(e, opts={})
337
+ if e.is_a?(Rollback)
338
+ raise e if opts[:rollback] == :reraise
339
+ else
340
+ raise_error(e, opts.merge(:classes=>database_error_classes))
341
+ end
342
+ end
343
+ end
344
+ end