sequel 4.9.0 → 4.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (110) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +79 -1
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +1 -1
  5. data/Rakefile +2 -12
  6. data/bin/sequel +1 -0
  7. data/doc/advanced_associations.rdoc +82 -25
  8. data/doc/association_basics.rdoc +21 -22
  9. data/doc/core_extensions.rdoc +1 -1
  10. data/doc/opening_databases.rdoc +7 -0
  11. data/doc/release_notes/4.10.0.txt +226 -0
  12. data/doc/security.rdoc +1 -0
  13. data/doc/testing.rdoc +7 -7
  14. data/doc/transactions.rdoc +8 -0
  15. data/lib/sequel/adapters/jdbc.rb +160 -168
  16. data/lib/sequel/adapters/jdbc/db2.rb +17 -18
  17. data/lib/sequel/adapters/jdbc/derby.rb +5 -28
  18. data/lib/sequel/adapters/jdbc/h2.rb +11 -22
  19. data/lib/sequel/adapters/jdbc/hsqldb.rb +31 -18
  20. data/lib/sequel/adapters/jdbc/jtds.rb +0 -15
  21. data/lib/sequel/adapters/jdbc/oracle.rb +36 -35
  22. data/lib/sequel/adapters/jdbc/postgresql.rb +72 -90
  23. data/lib/sequel/adapters/jdbc/sqlanywhere.rb +18 -16
  24. data/lib/sequel/adapters/jdbc/sqlite.rb +7 -0
  25. data/lib/sequel/adapters/jdbc/sqlserver.rb +10 -30
  26. data/lib/sequel/adapters/jdbc/transactions.rb +5 -6
  27. data/lib/sequel/adapters/openbase.rb +1 -7
  28. data/lib/sequel/adapters/postgres.rb +1 -1
  29. data/lib/sequel/adapters/shared/access.rb +3 -6
  30. data/lib/sequel/adapters/shared/cubrid.rb +24 -9
  31. data/lib/sequel/adapters/shared/db2.rb +13 -5
  32. data/lib/sequel/adapters/shared/firebird.rb +16 -16
  33. data/lib/sequel/adapters/shared/informix.rb +2 -5
  34. data/lib/sequel/adapters/shared/mssql.rb +72 -63
  35. data/lib/sequel/adapters/shared/mysql.rb +72 -40
  36. data/lib/sequel/adapters/shared/oracle.rb +27 -15
  37. data/lib/sequel/adapters/shared/postgres.rb +24 -44
  38. data/lib/sequel/adapters/shared/progress.rb +1 -5
  39. data/lib/sequel/adapters/shared/sqlanywhere.rb +26 -18
  40. data/lib/sequel/adapters/shared/sqlite.rb +21 -6
  41. data/lib/sequel/adapters/utils/emulate_offset_with_reverse_and_count.rb +8 -1
  42. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +8 -2
  43. data/lib/sequel/adapters/utils/split_alter_table.rb +8 -0
  44. data/lib/sequel/core.rb +14 -9
  45. data/lib/sequel/database/dataset_defaults.rb +1 -0
  46. data/lib/sequel/database/misc.rb +12 -0
  47. data/lib/sequel/database/query.rb +4 -1
  48. data/lib/sequel/database/schema_methods.rb +3 -2
  49. data/lib/sequel/database/transactions.rb +47 -17
  50. data/lib/sequel/dataset/features.rb +12 -2
  51. data/lib/sequel/dataset/mutation.rb +2 -0
  52. data/lib/sequel/dataset/placeholder_literalizer.rb +12 -4
  53. data/lib/sequel/dataset/prepared_statements.rb +6 -0
  54. data/lib/sequel/dataset/query.rb +1 -1
  55. data/lib/sequel/dataset/sql.rb +132 -70
  56. data/lib/sequel/extensions/columns_introspection.rb +1 -1
  57. data/lib/sequel/extensions/null_dataset.rb +8 -4
  58. data/lib/sequel/extensions/pg_array.rb +4 -4
  59. data/lib/sequel/extensions/pg_row.rb +1 -0
  60. data/lib/sequel/model/associations.rb +468 -188
  61. data/lib/sequel/model/base.rb +88 -13
  62. data/lib/sequel/plugins/association_pks.rb +23 -64
  63. data/lib/sequel/plugins/auto_validations.rb +3 -2
  64. data/lib/sequel/plugins/dataset_associations.rb +1 -3
  65. data/lib/sequel/plugins/many_through_many.rb +18 -65
  66. data/lib/sequel/plugins/pg_array_associations.rb +97 -86
  67. data/lib/sequel/plugins/prepared_statements.rb +2 -1
  68. data/lib/sequel/plugins/prepared_statements_associations.rb +36 -27
  69. data/lib/sequel/plugins/rcte_tree.rb +12 -16
  70. data/lib/sequel/plugins/sharding.rb +21 -3
  71. data/lib/sequel/plugins/single_table_inheritance.rb +2 -1
  72. data/lib/sequel/plugins/subclasses.rb +1 -9
  73. data/lib/sequel/plugins/tactical_eager_loading.rb +9 -0
  74. data/lib/sequel/plugins/tree.rb +2 -2
  75. data/lib/sequel/plugins/validation_class_methods.rb +1 -1
  76. data/lib/sequel/version.rb +1 -1
  77. data/spec/adapters/mssql_spec.rb +57 -15
  78. data/spec/adapters/mysql_spec.rb +11 -0
  79. data/spec/bin_spec.rb +2 -2
  80. data/spec/core/database_spec.rb +38 -4
  81. data/spec/core/dataset_spec.rb +45 -7
  82. data/spec/core/placeholder_literalizer_spec.rb +17 -0
  83. data/spec/core/schema_spec.rb +6 -1
  84. data/spec/extensions/active_model_spec.rb +18 -9
  85. data/spec/extensions/association_pks_spec.rb +20 -18
  86. data/spec/extensions/association_proxies_spec.rb +9 -9
  87. data/spec/extensions/auto_validations_spec.rb +6 -0
  88. data/spec/extensions/columns_introspection_spec.rb +1 -0
  89. data/spec/extensions/constraint_validations_spec.rb +3 -1
  90. data/spec/extensions/many_through_many_spec.rb +191 -111
  91. data/spec/extensions/pg_array_associations_spec.rb +133 -103
  92. data/spec/extensions/prepared_statements_associations_spec.rb +23 -4
  93. data/spec/extensions/rcte_tree_spec.rb +35 -27
  94. data/spec/extensions/sequel_3_dataset_methods_spec.rb +0 -1
  95. data/spec/extensions/sharding_spec.rb +2 -2
  96. data/spec/extensions/tactical_eager_loading_spec.rb +4 -0
  97. data/spec/extensions/to_dot_spec.rb +1 -0
  98. data/spec/extensions/touch_spec.rb +2 -2
  99. data/spec/integration/associations_test.rb +130 -37
  100. data/spec/integration/dataset_test.rb +17 -0
  101. data/spec/integration/model_test.rb +17 -0
  102. data/spec/integration/schema_test.rb +14 -0
  103. data/spec/integration/transaction_test.rb +25 -1
  104. data/spec/model/association_reflection_spec.rb +63 -24
  105. data/spec/model/associations_spec.rb +104 -57
  106. data/spec/model/base_spec.rb +14 -1
  107. data/spec/model/class_dataset_methods_spec.rb +1 -0
  108. data/spec/model/eager_loading_spec.rb +221 -74
  109. data/spec/model/model_spec.rb +119 -1
  110. metadata +4 -2
@@ -49,7 +49,7 @@ module Sequel
49
49
  from.probable_columns
50
50
  when Symbol, SQL::Identifier, SQL::QualifiedIdentifier
51
51
  schemas = db.instance_variable_get(:@schemas)
52
- if schemas && (sch = Sequel.synchronize{schemas[literal(from)]})
52
+ if schemas && (table = literal(from)) && (sch = Sequel.synchronize{schemas[table]})
53
53
  sch.map{|c,_| c}
54
54
  end
55
55
  end
@@ -2,10 +2,6 @@
2
2
  # returns a cloned dataset that will never issue a query to the
3
3
  # database. It implements the null object pattern for datasets.
4
4
  #
5
- # To load the extension:
6
- #
7
- # Sequel.extension :null_dataset
8
- #
9
5
  # The most common usage is probably in a method that must return
10
6
  # a dataset, where the method knows the dataset shouldn't return
11
7
  # anything. With standard Sequel, you'd probably just add a
@@ -26,6 +22,14 @@
26
22
  # the same options to get the columns.
27
23
  #
28
24
  # This extension uses Object#extend at runtime, which can hurt performance.
25
+ #
26
+ # To add the nullify method to a single dataset:
27
+ #
28
+ # ds = ds.extension(:null_dataset)
29
+ #
30
+ # To add the nullify method to all datasets on a single database:
31
+ #
32
+ # DB.extension(:null_dataset)
29
33
 
30
34
  module Sequel
31
35
  class Dataset
@@ -196,15 +196,14 @@ module Sequel
196
196
  def self.extended(db)
197
197
  db.instance_eval do
198
198
  @pg_array_schema_types ||= {}
199
+ procs = conversion_procs
200
+ procs[1115] = Creator.new("timestamp without time zone", procs[1114])
201
+ procs[1185] = Creator.new("timestamp with time zone", procs[1184])
199
202
  copy_conversion_procs([1009, 1007, 1016, 1231, 1022, 1000, 1001, 1182, 1183, 1270, 1005, 1028, 1021, 1014, 1015])
200
203
  [:string_array, :integer_array, :decimal_array, :float_array, :boolean_array, :blob_array, :date_array, :time_array, :datetime_array].each do |v|
201
204
  @schema_type_classes[v] = PGArray
202
205
  end
203
206
  end
204
-
205
- procs = db.conversion_procs
206
- procs[1115] = Creator.new("timestamp without time zone", procs[1114])
207
- procs[1185] = Creator.new("timestamp with time zone", procs[1184])
208
207
  end
209
208
 
210
209
  # Handle arrays in bound variables
@@ -232,6 +231,7 @@ module Sequel
232
231
  end
233
232
  PGArray.register(db_type, opts, &block)
234
233
  @schema_type_classes[:"#{opts[:type_symbol] || db_type}_array"] = PGArray
234
+ conversion_procs_updated
235
235
  end
236
236
 
237
237
  # Return PGArray if this type matches any supported array type.
@@ -499,6 +499,7 @@ module Sequel
499
499
  private meth
500
500
  end
501
501
 
502
+ conversion_procs_updated
502
503
  nil
503
504
  end
504
505
 
@@ -54,9 +54,10 @@ module Sequel
54
54
  end
55
55
 
56
56
  # The dataset associated via this association, with the non-instance specific
57
- # changes already applied.
57
+ # changes already applied. This will be a joined dataset if the association
58
+ # requires joining tables.
58
59
  def associated_dataset
59
- cached_fetch(:_dataset){apply_dataset_changes(associated_class.dataset.clone)}
60
+ cached_fetch(:_dataset){apply_dataset_changes(_associated_dataset)}
60
61
  end
61
62
 
62
63
  # Apply all non-instance specific changes to the given dataset and return it.
@@ -76,6 +77,16 @@ module Sequel
76
77
  ds
77
78
  end
78
79
 
80
+ # Apply all non-instance specific changes and the eager_block option to the given
81
+ # dataset and return it.
82
+ def apply_eager_dataset_changes(ds)
83
+ ds = apply_dataset_changes(ds)
84
+ if block = self[:eager_block]
85
+ ds = block.call(ds)
86
+ end
87
+ ds
88
+ end
89
+
79
90
  # Apply the eager graph limit strategy to the dataset to graph into the current dataset, or return
80
91
  # the dataset unmodified if no SQL limit strategy is needed.
81
92
  def apply_eager_graph_limit_strategy(strategy, ds)
@@ -91,8 +102,8 @@ module Sequel
91
102
 
92
103
  # Apply an eager limit strategy to the dataset, or return the dataset
93
104
  # unmodified if it doesn't need an eager limit strategy.
94
- def apply_eager_limit_strategy(ds)
95
- case eager_limit_strategy
105
+ def apply_eager_limit_strategy(ds, strategy=eager_limit_strategy)
106
+ case strategy
96
107
  when :distinct_on
97
108
  apply_distinct_on_eager_limit_strategy(ds)
98
109
  when :window_function
@@ -173,19 +184,31 @@ module Sequel
173
184
 
174
185
  # Return the symbol used for the row number column if the window function
175
186
  # eager limit strategy is being used, or nil otherwise.
176
- def delete_row_number_column(ds)
187
+ def delete_row_number_column(ds=associated_dataset)
177
188
  if eager_limit_strategy == :window_function
178
189
  ds.row_number_column
179
190
  end
180
191
  end
181
192
 
193
+ # Return an dataset that will load the appropriate associated objects for
194
+ # the given object using this association.
195
+ def association_dataset_for(object)
196
+ associated_dataset.where(predicate_keys.zip(predicate_key_values(object)))
197
+ end
198
+
199
+ ASSOCIATION_DATASET_PROC = proc{|r| r.association_dataset_for(self)}
200
+ # Proc used to create the association dataset method.
201
+ def association_dataset_proc
202
+ ASSOCIATION_DATASET_PROC
203
+ end
204
+
182
205
  # The eager_graph limit strategy to use for this dataset
183
206
  def eager_graph_limit_strategy(strategy)
184
207
  if self[:limit] || !returns_array?
185
208
  strategy = strategy[self[:name]] if strategy.is_a?(Hash)
186
209
  case strategy
187
210
  when true
188
- true_eager_limit_strategy
211
+ true_eager_graph_limit_strategy
189
212
  when Symbol
190
213
  strategy
191
214
  else
@@ -210,6 +233,39 @@ module Sequel
210
233
  end
211
234
  end
212
235
 
236
+ # Eager load the associated objects using the hash of eager options,
237
+ # yielding each row to the block.
238
+ def eager_load_results(eo, &block)
239
+ rows = eo[:rows]
240
+ initialize_association_cache(rows) unless eo[:initialize_rows] == false
241
+ strategy = eager_limit_strategy
242
+ cascade = eo[:associations]
243
+
244
+ if eo[:eager_block] || eo[:loader] == false
245
+ strategy = true_eager_graph_limit_strategy if strategy == :union
246
+ objects = apply_eager_limit_strategy(eager_loading_dataset(eo), strategy).all
247
+ cascade = nil
248
+ elsif strategy == :union
249
+ objects = []
250
+ ds = associated_dataset
251
+ ds = self[:eager_block].call(ds) if self[:eager_block]
252
+ loader = union_eager_loader
253
+ joiner = " UNION ALL "
254
+ eo[:id_map].keys.each_slice(subqueries_per_union).each do |slice|
255
+ objects.concat(ds.with_sql(slice.map{|k| loader.sql(*k)}.join(joiner)).to_a)
256
+ end
257
+ else
258
+ objects = placeholder_eager_loader.all(eo[:id_map].keys)
259
+ end
260
+
261
+ if cascade && !(cascade = associated_dataset.send(:eager_options_for_associations, [cascade])).empty?
262
+ associated_eager_dataset.send(:eager_load, objects, cascade)
263
+ end
264
+
265
+ objects.each(&block)
266
+ apply_ruby_eager_limit_strategy(rows)
267
+ end
268
+
213
269
  # The key to use for the key hash when eager loading
214
270
  def eager_loader_key
215
271
  self[:eager_loader_key]
@@ -279,11 +335,28 @@ module Sequel
279
335
  false
280
336
  end
281
337
 
338
+ # A placeholder literalizer that can be used to lazily load the association. If
339
+ # one can't be used, returns nil.
340
+ def placeholder_loader
341
+ if use_placeholder_loader?
342
+ cached_fetch(:placeholder_loader) do
343
+ Sequel::Dataset::PlaceholderLiteralizer.loader(associated_dataset) do |pl, ds|
344
+ ds.where(*predicate_keys.map{|k| SQL::BooleanExpression.new(:'=', k, pl.arg)})
345
+ end
346
+ end
347
+ end
348
+ end
349
+
282
350
  # The keys to use for loading of the regular dataset, as an array.
283
351
  def predicate_keys
284
352
  cached_fetch(:predicate_keys){Array(predicate_key)}
285
353
  end
286
354
 
355
+ # The values that predicate_keys should match for objects to be associated.
356
+ def predicate_key_values(object)
357
+ predicate_key_methods.map{|k| object.send(k)}
358
+ end
359
+
287
360
  # Qualify +col+ with the given table name. If +col+ is an array of columns,
288
361
  # return an array of qualified columns. Only qualifies Symbols and SQL::Identifier
289
362
  # values, other values are not modified.
@@ -425,10 +498,16 @@ module Sequel
425
498
  end
426
499
  end
427
500
 
501
+ # The base dataset used for the association, before any order/conditions
502
+ # options have been applied.
503
+ def _associated_dataset
504
+ associated_class.dataset.clone
505
+ end
506
+
428
507
  # Apply a limit strategy to the given dataset so that filter by
429
508
  # associations works with a limited dataset.
430
509
  def apply_filter_by_associations_limit_strategy(ds)
431
- case eager_limit_strategy
510
+ case filter_by_associations_limit_strategy
432
511
  when :distinct_on
433
512
  apply_filter_by_associations_distinct_on_limit_strategy(ds)
434
513
  when :window_function
@@ -443,30 +522,67 @@ module Sequel
443
522
  # each key is included.
444
523
  def apply_filter_by_associations_distinct_on_limit_strategy(ds)
445
524
  k = filter_by_associations_limit_key
446
- ds.where(k=>apply_distinct_on_eager_limit_strategy(filter_by_associations_limit_subquery.select(*k)))
525
+ ds.where(k=>apply_distinct_on_eager_limit_strategy(associated_eager_dataset.select(*k)))
447
526
  end
448
527
 
449
528
  # Apply a distinct on eager limit strategy using IN with a subquery
450
529
  # that uses a filter on the row_number window function to ensure
451
530
  # that only rows inside the limit are returned.
452
531
  def apply_filter_by_associations_window_function_limit_strategy(ds)
453
- ds.where(filter_by_associations_limit_key=>apply_window_function_eager_limit_strategy(filter_by_associations_limit_subquery.select(*filter_by_associations_limit_alias_key)).select(*filter_by_associations_limit_aliases))
532
+ ds.where(filter_by_associations_limit_key=>apply_window_function_eager_limit_strategy(associated_eager_dataset.select(*filter_by_associations_limit_alias_key)).select(*filter_by_associations_limit_aliases))
454
533
  end
455
534
 
456
535
  # The associated_dataset with the eager_block callback already applied.
457
536
  def associated_eager_dataset
458
537
  cached_fetch(:associated_eager_dataset) do
459
- ds = associated_dataset
460
- ds = self[:eager_block].call(ds) if self[:eager_block]
538
+ ds = associated_dataset.unlimited
539
+ if block = self[:eager_block]
540
+ ds = block.call(ds)
541
+ end
461
542
  ds
462
543
  end
463
544
  end
464
545
 
546
+ # The dataset to use for eager loading associated objects for multiple current objects,
547
+ # given the hash passed to the eager loader.
548
+ def eager_loading_dataset(eo=OPTS)
549
+ ds = eo[:dataset] || associated_eager_dataset
550
+ if id_map = eo[:id_map]
551
+ ds = ds.where(eager_loading_predicate_condition(id_map.keys))
552
+ end
553
+ if associations = eo[:associations]
554
+ ds = ds.eager(associations)
555
+ end
556
+ if block = eo[:eager_block]
557
+ ds = block.call(ds)
558
+ end
559
+ if eager_loading_use_associated_key?
560
+ ds = ds.select_append(*associated_key_array)
561
+ end
562
+ if self[:eager_graph]
563
+ raise(Error, "cannot eagerly load a #{self[:type]} association that uses :eager_graph") if eager_loading_use_associated_key?
564
+ ds = ds.eager_graph(self[:eager_graph])
565
+ end
566
+ ds
567
+ end
568
+
465
569
  # The default eager limit strategy to use for this association
466
570
  def default_eager_limit_strategy
467
571
  self[:model].default_eager_limit_strategy || :ruby
468
572
  end
469
573
 
574
+ # The predicate condition to use for the eager_loader.
575
+ def eager_loading_predicate_condition(keys)
576
+ {predicate_key=>keys}
577
+ end
578
+
579
+ # Add conditions to the dataset to not include NULL values for
580
+ # the associated keys, and select those keys.
581
+ def filter_by_associations_add_conditions_dataset_filter(ds)
582
+ k = filter_by_associations_conditions_associated_keys
583
+ ds.select(*k).where(Sequel.negate(k.zip([])))
584
+ end
585
+
470
586
  # The conditions to add to the filter by associations conditions
471
587
  # subquery to restrict it to to the object(s) that was used as the
472
588
  # filter value.
@@ -487,16 +603,28 @@ module Sequel
487
603
  # values.
488
604
  def filter_by_associations_conditions_dataset
489
605
  cached_fetch(:filter_by_associations_conditions_dataset) do
490
- ds = associated_eager_dataset.unordered.unlimited
606
+ ds = associated_eager_dataset.unordered
491
607
  ds = filter_by_associations_add_conditions_dataset_filter(ds)
492
608
  ds = apply_filter_by_associations_limit_strategy(ds)
493
609
  ds
494
610
  end
495
611
  end
496
612
 
497
- # The base subquery to use when filtering by limited associations
498
- def filter_by_associations_limit_subquery
499
- associated_eager_dataset.unlimited
613
+ # The strategy to use to filter by a limited association
614
+ def filter_by_associations_limit_strategy
615
+ v = fetch(:filter_limit_strategy, self[:eager_limit_strategy])
616
+ if v || self[:limit] || !returns_array?
617
+ case v ||= self[:model].default_eager_limit_strategy
618
+ when :union, :ruby
619
+ # Can't use a union or ruby-based strategy for filtering by associations, switch to default eager graph limit
620
+ # strategy.
621
+ true_eager_graph_limit_strategy
622
+ when Symbol
623
+ v
624
+ when true
625
+ true_eager_graph_limit_strategy
626
+ end
627
+ end
500
628
  end
501
629
 
502
630
  # Whether to limit the associated dataset to a single row.
@@ -509,6 +637,15 @@ module Sequel
509
637
  limit_and_offset.last
510
638
  end
511
639
 
640
+ # A placeholder literalizer used to speed up eager loading.
641
+ def placeholder_eager_loader
642
+ cached_fetch(:placeholder_eager_loader) do
643
+ Sequel::Dataset::PlaceholderLiteralizer.loader(associated_dataset) do |pl, ds|
644
+ apply_eager_limit_strategy(eager_loading_dataset.where(predicate_key=>pl.arg), eager_limit_strategy)
645
+ end
646
+ end
647
+ end
648
+
512
649
  # Whether the given association reflection is possible reciprocal
513
650
  # association for the current association reflection.
514
651
  def reciprocal_association?(assoc_reflect)
@@ -518,21 +655,60 @@ module Sequel
518
655
  assoc_reflect[:block].nil?
519
656
  end
520
657
 
658
+ # The number of subqueries to use in each union query, used to eagerly load
659
+ # limited associations. Defaults to 40, the optimal number depends on the
660
+ # latency between the database and the application.
661
+ def subqueries_per_union
662
+ self[:subqueries_per_union] || 40
663
+ end
664
+
521
665
  # If +s+ is an array, map +s+ over the block. Otherwise, just call the
522
666
  # block with +s+.
523
667
  def transform(s)
524
668
  s.is_a?(Array) ? s.map(&Proc.new) : (yield s)
525
669
  end
526
670
 
527
- # The eager limit strategy used when true is given as the value, choosing the
528
- # best strategy based on what the database supports.
671
+ # What eager limit strategy should be used when true is given as the value,
672
+ # defaults to UNION as that is the fastest strategy if the appropriate keys are indexed.
529
673
  def true_eager_limit_strategy
674
+ if self[:eager_graph] || (offset && !associated_dataset.supports_offsets_in_correlated_subqueries?)
675
+ # An SQL-based approach won't work if you are also eager graphing,
676
+ # so use a ruby based approach in that case.
677
+ :ruby
678
+ else
679
+ :union
680
+ end
681
+ end
682
+
683
+ # The eager_graph limit strategy used when true is given as the value, choosing the
684
+ # best strategy based on what the database supports.
685
+ def true_eager_graph_limit_strategy
530
686
  if associated_class.dataset.supports_window_functions?
531
687
  :window_function
532
688
  else
533
689
  :ruby
534
690
  end
535
691
  end
692
+
693
+ # A placeholder literalizer used to speed up the creation of union queries when eager
694
+ # loading a limited association.
695
+ def union_eager_loader
696
+ cached_fetch(:union_eager_loader) do
697
+ Sequel::Dataset::PlaceholderLiteralizer.loader(associated_dataset) do |pl, ds|
698
+ keys = predicate_keys
699
+ ds = ds.where(keys.map{pl.arg}.zip(keys))
700
+ if eager_loading_use_associated_key?
701
+ ds = ds.select_append(*associated_key_array)
702
+ end
703
+ ds.from_self
704
+ end
705
+ end
706
+ end
707
+
708
+ # Whether the placeholder loader can be used to load the association.
709
+ def use_placeholder_loader?
710
+ !self[:instance_specific] && !self[:eager_graph]
711
+ end
536
712
  end
537
713
 
538
714
  class ManyToOneAssociationReflection < AssociationReflection
@@ -571,6 +747,11 @@ module Sequel
571
747
  nil
572
748
  end
573
749
 
750
+ # many_to_one associations don't need a filter by associations limit strategy
751
+ def filter_by_associations_limit_strategy
752
+ nil
753
+ end
754
+
574
755
  # The expression to use on the left hand side of the IN lookup when eager loading
575
756
  def predicate_key
576
757
  cached_fetch(:predicate_key){qualified_primary_key}
@@ -623,9 +804,8 @@ module Sequel
623
804
 
624
805
  private
625
806
 
626
- def filter_by_associations_add_conditions_dataset_filter(ds)
627
- pk = qualify(associated_class.table_name, primary_keys)
628
- ds.select(*pk).where(Sequel.negate(pk.zip([])))
807
+ def filter_by_associations_conditions_associated_keys
808
+ qualify(associated_class.table_name, primary_keys)
629
809
  end
630
810
 
631
811
  def filter_by_associations_conditions_key
@@ -638,6 +818,10 @@ module Sequel
638
818
  super && self[:key]
639
819
  end
640
820
 
821
+ def predicate_key_methods
822
+ self[:keys]
823
+ end
824
+
641
825
  def reciprocal_association?(assoc_reflect)
642
826
  super && self[:keys] == assoc_reflect[:keys] && primary_key == assoc_reflect.primary_key
643
827
  end
@@ -652,6 +836,16 @@ module Sequel
652
836
  class OneToManyAssociationReflection < AssociationReflection
653
837
  ASSOCIATION_TYPES[:one_to_many] = self
654
838
 
839
+ # Support a correlated subquery limit strategy when using eager_graph.
840
+ def apply_eager_graph_limit_strategy(strategy, ds)
841
+ case strategy
842
+ when :correlated_subquery
843
+ apply_correlated_subquery_limit_strategy(ds)
844
+ else
845
+ super
846
+ end
847
+ end
848
+
655
849
  # The keys in the associated model's table related to this association
656
850
  def associated_object_keys
657
851
  self[:keys]
@@ -673,7 +867,7 @@ module Sequel
673
867
  def default_key
674
868
  :"#{underscore(demodulize(self[:model].name))}_id"
675
869
  end
676
-
870
+
677
871
  # Handle silent failure of add/remove methods if raise_on_save_failure is false.
678
872
  def handle_silent_modification_failure?
679
873
  self[:raise_on_save_failure] == false
@@ -684,7 +878,7 @@ module Sequel
684
878
  cached_fetch(:predicate_key){qualify_assoc(self[:key])}
685
879
  end
686
880
  alias qualified_key predicate_key
687
-
881
+
688
882
  # The column in the current table that the key in the associated table references.
689
883
  def primary_key
690
884
  self[:primary_key]
@@ -718,9 +912,35 @@ module Sequel
718
912
 
719
913
  private
720
914
 
721
- def filter_by_associations_add_conditions_dataset_filter(ds)
722
- k = qualify(associated_class.table_name, self[:keys])
723
- ds.select(*k).where(Sequel.negate(k.zip([])))
915
+ # Use a correlated subquery to limit the dataset. Note that this will not
916
+ # work correctly if the associated dataset uses qualified identifers in the WHERE clause,
917
+ # as they would reference the containing query instead of the subquery.
918
+ def apply_correlated_subquery_limit_strategy(ds)
919
+ table = ds.first_source_table
920
+ table_alias = ds.first_source_alias
921
+ primary_key = associated_class.primary_key
922
+ key = self[:key]
923
+ cs_alias = :t1
924
+ cs = associated_dataset.
925
+ from(Sequel.as(table, :t1)).
926
+ select(*qualify(cs_alias, primary_key)).
927
+ where(Array(qualify(cs_alias, key)).zip(Array(qualify(table_alias, key)))).
928
+ limit(*limit_and_offset)
929
+ ds.where(qualify(table_alias, primary_key)=>cs)
930
+ end
931
+
932
+ # Support correlated subquery strategy when filtering by limited associations.
933
+ def apply_filter_by_associations_limit_strategy(ds)
934
+ case filter_by_associations_limit_strategy
935
+ when :correlated_subquery
936
+ apply_correlated_subquery_limit_strategy(ds)
937
+ else
938
+ super
939
+ end
940
+ end
941
+
942
+ def filter_by_associations_conditions_associated_keys
943
+ qualify(associated_class.table_name, self[:keys])
724
944
  end
725
945
 
726
946
  def filter_by_associations_conditions_key
@@ -739,6 +959,10 @@ module Sequel
739
959
  qualify(associated_class.table_name, associated_class.primary_key)
740
960
  end
741
961
 
962
+ def predicate_key_methods
963
+ self[:primary_keys]
964
+ end
965
+
742
966
  def reciprocal_association?(assoc_reflect)
743
967
  super && self[:keys] == assoc_reflect[:keys] && primary_key == assoc_reflect.primary_key
744
968
  end
@@ -747,6 +971,19 @@ module Sequel
747
971
  def reciprocal_type
748
972
  :many_to_one
749
973
  end
974
+
975
+ # Support automatic use of correlated subqueries if :ruby option is best available option,
976
+ # MySQL is not being used, and either the associated class has a non-composite primary key
977
+ # or the database supports multiple columns in IN.
978
+ def true_eager_graph_limit_strategy
979
+ r = super
980
+ ds = associated_dataset
981
+ if r == :ruby && ds.supports_limits_in_correlated_subqueries? && (Array(associated_class.primary_key).length == 1 || ds.supports_multiple_column_in?) && (!offset || ds.supports_offsets_in_correlated_subqueries?)
982
+ :correlated_subquery
983
+ else
984
+ r
985
+ end
986
+ end
750
987
  end
751
988
 
752
989
  # Methods that turn an association that returns multiple objects into an association that
@@ -762,7 +999,17 @@ module Sequel
762
999
  # Add conditions when filtering by singular associations with orders, since the
763
1000
  # underlying relationship is probably not one-to-one.
764
1001
  def filter_by_associations_add_conditions?
765
- super || self[:order]
1002
+ super || self[:order] || self[:eager_limit_strategy] || self[:filter_limit_strategy]
1003
+ end
1004
+
1005
+ # Make sure singular associations always have 1 as the limit
1006
+ def limit_and_offset
1007
+ r = super
1008
+ if r.first == 1
1009
+ r
1010
+ else
1011
+ [1, r[1]]
1012
+ end
766
1013
  end
767
1014
 
768
1015
  # Singular associations always return a single object, not an array.
@@ -777,8 +1024,14 @@ module Sequel
777
1024
  super if self[:order] || offset
778
1025
  end
779
1026
 
1027
+ # Use a strategy for filtering by associations if there is an order or an offset,
1028
+ # or a specific limiting strategy has been specified.
1029
+ def filter_by_associations_limit_strategy
1030
+ super if self[:order] || offset || self[:eager_limit_strategy] || self[:filter_limit_strategy]
1031
+ end
1032
+
780
1033
  # Use the DISTINCT ON eager limit strategy for true if the database supports it.
781
- def true_eager_limit_strategy
1034
+ def true_eager_graph_limit_strategy
782
1035
  if associated_class.dataset.supports_ordered_distinct_on? && !offset
783
1036
  :distinct_on
784
1037
  else
@@ -838,6 +1091,36 @@ module Sequel
838
1091
  self[:uses_left_composite_keys] ? (0...self[:left_keys].length).map{|i| :"x_foreign_key_#{i}_x"} : :x_foreign_key_x
839
1092
  end
840
1093
 
1094
+ # The default eager loader used if the user doesn't override it. Extracted
1095
+ # to a method so the code can be shared with the many_through_many plugin.
1096
+ def default_eager_loader(eo)
1097
+ h = eo[:id_map]
1098
+ assign_singular = assign_singular?
1099
+ delete_rn = delete_row_number_column
1100
+ uses_lcks = self[:uses_left_composite_keys]
1101
+ left_key_alias = self[:left_key_alias]
1102
+ name = self[:name]
1103
+
1104
+ self[:model].eager_load_results(self, eo) do |assoc_record|
1105
+ assoc_record.values.delete(delete_rn) if delete_rn
1106
+ hash_key = if uses_lcks
1107
+ left_key_alias.map{|k| assoc_record.values.delete(k)}
1108
+ else
1109
+ assoc_record.values.delete(left_key_alias)
1110
+ end
1111
+ next unless objects = h[hash_key]
1112
+ if assign_singular
1113
+ objects.each do |object|
1114
+ object.associations[name] ||= assoc_record
1115
+ end
1116
+ else
1117
+ objects.each do |object|
1118
+ object.associations[name].push(assoc_record)
1119
+ end
1120
+ end
1121
+ end
1122
+ end
1123
+
841
1124
  # Default name symbol for the join table.
842
1125
  def default_join_table
843
1126
  [self[:class_name], self[:model].name].map{|i| underscore(pluralize(demodulize(i)))}.sort.join('_').to_sym
@@ -928,11 +1211,12 @@ module Sequel
928
1211
 
929
1212
  private
930
1213
 
931
- def filter_by_associations_add_conditions_dataset_filter(ds)
932
- k = qualify(join_table_alias, self[:left_keys])
933
- ds.select(*k).
934
- where(Sequel.negate(k.zip([]))).
935
- inner_join(self[:join_table], Array(self[:right_keys]).zip(right_primary_keys), :qualify=>:deep)
1214
+ def _associated_dataset
1215
+ super.inner_join(self[:join_table], self[:right_keys].zip(right_primary_keys), :qualify=>:deep)
1216
+ end
1217
+
1218
+ def filter_by_associations_conditions_associated_keys
1219
+ qualify(join_table_alias, self[:left_keys])
936
1220
  end
937
1221
 
938
1222
  def filter_by_associations_conditions_key
@@ -952,10 +1236,10 @@ module Sequel
952
1236
  qualify(join_table_alias, self[:left_keys]) + Array(qualify(associated_class.table_name, associated_class.primary_key))
953
1237
  end
954
1238
 
955
- def filter_by_associations_limit_subquery
956
- super.inner_join(self[:join_table], Array(self[:right_keys]).zip(right_primary_keys))
1239
+ def predicate_key_methods
1240
+ self[:left_primary_keys]
957
1241
  end
958
-
1242
+
959
1243
  def reciprocal_association?(assoc_reflect)
960
1244
  super && assoc_reflect[:left_keys] == self[:right_keys] &&
961
1245
  assoc_reflect[:right_keys] == self[:left_keys] &&
@@ -1066,11 +1350,9 @@ module Sequel
1066
1350
  association_reflections.values
1067
1351
  end
1068
1352
 
1069
- # Given an association reflection and a dataset, apply the
1070
- # :select, :conditions, :order, :eager, :distinct, and :eager_block
1071
- # association options to the given dataset and return the dataset
1072
- # or a modified copy of it.
1353
+ # REMOVE410
1073
1354
  def apply_association_dataset_opts(opts, ds)
1355
+ Deprecation.deprecate("Model.apply_association_dataset_opts/Model.eager_loading_dataset", "Use AssociationReflection#apply_dataset_changes/Association#reflection#apply_eager_dataset_changes instead.")
1074
1356
  ds = ds.select(*opts.select) if opts.select
1075
1357
  if c = opts[:conditions]
1076
1358
  ds = (c.is_a?(Array) && !Sequel.condition_specifier?(c)) ? ds.where(*c) : ds.where(c)
@@ -1168,6 +1450,9 @@ module Sequel
1168
1450
  # :eager_loader_key :: A symbol for the key column to use to populate the key_hash
1169
1451
  # for the eager loader. Can be set to nil to not populate the key_hash.
1170
1452
  # :extend :: A module or array of modules to extend the dataset with.
1453
+ # :filter_limit_strategy :: Determines the strategy used for enforcing limits and offsets when filtering by
1454
+ # limited associations. Possible options are :window_function, :distinct_on, or
1455
+ # :correlated_subquery depending on association type and database type.
1171
1456
  # :graph_alias_base :: The base name to use for the table alias when eager graphing. Defaults to the name
1172
1457
  # of the association. If the alias name has already been used in the query, Sequel will create
1173
1458
  # a unique alias by appending a numeric suffix (e.g. alias_0, alias_1, ...) until the alias is
@@ -1215,6 +1500,8 @@ module Sequel
1215
1500
  # the same name in both the join table and the associated table.
1216
1501
  # :setter :: Proc used to define the private _*= method for doing the work to setup the assocation
1217
1502
  # between the given object and the current object (*_to_one associations).
1503
+ # :subqueries_per_union :: The number of subqueries to use in each UNION query, for eager
1504
+ # loading limited associations using the default :union strategy.
1218
1505
  # :validate :: Set to false to not validate when implicitly saving any associated object.
1219
1506
  # === :many_to_one
1220
1507
  # :key :: foreign key in current model's table that references
@@ -1303,6 +1590,12 @@ module Sequel
1303
1590
 
1304
1591
  opts = orig_opts.merge(:type => type, :name => name, :cache=>{}, :model => self)
1305
1592
  opts[:block] = block if block
1593
+ if block || orig_opts[:block] || orig_opts[:dataset]
1594
+ # It's possible the association is instance specific, in that it depends on
1595
+ # values other than the foreign key value. This needs to be checked for
1596
+ # in certain places to disable optimizations.
1597
+ opts[:instance_specific] = true
1598
+ end
1306
1599
  opts = assoc_class.new.merge!(opts)
1307
1600
 
1308
1601
  if opts[:clone] && !opts.cloneable?(cloned_assoc)
@@ -1327,8 +1620,9 @@ module Sequel
1327
1620
 
1328
1621
  # Remove :class entry if it exists and is nil, to work with cached_fetch
1329
1622
  opts.delete(:class) unless opts[:class]
1330
-
1623
+
1331
1624
  send(:"def_#{type}", opts)
1625
+ def_association_instance_methods(opts)
1332
1626
 
1333
1627
  orig_opts.delete(:clone)
1334
1628
  orig_opts.merge!(:class_name=>opts[:class_name], :class=>opts[:class], :block=>opts[:block])
@@ -1347,7 +1641,12 @@ module Sequel
1347
1641
  association_reflections.keys
1348
1642
  end
1349
1643
 
1350
- # Modify and return eager loading dataset based on association options.
1644
+ # Eager load the association with the given eager loader options.
1645
+ def eager_load_results(opts, eo, &block)
1646
+ opts.eager_load_results(eo, &block)
1647
+ end
1648
+
1649
+ # REMOVE410
1351
1650
  def eager_loading_dataset(opts, ds, select, associations, eager_options=OPTS)
1352
1651
  ds = apply_association_dataset_opts(opts, ds)
1353
1652
  ds = ds.select(*select) if select
@@ -1411,29 +1710,58 @@ module Sequel
1411
1710
  association_module_def(name, opts, &block)
1412
1711
  association_module(opts).send(:private, name)
1413
1712
  end
1414
-
1415
- # Add the add_ instance method
1713
+
1714
+ # REMOVE410
1416
1715
  def def_add_method(opts)
1716
+ Deprecation.deprecate("Model.def_add_method", "The Model.associate method now sets up the add method you if an :adder association reflection entry is present.")
1417
1717
  association_module_def(opts.add_method, opts){|o,*args| add_associated_object(opts, o, *args)}
1418
1718
  end
1419
-
1420
- # Adds the association dataset methods to the association methods module.
1719
+
1720
+ # REMOVE410
1421
1721
  def def_association_dataset_methods(opts)
1722
+ Deprecation.deprecate("Model.def_association_dataset_methods", "The Model.associate method now sets up the association dataset methods.")
1422
1723
  association_module_def(opts.dataset_method, opts){_dataset(opts)}
1423
1724
  def_association_method(opts)
1424
1725
  end
1425
-
1726
+
1426
1727
  # Adds the association method to the association methods module.
1427
1728
  def def_association_method(opts)
1428
1729
  association_module_def(opts.association_method, opts){|*dynamic_opts, &block| load_associated_objects(opts, dynamic_opts[0], &block)}
1429
1730
  end
1430
1731
 
1732
+ # Define all of the association instance methods for this association.
1733
+ def def_association_instance_methods(opts)
1734
+ association_module_def(opts.dataset_method, opts){_dataset(opts)}
1735
+ def_association_method(opts)
1736
+
1737
+ return if opts[:read_only]
1738
+
1739
+ if opts[:setter] && opts[:_setter]
1740
+ # This is backwards due to backwards compatibility
1741
+ association_module_private_def(opts._setter_method, opts, &opts[:setter])
1742
+ association_module_def(opts.setter_method, opts, &opts[:_setter])
1743
+ end
1744
+
1745
+ if adder = opts[:adder]
1746
+ association_module_private_def(opts._add_method, opts, &adder)
1747
+ association_module_def(opts.add_method, opts){|o,*args| add_associated_object(opts, o, *args)}
1748
+ end
1749
+
1750
+ if remover = opts[:remover]
1751
+ association_module_private_def(opts._remove_method, opts, &remover)
1752
+ association_module_def(opts.remove_method, opts){|o,*args| remove_associated_object(opts, o, *args)}
1753
+ end
1754
+
1755
+ if clearer = opts[:clearer]
1756
+ association_module_private_def(opts._remove_all_method, opts, &clearer)
1757
+ association_module_def(opts.remove_all_method, opts){|*args| remove_all_associated_objects(opts, *args)}
1758
+ end
1759
+ end
1760
+
1431
1761
  # Configures many_to_many and one_through_one association reflection and adds the related association methods
1432
1762
  def def_many_to_many(opts)
1433
1763
  one_through_one = opts[:type] == :one_through_one
1434
1764
  opts[:read_only] = true if one_through_one
1435
- name = opts[:name]
1436
- model = self
1437
1765
  left = (opts[:left_key] ||= opts.default_left_key)
1438
1766
  lcks = opts[:left_keys] = Array(left)
1439
1767
  right = (opts[:right_key] ||= opts.default_right_key)
@@ -1448,49 +1776,15 @@ module Sequel
1448
1776
  rcpks = Array(opts[:right_primary_key])
1449
1777
  raise(Error, "mismatched number of right keys: #{rcks.inspect} vs #{rcpks.inspect}") unless rcks.length == rcpks.length
1450
1778
  end
1451
- uses_lcks = opts[:uses_left_composite_keys] = lcks.length > 1
1779
+ opts[:uses_left_composite_keys] = lcks.length > 1
1452
1780
  opts[:uses_right_composite_keys] = rcks.length > 1
1453
1781
  opts[:cartesian_product_number] ||= one_through_one ? 0 : 1
1454
1782
  join_table = (opts[:join_table] ||= opts.default_join_table)
1455
- left_key_alias = opts[:left_key_alias] ||= opts.default_associated_key_alias
1456
- graph_jt_conds = opts[:graph_join_table_conditions] = opts.fetch(:graph_join_table_conditions, []).to_a
1783
+ opts[:left_key_alias] ||= opts.default_associated_key_alias
1457
1784
  opts[:graph_join_table_join_type] ||= opts[:graph_join_type]
1458
1785
  opts[:after_load].unshift(:array_uniq!) if opts[:uniq]
1459
- slice_range = opts.slice_range
1460
- opts[:dataset] ||= proc{opts.associated_dataset.inner_join(join_table, rcks.zip(opts.right_primary_keys) + opts.predicate_keys.zip(lcpks.map{|k| send(k)}), :qualify=>:deep)}
1461
-
1462
- opts[:eager_loader] ||= proc do |eo|
1463
- h = eo[:id_map]
1464
- rows = eo[:rows]
1465
- r = rcks.zip(opts.right_primary_keys)
1466
- l = [[opts.predicate_key, h.keys]]
1467
-
1468
- ds = model.eager_loading_dataset(opts, opts.associated_class.inner_join(join_table, r + l, :qualify=>:deep), nil, eo[:associations], eo)
1469
- ds = opts.apply_eager_limit_strategy(ds)
1470
- opts.initialize_association_cache(rows)
1471
-
1472
- assign_singular = opts.assign_singular?
1473
- delete_rn = opts.delete_row_number_column(ds)
1474
- ds.all do |assoc_record|
1475
- assoc_record.values.delete(delete_rn) if delete_rn
1476
- hash_key = if uses_lcks
1477
- left_key_alias.map{|k| assoc_record.values.delete(k)}
1478
- else
1479
- assoc_record.values.delete(left_key_alias)
1480
- end
1481
- next unless objects = h[hash_key]
1482
- if assign_singular
1483
- objects.each do |object|
1484
- object.associations[name] ||= assoc_record
1485
- end
1486
- else
1487
- objects.each do |object|
1488
- object.associations[name].push(assoc_record)
1489
- end
1490
- end
1491
- end
1492
- opts.apply_ruby_eager_limit_strategy(rows)
1493
- end
1786
+ opts[:dataset] ||= opts.association_dataset_proc
1787
+ opts[:eager_loader] ||= opts.method(:default_eager_loader)
1494
1788
 
1495
1789
  join_type = opts[:graph_join_type]
1496
1790
  select = opts[:graph_select]
@@ -1498,6 +1792,7 @@ module Sequel
1498
1792
  only_conditions = opts[:graph_only_conditions]
1499
1793
  conditions = opts[:graph_conditions]
1500
1794
  graph_block = opts[:graph_block]
1795
+ graph_jt_conds = opts[:graph_join_table_conditions] = opts.fetch(:graph_join_table_conditions, []).to_a
1501
1796
  use_jt_only_conditions = opts.include?(:graph_join_table_only_conditions)
1502
1797
  jt_only_conditions = opts[:graph_join_table_only_conditions]
1503
1798
  jt_join_type = opts[:graph_join_table_join_type]
@@ -1520,36 +1815,27 @@ module Sequel
1520
1815
  end
1521
1816
  end
1522
1817
 
1523
- def_association_dataset_methods(opts)
1524
-
1525
1818
  return if opts[:read_only] || one_through_one
1526
1819
 
1527
- adder = opts[:adder] || proc do |o|
1820
+ opts[:adder] ||= proc do |o|
1528
1821
  h = {}
1529
1822
  lcks.zip(lcpks).each{|k, pk| h[k] = send(pk)}
1530
1823
  rcks.zip(opts.right_primary_key_methods).each{|k, pk| h[k] = o.send(pk)}
1531
1824
  _join_table_dataset(opts).insert(h)
1532
1825
  end
1533
- association_module_private_def(opts._add_method, opts, &adder)
1534
1826
 
1535
- remover = opts[:remover] || proc do |o|
1827
+ opts[:remover] ||= proc do |o|
1536
1828
  _join_table_dataset(opts).where(lcks.zip(lcpks.map{|k| send(k)}) + rcks.zip(opts.right_primary_key_methods.map{|k| o.send(k)})).delete
1537
1829
  end
1538
- association_module_private_def(opts._remove_method, opts, &remover)
1539
1830
 
1540
- clearer = opts[:clearer] || proc do
1831
+ opts[:clearer] ||= proc do
1541
1832
  _join_table_dataset(opts).where(lcks.zip(lcpks.map{|k| send(k)})).delete
1542
1833
  end
1543
- association_module_private_def(opts._remove_all_method, opts, &clearer)
1544
-
1545
- def_add_method(opts)
1546
- def_remove_methods(opts)
1547
1834
  end
1548
-
1835
+
1549
1836
  # Configures many_to_one association reflection and adds the related association methods
1550
1837
  def def_many_to_one(opts)
1551
1838
  name = opts[:name]
1552
- model = self
1553
1839
  opts[:key] = opts.default_key unless opts.has_key?(:key)
1554
1840
  key = opts[:key]
1555
1841
  opts[:eager_loader_key] = key unless opts.has_key?(:eager_loader_key)
@@ -1574,21 +1860,14 @@ module Sequel
1574
1860
  (auto_assocs[k] ||= []) << name
1575
1861
  end
1576
1862
 
1577
- opts[:dataset] ||= proc do
1578
- opts.associated_dataset.where(opts.predicate_keys.zip(cks.map{|k| send(k)}))
1579
- end
1863
+ opts[:dataset] ||= opts.association_dataset_proc
1580
1864
  opts[:eager_loader] ||= proc do |eo|
1581
1865
  h = eo[:id_map]
1582
- keys = h.keys
1866
+ pk_meths = opts.primary_key_methods
1583
1867
 
1584
- opts.initialize_association_cache(eo[:rows])
1585
-
1586
- # Skip eager loading if no objects have a foreign key for this association
1587
- unless keys.empty?
1588
- klass = opts.associated_class
1589
- model.eager_loading_dataset(opts, klass.where(opts.predicate_key=>keys), nil, eo[:associations], eo).all do |assoc_record|
1590
- hash_key = uses_cks ? opts.primary_key_methods.map{|k| assoc_record.send(k)} : assoc_record.send(opts.primary_key_method)
1591
- next unless objects = h[hash_key]
1868
+ eager_load_results(opts, eo) do |assoc_record|
1869
+ hash_key = uses_cks ? pk_meths.map{|k| assoc_record.send(k)} : assoc_record.send(opts.primary_key_method)
1870
+ if objects = h[hash_key]
1592
1871
  objects.each{|object| object.associations[name] = assoc_record}
1593
1872
  end
1594
1873
  end
@@ -1606,20 +1885,16 @@ module Sequel
1606
1885
  ds.graph(eager_graph_dataset(opts, eo), use_only_conditions ? only_conditions : opts.primary_keys.zip(graph_cks) + conditions, eo.merge(:select=>select, :join_type=>eo[:join_type]||join_type, :qualify=>:deep, :from_self_alias=>eo[:from_self_alias]), &graph_block)
1607
1886
  end
1608
1887
 
1609
- def_association_dataset_methods(opts)
1610
-
1611
1888
  return if opts[:read_only]
1612
1889
 
1613
- setter = opts[:setter] || proc{|o| cks.zip(opts.primary_key_methods).each{|k, pk| send(:"#{k}=", (o.send(pk) if o))}}
1614
- association_module_private_def(opts._setter_method, opts, &setter)
1615
- association_module_def(opts.setter_method, opts){|o| set_associated_object(opts, o)}
1890
+ opts[:setter] ||= proc{|o| cks.zip(opts.primary_key_methods).each{|k, pk| send(:"#{k}=", (o.send(pk) if o))}}
1891
+ opts[:_setter] = proc{|o| set_associated_object(opts, o)}
1616
1892
  end
1617
1893
 
1618
1894
  # Configures one_to_many and one_to_one association reflections and adds the related association methods
1619
1895
  def def_one_to_many(opts)
1620
1896
  one_to_one = opts[:type] == :one_to_one
1621
1897
  name = opts[:name]
1622
- model = self
1623
1898
  key = (opts[:key] ||= opts.default_key)
1624
1899
  km = opts[:key_method] ||= opts[:key]
1625
1900
  cks = opts[:keys] = Array(key)
@@ -1631,23 +1906,14 @@ module Sequel
1631
1906
  pkcs = opts[:primary_key_columns] ||= Array(pkc)
1632
1907
  raise(Error, "mismatched number of keys: #{cks.inspect} vs #{cpks.inspect}") unless cks.length == cpks.length
1633
1908
  uses_cks = opts[:uses_composite_keys] = cks.length > 1
1634
- slice_range = opts.slice_range
1635
- opts[:dataset] ||= proc do
1636
- opts.associated_dataset.where(opts.predicate_keys.zip(cpks.map{|k| send(k)}))
1637
- end
1909
+ opts[:dataset] ||= opts.association_dataset_proc
1638
1910
  opts[:eager_loader] ||= proc do |eo|
1639
1911
  h = eo[:id_map]
1640
- rows = eo[:rows]
1641
1912
  reciprocal = opts.reciprocal
1642
- klass = opts.associated_class
1643
-
1644
- ds = model.eager_loading_dataset(opts, klass.where(opts.predicate_key=>h.keys), nil, eo[:associations], eo)
1645
- ds = opts.apply_eager_limit_strategy(ds)
1646
- opts.initialize_association_cache(rows)
1647
-
1648
1913
  assign_singular = opts.assign_singular?
1649
- delete_rn = opts.delete_row_number_column(ds)
1650
- ds.all do |assoc_record|
1914
+ delete_rn = opts.delete_row_number_column
1915
+
1916
+ eager_load_results(opts, eo) do |assoc_record|
1651
1917
  assoc_record.values.delete(delete_rn) if delete_rn
1652
1918
  hash_key = uses_cks ? km.map{|k| assoc_record.send(k)} : assoc_record.send(km)
1653
1919
  next unless objects = h[hash_key]
@@ -1665,7 +1931,6 @@ module Sequel
1665
1931
  end
1666
1932
  end
1667
1933
  end
1668
- opts.apply_ruby_eager_limit_strategy(rows)
1669
1934
  end
1670
1935
 
1671
1936
  join_type = opts[:graph_join_type]
@@ -1683,50 +1948,40 @@ module Sequel
1683
1948
  ds
1684
1949
  end
1685
1950
 
1686
- def_association_dataset_methods(opts)
1687
-
1951
+ return if opts[:read_only]
1952
+
1953
+ save_opts = {:validate=>opts[:validate]}
1688
1954
  ck_nil_hash ={}
1689
1955
  cks.each{|k| ck_nil_hash[k] = nil}
1690
1956
 
1691
- unless opts[:read_only]
1692
- save_opts = {:validate=>opts[:validate]}
1693
-
1694
- if one_to_one
1695
- setter = opts[:setter] || proc do |o|
1696
- up_ds = _apply_association_options(opts, opts.associated_dataset.where(cks.zip(cpks.map{|k| send(k)})))
1697
- if o
1698
- up_ds = up_ds.exclude(o.pk_hash) unless o.new?
1699
- cks.zip(cpks).each{|k, pk| o.send(:"#{k}=", send(pk))}
1700
- end
1701
- checked_transaction do
1702
- up_ds.update(ck_nil_hash)
1703
- o.save(save_opts) || raise(Sequel::Error, "invalid associated object, cannot save") if o
1704
- end
1705
- end
1706
- association_module_private_def(opts._setter_method, opts, &setter)
1707
- association_module_def(opts.setter_method, opts){|o| set_one_to_one_associated_object(opts, o)}
1708
- else
1709
- save_opts[:raise_on_failure] = opts[:raise_on_save_failure] != false
1710
-
1711
- adder = opts[:adder] || proc do |o|
1957
+ if one_to_one
1958
+ opts[:setter] ||= proc do |o|
1959
+ up_ds = _apply_association_options(opts, opts.associated_dataset.where(cks.zip(cpks.map{|k| send(k)})))
1960
+ if o
1961
+ up_ds = up_ds.exclude(o.pk_hash) unless o.new?
1712
1962
  cks.zip(cpks).each{|k, pk| o.send(:"#{k}=", send(pk))}
1713
- o.save(save_opts)
1714
1963
  end
1715
- association_module_private_def(opts._add_method, opts, &adder)
1716
-
1717
- remover = opts[:remover] || proc do |o|
1718
- cks.each{|k| o.send(:"#{k}=", nil)}
1719
- o.save(save_opts)
1964
+ checked_transaction do
1965
+ up_ds.update(ck_nil_hash)
1966
+ o.save(save_opts) || raise(Sequel::Error, "invalid associated object, cannot save") if o
1720
1967
  end
1721
- association_module_private_def(opts._remove_method, opts, &remover)
1968
+ end
1969
+ opts[:_setter] = proc{|o| set_one_to_one_associated_object(opts, o)}
1970
+ else
1971
+ save_opts[:raise_on_failure] = opts[:raise_on_save_failure] != false
1722
1972
 
1723
- clearer = opts[:clearer] || proc do
1724
- _apply_association_options(opts, opts.associated_dataset.where(cks.zip(cpks.map{|k| send(k)}))).update(ck_nil_hash)
1725
- end
1726
- association_module_private_def(opts._remove_all_method, opts, &clearer)
1973
+ opts[:adder] ||= proc do |o|
1974
+ cks.zip(cpks).each{|k, pk| o.send(:"#{k}=", send(pk))}
1975
+ o.save(save_opts)
1976
+ end
1977
+
1978
+ opts[:remover] ||= proc do |o|
1979
+ cks.each{|k| o.send(:"#{k}=", nil)}
1980
+ o.save(save_opts)
1981
+ end
1727
1982
 
1728
- def_add_method(opts)
1729
- def_remove_methods(opts)
1983
+ opts[:clearer] ||= proc do
1984
+ _apply_association_options(opts, opts.associated_dataset.where(cks.zip(cpks.map{|k| send(k)}))).update(ck_nil_hash)
1730
1985
  end
1731
1986
  end
1732
1987
  end
@@ -1741,12 +1996,13 @@ module Sequel
1741
1996
  def_one_to_many(opts)
1742
1997
  end
1743
1998
 
1744
- # Add the remove_ and remove_all instance methods
1999
+ # REMOVE410
1745
2000
  def def_remove_methods(opts)
2001
+ Deprecation.deprecate("Model.def_remove_methods", "The Model.associate method now sets up the remove/remove_all methods for you if a :remover or :clearer association reflection entry is present.")
1746
2002
  association_module_def(opts.remove_method, opts){|o,*args| remove_associated_object(opts, o, *args)}
1747
2003
  association_module_def(opts.remove_all_method, opts){|*args| remove_all_associated_objects(opts, *args)}
1748
2004
  end
1749
-
2005
+
1750
2006
  # Return dataset to graph into given the association reflection, applying the :callback option if set.
1751
2007
  def eager_graph_dataset(opts, eager_options)
1752
2008
  ds = opts.associated_class.dataset
@@ -1796,6 +2052,13 @@ module Sequel
1796
2052
  ds
1797
2053
  end
1798
2054
 
2055
+ # A placeholder literalizer that can be used to load the association, or nil to not use one.
2056
+ def _associated_object_loader(opts, dynamic_opts)
2057
+ if !dynamic_opts[:callback] && (loader = opts.placeholder_loader)
2058
+ loader
2059
+ end
2060
+ end
2061
+
1799
2062
  # Return an association dataset for the given association reflection
1800
2063
  def _dataset(opts)
1801
2064
  raise(Sequel::Error, "model object #{inspect} does not have a primary key") if opts.dataset_need_primary_key? && !pk
@@ -1819,10 +2082,19 @@ module Sequel
1819
2082
  _load_associated_object_array(opts, dynamic_opts).first
1820
2083
  end
1821
2084
 
2085
+ # Return the associated single object using a primary key lookup on the associated class.
2086
+ def _load_associated_object_via_primary_key(opts)
2087
+ opts.associated_class.send(:primary_key_lookup, ((fk = opts[:key]).is_a?(Array) ? fk.map{|c| send(c)} : send(fk)))
2088
+ end
2089
+
1822
2090
  # Load the associated objects for the given association reflection and dynamic options
1823
2091
  # as an array.
1824
2092
  def _load_associated_object_array(opts, dynamic_opts)
1825
- _associated_dataset(opts, dynamic_opts).all
2093
+ if loader = _associated_object_loader(opts, dynamic_opts)
2094
+ loader.all(*opts.predicate_key_values(self))
2095
+ else
2096
+ _associated_dataset(opts, dynamic_opts).all
2097
+ end
1826
2098
  end
1827
2099
 
1828
2100
  # Return the associated objects from the dataset, without association callbacks, reciprocals, and caching.
@@ -1832,7 +2104,7 @@ module Sequel
1832
2104
  if opts.returns_array?
1833
2105
  _load_associated_object_array(opts, dynamic_opts)
1834
2106
  elsif load_with_primary_key_lookup?(opts, dynamic_opts)
1835
- opts.associated_class.send(:primary_key_lookup, ((fk = opts[:key]).is_a?(Array) ? fk.map{|c| send(c)} : send(fk)))
2107
+ _load_associated_object_via_primary_key(opts)
1836
2108
  else
1837
2109
  _load_associated_object(opts, dynamic_opts)
1838
2110
  end
@@ -2196,23 +2468,12 @@ END
2196
2468
  # Each association's order, if defined, is respected.
2197
2469
  # If the association uses a block or has an :eager_block argument, it is used.
2198
2470
  def eager(*associations)
2199
- opt = @opts[:eager]
2200
- opt = opt ? opt.dup : {}
2201
- associations.flatten.each do |association|
2202
- case association
2203
- when Symbol
2204
- check_association(model, association)
2205
- opt[association] = nil
2206
- when Hash
2207
- association.keys.each{|assoc| check_association(model, assoc)}
2208
- opt.merge!(association)
2209
- else
2210
- raise(Sequel::Error, 'Associations must be in the form of a symbol or hash')
2211
- end
2212
- end
2213
- clone(:eager=>opt)
2471
+ opts = @opts[:eager]
2472
+ association_opts = eager_options_for_associations(associations)
2473
+ opts = opts ? opts.merge(association_opts) : association_opts
2474
+ clone(:eager=>opts)
2214
2475
  end
2215
-
2476
+
2216
2477
  # The secondary eager loading method. Loads all associations in a single query. This
2217
2478
  # method should only be used if you need to filter or order based on columns in associated tables.
2218
2479
  #
@@ -2247,6 +2508,7 @@ END
2247
2508
  # Appropriate :limit_strategy values are:
2248
2509
  # true :: Pick the most appropriate based on what the database supports
2249
2510
  # :distinct_on :: Force use of DISTINCT ON stategy (*_one associations only)
2511
+ # :correlated_subquery :: Force use of correlated subquery strategy (one_to_* associations only)
2250
2512
  # :window_function :: Force use of window function strategy
2251
2513
  # :ruby :: Don't modify the SQL, implement limits/offsets with array slicing
2252
2514
  #
@@ -2321,7 +2583,6 @@ END
2321
2583
  associations = assoc.is_a?(Array) ? assoc : [assoc]
2322
2584
  end
2323
2585
  end
2324
- orig_ds = ds
2325
2586
  local_opts = ds.opts[:eager_graph][:local]
2326
2587
  limit_strategy = r.eager_graph_limit_strategy(local_opts[:limit_strategy])
2327
2588
  ds = loader.call(:self=>ds, :table_alias=>assoc_table_alias, :implicit_qualifier=>(ta == ds.opts[:eager_graph][:master]) ? first_source : qualifier_from_alias_symbol(ta, first_source), :callback=>callback, :join_type=>local_opts[:join_type], :limit_strategy=>limit_strategy, :from_self_alias=>ds.opts[:eager_graph][:master])
@@ -2391,6 +2652,25 @@ END
2391
2652
  end
2392
2653
  end
2393
2654
 
2655
+ # Process the array of associations arguments (Symbols, Arrays, and Hashes),
2656
+ # and return a hash of options suitable for cascading.
2657
+ def eager_options_for_associations(associations)
2658
+ opts = {}
2659
+ associations.flatten.each do |association|
2660
+ case association
2661
+ when Symbol
2662
+ check_association(model, association)
2663
+ opts[association] = nil
2664
+ when Hash
2665
+ association.keys.each{|assoc| check_association(model, assoc)}
2666
+ opts.merge!(association)
2667
+ else
2668
+ raise(Sequel::Error, 'Associations must be in the form of a symbol or hash')
2669
+ end
2670
+ end
2671
+ opts
2672
+ end
2673
+
2394
2674
  # Return an expression for filtering by the given association reflection and associated object.
2395
2675
  def association_filter_expression(op, ref, obj)
2396
2676
  meth = :"#{ref[:type]}_association_filter_expression"