diff --git a/executor/builder.go b/executor/builder.go index 5171435d5d..841e2694eb 100644 --- a/executor/builder.go +++ b/executor/builder.go @@ -95,6 +95,8 @@ func (b *executorBuilder) build(p plan.Plan) Executor { return b.buildSimple(v) case *plan.Sort: return b.buildSort(v) + case *plan.TableDual: + return b.buildTableDual(v) case *plan.TableScan: return b.buildTableScan(v) case *plan.Union: @@ -118,6 +120,11 @@ func (b *executorBuilder) buildFilter(src Executor, conditions []ast.ExprNode) E } } +func (b *executorBuilder) buildTableDual(v *plan.TableDual) Executor { + e := &TableDualExec{fields: v.Fields()} + return b.buildFilter(e, v.FilterConditions) +} + func (b *executorBuilder) buildTableScan(v *plan.TableScan) Executor { txn, err := b.ctx.GetTxn(false) if err != nil { diff --git a/executor/executor.go b/executor/executor.go index 69f0cc6036..2a9816c2d2 100644 --- a/executor/executor.go +++ b/executor/executor.go @@ -46,6 +46,7 @@ var ( _ Executor = &SelectLockExec{} _ Executor = &ShowDDLExec{} _ Executor = &SortExec{} + _ Executor = &TableDualExec{} _ Executor = &TableScanExec{} ) @@ -103,7 +104,7 @@ func (e *ShowDDLExec) Fields() []*ast.ResultField { return e.fields } -// Next implements Execution Next interface. +// Next implements Executor Next interface. func (e *ShowDDLExec) Next() (*Row, error) { if e.done { return nil, nil @@ -173,7 +174,7 @@ func (e *CheckTableExec) Fields() []*ast.ResultField { return nil } -// Next implements Execution Next interface. +// Next implements Executor Next interface. func (e *CheckTableExec) Next() (*Row, error) { if e.done { return nil, nil @@ -208,6 +209,31 @@ func (e *CheckTableExec) Close() error { return nil } +// TableDualExec represents a dual table executor. +type TableDualExec struct { + fields []*ast.ResultField + executed bool +} + +// Fields implements Executor Fields interface. +func (e *TableDualExec) Fields() []*ast.ResultField { + return e.fields +} + +// Next implements Executor Next interface. +func (e *TableDualExec) Next() (*Row, error) { + if e.executed { + return nil, nil + } + e.executed = true + return &Row{}, nil +} + +// Close implements plan.Plan Close interface. +func (e *TableDualExec) Close() error { + return nil +} + // TableScanExec represents a table scan executor. type TableScanExec struct { t table.Table @@ -224,7 +250,7 @@ func (e *TableScanExec) Fields() []*ast.ResultField { return e.fields } -// Next implements Execution Next interface. +// Next implements Executor Next interface. func (e *TableScanExec) Next() (*Row, error) { for { if e.cursor >= len(e.ranges) { diff --git a/optimizer/plan/planbuilder.go b/optimizer/plan/planbuilder.go index bb0552e344..960b50224d 100644 --- a/optimizer/plan/planbuilder.go +++ b/optimizer/plan/planbuilder.go @@ -231,6 +231,9 @@ func (b *planBuilder) buildSelect(sel *ast.SelectStmt) Plan { return nil } } else { + if sel.Where != nil { + p = b.buildTableDual(sel) + } if hasAgg { p = b.buildAggregate(p, aggFuncs, nil) } @@ -324,6 +327,13 @@ func (b *planBuilder) buildAllAccessMethodsPlan(path *joinPath) []Plan { return candidates } +func (b *planBuilder) buildTableDual(sel *ast.SelectStmt) Plan { + dual := &TableDual{FilterConditions: splitWhere(sel.Where)} + ret := ast.ResultField{} + dual.SetFields([]*ast.ResultField{&ret}) + return dual +} + func (b *planBuilder) buildTableScanPlan(path *joinPath) Plan { tn := path.table p := &TableScan{ diff --git a/optimizer/plan/plans.go b/optimizer/plan/plans.go index d7af39fc50..52d908d3d5 100644 --- a/optimizer/plan/plans.go +++ b/optimizer/plan/plans.go @@ -26,6 +26,21 @@ type TableRange struct { HighVal int64 } +// TableDual represents a dual table plan. +type TableDual struct { + basePlan + + HasAgg bool + // FilterConditions can be used to filter result. + FilterConditions []ast.ExprNode +} + +// Accept implements Plan Accept interface. +func (p *TableDual) Accept(v Visitor) (Plan, bool) { + np, _ := v.Enter(p) + return v.Leave(np) +} + // TableScan represents a table scan plan. type TableScan struct { basePlan diff --git a/session_test.go b/session_test.go index 6287a9bbb9..4c4d16abd5 100644 --- a/session_test.go +++ b/session_test.go @@ -814,6 +814,56 @@ func (s *testSessionSuite) TestSelect(c *C) { c.Assert(err, IsNil) match(c, row.Data, 1, 2) + // Testcase For https://github.com/pingcap/tidb/issues/1071 + r = mustExecSQL(c, se, `select 1 from dual where "0.1"`) + row, err = r.Next() + c.Assert(err, IsNil) + c.Assert(row, IsNil) + r = mustExecSQL(c, se, "select 1 from dual where 0.8") + row, err = r.Next() + c.Assert(err, IsNil) + match(c, row.Data, 1) + r = mustExecSQL(c, se, "select 1, count(*) from dual where 0.1") + row, err = r.Next() + c.Assert(err, IsNil) + match(c, row.Data, 1, 0) + r = mustExecSQL(c, se, "select count(*), 1 from dual where 0.8") + row, err = r.Next() + c.Assert(err, IsNil) + match(c, row.Data, 1, 1) + r = mustExecSQL(c, se, "select 1, 2 from dual where 0.1") + row, err = r.Next() + c.Assert(err, IsNil) + c.Assert(row, IsNil) + mustExecSQL(c, se, "create table if not exists t2 (c1 int, c2 int)") + mustExecSQL(c, se, "insert into t2 (c1, c2) values(1, 1), (2, 2), (3, 3)") + r = mustExecSQL(c, se, "select 1 from t2 where 0.1") + row, err = r.Next() + c.Assert(err, IsNil) + c.Assert(row, IsNil) + r = mustExecSQL(c, se, "select 1 from t2 where 0.9") + row, err = r.Next() + c.Assert(err, IsNil) + match(c, row.Data, 1) + row, err = r.Next() + c.Assert(err, IsNil) + match(c, row.Data, 1) + row, err = r.Next() + c.Assert(err, IsNil) + match(c, row.Data, 1) + r = mustExecSQL(c, se, "select sum(c1) from t2 where 0.1") + row, err = r.Next() + c.Assert(err, IsNil) + match(c, row.Data, nil) + r = mustExecSQL(c, se, "select sum(c1), c2 from t2 where 0.1") + row, err = r.Next() + c.Assert(err, IsNil) + match(c, row.Data, nil, nil) + r = mustExecSQL(c, se, "select 1+2, count(c1) from t2 where 0.1") + row, err = r.Next() + c.Assert(err, IsNil) + match(c, row.Data, 3, 0) + r = mustExecSQL(c, se, "select 1, 2 from dual where not exists (select * from t where c1=2)") row, err = r.Next() c.Assert(err, IsNil) diff --git a/util/types/convert.go b/util/types/convert.go index 9ea9a7e063..fee22526e2 100644 --- a/util/types/convert.go +++ b/util/types/convert.go @@ -387,9 +387,9 @@ func ToBool(value interface{}) (int64, error) { case uint64: isZero = (v == 0) case float32: - isZero = (v == 0) + isZero = (RoundFloat(float64(v)) == 0) case float64: - isZero = (v == 0) + isZero = (RoundFloat(v) == 0) case string: if len(v) == 0 { isZero = true @@ -416,7 +416,7 @@ func ToBool(value interface{}) (int64, error) { isZero = (v.Duration == 0) case mysql.Decimal: vv, _ := v.Float64() - isZero = (vv == 0) + isZero = (RoundFloat(vv) == 0) case mysql.Hex: isZero = (v.ToNumber() == 0) case mysql.Bit: diff --git a/util/types/convert_test.go b/util/types/convert_test.go index 80568331b9..3d9e4b7761 100644 --- a/util/types/convert_test.go +++ b/util/types/convert_test.go @@ -380,12 +380,12 @@ func (s *testTypeConvertSuite) TestConvertToBool(c *C) { testToBool(c, int(0), 0) testToBool(c, int64(0), 0) testToBool(c, uint64(0), 0) - testToBool(c, float32(0), 0) - testToBool(c, float64(0), 0) + testToBool(c, float32(0.1), 0) + testToBool(c, float64(0.1), 0) testToBool(c, "", 0) - testToBool(c, "0", 0) + testToBool(c, "0.1", 0) testToBool(c, []byte{}, 0) - testToBool(c, []byte("0"), 0) + testToBool(c, []byte("0.1"), 0) testToBool(c, mysql.Hex{Value: 0}, 0) testToBool(c, mysql.Bit{Value: 0, Width: 8}, 0) testToBool(c, mysql.Enum{Name: "a", Value: 1}, 1) @@ -401,9 +401,9 @@ func (s *testTypeConvertSuite) TestConvertToBool(c *C) { ft := NewFieldType(mysql.TypeNewDecimal) ft.Decimal = 5 - v, err := Convert(3.1415926, ft) + v, err := Convert(0.1415926, ft) c.Assert(err, IsNil) - testToBool(c, v, 1) + testToBool(c, v, 0) _, err = ToBool(&invalidMockType{}) c.Assert(err, NotNil) diff --git a/util/types/datum.go b/util/types/datum.go index 00ac0c03ca..60358a6b26 100644 --- a/util/types/datum.go +++ b/util/types/datum.go @@ -1065,9 +1065,9 @@ func (d *Datum) ToBool() (int64, error) { case KindUint64: isZero = (d.GetUint64() == 0) case KindFloat32: - isZero = (d.GetFloat32() == 0) + isZero = (RoundFloat(d.GetFloat64()) == 0) case KindFloat64: - isZero = (d.GetFloat64() == 0) + isZero = (RoundFloat(d.GetFloat64()) == 0) case KindString: s := d.GetString() if len(s) == 0 { @@ -1095,7 +1095,7 @@ func (d *Datum) ToBool() (int64, error) { isZero = (d.GetMysqlDuration().Duration == 0) case KindMysqlDecimal: v, _ := d.GetMysqlDecimal().Float64() - isZero = (v == 0) + isZero = (RoundFloat(v) == 0) case KindMysqlHex: isZero = (d.GetMysqlHex().ToNumber() == 0) case KindMysqlBit: diff --git a/util/types/datum_test.go b/util/types/datum_test.go index a9d92dae15..162d31066c 100644 --- a/util/types/datum_test.go +++ b/util/types/datum_test.go @@ -52,12 +52,12 @@ func (ts *testDatumSuite) TestToBool(c *C) { testDatumToBool(c, int(0), 0) testDatumToBool(c, int64(0), 0) testDatumToBool(c, uint64(0), 0) - testDatumToBool(c, float32(0), 0) - testDatumToBool(c, float64(0), 0) + testDatumToBool(c, float32(0.1), 0) + testDatumToBool(c, float64(0.1), 0) testDatumToBool(c, "", 0) - testDatumToBool(c, "0", 0) + testDatumToBool(c, "0.1", 0) testDatumToBool(c, []byte{}, 0) - testDatumToBool(c, []byte("0"), 0) + testDatumToBool(c, []byte("0.1"), 0) testDatumToBool(c, mysql.Hex{Value: 0}, 0) testDatumToBool(c, mysql.Bit{Value: 0, Width: 8}, 0) testDatumToBool(c, mysql.Enum{Name: "a", Value: 1}, 1) @@ -73,9 +73,9 @@ func (ts *testDatumSuite) TestToBool(c *C) { ft := NewFieldType(mysql.TypeNewDecimal) ft.Decimal = 5 - v, err := Convert(3.1415926, ft) + v, err := Convert(0.1415926, ft) c.Assert(err, IsNil) - testDatumToBool(c, v, 1) + testDatumToBool(c, v, 0) d := NewDatum(&invalidMockType{}) _, err = d.ToBool() c.Assert(err, NotNil)