sequel 5.58.0 → 5.78.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 (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