package orm import ( "net" "reflect" "time" ) // Field - represents a field with a valid SQL type in a Model type Field struct { Name string ColumnName string ColumnType string Type reflect.Type Original reflect.StructField Model *Model Index int AutoIncrement bool PrimaryKey bool Nullable bool embeddedFields map[string]*Field } func (f *Field) isAnonymous() bool { return f.Original.Anonymous } func (f *Field) anonymousColumnNames() []string { cols := make([]string, 0) if !f.isAnonymous() { return cols } for _, ef := range f.embeddedFields { cols = append(cols, ef.ColumnName) } return cols } func defaultColumnValue(ty reflect.Type) any { switch ty.Kind() { case reflect.Int32, reflect.Uint32, reflect.Int, reflect.Uint, reflect.Int64, reflect.Uint64: return 0 case reflect.Bool: return false case reflect.String: return "''" case reflect.Float32, reflect.Float64: return 0.0 case reflect.Struct: if canConvertTo[time.Time](ty) { return "now()" } if canConvertTo[net.IP](ty) { return "'0.0.0.0'::INET" } if canConvertTo[net.IPNet](ty) { return "'0.0.0.0/0'::CIDR" } case reflect.Slice: return "'{}'" } return "NULL" } func columnType(ty reflect.Type, isPk, isAutoInc bool) string { it := ty switch it.Kind() { case reflect.Ptr: for it.Kind() == reflect.Ptr { it = it.Elem() } case reflect.Int32, reflect.Uint32, reflect.Int, reflect.Uint: if isPk || isAutoInc { return "serial" } else { return "int" } case reflect.Int64, reflect.Uint64: if isPk || isAutoInc { return "bigserial" } else { return "bigint" } case reflect.String: return "text" case reflect.Float32: return "float4" case reflect.Float64: return "double precision" case reflect.Bool: return "boolean" case reflect.Struct: if canConvertTo[time.Time](ty) { return "timestamptz" } if canConvertTo[net.IP](ty) { return "inet" } if canConvertTo[net.IPNet](ty) { return "cidr" } default: return "" } return "" } func parseField(f reflect.StructField, minfo *Model, modelMap map[string]*Model, i int) *Field { field := &Field{ Name: f.Name, Original: f, Index: i, } tags := parseTags(f.Tag.Get("d")) if tags["-"] != "" { return nil } field.PrimaryKey = tags["pk"] != "" || tags["primarykey"] != "" || field.Name == "ID" field.AutoIncrement = tags["autoinc"] != "" field.Nullable = tags["nullable"] != "" field.ColumnType = tags["type"] if field.ColumnType == "" { field.ColumnType = columnType(f.Type, field.PrimaryKey, field.AutoIncrement) } field.ColumnName = tags["column"] if field.ColumnName == "" { field.ColumnName = pascalToSnakeCase(field.Name) } if field.PrimaryKey { minfo.IDField = field.Name } elem := f.Type for elem.Kind() == reflect.Ptr { if !field.Nullable { field.Nullable = true } elem = elem.Elem() } field.Type = elem switch elem.Kind() { case reflect.Array, reflect.Slice: elem = elem.Elem() fallthrough case reflect.Struct: if canConvertTo[Document](elem) && f.Anonymous { minfo.TableName = tags["table"] field.embeddedFields = make(map[string]*Field) for j := range elem.NumField() { efield := elem.Field(j) field.embeddedFields[pascalToSnakeCase(efield.Name)] = parseField(efield, minfo, modelMap, j) } } else if field.ColumnType == "" { minfo.Relationships[field.Name] = parseRelationship(f, modelMap, minfo.Type, i, tags) } } return field }