// Copyright 2020 Dolthub, Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package sqlutil import ( "context" "fmt" "github.com/dolthub/go-mysql-server/sql" "github.com/dolthub/dolt/go/libraries/doltcore/doltdb" "github.com/dolthub/dolt/go/libraries/doltcore/schema" "github.com/dolthub/dolt/go/libraries/doltcore/schema/typeinfo" "github.com/dolthub/dolt/go/store/types" ) // TODO: Many callers only care about field names and types, not the table or db names. // // Those callers may be passing in "" for these values, or may be passing in incorrect values // that are currently unused. func FromDoltSchema(ctx context.Context, dbName, tableName string, sch schema.Schema) (sql.PrimaryKeySchema, error) { cols := make(sql.Schema, sch.GetAllCols().Size()) var i int _ = sch.GetAllCols().Iter(func(tag uint64, col schema.Column) (stop bool, err error) { sqlType := col.TypeInfo.ToSqlType() var extra string if col.AutoIncrement { extra = "auto_increment" } var deflt, generated, onUpdate *sql.ColumnDefaultValue if col.Default != "" { deflt = sql.NewUnresolvedColumnDefaultValue(col.Default) } if col.Generated != "" { generated = sql.NewUnresolvedColumnDefaultValue(col.Generated) } if col.OnUpdate != "" { onUpdate = sql.NewUnresolvedColumnDefaultValue(col.OnUpdate) } cols[i] = &sql.Column{ Name: col.Name, Type: sqlType, Default: deflt, Generated: generated, OnUpdate: onUpdate, Nullable: col.IsNullable(), DatabaseSource: dbName, Source: tableName, PrimaryKey: col.IsPartOfPK, AutoIncrement: col.AutoIncrement, Comment: col.Comment, Virtual: col.Virtual, Extra: extra, Hidden: col.Hidden, HiddenSystem: col.SystemHidden, } i++ return false, nil }) return sql.NewPrimaryKeySchema(cols, sch.GetPkOrdinals()...), nil } // ToDoltSchema returns a dolt Schema from the sql schema given, suitable for use in creating a table. func ToDoltSchema( ctx context.Context, root doltdb.RootValue, tableName doltdb.TableName, sqlSchema sql.PrimaryKeySchema, headRoot doltdb.RootValue, collation sql.CollationID, ) (schema.Schema, error) { var cols []schema.Column var err error // generate tags for all columns var names []string var kinds []types.NomsKind for _, col := range sqlSchema.Schema { names = append(names, col.Name) ti, err := typeinfo.FromSqlType(col.Type) if err != nil { return nil, err } kinds = append(kinds, ti.NomsKind()) } tags, err := doltdb.GenerateTagsForNewColumns(ctx, root, tableName, names, kinds, headRoot) if err != nil { return nil, err } if len(tags) != len(sqlSchema.Schema) { return nil, fmt.Errorf("number of tags should equal number of columns") } for i, col := range sqlSchema.Schema { convertedCol, err := ToDoltCol(tags[i], col) if err != nil { return nil, err } cols = append(cols, convertedCol) } colColl := schema.NewColCollection(cols...) err = schema.ValidateForInsert(colColl) if err != nil { return nil, err } sch, err := schema.NewSchema(colColl, sqlSchema.PkOrdinals, schema.Collation(collation), nil, nil, ) if err != nil { return nil, err } return sch, nil } // ToDoltCol returns the dolt column corresponding to the SQL column given func ToDoltCol(tag uint64, col *sql.Column) (schema.Column, error) { var constraints []schema.ColConstraint if !col.Nullable || col.PrimaryKey { constraints = append(constraints, schema.NotNullConstraint{}) } typeInfo, err := typeinfo.FromSqlType(col.Type) if err != nil { return schema.Column{}, err } var defaultVal, generatedVal, onUpdateVal string if col.Default != nil { defaultVal = col.Default.String() } else if col.Generated != nil { generatedVal = col.Generated.String() } if col.OnUpdate != nil { onUpdateVal = col.OnUpdate.String() } c := schema.Column{ Name: col.Name, Tag: tag, Kind: typeInfo.NomsKind(), IsPartOfPK: col.PrimaryKey, TypeInfo: typeInfo, Default: defaultVal, Generated: generatedVal, OnUpdate: onUpdateVal, Virtual: col.Virtual, AutoIncrement: col.AutoIncrement, Comment: col.Comment, Constraints: constraints, Hidden: col.Hidden, SystemHidden: col.HiddenSystem, } err = schema.ValidateColumn(c) if err != nil { return schema.Column{}, err } return c, nil } // ToForeignKeyConstraint converts |fk| to a sql.ForeignKeyConstraint, using the name of the database // (|dbName|), as well as the schema from the parent and child tables in the FK relationship (|childSch| // and |parentSch|). func ToForeignKeyConstraint(fk doltdb.ForeignKey, dbName string, childSch, parentSch schema.Schema) (cst sql.ForeignKeyConstraint, err error) { cst = sql.ForeignKeyConstraint{ Name: fk.Name, Database: dbName, SchemaName: fk.TableName.Schema, Table: fk.TableName.Name, Columns: make([]string, len(fk.TableColumns)), ParentDatabase: dbName, ParentSchema: fk.ReferencedTableName.Schema, ParentTable: fk.ReferencedTableName.Name, ParentColumns: make([]string, len(fk.ReferencedTableColumns)), OnUpdate: ToReferentialAction(fk.OnUpdate), OnDelete: ToReferentialAction(fk.OnDelete), IsResolved: fk.IsResolved(), IsNotValid: fk.IsNotValid, MatchType: sql.ForeignKeyMatchType(fk.MatchType), } for i, tag := range fk.TableColumns { c, ok := childSch.GetAllCols().GetByTag(tag) if !ok { return cst, fmt.Errorf("cannot find column for tag %d "+ "in table %s used in foreign key %s", tag, fk.TableName, fk.Name) } cst.Columns[i] = c.Name } for i, tag := range fk.ReferencedTableColumns { c, ok := parentSch.GetAllCols().GetByTag(tag) if !ok { return cst, fmt.Errorf("cannot find column for tag %d "+ "in table %s used in foreign key %s", tag, fk.ReferencedTableName, fk.Name) } cst.ParentColumns[i] = c.Name } return cst, nil } // ToReferentialAction converts |opt| into a sql.ForeignKeyReferentialAction. func ToReferentialAction(opt doltdb.ForeignKeyReferentialAction) sql.ForeignKeyReferentialAction { switch opt { case doltdb.ForeignKeyReferentialAction_DefaultAction: return sql.ForeignKeyReferentialAction_DefaultAction case doltdb.ForeignKeyReferentialAction_Cascade: return sql.ForeignKeyReferentialAction_Cascade case doltdb.ForeignKeyReferentialAction_NoAction: return sql.ForeignKeyReferentialAction_NoAction case doltdb.ForeignKeyReferentialAction_Restrict: return sql.ForeignKeyReferentialAction_Restrict case doltdb.ForeignKeyReferentialAction_SetNull: return sql.ForeignKeyReferentialAction_SetNull case doltdb.ForeignKeyReferentialAction_SetDefault: return sql.ForeignKeyReferentialAction_SetDefault default: panic(fmt.Sprintf("Unhandled foreign key referential action %v", opt)) } }