package engine import ( "database/sql" "fmt" "net/url" "time" ) type MockDBConfig struct { Driver string Mock *sql.DB } // 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 } DuckDB struct { File string AccessMode string Threads int MaxMemory string 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", "vtable": return c.toSQLiteDSN() case "sqlserver": return c.toSQLServerDSN() case "duckdb": return c.toDuckDBDSN() 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 { // 构建连接字符串参数列表 var params []string // 确定主机地址 host := c.Host if host == "" && len(c.WriteHosts) > 0 { host = c.WriteHosts[0] } if host == "" && len(c.ReadHosts) > 0 { host = c.ReadHosts[0] } if host != "" { params = append(params, fmt.Sprintf("host=%s", host)) } // 设置端口 port := c.Port if port == "" { port = "5432" // 默认 PostgreSQL 端口 } params = append(params, fmt.Sprintf("port=%s", port)) // 添加用户名 if c.Username != "" { params = append(params, fmt.Sprintf("user=%s", c.Username)) } // 添加密码 if c.Password != "" { params = append(params, fmt.Sprintf("password=%s", c.Password)) } // 添加数据库名 if c.Database != "" { params = append(params, fmt.Sprintf("dbname=%s", c.Database)) } // 添加 SSL 模式 if c.PostgreSQL.Sslmode != "" { params = append(params, fmt.Sprintf("sslmode=%s", c.PostgreSQL.Sslmode)) } // 添加连接超时 if c.PostgreSQL.ConnectTimeout > 0 { params = append(params, fmt.Sprintf("connect_timeout=%d", c.PostgreSQL.ConnectTimeout)) } // 添加应用名称 if c.PostgreSQL.ApplicationName != "" { params = append(params, fmt.Sprintf("application_name=%s", c.PostgreSQL.ApplicationName)) } // 添加搜索路径 if c.PostgreSQL.SearchPath != "" { params = append(params, fmt.Sprintf("search_path=%s", c.PostgreSQL.SearchPath)) } // 用空格连接所有参数 return joinParams(params, " ") } // joinParams 连接参数列表 func joinParams(params []string, sep string) string { if len(params) == 0 { return "" } result := params[0] for i := 1; i < len(params); i++ { result += sep + params[i] } return result } // 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() } // toDuckDBDSN 生成 DuckDB DSN func (c *DBConfig) toDuckDBDSN() string { if c.DuckDB.Dsn != "" { return c.DuckDB.Dsn } // DuckDB 可以使用内存数据库或文件数据库 dsn := c.DuckDB.File if dsn == "" { dsn = "" // 空字符串表示内存数据库 } params := url.Values{} if c.DuckDB.AccessMode != "" { params.Add("access_mode", c.DuckDB.AccessMode) } if c.DuckDB.Threads > 0 { params.Add("threads", fmt.Sprintf("%d", c.DuckDB.Threads)) } if c.DuckDB.MaxMemory != "" { params.Add("max_memory", c.DuckDB.MaxMemory) } if len(params) > 0 { dsn += "?" + params.Encode() } return dsn } // 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, "duckdb": true, "vtable": 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", "vtable": if config.SQLite.File == "" { panic("File is required for sqlite3 driver") } case "duckdb": // DuckDB 文件参数可选,空字符串表示内存数据库 } 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 } } // WithMySQLCollation 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" && c.Driver != "vtable" { 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" && c.Driver != "vtable" { 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 } } // DuckDB 专用选项 func WithDuckDBFile(file string) Option { return func(c *DBConfig) { if c.Driver != "duckdb" { panic("WithDuckDBFile is only valid for duckdb driver") } c.DuckDB.File = file } } func WithDuckDBAccessMode(accessMode string) Option { return func(c *DBConfig) { if c.Driver != "duckdb" { panic("WithDuckDBAccessMode is only valid for duckdb driver") } c.DuckDB.AccessMode = accessMode } } func WithDuckDBThreads(threads int) Option { return func(c *DBConfig) { if c.Driver != "duckdb" { panic("WithDuckDBThreads is only valid for duckdb driver") } c.DuckDB.Threads = threads } } func WithDuckDBMaxMemory(maxMemory string) Option { return func(c *DBConfig) { if c.Driver != "duckdb" { panic("WithDuckDBMaxMemory is only valid for duckdb driver") } c.DuckDB.MaxMemory = maxMemory } }