sequel 5.39.0 → 5.72.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (219) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +408 -0
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +59 -27
  5. data/bin/sequel +11 -3
  6. data/doc/advanced_associations.rdoc +16 -14
  7. data/doc/association_basics.rdoc +119 -24
  8. data/doc/cheat_sheet.rdoc +11 -3
  9. data/doc/mass_assignment.rdoc +1 -1
  10. data/doc/migration.rdoc +13 -6
  11. data/doc/model_hooks.rdoc +1 -1
  12. data/doc/object_model.rdoc +8 -8
  13. data/doc/opening_databases.rdoc +26 -12
  14. data/doc/postgresql.rdoc +16 -8
  15. data/doc/querying.rdoc +5 -3
  16. data/doc/release_notes/5.40.0.txt +40 -0
  17. data/doc/release_notes/5.41.0.txt +25 -0
  18. data/doc/release_notes/5.42.0.txt +136 -0
  19. data/doc/release_notes/5.43.0.txt +98 -0
  20. data/doc/release_notes/5.44.0.txt +32 -0
  21. data/doc/release_notes/5.45.0.txt +34 -0
  22. data/doc/release_notes/5.46.0.txt +87 -0
  23. data/doc/release_notes/5.47.0.txt +59 -0
  24. data/doc/release_notes/5.48.0.txt +14 -0
  25. data/doc/release_notes/5.49.0.txt +59 -0
  26. data/doc/release_notes/5.50.0.txt +78 -0
  27. data/doc/release_notes/5.51.0.txt +47 -0
  28. data/doc/release_notes/5.52.0.txt +87 -0
  29. data/doc/release_notes/5.53.0.txt +23 -0
  30. data/doc/release_notes/5.54.0.txt +27 -0
  31. data/doc/release_notes/5.55.0.txt +21 -0
  32. data/doc/release_notes/5.56.0.txt +51 -0
  33. data/doc/release_notes/5.57.0.txt +23 -0
  34. data/doc/release_notes/5.58.0.txt +31 -0
  35. data/doc/release_notes/5.59.0.txt +73 -0
  36. data/doc/release_notes/5.60.0.txt +22 -0
  37. data/doc/release_notes/5.61.0.txt +43 -0
  38. data/doc/release_notes/5.62.0.txt +132 -0
  39. data/doc/release_notes/5.63.0.txt +33 -0
  40. data/doc/release_notes/5.64.0.txt +50 -0
  41. data/doc/release_notes/5.65.0.txt +21 -0
  42. data/doc/release_notes/5.66.0.txt +24 -0
  43. data/doc/release_notes/5.67.0.txt +32 -0
  44. data/doc/release_notes/5.68.0.txt +61 -0
  45. data/doc/release_notes/5.69.0.txt +26 -0
  46. data/doc/release_notes/5.70.0.txt +35 -0
  47. data/doc/release_notes/5.71.0.txt +21 -0
  48. data/doc/release_notes/5.72.0.txt +33 -0
  49. data/doc/schema_modification.rdoc +1 -1
  50. data/doc/security.rdoc +9 -9
  51. data/doc/sharding.rdoc +3 -1
  52. data/doc/sql.rdoc +28 -16
  53. data/doc/testing.rdoc +22 -11
  54. data/doc/transactions.rdoc +6 -6
  55. data/doc/virtual_rows.rdoc +2 -2
  56. data/lib/sequel/adapters/ado/access.rb +1 -1
  57. data/lib/sequel/adapters/ado.rb +17 -17
  58. data/lib/sequel/adapters/amalgalite.rb +3 -5
  59. data/lib/sequel/adapters/ibmdb.rb +2 -2
  60. data/lib/sequel/adapters/jdbc/derby.rb +8 -0
  61. data/lib/sequel/adapters/jdbc/h2.rb +60 -10
  62. data/lib/sequel/adapters/jdbc/hsqldb.rb +6 -0
  63. data/lib/sequel/adapters/jdbc/postgresql.rb +7 -4
  64. data/lib/sequel/adapters/jdbc.rb +16 -18
  65. data/lib/sequel/adapters/mysql.rb +92 -67
  66. data/lib/sequel/adapters/mysql2.rb +54 -49
  67. data/lib/sequel/adapters/odbc.rb +6 -2
  68. data/lib/sequel/adapters/oracle.rb +4 -3
  69. data/lib/sequel/adapters/postgres.rb +83 -40
  70. data/lib/sequel/adapters/shared/access.rb +11 -1
  71. data/lib/sequel/adapters/shared/db2.rb +30 -0
  72. data/lib/sequel/adapters/shared/mssql.rb +90 -9
  73. data/lib/sequel/adapters/shared/mysql.rb +47 -2
  74. data/lib/sequel/adapters/shared/oracle.rb +82 -1
  75. data/lib/sequel/adapters/shared/postgres.rb +496 -178
  76. data/lib/sequel/adapters/shared/sqlanywhere.rb +11 -1
  77. data/lib/sequel/adapters/shared/sqlite.rb +116 -11
  78. data/lib/sequel/adapters/sqlanywhere.rb +1 -1
  79. data/lib/sequel/adapters/sqlite.rb +60 -18
  80. data/lib/sequel/adapters/tinytds.rb +1 -1
  81. data/lib/sequel/adapters/trilogy.rb +117 -0
  82. data/lib/sequel/adapters/utils/columns_limit_1.rb +22 -0
  83. data/lib/sequel/adapters/utils/mysql_mysql2.rb +1 -1
  84. data/lib/sequel/ast_transformer.rb +6 -0
  85. data/lib/sequel/connection_pool/sharded_single.rb +5 -7
  86. data/lib/sequel/connection_pool/sharded_threaded.rb +16 -11
  87. data/lib/sequel/connection_pool/sharded_timed_queue.rb +374 -0
  88. data/lib/sequel/connection_pool/single.rb +6 -8
  89. data/lib/sequel/connection_pool/threaded.rb +14 -8
  90. data/lib/sequel/connection_pool/timed_queue.rb +270 -0
  91. data/lib/sequel/connection_pool.rb +55 -31
  92. data/lib/sequel/core.rb +28 -18
  93. data/lib/sequel/database/connecting.rb +27 -3
  94. data/lib/sequel/database/dataset.rb +16 -6
  95. data/lib/sequel/database/misc.rb +69 -14
  96. data/lib/sequel/database/query.rb +73 -2
  97. data/lib/sequel/database/schema_generator.rb +46 -53
  98. data/lib/sequel/database/schema_methods.rb +18 -2
  99. data/lib/sequel/dataset/actions.rb +108 -14
  100. data/lib/sequel/dataset/deprecated_singleton_class_methods.rb +42 -0
  101. data/lib/sequel/dataset/features.rb +20 -0
  102. data/lib/sequel/dataset/misc.rb +12 -2
  103. data/lib/sequel/dataset/placeholder_literalizer.rb +20 -9
  104. data/lib/sequel/dataset/prepared_statements.rb +2 -0
  105. data/lib/sequel/dataset/query.rb +171 -44
  106. data/lib/sequel/dataset/sql.rb +182 -47
  107. data/lib/sequel/dataset.rb +4 -0
  108. data/lib/sequel/extensions/_model_pg_row.rb +0 -12
  109. data/lib/sequel/extensions/_pretty_table.rb +1 -1
  110. data/lib/sequel/extensions/any_not_empty.rb +1 -1
  111. data/lib/sequel/extensions/async_thread_pool.rb +439 -0
  112. data/lib/sequel/extensions/auto_literal_strings.rb +1 -1
  113. data/lib/sequel/extensions/blank.rb +8 -0
  114. data/lib/sequel/extensions/connection_expiration.rb +15 -9
  115. data/lib/sequel/extensions/connection_validator.rb +16 -11
  116. data/lib/sequel/extensions/constraint_validations.rb +1 -1
  117. data/lib/sequel/extensions/core_refinements.rb +36 -11
  118. data/lib/sequel/extensions/date_arithmetic.rb +71 -31
  119. data/lib/sequel/extensions/date_parse_input_handler.rb +67 -0
  120. data/lib/sequel/extensions/datetime_parse_to_time.rb +5 -1
  121. data/lib/sequel/extensions/duplicate_columns_handler.rb +1 -1
  122. data/lib/sequel/extensions/eval_inspect.rb +2 -0
  123. data/lib/sequel/extensions/index_caching.rb +5 -1
  124. data/lib/sequel/extensions/inflector.rb +9 -1
  125. data/lib/sequel/extensions/is_distinct_from.rb +141 -0
  126. data/lib/sequel/extensions/looser_typecasting.rb +3 -0
  127. data/lib/sequel/extensions/migration.rb +11 -2
  128. data/lib/sequel/extensions/named_timezones.rb +26 -6
  129. data/lib/sequel/extensions/pagination.rb +1 -1
  130. data/lib/sequel/extensions/pg_array.rb +32 -4
  131. data/lib/sequel/extensions/pg_array_ops.rb +2 -2
  132. data/lib/sequel/extensions/pg_auto_parameterize.rb +509 -0
  133. data/lib/sequel/extensions/pg_auto_parameterize_in_array.rb +110 -0
  134. data/lib/sequel/extensions/pg_enum.rb +2 -3
  135. data/lib/sequel/extensions/pg_extended_date_support.rb +38 -27
  136. data/lib/sequel/extensions/pg_extended_integer_support.rb +116 -0
  137. data/lib/sequel/extensions/pg_hstore.rb +6 -1
  138. data/lib/sequel/extensions/pg_hstore_ops.rb +53 -3
  139. data/lib/sequel/extensions/pg_inet.rb +10 -11
  140. data/lib/sequel/extensions/pg_inet_ops.rb +1 -1
  141. data/lib/sequel/extensions/pg_interval.rb +45 -19
  142. data/lib/sequel/extensions/pg_json.rb +13 -15
  143. data/lib/sequel/extensions/pg_json_ops.rb +73 -2
  144. data/lib/sequel/extensions/pg_loose_count.rb +3 -1
  145. data/lib/sequel/extensions/pg_multirange.rb +367 -0
  146. data/lib/sequel/extensions/pg_range.rb +11 -24
  147. data/lib/sequel/extensions/pg_range_ops.rb +37 -9
  148. data/lib/sequel/extensions/pg_row.rb +21 -19
  149. data/lib/sequel/extensions/pg_row_ops.rb +1 -1
  150. data/lib/sequel/extensions/query.rb +2 -0
  151. data/lib/sequel/extensions/s.rb +2 -1
  152. data/lib/sequel/extensions/schema_caching.rb +1 -1
  153. data/lib/sequel/extensions/schema_dumper.rb +45 -11
  154. data/lib/sequel/extensions/server_block.rb +10 -13
  155. data/lib/sequel/extensions/set_literalizer.rb +58 -0
  156. data/lib/sequel/extensions/sql_comments.rb +110 -3
  157. data/lib/sequel/extensions/sql_log_normalizer.rb +108 -0
  158. data/lib/sequel/extensions/sqlite_json_ops.rb +255 -0
  159. data/lib/sequel/extensions/string_agg.rb +1 -1
  160. data/lib/sequel/extensions/string_date_time.rb +19 -23
  161. data/lib/sequel/extensions/symbol_aref.rb +2 -0
  162. data/lib/sequel/model/associations.rb +345 -101
  163. data/lib/sequel/model/base.rb +51 -27
  164. data/lib/sequel/model/dataset_module.rb +3 -0
  165. data/lib/sequel/model/errors.rb +10 -1
  166. data/lib/sequel/model/inflections.rb +1 -1
  167. data/lib/sequel/model/plugins.rb +5 -0
  168. data/lib/sequel/plugins/association_proxies.rb +2 -0
  169. data/lib/sequel/plugins/async_thread_pool.rb +39 -0
  170. data/lib/sequel/plugins/auto_restrict_eager_graph.rb +62 -0
  171. data/lib/sequel/plugins/auto_validations.rb +87 -15
  172. data/lib/sequel/plugins/auto_validations_constraint_validations_presence_message.rb +68 -0
  173. data/lib/sequel/plugins/class_table_inheritance.rb +2 -2
  174. data/lib/sequel/plugins/column_encryption.rb +728 -0
  175. data/lib/sequel/plugins/composition.rb +10 -4
  176. data/lib/sequel/plugins/concurrent_eager_loading.rb +174 -0
  177. data/lib/sequel/plugins/constraint_validations.rb +10 -6
  178. data/lib/sequel/plugins/dataset_associations.rb +4 -1
  179. data/lib/sequel/plugins/defaults_setter.rb +16 -0
  180. data/lib/sequel/plugins/dirty.rb +1 -1
  181. data/lib/sequel/plugins/enum.rb +124 -0
  182. data/lib/sequel/plugins/finder.rb +4 -2
  183. data/lib/sequel/plugins/insert_conflict.rb +4 -0
  184. data/lib/sequel/plugins/instance_specific_default.rb +1 -1
  185. data/lib/sequel/plugins/json_serializer.rb +39 -24
  186. data/lib/sequel/plugins/lazy_attributes.rb +3 -0
  187. data/lib/sequel/plugins/list.rb +3 -1
  188. data/lib/sequel/plugins/many_through_many.rb +109 -10
  189. data/lib/sequel/plugins/mssql_optimistic_locking.rb +8 -38
  190. data/lib/sequel/plugins/nested_attributes.rb +12 -7
  191. data/lib/sequel/plugins/optimistic_locking.rb +9 -42
  192. data/lib/sequel/plugins/optimistic_locking_base.rb +55 -0
  193. data/lib/sequel/plugins/pg_array_associations.rb +56 -38
  194. data/lib/sequel/plugins/pg_auto_constraint_validations.rb +11 -3
  195. data/lib/sequel/plugins/pg_xmin_optimistic_locking.rb +109 -0
  196. data/lib/sequel/plugins/prepared_statements.rb +12 -2
  197. data/lib/sequel/plugins/prepared_statements_safe.rb +2 -1
  198. data/lib/sequel/plugins/primary_key_lookup_check_values.rb +154 -0
  199. data/lib/sequel/plugins/rcte_tree.rb +27 -19
  200. data/lib/sequel/plugins/require_valid_schema.rb +67 -0
  201. data/lib/sequel/plugins/serialization.rb +9 -3
  202. data/lib/sequel/plugins/serialization_modification_detection.rb +2 -1
  203. data/lib/sequel/plugins/single_table_inheritance.rb +8 -0
  204. data/lib/sequel/plugins/sql_comments.rb +189 -0
  205. data/lib/sequel/plugins/static_cache.rb +39 -1
  206. data/lib/sequel/plugins/static_cache_cache.rb +5 -1
  207. data/lib/sequel/plugins/subclasses.rb +28 -11
  208. data/lib/sequel/plugins/tactical_eager_loading.rb +23 -10
  209. data/lib/sequel/plugins/timestamps.rb +1 -1
  210. data/lib/sequel/plugins/unused_associations.rb +521 -0
  211. data/lib/sequel/plugins/update_or_create.rb +1 -1
  212. data/lib/sequel/plugins/validate_associated.rb +22 -12
  213. data/lib/sequel/plugins/validation_helpers.rb +46 -12
  214. data/lib/sequel/plugins/validation_helpers_generic_type_messages.rb +73 -0
  215. data/lib/sequel/plugins/xml_serializer.rb +1 -1
  216. data/lib/sequel/sql.rb +1 -1
  217. data/lib/sequel/timezones.rb +12 -14
  218. data/lib/sequel/version.rb +1 -1
  219. metadata +132 -38
@@ -2,7 +2,7 @@
2
2
 
3
3
  require_relative 'threaded'
4
4
 
5
- # The slowest and most advanced connection, dealing with both multi-threaded
5
+ # The slowest and most advanced connection pool, dealing with both multi-threaded
6
6
  # access and configurations with multiple shards/servers.
7
7
  #
8
8
  # In addition, this pool subclass also handles scheduling in-use connections
@@ -22,6 +22,8 @@ class Sequel::ShardedThreadedConnectionPool < Sequel::ThreadedConnectionPool
22
22
  @connections_to_disconnect = []
23
23
  @servers = opts.fetch(:servers_hash, Hash.new(:default))
24
24
  remove_instance_variable(:@waiter)
25
+ remove_instance_variable(:@allocated)
26
+ @allocated = {}
25
27
  @waiters = {}
26
28
 
27
29
  add_servers([:default])
@@ -36,7 +38,9 @@ class Sequel::ShardedThreadedConnectionPool < Sequel::ThreadedConnectionPool
36
38
  unless @servers.has_key?(server)
37
39
  @servers[server] = server
38
40
  @available_connections[server] = []
39
- @allocated[server] = {}
41
+ allocated = {}
42
+ allocated.compare_by_identity
43
+ @allocated[server] = allocated
40
44
  @waiters[server] = ConditionVariable.new
41
45
  end
42
46
  end
@@ -108,7 +112,7 @@ class Sequel::ShardedThreadedConnectionPool < Sequel::ThreadedConnectionPool
108
112
  # available, creates a new connection. Passes the connection to the supplied
109
113
  # block:
110
114
  #
111
- # pool.hold {|conn| conn.execute('DROP TABLE posts')}
115
+ # pool.hold(:server1) {|conn| conn.execute('DROP TABLE posts')}
112
116
  #
113
117
  # Pool#hold is re-entrant, meaning it can be called recursively in
114
118
  # the same thread without blocking.
@@ -141,12 +145,13 @@ class Sequel::ShardedThreadedConnectionPool < Sequel::ThreadedConnectionPool
141
145
  # except that after it is used, future requests for the server will use the
142
146
  # :default server instead.
143
147
  def remove_servers(servers)
144
- conns = nil
148
+ conns = []
149
+ raise(Sequel::Error, "cannot remove default server") if servers.include?(:default)
150
+
145
151
  sync do
146
- raise(Sequel::Error, "cannot remove default server") if servers.include?(:default)
147
152
  servers.each do |server|
148
153
  if @servers.include?(server)
149
- conns = disconnect_server_connections(server)
154
+ conns.concat(disconnect_server_connections(server))
150
155
  @waiters.delete(server)
151
156
  @available_connections.delete(server)
152
157
  @allocated.delete(server)
@@ -155,9 +160,9 @@ class Sequel::ShardedThreadedConnectionPool < Sequel::ThreadedConnectionPool
155
160
  end
156
161
  end
157
162
 
158
- if conns
159
- disconnect_connections(conns)
160
- end
163
+ nil
164
+ ensure
165
+ disconnect_connections(conns)
161
166
  end
162
167
 
163
168
  # Return an array of symbols for servers in the connection pool.
@@ -182,7 +187,7 @@ class Sequel::ShardedThreadedConnectionPool < Sequel::ThreadedConnectionPool
182
187
  # is available. The calling code should NOT already have the mutex when
183
188
  # calling this.
184
189
  #
185
- # This should return a connection is one is available within the timeout,
190
+ # This should return a connection if one is available within the timeout,
186
191
  # or nil if a connection could not be acquired within the timeout.
187
192
  def acquire(thread, server)
188
193
  if conn = assign_connection(thread, server)
@@ -321,7 +326,7 @@ class Sequel::ShardedThreadedConnectionPool < Sequel::ThreadedConnectionPool
321
326
  # Create the maximum number of connections immediately. The calling code should
322
327
  # NOT have the mutex before calling this.
323
328
  def preconnect(concurrent = false)
324
- conn_servers = @servers.keys.map!{|s| Array.new(max_size - _size(s), s)}.flatten!
329
+ conn_servers = sync{@servers.keys}.map!{|s| Array.new(max_size - _size(s), s)}.flatten!
325
330
 
326
331
  if concurrent
327
332
  conn_servers.map!{|s| Thread.new{[s, make_new(s)]}}.map!(&:value)
@@ -0,0 +1,374 @@
1
+ # frozen-string-literal: true
2
+
3
+ # :nocov:
4
+ raise LoadError, "Sequel::ShardedTimedQueueConnectionPool is only available on Ruby 3.2+" unless RUBY_VERSION >= '3.2'
5
+ # :nocov:
6
+
7
+ # A connection pool allowing multi-threaded access to a sharded pool of connections,
8
+ # using a timed queue (only available in Ruby 3.2+).
9
+ class Sequel::ShardedTimedQueueConnectionPool < Sequel::ConnectionPool
10
+ # The maximum number of connections this pool will create per shard.
11
+ attr_reader :max_size
12
+
13
+ # The following additional options are respected:
14
+ # :max_connections :: The maximum number of connections the connection pool
15
+ # will open (default 4)
16
+ # :pool_timeout :: The amount of seconds to wait to acquire a connection
17
+ # before raising a PoolTimeout (default 5)
18
+ # :servers :: A hash of servers to use. Keys should be symbols. If not
19
+ # present, will use a single :default server.
20
+ # :servers_hash :: The base hash to use for the servers. By default,
21
+ # Sequel uses Hash.new(:default). You can use a hash with a default proc
22
+ # that raises an error if you want to catch all cases where a nonexistent
23
+ # server is used.
24
+ def initialize(db, opts = OPTS)
25
+ super
26
+
27
+ @max_size = Integer(opts[:max_connections] || 4)
28
+ raise(Sequel::Error, ':max_connections must be positive') if @max_size < 1
29
+ @mutex = Mutex.new
30
+ @timeout = Float(opts[:pool_timeout] || 5)
31
+
32
+ @allocated = {}
33
+ @sizes = {}
34
+ @queues = {}
35
+ @servers = opts.fetch(:servers_hash, Hash.new(:default))
36
+
37
+ add_servers([:default])
38
+ add_servers(opts[:servers].keys) if opts[:servers]
39
+ end
40
+
41
+ # Adds new servers to the connection pool. Allows for dynamic expansion of the potential replicas/shards
42
+ # at runtime. +servers+ argument should be an array of symbols.
43
+ def add_servers(servers)
44
+ sync do
45
+ servers.each do |server|
46
+ next if @servers.has_key?(server)
47
+
48
+ @servers[server] = server
49
+ @sizes[server] = 0
50
+ @queues[server] = Queue.new
51
+ (@allocated[server] = {}).compare_by_identity
52
+ end
53
+ end
54
+ nil
55
+ end
56
+
57
+ # Yield all of the available connections, and the one currently allocated to
58
+ # this thread (if one is allocated). This will not yield connections currently
59
+ # allocated to other threads, as it is not safe to operate on them.
60
+ def all_connections
61
+ thread = Sequel.current
62
+ sync{@queues.to_a}.each do |server, queue|
63
+ if conn = owned_connection(thread, server)
64
+ yield conn
65
+ end
66
+
67
+ # Use a hash to record all connections already seen. As soon as we
68
+ # come across a connection we've already seen, we stop the loop.
69
+ conns = {}
70
+ conns.compare_by_identity
71
+ while true
72
+ conn = nil
73
+ begin
74
+ break unless (conn = queue.pop(timeout: 0)) && !conns[conn]
75
+ conns[conn] = true
76
+ yield conn
77
+ ensure
78
+ queue.push(conn) if conn
79
+ end
80
+ end
81
+ end
82
+
83
+ nil
84
+ end
85
+
86
+ # Removes all connections currently in the pool's queue. This method has the effect of
87
+ # disconnecting from the database, assuming that no connections are currently
88
+ # being used.
89
+ #
90
+ # Once a connection is requested using #hold, the connection pool
91
+ # creates new connections to the database.
92
+ #
93
+ # If the :server option is provided, it should be a symbol or array of symbols,
94
+ # and then the method will only disconnect connectsion from those specified shards.
95
+ def disconnect(opts=OPTS)
96
+ (opts[:server] ? Array(opts[:server]) : sync{@servers.keys}).each do |server|
97
+ raise Sequel::Error, "invalid server" unless queue = sync{@queues[server]}
98
+ while conn = queue.pop(timeout: 0)
99
+ disconnect_pool_connection(conn, server)
100
+ end
101
+ fill_queue(server)
102
+ end
103
+ nil
104
+ end
105
+
106
+ # Chooses the first available connection for the given server, or if none are
107
+ # available, creates a new connection. Passes the connection to the supplied
108
+ # block:
109
+ #
110
+ # pool.hold(:server1) {|conn| conn.execute('DROP TABLE posts')}
111
+ #
112
+ # Pool#hold is re-entrant, meaning it can be called recursively in
113
+ # the same thread without blocking.
114
+ #
115
+ # If no connection is immediately available and the pool is already using the maximum
116
+ # number of connections, Pool#hold will block until a connection
117
+ # is available or the timeout expires. If the timeout expires before a
118
+ # connection can be acquired, a Sequel::PoolTimeout is raised.
119
+ def hold(server=:default)
120
+ server = pick_server(server)
121
+ t = Sequel.current
122
+ if conn = owned_connection(t, server)
123
+ return yield(conn)
124
+ end
125
+
126
+ begin
127
+ conn = acquire(t, server)
128
+ yield conn
129
+ rescue Sequel::DatabaseDisconnectError, *@error_classes => e
130
+ if disconnect_error?(e)
131
+ oconn = conn
132
+ conn = nil
133
+ disconnect_pool_connection(oconn, server) if oconn
134
+ sync{@allocated[server].delete(t)}
135
+ fill_queue(server)
136
+ end
137
+ raise
138
+ ensure
139
+ release(t, conn, server) if conn
140
+ end
141
+ end
142
+
143
+ # The total number of connections in the pool. Using a non-existant server will return nil.
144
+ def size(server=:default)
145
+ sync{@sizes[server]}
146
+ end
147
+
148
+ # Remove servers from the connection pool. Similar to disconnecting from all given servers,
149
+ # except that after it is used, future requests for the servers will use the
150
+ # :default server instead.
151
+ #
152
+ # Note that an error will be raised if there are any connections currently checked
153
+ # out for the given servers.
154
+ def remove_servers(servers)
155
+ conns = []
156
+ raise(Sequel::Error, "cannot remove default server") if servers.include?(:default)
157
+
158
+ sync do
159
+ servers.each do |server|
160
+ next unless @servers.has_key?(server)
161
+
162
+ queue = @queues[server]
163
+
164
+ while conn = queue.pop(timeout: 0)
165
+ @sizes[server] -= 1
166
+ conns << conn
167
+ end
168
+
169
+ unless @sizes[server] == 0
170
+ raise Sequel::Error, "cannot remove server #{server} as it has allocated connections"
171
+ end
172
+
173
+ @servers.delete(server)
174
+ @sizes.delete(server)
175
+ @queues.delete(server)
176
+ @allocated.delete(server)
177
+ end
178
+ end
179
+
180
+ nil
181
+ ensure
182
+ disconnect_connections(conns)
183
+ end
184
+
185
+ # Return an array of symbols for servers in the connection pool.
186
+ def servers
187
+ sync{@servers.keys}
188
+ end
189
+
190
+ def pool_type
191
+ :sharded_timed_queue
192
+ end
193
+
194
+ private
195
+
196
+ # Create a new connection, after the pool's current size has already
197
+ # been updated to account for the new connection. If there is an exception
198
+ # when creating the connection, decrement the current size.
199
+ #
200
+ # This should only be called after can_make_new?. If there is an exception
201
+ # between when can_make_new? is called and when preallocated_make_new
202
+ # is called, it has the effect of reducing the maximum size of the
203
+ # connection pool by 1, since the current size of the pool will show a
204
+ # higher number than the number of connections allocated or
205
+ # in the queue.
206
+ #
207
+ # Calling code should not have the mutex when calling this.
208
+ def preallocated_make_new(server)
209
+ make_new(server)
210
+ rescue Exception
211
+ sync{@sizes[server] -= 1}
212
+ raise
213
+ end
214
+
215
+ # Disconnect all available connections immediately, and schedule currently allocated connections for disconnection
216
+ # as soon as they are returned to the pool. The calling code should NOT
217
+ # have the mutex before calling this.
218
+ def disconnect_connections(conns)
219
+ conns.each{|conn| disconnect_connection(conn)}
220
+ end
221
+
222
+ # Decrement the current size of the pool for the server when disconnecting connections.
223
+ #
224
+ # Calling code should not have the mutex when calling this.
225
+ def disconnect_pool_connection(conn, server)
226
+ sync{@sizes[server] -= 1}
227
+ disconnect_connection(conn)
228
+ end
229
+
230
+ # If there are any threads waiting on the queue, try to create
231
+ # new connections in a separate thread if the pool is not yet at the
232
+ # maximum size.
233
+ #
234
+ # The reason for this method is to handle cases where acquire
235
+ # could not retrieve a connection immediately, and the pool
236
+ # was already at the maximum size. In that case, the acquire will
237
+ # wait on the queue until the timeout. This method is called
238
+ # after disconnecting to potentially add new connections to the
239
+ # pool, so the threads that are currently waiting for connections
240
+ # do not timeout after the pool is no longer full.
241
+ def fill_queue(server)
242
+ queue = sync{@queues[server]}
243
+ if queue.num_waiting > 0
244
+ Thread.new do
245
+ while queue.num_waiting > 0 && (conn = try_make_new(server))
246
+ queue.push(conn)
247
+ end
248
+ end
249
+ end
250
+ end
251
+
252
+ # Whether the given size is less than the maximum size of the pool.
253
+ # In that case, the pool's current size is incremented. If this
254
+ # method returns true, space in the pool for the connection is
255
+ # preallocated, and preallocated_make_new should be called to
256
+ # create the connection.
257
+ #
258
+ # Calling code should have the mutex when calling this.
259
+ def can_make_new?(server, current_size)
260
+ if @max_size > current_size
261
+ @sizes[server] += 1
262
+ end
263
+ end
264
+
265
+ # Try to make a new connection if there is space in the pool.
266
+ # If the pool is already full, look for dead threads/fibers and
267
+ # disconnect the related connections.
268
+ #
269
+ # Calling code should not have the mutex when calling this.
270
+ def try_make_new(server)
271
+ return preallocated_make_new(server) if sync{can_make_new?(server, @sizes[server])}
272
+
273
+ to_disconnect = nil
274
+ do_make_new = false
275
+
276
+ sync do
277
+ current_size = @sizes[server]
278
+ alloc = @allocated[server]
279
+ alloc.keys.each do |t|
280
+ unless t.alive?
281
+ (to_disconnect ||= []) << alloc.delete(t)
282
+ current_size -= 1
283
+ end
284
+ end
285
+
286
+ do_make_new = true if can_make_new?(server, current_size)
287
+ end
288
+
289
+ begin
290
+ preallocated_make_new(server) if do_make_new
291
+ ensure
292
+ if to_disconnect
293
+ to_disconnect.each{|conn| disconnect_pool_connection(conn, server)}
294
+ fill_queue(server)
295
+ end
296
+ end
297
+ end
298
+
299
+ # Assigns a connection to the supplied thread, if one
300
+ # is available.
301
+ #
302
+ # This should return a connection if one is available within the timeout,
303
+ # or raise PoolTimeout if a connection could not be acquired within the timeout.
304
+ #
305
+ # Calling code should not have the mutex when calling this.
306
+ def acquire(thread, server)
307
+ queue = sync{@queues[server]}
308
+ if conn = queue.pop(timeout: 0) || try_make_new(server) || queue.pop(timeout: @timeout)
309
+ sync{@allocated[server][thread] = conn}
310
+ else
311
+ name = db.opts[:name]
312
+ raise ::Sequel::PoolTimeout, "timeout: #{@timeout}, server: #{server}#{", database name: #{name}" if name}"
313
+ end
314
+ end
315
+
316
+ # Returns the connection owned by the supplied thread for the given server,
317
+ # if any. The calling code should NOT already have the mutex before calling this.
318
+ def owned_connection(thread, server)
319
+ sync{@allocated[server][thread]}
320
+ end
321
+
322
+ # If the server given is in the hash, return it, otherwise, return the default server.
323
+ def pick_server(server)
324
+ sync{@servers[server]}
325
+ end
326
+
327
+ # Create the maximum number of connections immediately. This should not be called
328
+ # with a true argument unless no code is currently operating on the database.
329
+ #
330
+ # Calling code should not have the mutex when calling this.
331
+ def preconnect(concurrent = false)
332
+ conn_servers = sync{@servers.keys}.map!{|s| Array.new(@max_size - @sizes[s], s)}.flatten!
333
+
334
+ if concurrent
335
+ conn_servers.map! do |server|
336
+ queue = sync{@queues[server]}
337
+ Thread.new do
338
+ if conn = try_make_new(server)
339
+ queue.push(conn)
340
+ end
341
+ end
342
+ end.each(&:value)
343
+ else
344
+ conn_servers.each do |server|
345
+ if conn = try_make_new(server)
346
+ sync{@queues[server]}.push(conn)
347
+ end
348
+ end
349
+ end
350
+
351
+ nil
352
+ end
353
+
354
+ # Releases the connection assigned to the supplied thread back to the pool.
355
+ #
356
+ # Calling code should not have the mutex when calling this.
357
+ def release(thread, _, server)
358
+ checkin_connection(sync{@allocated[server].delete(thread)}, server)
359
+ nil
360
+ end
361
+
362
+ # Adds a connection to the queue of available connections, returns the connection.
363
+ def checkin_connection(conn, server)
364
+ sync{@queues[server]}.push(conn)
365
+ conn
366
+ end
367
+
368
+ # Yield to the block while inside the mutex.
369
+ #
370
+ # Calling code should not have the mutex when calling this.
371
+ def sync
372
+ @mutex.synchronize{yield}
373
+ end
374
+ end
@@ -24,15 +24,13 @@ class Sequel::SingleConnectionPool < Sequel::ConnectionPool
24
24
 
25
25
  # Yield the connection to the block.
26
26
  def hold(server=nil)
27
- begin
28
- unless c = @conn.first
29
- @conn.replace([c = make_new(:default)])
30
- end
31
- yield c
32
- rescue Sequel::DatabaseDisconnectError, *@error_classes => e
33
- disconnect if disconnect_error?(e)
34
- raise
27
+ unless c = @conn.first
28
+ @conn.replace([c = make_new(:default)])
35
29
  end
30
+ yield c
31
+ rescue Sequel::DatabaseDisconnectError, *@error_classes => e
32
+ disconnect if disconnect_error?(e)
33
+ raise
36
34
  end
37
35
 
38
36
  # The SingleConnectionPool always has a maximum size of 1.
@@ -3,7 +3,7 @@
3
3
  # A connection pool allowing multi-threaded access to a pool of connections.
4
4
  # This is the default connection pool used by Sequel.
5
5
  class Sequel::ThreadedConnectionPool < Sequel::ConnectionPool
6
- USE_WAITER = true
6
+ USE_WAITER = true # SEQUEL6: Remove
7
7
  Sequel::Deprecation.deprecate_constant(self, :USE_WAITER)
8
8
 
9
9
  # The maximum number of connections this pool will create (per shard/server
@@ -12,17 +12,17 @@ class Sequel::ThreadedConnectionPool < Sequel::ConnectionPool
12
12
 
13
13
  # An array of connections that are available for use by the pool.
14
14
  # The calling code should already have the mutex before calling this.
15
- attr_reader :available_connections
15
+ attr_reader :available_connections # SEQUEL6: Remove
16
16
 
17
- # A hash with thread keys and connection values for currently allocated connections.
17
+ # A hash with thread/fiber keys and connection values for currently allocated connections.
18
18
  # The calling code should already have the mutex before calling this.
19
- attr_reader :allocated
19
+ attr_reader :allocated # SEQUEL6: Remove
20
20
 
21
21
  # The following additional options are respected:
22
22
  # :max_connections :: The maximum number of connections the connection pool
23
23
  # will open (default 4)
24
24
  # :pool_timeout :: The amount of seconds to wait to acquire a connection
25
- # before raising a PoolTimeoutError (default 5)
25
+ # before raising a PoolTimeout error (default 5)
26
26
  def initialize(db, opts = OPTS)
27
27
  super
28
28
  @max_size = Integer(opts[:max_connections] || 4)
@@ -31,6 +31,7 @@ class Sequel::ThreadedConnectionPool < Sequel::ConnectionPool
31
31
  @connection_handling = opts[:connection_handling]
32
32
  @available_connections = []
33
33
  @allocated = {}
34
+ @allocated.compare_by_identity
34
35
  @timeout = Float(opts[:pool_timeout] || 5)
35
36
  @waiter = ConditionVariable.new
36
37
  end
@@ -49,8 +50,7 @@ class Sequel::ThreadedConnectionPool < Sequel::ConnectionPool
49
50
  end
50
51
  end
51
52
 
52
- # Removes all connections currently available, optionally
53
- # yielding each connection to the given block. This method has the effect of
53
+ # Removes all connections currently available. This method has the effect of
54
54
  # disconnecting from the database, assuming that no connections are currently
55
55
  # being used. If you want to be able to disconnect connections that are
56
56
  # currently in use, use the ShardedThreadedConnectionPool, which can do that.
@@ -134,7 +134,7 @@ class Sequel::ThreadedConnectionPool < Sequel::ConnectionPool
134
134
  # calling this.
135
135
  #
136
136
  # This should return a connection is one is available within the timeout,
137
- # or nil if a connection could not be acquired within the timeout.
137
+ # or raise PoolTimeout if a connection could not be acquired within the timeout.
138
138
  def acquire(thread)
139
139
  if conn = assign_connection(thread)
140
140
  return conn
@@ -274,6 +274,12 @@ class Sequel::ThreadedConnectionPool < Sequel::ConnectionPool
274
274
  end
275
275
 
276
276
  @waiter.signal
277
+
278
+ # Ensure that after signalling the condition, some other thread is given the
279
+ # opportunity to acquire the mutex.
280
+ # See <https://github.com/socketry/async/issues/99> for more context.
281
+ sleep(0)
282
+
277
283
  nil
278
284
  end
279
285