From f733788323cc61f941d06768ed40c222cd500cf9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Toma=C5=BE=20Jerman?= Date: Tue, 20 Jun 2023 15:10:50 +0200 Subject: [PATCH] Tweak MySQL type fit check Make the check respect signed & unsigned as well as decimal and varchar sizes. --- server/store/adapters/rdbms/ddl/types.go | 18 ++ .../adapters/rdbms/drivers/mysql/dialect.go | 50 ++++-- .../rdbms/drivers/mysql/dialect_test.go | 156 ++++++++++++++++++ 3 files changed, 207 insertions(+), 17 deletions(-) create mode 100644 server/store/adapters/rdbms/ddl/types.go create mode 100644 server/store/adapters/rdbms/drivers/mysql/dialect_test.go diff --git a/server/store/adapters/rdbms/ddl/types.go b/server/store/adapters/rdbms/ddl/types.go new file mode 100644 index 000000000..9070024e0 --- /dev/null +++ b/server/store/adapters/rdbms/ddl/types.go @@ -0,0 +1,18 @@ +package ddl + +import "strings" + +func ParseColumnTypes(c *Column) (original, name string, meta []string) { + original = strings.ToLower(c.Type.Name) + + pp := strings.Split(original, "(") + name = pp[0] + if len(pp) > 1 { + meta = strings.Split(strings.TrimRight(pp[1], ")"), ",") + for i, m := range meta { + meta[i] = strings.TrimSpace(m) + } + } + + return +} diff --git a/server/store/adapters/rdbms/drivers/mysql/dialect.go b/server/store/adapters/rdbms/drivers/mysql/dialect.go index c5d3a0dc6..2bff8b81d 100644 --- a/server/store/adapters/rdbms/drivers/mysql/dialect.go +++ b/server/store/adapters/rdbms/drivers/mysql/dialect.go @@ -234,28 +234,29 @@ func (mysqlDialect) AttributeToColumn(attr *dal.Attribute) (col *ddl.Column, err } func (mysqlDialect) ColumnFits(target, assert *ddl.Column) bool { - targetType := strings.ToLower(target.Type.Name) - assertType := strings.ToLower(assert.Type.Name) - - if targetType == "bigint unsigned" { - targetType = "bigint" - } - if assertType == "bigint unsigned" { - assertType = "bigint" - } - - targetType = strings.Split(targetType, "(")[0] - assertType = strings.Split(assertType, "(")[0] + targetType, targetName, targetMeta := ddl.ParseColumnTypes(target) + assertType, assertName, assertMeta := ddl.ParseColumnTypes(assert) + // If everything matches up perfectly use that if assertType == targetType { return true } - // @todo check varchar sizes - // @todo signed & unsigned - + // See if we can guess it // [the type of the target column][what types fit the target col. type] - return map[string]map[string]bool{ + matches := map[string]map[string]bool{ + "bigint unsigned": { + "varchar": true, + "text": true, + + "decimal": true, + }, + "bigint signed": { + "varchar": true, + "text": true, + + "decimal": true, + }, "bigint": { "varchar": true, "text": true, @@ -300,7 +301,22 @@ func (mysqlDialect) ColumnFits(target, assert *ddl.Column) bool { "varchar": true, "text": true, }, - }[assertType][targetType] + } + + baseMatch := matches[assertName][targetName] + + // Special cases + switch { + case assertName == "varchar" && targetName == "varchar": + // Check varchar size + return baseMatch && assertMeta[0] <= targetMeta[0] + + case assertName == "decimal" && targetName == "decimal": + // Check decimal size and precision + return baseMatch && assertMeta[0] <= targetMeta[0] && assertMeta[1] <= targetMeta[1] + } + + return baseMatch } func (d mysqlDialect) ExprHandler(n *ql.ASTNode, args ...exp.Expression) (expr exp.Expression, err error) { diff --git a/server/store/adapters/rdbms/drivers/mysql/dialect_test.go b/server/store/adapters/rdbms/drivers/mysql/dialect_test.go new file mode 100644 index 000000000..d4e23d896 --- /dev/null +++ b/server/store/adapters/rdbms/drivers/mysql/dialect_test.go @@ -0,0 +1,156 @@ +package mysql + +import ( + "testing" + + "github.com/cortezaproject/corteza/server/store/adapters/rdbms/ddl" + "github.com/stretchr/testify/assert" +) + +func TestColumnFits(t *testing.T) { + tcc := []struct { + name string + target *ddl.Column + assert *ddl.Column + expected bool + }{ + { + name: "exact match (text)", + target: &ddl.Column{ + Type: &ddl.ColumnType{ + Name: "text", + }, + }, + assert: &ddl.Column{ + Type: &ddl.ColumnType{ + Name: "text", + }, + }, + expected: true, + }, + { + name: "fits somewhere", + target: &ddl.Column{ + Type: &ddl.ColumnType{ + Name: "text", + }, + }, + assert: &ddl.Column{ + Type: &ddl.ColumnType{ + Name: "decimal(1,2)", + }, + }, + expected: true, + }, + { + name: "doesn't fit", + target: &ddl.Column{ + Type: &ddl.ColumnType{ + Name: "decimal(1,2)", + }, + }, + assert: &ddl.Column{ + Type: &ddl.ColumnType{ + Name: "text", + }, + }, + expected: false, + }, + { + name: "decimal fits", + target: &ddl.Column{ + Type: &ddl.ColumnType{ + Name: "decimal(1,2)", + }, + }, + assert: &ddl.Column{ + Type: &ddl.ColumnType{ + Name: "decimal(1,2)", + }, + }, + expected: true, + }, + { + name: "decimal doesn't fit", + target: &ddl.Column{ + Type: &ddl.ColumnType{ + Name: "decimal(1,2)", + }, + }, + assert: &ddl.Column{ + Type: &ddl.ColumnType{ + Name: "decimal(2,3)", + }, + }, + expected: false, + }, + + { + name: "varchar fits", + target: &ddl.Column{ + Type: &ddl.ColumnType{ + Name: "varchar(42)", + }, + }, + assert: &ddl.Column{ + Type: &ddl.ColumnType{ + Name: "varchar(42)", + }, + }, + expected: true, + }, + { + name: "varchar doesn't fit", + target: &ddl.Column{ + Type: &ddl.ColumnType{ + Name: "varchar(42)", + }, + }, + assert: &ddl.Column{ + Type: &ddl.ColumnType{ + Name: "varchar(84)", + }, + }, + expected: false, + }, + + { + name: "sneaking unsigned into signed", + target: &ddl.Column{ + Type: &ddl.ColumnType{ + Name: "bigint signed", + }, + }, + assert: &ddl.Column{ + Type: &ddl.ColumnType{ + Name: "bigint unsigned", + }, + }, + expected: false, + }, + { + name: "sneaking signed into unsigned", + target: &ddl.Column{ + Type: &ddl.ColumnType{ + Name: "bigint unsigned", + }, + }, + assert: &ddl.Column{ + Type: &ddl.ColumnType{ + Name: "bigint signed", + }, + }, + expected: false, + }, + } + + d := mysqlDialect{} + + for _, c := range tcc { + t.Run(c.name, func(t *testing.T) { + out := d.ColumnFits(c.target, c.assert) + assert.Equal(t, c.expected, out) + }) + } + +}