@@ -27,6 +27,7 @@ import (
27
27
"vitess.io/vitess/go/mysql/collations/charset"
28
28
"vitess.io/vitess/go/mysql/collations/colldata"
29
29
vjson "vitess.io/vitess/go/mysql/json"
30
+ "vitess.io/vitess/go/mysql/sqlerror"
30
31
"vitess.io/vitess/go/sqltypes"
31
32
"vitess.io/vitess/go/vt/binlog/binlogplayer"
32
33
"vitess.io/vitess/go/vt/sqlparser"
@@ -257,7 +258,7 @@ func (tp *TablePlan) applyBulkInsert(sqlbuffer *bytes2.Buffer, rows []*querypb.R
257
258
if i > 0 {
258
259
sqlbuffer .WriteString (", " )
259
260
}
260
- if err := appendFromRow ( tp .BulkInsertValues , sqlbuffer , tp . Fields , row , tp . FieldsToSkip ); err != nil {
261
+ if err := tp .appendFromRow ( sqlbuffer , row ); err != nil {
261
262
return nil , err
262
263
}
263
264
}
@@ -312,6 +313,30 @@ func (tp *TablePlan) isOutsidePKRange(bindvars map[string]*querypb.BindVariable,
312
313
return false
313
314
}
314
315
316
+ // convertStringCharset does a charset conversion given raw data and an applicable conversion rule.
317
+ // In case of a conversion error, it returns an equivalent of MySQL error 1366, which is what you'd
318
+ // get in a failed `CONVERT()` function, e.g.:
319
+ //
320
+ // > create table tascii(v varchar(100) charset ascii);
321
+ // > insert into tascii values ('€');
322
+ // ERROR 1366 (HY000): Incorrect string value: '\xE2\x82\xAC' for column 'v' at row 1
323
+ func (tp * TablePlan ) convertStringCharset (raw []byte , conversion * binlogdatapb.CharsetConversion , fieldName string ) ([]byte , error ) {
324
+ fromCollation := tp .CollationEnv .DefaultCollationForCharset (conversion .FromCharset )
325
+ if fromCollation == collations .Unknown {
326
+ return nil , vterrors .Errorf (vtrpcpb .Code_INVALID_ARGUMENT , "character set %s not supported for column %s" , conversion .FromCharset , fieldName )
327
+ }
328
+ toCollation := tp .CollationEnv .DefaultCollationForCharset (conversion .ToCharset )
329
+ if toCollation == collations .Unknown {
330
+ return nil , vterrors .Errorf (vtrpcpb .Code_INVALID_ARGUMENT , "character set %s not supported for column %s" , conversion .ToCharset , fieldName )
331
+ }
332
+
333
+ out , err := charset .Convert (nil , colldata .Lookup (toCollation ).Charset (), raw , colldata .Lookup (fromCollation ).Charset ())
334
+ if err != nil {
335
+ return nil , sqlerror .NewSQLError (sqlerror .ERTruncatedWrongValueForField , sqlerror .SSUnknownSQLState , "Incorrect string value: %s" , err .Error ())
336
+ }
337
+ return out , nil
338
+ }
339
+
315
340
// bindFieldVal returns a bind variable based on given field and value.
316
341
// Most values will just bind directly. But some values may need manipulation:
317
342
// - text values with charset conversion
@@ -320,11 +345,7 @@ func (tp *TablePlan) isOutsidePKRange(bindvars map[string]*querypb.BindVariable,
320
345
func (tp * TablePlan ) bindFieldVal (field * querypb.Field , val * sqltypes.Value ) (* querypb.BindVariable , error ) {
321
346
if conversion , ok := tp .ConvertCharset [field .Name ]; ok && ! val .IsNull () {
322
347
// Non-null string value, for which we have a charset conversion instruction
323
- fromCollation := tp .CollationEnv .DefaultCollationForCharset (conversion .FromCharset )
324
- if fromCollation == collations .Unknown {
325
- return nil , vterrors .Errorf (vtrpcpb .Code_INVALID_ARGUMENT , "Character set %s not supported for column %s" , conversion .FromCharset , field .Name )
326
- }
327
- out , err := charset .Convert (nil , charset.Charset_utf8mb4 {}, val .Raw (), colldata .Lookup (fromCollation ).Charset ())
348
+ out , err := tp .convertStringCharset (val .Raw (), conversion , field .Name )
328
349
if err != nil {
329
350
return nil , err
330
351
}
@@ -590,28 +611,30 @@ func valsEqual(v1, v2 sqltypes.Value) bool {
590
611
// note: there can be more fields than bind locations since extra columns might be requested from the source if not all
591
612
// primary keys columns are present in the target table, for example. Also some values in the row may not correspond for
592
613
// values from the database on the source: sum/count for aggregation queries, for example
593
- func appendFromRow ( pq * sqlparser. ParsedQuery , buf * bytes2.Buffer , fields [] * querypb. Field , row * querypb.Row , skipFields map [ string ] bool ) error {
594
- bindLocations := pq .BindLocations ()
595
- if len (fields ) < len (bindLocations ) {
614
+ func ( tp * TablePlan ) appendFromRow ( buf * bytes2.Buffer , row * querypb.Row ) error {
615
+ bindLocations := tp . BulkInsertValues .BindLocations ()
616
+ if len (tp . Fields ) < len (bindLocations ) {
596
617
return vterrors .Errorf (vtrpcpb .Code_INTERNAL , "wrong number of fields: got %d fields for %d bind locations " ,
597
- len (fields ), len (bindLocations ))
618
+ len (tp . Fields ), len (bindLocations ))
598
619
}
599
620
600
621
type colInfo struct {
601
622
typ querypb.Type
602
623
length int64
603
624
offset int64
625
+ field * querypb.Field
604
626
}
605
627
rowInfo := make ([]* colInfo , 0 )
606
628
607
629
offset := int64 (0 )
608
- for i , field := range fields { // collect info required for fields to be bound
630
+ for i , field := range tp . Fields { // collect info required for fields to be bound
609
631
length := row .Lengths [i ]
610
- if ! skipFields [strings .ToLower (field .Name )] {
632
+ if ! tp . FieldsToSkip [strings .ToLower (field .Name )] {
611
633
rowInfo = append (rowInfo , & colInfo {
612
634
typ : field .Type ,
613
635
length : length ,
614
636
offset : offset ,
637
+ field : field ,
615
638
})
616
639
}
617
640
if length > 0 {
@@ -623,7 +646,7 @@ func appendFromRow(pq *sqlparser.ParsedQuery, buf *bytes2.Buffer, fields []*quer
623
646
var offsetQuery int
624
647
for i , loc := range bindLocations {
625
648
col := rowInfo [i ]
626
- buf .WriteString (pq .Query [offsetQuery :loc .Offset ])
649
+ buf .WriteString (tp . BulkInsertValues .Query [offsetQuery :loc .Offset ])
627
650
typ := col .typ
628
651
629
652
switch typ {
@@ -645,12 +668,25 @@ func appendFromRow(pq *sqlparser.ParsedQuery, buf *bytes2.Buffer, fields []*quer
645
668
// -1 means a null variable; serialize it directly
646
669
buf .WriteString (sqltypes .NullStr )
647
670
} else {
648
- vv := sqltypes .MakeTrusted (typ , row .Values [col .offset :col .offset + col .length ])
671
+ raw := row .Values [col .offset : col .offset + col .length ]
672
+ var vv sqltypes.Value
673
+
674
+ if conversion , ok := tp .ConvertCharset [col .field .Name ]; ok && col .length > 0 {
675
+ // Non-null string value, for which we have a charset conversion instruction
676
+ out , err := tp .convertStringCharset (raw , conversion , col .field .Name )
677
+ if err != nil {
678
+ return err
679
+ }
680
+ vv = sqltypes .MakeTrusted (typ , out )
681
+ } else {
682
+ vv = sqltypes .MakeTrusted (typ , raw )
683
+ }
684
+
649
685
vv .EncodeSQLBytes2 (buf )
650
686
}
651
687
}
652
688
offsetQuery = loc .Offset + loc .Length
653
689
}
654
- buf .WriteString (pq .Query [offsetQuery :])
690
+ buf .WriteString (tp . BulkInsertValues .Query [offsetQuery :])
655
691
return nil
656
692
}
0 commit comments