common.go 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143
  1. package common
  2. import (
  3. "context"
  4. "database/sql"
  5. "fmt"
  6. "io"
  7. "net/url"
  8. "os"
  9. "regexp"
  10. "strings"
  11. "time"
  12. "github.com/amacneil/dbmate/pkg/dbmate"
  13. _ "github.com/amacneil/dbmate/pkg/driver/mysql"
  14. _ "github.com/amacneil/dbmate/pkg/driver/postgres"
  15. _ "github.com/amacneil/dbmate/pkg/driver/sqlite"
  16. "golang.org/x/exp/slices"
  17. )
  18. type Engine interface {
  19. Begin(ctx context.Context, opts *sql.TxOptions) (*Tx, error)
  20. Close() error
  21. Exec(ctx context.Context, query string, args ...any) (sql.Result, error)
  22. Ping(context.Context) error
  23. Prepare(ctx context.Context, query string) (*sql.Stmt, error)
  24. Query(ctx context.Context, query string, args ...any) (*sql.Rows, error)
  25. QueryRow(ctx context.Context, query string, args ...any) *sql.Row
  26. Transaction(ctx context.Context, queries func(ctx context.Context, tx *Tx) error) error
  27. }
  28. var rLogSpacesAll = regexp.MustCompile(`[\s\t]+`)
  29. var rLogSpacesEnd = regexp.MustCompile(`[\s\t]+;$`)
  30. var rSqlParam = regexp.MustCompile(`\$\d+`)
  31. func log(w io.Writer, fname string, start time.Time, err error, tx bool, query string, args ...any) string {
  32. var values []string
  33. bold := "0"
  34. color := "33"
  35. // Transaction or not
  36. if tx {
  37. bold = "1"
  38. values = append(values, "[TX]")
  39. }
  40. // Function name
  41. if fname != "" {
  42. values = append(values, fname)
  43. }
  44. // SQL query
  45. if query != "" {
  46. values = append(values, rLogSpacesEnd.ReplaceAllString(
  47. strings.Trim(rLogSpacesAll.ReplaceAllString(query, " "), " "), ";",
  48. ))
  49. }
  50. // Params
  51. if len(args) > 0 {
  52. values = append(values, fmt.Sprintf("(%v)", args))
  53. } else {
  54. values = append(values, "(empty)")
  55. }
  56. // Error
  57. if err != nil {
  58. color = "31"
  59. values = append(values, "("+err.Error()+")")
  60. } else {
  61. values = append(values, "(nil)")
  62. }
  63. // Execute time with close color symbols
  64. values = append(values, fmt.Sprintf("%.3f ms\033[0m", time.Since(start).Seconds()))
  65. // Prepend start caption with colors
  66. values = append([]string{"\033[" + bold + ";" + color + "m[SQL]"}, values...)
  67. res := fmt.Sprintln(strings.Join(values, " "))
  68. fmt.Fprint(w, res)
  69. return res
  70. }
  71. func fixQuery(query string) string {
  72. return rSqlParam.ReplaceAllString(query, "?")
  73. }
  74. func ParseUrl(dbURL string) (*url.URL, error) {
  75. databaseURL, err := url.Parse(dbURL)
  76. if err != nil {
  77. return nil, fmt.Errorf("unable to parse URL: %w", err)
  78. }
  79. if databaseURL.Scheme == "" {
  80. return nil, fmt.Errorf("protocol scheme is not defined")
  81. }
  82. protocols := []string{"mysql", "postgres", "postgresql", "sqlite", "sqlite3"}
  83. if !slices.Contains(protocols, databaseURL.Scheme) {
  84. return nil, fmt.Errorf("unsupported protocol scheme: %s", databaseURL.Scheme)
  85. }
  86. return databaseURL, nil
  87. }
  88. func OpenDB(databaseURL *url.URL, migrationsDir string, debug bool) (*sql.DB, error) {
  89. mate := dbmate.New(databaseURL)
  90. mate.AutoDumpSchema = false
  91. mate.Log = io.Discard
  92. if migrationsDir != "" {
  93. mate.MigrationsDir = migrationsDir
  94. }
  95. driver, err := mate.GetDriver()
  96. if err != nil {
  97. return nil, fmt.Errorf("DB get driver error: %w", err)
  98. }
  99. if err := mate.CreateAndMigrate(); err != nil {
  100. return nil, fmt.Errorf("DB migration error: %w", err)
  101. }
  102. var db *sql.DB
  103. if debug {
  104. t := time.Now()
  105. db, err = driver.Open()
  106. log(os.Stdout, "[func Open]", t, err, false, "")
  107. if err != nil {
  108. return nil, fmt.Errorf("DB open error: %w", err)
  109. }
  110. } else {
  111. db, err = driver.Open()
  112. if err != nil {
  113. return nil, fmt.Errorf("DB open error: %w", err)
  114. }
  115. }
  116. return db, nil
  117. }