sequel 3.13.0 → 3.14.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 +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)
|