github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/ccl/importccl/read_import_mysql.go (about) 1 // Copyright 2018 The Cockroach Authors. 2 // 3 // Licensed as a CockroachDB Enterprise file under the Cockroach Community 4 // License (the "License"); you may not use this file except in compliance with 5 // the License. You may obtain a copy of the License at 6 // 7 // https://github.com/cockroachdb/cockroach/blob/master/licenses/CCL.txt 8 9 package importccl 10 11 import ( 12 "bufio" 13 "bytes" 14 "context" 15 "fmt" 16 "io" 17 "strconv" 18 "strings" 19 20 "github.com/cockroachdb/cockroach/pkg/keys" 21 "github.com/cockroachdb/cockroach/pkg/roachpb" 22 "github.com/cockroachdb/cockroach/pkg/sql" 23 "github.com/cockroachdb/cockroach/pkg/sql/execinfrapb" 24 "github.com/cockroachdb/cockroach/pkg/sql/lex" 25 "github.com/cockroachdb/cockroach/pkg/sql/parser" 26 "github.com/cockroachdb/cockroach/pkg/sql/row" 27 "github.com/cockroachdb/cockroach/pkg/sql/sem/tree" 28 "github.com/cockroachdb/cockroach/pkg/sql/sqlbase" 29 "github.com/cockroachdb/cockroach/pkg/sql/types" 30 "github.com/cockroachdb/cockroach/pkg/storage/cloud" 31 "github.com/cockroachdb/cockroach/pkg/util/ctxgroup" 32 "github.com/cockroachdb/cockroach/pkg/util/errorutil/unimplemented" 33 "github.com/cockroachdb/cockroach/pkg/util/hlc" 34 "github.com/cockroachdb/cockroach/pkg/util/log" 35 "github.com/cockroachdb/errors" 36 mysqltypes "vitess.io/vitess/go/sqltypes" 37 mysql "vitess.io/vitess/go/vt/sqlparser" 38 ) 39 40 // mysqldumpReader reads the default output of `mysqldump`, which consists of 41 // SQL statements, in MySQL-dialect, namely CREATE TABLE and INSERT statements, 42 // with some additional statements that control the loading process like LOCK or 43 // UNLOCK (which are simply ignored for the purposed of this reader). Data for 44 // tables with names that appear in the `tables` map is converted to Cockroach 45 // KVs using the mapped converter and sent to kvCh. 46 type mysqldumpReader struct { 47 evalCtx *tree.EvalContext 48 tables map[string]*row.DatumRowConverter 49 kvCh chan row.KVBatch 50 debugRow func(tree.Datums) 51 } 52 53 var _ inputConverter = &mysqldumpReader{} 54 55 func newMysqldumpReader( 56 ctx context.Context, 57 kvCh chan row.KVBatch, 58 tables map[string]*execinfrapb.ReadImportDataSpec_ImportTable, 59 evalCtx *tree.EvalContext, 60 ) (*mysqldumpReader, error) { 61 res := &mysqldumpReader{evalCtx: evalCtx, kvCh: kvCh} 62 63 converters := make(map[string]*row.DatumRowConverter, len(tables)) 64 for name, table := range tables { 65 if table.Desc == nil { 66 converters[name] = nil 67 continue 68 } 69 conv, err := row.NewDatumRowConverter(ctx, table.Desc, nil /* targetColNames */, evalCtx, kvCh) 70 if err != nil { 71 return nil, err 72 } 73 converters[name] = conv 74 } 75 res.tables = converters 76 return res, nil 77 } 78 79 func (m *mysqldumpReader) start(ctx ctxgroup.Group) { 80 } 81 82 func (m *mysqldumpReader) readFiles( 83 ctx context.Context, 84 dataFiles map[int32]string, 85 resumePos map[int32]int64, 86 format roachpb.IOFileFormat, 87 makeExternalStorage cloud.ExternalStorageFactory, 88 ) error { 89 return readInputFiles(ctx, dataFiles, resumePos, format, m.readFile, makeExternalStorage) 90 } 91 92 func (m *mysqldumpReader) readFile( 93 ctx context.Context, input *fileReader, inputIdx int32, resumePos int64, rejected chan string, 94 ) error { 95 var inserts, count int64 96 r := bufio.NewReaderSize(input, 1024*64) 97 tokens := mysql.NewTokenizer(r) 98 tokens.SkipSpecialComments = true 99 100 for _, conv := range m.tables { 101 conv.KvBatch.Source = inputIdx 102 conv.FractionFn = input.ReadFraction 103 conv.CompletedRowFn = func() int64 { 104 return count 105 } 106 } 107 108 for { 109 stmt, err := mysql.ParseNextStrictDDL(tokens) 110 if err == io.EOF { 111 break 112 } 113 if errors.Is(err, mysql.ErrEmpty) { 114 continue 115 } 116 if err != nil { 117 return errors.Wrap(err, "mysql parse error") 118 } 119 switch i := stmt.(type) { 120 case *mysql.Insert: 121 name := safeString(i.Table.Name) 122 conv, ok := m.tables[lex.NormalizeName(name)] 123 if !ok { 124 // not importing this table. 125 continue 126 } 127 if conv == nil { 128 return errors.Errorf("missing schema info for requested table %q", name) 129 } 130 inserts++ 131 rows, ok := i.Rows.(mysql.Values) 132 if !ok { 133 return errors.Errorf( 134 "insert statement %d: unexpected insert row type %T: %v", inserts, rows, i.Rows, 135 ) 136 } 137 startingCount := count 138 for _, inputRow := range rows { 139 count++ 140 141 if count <= resumePos { 142 continue 143 } 144 if expected, got := len(conv.VisibleCols), len(inputRow); expected != got { 145 return errors.Errorf("expected %d values, got %d: %v", expected, got, inputRow) 146 } 147 for i, raw := range inputRow { 148 converted, err := mysqlValueToDatum(raw, conv.VisibleColTypes[i], conv.EvalCtx) 149 if err != nil { 150 return errors.Wrapf(err, "reading row %d (%d in insert statement %d)", 151 count, count-startingCount, inserts) 152 } 153 conv.Datums[i] = converted 154 } 155 if err := conv.Row(ctx, inputIdx, count); err != nil { 156 return err 157 } 158 if m.debugRow != nil { 159 m.debugRow(conv.Datums) 160 } 161 } 162 default: 163 if log.V(3) { 164 log.Infof(ctx, "ignoring %T stmt: %v", i, i) 165 } 166 continue 167 } 168 } 169 for _, conv := range m.tables { 170 if err := conv.SendBatch(ctx); err != nil { 171 return err 172 } 173 } 174 return nil 175 } 176 177 const ( 178 zeroDate = "0000-00-00" 179 zeroYear = "0000" 180 zeroTime = "0000-00-00 00:00:00" 181 ) 182 183 // mysqlValueToDatum attempts to convert a value, as parsed from a mysqldump 184 // INSERT statement, in to a Cockroach Datum of type `desired`. The MySQL parser 185 // does not parse the values themselves to Go primitivies, rather leaving the 186 // original bytes uninterpreted in a value wrapper. The possible mysql value 187 // wrapper types are: StrVal, IntVal, FloatVal, HexNum, HexVal, ValArg, BitVal 188 // as well as NullVal. 189 func mysqlValueToDatum( 190 raw mysql.Expr, desired *types.T, evalContext *tree.EvalContext, 191 ) (tree.Datum, error) { 192 switch v := raw.(type) { 193 case mysql.BoolVal: 194 if v { 195 return tree.DBoolTrue, nil 196 } 197 return tree.DBoolFalse, nil 198 case *mysql.SQLVal: 199 switch v.Type { 200 case mysql.StrVal: 201 s := string(v.Val) 202 // https://github.com/cockroachdb/cockroach/issues/29298 203 204 if strings.HasPrefix(s, zeroYear) { 205 switch desired.Family() { 206 case types.TimestampTZFamily, types.TimestampFamily: 207 if s == zeroTime { 208 return tree.DNull, nil 209 } 210 case types.DateFamily: 211 if s == zeroDate { 212 return tree.DNull, nil 213 } 214 } 215 } 216 // This uses ParseDatumStringAsWithRawBytes instead of ParseDatumStringAs since mysql emits 217 // raw byte strings that do not use the same escaping as our ParseBytes 218 // function expects, and the difference between ParseStringAs and 219 // ParseDatumStringAs is whether or not it attempts to parse bytes. 220 return sqlbase.ParseDatumStringAsWithRawBytes(desired, s, evalContext) 221 case mysql.IntVal: 222 return sqlbase.ParseDatumStringAs(desired, string(v.Val), evalContext) 223 case mysql.FloatVal: 224 return sqlbase.ParseDatumStringAs(desired, string(v.Val), evalContext) 225 case mysql.HexVal: 226 v, err := v.HexDecode() 227 return tree.NewDBytes(tree.DBytes(v)), err 228 // ValArg appears to be for placeholders, which should not appear in dumps. 229 // TODO(dt): Do we need to handle HexNum or BitVal? 230 default: 231 return nil, fmt.Errorf("unsupported value type %c: %v", v.Type, v) 232 } 233 234 case *mysql.UnaryExpr: 235 switch v.Operator { 236 case "-": 237 parsed, err := mysqlValueToDatum(v.Expr, desired, evalContext) 238 if err != nil { 239 return nil, err 240 } 241 switch i := parsed.(type) { 242 case *tree.DInt: 243 return tree.NewDInt(-*i), nil 244 case *tree.DFloat: 245 return tree.NewDFloat(-*i), nil 246 case *tree.DDecimal: 247 dec := &i.Decimal 248 dd := &tree.DDecimal{} 249 dd.Decimal.Neg(dec) 250 return dd, nil 251 default: 252 return nil, errors.Errorf("unsupported negation of %T", i) 253 } 254 case "_binary", "_binary ": 255 // TODO(dt): do we want to use this hint to change our decoding logic? 256 return mysqlValueToDatum(v.Expr, desired, evalContext) 257 default: 258 return nil, errors.Errorf("unexpected operator: %q", v.Operator) 259 } 260 261 case *mysql.NullVal: 262 return tree.DNull, nil 263 264 default: 265 return nil, errors.Errorf("unexpected value type %T: %v", v, v) 266 } 267 } 268 269 // readMysqlCreateTable parses mysql-dialect SQL from input to extract table 270 // definitions and return them as Cockroach's TableDescriptors. If `match` is 271 // non-empty, only the table with that name is returned and an error is returned 272 // if a matching table is not found in the input. Otherwise, if match is empty, 273 // all tables encountered are returned (or an error is returned if no tables are 274 // found). Returned tables are given dummy, placeholder IDs -- it is up to the 275 // caller to allocate and assign real IDs. 276 func readMysqlCreateTable( 277 ctx context.Context, 278 input io.Reader, 279 evalCtx *tree.EvalContext, 280 p sql.PlanHookState, 281 startingID, parentID sqlbase.ID, 282 match string, 283 fks fkHandler, 284 seqVals map[sqlbase.ID]int64, 285 ) ([]*sqlbase.TableDescriptor, error) { 286 match = lex.NormalizeName(match) 287 r := bufio.NewReaderSize(input, 1024*64) 288 tokens := mysql.NewTokenizer(r) 289 tokens.SkipSpecialComments = true 290 291 var ret []*sqlbase.TableDescriptor 292 var fkDefs []delayedFK 293 var found bool 294 var names []string 295 for { 296 stmt, err := mysql.ParseNextStrictDDL(tokens) 297 if err == nil { 298 err = tokens.LastError 299 } 300 if err == io.EOF { 301 break 302 } 303 if errors.Is(err, mysql.ErrEmpty) { 304 continue 305 } 306 if err != nil { 307 return nil, errors.Wrap(err, "mysql parse error") 308 } 309 if i, ok := stmt.(*mysql.DDL); ok && i.Action == mysql.CreateStr { 310 name := safeString(i.NewName.Name) 311 if match != "" && match != name { 312 names = append(names, name) 313 continue 314 } 315 id := sqlbase.ID(int(startingID) + len(ret)) 316 tbl, moreFKs, err := mysqlTableToCockroach(ctx, evalCtx, p, parentID, id, name, i.TableSpec, fks, seqVals) 317 if err != nil { 318 return nil, err 319 } 320 fkDefs = append(fkDefs, moreFKs...) 321 ret = append(ret, tbl...) 322 if match == name { 323 found = true 324 break 325 } 326 } 327 } 328 if ret == nil { 329 return nil, errors.Errorf("no table definitions found") 330 } 331 if match != "" && !found { 332 return nil, errors.Errorf("table %q not found in file (found tables: %s)", match, strings.Join(names, ", ")) 333 } 334 if err := addDelayedFKs(ctx, fkDefs, fks.resolver, evalCtx); err != nil { 335 return nil, err 336 } 337 return ret, nil 338 } 339 340 type mysqlIdent interface{ CompliantName() string } 341 342 func safeString(in mysqlIdent) string { 343 return lex.NormalizeName(in.CompliantName()) 344 } 345 346 func safeName(in mysqlIdent) tree.Name { 347 return tree.Name(safeString(in)) 348 } 349 350 // mysqlTableToCockroach creates a Cockroach TableDescriptor from a parsed mysql 351 // CREATE TABLE statement, converting columns and indexes to their closest 352 // Cockroach counterparts. 353 func mysqlTableToCockroach( 354 ctx context.Context, 355 evalCtx *tree.EvalContext, 356 p sql.PlanHookState, 357 parentID, id sqlbase.ID, 358 name string, 359 in *mysql.TableSpec, 360 fks fkHandler, 361 seqVals map[sqlbase.ID]int64, 362 ) ([]*sqlbase.TableDescriptor, []delayedFK, error) { 363 if in == nil { 364 return nil, nil, errors.Errorf("could not read definition for table %q (possible unsupported type?)", name) 365 } 366 367 time := hlc.Timestamp{WallTime: evalCtx.GetStmtTimestamp().UnixNano()} 368 369 const seqOpt = "auto_increment=" 370 var seqName string 371 var startingValue int64 372 for _, opt := range strings.Fields(strings.ToLower(in.Options)) { 373 if strings.HasPrefix(opt, seqOpt) { 374 seqName = name + "_auto_inc" 375 i, err := strconv.Atoi(strings.TrimPrefix(opt, seqOpt)) 376 if err != nil { 377 return nil, nil, errors.Wrapf(err, "parsing AUTO_INCREMENT value") 378 } 379 startingValue = int64(i) 380 break 381 } 382 } 383 384 if seqName == "" { 385 for _, raw := range in.Columns { 386 if raw.Type.Autoincrement { 387 seqName = name + "_auto_inc" 388 break 389 } 390 } 391 } 392 393 var seqDesc *sqlbase.TableDescriptor 394 // If we have an auto-increment seq, create it and increment the id. 395 if seqName != "" { 396 priv := sqlbase.NewDefaultPrivilegeDescriptor() 397 var opts tree.SequenceOptions 398 if startingValue != 0 { 399 opts = tree.SequenceOptions{{Name: tree.SeqOptStart, IntVal: &startingValue}} 400 seqVals[id] = startingValue 401 } 402 var desc sqlbase.MutableTableDescriptor 403 var err error 404 if p != nil { 405 params := p.RunParams(ctx) 406 desc, err = sql.MakeSequenceTableDesc( 407 seqName, 408 opts, 409 parentID, 410 keys.PublicSchemaID, 411 id, 412 time, 413 priv, 414 false, /* temporary */ 415 ¶ms, 416 ) 417 } else { 418 desc, err = sql.MakeSequenceTableDesc( 419 seqName, 420 opts, 421 parentID, 422 keys.PublicSchemaID, 423 id, 424 time, 425 priv, 426 false, /* temporary */ 427 nil, /* params */ 428 ) 429 } 430 if err != nil { 431 return nil, nil, err 432 } 433 seqDesc = desc.TableDesc() 434 fks.resolver[seqName] = &desc 435 id++ 436 } 437 438 stmt := &tree.CreateTable{Table: tree.MakeUnqualifiedTableName(tree.Name(name))} 439 440 checks := make(map[string]*tree.CheckConstraintTableDef) 441 442 for _, raw := range in.Columns { 443 def, err := mysqlColToCockroach(safeString(raw.Name), raw.Type, checks) 444 if err != nil { 445 return nil, nil, err 446 } 447 if raw.Type.Autoincrement { 448 449 expr, err := parser.ParseExpr(fmt.Sprintf("nextval('%s':::STRING)", seqName)) 450 if err != nil { 451 return nil, nil, err 452 } 453 def.DefaultExpr.Expr = expr 454 } 455 stmt.Defs = append(stmt.Defs, def) 456 } 457 458 for _, raw := range in.Indexes { 459 var elems tree.IndexElemList 460 for _, col := range raw.Columns { 461 elems = append(elems, tree.IndexElem{Column: safeName(col.Column)}) 462 } 463 464 idxName := safeName(raw.Info.Name) 465 idx := tree.IndexTableDef{Name: idxName, Columns: elems} 466 if raw.Info.Primary || raw.Info.Unique { 467 stmt.Defs = append(stmt.Defs, &tree.UniqueConstraintTableDef{IndexTableDef: idx, PrimaryKey: raw.Info.Primary}) 468 } else { 469 stmt.Defs = append(stmt.Defs, &idx) 470 } 471 } 472 473 for _, c := range checks { 474 stmt.Defs = append(stmt.Defs, c) 475 } 476 477 desc, err := MakeSimpleTableDescriptor(evalCtx.Ctx(), evalCtx.Settings, stmt, parentID, id, fks, time.WallTime) 478 if err != nil { 479 return nil, nil, err 480 } 481 482 var fkDefs []delayedFK 483 for _, raw := range in.Constraints { 484 switch i := raw.Details.(type) { 485 case *mysql.ForeignKeyDefinition: 486 if !fks.allowed { 487 return nil, nil, errors.Errorf("foreign keys not supported: %s", mysql.String(raw)) 488 } 489 if fks.skip { 490 continue 491 } 492 fromCols := i.Source 493 toTable := tree.MakeTableName( 494 safeName(i.ReferencedTable.Qualifier), 495 safeName(i.ReferencedTable.Name), 496 ) 497 toCols := i.ReferencedColumns 498 d := &tree.ForeignKeyConstraintTableDef{ 499 Name: tree.Name(lex.NormalizeName(raw.Name)), 500 FromCols: toNameList(fromCols), 501 ToCols: toNameList(toCols), 502 } 503 504 if i.OnDelete != mysql.NoAction { 505 d.Actions.Delete = mysqlActionToCockroach(i.OnDelete) 506 } 507 if i.OnUpdate != mysql.NoAction { 508 d.Actions.Update = mysqlActionToCockroach(i.OnUpdate) 509 } 510 511 d.Table = toTable 512 fkDefs = append(fkDefs, delayedFK{desc, d}) 513 } 514 } 515 fks.resolver[desc.Name] = desc 516 if seqDesc != nil { 517 return []*sqlbase.TableDescriptor{seqDesc, desc.TableDesc()}, fkDefs, nil 518 } 519 return []*sqlbase.TableDescriptor{desc.TableDesc()}, fkDefs, nil 520 } 521 522 func mysqlActionToCockroach(action mysql.ReferenceAction) tree.ReferenceAction { 523 switch action { 524 case mysql.Restrict: 525 return tree.Restrict 526 case mysql.Cascade: 527 return tree.Cascade 528 case mysql.SetNull: 529 return tree.SetNull 530 case mysql.SetDefault: 531 return tree.SetDefault 532 } 533 return tree.NoAction 534 } 535 536 type delayedFK struct { 537 tbl *sqlbase.MutableTableDescriptor 538 def *tree.ForeignKeyConstraintTableDef 539 } 540 541 func addDelayedFKs( 542 ctx context.Context, defs []delayedFK, resolver fkResolver, evalCtx *tree.EvalContext, 543 ) error { 544 for _, def := range defs { 545 if err := sql.ResolveFK( 546 ctx, nil, resolver, def.tbl, def.def, map[sqlbase.ID]*sqlbase.MutableTableDescriptor{}, sql.NewTable, tree.ValidationDefault, evalCtx, 547 ); err != nil { 548 return err 549 } 550 if err := fixDescriptorFKState(def.tbl.TableDesc()); err != nil { 551 return err 552 } 553 if err := def.tbl.AllocateIDs(); err != nil { 554 return err 555 } 556 } 557 return nil 558 } 559 560 func toNameList(cols mysql.Columns) tree.NameList { 561 res := make([]tree.Name, len(cols)) 562 for i := range cols { 563 res[i] = safeName(cols[i]) 564 } 565 return res 566 } 567 568 // mysqlColToCockroach attempts to convert a parsed MySQL column definition to a 569 // Cockroach column definition, mapping the mysql type to its closest Cockroach 570 // counterpart (or returning an error if it is unable to do so). 571 // To the extent possible, parameters such as length or precision are preseved 572 // even if they have only cosmetic (i.e. when viewing schemas) effects compared 573 // to their behavior in MySQL. 574 func mysqlColToCockroach( 575 name string, col mysql.ColumnType, checks map[string]*tree.CheckConstraintTableDef, 576 ) (*tree.ColumnTableDef, error) { 577 def := &tree.ColumnTableDef{Name: tree.Name(name)} 578 579 var length, scale int 580 581 if col.Length != nil { 582 num, err := strconv.Atoi(string(col.Length.Val)) 583 if err != nil { 584 return nil, errors.Wrapf(err, "could not parse length for column %q", name) 585 } 586 length = num 587 } 588 589 if col.Scale != nil { 590 num, err := strconv.Atoi(string(col.Scale.Val)) 591 if err != nil { 592 return nil, errors.Wrapf(err, "could not parse scale for column %q", name) 593 } 594 scale = num 595 } 596 597 switch typ := col.SQLType(); typ { 598 599 case mysqltypes.Char: 600 def.Type = types.MakeChar(int32(length)) 601 case mysqltypes.VarChar: 602 def.Type = types.MakeVarChar(int32(length)) 603 case mysqltypes.Text: 604 def.Type = types.MakeString(int32(length)) 605 606 case mysqltypes.Blob: 607 def.Type = types.Bytes 608 case mysqltypes.VarBinary: 609 def.Type = types.Bytes 610 case mysqltypes.Binary: 611 def.Type = types.Bytes 612 613 case mysqltypes.Int8: 614 def.Type = types.Int2 615 case mysqltypes.Uint8: 616 def.Type = types.Int2 617 case mysqltypes.Int16: 618 def.Type = types.Int2 619 case mysqltypes.Uint16: 620 def.Type = types.Int4 621 case mysqltypes.Int24: 622 def.Type = types.Int4 623 case mysqltypes.Uint24: 624 def.Type = types.Int4 625 case mysqltypes.Int32: 626 def.Type = types.Int4 627 case mysqltypes.Uint32: 628 def.Type = types.Int 629 case mysqltypes.Int64: 630 def.Type = types.Int 631 case mysqltypes.Uint64: 632 def.Type = types.Int 633 634 case mysqltypes.Float32: 635 def.Type = types.Float4 636 case mysqltypes.Float64: 637 def.Type = types.Float 638 639 case mysqltypes.Decimal: 640 def.Type = types.MakeDecimal(int32(length), int32(scale)) 641 642 case mysqltypes.Date: 643 def.Type = types.Date 644 if col.Default != nil && bytes.Equal(col.Default.Val, []byte(zeroDate)) { 645 col.Default = nil 646 } 647 case mysqltypes.Time: 648 def.Type = types.Time 649 case mysqltypes.Timestamp: 650 def.Type = types.TimestampTZ 651 if col.Default != nil && bytes.Equal(col.Default.Val, []byte(zeroTime)) { 652 col.Default = nil 653 } 654 case mysqltypes.Datetime: 655 def.Type = types.TimestampTZ 656 if col.Default != nil && bytes.Equal(col.Default.Val, []byte(zeroTime)) { 657 col.Default = nil 658 } 659 case mysqltypes.Year: 660 def.Type = types.Int2 661 662 case mysqltypes.Enum: 663 def.Type = types.String 664 665 expr, err := parser.ParseExpr(fmt.Sprintf("%s in (%s)", name, strings.Join(col.EnumValues, ","))) 666 if err != nil { 667 return nil, err 668 } 669 checks[name] = &tree.CheckConstraintTableDef{ 670 Name: tree.Name(fmt.Sprintf("imported_from_enum_%s", name)), 671 Expr: expr, 672 } 673 674 case mysqltypes.TypeJSON: 675 def.Type = types.Jsonb 676 677 case mysqltypes.Set: 678 return nil, errors.WithHint( 679 unimplemented.NewWithIssue(32560, "cannot import SET columns at this time"), 680 "try converting the column to a 64-bit integer before import") 681 case mysqltypes.Geometry: 682 return nil, unimplemented.NewWithIssue(32559, "cannot import GEOMETRY columns at this time") 683 case mysqltypes.Bit: 684 return nil, errors.WithHint( 685 unimplemented.NewWithIssue(32561, "cannot import BIT columns at this time"), 686 "try converting the column to a 64-bit integer before import") 687 default: 688 return nil, unimplemented.Newf(fmt.Sprintf("import.mysqlcoltype.%s", typ), 689 "unsupported mysql type %q", col.Type) 690 } 691 692 if col.NotNull { 693 def.Nullable.Nullability = tree.NotNull 694 } else { 695 def.Nullable.Nullability = tree.Null 696 } 697 698 if col.Default != nil && !bytes.EqualFold(col.Default.Val, []byte("null")) { 699 exprString := string(col.Default.Val) 700 if col.Default.Type == mysql.StrVal { 701 def.DefaultExpr.Expr = tree.NewStrVal(exprString) 702 } else { 703 expr, err := parser.ParseExpr(exprString) 704 if err != nil { 705 return nil, unimplemented.Newf("import.mysql.default", "unsupported default expression %q for column %q: %v", exprString, name, err) 706 } 707 def.DefaultExpr.Expr = expr 708 } 709 } 710 return def, nil 711 }