Commit 9628d3de authored by Johnny's avatar Johnny

fix: detect legacy installations with empty schema version

parent 7c1defba
...@@ -84,14 +84,10 @@ func shouldApplyMigration(fileVersion, currentDBVersion, targetVersion string) b ...@@ -84,14 +84,10 @@ func shouldApplyMigration(fileVersion, currentDBVersion, targetVersion string) b
// validateMigrationFileName checks if a migration file follows the expected naming convention. // validateMigrationFileName checks if a migration file follows the expected naming convention.
// Expected format: "NN__description.sql" where NN is a zero-padded number. // Expected format: "NN__description.sql" where NN is a zero-padded number.
func validateMigrationFileName(filename string) error { func validateMigrationFileName(filename string) error {
if !strings.Contains(filename, MigrateFileNameSplit) { parts := strings.SplitN(filename, MigrateFileNameSplit, 2)
return errors.Errorf("invalid migration filename format (missing %s): %s", MigrateFileNameSplit, filename)
}
parts := strings.Split(filename, MigrateFileNameSplit)
if len(parts) < 2 { if len(parts) < 2 {
return errors.Errorf("invalid migration filename format: %s", filename) return errors.Errorf("invalid migration filename format (missing %s): %s", MigrateFileNameSplit, filename)
} }
// Check if first part is a number
if _, err := strconv.Atoi(parts[0]); err != nil { if _, err := strconv.Atoi(parts[0]); err != nil {
return errors.Errorf("migration filename must start with a number: %s", filename) return errors.Errorf("migration filename must start with a number: %s", filename)
} }
...@@ -122,7 +118,7 @@ func (s *Store) Migrate(ctx context.Context) error { ...@@ -122,7 +118,7 @@ func (s *Store) Migrate(ctx context.Context) error {
) )
return errors.Errorf("cannot downgrade schema version from %s to %s", instanceBasicSetting.SchemaVersion, currentSchemaVersion) return errors.Errorf("cannot downgrade schema version from %s to %s", instanceBasicSetting.SchemaVersion, currentSchemaVersion)
} }
// Apply migrations if needed (including when schema version is empty) // Apply migrations if needed.
if isVersionEmpty(instanceBasicSetting.SchemaVersion) || version.IsVersionGreaterThan(currentSchemaVersion, instanceBasicSetting.SchemaVersion) { if isVersionEmpty(instanceBasicSetting.SchemaVersion) || version.IsVersionGreaterThan(currentSchemaVersion, instanceBasicSetting.SchemaVersion) {
if err := s.applyMigrations(ctx, instanceBasicSetting.SchemaVersion, currentSchemaVersion); err != nil { if err := s.applyMigrations(ctx, instanceBasicSetting.SchemaVersion, currentSchemaVersion); err != nil {
return errors.Wrap(err, "failed to apply migrations") return errors.Wrap(err, "failed to apply migrations")
...@@ -254,6 +250,7 @@ func (s *Store) preMigrate(ctx context.Context) error { ...@@ -254,6 +250,7 @@ func (s *Store) preMigrate(ctx context.Context) error {
} }
return nil return nil
} }
func (s *Store) getMigrationBasePath() string { func (s *Store) getMigrationBasePath() string {
return fmt.Sprintf("migration/%s/", s.profile.Driver) return fmt.Sprintf("migration/%s/", s.profile.Driver)
} }
...@@ -370,36 +367,39 @@ func (s *Store) checkMinimumUpgradeVersion(ctx context.Context) error { ...@@ -370,36 +367,39 @@ func (s *Store) checkMinimumUpgradeVersion(ctx context.Context) error {
if err != nil { if err != nil {
return errors.Wrap(err, "failed to get instance basic setting") return errors.Wrap(err, "failed to get instance basic setting")
} }
schemaVersion := instanceBasicSetting.SchemaVersion schemaVersion := instanceBasicSetting.SchemaVersion
// If schema version is >= 0.22.0, the installation is up-to-date // Modern installation: nothing to check.
if !isVersionEmpty(schemaVersion) && version.IsVersionGreaterOrEqualThan(schemaVersion, "0.22.0") { if !isVersionEmpty(schemaVersion) && version.IsVersionGreaterOrEqualThan(schemaVersion, "0.22.0") {
return nil return nil
} }
// If schema version is set but < 0.22.0, this is an old installation // Schema version is empty for fresh installs too, but preMigrate sets it before we get here.
if !isVersionEmpty(schemaVersion) && !version.IsVersionGreaterOrEqualThan(schemaVersion, "0.22.0") { // So empty schema version on an initialized DB means a pre-v0.22 legacy installation.
currentVersion, _ := s.GetCurrentSchemaVersion() if isVersionEmpty(schemaVersion) {
initialized, err := s.driver.IsInitialized(ctx)
return errors.Errorf( if err != nil {
"Your Memos installation is too old to upgrade directly.\n\n"+ return errors.Wrap(err, "failed to check if database is initialized")
"Your current version: %s\n"+ }
"Target version: %s\n"+ if !initialized {
"Minimum required: v0.22.0 (May 2024)\n\n"+ return nil
"Upgrade path:\n"+ }
"1. First upgrade to v0.25.3: https://github.com/usememos/memos/releases/tag/v0.25.3\n"+
"2. Start the server and verify it works\n"+
"3. Then upgrade to the latest version\n\n"+
"This is required because schema version tracking was moved from migration_history\n"+
"to system_setting in v0.22.0. The intermediate upgrade handles this migration safely.",
schemaVersion,
currentVersion,
)
} }
// Schema version is empty - this is either a fresh install or corrupted installation // schemaVersion is either set but < 0.22.0, or empty on an initialized (legacy) DB.
// Fresh installs will have schema version set immediately after LATEST.sql is applied currentVersion, _ := s.GetCurrentSchemaVersion()
// So this should not be an issue in normal operation return errors.Errorf(
return nil "Your Memos installation is too old to upgrade directly.\n\n"+
"Your current version: %s\n"+
"Target version: %s\n"+
"Minimum required: v0.22.0 (May 2024)\n\n"+
"Upgrade path:\n"+
"1. First upgrade to v0.25.3: https://github.com/usememos/memos/releases/tag/v0.25.3\n"+
"2. Start the server and verify it works\n"+
"3. Then upgrade to the latest version\n\n"+
"This is required because schema version tracking was moved from migration_history\n"+
"to system_setting in v0.22.0. The intermediate upgrade handles this migration safely.",
schemaVersion,
currentVersion,
)
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment