sequel 5.44.0 → 5.48.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.
- checksums.yaml +4 -4
- data/CHANGELOG +38 -0
- data/README.rdoc +1 -2
- data/doc/association_basics.rdoc +70 -11
- data/doc/migration.rdoc +11 -5
- data/doc/querying.rdoc +1 -1
- data/doc/release_notes/5.45.0.txt +34 -0
- data/doc/release_notes/5.46.0.txt +87 -0
- data/doc/release_notes/5.47.0.txt +59 -0
- data/doc/release_notes/5.48.0.txt +14 -0
- data/doc/sql.rdoc +12 -0
- data/doc/testing.rdoc +4 -0
- data/doc/virtual_rows.rdoc +1 -1
- data/lib/sequel/adapters/odbc.rb +5 -1
- data/lib/sequel/adapters/shared/mssql.rb +15 -2
- data/lib/sequel/adapters/shared/mysql.rb +17 -0
- data/lib/sequel/adapters/shared/postgres.rb +0 -12
- data/lib/sequel/adapters/shared/sqlite.rb +53 -7
- data/lib/sequel/database/schema_methods.rb +1 -1
- data/lib/sequel/dataset/query.rb +2 -4
- data/lib/sequel/dataset/sql.rb +7 -0
- data/lib/sequel/extensions/pg_loose_count.rb +3 -1
- data/lib/sequel/extensions/schema_dumper.rb +11 -0
- data/lib/sequel/model/associations.rb +236 -81
- data/lib/sequel/model/errors.rb +10 -1
- data/lib/sequel/plugins/auto_validations_constraint_validations_presence_message.rb +68 -0
- data/lib/sequel/plugins/many_through_many.rb +108 -9
- data/lib/sequel/plugins/pg_array_associations.rb +46 -34
- data/lib/sequel/plugins/prepared_statements.rb +10 -1
- data/lib/sequel/plugins/timestamps.rb +1 -1
- data/lib/sequel/plugins/unused_associations.rb +521 -0
- data/lib/sequel/plugins/validation_helpers.rb +5 -8
- data/lib/sequel/version.rb +1 -1
- metadata +13 -3
@@ -0,0 +1,14 @@
|
|
1
|
+
= New Features
|
2
|
+
|
3
|
+
* A Sequel::Database#like_without_collate accessor has been added on
|
4
|
+
Microsoft SQL Server, which avoids using the COLLATE clause for
|
5
|
+
LIKE expressions. This can speed up query performance significantly.
|
6
|
+
|
7
|
+
* A private Sequel::Model::Errors#full_message method has been added
|
8
|
+
to make it easier to support internationalization for Sequel::Model
|
9
|
+
error messages.
|
10
|
+
|
11
|
+
= Other Improvements
|
12
|
+
|
13
|
+
* The association reflection tracking in the unused_associations
|
14
|
+
plugin now works correctly when combining coverage runs.
|
data/doc/sql.rdoc
CHANGED
@@ -428,6 +428,18 @@ As you can see, these literalize with ANDs by default. You can use the <tt>Sequ
|
|
428
428
|
|
429
429
|
Sequel.or(column1: 1, column2: 2) # (("column1" = 1) OR ("column2" = 2))
|
430
430
|
|
431
|
+
As you can see in the above examples, <tt>Sequel.|</tt> and <tt>Sequel.or</tt> work differently.
|
432
|
+
<tt>Sequel.|</tt> is for combining an arbitrary number of expressions using OR. If you pass a single
|
433
|
+
argument, <tt>Sequel.|</tt> will just convert it to a Sequel expression, similar to <tt>Sequel.expr</tt>.
|
434
|
+
<tt>Sequel.or</tt> is for taking a single hash or array of two element arrays and combining the
|
435
|
+
elements of that single argument using OR instead of AND:
|
436
|
+
|
437
|
+
Sequel.|(column1: 1, column2: 2) # (("column1" = 1) AND ("column2" = 2))
|
438
|
+
Sequel.or(column1: 1, column2: 2) # (("column1" = 1) OR ("column2" = 2))
|
439
|
+
|
440
|
+
Sequel.|({column1: 1}, {column2: 2}) # (("column1" = 1) OR ("column2" = 2))
|
441
|
+
Sequel.or({column1: 1}, {column2: 2}) # ArgumentError
|
442
|
+
|
431
443
|
You've already seen the <tt>Sequel.negate</tt> method, which will use ANDs if multiple entries are used:
|
432
444
|
|
433
445
|
Sequel.negate(column1: 1, column2: 2) # (("column1" != 1) AND ("column2" != 2))
|
data/doc/testing.rdoc
CHANGED
@@ -175,6 +175,10 @@ SEQUEL_NO_CACHE_ASSOCIATIONS :: Don't cache association metadata when running th
|
|
175
175
|
SEQUEL_CHECK_PENDING :: Try running all specs (note, can cause lockups for some adapters), and raise errors for skipped specs that don't fail
|
176
176
|
SEQUEL_NO_PENDING :: Don't skip any specs, try running all specs (note, can cause lockups for some adapters)
|
177
177
|
SEQUEL_PG_TIMESTAMPTZ :: Use the pg_timestamptz extension when running the postgres specs
|
178
|
+
SEQUEL_QUERY_PER_ASSOCIATION_DB_0_URL :: Run query-per-association integration tests with multiple databases (all 4 must be set to run)
|
179
|
+
SEQUEL_QUERY_PER_ASSOCIATION_DB_1_URL :: Run query-per-association integration tests with multiple databases (all 4 must be set to run)
|
180
|
+
SEQUEL_QUERY_PER_ASSOCIATION_DB_2_URL :: Run query-per-association integration tests with multiple databases (all 4 must be set to run)
|
181
|
+
SEQUEL_QUERY_PER_ASSOCIATION_DB_3_URL :: Run query-per-association integration tests with multiple databases (all 4 must be set to run)
|
178
182
|
SEQUEL_SPLIT_SYMBOLS :: Turn on symbol splitting when running the adapter and integration specs
|
179
183
|
SEQUEL_SYNCHRONIZE_SQL :: Use the synchronize_sql extension when running the specs
|
180
184
|
SEQUEL_TZINFO_VERSION :: Force the given tzinfo version when running the specs (e.g. '>=2')
|
data/doc/virtual_rows.rdoc
CHANGED
data/lib/sequel/adapters/odbc.rb
CHANGED
@@ -94,7 +94,11 @@ module Sequel
|
|
94
94
|
self.columns = columns
|
95
95
|
s.each do |row|
|
96
96
|
hash = {}
|
97
|
-
cols.each
|
97
|
+
cols.each do |n,t,j|
|
98
|
+
v = row[j]
|
99
|
+
# We can assume v is not false, so this shouldn't convert false to nil.
|
100
|
+
hash[n] = (convert_odbc_value(v, t) if v)
|
101
|
+
end
|
98
102
|
yield hash
|
99
103
|
end
|
100
104
|
end
|
@@ -24,6 +24,10 @@ module Sequel
|
|
24
24
|
# Database object.
|
25
25
|
attr_accessor :mssql_unicode_strings
|
26
26
|
|
27
|
+
# Whether to use LIKE without COLLATE Latin1_General_CS_AS. Skipping the COLLATE
|
28
|
+
# can significantly increase performance in some cases.
|
29
|
+
attr_accessor :like_without_collate
|
30
|
+
|
27
31
|
# Execute the given stored procedure with the given name.
|
28
32
|
#
|
29
33
|
# Options:
|
@@ -548,9 +552,9 @@ module Sequel
|
|
548
552
|
when :'||'
|
549
553
|
super(sql, :+, args)
|
550
554
|
when :LIKE, :"NOT LIKE"
|
551
|
-
super(sql, op, args
|
555
|
+
super(sql, op, complex_expression_sql_like_args(args, " COLLATE Latin1_General_CS_AS)"))
|
552
556
|
when :ILIKE, :"NOT ILIKE"
|
553
|
-
super(sql, (op == :ILIKE ? :LIKE : :"NOT LIKE"), args
|
557
|
+
super(sql, (op == :ILIKE ? :LIKE : :"NOT LIKE"), complex_expression_sql_like_args(args, " COLLATE Latin1_General_CI_AS)"))
|
554
558
|
when :<<, :>>
|
555
559
|
complex_expression_emulate_append(sql, op, args)
|
556
560
|
when :extract
|
@@ -847,6 +851,15 @@ module Sequel
|
|
847
851
|
server_version >= 11000000
|
848
852
|
end
|
849
853
|
|
854
|
+
# Determine whether to add the COLLATE for LIKE arguments, based on the Database setting.
|
855
|
+
def complex_expression_sql_like_args(args, collation)
|
856
|
+
if db.like_without_collate
|
857
|
+
args
|
858
|
+
else
|
859
|
+
args.map{|a| Sequel.lit(["(", collation], a)}
|
860
|
+
end
|
861
|
+
end
|
862
|
+
|
850
863
|
# Use strict ISO-8601 format with T between date and time,
|
851
864
|
# since that is the format that is multilanguage and not
|
852
865
|
# DATEFORMAT dependent.
|
@@ -187,6 +187,15 @@ module Sequel
|
|
187
187
|
def views(opts=OPTS)
|
188
188
|
full_tables('VIEW', opts)
|
189
189
|
end
|
190
|
+
|
191
|
+
# Renames multiple tables in a single call.
|
192
|
+
#
|
193
|
+
# DB.rename_tables [:items, :old_items], [:other_items, :old_other_items]
|
194
|
+
# # RENAME TABLE items TO old_items, other_items TO old_other_items
|
195
|
+
def rename_tables(*renames)
|
196
|
+
execute_ddl(rename_tables_sql(renames))
|
197
|
+
renames.each{|from,| remove_cached_schema(from)}
|
198
|
+
end
|
190
199
|
|
191
200
|
private
|
192
201
|
|
@@ -473,6 +482,14 @@ module Sequel
|
|
473
482
|
schema(table).select{|a| a[1][:primary_key]}.map{|a| a[0]}
|
474
483
|
end
|
475
484
|
|
485
|
+
# SQL statement for renaming multiple tables.
|
486
|
+
def rename_tables_sql(renames)
|
487
|
+
rename_tos = renames.map do |from, to|
|
488
|
+
"#{quote_schema_table(from)} TO #{quote_schema_table(to)}"
|
489
|
+
end.join(', ')
|
490
|
+
"RENAME TABLE #{rename_tos}"
|
491
|
+
end
|
492
|
+
|
476
493
|
# Rollback the currently open XA transaction
|
477
494
|
def rollback_transaction(conn, opts=OPTS)
|
478
495
|
if (s = opts[:prepare]) && savepoint_level(conn) <= 1
|
@@ -2141,18 +2141,6 @@ module Sequel
|
|
2141
2141
|
opts[:with].any?{|w| w[:recursive]} ? "WITH RECURSIVE " : super
|
2142
2142
|
end
|
2143
2143
|
|
2144
|
-
# Support WITH AS [NOT] MATERIALIZED if :materialized option is used.
|
2145
|
-
def select_with_sql_prefix(sql, w)
|
2146
|
-
super
|
2147
|
-
|
2148
|
-
case w[:materialized]
|
2149
|
-
when true
|
2150
|
-
sql << "MATERIALIZED "
|
2151
|
-
when false
|
2152
|
-
sql << "NOT MATERIALIZED "
|
2153
|
-
end
|
2154
|
-
end
|
2155
|
-
|
2156
2144
|
# The version of the database server
|
2157
2145
|
def server_version
|
2158
2146
|
db.server_version(@opts[:server])
|
@@ -239,8 +239,12 @@ module Sequel
|
|
239
239
|
super
|
240
240
|
end
|
241
241
|
when :drop_column
|
242
|
-
|
243
|
-
|
242
|
+
if sqlite_version >= 33500
|
243
|
+
super
|
244
|
+
else
|
245
|
+
ocp = lambda{|oc| oc.delete_if{|c| c.to_s == op[:name].to_s}}
|
246
|
+
duplicate_table(table, :old_columns_proc=>ocp){|columns| columns.delete_if{|s| s[:name].to_s == op[:name].to_s}}
|
247
|
+
end
|
244
248
|
when :rename_column
|
245
249
|
if sqlite_version >= 32500
|
246
250
|
super
|
@@ -558,10 +562,10 @@ module Sequel
|
|
558
562
|
EXTRACT_MAP = {:year=>"'%Y'", :month=>"'%m'", :day=>"'%d'", :hour=>"'%H'", :minute=>"'%M'", :second=>"'%f'"}.freeze
|
559
563
|
EXTRACT_MAP.each_value(&:freeze)
|
560
564
|
|
561
|
-
Dataset.def_sql_method(self, :delete, [['if db.sqlite_version >= 30803', %w'with delete from where'], ["else", %w'delete from where']])
|
562
|
-
Dataset.def_sql_method(self, :insert, [['if db.sqlite_version >= 30803', %w'with insert conflict into columns values on_conflict'], ["else", %w'insert conflict into columns values']])
|
565
|
+
Dataset.def_sql_method(self, :delete, [['if db.sqlite_version >= 33500', %w'with delete from where returning'], ['elsif db.sqlite_version >= 30803', %w'with delete from where'], ["else", %w'delete from where']])
|
566
|
+
Dataset.def_sql_method(self, :insert, [['if db.sqlite_version >= 33500', %w'with insert conflict into columns values on_conflict returning'], ['elsif db.sqlite_version >= 30803', %w'with insert conflict into columns values on_conflict'], ["else", %w'insert conflict into columns values']])
|
563
567
|
Dataset.def_sql_method(self, :select, [['if opts[:values]', %w'with values compounds'], ['else', %w'with select distinct columns from join where group having window compounds order limit lock']])
|
564
|
-
Dataset.def_sql_method(self, :update, [['if db.sqlite_version >= 33300', %w'with update table set from where'], ['elsif db.sqlite_version >= 30803', %w'with update table set where'], ["else", %w'update table set where']])
|
568
|
+
Dataset.def_sql_method(self, :update, [['if db.sqlite_version >= 33500', %w'with update table set from where returning'], ['elsif db.sqlite_version >= 33300', %w'with update table set from where'], ['elsif db.sqlite_version >= 30803', %w'with update table set where'], ["else", %w'update table set where']])
|
565
569
|
|
566
570
|
def cast_sql_append(sql, expr, type)
|
567
571
|
if type == Time or type == DateTime
|
@@ -635,8 +639,8 @@ module Sequel
|
|
635
639
|
# SQLite performs a TRUNCATE style DELETE if no filter is specified.
|
636
640
|
# Since we want to always return the count of records, add a condition
|
637
641
|
# that is always true and then delete.
|
638
|
-
def delete
|
639
|
-
@opts[:where] ? super : where(1=>1).delete
|
642
|
+
def delete(&block)
|
643
|
+
@opts[:where] ? super : where(1=>1).delete(&block)
|
640
644
|
end
|
641
645
|
|
642
646
|
# Return an array of strings specifying a query explanation for a SELECT of the
|
@@ -657,6 +661,21 @@ module Sequel
|
|
657
661
|
super
|
658
662
|
end
|
659
663
|
|
664
|
+
# Support insert select for associations, so that the model code can use
|
665
|
+
# returning instead of a separate query.
|
666
|
+
def insert_select(*values)
|
667
|
+
return unless supports_insert_select?
|
668
|
+
# Handle case where query does not return a row
|
669
|
+
server?(:default).with_sql_first(insert_select_sql(*values)) || false
|
670
|
+
end
|
671
|
+
|
672
|
+
# The SQL to use for an insert_select, adds a RETURNING clause to the insert
|
673
|
+
# unless the RETURNING clause is already present.
|
674
|
+
def insert_select_sql(*values)
|
675
|
+
ds = opts[:returning] ? self : returning
|
676
|
+
ds.insert_sql(*values)
|
677
|
+
end
|
678
|
+
|
660
679
|
# SQLite uses the nonstandard ` (backtick) for quoting identifiers.
|
661
680
|
def quoted_identifier_append(sql, c)
|
662
681
|
sql << '`' << c.to_s.gsub('`', '``') << '`'
|
@@ -738,6 +757,13 @@ module Sequel
|
|
738
757
|
insert_conflict(:ignore)
|
739
758
|
end
|
740
759
|
|
760
|
+
# Automatically add aliases to RETURNING values to work around SQLite bug.
|
761
|
+
def returning(*values)
|
762
|
+
return super if values.empty?
|
763
|
+
raise Error, "RETURNING is not supported on #{db.database_type}" unless supports_returning?(:insert)
|
764
|
+
clone(:returning=>_returning_values(values).freeze)
|
765
|
+
end
|
766
|
+
|
741
767
|
# SQLite 3.8.3+ supports common table expressions.
|
742
768
|
def supports_cte?(type=:select)
|
743
769
|
db.sqlite_version >= 30803
|
@@ -778,6 +804,11 @@ module Sequel
|
|
778
804
|
false
|
779
805
|
end
|
780
806
|
|
807
|
+
# SQLite 3.35.0 supports RETURNING on INSERT/UPDATE/DELETE.
|
808
|
+
def supports_returning?(_)
|
809
|
+
db.sqlite_version >= 33500
|
810
|
+
end
|
811
|
+
|
781
812
|
# SQLite supports timezones in literal timestamps, since it stores them
|
782
813
|
# as text. But using timezones in timestamps breaks SQLite datetime
|
783
814
|
# functions, so we allow the user to override the default per database.
|
@@ -810,6 +841,21 @@ module Sequel
|
|
810
841
|
|
811
842
|
private
|
812
843
|
|
844
|
+
# Add aliases to symbols and identifiers to work around SQLite bug.
|
845
|
+
def _returning_values(values)
|
846
|
+
values.map do |v|
|
847
|
+
case v
|
848
|
+
when Symbol
|
849
|
+
_, c, a = split_symbol(v)
|
850
|
+
a ? v : Sequel.as(v, c)
|
851
|
+
when SQL::Identifier, SQL::QualifiedIdentifier
|
852
|
+
Sequel.as(v, unqualified_column_for(v))
|
853
|
+
else
|
854
|
+
v
|
855
|
+
end
|
856
|
+
end
|
857
|
+
end
|
858
|
+
|
813
859
|
# SQLite uses string literals instead of identifiers in AS clauses.
|
814
860
|
def as_sql_append(sql, aliaz, column_aliases=nil)
|
815
861
|
raise Error, "sqlite does not support derived column lists" if column_aliases
|
@@ -63,7 +63,7 @@ module Sequel
|
|
63
63
|
# definitions using <tt>create_table</tt>, and +add_index+ accepts all the options
|
64
64
|
# available for index definition.
|
65
65
|
#
|
66
|
-
# See <tt>Schema::AlterTableGenerator</tt> and the {
|
66
|
+
# See <tt>Schema::AlterTableGenerator</tt> and the {Migrations guide}[rdoc-ref:doc/migration.rdoc].
|
67
67
|
def alter_table(name, &block)
|
68
68
|
generator = alter_table_generator(&block)
|
69
69
|
remove_cached_schema(name)
|
data/lib/sequel/dataset/query.rb
CHANGED
@@ -699,7 +699,7 @@ module Sequel
|
|
699
699
|
end
|
700
700
|
|
701
701
|
# Returns a copy of the dataset with a specified order. Can be safely combined with limit.
|
702
|
-
# If you call limit with an offset, it will override
|
702
|
+
# If you call limit with an offset, it will override the offset if you've called
|
703
703
|
# offset first.
|
704
704
|
#
|
705
705
|
# DB[:items].offset(10) # SELECT * FROM items OFFSET 10
|
@@ -1062,10 +1062,8 @@ module Sequel
|
|
1062
1062
|
# Options:
|
1063
1063
|
# :args :: Specify the arguments/columns for the CTE, should be an array of symbols.
|
1064
1064
|
# :recursive :: Specify that this is a recursive CTE
|
1065
|
-
#
|
1066
|
-
# PostgreSQL Specific Options:
|
1067
1065
|
# :materialized :: Set to false to force inlining of the CTE, or true to force not inlining
|
1068
|
-
# the CTE (PostgreSQL 12+).
|
1066
|
+
# the CTE (PostgreSQL 12+/SQLite 3.35+).
|
1069
1067
|
#
|
1070
1068
|
# DB[:items].with(:items, DB[:syx].where(Sequel[:name].like('A%')))
|
1071
1069
|
# # WITH items AS (SELECT * FROM syx WHERE (name LIKE 'A%' ESCAPE '\')) SELECT * FROM items
|
data/lib/sequel/dataset/sql.rb
CHANGED
@@ -1567,6 +1567,13 @@ module Sequel
|
|
1567
1567
|
sql << ')'
|
1568
1568
|
end
|
1569
1569
|
sql << ' AS '
|
1570
|
+
|
1571
|
+
case w[:materialized]
|
1572
|
+
when true
|
1573
|
+
sql << "MATERIALIZED "
|
1574
|
+
when false
|
1575
|
+
sql << "NOT MATERIALIZED "
|
1576
|
+
end
|
1570
1577
|
end
|
1571
1578
|
|
1572
1579
|
# Whether the symbol cache should be skipped when literalizing the dataset
|
@@ -12,7 +12,9 @@
|
|
12
12
|
#
|
13
13
|
# How accurate this count is depends on the number of rows
|
14
14
|
# added/deleted from the table since the last time it was
|
15
|
-
# analyzed.
|
15
|
+
# analyzed. If the table has not been vacuumed or analyzed
|
16
|
+
# yet, this can return 0 or -1 depending on the PostgreSQL
|
17
|
+
# version in use.
|
16
18
|
#
|
17
19
|
# To load the extension into the database:
|
18
20
|
#
|
@@ -6,6 +6,17 @@
|
|
6
6
|
# the current database). The main interface is through
|
7
7
|
# Sequel::Database#dump_schema_migration.
|
8
8
|
#
|
9
|
+
# The schema_dumper extension is quite limited in what types of
|
10
|
+
# database objects it supports. In general, it only supports
|
11
|
+
# dumping tables, columns, primary key and foreign key constraints,
|
12
|
+
# and some indexes. It does not support most table options, CHECK
|
13
|
+
# constraints, partial indexes, database functions, triggers,
|
14
|
+
# security grants/revokes, and a wide variety of other useful
|
15
|
+
# database properties. Be aware of the limitations when using the
|
16
|
+
# schema_dumper extension. If you are dumping the schema to restore
|
17
|
+
# to the same database type, it is recommended to use your database's
|
18
|
+
# dump and restore programs instead of the schema_dumper extension.
|
19
|
+
#
|
9
20
|
# To load the extension:
|
10
21
|
#
|
11
22
|
# DB.extension :schema_dumper
|
@@ -274,7 +274,9 @@ module Sequel
|
|
274
274
|
cascade = eo[:associations]
|
275
275
|
eager_limit = nil
|
276
276
|
|
277
|
-
if eo[:
|
277
|
+
if eo[:no_results]
|
278
|
+
no_results = true
|
279
|
+
elsif eo[:eager_block] || eo[:loader] == false
|
278
280
|
ds = eager_loading_dataset(eo)
|
279
281
|
|
280
282
|
strategy = ds.opts[:eager_limit_strategy] || strategy
|
@@ -313,7 +315,7 @@ module Sequel
|
|
313
315
|
objects = loader.all(ids)
|
314
316
|
end
|
315
317
|
|
316
|
-
Sequel.synchronize_with(eo[:mutex]){objects.each(&block)}
|
318
|
+
Sequel.synchronize_with(eo[:mutex]){objects.each(&block)} unless no_results
|
317
319
|
|
318
320
|
if strategy == :ruby
|
319
321
|
apply_ruby_eager_limit_strategy(rows, eager_limit || limit_and_offset)
|
@@ -638,9 +640,7 @@ module Sequel
|
|
638
640
|
# given the hash passed to the eager loader.
|
639
641
|
def eager_loading_dataset(eo=OPTS)
|
640
642
|
ds = eo[:dataset] || associated_eager_dataset
|
641
|
-
|
642
|
-
ds = ds.where(eager_loading_predicate_condition(id_map.keys))
|
643
|
-
end
|
643
|
+
ds = eager_loading_set_predicate_condition(ds, eo)
|
644
644
|
if associations = eo[:associations]
|
645
645
|
ds = ds.eager(associations)
|
646
646
|
end
|
@@ -667,6 +667,15 @@ module Sequel
|
|
667
667
|
self[:model].default_eager_limit_strategy || :ruby
|
668
668
|
end
|
669
669
|
|
670
|
+
# Set the predicate condition for the eager loading dataset based on the id map
|
671
|
+
# in the eager loading options.
|
672
|
+
def eager_loading_set_predicate_condition(ds, eo)
|
673
|
+
if id_map = eo[:id_map]
|
674
|
+
ds = ds.where(eager_loading_predicate_condition(id_map.keys))
|
675
|
+
end
|
676
|
+
ds
|
677
|
+
end
|
678
|
+
|
670
679
|
# The predicate condition to use for the eager_loader.
|
671
680
|
def eager_loading_predicate_condition(keys)
|
672
681
|
{predicate_key=>keys}
|
@@ -1318,7 +1327,7 @@ module Sequel
|
|
1318
1327
|
|
1319
1328
|
# many_to_many associations need to select a key in an associated table to eagerly load
|
1320
1329
|
def eager_loading_use_associated_key?
|
1321
|
-
|
1330
|
+
!separate_query_per_table?
|
1322
1331
|
end
|
1323
1332
|
|
1324
1333
|
# The source of the join table. This is the join table itself, unless it
|
@@ -1375,10 +1384,30 @@ module Sequel
|
|
1375
1384
|
cached_fetch(:select){default_select}
|
1376
1385
|
end
|
1377
1386
|
|
1387
|
+
# Whether a separate query should be used for the join table.
|
1388
|
+
def separate_query_per_table?
|
1389
|
+
self[:join_table_db]
|
1390
|
+
end
|
1391
|
+
|
1378
1392
|
private
|
1379
1393
|
|
1394
|
+
# Join to the the join table, unless using a separate query per table.
|
1380
1395
|
def _associated_dataset
|
1381
|
-
|
1396
|
+
if separate_query_per_table?
|
1397
|
+
super
|
1398
|
+
else
|
1399
|
+
super.inner_join(self[:join_table], self[:right_keys].zip(right_primary_keys), :qualify=>:deep)
|
1400
|
+
end
|
1401
|
+
end
|
1402
|
+
|
1403
|
+
# Use the right_keys from the eager loading options if
|
1404
|
+
# using a separate query per table.
|
1405
|
+
def eager_loading_set_predicate_condition(ds, eo)
|
1406
|
+
if separate_query_per_table?
|
1407
|
+
ds.where(right_primary_key=>eo[:right_keys])
|
1408
|
+
else
|
1409
|
+
super
|
1410
|
+
end
|
1382
1411
|
end
|
1383
1412
|
|
1384
1413
|
# The default selection for associations that require joins. These do not use the default
|
@@ -1595,6 +1624,7 @@ module Sequel
|
|
1595
1624
|
# === Multiple Types
|
1596
1625
|
# :adder :: Proc used to define the private _add_* method for doing the database work
|
1597
1626
|
# to associate the given object to the current object (*_to_many assocations).
|
1627
|
+
# Set to nil to not define a add_* method for the association.
|
1598
1628
|
# :after_add :: Symbol, Proc, or array of both/either specifying a callback to call
|
1599
1629
|
# after a new item is added to the association.
|
1600
1630
|
# :after_load :: Symbol, Proc, or array of both/either specifying a callback to call
|
@@ -1605,6 +1635,8 @@ module Sequel
|
|
1605
1635
|
# after an item is set using the association setter method.
|
1606
1636
|
# :allow_eager :: If set to false, you cannot load the association eagerly
|
1607
1637
|
# via eager or eager_graph
|
1638
|
+
# :allow_eager_graph :: If set to false, you cannot load the association eagerly via eager_graph.
|
1639
|
+
# :allow_filtering_by :: If set to false, you cannot use the association when filtering
|
1608
1640
|
# :before_add :: Symbol, Proc, or array of both/either specifying a callback to call
|
1609
1641
|
# before a new item is added to the association.
|
1610
1642
|
# :before_remove :: Symbol, Proc, or array of both/either specifying a callback to call
|
@@ -1623,6 +1655,7 @@ module Sequel
|
|
1623
1655
|
# the class. <tt>class: 'Foo', class_namespace: 'Bar'</tt> looks for <tt>::Bar::Foo</tt>.)
|
1624
1656
|
# :clearer :: Proc used to define the private _remove_all_* method for doing the database work
|
1625
1657
|
# to remove all objects associated to the current object (*_to_many assocations).
|
1658
|
+
# Set to nil to not define a remove_all_* method for the association.
|
1626
1659
|
# :clone :: Merge the current options and block into the options and block used in defining
|
1627
1660
|
# the given association. Can be used to DRY up a bunch of similar associations that
|
1628
1661
|
# all share the same options such as :class and :key, while changing the order and block used.
|
@@ -1677,7 +1710,7 @@ module Sequel
|
|
1677
1710
|
# :graph_only_conditions :: The conditions to use on the SQL join when eagerly loading
|
1678
1711
|
# the association via +eager_graph+, instead of the default conditions specified by the
|
1679
1712
|
# foreign/primary keys. This option causes the :graph_conditions option to be ignored.
|
1680
|
-
# :graph_order ::
|
1713
|
+
# :graph_order :: the order to use when using eager_graph, instead of the default order. This should be used
|
1681
1714
|
# in the case where :order contains an identifier qualified by the table's name, which may not match
|
1682
1715
|
# the alias used when eager graphing. By setting this to the unqualified identifier, it will be
|
1683
1716
|
# automatically qualified when using eager_graph.
|
@@ -1689,6 +1722,10 @@ module Sequel
|
|
1689
1722
|
# limit (first element) and an offset (second element).
|
1690
1723
|
# :methods_module :: The module that methods the association creates will be placed into. Defaults
|
1691
1724
|
# to the module containing the model's columns.
|
1725
|
+
# :no_association_method :: Do not add a method for the association. This can save memory if the association
|
1726
|
+
# method is never used.
|
1727
|
+
# :no_dataset_method :: Do not add a method for the association dataset. This can save memory if the dataset
|
1728
|
+
# method is never used.
|
1692
1729
|
# :order :: the column(s) by which to order the association dataset. Can be a
|
1693
1730
|
# singular column symbol or an array of column symbols.
|
1694
1731
|
# :order_eager_graph :: Whether to add the association's order to the graphed dataset's order when graphing
|
@@ -1701,6 +1738,7 @@ module Sequel
|
|
1701
1738
|
# the current association's key(s). Set to nil to not use a reciprocal.
|
1702
1739
|
# :remover :: Proc used to define the private _remove_* method for doing the database work
|
1703
1740
|
# to remove the association between the given object and the current object (*_to_many assocations).
|
1741
|
+
# Set to nil to not define a remove_* method for the association.
|
1704
1742
|
# :select :: the columns to select. Defaults to the associated class's table_name.* in an association
|
1705
1743
|
# that uses joins, which means it doesn't include the attributes from the
|
1706
1744
|
# join table. If you want to include the join table attributes, you can
|
@@ -1709,6 +1747,7 @@ module Sequel
|
|
1709
1747
|
# the same name in both the join table and the associated table.
|
1710
1748
|
# :setter :: Proc used to define the private _*= method for doing the work to setup the assocation
|
1711
1749
|
# between the given object and the current object (*_to_one associations).
|
1750
|
+
# Set to nil to not define a setter method for the association.
|
1712
1751
|
# :subqueries_per_union :: The number of subqueries to use in each UNION query, for eager
|
1713
1752
|
# loading limited associations using the default :union strategy.
|
1714
1753
|
# :validate :: Set to false to not validate when implicitly saving any associated object.
|
@@ -1765,6 +1804,9 @@ module Sequel
|
|
1765
1804
|
# underscored, sorted, and joined with '_'.
|
1766
1805
|
# :join_table_block :: proc that can be used to modify the dataset used in the add/remove/remove_all
|
1767
1806
|
# methods. Should accept a dataset argument and return a modified dataset if present.
|
1807
|
+
# :join_table_db :: When retrieving records when using lazy loading or eager loading via +eager+, instead of
|
1808
|
+
# a join between to the join table and the associated table, use a separate query for the
|
1809
|
+
# join table using the given Database object.
|
1768
1810
|
# :left_key :: foreign key in join table that points to current model's
|
1769
1811
|
# primary key, as a symbol. Defaults to :"#{self.name.underscore}_id".
|
1770
1812
|
# Can use an array of symbols for a composite key association.
|
@@ -1841,8 +1883,7 @@ module Sequel
|
|
1841
1883
|
# Remove :class entry if it exists and is nil, to work with cached_fetch
|
1842
1884
|
opts.delete(:class) unless opts[:class]
|
1843
1885
|
|
1844
|
-
|
1845
|
-
def_association_instance_methods(opts)
|
1886
|
+
def_association(opts)
|
1846
1887
|
|
1847
1888
|
orig_opts.delete(:clone)
|
1848
1889
|
opts[:orig_class] = orig_opts[:class] || orig_opts[:class_name]
|
@@ -1956,6 +1997,13 @@ module Sequel
|
|
1956
1997
|
association_module(opts).send(:private, name)
|
1957
1998
|
end
|
1958
1999
|
|
2000
|
+
# Delegate to the type-specific association method to setup the
|
2001
|
+
# association, and define the association instance methods.
|
2002
|
+
def def_association(opts)
|
2003
|
+
send(:"def_#{opts[:type]}", opts)
|
2004
|
+
def_association_instance_methods(opts)
|
2005
|
+
end
|
2006
|
+
|
1959
2007
|
# Adds the association method to the association methods module.
|
1960
2008
|
def def_association_method(opts)
|
1961
2009
|
association_module_def(opts.association_method, opts) do |dynamic_opts=OPTS, &block|
|
@@ -1981,13 +2029,13 @@ module Sequel
|
|
1981
2029
|
opts[:setter_method] = :"#{opts[:name]}="
|
1982
2030
|
end
|
1983
2031
|
|
1984
|
-
association_module_def(opts.dataset_method, opts){_dataset(opts)}
|
2032
|
+
association_module_def(opts.dataset_method, opts){_dataset(opts)} unless opts[:no_dataset_method]
|
1985
2033
|
if opts[:block]
|
1986
2034
|
opts[:block_method] = Plugins.def_sequel_method(association_module(opts), "#{opts[:name]}_block", 1, &opts[:block])
|
1987
2035
|
end
|
1988
2036
|
opts[:dataset_opt_arity] = opts[:dataset].arity == 0 ? 0 : 1
|
1989
2037
|
opts[:dataset_opt_method] = Plugins.def_sequel_method(association_module(opts), "#{opts[:name]}_dataset_opt", opts[:dataset_opt_arity], &opts[:dataset])
|
1990
|
-
def_association_method(opts)
|
2038
|
+
def_association_method(opts) unless opts[:no_association_method]
|
1991
2039
|
|
1992
2040
|
return if opts[:read_only]
|
1993
2041
|
|
@@ -2031,7 +2079,7 @@ module Sequel
|
|
2031
2079
|
raise(Error, "mismatched number of right keys: #{rcks.inspect} vs #{rcpks.inspect}") unless rcks.length == rcpks.length
|
2032
2080
|
end
|
2033
2081
|
opts[:uses_left_composite_keys] = lcks.length > 1
|
2034
|
-
opts[:uses_right_composite_keys] = rcks.length > 1
|
2082
|
+
uses_rcks = opts[:uses_right_composite_keys] = rcks.length > 1
|
2035
2083
|
opts[:cartesian_product_number] ||= one_through_one ? 0 : 1
|
2036
2084
|
join_table = (opts[:join_table] ||= opts.default_join_table)
|
2037
2085
|
opts[:left_key_alias] ||= opts.default_associated_key_alias
|
@@ -2040,8 +2088,75 @@ module Sequel
|
|
2040
2088
|
opts[:after_load] ||= []
|
2041
2089
|
opts[:after_load].unshift(:array_uniq!)
|
2042
2090
|
end
|
2043
|
-
opts[:
|
2044
|
-
|
2091
|
+
if join_table_db = opts[:join_table_db]
|
2092
|
+
opts[:use_placeholder_loader] = false
|
2093
|
+
opts[:allow_eager_graph] = false
|
2094
|
+
opts[:allow_filtering_by] = false
|
2095
|
+
opts[:eager_limit_strategy] = nil
|
2096
|
+
join_table_ds = join_table_db.from(join_table)
|
2097
|
+
opts[:dataset] ||= proc do |r|
|
2098
|
+
vals = join_table_ds.where(lcks.zip(lcpks.map{|k| get_column_value(k)})).select_map(right)
|
2099
|
+
ds = r.associated_dataset.where(opts.right_primary_key => vals)
|
2100
|
+
if uses_rcks
|
2101
|
+
vals.delete_if{|v| v.any?(&:nil?)}
|
2102
|
+
else
|
2103
|
+
vals.delete(nil)
|
2104
|
+
end
|
2105
|
+
ds = ds.clone(:no_results=>true) if vals.empty?
|
2106
|
+
ds
|
2107
|
+
end
|
2108
|
+
opts[:eager_loader] ||= proc do |eo|
|
2109
|
+
h = eo[:id_map]
|
2110
|
+
assign_singular = opts.assign_singular?
|
2111
|
+
rpk = opts.right_primary_key
|
2112
|
+
name = opts[:name]
|
2113
|
+
|
2114
|
+
join_map = join_table_ds.where(left=>h.keys).select_hash_groups(right, left)
|
2115
|
+
|
2116
|
+
if uses_rcks
|
2117
|
+
join_map.delete_if{|v,| v.any?(&:nil?)}
|
2118
|
+
else
|
2119
|
+
join_map.delete(nil)
|
2120
|
+
end
|
2121
|
+
|
2122
|
+
eo = Hash[eo]
|
2123
|
+
|
2124
|
+
if join_map.empty?
|
2125
|
+
eo[:no_results] = true
|
2126
|
+
else
|
2127
|
+
join_map.each_value do |vs|
|
2128
|
+
vs.replace(vs.flat_map{|v| h[v]})
|
2129
|
+
vs.uniq!
|
2130
|
+
end
|
2131
|
+
|
2132
|
+
eo[:loader] = false
|
2133
|
+
eo[:right_keys] = join_map.keys
|
2134
|
+
end
|
2135
|
+
|
2136
|
+
opts[:model].eager_load_results(opts, eo) do |assoc_record|
|
2137
|
+
rpkv = if uses_rcks
|
2138
|
+
assoc_record.values.values_at(*rpk)
|
2139
|
+
else
|
2140
|
+
assoc_record.values[rpk]
|
2141
|
+
end
|
2142
|
+
|
2143
|
+
objects = join_map[rpkv]
|
2144
|
+
|
2145
|
+
if assign_singular
|
2146
|
+
objects.each do |object|
|
2147
|
+
object.associations[name] ||= assoc_record
|
2148
|
+
end
|
2149
|
+
else
|
2150
|
+
objects.each do |object|
|
2151
|
+
object.associations[name].push(assoc_record)
|
2152
|
+
end
|
2153
|
+
end
|
2154
|
+
end
|
2155
|
+
end
|
2156
|
+
else
|
2157
|
+
opts[:dataset] ||= opts.association_dataset_proc
|
2158
|
+
opts[:eager_loader] ||= opts.method(:default_eager_loader)
|
2159
|
+
end
|
2045
2160
|
|
2046
2161
|
join_type = opts[:graph_join_type]
|
2047
2162
|
select = opts[:graph_select]
|
@@ -2075,50 +2190,60 @@ module Sequel
|
|
2075
2190
|
return if opts[:read_only]
|
2076
2191
|
|
2077
2192
|
if one_through_one
|
2078
|
-
opts
|
2079
|
-
|
2080
|
-
|
2081
|
-
|
2082
|
-
|
2083
|
-
checked_transaction do
|
2084
|
-
current = jtds.first
|
2193
|
+
unless opts.has_key?(:setter)
|
2194
|
+
opts[:setter] = proc do |o|
|
2195
|
+
h = {}
|
2196
|
+
lh = lcks.zip(lcpks.map{|k| get_column_value(k)})
|
2197
|
+
jtds = _join_table_dataset(opts).where(lh)
|
2085
2198
|
|
2086
|
-
|
2087
|
-
|
2088
|
-
rcks.zip(opts.right_primary_key_methods).each{|k, pk| new_values << (h[k] = o.get_column_value(pk))}
|
2089
|
-
end
|
2199
|
+
checked_transaction do
|
2200
|
+
current = jtds.first
|
2090
2201
|
|
2091
|
-
if current
|
2092
|
-
current_values = rcks.map{|k| current[k]}
|
2093
|
-
jtds = jtds.where(rcks.zip(current_values))
|
2094
2202
|
if o
|
2095
|
-
|
2096
|
-
|
2203
|
+
new_values = []
|
2204
|
+
rcks.zip(opts.right_primary_key_methods).each{|k, pk| new_values << (h[k] = o.get_column_value(pk))}
|
2205
|
+
end
|
2206
|
+
|
2207
|
+
if current
|
2208
|
+
current_values = rcks.map{|k| current[k]}
|
2209
|
+
jtds = jtds.where(rcks.zip(current_values))
|
2210
|
+
if o
|
2211
|
+
if current_values != new_values
|
2212
|
+
jtds.update(h)
|
2213
|
+
end
|
2214
|
+
else
|
2215
|
+
jtds.delete
|
2097
2216
|
end
|
2098
|
-
|
2099
|
-
|
2217
|
+
elsif o
|
2218
|
+
lh.each{|k,v| h[k] = v}
|
2219
|
+
jtds.insert(h)
|
2100
2220
|
end
|
2101
|
-
elsif o
|
2102
|
-
lh.each{|k,v| h[k] = v}
|
2103
|
-
jtds.insert(h)
|
2104
2221
|
end
|
2105
2222
|
end
|
2106
2223
|
end
|
2107
|
-
opts
|
2224
|
+
if opts.fetch(:setter, true)
|
2225
|
+
opts[:_setter] = proc{|o| set_one_through_one_associated_object(opts, o)}
|
2226
|
+
end
|
2108
2227
|
else
|
2109
|
-
opts
|
2110
|
-
|
2111
|
-
|
2112
|
-
|
2113
|
-
|
2228
|
+
unless opts.has_key?(:adder)
|
2229
|
+
opts[:adder] = proc do |o|
|
2230
|
+
h = {}
|
2231
|
+
lcks.zip(lcpks).each{|k, pk| h[k] = get_column_value(pk)}
|
2232
|
+
rcks.zip(opts.right_primary_key_methods).each{|k, pk| h[k] = o.get_column_value(pk)}
|
2233
|
+
_join_table_dataset(opts).insert(h)
|
2234
|
+
end
|
2114
2235
|
end
|
2115
2236
|
|
2116
|
-
opts
|
2117
|
-
|
2237
|
+
unless opts.has_key?(:remover)
|
2238
|
+
opts[:remover] = proc do |o|
|
2239
|
+
_join_table_dataset(opts).where(lcks.zip(lcpks.map{|k| get_column_value(k)}) + rcks.zip(opts.right_primary_key_methods.map{|k| o.get_column_value(k)})).delete
|
2240
|
+
end
|
2118
2241
|
end
|
2119
2242
|
|
2120
|
-
opts
|
2121
|
-
|
2243
|
+
unless opts.has_key?(:clearer)
|
2244
|
+
opts[:clearer] = proc do
|
2245
|
+
_join_table_dataset(opts).where(lcks.zip(lcpks.map{|k| get_column_value(k)})).delete
|
2246
|
+
end
|
2122
2247
|
end
|
2123
2248
|
end
|
2124
2249
|
end
|
@@ -2175,8 +2300,12 @@ module Sequel
|
|
2175
2300
|
|
2176
2301
|
return if opts[:read_only]
|
2177
2302
|
|
2178
|
-
|
2179
|
-
|
2303
|
+
unless opts.has_key?(:setter)
|
2304
|
+
opts[:setter] = proc{|o| cks.zip(opts.primary_key_methods).each{|k, pk| set_column_value(:"#{k}=", (o.get_column_value(pk) if o))}}
|
2305
|
+
end
|
2306
|
+
if opts.fetch(:setter, true)
|
2307
|
+
opts[:_setter] = proc{|o| set_associated_object(opts, o)}
|
2308
|
+
end
|
2180
2309
|
end
|
2181
2310
|
|
2182
2311
|
# Configures one_to_many and one_to_one association reflections and adds the related association methods
|
@@ -2243,49 +2372,59 @@ module Sequel
|
|
2243
2372
|
cks.each{|k| ck_nil_hash[k] = nil}
|
2244
2373
|
|
2245
2374
|
if one_to_one
|
2246
|
-
opts
|
2247
|
-
|
2375
|
+
unless opts.has_key?(:setter)
|
2376
|
+
opts[:setter] = proc do |o|
|
2377
|
+
up_ds = _apply_association_options(opts, opts.associated_dataset.where(cks.zip(cpks.map{|k| get_column_value(k)})))
|
2248
2378
|
|
2249
|
-
|
2250
|
-
|
2251
|
-
|
2379
|
+
if (froms = up_ds.opts[:from]) && (from = froms[0]) && (from.is_a?(Sequel::Dataset) || (from.is_a?(Sequel::SQL::AliasedExpression) && from.expression.is_a?(Sequel::Dataset)))
|
2380
|
+
if old = up_ds.first
|
2381
|
+
cks.each{|k| old.set_column_value(:"#{k}=", nil)}
|
2382
|
+
end
|
2383
|
+
save_old = true
|
2252
2384
|
end
|
2253
|
-
save_old = true
|
2254
|
-
end
|
2255
2385
|
|
2256
|
-
|
2257
|
-
|
2258
|
-
|
2386
|
+
if o
|
2387
|
+
if !o.new? && !save_old
|
2388
|
+
up_ds = up_ds.exclude(o.pk_hash)
|
2389
|
+
end
|
2390
|
+
cks.zip(cpks).each{|k, pk| o.set_column_value(:"#{k}=", get_column_value(pk))}
|
2259
2391
|
end
|
2260
|
-
cks.zip(cpks).each{|k, pk| o.set_column_value(:"#{k}=", get_column_value(pk))}
|
2261
|
-
end
|
2262
2392
|
|
2263
|
-
|
2264
|
-
|
2265
|
-
|
2266
|
-
|
2267
|
-
|
2268
|
-
|
2393
|
+
checked_transaction do
|
2394
|
+
if save_old
|
2395
|
+
old.save(save_opts) || raise(Sequel::Error, "invalid previously associated object, cannot save") if old
|
2396
|
+
else
|
2397
|
+
up_ds.skip_limit_check.update(ck_nil_hash)
|
2398
|
+
end
|
2269
2399
|
|
2270
|
-
|
2400
|
+
o.save(save_opts) || raise(Sequel::Error, "invalid associated object, cannot save") if o
|
2401
|
+
end
|
2271
2402
|
end
|
2272
2403
|
end
|
2273
|
-
opts
|
2404
|
+
if opts.fetch(:setter, true)
|
2405
|
+
opts[:_setter] = proc{|o| set_one_to_one_associated_object(opts, o)}
|
2406
|
+
end
|
2274
2407
|
else
|
2275
2408
|
save_opts[:raise_on_failure] = opts[:raise_on_save_failure] != false
|
2276
2409
|
|
2277
|
-
opts
|
2278
|
-
|
2279
|
-
|
2410
|
+
unless opts.has_key?(:adder)
|
2411
|
+
opts[:adder] = proc do |o|
|
2412
|
+
cks.zip(cpks).each{|k, pk| o.set_column_value(:"#{k}=", get_column_value(pk))}
|
2413
|
+
o.save(save_opts)
|
2414
|
+
end
|
2280
2415
|
end
|
2281
2416
|
|
2282
|
-
opts
|
2283
|
-
|
2284
|
-
|
2417
|
+
unless opts.has_key?(:remover)
|
2418
|
+
opts[:remover] = proc do |o|
|
2419
|
+
cks.each{|k| o.set_column_value(:"#{k}=", nil)}
|
2420
|
+
o.save(save_opts)
|
2421
|
+
end
|
2285
2422
|
end
|
2286
2423
|
|
2287
|
-
opts
|
2288
|
-
|
2424
|
+
unless opts.has_key?(:clearer)
|
2425
|
+
opts[:clearer] = proc do
|
2426
|
+
_apply_association_options(opts, opts.associated_dataset.where(cks.zip(cpks.map{|k| get_column_value(k)}))).update(ck_nil_hash)
|
2427
|
+
end
|
2289
2428
|
end
|
2290
2429
|
end
|
2291
2430
|
end
|
@@ -2379,7 +2518,7 @@ module Sequel
|
|
2379
2518
|
|
2380
2519
|
# Dataset for the join table of the given many to many association reflection
|
2381
2520
|
def _join_table_dataset(opts)
|
2382
|
-
ds = model.db.from(opts.join_table_source)
|
2521
|
+
ds = (opts[:join_table_db] || model.db).from(opts.join_table_source)
|
2383
2522
|
opts[:join_table_block] ? opts[:join_table_block].call(ds) : ds
|
2384
2523
|
end
|
2385
2524
|
|
@@ -2400,7 +2539,12 @@ module Sequel
|
|
2400
2539
|
if loader = _associated_object_loader(opts, dynamic_opts)
|
2401
2540
|
loader.all(*opts.predicate_key_values(self))
|
2402
2541
|
else
|
2403
|
-
_associated_dataset(opts, dynamic_opts)
|
2542
|
+
ds = _associated_dataset(opts, dynamic_opts)
|
2543
|
+
if ds.opts[:no_results]
|
2544
|
+
[]
|
2545
|
+
else
|
2546
|
+
ds.all
|
2547
|
+
end
|
2404
2548
|
end
|
2405
2549
|
end
|
2406
2550
|
|
@@ -2861,6 +3005,8 @@ module Sequel
|
|
2861
3005
|
(multiple = ((op == :IN || op == :'NOT IN') && ((is_ds = r.is_a?(Sequel::Dataset)) || (r.respond_to?(:all?) && r.all?{|x| x.is_a?(Sequel::Model)})))))
|
2862
3006
|
l = args[0]
|
2863
3007
|
if ar = model.association_reflections[l]
|
3008
|
+
raise Error, "filtering by associations is not allowed for #{ar.inspect}" if ar[:allow_filtering_by] == false
|
3009
|
+
|
2864
3010
|
if multiple
|
2865
3011
|
klass = ar.associated_class
|
2866
3012
|
if is_ds
|
@@ -3011,6 +3157,8 @@ module Sequel
|
|
3011
3157
|
# You can specify an custom alias and/or join type on a per-association basis by providing an
|
3012
3158
|
# Sequel::SQL::AliasedExpression object instead of an a Symbol for the association name.
|
3013
3159
|
#
|
3160
|
+
# You cannot mix calls to +eager_graph+ and +graph+ on the same dataset.
|
3161
|
+
#
|
3014
3162
|
# Examples:
|
3015
3163
|
#
|
3016
3164
|
# # For each album, eager_graph load the artist
|
@@ -3354,7 +3502,7 @@ module Sequel
|
|
3354
3502
|
# Allow associations that are eagerly graphed to be specified as an SQL::AliasedExpression, for
|
3355
3503
|
# per-call determining of the alias base.
|
3356
3504
|
def eager_graph_check_association(model, association)
|
3357
|
-
if association.is_a?(SQL::AliasedExpression)
|
3505
|
+
reflection = if association.is_a?(SQL::AliasedExpression)
|
3358
3506
|
expr = association.expression
|
3359
3507
|
if expr.is_a?(SQL::Identifier)
|
3360
3508
|
expr = expr.value
|
@@ -3363,10 +3511,17 @@ module Sequel
|
|
3363
3511
|
end
|
3364
3512
|
end
|
3365
3513
|
|
3366
|
-
|
3514
|
+
check_reflection = check_association(model, expr)
|
3515
|
+
SQL::AliasedExpression.new(check_reflection, association.alias || expr, association.columns)
|
3367
3516
|
else
|
3368
|
-
check_association(model, association)
|
3517
|
+
check_reflection = check_association(model, association)
|
3518
|
+
end
|
3519
|
+
|
3520
|
+
if check_reflection && check_reflection[:allow_eager_graph] == false
|
3521
|
+
raise Error, "eager_graph not allowed for #{reflection.inspect}"
|
3369
3522
|
end
|
3523
|
+
|
3524
|
+
reflection
|
3370
3525
|
end
|
3371
3526
|
|
3372
3527
|
# The EagerGraphLoader instance used for converting eager_graph results.
|