2023-08-18 23:59:15 +03:00
|
|
|
package database
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"fmt"
|
|
|
|
"net/url"
|
|
|
|
"strconv"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/golang-migrate/migrate/v4"
|
|
|
|
"github.com/golang-migrate/migrate/v4/database"
|
|
|
|
mpgx "github.com/golang-migrate/migrate/v4/database/pgx"
|
|
|
|
msqlite "github.com/golang-migrate/migrate/v4/database/sqlite"
|
|
|
|
"github.com/golang-migrate/migrate/v4/source/iofs"
|
2024-12-07 02:35:30 +03:00
|
|
|
"github.com/jackc/pgx/v5"
|
|
|
|
"github.com/jackc/pgx/v5/stdlib"
|
2023-08-18 23:59:15 +03:00
|
|
|
"github.com/jmoiron/sqlx"
|
2024-12-07 02:35:30 +03:00
|
|
|
"go.unistack.org/micro/v3/logger"
|
|
|
|
appconfig "go.unistack.org/pkgdash/internal/config"
|
2023-08-18 23:59:15 +03:00
|
|
|
_ "modernc.org/sqlite"
|
|
|
|
)
|
|
|
|
|
|
|
|
func ParseDSN(cfg *appconfig.DatabaseConfig) error {
|
|
|
|
var err error
|
|
|
|
|
|
|
|
u, err := url.Parse(cfg.DSN)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
values := u.Query()
|
|
|
|
var value string
|
|
|
|
|
|
|
|
if value = values.Get("conn_max"); value != "" {
|
|
|
|
values.Del("conn_max")
|
|
|
|
maxOpenConns, err := strconv.Atoi(value)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
cfg.MaxOpenConns = maxOpenConns
|
|
|
|
cfg.MaxIdleConns = maxOpenConns / 2
|
|
|
|
}
|
|
|
|
|
|
|
|
if value = values.Get("conn_maxidle"); value != "" {
|
|
|
|
values.Del("conn_maxidle")
|
|
|
|
maxIdleConns, err := strconv.Atoi(value)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
cfg.MaxIdleConns = maxIdleConns
|
|
|
|
}
|
|
|
|
|
|
|
|
if value = values.Get("conn_lifetime"); value != "" {
|
|
|
|
values.Del("conn_lifetime")
|
|
|
|
connMaxLifetime, err := time.ParseDuration(value)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
cfg.ConnMaxLifetime = connMaxLifetime
|
|
|
|
}
|
|
|
|
|
|
|
|
if value = values.Get("conn_maxidletime"); value != "" {
|
|
|
|
values.Del("conn_maxidletime")
|
|
|
|
connMaxIdleTime, err := time.ParseDuration(value)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
cfg.ConnMaxIdleTime = connMaxIdleTime
|
|
|
|
}
|
|
|
|
|
|
|
|
if mtype := values.Get("migrate"); mtype != "" {
|
|
|
|
values.Del("migrate")
|
|
|
|
cfg.Migrate = mtype
|
|
|
|
}
|
|
|
|
|
|
|
|
switch u.Scheme {
|
|
|
|
case "postgres", "pgsql", "postgresql":
|
|
|
|
u.Scheme = "postgres"
|
|
|
|
case "sqlite", "sqlite3":
|
|
|
|
u.Scheme = "sqlite"
|
|
|
|
default:
|
2024-04-03 17:29:57 +03:00
|
|
|
return fmt.Errorf("unknown database %s", u.Scheme)
|
2023-08-18 23:59:15 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
cfg.Type = u.Scheme
|
|
|
|
u.RawQuery = values.Encode()
|
|
|
|
|
|
|
|
cfg.ConnStr = u.String()
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func connect(ctx context.Context, cfg *appconfig.DatabaseConfig, log logger.Logger) (*sqlx.DB, error) {
|
|
|
|
var db *sqlx.DB
|
|
|
|
var err error
|
|
|
|
|
2024-03-24 20:52:32 +03:00
|
|
|
log.Info(ctx, "connect to %s", cfg.Type)
|
2023-08-18 23:59:15 +03:00
|
|
|
switch cfg.Type {
|
|
|
|
case "postgres", "pgsql", "postgresql":
|
|
|
|
db, err = connectPostgres(ctx, cfg.ConnStr)
|
|
|
|
cfg.Type = "postgres"
|
|
|
|
case "sqlite", "sqlite3":
|
|
|
|
db, err = connectSqlite(ctx, cfg.ConnStr)
|
|
|
|
cfg.Type = "sqlite"
|
|
|
|
default:
|
2024-04-03 17:29:57 +03:00
|
|
|
return nil, fmt.Errorf("unknown database type %s", cfg.Type)
|
2023-08-18 23:59:15 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return db, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func Connect(ctx context.Context, cfg *appconfig.DatabaseConfig, log logger.Logger) (*sqlx.DB, error) {
|
|
|
|
db, err := connect(ctx, cfg, log)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
m, err := migratePrepare(ctx, db, log, cfg.Type)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
switch cfg.Migrate {
|
|
|
|
case "":
|
|
|
|
break
|
|
|
|
case "up":
|
2024-12-07 02:35:30 +03:00
|
|
|
log.Info(ctx, "migrate up")
|
2023-08-18 23:59:15 +03:00
|
|
|
err = m.Up()
|
|
|
|
case "down":
|
2024-12-07 02:35:30 +03:00
|
|
|
log.Info(ctx, "migrate down")
|
2023-08-18 23:59:15 +03:00
|
|
|
err = m.Down()
|
|
|
|
case "seed":
|
2024-12-07 02:35:30 +03:00
|
|
|
log.Info(ctx, "migrate seed")
|
2023-08-18 23:59:15 +03:00
|
|
|
if err = m.Drop(); err == nil {
|
|
|
|
err = m.Up()
|
|
|
|
}
|
|
|
|
default:
|
2024-12-07 02:35:30 +03:00
|
|
|
log.Info(ctx, "migrate version")
|
2023-08-18 23:59:15 +03:00
|
|
|
v, verr := strconv.ParseUint(cfg.Type, 10, 64)
|
|
|
|
if verr != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
err = m.Migrate(uint(v))
|
|
|
|
}
|
|
|
|
|
|
|
|
if err == nil || err == migrate.ErrNoChange {
|
|
|
|
srcerr, dberr := m.Close()
|
|
|
|
if srcerr != nil {
|
|
|
|
err = srcerr
|
|
|
|
} else if dberr != nil {
|
|
|
|
err = dberr
|
|
|
|
} else {
|
|
|
|
err = nil
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if err == nil {
|
|
|
|
db, err = connect(ctx, cfg, log)
|
|
|
|
}
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
db.SetConnMaxIdleTime(cfg.ConnMaxIdleTime)
|
|
|
|
db.SetConnMaxLifetime(cfg.ConnMaxLifetime)
|
|
|
|
db.SetMaxIdleConns(cfg.MaxIdleConns)
|
|
|
|
db.SetMaxOpenConns(cfg.MaxOpenConns)
|
|
|
|
|
|
|
|
return db, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func connectSqlite(ctx context.Context, connstr string) (*sqlx.DB, error) {
|
|
|
|
if !strings.Contains(connstr, ":memory:") {
|
|
|
|
return sqlx.ConnectContext(ctx, "sqlite", "file:"+connstr[9:])
|
|
|
|
}
|
|
|
|
return sqlx.ConnectContext(ctx, "sqlite", connstr[9:])
|
|
|
|
}
|
|
|
|
|
|
|
|
func connectPostgres(ctx context.Context, connstr string) (*sqlx.DB, error) {
|
|
|
|
// parse connection string
|
|
|
|
dbConf, err := pgx.ParseConfig(connstr)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
// needed for pgbouncer
|
|
|
|
dbConf.RuntimeParams = map[string]string{
|
|
|
|
"standard_conforming_strings": "on",
|
|
|
|
"application_name": "authn",
|
|
|
|
}
|
|
|
|
|
|
|
|
// may be needed for pbbouncer, needs to check
|
|
|
|
// dbConf.PreferSimpleProtocol = true
|
|
|
|
// register pgx conn
|
|
|
|
connStr := stdlib.RegisterConnConfig(dbConf)
|
|
|
|
|
|
|
|
db, err := sqlx.ConnectContext(ctx, "pgx", connStr)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return db, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func migratePrepare(ctx context.Context, db *sqlx.DB, log logger.Logger, dbtype string) (*migrate.Migrate, error) {
|
|
|
|
var driver database.Driver
|
|
|
|
var err error
|
|
|
|
|
|
|
|
switch dbtype {
|
|
|
|
case "postgres":
|
|
|
|
driver, err = mpgx.WithInstance(db.DB, &mpgx.Config{
|
|
|
|
DatabaseName: "pkgdash",
|
|
|
|
MigrationsTable: "schema_migrations",
|
|
|
|
})
|
|
|
|
case "sqlite":
|
|
|
|
driver, err = msqlite.WithInstance(db.DB, &msqlite.Config{
|
|
|
|
DatabaseName: "pkgdash",
|
|
|
|
MigrationsTable: "schema_migrations",
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
source, err := iofs.New(assets, "migrations/"+dbtype)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
m, err := migrate.NewWithInstance("fs", source, "apigw", driver)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
m.Log = &mLog{ctx: ctx, l: log}
|
|
|
|
return m, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
type mLog struct {
|
|
|
|
ctx context.Context
|
|
|
|
l logger.Logger
|
|
|
|
}
|
|
|
|
|
|
|
|
func (l *mLog) Verbose() bool {
|
|
|
|
return l.l.V(logger.DebugLevel)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (l *mLog) Printf(format string, v ...interface{}) {
|
2024-03-24 20:52:32 +03:00
|
|
|
l.l.Info(l.ctx, format, v...)
|
2023-08-18 23:59:15 +03:00
|
|
|
}
|