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