120 lines
2.7 KiB
Go
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
|
|
}
|