From c70031d29801426ee6f95b3cc7303d5a49fdf7f1 Mon Sep 17 00:00:00 2001 From: Shlomi Noach <2607934+shlomi-noach@users.noreply.github.com> Date: Thu, 4 Jul 2024 08:38:09 +0300 Subject: [PATCH] `schemadiff` small internal refactor: formalizing column charset/collation (#16239) Signed-off-by: Shlomi Noach <2607934+shlomi-noach@users.noreply.github.com> --- go/vt/schemadiff/column.go | 163 ++++++++++++++++--------------------- go/vt/schemadiff/table.go | 15 +++- 2 files changed, 82 insertions(+), 96 deletions(-) diff --git a/go/vt/schemadiff/column.go b/go/vt/schemadiff/column.go index da2145f3ab0..7e55192cb06 100644 --- a/go/vt/schemadiff/column.go +++ b/go/vt/schemadiff/column.go @@ -71,11 +71,66 @@ func NewModifyColumnDiffByDefinition(definition *sqlparser.ColumnDefinition) *Mo } type ColumnDefinitionEntity struct { - columnDefinition *sqlparser.ColumnDefinition + columnDefinition *sqlparser.ColumnDefinition + tableCharsetCollate *charsetCollate + Env *Environment } -func NewColumnDefinitionEntity(c *sqlparser.ColumnDefinition) *ColumnDefinitionEntity { - return &ColumnDefinitionEntity{columnDefinition: c} +func NewColumnDefinitionEntity(env *Environment, c *sqlparser.ColumnDefinition, tableCharsetCollate *charsetCollate) *ColumnDefinitionEntity { + return &ColumnDefinitionEntity{ + columnDefinition: c, + tableCharsetCollate: tableCharsetCollate, + Env: env, + } +} + +func (c *ColumnDefinitionEntity) Clone() *ColumnDefinitionEntity { + clone := &ColumnDefinitionEntity{ + columnDefinition: sqlparser.Clone(c.columnDefinition), + tableCharsetCollate: c.tableCharsetCollate, + Env: c.Env, + } + return clone +} + +// SetExplicitCharsetCollate enriches this column definition with collation and charset. Those may be +// already present, or perhaps just one of them is present (in which case we use the one to populate the other), +// or both might be missing, in which case we use the table's charset/collation. +func (c *ColumnDefinitionEntity) SetExplicitCharsetCollate() error { + if !c.IsTextual() { + return nil + } + // We will now denormalize the columns charset & collate as needed (if empty, populate from table.) + // Normalizing _this_ column definition: + if c.columnDefinition.Type.Charset.Name != "" && c.columnDefinition.Type.Options.Collate == "" { + // Charset defined without collation. Assign the default collation for that charset. + collation := c.Env.CollationEnv().DefaultCollationForCharset(c.columnDefinition.Type.Charset.Name) + if collation == collations.Unknown { + return &UnknownColumnCharsetCollationError{Column: c.columnDefinition.Name.String(), Charset: c.tableCharsetCollate.charset} + } + c.columnDefinition.Type.Options.Collate = c.Env.CollationEnv().LookupName(collation) + } + if c.columnDefinition.Type.Charset.Name == "" && c.columnDefinition.Type.Options.Collate != "" { + // Column has explicit collation but no charset. We can infer the charset from the collation. + collationID := c.Env.CollationEnv().LookupByName(c.columnDefinition.Type.Options.Collate) + charset := c.Env.CollationEnv().LookupCharsetName(collationID) + if charset == "" { + return &UnknownColumnCollationCharsetError{Column: c.columnDefinition.Name.String(), Collation: c.columnDefinition.Type.Options.Collate} + } + c.columnDefinition.Type.Charset.Name = charset + } + if c.columnDefinition.Type.Charset.Name == "" { + // Still nothing? Assign the table's charset/collation. + c.columnDefinition.Type.Charset.Name = c.tableCharsetCollate.charset + if c.columnDefinition.Type.Options.Collate = c.tableCharsetCollate.collate; c.columnDefinition.Type.Options.Collate == "" { + collation := c.Env.CollationEnv().DefaultCollationForCharset(c.tableCharsetCollate.charset) + if collation == collations.Unknown { + return &UnknownColumnCharsetCollationError{Column: c.columnDefinition.Name.String(), Charset: c.tableCharsetCollate.charset} + } + c.columnDefinition.Type.Options.Collate = c.Env.CollationEnv().LookupName(collation) + } + } + return nil } // ColumnDiff compares this table statement with another table statement, and sees what it takes to @@ -98,100 +153,22 @@ func (c *ColumnDefinitionEntity) ColumnDiff( env *Environment, tableName string, other *ColumnDefinitionEntity, - t1cc *charsetCollate, - t2cc *charsetCollate, hints *DiffHints, ) (*ModifyColumnDiff, error) { + cClone := c // not real clone yet + otherClone := other // not real clone yet if c.IsTextual() || other.IsTextual() { - // We will now denormalize the columns charset & collate as needed (if empty, populate from table.) - // Normalizing _this_ column definition: - if c.columnDefinition.Type.Charset.Name != "" && c.columnDefinition.Type.Options.Collate == "" { - // Charset defined without collation. Assign the default collation for that charset. - collation := env.CollationEnv().DefaultCollationForCharset(c.columnDefinition.Type.Charset.Name) - if collation == collations.Unknown { - return nil, &UnknownColumnCharsetCollationError{Column: c.columnDefinition.Name.String(), Charset: t1cc.charset} - } - defer func() { - c.columnDefinition.Type.Options.Collate = "" - }() - c.columnDefinition.Type.Options.Collate = env.CollationEnv().LookupName(collation) - } - if c.columnDefinition.Type.Charset.Name == "" && c.columnDefinition.Type.Options.Collate != "" { - // Column has explicit collation but no charset. We can infer the charset from the collation. - collationID := env.CollationEnv().LookupByName(c.columnDefinition.Type.Options.Collate) - charset := env.CollationEnv().LookupCharsetName(collationID) - if charset == "" { - return nil, &UnknownColumnCollationCharsetError{Column: c.columnDefinition.Name.String(), Collation: c.columnDefinition.Type.Options.Collate} - } - defer func() { - c.columnDefinition.Type.Charset.Name = "" - }() - c.columnDefinition.Type.Charset.Name = charset - } - if c.columnDefinition.Type.Charset.Name == "" { - // Still nothing? Assign the table's charset/collation. - defer func() { - c.columnDefinition.Type.Charset.Name = "" - c.columnDefinition.Type.Options.Collate = "" - }() - c.columnDefinition.Type.Charset.Name = t1cc.charset - if c.columnDefinition.Type.Options.Collate == "" { - defer func() { - c.columnDefinition.Type.Options.Collate = "" - }() - c.columnDefinition.Type.Options.Collate = t1cc.collate - } - if c.columnDefinition.Type.Options.Collate = t1cc.collate; c.columnDefinition.Type.Options.Collate == "" { - collation := env.CollationEnv().DefaultCollationForCharset(t1cc.charset) - if collation == collations.Unknown { - return nil, &UnknownColumnCharsetCollationError{Column: c.columnDefinition.Name.String(), Charset: t1cc.charset} - } - c.columnDefinition.Type.Options.Collate = env.CollationEnv().LookupName(collation) - } + cClone = c.Clone() + if err := cClone.SetExplicitCharsetCollate(); err != nil { + return nil, err } - // Normalizing _the other_ column definition: - if other.columnDefinition.Type.Charset.Name != "" && other.columnDefinition.Type.Options.Collate == "" { - // Charset defined without collation. Assign the default collation for that charset. - collation := env.CollationEnv().DefaultCollationForCharset(other.columnDefinition.Type.Charset.Name) - if collation == collations.Unknown { - return nil, &UnknownColumnCharsetCollationError{Column: other.columnDefinition.Name.String(), Charset: t2cc.charset} - } - defer func() { - other.columnDefinition.Type.Options.Collate = "" - }() - other.columnDefinition.Type.Options.Collate = env.CollationEnv().LookupName(collation) - } - if other.columnDefinition.Type.Charset.Name == "" && other.columnDefinition.Type.Options.Collate != "" { - // Column has explicit collation but no charset. We can infer the charset from the collation. - collationID := env.CollationEnv().LookupByName(other.columnDefinition.Type.Options.Collate) - charset := env.CollationEnv().LookupCharsetName(collationID) - if charset == "" { - return nil, &UnknownColumnCollationCharsetError{Column: other.columnDefinition.Name.String(), Collation: other.columnDefinition.Type.Options.Collate} - } - defer func() { - other.columnDefinition.Type.Charset.Name = "" - }() - other.columnDefinition.Type.Charset.Name = charset - } - - if other.columnDefinition.Type.Charset.Name == "" { - // Still nothing? Assign the table's charset/collation. - defer func() { - other.columnDefinition.Type.Charset.Name = "" - other.columnDefinition.Type.Options.Collate = "" - }() - other.columnDefinition.Type.Charset.Name = t2cc.charset - if other.columnDefinition.Type.Options.Collate = t2cc.collate; other.columnDefinition.Type.Options.Collate == "" { - collation := env.CollationEnv().DefaultCollationForCharset(t2cc.charset) - if collation == collations.Unknown { - return nil, &UnknownColumnCharsetCollationError{Column: other.columnDefinition.Name.String(), Charset: t2cc.charset} - } - other.columnDefinition.Type.Options.Collate = env.CollationEnv().LookupName(collation) - } + otherClone = other.Clone() + if err := otherClone.SetExplicitCharsetCollate(); err != nil { + return nil, err } } - if sqlparser.Equals.RefOfColumnDefinition(c.columnDefinition, other.columnDefinition) { + if sqlparser.Equals.RefOfColumnDefinition(cClone.columnDefinition, otherClone.columnDefinition) { return nil, nil } @@ -204,11 +181,11 @@ func (c *ColumnDefinitionEntity) ColumnDiff( } switch hints.EnumReorderStrategy { case EnumReorderStrategyReject: - otherEnumValuesMap := getEnumValuesMap(other.columnDefinition.Type.EnumValues) - for ordinal, enumValue := range c.columnDefinition.Type.EnumValues { + otherEnumValuesMap := getEnumValuesMap(otherClone.columnDefinition.Type.EnumValues) + for ordinal, enumValue := range cClone.columnDefinition.Type.EnumValues { if otherOrdinal, ok := otherEnumValuesMap[enumValue]; ok { if ordinal != otherOrdinal { - return nil, &EnumValueOrdinalChangedError{Table: tableName, Column: c.columnDefinition.Name.String(), Value: enumValue, Ordinal: ordinal, NewOrdinal: otherOrdinal} + return nil, &EnumValueOrdinalChangedError{Table: tableName, Column: cClone.columnDefinition.Name.String(), Value: enumValue, Ordinal: ordinal, NewOrdinal: otherOrdinal} } } } diff --git a/go/vt/schemadiff/table.go b/go/vt/schemadiff/table.go index 5d21d524704..5629210b6c1 100644 --- a/go/vt/schemadiff/table.go +++ b/go/vt/schemadiff/table.go @@ -454,6 +454,15 @@ func NewCreateTableEntity(env *Environment, c *sqlparser.CreateTable) (*CreateTa return entity, nil } +func (c *CreateTableEntity) ColumnDefinitionEntities() []*ColumnDefinitionEntity { + cc := getTableCharsetCollate(c.Env, &c.CreateTable.TableSpec.Options) + entities := make([]*ColumnDefinitionEntity, len(c.CreateTable.TableSpec.Columns)) + for i := range c.CreateTable.TableSpec.Columns { + entities[i] = NewColumnDefinitionEntity(c.Env, c.CreateTable.TableSpec.Columns[i], cc) + } + return entities +} + // normalize cleans up the table definition: // - setting names to all keys // - table option case (upper/lower/special) @@ -1731,11 +1740,11 @@ func (c *CreateTableEntity) diffColumns(alterTable *sqlparser.AlterTable, t2ColName := t2Col.Name.Lowered() // we know that column exists in both tables t1Col := t1ColumnsMap[t2ColName] - t1ColEntity := NewColumnDefinitionEntity(t1Col.col) - t2ColEntity := NewColumnDefinitionEntity(t2Col) + t1ColEntity := NewColumnDefinitionEntity(c.Env, t1Col.col, t1cc) + t2ColEntity := NewColumnDefinitionEntity(c.Env, t2Col, t2cc) // check diff between before/after columns: - modifyColumnDiff, err := t1ColEntity.ColumnDiff(c.Env, c.Name(), t2ColEntity, t1cc, t2cc, hints) + modifyColumnDiff, err := t1ColEntity.ColumnDiff(c.Env, c.Name(), t2ColEntity, hints) if err != nil { return err }