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.
- checksums.yaml +7 -0
- data/CHANGELOG +34 -0
- data/README.rdoc +6 -0
- data/Rakefile +46 -33
- data/doc/release_notes/3.46.0.txt +122 -0
- data/doc/schema_modification.rdoc +42 -6
- data/doc/security.rdoc +379 -0
- data/doc/transactions.rdoc +1 -1
- data/lib/sequel/adapters/jdbc/as400.rb +1 -0
- data/lib/sequel/adapters/jdbc/h2.rb +11 -0
- data/lib/sequel/adapters/mysql2.rb +3 -9
- data/lib/sequel/adapters/postgres.rb +34 -2
- data/lib/sequel/adapters/shared/cubrid.rb +5 -0
- data/lib/sequel/adapters/shared/mssql.rb +27 -3
- data/lib/sequel/adapters/shared/mysql.rb +25 -4
- data/lib/sequel/adapters/shared/sqlite.rb +12 -1
- data/lib/sequel/connection_pool.rb +3 -3
- data/lib/sequel/connection_pool/sharded_threaded.rb +7 -8
- data/lib/sequel/connection_pool/threaded.rb +7 -8
- data/lib/sequel/core.rb +5 -2
- data/lib/sequel/database.rb +1 -1
- data/lib/sequel/database/connecting.rb +7 -7
- data/lib/sequel/database/features.rb +88 -0
- data/lib/sequel/database/misc.rb +14 -64
- data/lib/sequel/database/query.rb +0 -332
- data/lib/sequel/database/schema_generator.rb +36 -3
- data/lib/sequel/database/schema_methods.rb +48 -12
- data/lib/sequel/database/transactions.rb +344 -0
- data/lib/sequel/dataset/actions.rb +24 -9
- data/lib/sequel/dataset/mutation.rb +20 -0
- data/lib/sequel/dataset/query.rb +0 -17
- data/lib/sequel/dataset/sql.rb +7 -0
- data/lib/sequel/exceptions.rb +10 -6
- data/lib/sequel/extensions/_pretty_table.rb +2 -2
- data/lib/sequel/extensions/looser_typecasting.rb +10 -0
- data/lib/sequel/extensions/migration.rb +5 -2
- data/lib/sequel/model.rb +1 -1
- data/lib/sequel/model/associations.rb +16 -14
- data/lib/sequel/model/base.rb +14 -2
- data/lib/sequel/plugins/composition.rb +3 -3
- data/lib/sequel/plugins/dirty.rb +6 -6
- data/lib/sequel/plugins/hook_class_methods.rb +3 -0
- data/lib/sequel/plugins/serialization.rb +7 -17
- data/lib/sequel/plugins/string_stripper.rb +2 -1
- data/lib/sequel/plugins/validation_helpers.rb +1 -1
- data/lib/sequel/sql.rb +3 -0
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/mssql_spec.rb +21 -0
- data/spec/adapters/postgres_spec.rb +35 -8
- data/spec/core/database_spec.rb +4 -0
- data/spec/core/dataset_spec.rb +48 -2
- data/spec/core/schema_generator_spec.rb +10 -1
- data/spec/core/schema_spec.rb +69 -0
- data/spec/extensions/composition_spec.rb +21 -2
- data/spec/extensions/dirty_spec.rb +17 -10
- data/spec/extensions/eager_each_spec.rb +4 -1
- data/spec/extensions/looser_typecasting_spec.rb +16 -19
- data/spec/extensions/migration_spec.rb +7 -1
- data/spec/extensions/serialization_spec.rb +22 -0
- data/spec/extensions/single_table_inheritance_spec.rb +3 -2
- data/spec/extensions/validation_helpers_spec.rb +6 -0
- data/spec/integration/dataset_test.rb +5 -0
- data/spec/integration/schema_test.rb +16 -0
- data/spec/model/associations_spec.rb +40 -0
- data/spec/model/base_spec.rb +21 -1
- data/spec/model/record_spec.rb +3 -0
- 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 <<
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|