greatape/components/model/repository/base_repository.go

188 wiersze
6.3 KiB
Go

package repository
import (
"embed"
"encoding/json"
"fmt"
"io/ioutil"
"math/rand"
"reflect"
"strings"
"time"
. "github.com/xeronith/diamante/contracts/database"
. "github.com/xeronith/diamante/contracts/logging"
. "github.com/xeronith/diamante/contracts/system"
)
//go:embed scripts
var scripts embed.FS
type baseRepository struct {
name string
pluralName string
entityType reflect.Type
logger ILogger
database ISqlDatabase
volatile bool
}
func newBaseRepository(name, pluralName string, entityType reflect.Type, logger ILogger, volatile bool) baseRepository {
return baseRepository{
name: name,
pluralName: pluralName,
entityType: entityType,
logger: logger,
volatile: volatile,
}
}
func (repository *baseRepository) Name() string {
return repository.name
}
func (repository *baseRepository) Migrate() error {
schema := repository.GetSqlDatabase().GetSchema()
if schema == nil {
return fmt.Errorf("invalid_db_schema: %s", repository.GetSqlDatabase().GetName())
}
if !repository.volatile {
createTablesScript := repository.LoadScript("scripts/%s.sql")
createTriggersScript := repository.LoadScript("scripts/%s_triggers.sql")
if !schema.HasTable(repository.pluralName) {
script := createTablesScript + createTriggersScript
if err := repository.GetSqlDatabase().RunScript(script, "##########"); err != nil {
return err
}
schema = repository.GetSqlDatabase().GetSchema()
if !schema.HasTable(repository.pluralName) {
return fmt.Errorf("DB_MIGRATION: %s", repository.pluralName)
}
if !schema.HasHistoryTable(repository.pluralName) {
return fmt.Errorf("DB_MIGRATION: history.%s", repository.pluralName)
}
if !schema.HasTrigger(fmt.Sprintf("%s_before_update_trigger", repository.pluralName)) {
return fmt.Errorf("DB_MIGRATION: %s_before_update_trigger", repository.pluralName)
}
if !schema.HasTrigger(fmt.Sprintf("%s_before_delete_trigger", repository.pluralName)) {
return fmt.Errorf("DB_MIGRATION: %s_before_delete_trigger", repository.pluralName)
}
_, err := repository.database.Execute(`INSERT INTO "__system__" ("script") VALUES ($1);`, script)
if err != nil {
repository.logger.Alert(fmt.Sprintf("DB_MIGRATION: %s", err))
}
repository.logger.Debug(fmt.Sprintf("DB_MIGRATION: ✓ %s.%s", repository.GetSqlDatabase().GetName(), repository.pluralName))
}
changes := make([]string, 0)
commands := make([]string, 0)
historyCommands := make([]string, 0)
for i := 0; i < repository.entityType.NumField(); i++ {
field := repository.entityType.Field(i)
column := field.Tag.Get("json")
dbType := field.Tag.Get("storage")
defaultValue := field.Tag.Get("default")
if field.Name != "entity" && !schema.HasColumn(repository.pluralName, column) {
changes = append(changes, fmt.Sprintf("%s.%s", repository.pluralName, column))
commands = append(commands, fmt.Sprintf(`ALTER TABLE "%s" ADD COLUMN "%s" %s NOT NULL DEFAULT %s;`, repository.pluralName, column, dbType, defaultValue))
historyCommands = append(historyCommands, fmt.Sprintf(`ALTER TABLE "%s_history" ADD COLUMN "%s" %s NOT NULL DEFAULT %s;`, repository.pluralName, column, dbType, defaultValue))
}
}
var scriptLines []string
if len(commands) > 0 {
scriptLines = append([]string{}, historyCommands...)
scriptLines = append(scriptLines, fmt.Sprintf(`DROP TRIGGER "%s_before_update_trigger" ON "%s";`, repository.pluralName, repository.pluralName))
scriptLines = append(scriptLines, fmt.Sprintf(`DROP TRIGGER "%s_before_delete_trigger" ON "%s";`, repository.pluralName, repository.pluralName))
scriptLines = append(scriptLines, commands...)
scriptLines = append(scriptLines, createTriggersScript)
script := strings.Join(scriptLines, "\n##########\n")
if err := repository.GetSqlDatabase().RunScript(script, "##########"); err != nil {
return err
}
schema = repository.GetSqlDatabase().GetSchema()
for i := 0; i < repository.entityType.NumField(); i++ {
field := repository.entityType.Field(i)
column := field.Tag.Get("json")
if field.Name != "entity" && !schema.HasColumn(repository.pluralName, column) {
return fmt.Errorf("DB_MIGRATION: %s.%s", repository.pluralName, column)
}
}
if !schema.HasTrigger(fmt.Sprintf("%s_before_update_trigger", repository.pluralName)) {
return fmt.Errorf("DB_MIGRATION: %s_before_update_trigger", repository.pluralName)
}
if !schema.HasTrigger(fmt.Sprintf("%s_before_delete_trigger", repository.pluralName)) {
return fmt.Errorf("DB_MIGRATION: %s_before_delete_trigger", repository.pluralName)
}
_, err := repository.database.Execute(`INSERT INTO "__system__" ("script") VALUES ($1);`, script)
if err != nil {
repository.logger.Alert(fmt.Sprintf("DB_MIGRATION: %s", err))
}
for _, change := range changes {
repository.logger.Debug(fmt.Sprintf("DB_MIGRATION: ✓ %s.%s", repository.GetSqlDatabase().GetName(), change))
}
}
}
return nil
}
func (repository *baseRepository) LoadScript(resource string) string {
scriptData, err := scripts.ReadFile(fmt.Sprintf(resource, repository.name))
if err != nil {
panic(err)
}
return strings.ReplaceAll(string(scriptData), "###DATABASE###", repository.GetSqlDatabase().GetName())
}
func (repository *baseRepository) GetSqlDatabase() ISqlDatabase {
return repository.database
}
func (repository *baseRepository) SetSqlDatabase(database ISqlDatabase) {
repository.database = database
}
func (repository *baseRepository) Serialize(pointer Pointer, cause error) {
if data, err := json.Marshal(pointer); err != nil {
//TODO: Handle
repository.logger.Critical(fmt.Sprintf("EMERGENCY SERIALIZATION MARSHALLING FAILURE"))
} else {
timestamp := time.Now().UnixNano()
randomFactor := rand.Intn(1000)
dataFileName := fmt.Sprintf("%sE_%d_%d.json", repository.logger.SerializationPath(), timestamp, randomFactor)
if err := ioutil.WriteFile(dataFileName, data, 0644); err != nil {
//TODO: Handle
repository.logger.Critical(fmt.Sprintf("EMERGENCY SERIALIZATION PERSISTENCE FAILURE: %s", err))
}
errorFileName := fmt.Sprintf("%sE_%d_%d_ERROR.log", repository.logger.SerializationPath(), timestamp, randomFactor)
if err := ioutil.WriteFile(errorFileName, []byte(cause.Error()), 0644); err != nil {
//TODO: Handle
repository.logger.Critical(fmt.Sprintf("EMERGENCY SERIALIZATION ERROR PERSISTENCE FAILURE: %s", err))
}
}
}