sequel 3.45.0 → 3.46.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|