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

feat: Add support for Insert with row alias #15790

Merged
merged 8 commits into from
Apr 29, 2024
11 changes: 11 additions & 0 deletions changelog/20.0/20.0.0/summary.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
- [Delete with Subquery Support](#delete-subquery)
- [Delete with Multi Target Support](#delete-multi-target)
- [User Defined Functions Support](#udf-support)
- [Insert Row Alias Support](#insert-row-alias-support)
- **[Query Timeout](#query-timeout)**
- **[Flag changes](#flag-changes)**
- [`pprof-http` default change](#pprof-http-default)
Expand Down Expand Up @@ -197,6 +198,16 @@ Without this flag, VTGate will not be aware that there might be aggregating user

More details about how to load UDFs is available in [MySQL Docs](https://dev.mysql.com/doc/extending-mysql/8.0/en/adding-loadable-function.html)

#### <a id="insert-row-alias-support"/> Insert Row Alias Support

Support is added to have row alias in Insert statement to be used with `on duplicate key update`.

Example:
- `insert into user(id, name, email) valies (100, 'Alice', '[email protected]') as new on duplicate key update name = new.name, email = new.email`
- `insert into user(id, name, email) valies (100, 'Alice', '[email protected]') as new(m, n, p) on duplicate key update name = n, email = p`

More details about how it works is available in [MySQL Docs](https://dev.mysql.com/doc/refman/8.0/en/insert-on-duplicate.html)

### <a id="query-timeout"/>Query Timeout
On a query timeout, Vitess closed the connection using the `kill connection` statement. This leads to connection churn
which is not desirable in some cases. To avoid this, Vitess now uses the `kill query` statement to cancel the query.
Expand Down
1 change: 1 addition & 0 deletions go/mysql/sqlerror/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -235,6 +235,7 @@ const (
ERUnknownTimeZone = ErrorCode(1298)
ERInvalidCharacterString = ErrorCode(1300)
ERQueryInterrupted = ErrorCode(1317)
ERViewWrongList = ErrorCode(1353)
ERTruncatedWrongValueForField = ErrorCode(1366)
ERIllegalValueForType = ErrorCode(1367)
ERDataTooLong = ErrorCode(1406)
Expand Down
1 change: 1 addition & 0 deletions go/mysql/sqlerror/sql_error.go
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,7 @@ var stateToMysqlCode = map[vterrors.State]mysqlCode{
vterrors.OperandColumns: {num: EROperandColumns, state: SSWrongNumberOfColumns},
vterrors.WrongValueCountOnRow: {num: ERWrongValueCountOnRow, state: SSWrongValueCountOnRow},
vterrors.WrongArguments: {num: ERWrongArguments, state: SSUnknownSQLState},
vterrors.ViewWrongList: {num: ERViewWrongList, state: SSUnknownSQLState},
vterrors.UnknownStmtHandler: {num: ERUnknownStmtHandler, state: SSUnknownSQLState},
vterrors.KeyDoesNotExist: {num: ERKeyDoesNotExist, state: SSClientError},
vterrors.UnknownTimeZone: {num: ERUnknownTimeZone, state: SSUnknownSQLState},
Expand Down
24 changes: 24 additions & 0 deletions go/test/endtoend/vtgate/queries/dml/insert_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -462,3 +462,27 @@ func TestMixedCases(t *testing.T) {
// final check count on the lookup vindex table.
utils.AssertMatches(t, mcmp.VtConn, "select count(*) from lkp_mixed_idx", "[[INT64(12)]]")
}

// TestInsertAlias test the alias feature in insert statement.
func TestInsertAlias(t *testing.T) {
utils.SkipIfBinaryIsBelowVersion(t, 20, "vtgate")
utils.SkipIfBinaryIsBelowVersion(t, 20, "vttablet")

mcmp, closer := start(t)
defer closer()

// initial record
mcmp.Exec("insert into user_tbl(id, region_id, name) values (1, 1,'foo'),(2, 2,'bar'),(3, 3,'baz'),(4, 4,'buzz')")

qr := mcmp.Exec("insert into user_tbl(id, region_id, name) values (2, 2, 'foo') as new on duplicate key update name = new.name")
assert.EqualValues(t, 2, qr.RowsAffected)

// this validates the record.
mcmp.Exec("select id, region_id, name from user_tbl order by id")

qr = mcmp.Exec("insert into user_tbl(id, region_id, name) values (3, 3, 'foo') as new(m, n, p) on duplicate key update name = p")
assert.EqualValues(t, 2, qr.RowsAffected)

// this validates the record.
mcmp.Exec("select id, region_id, name from user_tbl order by id")
}
2 changes: 1 addition & 1 deletion go/vt/sqlparser/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -341,8 +341,8 @@ type (
Partitions Partitions
Columns Columns
Rows InsertRows
OnDup OnDup
RowAlias *RowAlias
OnDup OnDup
}

// Ignore represents whether ignore was specified or not
Expand Down
2 changes: 1 addition & 1 deletion go/vt/sqlparser/ast_clone.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions go/vt/sqlparser/ast_copy_on_rewrite.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions go/vt/sqlparser/ast_equals.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions go/vt/sqlparser/ast_rewrite.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions go/vt/sqlparser/ast_visit.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions go/vt/sqlparser/cached_size.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions go/vt/vterrors/code.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ var (
VT03030 = errorWithState("VT03030", vtrpcpb.Code_INVALID_ARGUMENT, WrongValueCountOnRow, "lookup column count does not match value count with the row (columns, count): (%v, %d)", "The number of columns you want to insert do not match the number of columns of your SELECT query.")
VT03031 = errorWithoutState("VT03031", vtrpcpb.Code_INVALID_ARGUMENT, "EXPLAIN is only supported for single keyspace", "EXPLAIN has to be sent down as a single query to the underlying MySQL, and this is not possible if it uses tables from multiple keyspaces")
VT03032 = errorWithState("VT03032", vtrpcpb.Code_INVALID_ARGUMENT, NonUpdateableTable, "the target table %s of the UPDATE is not updatable", "You cannot update a table that is not a real MySQL table.")
VT03033 = errorWithState("VT03033", vtrpcpb.Code_INVALID_ARGUMENT, ViewWrongList, "In definition of view, derived table or common table expression, SELECT list and column names list have different column counts", "The table column list and derived column list have different column counts.")

VT05001 = errorWithState("VT05001", vtrpcpb.Code_NOT_FOUND, DbDropExists, "cannot drop database '%s'; database does not exists", "The given database does not exist; Vitess cannot drop it.")
VT05002 = errorWithState("VT05002", vtrpcpb.Code_NOT_FOUND, BadDb, "cannot alter database '%s'; unknown database", "The given database does not exist; Vitess cannot alter it.")
Expand Down Expand Up @@ -146,6 +147,7 @@ var (
VT03030,
VT03031,
VT03032,
VT03033,
VT05001,
VT05002,
VT05003,
Expand Down
1 change: 1 addition & 0 deletions go/vt/vterrors/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ const (
WrongArguments
BadNullError
InvalidGroupFuncUse
ViewWrongList

// failed precondition
NoDB
Expand Down
4 changes: 3 additions & 1 deletion go/vt/vtgate/engine/cached_size.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

18 changes: 17 additions & 1 deletion go/vt/vtgate/engine/insert.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ type Insert struct {

// Mid is the row values for the sharded insert plans.
Mid sqlparser.Values

// Alias represents the row alias with columns if specified in the query.
Alias string
}

// newQueryInsert creates an Insert with a query string.
Expand Down Expand Up @@ -287,7 +290,7 @@ func (ins *Insert) getInsertShardedQueries(
}
}
}
rewritten := ins.Prefix + strings.Join(mids, ",") + sqlparser.String(ins.Suffix)
rewritten := ins.Prefix + strings.Join(mids, ",") + ins.Alias + sqlparser.String(ins.Suffix)
queries[i] = &querypb.BoundQuery{
Sql: rewritten,
BindVariables: shardBindVars,
Expand Down Expand Up @@ -363,6 +366,19 @@ func (ins *Insert) description() PrimitiveDescription {
other["VindexValues"] = valuesOffsets
}

// This is a check to ensure we send the correct query to the database.
// "ActualQuery" should not be part of the plan output, if it does, it means the query was not rewritten correctly.
if ins.Mid != nil {
var mids []string
for _, n := range ins.Mid {
mids = append(mids, sqlparser.String(n))
}
shardedQuery := ins.Prefix + strings.Join(mids, ", ") + ins.Alias + sqlparser.String(ins.Suffix)
if shardedQuery != ins.Query {
other["ActualQuery"] = shardedQuery
}
}

Comment on lines +369 to +381
Copy link
Member

Choose a reason for hiding this comment

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

Was this code intended for debugging to see when the sharded query and the original query are different?

Copy link
Member Author

Choose a reason for hiding this comment

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

I kept this for a reason to fail in future if anything goes wrong in the final query output.

return PrimitiveDescription{
OperatorType: "Insert",
Keyspace: ins.Keyspace,
Expand Down
5 changes: 4 additions & 1 deletion go/vt/vtgate/planbuilder/operator_transformers.go
Original file line number Diff line number Diff line change
Expand Up @@ -619,6 +619,9 @@ func buildInsertLogicalPlan(
// when unsharded query with autoincrement for that there is no input operator.
if eins.Opcode != engine.InsertUnsharded {
eins.Prefix, eins.Mid, eins.Suffix = generateInsertShardedQuery(ins.AST)
if ins.AST.RowAlias != nil {
eins.Alias = sqlparser.String(ins.AST.RowAlias)
}
}

eins.Query = generateQuery(stmt)
Expand Down Expand Up @@ -660,7 +663,7 @@ func generateInsertShardedQuery(ins *sqlparser.Insert) (prefix string, mids sqlp
prefixBuf := sqlparser.NewTrackedBuffer(dmlFormatter)
prefixBuf.Myprintf(prefixFormat,
ins.Comments, ins.Ignore.ToString(),
ins.Table, ins.Columns)
ins.Table, ins.Columns, ins.RowAlias)
prefix = prefixBuf.String()

suffix = sqlparser.CopyOnRewrite(ins.OnDup, nil, func(cursor *sqlparser.CopyOnWriteCursor) {
Expand Down
104 changes: 104 additions & 0 deletions go/vt/vtgate/planbuilder/testdata/dml_cases.json
Original file line number Diff line number Diff line change
Expand Up @@ -6752,5 +6752,109 @@
"user.user"
]
}
},
{
"comment": "RowAlias in INSERT",
"query": "INSERT INTO authoritative (user_id,col1,col2) VALUES (1,'2',3),(4,'5',6) AS new ON DUPLICATE KEY UPDATE col2 = new.user_id+new.col1",
"plan": {
"QueryType": "INSERT",
"Original": "INSERT INTO authoritative (user_id,col1,col2) VALUES (1,'2',3),(4,'5',6) AS new ON DUPLICATE KEY UPDATE col2 = new.user_id+new.col1",
"Instructions": {
"OperatorType": "Insert",
"Variant": "Sharded",
"Keyspace": {
"Name": "user",
"Sharded": true
},
"TargetTabletType": "PRIMARY",
"InsertIgnore": true,
"Query": "insert into authoritative(user_id, col1, col2) values (:_user_id_0, '2', 3), (:_user_id_1, '5', 6) as new on duplicate key update col2 = new.user_id + new.col1",
"TableName": "authoritative",
"VindexValues": {
"user_index": "1, 4"
}
},
"TablesUsed": [
"user.authoritative"
]
}
},
{
"comment": "RowAlias with explicit columns in INSERT",
"query": "INSERT INTO authoritative (user_id,col1,col2) VALUES (1,'2',3),(4,'5',6) AS new(a,b,c) ON DUPLICATE KEY UPDATE col1 = a+c",
"plan": {
"QueryType": "INSERT",
"Original": "INSERT INTO authoritative (user_id,col1,col2) VALUES (1,'2',3),(4,'5',6) AS new(a,b,c) ON DUPLICATE KEY UPDATE col1 = a+c",
"Instructions": {
"OperatorType": "Insert",
"Variant": "Sharded",
"Keyspace": {
"Name": "user",
"Sharded": true
},
"TargetTabletType": "PRIMARY",
"InsertIgnore": true,
"Query": "insert into authoritative(user_id, col1, col2) values (:_user_id_0, '2', 3), (:_user_id_1, '5', 6) as new (a, b, c) on duplicate key update col1 = a + c",
"TableName": "authoritative",
"VindexValues": {
"user_index": "1, 4"
}
},
"TablesUsed": [
"user.authoritative"
]
}
},
{
"comment": "RowAlias in INSERT (no column list)",
"query": "INSERT INTO authoritative VALUES (1,'2',3),(4,'5',6) AS new ON DUPLICATE KEY UPDATE col2 = new.user_id+new.col1",
"plan": {
"QueryType": "INSERT",
"Original": "INSERT INTO authoritative VALUES (1,'2',3),(4,'5',6) AS new ON DUPLICATE KEY UPDATE col2 = new.user_id+new.col1",
"Instructions": {
"OperatorType": "Insert",
"Variant": "Sharded",
"Keyspace": {
"Name": "user",
"Sharded": true
},
"TargetTabletType": "PRIMARY",
"InsertIgnore": true,
"Query": "insert into authoritative(user_id, col1, col2) values (:_user_id_0, '2', 3), (:_user_id_1, '5', 6) as new on duplicate key update col2 = new.user_id + new.col1",
"TableName": "authoritative",
"VindexValues": {
"user_index": "1, 4"
}
},
"TablesUsed": [
"user.authoritative"
]
}
},
{
"comment": "RowAlias with explicit columns in INSERT (no column list)",
"query": "INSERT INTO authoritative VALUES (1,'2',3),(4,'5',6) AS new(a,b,c) ON DUPLICATE KEY UPDATE col1 = a+c",
"plan": {
"QueryType": "INSERT",
"Original": "INSERT INTO authoritative VALUES (1,'2',3),(4,'5',6) AS new(a,b,c) ON DUPLICATE KEY UPDATE col1 = a+c",
"Instructions": {
"OperatorType": "Insert",
"Variant": "Sharded",
"Keyspace": {
"Name": "user",
"Sharded": true
},
"TargetTabletType": "PRIMARY",
"InsertIgnore": true,
"Query": "insert into authoritative(user_id, col1, col2) values (:_user_id_0, '2', 3), (:_user_id_1, '5', 6) as new (a, b, c) on duplicate key update col1 = a + c",
"TableName": "authoritative",
"VindexValues": {
"user_index": "1, 4"
}
},
"TablesUsed": [
"user.authoritative"
]
}
}
]
Loading
Loading