sequel 3.8.0 → 3.9.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.
- data/CHANGELOG +48 -0
- data/Rakefile +6 -28
- data/bin/sequel +7 -2
- data/doc/release_notes/3.9.0.txt +233 -0
- data/lib/sequel/adapters/ado.rb +4 -8
- data/lib/sequel/adapters/amalgalite.rb +1 -1
- data/lib/sequel/adapters/dbi.rb +3 -3
- data/lib/sequel/adapters/do.rb +7 -13
- data/lib/sequel/adapters/jdbc.rb +10 -16
- data/lib/sequel/adapters/jdbc/h2.rb +5 -0
- data/lib/sequel/adapters/mysql.rb +10 -23
- data/lib/sequel/adapters/odbc.rb +6 -10
- data/lib/sequel/adapters/postgres.rb +0 -5
- data/lib/sequel/adapters/shared/mssql.rb +17 -9
- data/lib/sequel/adapters/shared/mysql.rb +16 -7
- data/lib/sequel/adapters/shared/sqlite.rb +5 -0
- data/lib/sequel/adapters/sqlite.rb +2 -1
- data/lib/sequel/connection_pool.rb +67 -349
- data/lib/sequel/connection_pool/sharded_single.rb +84 -0
- data/lib/sequel/connection_pool/sharded_threaded.rb +211 -0
- data/lib/sequel/connection_pool/single.rb +29 -0
- data/lib/sequel/connection_pool/threaded.rb +150 -0
- data/lib/sequel/core.rb +46 -15
- data/lib/sequel/database.rb +11 -9
- data/lib/sequel/dataset/convenience.rb +23 -0
- data/lib/sequel/dataset/graph.rb +2 -2
- data/lib/sequel/dataset/query.rb +9 -5
- data/lib/sequel/dataset/sql.rb +87 -12
- data/lib/sequel/extensions/inflector.rb +8 -1
- data/lib/sequel/extensions/schema_dumper.rb +3 -4
- data/lib/sequel/model/associations.rb +5 -43
- data/lib/sequel/model/base.rb +9 -2
- data/lib/sequel/model/default_inflections.rb +1 -1
- data/lib/sequel/model/exceptions.rb +11 -1
- data/lib/sequel/model/inflections.rb +8 -1
- data/lib/sequel/model/plugins.rb +2 -12
- data/lib/sequel/plugins/active_model.rb +5 -0
- data/lib/sequel/plugins/association_dependencies.rb +1 -1
- data/lib/sequel/plugins/many_through_many.rb +1 -1
- data/lib/sequel/plugins/optimistic_locking.rb +65 -0
- data/lib/sequel/plugins/single_table_inheritance.rb +14 -3
- data/lib/sequel/plugins/validation_helpers.rb +2 -2
- data/lib/sequel/sql.rb +2 -2
- data/lib/sequel/timezones.rb +2 -2
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/mssql_spec.rb +19 -0
- data/spec/adapters/mysql_spec.rb +4 -0
- data/spec/adapters/postgres_spec.rb +180 -0
- data/spec/adapters/spec_helper.rb +15 -1
- data/spec/core/connection_pool_spec.rb +119 -78
- data/spec/core/database_spec.rb +41 -50
- data/spec/core/dataset_spec.rb +115 -4
- data/spec/extensions/active_model_spec.rb +40 -34
- data/spec/extensions/boolean_readers_spec.rb +1 -1
- data/spec/extensions/migration_spec.rb +43 -38
- data/spec/extensions/optimistic_locking_spec.rb +100 -0
- data/spec/extensions/schema_dumper_spec.rb +4 -4
- data/spec/extensions/single_table_inheritance_spec.rb +19 -11
- data/spec/integration/dataset_test.rb +44 -1
- data/spec/integration/plugin_test.rb +39 -0
- data/spec/integration/prepared_statement_test.rb +58 -7
- data/spec/integration/spec_helper.rb +4 -0
- data/spec/model/eager_loading_spec.rb +24 -0
- data/spec/model/validations_spec.rb +5 -1
- metadata +114 -106
data/lib/sequel/adapters/jdbc.rb
CHANGED
@@ -35,35 +35,35 @@ module Sequel
|
|
35
35
|
# Contains procs keyed on sub adapter type that extend the
|
36
36
|
# given database object so it supports the correct database type.
|
37
37
|
DATABASE_SETUP = {:postgresql=>proc do |db|
|
38
|
-
Sequel.
|
38
|
+
Sequel.ts_require 'adapters/jdbc/postgresql'
|
39
39
|
db.extend(Sequel::JDBC::Postgres::DatabaseMethods)
|
40
40
|
JDBC.load_gem('postgres')
|
41
41
|
org.postgresql.Driver
|
42
42
|
end,
|
43
43
|
:mysql=>proc do |db|
|
44
|
-
Sequel.
|
44
|
+
Sequel.ts_require 'adapters/jdbc/mysql'
|
45
45
|
db.extend(Sequel::JDBC::MySQL::DatabaseMethods)
|
46
46
|
JDBC.load_gem('mysql')
|
47
47
|
com.mysql.jdbc.Driver
|
48
48
|
end,
|
49
49
|
:sqlite=>proc do |db|
|
50
|
-
Sequel.
|
50
|
+
Sequel.ts_require 'adapters/jdbc/sqlite'
|
51
51
|
db.extend(Sequel::JDBC::SQLite::DatabaseMethods)
|
52
52
|
JDBC.load_gem('sqlite3')
|
53
53
|
org.sqlite.JDBC
|
54
54
|
end,
|
55
55
|
:oracle=>proc do |db|
|
56
|
-
Sequel.
|
56
|
+
Sequel.ts_require 'adapters/jdbc/oracle'
|
57
57
|
db.extend(Sequel::JDBC::Oracle::DatabaseMethods)
|
58
58
|
Java::oracle.jdbc.driver.OracleDriver
|
59
59
|
end,
|
60
60
|
:sqlserver=>proc do |db|
|
61
|
-
Sequel.
|
61
|
+
Sequel.ts_require 'adapters/jdbc/mssql'
|
62
62
|
db.extend(Sequel::JDBC::MSSQL::DatabaseMethods)
|
63
63
|
com.microsoft.sqlserver.jdbc.SQLServerDriver
|
64
64
|
end,
|
65
65
|
:h2=>proc do |db|
|
66
|
-
Sequel.
|
66
|
+
Sequel.ts_require 'adapters/jdbc/h2'
|
67
67
|
db.extend(Sequel::JDBC::H2::DatabaseMethods)
|
68
68
|
JDBC.load_gem('h2')
|
69
69
|
org.h2.Driver
|
@@ -74,7 +74,7 @@ module Sequel
|
|
74
74
|
# works for PostgreSQL, MySQL, and SQLite.
|
75
75
|
def self.load_gem(name)
|
76
76
|
begin
|
77
|
-
|
77
|
+
Sequel.tsk_require "jdbc/#{name}"
|
78
78
|
rescue LoadError
|
79
79
|
# jdbc gem not used, hopefully the user has the .jar in their CLASSPATH
|
80
80
|
end
|
@@ -98,13 +98,12 @@ module Sequel
|
|
98
98
|
# raise an error immediately if the connection doesn't have a
|
99
99
|
# uri, since JDBC requires one.
|
100
100
|
def initialize(opts)
|
101
|
-
|
102
|
-
@convert_types = opts.include?(:convert_types) ? typecast_value_boolean(opts[:convert_types]) : true
|
101
|
+
super
|
102
|
+
@convert_types = @opts.include?(:convert_types) ? typecast_value_boolean(@opts[:convert_types]) : true
|
103
103
|
raise(Error, "No connection string specified") unless uri
|
104
104
|
if match = /\Ajdbc:([^:]+)/.match(uri) and prok = DATABASE_SETUP[match[1].to_sym]
|
105
105
|
prok.call(self)
|
106
106
|
end
|
107
|
-
super(opts)
|
108
107
|
end
|
109
108
|
|
110
109
|
# Execute the given stored procedure with the give name. If a block is
|
@@ -215,7 +214,7 @@ module Sequel
|
|
215
214
|
end
|
216
215
|
|
217
216
|
# All tables in this database
|
218
|
-
def tables
|
217
|
+
def tables(opts={})
|
219
218
|
ts = []
|
220
219
|
m = output_identifier_meth
|
221
220
|
metadata(:getTables, nil, nil, nil, ['TABLE'].to_java(:string)){|h| ts << m.call(h[:table_name])}
|
@@ -240,11 +239,6 @@ module Sequel
|
|
240
239
|
super
|
241
240
|
end
|
242
241
|
|
243
|
-
# The JDBC adapter should not need the pool to convert exceptions.
|
244
|
-
def connection_pool_default_options
|
245
|
-
super.merge(:pool_convert_exceptions=>false)
|
246
|
-
end
|
247
|
-
|
248
242
|
# Close given adapter connections
|
249
243
|
def disconnect_connection(c)
|
250
244
|
c.close
|
@@ -9,23 +9,6 @@ Sequel.require %w'shared/mysql utils/stored_procedures', 'adapters'
|
|
9
9
|
|
10
10
|
module Sequel
|
11
11
|
# Module for holding all MySQL-related classes and modules for Sequel.
|
12
|
-
#
|
13
|
-
# A class level convert_invalid_date_time accessor exists if
|
14
|
-
# the native adapter is used. If set to nil or :nil, the adapter treats dates
|
15
|
-
# like 0000-00-00 and times like 838:00:00 as nil values. If set to :string,
|
16
|
-
# it returns the strings as is. It is false by default, which means that
|
17
|
-
# invalid dates and times will raise errors.
|
18
|
-
#
|
19
|
-
# Sequel::MySQL.convert_invalid_date_time = nil
|
20
|
-
#
|
21
|
-
# Sequel converts the column type tinyint(1) to a boolean by default when
|
22
|
-
# using the native MySQL adapter. You can turn off the conversion to use
|
23
|
-
# tinyint as an integer:
|
24
|
-
#
|
25
|
-
# Sequel::MySQL.convert_tinyint_to_bool = false
|
26
|
-
#
|
27
|
-
# In most cases these settings need to be made after the adapter has been
|
28
|
-
# loaded, since the Sequel::MySQL module probably won't exist before then.
|
29
12
|
module MySQL
|
30
13
|
# Mapping of type numbers to conversion procs
|
31
14
|
MYSQL_TYPES = {}
|
@@ -49,7 +32,16 @@ module Sequel
|
|
49
32
|
@convert_tinyint_to_bool = true
|
50
33
|
|
51
34
|
class << self
|
52
|
-
|
35
|
+
# By default, Sequel raises an exception if in invalid date or time is used.
|
36
|
+
# However, if this is set to nil or :nil, the adapter treats dates
|
37
|
+
# like 0000-00-00 and times like 838:00:00 as nil values. If set to :string,
|
38
|
+
# it returns the strings as is.
|
39
|
+
attr_accessor :convert_invalid_date_time
|
40
|
+
|
41
|
+
# Sequel converts the column type tinyint(1) to a boolean by default when
|
42
|
+
# using the native MySQL adapter. You can turn off the conversion by setting
|
43
|
+
# this to false.
|
44
|
+
attr_accessor :convert_tinyint_to_bool
|
53
45
|
end
|
54
46
|
|
55
47
|
# If convert_invalid_date_time is nil, :nil, or :string and
|
@@ -209,11 +201,6 @@ module Sequel
|
|
209
201
|
:query
|
210
202
|
end
|
211
203
|
|
212
|
-
# MySQL doesn't need the connection pool to convert exceptions.
|
213
|
-
def connection_pool_default_options
|
214
|
-
super.merge(:pool_convert_exceptions=>false)
|
215
|
-
end
|
216
|
-
|
217
204
|
# The MySQL adapter main error class is Mysql::Error
|
218
205
|
def database_error_classes
|
219
206
|
[Mysql::Error]
|
data/lib/sequel/adapters/odbc.rb
CHANGED
@@ -9,13 +9,13 @@ module Sequel
|
|
9
9
|
DRV_NAME_GUARDS = '{%s}'.freeze
|
10
10
|
|
11
11
|
def initialize(opts)
|
12
|
-
super
|
13
|
-
case opts[:db_type]
|
12
|
+
super
|
13
|
+
case @opts[:db_type]
|
14
14
|
when 'mssql'
|
15
|
-
Sequel.
|
15
|
+
Sequel.ts_require 'adapters/odbc/mssql'
|
16
16
|
extend Sequel::ODBC::MSSQL::DatabaseMethods
|
17
17
|
when 'progress'
|
18
|
-
Sequel.
|
18
|
+
Sequel.ts_require 'adapters/shared/progress'
|
19
19
|
extend Sequel::Progress::DatabaseMethods
|
20
20
|
end
|
21
21
|
end
|
@@ -50,7 +50,7 @@ module Sequel
|
|
50
50
|
begin
|
51
51
|
r = conn.run(sql)
|
52
52
|
yield(r) if block_given?
|
53
|
-
rescue ::ODBC::Error => e
|
53
|
+
rescue ::ODBC::Error, ArgumentError => e
|
54
54
|
raise_error(e)
|
55
55
|
ensure
|
56
56
|
r.drop if r
|
@@ -64,7 +64,7 @@ module Sequel
|
|
64
64
|
synchronize(opts[:server]) do |conn|
|
65
65
|
begin
|
66
66
|
conn.do(sql)
|
67
|
-
rescue ::ODBC::Error => e
|
67
|
+
rescue ::ODBC::Error, ArgumentError => e
|
68
68
|
raise_error(e)
|
69
69
|
end
|
70
70
|
end
|
@@ -73,10 +73,6 @@ module Sequel
|
|
73
73
|
|
74
74
|
private
|
75
75
|
|
76
|
-
def connection_pool_default_options
|
77
|
-
super.merge(:pool_convert_exceptions=>false)
|
78
|
-
end
|
79
|
-
|
80
76
|
def connection_execute_method
|
81
77
|
:do
|
82
78
|
end
|
@@ -254,11 +254,6 @@ module Sequel
|
|
254
254
|
end
|
255
255
|
end
|
256
256
|
|
257
|
-
# PostgreSQL doesn't need the connection pool to convert exceptions.
|
258
|
-
def connection_pool_default_options
|
259
|
-
super.merge(:pool_convert_exceptions=>false)
|
260
|
-
end
|
261
|
-
|
262
257
|
# Disconnect given connection
|
263
258
|
def disconnect_connection(conn)
|
264
259
|
begin
|
@@ -183,7 +183,7 @@ module Sequel
|
|
183
183
|
COMMA_SEPARATOR = ', '.freeze
|
184
184
|
DELETE_CLAUSE_METHODS = Dataset.clause_methods(:delete, %w'with from output from2 where')
|
185
185
|
INSERT_CLAUSE_METHODS = Dataset.clause_methods(:insert, %w'with into columns output values')
|
186
|
-
SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'with limit distinct columns from table_options join where group having order compounds')
|
186
|
+
SELECT_CLAUSE_METHODS = Dataset.clause_methods(:select, %w'with limit distinct columns into from table_options join where group having order compounds')
|
187
187
|
UPDATE_CLAUSE_METHODS = Dataset.clause_methods(:update, %w'with table set output from where')
|
188
188
|
WILDCARD = LiteralString.new('*').freeze
|
189
189
|
CONSTANT_MAP = {:CURRENT_DATE=>'CAST(CURRENT_TIMESTAMP AS DATE)'.freeze, :CURRENT_TIME=>'CAST(CURRENT_TIMESTAMP AS TIME)'.freeze}
|
@@ -234,6 +234,11 @@ module Sequel
|
|
234
234
|
naked.clone(default_server_opts(:sql=>output(nil, [:inserted.*]).insert_sql(*values))).single_record unless opts[:disable_insert_output]
|
235
235
|
end
|
236
236
|
|
237
|
+
# Specify a table for a SELECT ... INTO query.
|
238
|
+
def into(table)
|
239
|
+
clone(:into => table)
|
240
|
+
end
|
241
|
+
|
237
242
|
# MSSQL uses a UNION ALL statement to insert multiple values at once.
|
238
243
|
def multi_insert_sql(columns, values)
|
239
244
|
[insert_sql(columns, LiteralString.new(values.map {|r| "SELECT #{expression_list(r)}" }.join(" UNION ALL ")))]
|
@@ -285,7 +290,7 @@ module Sequel
|
|
285
290
|
#
|
286
291
|
# The implementation is ugly, cloning the current dataset and modifying
|
287
292
|
# the clone to add a ROW_NUMBER window function (and some other things),
|
288
|
-
# then using the modified clone in a
|
293
|
+
# then using the modified clone in a subselect which is selected from.
|
289
294
|
#
|
290
295
|
# If offset is used, an order must be provided, because the use of ROW_NUMBER
|
291
296
|
# requires an order.
|
@@ -293,16 +298,15 @@ module Sequel
|
|
293
298
|
return super unless o = @opts[:offset]
|
294
299
|
raise(Error, 'MSSQL requires an order be provided if using an offset') unless order = @opts[:order]
|
295
300
|
dsa1 = dataset_alias(1)
|
296
|
-
dsa2 = dataset_alias(2)
|
297
301
|
rn = row_number_column
|
298
|
-
|
302
|
+
sel = [Sequel::SQL::WindowFunction.new(:ROW_NUMBER.sql_function, Sequel::SQL::Window.new(:order=>order)).as(rn)]
|
303
|
+
sel.unshift(WILDCARD) unless osel = @opts[:select] and !osel.empty?
|
304
|
+
subselect_sql(unlimited.
|
299
305
|
unordered.
|
300
|
-
|
301
|
-
select{[WILDCARD, ROW_NUMBER(:over, :order=>order){}.as(rn)]}.
|
306
|
+
select_more(*sel).
|
302
307
|
from_self(:alias=>dsa1).
|
303
308
|
limit(@opts[:limit]).
|
304
|
-
where(rn > o)
|
305
|
-
select_sql
|
309
|
+
where(SQL::Identifier.new(rn) > o))
|
306
310
|
end
|
307
311
|
|
308
312
|
# The version of the database server.
|
@@ -420,9 +424,13 @@ module Sequel
|
|
420
424
|
SELECT_CLAUSE_METHODS
|
421
425
|
end
|
422
426
|
|
427
|
+
def select_into_sql(sql)
|
428
|
+
sql << " INTO #{table_ref(@opts[:into])}" if @opts[:into]
|
429
|
+
end
|
430
|
+
|
423
431
|
# MSSQL uses TOP for limit
|
424
432
|
def select_limit_sql(sql)
|
425
|
-
sql << " TOP #{@opts[:limit]}" if @opts[:limit]
|
433
|
+
sql << " TOP (#{literal(@opts[:limit])})" if @opts[:limit]
|
426
434
|
end
|
427
435
|
|
428
436
|
# MSSQL uses the WITH statement to lock tables
|
@@ -4,8 +4,17 @@ module Sequel
|
|
4
4
|
|
5
5
|
module MySQL
|
6
6
|
class << self
|
7
|
-
# Set the default
|
8
|
-
|
7
|
+
# Set the default charset used for CREATE TABLE. You can pass the
|
8
|
+
# :charset option to create_table to override this setting.
|
9
|
+
attr_accessor :default_charset
|
10
|
+
|
11
|
+
# Set the default collation used for CREATE TABLE. You can pass the
|
12
|
+
# :collate option to create_table to override this setting.
|
13
|
+
attr_accessor :default_collate
|
14
|
+
|
15
|
+
# Set the default engine used for CREATE TABLE. You can pass the
|
16
|
+
# :engine option to create_table to override this setting.
|
17
|
+
attr_accessor :default_engine
|
9
18
|
end
|
10
19
|
|
11
20
|
# Methods shared by Database instances that connect to MySQL,
|
@@ -120,16 +129,16 @@ module Sequel
|
|
120
129
|
super
|
121
130
|
end
|
122
131
|
|
123
|
-
# MySQL doesn't handle references as column constraints, it must use a separate table constraint
|
124
|
-
def column_references_column_constraint_sql(column)
|
125
|
-
"#{", FOREIGN KEY (#{quote_identifier(column[:name])})" unless column[:type] == :check}#{column_references_sql(column)}"
|
126
|
-
end
|
127
|
-
|
128
132
|
# Use MySQL specific syntax for engine type and character encoding
|
129
133
|
def create_table_sql(name, generator, options = {})
|
130
134
|
engine = options.include?(:engine) ? options[:engine] : Sequel::MySQL.default_engine
|
131
135
|
charset = options.include?(:charset) ? options[:charset] : Sequel::MySQL.default_charset
|
132
136
|
collate = options.include?(:collate) ? options[:collate] : Sequel::MySQL.default_collate
|
137
|
+
generator.columns.each do |c|
|
138
|
+
if t = c.delete(:table)
|
139
|
+
generator.foreign_key([c[:name]], t, c.merge(:name=>nil, :type=>:foreign_key))
|
140
|
+
end
|
141
|
+
end
|
133
142
|
"#{super}#{" ENGINE=#{engine}" if engine}#{" DEFAULT CHARSET=#{charset}" if charset}#{" DEFAULT COLLATE=#{collate}" if collate}"
|
134
143
|
end
|
135
144
|
|
@@ -265,6 +265,11 @@ module Sequel
|
|
265
265
|
@opts[:where] ? super : filter(1=>1).delete
|
266
266
|
end
|
267
267
|
|
268
|
+
# SQLite does not support DISTINCT ON
|
269
|
+
def supports_distinct_on?
|
270
|
+
false
|
271
|
+
end
|
272
|
+
|
268
273
|
# Return an array of strings specifying a query explanation for a SELECT of the
|
269
274
|
# current dataset.
|
270
275
|
def explain
|
@@ -100,7 +100,7 @@ module Sequel
|
|
100
100
|
# Also, force the max connections to 1 if a memory database is being
|
101
101
|
# used, as otherwise each connection gets a separate database.
|
102
102
|
def connection_pool_default_options
|
103
|
-
o = super.
|
103
|
+
o = super.dup
|
104
104
|
# Default to only a single connection if a memory database is used,
|
105
105
|
# because otherwise each connection will get a separate database
|
106
106
|
o[:max_connections] = 1 if @opts[:database] == ':memory:' || blank_object?(@opts[:database])
|
@@ -192,6 +192,7 @@ module Sequel
|
|
192
192
|
ps = to_prepared_statement(type, values)
|
193
193
|
ps.extend(PreparedStatementMethods)
|
194
194
|
db.prepared_statements[name] = ps if name
|
195
|
+
ps.prepared_sql
|
195
196
|
ps
|
196
197
|
end
|
197
198
|
|
@@ -1,369 +1,87 @@
|
|
1
|
-
#
|
2
|
-
#
|
3
|
-
#
|
1
|
+
# The base connection pool class, which all other connection pools are built
|
2
|
+
# on. This class is not instantiated directly, but subclasses should at
|
3
|
+
# the very least implement the following API:
|
4
|
+
# * initialize(Hash, &block) - The block is used as the connection proc,
|
5
|
+
# which should accept a single symbol argument.
|
6
|
+
# * hold(Symbol, &block) - yield a connection object (obtained from calling
|
7
|
+
# the block passed to initialize) to the current block. For sharded
|
8
|
+
# connection pools, the Symbol passed is the shard/server to use.
|
9
|
+
# * disconnect(Symbol, &block) - disconnect the connection object. If a
|
10
|
+
# block is given, pass the connection option to it, otherwise use the
|
11
|
+
# :disconnection_proc option in the hash passed to initialize. For sharded
|
12
|
+
# connection pools, the Symbol passed is the shard/server to use.
|
13
|
+
# * servers - an array of shard/server symbols for all shards/servers that this
|
14
|
+
# connection pool recognizes.
|
15
|
+
# * size - an integer representing the total number of connections in the pool,
|
16
|
+
# or for the given shard/server if sharding is supported.
|
17
|
+
#
|
18
|
+
# For sharded connection pools, the sharded API:
|
19
|
+
# * add_servers(Array of Symbols) - start recognizing all shards/servers specified
|
20
|
+
# by the array of symbols.
|
21
|
+
# * remove_servers(Array of Symbols) - no longer recognize all shards/servers
|
22
|
+
# specified by the array of symbols.
|
4
23
|
class Sequel::ConnectionPool
|
5
|
-
# The
|
6
|
-
|
7
|
-
|
8
|
-
#
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
#
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
# The connection pool takes the following options:
|
30
|
-
#
|
31
|
-
# * :disconnection_proc - The proc called when removing connections from the pool.
|
32
|
-
# * :max_connections - The maximum number of connections the connection pool
|
33
|
-
# will open (default 4)
|
34
|
-
# * :pool_convert_exceptions - Whether to convert non-StandardError based exceptions
|
35
|
-
# to RuntimeError exceptions (default true)
|
36
|
-
# * :pool_sleep_time - The amount of time to sleep before attempting to acquire
|
37
|
-
# a connection again (default 0.001)
|
38
|
-
# * :pool_timeout - The amount of seconds to wait to acquire a connection
|
39
|
-
# before raising a PoolTimeoutError (default 5)
|
40
|
-
# * :servers - A hash of servers to use. Keys should be symbols. If not
|
41
|
-
# present, will use a single :default server. The server name symbol will
|
42
|
-
# be passed to the connection_proc.
|
43
|
-
def initialize(opts = {}, &block)
|
44
|
-
@max_size = Integer(opts[:max_connections] || 4)
|
45
|
-
raise(Sequel::Error, ':max_connections must be positive') if @max_size < 1
|
46
|
-
@mutex = Mutex.new
|
47
|
-
@connection_proc = block
|
48
|
-
@disconnection_proc = opts[:disconnection_proc]
|
49
|
-
@available_connections = {}
|
50
|
-
@allocated = {}
|
51
|
-
@connections_to_remove = []
|
52
|
-
@servers = Hash.new(:default)
|
53
|
-
add_servers([:default])
|
54
|
-
add_servers(opts[:servers].keys) if opts[:servers]
|
55
|
-
@timeout = Integer(opts[:pool_timeout] || 5)
|
56
|
-
@sleep_time = Float(opts[:pool_sleep_time] || 0.001)
|
57
|
-
@convert_exceptions = opts.include?(:pool_convert_exceptions) ? opts[:pool_convert_exceptions] : true
|
58
|
-
end
|
59
|
-
|
60
|
-
# Adds new servers to the connection pool. Primarily used in conjunction with master/slave
|
61
|
-
# or shard configurations. Allows for dynamic expansion of the potential slaves/shards
|
62
|
-
# at runtime. servers argument should be an array of symbols.
|
63
|
-
def add_servers(servers)
|
64
|
-
sync do
|
65
|
-
servers.each do |server|
|
66
|
-
unless @servers.has_key?(server)
|
67
|
-
@servers[server] = server
|
68
|
-
@available_connections[server] = []
|
69
|
-
@allocated[server] = {}
|
70
|
-
end
|
24
|
+
# The default server to use
|
25
|
+
DEFAULT_SERVER = :default
|
26
|
+
|
27
|
+
# A map of [single threaded, sharded] values to files (indicating strings to
|
28
|
+
# be required) ConnectionPool subclasses.
|
29
|
+
CONNECTION_POOL_MAP = {[true, false] => :single,
|
30
|
+
[true, true] => :sharded_single,
|
31
|
+
[false, false] => :threaded,
|
32
|
+
[false, true] => :sharded_threaded}
|
33
|
+
|
34
|
+
# Class methods used to return an appropriate pool subclass, separated
|
35
|
+
# into a module for easier overridding by extensions.
|
36
|
+
module ClassMethods
|
37
|
+
# Return a pool subclass instance based on the given options. If a :pool_class
|
38
|
+
# option is provided is provided, use that pool class, otherwise
|
39
|
+
# use a new instance of an appropriate pool subclass based on the
|
40
|
+
# :single_threaded and :servers options.
|
41
|
+
def get_pool(opts = {}, &block)
|
42
|
+
case v = connection_pool_class(opts)
|
43
|
+
when Class
|
44
|
+
v.new(opts, &block)
|
45
|
+
when Symbol
|
46
|
+
Sequel.ts_require("connection_pool/#{v}")
|
47
|
+
connection_pool_class(opts).new(opts, &block) || raise(Sequel::Error, "No connection pool class found")
|
71
48
|
end
|
72
49
|
end
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
@allocated[server]
|
80
|
-
end
|
81
|
-
|
82
|
-
# An array of connections opened but not currently used, for the given
|
83
|
-
# server. Nonexistent servers will return nil. Treat this as read only, do
|
84
|
-
# not modify the resulting object.
|
85
|
-
def available_connections(server=:default)
|
86
|
-
@available_connections[server]
|
87
|
-
end
|
88
|
-
|
89
|
-
# The total number of connections opened for the given server, should
|
90
|
-
# be equal to available_connections.length + allocated.length. Nonexistent
|
91
|
-
# servers will return the created count of the default server.
|
92
|
-
def created_count(server=:default)
|
93
|
-
server = @servers[server]
|
94
|
-
@allocated[server].length + @available_connections[server].length
|
95
|
-
end
|
96
|
-
alias size created_count
|
97
|
-
|
98
|
-
# Removes all connection currently available on all servers, optionally
|
99
|
-
# yielding each connection to the given block. This method has the effect of
|
100
|
-
# disconnecting from the database, assuming that no connections are currently
|
101
|
-
# being used. If connections are being used, they are scheduled to be
|
102
|
-
# disconnected as soon as they are returned to the pool.
|
103
|
-
#
|
104
|
-
# Once a connection is requested using #hold, the connection pool
|
105
|
-
# creates new connections to the database. Options:
|
106
|
-
# * :server - Should be a symbol specifing the server to disconnect from,
|
107
|
-
# or an array of symbols to specify multiple servers.
|
108
|
-
def disconnect(opts={}, &block)
|
109
|
-
block ||= @disconnection_proc
|
110
|
-
sync do
|
111
|
-
(opts[:server] ? Array(opts[:server]) : @servers.keys).each do |s|
|
112
|
-
disconnect_server(s, &block)
|
113
|
-
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
# Return a connection pool class based on the given options.
|
54
|
+
def connection_pool_class(opts)
|
55
|
+
opts[:pool_class] || CONNECTION_POOL_MAP[[!!opts[:single_threaded], !!opts[:servers]]]
|
114
56
|
end
|
115
57
|
end
|
58
|
+
extend ClassMethods
|
116
59
|
|
117
|
-
#
|
118
|
-
#
|
119
|
-
#
|
120
|
-
#
|
121
|
-
#
|
122
|
-
#
|
123
|
-
# Pool#hold is re-entrant, meaning it can be called recursively in
|
124
|
-
# the same thread without blocking.
|
125
|
-
#
|
126
|
-
# If no connection is immediately available and the pool is already using the maximum
|
127
|
-
# number of connections, Pool#hold will block until a connection
|
128
|
-
# is available or the timeout expires. If the timeout expires before a
|
129
|
-
# connection can be acquired, a Sequel::PoolTimeout is
|
130
|
-
# raised.
|
131
|
-
def hold(server=:default)
|
132
|
-
begin
|
133
|
-
sync{server = @servers[server]}
|
134
|
-
t = Thread.current
|
135
|
-
if conn = owned_connection(t, server)
|
136
|
-
return yield(conn)
|
137
|
-
end
|
138
|
-
begin
|
139
|
-
unless conn = acquire(t, server)
|
140
|
-
time = Time.now
|
141
|
-
timeout = time + @timeout
|
142
|
-
sleep_time = @sleep_time
|
143
|
-
sleep sleep_time
|
144
|
-
until conn = acquire(t, server)
|
145
|
-
raise(::Sequel::PoolTimeout) if Time.now > timeout
|
146
|
-
sleep sleep_time
|
147
|
-
end
|
148
|
-
end
|
149
|
-
yield conn
|
150
|
-
rescue Sequel::DatabaseDisconnectError
|
151
|
-
sync{@connections_to_remove << conn} if conn
|
152
|
-
raise
|
153
|
-
ensure
|
154
|
-
sync{release(t, conn, server)} if conn
|
155
|
-
end
|
156
|
-
rescue StandardError
|
157
|
-
raise
|
158
|
-
rescue Exception => e
|
159
|
-
raise(@convert_exceptions ? RuntimeError.new(e.message) : e)
|
160
|
-
end
|
161
|
-
end
|
162
|
-
|
163
|
-
# Remove servers from the connection pool. Primarily used in conjunction with master/slave
|
164
|
-
# or shard configurations. Similar to disconnecting from all given servers,
|
165
|
-
# except that after it is used, future requests for the server will use the
|
166
|
-
# :default server instead.
|
167
|
-
def remove_servers(servers)
|
168
|
-
sync do
|
169
|
-
raise(Sequel::Error, "cannot remove default server") if servers.include?(:default)
|
170
|
-
servers.each do |server|
|
171
|
-
if @servers.include?(server)
|
172
|
-
disconnect_server(server, &@disconnection_proc)
|
173
|
-
@available_connections.delete(server)
|
174
|
-
@allocated.delete(server)
|
175
|
-
@servers.delete(server)
|
176
|
-
end
|
177
|
-
end
|
178
|
-
end
|
179
|
-
end
|
180
|
-
|
181
|
-
# Return an array of symbols for servers in the connection pool.
|
182
|
-
def servers
|
183
|
-
sync{@servers.keys}
|
184
|
-
end
|
185
|
-
|
186
|
-
private
|
187
|
-
|
188
|
-
# Assigns a connection to the supplied thread for the given server, if one
|
189
|
-
# is available. The calling code should NOT already have the mutex when
|
190
|
-
# calling this.
|
191
|
-
def acquire(thread, server)
|
192
|
-
sync do
|
193
|
-
if conn = available(server)
|
194
|
-
allocated(server)[thread] = conn
|
195
|
-
end
|
196
|
-
end
|
197
|
-
end
|
198
|
-
|
199
|
-
# Returns an available connection to the given server. If no connection is
|
200
|
-
# available, tries to create a new connection. The calling code should already
|
201
|
-
# have the mutex before calling this.
|
202
|
-
def available(server)
|
203
|
-
available_connections(server).pop || make_new(server)
|
204
|
-
end
|
205
|
-
|
206
|
-
# Disconnect from the given server. Disconnects available connections
|
207
|
-
# immediately, and schedules currently allocated connections for disconnection
|
208
|
-
# as soon as they are returned to the pool. The calling code should already
|
209
|
-
# have the mutex before calling this.
|
210
|
-
def disconnect_server(server, &block)
|
211
|
-
if conns = available_connections(server)
|
212
|
-
conns.each{|conn| block.call(conn)} if block
|
213
|
-
conns.clear
|
214
|
-
end
|
215
|
-
@connections_to_remove.concat(allocated(server).values)
|
216
|
-
end
|
217
|
-
|
218
|
-
# Creates a new connection to the given server if the size of the pool for
|
219
|
-
# the server is less than the maximum size of the pool. The calling code
|
220
|
-
# should already have the mutex before calling this.
|
221
|
-
def make_new(server)
|
222
|
-
if (n = created_count(server)) >= @max_size
|
223
|
-
allocated(server).to_a.each{|t, c| release(t, c, server) unless t.alive?}
|
224
|
-
n = nil
|
225
|
-
end
|
226
|
-
if (n || created_count(server)) < @max_size
|
227
|
-
raise(Sequel::Error, "No connection proc specified") unless @connection_proc
|
228
|
-
begin
|
229
|
-
conn = @connection_proc.call(server)
|
230
|
-
rescue Exception=>exception
|
231
|
-
raise Sequel.convert_exception_class(exception, Sequel::DatabaseConnectionError)
|
232
|
-
end
|
233
|
-
raise(Sequel::DatabaseConnectionError, "Connection parameters not valid") unless conn
|
234
|
-
conn
|
235
|
-
end
|
236
|
-
end
|
237
|
-
|
238
|
-
# Returns the connection owned by the supplied thread for the given server,
|
239
|
-
# if any. The calling code should NOT already have the mutex before calling this.
|
240
|
-
def owned_connection(thread, server)
|
241
|
-
sync{@allocated[server][thread]}
|
242
|
-
end
|
243
|
-
|
244
|
-
# Releases the connection assigned to the supplied thread and server. If the
|
245
|
-
# server or connection given is scheduled for disconnection, remove the
|
246
|
-
# connection instead of releasing it back to the pool.
|
247
|
-
# The calling code should already have the mutex before calling this.
|
248
|
-
def release(thread, conn, server)
|
249
|
-
if @connections_to_remove.include?(conn)
|
250
|
-
remove(thread, conn, server)
|
251
|
-
else
|
252
|
-
available_connections(server) << allocated(server).delete(thread)
|
253
|
-
end
|
254
|
-
end
|
255
|
-
|
256
|
-
# Removes the currently allocated connection from the connection pool. The
|
257
|
-
# calling code should already have the mutex before calling this.
|
258
|
-
def remove(thread, conn, server)
|
259
|
-
@connections_to_remove.delete(conn)
|
260
|
-
allocated(server).delete(thread) if @servers.include?(server)
|
261
|
-
@disconnection_proc.call(conn) if @disconnection_proc
|
262
|
-
end
|
263
|
-
|
264
|
-
# Yield to the block while inside the mutex. The calling code should NOT
|
265
|
-
# already have the mutex before calling this.
|
266
|
-
def sync
|
267
|
-
@mutex.synchronize{yield}
|
268
|
-
end
|
269
|
-
end
|
270
|
-
|
271
|
-
# A SingleThreadedPool acts as a replacement for a ConnectionPool for use
|
272
|
-
# in single-threaded applications. ConnectionPool imposes a substantial
|
273
|
-
# performance penalty, so SingleThreadedPool is used to gain some speed.
|
274
|
-
class Sequel::SingleThreadedPool
|
275
|
-
# The proc used to create a new database connection
|
276
|
-
attr_writer :connection_proc
|
277
|
-
|
278
|
-
# The proc used to disconnect a database connection.
|
279
|
-
attr_accessor :disconnection_proc
|
280
|
-
|
281
|
-
# Initializes the instance with the supplied block as the connection_proc.
|
282
|
-
#
|
283
|
-
# The single threaded pool takes the following options:
|
284
|
-
#
|
285
|
-
# * :disconnection_proc - The proc called when removing connections from the pool.
|
286
|
-
# * :pool_convert_exceptions - Whether to convert non-StandardError based exceptions
|
287
|
-
# to RuntimeError exceptions (default true)
|
288
|
-
# * :servers - A hash of servers to use. Keys should be symbols. If not
|
289
|
-
# present, will use a single :default server. The server name symbol will
|
290
|
-
# be passed to the connection_proc.
|
60
|
+
# Instantiates a connection pool with the given options. The block is called
|
61
|
+
# with a single symbol (specifying the server/shard to use) every time a new
|
62
|
+
# connection is needed. The following options are respected for all connection
|
63
|
+
# pools: #
|
64
|
+
# * :disconnection_proc - The proc called when removing connections from the pool,
|
65
|
+
# which is passed the connection to disconnect.
|
291
66
|
def initialize(opts={}, &block)
|
292
|
-
@connection_proc = block
|
67
|
+
raise(Sequel::Error, "No connection proc specified") unless @connection_proc = block
|
293
68
|
@disconnection_proc = opts[:disconnection_proc]
|
294
|
-
@conns = {}
|
295
|
-
@servers = Hash.new(:default)
|
296
|
-
add_servers([:default])
|
297
|
-
add_servers(opts[:servers].keys) if opts[:servers]
|
298
|
-
@convert_exceptions = opts.include?(:pool_convert_exceptions) ? opts[:pool_convert_exceptions] : true
|
299
69
|
end
|
300
70
|
|
301
|
-
#
|
302
|
-
|
303
|
-
|
304
|
-
def add_servers(servers)
|
305
|
-
servers.each{|s| @servers[s] = s}
|
71
|
+
# Alias for size, not aliased directly for ease of subclass implementation
|
72
|
+
def created_count(*args)
|
73
|
+
size(*args)
|
306
74
|
end
|
307
75
|
|
308
|
-
#
|
309
|
-
def conn(server=:default)
|
310
|
-
@conns[@servers[server]]
|
311
|
-
end
|
312
|
-
|
313
|
-
# Disconnects from the database. Once a connection is requested using
|
314
|
-
# #hold, the connection is reestablished. Options:
|
315
|
-
# * :server - Should be a symbol specifing the server to disconnect from,
|
316
|
-
# or an array of symbols to specify multiple servers.
|
317
|
-
def disconnect(opts={}, &block)
|
318
|
-
block ||= @disconnection_proc
|
319
|
-
(opts[:server] ? Array(opts[:server]) : servers).each{|s| disconnect_server(s, &block)}
|
320
|
-
end
|
321
|
-
|
322
|
-
# Yields the connection to the supplied block for the given server.
|
323
|
-
# This method simulates the ConnectionPool#hold API.
|
324
|
-
def hold(server=:default)
|
325
|
-
begin
|
326
|
-
begin
|
327
|
-
server = @servers[server]
|
328
|
-
yield(c = (@conns[server] ||= make_new(server)))
|
329
|
-
rescue Sequel::DatabaseDisconnectError
|
330
|
-
disconnect_server(server, &@disconnection_proc)
|
331
|
-
raise
|
332
|
-
end
|
333
|
-
rescue Exception => e
|
334
|
-
# if the error is not a StandardError it is converted into RuntimeError.
|
335
|
-
raise(@convert_exceptions && !e.is_a?(StandardError) ? RuntimeError.new(e.message) : e)
|
336
|
-
end
|
337
|
-
end
|
338
|
-
|
339
|
-
# Remove servers from the connection pool. Primarily used in conjunction with master/slave
|
340
|
-
# or shard configurations. Similar to disconnecting from all given servers,
|
341
|
-
# except that after it is used, future requests for the server will use the
|
342
|
-
# :default server instead.
|
343
|
-
def remove_servers(servers)
|
344
|
-
raise(Sequel::Error, "cannot remove default server") if servers.include?(:default)
|
345
|
-
servers.each do |server|
|
346
|
-
disconnect_server(server, &@disconnection_proc)
|
347
|
-
@servers.delete(server)
|
348
|
-
end
|
349
|
-
end
|
350
|
-
|
351
|
-
# Return an array of symbols for servers in the connection pool.
|
76
|
+
# An array of symbols for all shards/servers, which is a single :default by default.
|
352
77
|
def servers
|
353
|
-
|
78
|
+
[:default]
|
354
79
|
end
|
355
80
|
|
356
81
|
private
|
357
82
|
|
358
|
-
#
|
359
|
-
|
360
|
-
if conn = @conns.delete(server)
|
361
|
-
block.call(conn) if block
|
362
|
-
end
|
363
|
-
end
|
364
|
-
|
365
|
-
# Return a connection to the given server, raising DatabaseConnectionError
|
366
|
-
# if the connection_proc raises an error or doesn't return a valid connection.
|
83
|
+
# Return a new connection by calling the connection proc with the given server name,
|
84
|
+
# and checking for connection errors.
|
367
85
|
def make_new(server)
|
368
86
|
begin
|
369
87
|
conn = @connection_proc.call(server)
|