mirror of
https://github.com/photoprism/photoprism.git
synced 2026-01-23 02:24:24 +00:00
Merge e6319c3477 into 26b5cbafcd
This commit is contained in:
commit
d7125fc997
42 changed files with 404 additions and 244 deletions
|
|
@ -23,6 +23,7 @@ import (
|
|||
clusternode "github.com/photoprism/photoprism/internal/service/cluster/node"
|
||||
"github.com/photoprism/photoprism/internal/service/cluster/theme"
|
||||
"github.com/photoprism/photoprism/pkg/clean"
|
||||
"github.com/photoprism/photoprism/pkg/enum"
|
||||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
"github.com/photoprism/photoprism/pkg/http/header"
|
||||
"github.com/photoprism/photoprism/pkg/log/status"
|
||||
|
|
@ -449,7 +450,7 @@ func persistRegisterResponse(conf *config.Config, resp *cluster.RegisterResponse
|
|||
|
||||
// DB settings (MySQL/MariaDB only)
|
||||
if resp.Database.Name != "" && resp.Database.User != "" {
|
||||
updates.SetDatabaseDriver(config.MySQL)
|
||||
updates.SetDatabaseDriver(enum.MySQL)
|
||||
updates.SetDatabaseName(resp.Database.Name)
|
||||
updates.SetDatabaseServer(fmt.Sprintf("%s:%d", resp.Database.Host, resp.Database.Port))
|
||||
updates.SetDatabaseUser(resp.Database.User)
|
||||
|
|
|
|||
|
|
@ -61,6 +61,7 @@ import (
|
|||
"github.com/photoprism/photoprism/internal/thumb"
|
||||
"github.com/photoprism/photoprism/pkg/checksum"
|
||||
"github.com/photoprism/photoprism/pkg/clean"
|
||||
"github.com/photoprism/photoprism/pkg/enum"
|
||||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
"github.com/photoprism/photoprism/pkg/i18n"
|
||||
"github.com/photoprism/photoprism/pkg/rnd"
|
||||
|
|
@ -688,7 +689,7 @@ func (c *Config) IndexWorkers() int {
|
|||
}
|
||||
|
||||
// Limit number of workers when using SQLite3 to avoid database locking issues.
|
||||
if c.DatabaseDriver() == SQLite3 && (cores >= 8 && c.options.IndexWorkers <= 0 || c.options.IndexWorkers > 4) {
|
||||
if c.DatabaseDriver() == enum.SQLite3 && (cores >= 8 && c.options.IndexWorkers <= 0 || c.options.IndexWorkers > 4) {
|
||||
return 4
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -22,35 +22,26 @@ import (
|
|||
"github.com/photoprism/photoprism/internal/service/cluster"
|
||||
"github.com/photoprism/photoprism/pkg/clean"
|
||||
"github.com/photoprism/photoprism/pkg/dsn"
|
||||
"github.com/photoprism/photoprism/pkg/enum"
|
||||
"github.com/photoprism/photoprism/pkg/txt"
|
||||
)
|
||||
|
||||
// SQL Databases.
|
||||
// TODO: PostgreSQL support requires upgrading GORM, so generic column data types can be used.
|
||||
const (
|
||||
Auto = "auto"
|
||||
MySQL = dsn.DriverMySQL
|
||||
MariaDB = dsn.DriverMariaDB
|
||||
Postgres = dsn.DriverPostgres
|
||||
SQLite3 = dsn.DriverSQLite3
|
||||
)
|
||||
|
||||
// DatabaseDriver returns the database driver name.
|
||||
func (c *Config) DatabaseDriver() string {
|
||||
c.normalizeDatabaseDSN()
|
||||
|
||||
switch strings.ToLower(c.options.DatabaseDriver) {
|
||||
case MySQL, MariaDB:
|
||||
c.options.DatabaseDriver = MySQL
|
||||
case SQLite3, "sqlite", "test", "file", "":
|
||||
c.options.DatabaseDriver = SQLite3
|
||||
case enum.MySQL, enum.MariaDB:
|
||||
c.options.DatabaseDriver = enum.MySQL
|
||||
case enum.SQLite3, "sqlite", "test", "file", "":
|
||||
c.options.DatabaseDriver = enum.SQLite3
|
||||
case "tidb":
|
||||
log.Warnf("config: database driver 'tidb' is deprecated, using sqlite")
|
||||
c.options.DatabaseDriver = SQLite3
|
||||
c.options.DatabaseDriver = enum.SQLite3
|
||||
c.options.DatabaseDSN = ""
|
||||
default:
|
||||
log.Warnf("config: unsupported database driver %s, using sqlite", c.options.DatabaseDriver)
|
||||
c.options.DatabaseDriver = SQLite3
|
||||
c.options.DatabaseDriver = enum.SQLite3
|
||||
c.options.DatabaseDSN = ""
|
||||
}
|
||||
|
||||
|
|
@ -60,9 +51,9 @@ func (c *Config) DatabaseDriver() string {
|
|||
// DatabaseDriverName returns the formatted database driver name.
|
||||
func (c *Config) DatabaseDriverName() string {
|
||||
switch c.DatabaseDriver() {
|
||||
case MySQL, MariaDB:
|
||||
case enum.MySQL, enum.MariaDB:
|
||||
return "MariaDB"
|
||||
case SQLite3, "sqlite", "test", "file", "":
|
||||
case enum.SQLite3, "sqlite", "test", "file", "":
|
||||
return "SQLite"
|
||||
case "tidb":
|
||||
return "TiDB"
|
||||
|
|
@ -92,7 +83,7 @@ func (c *Config) DatabaseSsl() bool {
|
|||
}
|
||||
|
||||
switch c.DatabaseDriver() {
|
||||
case MySQL:
|
||||
case enum.MySQL:
|
||||
// see https://mariadb.org/mission-impossible-zero-configuration-ssl/
|
||||
return c.IsDatabaseVersion("v11.4")
|
||||
default:
|
||||
|
|
@ -115,7 +106,7 @@ func (c *Config) DatabaseDSN() string {
|
|||
// Generate matching database DSN based on the configured database driver.
|
||||
if c.NoDatabaseDSN() {
|
||||
switch c.DatabaseDriver() {
|
||||
case MySQL:
|
||||
case enum.MySQL:
|
||||
databaseServer := c.DatabaseServer()
|
||||
|
||||
// Connect via Unix Domain Socket?
|
||||
|
|
@ -132,10 +123,10 @@ func (c *Config) DatabaseDSN() string {
|
|||
c.DatabasePassword(),
|
||||
databaseServer,
|
||||
c.DatabaseName(),
|
||||
dsn.Params[dsn.DriverMySQL],
|
||||
dsn.Params[enum.MySQL],
|
||||
c.DatabaseTimeout(),
|
||||
)
|
||||
case Postgres:
|
||||
case enum.Postgres:
|
||||
return fmt.Sprintf(
|
||||
"user=%s password=%s dbname=%s host=%s port=%d connect_timeout=%d %s",
|
||||
c.DatabaseUser(),
|
||||
|
|
@ -144,10 +135,10 @@ func (c *Config) DatabaseDSN() string {
|
|||
c.DatabaseHost(),
|
||||
c.DatabasePort(),
|
||||
c.DatabaseTimeout(),
|
||||
dsn.Params[dsn.DriverPostgres],
|
||||
dsn.Params[enum.Postgres],
|
||||
)
|
||||
case SQLite3:
|
||||
return filepath.Join(c.StoragePath(), fmt.Sprintf("index.db?%s", dsn.Params[dsn.DriverSQLite3]))
|
||||
case enum.SQLite3:
|
||||
return filepath.Join(c.StoragePath(), fmt.Sprintf("index.db?%s", dsn.Params[enum.SQLite3]))
|
||||
default:
|
||||
log.Errorf("config: empty database dsn")
|
||||
return ""
|
||||
|
|
@ -155,11 +146,11 @@ func (c *Config) DatabaseDSN() string {
|
|||
}
|
||||
|
||||
// If missing, add the required parameters to the configured MySQL/MariaDB DSN.
|
||||
if c.DatabaseDriver() == MySQL && !strings.Contains(c.options.DatabaseDSN, "?") {
|
||||
if c.DatabaseDriver() == enum.MySQL && !strings.Contains(c.options.DatabaseDSN, "?") {
|
||||
c.options.DatabaseDSN = fmt.Sprintf(
|
||||
"%s?%s&timeout=%ds",
|
||||
c.options.DatabaseDSN,
|
||||
dsn.Params[dsn.DriverMySQL],
|
||||
dsn.Params[enum.MySQL],
|
||||
c.DatabaseTimeout())
|
||||
}
|
||||
|
||||
|
|
@ -181,7 +172,7 @@ func (c *Config) HasDatabaseDSN() bool {
|
|||
// ReportDatabaseDSN checks if the database data source name (DSN) should be reported
|
||||
// instead of database name, server, user, and password.
|
||||
func (c *Config) ReportDatabaseDSN() bool {
|
||||
if c.DatabaseDriver() == SQLite3 {
|
||||
if c.DatabaseDriver() == enum.SQLite3 {
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
@ -192,7 +183,7 @@ func (c *Config) ReportDatabaseDSN() bool {
|
|||
func (c *Config) ParseDatabaseDSN() {
|
||||
if c.NoDatabaseDSN() {
|
||||
return
|
||||
} else if c.options.DatabaseServer != "" && c.DatabaseDriver() == SQLite3 {
|
||||
} else if c.options.DatabaseServer != "" && c.DatabaseDriver() == enum.SQLite3 {
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -214,7 +205,7 @@ func (c *Config) DatabaseFile() string {
|
|||
func (c *Config) DatabaseServer() string {
|
||||
c.ParseDatabaseDSN()
|
||||
|
||||
if c.DatabaseDriver() == SQLite3 {
|
||||
if c.DatabaseDriver() == enum.SQLite3 {
|
||||
return ""
|
||||
} else if c.options.DatabaseServer == "" {
|
||||
return localhost
|
||||
|
|
@ -227,7 +218,7 @@ func (c *Config) DatabaseServer() string {
|
|||
func (c *Config) DatabaseHost() string {
|
||||
c.ParseDatabaseDSN()
|
||||
|
||||
if c.DatabaseDriver() == SQLite3 {
|
||||
if c.DatabaseDriver() == enum.SQLite3 {
|
||||
return ""
|
||||
}
|
||||
|
||||
|
|
@ -239,7 +230,7 @@ func (c *Config) DatabaseHost() string {
|
|||
func (c *Config) DatabasePort() int {
|
||||
c.ParseDatabaseDSN()
|
||||
|
||||
if c.DatabaseDriver() == SQLite3 {
|
||||
if c.DatabaseDriver() == enum.SQLite3 {
|
||||
return 0
|
||||
}
|
||||
|
||||
|
|
@ -249,7 +240,7 @@ func (c *Config) DatabasePort() int {
|
|||
|
||||
// DatabasePortString the database server port as string.
|
||||
func (c *Config) DatabasePortString() string {
|
||||
if c.DatabaseDriver() == SQLite3 {
|
||||
if c.DatabaseDriver() == enum.SQLite3 {
|
||||
return ""
|
||||
}
|
||||
|
||||
|
|
@ -260,7 +251,7 @@ func (c *Config) DatabasePortString() string {
|
|||
func (c *Config) DatabaseName() string {
|
||||
c.ParseDatabaseDSN()
|
||||
|
||||
if c.DatabaseDriver() == SQLite3 {
|
||||
if c.DatabaseDriver() == enum.SQLite3 {
|
||||
return c.DatabaseDSN()
|
||||
} else if c.options.DatabaseName == "" {
|
||||
return "photoprism"
|
||||
|
|
@ -271,7 +262,7 @@ func (c *Config) DatabaseName() string {
|
|||
|
||||
// DatabaseUser returns the database user name.
|
||||
func (c *Config) DatabaseUser() string {
|
||||
if c.DatabaseDriver() == SQLite3 {
|
||||
if c.DatabaseDriver() == enum.SQLite3 {
|
||||
return ""
|
||||
}
|
||||
|
||||
|
|
@ -286,7 +277,7 @@ func (c *Config) DatabaseUser() string {
|
|||
|
||||
// DatabasePassword returns the database user password.
|
||||
func (c *Config) DatabasePassword() string {
|
||||
if c.DatabaseDriver() == SQLite3 {
|
||||
if c.DatabaseDriver() == enum.SQLite3 {
|
||||
return ""
|
||||
}
|
||||
|
||||
|
|
@ -358,7 +349,7 @@ func (c *Config) DatabaseProvisionPrefix() string {
|
|||
// ShouldAutoRotateDatabase decides whether callers should request DB rotation automatically.
|
||||
// It is used by both the CLI and node bootstrap to avoid unnecessary provisioning calls.
|
||||
func (c *Config) ShouldAutoRotateDatabase() bool {
|
||||
if c.Portal() || c.DatabaseDriver() != MySQL {
|
||||
if c.Portal() || c.DatabaseDriver() != enum.MySQL {
|
||||
return false
|
||||
}
|
||||
|
||||
|
|
@ -440,11 +431,11 @@ func (c *Config) CloseDb() error {
|
|||
// SetDbOptions sets the database collation to unicode if supported.
|
||||
func (c *Config) SetDbOptions() {
|
||||
switch c.DatabaseDriver() {
|
||||
case MySQL, MariaDB:
|
||||
case enum.MySQL, enum.MariaDB:
|
||||
c.Db().Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci")
|
||||
case Postgres:
|
||||
case enum.Postgres:
|
||||
// Ignore for now.
|
||||
case SQLite3:
|
||||
case enum.SQLite3:
|
||||
// Not required as Unicode is default.
|
||||
}
|
||||
}
|
||||
|
|
@ -515,7 +506,7 @@ func (c *Config) checkDb(db *gorm.DB) error {
|
|||
}
|
||||
|
||||
switch c.DatabaseDriver() {
|
||||
case MySQL:
|
||||
case enum.MySQL:
|
||||
type Res struct {
|
||||
Value string `gorm:"column:Value;"`
|
||||
}
|
||||
|
|
@ -544,7 +535,7 @@ func (c *Config) checkDb(db *gorm.DB) error {
|
|||
case !c.IsDatabaseVersion("v10.5.12"):
|
||||
return fmt.Errorf("config: MariaDB %s is not supported, see https://docs.photoprism.app/getting-started/#databases", c.dbVersion)
|
||||
}
|
||||
case SQLite3:
|
||||
case enum.SQLite3:
|
||||
type Res struct {
|
||||
Value string `gorm:"column:Value;"`
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,8 @@ import (
|
|||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/photoprism/photoprism/pkg/enum"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/service/cluster"
|
||||
)
|
||||
|
||||
|
|
@ -26,16 +28,16 @@ func TestConfig_DatabaseDriver(t *testing.T) {
|
|||
c := NewConfig(CliTestContext())
|
||||
resetDatabaseOptions(c)
|
||||
|
||||
assert.Equal(t, SQLite3, c.DatabaseDriver())
|
||||
assert.Equal(t, enum.SQLite3, c.DatabaseDriver())
|
||||
})
|
||||
t.Run("NormalizesDeprecatedDSN", func(t *testing.T) {
|
||||
c := NewConfig(CliTestContext())
|
||||
resetDatabaseOptions(c)
|
||||
|
||||
c.options.DatabaseDriver = MySQL
|
||||
c.options.DatabaseDriver = enum.MySQL
|
||||
c.options.Deprecated.DatabaseDsn = "user:pass@tcp(localhost:3306)/photoprism"
|
||||
|
||||
assert.Equal(t, MySQL, c.DatabaseDriver())
|
||||
assert.Equal(t, enum.MySQL, c.DatabaseDriver())
|
||||
assert.Equal(t, "user:pass@tcp(localhost:3306)/photoprism", c.options.DatabaseDSN)
|
||||
assert.Empty(t, c.options.Deprecated.DatabaseDsn)
|
||||
})
|
||||
|
|
@ -65,7 +67,7 @@ func TestConfig_normalizeDatabaseDSN(t *testing.T) {
|
|||
c := NewConfig(CliTestContext())
|
||||
|
||||
c.options.Deprecated.DatabaseDsn = "foo:b@r@tcp(honeypot:1234)/baz?charset=utf8mb4,utf8&parseTime=true"
|
||||
c.options.DatabaseDriver = MySQL
|
||||
c.options.DatabaseDriver = enum.MySQL
|
||||
|
||||
assert.Equal(t, "honeypot:1234", c.DatabaseServer())
|
||||
assert.Equal(t, "honeypot", c.DatabaseHost())
|
||||
|
|
@ -79,7 +81,7 @@ func TestConfig_ParseDatabaseDSN(t *testing.T) {
|
|||
c := NewConfig(CliTestContext())
|
||||
|
||||
c.options.DatabaseDSN = "foo:b@r@tcp(honeypot:1234)/baz?charset=utf8mb4,utf8&parseTime=true"
|
||||
c.options.DatabaseDriver = SQLite3
|
||||
c.options.DatabaseDriver = enum.SQLite3
|
||||
|
||||
assert.Equal(t, "", c.DatabaseServer())
|
||||
assert.Equal(t, "", c.DatabaseHost())
|
||||
|
|
@ -88,7 +90,7 @@ func TestConfig_ParseDatabaseDSN(t *testing.T) {
|
|||
assert.Equal(t, "", c.DatabaseUser())
|
||||
assert.Equal(t, "", c.DatabasePassword())
|
||||
|
||||
c.options.DatabaseDriver = MySQL
|
||||
c.options.DatabaseDriver = enum.MySQL
|
||||
|
||||
assert.Equal(t, "honeypot:1234", c.DatabaseServer())
|
||||
assert.Equal(t, "honeypot", c.DatabaseHost())
|
||||
|
|
@ -97,7 +99,7 @@ func TestConfig_ParseDatabaseDSN(t *testing.T) {
|
|||
assert.Equal(t, "foo", c.DatabaseUser())
|
||||
assert.Equal(t, "b@r", c.DatabasePassword())
|
||||
|
||||
c.options.DatabaseDriver = SQLite3
|
||||
c.options.DatabaseDriver = enum.SQLite3
|
||||
|
||||
assert.Equal(t, "", c.DatabaseServer())
|
||||
assert.Equal(t, "", c.DatabaseHost())
|
||||
|
|
@ -110,7 +112,7 @@ func TestConfig_ParseDatabaseDSN(t *testing.T) {
|
|||
target := NewConfig(CliTestContext())
|
||||
resetDatabaseOptions(target)
|
||||
|
||||
target.options.DatabaseDriver = MySQL
|
||||
target.options.DatabaseDriver = enum.MySQL
|
||||
target.options.DatabaseServer = "db.internal:3306"
|
||||
target.options.DatabaseName = "photoprism"
|
||||
target.options.DatabaseUser = "app"
|
||||
|
|
@ -129,7 +131,7 @@ func TestConfig_ParseDatabaseDSN(t *testing.T) {
|
|||
cfg := NewConfig(CliTestContext())
|
||||
resetDatabaseOptions(cfg)
|
||||
|
||||
cfg.options.DatabaseDriver = SQLite3
|
||||
cfg.options.DatabaseDriver = enum.SQLite3
|
||||
cfg.options.DatabaseDSN = "file:/data/app.db?_busy_timeout=5000"
|
||||
cfg.options.DatabaseServer = "/tmp/mysql.sock"
|
||||
cfg.options.DatabaseName = "existing-name"
|
||||
|
|
@ -191,9 +193,9 @@ func TestConfig_DatabasePassword(t *testing.T) {
|
|||
// Test setting the password via secret file.
|
||||
_ = os.Setenv(FlagFileVar("DATABASE_PASSWORD"), "testdata/secret_database")
|
||||
assert.Equal(t, "", c.DatabasePassword())
|
||||
c.Options().DatabaseDriver = MySQL
|
||||
c.Options().DatabaseDriver = enum.MySQL
|
||||
assert.Equal(t, "StoryOfAmélie", c.DatabasePassword())
|
||||
c.Options().DatabaseDriver = SQLite3
|
||||
c.Options().DatabaseDriver = enum.SQLite3
|
||||
_ = os.Setenv(FlagFileVar("DATABASE_PASSWORD"), "")
|
||||
|
||||
assert.Equal(t, "", c.DatabasePassword())
|
||||
|
|
@ -222,19 +224,19 @@ func TestShouldAutoRotateDatabase(t *testing.T) {
|
|||
t.Run("PortalAlwaysFalse", func(t *testing.T) {
|
||||
conf := NewMinimalTestConfig(t.TempDir())
|
||||
conf.Options().NodeRole = cluster.RolePortal
|
||||
conf.Options().DatabaseDriver = MySQL
|
||||
conf.Options().DatabaseDriver = enum.MySQL
|
||||
assert.False(t, conf.ShouldAutoRotateDatabase())
|
||||
})
|
||||
|
||||
t.Run("NonMySQLDriverFalse", func(t *testing.T) {
|
||||
conf := NewMinimalTestConfig(t.TempDir())
|
||||
conf.Options().DatabaseDriver = SQLite3
|
||||
conf.Options().DatabaseDriver = enum.SQLite3
|
||||
assert.False(t, conf.ShouldAutoRotateDatabase())
|
||||
})
|
||||
|
||||
t.Run("MySQLMissingFieldsTrue", func(t *testing.T) {
|
||||
conf := NewMinimalTestConfig(t.TempDir())
|
||||
conf.Options().DatabaseDriver = MySQL
|
||||
conf.Options().DatabaseDriver = enum.MySQL
|
||||
conf.Options().DatabaseName = "photoprism"
|
||||
conf.Options().DatabaseUser = ""
|
||||
conf.Options().DatabasePassword = ""
|
||||
|
|
@ -246,7 +248,7 @@ func TestConfig_DatabaseDSN(t *testing.T) {
|
|||
c := NewConfig(CliTestContext())
|
||||
resetDatabaseOptions(c)
|
||||
driver := c.DatabaseDriver()
|
||||
assert.Equal(t, SQLite3, driver)
|
||||
assert.Equal(t, enum.SQLite3, driver)
|
||||
c.options.DatabaseDSN = ""
|
||||
c.options.DatabaseDriver = "MariaDB"
|
||||
assert.Equal(t, "photoprism:@tcp(localhost)/photoprism?charset=utf8mb4,utf8&collation=utf8mb4_unicode_ci&parseTime=true&timeout=15s", c.DatabaseDSN())
|
||||
|
|
@ -263,7 +265,7 @@ func TestConfig_DatabaseDSN(t *testing.T) {
|
|||
conf := NewConfig(CliTestContext())
|
||||
resetDatabaseOptions(conf)
|
||||
|
||||
conf.options.DatabaseDriver = MySQL
|
||||
conf.options.DatabaseDriver = enum.MySQL
|
||||
conf.options.DatabaseServer = "proxy.internal:6032"
|
||||
conf.options.DatabaseName = "tenantdb"
|
||||
conf.options.DatabaseUser = "tenant"
|
||||
|
|
@ -279,7 +281,7 @@ func TestConfig_DatabaseDSN(t *testing.T) {
|
|||
conf := NewConfig(CliTestContext())
|
||||
resetDatabaseOptions(conf)
|
||||
|
||||
conf.options.DatabaseDriver = MySQL
|
||||
conf.options.DatabaseDriver = enum.MySQL
|
||||
conf.options.DatabaseServer = "/var/run/mysql.sock"
|
||||
conf.options.DatabaseName = "tenantdb"
|
||||
conf.options.DatabaseUser = "tenant"
|
||||
|
|
@ -305,7 +307,7 @@ func TestConfig_DatabaseDSNFlags(t *testing.T) {
|
|||
conf := NewConfig(CliTestContext())
|
||||
resetDatabaseOptions(conf)
|
||||
|
||||
conf.options.DatabaseDriver = MySQL
|
||||
conf.options.DatabaseDriver = enum.MySQL
|
||||
conf.options.Deprecated.DatabaseDsn = "user:pass@tcp(db.internal:3306)/photoprism"
|
||||
|
||||
assert.False(t, conf.NoDatabaseDSN())
|
||||
|
|
@ -319,10 +321,10 @@ func TestConfig_ReportDatabaseDSN(t *testing.T) {
|
|||
conf := NewConfig(CliTestContext())
|
||||
resetDatabaseOptions(conf)
|
||||
|
||||
assert.Equal(t, SQLite3, conf.DatabaseDriver())
|
||||
assert.Equal(t, enum.SQLite3, conf.DatabaseDriver())
|
||||
assert.True(t, conf.ReportDatabaseDSN())
|
||||
|
||||
conf.options.DatabaseDriver = MySQL
|
||||
conf.options.DatabaseDriver = enum.MySQL
|
||||
conf.options.DatabaseDSN = ""
|
||||
assert.False(t, conf.ReportDatabaseDSN())
|
||||
|
||||
|
|
@ -335,7 +337,7 @@ func TestConfig_DatabaseFile(t *testing.T) {
|
|||
// Ensure SQLite defaults
|
||||
resetDatabaseOptions(c)
|
||||
driver := c.DatabaseDriver()
|
||||
assert.Equal(t, SQLite3, driver)
|
||||
assert.Equal(t, enum.SQLite3, driver)
|
||||
c.options.DatabaseDSN = ""
|
||||
assert.Equal(t, ProjectRoot+"/storage/testdata/index.db", c.DatabaseFile())
|
||||
assert.Equal(t, ProjectRoot+"/storage/testdata/index.db?_busy_timeout=5000", c.DatabaseDSN())
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import (
|
|||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/thumb"
|
||||
"github.com/photoprism/photoprism/pkg/enum"
|
||||
)
|
||||
|
||||
func TestConfig_ConvertSize(t *testing.T) {
|
||||
|
|
@ -48,7 +49,7 @@ func TestConfig_ThumbFilter(t *testing.T) {
|
|||
assert.Equal(t, thumb.ResampleLanczos, c.ThumbFilter())
|
||||
c.options.ThumbFilter = "linear"
|
||||
assert.Equal(t, thumb.ResampleLinear, c.ThumbFilter())
|
||||
c.options.ThumbFilter = Auto
|
||||
c.options.ThumbFilter = enum.Auto
|
||||
assert.Equal(t, thumb.ResampleLanczos, c.ThumbFilter())
|
||||
c.options.ThumbFilter = ""
|
||||
assert.Equal(t, thumb.ResampleLanczos, c.ThumbFilter())
|
||||
|
|
@ -92,7 +93,7 @@ func TestConfig_PngSize(t *testing.T) {
|
|||
func TestConfig_ThumbLibrary(t *testing.T) {
|
||||
c := NewConfig(CliTestContext())
|
||||
assert.False(t, c.DisableVips())
|
||||
c.options.ThumbLibrary = Auto
|
||||
c.options.ThumbLibrary = enum.Auto
|
||||
assert.Equal(t, "vips", c.ThumbLibrary())
|
||||
c.options.DisableVips = true
|
||||
assert.Equal(t, "imaging", c.ThumbLibrary())
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import (
|
|||
"github.com/photoprism/photoprism/internal/service/hub/places"
|
||||
"github.com/photoprism/photoprism/internal/thumb"
|
||||
"github.com/photoprism/photoprism/pkg/authn"
|
||||
"github.com/photoprism/photoprism/pkg/enum"
|
||||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
"github.com/photoprism/photoprism/pkg/http/header"
|
||||
"github.com/photoprism/photoprism/pkg/http/scheme"
|
||||
|
|
@ -941,7 +942,7 @@ var Flags = CliFlags{
|
|||
Flag: &cli.StringFlag{
|
||||
Name: "database-provision-driver",
|
||||
Usage: "auto-provisioning `DRIVER` (auto, mysql)",
|
||||
Value: Auto,
|
||||
Value: enum.Auto,
|
||||
EnvVars: EnvVars("DATABASE_PROVISION_DRIVER"),
|
||||
Hidden: true,
|
||||
}}, {
|
||||
|
|
@ -1113,7 +1114,7 @@ var Flags = CliFlags{
|
|||
Name: "thumb-library",
|
||||
Aliases: []string{"thumbs"},
|
||||
Usage: "image processing `LIBRARY` to be used for generating thumbnails (auto, imaging, vips)",
|
||||
Value: Auto,
|
||||
Value: enum.Auto,
|
||||
EnvVars: EnvVars("THUMB_LIBRARY"),
|
||||
}}, {
|
||||
Flag: &cli.StringFlag{
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/photoprism/photoprism/pkg/enum"
|
||||
)
|
||||
|
||||
func TestConfig_Report(t *testing.T) {
|
||||
|
|
@ -34,7 +36,7 @@ func TestConfig_ReportDatabaseSection(t *testing.T) {
|
|||
rows, _ := conf.Report()
|
||||
values := collect(rows)
|
||||
|
||||
assert.Equal(t, SQLite3, values["database-driver"])
|
||||
assert.Equal(t, enum.SQLite3, values["database-driver"])
|
||||
assert.Equal(t, conf.DatabaseDSN(), values["database-dsn"])
|
||||
_, hasName := values["database-name"]
|
||||
assert.False(t, hasName)
|
||||
|
|
@ -43,7 +45,7 @@ func TestConfig_ReportDatabaseSection(t *testing.T) {
|
|||
conf := NewConfig(CliTestContext())
|
||||
resetDatabaseOptions(conf)
|
||||
|
||||
conf.options.DatabaseDriver = MySQL
|
||||
conf.options.DatabaseDriver = enum.MySQL
|
||||
conf.options.DatabaseServer = "db.internal:3306"
|
||||
conf.options.DatabaseName = "photoprism"
|
||||
conf.options.DatabaseUser = "app"
|
||||
|
|
@ -52,7 +54,7 @@ func TestConfig_ReportDatabaseSection(t *testing.T) {
|
|||
rows, _ := conf.Report()
|
||||
values := collect(rows)
|
||||
|
||||
assert.Equal(t, MySQL, values["database-driver"])
|
||||
assert.Equal(t, enum.MySQL, values["database-driver"])
|
||||
assert.Equal(t, "photoprism", values["database-name"])
|
||||
assert.Equal(t, "db.internal:3306", values["database-server"])
|
||||
assert.Equal(t, "db.internal", values["database-host"])
|
||||
|
|
@ -66,13 +68,13 @@ func TestConfig_ReportDatabaseSection(t *testing.T) {
|
|||
conf := NewConfig(CliTestContext())
|
||||
resetDatabaseOptions(conf)
|
||||
|
||||
conf.options.DatabaseDriver = MySQL
|
||||
conf.options.DatabaseDriver = enum.MySQL
|
||||
conf.options.DatabaseDSN = "user:pass@tcp(db.internal:3306)/photoprism"
|
||||
|
||||
rows, _ := conf.Report()
|
||||
values := collect(rows)
|
||||
|
||||
assert.Equal(t, MySQL, values["database-driver"])
|
||||
assert.Equal(t, enum.MySQL, values["database-driver"])
|
||||
assert.Equal(t, "user:***@tcp(db.internal:3306)/photoprism?charset=utf8mb4,utf8&collation=utf8mb4_unicode_ci&parseTime=true&timeout=15s", values["database-dsn"])
|
||||
_, hasName := values["database-name"]
|
||||
assert.False(t, hasName)
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ import (
|
|||
"github.com/photoprism/photoprism/pkg/authn"
|
||||
"github.com/photoprism/photoprism/pkg/capture"
|
||||
"github.com/photoprism/photoprism/pkg/clean"
|
||||
"github.com/photoprism/photoprism/pkg/dsn"
|
||||
"github.com/photoprism/photoprism/pkg/enum"
|
||||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
"github.com/photoprism/photoprism/pkg/rnd"
|
||||
"github.com/photoprism/photoprism/pkg/txt/report"
|
||||
|
|
@ -127,19 +127,19 @@ func NewTestOptionsForPath(dbName, dataPath string) *Options {
|
|||
|
||||
// Set default test database driver.
|
||||
if testDriver == "test" || testDriver == "sqlite" || testDriver == "" || testDsn == "" {
|
||||
testDriver = dsn.DriverSQLite3
|
||||
testDriver = enum.SQLite3
|
||||
}
|
||||
|
||||
// Set default database DSN.
|
||||
if testDriver == dsn.DriverSQLite3 {
|
||||
if testDriver == enum.SQLite3 {
|
||||
if testDsn == "" && dbName != "" {
|
||||
if testDsn = fmt.Sprintf(".%s.db", clean.TypeLower(dbName)); !fs.FileExists(testDsn) {
|
||||
log.Tracef("sqlite: test database %s does not already exist", clean.Log(testDsn))
|
||||
} else if err := os.Remove(testDsn); err != nil {
|
||||
log.Errorf("sqlite: failed to remove existing test database %s (%s)", clean.Log(testDsn), err)
|
||||
}
|
||||
} else if testDsn == "" || testDsn == dsn.SQLiteTestDB {
|
||||
testDsn = dsn.SQLiteTestDB
|
||||
} else if testDsn == "" || testDsn == enum.SQLiteTestDB {
|
||||
testDsn = enum.SQLiteTestDB
|
||||
if !fs.FileExists(testDsn) {
|
||||
log.Tracef("sqlite: test database %s does not already exist", clean.Log(testDsn))
|
||||
} else if err := os.Remove(testDsn); err != nil {
|
||||
|
|
@ -205,7 +205,7 @@ func NewTestOptionsError() *Options {
|
|||
OriginalsPath: dataPath + "/originals",
|
||||
ImportPath: dataPath + "/import",
|
||||
TempPath: dataPath + "/temp",
|
||||
DatabaseDriver: SQLite3,
|
||||
DatabaseDriver: enum.SQLite3,
|
||||
DatabaseDSN: ".test-error.db",
|
||||
}
|
||||
|
||||
|
|
@ -244,7 +244,7 @@ func NewMinimalTestConfigWithDb(dbName, dataPath string) *Config {
|
|||
cachedDb := false
|
||||
|
||||
// Try to restore test db from cache.
|
||||
if len(testDbCache) > 0 && c.DatabaseDriver() == SQLite3 && !fs.FileExists(c.DatabaseDSN()) {
|
||||
if len(testDbCache) > 0 && c.DatabaseDriver() == enum.SQLite3 && !fs.FileExists(c.DatabaseDSN()) {
|
||||
if err := os.WriteFile(c.DatabaseDSN(), testDbCache, fs.ModeFile); err != nil {
|
||||
log.Warnf("config: %s (restore test database)", err)
|
||||
} else {
|
||||
|
|
@ -264,7 +264,7 @@ func NewMinimalTestConfigWithDb(dbName, dataPath string) *Config {
|
|||
|
||||
c.InitTestDb()
|
||||
|
||||
if testDbCache == nil && c.DatabaseDriver() == SQLite3 && fs.FileExistsNotEmpty(c.DatabaseDSN()) {
|
||||
if testDbCache == nil && c.DatabaseDriver() == enum.SQLite3 && fs.FileExistsNotEmpty(c.DatabaseDSN()) {
|
||||
testDbMutex.Lock()
|
||||
defer testDbMutex.Unlock()
|
||||
|
||||
|
|
|
|||
|
|
@ -8,16 +8,6 @@ import (
|
|||
"github.com/jinzhu/gorm"
|
||||
_ "github.com/jinzhu/gorm/dialects/mysql"
|
||||
_ "github.com/jinzhu/gorm/dialects/sqlite"
|
||||
|
||||
"github.com/photoprism/photoprism/pkg/dsn"
|
||||
)
|
||||
|
||||
// Supported test databases.
|
||||
const (
|
||||
MySQL = dsn.DriverMySQL
|
||||
SQLite3 = dsn.DriverSQLite3
|
||||
SQLiteTestDB = ".test.db"
|
||||
SQLiteMemoryDSN = ":memory:?cache=shared"
|
||||
)
|
||||
|
||||
// dbConn is the global gorm.DB connection provider.
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import (
|
|||
|
||||
"github.com/photoprism/photoprism/internal/mutex"
|
||||
"github.com/photoprism/photoprism/pkg/clean"
|
||||
"github.com/photoprism/photoprism/pkg/enum"
|
||||
"github.com/photoprism/photoprism/pkg/time/unix"
|
||||
)
|
||||
|
||||
|
|
@ -103,7 +104,7 @@ func UpdateSubjectCounts(public bool) (err error) {
|
|||
condition := gorm.Expr("subj_type = ?", SubjPerson)
|
||||
|
||||
switch DbDialect() {
|
||||
case MySQL:
|
||||
case enum.MySQL:
|
||||
res = Db().Exec(`UPDATE ? LEFT JOIN (
|
||||
SELECT m.subj_uid, COUNT(DISTINCT f.id) AS subj_files, COUNT(DISTINCT f.photo_id) AS subj_photos
|
||||
FROM files f
|
||||
|
|
@ -114,7 +115,7 @@ func UpdateSubjectCounts(public bool) (err error) {
|
|||
SET subjects.file_count = CASE WHEN b.subj_files IS NULL THEN 0 ELSE b.subj_files END,
|
||||
subjects.photo_count = CASE WHEN b.subj_photos IS NULL THEN 0 ELSE b.subj_photos END
|
||||
WHERE ?`, gorm.Expr(subjTable), photosJoin, condition)
|
||||
case SQLite3:
|
||||
case enum.SQLite3:
|
||||
// Update files count.
|
||||
res = Db().Table(subjTable).
|
||||
UpdateColumn("file_count", gorm.Expr("(SELECT COUNT(DISTINCT f.id)"+
|
||||
|
|
@ -187,7 +188,7 @@ func UpdateLabelCounts() (err error) {
|
|||
|
||||
start := time.Now()
|
||||
var res *gorm.DB
|
||||
if IsDialect(MySQL) {
|
||||
if IsDialect(enum.MySQL) {
|
||||
res = Db().Exec(`UPDATE labels LEFT JOIN (
|
||||
SELECT p2.label_id, COUNT(DISTINCT photo_id) AS label_photos FROM (
|
||||
SELECT pl.label_id as label_id, p.id AS photo_id FROM photos p
|
||||
|
|
@ -201,7 +202,7 @@ func UpdateLabelCounts() (err error) {
|
|||
) p2 GROUP BY p2.label_id
|
||||
) b ON b.label_id = labels.id
|
||||
SET photo_count = CASE WHEN b.label_photos IS NULL THEN 0 ELSE b.label_photos END`)
|
||||
} else if IsDialect(SQLite3) {
|
||||
} else if IsDialect(enum.SQLite3) {
|
||||
res = Db().
|
||||
Table("labels").
|
||||
UpdateColumn("photo_count",
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import (
|
|||
|
||||
"github.com/photoprism/photoprism/internal/entity/migrate"
|
||||
"github.com/photoprism/photoprism/pkg/clean"
|
||||
"github.com/photoprism/photoprism/pkg/enum"
|
||||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
)
|
||||
|
||||
|
|
@ -55,13 +56,13 @@ func InitTestDb(driver, dbDsn string) *DbConn {
|
|||
|
||||
// Set default test database driver.
|
||||
if driver == "test" || driver == "sqlite" || driver == "" || dbDsn == "" {
|
||||
driver = SQLite3
|
||||
driver = enum.SQLite3
|
||||
}
|
||||
|
||||
// Set default database DSN.
|
||||
if driver == SQLite3 {
|
||||
if dbDsn == "" || dbDsn == SQLiteTestDB {
|
||||
dbDsn = SQLiteTestDB
|
||||
if driver == enum.SQLite3 {
|
||||
if dbDsn == "" || dbDsn == enum.SQLiteTestDB {
|
||||
dbDsn = enum.SQLiteTestDB
|
||||
if !fs.FileExists(dbDsn) {
|
||||
log.Debugf("sqlite: test database %s does not already exist", clean.Log(dbDsn))
|
||||
} else if err := os.Remove(dbDsn); err != nil {
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/ai/face"
|
||||
"github.com/photoprism/photoprism/pkg/enum"
|
||||
"github.com/photoprism/photoprism/pkg/rnd"
|
||||
)
|
||||
|
||||
|
|
@ -340,7 +341,7 @@ func (m *Face) RefreshPhotos() error {
|
|||
|
||||
var err error
|
||||
switch DbDialect() {
|
||||
case MySQL:
|
||||
case enum.MySQL:
|
||||
update := fmt.Sprintf(`UPDATE photos p JOIN files f ON f.photo_id = p.id JOIN %s m ON m.file_uid = f.file_uid
|
||||
SET p.checked_at = NULL WHERE m.face_id = ?`, Marker{}.TableName())
|
||||
err = UnscopedDb().Exec(update, m.ID).Error
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import (
|
|||
"github.com/photoprism/photoprism/internal/ai/face"
|
||||
"github.com/photoprism/photoprism/internal/config/customize"
|
||||
"github.com/photoprism/photoprism/pkg/clean"
|
||||
"github.com/photoprism/photoprism/pkg/enum"
|
||||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
"github.com/photoprism/photoprism/pkg/media"
|
||||
"github.com/photoprism/photoprism/pkg/media/colors"
|
||||
|
|
@ -129,7 +130,7 @@ func (m File) RegenerateIndex() {
|
|||
}
|
||||
|
||||
switch DbDialect() {
|
||||
case MySQL:
|
||||
case enum.MySQL:
|
||||
Log("files", "regenerate photo_taken_at",
|
||||
Db().Exec("UPDATE files JOIN ? p ON p.id = files.photo_id SET files.photo_taken_at = p.taken_at_local WHERE ?",
|
||||
gorm.Expr(photosTable), updateWhere).Error)
|
||||
|
|
@ -141,7 +142,7 @@ func (m File) RegenerateIndex() {
|
|||
Log("files", "regenerate time_index",
|
||||
Db().Exec("UPDATE files SET time_index = CASE WHEN media_id IS NOT NULL AND photo_taken_at IS NOT NULL THEN CONCAT(100000000000000 - CAST(photo_taken_at AS UNSIGNED), '-', media_id) ELSE NULL END WHERE ?",
|
||||
updateWhere).Error)
|
||||
case SQLite3:
|
||||
case enum.SQLite3:
|
||||
Log("files", "regenerate photo_taken_at",
|
||||
Db().Exec("UPDATE files SET photo_taken_at = (SELECT p.taken_at_local FROM ? p WHERE p.id = photo_id) WHERE ?",
|
||||
gorm.Expr(photosTable), updateWhere).Error)
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import (
|
|||
"github.com/photoprism/photoprism/internal/form"
|
||||
"github.com/photoprism/photoprism/internal/thumb/crop"
|
||||
"github.com/photoprism/photoprism/pkg/clean"
|
||||
"github.com/photoprism/photoprism/pkg/enum"
|
||||
"github.com/photoprism/photoprism/pkg/rnd"
|
||||
)
|
||||
|
||||
|
|
@ -575,7 +576,7 @@ func (m *Marker) RefreshPhotos() error {
|
|||
|
||||
var err error
|
||||
switch DbDialect() {
|
||||
case MySQL:
|
||||
case enum.MySQL:
|
||||
err = UnscopedDb().Exec(`UPDATE photos p JOIN files f ON f.photo_id = p.id
|
||||
JOIN ? m ON m.file_uid = f.file_uid SET p.checked_at = NULL
|
||||
WHERE m.marker_uid = ?`,
|
||||
|
|
|
|||
|
|
@ -1,19 +1,17 @@
|
|||
package migrate
|
||||
|
||||
import "sync"
|
||||
import (
|
||||
"sync"
|
||||
|
||||
// Supported database dialects.
|
||||
const (
|
||||
MySQL = "mysql"
|
||||
SQLite3 = "sqlite3"
|
||||
"github.com/photoprism/photoprism/pkg/enum"
|
||||
)
|
||||
|
||||
var Dialects = map[string]Migrations{
|
||||
MySQL: DialectMySQL,
|
||||
SQLite3: DialectSQLite3,
|
||||
enum.MySQL: DialectMySQL,
|
||||
enum.SQLite3: DialectSQLite3,
|
||||
}
|
||||
|
||||
var once = map[string]*sync.Once{
|
||||
MySQL: {},
|
||||
SQLite3: {},
|
||||
enum.MySQL: {},
|
||||
enum.SQLite3: {},
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/entity/migrate"
|
||||
"github.com/photoprism/photoprism/pkg/enum"
|
||||
|
||||
"github.com/jinzhu/gorm"
|
||||
)
|
||||
|
|
@ -17,7 +18,7 @@ func TestMySQL8(t *testing.T) {
|
|||
t.Skip("skipping MySQL 8 test: PHOTOPRISM_TEST_DSN_MYSQL8 is not set")
|
||||
}
|
||||
|
||||
dbDriver := MySQL
|
||||
dbDriver := enum.MySQL
|
||||
db, err := gorm.Open(dbDriver, dbDsn)
|
||||
|
||||
if err != nil || db == nil {
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import (
|
|||
"github.com/jinzhu/gorm"
|
||||
|
||||
"github.com/photoprism/photoprism/pkg/clean"
|
||||
"github.com/photoprism/photoprism/pkg/enum"
|
||||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
"github.com/photoprism/photoprism/pkg/geo"
|
||||
"github.com/photoprism/photoprism/pkg/txt"
|
||||
|
|
@ -99,14 +100,14 @@ func (m *Photo) EstimateLocation(force bool) {
|
|||
var mostRecent Photos
|
||||
|
||||
switch DbDialect() {
|
||||
case MySQL:
|
||||
case enum.MySQL:
|
||||
err = UnscopedDb().
|
||||
Where("photo_lat <> 0 AND photo_lng <> 0").
|
||||
Where("place_src <> '' AND place_src <> ? AND place_id IS NOT NULL AND place_id <> '' AND place_id <> 'zz'", SrcEstimate).
|
||||
Where("taken_src <> '' AND taken_at BETWEEN CAST(? AS DATETIME) AND CAST(? AS DATETIME)", rangeMin, rangeMax).
|
||||
Order(gorm.Expr("ABS(TIMESTAMPDIFF(SECOND, taken_at, ?))", m.TakenAt)).Limit(2).
|
||||
Preload("Place").Find(&mostRecent).Error
|
||||
case SQLite3:
|
||||
case enum.SQLite3:
|
||||
err = UnscopedDb().
|
||||
Where("photo_lat <> 0 AND photo_lng <> 0").
|
||||
Where("place_src <> '' AND place_src <> ? AND place_id IS NOT NULL AND place_id <> '' AND place_id <> 'zz'", SrcEstimate).
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import (
|
|||
|
||||
"github.com/jinzhu/gorm"
|
||||
|
||||
"github.com/photoprism/photoprism/pkg/enum"
|
||||
"github.com/photoprism/photoprism/pkg/rnd"
|
||||
)
|
||||
|
||||
|
|
@ -108,11 +109,11 @@ func (m *Photo) Merge(mergeMeta, mergeUuid bool) (original Photo, merged Photos,
|
|||
logResult(UnscopedDb().Exec("UPDATE photos SET photo_quality = -1, deleted_at = ? WHERE id = ?", Now(), merge.ID))
|
||||
|
||||
switch DbDialect() {
|
||||
case MySQL:
|
||||
case enum.MySQL:
|
||||
logResult(UnscopedDb().Exec("UPDATE IGNORE photos_keywords SET photo_id = ? WHERE photo_id = ?", original.ID, merge.ID))
|
||||
logResult(UnscopedDb().Exec("UPDATE IGNORE photos_labels SET photo_id = ? WHERE photo_id = ?", original.ID, merge.ID))
|
||||
logResult(UnscopedDb().Exec("UPDATE IGNORE photos_albums SET photo_uid = ? WHERE photo_uid = ?", original.PhotoUID, merge.PhotoUID))
|
||||
case SQLite3:
|
||||
case enum.SQLite3:
|
||||
logResult(UnscopedDb().Exec("UPDATE OR IGNORE photos_keywords SET photo_id = ? WHERE photo_id = ?", original.ID, merge.ID))
|
||||
logResult(UnscopedDb().Exec("UPDATE OR IGNORE photos_labels SET photo_id = ? WHERE photo_id = ?", original.ID, merge.ID))
|
||||
logResult(UnscopedDb().Exec("UPDATE OR IGNORE photos_albums SET photo_uid = ? WHERE photo_uid = ?", original.PhotoUID, merge.PhotoUID))
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import (
|
|||
|
||||
"github.com/photoprism/photoprism/internal/entity/sortby"
|
||||
"github.com/photoprism/photoprism/pkg/clean"
|
||||
"github.com/photoprism/photoprism/pkg/enum"
|
||||
"github.com/photoprism/photoprism/pkg/media"
|
||||
"github.com/photoprism/photoprism/pkg/rnd"
|
||||
)
|
||||
|
|
@ -120,7 +121,7 @@ func UpdateAlbumDates() error {
|
|||
defer mutex.Index.Unlock()
|
||||
|
||||
switch DbDialect() {
|
||||
case MySQL:
|
||||
case enum.MySQL:
|
||||
return UnscopedDb().Exec(`UPDATE albums INNER JOIN (
|
||||
SELECT photo_path, MAX(taken_at_local) AS taken_max
|
||||
FROM photos WHERE taken_src = 'meta' AND photos.photo_quality >= 3 AND photos.deleted_at IS NULL
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ import (
|
|||
"github.com/photoprism/photoprism/internal/entity"
|
||||
"github.com/photoprism/photoprism/internal/mutex"
|
||||
"github.com/photoprism/photoprism/pkg/clean"
|
||||
"github.com/photoprism/photoprism/pkg/enum"
|
||||
"github.com/photoprism/photoprism/pkg/media"
|
||||
)
|
||||
|
||||
|
|
@ -45,7 +46,7 @@ func UpdateAlbumManualCovers(albums ...entity.Album) (err error) {
|
|||
condition := gorm.Expr("album_type = ? AND thumb_src = ?", entity.AlbumManual, entity.SrcAuto)
|
||||
|
||||
switch DbDialect() {
|
||||
case MySQL:
|
||||
case enum.MySQL:
|
||||
res = Db().Exec(`UPDATE albums LEFT JOIN (
|
||||
SELECT p2.album_uid, f.file_hash FROM files f, (
|
||||
SELECT pa.album_uid, max(p.id) AS photo_id FROM photos p
|
||||
|
|
@ -54,7 +55,7 @@ func UpdateAlbumManualCovers(albums ...entity.Album) (err error) {
|
|||
GROUP BY pa.album_uid) p2 WHERE p2.photo_id = f.photo_id AND f.file_primary = 1 AND f.file_error = '' AND f.file_type IN (?)
|
||||
) b ON b.album_uid = albums.album_uid
|
||||
SET thumb = b.file_hash WHERE ?`, media.PreviewExpr, condition)
|
||||
case SQLite3:
|
||||
case enum.SQLite3:
|
||||
res = Db().Table(entity.Album{}.TableName()).
|
||||
UpdateColumn("thumb", gorm.Expr(`(
|
||||
SELECT f.file_hash FROM files f
|
||||
|
|
@ -108,7 +109,7 @@ func UpdateAlbumFolderCovers(albums ...entity.Album) (err error) {
|
|||
condition := gorm.Expr("album_type = ? AND thumb_src = ?", entity.AlbumFolder, entity.SrcAuto)
|
||||
|
||||
switch DbDialect() {
|
||||
case MySQL:
|
||||
case enum.MySQL:
|
||||
res = Db().Exec(`UPDATE albums LEFT JOIN (
|
||||
SELECT p2.photo_path, f.file_hash FROM files f, (
|
||||
SELECT p.photo_path, max(p.id) AS photo_id FROM photos p
|
||||
|
|
@ -116,7 +117,7 @@ func UpdateAlbumFolderCovers(albums ...entity.Album) (err error) {
|
|||
GROUP BY p.photo_path) p2 WHERE p2.photo_id = f.photo_id AND f.file_primary = 1 AND f.file_error = '' AND f.file_type IN (?)
|
||||
) b ON b.photo_path = albums.album_path
|
||||
SET thumb = b.file_hash WHERE ?`, media.PreviewExpr, condition)
|
||||
case SQLite3:
|
||||
case enum.SQLite3:
|
||||
res = Db().Table(entity.Album{}.TableName()).UpdateColumn("thumb", gorm.Expr(`(
|
||||
SELECT f.file_hash FROM files f,(
|
||||
SELECT p.photo_path, max(p.id) AS photo_id FROM photos p
|
||||
|
|
@ -171,7 +172,7 @@ func UpdateAlbumMonthCovers(albums ...entity.Album) (err error) {
|
|||
condition := gorm.Expr("album_type = ? AND thumb_src = ?", entity.AlbumMonth, entity.SrcAuto)
|
||||
|
||||
switch DbDialect() {
|
||||
case MySQL:
|
||||
case enum.MySQL:
|
||||
res = Db().Exec(`UPDATE albums LEFT JOIN (
|
||||
SELECT p2.photo_year, p2.photo_month, f.file_hash FROM files f, (
|
||||
SELECT p.photo_year, p.photo_month, max(p.id) AS photo_id FROM photos p
|
||||
|
|
@ -179,7 +180,7 @@ func UpdateAlbumMonthCovers(albums ...entity.Album) (err error) {
|
|||
GROUP BY p.photo_year, p.photo_month) p2 WHERE p2.photo_id = f.photo_id AND f.file_primary = 1 AND f.file_error = '' AND f.file_type IN (?)
|
||||
) b ON b.photo_year = albums.album_year AND b.photo_month = albums.album_month
|
||||
SET thumb = b.file_hash WHERE ?`, media.PreviewExpr, condition)
|
||||
case SQLite3:
|
||||
case enum.SQLite3:
|
||||
res = Db().Table(entity.Album{}.TableName()).UpdateColumn("thumb", gorm.Expr(`(
|
||||
SELECT f.file_hash FROM files f,(
|
||||
SELECT p.photo_year, p.photo_month, max(p.id) AS photo_id FROM photos p
|
||||
|
|
@ -322,7 +323,7 @@ func refreshFolderAlbumCover(album entity.Album) error {
|
|||
}
|
||||
|
||||
switch DbDialect() {
|
||||
case MySQL:
|
||||
case enum.MySQL:
|
||||
res := Db().Exec(`UPDATE albums LEFT JOIN (
|
||||
SELECT p2.photo_path, f.file_hash FROM files f, (
|
||||
SELECT p.photo_path, max(p.id) AS photo_id FROM photos p
|
||||
|
|
@ -338,7 +339,7 @@ func refreshFolderAlbumCover(album entity.Album) error {
|
|||
)
|
||||
|
||||
return res.Error
|
||||
case SQLite3:
|
||||
case enum.SQLite3:
|
||||
res := Db().Table(entity.Album{}.TableName()).
|
||||
Where("album_uid = ? AND album_type = ? AND thumb_src = ?", album.AlbumUID, entity.AlbumFolder, entity.SrcAuto).
|
||||
UpdateColumn("thumb", gorm.Expr(`(
|
||||
|
|
@ -366,7 +367,7 @@ func refreshMonthAlbumCover(album entity.Album) error {
|
|||
}
|
||||
|
||||
switch DbDialect() {
|
||||
case MySQL:
|
||||
case enum.MySQL:
|
||||
res := Db().Exec(`UPDATE albums LEFT JOIN (
|
||||
SELECT p2.photo_year, p2.photo_month, f.file_hash FROM files f, (
|
||||
SELECT p.photo_year, p.photo_month, max(p.id) AS photo_id FROM photos p
|
||||
|
|
@ -383,7 +384,7 @@ func refreshMonthAlbumCover(album entity.Album) error {
|
|||
)
|
||||
|
||||
return res.Error
|
||||
case SQLite3:
|
||||
case enum.SQLite3:
|
||||
res := Db().Table(entity.Album{}.TableName()).
|
||||
Where("album_uid = ? AND album_type = ? AND thumb_src = ?", album.AlbumUID, entity.AlbumMonth, entity.SrcAuto).
|
||||
UpdateColumn("thumb", gorm.Expr(`(
|
||||
|
|
@ -416,7 +417,7 @@ func UpdateLabelCovers() (err error) {
|
|||
condition := gorm.Expr("thumb_src = ?", entity.SrcAuto)
|
||||
|
||||
switch DbDialect() {
|
||||
case MySQL:
|
||||
case enum.MySQL:
|
||||
res = Db().Exec(`UPDATE labels LEFT JOIN (
|
||||
SELECT p2.label_id, f.file_hash FROM files f, (
|
||||
SELECT pl.label_id as label_id, max(p.id) AS photo_id FROM photos p
|
||||
|
|
@ -432,7 +433,7 @@ func UpdateLabelCovers() (err error) {
|
|||
) p2 WHERE p2.photo_id = f.photo_id AND f.file_primary = 1 AND f.file_error = '' AND f.file_type IN (?) AND f.file_missing = 0
|
||||
) b ON b.label_id = labels.id
|
||||
SET thumb = b.file_hash WHERE ?`, media.PreviewExpr, condition)
|
||||
case SQLite3:
|
||||
case enum.SQLite3:
|
||||
res = Db().Table(entity.Label{}.TableName()).UpdateColumn("thumb", gorm.Expr(`(
|
||||
SELECT f.file_hash FROM files f
|
||||
JOIN photos_labels pl ON pl.label_id = labels.id AND pl.photo_id = f.photo_id AND pl.uncertainty < 100
|
||||
|
|
@ -494,7 +495,7 @@ func UpdateSubjectCovers(public bool) (err error) {
|
|||
|
||||
// Compose SQL update query.
|
||||
switch DbDialect() {
|
||||
case MySQL:
|
||||
case enum.MySQL:
|
||||
res = Db().Exec(`UPDATE subjects LEFT JOIN (
|
||||
SELECT m.subj_uid, m.q, MAX(m.thumb) AS marker_thumb
|
||||
FROM markers m
|
||||
|
|
@ -508,7 +509,7 @@ func UpdateSubjectCovers(public bool) (err error) {
|
|||
photosJoin,
|
||||
condition,
|
||||
)
|
||||
case SQLite3:
|
||||
case enum.SQLite3:
|
||||
// from := gorm.Expr(fmt.Sprintf("%s m WHERE m.subj_uid = %s.subj_uid ", markerTable, subjTable))
|
||||
res = Db().Table(entity.Subject{}.TableName()).UpdateColumn("thumb",
|
||||
gorm.Expr(`(
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/photoprism/photoprism/pkg/enum"
|
||||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/entity"
|
||||
|
|
@ -98,9 +99,9 @@ func SelectedFiles(frm form.Selection, o FileSelection) (results entity.Files, e
|
|||
|
||||
var concat string
|
||||
switch DbDialect() {
|
||||
case MySQL:
|
||||
case enum.MySQL:
|
||||
concat = "CONCAT(a.path, '/%')"
|
||||
case SQLite3:
|
||||
case enum.SQLite3:
|
||||
concat = "a.path || '/%'"
|
||||
default:
|
||||
return results, fmt.Errorf("unknown sql dialect: %s", DbDialect())
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import (
|
|||
|
||||
"github.com/photoprism/photoprism/internal/entity"
|
||||
"github.com/photoprism/photoprism/internal/mutex"
|
||||
"github.com/photoprism/photoprism/pkg/enum"
|
||||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
"github.com/photoprism/photoprism/pkg/media"
|
||||
)
|
||||
|
|
@ -75,7 +76,7 @@ func UpdateFolderDates() error {
|
|||
defer mutex.Index.Unlock()
|
||||
|
||||
switch DbDialect() {
|
||||
case MySQL:
|
||||
case enum.MySQL:
|
||||
return UnscopedDb().Exec(`UPDATE folders
|
||||
INNER JOIN
|
||||
(SELECT photo_path, MAX(taken_at_local) AS taken_max
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import (
|
|||
|
||||
"github.com/photoprism/photoprism/internal/entity"
|
||||
"github.com/photoprism/photoprism/internal/form"
|
||||
"github.com/photoprism/photoprism/pkg/enum"
|
||||
)
|
||||
|
||||
// SelectedPhotos finds photos based on the given selection form, e.g. for adding them to an album.
|
||||
|
|
@ -24,9 +25,9 @@ func SelectedPhotos(frm form.Selection) (results entity.Photos, err error) {
|
|||
var concat string
|
||||
|
||||
switch DbDialect() {
|
||||
case MySQL:
|
||||
case enum.MySQL:
|
||||
concat = "CONCAT(a.path, '/%')"
|
||||
case SQLite3:
|
||||
case enum.SQLite3:
|
||||
concat = "a.path || '/%'"
|
||||
default:
|
||||
return results, fmt.Errorf("unknown sql dialect: %s", DbDialect())
|
||||
|
|
|
|||
|
|
@ -29,16 +29,11 @@ import (
|
|||
|
||||
"github.com/photoprism/photoprism/internal/entity"
|
||||
"github.com/photoprism/photoprism/internal/event"
|
||||
"github.com/photoprism/photoprism/pkg/enum"
|
||||
)
|
||||
|
||||
var log = event.Log
|
||||
|
||||
// Supported database dialect identifiers.
|
||||
const (
|
||||
MySQL = "mysql"
|
||||
SQLite3 = "sqlite3"
|
||||
)
|
||||
|
||||
// Cols represents a list of database columns.
|
||||
type Cols []string
|
||||
|
||||
|
|
@ -79,7 +74,7 @@ func DbDialect() string {
|
|||
// BatchSize returns the maximum query parameter number based on the current sql database dialect.
|
||||
func BatchSize() int {
|
||||
switch DbDialect() {
|
||||
case SQLite3:
|
||||
case enum.SQLite3:
|
||||
return 333
|
||||
default:
|
||||
return 1000
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import (
|
|||
// expressions. It strips operators that we don't expect to persist in the
|
||||
// statement and lets callers provide their own surrounding wildcards.
|
||||
func Like(s string) string {
|
||||
return strings.Trim(clean.SqlString(s), " |&*%")
|
||||
return strings.Trim(clean.SQLString(s, Db().Dialect().GetName()), " |&*%")
|
||||
}
|
||||
|
||||
// LikeAny builds OR-chained LIKE predicates for a text column. The input string
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import (
|
|||
"github.com/photoprism/photoprism/internal/entity"
|
||||
|
||||
"github.com/photoprism/photoprism/pkg/clean"
|
||||
"github.com/photoprism/photoprism/pkg/enum"
|
||||
"github.com/photoprism/photoprism/pkg/txt"
|
||||
)
|
||||
|
||||
|
|
@ -16,8 +17,11 @@ func TestLike(t *testing.T) {
|
|||
assert.Equal(t, "", Like(""))
|
||||
})
|
||||
t.Run("Special", func(t *testing.T) {
|
||||
s := " ' \" \t \n %_''"
|
||||
exp := "'' \"\" %_''''"
|
||||
s := " ' \" \t \n %_''\\"
|
||||
exp := "'' \" %_''''\\"
|
||||
if entity.DbDialect() == enum.MySQL {
|
||||
exp = "'' \" %_''''\\\\"
|
||||
}
|
||||
result := Like(s)
|
||||
t.Logf("String..: %s", s)
|
||||
t.Logf("Expected: %s", exp)
|
||||
|
|
@ -135,7 +139,7 @@ func TestLikeAnyWord(t *testing.T) {
|
|||
t.Fatalf("two where conditions expected: %#v", w)
|
||||
} else {
|
||||
assert.Equal(t, "k.keyword LIKE 'spoon%' OR k.keyword LIKE 'table%'", w[0])
|
||||
assert.Equal(t, "k.keyword LIKE '\"\"us''a%'", w[1])
|
||||
assert.Equal(t, "k.keyword LIKE '\"us''a%'", w[1])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,21 +4,18 @@ import (
|
|||
"github.com/jinzhu/gorm"
|
||||
_ "github.com/jinzhu/gorm/dialects/mysql"
|
||||
_ "github.com/jinzhu/gorm/dialects/sqlite"
|
||||
)
|
||||
|
||||
const (
|
||||
MySQL = "mysql"
|
||||
SQLite3 = "sqlite3"
|
||||
"github.com/photoprism/photoprism/pkg/enum"
|
||||
)
|
||||
|
||||
// RandomExpr returns the name of the random function depending on the SQL dialect.
|
||||
func RandomExpr(dialect gorm.Dialect) *gorm.SqlExpr {
|
||||
switch dialect.GetName() {
|
||||
case MySQL:
|
||||
case enum.MySQL:
|
||||
// A seed integer can be passed as an argument, e.g. "RAND(2342)", to generate
|
||||
// reproducible pseudo-random values, see https://mariadb.com/kb/en/rand/.
|
||||
return gorm.Expr("RAND()")
|
||||
case SQLite3:
|
||||
case enum.SQLite3:
|
||||
// SQLite does not support specifying a seed to generate a deterministic sequence
|
||||
// of pseudo-random values, see https://www.sqlite.org/lang_corefunc.html#random.
|
||||
return gorm.Expr("RANDOM()")
|
||||
|
|
|
|||
|
|
@ -5,12 +5,14 @@ import (
|
|||
|
||||
"github.com/jinzhu/gorm"
|
||||
|
||||
"github.com/photoprism/photoprism/pkg/enum"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestRandomExpr(t *testing.T) {
|
||||
mysql, _ := gorm.GetDialect(MySQL)
|
||||
sqlite3, _ := gorm.GetDialect(SQLite3)
|
||||
mysql, _ := gorm.GetDialect(enum.MySQL)
|
||||
sqlite3, _ := gorm.GetDialect(enum.SQLite3)
|
||||
|
||||
assert.Equal(t, gorm.Expr("RAND()"), RandomExpr(mysql))
|
||||
assert.Equal(t, gorm.Expr("RANDOM()"), RandomExpr(sqlite3))
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import (
|
|||
"github.com/photoprism/photoprism/internal/event"
|
||||
"github.com/photoprism/photoprism/internal/form"
|
||||
"github.com/photoprism/photoprism/pkg/clean"
|
||||
"github.com/photoprism/photoprism/pkg/enum"
|
||||
"github.com/photoprism/photoprism/pkg/rnd"
|
||||
"github.com/photoprism/photoprism/pkg/txt"
|
||||
)
|
||||
|
|
@ -490,7 +491,7 @@ func (m *Subject) RefreshPhotos() error {
|
|||
|
||||
var err error
|
||||
switch DbDialect() {
|
||||
case MySQL:
|
||||
case enum.MySQL:
|
||||
update := fmt.Sprintf(`UPDATE photos p JOIN files f ON f.photo_id = p.id JOIN %s m ON m.file_uid = f.file_uid
|
||||
SET p.checked_at = NULL WHERE m.subj_uid = ?`, Marker{}.TableName())
|
||||
err = UnscopedDb().Exec(update, m.SubjUID).Error
|
||||
|
|
|
|||
|
|
@ -15,10 +15,10 @@ import (
|
|||
|
||||
"github.com/dustin/go-humanize/english"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/config"
|
||||
"github.com/photoprism/photoprism/internal/entity"
|
||||
"github.com/photoprism/photoprism/internal/photoprism/get"
|
||||
"github.com/photoprism/photoprism/pkg/clean"
|
||||
"github.com/photoprism/photoprism/pkg/enum"
|
||||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
)
|
||||
|
||||
|
|
@ -73,7 +73,7 @@ func Database(backupPath, fileName string, toStdOut, force bool, retain int) (er
|
|||
var cmd *exec.Cmd
|
||||
|
||||
switch c.DatabaseDriver() {
|
||||
case config.MySQL, config.MariaDB:
|
||||
case enum.MySQL, enum.MariaDB:
|
||||
// Connect via Unix Domain Socket?
|
||||
if socketName := c.DatabaseServer(); strings.HasPrefix(socketName, "/") {
|
||||
cmd = exec.Command( // #nosec G204 database connection parameters from trusted config
|
||||
|
|
@ -112,7 +112,7 @@ func Database(backupPath, fileName string, toStdOut, force bool, retain int) (er
|
|||
c.DatabaseName(),
|
||||
)
|
||||
}
|
||||
case config.SQLite3:
|
||||
case enum.SQLite3:
|
||||
if !fs.FileExistsNotEmpty(c.DatabaseFile()) {
|
||||
return fmt.Errorf("sqlite database file %s not found", clean.LogQuote(c.DatabaseFile()))
|
||||
}
|
||||
|
|
@ -254,7 +254,7 @@ func RestoreDatabase(backupPath, fileName string, fromStdIn, force bool) (err er
|
|||
var cmd *exec.Cmd
|
||||
|
||||
switch c.DatabaseDriver() {
|
||||
case config.MySQL, config.MariaDB:
|
||||
case enum.MySQL, enum.MariaDB:
|
||||
// Connect via Unix Domain Socket?
|
||||
if socketName := c.DatabaseServer(); strings.HasPrefix(socketName, "/") {
|
||||
cmd = exec.Command( // #nosec G204 database connection parameters from config
|
||||
|
|
@ -296,7 +296,7 @@ func RestoreDatabase(backupPath, fileName string, fromStdIn, force bool) (err er
|
|||
c.DatabaseName(),
|
||||
)
|
||||
}
|
||||
case config.SQLite3:
|
||||
case enum.SQLite3:
|
||||
log.Infoln("restore: dropping existing sqlite database tables")
|
||||
tables.Drop(c.Db())
|
||||
cmd = exec.Command( // #nosec G204 sqlite restore uses configured binary and db path
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import (
|
|||
|
||||
"github.com/photoprism/photoprism/internal/config"
|
||||
"github.com/photoprism/photoprism/internal/service/cluster"
|
||||
"github.com/photoprism/photoprism/pkg/dsn"
|
||||
"github.com/photoprism/photoprism/pkg/enum"
|
||||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
"github.com/photoprism/photoprism/pkg/rnd"
|
||||
)
|
||||
|
|
@ -61,7 +61,7 @@ func TestRegister_PersistSecretAndDB(t *testing.T) {
|
|||
Secrets: &cluster.RegisterSecrets{ClientSecret: cluster.ExampleClientSecret},
|
||||
JWKSUrl: jwksURL,
|
||||
Database: cluster.RegisterDatabase{
|
||||
Driver: dsn.DriverMySQL,
|
||||
Driver: enum.MySQL,
|
||||
Host: "db.local",
|
||||
Port: 3306,
|
||||
Name: "pp_db",
|
||||
|
|
@ -92,7 +92,7 @@ func TestRegister_PersistSecretAndDB(t *testing.T) {
|
|||
expectedAppName = c.About()
|
||||
expectedAppVersion = c.Version()
|
||||
// Gate rotate=true: driver mysql and no DSN/fields.
|
||||
c.Options().DatabaseDriver = dsn.DriverMySQL
|
||||
c.Options().DatabaseDriver = enum.MySQL
|
||||
c.Options().DatabaseDSN = ""
|
||||
c.Options().DatabaseName = ""
|
||||
c.Options().DatabaseUser = ""
|
||||
|
|
@ -105,7 +105,7 @@ func TestRegister_PersistSecretAndDB(t *testing.T) {
|
|||
assert.Equal(t, cluster.ExampleClientSecret, c.NodeClientSecret())
|
||||
// DSN branch should be preferred and persisted.
|
||||
assert.Contains(t, c.Options().DatabaseDSN, "@tcp(db.local:3306)/pp_db")
|
||||
assert.Equal(t, dsn.DriverMySQL, c.Options().DatabaseDriver)
|
||||
assert.Equal(t, enum.MySQL, c.Options().DatabaseDriver)
|
||||
assert.Equal(t, srv.URL+"/.well-known/jwks.json", c.JWKSUrl())
|
||||
assert.Equal(t, "192.0.2.0/24", c.ClusterCIDR())
|
||||
}
|
||||
|
|
@ -277,7 +277,7 @@ func TestRegister_SQLite_NoDBPersist(t *testing.T) {
|
|||
|
||||
// NodeClientSecret should persist, but DB should remain SQLite (no DSN update).
|
||||
assert.Equal(t, cluster.ExampleClientSecret, c.NodeClientSecret())
|
||||
assert.Equal(t, config.SQLite3, c.DatabaseDriver())
|
||||
assert.Equal(t, enum.SQLite3, c.DatabaseDriver())
|
||||
assert.Equal(t, origDSN, c.Options().DatabaseDSN)
|
||||
assert.Equal(t, srv.URL+"/.well-known/jwks.json", c.JWKSUrl())
|
||||
assert.Equal(t, "203.0.113.0/24", c.ClusterCIDR())
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/config"
|
||||
"github.com/photoprism/photoprism/pkg/dsn"
|
||||
"github.com/photoprism/photoprism/pkg/enum"
|
||||
)
|
||||
|
||||
// Credentials contains the connection details returned when ensuring a node database.
|
||||
|
|
@ -35,9 +35,9 @@ func EnsureCredentials(ctx context.Context, conf *config.Config, nodeUUID, nodeN
|
|||
driver := strings.ToLower(DatabaseDriver)
|
||||
|
||||
switch driver {
|
||||
case dsn.DriverMySQL, dsn.DriverMariaDB:
|
||||
case enum.MySQL, enum.MariaDB:
|
||||
// ok
|
||||
case dsn.DriverSQLite3, dsn.DriverPostgres:
|
||||
case enum.SQLite3, enum.Postgres:
|
||||
return out, false, errors.New("database must be MySQL/MariaDB for auto-provisioning")
|
||||
default:
|
||||
// Driver is configured externally for the provisioner (decoupled from app config).
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import (
|
|||
"github.com/photoprism/photoprism/internal/config"
|
||||
"github.com/photoprism/photoprism/internal/service/cluster"
|
||||
"github.com/photoprism/photoprism/pkg/dsn"
|
||||
"github.com/photoprism/photoprism/pkg/enum"
|
||||
"github.com/photoprism/photoprism/pkg/rnd"
|
||||
)
|
||||
|
||||
|
|
@ -64,9 +65,9 @@ func GenerateCredentials(conf *config.Config, nodeUUID, nodeName string) (dbName
|
|||
func BuildDSN(driver, host string, port int, user, pass, name string) string {
|
||||
d := strings.ToLower(driver)
|
||||
switch d {
|
||||
case dsn.DriverMySQL, dsn.DriverMariaDB:
|
||||
case enum.MySQL, enum.MariaDB:
|
||||
return fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?%s",
|
||||
user, pass, host, port, name, dsn.Params[dsn.DriverMySQL],
|
||||
user, pass, host, port, name, dsn.Params[enum.MySQL],
|
||||
)
|
||||
default:
|
||||
log.Warnf("provisioner: unsupported driver %q, falling back to mysql DSN format", driver)
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import (
|
|||
|
||||
"github.com/photoprism/photoprism/internal/config"
|
||||
"github.com/photoprism/photoprism/internal/service/cluster"
|
||||
"github.com/photoprism/photoprism/pkg/enum"
|
||||
)
|
||||
|
||||
func TestGenerateCredentials_StabilityAndBudgets(t *testing.T) {
|
||||
|
|
@ -103,7 +104,7 @@ func TestEnsureCredentials_SqliteRejected(t *testing.T) {
|
|||
ctx := context.Background()
|
||||
c := config.NewConfig(config.CliTestContext())
|
||||
origDriver := DatabaseDriver
|
||||
DatabaseDriver = config.SQLite3
|
||||
DatabaseDriver = enum.SQLite3
|
||||
t.Cleanup(func() { DatabaseDriver = origDriver })
|
||||
|
||||
_, _, err := EnsureCredentials(ctx, c, "11111111-1111-4111-8111-111111111111", "pp-node-01", false)
|
||||
|
|
|
|||
|
|
@ -1,24 +1,35 @@
|
|||
package clean
|
||||
|
||||
// SqlSpecial checks if the byte must be escaped/omitted in SQL.
|
||||
func SqlSpecial(b byte) (special bool, omit bool) {
|
||||
import "github.com/photoprism/photoprism/pkg/enum"
|
||||
|
||||
// SQLSpecial checks if the byte must be escaped/omitted in SQL.
|
||||
func SQLSpecial(b byte, dialect string) (special bool, omit bool) {
|
||||
if b < 32 {
|
||||
return true, true
|
||||
}
|
||||
|
||||
switch b {
|
||||
case '"', '\'', '\\':
|
||||
return true, false
|
||||
default:
|
||||
return false, false
|
||||
if dialect == enum.MySQL {
|
||||
switch b {
|
||||
case '\'', '\\':
|
||||
return true, false
|
||||
default:
|
||||
return false, false
|
||||
}
|
||||
} else {
|
||||
switch b {
|
||||
case '\'':
|
||||
return true, false
|
||||
default:
|
||||
return false, false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// SqlString escapes a string for use in an SQL query.
|
||||
func SqlString(s string) string {
|
||||
// SQLString escapes a string for use in an SQL query.
|
||||
func SQLString(s string, dialect string) string {
|
||||
var i int
|
||||
for i = 0; i < len(s); i++ {
|
||||
if found, _ := SqlSpecial(s[i]); found {
|
||||
if found, _ := SQLSpecial(s[i], dialect); found {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
|
@ -35,7 +46,7 @@ func SqlString(s string) string {
|
|||
j := i
|
||||
|
||||
for ; i < len(s); i++ {
|
||||
if special, omit := SqlSpecial(s[i]); omit {
|
||||
if special, omit := SQLSpecial(s[i], dialect); omit {
|
||||
// Omit control characters.
|
||||
continue
|
||||
} else if special {
|
||||
|
|
|
|||
|
|
@ -4,81 +4,210 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/photoprism/photoprism/pkg/enum"
|
||||
)
|
||||
|
||||
func TestSqlSpecial(t *testing.T) {
|
||||
t.Run("Special", func(t *testing.T) {
|
||||
if s, o := SqlSpecial(1); !s {
|
||||
func TestSQLSpecial(t *testing.T) {
|
||||
t.Run("Special MySQL", func(t *testing.T) {
|
||||
if s, o := SQLSpecial(1, enum.MySQL); !s {
|
||||
t.Error("char is special")
|
||||
} else if !o {
|
||||
t.Error("\" must be omitted")
|
||||
}
|
||||
|
||||
if s, o := SqlSpecial(31); !s {
|
||||
if s, o := SQLSpecial(31, enum.MySQL); !s {
|
||||
t.Error("char is special")
|
||||
} else if !o {
|
||||
t.Error("\" must be omitted")
|
||||
}
|
||||
|
||||
if s, o := SqlSpecial('\\'); !s {
|
||||
if s, o := SQLSpecial('\\', enum.MySQL); !s {
|
||||
t.Error("\\ is special")
|
||||
} else if o {
|
||||
t.Error("\\ must not be omitted")
|
||||
}
|
||||
|
||||
if s, o := SqlSpecial('\''); !s {
|
||||
if s, o := SQLSpecial('\'', enum.MySQL); !s {
|
||||
t.Error("' is special")
|
||||
} else if o {
|
||||
t.Error("' must not be omitted")
|
||||
}
|
||||
})
|
||||
t.Run("Special SQLite", func(t *testing.T) {
|
||||
if s, o := SQLSpecial(1, enum.SQLite3); !s {
|
||||
t.Error("char is special")
|
||||
} else if !o {
|
||||
t.Error("\" must be omitted")
|
||||
}
|
||||
|
||||
if s, o := SqlSpecial('"'); !s {
|
||||
t.Error("\" is special")
|
||||
if s, o := SQLSpecial(31, enum.SQLite3); !s {
|
||||
t.Error("char is special")
|
||||
} else if !o {
|
||||
t.Error("\" must be omitted")
|
||||
}
|
||||
|
||||
if s, o := SQLSpecial('\'', enum.SQLite3); !s {
|
||||
t.Error("' is special")
|
||||
} else if o {
|
||||
t.Error("\" must not be omitted")
|
||||
t.Error("' must not be omitted")
|
||||
}
|
||||
})
|
||||
t.Run("NotSpecial", func(t *testing.T) {
|
||||
if s, o := SqlSpecial(32); s {
|
||||
t.Run("Special Postgres", func(t *testing.T) {
|
||||
if s, o := SQLSpecial(1, enum.Postgres); !s {
|
||||
t.Error("char is special")
|
||||
} else if !o {
|
||||
t.Error("\" must be omitted")
|
||||
}
|
||||
|
||||
if s, o := SQLSpecial(31, enum.Postgres); !s {
|
||||
t.Error("char is special")
|
||||
} else if !o {
|
||||
t.Error("\" must be omitted")
|
||||
}
|
||||
|
||||
if s, o := SQLSpecial('\'', enum.Postgres); !s {
|
||||
t.Error("' is special")
|
||||
} else if o {
|
||||
t.Error("' must not be omitted")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("NotSpecial MySQL", func(t *testing.T) {
|
||||
if s, o := SQLSpecial(32, enum.MySQL); s {
|
||||
t.Error("space is not special")
|
||||
} else if o {
|
||||
t.Error("space must not be omitted")
|
||||
}
|
||||
|
||||
if s, o := SqlSpecial('A'); s {
|
||||
if s, o := SQLSpecial('A', enum.MySQL); s {
|
||||
t.Error("A is not special")
|
||||
} else if o {
|
||||
t.Error("A must not be omitted")
|
||||
}
|
||||
|
||||
if s, o := SqlSpecial('a'); s {
|
||||
if s, o := SQLSpecial('a', enum.MySQL); s {
|
||||
t.Error("a is not special")
|
||||
} else if o {
|
||||
t.Error("a must not be omitted")
|
||||
}
|
||||
|
||||
if s, o := SqlSpecial('_'); s {
|
||||
if s, o := SQLSpecial('_', enum.MySQL); s {
|
||||
t.Error("_ is not special")
|
||||
} else if o {
|
||||
t.Error("_ must not be omitted")
|
||||
}
|
||||
|
||||
if s, o := SQLSpecial('"', enum.MySQL); s {
|
||||
t.Error("\" is not special")
|
||||
} else if o {
|
||||
t.Error("\" must not be omitted")
|
||||
}
|
||||
})
|
||||
t.Run("NotSpecial SQLite", func(t *testing.T) {
|
||||
if s, o := SQLSpecial(32, enum.SQLite3); s {
|
||||
t.Error("space is not special")
|
||||
} else if o {
|
||||
t.Error("space must not be omitted")
|
||||
}
|
||||
|
||||
if s, o := SQLSpecial('A', enum.SQLite3); s {
|
||||
t.Error("A is not special")
|
||||
} else if o {
|
||||
t.Error("A must not be omitted")
|
||||
}
|
||||
|
||||
if s, o := SQLSpecial('a', enum.SQLite3); s {
|
||||
t.Error("a is not special")
|
||||
} else if o {
|
||||
t.Error("a must not be omitted")
|
||||
}
|
||||
|
||||
if s, o := SQLSpecial('_', enum.SQLite3); s {
|
||||
t.Error("_ is not special")
|
||||
} else if o {
|
||||
t.Error("_ must not be omitted")
|
||||
}
|
||||
|
||||
if s, o := SQLSpecial('"', enum.SQLite3); s {
|
||||
t.Error("\" is not special")
|
||||
} else if o {
|
||||
t.Error("\" must not be omitted")
|
||||
}
|
||||
|
||||
if s, o := SQLSpecial('\\', enum.SQLite3); s {
|
||||
t.Error("\\ is not special")
|
||||
} else if o {
|
||||
t.Error("\\ must not be omitted")
|
||||
}
|
||||
})
|
||||
t.Run("NotSpecial Postgres", func(t *testing.T) {
|
||||
if s, o := SQLSpecial(32, enum.Postgres); s {
|
||||
t.Error("space is not special")
|
||||
} else if o {
|
||||
t.Error("space must not be omitted")
|
||||
}
|
||||
|
||||
if s, o := SQLSpecial('A', enum.Postgres); s {
|
||||
t.Error("A is not special")
|
||||
} else if o {
|
||||
t.Error("A must not be omitted")
|
||||
}
|
||||
|
||||
if s, o := SQLSpecial('a', enum.Postgres); s {
|
||||
t.Error("a is not special")
|
||||
} else if o {
|
||||
t.Error("a must not be omitted")
|
||||
}
|
||||
|
||||
if s, o := SQLSpecial('_', enum.Postgres); s {
|
||||
t.Error("_ is not special")
|
||||
} else if o {
|
||||
t.Error("_ must not be omitted")
|
||||
}
|
||||
|
||||
if s, o := SQLSpecial('"', enum.Postgres); s {
|
||||
t.Error("\" is not special")
|
||||
} else if o {
|
||||
t.Error("\" must not be omitted")
|
||||
}
|
||||
|
||||
if s, o := SQLSpecial('\\', enum.Postgres); s {
|
||||
t.Error("\\ is not special")
|
||||
} else if o {
|
||||
t.Error("\\ must not be omitted")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestSqlString(t *testing.T) {
|
||||
t.Run("Empty", func(t *testing.T) {
|
||||
assert.Equal(t, "", SqlString(""))
|
||||
assert.Equal(t, "", SQLString("", enum.MySQL))
|
||||
assert.Equal(t, "", SQLString("", enum.SQLite3))
|
||||
assert.Equal(t, "", SQLString("", enum.Postgres))
|
||||
})
|
||||
t.Run("Special", func(t *testing.T) {
|
||||
s := "' \" \t \n %_''"
|
||||
exp := "'' \"\" %_''''"
|
||||
result := SqlString(s)
|
||||
s := "' \" \t \n %_''\\"
|
||||
exp := "'' \" %_''''\\\\"
|
||||
result := SQLString(s, enum.MySQL)
|
||||
t.Logf("String..: %s", s)
|
||||
t.Logf("Expected: %s", exp)
|
||||
t.Logf("Result..: %s", result)
|
||||
assert.Equal(t, exp, result)
|
||||
exp = "'' \" %_''''\\"
|
||||
result = SQLString(s, enum.SQLite3)
|
||||
t.Logf("String..: %s", s)
|
||||
t.Logf("Expected: %s", exp)
|
||||
t.Logf("Result..: %s", result)
|
||||
assert.Equal(t, exp, result)
|
||||
exp = "'' \" %_''''\\"
|
||||
result = SQLString(s, enum.Postgres)
|
||||
t.Logf("String..: %s", s)
|
||||
t.Logf("Expected: %s", exp)
|
||||
t.Logf("Result..: %s", result)
|
||||
assert.Equal(t, exp, result)
|
||||
})
|
||||
t.Run("Alnum", func(t *testing.T) {
|
||||
assert.Equal(t, "123ABCabc", SqlString("123ABCabc"))
|
||||
assert.Equal(t, "123ABCabc", SQLString("123ABCabc", enum.MySQL))
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,23 +1,11 @@
|
|||
package dsn
|
||||
|
||||
// SQL database drivers.
|
||||
const (
|
||||
DriverMySQL = "mysql"
|
||||
DriverMariaDB = "mariadb"
|
||||
DriverPostgres = "postgres"
|
||||
DriverSQLite3 = "sqlite3"
|
||||
)
|
||||
|
||||
// SQLite default DSNs.
|
||||
const (
|
||||
SQLiteTestDB = ".test.db"
|
||||
SQLiteMemory = ":memory:"
|
||||
)
|
||||
import "github.com/photoprism/photoprism/pkg/enum"
|
||||
|
||||
// Params maps required DSN parameters by driver type.
|
||||
var Params = Values{
|
||||
DriverMySQL: "charset=utf8mb4,utf8&collation=utf8mb4_unicode_ci&parseTime=true",
|
||||
DriverMariaDB: "charset=utf8mb4,utf8&collation=utf8mb4_unicode_ci&parseTime=true",
|
||||
DriverPostgres: "sslmode=disable TimeZone=UTC",
|
||||
DriverSQLite3: "_busy_timeout=5000",
|
||||
enum.MySQL: "charset=utf8mb4,utf8&collation=utf8mb4_unicode_ci&parseTime=true",
|
||||
enum.MariaDB: "charset=utf8mb4,utf8&collation=utf8mb4_unicode_ci&parseTime=true",
|
||||
enum.Postgres: "sslmode=disable TimeZone=UTC",
|
||||
enum.SQLite3: "_busy_timeout=5000",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,6 +31,8 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"github.com/photoprism/photoprism/pkg/enum"
|
||||
)
|
||||
|
||||
// dsnPattern is a regular expression matching a database DSN string.
|
||||
|
|
@ -75,7 +77,7 @@ func (d *DSN) MaskPassword() (s string) {
|
|||
}
|
||||
|
||||
// Mask password in PostgreSQL-style DSN.
|
||||
if d.Driver == DriverPostgres || strings.Contains(s, "password=") {
|
||||
if d.Driver == enum.Postgres || strings.Contains(s, "password=") {
|
||||
return dsnPostgresPasswordPattern.ReplaceAllStringFunc(s, func(segment string) string {
|
||||
matches := dsnPostgresPasswordPattern.FindStringSubmatch(segment)
|
||||
if len(matches) != 3 {
|
||||
|
|
@ -107,7 +109,7 @@ func (d *DSN) MaskPassword() (s string) {
|
|||
|
||||
// Host the database server host.
|
||||
func (d *DSN) Host() string {
|
||||
if d.Driver == DriverSQLite3 {
|
||||
if d.Driver == enum.SQLite3 {
|
||||
return ""
|
||||
}
|
||||
|
||||
|
|
@ -117,16 +119,17 @@ func (d *DSN) Host() string {
|
|||
|
||||
// Port the database server port.
|
||||
func (d *DSN) Port() int {
|
||||
if d.Driver == DriverSQLite3 {
|
||||
switch d.Driver {
|
||||
case enum.SQLite3:
|
||||
return 0
|
||||
}
|
||||
|
||||
defaultPort := 0
|
||||
|
||||
switch d.Driver {
|
||||
case DriverMySQL, DriverMariaDB:
|
||||
case enum.MySQL, enum.MariaDB:
|
||||
defaultPort = 3306
|
||||
case DriverPostgres:
|
||||
case enum.Postgres:
|
||||
defaultPort = 5432
|
||||
}
|
||||
|
||||
|
|
@ -249,7 +252,7 @@ func (d *DSN) parsePostgres() bool {
|
|||
}
|
||||
}
|
||||
|
||||
d.Driver = DriverPostgres
|
||||
d.Driver = enum.Postgres
|
||||
d.User = values["user"]
|
||||
d.Password = values["password"]
|
||||
d.Name = name
|
||||
|
|
@ -371,13 +374,13 @@ func (d *DSN) detectDriver() {
|
|||
|
||||
switch driver {
|
||||
case "postgres", "postgresql":
|
||||
d.Driver = DriverPostgres
|
||||
d.Driver = enum.Postgres
|
||||
return
|
||||
case "mysql", "mariadb":
|
||||
d.Driver = DriverMySQL
|
||||
d.Driver = enum.MySQL
|
||||
return
|
||||
case "sqlite", "sqlite3", "file":
|
||||
d.Driver = DriverSQLite3
|
||||
d.Driver = enum.SQLite3
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -389,26 +392,26 @@ func (d *DSN) detectDriver() {
|
|||
lower := strings.ToLower(d.DSN)
|
||||
|
||||
if strings.Contains(lower, "postgres://") || strings.Contains(lower, "postgresql://") {
|
||||
d.Driver = DriverPostgres
|
||||
d.Driver = enum.Postgres
|
||||
return
|
||||
}
|
||||
|
||||
if d.Net == "tcp" || d.Net == "unix" || strings.Contains(lower, "@tcp(") || strings.Contains(lower, "@unix(") {
|
||||
d.Driver = DriverMySQL
|
||||
d.Driver = enum.MySQL
|
||||
return
|
||||
}
|
||||
|
||||
if strings.HasPrefix(lower, "file:") || strings.HasSuffix(lower, ".db") || strings.HasSuffix(strings.ToLower(d.Name), ".db") {
|
||||
d.Driver = DriverSQLite3
|
||||
d.Driver = enum.SQLite3
|
||||
return
|
||||
}
|
||||
|
||||
if strings.Contains(lower, " host=") && strings.Contains(lower, " dbname=") {
|
||||
d.Driver = DriverPostgres
|
||||
d.Driver = enum.Postgres
|
||||
return
|
||||
}
|
||||
|
||||
if d.Server != "" && (strings.Contains(d.Server, ":") || d.Net != "") && d.Driver == "" {
|
||||
d.Driver = DriverMySQL
|
||||
d.Driver = enum.MySQL
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/photoprism/photoprism/pkg/enum"
|
||||
)
|
||||
|
||||
func TestDSN_HostAndPort(t *testing.T) {
|
||||
|
|
@ -95,7 +97,7 @@ func TestDSN_ParsePostgres(t *testing.T) {
|
|||
in: "user=alice password=s3cr3t dbname=app",
|
||||
want: DSN{
|
||||
DSN: "user=alice password=s3cr3t dbname=app",
|
||||
Driver: DriverPostgres,
|
||||
Driver: enum.Postgres,
|
||||
User: "alice",
|
||||
Password: "s3cr3t",
|
||||
Name: "app",
|
||||
|
|
@ -107,7 +109,7 @@ func TestDSN_ParsePostgres(t *testing.T) {
|
|||
in: "user=alice password=s3cr3t dbname=app host=db.internal port=5432 connect_timeout=5 sslmode=require",
|
||||
want: DSN{
|
||||
DSN: "user=alice password=s3cr3t dbname=app host=db.internal port=5432 connect_timeout=5 sslmode=require",
|
||||
Driver: DriverPostgres,
|
||||
Driver: enum.Postgres,
|
||||
User: "alice",
|
||||
Password: "s3cr3t",
|
||||
Server: "db.internal:5432",
|
||||
|
|
@ -121,7 +123,7 @@ func TestDSN_ParsePostgres(t *testing.T) {
|
|||
in: `user="alice" password="s ec ret" dbname="app" host=db.internal`,
|
||||
want: DSN{
|
||||
DSN: `user="alice" password="s ec ret" dbname="app" host=db.internal`,
|
||||
Driver: DriverPostgres,
|
||||
Driver: enum.Postgres,
|
||||
User: "alice",
|
||||
Password: "s ec ret",
|
||||
Server: "db.internal",
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"github.com/photoprism/photoprism/pkg/enum"
|
||||
)
|
||||
|
||||
func TestParse(t *testing.T) {
|
||||
|
|
@ -17,7 +19,7 @@ func TestParse(t *testing.T) {
|
|||
in: "user:secret@tcp(localhost:3306)/photoprism?charset=utf8mb4,utf8&collation=utf8mb4_unicode_ci&parseTime=true",
|
||||
want: DSN{
|
||||
DSN: "user:secret@tcp(localhost:3306)/photoprism?charset=utf8mb4,utf8&collation=utf8mb4_unicode_ci&parseTime=true",
|
||||
Driver: DriverMySQL,
|
||||
Driver: enum.MySQL,
|
||||
User: "user",
|
||||
Password: "secret",
|
||||
Net: "tcp",
|
||||
|
|
@ -31,7 +33,7 @@ func TestParse(t *testing.T) {
|
|||
in: "mysql://user:secret@localhost:3306/photoprism?parseTime=true",
|
||||
want: DSN{
|
||||
DSN: "mysql://user:secret@localhost:3306/photoprism?parseTime=true",
|
||||
Driver: DriverMySQL,
|
||||
Driver: enum.MySQL,
|
||||
User: "user",
|
||||
Password: "secret",
|
||||
Server: "localhost:3306",
|
||||
|
|
@ -44,7 +46,7 @@ func TestParse(t *testing.T) {
|
|||
in: "user:secret@unix(/var/run/mysql.sock)/photoprism",
|
||||
want: DSN{
|
||||
DSN: "user:secret@unix(/var/run/mysql.sock)/photoprism",
|
||||
Driver: DriverMySQL,
|
||||
Driver: enum.MySQL,
|
||||
User: "user",
|
||||
Password: "secret",
|
||||
Net: "unix",
|
||||
|
|
@ -57,7 +59,7 @@ func TestParse(t *testing.T) {
|
|||
in: "file:/data/index.db?_busy_timeout=5000",
|
||||
want: DSN{
|
||||
DSN: "file:/data/index.db?_busy_timeout=5000",
|
||||
Driver: DriverSQLite3,
|
||||
Driver: enum.SQLite3,
|
||||
Server: "file:/data",
|
||||
Name: "index.db",
|
||||
Params: "_busy_timeout=5000",
|
||||
|
|
@ -68,7 +70,7 @@ func TestParse(t *testing.T) {
|
|||
in: "/index.db?_busy_timeout=5000",
|
||||
want: DSN{
|
||||
DSN: "/index.db?_busy_timeout=5000",
|
||||
Driver: DriverSQLite3,
|
||||
Driver: enum.SQLite3,
|
||||
Server: "",
|
||||
Name: "index.db",
|
||||
Params: "_busy_timeout=5000",
|
||||
|
|
@ -79,7 +81,7 @@ func TestParse(t *testing.T) {
|
|||
in: "user=alice password=s3cr3t dbname=app host=db.internal port=5432 connect_timeout=5 sslmode=require",
|
||||
want: DSN{
|
||||
DSN: "user=alice password=s3cr3t dbname=app host=db.internal port=5432 connect_timeout=5 sslmode=require",
|
||||
Driver: DriverPostgres,
|
||||
Driver: enum.Postgres,
|
||||
User: "alice",
|
||||
Password: "s3cr3t",
|
||||
Server: "db.internal:5432",
|
||||
|
|
|
|||
|
|
@ -24,3 +24,8 @@ Additional information can be found in our Developer Guide:
|
|||
<https://docs.photoprism.app/developer-guide/>
|
||||
*/
|
||||
package enum
|
||||
|
||||
// Automatically choose the appropriate "thing"
|
||||
const (
|
||||
Auto = "auto"
|
||||
)
|
||||
|
|
|
|||
19
pkg/enum/sql.go
Normal file
19
pkg/enum/sql.go
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
package enum
|
||||
|
||||
// Supported database(s)
|
||||
const (
|
||||
MySQL = "mysql"
|
||||
MariaDB = "mariadb"
|
||||
SQLite3 = "sqlite3"
|
||||
)
|
||||
|
||||
// Future database(s)
|
||||
const (
|
||||
Postgres = "postgres"
|
||||
)
|
||||
|
||||
// Test database(s)
|
||||
const (
|
||||
SQLiteTestDB = ".test.db"
|
||||
SQLiteMemoryDSN = ":memory:?cache=shared"
|
||||
)
|
||||
Loading…
Add table
Add a link
Reference in a new issue