sequel 3.45.0 → 3.46.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 (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.