185 lines
6.6 KiB
Go
185 lines
6.6 KiB
Go
package mysql
|
|
|
|
import (
|
|
"fmt"
|
|
"sort"
|
|
"strings"
|
|
|
|
"git.warky.dev/wdevs/relspecgo/pkg/mariadb"
|
|
"git.warky.dev/wdevs/relspecgo/pkg/models"
|
|
)
|
|
|
|
// Diagnostics cover modeled schema semantics and features discovered by the
|
|
// MySQL reader. They do not certify arbitrary SQL expressions or catalog grants.
|
|
func conversionDiagnostics(db *models.Database) []string {
|
|
var out []string
|
|
warn := func(path, feature string) { out = append(out, path+": "+feature) }
|
|
unsupported := func(m map[string]any) {
|
|
switch values := m["mysql_unsupported"].(type) {
|
|
case []string:
|
|
for _, v := range values {
|
|
warn(v, "unsupported object")
|
|
}
|
|
case []any:
|
|
for _, v := range values {
|
|
warn(fmt.Sprint(v), "unsupported object")
|
|
}
|
|
}
|
|
}
|
|
metadata := func(path string, m map[string]any, allowed string) {
|
|
if len(models.GetDirectives(m)) > 0 {
|
|
warn(path, "dialect directives are not emitted")
|
|
}
|
|
for _, key := range keys(m) {
|
|
if strings.HasPrefix(key, "mysql_") && !strings.Contains(" "+allowed+" ", " "+key+" ") {
|
|
warn(path, "unsupported metadata "+key+": "+fmt.Sprint(m[key]))
|
|
}
|
|
}
|
|
}
|
|
metadata("database", db.Metadata, "mysql_unsupported mysql_sql_mode")
|
|
modeWarning := func(path string, m map[string]any) {
|
|
if mode := unsupportedMode(metaString(m, "mysql_sql_mode")); mode != "" {
|
|
warn(path, "unsupported source SQL mode "+mode+"; generated SQL requires standard quotes and backslash escaping")
|
|
}
|
|
}
|
|
modeWarning("database", db.Metadata)
|
|
unsupported(db.Metadata)
|
|
if len(db.Domains) > 0 {
|
|
warn("database", "domains are not emitted")
|
|
}
|
|
for _, s := range db.Schemas {
|
|
path := s.Name
|
|
metadata(path, s.Metadata, "mysql_unsupported mysql_sql_mode")
|
|
modeWarning(path, s.Metadata)
|
|
unsupported(s.Metadata)
|
|
if len(s.Views) > 0 {
|
|
warn(path, "views are not emitted")
|
|
}
|
|
if len(s.Sequences) > 0 {
|
|
warn(path, "standalone sequences are not emitted")
|
|
}
|
|
if len(s.Enums) > 0 {
|
|
warn(path, "standalone enum types are not emitted; use native column enum types")
|
|
}
|
|
if len(s.Scripts) > 0 {
|
|
warn(path, "scripts are not emitted")
|
|
}
|
|
if s.Owner != "" || len(s.Permissions) > 0 {
|
|
warn(path, "ownership and permissions are not emitted")
|
|
}
|
|
for _, t := range s.Tables {
|
|
tp := path + "." + t.Name
|
|
modeWarning(tp, t.Metadata)
|
|
if t.Tablespace != "" {
|
|
warn(tp, "tablespace is not emitted")
|
|
}
|
|
metadata(tp, t.Metadata, "mysql_engine mysql_collation mysql_create_options mysql_auto_increment mysql_sql_mode")
|
|
if options := metaString(t.Metadata, "mysql_create_options"); options != "" {
|
|
warn(tp, "table CREATE_OPTIONS are not emitted: "+options)
|
|
}
|
|
for _, name := range keys(t.Columns) {
|
|
c := t.Columns[name]
|
|
cp := tp + "." + c.Name
|
|
metadata(cp, c.Metadata, "mysql_type mysql_type_signature mysql_charset mysql_datetime_precision mysql_default_expression mysql_default_sql mysql_default_value mysql_generation_storage mysql_invisible mysql_on_update")
|
|
if _, known := mariadb.CanonicalToMariaDBTypes[strings.ToLower(c.Type)]; !known && metaString(c.Metadata, "mysql_type") == "" {
|
|
warn(cp, "unknown type "+c.Type+" maps to "+columnType(c))
|
|
}
|
|
if native := metaString(c.Metadata, "mysql_type"); native != "" && metaString(c.Metadata, "mysql_type_signature") != signature(c) {
|
|
warn(cp, "edited canonical type/dimensions override native declaration "+native)
|
|
}
|
|
if c.Type == "timestamptz" {
|
|
warn(cp, "time zone semantics are not supported")
|
|
}
|
|
if c.Type == "jsonb" {
|
|
warn(cp, "jsonb storage semantics become JSON")
|
|
}
|
|
if c.Identity {
|
|
warn(cp, "identity semantics are not emitted; use auto_increment")
|
|
}
|
|
if c.Generated && c.GenerationExpression == "" {
|
|
warn(cp, "generated column has no expression")
|
|
}
|
|
if !c.Generated && c.GenerationExpression != "" {
|
|
warn(cp, "generation expression requires generated=true")
|
|
}
|
|
if c.Generated && (c.Default != nil || c.AutoIncrement) {
|
|
warn(cp, "generated column cannot preserve a default or auto_increment")
|
|
}
|
|
if storage := metaString(c.Metadata, "mysql_generation_storage"); storage != "" && storage != "STORED" && storage != "VIRTUAL" {
|
|
warn(cp, "unsupported generation storage "+storage)
|
|
}
|
|
}
|
|
for _, name := range keys(t.Constraints) {
|
|
c := t.Constraints[name]
|
|
cp := tp + " constraint " + c.Name
|
|
metadata(cp, c.Metadata, "mysql_enforced")
|
|
if c.Deferrable != nil && *c.Deferrable || c.InitiallyDeferred != nil && *c.InitiallyDeferred {
|
|
warn(cp, "deferrable constraints are not supported")
|
|
}
|
|
switch c.Type {
|
|
case models.PrimaryKeyConstraint, models.UniqueConstraint:
|
|
case models.CheckConstraint:
|
|
if c.Expression == "" {
|
|
warn(cp, "check constraint has no expression")
|
|
}
|
|
case models.ForeignKeyConstraint:
|
|
engine := metaString(t.Metadata, "mysql_engine")
|
|
if engine != "" && !strings.EqualFold(engine, "InnoDB") && !strings.EqualFold(engine, "NDB") {
|
|
warn(cp, "foreign keys are not supported by engine "+engine)
|
|
}
|
|
for _, action := range []string{c.OnDelete, c.OnUpdate} {
|
|
switch strings.ToUpper(action) {
|
|
case "", "CASCADE", "SET NULL", "RESTRICT", "NO ACTION":
|
|
default:
|
|
warn(cp, "unsupported referential action is not emitted: "+action)
|
|
}
|
|
}
|
|
default:
|
|
warn(cp, "unsupported constraint type "+string(c.Type))
|
|
}
|
|
}
|
|
for _, name := range keys(t.Indexes) {
|
|
i := t.Indexes[name]
|
|
ip := tp + " index " + i.Name
|
|
metadata(ip, i.Metadata, "mysql_visible")
|
|
if i.Where != "" {
|
|
warn(ip, "partial predicate is not emitted")
|
|
}
|
|
if len(i.Include) > 0 {
|
|
warn(ip, "INCLUDE columns are not emitted")
|
|
}
|
|
if i.Concurrent {
|
|
warn(ip, "concurrent creation is not supported")
|
|
}
|
|
if strings.EqualFold(i.Type, "hash") && !strings.EqualFold(metaString(t.Metadata, "mysql_engine"), "MEMORY") {
|
|
warn(ip, "HASH index requires a compatible engine; InnoDB converts it to BTREE")
|
|
}
|
|
switch strings.ToUpper(i.Type) {
|
|
case "", "BTREE", "HASH", "FULLTEXT", "SPATIAL":
|
|
default:
|
|
warn(ip, "unsupported index type "+i.Type+"; index is not emitted")
|
|
}
|
|
for _, p := range i.KeyParts {
|
|
if (p.Name == "") == (p.Expression == "") {
|
|
warn(ip, "key part requires exactly one name or expression")
|
|
}
|
|
}
|
|
}
|
|
for _, name := range keys(t.Relationships) {
|
|
r := t.Relationships[name]
|
|
if t.Constraints[r.ForeignKey] == nil {
|
|
warn(tp+" relationship "+r.Name, "no backing foreign key constraint; relationship is not emitted")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
sort.Strings(out)
|
|
unique := out[:0]
|
|
for _, d := range out {
|
|
if len(unique) == 0 || unique[len(unique)-1] != d {
|
|
unique = append(unique, d)
|
|
}
|
|
}
|
|
return unique
|
|
}
|