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.
@@ -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')
@@ -54,7 +54,7 @@ methods in the surrounding scope. For example:
54
54
 
55
55
  # Regular block
56
56
  ds.where{|o| o.c > a - b + @d}
57
- # WHERE (c > 100)
57
+ # WHERE (c > 110)
58
58
 
59
59
  # Instance-evaled block
60
60
  ds.where{c > a - b + @d}
@@ -94,7 +94,11 @@ module Sequel
94
94
  self.columns = columns
95
95
  s.each do |row|
96
96
  hash = {}
97
- cols.each{|n,t,j| hash[n] = convert_odbc_value(row[j], t)}
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.map{|a| Sequel.lit(["(", " COLLATE Latin1_General_CS_AS)"], a)})
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.map{|a| Sequel.lit(["(", " COLLATE Latin1_General_CI_AS)"], a)})
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
- ocp = lambda{|oc| oc.delete_if{|c| c.to_s == op[:name].to_s}}
243
- duplicate_table(table, :old_columns_proc=>ocp){|columns| columns.delete_if{|s| s[:name].to_s == op[:name].to_s}}
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 {"Migrations and Schema Modification" guide}[rdoc-ref:doc/migration.rdoc].
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)
@@ -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 override the offset if you've called
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
@@ -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[:eager_block] || eo[:loader] == false
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
- if id_map = eo[:id_map]
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
- true
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
- super.inner_join(self[:join_table], self[:right_keys].zip(right_primary_keys), :qualify=>:deep)
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 :: Over the order to use when using eager_graph, instead of the default order. This should be used
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
- send(:"def_#{type}", opts)
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[:dataset] ||= opts.association_dataset_proc
2044
- opts[:eager_loader] ||= opts.method(:default_eager_loader)
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[:setter] ||= proc do |o|
2079
- h = {}
2080
- lh = lcks.zip(lcpks.map{|k| get_column_value(k)})
2081
- jtds = _join_table_dataset(opts).where(lh)
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
- if o
2087
- new_values = []
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
- if current_values != new_values
2096
- jtds.update(h)
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
- else
2099
- jtds.delete
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[:_setter] = proc{|o| set_one_through_one_associated_object(opts, o)}
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[:adder] ||= proc do |o|
2110
- h = {}
2111
- lcks.zip(lcpks).each{|k, pk| h[k] = get_column_value(pk)}
2112
- rcks.zip(opts.right_primary_key_methods).each{|k, pk| h[k] = o.get_column_value(pk)}
2113
- _join_table_dataset(opts).insert(h)
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[:remover] ||= proc do |o|
2117
- _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
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[:clearer] ||= proc do
2121
- _join_table_dataset(opts).where(lcks.zip(lcpks.map{|k| get_column_value(k)})).delete
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
- opts[:setter] ||= proc{|o| cks.zip(opts.primary_key_methods).each{|k, pk| set_column_value(:"#{k}=", (o.get_column_value(pk) if o))}}
2179
- opts[:_setter] = proc{|o| set_associated_object(opts, o)}
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[:setter] ||= proc do |o|
2247
- up_ds = _apply_association_options(opts, opts.associated_dataset.where(cks.zip(cpks.map{|k| get_column_value(k)})))
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
- 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)))
2250
- if old = up_ds.first
2251
- cks.each{|k| old.set_column_value(:"#{k}=", nil)}
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
- if o
2257
- if !o.new? && !save_old
2258
- up_ds = up_ds.exclude(o.pk_hash)
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
- checked_transaction do
2264
- if save_old
2265
- old.save(save_opts) || raise(Sequel::Error, "invalid previously associated object, cannot save") if old
2266
- else
2267
- up_ds.skip_limit_check.update(ck_nil_hash)
2268
- end
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
- o.save(save_opts) || raise(Sequel::Error, "invalid associated object, cannot save") if o
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[:_setter] = proc{|o| set_one_to_one_associated_object(opts, o)}
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[:adder] ||= proc do |o|
2278
- cks.zip(cpks).each{|k, pk| o.set_column_value(:"#{k}=", get_column_value(pk))}
2279
- o.save(save_opts)
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[:remover] ||= proc do |o|
2283
- cks.each{|k| o.set_column_value(:"#{k}=", nil)}
2284
- o.save(save_opts)
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[:clearer] ||= proc do
2288
- _apply_association_options(opts, opts.associated_dataset.where(cks.zip(cpks.map{|k| get_column_value(k)}))).update(ck_nil_hash)
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).all
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
- SQL::AliasedExpression.new(check_association(model, expr), association.alias || expr, association.columns)
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.