feat(sqlite): add SQLite writer for converting PostgreSQL schemas
All checks were successful
All checks were successful
- Implement SQLite DDL writer to convert PostgreSQL schemas to SQLite-compatible SQL statements. - Include automatic schema flattening, type mapping, auto-increment detection, and function translation. - Add templates for creating tables, indexes, unique constraints, check constraints, and foreign keys. - Implement tests for writer functionality and data type mapping.
This commit is contained in:
146
pkg/writers/sqlite/template_functions.go
Normal file
146
pkg/writers/sqlite/template_functions.go
Normal file
@@ -0,0 +1,146 @@
|
||||
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,
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
Reference in New Issue
Block a user