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.
- 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
|