fix(embed): update embedDirectives to return result directly fix(executor): clean up import order and formatting fix(syntax_test): change file permission syntax to octal fix(mysql): update file permission syntax to octal fix(pgsql): update file permission syntax to octal fix(sqlex): simplify Walk function parameter handling fix(transform): adjust identifier comparison logic fix(template): clean up function formatting and error handling
158 lines
5.0 KiB
Go
158 lines
5.0 KiB
Go
package transform
|
|
|
|
import (
|
|
"fmt"
|
|
"reflect"
|
|
"strings"
|
|
|
|
"git.warky.dev/wdevs/relspecgo/pkg/models"
|
|
)
|
|
|
|
// LowercaseIdentifiers copies the model and lowercases names and structured
|
|
// references. SQL expressions, defaults, enum values, comments and metadata are
|
|
// preserved. Callers must adjust identifiers inside their own SQL expressions.
|
|
// Collisions are rejected before any output is written.
|
|
func LowercaseIdentifiers(db *models.Database) (*models.Database, error) {
|
|
if db == nil {
|
|
return nil, fmt.Errorf("database is required")
|
|
}
|
|
seen := map[any]reflect.Value{}
|
|
nameFields := map[string]bool{}
|
|
for _, name := range strings.Fields("Name Schema Table Tablespace Collation TableName SchemaName FromTable FromSchema ToTable ToSchema ThroughTable ThroughSchema ForeignKey ReferencedTable ReferencedSchema OwnedByTable OwnedByColumn") {
|
|
nameFields[name] = true
|
|
}
|
|
listFields := map[string]bool{"Columns": true, "Include": true, "ReferencedColumns": true, "FromColumns": true, "ToColumns": true, "RunAfter": true}
|
|
var copyValue func(reflect.Value) (reflect.Value, error)
|
|
copyValue = func(v reflect.Value) (reflect.Value, error) {
|
|
if !v.IsValid() {
|
|
return v, nil
|
|
}
|
|
switch v.Kind() {
|
|
case reflect.Pointer:
|
|
if v.IsNil() || v.Type().Elem().PkgPath() != reflect.TypeOf(models.Database{}).PkgPath() {
|
|
return v, nil
|
|
}
|
|
if prior, ok := seen[v.Interface()]; ok {
|
|
return prior, nil
|
|
}
|
|
out := reflect.New(v.Type().Elem())
|
|
seen[v.Interface()] = out
|
|
copied, err := copyValue(v.Elem())
|
|
if err != nil {
|
|
return reflect.Value{}, err
|
|
}
|
|
out.Elem().Set(copied)
|
|
return out, nil
|
|
case reflect.Struct:
|
|
out := reflect.New(v.Type()).Elem()
|
|
out.Set(v)
|
|
for i := 0; i < v.NumField(); i++ {
|
|
field := v.Type().Field(i).Name
|
|
value := v.Field(i)
|
|
if nameFields[field] && value.Kind() == reflect.String {
|
|
out.Field(i).SetString(strings.ToLower(value.String()))
|
|
continue
|
|
}
|
|
if (v.Type() != reflect.TypeOf(models.Index{}) || field != "Columns") && listFields[field] && value.Kind() == reflect.Slice && value.Type().Elem().Kind() == reflect.String {
|
|
list := reflect.MakeSlice(value.Type(), value.Len(), value.Len())
|
|
for j := 0; j < value.Len(); j++ {
|
|
list.Index(j).SetString(strings.ToLower(value.Index(j).String()))
|
|
}
|
|
out.Field(i).Set(list)
|
|
continue
|
|
}
|
|
if field == "Metadata" || field == "Default" || field == "Properties" || field == "Permissions" {
|
|
continue
|
|
}
|
|
copied, err := copyValue(value)
|
|
if err != nil {
|
|
return reflect.Value{}, err
|
|
}
|
|
out.Field(i).Set(copied)
|
|
}
|
|
if v.Type() == reflect.TypeOf(models.Table{}) {
|
|
original := v.Interface().(models.Table)
|
|
copied := out.Addr().Interface().(*models.Table)
|
|
for originalKey, index := range original.Indexes {
|
|
if index == nil {
|
|
continue
|
|
}
|
|
target := copied.Indexes[strings.ToLower(originalKey)]
|
|
target.Columns = append([]string(nil), index.Columns...)
|
|
for i, reference := range index.Columns {
|
|
for _, column := range original.Columns {
|
|
if column != nil && (reference == column.Name || reference == `"`+strings.ReplaceAll(column.Name, `"`, `""`)+`"`) {
|
|
target.Columns[i] = strings.ToLower(column.Name)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return out, nil
|
|
case reflect.Slice:
|
|
if v.IsNil() {
|
|
return v, nil
|
|
}
|
|
out := reflect.MakeSlice(v.Type(), v.Len(), v.Len())
|
|
names := map[string]bool{}
|
|
for i := 0; i < v.Len(); i++ {
|
|
copied, err := copyValue(v.Index(i))
|
|
if err != nil {
|
|
return reflect.Value{}, err
|
|
}
|
|
out.Index(i).Set(copied)
|
|
if copied.Kind() == reflect.Pointer && !copied.IsNil() && copied.Elem().Kind() == reflect.Struct {
|
|
name := copied.Elem().FieldByName("Name")
|
|
if name.IsValid() {
|
|
n := name.String()
|
|
if names[n] {
|
|
return reflect.Value{}, fmt.Errorf("lowercase identifier collision: %q", n)
|
|
}
|
|
names[n] = true
|
|
}
|
|
}
|
|
}
|
|
return out, nil
|
|
case reflect.Map:
|
|
if v.IsNil() || v.Type().Elem().Kind() != reflect.Pointer {
|
|
return v, nil
|
|
}
|
|
out := reflect.MakeMap(v.Type())
|
|
names := map[string]bool{}
|
|
iter := v.MapRange()
|
|
for iter.Next() {
|
|
key := iter.Key()
|
|
if key.Kind() == reflect.String {
|
|
key = reflect.ValueOf(strings.ToLower(key.String())).Convert(key.Type())
|
|
}
|
|
if out.MapIndex(key).IsValid() {
|
|
return reflect.Value{}, fmt.Errorf("lowercase identifier collision: %v", key.Interface())
|
|
}
|
|
copied, err := copyValue(iter.Value())
|
|
if err != nil {
|
|
return reflect.Value{}, err
|
|
}
|
|
if copied.Kind() == reflect.Pointer && !copied.IsNil() && copied.Elem().Kind() == reflect.Struct {
|
|
name := copied.Elem().FieldByName("Name")
|
|
if name.IsValid() {
|
|
n := name.String()
|
|
if names[n] {
|
|
return reflect.Value{}, fmt.Errorf("lowercase identifier collision: %q", n)
|
|
}
|
|
names[n] = true
|
|
}
|
|
}
|
|
out.SetMapIndex(key, copied)
|
|
}
|
|
return out, nil
|
|
}
|
|
return v, nil
|
|
}
|
|
value, err := copyValue(reflect.ValueOf(db))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return value.Interface().(*models.Database), nil
|
|
}
|