sequel 5.62.0 → 5.64.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: df5fea5cf2d6dae7a5721c549c89a190df5a1cf79f2e7d48921dcdaeae398839
4
- data.tar.gz: e1155c60bb4c2845a96cddf0a7d5498358f1cba826fdc9d7f00754a017b4310f
3
+ metadata.gz: 1971882f6efac487f21b4b5fb74695b757f2e129c14d82ffdca062cd5bc9d58f
4
+ data.tar.gz: ca4d9df4e18bd2806ad0a288856cb87f270f702a73f8de0ebea3934776d4304c
5
5
  SHA512:
6
- metadata.gz: 8e52498bbcc1c2d472592b213be8e6e508f5881de5c1ae0579b426211aa448960e06bdb297f43eb26ee3024923f78e54bb17f9a886710c3ffe60f0c134746029
7
- data.tar.gz: 0d5ae732cde0ad90a4c331f40628628e346386c483267173eb50eaad067d7de14ba6ff3c83f598d7440a6fcc752e4439cf1856706489cc3d3b7cffaf5b6d8de8
6
+ metadata.gz: abdd0fbcb5da74256493a8cfb7ff32f9247d8b021096344d3d072d2d5981fec557ed222df11da3db12abae7318c43c09b9bb2c63795816244aea76425f4106ba
7
+ data.tar.gz: 7b37c7c8cdc9172baf273ee80b328fa79bd6694a070ef81220e31bbaa20bcb0dc90a137b5cccd4c0326a7b99f17dd57591deab375dbaf61881bac3536b4eeb12
data/CHANGELOG CHANGED
@@ -1,3 +1,27 @@
1
+ === 5.64.0 (2023-01-01)
2
+
3
+ * Make :db_type column schema entries on SQLAnywhere include precision/scale information (jeremyevans)
4
+
5
+ * Include :min_value and :max_value schema entries for decimal/numeric columns on most databases (rolftimmermans, jeremyevans) (#1975)
6
+
7
+ * Support :graph_use_association_block association option to make eager_graph use the association block (jeremyevans)
8
+
9
+ * Make many_through_many and many_through_one associations support eager_graph callbacks (jeremyevans)
10
+
11
+ === 5.63.0 (2022-12-01)
12
+
13
+ * Make validates_associated plugin avoid database type errors for non-integer association keys (jeremyevans) (#1968)
14
+
15
+ * Make tactical_eager_loading plugin work better with table inheritance plugins (rolftimmermans, jeremyevans) (#1962)
16
+
17
+ * Add support for pool_class: :timed_queue on Ruby 3.2+, using a Queue for available connections (jeremyevans)
18
+
19
+ * Allow :pool_class Database option to be specified as a string to more easily choose a different pool type (jeremyevans)
20
+
21
+ * Use compare_by_identity hashes for Thread-keyed hashes in threaded connection pools (jeremyevans)
22
+
23
+ * Skip use of JRuby workaround on JRuby 9.3.9.0+ in named_timezones extension as JRuby fixed the related bug (jeremyevans)
24
+
1
25
  === 5.62.0 (2022-11-01)
2
26
 
3
27
  * Add back the pg_auto_parameterize extension for automatically using bound variables when using postgres adapter with pg driver (jeremyevans)
@@ -1498,6 +1498,36 @@ as the qualifiers may not match the aliases automatically used by eager_graph.
1498
1498
  This should contain unqualified identifiers, and eager_graph will automatically
1499
1499
  qualify them with the appropriate alias.
1500
1500
 
1501
+ ==== :graph_use_association_block
1502
+
1503
+ Setting this to true makes eager_graph apply the association block to the
1504
+ associated dataset before graphing the associated dataset into the receiver.
1505
+ In most cases when this option is used and the association has a block, the
1506
+ dataset returned by eager_graph will contain a JOIN to a subquery.
1507
+
1508
+ By default (when this option is not used), the association block will be ignored
1509
+ when using eager_graph:
1510
+
1511
+ Artist.one_to_many :tracks do |ds|
1512
+ ds.where(foo: 3)
1513
+ end
1514
+ Artist.eager_graph(:tracks)
1515
+ # SELECT albums.id, tracks.id AS tracks_id, tracks.album_id
1516
+ # FROM albums
1517
+ # LEFT OUTER JOIN tracks
1518
+ # ON (tracks.album_id = albums.id)
1519
+
1520
+ When this option is used, the block will be respected:
1521
+
1522
+ Artist.one_to_many :tracks, graph_use_association_block: true do |ds|
1523
+ ds.where(foo: 3)
1524
+ end
1525
+ Artist.eager_graph(:tracks)
1526
+ # SELECT albums.id, tracks.id AS tracks_id, tracks.album_id
1527
+ # FROM albums
1528
+ # LEFT OUTER JOIN (SELECT * FROM tracks WHERE (foo = 3)) AS tracks
1529
+ # ON (tracks.album_id = albums.id)
1530
+
1501
1531
  ==== :graph_join_table_conditions [+many_to_many+, +one_through_one+]
1502
1532
 
1503
1533
  The additional conditions to use on the SQL join for the join table when
@@ -0,0 +1,33 @@
1
+ = New Features
2
+
3
+ * On Ruby 3.2, the pool_class: :timed_queue Database option can now
4
+ be used to use an alternative connection pool that stores
5
+ connections in a queue, and uses the new Queue#pop :timeout option
6
+ in Ruby 3.2 to implement the pool timeout. This new connection
7
+ pool is simpler than the default connection pool. It is not yet
8
+ the default connection pool on Ruby 3.2, but it may become the
9
+ default in a later version. Users of Ruby 3.2 are encouraged to
10
+ try out the pool_class: :timed_queue Database option and provide
11
+ feedback on how it works in their application.
12
+
13
+ = Other Improvements
14
+
15
+ * The tactical_eager_loading plugin now works in combination with the
16
+ single_table_inheritance and class_table_inheritance plugins, when
17
+ loading an association only defined in a specific subclass.
18
+ Previously, eager loading would be skipped in such a case. Now,
19
+ an eager load will be attempted for all instances supporting the
20
+ association.
21
+
22
+ * The validate_associated plugin now avoids database type errors for
23
+ non-integer association keys. In cases where the associated object
24
+ doesn't have a value for the associated key, and the current object
25
+ does not have a key value that can be set in the associated object,
26
+ validation errors in the associated object related to the associated
27
+ key will be ignored.
28
+
29
+ * Thread-keyed connection pool hashes now use compare_by_identity for
30
+ better performance.
31
+
32
+ * The JRuby workaround in the named_timezones extension is no longer
33
+ used on JRuby 9.3.9.0+, as JRuby fixed the related bug.
@@ -0,0 +1,50 @@
1
+ = New Features
2
+
3
+ * A :graph_use_association_block association option has been added,
4
+ which makes eager_graph use the association block (as eager does),
5
+ generally resulting in a JOIN to a subquery:
6
+
7
+ Artist.one_to_many :tracks, graph_use_association_block: true do |ds|
8
+ ds.where(foo: 3)
9
+ end
10
+ Artist.eager_graph(:tracks)
11
+ # SELECT albums.id, tracks.id AS tracks_id, tracks.album_id
12
+ # FROM albums
13
+ # LEFT OUTER JOIN (SELECT * FROM tracks WHERE (foo = 3)) AS tracks
14
+ # ON (tracks.album_id = albums.id)
15
+
16
+ Assuming that the database can optimize the query correctly, using
17
+ the :graph_use_association_block option is probably simpler than
18
+ than using other :graph_* options to duplicate the conditions added
19
+ by the association block.
20
+
21
+ * Numeric/Decimal column schema entries now include :min_value and
22
+ :max_value entries on most databases, indicating the minimum and
23
+ maximum values supported for the column. Similar to the support
24
+ for integer columns added in 5.62.0, this allows the
25
+ auto_validations plugin to automatically validate the values of
26
+ the columns are in the allowed range.
27
+
28
+ = Other Improvements
29
+
30
+ * many_through_{one,many} associations now support eager_graph
31
+ callbacks.
32
+
33
+ * The :db_type column schema entries on SQLAnywhere now include
34
+ precision/scale information, to work with the numeric/decimal
35
+ column min_value/max_value support.
36
+
37
+ * The oracle adapter now includes a :column_size column schema
38
+ entry containing the precision of the columns, to work with the
39
+ numeric/decimal column min_value/max_value support.
40
+
41
+ = Backwards Compatibility
42
+
43
+ * The private Database#column_schema_integer_min_max_values method
44
+ added in 5.62.0 now takes a column schema hash instead of a
45
+ database type string.
46
+
47
+ * Code that previously looked at the :db_type column schema entry on
48
+ SQLAnywhere should be updated to look at the :domain_name entry, and
49
+ code that looked at the :domain_name_with_size entry should be
50
+ updated to look at the :db_type entry.
@@ -312,6 +312,7 @@ module Sequel
312
312
  :char_used => column.char_used?,
313
313
  :char_size => column.char_size,
314
314
  :data_size => column.data_size,
315
+ :column_size => column.precision,
315
316
  :precision => column.precision,
316
317
  :scale => column.scale,
317
318
  :fsprecision => column.fsprecision,
@@ -60,8 +60,8 @@ module Sequel
60
60
  # Access's Byte type will accept much larger values,
61
61
  # even though it only stores 0-255. Do not set min/max
62
62
  # values for the Byte type.
63
- def column_schema_integer_min_max_values(db_type)
64
- return if /byte/i =~ db_type
63
+ def column_schema_integer_min_max_values(column)
64
+ return if /byte/i =~ column[:db_type]
65
65
  super
66
66
  end
67
67
 
@@ -553,7 +553,7 @@ module Sequel
553
553
  # Return nil if CHECK constraints are not supported, because
554
554
  # versions that don't support check constraints don't raise
555
555
  # errors for values outside of range.
556
- def column_schema_integer_min_max_values(db_type)
556
+ def column_schema_integer_min_max_values(column)
557
557
  super if supports_check_constraints?
558
558
  end
559
559
 
@@ -178,11 +178,11 @@ module Sequel
178
178
  ''
179
179
  end
180
180
 
181
- # Do not support min/max integer values on Oracle, since
182
- # Oracle uses a number type, and integer just adds a
183
- # constaint on the number type.
184
- def column_schema_integer_min_max_values(db_type)
185
- nil
181
+ # Support min/max integer values on Oracle only if
182
+ # they use a NUMBER column with a fixed precision
183
+ # and no scale.
184
+ def column_schema_integer_min_max_values(column)
185
+ super if column[:db_type] =~ /NUMBER\(\d+\)/i || (column[:db_type] == 'NUMBER' && column[:column_size].is_a?(Integer) && column[:scale] == 0)
186
186
  end
187
187
 
188
188
  def create_sequence_sql(name, opts=OPTS)
@@ -37,7 +37,7 @@ module Sequel
37
37
  row[:auto_increment] = auto_increment == 1 || auto_increment == true
38
38
  row[:primary_key] = row.delete(:pkey) == 'Y'
39
39
  row[:allow_null] = row[:nulls_allowed].is_a?(Integer) ? row.delete(:nulls_allowed) == 1 : row.delete(:nulls_allowed)
40
- row[:db_type] = row.delete(:domain_name)
40
+ row[:db_type] = row.delete(:domain_name_with_size)
41
41
  row[:type] = if row[:db_type] =~ /numeric/i and (row[:scale].is_a?(Integer) ? row[:scale] == 0 : !row[:scale])
42
42
  :integer
43
43
  else
@@ -320,10 +320,11 @@ module Sequel
320
320
  end
321
321
  end
322
322
 
323
- # SQLite does not restrict the integer type to a specific range.
324
- def column_schema_integer_min_max_values(db_type)
323
+ # SQLite does not restrict the integer or decimal type to a specific range.
324
+ def column_schema_integer_min_max_values(column)
325
325
  nil
326
326
  end
327
+ alias column_schema_decimal_min_max_values column_schema_integer_min_max_values
327
328
 
328
329
  # Array of PRAGMA SQL statements based on the Database options that should be applied to
329
330
  # new 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
@@ -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
@@ -0,0 +1,257 @@
1
+ # frozen-string-literal: true
2
+
3
+ # :nocov:
4
+ raise LoadError, "Sequel::TimedQueueConnectionPool 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 pool of connections,
8
+ # using a timed queue (only available in Ruby 3.2+).
9
+ class Sequel::TimedQueueConnectionPool < Sequel::ConnectionPool
10
+ # The maximum number of connections this pool will create.
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
+ def initialize(db, opts = OPTS)
19
+ super
20
+ @max_size = Integer(opts[:max_connections] || 4)
21
+ raise(Sequel::Error, ':max_connections must be positive') if @max_size < 1
22
+ @mutex = Mutex.new
23
+ # Size inside array so this still works while the pool is frozen.
24
+ @size = [0]
25
+ @allocated = {}
26
+ @allocated.compare_by_identity
27
+ @timeout = Float(opts[:pool_timeout] || 5)
28
+ @queue = Queue.new
29
+ end
30
+
31
+ # Yield all of the available connections, and the one currently allocated to
32
+ # this thread. This will not yield connections currently allocated to other
33
+ # threads, as it is not safe to operate on them.
34
+ def all_connections
35
+ hold do |conn|
36
+ yield conn
37
+
38
+ # Use a hash to record all connections already seen. As soon as we
39
+ # come across a connection we've already seen, we stop the loop.
40
+ conns = {}
41
+ conns.compare_by_identity
42
+ while true
43
+ conn = nil
44
+ begin
45
+ break unless (conn = @queue.pop(timeout: 0)) && !conns[conn]
46
+ conns[conn] = true
47
+ yield conn
48
+ ensure
49
+ @queue.push(conn) if conn
50
+ end
51
+ end
52
+ end
53
+ end
54
+
55
+ # Removes all connections currently in the pool's queue. This method has the effect of
56
+ # disconnecting from the database, assuming that no connections are currently
57
+ # being used.
58
+ #
59
+ # Once a connection is requested using #hold, the connection pool
60
+ # creates new connections to the database.
61
+ def disconnect(opts=OPTS)
62
+ while conn = @queue.pop(timeout: 0)
63
+ disconnect_connection(conn)
64
+ end
65
+ fill_queue
66
+ nil
67
+ end
68
+
69
+ # Chooses the first available connection, or if none are
70
+ # available, creates a new connection. Passes the connection to the supplied
71
+ # block:
72
+ #
73
+ # pool.hold {|conn| conn.execute('DROP TABLE posts')}
74
+ #
75
+ # Pool#hold is re-entrant, meaning it can be called recursively in
76
+ # the same thread without blocking.
77
+ #
78
+ # If no connection is immediately available and the pool is already using the maximum
79
+ # number of connections, Pool#hold will block until a connection
80
+ # is available or the timeout expires. If the timeout expires before a
81
+ # connection can be acquired, a Sequel::PoolTimeout is raised.
82
+ def hold(server=nil)
83
+ t = Sequel.current
84
+ if conn = sync{@allocated[t]}
85
+ return yield(conn)
86
+ end
87
+
88
+ begin
89
+ conn = acquire(t)
90
+ yield conn
91
+ rescue Sequel::DatabaseDisconnectError, *@error_classes => e
92
+ if disconnect_error?(e)
93
+ oconn = conn
94
+ conn = nil
95
+ disconnect_connection(oconn) if oconn
96
+ sync{@allocated.delete(t)}
97
+ fill_queue
98
+ end
99
+ raise
100
+ ensure
101
+ release(t) if conn
102
+ end
103
+ end
104
+
105
+ def pool_type
106
+ :timed_queue
107
+ end
108
+
109
+ # The total number of connections in the pool.
110
+ def size
111
+ sync{@size[0]}
112
+ end
113
+
114
+ private
115
+
116
+ # Create a new connection, after the pool's current size has already
117
+ # been updated to account for the new connection. If there is an exception
118
+ # when creating the connection, decrement the current size.
119
+ #
120
+ # This should only be called after can_make_new?. If there is an exception
121
+ # between when can_make_new? is called and when preallocated_make_new
122
+ # is called, it has the effect of reducing the maximum size of the
123
+ # connection pool by 1, since the current size of the pool will show a
124
+ # higher number than the number of connections allocated or
125
+ # in the queue.
126
+ #
127
+ # Calling code should not have the mutex when calling this.
128
+ def preallocated_make_new
129
+ make_new(:default)
130
+ rescue Exception
131
+ sync{@size[0] -= 1}
132
+ raise
133
+ end
134
+
135
+ # Decrement the current size of the pool when disconnecting connections.
136
+ #
137
+ # Calling code should not have the mutex when calling this.
138
+ def disconnect_connection(conn)
139
+ sync{@size[0] -= 1}
140
+ super
141
+ end
142
+
143
+ # If there are any threads waiting on the queue, try to create
144
+ # new connections in a separate thread if the pool is not yet at the
145
+ # maximum size.
146
+ #
147
+ # The reason for this method is to handle cases where acquire
148
+ # could not retrieve a connection immediately, and the pool
149
+ # was already at the maximum size. In that case, the acquire will
150
+ # wait on the queue until the timeout. This method is called
151
+ # after disconnecting to potentially add new connections to the
152
+ # pool, so the threads that are currently waiting for connections
153
+ # do not timeout after the pool is no longer full.
154
+ def fill_queue
155
+ if @queue.num_waiting > 0
156
+ Thread.new do
157
+ while @queue.num_waiting > 0 && (conn = try_make_new)
158
+ @queue.push(conn)
159
+ end
160
+ end
161
+ end
162
+ end
163
+
164
+ # Whether the given size is less than the maximum size of the pool.
165
+ # In that case, the pool's current size is incremented. If this
166
+ # method returns true, space in the pool for the connection is
167
+ # preallocated, and preallocated_make_new should be called to
168
+ # create the connection.
169
+ #
170
+ # Calling code should have the mutex when calling this.
171
+ def can_make_new?(current_size)
172
+ if @max_size > current_size
173
+ @size[0] += 1
174
+ end
175
+ end
176
+
177
+ # Try to make a new connection if there is space in the pool.
178
+ # If the pool is already full, look for dead threads/fibers and
179
+ # disconnect the related connections.
180
+ #
181
+ # Calling code should not have the mutex when calling this.
182
+ def try_make_new
183
+ return preallocated_make_new if sync{can_make_new?(@size[0])}
184
+
185
+ to_disconnect = nil
186
+ do_make_new = false
187
+
188
+ sync do
189
+ current_size = @size[0]
190
+ @allocated.keys.each do |t|
191
+ unless t.alive?
192
+ (to_disconnect ||= []) << @allocated.delete(t)
193
+ current_size -= 1
194
+ end
195
+ end
196
+
197
+ do_make_new = true if can_make_new?(current_size)
198
+ end
199
+
200
+ begin
201
+ preallocated_make_new if do_make_new
202
+ ensure
203
+ if to_disconnect
204
+ to_disconnect.each{|conn| disconnect_connection(conn)}
205
+ fill_queue
206
+ end
207
+ end
208
+ end
209
+
210
+ # Assigns a connection to the supplied thread, if one
211
+ # is available.
212
+ #
213
+ # This should return a connection is one is available within the timeout,
214
+ # or raise PoolTimeout if a connection could not be acquired within the timeout.
215
+ #
216
+ # Calling code should not have the mutex when calling this.
217
+ def acquire(thread)
218
+ if conn = @queue.pop(timeout: 0) || try_make_new || @queue.pop(timeout: @timeout)
219
+ sync{@allocated[thread] = conn}
220
+ else
221
+ name = db.opts[:name]
222
+ raise ::Sequel::PoolTimeout, "timeout: #{@timeout}#{", database name: #{name}" if name}"
223
+ end
224
+ end
225
+
226
+ # Create the maximum number of connections immediately. This should not be called
227
+ # with a true argument unles no code is currently operating on the database.
228
+ #
229
+ # Calling code should not have the mutex when calling this.
230
+ def preconnect(concurrent = false)
231
+ if concurrent
232
+ if times = sync{@max_size > (size = @size[0]) ? @max_size - size : false}
233
+ times.times.map{Thread.new{if conn = try_make_new; @queue.push(conn) end}}.map(&:value)
234
+ end
235
+ else
236
+ while conn = try_make_new
237
+ @queue.push(conn)
238
+ end
239
+ end
240
+
241
+ nil
242
+ end
243
+
244
+ # Releases the connection assigned to the supplied thread back to the pool.
245
+ #
246
+ # Calling code should not have the mutex when calling this.
247
+ def release(thread)
248
+ @queue.push(sync{@allocated.delete(thread)})
249
+ end
250
+
251
+ # Yield to the block while inside the mutex.
252
+ #
253
+ # Calling code should not have the mutex when calling this.
254
+ def sync
255
+ @mutex.synchronize{yield}
256
+ end
257
+ end
@@ -30,8 +30,11 @@ class Sequel::ConnectionPool
30
30
  :threaded => :ThreadedConnectionPool,
31
31
  :single => :SingleConnectionPool,
32
32
  :sharded_threaded => :ShardedThreadedConnectionPool,
33
- :sharded_single => :ShardedSingleConnectionPool
34
- }.freeze
33
+ :sharded_single => :ShardedSingleConnectionPool,
34
+ :timed_queue => :TimedQueueConnectionPool,
35
+ }
36
+ POOL_CLASS_MAP.to_a.each{|k, v| POOL_CLASS_MAP[k.to_s] = v}
37
+ POOL_CLASS_MAP.freeze
35
38
 
36
39
  # Class methods used to return an appropriate pool subclass, separated
37
40
  # into a module for easier overridding by extensions.
@@ -175,8 +175,14 @@ module Sequel
175
175
  if !c[:max_length] && c[:type] == :string && (max_length = column_schema_max_length(c[:db_type]))
176
176
  c[:max_length] = max_length
177
177
  end
178
- if !c[:max_value] && !c[:min_value] && c[:type] == :integer && (min_max = column_schema_integer_min_max_values(c[:db_type]))
179
- c[:min_value], c[:max_value] = min_max
178
+ if !c[:max_value] && !c[:min_value]
179
+ min_max = case c[:type]
180
+ when :integer
181
+ column_schema_integer_min_max_values(c)
182
+ when :decimal
183
+ column_schema_decimal_min_max_values(c)
184
+ end
185
+ c[:min_value], c[:max_value] = min_max if min_max
180
186
  end
181
187
  end
182
188
  schema_post_process(cols)
@@ -288,7 +294,15 @@ module Sequel
288
294
 
289
295
  # Look at the db_type and guess the minimum and maximum integer values for
290
296
  # the column.
291
- def column_schema_integer_min_max_values(db_type)
297
+ def column_schema_integer_min_max_values(column)
298
+ db_type = column[:db_type]
299
+ if /decimal|numeric|number/i =~ db_type
300
+ if min_max = column_schema_decimal_min_max_values(column)
301
+ min_max.map!(&:to_i)
302
+ end
303
+ return min_max
304
+ end
305
+
292
306
  unsigned = /unsigned/i =~ db_type
293
307
  case db_type
294
308
  when /big|int8/i
@@ -304,6 +318,26 @@ module Sequel
304
318
  end
305
319
  end
306
320
 
321
+ # Look at the db_type and guess the minimum and maximum decimal values for
322
+ # the column.
323
+ def column_schema_decimal_min_max_values(column)
324
+ if column[:column_size] && column[:scale]
325
+ precision = column[:column_size]
326
+ scale = column[:scale]
327
+ elsif /\((\d+)(?:,\s*(-?\d+))?\)/ =~ column[:db_type]
328
+ precision = $1.to_i
329
+ scale = $2.to_i if $2
330
+ end
331
+
332
+ if precision
333
+ limit = BigDecimal("9" * precision)
334
+ if scale
335
+ limit /= 10**(scale)
336
+ end
337
+ [-limit, limit]
338
+ end
339
+ end
340
+
307
341
  # Whether the tinyint type (if supported by the database) is unsigned by default.
308
342
  def column_schema_tinyint_type_is_unsigned?
309
343
  false
@@ -370,7 +404,7 @@ module Sequel
370
404
  :boolean
371
405
  when /\A(real|float( unsigned)?|double( precision)?|double\(\d+,\d+\)( unsigned)?)\z/io
372
406
  :float
373
- when /\A(?:(?:(?:num(?:ber|eric)?|decimal)(?:\(\d+,\s*(\d+|false|true)\))?))\z/io
407
+ when /\A(?:(?:(?:num(?:ber|eric)?|decimal)(?:\(\d+,\s*(-?\d+|false|true)\))?))\z/io
374
408
  $1 && ['0', 'false'].include?($1) ? :integer : :decimal
375
409
  when /bytea|blob|image|(var)?binary/io
376
410
  :blob
@@ -127,6 +127,18 @@ module Sequel
127
127
  #
128
128
  # DB[:table].delete # DELETE * FROM table
129
129
  # # => 3
130
+ #
131
+ # Some databases support using multiple tables in a DELETE query. This requires
132
+ # multiple FROM tables (JOINs can also be used). As multiple FROM tables use
133
+ # an implicit CROSS JOIN, you should make sure your WHERE condition uses the
134
+ # appropriate filters for the FROM tables:
135
+ #
136
+ # DB.from(:a, :b).join(:c, :d=>Sequel[:b][:e]).where{{a[:f]=>b[:g], a[:id]=>c[:h]}}.
137
+ # delete
138
+ # # DELETE FROM a
139
+ # # USING b
140
+ # # INNER JOIN c ON (c.d = b.e)
141
+ # # WHERE ((a.f = b.g) AND (a.id = c.h))
130
142
  def delete(&block)
131
143
  sql = delete_sql
132
144
  if uses_returning?(:delete)
@@ -926,6 +938,19 @@ module Sequel
926
938
  #
927
939
  # DB[:table].update(x: Sequel[:x]+1, y: 0) # UPDATE table SET x = (x + 1), y = 0
928
940
  # # => 10
941
+ #
942
+ # Some databases support using multiple tables in an UPDATE query. This requires
943
+ # multiple FROM tables (JOINs can also be used). As multiple FROM tables use
944
+ # an implicit CROSS JOIN, you should make sure your WHERE condition uses the
945
+ # appropriate filters for the FROM tables:
946
+ #
947
+ # DB.from(:a, :b).join(:c, :d=>Sequel[:b][:e]).where{{a[:f]=>b[:g], a[:id]=>10}}.
948
+ # update(:f=>Sequel[:c][:h])
949
+ # # UPDATE a
950
+ # # SET f = c.h
951
+ # # FROM b
952
+ # # INNER JOIN c ON (c.d = b.e)
953
+ # # WHERE ((a.f = b.g) AND (a.id = 10))
929
954
  def update(values=OPTS, &block)
930
955
  sql = update_sql(values)
931
956
  if uses_returning?(:update)
@@ -5,7 +5,7 @@
5
5
  # code
6
6
  #
7
7
  # DB.extension :async_thread_pool
8
- # foos = DB[:foos].async.where{name: 'A'..'M'}.all
8
+ # foos = DB[:foos].async.where(name: 'A'..'M').all
9
9
  # bar_names = DB[:bar].async.select_order_map(:name)
10
10
  # baz_1 = DB[:bazes].async.first(id: 1)
11
11
  #
@@ -15,7 +15,7 @@
15
15
  # of calling that method on the result of the query method. For example,
16
16
  # if you run:
17
17
  #
18
- # foos = DB[:foos].async.where{name: 'A'..'M'}.all
18
+ # foos = DB[:foos].async.where(name: 'A'..'M').all
19
19
  # bar_names = DB[:bars].async.select_order_map(:name)
20
20
  # baz_1 = DB[:bazes].async.first(id: 1)
21
21
  # sleep(1)
@@ -26,7 +26,7 @@
26
26
  # These three queries will generally be run concurrently in separate
27
27
  # threads. If you instead run:
28
28
  #
29
- # DB[:foos].async.where{name: 'A'..'M'}.all.size
29
+ # DB[:foos].async.where(name: 'A'..'M').all.size
30
30
  # DB[:bars].async.select_order_map(:name).first
31
31
  # DB[:bazes].async.first(id: 1).name
32
32
  #
@@ -37,7 +37,7 @@
37
37
  # What is run in the separate thread is the entire method call that
38
38
  # returns results. So with the original example:
39
39
  #
40
- # foos = DB[:foos].async.where{name: 'A'..'M'}.all
40
+ # foos = DB[:foos].async.where(name: 'A'..'M').all
41
41
  # bar_names = DB[:bars].async.select_order_map(:name)
42
42
  # baz_1 = DB[:bazes].async.first(id: 1)
43
43
  #
@@ -156,7 +156,7 @@
156
156
  # so that the query will run in the current thread instead of waiting
157
157
  # for an async thread to become available. With the following code:
158
158
  #
159
- # foos = DB[:foos].async.where{name: 'A'..'M'}.all
159
+ # foos = DB[:foos].async.where(name: 'A'..'M').all
160
160
  # bar_names = DB[:bar].async.select_order_map(:name)
161
161
  # if foos.length > 4
162
162
  # baz_1 = DB[:bazes].async.first(id: 1)
@@ -68,6 +68,10 @@ module Sequel
68
68
  private
69
69
 
70
70
  if RUBY_VERSION >= '2.6'
71
+ # Whether Time.at with :nsec and :in is broken. True on JRuby < 9.3.9.0.
72
+ BROKEN_TIME_AT_WITH_NSEC = defined?(JRUBY_VERSION) && (JRUBY_VERSION < '9.3' || (JRUBY_VERSION < '9.4' && JRUBY_VERSION.split('.')[2].to_i < 9))
73
+ private_constant :BROKEN_TIME_AT_WITH_NSEC
74
+
71
75
  # Convert the given input Time (which must be in UTC) to the given input timezone,
72
76
  # which should be a TZInfo::Timezone instance.
73
77
  def convert_input_time_other(v, input_timezone)
@@ -77,7 +81,7 @@ module Sequel
77
81
  period = input_timezone.period_for_local(v, &disamb)
78
82
  offset = period.utc_total_offset
79
83
  # :nocov:
80
- if defined?(JRUBY_VERSION)
84
+ if BROKEN_TIME_AT_WITH_NSEC
81
85
  Time.at(v.to_i - offset, :in => input_timezone) + v.nsec/1000000000.0
82
86
  # :nocov:
83
87
  else
@@ -89,7 +93,7 @@ module Sequel
89
93
  # which should be a TZInfo::Timezone instance.
90
94
  def convert_output_time_other(v, output_timezone)
91
95
  # :nocov:
92
- if defined?(JRUBY_VERSION)
96
+ if BROKEN_TIME_AT_WITH_NSEC
93
97
  Time.at(v.to_i, :in => output_timezone) + v.nsec/1000000000.0
94
98
  # :nocov:
95
99
  else
@@ -1722,6 +1722,8 @@ module Sequel
1722
1722
  # :graph_select :: A column or array of columns to select from the associated table
1723
1723
  # when eagerly loading the association via +eager_graph+. Defaults to all
1724
1724
  # columns in the associated table.
1725
+ # :graph_use_association_block :: Makes eager_graph consider the association block. Without this, eager_graph
1726
+ # ignores the bock and only use the :graph_* options.
1725
1727
  # :instance_specific :: Marks the association as instance specific. Should be used if the association block
1726
1728
  # uses instance specific state, or transient state (accessing current date/time, etc.).
1727
1729
  # :limit :: Limit the number of records to the provided value. Use
@@ -2461,6 +2463,9 @@ module Sequel
2461
2463
  # Return dataset to graph into given the association reflection, applying the :callback option if set.
2462
2464
  def eager_graph_dataset(opts, eager_options)
2463
2465
  ds = opts.associated_class.dataset
2466
+ if opts[:graph_use_association_block] && (b = opts[:block])
2467
+ ds = b.call(ds)
2468
+ end
2464
2469
  if cb = eager_options[:callback]
2465
2470
  ds = cb.call(ds)
2466
2471
  end
@@ -3552,11 +3557,11 @@ module Sequel
3552
3557
  end
3553
3558
 
3554
3559
  # Eagerly load all specified associations.
3555
- def eager_load(a, eager_assoc=@opts[:eager])
3560
+ def eager_load(a, eager_assoc=@opts[:eager], m=model)
3556
3561
  return if a.empty?
3557
3562
 
3558
3563
  # Reflections for all associations to eager load
3559
- reflections = eager_assoc.keys.map{|assoc| model.association_reflection(assoc) || (raise Sequel::UndefinedAssociation, "Model: #{self}, Association: #{assoc}")}
3564
+ reflections = eager_assoc.keys.map{|assoc| m.association_reflection(assoc) || (raise Sequel::UndefinedAssociation, "Model: #{self}, Association: #{assoc}")}
3560
3565
 
3561
3566
  perform_eager_loads(prepare_eager_load(a, reflections, eager_assoc))
3562
3567
 
@@ -385,7 +385,7 @@ module Sequel
385
385
  iq = nil
386
386
  end
387
387
  fe = opts.final_edge
388
- ds.graph(opts.associated_class.dataset, use_only_conditions ? only_conditions : (Array(opts.right_primary_key).zip(Array(fe[:left])) + conditions), :select=>select, :table_alias=>eo[:table_alias], :qualify=>:deep, :join_type=>eo[:join_type]||join_type, :join_only=>eo[:join_only], &graph_block)
388
+ ds.graph(eager_graph_dataset(opts, eo), use_only_conditions ? only_conditions : (Array(opts.right_primary_key).zip(Array(fe[:left])) + conditions), :select=>select, :table_alias=>eo[:table_alias], :qualify=>:deep, :join_type=>eo[:join_type]||join_type, :join_only=>eo[:join_only], &graph_block)
389
389
  end
390
390
  end
391
391
  end
@@ -253,6 +253,14 @@ module Sequel
253
253
 
254
254
  private
255
255
 
256
+ # Limit tactical eager loading objects to objects that support the same association.
257
+ def _filter_tactical_eager_load_objects(opts)
258
+ objects = defined?(super) ? super : retrieved_with.dup
259
+ name = opts[:name]
260
+ objects.select!{|x| x.model.association_reflections.include?(name)}
261
+ objects
262
+ end
263
+
256
264
  # Don't allow use of prepared statements.
257
265
  def use_prepared_statements_for?(type)
258
266
  false
@@ -152,23 +152,23 @@ module Sequel
152
152
  name = opts[:name]
153
153
  eager_reload = dynamic_opts[:eager_reload]
154
154
  if (!associations.include?(name) || eager_reload) && opts[:allow_eager] != false && retrieved_by && !frozen? && !dynamic_opts[:callback] && !dynamic_opts[:reload]
155
- begin
156
- objects = if eager_reload
157
- retrieved_with.reject(&:frozen?)
158
- else
159
- retrieved_with.reject{|x| x.frozen? || x.associations.include?(name)}
160
- end
161
- retrieved_by.send(:eager_load, objects, name=>dynamic_opts[:eager] || OPTS)
162
- rescue Sequel::UndefinedAssociation
163
- # This can happen if class table inheritance is used and the association
164
- # is only defined in a subclass. This particular instance can use the
165
- # association, but it can't be eagerly loaded as the parent class doesn't
166
- # have access to the association, and that's the class doing the eager loading.
167
- nil
168
- end
155
+ retrieved_by.send(:eager_load, _filter_tactical_eager_load_objects(:eager_reload=>eager_reload, :name=>name), {name=>dynamic_opts[:eager] || OPTS}, model)
169
156
  end
170
157
  super
171
158
  end
159
+
160
+ # Filter the objects used when tactical eager loading.
161
+ # By default, this removes frozen objects and objects that alreayd have the association loaded
162
+ def _filter_tactical_eager_load_objects(opts)
163
+ objects = defined?(super) ? super : retrieved_with.dup
164
+ if opts[:eager_reload]
165
+ objects.reject!(&:frozen?)
166
+ else
167
+ name = opts[:name]
168
+ objects.reject!{|x| x.frozen? || x.associations.include?(name)}
169
+ end
170
+ objects
171
+ end
172
172
  end
173
173
 
174
174
  module DatasetMethods
@@ -2,7 +2,7 @@
2
2
 
3
3
  module Sequel
4
4
  module Plugins
5
- # The validates_associated plugin allows you to validate associated
5
+ # The validate_associated plugin allows you to validate associated
6
6
  # objects. It also offers the ability to delay the validation of
7
7
  # associated objects until the current object is validated.
8
8
  # If the associated object is invalid, validation error messages
@@ -54,22 +54,32 @@ module Sequel
54
54
  return if reflection[:validate] == false
55
55
  association = reflection[:name]
56
56
  if (reflection[:type] == :one_to_many || reflection[:type] == :one_to_one) && (key = reflection[:key]).is_a?(Symbol) && !(pk_val = obj.values[key])
57
- # There could be a presence validation on the foreign key in the associated model,
58
- # which will fail if we validate before saving the current object. If there is
59
- # no value for the foreign key, set it to the current primary key value, or a dummy
60
- # value of 0 if we haven't saved the current object.
61
57
  p_key = pk unless pk.is_a?(Array)
62
- obj.values[key] = p_key || 0
63
- key = nil if p_key
58
+ if p_key
59
+ obj.values[key] = p_key
60
+ else
61
+ ignore_key_errors = true
62
+ end
64
63
  end
65
- obj.errors.full_messages.each{|m| errors.add(association, m)} unless obj.valid?
66
- if key && !pk_val
67
- # If we used a dummy value of 0, remove it so it doesn't accidently remain.
68
- obj.values.delete(key)
64
+
65
+ unless obj.valid?
66
+ if ignore_key_errors
67
+ # Ignore errors on the key column in the associated object. This column
68
+ # will be set when saving to a presumably valid value using a column
69
+ # in the current object (which may not be available until after the current
70
+ # object is saved).
71
+ obj.errors.delete(key)
72
+ obj.errors.delete_if{|k,| Array === k && k.include?(key)}
73
+ end
74
+
75
+ obj.errors.full_messages.each do |m|
76
+ errors.add(association, m)
77
+ end
69
78
  end
79
+
80
+ nil
70
81
  end
71
82
  end
72
83
  end
73
84
  end
74
85
  end
75
-
@@ -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 = 62
9
+ MINOR = 64
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,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sequel
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.62.0
4
+ version: 5.64.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeremy Evans
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-11-01 00:00:00.000000000 Z
11
+ date: 2023-01-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: minitest
@@ -194,6 +194,8 @@ extra_rdoc_files:
194
194
  - doc/release_notes/5.60.0.txt
195
195
  - doc/release_notes/5.61.0.txt
196
196
  - doc/release_notes/5.62.0.txt
197
+ - doc/release_notes/5.63.0.txt
198
+ - doc/release_notes/5.64.0.txt
197
199
  - doc/release_notes/5.7.0.txt
198
200
  - doc/release_notes/5.8.0.txt
199
201
  - doc/release_notes/5.9.0.txt
@@ -284,6 +286,8 @@ files:
284
286
  - doc/release_notes/5.60.0.txt
285
287
  - doc/release_notes/5.61.0.txt
286
288
  - doc/release_notes/5.62.0.txt
289
+ - doc/release_notes/5.63.0.txt
290
+ - doc/release_notes/5.64.0.txt
287
291
  - doc/release_notes/5.7.0.txt
288
292
  - doc/release_notes/5.8.0.txt
289
293
  - doc/release_notes/5.9.0.txt
@@ -352,6 +356,7 @@ files:
352
356
  - lib/sequel/connection_pool/sharded_threaded.rb
353
357
  - lib/sequel/connection_pool/single.rb
354
358
  - lib/sequel/connection_pool/threaded.rb
359
+ - lib/sequel/connection_pool/timed_queue.rb
355
360
  - lib/sequel/core.rb
356
361
  - lib/sequel/database.rb
357
362
  - lib/sequel/database/connecting.rb
@@ -606,7 +611,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
606
611
  - !ruby/object:Gem::Version
607
612
  version: '0'
608
613
  requirements: []
609
- rubygems_version: 3.3.7
614
+ rubygems_version: 3.4.1
610
615
  signing_key:
611
616
  specification_version: 4
612
617
  summary: The Database Toolkit for Ruby