sequel 5.60.1 → 5.81.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 (166) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +280 -0
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +27 -24
  5. data/doc/advanced_associations.rdoc +16 -14
  6. data/doc/association_basics.rdoc +53 -17
  7. data/doc/cheat_sheet.rdoc +3 -3
  8. data/doc/code_order.rdoc +5 -3
  9. data/doc/dataset_basics.rdoc +1 -1
  10. data/doc/mass_assignment.rdoc +1 -1
  11. data/doc/migration.rdoc +15 -0
  12. data/doc/model_hooks.rdoc +1 -1
  13. data/doc/object_model.rdoc +8 -8
  14. data/doc/opening_databases.rdoc +18 -7
  15. data/doc/postgresql.rdoc +8 -8
  16. data/doc/querying.rdoc +7 -2
  17. data/doc/release_notes/5.61.0.txt +43 -0
  18. data/doc/release_notes/5.62.0.txt +132 -0
  19. data/doc/release_notes/5.63.0.txt +33 -0
  20. data/doc/release_notes/5.64.0.txt +50 -0
  21. data/doc/release_notes/5.65.0.txt +21 -0
  22. data/doc/release_notes/5.66.0.txt +24 -0
  23. data/doc/release_notes/5.67.0.txt +32 -0
  24. data/doc/release_notes/5.68.0.txt +61 -0
  25. data/doc/release_notes/5.69.0.txt +26 -0
  26. data/doc/release_notes/5.70.0.txt +35 -0
  27. data/doc/release_notes/5.71.0.txt +21 -0
  28. data/doc/release_notes/5.72.0.txt +33 -0
  29. data/doc/release_notes/5.73.0.txt +66 -0
  30. data/doc/release_notes/5.74.0.txt +45 -0
  31. data/doc/release_notes/5.75.0.txt +35 -0
  32. data/doc/release_notes/5.76.0.txt +86 -0
  33. data/doc/release_notes/5.77.0.txt +63 -0
  34. data/doc/release_notes/5.78.0.txt +67 -0
  35. data/doc/release_notes/5.79.0.txt +28 -0
  36. data/doc/release_notes/5.80.0.txt +40 -0
  37. data/doc/release_notes/5.81.0.txt +31 -0
  38. data/doc/schema_modification.rdoc +3 -3
  39. data/doc/security.rdoc +9 -9
  40. data/doc/sharding.rdoc +3 -1
  41. data/doc/sql.rdoc +14 -14
  42. data/doc/testing.rdoc +16 -12
  43. data/doc/transactions.rdoc +6 -6
  44. data/doc/virtual_rows.rdoc +1 -1
  45. data/lib/sequel/adapters/ibmdb.rb +1 -1
  46. data/lib/sequel/adapters/jdbc/h2.rb +3 -0
  47. data/lib/sequel/adapters/jdbc/hsqldb.rb +2 -0
  48. data/lib/sequel/adapters/jdbc/postgresql.rb +3 -0
  49. data/lib/sequel/adapters/jdbc/sqlanywhere.rb +15 -0
  50. data/lib/sequel/adapters/jdbc/sqlserver.rb +4 -0
  51. data/lib/sequel/adapters/jdbc.rb +10 -6
  52. data/lib/sequel/adapters/mysql.rb +19 -7
  53. data/lib/sequel/adapters/mysql2.rb +2 -2
  54. data/lib/sequel/adapters/odbc/mssql.rb +1 -1
  55. data/lib/sequel/adapters/oracle.rb +1 -0
  56. data/lib/sequel/adapters/postgres.rb +10 -5
  57. data/lib/sequel/adapters/shared/access.rb +9 -1
  58. data/lib/sequel/adapters/shared/db2.rb +12 -0
  59. data/lib/sequel/adapters/shared/mssql.rb +71 -9
  60. data/lib/sequel/adapters/shared/mysql.rb +82 -3
  61. data/lib/sequel/adapters/shared/oracle.rb +17 -7
  62. data/lib/sequel/adapters/shared/postgres.rb +469 -160
  63. data/lib/sequel/adapters/shared/sqlanywhere.rb +18 -5
  64. data/lib/sequel/adapters/shared/sqlite.rb +39 -3
  65. data/lib/sequel/adapters/sqlite.rb +42 -3
  66. data/lib/sequel/adapters/trilogy.rb +117 -0
  67. data/lib/sequel/connection_pool/sharded_threaded.rb +16 -11
  68. data/lib/sequel/connection_pool/sharded_timed_queue.rb +374 -0
  69. data/lib/sequel/connection_pool/threaded.rb +14 -8
  70. data/lib/sequel/connection_pool/timed_queue.rb +270 -0
  71. data/lib/sequel/connection_pool.rb +57 -31
  72. data/lib/sequel/database/connecting.rb +25 -1
  73. data/lib/sequel/database/dataset.rb +16 -6
  74. data/lib/sequel/database/misc.rb +65 -14
  75. data/lib/sequel/database/query.rb +72 -1
  76. data/lib/sequel/database/schema_generator.rb +1 -1
  77. data/lib/sequel/database/schema_methods.rb +12 -5
  78. data/lib/sequel/database/transactions.rb +6 -0
  79. data/lib/sequel/dataset/actions.rb +60 -13
  80. data/lib/sequel/dataset/dataset_module.rb +1 -1
  81. data/lib/sequel/dataset/deprecated_singleton_class_methods.rb +42 -0
  82. data/lib/sequel/dataset/features.rb +15 -1
  83. data/lib/sequel/dataset/graph.rb +1 -0
  84. data/lib/sequel/dataset/misc.rb +12 -2
  85. data/lib/sequel/dataset/placeholder_literalizer.rb +20 -9
  86. data/lib/sequel/dataset/query.rb +119 -45
  87. data/lib/sequel/dataset/sql.rb +58 -36
  88. data/lib/sequel/dataset.rb +4 -0
  89. data/lib/sequel/exceptions.rb +5 -0
  90. data/lib/sequel/extensions/_model_pg_row.rb +0 -12
  91. data/lib/sequel/extensions/_pretty_table.rb +1 -1
  92. data/lib/sequel/extensions/any_not_empty.rb +2 -2
  93. data/lib/sequel/extensions/async_thread_pool.rb +21 -13
  94. data/lib/sequel/extensions/auto_cast_date_and_time.rb +94 -0
  95. data/lib/sequel/extensions/auto_literal_strings.rb +1 -1
  96. data/lib/sequel/extensions/caller_logging.rb +4 -1
  97. data/lib/sequel/extensions/connection_expiration.rb +15 -9
  98. data/lib/sequel/extensions/connection_validator.rb +16 -11
  99. data/lib/sequel/extensions/constraint_validations.rb +1 -1
  100. data/lib/sequel/extensions/date_arithmetic.rb +1 -1
  101. data/lib/sequel/extensions/duplicate_columns_handler.rb +10 -9
  102. data/lib/sequel/extensions/index_caching.rb +5 -1
  103. data/lib/sequel/extensions/looser_typecasting.rb +3 -0
  104. data/lib/sequel/extensions/migration.rb +65 -15
  105. data/lib/sequel/extensions/named_timezones.rb +22 -6
  106. data/lib/sequel/extensions/pg_array.rb +33 -4
  107. data/lib/sequel/extensions/pg_auto_parameterize.rb +509 -0
  108. data/lib/sequel/extensions/pg_auto_parameterize_in_array.rb +110 -0
  109. data/lib/sequel/extensions/pg_enum.rb +1 -2
  110. data/lib/sequel/extensions/pg_extended_date_support.rb +38 -27
  111. data/lib/sequel/extensions/pg_extended_integer_support.rb +116 -0
  112. data/lib/sequel/extensions/pg_hstore.rb +5 -0
  113. data/lib/sequel/extensions/pg_inet.rb +10 -11
  114. data/lib/sequel/extensions/pg_interval.rb +10 -11
  115. data/lib/sequel/extensions/pg_json.rb +10 -10
  116. data/lib/sequel/extensions/pg_json_ops.rb +3 -3
  117. data/lib/sequel/extensions/pg_multirange.rb +6 -11
  118. data/lib/sequel/extensions/pg_range.rb +9 -14
  119. data/lib/sequel/extensions/pg_row.rb +20 -19
  120. data/lib/sequel/extensions/pg_timestamptz.rb +27 -3
  121. data/lib/sequel/extensions/provenance.rb +110 -0
  122. data/lib/sequel/extensions/round_timestamps.rb +1 -1
  123. data/lib/sequel/extensions/schema_caching.rb +1 -1
  124. data/lib/sequel/extensions/schema_dumper.rb +32 -9
  125. data/lib/sequel/extensions/server_block.rb +2 -1
  126. data/lib/sequel/extensions/set_literalizer.rb +58 -0
  127. data/lib/sequel/extensions/sqlite_json_ops.rb +76 -18
  128. data/lib/sequel/extensions/temporarily_release_connection.rb +178 -0
  129. data/lib/sequel/extensions/transaction_connection_validator.rb +78 -0
  130. data/lib/sequel/model/associations.rb +38 -11
  131. data/lib/sequel/model/base.rb +32 -18
  132. data/lib/sequel/model/dataset_module.rb +3 -0
  133. data/lib/sequel/model/exceptions.rb +15 -3
  134. data/lib/sequel/plugins/auto_validations.rb +53 -15
  135. data/lib/sequel/plugins/class_table_inheritance.rb +2 -2
  136. data/lib/sequel/plugins/column_encryption.rb +28 -7
  137. data/lib/sequel/plugins/composition.rb +2 -2
  138. data/lib/sequel/plugins/concurrent_eager_loading.rb +4 -4
  139. data/lib/sequel/plugins/constraint_validations.rb +8 -5
  140. data/lib/sequel/plugins/defaults_setter.rb +16 -0
  141. data/lib/sequel/plugins/dirty.rb +1 -1
  142. data/lib/sequel/plugins/finder.rb +4 -2
  143. data/lib/sequel/plugins/input_transformer.rb +1 -1
  144. data/lib/sequel/plugins/list.rb +5 -2
  145. data/lib/sequel/plugins/many_through_many.rb +1 -1
  146. data/lib/sequel/plugins/mssql_optimistic_locking.rb +8 -38
  147. data/lib/sequel/plugins/nested_attributes.rb +4 -4
  148. data/lib/sequel/plugins/optimistic_locking.rb +9 -42
  149. data/lib/sequel/plugins/optimistic_locking_base.rb +55 -0
  150. data/lib/sequel/plugins/paged_operations.rb +181 -0
  151. data/lib/sequel/plugins/pg_auto_constraint_validations.rb +9 -3
  152. data/lib/sequel/plugins/pg_xmin_optimistic_locking.rb +109 -0
  153. data/lib/sequel/plugins/prepared_statements.rb +2 -1
  154. data/lib/sequel/plugins/prepared_statements_safe.rb +2 -1
  155. data/lib/sequel/plugins/primary_key_lookup_check_values.rb +154 -0
  156. data/lib/sequel/plugins/rcte_tree.rb +7 -4
  157. data/lib/sequel/plugins/single_table_inheritance.rb +8 -0
  158. data/lib/sequel/plugins/sql_comments.rb +1 -1
  159. data/lib/sequel/plugins/static_cache.rb +38 -0
  160. data/lib/sequel/plugins/static_cache_cache.rb +5 -1
  161. data/lib/sequel/plugins/tactical_eager_loading.rb +14 -14
  162. data/lib/sequel/plugins/validate_associated.rb +22 -12
  163. data/lib/sequel/plugins/validation_helpers.rb +29 -2
  164. data/lib/sequel/plugins/validation_helpers_generic_type_messages.rb +73 -0
  165. data/lib/sequel/version.rb +2 -2
  166. metadata +79 -6
@@ -0,0 +1,78 @@
1
+ # frozen-string-literal: true
2
+ #
3
+ # The transaction_connection_validator extension automatically
4
+ # retries a transaction on a connection if an disconnect error
5
+ # is raised when sending the statement to begin a new
6
+ # transaction, as long as the user has not already checked out
7
+ # a connection. This is safe to do because no other queries
8
+ # have been issued on the connection, and no user-level code
9
+ # is run before retrying.
10
+ #
11
+ # This approach to connection validation can be significantly
12
+ # lower overhead than the connection_validator extension,
13
+ # though it does not handle all cases handled by the
14
+ # connection_validator extension. However, it performs the
15
+ # validation checks on every new transaction, so it will
16
+ # automatically handle disconnected connections in some cases
17
+ # where the connection_validator extension will not by default
18
+ # (as the connection_validator extension only checks
19
+ # connections if they have not been used in the last hour by
20
+ # default).
21
+ #
22
+ # Related module: Sequel::TransactionConnectionValidator
23
+
24
+ #
25
+ module Sequel
26
+ module TransactionConnectionValidator
27
+ class DisconnectRetry < DatabaseDisconnectError
28
+ # The connection that raised the disconnect error
29
+ attr_accessor :connection
30
+
31
+ # The underlying disconnect error, in case it needs to be reraised.
32
+ attr_accessor :database_error
33
+ end
34
+
35
+ # Rescue disconnect errors raised when beginning a new transaction. If there
36
+ # is a disconnnect error, it should be safe to retry the transaction using a
37
+ # new connection, as we haven't yielded control to the user yet.
38
+ def transaction(opts=OPTS)
39
+ super
40
+ rescue DisconnectRetry => e
41
+ if synchronize(opts[:server]){|conn| conn.equal?(e.connection)}
42
+ # If retrying would use the same connection, that means the
43
+ # connection was not removed from the pool, which means the caller has
44
+ # already checked out the connection, and retrying will not be successful.
45
+ # In this case, we can only reraise the exception.
46
+ raise e.database_error
47
+ end
48
+
49
+ num_retries ||= 0
50
+ num_retries += 1
51
+ retry if num_retries < 5
52
+
53
+ raise e.database_error
54
+ end
55
+
56
+ private
57
+
58
+ # Reraise disconnect errors as DisconnectRetry so they can be retried.
59
+ def begin_new_transaction(conn, opts)
60
+ super
61
+ rescue Sequel::DatabaseDisconnectError, *database_error_classes => e
62
+ if e.is_a?(Sequel::DatabaseDisconnectError) || disconnect_error?(e, OPTS)
63
+ exception = DisconnectRetry.new(e.message)
64
+ exception.set_backtrace([])
65
+ exception.connection = conn
66
+ unless e.is_a?(Sequel::DatabaseError)
67
+ e = Sequel.convert_exception_class(e, database_error_class(e, OPTS))
68
+ end
69
+ exception.database_error = e
70
+ raise exception
71
+ end
72
+
73
+ raise
74
+ end
75
+ end
76
+
77
+ Database.register_extension(:transaction_connection_validator, TransactionConnectionValidator)
78
+ end
@@ -276,7 +276,7 @@ module Sequel
276
276
 
277
277
  if eo[:no_results]
278
278
  no_results = true
279
- elsif eo[:eager_block] || eo[:loader] == false
279
+ elsif eo[:eager_block] || eo[:loader] == false || !use_placeholder_loader?
280
280
  ds = eager_loading_dataset(eo)
281
281
 
282
282
  strategy = ds.opts[:eager_limit_strategy] || strategy
@@ -299,13 +299,28 @@ module Sequel
299
299
  strategy = :ruby if strategy == :correlated_subquery
300
300
  strategy = nil if strategy == :ruby && assign_singular?
301
301
  objects = apply_eager_limit_strategy(ds, strategy, eager_limit).all
302
+
303
+ if strategy == :window_function
304
+ delete_rn = ds.row_number_column
305
+ objects.each{|obj| obj.values.delete(delete_rn)}
306
+ end
302
307
  elsif strategy == :union
303
308
  objects = []
304
309
  ds = associated_dataset
305
310
  loader = union_eager_loader
306
311
  joiner = " UNION ALL "
307
312
  ids.each_slice(subqueries_per_union).each do |slice|
308
- objects.concat(ds.with_sql(slice.map{|k| loader.sql(*k)}.join(joiner)).to_a)
313
+ sql = loader.send(:sql_origin)
314
+ join = false
315
+ slice.each do |k|
316
+ if join
317
+ sql << joiner
318
+ else
319
+ join = true
320
+ end
321
+ loader.append_sql(sql, *k)
322
+ end
323
+ objects.concat(ds.with_sql(sql).to_a)
309
324
  end
310
325
  ds = ds.eager(cascade) if cascade
311
326
  ds.send(:post_load, objects)
@@ -441,7 +456,7 @@ module Sequel
441
456
  def placeholder_loader
442
457
  if use_placeholder_loader?
443
458
  cached_fetch(:placeholder_loader) do
444
- Sequel::Dataset::PlaceholderLiteralizer.loader(associated_dataset) do |pl, ds|
459
+ associated_dataset.placeholder_literalizer_loader do |pl, ds|
445
460
  ds = ds.where(Sequel.&(*predicate_keys.map{|k| SQL::BooleanExpression.new(:'=', k, pl.arg)}))
446
461
  if self[:block]
447
462
  ds = self[:block].call(ds)
@@ -743,8 +758,8 @@ module Sequel
743
758
  # A placeholder literalizer used to speed up eager loading.
744
759
  def placeholder_eager_loader
745
760
  cached_fetch(:placeholder_eager_loader) do
746
- Sequel::Dataset::PlaceholderLiteralizer.loader(associated_dataset) do |pl, ds|
747
- apply_eager_limit_strategy(eager_loading_dataset.where(predicate_key=>pl.arg), eager_limit_strategy)
761
+ eager_loading_dataset.placeholder_literalizer_loader do |pl, ds|
762
+ apply_eager_limit_strategy(ds.where(predicate_key=>pl.arg), eager_limit_strategy)
748
763
  end
749
764
  end
750
765
  end
@@ -803,7 +818,7 @@ module Sequel
803
818
  # loading a limited association.
804
819
  def union_eager_loader
805
820
  cached_fetch(:union_eager_loader) do
806
- Sequel::Dataset::PlaceholderLiteralizer.loader(associated_dataset) do |pl, ds|
821
+ associated_dataset.placeholder_literalizer_loader do |pl, ds|
807
822
  ds = self[:eager_block].call(ds) if self[:eager_block]
808
823
  keys = predicate_keys
809
824
  ds = ds.where(keys.map{pl.arg}.zip(keys))
@@ -817,7 +832,7 @@ module Sequel
817
832
 
818
833
  # Whether the placeholder loader can be used to load the association.
819
834
  def use_placeholder_loader?
820
- self[:use_placeholder_loader]
835
+ self[:use_placeholder_loader] && _associated_dataset.supports_placeholder_literalizer?
821
836
  end
822
837
  end
823
838
 
@@ -1717,6 +1732,8 @@ module Sequel
1717
1732
  # :graph_select :: A column or array of columns to select from the associated table
1718
1733
  # when eagerly loading the association via +eager_graph+. Defaults to all
1719
1734
  # columns in the associated table.
1735
+ # :graph_use_association_block :: Makes eager_graph consider the association block. Without this, eager_graph
1736
+ # ignores the bock and only use the :graph_* options.
1720
1737
  # :instance_specific :: Marks the association as instance specific. Should be used if the association block
1721
1738
  # uses instance specific state, or transient state (accessing current date/time, etc.).
1722
1739
  # :limit :: Limit the number of records to the provided value. Use
@@ -2456,6 +2473,9 @@ module Sequel
2456
2473
  # Return dataset to graph into given the association reflection, applying the :callback option if set.
2457
2474
  def eager_graph_dataset(opts, eager_options)
2458
2475
  ds = opts.associated_class.dataset
2476
+ if opts[:graph_use_association_block] && (b = opts[:block])
2477
+ ds = b.call(ds)
2478
+ end
2459
2479
  if cb = eager_options[:callback]
2460
2480
  ds = cb.call(ds)
2461
2481
  end
@@ -3367,8 +3387,15 @@ module Sequel
3367
3387
  local_opts = ds.opts[:eager_graph][:local]
3368
3388
  limit_strategy = r.eager_graph_limit_strategy(local_opts[:limit_strategy])
3369
3389
 
3370
- if r[:conditions] && !Sequel.condition_specifier?(r[:conditions]) && !r[:orig_opts].has_key?(:graph_conditions) && !r[:orig_opts].has_key?(:graph_only_conditions) && !r.has_key?(:graph_block)
3371
- raise Error, "Cannot eager_graph association when :conditions specified and not a hash or an array of pairs. Specify :graph_conditions, :graph_only_conditions, or :graph_block for the association. Model: #{r[:model]}, association: #{r[:name]}"
3390
+ # SEQUEL6: remove and integrate the auto_restrict_eager_graph plugin
3391
+ if !r[:orig_opts].has_key?(:graph_conditions) && !r[:orig_opts].has_key?(:graph_only_conditions) && !r.has_key?(:graph_block) && !r[:allow_eager_graph]
3392
+ if r[:conditions] && !Sequel.condition_specifier?(r[:conditions])
3393
+ raise Error, "Cannot eager_graph association when :conditions specified and not a hash or an array of pairs. Specify :graph_conditions, :graph_only_conditions, or :graph_block for the association. Model: #{r[:model]}, association: #{r[:name]}"
3394
+ end
3395
+
3396
+ if r[:block] && !r[:graph_use_association_block]
3397
+ warn "eager_graph used for association when association given a block without graph options. The block is ignored in this case. This will result in an exception starting in Sequel 6. Model: #{r[:model]}, association: #{r[:name]}"
3398
+ end
3372
3399
  end
3373
3400
 
3374
3401
  ds = loader.call(:self=>ds, :table_alias=>assoc_table_alias, :implicit_qualifier=>(ta == ds.opts[:eager_graph][:master]) ? first_source : qualifier_from_alias_symbol(ta, first_source), :callback=>callback, :join_type=>join_type || local_opts[:join_type], :join_only=>local_opts[:join_only], :limit_strategy=>limit_strategy, :from_self_alias=>ds.opts[:eager_graph][:master])
@@ -3547,11 +3574,11 @@ module Sequel
3547
3574
  end
3548
3575
 
3549
3576
  # Eagerly load all specified associations.
3550
- def eager_load(a, eager_assoc=@opts[:eager])
3577
+ def eager_load(a, eager_assoc=@opts[:eager], m=model)
3551
3578
  return if a.empty?
3552
3579
 
3553
3580
  # Reflections for all associations to eager load
3554
- reflections = eager_assoc.keys.map{|assoc| model.association_reflection(assoc) || (raise Sequel::UndefinedAssociation, "Model: #{self}, Association: #{assoc}")}
3581
+ reflections = eager_assoc.keys.map{|assoc| m.association_reflection(assoc) || (raise Sequel::UndefinedAssociation, "Model: #{self}, Association: #{assoc}")}
3555
3582
 
3556
3583
  perform_eager_loads(prepare_eager_load(a, reflections, eager_assoc))
3557
3584
 
@@ -17,7 +17,7 @@ module Sequel
17
17
  # natural_join, natural_left_join, natural_right_join, offset, order, order_append, order_by,
18
18
  # order_more, order_prepend, paged_each, qualify, reverse, reverse_order, right_join,
19
19
  # right_outer_join, select, select_all, select_append, select_group, select_hash,
20
- # select_hash_groups, select_map, select_more, select_order_map, server,
20
+ # select_hash_groups, select_map, select_more, select_order_map, select_prepend, server,
21
21
  # single_record, single_record!, single_value, single_value!, sum, to_hash, to_hash_groups,
22
22
  # truncate, unfiltered, ungraphed, ungrouped, union, unlimited, unordered, where, where_all,
23
23
  # where_each, where_single_value, with, with_recursive, with_sql
@@ -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
@@ -1141,7 +1142,7 @@ module Sequel
1141
1142
  #
1142
1143
  # Artist[1] === Artist[1] # => true
1143
1144
  # Artist.new === Artist.new # => false
1144
- # Artist[1].set(:name=>'Bob') === Artist[1] # => true
1145
+ # Artist[1].set(name: 'Bob') === Artist[1] # => true
1145
1146
  def ===(obj)
1146
1147
  case pkv = pk
1147
1148
  when nil
@@ -1160,7 +1161,7 @@ module Sequel
1160
1161
  #
1161
1162
  # Artist[1].pk_equal?(Artist[1]) # => true
1162
1163
  # Artist.new.pk_equal?(Artist.new) # => false
1163
- # Artist[1].set(:name=>'Bob').pk_equal?(Artist[1]) # => true
1164
+ # Artist[1].set(name: 'Bob').pk_equal?(Artist[1]) # => true
1164
1165
  alias pk_equal? ===
1165
1166
 
1166
1167
  # class is defined in Object, but it is also a keyword,
@@ -1232,7 +1233,7 @@ module Sequel
1232
1233
  #
1233
1234
  # Artist[1] == Artist[1] # => true
1234
1235
  # Artist.new == Artist.new # => true
1235
- # Artist[1].set(:name=>'Bob') == Artist[1] # => false
1236
+ # Artist[1].set(name: 'Bob') == Artist[1] # => false
1236
1237
  def eql?(obj)
1237
1238
  (obj.class == model) && (obj.values == @values)
1238
1239
  end
@@ -1243,18 +1244,21 @@ module Sequel
1243
1244
  @errors ||= errors_class.new
1244
1245
  end
1245
1246
 
1247
+ EXISTS_SELECT_ = SQL::AliasedExpression.new(1, :one)
1248
+ private_constant :EXISTS_SELECT_
1249
+
1246
1250
  # Returns true when current instance exists, false otherwise.
1247
1251
  # Generally an object that isn't new will exist unless it has
1248
1252
  # been deleted. Uses a database query to check for existence,
1249
1253
  # unless the model object is new, in which case this is always
1250
1254
  # false.
1251
1255
  #
1252
- # Artist[1].exists? # SELECT 1 FROM artists WHERE (id = 1)
1256
+ # Artist[1].exists? # SELECT 1 AS one FROM artists WHERE (id = 1)
1253
1257
  # # => true
1254
1258
  # Artist.new.exists?
1255
1259
  # # => false
1256
1260
  def exists?
1257
- new? ? false : !this.get(SQL::AliasedExpression.new(1, :one)).nil?
1261
+ new? ? false : !this.get(EXISTS_SELECT_).nil?
1258
1262
  end
1259
1263
 
1260
1264
  # Ignore the model's setter method cache when this instances extends a module, as the
@@ -1334,13 +1338,13 @@ module Sequel
1334
1338
  # a = Artist[1]
1335
1339
  # Artist.db.transaction do
1336
1340
  # a.lock!
1337
- # a.update(:name=>'A')
1341
+ # a.update(name: 'A')
1338
1342
  # end
1339
1343
  #
1340
1344
  # a = Artist[2]
1341
1345
  # Artist.db.transaction do
1342
1346
  # a.lock!('FOR NO KEY UPDATE')
1343
- # a.update(:name=>'B')
1347
+ # a.update(name: 'B')
1344
1348
  # end
1345
1349
  def lock!(style=:update)
1346
1350
  _refresh(this.lock_style(style)) unless new?
@@ -1944,8 +1948,10 @@ module Sequel
1944
1948
  end
1945
1949
 
1946
1950
  # If transactions should be used, wrap the yield in a transaction block.
1947
- def checked_transaction(opts=OPTS)
1948
- 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)
1949
1955
  end
1950
1956
 
1951
1957
  # Change the value of the column to given value, recording the change.
@@ -2030,19 +2036,20 @@ module Sequel
2030
2036
  meths = setter_methods(type)
2031
2037
  strict = strict_param_setting
2032
2038
  hash.each do |k,v|
2039
+ k = k.to_s
2033
2040
  m = "#{k}="
2034
2041
  if meths.include?(m)
2035
2042
  set_column_value(m, v)
2036
2043
  elsif strict
2037
2044
  # Avoid using respond_to? or creating symbols from user input
2038
2045
  if public_methods.map(&:to_s).include?(m)
2039
- if Array(model.primary_key).map(&:to_s).member?(k.to_s) && model.restrict_primary_key?
2040
- 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)
2041
2048
  else
2042
- raise MassAssignmentRestriction, "#{k} is a restricted column"
2049
+ raise MassAssignmentRestriction.create("#{k} is a restricted column", self, k)
2043
2050
  end
2044
2051
  else
2045
- raise MassAssignmentRestriction, "method #{m} doesn't exist"
2052
+ raise MassAssignmentRestriction.create("method #{m} doesn't exist", self, k)
2046
2053
  end
2047
2054
  end
2048
2055
  end
@@ -2146,8 +2153,9 @@ module Sequel
2146
2153
  # # DELETE FROM artists WHERE (id = 2)
2147
2154
  # # ...
2148
2155
  def destroy
2149
- pr = proc{all(&:destroy).length}
2150
- 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
2151
2159
  end
2152
2160
 
2153
2161
  # If there is no order already defined on this dataset, order it by
@@ -2227,11 +2235,17 @@ module Sequel
2227
2235
 
2228
2236
  private
2229
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
+
2230
2244
  # If the dataset is not already ordered, and the model has a primary key,
2231
2245
  # return a clone ordered by the primary key.
2232
2246
  def _primary_key_order
2233
- if @opts[:order].nil? && model && (pk = model.primary_key)
2234
- cached_dataset(:_pk_order_ds){order(*pk)}
2247
+ if @opts[:order].nil? && model && model.primary_key
2248
+ _force_primary_key_order
2235
2249
  end
2236
2250
  end
2237
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
 
@@ -565,7 +586,7 @@ module Sequel
565
586
  end
566
587
  end
567
588
 
568
- def self.apply(model, opts=OPTS)
589
+ def self.apply(model, opts=OPTS, &_)
569
590
  model.plugin :serialization
570
591
  end
571
592
 
@@ -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