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.
- checksums.yaml +4 -4
- data/CHANGELOG +308 -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 +89 -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/schema_modification.rdoc +1 -1
- data/doc/security.rdoc +9 -9
- data/doc/sql.rdoc +27 -15
- 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 +3 -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 +58 -7
- data/lib/sequel/adapters/shared/mysql.rb +40 -2
- data/lib/sequel/adapters/shared/oracle.rb +76 -0
- data/lib/sequel/adapters/shared/postgres.rb +418 -174
- data/lib/sequel/adapters/shared/sqlanywhere.rb +10 -0
- data/lib/sequel/adapters/shared/sqlite.rb +102 -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 +38 -1
- data/lib/sequel/database/schema_generator.rb +45 -52
- data/lib/sequel/database/schema_methods.rb +17 -1
- data/lib/sequel/dataset/actions.rb +107 -13
- data/lib/sequel/dataset/features.rb +20 -0
- data/lib/sequel/dataset/misc.rb +1 -1
- data/lib/sequel/dataset/prepared_statements.rb +2 -0
- data/lib/sequel/dataset/query.rb +118 -16
- data/lib/sequel/dataset/sql.rb +177 -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 +478 -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 +325 -96
- 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 +3 -1
- 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 +108 -9
- 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 +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
|
-
|
|
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]
|
|
@@ -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
|