sequel 2.7.1 → 2.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. data/CHANGELOG +56 -0
  2. data/README +1 -0
  3. data/Rakefile +1 -1
  4. data/lib/sequel_core.rb +9 -16
  5. data/lib/sequel_core/adapters/ado.rb +6 -15
  6. data/lib/sequel_core/adapters/db2.rb +8 -10
  7. data/lib/sequel_core/adapters/dbi.rb +6 -4
  8. data/lib/sequel_core/adapters/informix.rb +21 -22
  9. data/lib/sequel_core/adapters/jdbc.rb +69 -10
  10. data/lib/sequel_core/adapters/jdbc/postgresql.rb +1 -0
  11. data/lib/sequel_core/adapters/mysql.rb +81 -13
  12. data/lib/sequel_core/adapters/odbc.rb +32 -4
  13. data/lib/sequel_core/adapters/openbase.rb +6 -5
  14. data/lib/sequel_core/adapters/oracle.rb +23 -7
  15. data/lib/sequel_core/adapters/postgres.rb +42 -32
  16. data/lib/sequel_core/adapters/shared/mssql.rb +37 -62
  17. data/lib/sequel_core/adapters/shared/mysql.rb +22 -7
  18. data/lib/sequel_core/adapters/shared/oracle.rb +27 -48
  19. data/lib/sequel_core/adapters/shared/postgres.rb +64 -43
  20. data/lib/sequel_core/adapters/shared/progress.rb +31 -0
  21. data/lib/sequel_core/adapters/shared/sqlite.rb +15 -4
  22. data/lib/sequel_core/adapters/sqlite.rb +6 -14
  23. data/lib/sequel_core/connection_pool.rb +47 -13
  24. data/lib/sequel_core/database.rb +60 -35
  25. data/lib/sequel_core/database/schema.rb +4 -4
  26. data/lib/sequel_core/dataset.rb +12 -3
  27. data/lib/sequel_core/dataset/convenience.rb +4 -13
  28. data/lib/sequel_core/dataset/prepared_statements.rb +30 -28
  29. data/lib/sequel_core/dataset/sql.rb +144 -85
  30. data/lib/sequel_core/dataset/stored_procedures.rb +75 -0
  31. data/lib/sequel_core/dataset/unsupported.rb +31 -0
  32. data/lib/sequel_core/exceptions.rb +6 -0
  33. data/lib/sequel_core/schema/generator.rb +4 -3
  34. data/lib/sequel_core/schema/sql.rb +41 -23
  35. data/lib/sequel_core/sql.rb +29 -1
  36. data/lib/sequel_model/associations.rb +1 -1
  37. data/lib/sequel_model/record.rb +31 -28
  38. data/spec/adapters/mysql_spec.rb +37 -4
  39. data/spec/adapters/oracle_spec.rb +26 -4
  40. data/spec/adapters/sqlite_spec.rb +7 -0
  41. data/spec/integration/prepared_statement_test.rb +24 -0
  42. data/spec/integration/schema_test.rb +1 -1
  43. data/spec/sequel_core/connection_pool_spec.rb +49 -2
  44. data/spec/sequel_core/core_sql_spec.rb +9 -2
  45. data/spec/sequel_core/database_spec.rb +64 -14
  46. data/spec/sequel_core/dataset_spec.rb +105 -7
  47. data/spec/sequel_core/schema_spec.rb +40 -12
  48. data/spec/sequel_core/spec_helper.rb +1 -0
  49. data/spec/sequel_model/spec_helper.rb +1 -0
  50. metadata +6 -3
@@ -13,69 +13,48 @@ module Sequel
13
13
  end
14
14
 
15
15
  module DatasetMethods
16
+ include Dataset::UnsupportedIntersectExceptAll
17
+
18
+ SELECT_CLAUSE_ORDER = %w'distinct columns from join where group having union intersect except order limit'.freeze
19
+
16
20
  def empty?
17
21
  db[:dual].where(exists).get(1) == nil
18
22
  end
19
23
 
20
- # Formats a SELECT statement using the given options and the dataset
21
- # options.
22
- def select_sql(opts = nil)
23
- opts = opts ? @opts.merge(opts) : @opts
24
-
25
- if sql = opts[:sql]
26
- return sql
27
- end
28
-
29
- columns = opts[:select]
30
- select_columns = columns ? column_list(columns) : '*'
31
- sql = opts[:distinct] ? \
32
- "SELECT DISTINCT #{select_columns}" : \
33
- "SELECT #{select_columns}"
34
-
35
- if opts[:from]
36
- sql << " FROM #{source_list(opts[:from])}"
37
- end
38
-
39
- if join = opts[:join]
40
- join.each{|j| sql << literal(j)}
41
- end
42
-
43
- if where = opts[:where]
44
- sql << " WHERE #{literal(where)}"
45
- end
24
+ private
46
25
 
47
- if group = opts[:group]
48
- sql << " GROUP BY #{expression_list(group)}"
49
- end
26
+ # Oracle doesn't support the use of AS when aliasing a dataset. It doesn't require
27
+ # the use of AS anywhere, so this disables it in all cases.
28
+ def as_sql(expression, aliaz)
29
+ "#{expression} #{quote_identifier(aliaz)}"
30
+ end
50
31
 
51
- if having = opts[:having]
52
- sql << " HAVING #{literal(having)}"
53
- end
32
+ def select_clause_order
33
+ SELECT_CLAUSE_ORDER
34
+ end
54
35
 
55
- if union = opts[:union]
56
- sql << (opts[:union_all] ? \
57
- " UNION ALL #{union.sql}" : " UNION #{union.sql}")
58
- elsif intersect = opts[:intersect]
59
- sql << (opts[:intersect_all] ? \
60
- " INTERSECT ALL #{intersect.sql}" : " INTERSECT #{intersect.sql}")
61
- elsif except = opts[:except]
62
- sql << (opts[:except_all] ? \
63
- " EXCEPT ALL #{except.sql}" : " EXCEPT #{except.sql}")
36
+ # Oracle doesn't support DISTINCT ON
37
+ def select_distinct_sql(sql, opts)
38
+ if opts[:distinct]
39
+ raise(Error, "DISTINCT ON not supported by Oracle") unless opts[:distinct].empty?
40
+ sql << " DISTINCT"
64
41
  end
42
+ end
65
43
 
66
- if order = opts[:order]
67
- sql << " ORDER BY #{expression_list(order)}"
68
- end
44
+ # Oracle uses MINUS instead of EXCEPT, and doesn't support EXCEPT ALL
45
+ def select_except_sql(sql, opts)
46
+ sql << " MINUS #{opts[:except].sql}" if opts[:except]
47
+ end
69
48
 
49
+ # Oracle requires a subselect to do limit and offset
50
+ def select_limit_sql(sql, opts)
70
51
  if limit = opts[:limit]
71
52
  if (offset = opts[:offset]) && (offset > 0)
72
- sql = "SELECT * FROM (SELECT raw_sql_.*, ROWNUM raw_rnum_ FROM(#{sql}) raw_sql_ WHERE ROWNUM <= #{limit + offset}) WHERE raw_rnum_ > #{offset}"
53
+ sql.replace("SELECT * FROM (SELECT raw_sql_.*, ROWNUM raw_rnum_ FROM(#{sql}) raw_sql_ WHERE ROWNUM <= #{limit + offset}) WHERE raw_rnum_ > #{offset}")
73
54
  else
74
- sql = "SELECT * FROM (#{sql}) WHERE ROWNUM <= #{limit}"
55
+ sql.replace("SELECT * FROM (#{sql}) WHERE ROWNUM <= #{limit}")
75
56
  end
76
57
  end
77
-
78
- sql
79
58
  end
80
59
  end
81
60
  end
@@ -4,6 +4,15 @@ module Sequel
4
4
  # uses NativeExceptions, the native adapter uses PGError.
5
5
  CONVERTED_EXCEPTIONS = []
6
6
 
7
+ @force_standard_strings = true
8
+
9
+ # By default, Sequel forces the use of standard strings, so that
10
+ # '\\' is interpreted as '\\' and not '\\'. While PostgreSQL defaults
11
+ # to interpreting plain strings as extended strings, this will change
12
+ # in a future version of PostgreSQL. Sequel assumes that SQL standard
13
+ # strings will be used.
14
+ metaattr_accessor :force_standard_strings
15
+
7
16
  # Methods shared by adapter/connection instances.
8
17
  module AdapterMethods
9
18
  attr_writer :db
@@ -57,6 +66,20 @@ module Sequel
57
66
  # to implement multi-level transactions with savepoints.
58
67
  attr_accessor :transaction_depth
59
68
 
69
+ # Apply connection settings for this connection. Currently, turns
70
+ # standard_conforming_strings ON if Postgres.force_standard_strings
71
+ # is true.
72
+ def apply_connection_settings
73
+ if Postgres.force_standard_strings
74
+ sql = "SET standard_conforming_strings = ON"
75
+ @db.log_info(sql)
76
+ # This setting will only work on PostgreSQL 8.2 or greater
77
+ # and we don't know the server version at this point, so
78
+ # try it unconditionally and rescue any errors.
79
+ execute(sql) rescue nil
80
+ end
81
+ end
82
+
60
83
  # Get the last inserted value for the given sequence.
61
84
  def last_insert_id(sequence)
62
85
  sql = SELECT_CURRVAL % sequence
@@ -67,6 +90,15 @@ module Sequel
67
90
  end
68
91
  end
69
92
 
93
+ # Get the primary key for the given table.
94
+ def primary_key(schema, table)
95
+ sql = SELECT_PK % [schema, table]
96
+ @db.log_info(sql)
97
+ execute(sql) do |r|
98
+ return single_value(r)
99
+ end
100
+ end
101
+
70
102
  # Get the primary key and sequence for the given table.
71
103
  def sequence(schema, table)
72
104
  sql = SELECT_SERIAL_SEQUENCE % [schema, table]
@@ -82,15 +114,6 @@ module Sequel
82
114
  return single_value(r)
83
115
  end
84
116
  end
85
-
86
- # Get the primary key for the given table.
87
- def primary_key(schema, table)
88
- sql = SELECT_PK % [schema, table]
89
- @db.log_info(sql)
90
- execute(sql) do |r|
91
- return single_value(r)
92
- end
93
- end
94
117
  end
95
118
 
96
119
  # Methods shared by Database instances that connect to PostgreSQL.
@@ -110,11 +133,6 @@ module Sequel
110
133
  @default_schema ||= :public
111
134
  end
112
135
 
113
- # Set a new default schema to use.
114
- def default_schema=(schema)
115
- @default_schema = schema
116
- end
117
-
118
136
  # Remove the cached entries for primary keys and sequences when dropping a table.
119
137
  def drop_table(*names)
120
138
  names.each do |name|
@@ -161,12 +179,11 @@ module Sequel
161
179
  synchronize(server){|conn| primary_key_for_table(conn, table)}
162
180
  end
163
181
 
164
- # Support :schema__table format for table
165
- def schema(table_name=nil, opts={})
166
- schema, table_name = schema_and_table(table_name) if table_name
167
- opts[:schema] = schema if schema
168
- super(table_name, opts)
169
- end
182
+ # SQL DDL statement for renaming a table. PostgreSQL doesn't allow you to change a table's schema in
183
+ # a rename table operation, so speciying a new schema in new_name will not have an effect.
184
+ def rename_table_sql(name, new_name)
185
+ "ALTER TABLE #{quote_schema_table(name)} RENAME TO #{quote_identifier(schema_and_table(new_name).last)}"
186
+ end
170
187
 
171
188
  # PostgreSQL uses SERIAL psuedo-type instead of AUTOINCREMENT for
172
189
  # managing incrementing primary keys.
@@ -255,6 +272,11 @@ module Sequel
255
272
 
256
273
  private
257
274
 
275
+ # PostgreSQL folds unquoted identifiers to lowercase, so it shouldn't need to upcase identifiers by default.
276
+ def upcase_identifiers_default
277
+ false
278
+ end
279
+
258
280
  # The result of the insert for the given table and values. If values
259
281
  # is an array, assume the first column is the primary key and return
260
282
  # that. If values is a hash, lookup the primary key for the table. If
@@ -322,7 +344,7 @@ module Sequel
322
344
  def schema_parse_tables(opts)
323
345
  schemas = {}
324
346
  schema_parser_dataset(nil, opts).each do |row|
325
- (schemas[row.delete(:table).to_sym] ||= []) << row
347
+ (schemas[quote_schema_table(SQL::QualifiedIdentifier.new(row.delete(:schema), row.delete(:table)))] ||= []) << row
326
348
  end
327
349
  schemas.each do |table, rows|
328
350
  schemas[table] = schema_parse_rows(rows)
@@ -348,9 +370,9 @@ module Sequel
348
370
  if table_name
349
371
  ds.filter!(:pg_class__relname=>table_name.to_s)
350
372
  else
351
- ds.select_more!(:pg_class__relname___table)
373
+ ds.select_more!(:pg_class__relname___table, :pg_namespace__nspname___schema)
374
+ ds.join!(:pg_namespace, :oid=>:pg_class__relnamespace, :nspname=>(opts[:schema] || default_schema).to_s)
352
375
  end
353
- ds.join!(:pg_namespace, :oid=>:pg_class__relnamespace, :nspname=>(opts[:schema] || default_schema).to_s) if opts[:schema] || !opts.include?(:schema)
354
376
  ds
355
377
  end
356
378
  end
@@ -372,6 +394,7 @@ module Sequel
372
394
  QUERY_PLAN = 'QUERY PLAN'.to_sym
373
395
  ROW_EXCLUSIVE = 'ROW EXCLUSIVE'.freeze
374
396
  ROW_SHARE = 'ROW SHARE'.freeze
397
+ SELECT_CLAUSE_ORDER = %w'distinct columns from join where group having intersect union except order limit lock'.freeze
375
398
  SHARE = 'SHARE'.freeze
376
399
  SHARE_ROW_EXCLUSIVE = 'SHARE ROW EXCLUSIVE'.freeze
377
400
  SHARE_UPDATE_EXCLUSIVE = 'SHARE UPDATE EXCLUSIVE'.freeze
@@ -452,8 +475,10 @@ module Sequel
452
475
  case v
453
476
  when LiteralString
454
477
  v
478
+ when SQL::Blob
479
+ db.synchronize{|c| "E'#{c.escape_bytea(v)}'"}
455
480
  when String
456
- db.synchronize{|c| "'#{SQL::Blob === v ? c.escape_bytea(v) : c.escape_string(v)}'"}
481
+ db.synchronize{|c| "'#{c.escape_string(v)}'"}
457
482
  when Time
458
483
  "#{v.strftime(PG_TIMESTAMP_FORMAT)}.#{sprintf("%06d",v.usec)}'"
459
484
  when DateTime
@@ -489,25 +514,6 @@ module Sequel
489
514
  ["INSERT INTO #{source_list(@opts[:from])} (#{identifier_list(columns)}) VALUES #{values}"]
490
515
  end
491
516
 
492
- # PostgreSQL assumes unquoted identifiers are lower case by default,
493
- # so do not upcase the identifier when quoting it.
494
- def quoted_identifier(c)
495
- "\"#{c}\""
496
- end
497
-
498
- # Support lock mode, allowing FOR SHARE and FOR UPDATE queries.
499
- def select_sql(opts = nil)
500
- row_lock_mode = opts ? opts[:lock] : @opts[:lock]
501
- sql = super
502
- case row_lock_mode
503
- when :update
504
- sql << FOR_UPDATE
505
- when :share
506
- sql << FOR_SHARE
507
- end
508
- sql
509
- end
510
-
511
517
  private
512
518
 
513
519
  # Call execute_insert on the database object with the given values.
@@ -521,6 +527,21 @@ module Sequel
521
527
  insert_returning_sql(pk ? Sequel::SQL::Identifier.new(pk) : 'NULL'.lit, *values)
522
528
  end
523
529
 
530
+ # The order of clauses in the SELECT SQL statement
531
+ def select_clause_order
532
+ SELECT_CLAUSE_ORDER
533
+ end
534
+
535
+ # Support lock mode, allowing FOR SHARE and FOR UPDATE queries.
536
+ def select_lock_sql(sql, opts)
537
+ case opts[:lock]
538
+ when :update
539
+ sql << FOR_UPDATE
540
+ when :share
541
+ sql << FOR_SHARE
542
+ end
543
+ end
544
+
524
545
  # The version of the database server
525
546
  def server_version
526
547
  db.server_version(@opts[:server])
@@ -0,0 +1,31 @@
1
+ module Sequel
2
+ module Progress
3
+ module DatabaseMethods
4
+
5
+ def dataset(opts = nil)
6
+ ds = super
7
+ ds.extend(DatasetMethods)
8
+ ds
9
+ end
10
+ end
11
+
12
+ module DatasetMethods
13
+ include Dataset::UnsupportedIntersectExcept
14
+
15
+ SELECT_CLAUSE_ORDER = %w'limit distinct columns from join where group order having union'.freeze
16
+
17
+ private
18
+
19
+ def select_clause_order
20
+ SELECT_CLAUSE_ORDER
21
+ end
22
+
23
+ # Progress uses TOP for limit, but it is only supported in Progress 10.
24
+ # The Progress adapter targets Progress 9, so it silently ignores the option.
25
+ def select_limit_sql(sql, opts)
26
+ raise(Error, "OFFSET not supported") if opts[:offset]
27
+ #sql << " TOP #{opts[:limit]}" if opts[:limit]
28
+ end
29
+ end
30
+ end
31
+ end
@@ -6,6 +6,12 @@ module Sequel
6
6
  TABLES_FILTER = "type = 'table' AND NOT name = 'sqlite_sequence'"
7
7
  TEMP_STORE = {'0' => :default, '1' => :file, '2' => :memory}.freeze
8
8
 
9
+ # Run all alter_table commands in a transaction. This is technically only
10
+ # needed for drop column.
11
+ def alter_table(name, generator=nil, &block)
12
+ transaction{super}
13
+ end
14
+
9
15
  # SQLite supports limited table modification. You can add a column
10
16
  # or an index. Dropping columns is supported by copying the table into
11
17
  # a temporary table, dropping the table, and creating a new table without
@@ -18,14 +24,12 @@ module Sequel
18
24
  index_definition_sql(table, op)
19
25
  when :drop_column
20
26
  columns_str = (schema_parse_table(table, {}).map{|c| c[0]} - Array(op[:name])).join(",")
21
- ["BEGIN TRANSACTION",
22
- "CREATE TEMPORARY TABLE #{table}_backup(#{columns_str})",
27
+ ["CREATE TEMPORARY TABLE #{table}_backup(#{columns_str})",
23
28
  "INSERT INTO #{table}_backup SELECT #{columns_str} FROM #{table}",
24
29
  "DROP TABLE #{table}",
25
30
  "CREATE TABLE #{table}(#{columns_str})",
26
31
  "INSERT INTO #{table} SELECT #{columns_str} FROM #{table}_backup",
27
- "DROP TABLE #{table}_backup",
28
- "COMMIT"]
32
+ "DROP TABLE #{table}_backup"]
29
33
  else
30
34
  raise Error, "Unsupported ALTER TABLE operation"
31
35
  end
@@ -82,6 +86,11 @@ module Sequel
82
86
 
83
87
  private
84
88
 
89
+ # SQLite folds unquoted identifiers to lowercase, so it shouldn't need to upcase identifiers by default.
90
+ def upcase_identifiers_default
91
+ false
92
+ end
93
+
85
94
  # SQLite supports schema parsing using the table_info PRAGMA, so
86
95
  # parse the output of that into the format Sequel expects.
87
96
  def schema_parse_table(table_name, opts)
@@ -100,6 +109,8 @@ module Sequel
100
109
 
101
110
  # Instance methods for datasets that connect to an SQLite database
102
111
  module DatasetMethods
112
+ include Dataset::UnsupportedIntersectExceptAll
113
+
103
114
  # SQLite does not support pattern matching via regular expressions.
104
115
  # SQLite is case insensitive (depending on pragma), so use LIKE for
105
116
  # ILIKE.
@@ -46,11 +46,6 @@ module Sequel
46
46
  SQLite::Dataset.new(self, opts)
47
47
  end
48
48
 
49
- # Disconnect all connections from the database.
50
- def disconnect
51
- @pool.disconnect {|c| c.close}
52
- end
53
-
54
49
  # Run the given SQL with the given arguments and return the number of changed rows.
55
50
  def execute_dui(sql, opts={})
56
51
  _execute(sql, opts){|conn| conn.execute_batch(sql, opts[:arguments]); conn.changes}
@@ -110,6 +105,11 @@ module Sequel
110
105
  o[:max_connections] = 1 if @opts[:database] == ':memory:' || @opts[:database].blank?
111
106
  o
112
107
  end
108
+
109
+ # Disconnect given connections from the database.
110
+ def disconnect_connection(c)
111
+ c.close
112
+ end
113
113
  end
114
114
 
115
115
  # Dataset class for SQLite datasets that use the ruby-sqlite3 driver.
@@ -135,14 +135,6 @@ module Sequel
135
135
 
136
136
  private
137
137
 
138
- # Work around for the default prepared statement and argument
139
- # mapper code, which wants a hash that maps. SQLite doesn't
140
- # need to do this, but still requires a value for the argument
141
- # in order for the substitution to work correctly.
142
- def prepared_args_hash
143
- true
144
- end
145
-
146
138
  # SQLite uses a : before the name of the argument for named
147
139
  # arguments.
148
140
  def prepared_arg(k)
@@ -221,7 +213,7 @@ module Sequel
221
213
  # Prepare the given type of query with the given name and store
222
214
  # it in the database. Note that a new native prepared statement is
223
215
  # created on each call to this prepared statement.
224
- def prepare(type, name, values=nil)
216
+ def prepare(type, name=nil, values=nil)
225
217
  ps = to_prepared_statement(type, values)
226
218
  ps.extend(PreparedStatementMethods)
227
219
  db.prepared_statements[name] = ps if name
@@ -5,6 +5,9 @@ class Sequel::ConnectionPool
5
5
  # The proc used to create a new database connection.
6
6
  attr_accessor :connection_proc
7
7
 
8
+ # The proc used to disconnect a database connection.
9
+ attr_accessor :disconnection_proc
10
+
8
11
  # The maximum number of connections.
9
12
  attr_reader :max_size
10
13
 
@@ -40,6 +43,7 @@ class Sequel::ConnectionPool
40
43
  @max_size = opts[:max_connections] || 4
41
44
  @mutex = Mutex.new
42
45
  @connection_proc = block
46
+ @disconnection_proc = opts[:disconnection_proc]
43
47
  @servers = [:default]
44
48
  @servers += opts[:servers].keys - @servers if opts[:servers]
45
49
  @available_connections = Hash.new{|h,k| h[:default]}
@@ -103,8 +107,11 @@ class Sequel::ConnectionPool
103
107
  end
104
108
  begin
105
109
  yield conn
110
+ rescue Sequel::DatabaseDisconnectError => dde
111
+ remove(t, conn, server)
112
+ raise
106
113
  ensure
107
- release(t, conn, server)
114
+ release(t, conn, server) unless dde
108
115
  end
109
116
  rescue Exception => e
110
117
  raise(@convert_exceptions && !e.is_a?(StandardError) ? RuntimeError.new(e.message) : e)
@@ -116,24 +123,19 @@ class Sequel::ConnectionPool
116
123
  # disconnecting from the database, assuming that no connections are currently
117
124
  # being used. Once a connection is requested using #hold, the connection pool
118
125
  # creates new connections to the database.
119
- def disconnect
126
+ def disconnect(&block)
127
+ block ||= @disconnection_proc
120
128
  @mutex.synchronize do
121
129
  @available_connections.each do |server, conns|
122
- conns.each{|c| yield(c)} if block_given?
130
+ conns.each{|c| block.call(c)} if block
123
131
  conns.clear
124
- @created_count[server] = allocated(server).length
132
+ set_created_count(server, allocated(server).length)
125
133
  end
126
134
  end
127
135
  end
128
136
 
129
137
  private
130
138
 
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]}
135
- end
136
-
137
139
  # Assigns a connection to the supplied thread for the given server, if one
138
140
  # is available.
139
141
  def acquire(thread, server)
@@ -154,12 +156,18 @@ class Sequel::ConnectionPool
154
156
  # the server is less than the maximum size of the pool.
155
157
  def make_new(server)
156
158
  if @created_count[server] < @max_size
157
- @created_count[server] += 1
159
+ set_created_count(server, @created_count[server] + 1)
158
160
  @connection_proc ? @connection_proc.call(server) : \
159
161
  (raise Error, "No connection proc specified")
160
162
  end
161
163
  end
162
164
 
165
+ # Returns the connection owned by the supplied thread for the given server,
166
+ # if any.
167
+ def owned_connection(thread, server)
168
+ @mutex.synchronize{@allocated[server][thread]}
169
+ end
170
+
163
171
  # Releases the connection assigned to the supplied thread and server.
164
172
  def release(thread, conn, server)
165
173
  @mutex.synchronize do
@@ -167,6 +175,21 @@ class Sequel::ConnectionPool
167
175
  available_connections(server) << conn
168
176
  end
169
177
  end
178
+
179
+ # Removes the currently allocated connection from the connection pool.
180
+ def remove(thread, conn, server)
181
+ @mutex.synchronize do
182
+ allocated(server).delete(thread)
183
+ set_created_count(server, @created_count[server] - 1)
184
+ @disconnection_proc.call(conn) if @disconnection_proc
185
+ end
186
+ end
187
+
188
+ # Set the created count for the given server type
189
+ def set_created_count(server, value)
190
+ server = :default unless @created_count.include?(server)
191
+ @created_count[server] = value
192
+ end
170
193
  end
171
194
 
172
195
  # A SingleThreadedPool acts as a replacement for a ConnectionPool for use
@@ -179,6 +202,9 @@ class Sequel::SingleThreadedPool
179
202
  # The proc used to create a new database connection
180
203
  attr_writer :connection_proc
181
204
 
205
+ # The proc used to disconnect a database connection.
206
+ attr_accessor :disconnection_proc
207
+
182
208
  # Initializes the instance with the supplied block as the connection_proc.
183
209
  #
184
210
  # The single threaded pool takes the following options:
@@ -187,6 +213,7 @@ class Sequel::SingleThreadedPool
187
213
  # to RuntimeError exceptions (default true)
188
214
  def initialize(opts={}, &block)
189
215
  @connection_proc = block
216
+ @disconnection_proc = opts[:disconnection_proc]
190
217
  @conns = {}
191
218
  @convert_exceptions = opts.include?(:pool_convert_exceptions) ? opts[:pool_convert_exceptions] : true
192
219
  end
@@ -200,7 +227,13 @@ class Sequel::SingleThreadedPool
200
227
  # This method simulates the ConnectionPool#hold API.
201
228
  def hold(server=:default)
202
229
  begin
203
- yield(@conns[server] ||= @connection_proc.call(server))
230
+ begin
231
+ yield(c = (@conns[server] ||= @connection_proc.call(server)))
232
+ rescue Sequel::DatabaseDisconnectError => dde
233
+ @conns.delete(server)
234
+ @disconnection_proc.call(c) if @disconnection_proc
235
+ raise
236
+ end
204
237
  rescue Exception => e
205
238
  # if the error is not a StandardError it is converted into RuntimeError.
206
239
  raise(@convert_exceptions && !e.is_a?(StandardError) ? RuntimeError.new(e.message) : e)
@@ -210,7 +243,8 @@ class Sequel::SingleThreadedPool
210
243
  # Disconnects from the database. Once a connection is requested using
211
244
  # #hold, the connection is reestablished.
212
245
  def disconnect(&block)
213
- @conns.values.each{|conn| yield(conn) if block_given?}
246
+ block ||= @disconnection_proc
247
+ @conns.values.each{|conn| block.call(conn) if block}
214
248
  @conns = {}
215
249
  end
216
250
  end