From d5960cff8a4ed531feafca72b07d30ae07a80b07 Mon Sep 17 00:00:00 2001 From: Dirkjan Bussink Date: Fri, 26 Apr 2024 15:08:20 +0200 Subject: [PATCH] projection: Return correct collation information When we have a project, we correctly convert the value to the connection collation using: ```go c.Value(vcursor.ConnCollation()) ``` But we then don't update / change the collation on the fields information. This needs to match the actual data, otherwise the client might corrupt the data. This ensures we add the same rules for the resulting collation. Signed-off-by: Dirkjan Bussink --- go/vt/vtgate/engine/projection.go | 16 +++++++---- go/vt/vtgate/engine/projection_test.go | 38 ++++++++++++++++++++++++++ 2 files changed, 49 insertions(+), 5 deletions(-) diff --git a/go/vt/vtgate/engine/projection.go b/go/vt/vtgate/engine/projection.go index 77e07203476..6fb75bdf800 100644 --- a/go/vt/vtgate/engine/projection.go +++ b/go/vt/vtgate/engine/projection.go @@ -21,6 +21,7 @@ import ( "sync" "vitess.io/vitess/go/mysql" + "vitess.io/vitess/go/mysql/collations" "vitess.io/vitess/go/sqltypes" querypb "vitess.io/vitess/go/vt/proto/query" "vitess.io/vitess/go/vt/sqlparser" @@ -75,7 +76,7 @@ func (p *Projection) TryExecute(ctx context.Context, vcursor VCursor, bindVars m resultRows = append(resultRows, resultRow) } if wantfields { - result.Fields, err = p.evalFields(env, result.Fields) + result.Fields, err = p.evalFields(env, result.Fields, vcursor.ConnCollation()) if err != nil { return nil, err } @@ -96,7 +97,7 @@ func (p *Projection) TryStreamExecute(ctx context.Context, vcursor VCursor, bind defer mu.Unlock() if wantfields { once.Do(func() { - fields, err = p.evalFields(env, qr.Fields) + fields, err = p.evalFields(env, qr.Fields, vcursor.ConnCollation()) if err != nil { return } @@ -135,14 +136,14 @@ func (p *Projection) GetFields(ctx context.Context, vcursor VCursor, bindVars ma return nil, err } env := evalengine.NewExpressionEnv(ctx, bindVars, vcursor) - qr.Fields, err = p.evalFields(env, qr.Fields) + qr.Fields, err = p.evalFields(env, qr.Fields, vcursor.ConnCollation()) if err != nil { return nil, err } return qr, nil } -func (p *Projection) evalFields(env *evalengine.ExpressionEnv, infields []*querypb.Field) ([]*querypb.Field, error) { +func (p *Projection) evalFields(env *evalengine.ExpressionEnv, infields []*querypb.Field, coll collations.ID) ([]*querypb.Field, error) { // TODO: once the evalengine becomes smart enough, we should be able to remove the // dependency on these fields altogether env.Fields = infields @@ -157,10 +158,15 @@ func (p *Projection) evalFields(env *evalengine.ExpressionEnv, infields []*query if !sqltypes.IsNull(typ.Type()) && !typ.Nullable() { fl |= uint32(querypb.MySqlFlag_NOT_NULL_FLAG) } + typCol := typ.Collation() + if sqltypes.IsTextOrBinary(typ.Type()) && typCol != collations.CollationBinaryID { + typCol = coll + } + fields = append(fields, &querypb.Field{ Name: col, Type: typ.Type(), - Charset: uint32(typ.Collation()), + Charset: uint32(typCol), ColumnLength: uint32(typ.Size()), Decimals: uint32(typ.Scale()), Flags: fl, diff --git a/go/vt/vtgate/engine/projection_test.go b/go/vt/vtgate/engine/projection_test.go index af3b23fdf03..51b1ffb558c 100644 --- a/go/vt/vtgate/engine/projection_test.go +++ b/go/vt/vtgate/engine/projection_test.go @@ -228,3 +228,41 @@ func TestFields(t *testing.T) { }) } } + +func TestFieldConversion(t *testing.T) { + var testCases = []struct { + name string + expr string + typ querypb.Type + collation collations.ID + }{ + { + name: `convert different charset`, + expr: `_latin1 0xFF`, + typ: sqltypes.VarChar, + collation: collations.MySQL8().DefaultConnectionCharset(), + }, + } + + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + arg, err := sqlparser.NewTestParser().ParseExpr(testCase.expr) + require.NoError(t, err) + bindExpr, err := evalengine.Translate(arg, &evalengine.Config{ + Environment: vtenv.NewTestEnv(), + Collation: collations.MySQL8().DefaultConnectionCharset(), + }) + require.NoError(t, err) + proj := &Projection{ + Cols: []string{"col"}, + Exprs: []evalengine.Expr{bindExpr}, + Input: &SingleRow{}, + noTxNeeded: noTxNeeded{}, + } + qr, err := proj.TryExecute(context.Background(), &noopVCursor{}, nil, true) + require.NoError(t, err) + assert.Equal(t, testCase.typ, qr.Fields[0].Type) + assert.Equal(t, testCase.collation, collations.ID(qr.Fields[0].Charset)) + }) + } +}