fix(db): force parseTime in user-provided MySQL DSNs

A custom DatabaseURL is passed verbatim to sql.Open; without
parseTime=True the driver returns DATETIME columns as strings, which
fail to scan into time.Time fields after a v3 migration.

Closes Dvorinka/cloudreve#40 (upstream cloudreve/cloudreve#2872).

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
pull/3582/head
Tomas Dvorak 2 weeks ago
parent 325a3aa0a4
commit 582621afa0

@ -5,6 +5,7 @@ import (
rawsql "database/sql" rawsql "database/sql"
"database/sql/driver" "database/sql/driver"
"fmt" "fmt"
"strings"
"time" "time"
"entgo.io/ent/dialect/sql" "entgo.io/ent/dialect/sql"
@ -74,7 +75,7 @@ func NewRawEntClient(l logging.Logger, config conf.ConfigProvider) (*ent.Client,
// If Database connection string provided, use it directly. // If Database connection string provided, use it directly.
if dbConfig.DatabaseURL != "" { if dbConfig.DatabaseURL != "" {
l.Info("Connect to database with connection string") l.Info("Connect to database with connection string")
client, err = sql.Open(string(confDBType), dbConfig.DatabaseURL) client, err = sql.Open(string(confDBType), ensureMySQLParseTime(confDBType, dbConfig.DatabaseURL))
} else { } else {
switch confDBType { switch confDBType {
@ -142,6 +143,25 @@ func NewRawEntClient(l logging.Logger, config conf.ConfigProvider) (*ent.Client,
return ent.NewClient(driverOpt), nil return ent.NewClient(driverOpt), nil
} }
// ensureMySQLParseTime appends parseTime=True to a user-provided MySQL DSN
// when absent. Without it the driver returns DATETIME columns as strings,
// which fail to scan into time.Time fields (cloudreve/cloudreve#2872).
func ensureMySQLParseTime(dbType conf.DBType, dsn string) string {
if dbType != conf.MySqlDB {
return dsn
}
idx := strings.IndexByte(dsn, '?')
if idx < 0 {
return dsn + "?parseTime=True"
}
for _, p := range strings.Split(dsn[idx+1:], "&") {
if strings.HasPrefix(strings.ToLower(p), "parsetime=") {
return dsn
}
}
return dsn + "&parseTime=True"
}
type sqlite3Driver struct { type sqlite3Driver struct {
*sqlite.Driver *sqlite.Driver
} }

@ -0,0 +1,27 @@
package inventory
import (
"testing"
"github.com/cloudreve/Cloudreve/v4/pkg/conf"
"github.com/stretchr/testify/require"
)
func TestEnsureMySQLParseTime(t *testing.T) {
dsn := "user:pass@tcp(127.0.0.1:3306)/cloudreve"
require.Equal(t, dsn+"?parseTime=True", ensureMySQLParseTime(conf.MySqlDB, dsn))
withParams := dsn + "?charset=utf8mb4"
require.Equal(t, withParams+"&parseTime=True", ensureMySQLParseTime(conf.MySqlDB, withParams))
existing := dsn + "?charset=utf8mb4&parseTime=false"
require.Equal(t, existing, ensureMySQLParseTime(conf.MySqlDB, existing))
urlForm := "mysql://user:pass@host:3306/cloudreve?tls=true"
require.Equal(t, urlForm+"&parseTime=True", ensureMySQLParseTime(conf.MySqlDB, urlForm))
// Non-MySQL DSNs pass through untouched.
pg := "postgresql://user:pass@host:5432/cloudreve?sslmode=require"
require.Equal(t, pg, ensureMySQLParseTime(conf.PostgresDB, pg))
require.Equal(t, dsn, ensureMySQLParseTime(conf.SQLiteDB, dsn))
}
Loading…
Cancel
Save