sequel 3.25.0 → 3.26.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +28 -0
- data/README.rdoc +3 -3
- data/Rakefile +17 -11
- data/doc/release_notes/3.26.0.txt +88 -0
- data/lib/sequel/adapters/ado.rb +10 -0
- data/lib/sequel/adapters/do.rb +12 -0
- data/lib/sequel/adapters/jdbc.rb +6 -6
- data/lib/sequel/adapters/mysql.rb +8 -2
- data/lib/sequel/adapters/mysql2.rb +5 -1
- data/lib/sequel/adapters/odbc.rb +10 -2
- data/lib/sequel/adapters/oracle.rb +5 -1
- data/lib/sequel/adapters/postgres.rb +10 -4
- data/lib/sequel/adapters/shared/access.rb +11 -0
- data/lib/sequel/adapters/shared/oracle.rb +0 -4
- data/lib/sequel/adapters/shared/postgres.rb +0 -12
- data/lib/sequel/adapters/tinytds.rb +9 -0
- data/lib/sequel/connection_pool.rb +1 -1
- data/lib/sequel/connection_pool/threaded.rb +3 -2
- data/lib/sequel/core.rb +1 -1
- data/lib/sequel/database/connecting.rb +3 -3
- data/lib/sequel/database/dataset.rb +1 -1
- data/lib/sequel/database/dataset_defaults.rb +1 -1
- data/lib/sequel/database/logging.rb +1 -1
- data/lib/sequel/database/misc.rb +23 -6
- data/lib/sequel/database/query.rb +16 -15
- data/lib/sequel/database/schema_methods.rb +21 -16
- data/lib/sequel/dataset/actions.rb +19 -16
- data/lib/sequel/dataset/features.rb +8 -2
- data/lib/sequel/dataset/graph.rb +1 -1
- data/lib/sequel/dataset/misc.rb +29 -9
- data/lib/sequel/dataset/mutation.rb +3 -3
- data/lib/sequel/dataset/prepared_statements.rb +11 -11
- data/lib/sequel/dataset/query.rb +28 -7
- data/lib/sequel/dataset/sql.rb +2 -2
- data/lib/sequel/extensions/migration.rb +1 -0
- data/lib/sequel/model.rb +5 -4
- data/lib/sequel/model/associations.rb +487 -328
- data/lib/sequel/model/base.rb +43 -26
- data/lib/sequel/model/exceptions.rb +2 -0
- data/lib/sequel/plugins/identity_map.rb +111 -4
- data/lib/sequel/plugins/sharding.rb +12 -20
- data/lib/sequel/plugins/xml_serializer.rb +2 -2
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/postgres_spec.rb +0 -6
- data/spec/core/connection_pool_spec.rb +6 -0
- data/spec/core/database_spec.rb +12 -0
- data/spec/core/schema_spec.rb +9 -2
- data/spec/extensions/identity_map_spec.rb +162 -0
- data/spec/extensions/many_through_many_spec.rb +3 -19
- data/spec/extensions/xml_serializer_spec.rb +4 -4
- data/spec/model/eager_loading_spec.rb +7 -21
- data/spec/model/record_spec.rb +23 -0
- metadata +36 -34
@@ -1,7 +1,7 @@
|
|
1
1
|
module Sequel
|
2
2
|
class Dataset
|
3
3
|
# ---------------------
|
4
|
-
# :section: Methods that describe what the dataset supports
|
4
|
+
# :section: 4 - Methods that describe what the dataset supports
|
5
5
|
# These methods all return booleans, with most describing whether or not the
|
6
6
|
# dataset supports a feature.
|
7
7
|
# ---------------------
|
@@ -11,7 +11,13 @@ module Sequel
|
|
11
11
|
|
12
12
|
# Whether this dataset quotes identifiers.
|
13
13
|
def quote_identifiers?
|
14
|
-
@quote_identifiers
|
14
|
+
if defined?(@quote_identifiers)
|
15
|
+
@quote_identifiers
|
16
|
+
elsif db.respond_to?(:quote_identifiers?)
|
17
|
+
@quote_identifiers = db.quote_identifiers?
|
18
|
+
else
|
19
|
+
@quote_identifiers = false
|
20
|
+
end
|
15
21
|
end
|
16
22
|
|
17
23
|
# Whether this dataset will provide accurate number of rows matched for
|
data/lib/sequel/dataset/graph.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
module Sequel
|
2
2
|
class Dataset
|
3
3
|
# ---------------------
|
4
|
-
# :section: Methods related to dataset graphing
|
4
|
+
# :section: 5 - Methods related to dataset graphing
|
5
5
|
# Dataset graphing changes the dataset to yield hashes where keys are table
|
6
6
|
# name symbols and values are hashes representing the columns related to
|
7
7
|
# that table. All of these methods return modified copies of the receiver.
|
data/lib/sequel/dataset/misc.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
module Sequel
|
2
2
|
class Dataset
|
3
3
|
# ---------------------
|
4
|
-
# :section: Miscellaneous methods
|
4
|
+
# :section: 6 - Miscellaneous methods
|
5
5
|
# These methods don't fit cleanly into another section.
|
6
6
|
# ---------------------
|
7
7
|
|
@@ -23,17 +23,13 @@ module Sequel
|
|
23
23
|
# DB[:posts]
|
24
24
|
#
|
25
25
|
# Sequel::Dataset is an abstract class that is not useful by itself. Each
|
26
|
-
# database
|
26
|
+
# database adapter provides a subclass of Sequel::Dataset, and has
|
27
27
|
# the Database#dataset method return an instance of that subclass.
|
28
28
|
def initialize(db, opts = nil)
|
29
29
|
@db = db
|
30
|
-
@quote_identifiers = db.quote_identifiers? if db.respond_to?(:quote_identifiers?)
|
31
|
-
@identifier_input_method = db.identifier_input_method if db.respond_to?(:identifier_input_method)
|
32
|
-
@identifier_output_method = db.identifier_output_method if db.respond_to?(:identifier_output_method)
|
33
30
|
@opts = opts || {}
|
34
|
-
@row_proc = nil
|
35
31
|
end
|
36
|
-
|
32
|
+
|
37
33
|
# Define a hash value such that datasets with the same DB, opts, and SQL
|
38
34
|
# will be consider equal.
|
39
35
|
def ==(o)
|
@@ -87,10 +83,10 @@ module Sequel
|
|
87
83
|
# have a table, raises an error. If the table is aliased, returns the original
|
88
84
|
# table, not the alias
|
89
85
|
#
|
90
|
-
# DB[:table].
|
86
|
+
# DB[:table].first_source_table
|
91
87
|
# # => :table
|
92
88
|
#
|
93
|
-
# DB[:table___t].
|
89
|
+
# DB[:table___t].first_source_table
|
94
90
|
# # => :table
|
95
91
|
def first_source_table
|
96
92
|
source = @opts[:from]
|
@@ -114,6 +110,30 @@ module Sequel
|
|
114
110
|
[db, opts.sort_by{|k| k.to_s}, sql].hash
|
115
111
|
end
|
116
112
|
|
113
|
+
# The String instance method to call on identifiers before sending them to
|
114
|
+
# the database.
|
115
|
+
def identifier_input_method
|
116
|
+
if defined?(@identifier_input_method)
|
117
|
+
@identifier_input_method
|
118
|
+
elsif db.respond_to?(:identifier_input_method)
|
119
|
+
@identifier_input_method = db.identifier_input_method
|
120
|
+
else
|
121
|
+
@identifier_input_method = nil
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
# The String instance method to call on identifiers before sending them to
|
126
|
+
# the database.
|
127
|
+
def identifier_output_method
|
128
|
+
if defined?(@identifier_output_method)
|
129
|
+
@identifier_output_method
|
130
|
+
elsif db.respond_to?(:identifier_output_method)
|
131
|
+
@identifier_output_method = db.identifier_output_method
|
132
|
+
else
|
133
|
+
@identifier_output_method = nil
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
117
137
|
# Returns a string representation of the dataset including the class name
|
118
138
|
# and the corresponding SQL select statement.
|
119
139
|
def inspect
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module Sequel
|
2
2
|
class Dataset
|
3
3
|
# ---------------------
|
4
|
-
# :section: Mutation methods
|
4
|
+
# :section: 7 - Mutation methods
|
5
5
|
# These methods modify the receiving dataset and should be used with care.
|
6
6
|
# ---------------------
|
7
7
|
|
@@ -21,10 +21,10 @@ module Sequel
|
|
21
21
|
def_mutation_method(*MUTATION_METHODS)
|
22
22
|
|
23
23
|
# Set the method to call on identifiers going into the database for this dataset
|
24
|
-
|
24
|
+
attr_writer :identifier_input_method
|
25
25
|
|
26
26
|
# Set the method to call on identifiers coming the database for this dataset
|
27
|
-
|
27
|
+
attr_writer :identifier_output_method
|
28
28
|
|
29
29
|
# Whether to quote identifiers for this dataset
|
30
30
|
attr_writer :quote_identifiers
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module Sequel
|
2
2
|
class Dataset
|
3
3
|
# ---------------------
|
4
|
-
# :section: Methods related to prepared statements or bound variables
|
4
|
+
# :section: 8 - Methods related to prepared statements or bound variables
|
5
5
|
# On some adapters, these use native prepared statements and bound variables, on others
|
6
6
|
# support is emulated. For details, see the {"Prepared Statements/Bound Variables" guide}[link:files/doc/prepared_statements_rdoc.html].
|
7
7
|
# ---------------------
|
@@ -199,11 +199,9 @@ module Sequel
|
|
199
199
|
clone(:bind_vars=>@opts[:bind_vars] ? @opts[:bind_vars].merge(bind_vars) : bind_vars)
|
200
200
|
end
|
201
201
|
|
202
|
-
# For the given type (:select, :insert, :update, or :delete),
|
203
|
-
# run the sql with the bind variables
|
204
|
-
#
|
205
|
-
# insert or update (if one of those types is used),
|
206
|
-
# which may contain placeholders.
|
202
|
+
# For the given type (:select, :first, :insert, :insert_select, :update, or :delete),
|
203
|
+
# run the sql with the bind variables specified in the hash. +values+ is a hash passed to
|
204
|
+
# insert or update (if one of those types is used), which may contain placeholders.
|
207
205
|
#
|
208
206
|
# DB[:table].filter(:id=>:$id).call(:first, :id=>1)
|
209
207
|
# # SELECT * FROM table WHERE id = ? LIMIT 1 -- (1)
|
@@ -212,11 +210,13 @@ module Sequel
|
|
212
210
|
prepare(type, nil, *values).call(bind_variables, &block)
|
213
211
|
end
|
214
212
|
|
215
|
-
# Prepare an SQL statement for later execution.
|
216
|
-
#
|
217
|
-
#
|
218
|
-
#
|
219
|
-
#
|
213
|
+
# Prepare an SQL statement for later execution. Takes a type similar to #call,
|
214
|
+
# and the name symbol of the prepared statement.
|
215
|
+
# This returns a clone of the dataset extended with PreparedStatementMethods,
|
216
|
+
# which you can +call+ with the hash of bind variables to use.
|
217
|
+
# The prepared statement is also stored in
|
218
|
+
# the associated database, where it can be called by name.
|
219
|
+
# The following usage is identical:
|
220
220
|
#
|
221
221
|
# ps = DB[:table].filter(:name=>:$name).prepare(:first, :select_by_name)
|
222
222
|
#
|
data/lib/sequel/dataset/query.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
module Sequel
|
2
2
|
class Dataset
|
3
3
|
# ---------------------
|
4
|
-
# :section: Methods that return modified datasets
|
4
|
+
# :section: 1 - Methods that return modified datasets
|
5
5
|
# These methods all return modified copies of the receiver.
|
6
6
|
# ---------------------
|
7
7
|
|
@@ -80,7 +80,7 @@ module Sequel
|
|
80
80
|
# :from_self :: Set to false to not wrap the returned dataset in a from_self, use with care.
|
81
81
|
#
|
82
82
|
# DB[:items].except(DB[:other_items])
|
83
|
-
# # SELECT * FROM items EXCEPT SELECT * FROM other_items
|
83
|
+
# # SELECT * FROM (SELECT * FROM items EXCEPT SELECT * FROM other_items) AS t1
|
84
84
|
#
|
85
85
|
# DB[:items].except(DB[:other_items], :all=>true, :from_self=>false)
|
86
86
|
# # SELECT * FROM items EXCEPT ALL SELECT * FROM other_items
|
@@ -381,7 +381,11 @@ module Sequel
|
|
381
381
|
inner_join(*args, &block)
|
382
382
|
end
|
383
383
|
|
384
|
-
# Returns a joined dataset.
|
384
|
+
# Returns a joined dataset. Not usually called directly, users should use the
|
385
|
+
# appropriate join method (e.g. join, left_join, natural_join, cross_join) which fills
|
386
|
+
# in the +type+ argument.
|
387
|
+
#
|
388
|
+
# Takes the following arguments:
|
385
389
|
#
|
386
390
|
# * type - The type of join to do (e.g. :inner)
|
387
391
|
# * table - Depends on type:
|
@@ -411,6 +415,23 @@ module Sequel
|
|
411
415
|
# in which case it yields the table alias/name for the table currently being joined,
|
412
416
|
# the table alias/name for the last joined (or first table), and an array of previous
|
413
417
|
# SQL::JoinClause. Unlike +filter+, this block is not treated as a virtual row block.
|
418
|
+
#
|
419
|
+
# Examples:
|
420
|
+
#
|
421
|
+
# DB[:a].join_table(:cross, :b)
|
422
|
+
# # SELECT * FROM a CROSS JOIN b
|
423
|
+
#
|
424
|
+
# DB[:a].join_table(:inner, DB[:b], :c=>d)
|
425
|
+
# # SELECT * FROM a INNER JOIN (SELECT * FROM b) AS t1 ON (t1.c = a.d)
|
426
|
+
#
|
427
|
+
# DB[:a].join_table(:left, :b___c, [:d])
|
428
|
+
# # SELECT * FROM a LEFT JOIN b AS c USING (d)
|
429
|
+
#
|
430
|
+
# DB[:a].natural_join(:b).join_table(:inner, :c) do |ta, jta, js|
|
431
|
+
# (:d.qualify(ta) > :e.qualify(jta)) & {:f.qualify(ta)=>DB.from(js.first.table).select(:g)}
|
432
|
+
# end
|
433
|
+
# # SELECT * FROM a NATURAL JOIN b INNER JOIN c
|
434
|
+
# # ON ((c.d > b.e) AND (c.f IN (SELECT g FROM b)))
|
414
435
|
def join_table(type, table, expr=nil, options={}, &block)
|
415
436
|
using_join = expr.is_a?(Array) && !expr.empty? && expr.all?{|x| x.is_a?(Symbol)}
|
416
437
|
if using_join && !supports_join_using?
|
@@ -591,7 +612,7 @@ module Sequel
|
|
591
612
|
@opts[:order] ? ds.order_more(*@opts[:order]) : ds
|
592
613
|
end
|
593
614
|
|
594
|
-
# Qualify to the given table, or first source if
|
615
|
+
# Qualify to the given table, or first source if no table is given.
|
595
616
|
#
|
596
617
|
# DB[:items].filter(:id=>1).qualify
|
597
618
|
# # SELECT items.* FROM items WHERE (items.id = 1)
|
@@ -718,7 +739,7 @@ module Sequel
|
|
718
739
|
end
|
719
740
|
|
720
741
|
# Set the server for this dataset to use. Used to pick a specific database
|
721
|
-
# shard to run a query against, or to override the default (
|
742
|
+
# shard to run a query against, or to override the default (where SELECT uses
|
722
743
|
# :read_only database and all other queries use the :default database). This
|
723
744
|
# method is always available but is only useful when database sharding is being
|
724
745
|
# used.
|
@@ -790,8 +811,8 @@ module Sequel
|
|
790
811
|
# :all :: Set to true to use UNION ALL instead of UNION, so duplicate rows can occur
|
791
812
|
# :from_self :: Set to false to not wrap the returned dataset in a from_self, use with care.
|
792
813
|
#
|
793
|
-
# DB[:items].union(DB[:other_items])
|
794
|
-
#
|
814
|
+
# DB[:items].union(DB[:other_items])
|
815
|
+
# # SELECT * FROM (SELECT * FROM items UNION SELECT * FROM other_items) AS t1
|
795
816
|
#
|
796
817
|
# DB[:items].union(DB[:other_items], :all=>true, :from_self=>false)
|
797
818
|
# # SELECT * FROM items UNION ALL SELECT * FROM other_items
|
data/lib/sequel/dataset/sql.rb
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
module Sequel
|
2
2
|
class Dataset
|
3
3
|
# ---------------------
|
4
|
-
# :section: User Methods relating to SQL Creation
|
4
|
+
# :section: 3 - User Methods relating to SQL Creation
|
5
5
|
# These are methods you can call to see what SQL will be generated by the dataset.
|
6
6
|
# ---------------------
|
7
7
|
|
@@ -163,7 +163,7 @@ module Sequel
|
|
163
163
|
end
|
164
164
|
|
165
165
|
# ---------------------
|
166
|
-
# :section: Internal Methods relating to SQL Creation
|
166
|
+
# :section: 9 - Internal Methods relating to SQL Creation
|
167
167
|
# These methods, while public, are not designed to be used directly by the end user.
|
168
168
|
# ---------------------
|
169
169
|
|
@@ -2,6 +2,7 @@
|
|
2
2
|
# the user to easily group schema changes and migrate the database
|
3
3
|
# to a newer version or revert to a previous version.
|
4
4
|
|
5
|
+
#
|
5
6
|
module Sequel
|
6
7
|
# Sequel's older migration class, available for backward compatibility.
|
7
8
|
# Uses subclasses with up and down instance methods for each migration:
|
data/lib/sequel/model.rb
CHANGED
@@ -49,10 +49,11 @@ module Sequel
|
|
49
49
|
# called directly on the class. Model datasets return rows as model instances,
|
50
50
|
# which have fairly standard ORM instance behavior.
|
51
51
|
#
|
52
|
-
# <tt>Sequel::Model</tt> is built completely out of plugins
|
53
|
-
#
|
54
|
-
#
|
55
|
-
#
|
52
|
+
# <tt>Sequel::Model</tt> is built completely out of plugins. Plugins can override any class,
|
53
|
+
# instance, or dataset method defined by a previous plugin and call super to get the default
|
54
|
+
# behavior. By default, <tt>Sequel::Model</tt> loads two plugins, <tt>Sequel::Model</tt>
|
55
|
+
# (which is itself a plugin) for the base support, and <tt>Sequel::Model::Associations</tt>
|
56
|
+
# for the associations support.
|
56
57
|
#
|
57
58
|
# You can set the +SEQUEL_NO_ASSOCIATIONS+ constant or environment variable to
|
58
59
|
# make Sequel not load the associations plugin by default.
|
@@ -472,161 +472,165 @@ module Sequel
|
|
472
472
|
# Associates a related model with the current model. The following types are
|
473
473
|
# supported:
|
474
474
|
#
|
475
|
-
#
|
476
|
-
#
|
477
|
-
#
|
478
|
-
#
|
479
|
-
#
|
480
|
-
#
|
481
|
-
#
|
482
|
-
#
|
483
|
-
#
|
484
|
-
#
|
485
|
-
#
|
486
|
-
#
|
487
|
-
#
|
488
|
-
#
|
489
|
-
#
|
490
|
-
#
|
491
|
-
#
|
475
|
+
# :many_to_one :: Foreign key in current model's table points to
|
476
|
+
# associated model's primary key. Each associated model object can
|
477
|
+
# be associated with more than one current model objects. Each current
|
478
|
+
# model object can be associated with only one associated model object.
|
479
|
+
# :one_to_many :: Foreign key in associated model's table points to this
|
480
|
+
# model's primary key. Each current model object can be associated with
|
481
|
+
# more than one associated model objects. Each associated model object
|
482
|
+
# can be associated with only one current model object.
|
483
|
+
# :one_to_one :: Similar to one_to_many in terms of foreign keys, but
|
484
|
+
# only one object is associated to the current object through the
|
485
|
+
# association. The methods created are similar to many_to_one, except
|
486
|
+
# that the one_to_one setter method saves the passed object.
|
487
|
+
# :many_to_many :: A join table is used that has a foreign key that points
|
488
|
+
# to this model's primary key and a foreign key that points to the
|
489
|
+
# associated model's primary key. Each current model object can be
|
490
|
+
# associated with many associated model objects, and each associated
|
491
|
+
# model object can be associated with many current model objects.
|
492
492
|
#
|
493
493
|
# The following options can be supplied:
|
494
|
-
#
|
495
|
-
#
|
496
|
-
#
|
497
|
-
#
|
498
|
-
#
|
499
|
-
#
|
500
|
-
#
|
501
|
-
#
|
502
|
-
#
|
503
|
-
#
|
504
|
-
#
|
505
|
-
#
|
506
|
-
#
|
507
|
-
#
|
508
|
-
#
|
509
|
-
#
|
510
|
-
#
|
511
|
-
#
|
512
|
-
#
|
513
|
-
#
|
514
|
-
#
|
515
|
-
#
|
516
|
-
#
|
517
|
-
#
|
518
|
-
#
|
519
|
-
#
|
520
|
-
#
|
521
|
-
#
|
522
|
-
#
|
523
|
-
#
|
524
|
-
#
|
525
|
-
#
|
526
|
-
#
|
527
|
-
#
|
528
|
-
#
|
529
|
-
#
|
530
|
-
#
|
531
|
-
#
|
532
|
-
#
|
533
|
-
#
|
534
|
-
#
|
535
|
-
#
|
536
|
-
#
|
537
|
-
#
|
538
|
-
#
|
539
|
-
#
|
540
|
-
#
|
541
|
-
#
|
542
|
-
#
|
543
|
-
#
|
544
|
-
#
|
545
|
-
#
|
546
|
-
#
|
547
|
-
#
|
548
|
-
#
|
549
|
-
#
|
550
|
-
#
|
551
|
-
#
|
552
|
-
#
|
553
|
-
#
|
554
|
-
#
|
555
|
-
#
|
556
|
-
#
|
557
|
-
#
|
558
|
-
#
|
559
|
-
#
|
560
|
-
#
|
561
|
-
#
|
562
|
-
#
|
563
|
-
#
|
564
|
-
#
|
565
|
-
#
|
566
|
-
#
|
567
|
-
#
|
568
|
-
#
|
569
|
-
#
|
570
|
-
#
|
571
|
-
#
|
572
|
-
#
|
573
|
-
#
|
574
|
-
#
|
575
|
-
#
|
576
|
-
#
|
577
|
-
#
|
578
|
-
#
|
579
|
-
#
|
580
|
-
#
|
581
|
-
#
|
582
|
-
#
|
583
|
-
#
|
584
|
-
#
|
585
|
-
#
|
586
|
-
#
|
587
|
-
#
|
588
|
-
#
|
589
|
-
#
|
590
|
-
#
|
591
|
-
#
|
592
|
-
#
|
593
|
-
#
|
594
|
-
#
|
595
|
-
#
|
596
|
-
#
|
597
|
-
#
|
598
|
-
#
|
599
|
-
#
|
600
|
-
#
|
601
|
-
#
|
602
|
-
#
|
603
|
-
#
|
604
|
-
#
|
605
|
-
#
|
606
|
-
#
|
607
|
-
#
|
608
|
-
#
|
609
|
-
#
|
610
|
-
#
|
611
|
-
#
|
612
|
-
#
|
613
|
-
#
|
614
|
-
#
|
615
|
-
#
|
616
|
-
#
|
617
|
-
#
|
618
|
-
#
|
619
|
-
#
|
620
|
-
#
|
621
|
-
#
|
622
|
-
#
|
623
|
-
#
|
624
|
-
#
|
625
|
-
#
|
626
|
-
#
|
627
|
-
#
|
628
|
-
#
|
629
|
-
#
|
494
|
+
# === All Types
|
495
|
+
# :after_add :: Symbol, Proc, or array of both/either specifying a callback to call
|
496
|
+
# after a new item is added to the association.
|
497
|
+
# :after_load :: Symbol, Proc, or array of both/either specifying a callback to call
|
498
|
+
# after the associated record(s) have been retrieved from the database. Not called
|
499
|
+
# when eager loading via eager_graph, but called when eager loading via eager.
|
500
|
+
# :after_remove :: Symbol, Proc, or array of both/either specifying a callback to call
|
501
|
+
# after an item is removed from the association.
|
502
|
+
# :after_set :: Symbol, Proc, or array of both/either specifying a callback to call
|
503
|
+
# after an item is set using the association setter method.
|
504
|
+
# :allow_eager :: If set to false, you cannot load the association eagerly
|
505
|
+
# via eager or eager_graph
|
506
|
+
# :before_add :: Symbol, Proc, or array of both/either specifying a callback to call
|
507
|
+
# before a new item is added to the association.
|
508
|
+
# :before_remove :: Symbol, Proc, or array of both/either specifying a callback to call
|
509
|
+
# before an item is removed from the association.
|
510
|
+
# :before_set :: Symbol, Proc, or array of both/either specifying a callback to call
|
511
|
+
# before an item is set using the association setter method.
|
512
|
+
# :cartesian_product_number :: the number of joins completed by this association that could cause more
|
513
|
+
# than one row for each row in the current table (default: 0 for
|
514
|
+
# many_to_one and one_to_one associations, 1 for one_to_many and
|
515
|
+
# many_to_many associations).
|
516
|
+
# :class :: The associated class or its name. If not
|
517
|
+
# given, uses the association's name, which is camelized (and
|
518
|
+
# singularized unless the type is :many_to_one or :one_to_one)
|
519
|
+
# :clone :: Merge the current options and block into the options and block used in defining
|
520
|
+
# the given association. Can be used to DRY up a bunch of similar associations that
|
521
|
+
# all share the same options such as :class and :key, while changing the order and block used.
|
522
|
+
# :conditions :: The conditions to use to filter the association, can be any argument passed to filter.
|
523
|
+
# :dataset :: A proc that is instance_evaled to get the base dataset
|
524
|
+
# to use for the _dataset method (before the other options are applied).
|
525
|
+
# :distinct :: Use the DISTINCT clause when selecting associating object, both when
|
526
|
+
# lazy loading and eager loading via .eager (but not when using .eager_graph).
|
527
|
+
# :eager :: The associations to eagerly load via +eager+ when loading the associated object(s).
|
528
|
+
# :eager_block :: If given, use the block instead of the default block when
|
529
|
+
# eagerly loading. To not use a block when eager loading (when one is used normally),
|
530
|
+
# set to nil.
|
531
|
+
# :eager_graph :: The associations to eagerly load via +eager_graph+ when loading the associated object(s).
|
532
|
+
# many_to_many associations with this option cannot be eagerly loaded via +eager+.
|
533
|
+
# :eager_grapher :: A proc to use to implement eager loading via +eager_graph+, overriding the default.
|
534
|
+
# Takes one or three arguments. If three arguments, they are a dataset, an alias to use for
|
535
|
+
# the table to graph for this association, and the alias that was used for the current table
|
536
|
+
# (since you can cascade associations). If one argument, is passed a hash with keys :self,
|
537
|
+
# :table_alias, and :implicit_qualifier, corresponding to the three arguments, and an optional
|
538
|
+
# additional key :eager_block, a callback accepting one argument, the associated dataset. This
|
539
|
+
# is used to customize the association at query time.
|
540
|
+
# Should return a copy of the dataset with the association graphed into it.
|
541
|
+
# :eager_loader :: A proc to use to implement eager loading, overriding the default. Takes one or three arguments.
|
542
|
+
# If three arguments, the first should be a key hash (used solely to enhance performance), the
|
543
|
+
# second an array of records, and the third a hash of dependent associations. If one argument, is
|
544
|
+
# passed a hash with keys :key_hash, :rows, and :associations, corresponding to the three
|
545
|
+
# arguments, and an additional key :self, which is the dataset doing the eager loading. In the
|
546
|
+
# proc, the associated records should be queried from the database and the associations cache for
|
547
|
+
# each record should be populated.
|
548
|
+
# :eager_loader_key :: A symbol for the key column to use to populate the key hash
|
549
|
+
# for the eager loader.
|
550
|
+
# :extend :: A module or array of modules to extend the dataset with.
|
551
|
+
# :graph_block :: The block to pass to join_table when eagerly loading
|
552
|
+
# the association via +eager_graph+.
|
553
|
+
# :graph_conditions :: The additional conditions to use on the SQL join when eagerly loading
|
554
|
+
# the association via +eager_graph+. Should be a hash or an array of two element arrays. If not
|
555
|
+
# specified, the :conditions option is used if it is a hash or array of two element arrays.
|
556
|
+
# :graph_join_type :: The type of SQL join to use when eagerly loading the association via
|
557
|
+
# eager_graph. Defaults to :left_outer.
|
558
|
+
# :graph_only_conditions :: The conditions to use on the SQL join when eagerly loading
|
559
|
+
# the association via +eager_graph+, instead of the default conditions specified by the
|
560
|
+
# foreign/primary keys. This option causes the :graph_conditions option to be ignored.
|
561
|
+
# :graph_select :: A column or array of columns to select from the associated table
|
562
|
+
# when eagerly loading the association via +eager_graph+. Defaults to all
|
563
|
+
# columns in the associated table.
|
564
|
+
# :limit :: Limit the number of records to the provided value. Use
|
565
|
+
# an array with two elements for the value to specify a
|
566
|
+
# limit (first element) and an offset (second element).
|
567
|
+
# :methods_module :: The module that methods the association creates will be placed into. Defaults
|
568
|
+
# to the module containing the model's columns.
|
569
|
+
# :order :: the column(s) by which to order the association dataset. Can be a
|
570
|
+
# singular column symbol or an array of column symbols.
|
571
|
+
# :order_eager_graph :: Whether to add the association's order to the graphed dataset's order when graphing
|
572
|
+
# via +eager_graph+. Defaults to true, so set to false to disable.
|
573
|
+
# :read_only :: Do not add a setter method (for many_to_one or one_to_one associations),
|
574
|
+
# or add_/remove_/remove_all_ methods (for one_to_many and many_to_many associations).
|
575
|
+
# :reciprocal :: the symbol name of the reciprocal association,
|
576
|
+
# if it exists. By default, Sequel will try to determine it by looking at the
|
577
|
+
# associated model's assocations for a association that matches
|
578
|
+
# the current association's key(s). Set to nil to not use a reciprocal.
|
579
|
+
# :select :: the columns to select. Defaults to the associated class's
|
580
|
+
# table_name.* in a many_to_many association, which means it doesn't include the attributes from the
|
581
|
+
# join table. If you want to include the join table attributes, you can
|
582
|
+
# use this option, but beware that the join table attributes can clash with
|
583
|
+
# attributes from the model table, so you should alias any attributes that have
|
584
|
+
# the same name in both the join table and the associated table.
|
585
|
+
# :validate :: Set to false to not validate when implicitly saving any associated object.
|
586
|
+
# === :many_to_one
|
587
|
+
# :key :: foreign key in current model's table that references
|
588
|
+
# associated model's primary key, as a symbol. Defaults to :"#{name}_id". Can use an
|
589
|
+
# array of symbols for a composite key association.
|
590
|
+
# :primary_key :: column in the associated table that :key option references, as a symbol.
|
591
|
+
# Defaults to the primary key of the associated table. Can use an
|
592
|
+
# array of symbols for a composite key association.
|
593
|
+
# === :one_to_many and :one_to_one
|
594
|
+
# :key :: foreign key in associated model's table that references
|
595
|
+
# current model's primary key, as a symbol. Defaults to
|
596
|
+
# :"#{self.name.underscore}_id". Can use an
|
597
|
+
# array of symbols for a composite key association.
|
598
|
+
# :primary_key :: column in the current table that :key option references, as a symbol.
|
599
|
+
# Defaults to primary key of the current table. Can use an
|
600
|
+
# array of symbols for a composite key association.
|
601
|
+
# === :many_to_many
|
602
|
+
# :graph_join_table_block :: The block to pass to +join_table+ for
|
603
|
+
# the join table when eagerly loading the association via +eager_graph+.
|
604
|
+
# :graph_join_table_conditions :: The additional conditions to use on the SQL join for
|
605
|
+
# the join table when eagerly loading the association via +eager_graph+.
|
606
|
+
# Should be a hash or an array of two element arrays.
|
607
|
+
# :graph_join_table_join_type :: The type of SQL join to use for the join table when eagerly
|
608
|
+
# loading the association via +eager_graph+. Defaults to the
|
609
|
+
# :graph_join_type option or :left_outer.
|
610
|
+
# :graph_join_table_only_conditions :: The conditions to use on the SQL join for the join
|
611
|
+
# table when eagerly loading the association via +eager_graph+,
|
612
|
+
# instead of the default conditions specified by the
|
613
|
+
# foreign/primary keys. This option causes the
|
614
|
+
# :graph_join_table_conditions option to be ignored.
|
615
|
+
# :join_table :: name of table that includes the foreign keys to both
|
616
|
+
# the current model and the associated model, as a symbol. Defaults to the name
|
617
|
+
# of current model and name of associated model, pluralized,
|
618
|
+
# underscored, sorted, and joined with '_'.
|
619
|
+
# :join_table_block :: proc that can be used to modify the dataset used in the add/remove/remove_all
|
620
|
+
# methods. Should accept a dataset argument and return a modified dataset if present.
|
621
|
+
# :left_key :: foreign key in join table that points to current model's
|
622
|
+
# primary key, as a symbol. Defaults to :"#{self.name.underscore}_id".
|
623
|
+
# Can use an array of symbols for a composite key association.
|
624
|
+
# :left_primary_key :: column in current table that :left_key points to, as a symbol.
|
625
|
+
# Defaults to primary key of current table. Can use an
|
626
|
+
# array of symbols for a composite key association.
|
627
|
+
# :right_key :: foreign key in join table that points to associated
|
628
|
+
# model's primary key, as a symbol. Defaults to Defaults to :"#{name.to_s.singularize}_id".
|
629
|
+
# Can use an array of symbols for a composite key association.
|
630
|
+
# :right_primary_key :: column in associated table that :right_key points to, as a symbol.
|
631
|
+
# Defaults to primary key of the associated table. Can use an
|
632
|
+
# array of symbols for a composite key association.
|
633
|
+
# :uniq :: Adds a after_load callback that makes the array of objects unique.
|
630
634
|
def associate(type, name, opts = {}, &block)
|
631
635
|
raise(Error, 'one_to_many association type with :one_to_one option removed, used one_to_one association type') if opts[:one_to_one] && type == :one_to_many
|
632
636
|
raise(Error, 'invalid association type') unless assoc_class = ASSOCIATION_TYPES[type]
|
@@ -681,13 +685,13 @@ module Sequel
|
|
681
685
|
ds = ds.eager(opts[:eager]) if opts[:eager]
|
682
686
|
ds = ds.distinct if opts[:distinct]
|
683
687
|
if opts[:eager_graph]
|
688
|
+
raise(Error, "cannot eagerly load a #{opts[:type]} association that uses :eager_graph") if opts.eager_loading_use_associated_key?
|
684
689
|
ds = ds.eager_graph(opts[:eager_graph])
|
685
|
-
ds = ds.add_graph_aliases(opts.associated_key_alias=>[opts.associated_class.table_name, opts.associated_key_alias, SQL::QualifiedIdentifier.new(opts.associated_key_table, opts.associated_key_column)]) if opts.eager_loading_use_associated_key?
|
686
690
|
end
|
687
691
|
ds = ds.eager(associations) unless Array(associations).empty?
|
688
692
|
ds = opts[:eager_block].call(ds) if opts[:eager_block]
|
689
693
|
ds = eager_options[:eager_block].call(ds) if eager_options[:eager_block]
|
690
|
-
if
|
694
|
+
if opts.eager_loading_use_associated_key?
|
691
695
|
ds = if opts[:uses_left_composite_keys]
|
692
696
|
t = opts.associated_key_table
|
693
697
|
ds.select_append(*opts.associated_key_alias.zip(opts.associated_key_column).map{|a, c| SQL::AliasedExpression.new(SQL::QualifiedIdentifier.new(t, c), a)})
|
@@ -704,22 +708,22 @@ module Sequel
|
|
704
708
|
subclass.instance_variable_set(:@association_reflections, @association_reflections.dup)
|
705
709
|
end
|
706
710
|
|
707
|
-
# Shortcut for adding a many_to_many association, see associate
|
711
|
+
# Shortcut for adding a many_to_many association, see #associate
|
708
712
|
def many_to_many(name, opts={}, &block)
|
709
713
|
associate(:many_to_many, name, opts, &block)
|
710
714
|
end
|
711
715
|
|
712
|
-
# Shortcut for adding a many_to_one association, see associate
|
716
|
+
# Shortcut for adding a many_to_one association, see #associate
|
713
717
|
def many_to_one(name, opts={}, &block)
|
714
718
|
associate(:many_to_one, name, opts, &block)
|
715
719
|
end
|
716
720
|
|
717
|
-
# Shortcut for adding a one_to_many association, see associate
|
721
|
+
# Shortcut for adding a one_to_many association, see #associate
|
718
722
|
def one_to_many(name, opts={}, &block)
|
719
723
|
associate(:one_to_many, name, opts, &block)
|
720
724
|
end
|
721
725
|
|
722
|
-
# Shortcut for adding a one_to_one association, see associate.
|
726
|
+
# Shortcut for adding a one_to_one association, see #associate.
|
723
727
|
def one_to_one(name, opts={}, &block)
|
724
728
|
associate(:one_to_one, name, opts, &block)
|
725
729
|
end
|
@@ -1285,7 +1289,7 @@ module Sequel
|
|
1285
1289
|
# time, as it loads associated records using one query per association. However,
|
1286
1290
|
# it does not allow you the ability to filter or order based on columns in associated tables. +eager_graph+ loads
|
1287
1291
|
# all records in a single query using JOINs, allowing you to filter or order based on columns in associated
|
1288
|
-
# tables. However, +eager_graph+
|
1292
|
+
# tables. However, +eager_graph+ is usually slower than +eager+, especially if multiple
|
1289
1293
|
# one_to_many or many_to_many associations are joined.
|
1290
1294
|
#
|
1291
1295
|
# You can cascade the eager loading (loading associations on associated objects)
|
@@ -1330,6 +1334,56 @@ module Sequel
|
|
1330
1334
|
obj.def_mutation_method(:eager, :eager_graph)
|
1331
1335
|
end
|
1332
1336
|
|
1337
|
+
# If the expression is in the form <tt>x = y</tt> where +y+ is a <tt>Sequel::Model</tt>
|
1338
|
+
# instance, array of <tt>Sequel::Model</tt> instances, or a <tt>Sequel::Model</tt> dataset,
|
1339
|
+
# assume +x+ is an association symbol and look up the association reflection
|
1340
|
+
# via the dataset's model. From there, return the appropriate SQL based on the type of
|
1341
|
+
# association and the values of the foreign/primary keys of +y+. For most association
|
1342
|
+
# types, this is a simple transformation, but for +many_to_many+ associations this
|
1343
|
+
# creates a subquery to the join table.
|
1344
|
+
def complex_expression_sql(op, args)
|
1345
|
+
r = args.at(1)
|
1346
|
+
if (((op == :'=' || op == :'!=') and r.is_a?(Sequel::Model)) ||
|
1347
|
+
(multiple = ((op == :IN || op == :'NOT IN') and ((is_ds = r.is_a?(Sequel::Dataset)) or r.all?{|x| x.is_a?(Sequel::Model)}))))
|
1348
|
+
l = args.at(0)
|
1349
|
+
if ar = model.association_reflections[l]
|
1350
|
+
if multiple
|
1351
|
+
klass = ar.associated_class
|
1352
|
+
if is_ds
|
1353
|
+
if r.respond_to?(:model)
|
1354
|
+
unless r.model <= klass
|
1355
|
+
# A dataset for a different model class, could be a valid regular query
|
1356
|
+
return super
|
1357
|
+
end
|
1358
|
+
else
|
1359
|
+
# Not a model dataset, could be a valid regular query
|
1360
|
+
return super
|
1361
|
+
end
|
1362
|
+
else
|
1363
|
+
unless r.all?{|x| x.is_a?(klass)}
|
1364
|
+
raise Sequel::Error, "invalid association class for one object for association #{l.inspect} used in dataset filter for model #{model.inspect}, expected class #{klass.inspect}"
|
1365
|
+
end
|
1366
|
+
end
|
1367
|
+
elsif !r.is_a?(ar.associated_class)
|
1368
|
+
raise Sequel::Error, "invalid association class #{r.class.inspect} for association #{l.inspect} used in dataset filter for model #{model.inspect}, expected class #{ar.associated_class.inspect}"
|
1369
|
+
end
|
1370
|
+
|
1371
|
+
if exp = association_filter_expression(op, ar, r)
|
1372
|
+
literal(exp)
|
1373
|
+
else
|
1374
|
+
raise Sequel::Error, "invalid association type #{ar[:type].inspect} for association #{l.inspect} used in dataset filter for model #{model.inspect}"
|
1375
|
+
end
|
1376
|
+
elsif multiple && (is_ds || r.empty?)
|
1377
|
+
# Not a query designed for this support, could be a valid regular query
|
1378
|
+
super
|
1379
|
+
else
|
1380
|
+
raise Sequel::Error, "invalid association #{l.inspect} used in dataset filter for model #{model.inspect}"
|
1381
|
+
end
|
1382
|
+
else
|
1383
|
+
super
|
1384
|
+
end
|
1385
|
+
end
|
1386
|
+
|
1333
1387
|
# The preferred eager loading method. Loads all associated records using one
|
1334
1388
|
# query for each association.
|
1335
1389
|
#
|
@@ -1376,9 +1430,9 @@ module Sequel
|
|
1376
1430
|
# The secondary eager loading method. Loads all associations in a single query. This
|
1377
1431
|
# method should only be used if you need to filter or order based on columns in associated tables.
|
1378
1432
|
#
|
1379
|
-
# This method
|
1380
|
-
#
|
1381
|
-
# of model objects.
|
1433
|
+
# This method uses <tt>Dataset#graph</tt> to create appropriate aliases for columns in all the
|
1434
|
+
# tables. Then it uses the graph's metadata to build the associations from the single hash, and
|
1435
|
+
# finally replaces the array of hashes with an array model objects inside all.
|
1382
1436
|
#
|
1383
1437
|
# Be very careful when using this with multiple one_to_many or many_to_many associations, as you can
|
1384
1438
|
# create large cartesian products. If you must graph multiple one_to_many and many_to_many associations,
|
@@ -1392,8 +1446,7 @@ module Sequel
|
|
1392
1446
|
# all objects. You can use the :graph_* association options to modify the SQL query.
|
1393
1447
|
#
|
1394
1448
|
# Like +eager+, you need to call +all+ on the dataset for the eager loading to work. If you just
|
1395
|
-
# call +each+,
|
1396
|
-
# model object values).
|
1449
|
+
# call +each+, it will yield plain hashes, each containing all columns from all the tables.
|
1397
1450
|
def eager_graph(*associations)
|
1398
1451
|
ds = if @opts[:eager_graph]
|
1399
1452
|
self
|
@@ -1408,55 +1461,6 @@ module Sequel
|
|
1408
1461
|
ds.eager_graph_associations(ds, model, ds.opts[:eager_graph][:master], [], *associations)
|
1409
1462
|
end
|
1410
1463
|
|
1411
|
-
# If the expression is in the form <tt>x = y</tt> where +y+ is a <tt>Sequel::Model</tt>
|
1412
|
-
# instance, assume +x+ is an association symbol and look up the association reflection
|
1413
|
-
# via the dataset's model. From there, return the appropriate SQL based on the type of
|
1414
|
-
# association and the values of the foreign/primary keys of +y+. For most association
|
1415
|
-
# types, this is a simple transformation, but for +many_to_many+ associations this
|
1416
|
-
# creates a subquery to the join table.
|
1417
|
-
def complex_expression_sql(op, args)
|
1418
|
-
r = args.at(1)
|
1419
|
-
if (((op == :'=' || op == :'!=') and r.is_a?(Sequel::Model)) ||
|
1420
|
-
(multiple = ((op == :IN || op == :'NOT IN') and ((is_ds = r.is_a?(Sequel::Dataset)) or r.all?{|x| x.is_a?(Sequel::Model)}))))
|
1421
|
-
l = args.at(0)
|
1422
|
-
if ar = model.association_reflections[l]
|
1423
|
-
if multiple
|
1424
|
-
klass = ar.associated_class
|
1425
|
-
if is_ds
|
1426
|
-
if r.respond_to?(:model)
|
1427
|
-
unless r.model <= klass
|
1428
|
-
# A dataset for a different model class, could be a valid regular query
|
1429
|
-
return super
|
1430
|
-
end
|
1431
|
-
else
|
1432
|
-
# Not a model dataset, could be a valid regular query
|
1433
|
-
return super
|
1434
|
-
end
|
1435
|
-
else
|
1436
|
-
unless r.all?{|x| x.is_a?(klass)}
|
1437
|
-
raise Sequel::Error, "invalid association class for one object for association #{l.inspect} used in dataset filter for model #{model.inspect}, expected class #{klass.inspect}"
|
1438
|
-
end
|
1439
|
-
end
|
1440
|
-
elsif !r.is_a?(ar.associated_class)
|
1441
|
-
raise Sequel::Error, "invalid association class #{r.class.inspect} for association #{l.inspect} used in dataset filter for model #{model.inspect}, expected class #{ar.associated_class.inspect}"
|
1442
|
-
end
|
1443
|
-
|
1444
|
-
if exp = association_filter_expression(op, ar, r)
|
1445
|
-
literal(exp)
|
1446
|
-
else
|
1447
|
-
raise Sequel::Error, "invalid association type #{ar[:type].inspect} for association #{l.inspect} used in dataset filter for model #{model.inspect}"
|
1448
|
-
end
|
1449
|
-
elsif multiple && (is_ds || r.empty?)
|
1450
|
-
# Not a query designed for this support, could be a valid regular query
|
1451
|
-
super
|
1452
|
-
else
|
1453
|
-
raise Sequel::Error, "invalid association #{l.inspect} used in dataset filter for model #{model.inspect}"
|
1454
|
-
end
|
1455
|
-
else
|
1456
|
-
super
|
1457
|
-
end
|
1458
|
-
end
|
1459
|
-
|
1460
1464
|
# Do not attempt to split the result set into associations,
|
1461
1465
|
# just return results as simple objects. This is useful if you
|
1462
1466
|
# want to use eager_graph as a shortcut to have all of the joins
|
@@ -1506,7 +1510,7 @@ module Sequel
|
|
1506
1510
|
ds = ds.eager_graph_associations(ds, r.associated_class, assoc_table_alias, requirements + [assoc_table_alias], *associations) unless associations.empty?
|
1507
1511
|
ds
|
1508
1512
|
end
|
1509
|
-
|
1513
|
+
|
1510
1514
|
# Check the associations are valid for the given model.
|
1511
1515
|
# Call eager_graph_association on each association.
|
1512
1516
|
#
|
@@ -1533,67 +1537,10 @@ module Sequel
|
|
1533
1537
|
ds
|
1534
1538
|
end
|
1535
1539
|
|
1536
|
-
#
|
1537
|
-
|
1538
|
-
|
1539
|
-
|
1540
|
-
requirements = eager_graph[:requirements]
|
1541
|
-
alias_map = eager_graph[:alias_association_name_map]
|
1542
|
-
type_map = eager_graph[:alias_association_type_map]
|
1543
|
-
reciprocal_map = eager_graph[:reciprocals]
|
1544
|
-
|
1545
|
-
# Make dependency map hash out of requirements array for each association.
|
1546
|
-
# This builds a tree of dependencies that will be used for recursion
|
1547
|
-
# to ensure that all parts of the object graph are loaded into the
|
1548
|
-
# appropriate subordinate association.
|
1549
|
-
dependency_map = {}
|
1550
|
-
# Sort the associations by requirements length, so that
|
1551
|
-
# requirements are added to the dependency hash before their
|
1552
|
-
# dependencies.
|
1553
|
-
requirements.sort_by{|a| a[1].length}.each do |ta, deps|
|
1554
|
-
if deps.empty?
|
1555
|
-
dependency_map[ta] = {}
|
1556
|
-
else
|
1557
|
-
deps = deps.dup
|
1558
|
-
hash = dependency_map[deps.shift]
|
1559
|
-
deps.each do |dep|
|
1560
|
-
hash = hash[dep]
|
1561
|
-
end
|
1562
|
-
hash[ta] = {}
|
1563
|
-
end
|
1564
|
-
end
|
1565
|
-
|
1566
|
-
# This mapping is used to make sure that duplicate entries in the
|
1567
|
-
# result set are mapped to a single record. For example, using a
|
1568
|
-
# single one_to_many association with 10 associated records,
|
1569
|
-
# the main object will appear in the object graph 10 times.
|
1570
|
-
# We map by primary key, if available, or by the object's entire values,
|
1571
|
-
# if not. The mapping must be per table, so create sub maps for each table
|
1572
|
-
# alias.
|
1573
|
-
records_map = {master=>{}}
|
1574
|
-
alias_map.keys.each{|ta| records_map[ta] = {}}
|
1575
|
-
|
1576
|
-
# This will hold the final record set that we will be replacing the object graph with.
|
1577
|
-
records = []
|
1578
|
-
record_graphs.each do |record_graph|
|
1579
|
-
primary_record = record_graph[master]
|
1580
|
-
key = primary_record.pk_or_nil || primary_record.values.sort_by{|x| x[0].to_s}
|
1581
|
-
if cached_pr = records_map[master][key]
|
1582
|
-
primary_record = cached_pr
|
1583
|
-
else
|
1584
|
-
records_map[master][key] = primary_record
|
1585
|
-
# Only add it to the list of records to return if it is a new record
|
1586
|
-
records.push(primary_record)
|
1587
|
-
end
|
1588
|
-
# Build all associations for the current object and it's dependencies
|
1589
|
-
eager_graph_build_associations_graph(dependency_map, alias_map, type_map, reciprocal_map, records_map, primary_record, record_graph)
|
1590
|
-
end
|
1591
|
-
|
1592
|
-
# Remove duplicate records from all associations if this graph could possibly be a cartesian product
|
1593
|
-
eager_graph_make_associations_unique(records, dependency_map, alias_map, type_map) if eager_graph[:cartesian_product_number] > 1
|
1594
|
-
|
1595
|
-
# Replace the array of object graphs with an array of model objects
|
1596
|
-
record_graphs.replace(records)
|
1540
|
+
# Replace the array of plain hashes with an array of model objects will all eager_graphed
|
1541
|
+
# associations set in the associations cache for each object.
|
1542
|
+
def eager_graph_build_associations(hashes)
|
1543
|
+
hashes.replace(EagerGraphLoader.new(self).load(hashes))
|
1597
1544
|
end
|
1598
1545
|
|
1599
1546
|
private
|
@@ -1648,57 +1595,6 @@ module Sequel
|
|
1648
1595
|
reflection
|
1649
1596
|
end
|
1650
1597
|
|
1651
|
-
# Build associations for the current object. This is called recursively
|
1652
|
-
# to build all dependencies.
|
1653
|
-
def eager_graph_build_associations_graph(dependency_map, alias_map, type_map, reciprocal_map, records_map, current, record_graph)
|
1654
|
-
return if dependency_map.empty?
|
1655
|
-
# Don't clobber the cached association value for one_to_many and many_to_many associations if it has already been setup
|
1656
|
-
dependency_map.keys.each do |ta|
|
1657
|
-
assoc_name = alias_map[ta]
|
1658
|
-
current.associations[assoc_name] = type_map[ta] ? [] : nil unless current.associations.include?(assoc_name)
|
1659
|
-
end
|
1660
|
-
dependency_map.each do |ta, deps|
|
1661
|
-
next unless rec = record_graph[ta]
|
1662
|
-
key = rec.pk_or_nil || rec.values.sort_by{|x| x[0].to_s}
|
1663
|
-
if cached_rec = records_map[ta][key]
|
1664
|
-
rec = cached_rec
|
1665
|
-
else
|
1666
|
-
records_map[ta][key] = rec
|
1667
|
-
end
|
1668
|
-
assoc_name = alias_map[ta]
|
1669
|
-
if type_map[ta]
|
1670
|
-
current.associations[assoc_name].push(rec)
|
1671
|
-
if reciprocal = reciprocal_map[ta]
|
1672
|
-
rec.associations[reciprocal] = current
|
1673
|
-
end
|
1674
|
-
else
|
1675
|
-
current.associations[assoc_name] = rec
|
1676
|
-
end
|
1677
|
-
# Recurse into dependencies of the current object
|
1678
|
-
eager_graph_build_associations_graph(deps, alias_map, type_map, reciprocal_map, records_map, rec, record_graph)
|
1679
|
-
end
|
1680
|
-
end
|
1681
|
-
|
1682
|
-
# If the result set is the result of a cartesian product, then it is possible that
|
1683
|
-
# there are multiple records for each association when there should only be one.
|
1684
|
-
# In that case, for each object in all associations loaded via +eager_graph+, run
|
1685
|
-
# uniq! on the association to make sure no duplicate records show up.
|
1686
|
-
# Note that this can cause legitimate duplicate records to be removed.
|
1687
|
-
def eager_graph_make_associations_unique(records, dependency_map, alias_map, type_map)
|
1688
|
-
records.each do |record|
|
1689
|
-
dependency_map.each do |ta, deps|
|
1690
|
-
list = record.send(alias_map[ta])
|
1691
|
-
list = if type_map[ta]
|
1692
|
-
list.uniq!
|
1693
|
-
else
|
1694
|
-
[list] if list
|
1695
|
-
end
|
1696
|
-
# Recurse into dependencies
|
1697
|
-
eager_graph_make_associations_unique(list, deps, alias_map, type_map) if list
|
1698
|
-
end
|
1699
|
-
end
|
1700
|
-
end
|
1701
|
-
|
1702
1598
|
# Eagerly load all specified associations
|
1703
1599
|
def eager_load(a, eager_assoc=@opts[:eager])
|
1704
1600
|
return if a.empty?
|
@@ -1742,7 +1638,12 @@ module Sequel
|
|
1742
1638
|
a.each{|object| object.send(:run_association_callbacks, r, :after_load, object.associations[r[:name]])} unless r[:after_load].empty?
|
1743
1639
|
end
|
1744
1640
|
end
|
1745
|
-
|
1641
|
+
|
1642
|
+
# Return plain hashes instead of calling the row_proc if eager_graph is being used.
|
1643
|
+
def graph_each(&block)
|
1644
|
+
@opts[:eager_graph] ? fetch_rows(select_sql, &block) : super
|
1645
|
+
end
|
1646
|
+
|
1746
1647
|
# Return a subquery expression for filering by a many_to_many association
|
1747
1648
|
def many_to_many_association_filter_expression(op, ref, obj)
|
1748
1649
|
lpks, lks, rks = ref.values_at(:left_primary_keys, :left_keys, :right_keys)
|
@@ -1776,6 +1677,264 @@ module Sequel
|
|
1776
1677
|
super
|
1777
1678
|
end
|
1778
1679
|
end
|
1680
|
+
|
1681
|
+
# This class is the internal implementation of eager_graph. It is responsible for taking an array of plain
|
1682
|
+
# hashes and returning an array of model objects with all eager_graphed associations already set in the
|
1683
|
+
# association cache.
|
1684
|
+
class EagerGraphLoader
|
1685
|
+
# Hash with table alias symbol keys and association name values
|
1686
|
+
attr_reader :alias_map
|
1687
|
+
|
1688
|
+
# Hash with table alias symbol keys and subhash values mapping column_alias symbols to the
|
1689
|
+
# symbol of the real name of the column
|
1690
|
+
attr_reader :column_maps
|
1691
|
+
|
1692
|
+
# Recursive hash with table alias symbol keys mapping to hashes with dependent table alias symbol keys.
|
1693
|
+
attr_reader :dependency_map
|
1694
|
+
|
1695
|
+
# The table alias symbol for the primary model
|
1696
|
+
attr_reader :master
|
1697
|
+
|
1698
|
+
# Hash with table alias symbol keys and primary key symbol values (or arrays of primary key symbols for
|
1699
|
+
# composite key tables)
|
1700
|
+
attr_reader :primary_keys
|
1701
|
+
|
1702
|
+
# Hash with table alias symbol keys and reciprocal association symbol values,
|
1703
|
+
# used for setting reciprocals for one_to_many associations.
|
1704
|
+
attr_reader :reciprocal_map
|
1705
|
+
|
1706
|
+
# Hash with table alias symbol keys and subhash values mapping primary key symbols (or array of symbols)
|
1707
|
+
# to model instances. Used so that only a single model instance is created for each object.
|
1708
|
+
attr_reader :records_map
|
1709
|
+
|
1710
|
+
# Hash with table alias symbol keys and callable values used to create model instances
|
1711
|
+
attr_reader :row_procs
|
1712
|
+
|
1713
|
+
# Hash with table alias symbol keys and true/false values, where true means the
|
1714
|
+
# association represented by the table alias uses an array of values instead of
|
1715
|
+
# a single value (i.e. true => *_many, false => *_to_one).
|
1716
|
+
attr_reader :type_map
|
1717
|
+
|
1718
|
+
# Initialize all of the data structures used during loading.
|
1719
|
+
def initialize(dataset)
|
1720
|
+
opts = dataset.opts
|
1721
|
+
eager_graph = opts[:eager_graph]
|
1722
|
+
@master = eager_graph[:master]
|
1723
|
+
requirements = eager_graph[:requirements]
|
1724
|
+
alias_map = @alias_map = eager_graph[:alias_association_name_map]
|
1725
|
+
type_map = @type_map = eager_graph[:alias_association_type_map]
|
1726
|
+
reciprocal_map = @reciprocal_map = eager_graph[:reciprocals]
|
1727
|
+
@unique = eager_graph[:cartesian_product_number] > 1
|
1728
|
+
|
1729
|
+
# Make dependency map hash out of requirements array for each association.
|
1730
|
+
# This builds a tree of dependencies that will be used for recursion
|
1731
|
+
# to ensure that all parts of the object graph are loaded into the
|
1732
|
+
# appropriate subordinate association.
|
1733
|
+
@dependency_map = {}
|
1734
|
+
# Sort the associations by requirements length, so that
|
1735
|
+
# requirements are added to the dependency hash before their
|
1736
|
+
# dependencies.
|
1737
|
+
requirements.sort_by{|a| a[1].length}.each do |ta, deps|
|
1738
|
+
if deps.empty?
|
1739
|
+
dependency_map[ta] = {}
|
1740
|
+
else
|
1741
|
+
deps = deps.dup
|
1742
|
+
hash = dependency_map[deps.shift]
|
1743
|
+
deps.each do |dep|
|
1744
|
+
hash = hash[dep]
|
1745
|
+
end
|
1746
|
+
hash[ta] = {}
|
1747
|
+
end
|
1748
|
+
end
|
1749
|
+
|
1750
|
+
# This mapping is used to make sure that duplicate entries in the
|
1751
|
+
# result set are mapped to a single record. For example, using a
|
1752
|
+
# single one_to_many association with 10 associated records,
|
1753
|
+
# the main object column values appear in the object graph 10 times.
|
1754
|
+
# We map by primary key, if available, or by the object's entire values,
|
1755
|
+
# if not. The mapping must be per table, so create sub maps for each table
|
1756
|
+
# alias.
|
1757
|
+
records_map = {@master=>{}}
|
1758
|
+
alias_map.keys.each{|ta| records_map[ta] = {}}
|
1759
|
+
@records_map = records_map
|
1760
|
+
|
1761
|
+
datasets = opts[:graph][:table_aliases].to_a.reject{|ta,ds| ds.nil?}
|
1762
|
+
column_aliases = opts[:graph_aliases] || opts[:graph][:column_aliases]
|
1763
|
+
primary_keys = {}
|
1764
|
+
column_maps = {}
|
1765
|
+
models = {}
|
1766
|
+
row_procs = {}
|
1767
|
+
datasets.each do |ta, ds|
|
1768
|
+
models[ta] = ds.model
|
1769
|
+
primary_keys[ta] = []
|
1770
|
+
column_maps[ta] = {}
|
1771
|
+
row_procs[ta] = ds.row_proc
|
1772
|
+
end
|
1773
|
+
column_aliases.each do |col_alias, tc|
|
1774
|
+
ta, column = tc
|
1775
|
+
column_maps[ta][col_alias] = column
|
1776
|
+
end
|
1777
|
+
column_maps.each do |ta, h|
|
1778
|
+
pk = models[ta].primary_key
|
1779
|
+
if pk.is_a?(Array)
|
1780
|
+
primary_keys[ta] = []
|
1781
|
+
h.select{|ca, c| primary_keys[ta] << ca if pk.include?(c)}
|
1782
|
+
else
|
1783
|
+
h.select{|ca, c| primary_keys[ta] = ca if pk == c}
|
1784
|
+
end
|
1785
|
+
end
|
1786
|
+
@column_maps = column_maps
|
1787
|
+
@primary_keys = primary_keys
|
1788
|
+
@row_procs = row_procs
|
1789
|
+
|
1790
|
+
# For performance, create two special maps for the master table,
|
1791
|
+
# so you can skip a hash lookup.
|
1792
|
+
@master_column_map = column_maps[master]
|
1793
|
+
@master_primary_keys = primary_keys[master]
|
1794
|
+
|
1795
|
+
# Add a special hash mapping table alias symbols to 5 element arrays that just
|
1796
|
+
# contain the data in other data structures for that table alias. This is
|
1797
|
+
# used for performance, to get all values in one hash lookup instead of
|
1798
|
+
# separate hash lookups for each data structure.
|
1799
|
+
ta_map = {}
|
1800
|
+
alias_map.keys.each do |ta|
|
1801
|
+
ta_map[ta] = [records_map[ta], row_procs[ta], alias_map[ta], type_map[ta], reciprocal_map[ta]]
|
1802
|
+
end
|
1803
|
+
@ta_map = ta_map
|
1804
|
+
end
|
1805
|
+
|
1806
|
+
# Return an array of primary model instances with the associations cache prepopulated
|
1807
|
+
# for all model objects (both primary and associated).
|
1808
|
+
def load(hashes)
|
1809
|
+
master = master()
|
1810
|
+
|
1811
|
+
# Assign to local variables for speed increase
|
1812
|
+
rp = row_procs[master]
|
1813
|
+
rm = records_map[master]
|
1814
|
+
dm = dependency_map
|
1815
|
+
|
1816
|
+
# This will hold the final record set that we will be replacing the object graph with.
|
1817
|
+
records = []
|
1818
|
+
|
1819
|
+
hashes.each do |h|
|
1820
|
+
unless key = master_pk(h)
|
1821
|
+
key = hkey(master_hfor(h))
|
1822
|
+
end
|
1823
|
+
unless primary_record = rm[key]
|
1824
|
+
primary_record = rm[key] = rp.call(master_hfor(h))
|
1825
|
+
# Only add it to the list of records to return if it is a new record
|
1826
|
+
records.push(primary_record)
|
1827
|
+
end
|
1828
|
+
# Build all associations for the current object and it's dependencies
|
1829
|
+
_load(dm, primary_record, h)
|
1830
|
+
end
|
1831
|
+
|
1832
|
+
# Remove duplicate records from all associations if this graph could possibly be a cartesian product
|
1833
|
+
unique(records, dm) if @unique
|
1834
|
+
|
1835
|
+
records
|
1836
|
+
end
|
1837
|
+
|
1838
|
+
private
|
1839
|
+
|
1840
|
+
# Recursive method that creates associated model objects and associates them to the current model object.
|
1841
|
+
def _load(dependency_map, current, h)
|
1842
|
+
dependency_map.each do |ta, deps|
|
1843
|
+
unless key = pk(ta, h)
|
1844
|
+
ta_h = hfor(ta, h)
|
1845
|
+
unless ta_h.values.any?
|
1846
|
+
assoc_name = alias_map[ta]
|
1847
|
+
unless (assoc = current.associations).has_key?(assoc_name)
|
1848
|
+
assoc[assoc_name] = type_map[ta] ? [] : nil
|
1849
|
+
end
|
1850
|
+
next
|
1851
|
+
end
|
1852
|
+
key = hkey(ta_h)
|
1853
|
+
end
|
1854
|
+
rm, rp, assoc_name, tm, rcm = @ta_map[ta]
|
1855
|
+
unless rec = rm[key]
|
1856
|
+
rec = rm[key] = rp.call(hfor(ta, h))
|
1857
|
+
end
|
1858
|
+
|
1859
|
+
if tm
|
1860
|
+
unless (assoc = current.associations).has_key?(assoc_name)
|
1861
|
+
assoc[assoc_name] = []
|
1862
|
+
end
|
1863
|
+
assoc[assoc_name].push(rec)
|
1864
|
+
rec.associations[rcm] = current if rcm
|
1865
|
+
else
|
1866
|
+
current.associations[assoc_name] = rec
|
1867
|
+
end
|
1868
|
+
# Recurse into dependencies of the current object
|
1869
|
+
_load(deps, rec, h) unless deps.empty?
|
1870
|
+
end
|
1871
|
+
end
|
1872
|
+
|
1873
|
+
# Return the subhash for the specific table alias +ta+ by parsing the values out of the main hash +h+
|
1874
|
+
def hfor(ta, h)
|
1875
|
+
out = {}
|
1876
|
+
@column_maps[ta].each{|ca, c| out[c] = h[ca]}
|
1877
|
+
out
|
1878
|
+
end
|
1879
|
+
|
1880
|
+
# Return a suitable hash key for any subhash +h+, which is an array of values by column order.
|
1881
|
+
# This is only used if the primary key cannot be used.
|
1882
|
+
def hkey(h)
|
1883
|
+
h.sort_by{|x| x[0].to_s}
|
1884
|
+
end
|
1885
|
+
|
1886
|
+
# Return the subhash for the master table by parsing the values out of the main hash +h+
|
1887
|
+
def master_hfor(h)
|
1888
|
+
out = {}
|
1889
|
+
@master_column_map.each{|ca, c| out[c] = h[ca]}
|
1890
|
+
out
|
1891
|
+
end
|
1892
|
+
|
1893
|
+
# Return a primary key value for the master table by parsing it out of the main hash +h+.
|
1894
|
+
def master_pk(h)
|
1895
|
+
x = @master_primary_keys
|
1896
|
+
if x.is_a?(Array)
|
1897
|
+
unless x == []
|
1898
|
+
x = x.map{|ca| h[ca]}
|
1899
|
+
x if x.all?
|
1900
|
+
end
|
1901
|
+
else
|
1902
|
+
h[x]
|
1903
|
+
end
|
1904
|
+
end
|
1905
|
+
|
1906
|
+
# Return a primary key value for the given table alias by parsing it out of the main hash +h+.
|
1907
|
+
def pk(ta, h)
|
1908
|
+
x = primary_keys[ta]
|
1909
|
+
if x.is_a?(Array)
|
1910
|
+
unless x == []
|
1911
|
+
x = x.map{|ca| h[ca]}
|
1912
|
+
x if x.all?
|
1913
|
+
end
|
1914
|
+
else
|
1915
|
+
h[x]
|
1916
|
+
end
|
1917
|
+
end
|
1918
|
+
|
1919
|
+
# If the result set is the result of a cartesian product, then it is possible that
|
1920
|
+
# there are multiple records for each association when there should only be one.
|
1921
|
+
# In that case, for each object in all associations loaded via +eager_graph+, run
|
1922
|
+
# uniq! on the association to make sure no duplicate records show up.
|
1923
|
+
# Note that this can cause legitimate duplicate records to be removed.
|
1924
|
+
def unique(records, dependency_map)
|
1925
|
+
records.each do |record|
|
1926
|
+
dependency_map.each do |ta, deps|
|
1927
|
+
list = record.send(alias_map[ta])
|
1928
|
+
list = if type_map[ta]
|
1929
|
+
list.uniq!
|
1930
|
+
unique(list, deps) if !list.empty? && !deps.empty?
|
1931
|
+
elsif list
|
1932
|
+
unique([list], deps) unless deps.empty?
|
1933
|
+
end
|
1934
|
+
end
|
1935
|
+
end
|
1936
|
+
end
|
1937
|
+
end
|
1779
1938
|
end
|
1780
1939
|
end
|
1781
1940
|
end
|