sequel 5.100.0 → 5.102.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1e8ed38ad86fbbbd643eb1a2579b799025ff77400d250abe807f41e19a533d9e
4
- data.tar.gz: 2a4e5b271a96b87d1b2e78f073c4d9a3e91d5147297e555a23c75ac0537b918d
3
+ metadata.gz: 92eed5c337b361dfde5b948323fb6a0a22ad89ecb2473c284d9e5b70a5eed8e2
4
+ data.tar.gz: dd07fefb9f3d2821479be092a12b87c6e7b92f5d8d8c314ff656741d31e75531
5
5
  SHA512:
6
- metadata.gz: f1c4b4be8a1b1ce7537799d882e0cde6bd3967a414c8beb60c3ccfff08eb0ed2a84125bca68754510d4c6b80743a49c5c9ca784b1004b3d074e9674760c2e06c
7
- data.tar.gz: 4e9386540fa22fd976b2ea915d0536225b0d5d231cd3d820e916c7dcc636bcda5a4fabda8604d3848aa183443d0c84d1f951b5ba0bef94cd79cf69526866c014
6
+ metadata.gz: ea53c48308a031896491a59e57ca3902a9a3478b8d09d12de6548a927991419054fa8698463c18c18cef9801a9c4629dc299b27df03245272bb2296f79ccdc1c
7
+ data.tar.gz: adfd92660e225f0d67948dfdc4aa57c2c61c1118265908f8b6a26fe23179573aff4195aec759575abea984e46fa5d1b62701b75caa8ce7e78838f6434e208527
@@ -71,7 +71,7 @@ class Sequel::ShardedTimedQueueConnectionPool < Sequel::ConnectionPool
71
71
  while true
72
72
  conn = nil
73
73
  begin
74
- break unless (conn = queue.pop(timeout: 0)) && !conns[conn]
74
+ break unless (conn = available(queue, server)) && !conns[conn]
75
75
  conns[conn] = true
76
76
  yield conn
77
77
  ensure
@@ -95,7 +95,7 @@ class Sequel::ShardedTimedQueueConnectionPool < Sequel::ConnectionPool
95
95
  def disconnect(opts=OPTS)
96
96
  (opts[:server] ? Array(opts[:server]) : sync{@servers.keys}).each do |server|
97
97
  raise Sequel::Error, "invalid server" unless queue = sync{@queues[server]}
98
- while conn = queue.pop(timeout: 0)
98
+ while conn = available(queue, server)
99
99
  disconnect_pool_connection(conn, server)
100
100
  end
101
101
  fill_queue(server)
@@ -167,7 +167,7 @@ class Sequel::ShardedTimedQueueConnectionPool < Sequel::ConnectionPool
167
167
 
168
168
  queue = @queues[server]
169
169
 
170
- while conn = queue.pop(timeout: 0)
170
+ while conn = available(queue, server)
171
171
  @sizes[server] -= 1
172
172
  conns << conn
173
173
  end
@@ -225,6 +225,12 @@ class Sequel::ShardedTimedQueueConnectionPool < Sequel::ConnectionPool
225
225
  conns.each{|conn| disconnect_connection(conn)}
226
226
  end
227
227
 
228
+ # Only for use by extension that need to disconnect a connection inside acquire.
229
+ # Takes the connection and any arguments accepted by acquire.
230
+ def disconnect_acquired_connection(conn, _, server)
231
+ disconnect_pool_connection(conn, server)
232
+ end
233
+
228
234
  # Decrement the current size of the pool for the server when disconnecting connections.
229
235
  #
230
236
  # Calling code should not have the mutex when calling this.
@@ -311,7 +317,7 @@ class Sequel::ShardedTimedQueueConnectionPool < Sequel::ConnectionPool
311
317
  # Calling code should not have the mutex when calling this.
312
318
  def acquire(thread, server)
313
319
  queue = sync{@queues[server]}
314
- if conn = queue.pop(timeout: 0) || try_make_new(server) || queue.pop(timeout: @timeout)
320
+ if conn = available(queue, server) || try_make_new(server) || wait_until_available(queue, server)
315
321
  sync{@allocated[server][thread] = conn}
316
322
  else
317
323
  name = db.opts[:name]
@@ -319,6 +325,19 @@ class Sequel::ShardedTimedQueueConnectionPool < Sequel::ConnectionPool
319
325
  end
320
326
  end
321
327
 
328
+ # Return the next connection in the pool if there is one available. Returns nil
329
+ # if no connection is currently available.
330
+ def available(queue, _server)
331
+ queue.pop(timeout: 0)
332
+ end
333
+
334
+ # Return the next connection in the pool if there is one available. If not, wait
335
+ # until the timeout for a connection to become available. If there is still no
336
+ # available connection, return nil.
337
+ def wait_until_available(queue, _server)
338
+ queue.pop(timeout: @timeout)
339
+ end
340
+
322
341
  # Returns the connection owned by the supplied thread for the given server,
323
342
  # if any. The calling code should NOT already have the mutex before calling this.
324
343
  def owned_connection(thread, server)
@@ -42,7 +42,7 @@ class Sequel::TimedQueueConnectionPool < Sequel::ConnectionPool
42
42
  while true
43
43
  conn = nil
44
44
  begin
45
- break unless (conn = @queue.pop(timeout: 0)) && !conns[conn]
45
+ break unless (conn = available) && !conns[conn]
46
46
  conns[conn] = true
47
47
  yield conn
48
48
  ensure
@@ -59,7 +59,7 @@ class Sequel::TimedQueueConnectionPool < Sequel::ConnectionPool
59
59
  # Once a connection is requested using #hold, the connection pool
60
60
  # creates new connections to the database.
61
61
  def disconnect(opts=OPTS)
62
- while conn = @queue.pop(timeout: 0)
62
+ while conn = available
63
63
  disconnect_connection(conn)
64
64
  end
65
65
  fill_queue
@@ -220,7 +220,7 @@ class Sequel::TimedQueueConnectionPool < Sequel::ConnectionPool
220
220
  #
221
221
  # Calling code should not have the mutex when calling this.
222
222
  def acquire(thread)
223
- if conn = @queue.pop(timeout: 0) || try_make_new || @queue.pop(timeout: @timeout)
223
+ if conn = available || try_make_new || wait_until_available
224
224
  sync{@allocated[thread] = conn}
225
225
  else
226
226
  name = db.opts[:name]
@@ -228,6 +228,19 @@ class Sequel::TimedQueueConnectionPool < Sequel::ConnectionPool
228
228
  end
229
229
  end
230
230
 
231
+ # Return the next connection in the pool if there is one available. Returns nil
232
+ # if no connection is currently available.
233
+ def available
234
+ @queue.pop(timeout: 0)
235
+ end
236
+
237
+ # Return the next connection in the pool if there is one available. If not, wait
238
+ # until the timeout for a connection to become available. If there is still no
239
+ # available connection, return nil.
240
+ def wait_until_available
241
+ @queue.pop(timeout: @timeout)
242
+ end
243
+
231
244
  # Returns the connection owned by the supplied thread,
232
245
  # if any. The calling code should NOT already have the mutex before calling this.
233
246
  def owned_connection(thread)
@@ -131,6 +131,12 @@ class Sequel::ConnectionPool
131
131
  db.disconnect_connection(conn)
132
132
  end
133
133
 
134
+ # Only for use by extension that need to disconnect a connection inside acquire.
135
+ # Takes the connection and any arguments accepted by acquire.
136
+ def disconnect_acquired_connection(conn, *)
137
+ disconnect_connection(conn)
138
+ end
139
+
134
140
  # Whether the given exception is a disconnect exception.
135
141
  def disconnect_error?(exception)
136
142
  exception.is_a?(Sequel::DatabaseDisconnectError) || db.send(:disconnect_error?, exception, OPTS)
@@ -372,6 +372,15 @@ module Sequel
372
372
  # DB[:table].import([:x, :y], DB[:table2].select(:a, :b))
373
373
  # # INSERT INTO table (x, y) SELECT a, b FROM table2
374
374
  #
375
+ # The return value of this method is undefined and should not be used,
376
+ # except in two cases:
377
+ #
378
+ # * When the <tt>return: :primary_key</tt> option is used.
379
+ # * On PostgreSQL, when the dataset uses RETURNING. In this case, if
380
+ # a single value is returned per row, the return value is an array
381
+ # of those values. If multiple values are returned per row, the
382
+ # return value is an array of hashes.
383
+ #
375
384
  # Options:
376
385
  # :commit_every :: Open a new transaction for every given number of
377
386
  # records. For example, if you provide a value of 50,
@@ -0,0 +1,151 @@
1
+ # frozen-string-literal: true
2
+ #
3
+ # The connection_checkout_event_callback extension modifies a database's
4
+ # connection pool to allow for a checkout event callback. This callback is
5
+ # called with the following arguments:
6
+ #
7
+ # :immediately_available :: Connection immediately available and returned
8
+ # :not_immediately_available :: Connection not immediately available
9
+ # :new_connection :: New connection created and returned
10
+ # Float :: Number of seconds waiting to acquire a connection
11
+ #
12
+ # This is a low-level extension that allows for building telemetry
13
+ # information. It doesn't implement any telemetry reporting itself. The
14
+ # main reason for recording this information is to use it to determine the
15
+ # appropriate size for the connection pool. Having too large a connection
16
+ # pool can waste resources, while having too small a connection pool can
17
+ # result in substantial time to check out a connection. In general, you
18
+ # want to use as small a pool as possible while keeping the time to
19
+ # checkout a connection low.
20
+ #
21
+ # To use the connection checkout event callback, you must first load the
22
+ # extension:
23
+ #
24
+ # DB.extension(:connection_checkout_event_callback)
25
+ #
26
+ # By default, an empty proc is used as the callback so that loading the
27
+ # support doesn't break anything. If you are using the extension, you
28
+ # should set the callback at some point during application startup:
29
+ #
30
+ # DB.pool.on_checkout_event = proc do |event|
31
+ # # ...
32
+ # end
33
+ #
34
+ # When using the sharded connection pool, the callback is also
35
+ # passed a second argument, the requested server shard (generally a
36
+ # symbol), allowing for collection of per-shard telemetry:
37
+ #
38
+ # DB.pool.on_checkout_event = proc do |event, server|
39
+ # # ...
40
+ # end
41
+ #
42
+ # Note that the callback may be called currently by multiple threads.
43
+ # You should use some form of concurrency control inside the callback,
44
+ # such as a mutex or queue.
45
+ #
46
+ # Below is a brief example of usage to determine the percentage of
47
+ # connection requests where a connection was immediately available:
48
+ #
49
+ # mutex = Mutex.new
50
+ # total = immediates = 0
51
+ #
52
+ # DB.pool.on_checkout_event = proc do |event|
53
+ # case event
54
+ # when :immediately_available
55
+ # mutex.synchronize do
56
+ # total += 1
57
+ # immediates += 1
58
+ # end
59
+ # when :not_immediately_available
60
+ # mutex.synchronize do
61
+ # total += 1
62
+ # end
63
+ # end
64
+ # end
65
+ #
66
+ # immediate_percentage = lambda do
67
+ # mutex.synchronize do
68
+ # 100.0 * immediates / total
69
+ # end
70
+ # end
71
+ #
72
+ # Note that this extension only works with the timed_queue
73
+ # and sharded_timed_queue connection pools (the default
74
+ # connection pools when using Ruby 3.2+).
75
+ #
76
+ # Related modules: Sequel::ConnectionCheckoutEventCallbacks::TimedQueue,
77
+ # Sequel::ConnectionCheckoutEventCallbacks::ShardedTimedQueue
78
+
79
+ #
80
+ module Sequel
81
+ module ConnectionCheckoutEventCallbacks
82
+ module TimedQueue
83
+ # The callback that is called with connection checkout events.
84
+ attr_accessor :on_checkout_event
85
+
86
+ private
87
+
88
+ def available
89
+ conn = super
90
+ @on_checkout_event.call(conn ? :immediately_available : :not_immediately_available)
91
+ conn
92
+ end
93
+
94
+ def try_make_new
95
+ conn = super
96
+ @on_checkout_event.call(:new_connection) if conn
97
+ conn
98
+ end
99
+
100
+ def wait_until_available
101
+ timer = Sequel.start_timer
102
+ conn = super
103
+ @on_checkout_event.call(Sequel.elapsed_seconds_since(timer))
104
+ conn
105
+ end
106
+ end
107
+
108
+ module ShardedTimedQueue
109
+ # The callback that is called with connection checkout events.
110
+ attr_accessor :on_checkout_event
111
+
112
+ private
113
+
114
+ def available(queue, server)
115
+ conn = super
116
+ @on_checkout_event.call(conn ? :immediately_available : :not_immediately_available, server)
117
+ conn
118
+ end
119
+
120
+ def try_make_new(server)
121
+ conn = super
122
+ @on_checkout_event.call(:new_connection, server) if conn
123
+ conn
124
+ end
125
+
126
+ def wait_until_available(queue, server)
127
+ timer = Sequel.start_timer
128
+ conn = super
129
+ @on_checkout_event.call(Sequel.elapsed_seconds_since(timer), server)
130
+ conn
131
+ end
132
+ end
133
+ end
134
+
135
+ default_callback = proc{}
136
+
137
+ Database.register_extension(:connection_checkout_event_callback) do |db|
138
+ pool = db.pool
139
+
140
+ case pool.pool_type
141
+ when :timed_queue
142
+ db.pool.extend(ConnectionCheckoutEventCallbacks::TimedQueue)
143
+ when :sharded_timed_queue
144
+ db.pool.extend(ConnectionCheckoutEventCallbacks::ShardedTimedQueue)
145
+ else
146
+ raise Error, "the connection_checkout_event_callback extension is only supported when using a timed_queue connection pool"
147
+ end
148
+
149
+ pool.on_checkout_event ||= default_callback
150
+ end
151
+ end
@@ -91,7 +91,7 @@ module Sequel
91
91
  sync{@allocated.delete(Sequel.current)}
92
92
  end
93
93
 
94
- disconnect_connection(conn)
94
+ disconnect_acquired_connection(conn, *a)
95
95
  redo
96
96
  end
97
97
  end
@@ -118,7 +118,7 @@ module Sequel
118
118
  sync{@allocated.delete(Sequel.current)}
119
119
  end
120
120
 
121
- disconnect_connection(conn)
121
+ disconnect_acquired_connection(conn, *a)
122
122
  redo if valid == false
123
123
  end
124
124
  end
@@ -0,0 +1,164 @@
1
+ # frozen-string-literal: true
2
+
3
+ module Sequel
4
+ module Plugins
5
+ # The detect_unnecessary_association_options plugin can detect unnecessary
6
+ # association options, and either warn or raise if they are detected.
7
+ # This allows you to find and remove the unnecessary options.
8
+ # Association options are considered unnecessary if they specify the same
9
+ # value as Sequel's defaults.
10
+ #
11
+ # To detect unnecessary association options, you should load the plugin
12
+ # into your model base class (e.g. Sequel::Model) before loading your model
13
+ # classes. Then, after all models have been loaded, you can call the
14
+ # detect_unnecessary_association_options on each to check for unnecessary
15
+ # association options. Additionally, if you are calling finalize_associations,
16
+ # it will automatically check for unnecessary association options.
17
+ #
18
+ # A typical usage would be to combine this with the subclasses plugin:
19
+ #
20
+ # Sequel::Model.plugin :detect_unnecessary_association_options
21
+ # Sequel::Model.plugin :subclasses
22
+ # # load model classes
23
+ #
24
+ # # implicitly check all subclasses when freezing descendants
25
+ # Sequel::Model.freeze_descendants
26
+ #
27
+ # # or, if not freezing all descendants
28
+ # Sequel::Model.descendants.each(&:detect_unnecessary_association_options)
29
+ #
30
+ # By default, the plugin warns for every unnecessary association option.
31
+ # To raise an error instead, you can pass the <tt>action: :raise</tt> option when loading the
32
+ # plugin:
33
+ #
34
+ # Sequel::Model.plugin :detect_unnecessary_association_options, action: :raise
35
+ #
36
+ # This plugin only detects the most common unnecessary association options, such as:
37
+ #
38
+ # * :class (all associations)
39
+ # * :key and :primary_key (associations without join tables)
40
+ # * :join_table, :left_key, :right_key, :left_primary_key, :right_primary_key (single join table associations)
41
+ # * :left_primary_key, :right_primary_key (*_through_many associations)
42
+ #
43
+ # Only association types supported by default or supported by a plugin that
44
+ # ships with Sequel are supported by this plugin. Other association types are
45
+ # ignored.
46
+ module DetectUnnecessaryAssociationOptions
47
+ def self.configure(model, opts={})
48
+ model.instance_variable_set(:@detect_unnecessary_association_options_action, opts[:action] || :warn)
49
+ end
50
+
51
+ # Raised if the plugin action is to raise and an unnecessary association option
52
+ # is detected.
53
+ class UnnecessaryAssociationOption < Sequel::Error
54
+ end
55
+
56
+ module ClassMethods
57
+ Plugins.inherited_instance_variables(self, :@detect_unnecessary_association_options_action => nil)
58
+
59
+ # Implicitly check for unnecessary association options when finalizing associations.
60
+ def finalize_associations
61
+ res = super
62
+ detect_unnecessary_association_options
63
+ res
64
+ end
65
+
66
+ # Check for unnecessary association options.
67
+ def detect_unnecessary_association_options
68
+ @association_reflections.each_value do |ref|
69
+ meth = "detect_unnecessary_association_options_#{ref[:type]}"
70
+ # Expected to call private methods.
71
+ # Ignore unrecognized association types.
72
+ # External association types can define the appropriate method to
73
+ # support their own unnecessary association option checks.
74
+ if respond_to?(meth, true)
75
+ # All recognized association types need same class check
76
+ _detect_unnecessary_association_options_class(ref)
77
+ send(meth, ref)
78
+ end
79
+ end
80
+
81
+ nil
82
+ end
83
+
84
+ private
85
+
86
+ # Action to take if an unnecessary association option is detected.
87
+ def unnecessary_association_options_detected(ref, key)
88
+ if @detect_unnecessary_association_options_action == :raise
89
+ raise UnnecessaryAssociationOption, "#{ref.inspect} :#{key} option unnecessary"
90
+ else
91
+ warn "#{ref.inspect} :#{key} option unnecessary"
92
+ end
93
+ end
94
+
95
+ # Detect unnecessary :class option.
96
+ def _detect_unnecessary_association_options_class(ref)
97
+ return unless ref[:orig_class]
98
+
99
+ h = {}
100
+ name = ref[:name]
101
+ late_binding_class_option(h, ref.returns_array? ? singularize(name) : name)
102
+
103
+ begin
104
+ default_association_class = constantize(h[:class_name])
105
+ actual_association_class = ref.associated_class
106
+ rescue NameError
107
+ # Do not warn. For the default association class to not be a valid
108
+ # constant is expected. For the actual association class to not be
109
+ # a valid constant is not expected and a bug in the association, but
110
+ # the job of this plugin is not to detect invalid options, only
111
+ # unnecessary options.
112
+ else
113
+ if default_association_class.equal?(actual_association_class)
114
+ unnecessary_association_options_detected(ref, "class")
115
+ end
116
+ end
117
+ end
118
+
119
+ # Detect other unnecessary options. An option is considered unnecessary
120
+ # if the key was submitted as an association option and the value for
121
+ # the option is the same as the given value.
122
+ def _detect_unnecessary_association_options_key_value(ref, key, value)
123
+ if ref[:orig_opts].has_key?(key) && ref[:orig_opts][key] == value
124
+ unnecessary_association_options_detected(ref, key)
125
+ end
126
+ end
127
+
128
+ # Same as _detect_unnecessary_association_options_key_value, but calls
129
+ # the default_* method on the association reflection to get the default value.
130
+ def _detect_unnecessary_association_options_key(ref, key)
131
+ _detect_unnecessary_association_options_key_value(ref, key, ref.send(:"default_#{key}"))
132
+ end
133
+
134
+ def detect_unnecessary_association_options_many_to_one(ref)
135
+ _detect_unnecessary_association_options_key(ref, :key)
136
+ _detect_unnecessary_association_options_key_value(ref, :primary_key, ref.associated_class.primary_key)
137
+ end
138
+ alias detect_unnecessary_association_options_pg_array_to_many detect_unnecessary_association_options_many_to_one
139
+
140
+ def detect_unnecessary_association_options_one_to_many(ref)
141
+ _detect_unnecessary_association_options_key(ref, :key)
142
+ _detect_unnecessary_association_options_key_value(ref, :primary_key, primary_key)
143
+ end
144
+ alias detect_unnecessary_association_options_one_to_one detect_unnecessary_association_options_one_to_many
145
+ alias detect_unnecessary_association_options_many_to_pg_array detect_unnecessary_association_options_one_to_many
146
+
147
+ def detect_unnecessary_association_options_many_to_many(ref)
148
+ [:join_table, :left_key, :right_key].each do |key|
149
+ _detect_unnecessary_association_options_key(ref, key)
150
+ end
151
+ _detect_unnecessary_association_options_key_value(ref, :left_primary_key, primary_key)
152
+ _detect_unnecessary_association_options_key_value(ref, :right_primary_key, ref.associated_class.primary_key)
153
+ end
154
+ alias detect_unnecessary_association_options_one_through_one detect_unnecessary_association_options_many_to_many
155
+
156
+ def detect_unnecessary_association_options_many_through_many(ref)
157
+ _detect_unnecessary_association_options_key_value(ref, :left_primary_key, primary_key)
158
+ _detect_unnecessary_association_options_key_value(ref, :right_primary_key, ref.associated_class.primary_key)
159
+ end
160
+ alias detect_unnecessary_association_options_one_through_many detect_unnecessary_association_options_many_through_many
161
+ end
162
+ end
163
+ end
164
+ end
@@ -6,7 +6,7 @@ module Sequel
6
6
 
7
7
  # The minor version of Sequel. Bumped for every non-patch level
8
8
  # release, generally around once a month.
9
- MINOR = 100
9
+ MINOR = 102
10
10
 
11
11
  # The tiny version of Sequel. Usually 0, only bumped for bugfix
12
12
  # releases that fix regressions from previous versions.
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sequel
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.100.0
4
+ version: 5.102.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeremy Evans
@@ -212,6 +212,7 @@ files:
212
212
  - lib/sequel/extensions/blank.rb
213
213
  - lib/sequel/extensions/caller_logging.rb
214
214
  - lib/sequel/extensions/columns_introspection.rb
215
+ - lib/sequel/extensions/connection_checkout_event_callback.rb
215
216
  - lib/sequel/extensions/connection_expiration.rb
216
217
  - lib/sequel/extensions/connection_validator.rb
217
218
  - lib/sequel/extensions/constant_sql_override.rb
@@ -344,6 +345,7 @@ files:
344
345
  - lib/sequel/plugins/defaults_setter.rb
345
346
  - lib/sequel/plugins/delay_add_association.rb
346
347
  - lib/sequel/plugins/deprecated_associations.rb
348
+ - lib/sequel/plugins/detect_unnecessary_association_options.rb
347
349
  - lib/sequel/plugins/dirty.rb
348
350
  - lib/sequel/plugins/eager_each.rb
349
351
  - lib/sequel/plugins/eager_graph_eager.rb