sequel 3.40.0 → 3.41.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (89) hide show
  1. data/CHANGELOG +40 -0
  2. data/README.rdoc +2 -2
  3. data/doc/advanced_associations.rdoc +12 -0
  4. data/doc/bin_sequel.rdoc +144 -0
  5. data/doc/migration.rdoc +1 -1
  6. data/doc/object_model.rdoc +29 -0
  7. data/doc/release_notes/3.41.0.txt +155 -0
  8. data/lib/sequel/adapters/ado.rb +4 -4
  9. data/lib/sequel/adapters/amalgalite.rb +0 -5
  10. data/lib/sequel/adapters/cubrid.rb +2 -2
  11. data/lib/sequel/adapters/db2.rb +9 -5
  12. data/lib/sequel/adapters/dbi.rb +4 -6
  13. data/lib/sequel/adapters/do.rb +4 -5
  14. data/lib/sequel/adapters/firebird.rb +8 -4
  15. data/lib/sequel/adapters/ibmdb.rb +2 -3
  16. data/lib/sequel/adapters/informix.rb +0 -6
  17. data/lib/sequel/adapters/jdbc.rb +11 -7
  18. data/lib/sequel/adapters/jdbc/db2.rb +22 -0
  19. data/lib/sequel/adapters/jdbc/derby.rb +5 -5
  20. data/lib/sequel/adapters/jdbc/h2.rb +0 -5
  21. data/lib/sequel/adapters/jdbc/jtds.rb +1 -1
  22. data/lib/sequel/adapters/jdbc/sqlserver.rb +6 -0
  23. data/lib/sequel/adapters/mock.rb +3 -3
  24. data/lib/sequel/adapters/mysql.rb +7 -7
  25. data/lib/sequel/adapters/mysql2.rb +0 -5
  26. data/lib/sequel/adapters/odbc.rb +4 -4
  27. data/lib/sequel/adapters/openbase.rb +4 -6
  28. data/lib/sequel/adapters/oracle.rb +14 -6
  29. data/lib/sequel/adapters/postgres.rb +12 -8
  30. data/lib/sequel/adapters/shared/db2.rb +5 -0
  31. data/lib/sequel/adapters/shared/firebird.rb +10 -0
  32. data/lib/sequel/adapters/shared/mssql.rb +43 -1
  33. data/lib/sequel/adapters/shared/mysql.rb +1 -0
  34. data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +1 -1
  35. data/lib/sequel/adapters/shared/postgres.rb +12 -0
  36. data/lib/sequel/adapters/shared/sqlite.rb +32 -0
  37. data/lib/sequel/adapters/sqlite.rb +9 -8
  38. data/lib/sequel/adapters/swift.rb +3 -8
  39. data/lib/sequel/adapters/tinytds.rb +5 -5
  40. data/lib/sequel/connection_pool.rb +13 -19
  41. data/lib/sequel/connection_pool/sharded_single.rb +12 -12
  42. data/lib/sequel/connection_pool/sharded_threaded.rb +37 -17
  43. data/lib/sequel/connection_pool/single.rb +6 -3
  44. data/lib/sequel/connection_pool/threaded.rb +33 -13
  45. data/lib/sequel/database/connecting.rb +28 -1
  46. data/lib/sequel/database/logging.rb +1 -1
  47. data/lib/sequel/database/misc.rb +2 -5
  48. data/lib/sequel/database/query.rb +2 -2
  49. data/lib/sequel/database/schema_generator.rb +1 -1
  50. data/lib/sequel/database/schema_methods.rb +3 -0
  51. data/lib/sequel/dataset/query.rb +8 -4
  52. data/lib/sequel/dataset/sql.rb +7 -0
  53. data/lib/sequel/extensions/arbitrary_servers.rb +1 -1
  54. data/lib/sequel/extensions/connection_validator.rb +109 -0
  55. data/lib/sequel/extensions/pg_array.rb +2 -0
  56. data/lib/sequel/extensions/pg_hstore.rb +2 -0
  57. data/lib/sequel/extensions/pg_json.rb +4 -0
  58. data/lib/sequel/extensions/pg_range.rb +1 -0
  59. data/lib/sequel/extensions/pg_row.rb +4 -0
  60. data/lib/sequel/plugins/prepared_statements.rb +2 -1
  61. data/lib/sequel/plugins/single_table_inheritance.rb +53 -10
  62. data/lib/sequel/plugins/touch.rb +18 -6
  63. data/lib/sequel/plugins/validation_class_methods.rb +1 -0
  64. data/lib/sequel/plugins/validation_helpers.rb +3 -1
  65. data/lib/sequel/sql.rb +61 -19
  66. data/lib/sequel/version.rb +1 -1
  67. data/spec/adapters/firebird_spec.rb +52 -38
  68. data/spec/adapters/mssql_spec.rb +67 -0
  69. data/spec/adapters/mysql_spec.rb +192 -116
  70. data/spec/adapters/postgres_spec.rb +133 -70
  71. data/spec/adapters/spec_helper.rb +7 -0
  72. data/spec/adapters/sqlite_spec.rb +34 -1
  73. data/spec/core/connection_pool_spec.rb +79 -75
  74. data/spec/core/database_spec.rb +9 -4
  75. data/spec/core/dataset_spec.rb +15 -0
  76. data/spec/core/expression_filters_spec.rb +40 -2
  77. data/spec/extensions/connection_validator_spec.rb +118 -0
  78. data/spec/extensions/pg_array_spec.rb +4 -0
  79. data/spec/extensions/single_table_inheritance_spec.rb +42 -0
  80. data/spec/extensions/touch_spec.rb +40 -0
  81. data/spec/extensions/validation_class_methods_spec.rb +19 -1
  82. data/spec/extensions/validation_helpers_spec.rb +17 -0
  83. data/spec/integration/database_test.rb +14 -0
  84. data/spec/integration/dataset_test.rb +3 -3
  85. data/spec/integration/plugin_test.rb +41 -12
  86. data/spec/integration/schema_test.rb +14 -0
  87. data/spec/integration/spec_helper.rb +7 -0
  88. data/spec/integration/type_test.rb +3 -0
  89. metadata +9 -3
@@ -8,13 +8,12 @@ Sequel.require 'connection_pool/threaded'
8
8
  class Sequel::ShardedThreadedConnectionPool < Sequel::ThreadedConnectionPool
9
9
  # The following additional options are respected:
10
10
  # * :servers - A hash of servers to use. Keys should be symbols. If not
11
- # present, will use a single :default server. The server name symbol will
12
- # be passed to the connection_proc.
11
+ # present, will use a single :default server.
13
12
  # * :servers_hash - The base hash to use for the servers. By default,
14
13
  # Sequel uses Hash.new(:default). You can use a hash with a default proc
15
14
  # that raises an error if you want to catch all cases where a nonexistent
16
15
  # server is used.
17
- def initialize(opts = {}, &block)
16
+ def initialize(db, opts = {})
18
17
  super
19
18
  @available_connections = {}
20
19
  @connections_to_remove = []
@@ -87,11 +86,10 @@ class Sequel::ShardedThreadedConnectionPool < Sequel::ThreadedConnectionPool
87
86
  # creates new connections to the database. Options:
88
87
  # * :server - Should be a symbol specifing the server to disconnect from,
89
88
  # or an array of symbols to specify multiple servers.
90
- def disconnect(opts={}, &block)
91
- block ||= @disconnection_proc
89
+ def disconnect(opts={})
92
90
  sync do
93
91
  (opts[:server] ? Array(opts[:server]) : @servers.keys).each do |s|
94
- disconnect_server(s, &block)
92
+ disconnect_server(s)
95
93
  end
96
94
  end
97
95
  end
@@ -145,7 +143,7 @@ class Sequel::ShardedThreadedConnectionPool < Sequel::ThreadedConnectionPool
145
143
  raise(Sequel::Error, "cannot remove default server") if servers.include?(:default)
146
144
  servers.each do |server|
147
145
  if @servers.include?(server)
148
- disconnect_server(server, &@disconnection_proc)
146
+ disconnect_server(server)
149
147
  @available_connections.delete(server)
150
148
  @allocated.delete(server)
151
149
  @servers.delete(server)
@@ -159,6 +157,10 @@ class Sequel::ShardedThreadedConnectionPool < Sequel::ThreadedConnectionPool
159
157
  sync{@servers.keys}
160
158
  end
161
159
 
160
+ def pool_type
161
+ :sharded_threaded
162
+ end
163
+
162
164
  private
163
165
 
164
166
  # Assigns a connection to the supplied thread for the given server, if one
@@ -176,16 +178,30 @@ class Sequel::ShardedThreadedConnectionPool < Sequel::ThreadedConnectionPool
176
178
  # available, tries to create a new connection. The calling code should already
177
179
  # have the mutex before calling this.
178
180
  def available(server)
179
- available_connections(server).pop || make_new(server)
181
+ next_available(server) || make_new(server)
182
+ end
183
+
184
+ # Return a connection to the pool of available connections for the server,
185
+ # returns the connection. The calling code should already have the mutex
186
+ # before calling this.
187
+ def checkin_connection(server, conn)
188
+ case @connection_handling
189
+ when :queue
190
+ available_connections(server).unshift(conn)
191
+ else
192
+ available_connections(server) << conn
193
+ end
194
+
195
+ conn
180
196
  end
181
197
 
182
198
  # Disconnect from the given server. Disconnects available connections
183
199
  # immediately, and schedules currently allocated connections for disconnection
184
200
  # as soon as they are returned to the pool. The calling code should already
185
201
  # have the mutex before calling this.
186
- def disconnect_server(server, &block)
202
+ def disconnect_server(server)
187
203
  if conns = available_connections(server)
188
- conns.each{|conn| block.call(conn)} if block
204
+ conns.each{|conn| db.disconnect_connection(conn)}
189
205
  conns.clear
190
206
  end
191
207
  @connections_to_remove.concat(allocated(server).values)
@@ -201,6 +217,13 @@ class Sequel::ShardedThreadedConnectionPool < Sequel::ThreadedConnectionPool
201
217
  end
202
218
  default_make_new(server) if (n || size(server)) < @max_size
203
219
  end
220
+
221
+ # Return the next available connection in the pool for the given server, or nil
222
+ # if there is not currently an available connection for the server.
223
+ # The calling code should already have the mutex before calling this.
224
+ def next_available(server)
225
+ available_connections(server).pop
226
+ end
204
227
 
205
228
  # Returns the connection owned by the supplied thread for the given server,
206
229
  # if any. The calling code should NOT already have the mutex before calling this.
@@ -223,13 +246,10 @@ class Sequel::ShardedThreadedConnectionPool < Sequel::ThreadedConnectionPool
223
246
  else
224
247
  conn = allocated(server).delete(thread)
225
248
 
226
- case @connection_handling
227
- when :queue
228
- available_connections(server).unshift(conn)
229
- when :disconnect
230
- @disconnection_proc.call(conn) if @disconnection_proc
249
+ if @connection_handling == :disconnect
250
+ db.disconnect_connection(conn)
231
251
  else
232
- available_connections(server) << conn
252
+ checkin_connection(server, conn)
233
253
  end
234
254
  end
235
255
  end
@@ -239,7 +259,7 @@ class Sequel::ShardedThreadedConnectionPool < Sequel::ThreadedConnectionPool
239
259
  def remove(thread, conn, server)
240
260
  @connections_to_remove.delete(conn)
241
261
  allocated(server).delete(thread) if @servers.include?(server)
242
- @disconnection_proc.call(conn) if @disconnection_proc
262
+ db.disconnect_connection(conn)
243
263
  end
244
264
 
245
265
  CONNECTION_POOL_MAP[[false, true]] = self
@@ -14,10 +14,9 @@ class Sequel::SingleConnectionPool < Sequel::ConnectionPool
14
14
  end
15
15
 
16
16
  # Disconnect the connection from the database.
17
- def disconnect(opts=nil, &block)
17
+ def disconnect(opts=nil)
18
18
  return unless @conn
19
- block ||= @disconnection_proc
20
- block.call(@conn) if block
19
+ db.disconnect_connection(@conn)
21
20
  @conn = nil
22
21
  end
23
22
 
@@ -31,5 +30,9 @@ class Sequel::SingleConnectionPool < Sequel::ConnectionPool
31
30
  end
32
31
  end
33
32
 
33
+ def pool_type
34
+ :single
35
+ end
36
+
34
37
  CONNECTION_POOL_MAP[[true, false]] = self
35
38
  end
@@ -22,7 +22,7 @@ class Sequel::ThreadedConnectionPool < Sequel::ConnectionPool
22
22
  # a connection again (default 0.001)
23
23
  # * :pool_timeout - The amount of seconds to wait to acquire a connection
24
24
  # before raising a PoolTimeoutError (default 5)
25
- def initialize(opts = {}, &block)
25
+ def initialize(db, opts = {})
26
26
  super
27
27
  @max_size = Integer(opts[:max_connections] || 4)
28
28
  raise(Sequel::Error, ':max_connections must be positive') if @max_size < 1
@@ -64,10 +64,9 @@ class Sequel::ThreadedConnectionPool < Sequel::ConnectionPool
64
64
  #
65
65
  # Once a connection is requested using #hold, the connection pool
66
66
  # creates new connections to the database.
67
- def disconnect(opts={}, &block)
68
- block ||= @disconnection_proc
67
+ def disconnect(opts={})
69
68
  sync do
70
- @available_connections.each{|conn| block.call(conn)} if block
69
+ @available_connections.each{|conn| db.disconnect_connection(conn)}
71
70
  @available_connections.clear
72
71
  end
73
72
  end
@@ -106,13 +105,17 @@ class Sequel::ThreadedConnectionPool < Sequel::ConnectionPool
106
105
  rescue Sequel::DatabaseDisconnectError
107
106
  oconn = conn
108
107
  conn = nil
109
- @disconnection_proc.call(oconn) if @disconnection_proc && oconn
108
+ db.disconnect_connection(oconn) if oconn
110
109
  @allocated.delete(t)
111
110
  raise
112
111
  ensure
113
112
  sync{release(t)} if conn
114
113
  end
115
114
  end
115
+
116
+ def pool_type
117
+ :threaded
118
+ end
116
119
 
117
120
  private
118
121
 
@@ -131,7 +134,20 @@ class Sequel::ThreadedConnectionPool < Sequel::ConnectionPool
131
134
  # available, tries to create a new connection. The calling code should already
132
135
  # have the mutex before calling this.
133
136
  def available
134
- @available_connections.pop || make_new(DEFAULT_SERVER)
137
+ next_available || make_new(DEFAULT_SERVER)
138
+ end
139
+
140
+ # Return a connection to the pool of available connections, returns the connection.
141
+ # The calling code should already have the mutex before calling this.
142
+ def checkin_connection(conn)
143
+ case @connection_handling
144
+ when :queue
145
+ @available_connections.unshift(conn)
146
+ else
147
+ @available_connections << conn
148
+ end
149
+
150
+ conn
135
151
  end
136
152
 
137
153
  # Alias the default make_new method, so subclasses can call it directly.
@@ -147,7 +163,14 @@ class Sequel::ThreadedConnectionPool < Sequel::ConnectionPool
147
163
  end
148
164
  super if (n || size) < @max_size
149
165
  end
150
-
166
+
167
+ # Return the next available connection in the pool, or nil if there
168
+ # is not currently an available connection. The calling code should already
169
+ # have the mutex before calling this.
170
+ def next_available
171
+ @available_connections.pop
172
+ end
173
+
151
174
  # Returns the connection owned by the supplied thread,
152
175
  # if any. The calling code should NOT already have the mutex before calling this.
153
176
  def owned_connection(thread)
@@ -159,13 +182,10 @@ class Sequel::ThreadedConnectionPool < Sequel::ConnectionPool
159
182
  def release(thread)
160
183
  conn = @allocated.delete(thread)
161
184
 
162
- case @connection_handling
163
- when :queue
164
- @available_connections.unshift(conn)
165
- when :disconnect
166
- @disconnection_proc.call(conn) if @disconnection_proc
185
+ if @connection_handling == :disconnect
186
+ db.disconnect_connection(conn)
167
187
  else
168
- @available_connections << conn
188
+ checkin_connection(conn)
169
189
  end
170
190
  end
171
191
 
@@ -140,7 +140,7 @@ module Sequel
140
140
  @pool.add_servers(servers.keys)
141
141
  end
142
142
  end
143
-
143
+
144
144
  # Connects to the database. This method should be overridden by descendants.
145
145
  def connect(server)
146
146
  raise NotImplemented, "#connect should be overridden by adapters"
@@ -173,6 +173,13 @@ module Sequel
173
173
  pool.disconnect(opts)
174
174
  end
175
175
 
176
+ # Should only be called by the connection pool code to disconnect a connection.
177
+ # By default, calls the close method on the connection object, since most
178
+ # adapters use that, but should be overwritten on other adapters.
179
+ def disconnect_connection(conn)
180
+ conn.close
181
+ end
182
+
176
183
  # Yield a new Database instance for every server in the connection pool.
177
184
  # Intended for use in sharded environments where there is a need to make schema
178
185
  # modifications (DDL queries) on each shard.
@@ -243,6 +250,21 @@ module Sequel
243
250
  true
244
251
  end
245
252
 
253
+ # Check whether the given connection is currently valid, by
254
+ # running a query against it. If the query fails, the
255
+ # connection should probably be removed from the connection
256
+ # pool.
257
+ def valid_connection?(conn)
258
+ sql = valid_connection_sql
259
+ begin
260
+ log_connection_execute(conn, sql)
261
+ rescue Sequel::DatabaseError, *database_error_classes
262
+ false
263
+ else
264
+ true
265
+ end
266
+ end
267
+
246
268
  private
247
269
 
248
270
  # The default options for the connection pool.
@@ -271,5 +293,10 @@ module Sequel
271
293
  opts.delete(:servers)
272
294
  opts
273
295
  end
296
+
297
+ # The SQL query to issue to check if a connection is valid.
298
+ def valid_connection_sql
299
+ @valid_connection_sql ||= select(nil).sql
300
+ end
274
301
  end
275
302
  end
@@ -19,7 +19,7 @@ module Sequel
19
19
 
20
20
  # Log a message at error level, with information about the exception.
21
21
  def log_exception(exception, message)
22
- log_each(:error, "#{exception.class}: #{exception.message.strip}: #{message}")
22
+ log_each(:error, "#{exception.class}: #{exception.message.strip if exception.message}: #{message}")
23
23
  end
24
24
 
25
25
  # Log a message at level info to all loggers.
@@ -49,7 +49,6 @@ module Sequel
49
49
  #
50
50
  # Accepts the following options:
51
51
  # :default_schema :: The default schema to use, see #default_schema.
52
- # :disconnection_proc :: A proc used to disconnect the connection
53
52
  # :identifier_input_method :: A string method symbol to call on identifiers going into the database
54
53
  # :identifier_output_method :: A string method symbol to call on identifiers coming from the database
55
54
  # :logger :: A specific logger to use
@@ -59,14 +58,12 @@ module Sequel
59
58
  # :single_threaded :: Whether to use a single-threaded connection pool
60
59
  # :sql_log_level :: Method to use to log SQL to a logger, :info by default.
61
60
  #
62
- # All options given are also passed to the connection pool. If a block
63
- # is given, it is used as the connection_proc for the ConnectionPool.
61
+ # All options given are also passed to the connection pool.
64
62
  def initialize(opts = {}, &block)
65
63
  @opts ||= opts
66
64
  @opts = connection_pool_default_options.merge(@opts)
67
65
  @loggers = Array(@opts[:logger]) + Array(@opts[:loggers])
68
66
  self.log_warn_duration = @opts[:log_warn_duration]
69
- @opts[:disconnection_proc] ||= proc{|conn| disconnect_connection(conn)}
70
67
  block ||= proc{|server| connect(server)}
71
68
  @opts[:servers] = {} if @opts[:servers].is_a?(String)
72
69
  @opts[:adapter_class] = self.class
@@ -84,7 +81,7 @@ module Sequel
84
81
  @cache_schema = typecast_value_boolean(@opts.fetch(:cache_schema, true))
85
82
  @dataset_modules = []
86
83
  self.sql_log_level = @opts[:sql_log_level] ? @opts[:sql_log_level].to_sym : :info
87
- @pool = ConnectionPool.get_pool(@opts, &block)
84
+ @pool = ConnectionPool.get_pool(self, @opts)
88
85
 
89
86
  Sequel.synchronize{::Sequel::DATABASES.push(self)}
90
87
  end
@@ -605,7 +605,7 @@ module Sequel
605
605
  # such as :integer or :string.
606
606
  def schema_column_type(db_type)
607
607
  case db_type
608
- when /\A(character( varying)?|n?(var)?char|n?text|string)/io
608
+ when /\A(character( varying)?|n?(var)?char|n?text|string|clob)/io
609
609
  :string
610
610
  when /\A(int(eger)?|(big|small|tiny)int)/io
611
611
  :integer
@@ -621,7 +621,7 @@ module Sequel
621
621
  :float
622
622
  when /\A(?:(?:(?:num(?:ber|eric)?|decimal)(?:\(\d+,\s*(\d+|false|true)\))?)|(?:small)?money)\z/io
623
623
  $1 && ['0', 'false'].include?($1) ? :integer : :decimal
624
- when /bytea|[bc]lob|image|(var)?binary/io
624
+ when /bytea|blob|image|(var)?binary/io
625
625
  :blob
626
626
  when /\Aenum/io
627
627
  :enum
@@ -177,7 +177,7 @@ module Sequel
177
177
  # :concurrently :: Create the index concurrently, so it doesn't block
178
178
  # operations on the table while the index is being
179
179
  # built.
180
- # :op_class :: Use a specific operator class in the index.
180
+ # :opclass :: Use a specific operator class in the index.
181
181
  #
182
182
  # Microsoft SQL Server specific options:
183
183
  #
@@ -154,6 +154,9 @@ module Sequel
154
154
  # :collate :: The collation to use for the table.
155
155
  # :engine :: The table engine to use for the table.
156
156
  #
157
+ # PostgreSQL specific options:
158
+ # :unlogged :: Create the table as an unlogged table.
159
+ #
157
160
  # See <tt>Schema::Generator</tt> and the {"Schema Modification" guide}[link:files/doc/schema_modification_rdoc.html].
158
161
  def create_table(name, options={}, &block)
159
162
  remove_cached_schema(name)
@@ -592,7 +592,7 @@ module Sequel
592
592
  # DB[:items].limit(10...20) # SELECT * FROM items LIMIT 10 OFFSET 10
593
593
  # DB[:items].limit(10..20) # SELECT * FROM items LIMIT 11 OFFSET 10
594
594
  # DB[:items].limit(nil, 20) # SELECT * FROM items OFFSET 20
595
- def limit(l, o = nil)
595
+ def limit(l, o = (no_offset = true; nil))
596
596
  return from_self.limit(l, o) if @opts[:sql]
597
597
 
598
598
  if Range === l
@@ -610,6 +610,8 @@ module Sequel
610
610
  raise(Error, 'Offsets must be greater than or equal to 0') unless o >= 0
611
611
  end
612
612
  opts[:offset] = o
613
+ elsif !no_offset
614
+ opts[:offset] = nil
613
615
  end
614
616
  clone(opts)
615
617
  end
@@ -753,15 +755,17 @@ module Sequel
753
755
  # given, the existing order is inverted.
754
756
  #
755
757
  # DB[:items].reverse(:id) # SELECT * FROM items ORDER BY id DESC
758
+ # DB[:items].reverse{foo(bar)} # SELECT * FROM items ORDER BY foo(bar) DESC
756
759
  # DB[:items].order(:id).reverse # SELECT * FROM items ORDER BY id DESC
757
760
  # DB[:items].order(:id).reverse(Sequel.desc(:name)) # SELECT * FROM items ORDER BY name ASC
758
- def reverse(*order)
761
+ def reverse(*order, &block)
762
+ virtual_row_columns(order, block)
759
763
  order(*invert_order(order.empty? ? @opts[:order] : order))
760
764
  end
761
765
 
762
766
  # Alias of +reverse+
763
- def reverse_order(*order)
764
- reverse(*order)
767
+ def reverse_order(*order, &block)
768
+ reverse(*order, &block)
765
769
  end
766
770
 
767
771
  # Returns a copy of the dataset with the columns selected changed
@@ -290,6 +290,7 @@ module Sequel
290
290
  column_all_sql
291
291
  complex_expression_sql
292
292
  constant_sql
293
+ delayed_evaluation_sql
293
294
  function_sql
294
295
  join_clause_sql
295
296
  join_on_clause_sql
@@ -495,6 +496,12 @@ module Sequel
495
496
  sql << constant.to_s
496
497
  end
497
498
 
499
+ # SQL fragment for delayed evaluations, evaluating the
500
+ # object and literalizing the returned value.
501
+ def delayed_evaluation_sql_append(sql, callable)
502
+ literal_append(sql, callable.call)
503
+ end
504
+
498
505
  # SQL fragment specifying an emulated SQL function call.
499
506
  # By default, assumes just the function name may need to
500
507
  # be emulated, adapters should set an EMULATED_FUNCTION_MAP
@@ -97,7 +97,7 @@ module Sequel
97
97
  a = @allocated[thread]
98
98
  a.delete(server)
99
99
  @allocated.delete(thread) if a.empty?
100
- @disconnection_proc.call(conn) if @disconnection_proc
100
+ db.disconnect_connection(conn)
101
101
  else
102
102
  super
103
103
  end
@@ -0,0 +1,109 @@
1
+ # The connection_validator extension modifies a database's
2
+ # connection pool to validate that connections checked out
3
+ # from the pool are still valid, before yielding them for
4
+ # use. If it detects an invalid connection, it removes it
5
+ # from the pool and tries the next available connection,
6
+ # creating a new connection if no available connection is
7
+ # valid. Example of use:
8
+ #
9
+ # DB.extension(:connection_validator)
10
+ #
11
+ # As checking connections for validity involves issuing a
12
+ # query, which is potentially an expensive operation,
13
+ # the validation checks are only run if the connection has
14
+ # been idle for longer than a certain threshold. By default,
15
+ # that threshold is 3600 seconds (1 hour), but it can be
16
+ # modified by the user, set to -1 to always validate
17
+ # connections on checkout:
18
+ #
19
+ # DB.pool.connection_validation_timeout = -1
20
+ #
21
+ # Note that if you set the timeout to validate connections
22
+ # on every checkout, you should probably manually control
23
+ # connection checkouts on a coarse basis, using
24
+ # Database#synchonrize. In a web application, the optimal
25
+ # place for that would be a rack middleware. Validating
26
+ # connections on every checkout without setting up coarse
27
+ # connection checkouts will hurt performance, in some cases
28
+ # significantly. Note that setting up coarse connection
29
+ # checkouts reduces the concurrency level acheivable. For
30
+ # example, in a web application, using Database#synchronize
31
+ # in a rack middleware will limit the number of concurrent
32
+ # web requests to the number to connections in the database
33
+ # connection pool.
34
+ #
35
+ # Note that this extension only affects the default threaded
36
+ # and the sharded threaded connection pool. The single
37
+ # threaded and sharded single threaded connection pools are
38
+ # not affected. As the only reason to use the single threaded
39
+ # pools is for speed, and this extension makes the connection
40
+ # pool slower, there's not much point in modifying this
41
+ # extension to work with the single threaded pools. The
42
+ # threaded pools work fine even in single threaded code, so if
43
+ # you are currently using a single threaded pool and want to
44
+ # use this extension, switch to using a threaded pool.
45
+
46
+ module Sequel
47
+ module ConnectionValidator
48
+ class Retry < Error; end
49
+
50
+ # The number of seconds that need to pass since
51
+ # connection checkin before attempting to validate
52
+ # the connection when checking it out from the pool.
53
+ # Defaults to 3600 seconds (1 hour).
54
+ attr_accessor :connection_validation_timeout
55
+
56
+ # Initialize the data structures used by this extension.
57
+ def self.extended(pool)
58
+ pool.instance_eval do
59
+ @connection_timestamps ||= {}
60
+ @connection_validation_timeout = 3600
61
+ end
62
+
63
+ # Make sure the valid connection SQL query is precached,
64
+ # otherwise it's possible it will happen at runtime. While
65
+ # it should work correctly at runtime, it's better to avoid
66
+ # the possibility of failure altogether.
67
+ pool.db.send(:valid_connection_sql)
68
+ end
69
+
70
+ private
71
+
72
+ # Record the time the connection was checked back into the pool.
73
+ def checkin_connection(*)
74
+ conn = super
75
+ @connection_timestamps[conn] = Time.now
76
+ conn
77
+ end
78
+
79
+ # When acquiring a connection, if it has been
80
+ # idle for longer than the connection validation timeout,
81
+ # test the connection for validity. If it is not valid,
82
+ # disconnect the connection, and retry with a new connection.
83
+ def acquire(*a)
84
+ begin
85
+ if (conn = super) &&
86
+ (t = sync{@connection_timestamps.delete(conn)}) &&
87
+ Time.now - t > @connection_validation_timeout &&
88
+ !db.valid_connection?(conn)
89
+
90
+ if pool_type == :sharded_threaded
91
+ sync{allocated(a.last).delete(Thread.current)}
92
+ else
93
+ sync{@allocated.delete(Thread.current)}
94
+ end
95
+
96
+ db.disconnect_connection(conn)
97
+ raise Retry
98
+ end
99
+ rescue Retry
100
+ retry
101
+ end
102
+
103
+ conn
104
+ end
105
+ end
106
+
107
+ Database.register_extension(:connection_validator){|db| db.pool.extend(ConnectionValidator)}
108
+ end
109
+