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  				&params,
   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  }