sequel 3.7.0 → 3.8.0

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