Files
relspecgo/pkg/writers/pgsql/templates.go
Hein 5e1448dcdb
Some checks are pending
CI / Test (1.23) (push) Waiting to run
CI / Test (1.24) (push) Waiting to run
CI / Test (1.25) (push) Waiting to run
CI / Lint (push) Waiting to run
CI / Build (push) Waiting to run
sql writer
2025-12-17 20:44:02 +02:00

458 lines
12 KiB
Go

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,
}
}