sequel 3.45.0 → 3.46.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (67) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG +34 -0
  3. data/README.rdoc +6 -0
  4. data/Rakefile +46 -33
  5. data/doc/release_notes/3.46.0.txt +122 -0
  6. data/doc/schema_modification.rdoc +42 -6
  7. data/doc/security.rdoc +379 -0
  8. data/doc/transactions.rdoc +1 -1
  9. data/lib/sequel/adapters/jdbc/as400.rb +1 -0
  10. data/lib/sequel/adapters/jdbc/h2.rb +11 -0
  11. data/lib/sequel/adapters/mysql2.rb +3 -9
  12. data/lib/sequel/adapters/postgres.rb +34 -2
  13. data/lib/sequel/adapters/shared/cubrid.rb +5 -0
  14. data/lib/sequel/adapters/shared/mssql.rb +27 -3
  15. data/lib/sequel/adapters/shared/mysql.rb +25 -4
  16. data/lib/sequel/adapters/shared/sqlite.rb +12 -1
  17. data/lib/sequel/connection_pool.rb +3 -3
  18. data/lib/sequel/connection_pool/sharded_threaded.rb +7 -8
  19. data/lib/sequel/connection_pool/threaded.rb +7 -8
  20. data/lib/sequel/core.rb +5 -2
  21. data/lib/sequel/database.rb +1 -1
  22. data/lib/sequel/database/connecting.rb +7 -7
  23. data/lib/sequel/database/features.rb +88 -0
  24. data/lib/sequel/database/misc.rb +14 -64
  25. data/lib/sequel/database/query.rb +0 -332
  26. data/lib/sequel/database/schema_generator.rb +36 -3
  27. data/lib/sequel/database/schema_methods.rb +48 -12
  28. data/lib/sequel/database/transactions.rb +344 -0
  29. data/lib/sequel/dataset/actions.rb +24 -9
  30. data/lib/sequel/dataset/mutation.rb +20 -0
  31. data/lib/sequel/dataset/query.rb +0 -17
  32. data/lib/sequel/dataset/sql.rb +7 -0
  33. data/lib/sequel/exceptions.rb +10 -6
  34. data/lib/sequel/extensions/_pretty_table.rb +2 -2
  35. data/lib/sequel/extensions/looser_typecasting.rb +10 -0
  36. data/lib/sequel/extensions/migration.rb +5 -2
  37. data/lib/sequel/model.rb +1 -1
  38. data/lib/sequel/model/associations.rb +16 -14
  39. data/lib/sequel/model/base.rb +14 -2
  40. data/lib/sequel/plugins/composition.rb +3 -3
  41. data/lib/sequel/plugins/dirty.rb +6 -6
  42. data/lib/sequel/plugins/hook_class_methods.rb +3 -0
  43. data/lib/sequel/plugins/serialization.rb +7 -17
  44. data/lib/sequel/plugins/string_stripper.rb +2 -1
  45. data/lib/sequel/plugins/validation_helpers.rb +1 -1
  46. data/lib/sequel/sql.rb +3 -0
  47. data/lib/sequel/version.rb +1 -1
  48. data/spec/adapters/mssql_spec.rb +21 -0
  49. data/spec/adapters/postgres_spec.rb +35 -8
  50. data/spec/core/database_spec.rb +4 -0
  51. data/spec/core/dataset_spec.rb +48 -2
  52. data/spec/core/schema_generator_spec.rb +10 -1
  53. data/spec/core/schema_spec.rb +69 -0
  54. data/spec/extensions/composition_spec.rb +21 -2
  55. data/spec/extensions/dirty_spec.rb +17 -10
  56. data/spec/extensions/eager_each_spec.rb +4 -1
  57. data/spec/extensions/looser_typecasting_spec.rb +16 -19
  58. data/spec/extensions/migration_spec.rb +7 -1
  59. data/spec/extensions/serialization_spec.rb +22 -0
  60. data/spec/extensions/single_table_inheritance_spec.rb +3 -2
  61. data/spec/extensions/validation_helpers_spec.rb +6 -0
  62. data/spec/integration/dataset_test.rb +5 -0
  63. data/spec/integration/schema_test.rb +16 -0
  64. data/spec/model/associations_spec.rb +40 -0
  65. data/spec/model/base_spec.rb +21 -1
  66. data/spec/model/record_spec.rb +3 -0
  67. metadata +14 -10
@@ -10,7 +10,7 @@ module Sequel
10
10
  # Action methods defined by Sequel that execute code on the database.
11
11
  ACTION_METHODS = (<<-METHS).split.map{|x| x.to_sym}
12
12
  << [] []= all avg count columns columns! delete each
13
- empty? fetch_rows first get import insert insert_multiple interval last
13
+ empty? fetch_rows first first! get import insert insert_multiple interval last
14
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
@@ -176,8 +176,13 @@ module Sequel
176
176
  # matching records up to that limit. If no argument is passed,
177
177
  # it returns the first matching record. If any other type of
178
178
  # argument(s) is passed, it is given to filter and the
179
- # first matching record is returned. If a block is given, it is used
180
- # to filter the dataset before returning anything. Examples:
179
+ # first matching record is returned. If a block is given, it is used
180
+ # to filter the dataset before returning anything.
181
+ #
182
+ # If there are no records in the dataset, returns nil (or an empty
183
+ # array if an integer argument is given).
184
+ #
185
+ # Examples:
181
186
  #
182
187
  # DB[:table].first # SELECT * FROM table LIMIT 1
183
188
  # # => {:id=>7}
@@ -217,6 +222,12 @@ module Sequel
217
222
  end
218
223
  end
219
224
 
225
+ # Calls first. If first returns nil (signaling that no
226
+ # row matches), raise a Sequel::NoMatchingRow exception.
227
+ def first!(*args, &block)
228
+ first(*args, &block) || raise(Sequel::NoMatchingRow)
229
+ end
230
+
220
231
  # Return the column value for the first matching record in the dataset.
221
232
  # Raises an error if both an argument and block is given.
222
233
  #
@@ -869,21 +880,25 @@ module Sequel
869
880
  # Return a plain symbol given a potentially qualified or aliased symbol,
870
881
  # specifying the symbol that is likely to be used as the hash key
871
882
  # for the column when records are returned.
872
- def hash_key_symbol(s)
883
+ def hash_key_symbol(s, recursing=false)
873
884
  case s
874
885
  when Symbol
875
886
  _, c, a = split_symbol(s)
876
887
  (a || c).to_sym
877
888
  when SQL::Identifier, SQL::Wrapper
878
- hash_key_symbol(s.value)
889
+ hash_key_symbol(s.value, true)
879
890
  when SQL::QualifiedIdentifier
880
- hash_key_symbol(s.column)
891
+ hash_key_symbol(s.column, true)
881
892
  when SQL::AliasedExpression
882
- hash_key_symbol(s.aliaz)
893
+ hash_key_symbol(s.aliaz, true)
883
894
  when String
884
- s.to_sym
895
+ if recursing
896
+ s.to_sym
897
+ else
898
+ raise(Error, "#{s.inspect} is not supported, should be a Symbol, SQL::Identifier, SQL::QualifiedIdentifier, or SQL::AliasedExpression")
899
+ end
885
900
  else
886
- raise(Error, "#{s.inspect} is not supported, should be a Symbol, String, SQL::Identifier, SQL::QualifiedIdentifier, or SQL::AliasedExpression")
901
+ raise(Error, "#{s.inspect} is not supported, should be a Symbol, SQL::Identifier, SQL::QualifiedIdentifier, or SQL::AliasedExpression")
887
902
  end
888
903
  end
889
904
 
@@ -11,6 +11,9 @@ module Sequel
11
11
  # Setup mutation (e.g. filter!) methods. These operate the same as the
12
12
  # non-! methods, but replace the options of the current dataset with the
13
13
  # options of the resulting dataset.
14
+ #
15
+ # Do not call this method with untrusted input, as that can result in
16
+ # arbitrary code execution.
14
17
  def self.def_mutation_method(*meths)
15
18
  options = meths.pop if meths.last.is_a?(Hash)
16
19
  mod = options[:module] if options
@@ -36,6 +39,23 @@ module Sequel
36
39
  # a single hash argument and returns the object you want #each to return.
37
40
  attr_accessor :row_proc
38
41
 
42
+ # Load an extension into the receiver. In addition to requiring the extension file, this
43
+ # also modifies the dataset to work with the extension (usually extending it with a
44
+ # module defined in the extension file). If no related extension file exists or the
45
+ # extension does not have specific support for Database objects, an Error will be raised.
46
+ # Returns self.
47
+ def extension!(*exts)
48
+ Sequel.extension(*exts)
49
+ exts.each do |ext|
50
+ if pr = Sequel.synchronize{EXTENSIONS[ext]}
51
+ pr.call(self)
52
+ else
53
+ raise(Error, "Extension #{ext} does not have specific support handling individual datasets")
54
+ end
55
+ end
56
+ self
57
+ end
58
+
39
59
  # Avoid self-referential dataset by cloning.
40
60
  def from_self!(*args, &block)
41
61
  @opts.merge!(clone.from_self(*args, &block).opts)
@@ -161,23 +161,6 @@ module Sequel
161
161
  clone.extension!(*exts)
162
162
  end
163
163
 
164
- # Load an extension into the receiver. In addition to requiring the extension file, this
165
- # also modifies the dataset to work with the extension (usually extending it with a
166
- # module defined in the extension file). If no related extension file exists or the
167
- # extension does not have specific support for Database objects, an Error will be raised.
168
- # Returns self.
169
- def extension!(*exts)
170
- Sequel.extension(*exts)
171
- exts.each do |ext|
172
- if pr = Sequel.synchronize{EXTENSIONS[ext]}
173
- pr.call(self)
174
- else
175
- raise(Error, "Extension #{ext} does not have specific support handling individual datasets")
176
- end
177
- end
178
- self
179
- end
180
-
181
164
  # Returns a copy of the dataset with the given conditions imposed upon it.
182
165
  # If the query already has a HAVING clause, then the conditions are imposed in the
183
166
  # HAVING clause. If not, then they are imposed in the WHERE clause.
@@ -326,6 +326,13 @@ module Sequel
326
326
  subselect_sql
327
327
  table_ref
328
328
  END
329
+
330
+ # For each of the methods in the given array, define a method with
331
+ # that name that returns a string with the SQL fragment that the
332
+ # related *_append method would add.
333
+ #
334
+ # Do not call this method with untrusted input, as that can result in
335
+ # arbitrary code execution.
329
336
  def self.def_append_methods(meths)
330
337
  meths.each do |meth|
331
338
  class_eval(<<-END, __FILE__, __LINE__ + 1)
@@ -7,8 +7,8 @@ module Sequel
7
7
  attr_accessor :wrapped_exception
8
8
  end
9
9
 
10
- # Raised when the adapter requested doesn't exist or can't be loaded.
11
- class AdapterNotFound < Error ; end
10
+ # Error raised when the adapter requested doesn't exist or can't be loaded.
11
+ class AdapterNotFound < Error; end
12
12
 
13
13
  # Generic error raised by the database adapters, indicating a
14
14
  # problem originating from the database server. Usually raised
@@ -48,19 +48,23 @@ module Sequel
48
48
  class InvalidOperation < Error; end
49
49
 
50
50
  # Error raised when attempting an invalid type conversion.
51
- class InvalidValue < Error ; end
51
+ class InvalidValue < Error; end
52
+
53
+ # Error raised when the user requests a record via the first! or similar
54
+ # method, and the dataset does not yield any rows.
55
+ class NoMatchingRow < Error; end
52
56
 
53
57
  # Error raised when the adapter adapter hasn't implemented a method such as +tables+:
54
58
  class NotImplemented < Error; end
55
59
 
56
60
  # Error raised when the connection pool cannot acquire a database connection
57
61
  # before the timeout.
58
- class PoolTimeout < Error ; end
62
+ class PoolTimeout < Error; end
59
63
 
60
- # Exception that you should raise to signal a rollback of the current transaction.
64
+ # Error that you should raise to signal a rollback of the current transaction.
61
65
  # The transaction block will catch this exception, rollback the current transaction,
62
66
  # and won't reraise it (unless a reraise is requested).
63
- class Rollback < Error ; end
67
+ class Rollback < Error; end
64
68
 
65
69
  # Error raised when unbinding a dataset that has multiple different values
66
70
  # for a given variable.
@@ -39,12 +39,12 @@ module Sequel
39
39
  sizes = Hash.new {0}
40
40
  columns.each do |c|
41
41
  s = c.to_s.size
42
- sizes[c.to_sym] = s if s > sizes[c.to_sym]
42
+ sizes[c] = s if s > sizes[c]
43
43
  end
44
44
  records.each do |r|
45
45
  columns.each do |c|
46
46
  s = r[c].to_s.size
47
- sizes[c.to_sym] = s if s > sizes[c.to_sym]
47
+ sizes[c] = s if s > sizes[c]
48
48
  end
49
49
  end
50
50
  sizes
@@ -15,6 +15,16 @@ module Sequel
15
15
  def typecast_value_integer(value)
16
16
  value.to_i
17
17
  end
18
+
19
+ # Typecast the value to a BigDecimal, without checking if strings
20
+ # have a valid format.
21
+ def typecast_value_decimal(value)
22
+ if value.is_a?(String)
23
+ BigDecimal.new(value)
24
+ else
25
+ super
26
+ end
27
+ end
18
28
  end
19
29
 
20
30
  Database.register_extension(:looser_typecasting, LooserTypecasting)
@@ -241,11 +241,14 @@ module Sequel
241
241
  @actions << [:drop_constraint, args.first]
242
242
  end
243
243
 
244
- def add_foreign_key(*args)
244
+ def add_foreign_key(key, table, *args)
245
+ @actions << [:drop_foreign_key, key, *args]
246
+ end
247
+
248
+ def add_primary_key(*args)
245
249
  raise if args.first.is_a?(Array)
246
250
  @actions << [:drop_column, args.first]
247
251
  end
248
- alias add_primary_key add_foreign_key
249
252
 
250
253
  def add_index(*args)
251
254
  @actions << [:drop_index, *args]
@@ -72,7 +72,7 @@ module Sequel
72
72
 
73
73
  # Class methods added to model that call the method of the same name on the dataset
74
74
  DATASET_METHODS = (Dataset::ACTION_METHODS + Dataset::QUERY_METHODS +
75
- [:eager, :eager_graph, :each_page, :each_server, :print]) - [:and, :or, :[], :[]=, :columns, :columns!]
75
+ [:each_page, :each_server, :print]) - [:and, :or, :[], :[]=, :columns, :columns!]
76
76
 
77
77
  # Class instance variables to set to nil when a subclass is created, for -w compliance
78
78
  EMPTY_INSTANCE_VARIABLES = [:@overridable_methods_module, :@db]
@@ -1438,6 +1438,12 @@ module Sequel
1438
1438
  super
1439
1439
  end
1440
1440
 
1441
+ # Clear the associations cache when refreshing
1442
+ def set_values(hash)
1443
+ @associations.clear if @associations
1444
+ super
1445
+ end
1446
+
1441
1447
  # Formally used internally by the associations code, like pk but doesn't raise
1442
1448
  # an Error if the model has no primary key. Not used any longer, deprecated.
1443
1449
  def pk_or_nil
@@ -1510,12 +1516,6 @@ module Sequel
1510
1516
  end
1511
1517
  end
1512
1518
 
1513
- # Clear the associations cache when refreshing
1514
- def _refresh(dataset)
1515
- associations.clear
1516
- super
1517
- end
1518
-
1519
1519
  # Add the given associated object to the given association
1520
1520
  def add_associated_object(opts, o, *args)
1521
1521
  klass = opts.associated_class
@@ -1833,13 +1833,14 @@ module Sequel
1833
1833
  opt = opt ? opt.dup : {}
1834
1834
  associations.flatten.each do |association|
1835
1835
  case association
1836
- when Symbol
1837
- check_association(model, association)
1838
- opt[association] = nil
1839
- when Hash
1840
- association.keys.each{|assoc| check_association(model, assoc)}
1841
- opt.merge!(association)
1842
- else raise(Sequel::Error, 'Associations must be in the form of a symbol or hash')
1836
+ when Symbol
1837
+ check_association(model, association)
1838
+ opt[association] = nil
1839
+ when Hash
1840
+ association.keys.each{|assoc| check_association(model, assoc)}
1841
+ opt.merge!(association)
1842
+ else
1843
+ raise(Sequel::Error, 'Associations must be in the form of a symbol or hash')
1843
1844
  end
1844
1845
  end
1845
1846
  clone(:eager=>opt)
@@ -1956,7 +1957,8 @@ module Sequel
1956
1957
  ds = ds.eager_graph_association(ds, model, ta, requirements, eager_graph_check_association(model, assoc), assoc_assocs)
1957
1958
  end
1958
1959
  ds
1959
- else raise(Sequel::Error, 'Associations must be in the form of a symbol or hash')
1960
+ else
1961
+ raise(Sequel::Error, 'Associations must be in the form of a symbol or hash')
1960
1962
  end
1961
1963
  end
1962
1964
  ds
@@ -869,6 +869,9 @@ module Sequel
869
869
  # Define instance method(s) that calls class method(s) of the
870
870
  # same name, caching the result in an instance variable. Define
871
871
  # standard attr_writer method for modifying that instance variable.
872
+ #
873
+ # Do not call this method with untrusted input, as that can result in
874
+ # arbitrary code execution.
872
875
  def self.class_attr_overridable(*meths) # :nodoc:
873
876
  meths.each{|meth| class_eval("def #{meth}; !defined?(@#{meth}) ? (frozen? ? self.class.#{meth} : (@#{meth} = self.class.#{meth})) : @#{meth} end", __FILE__, __LINE__)}
874
877
  attr_writer(*meths)
@@ -878,6 +881,9 @@ module Sequel
878
881
  # same name. Replaces the construct:
879
882
  #
880
883
  # define_method(meth){self.class.send(meth)}
884
+ #
885
+ # Do not call this method with untrusted input, as that can result in
886
+ # arbitrary code execution.
881
887
  def self.class_attr_reader(*meths) # :nodoc:
882
888
  meths.each{|meth| class_eval("def #{meth}; self.class.#{meth} end", __FILE__, __LINE__)}
883
889
  end
@@ -1187,7 +1193,7 @@ module Sequel
1187
1193
  end
1188
1194
  end
1189
1195
 
1190
- # Returns a hash identifying mapping the receivers primary key column(s) to their values.
1196
+ # Returns a hash mapping the receivers primary key column(s) to their values.
1191
1197
  #
1192
1198
  # Artist[1].pk_hash # => {:id=>1}
1193
1199
  # Artist[[1, 2]].pk_hash # => {:id1=>1, :id2=>2}
@@ -1983,7 +1989,7 @@ module Sequel
1983
1989
  end
1984
1990
 
1985
1991
  # Given a primary key value, return the first record in the dataset with that primary key
1986
- # value.
1992
+ # value. If no records matches, returns nil.
1987
1993
  #
1988
1994
  # # Single primary key
1989
1995
  # Artist.dataset.with_pk(1) # SELECT * FROM artists WHERE (id = 1) LIMIT 1
@@ -1994,6 +2000,12 @@ module Sequel
1994
2000
  def with_pk(pk)
1995
2001
  first(model.qualified_primary_key_hash(pk))
1996
2002
  end
2003
+
2004
+ # Same as with_pk, but raises NoMatchingRow instead of returning nil if no
2005
+ # row matches.
2006
+ def with_pk!(pk)
2007
+ with_pk(pk) || raise(NoMatchingRow)
2008
+ end
1997
2009
  end
1998
2010
 
1999
2011
  extend ClassMethods
@@ -148,10 +148,10 @@ module Sequel
148
148
  end
149
149
 
150
150
  module InstanceMethods
151
- # Clear the cached compositions when refreshing.
152
- def _refresh(ds)
151
+ # Clear the cached compositions when setting values.
152
+ def set_values(hash)
153
+ @compositions.clear if @compositions
153
154
  super
154
- compositions.clear
155
155
  end
156
156
 
157
157
  # For each composition, set the columns in the model class based
@@ -115,6 +115,12 @@ module Sequel
115
115
  end
116
116
  end
117
117
 
118
+ # Reset the initial values when setting values.
119
+ def set_values(hash)
120
+ reset_initial_values
121
+ super
122
+ end
123
+
118
124
  # Manually specify that a column will change. This should only be used
119
125
  # if you plan to modify a column value in place, which is not recommended.
120
126
  #
@@ -157,12 +163,6 @@ module Sequel
157
163
  @previous_changes = column_changes
158
164
  end
159
165
 
160
- # Reset the initial values when refreshing.
161
- def _refresh(dataset)
162
- super
163
- reset_initial_values
164
- end
165
-
166
166
  # When changing the column value, save the initial column value. If the column
167
167
  # value is changed back to the initial value, update changed columns to remove
168
168
  # the column.
@@ -69,6 +69,9 @@ module Sequel
69
69
  # # move MyModel object to there
70
70
  # end
71
71
  # end
72
+ #
73
+ # Do not call this method with untrusted input, as that can result in
74
+ # arbitrary code execution.
72
75
  def add_hook_type(*hooks)
73
76
  Model::HOOKS.concat(hooks)
74
77
  hooks.each do |hook|
@@ -168,35 +168,25 @@ module Sequel
168
168
  end
169
169
 
170
170
  module InstanceMethods
171
- # Hash of deserialized values, used as a cache.
172
- attr_reader :deserialized_values
173
-
174
- # Set @deserialized_values to the empty hash
175
- def initialize_set(values)
176
- @deserialized_values = {}
177
- super
178
- end
179
-
180
171
  # Serialize deserialized values before saving
181
172
  def before_save
182
173
  serialize_deserialized_values
183
174
  super
184
175
  end
185
176
 
186
- # Initialization the deserialized values for objects retrieved from the database.
187
- def set_values(*)
177
+ # Hash of deserialized values, used as a cache.
178
+ def deserialized_values
188
179
  @deserialized_values ||= {}
189
- super
190
180
  end
191
181
 
192
- private
193
-
194
- # Empty the deserialized values when refreshing.
195
- def _refresh(*)
196
- @deserialized_values = {}
182
+ # Initialization the deserialized values for objects retrieved from the database.
183
+ def set_values(hash)
184
+ @deserialized_values.clear if @deserialized_values
197
185
  super
198
186
  end
199
187
 
188
+ private
189
+
200
190
  # Deserialize the column value. Called when the model column accessor is called to
201
191
  # return a deserialized value.
202
192
  def deserialize_value(column, v)
@@ -37,8 +37,9 @@ module Sequel
37
37
 
38
38
  # Set blob columns as skipping stripping when plugin is loaded.
39
39
  def set_dataset(*)
40
- super
40
+ res = super
41
41
  set_skipped_string_stripping_columns
42
+ res
42
43
  end
43
44
 
44
45
  # Skip stripping for the given columns.