sequel 5.39.0 → 5.63.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 (187) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG +308 -0
  3. data/MIT-LICENSE +1 -1
  4. data/README.rdoc +57 -25
  5. data/bin/sequel +11 -3
  6. data/doc/advanced_associations.rdoc +13 -13
  7. data/doc/association_basics.rdoc +89 -24
  8. data/doc/cheat_sheet.rdoc +11 -3
  9. data/doc/migration.rdoc +12 -6
  10. data/doc/model_hooks.rdoc +1 -1
  11. data/doc/object_model.rdoc +8 -8
  12. data/doc/opening_databases.rdoc +18 -11
  13. data/doc/postgresql.rdoc +16 -8
  14. data/doc/querying.rdoc +5 -3
  15. data/doc/release_notes/5.40.0.txt +40 -0
  16. data/doc/release_notes/5.41.0.txt +25 -0
  17. data/doc/release_notes/5.42.0.txt +136 -0
  18. data/doc/release_notes/5.43.0.txt +98 -0
  19. data/doc/release_notes/5.44.0.txt +32 -0
  20. data/doc/release_notes/5.45.0.txt +34 -0
  21. data/doc/release_notes/5.46.0.txt +87 -0
  22. data/doc/release_notes/5.47.0.txt +59 -0
  23. data/doc/release_notes/5.48.0.txt +14 -0
  24. data/doc/release_notes/5.49.0.txt +59 -0
  25. data/doc/release_notes/5.50.0.txt +78 -0
  26. data/doc/release_notes/5.51.0.txt +47 -0
  27. data/doc/release_notes/5.52.0.txt +87 -0
  28. data/doc/release_notes/5.53.0.txt +23 -0
  29. data/doc/release_notes/5.54.0.txt +27 -0
  30. data/doc/release_notes/5.55.0.txt +21 -0
  31. data/doc/release_notes/5.56.0.txt +51 -0
  32. data/doc/release_notes/5.57.0.txt +23 -0
  33. data/doc/release_notes/5.58.0.txt +31 -0
  34. data/doc/release_notes/5.59.0.txt +73 -0
  35. data/doc/release_notes/5.60.0.txt +22 -0
  36. data/doc/release_notes/5.61.0.txt +43 -0
  37. data/doc/release_notes/5.62.0.txt +132 -0
  38. data/doc/release_notes/5.63.0.txt +33 -0
  39. data/doc/schema_modification.rdoc +1 -1
  40. data/doc/security.rdoc +9 -9
  41. data/doc/sql.rdoc +27 -15
  42. data/doc/testing.rdoc +22 -11
  43. data/doc/transactions.rdoc +6 -6
  44. data/doc/virtual_rows.rdoc +2 -2
  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/postgresql.rb +4 -4
  53. data/lib/sequel/adapters/jdbc.rb +16 -18
  54. data/lib/sequel/adapters/mysql.rb +80 -67
  55. data/lib/sequel/adapters/mysql2.rb +54 -49
  56. data/lib/sequel/adapters/odbc.rb +6 -2
  57. data/lib/sequel/adapters/oracle.rb +3 -3
  58. data/lib/sequel/adapters/postgres.rb +83 -40
  59. data/lib/sequel/adapters/shared/access.rb +11 -1
  60. data/lib/sequel/adapters/shared/db2.rb +30 -0
  61. data/lib/sequel/adapters/shared/mssql.rb +58 -7
  62. data/lib/sequel/adapters/shared/mysql.rb +40 -2
  63. data/lib/sequel/adapters/shared/oracle.rb +76 -0
  64. data/lib/sequel/adapters/shared/postgres.rb +418 -174
  65. data/lib/sequel/adapters/shared/sqlanywhere.rb +10 -0
  66. data/lib/sequel/adapters/shared/sqlite.rb +102 -11
  67. data/lib/sequel/adapters/sqlanywhere.rb +1 -1
  68. data/lib/sequel/adapters/sqlite.rb +60 -18
  69. data/lib/sequel/adapters/tinytds.rb +1 -1
  70. data/lib/sequel/adapters/utils/columns_limit_1.rb +22 -0
  71. data/lib/sequel/adapters/utils/mysql_mysql2.rb +1 -1
  72. data/lib/sequel/ast_transformer.rb +6 -0
  73. data/lib/sequel/connection_pool/sharded_single.rb +5 -7
  74. data/lib/sequel/connection_pool/sharded_threaded.rb +5 -1
  75. data/lib/sequel/connection_pool/single.rb +6 -8
  76. data/lib/sequel/connection_pool/threaded.rb +8 -8
  77. data/lib/sequel/connection_pool/timed_queue.rb +257 -0
  78. data/lib/sequel/connection_pool.rb +47 -30
  79. data/lib/sequel/core.rb +28 -18
  80. data/lib/sequel/database/connecting.rb +26 -2
  81. data/lib/sequel/database/misc.rb +69 -14
  82. data/lib/sequel/database/query.rb +38 -1
  83. data/lib/sequel/database/schema_generator.rb +45 -52
  84. data/lib/sequel/database/schema_methods.rb +17 -1
  85. data/lib/sequel/dataset/actions.rb +107 -13
  86. data/lib/sequel/dataset/features.rb +20 -0
  87. data/lib/sequel/dataset/misc.rb +1 -1
  88. data/lib/sequel/dataset/prepared_statements.rb +2 -0
  89. data/lib/sequel/dataset/query.rb +118 -16
  90. data/lib/sequel/dataset/sql.rb +177 -47
  91. data/lib/sequel/extensions/_model_pg_row.rb +0 -12
  92. data/lib/sequel/extensions/_pretty_table.rb +1 -1
  93. data/lib/sequel/extensions/any_not_empty.rb +1 -1
  94. data/lib/sequel/extensions/async_thread_pool.rb +438 -0
  95. data/lib/sequel/extensions/auto_literal_strings.rb +1 -1
  96. data/lib/sequel/extensions/blank.rb +8 -0
  97. data/lib/sequel/extensions/constraint_validations.rb +1 -1
  98. data/lib/sequel/extensions/core_refinements.rb +36 -11
  99. data/lib/sequel/extensions/date_arithmetic.rb +71 -31
  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 +1 -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 +141 -0
  106. data/lib/sequel/extensions/looser_typecasting.rb +3 -0
  107. data/lib/sequel/extensions/migration.rb +7 -2
  108. data/lib/sequel/extensions/named_timezones.rb +26 -6
  109. data/lib/sequel/extensions/pagination.rb +1 -1
  110. data/lib/sequel/extensions/pg_array.rb +23 -3
  111. data/lib/sequel/extensions/pg_array_ops.rb +2 -2
  112. data/lib/sequel/extensions/pg_auto_parameterize.rb +478 -0
  113. data/lib/sequel/extensions/pg_enum.rb +1 -1
  114. data/lib/sequel/extensions/pg_extended_date_support.rb +28 -25
  115. data/lib/sequel/extensions/pg_extended_integer_support.rb +116 -0
  116. data/lib/sequel/extensions/pg_hstore.rb +6 -1
  117. data/lib/sequel/extensions/pg_hstore_ops.rb +53 -3
  118. data/lib/sequel/extensions/pg_inet.rb +10 -11
  119. data/lib/sequel/extensions/pg_inet_ops.rb +1 -1
  120. data/lib/sequel/extensions/pg_interval.rb +45 -19
  121. data/lib/sequel/extensions/pg_json.rb +13 -15
  122. data/lib/sequel/extensions/pg_json_ops.rb +73 -2
  123. data/lib/sequel/extensions/pg_loose_count.rb +3 -1
  124. data/lib/sequel/extensions/pg_multirange.rb +367 -0
  125. data/lib/sequel/extensions/pg_range.rb +10 -23
  126. data/lib/sequel/extensions/pg_range_ops.rb +37 -9
  127. data/lib/sequel/extensions/pg_row.rb +19 -13
  128. data/lib/sequel/extensions/pg_row_ops.rb +1 -1
  129. data/lib/sequel/extensions/query.rb +2 -0
  130. data/lib/sequel/extensions/s.rb +2 -1
  131. data/lib/sequel/extensions/schema_dumper.rb +13 -2
  132. data/lib/sequel/extensions/server_block.rb +8 -12
  133. data/lib/sequel/extensions/sql_comments.rb +110 -3
  134. data/lib/sequel/extensions/sql_log_normalizer.rb +108 -0
  135. data/lib/sequel/extensions/sqlite_json_ops.rb +255 -0
  136. data/lib/sequel/extensions/string_agg.rb +1 -1
  137. data/lib/sequel/extensions/string_date_time.rb +19 -23
  138. data/lib/sequel/extensions/symbol_aref.rb +2 -0
  139. data/lib/sequel/model/associations.rb +325 -96
  140. data/lib/sequel/model/base.rb +51 -27
  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 +5 -0
  144. data/lib/sequel/plugins/association_proxies.rb +2 -0
  145. data/lib/sequel/plugins/async_thread_pool.rb +39 -0
  146. data/lib/sequel/plugins/auto_restrict_eager_graph.rb +62 -0
  147. data/lib/sequel/plugins/auto_validations.rb +87 -15
  148. data/lib/sequel/plugins/auto_validations_constraint_validations_presence_message.rb +68 -0
  149. data/lib/sequel/plugins/class_table_inheritance.rb +2 -2
  150. data/lib/sequel/plugins/column_encryption.rb +728 -0
  151. data/lib/sequel/plugins/composition.rb +10 -4
  152. data/lib/sequel/plugins/concurrent_eager_loading.rb +174 -0
  153. data/lib/sequel/plugins/constraint_validations.rb +2 -1
  154. data/lib/sequel/plugins/dataset_associations.rb +4 -1
  155. data/lib/sequel/plugins/dirty.rb +1 -1
  156. data/lib/sequel/plugins/enum.rb +124 -0
  157. data/lib/sequel/plugins/finder.rb +3 -1
  158. data/lib/sequel/plugins/insert_conflict.rb +4 -0
  159. data/lib/sequel/plugins/instance_specific_default.rb +1 -1
  160. data/lib/sequel/plugins/json_serializer.rb +39 -24
  161. data/lib/sequel/plugins/lazy_attributes.rb +3 -0
  162. data/lib/sequel/plugins/list.rb +3 -1
  163. data/lib/sequel/plugins/many_through_many.rb +108 -9
  164. data/lib/sequel/plugins/nested_attributes.rb +12 -7
  165. data/lib/sequel/plugins/pg_array_associations.rb +56 -38
  166. data/lib/sequel/plugins/pg_auto_constraint_validations.rb +3 -1
  167. data/lib/sequel/plugins/prepared_statements.rb +10 -1
  168. data/lib/sequel/plugins/primary_key_lookup_check_values.rb +154 -0
  169. data/lib/sequel/plugins/rcte_tree.rb +27 -19
  170. data/lib/sequel/plugins/require_valid_schema.rb +67 -0
  171. data/lib/sequel/plugins/serialization.rb +9 -3
  172. data/lib/sequel/plugins/serialization_modification_detection.rb +2 -1
  173. data/lib/sequel/plugins/single_table_inheritance.rb +8 -0
  174. data/lib/sequel/plugins/sql_comments.rb +189 -0
  175. data/lib/sequel/plugins/static_cache.rb +1 -1
  176. data/lib/sequel/plugins/subclasses.rb +28 -11
  177. data/lib/sequel/plugins/tactical_eager_loading.rb +23 -10
  178. data/lib/sequel/plugins/timestamps.rb +1 -1
  179. data/lib/sequel/plugins/unused_associations.rb +521 -0
  180. data/lib/sequel/plugins/update_or_create.rb +1 -1
  181. data/lib/sequel/plugins/validate_associated.rb +22 -12
  182. data/lib/sequel/plugins/validation_helpers.rb +38 -11
  183. data/lib/sequel/plugins/xml_serializer.rb +1 -1
  184. data/lib/sequel/sql.rb +1 -1
  185. data/lib/sequel/timezones.rb +12 -14
  186. data/lib/sequel/version.rb +1 -1
  187. metadata +97 -43
@@ -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]
@@ -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?
@@ -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))
@@ -32,7 +32,7 @@ module Sequel
32
32
  #
33
33
  # Example:
34
34
  #
35
- # album = Album.new(:artist_id=>1) # Assume no such artist exists
35
+ # album = Album.new(artist_id: 1) # Assume no such artist exists
36
36
  # begin
37
37
  # album.save
38
38
  # rescue Sequel::ValidationFailed
@@ -250,7 +250,9 @@ module Sequel
250
250
  messages = model.pg_auto_constraint_validations_messages
251
251
 
252
252
  unless override
253
+ # :nocov:
253
254
  case e
255
+ # :nocov:
254
256
  when Sequel::NotNullConstraintViolation
255
257
  if column = info[:column]
256
258
  add_pg_constraint_validation_error([m.call(column)], messages[:not_null])
@@ -169,8 +169,17 @@ module Sequel
169
169
  end
170
170
 
171
171
  case type
172
- when :insert, :insert_select, :update
172
+ when :insert, :update
173
173
  true
174
+ when :insert_select
175
+ # SQLite RETURNING support has a bug that doesn't allow for committing transactions
176
+ # when a prepared statement with RETURNING has been used on the connection:
177
+ #
178
+ # SQLite3::BusyException: cannot commit transaction - SQL statements in progress: COMMIT
179
+ #
180
+ # Disabling usage of prepared statements for insert_select on SQLite seems to be the
181
+ # simplest way to workaround the problem.
182
+ db.database_type != :sqlite
174
183
  # :nocov:
175
184
  when :delete, :refresh
176
185
  Sequel::Deprecation.deprecate("The :delete and :refresh prepared statement types", "There should be no need to check if these types are supported")
@@ -0,0 +1,154 @@
1
+ # frozen-string-literal: true
2
+
3
+ module Sequel
4
+ module Plugins
5
+ # The primary_key_lookup_check_values plugin typecasts given primary key
6
+ # values before performing a lookup by primary key. If the given primary
7
+ # key value cannot be typecasted correctly, the lookup returns nil
8
+ # without issuing a query. If the schema for the primary key column
9
+ # includes minimum and maximum values, this also checks the given value
10
+ # is not outside the range. If the given value is outside the allowed
11
+ # range, the lookup returns nil without issuing a query.
12
+ #
13
+ # This affects the following Model methods:
14
+ #
15
+ # * Model.[] (when called with non-Hash)
16
+ # * Model.with_pk
17
+ # * Model.with_pk!
18
+ #
19
+ # It also affects the following Model dataset methods:
20
+ #
21
+ # * Dataset#[] (when called with Integer)
22
+ # * Dataset#with_pk
23
+ # * dataset#with_pk!
24
+ #
25
+ # Note that this can break working code. The above methods accept
26
+ # any filter condition by default, not just primary key values. The
27
+ # plugin will handle Symbol, Sequel::SQL::Expression, and
28
+ # Sequel::LiteralString objects, but code such as the following will break:
29
+ #
30
+ # # Return first Album where primary key is one of the given values
31
+ # Album.dataset.with_pk([1, 2, 3])
32
+ #
33
+ # Usage:
34
+ #
35
+ # # Make all model subclasses support checking primary key values before
36
+ # # lookup # (called before loading subclasses)
37
+ # Sequel::Model.plugin :primary_key_lookup_check_values
38
+ #
39
+ # # Make the Album class support checking primary key values before lookup
40
+ # Album.plugin :primary_key_lookup_check_values
41
+ module PrimaryKeyLookupCheckValues
42
+ def self.configure(model)
43
+ model.instance_exec do
44
+ setup_primary_key_lookup_check_values if @dataset
45
+ end
46
+ end
47
+
48
+ module ClassMethods
49
+ Plugins.after_set_dataset(self, :setup_primary_key_lookup_check_values)
50
+
51
+ Plugins.inherited_instance_variables(self,
52
+ :@primary_key_type=>nil,
53
+ :@primary_key_value_range=>nil)
54
+
55
+ private
56
+
57
+ # Check the given primary key value. Typecast it to the appropriate
58
+ # database type if the database type is known. If it cannot be
59
+ # typecasted, or the typecasted value is outside the range of column
60
+ # values, return nil.
61
+ def _check_pk_lookup_value(pk)
62
+ return if nil == pk
63
+ case pk
64
+ when SQL::Expression, LiteralString, Symbol
65
+ return pk
66
+ end
67
+ return pk unless pk_type = @primary_key_type
68
+
69
+ if pk_type.is_a?(Array)
70
+ return unless pk.is_a?(Array)
71
+ return unless pk.size == pk_type.size
72
+ return if pk.any?(&:nil?)
73
+
74
+ pk_value_range = @primary_key_value_range
75
+ i = 0
76
+ pk.map do |v|
77
+ if type = pk_type[i]
78
+ v = _typecast_pk_lookup_value(v, type)
79
+ return if nil == v
80
+ if pk_value_range
81
+ min, max = pk_value_range[i]
82
+ return if min && v < min
83
+ return if max && v > max
84
+ end
85
+ end
86
+ i += 1
87
+ v
88
+ end
89
+ elsif pk.is_a?(Array)
90
+ return
91
+ elsif nil != (pk = _typecast_pk_lookup_value(pk, pk_type))
92
+ min, max = @primary_key_value_range
93
+ return if min && pk < min
94
+ return if max && pk > max
95
+ pk
96
+ end
97
+ end
98
+
99
+ # Typecast the value to the appropriate type,
100
+ # returning nil if it cannot be typecasted.
101
+ def _typecast_pk_lookup_value(value, type)
102
+ db.typecast_value(type, value)
103
+ rescue InvalidValue
104
+ nil
105
+ end
106
+
107
+ # Skip the primary key lookup if the typecasted and checked
108
+ # primary key value is nil.
109
+ def primary_key_lookup(pk)
110
+ unless nil == (pk = _check_pk_lookup_value(pk))
111
+ super
112
+ end
113
+ end
114
+
115
+ # Setup the primary key type and value range used for checking
116
+ # primary key values during lookup.
117
+ def setup_primary_key_lookup_check_values
118
+ if primary_key.is_a?(Array)
119
+ types = []
120
+ value_ranges = []
121
+ primary_key.each do |pk|
122
+ type, min, max = _type_min_max_values_for_column(pk)
123
+ types << type
124
+ value_ranges << ([min, max].freeze if min || max)
125
+ end
126
+ @primary_key_type = (types.freeze if types.any?)
127
+ @primary_key_value_range = (value_ranges.freeze if @primary_key_type && value_ranges.any?)
128
+ else
129
+ @primary_key_type, min, max = _type_min_max_values_for_column(primary_key)
130
+ @primary_key_value_range = ([min, max].freeze if @primary_key_type && (min || max))
131
+ end
132
+ end
133
+
134
+ # Return the type, min_value, and max_value schema entries
135
+ # for the column, if they exist.
136
+ def _type_min_max_values_for_column(column)
137
+ if schema = db_schema[column]
138
+ schema.values_at(:type, :min_value, :max_value)
139
+ end
140
+ end
141
+ end
142
+
143
+ module DatasetMethods
144
+ # Skip the primary key lookup if the typecasted and checked
145
+ # primary key value is nil.
146
+ def with_pk(pk)
147
+ unless nil == (pk = model.send(:_check_pk_lookup_value, pk))
148
+ super
149
+ end
150
+ end
151
+ end
152
+ end
153
+ end
154
+ end
@@ -170,11 +170,13 @@ module Sequel
170
170
  id_map = eo[:id_map]
171
171
  parent_map = {}
172
172
  children_map = {}
173
- eo[:rows].each do |obj|
174
- parent_map[prkey_conv[obj]] = obj
175
- (children_map[key_conv[obj]] ||= []) << obj
176
- obj.associations[ancestors] = []
177
- obj.associations[parent] = nil
173
+ Sequel.synchronize_with(eo[:mutex]) do
174
+ eo[:rows].each do |obj|
175
+ parent_map[prkey_conv[obj]] = obj
176
+ (children_map[key_conv[obj]] ||= []) << obj
177
+ obj.associations[ancestors] = []
178
+ obj.associations[parent] = nil
179
+ end
178
180
  end
179
181
  r = model.association_reflection(ancestors)
180
182
  base_case = model.where(prkey=>id_map.keys).
@@ -207,10 +209,12 @@ module Sequel
207
209
  root.associations[ancestors] << obj
208
210
  end
209
211
  end
210
- parent_map.each do |parent_id, obj|
211
- if children = children_map[parent_id]
212
- children.each do |child|
213
- child.associations[parent] = obj
212
+ Sequel.synchronize_with(eo[:mutex]) do
213
+ parent_map.each do |parent_id, obj|
214
+ if children = children_map[parent_id]
215
+ children.each do |child|
216
+ child.associations[parent] = obj
217
+ end
214
218
  end
215
219
  end
216
220
  end
@@ -268,10 +272,12 @@ module Sequel
268
272
  associations = eo[:associations]
269
273
  parent_map = {}
270
274
  children_map = {}
271
- eo[:rows].each do |obj|
272
- parent_map[prkey_conv[obj]] = obj
273
- obj.associations[descendants] = []
274
- obj.associations[childrena] = []
275
+ Sequel.synchronize_with(eo[:mutex]) do
276
+ eo[:rows].each do |obj|
277
+ parent_map[prkey_conv[obj]] = obj
278
+ obj.associations[descendants] = []
279
+ obj.associations[childrena] = []
280
+ end
275
281
  end
276
282
  r = model.association_reflection(descendants)
277
283
  base_case = model.where(key=>id_map.keys).
@@ -316,12 +322,14 @@ module Sequel
316
322
 
317
323
  (children_map[key_conv[obj]] ||= []) << obj
318
324
  end
319
- children_map.each do |parent_id, objs|
320
- objs = objs.uniq
321
- parent_obj = parent_map[parent_id]
322
- parent_obj.associations[childrena] = objs
323
- objs.each do |obj|
324
- obj.associations[parent] = parent_obj
325
+ Sequel.synchronize_with(eo[:mutex]) do
326
+ children_map.each do |parent_id, objs|
327
+ objs = objs.uniq
328
+ parent_obj = parent_map[parent_id]
329
+ parent_obj.associations[childrena] = objs
330
+ objs.each do |obj|
331
+ obj.associations[parent] = parent_obj
332
+ end
325
333
  end
326
334
  end
327
335
  end