From f713eb201a4296fb0a276f2322dbe6683c0c9144 Mon Sep 17 00:00:00 2001 From: siddontang Date: Fri, 11 Sep 2015 15:24:48 +0800 Subject: [PATCH 01/12] *: add base compare subquery framework @qiuyesuifeng Conflicts: parser/parser_test.go --- expression/expressions/cmp_subquery.go | 62 +++++++++++++++++++++ expression/expressions/cmp_subquery_test.go | 14 +++++ expression/expressions/helper.go | 4 ++ parser/parser.y | 45 +++++++++++++++ parser/parser_test.go | 6 ++ parser/scanner.l | 4 ++ 6 files changed, 135 insertions(+) create mode 100644 expression/expressions/cmp_subquery.go create mode 100644 expression/expressions/cmp_subquery_test.go diff --git a/expression/expressions/cmp_subquery.go b/expression/expressions/cmp_subquery.go new file mode 100644 index 0000000000..be5db3988f --- /dev/null +++ b/expression/expressions/cmp_subquery.go @@ -0,0 +1,62 @@ +// 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/tidb/context" + "github.com/pingcap/tidb/expression" + "github.com/pingcap/tidb/parser/opcode" +) + +// CompareSubQuery is the expression for "expr cmp (select ...)". +type CompareSubQuery struct { + // L is the left expression + L expression.Expression + // Op is the comparison opcode. + Op opcode.Op + // R is the sub query for right expression. + R *SubQuery + // All is true, we should compare all records in subquery. + All bool +} + +// Clone implements the Expression Clone interface. +func (s *CompareSubQuery) Clone() (expression.Expression, error) { + return nil, nil +} + +// IsStatic implements the Expression IsStatic interface. +func (s *CompareSubQuery) IsStatic() bool { + return false +} + +// String implements the Expression String interface. +func (s *CompareSubQuery) String() string { + return "" +} + +// Eval implements the Expression Eval interface. +func (s *CompareSubQuery) Eval(ctx context.Context, args map[interface{}]interface{}) (interface{}, error) { + return nil, nil +} + +// NewCompareSubQuery creates a CompareSubQuery object. +func NewCompareSubQuery(op opcode.Op, lhs expression.Expression, rhs *SubQuery, all bool) *CompareSubQuery { + return &CompareSubQuery{ + Op: op, + L: lhs, + R: rhs, + All: all, + } +} diff --git a/expression/expressions/cmp_subquery_test.go b/expression/expressions/cmp_subquery_test.go new file mode 100644 index 0000000000..3d4f621023 --- /dev/null +++ b/expression/expressions/cmp_subquery_test.go @@ -0,0 +1,14 @@ +// 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 diff --git a/expression/expressions/helper.go b/expression/expressions/helper.go index 10dc8ad862..8e1cd3fd06 100644 --- a/expression/expressions/helper.go +++ b/expression/expressions/helper.go @@ -226,6 +226,8 @@ func mentionedAggregateFuncs(e expression.Expression, m *[]expression.Expression for _, expr := range x.Values { mentionedAggregateFuncs(expr, m) } + case *CompareSubQuery: + mentionedAggregateFuncs(x.L, m) default: log.Errorf("Unknown Expression: %T", e) } @@ -314,6 +316,8 @@ func mentionedColumns(e expression.Expression, m map[string]bool, names *[]strin for _, expr := range x.Values { mentionedColumns(expr, m, names) } + case *CompareSubQuery: + mentionedColumns(x.L, m, names) default: log.Errorf("Unknown Expression: %T", e) } diff --git a/parser/parser.y b/parser/parser.y index e8ea766902..db1857dbe9 100644 --- a/parser/parser.y +++ b/parser/parser.y @@ -70,6 +70,7 @@ import ( and "AND" andand "&&" andnot "&^" + any "ANY" as "AS" asc "ASC" autoIncrement "AUTO_INCREMENT" @@ -175,6 +176,7 @@ import ( share "SHARE" show "SHOW" signed "SIGNED" + some "SOME" start "START" stringType "string" substring "SUBSTRING" @@ -266,6 +268,7 @@ import ( AlterTableStmt "Alter table statement" AlterSpecification "Alter table specification" AlterSpecificationList "Alter table specification list" + AnyOrAll "Any or All for subquery" AsOpt "as optional" Assignment "assignment" AssignmentList "assignment list" @@ -1373,8 +1376,50 @@ Factor: { $$ = expressions.NewBinaryOperation(opcode.EQ, $1.(expression.Expression), $3.(expression.Expression)) } +| Factor ">=" AnyOrAll SubSelect %prec eq + { + $$ = expressions.NewCompareSubQuery(opcode.GE, $1.(expression.Expression), $4.(*expressions.SubQuery), $3.(bool)) + } +| Factor '>' AnyOrAll SubSelect %prec eq + { + $$ = expressions.NewCompareSubQuery(opcode.GT, $1.(expression.Expression), $4.(*expressions.SubQuery), $3.(bool)) + } +| Factor "<=" AnyOrAll SubSelect %prec eq + { + $$ = expressions.NewCompareSubQuery(opcode.LE, $1.(expression.Expression), $4.(*expressions.SubQuery), $3.(bool)) + } +| Factor '<' AnyOrAll SubSelect %prec eq + { + $$ = expressions.NewCompareSubQuery(opcode.LT, $1.(expression.Expression), $4.(*expressions.SubQuery), $3.(bool)) + } +| Factor "!=" AnyOrAll SubSelect %prec eq + { + $$ = expressions.NewCompareSubQuery(opcode.NE, $1.(expression.Expression), $4.(*expressions.SubQuery), $3.(bool)) + } +| Factor "<>" AnyOrAll SubSelect %prec eq + { + $$ = expressions.NewCompareSubQuery(opcode.NE, $1.(expression.Expression), $4.(*expressions.SubQuery), $3.(bool)) + } +| Factor "=" AnyOrAll SubSelect %prec eq + { + $$ = expressions.NewCompareSubQuery(opcode.EQ, $1.(expression.Expression), $4.(*expressions.SubQuery), $3.(bool)) + } | Factor1 +AnyOrAll: + "ANY" + { + $$ = false + } +| "SOME" + { + $$ = false + } +| "ALL" + { + $$ = true + } + Factor1: PrimaryFactor NotOpt "IN" '(' ExpressionList ')' { diff --git a/parser/parser_test.go b/parser/parser_test.go index eb9593e067..99239fc803 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -282,6 +282,12 @@ func (s *testParserSuite) TestParser0(c *C) { {"SHOW SESSION VARIABLES LIKE 'character_set_results'", true}, {"SHOW VARIABLES", true}, {"SHOW GLOBAL VARIABLES", true}, + + // For compare subquery + {"SELECT 1 > (select 1)", true}, + {"SELECT 1 > ANY (select 1)", true}, + {"SELECT 1 > ALL (select 1)", true}, + {"SELECT 1 > SOME (select 1)", true}, } for _, t := range table { diff --git a/parser/scanner.l b/parser/scanner.l index 68a3e5c1e4..d2b8be634d 100644 --- a/parser/scanner.l +++ b/parser/scanner.l @@ -229,6 +229,7 @@ after {a}{f}{t}{e}{r} all {a}{l}{l} alter {a}{l}{t}{e}{r} and {a}{n}{d} +any {a}{n}{y} as {a}{s} asc {a}{s}{c} auto_increment {a}{u}{t}{o}_{i}{n}{c}{r}{e}{m}{e}{n}{t} @@ -319,6 +320,7 @@ session {s}{e}{s}{s}{i}{o}{n} set {s}{e}{t} share {s}{h}{a}{r}{e} show {s}{h}{o}{w} +some {s}{o}{m}{e} start {s}{t}{a}{r}{t} substring {s}{u}{b}{s}{t}{r}{i}{n}{g} table {t}{a}{b}{l}{e} @@ -459,6 +461,7 @@ sys_var "@@"(({global}".")|({session}".")|{local}".")?{ident} {all} return all {alter} return alter {and} return and +{any} return any {asc} return asc {as} return as {auto_increment} lval.item = string(l.val) @@ -565,6 +568,7 @@ sys_var "@@"(({global}".")|({session}".")|{local}".")?{ident} {schemas} return schemas {session} lval.item = string(l.val) return session +{some} return some {start} lval.item = string(l.val) return start {global} lval.item = string(l.val) From 02c9334da4144868277eb98644073d6cb293a797 Mon Sep 17 00:00:00 2001 From: qiuyesuifeng Date: Fri, 11 Sep 2015 19:50:37 +0800 Subject: [PATCH 02/12] expression/expressions: add all/any for subquery comparation. support subquery. --- expression/expressions/binop.go | 39 +++++--- expression/expressions/cmp_subquery.go | 133 +++++++++++++++++++++++-- 2 files changed, 149 insertions(+), 23 deletions(-) diff --git a/expression/expressions/binop.go b/expression/expressions/binop.go index 6c83d2aaf5..f8c8ab2685 100644 --- a/expression/expressions/binop.go +++ b/expression/expressions/binop.go @@ -364,6 +364,25 @@ func (o *BinaryOperation) evalLogicOp(ctx context.Context, args map[interface{}] } } +func getCompResult(op opcode.Op, value int) (bool, error) { + switch op { + case opcode.LT: + return value < 0, nil + case opcode.LE: + return value <= 0, nil + case opcode.GE: + return value >= 0, nil + case opcode.GT: + return value > 0, nil + case opcode.EQ: + return value == 0, nil + case opcode.NE: + return value != 0, nil + default: + return false, errors.Errorf("invalid op %v in comparision operation", op) + } +} + // operator: >=, >, <=, <, !=, <>, = <=>, etc. // see https://dev.mysql.com/doc/refman/5.7/en/comparison-operators.html func (o *BinaryOperation) evalComparisonOp(ctx context.Context, args map[interface{}]interface{}) (interface{}, error) { @@ -383,22 +402,12 @@ func (o *BinaryOperation) evalComparisonOp(ctx context.Context, args map[interfa return nil, o.traceErr(err) } - switch o.Op { - case opcode.LT: - return n < 0, nil - case opcode.LE: - return n <= 0, nil - case opcode.GE: - return n >= 0, nil - case opcode.GT: - return n > 0, nil - case opcode.EQ: - return n == 0, nil - case opcode.NE: - return n != 0, nil - default: - return nil, o.errorf("invalid op %v in comparision operation", o.Op) + r, err := getCompResult(o.Op, n) + if err != nil { + return nil, o.errorf(err.Error()) } + + return r, nil } func (o *BinaryOperation) evalPlus(a interface{}, b interface{}) (interface{}, error) { diff --git a/expression/expressions/cmp_subquery.go b/expression/expressions/cmp_subquery.go index be5db3988f..31466699b1 100644 --- a/expression/expressions/cmp_subquery.go +++ b/expression/expressions/cmp_subquery.go @@ -14,12 +14,19 @@ package expressions import ( + "fmt" + + "github.com/juju/errors" "github.com/pingcap/tidb/context" "github.com/pingcap/tidb/expression" "github.com/pingcap/tidb/parser/opcode" + "github.com/pingcap/tidb/util/types" ) // CompareSubQuery is the expression for "expr cmp (select ...)". +// See: https://dev.mysql.com/doc/refman/5.7/en/comparisons-using-subqueries.html +// See: https://dev.mysql.com/doc/refman/5.7/en/any-in-some-subqueries.html +// See: https://dev.mysql.com/doc/refman/5.7/en/all-subqueries.html type CompareSubQuery struct { // L is the left expression L expression.Expression @@ -32,23 +39,133 @@ type CompareSubQuery struct { } // Clone implements the Expression Clone interface. -func (s *CompareSubQuery) Clone() (expression.Expression, error) { - return nil, nil +func (cs *CompareSubQuery) Clone() (expression.Expression, error) { + l, err := cs.L.Clone() + if err != nil { + return nil, errors.Trace(err) + } + + r, err := cs.R.Clone() + if err != nil { + return nil, errors.Trace(err) + } + + return &CompareSubQuery{L: l, Op: cs.Op, R: r.(*SubQuery), All: cs.All}, nil } // IsStatic implements the Expression IsStatic interface. -func (s *CompareSubQuery) IsStatic() bool { - return false +func (cs *CompareSubQuery) IsStatic() bool { + return cs.L.IsStatic() && cs.R.IsStatic() } // String implements the Expression String interface. -func (s *CompareSubQuery) String() string { - return "" +func (cs *CompareSubQuery) String() string { + anyOrAll := "any" + if cs.All { + anyOrAll = "all" + } + + return fmt.Sprintf("%s %s %s %s", cs.L, cs.Op, anyOrAll, cs.R) } // Eval implements the Expression Eval interface. -func (s *CompareSubQuery) Eval(ctx context.Context, args map[interface{}]interface{}) (interface{}, error) { - return nil, nil +func (cs *CompareSubQuery) Eval(ctx context.Context, args map[interface{}]interface{}) (interface{}, error) { + in, err := cs.L.Eval(ctx, args) + if err != nil { + return nil, errors.Trace(err) + } + + p, err := cs.R.Plan(ctx) + if err != nil { + return nil, errors.Trace(err) + } + + res := []interface{}{} + err = p.Do(ctx, func(id interface{}, data []interface{}) (bool, error) { + if len(data) == 1 { + res = append(res, data[0]) + } else { + res = append(res, data) + } + return true, nil + }) + if err != nil { + return nil, errors.Trace(err) + } + + return cs.checkResult(in, res) +} + +func (cs *CompareSubQuery) checkAllResult(in interface{}, result []interface{}) (interface{}, error) { + hasNull := false + for _, v := range result { + if v == nil { + hasNull = true + continue + } + + comRes, err := types.Compare(in, v) + if err != nil { + return nil, errors.Trace(err) + } + + res, err := getCompResult(cs.Op, comRes) + if err != nil { + return nil, errors.Trace(err) + } + if !res { + return false, nil + } + } + + if hasNull { + // If no matched but we get null, return null. + // Like `insert t (c) values (1),(2),(null)`, then + // `select 3 > all (select c from t)`, returns null. + return nil, nil + } + + return true, nil +} + +func (cs *CompareSubQuery) checkAnyResult(in interface{}, result []interface{}) (interface{}, error) { + hasNull := false + for _, v := range result { + if v == nil { + hasNull = true + continue + } + + comRes, err := types.Compare(in, v) + if err != nil { + return nil, errors.Trace(err) + } + + res, err := getCompResult(cs.Op, comRes) + if err != nil { + return nil, errors.Trace(err) + } + if res { + return true, nil + } + } + + if hasNull { + // If no matched but we get null, return null. + // Like `insert t (c) values (1),(2),(null)`, then + // `select 0 > any (select c from t)`, returns null. + return nil, nil + } + + return false, nil +} + +func (cs *CompareSubQuery) checkResult(in interface{}, result []interface{}) (interface{}, error) { + if cs.All { + return cs.checkAllResult(in, result) + } + + return cs.checkAnyResult(in, result) } // NewCompareSubQuery creates a CompareSubQuery object. From 1642355dfdf9fc5283d1df9b808ae447bea6ed4c Mon Sep 17 00:00:00 2001 From: qiuyesuifeng Date: Fri, 11 Sep 2015 22:30:54 +0800 Subject: [PATCH 03/12] expression/expressions: add null value check. --- expression/expressions/cmp_subquery.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/expression/expressions/cmp_subquery.go b/expression/expressions/cmp_subquery.go index 31466699b1..2a1b8eb19d 100644 --- a/expression/expressions/cmp_subquery.go +++ b/expression/expressions/cmp_subquery.go @@ -74,6 +74,9 @@ func (cs *CompareSubQuery) Eval(ctx context.Context, args map[interface{}]interf if err != nil { return nil, errors.Trace(err) } + if in == nil { + return nil, nil + } p, err := cs.R.Plan(ctx) if err != nil { From 32edd1c08f2b71271ede39b1468eb21321416916 Mon Sep 17 00:00:00 2001 From: qiuyesuifeng Date: Fri, 11 Sep 2015 22:31:38 +0800 Subject: [PATCH 04/12] expression/expressions: add cmp subquery test. --- expression/expressions/cmp_subquery_test.go | 144 ++++++++++++++++++++ 1 file changed, 144 insertions(+) diff --git a/expression/expressions/cmp_subquery_test.go b/expression/expressions/cmp_subquery_test.go index 3d4f621023..aec0366ff9 100644 --- a/expression/expressions/cmp_subquery_test.go +++ b/expression/expressions/cmp_subquery_test.go @@ -12,3 +12,147 @@ // 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(&testCompSubQuerySuite{}) + +type testCompSubQuerySuite struct { +} + +func (s *testCompSubQuerySuite) convert(v interface{}) interface{} { + switch x := v.(type) { + case nil: + return nil + case int: + return int64(x) + } + + return v +} + +func (s *testCompSubQuerySuite) TestRow(c *C) { + tbl := []struct { + lhs interface{} + op opcode.Op + rhs []interface{} + all bool + result interface{} // 0 for false, 1 for true, nil for nil. + }{ + // Test any subquery. + {nil, opcode.EQ, []interface{}{1, 2}, false, nil}, + {0, opcode.EQ, []interface{}{1, 2}, false, 0}, + {0, opcode.EQ, []interface{}{1, 2, nil}, false, nil}, + {1, opcode.EQ, []interface{}{1, 1}, false, 1}, + {1, opcode.EQ, []interface{}{1, 1, nil}, false, 1}, + {nil, opcode.NE, []interface{}{1, 2}, false, nil}, + {1, opcode.NE, []interface{}{1, 2}, false, 1}, + {1, opcode.NE, []interface{}{1, 2, nil}, false, 1}, + {1, opcode.NE, []interface{}{1, 1}, false, 0}, + {1, opcode.NE, []interface{}{1, 1, nil}, false, nil}, + {nil, opcode.GT, []interface{}{1, 2}, false, nil}, + {1, opcode.GT, []interface{}{1, 2}, false, 0}, + {1, opcode.GT, []interface{}{1, 2, nil}, false, nil}, + {2, opcode.GT, []interface{}{1, 2}, false, 1}, + {2, opcode.GT, []interface{}{1, 2, nil}, false, 1}, + {3, opcode.GT, []interface{}{1, 2}, false, 1}, + {3, opcode.GT, []interface{}{1, 2, nil}, false, 1}, + {nil, opcode.GE, []interface{}{1, 2}, false, nil}, + {0, opcode.GE, []interface{}{1, 2}, false, 0}, + {0, opcode.GE, []interface{}{1, 2, nil}, false, nil}, + {1, opcode.GE, []interface{}{1, 2}, false, 1}, + {1, opcode.GE, []interface{}{1, 2, nil}, false, 1}, + {2, opcode.GE, []interface{}{1, 2}, false, 1}, + {3, opcode.GE, []interface{}{1, 2}, false, 1}, + {nil, opcode.LT, []interface{}{1, 2}, false, nil}, + {0, opcode.LT, []interface{}{1, 2}, false, 1}, + {0, opcode.LT, []interface{}{1, 2, nil}, false, 1}, + {1, opcode.LT, []interface{}{1, 2}, false, 1}, + {2, opcode.LT, []interface{}{1, 2}, false, 0}, + {2, opcode.LT, []interface{}{1, 2, nil}, false, nil}, + {3, opcode.LT, []interface{}{1, 2}, false, 0}, + {nil, opcode.LE, []interface{}{1, 2}, false, nil}, + {0, opcode.LE, []interface{}{1, 2}, false, 1}, + {0, opcode.LE, []interface{}{1, 2, nil}, false, 1}, + {1, opcode.LE, []interface{}{1, 2}, false, 1}, + {2, opcode.LE, []interface{}{1, 2}, false, 1}, + {3, opcode.LE, []interface{}{1, 2}, false, 0}, + {3, opcode.LE, []interface{}{1, 2, nil}, false, nil}, + + // Test all subquery. + {nil, opcode.EQ, []interface{}{1, 2}, true, nil}, + {0, opcode.EQ, []interface{}{1, 2}, true, 0}, + {0, opcode.EQ, []interface{}{1, 2, nil}, true, 0}, + {1, opcode.EQ, []interface{}{1, 2}, true, 0}, + {1, opcode.EQ, []interface{}{1, 2, nil}, true, 0}, + {1, opcode.EQ, []interface{}{1, 1}, true, 1}, + {1, opcode.EQ, []interface{}{1, 1, nil}, true, nil}, + {nil, opcode.NE, []interface{}{1, 2}, true, nil}, + {0, opcode.NE, []interface{}{1, 2}, true, 1}, + {1, opcode.NE, []interface{}{1, 2, nil}, true, 0}, + {1, opcode.NE, []interface{}{1, 1}, true, 0}, + {1, opcode.NE, []interface{}{1, 1, nil}, true, 0}, + {nil, opcode.GT, []interface{}{1, 2}, true, nil}, + {1, opcode.GT, []interface{}{1, 2}, true, 0}, + {1, opcode.GT, []interface{}{1, 2, nil}, true, 0}, + {2, opcode.GT, []interface{}{1, 2}, true, 0}, + {2, opcode.GT, []interface{}{1, 2, nil}, true, 0}, + {3, opcode.GT, []interface{}{1, 2}, true, 1}, + {3, opcode.GT, []interface{}{1, 2, nil}, true, nil}, + {nil, opcode.GE, []interface{}{1, 2}, true, nil}, + {0, opcode.GE, []interface{}{1, 2}, true, 0}, + {0, opcode.GE, []interface{}{1, 2, nil}, true, 0}, + {1, opcode.GE, []interface{}{1, 2}, true, 0}, + {1, opcode.GE, []interface{}{1, 2, nil}, true, 0}, + {2, opcode.GE, []interface{}{1, 2}, true, 1}, + {3, opcode.GE, []interface{}{1, 2}, true, 1}, + {3, opcode.GE, []interface{}{1, 2, nil}, true, nil}, + {nil, opcode.LT, []interface{}{1, 2}, true, nil}, + {0, opcode.LT, []interface{}{1, 2}, true, 1}, + {0, opcode.LT, []interface{}{1, 2, nil}, true, nil}, + {1, opcode.LT, []interface{}{1, 2}, true, 0}, + {2, opcode.LT, []interface{}{1, 2}, true, 0}, + {2, opcode.LT, []interface{}{1, 2, nil}, true, 0}, + {3, opcode.LT, []interface{}{1, 2}, true, 0}, + {nil, opcode.LE, []interface{}{1, 2}, true, nil}, + {0, opcode.LE, []interface{}{1, 2}, true, 1}, + {0, opcode.LE, []interface{}{1, 2, nil}, true, nil}, + {1, opcode.LE, []interface{}{1, 2}, true, 1}, + {2, opcode.LE, []interface{}{1, 2}, true, 0}, + {3, opcode.LE, []interface{}{1, 2}, true, 0}, + {3, opcode.LE, []interface{}{1, 2, nil}, true, 0}, + } + + for _, t := range tbl { + lhs := s.convert(t.lhs) + + rhs := make([][]interface{}, 0, len(t.rhs)) + for _, v := range t.rhs { + rhs = append(rhs, []interface{}{s.convert(v)}) + } + + sq := newMockSubQuery(rhs, []string{"id"}) + expr := NewCompareSubQuery(t.op, Value{lhs}, sq, t.all) + + c.Assert(expr.IsStatic(), IsFalse) + + str := expr.String() + c.Assert(len(str), Greater, 0) + + v, err := expr.Eval(nil, nil) + c.Assert(err, IsNil) + + switch x := t.result.(type) { + case nil: + c.Assert(v, IsNil) + case int: + val, err := types.ToBool(v) + c.Assert(err, IsNil) + c.Assert(val, Equals, int64(x)) + } + } +} From 09502ecca265f7e9aad3d806df31630467d1c78a Mon Sep 17 00:00:00 2001 From: qiuyesuifeng Date: Fri, 11 Sep 2015 22:51:54 +0800 Subject: [PATCH 05/12] expression/expressions: add column count check. --- expression/expressions/cmp_subquery.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/expression/expressions/cmp_subquery.go b/expression/expressions/cmp_subquery.go index 2a1b8eb19d..bdac5a1bc7 100644 --- a/expression/expressions/cmp_subquery.go +++ b/expression/expressions/cmp_subquery.go @@ -70,6 +70,10 @@ func (cs *CompareSubQuery) String() string { // Eval implements the Expression Eval interface. func (cs *CompareSubQuery) Eval(ctx context.Context, args map[interface{}]interface{}) (interface{}, error) { + if err := hasSameColumnCount(ctx, cs.L, cs.R); err != nil { + return nil, errors.Trace(err) + } + in, err := cs.L.Eval(ctx, args) if err != nil { return nil, errors.Trace(err) From ace4331c97dede63aa6e05a053df608d2bb5ea34 Mon Sep 17 00:00:00 2001 From: qiuyesuifeng Date: Fri, 11 Sep 2015 22:52:21 +0800 Subject: [PATCH 06/12] expression/expressions: add error check test. --- expression/expressions/cmp_subquery_test.go | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/expression/expressions/cmp_subquery_test.go b/expression/expressions/cmp_subquery_test.go index aec0366ff9..9384d57740 100644 --- a/expression/expressions/cmp_subquery_test.go +++ b/expression/expressions/cmp_subquery_test.go @@ -135,7 +135,7 @@ func (s *testCompSubQuerySuite) TestRow(c *C) { rhs = append(rhs, []interface{}{s.convert(v)}) } - sq := newMockSubQuery(rhs, []string{"id"}) + sq := newMockSubQuery(rhs, []string{"c"}) expr := NewCompareSubQuery(t.op, Value{lhs}, sq, t.all) c.Assert(expr.IsStatic(), IsFalse) @@ -155,4 +155,16 @@ func (s *testCompSubQuerySuite) TestRow(c *C) { c.Assert(val, Equals, int64(x)) } } + + // Test error. + sq := newMockSubQuery([][]interface{}{{1, 2}}, []string{"c1", "c2"}) + expr := NewCompareSubQuery(opcode.EQ, Value{1}, sq, true) + + _, err := expr.Eval(nil, nil) + c.Assert(err, NotNil) + + expr = NewCompareSubQuery(opcode.EQ, Value{1}, sq, false) + + _, err = expr.Eval(nil, nil) + c.Assert(err, NotNil) } From fd248380ff96d9a4ac4cecc2d5d2117729a5aa6a Mon Sep 17 00:00:00 2001 From: siddontang Date: Sat, 12 Sep 2015 09:33:28 +0800 Subject: [PATCH 07/12] parser: handle unreserve any and some --- parser/parser.y | 2 +- parser/parser_test.go | 2 +- parser/scanner.l | 6 ++++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/parser/parser.y b/parser/parser.y index db1857dbe9..8501e75511 100644 --- a/parser/parser.y +++ b/parser/parser.y @@ -1585,7 +1585,7 @@ UnReservedKeyword: | "DATE" | "DATETIME" | "DEALLOCATE" | "DO" | "END" | "ENGINE" | "ENGINES" | "EXECUTE" | "FIRST" | "FULL" | "LOCAL" | "NAMES" | "OFFSET" | "PASSWORD" %prec lowerThanEq | "PREPARE" | "QUICK" | "ROLLBACK" | "SESSION" | "SIGNED" | "START" | "GLOBAL" | "TABLES"| "TEXT" | "TIME" | "TIMESTAMP" | "TRANSACTION" | "TRUNCATE" | "UNKNOWN" -| "VALUE" | "WARNINGS" | "YEAR" | "NOW" | "MODE" +| "VALUE" | "WARNINGS" | "YEAR" | "NOW" | "MODE" | "ANY" | "SOME" NotKeywordToken: "SQL_CALC_FOUND_ROWS" | "SUBSTRING" %prec lowerThanLeftParen diff --git a/parser/parser_test.go b/parser/parser_test.go index 99239fc803..e2b97684c6 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -310,7 +310,7 @@ func (s *testParserSuite) TestParser0(c *C) { "date", "datetime", "deallocate", "do", "end", "engine", "engines", "execute", "first", "full", "local", "names", "offset", "password", "prepare", "quick", "rollback", "session", "signed", "start", "global", "tables", "text", "time", "timestamp", "transaction", "truncate", "unknown", - "value", "warnings", "year", "now", "substring", "mode", + "value", "warnings", "year", "now", "substring", "mode", "any", "some", } for _, kw := range unreservedKws { src := fmt.Sprintf("SELECT %s FROM tbl;", kw) diff --git a/parser/scanner.l b/parser/scanner.l index d2b8be634d..557700e400 100644 --- a/parser/scanner.l +++ b/parser/scanner.l @@ -461,7 +461,8 @@ sys_var "@@"(({global}".")|({session}".")|{local}".")?{ident} {all} return all {alter} return alter {and} return and -{any} return any +{any} lval.item = string(l.val) + return any {asc} return asc {as} return as {auto_increment} lval.item = string(l.val) @@ -568,7 +569,8 @@ sys_var "@@"(({global}".")|({session}".")|{local}".")?{ident} {schemas} return schemas {session} lval.item = string(l.val) return session -{some} return some +{some} lval.item = string(l.val) + return some {start} lval.item = string(l.val) return start {global} lval.item = string(l.val) From ca60323edb2b80b8a0a4d813416191be338d070c Mon Sep 17 00:00:00 2001 From: qiuyesuifeng Date: Sat, 12 Sep 2015 12:03:17 +0800 Subject: [PATCH 08/12] expression/expressions: address comment. --- expression/expressions/cmp_subquery.go | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/expression/expressions/cmp_subquery.go b/expression/expressions/cmp_subquery.go index bdac5a1bc7..16c056a54f 100644 --- a/expression/expressions/cmp_subquery.go +++ b/expression/expressions/cmp_subquery.go @@ -60,9 +60,9 @@ func (cs *CompareSubQuery) IsStatic() bool { // String implements the Expression String interface. func (cs *CompareSubQuery) String() string { - anyOrAll := "any" + anyOrAll := "ANY" if cs.All { - anyOrAll = "all" + anyOrAll = "ALL" } return fmt.Sprintf("%s %s %s %s", cs.L, cs.Op, anyOrAll, cs.R) @@ -87,6 +87,10 @@ func (cs *CompareSubQuery) Eval(ctx context.Context, args map[interface{}]interf return nil, errors.Trace(err) } + if cs.R.Value != nil { + return cs.checkResult(in, cs.R.Value.([]interface{})) + } + res := []interface{}{} err = p.Do(ctx, func(id interface{}, data []interface{}) (bool, error) { if len(data) == 1 { @@ -100,7 +104,8 @@ func (cs *CompareSubQuery) Eval(ctx context.Context, args map[interface{}]interf return nil, errors.Trace(err) } - return cs.checkResult(in, res) + cs.R.Value = res + return cs.checkResult(in, cs.R.Value.([]interface{})) } func (cs *CompareSubQuery) checkAllResult(in interface{}, result []interface{}) (interface{}, error) { From 393e23b47796fbe487725622a845262e22b63f79 Mon Sep 17 00:00:00 2001 From: qiuyesuifeng Date: Sat, 12 Sep 2015 14:58:16 +0800 Subject: [PATCH 09/12] expression/expressions: tiny clean up. --- expression/expressions/cmp_subquery.go | 8 ++++---- expression/expressions/cmp_subquery_test.go | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/expression/expressions/cmp_subquery.go b/expression/expressions/cmp_subquery.go index 16c056a54f..d1addac62f 100644 --- a/expression/expressions/cmp_subquery.go +++ b/expression/expressions/cmp_subquery.go @@ -82,15 +82,15 @@ func (cs *CompareSubQuery) Eval(ctx context.Context, args map[interface{}]interf return nil, nil } + if cs.R.Value != nil { + return cs.checkResult(in, cs.R.Value.([]interface{})) + } + p, err := cs.R.Plan(ctx) if err != nil { return nil, errors.Trace(err) } - if cs.R.Value != nil { - return cs.checkResult(in, cs.R.Value.([]interface{})) - } - res := []interface{}{} err = p.Do(ctx, func(id interface{}, data []interface{}) (bool, error) { if len(data) == 1 { diff --git a/expression/expressions/cmp_subquery_test.go b/expression/expressions/cmp_subquery_test.go index 9384d57740..8357d88b69 100644 --- a/expression/expressions/cmp_subquery_test.go +++ b/expression/expressions/cmp_subquery_test.go @@ -35,7 +35,7 @@ func (s *testCompSubQuerySuite) convert(v interface{}) interface{} { return v } -func (s *testCompSubQuerySuite) TestRow(c *C) { +func (s *testCompSubQuerySuite) TestCompSubQuery(c *C) { tbl := []struct { lhs interface{} op opcode.Op From be9ed5094370f45229848ea62daa0e0efb489df2 Mon Sep 17 00:00:00 2001 From: qiuyesuifeng Date: Sat, 12 Sep 2015 20:36:47 +0800 Subject: [PATCH 10/12] parser: remove some/any from UnReservedKeyword. --- parser/parser.y | 2 +- parser/parser_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/parser/parser.y b/parser/parser.y index 2fc5894cb8..1fdda1a5e7 100644 --- a/parser/parser.y +++ b/parser/parser.y @@ -1599,7 +1599,7 @@ UnReservedKeyword: | "DATE" | "DATETIME" | "DEALLOCATE" | "DO" | "END" | "ENGINE" | "ENGINES" | "EXECUTE" | "FIRST" | "FULL" | "LOCAL" | "NAMES" | "OFFSET" | "PASSWORD" %prec lowerThanEq | "PREPARE" | "QUICK" | "ROLLBACK" | "SESSION" | "SIGNED" | "START" | "GLOBAL" | "TABLES"| "TEXT" | "TIME" | "TIMESTAMP" | "TRANSACTION" | "TRUNCATE" | "UNKNOWN" -| "VALUE" | "WARNINGS" | "YEAR" | "NOW" | "MODE" | "ANY" | "SOME" +| "VALUE" | "WARNINGS" | "YEAR" | "NOW" | "MODE" NotKeywordToken: "SQL_CALC_FOUND_ROWS" | "SUBSTRING" %prec lowerThanLeftParen diff --git a/parser/parser_test.go b/parser/parser_test.go index 94ca677a27..a0d6691f3a 100644 --- a/parser/parser_test.go +++ b/parser/parser_test.go @@ -313,7 +313,7 @@ func (s *testParserSuite) TestParser0(c *C) { "date", "datetime", "deallocate", "do", "end", "engine", "engines", "execute", "first", "full", "local", "names", "offset", "password", "prepare", "quick", "rollback", "session", "signed", "start", "global", "tables", "text", "time", "timestamp", "transaction", "truncate", "unknown", - "value", "warnings", "year", "now", "substring", "mode", "any", "some", + "value", "warnings", "year", "now", "substring", "mode", } for _, kw := range unreservedKws { src := fmt.Sprintf("SELECT %s FROM tbl;", kw) From 82f65233d6e6c93d5e1d4473d9db8381acd762e2 Mon Sep 17 00:00:00 2001 From: qiuyesuifeng Date: Mon, 14 Sep 2015 10:18:33 +0800 Subject: [PATCH 11/12] expression/expressions: address comment. --- expression/expressions/cmp_subquery.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/expression/expressions/cmp_subquery.go b/expression/expressions/cmp_subquery.go index d1addac62f..96a326a2e1 100644 --- a/expression/expressions/cmp_subquery.go +++ b/expression/expressions/cmp_subquery.go @@ -74,16 +74,16 @@ func (cs *CompareSubQuery) Eval(ctx context.Context, args map[interface{}]interf return nil, errors.Trace(err) } - in, err := cs.L.Eval(ctx, args) + lv, err := cs.L.Eval(ctx, args) if err != nil { return nil, errors.Trace(err) } - if in == nil { + if lv == nil { return nil, nil } if cs.R.Value != nil { - return cs.checkResult(in, cs.R.Value.([]interface{})) + return cs.checkResult(lv, cs.R.Value.([]interface{})) } p, err := cs.R.Plan(ctx) @@ -105,7 +105,7 @@ func (cs *CompareSubQuery) Eval(ctx context.Context, args map[interface{}]interf } cs.R.Value = res - return cs.checkResult(in, cs.R.Value.([]interface{})) + return cs.checkResult(lv, cs.R.Value.([]interface{})) } func (cs *CompareSubQuery) checkAllResult(in interface{}, result []interface{}) (interface{}, error) { From 002b2a3bf8be45b6a4526b08b9711cd3d8b24e0e Mon Sep 17 00:00:00 2001 From: qiuyesuifeng Date: Mon, 14 Sep 2015 10:27:57 +0800 Subject: [PATCH 12/12] expression/expressions: address comment. --- expression/expressions/cmp_subquery.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/expression/expressions/cmp_subquery.go b/expression/expressions/cmp_subquery.go index 96a326a2e1..d3a8031973 100644 --- a/expression/expressions/cmp_subquery.go +++ b/expression/expressions/cmp_subquery.go @@ -108,7 +108,7 @@ func (cs *CompareSubQuery) Eval(ctx context.Context, args map[interface{}]interf return cs.checkResult(lv, cs.R.Value.([]interface{})) } -func (cs *CompareSubQuery) checkAllResult(in interface{}, result []interface{}) (interface{}, error) { +func (cs *CompareSubQuery) checkAllResult(lv interface{}, result []interface{}) (interface{}, error) { hasNull := false for _, v := range result { if v == nil { @@ -116,7 +116,7 @@ func (cs *CompareSubQuery) checkAllResult(in interface{}, result []interface{}) continue } - comRes, err := types.Compare(in, v) + comRes, err := types.Compare(lv, v) if err != nil { return nil, errors.Trace(err) } @@ -140,7 +140,7 @@ func (cs *CompareSubQuery) checkAllResult(in interface{}, result []interface{}) return true, nil } -func (cs *CompareSubQuery) checkAnyResult(in interface{}, result []interface{}) (interface{}, error) { +func (cs *CompareSubQuery) checkAnyResult(lv interface{}, result []interface{}) (interface{}, error) { hasNull := false for _, v := range result { if v == nil { @@ -148,7 +148,7 @@ func (cs *CompareSubQuery) checkAnyResult(in interface{}, result []interface{}) continue } - comRes, err := types.Compare(in, v) + comRes, err := types.Compare(lv, v) if err != nil { return nil, errors.Trace(err) } @@ -172,12 +172,12 @@ func (cs *CompareSubQuery) checkAnyResult(in interface{}, result []interface{}) return false, nil } -func (cs *CompareSubQuery) checkResult(in interface{}, result []interface{}) (interface{}, error) { +func (cs *CompareSubQuery) checkResult(lv interface{}, result []interface{}) (interface{}, error) { if cs.All { - return cs.checkAllResult(in, result) + return cs.checkAllResult(lv, result) } - return cs.checkAnyResult(in, result) + return cs.checkAnyResult(lv, result) } // NewCompareSubQuery creates a CompareSubQuery object.