sequel 3.25.0 → 3.26.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 +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
|