sequel 3.36.1 → 3.37.0

Sign up to get free protection for your applications and to get access to all the features.
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