sequel 5.85.0 → 5.93.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 (70) hide show
  1. checksums.yaml +4 -4
  2. data/lib/sequel/adapters/ado.rb +1 -1
  3. data/lib/sequel/adapters/ibmdb.rb +1 -0
  4. data/lib/sequel/adapters/jdbc/db2.rb +2 -2
  5. data/lib/sequel/adapters/jdbc/derby.rb +2 -2
  6. data/lib/sequel/adapters/jdbc/h2.rb +2 -2
  7. data/lib/sequel/adapters/jdbc/hsqldb.rb +2 -2
  8. data/lib/sequel/adapters/jdbc/jtds.rb +2 -2
  9. data/lib/sequel/adapters/jdbc/mysql.rb +1 -1
  10. data/lib/sequel/adapters/jdbc/oracle.rb +5 -5
  11. data/lib/sequel/adapters/jdbc/postgresql.rb +5 -5
  12. data/lib/sequel/adapters/jdbc/sqlanywhere.rb +6 -6
  13. data/lib/sequel/adapters/jdbc/sqlite.rb +2 -2
  14. data/lib/sequel/adapters/jdbc/sqlserver.rb +2 -2
  15. data/lib/sequel/adapters/jdbc.rb +8 -8
  16. data/lib/sequel/adapters/mysql2.rb +8 -1
  17. data/lib/sequel/adapters/oracle.rb +16 -0
  18. data/lib/sequel/adapters/shared/access.rb +1 -0
  19. data/lib/sequel/adapters/shared/mssql.rb +4 -3
  20. data/lib/sequel/adapters/shared/mysql.rb +8 -4
  21. data/lib/sequel/adapters/shared/oracle.rb +1 -0
  22. data/lib/sequel/adapters/shared/postgres.rb +140 -9
  23. data/lib/sequel/adapters/sqlite.rb +4 -0
  24. data/lib/sequel/adapters/trilogy.rb +1 -2
  25. data/lib/sequel/core.rb +15 -0
  26. data/lib/sequel/database/dataset_defaults.rb +3 -3
  27. data/lib/sequel/database/misc.rb +17 -4
  28. data/lib/sequel/database/query.rb +11 -11
  29. data/lib/sequel/database/schema_generator.rb +8 -0
  30. data/lib/sequel/dataset/deprecated_singleton_class_methods.rb +1 -1
  31. data/lib/sequel/dataset/prepared_statements.rb +70 -25
  32. data/lib/sequel/dataset/query.rb +9 -5
  33. data/lib/sequel/dataset/sql.rb +19 -9
  34. data/lib/sequel/extensions/connection_validator.rb +15 -10
  35. data/lib/sequel/extensions/migration.rb +23 -3
  36. data/lib/sequel/extensions/null_dataset.rb +2 -2
  37. data/lib/sequel/extensions/pg_auto_parameterize.rb +6 -1
  38. data/lib/sequel/extensions/pg_auto_parameterize_in_array.rb +93 -10
  39. data/lib/sequel/extensions/pg_enum.rb +3 -3
  40. data/lib/sequel/extensions/pg_row.rb +3 -1
  41. data/lib/sequel/extensions/pg_schema_caching.rb +90 -0
  42. data/lib/sequel/extensions/query_blocker.rb +172 -0
  43. data/lib/sequel/extensions/schema_caching.rb +24 -9
  44. data/lib/sequel/extensions/schema_dumper.rb +16 -4
  45. data/lib/sequel/extensions/sqlite_json_ops.rb +1 -1
  46. data/lib/sequel/extensions/string_agg.rb +2 -2
  47. data/lib/sequel/extensions/virtual_row_method_block.rb +1 -0
  48. data/lib/sequel/model/associations.rb +28 -3
  49. data/lib/sequel/model/base.rb +67 -18
  50. data/lib/sequel/plugins/composition.rb +1 -1
  51. data/lib/sequel/plugins/enum.rb +1 -1
  52. data/lib/sequel/plugins/forbid_lazy_load.rb +14 -1
  53. data/lib/sequel/plugins/inspect_pk.rb +44 -0
  54. data/lib/sequel/plugins/instance_filters.rb +4 -1
  55. data/lib/sequel/plugins/inverted_subsets.rb +1 -0
  56. data/lib/sequel/plugins/lazy_attributes.rb +1 -1
  57. data/lib/sequel/plugins/nested_attributes.rb +10 -5
  58. data/lib/sequel/plugins/paged_operations.rb +5 -2
  59. data/lib/sequel/plugins/pg_auto_constraint_validations.rb +6 -1
  60. data/lib/sequel/plugins/pg_auto_validate_enums.rb +88 -0
  61. data/lib/sequel/plugins/pg_eager_any_typed_array.rb +95 -0
  62. data/lib/sequel/plugins/rcte_tree.rb +1 -1
  63. data/lib/sequel/plugins/serialization.rb +11 -5
  64. data/lib/sequel/plugins/sql_comments.rb +7 -2
  65. data/lib/sequel/plugins/static_cache_cache.rb +50 -13
  66. data/lib/sequel/plugins/subset_conditions.rb +85 -5
  67. data/lib/sequel/plugins/subset_static_cache.rb +263 -0
  68. data/lib/sequel/sql.rb +15 -6
  69. data/lib/sequel/version.rb +1 -1
  70. metadata +9 -6
@@ -51,14 +51,7 @@ module Sequel
51
51
  module SchemaCaching
52
52
  # Dump the cached schema to the filename given in Marshal format.
53
53
  def dump_schema_cache(file)
54
- sch = {}
55
- @schemas.sort.each do |k,v|
56
- sch[k] = v.map do |c, h|
57
- h = Hash[h]
58
- h.delete(:callable_default)
59
- [c, h]
60
- end
61
- end
54
+ sch = dumpable_schema_cache
62
55
  File.open(file, 'wb'){|f| f.write(Marshal.dump(sch))}
63
56
  nil
64
57
  end
@@ -72,7 +65,7 @@ module Sequel
72
65
  # Replace the schema cache with the data from the given file, which
73
66
  # should be in Marshal format.
74
67
  def load_schema_cache(file)
75
- @schemas = Marshal.load(File.read(file))
68
+ @schemas = load_schema_cache_file(file)
76
69
  @schemas.each_value{|v| schema_post_process(v)}
77
70
  nil
78
71
  end
@@ -82,6 +75,28 @@ module Sequel
82
75
  def load_schema_cache?(file)
83
76
  load_schema_cache(file) if File.exist?(file)
84
77
  end
78
+
79
+ private
80
+
81
+ # Return the deserialized schema cache file.
82
+ def load_schema_cache_file(file)
83
+ Marshal.load(File.read(file))
84
+ end
85
+
86
+ # A dumpable version of the schema cache.
87
+ def dumpable_schema_cache
88
+ sch = {}
89
+
90
+ @schemas.sort.each do |k,v|
91
+ sch[k] = v.map do |c, h|
92
+ h = Hash[h]
93
+ h.delete(:callable_default)
94
+ [c, h]
95
+ end
96
+ end
97
+
98
+ sch
99
+ end
85
100
  end
86
101
 
87
102
  Database.register_extension(:schema_caching, SchemaCaching)
@@ -27,6 +27,11 @@ Sequel.extension :eval_inspect
27
27
 
28
28
  module Sequel
29
29
  module SchemaDumper
30
+ # :nocov:
31
+ IGNORE_INDEX_ERRORS_KEY = RUBY_VERSION >= '3.4' ? 'ignore_index_errors: ' : ':ignore_index_errors=>'
32
+ # :nocov:
33
+ private_constant :IGNORE_INDEX_ERRORS_KEY
34
+
30
35
  # Convert the column schema information to a hash of column options, one of which must
31
36
  # be :type. The other options added should modify that type (e.g. :size). If a
32
37
  # database type is not recognized, return it as a String type.
@@ -161,7 +166,7 @@ END_MIG
161
166
  def dump_table_schema(table, options=OPTS)
162
167
  gen = dump_table_generator(table, options)
163
168
  commands = [gen.dump_columns, gen.dump_constraints, gen.dump_indexes].reject{|x| x == ''}.join("\n\n")
164
- "create_table(#{table.inspect}#{', :ignore_index_errors=>true' if !options[:same_db] && options[:indexes] != false && !gen.indexes.empty?}) do\n#{commands.gsub(/^/, ' ')}\nend"
169
+ "create_table(#{table.inspect}#{", #{IGNORE_INDEX_ERRORS_KEY}true" if !options[:same_db] && options[:indexes] != false && !gen.indexes.empty?}) do\n#{commands.gsub(/^/, ' ')}\nend"
165
170
  end
166
171
 
167
172
  private
@@ -252,7 +257,7 @@ END_MIG
252
257
  gen.foreign_key(name, table, col_opts)
253
258
  else
254
259
  gen.column(name, type, col_opts)
255
- 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
256
261
  gen.check(Sequel::SQL::Identifier.new(name) >= 0)
257
262
  end
258
263
  end
@@ -425,6 +430,13 @@ END_MIG
425
430
 
426
431
  module Schema
427
432
  class CreateTableGenerator
433
+ # :nocov:
434
+ DEFAULT_KEY = RUBY_VERSION >= '3.4' ? 'default: ' : ':default=>'
435
+ IGNORE_ERRORS_KEY = RUBY_VERSION >= '3.4' ? 'ignore_errors: ' : ':ignore_errors=>'
436
+ # :nocov:
437
+ private_constant :DEFAULT_KEY
438
+ private_constant :IGNORE_ERRORS_KEY
439
+
428
440
  # Dump this generator's columns to a string that could be evaled inside
429
441
  # another instance to represent the same columns
430
442
  def dump_columns
@@ -508,7 +520,7 @@ END_MIG
508
520
  c = c.dup
509
521
  cols = c.delete(:columns)
510
522
  if table = options[:add_index] || options[:drop_index]
511
- "#{options[:drop_index] ? 'drop' : 'add'}_index #{table.inspect}, #{cols.inspect}#{', :ignore_errors=>true' if options[:ignore_errors]}#{opts_inspect(c)}"
523
+ "#{options[:drop_index] ? 'drop' : 'add'}_index #{table.inspect}, #{cols.inspect}#{", #{IGNORE_ERRORS_KEY}true" if options[:ignore_errors]}#{opts_inspect(c)}"
512
524
  else
513
525
  "index #{cols.inspect}#{opts_inspect(c)}"
514
526
  end
@@ -526,7 +538,7 @@ END_MIG
526
538
  if opts[:default]
527
539
  opts = opts.dup
528
540
  de = Sequel.eval_inspect(opts.delete(:default))
529
- ", :default=>#{de}#{", #{opts.inspect[1...-1]}" if opts.length > 0}"
541
+ ", #{DEFAULT_KEY}#{de}#{", #{opts.inspect[1...-1]}" if opts.length > 0}"
530
542
  else
531
543
  ", #{opts.inspect[1...-1]}" if opts.length > 0
532
544
  end
@@ -35,7 +35,7 @@
35
35
  #
36
36
  # j[1] # (json_column ->> 1)
37
37
  # j.get(1) # (json_column ->> 1)
38
- # j.get_text(1) # (json_column -> 1)
38
+ # j.get_json(1) # (json_column -> 1)
39
39
  # j.extract('$.a') # json_extract(json_column, '$.a')
40
40
  # jb.extract('$.a') # jsonb_extract(jsonb_column, '$.a')
41
41
  #
@@ -173,7 +173,7 @@ module Sequel
173
173
  # Return a modified StringAgg that uses distinct expressions
174
174
  def distinct
175
175
  self.class.new(@expr, @separator) do |sa|
176
- sa.instance_variable_set(:@order_expr, @order_expr) if @order_expr
176
+ sa.instance_variable_set(:@order_expr, @order_expr)
177
177
  sa.instance_variable_set(:@distinct, true)
178
178
  end
179
179
  end
@@ -181,8 +181,8 @@ module Sequel
181
181
  # Return a modified StringAgg with the given order
182
182
  def order(*o)
183
183
  self.class.new(@expr, @separator) do |sa|
184
- sa.instance_variable_set(:@distinct, @distinct) if @distinct
185
184
  sa.instance_variable_set(:@order_expr, o.empty? ? nil : o.freeze)
185
+ sa.instance_variable_set(:@distinct, @distinct)
186
186
  end
187
187
  end
188
188
 
@@ -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
@@ -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)
@@ -87,7 +87,7 @@ module Sequel
87
87
  attr_reader :simple_pk
88
88
 
89
89
  # Should be the literal table name if this Model's dataset is a simple table (no select, order, join, etc.),
90
- # or nil otherwise. This and simple_pk are used for an optimization in Model.[].
90
+ # or nil otherwise. This and simple_pk are used for an optimization in Model[].
91
91
  attr_reader :simple_table
92
92
 
93
93
  # Whether mass assigning via .create/.new/#set/#update should raise an error
@@ -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
@@ -398,7 +398,7 @@ module Sequel
398
398
  end
399
399
 
400
400
  # Finds a single record according to the supplied filter.
401
- # You are encouraged to use Model.[] or Model.first instead of this method.
401
+ # You are encouraged to use Model[] or Model.first instead of this method.
402
402
  #
403
403
  # Artist.find(name: 'Bob')
404
404
  # # SELECT * FROM artists WHERE (name = 'Bob') LIMIT 1
@@ -533,10 +533,28 @@ module Sequel
533
533
  end
534
534
  end
535
535
 
536
+ # Return a qualified identifier or array of qualified identifiers for
537
+ # the model's primary key. Uses the given qualifier if provided, or
538
+ # the table_name otherwise. If the model does not have a primary key,
539
+ # raises an +Error+.
540
+ #
541
+ # Artist.order(Artist.qualified_primary_key)
542
+ # # SELECT * FROM artists ORDER BY artists.id
543
+ def qualified_primary_key(qualifier=table_name)
544
+ case key = @primary_key
545
+ when Symbol
546
+ SQL::QualifiedIdentifier.new(qualifier, key)
547
+ when Array
548
+ key.map{|k| SQL::QualifiedIdentifier.new(qualifier, k)}
549
+ else
550
+ raise(Error, "#{self} does not have a primary key")
551
+ end
552
+ end
553
+
536
554
  # Return a hash where the keys are qualified column references. Uses the given
537
555
  # qualifier if provided, or the table_name otherwise. This is useful if you
538
556
  # plan to join other tables to this table and you want the column references
539
- # to be qualified.
557
+ # to be qualified. If the model does not have a primary key, raises an +Error+.
540
558
  #
541
559
  # Artist.where(Artist.qualified_primary_key_hash(1))
542
560
  # # SELECT * FROM artists WHERE (artists.id = 1)
@@ -762,22 +780,36 @@ module Sequel
762
780
  end
763
781
  end
764
782
  end
783
+
784
+ # Module that the class methods that call dataset methods are kept in.
785
+ # This allows the methods to be overridden and call super with the
786
+ # default behavior.
787
+ def dataset_methods_module
788
+ return @dataset_methods_module if defined?(@dataset_methods_module)
789
+ mod_name = "#{name}::@dataset_methods_module"
790
+ Sequel.synchronize{@dataset_methods_module ||= Sequel.set_temp_name(Module.new){mod_name}}
791
+ extend(@dataset_methods_module)
792
+ @dataset_methods_module
793
+ end
765
794
 
766
- # Define a model method that calls the dataset method with the same name,
767
- # only used for methods with names that can't be represented directly in
768
- # ruby code.
795
+ # Define a model method that calls the dataset method with the same name.
769
796
  def def_model_dataset_method(meth)
770
797
  return if respond_to?(meth, true)
771
798
 
799
+ mod = dataset_methods_module
800
+
772
801
  if meth.to_s =~ /\A[A-Za-z_][A-Za-z0-9_]*\z/
773
- instance_eval("def #{meth}(*args, &block); dataset.#{meth}(*args, &block) end", __FILE__, __LINE__)
802
+ mod.module_eval(<<END, __FILE__, __LINE__ + 1)
803
+ def #{meth}(*args, &block); dataset.#{meth}(*args, &block) end
804
+ ruby2_keywords :#{meth} if respond_to?(:ruby2_keywords, true)
805
+ END
774
806
  else
775
- define_singleton_method(meth){|*args, &block| dataset.public_send(meth, *args, &block)}
807
+ mod.send(:define_method, meth){|*args, &block| dataset.public_send(meth, *args, &block)}
808
+ # :nocov:
809
+ mod.send(:ruby2_keywords, meth) if respond_to?(:ruby2_keywords, true)
810
+ # :nocov:
776
811
  end
777
- singleton_class.send(:alias_method, meth, meth)
778
- # :nocov:
779
- singleton_class.send(:ruby2_keywords, meth) if respond_to?(:ruby2_keywords, true)
780
- # :nocov:
812
+ mod.send(:alias_method, meth, meth)
781
813
  end
782
814
 
783
815
  # Get the schema from the database, fall back on checking the columns
@@ -943,7 +975,10 @@ module Sequel
943
975
  # Module that the class includes that holds methods the class adds for column accessors and
944
976
  # associations so that the methods can be overridden with +super+.
945
977
  def overridable_methods_module
946
- include(@overridable_methods_module = Module.new) unless @overridable_methods_module
978
+ return @overridable_methods_module if defined?(@overridable_methods_module)
979
+ mod_name = "#{name}::@overridable_methods_module"
980
+ Sequel.synchronize{@overridable_methods_module ||= Sequel.set_temp_name(Module.new){mod_name}}
981
+ include(@overridable_methods_module)
947
982
  @overridable_methods_module
948
983
  end
949
984
 
@@ -1311,7 +1346,7 @@ module Sequel
1311
1346
  # Returns a string representation of the model instance including
1312
1347
  # the class name and values.
1313
1348
  def inspect
1314
- "#<#{model.name} @values=#{inspect_values}>"
1349
+ "#<#{inspect_prefix} @values=#{inspect_values}>"
1315
1350
  end
1316
1351
 
1317
1352
  # Returns the keys in +values+. May not include all column names.
@@ -1597,7 +1632,7 @@ module Sequel
1597
1632
  @skip_validation_on_next_save = true
1598
1633
  end
1599
1634
 
1600
- # Returns (naked) dataset that should return only this instance.
1635
+ # Returns naked dataset that should return only the row related to this instance.
1601
1636
  #
1602
1637
  # Artist[1].this
1603
1638
  # # SELECT * FROM artists WHERE (id = 1) LIMIT 1
@@ -1994,7 +2029,12 @@ module Sequel
1994
2029
  set(h) unless h.empty?
1995
2030
  end
1996
2031
 
1997
- # Default inspection output for the values hash, overwrite to change what #inspect displays.
2032
+ # Default inspect output for the inspect, by default, just showing the class.
2033
+ def inspect_prefix
2034
+ model.name
2035
+ end
2036
+
2037
+ # Default inspect output for the values hash, overwrite to change what #inspect displays.
1998
2038
  def inspect_values
1999
2039
  @values.inspect
2000
2040
  end
@@ -2238,7 +2278,7 @@ module Sequel
2238
2278
  # Return the dataset ordered by the model's primary key. This should not
2239
2279
  # be used if the model does not have a primary key.
2240
2280
  def _force_primary_key_order
2241
- cached_dataset(:_pk_order_ds){order(*model.primary_key)}
2281
+ cached_dataset(:_pk_order_ds){order(*unambiguous_primary_key)}
2242
2282
  end
2243
2283
 
2244
2284
  # If the dataset is not already ordered, and the model has a primary key,
@@ -2266,6 +2306,15 @@ module Sequel
2266
2306
  end
2267
2307
  end
2268
2308
 
2309
+ # The primary key for the dataset's model, qualified if the dataset is joined.
2310
+ def unambiguous_primary_key
2311
+ if joined_dataset?
2312
+ model.qualified_primary_key
2313
+ else
2314
+ model.primary_key
2315
+ end
2316
+ end
2317
+
2269
2318
  def non_sql_option?(key)
2270
2319
  super || key == :model
2271
2320
  end
@@ -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
 
@@ -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
@@ -0,0 +1,44 @@
1
+ # frozen-string-literal: true
2
+
3
+ module Sequel
4
+ module Plugins
5
+ # The inspect_pk plugin includes the pk right next to the
6
+ # model name in inspect, allowing for easily copying and
7
+ # pasting to retrieve a copy of the object:
8
+ #
9
+ # Album.with_pk(1).inspect
10
+ # # default: #<Album @values={...}>
11
+ # # with inspect_pk: #<Album[1] @values={...}>
12
+ #
13
+ # Usage:
14
+ #
15
+ # # Make all model instances include pk in inspect output
16
+ # Sequel::Model.plugin :inspect_pk
17
+ #
18
+ # # Make Album instances include pk in inspect output
19
+ # Album.plugin :inspect_pk
20
+ module InspectPk
21
+ module InstanceMethods
22
+ private
23
+
24
+ # The primary key value to include in the inspect output, if any.
25
+ # For composite primary keys, this only includes a value if all
26
+ # fields are present.
27
+ def inspect_pk
28
+ if primary_key && (pk = self.pk) && (!(Array === pk) || pk.all?)
29
+ pk
30
+ end
31
+ end
32
+
33
+ # Include the instance's primary key in the output.
34
+ def inspect_prefix
35
+ if v = inspect_pk
36
+ "#{super}[#{v.inspect}]"
37
+ else
38
+ super
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -102,7 +102,10 @@ module Sequel
102
102
 
103
103
  # Apply the instance filters to the given dataset
104
104
  def apply_instance_filters(ds)
105
- instance_filters.inject(ds){|ds1, i| ds1.where(*i[0], &i[1])}
105
+ instance_filters.inject(ds) do |ds1, i|
106
+ block = i[1]
107
+ ds1.where(*i[0], &block)
108
+ end
106
109
  end
107
110
 
108
111
  # Clear the instance filters.
@@ -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|
@@ -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)
@@ -157,13 +157,16 @@ module Sequel
157
157
  raise Error, "the paged_operations plugin is not supported on DB2 when using emulated offsets, set the :offset_strategy Database option to 'limit_offset' or 'offset_fetch'"
158
158
  end
159
159
 
160
- case pk = model.primary_key
160
+ case pk = unambiguous_primary_key
161
161
  when Symbol
162
162
  Sequel.identifier(pk)
163
163
  when Array
164
164
  raise Error, "cannot use #{meth} on a model with a composite primary key"
165
- else
165
+ when nil
166
166
  raise Error, "cannot use #{meth} on a model without a primary key"
167
+ else
168
+ # Likely SQL::QualifiedIdentifier, if the dataset is joined.
169
+ pk
167
170
  end
168
171
  end
169
172
 
@@ -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