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.
147 lines
4.0 KiB
Go
147 lines
4.0 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,
|
|
}
|
|
}
|
|
|
|
// 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
|
|
}
|