Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9be8731dc8 |
@@ -209,10 +209,12 @@ All three subcommands operate on a single SQLite database file:
|
|||||||
`-shm` files beside it, which SQLite creates with the database
|
`-shm` files beside it, which SQLite creates with the database
|
||||||
file's permissions.
|
file's permissions.
|
||||||
- Schema (`PRAGMA user_version` is the schema version, currently 1; a
|
- Schema (`PRAGMA user_version` is the schema version, currently 1; a
|
||||||
database with any other version is a fatal error. A database at
|
database with any other version is a fatal error. `scan` creates
|
||||||
version 0 that already has a `files` table was not made by sfdupes;
|
the schema and sets the version in one transaction, so a first scan
|
||||||
every subcommand refuses it with an error telling the user to remove
|
stopped while doing so leaves an empty database the next scan sets
|
||||||
the file and rescan):
|
up. A database at version 0 that already has a `files` table was
|
||||||
|
therefore not made by sfdupes; every subcommand refuses it with an
|
||||||
|
error telling the user to remove the file and rescan):
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
CREATE TABLE files (
|
CREATE TABLE files (
|
||||||
|
|||||||
@@ -29,8 +29,8 @@
|
|||||||
|
|
||||||
# Completed Steps
|
# Completed Steps
|
||||||
|
|
||||||
- refuse an unversioned database that already has a `files` table with
|
- `scan` creates the schema in one transaction; a version-0 database with a
|
||||||
a clear schema-version error (2026-10-04,
|
`files` table is refused with a clear schema-version error (2026-10-04,
|
||||||
https://git.eeqj.de/sneak/sfdupes/issues/11)
|
https://git.eeqj.de/sneak/sfdupes/issues/11)
|
||||||
|
|
||||||
- the `Dockerfile` build stage keeps the Go module cache out of `builder`'s
|
- the `Dockerfile` build stage keeps the Go module cache out of `builder`'s
|
||||||
|
|||||||
@@ -266,10 +266,10 @@ func initSchema(ctx context.Context, db *sql.DB) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// checkUnversioned checks a database at user_version 0 before it is
|
// checkUnversioned checks a database at user_version 0 before it is
|
||||||
// taken for an empty one. A database that already has a files table
|
// taken for an empty one. createSchema creates the files table and
|
||||||
// was not created by this build: a foreign or partially initialized
|
// sets the version together, so a files table at version 0 was made by
|
||||||
// file. Adopting it could corrupt unrelated data, so that is a
|
// something else. Adopting it could corrupt unrelated data, so that is
|
||||||
// schema-version error telling the operator to remove the file and
|
// a schema-version error telling the operator to remove the file and
|
||||||
// rescan.
|
// rescan.
|
||||||
func checkUnversioned(ctx context.Context, db *sql.DB) error {
|
func checkUnversioned(ctx context.Context, db *sql.DB) error {
|
||||||
var name string
|
var name string
|
||||||
@@ -291,24 +291,39 @@ func checkUnversioned(ctx context.Context, db *sql.DB) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// createSchema applies the schema to a fresh database and stamps the
|
// createSchema applies the schema to a fresh database and stamps the
|
||||||
// schema version.
|
// schema version in one transaction, so a creation stopped partway, by
|
||||||
|
// an interrupt or an error, leaves an empty database the next scan
|
||||||
|
// sets up, never a files table at version 0, which checkUnversioned
|
||||||
|
// refuses.
|
||||||
func createSchema(ctx context.Context, db *sql.DB) error {
|
func createSchema(ctx context.Context, db *sql.DB) error {
|
||||||
_, err := db.ExecContext(ctx, createTableSQL)
|
tx, err := db.BeginTx(ctx, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("create schema: %w", err)
|
return fmt.Errorf("create schema: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = db.ExecContext(ctx, createIndexSQL)
|
defer func() { _ = tx.Rollback() }()
|
||||||
|
|
||||||
|
_, err = tx.ExecContext(ctx, createTableSQL)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("create schema: %w", err)
|
return fmt.Errorf("create schema: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = db.ExecContext(ctx,
|
_, err = tx.ExecContext(ctx, createIndexSQL)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("create schema: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = tx.ExecContext(ctx,
|
||||||
"PRAGMA user_version = "+strconv.Itoa(schemaVersion))
|
"PRAGMA user_version = "+strconv.Itoa(schemaVersion))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("set schema version: %w", err)
|
return fmt.Errorf("set schema version: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = tx.Commit()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("create schema: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+34
@@ -114,6 +114,40 @@ func TestOpenDatabaseUnversionedForeign(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSchemaCreationStoppedPartway(t *testing.T) {
|
||||||
|
t.Parallel()
|
||||||
|
|
||||||
|
// A first scan stopped while creating the schema must leave a
|
||||||
|
// database the next scan accepts. max_page_count(2) leaves room for
|
||||||
|
// the files table but not its index, so schema creation fails right
|
||||||
|
// after CREATE TABLE, a point an interrupt could also stop it at.
|
||||||
|
path := testDBPath(t)
|
||||||
|
|
||||||
|
db, err := openDB(path, scanParams+"&_pragma=max_page_count(2)")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = initSchema(t.Context(), db)
|
||||||
|
_ = db.Close()
|
||||||
|
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("initSchema with no room for the index succeeded")
|
||||||
|
}
|
||||||
|
|
||||||
|
db, err = openScanDatabase(t.Context(), path)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("next scan: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
defer func() { _ = db.Close() }()
|
||||||
|
|
||||||
|
v, err := userVersion(t.Context(), db)
|
||||||
|
if err != nil || v != schemaVersion {
|
||||||
|
t.Fatalf("userVersion = %d, %v; want %d, nil", v, err, schemaVersion)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestOpenReportDatabaseMissing(t *testing.T) {
|
func TestOpenReportDatabaseMissing(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user