sequel 5.62.0 → 5.63.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 +4 -4
- data/CHANGELOG +14 -0
- data/doc/release_notes/5.63.0.txt +33 -0
- data/lib/sequel/connection_pool/sharded_threaded.rb +5 -1
- data/lib/sequel/connection_pool/threaded.rb +8 -8
- data/lib/sequel/connection_pool/timed_queue.rb +257 -0
- data/lib/sequel/connection_pool.rb +5 -2
- data/lib/sequel/dataset/actions.rb +25 -0
- data/lib/sequel/extensions/async_thread_pool.rb +5 -5
- data/lib/sequel/extensions/named_timezones.rb +6 -2
- data/lib/sequel/model/associations.rb +2 -2
- data/lib/sequel/plugins/single_table_inheritance.rb +8 -0
- data/lib/sequel/plugins/tactical_eager_loading.rb +14 -14
- data/lib/sequel/plugins/validate_associated.rb +22 -12
- data/lib/sequel/version.rb +1 -1
- metadata +6 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: abbe367cded9066a40aee5dd911a9b915f580264d00e6b668fad956bb94e38ef
|
4
|
+
data.tar.gz: 40ab8f35b0129177b550469f5ce9b6b6fccbcc97bdcfb3b06a2d8528bca37432
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 87a17fc09f790b81df75fb7d170f851c9607a0f45abf6fc50c21f50243e418362fe7c804521bfe06a12e32656e234c7e79469d40d365436edad0a2e9c176a335
|
7
|
+
data.tar.gz: ee366f061a808d7983c2996dd969f834b74815e2a1ed77410f0bdc32f822dad72dee3c48a9ad22b6f77d791d8dc43de8bc0b4619929ac5870976495774959a7b
|
data/CHANGELOG
CHANGED
@@ -1,3 +1,17 @@
|
|
1
|
+
=== 5.63.0 (2022-12-01)
|
2
|
+
|
3
|
+
* Make validates_associated plugin avoid database type errors for non-integer association keys (jeremyevans) (#1968)
|
4
|
+
|
5
|
+
* Make tactical_eager_loading plugin work better with table inheritance plugins (rolftimmermans, jeremyevans) (#1962)
|
6
|
+
|
7
|
+
* Add support for pool_class: :timed_queue on Ruby 3.2+, using a Queue for available connections (jeremyevans)
|
8
|
+
|
9
|
+
* Allow :pool_class Database option to be specified as a string to more easily choose a different pool type (jeremyevans)
|
10
|
+
|
11
|
+
* Use compare_by_identity hashes for Thread-keyed hashes in threaded connection pools (jeremyevans)
|
12
|
+
|
13
|
+
* Skip use of JRuby workaround on JRuby 9.3.9.0+ in named_timezones extension as JRuby fixed the related bug (jeremyevans)
|
14
|
+
|
1
15
|
=== 5.62.0 (2022-11-01)
|
2
16
|
|
3
17
|
* Add back the pg_auto_parameterize extension for automatically using bound variables when using postgres adapter with pg driver (jeremyevans)
|
@@ -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.
|
@@ -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
|
-
|
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
|
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
|
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
|
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
|
-
|
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.
|
@@ -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
|
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
|
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
|
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
|
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
|
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
|
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
|
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
|
@@ -3552,11 +3552,11 @@ module Sequel
|
|
3552
3552
|
end
|
3553
3553
|
|
3554
3554
|
# Eagerly load all specified associations.
|
3555
|
-
def eager_load(a, eager_assoc=@opts[:eager])
|
3555
|
+
def eager_load(a, eager_assoc=@opts[:eager], m=model)
|
3556
3556
|
return if a.empty?
|
3557
3557
|
|
3558
3558
|
# Reflections for all associations to eager load
|
3559
|
-
reflections = eager_assoc.keys.map{|assoc|
|
3559
|
+
reflections = eager_assoc.keys.map{|assoc| m.association_reflection(assoc) || (raise Sequel::UndefinedAssociation, "Model: #{self}, Association: #{assoc}")}
|
3560
3560
|
|
3561
3561
|
perform_eager_loads(prepare_eager_load(a, reflections, eager_assoc))
|
3562
3562
|
|
@@ -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
|
-
|
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
|
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
|
-
|
63
|
-
|
58
|
+
if p_key
|
59
|
+
obj.values[key] = p_key
|
60
|
+
else
|
61
|
+
ignore_key_errors = true
|
62
|
+
end
|
64
63
|
end
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
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
|
-
|
data/lib/sequel/version.rb
CHANGED
@@ -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 =
|
9
|
+
MINOR = 63
|
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.
|
4
|
+
version: 5.63.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
|
+
date: 2022-12-01 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: minitest
|
@@ -194,6 +194,7 @@ 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
|
197
198
|
- doc/release_notes/5.7.0.txt
|
198
199
|
- doc/release_notes/5.8.0.txt
|
199
200
|
- doc/release_notes/5.9.0.txt
|
@@ -284,6 +285,7 @@ files:
|
|
284
285
|
- doc/release_notes/5.60.0.txt
|
285
286
|
- doc/release_notes/5.61.0.txt
|
286
287
|
- doc/release_notes/5.62.0.txt
|
288
|
+
- doc/release_notes/5.63.0.txt
|
287
289
|
- doc/release_notes/5.7.0.txt
|
288
290
|
- doc/release_notes/5.8.0.txt
|
289
291
|
- doc/release_notes/5.9.0.txt
|
@@ -352,6 +354,7 @@ files:
|
|
352
354
|
- lib/sequel/connection_pool/sharded_threaded.rb
|
353
355
|
- lib/sequel/connection_pool/single.rb
|
354
356
|
- lib/sequel/connection_pool/threaded.rb
|
357
|
+
- lib/sequel/connection_pool/timed_queue.rb
|
355
358
|
- lib/sequel/core.rb
|
356
359
|
- lib/sequel/database.rb
|
357
360
|
- lib/sequel/database/connecting.rb
|
@@ -606,7 +609,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
606
609
|
- !ruby/object:Gem::Version
|
607
610
|
version: '0'
|
608
611
|
requirements: []
|
609
|
-
rubygems_version: 3.3.
|
612
|
+
rubygems_version: 3.3.26
|
610
613
|
signing_key:
|
611
614
|
specification_version: 4
|
612
615
|
summary: The Database Toolkit for Ruby
|