sequel 5.89.0 → 5.91.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8248e272e580f0e89e508e2aa97ebea83ac083b3f1bd347fc9d34700fd98ae42
4
- data.tar.gz: a74658ae42f7e087a541056d5f0cac2db4cc67b11ca56367b2bea309e502df49
3
+ metadata.gz: d13e0998a9eb78392a4f9f7793b2ceceebf87fa5a4dd6f9fcfd5bbd05a686d83
4
+ data.tar.gz: d4c7da82fb811928c58a096b2d5fc500cdb7cee1bbff14795817b5bd3f4d3360
5
5
  SHA512:
6
- metadata.gz: c2875a0f53a46efa87b3380b3fe2c1bbae46542076e3e5615ddcd53b55ac6dfefa53fc828ec4f1277df85a7852eb0e2934377d074e58a6410837138ed2e2a7db
7
- data.tar.gz: 8c1a3591085be13aef51f3bc63f0407249858c57e91c3f314b92d929d362c4aafd0b801ee52ca0f96acb18f6fa133d33c4532b5a0fc13fc61e599ba3e05f4e6c
6
+ metadata.gz: c2bbfc37fbd2b97796c96352a0f1d7d41053f91f4b61e3888a5aea712b9a59c7600e8ed58a15919cef7d8b0cc1c35e4bc3d142206ac245ed6f4f13575188f3b9
7
+ data.tar.gz: 8f4c0b7b95ec967c471455df69e8b5b5ff9c2104790c7c18abaa9601f2e7c4514952d4410aac1e3f720304f30af2d7299ea0f1233a7e083bcb901c8a03f697bb
@@ -179,7 +179,7 @@ module Sequel
179
179
 
180
180
  def adapter_initialize
181
181
  case @opts[:conn_string]
182
- when /Microsoft\.(Jet|ACE)\.OLEDB/io
182
+ when /Microsoft\.(Jet|ACE)\.OLEDB/i
183
183
  require_relative 'ado/access'
184
184
  extend Sequel::ADO::Access::DatabaseMethods
185
185
  self.dataset_class = ADO::Access::Dataset
@@ -636,11 +636,11 @@ module Sequel
636
636
  def schema_column_set_db_type(schema)
637
637
  case schema[:type]
638
638
  when :string
639
- if schema[:db_type] =~ /\A(character( varying)?|n?(var)?char2?)\z/io && schema[:column_size] > 0
639
+ if schema[:db_type] =~ /\A(character( varying)?|n?(var)?char2?)\z/i && schema[:column_size] > 0
640
640
  schema[:db_type] += "(#{schema[:column_size]})"
641
641
  end
642
642
  when :decimal
643
- if schema[:db_type] =~ /\A(decimal|numeric)\z/io && schema[:column_size] > 0 && schema[:scale] >= 0
643
+ if schema[:db_type] =~ /\A(decimal|numeric)\z/i && schema[:column_size] > 0 && schema[:scale] >= 0
644
644
  schema[:db_type] += "(#{schema[:column_size]}, #{schema[:scale]})"
645
645
  end
646
646
  end
@@ -480,11 +480,11 @@ module Sequel
480
480
 
481
481
  def schema_column_type(db_type)
482
482
  case db_type
483
- when /\A(?:bit)\z/io
483
+ when /\A(?:bit)\z/i
484
484
  :boolean
485
- when /\A(?:(?:small)?money)\z/io
485
+ when /\A(?:(?:small)?money)\z/i
486
486
  :decimal
487
- when /\A(timestamp|rowversion)\z/io
487
+ when /\A(timestamp|rowversion)\z/i
488
488
  :blob
489
489
  else
490
490
  super
@@ -550,11 +550,11 @@ module Sequel
550
550
 
551
551
  def schema_column_type(db_type)
552
552
  case db_type
553
- when /\Aset/io
553
+ when /\Aset/i
554
554
  :set
555
- when /\Amediumint/io
555
+ when /\Amediumint/i
556
556
  :integer
557
- when /\Amediumtext/io
557
+ when /\Amediumtext/i
558
558
  :string
559
559
  else
560
560
  super
@@ -1663,9 +1663,9 @@ module Sequel
1663
1663
  # Handle interval and citext types.
1664
1664
  def schema_column_type(db_type)
1665
1665
  case db_type
1666
- when /\Ainterval\z/io
1666
+ when /\Ainterval\z/i
1667
1667
  :interval
1668
- when /\Acitext\z/io
1668
+ when /\Acitext\z/i
1669
1669
  :string
1670
1670
  else
1671
1671
  super
@@ -170,7 +170,7 @@ module Sequel
170
170
  c[:ruby_default] = column_schema_to_ruby_default(c[:default], c[:type]) unless c.has_key?(:ruby_default)
171
171
  if c[:primary_key] && !auto_increment_set
172
172
  # If adapter didn't set it, assume that integer primary keys are auto incrementing
173
- c[:auto_increment] = primary_keys == 1 && !!(c[:db_type] =~ /int/io)
173
+ c[:auto_increment] = primary_keys == 1 && !!(c[:db_type] =~ /int/i)
174
174
  end
175
175
  if !c[:max_length] && c[:type] == :string && (max_length = column_schema_max_length(c[:db_type]))
176
176
  c[:max_length] = max_length
@@ -390,25 +390,25 @@ module Sequel
390
390
  # such as :integer or :string.
391
391
  def schema_column_type(db_type)
392
392
  case db_type
393
- when /\A(character( varying)?|n?(var)?char|n?text|string|clob)/io
393
+ when /\A(character( varying)?|n?(var)?char|n?text|string|clob)/i
394
394
  :string
395
- when /\A(int(eger)?|(big|small|tiny)int)/io
395
+ when /\A(int(eger)?|(big|small|tiny)int)/i
396
396
  :integer
397
- when /\Adate\z/io
397
+ when /\Adate\z/i
398
398
  :date
399
- when /\A((small)?datetime|timestamp(\(\d\))?( with(out)? time zone)?)\z/io
399
+ when /\A((small)?datetime(\(\d\))?|timestamp(\(\d\))?( with(out)? time zone)?)\z/i
400
400
  :datetime
401
- when /\Atime( with(out)? time zone)?\z/io
401
+ when /\Atime( with(out)? time zone)?\z/i
402
402
  :time
403
- when /\A(bool(ean)?)\z/io
403
+ when /\A(bool(ean)?)\z/i
404
404
  :boolean
405
- when /\A(real|float( unsigned)?|double( precision)?|double\(\d+,\d+\)( unsigned)?)\z/io
405
+ when /\A(real|float( unsigned)?|double( precision)?|double\(\d+,\d+\)( unsigned)?)\z/i
406
406
  :float
407
- when /\A(?:(?:(?:num(?:ber|eric)?|decimal)(?:\(\d+,\s*(-?\d+|false|true)\))?))\z/io
407
+ when /\A(?:(?:(?:num(?:ber|eric)?|decimal)(?:\(\d+,\s*(-?\d+|false|true)\))?))\z/i
408
408
  $1 && ['0', 'false'].include?($1) ? :integer : :decimal
409
- when /bytea|blob|image|(var)?binary/io
409
+ when /bytea|blob|image|(var)?binary/i
410
410
  :blob
411
- when /\Aenum/io
411
+ when /\Aenum/i
412
412
  :enum
413
413
  end
414
414
  end
@@ -119,7 +119,7 @@ module Sequel
119
119
  end
120
120
 
121
121
  disconnect_connection(conn)
122
- redo
122
+ redo if valid == false
123
123
  end
124
124
  end
125
125
  end
@@ -21,15 +21,26 @@
21
21
  # DateTime :: timestamp (or timestamptz if pg_timestamptz extension is used)
22
22
  # Sequel::SQLTime :: time
23
23
  # Sequel::SQL::Blob :: bytea
24
+ #
25
+ # Arrays of string values are not automatically converted by default, because the Ruby
26
+ # String class can represent a number of different database types. To convert
27
+ # arrays of Ruby strings to an untyped array (a query parameter with no explicit
28
+ # type cast), set the +:treat_string_list_as_untyped_array+ Database option
29
+ # before loading the extension.
24
30
  #
25
- # String values are also supported using the +text+ type, but only if the
26
- # +:treat_string_list_as_text_array+ Database option is used. This is because
27
- # treating strings as text can break programs, since the type for
28
- # literal strings in PostgreSQL is +unknown+, not +text+.
31
+ # If you will only be using arrays of Ruby strings that represent the +text+ type,
32
+ # you can use the +:treat_string_list_as_text_array+ Database option is used. This
33
+ # can break programs, since the type for literal strings in PostgreSQL is +unknown+,
34
+ # not +text+.
29
35
  #
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).
36
+ # The conversion is only done for single dimensional arrays that have two or
37
+ # more elements, where all elements are of the same class (other than
38
+ # +nil+ values). You can also do the conversion for arrays of 1 element by setting
39
+ # <tt>pg_auto_parameterize_min_array_size: 1</tt> Database option. This makes
40
+ # finding cases that need special handling easier, but it doesn't match
41
+ # how PostgreSQL internally converts the expression (PostgreSQL converts
42
+ # <tt>IN (single_value)</tt> to <tt>= single_value</tt>, not
43
+ # <tt>= ANY(ARRAY[single_value])</tt>).
33
44
  #
34
45
  # Related module: Sequel::Postgres::AutoParameterizeInArray
35
46
 
@@ -37,6 +48,47 @@ module Sequel
37
48
  module Postgres
38
49
  # Enable automatically parameterizing queries.
39
50
  module AutoParameterizeInArray
51
+ module TreatStringListAsUntypedArray
52
+ # Sentinal value to use as an auto param type to use auto parameterization
53
+ # of a string array without an explicit type cast.
54
+ NO_EXPLICIT_CAST = Object.new.freeze
55
+
56
+ # Wrapper for untyped PGArray values that will be parameterized directly
57
+ # into the query. This should only be used in cases where you know the
58
+ # value should be added as a query parameter.
59
+ class ParameterizedUntypedPGArray < SQL::Wrapper
60
+ def to_s_append(ds, sql)
61
+ sql.add_arg(@value)
62
+ end
63
+ end
64
+
65
+ private
66
+
67
+ # Recognize NO_EXPLICIT_CAST sentinal value and use wrapped
68
+ # PGArray that will be parameterized into the query.
69
+ def _convert_array_to_pg_array_with_type(r, type)
70
+ if NO_EXPLICIT_CAST.equal?(type)
71
+ ParameterizedUntypedPGArray.new(Sequel.pg_array(r))
72
+ else
73
+ super
74
+ end
75
+ end
76
+
77
+ # Use a query parameter with no type cast for string arrays.
78
+ def _bound_variable_type_for_string_array(r)
79
+ NO_EXPLICIT_CAST
80
+ end
81
+ end
82
+
83
+ module TreatStringListAsTextArray
84
+ private
85
+
86
+ # Assume all string arrays used on RHS of IN/NOT IN are for type text[]
87
+ def _bound_variable_type_for_string_array(r)
88
+ "text"
89
+ end
90
+ end
91
+
40
92
  # Transform column IN (...) expressions into column = ANY($)
41
93
  # and column NOT IN (...) expressions into column != ALL($)
42
94
  # using an array bound variable for the ANY/ALL argument,
@@ -56,7 +108,7 @@ module Sequel
56
108
  op = :!=
57
109
  func = :ALL
58
110
  end
59
- args = [l, Sequel.function(func, Sequel.pg_array(r, type))]
111
+ args = [l, Sequel.function(func, _convert_array_to_pg_array_with_type(r, type))]
60
112
  end
61
113
  end
62
114
 
@@ -68,7 +120,7 @@ module Sequel
68
120
  # The bound variable type string to use for the bound variable array.
69
121
  # Returns nil if a bound variable should not be used for the array.
70
122
  def _bound_variable_type_for_array(r)
71
- return unless Array === r && r.size > 1
123
+ return unless Array === r && r.size >= pg_auto_parameterize_min_array_size
72
124
  classes = r.map(&:class)
73
125
  classes.uniq!
74
126
  classes.delete(NilClass)
@@ -81,7 +133,7 @@ module Sequel
81
133
  # arrays natively (though the SQL used is different)
82
134
  "int8"
83
135
  elsif klass == String
84
- "text" if db.typecast_value(:boolean, db.opts[:treat_string_list_as_text_array])
136
+ _bound_variable_type_for_string_array(r)
85
137
  elsif klass == BigDecimal
86
138
  "numeric"
87
139
  elsif klass == Date
@@ -100,11 +152,42 @@ module Sequel
100
152
  "bytea"
101
153
  end
102
154
  end
155
+
156
+ # Do not auto parameterize string arrays by default.
157
+ def _bound_variable_type_for_string_array(r)
158
+ nil
159
+ end
160
+
161
+ # The minimium size of array to auto parameterize.
162
+ def pg_auto_parameterize_min_array_size
163
+ 2
164
+ end
165
+
166
+ # Convert RHS of IN/NOT IN operator to PGArray with given type.
167
+ def _convert_array_to_pg_array_with_type(r, type)
168
+ Sequel.pg_array(r, type)
169
+ end
103
170
  end
104
171
  end
105
172
 
106
173
  Database.register_extension(:pg_auto_parameterize_in_array) do |db|
107
174
  db.extension(:pg_array, :pg_auto_parameterize)
108
175
  db.extend_datasets(Postgres::AutoParameterizeInArray)
176
+
177
+ if db.typecast_value(:boolean, db.opts[:treat_string_list_as_text_array])
178
+ db.extend_datasets(Postgres::AutoParameterizeInArray::TreatStringListAsTextArray)
179
+ elsif db.typecast_value(:boolean, db.opts[:treat_string_list_as_untyped_array])
180
+ db.extend_datasets(Postgres::AutoParameterizeInArray::TreatStringListAsUntypedArray)
181
+ end
182
+
183
+ if min_array_size = db.opts[:pg_auto_parameterize_min_array_size]
184
+ min_array_size = db.typecast_value(:integer, min_array_size)
185
+ mod = Module.new do
186
+ define_method(:pg_auto_parameterize_min_array_size){min_array_size}
187
+ private :pg_auto_parameterize_min_array_size
188
+ end
189
+ Sequel.set_temp_name(mod){"Sequel::Postgres::AutoParameterizeInArray::_MinArraySize#{min_array_size}"}
190
+ db.extend_datasets(mod)
191
+ end
109
192
  end
110
193
  end
@@ -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
 
@@ -257,7 +257,7 @@ END_MIG
257
257
  gen.foreign_key(name, table, col_opts)
258
258
  else
259
259
  gen.column(name, type, col_opts)
260
- if [Integer, :Bignum, Float, BigDecimal].include?(type) && schema[:db_type] =~ / unsigned\z/io
260
+ if [Integer, :Bignum, Float, BigDecimal].include?(type) && schema[:db_type] =~ / unsigned\z/i
261
261
  gen.check(Sequel::SQL::Identifier.new(name) >= 0)
262
262
  end
263
263
  end
@@ -414,6 +414,12 @@ module Sequel
414
414
  false
415
415
  end
416
416
 
417
+ # Hash value for the association reflection. This is precomputed to avoid
418
+ # concurrency issues at runtime.
419
+ def hash
420
+ self[:_hash]
421
+ end
422
+
417
423
  # Initialize the associations cache for the current association for the given objects.
418
424
  def initialize_association_cache(objects)
419
425
  name = self[:name]
@@ -693,6 +699,9 @@ module Sequel
693
699
 
694
700
  # The predicate condition to use for the eager_loader.
695
701
  def eager_loading_predicate_condition(keys)
702
+ if transform = self[:eager_loading_predicate_transform]
703
+ keys = transform.call(keys, self)
704
+ end
696
705
  {predicate_key=>keys}
697
706
  end
698
707
 
@@ -759,7 +768,15 @@ module Sequel
759
768
  def placeholder_eager_loader
760
769
  cached_fetch(:placeholder_eager_loader) do
761
770
  eager_loading_dataset.placeholder_literalizer_loader do |pl, ds|
762
- apply_eager_limit_strategy(ds.where(predicate_key=>pl.arg), eager_limit_strategy)
771
+ arg = pl.arg
772
+
773
+ if transform = self[:eager_loading_predicate_transform]
774
+ arg = arg.transform do |v|
775
+ transform.call(v, self)
776
+ end
777
+ end
778
+
779
+ apply_eager_limit_strategy(ds.where(predicate_key=>arg), eager_limit_strategy)
763
780
  end
764
781
  end
765
782
  end
@@ -1707,6 +1724,9 @@ module Sequel
1707
1724
  # record should be populated.
1708
1725
  # :eager_loader_key :: A symbol for the key column to use to populate the key_hash
1709
1726
  # for the eager loader. Can be set to nil to not populate the key_hash.
1727
+ # :eager_loading_predicate_transform :: A callable object with which to transform the predicate key values used
1728
+ # when eager loading. Called with two arguments, the array of predicate key
1729
+ # values, and a the reflection for the association being eager loaded.
1710
1730
  # :extend :: A module or array of modules to extend the dataset with.
1711
1731
  # :filter_limit_strategy :: Determines the strategy used for enforcing limits and offsets when filtering by
1712
1732
  # limited associations. Possible options are :window_function, :distinct_on, or
@@ -1769,6 +1789,9 @@ module Sequel
1769
1789
  # Set to nil to not define a setter method for the association.
1770
1790
  # :subqueries_per_union :: The number of subqueries to use in each UNION query, for eager
1771
1791
  # loading limited associations using the default :union strategy.
1792
+ # :use_placeholder_loader :: Whether to use a placeholder loader when eager loading the
1793
+ # association. Can be set to false to disable the use of a placeholder
1794
+ # loader if one would be used by default.
1772
1795
  # :validate :: Set to false to not validate when implicitly saving any associated object.
1773
1796
  # === :many_to_one
1774
1797
  # :key :: foreign key in current model's table that references
@@ -1891,7 +1914,7 @@ module Sequel
1891
1914
  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
1915
  end
1893
1916
 
1894
- opts[:use_placeholder_loader] = !opts[:instance_specific] && !opts[:eager_graph]
1917
+ opts[:use_placeholder_loader] = !opts[:instance_specific] && !opts[:eager_graph] unless opts.include?(:use_placeholder_loader)
1895
1918
  opts[:eager_block] = opts[:block] unless opts.include?(:eager_block)
1896
1919
  opts[:graph_join_type] ||= :left_outer
1897
1920
  opts[:order_eager_graph] = true unless opts.include?(:order_eager_graph)
@@ -1914,6 +1937,8 @@ module Sequel
1914
1937
  # Remove :class entry if it exists and is nil, to work with cached_fetch
1915
1938
  opts.delete(:class) unless opts[:class]
1916
1939
 
1940
+ opts[:_hash] = [self, name].hash
1941
+
1917
1942
  def_association(opts)
1918
1943
 
1919
1944
  orig_opts.delete(:clone)
@@ -3591,7 +3616,7 @@ module Sequel
3591
3616
 
3592
3617
  # Prepare a hash loaders and eager options which will be used to implement the eager loading.
3593
3618
  def prepare_eager_load(a, reflections, eager_assoc)
3594
- eager_load_data = {}
3619
+ eager_load_data = {}.compare_by_identity
3595
3620
 
3596
3621
  # Key is foreign/primary key name symbol.
3597
3622
  # Value is hash with keys being foreign/primary key values (generally integers)
@@ -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 ||= Sequel.set_temp_name(Module.new){"#{name}::@dataset_methods_module"}}
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 = Sequel.set_temp_name(Module.new){"#{name}::@overridable_methods_module"}) 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
@@ -39,6 +39,9 @@ module Sequel
39
39
  #
40
40
  # Album.first.artist # no error
41
41
  #
42
+ # This behavior of enabling +forbid_lazy_load+ automatically from dataset
43
+ # methods can be disabled using the plugin's +:allow_by_default+ option.
44
+ #
42
45
  # You can allow lazy loading associations for an instance that it
43
46
  # was previously forbidden for:
44
47
  #
@@ -98,7 +101,17 @@ module Sequel
98
101
  #
99
102
  # # Make the Album class support forbidding lazy load
100
103
  # Album.plugin :forbid_lazy_load
104
+ #
105
+ # # Let lazy loading be forbidden by object, but not automatically for any
106
+ # # object loaded via dataset.
107
+ # Album.plugin :forbid_lazy_load, allow_by_default: true
101
108
  module ForbidLazyLoad
109
+ def self.apply(model, opts=OPTS)
110
+ unless opts[:allow_by_default]
111
+ model.send(:dataset_extend, ForbidByDefault, :create_class_methods=>false)
112
+ end
113
+ end
114
+
102
115
  # Error raised when attempting to lazy load an association when
103
116
  # lazy loading has been forbidden.
104
117
  class Error < StandardError
@@ -179,7 +192,7 @@ module Sequel
179
192
  end
180
193
  end
181
194
 
182
- module DatasetMethods
195
+ module ForbidByDefault
183
196
  # Mark model instances retrieved in this call as forbidding lazy loading.
184
197
  def each
185
198
  if row_proc
@@ -188,11 +188,10 @@ module Sequel
188
188
 
189
189
  # Create a new associated object with the given attributes, validate
190
190
  # it when the parent is validated, and save it when the object is saved.
191
- # Returns the object created.
191
+ # Returns the new object.
192
192
  def nested_attributes_create(meta, attributes)
193
+ obj = nested_attributes_new(meta, attributes)
193
194
  reflection = meta[:reflection]
194
- obj = reflection.associated_class.new
195
- nested_attributes_set_attributes(meta, obj, attributes)
196
195
  delay_validate_associated_object(reflection, obj)
197
196
  if reflection.returns_array?
198
197
  public_send(reflection[:name]) << obj
@@ -254,7 +253,13 @@ module Sequel
254
253
  end
255
254
  obj
256
255
  end
257
-
256
+
257
+ # Returns a new object of the associated class with the given attributes set.
258
+ def nested_attributes_new(meta, attributes)
259
+ obj = meta[:reflection].associated_class.new
260
+ nested_attributes_set_attributes(meta, obj, attributes)
261
+ end
262
+
258
263
  # Set the fields in the obj based on the association, only allowing
259
264
  # specific :fields if configured.
260
265
  def nested_attributes_set_attributes(meta, obj, attributes)
@@ -135,7 +135,12 @@ module Sequel
135
135
  raise Error, "No pg_auto_constraint_validations setup" unless file = @pg_auto_constraint_validations_cache_file
136
136
  pg_auto_constraint_validations_cache = {}
137
137
  @pg_auto_constraint_validations_cache.sort.each do |k, v|
138
- pg_auto_constraint_validations_cache[k] = v
138
+ h = {}
139
+ v.each do |k, entry|
140
+ entry = Hash[entry.sort] if entry.is_a?(Hash)
141
+ h[k] = entry
142
+ end
143
+ pg_auto_constraint_validations_cache[k] = h
139
144
  end
140
145
  File.open(file, 'wb'){|f| f.write(Marshal.dump(pg_auto_constraint_validations_cache))}
141
146
  nil
@@ -0,0 +1,88 @@
1
+ # frozen-string-literal: true
2
+
3
+ module Sequel
4
+ module Plugins
5
+ # The pg_auto_validate_enums plugin implements automatic validations for
6
+ # enum columns, ensuring that enum columns have a valid value. With this
7
+ # plugin, trying to save with an invalid enum value results in
8
+ # Sequel::ValidationFailed before saving, instead of Sequel::DatabaseError
9
+ # (wrapping PG::InvalidTextRepresentation or similar exception) during saving.
10
+ #
11
+ # class Person < Sequel::Model
12
+ # # assume state enum column with allowed values active and inactive
13
+ # plugin :pg_auto_validate_enums
14
+ # end
15
+ # p = Person.new(state: "active").valid? # => true
16
+ # p = Person.new(state: "inactive").valid? # => true
17
+ # p = Person.new(state: "other").valid? # => false
18
+ #
19
+ # While you can load this into individual model classes, typical use would
20
+ # be to load it into Sequel::Model or the appropriate model base class,
21
+ # and have all models that inherit from that class automatically pick it up.
22
+ #
23
+ # This plugin depends on the validation_helpers plugin.
24
+ module PgAutoValidateEnums
25
+ # Load the validation_helpers plugin.
26
+ def self.apply(model, opts=OPTS)
27
+ model.plugin(:validation_helpers)
28
+ end
29
+
30
+ # Load the pg_enum extension into the database, and reload the schema
31
+ # if it is already loaded. The opts given are used for the validates_includes
32
+ # validations (with allow_nil: true and from: :values enabled by default,
33
+ # to avoid issues with nullable enum columns and cases where the column
34
+ # method has been overridden.
35
+ def self.configure(model, opts=OPTS)
36
+ model.instance_exec do
37
+ db.extension(:pg_enum) unless @db.instance_variable_get(:@enum_labels)
38
+ if @db_schema
39
+ get_db_schema(true)
40
+ _get_pg_pg_auto_validate_enums_metadata
41
+ end
42
+ @pg_auto_validate_enums_opts = {allow_nil: true, from: :values}.merge!(opts).freeze
43
+ end
44
+ end
45
+
46
+ module ClassMethods
47
+ # Hash with enum column symbol values and arrays of valid string values.
48
+ attr_reader :pg_auto_validate_enums_metadata
49
+
50
+ # Options to pass to the validates_includes calls used by the plugin.
51
+ attr_reader :pg_auto_validate_enums_opts
52
+
53
+ Plugins.after_set_dataset(self, :_get_pg_pg_auto_validate_enums_metadata)
54
+
55
+ Plugins.inherited_instance_variables(self,
56
+ :@pg_auto_validate_enums_metadata=>nil,
57
+ :@pg_auto_validate_enums_opts=>nil)
58
+
59
+ private
60
+
61
+ # Parse the column schema to find columns with :enum_values entries,
62
+ # which will be used to setup validations.
63
+ def _get_pg_pg_auto_validate_enums_metadata
64
+ metadata = {}
65
+ @db_schema.each do |key, sch|
66
+ if enum_values = sch[:enum_values]
67
+ metadata[key] = enum_values
68
+ end
69
+ end
70
+ @pg_auto_validate_enums_metadata = metadata.freeze
71
+ end
72
+ end
73
+
74
+ module InstanceMethods
75
+ # Validate that all of the model's enum columns have valid values.
76
+ def validate
77
+ super
78
+
79
+ klass = self.class
80
+ opts = klass.pg_auto_validate_enums_opts
81
+ klass.pg_auto_validate_enums_metadata.each do |column, values|
82
+ validates_includes(values, column, opts)
83
+ end
84
+ end
85
+ end
86
+ end
87
+ end
88
+ end
@@ -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
@@ -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
@@ -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 ||= Sequel.set_temp_name(Module.new){"#{name}::@subset_static_cache_module"}}
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
@@ -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 = 89
9
+ MINOR = 91
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.89.0
4
+ version: 5.91.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jeremy Evans
8
8
  bindir: bin
9
9
  cert_chain: []
10
- date: 2025-02-01 00:00:00.000000000 Z
10
+ date: 2025-04-01 00:00:00.000000000 Z
11
11
  dependencies:
12
12
  - !ruby/object:Gem::Dependency
13
13
  name: bigdecimal
@@ -371,6 +371,8 @@ files:
371
371
  - lib/sequel/plugins/paged_operations.rb
372
372
  - lib/sequel/plugins/pg_array_associations.rb
373
373
  - lib/sequel/plugins/pg_auto_constraint_validations.rb
374
+ - lib/sequel/plugins/pg_auto_validate_enums.rb
375
+ - lib/sequel/plugins/pg_eager_any_typed_array.rb
374
376
  - lib/sequel/plugins/pg_row.rb
375
377
  - lib/sequel/plugins/pg_xmin_optimistic_locking.rb
376
378
  - lib/sequel/plugins/prepared_statements.rb