160 lines
4.2 KiB
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
|
|
}
|