diamond-orm/field.go

160 lines
4.2 KiB
Go

package orm
import (
"net"
"reflect"
"time"
)
// Field - represents a field with a valid SQL type in a Model
type Field struct {
Name string // the name of this field as it appears in its Model's type definition
ColumnName string // this field's snake_cased column name as it appears in database
ColumnType string // the SQL type of this field's column (bigint, bigserial, text, ...)
Type reflect.Type // the reflect.Type of the struct field this Field represents
Original reflect.StructField // the raw struct field, as obtained by using reflect.Type.Field or reflect.Type.FieldByName
Model *Model // the Model this field belongs to
Index int // the index at which Original appears in its struct
AutoIncrement bool // whether this field's column is an auto-incrementing column
PrimaryKey bool // true if this field's column is a primary key
Nullable bool // true if this field's column can be NULL
embeddedFields map[string]*Field // mapping of column names to Field pointers that correspond to the surrounding struct's fields
}
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
}