-
Notifications
You must be signed in to change notification settings - Fork 5.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add exists subquery support. #144
Changes from all commits
76475b2
22612fd
f4e06f0
b7e641b
aab38d7
8849578
923dc22
ab51ff9
cb38257
d8246b7
c29d726
aae463b
026df59
45d4346
c6186c6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
// Copyright 2015 PingCAP, Inc. | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
package expressions | ||
|
||
import ( | ||
"fmt" | ||
|
||
"github.com/juju/errors" | ||
"github.com/pingcap/tidb/context" | ||
"github.com/pingcap/tidb/expression" | ||
) | ||
|
||
// ExistsSubQuery is the expression for "exists (select ...)". | ||
// https://dev.mysql.com/doc/refman/5.7/en/exists-and-not-exists-subqueries.html | ||
type ExistsSubQuery struct { | ||
// Sel is the sub query. | ||
Sel *SubQuery | ||
} | ||
|
||
// Clone implements the Expression Clone interface. | ||
func (es *ExistsSubQuery) Clone() (expression.Expression, error) { | ||
sel, err := es.Sel.Clone() | ||
if err != nil { | ||
return nil, errors.Trace(err) | ||
} | ||
|
||
return &ExistsSubQuery{Sel: sel.(*SubQuery)}, nil | ||
} | ||
|
||
// IsStatic implements the Expression IsStatic interface. | ||
func (es *ExistsSubQuery) IsStatic() bool { | ||
return es.Sel.IsStatic() | ||
} | ||
|
||
// String implements the Expression String interface. | ||
func (es *ExistsSubQuery) String() string { | ||
return fmt.Sprintf("EXISTS %s", es.Sel) | ||
} | ||
|
||
// Eval implements the Expression Eval interface. | ||
func (es *ExistsSubQuery) Eval(ctx context.Context, args map[interface{}]interface{}) (interface{}, error) { | ||
if es.Sel.Value != nil { | ||
return true, nil | ||
} | ||
|
||
p, err := es.Sel.Plan(ctx) | ||
if err != nil { | ||
return nil, errors.Trace(err) | ||
} | ||
defer p.Close() | ||
|
||
r, err := p.Next(ctx) | ||
if err != nil { | ||
return nil, errors.Trace(err) | ||
} | ||
if r != nil { | ||
es.Sel.Value = r | ||
return true, nil | ||
} | ||
|
||
return false, nil | ||
} | ||
|
||
// NewExistsSubQuery creates a ExistsSubQuery object. | ||
func NewExistsSubQuery(sel *SubQuery) *ExistsSubQuery { | ||
return &ExistsSubQuery{Sel: sel} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
// Copyright 2015 PingCAP, Inc. | ||
// | ||
// Licensed under the Apache License, Version 2.0 (the "License"); | ||
// you may not use this file except in compliance with the License. | ||
// You may obtain a copy of the License at | ||
// | ||
// http://www.apache.org/licenses/LICENSE-2.0 | ||
// | ||
// Unless required by applicable law or agreed to in writing, software | ||
// distributed under the License is distributed on an "AS IS" BASIS, | ||
// See the License for the specific language governing permissions and | ||
// limitations under the License. | ||
|
||
package expressions | ||
|
||
import ( | ||
. "github.com/pingcap/check" | ||
"github.com/pingcap/tidb/parser/opcode" | ||
"github.com/pingcap/tidb/util/types" | ||
) | ||
|
||
var _ = Suite(&testExistsSubQuerySuite{}) | ||
|
||
type testExistsSubQuerySuite struct { | ||
} | ||
|
||
func (s *testExistsSubQuerySuite) TestExistsSubQuery(c *C) { | ||
// Test exists subquery. | ||
tbl := []struct { | ||
in []interface{} | ||
result int64 // 0 for false, 1 for true. | ||
}{ | ||
{[]interface{}{1}, 1}, | ||
{[]interface{}{nil}, 1}, | ||
{[]interface{}{}, 0}, | ||
} | ||
|
||
for _, t := range tbl { | ||
in := make([][]interface{}, 0, len(t.in)) | ||
for _, v := range t.in { | ||
in = append(in, []interface{}{convert(v)}) | ||
} | ||
|
||
sq := newMockSubQuery(in, []string{"c"}) | ||
expr := NewExistsSubQuery(sq) | ||
|
||
c.Assert(expr.IsStatic(), IsFalse) | ||
|
||
exprc, err := expr.Clone() | ||
c.Assert(err, IsNil) | ||
|
||
str := exprc.String() | ||
c.Assert(len(str), Greater, 0) | ||
|
||
v, err := exprc.Eval(nil, nil) | ||
c.Assert(err, IsNil) | ||
|
||
val, err := types.ToBool(v) | ||
c.Assert(err, IsNil) | ||
c.Assert(val, Equals, t.result) | ||
} | ||
|
||
// Test not exists subquery. | ||
tbl = []struct { | ||
in []interface{} | ||
result int64 // 0 for false, 1 for true. | ||
}{ | ||
{[]interface{}{1}, 0}, | ||
{[]interface{}{nil}, 0}, | ||
{[]interface{}{}, 1}, | ||
} | ||
for _, t := range tbl { | ||
in := make([][]interface{}, 0, len(t.in)) | ||
for _, v := range t.in { | ||
in = append(in, []interface{}{convert(v)}) | ||
} | ||
|
||
sq := newMockSubQuery(in, []string{"c"}) | ||
es := NewExistsSubQuery(sq) | ||
|
||
c.Assert(es.IsStatic(), IsFalse) | ||
|
||
str := es.String() | ||
c.Assert(len(str), Greater, 0) | ||
|
||
expr := NewUnaryOperation(opcode.Not, es) | ||
|
||
exprc, err := expr.Clone() | ||
c.Assert(err, IsNil) | ||
|
||
str = exprc.String() | ||
c.Assert(len(str), Greater, 0) | ||
|
||
v, err := exprc.Eval(nil, nil) | ||
c.Assert(err, IsNil) | ||
|
||
val, err := types.ToBool(v) | ||
c.Assert(err, IsNil) | ||
c.Assert(val, Equals, t.result) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -294,6 +294,15 @@ func (s *testParserSuite) TestParser0(c *C) { | |
{"SELECT 1 > ALL (select 1)", true}, | ||
{"SELECT 1 > SOME (select 1)", true}, | ||
|
||
// For exists subquery | ||
{"SELECT EXISTS select 1", false}, | ||
{"SELECT EXISTS (select 1)", true}, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. no not exists test? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. add not exists test here too. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Got it. |
||
{"SELECT + EXISTS (select 1)", true}, | ||
{"SELECT - EXISTS (select 1)", true}, | ||
{"SELECT NOT EXISTS (select 1)", true}, | ||
{"SELECT + NOT EXISTS (select 1)", false}, | ||
{"SELECT - NOT EXISTS (select 1)", false}, | ||
|
||
// For update statement | ||
{"UPDATE t SET id = id + 1 ORDER BY id DESC;", true}, | ||
{"UPDATE items,month SET items.price=month.price WHERE items.id=month.id;", true}, | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if next returns value, we can hold this value for later Eval, so that we don't need to run sub query again.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Got it, my fault.