sequel 3.43.0 → 3.44.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (73) hide show
  1. data/CHANGELOG +32 -0
  2. data/doc/association_basics.rdoc +10 -3
  3. data/doc/release_notes/3.37.0.txt +1 -1
  4. data/doc/release_notes/3.44.0.txt +152 -0
  5. data/lib/sequel/adapters/ado.rb +0 -6
  6. data/lib/sequel/adapters/db2.rb +0 -3
  7. data/lib/sequel/adapters/dbi.rb +0 -6
  8. data/lib/sequel/adapters/ibmdb.rb +0 -3
  9. data/lib/sequel/adapters/jdbc.rb +8 -12
  10. data/lib/sequel/adapters/jdbc/as400.rb +2 -18
  11. data/lib/sequel/adapters/jdbc/derby.rb +10 -0
  12. data/lib/sequel/adapters/jdbc/h2.rb +10 -0
  13. data/lib/sequel/adapters/jdbc/hsqldb.rb +10 -0
  14. data/lib/sequel/adapters/jdbc/sqlite.rb +5 -0
  15. data/lib/sequel/adapters/jdbc/sqlserver.rb +2 -4
  16. data/lib/sequel/adapters/mock.rb +5 -0
  17. data/lib/sequel/adapters/mysql2.rb +4 -13
  18. data/lib/sequel/adapters/odbc.rb +0 -5
  19. data/lib/sequel/adapters/oracle.rb +3 -5
  20. data/lib/sequel/adapters/postgres.rb +2 -1
  21. data/lib/sequel/adapters/shared/access.rb +10 -0
  22. data/lib/sequel/adapters/shared/cubrid.rb +9 -0
  23. data/lib/sequel/adapters/shared/db2.rb +10 -0
  24. data/lib/sequel/adapters/shared/mssql.rb +10 -0
  25. data/lib/sequel/adapters/shared/mysql.rb +14 -0
  26. data/lib/sequel/adapters/shared/oracle.rb +15 -0
  27. data/lib/sequel/adapters/shared/postgres.rb +26 -2
  28. data/lib/sequel/adapters/shared/sqlite.rb +15 -0
  29. data/lib/sequel/adapters/tinytds.rb +5 -32
  30. data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +2 -37
  31. data/lib/sequel/core.rb +3 -3
  32. data/lib/sequel/database/misc.rb +40 -4
  33. data/lib/sequel/database/query.rb +1 -1
  34. data/lib/sequel/database/schema_methods.rb +33 -12
  35. data/lib/sequel/dataset/actions.rb +51 -2
  36. data/lib/sequel/dataset/features.rb +0 -6
  37. data/lib/sequel/dataset/sql.rb +1 -1
  38. data/lib/sequel/exceptions.rb +22 -7
  39. data/lib/sequel/extensions/columns_introspection.rb +30 -5
  40. data/lib/sequel/extensions/pg_auto_parameterize.rb +9 -0
  41. data/lib/sequel/model/associations.rb +50 -37
  42. data/lib/sequel/model/base.rb +30 -1
  43. data/lib/sequel/plugins/eager_each.rb +17 -21
  44. data/lib/sequel/plugins/identity_map.rb +2 -1
  45. data/lib/sequel/plugins/many_through_many.rb +1 -1
  46. data/lib/sequel/plugins/single_table_inheritance.rb +2 -2
  47. data/lib/sequel/plugins/tactical_eager_loading.rb +1 -1
  48. data/lib/sequel/sql.rb +4 -2
  49. data/lib/sequel/version.rb +1 -1
  50. data/spec/adapters/postgres_spec.rb +32 -2
  51. data/spec/adapters/sqlite_spec.rb +20 -0
  52. data/spec/core/database_spec.rb +40 -0
  53. data/spec/core/dataset_spec.rb +91 -4
  54. data/spec/core/mock_adapter_spec.rb +2 -1
  55. data/spec/core/schema_generator_spec.rb +4 -0
  56. data/spec/core/schema_spec.rb +9 -3
  57. data/spec/extensions/association_dependencies_spec.rb +3 -3
  58. data/spec/extensions/columns_introspection_spec.rb +28 -2
  59. data/spec/extensions/eager_each_spec.rb +0 -1
  60. data/spec/extensions/identity_map_spec.rb +3 -2
  61. data/spec/extensions/migration_spec.rb +6 -0
  62. data/spec/extensions/prepared_statements_associations_spec.rb +2 -2
  63. data/spec/extensions/rcte_tree_spec.rb +2 -2
  64. data/spec/extensions/single_table_inheritance_spec.rb +3 -0
  65. data/spec/extensions/tactical_eager_loading_spec.rb +1 -1
  66. data/spec/extensions/validation_class_methods_spec.rb +8 -0
  67. data/spec/integration/associations_test.rb +4 -4
  68. data/spec/integration/database_test.rb +68 -20
  69. data/spec/integration/dataset_test.rb +48 -0
  70. data/spec/integration/schema_test.rb +25 -1
  71. data/spec/model/associations_spec.rb +21 -8
  72. data/spec/model/dataset_methods_spec.rb +58 -18
  73. metadata +4 -2
data/lib/sequel/core.rb CHANGED
@@ -380,14 +380,14 @@ module Sequel
380
380
  end
381
381
 
382
382
  # If the supplied block takes a single argument,
383
- # yield a new <tt>SQL::VirtualRow</tt> instance to the block
384
- # argument. Otherwise, evaluate the block in the context of a new
383
+ # yield an <tt>SQL::VirtualRow</tt> instance to the block
384
+ # argument. Otherwise, evaluate the block in the context of a
385
385
  # <tt>SQL::VirtualRow</tt> instance.
386
386
  #
387
387
  # Sequel.virtual_row{a} # Sequel::SQL::Identifier.new(:a)
388
388
  # Sequel.virtual_row{|o| o.a{}} # Sequel::SQL::Function.new(:a)
389
389
  def self.virtual_row(&block)
390
- vr = SQL::VirtualRow.new
390
+ vr = VIRTUAL_ROW
391
391
  case block.arity
392
392
  when -1, 0
393
393
  vr.instance_exec(&block)
@@ -14,6 +14,10 @@ module Sequel
14
14
  # instances.
15
15
  DEFAULT_STRING_COLUMN_SIZE = 255
16
16
 
17
+ # Empty exception regexp to class map, used by default if Sequel doesn't
18
+ # have specific support for the database in use.
19
+ DEFAULT_DATABASE_ERROR_REGEXPS = {}.freeze
20
+
17
21
  # Register an extension callback for Database objects. ext should be the
18
22
  # extension name symbol, and mod should either be a Module that the
19
23
  # database is extended with, or a callable object called with the database
@@ -316,27 +320,59 @@ module Sequel
316
320
  obj.respond_to?(:empty?) ? obj.empty? : false
317
321
  end
318
322
  end
319
-
323
+
320
324
  # Which transaction errors to translate, blank by default.
321
325
  def database_error_classes
322
326
  []
323
327
  end
324
328
 
329
+ # A hash with regexp keys and exception class values, used
330
+ # to match against underlying driver exception messages in
331
+ # order to raise a more specific Sequel::Error subclass.
332
+ def database_error_regexps
333
+ DEFAULT_DATABASE_ERROR_REGEXPS
334
+ end
335
+
336
+ # Return the Sequel::DatabaseError subclass to wrap the given
337
+ # exception in.
338
+ def database_error_class(exception, opts)
339
+ database_specific_error_class(exception, opts) || DatabaseError
340
+ end
341
+
342
+ # Return a specific Sequel::DatabaseError exception class if
343
+ # one is appropriate for the underlying exception,
344
+ # or nil if there is no specific exception class.
345
+ def database_specific_error_class(exception, opts)
346
+ return DatabaseDisconnectError if disconnect_error?(exception, opts)
347
+
348
+ database_error_regexps.each do |regexp, klass|
349
+ return klass if exception.message =~ regexp
350
+ end
351
+
352
+ nil
353
+ end
354
+
325
355
  # Return true if exception represents a disconnect error, false otherwise.
326
356
  def disconnect_error?(exception, opts)
327
357
  opts[:disconnect]
328
358
  end
329
359
 
330
- # Convert the given exception to a DatabaseError, keeping message
331
- # and traceback.
360
+ # Convert the given exception to an appropriate Sequel::DatabaseError
361
+ # subclass, keeping message and traceback.
332
362
  def raise_error(exception, opts={})
333
363
  if !opts[:classes] || Array(opts[:classes]).any?{|c| exception.is_a?(c)}
334
- raise Sequel.convert_exception_class(exception, disconnect_error?(exception, opts) ? DatabaseDisconnectError : DatabaseError)
364
+ raise Sequel.convert_exception_class(exception, database_error_class(exception, opts))
335
365
  else
336
366
  raise exception
337
367
  end
338
368
  end
339
369
 
370
+ # Whether the database supports CREATE OR REPLACE VIEW. If not, support
371
+ # will be emulated by dropping the view first. false by default.
372
+ def supports_create_or_replace_view?
373
+ false
374
+ end
375
+
340
376
  # Typecast the value to an SQL::Blob
341
377
  def typecast_value_blob(value)
342
378
  value.is_a?(Sequel::SQL::Blob) ? value : Sequel::SQL::Blob.new(value)
@@ -585,7 +585,7 @@ module Sequel
585
585
 
586
586
  # Remove the cached schema for the given schema name
587
587
  def remove_cached_schema(table)
588
- @schemas.delete(quote_schema_table(table)) if @schemas
588
+ Sequel.synchronize{@schemas.delete(quote_schema_table(table))} if @schemas
589
589
  end
590
590
 
591
591
  # Remove the current thread from the list of active transactions
@@ -202,24 +202,34 @@ module Sequel
202
202
  create_table_generator_class.new(self, &block)
203
203
  end
204
204
 
205
- # Creates a view, replacing it if it already exists:
205
+ # Creates a view, replacing a view with the same name if one already exists.
206
206
  #
207
- # DB.create_or_replace_view(:cheap_items, "SELECT * FROM items WHERE price < 100")
208
- # DB.create_or_replace_view(:ruby_items, DB[:items].filter(:category => 'ruby'))
209
- def create_or_replace_view(name, source)
210
- source = source.sql if source.is_a?(Dataset)
211
- execute_ddl("CREATE OR REPLACE VIEW #{quote_schema_table(name)} AS #{source}")
212
- remove_cached_schema(name)
213
- nil
207
+ # DB.create_or_replace_view(:some_items, "SELECT * FROM items WHERE price < 100")
208
+ # DB.create_or_replace_view(:some_items, DB[:items].filter(:category => 'ruby'))
209
+ #
210
+ # For databases where replacing a view is not natively supported, support
211
+ # is emulated by dropping a view with the same name before creating the view.
212
+ def create_or_replace_view(name, source, options = {})
213
+ if supports_create_or_replace_view?
214
+ options = options.merge(:replace=>true)
215
+ else
216
+ drop_view(name) rescue nil
217
+ end
218
+
219
+ create_view(name, source, options)
214
220
  end
215
221
 
216
222
  # Creates a view based on a dataset or an SQL string:
217
223
  #
218
224
  # DB.create_view(:cheap_items, "SELECT * FROM items WHERE price < 100")
219
225
  # DB.create_view(:ruby_items, DB[:items].filter(:category => 'ruby'))
220
- def create_view(name, source)
221
- source = source.sql if source.is_a?(Dataset)
222
- execute_ddl("CREATE VIEW #{quote_schema_table(name)} AS #{source}")
226
+ #
227
+ # PostgreSQL/SQLite specific option:
228
+ # :temp :: Create a temporary view, automatically dropped on disconnect.
229
+ def create_view(name, source, options = {})
230
+ execute_ddl(create_view_sql(name, source, options))
231
+ remove_cached_schema(name)
232
+ nil
223
233
  end
224
234
 
225
235
  # Removes a column from the specified table:
@@ -581,11 +591,22 @@ module Sequel
581
591
  "#{create_table_prefix_sql(name, options)} AS #{sql}"
582
592
  end
583
593
 
584
- # DDL statement for creating a table with the given name, columns, and options
594
+ # DDL fragment for initial part of CREATE TABLE statement
585
595
  def create_table_prefix_sql(name, options)
586
596
  "CREATE #{temporary_table_sql if options[:temp]}TABLE#{' IF NOT EXISTS' if options[:if_not_exists]} #{options[:temp] ? quote_identifier(name) : quote_schema_table(name)}"
587
597
  end
588
598
 
599
+ # DDL statement for creating a view.
600
+ def create_view_sql(name, source, options)
601
+ source = source.sql if source.is_a?(Dataset)
602
+ "#{create_view_prefix_sql(name, options)} AS #{source}"
603
+ end
604
+
605
+ # DDL fragment for initial part of CREATE VIEW statement
606
+ def create_view_prefix_sql(name, options)
607
+ "CREATE #{'OR REPLACE 'if options[:replace]}VIEW #{quote_schema_table(name)}"
608
+ end
609
+
589
610
  # Default index name for the table and columns, may be too long
590
611
  # for certain databases.
591
612
  def default_index_name(table_name, columns)
@@ -11,7 +11,7 @@ module Sequel
11
11
  ACTION_METHODS = (<<-METHS).split.map{|x| x.to_sym}
12
12
  << [] []= all avg count columns columns! delete each
13
13
  empty? fetch_rows first get import insert insert_multiple interval last
14
- map max min multi_insert range select_hash select_hash_groups select_map select_order_map
14
+ map max min multi_insert paged_each range select_hash select_hash_groups select_map select_order_map
15
15
  set single_record single_value sum to_csv to_hash to_hash_groups truncate update
16
16
  METHS
17
17
 
@@ -80,7 +80,7 @@ module Sequel
80
80
  # # => [:id, :name]
81
81
  def columns
82
82
  return @columns if @columns
83
- ds = unfiltered.unordered.clone(:distinct => nil, :limit => 1, :offset=>nil)
83
+ ds = unfiltered.unordered.naked.clone(:distinct => nil, :limit => 1, :offset=>nil)
84
84
  ds.each{break}
85
85
  @columns = ds.instance_variable_get(:@columns)
86
86
  @columns || []
@@ -461,6 +461,55 @@ module Sequel
461
461
  import(columns, hashes.map{|h| columns.map{|c| h[c]}}, opts)
462
462
  end
463
463
 
464
+ # Yields each row in the dataset, but interally uses multiple queries as needed with
465
+ # limit and offset to process the entire result set without keeping all
466
+ # rows in the dataset in memory, even if the underlying driver buffers all
467
+ # query results in memory.
468
+ #
469
+ # Because this uses multiple queries internally, in order to remain consistent,
470
+ # it also uses a transaction internally. Additionally, to make sure that all rows
471
+ # in the dataset are yielded and none are yielded twice, the dataset must have an
472
+ # unambiguous order. Sequel requires that datasets using this method have an
473
+ # order, but it cannot ensure that the order is unambiguous.
474
+ #
475
+ # Options:
476
+ # :rows_per_fetch :: The number of rows to fetch per query. Defaults to 1000.
477
+ def paged_each(opts={})
478
+ unless @opts[:order]
479
+ raise Sequel::Error, "Dataset#paged_each requires the dataset be ordered"
480
+ end
481
+
482
+ total_limit = @opts[:limit]
483
+ offset = @opts[:offset] || 0
484
+
485
+ if server = @opts[:server]
486
+ opts = opts.merge(:server=>server)
487
+ end
488
+
489
+ rows_per_fetch = opts[:rows_per_fetch] || 1000
490
+ num_rows_yielded = rows_per_fetch
491
+ total_rows = 0
492
+
493
+ db.transaction(opts) do
494
+ while num_rows_yielded == rows_per_fetch && (total_limit.nil? || total_rows < total_limit)
495
+ if total_limit && total_rows + rows_per_fetch > total_limit
496
+ rows_per_fetch = total_limit - total_rows
497
+ end
498
+
499
+ num_rows_yielded = 0
500
+ limit(rows_per_fetch, offset).each do |row|
501
+ num_rows_yielded += 1
502
+ total_rows += 1 if total_limit
503
+ yield row
504
+ end
505
+
506
+ offset += rows_per_fetch
507
+ end
508
+ end
509
+
510
+ self
511
+ end
512
+
464
513
  # Returns a +Range+ instance made from the minimum and maximum values for the
465
514
  # given column/expression. Uses a virtual row block if no argument is given.
466
515
  #
@@ -159,12 +159,6 @@ module Sequel
159
159
  true
160
160
  end
161
161
 
162
- # Whether using an offset returns an extra row number column that should be
163
- # eliminated, false by default.
164
- def offset_returns_row_number_column?
165
- false
166
- end
167
-
168
162
  # Whether the RETURNING clause is used for the given dataset.
169
163
  # +type+ can be :insert, :update, or :delete.
170
164
  def uses_returning?(type)
@@ -654,7 +654,7 @@ module Sequel
654
654
  when SQL::Identifier
655
655
  [sch, table_name.value.to_s]
656
656
  when String
657
- [sch, table_name.to_s]
657
+ [sch, table_name]
658
658
  else
659
659
  raise Error, 'table_name should be a Symbol, SQL::QualifiedIdentifier, SQL::Identifier, or String'
660
660
  end
@@ -19,32 +19,47 @@ module Sequel
19
19
  # connection parameters it was given.
20
20
  class DatabaseConnectionError < DatabaseError; end
21
21
 
22
- # Error that should be raised by adapters when they determine that the connection
22
+ # Error raised by adapters when they determine that the connection
23
23
  # to the database has been lost. Instructs the connection pool code to
24
24
  # remove that connection from the pool so that other connections can be acquired
25
25
  # automatically.
26
26
  class DatabaseDisconnectError < DatabaseError; end
27
27
 
28
- # Raised on an invalid operation, such as trying to update or delete
28
+ # Generic error raised when Sequel determines a database constraint has been violated.
29
+ class ConstraintViolation < DatabaseError; end
30
+
31
+ # Error raised when Sequel determines a database check constraint has been violated.
32
+ class CheckConstraintViolation < ConstraintViolation; end
33
+
34
+ # Error raised when Sequel determines a database foreign key constraint has been violated.
35
+ class ForeignKeyConstraintViolation < ConstraintViolation; end
36
+
37
+ # Error raised when Sequel determines a database NOT NULL constraint has been violated.
38
+ class NotNullConstraintViolation < ConstraintViolation; end
39
+
40
+ # Error raised when Sequel determines a database unique constraint has been violated.
41
+ class UniqueConstraintViolation < ConstraintViolation; end
42
+
43
+ # Error raised on an invalid operation, such as trying to update or delete
29
44
  # a joined or grouped dataset.
30
45
  class InvalidOperation < Error; end
31
46
 
32
- # Raised when attempting an invalid type conversion.
47
+ # Error raised when attempting an invalid type conversion.
33
48
  class InvalidValue < Error ; end
34
49
 
35
- # Raised when the adapter adapter hasn't implemented a method such as +tables+:
50
+ # Error raised when the adapter adapter hasn't implemented a method such as +tables+:
36
51
  class NotImplemented < Error; end
37
52
 
38
- # Raised when the connection pool cannot acquire a database connection
53
+ # Error raised when the connection pool cannot acquire a database connection
39
54
  # before the timeout.
40
55
  class PoolTimeout < Error ; end
41
56
 
42
57
  # Exception that you should raise to signal a rollback of the current transaction.
43
58
  # The transaction block will catch this exception, rollback the current transaction,
44
- # and won't reraise it.
59
+ # and won't reraise it (unless a reraise is requested).
45
60
  class Rollback < Error ; end
46
61
 
47
- # Exception that occurs when unbinding a dataset that has multiple different values
62
+ # Error raised when unbinding a dataset that has multiple different values
48
63
  # for a given variable.
49
64
  class UnbindDuplicate < Error; end
50
65
 
@@ -2,8 +2,10 @@
2
2
  # selected columns for a dataset before issuing a query. If it
3
3
  # thinks it can guess correctly at the columns the query will use,
4
4
  # it will return the columns without issuing a database query.
5
+ #
5
6
  # This method is not fool-proof, it's possible that some databases
6
- # will use column names that Sequel does not expect.
7
+ # will use column names that Sequel does not expect. Also, it
8
+ # may not correctly handle all cases.
7
9
  #
8
10
  # To attempt to introspect columns for a single dataset:
9
11
  #
@@ -27,15 +29,38 @@ module Sequel
27
29
  # SQL::AliasedExpressions.
28
30
  def columns
29
31
  return @columns if @columns
30
- return columns_without_introspection unless cols = opts[:select] and !cols.empty?
31
- probable_columns = cols.map{|c| probable_column_name(c)}
32
- if probable_columns.all?
33
- @columns = probable_columns
32
+ if (pcs = probable_columns) && pcs.all?
33
+ @columns = pcs
34
34
  else
35
35
  columns_without_introspection
36
36
  end
37
37
  end
38
38
 
39
+ protected
40
+
41
+ # Return an array of probable column names for the dataset, or
42
+ # nil if it is not possible to determine that through
43
+ # introspection.
44
+ def probable_columns
45
+ if (cols = opts[:select]) && !cols.empty?
46
+ cols.map{|c| probable_column_name(c)}
47
+ elsif !opts[:join] && !opts[:with] && (from = opts[:from]) && from.length == 1 && (from = from.first)
48
+ if from.is_a?(SQL::AliasedExpression)
49
+ from = from.expression
50
+ end
51
+
52
+ case from
53
+ when Dataset
54
+ from.probable_columns
55
+ when Symbol, SQL::Identifier, SQL::QualifiedIdentifier
56
+ schemas = db.instance_variable_get(:@schemas)
57
+ if schemas && (sch = Sequel.synchronize{schemas[literal(from)]})
58
+ sch.map{|c,_| c}
59
+ end
60
+ end
61
+ end
62
+ end
63
+
39
64
  private
40
65
 
41
66
  # Return the probable name of the column, or nil if one
@@ -102,6 +102,15 @@ module Sequel
102
102
  end
103
103
  super
104
104
  end
105
+
106
+ private
107
+
108
+ def create_view_sql(name, source, options)
109
+ if source.is_a?(DatasetMethods)
110
+ source = source.no_auto_parameterize
111
+ end
112
+ super
113
+ end
105
114
  end
106
115
 
107
116
  module DatasetMethods
@@ -22,11 +22,6 @@ module Sequel
22
22
  :"_add_#{singularize(self[:name])}"
23
23
  end
24
24
 
25
- # Name symbol for the _dataset association method
26
- def _dataset_method
27
- :"_#{self[:name]}_dataset"
28
- end
29
-
30
25
  # Name symbol for the _remove_all internal association method
31
26
  def _remove_all_method
32
27
  :"_remove_all_#{self[:name]}"
@@ -56,6 +51,29 @@ module Sequel
56
51
  def associated_class
57
52
  cached_fetch(:class){constantize(self[:class_name])}
58
53
  end
54
+
55
+ # The dataset associated via this association, with the non-instance specific
56
+ # changes already applied.
57
+ def associated_dataset
58
+ cached_fetch(:_dataset){apply_dataset_changes(associated_class.dataset.clone)}
59
+ end
60
+
61
+ # Apply all non-instance specific changes to the given dataset and return it.
62
+ def apply_dataset_changes(ds)
63
+ ds.extend(AssociationDatasetMethods)
64
+ ds.association_reflection = self
65
+ self[:extend].each{|m| ds.extend(m)}
66
+ ds = ds.select(*select) if select
67
+ if c = self[:conditions]
68
+ ds = (c.is_a?(Array) && !Sequel.condition_specifier?(c)) ? ds.where(*c) : ds.where(c)
69
+ end
70
+ ds = ds.order(*self[:order]) if self[:order]
71
+ ds = ds.limit(*self[:limit]) if self[:limit]
72
+ ds = ds.limit(1) if !returns_array? && self[:key]
73
+ ds = ds.eager(*self[:eager]) if self[:eager]
74
+ ds = ds.distinct if self[:distinct]
75
+ ds
76
+ end
59
77
 
60
78
  # Whether this association can have associated objects, given the current
61
79
  # object. Should be false if obj cannot have associated objects because
@@ -688,7 +706,7 @@ module Sequel
688
706
  def apply_association_dataset_opts(opts, ds)
689
707
  ds = ds.select(*opts.select) if opts.select
690
708
  if c = opts[:conditions]
691
- ds = (c.is_a?(Array) && !Sequel.condition_specifier?(c)) ? ds.filter(*c) : ds.filter(c)
709
+ ds = (c.is_a?(Array) && !Sequel.condition_specifier?(c)) ? ds.where(*c) : ds.where(c)
692
710
  end
693
711
  ds = ds.order(*opts[:order]) if opts[:order]
694
712
  ds = ds.eager(opts[:eager]) if opts[:eager]
@@ -747,9 +765,10 @@ module Sequel
747
765
  # :clone :: Merge the current options and block into the options and block used in defining
748
766
  # the given association. Can be used to DRY up a bunch of similar associations that
749
767
  # all share the same options such as :class and :key, while changing the order and block used.
750
- # :conditions :: The conditions to use to filter the association, can be any argument passed to filter.
751
- # :dataset :: A proc that is instance_evaled to get the base dataset
752
- # to use for the _dataset method (before the other options are applied).
768
+ # :conditions :: The conditions to use to filter the association, can be any argument passed to where.
769
+ # :dataset :: A proc that is instance_execed to get the base dataset to use (before the other
770
+ # options are applied). If the proc accepts an argument, it is passed the related
771
+ # association reflection.
753
772
  # :distinct :: Use the DISTINCT clause when selecting associating object, both when
754
773
  # lazy loading and eager loading via .eager (but not when using .eager_graph).
755
774
  # :eager :: The associations to eagerly load via +eager+ when loading the associated object(s).
@@ -1047,7 +1066,7 @@ module Sequel
1047
1066
  else
1048
1067
  SQL::QualifiedIdentifier.new(table, pk)
1049
1068
  end
1050
- ds.filter(pk=>subquery)
1069
+ ds.where(pk=>subquery)
1051
1070
  end
1052
1071
 
1053
1072
  # Use a window function to limit the results of the eager loading dataset.
@@ -1091,7 +1110,6 @@ module Sequel
1091
1110
 
1092
1111
  # Adds the association dataset methods to the association methods module.
1093
1112
  def def_association_dataset_methods(opts)
1094
- association_module_private_def(opts._dataset_method, opts, &opts[:dataset])
1095
1113
  association_module_def(opts.dataset_method, opts){_dataset(opts)}
1096
1114
  def_association_method(opts)
1097
1115
  end
@@ -1127,7 +1145,7 @@ module Sequel
1127
1145
  graph_jt_conds = opts[:graph_join_table_conditions] = opts.fetch(:graph_join_table_conditions, []).to_a
1128
1146
  opts[:graph_join_table_join_type] ||= opts[:graph_join_type]
1129
1147
  opts[:after_load].unshift(:array_uniq!) if opts[:uniq]
1130
- opts[:dataset] ||= proc{opts.associated_class.inner_join(join_table, rcks.zip(opts.right_primary_keys) + opts.predicate_keys.zip(lcpks.map{|k| send(k)}), :qualify=>:deep)}
1148
+ opts[:dataset] ||= proc{opts.associated_dataset.inner_join(join_table, rcks.zip(opts.right_primary_keys) + opts.predicate_keys.zip(lcpks.map{|k| send(k)}), :qualify=>:deep)}
1131
1149
 
1132
1150
  opts[:eager_loader] ||= proc do |eo|
1133
1151
  h = eo[:id_map]
@@ -1190,10 +1208,10 @@ module Sequel
1190
1208
  _join_table_dataset(opts).insert(h)
1191
1209
  end
1192
1210
  association_module_private_def(opts._remove_method, opts) do |o|
1193
- _join_table_dataset(opts).filter(lcks.zip(lcpks.map{|k| send(k)}) + rcks.zip(opts.right_primary_key_methods.map{|k| o.send(k)})).delete
1211
+ _join_table_dataset(opts).where(lcks.zip(lcpks.map{|k| send(k)}) + rcks.zip(opts.right_primary_key_methods.map{|k| o.send(k)})).delete
1194
1212
  end
1195
1213
  association_module_private_def(opts._remove_all_method, opts) do
1196
- _join_table_dataset(opts).filter(lcks.zip(lcpks.map{|k| send(k)})).delete
1214
+ _join_table_dataset(opts).where(lcks.zip(lcpks.map{|k| send(k)})).delete
1197
1215
  end
1198
1216
 
1199
1217
  def_add_method(opts)
@@ -1219,8 +1237,7 @@ module Sequel
1219
1237
  qualify = opts[:qualify] != false
1220
1238
  opts[:cartesian_product_number] ||= 0
1221
1239
  opts[:dataset] ||= proc do
1222
- klass = opts.associated_class
1223
- klass.filter(opts.predicate_keys.zip(cks.map{|k| send(k)}))
1240
+ opts.associated_dataset.where(opts.predicate_keys.zip(cks.map{|k| send(k)}))
1224
1241
  end
1225
1242
  opts[:eager_loader] ||= proc do |eo|
1226
1243
  h = eo[:id_map]
@@ -1231,7 +1248,7 @@ module Sequel
1231
1248
  # Skip eager loading if no objects have a foreign key for this association
1232
1249
  unless keys.empty?
1233
1250
  klass = opts.associated_class
1234
- model.eager_loading_dataset(opts, klass.filter(opts.predicate_key=>keys), nil, eo[:associations], eo).all do |assoc_record|
1251
+ model.eager_loading_dataset(opts, klass.where(opts.predicate_key=>keys), nil, eo[:associations], eo).all do |assoc_record|
1235
1252
  hash_key = uses_cks ? opts.primary_key_methods.map{|k| assoc_record.send(k)} : assoc_record.send(opts.primary_key_method)
1236
1253
  next unless objects = h[hash_key]
1237
1254
  objects.each{|object| object.associations[name] = assoc_record}
@@ -1276,7 +1293,7 @@ module Sequel
1276
1293
  raise(Error, "mismatched number of keys: #{cks.inspect} vs #{cpks.inspect}") unless cks.length == cpks.length
1277
1294
  uses_cks = opts[:uses_composite_keys] = cks.length > 1
1278
1295
  opts[:dataset] ||= proc do
1279
- opts.associated_class.filter(opts.predicate_keys.zip(cpks.map{|k| send(k)}))
1296
+ opts.associated_dataset.where(opts.predicate_keys.zip(cpks.map{|k| send(k)}))
1280
1297
  end
1281
1298
  opts[:eager_loader] ||= proc do |eo|
1282
1299
  h = eo[:id_map]
@@ -1289,7 +1306,7 @@ module Sequel
1289
1306
  reciprocal = opts.reciprocal
1290
1307
  klass = opts.associated_class
1291
1308
  filter_keys = opts.predicate_key
1292
- ds = model.eager_loading_dataset(opts, klass.filter(filter_keys=>h.keys), nil, eo[:associations], eo)
1309
+ ds = model.eager_loading_dataset(opts, klass.where(filter_keys=>h.keys), nil, eo[:associations], eo)
1293
1310
  case opts.eager_limit_strategy
1294
1311
  when :distinct_on
1295
1312
  ds = ds.distinct(*filter_keys).order_prepend(*filter_keys)
@@ -1351,7 +1368,7 @@ module Sequel
1351
1368
 
1352
1369
  if one_to_one
1353
1370
  association_module_private_def(opts._setter_method, opts) do |o|
1354
- up_ds = _apply_association_options(opts, opts.associated_class.filter(cks.zip(cpks.map{|k| send(k)})))
1371
+ up_ds = _apply_association_options(opts, opts.associated_dataset.where(cks.zip(cpks.map{|k| send(k)})))
1355
1372
  if o
1356
1373
  up_ds = up_ds.exclude(o.pk_hash) unless o.new?
1357
1374
  cks.zip(cpks).each{|k, pk| o.send(:"#{k}=", send(pk))}
@@ -1374,7 +1391,7 @@ module Sequel
1374
1391
  o.save(:validate=>validate) || raise(Sequel::Error, "invalid associated object, cannot save")
1375
1392
  end
1376
1393
  association_module_private_def(opts._remove_all_method, opts) do
1377
- _apply_association_options(opts, opts.associated_class.filter(cks.zip(cpks.map{|k| send(k)}))).update(ck_nil_hash)
1394
+ _apply_association_options(opts, opts.associated_dataset.where(cks.zip(cpks.map{|k| send(k)}))).update(ck_nil_hash)
1378
1395
  end
1379
1396
  def_remove_methods(opts)
1380
1397
  end
@@ -1430,19 +1447,10 @@ module Sequel
1430
1447
 
1431
1448
  # Apply the association options such as :order and :limit to the given dataset, returning a modified dataset.
1432
1449
  def _apply_association_options(opts, ds)
1433
- ds.extend(AssociationDatasetMethods)
1434
- ds.model_object = self
1435
- ds.association_reflection = opts
1436
- opts[:extend].each{|m| ds.extend(m)}
1437
- ds = ds.select(*opts.select) if opts.select
1438
- if c = opts[:conditions]
1439
- ds = (c.is_a?(Array) && !Sequel.condition_specifier?(c)) ? ds.filter(*c) : ds.filter(c)
1450
+ unless ds.kind_of?(AssociationDatasetMethods)
1451
+ ds = opts.apply_dataset_changes(ds)
1440
1452
  end
1441
- ds = ds.order(*opts[:order]) if opts[:order]
1442
- ds = ds.limit(*opts[:limit]) if opts[:limit]
1443
- ds = ds.limit(1) if !opts.returns_array? && opts[:key]
1444
- ds = ds.eager(*opts[:eager]) if opts[:eager]
1445
- ds = ds.distinct if opts[:distinct]
1453
+ ds.model_object = self
1446
1454
  ds = ds.eager_graph(opts[:eager_graph]) if opts[:eager_graph] && opts.eager_graph_lazy_dataset?
1447
1455
  ds = instance_exec(ds, &opts[:block]) if opts[:block]
1448
1456
  ds
@@ -1460,7 +1468,12 @@ module Sequel
1460
1468
  # Return an association dataset for the given association reflection
1461
1469
  def _dataset(opts)
1462
1470
  raise(Sequel::Error, "model object #{inspect} does not have a primary key") if opts.dataset_need_primary_key? && !pk
1463
- _apply_association_options(opts, send(opts._dataset_method))
1471
+ ds = if opts[:dataset].arity == 1
1472
+ instance_exec(opts, &opts[:dataset])
1473
+ else
1474
+ instance_exec(&opts[:dataset])
1475
+ end
1476
+ _apply_association_options(opts, ds)
1464
1477
  end
1465
1478
 
1466
1479
  # Dataset for the join table of the given many to many association reflection
@@ -1601,7 +1614,7 @@ module Sequel
1601
1614
  o = remove_check_existing_object_from_pk(opts, o, *args)
1602
1615
  elsif !o.is_a?(klass)
1603
1616
  raise(Sequel::Error, "associated object #{o.inspect} not of correct type #{klass}")
1604
- elsif opts.remove_should_check_existing? && send(opts.dataset_method).filter(o.pk_hash).empty?
1617
+ elsif opts.remove_should_check_existing? && send(opts.dataset_method).where(o.pk_hash).empty?
1605
1618
  raise(Sequel::Error, "associated object #{o.inspect} is not currently associated to #{inspect}")
1606
1619
  end
1607
1620
  raise(Sequel::Error, "model object #{inspect} does not have a primary key") unless pk
@@ -1723,7 +1736,7 @@ module Sequel
1723
1736
  # allows you to customize it at association definition time. For example,
1724
1737
  # if you wanted artists with their albums since 1990:
1725
1738
  #
1726
- # Artist.eager(:albums => proc{|ds| ds.filter{year > 1990}})
1739
+ # Artist.eager(:albums => proc{|ds| ds.where{year > 1990}})
1727
1740
  #
1728
1741
  # Or if you needed albums and their artist's name only, using a single query:
1729
1742
  #
@@ -1734,7 +1747,7 @@ module Sequel
1734
1747
  # the cascaded associations as the value. This will load artists with their albums
1735
1748
  # since 1990, and also the tracks on those albums and the genre for those tracks:
1736
1749
  #
1737
- # Artist.eager(:albums => {proc{|ds| ds.filter{year > 1990}}=>{:tracks => :genre}})
1750
+ # Artist.eager(:albums => {proc{|ds| ds.where{year > 1990}}=>{:tracks => :genre}})
1738
1751
  module DatasetMethods
1739
1752
  # Add the <tt>eager!</tt> and <tt>eager_graph!</tt> mutation methods to the dataset.
1740
1753
  def self.extended(obj)