sequel 3.7.0 → 3.8.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.
Files changed (42) hide show
  1. data/CHANGELOG +50 -0
  2. data/doc/advanced_associations.rdoc +4 -3
  3. data/doc/release_notes/3.7.0.txt +2 -2
  4. data/doc/release_notes/3.8.0.txt +151 -0
  5. data/lib/sequel/adapters/jdbc.rb +10 -2
  6. data/lib/sequel/adapters/jdbc/h2.rb +14 -0
  7. data/lib/sequel/adapters/mysql.rb +36 -26
  8. data/lib/sequel/adapters/postgres.rb +27 -19
  9. data/lib/sequel/adapters/shared/mssql.rb +12 -4
  10. data/lib/sequel/adapters/shared/mysql.rb +16 -0
  11. data/lib/sequel/connection_pool.rb +178 -57
  12. data/lib/sequel/database.rb +60 -12
  13. data/lib/sequel/database/schema_generator.rb +1 -2
  14. data/lib/sequel/dataset.rb +10 -1
  15. data/lib/sequel/dataset/actions.rb +4 -8
  16. data/lib/sequel/dataset/convenience.rb +9 -2
  17. data/lib/sequel/dataset/query.rb +2 -5
  18. data/lib/sequel/dataset/sql.rb +0 -1
  19. data/lib/sequel/exceptions.rb +3 -3
  20. data/lib/sequel/metaprogramming.rb +4 -18
  21. data/lib/sequel/model/associations.rb +2 -2
  22. data/lib/sequel/model/base.rb +15 -18
  23. data/lib/sequel/model/default_inflections.rb +0 -1
  24. data/lib/sequel/model/plugins.rb +3 -2
  25. data/lib/sequel/plugins/boolean_readers.rb +3 -2
  26. data/lib/sequel/plugins/identity_map.rb +9 -0
  27. data/lib/sequel/plugins/validation_helpers.rb +8 -1
  28. data/lib/sequel/sql.rb +21 -11
  29. data/lib/sequel/version.rb +1 -1
  30. data/spec/adapters/mssql_spec.rb +7 -1
  31. data/spec/adapters/mysql_spec.rb +22 -0
  32. data/spec/core/connection_pool_spec.rb +211 -3
  33. data/spec/core/core_sql_spec.rb +7 -0
  34. data/spec/core/database_spec.rb +159 -7
  35. data/spec/core/dataset_spec.rb +33 -0
  36. data/spec/core/spec_helper.rb +1 -0
  37. data/spec/extensions/boolean_readers_spec.rb +6 -0
  38. data/spec/extensions/identity_map_spec.rb +29 -1
  39. data/spec/extensions/inflector_spec.rb +0 -1
  40. data/spec/extensions/validation_helpers_spec.rb +23 -0
  41. data/spec/integration/type_test.rb +1 -1
  42. metadata +131 -129
@@ -1,6 +1,5 @@
1
1
  module Sequel
2
- # The Schema module holds the schema generators and the SQL code relating
3
- # to SQL DDL (Data Definition Language).
2
+ # The Schema module holds the schema generators.
4
3
  module Schema
5
4
  # Schema::Generator is an internal class that the user is not expected
6
5
  # to instantiate directly. Instances are created by Database#create_table.
@@ -41,7 +41,7 @@ module Sequel
41
41
 
42
42
  # Which options don't affect the SQL generation. Used by simple_select_all?
43
43
  # to determine if this is a simple SELECT * FROM table.
44
- NON_SQL_OPTIONS = [:server, :defaults, :overrides]
44
+ NON_SQL_OPTIONS = [:server, :defaults, :overrides, :graph, :eager_graph, :graph_aliases]
45
45
 
46
46
  NOTIMPL_MSG = "This method must be overridden in Sequel adapters".freeze
47
47
  WITH_SUPPORTED=:select_with_sql
@@ -120,6 +120,15 @@ module Sequel
120
120
  end
121
121
  end
122
122
 
123
+ # Yield a dataset for each server in the connection pool that is tied to that server.
124
+ # Intended for use in sharded environments where all servers need to be modified
125
+ # with the same data:
126
+ #
127
+ # DB[:configs].where(:key=>'setting').each_server{|ds| ds.update(:value=>'new_value')}
128
+ def each_server
129
+ db.servers.each{|s| yield server(s)}
130
+ end
131
+
123
132
  # Returns a string representation of the dataset including the class name
124
133
  # and the corresponding SQL select statement.
125
134
  def inspect
@@ -1,6 +1,5 @@
1
1
  module Sequel
2
2
  class Dataset
3
-
4
3
  # Alias for insert, but not aliased directly so subclasses
5
4
  # don't have to override both methods.
6
5
  def <<(*args)
@@ -56,12 +55,10 @@ module Sequel
56
55
  def each(&block)
57
56
  if @opts[:graph]
58
57
  graph_each(&block)
58
+ elsif row_proc = @row_proc
59
+ fetch_rows(select_sql){|r| yield row_proc.call(r)}
59
60
  else
60
- if row_proc = @row_proc
61
- fetch_rows(select_sql){|r| yield row_proc.call(r)}
62
- else
63
- fetch_rows(select_sql, &block)
64
- end
61
+ fetch_rows(select_sql, &block)
65
62
  end
66
63
  self
67
64
  end
@@ -79,7 +76,7 @@ module Sequel
79
76
  execute_insert(insert_sql(*values))
80
77
  end
81
78
 
82
- # Alias for set, but not aliased directly so subclasses
79
+ # Alias for update, but not aliased directly so subclasses
83
80
  # don't have to override both methods.
84
81
  def set(*args)
85
82
  update(*args)
@@ -118,6 +115,5 @@ module Sequel
118
115
  def execute_insert(sql, opts={}, &block)
119
116
  @db.execute_insert(sql, default_server_opts(opts), &block)
120
117
  end
121
-
122
118
  end
123
119
  end
@@ -194,10 +194,16 @@ module Sequel
194
194
  end
195
195
  end
196
196
 
197
+ # Returns a hash with key_column values as keys and value_column values as
198
+ # values. Similar to to_hash, but only selects the two columns.
197
199
  def select_hash(key_column, value_column)
198
200
  select(key_column, value_column).to_hash(hash_key_symbol(key_column), hash_key_symbol(value_column))
199
201
  end
200
-
202
+
203
+ # Selects the column given (either as an argument or as a block), and
204
+ # returns an array of all values of that column in the dataset. If you
205
+ # give a block argument that returns an array with multiple entries,
206
+ # the contents of the resulting array are undefined.
201
207
  def select_map(column=nil, &block)
202
208
  ds = naked.ungraphed
203
209
  ds = if column
@@ -208,7 +214,8 @@ module Sequel
208
214
  end
209
215
  ds.map{|r| r.values.first}
210
216
  end
211
-
217
+
218
+ # The same as select_map, but in addition orders the array by the column.
212
219
  def select_order_map(column=nil, &block)
213
220
  ds = naked.ungraphed
214
221
  ds = if column
@@ -1,8 +1,5 @@
1
1
  module Sequel
2
2
  class Dataset
3
-
4
- FROM_SELF_KEEP_OPTS = [:graph, :eager_graph, :graph_aliases]
5
-
6
3
  # Adds an further filter to an existing filter using AND. If no filter
7
4
  # exists an error is raised. This method is identical to #filter except
8
5
  # it expects an existing filter.
@@ -148,7 +145,7 @@ module Sequel
148
145
  # ds.from_self(:alias=>:foo).sql #=> "SELECT * FROM (SELECT id, name FROM items ORDER BY name) AS foo"
149
146
  def from_self(opts={})
150
147
  fs = {}
151
- @opts.keys.each{|k| fs[k] = nil unless FROM_SELF_KEEP_OPTS.include?(k)}
148
+ @opts.keys.each{|k| fs[k] = nil unless NON_SQL_OPTIONS.include?(k)}
152
149
  clone(fs).from(opts[:alias] ? as(opts[:alias]) : self)
153
150
  end
154
151
 
@@ -263,7 +260,7 @@ module Sequel
263
260
  columns += Array(Sequel.virtual_row(&block)) if block
264
261
  clone(:order => (columns.compact.empty?) ? nil : columns)
265
262
  end
266
- alias_method :order_by, :order
263
+ alias order_by order
267
264
 
268
265
  # Returns a copy of the dataset with the order columns added
269
266
  # to the existing order.
@@ -1,6 +1,5 @@
1
1
  module Sequel
2
2
  class Dataset
3
-
4
3
  # Given a type (e.g. select) and an array of clauses,
5
4
  # return an array of methods to call to build the SQL string.
6
5
  def self.clause_methods(type, clauses)
@@ -28,14 +28,14 @@ module Sequel
28
28
  # Raised on an invalid operation, such as trying to update or delete
29
29
  # a joined or grouped dataset.
30
30
  class InvalidOperation < Error; end
31
-
31
+
32
32
  # Raised when attempting an invalid type conversion.
33
33
  class InvalidValue < Error ; end
34
-
34
+
35
35
  # Raised when the connection pool cannot acquire a database connection
36
36
  # before the timeout.
37
37
  class PoolTimeout < Error ; end
38
-
38
+
39
39
  # Exception that you should raise to signal a rollback of the current transaction.
40
40
  # The transaction block will catch this exception, rollback the current transaction,
41
41
  # and won't reraise it.
@@ -1,23 +1,9 @@
1
1
  module Sequel
2
- # Contains methods that ease metaprogramming, used by some of Sequel's classes.
2
+ # Contains meta_def method for adding methods to objects via blocks, used by some of Sequel's classes and objects.
3
3
  module Metaprogramming
4
- # Add methods to the object's metaclass
4
+ # Define a method with the given name and block body on the receiver.
5
5
  def meta_def(name, &block)
6
- meta_eval{define_method(name, &block)}
7
- end
8
-
9
- private
10
-
11
- # Evaluate the block in the context of the object's metaclass
12
- def meta_eval(&block)
13
- metaclass.instance_eval(&block)
14
- end
15
-
16
- # The hidden singleton lurks behind everyone
17
- def metaclass
18
- class << self
19
- self
20
- end
21
- end
6
+ (class << self; self end).send(:define_method, name, &block)
7
+ end
22
8
  end
23
9
  end
@@ -461,7 +461,7 @@ module Sequel
461
461
  # before a new item is added to the association.
462
462
  # - :before_remove - Symbol, Proc, or array of both/either specifying a callback to call
463
463
  # before an item is removed from the association.
464
- # - :cartesian_product_number - he number of joins completed by this association that could cause more
464
+ # - :cartesian_product_number - the number of joins completed by this association that could cause more
465
465
  # than one row for each row in the current table (default: 0 for many_to_one associations,
466
466
  # 1 for *_to_many associations).
467
467
  # - :class - The associated class or its name. If not
@@ -1377,7 +1377,7 @@ module Sequel
1377
1377
 
1378
1378
  # Make sure the association is valid for this model, and return the related AssociationReflection.
1379
1379
  def check_association(model, association)
1380
- raise(Sequel::Error, 'Invalid association') unless reflection = model.association_reflection(association)
1380
+ raise(Sequel::Error, "Invalid association #{association} for #{model.name}") unless reflection = model.association_reflection(association)
1381
1381
  raise(Sequel::Error, "Eager loading is not allowed for #{model.name} association #{association}") if reflection[:allow_eager] == false
1382
1382
  reflection
1383
1383
  end
@@ -290,13 +290,7 @@ module Sequel
290
290
  @dataset_methods.each{|meth, block| @dataset.meta_def(meth, &block)} if @dataset_methods
291
291
  end
292
292
  @dataset.model = self if @dataset.respond_to?(:model=)
293
- begin
294
- @db_schema = (inherited ? superclass.db_schema : get_db_schema)
295
- rescue Sequel::DatabaseConnectionError
296
- raise
297
- rescue
298
- nil
299
- end
293
+ check_non_connection_error{@db_schema = (inherited ? superclass.db_schema : get_db_schema)}
300
294
  self
301
295
  end
302
296
  alias dataset= set_dataset
@@ -368,6 +362,17 @@ module Sequel
368
362
 
369
363
  private
370
364
 
365
+ # Yield to the passed block and swallow all errors other than DatabaseConnectionErrors.
366
+ def check_non_connection_error
367
+ begin
368
+ yield
369
+ rescue Sequel::DatabaseConnectionError
370
+ raise
371
+ rescue
372
+ nil
373
+ end
374
+ end
375
+
371
376
  # Create a column accessor for a column with a method name that is hard to use in ruby code.
372
377
  def def_bad_column_accessor(column)
373
378
  overridable_methods_module.module_eval do
@@ -399,15 +404,7 @@ module Sequel
399
404
  ds_opts = dataset.opts
400
405
  single_table = ds_opts[:from] && (ds_opts[:from].length == 1) \
401
406
  && !ds_opts.include?(:join) && !ds_opts.include?(:sql)
402
- get_columns = proc do
403
- begin
404
- columns
405
- rescue Sequel::DatabaseConnectionError
406
- raise
407
- rescue
408
- []
409
- end
410
- end
407
+ get_columns = proc{check_non_connection_error{columns} || []}
411
408
  if single_table && (schema_array = (db.schema(table_name, :reload=>reload) rescue nil))
412
409
  schema_array.each{|k,v| schema_hash[k] = v}
413
410
  if ds_opts.include?(:select)
@@ -576,7 +573,7 @@ module Sequel
576
573
  # self.class.
577
574
  alias_method :model, :class
578
575
 
579
- # The current cached associations. A hash with the keys being the
576
+ # The currently cached associations. A hash with the keys being the
580
577
  # association name symbols and the values being the associated object
581
578
  # or nil (many_to_one), or the array of associated objects (*_to_many).
582
579
  def associations
@@ -904,7 +901,7 @@ module Sequel
904
901
 
905
902
  # If transactions should be used, wrap the yield in a transaction block.
906
903
  def checked_transaction(opts)
907
- use_transaction?(opts)? db.transaction(opts){yield} : yield
904
+ use_transaction?(opts) ? db.transaction(opts){yield} : yield
908
905
  end
909
906
 
910
907
  # Default inspection output for the values hash, overwrite to change what #inspect displays.
@@ -37,7 +37,6 @@ module Sequel
37
37
  irregular('child', 'children')
38
38
  irregular('sex', 'sexes')
39
39
  irregular('move', 'moves')
40
- irregular('ox', 'oxen')
41
40
  irregular('quiz', 'quizzes')
42
41
  irregular('testis', 'testes')
43
42
 
@@ -5,8 +5,9 @@ module Sequel
5
5
  # Plugins should be modules with one of the following conditions:
6
6
  # * A singleton method named apply, which takes a model,
7
7
  # additional arguments, and an optional block. This is called
8
- # once, the first time the plugin is loaded, with the arguments
9
- # and block provide to the call to Model.plugin.
8
+ # the first time the plugin is loaded for this model (unless it was
9
+ # already loaded by an ancestor class), with the arguments
10
+ # and block provided to the call to Model.plugin.
10
11
  # * A module inside the plugin module named InstanceMethods,
11
12
  # which will be included in the model class.
12
13
  # * A module inside the plugin module named ClassMethods,
@@ -15,7 +15,7 @@ module Sequel
15
15
  module BooleanReaders
16
16
  # Default proc for determining if given column is a boolean, which
17
17
  # just checks that the :type is boolean.
18
- DEFAULT_BOOLEAN_ATTRIBUTE_PROC = lambda{|c| db_schema[c][:type] == :boolean}
18
+ DEFAULT_BOOLEAN_ATTRIBUTE_PROC = lambda{|c| s = db_schema[c] and s[:type] == :boolean}
19
19
 
20
20
  # Add the boolean_attribute? class method to the model, and create
21
21
  # attribute? boolean reader methods for the class's columns if the class has a dataset.
@@ -44,7 +44,8 @@ module Sequel
44
44
  # Add attribute? methods for all of the boolean attributes for this model.
45
45
  def create_boolean_readers
46
46
  im = instance_methods.collect{|x| x.to_s}
47
- columns.each{|c| create_boolean_reader(c) if boolean_attribute?(c) && !im.include?("#{c}?")}
47
+ cs = columns rescue return
48
+ cs.each{|c| create_boolean_reader(c) if boolean_attribute?(c) && !im.include?("#{c}?")}
48
49
  end
49
50
  end
50
51
  end
@@ -80,6 +80,15 @@ module Sequel
80
80
  end
81
81
 
82
82
  module InstanceMethods
83
+ # Remove instances from the identity map cache if they are deleted.
84
+ def delete
85
+ super
86
+ if idm = model.identity_map
87
+ idm.delete(model.identity_map_key(pk))
88
+ end
89
+ self
90
+ end
91
+
83
92
  # Merge the current values into the values provided in the row, ensuring
84
93
  # that current values are not overridden by new values.
85
94
  def merge_db_update(row)
@@ -54,6 +54,7 @@ module Sequel
54
54
  :min_length=>{:message=>lambda{|min| "is shorter than #{min} characters"}},
55
55
  :not_string=>{:message=>lambda{|type| type ? "is not a valid #{type}" : "is a string"}},
56
56
  :numeric=>{:message=>lambda{"is not a number"}},
57
+ :type=>{:message=>lambda{|klass| "is not a #{klass}"}},
57
58
  :presence=>{:message=>lambda{"is not present"}},
58
59
  :unique=>{:message=>lambda{'is already taken'}}
59
60
  }
@@ -110,7 +111,7 @@ module Sequel
110
111
  def validates_not_string(atts, opts={})
111
112
  validatable_attributes_for_type(:not_string, atts, opts){|a,v,m| validation_error_message(m, (db_schema[a]||{})[:type]) if v.is_a?(String)}
112
113
  end
113
-
114
+
114
115
  # Check attribute value(s) string representation is a valid float.
115
116
  def validates_numeric(atts, opts={})
116
117
  validatable_attributes_for_type(:numeric, atts, opts) do |a,v,m|
@@ -122,6 +123,12 @@ module Sequel
122
123
  end
123
124
  end
124
125
  end
126
+
127
+ # Check if value is an instance of a class
128
+ def validates_type(klass, atts, opts={})
129
+ klass = klass.to_s.constantize if klass.is_a?(String) || klass.is_a?(Symbol)
130
+ validatable_attributes_for_type(:type, atts, opts){|a,v,m| validation_error_message(m, klass) if v && !v.is_a?(klass)}
131
+ end
125
132
 
126
133
  # Check attribute value(s) is not considered blank by the database, but allow false values.
127
134
  def validates_presence(atts, opts={})
@@ -36,6 +36,16 @@ module Sequel
36
36
 
37
37
  # Base class for all SQL fragments
38
38
  class Expression
39
+ # all instance variables declared to be readers are to be used for comparison.
40
+ def self.attr_reader(*args)
41
+ super
42
+ comparison_attrs.concat args
43
+ end
44
+
45
+ def self.comparison_attrs
46
+ @comparison_attrs ||= self == Expression ? [] : superclass.comparison_attrs.clone
47
+ end
48
+
39
49
  # Create a to_s instance method that takes a dataset, and calls
40
50
  # the method provided on the dataset with args as the argument (self by default).
41
51
  # Used to DRY up some code.
@@ -54,6 +64,13 @@ module Sequel
54
64
  def sql_literal(ds)
55
65
  to_s(ds)
56
66
  end
67
+
68
+ # Returns true if the receiver is the same expression as the
69
+ # the +other+ expression.
70
+ def eql?(other)
71
+ other.is_a?(self.class) && !self.class.comparison_attrs.find {|a| send(a) != other.send(a)}
72
+ end
73
+ alias == eql?
57
74
  end
58
75
 
59
76
  # Represents a complex SQL expression, with a given operator and one
@@ -74,10 +91,10 @@ module Sequel
74
91
  :'!~*' => :'~*', :NOT => :NOOP, :NOOP => :NOT, :ILIKE => :'NOT ILIKE',
75
92
  :'NOT ILIKE'=>:ILIKE}
76
93
 
77
- # Mathematical Operators used in NumericMethods
94
+ # Standard Mathematical Operators used in NumericMethods
78
95
  MATHEMATICAL_OPERATORS = [:+, :-, :/, :*]
79
96
 
80
- # Mathematical Operators used in NumericMethods
97
+ # Bitwise Mathematical Operators used in NumericMethods
81
98
  BITWISE_OPERATORS = [:&, :|, :^, :<<, :>>]
82
99
 
83
100
  # Inequality Operators used in InequalityMethods
@@ -125,13 +142,6 @@ module Sequel
125
142
  @op = op
126
143
  @args = args
127
144
  end
128
-
129
- # Returns true if the receiver is the same expression as the
130
- # the +other+ expression.
131
- def eql?(other)
132
- other.is_a?(self.class) && @op.eql?(other.op) && @args.eql?(other.args)
133
- end
134
- alias == eql?
135
145
 
136
146
  to_s_method :complex_expression_sql, '@op, @args'
137
147
  end
@@ -548,7 +558,7 @@ module Sequel
548
558
  include SubscriptMethods
549
559
  end
550
560
 
551
- # Represents constants or psuedo-constants (e.g. CURRENT_DATE) in SQL
561
+ # Represents constants or psuedo-constants (e.g. CURRENT_DATE) in SQL.
552
562
  class Constant < GenericExpression
553
563
  # Create an object with the given table
554
564
  def initialize(constant)
@@ -567,7 +577,7 @@ module Sequel
567
577
  end
568
578
 
569
579
  # Represents inverse boolean constants (currently only NOTNULL). A
570
- # special class to allow for special behavior
580
+ # special class to allow for special behavior.
571
581
  class NegativeBooleanConstant < BooleanConstant
572
582
  to_s_method :negative_boolean_constant_sql, '@constant'
573
583
  end
@@ -1,6 +1,6 @@
1
1
  module Sequel
2
2
  MAJOR = 3
3
- MINOR = 7
3
+ MINOR = 8
4
4
  TINY = 0
5
5
 
6
6
  VERSION = [MAJOR, MINOR, TINY].join('.')
@@ -1,5 +1,7 @@
1
1
  require File.join(File.dirname(__FILE__), 'spec_helper.rb')
2
2
 
3
+ require ENV['SEQUEL_MSSQL_SPEC_REQUIRE'] if ENV['SEQUEL_MSSQL_SPEC_REQUIRE']
4
+
3
5
  unless defined?(MSSQL_DB)
4
6
  MSSQL_URL = 'jdbc:sqlserver://localhost;integratedSecurity=true;database=sandbox' unless defined? MSSQL_URL
5
7
  MSSQL_DB = Sequel.connect(ENV['SEQUEL_MSSQL_SPEC_DB']||MSSQL_URL)
@@ -332,9 +334,13 @@ context "MSSSQL::Dataset#insert" do
332
334
  specify "should have insert_select return nil if disable_insert_output is used" do
333
335
  @ds.disable_insert_output.insert_select(:value=>10).should == nil
334
336
  end
337
+
338
+ specify "should have insert_select return nil if the server version is not 2005+" do
339
+ @ds.meta_def(:server_version){8000760}
340
+ @ds.insert_select(:value=>10).should == nil
341
+ end
335
342
 
336
343
  specify "should have insert_select insert the record and return the inserted record" do
337
- @ds.meta_def(:server_version){80201}
338
344
  h = @ds.insert_select(:value=>10)
339
345
  h[:value].should == 10
340
346
  @ds.first(:xid=>h[:xid])[:value].should == 10