sequel 3.36.1 → 3.37.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +84 -0
- data/Rakefile +13 -0
- data/bin/sequel +12 -16
- data/doc/advanced_associations.rdoc +36 -67
- data/doc/association_basics.rdoc +11 -16
- data/doc/release_notes/3.37.0.txt +338 -0
- data/doc/schema_modification.rdoc +4 -0
- data/lib/sequel/adapters/jdbc/h2.rb +1 -1
- data/lib/sequel/adapters/jdbc/postgresql.rb +26 -8
- data/lib/sequel/adapters/mysql2.rb +4 -3
- data/lib/sequel/adapters/odbc/mssql.rb +2 -2
- data/lib/sequel/adapters/postgres.rb +4 -60
- data/lib/sequel/adapters/shared/mssql.rb +2 -1
- data/lib/sequel/adapters/shared/mysql.rb +0 -5
- data/lib/sequel/adapters/shared/postgres.rb +68 -2
- data/lib/sequel/adapters/shared/sqlite.rb +17 -1
- data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +12 -1
- data/lib/sequel/adapters/utils/pg_types.rb +76 -0
- data/lib/sequel/core.rb +13 -0
- data/lib/sequel/database/misc.rb +41 -1
- data/lib/sequel/database/schema_generator.rb +23 -10
- data/lib/sequel/database/schema_methods.rb +26 -4
- data/lib/sequel/dataset/graph.rb +2 -1
- data/lib/sequel/dataset/query.rb +62 -2
- data/lib/sequel/extensions/_pretty_table.rb +7 -3
- data/lib/sequel/extensions/arbitrary_servers.rb +5 -4
- data/lib/sequel/extensions/blank.rb +4 -0
- data/lib/sequel/extensions/columns_introspection.rb +13 -2
- data/lib/sequel/extensions/core_extensions.rb +6 -0
- data/lib/sequel/extensions/eval_inspect.rb +158 -0
- data/lib/sequel/extensions/inflector.rb +4 -0
- data/lib/sequel/extensions/looser_typecasting.rb +5 -4
- data/lib/sequel/extensions/migration.rb +4 -1
- data/lib/sequel/extensions/named_timezones.rb +4 -0
- data/lib/sequel/extensions/null_dataset.rb +4 -0
- data/lib/sequel/extensions/pagination.rb +4 -0
- data/lib/sequel/extensions/pg_array.rb +219 -168
- data/lib/sequel/extensions/pg_array_ops.rb +7 -2
- data/lib/sequel/extensions/pg_auto_parameterize.rb +10 -4
- data/lib/sequel/extensions/pg_hstore.rb +3 -1
- data/lib/sequel/extensions/pg_hstore_ops.rb +7 -2
- data/lib/sequel/extensions/pg_inet.rb +28 -3
- data/lib/sequel/extensions/pg_interval.rb +192 -0
- data/lib/sequel/extensions/pg_json.rb +21 -9
- data/lib/sequel/extensions/pg_range.rb +487 -0
- data/lib/sequel/extensions/pg_range_ops.rb +122 -0
- data/lib/sequel/extensions/pg_statement_cache.rb +3 -2
- data/lib/sequel/extensions/pretty_table.rb +12 -1
- data/lib/sequel/extensions/query.rb +4 -0
- data/lib/sequel/extensions/query_literals.rb +6 -6
- data/lib/sequel/extensions/schema_dumper.rb +39 -38
- data/lib/sequel/extensions/select_remove.rb +4 -0
- data/lib/sequel/extensions/server_block.rb +3 -2
- data/lib/sequel/extensions/split_array_nil.rb +65 -0
- data/lib/sequel/extensions/sql_expr.rb +4 -0
- data/lib/sequel/extensions/string_date_time.rb +4 -0
- data/lib/sequel/extensions/thread_local_timezones.rb +9 -3
- data/lib/sequel/extensions/to_dot.rb +4 -0
- data/lib/sequel/model/associations.rb +150 -91
- data/lib/sequel/plugins/identity_map.rb +2 -2
- data/lib/sequel/plugins/list.rb +1 -0
- data/lib/sequel/plugins/many_through_many.rb +33 -32
- data/lib/sequel/plugins/nested_attributes.rb +11 -3
- data/lib/sequel/plugins/rcte_tree.rb +2 -2
- data/lib/sequel/plugins/schema.rb +1 -1
- data/lib/sequel/sql.rb +14 -14
- data/lib/sequel/version.rb +2 -2
- data/spec/adapters/mysql_spec.rb +25 -0
- data/spec/adapters/postgres_spec.rb +572 -28
- data/spec/adapters/sqlite_spec.rb +16 -1
- data/spec/core/database_spec.rb +61 -2
- data/spec/core/dataset_spec.rb +92 -0
- data/spec/core/expression_filters_spec.rb +12 -0
- data/spec/extensions/arbitrary_servers_spec.rb +1 -1
- data/spec/extensions/boolean_readers_spec.rb +25 -25
- data/spec/extensions/eval_inspect_spec.rb +58 -0
- data/spec/extensions/json_serializer_spec.rb +0 -6
- data/spec/extensions/list_spec.rb +1 -1
- data/spec/extensions/looser_typecasting_spec.rb +7 -7
- data/spec/extensions/many_through_many_spec.rb +81 -0
- data/spec/extensions/nested_attributes_spec.rb +21 -4
- data/spec/extensions/pg_array_ops_spec.rb +1 -11
- data/spec/extensions/pg_array_spec.rb +181 -90
- data/spec/extensions/pg_auto_parameterize_spec.rb +3 -3
- data/spec/extensions/pg_hstore_spec.rb +1 -3
- data/spec/extensions/pg_inet_spec.rb +6 -1
- data/spec/extensions/pg_interval_spec.rb +73 -0
- data/spec/extensions/pg_json_spec.rb +5 -9
- data/spec/extensions/pg_range_ops_spec.rb +49 -0
- data/spec/extensions/pg_range_spec.rb +372 -0
- data/spec/extensions/pg_statement_cache_spec.rb +1 -2
- data/spec/extensions/query_literals_spec.rb +1 -2
- data/spec/extensions/schema_dumper_spec.rb +48 -89
- data/spec/extensions/serialization_spec.rb +1 -5
- data/spec/extensions/server_block_spec.rb +2 -2
- data/spec/extensions/spec_helper.rb +12 -2
- data/spec/extensions/split_array_nil_spec.rb +24 -0
- data/spec/integration/associations_test.rb +4 -4
- data/spec/integration/database_test.rb +2 -2
- data/spec/integration/dataset_test.rb +4 -4
- data/spec/integration/eager_loader_test.rb +6 -6
- data/spec/integration/plugin_test.rb +2 -2
- data/spec/integration/spec_helper.rb +2 -2
- data/spec/model/association_reflection_spec.rb +5 -0
- data/spec/model/associations_spec.rb +156 -49
- data/spec/model/eager_loading_spec.rb +137 -2
- data/spec/model/model_spec.rb +10 -10
- 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.
|
5
|
-
#
|
6
|
-
#
|
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)
|
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
|
292
|
-
def
|
293
|
-
cached_fetch(:
|
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
|
377
|
-
cached_fetch(:
|
394
|
+
def predicate_key
|
395
|
+
cached_fetch(:predicate_key){qualify_assoc(self[:key])}
|
378
396
|
end
|
379
|
-
alias qualified_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
|
509
|
-
cached_fetch(:
|
521
|
+
def predicate_key
|
522
|
+
cached_fetch(:predicate_key){qualify(join_table_alias, self[:left_key])}
|
510
523
|
end
|
511
|
-
alias qualified_left_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
|
769
|
-
#
|
770
|
-
#
|
771
|
-
#
|
772
|
-
#
|
773
|
-
#
|
774
|
-
#
|
775
|
-
# :eager_loader_key :: A symbol for the key column to use to populate the
|
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
|
-
|
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.
|
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.
|
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.
|
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) +
|
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[:
|
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.
|
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.
|
1195
|
-
|
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
|
-
|
1198
|
-
|
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(
|
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[:
|
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.
|
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(
|
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[:
|
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.
|
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.
|
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(
|
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,
|
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,
|
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
|
2005
|
-
reflections.each
|
2006
|
-
|
2007
|
-
|
2008
|
-
|
2009
|
-
|
2010
|
-
|
2011
|
-
|
2012
|
-
|
2013
|
-
|
2014
|
-
|
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(:
|
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
|
-
|
2049
|
-
meths =
|
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[:
|
2061
|
-
meths =
|
2062
|
-
|
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[:
|
2069
|
-
meths =
|
2070
|
-
|
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
|