trainlog/docs/database.md

5.9 KiB

Desktop database

1. Status

TRAINLOG_DATABASE_SCHEMA_VERSION=5
DATABASE_SCHEMA_V5=PASS
TRAINLOG_FORMAT_V1=FROZEN

The desktop SQLite database is the canonical long-term Trainlog history.

Its schema evolves independently from all JSON exchange-format versions.

2. Versioning

Schema version uses:

PRAGMA user_version;

Current value:

5

Supported historical databases are migrated explicitly through the implemented migration chain. A database newer than the running binary understands is rejected.

A schema fixture must represent the real historical structure. Rewriting only user_version is not an acceptable migration test.

3. Connection invariants

Every connection enables:

PRAGMA foreign_keys = ON;

A bounded SQLite busy timeout is configured by the core.

4. Tables

exercises

Canonical desktop exercise catalog.

id
exercise_id          UNIQUE stable identity
name
normalized_name      UNIQUE normalized display form
tracking_mode        reps | duration
recording_mode       sets | continuous
data_fields          bounded bit mask

Rules include:

  • continuous implies duration tracking;
  • unknown supplemental field bits are rejected;
  • normalized names remain unique.

sessions

id
session_id           UNIQUE stable identity
started_at
ended_at             nullable
session_type         training | max_test
notes                nullable

session_exercises

Ordered exercise occurrence inside one session.

session_row_id
exercise_row_id
recording_mode
data_fields
position
load_mode
rest_seconds
target_sets
target_reps
target_duration_seconds
target_weight_kg
notes

Current desktop history snapshots recording_mode and data_fields in the session row. tracking_mode remains associated with the referenced exercise catalog identity.

SETS rows support two target shapes in schema v5:

explicit planned target
    target_sets + exactly one target metric

actual-only mobile observation
    target_sets = NULL
    target_reps = NULL
    target_duration_seconds = NULL

This v5 rule is what permits heterogeneous mobile performed sets without inventing a fake uniform target.

CONTINUOUS rows are targetless and require:

load_mode = none
rest_seconds = 0
target_weight_kg = NULL

performed_sets

Ordered actual set records.

session_exercise_row_id
position
reps                 nullable
duration_seconds     nullable
weight_kg            nullable

Exactly one primary actual metric is present:

reps
or
duration_seconds

Actual repetitions may be zero.

Each row is independent; heterogeneous repetition sequences are first-class data.

continuous_activity

One-to-one actual record for a continuous session exercise.

session_exercise_row_id  UNIQUE
duration_seconds
speed_kmh                nullable
distance_km              nullable

Continuous activity never creates a fake performed set.

body_observations

observation_id       UNIQUE
observed_at
session_row_id       optional UNIQUE link
body_weight_kg
neck_cm
shoulders_cm
chest_cm
waist_cm
hips_cm
left_arm_cm
right_arm_cm
left_forearm_cm
right_forearm_cm
left_thigh_cm
right_thigh_cm
left_calf_cm
right_calf_cm
notes

At least one body metric must be present.

5. Identifier generation

Official desktop creator prefixes:

ex_   exercise
se_   session
bo_   body observation
sy_   synchronization run

All use random UUIDv4 values.

Exchange parsers may accept other schema-valid opaque identities where their contract explicitly permits it.

6. Transactions

Multi-row user operations are atomic.

Persisted session correction replaces session child rows transactionally while preserving the parent:

session_id
started_at
ended_at
session_type
session notes
linked body observation

Removing an exercise from a persisted session is therefore a transactional replacement of the remaining child set.

A failed replacement rolls back to the previously persisted session.

Body-observation editing preserves its stable identity, timestamp, and optional session link.

7. Mobile import semantics

tools/import_mobile_export.py validates the complete mobile snapshot before committing database changes.

Properties:

schema-v5 aware
transactional
idempotent by stable IDs
profile-aware catalog reconciliation
heterogeneous performed sets preserved
no fake target generated
continuous activity kept separate

8. Units

Canonical desktop persistence:

weight/load          kg
body circumference   cm
duration/rest         seconds
speed                 km/h
distance              km

9. Android database

The Android SQLite database is independent.

Desktop and Android schema versions are not required to match.

Do not synchronize SQLite database files.

10. Validation

meson compile -C build
meson test -C build --print-errorlogs

Migration-specific regression coverage includes:

schema_v5_migration

The current normal suite contains 19 tests.

11. Measured-max derivation

Measured maxima require no desktop schema v6.

The existing sessions.session_type = max_test classification plus actual performed_sets are sufficient.

Exercise performance points carry the originating session type so the measured-max layer can distinguish explicit tests from ordinary training.

Rules:

training session
    never becomes measured max implicitly

max_test + external
    greatest successful actual load
    tie -> greatest reps/duration

max_test + assistance
    lowest successful assistance
    tie -> greatest reps/duration

max_test + no load
    greatest successful reps/duration

A zero-repetition failed attempt is not a successful measurement.

The current measured result is the newest successful max-test point. The record is the best max-test point using the same load mode.

No extra maximum row is persisted; results are derived from canonical history.