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)) + }) + } +}