sequel 5.58.0 → 5.78.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (161) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +288 -0
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +24 -23
  5. data/bin/sequel +11 -3
  6. data/doc/advanced_associations.rdoc +16 -14
  7. data/doc/association_basics.rdoc +53 -17
  8. data/doc/cheat_sheet.rdoc +3 -3
  9. data/doc/mass_assignment.rdoc +1 -1
  10. data/doc/migration.rdoc +15 -0
  11. data/doc/model_hooks.rdoc +1 -1
  12. data/doc/object_model.rdoc +8 -8
  13. data/doc/opening_databases.rdoc +20 -12
  14. data/doc/postgresql.rdoc +8 -8
  15. data/doc/querying.rdoc +1 -1
  16. data/doc/release_notes/5.59.0.txt +73 -0
  17. data/doc/release_notes/5.60.0.txt +22 -0
  18. data/doc/release_notes/5.61.0.txt +43 -0
  19. data/doc/release_notes/5.62.0.txt +132 -0
  20. data/doc/release_notes/5.63.0.txt +33 -0
  21. data/doc/release_notes/5.64.0.txt +50 -0
  22. data/doc/release_notes/5.65.0.txt +21 -0
  23. data/doc/release_notes/5.66.0.txt +24 -0
  24. data/doc/release_notes/5.67.0.txt +32 -0
  25. data/doc/release_notes/5.68.0.txt +61 -0
  26. data/doc/release_notes/5.69.0.txt +26 -0
  27. data/doc/release_notes/5.70.0.txt +35 -0
  28. data/doc/release_notes/5.71.0.txt +21 -0
  29. data/doc/release_notes/5.72.0.txt +33 -0
  30. data/doc/release_notes/5.73.0.txt +66 -0
  31. data/doc/release_notes/5.74.0.txt +45 -0
  32. data/doc/release_notes/5.75.0.txt +35 -0
  33. data/doc/release_notes/5.76.0.txt +86 -0
  34. data/doc/release_notes/5.77.0.txt +63 -0
  35. data/doc/release_notes/5.78.0.txt +67 -0
  36. data/doc/schema_modification.rdoc +3 -3
  37. data/doc/security.rdoc +9 -9
  38. data/doc/sharding.rdoc +3 -1
  39. data/doc/sql.rdoc +14 -14
  40. data/doc/testing.rdoc +16 -12
  41. data/doc/transactions.rdoc +6 -6
  42. data/doc/virtual_rows.rdoc +1 -1
  43. data/lib/sequel/adapters/ibmdb.rb +1 -1
  44. data/lib/sequel/adapters/jdbc/h2.rb +3 -0
  45. data/lib/sequel/adapters/jdbc/hsqldb.rb +2 -0
  46. data/lib/sequel/adapters/jdbc/postgresql.rb +3 -0
  47. data/lib/sequel/adapters/jdbc/sqlanywhere.rb +15 -0
  48. data/lib/sequel/adapters/jdbc/sqlserver.rb +4 -0
  49. data/lib/sequel/adapters/jdbc.rb +10 -6
  50. data/lib/sequel/adapters/mysql.rb +19 -7
  51. data/lib/sequel/adapters/mysql2.rb +2 -2
  52. data/lib/sequel/adapters/odbc/mssql.rb +1 -1
  53. data/lib/sequel/adapters/oracle.rb +1 -0
  54. data/lib/sequel/adapters/postgres.rb +62 -16
  55. data/lib/sequel/adapters/shared/access.rb +9 -1
  56. data/lib/sequel/adapters/shared/db2.rb +12 -0
  57. data/lib/sequel/adapters/shared/mssql.rb +71 -9
  58. data/lib/sequel/adapters/shared/mysql.rb +80 -1
  59. data/lib/sequel/adapters/shared/oracle.rb +17 -7
  60. data/lib/sequel/adapters/shared/postgres.rb +494 -164
  61. data/lib/sequel/adapters/shared/sqlanywhere.rb +18 -5
  62. data/lib/sequel/adapters/shared/sqlite.rb +40 -4
  63. data/lib/sequel/adapters/sqlite.rb +42 -3
  64. data/lib/sequel/adapters/trilogy.rb +117 -0
  65. data/lib/sequel/connection_pool/sharded_threaded.rb +16 -11
  66. data/lib/sequel/connection_pool/sharded_timed_queue.rb +374 -0
  67. data/lib/sequel/connection_pool/threaded.rb +14 -8
  68. data/lib/sequel/connection_pool/timed_queue.rb +270 -0
  69. data/lib/sequel/connection_pool.rb +57 -31
  70. data/lib/sequel/database/connecting.rb +25 -1
  71. data/lib/sequel/database/dataset.rb +16 -6
  72. data/lib/sequel/database/misc.rb +65 -14
  73. data/lib/sequel/database/query.rb +72 -1
  74. data/lib/sequel/database/schema_generator.rb +2 -1
  75. data/lib/sequel/database/schema_methods.rb +13 -3
  76. data/lib/sequel/database/transactions.rb +6 -0
  77. data/lib/sequel/dataset/actions.rb +60 -13
  78. data/lib/sequel/dataset/deprecated_singleton_class_methods.rb +42 -0
  79. data/lib/sequel/dataset/features.rb +15 -1
  80. data/lib/sequel/dataset/misc.rb +12 -2
  81. data/lib/sequel/dataset/placeholder_literalizer.rb +20 -9
  82. data/lib/sequel/dataset/query.rb +62 -37
  83. data/lib/sequel/dataset/sql.rb +58 -36
  84. data/lib/sequel/dataset.rb +4 -0
  85. data/lib/sequel/exceptions.rb +5 -0
  86. data/lib/sequel/extensions/_model_pg_row.rb +0 -12
  87. data/lib/sequel/extensions/_pretty_table.rb +1 -1
  88. data/lib/sequel/extensions/any_not_empty.rb +2 -2
  89. data/lib/sequel/extensions/async_thread_pool.rb +21 -13
  90. data/lib/sequel/extensions/auto_cast_date_and_time.rb +94 -0
  91. data/lib/sequel/extensions/auto_literal_strings.rb +1 -1
  92. data/lib/sequel/extensions/connection_expiration.rb +15 -9
  93. data/lib/sequel/extensions/connection_validator.rb +16 -11
  94. data/lib/sequel/extensions/constraint_validations.rb +1 -1
  95. data/lib/sequel/extensions/date_arithmetic.rb +36 -8
  96. data/lib/sequel/extensions/duplicate_columns_handler.rb +10 -9
  97. data/lib/sequel/extensions/index_caching.rb +5 -1
  98. data/lib/sequel/extensions/is_distinct_from.rb +3 -1
  99. data/lib/sequel/extensions/looser_typecasting.rb +3 -0
  100. data/lib/sequel/extensions/migration.rb +65 -15
  101. data/lib/sequel/extensions/named_timezones.rb +22 -6
  102. data/lib/sequel/extensions/pg_array.rb +33 -4
  103. data/lib/sequel/extensions/pg_auto_parameterize.rb +509 -0
  104. data/lib/sequel/extensions/pg_auto_parameterize_in_array.rb +110 -0
  105. data/lib/sequel/extensions/pg_enum.rb +1 -2
  106. data/lib/sequel/extensions/pg_extended_date_support.rb +38 -27
  107. data/lib/sequel/extensions/pg_extended_integer_support.rb +116 -0
  108. data/lib/sequel/extensions/pg_hstore.rb +5 -0
  109. data/lib/sequel/extensions/pg_inet.rb +10 -11
  110. data/lib/sequel/extensions/pg_interval.rb +10 -11
  111. data/lib/sequel/extensions/pg_json.rb +10 -10
  112. data/lib/sequel/extensions/pg_json_ops.rb +52 -0
  113. data/lib/sequel/extensions/pg_multirange.rb +6 -11
  114. data/lib/sequel/extensions/pg_range.rb +9 -14
  115. data/lib/sequel/extensions/pg_row.rb +20 -19
  116. data/lib/sequel/extensions/pg_timestamptz.rb +27 -3
  117. data/lib/sequel/extensions/round_timestamps.rb +1 -1
  118. data/lib/sequel/extensions/schema_caching.rb +1 -1
  119. data/lib/sequel/extensions/schema_dumper.rb +32 -9
  120. data/lib/sequel/extensions/server_block.rb +2 -1
  121. data/lib/sequel/extensions/set_literalizer.rb +58 -0
  122. data/lib/sequel/extensions/sqlite_json_ops.rb +76 -18
  123. data/lib/sequel/extensions/symbol_aref.rb +2 -0
  124. data/lib/sequel/extensions/transaction_connection_validator.rb +78 -0
  125. data/lib/sequel/model/associations.rb +50 -11
  126. data/lib/sequel/model/base.rb +45 -21
  127. data/lib/sequel/model/dataset_module.rb +3 -0
  128. data/lib/sequel/model/exceptions.rb +15 -3
  129. data/lib/sequel/plugins/auto_validations.rb +53 -15
  130. data/lib/sequel/plugins/class_table_inheritance.rb +2 -2
  131. data/lib/sequel/plugins/column_encryption.rb +27 -6
  132. data/lib/sequel/plugins/composition.rb +2 -2
  133. data/lib/sequel/plugins/concurrent_eager_loading.rb +4 -4
  134. data/lib/sequel/plugins/constraint_validations.rb +8 -5
  135. data/lib/sequel/plugins/defaults_setter.rb +16 -0
  136. data/lib/sequel/plugins/dirty.rb +1 -1
  137. data/lib/sequel/plugins/finder.rb +4 -2
  138. data/lib/sequel/plugins/list.rb +8 -3
  139. data/lib/sequel/plugins/many_through_many.rb +1 -1
  140. data/lib/sequel/plugins/mssql_optimistic_locking.rb +8 -38
  141. data/lib/sequel/plugins/nested_attributes.rb +4 -4
  142. data/lib/sequel/plugins/optimistic_locking.rb +9 -42
  143. data/lib/sequel/plugins/optimistic_locking_base.rb +55 -0
  144. data/lib/sequel/plugins/paged_operations.rb +181 -0
  145. data/lib/sequel/plugins/pg_auto_constraint_validations.rb +9 -3
  146. data/lib/sequel/plugins/pg_xmin_optimistic_locking.rb +109 -0
  147. data/lib/sequel/plugins/prepared_statements.rb +2 -1
  148. data/lib/sequel/plugins/prepared_statements_safe.rb +2 -1
  149. data/lib/sequel/plugins/primary_key_lookup_check_values.rb +154 -0
  150. data/lib/sequel/plugins/rcte_tree.rb +7 -4
  151. data/lib/sequel/plugins/require_valid_schema.rb +67 -0
  152. data/lib/sequel/plugins/single_table_inheritance.rb +8 -0
  153. data/lib/sequel/plugins/sql_comments.rb +5 -5
  154. data/lib/sequel/plugins/static_cache.rb +38 -0
  155. data/lib/sequel/plugins/static_cache_cache.rb +5 -1
  156. data/lib/sequel/plugins/tactical_eager_loading.rb +21 -14
  157. data/lib/sequel/plugins/validate_associated.rb +22 -12
  158. data/lib/sequel/plugins/validation_helpers.rb +29 -2
  159. data/lib/sequel/plugins/validation_helpers_generic_type_messages.rb +73 -0
  160. data/lib/sequel/version.rb +1 -1
  161. metadata +76 -6
@@ -606,6 +606,7 @@ module Sequel
606
606
  @db_schema = get_db_schema
607
607
  end
608
608
 
609
+ @fast_pk_lookup_sql = @fast_instance_delete_sql = nil unless @dataset.supports_placeholder_literalizer?
609
610
  reset_instance_dataset
610
611
  self
611
612
  end
@@ -680,10 +681,11 @@ module Sequel
680
681
 
681
682
  private
682
683
 
683
- # Yield to the passed block and if do_raise is false, swallow all errors other than DatabaseConnectionErrors.
684
+ # Yield to the passed block and if do_raise is false, swallow Sequel::Errors other than DatabaseConnectionError
685
+ # and DatabaseDisconnectError.
684
686
  def check_non_connection_error(do_raise=require_valid_table)
685
687
  db.transaction(:savepoint=>:only){yield}
686
- rescue Sequel::DatabaseConnectionError
688
+ rescue Sequel::DatabaseConnectionError, Sequel::DatabaseDisconnectError
687
689
  raise
688
690
  rescue Sequel::Error
689
691
  raise if do_raise
@@ -693,9 +695,12 @@ module Sequel
693
695
  # this model's dataset.
694
696
  def convert_input_dataset(ds)
695
697
  case ds
696
- when Symbol, SQL::Identifier, SQL::QualifiedIdentifier, SQL::AliasedExpression, LiteralString
698
+ when Symbol, SQL::Identifier, SQL::QualifiedIdentifier
697
699
  self.simple_table = db.literal(ds).freeze
698
700
  ds = db.from(ds)
701
+ when SQL::AliasedExpression, LiteralString
702
+ self.simple_table = nil
703
+ ds = db.from(ds)
699
704
  when Dataset
700
705
  ds = ds.from_self(:alias=>ds.first_source) if ds.joined_dataset?
701
706
 
@@ -784,7 +789,7 @@ module Sequel
784
789
  schema_hash = {}
785
790
  ds_opts = dataset.opts
786
791
  get_columns = proc{check_non_connection_error{columns} || []}
787
- schema_array = check_non_connection_error(false){db.schema(dataset, :reload=>reload)} if db.supports_schema_parsing?
792
+ schema_array = get_db_schema_array(reload) if db.supports_schema_parsing?
788
793
  if schema_array
789
794
  schema_array.each{|k,v| schema_hash[k] = v}
790
795
 
@@ -821,6 +826,12 @@ module Sequel
821
826
  schema_hash
822
827
  end
823
828
 
829
+ # Get the array of schema information for the dataset. Returns nil if
830
+ # the schema information cannot be determined.
831
+ def get_db_schema_array(reload)
832
+ check_non_connection_error(false){db.schema(dataset, :reload=>reload)}
833
+ end
834
+
824
835
  # Uncached version of setter_methods, to be overridden by plugins
825
836
  # that want to modify the methods used.
826
837
  def get_setter_methods
@@ -1131,7 +1142,7 @@ module Sequel
1131
1142
  #
1132
1143
  # Artist[1] === Artist[1] # => true
1133
1144
  # Artist.new === Artist.new # => false
1134
- # Artist[1].set(:name=>'Bob') === Artist[1] # => true
1145
+ # Artist[1].set(name: 'Bob') === Artist[1] # => true
1135
1146
  def ===(obj)
1136
1147
  case pkv = pk
1137
1148
  when nil
@@ -1150,7 +1161,7 @@ module Sequel
1150
1161
  #
1151
1162
  # Artist[1].pk_equal?(Artist[1]) # => true
1152
1163
  # Artist.new.pk_equal?(Artist.new) # => false
1153
- # Artist[1].set(:name=>'Bob').pk_equal?(Artist[1]) # => true
1164
+ # Artist[1].set(name: 'Bob').pk_equal?(Artist[1]) # => true
1154
1165
  alias pk_equal? ===
1155
1166
 
1156
1167
  # class is defined in Object, but it is also a keyword,
@@ -1222,7 +1233,7 @@ module Sequel
1222
1233
  #
1223
1234
  # Artist[1] == Artist[1] # => true
1224
1235
  # Artist.new == Artist.new # => true
1225
- # Artist[1].set(:name=>'Bob') == Artist[1] # => false
1236
+ # Artist[1].set(name: 'Bob') == Artist[1] # => false
1226
1237
  def eql?(obj)
1227
1238
  (obj.class == model) && (obj.values == @values)
1228
1239
  end
@@ -1233,18 +1244,21 @@ module Sequel
1233
1244
  @errors ||= errors_class.new
1234
1245
  end
1235
1246
 
1247
+ EXISTS_SELECT_ = SQL::AliasedExpression.new(1, :one)
1248
+ private_constant :EXISTS_SELECT_
1249
+
1236
1250
  # Returns true when current instance exists, false otherwise.
1237
1251
  # Generally an object that isn't new will exist unless it has
1238
1252
  # been deleted. Uses a database query to check for existence,
1239
1253
  # unless the model object is new, in which case this is always
1240
1254
  # false.
1241
1255
  #
1242
- # Artist[1].exists? # SELECT 1 FROM artists WHERE (id = 1)
1256
+ # Artist[1].exists? # SELECT 1 AS one FROM artists WHERE (id = 1)
1243
1257
  # # => true
1244
1258
  # Artist.new.exists?
1245
1259
  # # => false
1246
1260
  def exists?
1247
- new? ? false : !this.get(SQL::AliasedExpression.new(1, :one)).nil?
1261
+ new? ? false : !this.get(EXISTS_SELECT_).nil?
1248
1262
  end
1249
1263
 
1250
1264
  # Ignore the model's setter method cache when this instances extends a module, as the
@@ -1324,13 +1338,13 @@ module Sequel
1324
1338
  # a = Artist[1]
1325
1339
  # Artist.db.transaction do
1326
1340
  # a.lock!
1327
- # a.update(:name=>'A')
1341
+ # a.update(name: 'A')
1328
1342
  # end
1329
1343
  #
1330
1344
  # a = Artist[2]
1331
1345
  # Artist.db.transaction do
1332
1346
  # a.lock!('FOR NO KEY UPDATE')
1333
- # a.update(:name=>'B')
1347
+ # a.update(name: 'B')
1334
1348
  # end
1335
1349
  def lock!(style=:update)
1336
1350
  _refresh(this.lock_style(style)) unless new?
@@ -1934,8 +1948,10 @@ module Sequel
1934
1948
  end
1935
1949
 
1936
1950
  # If transactions should be used, wrap the yield in a transaction block.
1937
- def checked_transaction(opts=OPTS)
1938
- use_transaction?(opts) ? db.transaction({:server=>this_server}.merge!(opts)){yield} : yield
1951
+ def checked_transaction(opts=OPTS, &block)
1952
+ h = {:server=>this_server}.merge!(opts)
1953
+ h[:skip_transaction] = true unless use_transaction?(opts)
1954
+ db.transaction(h, &block)
1939
1955
  end
1940
1956
 
1941
1957
  # Change the value of the column to given value, recording the change.
@@ -2020,19 +2036,20 @@ module Sequel
2020
2036
  meths = setter_methods(type)
2021
2037
  strict = strict_param_setting
2022
2038
  hash.each do |k,v|
2039
+ k = k.to_s
2023
2040
  m = "#{k}="
2024
2041
  if meths.include?(m)
2025
2042
  set_column_value(m, v)
2026
2043
  elsif strict
2027
2044
  # Avoid using respond_to? or creating symbols from user input
2028
2045
  if public_methods.map(&:to_s).include?(m)
2029
- if Array(model.primary_key).map(&:to_s).member?(k.to_s) && model.restrict_primary_key?
2030
- raise MassAssignmentRestriction, "#{k} is a restricted primary key"
2046
+ if Array(model.primary_key).map(&:to_s).member?(k) && model.restrict_primary_key?
2047
+ raise MassAssignmentRestriction.create("#{k} is a restricted primary key", self, k)
2031
2048
  else
2032
- raise MassAssignmentRestriction, "#{k} is a restricted column"
2049
+ raise MassAssignmentRestriction.create("#{k} is a restricted column", self, k)
2033
2050
  end
2034
2051
  else
2035
- raise MassAssignmentRestriction, "method #{m} doesn't exist"
2052
+ raise MassAssignmentRestriction.create("method #{m} doesn't exist", self, k)
2036
2053
  end
2037
2054
  end
2038
2055
  end
@@ -2136,8 +2153,9 @@ module Sequel
2136
2153
  # # DELETE FROM artists WHERE (id = 2)
2137
2154
  # # ...
2138
2155
  def destroy
2139
- pr = proc{all(&:destroy).length}
2140
- model.use_transactions ? @db.transaction(:server=>opts[:server], &pr) : pr.call
2156
+ @db.transaction(:server=>opts[:server], :skip_transaction=>model.use_transactions == false) do
2157
+ all(&:destroy).length
2158
+ end
2141
2159
  end
2142
2160
 
2143
2161
  # If there is no order already defined on this dataset, order it by
@@ -2217,11 +2235,17 @@ module Sequel
2217
2235
 
2218
2236
  private
2219
2237
 
2238
+ # Return the dataset ordered by the model's primary key. This should not
2239
+ # be used if the model does not have a primary key.
2240
+ def _force_primary_key_order
2241
+ cached_dataset(:_pk_order_ds){order(*model.primary_key)}
2242
+ end
2243
+
2220
2244
  # If the dataset is not already ordered, and the model has a primary key,
2221
2245
  # return a clone ordered by the primary key.
2222
2246
  def _primary_key_order
2223
- if @opts[:order].nil? && model && (pk = model.primary_key)
2224
- cached_dataset(:_pk_order_ds){order(*pk)}
2247
+ if @opts[:order].nil? && model && model.primary_key
2248
+ _force_primary_key_order
2225
2249
  end
2226
2250
  end
2227
2251
 
@@ -8,6 +8,9 @@ module Sequel
8
8
  # automatically creates class methods for public dataset
9
9
  # methods.
10
10
  class DatasetModule < Dataset::DatasetModule
11
+ # The model class related to this dataset module.
12
+ attr_reader :model
13
+
11
14
  # Store the model related to this dataset module.
12
15
  def initialize(model)
13
16
  @model = model
@@ -24,11 +24,23 @@ module Sequel
24
24
  UndefinedAssociation = Class.new(Error)
25
25
  ).name
26
26
 
27
- (
28
27
  # Raised when a mass assignment method is called in strict mode with either a restricted column
29
28
  # or a column without a setter method.
30
- MassAssignmentRestriction = Class.new(Error)
31
- ).name
29
+ class MassAssignmentRestriction < Error
30
+ # The Sequel::Model object related to this exception.
31
+ attr_reader :model
32
+
33
+ # The column related to this exception, as a string.
34
+ attr_reader :column
35
+
36
+ # Create an instance of this class with the model and column set.
37
+ def self.create(msg, model, column)
38
+ e = new("#{msg} for class #{model.class.inspect}")
39
+ e.instance_variable_set(:@model, model)
40
+ e.instance_variable_set(:@column, column)
41
+ e
42
+ end
43
+ end
32
44
 
33
45
  # Exception class raised when +raise_on_save_failure+ is set and validation fails
34
46
  class ValidationFailed < Error
@@ -9,14 +9,16 @@ module Sequel
9
9
  # 2. not_null validations on NOT NULL columns (optionally, presence validations)
10
10
  # 3. unique validations on columns or sets of columns with unique indexes
11
11
  # 4. max length validations on string columns
12
+ # 5. no null byte validations on string columns
13
+ # 6. minimum and maximum values on columns
12
14
  #
13
- # To determine the columns to use for the type/not_null/max_length validations,
15
+ # To determine the columns to use for the type/not_null/max_length/no_null_byte/max_value/min_value validations,
14
16
  # the plugin looks at the database schema for the model's table. To determine
15
17
  # the unique validations, Sequel looks at the indexes on the table. In order
16
18
  # for this plugin to be fully functional, the underlying database adapter needs
17
19
  # to support both schema and index parsing. Additionally, unique validations are
18
20
  # only added for models that select from a simple table, they are not added for models
19
- # that select from a subquery or joined dataset.
21
+ # that select from a subquery.
20
22
  #
21
23
  # This plugin uses the validation_helpers plugin underneath to implement the
22
24
  # validations. It does not allow for any per-column validation message
@@ -50,7 +52,7 @@ module Sequel
50
52
  #
51
53
  # Model.plugin :auto_validations, unique_opts: {only_if_modified: true}
52
54
  #
53
- # This works for unique_opts, max_length_opts, schema_types_opts,
55
+ # This works for unique_opts, max_length_opts, schema_types_opts, max_value_opts, min_value_opts, no_null_byte_opts,
54
56
  # explicit_not_null_opts, and not_null_opts.
55
57
  #
56
58
  # If you only want auto_validations to add validations to columns that do not already
@@ -72,6 +74,19 @@ module Sequel
72
74
  SCHEMA_TYPES_OPTIONS = NOT_NULL_OPTIONS
73
75
  UNIQUE_OPTIONS = NOT_NULL_OPTIONS
74
76
  NO_NULL_BYTE_OPTIONS = MAX_LENGTH_OPTIONS
77
+ MAX_VALUE_OPTIONS = {:from=>:values, :allow_nil=>true, :skip_invalid=>true}.freeze
78
+ MIN_VALUE_OPTIONS = MAX_VALUE_OPTIONS
79
+ AUTO_VALIDATE_OPTIONS = {
80
+ :no_null_byte=>NO_NULL_BYTE_OPTIONS,
81
+ :not_null=>NOT_NULL_OPTIONS,
82
+ :explicit_not_null=>EXPLICIT_NOT_NULL_OPTIONS,
83
+ :max_length=>MAX_LENGTH_OPTIONS,
84
+ :max_value=>MAX_VALUE_OPTIONS,
85
+ :min_value=>MIN_VALUE_OPTIONS,
86
+ :schema_types=>SCHEMA_TYPES_OPTIONS,
87
+ :unique=>UNIQUE_OPTIONS
88
+ }.freeze
89
+
75
90
  EMPTY_ARRAY = [].freeze
76
91
 
77
92
  def self.apply(model, opts=OPTS)
@@ -82,17 +97,11 @@ module Sequel
82
97
  @auto_validate_not_null_columns = []
83
98
  @auto_validate_explicit_not_null_columns = []
84
99
  @auto_validate_max_length_columns = []
100
+ @auto_validate_max_value_columns = []
101
+ @auto_validate_min_value_columns = []
85
102
  @auto_validate_unique_columns = []
86
103
  @auto_validate_types = true
87
-
88
- @auto_validate_options = {
89
- :no_null_byte=>NO_NULL_BYTE_OPTIONS,
90
- :not_null=>NOT_NULL_OPTIONS,
91
- :explicit_not_null=>EXPLICIT_NOT_NULL_OPTIONS,
92
- :max_length=>MAX_LENGTH_OPTIONS,
93
- :schema_types=>SCHEMA_TYPES_OPTIONS,
94
- :unique=>UNIQUE_OPTIONS
95
- }.freeze
104
+ @auto_validate_options = AUTO_VALIDATE_OPTIONS
96
105
  end
97
106
  end
98
107
 
@@ -105,7 +114,7 @@ module Sequel
105
114
  end
106
115
 
107
116
  h = @auto_validate_options.dup
108
- [:not_null, :explicit_not_null, :max_length, :no_null_byte, :schema_types, :unique].each do |type|
117
+ [:not_null, :explicit_not_null, :max_length, :max_value, :min_value, :no_null_byte, :schema_types, :unique].each do |type|
109
118
  if type_opts = opts[:"#{type}_opts"]
110
119
  h[type] = h[type].merge(type_opts).freeze
111
120
  end
@@ -135,6 +144,14 @@ module Sequel
135
144
  # pairs, with the first entry being the column name and second entry being the maximum length.
136
145
  attr_reader :auto_validate_max_length_columns
137
146
 
147
+ # The columns with automatch max value validations, as an array of
148
+ # pairs, with the first entry being the column name and second entry being the maximum value.
149
+ attr_reader :auto_validate_max_value_columns
150
+
151
+ # The columns with automatch min value validations, as an array of
152
+ # pairs, with the first entry being the column name and second entry being the minimum value.
153
+ attr_reader :auto_validate_min_value_columns
154
+
138
155
  # The columns or sets of columns with automatic unique validations
139
156
  attr_reader :auto_validate_unique_columns
140
157
 
@@ -148,6 +165,8 @@ module Sequel
148
165
  :@auto_validate_not_null_columns=>:dup,
149
166
  :@auto_validate_explicit_not_null_columns=>:dup,
150
167
  :@auto_validate_max_length_columns=>:dup,
168
+ :@auto_validate_max_value_columns=>:dup,
169
+ :@auto_validate_min_value_columns=>:dup,
151
170
  :@auto_validate_unique_columns=>:dup,
152
171
  :@auto_validate_options => :dup)
153
172
  Plugins.after_set_dataset(self, :setup_auto_validations)
@@ -168,18 +187,23 @@ module Sequel
168
187
  @auto_validate_not_null_columns.freeze
169
188
  @auto_validate_explicit_not_null_columns.freeze
170
189
  @auto_validate_max_length_columns.freeze
190
+ @auto_validate_max_value_columns.freeze
191
+ @auto_validate_min_value_columns.freeze
171
192
  @auto_validate_unique_columns.freeze
172
193
 
173
194
  super
174
195
  end
175
196
 
176
197
  # Skip automatic validations for the given validation type
177
- # (:not_null, :types, :unique, :max_length, :no_null_byte).
198
+ # (:not_null, :no_null_byte, :types, :unique, :max_length, :max_value, :min_value).
178
199
  # If :all is given as the type, skip all auto validations.
200
+ #
201
+ # Skipping types validation automatically skips max_value and min_value validations,
202
+ # since those validations require valid types.
179
203
  def skip_auto_validations(type)
180
204
  case type
181
205
  when :all
182
- [:not_null, :no_null_byte, :types, :unique, :max_length].each{|v| skip_auto_validations(v)}
206
+ [:not_null, :no_null_byte, :types, :unique, :max_length, :max_value, :min_value].each{|v| skip_auto_validations(v)}
183
207
  when :not_null
184
208
  auto_validate_not_null_columns.clear
185
209
  auto_validate_explicit_not_null_columns.clear
@@ -199,6 +223,8 @@ module Sequel
199
223
  explicit_not_null_cols += Array(primary_key)
200
224
  @auto_validate_explicit_not_null_columns = explicit_not_null_cols.uniq
201
225
  @auto_validate_max_length_columns = db_schema.select{|col, sch| sch[:type] == :string && sch[:max_length].is_a?(Integer)}.map{|col, sch| [col, sch[:max_length]]}
226
+ @auto_validate_max_value_columns = db_schema.select{|col, sch| sch[:max_value]}.map{|col, sch| [col, sch[:max_value]]}
227
+ @auto_validate_min_value_columns = db_schema.select{|col, sch| sch[:min_value]}.map{|col, sch| [col, sch[:min_value]]}
202
228
  @auto_validate_no_null_byte_columns = db_schema.select{|_, sch| sch[:type] == :string}.map{|col, _| col}
203
229
  table = dataset.first_source_table
204
230
  @auto_validate_unique_columns = if db.supports_index_parsing? && [Symbol, SQL::QualifiedIdentifier, SQL::Identifier, String].any?{|c| table.is_a?(c)}
@@ -248,6 +274,18 @@ module Sequel
248
274
 
249
275
  unless skip.include?(:types) || !model.auto_validate_types?
250
276
  validates_schema_types(keys, opts[:schema_types])
277
+
278
+ unless skip.include?(:max_value) || ((max_value_columns = model.auto_validate_max_value_columns).empty?)
279
+ max_value_columns.each do |col, max|
280
+ validates_max_value(max, col, opts[:max_value])
281
+ end
282
+ end
283
+
284
+ unless skip.include?(:min_value) || ((min_value_columns = model.auto_validate_min_value_columns).empty?)
285
+ min_value_columns.each do |col, min|
286
+ validates_min_value(min, col, opts[:min_value])
287
+ end
288
+ end
251
289
  end
252
290
 
253
291
  unless skip.include?(:unique)
@@ -104,7 +104,7 @@ module Sequel
104
104
  # should reference the subquery alias (and qualified identifiers should not be needed
105
105
  # unless joining to another table):
106
106
  #
107
- # a = Executive.where(:id=>1).first # works
107
+ # a = Executive.where(id: 1).first # works
108
108
  # a = Executive.where{{employees[:id]=>1}}.first # works
109
109
  # a = Executive.where{{executives[:id]=>1}}.first # doesn't work
110
110
  #
@@ -115,7 +115,7 @@ module Sequel
115
115
  #
116
116
  # pks = Executive.where{num_staff < 10}.select_map(:id)
117
117
  # Executive.cti_tables.reverse_each do |table|
118
- # DB.from(table).where(:id=>pks).delete
118
+ # DB.from(table).where(id: pks).delete
119
119
  # end
120
120
  #
121
121
  # = Usage
@@ -31,7 +31,6 @@ rescue RuntimeError, OpenSSL::Cipher::CipherError
31
31
  # :nocov:
32
32
  end
33
33
 
34
- require 'base64'
35
34
  require 'securerandom'
36
35
 
37
36
  module Sequel
@@ -326,7 +325,7 @@ module Sequel
326
325
  # DB.alter_table(:ce_test) do
327
326
  # c = Sequel[:encrypted_column_name]
328
327
  # add_constraint(:enc_base64) do
329
- # octet_length(decode(regexp_replace(regexp_replace(c, '_', '/', 'g'), '-', '+', 'g'), 'base64')) >= 65}
328
+ # octet_length(decode(regexp_replace(regexp_replace(c, '_', '/', 'g'), '-', '+', 'g'), 'base64')) >= 65
330
329
  # end
331
330
  # end
332
331
  #
@@ -375,7 +374,7 @@ module Sequel
375
374
  # Decrypt using any supported format and any available key.
376
375
  def decrypt(data)
377
376
  begin
378
- data = Base64.urlsafe_decode64(data)
377
+ data = urlsafe_decode64(data)
379
378
  rescue ArgumentError
380
379
  raise Error, "Unable to decode encrypted column: invalid base64"
381
380
  end
@@ -448,7 +447,7 @@ module Sequel
448
447
  # The prefix string of columns for the given search type and the first configured encryption key.
449
448
  # Used to find values that do not use this prefix in order to perform reencryption.
450
449
  def current_key_prefix(search_type)
451
- Base64.urlsafe_encode64("#{search_type.chr}\0#{@key_id.chr}")
450
+ urlsafe_encode64("#{search_type.chr}\0#{@key_id.chr}")
452
451
  end
453
452
 
454
453
  # The prefix values to search for the given data (an array of strings), assuming the column uses
@@ -472,11 +471,33 @@ module Sequel
472
471
 
473
472
  private
474
473
 
474
+ if RUBY_VERSION >= '2.4'
475
+ def decode64(str)
476
+ str.unpack1("m0")
477
+ end
478
+ # :nocov:
479
+ else
480
+ def decode64(str)
481
+ str.unpack("m0")[0]
482
+ end
483
+ # :nocov:
484
+ end
485
+
486
+ def urlsafe_encode64(bin)
487
+ str = [bin].pack("m0")
488
+ str.tr!("+/", "-_")
489
+ str
490
+ end
491
+
492
+ def urlsafe_decode64(str)
493
+ decode64(str.tr("-_", "+/"))
494
+ end
495
+
475
496
  # An array of strings, one for each configured encryption key, to find encypted values matching
476
497
  # the given data and search format.
477
498
  def _search_prefixes(data, search_type)
478
499
  @key_map.map do |key_id, (key, _)|
479
- Base64.urlsafe_encode64(_search_prefix(data, search_type, key_id, key))
500
+ urlsafe_encode64(_search_prefix(data, search_type, key_id, key))
480
501
  end
481
502
  end
482
503
 
@@ -509,7 +530,7 @@ module Sequel
509
530
  cipher_text << cipher.update(data) if data_size > 0
510
531
  cipher_text << cipher.final
511
532
 
512
- Base64.urlsafe_encode64("#{prefix}#{random_data}#{cipher_iv}#{cipher.auth_tag}#{cipher_text}")
533
+ urlsafe_encode64("#{prefix}#{random_data}#{cipher_iv}#{cipher.auth_tag}#{cipher_text}")
513
534
  end
514
535
  end
515
536
 
@@ -39,8 +39,8 @@ module Sequel
39
39
  # also be implemented as:
40
40
  #
41
41
  # Album.composition :date,
42
- # :composer=>proc{Date.new(year, month, day) if year || month || day},
43
- # :decomposer=>(proc do
42
+ # composer: proc{Date.new(year, month, day) if year || month || day},
43
+ # decomposer: (proc do
44
44
  # if d = compositions[:date]
45
45
  # self.year = d.year
46
46
  # self.month = d.month
@@ -50,10 +50,10 @@ module Sequel
50
50
  # support concurrent eager loading. Taking this example from the
51
51
  # {Advanced Associations guide}[rdoc-ref:doc/advanced_associations.rdoc]
52
52
  #
53
- # Album.many_to_one :artist, :eager_loader=>(proc do |eo_opts|
53
+ # Album.many_to_one :artist, eager_loader: (proc do |eo_opts|
54
54
  # eo_opts[:rows].each{|album| album.associations[:artist] = nil}
55
55
  # id_map = eo_opts[:id_map]
56
- # Artist.where(:id=>id_map.keys).all do |artist|
56
+ # Artist.where(id: id_map.keys).all do |artist|
57
57
  # if albums = id_map[artist.id]
58
58
  # albums.each do |album|
59
59
  # album.associations[:artist] = artist
@@ -74,12 +74,12 @@ module Sequel
74
74
  # the code that loads the objects, since that will prevent concurrent loading.
75
75
  # So after the changes, the custom eager loader would look like this:
76
76
  #
77
- # Album.many_to_one :artist, :eager_loader=>(proc do |eo_opts|
77
+ # Album.many_to_one :artist, eager_loader: (proc do |eo_opts|
78
78
  # Sequel.synchronize_with(eo[:mutex]) do
79
79
  # eo_opts[:rows].each{|album| album.associations[:artist] = nil}
80
80
  # end
81
81
  # id_map = eo_opts[:id_map]
82
- # rows = Artist.where(:id=>id_map.keys).all
82
+ # rows = Artist.where(id: id_map.keys).all
83
83
  # Sequel.synchronize_with(eo[:mutex]) do
84
84
  # rows.each do |artist|
85
85
  # if albums = id_map[artist.id]
@@ -125,14 +125,15 @@ module Sequel
125
125
  ds = @dataset.with_quote_identifiers(false)
126
126
  table_name = ds.literal(ds.first_source_table)
127
127
  reflections = {}
128
- @constraint_validations = (Sequel.synchronize{hash[table_name]} || []).map{|r| constraint_validation_array(r, reflections)}
128
+ allow_missing_columns = db_schema.select{|col, sch| sch[:allow_null] == false && nil != sch[:default]}.map(&:first)
129
+ @constraint_validations = (Sequel.synchronize{hash[table_name]} || []).map{|r| constraint_validation_array(r, reflections, allow_missing_columns)}
129
130
  @constraint_validation_reflections = reflections
130
131
  end
131
132
  end
132
133
 
133
134
  # Given a specific database constraint validation metadata row hash, transform
134
135
  # it in an validation method call array suitable for splatting to send.
135
- def constraint_validation_array(r, reflections)
136
+ def constraint_validation_array(r, reflections, allow_missing_columns=EMPTY_ARRAY)
136
137
  opts = {}
137
138
  opts[:message] = r[:message] if r[:message]
138
139
  opts[:allow_nil] = true if db.typecast_value(:boolean, r[:allow_nil])
@@ -191,11 +192,13 @@ module Sequel
191
192
  reflection_opts[:argument] = arg
192
193
  end
193
194
 
194
- a << column
195
- unless opts.empty?
196
- a << opts
195
+ opts[:from] = :values
196
+ if column.is_a?(Symbol) && allow_missing_columns.include?(column)
197
+ opts[:allow_missing] = true
197
198
  end
198
199
 
200
+ a << column << opts
201
+
199
202
  if column.is_a?(Array) && column.length == 1
200
203
  column = column.first
201
204
  end
@@ -1,5 +1,7 @@
1
1
  # frozen-string-literal: true
2
2
 
3
+ require 'delegate'
4
+
3
5
  module Sequel
4
6
  module Plugins
5
7
  # The defaults_setter plugin makes the column getter methods return the default
@@ -106,6 +108,20 @@ module Sequel
106
108
  lambda{Date.today}
107
109
  when Sequel::CURRENT_TIMESTAMP
108
110
  lambda{dataset.current_datetime}
111
+ when Hash, Array
112
+ v = Marshal.dump(v).freeze
113
+ lambda{Marshal.load(v)}
114
+ when Delegator
115
+ # DelegateClass returns an anonymous case, which cannot be marshalled, so marshal the
116
+ # underlying object and create a new instance of the class with the unmarshalled object.
117
+ klass = v.class
118
+ case o = v.__getobj__
119
+ when Hash, Array
120
+ v = Marshal.dump(o).freeze
121
+ lambda{klass.new(Marshal.load(v))}
122
+ else
123
+ v
124
+ end
109
125
  else
110
126
  v
111
127
  end
@@ -37,7 +37,7 @@ module Sequel
37
37
  #
38
38
  # It also saves the previously changed values after an update:
39
39
  #
40
- # artist.update(:name=>'Bar')
40
+ # artist.update(name: 'Bar')
41
41
  # artist.column_changes # => {}
42
42
  # artist.previous_changes # => {:name=>['Foo', 'Bar']}
43
43
  #
@@ -97,7 +97,9 @@ module Sequel
97
97
  # Artist.first_by_name(nil)
98
98
  # # WHERE (name IS NULL)
99
99
  #
100
- # See Dataset::PlaceholderLiteralizer for additional caveats.
100
+ # See Dataset::PlaceholderLiteralizer for additional caveats. Note that if the model's
101
+ # dataset does not support placeholder literalizers, you will not be able to use this
102
+ # method.
101
103
  def finder(meth=OPTS, opts=OPTS, &block)
102
104
  if block
103
105
  raise Error, "cannot pass both a method name argument and a block of Model.finder" unless meth.is_a?(Hash)
@@ -149,7 +151,7 @@ module Sequel
149
151
  ds
150
152
  end
151
153
 
152
- Sequel::Dataset::PlaceholderLiteralizer.loader(model, &block)
154
+ model.dataset.placeholder_literalizer_class.loader(model, &block)
153
155
  end
154
156
  end
155
157
 
@@ -33,7 +33,9 @@ module Sequel
33
33
  # You can provide a <tt>:scope</tt> option to scope the list. This option
34
34
  # can be a symbol or array of symbols specifying column name(s), or a proc
35
35
  # that accepts a model instance and returns a dataset representing the list
36
- # the object is in.
36
+ # the object is in. You will need to provide a <tt>:scope</tt> option if
37
+ # the model's dataset uses a subquery (such as when using the class_table_inheritance
38
+ # plugin).
37
39
  #
38
40
  # For example, if each item has a +user_id+ field, and you want every user
39
41
  # to have their own list:
@@ -183,10 +185,13 @@ module Sequel
183
185
  end
184
186
 
185
187
  # Set the value of the position_field to the maximum value plus 1 unless the
186
- # position field already has a value.
188
+ # position field already has a value. If the list is empty, the position will
189
+ # be set to the model's +top_of_list+ value.
187
190
  def before_validation
188
191
  unless get_column_value(position_field)
189
- set_column_value("#{position_field}=", list_dataset.max(position_field).to_i+1)
192
+ current_max = list_dataset.max(position_field)
193
+ value = current_max.nil? ? model.top_of_list : current_max.to_i + 1
194
+ set_column_value("#{position_field}=", value)
190
195
  end
191
196
  super
192
197
  end
@@ -385,7 +385,7 @@ module Sequel
385
385
  iq = nil
386
386
  end
387
387
  fe = opts.final_edge
388
- ds.graph(opts.associated_class.dataset, use_only_conditions ? only_conditions : (Array(opts.right_primary_key).zip(Array(fe[:left])) + conditions), :select=>select, :table_alias=>eo[:table_alias], :qualify=>:deep, :join_type=>eo[:join_type]||join_type, :join_only=>eo[:join_only], &graph_block)
388
+ ds.graph(eager_graph_dataset(opts, eo), use_only_conditions ? only_conditions : (Array(opts.right_primary_key).zip(Array(fe[:left])) + conditions), :select=>select, :table_alias=>eo[:table_alias], :qualify=>:deep, :join_type=>eo[:join_type]||join_type, :join_only=>eo[:join_only], &graph_block)
389
389
  end
390
390
  end
391
391
  end