sequel 3.8.0 → 3.9.0

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