458 lines
12 KiB
Go
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,
|
|
}
|
|
}
|