diff --git a/go/test/endtoend/vtgate/misc_test.go b/go/test/endtoend/vtgate/misc_test.go index 133e8397d98..f46c77c7f98 100644 --- a/go/test/endtoend/vtgate/misc_test.go +++ b/go/test/endtoend/vtgate/misc_test.go @@ -529,6 +529,23 @@ func TestFlush(t *testing.T) { exec(t, conn, "flush local tables t1, t2") } +func TestShowVariables(t *testing.T) { + defer cluster.PanicHandler(t) + ctx := context.Background() + conn, err := mysql.Connect(ctx, &vtParams) + require.NoError(t, err) + defer conn.Close() + res := exec(t, conn, "show variables like \"%version%\";") + found := false + for _, row := range res.Rows { + if row[0].ToString() == "version" { + assert.Contains(t, row[1].ToString(), "vitess") + found = true + } + } + require.True(t, found, "Expected a row for version in show query") +} + func assertMatches(t *testing.T, conn *mysql.Conn, query, expected string) { t.Helper() qr := exec(t, conn, query) diff --git a/go/vt/sqlparser/analyzer.go b/go/vt/sqlparser/analyzer.go index b44f5b36b1c..27eaab393e8 100644 --- a/go/vt/sqlparser/analyzer.go +++ b/go/vt/sqlparser/analyzer.go @@ -130,11 +130,17 @@ func CachePlan(stmt Statement) bool { return false } -//IsSetStatement takes Statement and returns if the statement is set statement. -func IsSetStatement(stmt Statement) bool { - switch stmt.(type) { +//MustRewriteAST takes Statement and returns true if RewriteAST must run on it for correct execution irrespective of user flags. +func MustRewriteAST(stmt Statement) bool { + switch node := stmt.(type) { case *Set: return true + case *Show: + switch node.Internal.(type) { + case *ShowBasic: + return true + } + return false } return false } diff --git a/go/vt/sqlparser/ast_rewriting.go b/go/vt/sqlparser/ast_rewriting.go index bc4fbdc9df5..2494a39527f 100644 --- a/go/vt/sqlparser/ast_rewriting.go +++ b/go/vt/sqlparser/ast_rewriting.go @@ -178,6 +178,13 @@ func (er *expressionRewriter) rewrite(cursor *Cursor) bool { node.Expr = aliasTableName cursor.Replace(node) } + case *ShowBasic: + if node.Command == VariableGlobal || node.Command == VariableSession { + varsToAdd := sysvars.GetInterestingVariables() + for _, sysVar := range varsToAdd { + er.bindVars.AddSysVar(sysVar) + } + } } return true } diff --git a/go/vt/sqlparser/ast_rewriting_test.go b/go/vt/sqlparser/ast_rewriting_test.go index be76008c420..721e7ccdb44 100644 --- a/go/vt/sqlparser/ast_rewriting_test.go +++ b/go/vt/sqlparser/ast_rewriting_test.go @@ -181,6 +181,40 @@ func TestRewrites(in *testing.T) { in: "CALL proc(@foo)", expected: "CALL proc(:__vtudvfoo)", udv: 1, + }, { + in: "SHOW VARIABLES", + expected: "SHOW VARIABLES", + autocommit: true, + clientFoundRows: true, + skipQueryPlanCache: true, + sqlSelectLimit: true, + transactionMode: true, + workload: true, + version: true, + versionComment: true, + ddlStrategy: true, + sessionUUID: true, + sessionEnableSystemSettings: true, + rawGTID: true, + rawTimeout: true, + sessTrackGTID: true, + }, { + in: "SHOW GLOBAL VARIABLES", + expected: "SHOW GLOBAL VARIABLES", + autocommit: true, + clientFoundRows: true, + skipQueryPlanCache: true, + sqlSelectLimit: true, + transactionMode: true, + workload: true, + version: true, + versionComment: true, + ddlStrategy: true, + sessionUUID: true, + sessionEnableSystemSettings: true, + rawGTID: true, + rawTimeout: true, + sessTrackGTID: true, }} for _, tc := range tests { diff --git a/go/vt/sysvars/sysvars.go b/go/vt/sysvars/sysvars.go index d7a7f8d50d2..40cb7818cf3 100644 --- a/go/vt/sysvars/sysvars.go +++ b/go/vt/sysvars/sysvars.go @@ -238,3 +238,16 @@ var ( {Name: "version_tokens_session"}, } ) + +// GetInterestingVariables is used to return all the variables that may be listed in a SHOW VARIABLES command. +func GetInterestingVariables() []string { + var res []string + // Add all the vitess aware variables + for _, variable := range VitessAware { + res = append(res, variable.Name) + } + // Also add version and version comment + res = append(res, Version.Name) + res = append(res, VersionComment.Name) + return res +} diff --git a/go/vt/vtgate/engine/cached_size.go b/go/vt/vtgate/engine/cached_size.go index 2278127a81c..29ec6a925e8 100644 --- a/go/vt/vtgate/engine/cached_size.go +++ b/go/vt/vtgate/engine/cached_size.go @@ -443,6 +443,20 @@ func (cached *PulloutSubquery) CachedSize(alloc bool) int64 { } return size } +func (cached *ReplaceVariables) CachedSize(alloc bool) int64 { + if cached == nil { + return int64(0) + } + size := int64(0) + if alloc { + size += int64(16) + } + // field Input vitess.io/vitess/go/vt/vtgate/engine.Primitive + if cc, ok := cached.Input.(cachedObject); ok { + size += cc.CachedSize(true) + } + return size +} func (cached *Route) CachedSize(alloc bool) int64 { if cached == nil { return int64(0) diff --git a/go/vt/vtgate/engine/replace_variables.go b/go/vt/vtgate/engine/replace_variables.go new file mode 100644 index 00000000000..c0397e26403 --- /dev/null +++ b/go/vt/vtgate/engine/replace_variables.go @@ -0,0 +1,97 @@ +/* +Copyright 2021 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 ( + "vitess.io/vitess/go/sqltypes" + querypb "vitess.io/vitess/go/vt/proto/query" +) + +var _ Primitive = (*ReplaceVariables)(nil) + +// ReplaceVariables is used in SHOW VARIABLES statements so that it replaces the values for vitess-aware variables +type ReplaceVariables struct { + Input Primitive + noTxNeeded +} + +// NewReplaceVariables is used to create a new ReplaceVariables primitive +func NewReplaceVariables(input Primitive) *ReplaceVariables { + return &ReplaceVariables{Input: input} +} + +// RouteType implements the Primitive interface +func (r *ReplaceVariables) RouteType() string { + return r.Input.RouteType() +} + +// GetKeyspaceName implements the Primitive interface +func (r *ReplaceVariables) GetKeyspaceName() string { + return r.Input.GetKeyspaceName() +} + +// GetTableName implements the Primitive interface +func (r *ReplaceVariables) GetTableName() string { + return r.Input.GetTableName() +} + +// Execute implements the Primitive interface +func (r *ReplaceVariables) Execute(vcursor VCursor, bindVars map[string]*querypb.BindVariable, wantfields bool) (*sqltypes.Result, error) { + qr, err := r.Input.Execute(vcursor, bindVars, wantfields) + if err != nil { + return nil, err + } + replaceVariables(qr, bindVars) + return qr, nil +} + +// StreamExecute implements the Primitive interface +func (r *ReplaceVariables) StreamExecute(vcursor VCursor, bindVars map[string]*querypb.BindVariable, wantfields bool, callback func(*sqltypes.Result) error) error { + innerCallback := callback + callback = func(result *sqltypes.Result) error { + replaceVariables(result, bindVars) + return innerCallback(result) + } + return r.Input.StreamExecute(vcursor, bindVars, wantfields, callback) +} + +// GetFields implements the Primitive interface +func (r *ReplaceVariables) GetFields(vcursor VCursor, bindVars map[string]*querypb.BindVariable) (*sqltypes.Result, error) { + return r.Input.GetFields(vcursor, bindVars) +} + +// Inputs implements the Primitive interface +func (r *ReplaceVariables) Inputs() []Primitive { + return []Primitive{r.Input} +} + +// description implements the Primitive interface +func (r *ReplaceVariables) description() PrimitiveDescription { + return PrimitiveDescription{ + OperatorType: "ReplaceVariables", + } +} + +func replaceVariables(qr *sqltypes.Result, bindVars map[string]*querypb.BindVariable) { + for i, row := range qr.Rows { + variableName := row[0].ToString() + res, found := bindVars["__vt"+variableName] + if found { + qr.Rows[i][1] = sqltypes.NewVarChar(string(res.GetValue())) + } + } +} diff --git a/go/vt/vtgate/executor.go b/go/vt/vtgate/executor.go index 452ae56173d..8b5c1bd8313 100644 --- a/go/vt/vtgate/executor.go +++ b/go/vt/vtgate/executor.go @@ -1237,7 +1237,7 @@ func (e *Executor) getPlan(vcursor *vcursorImpl, sql string, comments sqlparser. vcursor.SetIgnoreMaxMemoryRows(ignoreMaxMemoryRows) // Normalize if possible and retry. - if (e.normalize && sqlparser.CanNormalize(stmt)) || sqlparser.IsSetStatement(stmt) { + if (e.normalize && sqlparser.CanNormalize(stmt)) || sqlparser.MustRewriteAST(stmt) { parameterize := e.normalize // the public flag is called normalize result, err := sqlparser.PrepareAST(stmt, bindVars, "vtg", parameterize, vcursor.keyspace) if err != nil { diff --git a/go/vt/vtgate/planbuilder/show.go b/go/vt/vtgate/planbuilder/show.go index 31b36d7e588..61001179c91 100644 --- a/go/vt/vtgate/planbuilder/show.go +++ b/go/vt/vtgate/planbuilder/show.go @@ -54,9 +54,10 @@ func buildShowBasicPlan(show *sqlparser.ShowBasic, vschema ContextVSchema) (engi switch show.Command { case sqlparser.Charset: return buildCharsetPlan(show) - case sqlparser.Collation, sqlparser.Function, sqlparser.Privilege, sqlparser.Procedure, - sqlparser.VariableGlobal, sqlparser.VariableSession: + case sqlparser.Collation, sqlparser.Function, sqlparser.Privilege, sqlparser.Procedure: return buildSendAnywherePlan(show, vschema) + case sqlparser.VariableGlobal, sqlparser.VariableSession: + return buildVariablePlan(show, vschema) case sqlparser.Column, sqlparser.Index: return buildShowTblPlan(show, vschema) case sqlparser.Database, sqlparser.Keyspace: @@ -98,6 +99,15 @@ func buildSendAnywherePlan(show *sqlparser.ShowBasic, vschema ContextVSchema) (e }, nil } +func buildVariablePlan(show *sqlparser.ShowBasic, vschema ContextVSchema) (engine.Primitive, error) { + plan, err := buildSendAnywherePlan(show, vschema) + if err != nil { + return nil, err + } + plan = engine.NewReplaceVariables(plan) + return plan, nil +} + func buildShowTblPlan(show *sqlparser.ShowBasic, vschema ContextVSchema) (engine.Primitive, error) { if show.DbName != "" { show.Tbl.Qualifier = sqlparser.NewTableIdent(show.DbName) diff --git a/go/vt/vtgate/planbuilder/testdata/show_cases.txt b/go/vt/vtgate/planbuilder/testdata/show_cases.txt index 8316f4c7560..1117121f1f0 100644 --- a/go/vt/vtgate/planbuilder/testdata/show_cases.txt +++ b/go/vt/vtgate/planbuilder/testdata/show_cases.txt @@ -220,15 +220,43 @@ "QueryType": "SHOW", "Original": "show variables", "Instructions": { - "OperatorType": "Send", - "Keyspace": { - "Name": "main", - "Sharded": false - }, - "TargetDestination": "AnyShard()", - "IsDML": false, - "Query": "show variables", - "SingleShardOnly": true + "OperatorType": "ReplaceVariables", + "Inputs": [ + { + "OperatorType": "Send", + "Keyspace": { + "Name": "main", + "Sharded": false + }, + "TargetDestination": "AnyShard()", + "IsDML": false, + "Query": "show variables", + "SingleShardOnly": true + } + ] + } +} + +# show global variables +"show global variables" +{ + "QueryType": "SHOW", + "Original": "show global variables", + "Instructions": { + "OperatorType": "ReplaceVariables", + "Inputs": [ + { + "OperatorType": "Send", + "Keyspace": { + "Name": "main", + "Sharded": false + }, + "TargetDestination": "AnyShard()", + "IsDML": false, + "Query": "show global variables", + "SingleShardOnly": true + } + ] } } diff --git a/go/vt/vtgate/planbuilder/testdata/show_cases_no_default_keyspace.txt b/go/vt/vtgate/planbuilder/testdata/show_cases_no_default_keyspace.txt index 737b6ed6bb2..df2253e38bb 100644 --- a/go/vt/vtgate/planbuilder/testdata/show_cases_no_default_keyspace.txt +++ b/go/vt/vtgate/planbuilder/testdata/show_cases_no_default_keyspace.txt @@ -40,14 +40,19 @@ "QueryType": "SHOW", "Original": "show variables", "Instructions": { - "OperatorType": "Send", - "Keyspace": { - "Name": "main", - "Sharded": false - }, - "TargetDestination": "AnyShard()", - "IsDML": false, - "Query": "show variables", - "SingleShardOnly": true + "OperatorType": "ReplaceVariables", + "Inputs": [ + { + "OperatorType": "Send", + "Keyspace": { + "Name": "main", + "Sharded": false + }, + "TargetDestination": "AnyShard()", + "IsDML": false, + "Query": "show variables", + "SingleShardOnly": true + } + ] } }