sequel 4.7.0 → 4.8.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +46 -0
- data/README.rdoc +25 -1
- data/doc/active_record.rdoc +1 -1
- data/doc/advanced_associations.rdoc +143 -17
- data/doc/association_basics.rdoc +80 -59
- data/doc/release_notes/4.8.0.txt +175 -0
- data/lib/sequel/adapters/odbc.rb +1 -1
- data/lib/sequel/adapters/odbc/mssql.rb +4 -2
- data/lib/sequel/adapters/shared/postgres.rb +19 -3
- data/lib/sequel/adapters/shared/sqlite.rb +3 -3
- data/lib/sequel/ast_transformer.rb +1 -1
- data/lib/sequel/dataset/actions.rb +1 -1
- data/lib/sequel/dataset/graph.rb +23 -9
- data/lib/sequel/dataset/misc.rb +2 -2
- data/lib/sequel/dataset/sql.rb +3 -3
- data/lib/sequel/extensions/columns_introspection.rb +1 -1
- data/lib/sequel/extensions/mssql_emulate_lateral_with_apply.rb +1 -1
- data/lib/sequel/extensions/pg_array.rb +1 -1
- data/lib/sequel/extensions/pg_array_ops.rb +6 -0
- data/lib/sequel/extensions/pg_hstore_ops.rb +7 -0
- data/lib/sequel/extensions/pg_json_ops.rb +5 -0
- data/lib/sequel/extensions/query.rb +8 -2
- data/lib/sequel/extensions/to_dot.rb +1 -1
- data/lib/sequel/model/associations.rb +476 -152
- data/lib/sequel/plugins/class_table_inheritance.rb +11 -3
- data/lib/sequel/plugins/dataset_associations.rb +21 -18
- data/lib/sequel/plugins/many_through_many.rb +87 -20
- data/lib/sequel/plugins/nested_attributes.rb +12 -0
- data/lib/sequel/plugins/pg_array_associations.rb +31 -12
- data/lib/sequel/plugins/single_table_inheritance.rb +9 -1
- data/lib/sequel/sql.rb +1 -0
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/mssql_spec.rb +2 -2
- data/spec/adapters/postgres_spec.rb +7 -0
- data/spec/core/object_graph_spec.rb +250 -196
- data/spec/extensions/core_refinements_spec.rb +1 -1
- data/spec/extensions/dataset_associations_spec.rb +100 -6
- data/spec/extensions/many_through_many_spec.rb +1002 -19
- data/spec/extensions/nested_attributes_spec.rb +24 -0
- data/spec/extensions/pg_array_associations_spec.rb +17 -12
- data/spec/extensions/pg_array_spec.rb +4 -2
- data/spec/extensions/spec_helper.rb +1 -1
- data/spec/integration/associations_test.rb +1003 -48
- data/spec/integration/dataset_test.rb +12 -5
- data/spec/integration/prepared_statement_test.rb +1 -1
- data/spec/integration/type_test.rb +1 -1
- data/spec/model/associations_spec.rb +467 -130
- data/spec/model/eager_loading_spec.rb +332 -5
- metadata +5 -3
@@ -1,6 +1,12 @@
|
|
1
|
-
# The query extension adds
|
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
|
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
|
#
|
@@ -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
|
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){
|
202
|
+
if self[:limit] || !returns_array?
|
203
|
+
case s = cached_fetch(:eager_limit_strategy){default_eager_limit_strategy}
|
101
204
|
when true
|
102
|
-
|
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 =
|
490
|
+
ds = associated_eager_dataset.unordered.unlimited
|
343
491
|
ds = filter_by_associations_add_conditions_dataset_filter(ds)
|
344
|
-
ds =
|
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
|
-
|
552
|
-
|
553
|
-
|
554
|
-
#
|
555
|
-
#
|
556
|
-
|
557
|
-
|
558
|
-
|
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
|
-
#
|
578
|
-
|
579
|
-
|
580
|
-
|
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
|
-
#
|
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
|
-
|
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
|
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
|
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)
|
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 =
|
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
|
-
|
1219
|
-
|
1220
|
-
|
1221
|
-
|
1222
|
-
|
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(
|
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
|
-
|
1232
|
-
|
1233
|
-
|
1234
|
-
|
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
|
-
|
1251
|
-
|
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
|
-
|
1315
|
-
|
1316
|
-
|
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=>
|
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
|
-
|
1375
|
-
ds = model.eager_loading_dataset(opts, klass.where(
|
1376
|
-
|
1377
|
-
|
1378
|
-
|
1379
|
-
|
1380
|
-
|
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(
|
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
|
-
|
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=>
|
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
|
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
|
1970
|
-
# :reflections
|
1971
|
-
# :requirements
|
1972
|
-
|
1973
|
-
|
1974
|
-
|
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.
|
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
|
-
|
2024
|
-
|
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
|
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.
|
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
|
-
|
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
|
-
|
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
|