sequel 3.21.0 → 3.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (158) hide show
  1. data/CHANGELOG +294 -0
  2. data/README.rdoc +20 -6
  3. data/Rakefile +20 -15
  4. data/doc/association_basics.rdoc +210 -43
  5. data/doc/dataset_basics.rdoc +4 -4
  6. data/doc/mass_assignment.rdoc +54 -0
  7. data/doc/migration.rdoc +15 -538
  8. data/doc/model_hooks.rdoc +64 -27
  9. data/doc/opening_databases.rdoc +37 -10
  10. data/doc/prepared_statements.rdoc +16 -10
  11. data/doc/reflection.rdoc +8 -2
  12. data/doc/release_notes/3.22.0.txt +39 -0
  13. data/doc/release_notes/3.23.0.txt +172 -0
  14. data/doc/release_notes/3.24.0.txt +420 -0
  15. data/doc/release_notes/3.25.0.txt +88 -0
  16. data/doc/release_notes/3.26.0.txt +88 -0
  17. data/doc/release_notes/3.27.0.txt +82 -0
  18. data/doc/release_notes/3.28.0.txt +304 -0
  19. data/doc/schema_modification.rdoc +547 -0
  20. data/doc/testing.rdoc +106 -0
  21. data/doc/transactions.rdoc +97 -0
  22. data/doc/virtual_rows.rdoc +2 -2
  23. data/lib/sequel/adapters/ado.rb +12 -1
  24. data/lib/sequel/adapters/amalgalite.rb +4 -0
  25. data/lib/sequel/adapters/db2.rb +95 -58
  26. data/lib/sequel/adapters/do.rb +12 -0
  27. data/lib/sequel/adapters/firebird.rb +25 -203
  28. data/lib/sequel/adapters/ibmdb.rb +440 -0
  29. data/lib/sequel/adapters/informix.rb +4 -19
  30. data/lib/sequel/adapters/jdbc/as400.rb +0 -7
  31. data/lib/sequel/adapters/jdbc/db2.rb +49 -0
  32. data/lib/sequel/adapters/jdbc/firebird.rb +34 -0
  33. data/lib/sequel/adapters/jdbc/h2.rb +16 -5
  34. data/lib/sequel/adapters/jdbc/informix.rb +31 -0
  35. data/lib/sequel/adapters/jdbc/jtds.rb +34 -0
  36. data/lib/sequel/adapters/jdbc/mssql.rb +0 -32
  37. data/lib/sequel/adapters/jdbc/mysql.rb +9 -0
  38. data/lib/sequel/adapters/jdbc/oracle.rb +2 -27
  39. data/lib/sequel/adapters/jdbc/postgresql.rb +6 -0
  40. data/lib/sequel/adapters/jdbc/sqlserver.rb +46 -0
  41. data/lib/sequel/adapters/jdbc/transactions.rb +34 -0
  42. data/lib/sequel/adapters/jdbc.rb +62 -29
  43. data/lib/sequel/adapters/mysql.rb +22 -139
  44. data/lib/sequel/adapters/mysql2.rb +9 -14
  45. data/lib/sequel/adapters/odbc/db2.rb +21 -0
  46. data/lib/sequel/adapters/odbc.rb +15 -3
  47. data/lib/sequel/adapters/oracle.rb +17 -1
  48. data/lib/sequel/adapters/postgres.rb +111 -16
  49. data/lib/sequel/adapters/shared/access.rb +21 -0
  50. data/lib/sequel/adapters/shared/db2.rb +290 -0
  51. data/lib/sequel/adapters/shared/firebird.rb +214 -0
  52. data/lib/sequel/adapters/shared/informix.rb +45 -0
  53. data/lib/sequel/adapters/shared/mssql.rb +85 -47
  54. data/lib/sequel/adapters/shared/mysql.rb +50 -7
  55. data/lib/sequel/adapters/shared/mysql_prepared_statements.rb +143 -0
  56. data/lib/sequel/adapters/shared/oracle.rb +0 -4
  57. data/lib/sequel/adapters/shared/postgres.rb +75 -43
  58. data/lib/sequel/adapters/shared/sqlite.rb +56 -8
  59. data/lib/sequel/adapters/sqlite.rb +12 -11
  60. data/lib/sequel/adapters/swift/mysql.rb +9 -0
  61. data/lib/sequel/adapters/tinytds.rb +139 -7
  62. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +55 -0
  63. data/lib/sequel/ast_transformer.rb +190 -0
  64. data/lib/sequel/connection_pool/threaded.rb +3 -2
  65. data/lib/sequel/connection_pool.rb +1 -1
  66. data/lib/sequel/core.rb +6 -5
  67. data/lib/sequel/database/connecting.rb +5 -5
  68. data/lib/sequel/database/dataset.rb +1 -1
  69. data/lib/sequel/database/dataset_defaults.rb +1 -1
  70. data/lib/sequel/database/logging.rb +1 -1
  71. data/lib/sequel/database/misc.rb +38 -17
  72. data/lib/sequel/database/query.rb +50 -19
  73. data/lib/sequel/database/schema_generator.rb +8 -5
  74. data/lib/sequel/database/schema_methods.rb +52 -27
  75. data/lib/sequel/dataset/actions.rb +167 -48
  76. data/lib/sequel/dataset/features.rb +57 -8
  77. data/lib/sequel/dataset/graph.rb +1 -1
  78. data/lib/sequel/dataset/misc.rb +39 -20
  79. data/lib/sequel/dataset/mutation.rb +3 -3
  80. data/lib/sequel/dataset/prepared_statements.rb +29 -14
  81. data/lib/sequel/dataset/query.rb +182 -32
  82. data/lib/sequel/dataset/sql.rb +31 -58
  83. data/lib/sequel/dataset.rb +8 -0
  84. data/lib/sequel/exceptions.rb +4 -0
  85. data/lib/sequel/extensions/columns_introspection.rb +61 -0
  86. data/lib/sequel/extensions/migration.rb +6 -4
  87. data/lib/sequel/extensions/to_dot.rb +95 -83
  88. data/lib/sequel/model/associations.rb +893 -309
  89. data/lib/sequel/model/base.rb +302 -105
  90. data/lib/sequel/model/errors.rb +1 -1
  91. data/lib/sequel/model/exceptions.rb +5 -1
  92. data/lib/sequel/model.rb +13 -7
  93. data/lib/sequel/plugins/association_pks.rb +22 -4
  94. data/lib/sequel/plugins/defaults_setter.rb +58 -0
  95. data/lib/sequel/plugins/identity_map.rb +113 -6
  96. data/lib/sequel/plugins/many_through_many.rb +67 -5
  97. data/lib/sequel/plugins/prepared_statements.rb +140 -0
  98. data/lib/sequel/plugins/prepared_statements_associations.rb +84 -0
  99. data/lib/sequel/plugins/prepared_statements_safe.rb +82 -0
  100. data/lib/sequel/plugins/prepared_statements_with_pk.rb +59 -0
  101. data/lib/sequel/plugins/serialization_modification_detection.rb +51 -0
  102. data/lib/sequel/plugins/sharding.rb +12 -20
  103. data/lib/sequel/plugins/single_table_inheritance.rb +2 -0
  104. data/lib/sequel/plugins/update_primary_key.rb +1 -1
  105. data/lib/sequel/plugins/xml_serializer.rb +3 -3
  106. data/lib/sequel/sql.rb +107 -51
  107. data/lib/sequel/timezones.rb +12 -3
  108. data/lib/sequel/version.rb +1 -1
  109. data/spec/adapters/db2_spec.rb +146 -0
  110. data/spec/adapters/mssql_spec.rb +36 -0
  111. data/spec/adapters/mysql_spec.rb +36 -19
  112. data/spec/adapters/postgres_spec.rb +115 -28
  113. data/spec/adapters/spec_helper.rb +6 -0
  114. data/spec/adapters/sqlite_spec.rb +11 -0
  115. data/spec/core/connection_pool_spec.rb +62 -77
  116. data/spec/core/database_spec.rb +244 -287
  117. data/spec/core/dataset_spec.rb +383 -34
  118. data/spec/core/expression_filters_spec.rb +159 -41
  119. data/spec/core/schema_spec.rb +326 -3
  120. data/spec/core/spec_helper.rb +45 -0
  121. data/spec/extensions/association_pks_spec.rb +38 -0
  122. data/spec/extensions/columns_introspection_spec.rb +91 -0
  123. data/spec/extensions/defaults_setter_spec.rb +64 -0
  124. data/spec/extensions/identity_map_spec.rb +162 -0
  125. data/spec/extensions/many_through_many_spec.rb +195 -20
  126. data/spec/extensions/migration_spec.rb +17 -17
  127. data/spec/extensions/nested_attributes_spec.rb +1 -0
  128. data/spec/extensions/prepared_statements_associations_spec.rb +126 -0
  129. data/spec/extensions/prepared_statements_safe_spec.rb +79 -0
  130. data/spec/extensions/prepared_statements_spec.rb +72 -0
  131. data/spec/extensions/prepared_statements_with_pk_spec.rb +38 -0
  132. data/spec/extensions/schema_dumper_spec.rb +2 -2
  133. data/spec/extensions/schema_spec.rb +12 -20
  134. data/spec/extensions/serialization_modification_detection_spec.rb +36 -0
  135. data/spec/extensions/single_table_inheritance_spec.rb +11 -0
  136. data/spec/extensions/spec_helper.rb +3 -1
  137. data/spec/extensions/to_dot_spec.rb +3 -5
  138. data/spec/extensions/xml_serializer_spec.rb +16 -4
  139. data/spec/integration/associations_test.rb +405 -15
  140. data/spec/integration/database_test.rb +4 -2
  141. data/spec/integration/dataset_test.rb +240 -20
  142. data/spec/integration/plugin_test.rb +142 -5
  143. data/spec/integration/prepared_statement_test.rb +174 -95
  144. data/spec/integration/schema_test.rb +128 -16
  145. data/spec/integration/spec_helper.rb +15 -0
  146. data/spec/integration/transaction_test.rb +40 -0
  147. data/spec/integration/type_test.rb +16 -2
  148. data/spec/model/association_reflection_spec.rb +91 -0
  149. data/spec/model/associations_spec.rb +476 -5
  150. data/spec/model/base_spec.rb +91 -1
  151. data/spec/model/eager_loading_spec.rb +519 -31
  152. data/spec/model/hooks_spec.rb +161 -0
  153. data/spec/model/model_spec.rb +89 -2
  154. data/spec/model/plugins_spec.rb +17 -0
  155. data/spec/model/record_spec.rb +184 -12
  156. data/spec/model/spec_helper.rb +5 -0
  157. data/spec/model/validations_spec.rb +11 -0
  158. metadata +85 -34
@@ -79,6 +79,27 @@ module Sequel
79
79
  true
80
80
  end
81
81
 
82
+ # The eager limit strategy to use for this dataset.
83
+ def eager_limit_strategy
84
+ fetch(:_eager_limit_strategy) do
85
+ self[:_eager_limit_strategy] = if self[:limit]
86
+ case s = self.fetch(:eager_limit_strategy){self[:model].default_eager_limit_strategy || :ruby}
87
+ when true
88
+ ds = associated_class.dataset
89
+ if ds.supports_window_functions?
90
+ :window_function
91
+ else
92
+ :ruby
93
+ end
94
+ else
95
+ s
96
+ end
97
+ else
98
+ nil
99
+ end
100
+ end
101
+ end
102
+
82
103
  # By default associations do not need to select a key in an associated table
83
104
  # to eagerly load.
84
105
  def eager_loading_use_associated_key?
@@ -92,6 +113,15 @@ module Sequel
92
113
  true
93
114
  end
94
115
 
116
+ # The limit and offset for this association (returned as a two element array).
117
+ def limit_and_offset
118
+ if (v = self[:limit]).is_a?(Array)
119
+ v
120
+ else
121
+ [v, nil]
122
+ end
123
+ end
124
+
95
125
  # Whether the associated object needs a primary key to be added/removed,
96
126
  # false by default.
97
127
  def need_associated_primary_key?
@@ -193,6 +223,11 @@ module Sequel
193
223
  self[:key].nil?
194
224
  end
195
225
 
226
+ # many_to_one associations don't need an eager limit strategy
227
+ def eager_limit_strategy
228
+ nil
229
+ end
230
+
196
231
  # The key to use for the key hash when eager loading
197
232
  def eager_loader_key
198
233
  self[:eager_loader_key] ||= self[:key]
@@ -248,7 +283,7 @@ module Sequel
248
283
  def can_have_associated_objects?(obj)
249
284
  !self[:primary_keys].any?{|k| obj.send(k).nil?}
250
285
  end
251
-
286
+
252
287
  # Default foreign key name symbol for key in associated table that points to
253
288
  # current table's primary key.
254
289
  def default_key
@@ -259,6 +294,11 @@ module Sequel
259
294
  def eager_loader_key
260
295
  self[:eager_loader_key] ||= primary_key
261
296
  end
297
+
298
+ # The hash key to use for the eager loading predicate (left side of IN (1, 2, 3))
299
+ def eager_loading_predicate_key
300
+ self[:eager_loading_predicate_key] ||= self[:uses_composite_keys] ? self[:keys].map{|k| SQL::QualifiedIdentifier.new(associated_class.table_name, k)} : SQL::QualifiedIdentifier.new(associated_class.table_name, self[:key])
301
+ end
262
302
 
263
303
  # The column in the current table that the key in the associated table references.
264
304
  def primary_key
@@ -297,6 +337,31 @@ module Sequel
297
337
  class OneToOneAssociationReflection < OneToManyAssociationReflection
298
338
  ASSOCIATION_TYPES[:one_to_one] = self
299
339
 
340
+ # one_to_one associations don't use an eager limit strategy by default, but
341
+ # support both DISTINCT ON and window functions as strategies.
342
+ def eager_limit_strategy
343
+ fetch(:_eager_limit_strategy) do
344
+ self[:_eager_limit_strategy] = case s = self[:eager_limit_strategy]
345
+ when Symbol
346
+ s
347
+ when true
348
+ ds = associated_class.dataset
349
+ if ds.supports_ordered_distinct_on?
350
+ :distinct_on
351
+ elsif ds.supports_window_functions?
352
+ :window_function
353
+ end
354
+ else
355
+ nil
356
+ end
357
+ end
358
+ end
359
+
360
+ # The limit and offset for this association (returned as a two element array).
361
+ def limit_and_offset
362
+ [1, nil]
363
+ end
364
+
300
365
  # one_to_one associations return a single object, not an array
301
366
  def returns_array?
302
367
  false
@@ -362,6 +427,11 @@ module Sequel
362
427
  self[:eager_loader_key] ||= self[:left_primary_key]
363
428
  end
364
429
 
430
+ # The hash key to use for the eager loading predicate (left side of IN (1, 2, 3))
431
+ def eager_loading_predicate_key
432
+ self[:eager_loading_predicate_key] ||= self[:uses_left_composite_keys] ? self[:left_keys].map{|k| SQL::QualifiedIdentifier.new(self[:join_table], k)} : SQL::QualifiedIdentifier.new(self[:join_table], self[:left_key])
433
+ end
434
+
365
435
  # many_to_many associations need to select a key in an associated table to eagerly load
366
436
  def eager_loading_use_associated_key?
367
437
  true
@@ -457,6 +527,12 @@ module Sequel
457
527
  # Project.association_reflection(:portfolio)
458
528
  # => {:type => :many_to_one, :name => :portfolio, ...}
459
529
  #
530
+ # Associations should not have the same names as any of the columns in the
531
+ # model's current table they reference. If you are dealing with an existing schema that
532
+ # has a column named status, you can't name the association status, you'd
533
+ # have to name it foo_status or something else. If you give an association the same name
534
+ # as a column, you will probably end up with an association that doesn't work, or a SystemStackError.
535
+ #
460
536
  # For a more in depth general overview, as well as a reference guide,
461
537
  # see the {Association Basics guide}[link:files/doc/association_basics_rdoc.html].
462
538
  # For examples of advanced usage, see the {Advanced Associations guide}[link:files/doc/advanced_associations_rdoc.html].
@@ -464,6 +540,9 @@ module Sequel
464
540
  # All association reflections defined for this model (default: {}).
465
541
  attr_reader :association_reflections
466
542
 
543
+ # The default :eager_limit_strategy option to use for *_many associations (default: nil)
544
+ attr_accessor :default_eager_limit_strategy
545
+
467
546
  # Array of all association reflections for this model class
468
547
  def all_association_reflections
469
548
  association_reflections.values
@@ -472,163 +551,179 @@ module Sequel
472
551
  # Associates a related model with the current model. The following types are
473
552
  # supported:
474
553
  #
475
- # * :many_to_one - Foreign key in current model's table points to
476
- # associated model's primary key. Each associated model object can
477
- # be associated with more than one current model objects. Each current
478
- # model object can be associated with only one associated model object.
479
- # * :one_to_many - Foreign key in associated model's table points to this
480
- # model's primary key. Each current model object can be associated with
481
- # more than one associated model objects. Each associated model object
482
- # can be associated with only one current model object.
483
- # * :one_to_one - Similar to one_to_many in terms of foreign keys, but
484
- # only one object is associated to the current object through the
485
- # association. The methods created are similar to many_to_one, except
486
- # that the one_to_one setter method saves the passed object.
487
- # * :many_to_many - A join table is used that has a foreign key that points
488
- # to this model's primary key and a foreign key that points to the
489
- # associated model's primary key. Each current model object can be
490
- # associated with many associated model objects, and each associated
491
- # model object can be associated with many current model objects.
554
+ # :many_to_one :: Foreign key in current model's table points to
555
+ # associated model's primary key. Each associated model object can
556
+ # be associated with more than one current model objects. Each current
557
+ # model object can be associated with only one associated model object.
558
+ # :one_to_many :: Foreign key in associated model's table points to this
559
+ # model's primary key. Each current model object can be associated with
560
+ # more than one associated model objects. Each associated model object
561
+ # can be associated with only one current model object.
562
+ # :one_to_one :: Similar to one_to_many in terms of foreign keys, but
563
+ # only one object is associated to the current object through the
564
+ # association. The methods created are similar to many_to_one, except
565
+ # that the one_to_one setter method saves the passed object.
566
+ # :many_to_many :: A join table is used that has a foreign key that points
567
+ # to this model's primary key and a foreign key that points to the
568
+ # associated model's primary key. Each current model object can be
569
+ # associated with many associated model objects, and each associated
570
+ # model object can be associated with many current model objects.
492
571
  #
493
572
  # The following options can be supplied:
494
- # * *ALL types*:
495
- # - :after_add - Symbol, Proc, or array of both/either specifying a callback to call
496
- # after a new item is added to the association.
497
- # - :after_load - Symbol, Proc, or array of both/either specifying a callback to call
498
- # after the associated record(s) have been retrieved from the database. Not called
499
- # when eager loading via eager_graph, but called when eager loading via eager.
500
- # - :after_remove - Symbol, Proc, or array of both/either specifying a callback to call
501
- # after an item is removed from the association.
502
- # - :after_set - Symbol, Proc, or array of both/either specifying a callback to call
503
- # after an item is set using the association setter method.
504
- # - :allow_eager - If set to false, you cannot load the association eagerly
505
- # via eager or eager_graph
506
- # - :before_add - Symbol, Proc, or array of both/either specifying a callback to call
507
- # before a new item is added to the association.
508
- # - :before_remove - Symbol, Proc, or array of both/either specifying a callback to call
509
- # before an item is removed from the association.
510
- # - :before_set - Symbol, Proc, or array of both/either specifying a callback to call
511
- # before an item is set using the association setter method.
512
- # - :cartesian_product_number - the number of joins completed by this association that could cause more
513
- # than one row for each row in the current table (default: 0 for many_to_one and one_to_one associations,
514
- # 1 for one_to_many and many_to_many associations).
515
- # - :class - The associated class or its name. If not
516
- # given, uses the association's name, which is camelized (and
517
- # singularized unless the type is :many_to_one or :one_to_one)
518
- # - :clone - Merge the current options and block into the options and block used in defining
519
- # the given association. Can be used to DRY up a bunch of similar associations that
520
- # all share the same options such as :class and :key, while changing the order and block used.
521
- # - :conditions - The conditions to use to filter the association, can be any argument passed to filter.
522
- # - :dataset - A proc that is instance_evaled to get the base dataset
523
- # to use for the _dataset method (before the other options are applied).
524
- # - :distinct - Use the DISTINCT clause when selecting associating object, both when
525
- # lazy loading and eager loading via .eager (but not when using .eager_graph).
526
- # - :eager - The associations to eagerly load via +eager+ when loading the associated object(s).
527
- # - :eager_block - If given, use the block instead of the default block when
528
- # eagerly loading. To not use a block when eager loading (when one is used normally),
529
- # set to nil.
530
- # - :eager_graph - The associations to eagerly load via +eager_graph+ when loading the associated object(s).
531
- # - :eager_grapher - A proc to use to implement eager loading via +eager_graph+, overriding the default.
532
- # Takes three arguments, a dataset, an alias to use for the table to graph for this association,
533
- # and the alias that was used for the current table (since you can cascade associations),
534
- # Should return a copy of the dataset with the association graphed into it.
535
- # - :eager_loader - A proc to use to implement eager loading, overriding the default. Takes one or three arguments.
536
- # If three arguments, the first should be a key hash (used solely to enhance performance), the second an array of records,
537
- # and the third a hash of dependent associations. If one argument, is passed a hash with keys :key_hash,
538
- # :rows, and :associations, corresponding to the three arguments, and an additional key :self, which is
539
- # the dataset doing the eager loading. In the proc, the associated records should
540
- # be queried from the database and the associations cache for each
541
- # record should be populated.
542
- # - :eager_loader_key - A symbol for the key column to use to populate the key hash
543
- # for the eager loader.
544
- # - :extend - A module or array of modules to extend the dataset with.
545
- # - :graph_block - The block to pass to join_table when eagerly loading
546
- # the association via +eager_graph+.
547
- # - :graph_conditions - The additional conditions to use on the SQL join when eagerly loading
548
- # the association via +eager_graph+. Should be a hash or an array of two element arrays. If not
549
- # specified, the :conditions option is used if it is a hash or array of two element arrays.
550
- # - :graph_join_type - The type of SQL join to use when eagerly loading the association via
551
- # eager_graph. Defaults to :left_outer.
552
- # - :graph_only_conditions - The conditions to use on the SQL join when eagerly loading
553
- # the association via +eager_graph+, instead of the default conditions specified by the
554
- # foreign/primary keys. This option causes the :graph_conditions option to be ignored.
555
- # - :graph_select - A column or array of columns to select from the associated table
556
- # when eagerly loading the association via +eager_graph+. Defaults to all
557
- # columns in the associated table.
558
- # - :limit - Limit the number of records to the provided value. Use
559
- # an array with two elements for the value to specify a limit (first element) and an offset (second element).
560
- # - :methods_module - The module that methods the association creates will be placed into. Defaults
561
- # to the module containing the model's columns.
562
- # - :order - the column(s) by which to order the association dataset. Can be a
563
- # singular column symbol or an array of column symbols.
564
- # - :order_eager_graph - Whether to add the association's order to the graphed dataset's order when graphing
565
- # via +eager_graph+. Defaults to true, so set to false to disable.
566
- # - :read_only - Do not add a setter method (for many_to_one or one_to_one associations),
567
- # or add_/remove_/remove_all_ methods (for one_to_many and many_to_many associations).
568
- # - :reciprocal - the symbol name of the reciprocal association,
569
- # if it exists. By default, Sequel will try to determine it by looking at the
570
- # associated model's assocations for a association that matches
571
- # the current association's key(s). Set to nil to not use a reciprocal.
572
- # - :select - the columns to select. Defaults to the associated class's
573
- # table_name.* in a many_to_many association, which means it doesn't include the attributes from the
574
- # join table. If you want to include the join table attributes, you can
575
- # use this option, but beware that the join table attributes can clash with
576
- # attributes from the model table, so you should alias any attributes that have
577
- # the same name in both the join table and the associated table.
578
- # - :validate - Set to false to not validate when implicitly saving any associated object.
579
- # * :many_to_one:
580
- # - :key - foreign_key in current model's table that references
581
- # associated model's primary key, as a symbol. Defaults to :"#{name}_id". Can use an
582
- # array of symbols for a composite key association.
583
- # - :primary_key - column in the associated table that :key option references, as a symbol.
584
- # Defaults to the primary key of the associated table. Can use an
585
- # array of symbols for a composite key association.
586
- # * :one_to_many and :one_to_one:
587
- # - :key - foreign key in associated model's table that references
588
- # current model's primary key, as a symbol. Defaults to
589
- # :"#{self.name.underscore}_id". Can use an
590
- # array of symbols for a composite key association.
591
- # - :primary_key - column in the current table that :key option references, as a symbol.
592
- # Defaults to primary key of the current table. Can use an
593
- # array of symbols for a composite key association.
594
- # * :many_to_many:
595
- # - :graph_join_table_block - The block to pass to +join_table+ for
596
- # the join table when eagerly loading the association via +eager_graph+.
597
- # - :graph_join_table_conditions - The additional conditions to use on the SQL join for
598
- # the join table when eagerly loading the association via +eager_graph+. Should be a hash
599
- # or an array of two element arrays.
600
- # - :graph_join_table_join_type - The type of SQL join to use for the join table when eagerly
601
- # loading the association via +eager_graph+. Defaults to the :graph_join_type option or
602
- # :left_outer.
603
- # - :graph_join_table_only_conditions - The conditions to use on the SQL join for the join
604
- # table when eagerly loading the association via +eager_graph+, instead of the default
605
- # conditions specified by the foreign/primary keys. This option causes the
606
- # :graph_join_table_conditions option to be ignored.
607
- # - :join_table - name of table that includes the foreign keys to both
608
- # the current model and the associated model, as a symbol. Defaults to the name
609
- # of current model and name of associated model, pluralized,
610
- # underscored, sorted, and joined with '_'.
611
- # - :join_table_block - proc that can be used to modify the dataset used in the add/remove/remove_all
612
- # methods. Should accept a dataset argument and return a modified dataset if present.
613
- # - :left_key - foreign key in join table that points to current model's
614
- # primary key, as a symbol. Defaults to :"#{self.name.underscore}_id".
615
- # Can use an array of symbols for a composite key association.
616
- # - :left_primary_key - column in current table that :left_key points to, as a symbol.
617
- # Defaults to primary key of current table. Can use an
618
- # array of symbols for a composite key association.
619
- # - :right_key - foreign key in join table that points to associated
620
- # model's primary key, as a symbol. Defaults to Defaults to :"#{name.to_s.singularize}_id".
621
- # Can use an array of symbols for a composite key association.
622
- # - :right_primary_key - column in associated table that :right_key points to, as a symbol.
623
- # Defaults to primary key of the associated table. Can use an
624
- # array of symbols for a composite key association.
625
- # - :uniq - Adds a after_load callback that makes the array of objects unique.
573
+ # === All Types
574
+ # :after_add :: Symbol, Proc, or array of both/either specifying a callback to call
575
+ # after a new item is added to the association.
576
+ # :after_load :: Symbol, Proc, or array of both/either specifying a callback to call
577
+ # after the associated record(s) have been retrieved from the database.
578
+ # :after_remove :: Symbol, Proc, or array of both/either specifying a callback to call
579
+ # after an item is removed from the association.
580
+ # :after_set :: Symbol, Proc, or array of both/either specifying a callback to call
581
+ # after an item is set using the association setter method.
582
+ # :allow_eager :: If set to false, you cannot load the association eagerly
583
+ # via eager or eager_graph
584
+ # :before_add :: Symbol, Proc, or array of both/either specifying a callback to call
585
+ # before a new item is added to the association.
586
+ # :before_remove :: Symbol, Proc, or array of both/either specifying a callback to call
587
+ # before an item is removed from the association.
588
+ # :before_set :: Symbol, Proc, or array of both/either specifying a callback to call
589
+ # before an item is set using the association setter method.
590
+ # :cartesian_product_number :: the number of joins completed by this association that could cause more
591
+ # than one row for each row in the current table (default: 0 for
592
+ # many_to_one and one_to_one associations, 1 for one_to_many and
593
+ # many_to_many associations).
594
+ # :class :: The associated class or its name. If not
595
+ # given, uses the association's name, which is camelized (and
596
+ # singularized unless the type is :many_to_one or :one_to_one)
597
+ # :clone :: Merge the current options and block into the options and block used in defining
598
+ # the given association. Can be used to DRY up a bunch of similar associations that
599
+ # all share the same options such as :class and :key, while changing the order and block used.
600
+ # :conditions :: The conditions to use to filter the association, can be any argument passed to filter.
601
+ # :dataset :: A proc that is instance_evaled to get the base dataset
602
+ # to use for the _dataset method (before the other options are applied).
603
+ # :distinct :: Use the DISTINCT clause when selecting associating object, both when
604
+ # lazy loading and eager loading via .eager (but not when using .eager_graph).
605
+ # :eager :: The associations to eagerly load via +eager+ when loading the associated object(s).
606
+ # :eager_block :: If given, use the block instead of the default block when
607
+ # eagerly loading. To not use a block when eager loading (when one is used normally),
608
+ # set to nil.
609
+ # :eager_graph :: The associations to eagerly load via +eager_graph+ when loading the associated object(s).
610
+ # many_to_many associations with this option cannot be eagerly loaded via +eager+.
611
+ # :eager_grapher :: A proc to use to implement eager loading via +eager_graph+, overriding the default.
612
+ # Takes one or three arguments. If three arguments, they are a dataset, an alias to use for
613
+ # the table to graph for this association, and the alias that was used for the current table
614
+ # (since you can cascade associations). If one argument, is passed a hash with keys :self,
615
+ # :table_alias, and :implicit_qualifier, corresponding to the three arguments, and an optional
616
+ # additional key :eager_block, a callback accepting one argument, the associated dataset. This
617
+ # is used to customize the association at query time.
618
+ # Should return a copy of the dataset with the association graphed into it.
619
+ # :eager_limit_strategy :: Determines the strategy used for enforcing limits when eager loading associations via
620
+ # the +eager+ method. For one_to_one associations, no strategy is used by default, and
621
+ # for *_many associations, the :ruby strategy is used by default, which still retrieves
622
+ # all records but slices the resulting array after the association is retrieved. You
623
+ # can pass a +true+ value for this option to have Sequel pick what it thinks is the best
624
+ # choice for the database, or specify a specific symbol to manually select a strategy.
625
+ # one_to_one associations support :distinct_on, :window_function, and :correlated_subquery.
626
+ # *_many associations support :ruby, :window_function, and :correlated_subquery.
627
+ # :eager_loader :: A proc to use to implement eager loading, overriding the default. Takes one or three arguments.
628
+ # If three arguments, the first should be a key hash (used solely to enhance performance), the
629
+ # second an array of records, and the third a hash of dependent associations. If one argument, is
630
+ # passed a hash with keys :key_hash, :rows, and :associations, corresponding to the three
631
+ # arguments, and an additional key :self, which is the dataset doing the eager loading. In the
632
+ # proc, the associated records should be queried from the database and the associations cache for
633
+ # each record should be populated.
634
+ # :eager_loader_key :: A symbol for the key column to use to populate the key hash
635
+ # for the eager loader.
636
+ # :extend :: A module or array of modules to extend the dataset with.
637
+ # :graph_block :: The block to pass to join_table when eagerly loading
638
+ # the association via +eager_graph+.
639
+ # :graph_conditions :: The additional conditions to use on the SQL join when eagerly loading
640
+ # the association via +eager_graph+. Should be a hash or an array of two element arrays. If not
641
+ # specified, the :conditions option is used if it is a hash or array of two element arrays.
642
+ # :graph_join_type :: The type of SQL join to use when eagerly loading the association via
643
+ # eager_graph. Defaults to :left_outer.
644
+ # :graph_only_conditions :: The conditions to use on the SQL join when eagerly loading
645
+ # the association via +eager_graph+, instead of the default conditions specified by the
646
+ # foreign/primary keys. This option causes the :graph_conditions option to be ignored.
647
+ # :graph_select :: A column or array of columns to select from the associated table
648
+ # when eagerly loading the association via +eager_graph+. Defaults to all
649
+ # columns in the associated table.
650
+ # :limit :: Limit the number of records to the provided value. Use
651
+ # an array with two elements for the value to specify a
652
+ # limit (first element) and an offset (second element).
653
+ # :methods_module :: The module that methods the association creates will be placed into. Defaults
654
+ # to the module containing the model's columns.
655
+ # :order :: the column(s) by which to order the association dataset. Can be a
656
+ # singular column symbol or an array of column symbols.
657
+ # :order_eager_graph :: Whether to add the association's order to the graphed dataset's order when graphing
658
+ # via +eager_graph+. Defaults to true, so set to false to disable.
659
+ # :read_only :: Do not add a setter method (for many_to_one or one_to_one associations),
660
+ # or add_/remove_/remove_all_ methods (for one_to_many and many_to_many associations).
661
+ # :reciprocal :: the symbol name of the reciprocal association,
662
+ # if it exists. By default, Sequel will try to determine it by looking at the
663
+ # associated model's assocations for a association that matches
664
+ # the current association's key(s). Set to nil to not use a reciprocal.
665
+ # :select :: the columns to select. Defaults to the associated class's
666
+ # table_name.* in a many_to_many association, which means it doesn't include the attributes from the
667
+ # join table. If you want to include the join table attributes, you can
668
+ # use this option, but beware that the join table attributes can clash with
669
+ # attributes from the model table, so you should alias any attributes that have
670
+ # the same name in both the join table and the associated table.
671
+ # :validate :: Set to false to not validate when implicitly saving any associated object.
672
+ # === :many_to_one
673
+ # :key :: foreign key in current model's table that references
674
+ # associated model's primary key, as a symbol. Defaults to :"#{name}_id". Can use an
675
+ # array of symbols for a composite key association.
676
+ # :primary_key :: column in the associated table that :key option references, as a symbol.
677
+ # Defaults to the primary key of the associated table. Can use an
678
+ # array of symbols for a composite key association.
679
+ # === :one_to_many and :one_to_one
680
+ # :key :: foreign key in associated model's table that references
681
+ # current model's primary key, as a symbol. Defaults to
682
+ # :"#{self.name.underscore}_id". Can use an
683
+ # array of symbols for a composite key association.
684
+ # :primary_key :: column in the current table that :key option references, as a symbol.
685
+ # Defaults to primary key of the current table. Can use an
686
+ # array of symbols for a composite key association.
687
+ # === :many_to_many
688
+ # :graph_join_table_block :: The block to pass to +join_table+ for
689
+ # the join table when eagerly loading the association via +eager_graph+.
690
+ # :graph_join_table_conditions :: The additional conditions to use on the SQL join for
691
+ # the join table when eagerly loading the association via +eager_graph+.
692
+ # Should be a hash or an array of two element arrays.
693
+ # :graph_join_table_join_type :: The type of SQL join to use for the join table when eagerly
694
+ # loading the association via +eager_graph+. Defaults to the
695
+ # :graph_join_type option or :left_outer.
696
+ # :graph_join_table_only_conditions :: The conditions to use on the SQL join for the join
697
+ # table when eagerly loading the association via +eager_graph+,
698
+ # instead of the default conditions specified by the
699
+ # foreign/primary keys. This option causes the
700
+ # :graph_join_table_conditions option to be ignored.
701
+ # :join_table :: name of table that includes the foreign keys to both
702
+ # the current model and the associated model, as a symbol. Defaults to the name
703
+ # of current model and name of associated model, pluralized,
704
+ # underscored, sorted, and joined with '_'.
705
+ # :join_table_block :: proc that can be used to modify the dataset used in the add/remove/remove_all
706
+ # methods. Should accept a dataset argument and return a modified dataset if present.
707
+ # :left_key :: foreign key in join table that points to current model's
708
+ # primary key, as a symbol. Defaults to :"#{self.name.underscore}_id".
709
+ # Can use an array of symbols for a composite key association.
710
+ # :left_primary_key :: column in current table that :left_key points to, as a symbol.
711
+ # Defaults to primary key of current table. Can use an
712
+ # array of symbols for a composite key association.
713
+ # :right_key :: foreign key in join table that points to associated
714
+ # model's primary key, as a symbol. Defaults to Defaults to :"#{name.to_s.singularize}_id".
715
+ # Can use an array of symbols for a composite key association.
716
+ # :right_primary_key :: column in associated table that :right_key points to, as a symbol.
717
+ # Defaults to primary key of the associated table. Can use an
718
+ # array of symbols for a composite key association.
719
+ # :uniq :: Adds a after_load callback that makes the array of objects unique.
626
720
  def associate(type, name, opts = {}, &block)
627
721
  raise(Error, 'one_to_many association type with :one_to_one option removed, used one_to_one association type') if opts[:one_to_one] && type == :one_to_many
628
722
  raise(Error, 'invalid association type') unless assoc_class = ASSOCIATION_TYPES[type]
629
723
  raise(Error, 'Model.associate name argument must be a symbol') unless Symbol === name
630
724
  raise(Error, ':eager_loader option must have an arity of 1 or 3') if opts[:eager_loader] && ![1, 3].include?(opts[:eager_loader].arity)
631
-
725
+ raise(Error, ':eager_grapher option must have an arity of 1 or 3') if opts[:eager_grapher] && ![1, 3].include?(opts[:eager_grapher].arity)
726
+
632
727
  # dup early so we don't modify opts
633
728
  orig_opts = opts.dup
634
729
  orig_opts = association_reflection(opts[:clone])[:orig_opts].merge(orig_opts) if opts[:clone]
@@ -676,49 +771,119 @@ module Sequel
676
771
  ds = ds.eager(opts[:eager]) if opts[:eager]
677
772
  ds = ds.distinct if opts[:distinct]
678
773
  if opts[:eager_graph]
774
+ raise(Error, "cannot eagerly load a #{opts[:type]} association that uses :eager_graph") if opts.eager_loading_use_associated_key?
679
775
  ds = ds.eager_graph(opts[:eager_graph])
680
- ds = ds.add_graph_aliases(opts.associated_key_alias=>[opts.associated_class.table_name, opts.associated_key_alias, SQL::QualifiedIdentifier.new(opts.associated_key_table, opts.associated_key_column)]) if opts.eager_loading_use_associated_key?
681
- elsif opts.eager_loading_use_associated_key?
776
+ end
777
+ ds = ds.eager(associations) unless Array(associations).empty?
778
+ ds = opts[:eager_block].call(ds) if opts[:eager_block]
779
+ ds = eager_options[:eager_block].call(ds) if eager_options[:eager_block]
780
+ if opts.eager_loading_use_associated_key?
682
781
  ds = if opts[:uses_left_composite_keys]
683
782
  t = opts.associated_key_table
684
- ds.select_more(*opts.associated_key_alias.zip(opts.associated_key_column).map{|a, c| SQL::AliasedExpression.new(SQL::QualifiedIdentifier.new(t, c), a)})
783
+ ds.select_append(*opts.associated_key_alias.zip(opts.associated_key_column).map{|a, c| SQL::AliasedExpression.new(SQL::QualifiedIdentifier.new(t, c), a)})
685
784
  else
686
- ds.select_more(SQL::AliasedExpression.new(SQL::QualifiedIdentifier.new(opts.associated_key_table, opts.associated_key_column), opts.associated_key_alias))
785
+ ds.select_append(SQL::AliasedExpression.new(SQL::QualifiedIdentifier.new(opts.associated_key_table, opts.associated_key_column), opts.associated_key_alias))
687
786
  end
688
787
  end
689
- ds = ds.eager(associations) unless Array(associations).empty?
690
- ds = opts[:eager_block].call(ds) if opts[:eager_block]
691
788
  ds
692
789
  end
693
790
 
694
791
  # Copy the association reflections to the subclass
695
792
  def inherited(subclass)
696
793
  super
697
- subclass.instance_variable_set(:@association_reflections, @association_reflections.dup)
794
+ subclass.instance_variable_set(:@association_reflections, association_reflections.dup)
795
+ subclass.default_eager_limit_strategy = default_eager_limit_strategy
698
796
  end
699
797
 
700
- # Shortcut for adding a many_to_many association, see associate
798
+ # Shortcut for adding a many_to_many association, see #associate
701
799
  def many_to_many(name, opts={}, &block)
702
800
  associate(:many_to_many, name, opts, &block)
703
801
  end
704
802
 
705
- # Shortcut for adding a many_to_one association, see associate
803
+ # Shortcut for adding a many_to_one association, see #associate
706
804
  def many_to_one(name, opts={}, &block)
707
805
  associate(:many_to_one, name, opts, &block)
708
806
  end
709
807
 
710
- # Shortcut for adding a one_to_many association, see associate
808
+ # Shortcut for adding a one_to_many association, see #associate
711
809
  def one_to_many(name, opts={}, &block)
712
810
  associate(:one_to_many, name, opts, &block)
713
811
  end
714
812
 
715
- # Shortcut for adding a one_to_one association, see associate.
813
+ # Shortcut for adding a one_to_one association, see #associate.
716
814
  def one_to_one(name, opts={}, &block)
717
815
  associate(:one_to_one, name, opts, &block)
718
816
  end
719
817
 
720
818
  private
721
819
 
820
+ # Use a correlated subquery to limit the results of the eager loading dataset.
821
+ def apply_correlated_subquery_eager_limit_strategy(ds, opts)
822
+ klass = opts.associated_class
823
+ kds = klass.dataset
824
+ dsa = ds.send(:dataset_alias, 1)
825
+ raise Error, "can't use a correlated subquery if the associated class (#{opts.associated_class.inspect}) does not have a primary key" unless pk = klass.primary_key
826
+ pka = Array(pk)
827
+ raise Error, "can't use a correlated subquery if the associated class (#{opts.associated_class.inspect}) has a composite primary key and the database does not support multiple column IN" if pka.length > 1 && !ds.supports_multiple_column_in?
828
+ table = kds.opts[:from]
829
+ raise Error, "can't use a correlated subquery unless the associated class (#{opts.associated_class.inspect}) uses a single FROM table" unless table && table.length == 1
830
+ table = table.first
831
+ if order = ds.opts[:order]
832
+ oproc = lambda do |x|
833
+ case x
834
+ when Symbol
835
+ t, c, a = ds.send(:split_symbol, x)
836
+ if t && t.to_sym == table
837
+ SQL::QualifiedIdentifier.new(dsa, c)
838
+ else
839
+ x
840
+ end
841
+ when SQL::QualifiedIdentifier
842
+ if x.table == table
843
+ SQL::QualifiedIdentifier.new(dsa, x.column)
844
+ else
845
+ x
846
+ end
847
+ when SQL::OrderedExpression
848
+ SQL::OrderedExpression.new(oproc.call(x.expression), x.descending, :nulls=>x.nulls)
849
+ else
850
+ x
851
+ end
852
+ end
853
+ order = order.map(&oproc)
854
+ end
855
+ limit, offset = opts.limit_and_offset
856
+
857
+ subquery = yield kds.
858
+ unlimited.
859
+ from(SQL::AliasedExpression.new(table, dsa)).
860
+ select(*pka.map{|k| SQL::QualifiedIdentifier.new(dsa, k)}).
861
+ order(*order).
862
+ limit(limit, offset)
863
+
864
+ pk = if pk.is_a?(Array)
865
+ pk.map{|k| SQL::QualifiedIdentifier.new(table, k)}
866
+ else
867
+ SQL::QualifiedIdentifier.new(table, pk)
868
+ end
869
+ ds.filter(pk=>subquery)
870
+ end
871
+
872
+ # Use a window function to limit the results of the eager loading dataset.
873
+ def apply_window_function_eager_limit_strategy(ds, opts)
874
+ rn = ds.row_number_column
875
+ limit, offset = opts.limit_and_offset
876
+ ds = ds.unordered.select_append{row_number(:over, :partition=>opts.eager_loading_predicate_key, :order=>ds.opts[:order]){}.as(rn)}.from_self
877
+ ds = if opts[:type] == :one_to_one
878
+ ds.where(rn => 1)
879
+ elsif offset
880
+ offset += 1
881
+ ds.where(rn => (offset...(offset+limit)))
882
+ else
883
+ ds.where{SQL::Identifier.new(rn) <= limit}
884
+ end
885
+ end
886
+
722
887
  # The module to use for the association's methods. Defaults to
723
888
  # the overridable_methods_module.
724
889
  def association_module(opts={})
@@ -753,9 +918,20 @@ module Sequel
753
918
  def_association_method(opts)
754
919
  end
755
920
 
756
- # Adds the association method to the association methods module.
757
- def def_association_method(opts)
758
- association_module_def(opts.association_method, opts){|*reload| load_associated_objects(opts, reload[0])}
921
+ # Adds the association method to the association methods module. Be backwards
922
+ # compatible with ruby 1.8.6, which doesn't support blocks taking block arguments.
923
+ if RUBY_VERSION >= '1.8.7'
924
+ class_eval <<-END, __FILE__, __LINE__+1
925
+ def def_association_method(opts)
926
+ association_module_def(opts.association_method, opts){|*dynamic_opts, &block| load_associated_objects(opts, dynamic_opts[0], &block)}
927
+ end
928
+ END
929
+ else
930
+ class_eval <<-END, __FILE__, __LINE__+1
931
+ def def_association_method(opts)
932
+ association_module_def(opts.association_method, opts){|*dynamic_opts| load_associated_objects(opts, dynamic_opts[0])}
933
+ end
934
+ END
759
935
  end
760
936
 
761
937
  # Configures many_to_many association reflection and adds the related association methods
@@ -782,14 +958,27 @@ module Sequel
782
958
  opts[:graph_join_table_join_type] ||= opts[:graph_join_type]
783
959
  opts[:after_load].unshift(:array_uniq!) if opts[:uniq]
784
960
  opts[:dataset] ||= proc{opts.associated_class.inner_join(join_table, rcks.zip(opts.right_primary_keys) + lcks.zip(lcpks.map{|k| send(k)}))}
785
- database = db
786
-
961
+
787
962
  opts[:eager_loader] ||= proc do |eo|
788
963
  h = eo[:key_hash][left_pk]
789
- eo[:rows].each{|object| object.associations[name] = []}
964
+ rows = eo[:rows]
965
+ rows.each{|object| object.associations[name] = []}
790
966
  r = uses_rcks ? rcks.zip(opts.right_primary_keys) : [[right, opts.right_primary_key]]
791
967
  l = uses_lcks ? [[lcks.map{|k| SQL::QualifiedIdentifier.new(join_table, k)}, h.keys]] : [[left, h.keys]]
792
- model.eager_loading_dataset(opts, opts.associated_class.inner_join(join_table, r + l), Array(opts.select), eo[:associations], eo).all do |assoc_record|
968
+ ds = model.eager_loading_dataset(opts, opts.associated_class.inner_join(join_table, r + l), Array(opts.select), eo[:associations], eo)
969
+ case opts.eager_limit_strategy
970
+ when :window_function
971
+ delete_rn = true
972
+ rn = ds.row_number_column
973
+ ds = apply_window_function_eager_limit_strategy(ds, opts)
974
+ when :correlated_subquery
975
+ ds = apply_correlated_subquery_eager_limit_strategy(ds, opts) do |xds|
976
+ dsa = ds.send(:dataset_alias, 2)
977
+ xds.inner_join(join_table, r + lcks.map{|k| [k, SQL::QualifiedIdentifier.new(join_table, k)]}, :table_alias=>dsa)
978
+ end
979
+ end
980
+ ds.all do |assoc_record|
981
+ assoc_record.values.delete(rn) if delete_rn
793
982
  hash_key = if uses_lcks
794
983
  left_key_alias.map{|k| assoc_record.values.delete(k)}
795
984
  else
@@ -798,6 +987,10 @@ module Sequel
798
987
  next unless objects = h[hash_key]
799
988
  objects.each{|object| object.associations[name].push(assoc_record)}
800
989
  end
990
+ if opts.eager_limit_strategy == :ruby
991
+ limit, offset = opts.limit_and_offset
992
+ rows.each{|o| o.associations[name] = o.associations[name].slice(offset||0, limit) || []}
993
+ end
801
994
  end
802
995
 
803
996
  join_type = opts[:graph_join_type]
@@ -810,9 +1003,10 @@ module Sequel
810
1003
  jt_only_conditions = opts[:graph_join_table_only_conditions]
811
1004
  jt_join_type = opts[:graph_join_table_join_type]
812
1005
  jt_graph_block = opts[:graph_join_table_block]
813
- opts[:eager_grapher] ||= proc do |ds, assoc_alias, table_alias|
814
- ds = ds.graph(join_table, use_jt_only_conditions ? jt_only_conditions : lcks.zip(lcpks) + graph_jt_conds, :select=>false, :table_alias=>ds.unused_table_alias(join_table), :join_type=>jt_join_type, :implicit_qualifier=>table_alias, :from_self_alias=>ds.opts[:eager_graph][:master], &jt_graph_block)
815
- ds.graph(opts.associated_class, use_only_conditions ? only_conditions : opts.right_primary_keys.zip(rcks) + conditions, :select=>select, :table_alias=>assoc_alias, :join_type=>join_type, &graph_block)
1006
+ opts[:eager_grapher] ||= proc do |eo|
1007
+ ds = eo[:self]
1008
+ ds = ds.graph(join_table, use_jt_only_conditions ? jt_only_conditions : lcks.zip(lcpks) + graph_jt_conds, :select=>false, :table_alias=>ds.unused_table_alias(join_table), :join_type=>jt_join_type, :implicit_qualifier=>eo[:implicit_qualifier], :from_self_alias=>ds.opts[:eager_graph][:master], &jt_graph_block)
1009
+ ds.graph(eager_graph_dataset(opts, eo), use_only_conditions ? only_conditions : opts.right_primary_keys.zip(rcks) + conditions, :select=>select, :table_alias=>eo[:table_alias], :join_type=>join_type, &graph_block)
816
1010
  end
817
1011
 
818
1012
  def_association_dataset_methods(opts)
@@ -876,8 +1070,9 @@ module Sequel
876
1070
  only_conditions = opts[:graph_only_conditions]
877
1071
  conditions = opts[:graph_conditions]
878
1072
  graph_block = opts[:graph_block]
879
- opts[:eager_grapher] ||= proc do |ds, assoc_alias, table_alias|
880
- ds.graph(opts.associated_class, use_only_conditions ? only_conditions : opts.primary_keys.zip(cks) + conditions, :select=>select, :table_alias=>assoc_alias, :join_type=>join_type, :implicit_qualifier=>table_alias, :from_self_alias=>ds.opts[:eager_graph][:master], &graph_block)
1073
+ opts[:eager_grapher] ||= proc do |eo|
1074
+ ds = eo[:self]
1075
+ ds.graph(eager_graph_dataset(opts, eo), use_only_conditions ? only_conditions : opts.primary_keys.zip(cks) + conditions, eo.merge(:select=>select, :join_type=>join_type, :from_self_alias=>ds.opts[:eager_graph][:master]), &graph_block)
881
1076
  end
882
1077
 
883
1078
  def_association_dataset_methods(opts)
@@ -905,20 +1100,38 @@ module Sequel
905
1100
  end
906
1101
  opts[:eager_loader] ||= proc do |eo|
907
1102
  h = eo[:key_hash][primary_key]
1103
+ rows = eo[:rows]
908
1104
  if one_to_one
909
- eo[:rows].each{|object| object.associations[name] = nil}
1105
+ rows.each{|object| object.associations[name] = nil}
910
1106
  else
911
- eo[:rows].each{|object| object.associations[name] = []}
1107
+ rows.each{|object| object.associations[name] = []}
912
1108
  end
913
1109
  reciprocal = opts.reciprocal
914
1110
  klass = opts.associated_class
915
- model.eager_loading_dataset(opts, klass.filter(uses_cks ? {cks.map{|k| SQL::QualifiedIdentifier.new(klass.table_name, k)}=>h.keys} : {SQL::QualifiedIdentifier.new(klass.table_name, key)=>h.keys}), opts.select, eo[:associations], eo).all do |assoc_record|
1111
+ filter_keys = opts.eager_loading_predicate_key
1112
+ ds = model.eager_loading_dataset(opts, klass.filter(filter_keys=>h.keys), opts.select, eo[:associations], eo)
1113
+ case opts.eager_limit_strategy
1114
+ when :distinct_on
1115
+ ds = ds.distinct(*filter_keys).order_prepend(*filter_keys)
1116
+ when :window_function
1117
+ delete_rn = true
1118
+ rn = ds.row_number_column
1119
+ ds = apply_window_function_eager_limit_strategy(ds, opts)
1120
+ when :correlated_subquery
1121
+ ds = apply_correlated_subquery_eager_limit_strategy(ds, opts) do |xds|
1122
+ xds.where(opts.associated_object_keys.map{|k| [SQL::QualifiedIdentifier.new(xds.first_source_alias, k), SQL::QualifiedIdentifier.new(xds.first_source_table, k)]})
1123
+ end
1124
+ end
1125
+ ds.all do |assoc_record|
1126
+ assoc_record.values.delete(rn) if delete_rn
916
1127
  hash_key = uses_cks ? cks.map{|k| assoc_record.send(k)} : assoc_record.send(key)
917
1128
  next unless objects = h[hash_key]
918
1129
  if one_to_one
919
1130
  objects.each do |object|
920
- object.associations[name] = assoc_record
921
- assoc_record.associations[reciprocal] = object if reciprocal
1131
+ unless object.associations[name]
1132
+ object.associations[name] = assoc_record
1133
+ assoc_record.associations[reciprocal] = object if reciprocal
1134
+ end
922
1135
  end
923
1136
  else
924
1137
  objects.each do |object|
@@ -927,6 +1140,10 @@ module Sequel
927
1140
  end
928
1141
  end
929
1142
  end
1143
+ if opts.eager_limit_strategy == :ruby
1144
+ limit, offset = opts.limit_and_offset
1145
+ rows.each{|o| o.associations[name] = o.associations[name].slice(offset||0, limit) || []}
1146
+ end
930
1147
  end
931
1148
 
932
1149
  join_type = opts[:graph_join_type]
@@ -936,10 +1153,11 @@ module Sequel
936
1153
  conditions = opts[:graph_conditions]
937
1154
  opts[:cartesian_product_number] ||= one_to_one ? 0 : 1
938
1155
  graph_block = opts[:graph_block]
939
- opts[:eager_grapher] ||= proc do |ds, assoc_alias, table_alias|
940
- ds = ds.graph(opts.associated_class, use_only_conditions ? only_conditions : cks.zip(cpks) + conditions, :select=>select, :table_alias=>assoc_alias, :join_type=>join_type, :implicit_qualifier=>table_alias, :from_self_alias=>ds.opts[:eager_graph][:master], &graph_block)
1156
+ opts[:eager_grapher] ||= proc do |eo|
1157
+ ds = eo[:self]
1158
+ ds = ds.graph(eager_graph_dataset(opts, eo), use_only_conditions ? only_conditions : cks.zip(cpks) + conditions, eo.merge(:select=>select, :join_type=>join_type, :from_self_alias=>ds.opts[:eager_graph][:master]), &graph_block)
941
1159
  # We only load reciprocals for one_to_many associations, as other reciprocals don't make sense
942
- ds.opts[:eager_graph][:reciprocals][assoc_alias] = opts.reciprocal
1160
+ ds.opts[:eager_graph][:reciprocals][eo[:table_alias]] = opts.reciprocal
943
1161
  ds
944
1162
  end
945
1163
 
@@ -993,6 +1211,15 @@ module Sequel
993
1211
  association_module_def(opts.remove_method, opts){|o,*args| remove_associated_object(opts, o, *args)}
994
1212
  association_module_def(opts.remove_all_method, opts){|*args| remove_all_associated_objects(opts, *args)}
995
1213
  end
1214
+
1215
+ # Return dataset to graph into given the association reflection, applying the :callback option if set.
1216
+ def eager_graph_dataset(opts, eager_options)
1217
+ ds = opts.associated_class.dataset
1218
+ if cb = eager_options[:callback]
1219
+ ds = cb.call(ds)
1220
+ end
1221
+ ds
1222
+ end
996
1223
  end
997
1224
 
998
1225
  # Instance methods used to implement the associations support.
@@ -1004,8 +1231,8 @@ module Sequel
1004
1231
  @associations ||= {}
1005
1232
  end
1006
1233
 
1007
- # Used internally by the associations code, like pk but doesn't raise
1008
- # an Error if the model has no primary key.
1234
+ # Formally used internally by the associations code, like pk but doesn't raise
1235
+ # an Error if the model has no primary key. Not used any longer, deprecated.
1009
1236
  def pk_or_nil
1010
1237
  key = primary_key
1011
1238
  key.is_a?(Array) ? key.map{|k| @values[k]} : @values[key]
@@ -1033,6 +1260,15 @@ module Sequel
1033
1260
  ds
1034
1261
  end
1035
1262
 
1263
+ # Return a dataset for the association after applying any dynamic callback.
1264
+ def _associated_dataset(opts, dynamic_opts)
1265
+ ds = send(opts.dataset_method)
1266
+ if callback = dynamic_opts[:callback]
1267
+ ds = callback.call(ds)
1268
+ end
1269
+ ds
1270
+ end
1271
+
1036
1272
  # Return an association dataset for the given association reflection
1037
1273
  def _dataset(opts)
1038
1274
  raise(Sequel::Error, "model object #{inspect} does not have a primary key") if opts.dataset_need_primary_key? && !pk
@@ -1045,13 +1281,14 @@ module Sequel
1045
1281
  opts[:join_table_block] ? opts[:join_table_block].call(ds) : ds
1046
1282
  end
1047
1283
 
1048
- # Return the associated objects from the dataset, without callbacks, reciprocals, and caching.
1049
- def _load_associated_objects(opts)
1284
+ # Return the associated objects from the dataset, without association callbacks, reciprocals, and caching.
1285
+ # Still apply the dynamic callback if present.
1286
+ def _load_associated_objects(opts, dynamic_opts={})
1050
1287
  if opts.returns_array?
1051
- opts.can_have_associated_objects?(self) ? send(opts.dataset_method).all : []
1288
+ opts.can_have_associated_objects?(self) ? _associated_dataset(opts, dynamic_opts).all : []
1052
1289
  else
1053
1290
  if opts.can_have_associated_objects?(self)
1054
- send(opts.dataset_method).all.first
1291
+ _associated_dataset(opts, dynamic_opts).all.first
1055
1292
  end
1056
1293
  end
1057
1294
  end
@@ -1113,13 +1350,20 @@ module Sequel
1113
1350
  end
1114
1351
 
1115
1352
  # Load the associated objects using the dataset, handling callbacks, reciprocals, and caching.
1116
- def load_associated_objects(opts, reload=false)
1353
+ def load_associated_objects(opts, dynamic_opts=nil)
1354
+ if dynamic_opts == true or dynamic_opts == false or dynamic_opts == nil
1355
+ dynamic_opts = {:reload=>dynamic_opts}
1356
+ elsif dynamic_opts.respond_to?(:call)
1357
+ dynamic_opts = {:callback=>dynamic_opts}
1358
+ end
1359
+ if block_given?
1360
+ dynamic_opts = dynamic_opts.merge(:callback=>Proc.new)
1361
+ end
1117
1362
  name = opts[:name]
1118
- if associations.include?(name) and !reload
1363
+ if associations.include?(name) and !dynamic_opts[:callback] and !dynamic_opts[:reload]
1119
1364
  associations[name]
1120
1365
  else
1121
- objs = _load_associated_objects(opts)
1122
- run_association_callbacks(opts, :after_load, objs)
1366
+ objs = _load_associated_objects(opts, dynamic_opts)
1123
1367
  if opts.set_reciprocal_to_self?
1124
1368
  if opts.returns_array?
1125
1369
  objs.each{|o| add_reciprocal_object(opts, o)}
@@ -1128,6 +1372,8 @@ module Sequel
1128
1372
  end
1129
1373
  end
1130
1374
  associations[name] = objs
1375
+ run_association_callbacks(opts, :after_load, objs)
1376
+ associations[name]
1131
1377
  end
1132
1378
  end
1133
1379
 
@@ -1238,7 +1484,7 @@ module Sequel
1238
1484
  # time, as it loads associated records using one query per association. However,
1239
1485
  # it does not allow you the ability to filter or order based on columns in associated tables. +eager_graph+ loads
1240
1486
  # all records in a single query using JOINs, allowing you to filter or order based on columns in associated
1241
- # tables. However, +eager_graph+ can be slower than +eager+, especially if multiple
1487
+ # tables. However, +eager_graph+ is usually slower than +eager+, especially if multiple
1242
1488
  # one_to_many or many_to_many associations are joined.
1243
1489
  #
1244
1490
  # You can cascade the eager loading (loading associations on associated objects)
@@ -1259,12 +1505,80 @@ module Sequel
1259
1505
  # Artist.eager_graph(:albums=>:tracks).all
1260
1506
  # Artist.eager(:albums=>{:tracks=>:genre}).all
1261
1507
  # Artist.eager_graph(:albums=>{:tracks=>:genre}).all
1508
+ #
1509
+ # You can also pass a callback as a hash value in order to customize the dataset being
1510
+ # eager loaded at query time, analogous to the way the :eager_block association option
1511
+ # allows you to customize it at association definition time. For example,
1512
+ # if you wanted artists with their albums since 1990:
1513
+ #
1514
+ # Artist.eager(:albums => proc{|ds| ds.filter{year > 1990}})
1515
+ #
1516
+ # Or if you needed albums and their artist's name only, using a single query:
1517
+ #
1518
+ # Albums.eager_graph(:artist => proc{|ds| ds.select(:name)})
1519
+ #
1520
+ # To cascade eager loading while using a callback, you substitute the cascaded
1521
+ # associations with a single entry hash that has the proc callback as the key and
1522
+ # the cascaded associations as the value. This will load artists with their albums
1523
+ # since 1990, and also the tracks on those albums and the genre for those tracks:
1524
+ #
1525
+ # Artist.eager(:albums => {proc{|ds| ds.filter{year > 1990}}=>{:tracks => :genre}})
1262
1526
  module DatasetMethods
1263
1527
  # Add the <tt>eager!</tt> and <tt>eager_graph!</tt> mutation methods to the dataset.
1264
1528
  def self.extended(obj)
1265
1529
  obj.def_mutation_method(:eager, :eager_graph)
1266
1530
  end
1267
1531
 
1532
+ # If the expression is in the form <tt>x = y</tt> where +y+ is a <tt>Sequel::Model</tt>
1533
+ # instance, array of <tt>Sequel::Model</tt> instances, or a <tt>Sequel::Model</tt> dataset,
1534
+ # assume +x+ is an association symbol and look up the association reflection
1535
+ # via the dataset's model. From there, return the appropriate SQL based on the type of
1536
+ # association and the values of the foreign/primary keys of +y+. For most association
1537
+ # types, this is a simple transformation, but for +many_to_many+ associations this
1538
+ # creates a subquery to the join table.
1539
+ def complex_expression_sql(op, args)
1540
+ r = args.at(1)
1541
+ if (((op == :'=' || op == :'!=') and r.is_a?(Sequel::Model)) ||
1542
+ (multiple = ((op == :IN || op == :'NOT IN') and ((is_ds = r.is_a?(Sequel::Dataset)) or r.all?{|x| x.is_a?(Sequel::Model)}))))
1543
+ l = args.at(0)
1544
+ if ar = model.association_reflections[l]
1545
+ if multiple
1546
+ klass = ar.associated_class
1547
+ if is_ds
1548
+ if r.respond_to?(:model)
1549
+ unless r.model <= klass
1550
+ # A dataset for a different model class, could be a valid regular query
1551
+ return super
1552
+ end
1553
+ else
1554
+ # Not a model dataset, could be a valid regular query
1555
+ return super
1556
+ end
1557
+ else
1558
+ unless r.all?{|x| x.is_a?(klass)}
1559
+ raise Sequel::Error, "invalid association class for one object for association #{l.inspect} used in dataset filter for model #{model.inspect}, expected class #{klass.inspect}"
1560
+ end
1561
+ end
1562
+ elsif !r.is_a?(ar.associated_class)
1563
+ raise Sequel::Error, "invalid association class #{r.class.inspect} for association #{l.inspect} used in dataset filter for model #{model.inspect}, expected class #{ar.associated_class.inspect}"
1564
+ end
1565
+
1566
+ if exp = association_filter_expression(op, ar, r)
1567
+ literal(exp)
1568
+ else
1569
+ raise Sequel::Error, "invalid association type #{ar[:type].inspect} for association #{l.inspect} used in dataset filter for model #{model.inspect}"
1570
+ end
1571
+ elsif multiple && (is_ds || r.empty?)
1572
+ # Not a query designed for this support, could be a valid regular query
1573
+ super
1574
+ else
1575
+ raise Sequel::Error, "invalid association #{l.inspect} used in dataset filter for model #{model.inspect}"
1576
+ end
1577
+ else
1578
+ super
1579
+ end
1580
+ end
1581
+
1268
1582
  # The preferred eager loading method. Loads all associated records using one
1269
1583
  # query for each association.
1270
1584
  #
@@ -1311,9 +1625,9 @@ module Sequel
1311
1625
  # The secondary eager loading method. Loads all associations in a single query. This
1312
1626
  # method should only be used if you need to filter or order based on columns in associated tables.
1313
1627
  #
1314
- # This method builds an object graph using <tt>Dataset#graph</tt>. Then it uses the graph
1315
- # to build the associations, and finally replaces the graph with a simple array
1316
- # of model objects.
1628
+ # This method uses <tt>Dataset#graph</tt> to create appropriate aliases for columns in all the
1629
+ # tables. Then it uses the graph's metadata to build the associations from the single hash, and
1630
+ # finally replaces the array of hashes with an array model objects inside all.
1317
1631
  #
1318
1632
  # Be very careful when using this with multiple one_to_many or many_to_many associations, as you can
1319
1633
  # create large cartesian products. If you must graph multiple one_to_many and many_to_many associations,
@@ -1327,18 +1641,16 @@ module Sequel
1327
1641
  # all objects. You can use the :graph_* association options to modify the SQL query.
1328
1642
  #
1329
1643
  # Like +eager+, you need to call +all+ on the dataset for the eager loading to work. If you just
1330
- # call +each+, you will get a normal graphed result back (a hash with table alias symbol keys and
1331
- # model object values).
1644
+ # call +each+, it will yield plain hashes, each containing all columns from all the tables.
1332
1645
  def eager_graph(*associations)
1333
1646
  ds = if @opts[:eager_graph]
1334
1647
  self
1335
1648
  else
1336
1649
  # Each of the following have a symbol key for the table alias, with the following values:
1337
1650
  # :reciprocals - the reciprocal instance variable to use for this association
1651
+ # :reflections - AssociationReflection instance related to this association
1338
1652
  # :requirements - array of requirements for this association
1339
- # :alias_association_type_map - the type of association for this association
1340
- # :alias_association_name_map - the name of the association for this association
1341
- clone(:eager_graph=>{:requirements=>{}, :master=>alias_symbol(first_source), :alias_association_type_map=>{}, :alias_association_name_map=>{}, :reciprocals=>{}, :cartesian_product_number=>0})
1653
+ clone(:eager_graph=>{:requirements=>{}, :master=>alias_symbol(first_source), :reflections=>{}, :reciprocals=>{}, :cartesian_product_number=>0})
1342
1654
  end
1343
1655
  ds.eager_graph_associations(ds, model, ds.opts[:eager_graph][:master], [], *associations)
1344
1656
  end
@@ -1366,20 +1678,32 @@ module Sequel
1366
1678
  # r :: association reflection for the current association
1367
1679
  # *associations :: any associations dependent on this one
1368
1680
  def eager_graph_association(ds, model, ta, requirements, r, *associations)
1369
- klass = r.associated_class
1370
1681
  assoc_name = r[:name]
1371
1682
  assoc_table_alias = ds.unused_table_alias(assoc_name)
1372
- ds = r[:eager_grapher].call(ds, assoc_table_alias, ta)
1683
+ loader = r[:eager_grapher]
1684
+ if !associations.empty?
1685
+ if associations.first.respond_to?(:call)
1686
+ callback = associations.first
1687
+ associations = {}
1688
+ elsif associations.length == 1 && (assocs = associations.first).is_a?(Hash) && assocs.length == 1 && (pr_assoc = assocs.to_a.first) && pr_assoc.first.respond_to?(:call)
1689
+ callback, assoc = pr_assoc
1690
+ associations = assoc.is_a?(Array) ? assoc : [assoc]
1691
+ end
1692
+ end
1693
+ ds = if loader.arity == 1
1694
+ loader.call(:self=>ds, :table_alias=>assoc_table_alias, :implicit_qualifier=>ta, :callback=>callback)
1695
+ else
1696
+ loader.call(ds, assoc_table_alias, ta)
1697
+ end
1373
1698
  ds = ds.order_more(*qualified_expression(r[:order], assoc_table_alias)) if r[:order] and r[:order_eager_graph]
1374
1699
  eager_graph = ds.opts[:eager_graph]
1375
1700
  eager_graph[:requirements][assoc_table_alias] = requirements.dup
1376
- eager_graph[:alias_association_name_map][assoc_table_alias] = assoc_name
1377
- eager_graph[:alias_association_type_map][assoc_table_alias] = r.returns_array?
1701
+ eager_graph[:reflections][assoc_table_alias] = r
1378
1702
  eager_graph[:cartesian_product_number] += r[:cartesian_product_number] || 2
1379
1703
  ds = ds.eager_graph_associations(ds, r.associated_class, assoc_table_alias, requirements + [assoc_table_alias], *associations) unless associations.empty?
1380
1704
  ds
1381
1705
  end
1382
-
1706
+
1383
1707
  # Check the associations are valid for the given model.
1384
1708
  # Call eager_graph_association on each association.
1385
1709
  #
@@ -1406,20 +1730,220 @@ module Sequel
1406
1730
  ds
1407
1731
  end
1408
1732
 
1409
- # Build associations out of the array of returned object graphs.
1410
- def eager_graph_build_associations(record_graphs)
1411
- eager_graph = @opts[:eager_graph]
1412
- master = eager_graph[:master]
1733
+ # Replace the array of plain hashes with an array of model objects will all eager_graphed
1734
+ # associations set in the associations cache for each object.
1735
+ def eager_graph_build_associations(hashes)
1736
+ hashes.replace(EagerGraphLoader.new(self).load(hashes))
1737
+ end
1738
+
1739
+ private
1740
+
1741
+ # Return an expression for filtering by the given association reflection and associated object.
1742
+ def association_filter_expression(op, ref, obj)
1743
+ meth = :"#{ref[:type]}_association_filter_expression"
1744
+ send(meth, op, ref, obj) if respond_to?(meth, true)
1745
+ end
1746
+
1747
+ # Handle inversion for association filters by returning an inverted expression,
1748
+ # plus also handling cases where the referenced columns are NULL.
1749
+ def association_filter_handle_inversion(op, exp, cols)
1750
+ if op == :'!=' || op == :'NOT IN'
1751
+ if exp == SQL::Constants::FALSE
1752
+ ~exp
1753
+ else
1754
+ ~exp | Sequel::SQL::BooleanExpression.from_value_pairs(cols.zip([]), :OR)
1755
+ end
1756
+ else
1757
+ exp
1758
+ end
1759
+ end
1760
+
1761
+ # Return an expression for making sure that the given keys match the value of
1762
+ # the given methods for either the single object given or for any of the objects
1763
+ # given if +obj+ is an array.
1764
+ def association_filter_key_expression(keys, meths, obj)
1765
+ vals = if obj.is_a?(Sequel::Dataset)
1766
+ {(keys.length == 1 ? keys.first : keys)=>obj.select(*meths).exclude(Sequel::SQL::BooleanExpression.from_value_pairs(meths.zip([]), :OR))}
1767
+ else
1768
+ vals = Array(obj).reject{|o| !meths.all?{|m| o.send(m)}}
1769
+ return SQL::Constants::FALSE if vals.empty?
1770
+ if obj.is_a?(Array)
1771
+ if keys.length == 1
1772
+ meth = meths.first
1773
+ {keys.first=>vals.map{|o| o.send(meth)}}
1774
+ else
1775
+ {keys=>vals.map{|o| meths.map{|m| o.send(m)}}}
1776
+ end
1777
+ else
1778
+ keys.zip(meths.map{|k| obj.send(k)})
1779
+ end
1780
+ end
1781
+ SQL::BooleanExpression.from_value_pairs(vals)
1782
+ end
1783
+
1784
+ # Make sure the association is valid for this model, and return the related AssociationReflection.
1785
+ def check_association(model, association)
1786
+ raise(Sequel::UndefinedAssociation, "Invalid association #{association} for #{model.name}") unless reflection = model.association_reflection(association)
1787
+ raise(Sequel::Error, "Eager loading is not allowed for #{model.name} association #{association}") if reflection[:allow_eager] == false
1788
+ reflection
1789
+ end
1790
+
1791
+ # Eagerly load all specified associations
1792
+ def eager_load(a, eager_assoc=@opts[:eager])
1793
+ return if a.empty?
1794
+ # Key is foreign/primary key name symbol
1795
+ # Value is hash with keys being foreign/primary key values (generally integers)
1796
+ # and values being an array of current model objects with that
1797
+ # specific foreign/primary key
1798
+ key_hash = {}
1799
+ # Reflections for all associations to eager load
1800
+ reflections = eager_assoc.keys.collect{|assoc| model.association_reflection(assoc) || (raise Sequel::UndefinedAssociation, "Model: #{self}, Association: #{assoc}")}
1801
+
1802
+ # Populate keys to monitor
1803
+ reflections.each{|reflection| key_hash[reflection.eager_loader_key] ||= Hash.new{|h,k| h[k] = []}}
1804
+
1805
+ # Associate each object with every key being monitored
1806
+ a.each do |rec|
1807
+ key_hash.each do |key, id_map|
1808
+ case key
1809
+ when Array
1810
+ id_map[key.map{|k| rec[k]}] << rec if key.all?{|k| rec[k]}
1811
+ when Symbol
1812
+ id_map[rec[key]] << rec if rec[key]
1813
+ end
1814
+ end
1815
+ end
1816
+
1817
+ reflections.each do |r|
1818
+ loader = r[:eager_loader]
1819
+ associations = eager_assoc[r[:name]]
1820
+ if associations.respond_to?(:call)
1821
+ eager_block = associations
1822
+ associations = {}
1823
+ elsif associations.is_a?(Hash) && associations.length == 1 && (pr_assoc = associations.to_a.first) && pr_assoc.first.respond_to?(:call)
1824
+ eager_block, associations = pr_assoc
1825
+ end
1826
+ if loader.arity == 1
1827
+ loader.call(:key_hash=>key_hash, :rows=>a, :associations=>associations, :self=>self, :eager_block=>eager_block)
1828
+ else
1829
+ loader.call(key_hash, a, associations)
1830
+ end
1831
+ a.each{|object| object.send(:run_association_callbacks, r, :after_load, object.associations[r[:name]])} unless r[:after_load].empty?
1832
+ end
1833
+ end
1834
+
1835
+ # Return plain hashes instead of calling the row_proc if eager_graph is being used.
1836
+ def graph_each(&block)
1837
+ @opts[:eager_graph] ? fetch_rows(select_sql, &block) : super
1838
+ end
1839
+
1840
+ # Return a subquery expression for filering by a many_to_many association
1841
+ def many_to_many_association_filter_expression(op, ref, obj)
1842
+ lpks, lks, rks = ref.values_at(:left_primary_keys, :left_keys, :right_keys)
1843
+ lpks = lpks.first if lpks.length == 1
1844
+ exp = association_filter_key_expression(rks, ref.right_primary_keys, obj)
1845
+ if exp == SQL::Constants::FALSE
1846
+ association_filter_handle_inversion(op, exp, Array(lpks))
1847
+ else
1848
+ association_filter_handle_inversion(op, SQL::BooleanExpression.from_value_pairs(lpks=>model.db[ref[:join_table]].select(*lks).where(exp).exclude(SQL::BooleanExpression.from_value_pairs(lks.zip([]), :OR))), Array(lpks))
1849
+ end
1850
+ end
1851
+
1852
+ # Return a simple equality expression for filering by a many_to_one association
1853
+ def many_to_one_association_filter_expression(op, ref, obj)
1854
+ keys = ref[:keys]
1855
+ association_filter_handle_inversion(op, association_filter_key_expression(keys, ref.primary_keys, obj), keys)
1856
+ end
1857
+
1858
+ # Return a simple equality expression for filering by a one_to_* association
1859
+ def one_to_many_association_filter_expression(op, ref, obj)
1860
+ keys = ref[:primary_keys]
1861
+ association_filter_handle_inversion(op, association_filter_key_expression(keys, ref[:keys], obj), keys)
1862
+ end
1863
+ alias one_to_one_association_filter_expression one_to_many_association_filter_expression
1864
+
1865
+ # Build associations from the graph if #eager_graph was used,
1866
+ # and/or load other associations if #eager was used.
1867
+ def post_load(all_records)
1868
+ eager_graph_build_associations(all_records) if @opts[:eager_graph]
1869
+ eager_load(all_records) if @opts[:eager]
1870
+ super
1871
+ end
1872
+ end
1873
+
1874
+ # This class is the internal implementation of eager_graph. It is responsible for taking an array of plain
1875
+ # hashes and returning an array of model objects with all eager_graphed associations already set in the
1876
+ # association cache.
1877
+ class EagerGraphLoader
1878
+ # Hash with table alias symbol keys and after_load hook values
1879
+ attr_reader :after_load_map
1880
+
1881
+ # Hash with table alias symbol keys and association name values
1882
+ attr_reader :alias_map
1883
+
1884
+ # Hash with table alias symbol keys and subhash values mapping column_alias symbols to the
1885
+ # symbol of the real name of the column
1886
+ attr_reader :column_maps
1887
+
1888
+ # Recursive hash with table alias symbol keys mapping to hashes with dependent table alias symbol keys.
1889
+ attr_reader :dependency_map
1890
+
1891
+ # Hash with table alias symbol keys and [limit, offset] values
1892
+ attr_reader :limit_map
1893
+
1894
+ # Hash with table alias symbol keys and callable values used to create model instances
1895
+ # The table alias symbol for the primary model
1896
+ attr_reader :master
1897
+
1898
+ # Hash with table alias symbol keys and primary key symbol values (or arrays of primary key symbols for
1899
+ # composite key tables)
1900
+ attr_reader :primary_keys
1901
+
1902
+ # Hash with table alias symbol keys and reciprocal association symbol values,
1903
+ # used for setting reciprocals for one_to_many associations.
1904
+ attr_reader :reciprocal_map
1905
+
1906
+ # Hash with table alias symbol keys and subhash values mapping primary key symbols (or array of symbols)
1907
+ # to model instances. Used so that only a single model instance is created for each object.
1908
+ attr_reader :records_map
1909
+
1910
+ # Hash with table alias symbol keys and AssociationReflection values
1911
+ attr_reader :reflection_map
1912
+
1913
+ # Hash with table alias symbol keys and callable values used to create model instances
1914
+ attr_reader :row_procs
1915
+
1916
+ # Hash with table alias symbol keys and true/false values, where true means the
1917
+ # association represented by the table alias uses an array of values instead of
1918
+ # a single value (i.e. true => *_many, false => *_to_one).
1919
+ attr_reader :type_map
1920
+
1921
+ # Initialize all of the data structures used during loading.
1922
+ def initialize(dataset)
1923
+ opts = dataset.opts
1924
+ eager_graph = opts[:eager_graph]
1925
+ @master = eager_graph[:master]
1413
1926
  requirements = eager_graph[:requirements]
1414
- alias_map = eager_graph[:alias_association_name_map]
1415
- type_map = eager_graph[:alias_association_type_map]
1416
- reciprocal_map = eager_graph[:reciprocals]
1927
+ reflection_map = @reflection_map = eager_graph[:reflections]
1928
+ reciprocal_map = @reciprocal_map = eager_graph[:reciprocals]
1929
+ @unique = eager_graph[:cartesian_product_number] > 1
1417
1930
 
1931
+ alias_map = @alias_map = {}
1932
+ type_map = @type_map = {}
1933
+ after_load_map = @after_load_map = {}
1934
+ limit_map = @limit_map = {}
1935
+ reflection_map.each do |k, v|
1936
+ alias_map[k] = v[:name]
1937
+ type_map[k] = v.returns_array?
1938
+ after_load_map[k] = v[:after_load] unless v[:after_load].empty?
1939
+ limit_map[k] = v.limit_and_offset if v[:limit]
1940
+ end
1941
+
1418
1942
  # Make dependency map hash out of requirements array for each association.
1419
1943
  # This builds a tree of dependencies that will be used for recursion
1420
1944
  # to ensure that all parts of the object graph are loaded into the
1421
1945
  # appropriate subordinate association.
1422
- dependency_map = {}
1946
+ @dependency_map = {}
1423
1947
  # Sort the associations by requirements length, so that
1424
1948
  # requirements are added to the dependency hash before their
1425
1949
  # dependencies.
@@ -1439,139 +1963,199 @@ module Sequel
1439
1963
  # This mapping is used to make sure that duplicate entries in the
1440
1964
  # result set are mapped to a single record. For example, using a
1441
1965
  # single one_to_many association with 10 associated records,
1442
- # the main object will appear in the object graph 10 times.
1966
+ # the main object column values appear in the object graph 10 times.
1443
1967
  # We map by primary key, if available, or by the object's entire values,
1444
1968
  # if not. The mapping must be per table, so create sub maps for each table
1445
1969
  # alias.
1446
- records_map = {master=>{}}
1970
+ records_map = {@master=>{}}
1447
1971
  alias_map.keys.each{|ta| records_map[ta] = {}}
1972
+ @records_map = records_map
1973
+
1974
+ datasets = opts[:graph][:table_aliases].to_a.reject{|ta,ds| ds.nil?}
1975
+ column_aliases = opts[:graph_aliases] || opts[:graph][:column_aliases]
1976
+ primary_keys = {}
1977
+ column_maps = {}
1978
+ models = {}
1979
+ row_procs = {}
1980
+ datasets.each do |ta, ds|
1981
+ models[ta] = ds.model
1982
+ primary_keys[ta] = []
1983
+ column_maps[ta] = {}
1984
+ row_procs[ta] = ds.row_proc
1985
+ end
1986
+ column_aliases.each do |col_alias, tc|
1987
+ ta, column = tc
1988
+ column_maps[ta][col_alias] = column
1989
+ end
1990
+ column_maps.each do |ta, h|
1991
+ pk = models[ta].primary_key
1992
+ if pk.is_a?(Array)
1993
+ primary_keys[ta] = []
1994
+ h.select{|ca, c| primary_keys[ta] << ca if pk.include?(c)}
1995
+ else
1996
+ h.select{|ca, c| primary_keys[ta] = ca if pk == c}
1997
+ end
1998
+ end
1999
+ @column_maps = column_maps
2000
+ @primary_keys = primary_keys
2001
+ @row_procs = row_procs
2002
+
2003
+ # For performance, create two special maps for the master table,
2004
+ # so you can skip a hash lookup.
2005
+ @master_column_map = column_maps[master]
2006
+ @master_primary_keys = primary_keys[master]
2007
+
2008
+ # Add a special hash mapping table alias symbols to 5 element arrays that just
2009
+ # contain the data in other data structures for that table alias. This is
2010
+ # used for performance, to get all values in one hash lookup instead of
2011
+ # separate hash lookups for each data structure.
2012
+ ta_map = {}
2013
+ alias_map.keys.each do |ta|
2014
+ ta_map[ta] = [records_map[ta], row_procs[ta], alias_map[ta], type_map[ta], reciprocal_map[ta]]
2015
+ end
2016
+ @ta_map = ta_map
2017
+ end
2018
+
2019
+ # Return an array of primary model instances with the associations cache prepopulated
2020
+ # for all model objects (both primary and associated).
2021
+ def load(hashes)
2022
+ master = master()
1448
2023
 
2024
+ # Assign to local variables for speed increase
2025
+ rp = row_procs[master]
2026
+ rm = records_map[master]
2027
+ dm = dependency_map
2028
+
1449
2029
  # This will hold the final record set that we will be replacing the object graph with.
1450
2030
  records = []
1451
- record_graphs.each do |record_graph|
1452
- primary_record = record_graph[master]
1453
- key = primary_record.pk_or_nil || primary_record.values.sort_by{|x| x[0].to_s}
1454
- if cached_pr = records_map[master][key]
1455
- primary_record = cached_pr
1456
- else
1457
- records_map[master][key] = primary_record
2031
+
2032
+ hashes.each do |h|
2033
+ unless key = master_pk(h)
2034
+ key = hkey(master_hfor(h))
2035
+ end
2036
+ unless primary_record = rm[key]
2037
+ primary_record = rm[key] = rp.call(master_hfor(h))
1458
2038
  # Only add it to the list of records to return if it is a new record
1459
2039
  records.push(primary_record)
1460
2040
  end
1461
2041
  # Build all associations for the current object and it's dependencies
1462
- eager_graph_build_associations_graph(dependency_map, alias_map, type_map, reciprocal_map, records_map, primary_record, record_graph)
2042
+ _load(dm, primary_record, h)
1463
2043
  end
1464
2044
 
1465
2045
  # Remove duplicate records from all associations if this graph could possibly be a cartesian product
1466
- eager_graph_make_associations_unique(records, dependency_map, alias_map, type_map) if eager_graph[:cartesian_product_number] > 1
1467
-
1468
- # Replace the array of object graphs with an array of model objects
1469
- record_graphs.replace(records)
2046
+ # Run after_load procs if there are any
2047
+ post_process(records, dm) if @unique || !after_load_map.empty? || !limit_map.empty?
2048
+
2049
+ records
1470
2050
  end
1471
2051
 
1472
2052
  private
1473
-
1474
- # Make sure the association is valid for this model, and return the related AssociationReflection.
1475
- def check_association(model, association)
1476
- raise(Sequel::UndefinedAssociation, "Invalid association #{association} for #{model.name}") unless reflection = model.association_reflection(association)
1477
- raise(Sequel::Error, "Eager loading is not allowed for #{model.name} association #{association}") if reflection[:allow_eager] == false
1478
- reflection
1479
- end
1480
-
1481
- # Build associations for the current object. This is called recursively
1482
- # to build all dependencies.
1483
- def eager_graph_build_associations_graph(dependency_map, alias_map, type_map, reciprocal_map, records_map, current, record_graph)
1484
- return if dependency_map.empty?
1485
- # Don't clobber the cached association value for one_to_many and many_to_many associations if it has already been setup
1486
- dependency_map.keys.each do |ta|
1487
- assoc_name = alias_map[ta]
1488
- current.associations[assoc_name] = type_map[ta] ? [] : nil unless current.associations.include?(assoc_name)
1489
- end
2053
+
2054
+ # Recursive method that creates associated model objects and associates them to the current model object.
2055
+ def _load(dependency_map, current, h)
1490
2056
  dependency_map.each do |ta, deps|
1491
- next unless rec = record_graph[ta]
1492
- key = rec.pk_or_nil || rec.values.sort_by{|x| x[0].to_s}
1493
- if cached_rec = records_map[ta][key]
1494
- rec = cached_rec
1495
- else
1496
- records_map[ta][key] = rec
2057
+ unless key = pk(ta, h)
2058
+ ta_h = hfor(ta, h)
2059
+ unless ta_h.values.any?
2060
+ assoc_name = alias_map[ta]
2061
+ unless (assoc = current.associations).has_key?(assoc_name)
2062
+ assoc[assoc_name] = type_map[ta] ? [] : nil
2063
+ end
2064
+ next
2065
+ end
2066
+ key = hkey(ta_h)
2067
+ end
2068
+ rm, rp, assoc_name, tm, rcm = @ta_map[ta]
2069
+ unless rec = rm[key]
2070
+ rec = rm[key] = rp.call(hfor(ta, h))
1497
2071
  end
1498
- assoc_name = alias_map[ta]
1499
- if type_map[ta]
1500
- current.associations[assoc_name].push(rec)
1501
- if reciprocal = reciprocal_map[ta]
1502
- rec.associations[reciprocal] = current
2072
+
2073
+ if tm
2074
+ unless (assoc = current.associations).has_key?(assoc_name)
2075
+ assoc[assoc_name] = []
1503
2076
  end
2077
+ assoc[assoc_name].push(rec)
2078
+ rec.associations[rcm] = current if rcm
1504
2079
  else
1505
- current.associations[assoc_name] = rec
2080
+ current.associations[assoc_name] ||= rec
1506
2081
  end
1507
2082
  # Recurse into dependencies of the current object
1508
- eager_graph_build_associations_graph(deps, alias_map, type_map, reciprocal_map, records_map, rec, record_graph)
2083
+ _load(deps, rec, h) unless deps.empty?
1509
2084
  end
1510
2085
  end
1511
2086
 
2087
+ # Return the subhash for the specific table alias +ta+ by parsing the values out of the main hash +h+
2088
+ def hfor(ta, h)
2089
+ out = {}
2090
+ @column_maps[ta].each{|ca, c| out[c] = h[ca]}
2091
+ out
2092
+ end
2093
+
2094
+ # Return a suitable hash key for any subhash +h+, which is an array of values by column order.
2095
+ # This is only used if the primary key cannot be used.
2096
+ def hkey(h)
2097
+ h.sort_by{|x| x[0].to_s}
2098
+ end
2099
+
2100
+ # Return the subhash for the master table by parsing the values out of the main hash +h+
2101
+ def master_hfor(h)
2102
+ out = {}
2103
+ @master_column_map.each{|ca, c| out[c] = h[ca]}
2104
+ out
2105
+ end
2106
+
2107
+ # Return a primary key value for the master table by parsing it out of the main hash +h+.
2108
+ def master_pk(h)
2109
+ x = @master_primary_keys
2110
+ if x.is_a?(Array)
2111
+ unless x == []
2112
+ x = x.map{|ca| h[ca]}
2113
+ x if x.all?
2114
+ end
2115
+ else
2116
+ h[x]
2117
+ end
2118
+ end
2119
+
2120
+ # Return a primary key value for the given table alias by parsing it out of the main hash +h+.
2121
+ def pk(ta, h)
2122
+ x = primary_keys[ta]
2123
+ if x.is_a?(Array)
2124
+ unless x == []
2125
+ x = x.map{|ca| h[ca]}
2126
+ x if x.all?
2127
+ end
2128
+ else
2129
+ h[x]
2130
+ end
2131
+ end
2132
+
1512
2133
  # If the result set is the result of a cartesian product, then it is possible that
1513
2134
  # there are multiple records for each association when there should only be one.
1514
2135
  # In that case, for each object in all associations loaded via +eager_graph+, run
1515
2136
  # uniq! on the association to make sure no duplicate records show up.
1516
2137
  # Note that this can cause legitimate duplicate records to be removed.
1517
- def eager_graph_make_associations_unique(records, dependency_map, alias_map, type_map)
2138
+ def post_process(records, dependency_map)
1518
2139
  records.each do |record|
1519
2140
  dependency_map.each do |ta, deps|
1520
- list = record.send(alias_map[ta])
1521
- list = if type_map[ta]
2141
+ assoc_name = alias_map[ta]
2142
+ list = record.send(assoc_name)
2143
+ rec_list = if type_map[ta]
1522
2144
  list.uniq!
2145
+ if lo = limit_map[ta]
2146
+ limit, offset = lo
2147
+ list.replace(list[offset||0, limit])
2148
+ end
2149
+ list
2150
+ elsif list
2151
+ [list]
1523
2152
  else
1524
- [list] if list
1525
- end
1526
- # Recurse into dependencies
1527
- eager_graph_make_associations_unique(list, deps, alias_map, type_map) if list
1528
- end
1529
- end
1530
- end
1531
-
1532
- # Eagerly load all specified associations
1533
- def eager_load(a, eager_assoc=@opts[:eager])
1534
- return if a.empty?
1535
- # Key is foreign/primary key name symbol
1536
- # Value is hash with keys being foreign/primary key values (generally integers)
1537
- # and values being an array of current model objects with that
1538
- # specific foreign/primary key
1539
- key_hash = {}
1540
- # Reflections for all associations to eager load
1541
- reflections = eager_assoc.keys.collect{|assoc| model.association_reflection(assoc) || (raise Sequel::UndefinedAssociation, "Model: #{self}, Association: #{assoc}")}
1542
-
1543
- # Populate keys to monitor
1544
- reflections.each{|reflection| key_hash[reflection.eager_loader_key] ||= Hash.new{|h,k| h[k] = []}}
1545
-
1546
- # Associate each object with every key being monitored
1547
- a.each do |rec|
1548
- key_hash.each do |key, id_map|
1549
- case key
1550
- when Array
1551
- id_map[key.map{|k| rec[k]}] << rec if key.all?{|k| rec[k]}
1552
- when Symbol
1553
- id_map[rec[key]] << rec if rec[key]
2153
+ []
1554
2154
  end
2155
+ record.send(:run_association_callbacks, reflection_map[ta], :after_load, list) if after_load_map[ta]
2156
+ post_process(rec_list, deps) if !rec_list.empty? && !deps.empty?
1555
2157
  end
1556
2158
  end
1557
-
1558
- reflections.each do |r|
1559
- loader = r[:eager_loader]
1560
- if loader.arity == 1
1561
- loader.call(:key_hash=>key_hash, :rows=>a, :associations=>eager_assoc[r[:name]], :self=>self)
1562
- else
1563
- loader.call(key_hash, a, eager_assoc[r[:name]])
1564
- end
1565
- a.each{|object| object.send(:run_association_callbacks, r, :after_load, object.associations[r[:name]])} unless r[:after_load].empty?
1566
- end
1567
- end
1568
-
1569
- # Build associations from the graph if #eager_graph was used,
1570
- # and/or load other associations if #eager was used.
1571
- def post_load(all_records)
1572
- eager_graph_build_associations(all_records) if @opts[:eager_graph]
1573
- eager_load(all_records) if @opts[:eager]
1574
- super
1575
2159
  end
1576
2160
  end
1577
2161
  end