sequel 5.81.0 → 5.82.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a23afa1510b7bd1ba6918319eedce156450672520ab4798e6de56461a59660e1
4
- data.tar.gz: bb6bbfbf0f1f82fe117fa8c725e9cf4fde49644a74b463ab8682018744a8a1de
3
+ metadata.gz: f09a139a164322e0a0e52e9a58c79b2f463972363509c41bf50e7082c1f542b7
4
+ data.tar.gz: e7222b81a79111b9853565160f81cb18c72b341f18bec839046c9b49a4b199ea
5
5
  SHA512:
6
- metadata.gz: 9e26620f8f1df2715205e9019c2012ec2e9f392d14ed427d66c76f488bccc3e5d3f27e4d626a47d54903d611695db0487f987d99191602024a1864d6c10f7bde
7
- data.tar.gz: cd070d8c35960949039d226b5eaf8c90ebec8d0cef3f09b5dae533551bb35170ca6e2503d88f1b1512110b6617ff9c03d9cb2afb62395c4d953314aa5a5ca87c
6
+ metadata.gz: d9f16395ad855ccbc4494bd2e1436b6b78f15ddbfd1c9b80d356040d99415a46a352f50042b2fc9ab23d29b65567dafec2fceb35c709498159b00a1402991ee6
7
+ data.tar.gz: a5693c2f3c3791c0216a5af9e933edaa41b0eeb030a3523e52a6951c666e720d0b12ec48ae3f46777ce617b27857f6519d7ce98ec1768a0c8226c4b494e501c1
data/CHANGELOG CHANGED
@@ -1,3 +1,17 @@
1
+ === 5.82.0 (2024-07-01)
2
+
3
+ * Limit tactically eager loading to objects that have the same association reflection (jeremyevans) (#2181)
4
+
5
+ * Fix race condition in threaded/sharded_threaded connection pools that could cause stalls (jeremyevans)
6
+
7
+ * Emulate dropping a unique column or a column that is part of an index on SQLite 3.35.0+ (jeremyevans) (#2176)
8
+
9
+ * Support MERGE RETURNING on PostgreSQL 17+ (jeremyevans)
10
+
11
+ * Remove use of logger library in bin/sequel (jeremyevans)
12
+
13
+ * Support :connect_opts_proc Database option for late binding options (jeremyevans) (#2164)
14
+
1
15
  === 5.81.0 (2024-06-01)
2
16
 
3
17
  * Fix ignored block warnings in a couple plugin apply methods on Ruby 3.4 (jeremyevans)
data/bin/sequel CHANGED
@@ -18,6 +18,21 @@ load_dirs = []
18
18
  exclusive_options = []
19
19
  loggers = []
20
20
 
21
+ logger_class = Class.new do
22
+ def initialize(device)
23
+ @device = device
24
+ end
25
+
26
+ def debug(msg)
27
+ end
28
+
29
+ [:info, :warn, :error].each do |meth|
30
+ define_method(meth) do |msg|
31
+ @device.puts("#{Time.now.strftime('%Y-%m-%d %T')} #{meth.to_s.upcase}: #{msg}")
32
+ end
33
+ end
34
+ end
35
+
21
36
  options = OptionParser.new do |opts|
22
37
  opts.banner = "Sequel: The Database Toolkit for Ruby"
23
38
  opts.define_head "Usage: sequel [options] <uri|path> [file]"
@@ -61,8 +76,7 @@ options = OptionParser.new do |opts|
61
76
  end
62
77
 
63
78
  opts.on("-E", "--echo", "echo SQL statements") do
64
- require 'logger'
65
- loggers << Logger.new($stdout)
79
+ loggers << logger_class.new($stdout)
66
80
  end
67
81
 
68
82
  opts.on("-I", "--include dir", "specify $LOAD_PATH directory") do |v|
@@ -70,8 +84,7 @@ options = OptionParser.new do |opts|
70
84
  end
71
85
 
72
86
  opts.on("-l", "--log logfile", "log SQL statements to log file") do |v|
73
- require 'logger'
74
- loggers << Logger.new(v)
87
+ loggers << logger_class.new(File.open(v, 'a'))
75
88
  end
76
89
 
77
90
  opts.on("-L", "--load-dir DIR", "loads all *.rb under specifed directory") do |v|
@@ -79,6 +79,9 @@ These options are shared by all adapters unless otherwise noted.
79
79
  or string with extensions separated by columns. These extensions are loaded after
80
80
  connections are made by the :preconnect option.
81
81
  :cache_schema :: Whether schema should be cached for this database (true by default)
82
+ :connect_opts_proc :: Callable object for modifying options hash used when connecting, designed for
83
+ cases where the option values (e.g. password) are automatically rotated on
84
+ a regular basis without involvement from the application using Sequel.
82
85
  :default_string_column_size :: The default size for string columns (255 by default)
83
86
  :host :: The hostname of the database server to which to connect
84
87
  :keep_reference :: Whether to keep a reference to the database in Sequel::DATABASES (true by default)
@@ -0,0 +1,61 @@
1
+ = New Features
2
+
3
+ * MERGE RETURNING is now supported when using PostgreSQL 17+. For
4
+ datasets supporting RETURNING, calling merge with a block
5
+ will yield each returned row:
6
+
7
+ DB[:table1].
8
+ returning.
9
+ merge_using(:table2, column1: :column2).
10
+ merge_insert(column3: :column4).
11
+ merge do |row|
12
+ # ...
13
+ end
14
+ # MERGE INTO "table1" USING "table2"
15
+ # ON ("column1" = "column2")
16
+ # WHEN NOT MATCHED THEN
17
+ # INSERT ("column3") VALUES ("column3")
18
+ # RETURNING *
19
+
20
+ * A :connect_opts_proc Database option is now supported, to allow
21
+ support for late-binding Database options. If provided, this
22
+ should be a callable object that is called with the options
23
+ used for connecting, and can modify the options. This makes
24
+ it simple to support authentication schemes that rotate
25
+ passwords automatically without user involvement:
26
+
27
+ Sequel.connect('postgres://user@host/database',
28
+ connect_opts_proc: lambda do |opts|
29
+ opts[:password] = SomeAuthLibrary.get_current_password(opts[:user])
30
+ end)
31
+
32
+ Note that the jdbc adapter relies on URIs and not option hashes,
33
+ so when using the jdbc adapter with this feature, you'll generally
34
+ need to set the :uri option.
35
+
36
+ = Other Improvements
37
+
38
+ * A race condition in the threaded connection pools that could result
39
+ in a delay or timeout error in checking out connections in low-traffic
40
+ environments has been fixed.
41
+
42
+ * Sequel now supports dropping a unique column or a column that is
43
+ part of an index on SQLite 3.35.0+, with the same emulation approach
44
+ it uses in earlier SQLite versions.
45
+
46
+ * The tactical_eager_loading plugin now handles cases where inheritance
47
+ is used and the objects used include associations with the same name
48
+ but different definitions. Sequel will now only eager load the
49
+ association for objects that use the same association definition as
50
+ the receiver.
51
+
52
+ = Backwards Compatibility
53
+
54
+ * bin/sequel no longer requires logger if passed the -E or -l options.
55
+ Instead, it uses a simple implementation that supports only
56
+ debug/warn/info/error methods. If you are using bin/sequel and
57
+ depending on the log format produced by the logger library, or
58
+ calling methods on the logger object other than
59
+ debug/warn/info/error, you'll need to update your code. This change
60
+ was made because logger is moving out of stdlib in a future Ruby
61
+ version.
data/doc/testing.rdoc CHANGED
@@ -47,6 +47,20 @@ These run each test in its own transaction, the recommended way to test.
47
47
  end
48
48
  end
49
49
 
50
+ == Transactional testing with multiple threads
51
+
52
+ Some tests may require executing code across multiple threads. The most common example are browser tests with Capybara, where the web server is running in a separate thread. For transactional tests to work in this case, the main thread needs to allow other threads to use its database connection while the transaction is in progress. This can be achieved with the temporarily_release_connection extension:
53
+
54
+ DB.extension :temporarily_release_connection
55
+ DB.transaction(rollback: :always, auto_savepoint: true) do |conn|
56
+ DB.temporarily_release_connection(conn) do
57
+ # Other threads can operate on connection safely inside the transaction
58
+ yield
59
+ end
60
+ end
61
+
62
+ This requires maximum connection pool size to be 1, so make sure to set the Database +:max_connections+ option to 1 in tests.
63
+
50
64
  == Transactional testing with multiple databases
51
65
 
52
66
  You can use the Sequel.transaction method to run a transaction on multiple databases, rolling all of them back. Instead of:
@@ -2058,6 +2058,16 @@ module Sequel
2058
2058
  nil
2059
2059
  end
2060
2060
 
2061
+ # Support MERGE RETURNING on PostgreSQL 17+.
2062
+ def merge(&block)
2063
+ sql = merge_sql
2064
+ if uses_returning?(:merge)
2065
+ returning_fetch_rows(sql, &block)
2066
+ else
2067
+ execute_ddl(sql)
2068
+ end
2069
+ end
2070
+
2061
2071
  # Return a dataset with a WHEN MATCHED THEN DO NOTHING clause added to the
2062
2072
  # MERGE statement. If a block is passed, treat it as a virtual row and
2063
2073
  # use it as additional conditions for the match.
@@ -2087,7 +2097,7 @@ module Sequel
2087
2097
  # Support OVERRIDING USER|SYSTEM VALUE for MERGE INSERT.
2088
2098
  def merge_insert(*values, &block)
2089
2099
  h = {:type=>:insert, :values=>values}
2090
- if override = @opts[:override]
2100
+ if @opts[:override]
2091
2101
  h[:override] = insert_override_sql(String.new)
2092
2102
  end
2093
2103
  _merge_when(h, &block)
@@ -2170,9 +2180,14 @@ module Sequel
2170
2180
  true
2171
2181
  end
2172
2182
 
2173
- # Returning is always supported.
2183
+ # MERGE RETURNING is supported on PostgreSQL 17+. Other RETURNING is supported
2184
+ # on all supported PostgreSQL versions.
2174
2185
  def supports_returning?(type)
2175
- true
2186
+ if type == :merge
2187
+ server_version >= 170000
2188
+ else
2189
+ true
2190
+ end
2176
2191
  end
2177
2192
 
2178
2193
  # PostgreSQL supports pattern matching via regular expressions
@@ -2295,6 +2310,12 @@ module Sequel
2295
2310
  end
2296
2311
  alias _merge_not_matched_sql _merge_matched_sql
2297
2312
 
2313
+ # Support MERGE RETURNING on PostgreSQL 17+.
2314
+ def _merge_when_sql(sql)
2315
+ super
2316
+ insert_returning_sql(sql) if uses_returning?(:merge)
2317
+ end
2318
+
2298
2319
  # Format TRUNCATE statement with PostgreSQL specific options.
2299
2320
  def _truncate_sql(table)
2300
2321
  to = @opts[:truncate_opts] || OPTS
@@ -245,7 +245,7 @@ module Sequel
245
245
  super
246
246
  end
247
247
  when :drop_column
248
- if sqlite_version >= 33500
248
+ if sqlite_version >= 33500 && !indexes(table).any?{|_, h| h[:columns].include?(op[:name])}
249
249
  super
250
250
  else
251
251
  ocp = lambda{|oc| oc.delete_if{|c| c.to_s == op[:name].to_s}}
@@ -197,11 +197,8 @@ class Sequel::ShardedThreadedConnectionPool < Sequel::ThreadedConnectionPool
197
197
  timeout = @timeout
198
198
  timer = Sequel.start_timer
199
199
 
200
- sync do
201
- @waiters[server].wait(@mutex, timeout)
202
- if conn = next_available(server)
203
- return(allocated(server)[thread] = conn)
204
- end
200
+ if conn = acquire_available(thread, server, timeout)
201
+ return conn
205
202
  end
206
203
 
207
204
  until conn = assign_connection(thread, server)
@@ -211,11 +208,8 @@ class Sequel::ShardedThreadedConnectionPool < Sequel::ThreadedConnectionPool
211
208
 
212
209
  # It's difficult to get to this point, it can only happen if there is a race condition
213
210
  # where a connection cannot be acquired even after the thread is signalled by the condition variable
214
- sync do
215
- @waiters[server].wait(@mutex, timeout - elapsed)
216
- if conn = next_available(server)
217
- return(allocated(server)[thread] = conn)
218
- end
211
+ if conn = acquire_available(thread, server, timeout - elapsed)
212
+ return conn
219
213
  end
220
214
  # :nocov:
221
215
  end
@@ -223,6 +217,28 @@ class Sequel::ShardedThreadedConnectionPool < Sequel::ThreadedConnectionPool
223
217
  conn
224
218
  end
225
219
 
220
+ # Acquire a connection if one is already available, or waiting until it becomes available.
221
+ def acquire_available(thread, server, timeout)
222
+ sync do
223
+ # Check if connection was checked in between when assign_connection failed and now.
224
+ # This is very unlikely, but necessary to prevent a situation where the waiter
225
+ # will wait for a connection even though one has already been checked in.
226
+ # :nocov:
227
+ if conn = next_available(server)
228
+ return(allocated(server)[thread] = conn)
229
+ end
230
+ # :nocov:
231
+
232
+ @waiters[server].wait(@mutex, timeout)
233
+
234
+ # Connection still not available, could be because a connection was disconnected,
235
+ # may have to retry assign_connection to see if a new connection can be made.
236
+ if conn = next_available(server)
237
+ return(allocated(server)[thread] = conn)
238
+ end
239
+ end
240
+ end
241
+
226
242
  # Assign a connection to the thread, or return nil if one cannot be assigned.
227
243
  # The caller should NOT have the mutex before calling this.
228
244
  def assign_connection(thread, server)
@@ -143,11 +143,8 @@ class Sequel::ThreadedConnectionPool < Sequel::ConnectionPool
143
143
  timeout = @timeout
144
144
  timer = Sequel.start_timer
145
145
 
146
- sync do
147
- @waiter.wait(@mutex, timeout)
148
- if conn = next_available
149
- return(@allocated[thread] = conn)
150
- end
146
+ if conn = acquire_available(thread, timeout)
147
+ return conn
151
148
  end
152
149
 
153
150
  until conn = assign_connection(thread)
@@ -157,11 +154,8 @@ class Sequel::ThreadedConnectionPool < Sequel::ConnectionPool
157
154
 
158
155
  # It's difficult to get to this point, it can only happen if there is a race condition
159
156
  # where a connection cannot be acquired even after the thread is signalled by the condition variable
160
- sync do
161
- @waiter.wait(@mutex, timeout - elapsed)
162
- if conn = next_available
163
- return(@allocated[thread] = conn)
164
- end
157
+ if conn = acquire_available(thread, timeout - elapsed)
158
+ return conn
165
159
  end
166
160
  # :nocov:
167
161
  end
@@ -169,6 +163,28 @@ class Sequel::ThreadedConnectionPool < Sequel::ConnectionPool
169
163
  conn
170
164
  end
171
165
 
166
+ # Acquire a connection if one is already available, or waiting until it becomes available.
167
+ def acquire_available(thread, timeout)
168
+ sync do
169
+ # Check if connection was checked in between when assign_connection failed and now.
170
+ # This is very unlikely, but necessary to prevent a situation where the waiter
171
+ # will wait for a connection even though one has already been checked in.
172
+ # :nocov:
173
+ if conn = next_available
174
+ return(@allocated[thread] = conn)
175
+ end
176
+ # :nocov:
177
+
178
+ @waiter.wait(@mutex, timeout)
179
+
180
+ # Connection still not available, could be because a connection was disconnected,
181
+ # may have to retry assign_connection to see if a new connection can be made.
182
+ if conn = next_available
183
+ return(@allocated[thread] = conn)
184
+ end
185
+ end
186
+ end
187
+
172
188
  # Assign a connection to the thread, or return nil if one cannot be assigned.
173
189
  # The caller should NOT have the mutex before calling this.
174
190
  def assign_connection(thread)
@@ -270,28 +270,20 @@ module Sequel
270
270
  @single_threaded
271
271
  end
272
272
 
273
- if RUBY_ENGINE == 'ruby' && RUBY_VERSION < '2.5'
274
- # :nocov:
275
- def synchronize(server=nil)
276
- @pool.hold(server || :default){|conn| yield conn}
277
- end
278
- # :nocov:
279
- else
280
- # Acquires a database connection, yielding it to the passed block. This is
281
- # useful if you want to make sure the same connection is used for all
282
- # database queries in the block. It is also useful if you want to gain
283
- # direct access to the underlying connection object if you need to do
284
- # something Sequel does not natively support.
285
- #
286
- # If a server option is given, acquires a connection for that specific
287
- # server, instead of the :default server.
288
- #
289
- # DB.synchronize do |conn|
290
- # # ...
291
- # end
292
- def synchronize(server=nil, &block)
293
- @pool.hold(server || :default, &block)
294
- end
273
+ # Acquires a database connection, yielding it to the passed block. This is
274
+ # useful if you want to make sure the same connection is used for all
275
+ # database queries in the block. It is also useful if you want to gain
276
+ # direct access to the underlying connection object if you need to do
277
+ # something Sequel does not natively support.
278
+ #
279
+ # If a server option is given, acquires a connection for that specific
280
+ # server, instead of the :default server.
281
+ #
282
+ # DB.synchronize do |conn|
283
+ # # ...
284
+ # end
285
+ def synchronize(server=nil, &block)
286
+ @pool.hold(server || :default, &block)
295
287
  end
296
288
 
297
289
  # Attempts to acquire a database connection. Returns true if successful.
@@ -343,6 +335,11 @@ module Sequel
343
335
  else
344
336
  @opts.dup
345
337
  end
338
+
339
+ if pr = opts[:connect_opts_proc]
340
+ pr.call(opts)
341
+ end
342
+
346
343
  opts.delete(:servers)
347
344
  opts
348
345
  end
@@ -108,6 +108,9 @@ module Sequel
108
108
  # :cache_schema :: Whether schema should be cached for this Database instance
109
109
  # :check_string_typecast_bytesize :: Whether to check the bytesize of strings before typecasting.
110
110
  # :connect_sqls :: An array of sql strings to execute on each new connection, after :after_connect runs.
111
+ # :connect_opts_proc :: Callable object for modifying options hash used when connecting, designed for
112
+ # cases where the option values (e.g. password) are automatically rotated on
113
+ # a regular basis without involvement from the application using Sequel.
111
114
  # :default_string_column_size :: The default size of string columns, 255 by default.
112
115
  # :extensions :: Extensions to load into this Database instance. Can be a symbol, array of symbols,
113
116
  # or string with extensions separated by columns. These extensions are loaded after
@@ -294,7 +294,7 @@ module Sequel
294
294
  sch = klass.db_schema
295
295
 
296
296
  if primary_key.is_a?(Array)
297
- if (cols = sch.values_at(*klass.primary_key)).all? && (convs = cols.map{|c| c[:type] == :integer}).all?
297
+ if (cols = sch.values_at(*klass.primary_key)).all? && (cols.map{|c| c[:type] == :integer}).all?
298
298
  db = model.db
299
299
  pks.map do |cpk|
300
300
  cpk.map do |pk|
@@ -158,15 +158,19 @@ module Sequel
158
158
  end
159
159
 
160
160
  # Filter the objects used when tactical eager loading.
161
- # By default, this removes frozen objects and objects that alreayd have the association loaded
161
+ # By default, this removes frozen objects and objects that alreayd have the association loaded,
162
+ # as well as objects where the reflection for the association is not the same as the receiver's
163
+ # reflection for the association.
162
164
  def _filter_tactical_eager_load_objects(opts)
163
165
  objects = defined?(super) ? super : retrieved_with.dup
166
+ name = opts[:name]
164
167
  if opts[:eager_reload]
165
168
  objects.reject!(&:frozen?)
166
169
  else
167
- name = opts[:name]
168
170
  objects.reject!{|x| x.frozen? || x.associations.include?(name)}
169
171
  end
172
+ reflection = self.class.association_reflection(name)
173
+ objects.select!{|x| x.class.association_reflection(name).equal?(reflection)}
170
174
  objects
171
175
  end
172
176
  end
@@ -53,7 +53,7 @@ module Sequel
53
53
  def validate_associated_object(reflection, obj)
54
54
  return if reflection[:validate] == false
55
55
  association = reflection[:name]
56
- if (reflection[:type] == :one_to_many || reflection[:type] == :one_to_one) && (key = reflection[:key]).is_a?(Symbol) && !(pk_val = obj.values[key])
56
+ if (reflection[:type] == :one_to_many || reflection[:type] == :one_to_one) && (key = reflection[:key]).is_a?(Symbol) && !(obj.values[key])
57
57
  p_key = pk unless pk.is_a?(Array)
58
58
  if p_key
59
59
  obj.values[key] = p_key
@@ -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 = 81
9
+ MINOR = 82
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.81.0
4
+ version: 5.82.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: 2024-06-01 00:00:00.000000000 Z
11
+ date: 2024-07-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bigdecimal
@@ -229,6 +229,7 @@ extra_rdoc_files:
229
229
  - doc/release_notes/5.8.0.txt
230
230
  - doc/release_notes/5.80.0.txt
231
231
  - doc/release_notes/5.81.0.txt
232
+ - doc/release_notes/5.82.0.txt
232
233
  - doc/release_notes/5.9.0.txt
233
234
  files:
234
235
  - CHANGELOG
@@ -338,6 +339,7 @@ files:
338
339
  - doc/release_notes/5.8.0.txt
339
340
  - doc/release_notes/5.80.0.txt
340
341
  - doc/release_notes/5.81.0.txt
342
+ - doc/release_notes/5.82.0.txt
341
343
  - doc/release_notes/5.9.0.txt
342
344
  - doc/schema_modification.rdoc
343
345
  - doc/security.rdoc