sequel 3.41.0 → 3.42.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.
@@ -140,6 +140,11 @@ module Sequel
140
140
  sqlite_version >= 30300
141
141
  end
142
142
 
143
+ # SQLite 3.6.19+ supports deferrable foreign key constraints.
144
+ def supports_deferrable_foreign_key_constraints?
145
+ sqlite_version >= 30619
146
+ end
147
+
143
148
  # SQLite 3.6.8+ supports savepoints.
144
149
  def supports_savepoints?
145
150
  sqlite_version >= 30608
@@ -10,6 +10,10 @@ module Sequel
10
10
  # in the extension).
11
11
  EXTENSIONS = {}
12
12
 
13
+ # The general default size for string columns for all Sequel::Database
14
+ # instances.
15
+ DEFAULT_STRING_COLUMN_SIZE = 255
16
+
13
17
  # Register an extension callback for Database objects. ext should be the
14
18
  # extension name symbol, and mod should either be a Module that the
15
19
  # database is extended with, or a callable object called with the database
@@ -44,11 +48,15 @@ module Sequel
44
48
  # Set the timezone to use for this database, overridding <tt>Sequel.database_timezone</tt>.
45
49
  attr_writer :timezone
46
50
 
51
+ # The specific default size of string columns for this Sequel::Database, usually 255 by default.
52
+ attr_accessor :default_string_column_size
53
+
47
54
  # Constructs a new instance of a database connection with the specified
48
55
  # options hash.
49
56
  #
50
57
  # Accepts the following options:
51
58
  # :default_schema :: The default schema to use, see #default_schema.
59
+ # :default_string_column_size :: The default size of string columns, 255 by default.
52
60
  # :identifier_input_method :: A string method symbol to call on identifiers going into the database
53
61
  # :identifier_output_method :: A string method symbol to call on identifiers coming from the database
54
62
  # :logger :: A specific logger to use
@@ -71,6 +79,7 @@ module Sequel
71
79
  @opts[:single_threaded] = @single_threaded = typecast_value_boolean(@opts.fetch(:single_threaded, @@single_threaded))
72
80
  @schemas = {}
73
81
  @default_schema = @opts.fetch(:default_schema, default_schema_default)
82
+ @default_string_column_size = @opts[:default_string_column_size] || DEFAULT_STRING_COLUMN_SIZE
74
83
  @prepared_statements = {}
75
84
  @transactions = {}
76
85
  @identifier_input_method = nil
@@ -205,6 +214,18 @@ module Sequel
205
214
  false
206
215
  end
207
216
 
217
+ # Whether the database supports deferrable constraints, false
218
+ # by default as few databases do.
219
+ def supports_deferrable_constraints?
220
+ false
221
+ end
222
+
223
+ # Whether the database supports deferrable foreign key constraints,
224
+ # false by default as few databases do.
225
+ def supports_deferrable_foreign_key_constraints?
226
+ supports_deferrable_constraints?
227
+ end
228
+
208
229
  # Whether the database supports DROP TABLE IF EXISTS syntax,
209
230
  # default is the same as #supports_create_table_if_not_exists?.
210
231
  def supports_drop_table_if_exists?
@@ -232,7 +232,7 @@ module Sequel
232
232
 
233
233
  # Starts a database transaction. When a database transaction is used,
234
234
  # either all statements are successful or none of the statements are
235
- # successful. Note that MySQL MyISAM tabels do not support transactions.
235
+ # successful. Note that MySQL MyISAM tables do not support transactions.
236
236
  #
237
237
  # The following general options are respected:
238
238
  #
@@ -333,7 +333,15 @@ module Sequel
333
333
  begin
334
334
  committed = commit_or_rollback_transaction(e, conn, opts)
335
335
  rescue Exception => e2
336
- raise_error(e2, :classes=>database_error_classes, :conn=>conn)
336
+ begin
337
+ raise_error(e2, :classes=>database_error_classes, :conn=>conn)
338
+ rescue Sequel::DatabaseError => e4
339
+ begin
340
+ rollback_transaction(conn, opts)
341
+ ensure
342
+ raise e4
343
+ end
344
+ end
337
345
  ensure
338
346
  remove_transaction(conn, committed)
339
347
  end
@@ -79,10 +79,11 @@ module Sequel
79
79
  # The following options are supported:
80
80
  #
81
81
  # :default :: The default value for the column.
82
- # :deferrable :: This ensure Referential Integrity will work even if
83
- # reference table will use for its foreign key a value that does not
84
- # exists(yet) on referenced table. Basically it adds
85
- # DEFERRABLE INITIALLY DEFERRED on key creation.
82
+ # :deferrable :: For foreign key columns, this ensures referential integrity will work even if
83
+ # referencing table uses a foreign key value that does not
84
+ # yet exist on referenced table (but will exist before the transaction commits).
85
+ # Basically it adds DEFERRABLE INITIALLY DEFERRED on key creation.
86
+ # If you use :immediate as the value, uses DEFERRABLE INITIALLY IMMEDIATE.
86
87
  # :index :: Create an index on this column. If given a hash, use the hash as the
87
88
  # options for the index.
88
89
  # :key :: For foreign key columns, the column in the associated table
@@ -239,6 +240,8 @@ module Sequel
239
240
  # Add a unique constraint on the given columns to the DDL.
240
241
  #
241
242
  # unique(:name) # UNIQUE (name)
243
+ #
244
+ # Supports the same :deferrable option as #column.
242
245
  def unique(columns, opts = {})
243
246
  constraints << {:type => :unique, :columns => Array(columns)}.merge(opts)
244
247
  end
@@ -304,6 +307,8 @@ module Sequel
304
307
  #
305
308
  # add_unique_constraint(:name) # ADD UNIQUE (name)
306
309
  # add_unique_constraint(:name, :name=>:unique_name) # ADD CONSTRAINT unique_name UNIQUE (name)
310
+ #
311
+ # Supports the same :deferrable option as CreateTableGenerator#column.
307
312
  def add_unique_constraint(columns, opts = {})
308
313
  @operations << {:op => :add_constraint, :type => :unique, :columns => Array(columns)}.merge(opts)
309
314
  end
@@ -493,7 +493,7 @@ module Sequel
493
493
  sql << "(#{Array(column[:key]).map{|x| quote_identifier(x)}.join(COMMA_SEPARATOR)})" if column[:key]
494
494
  sql << " ON DELETE #{on_delete_clause(column[:on_delete])}" if column[:on_delete]
495
495
  sql << " ON UPDATE #{on_update_clause(column[:on_update])}" if column[:on_update]
496
- sql << " DEFERRABLE INITIALLY DEFERRED" if column[:deferrable]
496
+ constraint_deferrable_sql_append(sql, column[:deferrable])
497
497
  sql
498
498
  end
499
499
 
@@ -524,9 +524,23 @@ module Sequel
524
524
  else
525
525
  raise Error, "Invalid constriant type #{constraint[:type]}, should be :check, :primary_key, :foreign_key, or :unique"
526
526
  end
527
+ constraint_deferrable_sql_append(sql, constraint[:deferrable])
527
528
  sql
528
529
  end
529
530
 
531
+ # SQL DDL fragment specifying the deferrable constraint attributes.
532
+ def constraint_deferrable_sql_append(sql, defer)
533
+ case defer
534
+ when nil
535
+ when false
536
+ sql << ' NOT DEFERRABLE'
537
+ when :immediate
538
+ sql << ' DEFERRABLE INITIALLY IMMEDIATE'
539
+ else
540
+ sql << ' DEFERRABLE INITIALLY DEFERRED'
541
+ end
542
+ end
543
+
530
544
  # Execute the create table statements using the generator.
531
545
  def create_table_from_generator(name, generator, options)
532
546
  execute_ddl(create_table_sql(name, generator, options))
@@ -788,9 +802,9 @@ module Sequel
788
802
  if column[:text]
789
803
  uses_clob_for_text? ? :clob : :text
790
804
  elsif column[:fixed]
791
- "char(#{column[:size]||255})"
805
+ "char(#{column[:size]||default_string_column_size})"
792
806
  else
793
- "varchar(#{column[:size]||255})"
807
+ "varchar(#{column[:size]||default_string_column_size})"
794
808
  end
795
809
  end
796
810
 
@@ -810,7 +824,7 @@ module Sequel
810
824
  def type_literal_specific(column)
811
825
  type = column[:type]
812
826
  type = "double precision" if type.to_s == 'double'
813
- column[:size] ||= 255 if type.to_s == 'varchar'
827
+ column[:size] ||= default_string_column_size if type.to_s == 'varchar'
814
828
  elements = column[:size] || column[:elements]
815
829
  "#{type}#{literal(Array(elements)) if elements}#{UNSIGNED if column[:unsigned]}"
816
830
  end
@@ -58,11 +58,14 @@ module Sequel
58
58
  a
59
59
  end
60
60
 
61
- # Returns the average value for the given column.
61
+ # Returns the average value for the given column/expression.
62
+ # Uses a virtual row block if no argument is given.
62
63
  #
63
64
  # DB[:table].avg(:number) # SELECT avg(number) FROM table LIMIT 1
64
65
  # # => 3
65
- def avg(column)
66
+ # DB[:table].avg{function(column)} # SELECT avg(function(column)) FROM table LIMIT 1
67
+ # # => 1
68
+ def avg(column=Sequel.virtual_row(&Proc.new))
66
69
  aggregate_dataset.get{avg(column)}
67
70
  end
68
71
 
@@ -342,11 +345,13 @@ module Sequel
342
345
  end
343
346
 
344
347
  # Returns the interval between minimum and maximum values for the given
345
- # column.
348
+ # column/expression. Uses a virtual row block if no argument is given.
346
349
  #
347
350
  # DB[:table].interval(:id) # SELECT (max(id) - min(id)) FROM table LIMIT 1
348
351
  # # => 6
349
- def interval(column)
352
+ # DB[:table].interval{function(column)} # SELECT (max(function(column)) - min(function(column))) FROM table LIMIT 1
353
+ # # => 7
354
+ def interval(column=Sequel.virtual_row(&Proc.new))
350
355
  aggregate_dataset.get{max(column) - min(column)}
351
356
  end
352
357
 
@@ -393,19 +398,25 @@ module Sequel
393
398
  end
394
399
  end
395
400
 
396
- # Returns the maximum value for the given column.
401
+ # Returns the maximum value for the given column/expression.
402
+ # Uses a virtual row block if no argument is given.
397
403
  #
398
404
  # DB[:table].max(:id) # SELECT max(id) FROM table LIMIT 1
399
405
  # # => 10
400
- def max(column)
406
+ # DB[:table].max{function(column)} # SELECT max(function(column)) FROM table LIMIT 1
407
+ # # => 7
408
+ def max(column=Sequel.virtual_row(&Proc.new))
401
409
  aggregate_dataset.get{max(column)}
402
410
  end
403
411
 
404
- # Returns the minimum value for the given column.
412
+ # Returns the minimum value for the given column/expression.
413
+ # Uses a virtual row block if no argument is given.
405
414
  #
406
415
  # DB[:table].min(:id) # SELECT min(id) FROM table LIMIT 1
407
416
  # # => 1
408
- def min(column)
417
+ # DB[:table].min{function(column)} # SELECT min(function(column)) FROM table LIMIT 1
418
+ # # => 0
419
+ def min(column=Sequel.virtual_row(&Proc.new))
409
420
  aggregate_dataset.get{min(column)}
410
421
  end
411
422
 
@@ -428,11 +439,13 @@ module Sequel
428
439
  end
429
440
 
430
441
  # Returns a +Range+ instance made from the minimum and maximum values for the
431
- # given column.
442
+ # given column/expression. Uses a virtual row block if no argument is given.
432
443
  #
433
444
  # DB[:table].range(:id) # SELECT max(id) AS v1, min(id) AS v2 FROM table LIMIT 1
434
445
  # # => 1..10
435
- def range(column)
446
+ # DB[:table].interval{function(column)} # SELECT max(function(column)) AS v1, min(function(column)) AS v2 FROM table LIMIT 1
447
+ # # => 0..7
448
+ def range(column=Sequel.virtual_row(&Proc.new))
436
449
  if r = aggregate_dataset.select{[min(column).as(v1), max(column).as(v2)]}.first
437
450
  (r[:v1]..r[:v2])
438
451
  end
@@ -543,11 +556,14 @@ module Sequel
543
556
  end
544
557
  end
545
558
 
546
- # Returns the sum for the given column.
559
+ # Returns the sum for the given column/expression.
560
+ # Uses a virtual row block if no column is given.
547
561
  #
548
562
  # DB[:table].sum(:id) # SELECT sum(id) FROM table LIMIT 1
549
563
  # # => 55
550
- def sum(column)
564
+ # DB[:table].sum{function(column)} # SELECT sum(function(column)) FROM table LIMIT 1
565
+ # # => 10
566
+ def sum(column=Sequel.virtual_row(&Proc.new))
551
567
  aggregate_dataset.get{sum(column)}
552
568
  end
553
569
 
@@ -1101,7 +1101,7 @@ module Sequel
1101
1101
  when String
1102
1102
  LiteralString.new("(#{expr})")
1103
1103
  else
1104
- raise(Error, 'Invalid filter argument')
1104
+ raise(Error, "Invalid filter argument: #{expr.inspect}")
1105
1105
  end
1106
1106
  end
1107
1107
 
@@ -206,7 +206,7 @@ module Sequel
206
206
  COMMA_SEPARATOR = COMMA
207
207
  CONDITION_FALSE = '(1 = 0)'.freeze
208
208
  CONDITION_TRUE = '(1 = 1)'.freeze
209
- COUNT_FROM_SELF_OPTS = [:distinct, :group, :sql, :limit, :compounds]
209
+ COUNT_FROM_SELF_OPTS = [:distinct, :group, :sql, :limit, :offset, :compounds]
210
210
  COUNT_OF_ALL_AS_COUNT = SQL::Function.new(:count, WILDCARD).as(:count)
211
211
  DATASET_ALIAS_BASE_NAME = 't'.freeze
212
212
  DEFAULT = LiteralString.new('DEFAULT').freeze
@@ -304,6 +304,7 @@ END_MIG
304
304
  end
305
305
  end
306
306
  h[:unique] = true if index_opts[:unique]
307
+ h[:deferrable] = true if index_opts[:deferrable]
307
308
  h
308
309
  end
309
310
 
data/lib/sequel/model.rb CHANGED
@@ -106,7 +106,7 @@ module Sequel
106
106
  # Class instance variables that are inherited in subclasses. If the value is <tt>:dup</tt>, dup is called
107
107
  # on the superclass's instance variable when creating the instance variable in the subclass.
108
108
  # If the value is +nil+, the superclass's instance variable is used directly in the subclass.
109
- INHERITED_INSTANCE_VARIABLES = {:@allowed_columns=>:dup, :@dataset_methods=>:dup,
109
+ INHERITED_INSTANCE_VARIABLES = {:@allowed_columns=>:dup,
110
110
  :@dataset_method_modules=>:dup, :@primary_key=>nil, :@use_transactions=>nil,
111
111
  :@raise_on_save_failure=>nil, :@require_modification=>nil,
112
112
  :@restricted_columns=>:dup, :@restrict_primary_key=>nil,
@@ -129,7 +129,6 @@ module Sequel
129
129
  @db = nil
130
130
  @db_schema = nil
131
131
  @dataset_method_modules = []
132
- @dataset_methods = {}
133
132
  @overridable_methods_module = nil
134
133
  @plugins = []
135
134
  @primary_key = :id
@@ -147,7 +146,7 @@ module Sequel
147
146
  @use_after_commit_rollback = true
148
147
  @use_transactions = true
149
148
 
150
- Sequel.require %w"default_inflections inflections plugins base exceptions errors", "model"
149
+ Sequel.require %w"default_inflections inflections plugins dataset_module base exceptions errors", "model"
151
150
  if !defined?(::SEQUEL_NO_ASSOCIATIONS) && !ENV.has_key?('SEQUEL_NO_ASSOCIATIONS')
152
151
  Sequel.require 'associations', 'model'
153
152
  plugin Model::Associations
@@ -24,11 +24,6 @@ module Sequel
24
24
  # with all of these modules.
25
25
  attr_reader :dataset_method_modules
26
26
 
27
- # Hash of dataset methods with method name keys and proc values that are
28
- # stored so when the dataset changes, methods defined with def_dataset_method
29
- # will be applied to the new dataset.
30
- attr_reader :dataset_methods
31
-
32
27
  # SQL string fragment used for faster DELETE statement creation when deleting/destroying
33
28
  # model instances, or nil if the optimization should not be used. For internal use only.
34
29
  attr_reader :fast_instance_delete_sql
@@ -179,13 +174,19 @@ module Sequel
179
174
  end
180
175
 
181
176
  # Extend the dataset with a module, similar to adding
182
- # a plugin with the methods defined in DatasetMethods. If a block
183
- # is given, an anonymous module is created and the module_evaled, otherwise
184
- # the argument should be a module. Returns the module given or the anonymous
185
- # module created.
177
+ # a plugin with the methods defined in DatasetMethods.
178
+ # This is the recommended way to add methods to model datasets.
179
+ #
180
+ # If an argument, it should be a module, and is used to extend
181
+ # the underlying dataset. Otherwise an anonymous module is created, and
182
+ # if a block is given, it is module_evaled, allowing you do define
183
+ # dataset methods directly using the standard ruby def syntax.
184
+ # Returns the module given or the anonymous module created.
186
185
  #
186
+ # # Usage with existing module
187
187
  # Artist.dataset_module Sequel::ColumnsIntrospection
188
188
  #
189
+ # # Usage with anonymous module
189
190
  # Artist.dataset_module do
190
191
  # def foo
191
192
  # :bar
@@ -195,13 +196,24 @@ module Sequel
195
196
  # # => :bar
196
197
  # Artist.foo
197
198
  # # => :bar
199
+ #
200
+ # Any anonymous modules created are actually instances of Sequel::Model::DatasetModule
201
+ # (a Module subclass), which allows you to call the subset method on them:
202
+ #
203
+ # Artist.dataset_module do
204
+ # subset :released, Sequel.identifier(release_date) > Sequel::CURRENT_DATE
205
+ # end
206
+ #
207
+ # Any public methods in the dataset module will have class methods created that
208
+ # call the method on the dataset, assuming that the class method is not already
209
+ # defined.
198
210
  def dataset_module(mod = nil)
199
211
  if mod
200
212
  raise Error, "can't provide both argument and block to Model.dataset_module" if block_given?
201
213
  dataset_extend(mod)
202
214
  mod
203
215
  else
204
- @dataset_module ||= Module.new
216
+ @dataset_module ||= DatasetModule.new(self)
205
217
  @dataset_module.module_eval(&Proc.new) if block_given?
206
218
  dataset_extend(@dataset_module)
207
219
  @dataset_module
@@ -270,6 +282,10 @@ module Sequel
270
282
  # If a block is not given, just define a class method on the model for each argument
271
283
  # that calls the dataset method of the same argument name.
272
284
  #
285
+ # It is recommended that you define methods inside a block passed to #dataset_module
286
+ # instead of using this method, as #dataset_module allows you to use normal
287
+ # ruby def syntax.
288
+ #
273
289
  # # Add new dataset method and class method that calls it
274
290
  # Artist.def_dataset_method(:by_name){order(:name)}
275
291
  # Artist.filter(:name.like('A%')).by_name
@@ -280,18 +296,12 @@ module Sequel
280
296
  # Artist.server!(:server1)
281
297
  def def_dataset_method(*args, &block)
282
298
  raise(Error, "No arguments given") if args.empty?
299
+
283
300
  if block
284
301
  raise(Error, "Defining a dataset method using a block requires only one argument") if args.length > 1
285
- meth = args.first
286
- @dataset_methods[meth] = block
287
- dataset.meta_def(meth, &block) if @dataset
288
- end
289
- args.each do |arg|
290
- if arg.to_s =~ NORMAL_METHOD_NAME_REGEXP
291
- instance_eval("def #{arg}(*args, &block); dataset.#{arg}(*args, &block) end", __FILE__, __LINE__) unless respond_to?(arg, true)
292
- else
293
- def_model_dataset_method_block(arg)
294
- end
302
+ dataset_module{define_method(args.first, &block)}
303
+ else
304
+ args.each{|arg| def_model_dataset_method(arg)}
295
305
  end
296
306
  end
297
307
 
@@ -483,8 +493,7 @@ module Sequel
483
493
  # Returns self.
484
494
  #
485
495
  # This changes the row_proc of the dataset to return
486
- # model objects, extends the dataset with the dataset_method_modules,
487
- # and defines methods on the dataset using the dataset_methods.
496
+ # model objects and extends the dataset with the dataset_method_modules.
488
497
  # It also attempts to determine the database schema for the model,
489
498
  # based on the given dataset.
490
499
  #
@@ -514,11 +523,10 @@ module Sequel
514
523
  @columns = @dataset.columns rescue nil
515
524
  else
516
525
  @dataset_method_modules.each{|m| @dataset.extend(m)} if @dataset_method_modules
517
- @dataset_methods.each{|meth, block| @dataset.meta_def(meth, &block)} if @dataset_methods
518
526
  end
519
527
  @dataset.model = self if @dataset.respond_to?(:model=)
520
528
  check_non_connection_error{@db_schema = (inherited ? superclass.db_schema : get_db_schema)}
521
- @instance_dataset = @dataset.limit(1).naked
529
+ reset_instance_dataset
522
530
  self
523
531
  end
524
532
 
@@ -577,8 +585,8 @@ module Sequel
577
585
  end
578
586
  end
579
587
 
580
- # Shortcut for +def_dataset_method+ that is restricted to modifying the
581
- # dataset's filter. Sometimes thought of as a scope, and like most dataset methods,
588
+ # Sets up a dataset method that returns a filtered dataset.
589
+ # Sometimes thought of as a scope, and like most dataset methods,
582
590
  # they can be chained.
583
591
  # For example:
584
592
  #
@@ -597,9 +605,10 @@ module Sequel
597
605
  # Both the args given and the block are passed to <tt>Dataset#filter</tt>.
598
606
  #
599
607
  # This method creates dataset methods that do not accept arguments. To create
600
- # dataset methods that accept arguments, you have to use def_dataset_method.
608
+ # dataset methods that accept arguments, you should use define a
609
+ # method directly inside a #dataset_module block.
601
610
  def subset(name, *args, &block)
602
- def_dataset_method(name){filter(*args, &block)}
611
+ dataset_module.subset(name, *args, &block)
603
612
  end
604
613
 
605
614
  # Returns name of primary table for the dataset. If the table for the dataset
@@ -640,10 +649,10 @@ module Sequel
640
649
  # module if the model has a dataset. Add dataset methods to the class for all
641
650
  # public dataset methods.
642
651
  def dataset_extend(mod)
643
- dataset.extend(mod) if @dataset
652
+ @dataset.extend(mod) if @dataset
653
+ reset_instance_dataset
644
654
  dataset_method_modules << mod
645
- meths = mod.public_instance_methods.reject{|x| NORMAL_METHOD_NAME_REGEXP !~ x.to_s}
646
- def_dataset_method(*meths) unless meths.empty?
655
+ mod.public_instance_methods.each{|meth| def_model_dataset_method(meth)}
647
656
  end
648
657
 
649
658
  # Create a column accessor for a column with a method name that is hard to use in ruby code.
@@ -671,8 +680,14 @@ module Sequel
671
680
  # Define a model method that calls the dataset method with the same name,
672
681
  # only used for methods with names that can't be presented directly in
673
682
  # ruby code.
674
- def def_model_dataset_method_block(arg)
675
- meta_def(arg){|*args, &block| dataset.send(arg, *args, &block)}
683
+ def def_model_dataset_method(meth)
684
+ return if respond_to?(meth, true)
685
+
686
+ if meth.to_s =~ NORMAL_METHOD_NAME_REGEXP
687
+ instance_eval("def #{meth}(*args, &block); dataset.#{meth}(*args, &block) end", __FILE__, __LINE__)
688
+ else
689
+ meta_def(meth){|*args, &block| dataset.send(meth, *args, &block)}
690
+ end
676
691
  end
677
692
 
678
693
  # Get the schema from the database, fall back on checking the columns
@@ -799,6 +814,12 @@ module Sequel
799
814
  "DELETE FROM #@simple_table WHERE #@simple_pk = ".freeze
800
815
  end
801
816
  end
817
+
818
+ # Reset the instance dataset to a modified copy of the current dataset,
819
+ # should be used whenever the model's dataset is modified.
820
+ def reset_instance_dataset
821
+ @instance_dataset = @dataset.limit(1).naked if @dataset
822
+ end
802
823
 
803
824
  # Set the columns for this model and create accessor methods for each column.
804
825
  def set_columns(new_columns)