Skip to content

Commit

Permalink
projection: Return correct collation information
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
dbussink committed Apr 26, 2024
1 parent 8f6cfaa commit d5960cf
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 5 deletions.
16 changes: 11 additions & 5 deletions go/vt/vtgate/engine/projection.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
}
Expand All @@ -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
}
Expand Down Expand Up @@ -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
Expand All @@ -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,
Expand Down
38 changes: 38 additions & 0 deletions go/vt/vtgate/engine/projection_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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))
})
}
}

0 comments on commit d5960cf

Please sign in to comment.