package sqlite import ( "bytes" "embed" "fmt" "sort" "text/template" "git.warky.dev/wdevs/relspecgo/pkg/models" "git.warky.dev/wdevs/relspecgo/pkg/writers" ) //go:embed templates/*.tmpl var templateFS embed.FS // TemplateExecutor manages and executes SQLite SQL templates type TemplateExecutor struct { templates *template.Template options *writers.WriterOptions } // NewTemplateExecutor creates a new template executor for SQLite func NewTemplateExecutor(opts *writers.WriterOptions) (*TemplateExecutor, error) { // Create template with SQLite-specific functions funcMap := GetTemplateFuncs(opts) tmpl, err := template.New("").Funcs(funcMap).ParseFS(templateFS, "templates/*.tmpl") if err != nil { return nil, fmt.Errorf("failed to parse templates: %w", err) } return &TemplateExecutor{ templates: tmpl, options: opts, }, nil } // Template data structures // TableTemplateData contains data for table template type TableTemplateData struct { Schema string Name string Columns []*models.Column PrimaryKey *models.Constraint } // IndexTemplateData contains data for index template type IndexTemplateData struct { Schema string Table string Name string Columns []string } // ConstraintTemplateData contains data for constraint templates type ConstraintTemplateData struct { Schema string Table string Name string Columns []string Expression string ForeignSchema string ForeignTable string ForeignColumns []string OnDelete string OnUpdate string } // Execute methods // ExecutePragmaForeignKeys executes the pragma foreign keys template func (te *TemplateExecutor) ExecutePragmaForeignKeys() (string, error) { var buf bytes.Buffer err := te.templates.ExecuteTemplate(&buf, "pragma_foreign_keys.tmpl", nil) if err != nil { return "", fmt.Errorf("failed to execute pragma_foreign_keys template: %w", err) } return buf.String(), nil } // ExecuteCreateTable executes the create table template func (te *TemplateExecutor) ExecuteCreateTable(data TableTemplateData) (string, error) { var buf bytes.Buffer err := te.templates.ExecuteTemplate(&buf, "create_table.tmpl", data) if err != nil { return "", fmt.Errorf("failed to execute create_table template: %w", err) } return buf.String(), nil } // ExecuteCreateIndex executes the create index template func (te *TemplateExecutor) ExecuteCreateIndex(data IndexTemplateData) (string, error) { var buf bytes.Buffer err := te.templates.ExecuteTemplate(&buf, "create_index.tmpl", data) if err != nil { return "", fmt.Errorf("failed to execute create_index template: %w", err) } return buf.String(), nil } // ExecuteCreateUniqueConstraint executes the create unique constraint template func (te *TemplateExecutor) ExecuteCreateUniqueConstraint(data ConstraintTemplateData) (string, error) { var buf bytes.Buffer err := te.templates.ExecuteTemplate(&buf, "create_unique_constraint.tmpl", data) if err != nil { return "", fmt.Errorf("failed to execute create_unique_constraint template: %w", err) } return buf.String(), nil } // ExecuteCreateCheckConstraint executes the create check constraint template func (te *TemplateExecutor) ExecuteCreateCheckConstraint(data ConstraintTemplateData) (string, error) { var buf bytes.Buffer err := te.templates.ExecuteTemplate(&buf, "create_check_constraint.tmpl", data) if err != nil { return "", fmt.Errorf("failed to execute create_check_constraint template: %w", err) } return buf.String(), nil } // ExecuteCreateForeignKey executes the create foreign key template func (te *TemplateExecutor) ExecuteCreateForeignKey(data ConstraintTemplateData) (string, error) { var buf bytes.Buffer err := te.templates.ExecuteTemplate(&buf, "create_foreign_key.tmpl", data) if err != nil { return "", fmt.Errorf("failed to execute create_foreign_key template: %w", err) } return buf.String(), nil } // Helper functions to build template data from models // BuildTableTemplateData builds TableTemplateData from a models.Table func BuildTableTemplateData(schema string, table *models.Table) TableTemplateData { columns := sortColumns(table.Columns) // Find primary key constraint var pk *models.Constraint for _, constraint := range sortConstraints(table.Constraints) { if constraint.Type == models.PrimaryKeyConstraint { pk = constraint break } } // If no explicit primary key constraint, build one from columns with IsPrimaryKey=true if pk == nil { pkCols := []string{} for _, col := range columns { if col.IsPrimaryKey { pkCols = append(pkCols, col.Name) } } if len(pkCols) > 0 { pk = &models.Constraint{ Name: "pk_" + table.Name, Type: models.PrimaryKeyConstraint, Columns: pkCols, } } } return TableTemplateData{ Schema: schema, Name: table.Name, Columns: columns, PrimaryKey: pk, } } // sortColumns returns columns sorted by Sequence then Name for deterministic output. func sortColumns(columns map[string]*models.Column) []*models.Column { result := make([]*models.Column, 0, len(columns)) for _, col := range columns { result = append(result, col) } sort.Slice(result, func(i, j int) bool { if result[i].Sequence > 0 && result[j].Sequence > 0 { return result[i].Sequence < result[j].Sequence } return result[i].Name < result[j].Name }) return result } // sortConstraints returns constraints sorted by Sequence then Name for deterministic output. func sortConstraints(constraints map[string]*models.Constraint) []*models.Constraint { result := make([]*models.Constraint, 0, len(constraints)) for _, c := range constraints { result = append(result, c) } sort.Slice(result, func(i, j int) bool { if result[i].Sequence > 0 && result[j].Sequence > 0 { return result[i].Sequence < result[j].Sequence } return result[i].Name < result[j].Name }) return result } // sortIndexes returns indexes sorted by Sequence then Name for deterministic output. func sortIndexes(indexes map[string]*models.Index) []*models.Index { result := make([]*models.Index, 0, len(indexes)) for _, idx := range indexes { result = append(result, idx) } sort.Slice(result, func(i, j int) bool { if result[i].Sequence > 0 && result[j].Sequence > 0 { return result[i].Sequence < result[j].Sequence } return result[i].Name < result[j].Name }) return result }