Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support UPDATE IGNORE #1135

Merged
merged 11 commits into from
Aug 2, 2022
11 changes: 11 additions & 0 deletions enginetest/enginetests.go
Original file line number Diff line number Diff line change
Expand Up @@ -583,6 +583,17 @@ func TestUpdate(t *testing.T, harness Harness) {
}
}

func TestUpdateIgnore(t *testing.T, harness Harness) {
harness.Setup(setup.MydbData, setup.MytableData, setup.Mytable_del_idxData, setup.FloattableData, setup.NiltableData, setup.TypestableData, setup.Pk_tablesData, setup.OthertableData, setup.TabletestData)
for _, tt := range queries.UpdateIgnoreTests {
RunWriteQueryTest(t, harness, tt)
}

for _, script := range queries.UpdateIgnoreScripts {
TestScript(t, harness, script)
}
}

func TestUpdateErrors(t *testing.T, harness Harness) {
harness.Setup(setup.MydbData, setup.MytableData, setup.FloattableData, setup.TypestableData)
for _, expectedFailure := range queries.GenericUpdateErrorTests {
Expand Down
4 changes: 4 additions & 0 deletions enginetest/memory_engine_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,10 @@ func TestUpdate(t *testing.T) {
enginetest.TestUpdate(t, enginetest.NewMemoryHarness("default", 1, testNumPartitions, true, mergableIndexDriver))
}

func TestUpdateIgnore(t *testing.T) {
enginetest.TestUpdateIgnore(t, enginetest.NewMemoryHarness("default", 1, testNumPartitions, true, mergableIndexDriver))
}

func TestUpdateErrors(t *testing.T) {
enginetest.TestUpdateErrors(t, enginetest.NewMemoryHarness("default", 1, testNumPartitions, true, mergableIndexDriver))
}
Expand Down
184 changes: 184 additions & 0 deletions enginetest/queries/update_queries.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
package queries

import (
"github.com/dolthub/vitess/go/mysql"

"github.com/dolthub/go-mysql-server/sql"
"github.com/dolthub/go-mysql-server/sql/plan"
)
Expand Down Expand Up @@ -532,6 +534,188 @@ var GenericUpdateErrorTests = []GenericErrorQueryTest{
},
}

var UpdateIgnoreTests = []WriteQueryTest{
{
WriteQuery: "UPDATE IGNORE mytable SET i = 2 where i = 1",
ExpectedWriteResult: []sql.Row{{newUpdateResult(1, 0)}},
SelectQuery: "SELECT * FROM mytable order by i",
ExpectedSelect: []sql.Row{
sql.NewRow(1, "first row"),
sql.NewRow(2, "second row"),
sql.NewRow(3, "third row"),
},
},
{
WriteQuery: "UPDATE IGNORE mytable SET i = i+1 where i = 1",
ExpectedWriteResult: []sql.Row{{newUpdateResult(1, 0)}},
SelectQuery: "SELECT * FROM mytable order by i",
ExpectedSelect: []sql.Row{
sql.NewRow(1, "first row"),
sql.NewRow(2, "second row"),
sql.NewRow(3, "third row"),
},
},
}

var UpdateIgnoreScripts = []ScriptTest{
{
Name: "UPDATE IGNORE with primary keys and indexes",
SetUpScript: []string{
"CREATE TABLE pkTable(pk int, val int, primary key(pk, val))",
"CREATE TABLE idxTable(pk int primary key, val int UNIQUE)",
"INSERT INTO pkTable VALUES (1, 1), (2, 2), (3, 3)",
"INSERT INTO idxTable VALUES (1, 1), (2, 2), (3, 3)",
},
Assertions: []ScriptTestAssertion{
{
Query: "UPDATE IGNORE pkTable set pk = pk + 1, val = val + 1",
Expected: []sql.Row{{newUpdateResult(3, 1)}},
ExpectedWarning: mysql.ERDupEntry,
},
{
Query: "SELECT * FROM pkTable order by pk",
Expected: []sql.Row{{1, 1}, {2, 2}, {4, 4}},
},
{
Query: "UPDATE IGNORE idxTable set val = val + 1",
Expected: []sql.Row{{newUpdateResult(3, 1)}},
ExpectedWarning: mysql.ERDupEntry,
},
{
Query: "SELECT * FROM idxTable order by pk",
Expected: []sql.Row{{1, 1}, {2, 2}, {3, 4}},
},
{
Query: "UPDATE IGNORE pkTable set val = val + 1 where pk = 2",
Expected: []sql.Row{{newUpdateResult(1, 1)}},
},
{
Query: "SELECT * FROM pkTable order by pk",
Expected: []sql.Row{{1, 1}, {2, 3}, {4, 4}},
},
{
Query: "UPDATE IGNORE pkTable SET pk = NULL",
Expected: []sql.Row{{newUpdateResult(3, 3)}},
ExpectedWarning: mysql.ERBadNullError,
},
{
Query: "SELECT * FROM pkTable order by pk",
Expected: []sql.Row{{0, 1}, {0, 3}, {0, 4}},
},
{
Query: "UPDATE IGNORE pkTable SET val = NULL",
Expected: []sql.Row{{newUpdateResult(3, 1)}},
},
{
Query: "SELECT * FROM pkTable order by pk",
Expected: []sql.Row{{0, 0}, {0, 3}, {0, 4}},
},
},
},
{
Name: "UPDATE IGNORE with type conversions",
SetUpScript: []string{
"CREATE TABLE t1 (pk int primary key, v1 int)",
"INSERT INTO t1 VALUES (1, 1)",
},
Assertions: []ScriptTestAssertion{
{
Query: "UPDATE IGNORE t1 SET v1 = 'dsddads'",
Expected: []sql.Row{{newUpdateResult(1, 1)}},
ExpectedWarning: mysql.ERTruncatedWrongValueForField,
},
{
Query: "SELECT * FROM t1",
Expected: []sql.Row{{1, 0}},
},
},
},
{
Name: "UPDATE IGNORE with foreign keys",
SetUpScript: []string{
"CREATE TABLE colors ( id INT NOT NULL, color VARCHAR(32) NOT NULL, PRIMARY KEY (id), INDEX color_index(color));",
"CREATE TABLE objects (id INT NOT NULL, name VARCHAR(64) NOT NULL,color VARCHAR(32), PRIMARY KEY(id),FOREIGN KEY (color) REFERENCES colors(color))",
"INSERT INTO colors (id,color) VALUES (1,'red'),(2,'green'),(3,'blue'),(4,'purple')",
"INSERT INTO objects (id,name,color) VALUES (1,'truck','red'),(2,'ball','green'),(3,'shoe','blue')",
},
Assertions: []ScriptTestAssertion{
{
Query: "UPDATE IGNORE objects SET color = 'orange' where id = 2",
Expected: []sql.Row{{newUpdateResult(1, 0)}},
ExpectedWarning: mysql.ErNoReferencedRow2,
},
{
Query: "SELECT * FROM objects ORDER BY id",
Expected: []sql.Row{{1, "truck", "red"}, {2, "ball", "green"}, {3, "shoe", "blue"}},
},
},
},
{
Name: "UPDATE IGNORE with check constraints",
SetUpScript: []string{
"CREATE TABLE checksTable(pk int)",
"ALTER TABLE checksTable ADD CONSTRAINT mycx CHECK (pk < 5)",
"INSERT INTO checksTable VALUES (1),(2),(3),(4)",
},
Assertions: []ScriptTestAssertion{
{
Query: "UPDATE IGNORE checksTable SET pk = pk + 1",
Expected: []sql.Row{{newUpdateResult(4, 3)}},
ExpectedWarning: mysql.ERUnknownError,
},
{
Query: "SELECT * from checksTable ORDER BY pk",
Expected: []sql.Row{{2}, {3}, {4}, {4}},
},
},
},
{
Name: "UPDATE IGNORE keyless tables and secondary indexes",
SetUpScript: []string{
"CREATE TABLE keyless(pk int, val int)",
"INSERT INTO keyless VALUES (1, 1), (2, 2), (3, 3)",
},
Assertions: []ScriptTestAssertion{
{
Query: "UPDATE IGNORE keyless SET val = 2 where pk = 1",
Expected: []sql.Row{{newUpdateResult(1, 1)}},
ExpectedWarning: mysql.ERDupEntry,
},
{
Query: "ALTER TABLE keyless ADD CONSTRAINT c UNIQUE(val)",
ExpectedErr: sql.ErrUniqueKeyViolation,
},
{
Query: "UPDATE IGNORE keyless SET val = 1 where pk = 1",
Expected: []sql.Row{{newUpdateResult(1, 1)}},
ExpectedWarning: mysql.ERDupEntry,
},
{
Query: "ALTER TABLE keyless ADD CONSTRAINT c UNIQUE(val)",
Expected: []sql.Row{{sql.NewOkResult(0)}},
},
{
Query: "UPDATE IGNORE keyless SET val = 2 where pk = 1",
Expected: []sql.Row{{newUpdateResult(1, 0)}},
ExpectedWarning: mysql.ERDupEntry,
},
{
Query: "SELECT * FROM keyless ORDER BY pk",
Expected: []sql.Row{{1, 1}, {2, 2}, {3, 3}},
},
{
Query: "UPDATE IGNORE keyless SET val = val + 1",
Expected: []sql.Row{{newUpdateResult(3, 1)}},
ExpectedWarning: mysql.ERDupEntry,
},
{
Query: "SELECT * FROM keyless ORDER BY pk",
Expected: []sql.Row{{1, 1}, {2, 2}, {3, 4}},
},
},
},
}

var UpdateErrorTests = []QueryErrorTest{
{
Query: `UPDATE keyless INNER JOIN one_pk on keyless.c0 = one_pk.pk SET keyless.c0 = keyless.c0 + 1`,
Expand Down
30 changes: 25 additions & 5 deletions sql/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -615,6 +615,10 @@ func CastSQLError(err error) (*mysql.SQLError, error, bool) {
return CastSQLError(w.Cause)
}

if wm, ok := err.(WrappedTypeConversionError); ok {
return CastSQLError(wm.Err)
}

switch {
case ErrTableNotFound.Is(err):
code = mysql.ERNoSuchTable
Expand Down Expand Up @@ -710,14 +714,30 @@ func (w WrappedInsertError) Error() string {
return w.Cause.Error()
}

type ErrInsertIgnore struct {
// IgnorableError is used propagate information about an error that needs to be ignore and does not interfere with
// any update accumulators
type IgnorableError struct {
OffendingRow Row
}

func NewErrInsertIgnore(row Row) ErrInsertIgnore {
return ErrInsertIgnore{OffendingRow: row}
func NewIgnorableError(row Row) IgnorableError {
return IgnorableError{OffendingRow: row}
}

func (e IgnorableError) Error() string {
return "Insert ignore error should never be printed"
}

type WrappedTypeConversionError struct {
OffendingVal interface{}
OffendingIdx int
Err error
}

func NewWrappedTypeConversionError(offendingVal interface{}, idx int, err error) WrappedTypeConversionError {
return WrappedTypeConversionError{OffendingVal: offendingVal, OffendingIdx: idx, Err: err}
}

func (e ErrInsertIgnore) Error() string {
return "Insert ignore error shoudl never be printed"
func (w WrappedTypeConversionError) Error() string {
return "Wrapped type conversion error should not be printed"
}
4 changes: 2 additions & 2 deletions sql/expression/set.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,9 @@ func (s *SetField) Eval(ctx *sql.Context, row sql.Row) (interface{}, error) {
if err != nil {
// Fill in error with information
if sql.ErrLengthBeyondLimit.Is(err) {
return nil, sql.ErrLengthBeyondLimit.New(val, getField.Name())
return nil, sql.NewWrappedTypeConversionError(val, getField.fieldIndex, sql.ErrLengthBeyondLimit.New(val, getField.Name()))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How can this break?

}
return nil, err
return nil, sql.NewWrappedTypeConversionError(val, getField.fieldIndex, err)
}
val = convertedVal
}
Expand Down
4 changes: 3 additions & 1 deletion sql/parse/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -1909,7 +1909,9 @@ func convertUpdate(ctx *sql.Context, d *sqlparser.Update) (sql.Node, error) {
}
}

return plan.NewUpdate(node, updateExprs), nil
ignore := d.Ignore != ""

return plan.NewUpdate(node, ignore, updateExprs), nil
}

func convertLoad(ctx *sql.Context, d *sqlparser.Load) (sql.Node, error) {
Expand Down
32 changes: 13 additions & 19 deletions sql/parse/parse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1705,16 +1705,13 @@ CREATE TABLE t2
expression.NewLiteral("a", sql.LongText),
&expression.DefaultColumn{},
}}), false, []string{"col1", "col2"}, []sql.Expression{}, false),
`UPDATE t1 SET col1 = ?, col2 = ? WHERE id = ?`: plan.NewUpdate(
plan.NewFilter(
expression.NewEquals(expression.NewUnresolvedColumn("id"), expression.NewBindVar("v3")),
plan.NewUnresolvedTable("t1", ""),
),
[]sql.Expression{
expression.NewSetField(expression.NewUnresolvedColumn("col1"), expression.NewBindVar("v1")),
expression.NewSetField(expression.NewUnresolvedColumn("col2"), expression.NewBindVar("v2")),
},
),
`UPDATE t1 SET col1 = ?, col2 = ? WHERE id = ?`: plan.NewUpdate(plan.NewFilter(
expression.NewEquals(expression.NewUnresolvedColumn("id"), expression.NewBindVar("v3")),
plan.NewUnresolvedTable("t1", ""),
), false, []sql.Expression{
expression.NewSetField(expression.NewUnresolvedColumn("col1"), expression.NewBindVar("v1")),
expression.NewSetField(expression.NewUnresolvedColumn("col2"), expression.NewBindVar("v2")),
}),
`REPLACE INTO t1 (col1, col2) VALUES ('a', 1)`: plan.NewInsertInto(sql.UnresolvedDatabase(""), plan.NewUnresolvedTable("t1", ""), plan.NewValues([][]sql.Expression{{
expression.NewLiteral("a", sql.LongText),
expression.NewLiteral(int8(1), sql.Int8),
Expand Down Expand Up @@ -3703,15 +3700,12 @@ var triggerFixtures = map[string]sql.Node{
plan.NewUnresolvedTable("foo", ""),
plan.NewBeginEndBlock(
plan.NewBlock([]sql.Node{
plan.NewUpdate(
plan.NewFilter(
expression.NewEquals(expression.NewUnresolvedColumn("z"), expression.NewUnresolvedQualifiedColumn("new", "y")),
plan.NewUnresolvedTable("bar", ""),
),
[]sql.Expression{
expression.NewSetField(expression.NewUnresolvedColumn("x"), expression.NewUnresolvedQualifiedColumn("old", "y")),
},
),
plan.NewUpdate(plan.NewFilter(
expression.NewEquals(expression.NewUnresolvedColumn("z"), expression.NewUnresolvedQualifiedColumn("new", "y")),
plan.NewUnresolvedTable("bar", ""),
), false, []sql.Expression{
expression.NewSetField(expression.NewUnresolvedColumn("x"), expression.NewUnresolvedQualifiedColumn("old", "y")),
}),
plan.NewDeleteFrom(
plan.NewFilter(
expression.NewEquals(expression.NewUnresolvedColumn("a"), expression.NewUnresolvedQualifiedColumn("old", "b")),
Expand Down
Loading