mysql
This commit is contained in:
@@ -7,10 +7,77 @@ import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
_ "modernc.org/sqlite"
|
||||
)
|
||||
|
||||
// MySQLConfig targets a shared MySQL database for multi-operator logging
|
||||
// (multiple OpsLog instances on one logbook, à la Log4OM).
|
||||
type MySQLConfig struct {
|
||||
Host string
|
||||
Port int
|
||||
User string
|
||||
Password string
|
||||
Database string
|
||||
}
|
||||
|
||||
func (c MySQLConfig) dsn() string {
|
||||
port := c.Port
|
||||
if port == 0 {
|
||||
port = 3306
|
||||
}
|
||||
// parseTime + UTC so DATETIME columns scan into time.Time; utf8mb4 for full
|
||||
// Unicode (names, comments…).
|
||||
return fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?parseTime=true&loc=UTC&charset=utf8mb4",
|
||||
c.User, c.Password, c.Host, port, c.Database)
|
||||
}
|
||||
|
||||
// validDBIdent guards a database name we splice into DDL (CREATE DATABASE can't
|
||||
// use a placeholder). Only plain identifiers allowed.
|
||||
func validDBIdent(s string) bool {
|
||||
if s == "" {
|
||||
return false
|
||||
}
|
||||
for _, r := range s {
|
||||
if r != '_' && !(r >= 'a' && r <= 'z') && !(r >= 'A' && r <= 'Z') && !(r >= '0' && r <= '9') {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// PingMySQL verifies a shared-database connection and creates the logbook
|
||||
// database if it doesn't exist yet. It connects at server level first (no
|
||||
// database selected) so a not-yet-created DB isn't an error, then runs
|
||||
// CREATE DATABASE IF NOT EXISTS. Backs the settings "Test connection" button.
|
||||
func PingMySQL(c MySQLConfig) error {
|
||||
if strings.TrimSpace(c.Host) == "" {
|
||||
return fmt.Errorf("host is required")
|
||||
}
|
||||
server := c
|
||||
server.Database = "" // connect to the server, not a specific DB
|
||||
conn, err := sql.Open("mysql", server.dsn())
|
||||
if err != nil {
|
||||
return fmt.Errorf("open mysql: %w", err)
|
||||
}
|
||||
defer conn.Close()
|
||||
conn.SetConnMaxLifetime(5 * time.Second)
|
||||
if err := conn.Ping(); err != nil {
|
||||
return fmt.Errorf("connect to %s:%d: %w", c.Host, c.Port, err)
|
||||
}
|
||||
if name := strings.TrimSpace(c.Database); name != "" {
|
||||
if !validDBIdent(name) {
|
||||
return fmt.Errorf("invalid database name %q (letters, digits, underscore only)", name)
|
||||
}
|
||||
if _, err := conn.Exec("CREATE DATABASE IF NOT EXISTS `" + name + "` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci"); err != nil {
|
||||
return fmt.Errorf("create database %q: %w", name, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//go:embed migrations/*.sql
|
||||
var migrationsFS embed.FS
|
||||
|
||||
|
||||
Reference in New Issue
Block a user