Files
gormcol/gormcol.go
T
2026-03-28 17:45:22 +01:00

183 lines
4.9 KiB
Go

package gormcol
import (
"fmt"
"reflect"
"strings"
"gorm.io/gorm/schema"
)
// NamingStrategy is the GORM naming strategy used for column/table name resolution.
// Defaults to gorm.io/gorm/schema.NamingStrategy.
// Set this to NamingStrategy when using within a project that has a database connection.
var NamingStrategy schema.Namer = schema.NamingStrategy{}
type Tabler interface {
TableName() string
}
// Field represents a GORM column descriptor with table context.
// Define package-level variables to get type-safe column references:
//
// var PsAccess = struct {
// IDProfile gormcol.Field
// IDAuthorizationRole gormcol.Field
// }{
// IDProfile: gormcol.Field{Table: "ps_access", Column: "id_profile"},
// IDAuthorizationRole: gormcol.Field{Table: "ps_access", Column: "id_authorization_role"},
// }
type Field struct {
Table string
Column string
}
// Column returns the column name from a Field descriptor.
//
// gormcol.Column(prestadb.PsAccess.IDAuthorizationRole) // "id_authorization_role"
func Column(f Field) string {
return f.Column
}
// ColumnOnTable returns "table.column" from a Field descriptor.
//
// gormcol.ColumnOnTable(prestadb.PsAccess.IDAuthorizationRole) // "ps_access.id_authorization_role"
func ColumnOnTable(f Field) string {
return f.Table + "." + f.Column
}
// TableField returns the table name from a Field descriptor.
func TableField(f Field) string {
return f.Table
}
// -- Reflection-based utilities (for structs without Field descriptors) --
// Col extracts the GORM column name from the `gorm:"column:..."` struct tag.
//
// gormcol.Col[prestadb.PsAccess]("IDAuthorizationRole") // "id_authorization_role"
func Col[T any](fieldName string) string {
var zero T
typ := reflect.TypeOf(zero)
return columnFromType(typ, fieldName)
}
// ColOf extracts a GORM column name from a field on a nested struct referenced
// by structFieldName.
func ColOf[T, S any](structFieldName, fieldName string) string {
var zero T
typ := reflect.TypeOf(zero)
field, ok := typ.FieldByName(structFieldName)
if !ok {
panic(fmt.Sprintf("gormcol: struct %q has no field %q", typ.Name(), structFieldName))
}
return columnFromType(field.Type, fieldName)
}
// Tbl returns the table name for a GORM model.
// Uses TableName() method if the struct implements Tabler,
// otherwise falls back to the GORM naming strategy.
//
// gormcol.Tbl[prestadb.PsAccess]() // "ps_access"
func Tbl[T any]() string {
var zero T
if t, ok := any(zero).(Tabler); ok {
return t.TableName()
}
typ := reflect.TypeOf(zero)
return NamingStrategy.TableName(typ.Name())
}
// ColOnTbl returns the column prefixed with the table name (reflection-based).
//
// gormcol.ColOnTbl[prestadb.PsAccess]("IDAuthorizationRole") // "ps_access.id_authorization_role"
func ColOnTbl[T any](fieldName string) string {
return Tbl[T]() + "." + Col[T](fieldName)
}
// PK returns the primary key column name for a GORM model.
//
// gormcol.PK[prestadb.PsAccess]() // "id_authorization_role"
func PK[T any]() string {
var zero T
typ := reflect.TypeOf(zero)
for i := 0; i < typ.NumField(); i++ {
field := typ.Field(i)
gormTag := field.Tag.Get("gorm")
if gormTag == "" || gormTag == "-" {
continue
}
for _, part := range strings.Split(gormTag, ";") {
if strings.TrimSpace(part) == "primaryKey" {
return parseGormColumn(gormTag)
}
}
}
return ""
}
// Columns returns all GORM column names for a struct type.
//
// gormcol.Columns[prestadb.PsAccess]() // []string{"id_profile", "id_authorization_role"}
func Columns[T any]() []string {
var zero T
typ := reflect.TypeOf(zero)
var cols []string
for i := 0; i < typ.NumField(); i++ {
field := typ.Field(i)
if field.PkgPath != "" {
continue
}
col := parseGormColumn(field.Tag.Get("gorm"))
if col == "" {
col = NamingStrategy.ColumnName(typ.Name(), field.Name)
}
cols = append(cols, col)
}
return cols
}
// ColByJSON finds the GORM column name by matching the struct field's json tag.
//
// gormcol.ColByJSON[prestadb.PsAccess]("id_authorization_role") // "id_authorization_role"
func ColByJSON[T any](jsonName string) string {
var zero T
typ := reflect.TypeOf(zero)
for i := 0; i < typ.NumField(); i++ {
field := typ.Field(i)
jsonTag := field.Tag.Get("json")
if jsonTag == "" {
continue
}
tagName := strings.Split(jsonTag, ",")[0]
if tagName == jsonName {
return columnFromType(typ, field.Name)
}
}
panic(fmt.Sprintf("gormcol: struct %q has no field with json tag %q", typ.Name(), jsonName))
}
func columnFromType(typ reflect.Type, fieldName string) string {
field, ok := typ.FieldByName(fieldName)
if !ok {
panic(fmt.Sprintf("gormcol: struct %q has no field %q", typ.Name(), fieldName))
}
gormTag := field.Tag.Get("gorm")
if gormTag == "" || gormTag == "-" {
return NamingStrategy.ColumnName(typ.Name(), fieldName)
}
col := parseGormColumn(gormTag)
if col != "" {
return col
}
return NamingStrategy.ColumnName(typ.Name(), fieldName)
}