sequel 5.81.0 → 5.83.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: 66956ac28806c0389a6c809b0aa70d2746a7aff47fd5b039d238c08f621cdbff
4
+ data.tar.gz: d2b8d3f88b13432c60e6164fda3577725300e18a0dfa03682871405d074d7ba0
5
5
  SHA512:
6
- metadata.gz: 9e26620f8f1df2715205e9019c2012ec2e9f392d14ed427d66c76f488bccc3e5d3f27e4d626a47d54903d611695db0487f987d99191602024a1864d6c10f7bde
7
- data.tar.gz: cd070d8c35960949039d226b5eaf8c90ebec8d0cef3f09b5dae533551bb35170ca6e2503d88f1b1512110b6617ff9c03d9cb2afb62395c4d953314aa5a5ca87c
6
+ metadata.gz: 55d45f7c3cb7154f275ddf5ea3649d78a38f147abdb49f1286358d8dbec9f607e3bf43c9ebe1dc31276d28820f455fc82dcce7b4c9808b325288380acf90edf4
7
+ data.tar.gz: b8301c7880dcb598ca027d225f5be791200ac1f13f4efe5351222d351b218019351a8efa1f485d499a0650cd4213ba0a148c3f10f0a67aa0b3c04f4ee611b074
data/CHANGELOG CHANGED
@@ -1,3 +1,33 @@
1
+ === 5.83.0 (2024-08-01)
2
+
3
+ * Make optimistic_locking plugin not keep lock column in changed_columns after updating instance (jeremyevans) (#2196)
4
+
5
+ * Have defaults_setter plugin pass model instance to default_values callable if it has arity 1 (pedrocarmona) (#2195)
6
+
7
+ * Support string_agg extension on SQLite 3.44+ (segiddins) (#2191)
8
+
9
+ * Support schema-qualified table names when using create_table :temp with a Sequel::SQL::QualifiedIdentifier (jeremyevans) (#2185)
10
+
11
+ * Support MERGE WHEN NOT MATCHED BY SOURCE on PostgreSQL 17+ (jeremyevans)
12
+
13
+ * Add stdio_logger extension for a minimal logger that Sequel::Database can use (jeremyevans)
14
+
15
+ * Simplify Database#inspect output to database_type, host, database, and user (jeremyevans)
16
+
17
+ === 5.82.0 (2024-07-01)
18
+
19
+ * Limit tactically eager loading to objects that have the same association reflection (jeremyevans) (#2181)
20
+
21
+ * Fix race condition in threaded/sharded_threaded connection pools that could cause stalls (jeremyevans)
22
+
23
+ * Emulate dropping a unique column or a column that is part of an index on SQLite 3.35.0+ (jeremyevans) (#2176)
24
+
25
+ * Support MERGE RETURNING on PostgreSQL 17+ (jeremyevans)
26
+
27
+ * Remove use of logger library in bin/sequel (jeremyevans)
28
+
29
+ * Support :connect_opts_proc Database option for late binding options (jeremyevans) (#2164)
30
+
1
31
  === 5.81.0 (2024-06-01)
2
32
 
3
33
  * Fix ignored block warnings in a couple plugin apply methods on Ruby 3.4 (jeremyevans)
data/bin/sequel CHANGED
@@ -61,8 +61,7 @@ options = OptionParser.new do |opts|
61
61
  end
62
62
 
63
63
  opts.on("-E", "--echo", "echo SQL statements") do
64
- require 'logger'
65
- loggers << Logger.new($stdout)
64
+ loggers << $stdout
66
65
  end
67
66
 
68
67
  opts.on("-I", "--include dir", "specify $LOAD_PATH directory") do |v|
@@ -70,8 +69,9 @@ options = OptionParser.new do |opts|
70
69
  end
71
70
 
72
71
  opts.on("-l", "--log logfile", "log SQL statements to log file") do |v|
73
- require 'logger'
74
- loggers << Logger.new(v)
72
+ file = File.open(v, 'a')
73
+ file.sync = true
74
+ loggers << file
75
75
  end
76
76
 
77
77
  opts.on("-L", "--load-dir DIR", "loads all *.rb under specifed directory") do |v|
@@ -155,6 +155,11 @@ begin
155
155
  end
156
156
  end
157
157
 
158
+ unless loggers.empty?
159
+ Sequel.extension :stdio_logger
160
+ loggers.map!{|io| Sequel::StdioLogger.new(io)}
161
+ end
162
+
158
163
  DB = connect_proc[db]
159
164
  load_dirs.each{|d| d.is_a?(Array) ? require(d.first) : Dir["#{d}/**/*.rb"].each{|f| load(f)}}
160
165
  if migrate_dir
@@ -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.
@@ -0,0 +1,56 @@
1
+ = New Features
2
+
3
+ * MERGE WHEN NOT MATCHED BY SOURCE is now supported when using
4
+ PostgreSQL 17+. You can use this SQL syntax via the following
5
+ Dataset methods:
6
+
7
+ * merge_delete_when_not_matched_by_source
8
+ * merge_update_when_not_matched_by_source
9
+ * merge_do_nothing_when_not_matched_by_source
10
+
11
+ These are similar to the existing merge_delete, merge_update,
12
+ and merge_do_nothing_when_matched, except they use
13
+ WHEN NOT MATCHED BY SOURCE instead of WHEN MATCHED.
14
+
15
+ * An stdio_logger extension has been added. This adds the
16
+ Sequel::StdioLogger class, which is a minimal logger implementation
17
+ that is compatible for usage with Sequel::Database. Example:
18
+
19
+ Sequel.extension :stdio_logger
20
+ DB.loggers << Sequel::StdioLogger.new($stdout)
21
+
22
+ = Other Improvements
23
+
24
+ * Database#inspect now only displays the database type, host, database
25
+ name, and user. In addition to being easier to read, this also
26
+ prevents displaying the password, enhancing security.
27
+
28
+ * The string_agg extension now supports SQLite 3.44+.
29
+
30
+ * The defaults_setter plugin now passes the model instance to a
31
+ default_values proc if the proc has arity 1. This allows default
32
+ values to depend on model instance state.
33
+
34
+ * The optimistic_locking plugin no longer adds the lock column to
35
+ changed_columns after updating the model instance.
36
+
37
+ * Database#create_temp with :temp option and an
38
+ SQL::QualifiedIdentifier table name will now attempt to create a
39
+ schema qualified table. Note that schema qualified temporary
40
+ tables are not supported by many (any?) databases, but this
41
+ change prevents the CREATE TABLE statement from succeeding with
42
+ an unexpected table name.
43
+
44
+ = Backwards Compatibility
45
+
46
+ * The Database.uri_to_options private class method now handles
47
+ conversion of URI parameters to options. Previously, this was
48
+ handled by callers of this method.
49
+
50
+ * The _merge_matched_sql and _merge_not_matched_sql private Dataset
51
+ methods in PostgreSQL have been replaced with
52
+ _merge_do_nothing_sql.
53
+
54
+ * An unnecessary space in submitted SQL has been removed when using
55
+ MERGE INSERT on PostgreSQL. This should only affect your code if
56
+ you are explicitly checking the produced SQL.
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:
@@ -115,7 +115,7 @@ module Sequel
115
115
  # Temporary table creation on Derby uses DECLARE instead of CREATE.
116
116
  def create_table_prefix_sql(name, options)
117
117
  if options[:temp]
118
- "DECLARE GLOBAL TEMPORARY TABLE #{quote_identifier(name)}"
118
+ "DECLARE GLOBAL TEMPORARY TABLE #{create_table_table_name_sql(name, options)}"
119
119
  else
120
120
  super
121
121
  end
@@ -198,7 +198,7 @@ module Sequel
198
198
  # http://www.ibm.com/developerworks/data/library/techarticle/dm-0912globaltemptable/
199
199
  def create_table_prefix_sql(name, options)
200
200
  if options[:temp]
201
- "DECLARE GLOBAL TEMPORARY TABLE #{quote_identifier(name)}"
201
+ "DECLARE GLOBAL TEMPORARY TABLE #{create_table_table_name_sql(name, options)}"
202
202
  else
203
203
  super
204
204
  end
@@ -379,9 +379,21 @@ module Sequel
379
379
  # a regular and temporary table, with temporary table names starting with
380
380
  # a #.
381
381
  def create_table_prefix_sql(name, options)
382
- "CREATE TABLE #{quote_schema_table(options[:temp] ? "##{name}" : name)}"
382
+ "CREATE TABLE #{create_table_table_name_sql(name, options)}"
383
383
  end
384
-
384
+
385
+ # The SQL to use for the table name for a temporary table.
386
+ def create_table_temp_table_name_sql(name, _options)
387
+ case name
388
+ when String, Symbol
389
+ "##{name}"
390
+ when SQL::Identifier
391
+ "##{name.value}"
392
+ else
393
+ raise Error, "temporary table names must be strings, symbols, or Sequel::SQL::Identifier instances on Microsoft SQL Server"
394
+ end
395
+ end
396
+
385
397
  # MSSQL doesn't support CREATE TABLE AS, it only supports SELECT INTO.
386
398
  # Emulating CREATE TABLE AS using SELECT INTO is only possible if a dataset
387
399
  # is given as the argument, it can't work with a string, so raise an
@@ -1429,7 +1429,7 @@ module Sequel
1429
1429
  'UNLOGGED '
1430
1430
  end
1431
1431
 
1432
- "CREATE #{prefix_sql}TABLE#{' IF NOT EXISTS' if options[:if_not_exists]} #{options[:temp] ? quote_identifier(name) : quote_schema_table(name)}"
1432
+ "CREATE #{prefix_sql}TABLE#{' IF NOT EXISTS' if options[:if_not_exists]} #{create_table_table_name_sql(name, options)}"
1433
1433
  end
1434
1434
 
1435
1435
  # SQL for creating a table with PostgreSQL specific options
@@ -2058,6 +2058,29 @@ 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
+
2071
+ # Return a dataset with a WHEN NOT MATCHED BY SOURCE THEN DELETE clause added to the
2072
+ # MERGE statement. If a block is passed, treat it as a virtual row and
2073
+ # use it as additional conditions for the match.
2074
+ #
2075
+ # merge_delete_not_matched_by_source
2076
+ # # WHEN NOT MATCHED BY SOURCE THEN DELETE
2077
+ #
2078
+ # merge_delete_not_matched_by_source{a > 30}
2079
+ # # WHEN NOT MATCHED BY SOURCE AND (a > 30) THEN DELETE
2080
+ def merge_delete_when_not_matched_by_source(&block)
2081
+ _merge_when(:type=>:delete_not_matched_by_source, &block)
2082
+ end
2083
+
2061
2084
  # Return a dataset with a WHEN MATCHED THEN DO NOTHING clause added to the
2062
2085
  # MERGE statement. If a block is passed, treat it as a virtual row and
2063
2086
  # use it as additional conditions for the match.
@@ -2084,15 +2107,41 @@ module Sequel
2084
2107
  _merge_when(:type=>:not_matched, &block)
2085
2108
  end
2086
2109
 
2110
+ # Return a dataset with a WHEN NOT MATCHED BY SOURCE THEN DO NOTHING clause added to the
2111
+ # MERGE BY SOURCE statement. If a block is passed, treat it as a virtual row and
2112
+ # use it as additional conditions for the match.
2113
+ #
2114
+ # merge_do_nothing_when_not_matched_by_source
2115
+ # # WHEN NOT MATCHED BY SOURCE THEN DO NOTHING
2116
+ #
2117
+ # merge_do_nothing_when_not_matched_by_source{a > 30}
2118
+ # # WHEN NOT MATCHED BY SOURCE AND (a > 30) THEN DO NOTHING
2119
+ def merge_do_nothing_when_not_matched_by_source(&block)
2120
+ _merge_when(:type=>:not_matched_by_source, &block)
2121
+ end
2122
+
2087
2123
  # Support OVERRIDING USER|SYSTEM VALUE for MERGE INSERT.
2088
2124
  def merge_insert(*values, &block)
2089
2125
  h = {:type=>:insert, :values=>values}
2090
- if override = @opts[:override]
2126
+ if @opts[:override]
2091
2127
  h[:override] = insert_override_sql(String.new)
2092
2128
  end
2093
2129
  _merge_when(h, &block)
2094
2130
  end
2095
2131
 
2132
+ # Return a dataset with a WHEN NOT MATCHED BY SOURCE THEN UPDATE clause added to the
2133
+ # MERGE statement. If a block is passed, treat it as a virtual row and
2134
+ # use it as additional conditions for the match.
2135
+ #
2136
+ # merge_update_not_matched_by_source(i1: Sequel[:i1]+:i2+10, a: Sequel[:a]+:b+20)
2137
+ # # WHEN NOT MATCHED BY SOURCE THEN UPDATE SET i1 = (i1 + i2 + 10), a = (a + b + 20)
2138
+ #
2139
+ # merge_update_not_matched_by_source(i1: :i2){a > 30}
2140
+ # # WHEN NOT MATCHED BY SOURCE AND (a > 30) THEN UPDATE SET i1 = i2
2141
+ def merge_update_when_not_matched_by_source(values, &block)
2142
+ _merge_when(:type=>:update_not_matched_by_source, :values=>values, &block)
2143
+ end
2144
+
2096
2145
  # Use OVERRIDING USER VALUE for INSERT statements, so that identity columns
2097
2146
  # always use the user supplied value, and an error is not raised for identity
2098
2147
  # columns that are GENERATED ALWAYS.
@@ -2170,9 +2219,14 @@ module Sequel
2170
2219
  true
2171
2220
  end
2172
2221
 
2173
- # Returning is always supported.
2222
+ # MERGE RETURNING is supported on PostgreSQL 17+. Other RETURNING is supported
2223
+ # on all supported PostgreSQL versions.
2174
2224
  def supports_returning?(type)
2175
- true
2225
+ if type == :merge
2226
+ server_version >= 170000
2227
+ else
2228
+ true
2229
+ end
2176
2230
  end
2177
2231
 
2178
2232
  # PostgreSQL supports pattern matching via regular expressions
@@ -2281,7 +2335,7 @@ module Sequel
2281
2335
 
2282
2336
  # Append the INSERT sql used in a MERGE
2283
2337
  def _merge_insert_sql(sql, data)
2284
- sql << " THEN INSERT "
2338
+ sql << " THEN INSERT"
2285
2339
  columns, values = _parse_insert_sql_args(data[:values])
2286
2340
  _insert_columns_sql(sql, columns)
2287
2341
  if override = data[:override]
@@ -2290,10 +2344,15 @@ module Sequel
2290
2344
  _insert_values_sql(sql, values)
2291
2345
  end
2292
2346
 
2293
- def _merge_matched_sql(sql, data)
2347
+ def _merge_do_nothing_sql(sql, data)
2294
2348
  sql << " THEN DO NOTHING"
2295
2349
  end
2296
- alias _merge_not_matched_sql _merge_matched_sql
2350
+
2351
+ # Support MERGE RETURNING on PostgreSQL 17+.
2352
+ def _merge_when_sql(sql)
2353
+ super
2354
+ insert_returning_sql(sql) if uses_returning?(:merge)
2355
+ end
2297
2356
 
2298
2357
  # Format TRUNCATE statement with PostgreSQL specific options.
2299
2358
  def _truncate_sql(table)
@@ -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)
@@ -34,10 +34,7 @@ module Sequel
34
34
  uri = URI.parse(conn_string)
35
35
  scheme = uri.scheme
36
36
  c = adapter_class(scheme)
37
- uri_options = c.send(:uri_to_options, uri)
38
- uri.query.split('&').map{|s| s.split('=')}.each{|k,v| uri_options[k.to_sym] = v if k && !k.empty?} unless uri.query.to_s.strip.empty?
39
- uri_options.to_a.each{|k,v| uri_options[k] = URI::DEFAULT_PARSER.unescape(v) if v.is_a?(String)}
40
- opts = uri_options.merge(opts).merge!(:orig_opts=>opts.dup, :uri=>conn_string, :adapter=>scheme)
37
+ opts = c.send(:uri_to_options, uri).merge!(opts).merge!(:orig_opts=>opts.dup, :uri=>conn_string, :adapter=>scheme)
41
38
  end
42
39
  when Hash
43
40
  opts = conn_string.merge(opts)
@@ -270,28 +267,20 @@ module Sequel
270
267
  @single_threaded
271
268
  end
272
269
 
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
270
+ # Acquires a database connection, yielding it to the passed block. This is
271
+ # useful if you want to make sure the same connection is used for all
272
+ # database queries in the block. It is also useful if you want to gain
273
+ # direct access to the underlying connection object if you need to do
274
+ # something Sequel does not natively support.
275
+ #
276
+ # If a server option is given, acquires a connection for that specific
277
+ # server, instead of the :default server.
278
+ #
279
+ # DB.synchronize do |conn|
280
+ # # ...
281
+ # end
282
+ def synchronize(server=nil, &block)
283
+ @pool.hold(server || :default, &block)
295
284
  end
296
285
 
297
286
  # Attempts to acquire a database connection. Returns true if successful.
@@ -343,6 +332,11 @@ module Sequel
343
332
  else
344
333
  @opts.dup
345
334
  end
335
+
336
+ if pr = opts[:connect_opts_proc]
337
+ pr.call(opts)
338
+ end
339
+
346
340
  opts.delete(:servers)
347
341
  opts
348
342
  end
@@ -72,13 +72,16 @@ module Sequel
72
72
  # Converts a uri to an options hash. These options are then passed
73
73
  # to a newly created database object.
74
74
  def self.uri_to_options(uri)
75
- {
75
+ uri_options = {
76
76
  :user => uri.user,
77
77
  :password => uri.password,
78
78
  :port => uri.port,
79
79
  :host => uri.hostname,
80
80
  :database => (m = /\/(.*)/.match(uri.path)) && (m[1])
81
81
  }
82
+ uri.query.split('&').map{|s| s.split('=')}.each{|k,v| uri_options[k.to_sym] = v if k && !k.empty?} unless uri.query.to_s.strip.empty?
83
+ uri_options.to_a.each{|k,v| uri_options[k] = URI::DEFAULT_PARSER.unescape(v) if v.is_a?(String)}
84
+ uri_options
82
85
  end
83
86
  private_class_method :uri_to_options
84
87
 
@@ -108,6 +111,9 @@ module Sequel
108
111
  # :cache_schema :: Whether schema should be cached for this Database instance
109
112
  # :check_string_typecast_bytesize :: Whether to check the bytesize of strings before typecasting.
110
113
  # :connect_sqls :: An array of sql strings to execute on each new connection, after :after_connect runs.
114
+ # :connect_opts_proc :: Callable object for modifying options hash used when connecting, designed for
115
+ # cases where the option values (e.g. password) are automatically rotated on
116
+ # a regular basis without involvement from the application using Sequel.
111
117
  # :default_string_column_size :: The default size of string columns, 255 by default.
112
118
  # :extensions :: Extensions to load into this Database instance. Can be a symbol, array of symbols,
113
119
  # or string with extensions separated by columns. These extensions are loaded after
@@ -250,15 +256,27 @@ module Sequel
250
256
  Sequel.convert_output_timestamp(v, timezone)
251
257
  end
252
258
 
253
- # Returns a string representation of the database object including the
254
- # class name and connection URI and options used when connecting (if any).
259
+ # Returns a string representation of the Database object, including
260
+ # the database type, host, database, and user, if present.
255
261
  def inspect
256
- a = []
257
- a << uri.inspect if uri
258
- if (oo = opts[:orig_opts]) && !oo.empty?
259
- a << oo.inspect
262
+ s = String.new
263
+ s << "#<#{self.class}"
264
+ s << " database_type=#{database_type}" if database_type && database_type != adapter_scheme
265
+
266
+ keys = [:host, :database, :user]
267
+ opts = self.opts
268
+ if !keys.any?{|k| opts[k]} && opts[:uri]
269
+ opts = self.class.send(:uri_to_options, URI.parse(opts[:uri]))
260
270
  end
261
- "#<#{self.class}: #{a.join(' ')}>"
271
+
272
+ keys.each do |key|
273
+ val = opts[key]
274
+ if val && val != ''
275
+ s << " #{key}=#{val}"
276
+ end
277
+ end
278
+
279
+ s << ">"
262
280
  end
263
281
 
264
282
  # Proxy the literal call to the dataset.
@@ -775,7 +775,21 @@ module Sequel
775
775
 
776
776
  # SQL fragment for initial part of CREATE TABLE statement
777
777
  def create_table_prefix_sql(name, options)
778
- "CREATE #{temporary_table_sql if options[:temp]}TABLE#{' IF NOT EXISTS' if options[:if_not_exists]} #{options[:temp] ? quote_identifier(name) : quote_schema_table(name)}"
778
+ "CREATE #{temporary_table_sql if options[:temp]}TABLE#{' IF NOT EXISTS' if options[:if_not_exists]} #{create_table_table_name_sql(name, options)}"
779
+ end
780
+
781
+ # The SQL to use for a table name when creating a table.
782
+ # Use of the :temp option can result in different SQL,
783
+ # because the rules for temp table naming can differ
784
+ # between databases, and temp tables should not use the
785
+ # default_schema.
786
+ def create_table_table_name_sql(name, options)
787
+ options[:temp] ? create_table_temp_table_name_sql(name, options) : quote_schema_table(name)
788
+ end
789
+
790
+ # The SQL to use for the table name for a temporary table.
791
+ def create_table_temp_table_name_sql(name, _options)
792
+ name.is_a?(String) ? quote_identifier(name) : literal(name)
779
793
  end
780
794
 
781
795
  # SQL fragment for initial part of CREATE VIEW statement
@@ -901,18 +901,31 @@ module Sequel
901
901
  MERGE_TYPE_SQL = {
902
902
  :insert => ' WHEN NOT MATCHED',
903
903
  :delete => ' WHEN MATCHED',
904
+ :delete_not_matched_by_source => ' WHEN NOT MATCHED BY SOURCE',
904
905
  :update => ' WHEN MATCHED',
906
+ :update_not_matched_by_source => ' WHEN NOT MATCHED BY SOURCE',
905
907
  :matched => ' WHEN MATCHED',
906
908
  :not_matched => ' WHEN NOT MATCHED',
909
+ :not_matched_by_source => ' WHEN NOT MATCHED BY SOURCE',
907
910
  }.freeze
908
911
  private_constant :MERGE_TYPE_SQL
909
912
 
913
+ MERGE_NORMALIZE_TYPE_MAP = {
914
+ :delete_not_matched_by_source => :delete,
915
+ :update_not_matched_by_source => :update,
916
+ :matched => :do_nothing,
917
+ :not_matched => :do_nothing,
918
+ :not_matched_by_source => :do_nothing,
919
+ }.freeze
920
+ private_constant :MERGE_NORMALIZE_TYPE_MAP
921
+
910
922
  # Add the WHEN clauses to the MERGE SQL
911
923
  def _merge_when_sql(sql)
912
924
  raise Error, "no WHEN [NOT] MATCHED clauses provided for MERGE" unless merge_when = @opts[:merge_when]
913
925
  merge_when.each do |data|
914
926
  type = data[:type]
915
927
  sql << MERGE_TYPE_SQL[type]
928
+ type = MERGE_NORMALIZE_TYPE_MAP[type] || type
916
929
  _merge_when_conditions_sql(sql, data)
917
930
  send(:"_merge_#{type}_sql", sql, data)
918
931
  end
@@ -0,0 +1,48 @@
1
+ # frozen-string-literal: true
2
+ #
3
+ # The stdio_logger extension exposes a Sequel::StdioLogger class that
4
+ # can be used for logging with Sequel, as a minimal alternative to
5
+ # the logger library. It exposes debug/info/warn/error methods for the
6
+ # different warning levels. The debug method is a no-op, so that setting
7
+ # the Database sql_log_level to debug will result in no output for normal
8
+ # queries. The info/warn/error methods log the current time, log level,
9
+ # and the given message.
10
+ #
11
+ # To use this extension:
12
+ #
13
+ # Sequel.extension :stdio_logger
14
+ #
15
+ # Then you you can use Sequel::StdioLogger to wrap IO objects that you
16
+ # would like Sequel to log to:
17
+ #
18
+ # DB.loggers << Sequel::StdioLogger.new($stdout)
19
+ #
20
+ # log_file = File.open("db_queries.log", 'a')
21
+ # log_file.sync = true
22
+ # DB.loggers << Sequel::StdioLogger.new(log_file)
23
+ #
24
+ # This is implemented as a global extension instead of a Database extension
25
+ # because Database loggers must be set before Database extensions are loaded.
26
+ #
27
+ # Related module: Sequel::StdioLogger
28
+
29
+ #
30
+ module Sequel
31
+ class StdioLogger
32
+ def initialize(device)
33
+ @device = device
34
+ end
35
+
36
+ # Do not log debug messages. This is so setting the Database
37
+ # sql_log_level to debug will result in no output.
38
+ def debug(msg)
39
+ end
40
+
41
+ [:info, :warn, :error].each do |meth|
42
+ define_method(meth) do |msg|
43
+ @device.write("#{Time.now.strftime('%F %T')} #{meth.to_s.upcase}: #{msg}\n")
44
+ nil
45
+ end
46
+ end
47
+ end
48
+ end
@@ -54,6 +54,7 @@
54
54
  # * MySQL
55
55
  # * HSQLDB
56
56
  # * H2
57
+ # * SQLite 3.44+ (distinct only works when separator is ',')
57
58
  #
58
59
  # Related module: Sequel::SQL::StringAgg
59
60
 
@@ -94,6 +95,19 @@ module Sequel
94
95
  distinct = sa.is_distinct?
95
96
 
96
97
  case db_type = db.database_type
98
+ when :sqlite
99
+ raise Error, "string_agg(DISTINCT) is not supported with a non-comma separator on #{db.database_type}" if distinct && separator != ","
100
+
101
+ args = [expr]
102
+ args << separator unless distinct
103
+ f = Function.new(:group_concat, *args)
104
+ if order
105
+ f = f.order(*order)
106
+ end
107
+ if distinct
108
+ f = f.distinct
109
+ end
110
+ literal_append(sql, f)
97
111
  when :postgres, :sqlanywhere
98
112
  f = Function.new(db_type == :postgres ? :string_agg : :list, expr, separator)
99
113
  if order
@@ -151,7 +165,7 @@ module Sequel
151
165
  freeze
152
166
  end
153
167
 
154
- # Whether the current expression uses distinct expressions
168
+ # Whether the current expression uses distinct expressions
155
169
  def is_distinct?
156
170
  @distinct == true
157
171
  end
@@ -178,4 +192,3 @@ module Sequel
178
192
 
179
193
  Dataset.register_extension(:string_agg, SQL::StringAgg::DatasetMethods)
180
194
  end
181
-
@@ -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|
@@ -26,6 +26,12 @@ module Sequel
26
26
  # Album.default_values[:a] = lambda{Date.today}
27
27
  # Album.new.a # => Date.today
28
28
  #
29
+ # If the proc accepts a single argument, it is passed the instance, allowing
30
+ # default values to depend on instance-specific state:
31
+ #
32
+ # Album.default_values[:a] = lambda{|album| album.b + 1}
33
+ # Album.new(b: 10).a # => 11
34
+ #
29
35
  # By default, default values returned are not cached:
30
36
  #
31
37
  # Album.new.a.equal?(Album.new.a) # => false
@@ -43,13 +49,13 @@ module Sequel
43
49
  # album.values # => {}
44
50
  # album.a
45
51
  # album.values # => {:a => Date.today}
46
- #
52
+ #
47
53
  # Usage:
48
54
  #
49
55
  # # Make all model subclass instances set defaults (called before loading subclasses)
50
56
  # Sequel::Model.plugin :defaults_setter
51
57
  #
52
- # # Make the Album class set defaults
58
+ # # Make the Album class set defaults
53
59
  # Album.plugin :defaults_setter
54
60
  module DefaultsSetter
55
61
  # Set the default values based on the model schema. Options:
@@ -76,7 +82,7 @@ module Sequel
76
82
  def cache_default_values?
77
83
  @cache_default_values
78
84
  end
79
-
85
+
80
86
  # Freeze default values when freezing model class
81
87
  def freeze
82
88
  @default_values.freeze
@@ -133,7 +139,13 @@ module Sequel
133
139
  def [](k)
134
140
  if new? && !values.has_key?(k)
135
141
  v = model.default_values.fetch(k){return}
136
- v = v.call if v.respond_to?(:call)
142
+ if v.respond_to?(:call)
143
+ v = if v.respond_to?(:arity) && v.arity == 1
144
+ v.call(self)
145
+ else
146
+ v.call
147
+ end
148
+ end
137
149
  values[k] = v if model.cache_default_values?
138
150
  v
139
151
  else
@@ -45,6 +45,8 @@ module Sequel
45
45
  columns[lc] = lcv + 1
46
46
  super
47
47
  set_column_value("#{lc}=", lcv + 1)
48
+ changed_columns.delete(lc)
49
+ nil
48
50
  end
49
51
  end
50
52
  end
@@ -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 = 83
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.83.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-08-01 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bigdecimal
@@ -229,6 +229,8 @@ 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
233
+ - doc/release_notes/5.83.0.txt
232
234
  - doc/release_notes/5.9.0.txt
233
235
  files:
234
236
  - CHANGELOG
@@ -338,6 +340,8 @@ files:
338
340
  - doc/release_notes/5.8.0.txt
339
341
  - doc/release_notes/5.80.0.txt
340
342
  - doc/release_notes/5.81.0.txt
343
+ - doc/release_notes/5.82.0.txt
344
+ - doc/release_notes/5.83.0.txt
341
345
  - doc/release_notes/5.9.0.txt
342
346
  - doc/schema_modification.rdoc
343
347
  - doc/security.rdoc
@@ -517,6 +521,7 @@ files:
517
521
  - lib/sequel/extensions/sql_expr.rb
518
522
  - lib/sequel/extensions/sql_log_normalizer.rb
519
523
  - lib/sequel/extensions/sqlite_json_ops.rb
524
+ - lib/sequel/extensions/stdio_logger.rb
520
525
  - lib/sequel/extensions/string_agg.rb
521
526
  - lib/sequel/extensions/string_date_time.rb
522
527
  - lib/sequel/extensions/symbol_aref.rb
@@ -672,7 +677,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
672
677
  - !ruby/object:Gem::Version
673
678
  version: '0'
674
679
  requirements: []
675
- rubygems_version: 3.5.9
680
+ rubygems_version: 3.5.11
676
681
  signing_key:
677
682
  specification_version: 4
678
683
  summary: The Database Toolkit for Ruby