sequel 5.39.0 → 5.72.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 (219) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +408 -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 +13 -6
  11. data/doc/model_hooks.rdoc +1 -1
  12. data/doc/object_model.rdoc +8 -8
  13. data/doc/opening_databases.rdoc +26 -12
  14. data/doc/postgresql.rdoc +16 -8
  15. data/doc/querying.rdoc +5 -3
  16. data/doc/release_notes/5.40.0.txt +40 -0
  17. data/doc/release_notes/5.41.0.txt +25 -0
  18. data/doc/release_notes/5.42.0.txt +136 -0
  19. data/doc/release_notes/5.43.0.txt +98 -0
  20. data/doc/release_notes/5.44.0.txt +32 -0
  21. data/doc/release_notes/5.45.0.txt +34 -0
  22. data/doc/release_notes/5.46.0.txt +87 -0
  23. data/doc/release_notes/5.47.0.txt +59 -0
  24. data/doc/release_notes/5.48.0.txt +14 -0
  25. data/doc/release_notes/5.49.0.txt +59 -0
  26. data/doc/release_notes/5.50.0.txt +78 -0
  27. data/doc/release_notes/5.51.0.txt +47 -0
  28. data/doc/release_notes/5.52.0.txt +87 -0
  29. data/doc/release_notes/5.53.0.txt +23 -0
  30. data/doc/release_notes/5.54.0.txt +27 -0
  31. data/doc/release_notes/5.55.0.txt +21 -0
  32. data/doc/release_notes/5.56.0.txt +51 -0
  33. data/doc/release_notes/5.57.0.txt +23 -0
  34. data/doc/release_notes/5.58.0.txt +31 -0
  35. data/doc/release_notes/5.59.0.txt +73 -0
  36. data/doc/release_notes/5.60.0.txt +22 -0
  37. data/doc/release_notes/5.61.0.txt +43 -0
  38. data/doc/release_notes/5.62.0.txt +132 -0
  39. data/doc/release_notes/5.63.0.txt +33 -0
  40. data/doc/release_notes/5.64.0.txt +50 -0
  41. data/doc/release_notes/5.65.0.txt +21 -0
  42. data/doc/release_notes/5.66.0.txt +24 -0
  43. data/doc/release_notes/5.67.0.txt +32 -0
  44. data/doc/release_notes/5.68.0.txt +61 -0
  45. data/doc/release_notes/5.69.0.txt +26 -0
  46. data/doc/release_notes/5.70.0.txt +35 -0
  47. data/doc/release_notes/5.71.0.txt +21 -0
  48. data/doc/release_notes/5.72.0.txt +33 -0
  49. data/doc/schema_modification.rdoc +1 -1
  50. data/doc/security.rdoc +9 -9
  51. data/doc/sharding.rdoc +3 -1
  52. data/doc/sql.rdoc +28 -16
  53. data/doc/testing.rdoc +22 -11
  54. data/doc/transactions.rdoc +6 -6
  55. data/doc/virtual_rows.rdoc +2 -2
  56. data/lib/sequel/adapters/ado/access.rb +1 -1
  57. data/lib/sequel/adapters/ado.rb +17 -17
  58. data/lib/sequel/adapters/amalgalite.rb +3 -5
  59. data/lib/sequel/adapters/ibmdb.rb +2 -2
  60. data/lib/sequel/adapters/jdbc/derby.rb +8 -0
  61. data/lib/sequel/adapters/jdbc/h2.rb +60 -10
  62. data/lib/sequel/adapters/jdbc/hsqldb.rb +6 -0
  63. data/lib/sequel/adapters/jdbc/postgresql.rb +7 -4
  64. data/lib/sequel/adapters/jdbc.rb +16 -18
  65. data/lib/sequel/adapters/mysql.rb +92 -67
  66. data/lib/sequel/adapters/mysql2.rb +54 -49
  67. data/lib/sequel/adapters/odbc.rb +6 -2
  68. data/lib/sequel/adapters/oracle.rb +4 -3
  69. data/lib/sequel/adapters/postgres.rb +83 -40
  70. data/lib/sequel/adapters/shared/access.rb +11 -1
  71. data/lib/sequel/adapters/shared/db2.rb +30 -0
  72. data/lib/sequel/adapters/shared/mssql.rb +90 -9
  73. data/lib/sequel/adapters/shared/mysql.rb +47 -2
  74. data/lib/sequel/adapters/shared/oracle.rb +82 -1
  75. data/lib/sequel/adapters/shared/postgres.rb +496 -178
  76. data/lib/sequel/adapters/shared/sqlanywhere.rb +11 -1
  77. data/lib/sequel/adapters/shared/sqlite.rb +116 -11
  78. data/lib/sequel/adapters/sqlanywhere.rb +1 -1
  79. data/lib/sequel/adapters/sqlite.rb +60 -18
  80. data/lib/sequel/adapters/tinytds.rb +1 -1
  81. data/lib/sequel/adapters/trilogy.rb +117 -0
  82. data/lib/sequel/adapters/utils/columns_limit_1.rb +22 -0
  83. data/lib/sequel/adapters/utils/mysql_mysql2.rb +1 -1
  84. data/lib/sequel/ast_transformer.rb +6 -0
  85. data/lib/sequel/connection_pool/sharded_single.rb +5 -7
  86. data/lib/sequel/connection_pool/sharded_threaded.rb +16 -11
  87. data/lib/sequel/connection_pool/sharded_timed_queue.rb +374 -0
  88. data/lib/sequel/connection_pool/single.rb +6 -8
  89. data/lib/sequel/connection_pool/threaded.rb +14 -8
  90. data/lib/sequel/connection_pool/timed_queue.rb +270 -0
  91. data/lib/sequel/connection_pool.rb +55 -31
  92. data/lib/sequel/core.rb +28 -18
  93. data/lib/sequel/database/connecting.rb +27 -3
  94. data/lib/sequel/database/dataset.rb +16 -6
  95. data/lib/sequel/database/misc.rb +69 -14
  96. data/lib/sequel/database/query.rb +73 -2
  97. data/lib/sequel/database/schema_generator.rb +46 -53
  98. data/lib/sequel/database/schema_methods.rb +18 -2
  99. data/lib/sequel/dataset/actions.rb +108 -14
  100. data/lib/sequel/dataset/deprecated_singleton_class_methods.rb +42 -0
  101. data/lib/sequel/dataset/features.rb +20 -0
  102. data/lib/sequel/dataset/misc.rb +12 -2
  103. data/lib/sequel/dataset/placeholder_literalizer.rb +20 -9
  104. data/lib/sequel/dataset/prepared_statements.rb +2 -0
  105. data/lib/sequel/dataset/query.rb +171 -44
  106. data/lib/sequel/dataset/sql.rb +182 -47
  107. data/lib/sequel/dataset.rb +4 -0
  108. data/lib/sequel/extensions/_model_pg_row.rb +0 -12
  109. data/lib/sequel/extensions/_pretty_table.rb +1 -1
  110. data/lib/sequel/extensions/any_not_empty.rb +1 -1
  111. data/lib/sequel/extensions/async_thread_pool.rb +439 -0
  112. data/lib/sequel/extensions/auto_literal_strings.rb +1 -1
  113. data/lib/sequel/extensions/blank.rb +8 -0
  114. data/lib/sequel/extensions/connection_expiration.rb +15 -9
  115. data/lib/sequel/extensions/connection_validator.rb +16 -11
  116. data/lib/sequel/extensions/constraint_validations.rb +1 -1
  117. data/lib/sequel/extensions/core_refinements.rb +36 -11
  118. data/lib/sequel/extensions/date_arithmetic.rb +71 -31
  119. data/lib/sequel/extensions/date_parse_input_handler.rb +67 -0
  120. data/lib/sequel/extensions/datetime_parse_to_time.rb +5 -1
  121. data/lib/sequel/extensions/duplicate_columns_handler.rb +1 -1
  122. data/lib/sequel/extensions/eval_inspect.rb +2 -0
  123. data/lib/sequel/extensions/index_caching.rb +5 -1
  124. data/lib/sequel/extensions/inflector.rb +9 -1
  125. data/lib/sequel/extensions/is_distinct_from.rb +141 -0
  126. data/lib/sequel/extensions/looser_typecasting.rb +3 -0
  127. data/lib/sequel/extensions/migration.rb +11 -2
  128. data/lib/sequel/extensions/named_timezones.rb +26 -6
  129. data/lib/sequel/extensions/pagination.rb +1 -1
  130. data/lib/sequel/extensions/pg_array.rb +32 -4
  131. data/lib/sequel/extensions/pg_array_ops.rb +2 -2
  132. data/lib/sequel/extensions/pg_auto_parameterize.rb +509 -0
  133. data/lib/sequel/extensions/pg_auto_parameterize_in_array.rb +110 -0
  134. data/lib/sequel/extensions/pg_enum.rb +2 -3
  135. data/lib/sequel/extensions/pg_extended_date_support.rb +38 -27
  136. data/lib/sequel/extensions/pg_extended_integer_support.rb +116 -0
  137. data/lib/sequel/extensions/pg_hstore.rb +6 -1
  138. data/lib/sequel/extensions/pg_hstore_ops.rb +53 -3
  139. data/lib/sequel/extensions/pg_inet.rb +10 -11
  140. data/lib/sequel/extensions/pg_inet_ops.rb +1 -1
  141. data/lib/sequel/extensions/pg_interval.rb +45 -19
  142. data/lib/sequel/extensions/pg_json.rb +13 -15
  143. data/lib/sequel/extensions/pg_json_ops.rb +73 -2
  144. data/lib/sequel/extensions/pg_loose_count.rb +3 -1
  145. data/lib/sequel/extensions/pg_multirange.rb +367 -0
  146. data/lib/sequel/extensions/pg_range.rb +11 -24
  147. data/lib/sequel/extensions/pg_range_ops.rb +37 -9
  148. data/lib/sequel/extensions/pg_row.rb +21 -19
  149. data/lib/sequel/extensions/pg_row_ops.rb +1 -1
  150. data/lib/sequel/extensions/query.rb +2 -0
  151. data/lib/sequel/extensions/s.rb +2 -1
  152. data/lib/sequel/extensions/schema_caching.rb +1 -1
  153. data/lib/sequel/extensions/schema_dumper.rb +45 -11
  154. data/lib/sequel/extensions/server_block.rb +10 -13
  155. data/lib/sequel/extensions/set_literalizer.rb +58 -0
  156. data/lib/sequel/extensions/sql_comments.rb +110 -3
  157. data/lib/sequel/extensions/sql_log_normalizer.rb +108 -0
  158. data/lib/sequel/extensions/sqlite_json_ops.rb +255 -0
  159. data/lib/sequel/extensions/string_agg.rb +1 -1
  160. data/lib/sequel/extensions/string_date_time.rb +19 -23
  161. data/lib/sequel/extensions/symbol_aref.rb +2 -0
  162. data/lib/sequel/model/associations.rb +345 -101
  163. data/lib/sequel/model/base.rb +51 -27
  164. data/lib/sequel/model/dataset_module.rb +3 -0
  165. data/lib/sequel/model/errors.rb +10 -1
  166. data/lib/sequel/model/inflections.rb +1 -1
  167. data/lib/sequel/model/plugins.rb +5 -0
  168. data/lib/sequel/plugins/association_proxies.rb +2 -0
  169. data/lib/sequel/plugins/async_thread_pool.rb +39 -0
  170. data/lib/sequel/plugins/auto_restrict_eager_graph.rb +62 -0
  171. data/lib/sequel/plugins/auto_validations.rb +87 -15
  172. data/lib/sequel/plugins/auto_validations_constraint_validations_presence_message.rb +68 -0
  173. data/lib/sequel/plugins/class_table_inheritance.rb +2 -2
  174. data/lib/sequel/plugins/column_encryption.rb +728 -0
  175. data/lib/sequel/plugins/composition.rb +10 -4
  176. data/lib/sequel/plugins/concurrent_eager_loading.rb +174 -0
  177. data/lib/sequel/plugins/constraint_validations.rb +10 -6
  178. data/lib/sequel/plugins/dataset_associations.rb +4 -1
  179. data/lib/sequel/plugins/defaults_setter.rb +16 -0
  180. data/lib/sequel/plugins/dirty.rb +1 -1
  181. data/lib/sequel/plugins/enum.rb +124 -0
  182. data/lib/sequel/plugins/finder.rb +4 -2
  183. data/lib/sequel/plugins/insert_conflict.rb +4 -0
  184. data/lib/sequel/plugins/instance_specific_default.rb +1 -1
  185. data/lib/sequel/plugins/json_serializer.rb +39 -24
  186. data/lib/sequel/plugins/lazy_attributes.rb +3 -0
  187. data/lib/sequel/plugins/list.rb +3 -1
  188. data/lib/sequel/plugins/many_through_many.rb +109 -10
  189. data/lib/sequel/plugins/mssql_optimistic_locking.rb +8 -38
  190. data/lib/sequel/plugins/nested_attributes.rb +12 -7
  191. data/lib/sequel/plugins/optimistic_locking.rb +9 -42
  192. data/lib/sequel/plugins/optimistic_locking_base.rb +55 -0
  193. data/lib/sequel/plugins/pg_array_associations.rb +56 -38
  194. data/lib/sequel/plugins/pg_auto_constraint_validations.rb +11 -3
  195. data/lib/sequel/plugins/pg_xmin_optimistic_locking.rb +109 -0
  196. data/lib/sequel/plugins/prepared_statements.rb +12 -2
  197. data/lib/sequel/plugins/prepared_statements_safe.rb +2 -1
  198. data/lib/sequel/plugins/primary_key_lookup_check_values.rb +154 -0
  199. data/lib/sequel/plugins/rcte_tree.rb +27 -19
  200. data/lib/sequel/plugins/require_valid_schema.rb +67 -0
  201. data/lib/sequel/plugins/serialization.rb +9 -3
  202. data/lib/sequel/plugins/serialization_modification_detection.rb +2 -1
  203. data/lib/sequel/plugins/single_table_inheritance.rb +8 -0
  204. data/lib/sequel/plugins/sql_comments.rb +189 -0
  205. data/lib/sequel/plugins/static_cache.rb +39 -1
  206. data/lib/sequel/plugins/static_cache_cache.rb +5 -1
  207. data/lib/sequel/plugins/subclasses.rb +28 -11
  208. data/lib/sequel/plugins/tactical_eager_loading.rb +23 -10
  209. data/lib/sequel/plugins/timestamps.rb +1 -1
  210. data/lib/sequel/plugins/unused_associations.rb +521 -0
  211. data/lib/sequel/plugins/update_or_create.rb +1 -1
  212. data/lib/sequel/plugins/validate_associated.rb +22 -12
  213. data/lib/sequel/plugins/validation_helpers.rb +46 -12
  214. data/lib/sequel/plugins/validation_helpers_generic_type_messages.rb +73 -0
  215. data/lib/sequel/plugins/xml_serializer.rb +1 -1
  216. data/lib/sequel/sql.rb +1 -1
  217. data/lib/sequel/timezones.rb +12 -14
  218. data/lib/sequel/version.rb +1 -1
  219. metadata +132 -38
@@ -123,16 +123,25 @@ module Sequel
123
123
  nil
124
124
  end
125
125
 
126
+ # Whether a separate query should be used for each join table.
127
+ def separate_query_per_table?
128
+ self[:separate_query_per_table]
129
+ end
130
+
126
131
  private
127
132
 
128
133
  def _associated_dataset
129
134
  ds = associated_class
130
- (reverse_edges + [final_reverse_edge]).each do |t|
131
- h = {:qualify=>:deep}
132
- if t[:alias] != t[:table]
133
- h[:table_alias] = t[:alias]
135
+ if separate_query_per_table?
136
+ ds = ds.dataset
137
+ else
138
+ (reverse_edges + [final_reverse_edge]).each do |t|
139
+ h = {:qualify=>:deep}
140
+ if t[:alias] != t[:table]
141
+ h[:table_alias] = t[:alias]
142
+ end
143
+ ds = ds.join(t[:table], Array(t[:left]).zip(Array(t[:right])), h)
134
144
  end
135
- ds = ds.join(t[:table], Array(t[:left]).zip(Array(t[:right])), h)
136
145
  end
137
146
  ds
138
147
  end
@@ -208,6 +217,7 @@ module Sequel
208
217
  # :right (last array element) :: The key joining the table to the next table. Can use an
209
218
  # array of symbols for a composite key association.
210
219
  # If a hash is provided, the following keys are respected when using eager_graph:
220
+ # :db :: The Database containing the table. This changes lookup to use a separate query for each join table.
211
221
  # :block :: A proc to use as the block argument to join.
212
222
  # :conditions :: Extra conditions to add to the JOIN ON clause. Must be a hash or array of two pairs.
213
223
  # :join_type :: The join type to use for the join, defaults to :left_outer.
@@ -233,32 +243,121 @@ module Sequel
233
243
  opts[:after_load].unshift(:array_uniq!)
234
244
  end
235
245
  opts[:cartesian_product_number] ||= one_through_many ? 0 : 2
236
- opts[:through] = opts[:through].map do |e|
246
+ separate_query_per_table = false
247
+ through = opts[:through] = opts[:through].map do |e|
237
248
  case e
238
249
  when Array
239
250
  raise(Error, "array elements of the through option/argument for many_through_many associations must have at least three elements") unless e.length == 3
240
251
  {:table=>e[0], :left=>e[1], :right=>e[2]}
241
252
  when Hash
242
253
  raise(Error, "hash elements of the through option/argument for many_through_many associations must contain :table, :left, and :right keys") unless e[:table] && e[:left] && e[:right]
254
+ separate_query_per_table = true if e[:db]
243
255
  e
244
256
  else
245
257
  raise(Error, "the through option/argument for many_through_many associations must be an enumerable of arrays or hashes")
246
258
  end
247
259
  end
260
+ opts[:separate_query_per_table] = separate_query_per_table
248
261
 
249
262
  left_key = opts[:left_key] = opts[:through].first[:left]
250
263
  opts[:left_keys] = Array(left_key)
251
- opts[:uses_left_composite_keys] = left_key.is_a?(Array)
264
+ uses_lcks = opts[:uses_left_composite_keys] = left_key.is_a?(Array)
252
265
  left_pk = (opts[:left_primary_key] ||= self.primary_key)
253
266
  raise(Error, "no primary key specified for #{inspect}") unless left_pk
254
267
  opts[:eager_loader_key] = left_pk unless opts.has_key?(:eager_loader_key)
255
268
  opts[:left_primary_keys] = Array(left_pk)
256
269
  lpkc = opts[:left_primary_key_column] ||= left_pk
257
270
  lpkcs = opts[:left_primary_key_columns] ||= Array(lpkc)
258
- opts[:dataset] ||= opts.association_dataset_proc
259
271
 
260
272
  opts[:left_key_alias] ||= opts.default_associated_key_alias
261
- opts[:eager_loader] ||= opts.method(:default_eager_loader)
273
+ if separate_query_per_table
274
+ opts[:use_placeholder_loader] = false
275
+ opts[:allow_eager_graph] = false
276
+ opts[:allow_filtering_by] = false
277
+ opts[:eager_limit_strategy] = nil
278
+
279
+ opts[:dataset] ||= proc do |r|
280
+ def_db = r.associated_class.db
281
+ vals = uses_lcks ? [lpkcs.map{|k| get_column_value(k)}] : get_column_value(left_pk)
282
+
283
+ has_results = through.each do |edge|
284
+ ds = (edge[:db] || def_db).from(edge[:table]).where(edge[:left]=>vals)
285
+ ds = ds.where(edge[:conditions]) if edge[:conditions]
286
+ right = edge[:right]
287
+ vals = ds.select_map(right)
288
+ if right.is_a?(Array)
289
+ vals.delete_if{|v| v.any?(&:nil?)}
290
+ else
291
+ vals.delete(nil)
292
+ end
293
+ break if vals.empty?
294
+ end
295
+
296
+ ds = r.associated_dataset.where(opts.right_primary_key=>vals)
297
+ ds = ds.clone(:no_results=>true) unless has_results
298
+ ds
299
+ end
300
+ opts[:eager_loader] ||= proc do |eo|
301
+ h = eo[:id_map]
302
+ assign_singular = opts.assign_singular?
303
+ uses_rcks = opts.right_primary_key.is_a?(Array)
304
+ rpk = uses_rcks ? opts.right_primary_keys : opts.right_primary_key
305
+ name = opts[:name]
306
+ def_db = opts.associated_class.db
307
+ join_map = h
308
+
309
+ run_query = through.each do |edge|
310
+ ds = (edge[:db] || def_db).from(edge[:table])
311
+ ds = ds.where(edge[:conditions]) if edge[:conditions]
312
+ left = edge[:left]
313
+ right = edge[:right]
314
+ prev_map = join_map
315
+ join_map = ds.where(left=>join_map.keys).select_hash_groups(right, left)
316
+ if right.is_a?(Array)
317
+ join_map.delete_if{|v,| v.any?(&:nil?)}
318
+ else
319
+ join_map.delete(nil)
320
+ end
321
+ break if join_map.empty?
322
+ join_map.each_value do |vs|
323
+ vs.replace(vs.flat_map{|v| prev_map[v]})
324
+ vs.uniq!
325
+ end
326
+ end
327
+
328
+ eo = Hash[eo]
329
+
330
+ if run_query
331
+ eo[:loader] = false
332
+ eo[:right_keys] = join_map.keys
333
+ else
334
+ eo[:no_results] = true
335
+ end
336
+
337
+ opts[:model].eager_load_results(opts, eo) do |assoc_record|
338
+ rpkv = if uses_rcks
339
+ assoc_record.values.values_at(*rpk)
340
+ else
341
+ assoc_record.values[rpk]
342
+ end
343
+
344
+ objects = join_map[rpkv]
345
+
346
+ if assign_singular
347
+ objects.each do |object|
348
+ object.associations[name] ||= assoc_record
349
+ end
350
+ else
351
+ objects.each do |object|
352
+ object.associations[name].push(assoc_record)
353
+ end
354
+ end
355
+ end
356
+ end
357
+ else
358
+ opts[:dataset] ||= opts.association_dataset_proc
359
+ opts[:eager_loader] ||= opts.method(:default_eager_loader)
360
+ end
262
361
 
263
362
  join_type = opts[:graph_join_type]
264
363
  select = opts[:graph_select]
@@ -286,7 +385,7 @@ module Sequel
286
385
  iq = nil
287
386
  end
288
387
  fe = opts.final_edge
289
- ds.graph(opts.associated_class.dataset, use_only_conditions ? only_conditions : (Array(opts.right_primary_key).zip(Array(fe[:left])) + conditions), :select=>select, :table_alias=>eo[:table_alias], :qualify=>:deep, :join_type=>eo[:join_type]||join_type, :join_only=>eo[:join_only], &graph_block)
388
+ ds.graph(eager_graph_dataset(opts, eo), use_only_conditions ? only_conditions : (Array(opts.right_primary_key).zip(Array(fe[:left])) + conditions), :select=>select, :table_alias=>eo[:table_alias], :qualify=>:deep, :join_type=>eo[:join_type]||join_type, :join_only=>eo[:join_only], &graph_block)
290
389
  end
291
390
  end
292
391
  end
@@ -26,57 +26,27 @@ module Sequel
26
26
  module MssqlOptimisticLocking
27
27
  # Load the instance_filters plugin into the model.
28
28
  def self.apply(model, opts=OPTS)
29
- model.plugin :instance_filters
29
+ model.plugin(:optimistic_locking_base)
30
30
  end
31
31
 
32
- # Set the lock_column to the :lock_column option (default: :timestamp)
32
+ # Set the lock column
33
33
  def self.configure(model, opts=OPTS)
34
- model.lock_column = opts[:lock_column] || :timestamp
34
+ model.lock_column = opts[:lock_column] || model.lock_column || :timestamp
35
35
  end
36
-
37
- module ClassMethods
38
- # The timestamp/rowversion column containing the version for the current row.
39
- attr_accessor :lock_column
40
-
41
- Plugins.inherited_instance_variables(self, :@lock_column=>nil)
42
- end
43
-
36
+
44
37
  module InstanceMethods
45
- # Add the lock column instance filter to the object before destroying it.
46
- def before_destroy
47
- lock_column_instance_filter
48
- super
49
- end
50
-
51
- # Add the lock column instance filter to the object before updating it.
52
- def before_update
53
- lock_column_instance_filter
54
- super
55
- end
56
-
57
38
  private
58
39
 
59
- # Add the lock column instance filter to the object.
60
- def lock_column_instance_filter
61
- lc = model.lock_column
62
- instance_filter(lc=>Sequel.blob(get_column_value(lc)))
63
- end
64
-
65
- # Clear the instance filters when refreshing, so that attempting to
66
- # refresh after a failed save removes the previous lock column filter
67
- # (the new one will be added before updating).
68
- def _refresh(ds)
69
- clear_instance_filters
70
- super
40
+ # Make the instance filter value a blob.
41
+ def lock_column_instance_filter_value
42
+ Sequel.blob(super)
71
43
  end
72
44
 
73
45
  # Remove the lock column from the columns to update.
74
46
  # SQL Server automatically updates the lock column value, and does not like
75
47
  # it to be assigned.
76
48
  def _save_update_all_columns_hash
77
- v = @values.dup
78
- cc = changed_columns
79
- Array(primary_key).each{|x| v.delete(x) unless cc.include?(x)}
49
+ v = super
80
50
  v.delete(model.lock_column)
81
51
  v
82
52
  end
@@ -33,7 +33,7 @@ module Sequel
33
33
  # objects. You just need to make sure that the primary key field is filled in for the
34
34
  # associated object:
35
35
  #
36
- # a.update(:albums_attributes => [{id: 1, name: 'T'}])
36
+ # a.update(albums_attributes: [{id: 1, name: 'T'}])
37
37
  #
38
38
  # Since the primary key field is filled in, the plugin will update the album with id 1 instead
39
39
  # of creating a new album.
@@ -42,14 +42,14 @@ module Sequel
42
42
  # entry to the hash, and also pass the :destroy option when calling +nested_attributes+:
43
43
  #
44
44
  # Artist.nested_attributes :albums, destroy: true
45
- # a.update(:albums_attributes => [{id: 1, _delete: true}])
45
+ # a.update(albums_attributes: [{id: 1, _delete: true}])
46
46
  #
47
47
  # This will delete the related associated object from the database. If you want to leave the
48
48
  # associated object in the database, but just remove it from the association, add a _remove
49
49
  # entry in the hash, and also pass the :remove option when calling +nested_attributes+:
50
50
  #
51
51
  # Artist.nested_attributes :albums, remove: true
52
- # a.update(:albums_attributes => [{id: 1, _remove: true}])
52
+ # a.update(albums_attributes: [{id: 1, _remove: true}])
53
53
  #
54
54
  # The above example was for a one_to_many association, but the plugin also works similarly
55
55
  # for other association types. For one_to_one and many_to_one associations, you need to
@@ -84,7 +84,7 @@ module Sequel
84
84
  # nested attributes options for that association. This is useful for per-call filtering
85
85
  # of the allowed fields:
86
86
  #
87
- # a.set_nested_attributes(:albums, params['artist'], :fields=>%w'name')
87
+ # a.set_nested_attributes(:albums, params['artist'], fields: %w'name')
88
88
  module NestedAttributes
89
89
  # Depend on the validate_associated plugin.
90
90
  def self.apply(model)
@@ -108,9 +108,10 @@ module Sequel
108
108
  # array of the allowable fields.
109
109
  # :limit :: For *_to_many associations, a limit on the number of records
110
110
  # that will be processed, to prevent denial of service attacks.
111
- # :reject_if :: A proc that is given each attribute hash before it is
111
+ # :reject_if :: A proc that is called with each attribute hash before it is
112
112
  # passed to its associated object. If the proc returns a truthy
113
113
  # value, the attribute hash is ignored.
114
+ # :reject_nil :: Ignore nil objects passed to nested attributes setter methods.
114
115
  # :remove :: Allow disassociation of nested records (can remove the associated
115
116
  # object from the parent object, but not destroy the associated object).
116
117
  # :require_modification :: Whether to require modification of nested objects when
@@ -145,9 +146,12 @@ module Sequel
145
146
  # class.
146
147
  def def_nested_attribute_method(reflection)
147
148
  @nested_attributes_module.class_eval do
148
- define_method("#{reflection[:name]}_attributes=") do |v|
149
- set_nested_attributes(reflection[:name], v)
149
+ meth = :"#{reflection[:name]}_attributes="
150
+ assoc = reflection[:name]
151
+ define_method(meth) do |v|
152
+ set_nested_attributes(assoc, v)
150
153
  end
154
+ alias_method meth, meth
151
155
  end
152
156
  end
153
157
  end
@@ -159,6 +163,7 @@ module Sequel
159
163
  def set_nested_attributes(assoc, obj, opts=OPTS)
160
164
  raise(Error, "no association named #{assoc} for #{model.inspect}") unless ref = model.association_reflection(assoc)
161
165
  raise(Error, "nested attributes are not enabled for association #{assoc} for #{model.inspect}") unless meta = ref[:nested_attributes]
166
+ return if obj.nil? && meta[:reject_nil]
162
167
  meta = meta.merge(opts)
163
168
  meta[:reflection] = ref
164
169
  if ref.returns_array?
@@ -12,64 +12,31 @@ module Sequel
12
12
  # p1 = Person[1]
13
13
  # p2 = Person[1]
14
14
  # p1.update(name: 'Jim') # works
15
- # p2.update(name: 'Bob') # raises Sequel::Plugins::OptimisticLocking::Error
15
+ # p2.update(name: 'Bob') # raises Sequel::NoExistingObject
16
16
  #
17
17
  # In order for this plugin to work, you need to make sure that the database
18
- # table has a +lock_version+ column (or other column you name via the lock_column
19
- # class level accessor) that defaults to 0.
18
+ # table has a +lock_version+ column that defaults to 0. To change the column
19
+ # used, provide a +:lock_column+ option when loading the plugin:
20
+ #
21
+ # plugin :optimistic_locking, lock_column: :version
20
22
  #
21
23
  # This plugin relies on the instance_filters plugin.
22
24
  module OptimisticLocking
23
25
  # Exception class raised when trying to update or destroy a stale object.
24
26
  Error = Sequel::NoExistingObject
25
27
 
26
- # Load the instance_filters plugin into the model.
27
28
  def self.apply(model, opts=OPTS)
28
- model.plugin :instance_filters
29
+ model.plugin(:optimistic_locking_base)
29
30
  end
30
31
 
31
- # Set the lock_column to the :lock_column option, or :lock_version if
32
- # that option is not given.
32
+ # Set the lock column
33
33
  def self.configure(model, opts=OPTS)
34
- model.lock_column = opts[:lock_column] || :lock_version
34
+ model.lock_column = opts[:lock_column] || model.lock_column || :lock_version
35
35
  end
36
-
37
- module ClassMethods
38
- # The column holding the version of the lock
39
- attr_accessor :lock_column
40
-
41
- Plugins.inherited_instance_variables(self, :@lock_column=>nil)
42
- end
43
-
36
+
44
37
  module InstanceMethods
45
- # Add the lock column instance filter to the object before destroying it.
46
- def before_destroy
47
- lock_column_instance_filter
48
- super
49
- end
50
-
51
- # Add the lock column instance filter to the object before updating it.
52
- def before_update
53
- lock_column_instance_filter
54
- super
55
- end
56
-
57
38
  private
58
39
 
59
- # Add the lock column instance filter to the object.
60
- def lock_column_instance_filter
61
- lc = model.lock_column
62
- instance_filter(lc=>get_column_value(lc))
63
- end
64
-
65
- # Clear the instance filters when refreshing, so that attempting to
66
- # refresh after a failed save removes the previous lock column filter
67
- # (the new one will be added before updating).
68
- def _refresh(ds)
69
- clear_instance_filters
70
- super
71
- end
72
-
73
40
  # Only update the row if it has the same lock version, and increment the
74
41
  # lock version.
75
42
  def _update_columns(columns)
@@ -0,0 +1,55 @@
1
+ # frozen-string-literal: true
2
+
3
+ module Sequel
4
+ module Plugins
5
+ # Base for other optimistic locking plugins
6
+ module OptimisticLockingBase
7
+ # Load the instance_filters plugin into the model.
8
+ def self.apply(model)
9
+ model.plugin :instance_filters
10
+ end
11
+
12
+ module ClassMethods
13
+ # The column holding the version of the lock
14
+ attr_accessor :lock_column
15
+
16
+ Plugins.inherited_instance_variables(self, :@lock_column=>nil)
17
+ end
18
+
19
+ module InstanceMethods
20
+ # Add the lock column instance filter to the object before destroying it.
21
+ def before_destroy
22
+ lock_column_instance_filter
23
+ super
24
+ end
25
+
26
+ # Add the lock column instance filter to the object before updating it.
27
+ def before_update
28
+ lock_column_instance_filter
29
+ super
30
+ end
31
+
32
+ private
33
+
34
+ # Add the lock column instance filter to the object.
35
+ def lock_column_instance_filter
36
+ instance_filter(model.lock_column=>lock_column_instance_filter_value)
37
+ end
38
+
39
+ # Use the current value of the lock column
40
+ def lock_column_instance_filter_value
41
+ public_send(model.lock_column)
42
+ end
43
+
44
+ # Clear the instance filters when refreshing, so that attempting to
45
+ # refresh after a failed save removes the previous lock column filter
46
+ # (the new one will be added before updating).
47
+ def _refresh(ds)
48
+ clear_instance_filters
49
+ super
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
55
+
@@ -384,26 +384,32 @@ module Sequel
384
384
  save_opts = {:validate=>opts[:validate]}
385
385
  save_opts[:raise_on_failure] = opts[:raise_on_save_failure] != false
386
386
 
387
- opts[:adder] ||= proc do |o|
388
- if array = o.get_column_value(key)
389
- array << get_column_value(pk)
390
- else
391
- o.set_column_value("#{key}=", Sequel.pg_array([get_column_value(pk)], opts.array_type))
387
+ unless opts.has_key?(:adder)
388
+ opts[:adder] = proc do |o|
389
+ if array = o.get_column_value(key)
390
+ array << get_column_value(pk)
391
+ else
392
+ o.set_column_value("#{key}=", Sequel.pg_array([get_column_value(pk)], opts.array_type))
393
+ end
394
+ o.save(save_opts)
392
395
  end
393
- o.save(save_opts)
394
396
  end
395
-
396
- opts[:remover] ||= proc do |o|
397
- if (array = o.get_column_value(key)) && !array.empty?
398
- array.delete(get_column_value(pk))
399
- o.save(save_opts)
397
+
398
+ unless opts.has_key?(:remover)
399
+ opts[:remover] = proc do |o|
400
+ if (array = o.get_column_value(key)) && !array.empty?
401
+ array.delete(get_column_value(pk))
402
+ o.save(save_opts)
403
+ end
400
404
  end
401
405
  end
402
406
 
403
- opts[:clearer] ||= proc do
404
- pk_value = get_column_value(pk)
405
- db_type = opts.array_type
406
- opts.associated_dataset.where(Sequel.pg_array_op(key).contains(Sequel.pg_array([pk_value], db_type))).update(key=>Sequel.function(:array_remove, key, Sequel.cast(pk_value, db_type)))
407
+ unless opts.has_key?(:clearer)
408
+ opts[:clearer] = proc do
409
+ pk_value = get_column_value(pk)
410
+ db_type = opts.array_type
411
+ opts.associated_dataset.where(Sequel.pg_array_op(key).contains(Sequel.pg_array([pk_value], db_type))).update(key=>Sequel.function(:array_remove, key, Sequel.cast(pk_value, db_type)))
412
+ end
407
413
  end
408
414
  end
409
415
 
@@ -426,10 +432,12 @@ module Sequel
426
432
  id_map = {}
427
433
  pkm = opts.primary_key_method
428
434
 
429
- rows.each do |object|
430
- if associated_pks = object.get_column_value(key)
431
- associated_pks.each do |apk|
432
- (id_map[apk] ||= []) << object
435
+ Sequel.synchronize_with(eo[:mutex]) do
436
+ rows.each do |object|
437
+ if associated_pks = object.get_column_value(key)
438
+ associated_pks.each do |apk|
439
+ (id_map[apk] ||= []) << object
440
+ end
433
441
  end
434
442
  end
435
443
  end
@@ -484,30 +492,36 @@ module Sequel
484
492
  end
485
493
  end
486
494
 
487
- opts[:adder] ||= proc do |o|
488
- opk = o.get_column_value(opts.primary_key)
489
- if array = get_column_value(key)
490
- modified!(key)
491
- array << opk
492
- else
493
- set_column_value("#{key}=", Sequel.pg_array([opk], opts.array_type))
495
+ unless opts.has_key?(:adder)
496
+ opts[:adder] = proc do |o|
497
+ opk = o.get_column_value(opts.primary_key)
498
+ if array = get_column_value(key)
499
+ modified!(key)
500
+ array << opk
501
+ else
502
+ set_column_value("#{key}=", Sequel.pg_array([opk], opts.array_type))
503
+ end
504
+ save_after_modify.call(self) if save_after_modify
494
505
  end
495
- save_after_modify.call(self) if save_after_modify
496
506
  end
497
-
498
- opts[:remover] ||= proc do |o|
499
- if (array = get_column_value(key)) && !array.empty?
500
- modified!(key)
501
- array.delete(o.get_column_value(opts.primary_key))
502
- save_after_modify.call(self) if save_after_modify
507
+
508
+ unless opts.has_key?(:remover)
509
+ opts[:remover] = proc do |o|
510
+ if (array = get_column_value(key)) && !array.empty?
511
+ modified!(key)
512
+ array.delete(o.get_column_value(opts.primary_key))
513
+ save_after_modify.call(self) if save_after_modify
514
+ end
503
515
  end
504
516
  end
505
517
 
506
- opts[:clearer] ||= proc do
507
- if (array = get_column_value(key)) && !array.empty?
508
- modified!(key)
509
- array.clear
510
- save_after_modify.call(self) if save_after_modify
518
+ unless opts.has_key?(:clearer)
519
+ opts[:clearer] = proc do
520
+ if (array = get_column_value(key)) && !array.empty?
521
+ modified!(key)
522
+ array.clear
523
+ save_after_modify.call(self) if save_after_modify
524
+ end
511
525
  end
512
526
  end
513
527
  end
@@ -520,7 +534,9 @@ module Sequel
520
534
  def many_to_pg_array_association_filter_expression(op, ref, obj)
521
535
  pk = ref.qualify(model.table_name, ref.primary_key)
522
536
  key = ref[:key]
537
+ # :nocov:
523
538
  expr = case obj
539
+ # :nocov:
524
540
  when Sequel::Model
525
541
  if (assoc_pks = obj.get_column_value(key)) && !assoc_pks.empty?
526
542
  Sequel[pk=>assoc_pks.to_a]
@@ -540,7 +556,9 @@ module Sequel
540
556
  # Support filtering by pg_array_to_many associations using a subquery.
541
557
  def pg_array_to_many_association_filter_expression(op, ref, obj)
542
558
  key = ref.qualify(model.table_name, ref[:key_column])
559
+ # :nocov:
543
560
  expr = case obj
561
+ # :nocov:
544
562
  when Sequel::Model
545
563
  if pkv = obj.get_column_value(ref.primary_key_method)
546
564
  Sequel.pg_array_op(key).contains(Sequel.pg_array([pkv], ref.array_type))
@@ -28,11 +28,13 @@ module Sequel
28
28
  #
29
29
  # This plugin only works on the postgres adapter when using the pg 0.16+ driver,
30
30
  # PostgreSQL 9.3+ server, and PostgreSQL 9.3+ client library (libpq). In other cases
31
- # it will be a no-op.
31
+ # it will be a no-op. Additionally, the plugin only handles models that select
32
+ # from tables. It does not handle models that select from subqueries, such as
33
+ # subclasses of models using the class_table_inheritance plugin.
32
34
  #
33
35
  # Example:
34
36
  #
35
- # album = Album.new(:artist_id=>1) # Assume no such artist exists
37
+ # album = Album.new(artist_id: 1) # Assume no such artist exists
36
38
  # begin
37
39
  # album.save
38
40
  # rescue Sequel::ValidationFailed
@@ -131,7 +133,11 @@ module Sequel
131
133
  # Dump the in-memory cached metadata to the cache file.
132
134
  def dump_pg_auto_constraint_validations_cache
133
135
  raise Error, "No pg_auto_constraint_validations setup" unless file = @pg_auto_constraint_validations_cache_file
134
- File.open(file, 'wb'){|f| f.write(Marshal.dump(@pg_auto_constraint_validations_cache))}
136
+ pg_auto_constraint_validations_cache = {}
137
+ @pg_auto_constraint_validations_cache.sort.each do |k, v|
138
+ pg_auto_constraint_validations_cache[k] = v
139
+ end
140
+ File.open(file, 'wb'){|f| f.write(Marshal.dump(pg_auto_constraint_validations_cache))}
135
141
  nil
136
142
  end
137
143
 
@@ -250,7 +256,9 @@ module Sequel
250
256
  messages = model.pg_auto_constraint_validations_messages
251
257
 
252
258
  unless override
259
+ # :nocov:
253
260
  case e
261
+ # :nocov:
254
262
  when Sequel::NotNullConstraintViolation
255
263
  if column = info[:column]
256
264
  add_pg_constraint_validation_error([m.call(column)], messages[:not_null])