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.
Files changed (65) hide show
  1. data/CHANGELOG +48 -0
  2. data/Rakefile +6 -28
  3. data/bin/sequel +7 -2
  4. data/doc/release_notes/3.9.0.txt +233 -0
  5. data/lib/sequel/adapters/ado.rb +4 -8
  6. data/lib/sequel/adapters/amalgalite.rb +1 -1
  7. data/lib/sequel/adapters/dbi.rb +3 -3
  8. data/lib/sequel/adapters/do.rb +7 -13
  9. data/lib/sequel/adapters/jdbc.rb +10 -16
  10. data/lib/sequel/adapters/jdbc/h2.rb +5 -0
  11. data/lib/sequel/adapters/mysql.rb +10 -23
  12. data/lib/sequel/adapters/odbc.rb +6 -10
  13. data/lib/sequel/adapters/postgres.rb +0 -5
  14. data/lib/sequel/adapters/shared/mssql.rb +17 -9
  15. data/lib/sequel/adapters/shared/mysql.rb +16 -7
  16. data/lib/sequel/adapters/shared/sqlite.rb +5 -0
  17. data/lib/sequel/adapters/sqlite.rb +2 -1
  18. data/lib/sequel/connection_pool.rb +67 -349
  19. data/lib/sequel/connection_pool/sharded_single.rb +84 -0
  20. data/lib/sequel/connection_pool/sharded_threaded.rb +211 -0
  21. data/lib/sequel/connection_pool/single.rb +29 -0
  22. data/lib/sequel/connection_pool/threaded.rb +150 -0
  23. data/lib/sequel/core.rb +46 -15
  24. data/lib/sequel/database.rb +11 -9
  25. data/lib/sequel/dataset/convenience.rb +23 -0
  26. data/lib/sequel/dataset/graph.rb +2 -2
  27. data/lib/sequel/dataset/query.rb +9 -5
  28. data/lib/sequel/dataset/sql.rb +87 -12
  29. data/lib/sequel/extensions/inflector.rb +8 -1
  30. data/lib/sequel/extensions/schema_dumper.rb +3 -4
  31. data/lib/sequel/model/associations.rb +5 -43
  32. data/lib/sequel/model/base.rb +9 -2
  33. data/lib/sequel/model/default_inflections.rb +1 -1
  34. data/lib/sequel/model/exceptions.rb +11 -1
  35. data/lib/sequel/model/inflections.rb +8 -1
  36. data/lib/sequel/model/plugins.rb +2 -12
  37. data/lib/sequel/plugins/active_model.rb +5 -0
  38. data/lib/sequel/plugins/association_dependencies.rb +1 -1
  39. data/lib/sequel/plugins/many_through_many.rb +1 -1
  40. data/lib/sequel/plugins/optimistic_locking.rb +65 -0
  41. data/lib/sequel/plugins/single_table_inheritance.rb +14 -3
  42. data/lib/sequel/plugins/validation_helpers.rb +2 -2
  43. data/lib/sequel/sql.rb +2 -2
  44. data/lib/sequel/timezones.rb +2 -2
  45. data/lib/sequel/version.rb +1 -1
  46. data/spec/adapters/mssql_spec.rb +19 -0
  47. data/spec/adapters/mysql_spec.rb +4 -0
  48. data/spec/adapters/postgres_spec.rb +180 -0
  49. data/spec/adapters/spec_helper.rb +15 -1
  50. data/spec/core/connection_pool_spec.rb +119 -78
  51. data/spec/core/database_spec.rb +41 -50
  52. data/spec/core/dataset_spec.rb +115 -4
  53. data/spec/extensions/active_model_spec.rb +40 -34
  54. data/spec/extensions/boolean_readers_spec.rb +1 -1
  55. data/spec/extensions/migration_spec.rb +43 -38
  56. data/spec/extensions/optimistic_locking_spec.rb +100 -0
  57. data/spec/extensions/schema_dumper_spec.rb +4 -4
  58. data/spec/extensions/single_table_inheritance_spec.rb +19 -11
  59. data/spec/integration/dataset_test.rb +44 -1
  60. data/spec/integration/plugin_test.rb +39 -0
  61. data/spec/integration/prepared_statement_test.rb +58 -7
  62. data/spec/integration/spec_helper.rb +4 -0
  63. data/spec/model/eager_loading_spec.rb +24 -0
  64. data/spec/model/validations_spec.rb +5 -1
  65. metadata +114 -106
@@ -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.require 'adapters/jdbc/postgresql'
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.require 'adapters/jdbc/mysql'
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.require 'adapters/jdbc/sqlite'
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.require 'adapters/jdbc/oracle'
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.require 'adapters/jdbc/mssql'
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.require 'adapters/jdbc/h2'
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
- require "jdbc/#{name}"
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
- @opts = opts
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
@@ -101,6 +101,11 @@ module Sequel
101
101
  # H2 doesn't support JOIN USING
102
102
  def supports_join_using?
103
103
  false
104
+ end
105
+
106
+ # H2 doesn't support multiple columns in IN/NOT IN
107
+ def supports_multiple_column_in?
108
+ false
104
109
  end
105
110
 
106
111
  private
@@ -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
- attr_accessor :convert_invalid_date_time, :convert_tinyint_to_bool
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]
@@ -9,13 +9,13 @@ module Sequel
9
9
  DRV_NAME_GUARDS = '{%s}'.freeze
10
10
 
11
11
  def initialize(opts)
12
- super(opts)
13
- case opts[:db_type]
12
+ super
13
+ case @opts[:db_type]
14
14
  when 'mssql'
15
- Sequel.require 'adapters/odbc/mssql'
15
+ Sequel.ts_require 'adapters/odbc/mssql'
16
16
  extend Sequel::ODBC::MSSQL::DatabaseMethods
17
17
  when 'progress'
18
- Sequel.require 'adapters/shared/progress'
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 CTE which is selected from.
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
- unlimited.
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
- from_self(:alias=>dsa2).
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 options used for CREATE TABLE
8
- attr_accessor :default_charset, :default_collate, :default_engine
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.merge(:pool_convert_exceptions=>false)
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
- # A ConnectionPool manages access to database connections by keeping
2
- # multiple connections and giving threads exclusive access to each
3
- # connection.
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 proc used to create a new database connection.
6
- attr_accessor :connection_proc
7
-
8
- # The proc used to disconnect a database connection.
9
- attr_accessor :disconnection_proc
10
-
11
- # The maximum number of connections.
12
- attr_reader :max_size
13
-
14
- # The mutex that protects access to the other internal variables. You must use
15
- # this if you want to manipulate the variables safely.
16
- attr_reader :mutex
17
-
18
- # Constructs a new pool with a maximum size. If a block is supplied, it
19
- # is used to create new connections as they are needed.
20
- #
21
- # pool = ConnectionPool.new(:max_connections=>10) {MyConnection.new(opts)}
22
- #
23
- # The connection creation proc can be changed at any time by assigning a
24
- # Proc to pool#connection_proc.
25
- #
26
- # pool = ConnectionPool.new(:max_connections=>10)
27
- # pool.connection_proc = proc {MyConnection.new(opts)}
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
- end
74
-
75
- # A hash of connections currently being used for the given server, key is the
76
- # Thread, value is the connection. Nonexistent servers will return nil. Treat
77
- # this as read only, do not modify the resulting object.
78
- def allocated(server=:default)
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
- # Chooses the first available connection to the given server, or if none are
118
- # available, creates a new connection. Passes the connection to the supplied
119
- # block:
120
- #
121
- # pool.hold {|conn| conn.execute('DROP TABLE posts')}
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
- # Adds new servers to the connection pool. Primarily used in conjunction with master/slave
302
- # or shard configurations. Allows for dynamic expansion of the potential slaves/shards
303
- # at runtime. servers argument should be an array of symbols.
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
- # The connection for the given server.
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
- @servers.keys
78
+ [:default]
354
79
  end
355
80
 
356
81
  private
357
82
 
358
- # Disconnect from the given server, if connected.
359
- def disconnect_server(server, &block)
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)