|
| 1 | +--- |
| 2 | +'@objectstack/driver-sql': minor |
| 3 | +'@objectstack/service-datasource': minor |
| 4 | +--- |
| 5 | + |
| 6 | +fix(driver-sql): a SQLite database whose header SQLite cannot read fails `connect()` at once, naming the file, and a dangling symbolic link as the database's directory is refused on the boot that writes, as the read-only boot already refused it (#25019) |
| 7 | + |
| 8 | +Three more SQLite files a declared datasource cannot use, each now classified by the file's own driver at connect: |
| 9 | + |
| 10 | +- **A database whose header or schema SQLite cannot read** (a file truncated inside its 100-byte header, or short of the pages its schema names). The connection's first statement, `PRAGMA auto_vacuum`, answers `SQLITE_CORRUPT`; `SqlDriver.connect()` logged it as "Failed to set PRAGMA auto_vacuum=INCREMENTAL" and resolved over a file no statement could read. On a development boot, `resolveSqliteDriver` (`@objectstack/service-datasource`) then stepped down to wasm SQLite, whose open renamed the file to `<file>.corrupt-<timestamp>` and wrote an empty database in its place, under a warning that blamed the native addon. `connect()` now refuses it with a `SqliteDatabaseFileError` (`code: 'SQLITE_CORRUPT'`, `filename`, a message naming the file) in every mode, before anything is written, and `resolveSqliteDriver` re-throws it instead of stepping down. A database damaged deeper inside still connects: its first statement answers, and the damage surfaces on the read that reaches it, as before. |
| 11 | +- **A dangling symbolic link as the database's directory, on the boot that writes.** `connect()` took `mkdir`'s `EEXIST` on the link for a concurrent creator and resolved; the first statement then failed with a code-less "directory does not exist", and a development boot stepped down to wasm SQLite under the same false native-addon warning. `connect()` now refuses it with `SqliteDatabaseFileError` (`code: 'ENOENT'`), naming the file and the link, which is the error the read-only boot (`sqliteAbsentFile: 'empty-in-memory'`) gives for the same path. |
| 12 | +- **A dangling symbolic link as the database file itself, on the read-only boot.** When the directory its chain of links ends in does not exist, the read-only boot read the file absent and reported the datasource connected, while the boot that writes refuses the same path with `SQLITE_CANTOPEN`. The read-only boot now refuses it too (`code: 'ENOENT'`), naming the link's target. When that directory exists, both boots behave as before: the read-only boot opens an empty in-memory database, and the boot that writes creates the file through the link. |
| 13 | + |
| 14 | +A real database file, an absent file in a directory that exists or can be made, and a native addon that cannot load over a real database keep today's behaviour. |
| 15 | + |
| 16 | +Clause-②: no (narrowing) |
| 17 | + |
| 18 | +<!-- adr-0087: not-required (no-migration-prescription) No metadata moves: no spec key, authorable spelling, object definition or stored shape is added, removed, renamed or re-shaped, and no exported declaration is removed or narrowed (`SqliteDatabaseFileError` keeps its shape; only the set of codes and paths it is raised for grows), so there is nothing for `objectstack migrate meta` to rewrite. What changes is the answer `SqlDriver.connect()` and the read-only judgement give for three SQLite files a datasource cannot use: a rejection where each resolved. The remedy is the datasource's file or its path, never a rewrite of a package source or a stored row. The other categories are closed on facts: both packages publish (not unpublished); no ADR-0087 id is named or touched (not registered or already-registered); and nothing exported is removed or narrowed (not runtime-interface-only, type-surface-only or runtime-export-retired). --> |
| 19 | + |
| 20 | +**BREAKING** (`SqlDriver.connect()`'s accept set and the read-only boot's narrow): a connect that resolved over a SQLite database whose header SQLite cannot read, or over a path whose directory is a dangling symbolic link, now rejects; a read-only boot that read a dangling file link absent although its target's directory does not exist now rejects. It ships as `minor` under the launch-window convention for accept-set narrowings: during the launch window a breaking change does not take `major`, so the level does not say it breaks, and breaking-ness is carried by this banner and the disposition above. |
| 21 | + |
| 22 | +- **Before:** `connect()` resolved; in production the first query failed with `DATABASE_ERROR`, and on a development boot a damaged file was renamed to `<file>.corrupt-<timestamp>` and replaced with an empty database, and a dangling-link directory stepped down to wasm SQLite under a warning that blamed the native addon. The read-only boot reported a dangling file link connected. |
| 23 | +- **Now:** `connect()` rejects with `SqliteDatabaseFileError`, `code: 'SQLITE_CORRUPT'` for the damaged file and `code: 'ENOENT'` for the dangling-link directory, naming the file (and the link); the read-only boot rejects the dangling file link with `code: 'ENOENT'`, naming its target. The file is untouched. |
| 24 | +- **Fix:** restore the database from a backup, or move the damaged file aside yourself (an absent file is created empty on the next connect); point the dangling link at a directory that exists, or point the datasource at a real path. |
0 commit comments