From fe3df46efba274b50ec31149de61fc020ea13c35 Mon Sep 17 00:00:00 2001 From: Andres Taylor Date: Thu, 28 Nov 2024 17:00:22 +0100 Subject: [PATCH 1/5] feat: make last_insert_id with argument work for sharded plans Signed-off-by: Andres Taylor --- .../expressions/expressions.test | 4 + go/vt/vtgate/engine/fake_vcursor_test.go | 7 + go/vt/vtgate/engine/primitive.go | 2 +- go/vt/vtgate/engine/save_to_session.go | 125 ++++++++++++++++++ .../planbuilder/operator_transformers.go | 25 ++++ .../planbuilder/operators/query_planning.go | 27 +++- .../planbuilder/operators/save_to_session.go | 71 ++++++++++ .../planbuilder/testdata/select_cases.json | 80 +++++++++-- go/vt/vtgate/semantics/analyzer.go | 4 + go/vt/vtgate/semantics/semantic_table.go | 15 ++- go/vt/vtgate/vcursor_impl.go | 4 + 11 files changed, 345 insertions(+), 19 deletions(-) create mode 100644 go/vt/vtgate/engine/save_to_session.go create mode 100644 go/vt/vtgate/planbuilder/operators/save_to_session.go diff --git a/go/test/endtoend/vtgate/vitess_tester/expressions/expressions.test b/go/test/endtoend/vtgate/vitess_tester/expressions/expressions.test index 60c1e641463..9d8c1e55f67 100644 --- a/go/test/endtoend/vtgate/vitess_tester/expressions/expressions.test +++ b/go/test/endtoend/vtgate/vitess_tester/expressions/expressions.test @@ -28,3 +28,7 @@ SELECT (~ (1 || 0)) IS NULL; SELECT 1 WHERE (~ (1 || 0)) IS NULL; + +select last_insert_id(12); + +select last_insert_id(); \ No newline at end of file diff --git a/go/vt/vtgate/engine/fake_vcursor_test.go b/go/vt/vtgate/engine/fake_vcursor_test.go index 9ba4fdc6a6b..b2e9bbacc05 100644 --- a/go/vt/vtgate/engine/fake_vcursor_test.go +++ b/go/vt/vtgate/engine/fake_vcursor_test.go @@ -263,6 +263,10 @@ func (t *noopVCursor) SetFoundRows(u uint64) { panic("implement me") } +func (t *noopVCursor) SetLastInsertID(id uint64) { + panic("implement me") +} + func (t *noopVCursor) InTransactionAndIsDML() bool { panic("implement me") } @@ -500,6 +504,9 @@ func (f *loggingVCursor) GetSystemVariables(func(k string, v string)) { func (f *loggingVCursor) SetFoundRows(u uint64) { panic("implement me") } +func (f *loggingVCursor) SetLastInsertID(id uint64) { + panic("implement me") +} func (f *loggingVCursor) InTransactionAndIsDML() bool { return false diff --git a/go/vt/vtgate/engine/primitive.go b/go/vt/vtgate/engine/primitive.go index 4f3a388d04f..d79eaa13b05 100644 --- a/go/vt/vtgate/engine/primitive.go +++ b/go/vt/vtgate/engine/primitive.go @@ -182,7 +182,7 @@ type ( SetPriority(string) SetExecQueryTimeout(timeout *int) SetFoundRows(uint64) - + SetLastInsertID(uint64) SetDDLStrategy(string) GetDDLStrategy() string SetMigrationContext(string) diff --git a/go/vt/vtgate/engine/save_to_session.go b/go/vt/vtgate/engine/save_to_session.go new file mode 100644 index 00000000000..98852bf4c0d --- /dev/null +++ b/go/vt/vtgate/engine/save_to_session.go @@ -0,0 +1,125 @@ +/* +Copyright 2019 The Vitess Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package engine + +import ( + "context" + + "vitess.io/vitess/go/sqltypes" + querypb "vitess.io/vitess/go/vt/proto/query" + "vitess.io/vitess/go/vt/sqlparser" + "vitess.io/vitess/go/vt/vtgate/evalengine" +) + +type SaveToSession struct { + noTxNeeded + + Source Primitive + Offset evalengine.Expr +} + +var _ Primitive = (*SaveToSession)(nil) + +func (s *SaveToSession) RouteType() string { + return s.Source.RouteType() +} + +func (s *SaveToSession) GetKeyspaceName() string { + return s.Source.GetKeyspaceName() +} + +func (s *SaveToSession) GetTableName() string { + return s.Source.GetTableName() +} + +func (s *SaveToSession) GetFields(ctx context.Context, vcursor VCursor, bindVars map[string]*querypb.BindVariable) (*sqltypes.Result, error) { + return s.Source.GetFields(ctx, vcursor, bindVars) +} + +// TryExecute on SaveToSession will execute the Source and save the last row's value of Offset into the session. +func (s *SaveToSession) TryExecute(ctx context.Context, vcursor VCursor, bindVars map[string]*querypb.BindVariable, wantfields bool) (*sqltypes.Result, error) { + result, err := s.Source.TryExecute(ctx, vcursor, bindVars, wantfields) + if err != nil { + return nil, err + } + + intEvalResult, ok, err := s.getUintFromOffset(ctx, vcursor, bindVars, result) + if err != nil { + return nil, err + } + if ok { + vcursor.Session().SetLastInsertID(intEvalResult) + } + return result, nil +} + +func (s *SaveToSession) getUintFromOffset(ctx context.Context, vcursor VCursor, bindVars map[string]*querypb.BindVariable, result *sqltypes.Result) (uint64, bool, error) { + if len(result.Rows) == 0 { + return 0, false, nil + } + + env := evalengine.NewExpressionEnv(ctx, bindVars, vcursor) + env.Row = result.Rows[len(result.Rows)-1] // last row + evalResult, err := env.Evaluate(s.Offset) + if err != nil { + return 0, false, err + } + value, err := evalResult.Value(vcursor.ConnCollation()).ToCastUint64() + if err != nil { + return 0, false, err + } + return value, true, nil +} + +// TryStreamExecute on SaveToSession will execute the Source and save the last row's value of Offset into the session. +func (s *SaveToSession) TryStreamExecute(ctx context.Context, vcursor VCursor, bindVars map[string]*querypb.BindVariable, wantfields bool, callback func(*sqltypes.Result) error) error { + var value *uint64 + + f := func(qr *sqltypes.Result) error { + v, ok, err := s.getUintFromOffset(ctx, vcursor, bindVars, qr) + if err != nil { + return err + } + if ok { + value = &v + } + return callback(qr) + } + + err := s.Source.TryStreamExecute(ctx, vcursor, bindVars, wantfields, f) + if err != nil { + return err + } + if value != nil { + vcursor.Session().SetLastInsertID(*value) + } + + return nil +} + +func (s *SaveToSession) Inputs() ([]Primitive, []map[string]any) { + return []Primitive{s.Source}, nil +} + +func (s *SaveToSession) description() PrimitiveDescription { + return PrimitiveDescription{ + OperatorType: "SaveToSession", + Other: map[string]interface{}{ + "Offset": sqlparser.String(s.Offset), + }, + } +} diff --git a/go/vt/vtgate/planbuilder/operator_transformers.go b/go/vt/vtgate/planbuilder/operator_transformers.go index a22719b4489..f7dd499ee86 100644 --- a/go/vt/vtgate/planbuilder/operator_transformers.go +++ b/go/vt/vtgate/planbuilder/operator_transformers.go @@ -81,6 +81,8 @@ func transformToPrimitive(ctx *plancontext.PlanningContext, op operators.Operato return transformRecurseCTE(ctx, op) case *operators.PercentBasedMirror: return transformPercentBasedMirror(ctx, op) + case *operators.SaveToSession: + return transformSaveToSession(ctx, op) } return nil, vterrors.VT13001(fmt.Sprintf("unknown type encountered: %T (transformToPrimitive)", op)) @@ -481,6 +483,29 @@ func newSimpleProjection(cols []int, colNames []string, src engine.Primitive) en } } +func transformSaveToSession(ctx *plancontext.PlanningContext, op *operators.SaveToSession) (engine.Primitive, error) { + src, err := transformToPrimitive(ctx, op.Source) + if err != nil { + return nil, err + } + + cfg := &evalengine.Config{ + ResolveType: ctx.TypeForExpr, + Collation: ctx.SemTable.Collation, + Environment: ctx.VSchema.Environment(), + } + + offset, err := evalengine.Translate(sqlparser.NewOffset(op.Offset, nil), cfg) + if err != nil { + return nil, err + } + + return &engine.SaveToSession{ + Source: src, + Offset: offset, + }, nil +} + func transformFilter(ctx *plancontext.PlanningContext, op *operators.Filter) (engine.Primitive, error) { src, err := transformToPrimitive(ctx, op.Source) if err != nil { diff --git a/go/vt/vtgate/planbuilder/operators/query_planning.go b/go/vt/vtgate/planbuilder/operators/query_planning.go index 5fe0c7773c1..f47d6b6927c 100644 --- a/go/vt/vtgate/planbuilder/operators/query_planning.go +++ b/go/vt/vtgate/planbuilder/operators/query_planning.go @@ -829,11 +829,34 @@ func addTruncationOrProjectionToReturnOutput(ctx *plancontext.PlanningContext, s cols := output.GetSelectExprs(ctx) sizeCorrect := len(selExprs) == len(cols) || tryTruncateColumnsAt(output, len(selExprs)) - if sizeCorrect && colNamesAlign(selExprs, cols) { + if !sizeCorrect || !colNamesAlign(selExprs, cols) { + output = createSimpleProjection(ctx, selExprs, output) + } + + if !ctx.SemTable.QuerySignature.LastInsertIDArg { return output } - return createSimpleProjection(ctx, selExprs, output) + var offset int + for i, expr := range selExprs { + ae, ok := expr.(*sqlparser.AliasedExpr) + if !ok { + panic(vterrors.VT09015()) + } + fnc, ok := ae.Expr.(*sqlparser.FuncExpr) + if !ok || !fnc.Name.EqualString("last_insert_id") { + continue + } + offset = i + break + } + + return &SaveToSession{ + unaryOperator: unaryOperator{ + Source: output, + }, + Offset: offset, + } } func colNamesAlign(expected, actual sqlparser.SelectExprs) bool { diff --git a/go/vt/vtgate/planbuilder/operators/save_to_session.go b/go/vt/vtgate/planbuilder/operators/save_to_session.go new file mode 100644 index 00000000000..da00b7a4426 --- /dev/null +++ b/go/vt/vtgate/planbuilder/operators/save_to_session.go @@ -0,0 +1,71 @@ +/* +Copyright 2022 The Vitess Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package operators + +import ( + "vitess.io/vitess/go/vt/sqlparser" + "vitess.io/vitess/go/vt/vtgate/planbuilder/plancontext" +) + +// SaveToSession is used to save a value to the session. +// At the moment it's only used for last_insert_id, but it could be used for other things in the future. +type SaveToSession struct { + unaryOperator + Offset int +} + +var _ Operator = (*SaveToSession)(nil) + +func (s *SaveToSession) Clone(inputs []Operator) Operator { + k := *s + k.Source = inputs[0] + return &k +} + +func (s *SaveToSession) AddPredicate(ctx *plancontext.PlanningContext, expr sqlparser.Expr) Operator { + src := s.Source.AddPredicate(ctx, expr) + s.Source = src + return s +} + +func (s *SaveToSession) AddColumn(ctx *plancontext.PlanningContext, reuseExisting bool, addToGroupBy bool, expr *sqlparser.AliasedExpr) int { + return s.Source.AddColumn(ctx, reuseExisting, addToGroupBy, expr) +} + +func (s *SaveToSession) AddWSColumn(ctx *plancontext.PlanningContext, offset int, underRoute bool) int { + return s.Source.AddWSColumn(ctx, offset, underRoute) +} + +func (s *SaveToSession) FindCol(ctx *plancontext.PlanningContext, expr sqlparser.Expr, underRoute bool) int { + return s.Source.FindCol(ctx, expr, underRoute) +} + +func (s *SaveToSession) GetColumns(ctx *plancontext.PlanningContext) []*sqlparser.AliasedExpr { + return s.Source.GetColumns(ctx) +} + +func (s *SaveToSession) GetSelectExprs(ctx *plancontext.PlanningContext) sqlparser.SelectExprs { + return s.Source.GetSelectExprs(ctx) +} + +func (s *SaveToSession) ShortDescription() string { + return "" +} + +func (s *SaveToSession) GetOrdering(ctx *plancontext.PlanningContext) []OrderBy { + return s.Source.GetOrdering(ctx) +} diff --git a/go/vt/vtgate/planbuilder/testdata/select_cases.json b/go/vt/vtgate/planbuilder/testdata/select_cases.json index ab69df2cc47..6341e397b13 100644 --- a/go/vt/vtgate/planbuilder/testdata/select_cases.json +++ b/go/vt/vtgate/planbuilder/testdata/select_cases.json @@ -2116,6 +2116,62 @@ ] } }, + { + "comment": "save literal to session", + "query": "select last_insert_id(12)", + "plan": { + "QueryType": "SELECT", + "Original": "select last_insert_id(12)", + "Instructions": { + "OperatorType": "SaveToSession", + "Offset": "_vt_column_0", + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "Reference", + "Keyspace": { + "Name": "main", + "Sharded": false + }, + "FieldQuery": "select last_insert_id(12) from dual where 1 != 1", + "Query": "select last_insert_id(12) from dual", + "Table": "dual" + } + ] + }, + "TablesUsed": [ + "main.dual" + ] + } + }, + { + "comment": "save column value to session - sharded", + "query": "select bar, 12, last_insert_id(foo) from user", + "plan": { + "QueryType": "SELECT", + "Original": "select bar, 12, last_insert_id(foo) from user", + "Instructions": { + "OperatorType": "SaveToSession", + "Offset": "_vt_column_2", + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "Scatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select bar, 12, last_insert_id(foo) from `user` where 1 != 1", + "Query": "select bar, 12, last_insert_id(foo) from `user`", + "Table": "`user`" + } + ] + }, + "TablesUsed": [ + "user.user" + ] + } + }, { "comment": "sql_calc_found_rows with SelectEqualUnique plans", "query": "select sql_calc_found_rows * from music where user_id = 1 limit 2", @@ -5301,15 +5357,21 @@ "QueryType": "SELECT", "Original": "select last_insert_id(id) from user", "Instructions": { - "OperatorType": "Route", - "Variant": "Scatter", - "Keyspace": { - "Name": "user", - "Sharded": true - }, - "FieldQuery": "select last_insert_id(id) from `user` where 1 != 1", - "Query": "select last_insert_id(id) from `user`", - "Table": "`user`" + "OperatorType": "SaveToSession", + "Offset": "_vt_column_0", + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "Scatter", + "Keyspace": { + "Name": "user", + "Sharded": true + }, + "FieldQuery": "select last_insert_id(id) from `user` where 1 != 1", + "Query": "select last_insert_id(id) from `user`", + "Table": "`user`" + } + ] }, "TablesUsed": [ "user.user" diff --git a/go/vt/vtgate/semantics/analyzer.go b/go/vt/vtgate/semantics/analyzer.go index 0a9d2480d9b..fe48405aa45 100644 --- a/go/vt/vtgate/semantics/analyzer.go +++ b/go/vt/vtgate/semantics/analyzer.go @@ -463,6 +463,10 @@ func (a *analyzer) noteQuerySignature(node sqlparser.SQLNode) { a.sig.Aggregation = true case *sqlparser.Delete, *sqlparser.Update, *sqlparser.Insert: a.sig.DML = true + case *sqlparser.FuncExpr: + if len(node.Exprs) == 1 && node.Name.EqualString("last_insert_id") { + a.sig.LastInsertIDArg = true + } } } diff --git a/go/vt/vtgate/semantics/semantic_table.go b/go/vt/vtgate/semantics/semantic_table.go index f9856a901a6..77c8f6db835 100644 --- a/go/vt/vtgate/semantics/semantic_table.go +++ b/go/vt/vtgate/semantics/semantic_table.go @@ -80,13 +80,14 @@ type ( // QuerySignature is used to identify shortcuts in the planning process QuerySignature struct { - Aggregation bool - DML bool - Distinct bool - HashJoin bool - SubQueries bool - Union bool - RecursiveCTE bool + Aggregation bool + DML bool + Distinct bool + HashJoin bool + SubQueries bool + Union bool + RecursiveCTE bool + LastInsertIDArg bool // LastInsertIDArg is true if the query has a LAST_INSERT_ID(x) with an argument } // MirrorInfo stores information used to produce mirror diff --git a/go/vt/vtgate/vcursor_impl.go b/go/vt/vtgate/vcursor_impl.go index e9b1d3d7712..2e68b8f2c06 100644 --- a/go/vt/vtgate/vcursor_impl.go +++ b/go/vt/vtgate/vcursor_impl.go @@ -1027,6 +1027,10 @@ func (vc *vcursorImpl) SetFoundRows(foundRows uint64) { vc.safeSession.foundRowsHandled = true } +func (vc *vcursorImpl) SetLastInsertID(id uint64) { + vc.safeSession.LastInsertId = id +} + // SetDDLStrategy implements the SessionActions interface func (vc *vcursorImpl) SetDDLStrategy(strategy string) { vc.safeSession.SetDDLStrategy(strategy) From 28a9a210de3faf8bcbe4d6d44c88e01867054aa8 Mon Sep 17 00:00:00 2001 From: Andres Taylor Date: Thu, 28 Nov 2024 17:32:45 +0100 Subject: [PATCH 2/5] feat: make it possible to use the last_insert_id() with argument on unsharded select queries Signed-off-by: Andres Taylor --- .../planbuilder/operator_transformers.go | 7 ++++- .../planbuilder/single_sharded_shortcut.go | 24 ++++++++++++++++ .../planbuilder/testdata/select_cases.json | 28 +++++++++++++++++++ go/vt/vtgate/semantics/analyzer.go | 6 +++- go/vt/vtgate/semantics/table_collector.go | 27 +++++++++++------- 5 files changed, 80 insertions(+), 12 deletions(-) diff --git a/go/vt/vtgate/planbuilder/operator_transformers.go b/go/vt/vtgate/planbuilder/operator_transformers.go index f7dd499ee86..4314e8c4dde 100644 --- a/go/vt/vtgate/planbuilder/operator_transformers.go +++ b/go/vt/vtgate/planbuilder/operator_transformers.go @@ -489,13 +489,18 @@ func transformSaveToSession(ctx *plancontext.PlanningContext, op *operators.Save return nil, err } + v := op.Offset + return createSaveToSessionOnOffset(ctx, v, src) +} + +func createSaveToSessionOnOffset(ctx *plancontext.PlanningContext, v int, src engine.Primitive) (engine.Primitive, error) { cfg := &evalengine.Config{ ResolveType: ctx.TypeForExpr, Collation: ctx.SemTable.Collation, Environment: ctx.VSchema.Environment(), } - offset, err := evalengine.Translate(sqlparser.NewOffset(op.Offset, nil), cfg) + offset, err := evalengine.Translate(sqlparser.NewOffset(v, nil), cfg) if err != nil { return nil, err } diff --git a/go/vt/vtgate/planbuilder/single_sharded_shortcut.go b/go/vt/vtgate/planbuilder/single_sharded_shortcut.go index 5d877cd341d..a6910e22179 100644 --- a/go/vt/vtgate/planbuilder/single_sharded_shortcut.go +++ b/go/vt/vtgate/planbuilder/single_sharded_shortcut.go @@ -21,6 +21,7 @@ import ( "strings" "vitess.io/vitess/go/vt/sqlparser" + "vitess.io/vitess/go/vt/vterrors" "vitess.io/vitess/go/vt/vtgate/engine" "vitess.io/vitess/go/vt/vtgate/planbuilder/operators" "vitess.io/vitess/go/vt/vtgate/planbuilder/plancontext" @@ -57,6 +58,29 @@ func selectUnshardedShortcut(ctx *plancontext.PlanningContext, stmt sqlparser.Se if err != nil { return nil, nil, err } + + if ctx.SemTable.QuerySignature.LastInsertIDArg { + var offset *int + for i, expr := range stmt.GetColumns() { + ae, ok := expr.(*sqlparser.AliasedExpr) + if !ok { + panic(vterrors.VT09015()) + } + fnc, ok := ae.Expr.(*sqlparser.FuncExpr) + if !ok || !fnc.Name.EqualString("last_insert_id") { + continue + } + offset = &i + break + } + if offset == nil { + return nil, nil, vterrors.VT12001("couldn't find last_insert_id in select expressions") + } + prim, err = createSaveToSessionOnOffset(ctx, *offset, prim) + if err != nil { + return nil, nil, err + } + } return prim, operators.QualifiedTableNames(ks, tableNames), nil } diff --git a/go/vt/vtgate/planbuilder/testdata/select_cases.json b/go/vt/vtgate/planbuilder/testdata/select_cases.json index 6341e397b13..525cadac691 100644 --- a/go/vt/vtgate/planbuilder/testdata/select_cases.json +++ b/go/vt/vtgate/planbuilder/testdata/select_cases.json @@ -2172,6 +2172,34 @@ ] } }, + { + "comment": "save column value to session - unsharded", + "query": "select bar, 12, last_insert_id(foo) from unsharded", + "plan": { + "QueryType": "SELECT", + "Original": "select bar, 12, last_insert_id(foo) from unsharded", + "Instructions": { + "OperatorType": "SaveToSession", + "Offset": "_vt_column_2", + "Inputs": [ + { + "OperatorType": "Route", + "Variant": "Unsharded", + "Keyspace": { + "Name": "main", + "Sharded": false + }, + "FieldQuery": "select bar, 12, last_insert_id(foo) from unsharded where 1 != 1", + "Query": "select bar, 12, last_insert_id(foo) from unsharded", + "Table": "unsharded" + } + ] + }, + "TablesUsed": [ + "main.unsharded" + ] + } + }, { "comment": "sql_calc_found_rows with SelectEqualUnique plans", "query": "select sql_calc_found_rows * from music where user_id = 1 limit 2", diff --git a/go/vt/vtgate/semantics/analyzer.go b/go/vt/vtgate/semantics/analyzer.go index fe48405aa45..b01cc3fa836 100644 --- a/go/vt/vtgate/semantics/analyzer.go +++ b/go/vt/vtgate/semantics/analyzer.go @@ -151,7 +151,7 @@ func (a *analyzer) newSemTable( ExpandedColumns: map[sqlparser.TableName][]*sqlparser.ColName{}, columns: map[*sqlparser.Union]sqlparser.SelectExprs{}, StatementIDs: a.scoper.statementIDs, - QuerySignature: QuerySignature{}, + QuerySignature: a.sig, childForeignKeysInvolved: map[TableSet][]vindexes.ChildFKInfo{}, parentForeignKeysInvolved: map[TableSet][]vindexes.ParentFKInfo{}, childFkToUpdExprs: map[string]sqlparser.UpdateExprs{}, @@ -362,6 +362,10 @@ func (a *analyzer) analyze(statement sqlparser.Statement) error { return a.err } + if a.earlyTables.lastInsertIdWithArgument { + a.sig.LastInsertIDArg = true + } + if a.canShortCut(statement) { return nil } diff --git a/go/vt/vtgate/semantics/table_collector.go b/go/vt/vtgate/semantics/table_collector.go index 191d9c3b38e..c999b74384d 100644 --- a/go/vt/vtgate/semantics/table_collector.go +++ b/go/vt/vtgate/semantics/table_collector.go @@ -46,6 +46,10 @@ type ( // cte is a map of CTE definitions that are used in the query cte map[string]*CTE + + // lastInsertIdWithArgument is used to signal to later stages that we + // need to do special handling of the engine primitive + lastInsertIdWithArgument bool } ) @@ -59,16 +63,19 @@ func newEarlyTableCollector(si SchemaInformation, currentDb string) *earlyTableC } func (etc *earlyTableCollector) down(cursor *sqlparser.Cursor) bool { - with, ok := cursor.Node().(*sqlparser.With) - if !ok { - return true - } - for _, cte := range with.CTEs { - etc.cte[cte.ID.String()] = &CTE{ - Name: cte.ID.String(), - Query: cte.Subquery, - Columns: cte.Columns, - Recursive: with.Recursive, + switch node := cursor.Node().(type) { + case *sqlparser.With: + for _, cte := range node.CTEs { + etc.cte[cte.ID.String()] = &CTE{ + Name: cte.ID.String(), + Query: cte.Subquery, + Columns: cte.Columns, + Recursive: node.Recursive, + } + } + case *sqlparser.FuncExpr: + if node.Name.EqualString("last_insert_id") && len(node.Exprs) == 1 { + etc.lastInsertIdWithArgument = true } } return true From 44595bb72d0b2371bc2bbc2421bf3e30386af9a0 Mon Sep 17 00:00:00 2001 From: Andres Taylor Date: Thu, 28 Nov 2024 17:41:31 +0100 Subject: [PATCH 3/5] codegen Signed-off-by: Andres Taylor --- go/vt/vtgate/engine/cached_size.go | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/go/vt/vtgate/engine/cached_size.go b/go/vt/vtgate/engine/cached_size.go index 4c0d1009bd1..f5d04e9566d 100644 --- a/go/vt/vtgate/engine/cached_size.go +++ b/go/vt/vtgate/engine/cached_size.go @@ -1079,6 +1079,24 @@ func (cached *SQLCalcFoundRows) CachedSize(alloc bool) int64 { } return size } +func (cached *SaveToSession) CachedSize(alloc bool) int64 { + if cached == nil { + return int64(0) + } + size := int64(0) + if alloc { + size += int64(32) + } + // field Source vitess.io/vitess/go/vt/vtgate/engine.Primitive + if cc, ok := cached.Source.(cachedObject); ok { + size += cc.CachedSize(true) + } + // field Offset vitess.io/vitess/go/vt/vtgate/evalengine.Expr + if cc, ok := cached.Offset.(cachedObject); ok { + size += cc.CachedSize(true) + } + return size +} func (cached *ScalarAggregate) CachedSize(alloc bool) int64 { if cached == nil { return int64(0) From bbc97ffa22cba0aff2224720d14a742ee514b967 Mon Sep 17 00:00:00 2001 From: Andres Taylor Date: Thu, 28 Nov 2024 17:50:33 +0100 Subject: [PATCH 4/5] added note to release summary Signed-off-by: Andres Taylor --- changelog/22.0/22.0.0/summary.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/changelog/22.0/22.0.0/summary.md b/changelog/22.0/22.0.0/summary.md index 7c7257bfae3..e1ea1d61503 100644 --- a/changelog/22.0/22.0.0/summary.md +++ b/changelog/22.0/22.0.0/summary.md @@ -5,6 +5,8 @@ - **[Major Changes](#major-changes)** - **[RPC Changes](#rpc-changes)** - **[Prefer not promoting a replica that is currently taking a backup](#reparents-prefer-not-backing-up)** + - **[Added support for LAST_INSERT_ID(expr)](#last_insert_id)** + ## Major Changes @@ -25,4 +27,8 @@ For planned reparents, hosts taking backups with a backup engine other than `bui valid candidates. This means they will never get promoted - not even if there's no other candidates. Note that behavior for `builtin` backups remains unchanged: a replica that is currently taking a `builtin` backup will -never be promoted, neither by planned nor by emergency reparents. \ No newline at end of file +never be promoted, neither by planned nor by emergency reparents. + +### Added support for LAST_INSERT_ID(expr) + +Added support for LAST_INSERT_ID(expr) to align with MySQL behavior, enabling session-level assignment of the last insert ID via query expressions. \ No newline at end of file From 2792a288c251f5781dd6f457adc43382a23dfc03 Mon Sep 17 00:00:00 2001 From: Andres Taylor Date: Thu, 28 Nov 2024 17:53:38 +0100 Subject: [PATCH 5/5] refactor: removed unneccessary code Signed-off-by: Andres Taylor --- go/vt/vtgate/semantics/analyzer.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/go/vt/vtgate/semantics/analyzer.go b/go/vt/vtgate/semantics/analyzer.go index b01cc3fa836..988932f4414 100644 --- a/go/vt/vtgate/semantics/analyzer.go +++ b/go/vt/vtgate/semantics/analyzer.go @@ -467,10 +467,6 @@ func (a *analyzer) noteQuerySignature(node sqlparser.SQLNode) { a.sig.Aggregation = true case *sqlparser.Delete, *sqlparser.Update, *sqlparser.Insert: a.sig.DML = true - case *sqlparser.FuncExpr: - if len(node.Exprs) == 1 && node.Name.EqualString("last_insert_id") { - a.sig.LastInsertIDArg = true - } } }