github.com/dolthub/dolt/go@v0.40.5-0.20240520175717-68db7794bea6/libraries/doltcore/schema/encoding/serialization.go (about)

     1  // Copyright 2022 Dolthub, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package encoding
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  
    21  	fb "github.com/dolthub/flatbuffers/v23/go"
    22  	"github.com/dolthub/go-mysql-server/sql"
    23  	"github.com/dolthub/go-mysql-server/sql/planbuilder"
    24  	sqltypes "github.com/dolthub/go-mysql-server/sql/types"
    25  
    26  	"github.com/dolthub/dolt/go/gen/fb/serial"
    27  	"github.com/dolthub/dolt/go/libraries/doltcore/schema"
    28  	"github.com/dolthub/dolt/go/libraries/doltcore/schema/typeinfo"
    29  	"github.com/dolthub/dolt/go/store/types"
    30  )
    31  
    32  const (
    33  	builderBufferSize = 1500
    34  
    35  	keylessIdCol   = "keyless_hash_id"
    36  	keylessCardCol = "keyless_cardinality"
    37  )
    38  
    39  // SerializeSchema serializes a schema.Schema as a Flatbuffer message wrapped in a serial.Message.
    40  func SerializeSchema(ctx context.Context, vrw types.ValueReadWriter, sch schema.Schema) (types.SerialMessage, error) {
    41  	buf, err := serializeSchemaAsFlatbuffer(sch)
    42  	if err != nil {
    43  		return nil, err
    44  	}
    45  
    46  	v := types.SerialMessage(buf)
    47  	if _, err = vrw.WriteValue(ctx, v); err != nil {
    48  		return nil, err
    49  	}
    50  	return v, nil
    51  }
    52  
    53  func serializeSchemaAsFlatbuffer(sch schema.Schema) ([]byte, error) {
    54  	b := fb.NewBuilder(1024)
    55  	columns := serializeSchemaColumns(b, sch)
    56  	rows := serializeClusteredIndex(b, sch)
    57  	indexes := serializeSecondaryIndexes(b, sch, sch.Indexes().AllIndexes())
    58  	checks := serializeChecks(b, sch.Checks().AllChecks())
    59  	comment := b.CreateString(sch.GetComment())
    60  
    61  	var hasFeaturesAfterTryAccessors bool
    62  	for _, col := range sch.GetAllCols().GetColumns() {
    63  		if col.OnUpdate != "" {
    64  			hasFeaturesAfterTryAccessors = true
    65  			break
    66  		}
    67  	}
    68  
    69  	serial.TableSchemaStart(b)
    70  	serial.TableSchemaAddClusteredIndex(b, rows)
    71  	serial.TableSchemaAddColumns(b, columns)
    72  	serial.TableSchemaAddSecondaryIndexes(b, indexes)
    73  	serial.TableSchemaAddChecks(b, checks)
    74  	serial.TableSchemaAddCollation(b, serial.Collation(sch.GetCollation()))
    75  	if sch.GetComment() != "" {
    76  		serial.TableSchemaAddComment(b, comment)
    77  		hasFeaturesAfterTryAccessors = true
    78  	}
    79  	if hasFeaturesAfterTryAccessors {
    80  		serial.TableSchemaAddHasFeaturesAfterTryAccessors(b, hasFeaturesAfterTryAccessors)
    81  	}
    82  	root := serial.TableSchemaEnd(b)
    83  	bs := serial.FinishMessage(b, root, []byte(serial.TableSchemaFileID))
    84  	return bs, nil
    85  }
    86  
    87  // DeserializeSchema deserializes a schema.Schema from a serial.Message.
    88  func DeserializeSchema(ctx context.Context, nbf *types.NomsBinFormat, v types.Value) (schema.Schema, error) {
    89  	assertTrue(nbf.UsesFlatbuffers(), "cannot call DeserializeSchema with non-Flatbuffers NomsBinFormat")
    90  	sm, ok := v.(types.SerialMessage)
    91  	assertTrue(ok, "must pass types.SerialMessage value to DeserializeSchema")
    92  	return deserializeSchemaFromFlatbuffer(ctx, sm)
    93  }
    94  
    95  func deserializeSchemaFromFlatbuffer(ctx context.Context, buf []byte) (schema.Schema, error) {
    96  	assertTrue(serial.GetFileID(buf) == serial.TableSchemaFileID, "serialized schema must have FileID == TableSchemaFileID")
    97  	s, err := serial.TryGetRootAsTableSchema(buf, serial.MessagePrefixSz)
    98  	if err != nil {
    99  		return nil, err
   100  	}
   101  
   102  	cols, err := deserializeColumns(ctx, s)
   103  	if err != nil {
   104  		return nil, err
   105  	}
   106  	sch, err := schema.SchemaFromCols(schema.NewColCollection(cols...))
   107  	if err != nil {
   108  		return nil, err
   109  	}
   110  
   111  	dci, err := deserializeClusteredIndex(s)
   112  	if err != nil {
   113  		return nil, err
   114  	}
   115  	err = sch.SetPkOrdinals(dci)
   116  	if err != nil {
   117  		return nil, err
   118  	}
   119  
   120  	err = deserializeSecondaryIndexes(sch, s)
   121  	if err != nil {
   122  		return nil, err
   123  	}
   124  
   125  	err = deserializeChecks(sch, s)
   126  	if err != nil {
   127  		return nil, err
   128  	}
   129  
   130  	sch.SetCollation(schema.Collation(s.Collation()))
   131  	sch.SetComment(string(s.Comment()))
   132  
   133  	return sch, nil
   134  }
   135  
   136  // clustered indexes
   137  
   138  func serializeClusteredIndex(b *fb.Builder, sch schema.Schema) fb.UOffsetT {
   139  	keyless := schema.IsKeyless(sch)
   140  
   141  	// serialize key columns
   142  	var ko fb.UOffsetT
   143  	if keyless {
   144  		// keyless id is the 2nd to last column
   145  		// in the columns vector (by convention)
   146  		// and the only field in key tuples of
   147  		// the clustered index.
   148  		idPos := sch.GetAllCols().Size()
   149  		serial.IndexStartIndexColumnsVector(b, 1)
   150  		b.PrependUint16(uint16(idPos))
   151  		ko = b.EndVector(1)
   152  	} else {
   153  		pkMap := sch.GetPkOrdinals()
   154  		serial.IndexStartIndexColumnsVector(b, len(pkMap))
   155  		for i := len(pkMap) - 1; i >= 0; i-- {
   156  			b.PrependUint16(uint16(pkMap[i]))
   157  		}
   158  		ko = b.EndVector(len(pkMap))
   159  	}
   160  
   161  	// serialize value columns
   162  	nonPk := sch.GetNonPKCols().GetColumns()
   163  	length := len(nonPk)
   164  	if keyless {
   165  		length++
   166  	}
   167  	serial.IndexStartValueColumnsVector(b, length)
   168  	for i := len(nonPk) - 1; i >= 0; i-- {
   169  		col := nonPk[i]
   170  		pos := sch.GetAllCols().TagToIdx[col.Tag]
   171  		b.PrependUint16(uint16(pos))
   172  	}
   173  	if keyless {
   174  		// keyless cardinality is the last column
   175  		// in the columns vector (by convention)
   176  		// and the first field in value tuples of
   177  		// the clustered index.
   178  		cardPos := sch.GetAllCols().Size() + 1
   179  		b.PrependUint16(uint16(cardPos))
   180  	}
   181  	vo := b.EndVector(length)
   182  
   183  	serial.IndexStart(b)
   184  	// key_columns == index_columns for clustered index
   185  	serial.IndexAddIndexColumns(b, ko)
   186  	serial.IndexAddKeyColumns(b, ko)
   187  	serial.IndexAddValueColumns(b, vo)
   188  	serial.IndexAddPrimaryKey(b, true)
   189  	serial.IndexAddUniqueKey(b, true)
   190  	serial.IndexAddSpatialKey(b, false)
   191  	serial.IndexAddSystemDefined(b, false)
   192  	return serial.IndexEnd(b)
   193  }
   194  
   195  func deserializeClusteredIndex(s *serial.TableSchema) ([]int, error) {
   196  	// check for keyless schema
   197  	kss, err := keylessSerialSchema(s)
   198  	if err != nil {
   199  		return nil, err
   200  	}
   201  	if kss {
   202  		return nil, nil
   203  	}
   204  
   205  	ci, err := s.TryClusteredIndex(nil)
   206  	if err != nil {
   207  		return nil, err
   208  	}
   209  	pkOrdinals := make([]int, ci.KeyColumnsLength())
   210  	for i := range pkOrdinals {
   211  		pkOrdinals[i] = int(ci.KeyColumns(i))
   212  	}
   213  	return pkOrdinals, nil
   214  }
   215  
   216  func serializeSchemaColumns(b *fb.Builder, sch schema.Schema) fb.UOffsetT {
   217  	cols := sch.GetAllCols().GetColumns()
   218  	offs := make([]fb.UOffsetT, len(cols))
   219  
   220  	if schema.IsKeyless(sch) {
   221  		// (6/15/22)
   222  		// currently, keyless id and cardinality columns
   223  		// do not exist in schema.Schema
   224  		// we do serialize them in the flatbuffer
   225  		// message, in order to describe index storage.
   226  		// by convention, they are stored as the last
   227  		// two columns in the columns vector.
   228  		id, card := serializeHiddenKeylessColumns(b)
   229  		offs = append(offs, id, card)
   230  	}
   231  
   232  	// serialize columns in |cols|
   233  	for i := len(cols) - 1; i >= 0; i-- {
   234  		col := cols[i]
   235  		var defVal, onUpdateVal string
   236  		if col.Default != "" {
   237  			defVal = col.Default
   238  		} else {
   239  			defVal = col.Generated
   240  		}
   241  
   242  		if col.OnUpdate != "" {
   243  			onUpdateVal = col.OnUpdate
   244  		}
   245  
   246  		co := b.CreateString(col.Comment)
   247  		do := b.CreateString(defVal)
   248  		ou := b.CreateString(onUpdateVal)
   249  
   250  		typeString := sqlTypeString(col.TypeInfo)
   251  		to := b.CreateString(typeString)
   252  		no := b.CreateString(col.Name)
   253  
   254  		serial.ColumnStart(b)
   255  		serial.ColumnAddName(b, no)
   256  		serial.ColumnAddSqlType(b, to)
   257  		serial.ColumnAddDefaultValue(b, do)
   258  		serial.ColumnAddComment(b, co)
   259  		// schema.Schema determines display order
   260  		serial.ColumnAddDisplayOrder(b, int16(i))
   261  		serial.ColumnAddTag(b, col.Tag)
   262  		serial.ColumnAddEncoding(b, encodingFromTypeinfo(col.TypeInfo))
   263  		serial.ColumnAddPrimaryKey(b, col.IsPartOfPK)
   264  		serial.ColumnAddAutoIncrement(b, col.AutoIncrement)
   265  		serial.ColumnAddNullable(b, col.IsNullable())
   266  		serial.ColumnAddGenerated(b, col.Generated != "")
   267  		serial.ColumnAddVirtual(b, col.Virtual)
   268  		if onUpdateVal != "" {
   269  			serial.ColumnAddOnUpdateValue(b, ou)
   270  		}
   271  		serial.ColumnAddHidden(b, false)
   272  		offs[i] = serial.ColumnEnd(b)
   273  	}
   274  
   275  	// create the columns array with all columns
   276  	serial.TableSchemaStartColumnsVector(b, len(offs))
   277  	for i := len(offs) - 1; i >= 0; i-- {
   278  		b.PrependUOffsetT(offs[i])
   279  	}
   280  	return b.EndVector(len(offs))
   281  }
   282  
   283  func serializeHiddenKeylessColumns(b *fb.Builder) (id, card fb.UOffsetT) {
   284  	// cardinality column
   285  	no := b.CreateString(keylessCardCol)
   286  	serial.ColumnStart(b)
   287  	serial.ColumnAddName(b, no)
   288  	serial.ColumnAddDisplayOrder(b, int16(-1))
   289  	serial.ColumnAddTag(b, schema.KeylessRowCardinalityTag)
   290  	serial.ColumnAddEncoding(b, serial.EncodingUint64)
   291  	// set hidden and generated to true
   292  	serial.ColumnAddGenerated(b, true)
   293  	serial.ColumnAddHidden(b, true)
   294  	serial.ColumnAddPrimaryKey(b, false)
   295  	serial.ColumnAddAutoIncrement(b, false)
   296  	serial.ColumnAddNullable(b, false)
   297  	serial.ColumnAddVirtual(b, false)
   298  	card = serial.ColumnEnd(b)
   299  
   300  	// hash id column
   301  	no = b.CreateString(keylessIdCol)
   302  	serial.ColumnStart(b)
   303  	serial.ColumnAddName(b, no)
   304  	serial.ColumnAddDisplayOrder(b, int16(-1))
   305  	serial.ColumnAddTag(b, schema.KeylessRowIdTag)
   306  	serial.ColumnAddEncoding(b, serial.EncodingHash128)
   307  	// set hidden and generated to true
   308  	serial.ColumnAddGenerated(b, true)
   309  	serial.ColumnAddHidden(b, true)
   310  	serial.ColumnAddPrimaryKey(b, false)
   311  	serial.ColumnAddAutoIncrement(b, false)
   312  	serial.ColumnAddNullable(b, false)
   313  	serial.ColumnAddVirtual(b, false)
   314  	id = serial.ColumnEnd(b)
   315  
   316  	return
   317  }
   318  
   319  func deserializeColumns(ctx context.Context, s *serial.TableSchema) ([]schema.Column, error) {
   320  	length := s.ColumnsLength()
   321  	isKeyless, err := keylessSerialSchema(s)
   322  	if err != nil {
   323  		return nil, err
   324  	}
   325  	if isKeyless {
   326  		// (6/15/22)
   327  		// currently, keyless id and cardinality columns
   328  		// do not exist in schema.Schema
   329  		// we do serialize them in the flatbuffer
   330  		// message, in order to describe index storage.
   331  		// by convention, they are stored as the last
   332  		// two columns in the columns vector.
   333  		length -= 2
   334  	}
   335  
   336  	cols := make([]schema.Column, length)
   337  	c := serial.Column{}
   338  	for i := range cols {
   339  		_, err := s.TryColumns(&c, i)
   340  		if err != nil {
   341  			return nil, err
   342  		}
   343  		sqlType, err := typeinfoFromSqlType(string(c.SqlType()))
   344  		if err != nil {
   345  			return nil, err
   346  		}
   347  
   348  		var defVal, generatedVal, onUpdateVal string
   349  		if c.DefaultValue() != nil {
   350  			if c.Generated() {
   351  				generatedVal = string(c.DefaultValue())
   352  			} else {
   353  				defVal = string(c.DefaultValue())
   354  			}
   355  		}
   356  
   357  		if c.OnUpdateValue() != nil {
   358  			onUpdateVal = string(c.OnUpdateValue())
   359  		}
   360  
   361  		cols[i] = schema.Column{
   362  			Name:          string(c.Name()),
   363  			Tag:           c.Tag(),
   364  			Kind:          sqlType.NomsKind(),
   365  			IsPartOfPK:    c.PrimaryKey(),
   366  			TypeInfo:      sqlType,
   367  			Default:       defVal,
   368  			Generated:     generatedVal,
   369  			OnUpdate:      onUpdateVal,
   370  			Virtual:       c.Virtual(),
   371  			AutoIncrement: c.AutoIncrement(),
   372  			Comment:       string(c.Comment()),
   373  			Constraints:   constraintsFromSerialColumn(&c),
   374  		}
   375  	}
   376  	return cols, nil
   377  }
   378  
   379  func serializeSecondaryIndexes(b *fb.Builder, sch schema.Schema, indexes []schema.Index) fb.UOffsetT {
   380  	ordinalMap := sch.GetAllCols().TagToIdx
   381  	offs := make([]fb.UOffsetT, len(indexes))
   382  	for i := len(offs) - 1; i >= 0; i-- {
   383  		idx := indexes[i]
   384  		no := b.CreateString(idx.Name())
   385  		co := b.CreateString(idx.Comment())
   386  
   387  		// serialize indexed columns
   388  		tags := idx.IndexedColumnTags()
   389  		serial.IndexStartIndexColumnsVector(b, len(tags))
   390  		for j := len(tags) - 1; j >= 0; j-- {
   391  			pos := ordinalMap[tags[j]]
   392  			b.PrependUint16(uint16(pos))
   393  		}
   394  		ico := b.EndVector(len(tags))
   395  
   396  		// serialize key columns
   397  		tags = idx.AllTags()
   398  		serial.IndexStartKeyColumnsVector(b, len(tags))
   399  		for j := len(tags) - 1; j >= 0; j-- {
   400  			pos := ordinalMap[tags[j]]
   401  			b.PrependUint16(uint16(pos))
   402  		}
   403  		ko := b.EndVector(len(tags))
   404  
   405  		// serialize prefix lengths
   406  		prefixLengths := idx.PrefixLengths()
   407  		serial.IndexStartPrefixLengthsVector(b, len(prefixLengths))
   408  		for j := len(prefixLengths) - 1; j >= 0; j-- {
   409  			b.PrependUint16(prefixLengths[j])
   410  		}
   411  		po := b.EndVector(len(prefixLengths))
   412  
   413  		var ftInfo fb.UOffsetT
   414  		if idx.IsFullText() {
   415  			ftInfo = serializeFullTextInfo(b, idx)
   416  		}
   417  
   418  		serial.IndexStart(b)
   419  		serial.IndexAddName(b, no)
   420  		serial.IndexAddComment(b, co)
   421  		serial.IndexAddIndexColumns(b, ico)
   422  		serial.IndexAddKeyColumns(b, ko)
   423  		serial.IndexAddPrimaryKey(b, false)
   424  		serial.IndexAddUniqueKey(b, idx.IsUnique())
   425  		serial.IndexAddSystemDefined(b, !idx.IsUserDefined())
   426  		serial.IndexAddPrefixLengths(b, po)
   427  		serial.IndexAddSpatialKey(b, idx.IsSpatial())
   428  		serial.IndexAddFulltextKey(b, idx.IsFullText())
   429  		if idx.IsFullText() {
   430  			serial.IndexAddFulltextInfo(b, ftInfo)
   431  		}
   432  		offs[i] = serial.IndexEnd(b)
   433  	}
   434  
   435  	serial.TableSchemaStartSecondaryIndexesVector(b, len(indexes))
   436  	for i := len(offs) - 1; i >= 0; i-- {
   437  		b.PrependUOffsetT(offs[i])
   438  	}
   439  	return b.EndVector(len(indexes))
   440  }
   441  
   442  func deserializeSecondaryIndexes(sch schema.Schema, s *serial.TableSchema) error {
   443  	idx := serial.Index{}
   444  	col := serial.Column{}
   445  	for i := 0; i < s.SecondaryIndexesLength(); i++ {
   446  		_, err := s.TrySecondaryIndexes(&idx, i)
   447  		if err != nil {
   448  			return err
   449  		}
   450  		assertTrue(!idx.PrimaryKey(), "cannot deserialize secondary index with PrimaryKey() == true")
   451  
   452  		fti, err := deserializeFullTextInfo(&idx)
   453  		if err != nil {
   454  			return err
   455  		}
   456  
   457  		name := string(idx.Name())
   458  		props := schema.IndexProperties{
   459  			IsUnique:           idx.UniqueKey(),
   460  			IsSpatial:          idx.SpatialKey(),
   461  			IsFullText:         idx.FulltextKey(),
   462  			IsUserDefined:      !idx.SystemDefined(),
   463  			Comment:            string(idx.Comment()),
   464  			FullTextProperties: fti,
   465  		}
   466  
   467  		tags := make([]uint64, idx.IndexColumnsLength())
   468  		for j := range tags {
   469  			pos := idx.IndexColumns(j)
   470  			_, err := s.TryColumns(&col, int(pos))
   471  			if err != nil {
   472  				return err
   473  			}
   474  			tags[j] = col.Tag()
   475  		}
   476  
   477  		var prefixLengths []uint16
   478  		prefixLengthsLength := idx.PrefixLengthsLength()
   479  		if prefixLengthsLength > 0 {
   480  			prefixLengths = make([]uint16, prefixLengthsLength)
   481  			for j := range prefixLengths {
   482  				prefixLengths[j] = idx.PrefixLengths(j)
   483  			}
   484  		}
   485  
   486  		_, err = sch.Indexes().AddIndexByColTags(name, tags, prefixLengths, props)
   487  		if err != nil {
   488  			return err
   489  		}
   490  	}
   491  	return nil
   492  }
   493  
   494  func serializeChecks(b *fb.Builder, checks []schema.Check) fb.UOffsetT {
   495  	offs := make([]fb.UOffsetT, len(checks))
   496  	for i := len(offs) - 1; i >= 0; i-- {
   497  		eo := b.CreateString(checks[i].Expression())
   498  		no := b.CreateString(checks[i].Name())
   499  		serial.CheckConstraintStart(b)
   500  		serial.CheckConstraintAddEnforced(b, checks[i].Enforced())
   501  		serial.CheckConstraintAddExpression(b, eo)
   502  		serial.CheckConstraintAddName(b, no)
   503  		offs[i] = serial.CheckConstraintEnd(b)
   504  	}
   505  
   506  	serial.TableSchemaStartChecksVector(b, len(checks))
   507  	for i := len(offs) - 1; i >= 0; i-- {
   508  		b.PrependUOffsetT(offs[i])
   509  	}
   510  	return b.EndVector(len(checks))
   511  }
   512  
   513  func deserializeChecks(sch schema.Schema, s *serial.TableSchema) error {
   514  	coll := sch.Checks()
   515  	c := serial.CheckConstraint{}
   516  	for i := 0; i < s.ChecksLength(); i++ {
   517  		_, err := s.TryChecks(&c, i)
   518  		if err != nil {
   519  			return err
   520  		}
   521  		n, e := string(c.Name()), string(c.Expression())
   522  		if _, err := coll.AddCheck(n, e, c.Enforced()); err != nil {
   523  			return err
   524  		}
   525  	}
   526  	return nil
   527  }
   528  
   529  func serializeFullTextInfo(b *fb.Builder, idx schema.Index) fb.UOffsetT {
   530  	props := idx.FullTextProperties()
   531  
   532  	configTable := b.CreateString(props.ConfigTable)
   533  	posTable := b.CreateString(props.PositionTable)
   534  	docCountTable := b.CreateString(props.DocCountTable)
   535  	globalCountTable := b.CreateString(props.GlobalCountTable)
   536  	rowCountTable := b.CreateString(props.RowCountTable)
   537  	keyName := b.CreateString(props.KeyName)
   538  
   539  	keyPositions := idx.FullTextProperties().KeyPositions
   540  	serial.FulltextInfoStartKeyPositionsVector(b, len(keyPositions))
   541  	for j := len(keyPositions) - 1; j >= 0; j-- {
   542  		b.PrependUint16(keyPositions[j])
   543  	}
   544  	keyPos := b.EndVector(len(keyPositions))
   545  
   546  	serial.FulltextInfoStart(b)
   547  	serial.FulltextInfoAddConfigTable(b, configTable)
   548  	serial.FulltextInfoAddPositionTable(b, posTable)
   549  	serial.FulltextInfoAddDocCountTable(b, docCountTable)
   550  	serial.FulltextInfoAddGlobalCountTable(b, globalCountTable)
   551  	serial.FulltextInfoAddRowCountTable(b, rowCountTable)
   552  	serial.FulltextInfoAddKeyType(b, props.KeyType)
   553  	serial.FulltextInfoAddKeyName(b, keyName)
   554  	serial.FulltextInfoAddKeyPositions(b, keyPos)
   555  	return serial.FulltextInfoEnd(b)
   556  }
   557  
   558  func deserializeFullTextInfo(idx *serial.Index) (schema.FullTextProperties, error) {
   559  	fulltext := serial.FulltextInfo{}
   560  	has, err := idx.TryFulltextInfo(&fulltext)
   561  	if err != nil {
   562  		return schema.FullTextProperties{}, err
   563  	}
   564  	if has == nil {
   565  		return schema.FullTextProperties{}, nil
   566  	}
   567  
   568  	var keyPositions []uint16
   569  	keyPositionsLength := fulltext.KeyPositionsLength()
   570  	if keyPositionsLength > 0 {
   571  		keyPositions = make([]uint16, keyPositionsLength)
   572  		for j := range keyPositions {
   573  			keyPositions[j] = fulltext.KeyPositions(j)
   574  		}
   575  	}
   576  
   577  	return schema.FullTextProperties{
   578  		ConfigTable:      string(fulltext.ConfigTable()),
   579  		PositionTable:    string(fulltext.PositionTable()),
   580  		DocCountTable:    string(fulltext.DocCountTable()),
   581  		GlobalCountTable: string(fulltext.GlobalCountTable()),
   582  		RowCountTable:    string(fulltext.RowCountTable()),
   583  		KeyType:          fulltext.KeyType(),
   584  		KeyName:          string(fulltext.KeyName()),
   585  		KeyPositions:     keyPositions,
   586  	}, nil
   587  }
   588  
   589  func keylessSerialSchema(s *serial.TableSchema) (bool, error) {
   590  	n := s.ColumnsLength()
   591  	if n < 2 {
   592  		return false, nil
   593  	}
   594  	// keyless id is the 2nd to last column
   595  	// in the columns vector (by convention)
   596  	// and the only field in key tuples of
   597  	// the clustered index.
   598  	id := serial.Column{}
   599  	_, err := s.TryColumns(&id, n-2)
   600  	if err != nil {
   601  		return false, err
   602  	}
   603  	ok := id.Generated() && id.Hidden() &&
   604  		string(id.Name()) == keylessIdCol
   605  	if !ok {
   606  		return false, nil
   607  	}
   608  
   609  	// keyless cardinality is the last column
   610  	// in the columns vector (by convention)
   611  	// and the first field in value tuples of
   612  	// the clustered index.
   613  	card := serial.Column{}
   614  	_, err = s.TryColumns(&card, n-1)
   615  	if err != nil {
   616  		return false, err
   617  	}
   618  	return card.Generated() && card.Hidden() &&
   619  		string(card.Name()) == keylessCardCol, nil
   620  }
   621  
   622  func sqlTypeString(t typeinfo.TypeInfo) string {
   623  	typ := t.ToSqlType()
   624  	if st, ok := typ.(sql.SpatialColumnType); ok {
   625  		// for spatial types, we must append the SRID
   626  		if srid, ok := st.GetSpatialTypeSRID(); ok {
   627  			return fmt.Sprintf("%s SRID %d", typ.String(), srid)
   628  		}
   629  	}
   630  
   631  	// For datetime types, always store the precision explicitly so that it can be read back precisely, although MySQL
   632  	// omits the precision when it's 0 (the default).
   633  	if sqltypes.IsDatetimeType(typ) || sqltypes.IsTimestampType(typ) {
   634  		dt := typ.(sql.DatetimeType)
   635  		if dt.Precision() == 0 {
   636  			return fmt.Sprintf("%s(0)", typ.String())
   637  		}
   638  		return typ.String()
   639  	}
   640  
   641  	// Extended types are string serializable, so we'll just prepend a tag
   642  	if extendedType, ok := typ.(sqltypes.ExtendedType); ok {
   643  		serializedType, err := sqltypes.SerializeTypeToString(extendedType)
   644  		if err != nil {
   645  			panic(err)
   646  		}
   647  		return planbuilder.ExtendedTypeTag + serializedType
   648  	}
   649  
   650  	return typ.String()
   651  }
   652  
   653  func typeinfoFromSqlType(s string) (typeinfo.TypeInfo, error) {
   654  	sqlType, err := planbuilder.ParseColumnTypeString(s)
   655  	if err != nil {
   656  		return nil, err
   657  	}
   658  	return typeinfo.FromSqlType(sqlType)
   659  }
   660  
   661  func encodingFromTypeinfo(t typeinfo.TypeInfo) serial.Encoding {
   662  	return schema.EncodingFromSqlType(t.ToSqlType())
   663  }
   664  
   665  func constraintsFromSerialColumn(col *serial.Column) (cc []schema.ColConstraint) {
   666  	if !col.Nullable() || col.PrimaryKey() {
   667  		cc = append(cc, schema.NotNullConstraint{})
   668  	}
   669  	return
   670  }
   671  
   672  func assertTrue(b bool, msg string) {
   673  	if !b {
   674  		panic("assertion failed: " + msg)
   675  	}
   676  }