sequel 5.44.0 → 5.48.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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.