sequel 2.3.0 → 2.4.0

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