From 3ccff206a0172ef68f9b3561fbeeaba306b7d434 Mon Sep 17 00:00:00 2001 From: Arthur Schreiber Date: Thu, 11 Apr 2024 10:29:34 +0000 Subject: [PATCH] Add a test case verifying matching behaviour for the `last_insert_id` behaviour when performing `INSERT ... ON DUPLICATE KEY UPDATE ...` statements. Signed-off-by: Arthur Schreiber --- .../vtgate/queries/dml/insert_test.go | 64 +++++++++++++++++++ .../vtgate/queries/dml/sharded_schema.sql | 10 +++ .../vtgate/queries/dml/unsharded_schema.sql | 12 +++- .../endtoend/vtgate/queries/dml/vschema.json | 17 ++++- 4 files changed, 101 insertions(+), 2 deletions(-) diff --git a/go/test/endtoend/vtgate/queries/dml/insert_test.go b/go/test/endtoend/vtgate/queries/dml/insert_test.go index ce052b7b2ba..e297eea39fc 100644 --- a/go/test/endtoend/vtgate/queries/dml/insert_test.go +++ b/go/test/endtoend/vtgate/queries/dml/insert_test.go @@ -396,6 +396,70 @@ func TestInsertSelectUnshardedUsingSharded(t *testing.T) { } } +// MySQL returns 0 as the `LastInsertId` on `... ON DUPLICATE KEY UPDATE ...` queries +// when no new row is inserted. This test verifies that Vitess behaves the same way. +func TestInsertShardedWithOnDuplicateKeyNoInserts(t *testing.T) { + mcmp, closer := start(t) + defer closer() + + mcmp.Exec("insert into last_insert_id_test (id, sharding_key, user_id, reason) values (1, '1:1:1', 1, 'foo'), (2, '2:2:2', 2, 'bar')") + + // Bump the sequence value so the sequence accounts for the 2 explicit inserts above. + utils.Exec(t, mcmp.VtConn, "SELECT NEXT 2 VALUES FROM uks.last_insert_id_test_seq") + + // First test case, insert a row that already exists, and don't actually change any column at all. + query := "insert into last_insert_id_test (sharding_key, user_id, reason) values ('1:1:1', 1, 'foo') on duplicate key update reason = reason" + + mysqlResult := utils.Exec(t, mcmp.MySQLConn, query) + // no new row inserted, so insert id should be 0. + assert.Equal(t, uint64(0), mysqlResult.InsertID) + // no row was modified, so rows affected should be 0. + assert.Equal(t, uint64(0), mysqlResult.RowsAffected) + + vitessResult := utils.Exec(t, mcmp.VtConn, query) + assert.Equal(t, mysqlResult.RowsAffected, vitessResult.RowsAffected) + assert.Equal(t, mysqlResult.InsertID, vitessResult.InsertID) + + // Second test case, insert a row that already exists, and change a column on the existing row. + query = "insert into last_insert_id_test (sharding_key, user_id, reason) values ('1:1:1', 1, 'bar') on duplicate key update reason = VALUES(reason)" + + mysqlResult = utils.Exec(t, mcmp.MySQLConn, query) + // a row was modified, so insert id should match the auto increment column value of the modified row + assert.Equal(t, uint64(1), mysqlResult.InsertID) + // one row was modified, so rows affected should be 2. + assert.Equal(t, uint64(2), mysqlResult.RowsAffected) + + vitessResult = utils.Exec(t, mcmp.VtConn, query) + assert.Equal(t, mysqlResult.RowsAffected, vitessResult.RowsAffected) + assert.Equal(t, mysqlResult.InsertID, vitessResult.InsertID) + + // Second test case, insert multiple rows, all of which already exist, and change a column on existing rows. + query = "insert into last_insert_id_test (sharding_key, user_id, reason) values ('2:2:2', 2, 'qux'), ('1:1:1', 1, 'baz') on duplicate key update reason = VALUES(reason)" + + mysqlResult = utils.Exec(t, mcmp.MySQLConn, query) + // two rows were modified, so insert id will match the auto increment column value of the last modified row + assert.Equal(t, uint64(1), mysqlResult.InsertID) + // two rows were modified, so rows affected should be 2. + assert.Equal(t, uint64(4), mysqlResult.RowsAffected) + + vitessResult = utils.Exec(t, mcmp.VtConn, query) + assert.Equal(t, mysqlResult.RowsAffected, vitessResult.RowsAffected) + assert.Equal(t, mysqlResult.InsertID, vitessResult.InsertID) + + // Third test case, insert multiple rows, some of which already exist, and change a column on existing rows. + query = "insert into last_insert_id_test (sharding_key, user_id, reason) values ('3:3:3', 3, 'apa'), ('2:2:2', 2, 'bpa'), ('1:1:1', 1, 'cpa') on duplicate key update reason = VALUES(reason)" + + mysqlResult = utils.Exec(t, mcmp.MySQLConn, query) + // a new row was inserted, so insert id should match the auto increment column value of the new row + assert.Equal(t, uint64(7), mysqlResult.InsertID) + // one row was inserted, two rows were modified, so rows affected should be 5. + assert.Equal(t, uint64(5), mysqlResult.RowsAffected) + + vitessResult = utils.Exec(t, mcmp.VtConn, query) + assert.Equal(t, mysqlResult.RowsAffected, vitessResult.RowsAffected) + assert.Equal(t, mysqlResult.InsertID, vitessResult.InsertID) +} + func TestRedactDupError(t *testing.T) { mcmp, closer := start(t) defer closer() diff --git a/go/test/endtoend/vtgate/queries/dml/sharded_schema.sql b/go/test/endtoend/vtgate/queries/dml/sharded_schema.sql index 8ddf9250e45..855b4c90f6d 100644 --- a/go/test/endtoend/vtgate/queries/dml/sharded_schema.sql +++ b/go/test/endtoend/vtgate/queries/dml/sharded_schema.sql @@ -87,3 +87,13 @@ create table lkp_mixed_idx keyspace_id varbinary(20), primary key (lkp_key) ) Engine = InnoDB; + +create table last_insert_id_test +( + id bigint NOT NULL AUTO_INCREMENT, + user_id bigint, + reason varchar(20), + sharding_key varchar(20), + primary key (id), + unique (user_id, sharding_key) +) diff --git a/go/test/endtoend/vtgate/queries/dml/unsharded_schema.sql b/go/test/endtoend/vtgate/queries/dml/unsharded_schema.sql index 4d2ad06618a..e72f37745d8 100644 --- a/go/test/endtoend/vtgate/queries/dml/unsharded_schema.sql +++ b/go/test/endtoend/vtgate/queries/dml/unsharded_schema.sql @@ -29,9 +29,19 @@ create table u_tbl primary key (id) ) Engine = InnoDB; +create table last_insert_id_test_seq +( + id int default 0, + next_id bigint default null, + cache bigint default null, + primary key (id) +) comment 'vitess_sequence' Engine = InnoDB; + insert into user_seq(id, next_id, cache) values (0, 1, 1000); insert into auto_seq(id, next_id, cache) values (0, 666, 1000); insert into mixed_seq(id, next_id, cache) -values (0, 1, 1000); \ No newline at end of file +values (0, 1, 1000); +insert into last_insert_id_test_seq(id, next_id, cache) +values (0, 1, 1000); diff --git a/go/test/endtoend/vtgate/queries/dml/vschema.json b/go/test/endtoend/vtgate/queries/dml/vschema.json index a42a93d7403..0ab7eec46cb 100644 --- a/go/test/endtoend/vtgate/queries/dml/vschema.json +++ b/go/test/endtoend/vtgate/queries/dml/vschema.json @@ -4,6 +4,9 @@ "hash": { "type": "hash" }, + "xxhash": { + "type": "xxhash" + }, "num_vdx": { "type": "consistent_lookup_unique", "params": { @@ -188,6 +191,18 @@ "name": "hash" } ] + }, + "last_insert_id_test": { + "auto_increment": { + "column": "id", + "sequence": "uks.last_insert_id_test_seq" + }, + "column_vindexes": [ + { + "column": "sharding_key", + "name": "xxhash" + } + ] } } -} \ No newline at end of file +}