sequel 3.23.0 → 3.24.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.
- data/CHANGELOG +64 -0
- data/doc/association_basics.rdoc +43 -5
- data/doc/model_hooks.rdoc +64 -27
- data/doc/prepared_statements.rdoc +8 -4
- data/doc/reflection.rdoc +8 -2
- data/doc/release_notes/3.23.0.txt +1 -1
- data/doc/release_notes/3.24.0.txt +420 -0
- data/lib/sequel/adapters/db2.rb +8 -1
- data/lib/sequel/adapters/firebird.rb +25 -9
- data/lib/sequel/adapters/informix.rb +4 -19
- data/lib/sequel/adapters/jdbc.rb +34 -17
- data/lib/sequel/adapters/jdbc/h2.rb +5 -0
- data/lib/sequel/adapters/jdbc/informix.rb +31 -0
- data/lib/sequel/adapters/jdbc/jtds.rb +34 -0
- data/lib/sequel/adapters/jdbc/mssql.rb +0 -32
- data/lib/sequel/adapters/jdbc/mysql.rb +9 -0
- data/lib/sequel/adapters/jdbc/sqlserver.rb +46 -0
- data/lib/sequel/adapters/postgres.rb +30 -1
- data/lib/sequel/adapters/shared/access.rb +10 -0
- data/lib/sequel/adapters/shared/informix.rb +45 -0
- data/lib/sequel/adapters/shared/mssql.rb +82 -8
- data/lib/sequel/adapters/shared/mysql.rb +25 -7
- data/lib/sequel/adapters/shared/postgres.rb +39 -6
- data/lib/sequel/adapters/shared/sqlite.rb +57 -5
- data/lib/sequel/adapters/sqlite.rb +8 -3
- data/lib/sequel/adapters/swift/mysql.rb +9 -0
- data/lib/sequel/ast_transformer.rb +190 -0
- data/lib/sequel/core.rb +1 -1
- data/lib/sequel/database/misc.rb +6 -0
- data/lib/sequel/database/query.rb +33 -3
- data/lib/sequel/database/schema_methods.rb +6 -2
- data/lib/sequel/dataset/features.rb +6 -0
- data/lib/sequel/dataset/prepared_statements.rb +17 -2
- data/lib/sequel/dataset/query.rb +17 -0
- data/lib/sequel/dataset/sql.rb +2 -53
- data/lib/sequel/exceptions.rb +4 -0
- data/lib/sequel/extensions/to_dot.rb +95 -83
- data/lib/sequel/model.rb +5 -0
- data/lib/sequel/model/associations.rb +80 -14
- data/lib/sequel/model/base.rb +182 -55
- data/lib/sequel/model/exceptions.rb +3 -1
- data/lib/sequel/plugins/association_pks.rb +6 -4
- data/lib/sequel/plugins/defaults_setter.rb +58 -0
- data/lib/sequel/plugins/many_through_many.rb +8 -3
- data/lib/sequel/plugins/prepared_statements.rb +140 -0
- data/lib/sequel/plugins/prepared_statements_associations.rb +84 -0
- data/lib/sequel/plugins/prepared_statements_safe.rb +72 -0
- data/lib/sequel/plugins/prepared_statements_with_pk.rb +59 -0
- data/lib/sequel/sql.rb +8 -0
- data/lib/sequel/version.rb +1 -1
- data/spec/adapters/postgres_spec.rb +43 -18
- data/spec/core/connection_pool_spec.rb +56 -77
- data/spec/core/database_spec.rb +25 -0
- data/spec/core/dataset_spec.rb +127 -16
- data/spec/core/expression_filters_spec.rb +13 -0
- data/spec/core/schema_spec.rb +6 -1
- data/spec/extensions/association_pks_spec.rb +7 -0
- data/spec/extensions/defaults_setter_spec.rb +64 -0
- data/spec/extensions/many_through_many_spec.rb +60 -4
- data/spec/extensions/nested_attributes_spec.rb +1 -0
- data/spec/extensions/prepared_statements_associations_spec.rb +126 -0
- data/spec/extensions/prepared_statements_safe_spec.rb +69 -0
- data/spec/extensions/prepared_statements_spec.rb +72 -0
- data/spec/extensions/prepared_statements_with_pk_spec.rb +38 -0
- data/spec/extensions/to_dot_spec.rb +3 -5
- data/spec/integration/associations_test.rb +155 -1
- data/spec/integration/dataset_test.rb +8 -1
- data/spec/integration/plugin_test.rb +119 -0
- data/spec/integration/prepared_statement_test.rb +72 -1
- data/spec/integration/schema_test.rb +66 -8
- data/spec/integration/transaction_test.rb +40 -0
- data/spec/model/associations_spec.rb +349 -8
- data/spec/model/base_spec.rb +59 -0
- data/spec/model/hooks_spec.rb +161 -0
- data/spec/model/record_spec.rb +24 -0
- metadata +21 -4
data/lib/sequel/dataset/sql.rb
CHANGED
@@ -269,6 +269,7 @@ module Sequel
|
|
269
269
|
literal(op == :IN ? expr : ~expr)
|
270
270
|
else
|
271
271
|
old_vals = vals
|
272
|
+
vals = vals.naked if vals.is_a?(Sequel::Dataset)
|
272
273
|
vals = vals.to_a
|
273
274
|
val_cols = old_vals.columns
|
274
275
|
complex_expression_sql(op, [cols, vals.map!{|x| x.values_at(*val_cols)}])
|
@@ -774,59 +775,7 @@ module Sequel
|
|
774
775
|
|
775
776
|
# Qualify the given expression e to the given table.
|
776
777
|
def qualified_expression(e, table)
|
777
|
-
|
778
|
-
when Symbol
|
779
|
-
t, column, aliaz = split_symbol(e)
|
780
|
-
if t
|
781
|
-
e
|
782
|
-
elsif aliaz
|
783
|
-
SQL::AliasedExpression.new(SQL::QualifiedIdentifier.new(table, SQL::Identifier.new(column)), aliaz)
|
784
|
-
else
|
785
|
-
SQL::QualifiedIdentifier.new(table, e)
|
786
|
-
end
|
787
|
-
when Array
|
788
|
-
e.map{|a| qualified_expression(a, table)}
|
789
|
-
when Hash
|
790
|
-
h = {}
|
791
|
-
e.each{|k,v| h[qualified_expression(k, table)] = qualified_expression(v, table)}
|
792
|
-
h
|
793
|
-
when SQL::Identifier
|
794
|
-
SQL::QualifiedIdentifier.new(table, e)
|
795
|
-
when SQL::OrderedExpression
|
796
|
-
SQL::OrderedExpression.new(qualified_expression(e.expression, table), e.descending, :nulls=>e.nulls)
|
797
|
-
when SQL::AliasedExpression
|
798
|
-
SQL::AliasedExpression.new(qualified_expression(e.expression, table), e.aliaz)
|
799
|
-
when SQL::CaseExpression
|
800
|
-
args = [qualified_expression(e.conditions, table), qualified_expression(e.default, table)]
|
801
|
-
args << qualified_expression(e.expression, table) if e.expression?
|
802
|
-
SQL::CaseExpression.new(*args)
|
803
|
-
when SQL::Cast
|
804
|
-
SQL::Cast.new(qualified_expression(e.expr, table), e.type)
|
805
|
-
when SQL::Function
|
806
|
-
SQL::Function.new(e.f, *qualified_expression(e.args, table))
|
807
|
-
when SQL::ComplexExpression
|
808
|
-
SQL::ComplexExpression.new(e.op, *qualified_expression(e.args, table))
|
809
|
-
when SQL::Subscript
|
810
|
-
SQL::Subscript.new(qualified_expression(e.f, table), qualified_expression(e.sub, table))
|
811
|
-
when SQL::WindowFunction
|
812
|
-
SQL::WindowFunction.new(qualified_expression(e.function, table), qualified_expression(e.window, table))
|
813
|
-
when SQL::Window
|
814
|
-
o = e.opts.dup
|
815
|
-
o[:partition] = qualified_expression(o[:partition], table) if o[:partition]
|
816
|
-
o[:order] = qualified_expression(o[:order], table) if o[:order]
|
817
|
-
SQL::Window.new(o)
|
818
|
-
when SQL::PlaceholderLiteralString
|
819
|
-
args = if e.args.is_a?(Hash)
|
820
|
-
h = {}
|
821
|
-
e.args.each{|k,v| h[k] = qualified_expression(v, table)}
|
822
|
-
h
|
823
|
-
else
|
824
|
-
qualified_expression(e.args, table)
|
825
|
-
end
|
826
|
-
SQL::PlaceholderLiteralString.new(e.str, args, e.parens)
|
827
|
-
else
|
828
|
-
e
|
829
|
-
end
|
778
|
+
Qualifier.new(self, table).transform(e)
|
830
779
|
end
|
831
780
|
|
832
781
|
# The order of methods to call to build the SELECT SQL statement
|
data/lib/sequel/exceptions.rb
CHANGED
@@ -44,6 +44,10 @@ module Sequel
|
|
44
44
|
# and won't reraise it.
|
45
45
|
class Rollback < Error ; end
|
46
46
|
|
47
|
+
# Exception that occurs when unbinding a dataset that has multiple different values
|
48
|
+
# for a given variable.
|
49
|
+
class UnbindDuplicate < Error; end
|
50
|
+
|
47
51
|
class Error
|
48
52
|
AdapterNotFound = Sequel::AdapterNotFound
|
49
53
|
InvalidOperation = Sequel::InvalidOperation
|
@@ -4,109 +4,111 @@
|
|
4
4
|
# of the dataset's abstract syntax tree.
|
5
5
|
|
6
6
|
module Sequel
|
7
|
-
class
|
7
|
+
class ToDot
|
8
8
|
# The option keys that should be included in the dot output.
|
9
9
|
TO_DOT_OPTIONS = [:with, :distinct, :select, :from, :join, :where, :group, :having, :compounds, :order, :limit, :offset, :lock].freeze
|
10
10
|
|
11
|
-
#
|
12
|
-
#
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
11
|
+
# Given a +Dataset+, return a string in +dot+ format that will
|
12
|
+
# generate a visualization of the dataset.
|
13
|
+
def self.output(ds)
|
14
|
+
new(ds).output
|
15
|
+
end
|
16
|
+
|
17
|
+
# Given a +Dataset+, parse the internal structure to generate
|
18
|
+
# a dataset visualization.
|
19
|
+
def initialize(ds)
|
20
|
+
@i = 0
|
21
|
+
@stack = [@i]
|
22
|
+
@dot = ["digraph G {", "0 [label=\"self\"];"]
|
23
|
+
v(ds, "")
|
24
|
+
@dot << "}"
|
25
|
+
end
|
26
|
+
|
27
|
+
# Output the dataset visualization as a string in +dot+ format.
|
28
|
+
def output
|
29
|
+
@dot.join("\n")
|
20
30
|
end
|
21
31
|
|
22
32
|
private
|
23
33
|
|
24
|
-
#
|
25
|
-
#
|
26
|
-
#
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
#
|
32
|
-
#
|
33
|
-
#
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
# its children are then added by recursively calling this method. The
|
39
|
-
# return value is the integer representing the last created node.
|
40
|
-
def _to_dot(dot, l, c, e, i)
|
41
|
-
i += 1
|
42
|
-
dot << "#{c} -> #{i} [label=\"#{l}\"];" if l
|
43
|
-
c = i
|
34
|
+
# Add an entry to the +dot+ output with the given label. If +j+
|
35
|
+
# is given, it is used directly as the node or transition. Otherwise
|
36
|
+
# a node is created for the current object.
|
37
|
+
def dot(label, j=nil)
|
38
|
+
@dot << "#{j||@i} [label=#{label.to_s.inspect}];"
|
39
|
+
end
|
40
|
+
|
41
|
+
# Recursive method that parses all of Sequel's internal datastructures,
|
42
|
+
# adding the appropriate nodes and transitions to the internal +dot+
|
43
|
+
# structure.
|
44
|
+
def v(e, l)
|
45
|
+
@i += 1
|
46
|
+
dot(l, "#{@stack.last} -> #{@i}") if l
|
47
|
+
@stack.push(@i)
|
44
48
|
case e
|
45
49
|
when LiteralString
|
46
|
-
dot
|
47
|
-
i
|
50
|
+
dot "#{e.inspect}.lit"
|
48
51
|
when Symbol, Numeric, String, Class, TrueClass, FalseClass, NilClass
|
49
|
-
dot
|
50
|
-
i
|
52
|
+
dot e.inspect
|
51
53
|
when Array
|
52
|
-
dot
|
53
|
-
e.each_with_index do |
|
54
|
-
|
54
|
+
dot "Array"
|
55
|
+
e.each_with_index do |val, j|
|
56
|
+
v(val, j)
|
55
57
|
end
|
56
58
|
when Hash
|
57
|
-
dot
|
58
|
-
e.each do |k,
|
59
|
-
|
59
|
+
dot "Hash"
|
60
|
+
e.each do |k, val|
|
61
|
+
v(val, k)
|
60
62
|
end
|
61
63
|
when SQL::ComplexExpression
|
62
|
-
dot
|
63
|
-
e.args.each_with_index do |
|
64
|
-
|
64
|
+
dot "ComplexExpression: #{e.op}"
|
65
|
+
e.args.each_with_index do |val, j|
|
66
|
+
v(val, j)
|
65
67
|
end
|
66
68
|
when SQL::Identifier
|
67
|
-
dot
|
68
|
-
|
69
|
+
dot "Identifier"
|
70
|
+
v(e.value, :value)
|
69
71
|
when SQL::QualifiedIdentifier
|
70
|
-
dot
|
71
|
-
|
72
|
-
|
72
|
+
dot "QualifiedIdentifier"
|
73
|
+
v(e.table, :table)
|
74
|
+
v(e.column, :column)
|
73
75
|
when SQL::OrderedExpression
|
74
|
-
dot
|
75
|
-
|
76
|
+
dot "OrderedExpression: #{e.descending ? :DESC : :ASC}#{" NULLS #{e.nulls.to_s.upcase}" if e.nulls}"
|
77
|
+
v(e.expression, :expression)
|
76
78
|
when SQL::AliasedExpression
|
77
|
-
dot
|
78
|
-
|
79
|
-
|
79
|
+
dot "AliasedExpression"
|
80
|
+
v(e.expression, :expression)
|
81
|
+
v(e.aliaz, :alias)
|
80
82
|
when SQL::CaseExpression
|
81
|
-
dot
|
82
|
-
|
83
|
-
|
84
|
-
|
83
|
+
dot "CaseExpression"
|
84
|
+
v(e.expression, :expression) if e.expression
|
85
|
+
v(e.conditions, :conditions)
|
86
|
+
v(e.default, :default)
|
85
87
|
when SQL::Cast
|
86
|
-
dot
|
87
|
-
|
88
|
-
|
88
|
+
dot "Cast"
|
89
|
+
v(e.expr, :expr)
|
90
|
+
v(e.type, :type)
|
89
91
|
when SQL::Function
|
90
|
-
dot
|
91
|
-
e.args.each_with_index do |
|
92
|
-
|
92
|
+
dot "Function: #{e.f}"
|
93
|
+
e.args.each_with_index do |val, j|
|
94
|
+
v(val, j)
|
93
95
|
end
|
94
96
|
when SQL::Subscript
|
95
|
-
dot
|
96
|
-
|
97
|
-
|
97
|
+
dot "Subscript"
|
98
|
+
v(e.f, :f)
|
99
|
+
v(e.sub, :sub)
|
98
100
|
when SQL::WindowFunction
|
99
|
-
dot
|
100
|
-
|
101
|
-
|
101
|
+
dot "WindowFunction"
|
102
|
+
v(e.function, :function)
|
103
|
+
v(e.window, :window)
|
102
104
|
when SQL::Window
|
103
|
-
dot
|
104
|
-
|
105
|
+
dot "Window"
|
106
|
+
v(e.opts, :opts)
|
105
107
|
when SQL::PlaceholderLiteralString
|
106
108
|
str = e.str
|
107
109
|
str = "(#{str})" if e.parens
|
108
|
-
dot
|
109
|
-
|
110
|
+
dot "PlaceholderLiteralString: #{str.inspect}"
|
111
|
+
v(e.args, :args)
|
110
112
|
when SQL::JoinClause
|
111
113
|
str = "#{e.join_type.to_s.upcase} JOIN"
|
112
114
|
if e.is_a?(SQL::JoinOnClause)
|
@@ -114,24 +116,34 @@ module Sequel
|
|
114
116
|
elsif e.is_a?(SQL::JoinUsingClause)
|
115
117
|
str << " USING"
|
116
118
|
end
|
117
|
-
dot
|
118
|
-
|
119
|
-
|
119
|
+
dot str
|
120
|
+
v(e.table, :table)
|
121
|
+
v(e.table_alias, :alias) if e.table_alias
|
120
122
|
if e.is_a?(SQL::JoinOnClause)
|
121
|
-
|
123
|
+
v(e.on, :on)
|
122
124
|
elsif e.is_a?(SQL::JoinUsingClause)
|
123
|
-
|
125
|
+
v(e.using, :using)
|
124
126
|
end
|
125
127
|
when Dataset
|
126
|
-
dot
|
128
|
+
dot "Dataset"
|
127
129
|
TO_DOT_OPTIONS.each do |k|
|
128
|
-
|
129
|
-
|
130
|
+
if val = e.opts[k]
|
131
|
+
v(val, k.to_s)
|
132
|
+
end
|
130
133
|
end
|
131
134
|
else
|
132
|
-
dot
|
135
|
+
dot "Unhandled: #{e.inspect}"
|
133
136
|
end
|
134
|
-
|
137
|
+
@stack.pop
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
class Dataset
|
142
|
+
# Return a string that can be processed by the +dot+ program (included
|
143
|
+
# with graphviz) in order to see a visualization of the dataset's
|
144
|
+
# abstract syntax tree.
|
145
|
+
def to_dot
|
146
|
+
ToDot.output(self)
|
135
147
|
end
|
136
148
|
end
|
137
149
|
end
|
data/lib/sequel/model.rb
CHANGED
@@ -80,6 +80,11 @@ module Sequel
|
|
80
80
|
# +super+ on the first line of your method, so later hooks are called after earlier hooks.
|
81
81
|
AFTER_HOOKS = [:after_initialize, :after_create, :after_update, :after_save, :after_destroy, :after_validation]
|
82
82
|
|
83
|
+
# Hooks that are called around an action. If overridden, these methods must call super
|
84
|
+
# exactly once if the behavior they wrap is desired. The can be used to rescue exceptions
|
85
|
+
# raised by the code they wrap or ensure that some behavior is executed no matter what.
|
86
|
+
AROUND_HOOKS = [:around_create, :around_update, :around_save, :around_destroy, :around_validation]
|
87
|
+
|
83
88
|
# Empty instance methods to create that the user can override to get hook/callback behavior.
|
84
89
|
# Just like any other method defined by Sequel, if you override one of these, you should
|
85
90
|
# call +super+ to get the default behavior (while empty by default, they can also be defined
|
@@ -1415,18 +1415,40 @@ module Sequel
|
|
1415
1415
|
# types, this is a simple transformation, but for +many_to_many+ associations this
|
1416
1416
|
# creates a subquery to the join table.
|
1417
1417
|
def complex_expression_sql(op, args)
|
1418
|
-
|
1419
|
-
|
1418
|
+
r = args.at(1)
|
1419
|
+
if (((op == :'=' || op == :'!=') and r.is_a?(Sequel::Model)) ||
|
1420
|
+
(multiple = ((op == :IN || op == :'NOT IN') and ((is_ds = r.is_a?(Sequel::Dataset)) or r.all?{|x| x.is_a?(Sequel::Model)}))))
|
1421
|
+
l = args.at(0)
|
1420
1422
|
if ar = model.association_reflections[l]
|
1421
|
-
|
1423
|
+
if multiple
|
1424
|
+
klass = ar.associated_class
|
1425
|
+
if is_ds
|
1426
|
+
if r.respond_to?(:model)
|
1427
|
+
unless r.model <= klass
|
1428
|
+
# A dataset for a different model class, could be a valid regular query
|
1429
|
+
return super
|
1430
|
+
end
|
1431
|
+
else
|
1432
|
+
# Not a model dataset, could be a valid regular query
|
1433
|
+
return super
|
1434
|
+
end
|
1435
|
+
else
|
1436
|
+
unless r.all?{|x| x.is_a?(klass)}
|
1437
|
+
raise Sequel::Error, "invalid association class for one object for association #{l.inspect} used in dataset filter for model #{model.inspect}, expected class #{klass.inspect}"
|
1438
|
+
end
|
1439
|
+
end
|
1440
|
+
elsif !r.is_a?(ar.associated_class)
|
1422
1441
|
raise Sequel::Error, "invalid association class #{r.class.inspect} for association #{l.inspect} used in dataset filter for model #{model.inspect}, expected class #{ar.associated_class.inspect}"
|
1423
1442
|
end
|
1424
1443
|
|
1425
|
-
if exp = association_filter_expression(ar, r)
|
1444
|
+
if exp = association_filter_expression(op, ar, r)
|
1426
1445
|
literal(exp)
|
1427
1446
|
else
|
1428
1447
|
raise Sequel::Error, "invalid association type #{ar[:type].inspect} for association #{l.inspect} used in dataset filter for model #{model.inspect}"
|
1429
1448
|
end
|
1449
|
+
elsif multiple && (is_ds || r.empty?)
|
1450
|
+
# Not a query designed for this support, could be a valid regular query
|
1451
|
+
super
|
1430
1452
|
else
|
1431
1453
|
raise Sequel::Error, "invalid association #{l.inspect} used in dataset filter for model #{model.inspect}"
|
1432
1454
|
end
|
@@ -1577,9 +1599,46 @@ module Sequel
|
|
1577
1599
|
private
|
1578
1600
|
|
1579
1601
|
# Return an expression for filtering by the given association reflection and associated object.
|
1580
|
-
def association_filter_expression(ref, obj)
|
1602
|
+
def association_filter_expression(op, ref, obj)
|
1581
1603
|
meth = :"#{ref[:type]}_association_filter_expression"
|
1582
|
-
send(meth, ref, obj) if respond_to?(meth, true)
|
1604
|
+
send(meth, op, ref, obj) if respond_to?(meth, true)
|
1605
|
+
end
|
1606
|
+
|
1607
|
+
# Handle inversion for association filters by returning an inverted expression,
|
1608
|
+
# plus also handling cases where the referenced columns are NULL.
|
1609
|
+
def association_filter_handle_inversion(op, exp, cols)
|
1610
|
+
if op == :'!=' || op == :'NOT IN'
|
1611
|
+
if exp == SQL::Constants::FALSE
|
1612
|
+
~exp
|
1613
|
+
else
|
1614
|
+
~exp | Sequel::SQL::BooleanExpression.from_value_pairs(cols.zip([]), :OR)
|
1615
|
+
end
|
1616
|
+
else
|
1617
|
+
exp
|
1618
|
+
end
|
1619
|
+
end
|
1620
|
+
|
1621
|
+
# Return an expression for making sure that the given keys match the value of
|
1622
|
+
# the given methods for either the single object given or for any of the objects
|
1623
|
+
# given if +obj+ is an array.
|
1624
|
+
def association_filter_key_expression(keys, meths, obj)
|
1625
|
+
vals = if obj.is_a?(Sequel::Dataset)
|
1626
|
+
{(keys.length == 1 ? keys.first : keys)=>obj.select(*meths).exclude(Sequel::SQL::BooleanExpression.from_value_pairs(meths.zip([]), :OR))}
|
1627
|
+
else
|
1628
|
+
vals = Array(obj).reject{|o| !meths.all?{|m| o.send(m)}}
|
1629
|
+
return SQL::Constants::FALSE if vals.empty?
|
1630
|
+
if obj.is_a?(Array)
|
1631
|
+
if keys.length == 1
|
1632
|
+
meth = meths.first
|
1633
|
+
{keys.first=>vals.map{|o| o.send(meth)}}
|
1634
|
+
else
|
1635
|
+
{keys=>vals.map{|o| meths.map{|m| o.send(m)}}}
|
1636
|
+
end
|
1637
|
+
else
|
1638
|
+
keys.zip(meths.map{|k| obj.send(k)})
|
1639
|
+
end
|
1640
|
+
end
|
1641
|
+
SQL::BooleanExpression.from_value_pairs(vals)
|
1583
1642
|
end
|
1584
1643
|
|
1585
1644
|
# Make sure the association is valid for this model, and return the related AssociationReflection.
|
@@ -1685,20 +1744,27 @@ module Sequel
|
|
1685
1744
|
end
|
1686
1745
|
|
1687
1746
|
# Return a subquery expression for filering by a many_to_many association
|
1688
|
-
def many_to_many_association_filter_expression(ref, obj)
|
1689
|
-
|
1690
|
-
|
1691
|
-
|
1747
|
+
def many_to_many_association_filter_expression(op, ref, obj)
|
1748
|
+
lpks, lks, rks = ref.values_at(:left_primary_keys, :left_keys, :right_keys)
|
1749
|
+
lpks = lpks.first if lpks.length == 1
|
1750
|
+
exp = association_filter_key_expression(rks, ref.right_primary_keys, obj)
|
1751
|
+
if exp == SQL::Constants::FALSE
|
1752
|
+
association_filter_handle_inversion(op, exp, Array(lpks))
|
1753
|
+
else
|
1754
|
+
association_filter_handle_inversion(op, SQL::BooleanExpression.from_value_pairs(lpks=>model.db[ref[:join_table]].select(*lks).where(exp).exclude(SQL::BooleanExpression.from_value_pairs(lks.zip([]), :OR))), Array(lpks))
|
1755
|
+
end
|
1692
1756
|
end
|
1693
1757
|
|
1694
1758
|
# Return a simple equality expression for filering by a many_to_one association
|
1695
|
-
def many_to_one_association_filter_expression(ref, obj)
|
1696
|
-
|
1759
|
+
def many_to_one_association_filter_expression(op, ref, obj)
|
1760
|
+
keys = ref[:keys]
|
1761
|
+
association_filter_handle_inversion(op, association_filter_key_expression(keys, ref.primary_keys, obj), keys)
|
1697
1762
|
end
|
1698
1763
|
|
1699
1764
|
# Return a simple equality expression for filering by a one_to_* association
|
1700
|
-
def one_to_many_association_filter_expression(ref, obj)
|
1701
|
-
|
1765
|
+
def one_to_many_association_filter_expression(op, ref, obj)
|
1766
|
+
keys = ref[:primary_keys]
|
1767
|
+
association_filter_handle_inversion(op, association_filter_key_expression(keys, ref[:keys], obj), keys)
|
1702
1768
|
end
|
1703
1769
|
alias one_to_one_association_filter_expression one_to_many_association_filter_expression
|
1704
1770
|
|