sequel 5.39.0 → 5.66.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG +336 -0
- data/MIT-LICENSE +1 -1
- data/README.rdoc +57 -25
- data/bin/sequel +11 -3
- data/doc/advanced_associations.rdoc +13 -13
- data/doc/association_basics.rdoc +119 -24
- data/doc/cheat_sheet.rdoc +11 -3
- data/doc/migration.rdoc +12 -6
- data/doc/model_hooks.rdoc +1 -1
- data/doc/object_model.rdoc +8 -8
- data/doc/opening_databases.rdoc +18 -11
- data/doc/postgresql.rdoc +16 -8
- data/doc/querying.rdoc +5 -3
- data/doc/release_notes/5.40.0.txt +40 -0
- data/doc/release_notes/5.41.0.txt +25 -0
- data/doc/release_notes/5.42.0.txt +136 -0
- data/doc/release_notes/5.43.0.txt +98 -0
- data/doc/release_notes/5.44.0.txt +32 -0
- data/doc/release_notes/5.45.0.txt +34 -0
- data/doc/release_notes/5.46.0.txt +87 -0
- data/doc/release_notes/5.47.0.txt +59 -0
- data/doc/release_notes/5.48.0.txt +14 -0
- data/doc/release_notes/5.49.0.txt +59 -0
- data/doc/release_notes/5.50.0.txt +78 -0
- data/doc/release_notes/5.51.0.txt +47 -0
- data/doc/release_notes/5.52.0.txt +87 -0
- data/doc/release_notes/5.53.0.txt +23 -0
- data/doc/release_notes/5.54.0.txt +27 -0
- data/doc/release_notes/5.55.0.txt +21 -0
- data/doc/release_notes/5.56.0.txt +51 -0
- data/doc/release_notes/5.57.0.txt +23 -0
- data/doc/release_notes/5.58.0.txt +31 -0
- data/doc/release_notes/5.59.0.txt +73 -0
- data/doc/release_notes/5.60.0.txt +22 -0
- data/doc/release_notes/5.61.0.txt +43 -0
- data/doc/release_notes/5.62.0.txt +132 -0
- data/doc/release_notes/5.63.0.txt +33 -0
- data/doc/release_notes/5.64.0.txt +50 -0
- data/doc/release_notes/5.65.0.txt +21 -0
- data/doc/release_notes/5.66.0.txt +24 -0
- data/doc/schema_modification.rdoc +1 -1
- data/doc/security.rdoc +9 -9
- data/doc/sql.rdoc +28 -16
- data/doc/testing.rdoc +22 -11
- data/doc/transactions.rdoc +6 -6
- data/doc/virtual_rows.rdoc +2 -2
- data/lib/sequel/adapters/ado/access.rb +1 -1
- data/lib/sequel/adapters/ado.rb +17 -17
- data/lib/sequel/adapters/amalgalite.rb +3 -5
- data/lib/sequel/adapters/ibmdb.rb +2 -2
- data/lib/sequel/adapters/jdbc/derby.rb +8 -0
- data/lib/sequel/adapters/jdbc/h2.rb +60 -10
- data/lib/sequel/adapters/jdbc/hsqldb.rb +6 -0
- data/lib/sequel/adapters/jdbc/postgresql.rb +4 -4
- data/lib/sequel/adapters/jdbc.rb +16 -18
- data/lib/sequel/adapters/mysql.rb +80 -67
- data/lib/sequel/adapters/mysql2.rb +54 -49
- data/lib/sequel/adapters/odbc.rb +6 -2
- data/lib/sequel/adapters/oracle.rb +4 -3
- data/lib/sequel/adapters/postgres.rb +83 -40
- data/lib/sequel/adapters/shared/access.rb +11 -1
- data/lib/sequel/adapters/shared/db2.rb +30 -0
- data/lib/sequel/adapters/shared/mssql.rb +83 -7
- data/lib/sequel/adapters/shared/mysql.rb +47 -2
- data/lib/sequel/adapters/shared/oracle.rb +82 -1
- data/lib/sequel/adapters/shared/postgres.rb +430 -174
- data/lib/sequel/adapters/shared/sqlanywhere.rb +11 -1
- data/lib/sequel/adapters/shared/sqlite.rb +116 -11
- data/lib/sequel/adapters/sqlanywhere.rb +1 -1
- data/lib/sequel/adapters/sqlite.rb +60 -18
- data/lib/sequel/adapters/tinytds.rb +1 -1
- data/lib/sequel/adapters/utils/columns_limit_1.rb +22 -0
- data/lib/sequel/adapters/utils/mysql_mysql2.rb +1 -1
- data/lib/sequel/ast_transformer.rb +6 -0
- data/lib/sequel/connection_pool/sharded_single.rb +5 -7
- data/lib/sequel/connection_pool/sharded_threaded.rb +5 -1
- data/lib/sequel/connection_pool/single.rb +6 -8
- data/lib/sequel/connection_pool/threaded.rb +8 -8
- data/lib/sequel/connection_pool/timed_queue.rb +257 -0
- data/lib/sequel/connection_pool.rb +47 -30
- data/lib/sequel/core.rb +28 -18
- data/lib/sequel/database/connecting.rb +26 -2
- data/lib/sequel/database/misc.rb +69 -14
- data/lib/sequel/database/query.rb +73 -2
- data/lib/sequel/database/schema_generator.rb +45 -52
- data/lib/sequel/database/schema_methods.rb +17 -1
- data/lib/sequel/dataset/actions.rb +108 -14
- data/lib/sequel/dataset/features.rb +20 -0
- data/lib/sequel/dataset/misc.rb +12 -2
- data/lib/sequel/dataset/placeholder_literalizer.rb +20 -9
- data/lib/sequel/dataset/prepared_statements.rb +2 -0
- data/lib/sequel/dataset/query.rb +118 -16
- data/lib/sequel/dataset/sql.rb +182 -47
- data/lib/sequel/extensions/_model_pg_row.rb +0 -12
- data/lib/sequel/extensions/_pretty_table.rb +1 -1
- data/lib/sequel/extensions/any_not_empty.rb +1 -1
- data/lib/sequel/extensions/async_thread_pool.rb +438 -0
- data/lib/sequel/extensions/auto_literal_strings.rb +1 -1
- data/lib/sequel/extensions/blank.rb +8 -0
- data/lib/sequel/extensions/constraint_validations.rb +1 -1
- data/lib/sequel/extensions/core_refinements.rb +36 -11
- data/lib/sequel/extensions/date_arithmetic.rb +71 -31
- data/lib/sequel/extensions/date_parse_input_handler.rb +67 -0
- data/lib/sequel/extensions/datetime_parse_to_time.rb +5 -1
- data/lib/sequel/extensions/duplicate_columns_handler.rb +1 -1
- data/lib/sequel/extensions/eval_inspect.rb +2 -0
- data/lib/sequel/extensions/inflector.rb +9 -1
- data/lib/sequel/extensions/is_distinct_from.rb +141 -0
- data/lib/sequel/extensions/looser_typecasting.rb +3 -0
- data/lib/sequel/extensions/migration.rb +7 -2
- data/lib/sequel/extensions/named_timezones.rb +26 -6
- data/lib/sequel/extensions/pagination.rb +1 -1
- data/lib/sequel/extensions/pg_array.rb +23 -3
- data/lib/sequel/extensions/pg_array_ops.rb +2 -2
- data/lib/sequel/extensions/pg_auto_parameterize.rb +509 -0
- data/lib/sequel/extensions/pg_enum.rb +1 -1
- data/lib/sequel/extensions/pg_extended_date_support.rb +28 -25
- data/lib/sequel/extensions/pg_extended_integer_support.rb +116 -0
- data/lib/sequel/extensions/pg_hstore.rb +6 -1
- data/lib/sequel/extensions/pg_hstore_ops.rb +53 -3
- data/lib/sequel/extensions/pg_inet.rb +10 -11
- data/lib/sequel/extensions/pg_inet_ops.rb +1 -1
- data/lib/sequel/extensions/pg_interval.rb +45 -19
- data/lib/sequel/extensions/pg_json.rb +13 -15
- data/lib/sequel/extensions/pg_json_ops.rb +73 -2
- data/lib/sequel/extensions/pg_loose_count.rb +3 -1
- data/lib/sequel/extensions/pg_multirange.rb +367 -0
- data/lib/sequel/extensions/pg_range.rb +10 -23
- data/lib/sequel/extensions/pg_range_ops.rb +37 -9
- data/lib/sequel/extensions/pg_row.rb +19 -13
- data/lib/sequel/extensions/pg_row_ops.rb +1 -1
- data/lib/sequel/extensions/query.rb +2 -0
- data/lib/sequel/extensions/s.rb +2 -1
- data/lib/sequel/extensions/schema_dumper.rb +13 -2
- data/lib/sequel/extensions/server_block.rb +8 -12
- data/lib/sequel/extensions/sql_comments.rb +110 -3
- data/lib/sequel/extensions/sql_log_normalizer.rb +108 -0
- data/lib/sequel/extensions/sqlite_json_ops.rb +255 -0
- data/lib/sequel/extensions/string_agg.rb +1 -1
- data/lib/sequel/extensions/string_date_time.rb +19 -23
- data/lib/sequel/extensions/symbol_aref.rb +2 -0
- data/lib/sequel/model/associations.rb +345 -101
- data/lib/sequel/model/base.rb +51 -27
- data/lib/sequel/model/errors.rb +10 -1
- data/lib/sequel/model/inflections.rb +1 -1
- data/lib/sequel/model/plugins.rb +5 -0
- data/lib/sequel/plugins/association_proxies.rb +2 -0
- data/lib/sequel/plugins/async_thread_pool.rb +39 -0
- data/lib/sequel/plugins/auto_restrict_eager_graph.rb +62 -0
- data/lib/sequel/plugins/auto_validations.rb +87 -15
- data/lib/sequel/plugins/auto_validations_constraint_validations_presence_message.rb +68 -0
- data/lib/sequel/plugins/class_table_inheritance.rb +2 -2
- data/lib/sequel/plugins/column_encryption.rb +728 -0
- data/lib/sequel/plugins/composition.rb +10 -4
- data/lib/sequel/plugins/concurrent_eager_loading.rb +174 -0
- data/lib/sequel/plugins/constraint_validations.rb +2 -1
- data/lib/sequel/plugins/dataset_associations.rb +4 -1
- data/lib/sequel/plugins/dirty.rb +1 -1
- data/lib/sequel/plugins/enum.rb +124 -0
- data/lib/sequel/plugins/finder.rb +4 -2
- data/lib/sequel/plugins/insert_conflict.rb +4 -0
- data/lib/sequel/plugins/instance_specific_default.rb +1 -1
- data/lib/sequel/plugins/json_serializer.rb +39 -24
- data/lib/sequel/plugins/lazy_attributes.rb +3 -0
- data/lib/sequel/plugins/list.rb +3 -1
- data/lib/sequel/plugins/many_through_many.rb +109 -10
- data/lib/sequel/plugins/nested_attributes.rb +12 -7
- data/lib/sequel/plugins/pg_array_associations.rb +56 -38
- data/lib/sequel/plugins/pg_auto_constraint_validations.rb +3 -1
- data/lib/sequel/plugins/prepared_statements.rb +10 -1
- data/lib/sequel/plugins/primary_key_lookup_check_values.rb +154 -0
- data/lib/sequel/plugins/rcte_tree.rb +27 -19
- data/lib/sequel/plugins/require_valid_schema.rb +67 -0
- data/lib/sequel/plugins/serialization.rb +9 -3
- data/lib/sequel/plugins/serialization_modification_detection.rb +2 -1
- data/lib/sequel/plugins/single_table_inheritance.rb +8 -0
- data/lib/sequel/plugins/sql_comments.rb +189 -0
- data/lib/sequel/plugins/static_cache.rb +1 -1
- data/lib/sequel/plugins/subclasses.rb +28 -11
- data/lib/sequel/plugins/tactical_eager_loading.rb +23 -10
- data/lib/sequel/plugins/timestamps.rb +1 -1
- data/lib/sequel/plugins/unused_associations.rb +521 -0
- data/lib/sequel/plugins/update_or_create.rb +1 -1
- data/lib/sequel/plugins/validate_associated.rb +22 -12
- data/lib/sequel/plugins/validation_helpers.rb +38 -11
- data/lib/sequel/plugins/xml_serializer.rb +1 -1
- data/lib/sequel/sql.rb +1 -1
- data/lib/sequel/timezones.rb +12 -14
- data/lib/sequel/version.rb +1 -1
- metadata +103 -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
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
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
|
-
|
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
|
-
|
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
|
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
|
@@ -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(:
|
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(:
|
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(:
|
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'], :
|
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
|
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
|
-
|
149
|
-
|
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
|
388
|
-
|
389
|
-
array
|
390
|
-
|
391
|
-
|
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
|
397
|
-
|
398
|
-
array.
|
399
|
-
|
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
|
404
|
-
|
405
|
-
|
406
|
-
|
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
|
-
|
430
|
-
|
431
|
-
associated_pks
|
432
|
-
|
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
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
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
|
499
|
-
|
500
|
-
|
501
|
-
|
502
|
-
|
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
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
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(:
|
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, :
|
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[:
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
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
|
-
|
211
|
-
|
212
|
-
children
|
213
|
-
|
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[:
|
272
|
-
|
273
|
-
|
274
|
-
|
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
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
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
|