Nigiri/constraints.go
2025-08-15 16:37:17 -05:00

120 lines
2.7 KiB
Go

package nigiri
import (
"reflect"
"strings"
)
// Constraint types
type ConstraintType string
const (
ConstraintUnique ConstraintType = "unique"
ConstraintForeign ConstraintType = "fkey"
ConstraintRequired ConstraintType = "required"
ConstraintIndex ConstraintType = "index"
)
type FieldConstraint struct {
Type ConstraintType
Field string
Target string // for foreign keys: "table.field"
IndexName string // for custom index names
}
type SchemaInfo struct {
Fields map[string]reflect.Type
Constraints map[string][]FieldConstraint
Indices map[string]string // field -> index name
}
// ParseSchema extracts constraints from struct tags
func ParseSchema[T any]() *SchemaInfo {
var zero T
t := reflect.TypeOf(zero)
if t.Kind() == reflect.Ptr {
t = t.Elem()
}
schema := &SchemaInfo{
Fields: make(map[string]reflect.Type),
Constraints: make(map[string][]FieldConstraint),
Indices: make(map[string]string),
}
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
fieldName := field.Name
schema.Fields[fieldName] = field.Type
dbTag := field.Tag.Get("db")
if dbTag == "" {
continue
}
constraints := parseDBTag(fieldName, dbTag)
if len(constraints) > 0 {
schema.Constraints[fieldName] = constraints
}
// Auto-create indices for unique and indexed fields
for _, constraint := range constraints {
if constraint.Type == ConstraintUnique || constraint.Type == ConstraintIndex {
indexName := constraint.IndexName
if indexName == "" {
indexName = fieldName + "_idx"
}
schema.Indices[fieldName] = indexName
}
}
}
return schema
}
func parseDBTag(fieldName, tag string) []FieldConstraint {
var constraints []FieldConstraint
parts := strings.Split(tag, ",")
for _, part := range parts {
part = strings.TrimSpace(part)
if part == "" {
continue
}
switch {
case part == "unique":
constraints = append(constraints, FieldConstraint{
Type: ConstraintUnique,
Field: fieldName,
})
case part == "required":
constraints = append(constraints, FieldConstraint{
Type: ConstraintRequired,
Field: fieldName,
})
case part == "index":
constraints = append(constraints, FieldConstraint{
Type: ConstraintIndex,
Field: fieldName,
})
case strings.HasPrefix(part, "index:"):
indexName := strings.TrimPrefix(part, "index:")
constraints = append(constraints, FieldConstraint{
Type: ConstraintIndex,
Field: fieldName,
IndexName: indexName,
})
case strings.HasPrefix(part, "fkey:"):
target := strings.TrimPrefix(part, "fkey:")
constraints = append(constraints, FieldConstraint{
Type: ConstraintForeign,
Field: fieldName,
Target: target,
})
}
}
return constraints
}