Refuse an unversioned database that already has a files table (closes #11)
check / check (push) Failing after 2s

A database at user_version 0 that already has a files table was not
created by sfdupes: a foreign or partially initialized file. scan used
to run its CREATE TABLE on it and fail with a raw SQLite error, and
report and trees gave only a bare version mismatch. All three now
check for a files table at version 0 and refuse such a database with
the schema-version error telling the operator to remove the file and
rescan. A genuinely empty database is unchanged: scan creates the
schema, report and trees refuse it as before.

Model: opus-4-8 (implementation); opus-5-5 (rebase)
This commit is contained in:
2026-10-04 08:02:43 +00:00
parent 2eeba3df6f
commit f2e71e4f41
4 changed files with 81 additions and 1 deletions
+36
View File
@@ -219,6 +219,12 @@ func openReportDatabase(ctx context.Context,
}
v, err := userVersion(ctx, db)
if err == nil && v == 0 {
// An empty database passes this check and fails the version
// check below.
err = checkUnversioned(ctx, db)
}
if err != nil {
_ = db.Close()
@@ -245,6 +251,11 @@ func initSchema(ctx context.Context, db *sql.DB) error {
switch v {
case 0:
err = checkUnversioned(ctx, db)
if err != nil {
return err
}
return createSchema(ctx, db)
case schemaVersion:
return nil
@@ -254,6 +265,31 @@ func initSchema(ctx context.Context, db *sql.DB) error {
}
}
// checkUnversioned checks a database at user_version 0 before it is
// taken for an empty one. A database that already has a files table
// was not created by this build: a foreign or partially initialized
// file. Adopting it could corrupt unrelated data, so that is a
// schema-version error telling the operator to remove the file and
// rescan.
func checkUnversioned(ctx context.Context, db *sql.DB) error {
var name string
err := db.QueryRowContext(ctx,
"SELECT name FROM sqlite_master "+
"WHERE type = 'table' AND name = 'files'").Scan(&name)
switch {
case err == nil:
return fmt.Errorf(
"has a files table but no schema version; "+
"remove the file and rescan: %w", errSchemaVersion)
case errors.Is(err, sql.ErrNoRows):
return nil
default:
return fmt.Errorf("check for files table: %w", err)
}
}
// createSchema applies the schema to a fresh database and stamps the
// schema version.
func createSchema(ctx context.Context, db *sql.DB) error {