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