sequel 3.13.0 → 3.14.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +36 -0
- data/doc/release_notes/3.14.0.txt +118 -0
- data/lib/sequel/adapters/oracle.rb +7 -2
- data/lib/sequel/adapters/shared/mssql.rb +9 -3
- data/lib/sequel/connection_pool/sharded_threaded.rb +1 -1
- data/lib/sequel/connection_pool/threaded.rb +3 -3
- data/lib/sequel/database/connecting.rb +47 -11
- data/lib/sequel/database/dataset.rb +17 -6
- data/lib/sequel/database/dataset_defaults.rb +15 -3
- data/lib/sequel/database/logging.rb +4 -3
- data/lib/sequel/database/misc.rb +33 -21
- data/lib/sequel/database/query.rb +61 -22
- data/lib/sequel/database/schema_generator.rb +108 -45
- data/lib/sequel/database/schema_methods.rb +8 -5
- data/lib/sequel/dataset/actions.rb +194 -45
- data/lib/sequel/dataset/features.rb +1 -1
- data/lib/sequel/dataset/graph.rb +51 -43
- data/lib/sequel/dataset/misc.rb +29 -5
- data/lib/sequel/dataset/mutation.rb +0 -1
- data/lib/sequel/dataset/prepared_statements.rb +14 -2
- data/lib/sequel/dataset/query.rb +268 -125
- data/lib/sequel/dataset/sql.rb +33 -44
- data/lib/sequel/extensions/migration.rb +3 -2
- data/lib/sequel/extensions/pagination.rb +1 -1
- data/lib/sequel/model/associations.rb +89 -87
- data/lib/sequel/model/base.rb +386 -109
- data/lib/sequel/model/errors.rb +15 -1
- data/lib/sequel/model/exceptions.rb +3 -3
- data/lib/sequel/model/inflections.rb +2 -2
- data/lib/sequel/model/plugins.rb +9 -5
- data/lib/sequel/plugins/rcte_tree.rb +43 -15
- data/lib/sequel/plugins/schema.rb +6 -5
- data/lib/sequel/plugins/serialization.rb +1 -1
- data/lib/sequel/plugins/single_table_inheritance.rb +1 -1
- data/lib/sequel/plugins/tree.rb +33 -1
- data/lib/sequel/timezones.rb +16 -10
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/mssql_spec.rb +36 -2
- data/spec/adapters/mysql_spec.rb +4 -4
- data/spec/adapters/postgres_spec.rb +1 -1
- data/spec/adapters/spec_helper.rb +2 -2
- data/spec/core/database_spec.rb +8 -1
- data/spec/core/dataset_spec.rb +36 -1
- data/spec/extensions/pagination_spec.rb +1 -1
- data/spec/extensions/rcte_tree_spec.rb +40 -8
- data/spec/extensions/schema_spec.rb +5 -0
- data/spec/extensions/serialization_spec.rb +4 -4
- data/spec/extensions/single_table_inheritance_spec.rb +7 -0
- data/spec/extensions/tree_spec.rb +36 -0
- data/spec/integration/dataset_test.rb +19 -0
- data/spec/integration/prepared_statement_test.rb +2 -2
- data/spec/integration/schema_test.rb +1 -1
- data/spec/integration/spec_helper.rb +4 -4
- data/spec/integration/timezone_test.rb +27 -21
- data/spec/model/associations_spec.rb +5 -5
- data/spec/model/dataset_methods_spec.rb +13 -0
- data/spec/model/hooks_spec.rb +31 -0
- data/spec/model/record_spec.rb +24 -7
- data/spec/model/validations_spec.rb +9 -4
- metadata +6 -4
data/lib/sequel/dataset/sql.rb
CHANGED
@@ -5,40 +5,28 @@ module Sequel
|
|
5
5
|
# These are methods you can call to see what SQL will be generated by the dataset.
|
6
6
|
# ---------------------
|
7
7
|
|
8
|
-
#
|
8
|
+
# Returns a DELETE SQL query string. See +delete+.
|
9
9
|
#
|
10
|
-
# dataset.filter{|o| o.price >= 100}.delete_sql
|
11
|
-
#
|
10
|
+
# dataset.filter{|o| o.price >= 100}.delete_sql
|
11
|
+
# # => "DELETE FROM items WHERE (price >= 100)"
|
12
12
|
def delete_sql
|
13
13
|
return static_sql(opts[:sql]) if opts[:sql]
|
14
14
|
check_modification_allowed!
|
15
15
|
clause_sql(:delete)
|
16
16
|
end
|
17
17
|
|
18
|
-
# Returns an EXISTS clause for the dataset as a LiteralString
|
18
|
+
# Returns an EXISTS clause for the dataset as a +LiteralString+.
|
19
19
|
#
|
20
|
-
# DB.select(1).where(DB[:items].exists)
|
21
|
-
#
|
20
|
+
# DB.select(1).where(DB[:items].exists)
|
21
|
+
# # SELECT 1 WHERE (EXISTS (SELECT * FROM items))
|
22
22
|
def exists
|
23
23
|
LiteralString.new("EXISTS (#{select_sql})")
|
24
24
|
end
|
25
25
|
|
26
|
-
#
|
27
|
-
# complex, and best explained by example:
|
26
|
+
# Returns an INSERT SQL query string. See +insert+.
|
28
27
|
#
|
29
|
-
#
|
30
|
-
#
|
31
|
-
# DB[:items].insert_sql({}) #=> 'INSERT INTO items DEFAULT VALUES'
|
32
|
-
# # Values without columns
|
33
|
-
# DB[:items].insert_sql(1,2,3) #=> 'INSERT INTO items VALUES (1, 2, 3)'
|
34
|
-
# DB[:items].insert_sql([1,2,3]) #=> 'INSERT INTO items VALUES (1, 2, 3)'
|
35
|
-
# # Values with columns
|
36
|
-
# DB[:items].insert_sql([:a, :b], [1,2]) #=> 'INSERT INTO items (a, b) VALUES (1, 2)'
|
37
|
-
# DB[:items].insert_sql(:a => 1, :b => 2) #=> 'INSERT INTO items (a, b) VALUES (1, 2)'
|
38
|
-
# # Using a subselect
|
39
|
-
# DB[:items].insert_sql(DB[:old_items]) #=> 'INSERT INTO items SELECT * FROM old_items
|
40
|
-
# # Using a subselect with columns
|
41
|
-
# DB[:items].insert_sql([:a, :b], DB[:old_items]) #=> 'INSERT INTO items (a, b) SELECT * FROM old_items
|
28
|
+
# DB[:items].insert_sql(:a=>1)
|
29
|
+
# # => "INSERT INTO items (a) VALUES (1)"
|
42
30
|
def insert_sql(*values)
|
43
31
|
return static_sql(@opts[:sql]) if @opts[:sql]
|
44
32
|
|
@@ -80,13 +68,13 @@ module Sequel
|
|
80
68
|
# Returns a literal representation of a value to be used as part
|
81
69
|
# of an SQL expression.
|
82
70
|
#
|
83
|
-
#
|
84
|
-
#
|
85
|
-
#
|
86
|
-
#
|
87
|
-
#
|
71
|
+
# DB[:items].literal("abc'def\\") #=> "'abc''def\\\\'"
|
72
|
+
# DB[:items].literal(:items__id) #=> "items.id"
|
73
|
+
# DB[:items].literal([1, 2, 3]) => "(1, 2, 3)"
|
74
|
+
# DB[:items].literal(DB[:items]) => "(SELECT * FROM items)"
|
75
|
+
# DB[:items].literal(:x + 1 > :y) => "((x + 1) > y)"
|
88
76
|
#
|
89
|
-
# If an unsupported object is given, an
|
77
|
+
# If an unsupported object is given, an +Error+ is raised.
|
90
78
|
def literal(v)
|
91
79
|
case v
|
92
80
|
when String
|
@@ -126,7 +114,7 @@ module Sequel
|
|
126
114
|
end
|
127
115
|
|
128
116
|
# Returns an array of insert statements for inserting multiple records.
|
129
|
-
# This method is used by
|
117
|
+
# This method is used by +multi_insert+ to format insert statements and
|
130
118
|
# expects a keys array and and an array of value arrays.
|
131
119
|
#
|
132
120
|
# This method should be overridden by descendants if the support
|
@@ -135,7 +123,7 @@ module Sequel
|
|
135
123
|
values.map{|r| insert_sql(columns, r)}
|
136
124
|
end
|
137
125
|
|
138
|
-
#
|
126
|
+
# Returns a SELECT SQL query string.
|
139
127
|
#
|
140
128
|
# dataset.select_sql # => "SELECT * FROM items"
|
141
129
|
def select_sql
|
@@ -143,12 +131,14 @@ module Sequel
|
|
143
131
|
clause_sql(:select)
|
144
132
|
end
|
145
133
|
|
146
|
-
# Same as select_sql
|
134
|
+
# Same as +select_sql+, not aliased directly to make subclassing simpler.
|
147
135
|
def sql
|
148
136
|
select_sql
|
149
137
|
end
|
150
138
|
|
151
|
-
# SQL query
|
139
|
+
# Returns a TRUNCATE SQL query string. See +truncate+
|
140
|
+
#
|
141
|
+
# DB[:items].truncate_sql # => 'TRUNCATE items'
|
152
142
|
def truncate_sql
|
153
143
|
if opts[:sql]
|
154
144
|
static_sql(opts[:sql])
|
@@ -159,12 +149,12 @@ module Sequel
|
|
159
149
|
end
|
160
150
|
end
|
161
151
|
|
162
|
-
# Formats an UPDATE statement using the given values.
|
152
|
+
# Formats an UPDATE statement using the given values. See +update+.
|
163
153
|
#
|
164
|
-
#
|
165
|
-
#
|
154
|
+
# DB[:items].update_sql(:price => 100, :category => 'software')
|
155
|
+
# # => "UPDATE items SET price = 100, category = 'software'
|
166
156
|
#
|
167
|
-
# Raises an
|
157
|
+
# Raises an +Error+ if the dataset is grouped or includes more
|
168
158
|
# than one table.
|
169
159
|
def update_sql(values = {})
|
170
160
|
return static_sql(opts[:sql]) if opts[:sql]
|
@@ -210,12 +200,12 @@ module Sequel
|
|
210
200
|
WILDCARD = LiteralString.new('*').freeze
|
211
201
|
SQL_WITH = "WITH ".freeze
|
212
202
|
|
213
|
-
# SQL fragment for
|
203
|
+
# SQL fragment for AliasedExpression
|
214
204
|
def aliased_expression_sql(ae)
|
215
205
|
as_sql(literal(ae.expression), ae.aliaz)
|
216
206
|
end
|
217
207
|
|
218
|
-
# SQL fragment for
|
208
|
+
# SQL fragment for Array
|
219
209
|
def array_sql(a)
|
220
210
|
a.empty? ? '(NULL)' : "(#{expression_list(a)})"
|
221
211
|
end
|
@@ -225,7 +215,7 @@ module Sequel
|
|
225
215
|
literal(constant)
|
226
216
|
end
|
227
217
|
|
228
|
-
# SQL fragment for
|
218
|
+
# SQL fragment for CaseExpression
|
229
219
|
def case_expression_sql(ce)
|
230
220
|
sql = '(CASE '
|
231
221
|
sql << "#{literal(ce.expression)} " if ce.expression?
|
@@ -235,12 +225,12 @@ module Sequel
|
|
235
225
|
sql << "ELSE #{literal(ce.default)} END)"
|
236
226
|
end
|
237
227
|
|
238
|
-
# SQL fragment for the SQL CAST expression
|
228
|
+
# SQL fragment for the SQL CAST expression
|
239
229
|
def cast_sql(expr, type)
|
240
230
|
"CAST(#{literal(expr)} AS #{db.cast_type_literal(type)})"
|
241
231
|
end
|
242
232
|
|
243
|
-
# SQL fragment for specifying all columns in a given table
|
233
|
+
# SQL fragment for specifying all columns in a given table
|
244
234
|
def column_all_sql(ca)
|
245
235
|
"#{quote_schema_table(ca.table)}.*"
|
246
236
|
end
|
@@ -427,7 +417,7 @@ module Sequel
|
|
427
417
|
end
|
428
418
|
end
|
429
419
|
|
430
|
-
# SQL fragment for specifying subscripts (SQL
|
420
|
+
# SQL fragment for specifying subscripts (SQL array accesses)
|
431
421
|
def subscript_sql(s)
|
432
422
|
"#{literal(s.f)}[#{expression_list(s.sub)}]"
|
433
423
|
end
|
@@ -610,7 +600,6 @@ module Sequel
|
|
610
600
|
sprintf(".%06d", usec)
|
611
601
|
end
|
612
602
|
|
613
|
-
# SQL fragment specifying a list of identifiers
|
614
603
|
# SQL fragment specifying a list of identifiers
|
615
604
|
def identifier_list(columns)
|
616
605
|
columns.map{|i| quote_identifier(i)}.join(COMMA_SEPARATOR)
|
@@ -725,8 +714,8 @@ module Sequel
|
|
725
714
|
end
|
726
715
|
|
727
716
|
# SQL fragment for a type of object not handled by Dataset#literal.
|
728
|
-
# Calls sql_literal if object responds to it, otherwise raises an error.
|
729
|
-
# Classes implementing sql_literal should call a class-specific method on the dataset
|
717
|
+
# Calls +sql_literal+ if object responds to it, otherwise raises an error.
|
718
|
+
# Classes implementing +sql_literal+ should call a class-specific method on the dataset
|
730
719
|
# provided and should add that method to Sequel::Dataset, allowing for adapters
|
731
720
|
# to provide customized literalizations.
|
732
721
|
# If a database specific type is allowed, this should be overriden in a subclass.
|
@@ -305,11 +305,12 @@ module Sequel
|
|
305
305
|
super
|
306
306
|
@target = opts[:target] || latest_migration_version
|
307
307
|
@current = opts[:current] || current_migration_version
|
308
|
-
@direction = current < target ? :up : :down
|
309
|
-
@migrations = get_migrations
|
310
308
|
|
311
309
|
raise(Error, "No current version available") unless current
|
312
310
|
raise(Error, "No target version available") unless target
|
311
|
+
|
312
|
+
@direction = current < target ? :up : :down
|
313
|
+
@migrations = get_migrations
|
313
314
|
end
|
314
315
|
|
315
316
|
# Apply all migrations on the database
|
@@ -17,7 +17,7 @@ module Sequel
|
|
17
17
|
|
18
18
|
# Yields a paginated dataset for each page and returns the receiver. Does
|
19
19
|
# a count to find the total number of records for this dataset.
|
20
|
-
def each_page(page_size
|
20
|
+
def each_page(page_size)
|
21
21
|
raise(Error, "You cannot paginate a dataset that already has a limit") if @opts[:limit]
|
22
22
|
record_count = count
|
23
23
|
total_pages = (record_count / page_size.to_f).ceil
|
@@ -442,13 +442,13 @@ module Sequel
|
|
442
442
|
# Project.associations
|
443
443
|
# => [:portfolio, :milestones]
|
444
444
|
# Project.association_reflection(:portfolio)
|
445
|
-
# => {:type => :many_to_one, :name => :portfolio,
|
445
|
+
# => {:type => :many_to_one, :name => :portfolio, ...}
|
446
446
|
#
|
447
447
|
# For a more in depth general overview, as well as a reference guide,
|
448
448
|
# see the {Association Basics guide}[link:files/doc/association_basics_rdoc.html].
|
449
449
|
# For examples of advanced usage, see the {Advanced Associations guide}[link:files/doc/advanced_associations_rdoc.html].
|
450
450
|
module ClassMethods
|
451
|
-
# All association reflections defined for this model (default:
|
451
|
+
# All association reflections defined for this model (default: {}).
|
452
452
|
attr_reader :association_reflections
|
453
453
|
|
454
454
|
# Array of all association reflections for this model class
|
@@ -497,11 +497,11 @@ module Sequel
|
|
497
497
|
# - :before_set - Symbol, Proc, or array of both/either specifying a callback to call
|
498
498
|
# before an item is set using the association setter method.
|
499
499
|
# - :cartesian_product_number - the number of joins completed by this association that could cause more
|
500
|
-
# than one row for each row in the current table (default: 0 for many_to_one associations,
|
501
|
-
# 1 for
|
500
|
+
# than one row for each row in the current table (default: 0 for many_to_one and one_to_one associations,
|
501
|
+
# 1 for one_to_many and many_to_many associations).
|
502
502
|
# - :class - The associated class or its name. If not
|
503
503
|
# given, uses the association's name, which is camelized (and
|
504
|
-
# singularized unless the type is :many_to_one)
|
504
|
+
# singularized unless the type is :many_to_one or :one_to_one)
|
505
505
|
# - :clone - Merge the current options and block into the options and block used in defining
|
506
506
|
# the given association. Can be used to DRY up a bunch of similar associations that
|
507
507
|
# all share the same options such as :class and :key, while changing the order and block used.
|
@@ -510,18 +510,12 @@ module Sequel
|
|
510
510
|
# to use for the _dataset method (before the other options are applied).
|
511
511
|
# - :distinct - Use the DISTINCT clause when selecting associating object, both when
|
512
512
|
# lazy loading and eager loading via .eager (but not when using .eager_graph).
|
513
|
-
# - :eager - The associations to eagerly load via
|
514
|
-
# For many_to_one associations, this is ignored unless this association is
|
515
|
-
# being eagerly loaded, as it doesn't save queries unless multiple objects
|
516
|
-
# can be loaded at once.
|
513
|
+
# - :eager - The associations to eagerly load via +eager+ when loading the associated object(s).
|
517
514
|
# - :eager_block - If given, use the block instead of the default block when
|
518
515
|
# eagerly loading. To not use a block when eager loading (when one is used normally),
|
519
516
|
# set to nil.
|
520
|
-
# - :eager_graph - The associations to eagerly load via
|
521
|
-
#
|
522
|
-
# being eagerly loaded, as it doesn't save queries unless multiple objects
|
523
|
-
# can be loaded at once.
|
524
|
-
# - :eager_grapher - A proc to use to implement eager loading via eager graph, overriding the default.
|
517
|
+
# - :eager_graph - The associations to eagerly load via +eager_graph+ when loading the associated object(s).
|
518
|
+
# - :eager_grapher - A proc to use to implement eager loading via +eager_graph+, overriding the default.
|
525
519
|
# Takes three arguments, a dataset, an alias to use for the table to graph for this association,
|
526
520
|
# and the alias that was used for the current table (since you can cascade associations),
|
527
521
|
# Should return a copy of the dataset with the association graphed into it.
|
@@ -531,38 +525,38 @@ module Sequel
|
|
531
525
|
# :rows, and :associations, corresponding to the three arguments, and an additional key :self, which is
|
532
526
|
# the dataset doing the eager loading. In the proc, the associated records should
|
533
527
|
# be queried from the database and the associations cache for each
|
534
|
-
# record should be populated
|
528
|
+
# record should be populated.
|
535
529
|
# - :eager_loader_key - A symbol for the key column to use to populate the key hash
|
536
530
|
# for the eager loader.
|
537
531
|
# - :extend - A module or array of modules to extend the dataset with.
|
538
532
|
# - :graph_block - The block to pass to join_table when eagerly loading
|
539
|
-
# the association via eager_graph
|
533
|
+
# the association via +eager_graph+.
|
540
534
|
# - :graph_conditions - The additional conditions to use on the SQL join when eagerly loading
|
541
|
-
# the association via eager_graph
|
542
|
-
# specified, the :conditions option is used if it is a hash or array of
|
535
|
+
# the association via +eager_graph+. Should be a hash or an array of two element arrays. If not
|
536
|
+
# specified, the :conditions option is used if it is a hash or array of two element arrays.
|
543
537
|
# - :graph_join_type - The type of SQL join to use when eagerly loading the association via
|
544
538
|
# eager_graph. Defaults to :left_outer.
|
545
539
|
# - :graph_only_conditions - The conditions to use on the SQL join when eagerly loading
|
546
|
-
# the association via eager_graph
|
540
|
+
# the association via +eager_graph+, instead of the default conditions specified by the
|
547
541
|
# foreign/primary keys. This option causes the :graph_conditions option to be ignored.
|
548
542
|
# - :graph_select - A column or array of columns to select from the associated table
|
549
|
-
# when eagerly loading the association via eager_graph
|
543
|
+
# when eagerly loading the association via +eager_graph+. Defaults to all
|
550
544
|
# columns in the associated table.
|
551
545
|
# - :limit - Limit the number of records to the provided value. Use
|
552
|
-
# an array with two
|
546
|
+
# an array with two elements for the value to specify a limit (first element) and an offset (second element).
|
553
547
|
# - :methods_module - The module that methods the association creates will be placed into. Defaults
|
554
548
|
# to the module containing the model's columns.
|
555
549
|
# - :order - the column(s) by which to order the association dataset. Can be a
|
556
|
-
# singular column or an array.
|
557
|
-
# - :order_eager_graph - Whether to add the order to the dataset's order when graphing
|
558
|
-
# via
|
559
|
-
# - :read_only - Do not add a setter method (for many_to_one or
|
560
|
-
# or add_/remove_/remove_all_ methods (for one_to_many
|
550
|
+
# singular column symbol or an array of column symbols.
|
551
|
+
# - :order_eager_graph - Whether to add the association's order to the graphed dataset's order when graphing
|
552
|
+
# via +eager_graph+. Defaults to true, so set to false to disable.
|
553
|
+
# - :read_only - Do not add a setter method (for many_to_one or one_to_one associations),
|
554
|
+
# or add_/remove_/remove_all_ methods (for one_to_many and many_to_many associations).
|
561
555
|
# - :reciprocal - the symbol name of the reciprocal association,
|
562
|
-
# if it exists. By default,
|
556
|
+
# if it exists. By default, Sequel will try to determine it by looking at the
|
563
557
|
# associated model's assocations for a association that matches
|
564
558
|
# the current association's key(s). Set to nil to not use a reciprocal.
|
565
|
-
# - :select - the
|
559
|
+
# - :select - the columns to select. Defaults to the associated class's
|
566
560
|
# table_name.* in a many_to_many association, which means it doesn't include the attributes from the
|
567
561
|
# join table. If you want to include the join table attributes, you can
|
568
562
|
# use this option, but beware that the join table attributes can clash with
|
@@ -585,16 +579,16 @@ module Sequel
|
|
585
579
|
# Defaults to primary key of the current table. Can use an
|
586
580
|
# array of symbols for a composite key association.
|
587
581
|
# * :many_to_many:
|
588
|
-
# - :graph_join_table_block - The block to pass to join_table for
|
589
|
-
# the join table when eagerly loading the association via eager_graph
|
582
|
+
# - :graph_join_table_block - The block to pass to +join_table+ for
|
583
|
+
# the join table when eagerly loading the association via +eager_graph+.
|
590
584
|
# - :graph_join_table_conditions - The additional conditions to use on the SQL join for
|
591
|
-
# the join table when eagerly loading the association via eager_graph
|
592
|
-
# or an array of
|
585
|
+
# the join table when eagerly loading the association via +eager_graph+. Should be a hash
|
586
|
+
# or an array of two element arrays.
|
593
587
|
# - :graph_join_table_join_type - The type of SQL join to use for the join table when eagerly
|
594
|
-
# loading the association via eager_graph
|
588
|
+
# loading the association via +eager_graph+. Defaults to the :graph_join_type option or
|
595
589
|
# :left_outer.
|
596
590
|
# - :graph_join_table_only_conditions - The conditions to use on the SQL join for the join
|
597
|
-
# table when eagerly loading the association via eager_graph
|
591
|
+
# table when eagerly loading the association via +eager_graph+, instead of the default
|
598
592
|
# conditions specified by the foreign/primary keys. This option causes the
|
599
593
|
# :graph_join_table_conditions option to be ignored.
|
600
594
|
# - :join_table - name of table that includes the foreign keys to both
|
@@ -622,7 +616,7 @@ module Sequel
|
|
622
616
|
raise(Error, 'Model.associate name argument must be a symbol') unless Symbol === name
|
623
617
|
raise(Error, ':eager_loader option must have an arity of 1 or 3') if opts[:eager_loader] && ![1, 3].include?(opts[:eager_loader].arity)
|
624
618
|
|
625
|
-
#
|
619
|
+
# dup early so we don't modify opts
|
626
620
|
orig_opts = opts.dup
|
627
621
|
orig_opts = association_reflection(opts[:clone])[:orig_opts].merge(orig_opts) if opts[:clone]
|
628
622
|
opts = orig_opts.merge(:type => type, :name => name, :cache => true, :model => self)
|
@@ -659,7 +653,7 @@ module Sequel
|
|
659
653
|
association_reflections.keys
|
660
654
|
end
|
661
655
|
|
662
|
-
# Modify and return eager loading dataset based on association options.
|
656
|
+
# Modify and return eager loading dataset based on association options.
|
663
657
|
def eager_loading_dataset(opts, ds, select, associations, eager_options={})
|
664
658
|
ds = ds.select(*select) if select
|
665
659
|
if c = opts[:conditions]
|
@@ -736,7 +730,7 @@ module Sequel
|
|
736
730
|
association_module_def(opts.add_method, opts){|o,*args| add_associated_object(opts, o, *args)}
|
737
731
|
end
|
738
732
|
|
739
|
-
# Adds
|
733
|
+
# Adds the association dataset methods to the association methods module.
|
740
734
|
def def_association_dataset_methods(opts)
|
741
735
|
# If a block is given, define a helper method for it, because it takes
|
742
736
|
# an argument. This is unnecessary in Ruby 1.9, as that has instance_exec.
|
@@ -746,12 +740,12 @@ module Sequel
|
|
746
740
|
def_association_method(opts)
|
747
741
|
end
|
748
742
|
|
749
|
-
# Adds
|
743
|
+
# Adds the association method to the association methods module.
|
750
744
|
def def_association_method(opts)
|
751
745
|
association_module_def(opts.association_method, opts){|*reload| load_associated_objects(opts, reload[0])}
|
752
746
|
end
|
753
747
|
|
754
|
-
#
|
748
|
+
# Configures many_to_many association reflection and adds the related association methods
|
755
749
|
def def_many_to_many(opts)
|
756
750
|
name = opts[:name]
|
757
751
|
model = self
|
@@ -761,8 +755,11 @@ module Sequel
|
|
761
755
|
rcks = opts[:right_keys] = Array(right)
|
762
756
|
left_pk = (opts[:left_primary_key] ||= self.primary_key)
|
763
757
|
lcpks = opts[:left_primary_keys] = Array(left_pk)
|
764
|
-
raise(Error,
|
765
|
-
|
758
|
+
raise(Error, "mismatched number of left composite keys: #{lcks.inspect} vs #{lcpks.inspect}") unless lcks.length == lcpks.length
|
759
|
+
if opts[:right_primary_key]
|
760
|
+
rcpks = Array(opts[:right_primary_key])
|
761
|
+
raise(Error, "mismatched number of right composite keys: #{rcks.inspect} vs #{rcpks.inspect}") unless rcks.length == rcpks.length
|
762
|
+
end
|
766
763
|
uses_lcks = opts[:uses_left_composite_keys] = lcks.length > 1
|
767
764
|
uses_rcks = opts[:uses_right_composite_keys] = rcks.length > 1
|
768
765
|
opts[:cartesian_product_number] ||= 1
|
@@ -826,14 +823,17 @@ module Sequel
|
|
826
823
|
def_remove_methods(opts)
|
827
824
|
end
|
828
825
|
|
829
|
-
#
|
826
|
+
# Configures many_to_one association reflection and adds the related association methods
|
830
827
|
def def_many_to_one(opts)
|
831
828
|
name = opts[:name]
|
832
829
|
model = self
|
833
830
|
opts[:key] = opts.default_key unless opts.include?(:key)
|
834
831
|
key = opts[:key]
|
835
832
|
cks = opts[:keys] = Array(opts[:key])
|
836
|
-
|
833
|
+
if opts[:primary_key]
|
834
|
+
cpks = Array(opts[:primary_key])
|
835
|
+
raise(Error, "mismatched number of composite keys: #{cks.inspect} vs #{cpks.inspect}") unless cks.length == cpks.length
|
836
|
+
end
|
837
837
|
uses_cks = opts[:uses_composite_keys] = cks.length > 1
|
838
838
|
opts[:cartesian_product_number] ||= 0
|
839
839
|
opts[:dataset] ||= proc do
|
@@ -875,7 +875,7 @@ module Sequel
|
|
875
875
|
association_module_def(opts.setter_method, opts){|o| set_associated_object(opts, o)}
|
876
876
|
end
|
877
877
|
|
878
|
-
#
|
878
|
+
# Configures one_to_many and one_to_one association reflections and adds the related association methods
|
879
879
|
def def_one_to_many(opts)
|
880
880
|
one_to_one = opts[:type] == :one_to_one
|
881
881
|
name = opts[:name]
|
@@ -884,7 +884,7 @@ module Sequel
|
|
884
884
|
cks = opts[:keys] = Array(key)
|
885
885
|
primary_key = (opts[:primary_key] ||= self.primary_key)
|
886
886
|
cpks = opts[:primary_keys] = Array(primary_key)
|
887
|
-
raise(Error,
|
887
|
+
raise(Error, "mismatched number of composite keys: #{cks.inspect} vs #{cpks.inspect}") unless cks.length == cpks.length
|
888
888
|
uses_cks = opts[:uses_composite_keys] = cks.length > 1
|
889
889
|
opts[:dataset] ||= proc do
|
890
890
|
klass = opts.associated_class
|
@@ -970,7 +970,7 @@ module Sequel
|
|
970
970
|
end
|
971
971
|
end
|
972
972
|
|
973
|
-
# Alias of def_one_to_many
|
973
|
+
# Alias of def_one_to_many, since they share pretty much the same code.
|
974
974
|
def def_one_to_one(opts)
|
975
975
|
def_one_to_many(opts)
|
976
976
|
end
|
@@ -982,7 +982,7 @@ module Sequel
|
|
982
982
|
end
|
983
983
|
end
|
984
984
|
|
985
|
-
#
|
985
|
+
# Instance methods used to implement the associations support.
|
986
986
|
module InstanceMethods
|
987
987
|
# The currently cached associations. A hash with the keys being the
|
988
988
|
# association name symbols and the values being the associated object
|
@@ -1000,6 +1000,7 @@ module Sequel
|
|
1000
1000
|
|
1001
1001
|
private
|
1002
1002
|
|
1003
|
+
# Apply the association options such as :order and :limit to the given dataset, returning a modified dataset.
|
1003
1004
|
def _apply_association_options(opts, ds)
|
1004
1005
|
ds.extend(AssociationDatasetMethods)
|
1005
1006
|
ds.model_object = self
|
@@ -1019,7 +1020,7 @@ module Sequel
|
|
1019
1020
|
ds
|
1020
1021
|
end
|
1021
1022
|
|
1022
|
-
#
|
1023
|
+
# Return an association dataset for the given association reflection
|
1023
1024
|
def _dataset(opts)
|
1024
1025
|
raise(Sequel::Error, "model object #{inspect} does not have a primary key") if opts.dataset_need_primary_key? && !pk
|
1025
1026
|
_apply_association_options(opts, send(opts._dataset_method))
|
@@ -1144,8 +1145,8 @@ module Sequel
|
|
1144
1145
|
o
|
1145
1146
|
end
|
1146
1147
|
|
1147
|
-
#
|
1148
|
-
# is currently associated to the
|
1148
|
+
# Check that the object from the associated table specified by the primary key
|
1149
|
+
# is currently associated to the receiver. If it is associated, return the object, otherwise
|
1149
1150
|
# raise an error.
|
1150
1151
|
def remove_check_existing_object_from_pk(opts, o, *args)
|
1151
1152
|
key = o
|
@@ -1186,7 +1187,7 @@ module Sequel
|
|
1186
1187
|
end
|
1187
1188
|
end
|
1188
1189
|
|
1189
|
-
# Set the given object as the associated object for the given association
|
1190
|
+
# Set the given object as the associated object for the given many_to_one association reflection
|
1190
1191
|
def set_associated_object(opts, o)
|
1191
1192
|
raise(Error, "associated object #{o.inspect} does not have a primary key") if o && !o.pk
|
1192
1193
|
run_association_callbacks(opts, :before_set, o)
|
@@ -1200,7 +1201,7 @@ module Sequel
|
|
1200
1201
|
o
|
1201
1202
|
end
|
1202
1203
|
|
1203
|
-
# Set the given object as the associated object for the given association
|
1204
|
+
# Set the given object as the associated object for the given one_to_one association reflection
|
1204
1205
|
def set_one_to_one_associated_object(opts, o)
|
1205
1206
|
raise(Error, "object #{inspect} does not have a primary key") unless pk
|
1206
1207
|
run_association_callbacks(opts, :before_set, o)
|
@@ -1218,15 +1219,15 @@ module Sequel
|
|
1218
1219
|
# Eager loading makes it so that you can load all associated records for a
|
1219
1220
|
# set of objects in a single query, instead of a separate query for each object.
|
1220
1221
|
#
|
1221
|
-
# Two separate implementations are provided.
|
1222
|
+
# Two separate implementations are provided. +eager+ should be used most of the
|
1222
1223
|
# time, as it loads associated records using one query per association. However,
|
1223
|
-
# it does not allow you the ability to filter based on columns in associated tables.
|
1224
|
-
# all records in
|
1225
|
-
# tables. However,
|
1226
|
-
#
|
1224
|
+
# it does not allow you the ability to filter or order based on columns in associated tables. +eager_graph+ loads
|
1225
|
+
# all records in a single query using JOINs, allowing you to filter or order based on columns in associated
|
1226
|
+
# tables. However, +eager_graph+ can be slower than +eager+, especially if multiple
|
1227
|
+
# one_to_many or many_to_many associations are joined.
|
1227
1228
|
#
|
1228
|
-
# You can cascade the eager loading (loading associations
|
1229
|
-
# with no limit to the depth of the cascades. You do this by passing a hash to
|
1229
|
+
# You can cascade the eager loading (loading associations on associated objects)
|
1230
|
+
# with no limit to the depth of the cascades. You do this by passing a hash to +eager+ or +eager_graph+
|
1230
1231
|
# with the keys being associations of the current model and values being
|
1231
1232
|
# associations of the model associated with the current model via the key.
|
1232
1233
|
#
|
@@ -1244,7 +1245,7 @@ module Sequel
|
|
1244
1245
|
# Artist.eager(:albums=>{:tracks=>:genre}).all
|
1245
1246
|
# Artist.eager_graph(:albums=>{:tracks=>:genre}).all
|
1246
1247
|
module DatasetMethods
|
1247
|
-
# Add the
|
1248
|
+
# Add the <tt>eager!</tt> and <tt>eager_graph!</tt> mutation methods to the dataset.
|
1248
1249
|
def self.extended(obj)
|
1249
1250
|
obj.def_mutation_method(:eager, :eager_graph)
|
1250
1251
|
end
|
@@ -1253,23 +1254,23 @@ module Sequel
|
|
1253
1254
|
# query for each association.
|
1254
1255
|
#
|
1255
1256
|
# The basic idea for how it works is that the dataset is first loaded normally.
|
1256
|
-
# Then it goes through all associations that have been specified via eager
|
1257
|
+
# Then it goes through all associations that have been specified via +eager+.
|
1257
1258
|
# It loads each of those associations separately, then associates them back
|
1258
1259
|
# to the original dataset via primary/foreign keys. Due to the necessity of
|
1259
|
-
# all objects being present, you need to use
|
1260
|
-
# can't work with
|
1260
|
+
# all objects being present, you need to use +all+ to use eager loading, as it
|
1261
|
+
# can't work with +each+.
|
1261
1262
|
#
|
1262
1263
|
# This implementation avoids the complexity of extracting an object graph out
|
1263
1264
|
# of a single dataset, by building the object graph out of multiple datasets,
|
1264
1265
|
# one for each association. By using a separate dataset for each association,
|
1265
1266
|
# it avoids problems such as aliasing conflicts and creating cartesian product
|
1266
|
-
# result sets if multiple
|
1267
|
+
# result sets if multiple one_to_many or many_to_many eager associations are requested.
|
1267
1268
|
#
|
1268
1269
|
# One limitation of using this method is that you cannot filter the dataset
|
1269
1270
|
# based on values of columns in an associated table, since the associations are loaded
|
1270
1271
|
# in separate queries. To do that you need to load all associations in the
|
1271
1272
|
# same query, and extract an object graph from the results of that query. If you
|
1272
|
-
# need to filter based on columns in associated tables, look at
|
1273
|
+
# need to filter based on columns in associated tables, look at +eager_graph+
|
1273
1274
|
# or join the tables you need to filter on manually.
|
1274
1275
|
#
|
1275
1276
|
# Each association's order, if defined, is respected. Eager also works
|
@@ -1293,25 +1294,26 @@ module Sequel
|
|
1293
1294
|
end
|
1294
1295
|
|
1295
1296
|
# The secondary eager loading method. Loads all associations in a single query. This
|
1296
|
-
# method should only be used if you need to filter based on columns in associated tables.
|
1297
|
+
# method should only be used if you need to filter or order based on columns in associated tables.
|
1297
1298
|
#
|
1298
|
-
# This method builds an object graph using Dataset#graph
|
1299
|
+
# This method builds an object graph using <tt>Dataset#graph</tt>. Then it uses the graph
|
1299
1300
|
# to build the associations, and finally replaces the graph with a simple array
|
1300
1301
|
# of model objects.
|
1301
1302
|
#
|
1302
|
-
# Be very careful when using this with multiple
|
1303
|
-
# create large cartesian products. If you must graph multiple
|
1304
|
-
# make sure your filters are
|
1303
|
+
# Be very careful when using this with multiple one_to_many or many_to_many associations, as you can
|
1304
|
+
# create large cartesian products. If you must graph multiple one_to_many and many_to_many associations,
|
1305
|
+
# make sure your filters are narrow if you have a large database.
|
1305
1306
|
#
|
1306
|
-
# Each association's order, if definied, is respected.
|
1307
|
+
# Each association's order, if definied, is respected. +eager_graph+ probably
|
1307
1308
|
# won't work correctly on a limited dataset, unless you are
|
1308
|
-
# only graphing many_to_one associations.
|
1309
|
+
# only graphing many_to_one and one_to_one associations.
|
1309
1310
|
#
|
1310
1311
|
# Does not use the block defined for the association, since it does a single query for
|
1311
1312
|
# all objects. You can use the :graph_* association options to modify the SQL query.
|
1312
1313
|
#
|
1313
|
-
# Like eager
|
1314
|
-
# call each
|
1314
|
+
# Like +eager+, you need to call +all+ on the dataset for the eager loading to work. If you just
|
1315
|
+
# call +each+, you will get a normal graphed result back (a hash with table alias symbol keys and
|
1316
|
+
# model object values).
|
1315
1317
|
def eager_graph(*associations)
|
1316
1318
|
ds = if @opts[:eager_graph]
|
1317
1319
|
self
|
@@ -1342,12 +1344,12 @@ module Sequel
|
|
1342
1344
|
# (which would be dependencies of the current association)
|
1343
1345
|
#
|
1344
1346
|
# Arguments:
|
1345
|
-
#
|
1346
|
-
#
|
1347
|
-
#
|
1348
|
-
#
|
1349
|
-
#
|
1350
|
-
# *
|
1347
|
+
# ds :: Current dataset
|
1348
|
+
# model :: Current Model
|
1349
|
+
# ta :: table_alias used for the parent association
|
1350
|
+
# requirements :: an array, used as a stack for requirements
|
1351
|
+
# r :: association reflection for the current association
|
1352
|
+
# *associations :: any associations dependent on this one
|
1351
1353
|
def eager_graph_association(ds, model, ta, requirements, r, *associations)
|
1352
1354
|
klass = r.associated_class
|
1353
1355
|
assoc_name = r[:name]
|
@@ -1367,11 +1369,11 @@ module Sequel
|
|
1367
1369
|
# Call eager_graph_association on each association.
|
1368
1370
|
#
|
1369
1371
|
# Arguments:
|
1370
|
-
#
|
1371
|
-
#
|
1372
|
-
#
|
1373
|
-
#
|
1374
|
-
# *
|
1372
|
+
# ds :: Current dataset
|
1373
|
+
# model :: Current Model
|
1374
|
+
# ta :: table_alias used for the parent association
|
1375
|
+
# requirements :: an array, used as a stack for requirements
|
1376
|
+
# *associations :: the associations to add to the graph
|
1375
1377
|
def eager_graph_associations(ds, model, ta, requirements, *associations)
|
1376
1378
|
return ds if associations.empty?
|
1377
1379
|
associations.flatten.each do |association|
|
@@ -1465,7 +1467,7 @@ module Sequel
|
|
1465
1467
|
# to build all dependencies.
|
1466
1468
|
def eager_graph_build_associations_graph(dependency_map, alias_map, type_map, reciprocal_map, records_map, current, record_graph)
|
1467
1469
|
return if dependency_map.empty?
|
1468
|
-
# Don't clobber the
|
1470
|
+
# Don't clobber the cached association value for one_to_many and many_to_many associations if it has already been setup
|
1469
1471
|
dependency_map.keys.each do |ta|
|
1470
1472
|
assoc_name = alias_map[ta]
|
1471
1473
|
current.associations[assoc_name] = type_map[ta] ? [] : nil unless current.associations.include?(assoc_name)
|
@@ -1494,7 +1496,7 @@ module Sequel
|
|
1494
1496
|
|
1495
1497
|
# If the result set is the result of a cartesian product, then it is possible that
|
1496
1498
|
# there are multiple records for each association when there should only be one.
|
1497
|
-
# In that case, for each object in all associations loaded via
|
1499
|
+
# In that case, for each object in all associations loaded via +eager_graph+, run
|
1498
1500
|
# uniq! on the association to make sure no duplicate records show up.
|
1499
1501
|
# Note that this can cause legitimate duplicate records to be removed.
|
1500
1502
|
def eager_graph_make_associations_unique(records, dependency_map, alias_map, type_map)
|