Skip to content

Commit 899e18d

Browse files
committed
Added statement boundaries
1 parent c6e19e4 commit 899e18d

File tree

10 files changed

+211
-18
lines changed

10 files changed

+211
-18
lines changed

enginetest/memory_engine_test.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -373,6 +373,12 @@ func TestTruncate(t *testing.T) {
373373
}
374374

375375
func TestScripts(t *testing.T) {
376+
//TODO: when foreign keys are implemented in the memory table, we can do the following test
377+
for i := len(enginetest.ScriptTests) - 1; i >= 0; i-- {
378+
if enginetest.ScriptTests[i].Name == "failed statements data validation for DELETE, REPLACE" {
379+
enginetest.ScriptTests = append(enginetest.ScriptTests[:i], enginetest.ScriptTests[i+1:]...)
380+
}
381+
}
376382
enginetest.TestScripts(t, enginetest.NewMemoryHarness("default", 1, testNumPartitions, true, mergableIndexDriver))
377383
}
378384

enginetest/script_queries.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,66 @@ type ScriptTestAssertion struct {
5555
// Unlike other engine tests, ScriptTests must be self-contained. No other tables are created outside the definition of
5656
// the tests.
5757
var ScriptTests = []ScriptTest{
58+
{
59+
Name: "failed statements data validation for INSERT, UPDATE",
60+
SetUpScript: []string{
61+
"CREATE TABLE test (pk BIGINT PRIMARY KEY, v1 BIGINT, INDEX (v1));",
62+
"INSERT INTO test VALUES (1,1), (4,4), (5,5);",
63+
},
64+
Assertions: []ScriptTestAssertion{
65+
{
66+
Query: "INSERT INTO test VALUES (2,2), (3,3), (1,1);",
67+
ExpectedErrStr: "duplicate primary key given: [1]",
68+
},
69+
{
70+
Query: "SELECT * FROM test;",
71+
Expected: []sql.Row{{1, 1}, {4, 4}, {5, 5}},
72+
},
73+
{
74+
Query: "UPDATE test SET pk = pk + 1;",
75+
ExpectedErrStr: "duplicate primary key given: [5]",
76+
},
77+
{
78+
Query: "SELECT * FROM test;",
79+
Expected: []sql.Row{{1, 1}, {4, 4}, {5, 5}},
80+
},
81+
},
82+
},
83+
{
84+
Name: "failed statements data validation for DELETE, REPLACE",
85+
SetUpScript: []string{
86+
"CREATE TABLE test (pk BIGINT PRIMARY KEY, v1 BIGINT, INDEX (v1));",
87+
"INSERT INTO test VALUES (1,1), (4,4), (5,5);",
88+
"CREATE TABLE test2 (pk BIGINT PRIMARY KEY, CONSTRAINT fk_test FOREIGN KEY (pk) REFERENCES test (v1));",
89+
"INSERT INTO test2 VALUES (4);",
90+
},
91+
Assertions: []ScriptTestAssertion{
92+
{
93+
Query: "DELETE FROM test WHERE pk > 0;",
94+
ExpectedErr: sql.ErrForeignKeyChildViolation,
95+
},
96+
{
97+
Query: "SELECT * FROM test;",
98+
Expected: []sql.Row{{1, 1}, {4, 4}, {5, 5}},
99+
},
100+
{
101+
Query: "SELECT * FROM test2;",
102+
Expected: []sql.Row{{4}},
103+
},
104+
{
105+
Query: "REPLACE INTO test VALUES (1,7), (4,8), (5,9);",
106+
ExpectedErr: sql.ErrForeignKeyChildViolation,
107+
},
108+
{
109+
Query: "SELECT * FROM test;",
110+
Expected: []sql.Row{{1, 1}, {4, 4}, {5, 5}},
111+
},
112+
{
113+
Query: "SELECT * FROM test2;",
114+
Expected: []sql.Row{{4}},
115+
},
116+
},
117+
},
58118
{
59119
Name: "delete with in clause",
60120
SetUpScript: []string{

go.sum

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,6 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
1414
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
1515
github.com/dolthub/sqllogictest/go v0.0.0-20201107003712-816f3ae12d81 h1:7/v8q9XGFa6q5Ap4Z/OhNkAMBaK5YeuEzwJt+NZdhiE=
1616
github.com/dolthub/sqllogictest/go v0.0.0-20201107003712-816f3ae12d81/go.mod h1:siLfyv2c92W1eN/R4QqG/+RjjX5W2+gCTRjZxBjI3TY=
17-
github.com/dolthub/vitess v0.0.0-20210428165934-5801b1103b04 h1:yvfUh1EqwPu10H9VpaUxBEWzqKUmKxsW689ufJWNLsg=
18-
github.com/dolthub/vitess v0.0.0-20210428165934-5801b1103b04/go.mod h1:hUE8oSk2H5JZnvtlLBhJPYC8WZCA5AoSntdLTcBvdBM=
1917
github.com/dolthub/vitess v0.0.0-20210524220733-7b048c544267 h1:g3KWBmLtSWlEbwUF4NV4a4jzE5aec8n2ZHWwSDy9IGY=
2018
github.com/dolthub/vitess v0.0.0-20210524220733-7b048c544267/go.mod h1:hUE8oSk2H5JZnvtlLBhJPYC8WZCA5AoSntdLTcBvdBM=
2119
github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=

memory/table.go

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -377,38 +377,65 @@ func EncodeIndexValue(value *IndexValue) ([]byte, error) {
377377
}
378378

379379
type tableEditor struct {
380-
table *Table
380+
table *Table
381+
initialAutoIncVal interface{}
382+
initialPartitions map[string][]sql.Row
383+
initialInsert int
381384
}
382385

383386
var _ sql.RowReplacer = (*tableEditor)(nil)
384387
var _ sql.RowUpdater = (*tableEditor)(nil)
385388
var _ sql.RowInserter = (*tableEditor)(nil)
386389
var _ sql.RowDeleter = (*tableEditor)(nil)
387390

388-
func (t tableEditor) Close(*sql.Context) error {
391+
func (t *tableEditor) Close(*sql.Context) error {
389392
// TODO: it would be nice to apply all pending updates here at once, rather than directly in the Insert / Update
390393
// / Delete methods.
391394
return nil
392395
}
393396

397+
func (t *tableEditor) StatementBegin(ctx *sql.Context) {
398+
t.initialInsert = t.table.insert
399+
t.initialAutoIncVal = t.table.autoIncVal
400+
t.initialPartitions = make(map[string][]sql.Row)
401+
for partStr, rowSlice := range t.table.partitions {
402+
newRowSlice := make([]sql.Row, len(rowSlice))
403+
for i, row := range rowSlice {
404+
newRowSlice[i] = row.Copy()
405+
}
406+
t.initialPartitions[partStr] = newRowSlice
407+
}
408+
}
409+
410+
func (t *tableEditor) DiscardChanges(ctx *sql.Context, errorEncountered error) error {
411+
t.table.insert = t.initialInsert
412+
t.table.autoIncVal = t.initialAutoIncVal
413+
t.table.partitions = t.initialPartitions
414+
return nil
415+
}
416+
417+
func (t *tableEditor) StatementComplete(ctx *sql.Context) error {
418+
return nil
419+
}
420+
394421
func (t *Table) Inserter(*sql.Context) sql.RowInserter {
395-
return &tableEditor{t}
422+
return &tableEditor{t, nil, nil, 0}
396423
}
397424

398425
func (t *Table) Updater(*sql.Context) sql.RowUpdater {
399-
return &tableEditor{t}
426+
return &tableEditor{t, nil, nil, 0}
400427
}
401428

402429
func (t *Table) Replacer(*sql.Context) sql.RowReplacer {
403-
return &tableEditor{t}
430+
return &tableEditor{t, nil, nil, 0}
404431
}
405432

406433
func (t *Table) Deleter(*sql.Context) sql.RowDeleter {
407-
return &tableEditor{t}
434+
return &tableEditor{t, nil, nil, 0}
408435
}
409436

410437
func (t *Table) AutoIncrementSetter(*sql.Context) sql.AutoIncrementSetter {
411-
return &tableEditor{t}
438+
return &tableEditor{t, nil, nil, 0}
412439
}
413440

414441
func (t *Table) Truncate(ctx *sql.Context) (int, error) {

sql/core.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,20 @@ type CheckAlterableTable interface {
341341
DropCheck(ctx *Context, chName string) error
342342
}
343343

344+
// TableEditor is the base interface for sub interfaces that can update rows in a table during an INSERT, REPLACE,
345+
// UPDATE, or DELETE statement.
346+
type TableEditor interface {
347+
// StatementBegin is called before the first operation of a statement. Integrators should mark the state of the data
348+
// in some way that it may be returned to in the case of an error.
349+
StatementBegin(ctx *Context)
350+
// DiscardChanges is called if a statement encounters an error, and all current changes since the statement beginning
351+
// should be discarded.
352+
DiscardChanges(ctx *Context, errorEncountered error) error
353+
// StatementComplete is called after the last operation of the statement, indicating that it has successfully completed.
354+
// The mark set in StatementBegin may be removed, and a new one should be created on the next StatementBegin.
355+
StatementComplete(ctx *Context) error
356+
}
357+
344358
// InsertableTable is a table that can process insertion of new rows.
345359
type InsertableTable interface {
346360
Table
@@ -351,6 +365,7 @@ type InsertableTable interface {
351365

352366
// RowInserter is an insert cursor that can insert one or more values to a table.
353367
type RowInserter interface {
368+
TableEditor
354369
// Insert inserts the row given, returning an error if it cannot. Insert will be called once for each row to process
355370
// for the insert operation, which may involve many rows. After all rows in an operation have been processed, Close
356371
// is called.
@@ -369,6 +384,7 @@ type DeletableTable interface {
369384

370385
// RowDeleter is a delete cursor that can delete one or more rows from a table.
371386
type RowDeleter interface {
387+
TableEditor
372388
// Delete deletes the given row. Returns ErrDeleteRowNotFound if the row was not found. Delete will be called once for
373389
// each row to process for the delete operation, which may involve many rows. After all rows have been processed,
374390
// Close is called.
@@ -419,6 +435,7 @@ type Closer interface {
419435
// TODO: We can't embed those interfaces because go 1.13 doesn't allow for overlapping interfaces (they both declare
420436
// Close). Go 1.14 fixes this problem, but we aren't ready to drop support for 1.13 yet.
421437
type RowReplacer interface {
438+
TableEditor
422439
// Insert inserts the row given, returning an error if it cannot. Insert will be called once for each row to process
423440
// for the replace operation, which may involve many rows. After all rows in an operation have been processed, Close
424441
// is called.
@@ -449,6 +466,7 @@ type UpdatableTable interface {
449466

450467
// RowUpdater is an update cursor that can update one or more rows in a table.
451468
type RowUpdater interface {
469+
TableEditor
452470
// Update the given row. Provides both the old and new rows.
453471
Update(ctx *Context, old Row, new Row) error
454472
// Close finalizes the update operation, persisting the result.

sql/plan/delete.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -138,8 +138,13 @@ func (d *deleteIter) Close(ctx *sql.Context) error {
138138
return nil
139139
}
140140

141-
func newDeleteIter(childIter sql.RowIter, deleter sql.RowDeleter, schema sql.Schema, ctx *sql.Context) *deleteIter {
142-
return &deleteIter{deleter: deleter, childIter: childIter, schema: schema, ctx: ctx}
141+
func newDeleteIter(childIter sql.RowIter, deleter sql.RowDeleter, schema sql.Schema, ctx *sql.Context) sql.RowIter {
142+
return NewTableEditorIter(ctx, deleter, &deleteIter{
143+
deleter: deleter,
144+
childIter: childIter,
145+
schema: schema,
146+
ctx: ctx,
147+
})
143148
}
144149

145150
// WithChildren implements the Node interface.

sql/plan/insert.go

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -166,7 +166,7 @@ func newInsertIter(
166166
checks sql.CheckConstraints,
167167
row sql.Row,
168168
ignore bool,
169-
) (*insertIter, error) {
169+
) (sql.RowIter, error) {
170170
dstSchema := table.Schema()
171171

172172
insertable, err := GetInsertable(table)
@@ -194,8 +194,7 @@ func newInsertIter(
194194
}
195195

196196
insertExpressions := getInsertExpressions(values)
197-
198-
return &insertIter{
197+
insertIter := &insertIter{
199198
schema: dstSchema,
200199
tableNode: table,
201200
inserter: inserter,
@@ -207,7 +206,13 @@ func newInsertIter(
207206
checks: checks,
208207
ctx: ctx,
209208
ignore: ignore,
210-
}, nil
209+
}
210+
211+
if replacer != nil {
212+
return NewTableEditorIter(ctx, replacer, insertIter), nil
213+
} else {
214+
return NewTableEditorIter(ctx, inserter, insertIter), nil
215+
}
211216
}
212217

213218
func getInsertExpressions(values sql.Node) []sql.Expression {

sql/plan/table_editor.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
// Copyright 2021 Dolthub, Inc.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package plan
16+
17+
import (
18+
"io"
19+
"sync"
20+
21+
"github.com/dolthub/go-mysql-server/sql"
22+
)
23+
24+
// tableEditorIter wraps the given iterator and calls the Begin and Complete functions on the given table.
25+
type tableEditorIter struct {
26+
once *sync.Once
27+
onceCtx *sql.Context
28+
editor sql.TableEditor
29+
inner sql.RowIter
30+
errorEncountered error
31+
}
32+
33+
var _ sql.RowIter = (*tableEditorIter)(nil)
34+
35+
// NewTableEditorIter returns a new *tableEditorIter by wrapping the given iterator. If the
36+
// "statement_boundaries" session variable is set to false, then the original iterator is returned.
37+
func NewTableEditorIter(ctx *sql.Context, table sql.TableEditor, wrappedIter sql.RowIter) sql.RowIter {
38+
return &tableEditorIter{
39+
once: &sync.Once{},
40+
onceCtx: ctx,
41+
editor: table,
42+
inner: wrappedIter,
43+
errorEncountered: nil,
44+
}
45+
}
46+
47+
// Next implements the interface sql.RowIter.
48+
func (s *tableEditorIter) Next() (sql.Row, error) {
49+
s.once.Do(func() {
50+
s.editor.StatementBegin(s.onceCtx)
51+
})
52+
row, err := s.inner.Next()
53+
if err != nil && err != io.EOF && !ErrInsertIgnore.Is(err) {
54+
s.errorEncountered = err
55+
}
56+
return row, err
57+
}
58+
59+
// Close implements the interface sql.RowIter.
60+
func (s *tableEditorIter) Close(ctx *sql.Context) error {
61+
var err error
62+
if s.errorEncountered != nil {
63+
err = s.editor.DiscardChanges(ctx, s.errorEncountered)
64+
} else {
65+
err = s.editor.StatementComplete(ctx)
66+
}
67+
if err != nil {
68+
_ = s.inner.Close(ctx)
69+
} else {
70+
err = s.inner.Close(ctx)
71+
}
72+
return err
73+
}

sql/plan/update.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -209,14 +209,14 @@ func newUpdateIter(
209209
schema sql.Schema,
210210
updater sql.RowUpdater,
211211
checks sql.CheckConstraints,
212-
) *updateIter {
213-
return &updateIter{
212+
) sql.RowIter {
213+
return NewTableEditorIter(ctx, updater, &updateIter{
214214
childIter: childIter,
215215
updater: updater,
216216
schema: schema,
217217
checks: checks,
218218
ctx: ctx,
219-
}
219+
})
220220
}
221221

222222
// RowIter implements the Node interface.

sql/row.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,7 @@ func RowIterToRows(ctx *Context, i RowIter) ([]Row, error) {
9999
}
100100

101101
if err != nil {
102+
_ = i.Close(ctx)
102103
return nil, err
103104
}
104105

0 commit comments

Comments
 (0)