Skip to content

Commit f256e36

Browse files
committed
Добавлен модуль работы с подключением к SQL базе данных и миграциями.
1 parent 45294a2 commit f256e36

12 files changed

Lines changed: 866 additions & 5 deletions

File tree

application/component/bootstrap/bootstrap.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -41,20 +41,20 @@ func (brp *impl) Preferences() kitTypes.ComponentPreferences {
4141
cEnvironment = `(?mi)application/component/environment$`
4242
cInterrupt = `(?mi)application/component/interrupt$`
4343
cConfiguration = `(?mi)application/component/configuration$`
44-
cLogging = `(?mi)application/component/logging$`
45-
cLoggerLogrus = `(?mi)application/component/logger_logrus$`
44+
cLogging = `(?mi)application/component/logg.*`
45+
cLoggerConsole = `(?mi)application/component/logger_console$`
4646
cPidfile = `(?mi)application/component/pidfile$`
47-
cMigrations = `(?mi)application/component/migrations$`
47+
cMigration = `(?mi)application/component/migration.*$`
4848
)
4949
return kitTypes.ComponentPreferences{
5050
After: []string{
5151
cEnvironment,
5252
cConfiguration,
5353
cLogging,
54-
cLoggerLogrus,
54+
cLoggerConsole,
5555
cInterrupt,
5656
cPidfile,
57-
cMigrations,
57+
cMigration,
5858
},
5959
}
6060
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
// Package migration_sql
2+
package migration_sql
3+
4+
import (
5+
kitModuleCfg "github.com/webnice/kit/v3/module/cfg"
6+
kitModuleCfgReg "github.com/webnice/kit/v3/module/cfg/reg"
7+
kitModuleDbSql "github.com/webnice/kit/v3/module/db/sql"
8+
kitTypes "github.com/webnice/kit/v3/types"
9+
kitTypesDb "github.com/webnice/kit/v3/types/db"
10+
)
11+
12+
// Структура объекта компоненты.
13+
type impl struct {
14+
cfg kitModuleCfg.Interface
15+
databaseSql *kitTypesDb.DatabaseSqlConfiguration
16+
}
17+
18+
// Регистрация компоненты в приложении.
19+
func init() { kitModuleCfgReg.Registration(newComponent()) }
20+
21+
// Конструктор объекта компоненты.
22+
func newComponent() kitTypes.Component {
23+
var m8s = &impl{
24+
cfg: kitModuleCfg.Get(),
25+
databaseSql: new(kitTypesDb.DatabaseSqlConfiguration),
26+
}
27+
28+
m8s.registrationConfigurationError(m8s.cfg.Gist().ConfigurationRegistration(m8s.databaseSql))
29+
30+
return m8s
31+
}
32+
33+
// Ссылка на менеджер логирования, для удобного использования внутри компоненты или модуля.
34+
func (m8s *impl) log() kitTypes.Logger { return m8s.cfg.Log() }
35+
36+
// Обработка ошибки регистрации конфигурации.
37+
func (m8s *impl) registrationConfigurationError(err error) {
38+
if err == nil {
39+
return
40+
}
41+
switch eto := err.(type) {
42+
case kitModuleCfg.Err:
43+
m8s.cfg.Gist().ErrorAppend(eto)
44+
default:
45+
m8s.cfg.Gist().ErrorAppend(m8s.cfg.Errors().ConfigurationApplicationObject(0, eto))
46+
}
47+
}
48+
49+
// Preferences Функция возвращает настройки компоненты.
50+
func (m8s *impl) Preferences() kitTypes.ComponentPreferences {
51+
const (
52+
cEnvironment = `(?mi)application/component/environment$`
53+
cInterrupt = `(?mi)application/component/interrupt$`
54+
cConfiguration = `(?mi)application/component/configuration$`
55+
cLogging = `(?mi)application/component/logg.*`
56+
cLoggerConsole = `(?mi)application/component/logger_console$`
57+
cPidfile = `(?mi)application/component/pidfile$`
58+
cBootstrap = `(?mi)application/component/bootstrap$`
59+
)
60+
return kitTypes.ComponentPreferences{
61+
After: []string{cConfiguration, cLoggerConsole, cLogging, cPidfile, cInterrupt, cEnvironment},
62+
Require: []string{cPidfile},
63+
Before: []string{cBootstrap},
64+
}
65+
}
66+
67+
// Initiate Функция инициализации компонента и подготовки компонента к запуску.
68+
func (m8s *impl) Initiate() (err error) {
69+
var (
70+
elm interface{}
71+
ok bool
72+
c *kitTypesDb.DatabaseSqlConfiguration
73+
)
74+
75+
// Загрузка конфигурации базы данных, сохранённой в конфигурации приложения.
76+
if elm, err = m8s.cfg.ConfigurationByObject(m8s.databaseSql); err != nil {
77+
return
78+
}
79+
// Приведение пустого интерфейса к типу данных.
80+
if c, ok = elm.(*kitTypesDb.DatabaseSqlConfiguration); ok {
81+
// Исправление пути к миграции на абсолютный путь, исправление по адресу, поэтому все кто запросят
82+
// конфигурацию базы данных, получат исправленный вариант.
83+
m8s.cfg.Gist().AbsolutePathAndUpdate(&c.SqlDB.Migration)
84+
// Обновление локальной копии конфигурации, так как после работы yaml библиотеки может слетать адрес.
85+
m8s.databaseSql = c
86+
}
87+
88+
return
89+
}
90+
91+
// Do Выполнение компонента приложения.
92+
func (m8s *impl) Do() (levelDone bool, levelExit bool, err error) {
93+
if err = kitModuleDbSql.Get().MigrationUp(); err != nil {
94+
levelDone, levelExit = true, true
95+
}
96+
97+
return
98+
}
99+
100+
// Finalize Функция вызывается перед завершением компонента и приложения в целом.
101+
func (m8s *impl) Finalize() (err error) { return }

module/db/sql/dsn.go

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
// Package sql
2+
package sql
3+
4+
import (
5+
"fmt"
6+
"strings"
7+
)
8+
9+
// Создание DSN для подключения к базе данных.
10+
func (mys *impl) makeDsn() (err error) {
11+
const keyTcp, keySocket = `tcp`, `socket`
12+
var (
13+
n int
14+
found bool
15+
)
16+
17+
// Проверка конфигурации.
18+
if mys.cfg == nil {
19+
err = mys.Errors().ConfigurationIsEmpty(0)
20+
return
21+
}
22+
// Проверка драйвера базы данных.
23+
for n = range supportDrivers {
24+
if strings.EqualFold(mys.cfg.Driver, supportDrivers[n]) {
25+
mys.cfg.Driver, found = supportDrivers[n], true
26+
break
27+
}
28+
}
29+
if !found {
30+
err = mys.Errors().UnknownDatabaseDriver(0, mys.cfg.Driver)
31+
return
32+
}
33+
// Самая простая конфигурация: sqlite
34+
if mys.cfg.Driver == driverSqlite {
35+
mys.dsn = fmt.Sprintf("%s?%s", mys.cfg.Name, dsnTimeSettings)
36+
return
37+
}
38+
// Имя пользователя.
39+
if mys.cfg.Login == "" {
40+
err = mys.Errors().UsernameIsEmpty(0)
41+
return
42+
}
43+
// Имя пользователя и пароль можно добавлять в DSN.
44+
mys.dsn = fmt.Sprintf("%s:%s", mys.cfg.Login, mys.cfg.Password)
45+
// Тип подключения.
46+
switch strings.ToLower(mys.cfg.Type) {
47+
case keyTcp:
48+
mys.dsn += fmt.Sprintf("@%s(%s:%d)", keyTcp, mys.cfg.Host, mys.cfg.Port)
49+
case keySocket:
50+
mys.dsn += fmt.Sprintf(dsnUnixTpl, mys.cfg.Socket)
51+
default:
52+
err = mys.Errors().WrongConnectionType(0, mys.cfg.Type)
53+
return
54+
}
55+
mys.cfg.Type = strings.ToLower(mys.cfg.Type)
56+
// Название базы данных.
57+
mys.dsn += fmt.Sprintf("/%s", mys.cfg.Name)
58+
// Парсинг времени.
59+
mys.dsn += fmt.Sprintf(dsnTimeSettings, mys.cfg.ParseTime)
60+
// Зона времени.
61+
if mys.cfg.TimezoneLocation != "" {
62+
mys.dsn += fmt.Sprintf(dsnLocationSettings, mys.cfg.TimezoneLocation)
63+
}
64+
// Кодировка соединения с базой данных.
65+
if mys.cfg.Charset != "" {
66+
mys.dsn += fmt.Sprintf(dsnCharsetTpl, mys.cfg.Charset)
67+
}
68+
69+
return
70+
}

module/db/sql/errors.go

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
// Package sql
2+
package sql
3+
4+
// Обычные ошибки
5+
const (
6+
eConfigurationIsEmpty uint = iota + 1 // 001
7+
eUnknownDatabaseDriver // 002
8+
eUsernameIsEmpty // 003
9+
eWrongConnectionType // 004
10+
eConnectError // 005
11+
eDriverUnImplemented // 006
12+
eApplyMigration // 007
13+
eUnknownDialect // 008
14+
)
15+
16+
// Текстовые значения кодов ошибок на основном языке приложения.
17+
const (
18+
cConfigurationIsEmpty = `Конфигурация подключения к базе данных пустая.`
19+
cUnknownDatabaseDriver = `Указан неизвестный или не поддерживаемый драйвер базы данных: ` + "%q."
20+
cUsernameIsEmpty = `Не указано имя пользователя, для подключения к базе данных.`
21+
cWrongConnectionType = `Указан неизвестный или не поддерживаемый способ подключения к базе данных: ` + "%q."
22+
cConnectError = `Подключение к базе данных завершилось ошибкой: ` + "%s."
23+
cDriverUnImplemented = `Подключение к базе данных с помощью драйвера %q не создано.`
24+
cApplyMigration = `Применение новых миграций базы данных прервано ошибкой: ` + "%s."
25+
cUnknownDialect = `Применение миграций базы данных, настройка диалекта %q прервано ошибкой: ` + "%s."
26+
)
27+
28+
// Константы указаны в объектах, адрес которых фиксирован всё время работы приложения.
29+
// Это позволяет сравнивать ошибки между собой используя обычное сравнение "==", но сравнивать необходимо только
30+
// якорь "Anchor()" объекта ошибки.
31+
var (
32+
errSingleton = &Error{}
33+
errConfigurationIsEmpty = err{tpl: cConfigurationIsEmpty, code: eConfigurationIsEmpty}
34+
errUnknownDatabaseDriver = err{tpl: cUnknownDatabaseDriver, code: eUnknownDatabaseDriver}
35+
errUsernameIsEmpty = err{tpl: cUsernameIsEmpty, code: eUsernameIsEmpty}
36+
errWrongConnectionType = err{tpl: cWrongConnectionType, code: eWrongConnectionType}
37+
errConnectError = err{tpl: cConnectError, code: eConnectError}
38+
errDriverUnImplemented = err{tpl: cDriverUnImplemented, code: eDriverUnImplemented}
39+
errApplyMigration = err{tpl: cApplyMigration, code: eApplyMigration}
40+
errUnknownDialect = err{tpl: cUnknownDialect, code: eUnknownDialect}
41+
)
42+
43+
// ERRORS: Реализация ошибок с возможностью сравнения ошибок между собой.
44+
45+
// ConfigurationIsEmpty Конфигурация подключения к базе данных пустая.
46+
func (e *Error) ConfigurationIsEmpty(code uint) Err { return newErr(&errConfigurationIsEmpty, code) }
47+
48+
// UnknownDatabaseDriver Указан неизвестный или не поддерживаемый драйвер базы данных: ...
49+
func (e *Error) UnknownDatabaseDriver(code uint, driver string) Err {
50+
return newErr(&errUnknownDatabaseDriver, code, driver)
51+
}
52+
53+
// UsernameIsEmpty Не указано имя пользователя, для подключения к базе данных.
54+
func (e *Error) UsernameIsEmpty(code uint) Err { return newErr(&errUsernameIsEmpty, code) }
55+
56+
// WrongConnectionType Указан неизвестный или не поддерживаемый способ подключения к базе данных: ...
57+
func (e *Error) WrongConnectionType(code uint, connType string) Err {
58+
return newErr(&errWrongConnectionType, code, connType)
59+
}
60+
61+
// ConnectError Подключение к базе данных завершилось ошибкой: ...
62+
func (e *Error) ConnectError(code uint, err error) Err { return newErr(&errConnectError, code, err) }
63+
64+
// DriverUnImplemented Подключение к базе данных с помощью драйвера ... не создано.
65+
func (e *Error) DriverUnImplemented(code uint, driver string) Err {
66+
return newErr(&errDriverUnImplemented, code, driver)
67+
}
68+
69+
// ApplyMigration Применение новых миграций базы данных прервано ошибкой: ...
70+
func (e *Error) ApplyMigration(code uint, err error) Err {
71+
return newErr(&errApplyMigration, code, err)
72+
}
73+
74+
// UnknownDialect Применение миграций базы данных, настройка диалекта ... прервано ошибкой: ...
75+
func (e *Error) UnknownDialect(code uint, dialect string, err error) Err {
76+
return newErr(&errUnknownDialect, code, dialect, err)
77+
}

module/db/sql/gist.go

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
// Package sql
2+
package sql
3+
4+
import (
5+
"github.com/jmoiron/sqlx"
6+
"gorm.io/gorm"
7+
)
8+
9+
// Gist Возвращается настроенный и готовый к работе интерфейс подключения к базе данных.
10+
func (db *Implementation) Gist() Interface { return db.getParent() }
11+
12+
// Gorm Возвращается настроенный и готовый к работе объект ORM gorm.io/gorm.
13+
func (db *Implementation) Gorm() *gorm.DB { return db.getParent().GormDB() }
14+
15+
// Sqlx Настроенный и готовый к работе объект обёртки над соединением с БД github.com/jmoiron/sqlx.
16+
func (db *Implementation) Sqlx() *sqlx.DB { return db.getParent().SqlxDB() }
17+
18+
// Возвращает объект родителя, с запоминанием объекта.
19+
func (db *Implementation) getParent() Interface {
20+
if db.parent != nil {
21+
return db.parent
22+
}
23+
db.parent = Get()
24+
25+
return db.parent
26+
}

module/db/sql/logger_gorm.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// Package sql
2+
package sql
3+
4+
import (
5+
"context"
6+
"time"
7+
8+
kitModuleDye "github.com/webnice/kit/v3/module/dye"
9+
kitTypes "github.com/webnice/kit/v3/types"
10+
11+
gormLogger "gorm.io/gorm/logger"
12+
)
13+
14+
func (mys *impl) LogMode(l gormLogger.LogLevel) gormLogger.Interface {
15+
mys.log().Noticef("gorm уровень логирования: %d", int(l))
16+
return mys
17+
}
18+
19+
func (mys *impl) Info(_ context.Context, s string, i ...interface{}) { mys.log().Infof(s, i...) }
20+
21+
func (mys *impl) Warn(_ context.Context, s string, i ...interface{}) { mys.log().Warningf(s, i...) }
22+
23+
func (mys *impl) Error(_ context.Context, s string, i ...interface{}) { mys.log().Errorf(s, i...) }
24+
25+
func (mys *impl) Trace(_ context.Context, begin time.Time, fc func() (sql string, rowsAffected int64), err error) {
26+
const (
27+
keyQuery, keySql = `query`, `sql`
28+
keyDriver, keyElapsed, keyRows = `driver`, `elapsed`, `rows`
29+
tplTracef, tplErrorf = `sql:"%s"`, `sql:"%s", ошибка: %s`
30+
)
31+
var (
32+
elapsed time.Duration
33+
sql string
34+
rows int64
35+
keys kitTypes.LoggerKey
36+
)
37+
38+
elapsed = time.Since(begin)
39+
sql, rows = fc()
40+
keys = kitTypes.LoggerKey{
41+
keyQuery: keySql,
42+
keyDriver: mys.cfg.Driver,
43+
keyElapsed: elapsed,
44+
keyRows: rows,
45+
}
46+
switch err {
47+
case nil:
48+
mys.log().Key(keys).Tracef(
49+
tplTracef,
50+
kitModuleDye.New().Yellow().Done().String()+sql+kitModuleDye.New().Normal().Done().String(),
51+
)
52+
default:
53+
mys.log().Key(keys).Errorf(
54+
tplErrorf,
55+
kitModuleDye.New().Yellow().Done().String()+sql+kitModuleDye.New().Reset().Done().String(),
56+
kitModuleDye.New().Red().Done().String()+err.Error()+kitModuleDye.New().Reset().Done().String(),
57+
)
58+
}
59+
}

0 commit comments

Comments
 (0)