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