Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion go/test/endtoend/vtgate/lookup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,7 @@ func TestConsistentLookup(t *testing.T) {
mysqlErr := err.(*sqlerror.SQLError)
assert.Equal(t, sqlerror.ERDupEntry, mysqlErr.Num)
assert.Equal(t, "23000", mysqlErr.State)
assert.ErrorContains(t, mysqlErr, "reverted partial DML execution")
assert.ErrorContains(t, mysqlErr, "lookup.Create: target: ks.80-.primary: vttablet: (errno 1062) (sqlstate 23000)")

// Simple delete.
utils.Exec(t, conn, "begin")
Expand Down
124 changes: 104 additions & 20 deletions go/test/endtoend/vtgate/queries/dml/dml_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,92 @@ import (
"github.com/stretchr/testify/require"
)

// TestUniqueLookupDuplicateEntries should fail if the is duplicate in unique lookup column.
func TestUniqueLookupDuplicateEntries(t *testing.T) {
mcmp, closer := start(t)
defer closer()

// initial row
utils.Exec(t, mcmp.VtConn, "insert into s_tbl(id, num) values (1,10)")
utils.AssertMatches(t, mcmp.VtConn, "select id, num from s_tbl order by id", `[[INT64(1) INT64(10)]]`)
utils.AssertMatches(t, mcmp.VtConn, "select num, hex(keyspace_id) from num_vdx_tbl order by num", `[[INT64(10) VARCHAR("166B40B44ABA4BD6")]]`)

// insert duplicate row
utils.AssertContainsError(t, mcmp.VtConn, "insert into s_tbl(id, num) values (2,10)", "lookup.Create: target: sks.-80.primary: vttablet: "+
"Duplicate entry '10' for key 'num_vdx_tbl.PRIMARY'")
utils.AssertMatches(t, mcmp.VtConn, "select id, num from s_tbl order by id", `[[INT64(1) INT64(10)]]`)
utils.AssertMatches(t, mcmp.VtConn, "select num, hex(keyspace_id) from num_vdx_tbl order by num", `[[INT64(10) VARCHAR("166B40B44ABA4BD6")]]`)

// insert duplicate row in multi-row insert multi shard
utils.AssertContainsError(t, mcmp.VtConn, "insert into s_tbl(id, num) values (3,20), (4,20),(5,30)",
"transaction rolled back to reverse changes of partial DML execution: target: sks.80-.primary: vttablet: "+
"Duplicate entry '20' for key 'num_vdx_tbl.PRIMARY'")
utils.AssertMatches(t, mcmp.VtConn, "select id, num from s_tbl order by id", `[[INT64(1) INT64(10)]]`)
utils.AssertMatches(t, mcmp.VtConn, "select num, hex(keyspace_id) from num_vdx_tbl order by num", `[[INT64(10) VARCHAR("166B40B44ABA4BD6")]]`)

// insert duplicate row in multi-row insert - lookup single shard
utils.AssertContainsError(t, mcmp.VtConn, "insert into s_tbl(id, num) values (3,20), (4,20)",
"transaction rolled back to reverse changes of partial DML execution: lookup.Create: target: sks.80-.primary: vttablet: "+
"Duplicate entry '20' for key 'num_vdx_tbl.PRIMARY'")
utils.AssertMatches(t, mcmp.VtConn, "select id, num from s_tbl order by id", `[[INT64(1) INT64(10)]]`)
utils.AssertMatches(t, mcmp.VtConn, "select num, hex(keyspace_id) from num_vdx_tbl order by num", `[[INT64(10) VARCHAR("166B40B44ABA4BD6")]]`)

// insert second row to test with limit update.
utils.Exec(t, mcmp.VtConn, "insert into s_tbl(id, num) values (10,100)")
utils.AssertMatches(t, mcmp.VtConn, "select id, num from s_tbl order by id", `[[INT64(1) INT64(10)] [INT64(10) INT64(100)]]`)
utils.AssertMatches(t, mcmp.VtConn, "select num, hex(keyspace_id) from num_vdx_tbl order by num", `[[INT64(10) VARCHAR("166B40B44ABA4BD6")] [INT64(100) VARCHAR("594764E1A2B2D98E")]]`)

// update with limit 1 succeed.
utils.Exec(t, mcmp.VtConn, "update s_tbl set num = 30 order by id limit 1")
utils.AssertMatches(t, mcmp.VtConn, "select id, num from s_tbl order by id", `[[INT64(1) INT64(30)] [INT64(10) INT64(100)]]`)
utils.AssertMatches(t, mcmp.VtConn, "select num, hex(keyspace_id) from num_vdx_tbl order by num", `[[INT64(30) VARCHAR("166B40B44ABA4BD6")] [INT64(100) VARCHAR("594764E1A2B2D98E")]]`)

// update to same value on multiple row should fail.
utils.AssertContainsError(t, mcmp.VtConn, "update s_tbl set num = 40 limit 2",
"lookup.Create: transaction rolled back to reverse changes of partial DML execution: target: sks.80-.primary: vttablet: "+
"rpc error: code = AlreadyExists desc = Duplicate entry '40' for key 'num_vdx_tbl.PRIMARY'")
utils.AssertMatches(t, mcmp.VtConn, "select id, num from s_tbl order by id", `[[INT64(1) INT64(30)] [INT64(10) INT64(100)]]`)
utils.AssertMatches(t, mcmp.VtConn, "select num, hex(keyspace_id) from num_vdx_tbl order by num", `[[INT64(30) VARCHAR("166B40B44ABA4BD6")] [INT64(100) VARCHAR("594764E1A2B2D98E")]]`)
}

// TestUniqueLookupDuplicateIgnore tests the insert ignore on lookup table.
func TestUniqueLookupDuplicateIgnore(t *testing.T) {
mcmp, closer := start(t)
defer closer()

// initial row
utils.Exec(t, mcmp.VtConn, "insert into s_tbl(id, num) values (1,10)")
utils.AssertMatches(t, mcmp.VtConn, "select id, num from s_tbl order by id", `[[INT64(1) INT64(10)]]`)
utils.AssertMatches(t, mcmp.VtConn, "select num, hex(keyspace_id) from num_vdx_tbl order by num", `[[INT64(10) VARCHAR("166B40B44ABA4BD6")]]`)

// insert ignore duplicate row
qr := utils.Exec(t, mcmp.VtConn, "insert ignore into s_tbl(id, num) values (2,10)")
assert.EqualValues(t, 0, qr.RowsAffected)
utils.AssertMatches(t, mcmp.VtConn, "select id, num from s_tbl order by id", `[[INT64(1) INT64(10)]]`)
utils.AssertMatches(t, mcmp.VtConn, "select num, hex(keyspace_id) from num_vdx_tbl order by num", `[[INT64(10) VARCHAR("166B40B44ABA4BD6")]]`)

// insert duplicate row in multi-row insert - lookup single shard
// Current behavior does not work as expected—one of the rows should be inserted.
// The lookup table is updated, but the main table is not. This is a bug in Vitess.
// The issue occurs because the table has two vindex columns (`num` and `col`), both of which ignore nulls during vindex insertion.
// In the `INSERT IGNORE` case, after the vindex create API call, a verify call checks if the row exists in the lookup table.
// - If the row exists, it is inserted into the main table.
// - If the row does not exist, the main table insertion is skipped.
// Since the `col` column is null, the row is not inserted into the lookup table, causing the main table insertion to be ignored.
qr = utils.Exec(t, mcmp.VtConn, "insert ignore into s_tbl(id, num) values (3,20), (4,20)")
assert.EqualValues(t, 0, qr.RowsAffected)
utils.AssertMatches(t, mcmp.VtConn, "select id, num from s_tbl order by id", `[[INT64(1) INT64(10)]]`)
utils.AssertMatches(t, mcmp.VtConn, "select num, hex(keyspace_id) from num_vdx_tbl order by num", `[[INT64(10) VARCHAR("166B40B44ABA4BD6")] [INT64(20) VARCHAR("4EB190C9A2FA169C")]]`)

// insert duplicate row in multi-row insert - vindex values are not null
qr = utils.Exec(t, mcmp.VtConn, "insert ignore into s_tbl(id, num, col) values (3,20, 30), (4,20, 40)")
assert.EqualValues(t, 1, qr.RowsAffected)
utils.AssertMatches(t, mcmp.VtConn, "select id, num, col from s_tbl order by id", `[[INT64(1) INT64(10) NULL] [INT64(3) INT64(20) INT64(30)]]`)
utils.AssertMatches(t, mcmp.VtConn, "select num, hex(keyspace_id) from num_vdx_tbl order by num", `[[INT64(10) VARCHAR("166B40B44ABA4BD6")] [INT64(20) VARCHAR("4EB190C9A2FA169C")]]`)
utils.AssertMatches(t, mcmp.VtConn, "select col, hex(keyspace_id) from col_vdx_tbl order by col", `[[INT64(30) VARCHAR("4EB190C9A2FA169C")]]`)

}

func TestMultiEqual(t *testing.T) {
if clusterInstance.HasPartialKeyspaces {
t.Skip("test uses multiple keyspaces, test framework only supports partial keyspace testing for a single keyspace")
Expand Down Expand Up @@ -81,7 +167,7 @@ func TestDeleteWithLimit(t *testing.T) {
defer closer()

// initial rows
mcmp.Exec("insert into s_tbl(id, num) values (1,10), (2,10), (3,10), (4,20), (5,5), (6,15), (7,17), (8,80)")
mcmp.Exec("insert into s_tbl(id, num) values (1,10), (4,20), (5,5), (6,15), (7,17), (8,80)")
mcmp.Exec("insert into order_tbl(region_id, oid, cust_no) values (1,1,4), (1,2,2), (2,3,5), (2,4,55)")

// delete with limit
Expand All @@ -93,25 +179,23 @@ func TestDeleteWithLimit(t *testing.T) {

// check rows
mcmp.AssertMatches(`select id, num from s_tbl order by id`,
`[[INT64(3) INT64(10)] [INT64(4) INT64(20)] [INT64(6) INT64(15)] [INT64(7) INT64(17)] [INT64(8) INT64(80)]]`)
`[[INT64(4) INT64(20)] [INT64(7) INT64(17)] [INT64(8) INT64(80)]]`)
// 2 rows matches but limit is 1, so any one of the row can remain in table.
mcmp.AssertMatchesAnyNoCompare(`select region_id, oid, cust_no from order_tbl order by oid`,
`[[INT64(1) INT64(2) INT64(2)] [INT64(2) INT64(3) INT64(5)] [INT64(2) INT64(4) INT64(55)]]`,
`[[INT64(1) INT64(1) INT64(4)] [INT64(2) INT64(3) INT64(5)] [INT64(2) INT64(4) INT64(55)]]`)

// delete with limit
qr = mcmp.Exec(`delete from s_tbl where num < 20 limit 2`)
qr = mcmp.Exec(`delete from s_tbl where num < 25 limit 2`)
require.EqualValues(t, 2, qr.RowsAffected)

qr = mcmp.Exec(`delete from order_tbl limit 5`)
require.EqualValues(t, 3, qr.RowsAffected)

// check rows
// 3 rows matches `num < 20` but limit is 2 so any one of them can remain in the table.
mcmp.AssertMatchesAnyNoCompare(`select id, num from s_tbl order by id`,
`[[INT64(4) INT64(20)] [INT64(7) INT64(17)] [INT64(8) INT64(80)]]`,
`[[INT64(3) INT64(10)] [INT64(4) INT64(20)] [INT64(8) INT64(80)]]`,
`[[INT64(4) INT64(20)] [INT64(6) INT64(15)] [INT64(8) INT64(80)]]`)
mcmp.AssertMatches(`select id, num from s_tbl order by id`,
`[[INT64(8) INT64(80)]]`)
mcmp.AssertMatches(`select region_id, oid, cust_no from order_tbl order by oid`,
`[]`)

Expand All @@ -134,49 +218,49 @@ func TestUpdateWithLimit(t *testing.T) {
defer closer()

// initial rows
mcmp.Exec("insert into s_tbl(id, num) values (1,10), (4,20), (5,5), (6,15), (7,17), (8,80)")
mcmp.Exec("insert into s_tbl(id, col) values (1,10), (4,20), (5,5), (6,15), (7,17), (8,80)")
mcmp.Exec("insert into order_tbl(region_id, oid, cust_no) values (1,1,4), (1,2,2), (2,3,5), (2,4,55)")

// update with limit
qr := mcmp.Exec(`update s_tbl set num = 12 order by num, id limit 1`)
qr := mcmp.Exec(`update s_tbl set col = 12 order by col, id limit 1`)
require.EqualValues(t, 1, qr.RowsAffected)

qr = mcmp.Exec(`update order_tbl set cust_no = 12 where region_id = 1 limit 1`)
require.EqualValues(t, 1, qr.RowsAffected)

// check rows
mcmp.AssertMatches(`select id, num from s_tbl order by id`,
mcmp.AssertMatches(`select id, col from s_tbl order by id`,
`[[INT64(1) INT64(10)] [INT64(4) INT64(20)] [INT64(5) INT64(12)] [INT64(6) INT64(15)] [INT64(7) INT64(17)] [INT64(8) INT64(80)]]`)
// 2 rows matches but limit is 1, so any one of the row can be modified in the table.
mcmp.AssertMatchesAnyNoCompare(`select region_id, oid, cust_no from order_tbl order by oid`,
`[[INT64(1) INT64(1) INT64(12)] [INT64(1) INT64(2) INT64(2)] [INT64(2) INT64(3) INT64(5)] [INT64(2) INT64(4) INT64(55)]]`,
`[[INT64(1) INT64(1) INT64(4)] [INT64(1) INT64(2) INT64(12)] [INT64(2) INT64(3) INT64(5)] [INT64(2) INT64(4) INT64(55)]]`)

// update with limit
qr = mcmp.Exec(`update s_tbl set num = 32 where num > 17 limit 1`)
qr = mcmp.Exec(`update s_tbl set col = 32 where col > 17 limit 1`)
require.EqualValues(t, 1, qr.RowsAffected)

qr = mcmp.Exec(`update order_tbl set cust_no = cust_no + 10 limit 5`)
require.EqualValues(t, 4, qr.RowsAffected)

// check rows
// 2 rows matches `num > 17` but limit is 1 so any one of them will be updated.
mcmp.AssertMatchesAnyNoCompare(`select id, num from s_tbl order by id`,
// 2 rows matches `col > 17` but limit is 1 so any one of them will be updated.
mcmp.AssertMatchesAnyNoCompare(`select id, col from s_tbl order by id`,
`[[INT64(1) INT64(10)] [INT64(4) INT64(32)] [INT64(5) INT64(12)] [INT64(6) INT64(15)] [INT64(7) INT64(17)] [INT64(8) INT64(80)]]`,
`[[INT64(1) INT64(10)] [INT64(4) INT64(20)] [INT64(5) INT64(12)] [INT64(6) INT64(15)] [INT64(7) INT64(17)] [INT64(8) INT64(32)]]`)
mcmp.AssertMatchesAnyNoCompare(`select region_id, oid, cust_no from order_tbl order by oid`,
`[[INT64(1) INT64(1) INT64(22)] [INT64(1) INT64(2) INT64(12)] [INT64(2) INT64(3) INT64(15)] [INT64(2) INT64(4) INT64(65)]]`,
`[[INT64(1) INT64(1) INT64(14)] [INT64(1) INT64(2) INT64(22)] [INT64(2) INT64(3) INT64(15)] [INT64(2) INT64(4) INT64(65)]]`)

// trying with zero limit.
qr = mcmp.Exec(`update s_tbl set num = 44 limit 0`)
qr = mcmp.Exec(`update s_tbl set col = 44 limit 0`)
require.EqualValues(t, 0, qr.RowsAffected)

qr = mcmp.Exec(`update order_tbl set oid = 44 limit 0`)
require.EqualValues(t, 0, qr.RowsAffected)

// trying with limit with no-matching row.
qr = mcmp.Exec(`update s_tbl set num = 44 where id > 100 limit 2`)
qr = mcmp.Exec(`update s_tbl set col = 44 where id > 100 limit 2`)
require.EqualValues(t, 0, qr.RowsAffected)

qr = mcmp.Exec(`update order_tbl set oid = 44 where region_id > 100 limit 2`)
Expand Down Expand Up @@ -219,25 +303,25 @@ func TestDeleteWithSubquery(t *testing.T) {
defer closer()

// initial rows
mcmp.Exec("insert into s_tbl(id, num) values (1,10), (2,10), (3,10), (4,20), (5,5), (6,15), (7,17), (8,80)")
mcmp.Exec("insert into s_tbl(id, col) values (1,10), (2,10), (3,10), (4,20), (5,5), (6,15), (7,17), (8,80)")
mcmp.Exec("insert into order_tbl(region_id, oid, cust_no) values (1,1,4), (1,2,2), (2,3,5), (2,4,55)")

// delete with subquery on s_tbl
qr := mcmp.Exec(`delete from s_tbl where id in (select oid from order_tbl)`)
require.EqualValues(t, 4, qr.RowsAffected)

// check rows
mcmp.AssertMatches(`select id, num from s_tbl order by id`,
mcmp.AssertMatches(`select id, col from s_tbl order by id`,
`[[INT64(5) INT64(5)] [INT64(6) INT64(15)] [INT64(7) INT64(17)] [INT64(8) INT64(80)]]`)
mcmp.AssertMatches(`select region_id, oid, cust_no from order_tbl order by oid`,
`[[INT64(1) INT64(1) INT64(4)] [INT64(1) INT64(2) INT64(2)] [INT64(2) INT64(3) INT64(5)] [INT64(2) INT64(4) INT64(55)]]`)

// delete with subquery on order_tbl
qr = mcmp.Exec(`delete from order_tbl where cust_no > (select num from s_tbl where id = 7)`)
qr = mcmp.Exec(`delete from order_tbl where cust_no > (select col from s_tbl where id = 7)`)
require.EqualValues(t, 1, qr.RowsAffected)

// check rows
mcmp.AssertMatches(`select id, num from s_tbl order by id`,
mcmp.AssertMatches(`select id, col from s_tbl order by id`,
`[[INT64(5) INT64(5)] [INT64(6) INT64(15)] [INT64(7) INT64(17)] [INT64(8) INT64(80)]]`)
mcmp.AssertMatches(`select region_id, oid, cust_no from order_tbl order by oid`,
`[[INT64(1) INT64(1) INT64(4)] [INT64(1) INT64(2) INT64(2)] [INT64(2) INT64(3) INT64(5)]]`)
Expand All @@ -251,7 +335,7 @@ func TestDeleteWithSubquery(t *testing.T) {
require.EqualValues(t, 1, qr.RowsAffected)

// check rows
utils.AssertMatches(t, mcmp.VtConn, `select id, num from s_tbl order by id`,
utils.AssertMatches(t, mcmp.VtConn, `select id, col from s_tbl order by id`,
`[[INT64(5) INT64(5)] [INT64(6) INT64(15)] [INT64(7) INT64(17)] [INT64(8) INT64(80)]]`)
utils.AssertMatches(t, mcmp.VtConn, `select region_id, oid, cust_no from order_tbl order by oid`,
`[[INT64(1) INT64(1) INT64(4)] [INT64(1) INT64(2) INT64(2)]]`)
Expand Down
2 changes: 1 addition & 1 deletion go/test/endtoend/vtgate/queries/dml/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ func start(t *testing.T) (utils.MySQLCompare, func()) {
_, _ = utils.ExecAllowError(t, mcmp.VtConn, "set workload = oltp")

tables := []string{
"s_tbl", "num_vdx_tbl", "user_tbl", "order_tbl", "oevent_tbl", "oextra_tbl",
"s_tbl", "num_vdx_tbl", "col_vdx_tbl", "user_tbl", "order_tbl", "oevent_tbl", "oextra_tbl",
"auto_tbl", "oid_vdx_tbl", "unq_idx", "nonunq_idx", "u_tbl", "mixed_tbl", "j_tbl", "j_utbl",
"t1", "t2",
}
Expand Down
10 changes: 10 additions & 0 deletions go/test/endtoend/vtgate/queries/dml/sharded_schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ create table s_tbl
(
id bigint,
num bigint,
col bigint,
unique key (num),
primary key (id)
) Engine = InnoDB;

Expand All @@ -12,6 +14,14 @@ create table num_vdx_tbl
primary key (num)
) Engine = InnoDB;

create table col_vdx_tbl
(
col bigint,
id bigint,
keyspace_id varbinary(20),
primary key (col, id)
) Engine = InnoDB;

create table user_tbl
(
id bigint,
Expand Down
25 changes: 24 additions & 1 deletion go/test/endtoend/vtgate/queries/dml/vschema.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,18 @@
"params": {
"table": "num_vdx_tbl",
"from": "num",
"to": "keyspace_id"
"to": "keyspace_id",
"ignore_nulls": "true"
},
"owner": "s_tbl"
},
"col_vdx": {
"type": "consistent_lookup",
"params": {
"table": "col_vdx_tbl",
"from": "col,id",
"to": "keyspace_id",
"ignore_nulls": "true"
},
"owner": "s_tbl"
},
Expand Down Expand Up @@ -63,6 +74,10 @@
{
"column": "num",
"name": "num_vdx"
},
{
"columns": ["col", "id"],
"name": "col_vdx"
}
]
},
Expand All @@ -74,6 +89,14 @@
}
]
},
"col_vdx_tbl": {
"column_vindexes": [
{
"column": "col",
"name": "hash"
}
]
},
"user_tbl": {
"auto_increment": {
"column": "id",
Expand Down
32 changes: 19 additions & 13 deletions go/vt/vterrors/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,26 +27,32 @@ const (
// RxOp regex for operation not allowed error
var RxOp = regexp.MustCompile("operation not allowed in state (NOT_SERVING|SHUTTING_DOWN)")

// TxEngineClosed for transaction engine closed error
const TxEngineClosed = "tx engine can't accept new connections in state %v"
// Constants for error messages
const (
// TxEngineClosed for transaction engine closed error
TxEngineClosed = "tx engine can't accept new connections in state %v"

// PrimaryVindexNotSet is the error message to be used when there is no primary vindex found on a table
PrimaryVindexNotSet = "table '%s' does not have a primary vindex"

// WrongTablet for invalid tablet type error
WrongTablet = "wrong tablet type"

// TxKillerRollback purpose when acquire lock on connection for rolling back transaction.
TxKillerRollback = "in use: for tx killer rollback"

// RevertedPartialExec is the error message to be used when a partial DML execution failure is reverted using savepoint.
RevertedPartialExec = "reverted partial DML execution failure"

// WrongTablet for invalid tablet type error
const WrongTablet = "wrong tablet type"
// TxRollbackOnPartialExec is the error message to be used when a transaction is rolled back to reverse changes of partial DML execution
TxRollbackOnPartialExec = "transaction rolled back to reverse changes of partial DML execution"
)

// ConnectionRefused is for gRPC client not being able to connect to a server
const ConnectionRefused = "connection refused"

// RxWrongTablet regex for invalid tablet type error
var RxWrongTablet = regexp.MustCompile("(wrong|invalid) tablet type")

// Constants for error messages
const (
// PrimaryVindexNotSet is the error message to be used when there is no primary vindex found on a table
PrimaryVindexNotSet = "table '%s' does not have a primary vindex"
)

// TxKillerRollback purpose when acquire lock on connection for rolling back transaction.
const TxKillerRollback = "in use: for tx killer rollback"

// TxClosed regex for connection closed
var TxClosed = regexp.MustCompile("transaction ([a-z0-9:]+) (?:ended|not found|in use: for tx killer rollback)")
Loading
Loading