sequel 4.7.0 → 4.8.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 (50) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +46 -0
  3. data/README.rdoc +25 -1
  4. data/doc/active_record.rdoc +1 -1
  5. data/doc/advanced_associations.rdoc +143 -17
  6. data/doc/association_basics.rdoc +80 -59
  7. data/doc/release_notes/4.8.0.txt +175 -0
  8. data/lib/sequel/adapters/odbc.rb +1 -1
  9. data/lib/sequel/adapters/odbc/mssql.rb +4 -2
  10. data/lib/sequel/adapters/shared/postgres.rb +19 -3
  11. data/lib/sequel/adapters/shared/sqlite.rb +3 -3
  12. data/lib/sequel/ast_transformer.rb +1 -1
  13. data/lib/sequel/dataset/actions.rb +1 -1
  14. data/lib/sequel/dataset/graph.rb +23 -9
  15. data/lib/sequel/dataset/misc.rb +2 -2
  16. data/lib/sequel/dataset/sql.rb +3 -3
  17. data/lib/sequel/extensions/columns_introspection.rb +1 -1
  18. data/lib/sequel/extensions/mssql_emulate_lateral_with_apply.rb +1 -1
  19. data/lib/sequel/extensions/pg_array.rb +1 -1
  20. data/lib/sequel/extensions/pg_array_ops.rb +6 -0
  21. data/lib/sequel/extensions/pg_hstore_ops.rb +7 -0
  22. data/lib/sequel/extensions/pg_json_ops.rb +5 -0
  23. data/lib/sequel/extensions/query.rb +8 -2
  24. data/lib/sequel/extensions/to_dot.rb +1 -1
  25. data/lib/sequel/model/associations.rb +476 -152
  26. data/lib/sequel/plugins/class_table_inheritance.rb +11 -3
  27. data/lib/sequel/plugins/dataset_associations.rb +21 -18
  28. data/lib/sequel/plugins/many_through_many.rb +87 -20
  29. data/lib/sequel/plugins/nested_attributes.rb +12 -0
  30. data/lib/sequel/plugins/pg_array_associations.rb +31 -12
  31. data/lib/sequel/plugins/single_table_inheritance.rb +9 -1
  32. data/lib/sequel/sql.rb +1 -0
  33. data/lib/sequel/version.rb +1 -1
  34. data/spec/adapters/mssql_spec.rb +2 -2
  35. data/spec/adapters/postgres_spec.rb +7 -0
  36. data/spec/core/object_graph_spec.rb +250 -196
  37. data/spec/extensions/core_refinements_spec.rb +1 -1
  38. data/spec/extensions/dataset_associations_spec.rb +100 -6
  39. data/spec/extensions/many_through_many_spec.rb +1002 -19
  40. data/spec/extensions/nested_attributes_spec.rb +24 -0
  41. data/spec/extensions/pg_array_associations_spec.rb +17 -12
  42. data/spec/extensions/pg_array_spec.rb +4 -2
  43. data/spec/extensions/spec_helper.rb +1 -1
  44. data/spec/integration/associations_test.rb +1003 -48
  45. data/spec/integration/dataset_test.rb +12 -5
  46. data/spec/integration/prepared_statement_test.rb +1 -1
  47. data/spec/integration/type_test.rb +1 -1
  48. data/spec/model/associations_spec.rb +467 -130
  49. data/spec/model/eager_loading_spec.rb +332 -5
  50. metadata +5 -3
@@ -1,6 +1,12 @@
1
- # The query extension adds Sequel::Dataset#query which allows
1
+ # The query extension adds a query method which allows
2
2
  # a different way to construct queries instead of the usual
3
- # method chaining. See Sequel::Dataset#query for details.
3
+ # method chaining:
4
+ #
5
+ # dataset = DB[:items].query do
6
+ # select :x, :y, :z
7
+ # filter{(x > 1) & (y > 2)}
8
+ # reverse :z
9
+ # end
4
10
  #
5
11
  # You can load this extension into specific datasets:
6
12
  #
@@ -91,7 +91,7 @@ module Sequel
91
91
  when SQL::AliasedExpression
92
92
  dot "AliasedExpression"
93
93
  v(e.expression, :expression)
94
- v(e.aliaz, :alias)
94
+ v(e.alias, :alias)
95
95
  when SQL::CaseExpression
96
96
  dot "CaseExpression"
97
97
  v(e.expression, :expression) if e.expression
@@ -70,12 +70,84 @@ module Sequel
70
70
  end
71
71
  ds = ds.order(*self[:order]) if self[:order]
72
72
  ds = ds.limit(*self[:limit]) if self[:limit]
73
- ds = ds.limit(1) if !returns_array? && self[:key]
73
+ ds = ds.limit(1) if limit_to_single_row?
74
74
  ds = ds.eager(*self[:eager]) if self[:eager]
75
75
  ds = ds.distinct if self[:distinct]
76
76
  ds
77
77
  end
78
-
78
+
79
+ # Apply the eager graph limit strategy to the dataset to graph into the current dataset, or return
80
+ # the dataset unmodified if no SQL limit strategy is needed.
81
+ def apply_eager_graph_limit_strategy(strategy, ds)
82
+ case strategy
83
+ when :distinct_on
84
+ apply_distinct_on_eager_limit_strategy(ds.order_prepend(*self[:order]))
85
+ when :window_function
86
+ apply_window_function_eager_limit_strategy(ds.order_prepend(*self[:order])).select(*ds.columns)
87
+ else
88
+ ds
89
+ end
90
+ end
91
+
92
+ # Apply an eager limit strategy to the dataset, or return the dataset
93
+ # unmodified if it doesn't need an eager limit strategy.
94
+ def apply_eager_limit_strategy(ds)
95
+ case eager_limit_strategy
96
+ when :distinct_on
97
+ apply_distinct_on_eager_limit_strategy(ds)
98
+ when :window_function
99
+ apply_window_function_eager_limit_strategy(ds)
100
+ else
101
+ ds
102
+ end
103
+ end
104
+
105
+ # Use DISTINCT ON and ORDER BY clauses to limit the results to the first record with matching keys.
106
+ def apply_distinct_on_eager_limit_strategy(ds)
107
+ keys = predicate_key
108
+ ds.distinct(*keys).order_prepend(*keys)
109
+ end
110
+
111
+ # Use a window function to limit the results of the eager loading dataset.
112
+ def apply_window_function_eager_limit_strategy(ds)
113
+ rn = ds.row_number_column
114
+ limit, offset = limit_and_offset
115
+ ds = ds.unordered.select_append{|o| o.row_number{}.over(:partition=>predicate_key, :order=>ds.opts[:order]).as(rn)}.from_self
116
+ ds = if !returns_array?
117
+ ds.where(rn => offset ? offset+1 : 1)
118
+ elsif offset
119
+ offset += 1
120
+ if limit
121
+ ds.where(rn => (offset...(offset+limit)))
122
+ else
123
+ ds.where{SQL::Identifier.new(rn) >= offset}
124
+ end
125
+ else
126
+ ds.where{SQL::Identifier.new(rn) <= limit}
127
+ end
128
+ end
129
+
130
+ # If the ruby eager limit strategy is being used, slice the array using the slice
131
+ # range to return the object(s) at the correct offset/limit.
132
+ def apply_ruby_eager_limit_strategy(rows)
133
+ if eager_limit_strategy == :ruby
134
+ name = self[:name]
135
+ if returns_array?
136
+ range = slice_range
137
+ rows.each{|o| o.associations[name] = o.associations[name][range] || []}
138
+ elsif slice_range
139
+ offset = slice_range.begin
140
+ rows.each{|o| o.associations[name] = o.associations[name][offset]}
141
+ end
142
+ end
143
+ end
144
+
145
+ # Whether the associations cache should use an array when storing the
146
+ # associated records during eager loading.
147
+ def assign_singular?
148
+ !returns_array?
149
+ end
150
+
79
151
  # Whether this association can have associated objects, given the current
80
152
  # object. Should be false if obj cannot have associated objects because
81
153
  # the necessary key columns are NULL.
@@ -83,6 +155,12 @@ module Sequel
83
155
  true
84
156
  end
85
157
 
158
+ # Whether you are able to clone from the given association type to the current
159
+ # association type, true by default only if the types match.
160
+ def cloneable?(ref)
161
+ ref[:type] == self[:type]
162
+ end
163
+
86
164
  # Name symbol for the dataset association method
87
165
  def dataset_method
88
166
  :"#{self[:name]}_dataset"
@@ -93,23 +171,41 @@ module Sequel
93
171
  true
94
172
  end
95
173
 
174
+ # Return the symbol used for the row number column if the window function
175
+ # eager limit strategy is being used, or nil otherwise.
176
+ def delete_row_number_column(ds)
177
+ if eager_limit_strategy == :window_function
178
+ ds.row_number_column
179
+ end
180
+ end
181
+
182
+ # The eager_graph limit strategy to use for this dataset
183
+ def eager_graph_limit_strategy(strategy)
184
+ if self[:limit] || !returns_array?
185
+ strategy = strategy[self[:name]] if strategy.is_a?(Hash)
186
+ case strategy
187
+ when true
188
+ true_eager_limit_strategy
189
+ when Symbol
190
+ strategy
191
+ else
192
+ if returns_array? || offset
193
+ :ruby
194
+ end
195
+ end
196
+ end
197
+ end
198
+
96
199
  # The eager limit strategy to use for this dataset.
97
200
  def eager_limit_strategy
98
201
  cached_fetch(:_eager_limit_strategy) do
99
- if self[:limit]
100
- case s = cached_fetch(:eager_limit_strategy){self[:model].default_eager_limit_strategy || :ruby}
202
+ if self[:limit] || !returns_array?
203
+ case s = cached_fetch(:eager_limit_strategy){default_eager_limit_strategy}
101
204
  when true
102
- ds = associated_class.dataset
103
- if ds.supports_window_functions?
104
- :window_function
105
- else
106
- :ruby
107
- end
205
+ true_eager_limit_strategy
108
206
  else
109
207
  s
110
208
  end
111
- else
112
- nil
113
209
  end
114
210
  end
115
211
  end
@@ -140,7 +236,7 @@ module Sequel
140
236
  # Whether additional conditions should be added when using the filter
141
237
  # by associations support.
142
238
  def filter_by_associations_add_conditions?
143
- self[:conditions] || self[:eager_block]
239
+ self[:conditions] || self[:eager_block] || self[:limit]
144
240
  end
145
241
 
146
242
  # The expression to use for the additional conditions to be added for
@@ -158,6 +254,16 @@ module Sequel
158
254
  false
159
255
  end
160
256
 
257
+ # Initialize the associations cache for the current association for the given objects.
258
+ def initialize_association_cache(objects)
259
+ name = self[:name]
260
+ if assign_singular?
261
+ objects.each{|object| object.associations[name] = nil}
262
+ else
263
+ objects.each{|object| object.associations[name] = []}
264
+ end
265
+ end
266
+
161
267
  # The limit and offset for this association (returned as a two element array).
162
268
  def limit_and_offset
163
269
  if (v = self[:limit]).is_a?(Array)
@@ -319,6 +425,48 @@ module Sequel
319
425
  end
320
426
  end
321
427
 
428
+ # Apply a limit strategy to the given dataset so that filter by
429
+ # associations works with a limited dataset.
430
+ def apply_filter_by_associations_limit_strategy(ds)
431
+ case eager_limit_strategy
432
+ when :distinct_on
433
+ apply_filter_by_associations_distinct_on_limit_strategy(ds)
434
+ when :window_function
435
+ apply_filter_by_associations_window_function_limit_strategy(ds)
436
+ else
437
+ ds
438
+ end
439
+ end
440
+
441
+ # Apply a distinct on eager limit strategy using IN with a subquery
442
+ # that uses DISTINCT ON to ensure only the first matching record for
443
+ # each key is included.
444
+ def apply_filter_by_associations_distinct_on_limit_strategy(ds)
445
+ k = filter_by_associations_limit_key
446
+ ds.where(k=>apply_distinct_on_eager_limit_strategy(filter_by_associations_limit_subquery.select(*k)))
447
+ end
448
+
449
+ # Apply a distinct on eager limit strategy using IN with a subquery
450
+ # that uses a filter on the row_number window function to ensure
451
+ # that only rows inside the limit are returned.
452
+ 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))
454
+ end
455
+
456
+ # The associated_dataset with the eager_block callback already applied.
457
+ def associated_eager_dataset
458
+ cached_fetch(:associated_eager_dataset) do
459
+ ds = associated_dataset
460
+ ds = self[:eager_block].call(ds) if self[:eager_block]
461
+ ds
462
+ end
463
+ end
464
+
465
+ # The default eager limit strategy to use for this association
466
+ def default_eager_limit_strategy
467
+ self[:model].default_eager_limit_strategy || :ruby
468
+ end
469
+
322
470
  # The conditions to add to the filter by associations conditions
323
471
  # subquery to restrict it to to the object(s) that was used as the
324
472
  # filter value.
@@ -339,13 +487,28 @@ module Sequel
339
487
  # values.
340
488
  def filter_by_associations_conditions_dataset
341
489
  cached_fetch(:filter_by_associations_conditions_dataset) do
342
- ds = associated_dataset.unordered.unlimited
490
+ ds = associated_eager_dataset.unordered.unlimited
343
491
  ds = filter_by_associations_add_conditions_dataset_filter(ds)
344
- ds = self[:eager_block].call(ds) if self[:eager_block]
492
+ ds = apply_filter_by_associations_limit_strategy(ds)
345
493
  ds
346
494
  end
347
495
  end
348
496
 
497
+ # The base subquery to use when filtering by limited associations
498
+ def filter_by_associations_limit_subquery
499
+ associated_eager_dataset.unlimited
500
+ end
501
+
502
+ # Whether to limit the associated dataset to a single row.
503
+ def limit_to_single_row?
504
+ !returns_array?
505
+ end
506
+
507
+ # Any offset to use for this association (or nil if there is no offset).
508
+ def offset
509
+ limit_and_offset.last
510
+ end
511
+
349
512
  # Whether the given association reflection is possible reciprocal
350
513
  # association for the current association reflection.
351
514
  def reciprocal_association?(assoc_reflect)
@@ -360,6 +523,16 @@ module Sequel
360
523
  def transform(s)
361
524
  s.is_a?(Array) ? s.map(&Proc.new) : (yield s)
362
525
  end
526
+
527
+ # The eager limit strategy used when true is given as the value, choosing the
528
+ # best strategy based on what the database supports.
529
+ def true_eager_limit_strategy
530
+ if associated_class.dataset.supports_window_functions?
531
+ :window_function
532
+ else
533
+ :ruby
534
+ end
535
+ end
363
536
  end
364
537
 
365
538
  class ManyToOneAssociationReflection < AssociationReflection
@@ -388,6 +561,11 @@ module Sequel
388
561
  self[:key].nil?
389
562
  end
390
563
 
564
+ # many_to_one associations don't need an eager_graph limit strategy
565
+ def eager_graph_limit_strategy(_)
566
+ nil
567
+ end
568
+
391
569
  # many_to_one associations don't need an eager limit strategy
392
570
  def eager_limit_strategy
393
571
  nil
@@ -454,6 +632,12 @@ module Sequel
454
632
  qualify(self[:model].table_name, self[:key_column])
455
633
  end
456
634
 
635
+ # many_to_one associations do not need to be limited to a single row if they
636
+ # explicitly do not have a key.
637
+ def limit_to_single_row?
638
+ super && self[:key]
639
+ end
640
+
457
641
  def reciprocal_association?(assoc_reflect)
458
642
  super && self[:keys] == assoc_reflect[:keys] && primary_key == assoc_reflect.primary_key
459
643
  end
@@ -479,6 +663,11 @@ module Sequel
479
663
  !self[:primary_keys].any?{|k| obj.send(k).nil?}
480
664
  end
481
665
 
666
+ # one_to_many and one_to_one associations can be clones
667
+ def cloneable?(ref)
668
+ ref[:type] == :one_to_many || ref[:type] == :one_to_one
669
+ end
670
+
482
671
  # Default foreign key name symbol for key in associated table that points to
483
672
  # current table's primary key.
484
673
  def default_key
@@ -538,6 +727,18 @@ module Sequel
538
727
  qualify(self[:model].table_name, self[:primary_key_column])
539
728
  end
540
729
 
730
+ def filter_by_associations_limit_alias_key
731
+ Array(filter_by_associations_limit_key)
732
+ end
733
+
734
+ def filter_by_associations_limit_aliases
735
+ filter_by_associations_limit_alias_key.map{|v| v.column}
736
+ end
737
+
738
+ def filter_by_associations_limit_key
739
+ qualify(associated_class.table_name, associated_class.primary_key)
740
+ end
741
+
541
742
  def reciprocal_association?(assoc_reflect)
542
743
  super && self[:keys] == assoc_reflect[:keys] && primary_key == assoc_reflect.primary_key
543
744
  end
@@ -547,46 +748,48 @@ module Sequel
547
748
  :many_to_one
548
749
  end
549
750
  end
550
-
551
- class OneToOneAssociationReflection < OneToManyAssociationReflection
552
- ASSOCIATION_TYPES[:one_to_one] = self
553
-
554
- # one_to_one associations don't use an eager limit strategy by default, but
555
- # support both DISTINCT ON and window functions as strategies.
556
- def eager_limit_strategy
557
- cached_fetch(:_eager_limit_strategy) do
558
- offset = limit_and_offset.last
559
- case s = self.fetch(:eager_limit_strategy){(self[:model].default_eager_limit_strategy || :ruby) if offset}
560
- when Symbol
561
- s
562
- when true
563
- ds = associated_class.dataset
564
- if ds.supports_ordered_distinct_on? && offset.nil?
565
- :distinct_on
566
- elsif ds.supports_window_functions?
567
- :window_function
568
- else
569
- :ruby
570
- end
571
- else
572
- nil
573
- end
574
- end
751
+
752
+ # Methods that turn an association that returns multiple objects into an association that
753
+ # returns a single object.
754
+ module SingularAssociationReflection
755
+ # Singular associations do not assign singular if they are using the ruby eager limit strategy
756
+ # and have a slice range, since they need to store the array of associated objects in order to
757
+ # pick the correct one with an offset.
758
+ def assign_singular?
759
+ super && (eager_limit_strategy != :ruby || !slice_range)
575
760
  end
576
761
 
577
- # The limit and offset for this association (returned as a two element array).
578
- def limit_and_offset
579
- if (v = self[:limit]).is_a?(Array)
580
- v
581
- else
582
- [v, nil]
583
- end
762
+ # Add conditions when filtering by singular associations with orders, since the
763
+ # underlying relationship is probably not one-to-one.
764
+ def filter_by_associations_add_conditions?
765
+ super || self[:order]
584
766
  end
585
767
 
586
- # one_to_one associations return a single object, not an array
768
+ # Singular associations always return a single object, not an array.
587
769
  def returns_array?
588
770
  false
589
771
  end
772
+
773
+ private
774
+
775
+ # Only use a eager limit strategy by default if there is an offset or an order.
776
+ def default_eager_limit_strategy
777
+ super if self[:order] || offset
778
+ end
779
+
780
+ # Use the DISTINCT ON eager limit strategy for true if the database supports it.
781
+ def true_eager_limit_strategy
782
+ if associated_class.dataset.supports_ordered_distinct_on? && !offset
783
+ :distinct_on
784
+ else
785
+ super
786
+ end
787
+ end
788
+ end
789
+
790
+ class OneToOneAssociationReflection < OneToManyAssociationReflection
791
+ ASSOCIATION_TYPES[:one_to_one] = self
792
+ include SingularAssociationReflection
590
793
  end
591
794
 
592
795
  class ManyToManyAssociationReflection < AssociationReflection
@@ -597,6 +800,17 @@ module Sequel
597
800
  self[:left_key_alias]
598
801
  end
599
802
 
803
+ # Array of associated keys used when eagerly loading.
804
+ def associated_key_array
805
+ cached_fetch(:associated_key_array) do
806
+ if self[:uses_left_composite_keys]
807
+ associated_key_alias.zip(predicate_keys).map{|a, k| SQL::AliasedExpression.new(k, a)}
808
+ else
809
+ [SQL::AliasedExpression.new(predicate_key, associated_key_alias)]
810
+ end
811
+ end
812
+ end
813
+
600
814
  # The column to use for the associated key when eagerly loading
601
815
  def associated_key_column
602
816
  self[:left_key]
@@ -613,6 +827,11 @@ module Sequel
613
827
  !self[:left_primary_keys].any?{|k| obj.send(k).nil?}
614
828
  end
615
829
 
830
+ # one_through_one and many_to_many associations can be clones
831
+ def cloneable?(ref)
832
+ ref[:type] == :many_to_many || ref[:type] == :one_through_one
833
+ end
834
+
616
835
  # The default associated key alias(es) to use when eager loading
617
836
  # associations via eager.
618
837
  def default_associated_key_alias
@@ -710,7 +929,9 @@ module Sequel
710
929
  private
711
930
 
712
931
  def filter_by_associations_add_conditions_dataset_filter(ds)
713
- ds.select(*qualify(join_table_alias, self[:left_keys])).
932
+ k = qualify(join_table_alias, self[:left_keys])
933
+ ds.select(*k).
934
+ where(Sequel.negate(k.zip([]))).
714
935
  inner_join(self[:join_table], Array(self[:right_keys]).zip(right_primary_keys), :qualify=>:deep)
715
936
  end
716
937
 
@@ -718,6 +939,23 @@ module Sequel
718
939
  qualify(self[:model].table_name, self[:left_primary_key_column])
719
940
  end
720
941
 
942
+ def filter_by_associations_limit_alias_key
943
+ aliaz = 'a'
944
+ filter_by_associations_limit_key.map{|c| c.as(Sequel.identifier(aliaz = aliaz.next))}
945
+ end
946
+
947
+ def filter_by_associations_limit_aliases
948
+ filter_by_associations_limit_alias_key.map{|v| v.alias}
949
+ end
950
+
951
+ def filter_by_associations_limit_key
952
+ qualify(join_table_alias, self[:left_keys]) + Array(qualify(associated_class.table_name, associated_class.primary_key))
953
+ end
954
+
955
+ def filter_by_associations_limit_subquery
956
+ super.inner_join(self[:join_table], Array(self[:right_keys]).zip(right_primary_keys))
957
+ end
958
+
721
959
  def reciprocal_association?(assoc_reflect)
722
960
  super && assoc_reflect[:left_keys] == self[:right_keys] &&
723
961
  assoc_reflect[:right_keys] == self[:left_keys] &&
@@ -736,6 +974,22 @@ module Sequel
736
974
  end
737
975
  end
738
976
 
977
+ class OneThroughOneAssociationReflection < ManyToManyAssociationReflection
978
+ ASSOCIATION_TYPES[:one_through_one] = self
979
+ include SingularAssociationReflection
980
+
981
+ # one_through_one associations should not singularize the association name when
982
+ # creating the foreign key.
983
+ def default_right_key
984
+ :"#{self[:name]}_id"
985
+ end
986
+
987
+ # one_through_one associations have no reciprocals
988
+ def reciprocal
989
+ nil
990
+ end
991
+ end
992
+
739
993
  # This module contains methods added to all association datasets
740
994
  module AssociationDatasetMethods
741
995
  # The model object that created the association dataset
@@ -839,6 +1093,9 @@ module Sequel
839
1093
  # model's primary key. Each current model object can be associated with
840
1094
  # more than one associated model objects. Each associated model object
841
1095
  # can be associated with only one current model object.
1096
+ # :one_through_one :: Similar to many_to_many in terms of foreign keys, but only one object
1097
+ # is associated to the current object through the association.
1098
+ # Provides only getter methods, no setter or modification methods.
842
1099
  # :one_to_one :: Similar to one_to_many in terms of foreign keys, but
843
1100
  # only one object is associated to the current object through the
844
1101
  # association. The methods created are similar to many_to_one, except
@@ -871,11 +1128,11 @@ module Sequel
871
1128
  # before an item is set using the association setter method.
872
1129
  # :cartesian_product_number :: the number of joins completed by this association that could cause more
873
1130
  # than one row for each row in the current table (default: 0 for
874
- # many_to_one and one_to_one associations, 1 for one_to_many and
875
- # many_to_many associations).
1131
+ # many_to_one, one_to_one, and one_through_one associations, 1
1132
+ # for one_to_many and many_to_many associations).
876
1133
  # :class :: The associated class or its name as a string or symbol. If not
877
1134
  # given, uses the association's name, which is camelized (and
878
- # singularized unless the type is :many_to_one or :one_to_one). If this is specified
1135
+ # singularized unless the type is :many_to_one, :one_to_one, or one_through_one). If this is specified
879
1136
  # as a string or symbol, you must specify the full class name (e.g. "SomeModule::MyModel").
880
1137
  # :clearer :: Proc used to define the private _remove_all_* method for doing the database work
881
1138
  # to remove all objects associated to the current object (*_to_many assocations).
@@ -895,10 +1152,9 @@ module Sequel
895
1152
  # :eager_graph :: The associations to eagerly load via +eager_graph+ when loading the associated object(s).
896
1153
  # many_to_many associations with this option cannot be eagerly loaded via +eager+.
897
1154
  # :eager_grapher :: A proc to use to implement eager loading via +eager_graph+, overriding the default.
898
- # Takes an options hash with the entries :self (the receiver of the eager_graph call),
899
- # :table_alias (the alias to use for table to graph into the association), :implicit_qualifier
900
- # (the alias that was used for the current table), and possibly :eager_block (a callback
901
- # proc accepting the associated dataset, for per-call customization).
1155
+ # Takes an options hash with at least the entries :self (the receiver of the eager_graph call),
1156
+ # :table_alias (the alias to use for table to graph into the association), and :implicit_qualifier
1157
+ # (the alias that was used for the current table).
902
1158
  # Should return a copy of the dataset with the association graphed into it.
903
1159
  # :eager_limit_strategy :: Determines the strategy used for enforcing limits and offsets when eager loading
904
1160
  # associations via the +eager+ method.
@@ -926,6 +1182,10 @@ module Sequel
926
1182
  # :graph_only_conditions :: The conditions to use on the SQL join when eagerly loading
927
1183
  # the association via +eager_graph+, instead of the default conditions specified by the
928
1184
  # foreign/primary keys. This option causes the :graph_conditions option to be ignored.
1185
+ # :graph_order :: Over the order to use when using eager_graph, instead of the default order. This should be used
1186
+ # in the case where :order contains an identifier qualified by the table's name, which may not match
1187
+ # the alias used when eager graphing. By setting this to the unqualified identifier, it will be
1188
+ # automatically qualified when using eager_graph.
929
1189
  # :graph_select :: A column or array of columns to select from the associated table
930
1190
  # when eagerly loading the association via +eager_graph+. Defaults to all
931
1191
  # columns in the associated table.
@@ -939,7 +1199,8 @@ module Sequel
939
1199
  # :order_eager_graph :: Whether to add the association's order to the graphed dataset's order when graphing
940
1200
  # via +eager_graph+. Defaults to true, so set to false to disable.
941
1201
  # :read_only :: Do not add a setter method (for many_to_one or one_to_one associations),
942
- # or add_/remove_/remove_all_ methods (for one_to_many and many_to_many associations).
1202
+ # or add_/remove_/remove_all_ methods (for one_to_many and many_to_many associations). Always
1203
+ # true for one_through_one associations.
943
1204
  # :reciprocal :: the symbol name of the reciprocal association,
944
1205
  # if it exists. By default, Sequel will try to determine it by looking at the
945
1206
  # associated model's assocations for a association that matches
@@ -988,7 +1249,7 @@ module Sequel
988
1249
  # conjunction with defining a model alias method for the primary key column.
989
1250
  # :raise_on_save_failure :: Do not raise exceptions for hook or validation failures when saving associated
990
1251
  # objects in the add/remove methods (return nil instead) [one_to_many only].
991
- # === :many_to_many
1252
+ # === :many_to_many and :one_through_one
992
1253
  # :graph_join_table_block :: The block to pass to +join_table+ for
993
1254
  # the join table when eagerly loading the association via +eager_graph+.
994
1255
  # :graph_join_table_conditions :: The additional conditions to use on the SQL join for
@@ -1034,14 +1295,20 @@ module Sequel
1034
1295
 
1035
1296
  # dup early so we don't modify opts
1036
1297
  orig_opts = opts.dup
1298
+
1037
1299
  if opts[:clone]
1038
1300
  cloned_assoc = association_reflection(opts[:clone])
1039
- raise(Error, "cannot clone an association to an association of different type (association #{name} with type #{type} cloning #{opts[:clone]} with type #{cloned_assoc[:type]})") unless cloned_assoc[:type] == type || [cloned_assoc[:type], type].all?{|t| [:one_to_many, :one_to_one].include?(t)}
1040
1301
  orig_opts = cloned_assoc[:orig_opts].merge(orig_opts)
1041
1302
  end
1303
+
1042
1304
  opts = orig_opts.merge(:type => type, :name => name, :cache=>{}, :model => self)
1043
1305
  opts[:block] = block if block
1044
1306
  opts = assoc_class.new.merge!(opts)
1307
+
1308
+ if opts[:clone] && !opts.cloneable?(cloned_assoc)
1309
+ raise(Error, "cannot clone an association to an association of different type (association #{name} with type #{type} cloning #{opts[:clone]} with type #{cloned_assoc[:type]})")
1310
+ end
1311
+
1045
1312
  opts[:eager_block] = opts[:block] unless opts.include?(:eager_block)
1046
1313
  if !opts.has_key?(:predicate_key) && opts.has_key?(:eager_loading_predicate_key)
1047
1314
  opts[:predicate_key] = opts[:eager_loading_predicate_key]
@@ -1091,11 +1358,7 @@ module Sequel
1091
1358
  ds = ds.eager(associations) unless Array(associations).empty?
1092
1359
  ds = eager_options[:eager_block].call(ds) if eager_options[:eager_block]
1093
1360
  if opts.eager_loading_use_associated_key?
1094
- ds = if opts[:uses_left_composite_keys]
1095
- ds.select_append(*opts.associated_key_alias.zip(opts.predicate_keys).map{|a, k| SQL::AliasedExpression.new(k, a)})
1096
- else
1097
- ds.select_append(SQL::AliasedExpression.new(opts.predicate_key, opts.associated_key_alias))
1098
- end
1361
+ ds = ds.select_append(*opts.associated_key_array)
1099
1362
  end
1100
1363
  ds
1101
1364
  end
@@ -1110,6 +1373,11 @@ module Sequel
1110
1373
  associate(:many_to_one, name, opts, &block)
1111
1374
  end
1112
1375
 
1376
+ # Shortcut for adding a one_through_one association, see #associate.
1377
+ def one_through_one(name, opts=OPTS, &block)
1378
+ associate(:one_through_one, name, opts, &block)
1379
+ end
1380
+
1113
1381
  # Shortcut for adding a one_to_many association, see #associate
1114
1382
  def one_to_many(name, opts=OPTS, &block)
1115
1383
  associate(:one_to_many, name, opts, &block)
@@ -1121,29 +1389,10 @@ module Sequel
1121
1389
  end
1122
1390
 
1123
1391
  Plugins.inherited_instance_variables(self, :@association_reflections=>:dup, :@autoreloading_associations=>:hash_dup, :@default_eager_limit_strategy=>nil)
1124
- Plugins.def_dataset_methods(self, [:eager, :eager_graph])
1392
+ Plugins.def_dataset_methods(self, [:eager, :eager_graph, :eager_graph_with_options, :association_join, :association_full_join, :association_inner_join, :association_left_join, :association_right_join])
1125
1393
 
1126
1394
  private
1127
1395
 
1128
- # Use a window function to limit the results of the eager loading dataset.
1129
- def apply_window_function_eager_limit_strategy(ds, opts)
1130
- rn = ds.row_number_column
1131
- limit, offset = opts.limit_and_offset
1132
- ds = ds.unordered.select_append{row_number{}.over(:partition=>opts.predicate_key, :order=>ds.opts[:order]).as(rn)}.from_self
1133
- ds = if opts[:type] == :one_to_one
1134
- ds.where(rn => offset ? offset+1 : 1)
1135
- elsif offset
1136
- offset += 1
1137
- if limit
1138
- ds.where(rn => (offset...(offset+limit)))
1139
- else
1140
- ds.where{SQL::Identifier.new(rn) >= offset}
1141
- end
1142
- else
1143
- ds.where{SQL::Identifier.new(rn) <= limit}
1144
- end
1145
- end
1146
-
1147
1396
  # The module to use for the association's methods. Defaults to
1148
1397
  # the overridable_methods_module.
1149
1398
  def association_module(opts=OPTS)
@@ -1179,8 +1428,10 @@ module Sequel
1179
1428
  association_module_def(opts.association_method, opts){|*dynamic_opts, &block| load_associated_objects(opts, dynamic_opts[0], &block)}
1180
1429
  end
1181
1430
 
1182
- # Configures many_to_many association reflection and adds the related association methods
1431
+ # Configures many_to_many and one_through_one association reflection and adds the related association methods
1183
1432
  def def_many_to_many(opts)
1433
+ one_through_one = opts[:type] == :one_through_one
1434
+ opts[:read_only] = true if one_through_one
1184
1435
  name = opts[:name]
1185
1436
  model = self
1186
1437
  left = (opts[:left_key] ||= opts.default_left_key)
@@ -1199,7 +1450,7 @@ module Sequel
1199
1450
  end
1200
1451
  uses_lcks = opts[:uses_left_composite_keys] = lcks.length > 1
1201
1452
  opts[:uses_right_composite_keys] = rcks.length > 1
1202
- opts[:cartesian_product_number] ||= 1
1453
+ opts[:cartesian_product_number] ||= one_through_one ? 0 : 1
1203
1454
  join_table = (opts[:join_table] ||= opts.default_join_table)
1204
1455
  left_key_alias = opts[:left_key_alias] ||= opts.default_associated_key_alias
1205
1456
  graph_jt_conds = opts[:graph_join_table_conditions] = opts.fetch(:graph_join_table_conditions, []).to_a
@@ -1211,28 +1462,34 @@ module Sequel
1211
1462
  opts[:eager_loader] ||= proc do |eo|
1212
1463
  h = eo[:id_map]
1213
1464
  rows = eo[:rows]
1214
- rows.each{|object| object.associations[name] = []}
1215
1465
  r = rcks.zip(opts.right_primary_keys)
1216
1466
  l = [[opts.predicate_key, h.keys]]
1467
+
1217
1468
  ds = model.eager_loading_dataset(opts, opts.associated_class.inner_join(join_table, r + l, :qualify=>:deep), nil, eo[:associations], eo)
1218
- if opts.eager_limit_strategy == :window_function
1219
- delete_rn = true
1220
- rn = ds.row_number_column
1221
- ds = apply_window_function_eager_limit_strategy(ds, opts)
1222
- end
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)
1223
1474
  ds.all do |assoc_record|
1224
- assoc_record.values.delete(rn) if delete_rn
1475
+ assoc_record.values.delete(delete_rn) if delete_rn
1225
1476
  hash_key = if uses_lcks
1226
1477
  left_key_alias.map{|k| assoc_record.values.delete(k)}
1227
1478
  else
1228
1479
  assoc_record.values.delete(left_key_alias)
1229
1480
  end
1230
1481
  next unless objects = h[hash_key]
1231
- objects.each{|object| object.associations[name].push(assoc_record)}
1232
- end
1233
- if opts.eager_limit_strategy == :ruby
1234
- rows.each{|o| o.associations[name] = o.associations[name][slice_range] || []}
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
1235
1491
  end
1492
+ opts.apply_ruby_eager_limit_strategy(rows)
1236
1493
  end
1237
1494
 
1238
1495
  join_type = opts[:graph_join_type]
@@ -1247,13 +1504,25 @@ module Sequel
1247
1504
  jt_graph_block = opts[:graph_join_table_block]
1248
1505
  opts[:eager_grapher] ||= proc do |eo|
1249
1506
  ds = eo[:self]
1250
- ds = ds.graph(join_table, use_jt_only_conditions ? jt_only_conditions : lcks.zip(lpkcs) + graph_jt_conds, :select=>false, :table_alias=>ds.unused_table_alias(join_table, [eo[:table_alias]]), :join_type=>jt_join_type, :implicit_qualifier=>eo[:implicit_qualifier], :qualify=>:deep, :from_self_alias=>ds.opts[:eager_graph][:master], &jt_graph_block)
1251
- ds.graph(eager_graph_dataset(opts, eo), use_only_conditions ? only_conditions : opts.right_primary_keys.zip(rcks) + conditions, :select=>select, :table_alias=>eo[:table_alias], :qualify=>:deep, :join_type=>join_type, &graph_block)
1507
+ egls = eo[:limit_strategy]
1508
+ if egls && egls != :ruby
1509
+ associated_key_array = opts.associated_key_array
1510
+ orig_egds = egds = eager_graph_dataset(opts, eo)
1511
+ egds = egds.
1512
+ inner_join(join_table, rcks.zip(opts.right_primary_keys) + graph_jt_conds, :qualify=>:deep).
1513
+ select_all(egds.first_source).
1514
+ select_append(*associated_key_array)
1515
+ egds = opts.apply_eager_graph_limit_strategy(egls, egds)
1516
+ ds.graph(egds, associated_key_array.map{|v| v.alias}.zip(lpkcs) + conditions, :qualify=>:deep, :table_alias=>eo[:table_alias], :implicit_qualifier=>eo[:implicit_qualifier], :join_type=>eo[:join_type]||join_type, :from_self_alias=>eo[:from_self_alias], :select=>select||orig_egds.columns, &graph_block)
1517
+ else
1518
+ ds = ds.graph(join_table, use_jt_only_conditions ? jt_only_conditions : lcks.zip(lpkcs) + graph_jt_conds, :select=>false, :table_alias=>ds.unused_table_alias(join_table, [eo[:table_alias]]), :join_type=>eo[:join_type]||jt_join_type, :implicit_qualifier=>eo[:implicit_qualifier], :qualify=>:deep, :from_self_alias=>eo[:from_self_alias], &jt_graph_block)
1519
+ ds.graph(eager_graph_dataset(opts, eo), use_only_conditions ? only_conditions : opts.right_primary_keys.zip(rcks) + conditions, :select=>select, :table_alias=>eo[:table_alias], :qualify=>:deep, :join_type=>eo[:join_type]||join_type, &graph_block)
1520
+ end
1252
1521
  end
1253
1522
 
1254
1523
  def_association_dataset_methods(opts)
1255
1524
 
1256
- return if opts[:read_only]
1525
+ return if opts[:read_only] || one_through_one
1257
1526
 
1258
1527
  adder = opts[:adder] || proc do |o|
1259
1528
  h = {}
@@ -1311,9 +1580,9 @@ module Sequel
1311
1580
  opts[:eager_loader] ||= proc do |eo|
1312
1581
  h = eo[:id_map]
1313
1582
  keys = h.keys
1314
- # Default the cached association to nil, so any object that doesn't have it
1315
- # populated will have cached the negative lookup.
1316
- eo[:rows].each{|object| object.associations[name] = nil}
1583
+
1584
+ opts.initialize_association_cache(eo[:rows])
1585
+
1317
1586
  # Skip eager loading if no objects have a foreign key for this association
1318
1587
  unless keys.empty?
1319
1588
  klass = opts.associated_class
@@ -1334,7 +1603,7 @@ module Sequel
1334
1603
  graph_cks = opts[:graph_keys]
1335
1604
  opts[:eager_grapher] ||= proc do |eo|
1336
1605
  ds = eo[:self]
1337
- 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=>join_type, :qualify=>:deep, :from_self_alias=>ds.opts[:eager_graph][:master]), &graph_block)
1606
+ 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)
1338
1607
  end
1339
1608
 
1340
1609
  def_association_dataset_methods(opts)
@@ -1371,26 +1640,15 @@ module Sequel
1371
1640
  rows = eo[:rows]
1372
1641
  reciprocal = opts.reciprocal
1373
1642
  klass = opts.associated_class
1374
- filter_keys = opts.predicate_key
1375
- ds = model.eager_loading_dataset(opts, klass.where(filter_keys=>h.keys), nil, eo[:associations], eo)
1376
- assign_singular = true if one_to_one
1377
- case opts.eager_limit_strategy
1378
- when :distinct_on
1379
- ds = ds.distinct(*filter_keys).order_prepend(*filter_keys)
1380
- when :window_function
1381
- delete_rn = true
1382
- rn = ds.row_number_column
1383
- ds = apply_window_function_eager_limit_strategy(ds, opts)
1384
- when :ruby
1385
- assign_singular = false if one_to_one && slice_range
1386
- end
1387
- if assign_singular
1388
- rows.each{|object| object.associations[name] = nil}
1389
- else
1390
- rows.each{|object| object.associations[name] = []}
1391
- end
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
+ assign_singular = opts.assign_singular?
1649
+ delete_rn = opts.delete_row_number_column(ds)
1392
1650
  ds.all do |assoc_record|
1393
- assoc_record.values.delete(rn) if delete_rn
1651
+ assoc_record.values.delete(delete_rn) if delete_rn
1394
1652
  hash_key = uses_cks ? km.map{|k| assoc_record.send(k)} : assoc_record.send(km)
1395
1653
  next unless objects = h[hash_key]
1396
1654
  if assign_singular
@@ -1407,15 +1665,7 @@ module Sequel
1407
1665
  end
1408
1666
  end
1409
1667
  end
1410
- if opts.eager_limit_strategy == :ruby
1411
- if one_to_one
1412
- if slice_range
1413
- rows.each{|o| o.associations[name] = o.associations[name][slice_range.begin]}
1414
- end
1415
- else
1416
- rows.each{|o| o.associations[name] = o.associations[name][slice_range] || []}
1417
- end
1418
- end
1668
+ opts.apply_ruby_eager_limit_strategy(rows)
1419
1669
  end
1420
1670
 
1421
1671
  join_type = opts[:graph_join_type]
@@ -1427,7 +1677,7 @@ module Sequel
1427
1677
  graph_block = opts[:graph_block]
1428
1678
  opts[:eager_grapher] ||= proc do |eo|
1429
1679
  ds = eo[:self]
1430
- ds = ds.graph(eager_graph_dataset(opts, eo), use_only_conditions ? only_conditions : cks.zip(pkcs) + conditions, eo.merge(:select=>select, :join_type=>join_type, :qualify=>:deep, :from_self_alias=>ds.opts[:eager_graph][:master]), &graph_block)
1680
+ ds = ds.graph(opts.apply_eager_graph_limit_strategy(eo[:limit_strategy], eager_graph_dataset(opts, eo)), use_only_conditions ? only_conditions : cks.zip(pkcs) + conditions, eo.merge(:select=>select, :join_type=>eo[:join_type]||join_type, :qualify=>:deep, :from_self_alias=>eo[:from_self_alias]), &graph_block)
1431
1681
  # We only load reciprocals for one_to_many associations, as other reciprocals don't make sense
1432
1682
  ds.opts[:eager_graph][:reciprocals][eo[:table_alias]] = opts.reciprocal
1433
1683
  ds
@@ -1481,6 +1731,11 @@ module Sequel
1481
1731
  end
1482
1732
  end
1483
1733
 
1734
+ # Alias of def_many_to_many, since they share pretty much the same code.
1735
+ def def_one_through_one(opts)
1736
+ def_many_to_many(opts)
1737
+ end
1738
+
1484
1739
  # Alias of def_one_to_many, since they share pretty much the same code.
1485
1740
  def def_one_to_one(opts)
1486
1741
  def_one_to_many(opts)
@@ -1844,6 +2099,26 @@ module Sequel
1844
2099
  # Artist.eager(:albums => {proc{|ds| ds.where{year > 1990}}=>{:tracks => :genre}})
1845
2100
  module DatasetMethods
1846
2101
  Sequel::Dataset.def_mutation_method(:eager, :eager_graph, :module=>self)
2102
+
2103
+ %w'inner left right full'.each do |type|
2104
+ class_eval <<END, __FILE__, __LINE__+1
2105
+ def association_#{type}_join(*associations)
2106
+ _association_join(:#{type}, associations)
2107
+ end
2108
+ END
2109
+ end
2110
+
2111
+ # Adds one or more INNER JOINs to the existing dataset using the keys and conditions
2112
+ # specified by the given association. The following methods also exist for specifying
2113
+ # a different type of JOIN:
2114
+ #
2115
+ # association_full_join :: FULL JOIN
2116
+ # association_inner_join :: INNER JOIN
2117
+ # association_left_join :: LEFT JOIN
2118
+ # association_right_join :: RIGHT JOIN
2119
+ def association_join(*associations)
2120
+ association_inner_join(*associations)
2121
+ end
1847
2122
 
1848
2123
  # If the expression is in the form <tt>x = y</tt> where +y+ is a <tt>Sequel::Model</tt>
1849
2124
  # instance, array of <tt>Sequel::Model</tt> instances, or a <tt>Sequel::Model</tt> dataset,
@@ -1951,7 +2226,7 @@ module Sequel
1951
2226
  #
1952
2227
  # Each association's order, if definied, is respected. +eager_graph+ probably
1953
2228
  # won't work correctly on a limited dataset, unless you are
1954
- # only graphing many_to_one and one_to_one associations.
2229
+ # only graphing many_to_one, one_to_one, and one_through_one associations.
1955
2230
  #
1956
2231
  # Does not use the block defined for the association, since it does a single query for
1957
2232
  # all objects. You can use the :graph_* association options to modify the SQL query.
@@ -1959,22 +2234,48 @@ module Sequel
1959
2234
  # Like +eager+, you need to call +all+ on the dataset for the eager loading to work. If you just
1960
2235
  # call +each+, it will yield plain hashes, each containing all columns from all the tables.
1961
2236
  def eager_graph(*associations)
2237
+ eager_graph_with_options(associations)
2238
+ end
2239
+
2240
+ # Run eager_graph with some options specific to just this call. Unlike eager_graph, this takes
2241
+ # the associations as a single argument instead of multiple arguments.
2242
+ #
2243
+ # Options:
2244
+ #
2245
+ # :join_type :: Override the join type specified in the association
2246
+ # :limit_strategy :: Use a strategy for handling limits on associations.
2247
+ # Appropriate :limit_strategy values are:
2248
+ # true :: Pick the most appropriate based on what the database supports
2249
+ # :distinct_on :: Force use of DISTINCT ON stategy (*_one associations only)
2250
+ # :window_function :: Force use of window function strategy
2251
+ # :ruby :: Don't modify the SQL, implement limits/offsets with array slicing
2252
+ #
2253
+ # This can also be a hash with association name symbol keys and one of the above values,
2254
+ # to use different strategies per association.
2255
+ #
2256
+ # The default is the :ruby strategy. Choosing a different strategy can make your code
2257
+ # significantly slower in some cases (perhaps even the majority of cases), so you should
2258
+ # only use this if you have benchmarked that it is faster for your use cases.
2259
+ def eager_graph_with_options(associations, opts=OPTS)
2260
+ associations = [associations] unless associations.is_a?(Array)
1962
2261
  if eg = @opts[:eager_graph]
1963
2262
  eg = eg.dup
1964
- [:requirements, :reflections, :reciprocals].each{|k| eg[k] = eg[k].dup}
2263
+ [:requirements, :reflections, :reciprocals, :limits].each{|k| eg[k] = eg[k].dup}
2264
+ eg[:local] = opts
1965
2265
  ds = clone(:eager_graph=>eg)
1966
2266
  ds.eager_graph_associations(ds, model, ds.opts[:eager_graph][:master], [], *associations)
1967
2267
  else
1968
2268
  # Each of the following have a symbol key for the table alias, with the following values:
1969
- # :reciprocals - the reciprocal instance variable to use for this association
1970
- # :reflections - AssociationReflection instance related to this association
1971
- # :requirements - array of requirements for this association
1972
- ds = clone(:eager_graph=>{:requirements=>{}, :master=>alias_symbol(first_source), :reflections=>{}, :reciprocals=>{}, :cartesian_product_number=>0, :row_proc=>row_proc})
1973
- ds.eager_graph_associations(ds, model, ds.opts[:eager_graph][:master], [], *associations).
1974
- naked
2269
+ # :reciprocals :: the reciprocal value to use for this association
2270
+ # :reflections :: AssociationReflection instance related to this association
2271
+ # :requirements :: array of requirements for this association
2272
+ # :limits :: Any limit/offset array slicing that need to be handled in ruby land after loading
2273
+ opts = {:requirements=>{}, :master=>alias_symbol(first_source), :reflections=>{}, :reciprocals=>{}, :limits=>{}, :local=>opts, :cartesian_product_number=>0, :row_proc=>row_proc}
2274
+ ds = clone(:eager_graph=>opts)
2275
+ ds.eager_graph_associations(ds, model, ds.opts[:eager_graph][:master], [], *associations).naked
1975
2276
  end
1976
2277
  end
1977
-
2278
+
1978
2279
  # Do not attempt to split the result set into associations,
1979
2280
  # just return results as simple objects. This is useful if you
1980
2281
  # want to use eager_graph as a shortcut to have all of the joins
@@ -2004,7 +2305,7 @@ module Sequel
2004
2305
  # *associations :: any associations dependent on this one
2005
2306
  def eager_graph_association(ds, model, ta, requirements, r, *associations)
2006
2307
  if r.is_a?(SQL::AliasedExpression)
2007
- alias_base = r.aliaz
2308
+ alias_base = r.alias
2008
2309
  r = r.expression
2009
2310
  else
2010
2311
  alias_base = r[:graph_alias_base]
@@ -2020,11 +2321,19 @@ module Sequel
2020
2321
  associations = assoc.is_a?(Array) ? assoc : [assoc]
2021
2322
  end
2022
2323
  end
2023
- ds = loader.call(:self=>ds, :table_alias=>assoc_table_alias, :implicit_qualifier=>ta, :callback=>callback)
2024
- ds = ds.order_more(*qualified_expression(r[:order], assoc_table_alias)) if r[:order] and r[:order_eager_graph]
2324
+ orig_ds = ds
2325
+ local_opts = ds.opts[:eager_graph][:local]
2326
+ limit_strategy = r.eager_graph_limit_strategy(local_opts[:limit_strategy])
2327
+ ds = loader.call(:self=>ds, :table_alias=>assoc_table_alias, :implicit_qualifier=>ta, :callback=>callback, :join_type=>local_opts[:join_type], :limit_strategy=>limit_strategy, :from_self_alias=>ds.opts[:eager_graph][:master])
2328
+ if r[:order_eager_graph] && (order = r.fetch(:graph_order, r[:order]))
2329
+ ds = ds.order_more(*qualified_expression(order, assoc_table_alias))
2330
+ end
2025
2331
  eager_graph = ds.opts[:eager_graph]
2026
2332
  eager_graph[:requirements][assoc_table_alias] = requirements.dup
2027
2333
  eager_graph[:reflections][assoc_table_alias] = r
2334
+ if limit_strategy == :ruby
2335
+ eager_graph[:limits][assoc_table_alias] = r.limit_and_offset
2336
+ end
2028
2337
  eager_graph[:cartesian_product_number] += r[:cartesian_product_number] || 2
2029
2338
  ds = ds.eager_graph_associations(ds, r.associated_class, assoc_table_alias, requirements + [assoc_table_alias], *associations) unless associations.empty?
2030
2339
  ds
@@ -2065,12 +2374,18 @@ module Sequel
2065
2374
 
2066
2375
  private
2067
2376
 
2377
+ # Return a new dataset with JOINs of the given type added, using the tables and
2378
+ # conditions specified by the associations.
2379
+ def _association_join(type, associations)
2380
+ clone(:join=>clone(:graph_from_self=>false).eager_graph_with_options(associations, :join_type=>type).opts[:join])
2381
+ end
2382
+
2068
2383
  # If the association has conditions itself, then it requires additional filters be
2069
2384
  # added to the current dataset to ensure that the passed in object would also be
2070
2385
  # included by the association's conditions.
2071
2386
  def add_association_filter_conditions(ref, obj, expr)
2072
2387
  if expr != SQL::Constants::FALSE && ref.filter_by_associations_add_conditions?
2073
- Sequel.&(expr, ref.filter_by_associations_conditions_expression(obj))
2388
+ Sequel.expr(ref.filter_by_associations_conditions_expression(obj))
2074
2389
  else
2075
2390
  expr
2076
2391
  end
@@ -2130,7 +2445,7 @@ module Sequel
2130
2445
  # per-call determining of the alias base.
2131
2446
  def eager_graph_check_association(model, association)
2132
2447
  if association.is_a?(SQL::AliasedExpression)
2133
- SQL::AliasedExpression.new(check_association(model, association.expression), association.aliaz)
2448
+ SQL::AliasedExpression.new(check_association(model, association.expression), association.alias)
2134
2449
  else
2135
2450
  check_association(model, association)
2136
2451
  end
@@ -2210,6 +2525,7 @@ module Sequel
2210
2525
 
2211
2526
  association_filter_handle_inversion(op, expr, Array(lpks))
2212
2527
  end
2528
+ alias one_through_one_association_filter_expression many_to_many_association_filter_expression
2213
2529
 
2214
2530
  # Return a simple equality expression for filering by a many_to_one association
2215
2531
  def many_to_one_association_filter_expression(op, ref, obj)
@@ -2304,17 +2620,20 @@ module Sequel
2304
2620
  requirements = eager_graph[:requirements]
2305
2621
  reflection_map = @reflection_map = eager_graph[:reflections]
2306
2622
  reciprocal_map = @reciprocal_map = eager_graph[:reciprocals]
2623
+ limit_map = @limit_map = eager_graph[:limits]
2307
2624
  @unique = eager_graph[:cartesian_product_number] > 1
2308
2625
 
2309
2626
  alias_map = @alias_map = {}
2310
2627
  type_map = @type_map = {}
2311
2628
  after_load_map = @after_load_map = {}
2312
- limit_map = @limit_map = {}
2313
2629
  reflection_map.each do |k, v|
2314
2630
  alias_map[k] = v[:name]
2315
- type_map[k] = v.returns_array?
2316
2631
  after_load_map[k] = v[:after_load] unless v[:after_load].empty?
2317
- limit_map[k] = v.limit_and_offset if v[:limit]
2632
+ type_map[k] = if v.returns_array?
2633
+ true
2634
+ elsif (limit_and_offset = limit_map[k]) && !limit_and_offset.last.nil?
2635
+ :offset
2636
+ end
2318
2637
  end
2319
2638
 
2320
2639
  # Make dependency map hash out of requirements array for each association.
@@ -2523,9 +2842,14 @@ module Sequel
2523
2842
  if lo = limit_map[ta]
2524
2843
  limit, offset = lo
2525
2844
  offset ||= 0
2526
- list.replace(list[(offset)..(limit ? (offset)+limit-1 : -1)])
2845
+ if type_map[ta] == :offset
2846
+ [record.associations[assoc_name] = list[offset]]
2847
+ else
2848
+ list.replace(list[(offset)..(limit ? (offset)+limit-1 : -1)] || [])
2849
+ end
2850
+ else
2851
+ list
2527
2852
  end
2528
- list
2529
2853
  elsif list
2530
2854
  [list]
2531
2855
  else