sequel 4.9.0 → 4.10.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 (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"