sequel 5.33.0 → 5.58.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (191) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +318 -0
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +40 -9
  5. data/doc/association_basics.rdoc +77 -13
  6. data/doc/cheat_sheet.rdoc +13 -5
  7. data/doc/code_order.rdoc +0 -12
  8. data/doc/dataset_filtering.rdoc +2 -2
  9. data/doc/fork_safety.rdoc +84 -0
  10. data/doc/migration.rdoc +12 -6
  11. data/doc/model_plugins.rdoc +1 -1
  12. data/doc/opening_databases.rdoc +15 -3
  13. data/doc/postgresql.rdoc +9 -1
  14. data/doc/querying.rdoc +7 -5
  15. data/doc/release_notes/5.34.0.txt +40 -0
  16. data/doc/release_notes/5.35.0.txt +56 -0
  17. data/doc/release_notes/5.36.0.txt +60 -0
  18. data/doc/release_notes/5.37.0.txt +30 -0
  19. data/doc/release_notes/5.38.0.txt +28 -0
  20. data/doc/release_notes/5.39.0.txt +19 -0
  21. data/doc/release_notes/5.40.0.txt +40 -0
  22. data/doc/release_notes/5.41.0.txt +25 -0
  23. data/doc/release_notes/5.42.0.txt +136 -0
  24. data/doc/release_notes/5.43.0.txt +98 -0
  25. data/doc/release_notes/5.44.0.txt +32 -0
  26. data/doc/release_notes/5.45.0.txt +34 -0
  27. data/doc/release_notes/5.46.0.txt +87 -0
  28. data/doc/release_notes/5.47.0.txt +59 -0
  29. data/doc/release_notes/5.48.0.txt +14 -0
  30. data/doc/release_notes/5.49.0.txt +59 -0
  31. data/doc/release_notes/5.50.0.txt +78 -0
  32. data/doc/release_notes/5.51.0.txt +47 -0
  33. data/doc/release_notes/5.52.0.txt +87 -0
  34. data/doc/release_notes/5.53.0.txt +23 -0
  35. data/doc/release_notes/5.54.0.txt +27 -0
  36. data/doc/release_notes/5.55.0.txt +21 -0
  37. data/doc/release_notes/5.56.0.txt +51 -0
  38. data/doc/release_notes/5.57.0.txt +23 -0
  39. data/doc/release_notes/5.58.0.txt +31 -0
  40. data/doc/sql.rdoc +14 -2
  41. data/doc/testing.rdoc +10 -1
  42. data/doc/transactions.rdoc +0 -8
  43. data/doc/validations.rdoc +1 -1
  44. data/doc/virtual_rows.rdoc +1 -1
  45. data/lib/sequel/adapters/ado/access.rb +1 -1
  46. data/lib/sequel/adapters/ado.rb +17 -17
  47. data/lib/sequel/adapters/amalgalite.rb +3 -5
  48. data/lib/sequel/adapters/ibmdb.rb +2 -2
  49. data/lib/sequel/adapters/jdbc/derby.rb +8 -0
  50. data/lib/sequel/adapters/jdbc/h2.rb +60 -10
  51. data/lib/sequel/adapters/jdbc/hsqldb.rb +6 -0
  52. data/lib/sequel/adapters/jdbc/mysql.rb +4 -4
  53. data/lib/sequel/adapters/jdbc/postgresql.rb +4 -4
  54. data/lib/sequel/adapters/jdbc.rb +29 -19
  55. data/lib/sequel/adapters/mysql.rb +80 -67
  56. data/lib/sequel/adapters/mysql2.rb +54 -49
  57. data/lib/sequel/adapters/odbc.rb +8 -6
  58. data/lib/sequel/adapters/oracle.rb +5 -4
  59. data/lib/sequel/adapters/postgres.rb +27 -29
  60. data/lib/sequel/adapters/shared/access.rb +2 -0
  61. data/lib/sequel/adapters/shared/db2.rb +30 -0
  62. data/lib/sequel/adapters/shared/mssql.rb +84 -7
  63. data/lib/sequel/adapters/shared/mysql.rb +33 -2
  64. data/lib/sequel/adapters/shared/oracle.rb +82 -7
  65. data/lib/sequel/adapters/shared/postgres.rb +158 -20
  66. data/lib/sequel/adapters/shared/sqlanywhere.rb +3 -0
  67. data/lib/sequel/adapters/shared/sqlite.rb +102 -10
  68. data/lib/sequel/adapters/sqlanywhere.rb +1 -1
  69. data/lib/sequel/adapters/sqlite.rb +60 -18
  70. data/lib/sequel/adapters/tinytds.rb +2 -1
  71. data/lib/sequel/adapters/utils/columns_limit_1.rb +22 -0
  72. data/lib/sequel/adapters/utils/mysql_mysql2.rb +2 -1
  73. data/lib/sequel/ast_transformer.rb +6 -0
  74. data/lib/sequel/connection_pool/sharded_single.rb +9 -8
  75. data/lib/sequel/connection_pool/sharded_threaded.rb +10 -10
  76. data/lib/sequel/connection_pool/single.rb +7 -9
  77. data/lib/sequel/connection_pool/threaded.rb +1 -1
  78. data/lib/sequel/core.rb +33 -24
  79. data/lib/sequel/database/connecting.rb +3 -4
  80. data/lib/sequel/database/misc.rb +37 -12
  81. data/lib/sequel/database/query.rb +3 -1
  82. data/lib/sequel/database/schema_generator.rb +50 -53
  83. data/lib/sequel/database/schema_methods.rb +45 -23
  84. data/lib/sequel/database/transactions.rb +9 -6
  85. data/lib/sequel/dataset/actions.rb +61 -8
  86. data/lib/sequel/dataset/features.rb +15 -0
  87. data/lib/sequel/dataset/placeholder_literalizer.rb +3 -7
  88. data/lib/sequel/dataset/prepared_statements.rb +2 -0
  89. data/lib/sequel/dataset/query.rb +114 -11
  90. data/lib/sequel/dataset/sql.rb +172 -46
  91. data/lib/sequel/deprecated.rb +3 -1
  92. data/lib/sequel/exceptions.rb +2 -0
  93. data/lib/sequel/extensions/_pretty_table.rb +1 -2
  94. data/lib/sequel/extensions/any_not_empty.rb +1 -1
  95. data/lib/sequel/extensions/async_thread_pool.rb +438 -0
  96. data/lib/sequel/extensions/blank.rb +8 -0
  97. data/lib/sequel/extensions/columns_introspection.rb +1 -2
  98. data/lib/sequel/extensions/core_refinements.rb +38 -11
  99. data/lib/sequel/extensions/date_arithmetic.rb +36 -24
  100. data/lib/sequel/extensions/date_parse_input_handler.rb +67 -0
  101. data/lib/sequel/extensions/datetime_parse_to_time.rb +5 -1
  102. data/lib/sequel/extensions/duplicate_columns_handler.rb +3 -1
  103. data/lib/sequel/extensions/eval_inspect.rb +2 -0
  104. data/lib/sequel/extensions/inflector.rb +9 -1
  105. data/lib/sequel/extensions/is_distinct_from.rb +139 -0
  106. data/lib/sequel/extensions/migration.rb +13 -2
  107. data/lib/sequel/extensions/named_timezones.rb +5 -1
  108. data/lib/sequel/extensions/pagination.rb +1 -1
  109. data/lib/sequel/extensions/pg_array.rb +1 -0
  110. data/lib/sequel/extensions/pg_array_ops.rb +6 -2
  111. data/lib/sequel/extensions/pg_enum.rb +3 -1
  112. data/lib/sequel/extensions/pg_extended_date_support.rb +2 -2
  113. data/lib/sequel/extensions/pg_hstore.rb +1 -1
  114. data/lib/sequel/extensions/pg_hstore_ops.rb +55 -3
  115. data/lib/sequel/extensions/pg_inet.rb +2 -0
  116. data/lib/sequel/extensions/pg_inet_ops.rb +1 -1
  117. data/lib/sequel/extensions/pg_interval.rb +35 -8
  118. data/lib/sequel/extensions/pg_json.rb +3 -5
  119. data/lib/sequel/extensions/pg_json_ops.rb +119 -4
  120. data/lib/sequel/extensions/pg_loose_count.rb +3 -1
  121. data/lib/sequel/extensions/pg_multirange.rb +372 -0
  122. data/lib/sequel/extensions/pg_range.rb +7 -19
  123. data/lib/sequel/extensions/pg_range_ops.rb +39 -9
  124. data/lib/sequel/extensions/pg_row.rb +1 -1
  125. data/lib/sequel/extensions/pg_row_ops.rb +25 -1
  126. data/lib/sequel/extensions/query.rb +3 -0
  127. data/lib/sequel/extensions/run_transaction_hooks.rb +1 -1
  128. data/lib/sequel/extensions/s.rb +4 -1
  129. data/lib/sequel/extensions/schema_dumper.rb +16 -5
  130. data/lib/sequel/extensions/server_block.rb +8 -12
  131. data/lib/sequel/extensions/sql_comments.rb +110 -3
  132. data/lib/sequel/extensions/sql_log_normalizer.rb +108 -0
  133. data/lib/sequel/extensions/sqlite_json_ops.rb +255 -0
  134. data/lib/sequel/extensions/string_agg.rb +1 -1
  135. data/lib/sequel/extensions/string_date_time.rb +19 -23
  136. data/lib/sequel/extensions/symbol_aref_refinement.rb +2 -0
  137. data/lib/sequel/extensions/symbol_as_refinement.rb +2 -0
  138. data/lib/sequel/extensions/to_dot.rb +9 -3
  139. data/lib/sequel/model/associations.rb +342 -114
  140. data/lib/sequel/model/base.rb +45 -24
  141. data/lib/sequel/model/errors.rb +10 -1
  142. data/lib/sequel/model/inflections.rb +1 -1
  143. data/lib/sequel/model/plugins.rb +8 -3
  144. data/lib/sequel/model.rb +3 -1
  145. data/lib/sequel/plugins/association_pks.rb +60 -18
  146. data/lib/sequel/plugins/association_proxies.rb +3 -0
  147. data/lib/sequel/plugins/async_thread_pool.rb +39 -0
  148. data/lib/sequel/plugins/auto_restrict_eager_graph.rb +62 -0
  149. data/lib/sequel/plugins/auto_validations.rb +39 -5
  150. data/lib/sequel/plugins/auto_validations_constraint_validations_presence_message.rb +68 -0
  151. data/lib/sequel/plugins/blacklist_security.rb +1 -2
  152. data/lib/sequel/plugins/class_table_inheritance.rb +3 -8
  153. data/lib/sequel/plugins/column_encryption.rb +728 -0
  154. data/lib/sequel/plugins/composition.rb +8 -2
  155. data/lib/sequel/plugins/concurrent_eager_loading.rb +174 -0
  156. data/lib/sequel/plugins/constraint_validations.rb +2 -1
  157. data/lib/sequel/plugins/csv_serializer.rb +2 -0
  158. data/lib/sequel/plugins/dataset_associations.rb +4 -1
  159. data/lib/sequel/plugins/dirty.rb +44 -0
  160. data/lib/sequel/plugins/enum.rb +124 -0
  161. data/lib/sequel/plugins/forbid_lazy_load.rb +2 -0
  162. data/lib/sequel/plugins/insert_conflict.rb +4 -0
  163. data/lib/sequel/plugins/instance_specific_default.rb +113 -0
  164. data/lib/sequel/plugins/json_serializer.rb +39 -24
  165. data/lib/sequel/plugins/lazy_attributes.rb +4 -1
  166. data/lib/sequel/plugins/many_through_many.rb +108 -9
  167. data/lib/sequel/plugins/nested_attributes.rb +8 -3
  168. data/lib/sequel/plugins/pg_array_associations.rb +58 -41
  169. data/lib/sequel/plugins/pg_auto_constraint_validations.rb +2 -0
  170. data/lib/sequel/plugins/prepared_statements.rb +15 -12
  171. data/lib/sequel/plugins/prepared_statements_safe.rb +1 -3
  172. data/lib/sequel/plugins/rcte_tree.rb +37 -35
  173. data/lib/sequel/plugins/serialization.rb +9 -3
  174. data/lib/sequel/plugins/serialization_modification_detection.rb +2 -1
  175. data/lib/sequel/plugins/single_table_inheritance.rb +7 -0
  176. data/lib/sequel/plugins/sql_comments.rb +189 -0
  177. data/lib/sequel/plugins/static_cache.rb +1 -1
  178. data/lib/sequel/plugins/string_stripper.rb +1 -1
  179. data/lib/sequel/plugins/subclasses.rb +28 -11
  180. data/lib/sequel/plugins/tactical_eager_loading.rb +8 -2
  181. data/lib/sequel/plugins/timestamps.rb +1 -1
  182. data/lib/sequel/plugins/tree.rb +9 -4
  183. data/lib/sequel/plugins/unused_associations.rb +521 -0
  184. data/lib/sequel/plugins/update_or_create.rb +1 -1
  185. data/lib/sequel/plugins/validation_class_methods.rb +5 -1
  186. data/lib/sequel/plugins/validation_helpers.rb +18 -11
  187. data/lib/sequel/plugins/xml_serializer.rb +1 -1
  188. data/lib/sequel/sql.rb +1 -1
  189. data/lib/sequel/timezones.rb +20 -17
  190. data/lib/sequel/version.rb +1 -1
  191. metadata +93 -39
@@ -491,6 +491,11 @@ module Sequel
491
491
  # the module using a the camelized plugin name under Sequel::Plugins.
492
492
  def plugin(plugin, *args, &block)
493
493
  m = plugin.is_a?(Module) ? plugin : plugin_module(plugin)
494
+
495
+ if !m.respond_to?(:apply) && !m.respond_to?(:configure) && (!args.empty? || block)
496
+ Deprecation.deprecate("Plugin #{plugin} accepts no arguments or block, and passing arguments/block to it", "Remove arguments and block when loading the plugin")
497
+ end
498
+
494
499
  unless @plugins.include?(m)
495
500
  @plugins << m
496
501
  m.apply(self, *args, &block) if m.respond_to?(:apply)
@@ -500,8 +505,12 @@ module Sequel
500
505
  dataset_extend(m::DatasetMethods, :create_class_methods=>false)
501
506
  end
502
507
  end
508
+
503
509
  m.configure(self, *args, &block) if m.respond_to?(:configure)
504
510
  end
511
+ # :nocov:
512
+ ruby2_keywords(:plugin) if respond_to?(:ruby2_keywords, true)
513
+ # :nocov:
505
514
 
506
515
  # Returns primary key attribute hash. If using a composite primary key
507
516
  # value such be an array with values for each primary key in the correct
@@ -593,7 +602,7 @@ module Sequel
593
602
  @columns = superclass.instance_variable_get(:@columns)
594
603
  @db_schema = superclass.instance_variable_get(:@db_schema)
595
604
  else
596
- @dataset = @dataset.with_extend(*@dataset_method_modules.reverse) if @dataset_method_modules
605
+ @dataset = @dataset.with_extend(*@dataset_method_modules.reverse)
597
606
  @db_schema = get_db_schema
598
607
  end
599
608
 
@@ -632,8 +641,7 @@ module Sequel
632
641
 
633
642
  # Cache of setter methods to allow by default, in order to speed up mass assignment.
634
643
  def setter_methods
635
- return @setter_methods if @setter_methods
636
- @setter_methods = get_setter_methods
644
+ @setter_methods || (@setter_methods = get_setter_methods)
637
645
  end
638
646
 
639
647
  # Returns name of primary table for the dataset. If the table for the dataset
@@ -674,13 +682,11 @@ module Sequel
674
682
 
675
683
  # Yield to the passed block and if do_raise is false, swallow all errors other than DatabaseConnectionErrors.
676
684
  def check_non_connection_error(do_raise=require_valid_table)
677
- begin
678
- db.transaction(:savepoint=>:only){yield}
679
- rescue Sequel::DatabaseConnectionError
680
- raise
681
- rescue Sequel::Error
682
- raise if do_raise
683
- end
685
+ db.transaction(:savepoint=>:only){yield}
686
+ rescue Sequel::DatabaseConnectionError
687
+ raise
688
+ rescue Sequel::Error
689
+ raise if do_raise
684
690
  end
685
691
 
686
692
  # Convert the given object to a Dataset that should be used as
@@ -721,8 +727,14 @@ module Sequel
721
727
  im = instance_methods
722
728
  overridable_methods_module.module_eval do
723
729
  meth = :"#{column}="
724
- define_method(column){self[column]} unless im.include?(column)
725
- define_method(meth){|v| self[column] = v} unless im.include?(meth)
730
+ unless im.include?(column)
731
+ define_method(column){self[column]}
732
+ alias_method(column, column)
733
+ end
734
+ unless im.include?(meth)
735
+ define_method(meth){|v| self[column] = v}
736
+ alias_method(meth, meth)
737
+ end
726
738
  end
727
739
  end
728
740
 
@@ -735,8 +747,14 @@ module Sequel
735
747
  im = instance_methods
736
748
  columns.each do |column|
737
749
  meth = :"#{column}="
738
- overridable_methods_module.module_eval("def #{column}; self[:#{column}] end", __FILE__, __LINE__) unless im.include?(column)
739
- overridable_methods_module.module_eval("def #{meth}(v); self[:#{column}] = v end", __FILE__, __LINE__) unless im.include?(meth)
750
+ unless im.include?(column)
751
+ overridable_methods_module.module_eval("def #{column}; self[:#{column}] end", __FILE__, __LINE__)
752
+ overridable_methods_module.send(:alias_method, column, column)
753
+ end
754
+ unless im.include?(meth)
755
+ overridable_methods_module.module_eval("def #{meth}(v); self[:#{column}] = v end", __FILE__, __LINE__)
756
+ overridable_methods_module.send(:alias_method, meth, meth)
757
+ end
740
758
  end
741
759
  end
742
760
 
@@ -751,6 +769,10 @@ module Sequel
751
769
  else
752
770
  define_singleton_method(meth){|*args, &block| dataset.public_send(meth, *args, &block)}
753
771
  end
772
+ singleton_class.send(:alias_method, meth, meth)
773
+ # :nocov:
774
+ singleton_class.send(:ruby2_keywords, meth) if respond_to?(:ruby2_keywords, true)
775
+ # :nocov:
754
776
  end
755
777
 
756
778
  # Get the schema from the database, fall back on checking the columns
@@ -820,7 +842,6 @@ module Sequel
820
842
  super
821
843
  ivs = subclass.instance_variables
822
844
  inherited_instance_variables.each do |iv, dup|
823
- next if ivs.include?(iv)
824
845
  if (sup_class_value = instance_variable_get(iv)) && dup
825
846
  sup_class_value = case dup
826
847
  when :dup
@@ -1070,7 +1091,7 @@ module Sequel
1070
1091
  @modified = true
1071
1092
  initialize_set(values)
1072
1093
  _clear_changed_columns(:initialize)
1073
- yield self if block_given?
1094
+ yield self if defined?(yield)
1074
1095
  end
1075
1096
 
1076
1097
  # Returns value of the column's attribute.
@@ -1116,7 +1137,7 @@ module Sequel
1116
1137
  when nil
1117
1138
  return false
1118
1139
  when Array
1119
- return false if pk.any?(&:nil?)
1140
+ return false if pkv.any?(&:nil?)
1120
1141
  end
1121
1142
 
1122
1143
  (obj.class == model) && (obj.pk == pkv)
@@ -1237,12 +1258,12 @@ module Sequel
1237
1258
  # Once an object is frozen, you cannot modify it's values, changed_columns,
1238
1259
  # errors, or dataset.
1239
1260
  def freeze
1240
- values.freeze
1241
- _changed_columns.freeze
1242
1261
  unless errors.frozen?
1243
1262
  validate
1244
1263
  errors.freeze
1245
1264
  end
1265
+ values.freeze
1266
+ _changed_columns.freeze
1246
1267
  this if !new? && model.primary_key
1247
1268
  super
1248
1269
  end
@@ -1607,11 +1628,9 @@ module Sequel
1607
1628
  # artist.set(name: 'Invalid').valid? # => false
1608
1629
  # artist.errors.full_messages # => ['name cannot be Invalid']
1609
1630
  def valid?(opts = OPTS)
1610
- begin
1611
- _valid?(opts)
1612
- rescue HookFailed
1613
- false
1614
- end
1631
+ _valid?(opts)
1632
+ rescue HookFailed
1633
+ false
1615
1634
  end
1616
1635
 
1617
1636
  private
@@ -2232,7 +2251,9 @@ module Sequel
2232
2251
  plugin self
2233
2252
 
2234
2253
  singleton_class.send(:undef_method, :dup, :clone, :initialize_copy)
2254
+ # :nocov:
2235
2255
  if RUBY_VERSION >= '1.9.3'
2256
+ # :nocov:
2236
2257
  singleton_class.send(:undef_method, :initialize_clone, :initialize_dup)
2237
2258
  end
2238
2259
  end
@@ -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
@@ -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
 
@@ -31,6 +31,9 @@ module Sequel
31
31
  def self.def_dataset_methods(mod, meths)
32
32
  Array(meths).each do |meth|
33
33
  mod.class_eval("def #{meth}(*args, &block); dataset.#{meth}(*args, &block) end", __FILE__, __LINE__)
34
+ # :nocov:
35
+ mod.send(:ruby2_keywords, meth) if respond_to?(:ruby2_keywords, true)
36
+ # :nocov:
34
37
  end
35
38
  end
36
39
 
@@ -119,6 +122,7 @@ module Sequel
119
122
 
120
123
  model.send(:define_method, meth, &block)
121
124
  model.send(:private, meth)
125
+ model.send(:alias_method, meth, meth)
122
126
  call_meth
123
127
  end
124
128
 
@@ -140,6 +144,8 @@ module Sequel
140
144
  keyword = :required
141
145
  when :key, :keyrest
142
146
  keyword ||= true
147
+ else
148
+ raise Error, "invalid arg_type passed to _define_sequel_method_arg_numbers: #{arg_type}"
143
149
  end
144
150
  end
145
151
  arity = callable.arity
@@ -149,9 +155,8 @@ module Sequel
149
155
  required_args = arity
150
156
  arity -= 1 if keyword == :required
151
157
 
152
- if callable.is_a?(Proc) && !callable.lambda?
153
- optional_args -= arity
154
- end
158
+ # callable currently is always a non-lambda Proc
159
+ optional_args -= arity
155
160
 
156
161
  [required_args, optional_args, rest, keyword]
157
162
  end
data/lib/sequel/model.rb CHANGED
@@ -69,7 +69,9 @@ module Sequel
69
69
  require_relative "model/base"
70
70
  require_relative "model/exceptions"
71
71
  require_relative "model/errors"
72
+ # :nocov:
72
73
  if !defined?(::SEQUEL_NO_ASSOCIATIONS) && !ENV.has_key?('SEQUEL_NO_ASSOCIATIONS')
74
+ # :nocov:
73
75
  require_relative 'model/associations'
74
76
  plugin Model::Associations
75
77
  end
@@ -77,7 +79,7 @@ module Sequel
77
79
  def_Model(::Sequel)
78
80
 
79
81
  # The setter methods (methods ending with =) that are never allowed
80
- # to be called automatically via +set+/+update+/+new+/etc..
82
+ # to be called automatically via +set+/+update+/+new+/etc.
81
83
  RESTRICTED_SETTER_METHODS = instance_methods.map(&:to_s).select{|l| l.end_with?('=')}.freeze
82
84
  end
83
85
  end
@@ -2,13 +2,17 @@
2
2
 
3
3
  module Sequel
4
4
  module Plugins
5
- # The association_pks plugin adds association_pks and association_pks=
6
- # instance methods to the model class for each association added. These
7
- # methods allow for easily returning the primary keys of the associated
8
- # objects, and easily modifying which objects are associated:
5
+ # The association_pks plugin adds association_pks, association_pks=, and
6
+ # association_pks_dataset instance methods to the model class for each
7
+ # one_to_many and many_to_many association added. These methods allow for
8
+ # easily returning the primary keys of the associated objects, and easily
9
+ # modifying which objects are associated:
9
10
  #
10
11
  # Artist.one_to_many :albums
11
12
  # artist = Artist[1]
13
+ # artist.album_pks_dataset
14
+ # # SELECT id FROM albums WHERE (albums.artist_id = 1)
15
+ #
12
16
  # artist.album_pks # [1, 2, 3]
13
17
  # artist.album_pks = [2, 4]
14
18
  # artist.album_pks # [2, 4]
@@ -22,11 +26,18 @@ module Sequel
22
26
  # This plugin makes modifications directly to the underlying tables,
23
27
  # it does not create or return any model objects, and therefore does
24
28
  # not call any callbacks. If you have any association callbacks,
25
- # you probably should not use the setter methods.
29
+ # you probably should not use the setter methods this plugin adds.
26
30
  #
27
31
  # By default, changes to the association will not happen until the object
28
- # is saved. However, using the delay_pks: false option, you can have the
29
- # changes made immediately when the association_pks setter method is called.
32
+ # is saved. However, using the delay_pks: false association option, you can have
33
+ # the changes made immediately when the association_pks setter method is called.
34
+ #
35
+ # By default, repeated calls to the association_pks getter method will not be
36
+ # cached, unless the setter method has been used and the delay_pks: false
37
+ # association option is not used. You can set caching of repeated calls to the
38
+ # association_pks getter method using the :cache_pks association option. You can
39
+ # pass the :refresh option when calling the getter method to ignore any existing
40
+ # cached values, similar to how the :refresh option works with associations.
30
41
  #
31
42
  # By default, if you pass a nil value to the setter, an exception will be raised.
32
43
  # You can change this behavior by using the :association_pks_nil association option.
@@ -60,9 +71,11 @@ module Sequel
60
71
 
61
72
  # Define a association_pks method using the block for the association reflection
62
73
  def def_association_pks_methods(opts)
74
+ association_module_def(opts[:pks_dataset_method], &opts[:pks_dataset])
75
+
63
76
  opts[:pks_getter_method] = :"#{singularize(opts[:name])}_pks_getter"
64
77
  association_module_def(opts[:pks_getter_method], &opts[:pks_getter])
65
- association_module_def(:"#{singularize(opts[:name])}_pks", opts){_association_pks_getter(opts)}
78
+ association_module_def(:"#{singularize(opts[:name])}_pks", opts){|dynamic_opts=OPTS| _association_pks_getter(opts, dynamic_opts)}
66
79
 
67
80
  if opts[:pks_setter]
68
81
  opts[:pks_setter_method] = :"#{singularize(opts[:name])}_pks_setter"
@@ -84,7 +97,9 @@ module Sequel
84
97
  clpk = lpk.is_a?(Array)
85
98
  crk = rk.is_a?(Array)
86
99
 
87
- opts[:pks_getter] = if join_associated_table = opts[:association_pks_use_associated_table]
100
+ dataset_method = opts[:pks_dataset_method] = :"#{singularize(opts[:name])}_pks_dataset"
101
+
102
+ opts[:pks_dataset] = if join_associated_table = opts[:association_pks_use_associated_table]
88
103
  tname = opts[:join_table]
89
104
  lambda do
90
105
  cond = if clpk
@@ -95,16 +110,26 @@ module Sequel
95
110
  rpk = opts.associated_class.primary_key
96
111
  opts.associated_dataset.
97
112
  naked.where(cond).
98
- select_map(Sequel.public_send(rpk.is_a?(Array) ? :deep_qualify : :qualify, opts.associated_class.table_name, rpk))
113
+ select(*Sequel.public_send(rpk.is_a?(Array) ? :deep_qualify : :qualify, opts.associated_class.table_name, rpk))
99
114
  end
100
115
  elsif clpk
101
116
  lambda do
102
117
  cond = lk.zip(lpk).map{|k, pk| [k, get_column_value(pk)]}
103
- _join_table_dataset(opts).where(cond).select_map(rk)
118
+ _join_table_dataset(opts).where(cond).select(*rk)
119
+ end
120
+ else
121
+ lambda do
122
+ _join_table_dataset(opts).where(lk=>get_column_value(lpk)).select(*rk)
123
+ end
124
+ end
125
+
126
+ opts[:pks_getter] = if join_associated_table = opts[:association_pks_use_associated_table]
127
+ lambda do
128
+ public_send(dataset_method).map(opts.associated_class.primary_key)
104
129
  end
105
130
  else
106
131
  lambda do
107
- _join_table_dataset(opts).where(lk=>get_column_value(lpk)).select_map(rk)
132
+ public_send(dataset_method).map(rk)
108
133
  end
109
134
  end
110
135
 
@@ -145,8 +170,14 @@ module Sequel
145
170
 
146
171
  key = opts[:key]
147
172
 
173
+ dataset_method = opts[:pks_dataset_method] = :"#{singularize(opts[:name])}_pks_dataset"
174
+
175
+ opts[:pks_dataset] = lambda do
176
+ public_send(opts[:dataset_method]).select(*opts.associated_class.primary_key)
177
+ end
178
+
148
179
  opts[:pks_getter] = lambda do
149
- public_send(opts[:dataset_method]).select_map(opts.associated_class.primary_key)
180
+ public_send(dataset_method).map(opts.associated_class.primary_key)
150
181
  end
151
182
 
152
183
  unless opts[:read_only]
@@ -207,12 +238,22 @@ module Sequel
207
238
  # Return the primary keys of the associated objects.
208
239
  # If the receiver is a new object, return any saved
209
240
  # pks, or an empty array if no pks have been saved.
210
- def _association_pks_getter(opts)
241
+ def _association_pks_getter(opts, dynamic_opts=OPTS)
242
+ do_cache = opts[:cache_pks]
211
243
  delay = opts.fetch(:delay_pks, true)
212
- if new? && delay
244
+ cache_or_delay = do_cache || delay
245
+
246
+ if dynamic_opts[:refresh] && @_association_pks
247
+ @_association_pks.delete(opts[:name])
248
+ end
249
+
250
+ if new? && cache_or_delay
213
251
  (@_association_pks ||= {})[opts[:name]] ||= []
214
- elsif delay && @_association_pks && (objs = @_association_pks[opts[:name]])
252
+ elsif cache_or_delay && @_association_pks && (objs = @_association_pks[opts[:name]])
215
253
  objs
254
+ elsif do_cache
255
+ # pks_getter_method is private
256
+ (@_association_pks ||= {})[opts[:name]] = send(opts[:pks_getter_method])
216
257
  else
217
258
  # pks_getter_method is private
218
259
  send(opts[:pks_getter_method])
@@ -254,9 +295,10 @@ module Sequel
254
295
 
255
296
  if primary_key.is_a?(Array)
256
297
  if (cols = sch.values_at(*klass.primary_key)).all? && (convs = cols.map{|c| c[:type] == :integer}).all?
298
+ db = model.db
257
299
  pks.map do |cpk|
258
- cpk.zip(convs).map do |pk, conv|
259
- conv ? model.db.typecast_value(:integer, pk) : pk
300
+ cpk.map do |pk|
301
+ db.typecast_value(:integer, pk)
260
302
  end
261
303
  end
262
304
  else
@@ -99,6 +99,9 @@ module Sequel
99
99
  end
100
100
  v.public_send(meth, *args, &block)
101
101
  end
102
+ # :nocov:
103
+ ruby2_keywords(:method_missing) if respond_to?(:ruby2_keywords, true)
104
+ # :nocov:
102
105
  end
103
106
 
104
107
  module ClassMethods
@@ -0,0 +1,39 @@
1
+ # frozen-string-literal: true
2
+
3
+ module Sequel
4
+ extension 'async_thread_pool'
5
+
6
+ module Plugins
7
+ # The async_thread_pool plugin makes it slightly easier to use the async_thread_pool
8
+ # Database extension with models. It makes Model.async return an async dataset for the
9
+ # model, and support async behavior for #destroy, #with_pk, and #with_pk! for model
10
+ # datasets:
11
+ #
12
+ # # Will load the artist with primary key 1 asynchronously
13
+ # artist = Artist.async.with_pk(1)
14
+ #
15
+ # You must load the async_thread_pool Database extension into the Database object the
16
+ # model class uses in order for async behavior to work.
17
+ #
18
+ # Usage:
19
+ #
20
+ # # Make all model subclass datasets support support async class methods and additional
21
+ # # async dataset methods
22
+ # Sequel::Model.plugin :async_thread_pool
23
+ #
24
+ # # Make the Album class support async class method and additional async dataset methods
25
+ # Album.plugin :async_thread_pool
26
+ module AsyncThreadPool
27
+ module ClassMethods
28
+ Plugins.def_dataset_methods(self, :async)
29
+ end
30
+
31
+ module DatasetMethods
32
+ [:destroy, :with_pk, :with_pk!].each do |meth|
33
+ ::Sequel::Database::AsyncThreadPool::DatasetMethods.define_async_method(self, meth)
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+
@@ -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
@@ -14,7 +14,9 @@ module Sequel
14
14
  # the plugin looks at the database schema for the model's table. To determine
15
15
  # the unique validations, Sequel looks at the indexes on the table. In order
16
16
  # for this plugin to be fully functional, the underlying database adapter needs
17
- # to support both schema and index parsing.
17
+ # to support both schema and index parsing. Additionally, unique validations are
18
+ # 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.
18
20
  #
19
21
  # This plugin uses the validation_helpers plugin underneath to implement the
20
22
  # validations. It does not allow for any per-column validation message
@@ -51,6 +53,11 @@ module Sequel
51
53
  # This works for unique_opts, max_length_opts, schema_types_opts,
52
54
  # explicit_not_null_opts, and not_null_opts.
53
55
  #
56
+ # If you only want auto_validations to add validations to columns that do not already
57
+ # have an error associated with them, you can use the skip_invalid option:
58
+ #
59
+ # Model.plugin :auto_validations, skip_invalid: true
60
+ #
54
61
  # Usage:
55
62
  #
56
63
  # # Make all model subclass use auto validations (called before loading subclasses)
@@ -64,12 +71,14 @@ module Sequel
64
71
  MAX_LENGTH_OPTIONS = {:from=>:values, :allow_nil=>true}.freeze
65
72
  SCHEMA_TYPES_OPTIONS = NOT_NULL_OPTIONS
66
73
  UNIQUE_OPTIONS = NOT_NULL_OPTIONS
74
+ NO_NULL_BYTE_OPTIONS = MAX_LENGTH_OPTIONS
67
75
  EMPTY_ARRAY = [].freeze
68
76
 
69
77
  def self.apply(model, opts=OPTS)
70
78
  model.instance_exec do
71
79
  plugin :validation_helpers
72
80
  @auto_validate_presence = false
81
+ @auto_validate_no_null_byte_columns = []
73
82
  @auto_validate_not_null_columns = []
74
83
  @auto_validate_explicit_not_null_columns = []
75
84
  @auto_validate_max_length_columns = []
@@ -77,6 +86,7 @@ module Sequel
77
86
  @auto_validate_types = true
78
87
 
79
88
  @auto_validate_options = {
89
+ :no_null_byte=>NO_NULL_BYTE_OPTIONS,
80
90
  :not_null=>NOT_NULL_OPTIONS,
81
91
  :explicit_not_null=>EXPLICIT_NOT_NULL_OPTIONS,
82
92
  :max_length=>MAX_LENGTH_OPTIONS,
@@ -95,16 +105,26 @@ module Sequel
95
105
  end
96
106
 
97
107
  h = @auto_validate_options.dup
98
- [:not_null, :explicit_not_null, :max_length, :schema_types, :unique].each do |type|
108
+ [:not_null, :explicit_not_null, :max_length, :no_null_byte, :schema_types, :unique].each do |type|
99
109
  if type_opts = opts[:"#{type}_opts"]
100
110
  h[type] = h[type].merge(type_opts).freeze
101
111
  end
102
112
  end
113
+
114
+ if opts[:skip_invalid]
115
+ [:not_null, :explicit_not_null, :no_null_byte, :max_length, :schema_types].each do |type|
116
+ h[type] = h[type].merge(:skip_invalid=>true).freeze
117
+ end
118
+ end
119
+
103
120
  @auto_validate_options = h.freeze
104
121
  end
105
122
  end
106
123
 
107
124
  module ClassMethods
125
+ # The columns with automatic no_null_byte validations
126
+ attr_reader :auto_validate_no_null_byte_columns
127
+
108
128
  # The columns with automatic not_null validations
109
129
  attr_reader :auto_validate_not_null_columns
110
130
 
@@ -121,7 +141,15 @@ module Sequel
121
141
  # Inherited options
122
142
  attr_reader :auto_validate_options
123
143
 
124
- 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)
144
+ Plugins.inherited_instance_variables(self,
145
+ :@auto_validate_presence=>nil,
146
+ :@auto_validate_types=>nil,
147
+ :@auto_validate_no_null_byte_columns=>:dup,
148
+ :@auto_validate_not_null_columns=>:dup,
149
+ :@auto_validate_explicit_not_null_columns=>:dup,
150
+ :@auto_validate_max_length_columns=>:dup,
151
+ :@auto_validate_unique_columns=>:dup,
152
+ :@auto_validate_options => :dup)
125
153
  Plugins.after_set_dataset(self, :setup_auto_validations)
126
154
 
127
155
  # Whether to use a presence validation for not null columns
@@ -136,6 +164,7 @@ module Sequel
136
164
 
137
165
  # Freeze auto_validation settings when freezing model class.
138
166
  def freeze
167
+ @auto_validate_no_null_byte_columns.freeze
139
168
  @auto_validate_not_null_columns.freeze
140
169
  @auto_validate_explicit_not_null_columns.freeze
141
170
  @auto_validate_max_length_columns.freeze
@@ -144,12 +173,13 @@ module Sequel
144
173
  super
145
174
  end
146
175
 
147
- # Skip automatic validations for the given validation type (:not_null, :types, :unique).
176
+ # Skip automatic validations for the given validation type
177
+ # (:not_null, :types, :unique, :max_length, :no_null_byte).
148
178
  # If :all is given as the type, skip all auto validations.
149
179
  def skip_auto_validations(type)
150
180
  case type
151
181
  when :all
152
- [:not_null, :types, :unique, :max_length].each{|v| skip_auto_validations(v)}
182
+ [:not_null, :no_null_byte, :types, :unique, :max_length].each{|v| skip_auto_validations(v)}
153
183
  when :not_null
154
184
  auto_validate_not_null_columns.clear
155
185
  auto_validate_explicit_not_null_columns.clear
@@ -169,6 +199,7 @@ module Sequel
169
199
  explicit_not_null_cols += Array(primary_key)
170
200
  @auto_validate_explicit_not_null_columns = explicit_not_null_cols.uniq
171
201
  @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]]}
202
+ @auto_validate_no_null_byte_columns = db_schema.select{|_, sch| sch[:type] == :string}.map{|col, _| col}
172
203
  table = dataset.first_source_table
173
204
  @auto_validate_unique_columns = if db.supports_index_parsing? && [Symbol, SQL::QualifiedIdentifier, SQL::Identifier, String].any?{|c| table.is_a?(c)}
174
205
  db.indexes(table).select{|name, idx| idx[:unique] == true}.map{|name, idx| idx[:columns].length == 1 ? idx[:columns].first : idx[:columns]}
@@ -195,6 +226,9 @@ module Sequel
195
226
  return if skip.include?(:all)
196
227
  opts = model.auto_validate_options
197
228
 
229
+ unless skip.include?(:no_null_byte) || (no_null_byte_columns = model.auto_validate_no_null_byte_columns).empty?
230
+ validates_no_null_byte(no_null_byte_columns, opts[:no_null_byte])
231
+ end
198
232
 
199
233
  unless skip.include?(:not_null)
200
234
  not_null_method = model.auto_validate_presence? ? :validates_presence : :validates_not_null