Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion docs/SUMMARY.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ generator, in this case, **mdbook**. It defines the structure and navigation of
- [User Flows](architecture/user-flows.md)
- [Settings](architecture/settings.md)
- [Feature Flags](architecture/feature-flags.md)
- [Database](architecture/database.md)
- [Legacy Module Integration](architecture/legacy-module-integration.md)
- [Engineering](engineering/README.md)
- [Delivery Planning](engineering/delivery-planning.md)
Expand All @@ -39,14 +40,14 @@ generator, in this case, **mdbook**. It defines the structure and navigation of
- [RFCs](engineering/rfcs/README.md)
- [Template](engineering/rfcs/0000-rfc-template.md)
- [Proposed]()
- [0007 - Global Database](engineering/rfcs/0007-global-database.md)
- [Accepted]()
- [0001 - Changelog System Replacement](engineering/rfcs/0001-changelog-system-replacement.md)
- [0002 - Thundermail – Add an Account interim screen](engineering/rfcs/0002-thundermail-add-an-account-interim-screen.md)
- [0003 - Render inline images in plain-text messages](engineering/rfcs/0003-render-inline-images-in-plain-text-messages.md)
- [0004 - Add a Declarative Feature Flag Catalog](engineering/rfcs/0004-feature-flag-new-architecture.md)
- [0005: Enable IMAP IDLE ("Push") for the Inbox by Default on Newly Added IMAP Accounts](engineering/rfcs/0005-imap-idle-push-on-new-account-inboxes.md)
- [0006: Remote Feature Flags](engineering/rfcs/0006-remote-feature-flags.md)
- [0007 - Global Database](engineering/rfcs/0007-global-database.md)
- [Rejected]()
- [Obsolete]()
- [Architecture Decision Records](engineering/adr/README.md)
Expand All @@ -68,6 +69,8 @@ generator, in this case, **mdbook**. It defines the structure and navigation of
- [Technical Designs](engineering/technical-designs/README.md)
- [Template](engineering/technical-designs/0000-technical-design-template.md)
- [Proposed]()
- [0003 - Global Database](engineering/technical-designs/0003-global-database.md)
- [Legacy Table Inventory](engineering/technical-designs/0003-global-database/legacy-table-inventory.md)
- [Accepted]()
- [0001 - Changelog System Replacement](engineering/technical-designs/0001-changelog-system-replacement.md)
- [0002: Declarative Feature Flag Catalog](engineering/technical-designs/0002-feature-flag-declarative-catalog.md)
Expand Down
91 changes: 91 additions & 0 deletions docs/architecture/database.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
# Database Architecture

> **Status:** Proposed direction. This describes the target architecture introduced by Global Database. It is not a
> description of the current legacy implementation.

## Purpose

The database architecture provides one local source of truth for mail data in the application, while keeping
mail domain code independent of storage technology and legacy storage types.
It establishes the boundaries that Global Database implements. The [RFC](../engineering/rfcs/0007-global-database.md)
and [technical design](../engineering/technical-designs/0003-global-database.md) remain the authoritative records
for its scope and implementation.

## Current and target state

Today, legacy mail storage uses one SQLite database and one attachment directory per account.

Global Database introduces one Room 3-backed mail database and one file-backed attachment directory for the
application.

## Boundaries and ownership

```mermaid
flowchart LR
Caller[Mail domain callers] --> Contract[Focused mail repository contracts]
Contract --> Implementation[Repository implementations]
Implementation --> GlobalSource[Global local data sources]
Implementation --> LegacySource[Legacy local data sources during migration]
GlobalSource --> MailDatabase[Global mail database]
GlobalSource --> AttachmentFiles[Global attachment files]
MailDatabase --> CoreDatabase[core:database]
LegacySource --> LegacyDatabase[Per-account legacy databases]
LegacySource --> LegacyFiles[Per-account attachment files]
AppComposition[Application composition] --> Implementation
```

- **Mail repository contracts** are the only storage boundary visible to mail-domain callers. They follow the
[Repository pattern ADR](../engineering/adr/0010-adopt-project-wide-repository-pattern.md): they are focused,
explicitly account-scoped, and do not expose Room, SQL, cursors, files, or legacy `LocalStore` types.
- **Repository implementations and local data sources** belong to the mail feature's internal persistence
implementation. They own mappings between domain data and both global and legacy representations.
- **The global mail database** owns the legacy-compatible mail schema, migration metadata, and derived data.
It is one physical database for the application, not one database per account.
- **The attachment store** remains file-backed for content that legacy already held on disk, which is every body part
above a size threshold. Parts at or below it stay `message_parts` BLOBs, as in legacy. Database records and attachment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Parts at or below it stay message_parts BLOBs, as in legacy.

I wonder what the benefits are of having two sources for attachments. Why not store everything as files on disk instead of storing them as BLOBs in the database?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wanted to keep the existing behavior, if it's not too much work, the migration could change that too, but adds complexity.

files are associated through internal persistence mappings.
- **`core:database`** implements Room 3 as one domain-neutral backend and provides lifecycle, transaction, migration,
and Android/JVM desktop driver support needed by the mail implementation. It composes feature schema contributions
deterministically and owns one coordinated schema and migration history. It must not own mail schema, mail entities
or DAOs, repository contracts, or mail domain types.
- **Application composition** binds repository contracts to one implementation. It is the only place that switches from
legacy to global storage after successful migration.

## Data and migration model

The global schema begins as a compatible representation of the legacy mail-store schema and behavior.
Account-local numeric IDs are preserved through account-qualified legacy keys. Global identifiers are added only where
repository contracts require them.
The [legacy table inventory](../engineering/technical-designs/0003-global-database/legacy-table-inventory.md)
defines the exact source-to-target mapping, including external attachment files.

Migration has one authoritative storage representation at a time:

| Migration phase | Authoritative storage | Normal mail access |
|------------------------------------|-------------------------------------------|-----------------------------------------------------------------------------------|
| Before cutover | Legacy per-account storage | Legacy repository implementation |
| Import and validation | Legacy per-account storage | Migration gate holds UI and background mail work. Global data remains unpublished |
| After cutover state is established | Global mail database and attachment store | Global repository implementation |

No normal caller may read from or write to both representations.
Legacy database and attachment artifacts are deleted only after validation and the durable cutover state is established.
The legacy storage implementation stays in the codebase, unbound and unused, and a later release removes it.

## Constraints

- Global Database does not redesign or normalize the legacy mail schema.
- Android and JVM desktop drivers are in scope. The global attachment-store contract is KMP-safe on both targets.
Legacy import is Android-only.
- On Android, a dedicated migration screen owns the migration UI. It could reuse the existing database-migration
activity or introduce a suitable replacement. It shows non-sensitive progress and a completion or failure state with
retry and local report export. The shared migration gate blocks normal mail access and background sync until cutover
or failure.
- Database implementation types remain internal and follow the API/internal module boundary.
- Migration reports are local and user-exportable, but exclude personally identifiable data and are never uploaded.

## Related documentation

- [RFC 0007: Global Database](../engineering/rfcs/0007-global-database.md)
- [Technical Design 0003: Global Database](../engineering/technical-designs/0003-global-database.md)
- [Legacy Table Inventory](../engineering/technical-designs/0003-global-database/legacy-table-inventory.md)

18 changes: 10 additions & 8 deletions docs/engineering/rfcs/0007-global-database.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
- Related RFC: [UUIDv7 Identifier Migration](0009-uuidv7-identifier-migration.md)
- Repository pattern: [ADR 0010 proposal](https://github.com/thunderbird/thunderbird-android/pull/11452)
- Portable data format: [RFC 0008: Portable Profile Data Format](0008-portable-profile-data-format.md)
- Status: **Proposed**
- Status: **Accepted**
- Backend decision: **Room 3 selected by the
[completed database spike](https://github.com/thunderbird/thunderbird-android/issues/11195)**

Expand Down Expand Up @@ -102,12 +102,14 @@ codebase, unbound and unused, until a later release removes it.

### Migration gate and reporting

The migration surfaces progress, failure feedback, retry, and local report export in a dedicated migration screen. That
screen could reuse the existing Android database-migration activity or introduce a suitable replacement. Startup
routing and background mail work respect the migration gate. No mail work bypasses it.
The migration surfaces progress, failure feedback, retry, and local report export in a dedicated migration screen. On
Android, a user-initiated data synchronization foreground service continues the migration when the app leaves the
foreground and provides progress and completion through a persistent notification. The in-app screen and notification
show the same migration state.

Progress information, failure reports, and logs exclude personally identifiable data. They stay on the device and are
never uploaded.
Startup routing and background mail work respect the migration gate. No mail work bypasses it. Progress information,
failure reports, notifications, and logs exclude personally identifiable data. They stay on the device and are never
uploaded.

### Platform support

Expand Down Expand Up @@ -153,6 +155,8 @@ Validation before cutover and the archive from step 1 cover that risk instead.
- Migration requires time and storage, especially for downloaded attachments.
- The user or operating system can terminate the app during migration, leaving an incomplete global database that must
never become authoritative. The migration requires interruption-safe state tracking and restart recovery.
- Android can limit foreground service execution, and a user can explicitly stop the app. Long migrations therefore
cannot rely on the service alone and must remain safe to retry after interruption.
- Rebuilding the search index during migration adds time proportional to locally stored mail.
- A device without enough free space cannot migrate until space is freed.
- After cutover, locally stored mail exists only in the global database and in any archive the user created.
Expand All @@ -163,8 +167,6 @@ Validation before cutover and the archive from step 1 cover that risk instead.

## Open Questions

- Should migration require a pre-flight free-space check with a stated minimum headroom, and should a failed check block
migration or only warn?
- Does declining the POP3 archive require a durable record of the user's acknowledgement?

The technical design owns implementation questions, including driver configuration, the identifier mapping inventory,
Expand Down
Loading