package pgsql import ( "bytes" "embed" "fmt" "strings" "text/template" "git.warky.dev/wdevs/relspecgo/pkg/models" ) //go:embed templates/*.tmpl var templateFS embed.FS // TemplateExecutor manages and executes SQL templates type TemplateExecutor struct { templates *template.Template } // NewTemplateExecutor creates a new template executor func NewTemplateExecutor() (*TemplateExecutor, error) { // Create template with custom functions funcMap := make(template.FuncMap) for k, v := range TemplateFunctions() { funcMap[k] = v } 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, }, nil } // Template data structures // CreateTableData contains data for create table template type CreateTableData struct { SchemaName string TableName string Columns []ColumnData } // ColumnData represents column information type ColumnData struct { Name string Type string Default string NotNull bool } // AddColumnData contains data for add column template type AddColumnData struct { SchemaName string TableName string ColumnName string ColumnType string Default string NotNull bool } // AlterColumnTypeData contains data for alter column type template type AlterColumnTypeData struct { SchemaName string TableName string ColumnName string NewType string } // AlterColumnDefaultData contains data for alter column default template type AlterColumnDefaultData struct { SchemaName string TableName string ColumnName string SetDefault bool DefaultValue string } // CreatePrimaryKeyData contains data for create primary key template type CreatePrimaryKeyData struct { SchemaName string TableName string ConstraintName string Columns string } // CreateIndexData contains data for create index template type CreateIndexData struct { SchemaName string TableName string IndexName string IndexType string Columns string Unique bool } // CreateForeignKeyData contains data for create foreign key template type CreateForeignKeyData struct { SchemaName string TableName string ConstraintName string SourceColumns string TargetSchema string TargetTable string TargetColumns string OnDelete string OnUpdate string } // DropConstraintData contains data for drop constraint template type DropConstraintData struct { SchemaName string TableName string ConstraintName string } // DropIndexData contains data for drop index template type DropIndexData struct { SchemaName string IndexName string } // CommentTableData contains data for table comment template type CommentTableData struct { SchemaName string TableName string Comment string } // CommentColumnData contains data for column comment template type CommentColumnData struct { SchemaName string TableName string ColumnName string Comment string } // AuditTablesData contains data for audit tables template type AuditTablesData struct { AuditSchema string } // AuditColumnData represents a column in audit template type AuditColumnData struct { Name string OldValue string NewValue string } // AuditFunctionData contains data for audit function template type AuditFunctionData struct { SchemaName string FunctionName string TableName string TablePrefix string PrimaryKey string AuditSchema string UserFunction string AuditInsert bool AuditUpdate bool AuditDelete bool UpdateCondition string UpdateColumns []AuditColumnData DeleteColumns []AuditColumnData } // AuditTriggerData contains data for audit trigger template type AuditTriggerData struct { SchemaName string TableName string TriggerName string FunctionName string Events string } // Execute methods for each template // ExecuteCreateTable executes the create table template func (te *TemplateExecutor) ExecuteCreateTable(data CreateTableData) (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 } // ExecuteAddColumn executes the add column template func (te *TemplateExecutor) ExecuteAddColumn(data AddColumnData) (string, error) { var buf bytes.Buffer err := te.templates.ExecuteTemplate(&buf, "add_column.tmpl", data) if err != nil { return "", fmt.Errorf("failed to execute add_column template: %w", err) } return buf.String(), nil } // ExecuteAlterColumnType executes the alter column type template func (te *TemplateExecutor) ExecuteAlterColumnType(data AlterColumnTypeData) (string, error) { var buf bytes.Buffer err := te.templates.ExecuteTemplate(&buf, "alter_column_type.tmpl", data) if err != nil { return "", fmt.Errorf("failed to execute alter_column_type template: %w", err) } return buf.String(), nil } // ExecuteAlterColumnDefault executes the alter column default template func (te *TemplateExecutor) ExecuteAlterColumnDefault(data AlterColumnDefaultData) (string, error) { var buf bytes.Buffer err := te.templates.ExecuteTemplate(&buf, "alter_column_default.tmpl", data) if err != nil { return "", fmt.Errorf("failed to execute alter_column_default template: %w", err) } return buf.String(), nil } // ExecuteCreatePrimaryKey executes the create primary key template func (te *TemplateExecutor) ExecuteCreatePrimaryKey(data CreatePrimaryKeyData) (string, error) { var buf bytes.Buffer err := te.templates.ExecuteTemplate(&buf, "create_primary_key.tmpl", data) if err != nil { return "", fmt.Errorf("failed to execute create_primary_key template: %w", err) } return buf.String(), nil } // ExecuteCreateIndex executes the create index template func (te *TemplateExecutor) ExecuteCreateIndex(data CreateIndexData) (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 } // ExecuteCreateForeignKey executes the create foreign key template func (te *TemplateExecutor) ExecuteCreateForeignKey(data CreateForeignKeyData) (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 } // ExecuteDropConstraint executes the drop constraint template func (te *TemplateExecutor) ExecuteDropConstraint(data DropConstraintData) (string, error) { var buf bytes.Buffer err := te.templates.ExecuteTemplate(&buf, "drop_constraint.tmpl", data) if err != nil { return "", fmt.Errorf("failed to execute drop_constraint template: %w", err) } return buf.String(), nil } // ExecuteDropIndex executes the drop index template func (te *TemplateExecutor) ExecuteDropIndex(data DropIndexData) (string, error) { var buf bytes.Buffer err := te.templates.ExecuteTemplate(&buf, "drop_index.tmpl", data) if err != nil { return "", fmt.Errorf("failed to execute drop_index template: %w", err) } return buf.String(), nil } // ExecuteCommentTable executes the table comment template func (te *TemplateExecutor) ExecuteCommentTable(data CommentTableData) (string, error) { var buf bytes.Buffer err := te.templates.ExecuteTemplate(&buf, "comment_table.tmpl", data) if err != nil { return "", fmt.Errorf("failed to execute comment_table template: %w", err) } return buf.String(), nil } // ExecuteCommentColumn executes the column comment template func (te *TemplateExecutor) ExecuteCommentColumn(data CommentColumnData) (string, error) { var buf bytes.Buffer err := te.templates.ExecuteTemplate(&buf, "comment_column.tmpl", data) if err != nil { return "", fmt.Errorf("failed to execute comment_column template: %w", err) } return buf.String(), nil } // ExecuteAuditTables executes the audit tables template func (te *TemplateExecutor) ExecuteAuditTables(data AuditTablesData) (string, error) { var buf bytes.Buffer err := te.templates.ExecuteTemplate(&buf, "audit_tables.tmpl", data) if err != nil { return "", fmt.Errorf("failed to execute audit_tables template: %w", err) } return buf.String(), nil } // ExecuteAuditFunction executes the audit function template func (te *TemplateExecutor) ExecuteAuditFunction(data AuditFunctionData) (string, error) { var buf bytes.Buffer err := te.templates.ExecuteTemplate(&buf, "audit_function.tmpl", data) if err != nil { return "", fmt.Errorf("failed to execute audit_function template: %w", err) } return buf.String(), nil } // ExecuteAuditTrigger executes the audit trigger template func (te *TemplateExecutor) ExecuteAuditTrigger(data AuditTriggerData) (string, error) { var buf bytes.Buffer err := te.templates.ExecuteTemplate(&buf, "audit_trigger.tmpl", data) if err != nil { return "", fmt.Errorf("failed to execute audit_trigger template: %w", err) } return buf.String(), nil } // Helper functions to build template data from models // BuildCreateTableData builds CreateTableData from a models.Table func BuildCreateTableData(schemaName string, table *models.Table) CreateTableData { columns := make([]ColumnData, 0, len(table.Columns)) // Get sorted columns sortedCols := getSortedColumns(table.Columns) for _, col := range sortedCols { colData := ColumnData{ Name: col.Name, Type: col.Type, NotNull: col.NotNull, } if col.Default != nil { colData.Default = fmt.Sprintf("%v", col.Default) } columns = append(columns, colData) } return CreateTableData{ SchemaName: schemaName, TableName: table.Name, Columns: columns, } } // BuildAuditFunctionData builds AuditFunctionData from table and config func BuildAuditFunctionData( schemaName string, table *models.Table, pk *models.Column, config *TableAuditConfig, auditSchema string, userFunction string, ) AuditFunctionData { funcName := fmt.Sprintf("ft_audit_%s", table.Name) // Build list of audited columns auditedColumns := make([]*models.Column, 0) for _, col := range table.Columns { if col.Name == pk.Name { continue } excluded := false for _, excl := range config.ExcludedColumns { if strings.EqualFold(col.Name, excl) { excluded = true break } } if excluded { continue } auditedColumns = append(auditedColumns, col) } // Build update condition updateComparisons := make([]string, 0) for _, col := range auditedColumns { updateComparisons = append(updateComparisons, fmt.Sprintf("old.%s IS DISTINCT FROM new.%s", col.Name, col.Name)) } updateCondition := strings.Join(updateComparisons, " OR ") // Build update columns data updateColumns := make([]AuditColumnData, 0) for _, col := range auditedColumns { isEncrypted := false for _, enc := range config.EncryptedColumns { if strings.EqualFold(col.Name, enc) { isEncrypted = true break } } oldValue := fmt.Sprintf("old.%s::text", col.Name) newValue := fmt.Sprintf("new.%s::text", col.Name) if isEncrypted { oldValue = "'****************'" newValue = "'****************'" } updateColumns = append(updateColumns, AuditColumnData{ Name: col.Name, OldValue: oldValue, NewValue: newValue, }) } // Build delete columns data (same as update but only old values) deleteColumns := make([]AuditColumnData, 0) for _, col := range auditedColumns { isEncrypted := false for _, enc := range config.EncryptedColumns { if strings.EqualFold(col.Name, enc) { isEncrypted = true break } } oldValue := fmt.Sprintf("old.%s::text", col.Name) if isEncrypted { oldValue = "'****************'" } deleteColumns = append(deleteColumns, AuditColumnData{ Name: col.Name, OldValue: oldValue, }) } tablePrefix := "NULL" if config.TablePrefix != "" { tablePrefix = fmt.Sprintf("'%s'", config.TablePrefix) } return AuditFunctionData{ SchemaName: schemaName, FunctionName: funcName, TableName: table.Name, TablePrefix: tablePrefix, PrimaryKey: pk.Name, AuditSchema: auditSchema, UserFunction: userFunction, AuditInsert: config.AuditInsert, AuditUpdate: config.AuditUpdate, AuditDelete: config.AuditDelete, UpdateCondition: updateCondition, UpdateColumns: updateColumns, DeleteColumns: deleteColumns, } }