Skip to content

Commit 1251ac8

Browse files
committed
Added support for DELETE ... RETURNING
1 parent 466cdf1 commit 1251ac8

File tree

6 files changed

+85
-11
lines changed

6 files changed

+85
-11
lines changed

sql/analyzer/process_truncate.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,9 @@ func processTruncate(ctx *sql.Context, a *Analyzer, node sql.Node, scope *plan.S
3333

3434
switch n := node.(type) {
3535
case *plan.DeleteFrom:
36-
if !n.Resolved() {
36+
// If there are any returning expressions, then we can't convert to a Truncate operation,
37+
// since we need to process all rows and return results.
38+
if !n.Resolved() || len(n.Returning) > 0 {
3739
return n, transform.SameTree, nil
3840
}
3941
return deleteToTruncate(ctx, a, n)

sql/plan/delete.go

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"gopkg.in/src-d/go-errors.v1"
2121

2222
"github.com/dolthub/go-mysql-server/sql"
23+
"github.com/dolthub/go-mysql-server/sql/transform"
2324
)
2425

2526
var ErrDeleteFromNotSupported = errors.NewKind("table doesn't support DELETE FROM")
@@ -33,6 +34,10 @@ type DeleteFrom struct {
3334
explicitTargets []sql.Node
3435
RefsSingleRel bool
3536
IsProcNested bool
37+
38+
// Returning is a list of expressions to return after the delete operation. This feature is not
39+
// supported in MySQL's syntax, but is exposed through PostgreSQL's syntax.
40+
Returning []sql.Expression
3641
}
3742

3843
var _ sql.Databaseable = (*DeleteFrom)(nil)
@@ -73,6 +78,24 @@ func (p *DeleteFrom) GetDeleteTargets() []sql.Node {
7378
}
7479
}
7580

81+
// Schema implements the sql.Node interface.
82+
func (p *DeleteFrom) Schema() sql.Schema {
83+
// Postgres allows the returned values of the delete statement to be controlled, so if returning
84+
// expressions were specified, then we return a different schema.
85+
if p.Returning != nil {
86+
// We know that returning exprs are resolved here, because you can't call Schema()
87+
// safely until Resolved() is true.
88+
returningSchema := sql.Schema{}
89+
for _, expr := range p.Returning {
90+
returningSchema = append(returningSchema, transform.ExpressionToColumn(expr, ""))
91+
}
92+
93+
return returningSchema
94+
}
95+
96+
return p.Child.Schema()
97+
}
98+
7699
// Resolved implements the sql.Resolvable interface.
77100
func (p *DeleteFrom) Resolved() bool {
78101
if p.Child.Resolved() == false {
@@ -85,9 +108,31 @@ func (p *DeleteFrom) Resolved() bool {
85108
}
86109
}
87110

111+
for _, expr := range p.Returning {
112+
if expr.Resolved() == false {
113+
return false
114+
}
115+
}
116+
88117
return true
89118
}
90119

120+
// Expressions implements the sql.Expressioner interface.
121+
func (p *DeleteFrom) Expressions() []sql.Expression {
122+
return p.Returning
123+
}
124+
125+
// WithExpressions implements the sql.Expressioner interface.
126+
func (p *DeleteFrom) WithExpressions(newExprs ...sql.Expression) (sql.Node, error) {
127+
if len(newExprs) != len(p.Returning) {
128+
return nil, sql.ErrInvalidChildrenNumber.New(p, len(newExprs), len(p.Returning))
129+
}
130+
131+
copy := *p
132+
copy.Returning = newExprs
133+
return &copy, nil
134+
}
135+
91136
func (p *DeleteFrom) IsReadOnly() bool {
92137
return false
93138
}
@@ -111,7 +156,9 @@ func (p *DeleteFrom) WithChildren(children ...sql.Node) (sql.Node, error) {
111156
return nil, sql.ErrInvalidChildrenNumber.New(p, len(children), 1)
112157
}
113158

114-
return NewDeleteFrom(children[0], p.explicitTargets), nil
159+
deleteFrom := NewDeleteFrom(children[0], p.explicitTargets)
160+
deleteFrom.Returning = p.Returning
161+
return deleteFrom, nil
115162
}
116163

117164
func GetDeletable(node sql.Node) (sql.DeletableTable, error) {

sql/planbuilder/dml.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -492,6 +492,11 @@ func (b *Builder) buildDelete(inScope *scope, d *ast.Delete) (outScope *scope) {
492492
del.RefsSingleRel = !outScope.refsSubquery
493493
del.IsProcNested = b.ProcCtx().DbName != ""
494494
outScope.node = del
495+
496+
if len(d.Returning) > 0 {
497+
del.Returning = b.analyzeSelectList(outScope, outScope, d.Returning)
498+
}
499+
495500
return
496501
}
497502

sql/rowexec/delete.go

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,12 @@ func findSourcePosition(schema sql.Schema, name string) (uint, uint, error) {
6464
// but in more complex scenarios when there are columns contributed by outer scopes and for DELETE FROM JOIN statements
6565
// the child iterator will return a row that is composed of rows from multiple table sources.
6666
type deleteIter struct {
67-
deleters []schemaPositionDeleter
68-
schema sql.Schema
69-
childIter sql.RowIter
70-
closed bool
67+
deleters []schemaPositionDeleter
68+
schema sql.Schema
69+
childIter sql.RowIter
70+
closed bool
71+
returnExprs []sql.Expression
72+
returnSchema sql.Schema
7173
}
7274

7375
func (d *deleteIter) Next(ctx *sql.Context) (sql.Row, error) {
@@ -98,6 +100,18 @@ func (d *deleteIter) Next(ctx *sql.Context) (sql.Row, error) {
98100
}
99101
}
100102

103+
if len(d.returnExprs) > 0 {
104+
var retExprRow sql.Row
105+
for _, returnExpr := range d.returnExprs {
106+
result, err := returnExpr.Eval(ctx, row)
107+
if err != nil {
108+
return nil, err
109+
}
110+
retExprRow = append(retExprRow, result)
111+
}
112+
return retExprRow, nil
113+
}
114+
101115
return row, nil
102116
}
103117

@@ -125,14 +139,16 @@ func (d *deleteIter) Close(ctx *sql.Context) error {
125139
return nil
126140
}
127141

128-
func newDeleteIter(childIter sql.RowIter, schema sql.Schema, deleters ...schemaPositionDeleter) sql.RowIter {
142+
func newDeleteIter(childIter sql.RowIter, schema sql.Schema, deleters []schemaPositionDeleter, returnExprs []sql.Expression, returnSchema sql.Schema) sql.RowIter {
129143
openerClosers := make([]sql.EditOpenerCloser, len(deleters))
130144
for i, ds := range deleters {
131145
openerClosers[i] = ds.deleter
132146
}
133147
return plan.NewTableEditorIter(&deleteIter{
134-
deleters: deleters,
135-
childIter: childIter,
136-
schema: schema,
148+
deleters: deleters,
149+
childIter: childIter,
150+
schema: schema,
151+
returnExprs: returnExprs,
152+
returnSchema: returnSchema,
137153
}, openerClosers...)
138154
}

sql/rowexec/dml.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ func (b *BaseBuilder) buildDeleteFrom(ctx *sql.Context, n *plan.DeleteFrom, row
149149
}
150150
schemaPositionDeleters[i] = schemaPositionDeleter{deleter, int(start), int(end)}
151151
}
152-
return newDeleteIter(iter, schema, schemaPositionDeleters...), nil
152+
return newDeleteIter(iter, schema, schemaPositionDeleters, n.Returning, n.Schema()), nil
153153
}
154154

155155
func (b *BaseBuilder) buildForeignKeyHandler(ctx *sql.Context, n *plan.ForeignKeyHandler, row sql.Row) (sql.RowIter, error) {

sql/rowexec/dml_iters.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -619,6 +619,10 @@ func AddAccumulatorIter(ctx *sql.Context, iter sql.RowIter) (sql.RowIter, sql.Sc
619619
if len(innerIter.returnExprs) > 0 {
620620
return innerIter, innerIter.returnSchema
621621
}
622+
case *deleteIter:
623+
if len(innerIter.returnExprs) > 0 {
624+
return innerIter, innerIter.returnSchema
625+
}
622626
}
623627

624628
return defaultAccumulatorIter(ctx, iter)

0 commit comments

Comments
 (0)