sequel 5.45.0 → 5.77.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 (218) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +434 -0
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +59 -27
  5. data/bin/sequel +11 -3
  6. data/doc/advanced_associations.rdoc +16 -14
  7. data/doc/association_basics.rdoc +119 -24
  8. data/doc/cheat_sheet.rdoc +11 -3
  9. data/doc/mass_assignment.rdoc +1 -1
  10. data/doc/migration.rdoc +27 -6
  11. data/doc/model_hooks.rdoc +1 -1
  12. data/doc/object_model.rdoc +8 -8
  13. data/doc/opening_databases.rdoc +28 -12
  14. data/doc/postgresql.rdoc +16 -8
  15. data/doc/querying.rdoc +5 -3
  16. data/doc/release_notes/5.46.0.txt +87 -0
  17. data/doc/release_notes/5.47.0.txt +59 -0
  18. data/doc/release_notes/5.48.0.txt +14 -0
  19. data/doc/release_notes/5.49.0.txt +59 -0
  20. data/doc/release_notes/5.50.0.txt +78 -0
  21. data/doc/release_notes/5.51.0.txt +47 -0
  22. data/doc/release_notes/5.52.0.txt +87 -0
  23. data/doc/release_notes/5.53.0.txt +23 -0
  24. data/doc/release_notes/5.54.0.txt +27 -0
  25. data/doc/release_notes/5.55.0.txt +21 -0
  26. data/doc/release_notes/5.56.0.txt +51 -0
  27. data/doc/release_notes/5.57.0.txt +23 -0
  28. data/doc/release_notes/5.58.0.txt +31 -0
  29. data/doc/release_notes/5.59.0.txt +73 -0
  30. data/doc/release_notes/5.60.0.txt +22 -0
  31. data/doc/release_notes/5.61.0.txt +43 -0
  32. data/doc/release_notes/5.62.0.txt +132 -0
  33. data/doc/release_notes/5.63.0.txt +33 -0
  34. data/doc/release_notes/5.64.0.txt +50 -0
  35. data/doc/release_notes/5.65.0.txt +21 -0
  36. data/doc/release_notes/5.66.0.txt +24 -0
  37. data/doc/release_notes/5.67.0.txt +32 -0
  38. data/doc/release_notes/5.68.0.txt +61 -0
  39. data/doc/release_notes/5.69.0.txt +26 -0
  40. data/doc/release_notes/5.70.0.txt +35 -0
  41. data/doc/release_notes/5.71.0.txt +21 -0
  42. data/doc/release_notes/5.72.0.txt +33 -0
  43. data/doc/release_notes/5.73.0.txt +66 -0
  44. data/doc/release_notes/5.74.0.txt +45 -0
  45. data/doc/release_notes/5.75.0.txt +35 -0
  46. data/doc/release_notes/5.76.0.txt +86 -0
  47. data/doc/release_notes/5.77.0.txt +63 -0
  48. data/doc/schema_modification.rdoc +1 -1
  49. data/doc/security.rdoc +9 -9
  50. data/doc/sharding.rdoc +3 -1
  51. data/doc/sql.rdoc +27 -15
  52. data/doc/testing.rdoc +23 -13
  53. data/doc/transactions.rdoc +6 -6
  54. data/doc/virtual_rows.rdoc +1 -1
  55. data/lib/sequel/adapters/ado/access.rb +1 -1
  56. data/lib/sequel/adapters/ado.rb +1 -1
  57. data/lib/sequel/adapters/amalgalite.rb +3 -5
  58. data/lib/sequel/adapters/ibmdb.rb +3 -3
  59. data/lib/sequel/adapters/jdbc/derby.rb +8 -0
  60. data/lib/sequel/adapters/jdbc/h2.rb +63 -10
  61. data/lib/sequel/adapters/jdbc/hsqldb.rb +8 -0
  62. data/lib/sequel/adapters/jdbc/postgresql.rb +7 -4
  63. data/lib/sequel/adapters/jdbc/sqlanywhere.rb +15 -0
  64. data/lib/sequel/adapters/jdbc/sqlserver.rb +4 -0
  65. data/lib/sequel/adapters/jdbc.rb +24 -22
  66. data/lib/sequel/adapters/mysql.rb +92 -67
  67. data/lib/sequel/adapters/mysql2.rb +56 -51
  68. data/lib/sequel/adapters/odbc/mssql.rb +1 -1
  69. data/lib/sequel/adapters/odbc.rb +1 -1
  70. data/lib/sequel/adapters/oracle.rb +4 -3
  71. data/lib/sequel/adapters/postgres.rb +89 -45
  72. data/lib/sequel/adapters/shared/access.rb +11 -1
  73. data/lib/sequel/adapters/shared/db2.rb +42 -0
  74. data/lib/sequel/adapters/shared/mssql.rb +91 -10
  75. data/lib/sequel/adapters/shared/mysql.rb +78 -3
  76. data/lib/sequel/adapters/shared/oracle.rb +86 -7
  77. data/lib/sequel/adapters/shared/postgres.rb +576 -171
  78. data/lib/sequel/adapters/shared/sqlanywhere.rb +21 -5
  79. data/lib/sequel/adapters/shared/sqlite.rb +92 -8
  80. data/lib/sequel/adapters/sqlanywhere.rb +1 -1
  81. data/lib/sequel/adapters/sqlite.rb +99 -18
  82. data/lib/sequel/adapters/tinytds.rb +1 -1
  83. data/lib/sequel/adapters/trilogy.rb +117 -0
  84. data/lib/sequel/adapters/utils/columns_limit_1.rb +22 -0
  85. data/lib/sequel/adapters/utils/mysql_mysql2.rb +1 -1
  86. data/lib/sequel/ast_transformer.rb +6 -0
  87. data/lib/sequel/connection_pool/sharded_single.rb +5 -7
  88. data/lib/sequel/connection_pool/sharded_threaded.rb +16 -11
  89. data/lib/sequel/connection_pool/sharded_timed_queue.rb +374 -0
  90. data/lib/sequel/connection_pool/single.rb +6 -8
  91. data/lib/sequel/connection_pool/threaded.rb +14 -8
  92. data/lib/sequel/connection_pool/timed_queue.rb +270 -0
  93. data/lib/sequel/connection_pool.rb +57 -31
  94. data/lib/sequel/core.rb +17 -18
  95. data/lib/sequel/database/connecting.rb +27 -3
  96. data/lib/sequel/database/dataset.rb +16 -6
  97. data/lib/sequel/database/misc.rb +70 -14
  98. data/lib/sequel/database/query.rb +73 -2
  99. data/lib/sequel/database/schema_generator.rb +11 -6
  100. data/lib/sequel/database/schema_methods.rb +23 -4
  101. data/lib/sequel/database/transactions.rb +6 -0
  102. data/lib/sequel/dataset/actions.rb +111 -15
  103. data/lib/sequel/dataset/deprecated_singleton_class_methods.rb +42 -0
  104. data/lib/sequel/dataset/features.rb +20 -1
  105. data/lib/sequel/dataset/misc.rb +12 -2
  106. data/lib/sequel/dataset/placeholder_literalizer.rb +20 -9
  107. data/lib/sequel/dataset/query.rb +170 -41
  108. data/lib/sequel/dataset/sql.rb +190 -71
  109. data/lib/sequel/dataset.rb +4 -0
  110. data/lib/sequel/extensions/_model_pg_row.rb +0 -12
  111. data/lib/sequel/extensions/_pretty_table.rb +1 -1
  112. data/lib/sequel/extensions/any_not_empty.rb +2 -2
  113. data/lib/sequel/extensions/async_thread_pool.rb +14 -13
  114. data/lib/sequel/extensions/auto_cast_date_and_time.rb +94 -0
  115. data/lib/sequel/extensions/auto_literal_strings.rb +1 -1
  116. data/lib/sequel/extensions/connection_expiration.rb +15 -9
  117. data/lib/sequel/extensions/connection_validator.rb +16 -11
  118. data/lib/sequel/extensions/constraint_validations.rb +1 -1
  119. data/lib/sequel/extensions/core_refinements.rb +36 -11
  120. data/lib/sequel/extensions/date_arithmetic.rb +36 -8
  121. data/lib/sequel/extensions/date_parse_input_handler.rb +67 -0
  122. data/lib/sequel/extensions/datetime_parse_to_time.rb +5 -1
  123. data/lib/sequel/extensions/duplicate_columns_handler.rb +11 -10
  124. data/lib/sequel/extensions/index_caching.rb +5 -1
  125. data/lib/sequel/extensions/inflector.rb +1 -1
  126. data/lib/sequel/extensions/is_distinct_from.rb +141 -0
  127. data/lib/sequel/extensions/looser_typecasting.rb +3 -0
  128. data/lib/sequel/extensions/migration.rb +57 -15
  129. data/lib/sequel/extensions/named_timezones.rb +22 -6
  130. data/lib/sequel/extensions/pagination.rb +1 -1
  131. data/lib/sequel/extensions/pg_array.rb +33 -4
  132. data/lib/sequel/extensions/pg_array_ops.rb +2 -2
  133. data/lib/sequel/extensions/pg_auto_parameterize.rb +509 -0
  134. data/lib/sequel/extensions/pg_auto_parameterize_in_array.rb +110 -0
  135. data/lib/sequel/extensions/pg_enum.rb +1 -2
  136. data/lib/sequel/extensions/pg_extended_date_support.rb +39 -28
  137. data/lib/sequel/extensions/pg_extended_integer_support.rb +116 -0
  138. data/lib/sequel/extensions/pg_hstore.rb +6 -1
  139. data/lib/sequel/extensions/pg_hstore_ops.rb +53 -3
  140. data/lib/sequel/extensions/pg_inet.rb +10 -11
  141. data/lib/sequel/extensions/pg_inet_ops.rb +1 -1
  142. data/lib/sequel/extensions/pg_interval.rb +11 -11
  143. data/lib/sequel/extensions/pg_json.rb +13 -15
  144. data/lib/sequel/extensions/pg_json_ops.rb +125 -2
  145. data/lib/sequel/extensions/pg_multirange.rb +367 -0
  146. data/lib/sequel/extensions/pg_range.rb +13 -26
  147. data/lib/sequel/extensions/pg_range_ops.rb +37 -9
  148. data/lib/sequel/extensions/pg_row.rb +20 -19
  149. data/lib/sequel/extensions/pg_row_ops.rb +1 -1
  150. data/lib/sequel/extensions/pg_timestamptz.rb +27 -3
  151. data/lib/sequel/extensions/round_timestamps.rb +1 -1
  152. data/lib/sequel/extensions/s.rb +2 -1
  153. data/lib/sequel/extensions/schema_caching.rb +1 -1
  154. data/lib/sequel/extensions/schema_dumper.rb +45 -11
  155. data/lib/sequel/extensions/server_block.rb +10 -13
  156. data/lib/sequel/extensions/set_literalizer.rb +58 -0
  157. data/lib/sequel/extensions/sql_comments.rb +110 -3
  158. data/lib/sequel/extensions/sql_log_normalizer.rb +108 -0
  159. data/lib/sequel/extensions/sqlite_json_ops.rb +255 -0
  160. data/lib/sequel/extensions/string_agg.rb +1 -1
  161. data/lib/sequel/extensions/string_date_time.rb +19 -23
  162. data/lib/sequel/extensions/symbol_aref.rb +2 -0
  163. data/lib/sequel/extensions/transaction_connection_validator.rb +78 -0
  164. data/lib/sequel/model/associations.rb +286 -92
  165. data/lib/sequel/model/base.rb +53 -33
  166. data/lib/sequel/model/dataset_module.rb +3 -0
  167. data/lib/sequel/model/errors.rb +10 -1
  168. data/lib/sequel/model/exceptions.rb +15 -3
  169. data/lib/sequel/model/inflections.rb +1 -1
  170. data/lib/sequel/plugins/auto_restrict_eager_graph.rb +62 -0
  171. data/lib/sequel/plugins/auto_validations.rb +74 -16
  172. data/lib/sequel/plugins/class_table_inheritance.rb +2 -2
  173. data/lib/sequel/plugins/column_encryption.rb +29 -8
  174. data/lib/sequel/plugins/composition.rb +3 -2
  175. data/lib/sequel/plugins/concurrent_eager_loading.rb +4 -4
  176. data/lib/sequel/plugins/constraint_validations.rb +8 -5
  177. data/lib/sequel/plugins/defaults_setter.rb +16 -0
  178. data/lib/sequel/plugins/dirty.rb +1 -1
  179. data/lib/sequel/plugins/enum.rb +124 -0
  180. data/lib/sequel/plugins/finder.rb +4 -2
  181. data/lib/sequel/plugins/insert_conflict.rb +4 -0
  182. data/lib/sequel/plugins/instance_specific_default.rb +1 -1
  183. data/lib/sequel/plugins/json_serializer.rb +2 -2
  184. data/lib/sequel/plugins/lazy_attributes.rb +3 -0
  185. data/lib/sequel/plugins/list.rb +8 -3
  186. data/lib/sequel/plugins/many_through_many.rb +109 -10
  187. data/lib/sequel/plugins/mssql_optimistic_locking.rb +8 -38
  188. data/lib/sequel/plugins/nested_attributes.rb +4 -4
  189. data/lib/sequel/plugins/optimistic_locking.rb +9 -42
  190. data/lib/sequel/plugins/optimistic_locking_base.rb +55 -0
  191. data/lib/sequel/plugins/paged_operations.rb +181 -0
  192. data/lib/sequel/plugins/pg_array_associations.rb +46 -34
  193. data/lib/sequel/plugins/pg_auto_constraint_validations.rb +9 -3
  194. data/lib/sequel/plugins/pg_xmin_optimistic_locking.rb +109 -0
  195. data/lib/sequel/plugins/prepared_statements.rb +12 -2
  196. data/lib/sequel/plugins/prepared_statements_safe.rb +2 -1
  197. data/lib/sequel/plugins/primary_key_lookup_check_values.rb +154 -0
  198. data/lib/sequel/plugins/rcte_tree.rb +7 -4
  199. data/lib/sequel/plugins/require_valid_schema.rb +67 -0
  200. data/lib/sequel/plugins/serialization.rb +1 -0
  201. data/lib/sequel/plugins/serialization_modification_detection.rb +1 -0
  202. data/lib/sequel/plugins/single_table_inheritance.rb +8 -0
  203. data/lib/sequel/plugins/sql_comments.rb +189 -0
  204. data/lib/sequel/plugins/static_cache.rb +39 -1
  205. data/lib/sequel/plugins/static_cache_cache.rb +5 -1
  206. data/lib/sequel/plugins/subclasses.rb +28 -11
  207. data/lib/sequel/plugins/tactical_eager_loading.rb +23 -10
  208. data/lib/sequel/plugins/timestamps.rb +1 -1
  209. data/lib/sequel/plugins/unused_associations.rb +521 -0
  210. data/lib/sequel/plugins/update_or_create.rb +1 -1
  211. data/lib/sequel/plugins/validate_associated.rb +22 -12
  212. data/lib/sequel/plugins/validation_helpers.rb +41 -11
  213. data/lib/sequel/plugins/validation_helpers_generic_type_messages.rb +73 -0
  214. data/lib/sequel/plugins/xml_serializer.rb +1 -1
  215. data/lib/sequel/sql.rb +1 -1
  216. data/lib/sequel/timezones.rb +12 -14
  217. data/lib/sequel/version.rb +1 -1
  218. metadata +109 -19
@@ -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,24 +681,26 @@ 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
- begin
686
- db.transaction(:savepoint=>:only){yield}
687
- rescue Sequel::DatabaseConnectionError
688
- raise
689
- rescue Sequel::Error
690
- raise if do_raise
691
- end
687
+ db.transaction(:savepoint=>:only){yield}
688
+ rescue Sequel::DatabaseConnectionError, Sequel::DatabaseDisconnectError
689
+ raise
690
+ rescue Sequel::Error
691
+ raise if do_raise
692
692
  end
693
693
 
694
694
  # Convert the given object to a Dataset that should be used as
695
695
  # this model's dataset.
696
696
  def convert_input_dataset(ds)
697
697
  case ds
698
- when Symbol, SQL::Identifier, SQL::QualifiedIdentifier, SQL::AliasedExpression, LiteralString
698
+ when Symbol, SQL::Identifier, SQL::QualifiedIdentifier
699
699
  self.simple_table = db.literal(ds).freeze
700
700
  ds = db.from(ds)
701
+ when SQL::AliasedExpression, LiteralString
702
+ self.simple_table = nil
703
+ ds = db.from(ds)
701
704
  when Dataset
702
705
  ds = ds.from_self(:alias=>ds.first_source) if ds.joined_dataset?
703
706
 
@@ -786,7 +789,7 @@ module Sequel
786
789
  schema_hash = {}
787
790
  ds_opts = dataset.opts
788
791
  get_columns = proc{check_non_connection_error{columns} || []}
789
- 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?
790
793
  if schema_array
791
794
  schema_array.each{|k,v| schema_hash[k] = v}
792
795
 
@@ -823,6 +826,12 @@ module Sequel
823
826
  schema_hash
824
827
  end
825
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
+
826
835
  # Uncached version of setter_methods, to be overridden by plugins
827
836
  # that want to modify the methods used.
828
837
  def get_setter_methods
@@ -1093,7 +1102,7 @@ module Sequel
1093
1102
  @modified = true
1094
1103
  initialize_set(values)
1095
1104
  _clear_changed_columns(:initialize)
1096
- yield self if block_given?
1105
+ yield self if defined?(yield)
1097
1106
  end
1098
1107
 
1099
1108
  # Returns value of the column's attribute.
@@ -1133,7 +1142,7 @@ module Sequel
1133
1142
  #
1134
1143
  # Artist[1] === Artist[1] # => true
1135
1144
  # Artist.new === Artist.new # => false
1136
- # Artist[1].set(:name=>'Bob') === Artist[1] # => true
1145
+ # Artist[1].set(name: 'Bob') === Artist[1] # => true
1137
1146
  def ===(obj)
1138
1147
  case pkv = pk
1139
1148
  when nil
@@ -1152,7 +1161,7 @@ module Sequel
1152
1161
  #
1153
1162
  # Artist[1].pk_equal?(Artist[1]) # => true
1154
1163
  # Artist.new.pk_equal?(Artist.new) # => false
1155
- # Artist[1].set(:name=>'Bob').pk_equal?(Artist[1]) # => true
1164
+ # Artist[1].set(name: 'Bob').pk_equal?(Artist[1]) # => true
1156
1165
  alias pk_equal? ===
1157
1166
 
1158
1167
  # class is defined in Object, but it is also a keyword,
@@ -1224,7 +1233,7 @@ module Sequel
1224
1233
  #
1225
1234
  # Artist[1] == Artist[1] # => true
1226
1235
  # Artist.new == Artist.new # => true
1227
- # Artist[1].set(:name=>'Bob') == Artist[1] # => false
1236
+ # Artist[1].set(name: 'Bob') == Artist[1] # => false
1228
1237
  def eql?(obj)
1229
1238
  (obj.class == model) && (obj.values == @values)
1230
1239
  end
@@ -1235,18 +1244,21 @@ module Sequel
1235
1244
  @errors ||= errors_class.new
1236
1245
  end
1237
1246
 
1247
+ EXISTS_SELECT_ = SQL::AliasedExpression.new(1, :one)
1248
+ private_constant :EXISTS_SELECT_
1249
+
1238
1250
  # Returns true when current instance exists, false otherwise.
1239
1251
  # Generally an object that isn't new will exist unless it has
1240
1252
  # been deleted. Uses a database query to check for existence,
1241
1253
  # unless the model object is new, in which case this is always
1242
1254
  # false.
1243
1255
  #
1244
- # Artist[1].exists? # SELECT 1 FROM artists WHERE (id = 1)
1256
+ # Artist[1].exists? # SELECT 1 AS one FROM artists WHERE (id = 1)
1245
1257
  # # => true
1246
1258
  # Artist.new.exists?
1247
1259
  # # => false
1248
1260
  def exists?
1249
- new? ? false : !this.get(SQL::AliasedExpression.new(1, :one)).nil?
1261
+ new? ? false : !this.get(EXISTS_SELECT_).nil?
1250
1262
  end
1251
1263
 
1252
1264
  # Ignore the model's setter method cache when this instances extends a module, as the
@@ -1326,13 +1338,13 @@ module Sequel
1326
1338
  # a = Artist[1]
1327
1339
  # Artist.db.transaction do
1328
1340
  # a.lock!
1329
- # a.update(:name=>'A')
1341
+ # a.update(name: 'A')
1330
1342
  # end
1331
1343
  #
1332
1344
  # a = Artist[2]
1333
1345
  # Artist.db.transaction do
1334
1346
  # a.lock!('FOR NO KEY UPDATE')
1335
- # a.update(:name=>'B')
1347
+ # a.update(name: 'B')
1336
1348
  # end
1337
1349
  def lock!(style=:update)
1338
1350
  _refresh(this.lock_style(style)) unless new?
@@ -1630,11 +1642,9 @@ module Sequel
1630
1642
  # artist.set(name: 'Invalid').valid? # => false
1631
1643
  # artist.errors.full_messages # => ['name cannot be Invalid']
1632
1644
  def valid?(opts = OPTS)
1633
- begin
1634
- _valid?(opts)
1635
- rescue HookFailed
1636
- false
1637
- end
1645
+ _valid?(opts)
1646
+ rescue HookFailed
1647
+ false
1638
1648
  end
1639
1649
 
1640
1650
  private
@@ -1938,8 +1948,10 @@ module Sequel
1938
1948
  end
1939
1949
 
1940
1950
  # If transactions should be used, wrap the yield in a transaction block.
1941
- def checked_transaction(opts=OPTS)
1942
- 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)
1943
1955
  end
1944
1956
 
1945
1957
  # Change the value of the column to given value, recording the change.
@@ -2024,19 +2036,20 @@ module Sequel
2024
2036
  meths = setter_methods(type)
2025
2037
  strict = strict_param_setting
2026
2038
  hash.each do |k,v|
2039
+ k = k.to_s
2027
2040
  m = "#{k}="
2028
2041
  if meths.include?(m)
2029
2042
  set_column_value(m, v)
2030
2043
  elsif strict
2031
2044
  # Avoid using respond_to? or creating symbols from user input
2032
2045
  if public_methods.map(&:to_s).include?(m)
2033
- if Array(model.primary_key).map(&:to_s).member?(k.to_s) && model.restrict_primary_key?
2034
- 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)
2035
2048
  else
2036
- raise MassAssignmentRestriction, "#{k} is a restricted column"
2049
+ raise MassAssignmentRestriction.create("#{k} is a restricted column", self, k)
2037
2050
  end
2038
2051
  else
2039
- raise MassAssignmentRestriction, "method #{m} doesn't exist"
2052
+ raise MassAssignmentRestriction.create("method #{m} doesn't exist", self, k)
2040
2053
  end
2041
2054
  end
2042
2055
  end
@@ -2140,8 +2153,9 @@ module Sequel
2140
2153
  # # DELETE FROM artists WHERE (id = 2)
2141
2154
  # # ...
2142
2155
  def destroy
2143
- pr = proc{all(&:destroy).length}
2144
- 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
2145
2159
  end
2146
2160
 
2147
2161
  # If there is no order already defined on this dataset, order it by
@@ -2221,11 +2235,17 @@ module Sequel
2221
2235
 
2222
2236
  private
2223
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
+
2224
2244
  # If the dataset is not already ordered, and the model has a primary key,
2225
2245
  # return a clone ordered by the primary key.
2226
2246
  def _primary_key_order
2227
- if @opts[:order].nil? && model && (pk = model.primary_key)
2228
- cached_dataset(:_pk_order_ds){order(*pk)}
2247
+ if @opts[:order].nil? && model && model.primary_key
2248
+ _force_primary_key_order
2229
2249
  end
2230
2250
  end
2231
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
@@ -38,7 +38,7 @@ module Sequel
38
38
  def full_messages
39
39
  inject([]) do |m, kv|
40
40
  att, errors = *kv
41
- errors.each {|e| m << (e.is_a?(LiteralString) ? e : "#{Array(att).join(' and ')} #{e}")}
41
+ errors.each {|e| m << (e.is_a?(LiteralString) ? e : full_message(att, e))}
42
42
  m
43
43
  end
44
44
  end
@@ -53,6 +53,15 @@ module Sequel
53
53
  v
54
54
  end
55
55
  end
56
+
57
+ private
58
+
59
+ # Create full error message to use for the given attribute (or array of attributes)
60
+ # and error message. This can be overridden for easier internalization.
61
+ def full_message(att, error_msg)
62
+ att = att.join(' and ') if att.is_a?(Array)
63
+ "#{att} #{error_msg}"
64
+ end
56
65
  end
57
66
  end
58
67
  end
@@ -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
@@ -4,7 +4,7 @@ module Sequel
4
4
  # Yield the Inflections module if a block is given, and return
5
5
  # the Inflections module.
6
6
  def self.inflections
7
- yield Inflections if block_given?
7
+ yield Inflections if defined?(yield)
8
8
  Inflections
9
9
  end
10
10
 
@@ -0,0 +1,62 @@
1
+ # frozen-string-literal: true
2
+
3
+ module Sequel
4
+ module Plugins
5
+ # The auto_restrict_eager_graph plugin will automatically disallow the use
6
+ # of eager_graph for associations that have associated blocks but no :graph_*
7
+ # association options. The reason for this is the block will have an effect
8
+ # during regular and eager loading, but not loading via eager_graph, and it
9
+ # is likely that whatever the block is doing should have an equivalent done
10
+ # when eager_graphing. Most likely, not including a :graph_* option was either
11
+ # an oversight (and one should be added), or use with eager_graph was never
12
+ # intended (and usage should be forbidden). Disallowing eager_graph in this
13
+ # case prevents likely unexpected behavior during eager_graph.
14
+ #
15
+ # As an example of this, consider the following code:
16
+ #
17
+ # Album.one_to_many :popular_tracks, class: :Track do |ds|
18
+ # ds = ds.where(popular: true)
19
+ # end
20
+ #
21
+ # Album.eager(:popular_tracks).all
22
+ # # SELECT * FROM albums
23
+ # # SELECT * FROM tracks WHERE ((popular IS TRUE) AND (album_id IN (...)))
24
+ #
25
+ # # Notice that no condition for tracks.popular is added.
26
+ # Album.eager_graph(:popular_tracks).all
27
+ # # SELECT ... FROM albums LEFT JOIN tracks ON (tracks.album_id = albums.id)
28
+ #
29
+ # With the auto_restrict_eager_graph plugin, the eager_graph call above will
30
+ # raise an error, alerting you to the fact that you either should not be
31
+ # using eager_graph with the association, or that you should be adding an
32
+ # appropriate :graph_* option, such as:
33
+ #
34
+ # Album.one_to_many :popular_tracks, class: :Track, graph_conditions: {popular: true} do |ds|
35
+ # ds = ds.where(popular: true)
36
+ # end
37
+ #
38
+ # Usage:
39
+ #
40
+ # # Automatically restrict eager_graph for associations if appropriate for all
41
+ # # model subclasses (called before loading subclasses)
42
+ # Sequel::Model.plugin :auto_restrict_eager_graph
43
+ #
44
+ # # Automatically restrict eager_graph for associations in Album class
45
+ # Album.plugin :auto_restrict_eager_graph
46
+ module AutoRestrictEagerGraph
47
+ module ClassMethods
48
+ # When defining an association, if a block is given for the association, but
49
+ # a :graph_* option is not used, disallow the use of eager_graph.
50
+ def associate(type, name, opts = OPTS, &block)
51
+ opts = super
52
+
53
+ if opts[:block] && !opts.has_key?(:allow_eager_graph) && !opts[:orig_opts].any?{|k,| /\Agraph_/ =~ k}
54
+ opts[:allow_eager_graph] = false
55
+ end
56
+
57
+ opts
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -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
@@ -71,25 +73,35 @@ module Sequel
71
73
  MAX_LENGTH_OPTIONS = {:from=>:values, :allow_nil=>true}.freeze
72
74
  SCHEMA_TYPES_OPTIONS = NOT_NULL_OPTIONS
73
75
  UNIQUE_OPTIONS = NOT_NULL_OPTIONS
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
+
74
90
  EMPTY_ARRAY = [].freeze
75
91
 
76
92
  def self.apply(model, opts=OPTS)
77
93
  model.instance_exec do
78
94
  plugin :validation_helpers
79
95
  @auto_validate_presence = false
96
+ @auto_validate_no_null_byte_columns = []
80
97
  @auto_validate_not_null_columns = []
81
98
  @auto_validate_explicit_not_null_columns = []
82
99
  @auto_validate_max_length_columns = []
100
+ @auto_validate_max_value_columns = []
101
+ @auto_validate_min_value_columns = []
83
102
  @auto_validate_unique_columns = []
84
103
  @auto_validate_types = true
85
-
86
- @auto_validate_options = {
87
- :not_null=>NOT_NULL_OPTIONS,
88
- :explicit_not_null=>EXPLICIT_NOT_NULL_OPTIONS,
89
- :max_length=>MAX_LENGTH_OPTIONS,
90
- :schema_types=>SCHEMA_TYPES_OPTIONS,
91
- :unique=>UNIQUE_OPTIONS
92
- }.freeze
104
+ @auto_validate_options = AUTO_VALIDATE_OPTIONS
93
105
  end
94
106
  end
95
107
 
@@ -102,14 +114,14 @@ module Sequel
102
114
  end
103
115
 
104
116
  h = @auto_validate_options.dup
105
- [:not_null, :explicit_not_null, :max_length, :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|
106
118
  if type_opts = opts[:"#{type}_opts"]
107
119
  h[type] = h[type].merge(type_opts).freeze
108
120
  end
109
121
  end
110
122
 
111
123
  if opts[:skip_invalid]
112
- [:not_null, :explicit_not_null, :max_length, :schema_types].each do |type|
124
+ [:not_null, :explicit_not_null, :no_null_byte, :max_length, :schema_types].each do |type|
113
125
  h[type] = h[type].merge(:skip_invalid=>true).freeze
114
126
  end
115
127
  end
@@ -119,6 +131,9 @@ module Sequel
119
131
  end
120
132
 
121
133
  module ClassMethods
134
+ # The columns with automatic no_null_byte validations
135
+ attr_reader :auto_validate_no_null_byte_columns
136
+
122
137
  # The columns with automatic not_null validations
123
138
  attr_reader :auto_validate_not_null_columns
124
139
 
@@ -129,13 +144,31 @@ module Sequel
129
144
  # pairs, with the first entry being the column name and second entry being the maximum length.
130
145
  attr_reader :auto_validate_max_length_columns
131
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
+
132
155
  # The columns or sets of columns with automatic unique validations
133
156
  attr_reader :auto_validate_unique_columns
134
157
 
135
158
  # Inherited options
136
159
  attr_reader :auto_validate_options
137
160
 
138
- Plugins.inherited_instance_variables(self, :@auto_validate_presence=>nil, :@auto_validate_types=>nil, :@auto_validate_not_null_columns=>:dup, :@auto_validate_explicit_not_null_columns=>:dup, :@auto_validate_max_length_columns=>:dup, :@auto_validate_unique_columns=>:dup, :@auto_validate_options => :dup)
161
+ Plugins.inherited_instance_variables(self,
162
+ :@auto_validate_presence=>nil,
163
+ :@auto_validate_types=>nil,
164
+ :@auto_validate_no_null_byte_columns=>:dup,
165
+ :@auto_validate_not_null_columns=>:dup,
166
+ :@auto_validate_explicit_not_null_columns=>:dup,
167
+ :@auto_validate_max_length_columns=>:dup,
168
+ :@auto_validate_max_value_columns=>:dup,
169
+ :@auto_validate_min_value_columns=>:dup,
170
+ :@auto_validate_unique_columns=>:dup,
171
+ :@auto_validate_options => :dup)
139
172
  Plugins.after_set_dataset(self, :setup_auto_validations)
140
173
 
141
174
  # Whether to use a presence validation for not null columns
@@ -150,20 +183,27 @@ module Sequel
150
183
 
151
184
  # Freeze auto_validation settings when freezing model class.
152
185
  def freeze
186
+ @auto_validate_no_null_byte_columns.freeze
153
187
  @auto_validate_not_null_columns.freeze
154
188
  @auto_validate_explicit_not_null_columns.freeze
155
189
  @auto_validate_max_length_columns.freeze
190
+ @auto_validate_max_value_columns.freeze
191
+ @auto_validate_min_value_columns.freeze
156
192
  @auto_validate_unique_columns.freeze
157
193
 
158
194
  super
159
195
  end
160
196
 
161
- # Skip automatic validations for the given validation type (:not_null, :types, :unique).
197
+ # Skip automatic validations for the given validation type
198
+ # (:not_null, :no_null_byte, :types, :unique, :max_length, :max_value, :min_value).
162
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.
163
203
  def skip_auto_validations(type)
164
204
  case type
165
205
  when :all
166
- [:not_null, :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)}
167
207
  when :not_null
168
208
  auto_validate_not_null_columns.clear
169
209
  auto_validate_explicit_not_null_columns.clear
@@ -183,6 +223,9 @@ module Sequel
183
223
  explicit_not_null_cols += Array(primary_key)
184
224
  @auto_validate_explicit_not_null_columns = explicit_not_null_cols.uniq
185
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]]}
228
+ @auto_validate_no_null_byte_columns = db_schema.select{|_, sch| sch[:type] == :string}.map{|col, _| col}
186
229
  table = dataset.first_source_table
187
230
  @auto_validate_unique_columns = if db.supports_index_parsing? && [Symbol, SQL::QualifiedIdentifier, SQL::Identifier, String].any?{|c| table.is_a?(c)}
188
231
  db.indexes(table).select{|name, idx| idx[:unique] == true}.map{|name, idx| idx[:columns].length == 1 ? idx[:columns].first : idx[:columns]}
@@ -209,6 +252,9 @@ module Sequel
209
252
  return if skip.include?(:all)
210
253
  opts = model.auto_validate_options
211
254
 
255
+ unless skip.include?(:no_null_byte) || (no_null_byte_columns = model.auto_validate_no_null_byte_columns).empty?
256
+ validates_no_null_byte(no_null_byte_columns, opts[:no_null_byte])
257
+ end
212
258
 
213
259
  unless skip.include?(:not_null)
214
260
  not_null_method = model.auto_validate_presence? ? :validates_presence : :validates_not_null
@@ -228,6 +274,18 @@ module Sequel
228
274
 
229
275
  unless skip.include?(:types) || !model.auto_validate_types?
230
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
231
289
  end
232
290
 
233
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
  #
@@ -356,7 +355,7 @@ module Sequel
356
355
 
357
356
  # Keys should be an array of arrays containing key_id, key string, auth_data, and padding.
358
357
  def initialize(keys)
359
- if keys.empty?
358
+ if !keys || keys.empty?
360
359
  raise Error, "Cannot initialize encryptor without encryption key"
361
360
  end
362
361
 
@@ -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
 
@@ -608,7 +629,7 @@ module Sequel
608
629
 
609
630
  # Setup encryption for the given column.
610
631
  def _encrypt_column(column, opts)
611
- cryptor ||= if block_given?
632
+ cryptor ||= if defined?(yield)
612
633
  dsl = ColumnDSL.new
613
634
  yield dsl
614
635
  Cryptor.new(dsl.keys)