sequel 3.36.1 → 3.37.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 (108) hide show
  1. data/CHANGELOG +84 -0
  2. data/Rakefile +13 -0
  3. data/bin/sequel +12 -16
  4. data/doc/advanced_associations.rdoc +36 -67
  5. data/doc/association_basics.rdoc +11 -16
  6. data/doc/release_notes/3.37.0.txt +338 -0
  7. data/doc/schema_modification.rdoc +4 -0
  8. data/lib/sequel/adapters/jdbc/h2.rb +1 -1
  9. data/lib/sequel/adapters/jdbc/postgresql.rb +26 -8
  10. data/lib/sequel/adapters/mysql2.rb +4 -3
  11. data/lib/sequel/adapters/odbc/mssql.rb +2 -2
  12. data/lib/sequel/adapters/postgres.rb +4 -60
  13. data/lib/sequel/adapters/shared/mssql.rb +2 -1
  14. data/lib/sequel/adapters/shared/mysql.rb +0 -5
  15. data/lib/sequel/adapters/shared/postgres.rb +68 -2
  16. data/lib/sequel/adapters/shared/sqlite.rb +17 -1
  17. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +12 -1
  18. data/lib/sequel/adapters/utils/pg_types.rb +76 -0
  19. data/lib/sequel/core.rb +13 -0
  20. data/lib/sequel/database/misc.rb +41 -1
  21. data/lib/sequel/database/schema_generator.rb +23 -10
  22. data/lib/sequel/database/schema_methods.rb +26 -4
  23. data/lib/sequel/dataset/graph.rb +2 -1
  24. data/lib/sequel/dataset/query.rb +62 -2
  25. data/lib/sequel/extensions/_pretty_table.rb +7 -3
  26. data/lib/sequel/extensions/arbitrary_servers.rb +5 -4
  27. data/lib/sequel/extensions/blank.rb +4 -0
  28. data/lib/sequel/extensions/columns_introspection.rb +13 -2
  29. data/lib/sequel/extensions/core_extensions.rb +6 -0
  30. data/lib/sequel/extensions/eval_inspect.rb +158 -0
  31. data/lib/sequel/extensions/inflector.rb +4 -0
  32. data/lib/sequel/extensions/looser_typecasting.rb +5 -4
  33. data/lib/sequel/extensions/migration.rb +4 -1
  34. data/lib/sequel/extensions/named_timezones.rb +4 -0
  35. data/lib/sequel/extensions/null_dataset.rb +4 -0
  36. data/lib/sequel/extensions/pagination.rb +4 -0
  37. data/lib/sequel/extensions/pg_array.rb +219 -168
  38. data/lib/sequel/extensions/pg_array_ops.rb +7 -2
  39. data/lib/sequel/extensions/pg_auto_parameterize.rb +10 -4
  40. data/lib/sequel/extensions/pg_hstore.rb +3 -1
  41. data/lib/sequel/extensions/pg_hstore_ops.rb +7 -2
  42. data/lib/sequel/extensions/pg_inet.rb +28 -3
  43. data/lib/sequel/extensions/pg_interval.rb +192 -0
  44. data/lib/sequel/extensions/pg_json.rb +21 -9
  45. data/lib/sequel/extensions/pg_range.rb +487 -0
  46. data/lib/sequel/extensions/pg_range_ops.rb +122 -0
  47. data/lib/sequel/extensions/pg_statement_cache.rb +3 -2
  48. data/lib/sequel/extensions/pretty_table.rb +12 -1
  49. data/lib/sequel/extensions/query.rb +4 -0
  50. data/lib/sequel/extensions/query_literals.rb +6 -6
  51. data/lib/sequel/extensions/schema_dumper.rb +39 -38
  52. data/lib/sequel/extensions/select_remove.rb +4 -0
  53. data/lib/sequel/extensions/server_block.rb +3 -2
  54. data/lib/sequel/extensions/split_array_nil.rb +65 -0
  55. data/lib/sequel/extensions/sql_expr.rb +4 -0
  56. data/lib/sequel/extensions/string_date_time.rb +4 -0
  57. data/lib/sequel/extensions/thread_local_timezones.rb +9 -3
  58. data/lib/sequel/extensions/to_dot.rb +4 -0
  59. data/lib/sequel/model/associations.rb +150 -91
  60. data/lib/sequel/plugins/identity_map.rb +2 -2
  61. data/lib/sequel/plugins/list.rb +1 -0
  62. data/lib/sequel/plugins/many_through_many.rb +33 -32
  63. data/lib/sequel/plugins/nested_attributes.rb +11 -3
  64. data/lib/sequel/plugins/rcte_tree.rb +2 -2
  65. data/lib/sequel/plugins/schema.rb +1 -1
  66. data/lib/sequel/sql.rb +14 -14
  67. data/lib/sequel/version.rb +2 -2
  68. data/spec/adapters/mysql_spec.rb +25 -0
  69. data/spec/adapters/postgres_spec.rb +572 -28
  70. data/spec/adapters/sqlite_spec.rb +16 -1
  71. data/spec/core/database_spec.rb +61 -2
  72. data/spec/core/dataset_spec.rb +92 -0
  73. data/spec/core/expression_filters_spec.rb +12 -0
  74. data/spec/extensions/arbitrary_servers_spec.rb +1 -1
  75. data/spec/extensions/boolean_readers_spec.rb +25 -25
  76. data/spec/extensions/eval_inspect_spec.rb +58 -0
  77. data/spec/extensions/json_serializer_spec.rb +0 -6
  78. data/spec/extensions/list_spec.rb +1 -1
  79. data/spec/extensions/looser_typecasting_spec.rb +7 -7
  80. data/spec/extensions/many_through_many_spec.rb +81 -0
  81. data/spec/extensions/nested_attributes_spec.rb +21 -4
  82. data/spec/extensions/pg_array_ops_spec.rb +1 -11
  83. data/spec/extensions/pg_array_spec.rb +181 -90
  84. data/spec/extensions/pg_auto_parameterize_spec.rb +3 -3
  85. data/spec/extensions/pg_hstore_spec.rb +1 -3
  86. data/spec/extensions/pg_inet_spec.rb +6 -1
  87. data/spec/extensions/pg_interval_spec.rb +73 -0
  88. data/spec/extensions/pg_json_spec.rb +5 -9
  89. data/spec/extensions/pg_range_ops_spec.rb +49 -0
  90. data/spec/extensions/pg_range_spec.rb +372 -0
  91. data/spec/extensions/pg_statement_cache_spec.rb +1 -2
  92. data/spec/extensions/query_literals_spec.rb +1 -2
  93. data/spec/extensions/schema_dumper_spec.rb +48 -89
  94. data/spec/extensions/serialization_spec.rb +1 -5
  95. data/spec/extensions/server_block_spec.rb +2 -2
  96. data/spec/extensions/spec_helper.rb +12 -2
  97. data/spec/extensions/split_array_nil_spec.rb +24 -0
  98. data/spec/integration/associations_test.rb +4 -4
  99. data/spec/integration/database_test.rb +2 -2
  100. data/spec/integration/dataset_test.rb +4 -4
  101. data/spec/integration/eager_loader_test.rb +6 -6
  102. data/spec/integration/plugin_test.rb +2 -2
  103. data/spec/integration/spec_helper.rb +2 -2
  104. data/spec/model/association_reflection_spec.rb +5 -0
  105. data/spec/model/associations_spec.rb +156 -49
  106. data/spec/model/eager_loading_spec.rb +137 -2
  107. data/spec/model/model_spec.rb +10 -10
  108. metadata +15 -2
@@ -7,6 +7,10 @@
7
7
  # true.sql_expr | :a # TRUE OR a
8
8
  # ~nil.sql_expr # NOT NULL
9
9
  # "a".sql_expr + "b" # 'a' || 'b'
10
+ #
11
+ # To load the extension:
12
+ #
13
+ # Sequel.extension :sql_expr
10
14
 
11
15
  class Object
12
16
  # Return the object wrapper in an appropriate Sequel expression object.
@@ -1,6 +1,10 @@
1
1
  # The string_date_time extension provides String instance methods
2
2
  # for converting the strings to a date (e.g. String#to_date), allowing
3
3
  # for backwards compatibility with legacy Sequel code.
4
+ #
5
+ # To load the extension:
6
+ #
7
+ # Sequel.extension :string_date_time
4
8
 
5
9
  class String
6
10
  # Converts a string into a Date object.
@@ -1,9 +1,15 @@
1
1
  # The thread_local_timezones extension allows you to set a per-thread timezone that
2
2
  # will override the default global timezone while the thread is executing. The
3
3
  # main use case is for web applications that execute each request in its own thread,
4
- # and want to set the timezones based on the request. The most common example
5
- # is having the database always store time in UTC, but have the application deal
6
- # with the timezone of the current user. That can be done with:
4
+ # and want to set the timezones based on the request.
5
+ #
6
+ # To load the extension:
7
+ #
8
+ # Sequel.extension :thread_local_timezones
9
+ #
10
+ # The most common example is having the database always store time in
11
+ # UTC, but have the application deal with the timezone of the current
12
+ # user. That can be done with:
7
13
  #
8
14
  # Sequel.database_timezone = :utc
9
15
  # # In each thread:
@@ -2,6 +2,10 @@
2
2
  # returns a string that can be processed by graphviz's +dot+ program in
3
3
  # order to get a visualization of the dataset. Basically, it shows a version
4
4
  # of the dataset's abstract syntax tree.
5
+ #
6
+ # To load the extension:
7
+ #
8
+ # Sequel.extension :to_dot
5
9
 
6
10
  module Sequel
7
11
  class ToDot
@@ -100,12 +100,22 @@ module Sequel
100
100
  end
101
101
  end
102
102
 
103
+ # The key to use for the key hash when eager loading
104
+ def eager_loader_key
105
+ self[:eager_loader_key]
106
+ end
107
+
103
108
  # By default associations do not need to select a key in an associated table
104
109
  # to eagerly load.
105
110
  def eager_loading_use_associated_key?
106
111
  false
107
112
  end
108
113
 
114
+ # Alias of predicate_key, only for backwards compatibility.
115
+ def eager_loading_predicate_key
116
+ predicate_key
117
+ end
118
+
109
119
  # Whether to eagerly graph a lazy dataset, true by default. If this
110
120
  # is false, the association won't respect the :eager_graph option
111
121
  # when loading the association for a single record.
@@ -128,10 +138,23 @@ module Sequel
128
138
  false
129
139
  end
130
140
 
141
+ # The keys to use for loading of the regular dataset, as an array.
142
+ def predicate_keys
143
+ cached_fetch(:predicate_keys){Array(predicate_key)}
144
+ end
145
+
131
146
  # Qualify +col+ with the given table name. If +col+ is an array of columns,
132
- # return an array of qualified columns.
147
+ # return an array of qualified columns. Only qualifies Symbols and SQL::Identifier
148
+ # values, other values are not modified.
133
149
  def qualify(table, col)
134
- transform(col){|k| SQL::QualifiedIdentifier.new(table, k)}
150
+ transform(col) do |k|
151
+ case k
152
+ when Symbol, SQL::Identifier
153
+ SQL::QualifiedIdentifier.new(table, k)
154
+ else
155
+ Sequel::Qualifier.new(self[:model].dataset, table).transform(k)
156
+ end
157
+ end
135
158
  end
136
159
 
137
160
  # Qualify col with the associated model's table name.
@@ -288,11 +311,11 @@ module Sequel
288
311
  nil
289
312
  end
290
313
 
291
- # The key to use for the key hash when eager loading
292
- def eager_loader_key
293
- cached_fetch(:eager_loader_key){self[:key]}
314
+ # The expression to use on the left hand side of the IN lookup when eager loading
315
+ def predicate_key
316
+ cached_fetch(:predicate_key){qualified_primary_key}
294
317
  end
295
-
318
+
296
319
  # The column(s) in the associated table that the key in the current table references (either a symbol or an array).
297
320
  def primary_key
298
321
  cached_fetch(:primary_key){associated_class.primary_key}
@@ -367,16 +390,11 @@ module Sequel
367
390
  :"#{underscore(demodulize(self[:model].name))}_id"
368
391
  end
369
392
 
370
- # The key to use for the key hash when eager loading
371
- def eager_loader_key
372
- cached_fetch(:eager_loader_key){primary_key}
373
- end
374
-
375
393
  # The hash key to use for the eager loading predicate (left side of IN (1, 2, 3))
376
- def eager_loading_predicate_key
377
- cached_fetch(:eager_loading_predicate_key){qualify_assoc(self[:key])}
394
+ def predicate_key
395
+ cached_fetch(:predicate_key){qualify_assoc(self[:key])}
378
396
  end
379
- alias qualified_key eager_loading_predicate_key
397
+ alias qualified_key predicate_key
380
398
 
381
399
  # The column in the current table that the key in the associated table references.
382
400
  def primary_key
@@ -498,17 +516,12 @@ module Sequel
498
516
  :"#{singularize(self[:name])}_id"
499
517
  end
500
518
 
501
- # The key to use for the key hash when eager loading
502
- def eager_loader_key
503
- cached_fetch(:eager_loader_key){self[:left_primary_key]}
504
- end
505
-
506
519
  # The hash key to use for the eager loading predicate (left side of IN (1, 2, 3)).
507
520
  # The left key qualified by the join table.
508
- def eager_loading_predicate_key
509
- cached_fetch(:eager_loading_predicate_key){qualify(join_table_alias, self[:left_key])}
521
+ def predicate_key
522
+ cached_fetch(:predicate_key){qualify(join_table_alias, self[:left_key])}
510
523
  end
511
- alias qualified_left_key eager_loading_predicate_key
524
+ alias qualified_left_key predicate_key
512
525
 
513
526
  # The right key qualified by the join table.
514
527
  def qualified_right_key
@@ -765,15 +778,15 @@ module Sequel
765
778
  # choice for the database, or specify a specific symbol to manually select a strategy.
766
779
  # one_to_one associations support :distinct_on, :window_function, and :correlated_subquery.
767
780
  # *_many associations support :ruby, :window_function, and :correlated_subquery.
768
- # :eager_loader :: A proc to use to implement eager loading, overriding the default. Takes one or three arguments.
769
- # If three arguments, the first should be a key hash (used solely to enhance performance), the
770
- # second an array of records, and the third a hash of dependent associations. If one argument, is
771
- # passed a hash with keys :key_hash, :rows, and :associations, corresponding to the three
772
- # arguments, and an additional key :self, which is the dataset doing the eager loading. In the
773
- # proc, the associated records should be queried from the database and the associations cache for
774
- # each record should be populated.
775
- # :eager_loader_key :: A symbol for the key column to use to populate the key hash
776
- # for the eager loader.
781
+ # :eager_loader :: A proc to use to implement eager loading, overriding the default. Takes a single hash argument,
782
+ # with at least the keys: :rows, which is an array of current model instances, :associations,
783
+ # which is a hash of dependent associations, :self, which is the dataset doing the eager loading,
784
+ # :eager_block, which is a dynamic callback that should be called with the dataset, and :id_map,
785
+ # which is a mapping of key values to arrays of current model instances. In the proc, the
786
+ # associated records should be queried from the database and the associations cache for each
787
+ # record should be populated.
788
+ # :eager_loader_key :: A symbol for the key column to use to populate the key_hash
789
+ # for the eager loader. Can be set to nil to not populate the key_hash.
777
790
  # :extend :: A module or array of modules to extend the dataset with.
778
791
  # :graph_alias_base :: The base name to use for the table alias when eager graphing. Defaults to the name
779
792
  # of the association. If the alias name has already been used in the query, Sequel will create
@@ -894,11 +907,18 @@ module Sequel
894
907
 
895
908
  # dup early so we don't modify opts
896
909
  orig_opts = opts.dup
897
- orig_opts = association_reflection(opts[:clone])[:orig_opts].merge(orig_opts) if opts[:clone]
910
+ if opts[:clone]
911
+ cloned_assoc = association_reflection(opts[:clone])
912
+ raise(Error, "cannot clone an association to an association of different type (association #{name} with type #{type} cloning #{opts[:clone]} with type #{cloned_assoc[:type]})") unless cloned_assoc[:type] == type || [cloned_assoc[:type], type].all?{|t| [:one_to_many, :one_to_one].include?(t)}
913
+ orig_opts = cloned_assoc[:orig_opts].merge(orig_opts)
914
+ end
898
915
  opts = orig_opts.merge(:type => type, :name => name, :cache=>{}, :model => self)
899
916
  opts[:block] = block if block
900
917
  opts = assoc_class.new.merge!(opts)
901
918
  opts[:eager_block] = block unless opts.include?(:eager_block)
919
+ if !opts.has_key?(:predicate_key) && opts.has_key?(:eager_loading_predicate_key)
920
+ opts[:predicate_key] = opts[:eager_loading_predicate_key]
921
+ end
902
922
  opts[:graph_join_type] ||= :left_outer
903
923
  opts[:order_eager_graph] = true unless opts.include?(:order_eager_graph)
904
924
  conds = opts[:conditions]
@@ -945,9 +965,9 @@ module Sequel
945
965
  ds = eager_options[:eager_block].call(ds) if eager_options[:eager_block]
946
966
  if opts.eager_loading_use_associated_key?
947
967
  ds = if opts[:uses_left_composite_keys]
948
- ds.select_append(*opts.associated_key_alias.zip(opts.eager_loading_predicate_key).map{|a, k| SQL::AliasedExpression.new(k, a)})
968
+ ds.select_append(*opts.associated_key_alias.zip(opts.predicate_keys).map{|a, k| SQL::AliasedExpression.new(k, a)})
949
969
  else
950
- ds.select_append(SQL::AliasedExpression.new(opts.eager_loading_predicate_key, opts.associated_key_alias))
970
+ ds.select_append(SQL::AliasedExpression.new(opts.predicate_key, opts.associated_key_alias))
951
971
  end
952
972
  end
953
973
  ds
@@ -1038,7 +1058,7 @@ module Sequel
1038
1058
  def apply_window_function_eager_limit_strategy(ds, opts)
1039
1059
  rn = ds.row_number_column
1040
1060
  limit, offset = opts.limit_and_offset
1041
- ds = ds.unordered.select_append{row_number(:over, :partition=>opts.eager_loading_predicate_key, :order=>ds.opts[:order]){}.as(rn)}.from_self
1061
+ ds = ds.unordered.select_append{row_number(:over, :partition=>opts.predicate_key, :order=>ds.opts[:order]){}.as(rn)}.from_self
1042
1062
  ds = if opts[:type] == :one_to_one
1043
1063
  ds.where(rn => 1)
1044
1064
  elsif offset
@@ -1097,10 +1117,10 @@ module Sequel
1097
1117
  right = (opts[:right_key] ||= opts.default_right_key)
1098
1118
  rcks = opts[:right_keys] = Array(right)
1099
1119
  left_pk = (opts[:left_primary_key] ||= self.primary_key)
1120
+ opts[:eager_loader_key] = left_pk unless opts.has_key?(:eager_loader_key)
1100
1121
  lcpks = opts[:left_primary_keys] = Array(left_pk)
1101
1122
  lpkc = opts[:left_primary_key_column] ||= left_pk
1102
1123
  lpkcs = opts[:left_primary_key_columns] ||= Array(lpkc)
1103
- elk = opts.eager_loader_key
1104
1124
  raise(Error, "mismatched number of left composite keys: #{lcks.inspect} vs #{lcpks.inspect}") unless lcks.length == lcpks.length
1105
1125
  if opts[:right_primary_key]
1106
1126
  rcpks = Array(opts[:right_primary_key])
@@ -1114,15 +1134,15 @@ module Sequel
1114
1134
  graph_jt_conds = opts[:graph_join_table_conditions] = opts.fetch(:graph_join_table_conditions, []).to_a
1115
1135
  opts[:graph_join_table_join_type] ||= opts[:graph_join_type]
1116
1136
  opts[:after_load].unshift(:array_uniq!) if opts[:uniq]
1117
- opts[:dataset] ||= proc{opts.associated_class.inner_join(join_table, rcks.zip(opts.right_primary_keys) + lcks.zip(lcpks.map{|k| send(k)}))}
1137
+ opts[:dataset] ||= proc{opts.associated_class.inner_join(join_table, rcks.zip(opts.right_primary_keys) + opts.predicate_keys.zip(lcpks.map{|k| send(k)}), :qualify=>:deep)}
1118
1138
 
1119
1139
  opts[:eager_loader] ||= proc do |eo|
1120
- h = eo[:key_hash][elk]
1140
+ h = eo[:id_map]
1121
1141
  rows = eo[:rows]
1122
1142
  rows.each{|object| object.associations[name] = []}
1123
1143
  r = rcks.zip(opts.right_primary_keys)
1124
- l = [[opts.qualify(opts.join_table_alias, left), h.keys]]
1125
- ds = model.eager_loading_dataset(opts, opts.associated_class.inner_join(join_table, r + l), nil, eo[:associations], eo)
1144
+ l = [[opts.predicate_key, h.keys]]
1145
+ ds = model.eager_loading_dataset(opts, opts.associated_class.inner_join(join_table, r + l, :qualify=>:deep), nil, eo[:associations], eo)
1126
1146
  case opts.eager_limit_strategy
1127
1147
  when :window_function
1128
1148
  delete_rn = true
@@ -1131,7 +1151,7 @@ module Sequel
1131
1151
  when :correlated_subquery
1132
1152
  ds = apply_correlated_subquery_eager_limit_strategy(ds, opts) do |xds|
1133
1153
  dsa = ds.send(:dataset_alias, 2)
1134
- xds.inner_join(join_table, r + lcks.map{|k| [k, SQL::QualifiedIdentifier.new(opts.join_table_alias, k)]}, :table_alias=>dsa)
1154
+ xds.inner_join(join_table, r + lcks.map{|k| [k, SQL::QualifiedIdentifier.new(opts.join_table_alias, k)]}, :table_alias=>dsa, :qualify=>:deep)
1135
1155
  end
1136
1156
  end
1137
1157
  ds.all do |assoc_record|
@@ -1162,8 +1182,8 @@ module Sequel
1162
1182
  jt_graph_block = opts[:graph_join_table_block]
1163
1183
  opts[:eager_grapher] ||= proc do |eo|
1164
1184
  ds = eo[:self]
1165
- ds = ds.graph(join_table, use_jt_only_conditions ? jt_only_conditions : lcks.zip(lpkcs) + graph_jt_conds, :select=>false, :table_alias=>ds.unused_table_alias(join_table, [eo[:table_alias]]), :join_type=>jt_join_type, :implicit_qualifier=>eo[:implicit_qualifier], :from_self_alias=>ds.opts[:eager_graph][:master], &jt_graph_block)
1166
- 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)
1185
+ ds = ds.graph(join_table, use_jt_only_conditions ? jt_only_conditions : lcks.zip(lpkcs) + graph_jt_conds, :select=>false, :table_alias=>ds.unused_table_alias(join_table, [eo[:table_alias]]), :join_type=>jt_join_type, :implicit_qualifier=>eo[:implicit_qualifier], :qualify=>:deep, :from_self_alias=>ds.opts[:eager_graph][:master], &jt_graph_block)
1186
+ ds.graph(eager_graph_dataset(opts, eo), use_only_conditions ? only_conditions : opts.right_primary_keys.zip(rcks) + conditions, :select=>select, :table_alias=>eo[:table_alias], :qualify=>:deep, :join_type=>join_type, &graph_block)
1167
1187
  end
1168
1188
 
1169
1189
  def_association_dataset_methods(opts)
@@ -1191,14 +1211,12 @@ module Sequel
1191
1211
  def def_many_to_one(opts)
1192
1212
  name = opts[:name]
1193
1213
  model = self
1194
- opts[:key] = opts.default_key unless opts.include?(:key)
1195
- key_column = key = opts[:key]
1214
+ opts[:key] = opts.default_key unless opts.has_key?(:key)
1215
+ key = opts[:key]
1216
+ opts[:eager_loader_key] = key unless opts.has_key?(:eager_loader_key)
1196
1217
  cks = opts[:graph_keys] = opts[:keys] = Array(key)
1197
- if opts[:key_column]
1198
- key_column = opts[:key_column]
1199
- opts[:eager_loader_key] ||= key_column
1200
- opts[:graph_keys] = Array(key_column)
1201
- end
1218
+ opts[:key_column] ||= key
1219
+ opts[:graph_keys] = opts[:key_columns] = Array(opts[:key_column])
1202
1220
  opts[:qualified_key] = opts.qualify_cur(key)
1203
1221
  if opts[:primary_key]
1204
1222
  cpks = Array(opts[:primary_key])
@@ -1209,10 +1227,10 @@ module Sequel
1209
1227
  opts[:cartesian_product_number] ||= 0
1210
1228
  opts[:dataset] ||= proc do
1211
1229
  klass = opts.associated_class
1212
- klass.filter(Array(opts.qualified_primary_key).zip(cks.map{|k| send(k)}))
1230
+ klass.filter(opts.predicate_keys.zip(cks.map{|k| send(k)}))
1213
1231
  end
1214
1232
  opts[:eager_loader] ||= proc do |eo|
1215
- h = eo[:key_hash][key_column]
1233
+ h = eo[:id_map]
1216
1234
  keys = h.keys
1217
1235
  # Default the cached association to nil, so any object that doesn't have it
1218
1236
  # populated will have cached the negative lookup.
@@ -1220,7 +1238,7 @@ module Sequel
1220
1238
  # Skip eager loading if no objects have a foreign key for this association
1221
1239
  unless keys.empty?
1222
1240
  klass = opts.associated_class
1223
- model.eager_loading_dataset(opts, klass.filter(opts.qualified_primary_key=>keys), nil, eo[:associations], eo).all do |assoc_record|
1241
+ model.eager_loading_dataset(opts, klass.filter(opts.predicate_key=>keys), nil, eo[:associations], eo).all do |assoc_record|
1224
1242
  hash_key = uses_cks ? opts.primary_key_methods.map{|k| assoc_record.send(k)} : assoc_record.send(opts.primary_key_method)
1225
1243
  next unless objects = h[hash_key]
1226
1244
  objects.each{|object| object.associations[name] = assoc_record}
@@ -1237,7 +1255,7 @@ module Sequel
1237
1255
  graph_cks = opts[:graph_keys]
1238
1256
  opts[:eager_grapher] ||= proc do |eo|
1239
1257
  ds = eo[:self]
1240
- ds.graph(eager_graph_dataset(opts, eo), use_only_conditions ? only_conditions : opts.primary_keys.zip(graph_cks) + conditions, eo.merge(:select=>select, :join_type=>join_type, :from_self_alias=>ds.opts[:eager_graph][:master]), &graph_block)
1258
+ ds.graph(eager_graph_dataset(opts, eo), use_only_conditions ? only_conditions : opts.primary_keys.zip(graph_cks) + conditions, eo.merge(:select=>select, :join_type=>join_type, :qualify=>:deep, :from_self_alias=>ds.opts[:eager_graph][:master]), &graph_block)
1241
1259
  end
1242
1260
 
1243
1261
  def_association_dataset_methods(opts)
@@ -1256,18 +1274,19 @@ module Sequel
1256
1274
  key = (opts[:key] ||= opts.default_key)
1257
1275
  km = opts[:key_method] ||= opts[:key]
1258
1276
  cks = opts[:keys] = Array(key)
1277
+ opts[:key_methods] = Array(opts[:key_method])
1259
1278
  primary_key = (opts[:primary_key] ||= self.primary_key)
1279
+ opts[:eager_loader_key] = primary_key unless opts.has_key?(:eager_loader_key)
1260
1280
  cpks = opts[:primary_keys] = Array(primary_key)
1261
1281
  pkc = opts[:primary_key_column] ||= primary_key
1262
1282
  pkcs = opts[:primary_key_columns] ||= Array(pkc)
1263
- elk = opts.eager_loader_key
1264
1283
  raise(Error, "mismatched number of composite keys: #{cks.inspect} vs #{cpks.inspect}") unless cks.length == cpks.length
1265
1284
  uses_cks = opts[:uses_composite_keys] = cks.length > 1
1266
1285
  opts[:dataset] ||= proc do
1267
- opts.associated_class.filter(Array(opts.qualified_key).zip(cpks.map{|k| send(k)}))
1286
+ opts.associated_class.filter(opts.predicate_keys.zip(cpks.map{|k| send(k)}))
1268
1287
  end
1269
1288
  opts[:eager_loader] ||= proc do |eo|
1270
- h = eo[:key_hash][elk]
1289
+ h = eo[:id_map]
1271
1290
  rows = eo[:rows]
1272
1291
  if one_to_one
1273
1292
  rows.each{|object| object.associations[name] = nil}
@@ -1276,7 +1295,7 @@ module Sequel
1276
1295
  end
1277
1296
  reciprocal = opts.reciprocal
1278
1297
  klass = opts.associated_class
1279
- filter_keys = opts.eager_loading_predicate_key
1298
+ filter_keys = opts.predicate_key
1280
1299
  ds = model.eager_loading_dataset(opts, klass.filter(filter_keys=>h.keys), nil, eo[:associations], eo)
1281
1300
  case opts.eager_limit_strategy
1282
1301
  when :distinct_on
@@ -1323,7 +1342,7 @@ module Sequel
1323
1342
  graph_block = opts[:graph_block]
1324
1343
  opts[:eager_grapher] ||= proc do |eo|
1325
1344
  ds = eo[:self]
1326
- ds = ds.graph(eager_graph_dataset(opts, eo), use_only_conditions ? only_conditions : cks.zip(pkcs) + conditions, eo.merge(:select=>select, :join_type=>join_type, :from_self_alias=>ds.opts[:eager_graph][:master]), &graph_block)
1345
+ ds = ds.graph(eager_graph_dataset(opts, eo), use_only_conditions ? only_conditions : cks.zip(pkcs) + conditions, eo.merge(:select=>select, :join_type=>join_type, :qualify=>:deep, :from_self_alias=>ds.opts[:eager_graph][:master]), &graph_block)
1327
1346
  # We only load reciprocals for one_to_many associations, as other reciprocals don't make sense
1328
1347
  ds.opts[:eager_graph][:reciprocals][eo[:table_alias]] = opts.reciprocal
1329
1348
  ds
@@ -1341,7 +1360,7 @@ module Sequel
1341
1360
  association_module_private_def(opts._setter_method, opts) do |o|
1342
1361
  up_ds = _apply_association_options(opts, opts.associated_class.filter(cks.zip(cpks.map{|k| send(k)})))
1343
1362
  if o
1344
- up_ds = up_ds.exclude(o.pk_hash)
1363
+ up_ds = up_ds.exclude(o.pk_hash) unless o.new?
1345
1364
  cks.zip(cpks).each{|k, pk| o.send(:"#{k}=", send(pk))}
1346
1365
  end
1347
1366
  checked_transaction do
@@ -1802,8 +1821,7 @@ module Sequel
1802
1821
  # need to filter based on columns in associated tables, look at +eager_graph+
1803
1822
  # or join the tables you need to filter on manually.
1804
1823
  #
1805
- # Each association's order, if defined, is respected. Eager also works
1806
- # on a limited dataset, but does not use any :limit options for associations.
1824
+ # Each association's order, if defined, is respected.
1807
1825
  # If the association uses a block or has an :eager_block argument, it is used.
1808
1826
  def eager(*associations)
1809
1827
  opt = @opts[:eager]
@@ -1877,11 +1895,18 @@ module Sequel
1877
1895
  # model :: Current Model
1878
1896
  # ta :: table_alias used for the parent association
1879
1897
  # requirements :: an array, used as a stack for requirements
1880
- # r :: association reflection for the current association
1898
+ # r :: association reflection for the current association, or an SQL::AliasedExpression
1899
+ # with the reflection as the expression and the alias base as the aliaz.
1881
1900
  # *associations :: any associations dependent on this one
1882
1901
  def eager_graph_association(ds, model, ta, requirements, r, *associations)
1902
+ if r.is_a?(SQL::AliasedExpression)
1903
+ alias_base = r.aliaz
1904
+ r = r.expression
1905
+ else
1906
+ alias_base = r[:graph_alias_base]
1907
+ end
1883
1908
  assoc_name = r[:name]
1884
- assoc_table_alias = ds.unused_table_alias(r[:graph_alias_base])
1909
+ assoc_table_alias = ds.unused_table_alias(alias_base)
1885
1910
  loader = r[:eager_grapher]
1886
1911
  if !associations.empty?
1887
1912
  if associations.first.respond_to?(:call)
@@ -1919,11 +1944,11 @@ module Sequel
1919
1944
  return ds if associations.empty?
1920
1945
  associations.flatten.each do |association|
1921
1946
  ds = case association
1922
- when Symbol
1923
- ds.eager_graph_association(ds, model, ta, requirements, check_association(model, association))
1947
+ when Symbol, SQL::AliasedExpression
1948
+ ds.eager_graph_association(ds, model, ta, requirements, eager_graph_check_association(model, association))
1924
1949
  when Hash
1925
1950
  association.each do |assoc, assoc_assocs|
1926
- ds = ds.eager_graph_association(ds, model, ta, requirements, check_association(model, assoc), assoc_assocs)
1951
+ ds = ds.eager_graph_association(ds, model, ta, requirements, eager_graph_check_association(model, assoc), assoc_assocs)
1927
1952
  end
1928
1953
  ds
1929
1954
  else raise(Sequel::Error, 'Associations must be in the form of a symbol or hash')
@@ -1931,7 +1956,7 @@ module Sequel
1931
1956
  end
1932
1957
  ds
1933
1958
  end
1934
-
1959
+
1935
1960
  # Replace the array of plain hashes with an array of model objects will all eager_graphed
1936
1961
  # associations set in the associations cache for each object.
1937
1962
  def eager_graph_build_associations(hashes)
@@ -1990,6 +2015,16 @@ module Sequel
1990
2015
  reflection
1991
2016
  end
1992
2017
 
2018
+ # Allow associations that are eagerly graphed to be specified as an SQL::AliasedExpression, for
2019
+ # per-call determining of the alias base.
2020
+ def eager_graph_check_association(model, association)
2021
+ if association.is_a?(SQL::AliasedExpression)
2022
+ SQL::AliasedExpression.new(check_association(model, association.expression), association.aliaz)
2023
+ else
2024
+ check_association(model, association)
2025
+ end
2026
+ end
2027
+
1993
2028
  # Eagerly load all specified associations
1994
2029
  def eager_load(a, eager_assoc=@opts[:eager])
1995
2030
  return if a.empty?
@@ -2001,22 +2036,35 @@ module Sequel
2001
2036
  # Reflections for all associations to eager load
2002
2037
  reflections = eager_assoc.keys.collect{|assoc| model.association_reflection(assoc) || (raise Sequel::UndefinedAssociation, "Model: #{self}, Association: #{assoc}")}
2003
2038
 
2004
- # Populate keys to monitor
2005
- reflections.each{|reflection| key_hash[reflection.eager_loader_key] ||= Hash.new{|h,k| h[k] = []}}
2006
-
2007
- # Associate each object with every key being monitored
2008
- a.each do |rec|
2009
- key_hash.each do |key, id_map|
2010
- case key
2011
- when Array
2012
- id_map[key.map{|k| rec[k]}] << rec if key.all?{|k| rec[k]}
2013
- when Symbol
2014
- id_map[rec[key]] << rec if rec[key]
2039
+ # Populate the key_hash entry for each association being eagerly loaded
2040
+ reflections.each do |r|
2041
+ if key = r.eager_loader_key
2042
+ # key_hash for this key has already been populated,
2043
+ # skip populating again so that duplicate values
2044
+ # aren't added.
2045
+ unless id_map = key_hash[key]
2046
+ id_map = key_hash[key] = Hash.new{|h,k| h[k] = []}
2047
+
2048
+ # Supporting both single (Symbol) and composite (Array) keys.
2049
+ a.each do |rec|
2050
+ case key
2051
+ when Array
2052
+ if (k = key.map{|k2| rec.send(k2)}) && k.all?
2053
+ id_map[k] << rec
2054
+ end
2055
+ when Symbol
2056
+ if k = rec.send(key)
2057
+ id_map[k] << rec
2058
+ end
2059
+ else
2060
+ raise Error, "unhandled eager_loader_key #{key.inspect} for association #{r[:name]}"
2061
+ end
2062
+ end
2015
2063
  end
2064
+ else
2065
+ id_map = nil
2016
2066
  end
2017
- end
2018
2067
 
2019
- reflections.each do |r|
2020
2068
  loader = r[:eager_loader]
2021
2069
  associations = eager_assoc[r[:name]]
2022
2070
  if associations.respond_to?(:call)
@@ -2026,7 +2074,7 @@ module Sequel
2026
2074
  eager_block, associations = pr_assoc
2027
2075
  end
2028
2076
  if loader.arity == 1
2029
- loader.call(:key_hash=>key_hash, :rows=>a, :associations=>associations, :self=>self, :eager_block=>eager_block)
2077
+ loader.call(:key_hash=>key_hash, :rows=>a, :associations=>associations, :self=>self, :eager_block=>eager_block, :id_map=>id_map)
2030
2078
  else
2031
2079
  loader.call(key_hash, a, associations)
2032
2080
  end
@@ -2041,12 +2089,17 @@ module Sequel
2041
2089
 
2042
2090
  # Return a subquery expression for filering by a many_to_many association
2043
2091
  def many_to_many_association_filter_expression(op, ref, obj)
2044
- lpks, lks, rks = ref.values_at(:left_primary_keys, :left_keys, :right_keys)
2092
+ lpks, lks, rks = ref.values_at(:left_primary_key_columns, :left_keys, :right_keys)
2045
2093
  jt = ref.join_table_alias
2046
2094
  lpks = lpks.first if lpks.length == 1
2047
2095
  lpks = ref.qualify(model.table_name, lpks)
2048
- meths = ref.right_primary_keys
2049
- meths = ref.qualify(obj.model.table_name, meths) if obj.is_a?(Sequel::Dataset)
2096
+
2097
+ meths = if obj.is_a?(Sequel::Dataset)
2098
+ ref.qualify(obj.model.table_name, ref.right_primary_keys)
2099
+ else
2100
+ ref.right_primary_key_methods
2101
+ end
2102
+
2050
2103
  exp = association_filter_key_expression(ref.qualify(jt, rks), meths, obj)
2051
2104
  if exp == SQL::Constants::FALSE
2052
2105
  association_filter_handle_inversion(op, exp, Array(lpks))
@@ -2057,17 +2110,23 @@ module Sequel
2057
2110
 
2058
2111
  # Return a simple equality expression for filering by a many_to_one association
2059
2112
  def many_to_one_association_filter_expression(op, ref, obj)
2060
- keys = ref.qualify(model.table_name, ref[:keys])
2061
- meths = ref.primary_keys
2062
- meths = ref.qualify(obj.model.table_name, meths) if obj.is_a?(Sequel::Dataset)
2113
+ keys = ref.qualify(model.table_name, ref[:key_columns])
2114
+ meths = if obj.is_a?(Sequel::Dataset)
2115
+ ref.qualify(obj.model.table_name, ref.primary_keys)
2116
+ else
2117
+ ref.primary_key_methods
2118
+ end
2063
2119
  association_filter_handle_inversion(op, association_filter_key_expression(keys, meths, obj), keys)
2064
2120
  end
2065
2121
 
2066
2122
  # Return a simple equality expression for filering by a one_to_* association
2067
2123
  def one_to_many_association_filter_expression(op, ref, obj)
2068
- keys = ref.qualify(model.table_name, ref[:primary_keys])
2069
- meths = ref[:keys]
2070
- meths = ref.qualify(obj.model.table_name, meths) if obj.is_a?(Sequel::Dataset)
2124
+ keys = ref.qualify(model.table_name, ref[:primary_key_columns])
2125
+ meths = if obj.is_a?(Sequel::Dataset)
2126
+ ref.qualify(obj.model.table_name, ref[:keys])
2127
+ else
2128
+ ref[:key_methods]
2129
+ end
2071
2130
  association_filter_handle_inversion(op, association_filter_key_expression(keys, meths, obj), keys)
2072
2131
  end
2073
2132
  alias one_to_one_association_filter_expression one_to_many_association_filter_expression