Skip to content

Commit

Permalink
Добавлен модуль работы с подключением к SQL базе данных и миграциями.
Browse files Browse the repository at this point in the history
  • Loading branch information
monoflash committed Jul 7, 2022
1 parent 45294a2 commit f256e36
Show file tree
Hide file tree
Showing 12 changed files with 866 additions and 5 deletions.
10 changes: 5 additions & 5 deletions application/component/bootstrap/bootstrap.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,20 +41,20 @@ func (brp *impl) Preferences() kitTypes.ComponentPreferences {
cEnvironment = `(?mi)application/component/environment$`
cInterrupt = `(?mi)application/component/interrupt$`
cConfiguration = `(?mi)application/component/configuration$`
cLogging = `(?mi)application/component/logging$`
cLoggerLogrus = `(?mi)application/component/logger_logrus$`
cLogging = `(?mi)application/component/logg.*`
cLoggerConsole = `(?mi)application/component/logger_console$`
cPidfile = `(?mi)application/component/pidfile$`
cMigrations = `(?mi)application/component/migrations$`
cMigration = `(?mi)application/component/migration.*$`
)
return kitTypes.ComponentPreferences{
After: []string{
cEnvironment,
cConfiguration,
cLogging,
cLoggerLogrus,
cLoggerConsole,
cInterrupt,
cPidfile,
cMigrations,
cMigration,
},
}
}
Expand Down
101 changes: 101 additions & 0 deletions application/component/migration_sql/migration_sql.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// Package migration_sql
package migration_sql

import (
kitModuleCfg "github.com/webnice/kit/v3/module/cfg"
kitModuleCfgReg "github.com/webnice/kit/v3/module/cfg/reg"
kitModuleDbSql "github.com/webnice/kit/v3/module/db/sql"
kitTypes "github.com/webnice/kit/v3/types"
kitTypesDb "github.com/webnice/kit/v3/types/db"
)

// Структура объекта компоненты.
type impl struct {
cfg kitModuleCfg.Interface
databaseSql *kitTypesDb.DatabaseSqlConfiguration
}

// Регистрация компоненты в приложении.
func init() { kitModuleCfgReg.Registration(newComponent()) }

// Конструктор объекта компоненты.
func newComponent() kitTypes.Component {
var m8s = &impl{
cfg: kitModuleCfg.Get(),
databaseSql: new(kitTypesDb.DatabaseSqlConfiguration),
}

m8s.registrationConfigurationError(m8s.cfg.Gist().ConfigurationRegistration(m8s.databaseSql))

return m8s
}

// Ссылка на менеджер логирования, для удобного использования внутри компоненты или модуля.
func (m8s *impl) log() kitTypes.Logger { return m8s.cfg.Log() }

// Обработка ошибки регистрации конфигурации.
func (m8s *impl) registrationConfigurationError(err error) {
if err == nil {
return
}
switch eto := err.(type) {
case kitModuleCfg.Err:
m8s.cfg.Gist().ErrorAppend(eto)
default:
m8s.cfg.Gist().ErrorAppend(m8s.cfg.Errors().ConfigurationApplicationObject(0, eto))
}
}

// Preferences Функция возвращает настройки компоненты.
func (m8s *impl) Preferences() kitTypes.ComponentPreferences {
const (
cEnvironment = `(?mi)application/component/environment$`
cInterrupt = `(?mi)application/component/interrupt$`
cConfiguration = `(?mi)application/component/configuration$`
cLogging = `(?mi)application/component/logg.*`
cLoggerConsole = `(?mi)application/component/logger_console$`
cPidfile = `(?mi)application/component/pidfile$`
cBootstrap = `(?mi)application/component/bootstrap$`
)
return kitTypes.ComponentPreferences{
After: []string{cConfiguration, cLoggerConsole, cLogging, cPidfile, cInterrupt, cEnvironment},
Require: []string{cPidfile},
Before: []string{cBootstrap},
}
}

// Initiate Функция инициализации компонента и подготовки компонента к запуску.
func (m8s *impl) Initiate() (err error) {
var (
elm interface{}
ok bool
c *kitTypesDb.DatabaseSqlConfiguration
)

// Загрузка конфигурации базы данных, сохранённой в конфигурации приложения.
if elm, err = m8s.cfg.ConfigurationByObject(m8s.databaseSql); err != nil {
return
}
// Приведение пустого интерфейса к типу данных.
if c, ok = elm.(*kitTypesDb.DatabaseSqlConfiguration); ok {
// Исправление пути к миграции на абсолютный путь, исправление по адресу, поэтому все кто запросят
// конфигурацию базы данных, получат исправленный вариант.
m8s.cfg.Gist().AbsolutePathAndUpdate(&c.SqlDB.Migration)
// Обновление локальной копии конфигурации, так как после работы yaml библиотеки может слетать адрес.
m8s.databaseSql = c
}

return
}

// Do Выполнение компонента приложения.
func (m8s *impl) Do() (levelDone bool, levelExit bool, err error) {
if err = kitModuleDbSql.Get().MigrationUp(); err != nil {
levelDone, levelExit = true, true
}

return
}

// Finalize Функция вызывается перед завершением компонента и приложения в целом.
func (m8s *impl) Finalize() (err error) { return }
70 changes: 70 additions & 0 deletions module/db/sql/dsn.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// Package sql
package sql

import (
"fmt"
"strings"
)

// Создание DSN для подключения к базе данных.
func (mys *impl) makeDsn() (err error) {
const keyTcp, keySocket = `tcp`, `socket`
var (
n int
found bool
)

// Проверка конфигурации.
if mys.cfg == nil {
err = mys.Errors().ConfigurationIsEmpty(0)
return
}
// Проверка драйвера базы данных.
for n = range supportDrivers {
if strings.EqualFold(mys.cfg.Driver, supportDrivers[n]) {
mys.cfg.Driver, found = supportDrivers[n], true
break
}
}
if !found {
err = mys.Errors().UnknownDatabaseDriver(0, mys.cfg.Driver)
return
}
// Самая простая конфигурация: sqlite
if mys.cfg.Driver == driverSqlite {
mys.dsn = fmt.Sprintf("%s?%s", mys.cfg.Name, dsnTimeSettings)
return
}
// Имя пользователя.
if mys.cfg.Login == "" {
err = mys.Errors().UsernameIsEmpty(0)
return
}
// Имя пользователя и пароль можно добавлять в DSN.
mys.dsn = fmt.Sprintf("%s:%s", mys.cfg.Login, mys.cfg.Password)
// Тип подключения.
switch strings.ToLower(mys.cfg.Type) {
case keyTcp:
mys.dsn += fmt.Sprintf("@%s(%s:%d)", keyTcp, mys.cfg.Host, mys.cfg.Port)
case keySocket:
mys.dsn += fmt.Sprintf(dsnUnixTpl, mys.cfg.Socket)
default:
err = mys.Errors().WrongConnectionType(0, mys.cfg.Type)
return
}
mys.cfg.Type = strings.ToLower(mys.cfg.Type)
// Название базы данных.
mys.dsn += fmt.Sprintf("/%s", mys.cfg.Name)
// Парсинг времени.
mys.dsn += fmt.Sprintf(dsnTimeSettings, mys.cfg.ParseTime)
// Зона времени.
if mys.cfg.TimezoneLocation != "" {
mys.dsn += fmt.Sprintf(dsnLocationSettings, mys.cfg.TimezoneLocation)
}
// Кодировка соединения с базой данных.
if mys.cfg.Charset != "" {
mys.dsn += fmt.Sprintf(dsnCharsetTpl, mys.cfg.Charset)
}

return
}
77 changes: 77 additions & 0 deletions module/db/sql/errors.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// Package sql
package sql

// Обычные ошибки
const (
eConfigurationIsEmpty uint = iota + 1 // 001
eUnknownDatabaseDriver // 002
eUsernameIsEmpty // 003
eWrongConnectionType // 004
eConnectError // 005
eDriverUnImplemented // 006
eApplyMigration // 007
eUnknownDialect // 008
)

// Текстовые значения кодов ошибок на основном языке приложения.
const (
cConfigurationIsEmpty = `Конфигурация подключения к базе данных пустая.`
cUnknownDatabaseDriver = `Указан неизвестный или не поддерживаемый драйвер базы данных: ` + "%q."
cUsernameIsEmpty = `Не указано имя пользователя, для подключения к базе данных.`
cWrongConnectionType = `Указан неизвестный или не поддерживаемый способ подключения к базе данных: ` + "%q."
cConnectError = `Подключение к базе данных завершилось ошибкой: ` + "%s."
cDriverUnImplemented = `Подключение к базе данных с помощью драйвера %q не создано.`
cApplyMigration = `Применение новых миграций базы данных прервано ошибкой: ` + "%s."
cUnknownDialect = `Применение миграций базы данных, настройка диалекта %q прервано ошибкой: ` + "%s."
)

// Константы указаны в объектах, адрес которых фиксирован всё время работы приложения.
// Это позволяет сравнивать ошибки между собой используя обычное сравнение "==", но сравнивать необходимо только
// якорь "Anchor()" объекта ошибки.
var (
errSingleton = &Error{}
errConfigurationIsEmpty = err{tpl: cConfigurationIsEmpty, code: eConfigurationIsEmpty}
errUnknownDatabaseDriver = err{tpl: cUnknownDatabaseDriver, code: eUnknownDatabaseDriver}
errUsernameIsEmpty = err{tpl: cUsernameIsEmpty, code: eUsernameIsEmpty}
errWrongConnectionType = err{tpl: cWrongConnectionType, code: eWrongConnectionType}
errConnectError = err{tpl: cConnectError, code: eConnectError}
errDriverUnImplemented = err{tpl: cDriverUnImplemented, code: eDriverUnImplemented}
errApplyMigration = err{tpl: cApplyMigration, code: eApplyMigration}
errUnknownDialect = err{tpl: cUnknownDialect, code: eUnknownDialect}
)

// ERRORS: Реализация ошибок с возможностью сравнения ошибок между собой.

// ConfigurationIsEmpty Конфигурация подключения к базе данных пустая.
func (e *Error) ConfigurationIsEmpty(code uint) Err { return newErr(&errConfigurationIsEmpty, code) }

// UnknownDatabaseDriver Указан неизвестный или не поддерживаемый драйвер базы данных: ...
func (e *Error) UnknownDatabaseDriver(code uint, driver string) Err {
return newErr(&errUnknownDatabaseDriver, code, driver)
}

// UsernameIsEmpty Не указано имя пользователя, для подключения к базе данных.
func (e *Error) UsernameIsEmpty(code uint) Err { return newErr(&errUsernameIsEmpty, code) }

// WrongConnectionType Указан неизвестный или не поддерживаемый способ подключения к базе данных: ...
func (e *Error) WrongConnectionType(code uint, connType string) Err {
return newErr(&errWrongConnectionType, code, connType)
}

// ConnectError Подключение к базе данных завершилось ошибкой: ...
func (e *Error) ConnectError(code uint, err error) Err { return newErr(&errConnectError, code, err) }

// DriverUnImplemented Подключение к базе данных с помощью драйвера ... не создано.
func (e *Error) DriverUnImplemented(code uint, driver string) Err {
return newErr(&errDriverUnImplemented, code, driver)
}

// ApplyMigration Применение новых миграций базы данных прервано ошибкой: ...
func (e *Error) ApplyMigration(code uint, err error) Err {
return newErr(&errApplyMigration, code, err)
}

// UnknownDialect Применение миграций базы данных, настройка диалекта ... прервано ошибкой: ...
func (e *Error) UnknownDialect(code uint, dialect string, err error) Err {
return newErr(&errUnknownDialect, code, dialect, err)
}
26 changes: 26 additions & 0 deletions module/db/sql/gist.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Package sql
package sql

import (
"github.com/jmoiron/sqlx"
"gorm.io/gorm"
)

// Gist Возвращается настроенный и готовый к работе интерфейс подключения к базе данных.
func (db *Implementation) Gist() Interface { return db.getParent() }

// Gorm Возвращается настроенный и готовый к работе объект ORM gorm.io/gorm.
func (db *Implementation) Gorm() *gorm.DB { return db.getParent().GormDB() }

// Sqlx Настроенный и готовый к работе объект обёртки над соединением с БД github.com/jmoiron/sqlx.
func (db *Implementation) Sqlx() *sqlx.DB { return db.getParent().SqlxDB() }

// Возвращает объект родителя, с запоминанием объекта.
func (db *Implementation) getParent() Interface {
if db.parent != nil {
return db.parent
}
db.parent = Get()

return db.parent
}
59 changes: 59 additions & 0 deletions module/db/sql/logger_gorm.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// Package sql
package sql

import (
"context"
"time"

kitModuleDye "github.com/webnice/kit/v3/module/dye"
kitTypes "github.com/webnice/kit/v3/types"

gormLogger "gorm.io/gorm/logger"
)

func (mys *impl) LogMode(l gormLogger.LogLevel) gormLogger.Interface {
mys.log().Noticef("gorm уровень логирования: %d", int(l))
return mys
}

func (mys *impl) Info(_ context.Context, s string, i ...interface{}) { mys.log().Infof(s, i...) }

func (mys *impl) Warn(_ context.Context, s string, i ...interface{}) { mys.log().Warningf(s, i...) }

func (mys *impl) Error(_ context.Context, s string, i ...interface{}) { mys.log().Errorf(s, i...) }

func (mys *impl) Trace(_ context.Context, begin time.Time, fc func() (sql string, rowsAffected int64), err error) {
const (
keyQuery, keySql = `query`, `sql`
keyDriver, keyElapsed, keyRows = `driver`, `elapsed`, `rows`
tplTracef, tplErrorf = `sql:"%s"`, `sql:"%s", ошибка: %s`
)
var (
elapsed time.Duration
sql string
rows int64
keys kitTypes.LoggerKey
)

elapsed = time.Since(begin)
sql, rows = fc()
keys = kitTypes.LoggerKey{
keyQuery: keySql,
keyDriver: mys.cfg.Driver,
keyElapsed: elapsed,
keyRows: rows,
}
switch err {
case nil:
mys.log().Key(keys).Tracef(
tplTracef,
kitModuleDye.New().Yellow().Done().String()+sql+kitModuleDye.New().Normal().Done().String(),
)
default:
mys.log().Key(keys).Errorf(
tplErrorf,
kitModuleDye.New().Yellow().Done().String()+sql+kitModuleDye.New().Reset().Done().String(),
kitModuleDye.New().Red().Done().String()+err.Error()+kitModuleDye.New().Reset().Done().String(),
)
}
}
Loading

0 comments on commit f256e36

Please sign in to comment.