fork github.com/doug-martin
This commit is contained in:
@@ -0,0 +1,89 @@
|
||||
package engine
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
|
||||
"github.com/lib/pq"
|
||||
|
||||
_ "github.com/denisenkom/go-mssqldb"
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
|
||||
_ "git.fsdpf.net/go/db/v2/dialect/mysql"
|
||||
_ "git.fsdpf.net/go/db/v2/dialect/postgres"
|
||||
_ "git.fsdpf.net/go/db/v2/dialect/sqlite3"
|
||||
_ "git.fsdpf.net/go/db/v2/dialect/sqlserver"
|
||||
|
||||
"git.fsdpf.net/go/db/v2"
|
||||
"git.fsdpf.net/go/db/v2/internal/errors"
|
||||
)
|
||||
|
||||
type Engine struct {
|
||||
configs map[string]DBConfig
|
||||
dbs map[string]*sql.DB
|
||||
}
|
||||
|
||||
var _engine *Engine
|
||||
|
||||
func init() {
|
||||
_engine = &Engine{
|
||||
configs: make(map[string]DBConfig),
|
||||
dbs: make(map[string]*sql.DB),
|
||||
}
|
||||
}
|
||||
|
||||
func (e Engine) Connection(name string) *db.Database {
|
||||
cfg, ok := e.configs[name]
|
||||
|
||||
if !ok {
|
||||
panic(errors.New(fmt.Sprintf("Database connection %s not configured.", name)))
|
||||
}
|
||||
|
||||
_db, ok := e.dbs[name]
|
||||
|
||||
if !ok {
|
||||
_db = e.MakeConnection(cfg)
|
||||
e.dbs[name] = _db
|
||||
}
|
||||
|
||||
return db.New(cfg.Driver, _db)
|
||||
}
|
||||
|
||||
func (e Engine) MakeConnection(cfg DBConfig) *sql.DB {
|
||||
dsn := cfg.ToDSN()
|
||||
|
||||
switch cfg.Driver {
|
||||
case "mysql":
|
||||
case "sqlite3":
|
||||
case "sqlserver":
|
||||
case "postgres":
|
||||
if url, err := pq.ParseURL(dsn); err == nil {
|
||||
dsn = url
|
||||
} else {
|
||||
panic(err)
|
||||
}
|
||||
default:
|
||||
panic(fmt.Sprintf("Unsupported driver: %s", cfg.Driver))
|
||||
}
|
||||
|
||||
db, err := sql.Open(cfg.Driver, dsn)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if err := db.Ping(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return db
|
||||
}
|
||||
|
||||
func Open(cfgs map[string]DBConfig) *Engine {
|
||||
for n, cfg := range cfgs {
|
||||
_engine.configs[n] = cfg
|
||||
}
|
||||
|
||||
return _engine
|
||||
}
|
||||
@@ -0,0 +1,399 @@
|
||||
package engine
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"time"
|
||||
)
|
||||
|
||||
// DBConfig 数据库配置结构体
|
||||
type DBConfig struct {
|
||||
Driver string
|
||||
Host string
|
||||
Port string
|
||||
Database string
|
||||
Username string
|
||||
Password string
|
||||
Charset string
|
||||
Prefix string
|
||||
ConnMaxLifetime time.Duration
|
||||
ConnMaxIdleTime time.Duration
|
||||
MaxIdleConns int
|
||||
MaxOpenConns int
|
||||
ParseTime bool
|
||||
EnableLog bool
|
||||
ReadHosts []string
|
||||
WriteHosts []string
|
||||
|
||||
MySQL struct {
|
||||
Dsn string
|
||||
Collation string
|
||||
UnixSocket string
|
||||
MultiStatements bool
|
||||
}
|
||||
|
||||
PostgreSQL struct {
|
||||
Sslmode string
|
||||
TLS string
|
||||
SearchPath string
|
||||
ConnectTimeout int
|
||||
ApplicationName string
|
||||
}
|
||||
|
||||
SQLite struct {
|
||||
File string
|
||||
Journal string
|
||||
Locking string
|
||||
Mode string
|
||||
Synchronous int
|
||||
Cache string
|
||||
BusyTimeout int
|
||||
}
|
||||
|
||||
SQLServer struct {
|
||||
Instance string
|
||||
Encrypt string
|
||||
TrustServerCert bool
|
||||
AppName string
|
||||
FailoverPartner string
|
||||
PacketSize int
|
||||
WorkstationID string
|
||||
ConnectionTimeout int
|
||||
KeepAlive int
|
||||
Dsn string
|
||||
}
|
||||
}
|
||||
|
||||
// Option 配置选项类型
|
||||
type Option func(*DBConfig)
|
||||
|
||||
// ToDSN 生成对应驱动的 DSN
|
||||
func (c *DBConfig) ToDSN() string {
|
||||
switch c.Driver {
|
||||
case "mysql":
|
||||
return c.toMySQLDSN()
|
||||
case "pgsql":
|
||||
return c.toPostgreSQLDSN()
|
||||
case "sqlite3":
|
||||
return c.toSQLiteDSN()
|
||||
case "sqlserver":
|
||||
return c.toSQLServerDSN()
|
||||
default:
|
||||
panic(fmt.Sprintf("Unsupported driver for DSN generation: %s", c.Driver))
|
||||
}
|
||||
}
|
||||
|
||||
// toMySQLDSN 生成 MySQL DSN
|
||||
func (c *DBConfig) toMySQLDSN() string {
|
||||
if c.MySQL.Dsn != "" {
|
||||
return c.MySQL.Dsn
|
||||
}
|
||||
var dsn string
|
||||
if c.Username != "" {
|
||||
if c.Password != "" {
|
||||
dsn = fmt.Sprintf("%s:%s@", c.Username, c.Password)
|
||||
} else {
|
||||
dsn = fmt.Sprintf("%s@", c.Username)
|
||||
}
|
||||
}
|
||||
if c.MySQL.UnixSocket != "" {
|
||||
dsn += fmt.Sprintf("unix(%s)", c.MySQL.UnixSocket)
|
||||
} else {
|
||||
host := c.Host
|
||||
if host == "" && len(c.WriteHosts) > 0 {
|
||||
host = c.WriteHosts[0]
|
||||
}
|
||||
if host == "" && len(c.ReadHosts) > 0 {
|
||||
host = c.ReadHosts[0]
|
||||
}
|
||||
if c.Port == "" {
|
||||
c.Port = "3306"
|
||||
}
|
||||
dsn += fmt.Sprintf("tcp(%s:%s)", host, c.Port)
|
||||
}
|
||||
dsn += fmt.Sprintf("/%s", c.Database)
|
||||
params := url.Values{}
|
||||
if c.Charset != "" {
|
||||
params.Add("charset", c.Charset)
|
||||
}
|
||||
if c.ParseTime {
|
||||
params.Add("parseTime", "true")
|
||||
}
|
||||
if c.MySQL.Collation != "" {
|
||||
params.Add("collation", c.MySQL.Collation)
|
||||
}
|
||||
if c.MySQL.MultiStatements {
|
||||
params.Add("multiStatements", "true")
|
||||
}
|
||||
if len(params) > 0 {
|
||||
dsn += "?" + params.Encode()
|
||||
}
|
||||
return dsn
|
||||
}
|
||||
|
||||
// toPostgreSQLDSN 生成 PostgreSQL DSN
|
||||
func (c *DBConfig) toPostgreSQLDSN() string {
|
||||
params := url.Values{}
|
||||
if c.Host != "" {
|
||||
params.Add("host", c.Host)
|
||||
} else if len(c.WriteHosts) > 0 {
|
||||
params.Add("host", c.WriteHosts[0])
|
||||
} else if len(c.ReadHosts) > 0 {
|
||||
params.Add("host", c.ReadHosts[0])
|
||||
}
|
||||
if c.Port == "" {
|
||||
c.Port = "5432" // 默认 PostgreSQL 端口
|
||||
}
|
||||
params.Add("port", c.Port)
|
||||
params.Add("dbname", c.Database)
|
||||
params.Add("user", c.Username)
|
||||
if c.Password != "" {
|
||||
params.Add("password", c.Password)
|
||||
}
|
||||
if c.PostgreSQL.Sslmode != "" {
|
||||
params.Add("sslmode", c.PostgreSQL.Sslmode)
|
||||
}
|
||||
if c.PostgreSQL.ConnectTimeout > 0 {
|
||||
params.Add("connect_timeout", fmt.Sprintf("%d", c.PostgreSQL.ConnectTimeout))
|
||||
}
|
||||
if c.PostgreSQL.ApplicationName != "" {
|
||||
params.Add("application_name", c.PostgreSQL.ApplicationName)
|
||||
}
|
||||
return "postgres://" + c.Username + ":" + url.QueryEscape(c.Password) + "@" + params.Get("host") + ":" + c.Port + "/" + c.Database + "?" + params.Encode()
|
||||
}
|
||||
|
||||
// toSQLiteDSN 生成 SQLite DSN
|
||||
func (c *DBConfig) toSQLiteDSN() string {
|
||||
if c.SQLite.File == "" {
|
||||
return ""
|
||||
}
|
||||
dsn := c.SQLite.File
|
||||
params := url.Values{}
|
||||
if c.SQLite.Journal != "" {
|
||||
params.Add("_journal", c.SQLite.Journal)
|
||||
}
|
||||
if c.SQLite.Locking != "" {
|
||||
params.Add("_locking", c.SQLite.Locking)
|
||||
}
|
||||
if c.SQLite.Mode != "" {
|
||||
params.Add("_mode", c.SQLite.Mode)
|
||||
}
|
||||
if c.SQLite.Synchronous > 0 {
|
||||
params.Add("_synchronous", fmt.Sprintf("%d", c.SQLite.Synchronous))
|
||||
}
|
||||
if c.SQLite.Cache != "" {
|
||||
params.Add("_cache", c.SQLite.Cache)
|
||||
}
|
||||
if c.SQLite.BusyTimeout > 0 {
|
||||
params.Add("_busy_timeout", fmt.Sprintf("%d", c.SQLite.BusyTimeout))
|
||||
}
|
||||
if len(params) > 0 {
|
||||
dsn += "?" + params.Encode()
|
||||
}
|
||||
return dsn
|
||||
}
|
||||
|
||||
// toSQLServerDSN 生成 SQL Server DSN
|
||||
func (c *DBConfig) toSQLServerDSN() string {
|
||||
if c.SQLServer.Dsn != "" {
|
||||
return c.SQLServer.Dsn
|
||||
}
|
||||
host := c.Host
|
||||
if host == "" && len(c.WriteHosts) > 0 {
|
||||
host = c.WriteHosts[0]
|
||||
}
|
||||
if host == "" && len(c.ReadHosts) > 0 {
|
||||
host = c.ReadHosts[0]
|
||||
}
|
||||
if c.Port == "" {
|
||||
c.Port = "1433" // 默认 SQL Server 端口
|
||||
}
|
||||
if c.SQLServer.Instance != "" {
|
||||
host += "\\" + c.SQLServer.Instance
|
||||
}
|
||||
params := url.Values{}
|
||||
params.Add("server", host)
|
||||
params.Add("database", c.Database)
|
||||
if c.SQLServer.Encrypt != "" {
|
||||
params.Add("encrypt", c.SQLServer.Encrypt)
|
||||
}
|
||||
if c.SQLServer.TrustServerCert {
|
||||
params.Add("TrustServerCertificate", "true")
|
||||
}
|
||||
if c.SQLServer.AppName != "" {
|
||||
params.Add("app name", c.SQLServer.AppName)
|
||||
}
|
||||
if c.SQLServer.ConnectionTimeout > 0 {
|
||||
params.Add("connection timeout", fmt.Sprintf("%d", c.SQLServer.ConnectionTimeout))
|
||||
}
|
||||
return "sqlserver://" + c.Username + ":" + url.QueryEscape(c.Password) + "@" + host + ":" + c.Port + "?" + params.Encode()
|
||||
}
|
||||
|
||||
// NewDBConfig 创建新的 DBConfig 实例,检查关键参数
|
||||
func NewDBConfig(driver string, options ...Option) DBConfig {
|
||||
if driver == "" {
|
||||
panic("Driver is required and cannot be empty")
|
||||
}
|
||||
|
||||
// 支持的驱动类型
|
||||
validDrivers := map[string]bool{
|
||||
"mysql": true,
|
||||
"pgsql": true,
|
||||
"sqlite3": true,
|
||||
"sqlserver": true,
|
||||
}
|
||||
if !validDrivers[driver] {
|
||||
panic(fmt.Sprintf("Unsupported driver: %s", driver))
|
||||
}
|
||||
|
||||
// 初始化配置
|
||||
config := &DBConfig{
|
||||
Driver: driver,
|
||||
Charset: "utf8mb4",
|
||||
ConnMaxLifetime: 2 * time.Hour,
|
||||
ConnMaxIdleTime: 30 * time.Minute,
|
||||
MaxIdleConns: 10,
|
||||
MaxOpenConns: 100,
|
||||
}
|
||||
|
||||
// 应用所有选项
|
||||
for _, opt := range options {
|
||||
opt(config)
|
||||
}
|
||||
|
||||
// 检查关键参数
|
||||
switch config.Driver {
|
||||
case "mysql", "pgsql", "sqlserver":
|
||||
if config.Host == "" && len(config.ReadHosts) == 0 && len(config.WriteHosts) == 0 {
|
||||
panic(fmt.Sprintf("Host, ReadHosts, or WriteHosts is required for %s driver", config.Driver))
|
||||
}
|
||||
if config.Database == "" {
|
||||
panic(fmt.Sprintf("Database is required for %s driver", config.Driver))
|
||||
}
|
||||
if config.Username == "" {
|
||||
panic(fmt.Sprintf("Username is required for %s driver", config.Driver))
|
||||
}
|
||||
if config.Password == "" {
|
||||
panic(fmt.Sprintf("Password is required for %s driver", config.Driver))
|
||||
}
|
||||
case "sqlite3":
|
||||
if config.SQLite.File == "" {
|
||||
panic("File is required for sqlite3 driver")
|
||||
}
|
||||
}
|
||||
|
||||
return *config
|
||||
}
|
||||
|
||||
// 通用的 WithOption 配置函数
|
||||
func WithHost(host string) Option {
|
||||
return func(c *DBConfig) {
|
||||
c.Host = host
|
||||
}
|
||||
}
|
||||
|
||||
func WithPort(port string) Option {
|
||||
return func(c *DBConfig) {
|
||||
c.Port = port
|
||||
}
|
||||
}
|
||||
|
||||
func WithDatabase(database string) Option {
|
||||
return func(c *DBConfig) {
|
||||
c.Database = database
|
||||
}
|
||||
}
|
||||
|
||||
func WithUsername(username string) Option {
|
||||
return func(c *DBConfig) {
|
||||
c.Username = username
|
||||
}
|
||||
}
|
||||
|
||||
func WithPassword(password string) Option {
|
||||
return func(c *DBConfig) {
|
||||
c.Password = password
|
||||
}
|
||||
}
|
||||
|
||||
func WithParseTime(parseTime bool) Option {
|
||||
return func(c *DBConfig) {
|
||||
c.ParseTime = parseTime
|
||||
}
|
||||
}
|
||||
|
||||
func WithCharset(charset string) Option {
|
||||
return func(c *DBConfig) {
|
||||
c.Charset = charset
|
||||
}
|
||||
}
|
||||
|
||||
func WithReadHosts(hosts []string) Option {
|
||||
return func(c *DBConfig) {
|
||||
c.ReadHosts = hosts
|
||||
}
|
||||
}
|
||||
|
||||
func WithWriteHosts(hosts []string) Option {
|
||||
return func(c *DBConfig) {
|
||||
c.WriteHosts = hosts
|
||||
}
|
||||
}
|
||||
|
||||
// MySQL 专用选项
|
||||
func WithMySQLCollation(collation string) Option {
|
||||
return func(c *DBConfig) {
|
||||
if c.Driver != "mysql" {
|
||||
panic("WithMySQLCollation is only valid for mysql driver")
|
||||
}
|
||||
c.MySQL.Collation = collation
|
||||
}
|
||||
}
|
||||
|
||||
func WithMySQLUnixSocket(socket string) Option {
|
||||
return func(c *DBConfig) {
|
||||
if c.Driver != "mysql" {
|
||||
panic("WithMySQLUnixSocket is only valid for mysql driver")
|
||||
}
|
||||
c.MySQL.UnixSocket = socket
|
||||
}
|
||||
}
|
||||
|
||||
// PostgreSQL 专用选项
|
||||
func WithPgSslmode(sslmode string) Option {
|
||||
return func(c *DBConfig) {
|
||||
if c.Driver != "pgsql" {
|
||||
panic("WithPgSslmode is only valid for pgsql driver")
|
||||
}
|
||||
c.PostgreSQL.Sslmode = sslmode
|
||||
}
|
||||
}
|
||||
|
||||
// SQLite 专用选项
|
||||
func WithSQLiteFile(file string) Option {
|
||||
return func(c *DBConfig) {
|
||||
if c.Driver != "sqlite3" {
|
||||
panic("WithSQLiteFile is only valid for sqlite3 driver")
|
||||
}
|
||||
c.SQLite.File = file
|
||||
}
|
||||
}
|
||||
|
||||
func WithSQLiteJournal(journal string) Option {
|
||||
return func(c *DBConfig) {
|
||||
if c.Driver != "sqlite3" {
|
||||
panic("WithSQLiteJournal is only valid for sqlite3 driver")
|
||||
}
|
||||
c.SQLite.Journal = journal
|
||||
}
|
||||
}
|
||||
|
||||
// SQL Server 专用选项
|
||||
func WithSQLServerInstance(instance string) Option {
|
||||
return func(c *DBConfig) {
|
||||
if c.Driver != "sqlserver" {
|
||||
panic("WithSQLServerInstance is only valid for sqlserver driver")
|
||||
}
|
||||
c.SQLServer.Instance = instance
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,383 @@
|
||||
package engine_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.fsdpf.net/go/db/v2/engine"
|
||||
)
|
||||
|
||||
// TestMySQLConfig 测试 MySQL 驱动的配置
|
||||
func TestMySQLConfig(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
options []engine.Option
|
||||
wantErr bool
|
||||
errMsg string
|
||||
}{
|
||||
{
|
||||
name: "Valid Config",
|
||||
options: []engine.Option{
|
||||
engine.WithHost("localhost"),
|
||||
engine.WithDatabase("testdb"),
|
||||
engine.WithUsername("root"),
|
||||
engine.WithPassword("password"),
|
||||
engine.WithMySQLCollation("utf8mb4_unicode_ci"),
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Missing Host",
|
||||
options: []engine.Option{
|
||||
engine.WithDatabase("testdb"),
|
||||
engine.WithUsername("root"),
|
||||
engine.WithPassword("password"),
|
||||
},
|
||||
wantErr: true,
|
||||
errMsg: "Host, ReadHosts, or WriteHosts is required for mysql driver",
|
||||
},
|
||||
{
|
||||
name: "Missing Database",
|
||||
options: []engine.Option{
|
||||
engine.WithHost("localhost"),
|
||||
engine.WithUsername("root"),
|
||||
engine.WithPassword("password"),
|
||||
},
|
||||
wantErr: true,
|
||||
errMsg: "Database is required for mysql driver",
|
||||
},
|
||||
{
|
||||
name: "engine.engine.With SQL Server engine.Option",
|
||||
options: []engine.Option{
|
||||
engine.WithHost("localhost"),
|
||||
engine.WithDatabase("testdb"),
|
||||
engine.WithUsername("root"),
|
||||
engine.WithPassword("password"),
|
||||
engine.WithSQLServerInstance("SQLEXPRESS"),
|
||||
},
|
||||
wantErr: true,
|
||||
errMsg: "engine.WithSQLServerInstance is only valid for sqlserver driver",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
if !tt.wantErr {
|
||||
t.Errorf("engine.NewDBConfig() panicked unexpectedly: %v", r)
|
||||
} else if tt.errMsg != "" && r != tt.errMsg {
|
||||
t.Errorf("engine.NewDBConfig() panic = %v, want %v", r, tt.errMsg)
|
||||
}
|
||||
} else if tt.wantErr {
|
||||
t.Errorf("engine.NewDBConfig() did not panic, expected panic with: %v", tt.errMsg)
|
||||
}
|
||||
}()
|
||||
|
||||
config := engine.NewDBConfig("mysql", tt.options...)
|
||||
if !tt.wantErr && (config.Driver != "mysql" || config.MySQL.Collation != "utf8mb4_unicode_ci") {
|
||||
t.Errorf("MySQL config not set correctly: %+v", config)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestPostgreSQLConfig 测试 PostgreSQL 驱动的配置
|
||||
func TestPostgreSQLConfig(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
options []engine.Option
|
||||
wantErr bool
|
||||
errMsg string
|
||||
}{
|
||||
{
|
||||
name: "Valid Config",
|
||||
options: []engine.Option{
|
||||
engine.WithHost("localhost"),
|
||||
engine.WithDatabase("testdb"),
|
||||
engine.WithUsername("postgres"),
|
||||
engine.WithPassword("password"),
|
||||
engine.WithPgSslmode("disable"),
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Missing Username",
|
||||
options: []engine.Option{
|
||||
engine.WithHost("localhost"),
|
||||
engine.WithDatabase("testdb"),
|
||||
engine.WithPassword("password"),
|
||||
},
|
||||
wantErr: true,
|
||||
errMsg: "Username is required for pgsql driver",
|
||||
},
|
||||
{
|
||||
name: "engine.engine.With MySQL engine.Option",
|
||||
options: []engine.Option{
|
||||
engine.WithHost("localhost"),
|
||||
engine.WithDatabase("testdb"),
|
||||
engine.WithUsername("postgres"),
|
||||
engine.WithPassword("password"),
|
||||
engine.WithMySQLCollation("utf8mb4_unicode_ci"),
|
||||
},
|
||||
wantErr: true,
|
||||
errMsg: "engine.WithMySQLCollation is only valid for mysql driver",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
if !tt.wantErr {
|
||||
t.Errorf("engine.NewDBConfig() panicked unexpectedly: %v", r)
|
||||
} else if tt.errMsg != "" && r != tt.errMsg {
|
||||
t.Errorf("engine.NewDBConfig() panic = %v, want %v", r, tt.errMsg)
|
||||
}
|
||||
} else if tt.wantErr {
|
||||
t.Errorf("engine.NewDBConfig() did not panic, expected panic with: %v", tt.errMsg)
|
||||
}
|
||||
}()
|
||||
|
||||
config := engine.NewDBConfig("pgsql", tt.options...)
|
||||
if !tt.wantErr && (config.Driver != "pgsql" || config.PostgreSQL.Sslmode != "disable") {
|
||||
t.Errorf("PostgreSQL config not set correctly: %+v", config)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestSQLiteConfig 测试 SQLite 驱动的配置
|
||||
func TestSQLiteConfig(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
options []engine.Option
|
||||
wantErr bool
|
||||
errMsg string
|
||||
}{
|
||||
{
|
||||
name: "Valid Config",
|
||||
options: []engine.Option{
|
||||
engine.WithSQLiteFile("test.db"),
|
||||
engine.WithSQLiteJournal("WAL"),
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Missing File",
|
||||
options: []engine.Option{},
|
||||
wantErr: true,
|
||||
errMsg: "File is required for sqlite3 driver",
|
||||
},
|
||||
{
|
||||
name: "engine.engine.With PostgreSQL engine.Option",
|
||||
options: []engine.Option{
|
||||
engine.WithSQLiteFile("test.db"),
|
||||
engine.WithPgSslmode("disable"),
|
||||
},
|
||||
wantErr: true,
|
||||
errMsg: "engine.WithPgSslmode is only valid for pgsql driver",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
if !tt.wantErr {
|
||||
t.Errorf("engine.NewDBConfig() panicked unexpectedly: %v", r)
|
||||
} else if tt.errMsg != "" && r != tt.errMsg {
|
||||
t.Errorf("engine.NewDBConfig() panic = %v, want %v", r, tt.errMsg)
|
||||
}
|
||||
} else if tt.wantErr {
|
||||
t.Errorf("engine.NewDBConfig() did not panic, expected panic with: %v", tt.errMsg)
|
||||
}
|
||||
}()
|
||||
|
||||
config := engine.NewDBConfig("sqlite3", tt.options...)
|
||||
if !tt.wantErr && (config.Driver != "sqlite3" || config.SQLite.File != "test.db") {
|
||||
t.Errorf("SQLite config not set correctly: %+v", config)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestSQLServerConfig 测试 SQL Server 驱动的配置
|
||||
func TestSQLServerConfig(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
options []engine.Option
|
||||
wantErr bool
|
||||
errMsg string
|
||||
}{
|
||||
{
|
||||
name: "Valid Config",
|
||||
options: []engine.Option{
|
||||
engine.WithHost("sqlserver.example.com"),
|
||||
engine.WithDatabase("master"),
|
||||
engine.WithUsername("sa"),
|
||||
engine.WithPassword("password"),
|
||||
engine.WithSQLServerInstance("SQLEXPRESS"),
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "Missing Password",
|
||||
options: []engine.Option{
|
||||
engine.WithHost("sqlserver.example.com"),
|
||||
engine.WithDatabase("master"),
|
||||
engine.WithUsername("sa"),
|
||||
},
|
||||
wantErr: true,
|
||||
errMsg: "Password is required for sqlserver driver",
|
||||
},
|
||||
{
|
||||
name: "engine.engine.With SQLite engine.Option",
|
||||
options: []engine.Option{
|
||||
engine.WithHost("sqlserver.example.com"),
|
||||
engine.WithDatabase("master"),
|
||||
engine.WithUsername("sa"),
|
||||
engine.WithPassword("password"),
|
||||
engine.WithSQLiteFile("test.db"),
|
||||
},
|
||||
wantErr: true,
|
||||
errMsg: "engine.WithSQLiteFile is only valid for sqlite3 driver",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
if !tt.wantErr {
|
||||
t.Errorf("engine.NewDBConfig() panicked unexpectedly: %v", r)
|
||||
} else if tt.errMsg != "" && r != tt.errMsg {
|
||||
t.Errorf("engine.NewDBConfig() panic = %v, want %v", r, tt.errMsg)
|
||||
}
|
||||
} else if tt.wantErr {
|
||||
t.Errorf("engine.NewDBConfig() did not panic, expected panic with: %v", tt.errMsg)
|
||||
}
|
||||
}()
|
||||
|
||||
config := engine.NewDBConfig("sqlserver", tt.options...)
|
||||
if !tt.wantErr && (config.Driver != "sqlserver" || config.SQLServer.Instance != "SQLEXPRESS") {
|
||||
t.Errorf("SQL Server config not set correctly: %+v", config)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestInvalidDriver 测试不支持的驱动
|
||||
func TestInvalidDriver(t *testing.T) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
if r != "Unsupported driver: oracle" {
|
||||
t.Errorf("engine.NewDBConfig() panic = %v, want 'Unsupported driver: oracle'", r)
|
||||
}
|
||||
} else {
|
||||
t.Errorf("engine.NewDBConfig() did not panic for invalid driver")
|
||||
}
|
||||
}()
|
||||
|
||||
engine.NewDBConfig("oracle")
|
||||
}
|
||||
|
||||
// TestDefaultValues 测试默认值
|
||||
func TestDefaultValues(t *testing.T) {
|
||||
config := engine.NewDBConfig("mysql",
|
||||
engine.WithHost("localhost"),
|
||||
engine.WithDatabase("testdb"),
|
||||
engine.WithUsername("root"),
|
||||
engine.WithPassword("password"),
|
||||
)
|
||||
|
||||
if config.Charset != "utf8mb4" {
|
||||
t.Errorf("Default Charset = %v, want utf8mb4", config.Charset)
|
||||
}
|
||||
if config.ConnMaxLifetime != 2*time.Hour {
|
||||
t.Errorf("Default ConnMaxLifetime = %v, want 2h", config.ConnMaxLifetime)
|
||||
}
|
||||
if config.MaxOpenConns != 100 {
|
||||
t.Errorf("Default MaxOpenConns = %v, want 100", config.MaxOpenConns)
|
||||
}
|
||||
}
|
||||
|
||||
func TestToDSN(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
config engine.DBConfig
|
||||
wantDSN string
|
||||
wantErr bool
|
||||
errMsg string
|
||||
}{
|
||||
{
|
||||
name: "MySQL DSN",
|
||||
config: engine.NewDBConfig("mysql",
|
||||
engine.WithHost("localhost"),
|
||||
engine.WithPort("3306"),
|
||||
engine.WithDatabase("testdb"),
|
||||
engine.WithUsername("root"),
|
||||
engine.WithPassword("password"),
|
||||
engine.WithMySQLCollation("utf8mb4_unicode_ci"),
|
||||
),
|
||||
wantDSN: "root:password@tcp(localhost:3306)/testdb?charset=utf8mb4&collation=utf8mb4_unicode_ci",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "PostgreSQL DSN",
|
||||
config: engine.NewDBConfig("pgsql",
|
||||
engine.WithHost("localhost"),
|
||||
engine.WithPort("5432"),
|
||||
engine.WithDatabase("testdb"),
|
||||
engine.WithUsername("postgres"),
|
||||
engine.WithPassword("password"),
|
||||
engine.WithPgSslmode("disable"),
|
||||
),
|
||||
wantDSN: "postgres://postgres:password@localhost:5432/testdb?host=localhost&port=5432&dbname=testdb&user=postgres&password=password&sslmode=disable",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "SQLite DSN",
|
||||
config: engine.NewDBConfig("sqlite3",
|
||||
engine.WithSQLiteFile("test.db"),
|
||||
engine.WithSQLiteJournal("WAL"),
|
||||
),
|
||||
wantDSN: "test.db?_journal=WAL",
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "SQL Server DSN",
|
||||
config: engine.NewDBConfig("sqlserver",
|
||||
engine.WithHost("sqlserver.example.com"),
|
||||
engine.WithPort("1433"),
|
||||
engine.WithDatabase("master"),
|
||||
engine.WithUsername("sa"),
|
||||
engine.WithPassword("password"),
|
||||
engine.WithSQLServerInstance("SQLEXPRESS"),
|
||||
),
|
||||
wantDSN: "sqlserver://sa:password@sqlserver.example.com\\SQLEXPRESS:1433?server=sqlserver.example.com\\SQLEXPRESS&database=master",
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
if !tt.wantErr {
|
||||
t.Errorf("ToDSN() panicked unexpectedly: %v", r)
|
||||
} else if tt.errMsg != "" && r != tt.errMsg {
|
||||
t.Errorf("ToDSN() panic = %v, want %v", r, tt.errMsg)
|
||||
}
|
||||
} else if tt.wantErr {
|
||||
t.Errorf("ToDSN() did not panic, expected panic with: %v", tt.errMsg)
|
||||
}
|
||||
}()
|
||||
|
||||
dsn := tt.config.ToDSN()
|
||||
if !tt.wantErr && dsn != tt.wantDSN {
|
||||
t.Errorf("ToDSN() = %v, want %v", dsn, tt.wantDSN)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user