Schema Parser
The Schema Parser is a core component of Goofer ORM that uses Go’s reflection capabilities to analyze your entity structs at runtime. It extracts metadata from struct tags, maps Go types to database types, and builds a complete schema registry for your application.
How It Works
The Schema Parser works by:
- Analyzing entity structs using reflection
- Parsing ORM tags on struct fields
- Extracting metadata about fields, types, and relationships
- Building a schema registry that the ORM can use for database operations
Parsing Process
When you register an entity with Goofer ORM, the Schema Parser performs the following steps:
// Register an entity
if err := schema.Registry.RegisterEntity(User{}); err != nil {
log.Fatalf("Failed to register User entity: %v", err)
}1. Type Analysis
First, the parser analyzes the entity’s type using reflection:
entityType := reflect.TypeOf(entity)
if entityType.Kind() == reflect.Ptr {
entityType = entityType.Elem()
}2. Table Name Resolution
Next, it calls the TableName() method to get the database table name:
meta := &EntityMetadata{
TableName: entity.TableName(),
}3. Field Analysis
Then, it iterates through each field in the struct:
for i := 0; i < entityType.NumField(); i++ {
field := entityType.Field(i)
tag := field.Tag.Get(TagName)
if tag == "" || tag == "-" {
continue
}
fieldMeta, err := parseFieldTag(field, tag)
if err != nil {
return err
}
meta.Fields = append(meta.Fields, *fieldMeta)
}4. Tag Parsing
For each field, it parses the ORM tag to extract metadata:
func parseFieldTag(field reflect.StructField, tag string) (*FieldMetadata, error) {
options := parseTagOptions(tag)
meta := &FieldMetadata{
Name: field.Name,
DBName: snakeCase(field.Name),
IsNullable: true, // Default to nullable
}
for _, opt := range options {
switch {
case opt == PrimaryKeyOption:
meta.IsPrimaryKey = true
case opt == AutoIncrementOpt:
meta.IsAutoIncr = true
case opt == UniqueOption:
meta.IsUnique = true
case opt == IndexOption:
meta.IsIndexed = true
case opt == NotNullOption:
meta.IsNullable = false
case strings.HasPrefix(opt, TypeOption+":"):
meta.Type = strings.TrimPrefix(opt, TypeOption+":")
case strings.HasPrefix(opt, DefaultOption+":"):
meta.Default = strings.TrimPrefix(opt, DefaultOption+":")
case strings.HasPrefix(opt, RelationOption+":"):
relType := strings.TrimPrefix(opt, RelationOption+":")
meta.Relation = &RelationMetadata{
Type: RelationType(relType),
}
case strings.HasPrefix(opt, ForeignKeyOption+":"):
if meta.Relation != nil {
meta.Relation.ForeignKey = strings.TrimPrefix(opt, ForeignKeyOption+":")
}
}
}
// Infer type from Go type if not specified
if meta.Type == "" {
meta.Type = inferSQLType(field.Type)
}
return meta, nil
}5. Type Inference
If a field doesn’t have an explicit type specified, the parser infers the SQL type from the Go type:
func inferSQLType(t reflect.Type) string {
switch t.Kind() {
case reflect.String:
return "VARCHAR(255)"
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64,
reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
return "INTEGER"
case reflect.Float32, reflect.Float64:
return "FLOAT"
case reflect.Bool:
return "BOOLEAN"
case reflect.Struct:
if t.String() == "time.Time" {
return "TIMESTAMP"
}
case reflect.Slice:
if t.Elem().Kind() == reflect.Uint8 {
return "BLOB"
}
}
return "TEXT"
}6. Relationship Analysis
For fields that represent relationships, the parser extracts relationship metadata:
if meta.Relation != nil {
meta.Relations = append(meta.Relations, *fieldMeta.Relation)
}7. Schema Registry
Finally, the parser stores the entity metadata in the global schema registry:
r.entities[entityType] = metaSchema Registry
The Schema Registry is a global repository of entity metadata that the ORM uses for database operations:
// Global registry instance
var Registry = NewSchemaRegistry()
// SchemaRegistry maintains entity metadata
type SchemaRegistry struct {
entities map[reflect.Type]*EntityMetadata
}You can access the registry to get metadata for an entity:
userMeta, exists := schema.Registry.GetEntityMetadata(schema.GetEntityType(User{}))
if !exists {
log.Fatalf("User entity not registered")
}Entity Metadata
The entity metadata contains all the information the ORM needs to work with an entity:
// EntityMetadata contains complete entity schema
type EntityMetadata struct {
TableName string
Fields []FieldMetadata
PrimaryKey *FieldMetadata
Relations []RelationMetadata
Indexes []IndexMetadata
}Field Metadata
Each field in an entity has its own metadata:
// FieldMetadata contains parsed ORM tag information
type FieldMetadata struct {
Name string
DBName string
Type string
IsPrimaryKey bool
IsAutoIncr bool
IsUnique bool
IsIndexed bool
IsNullable bool
Default interface{}
Relation *RelationMetadata
}Relation Metadata
Relationship fields have additional metadata:
// RelationMetadata describes entity relationships
type RelationMetadata struct {
Type RelationType
Entity reflect.Type
ForeignKey string
}Best Practices
- Register all entities at application startup
- Use explicit types in ORM tags for clarity
- Keep entity definitions clean and focused
- Use meaningful names for fields and relationships
- Validate the schema registry after registration
Next Steps
- Learn about Entity System to understand how to define entities
- Explore Relation Mapping to see how to define and work with entity relationships
- Check out the Migration Engine to learn how to generate database schemas from entity metadata