sequel 3.40.0 → 3.41.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 (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
+