sequel 3.28.0 → 3.29.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +119 -3
- data/Rakefile +5 -3
- data/bin/sequel +1 -5
- data/doc/model_hooks.rdoc +9 -1
- data/doc/opening_databases.rdoc +49 -40
- data/doc/prepared_statements.rdoc +27 -6
- data/doc/release_notes/3.28.0.txt +2 -2
- data/doc/release_notes/3.29.0.txt +459 -0
- data/doc/sharding.rdoc +7 -1
- data/doc/testing.rdoc +18 -9
- data/doc/transactions.rdoc +41 -1
- data/lib/sequel/adapters/ado.rb +28 -17
- data/lib/sequel/adapters/ado/mssql.rb +18 -6
- data/lib/sequel/adapters/amalgalite.rb +11 -7
- data/lib/sequel/adapters/db2.rb +122 -70
- data/lib/sequel/adapters/dbi.rb +15 -15
- data/lib/sequel/adapters/do.rb +5 -36
- data/lib/sequel/adapters/do/mysql.rb +0 -5
- data/lib/sequel/adapters/do/postgres.rb +0 -5
- data/lib/sequel/adapters/do/sqlite.rb +0 -5
- data/lib/sequel/adapters/firebird.rb +3 -6
- data/lib/sequel/adapters/ibmdb.rb +24 -16
- data/lib/sequel/adapters/informix.rb +2 -4
- data/lib/sequel/adapters/jdbc.rb +47 -11
- data/lib/sequel/adapters/jdbc/as400.rb +5 -24
- data/lib/sequel/adapters/jdbc/db2.rb +0 -5
- data/lib/sequel/adapters/jdbc/derby.rb +217 -0
- data/lib/sequel/adapters/jdbc/firebird.rb +0 -5
- data/lib/sequel/adapters/jdbc/h2.rb +10 -12
- data/lib/sequel/adapters/jdbc/hsqldb.rb +166 -0
- data/lib/sequel/adapters/jdbc/informix.rb +0 -5
- data/lib/sequel/adapters/jdbc/jtds.rb +0 -5
- data/lib/sequel/adapters/jdbc/mysql.rb +0 -10
- data/lib/sequel/adapters/jdbc/oracle.rb +70 -3
- data/lib/sequel/adapters/jdbc/postgresql.rb +0 -11
- data/lib/sequel/adapters/jdbc/sqlite.rb +0 -5
- data/lib/sequel/adapters/jdbc/sqlserver.rb +0 -5
- data/lib/sequel/adapters/jdbc/transactions.rb +56 -7
- data/lib/sequel/adapters/mock.rb +315 -0
- data/lib/sequel/adapters/mysql.rb +64 -51
- data/lib/sequel/adapters/mysql2.rb +15 -9
- data/lib/sequel/adapters/odbc.rb +13 -6
- data/lib/sequel/adapters/odbc/db2.rb +0 -4
- data/lib/sequel/adapters/odbc/mssql.rb +0 -5
- data/lib/sequel/adapters/openbase.rb +2 -4
- data/lib/sequel/adapters/oracle.rb +333 -51
- data/lib/sequel/adapters/postgres.rb +80 -27
- data/lib/sequel/adapters/shared/access.rb +0 -6
- data/lib/sequel/adapters/shared/db2.rb +13 -15
- data/lib/sequel/adapters/shared/firebird.rb +6 -6
- data/lib/sequel/adapters/shared/mssql.rb +23 -18
- data/lib/sequel/adapters/shared/mysql.rb +6 -6
- data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +6 -0
- data/lib/sequel/adapters/shared/oracle.rb +185 -30
- data/lib/sequel/adapters/shared/postgres.rb +35 -18
- data/lib/sequel/adapters/shared/progress.rb +0 -6
- data/lib/sequel/adapters/shared/sqlite.rb +116 -37
- data/lib/sequel/adapters/sqlite.rb +16 -8
- data/lib/sequel/adapters/swift.rb +5 -5
- data/lib/sequel/adapters/swift/mysql.rb +0 -5
- data/lib/sequel/adapters/swift/postgres.rb +0 -5
- data/lib/sequel/adapters/swift/sqlite.rb +6 -4
- data/lib/sequel/adapters/tinytds.rb +13 -10
- data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +8 -0
- data/lib/sequel/core.rb +40 -0
- data/lib/sequel/database/connecting.rb +1 -2
- data/lib/sequel/database/dataset.rb +3 -3
- data/lib/sequel/database/dataset_defaults.rb +58 -0
- data/lib/sequel/database/misc.rb +62 -2
- data/lib/sequel/database/query.rb +113 -49
- data/lib/sequel/database/schema_methods.rb +7 -2
- data/lib/sequel/dataset/actions.rb +37 -19
- data/lib/sequel/dataset/features.rb +24 -0
- data/lib/sequel/dataset/graph.rb +7 -6
- data/lib/sequel/dataset/misc.rb +11 -3
- data/lib/sequel/dataset/mutation.rb +2 -3
- data/lib/sequel/dataset/prepared_statements.rb +6 -4
- data/lib/sequel/dataset/query.rb +46 -15
- data/lib/sequel/dataset/sql.rb +28 -4
- data/lib/sequel/extensions/named_timezones.rb +5 -0
- data/lib/sequel/extensions/thread_local_timezones.rb +1 -1
- data/lib/sequel/model.rb +2 -1
- data/lib/sequel/model/associations.rb +115 -33
- data/lib/sequel/model/base.rb +91 -31
- data/lib/sequel/plugins/class_table_inheritance.rb +4 -4
- data/lib/sequel/plugins/dataset_associations.rb +100 -0
- data/lib/sequel/plugins/force_encoding.rb +6 -6
- data/lib/sequel/plugins/identity_map.rb +1 -1
- data/lib/sequel/plugins/many_through_many.rb +6 -10
- data/lib/sequel/plugins/prepared_statements.rb +12 -1
- data/lib/sequel/plugins/prepared_statements_associations.rb +1 -1
- data/lib/sequel/plugins/rcte_tree.rb +29 -15
- data/lib/sequel/plugins/serialization.rb +6 -1
- data/lib/sequel/plugins/sharding.rb +0 -5
- data/lib/sequel/plugins/single_table_inheritance.rb +1 -1
- data/lib/sequel/plugins/typecast_on_load.rb +9 -12
- data/lib/sequel/plugins/update_primary_key.rb +1 -1
- data/lib/sequel/timezones.rb +42 -42
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/mssql_spec.rb +29 -29
- data/spec/adapters/mysql_spec.rb +86 -104
- data/spec/adapters/oracle_spec.rb +48 -76
- data/spec/adapters/postgres_spec.rb +98 -33
- data/spec/adapters/spec_helper.rb +0 -5
- data/spec/adapters/sqlite_spec.rb +24 -21
- data/spec/core/connection_pool_spec.rb +9 -15
- data/spec/core/core_sql_spec.rb +20 -31
- data/spec/core/database_spec.rb +491 -227
- data/spec/core/dataset_spec.rb +638 -1051
- data/spec/core/expression_filters_spec.rb +0 -1
- data/spec/core/mock_adapter_spec.rb +378 -0
- data/spec/core/object_graph_spec.rb +48 -114
- data/spec/core/schema_generator_spec.rb +3 -3
- data/spec/core/schema_spec.rb +51 -114
- data/spec/core/spec_helper.rb +3 -90
- data/spec/extensions/class_table_inheritance_spec.rb +1 -1
- data/spec/extensions/dataset_associations_spec.rb +199 -0
- data/spec/extensions/instance_hooks_spec.rb +71 -0
- data/spec/extensions/named_timezones_spec.rb +22 -2
- data/spec/extensions/nested_attributes_spec.rb +3 -0
- data/spec/extensions/schema_spec.rb +1 -1
- data/spec/extensions/serialization_modification_detection_spec.rb +1 -0
- data/spec/extensions/serialization_spec.rb +5 -8
- data/spec/extensions/spec_helper.rb +4 -0
- data/spec/extensions/thread_local_timezones_spec.rb +22 -2
- data/spec/extensions/typecast_on_load_spec.rb +1 -6
- data/spec/integration/associations_test.rb +123 -12
- data/spec/integration/dataset_test.rb +140 -47
- data/spec/integration/eager_loader_test.rb +19 -21
- data/spec/integration/model_test.rb +80 -1
- data/spec/integration/plugin_test.rb +179 -128
- data/spec/integration/prepared_statement_test.rb +92 -91
- data/spec/integration/schema_test.rb +42 -23
- data/spec/integration/spec_helper.rb +25 -31
- data/spec/integration/timezone_test.rb +38 -12
- data/spec/integration/transaction_test.rb +161 -34
- data/spec/integration/type_test.rb +3 -3
- data/spec/model/association_reflection_spec.rb +83 -7
- data/spec/model/associations_spec.rb +393 -676
- data/spec/model/base_spec.rb +186 -116
- data/spec/model/dataset_methods_spec.rb +7 -27
- data/spec/model/eager_loading_spec.rb +343 -867
- data/spec/model/hooks_spec.rb +160 -79
- data/spec/model/model_spec.rb +118 -165
- data/spec/model/plugins_spec.rb +7 -13
- data/spec/model/record_spec.rb +138 -207
- data/spec/model/spec_helper.rb +10 -73
- metadata +14 -8
@@ -9,11 +9,6 @@ module Sequel
|
|
9
9
|
module DatabaseMethods
|
10
10
|
include Sequel::MySQL::DatabaseMethods
|
11
11
|
|
12
|
-
# Return instance of Sequel::Swift::MySQL::Dataset with the given opts.
|
13
|
-
def dataset(opts=nil)
|
14
|
-
Sequel::Swift::MySQL::Dataset.new(self, opts)
|
15
|
-
end
|
16
|
-
|
17
12
|
private
|
18
13
|
|
19
14
|
# The database name for the given database.
|
@@ -41,11 +41,6 @@ module Sequel
|
|
41
41
|
end
|
42
42
|
end
|
43
43
|
|
44
|
-
# Return instance of Sequel::Swift::Postgres::Dataset with the given opts.
|
45
|
-
def dataset(opts=nil)
|
46
|
-
Sequel::Swift::Postgres::Dataset.new(self, opts)
|
47
|
-
end
|
48
|
-
|
49
44
|
# Run the SELECT SQL on the database and yield the rows
|
50
45
|
def execute(sql, opts={})
|
51
46
|
synchronize(opts[:server]) do |conn|
|
@@ -8,10 +8,12 @@ module Sequel
|
|
8
8
|
# Database instance methods for SQLite databases accessed via Swift.
|
9
9
|
module DatabaseMethods
|
10
10
|
include Sequel::SQLite::DatabaseMethods
|
11
|
-
|
12
|
-
#
|
13
|
-
def
|
14
|
-
|
11
|
+
|
12
|
+
# Set the correct pragmas on the connection.
|
13
|
+
def connect(opts)
|
14
|
+
c = super
|
15
|
+
connection_pragmas.each{|s| log_yield(s){c.execute(s)}}
|
16
|
+
c
|
15
17
|
end
|
16
18
|
end
|
17
19
|
|
@@ -16,11 +16,6 @@ module Sequel
|
|
16
16
|
TinyTds::Client.new(opts)
|
17
17
|
end
|
18
18
|
|
19
|
-
# Return instance of Sequel::TinyTDS::Dataset with the given options.
|
20
|
-
def dataset(opts = nil)
|
21
|
-
TinyTDS::Dataset.new(self, opts)
|
22
|
-
end
|
23
|
-
|
24
19
|
# Execute the given +sql+ on the server. If the :return option
|
25
20
|
# is present, its value should be a method symbol that is called
|
26
21
|
# on the TinyTds::Result object returned from executing the
|
@@ -150,6 +145,8 @@ module Sequel
|
|
150
145
|
|
151
146
|
class Dataset < Sequel::Dataset
|
152
147
|
include Sequel::MSSQL::DatasetMethods
|
148
|
+
|
149
|
+
Database::DatasetClass = self
|
153
150
|
|
154
151
|
# SQLite already supports named bind arguments, so use directly.
|
155
152
|
module ArgumentMapper
|
@@ -208,22 +205,28 @@ module Sequel
|
|
208
205
|
def fetch_rows(sql)
|
209
206
|
execute(sql) do |result|
|
210
207
|
each_opts = {:cache_rows=>false}
|
211
|
-
each_opts[:timezone] = :utc if
|
212
|
-
|
213
|
-
|
208
|
+
each_opts[:timezone] = :utc if db.timezone == :utc
|
209
|
+
rn = row_number_column if @opts[:offset]
|
210
|
+
columns = cols = result.fields.map{|c| output_identifier(c)}
|
211
|
+
if opts[:offset]
|
212
|
+
rn = row_number_column
|
213
|
+
columns = columns.dup
|
214
|
+
columns.delete(rn)
|
215
|
+
end
|
216
|
+
@columns = columns
|
214
217
|
if identifier_output_method
|
215
218
|
each_opts[:as] = :array
|
216
219
|
result.each(each_opts) do |r|
|
217
220
|
h = {}
|
218
221
|
cols.zip(r).each{|k, v| h[k] = v}
|
219
|
-
h.delete(
|
222
|
+
h.delete(rn) if rn
|
220
223
|
yield h
|
221
224
|
end
|
222
225
|
else
|
223
226
|
each_opts[:symbolize_keys] = true
|
224
227
|
if offset
|
225
228
|
result.each(each_opts) do |r|
|
226
|
-
r.delete(
|
229
|
+
r.delete(rn) if rn
|
227
230
|
yield r
|
228
231
|
end
|
229
232
|
else
|
@@ -51,5 +51,13 @@ module Sequel
|
|
51
51
|
limit(@opts[:limit]).
|
52
52
|
where(SQL::Identifier.new(rn) > o))
|
53
53
|
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
# This emulation adds an extra row number column that should be
|
58
|
+
# eliminated.
|
59
|
+
def offset_returns_row_number_column?
|
60
|
+
true
|
61
|
+
end
|
54
62
|
end
|
55
63
|
end
|
data/lib/sequel/core.rb
CHANGED
@@ -240,6 +240,46 @@ module Sequel
|
|
240
240
|
end
|
241
241
|
end
|
242
242
|
|
243
|
+
# Uses a transaction on all given databases with the given options. This:
|
244
|
+
#
|
245
|
+
# Sequel.transaction([DB1, DB2, DB3]){...}
|
246
|
+
#
|
247
|
+
# is equivalent to:
|
248
|
+
#
|
249
|
+
# DB1.transaction do
|
250
|
+
# DB2.transaction do
|
251
|
+
# DB3.transaction do
|
252
|
+
# ...
|
253
|
+
# end
|
254
|
+
# end
|
255
|
+
# end
|
256
|
+
#
|
257
|
+
# except that if Sequel::Rollback is raised by the block, the transaction is
|
258
|
+
# rolled back on all databases instead of just the last one.
|
259
|
+
#
|
260
|
+
# Note that this method cannot guarantee that all databases will commit or
|
261
|
+
# rollback. For example, if DB3 commits but attempting to commit on DB2
|
262
|
+
# fails (maybe because foreign key checks are deferred), there is no way
|
263
|
+
# to uncommit the changes on DB3. For that kind of support, you need to
|
264
|
+
# have two-phase commit/prepared transactions (which Sequel supports on
|
265
|
+
# some databases).
|
266
|
+
def self.transaction(dbs, opts={}, &block)
|
267
|
+
unless opts[:rollback]
|
268
|
+
rescue_rollback = true
|
269
|
+
opts = opts.merge(:rollback=>:reraise)
|
270
|
+
end
|
271
|
+
pr = dbs.reverse.inject(block){|bl, db| proc{db.transaction(opts, &bl)}}
|
272
|
+
if rescue_rollback
|
273
|
+
begin
|
274
|
+
pr.call
|
275
|
+
rescue Sequel::Rollback => e
|
276
|
+
nil
|
277
|
+
end
|
278
|
+
else
|
279
|
+
pr.call
|
280
|
+
end
|
281
|
+
end
|
282
|
+
|
243
283
|
# Same as Sequel.require, but wrapped in a mutex in order to be thread safe.
|
244
284
|
def self.ts_require(*args)
|
245
285
|
check_requiring_thread{require(*args)}
|
@@ -6,7 +6,7 @@ module Sequel
|
|
6
6
|
# ---------------------
|
7
7
|
|
8
8
|
# Array of supported database adapters
|
9
|
-
ADAPTERS = %w'ado amalgalite db2 dbi do firebird ibmdb informix jdbc mysql mysql2 odbc openbase oracle postgres sqlite swift tinytds'.collect{|x| x.to_sym}
|
9
|
+
ADAPTERS = %w'ado amalgalite db2 dbi do firebird ibmdb informix jdbc mock mysql mysql2 odbc openbase oracle postgres sqlite swift tinytds'.collect{|x| x.to_sym}
|
10
10
|
|
11
11
|
# Whether to use the single threaded connection pool by default
|
12
12
|
@@single_threaded = false
|
@@ -218,7 +218,6 @@ module Sequel
|
|
218
218
|
# If a server option is given, acquires a connection for that specific
|
219
219
|
# server, instead of the :default server.
|
220
220
|
#
|
221
|
-
#
|
222
221
|
# DB.synchronize do |conn|
|
223
222
|
# ...
|
224
223
|
# end
|
@@ -24,10 +24,10 @@ module Sequel
|
|
24
24
|
#
|
25
25
|
# DB.dataset # SELECT *
|
26
26
|
# DB.dataset.from(:items) # SELECT * FROM items
|
27
|
-
def dataset
|
28
|
-
|
27
|
+
def dataset(opts=nil)
|
28
|
+
@dataset_class.new(self, opts)
|
29
29
|
end
|
30
|
-
|
30
|
+
|
31
31
|
# Fetches records for an arbitrary SQL statement. If a block is given,
|
32
32
|
# it is used to iterate over the records:
|
33
33
|
#
|
@@ -5,6 +5,9 @@ module Sequel
|
|
5
5
|
# This methods change the default behavior of this database's datasets.
|
6
6
|
# ---------------------
|
7
7
|
|
8
|
+
# The default class to use for datasets
|
9
|
+
DatasetClass = Sequel::Dataset
|
10
|
+
|
8
11
|
# The identifier input method to use by default
|
9
12
|
@@identifier_input_method = nil
|
10
13
|
|
@@ -42,9 +45,59 @@ module Sequel
|
|
42
45
|
@@quote_identifiers = value
|
43
46
|
end
|
44
47
|
|
48
|
+
# The class to use for creating datasets. Should respond to
|
49
|
+
# new with the Database argument as the first argument, and
|
50
|
+
# an optional options hash.
|
51
|
+
attr_reader :dataset_class
|
52
|
+
|
45
53
|
# The default schema to use, generally should be nil.
|
46
54
|
attr_accessor :default_schema
|
47
55
|
|
56
|
+
# If the database has any dataset modules associated with it,
|
57
|
+
# use a subclass of the given class that includes the modules
|
58
|
+
# as the dataset class.
|
59
|
+
def dataset_class=(c)
|
60
|
+
unless @dataset_modules.empty?
|
61
|
+
c = Class.new(c)
|
62
|
+
@dataset_modules.each{|m| c.send(:include, m)}
|
63
|
+
end
|
64
|
+
@dataset_class = c
|
65
|
+
end
|
66
|
+
|
67
|
+
# Equivalent to extending all datasets produced by the database with a
|
68
|
+
# module. What it actually does is use a subclass of the current dataset_class
|
69
|
+
# as the new dataset_class, and include the module in the subclass.
|
70
|
+
# Instead of a module, you can provide a block that is used to create an
|
71
|
+
# anonymous module.
|
72
|
+
#
|
73
|
+
# This allows you to override any of the dataset methods even if they are
|
74
|
+
# defined directly on the dataset class that this Database object uses.
|
75
|
+
#
|
76
|
+
# Examples:
|
77
|
+
#
|
78
|
+
# # Introspec columns for all of DB's datasets
|
79
|
+
# DB.extend_datasets(Sequel::ColumnsIntrospection)
|
80
|
+
#
|
81
|
+
# # Trace all SELECT queries by printing the SQL and the full backtrace
|
82
|
+
# DB.extend_datasets do
|
83
|
+
# def fetch_rows(sql)
|
84
|
+
# puts sql
|
85
|
+
# puts caller
|
86
|
+
# super
|
87
|
+
# end
|
88
|
+
# end
|
89
|
+
def extend_datasets(mod=nil, &block)
|
90
|
+
raise(Error, "must provide either mod or block, not both") if mod && block
|
91
|
+
mod = Module.new(&block) if block
|
92
|
+
if @dataset_modules.empty?
|
93
|
+
@dataset_modules = [mod]
|
94
|
+
@dataset_class = Class.new(@dataset_class)
|
95
|
+
else
|
96
|
+
@dataset_modules << mod
|
97
|
+
end
|
98
|
+
@dataset_class.send(:include, mod)
|
99
|
+
end
|
100
|
+
|
48
101
|
# The method to call on identifiers going into the database
|
49
102
|
def identifier_input_method
|
50
103
|
case @identifier_input_method
|
@@ -109,6 +162,11 @@ module Sequel
|
|
109
162
|
|
110
163
|
private
|
111
164
|
|
165
|
+
# The default dataset class to use for the database
|
166
|
+
def dataset_class_default
|
167
|
+
self.class.const_get(:DatasetClass)
|
168
|
+
end
|
169
|
+
|
112
170
|
# The default value for default_schema.
|
113
171
|
def default_schema_default
|
114
172
|
nil
|
data/lib/sequel/database/misc.rb
CHANGED
@@ -19,6 +19,9 @@ module Sequel
|
|
19
19
|
# The options hash for this database
|
20
20
|
attr_reader :opts
|
21
21
|
|
22
|
+
# Set the timezone to use for this database, overridding <tt>Sequel.database_timezone</tt>.
|
23
|
+
attr_writer :timezone
|
24
|
+
|
22
25
|
# Constructs a new instance of a database connection with the specified
|
23
26
|
# options hash.
|
24
27
|
#
|
@@ -50,15 +53,46 @@ module Sequel
|
|
50
53
|
@schemas = {}
|
51
54
|
@default_schema = @opts.fetch(:default_schema, default_schema_default)
|
52
55
|
@prepared_statements = {}
|
53
|
-
@transactions =
|
56
|
+
@transactions = {}
|
54
57
|
@identifier_input_method = nil
|
55
58
|
@identifier_output_method = nil
|
56
59
|
@quote_identifiers = nil
|
60
|
+
@timezone = nil
|
61
|
+
@dataset_class = dataset_class_default
|
62
|
+
@dataset_modules = []
|
57
63
|
self.sql_log_level = @opts[:sql_log_level] ? @opts[:sql_log_level].to_sym : :info
|
58
64
|
@pool = ConnectionPool.get_pool(@opts, &block)
|
59
65
|
|
60
66
|
::Sequel::DATABASES.push(self)
|
61
67
|
end
|
68
|
+
|
69
|
+
# If a transaction is not currently in process, yield to the block immediately.
|
70
|
+
# Otherwise, add the block to the list of blocks to call after the currently
|
71
|
+
# in progress transaction commits (and only if it commits).
|
72
|
+
def after_commit(opts={}, &block)
|
73
|
+
raise Error, "must provide block to after_commit" unless block
|
74
|
+
synchronize(opts) do |conn|
|
75
|
+
if h = @transactions[conn]
|
76
|
+
raise Error, "cannot call after_commit in a prepared transaction" if h[:prepare]
|
77
|
+
(h[:after_commit] ||= []) << block
|
78
|
+
else
|
79
|
+
yield
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
# If a transaction is not currently in progress, ignore the block.
|
85
|
+
# Otherwise, add the block to the list of the blocks to call after the currently
|
86
|
+
# in progress transaction rolls back (and only if it rolls back).
|
87
|
+
def after_rollback(opts={}, &block)
|
88
|
+
raise Error, "must provide block to after_rollback" unless block
|
89
|
+
synchronize(opts) do |conn|
|
90
|
+
if h = @transactions[conn]
|
91
|
+
raise Error, "cannot call after_rollback in a prepared transaction" if h[:prepare]
|
92
|
+
(h[:after_rollback] ||= []) << block
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
62
96
|
|
63
97
|
# Cast the given type to a literal type
|
64
98
|
#
|
@@ -68,6 +102,20 @@ module Sequel
|
|
68
102
|
type_literal(:type=>type)
|
69
103
|
end
|
70
104
|
|
105
|
+
# Convert the given timestamp from the application's timezone,
|
106
|
+
# to the databases's timezone or the default database timezone if
|
107
|
+
# the database does not have a timezone.
|
108
|
+
def from_application_timestamp(v)
|
109
|
+
Sequel.convert_output_timestamp(v, timezone)
|
110
|
+
end
|
111
|
+
|
112
|
+
# Return true if already in a transaction given the options,
|
113
|
+
# false otherwise. Respects the :server option for selecting
|
114
|
+
# a shard.
|
115
|
+
def in_transaction?(opts={})
|
116
|
+
synchronize(opts[:server]){|conn| !!@transactions[conn]}
|
117
|
+
end
|
118
|
+
|
71
119
|
# Returns a string representation of the database object including the
|
72
120
|
# class name and the connection URI (or the opts if the URI
|
73
121
|
# cannot be constructed).
|
@@ -112,6 +160,18 @@ module Sequel
|
|
112
160
|
false
|
113
161
|
end
|
114
162
|
|
163
|
+
# The timezone to use for this database, defaulting to <tt>Sequel.database_timezone</tt>.
|
164
|
+
def timezone
|
165
|
+
@timezone || Sequel.database_timezone
|
166
|
+
end
|
167
|
+
|
168
|
+
# Convert the given timestamp to the application's timezone,
|
169
|
+
# from the databases's timezone or the default database timezone if
|
170
|
+
# the database does not have a timezone.
|
171
|
+
def to_application_timestamp(v)
|
172
|
+
Sequel.convert_timestamp(v, timezone)
|
173
|
+
end
|
174
|
+
|
115
175
|
# Typecast the value to the given column_type. Calls
|
116
176
|
# typecast_value_#{column_type} if the method exists,
|
117
177
|
# otherwise returns the value.
|
@@ -205,7 +265,7 @@ module Sequel
|
|
205
265
|
# Typecast the value to true, false, or nil
|
206
266
|
def typecast_value_boolean(value)
|
207
267
|
case value
|
208
|
-
when false, 0, "0", /\Af(alse)?\z/i
|
268
|
+
when false, 0, "0", /\Af(alse)?\z/i, /\Ano?\z/i
|
209
269
|
false
|
210
270
|
else
|
211
271
|
blank_object?(value) ? nil : true
|
@@ -37,9 +37,12 @@ module Sequel
|
|
37
37
|
attr_accessor :transaction_isolation_level
|
38
38
|
|
39
39
|
# Runs the supplied SQL statement string on the database server.
|
40
|
-
#
|
40
|
+
# Returns self so it can be safely chained:
|
41
|
+
#
|
42
|
+
# DB << "UPADTE albums SET artist_id = NULL" << "DROP TABLE artists"
|
41
43
|
def <<(sql)
|
42
44
|
run(sql)
|
45
|
+
self
|
43
46
|
end
|
44
47
|
|
45
48
|
# Call the prepared statement with the given name with the given hash
|
@@ -112,14 +115,15 @@ module Sequel
|
|
112
115
|
|
113
116
|
# Returns the schema for the given table as an array with all members being arrays of length 2,
|
114
117
|
# the first member being the column name, and the second member being a hash of column information.
|
118
|
+
# The table argument can also be a dataset, as long as it only has one table.
|
115
119
|
# Available options are:
|
116
120
|
#
|
117
121
|
# :reload :: Ignore any cached results, and get fresh information from the database.
|
118
122
|
# :schema :: An explicit schema to use. It may also be implicitly provided
|
119
123
|
# via the table name.
|
120
124
|
#
|
121
|
-
# If schema parsing is supported by the database, the column information should at least contain the
|
122
|
-
# following
|
125
|
+
# If schema parsing is supported by the database, the column information should hash at least contain the
|
126
|
+
# following entries:
|
123
127
|
#
|
124
128
|
# :allow_null :: Whether NULL is an allowed value for the column.
|
125
129
|
# :db_type :: The database type for the column, as a database specific string.
|
@@ -152,9 +156,20 @@ module Sequel
|
|
152
156
|
def schema(table, opts={})
|
153
157
|
raise(Error, 'schema parsing is not implemented on this database') unless respond_to?(:schema_parse_table, true)
|
154
158
|
|
155
|
-
|
156
|
-
|
157
|
-
|
159
|
+
opts = opts.dup
|
160
|
+
if table.is_a?(Dataset)
|
161
|
+
o = table.opts
|
162
|
+
from = o[:from]
|
163
|
+
raise(Error, "can only parse the schema for a dataset with a single from table") unless from && from.length == 1 && !o.include?(:join) && !o.include?(:sql)
|
164
|
+
tab = table.first_source_table
|
165
|
+
sch, table_name = schema_and_table(tab)
|
166
|
+
quoted_name = table.literal(tab)
|
167
|
+
opts[:dataset] = table
|
168
|
+
else
|
169
|
+
sch, table_name = schema_and_table(table)
|
170
|
+
quoted_name = quote_schema_table(table)
|
171
|
+
end
|
172
|
+
opts[:schema] = sch if sch && !opts.include?(:schema)
|
158
173
|
|
159
174
|
@schemas.delete(quoted_name) if opts[:reload]
|
160
175
|
return @schemas[quoted_name] if @schemas[quoted_name]
|
@@ -199,6 +214,9 @@ module Sequel
|
|
199
214
|
# :prepare :: A string to use as the transaction identifier for a
|
200
215
|
# prepared transaction (two-phase commit), if the database/adapter
|
201
216
|
# supports prepared transactions.
|
217
|
+
# :rollback :: Can the set to :reraise to reraise any Sequel::Rollback exceptions
|
218
|
+
# raised, or :always to always rollback even if no exceptions occur
|
219
|
+
# (useful for testing).
|
202
220
|
# :server :: The server to use for the transaction.
|
203
221
|
# :savepoint :: Whether to create a new savepoint for this transaction,
|
204
222
|
# only respected if the database/adapter supports savepoints. By
|
@@ -225,40 +243,65 @@ module Sequel
|
|
225
243
|
# not a Sequel::Rollback, the error will be reraised. If no exception occurs
|
226
244
|
# inside the block, the transaction is commited.
|
227
245
|
def _transaction(conn, opts={})
|
246
|
+
rollback = opts[:rollback]
|
228
247
|
begin
|
229
|
-
add_transaction
|
230
|
-
|
231
|
-
|
248
|
+
add_transaction(conn, opts)
|
249
|
+
begin_transaction(conn, opts)
|
250
|
+
if rollback == :always
|
251
|
+
begin
|
252
|
+
yield(conn)
|
253
|
+
rescue Exception => e1
|
254
|
+
raise e1
|
255
|
+
ensure
|
256
|
+
raise ::Sequel::Rollback unless e1
|
257
|
+
end
|
258
|
+
else
|
259
|
+
yield(conn)
|
260
|
+
end
|
232
261
|
rescue Exception => e
|
233
|
-
rollback_transaction(
|
234
|
-
transaction_error(e, :conn=>conn)
|
262
|
+
rollback_transaction(conn, opts)
|
263
|
+
transaction_error(e, :conn=>conn, :rollback=>rollback)
|
235
264
|
ensure
|
236
265
|
begin
|
237
|
-
commit_or_rollback_transaction(e,
|
238
|
-
rescue Exception =>
|
239
|
-
raise_error(
|
266
|
+
committed = commit_or_rollback_transaction(e, conn, opts)
|
267
|
+
rescue Exception => e2
|
268
|
+
raise_error(e2, :classes=>database_error_classes, :conn=>conn)
|
240
269
|
ensure
|
241
|
-
remove_transaction(
|
270
|
+
remove_transaction(conn, committed)
|
242
271
|
end
|
243
272
|
end
|
244
273
|
end
|
245
274
|
|
246
275
|
# Add the current thread to the list of active transactions
|
247
|
-
def add_transaction
|
248
|
-
th = Thread.current
|
276
|
+
def add_transaction(conn, opts)
|
249
277
|
if supports_savepoints?
|
250
|
-
unless @transactions
|
251
|
-
|
252
|
-
@transactions
|
278
|
+
unless @transactions[conn]
|
279
|
+
@transactions[conn] = {:savepoint_level=>0}
|
280
|
+
@transactions[conn][:prepare] = opts[:prepare] if supports_prepared_transactions?
|
253
281
|
end
|
254
282
|
else
|
255
|
-
@transactions
|
283
|
+
@transactions[conn] = {}
|
284
|
+
@transactions[conn][:prepare] = opts[:prepare] if supports_prepared_transactions?
|
256
285
|
end
|
257
286
|
end
|
258
287
|
|
288
|
+
# Call all stored after_commit blocks for the given transaction
|
289
|
+
def after_transaction_commit(conn)
|
290
|
+
if ary = @transactions[conn][:after_commit]
|
291
|
+
ary.each{|b| b.call}
|
292
|
+
end
|
293
|
+
end
|
294
|
+
|
295
|
+
# Call all stored after_rollback blocks for the given transaction
|
296
|
+
def after_transaction_rollback(conn)
|
297
|
+
if ary = @transactions[conn][:after_rollback]
|
298
|
+
ary.each{|b| b.call}
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
259
302
|
# Whether the current thread/connection is already inside a transaction
|
260
303
|
def already_in_transaction?(conn, opts)
|
261
|
-
@transactions.
|
304
|
+
@transactions.has_key?(conn) && (!supports_savepoints? || !opts[:savepoint])
|
262
305
|
end
|
263
306
|
|
264
307
|
# SQL to start a new savepoint
|
@@ -275,17 +318,16 @@ module Sequel
|
|
275
318
|
# Start a new database transaction or a new savepoint on the given connection.
|
276
319
|
def begin_transaction(conn, opts={})
|
277
320
|
if supports_savepoints?
|
278
|
-
th =
|
279
|
-
if (depth = th[:
|
321
|
+
th = @transactions[conn]
|
322
|
+
if (depth = th[:savepoint_level]) > 0
|
280
323
|
log_connection_execute(conn, begin_savepoint_sql(depth))
|
281
324
|
else
|
282
325
|
begin_new_transaction(conn, opts)
|
283
326
|
end
|
284
|
-
th[:
|
327
|
+
th[:savepoint_level] += 1
|
285
328
|
else
|
286
329
|
begin_new_transaction(conn, opts)
|
287
330
|
end
|
288
|
-
conn
|
289
331
|
end
|
290
332
|
|
291
333
|
# SQL to BEGIN a transaction.
|
@@ -348,12 +390,16 @@ module Sequel
|
|
348
390
|
# Thread.current.status is checked because Thread#kill skips rescue
|
349
391
|
# blocks (so exception would be nil), but the transaction should
|
350
392
|
# still be rolled back.
|
351
|
-
def commit_or_rollback_transaction(exception,
|
352
|
-
|
393
|
+
def commit_or_rollback_transaction(exception, conn, opts)
|
394
|
+
if exception
|
395
|
+
false
|
396
|
+
else
|
353
397
|
if Thread.current.status == 'aborting'
|
354
|
-
rollback_transaction(
|
398
|
+
rollback_transaction(conn, opts)
|
399
|
+
false
|
355
400
|
else
|
356
|
-
commit_transaction(
|
401
|
+
commit_transaction(conn, opts)
|
402
|
+
true
|
357
403
|
end
|
358
404
|
end
|
359
405
|
end
|
@@ -361,8 +407,13 @@ module Sequel
|
|
361
407
|
# Whether to commit the current transaction. On ruby 1.9 and JRuby,
|
362
408
|
# transactions will be committed if Thread#kill is used on an thread
|
363
409
|
# that has a transaction open, and there isn't a work around.
|
364
|
-
def commit_or_rollback_transaction(exception,
|
365
|
-
|
410
|
+
def commit_or_rollback_transaction(exception, conn, opts)
|
411
|
+
if exception
|
412
|
+
false
|
413
|
+
else
|
414
|
+
commit_transaction(conn, opts)
|
415
|
+
true
|
416
|
+
end
|
366
417
|
end
|
367
418
|
end
|
368
419
|
|
@@ -374,7 +425,7 @@ module Sequel
|
|
374
425
|
# Commit the active transaction on the connection
|
375
426
|
def commit_transaction(conn, opts={})
|
376
427
|
if supports_savepoints?
|
377
|
-
depth =
|
428
|
+
depth = @transactions[conn][:savepoint_level]
|
378
429
|
log_connection_execute(conn, depth > 1 ? commit_savepoint_sql(depth-1) : commit_transaction_sql)
|
379
430
|
else
|
380
431
|
log_connection_execute(conn, commit_transaction_sql)
|
@@ -395,8 +446,8 @@ module Sequel
|
|
395
446
|
# Return a Method object for the dataset's output_identifier_method.
|
396
447
|
# Used in metadata parsing to make sure the returned information is in the
|
397
448
|
# correct format.
|
398
|
-
def input_identifier_meth
|
399
|
-
dataset.method(:input_identifier)
|
449
|
+
def input_identifier_meth(ds=nil)
|
450
|
+
(ds || dataset).method(:input_identifier)
|
400
451
|
end
|
401
452
|
|
402
453
|
# Return a dataset that uses the default identifier input and output methods
|
@@ -413,24 +464,28 @@ module Sequel
|
|
413
464
|
# Return a Method object for the dataset's output_identifier_method.
|
414
465
|
# Used in metadata parsing to make sure the returned information is in the
|
415
466
|
# correct format.
|
416
|
-
def output_identifier_meth
|
417
|
-
dataset.method(:output_identifier)
|
467
|
+
def output_identifier_meth(ds=nil)
|
468
|
+
(ds || dataset).method(:output_identifier)
|
418
469
|
end
|
419
470
|
|
420
|
-
# SQL to ROLLBACK a transaction.
|
421
|
-
def rollback_transaction_sql
|
422
|
-
SQL_ROLLBACK
|
423
|
-
end
|
424
|
-
|
425
471
|
# Remove the cached schema for the given schema name
|
426
472
|
def remove_cached_schema(table)
|
427
473
|
@schemas.delete(quote_schema_table(table)) if @schemas
|
428
474
|
end
|
429
475
|
|
430
476
|
# Remove the current thread from the list of active transactions
|
431
|
-
def remove_transaction(conn)
|
432
|
-
|
433
|
-
|
477
|
+
def remove_transaction(conn, committed)
|
478
|
+
if !supports_savepoints? || ((@transactions[conn][:savepoint_level] -= 1) <= 0)
|
479
|
+
begin
|
480
|
+
if committed
|
481
|
+
after_transaction_commit(conn)
|
482
|
+
else
|
483
|
+
after_transaction_rollback(conn)
|
484
|
+
end
|
485
|
+
ensure
|
486
|
+
@transactions.delete(conn)
|
487
|
+
end
|
488
|
+
end
|
434
489
|
end
|
435
490
|
|
436
491
|
# SQL to rollback to a savepoint
|
@@ -441,13 +496,18 @@ module Sequel
|
|
441
496
|
# Rollback the active transaction on the connection
|
442
497
|
def rollback_transaction(conn, opts={})
|
443
498
|
if supports_savepoints?
|
444
|
-
depth =
|
499
|
+
depth = @transactions[conn][:savepoint_level]
|
445
500
|
log_connection_execute(conn, depth > 1 ? rollback_savepoint_sql(depth-1) : rollback_transaction_sql)
|
446
501
|
else
|
447
502
|
log_connection_execute(conn, rollback_transaction_sql)
|
448
503
|
end
|
449
504
|
end
|
450
505
|
|
506
|
+
# SQL to ROLLBACK a transaction.
|
507
|
+
def rollback_transaction_sql
|
508
|
+
SQL_ROLLBACK
|
509
|
+
end
|
510
|
+
|
451
511
|
# Match the database's column type to a ruby type via a
|
452
512
|
# regular expression, and return the ruby type as a symbol
|
453
513
|
# such as :integer or :string.
|
@@ -469,8 +529,8 @@ module Sequel
|
|
469
529
|
:boolean
|
470
530
|
when /\A(real|float|double( precision)?)\z/io
|
471
531
|
:float
|
472
|
-
when /\A(?:(?:(?:num(?:ber|eric)?|decimal)(?:\(\d+,\s*(\d
|
473
|
-
$1 &&
|
532
|
+
when /\A(?:(?:(?:num(?:ber|eric)?|decimal)(?:\(\d+,\s*(\d+|false|true)\))?)|(?:small)?money)\z/io
|
533
|
+
$1 && ['0', 'false'].include?($1) ? :integer : :decimal
|
474
534
|
when /bytea|[bc]lob|image|(var)?binary/io
|
475
535
|
:blob
|
476
536
|
when /\Aenum/io
|
@@ -492,7 +552,11 @@ module Sequel
|
|
492
552
|
|
493
553
|
# Raise a database error unless the exception is an Rollback.
|
494
554
|
def transaction_error(e, opts={})
|
495
|
-
|
555
|
+
if e.is_a?(Rollback)
|
556
|
+
raise e if opts[:rollback] == :reraise
|
557
|
+
else
|
558
|
+
raise_error(e, opts.merge(:classes=>database_error_classes))
|
559
|
+
end
|
496
560
|
end
|
497
561
|
end
|
498
562
|
end
|