sequel 4.9.0 → 4.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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"
|