sequel 2.3.0 → 2.4.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 (43) hide show
  1. data/CHANGELOG +16 -0
  2. data/README +4 -1
  3. data/Rakefile +17 -19
  4. data/doc/prepared_statements.rdoc +104 -0
  5. data/doc/sharding.rdoc +113 -0
  6. data/lib/sequel_core/adapters/ado.rb +24 -17
  7. data/lib/sequel_core/adapters/db2.rb +30 -33
  8. data/lib/sequel_core/adapters/dbi.rb +15 -13
  9. data/lib/sequel_core/adapters/informix.rb +13 -14
  10. data/lib/sequel_core/adapters/jdbc.rb +243 -60
  11. data/lib/sequel_core/adapters/jdbc/mysql.rb +32 -24
  12. data/lib/sequel_core/adapters/jdbc/postgresql.rb +32 -2
  13. data/lib/sequel_core/adapters/jdbc/sqlite.rb +16 -20
  14. data/lib/sequel_core/adapters/mysql.rb +164 -76
  15. data/lib/sequel_core/adapters/odbc.rb +21 -34
  16. data/lib/sequel_core/adapters/openbase.rb +10 -7
  17. data/lib/sequel_core/adapters/oracle.rb +17 -23
  18. data/lib/sequel_core/adapters/postgres.rb +246 -35
  19. data/lib/sequel_core/adapters/shared/mssql.rb +106 -0
  20. data/lib/sequel_core/adapters/shared/mysql.rb +34 -26
  21. data/lib/sequel_core/adapters/shared/postgres.rb +82 -38
  22. data/lib/sequel_core/adapters/shared/sqlite.rb +48 -16
  23. data/lib/sequel_core/adapters/sqlite.rb +141 -44
  24. data/lib/sequel_core/connection_pool.rb +85 -63
  25. data/lib/sequel_core/database.rb +46 -17
  26. data/lib/sequel_core/dataset.rb +21 -40
  27. data/lib/sequel_core/dataset/convenience.rb +3 -3
  28. data/lib/sequel_core/dataset/prepared_statements.rb +218 -0
  29. data/lib/sequel_core/exceptions.rb +0 -12
  30. data/lib/sequel_model/base.rb +1 -2
  31. data/lib/sequel_model/plugins.rb +1 -1
  32. data/spec/adapters/ado_spec.rb +32 -3
  33. data/spec/adapters/mysql_spec.rb +7 -8
  34. data/spec/integration/prepared_statement_test.rb +106 -0
  35. data/spec/sequel_core/connection_pool_spec.rb +105 -3
  36. data/spec/sequel_core/database_spec.rb +41 -3
  37. data/spec/sequel_core/dataset_spec.rb +117 -7
  38. data/spec/sequel_core/spec_helper.rb +2 -2
  39. data/spec/sequel_model/model_spec.rb +0 -6
  40. data/spec/sequel_model/spec_helper.rb +1 -1
  41. metadata +11 -6
  42. data/lib/sequel_core/adapters/adapter_skeleton.rb +0 -54
  43. data/lib/sequel_core/adapters/odbc_mssql.rb +0 -106
@@ -7,6 +7,10 @@ module Sequel
7
7
  TABLES_FILTER = "type = 'table' AND NOT name = 'sqlite_sequence'"
8
8
  TEMP_STORE = {'0' => :default, '1' => :file, '2' => :memory}.freeze
9
9
 
10
+ # SQLite supports limited table modification. You can add a column
11
+ # or an index. Dropping columns is supported by copying the table into
12
+ # a temporary table, dropping the table, and creating a new table without
13
+ # the column inside of a transaction.
10
14
  def alter_table_sql(table, op)
11
15
  case op[:op]
12
16
  when :add_column
@@ -28,51 +32,59 @@ module Sequel
28
32
  end
29
33
  end
30
34
 
35
+ # A symbol signifying the value of the auto_vacuum PRAGMA.
31
36
  def auto_vacuum
32
37
  AUTO_VACUUM[pragma_get(:auto_vacuum).to_s]
33
38
  end
34
39
 
40
+ # Set the auto_vacuum PRAGMA using the given symbol (:none, :full, or
41
+ # :incremental).
35
42
  def auto_vacuum=(value)
36
43
  value = AUTO_VACUUM.key(value) || (raise Error, "Invalid value for auto_vacuum option. Please specify one of :none, :full, :incremental.")
37
44
  pragma_set(:auto_vacuum, value)
38
45
  end
39
46
 
47
+ # Get the value of the given PRAGMA.
40
48
  def pragma_get(name)
41
49
  self["PRAGMA #{name}"].single_value
42
50
  end
43
51
 
52
+ # Set the value of the given PRAGMA to value.
44
53
  def pragma_set(name, value)
45
54
  execute_ddl("PRAGMA #{name} = #{value}")
46
55
  end
47
56
 
48
- def serial_primary_key_options
49
- {:primary_key => true, :type => :integer, :auto_increment => true}
50
- end
51
-
57
+ # A symbol signifying the value of the synchronous PRAGMA.
52
58
  def synchronous
53
59
  SYNCHRONOUS[pragma_get(:synchronous).to_s]
54
60
  end
55
61
 
62
+ # Set the synchronous PRAGMA using the given symbol (:off, :normal, or :full).
56
63
  def synchronous=(value)
57
64
  value = SYNCHRONOUS.key(value) || (raise Error, "Invalid value for synchronous option. Please specify one of :off, :normal, :full.")
58
65
  pragma_set(:synchronous, value)
59
66
  end
60
-
67
+
68
+ # Array of symbols specifying the table names in the current database.
61
69
  def tables
62
70
  self[:sqlite_master].filter(TABLES_FILTER).map {|r| r[:name].to_sym}
63
71
  end
64
72
 
73
+ # A symbol signifying the value of the temp_store PRAGMA.
65
74
  def temp_store
66
75
  TEMP_STORE[pragma_get(:temp_store).to_s]
67
76
  end
68
77
 
78
+ # Set the temp_store PRAGMA using the given symbol (:default, :file, or :memory).
69
79
  def temp_store=(value)
70
80
  value = TEMP_STORE.key(value) || (raise Error, "Invalid value for temp_store option. Please specify one of :default, :file, :memory.")
71
81
  pragma_set(:temp_store, value)
72
82
  end
73
83
 
74
84
  private
75
-
85
+
86
+ # SQLite supports schema parsing using the table_info PRAGMA, so
87
+ # parse the output of that into the format Sequel expects.
76
88
  def schema_parse_table(table_name, opts)
77
89
  rows = self["PRAGMA table_info(?)", table_name].collect do |row|
78
90
  row.delete(:cid)
@@ -92,15 +104,21 @@ module Sequel
92
104
  end
93
105
  schema_parse_rows(rows)
94
106
  end
95
-
107
+
108
+ # SQLite doesn't support getting the schema of all tables at once,
109
+ # so loop through the output of #tables to get them.
96
110
  def schema_parse_tables(opts)
97
111
  schemas = {}
98
112
  tables.each{|table| schemas[table] = schema_parse_table(table, opts)}
99
113
  schemas
100
114
  end
101
115
  end
102
-
103
- module DatasetMethods
116
+
117
+ # Instance methods for datasets that connect to an SQLite database
118
+ module DatasetMethods
119
+ # SQLite does not support pattern matching via regular expressions.
120
+ # SQLite is case insensitive (depending on pragma), so use LIKE for
121
+ # ILIKE.
104
122
  def complex_expression_sql(op, args)
105
123
  case op
106
124
  when :~, :'!~', :'~*', :'!~*'
@@ -113,23 +131,29 @@ module Sequel
113
131
  end
114
132
  end
115
133
 
116
- def delete(opts = nil)
134
+ # SQLite performs a TRUNCATE style DELETE if no filter is specified.
135
+ # Since we want to always return the count of records, do a specific
136
+ # count in the case of no filter.
137
+ def delete(opts = {})
117
138
  # check if no filter is specified
118
- unless (opts && opts[:where]) || @opts[:where]
119
- @db.transaction do
139
+ opts = @opts.merge(opts)
140
+ unless opts[:where]
141
+ @db.transaction(opts[:server]) do
120
142
  unfiltered_count = count
121
- @db.execute_dui delete_sql(opts)
143
+ execute_dui(delete_sql(opts))
122
144
  unfiltered_count
123
145
  end
124
146
  else
125
- @db.execute_dui delete_sql(opts)
147
+ execute_dui(delete_sql(opts))
126
148
  end
127
149
  end
128
150
 
151
+ # Insert the values into the database.
129
152
  def insert(*values)
130
- @db.execute_insert insert_sql(*values)
153
+ execute_insert(insert_sql(*values))
131
154
  end
132
-
155
+
156
+ # Allow inserting of values directly from a dataset.
133
157
  def insert_sql(*values)
134
158
  if (values.size == 1) && values.first.is_a?(Sequel::Dataset)
135
159
  "INSERT INTO #{source_list(@opts[:from])} #{values.first.sql};"
@@ -138,9 +162,17 @@ module Sequel
138
162
  end
139
163
  end
140
164
 
165
+ # SQLite uses the nonstandard ` (backtick) for quoting identifiers.
141
166
  def quoted_identifier(c)
142
167
  "`#{c}`"
143
168
  end
169
+
170
+ private
171
+
172
+ # Call execute_insert on the database with the given SQL.
173
+ def execute_insert(sql, opts={})
174
+ @db.execute_insert(sql, {:server=>@opts[:server] || :default}.merge(opts))
175
+ end
144
176
  end
145
177
  end
146
178
  end
@@ -2,22 +2,33 @@ require 'sqlite3'
2
2
  require 'sequel_core/adapters/shared/sqlite'
3
3
 
4
4
  module Sequel
5
+ # Top level module for holding all SQLite-related modules and classes
6
+ # for Sequel.
5
7
  module SQLite
8
+ # Database class for PostgreSQL databases used with Sequel and the
9
+ # ruby-sqlite3 driver.
6
10
  class Database < Sequel::Database
7
11
  include ::Sequel::SQLite::DatabaseMethods
8
12
 
9
13
  set_adapter_scheme :sqlite
10
14
 
15
+ # Mimic the file:// uri, by having 2 preceding slashes specify a relative
16
+ # path, and 3 preceding slashes specify an absolute path.
11
17
  def self.uri_to_options(uri) # :nodoc:
12
18
  { :database => (uri.host.nil? && uri.path == '/') ? nil : "#{uri.host}#{uri.path}" }
13
19
  end
14
-
20
+
15
21
  private_class_method :uri_to_options
16
-
17
- def connect
18
- @opts[:database] = ':memory:' if @opts[:database].blank?
19
- db = ::SQLite3::Database.new(@opts[:database])
20
- db.busy_timeout(@opts.fetch(:timeout, 5000))
22
+
23
+ # Connect to the database. Since SQLite is a file based database,
24
+ # the only options available are :database (to specify the database
25
+ # name), and :timeout, to specify how long to wait for the database to
26
+ # be available if it is locked, given in milliseconds (default is 5000).
27
+ def connect(server)
28
+ opts = server_opts(server)
29
+ opts[:database] = ':memory:' if opts[:database].blank?
30
+ db = ::SQLite3::Database.new(opts[:database])
31
+ db.busy_timeout(opts.fetch(:timeout, 5000))
21
32
  db.type_translation = true
22
33
  # fix for timestamp translation
23
34
  db.translator.add_translator("timestamp") do |t, v|
@@ -26,58 +37,45 @@ module Sequel
26
37
  db
27
38
  end
28
39
 
40
+ # Return instance of Sequel::SQLite::Dataset with the given options.
29
41
  def dataset(opts = nil)
30
42
  SQLite::Dataset.new(self, opts)
31
43
  end
32
44
 
45
+ # Disconnect all connections from the database.
33
46
  def disconnect
34
47
  @pool.disconnect {|c| c.close}
35
48
  end
36
-
37
- def execute(sql)
38
- begin
39
- log_info(sql)
40
- @pool.hold {|conn| conn.execute_batch(sql); conn.changes}
41
- rescue SQLite3::Exception => e
42
- raise Error::InvalidStatement, "#{sql}\r\n#{e.message}"
43
- end
49
+
50
+ # Run the given SQL with the given arguments and return the number of changed rows.
51
+ def execute_dui(sql, opts={})
52
+ _execute(sql, opts){|conn| conn.execute_batch(sql, opts[:arguments]); conn.changes}
44
53
  end
45
54
 
46
- def execute_insert(sql)
47
- begin
48
- log_info(sql)
49
- @pool.hold {|conn| conn.execute(sql); conn.last_insert_row_id}
50
- rescue SQLite3::Exception => e
51
- raise Error::InvalidStatement, "#{sql}\r\n#{e.message}"
52
- end
55
+ # Run the given SQL with the given arguments and return the last inserted row id.
56
+ def execute_insert(sql, opts={})
57
+ _execute(sql, opts){|conn| conn.execute(sql, opts[:arguments]); conn.last_insert_row_id}
53
58
  end
54
59
 
55
- def single_value(sql)
56
- begin
57
- log_info(sql)
58
- @pool.hold {|conn| conn.get_first_value(sql)}
59
- rescue SQLite3::Exception => e
60
- raise Error::InvalidStatement, "#{sql}\r\n#{e.message}"
61
- end
60
+ # Run the given SQL with the given arguments and yield each row.
61
+ def execute(sql, opts={}, &block)
62
+ _execute(sql, opts){|conn| conn.query(sql, opts[:arguments], &block)}
62
63
  end
63
64
 
64
- def execute_select(sql, &block)
65
- begin
66
- log_info(sql)
67
- @pool.hold {|conn| conn.query(sql, &block)}
68
- rescue SQLite3::Exception => e
69
- raise Error::InvalidStatement, "#{sql}\r\n#{e.message}"
70
- end
65
+ # Run the given SQL with the given arguments and return the first value of the first row.
66
+ def single_value(sql, opts={})
67
+ _execute(sql, opts){|conn| conn.get_first_value(sql, opts[:arguments])}
71
68
  end
72
69
 
73
- def transaction(&block)
74
- @pool.hold do |conn|
75
- if conn.transaction_active?
76
- return yield(conn)
77
- end
70
+ # Use the native driver transaction method if there isn't already a transaction
71
+ # in progress on the connection, always yielding a connection inside a transaction
72
+ # transaction.
73
+ def transaction(server=nil, &block)
74
+ synchronize(server) do |conn|
75
+ return yield(conn) if conn.transaction_active?
78
76
  begin
79
77
  result = nil
80
- conn.transaction {result = yield(conn)}
78
+ conn.transaction{result = yield(conn)}
81
79
  result
82
80
  rescue ::Exception => e
83
81
  raise (SQLite3::Exception === e ? Error.new(e.message) : e) unless Error::Rollback === e
@@ -87,6 +85,20 @@ module Sequel
87
85
 
88
86
  private
89
87
 
88
+ # Log the SQL and the arguments, and yield an available connection. Rescue
89
+ # any SQLite3::Exceptions and turn the into Error::InvalidStatements.
90
+ def _execute(sql, opts)
91
+ begin
92
+ log_info(sql, opts[:arguments])
93
+ synchronize(opts[:server]){|conn| yield conn}
94
+ rescue SQLite3::Exception => e
95
+ raise Error::InvalidStatement, "#{sql}\r\n#{e.message}"
96
+ end
97
+ end
98
+
99
+ # SQLite does not need the pool to convert exceptions.
100
+ # Also, force the max connections to 1 if a memory database is being
101
+ # used, as otherwise each connection gets a separate database.
90
102
  def connection_pool_default_options
91
103
  o = super.merge(:pool_convert_exceptions=>false)
92
104
  # Default to only a single connection if a memory database is used,
@@ -96,19 +108,85 @@ module Sequel
96
108
  end
97
109
  end
98
110
 
111
+ # Dataset class for SQLite datasets that use the ruby-sqlite3 driver.
99
112
  class Dataset < Sequel::Dataset
100
113
  include ::Sequel::SQLite::DatasetMethods
101
114
 
102
115
  EXPLAIN = 'EXPLAIN %s'.freeze
116
+ PREPARED_ARG_PLACEHOLDER = ':'.freeze
117
+
118
+ # SQLite already supports named bind arguments, so use directly.
119
+ module ArgumentMapper
120
+ include Sequel::Dataset::ArgumentMapper
121
+
122
+ protected
123
+
124
+ # Return a hash with the same values as the given hash,
125
+ # but with the keys converted to strings.
126
+ def map_to_prepared_args(hash)
127
+ args = {}
128
+ hash.each{|k,v| args[k.to_s] = v}
129
+ args
130
+ end
131
+
132
+ private
133
+
134
+ # Work around for the default prepared statement and argument
135
+ # mapper code, which wants a hash that maps. SQLite doesn't
136
+ # need to do this, but still requires a value for the argument
137
+ # in order for the substitution to work correctly.
138
+ def prepared_args_hash
139
+ true
140
+ end
141
+
142
+ # SQLite uses a : before the name of the argument for named
143
+ # arguments.
144
+ def prepared_arg(k)
145
+ "#{prepared_arg_placeholder}#{k}".lit
146
+ end
147
+ end
148
+
149
+ # SQLite prepared statement uses a new prepared statement each time
150
+ # it is called, but it does use the bind arguments.
151
+ module PreparedStatementMethods
152
+ include ArgumentMapper
153
+
154
+ private
155
+
156
+ # Run execute_select on the database with the given SQL and the stored
157
+ # bind arguments.
158
+ def execute(sql, opts={}, &block)
159
+ super(sql, {:arguments=>bind_arguments}.merge(opts), &block)
160
+ end
161
+
162
+ # Same as execute, explicit due to intricacies of alias and super.
163
+ def execute_dui(sql, opts={}, &block)
164
+ super(sql, {:arguments=>bind_arguments}.merge(opts), &block)
165
+ end
166
+
167
+ # Same as execute, explicit due to intricacies of alias and super.
168
+ def execute_insert(sql, opts={}, &block)
169
+ super(sql, {:arguments=>bind_arguments}.merge(opts), &block)
170
+ end
171
+ end
103
172
 
173
+ # Prepare an unnamed statement of the given type and call it with the
174
+ # given values.
175
+ def call(type, hash, values=nil, &block)
176
+ prepare(type, nil, values).call(hash, &block)
177
+ end
178
+
179
+ # Return an array of strings specifying a query explanation for the
180
+ # current dataset.
104
181
  def explain
105
182
  res = []
106
183
  @db.result_set(EXPLAIN % select_sql(opts), nil) {|r| res << r}
107
184
  res
108
185
  end
109
-
186
+
187
+ # Yield a hash for each row in the dataset.
110
188
  def fetch_rows(sql)
111
- @db.execute_select(sql) do |result|
189
+ execute(sql) do |result|
112
190
  @columns = result.columns.map {|c| c.to_sym}
113
191
  column_count = @columns.size
114
192
  result.each do |values|
@@ -118,7 +196,9 @@ module Sequel
118
196
  end
119
197
  end
120
198
  end
121
-
199
+
200
+ # Use the ISO format for dates and timestamps, and quote strings
201
+ # using the ::SQLite3::Database.quote method.
122
202
  def literal(v)
123
203
  case v
124
204
  when LiteralString
@@ -133,6 +213,23 @@ module Sequel
133
213
  super
134
214
  end
135
215
  end
216
+
217
+ # Prepare the given type of query with the given name and store
218
+ # it in the database. Note that a new native prepared statement is
219
+ # created on each call to this prepared statement.
220
+ def prepare(type, name, values=nil)
221
+ ps = to_prepared_statement(type, values)
222
+ ps.extend(PreparedStatementMethods)
223
+ db.prepared_statements[name] = ps if name
224
+ ps
225
+ end
226
+
227
+ private
228
+
229
+ # SQLite uses a : before the name of the argument as a placeholder.
230
+ def prepared_arg_placeholder
231
+ PREPARED_ARG_PLACEHOLDER
232
+ end
136
233
  end
137
234
  end
138
235
  end
@@ -2,21 +2,8 @@
2
2
  # multiple connections and giving threads exclusive access to each
3
3
  # connection.
4
4
  class Sequel::ConnectionPool
5
- # A hash of connections currently being used, key is the Thread,
6
- # value is the connection.
7
- attr_reader :allocated
8
-
9
- # An array of connections opened but not currently used
10
- attr_reader :available_connections
11
-
12
5
  # The proc used to create a new database connection.
13
6
  attr_accessor :connection_proc
14
-
15
- # The total number of connections opened, should
16
- # be equal to available_connections.length +
17
- # allocated.length
18
- attr_reader :created_count
19
- alias_method :size, :created_count
20
7
 
21
8
  # The maximum number of connections.
22
9
  attr_reader :max_size
@@ -25,7 +12,6 @@ class Sequel::ConnectionPool
25
12
  # this if you want to manipulate the variables safely.
26
13
  attr_reader :mutex
27
14
 
28
-
29
15
  # Constructs a new pool with a maximum size. If a block is supplied, it
30
16
  # is used to create new connections as they are needed.
31
17
  #
@@ -47,21 +33,50 @@ class Sequel::ConnectionPool
47
33
  # a connection again (default 0.001)
48
34
  # * :pool_timeout - The amount of seconds to wait to acquire a connection
49
35
  # before raising a PoolTimeoutError (default 5)
36
+ # * :servers - A hash of servers to use. Keys should be symbols. If not
37
+ # present, will use a single :default server. The server name symbol will
38
+ # be passed to the connection_proc.
50
39
  def initialize(opts = {}, &block)
51
40
  @max_size = opts[:max_connections] || 4
52
41
  @mutex = Mutex.new
53
42
  @connection_proc = block
54
-
55
- @available_connections = []
56
- @allocated = {}
57
- @created_count = 0
43
+ @servers = [:default]
44
+ @servers += opts[:servers].keys - @servers if opts[:servers]
45
+ @available_connections = Hash.new{|h,k| h[:default]}
46
+ @allocated = Hash.new{|h,k| h[:default]}
47
+ @created_count = Hash.new{|h,k| h[:default]}
48
+ @servers.each do |s|
49
+ @available_connections[s] = []
50
+ @allocated[s] = {}
51
+ @created_count[s] = 0
52
+ end
58
53
  @timeout = opts[:pool_timeout] || 5
59
54
  @sleep_time = opts[:pool_sleep_time] || 0.001
60
55
  @convert_exceptions = opts.include?(:pool_convert_exceptions) ? opts[:pool_convert_exceptions] : true
61
56
  end
62
57
 
63
- # Chooses the first available connection, or if none are available,
64
- # creates a new connection. Passes the connection to the supplied block:
58
+ # A hash of connections currently being used for the given server, key is the
59
+ # Thread, value is the connection.
60
+ def allocated(server=:default)
61
+ @allocated[server]
62
+ end
63
+
64
+ # An array of connections opened but not currently used, for the given
65
+ # server.
66
+ def available_connections(server=:default)
67
+ @available_connections[server]
68
+ end
69
+
70
+ # The total number of connections opened for the given server, should
71
+ # be equal to available_connections.length + allocated.length
72
+ def created_count(server=:default)
73
+ @created_count[server]
74
+ end
75
+ alias size created_count
76
+
77
+ # Chooses the first available connection to the given server, or if none are
78
+ # available, creates a new connection. Passes the connection to the supplied
79
+ # block:
65
80
  #
66
81
  # pool.hold {|conn| conn.execute('DROP TABLE posts')}
67
82
  #
@@ -73,78 +88,83 @@ class Sequel::ConnectionPool
73
88
  # is available or the timeout expires. If the timeout expires before a
74
89
  # connection can be acquired, a Sequel::Error::PoolTimeoutError is
75
90
  # raised.
76
- def hold
91
+ def hold(server=:default)
77
92
  begin
78
93
  t = Thread.current
79
94
  time = Time.new
80
95
  timeout = time + @timeout
81
96
  sleep_time = @sleep_time
82
- if conn = owned_connection(t)
97
+ if conn = owned_connection(t, server)
83
98
  return yield(conn)
84
99
  end
85
- until conn = acquire(t)
100
+ until conn = acquire(t, server)
86
101
  raise(::Sequel::Error::PoolTimeoutError) if Time.new > timeout
87
102
  sleep sleep_time
88
103
  end
89
104
  begin
90
105
  yield conn
91
106
  ensure
92
- release(t, conn)
107
+ release(t, conn, server)
93
108
  end
94
109
  rescue Exception => e
95
110
  raise(@convert_exceptions && !e.is_a?(StandardError) ? RuntimeError.new(e.message) : e)
96
111
  end
97
112
  end
98
113
 
99
- # Removes all connection currently available, optionally yielding each
100
- # connection to the given block. This method has the effect of
101
- # disconnecting from the database. Once a connection is requested using
102
- # #hold, the connection pool creates new connections to the database.
103
- def disconnect(&block)
114
+ # Removes all connection currently available on all servers, optionally
115
+ # yielding each connection to the given block. This method has the effect of
116
+ # disconnecting from the database, assuming that no connections are currently
117
+ # being used. Once a connection is requested using #hold, the connection pool
118
+ # creates new connections to the database.
119
+ def disconnect
104
120
  @mutex.synchronize do
105
- @available_connections.each {|c| block[c]} if block
106
- @available_connections = []
107
- @created_count = @allocated.size
121
+ @available_connections.each do |server, conns|
122
+ conns.each{|c| yield(c)} if block_given?
123
+ conns.clear
124
+ @created_count[server] = allocated(server).length
125
+ end
108
126
  end
109
127
  end
110
128
 
111
129
  private
112
130
 
113
- # Returns the connection owned by the supplied thread, if any.
114
- def owned_connection(thread)
115
- @mutex.synchronize{@allocated[thread]}
131
+ # Returns the connection owned by the supplied thread for the given server,
132
+ # if any.
133
+ def owned_connection(thread, server)
134
+ @mutex.synchronize{@allocated[server][thread]}
116
135
  end
117
136
 
118
- # Assigns a connection to the supplied thread, if one is available.
119
- def acquire(thread)
137
+ # Assigns a connection to the supplied thread for the given server, if one
138
+ # is available.
139
+ def acquire(thread, server)
120
140
  @mutex.synchronize do
121
- if conn = available
122
- @allocated[thread] = conn
141
+ if conn = available(server)
142
+ allocated(server)[thread] = conn
123
143
  end
124
144
  end
125
145
  end
126
146
 
127
- # Returns an available connection. If no connection is available,
128
- # tries to create a new connection.
129
- def available
130
- @available_connections.pop || make_new
147
+ # Returns an available connection to the given server. If no connection is
148
+ # available, tries to create a new connection.
149
+ def available(server)
150
+ available_connections(server).pop || make_new(server)
131
151
  end
132
152
 
133
- # Creates a new connection if the size of the pool is less than the
134
- # maximum size.
135
- def make_new
136
- if @created_count < @max_size
137
- @created_count += 1
138
- @connection_proc ? @connection_proc.call : \
153
+ # Creates a new connection to the given server if the size of the pool for
154
+ # the server is less than the maximum size of the pool.
155
+ def make_new(server)
156
+ if @created_count[server] < @max_size
157
+ @created_count[server] += 1
158
+ @connection_proc ? @connection_proc.call(server) : \
139
159
  (raise Error, "No connection proc specified")
140
160
  end
141
161
  end
142
162
 
143
- # Releases the connection assigned to the supplied thread.
144
- def release(thread, conn)
163
+ # Releases the connection assigned to the supplied thread and server.
164
+ def release(thread, conn, server)
145
165
  @mutex.synchronize do
146
- @allocated.delete(thread)
147
- @available_connections << conn
166
+ allocated(server).delete(thread)
167
+ available_connections(server) << conn
148
168
  end
149
169
  end
150
170
  end
@@ -156,9 +176,6 @@ end
156
176
  # Note that using a single threaded pool with some adapters can cause
157
177
  # errors in certain cases, see Sequel.single_threaded=.
158
178
  class Sequel::SingleThreadedPool
159
- # The single database connection for the pool
160
- attr_reader :conn
161
-
162
179
  # The proc used to create a new database connection
163
180
  attr_writer :connection_proc
164
181
 
@@ -170,15 +187,20 @@ class Sequel::SingleThreadedPool
170
187
  # to RuntimeError exceptions (default true)
171
188
  def initialize(opts={}, &block)
172
189
  @connection_proc = block
190
+ @conns = {}
173
191
  @convert_exceptions = opts.include?(:pool_convert_exceptions) ? opts[:pool_convert_exceptions] : true
174
192
  end
175
193
 
176
- # Yields the connection to the supplied block. This method simulates the
177
- # ConnectionPool#hold API.
178
- def hold
194
+ # The connection for the given server.
195
+ def conn(server=:default)
196
+ @conns[server]
197
+ end
198
+
199
+ # Yields the connection to the supplied block for the given server.
200
+ # This method simulates the ConnectionPool#hold API.
201
+ def hold(server=:default)
179
202
  begin
180
- @conn ||= @connection_proc.call
181
- yield @conn
203
+ yield(@conns[server] ||= @connection_proc.call(server))
182
204
  rescue Exception => e
183
205
  # if the error is not a StandardError it is converted into RuntimeError.
184
206
  raise(@convert_exceptions && !e.is_a?(StandardError) ? RuntimeError.new(e.message) : e)
@@ -188,7 +210,7 @@ class Sequel::SingleThreadedPool
188
210
  # Disconnects from the database. Once a connection is requested using
189
211
  # #hold, the connection is reestablished.
190
212
  def disconnect(&block)
191
- block[@conn] if block && @conn
192
- @conn = nil
213
+ @conns.values.each{|conn| yield(conn) if block_given?}
214
+ @conns = {}
193
215
  end
194
216
  end