From 5716e7344cc7ecb7314ac0d59472002d43690db8 Mon Sep 17 00:00:00 2001 From: Keith Martin Date: Tue, 2 Sep 2025 22:35:44 +1000 Subject: [PATCH 1/8] Search: Update Sql string escaping to match different DBMS --- internal/entity/search/conditions.go | 2 +- internal/entity/search/conditions_test.go | 9 +- pkg/clean/sql.go | 34 +++-- pkg/clean/sql_test.go | 163 +++++++++++++++++++--- 4 files changed, 177 insertions(+), 31 deletions(-) diff --git a/internal/entity/search/conditions.go b/internal/entity/search/conditions.go index d2fdb0d74..20ecb2e13 100644 --- a/internal/entity/search/conditions.go +++ b/internal/entity/search/conditions.go @@ -12,7 +12,7 @@ import ( // Like escapes a string for use in a query. func Like(s string) string { - return strings.Trim(clean.SqlString(s), " |&*%") + return strings.Trim(clean.SqlString(s, Db().Dialect().GetName()), " |&*%") } // LikeAny returns a single where condition matching the search words. diff --git a/internal/entity/search/conditions_test.go b/internal/entity/search/conditions_test.go index 0a88c1b89..a9b8d4396 100644 --- a/internal/entity/search/conditions_test.go +++ b/internal/entity/search/conditions_test.go @@ -16,8 +16,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() == entity.MySQL { + exp = "'' \" %_''''\\\\" + } result := Like(s) t.Logf("String..: %s", s) t.Logf("Expected: %s", exp) @@ -140,7 +143,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]) } }) } diff --git a/pkg/clean/sql.go b/pkg/clean/sql.go index ba41876a6..50abaf80a 100644 --- a/pkg/clean/sql.go +++ b/pkg/clean/sql.go @@ -1,24 +1,40 @@ package clean +const ( + MySQL = "mysql" + MariaDB = "mariadb" + Postgres = "postgres" + SQLite3 = "sqlite3" +) + // SqlSpecial checks if the byte must be escaped/omitted in SQL. -func SqlSpecial(b byte) (special bool, omit bool) { +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 == 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 { +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 +51,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 { diff --git a/pkg/clean/sql_test.go b/pkg/clean/sql_test.go index 5da499d28..04d848210 100644 --- a/pkg/clean/sql_test.go +++ b/pkg/clean/sql_test.go @@ -7,78 +7,205 @@ import ( ) func TestSqlSpecial(t *testing.T) { - t.Run("Special", func(t *testing.T) { - if s, o := SqlSpecial(1); !s { + t.Run("Special MySQL", func(t *testing.T) { + if s, o := SqlSpecial(1, 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, MySQL); !s { t.Error("char is special") } else if !o { t.Error("\" must be omitted") } - if s, o := SqlSpecial('\\'); !s { + if s, o := SqlSpecial('\\', MySQL); !s { t.Error("\\ is special") } else if o { t.Error("\\ must not be omitted") } - if s, o := SqlSpecial('\''); !s { + if s, o := SqlSpecial('\'', 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, 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, SQLite3); !s { + t.Error("char is special") + } else if !o { + t.Error("\" must be omitted") + } + + if s, o := SqlSpecial('\'', 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, Postgres); !s { + t.Error("char is special") + } else if !o { + t.Error("\" must be omitted") + } + + if s, o := SqlSpecial(31, Postgres); !s { + t.Error("char is special") + } else if !o { + t.Error("\" must be omitted") + } + + if s, o := SqlSpecial('\'', 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, 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', 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', 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('_', MySQL); s { t.Error("_ is not special") } else if o { t.Error("_ must not be omitted") } + + if s, o := SqlSpecial('"', 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, SQLite3); s { + t.Error("space is not special") + } else if o { + t.Error("space must not be omitted") + } + + if s, o := SqlSpecial('A', SQLite3); s { + t.Error("A is not special") + } else if o { + t.Error("A must not be omitted") + } + + if s, o := SqlSpecial('a', SQLite3); s { + t.Error("a is not special") + } else if o { + t.Error("a must not be omitted") + } + + if s, o := SqlSpecial('_', SQLite3); s { + t.Error("_ is not special") + } else if o { + t.Error("_ must not be omitted") + } + + if s, o := SqlSpecial('"', SQLite3); s { + t.Error("\" is not special") + } else if o { + t.Error("\" must not be omitted") + } + + if s, o := SqlSpecial('\\', 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, Postgres); s { + t.Error("space is not special") + } else if o { + t.Error("space must not be omitted") + } + + if s, o := SqlSpecial('A', Postgres); s { + t.Error("A is not special") + } else if o { + t.Error("A must not be omitted") + } + + if s, o := SqlSpecial('a', Postgres); s { + t.Error("a is not special") + } else if o { + t.Error("a must not be omitted") + } + + if s, o := SqlSpecial('_', Postgres); s { + t.Error("_ is not special") + } else if o { + t.Error("_ must not be omitted") + } + + if s, o := SqlSpecial('"', Postgres); s { + t.Error("\" is not special") + } else if o { + t.Error("\" must not be omitted") + } + + if s, o := SqlSpecial('\\', 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("", MySQL)) + assert.Equal(t, "", SqlString("", SQLite3)) + assert.Equal(t, "", SqlString("", Postgres)) }) t.Run("Special", func(t *testing.T) { - s := "' \" \t \n %_''" - exp := "'' \"\" %_''''" - result := SqlString(s) + s := "' \" \t \n %_''\\" + exp := "'' \" %_''''\\\\" + result := SqlString(s, 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, 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, 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", MySQL)) }) } From 275b2c91e5b219bbcf8909b6ccceb9810ee77f75 Mon Sep 17 00:00:00 2001 From: Keith Martin Date: Wed, 3 Sep 2025 10:36:41 +1000 Subject: [PATCH 2/8] Backend: add constants package with databases and implement --- internal/config/config.go | 3 +- internal/config/config_db.go | 56 ++++++++---------- internal/config/config_db_test.go | 18 +++--- internal/config/report.go | 4 +- internal/config/test.go | 7 ++- internal/entity/db_conn.go | 8 --- internal/entity/entity_counts.go | 9 +-- internal/entity/entity_init.go | 9 +-- internal/entity/face.go | 3 +- internal/entity/file.go | 5 +- internal/entity/marker.go | 3 +- internal/entity/migrate/dialects.go | 16 +++--- internal/entity/mysql8_test.go | 3 +- internal/entity/photo_estimate.go | 5 +- internal/entity/photo_merge.go | 5 +- internal/entity/query/albums.go | 3 +- internal/entity/query/covers.go | 21 +++---- internal/entity/query/file_selection.go | 5 +- internal/entity/query/folders.go | 3 +- internal/entity/query/photo_selection.go | 5 +- internal/entity/query/query.go | 8 +-- internal/entity/search/conditions_test.go | 3 +- internal/entity/sortby/random.go | 9 +-- internal/entity/sortby/random_test.go | 6 +- internal/entity/subject.go | 3 +- internal/photoprism/backup/database.go | 10 ++-- pkg/clean/sql.go | 9 +-- pkg/clean/sql_test.go | 70 ++++++++++++----------- pkg/constants/sql.go | 19 ++++++ 29 files changed, 171 insertions(+), 157 deletions(-) create mode 100644 pkg/constants/sql.go diff --git a/internal/config/config.go b/internal/config/config.go index 8b9301c2a..a1e493b84 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -58,6 +58,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/constants" "github.com/photoprism/photoprism/pkg/fs" "github.com/photoprism/photoprism/pkg/i18n" "github.com/photoprism/photoprism/pkg/rnd" @@ -642,7 +643,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() == constants.SQLite3 && (cores >= 8 && c.options.IndexWorkers <= 0 || c.options.IndexWorkers > 4) { return 4 } diff --git a/internal/config/config_db.go b/internal/config/config_db.go index 6fce5a9d7..bf8831a5a 100644 --- a/internal/config/config_db.go +++ b/internal/config/config_db.go @@ -19,15 +19,7 @@ import ( "github.com/photoprism/photoprism/internal/entity/migrate" "github.com/photoprism/photoprism/internal/mutex" "github.com/photoprism/photoprism/pkg/clean" -) - -// SQL Databases. -// TODO: PostgreSQL support requires upgrading GORM, so generic column data types can be used. -const ( - MySQL = "mysql" - MariaDB = "mariadb" - Postgres = "postgres" - SQLite3 = "sqlite3" + "github.com/photoprism/photoprism/pkg/constants" ) // SQLite default DSNs. @@ -39,17 +31,17 @@ const ( // DatabaseDriver returns the database driver name. func (c *Config) DatabaseDriver() string { switch strings.ToLower(c.options.DatabaseDriver) { - case MySQL, MariaDB: - c.options.DatabaseDriver = MySQL - case SQLite3, "sqlite", "test", "file", "": - c.options.DatabaseDriver = SQLite3 + case constants.MySQL, constants.MariaDB: + c.options.DatabaseDriver = constants.MySQL + case constants.SQLite3, "sqlite", "test", "file", "": + c.options.DatabaseDriver = constants.SQLite3 case "tidb": log.Warnf("config: database driver 'tidb' is deprecated, using sqlite") - c.options.DatabaseDriver = SQLite3 + c.options.DatabaseDriver = constants.SQLite3 c.options.DatabaseDsn = "" default: log.Warnf("config: unsupported database driver %s, using sqlite", c.options.DatabaseDriver) - c.options.DatabaseDriver = SQLite3 + c.options.DatabaseDriver = constants.SQLite3 c.options.DatabaseDsn = "" } @@ -59,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 constants.MySQL, constants.MariaDB: return "MariaDB" - case SQLite3, "sqlite", "test", "file", "": + case constants.SQLite3, "sqlite", "test", "file", "": return "SQLite" case "tidb": return "TiDB" @@ -91,7 +83,7 @@ func (c *Config) DatabaseSsl() bool { } switch c.DatabaseDriver() { - case MySQL: + case constants.MySQL: // see https://mariadb.org/mission-impossible-zero-configuration-ssl/ return c.IsDatabaseVersion("v11.4") default: @@ -103,7 +95,7 @@ func (c *Config) DatabaseSsl() bool { func (c *Config) DatabaseDsn() string { if c.options.DatabaseDsn == "" { switch c.DatabaseDriver() { - case MySQL, MariaDB: + case constants.MySQL, constants.MariaDB: databaseServer := c.DatabaseServer() // Connect via Unix Domain Socket? @@ -122,7 +114,7 @@ func (c *Config) DatabaseDsn() string { c.DatabaseName(), c.DatabaseTimeout(), ) - case Postgres: + case constants.Postgres: return fmt.Sprintf( "user=%s password=%s dbname=%s host=%s port=%d connect_timeout=%d sslmode=disable TimeZone=UTC", c.DatabaseUser(), @@ -132,7 +124,7 @@ func (c *Config) DatabaseDsn() string { c.DatabasePort(), c.DatabaseTimeout(), ) - case SQLite3: + case constants.SQLite3: return filepath.Join(c.StoragePath(), "index.db?_busy_timeout=5000") default: log.Errorf("config: empty database dsn") @@ -167,7 +159,7 @@ func (c *Config) ParseDatabaseDsn() { func (c *Config) DatabaseServer() string { c.ParseDatabaseDsn() - if c.DatabaseDriver() == SQLite3 { + if c.DatabaseDriver() == constants.SQLite3 { return "" } else if c.options.DatabaseServer == "" { return localhost @@ -178,7 +170,7 @@ func (c *Config) DatabaseServer() string { // DatabaseHost the database server host. func (c *Config) DatabaseHost() string { - if c.DatabaseDriver() == SQLite3 { + if c.DatabaseDriver() == constants.SQLite3 { return "" } @@ -208,7 +200,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() == constants.SQLite3 { return "" } @@ -219,7 +211,7 @@ func (c *Config) DatabasePortString() string { func (c *Config) DatabaseName() string { c.ParseDatabaseDsn() - if c.DatabaseDriver() == SQLite3 { + if c.DatabaseDriver() == constants.SQLite3 { return c.DatabaseDsn() } else if c.options.DatabaseName == "" { return "photoprism" @@ -230,7 +222,7 @@ func (c *Config) DatabaseName() string { // DatabaseUser returns the database user name. func (c *Config) DatabaseUser() string { - if c.DatabaseDriver() == SQLite3 { + if c.DatabaseDriver() == constants.SQLite3 { return "" } @@ -245,7 +237,7 @@ func (c *Config) DatabaseUser() string { // DatabasePassword returns the database user password. func (c *Config) DatabasePassword() string { - if c.DatabaseDriver() == SQLite3 { + if c.DatabaseDriver() == constants.SQLite3 { return "" } @@ -336,11 +328,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 constants.MySQL, constants.MariaDB: c.Db().Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci") - case Postgres: + case constants.Postgres: // Ignore for now. - case SQLite3: + case constants.SQLite3: // Not required as unicode is default. } } @@ -396,7 +388,7 @@ func (c *Config) InitTestDb() { // checkDb checks the database server version. func (c *Config) checkDb(db *gorm.DB) error { switch c.DatabaseDriver() { - case MySQL: + case constants.MySQL: type Res struct { Value string `gorm:"column:Value;"` } @@ -424,7 +416,7 @@ func (c *Config) checkDb(db *gorm.DB) error { } else if !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 constants.SQLite3: type Res struct { Value string `gorm:"column:Value;"` } diff --git a/internal/config/config_db_test.go b/internal/config/config_db_test.go index d896513a5..6f8d370ba 100644 --- a/internal/config/config_db_test.go +++ b/internal/config/config_db_test.go @@ -5,13 +5,15 @@ import ( "testing" "github.com/stretchr/testify/assert" + + "github.com/photoprism/photoprism/pkg/constants" ) func TestConfig_DatabaseDriver(t *testing.T) { c := NewConfig(CliTestContext()) driver := c.DatabaseDriver() - assert.Equal(t, SQLite3, driver) + assert.Equal(t, constants.SQLite3, driver) } func TestConfig_DatabaseDriverName(t *testing.T) { @@ -38,7 +40,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 = constants.SQLite3 assert.Equal(t, "", c.DatabaseServer()) assert.Equal(t, "", c.DatabaseHost()) @@ -47,7 +49,7 @@ func TestConfig_ParseDatabaseDsn(t *testing.T) { assert.Equal(t, "", c.DatabaseUser()) assert.Equal(t, "", c.DatabasePassword()) - c.options.DatabaseDriver = MySQL + c.options.DatabaseDriver = constants.MySQL assert.Equal(t, "honeypot:1234", c.DatabaseServer()) assert.Equal(t, "honeypot", c.DatabaseHost()) @@ -56,7 +58,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 = constants.SQLite3 assert.Equal(t, "", c.DatabaseServer()) assert.Equal(t, "", c.DatabaseHost()) @@ -112,9 +114,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 = constants.MySQL assert.Equal(t, "StoryOfAmélie", c.DatabasePassword()) - c.Options().DatabaseDriver = SQLite3 + c.Options().DatabaseDriver = constants.SQLite3 _ = os.Setenv(FlagFileVar("DATABASE_PASSWORD"), "") assert.Equal(t, "", c.DatabasePassword()) @@ -124,7 +126,7 @@ func TestConfig_DatabaseDsn(t *testing.T) { c := NewConfig(CliTestContext()) driver := c.DatabaseDriver() - assert.Equal(t, SQLite3, driver) + assert.Equal(t, constants.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()) @@ -142,7 +144,7 @@ func TestConfig_DatabaseFile(t *testing.T) { c := NewConfig(CliTestContext()) driver := c.DatabaseDriver() - assert.Equal(t, SQLite3, driver) + assert.Equal(t, constants.SQLite3, driver) c.options.DatabaseDsn = "" assert.Equal(t, "/go/src/github.com/photoprism/photoprism/storage/testdata/index.db", c.DatabaseFile()) assert.Equal(t, "/go/src/github.com/photoprism/photoprism/storage/testdata/index.db?_busy_timeout=5000", c.DatabaseDsn()) diff --git a/internal/config/report.go b/internal/config/report.go index 442530cf5..b86cf19c5 100644 --- a/internal/config/report.go +++ b/internal/config/report.go @@ -5,6 +5,8 @@ import ( "strings" "time" "unicode/utf8" + + "github.com/photoprism/photoprism/pkg/constants" ) // Report returns global config values as a table for reporting. @@ -13,7 +15,7 @@ func (c *Config) Report() (rows [][]string, cols []string) { var dbKey string - if c.DatabaseDriver() == SQLite3 { + if c.DatabaseDriver() == constants.SQLite3 { dbKey = "database-dsn" } else { dbKey = "database-name" diff --git a/internal/config/test.go b/internal/config/test.go index a8a3416e6..a1c3316ef 100644 --- a/internal/config/test.go +++ b/internal/config/test.go @@ -20,6 +20,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/constants" "github.com/photoprism/photoprism/pkg/fs" "github.com/photoprism/photoprism/pkg/rnd" ) @@ -68,11 +69,11 @@ func NewTestOptions(pkg string) *Options { // Set default test database driver. if driver == "test" || driver == "sqlite" || driver == "" || dsn == "" { - driver = SQLite3 + driver = constants.SQLite3 } // Set default database DSN. - if driver == SQLite3 { + if driver == constants.SQLite3 { if dsn == "" && pkg != "" { if dsn = fmt.Sprintf(".%s.db", clean.TypeLower(pkg)); !fs.FileExists(dsn) { log.Debugf("sqlite: test database %s does not already exist", clean.Log(dsn)) @@ -142,7 +143,7 @@ func NewTestOptionsError() *Options { OriginalsPath: dataPath + "/originals", ImportPath: dataPath + "/import", TempPath: dataPath + "/temp", - DatabaseDriver: SQLite3, + DatabaseDriver: constants.SQLite3, DatabaseDsn: ".test-error.db", } diff --git a/internal/entity/db_conn.go b/internal/entity/db_conn.go index 14db43ab1..4f3bc77ab 100644 --- a/internal/entity/db_conn.go +++ b/internal/entity/db_conn.go @@ -10,14 +10,6 @@ import ( _ "github.com/jinzhu/gorm/dialects/sqlite" ) -// Supported test databases. -const ( - MySQL = "mysql" - SQLite3 = "sqlite3" - SQLiteTestDB = ".test.db" - SQLiteMemoryDSN = ":memory:?cache=shared" -) - // dbConn is the global gorm.DB connection provider. var dbConn Gorm diff --git a/internal/entity/entity_counts.go b/internal/entity/entity_counts.go index d6315549d..ac1bf7209 100644 --- a/internal/entity/entity_counts.go +++ b/internal/entity/entity_counts.go @@ -11,6 +11,7 @@ import ( "github.com/photoprism/photoprism/internal/mutex" "github.com/photoprism/photoprism/pkg/clean" + "github.com/photoprism/photoprism/pkg/constants" ) // countsBusy is true when the covers are currently updating. @@ -101,7 +102,7 @@ func UpdateSubjectCounts(public bool) (err error) { condition := gorm.Expr("subj_type = ?", SubjPerson) switch DbDialect() { - case MySQL: + case constants.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 @@ -112,7 +113,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 constants.SQLite3: // Update files count. res = Db().Table(subjTable). UpdateColumn("file_count", gorm.Expr("(SELECT COUNT(DISTINCT f.id)"+ @@ -151,7 +152,7 @@ func UpdateLabelCounts() (err error) { start := time.Now() var res *gorm.DB - if IsDialect(MySQL) { + if IsDialect(constants.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 @@ -165,7 +166,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(constants.SQLite3) { res = Db(). Table("labels"). UpdateColumn("photo_count", diff --git a/internal/entity/entity_init.go b/internal/entity/entity_init.go index 62f034dee..b948379aa 100644 --- a/internal/entity/entity_init.go +++ b/internal/entity/entity_init.go @@ -6,6 +6,7 @@ import ( "github.com/photoprism/photoprism/internal/entity/migrate" "github.com/photoprism/photoprism/pkg/clean" + "github.com/photoprism/photoprism/pkg/constants" "github.com/photoprism/photoprism/pkg/fs" ) @@ -57,13 +58,13 @@ func InitTestDb(driver, dsn string) *DbConn { // Set default test database driver. if driver == "test" || driver == "sqlite" || driver == "" || dsn == "" { - driver = SQLite3 + driver = constants.SQLite3 } // Set default database DSN. - if driver == SQLite3 { - if dsn == "" || dsn == SQLiteTestDB { - dsn = SQLiteTestDB + if driver == constants.SQLite3 { + if dsn == "" || dsn == constants.SQLiteTestDB { + dsn = constants.SQLiteTestDB if !fs.FileExists(dsn) { log.Debugf("sqlite: test database %s does not already exist", clean.Log(dsn)) } else if err := os.Remove(dsn); err != nil { diff --git a/internal/entity/face.go b/internal/entity/face.go index e398ec234..df4966c5a 100644 --- a/internal/entity/face.go +++ b/internal/entity/face.go @@ -11,6 +11,7 @@ import ( "time" "github.com/photoprism/photoprism/internal/ai/face" + "github.com/photoprism/photoprism/pkg/constants" "github.com/photoprism/photoprism/pkg/rnd" ) @@ -306,7 +307,7 @@ func (m *Face) RefreshPhotos() error { var err error switch DbDialect() { - case MySQL: + case constants.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 diff --git a/internal/entity/file.go b/internal/entity/file.go index 652ed03af..a14ec84ce 100644 --- a/internal/entity/file.go +++ b/internal/entity/file.go @@ -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/constants" "github.com/photoprism/photoprism/pkg/fs" "github.com/photoprism/photoprism/pkg/media" "github.com/photoprism/photoprism/pkg/media/colors" @@ -128,7 +129,7 @@ func (m File) RegenerateIndex() { } switch DbDialect() { - case MySQL: + case constants.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) @@ -140,7 +141,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 constants.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) diff --git a/internal/entity/marker.go b/internal/entity/marker.go index e968fffb3..fdb953356 100644 --- a/internal/entity/marker.go +++ b/internal/entity/marker.go @@ -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/constants" "github.com/photoprism/photoprism/pkg/rnd" ) @@ -568,7 +569,7 @@ func (m *Marker) RefreshPhotos() error { var err error switch DbDialect() { - case MySQL: + case constants.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 = ?`, diff --git a/internal/entity/migrate/dialects.go b/internal/entity/migrate/dialects.go index 9ee0d400e..ec8b3039c 100644 --- a/internal/entity/migrate/dialects.go +++ b/internal/entity/migrate/dialects.go @@ -1,19 +1,17 @@ package migrate -import "sync" +import ( + "sync" -// Supported database dialects. -const ( - MySQL = "mysql" - SQLite3 = "sqlite3" + "github.com/photoprism/photoprism/pkg/constants" ) var Dialects = map[string]Migrations{ - MySQL: DialectMySQL, - SQLite3: DialectSQLite3, + constants.MySQL: DialectMySQL, + constants.SQLite3: DialectSQLite3, } var once = map[string]*sync.Once{ - MySQL: {}, - SQLite3: {}, + constants.MySQL: {}, + constants.SQLite3: {}, } diff --git a/internal/entity/mysql8_test.go b/internal/entity/mysql8_test.go index 3ce57e920..4d585bc1a 100644 --- a/internal/entity/mysql8_test.go +++ b/internal/entity/mysql8_test.go @@ -6,6 +6,7 @@ import ( "time" "github.com/photoprism/photoprism/internal/entity/migrate" + "github.com/photoprism/photoprism/pkg/constants" "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 := constants.MySQL db, err := gorm.Open(dbDriver, dbDsn) if err != nil || db == nil { diff --git a/internal/entity/photo_estimate.go b/internal/entity/photo_estimate.go index 2160640d6..3c543e88d 100644 --- a/internal/entity/photo_estimate.go +++ b/internal/entity/photo_estimate.go @@ -6,6 +6,7 @@ import ( "github.com/jinzhu/gorm" "github.com/photoprism/photoprism/pkg/clean" + "github.com/photoprism/photoprism/pkg/constants" "github.com/photoprism/photoprism/pkg/fs" "github.com/photoprism/photoprism/pkg/geo" "github.com/photoprism/photoprism/pkg/txt" @@ -97,14 +98,14 @@ func (m *Photo) EstimateLocation(force bool) { var mostRecent Photos switch DbDialect() { - case MySQL: + case constants.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 constants.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). diff --git a/internal/entity/photo_merge.go b/internal/entity/photo_merge.go index fdccad6cb..87bb2050d 100644 --- a/internal/entity/photo_merge.go +++ b/internal/entity/photo_merge.go @@ -5,6 +5,7 @@ import ( "github.com/jinzhu/gorm" + "github.com/photoprism/photoprism/pkg/constants" "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 constants.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 constants.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)) diff --git a/internal/entity/query/albums.go b/internal/entity/query/albums.go index 95691da52..e0cbde103 100644 --- a/internal/entity/query/albums.go +++ b/internal/entity/query/albums.go @@ -10,6 +10,7 @@ import ( "github.com/photoprism/photoprism/internal/entity/sortby" "github.com/photoprism/photoprism/pkg/clean" + "github.com/photoprism/photoprism/pkg/constants" "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 constants.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 diff --git a/internal/entity/query/covers.go b/internal/entity/query/covers.go index b5f0297df..75283eafb 100644 --- a/internal/entity/query/covers.go +++ b/internal/entity/query/covers.go @@ -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/constants" "github.com/photoprism/photoprism/pkg/media" ) @@ -30,7 +31,7 @@ func UpdateAlbumDefaultCovers() (err error) { condition := gorm.Expr("album_type = ? AND thumb_src = ?", entity.AlbumManual, entity.SrcAuto) switch DbDialect() { - case MySQL: + case constants.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 @@ -39,7 +40,7 @@ func UpdateAlbumDefaultCovers() (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 constants.SQLite3: res = Db().Table(entity.Album{}.TableName()). UpdateColumn("thumb", gorm.Expr(`( SELECT f.file_hash FROM files f @@ -78,7 +79,7 @@ func UpdateAlbumFolderCovers() (err error) { condition := gorm.Expr("album_type = ? AND thumb_src = ?", entity.AlbumFolder, entity.SrcAuto) switch DbDialect() { - case MySQL: + case constants.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 @@ -86,7 +87,7 @@ func UpdateAlbumFolderCovers() (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 constants.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 @@ -126,7 +127,7 @@ func UpdateAlbumMonthCovers() (err error) { condition := gorm.Expr("album_type = ? AND thumb_src = ?", entity.AlbumMonth, entity.SrcAuto) switch DbDialect() { - case MySQL: + case constants.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 @@ -134,7 +135,7 @@ func UpdateAlbumMonthCovers() (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 constants.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 @@ -194,7 +195,7 @@ func UpdateLabelCovers() (err error) { condition := gorm.Expr("thumb_src = ?", entity.SrcAuto) switch DbDialect() { - case MySQL: + case constants.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 @@ -210,7 +211,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 constants.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 @@ -272,7 +273,7 @@ func UpdateSubjectCovers(public bool) (err error) { // Compose SQL update query. switch DbDialect() { - case MySQL: + case constants.MySQL: res = Db().Exec(`UPDATE subjects LEFT JOIN ( SELECT m.subj_uid, m.q, MAX(m.thumb) AS marker_thumb FROM markers m @@ -286,7 +287,7 @@ func UpdateSubjectCovers(public bool) (err error) { photosJoin, condition, ) - case SQLite3: + case constants.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(`( diff --git a/internal/entity/query/file_selection.go b/internal/entity/query/file_selection.go index 65a89a797..1581342ad 100644 --- a/internal/entity/query/file_selection.go +++ b/internal/entity/query/file_selection.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" + "github.com/photoprism/photoprism/pkg/constants" "github.com/photoprism/photoprism/pkg/fs" "github.com/photoprism/photoprism/internal/entity" @@ -97,9 +98,9 @@ func SelectedFiles(frm form.Selection, o FileSelection) (results entity.Files, e var concat string switch DbDialect() { - case MySQL: + case constants.MySQL: concat = "CONCAT(a.path, '/%')" - case SQLite3: + case constants.SQLite3: concat = "a.path || '/%'" default: return results, fmt.Errorf("unknown sql dialect: %s", DbDialect()) diff --git a/internal/entity/query/folders.go b/internal/entity/query/folders.go index 5a2ec3dcb..2253377c7 100644 --- a/internal/entity/query/folders.go +++ b/internal/entity/query/folders.go @@ -5,6 +5,7 @@ import ( "github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/mutex" + "github.com/photoprism/photoprism/pkg/constants" "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 constants.MySQL: return UnscopedDb().Exec(`UPDATE folders INNER JOIN (SELECT photo_path, MAX(taken_at_local) AS taken_max diff --git a/internal/entity/query/photo_selection.go b/internal/entity/query/photo_selection.go index b282dec3d..07fe92c94 100644 --- a/internal/entity/query/photo_selection.go +++ b/internal/entity/query/photo_selection.go @@ -6,6 +6,7 @@ import ( "github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/form" + "github.com/photoprism/photoprism/pkg/constants" ) // 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 constants.MySQL: concat = "CONCAT(a.path, '/%')" - case SQLite3: + case constants.SQLite3: concat = "a.path || '/%'" default: return results, fmt.Errorf("unknown sql dialect: %s", DbDialect()) diff --git a/internal/entity/query/query.go b/internal/entity/query/query.go index 9ef22f9a3..abc080545 100644 --- a/internal/entity/query/query.go +++ b/internal/entity/query/query.go @@ -29,15 +29,11 @@ import ( "github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/event" + "github.com/photoprism/photoprism/pkg/constants" ) var log = event.Log -const ( - MySQL = "mysql" - SQLite3 = "sqlite3" -) - // Cols represents a list of database columns. type Cols []string @@ -78,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 constants.SQLite3: return 333 default: return 1000 diff --git a/internal/entity/search/conditions_test.go b/internal/entity/search/conditions_test.go index a9b8d4396..2335e18a7 100644 --- a/internal/entity/search/conditions_test.go +++ b/internal/entity/search/conditions_test.go @@ -8,6 +8,7 @@ import ( "github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/pkg/clean" + "github.com/photoprism/photoprism/pkg/constants" "github.com/photoprism/photoprism/pkg/txt" ) @@ -18,7 +19,7 @@ func TestLike(t *testing.T) { t.Run("Special", func(t *testing.T) { s := " ' \" \t \n %_''\\" exp := "'' \" %_''''\\" - if entity.DbDialect() == entity.MySQL { + if entity.DbDialect() == constants.MySQL { exp = "'' \" %_''''\\\\" } result := Like(s) diff --git a/internal/entity/sortby/random.go b/internal/entity/sortby/random.go index 3a79a7b98..734b4180d 100644 --- a/internal/entity/sortby/random.go +++ b/internal/entity/sortby/random.go @@ -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/constants" ) // 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 constants.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 constants.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()") diff --git a/internal/entity/sortby/random_test.go b/internal/entity/sortby/random_test.go index 4f7f91f02..2491b583a 100644 --- a/internal/entity/sortby/random_test.go +++ b/internal/entity/sortby/random_test.go @@ -5,12 +5,14 @@ import ( "github.com/jinzhu/gorm" + "github.com/photoprism/photoprism/pkg/constants" + "github.com/stretchr/testify/assert" ) func TestRandomExpr(t *testing.T) { - mysql, _ := gorm.GetDialect(MySQL) - sqlite3, _ := gorm.GetDialect(SQLite3) + mysql, _ := gorm.GetDialect(constants.MySQL) + sqlite3, _ := gorm.GetDialect(constants.SQLite3) assert.Equal(t, gorm.Expr("RAND()"), RandomExpr(mysql)) assert.Equal(t, gorm.Expr("RANDOM()"), RandomExpr(sqlite3)) diff --git a/internal/entity/subject.go b/internal/entity/subject.go index 3b7370e6e..ffcf6e9d1 100644 --- a/internal/entity/subject.go +++ b/internal/entity/subject.go @@ -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/constants" "github.com/photoprism/photoprism/pkg/rnd" "github.com/photoprism/photoprism/pkg/txt" ) @@ -470,7 +471,7 @@ func (m *Subject) RefreshPhotos() error { var err error switch DbDialect() { - case MySQL: + case constants.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 diff --git a/internal/photoprism/backup/database.go b/internal/photoprism/backup/database.go index 21b8de003..9989df00f 100644 --- a/internal/photoprism/backup/database.go +++ b/internal/photoprism/backup/database.go @@ -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/constants" "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 constants.MySQL, constants.MariaDB: // Connect via Unix Domain Socket? if socketName := c.DatabaseServer(); strings.HasPrefix(socketName, "/") { cmd = exec.Command( @@ -112,7 +112,7 @@ func Database(backupPath, fileName string, toStdOut, force bool, retain int) (er c.DatabaseName(), ) } - case config.SQLite3: + case constants.SQLite3: if !fs.FileExistsNotEmpty(c.DatabaseFile()) { return fmt.Errorf("sqlite database file %s not found", clean.LogQuote(c.DatabaseFile())) } @@ -252,7 +252,7 @@ func RestoreDatabase(backupPath, fileName string, fromStdIn, force bool) (err er var cmd *exec.Cmd switch c.DatabaseDriver() { - case config.MySQL, config.MariaDB: + case constants.MySQL, constants.MariaDB: // Connect via Unix Domain Socket? if socketName := c.DatabaseServer(); strings.HasPrefix(socketName, "/") { cmd = exec.Command( @@ -294,7 +294,7 @@ func RestoreDatabase(backupPath, fileName string, fromStdIn, force bool) (err er c.DatabaseName(), ) } - case config.SQLite3: + case constants.SQLite3: log.Infoln("restore: dropping existing sqlite database tables") tables.Drop(c.Db()) cmd = exec.Command( diff --git a/pkg/clean/sql.go b/pkg/clean/sql.go index 50abaf80a..caea6fd41 100644 --- a/pkg/clean/sql.go +++ b/pkg/clean/sql.go @@ -1,11 +1,6 @@ package clean -const ( - MySQL = "mysql" - MariaDB = "mariadb" - Postgres = "postgres" - SQLite3 = "sqlite3" -) +import "github.com/photoprism/photoprism/pkg/constants" // SqlSpecial checks if the byte must be escaped/omitted in SQL. func SqlSpecial(b byte, dialect string) (special bool, omit bool) { @@ -13,7 +8,7 @@ func SqlSpecial(b byte, dialect string) (special bool, omit bool) { return true, true } - if dialect == MySQL { + if dialect == constants.MySQL { switch b { case '\'', '\\': return true, false diff --git a/pkg/clean/sql_test.go b/pkg/clean/sql_test.go index 04d848210..5a8470519 100644 --- a/pkg/clean/sql_test.go +++ b/pkg/clean/sql_test.go @@ -4,67 +4,69 @@ import ( "testing" "github.com/stretchr/testify/assert" + + "github.com/photoprism/photoprism/pkg/constants" ) func TestSqlSpecial(t *testing.T) { t.Run("Special MySQL", func(t *testing.T) { - if s, o := SqlSpecial(1, MySQL); !s { + if s, o := SqlSpecial(1, constants.MySQL); !s { t.Error("char is special") } else if !o { t.Error("\" must be omitted") } - if s, o := SqlSpecial(31, MySQL); !s { + if s, o := SqlSpecial(31, constants.MySQL); !s { t.Error("char is special") } else if !o { t.Error("\" must be omitted") } - if s, o := SqlSpecial('\\', MySQL); !s { + if s, o := SqlSpecial('\\', constants.MySQL); !s { t.Error("\\ is special") } else if o { t.Error("\\ must not be omitted") } - if s, o := SqlSpecial('\'', MySQL); !s { + if s, o := SqlSpecial('\'', constants.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, SQLite3); !s { + if s, o := SqlSpecial(1, constants.SQLite3); !s { t.Error("char is special") } else if !o { t.Error("\" must be omitted") } - if s, o := SqlSpecial(31, SQLite3); !s { + if s, o := SqlSpecial(31, constants.SQLite3); !s { t.Error("char is special") } else if !o { t.Error("\" must be omitted") } - if s, o := SqlSpecial('\'', SQLite3); !s { + if s, o := SqlSpecial('\'', constants.SQLite3); !s { t.Error("' is special") } else if o { t.Error("' must not be omitted") } }) t.Run("Special Postgres", func(t *testing.T) { - if s, o := SqlSpecial(1, Postgres); !s { + if s, o := SqlSpecial(1, constants.Postgres); !s { t.Error("char is special") } else if !o { t.Error("\" must be omitted") } - if s, o := SqlSpecial(31, Postgres); !s { + if s, o := SqlSpecial(31, constants.Postgres); !s { t.Error("char is special") } else if !o { t.Error("\" must be omitted") } - if s, o := SqlSpecial('\'', Postgres); !s { + if s, o := SqlSpecial('\'', constants.Postgres); !s { t.Error("' is special") } else if o { t.Error("' must not be omitted") @@ -72,105 +74,105 @@ func TestSqlSpecial(t *testing.T) { }) t.Run("NotSpecial MySQL", func(t *testing.T) { - if s, o := SqlSpecial(32, MySQL); s { + if s, o := SqlSpecial(32, constants.MySQL); s { t.Error("space is not special") } else if o { t.Error("space must not be omitted") } - if s, o := SqlSpecial('A', MySQL); s { + if s, o := SqlSpecial('A', constants.MySQL); s { t.Error("A is not special") } else if o { t.Error("A must not be omitted") } - if s, o := SqlSpecial('a', MySQL); s { + if s, o := SqlSpecial('a', constants.MySQL); s { t.Error("a is not special") } else if o { t.Error("a must not be omitted") } - if s, o := SqlSpecial('_', MySQL); s { + if s, o := SqlSpecial('_', constants.MySQL); s { t.Error("_ is not special") } else if o { t.Error("_ must not be omitted") } - if s, o := SqlSpecial('"', MySQL); s { + if s, o := SqlSpecial('"', constants.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, SQLite3); s { + if s, o := SqlSpecial(32, constants.SQLite3); s { t.Error("space is not special") } else if o { t.Error("space must not be omitted") } - if s, o := SqlSpecial('A', SQLite3); s { + if s, o := SqlSpecial('A', constants.SQLite3); s { t.Error("A is not special") } else if o { t.Error("A must not be omitted") } - if s, o := SqlSpecial('a', SQLite3); s { + if s, o := SqlSpecial('a', constants.SQLite3); s { t.Error("a is not special") } else if o { t.Error("a must not be omitted") } - if s, o := SqlSpecial('_', SQLite3); s { + if s, o := SqlSpecial('_', constants.SQLite3); s { t.Error("_ is not special") } else if o { t.Error("_ must not be omitted") } - if s, o := SqlSpecial('"', SQLite3); s { + if s, o := SqlSpecial('"', constants.SQLite3); s { t.Error("\" is not special") } else if o { t.Error("\" must not be omitted") } - if s, o := SqlSpecial('\\', SQLite3); s { + if s, o := SqlSpecial('\\', constants.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, Postgres); s { + if s, o := SqlSpecial(32, constants.Postgres); s { t.Error("space is not special") } else if o { t.Error("space must not be omitted") } - if s, o := SqlSpecial('A', Postgres); s { + if s, o := SqlSpecial('A', constants.Postgres); s { t.Error("A is not special") } else if o { t.Error("A must not be omitted") } - if s, o := SqlSpecial('a', Postgres); s { + if s, o := SqlSpecial('a', constants.Postgres); s { t.Error("a is not special") } else if o { t.Error("a must not be omitted") } - if s, o := SqlSpecial('_', Postgres); s { + if s, o := SqlSpecial('_', constants.Postgres); s { t.Error("_ is not special") } else if o { t.Error("_ must not be omitted") } - if s, o := SqlSpecial('"', Postgres); s { + if s, o := SqlSpecial('"', constants.Postgres); s { t.Error("\" is not special") } else if o { t.Error("\" must not be omitted") } - if s, o := SqlSpecial('\\', Postgres); s { + if s, o := SqlSpecial('\\', constants.Postgres); s { t.Error("\\ is not special") } else if o { t.Error("\\ must not be omitted") @@ -180,32 +182,32 @@ func TestSqlSpecial(t *testing.T) { func TestSqlString(t *testing.T) { t.Run("Empty", func(t *testing.T) { - assert.Equal(t, "", SqlString("", MySQL)) - assert.Equal(t, "", SqlString("", SQLite3)) - assert.Equal(t, "", SqlString("", Postgres)) + assert.Equal(t, "", SqlString("", constants.MySQL)) + assert.Equal(t, "", SqlString("", constants.SQLite3)) + assert.Equal(t, "", SqlString("", constants.Postgres)) }) t.Run("Special", func(t *testing.T) { s := "' \" \t \n %_''\\" exp := "'' \" %_''''\\\\" - result := SqlString(s, MySQL) + result := SqlString(s, constants.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, SQLite3) + result = SqlString(s, constants.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, Postgres) + result = SqlString(s, constants.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", MySQL)) + assert.Equal(t, "123ABCabc", SqlString("123ABCabc", constants.MySQL)) }) } diff --git a/pkg/constants/sql.go b/pkg/constants/sql.go new file mode 100644 index 000000000..e17e27886 --- /dev/null +++ b/pkg/constants/sql.go @@ -0,0 +1,19 @@ +package constants + +// 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" +) From 9a145cae338c7ecb1d19ed0b29218836d3784032 Mon Sep 17 00:00:00 2001 From: Keith Martin Date: Wed, 3 Sep 2025 17:40:38 +1000 Subject: [PATCH 3/8] Backend: rename package constants to enum --- internal/config/config.go | 4 +- internal/config/config_db.go | 48 ++++++++-------- internal/config/config_db_test.go | 18 +++--- internal/config/report.go | 4 +- internal/config/test.go | 8 +-- internal/entity/entity_counts.go | 10 ++-- internal/entity/entity_init.go | 10 ++-- internal/entity/face.go | 4 +- internal/entity/file.go | 6 +- internal/entity/marker.go | 4 +- internal/entity/migrate/dialects.go | 10 ++-- internal/entity/mysql8_test.go | 4 +- internal/entity/photo_estimate.go | 6 +- internal/entity/photo_merge.go | 6 +- internal/entity/query/albums.go | 4 +- internal/entity/query/covers.go | 22 +++---- internal/entity/query/file_selection.go | 6 +- internal/entity/query/folders.go | 4 +- internal/entity/query/photo_selection.go | 6 +- internal/entity/query/query.go | 4 +- internal/entity/search/conditions_test.go | 4 +- internal/entity/sortby/random.go | 6 +- internal/entity/sortby/random_test.go | 6 +- internal/entity/subject.go | 4 +- internal/photoprism/backup/database.go | 10 ++-- pkg/clean/sql.go | 4 +- pkg/clean/sql_test.go | 70 +++++++++++------------ pkg/{constants => enum}/sql.go | 2 +- 28 files changed, 147 insertions(+), 147 deletions(-) rename pkg/{constants => enum}/sql.go (93%) diff --git a/internal/config/config.go b/internal/config/config.go index a1e493b84..984004f6c 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -58,7 +58,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/constants" + "github.com/photoprism/photoprism/pkg/enum" "github.com/photoprism/photoprism/pkg/fs" "github.com/photoprism/photoprism/pkg/i18n" "github.com/photoprism/photoprism/pkg/rnd" @@ -643,7 +643,7 @@ func (c *Config) IndexWorkers() int { } // Limit number of workers when using SQLite3 to avoid database locking issues. - if c.DatabaseDriver() == constants.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 } diff --git a/internal/config/config_db.go b/internal/config/config_db.go index bf8831a5a..e879042e5 100644 --- a/internal/config/config_db.go +++ b/internal/config/config_db.go @@ -19,7 +19,7 @@ import ( "github.com/photoprism/photoprism/internal/entity/migrate" "github.com/photoprism/photoprism/internal/mutex" "github.com/photoprism/photoprism/pkg/clean" - "github.com/photoprism/photoprism/pkg/constants" + "github.com/photoprism/photoprism/pkg/enum" ) // SQLite default DSNs. @@ -31,17 +31,17 @@ const ( // DatabaseDriver returns the database driver name. func (c *Config) DatabaseDriver() string { switch strings.ToLower(c.options.DatabaseDriver) { - case constants.MySQL, constants.MariaDB: - c.options.DatabaseDriver = constants.MySQL - case constants.SQLite3, "sqlite", "test", "file", "": - c.options.DatabaseDriver = constants.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 = constants.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 = constants.SQLite3 + c.options.DatabaseDriver = enum.SQLite3 c.options.DatabaseDsn = "" } @@ -51,9 +51,9 @@ func (c *Config) DatabaseDriver() string { // DatabaseDriverName returns the formatted database driver name. func (c *Config) DatabaseDriverName() string { switch c.DatabaseDriver() { - case constants.MySQL, constants.MariaDB: + case enum.MySQL, enum.MariaDB: return "MariaDB" - case constants.SQLite3, "sqlite", "test", "file", "": + case enum.SQLite3, "sqlite", "test", "file", "": return "SQLite" case "tidb": return "TiDB" @@ -83,7 +83,7 @@ func (c *Config) DatabaseSsl() bool { } switch c.DatabaseDriver() { - case constants.MySQL: + case enum.MySQL: // see https://mariadb.org/mission-impossible-zero-configuration-ssl/ return c.IsDatabaseVersion("v11.4") default: @@ -95,7 +95,7 @@ func (c *Config) DatabaseSsl() bool { func (c *Config) DatabaseDsn() string { if c.options.DatabaseDsn == "" { switch c.DatabaseDriver() { - case constants.MySQL, constants.MariaDB: + case enum.MySQL, enum.MariaDB: databaseServer := c.DatabaseServer() // Connect via Unix Domain Socket? @@ -114,7 +114,7 @@ func (c *Config) DatabaseDsn() string { c.DatabaseName(), c.DatabaseTimeout(), ) - case constants.Postgres: + case enum.Postgres: return fmt.Sprintf( "user=%s password=%s dbname=%s host=%s port=%d connect_timeout=%d sslmode=disable TimeZone=UTC", c.DatabaseUser(), @@ -124,7 +124,7 @@ func (c *Config) DatabaseDsn() string { c.DatabasePort(), c.DatabaseTimeout(), ) - case constants.SQLite3: + case enum.SQLite3: return filepath.Join(c.StoragePath(), "index.db?_busy_timeout=5000") default: log.Errorf("config: empty database dsn") @@ -159,7 +159,7 @@ func (c *Config) ParseDatabaseDsn() { func (c *Config) DatabaseServer() string { c.ParseDatabaseDsn() - if c.DatabaseDriver() == constants.SQLite3 { + if c.DatabaseDriver() == enum.SQLite3 { return "" } else if c.options.DatabaseServer == "" { return localhost @@ -170,7 +170,7 @@ func (c *Config) DatabaseServer() string { // DatabaseHost the database server host. func (c *Config) DatabaseHost() string { - if c.DatabaseDriver() == constants.SQLite3 { + if c.DatabaseDriver() == enum.SQLite3 { return "" } @@ -200,7 +200,7 @@ func (c *Config) DatabasePort() int { // DatabasePortString the database server port as string. func (c *Config) DatabasePortString() string { - if c.DatabaseDriver() == constants.SQLite3 { + if c.DatabaseDriver() == enum.SQLite3 { return "" } @@ -211,7 +211,7 @@ func (c *Config) DatabasePortString() string { func (c *Config) DatabaseName() string { c.ParseDatabaseDsn() - if c.DatabaseDriver() == constants.SQLite3 { + if c.DatabaseDriver() == enum.SQLite3 { return c.DatabaseDsn() } else if c.options.DatabaseName == "" { return "photoprism" @@ -222,7 +222,7 @@ func (c *Config) DatabaseName() string { // DatabaseUser returns the database user name. func (c *Config) DatabaseUser() string { - if c.DatabaseDriver() == constants.SQLite3 { + if c.DatabaseDriver() == enum.SQLite3 { return "" } @@ -237,7 +237,7 @@ func (c *Config) DatabaseUser() string { // DatabasePassword returns the database user password. func (c *Config) DatabasePassword() string { - if c.DatabaseDriver() == constants.SQLite3 { + if c.DatabaseDriver() == enum.SQLite3 { return "" } @@ -328,11 +328,11 @@ func (c *Config) CloseDb() error { // SetDbOptions sets the database collation to unicode if supported. func (c *Config) SetDbOptions() { switch c.DatabaseDriver() { - case constants.MySQL, constants.MariaDB: + case enum.MySQL, enum.MariaDB: c.Db().Set("gorm:table_options", "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci") - case constants.Postgres: + case enum.Postgres: // Ignore for now. - case constants.SQLite3: + case enum.SQLite3: // Not required as unicode is default. } } @@ -388,7 +388,7 @@ func (c *Config) InitTestDb() { // checkDb checks the database server version. func (c *Config) checkDb(db *gorm.DB) error { switch c.DatabaseDriver() { - case constants.MySQL: + case enum.MySQL: type Res struct { Value string `gorm:"column:Value;"` } @@ -416,7 +416,7 @@ func (c *Config) checkDb(db *gorm.DB) error { } else if !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 constants.SQLite3: + case enum.SQLite3: type Res struct { Value string `gorm:"column:Value;"` } diff --git a/internal/config/config_db_test.go b/internal/config/config_db_test.go index 6f8d370ba..e27c60e8f 100644 --- a/internal/config/config_db_test.go +++ b/internal/config/config_db_test.go @@ -6,14 +6,14 @@ import ( "github.com/stretchr/testify/assert" - "github.com/photoprism/photoprism/pkg/constants" + "github.com/photoprism/photoprism/pkg/enum" ) func TestConfig_DatabaseDriver(t *testing.T) { c := NewConfig(CliTestContext()) driver := c.DatabaseDriver() - assert.Equal(t, constants.SQLite3, driver) + assert.Equal(t, enum.SQLite3, driver) } func TestConfig_DatabaseDriverName(t *testing.T) { @@ -40,7 +40,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 = constants.SQLite3 + c.options.DatabaseDriver = enum.SQLite3 assert.Equal(t, "", c.DatabaseServer()) assert.Equal(t, "", c.DatabaseHost()) @@ -49,7 +49,7 @@ func TestConfig_ParseDatabaseDsn(t *testing.T) { assert.Equal(t, "", c.DatabaseUser()) assert.Equal(t, "", c.DatabasePassword()) - c.options.DatabaseDriver = constants.MySQL + c.options.DatabaseDriver = enum.MySQL assert.Equal(t, "honeypot:1234", c.DatabaseServer()) assert.Equal(t, "honeypot", c.DatabaseHost()) @@ -58,7 +58,7 @@ func TestConfig_ParseDatabaseDsn(t *testing.T) { assert.Equal(t, "foo", c.DatabaseUser()) assert.Equal(t, "b@r", c.DatabasePassword()) - c.options.DatabaseDriver = constants.SQLite3 + c.options.DatabaseDriver = enum.SQLite3 assert.Equal(t, "", c.DatabaseServer()) assert.Equal(t, "", c.DatabaseHost()) @@ -114,9 +114,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 = constants.MySQL + c.Options().DatabaseDriver = enum.MySQL assert.Equal(t, "StoryOfAmélie", c.DatabasePassword()) - c.Options().DatabaseDriver = constants.SQLite3 + c.Options().DatabaseDriver = enum.SQLite3 _ = os.Setenv(FlagFileVar("DATABASE_PASSWORD"), "") assert.Equal(t, "", c.DatabasePassword()) @@ -126,7 +126,7 @@ func TestConfig_DatabaseDsn(t *testing.T) { c := NewConfig(CliTestContext()) driver := c.DatabaseDriver() - assert.Equal(t, constants.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()) @@ -144,7 +144,7 @@ func TestConfig_DatabaseFile(t *testing.T) { c := NewConfig(CliTestContext()) driver := c.DatabaseDriver() - assert.Equal(t, constants.SQLite3, driver) + assert.Equal(t, enum.SQLite3, driver) c.options.DatabaseDsn = "" assert.Equal(t, "/go/src/github.com/photoprism/photoprism/storage/testdata/index.db", c.DatabaseFile()) assert.Equal(t, "/go/src/github.com/photoprism/photoprism/storage/testdata/index.db?_busy_timeout=5000", c.DatabaseDsn()) diff --git a/internal/config/report.go b/internal/config/report.go index b86cf19c5..8cb2e492a 100644 --- a/internal/config/report.go +++ b/internal/config/report.go @@ -6,7 +6,7 @@ import ( "time" "unicode/utf8" - "github.com/photoprism/photoprism/pkg/constants" + "github.com/photoprism/photoprism/pkg/enum" ) // Report returns global config values as a table for reporting. @@ -15,7 +15,7 @@ func (c *Config) Report() (rows [][]string, cols []string) { var dbKey string - if c.DatabaseDriver() == constants.SQLite3 { + if c.DatabaseDriver() == enum.SQLite3 { dbKey = "database-dsn" } else { dbKey = "database-name" diff --git a/internal/config/test.go b/internal/config/test.go index a1c3316ef..893757611 100644 --- a/internal/config/test.go +++ b/internal/config/test.go @@ -20,7 +20,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/constants" + "github.com/photoprism/photoprism/pkg/enum" "github.com/photoprism/photoprism/pkg/fs" "github.com/photoprism/photoprism/pkg/rnd" ) @@ -69,11 +69,11 @@ func NewTestOptions(pkg string) *Options { // Set default test database driver. if driver == "test" || driver == "sqlite" || driver == "" || dsn == "" { - driver = constants.SQLite3 + driver = enum.SQLite3 } // Set default database DSN. - if driver == constants.SQLite3 { + if driver == enum.SQLite3 { if dsn == "" && pkg != "" { if dsn = fmt.Sprintf(".%s.db", clean.TypeLower(pkg)); !fs.FileExists(dsn) { log.Debugf("sqlite: test database %s does not already exist", clean.Log(dsn)) @@ -143,7 +143,7 @@ func NewTestOptionsError() *Options { OriginalsPath: dataPath + "/originals", ImportPath: dataPath + "/import", TempPath: dataPath + "/temp", - DatabaseDriver: constants.SQLite3, + DatabaseDriver: enum.SQLite3, DatabaseDsn: ".test-error.db", } diff --git a/internal/entity/entity_counts.go b/internal/entity/entity_counts.go index ac1bf7209..05c3016b7 100644 --- a/internal/entity/entity_counts.go +++ b/internal/entity/entity_counts.go @@ -11,7 +11,7 @@ import ( "github.com/photoprism/photoprism/internal/mutex" "github.com/photoprism/photoprism/pkg/clean" - "github.com/photoprism/photoprism/pkg/constants" + "github.com/photoprism/photoprism/pkg/enum" ) // countsBusy is true when the covers are currently updating. @@ -102,7 +102,7 @@ func UpdateSubjectCounts(public bool) (err error) { condition := gorm.Expr("subj_type = ?", SubjPerson) switch DbDialect() { - case constants.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 @@ -113,7 +113,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 constants.SQLite3: + case enum.SQLite3: // Update files count. res = Db().Table(subjTable). UpdateColumn("file_count", gorm.Expr("(SELECT COUNT(DISTINCT f.id)"+ @@ -152,7 +152,7 @@ func UpdateLabelCounts() (err error) { start := time.Now() var res *gorm.DB - if IsDialect(constants.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 @@ -166,7 +166,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(constants.SQLite3) { + } else if IsDialect(enum.SQLite3) { res = Db(). Table("labels"). UpdateColumn("photo_count", diff --git a/internal/entity/entity_init.go b/internal/entity/entity_init.go index b948379aa..1217c2dc9 100644 --- a/internal/entity/entity_init.go +++ b/internal/entity/entity_init.go @@ -6,7 +6,7 @@ import ( "github.com/photoprism/photoprism/internal/entity/migrate" "github.com/photoprism/photoprism/pkg/clean" - "github.com/photoprism/photoprism/pkg/constants" + "github.com/photoprism/photoprism/pkg/enum" "github.com/photoprism/photoprism/pkg/fs" ) @@ -58,13 +58,13 @@ func InitTestDb(driver, dsn string) *DbConn { // Set default test database driver. if driver == "test" || driver == "sqlite" || driver == "" || dsn == "" { - driver = constants.SQLite3 + driver = enum.SQLite3 } // Set default database DSN. - if driver == constants.SQLite3 { - if dsn == "" || dsn == constants.SQLiteTestDB { - dsn = constants.SQLiteTestDB + if driver == enum.SQLite3 { + if dsn == "" || dsn == enum.SQLiteTestDB { + dsn = enum.SQLiteTestDB if !fs.FileExists(dsn) { log.Debugf("sqlite: test database %s does not already exist", clean.Log(dsn)) } else if err := os.Remove(dsn); err != nil { diff --git a/internal/entity/face.go b/internal/entity/face.go index df4966c5a..e6f6a85a8 100644 --- a/internal/entity/face.go +++ b/internal/entity/face.go @@ -11,7 +11,7 @@ import ( "time" "github.com/photoprism/photoprism/internal/ai/face" - "github.com/photoprism/photoprism/pkg/constants" + "github.com/photoprism/photoprism/pkg/enum" "github.com/photoprism/photoprism/pkg/rnd" ) @@ -307,7 +307,7 @@ func (m *Face) RefreshPhotos() error { var err error switch DbDialect() { - case constants.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 diff --git a/internal/entity/file.go b/internal/entity/file.go index a14ec84ce..ce06c5cd4 100644 --- a/internal/entity/file.go +++ b/internal/entity/file.go @@ -17,7 +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/constants" + "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 +129,7 @@ func (m File) RegenerateIndex() { } switch DbDialect() { - case constants.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 +141,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 constants.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) diff --git a/internal/entity/marker.go b/internal/entity/marker.go index fdb953356..aa2fa12ac 100644 --- a/internal/entity/marker.go +++ b/internal/entity/marker.go @@ -14,7 +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/constants" + "github.com/photoprism/photoprism/pkg/enum" "github.com/photoprism/photoprism/pkg/rnd" ) @@ -569,7 +569,7 @@ func (m *Marker) RefreshPhotos() error { var err error switch DbDialect() { - case constants.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 = ?`, diff --git a/internal/entity/migrate/dialects.go b/internal/entity/migrate/dialects.go index ec8b3039c..41cd23b62 100644 --- a/internal/entity/migrate/dialects.go +++ b/internal/entity/migrate/dialects.go @@ -3,15 +3,15 @@ package migrate import ( "sync" - "github.com/photoprism/photoprism/pkg/constants" + "github.com/photoprism/photoprism/pkg/enum" ) var Dialects = map[string]Migrations{ - constants.MySQL: DialectMySQL, - constants.SQLite3: DialectSQLite3, + enum.MySQL: DialectMySQL, + enum.SQLite3: DialectSQLite3, } var once = map[string]*sync.Once{ - constants.MySQL: {}, - constants.SQLite3: {}, + enum.MySQL: {}, + enum.SQLite3: {}, } diff --git a/internal/entity/mysql8_test.go b/internal/entity/mysql8_test.go index 4d585bc1a..7c6842fbc 100644 --- a/internal/entity/mysql8_test.go +++ b/internal/entity/mysql8_test.go @@ -6,7 +6,7 @@ import ( "time" "github.com/photoprism/photoprism/internal/entity/migrate" - "github.com/photoprism/photoprism/pkg/constants" + "github.com/photoprism/photoprism/pkg/enum" "github.com/jinzhu/gorm" ) @@ -18,7 +18,7 @@ func TestMySQL8(t *testing.T) { t.Skip("skipping MySQL 8 test: PHOTOPRISM_TEST_DSN_MYSQL8 is not set") } - dbDriver := constants.MySQL + dbDriver := enum.MySQL db, err := gorm.Open(dbDriver, dbDsn) if err != nil || db == nil { diff --git a/internal/entity/photo_estimate.go b/internal/entity/photo_estimate.go index 3c543e88d..4f842235c 100644 --- a/internal/entity/photo_estimate.go +++ b/internal/entity/photo_estimate.go @@ -6,7 +6,7 @@ import ( "github.com/jinzhu/gorm" "github.com/photoprism/photoprism/pkg/clean" - "github.com/photoprism/photoprism/pkg/constants" + "github.com/photoprism/photoprism/pkg/enum" "github.com/photoprism/photoprism/pkg/fs" "github.com/photoprism/photoprism/pkg/geo" "github.com/photoprism/photoprism/pkg/txt" @@ -98,14 +98,14 @@ func (m *Photo) EstimateLocation(force bool) { var mostRecent Photos switch DbDialect() { - case constants.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 constants.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). diff --git a/internal/entity/photo_merge.go b/internal/entity/photo_merge.go index 87bb2050d..3c1258fd4 100644 --- a/internal/entity/photo_merge.go +++ b/internal/entity/photo_merge.go @@ -5,7 +5,7 @@ import ( "github.com/jinzhu/gorm" - "github.com/photoprism/photoprism/pkg/constants" + "github.com/photoprism/photoprism/pkg/enum" "github.com/photoprism/photoprism/pkg/rnd" ) @@ -109,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 constants.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 constants.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)) diff --git a/internal/entity/query/albums.go b/internal/entity/query/albums.go index e0cbde103..79c39f137 100644 --- a/internal/entity/query/albums.go +++ b/internal/entity/query/albums.go @@ -10,7 +10,7 @@ import ( "github.com/photoprism/photoprism/internal/entity/sortby" "github.com/photoprism/photoprism/pkg/clean" - "github.com/photoprism/photoprism/pkg/constants" + "github.com/photoprism/photoprism/pkg/enum" "github.com/photoprism/photoprism/pkg/media" "github.com/photoprism/photoprism/pkg/rnd" ) @@ -121,7 +121,7 @@ func UpdateAlbumDates() error { defer mutex.Index.Unlock() switch DbDialect() { - case constants.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 diff --git a/internal/entity/query/covers.go b/internal/entity/query/covers.go index 75283eafb..002eef21a 100644 --- a/internal/entity/query/covers.go +++ b/internal/entity/query/covers.go @@ -12,7 +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/constants" + "github.com/photoprism/photoprism/pkg/enum" "github.com/photoprism/photoprism/pkg/media" ) @@ -31,7 +31,7 @@ func UpdateAlbumDefaultCovers() (err error) { condition := gorm.Expr("album_type = ? AND thumb_src = ?", entity.AlbumManual, entity.SrcAuto) switch DbDialect() { - case constants.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 @@ -40,7 +40,7 @@ func UpdateAlbumDefaultCovers() (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 constants.SQLite3: + case enum.SQLite3: res = Db().Table(entity.Album{}.TableName()). UpdateColumn("thumb", gorm.Expr(`( SELECT f.file_hash FROM files f @@ -79,7 +79,7 @@ func UpdateAlbumFolderCovers() (err error) { condition := gorm.Expr("album_type = ? AND thumb_src = ?", entity.AlbumFolder, entity.SrcAuto) switch DbDialect() { - case constants.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 @@ -87,7 +87,7 @@ func UpdateAlbumFolderCovers() (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 constants.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 @@ -127,7 +127,7 @@ func UpdateAlbumMonthCovers() (err error) { condition := gorm.Expr("album_type = ? AND thumb_src = ?", entity.AlbumMonth, entity.SrcAuto) switch DbDialect() { - case constants.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 @@ -135,7 +135,7 @@ func UpdateAlbumMonthCovers() (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 constants.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 @@ -195,7 +195,7 @@ func UpdateLabelCovers() (err error) { condition := gorm.Expr("thumb_src = ?", entity.SrcAuto) switch DbDialect() { - case constants.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 @@ -211,7 +211,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 constants.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 @@ -273,7 +273,7 @@ func UpdateSubjectCovers(public bool) (err error) { // Compose SQL update query. switch DbDialect() { - case constants.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 @@ -287,7 +287,7 @@ func UpdateSubjectCovers(public bool) (err error) { photosJoin, condition, ) - case constants.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(`( diff --git a/internal/entity/query/file_selection.go b/internal/entity/query/file_selection.go index 1581342ad..b297608d5 100644 --- a/internal/entity/query/file_selection.go +++ b/internal/entity/query/file_selection.go @@ -4,7 +4,7 @@ import ( "errors" "fmt" - "github.com/photoprism/photoprism/pkg/constants" + "github.com/photoprism/photoprism/pkg/enum" "github.com/photoprism/photoprism/pkg/fs" "github.com/photoprism/photoprism/internal/entity" @@ -98,9 +98,9 @@ func SelectedFiles(frm form.Selection, o FileSelection) (results entity.Files, e var concat string switch DbDialect() { - case constants.MySQL: + case enum.MySQL: concat = "CONCAT(a.path, '/%')" - case constants.SQLite3: + case enum.SQLite3: concat = "a.path || '/%'" default: return results, fmt.Errorf("unknown sql dialect: %s", DbDialect()) diff --git a/internal/entity/query/folders.go b/internal/entity/query/folders.go index 2253377c7..7f9f31a43 100644 --- a/internal/entity/query/folders.go +++ b/internal/entity/query/folders.go @@ -5,7 +5,7 @@ import ( "github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/mutex" - "github.com/photoprism/photoprism/pkg/constants" + "github.com/photoprism/photoprism/pkg/enum" "github.com/photoprism/photoprism/pkg/fs" "github.com/photoprism/photoprism/pkg/media" ) @@ -76,7 +76,7 @@ func UpdateFolderDates() error { defer mutex.Index.Unlock() switch DbDialect() { - case constants.MySQL: + case enum.MySQL: return UnscopedDb().Exec(`UPDATE folders INNER JOIN (SELECT photo_path, MAX(taken_at_local) AS taken_max diff --git a/internal/entity/query/photo_selection.go b/internal/entity/query/photo_selection.go index 07fe92c94..746b179a7 100644 --- a/internal/entity/query/photo_selection.go +++ b/internal/entity/query/photo_selection.go @@ -6,7 +6,7 @@ import ( "github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/form" - "github.com/photoprism/photoprism/pkg/constants" + "github.com/photoprism/photoprism/pkg/enum" ) // SelectedPhotos finds photos based on the given selection form, e.g. for adding them to an album. @@ -25,9 +25,9 @@ func SelectedPhotos(frm form.Selection) (results entity.Photos, err error) { var concat string switch DbDialect() { - case constants.MySQL: + case enum.MySQL: concat = "CONCAT(a.path, '/%')" - case constants.SQLite3: + case enum.SQLite3: concat = "a.path || '/%'" default: return results, fmt.Errorf("unknown sql dialect: %s", DbDialect()) diff --git a/internal/entity/query/query.go b/internal/entity/query/query.go index abc080545..ccc72af67 100644 --- a/internal/entity/query/query.go +++ b/internal/entity/query/query.go @@ -29,7 +29,7 @@ import ( "github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/event" - "github.com/photoprism/photoprism/pkg/constants" + "github.com/photoprism/photoprism/pkg/enum" ) var log = event.Log @@ -74,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 constants.SQLite3: + case enum.SQLite3: return 333 default: return 1000 diff --git a/internal/entity/search/conditions_test.go b/internal/entity/search/conditions_test.go index 2335e18a7..3595f3bf1 100644 --- a/internal/entity/search/conditions_test.go +++ b/internal/entity/search/conditions_test.go @@ -8,7 +8,7 @@ import ( "github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/pkg/clean" - "github.com/photoprism/photoprism/pkg/constants" + "github.com/photoprism/photoprism/pkg/enum" "github.com/photoprism/photoprism/pkg/txt" ) @@ -19,7 +19,7 @@ func TestLike(t *testing.T) { t.Run("Special", func(t *testing.T) { s := " ' \" \t \n %_''\\" exp := "'' \" %_''''\\" - if entity.DbDialect() == constants.MySQL { + if entity.DbDialect() == enum.MySQL { exp = "'' \" %_''''\\\\" } result := Like(s) diff --git a/internal/entity/sortby/random.go b/internal/entity/sortby/random.go index 734b4180d..7f47dbebe 100644 --- a/internal/entity/sortby/random.go +++ b/internal/entity/sortby/random.go @@ -5,17 +5,17 @@ import ( _ "github.com/jinzhu/gorm/dialects/mysql" _ "github.com/jinzhu/gorm/dialects/sqlite" - "github.com/photoprism/photoprism/pkg/constants" + "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 constants.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 constants.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()") diff --git a/internal/entity/sortby/random_test.go b/internal/entity/sortby/random_test.go index 2491b583a..d127b3fd1 100644 --- a/internal/entity/sortby/random_test.go +++ b/internal/entity/sortby/random_test.go @@ -5,14 +5,14 @@ import ( "github.com/jinzhu/gorm" - "github.com/photoprism/photoprism/pkg/constants" + "github.com/photoprism/photoprism/pkg/enum" "github.com/stretchr/testify/assert" ) func TestRandomExpr(t *testing.T) { - mysql, _ := gorm.GetDialect(constants.MySQL) - sqlite3, _ := gorm.GetDialect(constants.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)) diff --git a/internal/entity/subject.go b/internal/entity/subject.go index ffcf6e9d1..e306d4a3e 100644 --- a/internal/entity/subject.go +++ b/internal/entity/subject.go @@ -11,7 +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/constants" + "github.com/photoprism/photoprism/pkg/enum" "github.com/photoprism/photoprism/pkg/rnd" "github.com/photoprism/photoprism/pkg/txt" ) @@ -471,7 +471,7 @@ func (m *Subject) RefreshPhotos() error { var err error switch DbDialect() { - case constants.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 diff --git a/internal/photoprism/backup/database.go b/internal/photoprism/backup/database.go index 9989df00f..d11b17569 100644 --- a/internal/photoprism/backup/database.go +++ b/internal/photoprism/backup/database.go @@ -18,7 +18,7 @@ import ( "github.com/photoprism/photoprism/internal/entity" "github.com/photoprism/photoprism/internal/photoprism/get" "github.com/photoprism/photoprism/pkg/clean" - "github.com/photoprism/photoprism/pkg/constants" + "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 constants.MySQL, constants.MariaDB: + case enum.MySQL, enum.MariaDB: // Connect via Unix Domain Socket? if socketName := c.DatabaseServer(); strings.HasPrefix(socketName, "/") { cmd = exec.Command( @@ -112,7 +112,7 @@ func Database(backupPath, fileName string, toStdOut, force bool, retain int) (er c.DatabaseName(), ) } - case constants.SQLite3: + case enum.SQLite3: if !fs.FileExistsNotEmpty(c.DatabaseFile()) { return fmt.Errorf("sqlite database file %s not found", clean.LogQuote(c.DatabaseFile())) } @@ -252,7 +252,7 @@ func RestoreDatabase(backupPath, fileName string, fromStdIn, force bool) (err er var cmd *exec.Cmd switch c.DatabaseDriver() { - case constants.MySQL, constants.MariaDB: + case enum.MySQL, enum.MariaDB: // Connect via Unix Domain Socket? if socketName := c.DatabaseServer(); strings.HasPrefix(socketName, "/") { cmd = exec.Command( @@ -294,7 +294,7 @@ func RestoreDatabase(backupPath, fileName string, fromStdIn, force bool) (err er c.DatabaseName(), ) } - case constants.SQLite3: + case enum.SQLite3: log.Infoln("restore: dropping existing sqlite database tables") tables.Drop(c.Db()) cmd = exec.Command( diff --git a/pkg/clean/sql.go b/pkg/clean/sql.go index caea6fd41..628c39445 100644 --- a/pkg/clean/sql.go +++ b/pkg/clean/sql.go @@ -1,6 +1,6 @@ package clean -import "github.com/photoprism/photoprism/pkg/constants" +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) { @@ -8,7 +8,7 @@ func SqlSpecial(b byte, dialect string) (special bool, omit bool) { return true, true } - if dialect == constants.MySQL { + if dialect == enum.MySQL { switch b { case '\'', '\\': return true, false diff --git a/pkg/clean/sql_test.go b/pkg/clean/sql_test.go index 5a8470519..31534ec52 100644 --- a/pkg/clean/sql_test.go +++ b/pkg/clean/sql_test.go @@ -5,68 +5,68 @@ import ( "github.com/stretchr/testify/assert" - "github.com/photoprism/photoprism/pkg/constants" + "github.com/photoprism/photoprism/pkg/enum" ) func TestSqlSpecial(t *testing.T) { t.Run("Special MySQL", func(t *testing.T) { - if s, o := SqlSpecial(1, constants.MySQL); !s { + 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, constants.MySQL); !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('\\', constants.MySQL); !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('\'', constants.MySQL); !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, constants.SQLite3); !s { + 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(31, constants.SQLite3); !s { + 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('\'', constants.SQLite3); !s { + if s, o := SqlSpecial('\'', enum.SQLite3); !s { t.Error("' is special") } else if o { t.Error("' must not be omitted") } }) t.Run("Special Postgres", func(t *testing.T) { - if s, o := SqlSpecial(1, constants.Postgres); !s { + 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, constants.Postgres); !s { + 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('\'', constants.Postgres); !s { + if s, o := SqlSpecial('\'', enum.Postgres); !s { t.Error("' is special") } else if o { t.Error("' must not be omitted") @@ -74,105 +74,105 @@ func TestSqlSpecial(t *testing.T) { }) t.Run("NotSpecial MySQL", func(t *testing.T) { - if s, o := SqlSpecial(32, constants.MySQL); s { + 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', constants.MySQL); 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', constants.MySQL); 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('_', constants.MySQL); 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('"', constants.MySQL); s { + 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, constants.SQLite3); s { + 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', constants.SQLite3); s { + 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', constants.SQLite3); s { + 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('_', constants.SQLite3); s { + 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('"', constants.SQLite3); s { + 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('\\', constants.SQLite3); s { + 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, constants.Postgres); s { + 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', constants.Postgres); s { + 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', constants.Postgres); s { + 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('_', constants.Postgres); s { + 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('"', constants.Postgres); s { + 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('\\', constants.Postgres); s { + if s, o := SqlSpecial('\\', enum.Postgres); s { t.Error("\\ is not special") } else if o { t.Error("\\ must not be omitted") @@ -182,32 +182,32 @@ func TestSqlSpecial(t *testing.T) { func TestSqlString(t *testing.T) { t.Run("Empty", func(t *testing.T) { - assert.Equal(t, "", SqlString("", constants.MySQL)) - assert.Equal(t, "", SqlString("", constants.SQLite3)) - assert.Equal(t, "", SqlString("", constants.Postgres)) + 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, constants.MySQL) + 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, constants.SQLite3) + 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, constants.Postgres) + 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", constants.MySQL)) + assert.Equal(t, "123ABCabc", SqlString("123ABCabc", enum.MySQL)) }) } diff --git a/pkg/constants/sql.go b/pkg/enum/sql.go similarity index 93% rename from pkg/constants/sql.go rename to pkg/enum/sql.go index e17e27886..8b261bbad 100644 --- a/pkg/constants/sql.go +++ b/pkg/enum/sql.go @@ -1,4 +1,4 @@ -package constants +package enum // Supported database(s) const ( From 7cd937f0342e97f754d246caf73380d3983e248e Mon Sep 17 00:00:00 2001 From: Keith Martin Date: Wed, 8 Oct 2025 17:26:27 +1000 Subject: [PATCH 4/8] Enum: utilise enum package --- internal/commands/cluster_register.go | 3 +- internal/config/config_thumb_test.go | 5 ++-- internal/config/flags.go | 5 ++-- internal/config/test.go | 4 +-- internal/service/cluster/node/bootstrap.go | 3 +- .../service/cluster/node/bootstrap_test.go | 9 +++--- .../cluster/provisioner/credentials.go | 5 ++-- .../service/cluster/provisioner/naming.go | 3 +- .../cluster/provisioner/naming_test.go | 3 +- pkg/enum/enum.go | 30 +++++++++++++++++++ 10 files changed, 54 insertions(+), 16 deletions(-) create mode 100644 pkg/enum/enum.go diff --git a/internal/commands/cluster_register.go b/internal/commands/cluster_register.go index 2722b6091..699050a8b 100644 --- a/internal/commands/cluster_register.go +++ b/internal/commands/cluster_register.go @@ -19,6 +19,7 @@ import ( "github.com/photoprism/photoprism/internal/config" "github.com/photoprism/photoprism/internal/service/cluster" "github.com/photoprism/photoprism/pkg/clean" + "github.com/photoprism/photoprism/pkg/enum" "github.com/photoprism/photoprism/pkg/fs" "github.com/photoprism/photoprism/pkg/rnd" "github.com/photoprism/photoprism/pkg/service/http/header" @@ -359,7 +360,7 @@ func persistRegisterResponse(conf *config.Config, resp *cluster.RegisterResponse // DB settings (MySQL/MariaDB only) if resp.Database.Name != "" && resp.Database.User != "" { - updates["DatabaseDriver"] = config.MySQL + updates["DatabaseDriver"] = enum.MySQL updates["DatabaseName"] = resp.Database.Name updates["DatabaseServer"] = fmt.Sprintf("%s:%d", resp.Database.Host, resp.Database.Port) updates["DatabaseUser"] = resp.Database.User diff --git a/internal/config/config_thumb_test.go b/internal/config/config_thumb_test.go index 0db6bf2f7..d894e575d 100644 --- a/internal/config/config_thumb_test.go +++ b/internal/config/config_thumb_test.go @@ -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()) diff --git a/internal/config/flags.go b/internal/config/flags.go index c264a354a..4694f9401 100644 --- a/internal/config/flags.go +++ b/internal/config/flags.go @@ -13,6 +13,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/i18n" "github.com/photoprism/photoprism/pkg/media" @@ -915,7 +916,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, }}, { @@ -1074,7 +1075,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{ diff --git a/internal/config/test.go b/internal/config/test.go index b557cd0e3..1c26d4cb7 100644 --- a/internal/config/test.go +++ b/internal/config/test.go @@ -241,7 +241,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 { @@ -261,7 +261,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() diff --git a/internal/service/cluster/node/bootstrap.go b/internal/service/cluster/node/bootstrap.go index ae55d740b..662a01b6c 100644 --- a/internal/service/cluster/node/bootstrap.go +++ b/internal/service/cluster/node/bootstrap.go @@ -23,6 +23,7 @@ import ( "github.com/photoprism/photoprism/internal/event" "github.com/photoprism/photoprism/internal/service/cluster" "github.com/photoprism/photoprism/pkg/clean" + "github.com/photoprism/photoprism/pkg/enum" "github.com/photoprism/photoprism/pkg/fs" "github.com/photoprism/photoprism/pkg/rnd" ) @@ -119,7 +120,7 @@ func registerWithPortal(c *config.Config, portal *url.URL, token string) error { // and no DSN/fields are set (raw options) and no password is provided via file. opts := c.Options() driver := c.DatabaseDriver() - wantRotateDatabase := (driver == config.MySQL || driver == config.MariaDB) && + wantRotateDatabase := (driver == enum.MySQL || driver == enum.MariaDB) && opts.DatabaseDSN == "" && opts.DatabaseName == "" && opts.DatabaseUser == "" && opts.DatabasePassword == "" && c.DatabasePassword() == "" diff --git a/internal/service/cluster/node/bootstrap_test.go b/internal/service/cluster/node/bootstrap_test.go index d19cabbbc..f8b2cdf63 100644 --- a/internal/service/cluster/node/bootstrap_test.go +++ b/internal/service/cluster/node/bootstrap_test.go @@ -14,6 +14,7 @@ import ( "github.com/photoprism/photoprism/internal/config" "github.com/photoprism/photoprism/internal/service/cluster" + "github.com/photoprism/photoprism/pkg/enum" "github.com/photoprism/photoprism/pkg/fs" "github.com/photoprism/photoprism/pkg/rnd" ) @@ -52,7 +53,7 @@ func TestRegister_PersistSecretAndDB(t *testing.T) { Secrets: &cluster.RegisterSecrets{ClientSecret: cluster.ExampleClientSecret}, JWKSUrl: jwksURL, Database: cluster.RegisterDatabase{ - Driver: config.MySQL, + Driver: enum.MySQL, Host: "db.local", Port: 3306, Name: "pp_db", @@ -79,7 +80,7 @@ func TestRegister_PersistSecretAndDB(t *testing.T) { c.Options().PortalUrl = srv.URL c.Options().JoinToken = cluster.ExampleJoinToken // Gate rotate=true: driver mysql and no DSN/fields. - c.Options().DatabaseDriver = config.MySQL + c.Options().DatabaseDriver = enum.MySQL c.Options().DatabaseDSN = "" c.Options().DatabaseName = "" c.Options().DatabaseUser = "" @@ -92,7 +93,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, config.MySQL, 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()) } @@ -189,7 +190,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()) diff --git a/internal/service/cluster/provisioner/credentials.go b/internal/service/cluster/provisioner/credentials.go index 391ef0964..fa6e2f513 100644 --- a/internal/service/cluster/provisioner/credentials.go +++ b/internal/service/cluster/provisioner/credentials.go @@ -8,6 +8,7 @@ import ( "time" "github.com/photoprism/photoprism/internal/config" + "github.com/photoprism/photoprism/pkg/enum" ) // Credentials contains the connection details returned when ensuring a node database. @@ -34,9 +35,9 @@ func GetCredentials(ctx context.Context, conf *config.Config, nodeUUID, nodeName driver := strings.ToLower(DatabaseDriver) switch driver { - case config.MySQL, config.MariaDB: + case enum.MySQL, enum.MariaDB: // ok - case config.SQLite3, config.Postgres: + 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). diff --git a/internal/service/cluster/provisioner/naming.go b/internal/service/cluster/provisioner/naming.go index f8ae3a774..843747aa9 100644 --- a/internal/service/cluster/provisioner/naming.go +++ b/internal/service/cluster/provisioner/naming.go @@ -8,6 +8,7 @@ import ( "strings" "github.com/photoprism/photoprism/internal/config" + "github.com/photoprism/photoprism/pkg/enum" "github.com/photoprism/photoprism/pkg/rnd" ) @@ -47,7 +48,7 @@ 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 config.MySQL, config.MariaDB: + case enum.MySQL, enum.MariaDB: return fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?charset=utf8mb4&collation=utf8mb4_unicode_ci&parseTime=true", user, pass, host, port, name, ) diff --git a/internal/service/cluster/provisioner/naming_test.go b/internal/service/cluster/provisioner/naming_test.go index 9c83907b5..7c1e79715 100644 --- a/internal/service/cluster/provisioner/naming_test.go +++ b/internal/service/cluster/provisioner/naming_test.go @@ -8,6 +8,7 @@ import ( "github.com/stretchr/testify/assert" "github.com/photoprism/photoprism/internal/config" + "github.com/photoprism/photoprism/pkg/enum" ) func TestGenerateCredentials_StabilityAndBudgets(t *testing.T) { @@ -78,7 +79,7 @@ func TestGetCredentials_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 := GetCredentials(ctx, c, "11111111-1111-4111-8111-111111111111", "pp-node-01", false) diff --git a/pkg/enum/enum.go b/pkg/enum/enum.go new file mode 100644 index 000000000..d06fa76b1 --- /dev/null +++ b/pkg/enum/enum.go @@ -0,0 +1,30 @@ +/* +Package enum provides enumerated values. + +Copyright (c) 2018 - 2025 PhotoPrism UG. All rights reserved. + + This program is free software: you can redistribute it and/or modify + it under Version 3 of the GNU Affero General Public License (the "AGPL"): + + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU Affero General Public License for more details. + + The AGPL is supplemented by our Trademark and Brand Guidelines, + which describe how our Brand Assets may be used: + + +Feel free to send an email to hello@photoprism.app if you have questions, +want to support our work, or just want to say hello. + +Additional information can be found in our Developer Guide: + +*/ +package enum + +// Automatically choose the appropriate "thing" +const ( + Auto = "auto" +) From 2197deeb252a7193a8b743c30923944ca7ea9794 Mon Sep 17 00:00:00 2001 From: Keith Martin Date: Sat, 11 Oct 2025 20:05:51 +1000 Subject: [PATCH 5/8] Query: address movement of enums --- internal/entity/query/covers.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/entity/query/covers.go b/internal/entity/query/covers.go index fcbcaad57..210d7a11c 100644 --- a/internal/entity/query/covers.go +++ b/internal/entity/query/covers.go @@ -323,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 @@ -339,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(`( @@ -367,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 @@ -384,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(`( From 1c78e038fffb907aae53659ff76a6c4336d3f503 Mon Sep 17 00:00:00 2001 From: Keith Martin Date: Thu, 6 Nov 2025 17:16:50 +1000 Subject: [PATCH 6/8] Config: Apply enum package use for DBMS --- internal/config/config_db.go | 24 +++++++++--------------- internal/config/config_db_test.go | 22 +++++++++++----------- internal/config/report_test.go | 12 +++++++----- internal/config/test.go | 5 ++--- 4 files changed, 29 insertions(+), 34 deletions(-) diff --git a/internal/config/config_db.go b/internal/config/config_db.go index f6d6f964e..d328ef454 100644 --- a/internal/config/config_db.go +++ b/internal/config/config_db.go @@ -26,12 +26,6 @@ import ( "github.com/photoprism/photoprism/pkg/txt" ) -// SQLite default DSNs. -const ( - SQLiteTestDB = ".test.db" - SQLiteMemoryDSN = ":memory:" -) - // DatabaseDriver returns the database driver name. func (c *Config) DatabaseDriver() string { c.normalizeDatabaseDSN() @@ -129,7 +123,7 @@ func (c *Config) DatabaseDSN() string { c.DatabasePassword(), databaseServer, c.DatabaseName(), - dsn.Params[dsn.DriverMySQL], + dsn.Params[enum.MySQL], c.DatabaseTimeout(), ) case enum.Postgres: @@ -141,10 +135,10 @@ func (c *Config) DatabaseDSN() string { c.DatabaseHost(), c.DatabasePort(), c.DatabaseTimeout(), - dsn.Params[dsn.DriverPostgres], + dsn.Params[enum.Postgres], ) case enum.SQLite3: - return filepath.Join(c.StoragePath(), fmt.Sprintf("index.db?%s", dsn.Params[dsn.DriverSQLite3])) + return filepath.Join(c.StoragePath(), fmt.Sprintf("index.db?%s", dsn.Params[enum.SQLite3])) default: log.Errorf("config: empty database dsn") return "" @@ -152,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()) } @@ -178,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 } @@ -189,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 } @@ -236,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 } @@ -355,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 } diff --git a/internal/config/config_db_test.go b/internal/config/config_db_test.go index 0793f5fad..284b6163f 100644 --- a/internal/config/config_db_test.go +++ b/internal/config/config_db_test.go @@ -67,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()) @@ -112,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" @@ -131,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" @@ -224,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 = "" @@ -265,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" @@ -281,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" @@ -307,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()) @@ -321,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()) diff --git a/internal/config/report_test.go b/internal/config/report_test.go index d02a55697..f1286cdab 100644 --- a/internal/config/report_test.go +++ b/internal/config/report_test.go @@ -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) diff --git a/internal/config/test.go b/internal/config/test.go index 08b2a6f9d..6edad39ce 100644 --- a/internal/config/test.go +++ b/internal/config/test.go @@ -23,7 +23,6 @@ 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" @@ -139,8 +138,8 @@ func NewTestOptionsForPath(dbName, dataPath string) *Options { } 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 { From ba49070bc9cc59bd85953044b66079b513d72ada Mon Sep 17 00:00:00 2001 From: Keith Martin Date: Thu, 6 Nov 2025 17:17:46 +1000 Subject: [PATCH 7/8] Dsn: Apply enum for DBMS --- pkg/dsn/driver.go | 22 +++++----------------- pkg/dsn/dsn.go | 30 ++++++++++++++++-------------- pkg/dsn/dsn_test.go | 8 +++++--- pkg/dsn/parse_test.go | 14 ++++++++------ 4 files changed, 34 insertions(+), 40 deletions(-) diff --git a/pkg/dsn/driver.go b/pkg/dsn/driver.go index 70c0bedac..8c4f20540 100644 --- a/pkg/dsn/driver.go +++ b/pkg/dsn/driver.go @@ -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", } diff --git a/pkg/dsn/dsn.go b/pkg/dsn/dsn.go index 1a2731d30..740105d99 100644 --- a/pkg/dsn/dsn.go +++ b/pkg/dsn/dsn.go @@ -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 "" } @@ -118,16 +120,16 @@ func (d *DSN) Host() string { // Port the database server port. func (d *DSN) Port() int { switch d.Driver { - case DriverSQLite3: + 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 } @@ -250,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 @@ -372,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 } @@ -390,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 } } diff --git a/pkg/dsn/dsn_test.go b/pkg/dsn/dsn_test.go index 52af01054..ae8fa0af6 100644 --- a/pkg/dsn/dsn_test.go +++ b/pkg/dsn/dsn_test.go @@ -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", diff --git a/pkg/dsn/parse_test.go b/pkg/dsn/parse_test.go index 26b28feef..fd634d57f 100644 --- a/pkg/dsn/parse_test.go +++ b/pkg/dsn/parse_test.go @@ -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", From 862207c185917c8069d8ac6d8ede886333f25a23 Mon Sep 17 00:00:00 2001 From: Keith Martin Date: Sun, 23 Nov 2025 10:46:41 +1000 Subject: [PATCH 8/8] Clean: Rename Sql to SQL as per linter --- internal/entity/search/conditions.go | 2 +- pkg/clean/sql.go | 12 ++--- pkg/clean/sql_test.go | 70 ++++++++++++++-------------- 3 files changed, 42 insertions(+), 42 deletions(-) diff --git a/internal/entity/search/conditions.go b/internal/entity/search/conditions.go index 81f6840ea..7530bfa9e 100644 --- a/internal/entity/search/conditions.go +++ b/internal/entity/search/conditions.go @@ -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, Db().Dialect().GetName()), " |&*%") + return strings.Trim(clean.SQLString(s, Db().Dialect().GetName()), " |&*%") } // LikeAny builds OR-chained LIKE predicates for a text column. The input string diff --git a/pkg/clean/sql.go b/pkg/clean/sql.go index 628c39445..1a88f3fe2 100644 --- a/pkg/clean/sql.go +++ b/pkg/clean/sql.go @@ -2,8 +2,8 @@ package clean 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) { +// 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 } @@ -25,11 +25,11 @@ func SqlSpecial(b byte, dialect string) (special bool, omit bool) { } } -// SqlString escapes a string for use in an SQL query. -func SqlString(s string, dialect 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], dialect); found { + if found, _ := SQLSpecial(s[i], dialect); found { break } } @@ -46,7 +46,7 @@ func SqlString(s string, dialect string) string { j := i for ; i < len(s); i++ { - if special, omit := SqlSpecial(s[i], dialect); omit { + if special, omit := SQLSpecial(s[i], dialect); omit { // Omit control characters. continue } else if special { diff --git a/pkg/clean/sql_test.go b/pkg/clean/sql_test.go index 31534ec52..dbbb8ff5e 100644 --- a/pkg/clean/sql_test.go +++ b/pkg/clean/sql_test.go @@ -8,65 +8,65 @@ import ( "github.com/photoprism/photoprism/pkg/enum" ) -func TestSqlSpecial(t *testing.T) { +func TestSQLSpecial(t *testing.T) { t.Run("Special MySQL", func(t *testing.T) { - if s, o := SqlSpecial(1, enum.MySQL); !s { + 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, enum.MySQL); !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('\\', enum.MySQL); !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('\'', enum.MySQL); !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 { + 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(31, enum.SQLite3); !s { + 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 { + if s, o := SQLSpecial('\'', enum.SQLite3); !s { t.Error("' is special") } else if o { t.Error("' must not be omitted") } }) t.Run("Special Postgres", func(t *testing.T) { - if s, o := SqlSpecial(1, enum.Postgres); !s { + 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 { + 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 { + if s, o := SQLSpecial('\'', enum.Postgres); !s { t.Error("' is special") } else if o { t.Error("' must not be omitted") @@ -74,105 +74,105 @@ func TestSqlSpecial(t *testing.T) { }) t.Run("NotSpecial MySQL", func(t *testing.T) { - if s, o := SqlSpecial(32, enum.MySQL); s { + 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', enum.MySQL); 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', enum.MySQL); 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('_', enum.MySQL); 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + 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 { + if s, o := SQLSpecial('\\', enum.Postgres); s { t.Error("\\ is not special") } else if o { t.Error("\\ must not be omitted") @@ -182,32 +182,32 @@ func TestSqlSpecial(t *testing.T) { func TestSqlString(t *testing.T) { t.Run("Empty", func(t *testing.T) { - assert.Equal(t, "", SqlString("", enum.MySQL)) - assert.Equal(t, "", SqlString("", enum.SQLite3)) - assert.Equal(t, "", SqlString("", enum.Postgres)) + 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, enum.MySQL) + 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) + 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) + 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", enum.MySQL)) + assert.Equal(t, "123ABCabc", SQLString("123ABCabc", enum.MySQL)) }) }