Files
relspecgo/pkg/writers/sqlite/template_functions.go
T
HeinandClaude Sonnet 5 ce3b615b0a feat(dbml): @postgres/@sqlite dialect directives (#19)
Add parseable `@<namespace>[(<target>)]: <args>` directives embedded in DBML.
They are stored losslessly on each object's Metadata, round-trip unchanged
through the DBML writer, and are translated to SQL only by the writer for the
matching dialect.

- models: Directive type + catalog; Metadata map added to Column and Index
- dbml reader: parse and attach directives at database/table/column/index
  level; line-numbered errors; repeatable by default with singleton duplicate
  detection. Fixes a preexisting bug where an `indexes {}` closing brace ended
  the table early, dropping trailing Note: and directive lines.
- dbml writer: re-emit directives at their location; idempotent output
- pgsql writer: PARTITION BY / INHERITS / WITH / TABLESPACE (table),
  STORAGE / COMPRESSION / identity (column), WITH / TABLESPACE (index)
- sqlite writer: WITHOUT ROWID / STRICT (table), COLLATE (column)
- --strict-directives flag on ReaderOptions and WriterOptions
- docs/DBML_DIRECTIVES.md + reader/writer READMEs

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Ss2MY5J11cRGwEz86ZXk7d
2026-09-08 16:17:37 +02:00

148 lines
4.1 KiB
Go

package sqlite
import (
"fmt"
"strings"
"text/template"
"git.warky.dev/wdevs/relspecgo/pkg/models"
"git.warky.dev/wdevs/relspecgo/pkg/writers"
)
// GetTemplateFuncs returns template functions for SQLite SQL generation
func GetTemplateFuncs(opts *writers.WriterOptions) template.FuncMap {
return template.FuncMap{
"quote_ident": QuoteIdentifier,
"map_type": MapPostgreSQLType,
"is_autoincrement": IsAutoIncrementCandidate,
"qualified_table_name": func(schema, table string) string {
return writers.QualifiedTableName(schema, table, opts.FlattenSchema)
},
"format_default": FormatDefault,
"format_constraint_name": func(schema, table, constraint string) string {
return FormatConstraintName(schema, table, constraint, opts)
},
"join": strings.Join,
"lower": strings.ToLower,
"upper": strings.ToUpper,
"column_collate": sqliteColumnCollate,
}
}
// QuoteIdentifier quotes an identifier for SQLite (double quotes)
func QuoteIdentifier(name string) string {
// SQLite uses double quotes for identifiers
// Escape any existing double quotes by doubling them
escaped := strings.ReplaceAll(name, `"`, `""`)
return fmt.Sprintf(`"%s"`, escaped)
}
// IsAutoIncrementCandidate checks if a column should use AUTOINCREMENT
func IsAutoIncrementCandidate(col *models.Column) bool {
// Must be a primary key
if !col.IsPrimaryKey {
return false
}
// Must be an integer type
if !IsIntegerType(col.Type) {
return false
}
// Check AutoIncrement field
if col.AutoIncrement {
return true
}
// Check if default suggests auto-increment
if col.Default != nil {
defaultStr, ok := col.Default.(string)
if ok {
defaultLower := strings.ToLower(defaultStr)
if strings.Contains(defaultLower, "nextval") ||
strings.Contains(defaultLower, "autoincrement") ||
strings.Contains(defaultLower, "auto_increment") {
return true
}
}
}
// Serial types are auto-increment
typeLower := strings.ToLower(col.Type)
return strings.Contains(typeLower, "serial")
}
// FormatDefault formats a default value for SQLite
func FormatDefault(col *models.Column) string {
if col.Default == nil {
return ""
}
// Skip auto-increment defaults (handled by AUTOINCREMENT keyword)
if IsAutoIncrementCandidate(col) {
return ""
}
// Convert to string
defaultStr, ok := col.Default.(string)
if !ok {
// If not a string, convert to string representation
defaultStr = fmt.Sprintf("%v", col.Default)
}
if defaultStr == "" {
return ""
}
// Convert PostgreSQL-specific functions to SQLite equivalents
defaultLower := strings.ToLower(defaultStr)
// Current timestamp functions
if strings.Contains(defaultLower, "current_timestamp") ||
strings.Contains(defaultLower, "now()") {
return "CURRENT_TIMESTAMP"
}
// Current date
if strings.Contains(defaultLower, "current_date") {
return "CURRENT_DATE"
}
// Current time
if strings.Contains(defaultLower, "current_time") {
return "CURRENT_TIME"
}
// Boolean values
sqliteType := MapPostgreSQLType(col.Type)
if sqliteType == TypeInteger {
typeLower := strings.ToLower(col.Type)
if strings.Contains(typeLower, "bool") {
return MapBooleanValue(defaultStr)
}
}
// UUID generation - SQLite doesn't have built-in UUID, comment it out
if strings.Contains(defaultLower, "uuid") || strings.Contains(defaultLower, "gen_random_uuid") {
return "" // Remove UUID defaults, users must handle this
}
// Remove PostgreSQL-specific casting
defaultStr = strings.ReplaceAll(defaultStr, "::text", "")
defaultStr = strings.ReplaceAll(defaultStr, "::integer", "")
defaultStr = strings.ReplaceAll(defaultStr, "::bigint", "")
defaultStr = strings.ReplaceAll(defaultStr, "::boolean", "")
return defaultStr
}
// FormatConstraintName formats a constraint name with table prefix if flattening
func FormatConstraintName(schema, table, constraint string, opts *writers.WriterOptions) string {
if opts.FlattenSchema && schema != "" {
// Prefix constraint with flattened table name
flatTable := writers.QualifiedTableName(schema, table, opts.FlattenSchema)
return fmt.Sprintf("%s_%s", flatTable, constraint)
}
return constraint
}