Table & Column Naming
go-lightning converts Go struct and field names to database table and column names using a naming strategy. The default converts CamelCase to snake_case, but you can customize this behavior.
The DbNamingStrategy Interface
All naming is controlled through this interface:
type DbNamingStrategy interface {
GetTableNameFromStructName(string) string
GetColumnNameFromStructName(string) string
}The built-in DefaultDbNamingStrategy applies these rules:
- Table names: Struct name → snake_case + "s" (pluralized)
- Column names: Field name → snake_case
Column Naming
Default Column Names
Field names are converted from CamelCase to snake_case. Consecutive uppercase letters (acronyms) are kept together:
| Field | Column |
|---|---|
Id | id |
FirstName | first_name |
CreatedAt | created_at |
UserID | user_id |
HTTPCode | http_code |
XMLData | xml_data |
Custom Column Names with lit Tags
The simplest way to override column names for specific fields is using the lit struct tag. This is useful when you need to map a few fields to non-standard column names without creating a full naming strategy.
type User struct {
Id int `lit:"id"`
FirstName string `lit:"first_name"`
LastName string `lit:"surname"` // Maps to "surname" instead of "last_name"
Email string `lit:"email_address"` // Maps to "email_address" instead of "email"
}Mixing Tagged and Untagged Fields
You can use lit tags on only some fields. Fields without tags use the default snake_case conversion:
type User struct {
Id int // Uses default: "id"
FirstName string `lit:"given_name"` // Uses tag: "given_name"
LastName string // Uses default: "last_name"
PhoneNumber string `lit:"phone"` // Uses tag: "phone"
}When to Use lit Tags
The lit tag is ideal for:
- Mapping to existing database columns with non-standard names
- Quick overrides when you only need to change a few column names
- Legacy database integration where column names don't follow conventions
The tag affects:
- INSERT queries: Column names in the generated INSERT statement
- UPDATE queries: Column names in the generated UPDATE statement
- SELECT queries: Mapping database columns back to struct fields
Precedence
The lit tag takes precedence over any naming strategy. If a field has a lit tag, that value is always used regardless of the registered naming strategy.
type MyNamingStrategy struct{}
func (MyNamingStrategy) GetColumnNameFromStructName(name string) string {
return strings.ToUpper(name) // Would return "FIRSTNAME"
}
func (MyNamingStrategy) GetTableNameFromStructName(name string) string {
return strings.ToLower(name) + "s"
}
type User struct {
FirstName string `lit:"fname"` // Uses "fname", not "FIRSTNAME"
}
lit.RegisterModelWithNaming[User](lit.PostgreSQL, MyNamingStrategy{})Custom Column Naming Strategy
For full control over all column names, implement GetColumnNameFromStructName:
type UppercaseColumnsStrategy struct{}
func (UppercaseColumnsStrategy) GetTableNameFromStructName(name string) string {
return toSnakeCase(name) + "s" // Keep default table naming
}
func (UppercaseColumnsStrategy) GetColumnNameFromStructName(name string) string {
return strings.ToUpper(toSnakeCase(name)) // FIRST_NAME, LAST_NAME, etc.
}
// Usage
lit.RegisterModelWithNaming[User](lit.PostgreSQL, UppercaseColumnsStrategy{})
// FirstName → FIRST_NAMETable Naming
Default Table Names
Struct names are converted from CamelCase to snake_case and pluralized with "s". Consecutive uppercase letters (acronyms) are kept together:
| Struct | Table |
|---|---|
User | users |
OrderItem | order_items |
HTTPRequest | http_requests |
UserID | user_ids |
OAuth2Token | oauth2_tokens |
Custom Table Naming Strategy
To customize how table names are derived, implement GetTableNameFromStructName:
type PrefixedTableStrategy struct {
Prefix string
}
func (s PrefixedTableStrategy) GetTableNameFromStructName(name string) string {
return s.Prefix + toSnakeCase(name) + "s"
}
func (s PrefixedTableStrategy) GetColumnNameFromStructName(name string) string {
return toSnakeCase(name) // Keep default column naming
}
// Usage
lit.RegisterModelWithNaming[User](lit.PostgreSQL, PrefixedTableStrategy{Prefix: "app_"})
// User → app_usersExample: Singular Table Names (No Pluralization)
type SingularTableStrategy struct{}
func (SingularTableStrategy) GetTableNameFromStructName(name string) string {
return toSnakeCase(name) // No "s" suffix: User → user
}
func (SingularTableStrategy) GetColumnNameFromStructName(name string) string {
return toSnakeCase(name)
}
// Usage
lit.RegisterModelWithNaming[User](lit.PostgreSQL, SingularTableStrategy{})
// User → user (not "users")Example: Exact Table Name
When you need to map to a specific table name that doesn't follow any convention:
type ExactTableStrategy struct {
TableName string
}
func (s ExactTableStrategy) GetTableNameFromStructName(name string) string {
return s.TableName // Use exact name provided
}
func (s ExactTableStrategy) GetColumnNameFromStructName(name string) string {
return toSnakeCase(name)
}
// Usage - map User struct to "TBL_USERS" table
lit.RegisterModelWithNaming[User](lit.PostgreSQL, ExactTableStrategy{TableName: "TBL_USERS"})
// User → TBL_USERSExample: Schema-Qualified Table Names
type SchemaTableStrategy struct {
Schema string
}
func (s SchemaTableStrategy) GetTableNameFromStructName(name string) string {
return s.Schema + "." + toSnakeCase(name) + "s"
}
func (s SchemaTableStrategy) GetColumnNameFromStructName(name string) string {
return toSnakeCase(name)
}
// Usage
lit.RegisterModelWithNaming[User](lit.PostgreSQL, SchemaTableStrategy{Schema: "public"})
// User → public.usersUsing Different Strategies Per Model
Each model can have its own naming strategy:
// Default naming for User
lit.RegisterModel[User](lit.PostgreSQL)
// User → users
// Prefixed tables for LegacyCustomer
lit.RegisterModelWithNaming[LegacyCustomer](lit.PostgreSQL, PrefixedTableStrategy{Prefix: "legacy_"})
// LegacyCustomer → legacy_legacy_customers
// Singular naming for AuditLog
lit.RegisterModelWithNaming[AuditLog](lit.PostgreSQL, SingularTableStrategy{})
// AuditLog → audit_log
// Exact table name for SpecialModel
lit.RegisterModelWithNaming[SpecialModel](lit.PostgreSQL, ExactTableStrategy{TableName: "my_special_table"})
// SpecialModel → my_special_tableComplete Example: Legacy Database Integration
When working with an existing database that doesn't follow conventions, combine table and column naming:
type LegacyNamingStrategy struct {
TableName string
Columns map[string]string
}
func (s LegacyNamingStrategy) GetTableNameFromStructName(name string) string {
return s.TableName
}
func (s LegacyNamingStrategy) GetColumnNameFromStructName(name string) string {
if col, ok := s.Columns[name]; ok {
return col
}
return name // Fallback to exact field name
}
// Usage for a legacy table "TBL_USERS" with non-standard columns
lit.RegisterModelWithNaming[User](lit.PostgreSQL, LegacyNamingStrategy{
TableName: "TBL_USERS",
Columns: map[string]string{
"Id": "USER_ID",
"FirstName": "FNAME",
"LastName": "LNAME",
"Email": "EMAIL_ADDR",
},
})
// User struct maps to:
// - Table: TBL_USERS
// - Columns: USER_ID, FNAME, LNAME, EMAIL_ADDRHelper Function
Most examples use this helper for snake_case conversion:
func toSnakeCase(input string) string {
var result strings.Builder
runes := []rune(input)
for i := 0; i < len(runes); i++ {
r := runes[i]
if unicode.IsUpper(r) {
if i > 0 {
prevLower := unicode.IsLower(runes[i-1])
nextLower := i+1 < len(runes) && unicode.IsLower(runes[i+1])
prevUpper := unicode.IsUpper(runes[i-1])
if prevLower || (prevUpper && nextLower) {
result.WriteRune('_')
}
}
result.WriteRune(unicode.ToLower(r))
} else {
result.WriteRune(r)
}
}
return result.String()
}