sequel 5.39.0 → 5.72.0

Sign up to get free protection for your applications and to get access to all the features.
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])