sequel 3.45.0 → 3.46.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.
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