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