Merge pull request #895 from pingcap/shenli/delete

*: Support delete in new plan
This commit is contained in:
Shen Li
2016-02-15 11:49:58 +08:00
11 changed files with 260 additions and 53 deletions

View File

@ -120,6 +120,31 @@ func (n *TableName) Accept(v Visitor) (Node, bool) {
return v.Leave(n)
}
// DeleteTableList is the tablelist used in delete statement multi-table mode.
type DeleteTableList struct {
node
Tables []*TableName
}
// Accept implements Node Accept interface.
func (n *DeleteTableList) Accept(v Visitor) (Node, bool) {
newNode, skipChildren := v.Enter(n)
if skipChildren {
return v.Leave(newNode)
}
n = newNode.(*DeleteTableList)
if n != nil {
for i, t := range n.Tables {
node, ok := t.Accept(v)
if !ok {
return n, false
}
n.Tables[i] = node.(*TableName)
}
}
return v.Leave(n)
}
// OnCondition represetns JOIN on condition.
type OnCondition struct {
node
@ -652,15 +677,15 @@ type DeleteStmt struct {
// Used in both single table and multiple table delete statement.
TableRefs *TableRefsClause
// Only used in multiple table delete statement.
Tables []*TableName
Where ExprNode
Order *OrderByClause
Limit *Limit
LowPriority bool
Ignore bool
Quick bool
MultiTable bool
BeforeFrom bool
Tables *DeleteTableList
Where ExprNode
Order *OrderByClause
Limit *Limit
LowPriority bool
Ignore bool
Quick bool
IsMultiTable bool
BeforeFrom bool
}
// Accept implements Node Accept interface.
@ -677,13 +702,11 @@ func (n *DeleteStmt) Accept(v Visitor) (Node, bool) {
}
n.TableRefs = node.(*TableRefsClause)
for i, val := range n.Tables {
node, ok = val.Accept(v)
if !ok {
return n, false
}
n.Tables[i] = node.(*TableName)
node, ok = n.Tables.Accept(v)
if !ok {
return n, false
}
n.Tables = node.(*DeleteTableList)
if n.Where != nil {
node, ok = n.Where.Accept(v)

View File

@ -51,6 +51,8 @@ func (b *executorBuilder) build(p plan.Plan) Executor {
return b.buildCheckTable(v)
case *plan.Deallocate:
return b.buildDeallocate(v)
case *plan.Delete:
return b.buildDelete(v)
case *plan.Execute:
return b.buildExecute(v)
case *plan.Filter:
@ -295,3 +297,13 @@ func (b *executorBuilder) buildUpdate(v *plan.Update) Executor {
selExec := b.build(v.SelectPlan)
return &UpdateExec{ctx: b.ctx, SelectExec: selExec, OrderedList: v.OrderedList}
}
func (b *executorBuilder) buildDelete(v *plan.Delete) Executor {
selExec := b.build(v.SelectPlan)
return &DeleteExec{
ctx: b.ctx,
SelectExec: selExec,
Tables: v.Tables,
IsMultiTable: v.IsMultiTable,
}
}

View File

@ -103,7 +103,7 @@ func convertDelete(converter *expressionConverter, v *ast.DeleteStmt) (*stmts.De
BeforeFrom: v.BeforeFrom,
Ignore: v.Ignore,
LowPriority: v.LowPriority,
MultiTable: v.MultiTable,
MultiTable: v.IsMultiTable,
Quick: v.Quick,
Text: v.Text(),
}
@ -112,9 +112,11 @@ func convertDelete(converter *expressionConverter, v *ast.DeleteStmt) (*stmts.De
return nil, errors.Trace(err)
}
oldDelete.Refs = oldRefs
for _, val := range v.Tables {
tableIdent := table.Ident{Schema: val.Schema, Name: val.Name}
oldDelete.TableIdents = append(oldDelete.TableIdents, tableIdent)
if v.Tables != nil {
for _, val := range v.Tables.Tables {
tableIdent := table.Ident{Schema: val.Schema, Name: val.Name}
oldDelete.TableIdents = append(oldDelete.TableIdents, tableIdent)
}
}
if v.Where != nil {
oldDelete.Where, err = convertExpr(converter, v.Where)

View File

@ -21,6 +21,7 @@ import (
"github.com/pingcap/tidb/kv"
"github.com/pingcap/tidb/mysql"
"github.com/pingcap/tidb/optimizer/evaluator"
"github.com/pingcap/tidb/sessionctx"
"github.com/pingcap/tidb/sessionctx/variable"
"github.com/pingcap/tidb/table"
"github.com/pingcap/tidb/table/tables"
@ -29,6 +30,7 @@ import (
var (
_ Executor = &UpdateExec{}
_ Executor = &DeleteExec{}
)
// UpdateExec represents an update executor.
@ -45,7 +47,7 @@ type UpdateExec struct {
cursor int
}
// Next implements Execution Next interface.
// Next implements Executor Next interface.
func (e *UpdateExec) Next() (*Row, error) {
if !e.fetched {
err := e.fetchRows()
@ -234,3 +236,109 @@ func (e *UpdateExec) Fields() []*ast.ResultField {
func (e *UpdateExec) Close() error {
return e.SelectExec.Close()
}
// DeleteExec represents a delete executor.
// See: https://dev.mysql.com/doc/refman/5.7/en/delete.html
type DeleteExec struct {
SelectExec Executor
ctx context.Context
Tables []*ast.TableName
IsMultiTable bool
finished bool
}
// Next implements Executor Next interface.
func (e *DeleteExec) Next() (*Row, error) {
if e.finished {
return nil, nil
}
defer func() {
e.finished = true
}()
if e.IsMultiTable && len(e.Tables) == 0 {
return &Row{}, nil
}
tblIDMap := make(map[int64]bool, len(e.Tables))
// Get table alias map.
tblNames := make(map[string]string)
rowKeyMap := make(map[string]table.Table)
if e.IsMultiTable {
// Delete from multiple tables should consider table ident list.
fs := e.SelectExec.Fields()
for _, f := range fs {
if len(f.TableAsName.L) > 0 {
tblNames[f.TableAsName.L] = f.TableName.Name.L
} else {
tblNames[f.TableName.Name.L] = f.TableName.Name.L
}
}
for _, t := range e.Tables {
// Consider DBName.
_, ok := tblNames[t.Name.L]
if !ok {
return nil, errors.Errorf("Unknown table '%s' in MULTI DELETE", t.Name.O)
}
tblIDMap[t.TableInfo.ID] = true
}
}
for {
row, err := e.SelectExec.Next()
if err != nil {
return nil, errors.Trace(err)
}
if row == nil {
break
}
for _, entry := range row.RowKeys {
if e.IsMultiTable {
tid := entry.Tbl.TableID()
if _, ok := tblIDMap[tid]; !ok {
continue
}
}
rowKeyMap[entry.Key] = entry.Tbl
}
}
for k, t := range rowKeyMap {
handle, err := tables.DecodeRecordKeyHandle(kv.Key(k))
if err != nil {
return nil, errors.Trace(err)
}
data, err := t.Row(e.ctx, handle)
if err != nil {
return nil, errors.Trace(err)
}
err = e.removeRow(e.ctx, t, handle, data)
if err != nil {
return nil, errors.Trace(err)
}
}
return nil, nil
}
func (e *DeleteExec) getTable(ctx context.Context, tableName *ast.TableName) (table.Table, error) {
return sessionctx.GetDomain(ctx).InfoSchema().TableByName(tableName.Schema, tableName.Name)
}
func (e *DeleteExec) removeRow(ctx context.Context, t table.Table, h int64, data []interface{}) error {
err := t.RemoveRecord(ctx, h, data)
if err != nil {
return errors.Trace(err)
}
variable.GetSessionVars(ctx).AddAffectedRows(1)
return nil
}
// Fields implements Executor Fields interface.
// Returns nil to indicate there is no output.
func (e *DeleteExec) Fields() []*ast.ResultField {
return nil
}
// Close implements Executor Close interface.
func (e *DeleteExec) Close() error {
return e.SelectExec.Close()
}

View File

@ -91,7 +91,7 @@ func (c *supportChecker) Leave(in ast.Node) (ast.Node, bool) {
func IsSupported(node ast.Node) bool {
switch node.(type) {
case *ast.SelectStmt, *ast.PrepareStmt, *ast.ExecuteStmt, *ast.DeallocateStmt,
*ast.AdminStmt, *ast.UpdateStmt:
*ast.AdminStmt, *ast.UpdateStmt, *ast.DeleteStmt:
default:
return false
}

View File

@ -59,6 +59,8 @@ func (b *planBuilder) build(node ast.Node) Plan {
return b.buildAdmin(x)
case *ast.DeallocateStmt:
return &Deallocate{Name: x.Name}
case *ast.DeleteStmt:
return b.buildDelete(x)
case *ast.ExecuteStmt:
return &Execute{Name: x.Name, UsingVars: x.UsingVars}
case *ast.PrepareStmt:
@ -647,6 +649,32 @@ func (b *planBuilder) buildUpdateLists(list []*ast.Assignment, fields []*ast.Res
return newList
}
func (b *planBuilder) buildDelete(del *ast.DeleteStmt) Plan {
sel := &ast.SelectStmt{From: del.TableRefs, Where: del.Where, OrderBy: del.Order, Limit: del.Limit}
p := b.buildFrom(sel)
if sel.OrderBy != nil && !matchOrder(p, sel.OrderBy.Items) {
p = b.buildSort(p, sel.OrderBy.Items)
if b.err != nil {
return nil
}
}
if sel.Limit != nil {
p = b.buildLimit(p, sel.Limit)
if b.err != nil {
return nil
}
}
var tables []*ast.TableName
if del.Tables != nil {
tables = del.Tables.Tables
}
return &Delete{
Tables: tables,
IsMultiTable: del.IsMultiTable,
SelectPlan: p,
}
}
func columnOffsetInFields(cn *ast.ColumnName, fields []*ast.ResultField) (int, error) {
offset := -1
tableNameL := cn.Table.L

View File

@ -461,6 +461,30 @@ func (p *Update) Accept(v Visitor) (Plan, bool) {
return v.Leave(p)
}
// Delete represents a delete plan.
type Delete struct {
basePlan
SelectPlan Plan
Tables []*ast.TableName
IsMultiTable bool
}
// Accept implements Plan Accept interface.
func (p *Delete) Accept(v Visitor) (Plan, bool) {
np, skip := v.Enter(p)
if skip {
v.Leave(np)
}
p = np.(*Delete)
var ok bool
p.SelectPlan, ok = p.SelectPlan.Accept(v)
if !ok {
return p, false
}
return v.Leave(p)
}
// Filter represents a plan that filter srcplan result.
type Filter struct {
planWithSrc

View File

@ -89,6 +89,8 @@ type resolverContext struct {
inByItemExpression bool
// If subquery use outer context.
useOuterContext bool
// When visiting multi-table delete stmt table list.
inDeleteTableList bool
}
// currentContext gets the current resolverContext.
@ -128,6 +130,8 @@ func (nr *nameResolver) popJoin() {
// Enter implements ast.Visitor interface.
func (nr *nameResolver) Enter(inNode ast.Node) (outNode ast.Node, skipChildren bool) {
switch v := inNode.(type) {
case *ast.AdminStmt:
nr.pushContext()
case *ast.AggregateFuncExpr:
ctx := nr.currentContext()
if ctx.inHaving {
@ -148,6 +152,8 @@ func (nr *nameResolver) Enter(inNode ast.Node) (outNode ast.Node, skipChildren b
}
case *ast.DeleteStmt:
nr.pushContext()
case *ast.DeleteTableList:
nr.currentContext().inDeleteTableList = true
case *ast.FieldList:
nr.currentContext().inFieldList = true
case *ast.GroupByClause:
@ -175,6 +181,8 @@ func (nr *nameResolver) Enter(inNode ast.Node) (outNode ast.Node, skipChildren b
// Leave implements ast.Visitor interface.
func (nr *nameResolver) Leave(inNode ast.Node) (node ast.Node, ok bool) {
switch v := inNode.(type) {
case *ast.AdminStmt:
nr.popContext()
case *ast.AggregateFuncExpr:
ctx := nr.currentContext()
if ctx.inHaving {
@ -184,6 +192,8 @@ func (nr *nameResolver) Leave(inNode ast.Node) (node ast.Node, ok bool) {
nr.handleTableName(v)
case *ast.ColumnNameExpr:
nr.handleColumnName(v)
case *ast.DeleteTableList:
nr.currentContext().inDeleteTableList = false
case *ast.TableSource:
nr.handleTableSource(v)
case *ast.OnCondition:
@ -243,9 +253,23 @@ func (nr *nameResolver) handleTableName(tn *ast.TableName) {
if tn.Schema.L == "" {
tn.Schema = nr.DefaultSchema
}
ctx := nr.currentContext()
if ctx.inDeleteTableList {
idx, ok := ctx.tableMap[nr.tableUniqueName(tn.Schema, tn.Name)]
if !ok {
nr.Err = errors.Errorf("Unknown table %s", tn.Name.O)
return
}
ts := ctx.tables[idx]
tableName := ts.Source.(*ast.TableName)
tn.DBInfo = tableName.DBInfo
tn.TableInfo = tableName.TableInfo
tn.SetResultFields(tableName.GetResultFields())
return
}
table, err := nr.Info.TableByName(tn.Schema, tn.Name)
if err != nil {
nr.Err = err
nr.Err = errors.Trace(err)
return
}
tn.TableInfo = table.Meta()

View File

@ -6317,13 +6317,13 @@ yynewstate:
{
// Multiple Table
x := &ast.DeleteStmt{
LowPriority: yyS[yypt-6].item.(bool),
Quick: yyS[yypt-5].item.(bool),
Ignore: yyS[yypt-4].item.(bool),
MultiTable: true,
BeforeFrom: true,
Tables: yyS[yypt-3].item.([]*ast.TableName),
TableRefs: &ast.TableRefsClause{TableRefs: yyS[yypt-1].item.(*ast.Join)},
LowPriority: yyS[yypt-6].item.(bool),
Quick: yyS[yypt-5].item.(bool),
Ignore: yyS[yypt-4].item.(bool),
IsMultiTable: true,
BeforeFrom: true,
Tables: &ast.DeleteTableList{Tables: yyS[yypt-3].item.([]*ast.TableName)},
TableRefs: &ast.TableRefsClause{TableRefs: yyS[yypt-1].item.(*ast.Join)},
}
if yyS[yypt-0].item != nil {
x.Where = yyS[yypt-0].item.(ast.ExprNode)
@ -6337,12 +6337,12 @@ yynewstate:
{
// Multiple Table
x := &ast.DeleteStmt{
LowPriority: yyS[yypt-7].item.(bool),
Quick: yyS[yypt-6].item.(bool),
Ignore: yyS[yypt-5].item.(bool),
MultiTable: true,
Tables: yyS[yypt-3].item.([]*ast.TableName),
TableRefs: &ast.TableRefsClause{TableRefs: yyS[yypt-1].item.(*ast.Join)},
LowPriority: yyS[yypt-7].item.(bool),
Quick: yyS[yypt-6].item.(bool),
Ignore: yyS[yypt-5].item.(bool),
IsMultiTable: true,
Tables: &ast.DeleteTableList{Tables: yyS[yypt-3].item.([]*ast.TableName)},
TableRefs: &ast.TableRefsClause{TableRefs: yyS[yypt-1].item.(*ast.Join)},
}
if yyS[yypt-0].item != nil {
x.Where = yyS[yypt-0].item.(ast.ExprNode)

View File

@ -1224,9 +1224,9 @@ DeleteFromStmt:
LowPriority: $2.(bool),
Quick: $3.(bool),
Ignore: $4.(bool),
MultiTable: true,
IsMultiTable: true,
BeforeFrom: true,
Tables: $5.([]*ast.TableName),
Tables: &ast.DeleteTableList{Tables: $5.([]*ast.TableName)},
TableRefs: &ast.TableRefsClause{TableRefs: $7.(*ast.Join)},
}
if $8 != nil {
@ -1244,8 +1244,8 @@ DeleteFromStmt:
LowPriority: $2.(bool),
Quick: $3.(bool),
Ignore: $4.(bool),
MultiTable: true,
Tables: $6.([]*ast.TableName),
IsMultiTable: true,
Tables: &ast.DeleteTableList{Tables: $6.([]*ast.TableName)},
TableRefs: &ast.TableRefsClause{TableRefs: $8.(*ast.Join)},
}
if $9 != nil {

View File

@ -18,7 +18,6 @@ import (
"strings"
. "github.com/pingcap/check"
"github.com/pingcap/tidb"
)
func (s *testStmtSuite) fillData(currDB *sql.DB, c *C) {
@ -70,13 +69,6 @@ func (s *testStmtSuite) queryStrings(currDB *sql.DB, sql string, c *C) []string
func (s *testStmtSuite) TestDelete(c *C) {
s.fillData(s.testDB, c)
// Test compile
stmtList, err := tidb.Compile(s.ctx, "DELETE from test where id = 2;")
c.Assert(err, IsNil)
str := stmtList[0].OriginText()
c.Assert(0, Less, len(str))
r := mustExec(c, s.testDB, `UPDATE test SET name = "abc" where id = 2;`)
checkResult(c, r, 1, 0)
@ -140,12 +132,6 @@ func (s *testStmtSuite) TestDelete(c *C) {
func (s *testStmtSuite) TestMultiTableDelete(c *C) {
s.fillDataMultiTable(s.testDB, c)
// Test compile
stmtList, err := tidb.Compile(s.ctx, "DELETE t1, t2 FROM t1 INNER JOIN t2 INNER JOIN t3 WHERE t1.id=t2.id AND t2.id=t3.id;")
c.Assert(err, IsNil)
str := stmtList[0].OriginText()
c.Assert(0, Less, len(str))
r := mustExec(c, s.testDB, `DELETE t1, t2 FROM t1 INNER JOIN t2 INNER JOIN t3 WHERE t1.id=t2.id AND t2.id=t3.id;`)
checkResult(c, r, 2, 0)