sequel 5.88.0 → 5.90.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.
Files changed (36) hide show
  1. checksums.yaml +4 -4
  2. data/lib/sequel/adapters/ibmdb.rb +1 -0
  3. data/lib/sequel/adapters/mysql2.rb +8 -1
  4. data/lib/sequel/adapters/shared/access.rb +1 -0
  5. data/lib/sequel/adapters/shared/mssql.rb +1 -0
  6. data/lib/sequel/adapters/shared/oracle.rb +1 -0
  7. data/lib/sequel/adapters/shared/postgres.rb +15 -4
  8. data/lib/sequel/core.rb +15 -0
  9. data/lib/sequel/database/dataset_defaults.rb +3 -3
  10. data/lib/sequel/database/misc.rb +5 -1
  11. data/lib/sequel/database/schema_generator.rb +8 -0
  12. data/lib/sequel/dataset/deprecated_singleton_class_methods.rb +1 -1
  13. data/lib/sequel/dataset/prepared_statements.rb +2 -1
  14. data/lib/sequel/dataset/query.rb +2 -2
  15. data/lib/sequel/extensions/connection_validator.rb +15 -10
  16. data/lib/sequel/extensions/pg_auto_parameterize_in_array.rb +9 -4
  17. data/lib/sequel/extensions/pg_enum.rb +3 -3
  18. data/lib/sequel/extensions/pg_row.rb +3 -1
  19. data/lib/sequel/extensions/query_blocker.rb +172 -0
  20. data/lib/sequel/extensions/virtual_row_method_block.rb +1 -0
  21. data/lib/sequel/model/associations.rb +19 -2
  22. data/lib/sequel/model/base.rb +8 -4
  23. data/lib/sequel/plugins/composition.rb +1 -1
  24. data/lib/sequel/plugins/enum.rb +1 -1
  25. data/lib/sequel/plugins/inverted_subsets.rb +1 -0
  26. data/lib/sequel/plugins/lazy_attributes.rb +1 -1
  27. data/lib/sequel/plugins/nested_attributes.rb +1 -1
  28. data/lib/sequel/plugins/pg_eager_any_typed_array.rb +95 -0
  29. data/lib/sequel/plugins/rcte_tree.rb +1 -1
  30. data/lib/sequel/plugins/serialization.rb +1 -1
  31. data/lib/sequel/plugins/sql_comments.rb +7 -2
  32. data/lib/sequel/plugins/subset_conditions.rb +1 -0
  33. data/lib/sequel/plugins/subset_static_cache.rb +2 -1
  34. data/lib/sequel/sql.rb +8 -1
  35. data/lib/sequel/version.rb +1 -1
  36. metadata +4 -2
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6a4a563fddfd5332195e8b9ba2588aef535f27ea0cee15f43afdc877910775ba
4
- data.tar.gz: 47cb743d96f031e4fa7e415708473158b2f564b3faa155335a4787e79bce34bd
3
+ metadata.gz: ba9973675e3b9e358ff96c334052ab6ba73e1db4c87c455163b60e367d0eb568
4
+ data.tar.gz: 86c4197ba9ced0d6bd3a789e5c20e4d0656bf5baca103f0b6db33e45b7981258
5
5
  SHA512:
6
- metadata.gz: b3dd656f73cdf525bda28a2cb5f136f91ae2935f48cfd94f8dc06f042b7f682c2faf48a48625b7845ae058b92189447433c152ab11eeebb8baae925d5371326d
7
- data.tar.gz: 84ae987e64b8c872351d259d88a1e8b63b7926e06f9bda7a0d71dda4826e4e17c83fdc5538bcde63cc0d16f77ffd75c57b504947a692898d552a77e1814a7c44
6
+ metadata.gz: 0247f84faceeb8a8c97d85bdefc3d0b781986e630981c163ca58bf7965f852a472d9e9501c5913ee1f4e7b02c005817c76ae8e71fcb0479fae252b27662e05f7
7
+ data.tar.gz: f90672de6e1ae901dca735988fa1466647bdc8f0cfeba75c1f27892e14e4eaf316b0141906039c9a33a70150b1860cda1f7a1972858ece6e3752614a91a60f2c
@@ -7,6 +7,7 @@ module Sequel
7
7
 
8
8
  module IBMDB
9
9
  tt = Class.new do
10
+ Sequel.set_temp_name(self){"Sequel::IBMDB::_TypeTranslator"}
10
11
  def boolean(s) !s.to_i.zero? end
11
12
  def int(s) s.to_i end
12
13
  end.new
@@ -97,7 +97,14 @@ module Sequel
97
97
  synchronize(opts[:server]) do |conn|
98
98
  stmt, ps_sql = conn.prepared_statements[ps_name]
99
99
  unless ps_sql == sql
100
- stmt.close if stmt
100
+ if stmt
101
+ begin
102
+ stmt.close
103
+ rescue ::Mysql2::Error
104
+ # probably Invalid statement handle, can happen from dropping
105
+ # related table, ignore as we won't be using it again.
106
+ end
107
+ end
101
108
  stmt = log_connection_yield("Preparing #{ps_name}: #{sql}", conn){conn.prepare(sql)}
102
109
  conn.prepared_statements[ps_name] = [stmt, sql]
103
110
  end
@@ -88,6 +88,7 @@ module Sequel
88
88
 
89
89
  module DatasetMethods
90
90
  include(Module.new do
91
+ Sequel.set_temp_name(self){"Sequel::Access::DatasetMethods::_SQLMethods"}
91
92
  Dataset.def_sql_method(self, :select, %w'select distinct limit columns into from join where group order having compounds')
92
93
  end)
93
94
  include EmulateOffsetWithReverseAndCount
@@ -574,6 +574,7 @@ module Sequel
574
574
 
575
575
  module DatasetMethods
576
576
  include(Module.new do
577
+ Sequel.set_temp_name(self){"Sequel::MSSQL::DatasetMethods::_SQLMethods"}
577
578
  Dataset.def_sql_method(self, :select, %w'with select distinct limit columns into from lock join where group having compounds order')
578
579
  end)
579
580
  include EmulateOffsetWithRowNumber
@@ -333,6 +333,7 @@ module Sequel
333
333
  BITAND_PROC = lambda{|a, b| Sequel.lit(["CAST(BITAND(", ", ", ") AS INTEGER)"], a, b)}
334
334
 
335
335
  include(Module.new do
336
+ Sequel.set_temp_name(self){"Sequel::Oracle::DatasetMethods::_SQLMethods"}
336
337
  Dataset.def_sql_method(self, :select, %w'with select distinct columns from join where group having compounds order limit lock')
337
338
  end)
338
339
 
@@ -1271,21 +1271,32 @@ module Sequel
1271
1271
 
1272
1272
  # Handle exclusion constraints.
1273
1273
  def constraint_definition_sql(constraint)
1274
- case constraint[:type]
1274
+ case type = constraint[:type]
1275
1275
  when :exclude
1276
1276
  elements = constraint[:elements].map{|c, op| "#{literal(c)} WITH #{op}"}.join(', ')
1277
1277
  sql = String.new
1278
1278
  sql << "#{"CONSTRAINT #{quote_identifier(constraint[:name])} " if constraint[:name]}EXCLUDE USING #{constraint[:using]||'gist'} (#{elements})#{" WHERE #{filter_expr(constraint[:where])}" if constraint[:where]}"
1279
1279
  constraint_deferrable_sql_append(sql, constraint[:deferrable])
1280
1280
  sql
1281
- when :foreign_key, :check
1281
+ when :primary_key, :unique
1282
+ if using_index = constraint[:using_index]
1283
+ sql = String.new
1284
+ sql << "CONSTRAINT #{quote_identifier(constraint[:name])} " if constraint[:name]
1285
+ if type == :primary_key
1286
+ sql << primary_key_constraint_sql_fragment(constraint)
1287
+ else
1288
+ sql << unique_constraint_sql_fragment(constraint)
1289
+ end
1290
+ sql << " USING INDEX " << quote_identifier(using_index)
1291
+ else
1292
+ super
1293
+ end
1294
+ else # when :foreign_key, :check
1282
1295
  sql = super
1283
1296
  if constraint[:not_valid]
1284
1297
  sql << " NOT VALID"
1285
1298
  end
1286
1299
  sql
1287
- else
1288
- super
1289
1300
  end
1290
1301
  end
1291
1302
 
data/lib/sequel/core.rb CHANGED
@@ -164,6 +164,21 @@ module Sequel
164
164
  JSON::ParserError
165
165
  end
166
166
 
167
+ if RUBY_VERSION >= '3.3'
168
+ # Create a new module using the block, and set the temporary name
169
+ # on it using the given a containing module and name.
170
+ def set_temp_name(mod)
171
+ mod.set_temporary_name(yield)
172
+ mod
173
+ end
174
+ # :nocov:
175
+ else
176
+ def set_temp_name(mod)
177
+ mod
178
+ end
179
+ end
180
+ # :nocov:
181
+
167
182
  # Convert given object to json and return the result.
168
183
  # This can be overridden to use an alternative json implementation.
169
184
  def object_to_json(obj, *args, &block)
@@ -17,7 +17,7 @@ module Sequel
17
17
  # as the dataset class.
18
18
  def dataset_class=(c)
19
19
  unless @dataset_modules.empty?
20
- c = Class.new(c)
20
+ c = Sequel.set_temp_name(Class.new(c)){"Sequel::Dataset::_Subclass"}
21
21
  @dataset_modules.each{|m| c.send(:include, m)}
22
22
  end
23
23
  @dataset_class = c
@@ -61,10 +61,10 @@ module Sequel
61
61
  # # SELECT id, name FROM table WHERE active ORDER BY id
62
62
  def extend_datasets(mod=nil, &block)
63
63
  raise(Error, "must provide either mod or block, not both") if mod && block
64
- mod = Dataset::DatasetModule.new(&block) if block
64
+ mod = Sequel.set_temp_name(Dataset::DatasetModule.new(&block)){"Sequel::Dataset::_DatasetModule(#{block.source_location.join(':')})"} if block
65
65
  if @dataset_modules.empty?
66
66
  @dataset_modules = [mod]
67
- @dataset_class = Class.new(@dataset_class)
67
+ @dataset_class = Sequel.set_temp_name(Class.new(@dataset_class)){"Sequel::Dataset::_Subclass"}
68
68
  else
69
69
  @dataset_modules << mod
70
70
  end
@@ -116,6 +116,10 @@ module Sequel
116
116
  # :after_connect :: A callable object called after each new connection is made, with the
117
117
  # connection object (and server argument if the callable accepts 2 arguments),
118
118
  # useful for customizations that you want to apply to all connections.
119
+ # :compare_connections_by_identity :: Whether to use compare_by_identity on hashes that use
120
+ # connection objects as keys. Defaults to true. This should only
121
+ # be set to false to work around bugs in libraries or
122
+ # ruby implementations.
119
123
  # :before_preconnect :: Callable that runs after extensions from :preconnect_extensions are loaded,
120
124
  # but before any connections are created.
121
125
  # :cache_schema :: Whether schema should be cached for this Database instance
@@ -165,7 +169,7 @@ module Sequel
165
169
  @schemas = {}
166
170
  @prepared_statements = {}
167
171
  @transactions = {}
168
- @transactions.compare_by_identity
172
+ @transactions.compare_by_identity if typecast_value_boolean(@opts.fetch(:compare_connections_by_identity, true))
169
173
  @symbol_literal_cache = {}
170
174
 
171
175
  @timezone = nil
@@ -430,6 +430,10 @@ module Sequel
430
430
  # add_unique_constraint(:name, name: :unique_name) # ADD CONSTRAINT unique_name UNIQUE (name)
431
431
  #
432
432
  # Supports the same :deferrable option as CreateTableGenerator#column.
433
+ #
434
+ # PostgreSQL specific options:
435
+ #
436
+ # :using_index :: Use the USING INDEX clause to specify an existing unique index
433
437
  def add_unique_constraint(columns, opts = OPTS)
434
438
  @operations << {:op => :add_constraint, :type => :unique, :columns => Array(columns)}.merge!(opts)
435
439
  nil
@@ -483,6 +487,10 @@ module Sequel
483
487
  #
484
488
  # add_primary_key(:id) # ADD COLUMN id serial PRIMARY KEY
485
489
  # add_primary_key([:artist_id, :name]) # ADD PRIMARY KEY (artist_id, name)
490
+ #
491
+ # PostgreSQL specific options:
492
+ #
493
+ # :using_index :: Use the USING INDEX clause to specify an existing unique index
486
494
  def add_primary_key(name, opts = OPTS)
487
495
  return add_composite_primary_key(name, opts) if name.is_a?(Array)
488
496
  opts = @db.serial_primary_key_options.merge(opts)
@@ -19,7 +19,7 @@ module Sequel
19
19
  def with_extend(*mods, &block)
20
20
  c = _clone(:freeze=>false)
21
21
  c.extend(*mods) unless mods.empty?
22
- c.extend(DatasetModule.new(&block)) if block
22
+ c.extend(Sequel.set_temp_name(DatasetModule.new(&block)){"Sequel::Dataset::_DatasetModule(#{block.source_location.join(':')})"}) if block
23
23
  c.freeze
24
24
  end
25
25
 
@@ -20,7 +20,8 @@ module Sequel
20
20
  def self.prepared_statements_module(code, mods, meths=DEFAULT_PREPARED_STATEMENT_MODULE_METHODS, &block)
21
21
  code = PREPARED_STATEMENT_MODULE_CODE[code] || code
22
22
 
23
- Module.new do
23
+ Module.new do
24
+ Sequel.set_temp_name(self){"Sequel::Dataset::_PreparedStatementsModule(#{block.source_location.join(':') if block})"}
24
25
  Array(mods).each do |mod|
25
26
  include mod
26
27
  end
@@ -1238,9 +1238,9 @@ module Sequel
1238
1238
  # Note that like Object#extend, when multiple modules are provided
1239
1239
  # as arguments the subclass includes the modules in reverse order.
1240
1240
  def with_extend(*mods, &block)
1241
- c = Class.new(self.class)
1241
+ c = Sequel.set_temp_name(Class.new(self.class)){"Sequel::Dataset::_Subclass"}
1242
1242
  c.include(*mods) unless mods.empty?
1243
- c.include(DatasetModule.new(&block)) if block
1243
+ c.include(Sequel.set_temp_name(DatasetModule.new(&block)){"Sequel::Dataset::_DatasetModule(#{block.source_location.join(':')})"}) if block
1244
1244
  o = c.freeze.allocate
1245
1245
  o.instance_variable_set(:@db, @db)
1246
1246
  o.instance_variable_set(:@opts, @opts)
@@ -105,18 +105,23 @@ module Sequel
105
105
  1.times do
106
106
  if (conn = super) &&
107
107
  (timer = sync{@connection_timestamps.delete(conn)}) &&
108
- Sequel.elapsed_seconds_since(timer) > @connection_validation_timeout &&
109
- !db.valid_connection?(conn)
108
+ Sequel.elapsed_seconds_since(timer) > @connection_validation_timeout
110
109
 
111
- case pool_type
112
- when :sharded_threaded, :sharded_timed_queue
113
- sync{@allocated[a.last].delete(Sequel.current)}
114
- else
115
- sync{@allocated.delete(Sequel.current)}
116
- end
110
+ begin
111
+ valid = db.valid_connection?(conn)
112
+ ensure
113
+ unless valid
114
+ case pool_type
115
+ when :sharded_threaded, :sharded_timed_queue
116
+ sync{@allocated[a.last].delete(Sequel.current)}
117
+ else
118
+ sync{@allocated.delete(Sequel.current)}
119
+ end
117
120
 
118
- disconnect_connection(conn)
119
- redo
121
+ disconnect_connection(conn)
122
+ redo if valid == false
123
+ end
124
+ end
120
125
  end
121
126
  end
122
127
 
@@ -27,9 +27,14 @@
27
27
  # treating strings as text can break programs, since the type for
28
28
  # literal strings in PostgreSQL is +unknown+, not +text+.
29
29
  #
30
- # The conversion is only done for single dimensional arrays that have more
31
- # than two elements, where all elements are of the same class (other than
32
- # nil values).
30
+ # The conversion is only done for single dimensional arrays that have two or
31
+ # more elements, where all elements are of the same class (other than
32
+ # +nil+ values). You can also do the conversion for arrays of 1 element by setting
33
+ # <tt>pg_auto_parameterize_min_array_size: 1</tt> Database option. This makes
34
+ # finding cases that need special handling easier, but it doesn't match
35
+ # how PostgreSQL internally converts the expression (PostgreSQL converts
36
+ # <tt>IN (single_value)</tt> to <tt>= single_value</tt>, not
37
+ # <tt>= ANY(ARRAY[single_value])</tt>).
33
38
  #
34
39
  # Related module: Sequel::Postgres::AutoParameterizeInArray
35
40
 
@@ -68,7 +73,7 @@ module Sequel
68
73
  # The bound variable type string to use for the bound variable array.
69
74
  # Returns nil if a bound variable should not be used for the array.
70
75
  def _bound_variable_type_for_array(r)
71
- return unless Array === r && r.size > 1
76
+ return unless Array === r && r.size >= (db.typecast_value(:integer, db.opts[:pg_auto_parameterize_min_array_size]) || 2)
72
77
  classes = r.map(&:class)
73
78
  classes.uniq!
74
79
  classes.delete(NilClass)
@@ -149,12 +149,12 @@ module Sequel
149
149
  from(:pg_type).
150
150
  where(:oid=>enum_labels.keys).
151
151
  exclude(:typarray=>0).
152
- select_map([:typname, Sequel.cast(:typarray, Integer).as(:v)])
152
+ select_map([:typname, Sequel.cast(:typarray, Integer).as(:v), Sequel.cast(:oid, Integer).as(:sv)])
153
153
 
154
154
  existing_oids = conversion_procs.keys
155
- array_types.each do |name, oid|
155
+ array_types.each do |name, oid, scalar_oid|
156
156
  next if existing_oids.include?(oid)
157
- register_array_type(name, :oid=>oid)
157
+ register_array_type(name, :oid=>oid, :scalar_oid=>scalar_oid)
158
158
  end
159
159
  end
160
160
 
@@ -113,6 +113,7 @@ module Sequel
113
113
  # automatically casted to the database type when literalizing.
114
114
  def self.subclass(db_type)
115
115
  Class.new(self) do
116
+ Sequel.set_temp_name(self){"Sequel::Postgres::PGRow::ArrayRow::_Subclass(#{db_type})"}
116
117
  @db_type = db_type
117
118
  end
118
119
  end
@@ -170,6 +171,7 @@ module Sequel
170
171
  # type and columns.
171
172
  def self.subclass(db_type, columns)
172
173
  Class.new(self) do
174
+ Sequel.set_temp_name(self){"Sequel::Postgres::PGRow::HashRow::_Subclass(#{db_type})"}
173
175
  @db_type = db_type
174
176
  @columns = columns
175
177
  end
@@ -391,7 +393,7 @@ module Sequel
391
393
  db.instance_exec do
392
394
  @row_types = {}
393
395
  @row_schema_types = {}
394
- extend(@row_type_method_module = Module.new)
396
+ extend(@row_type_method_module = Sequel.set_temp_name(Module.new){"Sequel::Postgres::PGRow::DatabaseMethods::_RowTypeMethodModule"})
395
397
  add_conversion_proc(2249, PGRow::Parser.new(:converter=>PGRow::ArrayRow))
396
398
  if respond_to?(:register_array_type)
397
399
  register_array_type('record', :oid=>2287, :scalar_oid=>2249)
@@ -0,0 +1,172 @@
1
+ # frozen-string-literal: true
2
+ #
3
+ # The query_blocker extension adds Database#block_queries.
4
+ # Inside the block passed to #block_queries, any attempts to
5
+ # execute a query/statement on the database will raise a
6
+ # Sequel::QueryBlocker::BlockedQuery exception.
7
+ #
8
+ # DB.extension :query_blocker
9
+ # DB.block_queries do
10
+ # ds = DB[:table] # No exception
11
+ # ds = ds.where(column: 1) # No exception
12
+ # ds.all # Exception raised
13
+ # end
14
+ #
15
+ # To handle concurrency, you can pass a :scope option:
16
+ #
17
+ # # Current Thread
18
+ # DB.block_queries(scope: :thread){}
19
+ #
20
+ # # Current Fiber
21
+ # DB.block_queries(scope: :fiber){}
22
+ #
23
+ # # Specific Thread
24
+ # DB.block_queries(scope: Thread.current){}
25
+ #
26
+ # # Specific Fiber
27
+ # DB.block_queries(scope: Fiber.current){}
28
+ #
29
+ # Database#block_queries is useful for blocking queries inside
30
+ # the block. However, there may be cases where you want to
31
+ # allow queries in specific places inside a block_queries block.
32
+ # You can use Database#allow_queries for that:
33
+ #
34
+ # DB.block_queries do
35
+ # DB.allow_queries do
36
+ # DB[:table].all # Query allowed
37
+ # end
38
+ #
39
+ # DB[:table].all # Exception raised
40
+ # end
41
+ #
42
+ # When mixing block_queries and allow_queries with scopes, the
43
+ # narrowest scope has priority. So if you are blocking with
44
+ # :thread scope, and allowing with :fiber scope, queries in the
45
+ # current fiber will be allowed, but queries in different fibers of
46
+ # the current thread will be blocked.
47
+ #
48
+ # Note that this should catch all queries executed through the
49
+ # Database instance. Whether it catches queries executed directly
50
+ # on a connection object depends on the adapter in use.
51
+ #
52
+ # Related module: Sequel::QueryBlocker
53
+
54
+ require "fiber"
55
+
56
+ #
57
+ module Sequel
58
+ module QueryBlocker
59
+ # Exception class raised if there is an attempt to execute a
60
+ # query/statement on the database inside a block passed to
61
+ # block_queries.
62
+ class BlockedQuery < Sequel::Error
63
+ end
64
+
65
+ def self.extended(db)
66
+ db.instance_exec do
67
+ @blocked_query_scopes ||= {}
68
+ end
69
+ end
70
+
71
+ # If checking a connection for validity, and a BlockedQuery exception is
72
+ # raised, treat it as a valid connection. You cannot check whether the
73
+ # connection is valid without issuing a query, and if queries are blocked,
74
+ # you need to assume it is valid or assume it is not. Since it most cases
75
+ # it will be valid, this assumes validity.
76
+ def valid_connection?(conn)
77
+ super
78
+ rescue BlockedQuery
79
+ true
80
+ end
81
+
82
+ # Check whether queries are blocked before executing them.
83
+ def log_connection_yield(sql, conn, args=nil)
84
+ # All database adapters should be calling this method around
85
+ # query execution (otherwise the queries would not get logged),
86
+ # ensuring the blocking is checked. Any database adapter issuing
87
+ # a query without calling this method is considered buggy.
88
+ check_blocked_queries!
89
+ super
90
+ end
91
+
92
+ # Whether queries are currently blocked.
93
+ def block_queries?
94
+ b = @blocked_query_scopes
95
+ b.fetch(Fiber.current) do
96
+ b.fetch(Thread.current) do
97
+ b.fetch(:global, false)
98
+ end
99
+ end
100
+ end
101
+
102
+ # Allow queries inside the block. Only useful if they are already blocked
103
+ # for the same scope. Useful for blocking queries generally, and only allowing
104
+ # them in specific places. Takes the same :scope option as #block_queries.
105
+ def allow_queries(opts=OPTS, &block)
106
+ _allow_or_block_queries(false, opts, &block)
107
+ end
108
+
109
+ # Reject (raise an BlockedQuery exception) if there is an attempt to execute
110
+ # a query/statement inside the block.
111
+ #
112
+ # The :scope option indicates which queries are rejected inside the block:
113
+ #
114
+ # :global :: This is the default, and rejects all queries.
115
+ # :thread :: Reject all queries in the current thread.
116
+ # :fiber :: Reject all queries in the current fiber.
117
+ # Thread :: Reject all queries in the given thread.
118
+ # Fiber :: Reject all queries in the given fiber.
119
+ def block_queries(opts=OPTS, &block)
120
+ _allow_or_block_queries(true, opts, &block)
121
+ end
122
+
123
+ private
124
+
125
+ # Internals of block_queries and allow_queries.
126
+ def _allow_or_block_queries(value, opts)
127
+ scope = query_blocker_scope(opts)
128
+ prev_value = nil
129
+ scopes = @blocked_query_scopes
130
+
131
+ begin
132
+ Sequel.synchronize do
133
+ prev_value = scopes[scope]
134
+ scopes[scope] = value
135
+ end
136
+
137
+ yield
138
+ ensure
139
+ Sequel.synchronize do
140
+ if prev_value.nil?
141
+ scopes.delete(scope)
142
+ else
143
+ scopes[scope] = prev_value
144
+ end
145
+ end
146
+ end
147
+ end
148
+
149
+ # The scope for the query block, either :global, or a Thread or Fiber instance.
150
+ def query_blocker_scope(opts)
151
+ case scope = opts[:scope]
152
+ when nil
153
+ :global
154
+ when :global, Thread, Fiber
155
+ scope
156
+ when :thread
157
+ Thread.current
158
+ when :fiber
159
+ Fiber.current
160
+ else
161
+ raise Sequel::Error, "invalid scope given to block_queries: #{scope.inspect}"
162
+ end
163
+ end
164
+
165
+ # Raise a BlockQuery exception if queries are currently blocked.
166
+ def check_blocked_queries!
167
+ raise BlockedQuery, "cannot execute query inside a block_queries block" if block_queries?
168
+ end
169
+ end
170
+
171
+ Database.register_extension(:query_blocker, QueryBlocker)
172
+ end
@@ -14,6 +14,7 @@ module Sequel
14
14
  module SQL
15
15
  class VirtualRow < BasicObject
16
16
  include(Module.new do
17
+ Sequel.set_temp_name(self){"Sequel::SQL:VirtualRow::_MethodBlockMethodMissing"}
17
18
  # Handle blocks passed to methods and change the behavior.
18
19
  def method_missing(m, *args, &block)
19
20
  if block
@@ -693,6 +693,9 @@ module Sequel
693
693
 
694
694
  # The predicate condition to use for the eager_loader.
695
695
  def eager_loading_predicate_condition(keys)
696
+ if transform = self[:eager_loading_predicate_transform]
697
+ keys = transform.call(keys, self)
698
+ end
696
699
  {predicate_key=>keys}
697
700
  end
698
701
 
@@ -759,7 +762,15 @@ module Sequel
759
762
  def placeholder_eager_loader
760
763
  cached_fetch(:placeholder_eager_loader) do
761
764
  eager_loading_dataset.placeholder_literalizer_loader do |pl, ds|
762
- apply_eager_limit_strategy(ds.where(predicate_key=>pl.arg), eager_limit_strategy)
765
+ arg = pl.arg
766
+
767
+ if transform = self[:eager_loading_predicate_transform]
768
+ arg = arg.transform do |v|
769
+ transform.call(v, self)
770
+ end
771
+ end
772
+
773
+ apply_eager_limit_strategy(ds.where(predicate_key=>arg), eager_limit_strategy)
763
774
  end
764
775
  end
765
776
  end
@@ -1707,6 +1718,9 @@ module Sequel
1707
1718
  # record should be populated.
1708
1719
  # :eager_loader_key :: A symbol for the key column to use to populate the key_hash
1709
1720
  # for the eager loader. Can be set to nil to not populate the key_hash.
1721
+ # :eager_loading_predicate_transform :: A callable object with which to transform the predicate key values used
1722
+ # when eager loading. Called with two arguments, the array of predicate key
1723
+ # values, and a the reflection for the association being eager loaded.
1710
1724
  # :extend :: A module or array of modules to extend the dataset with.
1711
1725
  # :filter_limit_strategy :: Determines the strategy used for enforcing limits and offsets when filtering by
1712
1726
  # limited associations. Possible options are :window_function, :distinct_on, or
@@ -1769,6 +1783,9 @@ module Sequel
1769
1783
  # Set to nil to not define a setter method for the association.
1770
1784
  # :subqueries_per_union :: The number of subqueries to use in each UNION query, for eager
1771
1785
  # loading limited associations using the default :union strategy.
1786
+ # :use_placeholder_loader :: Whether to use a placeholder loader when eager loading the
1787
+ # association. Can be set to false to disable the use of a placeholder
1788
+ # loader if one would be used by default.
1772
1789
  # :validate :: Set to false to not validate when implicitly saving any associated object.
1773
1790
  # === :many_to_one
1774
1791
  # :key :: foreign key in current model's table that references
@@ -1891,7 +1908,7 @@ module Sequel
1891
1908
  raise(Error, "cannot clone an association to an association of different type (association #{name} with type #{type} cloning #{opts[:clone]} with type #{cloned_assoc[:type]})")
1892
1909
  end
1893
1910
 
1894
- opts[:use_placeholder_loader] = !opts[:instance_specific] && !opts[:eager_graph]
1911
+ opts[:use_placeholder_loader] = !opts[:instance_specific] && !opts[:eager_graph] unless opts.include?(:use_placeholder_loader)
1895
1912
  opts[:eager_block] = opts[:block] unless opts.include?(:eager_block)
1896
1913
  opts[:graph_join_type] ||= :left_outer
1897
1914
  opts[:order_eager_graph] = true unless opts.include?(:order_eager_graph)
@@ -184,7 +184,7 @@ module Sequel
184
184
  end
185
185
  end
186
186
 
187
- klass = Class.new(self)
187
+ klass = Sequel.set_temp_name(Class.new(self)){"Sequel::_Model(#{source.inspect})"}
188
188
 
189
189
  if source.is_a?(::Sequel::Database)
190
190
  klass.db = source
@@ -768,7 +768,8 @@ module Sequel
768
768
  # default behavior.
769
769
  def dataset_methods_module
770
770
  return @dataset_methods_module if defined?(@dataset_methods_module)
771
- Sequel.synchronize{@dataset_methods_module ||= Module.new}
771
+ mod_name = "#{name}::@dataset_methods_module"
772
+ Sequel.synchronize{@dataset_methods_module ||= Sequel.set_temp_name(Module.new){mod_name}}
772
773
  extend(@dataset_methods_module)
773
774
  @dataset_methods_module
774
775
  end
@@ -956,7 +957,10 @@ END
956
957
  # Module that the class includes that holds methods the class adds for column accessors and
957
958
  # associations so that the methods can be overridden with +super+.
958
959
  def overridable_methods_module
959
- include(@overridable_methods_module = Module.new) unless @overridable_methods_module
960
+ return @overridable_methods_module if defined?(@overridable_methods_module)
961
+ mod_name = "#{name}::@overridable_methods_module"
962
+ Sequel.synchronize{@overridable_methods_module ||= Sequel.set_temp_name(Module.new){mod_name}}
963
+ include(@overridable_methods_module)
960
964
  @overridable_methods_module
961
965
  end
962
966
 
@@ -1610,7 +1614,7 @@ END
1610
1614
  @skip_validation_on_next_save = true
1611
1615
  end
1612
1616
 
1613
- # Returns (naked) dataset that should return only this instance.
1617
+ # Returns naked dataset that should return only the row related to this instance.
1614
1618
  #
1615
1619
  # Artist[1].this
1616
1620
  # # SELECT * FROM artists WHERE (id = 1) LIMIT 1
@@ -61,7 +61,7 @@ module Sequel
61
61
  def self.apply(model)
62
62
  model.instance_exec do
63
63
  @compositions = {}
64
- include(@composition_module ||= Module.new)
64
+ include(@composition_module ||= Sequel.set_temp_name(Module.new){"#{name}::@composition_module"})
65
65
  end
66
66
  end
67
67
 
@@ -80,7 +80,7 @@ module Sequel
80
80
  inverted = values.invert.freeze
81
81
 
82
82
  unless @enum_methods
83
- @enum_methods = Module.new
83
+ @enum_methods = Sequel.set_temp_name(Module.new){"#{name}::@enum_methods"}
84
84
  include @enum_methods
85
85
  end
86
86
 
@@ -32,6 +32,7 @@ module Sequel
32
32
  def self.apply(model, &block)
33
33
  model.instance_exec do
34
34
  @dataset_module_class = Class.new(@dataset_module_class) do
35
+ Sequel.set_temp_name(self){"#{model.name}::@dataset_module_class(InvertedSubsets)"}
35
36
  include DatasetModuleMethods
36
37
  if block
37
38
  define_method(:inverted_subset_name, &block)
@@ -64,7 +64,7 @@ module Sequel
64
64
  # :dataset :: The base dataset to use for the lazy attribute lookup
65
65
  # :table :: The table name to use to qualify the attribute and primary key columns.
66
66
  def define_lazy_attribute_getter(a, opts=OPTS)
67
- include(@lazy_attributes_module ||= Module.new) unless @lazy_attributes_module
67
+ include(@lazy_attributes_module ||= Sequel.set_temp_name(Module.new){"#{name}::@lazy_attributes_module"}) unless @lazy_attributes_module
68
68
  @lazy_attributes_module.class_eval do
69
69
  define_method(a) do
70
70
  if !values.has_key?(a) && !new?
@@ -129,7 +129,7 @@ module Sequel
129
129
  #
130
130
  # If a block is provided, it is used to set the :reject_if option.
131
131
  def nested_attributes(*associations, &block)
132
- include(@nested_attributes_module ||= Module.new) unless @nested_attributes_module
132
+ include(@nested_attributes_module ||= Sequel.set_temp_name(Module.new){"#{name}::@nested_attributes_module"}) unless @nested_attributes_module
133
133
  opts = associations.last.is_a?(Hash) ? associations.pop : OPTS
134
134
  reflections = associations.map{|a| association_reflection(a) || raise(Error, "no association named #{a} for #{self}")}
135
135
  reflections.each do |r|
@@ -0,0 +1,95 @@
1
+ # frozen-string-literal: true
2
+
3
+ module Sequel
4
+ module Plugins
5
+ # The pg_eager_any_typed_array plugin automatically converts
6
+ # the predicate expressions used for eager loading from:
7
+ #
8
+ # table.column IN (value_list)
9
+ #
10
+ # to:
11
+ #
12
+ # table.column = ANY(array_expr::type[])
13
+ #
14
+ # This makes it easier to use the pg_auto_parameterize_in_array
15
+ # extension with the :treat_string_list_as_text_array option,
16
+ # when using foreign keys with non-text database types that are represented
17
+ # by Ruby strings, such as enum and uuid types.
18
+ #
19
+ # Most association types that ship with Sequel have their predicate
20
+ # expressions converted by this plugin. Here are the exceptions:
21
+ #
22
+ # * associations using composite predicate keys
23
+ # * many_to_pg_array associations
24
+ # * many_to_many/one_through_one associations using :join_table_db option
25
+ # * many_through_many/one_through_many associations using
26
+ # :separate_table_per_query option
27
+ #
28
+ # To avoid predicate conversion for particular associations, set the
29
+ # :eager_loading_predicate_transform association option to nil/false.
30
+ #
31
+ # This plugin loads the pg_array extension into the model's Database.
32
+ module PgEagerAnyTypedArray
33
+ # Add the pg_array extension to the database
34
+ def self.apply(model)
35
+ model.db.extension(:pg_array)
36
+ end
37
+
38
+ module ClassMethods
39
+ TRANSFORM = proc do |values, ref|
40
+ type = ref.send(:cached_fetch, :_pg_eager_any_typed_array_type) do
41
+ key = ref.predicate_key
42
+ next if key.is_a?(Array)
43
+
44
+ while key.is_a?(SQL::QualifiedIdentifier)
45
+ key = key.column
46
+ end
47
+
48
+ # :nocov:
49
+ # many_to_pg_array association type does not need changes, as it
50
+ # already converts the values to a typed postgres array, it does
51
+ # not call the code that uses :eager_loading_predicate_transform.
52
+ #
53
+ # No association type that ships with Sequel can reach this code
54
+ # unless it is one of these types, but external association types
55
+ # could potentially reach it.
56
+ sch = case ref[:type]
57
+ # :nocov:
58
+ when :many_to_one, :one_to_one, :one_to_many, :pg_array_to_many
59
+ ref.associated_class.db_schema
60
+ when :many_to_many, :one_through_one
61
+ # Not compatible with the :join_table_db option, but that option
62
+ # does not call into this code.
63
+ Hash[ref.associated_class.db.schema(ref.join_table_source)]
64
+ when :many_through_many, :one_through_many
65
+ # Not compatible with the :separate_query_per_table option, but
66
+ # that option does not call into this code.
67
+ Hash[ref.associated_class.db.schema(ref[:through][0][:table])]
68
+ end
69
+
70
+ if sch && (sch = sch[key])
71
+ sch[:db_type]
72
+ end
73
+ end
74
+
75
+ if type
76
+ Sequel.function(:ANY, Sequel.pg_array(values, type))
77
+ else
78
+ values
79
+ end
80
+ end
81
+
82
+ # Set the :eager_loading_predicate_transform option if not already set
83
+ def associate(type, name, opts = OPTS, &block)
84
+ res = super
85
+
86
+ unless res.has_key?(:eager_loading_predicate_transform)
87
+ res[:eager_loading_predicate_transform] = TRANSFORM
88
+ end
89
+
90
+ res
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
@@ -81,7 +81,7 @@ module Sequel
81
81
 
82
82
  opts = opts.dup
83
83
  opts[:class] = model
84
- opts[:methods_module] = Module.new
84
+ opts[:methods_module] = Sequel.set_temp_name(Module.new){"#{model.name}::_rcte_tree[:methods_module]"}
85
85
  opts[:union_all] = opts[:union_all].nil? ? true : opts[:union_all]
86
86
  model.send(:include, opts[:methods_module])
87
87
 
@@ -146,7 +146,7 @@ module Sequel
146
146
  # Add serializated attribute acessor methods to the serialization_module
147
147
  def define_serialized_attribute_accessor(serializer, deserializer, *columns)
148
148
  m = self
149
- include(@serialization_module ||= Module.new) unless @serialization_module
149
+ include(@serialization_module ||= Sequel.set_temp_name(Module.new){"#{name}::@serialization_module"}) unless @serialization_module
150
150
  @serialization_module.class_eval do
151
151
  columns.each do |column|
152
152
  m.serialization_map[column] = serializer
@@ -40,7 +40,8 @@ module Sequel
40
40
  # Album.sql_comments_dataset_methods :to_csv # csv_serializer plugin
41
41
  #
42
42
  # In order for the sql_comments plugin to work, the sql_comments
43
- # Database extension must be loaded into the model's database.
43
+ # Database extension must be loaded into the model's database, so
44
+ # loading the plugin does this automatically.
44
45
  #
45
46
  # Note that in order to make sure SQL comments are included, some
46
47
  # optimizations are disabled if this plugin is loaded.
@@ -67,6 +68,10 @@ module Sequel
67
68
  # :nocov:
68
69
  end
69
70
 
71
+ def self.apply(model)
72
+ model.db.extension(:sql_comments)
73
+ end
74
+
70
75
  def self.configure(model)
71
76
  model.send(:reset_fast_pk_lookup_sql)
72
77
  end
@@ -85,7 +90,7 @@ module Sequel
85
90
  # Use automatic SQL comments for the given dataset methods.
86
91
  def sql_comments_dataset_methods(*meths)
87
92
  unless @_sql_comments_dataset_module
88
- dataset_module(@_sql_comments_dataset_module = Module.new)
93
+ dataset_module(@_sql_comments_dataset_module = Sequel.set_temp_name(Module.new){"#{name}::@_sql_comments_dataset_module"})
89
94
  end
90
95
  _sql_comments_methods(@_sql_comments_dataset_module, :dataset, meths)
91
96
  end
@@ -48,6 +48,7 @@ module Sequel
48
48
  def self.apply(model, &block)
49
49
  model.instance_exec do
50
50
  @dataset_module_class = Class.new(@dataset_module_class) do
51
+ Sequel.set_temp_name(self){"#{model.name}::@dataset_module_class(SubsetConditions)"}
51
52
  include DatasetModuleMethods
52
53
  end
53
54
  end
@@ -99,7 +99,8 @@ module Sequel
99
99
  # it before calling creating this module.
100
100
  dataset_methods_module
101
101
 
102
- Sequel.synchronize{@subset_static_cache_module ||= Module.new}
102
+ mod_name = "#{name}::@subset_static_cache_module"
103
+ Sequel.synchronize{@subset_static_cache_module ||= Sequel.set_temp_name(Module.new){mod_name}}
103
104
  extend(@subset_static_cache_module)
104
105
  @subset_static_cache_module
105
106
  end
data/lib/sequel/sql.rb CHANGED
@@ -1127,7 +1127,13 @@ module Sequel
1127
1127
  when DelayedEvaluation
1128
1128
  Sequel.delay{|ds| from_value_pair(l, r.call(ds))}
1129
1129
  when Dataset::PlaceholderLiteralizer::Argument
1130
- r.transform{|v| from_value_pair(l, v)}
1130
+ prev_transform = r.instance_variable_get(:@transformer)
1131
+ r.transform do |v|
1132
+ if prev_transform
1133
+ v = prev_transform.call(v)
1134
+ end
1135
+ from_value_pair(l, v)
1136
+ end
1131
1137
  else
1132
1138
  new(:'=', l, r)
1133
1139
  end
@@ -1915,6 +1921,7 @@ module Sequel
1915
1921
  end
1916
1922
 
1917
1923
  m = Module.new do
1924
+ Sequel.set_temp_name(Module.new){"Sequel::SQL::VirtualRow::_BaseMethodMissing"}
1918
1925
  # Return an +Identifier+, +QualifiedIdentifier+, or +Function+, depending
1919
1926
  # on arguments and whether a block is provided. Does not currently call the block.
1920
1927
  # See the class level documentation.
@@ -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 = 88
9
+ MINOR = 90
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,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: sequel
3
3
  version: !ruby/object:Gem::Version
4
- version: 5.88.0
4
+ version: 5.90.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeremy Evans
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-01-01 00:00:00.000000000 Z
10
+ date: 2025-03-01 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: bigdecimal
@@ -273,6 +273,7 @@ files:
273
273
  - lib/sequel/extensions/pretty_table.rb
274
274
  - lib/sequel/extensions/provenance.rb
275
275
  - lib/sequel/extensions/query.rb
276
+ - lib/sequel/extensions/query_blocker.rb
276
277
  - lib/sequel/extensions/round_timestamps.rb
277
278
  - lib/sequel/extensions/run_transaction_hooks.rb
278
279
  - lib/sequel/extensions/s.rb
@@ -370,6 +371,7 @@ files:
370
371
  - lib/sequel/plugins/paged_operations.rb
371
372
  - lib/sequel/plugins/pg_array_associations.rb
372
373
  - lib/sequel/plugins/pg_auto_constraint_validations.rb
374
+ - lib/sequel/plugins/pg_eager_any_typed_array.rb
373
375
  - lib/sequel/plugins/pg_row.rb
374
376
  - lib/sequel/plugins/pg_xmin_optimistic_locking.rb
375
377
  - lib/sequel/plugins/prepared_statements.rb