sequel 5.62.0 → 5.63.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|