sequel 5.43.0 → 5.47.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 (38) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +40 -0
  3. data/README.rdoc +1 -2
  4. data/doc/association_basics.rdoc +70 -11
  5. data/doc/migration.rdoc +11 -5
  6. data/doc/release_notes/5.44.0.txt +32 -0
  7. data/doc/release_notes/5.45.0.txt +34 -0
  8. data/doc/release_notes/5.46.0.txt +87 -0
  9. data/doc/release_notes/5.47.0.txt +59 -0
  10. data/doc/sql.rdoc +12 -0
  11. data/doc/testing.rdoc +5 -0
  12. data/doc/virtual_rows.rdoc +1 -1
  13. data/lib/sequel/adapters/odbc.rb +5 -1
  14. data/lib/sequel/adapters/shared/mysql.rb +17 -0
  15. data/lib/sequel/adapters/shared/postgres.rb +0 -12
  16. data/lib/sequel/adapters/shared/sqlite.rb +55 -9
  17. data/lib/sequel/core.rb +11 -0
  18. data/lib/sequel/database/schema_generator.rb +25 -46
  19. data/lib/sequel/database/schema_methods.rb +1 -1
  20. data/lib/sequel/dataset/query.rb +2 -4
  21. data/lib/sequel/dataset/sql.rb +7 -0
  22. data/lib/sequel/extensions/date_arithmetic.rb +29 -15
  23. data/lib/sequel/extensions/pg_enum.rb +1 -1
  24. data/lib/sequel/extensions/pg_loose_count.rb +3 -1
  25. data/lib/sequel/extensions/schema_dumper.rb +11 -0
  26. data/lib/sequel/model/associations.rb +275 -89
  27. data/lib/sequel/plugins/async_thread_pool.rb +1 -1
  28. data/lib/sequel/plugins/auto_validations_constraint_validations_presence_message.rb +68 -0
  29. data/lib/sequel/plugins/column_encryption.rb +20 -3
  30. data/lib/sequel/plugins/concurrent_eager_loading.rb +174 -0
  31. data/lib/sequel/plugins/many_through_many.rb +108 -9
  32. data/lib/sequel/plugins/pg_array_associations.rb +52 -38
  33. data/lib/sequel/plugins/prepared_statements.rb +10 -1
  34. data/lib/sequel/plugins/rcte_tree.rb +27 -19
  35. data/lib/sequel/plugins/timestamps.rb +1 -1
  36. data/lib/sequel/plugins/unused_associations.rb +520 -0
  37. data/lib/sequel/version.rb +1 -1
  38. metadata +14 -3
@@ -42,7 +42,7 @@
42
42
  #
43
43
  # This extension integrates with the pg_array extension. If you plan
44
44
  # to use arrays of enum types, load the pg_array extension before the
45
- # pg_interval extension:
45
+ # pg_enum extension:
46
46
  #
47
47
  # DB.extension :pg_array, :pg_enum
48
48
  #
@@ -12,7 +12,9 @@
12
12
  #
13
13
  # How accurate this count is depends on the number of rows
14
14
  # added/deleted from the table since the last time it was
15
- # analyzed.
15
+ # analyzed. If the table has not been vacuumed or analyzed
16
+ # yet, this can return 0 or -1 depending on the PostgreSQL
17
+ # version in use.
16
18
  #
17
19
  # To load the extension into the database:
18
20
  #
@@ -6,6 +6,17 @@
6
6
  # the current database). The main interface is through
7
7
  # Sequel::Database#dump_schema_migration.
8
8
  #
9
+ # The schema_dumper extension is quite limited in what types of
10
+ # database objects it supports. In general, it only supports
11
+ # dumping tables, columns, primary key and foreign key constraints,
12
+ # and some indexes. It does not support most table options, CHECK
13
+ # constraints, partial indexes, database functions, triggers,
14
+ # security grants/revokes, and a wide variety of other useful
15
+ # database properties. Be aware of the limitations when using the
16
+ # schema_dumper extension. If you are dumping the schema to restore
17
+ # to the same database type, it is recommended to use your database's
18
+ # dump and restore programs instead of the schema_dumper extension.
19
+ #
9
20
  # To load the extension:
10
21
  #
11
22
  # DB.extension :schema_dumper
@@ -263,7 +263,9 @@ module Sequel
263
263
  # yielding each row to the block.
264
264
  def eager_load_results(eo, &block)
265
265
  rows = eo[:rows]
266
- initialize_association_cache(rows) unless eo[:initialize_rows] == false
266
+ unless eo[:initialize_rows] == false
267
+ Sequel.synchronize_with(eo[:mutex]){initialize_association_cache(rows)}
268
+ end
267
269
  if eo[:id_map]
268
270
  ids = eo[:id_map].keys
269
271
  return ids if ids.empty?
@@ -272,7 +274,9 @@ module Sequel
272
274
  cascade = eo[:associations]
273
275
  eager_limit = nil
274
276
 
275
- if eo[:eager_block] || eo[:loader] == false
277
+ if eo[:no_results]
278
+ no_results = true
279
+ elsif eo[:eager_block] || eo[:loader] == false
276
280
  ds = eager_loading_dataset(eo)
277
281
 
278
282
  strategy = ds.opts[:eager_limit_strategy] || strategy
@@ -311,7 +315,8 @@ module Sequel
311
315
  objects = loader.all(ids)
312
316
  end
313
317
 
314
- objects.each(&block)
318
+ Sequel.synchronize_with(eo[:mutex]){objects.each(&block)} unless no_results
319
+
315
320
  if strategy == :ruby
316
321
  apply_ruby_eager_limit_strategy(rows, eager_limit || limit_and_offset)
317
322
  end
@@ -635,9 +640,7 @@ module Sequel
635
640
  # given the hash passed to the eager loader.
636
641
  def eager_loading_dataset(eo=OPTS)
637
642
  ds = eo[:dataset] || associated_eager_dataset
638
- if id_map = eo[:id_map]
639
- ds = ds.where(eager_loading_predicate_condition(id_map.keys))
640
- end
643
+ ds = eager_loading_set_predicate_condition(ds, eo)
641
644
  if associations = eo[:associations]
642
645
  ds = ds.eager(associations)
643
646
  end
@@ -664,6 +667,15 @@ module Sequel
664
667
  self[:model].default_eager_limit_strategy || :ruby
665
668
  end
666
669
 
670
+ # Set the predicate condition for the eager loading dataset based on the id map
671
+ # in the eager loading options.
672
+ def eager_loading_set_predicate_condition(ds, eo)
673
+ if id_map = eo[:id_map]
674
+ ds = ds.where(eager_loading_predicate_condition(id_map.keys))
675
+ end
676
+ ds
677
+ end
678
+
667
679
  # The predicate condition to use for the eager_loader.
668
680
  def eager_loading_predicate_condition(keys)
669
681
  {predicate_key=>keys}
@@ -1315,7 +1327,7 @@ module Sequel
1315
1327
 
1316
1328
  # many_to_many associations need to select a key in an associated table to eagerly load
1317
1329
  def eager_loading_use_associated_key?
1318
- true
1330
+ !separate_query_per_table?
1319
1331
  end
1320
1332
 
1321
1333
  # The source of the join table. This is the join table itself, unless it
@@ -1372,10 +1384,30 @@ module Sequel
1372
1384
  cached_fetch(:select){default_select}
1373
1385
  end
1374
1386
 
1387
+ # Whether a separate query should be used for the join table.
1388
+ def separate_query_per_table?
1389
+ self[:join_table_db]
1390
+ end
1391
+
1375
1392
  private
1376
1393
 
1394
+ # Join to the the join table, unless using a separate query per table.
1377
1395
  def _associated_dataset
1378
- super.inner_join(self[:join_table], self[:right_keys].zip(right_primary_keys), :qualify=>:deep)
1396
+ if separate_query_per_table?
1397
+ super
1398
+ else
1399
+ super.inner_join(self[:join_table], self[:right_keys].zip(right_primary_keys), :qualify=>:deep)
1400
+ end
1401
+ end
1402
+
1403
+ # Use the right_keys from the eager loading options if
1404
+ # using a separate query per table.
1405
+ def eager_loading_set_predicate_condition(ds, eo)
1406
+ if separate_query_per_table?
1407
+ ds.where(right_primary_key=>eo[:right_keys])
1408
+ else
1409
+ super
1410
+ end
1379
1411
  end
1380
1412
 
1381
1413
  # The default selection for associations that require joins. These do not use the default
@@ -1592,6 +1624,7 @@ module Sequel
1592
1624
  # === Multiple Types
1593
1625
  # :adder :: Proc used to define the private _add_* method for doing the database work
1594
1626
  # to associate the given object to the current object (*_to_many assocations).
1627
+ # Set to nil to not define a add_* method for the association.
1595
1628
  # :after_add :: Symbol, Proc, or array of both/either specifying a callback to call
1596
1629
  # after a new item is added to the association.
1597
1630
  # :after_load :: Symbol, Proc, or array of both/either specifying a callback to call
@@ -1602,6 +1635,8 @@ module Sequel
1602
1635
  # after an item is set using the association setter method.
1603
1636
  # :allow_eager :: If set to false, you cannot load the association eagerly
1604
1637
  # via eager or eager_graph
1638
+ # :allow_eager_graph :: If set to false, you cannot load the association eagerly via eager_graph.
1639
+ # :allow_filtering_by :: If set to false, you cannot use the association when filtering
1605
1640
  # :before_add :: Symbol, Proc, or array of both/either specifying a callback to call
1606
1641
  # before a new item is added to the association.
1607
1642
  # :before_remove :: Symbol, Proc, or array of both/either specifying a callback to call
@@ -1620,6 +1655,7 @@ module Sequel
1620
1655
  # the class. <tt>class: 'Foo', class_namespace: 'Bar'</tt> looks for <tt>::Bar::Foo</tt>.)
1621
1656
  # :clearer :: Proc used to define the private _remove_all_* method for doing the database work
1622
1657
  # to remove all objects associated to the current object (*_to_many assocations).
1658
+ # Set to nil to not define a remove_all_* method for the association.
1623
1659
  # :clone :: Merge the current options and block into the options and block used in defining
1624
1660
  # the given association. Can be used to DRY up a bunch of similar associations that
1625
1661
  # all share the same options such as :class and :key, while changing the order and block used.
@@ -1674,7 +1710,7 @@ module Sequel
1674
1710
  # :graph_only_conditions :: The conditions to use on the SQL join when eagerly loading
1675
1711
  # the association via +eager_graph+, instead of the default conditions specified by the
1676
1712
  # foreign/primary keys. This option causes the :graph_conditions option to be ignored.
1677
- # :graph_order :: Over the order to use when using eager_graph, instead of the default order. This should be used
1713
+ # :graph_order :: the order to use when using eager_graph, instead of the default order. This should be used
1678
1714
  # in the case where :order contains an identifier qualified by the table's name, which may not match
1679
1715
  # the alias used when eager graphing. By setting this to the unqualified identifier, it will be
1680
1716
  # automatically qualified when using eager_graph.
@@ -1686,6 +1722,10 @@ module Sequel
1686
1722
  # limit (first element) and an offset (second element).
1687
1723
  # :methods_module :: The module that methods the association creates will be placed into. Defaults
1688
1724
  # to the module containing the model's columns.
1725
+ # :no_association_method :: Do not add a method for the association. This can save memory if the association
1726
+ # method is never used.
1727
+ # :no_dataset_method :: Do not add a method for the association dataset. This can save memory if the dataset
1728
+ # method is never used.
1689
1729
  # :order :: the column(s) by which to order the association dataset. Can be a
1690
1730
  # singular column symbol or an array of column symbols.
1691
1731
  # :order_eager_graph :: Whether to add the association's order to the graphed dataset's order when graphing
@@ -1698,6 +1738,7 @@ module Sequel
1698
1738
  # the current association's key(s). Set to nil to not use a reciprocal.
1699
1739
  # :remover :: Proc used to define the private _remove_* method for doing the database work
1700
1740
  # to remove the association between the given object and the current object (*_to_many assocations).
1741
+ # Set to nil to not define a remove_* method for the association.
1701
1742
  # :select :: the columns to select. Defaults to the associated class's table_name.* in an association
1702
1743
  # that uses joins, which means it doesn't include the attributes from the
1703
1744
  # join table. If you want to include the join table attributes, you can
@@ -1706,6 +1747,7 @@ module Sequel
1706
1747
  # the same name in both the join table and the associated table.
1707
1748
  # :setter :: Proc used to define the private _*= method for doing the work to setup the assocation
1708
1749
  # between the given object and the current object (*_to_one associations).
1750
+ # Set to nil to not define a setter method for the association.
1709
1751
  # :subqueries_per_union :: The number of subqueries to use in each UNION query, for eager
1710
1752
  # loading limited associations using the default :union strategy.
1711
1753
  # :validate :: Set to false to not validate when implicitly saving any associated object.
@@ -1762,6 +1804,9 @@ module Sequel
1762
1804
  # underscored, sorted, and joined with '_'.
1763
1805
  # :join_table_block :: proc that can be used to modify the dataset used in the add/remove/remove_all
1764
1806
  # methods. Should accept a dataset argument and return a modified dataset if present.
1807
+ # :join_table_db :: When retrieving records when using lazy loading or eager loading via +eager+, instead of
1808
+ # a join between to the join table and the associated table, use a separate query for the
1809
+ # join table using the given Database object.
1765
1810
  # :left_key :: foreign key in join table that points to current model's
1766
1811
  # primary key, as a symbol. Defaults to :"#{self.name.underscore}_id".
1767
1812
  # Can use an array of symbols for a composite key association.
@@ -1838,8 +1883,7 @@ module Sequel
1838
1883
  # Remove :class entry if it exists and is nil, to work with cached_fetch
1839
1884
  opts.delete(:class) unless opts[:class]
1840
1885
 
1841
- send(:"def_#{type}", opts)
1842
- def_association_instance_methods(opts)
1886
+ def_association(opts)
1843
1887
 
1844
1888
  orig_opts.delete(:clone)
1845
1889
  opts[:orig_class] = orig_opts[:class] || orig_opts[:class_name]
@@ -1953,6 +1997,13 @@ module Sequel
1953
1997
  association_module(opts).send(:private, name)
1954
1998
  end
1955
1999
 
2000
+ # Delegate to the type-specific association method to setup the
2001
+ # association, and define the association instance methods.
2002
+ def def_association(opts)
2003
+ send(:"def_#{opts[:type]}", opts)
2004
+ def_association_instance_methods(opts)
2005
+ end
2006
+
1956
2007
  # Adds the association method to the association methods module.
1957
2008
  def def_association_method(opts)
1958
2009
  association_module_def(opts.association_method, opts) do |dynamic_opts=OPTS, &block|
@@ -1978,13 +2029,13 @@ module Sequel
1978
2029
  opts[:setter_method] = :"#{opts[:name]}="
1979
2030
  end
1980
2031
 
1981
- association_module_def(opts.dataset_method, opts){_dataset(opts)}
2032
+ association_module_def(opts.dataset_method, opts){_dataset(opts)} unless opts[:no_dataset_method]
1982
2033
  if opts[:block]
1983
2034
  opts[:block_method] = Plugins.def_sequel_method(association_module(opts), "#{opts[:name]}_block", 1, &opts[:block])
1984
2035
  end
1985
2036
  opts[:dataset_opt_arity] = opts[:dataset].arity == 0 ? 0 : 1
1986
2037
  opts[:dataset_opt_method] = Plugins.def_sequel_method(association_module(opts), "#{opts[:name]}_dataset_opt", opts[:dataset_opt_arity], &opts[:dataset])
1987
- def_association_method(opts)
2038
+ def_association_method(opts) unless opts[:no_association_method]
1988
2039
 
1989
2040
  return if opts[:read_only]
1990
2041
 
@@ -2028,7 +2079,7 @@ module Sequel
2028
2079
  raise(Error, "mismatched number of right keys: #{rcks.inspect} vs #{rcpks.inspect}") unless rcks.length == rcpks.length
2029
2080
  end
2030
2081
  opts[:uses_left_composite_keys] = lcks.length > 1
2031
- opts[:uses_right_composite_keys] = rcks.length > 1
2082
+ uses_rcks = opts[:uses_right_composite_keys] = rcks.length > 1
2032
2083
  opts[:cartesian_product_number] ||= one_through_one ? 0 : 1
2033
2084
  join_table = (opts[:join_table] ||= opts.default_join_table)
2034
2085
  opts[:left_key_alias] ||= opts.default_associated_key_alias
@@ -2037,8 +2088,75 @@ module Sequel
2037
2088
  opts[:after_load] ||= []
2038
2089
  opts[:after_load].unshift(:array_uniq!)
2039
2090
  end
2040
- opts[:dataset] ||= opts.association_dataset_proc
2041
- opts[:eager_loader] ||= opts.method(:default_eager_loader)
2091
+ if join_table_db = opts[:join_table_db]
2092
+ opts[:use_placeholder_loader] = false
2093
+ opts[:allow_eager_graph] = false
2094
+ opts[:allow_filtering_by] = false
2095
+ opts[:eager_limit_strategy] = nil
2096
+ join_table_ds = join_table_db.from(join_table)
2097
+ opts[:dataset] ||= proc do |r|
2098
+ vals = join_table_ds.where(lcks.zip(lcpks.map{|k| get_column_value(k)})).select_map(right)
2099
+ ds = r.associated_dataset.where(opts.right_primary_key => vals)
2100
+ if uses_rcks
2101
+ vals.delete_if{|v| v.any?(&:nil?)}
2102
+ else
2103
+ vals.delete(nil)
2104
+ end
2105
+ ds = ds.clone(:no_results=>true) if vals.empty?
2106
+ ds
2107
+ end
2108
+ opts[:eager_loader] ||= proc do |eo|
2109
+ h = eo[:id_map]
2110
+ assign_singular = opts.assign_singular?
2111
+ rpk = opts.right_primary_key
2112
+ name = opts[:name]
2113
+
2114
+ join_map = join_table_ds.where(left=>h.keys).select_hash_groups(right, left)
2115
+
2116
+ if uses_rcks
2117
+ join_map.delete_if{|v,| v.any?(&:nil?)}
2118
+ else
2119
+ join_map.delete(nil)
2120
+ end
2121
+
2122
+ eo = Hash[eo]
2123
+
2124
+ if join_map.empty?
2125
+ eo[:no_results] = true
2126
+ else
2127
+ join_map.each_value do |vs|
2128
+ vs.replace(vs.flat_map{|v| h[v]})
2129
+ vs.uniq!
2130
+ end
2131
+
2132
+ eo[:loader] = false
2133
+ eo[:right_keys] = join_map.keys
2134
+ end
2135
+
2136
+ opts[:model].eager_load_results(opts, eo) do |assoc_record|
2137
+ rpkv = if uses_rcks
2138
+ assoc_record.values.values_at(*rpk)
2139
+ else
2140
+ assoc_record.values[rpk]
2141
+ end
2142
+
2143
+ objects = join_map[rpkv]
2144
+
2145
+ if assign_singular
2146
+ objects.each do |object|
2147
+ object.associations[name] ||= assoc_record
2148
+ end
2149
+ else
2150
+ objects.each do |object|
2151
+ object.associations[name].push(assoc_record)
2152
+ end
2153
+ end
2154
+ end
2155
+ end
2156
+ else
2157
+ opts[:dataset] ||= opts.association_dataset_proc
2158
+ opts[:eager_loader] ||= opts.method(:default_eager_loader)
2159
+ end
2042
2160
 
2043
2161
  join_type = opts[:graph_join_type]
2044
2162
  select = opts[:graph_select]
@@ -2072,50 +2190,60 @@ module Sequel
2072
2190
  return if opts[:read_only]
2073
2191
 
2074
2192
  if one_through_one
2075
- opts[:setter] ||= proc do |o|
2076
- h = {}
2077
- lh = lcks.zip(lcpks.map{|k| get_column_value(k)})
2078
- jtds = _join_table_dataset(opts).where(lh)
2193
+ unless opts.has_key?(:setter)
2194
+ opts[:setter] = proc do |o|
2195
+ h = {}
2196
+ lh = lcks.zip(lcpks.map{|k| get_column_value(k)})
2197
+ jtds = _join_table_dataset(opts).where(lh)
2079
2198
 
2080
- checked_transaction do
2081
- current = jtds.first
2199
+ checked_transaction do
2200
+ current = jtds.first
2082
2201
 
2083
- if o
2084
- new_values = []
2085
- rcks.zip(opts.right_primary_key_methods).each{|k, pk| new_values << (h[k] = o.get_column_value(pk))}
2086
- end
2087
-
2088
- if current
2089
- current_values = rcks.map{|k| current[k]}
2090
- jtds = jtds.where(rcks.zip(current_values))
2091
2202
  if o
2092
- if current_values != new_values
2093
- jtds.update(h)
2203
+ new_values = []
2204
+ rcks.zip(opts.right_primary_key_methods).each{|k, pk| new_values << (h[k] = o.get_column_value(pk))}
2205
+ end
2206
+
2207
+ if current
2208
+ current_values = rcks.map{|k| current[k]}
2209
+ jtds = jtds.where(rcks.zip(current_values))
2210
+ if o
2211
+ if current_values != new_values
2212
+ jtds.update(h)
2213
+ end
2214
+ else
2215
+ jtds.delete
2094
2216
  end
2095
- else
2096
- jtds.delete
2217
+ elsif o
2218
+ lh.each{|k,v| h[k] = v}
2219
+ jtds.insert(h)
2097
2220
  end
2098
- elsif o
2099
- lh.each{|k,v| h[k] = v}
2100
- jtds.insert(h)
2101
2221
  end
2102
2222
  end
2103
2223
  end
2104
- opts[:_setter] = proc{|o| set_one_through_one_associated_object(opts, o)}
2224
+ if opts.fetch(:setter, true)
2225
+ opts[:_setter] = proc{|o| set_one_through_one_associated_object(opts, o)}
2226
+ end
2105
2227
  else
2106
- opts[:adder] ||= proc do |o|
2107
- h = {}
2108
- lcks.zip(lcpks).each{|k, pk| h[k] = get_column_value(pk)}
2109
- rcks.zip(opts.right_primary_key_methods).each{|k, pk| h[k] = o.get_column_value(pk)}
2110
- _join_table_dataset(opts).insert(h)
2228
+ unless opts.has_key?(:adder)
2229
+ opts[:adder] = proc do |o|
2230
+ h = {}
2231
+ lcks.zip(lcpks).each{|k, pk| h[k] = get_column_value(pk)}
2232
+ rcks.zip(opts.right_primary_key_methods).each{|k, pk| h[k] = o.get_column_value(pk)}
2233
+ _join_table_dataset(opts).insert(h)
2234
+ end
2111
2235
  end
2112
2236
 
2113
- opts[:remover] ||= proc do |o|
2114
- _join_table_dataset(opts).where(lcks.zip(lcpks.map{|k| get_column_value(k)}) + rcks.zip(opts.right_primary_key_methods.map{|k| o.get_column_value(k)})).delete
2237
+ unless opts.has_key?(:remover)
2238
+ opts[:remover] = proc do |o|
2239
+ _join_table_dataset(opts).where(lcks.zip(lcpks.map{|k| get_column_value(k)}) + rcks.zip(opts.right_primary_key_methods.map{|k| o.get_column_value(k)})).delete
2240
+ end
2115
2241
  end
2116
2242
 
2117
- opts[:clearer] ||= proc do
2118
- _join_table_dataset(opts).where(lcks.zip(lcpks.map{|k| get_column_value(k)})).delete
2243
+ unless opts.has_key?(:clearer)
2244
+ opts[:clearer] = proc do
2245
+ _join_table_dataset(opts).where(lcks.zip(lcpks.map{|k| get_column_value(k)})).delete
2246
+ end
2119
2247
  end
2120
2248
  end
2121
2249
  end
@@ -2172,8 +2300,12 @@ module Sequel
2172
2300
 
2173
2301
  return if opts[:read_only]
2174
2302
 
2175
- opts[:setter] ||= proc{|o| cks.zip(opts.primary_key_methods).each{|k, pk| set_column_value(:"#{k}=", (o.get_column_value(pk) if o))}}
2176
- opts[:_setter] = proc{|o| set_associated_object(opts, o)}
2303
+ unless opts.has_key?(:setter)
2304
+ opts[:setter] = proc{|o| cks.zip(opts.primary_key_methods).each{|k, pk| set_column_value(:"#{k}=", (o.get_column_value(pk) if o))}}
2305
+ end
2306
+ if opts.fetch(:setter, true)
2307
+ opts[:_setter] = proc{|o| set_associated_object(opts, o)}
2308
+ end
2177
2309
  end
2178
2310
 
2179
2311
  # Configures one_to_many and one_to_one association reflections and adds the related association methods
@@ -2240,49 +2372,59 @@ module Sequel
2240
2372
  cks.each{|k| ck_nil_hash[k] = nil}
2241
2373
 
2242
2374
  if one_to_one
2243
- opts[:setter] ||= proc do |o|
2244
- up_ds = _apply_association_options(opts, opts.associated_dataset.where(cks.zip(cpks.map{|k| get_column_value(k)})))
2375
+ unless opts.has_key?(:setter)
2376
+ opts[:setter] = proc do |o|
2377
+ up_ds = _apply_association_options(opts, opts.associated_dataset.where(cks.zip(cpks.map{|k| get_column_value(k)})))
2245
2378
 
2246
- if (froms = up_ds.opts[:from]) && (from = froms[0]) && (from.is_a?(Sequel::Dataset) || (from.is_a?(Sequel::SQL::AliasedExpression) && from.expression.is_a?(Sequel::Dataset)))
2247
- if old = up_ds.first
2248
- cks.each{|k| old.set_column_value(:"#{k}=", nil)}
2379
+ if (froms = up_ds.opts[:from]) && (from = froms[0]) && (from.is_a?(Sequel::Dataset) || (from.is_a?(Sequel::SQL::AliasedExpression) && from.expression.is_a?(Sequel::Dataset)))
2380
+ if old = up_ds.first
2381
+ cks.each{|k| old.set_column_value(:"#{k}=", nil)}
2382
+ end
2383
+ save_old = true
2249
2384
  end
2250
- save_old = true
2251
- end
2252
2385
 
2253
- if o
2254
- if !o.new? && !save_old
2255
- up_ds = up_ds.exclude(o.pk_hash)
2386
+ if o
2387
+ if !o.new? && !save_old
2388
+ up_ds = up_ds.exclude(o.pk_hash)
2389
+ end
2390
+ cks.zip(cpks).each{|k, pk| o.set_column_value(:"#{k}=", get_column_value(pk))}
2256
2391
  end
2257
- cks.zip(cpks).each{|k, pk| o.set_column_value(:"#{k}=", get_column_value(pk))}
2258
- end
2259
2392
 
2260
- checked_transaction do
2261
- if save_old
2262
- old.save(save_opts) || raise(Sequel::Error, "invalid previously associated object, cannot save") if old
2263
- else
2264
- up_ds.skip_limit_check.update(ck_nil_hash)
2265
- end
2393
+ checked_transaction do
2394
+ if save_old
2395
+ old.save(save_opts) || raise(Sequel::Error, "invalid previously associated object, cannot save") if old
2396
+ else
2397
+ up_ds.skip_limit_check.update(ck_nil_hash)
2398
+ end
2266
2399
 
2267
- o.save(save_opts) || raise(Sequel::Error, "invalid associated object, cannot save") if o
2400
+ o.save(save_opts) || raise(Sequel::Error, "invalid associated object, cannot save") if o
2401
+ end
2268
2402
  end
2269
2403
  end
2270
- opts[:_setter] = proc{|o| set_one_to_one_associated_object(opts, o)}
2404
+ if opts.fetch(:setter, true)
2405
+ opts[:_setter] = proc{|o| set_one_to_one_associated_object(opts, o)}
2406
+ end
2271
2407
  else
2272
2408
  save_opts[:raise_on_failure] = opts[:raise_on_save_failure] != false
2273
2409
 
2274
- opts[:adder] ||= proc do |o|
2275
- cks.zip(cpks).each{|k, pk| o.set_column_value(:"#{k}=", get_column_value(pk))}
2276
- o.save(save_opts)
2410
+ unless opts.has_key?(:adder)
2411
+ opts[:adder] = proc do |o|
2412
+ cks.zip(cpks).each{|k, pk| o.set_column_value(:"#{k}=", get_column_value(pk))}
2413
+ o.save(save_opts)
2414
+ end
2277
2415
  end
2278
2416
 
2279
- opts[:remover] ||= proc do |o|
2280
- cks.each{|k| o.set_column_value(:"#{k}=", nil)}
2281
- o.save(save_opts)
2417
+ unless opts.has_key?(:remover)
2418
+ opts[:remover] = proc do |o|
2419
+ cks.each{|k| o.set_column_value(:"#{k}=", nil)}
2420
+ o.save(save_opts)
2421
+ end
2282
2422
  end
2283
2423
 
2284
- opts[:clearer] ||= proc do
2285
- _apply_association_options(opts, opts.associated_dataset.where(cks.zip(cpks.map{|k| get_column_value(k)}))).update(ck_nil_hash)
2424
+ unless opts.has_key?(:clearer)
2425
+ opts[:clearer] = proc do
2426
+ _apply_association_options(opts, opts.associated_dataset.where(cks.zip(cpks.map{|k| get_column_value(k)}))).update(ck_nil_hash)
2427
+ end
2286
2428
  end
2287
2429
  end
2288
2430
  end
@@ -2376,7 +2518,7 @@ module Sequel
2376
2518
 
2377
2519
  # Dataset for the join table of the given many to many association reflection
2378
2520
  def _join_table_dataset(opts)
2379
- ds = model.db.from(opts.join_table_source)
2521
+ ds = (opts[:join_table_db] || model.db).from(opts.join_table_source)
2380
2522
  opts[:join_table_block] ? opts[:join_table_block].call(ds) : ds
2381
2523
  end
2382
2524
 
@@ -2397,7 +2539,12 @@ module Sequel
2397
2539
  if loader = _associated_object_loader(opts, dynamic_opts)
2398
2540
  loader.all(*opts.predicate_key_values(self))
2399
2541
  else
2400
- _associated_dataset(opts, dynamic_opts).all
2542
+ ds = _associated_dataset(opts, dynamic_opts)
2543
+ if ds.opts[:no_results]
2544
+ []
2545
+ else
2546
+ ds.all
2547
+ end
2401
2548
  end
2402
2549
  end
2403
2550
 
@@ -2858,6 +3005,8 @@ module Sequel
2858
3005
  (multiple = ((op == :IN || op == :'NOT IN') && ((is_ds = r.is_a?(Sequel::Dataset)) || (r.respond_to?(:all?) && r.all?{|x| x.is_a?(Sequel::Model)})))))
2859
3006
  l = args[0]
2860
3007
  if ar = model.association_reflections[l]
3008
+ raise Error, "filtering by associations is not allowed for #{ar.inspect}" if ar[:allow_filtering_by] == false
3009
+
2861
3010
  if multiple
2862
3011
  klass = ar.associated_class
2863
3012
  if is_ds
@@ -3008,6 +3157,8 @@ module Sequel
3008
3157
  # You can specify an custom alias and/or join type on a per-association basis by providing an
3009
3158
  # Sequel::SQL::AliasedExpression object instead of an a Symbol for the association name.
3010
3159
  #
3160
+ # You cannot mix calls to +eager_graph+ and +graph+ on the same dataset.
3161
+ #
3011
3162
  # Examples:
3012
3163
  #
3013
3164
  # # For each album, eager_graph load the artist
@@ -3351,7 +3502,7 @@ module Sequel
3351
3502
  # Allow associations that are eagerly graphed to be specified as an SQL::AliasedExpression, for
3352
3503
  # per-call determining of the alias base.
3353
3504
  def eager_graph_check_association(model, association)
3354
- if association.is_a?(SQL::AliasedExpression)
3505
+ reflection = if association.is_a?(SQL::AliasedExpression)
3355
3506
  expr = association.expression
3356
3507
  if expr.is_a?(SQL::Identifier)
3357
3508
  expr = expr.value
@@ -3360,10 +3511,17 @@ module Sequel
3360
3511
  end
3361
3512
  end
3362
3513
 
3363
- SQL::AliasedExpression.new(check_association(model, expr), association.alias || expr, association.columns)
3514
+ check_reflection = check_association(model, expr)
3515
+ SQL::AliasedExpression.new(check_reflection, association.alias || expr, association.columns)
3364
3516
  else
3365
- check_association(model, association)
3517
+ check_reflection = check_association(model, association)
3366
3518
  end
3519
+
3520
+ if check_reflection && check_reflection[:allow_eager_graph] == false
3521
+ raise Error, "eager_graph not allowed for #{reflection.inspect}"
3522
+ end
3523
+
3524
+ reflection
3367
3525
  end
3368
3526
 
3369
3527
  # The EagerGraphLoader instance used for converting eager_graph results.
@@ -3374,15 +3532,30 @@ module Sequel
3374
3532
  egl.dup
3375
3533
  end
3376
3534
 
3377
- # Eagerly load all specified associations
3535
+ # Eagerly load all specified associations.
3378
3536
  def eager_load(a, eager_assoc=@opts[:eager])
3379
3537
  return if a.empty?
3538
+
3539
+ # Reflections for all associations to eager load
3540
+ reflections = eager_assoc.keys.map{|assoc| model.association_reflection(assoc) || (raise Sequel::UndefinedAssociation, "Model: #{self}, Association: #{assoc}")}
3541
+
3542
+ perform_eager_loads(prepare_eager_load(a, reflections, eager_assoc))
3543
+
3544
+ reflections.each do |r|
3545
+ a.each{|object| object.send(:run_association_callbacks, r, :after_load, object.associations[r[:name]])} if r[:after_load]
3546
+ end
3547
+
3548
+ nil
3549
+ end
3550
+
3551
+ # Prepare a hash loaders and eager options which will be used to implement the eager loading.
3552
+ def prepare_eager_load(a, reflections, eager_assoc)
3553
+ eager_load_data = {}
3554
+
3380
3555
  # Key is foreign/primary key name symbol.
3381
3556
  # Value is hash with keys being foreign/primary key values (generally integers)
3382
3557
  # and values being an array of current model objects with that specific foreign/primary key
3383
3558
  key_hash = {}
3384
- # Reflections for all associations to eager load
3385
- reflections = eager_assoc.keys.map{|assoc| model.association_reflection(assoc) || (raise Sequel::UndefinedAssociation, "Model: #{self}, Association: #{assoc}")}
3386
3559
 
3387
3560
  # Populate the key_hash entry for each association being eagerly loaded
3388
3561
  reflections.each do |r|
@@ -3413,7 +3586,6 @@ module Sequel
3413
3586
  id_map = nil
3414
3587
  end
3415
3588
 
3416
- loader = r[:eager_loader]
3417
3589
  associations = eager_assoc[r[:name]]
3418
3590
  if associations.respond_to?(:call)
3419
3591
  eager_block = associations
@@ -3421,9 +3593,23 @@ module Sequel
3421
3593
  elsif associations.is_a?(Hash) && associations.length == 1 && (pr_assoc = associations.to_a.first) && pr_assoc.first.respond_to?(:call)
3422
3594
  eager_block, associations = pr_assoc
3423
3595
  end
3424
- loader.call(:key_hash=>key_hash, :rows=>a, :associations=>associations, :self=>self, :eager_block=>eager_block, :id_map=>id_map)
3425
- a.each{|object| object.send(:run_association_callbacks, r, :after_load, object.associations[r[:name]])} if r[:after_load]
3426
- end
3596
+
3597
+ eager_load_data[r[:eager_loader]] = {:key_hash=>key_hash, :rows=>a, :associations=>associations, :self=>self, :eager_block=>eager_block, :id_map=>id_map}
3598
+ end
3599
+
3600
+ eager_load_data
3601
+ end
3602
+
3603
+ # Using the hash of loaders and eager options, perform the eager loading.
3604
+ def perform_eager_loads(eager_load_data)
3605
+ eager_load_data.map do |loader, eo|
3606
+ perform_eager_load(loader, eo)
3607
+ end
3608
+ end
3609
+
3610
+ # Perform eager loading for a single association using the loader and eager options.
3611
+ def perform_eager_load(loader, eo)
3612
+ loader.call(eo)
3427
3613
  end
3428
3614
 
3429
3615
  # Return a subquery expression for filering by a many_to_many association