sequel 4.9.0 → 4.10.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 +79 -1
- data/MIT-LICENSE +1 -1
- data/README.rdoc +1 -1
- data/Rakefile +2 -12
- data/bin/sequel +1 -0
- data/doc/advanced_associations.rdoc +82 -25
- data/doc/association_basics.rdoc +21 -22
- data/doc/core_extensions.rdoc +1 -1
- data/doc/opening_databases.rdoc +7 -0
- data/doc/release_notes/4.10.0.txt +226 -0
- data/doc/security.rdoc +1 -0
- data/doc/testing.rdoc +7 -7
- data/doc/transactions.rdoc +8 -0
- data/lib/sequel/adapters/jdbc.rb +160 -168
- data/lib/sequel/adapters/jdbc/db2.rb +17 -18
- data/lib/sequel/adapters/jdbc/derby.rb +5 -28
- data/lib/sequel/adapters/jdbc/h2.rb +11 -22
- data/lib/sequel/adapters/jdbc/hsqldb.rb +31 -18
- data/lib/sequel/adapters/jdbc/jtds.rb +0 -15
- data/lib/sequel/adapters/jdbc/oracle.rb +36 -35
- data/lib/sequel/adapters/jdbc/postgresql.rb +72 -90
- data/lib/sequel/adapters/jdbc/sqlanywhere.rb +18 -16
- data/lib/sequel/adapters/jdbc/sqlite.rb +7 -0
- data/lib/sequel/adapters/jdbc/sqlserver.rb +10 -30
- data/lib/sequel/adapters/jdbc/transactions.rb +5 -6
- data/lib/sequel/adapters/openbase.rb +1 -7
- data/lib/sequel/adapters/postgres.rb +1 -1
- data/lib/sequel/adapters/shared/access.rb +3 -6
- data/lib/sequel/adapters/shared/cubrid.rb +24 -9
- data/lib/sequel/adapters/shared/db2.rb +13 -5
- data/lib/sequel/adapters/shared/firebird.rb +16 -16
- data/lib/sequel/adapters/shared/informix.rb +2 -5
- data/lib/sequel/adapters/shared/mssql.rb +72 -63
- data/lib/sequel/adapters/shared/mysql.rb +72 -40
- data/lib/sequel/adapters/shared/oracle.rb +27 -15
- data/lib/sequel/adapters/shared/postgres.rb +24 -44
- data/lib/sequel/adapters/shared/progress.rb +1 -5
- data/lib/sequel/adapters/shared/sqlanywhere.rb +26 -18
- data/lib/sequel/adapters/shared/sqlite.rb +21 -6
- data/lib/sequel/adapters/utils/emulate_offset_with_reverse_and_count.rb +8 -1
- data/lib/sequel/adapters/utils/emulate_offset_with_row_number.rb +8 -2
- data/lib/sequel/adapters/utils/split_alter_table.rb +8 -0
- data/lib/sequel/core.rb +14 -9
- data/lib/sequel/database/dataset_defaults.rb +1 -0
- data/lib/sequel/database/misc.rb +12 -0
- data/lib/sequel/database/query.rb +4 -1
- data/lib/sequel/database/schema_methods.rb +3 -2
- data/lib/sequel/database/transactions.rb +47 -17
- data/lib/sequel/dataset/features.rb +12 -2
- data/lib/sequel/dataset/mutation.rb +2 -0
- data/lib/sequel/dataset/placeholder_literalizer.rb +12 -4
- data/lib/sequel/dataset/prepared_statements.rb +6 -0
- data/lib/sequel/dataset/query.rb +1 -1
- data/lib/sequel/dataset/sql.rb +132 -70
- data/lib/sequel/extensions/columns_introspection.rb +1 -1
- data/lib/sequel/extensions/null_dataset.rb +8 -4
- data/lib/sequel/extensions/pg_array.rb +4 -4
- data/lib/sequel/extensions/pg_row.rb +1 -0
- data/lib/sequel/model/associations.rb +468 -188
- data/lib/sequel/model/base.rb +88 -13
- data/lib/sequel/plugins/association_pks.rb +23 -64
- data/lib/sequel/plugins/auto_validations.rb +3 -2
- data/lib/sequel/plugins/dataset_associations.rb +1 -3
- data/lib/sequel/plugins/many_through_many.rb +18 -65
- data/lib/sequel/plugins/pg_array_associations.rb +97 -86
- data/lib/sequel/plugins/prepared_statements.rb +2 -1
- data/lib/sequel/plugins/prepared_statements_associations.rb +36 -27
- data/lib/sequel/plugins/rcte_tree.rb +12 -16
- data/lib/sequel/plugins/sharding.rb +21 -3
- data/lib/sequel/plugins/single_table_inheritance.rb +2 -1
- data/lib/sequel/plugins/subclasses.rb +1 -9
- data/lib/sequel/plugins/tactical_eager_loading.rb +9 -0
- data/lib/sequel/plugins/tree.rb +2 -2
- data/lib/sequel/plugins/validation_class_methods.rb +1 -1
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/mssql_spec.rb +57 -15
- data/spec/adapters/mysql_spec.rb +11 -0
- data/spec/bin_spec.rb +2 -2
- data/spec/core/database_spec.rb +38 -4
- data/spec/core/dataset_spec.rb +45 -7
- data/spec/core/placeholder_literalizer_spec.rb +17 -0
- data/spec/core/schema_spec.rb +6 -1
- data/spec/extensions/active_model_spec.rb +18 -9
- data/spec/extensions/association_pks_spec.rb +20 -18
- data/spec/extensions/association_proxies_spec.rb +9 -9
- data/spec/extensions/auto_validations_spec.rb +6 -0
- data/spec/extensions/columns_introspection_spec.rb +1 -0
- data/spec/extensions/constraint_validations_spec.rb +3 -1
- data/spec/extensions/many_through_many_spec.rb +191 -111
- data/spec/extensions/pg_array_associations_spec.rb +133 -103
- data/spec/extensions/prepared_statements_associations_spec.rb +23 -4
- data/spec/extensions/rcte_tree_spec.rb +35 -27
- data/spec/extensions/sequel_3_dataset_methods_spec.rb +0 -1
- data/spec/extensions/sharding_spec.rb +2 -2
- data/spec/extensions/tactical_eager_loading_spec.rb +4 -0
- data/spec/extensions/to_dot_spec.rb +1 -0
- data/spec/extensions/touch_spec.rb +2 -2
- data/spec/integration/associations_test.rb +130 -37
- data/spec/integration/dataset_test.rb +17 -0
- data/spec/integration/model_test.rb +17 -0
- data/spec/integration/schema_test.rb +14 -0
- data/spec/integration/transaction_test.rb +25 -1
- data/spec/model/association_reflection_spec.rb +63 -24
- data/spec/model/associations_spec.rb +104 -57
- data/spec/model/base_spec.rb +14 -1
- data/spec/model/class_dataset_methods_spec.rb +1 -0
- data/spec/model/eager_loading_spec.rb +221 -74
- data/spec/model/model_spec.rb +119 -1
- metadata +4 -2
data/lib/sequel/model/base.rb
CHANGED
@@ -399,28 +399,59 @@ module Sequel
|
|
399
399
|
end
|
400
400
|
|
401
401
|
type = opts.fetch(:type, :first)
|
402
|
-
|
402
|
+
unless prepare = opts[:prepare]
|
403
|
+
raise Error, ":type option to Model.finder must be :first, :all, :each, or :get" unless FINDER_TYPES.include?(type)
|
404
|
+
end
|
403
405
|
limit1 = type == :first || type == :get
|
404
406
|
meth_name ||= opts[:name] || :"#{type}_#{meth}"
|
405
407
|
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
408
|
+
argn = lambda do |model|
|
409
|
+
if arity = opts[:arity]
|
410
|
+
arity
|
411
|
+
else
|
412
|
+
method = block || model.method(meth)
|
413
|
+
(method.arity < 0 ? method.arity.abs - 1 : method.arity)
|
414
|
+
end
|
415
|
+
end
|
416
|
+
|
417
|
+
loader_proc = if prepare
|
418
|
+
proc do |model|
|
419
|
+
args = prepare_method_args('$a', argn.call(model))
|
420
|
+
ds = if block
|
421
|
+
model.instance_exec(*args, &block)
|
422
|
+
else
|
423
|
+
model.send(meth, *args)
|
424
|
+
end
|
425
|
+
ds = ds.limit(1) if limit1
|
426
|
+
model_name = model.name
|
427
|
+
if model_name.to_s.empty?
|
428
|
+
model_name = model.object_id
|
429
|
+
else
|
430
|
+
model_name = model_name.gsub(/\W/, '_')
|
431
|
+
end
|
432
|
+
ds.prepare(type, :"#{model_name}_#{meth_name}")
|
433
|
+
end
|
434
|
+
else
|
435
|
+
proc do |model|
|
436
|
+
n = argn.call(model)
|
437
|
+
block ||= lambda do |pl, model2|
|
438
|
+
args = (0...n).map{pl.arg}
|
439
|
+
ds = model2.send(meth, *args)
|
414
440
|
ds = ds.limit(1) if limit1
|
415
441
|
ds
|
416
442
|
end
|
417
|
-
end
|
418
443
|
|
419
|
-
|
444
|
+
Sequel::Dataset::PlaceholderLiteralizer.loader(model, &block)
|
445
|
+
end
|
420
446
|
end
|
447
|
+
|
421
448
|
Sequel.synchronize{@finder_loaders[meth_name] = loader_proc}
|
422
449
|
mod = opts[:mod] || (class << self; self; end)
|
423
|
-
|
450
|
+
if prepare
|
451
|
+
def_prepare_method(mod, meth_name)
|
452
|
+
else
|
453
|
+
def_finder_method(mod, meth_name, type)
|
454
|
+
end
|
424
455
|
end
|
425
456
|
|
426
457
|
# An alias for calling first on the model's dataset, but with
|
@@ -442,7 +473,7 @@ module Sequel
|
|
442
473
|
|
443
474
|
# Clear the setter_methods cache when a module is included, as it
|
444
475
|
# may contain setter methods.
|
445
|
-
def include(
|
476
|
+
def include(*mods)
|
446
477
|
clear_setter_methods_cache
|
447
478
|
super
|
448
479
|
end
|
@@ -573,6 +604,27 @@ module Sequel
|
|
573
604
|
h
|
574
605
|
end
|
575
606
|
|
607
|
+
# Similar to finder, but uses a prepared statement instead of a placeholder
|
608
|
+
# literalizer. This makes the SQL used static (cannot vary per call), but
|
609
|
+
# allows binding argument values instead of literalizing them into the SQL
|
610
|
+
# query string.
|
611
|
+
#
|
612
|
+
# If a block is used with this method, it is instance_execed by the model,
|
613
|
+
# and should accept the desired number of placeholder arguments.
|
614
|
+
#
|
615
|
+
# The options are the same as the options for finder, with the following
|
616
|
+
# exception:
|
617
|
+
# :type :: Specifies the type of prepared statement to create
|
618
|
+
def prepared_finder(meth=OPTS, opts=OPTS, &block)
|
619
|
+
if block
|
620
|
+
raise Error, "cannot pass both a method name argument and a block of Model.finder" unless meth.is_a?(Hash)
|
621
|
+
meth = meth.merge(:prepare=>true)
|
622
|
+
else
|
623
|
+
opts = opts.merge(:prepare=>true)
|
624
|
+
end
|
625
|
+
finder(meth, opts, &block)
|
626
|
+
end
|
627
|
+
|
576
628
|
# Restrict the setting of the primary key(s) when using mass assignment (e.g. +set+). Because
|
577
629
|
# this is the default, this only make sense to use in a subclass where the
|
578
630
|
# parent class has used +unrestrict_primary_key+.
|
@@ -818,6 +870,12 @@ module Sequel
|
|
818
870
|
mod.send(:define_method, meth){|*args, &block| finder_for(meth).send(type, *args, &block)}
|
819
871
|
end
|
820
872
|
|
873
|
+
# Define a prepared_finder method in the given module that will call the associated prepared
|
874
|
+
# statement.
|
875
|
+
def def_prepare_method(mod, meth)
|
876
|
+
mod.send(:define_method, meth){|*args, &block| finder_for(meth).call(prepare_method_arg_hash(args), &block)}
|
877
|
+
end
|
878
|
+
|
821
879
|
# Find the finder to use for the give method. If a finder has not been loaded
|
822
880
|
# for the method, load the finder and set correctly in the finders hash, then
|
823
881
|
# return the finder.
|
@@ -948,6 +1006,23 @@ module Sequel
|
|
948
1006
|
end
|
949
1007
|
end
|
950
1008
|
|
1009
|
+
# An hash of prepared argument values for the given arguments, with keys
|
1010
|
+
# starting at a. Used by the methods created by prepared_finder.
|
1011
|
+
def prepare_method_arg_hash(args)
|
1012
|
+
h = {}
|
1013
|
+
prepare_method_args('a', args.length).zip(args).each{|k, v| h[k] = v}
|
1014
|
+
h
|
1015
|
+
end
|
1016
|
+
|
1017
|
+
# An array of prepared statement argument names, of length n and starting with base.
|
1018
|
+
def prepare_method_args(base, n)
|
1019
|
+
(0...n).map do
|
1020
|
+
s = base.to_sym
|
1021
|
+
base = base.next
|
1022
|
+
s
|
1023
|
+
end
|
1024
|
+
end
|
1025
|
+
|
951
1026
|
# Find the row in the dataset that matches the primary key. Uses
|
952
1027
|
# a static SQL optimization if the table and primary key are simple.
|
953
1028
|
#
|
@@ -49,85 +49,44 @@ module Sequel
|
|
49
49
|
# a setter that deletes from or inserts into the join table.
|
50
50
|
def def_many_to_many(opts)
|
51
51
|
super
|
52
|
+
|
53
|
+
return if opts[:type] == :one_through_one
|
54
|
+
|
52
55
|
# Grab values from the reflection so that the hash lookup only needs to be
|
53
56
|
# done once instead of inside ever method call.
|
54
57
|
lk, lpk, rk = opts.values_at(:left_key, :left_primary_key, :right_key)
|
58
|
+
clpk = lpk.is_a?(Array)
|
59
|
+
crk = rk.is_a?(Array)
|
55
60
|
|
56
|
-
|
57
|
-
# composite and singular left key cases, and 4 separate implementations of the setter
|
58
|
-
# method optimized for each combination of composite and singular keys for both
|
59
|
-
# the left and right keys.
|
60
|
-
if lpk.is_a?(Array)
|
61
|
+
if clpk
|
61
62
|
def_association_pks_getter(opts) do
|
62
63
|
h = {}
|
63
64
|
lk.zip(lpk).each{|k, pk| h[k] = send(pk)}
|
64
65
|
_join_table_dataset(opts).filter(h).select_map(rk)
|
65
66
|
end
|
66
|
-
|
67
|
-
if rk.is_a?(Array)
|
68
|
-
def_association_pks_setter(opts) do |pks|
|
69
|
-
pks = convert_cpk_array(opts, pks)
|
70
|
-
checked_transaction do
|
71
|
-
lpkv = lpk.map{|k| send(k)}
|
72
|
-
ds = _join_table_dataset(opts).filter(lk.zip(lpkv))
|
73
|
-
ds.exclude(rk=>pks).delete
|
74
|
-
pks -= ds.select_map(rk)
|
75
|
-
h = {}
|
76
|
-
lk.zip(lpkv).each{|k, v| h[k] = v}
|
77
|
-
pks.each do |pk|
|
78
|
-
ih = h.dup
|
79
|
-
rk.zip(pk).each{|k, v| ih[k] = v}
|
80
|
-
ds.insert(ih)
|
81
|
-
end
|
82
|
-
end
|
83
|
-
end
|
84
|
-
else
|
85
|
-
def_association_pks_setter(opts) do |pks|
|
86
|
-
pks = convert_pk_array(opts, pks)
|
87
|
-
checked_transaction do
|
88
|
-
lpkv = lpk.map{|k| send(k)}
|
89
|
-
ds = _join_table_dataset(opts).filter(lk.zip(lpkv))
|
90
|
-
ds.exclude(rk=>pks).delete
|
91
|
-
pks -= ds.select_map(rk)
|
92
|
-
h = {}
|
93
|
-
lk.zip(lpkv).each{|k, v| h[k] = v}
|
94
|
-
pks.each do |pk|
|
95
|
-
ds.insert(h.merge(rk=>pk))
|
96
|
-
end
|
97
|
-
end
|
98
|
-
end
|
99
|
-
end
|
100
67
|
else
|
101
68
|
def_association_pks_getter(opts) do
|
102
69
|
_join_table_dataset(opts).filter(lk=>send(lpk)).select_map(rk)
|
103
70
|
end
|
71
|
+
end
|
104
72
|
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
h = {lk=>lpkv}
|
115
|
-
rk.zip(pk).each{|k, v| h[k] = v}
|
116
|
-
ds.insert(h)
|
117
|
-
end
|
118
|
-
end
|
119
|
-
end
|
120
|
-
else
|
121
|
-
def_association_pks_setter(opts) do |pks|
|
122
|
-
pks = convert_pk_array(opts, pks)
|
123
|
-
checked_transaction do
|
124
|
-
lpkv = send(lpk)
|
125
|
-
ds = _join_table_dataset(opts).filter(lk=>lpkv)
|
126
|
-
ds.exclude(rk=>pks).delete
|
127
|
-
pks -= ds.select_map(rk)
|
128
|
-
pks.each{|pk| ds.insert(lk=>lpkv, rk=>pk)}
|
129
|
-
end
|
73
|
+
def_association_pks_setter(opts) do |pks|
|
74
|
+
pks = send(crk ? :convert_cpk_array : :convert_pk_array, opts, pks)
|
75
|
+
checked_transaction do
|
76
|
+
if clpk
|
77
|
+
lpkv = lpk.map{|k| send(k)}
|
78
|
+
cond = lk.zip(lpkv)
|
79
|
+
else
|
80
|
+
lpkv = send(lpk)
|
81
|
+
cond = {lk=>lpkv}
|
130
82
|
end
|
83
|
+
ds = _join_table_dataset(opts).filter(cond)
|
84
|
+
ds.exclude(rk=>pks).delete
|
85
|
+
pks -= ds.select_map(rk)
|
86
|
+
lpkv = Array(lpkv)
|
87
|
+
key_array = crk ? pks.map{|pk| lpkv + pk} : pks.map{|pk| lpkv + [pk]}
|
88
|
+
key_columns = Array(lk) + Array(rk)
|
89
|
+
ds.import(key_columns, key_array)
|
131
90
|
end
|
132
91
|
end
|
133
92
|
end
|
@@ -107,8 +107,9 @@ module Sequel
|
|
107
107
|
@auto_validate_not_null_columns = not_null_cols - Array(primary_key)
|
108
108
|
explicit_not_null_cols += Array(primary_key)
|
109
109
|
@auto_validate_explicit_not_null_columns = explicit_not_null_cols.uniq
|
110
|
-
|
111
|
-
|
110
|
+
table = dataset.first_source_table
|
111
|
+
@auto_validate_unique_columns = if db.supports_index_parsing? && [Symbol, SQL::QualifiedIdentifier, SQL::Identifier, String].any?{|c| table.is_a?(c)}
|
112
|
+
db.indexes(table).select{|name, idx| idx[:unique] == true}.map{|name, idx| idx[:columns]}
|
112
113
|
else
|
113
114
|
[]
|
114
115
|
end
|
@@ -99,9 +99,7 @@ module Sequel
|
|
99
99
|
else
|
100
100
|
raise Error, "unrecognized association type for association #{name.inspect}: #{r[:type].inspect}"
|
101
101
|
end
|
102
|
-
|
103
|
-
r[:extend].each{|m| ds.extend(m)}
|
104
|
-
ds
|
102
|
+
r.apply_eager_dataset_changes(ds).unlimited
|
105
103
|
end
|
106
104
|
end
|
107
105
|
end
|
@@ -101,6 +101,11 @@ module Sequel
|
|
101
101
|
END
|
102
102
|
end
|
103
103
|
|
104
|
+
# The alias for the first join table.
|
105
|
+
def join_table_alias
|
106
|
+
final_reverse_edge[:alias]
|
107
|
+
end
|
108
|
+
|
104
109
|
# Many through many associations don't have a reciprocal
|
105
110
|
def reciprocal
|
106
111
|
nil
|
@@ -108,6 +113,13 @@ module Sequel
|
|
108
113
|
|
109
114
|
private
|
110
115
|
|
116
|
+
def _associated_dataset
|
117
|
+
ds = associated_class
|
118
|
+
reverse_edges.each{|t| ds = ds.join(t[:table], Array(t[:left]).zip(Array(t[:right])), :table_alias=>t[:alias], :qualify=>:deep)}
|
119
|
+
ft = final_reverse_edge
|
120
|
+
ds.join(ft[:table], Array(ft[:left]).zip(Array(ft[:right])), :table_alias=>ft[:alias], :qualify=>:deep)
|
121
|
+
end
|
122
|
+
|
111
123
|
# Make sure to use unique table aliases when lazy loading or eager loading
|
112
124
|
def calculate_reverse_edge_aliases(reverse_edges)
|
113
125
|
aliases = [associated_class.table_name]
|
@@ -156,26 +168,10 @@ module Sequel
|
|
156
168
|
h
|
157
169
|
end
|
158
170
|
|
159
|
-
def filter_by_associations_add_conditions_dataset_filter(ds)
|
160
|
-
reverse_edges.each{|t| ds = ds.join(t[:table], Array(t[:left]).zip(Array(t[:right])), :table_alias=>t[:alias], :qualify=>:deep)}
|
161
|
-
ft = final_reverse_edge
|
162
|
-
k = qualify(ft[:alias], Array(self[:left_key]))
|
163
|
-
ds.join(ft[:table], Array(ft[:left]).zip(Array(ft[:right])), :table_alias=>ft[:alias], :qualify=>:deep).
|
164
|
-
where(Sequel.negate(k.zip([]))).
|
165
|
-
select(*k)
|
166
|
-
end
|
167
|
-
|
168
171
|
def filter_by_associations_limit_key
|
169
172
|
fe = edges.first
|
170
173
|
Array(qualify(fe[:table], fe[:right])) + Array(qualify(associated_class.table_name, associated_class.primary_key))
|
171
174
|
end
|
172
|
-
|
173
|
-
def filter_by_associations_limit_subquery
|
174
|
-
subquery = associated_eager_dataset.unlimited
|
175
|
-
reverse_edges.each{|t| subquery = subquery.join(t[:table], Array(t[:left]).zip(Array(t[:right])), :table_alias=>t[:alias], :qualify=>:deep)}
|
176
|
-
ft = final_reverse_edge
|
177
|
-
subquery.join(ft[:table], Array(ft[:left]).zip(Array(ft[:right])), :table_alias=>ft[:alias], :qualify=>:deep)
|
178
|
-
end
|
179
175
|
end
|
180
176
|
|
181
177
|
class OneThroughManyAssociationReflection < ManyThroughManyAssociationReflection
|
@@ -214,8 +210,6 @@ module Sequel
|
|
214
210
|
# Create the association methods and :eager_loader and :eager_grapher procs.
|
215
211
|
def def_many_through_many(opts)
|
216
212
|
one_through_many = opts[:type] == :one_through_many
|
217
|
-
name = opts[:name]
|
218
|
-
model = self
|
219
213
|
opts[:read_only] = true
|
220
214
|
opts[:after_load].unshift(:array_uniq!) if opts[:uniq]
|
221
215
|
opts[:cartesian_product_number] ||= one_through_many ? 0 : 2
|
@@ -233,56 +227,17 @@ module Sequel
|
|
233
227
|
end
|
234
228
|
|
235
229
|
left_key = opts[:left_key] = opts[:through].first[:left]
|
236
|
-
|
237
|
-
|
230
|
+
opts[:left_keys] = Array(left_key)
|
231
|
+
opts[:uses_left_composite_keys] = left_key.is_a?(Array)
|
238
232
|
left_pk = (opts[:left_primary_key] ||= self.primary_key)
|
239
233
|
opts[:eager_loader_key] = left_pk unless opts.has_key?(:eager_loader_key)
|
240
|
-
|
234
|
+
opts[:left_primary_keys] = Array(left_pk)
|
241
235
|
lpkc = opts[:left_primary_key_column] ||= left_pk
|
242
236
|
lpkcs = opts[:left_primary_key_columns] ||= Array(lpkc)
|
243
|
-
opts[:dataset] ||=
|
244
|
-
ds = opts.associated_dataset
|
245
|
-
opts.reverse_edges.each{|t| ds = ds.join(t[:table], Array(t[:left]).zip(Array(t[:right])), :table_alias=>t[:alias], :qualify=>:deep)}
|
246
|
-
ft = opts.final_reverse_edge
|
247
|
-
ds.join(ft[:table], Array(ft[:left]).zip(Array(ft[:right])) + opts.predicate_keys.zip(left_pks.map{|k| send(k)}), :table_alias=>ft[:alias], :qualify=>:deep)
|
248
|
-
end
|
249
|
-
|
250
|
-
slice_range = opts.slice_range
|
251
|
-
left_key_alias = opts[:left_key_alias] ||= opts.default_associated_key_alias
|
252
|
-
opts[:eager_loader] ||= lambda do |eo|
|
253
|
-
h = eo[:id_map]
|
254
|
-
rows = eo[:rows]
|
255
|
-
ds = opts.associated_class
|
256
|
-
opts.reverse_edges.each{|t| ds = ds.join(t[:table], Array(t[:left]).zip(Array(t[:right])), :table_alias=>t[:alias], :qualify=>:deep)}
|
257
|
-
ft = opts.final_reverse_edge
|
258
|
-
|
259
|
-
ds = ds.join(ft[:table], Array(ft[:left]).zip(Array(ft[:right])) + [[opts.predicate_key, h.keys]], :table_alias=>ft[:alias], :qualify=>:deep)
|
260
|
-
ds = model.eager_loading_dataset(opts, ds, nil, eo[:associations], eo)
|
261
|
-
ds = opts.apply_eager_limit_strategy(ds)
|
262
|
-
opts.initialize_association_cache(rows)
|
237
|
+
opts[:dataset] ||= opts.association_dataset_proc
|
263
238
|
|
264
|
-
|
265
|
-
|
266
|
-
ds.all do |assoc_record|
|
267
|
-
assoc_record.values.delete(delete_rn) if delete_rn
|
268
|
-
hash_key = if uses_lcks
|
269
|
-
left_key_alias.map{|k| assoc_record.values.delete(k)}
|
270
|
-
else
|
271
|
-
assoc_record.values.delete(left_key_alias)
|
272
|
-
end
|
273
|
-
next unless objects = h[hash_key]
|
274
|
-
if assign_singular
|
275
|
-
objects.each do |object|
|
276
|
-
object.associations[name] ||= assoc_record
|
277
|
-
end
|
278
|
-
else
|
279
|
-
objects.each do |object|
|
280
|
-
object.associations[name].push(assoc_record)
|
281
|
-
end
|
282
|
-
end
|
283
|
-
end
|
284
|
-
opts.apply_ruby_eager_limit_strategy(rows)
|
285
|
-
end
|
239
|
+
opts[:left_key_alias] ||= opts.default_associated_key_alias
|
240
|
+
opts[:eager_loader] ||= opts.method(:default_eager_loader)
|
286
241
|
|
287
242
|
join_type = opts[:graph_join_type]
|
288
243
|
select = opts[:graph_select]
|
@@ -313,8 +268,6 @@ module Sequel
|
|
313
268
|
ds.graph(opts.associated_class, 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, &graph_block)
|
314
269
|
end
|
315
270
|
end
|
316
|
-
|
317
|
-
def_association_dataset_methods(opts)
|
318
271
|
end
|
319
272
|
|
320
273
|
# Use def_many_through_many, since they share pretty much the same code.
|
@@ -47,11 +47,9 @@ module Sequel
|
|
47
47
|
#
|
48
48
|
# They support some additional options specific to this plugin:
|
49
49
|
#
|
50
|
-
# :array_type :: This
|
51
|
-
# is
|
52
|
-
#
|
53
|
-
# and typecasting is turned off or not setup correctly
|
54
|
-
# for the model object.
|
50
|
+
# :array_type :: This overrides the type of the array. By default, the type
|
51
|
+
# is determined by looking at the db_schema for the model, and if that fails,
|
52
|
+
# it defaults to :integer.
|
55
53
|
# :raise_on_save_failure :: Do not raise exceptions for hook or validation failures when saving associated
|
56
54
|
# objects in the add/remove methods (return nil instead).
|
57
55
|
# :save_after_modify :: For pg_array_to_many associations, this makes the
|
@@ -77,6 +75,16 @@ module Sequel
|
|
77
75
|
class ManyToPgArrayAssociationReflection < Sequel::Model::Associations::AssociationReflection
|
78
76
|
Sequel::Model::Associations::ASSOCIATION_TYPES[:many_to_pg_array] = self
|
79
77
|
|
78
|
+
def array_type
|
79
|
+
cached_fetch(:array_type) do
|
80
|
+
if (sch = associated_class.db_schema) && (s = sch[self[:key]]) && (t = s[:db_type])
|
81
|
+
t
|
82
|
+
else
|
83
|
+
:integer
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
80
88
|
# The array column in the associated model containing foreign keys to
|
81
89
|
# the current model.
|
82
90
|
def associated_object_keys
|
@@ -107,6 +115,11 @@ module Sequel
|
|
107
115
|
end
|
108
116
|
end
|
109
117
|
|
118
|
+
# Don't use a filter by associations limit strategy
|
119
|
+
def filter_by_associations_limit_strategy
|
120
|
+
nil
|
121
|
+
end
|
122
|
+
|
110
123
|
# Handle silent failure of add/remove methods if raise_on_save_failure is false.
|
111
124
|
def handle_silent_modification_failure?
|
112
125
|
self[:raise_on_save_failure] == false
|
@@ -131,6 +144,11 @@ module Sequel
|
|
131
144
|
|
132
145
|
private
|
133
146
|
|
147
|
+
# The predicate condition to use for the eager_loader.
|
148
|
+
def eager_loading_predicate_condition(keys)
|
149
|
+
Sequel.pg_array_op(predicate_key).overlaps(Sequel.pg_array(keys, array_type))
|
150
|
+
end
|
151
|
+
|
134
152
|
def filter_by_associations_add_conditions_dataset_filter(ds)
|
135
153
|
key = qualify(associated_class.table_name, self[:key])
|
136
154
|
ds.select{unnest(key)}.exclude(key=>nil)
|
@@ -149,12 +167,26 @@ module Sequel
|
|
149
167
|
def reciprocal_type
|
150
168
|
:pg_array_to_many
|
151
169
|
end
|
170
|
+
|
171
|
+
def use_placeholder_loader?
|
172
|
+
false
|
173
|
+
end
|
152
174
|
end
|
153
175
|
|
154
176
|
# The AssociationReflection subclass for pg_array_to_many associations.
|
155
177
|
class PgArrayToManyAssociationReflection < Sequel::Model::Associations::AssociationReflection
|
156
178
|
Sequel::Model::Associations::ASSOCIATION_TYPES[:pg_array_to_many] = self
|
157
179
|
|
180
|
+
def array_type
|
181
|
+
cached_fetch(:array_type) do
|
182
|
+
if (sch = self[:model].db_schema) && (s = sch[self[:key]]) && (t = s[:db_type])
|
183
|
+
t
|
184
|
+
else
|
185
|
+
:integer
|
186
|
+
end
|
187
|
+
end
|
188
|
+
end
|
189
|
+
|
158
190
|
# An array containing the primary key for the associated model.
|
159
191
|
def associated_object_keys
|
160
192
|
Array(primary_key)
|
@@ -190,6 +222,11 @@ module Sequel
|
|
190
222
|
end
|
191
223
|
end
|
192
224
|
|
225
|
+
# Don't use a filter by associations limit strategy
|
226
|
+
def filter_by_associations_limit_strategy
|
227
|
+
nil
|
228
|
+
end
|
229
|
+
|
193
230
|
# Handle silent failure of add/remove methods if raise_on_save_failure is false
|
194
231
|
# and save_after_modify is true.
|
195
232
|
def handle_silent_modification_failure?
|
@@ -236,6 +273,10 @@ module Sequel
|
|
236
273
|
def reciprocal_type
|
237
274
|
:many_to_pg_array
|
238
275
|
end
|
276
|
+
|
277
|
+
def use_placeholder_loader?
|
278
|
+
false
|
279
|
+
end
|
239
280
|
end
|
240
281
|
|
241
282
|
module ClassMethods
|
@@ -264,18 +305,13 @@ module Sequel
|
|
264
305
|
key = opts[:key]
|
265
306
|
key_column = opts[:key_column] ||= opts[:key]
|
266
307
|
opts[:after_load].unshift(:array_uniq!) if opts[:uniq]
|
267
|
-
slice_range = opts.slice_range
|
268
308
|
opts[:dataset] ||= lambda do
|
269
|
-
opts.associated_dataset.where(Sequel.pg_array_op(opts.predicate_key).contains([send(pk)]))
|
309
|
+
opts.associated_dataset.where(Sequel.pg_array_op(opts.predicate_key).contains(Sequel.pg_array([send(pk)], opts.array_type)))
|
270
310
|
end
|
271
311
|
opts[:eager_loader] ||= proc do |eo|
|
272
312
|
id_map = eo[:id_map]
|
273
|
-
rows = eo[:rows]
|
274
|
-
opts.initialize_association_cache(rows)
|
275
313
|
|
276
|
-
|
277
|
-
ds = model.eager_loading_dataset(opts, klass.where(Sequel.pg_array_op(opts.predicate_key).overlaps(id_map.keys)), nil, eo[:associations], eo)
|
278
|
-
ds.all do |assoc_record|
|
314
|
+
eager_load_results(opts, eo.merge(:loader=>false)) do |assoc_record|
|
279
315
|
if pks ||= assoc_record.send(key)
|
280
316
|
pks.each do |pkv|
|
281
317
|
next unless objects = id_map[pkv]
|
@@ -285,7 +321,6 @@ module Sequel
|
|
285
321
|
end
|
286
322
|
end
|
287
323
|
end
|
288
|
-
opts.apply_ruby_eager_limit_strategy(rows)
|
289
324
|
end
|
290
325
|
|
291
326
|
join_type = opts[:graph_join_type]
|
@@ -316,51 +351,40 @@ module Sequel
|
|
316
351
|
ds
|
317
352
|
end
|
318
353
|
|
319
|
-
|
354
|
+
return if opts[:read_only]
|
320
355
|
|
321
|
-
|
322
|
-
|
323
|
-
save_opts[:raise_on_failure] = opts[:raise_on_save_failure] != false
|
356
|
+
save_opts = {:validate=>opts[:validate]}
|
357
|
+
save_opts[:raise_on_failure] = opts[:raise_on_save_failure] != false
|
324
358
|
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
o.send("#{key}=", Sequel.pg_array([send(pk)], array_type))
|
331
|
-
end
|
332
|
-
o.save(save_opts)
|
333
|
-
end
|
334
|
-
association_module_private_def(opts._add_method, opts, &adder)
|
335
|
-
|
336
|
-
remover = opts[:remover] || proc do |o|
|
337
|
-
if (array = o.send(key)) && !array.empty?
|
338
|
-
array.delete(send(pk))
|
339
|
-
o.save(save_opts)
|
340
|
-
end
|
359
|
+
opts[:adder] ||= proc do |o|
|
360
|
+
if array = o.send(key)
|
361
|
+
array << send(pk)
|
362
|
+
else
|
363
|
+
o.send("#{key}=", Sequel.pg_array([send(pk)], opts.array_type))
|
341
364
|
end
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
365
|
+
o.save(save_opts)
|
366
|
+
end
|
367
|
+
|
368
|
+
opts[:remover] ||= proc do |o|
|
369
|
+
if (array = o.send(key)) && !array.empty?
|
370
|
+
array.delete(send(pk))
|
371
|
+
o.save(save_opts)
|
346
372
|
end
|
347
|
-
|
373
|
+
end
|
348
374
|
|
349
|
-
|
350
|
-
|
375
|
+
opts[:clearer] ||= proc do
|
376
|
+
opts.associated_dataset.where(Sequel.pg_array_op(key).contains([send(pk)])).update(key=>Sequel.function(:array_remove, key, send(pk)))
|
351
377
|
end
|
352
378
|
end
|
353
379
|
|
354
380
|
# Setup the pg_array_to_many-specific datasets, eager loaders, and modification methods.
|
355
381
|
def def_pg_array_to_many(opts)
|
356
382
|
name = opts[:name]
|
357
|
-
model = self
|
358
383
|
opts[:key] = opts.default_key unless opts.has_key?(:key)
|
359
384
|
key = opts[:key]
|
360
385
|
key_column = opts[:key_column] ||= key
|
361
386
|
opts[:eager_loader_key] = nil
|
362
387
|
opts[:after_load].unshift(:array_uniq!) if opts[:uniq]
|
363
|
-
slice_range = opts.slice_range
|
364
388
|
opts[:dataset] ||= lambda do
|
365
389
|
opts.associated_dataset.where(opts.predicate_key=>send(key).to_a)
|
366
390
|
end
|
@@ -368,7 +392,6 @@ module Sequel
|
|
368
392
|
rows = eo[:rows]
|
369
393
|
id_map = {}
|
370
394
|
pkm = opts.primary_key_method
|
371
|
-
opts.initialize_association_cache(rows)
|
372
395
|
|
373
396
|
rows.each do |object|
|
374
397
|
if associated_pks = object.send(key)
|
@@ -378,16 +401,13 @@ module Sequel
|
|
378
401
|
end
|
379
402
|
end
|
380
403
|
|
381
|
-
|
382
|
-
ds = model.eager_loading_dataset(opts, klass.where(opts.predicate_key=>id_map.keys), nil, eo[:associations], eo)
|
383
|
-
ds.all do |assoc_record|
|
404
|
+
eager_load_results(opts, eo.merge(:id_map=>id_map)) do |assoc_record|
|
384
405
|
if objects = id_map[assoc_record.send(pkm)]
|
385
406
|
objects.each do |object|
|
386
407
|
object.associations[name].push(assoc_record)
|
387
408
|
end
|
388
409
|
end
|
389
410
|
end
|
390
|
-
opts.apply_ruby_eager_limit_strategy(rows)
|
391
411
|
end
|
392
412
|
|
393
413
|
join_type = opts[:graph_join_type]
|
@@ -418,51 +438,42 @@ module Sequel
|
|
418
438
|
ds
|
419
439
|
end
|
420
440
|
|
421
|
-
|
441
|
+
return if opts[:read_only]
|
422
442
|
|
423
|
-
|
424
|
-
|
425
|
-
save_opts[:raise_on_failure] = opts[:raise_on_save_failure] != false
|
426
|
-
array_type = opts[:array_type] ||= :integer
|
443
|
+
save_opts = {:validate=>opts[:validate]}
|
444
|
+
save_opts[:raise_on_failure] = opts[:raise_on_save_failure] != false
|
427
445
|
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
end
|
446
|
+
if opts[:save_after_modify]
|
447
|
+
save_after_modify = proc do |obj|
|
448
|
+
obj.save(save_opts)
|
432
449
|
end
|
450
|
+
end
|
433
451
|
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
end
|
442
|
-
save_after_modify.call(self) if save_after_modify
|
452
|
+
opts[:adder] ||= proc do |o|
|
453
|
+
opk = o.send(opts.primary_key)
|
454
|
+
if array = send(key)
|
455
|
+
modified!(key)
|
456
|
+
array << opk
|
457
|
+
else
|
458
|
+
send("#{key}=", Sequel.pg_array([opk], opts.array_type))
|
443
459
|
end
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
460
|
+
save_after_modify.call(self) if save_after_modify
|
461
|
+
end
|
462
|
+
|
463
|
+
opts[:remover] ||= proc do |o|
|
464
|
+
if (array = send(key)) && !array.empty?
|
465
|
+
modified!(key)
|
466
|
+
array.delete(o.send(opts.primary_key))
|
467
|
+
save_after_modify.call(self) if save_after_modify
|
452
468
|
end
|
453
|
-
|
469
|
+
end
|
454
470
|
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
end
|
471
|
+
opts[:clearer] ||= proc do
|
472
|
+
if (array = send(key)) && !array.empty?
|
473
|
+
modified!(key)
|
474
|
+
array.clear
|
475
|
+
save_after_modify.call(self) if save_after_modify
|
461
476
|
end
|
462
|
-
association_module_private_def(opts._remove_all_method, opts, &clearer)
|
463
|
-
|
464
|
-
def_add_method(opts)
|
465
|
-
def_remove_methods(opts)
|
466
477
|
end
|
467
478
|
end
|
468
479
|
end
|
@@ -497,11 +508,11 @@ module Sequel
|
|
497
508
|
expr = case obj
|
498
509
|
when Sequel::Model
|
499
510
|
if pkv = obj.send(ref.primary_key_method)
|
500
|
-
Sequel.pg_array_op(key).contains([pkv])
|
511
|
+
Sequel.pg_array_op(key).contains(Sequel.pg_array([pkv], ref.array_type))
|
501
512
|
end
|
502
513
|
when Array
|
503
514
|
if (pkvs = obj.map{|o| o.send(ref.primary_key_method)}.compact) && !pkvs.empty?
|
504
|
-
Sequel.pg_array(key).overlaps(pkvs)
|
515
|
+
Sequel.pg_array(key).overlaps(Sequel.pg_array(pkvs, ref.array_type))
|
505
516
|
end
|
506
517
|
when Sequel::Dataset
|
507
518
|
Sequel.function(:coalesce, Sequel.pg_array_op(key).overlaps(obj.select{array_agg(ref.qualify(obj.model.table_name, ref.primary_key))}), Sequel::SQL::Constants::FALSE)
|