sequel 3.41.0 → 3.42.0

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