From ed9395366ebf5e19022a21a73b4b39353a2f082b Mon Sep 17 00:00:00 2001 From: fy59 Date: Fri, 18 Sep 2026 16:18:34 +0200 Subject: [PATCH] refactor(programs): clarify persistence and transaction flow --- tools/causal_delete_exchange.py | 4 +- tools/execution_draft_exchange.py | 4 +- tools/export_ai_history.py | 2 +- tools/export_ai_session_drafts.py | 5 +- tools/export_equipment_associations.py | 3 +- tools/export_equipment_definitions.py | 3 +- tools/export_exercise_aliases.py | 4 +- tools/export_exercise_body_zones.py | 3 +- tools/export_exercise_profile_state.py | 2 +- tools/export_pc_catalog.py | 2 +- tools/export_pc_mobile.py | 6 +- tools/export_session_preparations.py | 5 +- tools/export_training_feedback.py | 4 +- tools/import_ai_session_draft.py | 5 +- tools/import_equipment_associations.py | 3 +- tools/import_equipment_definitions.py | 3 +- tools/import_exercise_aliases.py | 3 +- tools/import_exercise_body_zones.py | 4 +- tools/import_exercise_profile_state.py | 4 +- tools/import_mobile_export.py | 8 +- tools/import_training_feedback.py | 4 +- tools/sync_generation_exchange.py | 5 +- tui/include/trainlog/database.h | 451 +- tui/include/trainlog/id.h | 3 +- tui/meson.build | 15 + tui/src/database.c | 6465 +++++++++++------------- tui/src/id.c | 2 +- tui/src/web_server.c | 269 +- tui/src/web_sessions.c | 211 +- tui/tests/test_schema_v21_migration.c | 102 +- tui/tests/test_schema_v7_migration.c | 149 +- tui/tests/test_schema_v9_migration.c | 90 +- 32 files changed, 3732 insertions(+), 4111 deletions(-) diff --git a/tools/causal_delete_exchange.py b/tools/causal_delete_exchange.py index d301a85..ab73248 100644 --- a/tools/causal_delete_exchange.py +++ b/tools/causal_delete_exchange.py @@ -38,8 +38,8 @@ def digest(value) -> str: def require_schema(db: sqlite3.Connection) -> None: version = db.execute("PRAGMA user_version").fetchone()[0] - if version not in (20, 21, 22, 23, 24): - raise CausalError(f"desktop schema v20-v24 required, found v{version}") + if version not in (20, 21, 22, 23, 24, 25): + raise CausalError(f"desktop schema v20-v25 required, found v{version}") def target_snapshot(db: sqlite3.Connection, kind: str, target: str): diff --git a/tools/execution_draft_exchange.py b/tools/execution_draft_exchange.py index 9eb80a0..c45fd58 100644 --- a/tools/execution_draft_exchange.py +++ b/tools/execution_draft_exchange.py @@ -182,8 +182,8 @@ def causal_payload(draft): def require_schema(connection): version = connection.execute("PRAGMA user_version").fetchone()[0] - if version not in (19, 20, 21, 22, 23, 24): - raise LifecycleError(f"desktop schema v19-v24 required, found v{version}") + if version not in (19, 20, 21, 22, 23, 24, 25): + raise LifecycleError(f"desktop schema v19-v25 required, found v{version}") def import_document(connection, document, own_transaction=True): diff --git a/tools/export_ai_history.py b/tools/export_ai_history.py index eef86ae..2d026b1 100644 --- a/tools/export_ai_history.py +++ b/tools/export_ai_history.py @@ -12,7 +12,7 @@ from pathlib import Path from trainlog_sqlite import configure_connection -SUPPORTED_SCHEMA_VERSIONS = (15, 16, 17, 18, 19, 20, 21, 22, 23, 24) +SUPPORTED_SCHEMA_VERSIONS = (15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25) MEASUREMENT_FIELDS = ( "body_weight_kg", "neck_cm", "shoulders_cm", "chest_cm", "waist_cm", "hips_cm", "left_arm_cm", "right_arm_cm", "left_forearm_cm", diff --git a/tools/export_ai_session_drafts.py b/tools/export_ai_session_drafts.py index 2f85289..7bd9202 100644 --- a/tools/export_ai_session_drafts.py +++ b/tools/export_ai_session_drafts.py @@ -20,8 +20,9 @@ ID_PREFIX = "aid_" def ensure_publication_state(connection): """Install the additive v18 cursor for direct syncd/tool invocations.""" owned_transaction = not connection.in_transaction - if connection.execute("PRAGMA user_version").fetchone()[0] not in (18, 19, 20, 21, 22, 23, 24): - raise ValueError("schema desktop v18-v24 requis") + supported_versions = (18, 19, 20, 21, 22, 23, 24, 25) + if connection.execute("PRAGMA user_version").fetchone()[0] not in supported_versions: + raise ValueError("schema desktop v18-v25 requis") columns = {row[1] for row in connection.execute( "PRAGMA table_info(ai_session_drafts)")} if "published_at" not in columns: diff --git a/tools/export_equipment_associations.py b/tools/export_equipment_associations.py index 12ec2d5..073eeb5 100644 --- a/tools/export_equipment_associations.py +++ b/tools/export_equipment_associations.py @@ -28,7 +28,8 @@ def main(): args = parser.parse_args() connection = connect_database(args.database) try: - if connection.execute("PRAGMA user_version;").fetchone()[0] not in (8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24): + supported_versions = tuple(range(8, 26)) + if connection.execute("PRAGMA user_version;").fetchone()[0] not in supported_versions: raise ValueError("schema desktop v8 à v16 requis") known_equipment = load_supplied_equipment_ids(args.catalog) known_equipment.update(row[0] for row in connection.execute( diff --git a/tools/export_equipment_definitions.py b/tools/export_equipment_definitions.py index b726eb8..87a66ce 100644 --- a/tools/export_equipment_definitions.py +++ b/tools/export_equipment_definitions.py @@ -24,7 +24,8 @@ def main(complete_causal_envelope=False): connection = connect_database(args.database) connection.row_factory = sqlite3.Row try: - if connection.execute("PRAGMA user_version").fetchone()[0] not in (8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24): + supported_versions = tuple(range(8, 26)) + if connection.execute("PRAGMA user_version").fetchone()[0] not in supported_versions: raise ValueError("schema desktop v8 à v16 requis") if not complete_causal_envelope and connection.execute("PRAGMA user_version").fetchone()[0] >= 20 and connection.execute("SELECT 1 FROM sync_causal_state WHERE target_kind='custom_equipment' AND deleted=1 LIMIT 1").fetchone(): raise ValueError("causal equipment protection requires the staged artifact") diff --git a/tools/export_exercise_aliases.py b/tools/export_exercise_aliases.py index 500f820..cc50a84 100644 --- a/tools/export_exercise_aliases.py +++ b/tools/export_exercise_aliases.py @@ -17,8 +17,8 @@ def main(): con = connect_database(args.database) try: if con.execute("PRAGMA user_version").fetchone()[0] not in ( - 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24): - raise ValueError("schema desktop v12-v24 requis") + 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25): + raise ValueError("schema desktop v12-v25 requis") rows = con.execute( "SELECT source_exercise_id,canonical_exercise_id FROM exercise_aliases " "ORDER BY source_exercise_id COLLATE BINARY" diff --git a/tools/export_exercise_body_zones.py b/tools/export_exercise_body_zones.py index 63d4ea0..68813f9 100644 --- a/tools/export_exercise_body_zones.py +++ b/tools/export_exercise_body_zones.py @@ -41,7 +41,8 @@ def main(complete_causal_envelope=False): connection = connect_database(args.database) connection.row_factory = sqlite3.Row try: - if connection.execute("PRAGMA user_version").fetchone()[0] not in (11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24): + supported_versions = tuple(range(11, 26)) + if connection.execute("PRAGMA user_version").fetchone()[0] not in supported_versions: raise ValueError("schema desktop v11-v16 requis") if not complete_causal_envelope and connection.execute("PRAGMA user_version").fetchone()[0] >= 20 and connection.execute("SELECT 1 FROM sync_causal_state WHERE target_kind='body_zone_relation' AND deleted=1 LIMIT 1").fetchone(): raise ValueError("causal BODY ZONES protection requires the staged artifact") diff --git a/tools/export_exercise_profile_state.py b/tools/export_exercise_profile_state.py index c64e144..fce458b 100644 --- a/tools/export_exercise_profile_state.py +++ b/tools/export_exercise_profile_state.py @@ -25,7 +25,7 @@ def main(): con.row_factory = sqlite3.Row try: version = con.execute("PRAGMA user_version").fetchone()[0] - if version not in (17, 18, 19, 20, 21, 22, 23, 24): + if version not in (17, 18, 19, 20, 21, 22, 23, 24, 25): raise SystemExit(f"EXERCISE_PROFILE_STATE_EXPORT=FAIL schema={version}") rows = con.execute(""" SELECT e.exercise_id,e.recording_mode,e.tracking_mode,e.data_fields, diff --git a/tools/export_pc_catalog.py b/tools/export_pc_catalog.py index 2f25211..6d098e3 100755 --- a/tools/export_pc_catalog.py +++ b/tools/export_pc_catalog.py @@ -71,7 +71,7 @@ def main(): # CONTRACT: v8 adds only desktop-local custom equipment. The PC # catalogue artifact is unchanged, but it must read the current # canonical desktop schema rather than accept a stale pre-v8 database. - if version not in (8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24): + if version not in range(8, 26): raise SystemExit( "PC_CATALOG_EXPORT=FAIL " f"schema={version}" diff --git a/tools/export_pc_mobile.py b/tools/export_pc_mobile.py index 8e2b30a..93f09aa 100644 --- a/tools/export_pc_mobile.py +++ b/tools/export_pc_mobile.py @@ -83,9 +83,11 @@ def main(complete_causal_envelope=False): con.row_factory = sqlite3.Row try: schema_version = con.execute("PRAGMA user_version").fetchone()[0] - if args.version == 4 and schema_version not in (19, 20, 21, 22, 23, 24): + if args.version == 4 and schema_version not in (19, 20, 21, 22, 23, 24, 25): raise ValueError("mobile V4 exige le schéma desktop v19 ou v20") - if schema_version not in (11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24) and not (args.version == 2 and schema_version == 10): + supported_versions = range(11, 26) + legacy_v2_schema = args.version == 2 and schema_version == 10 + if schema_version not in supported_versions and not legacy_v2_schema: raise ValueError("schema desktop v11-v16 requis (v10 accepté pour export V2 explicite)") if not complete_causal_envelope and schema_version >= 20 and con.execute( "SELECT 1 FROM sync_causal_state WHERE deleted=1 LIMIT 1").fetchone(): diff --git a/tools/export_session_preparations.py b/tools/export_session_preparations.py index c1b7add..bdbaece 100644 --- a/tools/export_session_preparations.py +++ b/tools/export_session_preparations.py @@ -17,8 +17,9 @@ MAX_WITHDRAWALS = 128 def build_export(connection: sqlite3.Connection) -> dict: - if connection.execute("PRAGMA user_version").fetchone()[0] != 24: - raise ValueError("desktop schema v24 required") + supported_versions = (24, 25) + if connection.execute("PRAGMA user_version").fetchone()[0] not in supported_versions: + raise ValueError("desktop schema v24 or v25 required") deliveries = [] rows = connection.execute( "SELECT d.delivery_id,d.preparation_id,d.revision_id,d.execution_session_id,d.state," diff --git a/tools/export_training_feedback.py b/tools/export_training_feedback.py index f5d6b12..e7bd3c4 100644 --- a/tools/export_training_feedback.py +++ b/tools/export_training_feedback.py @@ -10,7 +10,9 @@ def main(complete_causal_envelope=False): parser=argparse.ArgumentParser(); parser.add_argument("output",type=Path); parser.add_argument("--database",type=Path,required=True); args=parser.parse_args() db=connect_database(args.database) try: - if db.execute("PRAGMA user_version").fetchone()[0] not in (15, 16, 17, 18, 19, 20, 21, 22, 23, 24): raise ValueError("schema desktop v15-v24 requis") + supported_versions = range(15, 26) + if db.execute("PRAGMA user_version").fetchone()[0] not in supported_versions: + raise ValueError("schema desktop v15-v25 requis") if not complete_causal_envelope and db.execute("PRAGMA user_version").fetchone()[0] >= 20 and db.execute("SELECT 1 FROM sync_causal_state WHERE target_kind='feedback' AND deleted=1 LIMIT 1").fetchone(): raise ValueError("causal feedback protection requires the staged artifact") roots=db.execute("SELECT f.feedback_id,s.session_id,se.entry_id,e.exercise_id,f.observed_at FROM exercise_feedback f JOIN session_exercises se ON se.id=f.session_exercise_row_id JOIN sessions s ON s.id=se.session_row_id JOIN exercises e ON e.id=se.exercise_row_id").fetchall() followups=db.execute("SELECT f.followup_id,s.session_id,f.observed_at FROM session_followups f JOIN sessions s ON s.id=f.session_row_id").fetchall() diff --git a/tools/import_ai_session_draft.py b/tools/import_ai_session_draft.py index 30b0143..df66934 100644 --- a/tools/import_ai_session_draft.py +++ b/tools/import_ai_session_draft.py @@ -193,8 +193,9 @@ def import_payload(connection, payload, imported_at=None): INVARIANT: validation and every child insert share one IMMEDIATE transaction; any failure leaves no draft, child, or import-identity row behind. """ - if connection.execute("PRAGMA user_version").fetchone()[0] not in (18, 19, 20, 21, 22, 23, 24): - raise ImportFailure("schema desktop v18-v24 requis") + supported_versions = range(18, 26) + if connection.execute("PRAGMA user_version").fetchone()[0] not in supported_versions: + raise ImportFailure("schema desktop v18-v25 requis") draft, entries = validate(payload, connection) digest = canonical_digest(payload) imported_at = imported_at or dt.datetime.now(dt.timezone.utc).isoformat(timespec="seconds").replace("+00:00", "Z") diff --git a/tools/import_equipment_associations.py b/tools/import_equipment_associations.py index ca68c13..f40eadf 100644 --- a/tools/import_equipment_associations.py +++ b/tools/import_equipment_associations.py @@ -187,7 +187,8 @@ def main(): payload = json.loads(args.artifact.read_text(encoding="utf-8")) connection = connect_database(args.database) try: - if connection.execute("PRAGMA user_version;").fetchone()[0] not in (8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24): + supported_versions = range(8, 26) + if connection.execute("PRAGMA user_version;").fetchone()[0] not in supported_versions: fail("schema desktop v8 à v21 requis") proof_path = args.mobile_export if proof_path is None: diff --git a/tools/import_equipment_definitions.py b/tools/import_equipment_definitions.py index bfc1896..8ebba8b 100644 --- a/tools/import_equipment_definitions.py +++ b/tools/import_equipment_definitions.py @@ -53,7 +53,8 @@ def validate(payload, reserved): def apply_definitions(connection, definitions, complete_causal_envelope=False): """Apply validated definitions without owning the surrounding transaction.""" - if connection.execute("PRAGMA user_version").fetchone()[0] not in (8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24): + supported_versions = range(8, 26) + if connection.execute("PRAGMA user_version").fetchone()[0] not in supported_versions: fail("schema desktop v8 à v21 requis") if not complete_causal_envelope and connection.execute("PRAGMA user_version").fetchone()[0] >= 20 and connection.execute("SELECT 1 FROM sync_causal_state WHERE target_kind='custom_equipment' AND deleted=1 LIMIT 1").fetchone(): fail("causal equipment protection requires the staged artifact") diff --git a/tools/import_exercise_aliases.py b/tools/import_exercise_aliases.py index bb2a4fa..604aeac 100644 --- a/tools/import_exercise_aliases.py +++ b/tools/import_exercise_aliases.py @@ -56,7 +56,8 @@ def load(path): def apply_aliases(con, aliases): """Apply parsed aliases inside the caller-owned transaction.""" - if con.execute("PRAGMA user_version").fetchone()[0] not in (12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24): + supported_versions = range(12, 26) + if con.execute("PRAGMA user_version").fetchone()[0] not in supported_versions: fail("schema desktop v12-v21 requis") for source, canonical in aliases: target = con.execute("SELECT id,tracking_mode,recording_mode,data_fields FROM exercises WHERE exercise_id=?", (canonical,)).fetchone() diff --git a/tools/import_exercise_body_zones.py b/tools/import_exercise_body_zones.py index 4bf6539..9045059 100644 --- a/tools/import_exercise_body_zones.py +++ b/tools/import_exercise_body_zones.py @@ -151,7 +151,9 @@ def replace(connection, row_id, primary, secondary): def apply_body_zones(connection, parsed, mobile_proof, complete_causal_envelope=False): """Apply validated zone rows without owning transaction boundaries.""" - if connection.execute("PRAGMA user_version").fetchone()[0] not in (11,12,13,14,15,16,17,18,19,20,21,22,23,24): raise ImportFailure("schema desktop v11-v24 requis") + supported_versions = range(11, 26) + if connection.execute("PRAGMA user_version").fetchone()[0] not in supported_versions: + raise ImportFailure("schema desktop v11-v25 requis") if not complete_causal_envelope and connection.execute("PRAGMA user_version").fetchone()[0]>=20 and connection.execute("SELECT 1 FROM sync_causal_state WHERE target_kind='body_zone_relation' AND deleted=1 LIMIT 1").fetchone(): raise ImportFailure("causal BODY ZONES protection requires the staged artifact") connection.execute("PRAGMA foreign_keys=ON");grouped={} # CONTRACT: resolve complete alias groups before the first write; direct diff --git a/tools/import_exercise_profile_state.py b/tools/import_exercise_profile_state.py index ec2f105..c67e300 100644 --- a/tools/import_exercise_profile_state.py +++ b/tools/import_exercise_profile_state.py @@ -49,7 +49,9 @@ def validate(item): def apply_profile_state(con, root, allow_pending=False): if not isinstance(root,dict) or set(root)!={"format","version","generated_at","exercises"} or root["format"]!="trainlog-exercise-profile-state" or type(root["version"]) is not int or root["version"]!=1 or not isinstance(root["exercises"],list): fail("enveloppe profile-state invalide") parse_timestamp(root["generated_at"], "generated_at");con.row_factory=sqlite3.Row - if con.execute("PRAGMA user_version").fetchone()[0] not in (17,18,19,20,21,22,23,24): fail("schema desktop v17-v24 requis") + if con.execute("PRAGMA user_version").fetchone()[0] not in ( + 17, 18, 19, 20, 21, 22, 23, 24, 25): + fail("schema desktop v17-v25 requis") applied=equal=ancestor=pending=0;seen=set() for item in root["exercises"]: validate(item);eid=item["exercise_id"] diff --git a/tools/import_mobile_export.py b/tools/import_mobile_export.py index 59899e5..e130f31 100755 --- a/tools/import_mobile_export.py +++ b/tools/import_mobile_export.py @@ -846,9 +846,9 @@ def require_supported_schema(connection): # CONTRACT: v9 owns explicit max_results; earlier supported schemas remain # readable for legacy artifacts and are never made to fake that table. - if version not in (5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24): + if version not in range(5, 26): raise ImportFailure( - f"base desktop schema v5 à v24 attendue, version trouvée: {version}" + f"base desktop schema v5 à v25 attendue, version trouvée: {version}" ) @@ -1978,8 +1978,8 @@ def apply_payload(connection, payload, trace_exercises=False): require_supported_schema(connection) schema_version = connection.execute("PRAGMA user_version;").fetchone()[0] - if payload["version"] == 4 and schema_version not in (19, 20, 21, 22, 23, 24): - raise ImportFailure("mobile V4 exige le schéma desktop v19 à v24") + if payload["version"] == 4 and schema_version not in (19, 20, 21, 22, 23, 24, 25): + raise ImportFailure("mobile V4 exige le schéma desktop v19 à v25") if schema_version >= 20: protected = {(row[0], row[1]) for row in connection.execute( "SELECT target_kind,target_id FROM sync_causal_state WHERE deleted=1")} diff --git a/tools/import_training_feedback.py b/tools/import_training_feedback.py index 4b2a82f..0558205 100644 --- a/tools/import_training_feedback.py +++ b/tools/import_training_feedback.py @@ -52,7 +52,9 @@ def merge_revisions(db,table,parent_column,parent_id,revisions): def apply_feedback(db,root,complete_causal_envelope=False): roots_added=roots_skipped=revisions_added=revisions_skipped=0 db.execute("PRAGMA foreign_keys=ON") - if db.execute("PRAGMA user_version").fetchone()[0] not in (15,16,17,18,19,20,21,22,23,24):fail("schema desktop v15-v24 requis") + supported_versions = range(15, 26) + if db.execute("PRAGMA user_version").fetchone()[0] not in supported_versions: + fail("schema desktop v15-v25 requis") if not complete_causal_envelope and db.execute("PRAGMA user_version").fetchone()[0]>=20 and db.execute("SELECT 1 FROM sync_causal_state WHERE target_kind='feedback' AND deleted=1 LIMIT 1").fetchone():fail("causal feedback protection requires the staged artifact") seen=set() for index,item in enumerate(root["exercise_feedback"]): diff --git a/tools/sync_generation_exchange.py b/tools/sync_generation_exchange.py index 5a41f31..20731da 100644 --- a/tools/sync_generation_exchange.py +++ b/tools/sync_generation_exchange.py @@ -201,8 +201,9 @@ def validate_manifest_bytes(raw: bytes, expected_consumer: str | None = None): def require_schema(db: sqlite3.Connection) -> None: - if db.execute("PRAGMA user_version").fetchone()[0] != 24: - raise GenerationError("desktop schema v24 required") + supported_versions = (24, 25) + if db.execute("PRAGMA user_version").fetchone()[0] not in supported_versions: + raise GenerationError("desktop schema v24 or v25 required") def peer_identity(db: sqlite3.Connection, kind: str) -> str: diff --git a/tui/include/trainlog/database.h b/tui/include/trainlog/database.h index f936e92..76692d0 100644 --- a/tui/include/trainlog/database.h +++ b/tui/include/trainlog/database.h @@ -1,7 +1,8 @@ /* * Trainlog database interface. * - * Declares the module boundary and ownership contract; implementation and persistence remain in their owning modules. + * Declares the module boundary and ownership contract; implementation and persistence remain in + * their owning modules. */ #ifndef TRAINLOG_DATABASE_H #define TRAINLOG_DATABASE_H @@ -16,7 +17,7 @@ #include "trainlog/model.h" #include "trainlog/status.h" -#define TRAINLOG_DATABASE_SCHEMA_VERSION 24 +#define TRAINLOG_DATABASE_SCHEMA_VERSION 25 typedef struct TrainlogDatabase TrainlogDatabase; @@ -31,16 +32,20 @@ typedef struct TrainlogFeedbackView { /* CONTRACT: read-only chronological consultation; Android remains the sole * V1 creator. Ties use the stable ID byte order. No SQLite resources escape. */ -TrainlogStatus trainlog_database_list_training_feedback( - TrainlogDatabase *database, const char *session_id, - TrainlogFeedbackView *exercise_feedback, size_t exercise_capacity, - size_t *exercise_count, TrainlogFeedbackView *followups, - size_t followup_capacity, size_t *followup_count); +TrainlogStatus trainlog_database_list_training_feedback(TrainlogDatabase *database, + const char *session_id, + TrainlogFeedbackView *exercise_feedback, + size_t exercise_capacity, + size_t *exercise_count, + TrainlogFeedbackView *followups, + size_t followup_capacity, + size_t *followup_count); /* Nestable read savepoints let composition services observe one database state * without exposing SQLite or issuing writes to application data. */ TrainlogStatus trainlog_database_read_snapshot_begin(TrainlogDatabase *database); -TrainlogStatus trainlog_database_read_snapshot_end(TrainlogDatabase *database, bool commit_snapshot); +TrainlogStatus trainlog_database_read_snapshot_end(TrainlogDatabase *database, + bool commit_snapshot); /* WHY: session occurrences retain an equipment ID, while custom definitions * need durable presentation metadata. A reference alone is never a definition. */ @@ -69,16 +74,12 @@ typedef struct TrainlogResolvedEquipment { TrainlogEquipmentOrigin origin; } TrainlogResolvedEquipment; -TrainlogStatus trainlog_database_create_custom_equipment( - TrainlogDatabase *database, - const TrainlogCustomEquipment *equipment -); -TrainlogStatus trainlog_database_list_custom_equipment( - TrainlogDatabase *database, - TrainlogCustomEquipment *output, - size_t capacity, - size_t *output_count -); +TrainlogStatus trainlog_database_create_custom_equipment(TrainlogDatabase *database, + const TrainlogCustomEquipment *equipment); +TrainlogStatus trainlog_database_list_custom_equipment(TrainlogDatabase *database, + TrainlogCustomEquipment *output, + size_t capacity, + size_t *output_count); /* CONTRACT: reads at most capacity + 1 ordered definitions, so callers can * filter pages before imposing a presentation cap. database and output are * borrowed for the call; the caller owns copied strings. This read-only API @@ -90,33 +91,24 @@ TrainlogStatus trainlog_database_list_custom_equipment( * including any storage copied before the failure. offset is valid only while * data is unchanged; refresh after writes. It is neither a durable cursor nor * an idempotency mechanism. */ -TrainlogStatus trainlog_database_list_custom_equipment_page( - TrainlogDatabase *database, - size_t offset, - TrainlogCustomEquipment *output, - size_t capacity, - size_t *output_count, - bool *output_more -); +TrainlogStatus trainlog_database_list_custom_equipment_page(TrainlogDatabase *database, + size_t offset, + TrainlogCustomEquipment *output, + size_t capacity, + size_t *output_count, + bool *output_more); /* CONTRACT: an occurrence ID always resolves to a visible value. Unknown IDs * are returned verbatim with TRAINLOG_EQUIPMENT_UNKNOWN, never hidden. */ -TrainlogStatus trainlog_database_resolve_equipment( - TrainlogDatabase *database, - const char *equipment_id, - TrainlogResolvedEquipment *output -); -TrainlogStatus trainlog_database_list_exercise_equipment( - TrainlogDatabase *database, - const char *exercise_id, - TrainlogResolvedEquipment *output, - size_t capacity, - size_t *output_count -); +TrainlogStatus trainlog_database_resolve_equipment(TrainlogDatabase *database, + const char *equipment_id, + TrainlogResolvedEquipment *output); +TrainlogStatus trainlog_database_list_exercise_equipment(TrainlogDatabase *database, + const char *exercise_id, + TrainlogResolvedEquipment *output, + size_t capacity, + size_t *output_count); -TrainlogStatus trainlog_database_open( - const char *path, - TrainlogDatabase **output_database -); +TrainlogStatus trainlog_database_open(const char *path, TrainlogDatabase **output_database); /** * @brief Open a database and report the backend failure that prevented it. @@ -127,24 +119,17 @@ TrainlogStatus trainlog_database_open( * This preserves the stable application status surface without hiding the * path-specific reason needed to repair a user's durable database safely. */ -TrainlogStatus trainlog_database_open_with_diagnostic( - const char *path, - TrainlogDatabase **output_database, - char *output_diagnostic, - size_t output_diagnostic_capacity -); +TrainlogStatus trainlog_database_open_with_diagnostic(const char *path, + TrainlogDatabase **output_database, + char *output_diagnostic, + size_t output_diagnostic_capacity); void trainlog_database_close(TrainlogDatabase *database); -TrainlogStatus trainlog_database_schema_version( - TrainlogDatabase *database, - int *output_version -); +TrainlogStatus trainlog_database_schema_version(TrainlogDatabase *database, int *output_version); -TrainlogStatus trainlog_database_foreign_keys_enabled( - TrainlogDatabase *database, - int *output_enabled -); +TrainlogStatus trainlog_database_foreign_keys_enabled(TrainlogDatabase *database, + int *output_enabled); TrainlogStatus trainlog_database_begin(TrainlogDatabase *database); TrainlogStatus trainlog_database_commit(TrainlogDatabase *database); @@ -156,35 +141,26 @@ TrainlogStatus trainlog_database_rollback(TrainlogDatabase *database); * Unicode normalization belongs to catalog.c. This lower-level API owns the * final SQLite uniqueness barrier. */ -TrainlogStatus trainlog_database_insert_exercise( - TrainlogDatabase *database, - const char *exercise_id, - const char *name, - const char *normalized_name, - TrainlogTrackingMode tracking_mode -); +TrainlogStatus trainlog_database_insert_exercise(TrainlogDatabase *database, + const char *exercise_id, + const char *name, + const char *normalized_name, + TrainlogTrackingMode tracking_mode); -TrainlogStatus trainlog_database_insert_exercise_profiled( - TrainlogDatabase *database, - const char *exercise_id, - const char *name, - const char *normalized_name, - TrainlogTrackingMode tracking_mode, - TrainlogRecordingMode recording_mode, - TrainlogExerciseDataFields data_fields -); +TrainlogStatus trainlog_database_insert_exercise_profiled(TrainlogDatabase *database, + const char *exercise_id, + const char *name, + const char *normalized_name, + TrainlogTrackingMode tracking_mode, + TrainlogRecordingMode recording_mode, + TrainlogExerciseDataFields data_fields); -TrainlogStatus trainlog_database_exercise_count( - TrainlogDatabase *database, - size_t *output_count -); +TrainlogStatus trainlog_database_exercise_count(TrainlogDatabase *database, size_t *output_count); -TrainlogStatus trainlog_database_list_exercises( - TrainlogDatabase *database, - TrainlogExercise *output, - size_t capacity, - size_t *output_count -); +TrainlogStatus trainlog_database_list_exercises(TrainlogDatabase *database, + TrainlogExercise *output, + size_t capacity, + size_t *output_count); /** * @brief Resolve a current or merged exercise identity to its canonical ID. @@ -193,12 +169,10 @@ TrainlogStatus trainlog_database_list_exercises( * resolve through exercise_aliases. Unknown IDs return NOT_FOUND. The output * buffer is caller-owned and must hold TRAINLOG_ID_MAX + 1 bytes. */ -TrainlogStatus trainlog_database_resolve_exercise_id( - TrainlogDatabase *database, - const char *exercise_id, - char *output_canonical_id, - size_t output_capacity -); +TrainlogStatus trainlog_database_resolve_exercise_id(TrainlogDatabase *database, + const char *exercise_id, + char *output_canonical_id, + size_t output_capacity); /** * @brief Atomically merge one current catalogue exercise into another. @@ -210,11 +184,9 @@ TrainlogStatus trainlog_database_resolve_exercise_id( * its stable entry ID or any owned actual/planning data. Existing aliases to * source collapse directly to canonical and source becomes a durable alias. */ -TrainlogStatus trainlog_database_merge_exercises( - TrainlogDatabase *database, - const char *source_exercise_id, - const char *canonical_exercise_id -); +TrainlogStatus trainlog_database_merge_exercises(TrainlogDatabase *database, + const char *source_exercise_id, + const char *canonical_exercise_id); typedef struct TrainlogExerciseMergePreview { size_t occurrences; @@ -227,11 +199,9 @@ typedef struct TrainlogExerciseMergePreview { /* CONTRACT: counts describe source-owned records which the atomic merge will * repoint or union. Unknown IDs return NOT_FOUND and output is never partial. */ -TrainlogStatus trainlog_database_preview_exercise_merge( - TrainlogDatabase *database, - const char *source_exercise_id, - TrainlogExerciseMergePreview *output -); +TrainlogStatus trainlog_database_preview_exercise_merge(TrainlogDatabase *database, + const char *source_exercise_id, + TrainlogExerciseMergePreview *output); /** * @brief Atomically replace one exercise's direct body-zone relations. @@ -245,24 +215,20 @@ TrainlogStatus trainlog_database_preview_exercise_merge( * @return INVALID_ARGUMENT for unknown/group/duplicate zones, NOT_FOUND for * an unknown exercise, or the explicit persistence status. */ -TrainlogStatus trainlog_database_replace_exercise_body_zones( - TrainlogDatabase *database, - const char *exercise_id, - const char *primary_zone_id, - const char *const *secondary_zone_ids, - size_t secondary_count -); +TrainlogStatus trainlog_database_replace_exercise_body_zones(TrainlogDatabase *database, + const char *exercise_id, + const char *primary_zone_id, + const char *const *secondary_zone_ids, + size_t secondary_count); /* NOT_FOUND distinguishes an unknown exercise identity from the valid empty * relation set. Output is caller-owned; output_count reports required capacity * and no relation is truncated as a successful result. */ -TrainlogStatus trainlog_database_list_exercise_body_zones( - TrainlogDatabase *database, - const char *exercise_id, - TrainlogExerciseBodyZone *output, - size_t capacity, - size_t *output_count -); +TrainlogStatus trainlog_database_list_exercise_body_zones(TrainlogDatabase *database, + const char *exercise_id, + TrainlogExerciseBodyZone *output, + size_t capacity, + size_t *output_count); /* CONTRACT: the caller owns output storage; output_count reports the required * count and INVALID_ARGUMENT is returned when capacity is insufficient. @@ -273,17 +239,15 @@ TrainlogStatus trainlog_database_list_exercise_body_zones( * The resulting exercise IDs compose with list_exercise_body_zones() and * list_exercise_performance(); the latter exposes newest history and explicit * MAX without duplicating either datum for a future session generator. */ -TrainlogStatus trainlog_database_list_exercises_filtered( - TrainlogDatabase *database, - const char *normalized_prefix, - const char *zone_id, - bool include_descendants, - bool primary_only, - bool unclassified_only, - TrainlogExercise *output, - size_t capacity, - size_t *output_count -); +TrainlogStatus trainlog_database_list_exercises_filtered(TrainlogDatabase *database, + const char *normalized_prefix, + const char *zone_id, + bool include_descendants, + bool primary_only, + bool unclassified_only, + TrainlogExercise *output, + size_t capacity, + size_t *output_count); /* CONTRACT: name/profile/direct zones are one transaction and all string/list * inputs are borrowed only for the duration of the call. A profile change is @@ -291,51 +255,38 @@ TrainlogStatus trainlog_database_list_exercises_filtered( * rename or zone replacement preserves exercise_id and history. Zone rules * match replace_exercise_body_zones(). Unknown exercise IDs return NOT_FOUND; * invalid metadata and name collisions remain explicit errors. */ -TrainlogStatus trainlog_database_update_exercise_profiled( - TrainlogDatabase *database, - const char *exercise_id, - const char *name, - const char *normalized_name, - TrainlogTrackingMode tracking_mode, - TrainlogRecordingMode recording_mode, - TrainlogExerciseDataFields data_fields, - const char *primary_zone_id, - const char *const *secondary_zone_ids, - size_t secondary_count -); +TrainlogStatus trainlog_database_update_exercise_profiled(TrainlogDatabase *database, + const char *exercise_id, + const char *name, + const char *normalized_name, + TrainlogTrackingMode tracking_mode, + TrainlogRecordingMode recording_mode, + TrainlogExerciseDataFields data_fields, + const char *primary_zone_id, + const char *const *secondary_zone_ids, + size_t secondary_count); -TrainlogStatus trainlog_database_insert_session( - TrainlogDatabase *database, - const TrainlogSessionInput *session -); +TrainlogStatus trainlog_database_insert_session(TrainlogDatabase *database, + const TrainlogSessionInput *session); -TrainlogStatus trainlog_database_session_count( - TrainlogDatabase *database, - size_t *output_count -); +TrainlogStatus trainlog_database_session_count(TrainlogDatabase *database, size_t *output_count); /* Observable history only: a returned session owns at least one persisted * performed set, continuous activity, or explicit MAX result. ended_at is * presentation metadata and is not an actual-work gate. */ -TrainlogStatus trainlog_database_list_sessions( - TrainlogDatabase *database, - TrainlogSessionSummary *output, - size_t capacity, - size_t *output_count -); +TrainlogStatus trainlog_database_list_sessions(TrainlogDatabase *database, + TrainlogSessionSummary *output, + size_t capacity, + size_t *output_count); -TrainlogStatus trainlog_database_insert_body_observation( - TrainlogDatabase *database, - const TrainlogBodyObservationInput *observation -); - -TrainlogStatus trainlog_database_list_weight_points( - TrainlogDatabase *database, - TrainlogWeightPoint *output, - size_t capacity, - size_t *output_count -); +TrainlogStatus +trainlog_database_insert_body_observation(TrainlogDatabase *database, + const TrainlogBodyObservationInput *observation); +TrainlogStatus trainlog_database_list_weight_points(TrainlogDatabase *database, + TrainlogWeightPoint *output, + size_t capacity, + size_t *output_count); /* TRAINLOG_SESSION_DETAILS_API */ @@ -382,21 +333,17 @@ typedef struct TrainlogPersistedExerciseDetail { * every partial allocation and, when output_exercise_count is non-NULL, sets * it to zero. A nonzero capacity requires a non-NULL output_exercises array. */ -TrainlogStatus trainlog_database_get_session_details( - TrainlogDatabase *database, - const char *session_id, - TrainlogSessionSummary *output_session, - TrainlogPersistedExerciseDetail *output_exercises, - size_t exercise_capacity, - size_t *output_exercise_count -); +TrainlogStatus +trainlog_database_get_session_details(TrainlogDatabase *database, + const char *session_id, + TrainlogSessionSummary *output_session, + TrainlogPersistedExerciseDetail *output_exercises, + size_t exercise_capacity, + size_t *output_exercise_count); /* Safe for zero-initialized details and details returned by the loader. */ -void trainlog_database_free_session_details( - TrainlogPersistedExerciseDetail *exercises, - size_t exercise_count -); - +void trainlog_database_free_session_details(TrainlogPersistedExerciseDetail *exercises, + size_t exercise_count); /* TRAINLOG_BODY_METRIC_HISTORY_API */ @@ -430,21 +377,16 @@ typedef struct TrainlogBodyPairPoint { double right_value; } TrainlogBodyPairPoint; -TrainlogStatus trainlog_database_list_body_metric_points( - TrainlogDatabase *database, - TrainlogBodyMetric metric, - TrainlogBodyMetricPoint *output, - size_t capacity, - size_t *output_count -); - -TrainlogStatus trainlog_database_latest_body_pair( - TrainlogDatabase *database, - TrainlogBodyMetric left_metric, - TrainlogBodyMetric right_metric, - TrainlogBodyPairPoint *output -); +TrainlogStatus trainlog_database_list_body_metric_points(TrainlogDatabase *database, + TrainlogBodyMetric metric, + TrainlogBodyMetricPoint *output, + size_t capacity, + size_t *output_count); +TrainlogStatus trainlog_database_latest_body_pair(TrainlogDatabase *database, + TrainlogBodyMetric left_metric, + TrainlogBodyMetric right_metric, + TrainlogBodyPairPoint *output); /* TRAINLOG_EXERCISE_PERFORMANCE_API */ @@ -476,13 +418,11 @@ typedef struct TrainlogExercisePerformancePoint { * A zero-repetition failed attempt is never promoted to representative * performance. This API does not create or infer a measured maximum. */ -TrainlogStatus trainlog_database_list_exercise_performance( - TrainlogDatabase *database, - const char *exercise_id, - TrainlogExercisePerformancePoint *output, - size_t capacity, - size_t *output_count -); +TrainlogStatus trainlog_database_list_exercise_performance(TrainlogDatabase *database, + const char *exercise_id, + TrainlogExercisePerformancePoint *output, + size_t capacity, + size_t *output_count); /* TRAINING_KNOWLEDGE_RUNTIME_READ_V1 */ #define TRAINLOG_OCCURRENCE_PAGE_MAX 32U @@ -534,11 +474,9 @@ typedef struct TrainlogLatestExplicitMax { } TrainlogLatestExplicitMax; /* Exact-ID profile read; unknown IDs return NOT_FOUND. */ -TrainlogStatus trainlog_database_get_exercise_profile( - TrainlogDatabase *database, - const char *exercise_id, - TrainlogExercise *output -); +TrainlogStatus trainlog_database_get_exercise_profile(TrainlogDatabase *database, + const char *exercise_id, + TrainlogExercise *output); /** * Current-data keyset page ordered by the exact started_at instant, then @@ -555,53 +493,46 @@ TrainlogStatus trainlog_database_get_exercise_profile( * are DATABASE_ERROR rather than omitted or truncated. limit must be * 1..TRAINLOG_OCCURRENCE_PAGE_MAX. */ -TrainlogStatus trainlog_database_list_exercise_occurrences_page( - TrainlogDatabase *database, - const char *exercise_id, - const TrainlogExerciseOccurrenceCursor *after, - size_t limit, - TrainlogExerciseOccurrence *output, - size_t *output_count, - bool *output_has_more, - TrainlogExerciseOccurrenceCursor *output_next -); +TrainlogStatus +trainlog_database_list_exercise_occurrences_page(TrainlogDatabase *database, + const char *exercise_id, + const TrainlogExerciseOccurrenceCursor *after, + size_t limit, + TrainlogExerciseOccurrence *output, + size_t *output_count, + bool *output_has_more, + TrainlogExerciseOccurrenceCursor *output_next); /* Original set positions are preserved. after_position is exclusive; use -1 * for the first page. Reps are non-negative, so zero preserves a failed * attempt. Nullable values remain distinct from zero. Corrupt storage types or * values outside the public int/size_t ranges return DATABASE_ERROR. No rows * exist for continuous occurrences. Output remains caller-owned. */ -TrainlogStatus trainlog_database_list_occurrence_sets_page( - TrainlogDatabase *database, - const char *entry_id, - int after_position, - size_t limit, - TrainlogOccurrenceSet *output, - size_t *output_count, - bool *output_has_more, - int *output_next_position -); +TrainlogStatus trainlog_database_list_occurrence_sets_page(TrainlogDatabase *database, + const char *entry_id, + int after_position, + size_t limit, + TrainlogOccurrenceSet *output, + size_t *output_count, + bool *output_has_more, + int *output_next_position); /* Reads max_results joined only through max_test sessions; ordinary large sets * never qualify. It uses the same exact instant and bytewise-ID ordering as * occurrence pagination. Output keeps the exact source timestamp and * occurrence load/equipment context; corrupt or over-capacity selected storage * returns DATABASE_ERROR. */ -TrainlogStatus trainlog_database_latest_explicit_max_context( - TrainlogDatabase *database, - const char *exercise_id, - TrainlogLatestExplicitMax *output -); +TrainlogStatus trainlog_database_latest_explicit_max_context(TrainlogDatabase *database, + const char *exercise_id, + TrainlogLatestExplicitMax *output); /* Same chronological contract, additionally restricted to one exact nonempty * equipment ID. This is the compatibility reader for user-directed %MAX. */ -TrainlogStatus trainlog_database_latest_explicit_max_equipment_context( - TrainlogDatabase *database, - const char *exercise_id, - const char *equipment_id, - TrainlogLatestExplicitMax *output -); - +TrainlogStatus +trainlog_database_latest_explicit_max_equipment_context(TrainlogDatabase *database, + const char *exercise_id, + const char *equipment_id, + TrainlogLatestExplicitMax *output); /* TRAINLOG_SESSION_EDIT_API */ @@ -639,17 +570,16 @@ typedef struct TrainlogEditableExerciseRecord { * too small it returns TRAINLOG_STATUS_INVALID_ARGUMENT and the outputs must not * be used. */ -TrainlogStatus trainlog_database_load_session_editable( - TrainlogDatabase *database, - const char *session_id, - TrainlogSessionSummary *output_session, - TrainlogEditableExerciseRecord *output_exercises, - size_t exercise_capacity, - size_t *output_exercise_count, - TrainlogSetInput *output_sets, - size_t set_capacity, - size_t *output_set_count -); +TrainlogStatus +trainlog_database_load_session_editable(TrainlogDatabase *database, + const char *session_id, + TrainlogSessionSummary *output_session, + TrainlogEditableExerciseRecord *output_exercises, + size_t exercise_capacity, + size_t *output_exercise_count, + TrainlogSetInput *output_sets, + size_t set_capacity, + size_t *output_set_count); /** * @brief Replace only the exercise/set contents of an existing session. @@ -661,13 +591,11 @@ TrainlogStatus trainlog_database_load_session_editable( * inherit feedback. The replacement is atomic: any error rolls the whole * operation back. */ -TrainlogStatus trainlog_database_replace_session_exercises( - TrainlogDatabase *database, - const char *session_id, - const TrainlogSessionExerciseInput *exercises, - size_t exercise_count -); - +TrainlogStatus +trainlog_database_replace_session_exercises(TrainlogDatabase *database, + const char *session_id, + const TrainlogSessionExerciseInput *exercises, + size_t exercise_count); /* TRAINLOG_BODY_OBSERVATION_RECORD_API */ @@ -708,18 +636,14 @@ typedef struct TrainlogBodyObservationRecord { char notes[TRAINLOG_NOTE_MAX + 1U]; } TrainlogBodyObservationRecord; -TrainlogStatus trainlog_database_list_body_observations( - TrainlogDatabase *database, - TrainlogBodyObservationRecord *output, - size_t capacity, - size_t *output_count -); +TrainlogStatus trainlog_database_list_body_observations(TrainlogDatabase *database, + TrainlogBodyObservationRecord *output, + size_t capacity, + size_t *output_count); -TrainlogStatus trainlog_database_get_body_observation( - TrainlogDatabase *database, - const char *observation_id, - TrainlogBodyObservationRecord *output -); +TrainlogStatus trainlog_database_get_body_observation(TrainlogDatabase *database, + const char *observation_id, + TrainlogBodyObservationRecord *output); /** * @brief Correct metric values of one existing observation. @@ -727,9 +651,8 @@ TrainlogStatus trainlog_database_get_body_observation( * observation_id identifies the stable row. observed_at and session linkage * remain unchanged. At least one body metric must remain present. */ -TrainlogStatus trainlog_database_update_body_observation( - TrainlogDatabase *database, - const TrainlogBodyObservationInput *observation -); +TrainlogStatus +trainlog_database_update_body_observation(TrainlogDatabase *database, + const TrainlogBodyObservationInput *observation); #endif diff --git a/tui/include/trainlog/id.h b/tui/include/trainlog/id.h index 0905d5e..10bcf54 100644 --- a/tui/include/trainlog/id.h +++ b/tui/include/trainlog/id.h @@ -41,7 +41,8 @@ * - `sy` for synchronization runs; * - `sxe` for persisted session-exercise occurrences; * - `sp`, `spr`, `spe`, `spd`, and `spw` for preparation, revision, - * occurrence, delivery, and withdrawal identities. + * occurrence, delivery, and withdrawal identities; + * - `pgr` for desktop Program V1 revision identities. * * Imported v1 documents may contain other schema-valid opaque identifiers; * this API defines creation policy, not import validation. diff --git a/tui/meson.build b/tui/meson.build index 0785686..d76c9a8 100644 --- a/tui/meson.build +++ b/tui/meson.build @@ -9,6 +9,7 @@ mtp_dep = dependency('libmtp', required: true) notcurses_dep = dependency('notcurses-core', required: true) microhttpd_dep = dependency('libmicrohttpd', required: true) yyjson_dep = dependency('yyjson', required: true) +crypto_dep = dependency('libcrypto', required: true) utf8proc_dep = dependency('libutf8proc', required: false) if not utf8proc_dep.found() @@ -74,6 +75,8 @@ if web_assets_enabled '../web/src/routes/PlaceholderPage.tsx', '../web/src/routes/SessionsPage.tsx', '../web/src/api/sessions.ts', + '../web/src/api/programs.ts', + '../web/src/routes/ProgramsTab.tsx', '../web/src/theme/app.css', '../web/src/theme/tokens.css', ) @@ -202,6 +205,7 @@ trainlog_core_sources = files( 'src/web_prepared_items.c', 'src/web_prepared_items_json.c', 'src/web_sessions.c', + 'src/web_programs.c', 'src/duration.c', 'src/id.c', 'src/timeutil.c', @@ -235,6 +239,7 @@ trainlog_core = static_library( udev_dep, mtp_dep, yyjson_dep, + crypto_dep, ], c_args: strict_c_args, ) @@ -250,6 +255,7 @@ trainlog_core_dep = declare_dependency( udev_dep, mtp_dep, yyjson_dep, + crypto_dep, ], ) @@ -364,6 +370,15 @@ test_web_sessions = executable( ) test('web_sessions', test_web_sessions) +test_web_programs = executable( + 'test_web_programs', + 'tests/test_web_programs.c', + dependencies: trainlog_core_dep, + include_directories: trainlog_private_include, + c_args: strict_c_args, +) +test('web_programs', test_web_programs) + test_dashboard_header = executable( 'test_dashboard_header', 'tests/test_dashboard_header.c', diff --git a/tui/src/database.c b/tui/src/database.c index 142558b..77030c0 100644 --- a/tui/src/database.c +++ b/tui/src/database.c @@ -23,20 +23,31 @@ #include /* CONTRACT: exact parity with Android/Python UUIDv5 over parent, profile tuple. */ -static void sqlite_profile_revision(sqlite3_context *context, int argc, sqlite3_value **argv) -{ +static void sqlite_profile_revision(sqlite3_context *context, int argc, sqlite3_value **argv) { uuid_t namespace_id, revision; char name[256], uuid_text[37], output[41]; const char *parent, *recording, *tracking; int length; if (argc != 4 || sqlite3_value_type(argv[0]) != SQLITE_TEXT || sqlite3_value_type(argv[1]) != SQLITE_TEXT || sqlite3_value_type(argv[2]) != SQLITE_TEXT || - sqlite3_value_type(argv[3]) != SQLITE_INTEGER) { sqlite3_result_error(context, "invalid profile revision", -1); return; } - parent = (const char *)sqlite3_value_text(argv[0]); recording = (const char *)sqlite3_value_text(argv[1]); + sqlite3_value_type(argv[3]) != SQLITE_INTEGER) { + sqlite3_result_error(context, "invalid profile revision", -1); + return; + } + parent = (const char *)sqlite3_value_text(argv[0]); + recording = (const char *)sqlite3_value_text(argv[1]); tracking = (const char *)sqlite3_value_text(argv[2]); - length = snprintf(name, sizeof(name), "%s\n%s\n%s\n%d", parent, recording, tracking, sqlite3_value_int(argv[3])); - if (length < 0 || (size_t)length >= sizeof(name) || uuid_parse("4f4c8ea6-18f6-5e48-9d65-a54a24889149", namespace_id) != 0) { - sqlite3_result_error(context, "profile revision overflow", -1); return; + length = snprintf(name, + sizeof(name), + "%s\n%s\n%s\n%d", + parent, + recording, + tracking, + sqlite3_value_int(argv[3])); + if (length < 0 || (size_t)length >= sizeof(name) || + uuid_parse("4f4c8ea6-18f6-5e48-9d65-a54a24889149", namespace_id) != 0) { + sqlite3_result_error(context, "profile revision overflow", -1); + return; } uuid_generate_sha1(revision, namespace_id, name, (size_t)length); uuid_unparse_lower(revision, uuid_text); @@ -45,17 +56,16 @@ static void sqlite_profile_revision(sqlite3_context *context, int argc, sqlite3_ } /* Exact recognizer for the unpublished variable-width prototype token. */ -static void sqlite_prototype_profile_valid(sqlite3_context *context, int argc, - sqlite3_value **argv) -{ +static void +sqlite_prototype_profile_valid(sqlite3_context *context, int argc, sqlite3_value **argv) { const char *token, *recording, *tracking; char expected[96]; int fields, length; if (argc != 4 || sqlite3_value_type(argv[0]) != SQLITE_TEXT || - sqlite3_value_type(argv[1]) != SQLITE_TEXT || - sqlite3_value_type(argv[2]) != SQLITE_TEXT || + sqlite3_value_type(argv[1]) != SQLITE_TEXT || sqlite3_value_type(argv[2]) != SQLITE_TEXT || sqlite3_value_type(argv[3]) != SQLITE_INTEGER) { - sqlite3_result_int(context, 0); return; + sqlite3_result_int(context, 0); + return; } token = (const char *)sqlite3_value_text(argv[0]); recording = (const char *)sqlite3_value_text(argv[1]); @@ -63,49 +73,57 @@ static void sqlite_prototype_profile_valid(sqlite3_context *context, int argc, fields = sqlite3_value_int(argv[3]); if ((strcmp(recording, "sets") != 0 && strcmp(recording, "continuous") != 0) || (strcmp(tracking, "reps") != 0 && strcmp(tracking, "duration") != 0) || - (strcmp(recording, "continuous") == 0 && strcmp(tracking, "duration") != 0) || - fields < 0 || (fields & ~3) != 0 || - (strcmp(recording, "sets") == 0 && fields != 0)) { - sqlite3_result_int(context, 0); return; + (strcmp(recording, "continuous") == 0 && strcmp(tracking, "duration") != 0) || fields < 0 || + (fields & ~3) != 0 || (strcmp(recording, "sets") == 0 && fields != 0)) { + sqlite3_result_int(context, 0); + return; } - length = snprintf(expected, sizeof(expected), "pr1|%s|%s|%d", - recording, tracking, fields); - sqlite3_result_int(context, length >= 0 && (size_t)length < sizeof(expected) && - strcmp(token, expected) == 0); + length = snprintf(expected, sizeof(expected), "pr1|%s|%s|%d", recording, tracking, fields); + sqlite3_result_int( + context, length >= 0 && (size_t)length < sizeof(expected) && strcmp(token, expected) == 0); } -TrainlogStatus trainlog_database_read_snapshot_begin(TrainlogDatabase *database) -{ +TrainlogStatus trainlog_database_read_snapshot_begin(TrainlogDatabase *database) { char sql[64]; - if (database == NULL || database->connection == NULL || database->read_snapshot_depth == UINT_MAX) + if (database == NULL || database->connection == NULL || + database->read_snapshot_depth == UINT_MAX) { return TRAINLOG_STATUS_INVALID_ARGUMENT; + } (void)snprintf(sql, sizeof(sql), "SAVEPOINT trainlog_read_%u;", database->read_snapshot_depth); - if (sqlite3_exec(database->connection, sql, NULL, NULL, NULL) != SQLITE_OK) + if (sqlite3_exec(database->connection, sql, NULL, NULL, NULL) != SQLITE_OK) { return TRAINLOG_STATUS_DATABASE_ERROR; + } ++database->read_snapshot_depth; return TRAINLOG_STATUS_OK; } -TrainlogStatus trainlog_database_read_snapshot_end(TrainlogDatabase *database, bool commit_snapshot) -{ +TrainlogStatus trainlog_database_read_snapshot_end(TrainlogDatabase *database, + bool commit_snapshot) { char sql[160]; unsigned int depth; - if (database == NULL || database->connection == NULL || database->read_snapshot_depth == 0U) + if (database == NULL || database->connection == NULL || database->read_snapshot_depth == 0U) { return TRAINLOG_STATUS_INVALID_ARGUMENT; + } depth = database->read_snapshot_depth - 1U; - if (commit_snapshot) + if (commit_snapshot) { (void)snprintf(sql, sizeof(sql), "RELEASE trainlog_read_%u;", depth); - else - (void)snprintf(sql, sizeof(sql), "ROLLBACK TO trainlog_read_%u; RELEASE trainlog_read_%u;", depth, depth); - if (sqlite3_exec(database->connection, sql, NULL, NULL, NULL) != SQLITE_OK) + } else { + (void)snprintf(sql, + sizeof(sql), + "ROLLBACK TO trainlog_read_%u; RELEASE trainlog_read_%u;", + depth, + depth); + } + if (sqlite3_exec(database->connection, sql, NULL, NULL, NULL) != SQLITE_OK) { return TRAINLOG_STATUS_DATABASE_ERROR; + } database->read_snapshot_depth = depth; return TRAINLOG_STATUS_OK; } -TrainlogStatus trainlog_database_scan_generation_history( - TrainlogDatabase *database, TrainlogGenerationHistoryVisitor visitor, void *context) -{ +TrainlogStatus trainlog_database_scan_generation_history(TrainlogDatabase *database, + TrainlogGenerationHistoryVisitor visitor, + void *context) { static const char *const SQL = "SELECT s.session_id,s.started_at,se.entry_id,e.exercise_id,e.recording_mode," "se.tracking_mode,se.equipment_id,se.load_mode,se.rest_seconds," @@ -119,9 +137,13 @@ TrainlogStatus trainlog_database_scan_generation_history( sqlite3_stmt *statement = NULL; TrainlogStatus status; int rc; - if (database == NULL || visitor == NULL) return TRAINLOG_STATUS_INVALID_ARGUMENT; + if (database == NULL || visitor == NULL) { + return TRAINLOG_STATUS_INVALID_ARGUMENT; + } status = trainlog_database_read_snapshot_begin(database); - if (status != TRAINLOG_STATUS_OK) return status; + if (status != TRAINLOG_STATUS_OK) { + return status; + } if (sqlite3_prepare_v2(database->connection, SQL, -1, &statement, NULL) != SQLITE_OK) { (void)trainlog_database_read_snapshot_end(database, false); return TRAINLOG_STATUS_DATABASE_ERROR; @@ -134,21 +156,27 @@ TrainlogStatus trainlog_database_scan_generation_history( /* CONTRACT: SQLite affinity cannot turn corrupt dynamic types into a * complete scientific result; validate every projected field before * the borrowed callback row is exposed. */ - for (index = 0; index <= 3; ++index) + for (index = 0; index <= 3; ++index) { if (sqlite3_column_type(statement, index) != SQLITE_TEXT) { - status = TRAINLOG_STATUS_DATABASE_ERROR; goto done; + status = TRAINLOG_STATUS_DATABASE_ERROR; + goto done; } - for (index = 4; index <= 5; ++index) + } + for (index = 4; index <= 5; ++index) { if (sqlite3_column_type(statement, index) != SQLITE_TEXT) { - status = TRAINLOG_STATUS_DATABASE_ERROR; goto done; + status = TRAINLOG_STATUS_DATABASE_ERROR; + goto done; } + } if (sqlite3_column_type(statement, 6) != SQLITE_NULL && sqlite3_column_type(statement, 6) != SQLITE_TEXT) { - status = TRAINLOG_STATUS_DATABASE_ERROR; goto done; + status = TRAINLOG_STATUS_DATABASE_ERROR; + goto done; } if (sqlite3_column_type(statement, 7) != SQLITE_TEXT || sqlite3_column_type(statement, 8) != SQLITE_INTEGER) { - status = TRAINLOG_STATUS_DATABASE_ERROR; goto done; + status = TRAINLOG_STATUS_DATABASE_ERROR; + goto done; } row.session_id = (const char *)sqlite3_column_text(statement, 0); row.started_at = (const char *)sqlite3_column_text(statement, 1); @@ -156,19 +184,36 @@ TrainlogStatus trainlog_database_scan_generation_history( row.exercise_id = (const char *)sqlite3_column_text(statement, 3); recording = sqlite3_column_text(statement, 4); tracking = sqlite3_column_text(statement, 5); - row.equipment_id = sqlite3_column_type(statement, 6) == SQLITE_NULL ? NULL : - (const char *)sqlite3_column_text(statement, 6); + row.equipment_id = sqlite3_column_type(statement, 6) == SQLITE_NULL + ? NULL + : (const char *)sqlite3_column_text(statement, 6); load = sqlite3_column_text(statement, 7); - if (strcmp((const char *)recording, "sets") == 0) row.recording_mode = TRAINLOG_RECORDING_SETS; - else if (strcmp((const char *)recording, "continuous") == 0) row.recording_mode = TRAINLOG_RECORDING_CONTINUOUS; - else { status = TRAINLOG_STATUS_DATABASE_ERROR; goto done; } - if (strcmp((const char *)tracking, "reps") == 0) row.tracking_mode = TRAINLOG_TRACKING_REPS; - else if (strcmp((const char *)tracking, "duration") == 0) row.tracking_mode = TRAINLOG_TRACKING_DURATION; - else { status = TRAINLOG_STATUS_DATABASE_ERROR; goto done; } - if (strcmp((const char *)load, "none") == 0) row.load_mode = TRAINLOG_LOAD_NONE; - else if (strcmp((const char *)load, "external") == 0) row.load_mode = TRAINLOG_LOAD_EXTERNAL; - else if (strcmp((const char *)load, "assistance") == 0) row.load_mode = TRAINLOG_LOAD_ASSISTANCE; - else { status = TRAINLOG_STATUS_DATABASE_ERROR; goto done; } + if (strcmp((const char *)recording, "sets") == 0) { + row.recording_mode = TRAINLOG_RECORDING_SETS; + } else if (strcmp((const char *)recording, "continuous") == 0) { + row.recording_mode = TRAINLOG_RECORDING_CONTINUOUS; + } else { + status = TRAINLOG_STATUS_DATABASE_ERROR; + goto done; + } + if (strcmp((const char *)tracking, "reps") == 0) { + row.tracking_mode = TRAINLOG_TRACKING_REPS; + } else if (strcmp((const char *)tracking, "duration") == 0) { + row.tracking_mode = TRAINLOG_TRACKING_DURATION; + } else { + status = TRAINLOG_STATUS_DATABASE_ERROR; + goto done; + } + if (strcmp((const char *)load, "none") == 0) { + row.load_mode = TRAINLOG_LOAD_NONE; + } else if (strcmp((const char *)load, "external") == 0) { + row.load_mode = TRAINLOG_LOAD_EXTERNAL; + } else if (strcmp((const char *)load, "assistance") == 0) { + row.load_mode = TRAINLOG_LOAD_ASSISTANCE; + } else { + status = TRAINLOG_STATUS_DATABASE_ERROR; + goto done; + } row.rest_seconds = sqlite3_column_int(statement, 8); row.has_target_sets = sqlite3_column_type(statement, 9) != SQLITE_NULL; row.has_target_reps = sqlite3_column_type(statement, 10) != SQLITE_NULL; @@ -176,12 +221,12 @@ TrainlogStatus trainlog_database_scan_generation_history( row.has_target_weight = sqlite3_column_type(statement, 12) != SQLITE_NULL; row.has_actual_set = sqlite3_column_type(statement, 13) != SQLITE_NULL; row.has_explicit_max = sqlite3_column_type(statement, 16) != SQLITE_NULL; - if (row.has_explicit_max && - ((sqlite3_column_type(statement, 16) != SQLITE_FLOAT && - sqlite3_column_type(statement, 16) != SQLITE_INTEGER) || - !isfinite(sqlite3_column_double(statement, 16)) || - sqlite3_column_double(statement, 16) <= 0.0)) { - status = TRAINLOG_STATUS_DATABASE_ERROR; goto done; + if (row.has_explicit_max && ((sqlite3_column_type(statement, 16) != SQLITE_FLOAT && + sqlite3_column_type(statement, 16) != SQLITE_INTEGER) || + !isfinite(sqlite3_column_double(statement, 16)) || + sqlite3_column_double(statement, 16) <= 0.0)) { + status = TRAINLOG_STATUS_DATABASE_ERROR; + goto done; } if (row.has_actual_set) { sqlite3_int64 position = sqlite3_column_int64(statement, 13); @@ -189,50 +234,62 @@ TrainlogStatus trainlog_database_scan_generation_history( (uint64_t)position > (uint64_t)SIZE_MAX || (sqlite3_column_type(statement, 14) != SQLITE_INTEGER && sqlite3_column_type(statement, 14) != SQLITE_NULL)) { - status = TRAINLOG_STATUS_DATABASE_ERROR; goto done; + status = TRAINLOG_STATUS_DATABASE_ERROR; + goto done; } row.set_position = (size_t)position; - row.repetitions = sqlite3_column_type(statement, 14) == SQLITE_NULL ? 0 : - sqlite3_column_int(statement, 14); + row.repetitions = sqlite3_column_type(statement, 14) == SQLITE_NULL + ? 0 + : sqlite3_column_int(statement, 14); row.has_weight = sqlite3_column_type(statement, 15) != SQLITE_NULL; if (row.has_weight) { if (sqlite3_column_type(statement, 15) != SQLITE_FLOAT && sqlite3_column_type(statement, 15) != SQLITE_INTEGER) { - status = TRAINLOG_STATUS_DATABASE_ERROR; goto done; + status = TRAINLOG_STATUS_DATABASE_ERROR; + goto done; } row.weight_kg = sqlite3_column_double(statement, 15); } } status = visitor(context, &row); - if (status != TRAINLOG_STATUS_OK) goto done; + if (status != TRAINLOG_STATUS_OK) { + goto done; + } } status = rc == SQLITE_DONE ? TRAINLOG_STATUS_OK : TRAINLOG_STATUS_DATABASE_ERROR; done: - if (sqlite3_finalize(statement) != SQLITE_OK && status == TRAINLOG_STATUS_OK) + if (sqlite3_finalize(statement) != SQLITE_OK && status == TRAINLOG_STATUS_OK) { status = TRAINLOG_STATUS_DATABASE_ERROR; + } { - TrainlogStatus end_status = trainlog_database_read_snapshot_end(database, - status == TRAINLOG_STATUS_OK); - if (status == TRAINLOG_STATUS_OK) status = end_status; + TrainlogStatus end_status = + trainlog_database_read_snapshot_end(database, status == TRAINLOG_STATUS_OK); + if (status == TRAINLOG_STATUS_OK) { + status = end_status; + } } return status; } -static TrainlogStatus lookup_exercise_row_id( - TrainlogDatabase *database, - const char *exercise_id, - sqlite3_int64 *output_row_id -); +static TrainlogStatus lookup_exercise_row_id(TrainlogDatabase *database, + const char *exercise_id, + sqlite3_int64 *output_row_id); -static bool custom_equipment_exists(TrainlogDatabase *database, const char *equipment_id) -{ +static bool custom_equipment_exists(TrainlogDatabase *database, const char *equipment_id) { sqlite3_stmt *statement = NULL; int rc; bool found = false; - if (database == NULL || equipment_id == NULL) return false; + if (database == NULL || equipment_id == NULL) { + return false; + } rc = sqlite3_prepare_v2(database->connection, - "SELECT 1 FROM custom_equipment WHERE equipment_id=?1;", -1, &statement, NULL); - if (rc != SQLITE_OK) return false; + "SELECT 1 FROM custom_equipment WHERE equipment_id=?1;", + -1, + &statement, + NULL); + if (rc != SQLITE_OK) { + return false; + } (void)sqlite3_bind_text(statement, 1, equipment_id, -1, SQLITE_TRANSIENT); found = sqlite3_step(statement) == SQLITE_ROW; (void)sqlite3_finalize(statement); @@ -240,13 +297,7 @@ static bool custom_equipment_exists(TrainlogDatabase *database, const char *equi } static void set_open_diagnostic( - char *output, - size_t capacity, - const char *operation, - sqlite3 *connection, - int sqlite_status -) -{ + char *output, size_t capacity, const char *operation, sqlite3 *connection, int sqlite_status) { const char *message; int extended_status = sqlite_status; @@ -263,23 +314,16 @@ static void set_open_diagnostic( } /* WHY: preserve the actionable SQLite result while the owning connection * is still open; rollback/close may replace its diagnostic state. */ - (void)snprintf( - output, - capacity, - "%s: SQLite rc=%d extended_rc=%d: %s", - operation, - sqlite_status, - extended_status, - message - ); + (void)snprintf(output, + capacity, + "%s: SQLite rc=%d extended_rc=%d: %s", + operation, + sqlite_status, + extended_status, + message); } -static void set_application_diagnostic( - char *output, - size_t capacity, - const char *message -) -{ +static void set_application_diagnostic(char *output, size_t capacity, const char *message) { if (output == NULL || capacity == 0U) { return; } @@ -446,15 +490,26 @@ static const char *const MIGRATE_V6_TO_V7_SQL = "position INTEGER NOT NULL, load_mode TEXT NOT NULL, rest_seconds INTEGER NOT NULL," "target_sets INTEGER, target_reps INTEGER, target_duration_seconds INTEGER," "target_weight_kg REAL, equipment_id TEXT, notes TEXT, UNIQUE(session_row_id,position));" - "INSERT INTO session_exercises(id,entry_id,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,equipment_id,notes) " - "SELECT id,'sxe_legacy_' || id,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,equipment_id,notes FROM session_exercises_v6;" + "INSERT INTO " + "session_exercises(id,entry_id,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,equipment_id,notes) " + "SELECT id,'sxe_legacy_' || " + "id,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,equipment_id,notes FROM " + "session_exercises_v6;" "ALTER TABLE performed_sets RENAME TO performed_sets_v6;" - "CREATE TABLE performed_sets (id INTEGER PRIMARY KEY,session_exercise_row_id INTEGER NOT NULL REFERENCES session_exercises(id) ON DELETE CASCADE,position INTEGER NOT NULL,reps INTEGER,duration_seconds INTEGER,weight_kg REAL,UNIQUE(session_exercise_row_id,position));" + "CREATE TABLE performed_sets (id INTEGER PRIMARY KEY,session_exercise_row_id INTEGER NOT NULL " + "REFERENCES session_exercises(id) ON DELETE CASCADE,position INTEGER NOT NULL,reps " + "INTEGER,duration_seconds INTEGER,weight_kg REAL,UNIQUE(session_exercise_row_id,position));" "INSERT INTO performed_sets SELECT * FROM performed_sets_v6;" "ALTER TABLE continuous_activity RENAME TO continuous_activity_v6;" - "CREATE TABLE continuous_activity (id INTEGER PRIMARY KEY,session_exercise_row_id INTEGER NOT NULL UNIQUE REFERENCES session_exercises(id) ON DELETE CASCADE,duration_seconds INTEGER NOT NULL,speed_kmh REAL,distance_km REAL);" + "CREATE TABLE continuous_activity (id INTEGER PRIMARY KEY,session_exercise_row_id INTEGER NOT " + "NULL UNIQUE REFERENCES session_exercises(id) ON DELETE CASCADE,duration_seconds INTEGER NOT " + "NULL,speed_kmh REAL,distance_km REAL);" "INSERT INTO continuous_activity SELECT * FROM continuous_activity_v6;" - "DROP TABLE performed_sets_v6;DROP TABLE continuous_activity_v6;DROP TABLE session_exercises_v6;" + "DROP TABLE performed_sets_v6;DROP TABLE continuous_activity_v6;DROP TABLE " + "session_exercises_v6;" "PRAGMA user_version = 7;COMMIT;PRAGMA foreign_keys = ON;"; /* CONTRACT: v8 stores only user-created equipment definitions. Canonical @@ -572,51 +627,138 @@ static const char *const MIGRATE_V11_TO_V12_SQL = * session/entry IDs and every child fact remain untouched. */ static const char *const MIGRATE_V12_TO_V13_SQL_A = "BEGIN IMMEDIATE;" - "ALTER TABLE exercises ADD COLUMN load_semantics TEXT CHECK(load_semantics IN ('none','external','assistance','bodyweight','cardio'));" + "ALTER TABLE exercises ADD COLUMN load_semantics TEXT CHECK(load_semantics IN " + "('none','external','assistance','bodyweight','cardio'));" "ALTER TABLE exercises ADD COLUMN machine_variant TEXT;" "ALTER TABLE exercises ADD COLUMN machine_provenance TEXT;" "ALTER TABLE exercises ADD COLUMN scientific_profile_id TEXT;" - "ALTER TABLE exercises ADD COLUMN science_state TEXT NOT NULL DEFAULT 'unresolved' CHECK(science_state IN ('resolved','unresolved'));" + "ALTER TABLE exercises ADD COLUMN science_state TEXT NOT NULL DEFAULT 'unresolved' " + "CHECK(science_state IN ('resolved','unresolved'));" "ALTER TABLE exercises ADD COLUMN legacy_equipment_id TEXT;" - "UPDATE exercises SET name='Hip Abduction',normalized_name='hip abduction',load_semantics='external',machine_variant='selectorized',scientific_profile_id='sp_hip_abduction_v1',science_state='resolved',legacy_equipment_id='hip_abduction' WHERE exercise_id='ex_7e7cf906-2214-4066-bcb7-c16382d83b3b' AND name='Abduction de hanche assise';" - "UPDATE exercises SET name='Leg Extension',normalized_name='leg extension',load_semantics='external',machine_variant='selectorized',scientific_profile_id='sp_leg_extension_v1',science_state='resolved',legacy_equipment_id='leg_extension' WHERE exercise_id='ex_1872246a-39ae-44dc-b58d-f87e90ca49ab' AND name='Extension de genou assise';" - "UPDATE exercises SET name='Back Extension',normalized_name='back extension',load_semantics='external',machine_variant='selectorized',scientific_profile_id='sp_back_extension_v1',science_state='resolved',legacy_equipment_id='back_extension' WHERE exercise_id='ex_474ec393-3efa-4aaa-8e08-1a0245ed7835' AND name='Extension du tronc';" - "UPDATE exercises SET name='Arm Curl',normalized_name='arm curl',load_semantics='external',machine_variant='selectorized',scientific_profile_id='sp_arm_curl_v1',science_state='resolved',legacy_equipment_id='arm_curl' WHERE exercise_id='ex_ec619fc2-4685-4044-873c-86764bd4a0fe' AND name='Flexion de coude à la machine';" - "UPDATE exercises SET name='Prone Leg Curl',normalized_name='prone leg curl',load_semantics='external',machine_variant='selectorized',scientific_profile_id='sp_prone_leg_curl_v1',science_state='resolved',legacy_equipment_id='prone_leg_curl' WHERE exercise_id='ex_d7398d9f-d928-4d2e-94e9-74e201da55c5' AND name='Flexion de genou couchée';" - "UPDATE exercises SET name='Rotary Torso',normalized_name='rotary torso',load_semantics='external',machine_variant='selectorized',scientific_profile_id='sp_rotary_torso_v1',science_state='resolved',legacy_equipment_id='rotary_torso' WHERE exercise_id='ex_1a34814c-2e46-40fc-b1f4-6d60b8e5a3e0' AND name='Rotation du tronc à la machine';"; + "UPDATE exercises SET name='Hip Abduction',normalized_name='hip " + "abduction',load_semantics='external',machine_variant='selectorized',scientific_profile_id='sp_" + "hip_abduction_v1',science_state='resolved',legacy_equipment_id='hip_abduction' WHERE " + "exercise_id='ex_7e7cf906-2214-4066-bcb7-c16382d83b3b' AND name='Abduction de hanche assise';" + "UPDATE exercises SET name='Leg Extension',normalized_name='leg " + "extension',load_semantics='external',machine_variant='selectorized',scientific_profile_id='sp_" + "leg_extension_v1',science_state='resolved',legacy_equipment_id='leg_extension' WHERE " + "exercise_id='ex_1872246a-39ae-44dc-b58d-f87e90ca49ab' AND name='Extension de genou assise';" + "UPDATE exercises SET name='Back Extension',normalized_name='back " + "extension',load_semantics='external',machine_variant='selectorized',scientific_profile_id='sp_" + "back_extension_v1',science_state='resolved',legacy_equipment_id='back_extension' WHERE " + "exercise_id='ex_474ec393-3efa-4aaa-8e08-1a0245ed7835' AND name='Extension du tronc';" + "UPDATE exercises SET name='Arm Curl',normalized_name='arm " + "curl',load_semantics='external',machine_variant='selectorized',scientific_profile_id='sp_arm_" + "curl_v1',science_state='resolved',legacy_equipment_id='arm_curl' WHERE " + "exercise_id='ex_ec619fc2-4685-4044-873c-86764bd4a0fe' AND name='Flexion de coude à la " + "machine';" + "UPDATE exercises SET name='Prone Leg Curl',normalized_name='prone leg " + "curl',load_semantics='external',machine_variant='selectorized',scientific_profile_id='sp_" + "prone_leg_curl_v1',science_state='resolved',legacy_equipment_id='prone_leg_curl' WHERE " + "exercise_id='ex_d7398d9f-d928-4d2e-94e9-74e201da55c5' AND name='Flexion de genou couchée';" + "UPDATE exercises SET name='Rotary Torso',normalized_name='rotary " + "torso',load_semantics='external',machine_variant='selectorized',scientific_profile_id='sp_" + "rotary_torso_v1',science_state='resolved',legacy_equipment_id='rotary_torso' WHERE " + "exercise_id='ex_1a34814c-2e46-40fc-b1f4-6d60b8e5a3e0' AND name='Rotation du tronc à la " + "machine';"; static const char *const MIGRATE_V12_TO_V13_SQL_B = - "UPDATE exercises SET name='Seated Row',normalized_name='seated row',load_semantics='external',machine_variant='selectorized',scientific_profile_id='sp_seated_row_v1',science_state='resolved',legacy_equipment_id='seated_row' WHERE exercise_id='ex_33f79331-871c-4eed-babe-346e53a99070' AND name='Tirage horizontal assis';" - "UPDATE exercises SET name='Diverging Seated Row',normalized_name='diverging seated row',load_semantics='external',machine_variant='diverging',scientific_profile_id='sp_seated_row_v1',science_state='resolved',legacy_equipment_id='diverging_seated_row' WHERE exercise_id='ex_1b0c6b8b-b05e-4e6f-8809-5f7d85d668de' AND name='Tirage horizontal divergent assis';" - "UPDATE exercises SET name='Diverging Lat Pulldown',normalized_name='diverging lat pulldown',load_semantics='external',machine_variant='diverging',scientific_profile_id='sp_vertical_pull_v1',science_state='resolved',legacy_equipment_id='diverging_lat_pulldown' WHERE exercise_id='ex_b4d1daf1-de4a-4016-abdf-487bf6014ce6' AND name='Tirage vertical divergent';" - "UPDATE exercises SET name='Lat Pull',normalized_name='lat pull',load_semantics='external',machine_variant='selectorized',scientific_profile_id='sp_vertical_pull_v1',science_state='resolved',legacy_equipment_id='lat_pull' WHERE exercise_id='ex_a72fa713-4b0e-431d-95e2-42d95beb77b1' AND name='Tirage vertical à la poulie';" - "UPDATE exercises SET name='Converging Shoulder Press',normalized_name='converging shoulder press',load_semantics='external',machine_variant='converging',scientific_profile_id='sp_shoulder_press_v1',science_state='resolved',legacy_equipment_id='converging_shoulder_press' WHERE exercise_id='ex_6dfc7ffd-8891-464e-a995-808baf1b0d7b' AND name='Développé épaules convergent';" - "UPDATE exercises SET name='Seated Leg Curl',normalized_name='seated leg curl',load_semantics='external',machine_variant='selectorized',scientific_profile_id='sp_seated_leg_curl_v1',science_state='resolved',legacy_equipment_id='seated_leg_curl' WHERE exercise_id='ex_a1ef5047-b44b-4c64-a6ed-c7a3bc13b163' AND name='Flexion de genou assise';" - "UPDATE exercises SET name='Leg Press',normalized_name='leg press',load_semantics='external',machine_variant='selectorized',scientific_profile_id='sp_leg_press_v1',science_state='resolved',legacy_equipment_id='leg_press' WHERE exercise_id='ex_b432623f-bfe9-4daf-a653-60ec7fdffbde' AND name IN ('Presse à cuisses','Leg press');" - "UPDATE exercises SET name='Rear Delt',normalized_name='rear delt',load_semantics='external',machine_variant='rear_delt',scientific_profile_id='sp_rear_delt_v1',science_state='resolved',legacy_equipment_id='rear_delt_pec_fly' WHERE exercise_id='ex_4cd2433e-80b1-478a-b8df-73fc6ef80962' AND name='Écarté inversé à la machine';" - "INSERT INTO exercises(exercise_id,name,normalized_name,tracking_mode,recording_mode,data_fields,load_semantics,machine_variant,scientific_profile_id,science_state,legacy_equipment_id) VALUES" - "('ex_0e26c06f-a458-40a4-be20-4ed219ede30d','Plate Loaded Leg Press','plate loaded leg press','reps','sets',0,'external','plate_loaded','sp_leg_press_v1','resolved','plate_loaded_leg_press')," - "('ex_f01d2a46-6984-4dec-8934-4d82fca6dfc2','Treadmill','treadmill','duration','continuous',3,'cardio','treadmill',NULL,'unresolved','treadmill')," - "('ex_54dcdfd2-280d-4c2b-ae6b-c6089a985eee','Pec Fly','pec fly','reps','sets',0,'external','pec_fly','sp_pec_fly_v1','resolved','rear_delt_pec_fly')," - "('ex_58b8dfbc-92b2-4783-a449-9947a42480b8','Chin Assist','chin assist','reps','sets',0,'assistance','assisted','sp_assisted_chin_v1','resolved','assisted_dip_chin_machine')," - "('ex_44358a7b-09c8-4992-8f70-7eee4e99bbdf','Dip Assist','dip assist','reps','sets',0,'assistance','assisted','sp_assisted_dip_v1','resolved','assisted_dip_chin_machine');"; + "UPDATE exercises SET name='Seated Row',normalized_name='seated " + "row',load_semantics='external',machine_variant='selectorized',scientific_profile_id='sp_" + "seated_row_v1',science_state='resolved',legacy_equipment_id='seated_row' WHERE " + "exercise_id='ex_33f79331-871c-4eed-babe-346e53a99070' AND name='Tirage horizontal assis';" + "UPDATE exercises SET name='Diverging Seated Row',normalized_name='diverging seated " + "row',load_semantics='external',machine_variant='diverging',scientific_profile_id='sp_seated_" + "row_v1',science_state='resolved',legacy_equipment_id='diverging_seated_row' WHERE " + "exercise_id='ex_1b0c6b8b-b05e-4e6f-8809-5f7d85d668de' AND name='Tirage horizontal divergent " + "assis';" + "UPDATE exercises SET name='Diverging Lat Pulldown',normalized_name='diverging lat " + "pulldown',load_semantics='external',machine_variant='diverging',scientific_profile_id='sp_" + "vertical_pull_v1',science_state='resolved',legacy_equipment_id='diverging_lat_pulldown' WHERE " + "exercise_id='ex_b4d1daf1-de4a-4016-abdf-487bf6014ce6' AND name='Tirage vertical divergent';" + "UPDATE exercises SET name='Lat Pull',normalized_name='lat " + "pull',load_semantics='external',machine_variant='selectorized',scientific_profile_id='sp_" + "vertical_pull_v1',science_state='resolved',legacy_equipment_id='lat_pull' WHERE " + "exercise_id='ex_a72fa713-4b0e-431d-95e2-42d95beb77b1' AND name='Tirage vertical à la poulie';" + "UPDATE exercises SET name='Converging Shoulder Press',normalized_name='converging shoulder " + "press',load_semantics='external',machine_variant='converging',scientific_profile_id='sp_" + "shoulder_press_v1',science_state='resolved',legacy_equipment_id='converging_shoulder_press' " + "WHERE exercise_id='ex_6dfc7ffd-8891-464e-a995-808baf1b0d7b' AND name='Développé épaules " + "convergent';" + "UPDATE exercises SET name='Seated Leg Curl',normalized_name='seated leg " + "curl',load_semantics='external',machine_variant='selectorized',scientific_profile_id='sp_" + "seated_leg_curl_v1',science_state='resolved',legacy_equipment_id='seated_leg_curl' WHERE " + "exercise_id='ex_a1ef5047-b44b-4c64-a6ed-c7a3bc13b163' AND name='Flexion de genou assise';" + "UPDATE exercises SET name='Leg Press',normalized_name='leg " + "press',load_semantics='external',machine_variant='selectorized',scientific_profile_id='sp_leg_" + "press_v1',science_state='resolved',legacy_equipment_id='leg_press' WHERE " + "exercise_id='ex_b432623f-bfe9-4daf-a653-60ec7fdffbde' AND name IN ('Presse à cuisses','Leg " + "press');" + "UPDATE exercises SET name='Rear Delt',normalized_name='rear " + "delt',load_semantics='external',machine_variant='rear_delt',scientific_profile_id='sp_rear_" + "delt_v1',science_state='resolved',legacy_equipment_id='rear_delt_pec_fly' WHERE " + "exercise_id='ex_4cd2433e-80b1-478a-b8df-73fc6ef80962' AND name='Écarté inversé à la machine';" + "INSERT INTO " + "exercises(exercise_id,name,normalized_name,tracking_mode,recording_mode,data_fields,load_" + "semantics,machine_variant,scientific_profile_id,science_state,legacy_equipment_id) VALUES" + "('ex_0e26c06f-a458-40a4-be20-4ed219ede30d','Plate Loaded Leg Press','plate loaded leg " + "press','reps','sets',0,'external','plate_loaded','sp_leg_press_v1','resolved','plate_loaded_" + "leg_press')," + "('ex_f01d2a46-6984-4dec-8934-4d82fca6dfc2','Treadmill','treadmill','duration','continuous',3,'" + "cardio','treadmill',NULL,'unresolved','treadmill')," + "('ex_54dcdfd2-280d-4c2b-ae6b-c6089a985eee','Pec Fly','pec " + "fly','reps','sets',0,'external','pec_fly','sp_pec_fly_v1','resolved','rear_delt_pec_fly')," + "('ex_58b8dfbc-92b2-4783-a449-9947a42480b8','Chin Assist','chin " + "assist','reps','sets',0,'assistance','assisted','sp_assisted_chin_v1','resolved','assisted_" + "dip_chin_machine')," + "('ex_44358a7b-09c8-4992-8f70-7eee4e99bbdf','Dip Assist','dip " + "assist','reps','sets',0,'assistance','assisted','sp_assisted_dip_v1','resolved','assisted_dip_" + "chin_machine');"; static const char *const MIGRATE_V12_TO_V13_SQL_C = - "INSERT INTO exercise_body_zones(exercise_row_id,zone_id,role) SELECT n.id,z.zone_id,z.role FROM exercises n JOIN exercises o ON o.exercise_id='ex_b432623f-bfe9-4daf-a653-60ec7fdffbde' JOIN exercise_body_zones z ON z.exercise_row_id=o.id WHERE n.exercise_id='ex_0e26c06f-a458-40a4-be20-4ed219ede30d';" - "INSERT INTO exercise_body_zones(exercise_row_id,zone_id,role) SELECT id,'chest','primary' FROM exercises WHERE exercise_id='ex_54dcdfd2-280d-4c2b-ae6b-c6089a985eee';" - "INSERT INTO exercise_body_zones(exercise_row_id,zone_id,role) SELECT id,'shoulders','secondary' FROM exercises WHERE exercise_id='ex_54dcdfd2-280d-4c2b-ae6b-c6089a985eee';" - "INSERT INTO exercise_body_zones(exercise_row_id,zone_id,role) SELECT id,'back','primary' FROM exercises WHERE exercise_id='ex_58b8dfbc-92b2-4783-a449-9947a42480b8';" - "INSERT INTO exercise_body_zones(exercise_row_id,zone_id,role) SELECT id,'arms','secondary' FROM exercises WHERE exercise_id='ex_58b8dfbc-92b2-4783-a449-9947a42480b8';" - "INSERT INTO exercise_body_zones(exercise_row_id,zone_id,role) SELECT id,'arms','primary' FROM exercises WHERE exercise_id='ex_44358a7b-09c8-4992-8f70-7eee4e99bbdf';" - "INSERT INTO exercise_body_zones(exercise_row_id,zone_id,role) SELECT id,'chest','secondary' FROM exercises WHERE exercise_id='ex_44358a7b-09c8-4992-8f70-7eee4e99bbdf';" - "INSERT INTO exercise_body_zones(exercise_row_id,zone_id,role) SELECT id,'shoulders','secondary' FROM exercises WHERE exercise_id='ex_44358a7b-09c8-4992-8f70-7eee4e99bbdf';" - "INSERT INTO exercise_body_zone_sync(exercise_row_id,synced_state) SELECT id,'thighs|glutes' FROM exercises WHERE exercise_id='ex_0e26c06f-a458-40a4-be20-4ed219ede30d';" - "INSERT INTO exercise_body_zone_sync(exercise_row_id,synced_state) SELECT id,'chest|shoulders' FROM exercises WHERE exercise_id='ex_54dcdfd2-280d-4c2b-ae6b-c6089a985eee';" - "INSERT INTO exercise_body_zone_sync(exercise_row_id,synced_state) SELECT id,'back|arms' FROM exercises WHERE exercise_id='ex_58b8dfbc-92b2-4783-a449-9947a42480b8';" - "INSERT INTO exercise_body_zone_sync(exercise_row_id,synced_state) SELECT id,'arms|chest,shoulders' FROM exercises WHERE exercise_id='ex_44358a7b-09c8-4992-8f70-7eee4e99bbdf';" - "UPDATE session_exercises SET exercise_row_id=(SELECT id FROM exercises WHERE exercise_id='ex_0e26c06f-a458-40a4-be20-4ed219ede30d') WHERE entry_id='sxe_draft_legacy_7' AND equipment_id='plate_loaded_leg_press' AND exercise_row_id=(SELECT id FROM exercises WHERE exercise_id='ex_b432623f-bfe9-4daf-a653-60ec7fdffbde');" - "UPDATE session_exercises SET exercise_row_id=(SELECT id FROM exercises WHERE exercise_id='ex_f01d2a46-6984-4dec-8934-4d82fca6dfc2') WHERE entry_id IN ('sxe_093c1331-beaa-4b69-91b3-240292709be6','sxe_f25142c8-455e-4346-9bfc-31d0989e275d','sxe_f2242691-ba6c-48a0-b938-a1f744375d75','sxe_9f8882f4-069e-49d5-8216-19d16b467e4a') AND equipment_id='treadmill' AND exercise_row_id=(SELECT id FROM exercises WHERE exercise_id='ex_b1e6ffc6-75b5-45ff-a3c0-e7433c58013d');" - "UPDATE exercises SET load_semantics=NULL,machine_variant=NULL,machine_provenance=NULL,scientific_profile_id=NULL,science_state='unresolved',legacy_equipment_id=NULL WHERE exercise_id='ex_617007f9-7420-4408-91b9-8ffb77900f13';" + "INSERT INTO exercise_body_zones(exercise_row_id,zone_id,role) SELECT n.id,z.zone_id,z.role " + "FROM exercises n JOIN exercises o ON o.exercise_id='ex_b432623f-bfe9-4daf-a653-60ec7fdffbde' " + "JOIN exercise_body_zones z ON z.exercise_row_id=o.id WHERE " + "n.exercise_id='ex_0e26c06f-a458-40a4-be20-4ed219ede30d';" + "INSERT INTO exercise_body_zones(exercise_row_id,zone_id,role) SELECT id,'chest','primary' " + "FROM exercises WHERE exercise_id='ex_54dcdfd2-280d-4c2b-ae6b-c6089a985eee';" + "INSERT INTO exercise_body_zones(exercise_row_id,zone_id,role) SELECT " + "id,'shoulders','secondary' FROM exercises WHERE " + "exercise_id='ex_54dcdfd2-280d-4c2b-ae6b-c6089a985eee';" + "INSERT INTO exercise_body_zones(exercise_row_id,zone_id,role) SELECT id,'back','primary' FROM " + "exercises WHERE exercise_id='ex_58b8dfbc-92b2-4783-a449-9947a42480b8';" + "INSERT INTO exercise_body_zones(exercise_row_id,zone_id,role) SELECT id,'arms','secondary' " + "FROM exercises WHERE exercise_id='ex_58b8dfbc-92b2-4783-a449-9947a42480b8';" + "INSERT INTO exercise_body_zones(exercise_row_id,zone_id,role) SELECT id,'arms','primary' FROM " + "exercises WHERE exercise_id='ex_44358a7b-09c8-4992-8f70-7eee4e99bbdf';" + "INSERT INTO exercise_body_zones(exercise_row_id,zone_id,role) SELECT id,'chest','secondary' " + "FROM exercises WHERE exercise_id='ex_44358a7b-09c8-4992-8f70-7eee4e99bbdf';" + "INSERT INTO exercise_body_zones(exercise_row_id,zone_id,role) SELECT " + "id,'shoulders','secondary' FROM exercises WHERE " + "exercise_id='ex_44358a7b-09c8-4992-8f70-7eee4e99bbdf';" + "INSERT INTO exercise_body_zone_sync(exercise_row_id,synced_state) SELECT id,'thighs|glutes' " + "FROM exercises WHERE exercise_id='ex_0e26c06f-a458-40a4-be20-4ed219ede30d';" + "INSERT INTO exercise_body_zone_sync(exercise_row_id,synced_state) SELECT id,'chest|shoulders' " + "FROM exercises WHERE exercise_id='ex_54dcdfd2-280d-4c2b-ae6b-c6089a985eee';" + "INSERT INTO exercise_body_zone_sync(exercise_row_id,synced_state) SELECT id,'back|arms' FROM " + "exercises WHERE exercise_id='ex_58b8dfbc-92b2-4783-a449-9947a42480b8';" + "INSERT INTO exercise_body_zone_sync(exercise_row_id,synced_state) SELECT " + "id,'arms|chest,shoulders' FROM exercises WHERE " + "exercise_id='ex_44358a7b-09c8-4992-8f70-7eee4e99bbdf';" + "UPDATE session_exercises SET exercise_row_id=(SELECT id FROM exercises WHERE " + "exercise_id='ex_0e26c06f-a458-40a4-be20-4ed219ede30d') WHERE entry_id='sxe_draft_legacy_7' " + "AND equipment_id='plate_loaded_leg_press' AND exercise_row_id=(SELECT id FROM exercises WHERE " + "exercise_id='ex_b432623f-bfe9-4daf-a653-60ec7fdffbde');" + "UPDATE session_exercises SET exercise_row_id=(SELECT id FROM exercises WHERE " + "exercise_id='ex_f01d2a46-6984-4dec-8934-4d82fca6dfc2') WHERE entry_id IN " + "('sxe_093c1331-beaa-4b69-91b3-240292709be6','sxe_f25142c8-455e-4346-9bfc-31d0989e275d','sxe_" + "f2242691-ba6c-48a0-b938-a1f744375d75','sxe_9f8882f4-069e-49d5-8216-19d16b467e4a') AND " + "equipment_id='treadmill' AND exercise_row_id=(SELECT id FROM exercises WHERE " + "exercise_id='ex_b1e6ffc6-75b5-45ff-a3c0-e7433c58013d');" + "UPDATE exercises SET " + "load_semantics=NULL,machine_variant=NULL,machine_provenance=NULL,scientific_profile_id=NULL," + "science_state='unresolved',legacy_equipment_id=NULL WHERE " + "exercise_id='ex_617007f9-7420-4408-91b9-8ffb77900f13';" "PRAGMA user_version=13;COMMIT;"; /* WHY: subjective observations have their own immutable chronology and must @@ -631,12 +773,14 @@ static const char *const MIGRATE_V13_TO_V14_SQL = "id INTEGER PRIMARY KEY,feedback_id TEXT NOT NULL UNIQUE," "session_exercise_row_id INTEGER NOT NULL REFERENCES session_exercises(id) ON DELETE CASCADE," "observed_at TEXT NOT NULL,raw_text TEXT NOT NULL CHECK(length(trim(raw_text))>0));" - "CREATE INDEX exercise_feedback_timeline ON exercise_feedback(session_exercise_row_id,observed_at,feedback_id);" + "CREATE INDEX exercise_feedback_timeline ON " + "exercise_feedback(session_exercise_row_id,observed_at,feedback_id);" "CREATE TABLE session_followups(" "id INTEGER PRIMARY KEY,followup_id TEXT NOT NULL UNIQUE," "session_row_id INTEGER NOT NULL REFERENCES sessions(id) ON DELETE CASCADE," "observed_at TEXT NOT NULL,raw_text TEXT NOT NULL CHECK(length(trim(raw_text))>0));" - "CREATE INDEX session_followups_timeline ON session_followups(session_row_id,observed_at,followup_id);" + "CREATE INDEX session_followups_timeline ON " + "session_followups(session_row_id,observed_at,followup_id);" "PRAGMA user_version=14;COMMIT;"; /* WHY: wording corrections must not overwrite the original wording. @@ -647,21 +791,25 @@ static const char *const MIGRATE_V13_TO_V14_SQL = static const char *const MIGRATE_V14_TO_V15_SQL = "BEGIN IMMEDIATE;" "CREATE TABLE exercise_feedback_revisions(" - "revision_id TEXT PRIMARY KEY,feedback_id TEXT NOT NULL REFERENCES exercise_feedback(feedback_id) ON DELETE CASCADE," + "revision_id TEXT PRIMARY KEY,feedback_id TEXT NOT NULL REFERENCES " + "exercise_feedback(feedback_id) ON DELETE CASCADE," "created_at TEXT NOT NULL,raw_text TEXT NOT NULL CHECK(length(trim(raw_text))>0));" - "CREATE INDEX exercise_feedback_revisions_current ON exercise_feedback_revisions(feedback_id,created_at,revision_id);" + "CREATE INDEX exercise_feedback_revisions_current ON " + "exercise_feedback_revisions(feedback_id,created_at,revision_id);" "CREATE TABLE session_followup_revisions(" - "revision_id TEXT PRIMARY KEY,followup_id TEXT NOT NULL REFERENCES session_followups(followup_id) ON DELETE CASCADE," + "revision_id TEXT PRIMARY KEY,followup_id TEXT NOT NULL REFERENCES " + "session_followups(followup_id) ON DELETE CASCADE," "created_at TEXT NOT NULL,raw_text TEXT NOT NULL CHECK(length(trim(raw_text))>0));" - "CREATE INDEX session_followup_revisions_current ON session_followup_revisions(followup_id,created_at,revision_id);" + "CREATE INDEX session_followup_revisions_current ON " + "session_followup_revisions(followup_id,created_at,revision_id);" "INSERT INTO exercise_feedback_revisions(revision_id,feedback_id,created_at,raw_text) " "SELECT 'fr0_'||feedback_id,feedback_id,observed_at,raw_text FROM exercise_feedback;" "INSERT INTO session_followup_revisions(revision_id,followup_id,created_at,raw_text) " "SELECT 'fr0_'||followup_id,followup_id,observed_at,raw_text FROM session_followups;" "PRAGMA user_version=15;COMMIT;"; -static TrainlogStatus read_single_int_pragma( - TrainlogDatabase *database, const char *sql, int *output); +static TrainlogStatus +read_single_int_pragma(TrainlogDatabase *database, const char *sql, int *output); static TrainlogStatus execute_sql(TrainlogDatabase *database, const char *sql); /* WHY: catalogue profiles describe future capture and may change, while an @@ -670,28 +818,30 @@ static TrainlogStatus execute_sql(TrainlogDatabase *database, const char *sql); * mode exactly once before any later profile edit. INVARIANT: every existing * row/child/stable identity remains untouched and version 16 is published only * after all snapshots are valid and foreign keys remain satisfied. */ -static TrainlogStatus migrate_v15_to_v16(TrainlogDatabase *database) -{ +static TrainlogStatus migrate_v15_to_v16(TrainlogDatabase *database) { sqlite3_stmt *statement = NULL; int version = -1; int invalid = -1; int source_count = -1; int migrated_count = -1; int rc; - if (read_single_int_pragma(database, "PRAGMA user_version;", &version) != - TRAINLOG_STATUS_OK || version != 15) + if (read_single_int_pragma(database, "PRAGMA user_version;", &version) != TRAINLOG_STATUS_OK || + version != 15) { return TRAINLOG_STATUS_SCHEMA_UNSUPPORTED; + } if (read_single_int_pragma(database, - "SELECT COUNT(*) FROM session_exercises;", &source_count) != - TRAINLOG_STATUS_OK) + "SELECT COUNT(*) FROM session_exercises;", + &source_count) != TRAINLOG_STATUS_OK) { return TRAINLOG_STATUS_DATABASE_ERROR; + } /* SQLite cannot add a NOT NULL column without a lasting default. Rebuild * the parent table with FK enforcement temporarily disabled so child rows * are neither cascaded nor retargeted to a temporary table name. The * transaction plus the explicit foreign_key_check preserves atomicity. */ if (execute_sql(database, "PRAGMA foreign_keys=OFF;") != TRAINLOG_STATUS_OK || execute_sql(database, "BEGIN IMMEDIATE;") != TRAINLOG_STATUS_OK || - execute_sql(database, + execute_sql( + database, "CREATE TABLE session_exercises_v16(" "id INTEGER PRIMARY KEY,entry_id TEXT NOT NULL UNIQUE," "session_row_id INTEGER NOT NULL REFERENCES sessions(id) ON DELETE CASCADE," @@ -708,31 +858,50 @@ static TrainlogStatus migrate_v15_to_v16(TrainlogDatabase *database) "target_weight_kg,equipment_id,notes,tracking_mode) " "SELECT se.id,se.entry_id,se.session_row_id,se.exercise_row_id,se.recording_mode," "se.data_fields,se.position,se.load_mode,se.rest_seconds,se.target_sets,se.target_reps," - "se.target_duration_seconds,se.target_weight_kg,se.equipment_id,se.notes,e.tracking_mode " + "se.target_duration_seconds,se.target_weight_kg,se.equipment_id,se.notes,e.tracking_" + "mode " "FROM session_exercises se JOIN exercises e ON e.id=se.exercise_row_id;" "DROP TABLE session_exercises;" "ALTER TABLE session_exercises_v16 RENAME TO session_exercises;") != - TRAINLOG_STATUS_OK) goto rollback; + TRAINLOG_STATUS_OK) { + goto rollback; + } rc = sqlite3_prepare_v2(database->connection, - "SELECT COUNT(*) FROM session_exercises se LEFT JOIN exercises e " - "ON e.id=se.exercise_row_id WHERE e.id IS NULL OR " - "se.tracking_mode NOT IN('reps','duration') OR " - "se.tracking_mode<>e.tracking_mode;", -1, &statement, NULL); - if (rc != SQLITE_OK || sqlite3_step(statement) != SQLITE_ROW) goto rollback; + "SELECT COUNT(*) FROM session_exercises se LEFT JOIN exercises e " + "ON e.id=se.exercise_row_id WHERE e.id IS NULL OR " + "se.tracking_mode NOT IN('reps','duration') OR " + "se.tracking_mode<>e.tracking_mode;", + -1, + &statement, + NULL); + if (rc != SQLITE_OK || sqlite3_step(statement) != SQLITE_ROW) { + goto rollback; + } invalid = sqlite3_column_int(statement, 0); - if (sqlite3_finalize(statement) != SQLITE_OK) { statement = NULL; goto rollback; } + if (sqlite3_finalize(statement) != SQLITE_OK) { + statement = NULL; + goto rollback; + } statement = NULL; if (read_single_int_pragma(database, - "SELECT COUNT(*) FROM session_exercises;", &migrated_count) != - TRAINLOG_STATUS_OK || migrated_count != source_count || invalid != 0 || - read_single_int_pragma(database, "SELECT COUNT(*) FROM pragma_foreign_key_check;", &invalid) != TRAINLOG_STATUS_OK || - invalid != 0 || execute_sql(database, - "PRAGMA user_version=16;COMMIT;") != TRAINLOG_STATUS_OK) goto rollback; - if (execute_sql(database, "PRAGMA foreign_keys=ON;") != TRAINLOG_STATUS_OK) + "SELECT COUNT(*) FROM session_exercises;", + &migrated_count) != TRAINLOG_STATUS_OK || + migrated_count != source_count || invalid != 0 || + read_single_int_pragma(database, + "SELECT COUNT(*) FROM pragma_foreign_key_check;", + &invalid) != TRAINLOG_STATUS_OK || + invalid != 0 || + execute_sql(database, "PRAGMA user_version=16;COMMIT;") != TRAINLOG_STATUS_OK) { + goto rollback; + } + if (execute_sql(database, "PRAGMA foreign_keys=ON;") != TRAINLOG_STATUS_OK) { return TRAINLOG_STATUS_DATABASE_ERROR; + } return TRAINLOG_STATUS_OK; rollback: - if (statement != NULL) (void)sqlite3_finalize(statement); + if (statement != NULL) { + (void)sqlite3_finalize(statement); + } (void)sqlite3_exec(database->connection, "ROLLBACK;", NULL, NULL, NULL); (void)sqlite3_exec(database->connection, "PRAGMA foreign_keys=ON;", NULL, NULL, NULL); return TRAINLOG_STATUS_DATABASE_ERROR; @@ -750,26 +919,46 @@ static const char *const MIGRATE_V16_TO_V17_SQL = "exercise_row_id INTEGER PRIMARY KEY REFERENCES exercises(id) ON DELETE CASCADE," "revision_id TEXT NOT NULL,parent_revision_id TEXT," "legacy_seed INTEGER NOT NULL CHECK(legacy_seed IN(0,1)));" - "INSERT INTO exercise_profile_state(exercise_row_id,revision_id,parent_revision_id,legacy_seed) " + "INSERT INTO " + "exercise_profile_state(exercise_row_id,revision_id,parent_revision_id,legacy_seed) " "SELECT id,'pr_legacy_v1',NULL,1 FROM exercises;" "CREATE TABLE exercise_profile_revisions(" "exercise_row_id INTEGER NOT NULL REFERENCES exercises(id) ON DELETE CASCADE," "revision_id TEXT NOT NULL,parent_revision_id TEXT,recording_mode TEXT NOT NULL," - "tracking_mode TEXT NOT NULL,data_fields INTEGER NOT NULL,legacy_seed INTEGER NOT NULL CHECK(legacy_seed IN(0,1))," + "tracking_mode TEXT NOT NULL,data_fields INTEGER NOT NULL,legacy_seed INTEGER NOT NULL " + "CHECK(legacy_seed IN(0,1))," "PRIMARY KEY(exercise_row_id,revision_id));" - "INSERT INTO exercise_profile_revisions SELECT id,'pr_legacy_v1',NULL,recording_mode,tracking_mode,data_fields,1 FROM exercises;" + "INSERT INTO exercise_profile_revisions SELECT " + "id,'pr_legacy_v1',NULL,recording_mode,tracking_mode,data_fields,1 FROM exercises;" "CREATE TRIGGER exercise_profile_state_insert AFTER INSERT ON exercises BEGIN " - "INSERT INTO exercise_profile_state(exercise_row_id,revision_id,parent_revision_id,legacy_seed) " - "VALUES(NEW.id,trainlog_profile_revision('pr_legacy_v1',NEW.recording_mode,NEW.tracking_mode,NEW.data_fields),'pr_legacy_v1',0);" - "INSERT INTO exercise_profile_revisions VALUES(NEW.id,'pr_legacy_v1',NULL,NEW.recording_mode,NEW.tracking_mode,NEW.data_fields,1);" - "INSERT INTO exercise_profile_revisions VALUES(NEW.id,trainlog_profile_revision('pr_legacy_v1',NEW.recording_mode,NEW.tracking_mode,NEW.data_fields),'pr_legacy_v1',NEW.recording_mode,NEW.tracking_mode,NEW.data_fields,0);END;" - "CREATE TRIGGER exercise_profile_state_update AFTER UPDATE OF recording_mode,tracking_mode,data_fields ON exercises " - "WHEN (OLD.recording_mode<>NEW.recording_mode OR OLD.tracking_mode<>NEW.tracking_mode OR OLD.data_fields<>NEW.data_fields) " - "AND NOT EXISTS(SELECT 1 FROM exercise_profile_state s JOIN exercise_profile_revisions r ON r.exercise_row_id=s.exercise_row_id AND r.revision_id=s.revision_id WHERE s.exercise_row_id=NEW.id AND r.recording_mode=NEW.recording_mode AND r.tracking_mode=NEW.tracking_mode AND r.data_fields=NEW.data_fields) BEGIN " - "SELECT CASE WHEN (SELECT COUNT(*) FROM exercise_profile_revisions WHERE exercise_row_id=NEW.id)>=32 THEN RAISE(ABORT,'profile revision history limit') END;" - "INSERT OR IGNORE INTO exercise_profile_revisions SELECT NEW.id,s.revision_id,s.parent_revision_id,OLD.recording_mode,OLD.tracking_mode,OLD.data_fields,s.legacy_seed FROM exercise_profile_state s WHERE s.exercise_row_id=NEW.id;" - "UPDATE exercise_profile_state SET parent_revision_id=revision_id,revision_id=trainlog_profile_revision(revision_id,NEW.recording_mode,NEW.tracking_mode,NEW.data_fields),legacy_seed=0 WHERE exercise_row_id=NEW.id;" - "INSERT INTO exercise_profile_revisions SELECT NEW.id,s.revision_id,s.parent_revision_id,NEW.recording_mode,NEW.tracking_mode,NEW.data_fields,0 FROM exercise_profile_state s WHERE s.exercise_row_id=NEW.id;END;" + "INSERT INTO " + "exercise_profile_state(exercise_row_id,revision_id,parent_revision_id,legacy_seed) " + "VALUES(NEW.id,trainlog_profile_revision('pr_legacy_v1',NEW.recording_mode,NEW.tracking_mode," + "NEW.data_fields),'pr_legacy_v1',0);" + "INSERT INTO exercise_profile_revisions " + "VALUES(NEW.id,'pr_legacy_v1',NULL,NEW.recording_mode,NEW.tracking_mode,NEW.data_fields,1);" + "INSERT INTO exercise_profile_revisions " + "VALUES(NEW.id,trainlog_profile_revision('pr_legacy_v1',NEW.recording_mode,NEW.tracking_mode," + "NEW.data_fields),'pr_legacy_v1',NEW.recording_mode,NEW.tracking_mode,NEW.data_fields,0);END;" + "CREATE TRIGGER exercise_profile_state_update AFTER UPDATE OF " + "recording_mode,tracking_mode,data_fields ON exercises " + "WHEN (OLD.recording_mode<>NEW.recording_mode OR OLD.tracking_mode<>NEW.tracking_mode OR " + "OLD.data_fields<>NEW.data_fields) " + "AND NOT EXISTS(SELECT 1 FROM exercise_profile_state s JOIN exercise_profile_revisions r ON " + "r.exercise_row_id=s.exercise_row_id AND r.revision_id=s.revision_id WHERE " + "s.exercise_row_id=NEW.id AND r.recording_mode=NEW.recording_mode AND " + "r.tracking_mode=NEW.tracking_mode AND r.data_fields=NEW.data_fields) BEGIN " + "SELECT CASE WHEN (SELECT COUNT(*) FROM exercise_profile_revisions WHERE " + "exercise_row_id=NEW.id)>=32 THEN RAISE(ABORT,'profile revision history limit') END;" + "INSERT OR IGNORE INTO exercise_profile_revisions SELECT " + "NEW.id,s.revision_id,s.parent_revision_id,OLD.recording_mode,OLD.tracking_mode,OLD.data_" + "fields,s.legacy_seed FROM exercise_profile_state s WHERE s.exercise_row_id=NEW.id;" + "UPDATE exercise_profile_state SET " + "parent_revision_id=revision_id,revision_id=trainlog_profile_revision(revision_id,NEW." + "recording_mode,NEW.tracking_mode,NEW.data_fields),legacy_seed=0 WHERE exercise_row_id=NEW.id;" + "INSERT INTO exercise_profile_revisions SELECT " + "NEW.id,s.revision_id,s.parent_revision_id,NEW.recording_mode,NEW.tracking_mode,NEW.data_" + "fields,0 FROM exercise_profile_state s WHERE s.exercise_row_id=NEW.id;END;" "PRAGMA user_version=17;COMMIT;"; /* WHY: generated plans are untrusted inbox data and must never masquerade as @@ -786,10 +975,13 @@ static const char *const MIGRATE_V17_TO_V18_SQL = "CHECK(archive_status IN('pending','archived','failed'))," "archived_at TEXT,archive_error TEXT,published_at TEXT);" "CREATE TABLE ai_session_draft_entries(" - "id INTEGER PRIMARY KEY,draft_row_id INTEGER NOT NULL REFERENCES ai_session_drafts(id) ON DELETE CASCADE," - "entry_id TEXT NOT NULL UNIQUE,position INTEGER NOT NULL CHECK(position>=0),exercise_row_id INTEGER NOT NULL REFERENCES exercises(id) ON DELETE RESTRICT," + "id INTEGER PRIMARY KEY,draft_row_id INTEGER NOT NULL REFERENCES ai_session_drafts(id) ON " + "DELETE CASCADE," + "entry_id TEXT NOT NULL UNIQUE,position INTEGER NOT NULL CHECK(position>=0),exercise_row_id " + "INTEGER NOT NULL REFERENCES exercises(id) ON DELETE RESTRICT," "source_exercise_id TEXT NOT NULL,recording_mode TEXT NOT NULL CHECK(recording_mode='sets')," - "tracking_mode TEXT NOT NULL CHECK(tracking_mode IN('reps','duration')),data_fields INTEGER NOT NULL," + "tracking_mode TEXT NOT NULL CHECK(tracking_mode IN('reps','duration')),data_fields INTEGER " + "NOT NULL," "equipment_id TEXT,load_mode TEXT NOT NULL CHECK(load_mode IN('none','external'))," "target_sets INTEGER NOT NULL CHECK(target_sets BETWEEN 1 AND 99)," "target_reps INTEGER,target_duration_seconds INTEGER,target_weight_kg REAL," @@ -820,13 +1012,16 @@ static const char *const MIGRATE_V18_TO_V19_SQL = "CREATE TABLE sync_note_state(" "owner_kind TEXT NOT NULL,owner_id TEXT NOT NULL,revision_id TEXT NOT NULL," "PRIMARY KEY(owner_kind,owner_id)," - "FOREIGN KEY(owner_kind,owner_id,revision_id) REFERENCES sync_note_revisions(owner_kind,owner_id,revision_id));" + "FOREIGN KEY(owner_kind,owner_id,revision_id) REFERENCES " + "sync_note_revisions(owner_kind,owner_id,revision_id));" "CREATE TABLE execution_drafts(" - "session_id TEXT PRIMARY KEY,session_type TEXT NOT NULL CHECK(session_type IN('training','max_test'))," + "session_id TEXT PRIMARY KEY,session_type TEXT NOT NULL CHECK(session_type " + "IN('training','max_test'))," "source_session_id TEXT,started_at TEXT,revision_id TEXT NOT NULL,parent_revision_id TEXT," "state TEXT NOT NULL CHECK(state IN('active','pending')),payload_json TEXT NOT NULL);" "CREATE TABLE execution_draft_revisions(" - "session_id TEXT NOT NULL,revision_id TEXT NOT NULL,parent_revision_id TEXT,payload_json TEXT NOT NULL," + "session_id TEXT NOT NULL,revision_id TEXT NOT NULL,parent_revision_id TEXT,payload_json TEXT " + "NOT NULL," "PRIMARY KEY(session_id,revision_id));" "CREATE TABLE execution_draft_finalizations(" "session_id TEXT PRIMARY KEY,final_revision_id TEXT NOT NULL,finalized_at TEXT NOT NULL);" @@ -862,12 +1057,31 @@ static const char *const MIGRATE_V19_TO_V20_SQL = * generation, acknowledgement, consumption, ancestry, or causal operation. */ static const char *const MIGRATE_V20_TO_V21_SQL = "BEGIN IMMEDIATE;" - "CREATE TABLE sync_peer_identity(singleton INTEGER PRIMARY KEY CHECK(singleton=1),peer_id TEXT NOT NULL UNIQUE,kind TEXT NOT NULL CHECK(kind IN('desktop','android')));" - "CREATE TABLE sync_generations(generation_id TEXT PRIMARY KEY,run_id TEXT NOT NULL,producer_peer_id TEXT NOT NULL,consumer_peer_id TEXT NOT NULL,producer_kind TEXT NOT NULL,generated_at TEXT NOT NULL,parent_generation_id TEXT,manifest_sha256 TEXT NOT NULL,status TEXT NOT NULL CHECK(status IN('captured','published','waiting_acknowledgement','acknowledged','rejected')),manifest_json TEXT NOT NULL,staging_path TEXT NOT NULL,acknowledged_at TEXT);" - "CREATE TABLE sync_generation_artifacts(generation_id TEXT NOT NULL REFERENCES sync_generations(generation_id) ON DELETE CASCADE,logical_name TEXT NOT NULL,format TEXT NOT NULL,version INTEGER NOT NULL,filename TEXT NOT NULL,size_bytes INTEGER NOT NULL,sha256 TEXT NOT NULL,required INTEGER NOT NULL CHECK(required IN(0,1)),PRIMARY KEY(generation_id,logical_name),UNIQUE(generation_id,filename));" - "CREATE TABLE sync_consumed_generations(generation_id TEXT PRIMARY KEY,run_id TEXT NOT NULL,producer_peer_id TEXT NOT NULL,consumer_peer_id TEXT NOT NULL,parent_generation_id TEXT,manifest_sha256 TEXT NOT NULL,consumed_at TEXT NOT NULL,result TEXT NOT NULL CHECK(result IN('consumed','rejected')),durability TEXT NOT NULL,diagnostic TEXT NOT NULL,ack_json TEXT NOT NULL,UNIQUE(producer_peer_id,generation_id));" - "CREATE TABLE sync_acknowledgements(ack_id TEXT PRIMARY KEY,generation_id TEXT NOT NULL,run_id TEXT NOT NULL,producer_peer_id TEXT NOT NULL,consumer_peer_id TEXT NOT NULL,manifest_sha256 TEXT NOT NULL,result TEXT NOT NULL CHECK(result IN('consumed','rejected')),durability TEXT NOT NULL,created_at TEXT NOT NULL,diagnostic TEXT NOT NULL,payload_sha256 TEXT NOT NULL);" - "CREATE TABLE sync_causal_publications(operation_id TEXT NOT NULL REFERENCES sync_causal_operations(operation_id),generation_id TEXT NOT NULL,first_emission INTEGER NOT NULL CHECK(first_emission IN(0,1)),PRIMARY KEY(operation_id,generation_id));" + "CREATE TABLE sync_peer_identity(singleton INTEGER PRIMARY KEY CHECK(singleton=1),peer_id TEXT " + "NOT NULL UNIQUE,kind TEXT NOT NULL CHECK(kind IN('desktop','android')));" + "CREATE TABLE sync_generations(generation_id TEXT PRIMARY KEY,run_id TEXT NOT " + "NULL,producer_peer_id TEXT NOT NULL,consumer_peer_id TEXT NOT NULL,producer_kind TEXT NOT " + "NULL,generated_at TEXT NOT NULL,parent_generation_id TEXT,manifest_sha256 TEXT NOT " + "NULL,status TEXT NOT NULL CHECK(status " + "IN('captured','published','waiting_acknowledgement','acknowledged','rejected')),manifest_json " + "TEXT NOT NULL,staging_path TEXT NOT NULL,acknowledged_at TEXT);" + "CREATE TABLE sync_generation_artifacts(generation_id TEXT NOT NULL REFERENCES " + "sync_generations(generation_id) ON DELETE CASCADE,logical_name TEXT NOT NULL,format TEXT NOT " + "NULL,version INTEGER NOT NULL,filename TEXT NOT NULL,size_bytes INTEGER NOT NULL,sha256 TEXT " + "NOT NULL,required INTEGER NOT NULL CHECK(required IN(0,1)),PRIMARY " + "KEY(generation_id,logical_name),UNIQUE(generation_id,filename));" + "CREATE TABLE sync_consumed_generations(generation_id TEXT PRIMARY KEY,run_id TEXT NOT " + "NULL,producer_peer_id TEXT NOT NULL,consumer_peer_id TEXT NOT NULL,parent_generation_id " + "TEXT,manifest_sha256 TEXT NOT NULL,consumed_at TEXT NOT NULL,result TEXT NOT NULL " + "CHECK(result IN('consumed','rejected')),durability TEXT NOT NULL,diagnostic TEXT NOT " + "NULL,ack_json TEXT NOT NULL,UNIQUE(producer_peer_id,generation_id));" + "CREATE TABLE sync_acknowledgements(ack_id TEXT PRIMARY KEY,generation_id TEXT NOT NULL,run_id " + "TEXT NOT NULL,producer_peer_id TEXT NOT NULL,consumer_peer_id TEXT NOT NULL,manifest_sha256 " + "TEXT NOT NULL,result TEXT NOT NULL CHECK(result IN('consumed','rejected')),durability TEXT " + "NOT NULL,created_at TEXT NOT NULL,diagnostic TEXT NOT NULL,payload_sha256 TEXT NOT NULL);" + "CREATE TABLE sync_causal_publications(operation_id TEXT NOT NULL REFERENCES " + "sync_causal_operations(operation_id),generation_id TEXT NOT NULL,first_emission INTEGER NOT " + "NULL CHECK(first_emission IN(0,1)),PRIMARY KEY(operation_id,generation_id));" "PRAGMA user_version=21;COMMIT;"; /* WHY: acknowledged payload copies cannot consume active admission forever, @@ -894,30 +1108,41 @@ static const char *const MIGRATE_V22_TO_V23_SQL = "CREATE TABLE session_preparations(" "preparation_id TEXT PRIMARY KEY,current_revision_id TEXT NOT NULL,created_at TEXT NOT NULL," "updated_at TEXT NOT NULL,editing_state TEXT NOT NULL CHECK(editing_state IN('draft','ready'))," - "delivery_state TEXT NOT NULL DEFAULT 'local' CHECK(delivery_state IN('local','pending','acknowledged','remote_unknown','cancelled'))," + "delivery_state TEXT NOT NULL DEFAULT 'local' CHECK(delivery_state " + "IN('local','pending','acknowledged','remote_unknown','cancelled'))," "source_proposal_id TEXT,source_payload_sha256 TEXT);" "CREATE TABLE session_preparation_revisions(" - "revision_id TEXT PRIMARY KEY,preparation_id TEXT NOT NULL REFERENCES session_preparations(preparation_id) ON DELETE RESTRICT," - "parent_revision_id TEXT,title TEXT NOT NULL,session_type TEXT NOT NULL CHECK(session_type IN('training','max_test'))," + "revision_id TEXT PRIMARY KEY,preparation_id TEXT NOT NULL REFERENCES " + "session_preparations(preparation_id) ON DELETE RESTRICT," + "parent_revision_id TEXT,title TEXT NOT NULL,session_type TEXT NOT NULL CHECK(session_type " + "IN('training','max_test'))," "planned_for TEXT,notes TEXT,created_at TEXT NOT NULL," - "CHECK(length(CAST(title AS BLOB))<=200),CHECK(notes IS NULL OR length(CAST(notes AS BLOB))<=4000)," + "CHECK(length(CAST(title AS BLOB))<=200),CHECK(notes IS NULL OR length(CAST(notes AS " + "BLOB))<=4000)," "UNIQUE(preparation_id,revision_id));" "CREATE TABLE session_preparation_entries(" - "revision_id TEXT NOT NULL REFERENCES session_preparation_revisions(revision_id) ON DELETE RESTRICT," + "revision_id TEXT NOT NULL REFERENCES session_preparation_revisions(revision_id) ON DELETE " + "RESTRICT," "entry_id TEXT NOT NULL,position INTEGER NOT NULL CHECK(position>=0),exercise_id TEXT NOT NULL," "equipment_id TEXT,recording_mode TEXT NOT NULL CHECK(recording_mode IN('sets','continuous'))," - "tracking_mode TEXT NOT NULL CHECK(tracking_mode IN('reps','duration')),data_fields INTEGER NOT NULL," - "load_mode TEXT NOT NULL CHECK(load_mode IN('none','external','assistance')),rest_seconds INTEGER NOT NULL CHECK(rest_seconds BETWEEN 0 AND 86400)," - "target_sets INTEGER,target_reps INTEGER,target_duration_seconds INTEGER,target_weight_kg REAL,notes TEXT," + "tracking_mode TEXT NOT NULL CHECK(tracking_mode IN('reps','duration')),data_fields INTEGER " + "NOT NULL," + "load_mode TEXT NOT NULL CHECK(load_mode IN('none','external','assistance')),rest_seconds " + "INTEGER NOT NULL CHECK(rest_seconds BETWEEN 0 AND 86400)," + "target_sets INTEGER,target_reps INTEGER,target_duration_seconds INTEGER,target_weight_kg " + "REAL,notes TEXT," "PRIMARY KEY(revision_id,entry_id),UNIQUE(revision_id,position)," "CHECK(notes IS NULL OR length(CAST(notes AS BLOB))<=4000));" "CREATE INDEX session_preparation_entries_exercise ON session_preparation_entries(exercise_id);" "CREATE TABLE session_preparation_requests(" - "request_id TEXT PRIMARY KEY,command TEXT NOT NULL,preparation_id TEXT NOT NULL,revision_id TEXT NOT NULL," + "request_id TEXT PRIMARY KEY,command TEXT NOT NULL,preparation_id TEXT NOT NULL,revision_id " + "TEXT NOT NULL," "response_json TEXT NOT NULL,created_at TEXT NOT NULL);" "CREATE TABLE session_preparation_deliveries(" - "delivery_id TEXT PRIMARY KEY,preparation_id TEXT NOT NULL REFERENCES session_preparations(preparation_id)," - "revision_id TEXT NOT NULL REFERENCES session_preparation_revisions(revision_id),execution_session_id TEXT NOT NULL UNIQUE," + "delivery_id TEXT PRIMARY KEY,preparation_id TEXT NOT NULL REFERENCES " + "session_preparations(preparation_id)," + "revision_id TEXT NOT NULL REFERENCES " + "session_preparation_revisions(revision_id),execution_session_id TEXT NOT NULL UNIQUE," "state TEXT NOT NULL CHECK(state IN('pending','acknowledged','remote_unknown','cancelled'))," "created_at TEXT NOT NULL,generation_id TEXT,acknowledged_at TEXT," "UNIQUE(preparation_id,revision_id,delivery_id));" @@ -939,20 +1164,106 @@ static const char *const MIGRATE_V23_TO_V24_SQL = "UNIQUE(preparation_id,revision_id));" "PRAGMA user_version=24;COMMIT;"; +/* WHY: reusable programs are planning definitions, not performed sessions or + * executable drafts. CONTRACT: v25 stores imported Program V1 documents and + * their ordered definitions separately, and records optional provenance on a + * preparation created from one definition. INVARIANT: migration invents no + * program, preparation, delivery, draft, or performed workout. */ +static const char *const MIGRATE_V24_TO_V25_SQL = + "BEGIN IMMEDIATE;" + "CREATE TABLE IF NOT EXISTS programs(" + "program_id TEXT PRIMARY KEY,title TEXT NOT NULL,note TEXT," + "state TEXT NOT NULL CHECK(state IN('active','archived'))," + "start_date TEXT,end_date TEXT,created_at TEXT NOT NULL,updated_at TEXT NOT NULL," + "revision_id TEXT NOT NULL,source_format TEXT NOT NULL,source_version INTEGER NOT NULL," + "source_payload_sha256 TEXT NOT NULL," + "CHECK(length(CAST(title AS BLOB)) BETWEEN 1 AND 200)," + "CHECK(note IS NULL OR length(CAST(note AS BLOB))<=4000)," + "CHECK(length(source_payload_sha256)=64),UNIQUE(program_id,source_payload_sha256));" + "CREATE TABLE IF NOT EXISTS program_sessions(" + "program_session_id TEXT PRIMARY KEY," + "program_id TEXT NOT NULL REFERENCES programs(program_id) ON DELETE RESTRICT," + "position INTEGER NOT NULL CHECK(position>=0),title TEXT NOT NULL," + "session_type TEXT NOT NULL CHECK(session_type IN('training','max_test'))," + "planned_for TEXT,note TEXT," + "CHECK(length(CAST(title AS BLOB)) BETWEEN 1 AND 200)," + "CHECK(note IS NULL OR length(CAST(note AS BLOB))<=4000)," + "UNIQUE(program_id,position));" + "CREATE TABLE IF NOT EXISTS program_session_entries(" + "program_session_id TEXT NOT NULL REFERENCES program_sessions(program_session_id) " + "ON DELETE RESTRICT," + "entry_id TEXT NOT NULL,position INTEGER NOT NULL CHECK(position>=0)," + "exercise_id TEXT NOT NULL,equipment_id TEXT," + "recording_mode TEXT NOT NULL CHECK(recording_mode IN('sets','continuous'))," + "tracking_mode TEXT NOT NULL CHECK(tracking_mode IN('reps','duration'))," + "data_fields INTEGER NOT NULL," + "load_mode TEXT NOT NULL CHECK(load_mode IN('none','external','assistance'))," + "rest_seconds INTEGER NOT NULL CHECK(rest_seconds BETWEEN 0 AND 86400)," + "target_sets INTEGER,target_reps INTEGER,target_duration_seconds INTEGER," + "target_weight_kg REAL,notes TEXT," + "CHECK(notes IS NULL OR length(CAST(notes AS BLOB))<=4000)," + "PRIMARY KEY(program_session_id,entry_id),UNIQUE(program_session_id,position));" + "CREATE INDEX IF NOT EXISTS program_session_entries_exercise " + "ON program_session_entries(exercise_id);" + "CREATE TABLE IF NOT EXISTS program_requests(" + "request_id TEXT PRIMARY KEY,command TEXT NOT NULL,program_id TEXT NOT NULL," + "response_json TEXT NOT NULL,created_at TEXT NOT NULL);" + "PRAGMA user_version=25;COMMIT;"; + +/* CONTRACT: migration fixtures may retain additive v25 columns while + * deliberately lowering user_version. Add each provenance column only when + * absent; a genuine v24 database still receives both before opening returns. */ +static TrainlogStatus ensure_v25_preparation_provenance(TrainlogDatabase *database) { + sqlite3_stmt *statement = NULL; + bool has_program_id = false; + bool has_program_session_id = false; + int result; + + result = sqlite3_prepare_v2( + database->connection, "PRAGMA table_info(session_preparations);", -1, &statement, NULL); + if (result != SQLITE_OK) { + return TRAINLOG_STATUS_DATABASE_ERROR; + } + while ((result = sqlite3_step(statement)) == SQLITE_ROW) { + const char *name = (const char *)sqlite3_column_text(statement, 1); + if (name != NULL && strcmp(name, "source_program_id") == 0) { + has_program_id = true; + } + if (name != NULL && strcmp(name, "source_program_session_id") == 0) { + has_program_session_id = true; + } + } + if (result != SQLITE_DONE || sqlite3_finalize(statement) != SQLITE_OK) { + return TRAINLOG_STATUS_DATABASE_ERROR; + } + if (!has_program_id && + execute_sql(database, + "ALTER TABLE session_preparations ADD COLUMN source_program_id TEXT " + "REFERENCES programs(program_id);") != TRAINLOG_STATUS_OK) { + return TRAINLOG_STATUS_DATABASE_ERROR; + } + if (!has_program_session_id && + execute_sql(database, + "ALTER TABLE session_preparations ADD COLUMN source_program_session_id TEXT " + "REFERENCES program_sessions(program_session_id);") != TRAINLOG_STATUS_OK) { + return TRAINLOG_STATUS_DATABASE_ERROR; + } + return TRAINLOG_STATUS_OK; +} + /* CONTRACT: v18 was already deployed before bounded draft publication was * added. Keep its version number and add only the nullable lifecycle cursor; * NULL deliberately means retryable/not yet published. */ -static TrainlogStatus ensure_v18_ai_draft_publication_state( - TrainlogDatabase *database -) -{ +static TrainlogStatus ensure_v18_ai_draft_publication_state(TrainlogDatabase *database) { sqlite3_stmt *statement = NULL; int rc; bool found = false; - rc = sqlite3_prepare_v2(database->connection, - "PRAGMA table_info(ai_session_drafts);", -1, &statement, NULL); - if (rc != SQLITE_OK) return TRAINLOG_STATUS_DATABASE_ERROR; + rc = sqlite3_prepare_v2( + database->connection, "PRAGMA table_info(ai_session_drafts);", -1, &statement, NULL); + if (rc != SQLITE_OK) { + return TRAINLOG_STATUS_DATABASE_ERROR; + } while ((rc = sqlite3_step(statement)) == SQLITE_ROW) { const unsigned char *name = sqlite3_column_text(statement, 1); if (name != NULL && strcmp((const char *)name, "published_at") == 0) { @@ -964,18 +1275,21 @@ static TrainlogStatus ensure_v18_ai_draft_publication_state( (void)sqlite3_finalize(statement); return TRAINLOG_STATUS_DATABASE_ERROR; } - if (sqlite3_finalize(statement) != SQLITE_OK) + if (sqlite3_finalize(statement) != SQLITE_OK) { return TRAINLOG_STATUS_DATABASE_ERROR; - if (!found && execute_sql(database, - "ALTER TABLE ai_session_drafts ADD COLUMN published_at TEXT;") != - TRAINLOG_STATUS_OK) return TRAINLOG_STATUS_DATABASE_ERROR; + } + if (!found && + execute_sql(database, "ALTER TABLE ai_session_drafts ADD COLUMN published_at TEXT;") != + TRAINLOG_STATUS_OK) { + return TRAINLOG_STATUS_DATABASE_ERROR; + } /* INVARIANT: older v18 tables may still declare a cascading FK. This * guard runs before it and makes replay identity durable for both shapes. */ return execute_sql(database, - "CREATE TRIGGER IF NOT EXISTS ai_session_draft_import_identity_guard " - "BEFORE DELETE ON ai_session_drafts WHEN EXISTS(SELECT 1 FROM " - "ai_session_draft_imports i WHERE i.draft_id=OLD.draft_id) BEGIN " - "SELECT RAISE(ABORT,'AI draft import identity is permanent');END;"); + "CREATE TRIGGER IF NOT EXISTS ai_session_draft_import_identity_guard " + "BEFORE DELETE ON ai_session_drafts WHEN EXISTS(SELECT 1 FROM " + "ai_session_draft_imports i WHERE i.draft_id=OLD.draft_id) BEGIN " + "SELECT RAISE(ABORT,'AI draft import identity is permanent');END;"); } /* WHY: the published-unreleased v17 prototype may already exist locally. @@ -988,42 +1302,65 @@ static const char *const ENSURE_V17_PROFILE_REVISIONS_SQL = "CREATE TABLE IF NOT EXISTS exercise_profile_revisions(" "exercise_row_id INTEGER NOT NULL REFERENCES exercises(id) ON DELETE CASCADE," "revision_id TEXT NOT NULL,parent_revision_id TEXT,recording_mode TEXT NOT NULL," - "tracking_mode TEXT NOT NULL,data_fields INTEGER NOT NULL,legacy_seed INTEGER NOT NULL CHECK(legacy_seed IN(0,1))," + "tracking_mode TEXT NOT NULL,data_fields INTEGER NOT NULL,legacy_seed INTEGER NOT NULL " + "CHECK(legacy_seed IN(0,1))," "PRIMARY KEY(exercise_row_id,revision_id));" "INSERT OR IGNORE INTO exercise_profile_revisions " - "SELECT e.id,s.revision_id,s.parent_revision_id,e.recording_mode,e.tracking_mode,e.data_fields,s.legacy_seed " + "SELECT " + "e.id,s.revision_id,s.parent_revision_id,e.recording_mode,e.tracking_mode,e.data_fields,s." + "legacy_seed " "FROM exercises e JOIN exercise_profile_state s ON s.exercise_row_id=e.id;" "CREATE TRIGGER exercise_profile_state_insert AFTER INSERT ON exercises BEGIN " - "INSERT INTO exercise_profile_state VALUES(NEW.id,trainlog_profile_revision('pr_legacy_v1',NEW.recording_mode,NEW.tracking_mode,NEW.data_fields),'pr_legacy_v1',0);" - "INSERT INTO exercise_profile_revisions VALUES(NEW.id,'pr_legacy_v1',NULL,NEW.recording_mode,NEW.tracking_mode,NEW.data_fields,1);" - "INSERT INTO exercise_profile_revisions VALUES(NEW.id,trainlog_profile_revision('pr_legacy_v1',NEW.recording_mode,NEW.tracking_mode,NEW.data_fields),'pr_legacy_v1',NEW.recording_mode,NEW.tracking_mode,NEW.data_fields,0);END;" - "CREATE TRIGGER exercise_profile_state_update AFTER UPDATE OF recording_mode,tracking_mode,data_fields ON exercises " - "WHEN (OLD.recording_mode<>NEW.recording_mode OR OLD.tracking_mode<>NEW.tracking_mode OR OLD.data_fields<>NEW.data_fields) " - "AND NOT EXISTS(SELECT 1 FROM exercise_profile_state s JOIN exercise_profile_revisions r ON r.exercise_row_id=s.exercise_row_id AND r.revision_id=s.revision_id WHERE s.exercise_row_id=NEW.id AND r.recording_mode=NEW.recording_mode AND r.tracking_mode=NEW.tracking_mode AND r.data_fields=NEW.data_fields) BEGIN " - "SELECT CASE WHEN (SELECT COUNT(*) FROM exercise_profile_revisions WHERE exercise_row_id=NEW.id)>=32 THEN RAISE(ABORT,'profile revision history limit') END;" - "INSERT OR IGNORE INTO exercise_profile_revisions SELECT NEW.id,s.revision_id,s.parent_revision_id,OLD.recording_mode,OLD.tracking_mode,OLD.data_fields,s.legacy_seed FROM exercise_profile_state s WHERE s.exercise_row_id=NEW.id;" - "UPDATE exercise_profile_state SET parent_revision_id=revision_id,revision_id=trainlog_profile_revision(revision_id,NEW.recording_mode,NEW.tracking_mode,NEW.data_fields),legacy_seed=0 WHERE exercise_row_id=NEW.id;" - "INSERT INTO exercise_profile_revisions SELECT NEW.id,s.revision_id,s.parent_revision_id,NEW.recording_mode,NEW.tracking_mode,NEW.data_fields,0 FROM exercise_profile_state s WHERE s.exercise_row_id=NEW.id;END;"; + "INSERT INTO exercise_profile_state " + "VALUES(NEW.id,trainlog_profile_revision('pr_legacy_v1',NEW.recording_mode,NEW.tracking_mode," + "NEW.data_fields),'pr_legacy_v1',0);" + "INSERT INTO exercise_profile_revisions " + "VALUES(NEW.id,'pr_legacy_v1',NULL,NEW.recording_mode,NEW.tracking_mode,NEW.data_fields,1);" + "INSERT INTO exercise_profile_revisions " + "VALUES(NEW.id,trainlog_profile_revision('pr_legacy_v1',NEW.recording_mode,NEW.tracking_mode," + "NEW.data_fields),'pr_legacy_v1',NEW.recording_mode,NEW.tracking_mode,NEW.data_fields,0);END;" + "CREATE TRIGGER exercise_profile_state_update AFTER UPDATE OF " + "recording_mode,tracking_mode,data_fields ON exercises " + "WHEN (OLD.recording_mode<>NEW.recording_mode OR OLD.tracking_mode<>NEW.tracking_mode OR " + "OLD.data_fields<>NEW.data_fields) " + "AND NOT EXISTS(SELECT 1 FROM exercise_profile_state s JOIN exercise_profile_revisions r ON " + "r.exercise_row_id=s.exercise_row_id AND r.revision_id=s.revision_id WHERE " + "s.exercise_row_id=NEW.id AND r.recording_mode=NEW.recording_mode AND " + "r.tracking_mode=NEW.tracking_mode AND r.data_fields=NEW.data_fields) BEGIN " + "SELECT CASE WHEN (SELECT COUNT(*) FROM exercise_profile_revisions WHERE " + "exercise_row_id=NEW.id)>=32 THEN RAISE(ABORT,'profile revision history limit') END;" + "INSERT OR IGNORE INTO exercise_profile_revisions SELECT " + "NEW.id,s.revision_id,s.parent_revision_id,OLD.recording_mode,OLD.tracking_mode,OLD.data_" + "fields,s.legacy_seed FROM exercise_profile_state s WHERE s.exercise_row_id=NEW.id;" + "UPDATE exercise_profile_state SET " + "parent_revision_id=revision_id,revision_id=trainlog_profile_revision(revision_id,NEW." + "recording_mode,NEW.tracking_mode,NEW.data_fields),legacy_seed=0 WHERE exercise_row_id=NEW.id;" + "INSERT INTO exercise_profile_revisions SELECT " + "NEW.id,s.revision_id,s.parent_revision_id,NEW.recording_mode,NEW.tracking_mode,NEW.data_" + "fields,0 FROM exercise_profile_state s WHERE s.exercise_row_id=NEW.id;END;"; /* CONTRACT: normalize only the exact unpublished pr1 current-state shape. * Validation precedes every mutation, and the caller owns one transaction * covering compatibility-table setup, conversion, and trigger replacement. */ -static TrainlogStatus normalize_v17_prototype_profiles(TrainlogDatabase *database) -{ +static TrainlogStatus normalize_v17_prototype_profiles(TrainlogDatabase *database) { int invalid = 0; static const char *const VALIDATION_SQL = "SELECT (" "(SELECT COUNT(*) FROM exercise_profile_state s JOIN exercises e ON e.id=s.exercise_row_id " - " WHERE s.revision_id GLOB 'pr1*' AND (s.parent_revision_id IS NOT NULL OR s.legacy_seed<>1 OR " + " WHERE s.revision_id GLOB 'pr1*' AND (s.parent_revision_id IS NOT NULL OR " + "s.legacy_seed<>1 OR " " trainlog_pr1_valid(s.revision_id,e.recording_mode,e.tracking_mode,e.data_fields)<>1)) +" "(SELECT COUNT(*) FROM exercise_profile_revisions r LEFT JOIN exercise_profile_state s " " ON s.exercise_row_id=r.exercise_row_id AND s.revision_id=r.revision_id " - " WHERE r.revision_id GLOB 'pr1*' AND (s.exercise_row_id IS NULL OR r.parent_revision_id IS NOT NULL OR " - " r.legacy_seed<>1 OR r.recording_mode<>(SELECT recording_mode FROM exercises WHERE id=r.exercise_row_id) OR " + " WHERE r.revision_id GLOB 'pr1*' AND (s.exercise_row_id IS NULL OR r.parent_revision_id " + "IS NOT NULL OR " + " r.legacy_seed<>1 OR r.recording_mode<>(SELECT recording_mode FROM exercises WHERE " + "id=r.exercise_row_id) OR " " r.tracking_mode<>(SELECT tracking_mode FROM exercises WHERE id=r.exercise_row_id) OR " " r.data_fields<>(SELECT data_fields FROM exercises WHERE id=r.exercise_row_id))) +" "(SELECT COUNT(*) FROM exercise_profile_state s WHERE s.revision_id GLOB 'pr1*' AND " - " (SELECT COUNT(*) FROM exercise_profile_revisions r WHERE r.exercise_row_id=s.exercise_row_id)<>1)" + " (SELECT COUNT(*) FROM exercise_profile_revisions r WHERE " + "r.exercise_row_id=s.exercise_row_id)<>1)" ");"; static const char *const NORMALIZE_SQL = "DELETE FROM exercise_profile_revisions WHERE revision_id GLOB 'pr1*';" @@ -1032,7 +1369,9 @@ static TrainlogStatus normalize_v17_prototype_profiles(TrainlogDatabase *databas "FROM exercises e JOIN exercise_profile_state s ON s.exercise_row_id=e.id " "WHERE s.revision_id GLOB 'pr1*';" "INSERT INTO exercise_profile_revisions " - "SELECT e.id,trainlog_profile_revision('pr_legacy_v1',e.recording_mode,e.tracking_mode,e.data_fields)," + "SELECT " + "e.id,trainlog_profile_revision('pr_legacy_v1',e.recording_mode,e.tracking_mode,e.data_" + "fields)," "'pr_legacy_v1',e.recording_mode,e.tracking_mode,e.data_fields,0 " "FROM exercises e JOIN exercise_profile_state s ON s.exercise_row_id=e.id " "WHERE s.revision_id GLOB 'pr1*';" @@ -1043,7 +1382,9 @@ static TrainlogStatus normalize_v17_prototype_profiles(TrainlogDatabase *databas "(SELECT data_fields FROM exercises WHERE id=exercise_row_id)) " "WHERE revision_id GLOB 'pr1*';"; if (read_single_int_pragma(database, VALIDATION_SQL, &invalid) != TRAINLOG_STATUS_OK || - invalid != 0) return TRAINLOG_STATUS_DATABASE_ERROR; + invalid != 0) { + return TRAINLOG_STATUS_DATABASE_ERROR; + } return execute_sql(database, NORMALIZE_SQL); } @@ -1329,11 +1670,7 @@ static const char *const MIGRATE_V4_TO_V5_SQL_B = "PRAGMA user_version = 5;" "COMMIT;"; -static TrainlogStatus execute_sql( - TrainlogDatabase *database, - const char *sql -) -{ +static TrainlogStatus execute_sql(TrainlogDatabase *database, const char *sql) { if (database == NULL || database->connection == NULL || sql == NULL) { return TRAINLOG_STATUS_INVALID_ARGUMENT; } @@ -1345,16 +1682,12 @@ static TrainlogStatus execute_sql( return TRAINLOG_STATUS_OK; } -static bool bind_initial_body_zone( - sqlite3_stmt *statement, - const char *exercise_id, - const char *zone_id, - const char *role -) -{ +static bool bind_initial_body_zone(sqlite3_stmt *statement, + const char *exercise_id, + const char *zone_id, + const char *role) { int rc; - if (sqlite3_reset(statement) != SQLITE_OK || - sqlite3_clear_bindings(statement) != SQLITE_OK || + if (sqlite3_reset(statement) != SQLITE_OK || sqlite3_clear_bindings(statement) != SQLITE_OK || sqlite3_bind_text(statement, 1, exercise_id, -1, SQLITE_TRANSIENT) != SQLITE_OK || sqlite3_bind_text(statement, 2, zone_id, -1, SQLITE_TRANSIENT) != SQLITE_OK || sqlite3_bind_text(statement, 3, role, -1, SQLITE_STATIC) != SQLITE_OK) { @@ -1364,8 +1697,7 @@ static bool bind_initial_body_zone( return rc == SQLITE_DONE; } -static TrainlogStatus migrate_v10_to_v11(TrainlogDatabase *database) -{ +static TrainlogStatus migrate_v10_to_v11(TrainlogDatabase *database) { static const char *const INSERT_SQL = "INSERT INTO exercise_body_zones(exercise_row_id,zone_id,role) " "SELECT id,?2,?3 FROM exercises WHERE exercise_id=?1;"; @@ -1384,8 +1716,8 @@ static TrainlogStatus migrate_v10_to_v11(TrainlogDatabase *database) trainlog_body_zone_initial_mapping_at(index); const char *cursor; if (mapping == NULL || - !bind_initial_body_zone(statement, mapping->exercise_id, - mapping->primary_zone_id, "primary")) { + !bind_initial_body_zone( + statement, mapping->exercise_id, mapping->primary_zone_id, "primary")) { goto rollback; } cursor = mapping->secondary_zone_ids; @@ -1393,11 +1725,12 @@ static TrainlogStatus migrate_v10_to_v11(TrainlogDatabase *database) const char *end = strchr(cursor, '\n'); size_t length = end == NULL ? strlen(cursor) : (size_t)(end - cursor); char zone_id[TRAINLOG_ZONE_ID_MAX + 1U]; - if (length == 0U || length > TRAINLOG_ZONE_ID_MAX) goto rollback; + if (length == 0U || length > TRAINLOG_ZONE_ID_MAX) { + goto rollback; + } (void)memcpy(zone_id, cursor, length); zone_id[length] = '\0'; - if (!bind_initial_body_zone(statement, mapping->exercise_id, - zone_id, "secondary")) { + if (!bind_initial_body_zone(statement, mapping->exercise_id, zone_id, "secondary")) { goto rollback; } cursor = end == NULL ? NULL : end + 1; @@ -1408,13 +1741,15 @@ static TrainlogStatus migrate_v10_to_v11(TrainlogDatabase *database) * data. `primary|secondary,...` is deterministic because zone IDs exclude * delimiters and secondary IDs are sorted bytewise. */ if (execute_sql(database, - "INSERT INTO exercise_body_zone_sync(exercise_row_id,synced_state) " - "SELECT e.id,COALESCE((SELECT p.zone_id FROM exercise_body_zones p " - "WHERE p.exercise_row_id=e.id AND p.role='primary'),'')||'|'||" - "COALESCE((SELECT group_concat(s.zone_id,',') FROM " - "(SELECT zone_id FROM exercise_body_zones WHERE exercise_row_id=e.id " - "AND role='secondary' ORDER BY zone_id) s),'') FROM exercises e;" - ) != TRAINLOG_STATUS_OK) goto rollback; + "INSERT INTO exercise_body_zone_sync(exercise_row_id,synced_state) " + "SELECT e.id,COALESCE((SELECT p.zone_id FROM exercise_body_zones p " + "WHERE p.exercise_row_id=e.id AND p.role='primary'),'')||'|'||" + "COALESCE((SELECT group_concat(s.zone_id,',') FROM " + "(SELECT zone_id FROM exercise_body_zones WHERE exercise_row_id=e.id " + "AND role='secondary' ORDER BY zone_id) s),'') FROM exercises e;") != + TRAINLOG_STATUS_OK) { + goto rollback; + } if (sqlite3_finalize(statement) != SQLITE_OK) { statement = NULL; @@ -1427,24 +1762,19 @@ static TrainlogStatus migrate_v10_to_v11(TrainlogDatabase *database) return TRAINLOG_STATUS_OK; rollback: - if (statement != NULL) (void)sqlite3_finalize(statement); + if (statement != NULL) { + (void)sqlite3_finalize(statement); + } (void)sqlite3_exec(database->connection, "ROLLBACK;", NULL, NULL, NULL); return status; } -static TrainlogStatus read_single_int_pragma( - TrainlogDatabase *database, - const char *sql, - int *output -) -{ +static TrainlogStatus +read_single_int_pragma(TrainlogDatabase *database, const char *sql, int *output) { sqlite3_stmt *statement = NULL; int rc; - if (database == NULL || - database->connection == NULL || - sql == NULL || - output == NULL) { + if (database == NULL || database->connection == NULL || sql == NULL || output == NULL) { return TRAINLOG_STATUS_INVALID_ARGUMENT; } @@ -1462,25 +1792,28 @@ static TrainlogStatus read_single_int_pragma( *output = sqlite3_column_int(statement, 0); rc = sqlite3_finalize(statement); - return rc == SQLITE_OK - ? TRAINLOG_STATUS_OK - : TRAINLOG_STATUS_DATABASE_ERROR; + return rc == SQLITE_OK ? TRAINLOG_STATUS_OK : TRAINLOG_STATUS_DATABASE_ERROR; } -static TrainlogStatus list_feedback_query( - TrainlogDatabase *database, const char *sql, const char *session_id, - TrainlogFeedbackView *output, size_t capacity, size_t *output_count, - bool has_entry) -{ +static TrainlogStatus list_feedback_query(TrainlogDatabase *database, + const char *sql, + const char *session_id, + TrainlogFeedbackView *output, + size_t capacity, + size_t *output_count, + bool has_entry) { sqlite3_stmt *statement = NULL; size_t count = 0U; int rc; if (database == NULL || sql == NULL || session_id == NULL || output == NULL || - output_count == NULL || capacity == 0U) return TRAINLOG_STATUS_INVALID_ARGUMENT; + output_count == NULL || capacity == 0U) { + return TRAINLOG_STATUS_INVALID_ARGUMENT; + } rc = sqlite3_prepare_v2(database->connection, sql, -1, &statement, NULL); - if (rc != SQLITE_OK || sqlite3_bind_text(statement, 1, session_id, -1, - SQLITE_TRANSIENT) != SQLITE_OK) { - (void)sqlite3_finalize(statement); return TRAINLOG_STATUS_DATABASE_ERROR; + if (rc != SQLITE_OK || + sqlite3_bind_text(statement, 1, session_id, -1, SQLITE_TRANSIENT) != SQLITE_OK) { + (void)sqlite3_finalize(statement); + return TRAINLOG_STATUS_DATABASE_ERROR; } while (count < capacity && (rc = sqlite3_step(statement)) == SQLITE_ROW) { const unsigned char *stable = sqlite3_column_text(statement, 0); @@ -1489,120 +1822,114 @@ static TrainlogStatus list_feedback_query( const unsigned char *text = sqlite3_column_text(statement, has_entry ? 3 : 2); if (stable == NULL || at == NULL || text == NULL || strlen((const char *)text) > TRAINLOG_FEEDBACK_TEXT_MAX) { - (void)sqlite3_finalize(statement); return TRAINLOG_STATUS_DATABASE_ERROR; + (void)sqlite3_finalize(statement); + return TRAINLOG_STATUS_DATABASE_ERROR; } (void)snprintf(output[count].stable_id, sizeof(output[count].stable_id), "%s", stable); - (void)snprintf(output[count].entry_id, sizeof(output[count].entry_id), "%s", - entry != NULL ? (const char *)entry : ""); + (void)snprintf(output[count].entry_id, + sizeof(output[count].entry_id), + "%s", + entry != NULL ? (const char *)entry : ""); (void)snprintf(output[count].observed_at, sizeof(output[count].observed_at), "%s", at); (void)snprintf(output[count].raw_text, sizeof(output[count].raw_text), "%s", text); count += 1U; } if (rc != SQLITE_DONE && count < capacity) { - (void)sqlite3_finalize(statement); return TRAINLOG_STATUS_DATABASE_ERROR; + (void)sqlite3_finalize(statement); + return TRAINLOG_STATUS_DATABASE_ERROR; } - rc = sqlite3_finalize(statement); *output_count = count; + rc = sqlite3_finalize(statement); + *output_count = count; return rc == SQLITE_OK ? TRAINLOG_STATUS_OK : TRAINLOG_STATUS_DATABASE_ERROR; } -static int compare_feedback_view(const void *left_value, const void *right_value) -{ +static int compare_feedback_view(const void *left_value, const void *right_value) { const TrainlogFeedbackView *left = left_value; const TrainlogFeedbackView *right = right_value; TrainlogTimestampKey left_key, right_key; int compared; if (!trainlog_timestamp_parse(left->observed_at, strlen(left->observed_at), &left_key) || - !trainlog_timestamp_parse(right->observed_at, strlen(right->observed_at), &right_key)) + !trainlog_timestamp_parse(right->observed_at, strlen(right->observed_at), &right_key)) { return strcmp(left->stable_id, right->stable_id); + } compared = trainlog_timestamp_compare(&left_key, &right_key); return compared != 0 ? compared : strcmp(left->stable_id, right->stable_id); } -TrainlogStatus trainlog_database_list_training_feedback( - TrainlogDatabase *database, const char *session_id, - TrainlogFeedbackView *exercise_feedback, size_t exercise_capacity, - size_t *exercise_count, TrainlogFeedbackView *followups, - size_t followup_capacity, size_t *followup_count) -{ - TrainlogStatus status = list_feedback_query(database, +TrainlogStatus trainlog_database_list_training_feedback(TrainlogDatabase *database, + const char *session_id, + TrainlogFeedbackView *exercise_feedback, + size_t exercise_capacity, + size_t *exercise_count, + TrainlogFeedbackView *followups, + size_t followup_capacity, + size_t *followup_count) { + TrainlogStatus status = list_feedback_query( + database, "SELECT f.feedback_id,se.entry_id,f.observed_at,f.raw_text FROM exercise_feedback f " - "JOIN session_exercises se ON se.id=f.session_exercise_row_id JOIN sessions s ON s.id=se.session_row_id " + "JOIN session_exercises se ON se.id=f.session_exercise_row_id JOIN sessions s ON " + "s.id=se.session_row_id " "WHERE s.session_id=?1 AND NOT EXISTS(SELECT 1 FROM sync_causal_state cs " "WHERE cs.target_kind='feedback' AND cs.target_id=f.feedback_id AND cs.deleted=1) " "ORDER BY f.observed_at COLLATE BINARY,f.feedback_id COLLATE BINARY", - session_id, exercise_feedback, exercise_capacity, exercise_count, true); - if (status != TRAINLOG_STATUS_OK) return status; + session_id, + exercise_feedback, + exercise_capacity, + exercise_count, + true); + if (status != TRAINLOG_STATUS_OK) { + return status; + } qsort(exercise_feedback, *exercise_count, sizeof(*exercise_feedback), compare_feedback_view); - status = list_feedback_query(database, + status = list_feedback_query( + database, "SELECT f.followup_id,f.observed_at,f.raw_text FROM session_followups f " "JOIN sessions s ON s.id=f.session_row_id WHERE s.session_id=?1 " "ORDER BY f.observed_at COLLATE BINARY,f.followup_id COLLATE BINARY", - session_id, followups, followup_capacity, followup_count, false); - if (status == TRAINLOG_STATUS_OK) + session_id, + followups, + followup_capacity, + followup_count, + false); + if (status == TRAINLOG_STATUS_OK) { qsort(followups, *followup_count, sizeof(*followups), compare_feedback_view); + } return status; } -static TrainlogStatus initialize_or_validate_schema( - TrainlogDatabase *database, - char *output_diagnostic, - size_t output_diagnostic_capacity -) -{ +static TrainlogStatus initialize_or_validate_schema(TrainlogDatabase *database, + char *output_diagnostic, + size_t output_diagnostic_capacity) { int version = 0; TrainlogStatus status; - status = - trainlog_database_schema_version( - database, - &version - ); + status = trainlog_database_schema_version(database, &version); - if ( - status != - TRAINLOG_STATUS_OK - ) { - set_open_diagnostic( - output_diagnostic, - output_diagnostic_capacity, - "read schema version", - database->connection, - SQLITE_ERROR - ); + if (status != TRAINLOG_STATUS_OK) { + set_open_diagnostic(output_diagnostic, + output_diagnostic_capacity, + "read schema version", + database->connection, + SQLITE_ERROR); return status; } - if ( - version > - TRAINLOG_DATABASE_SCHEMA_VERSION - ) { + if (version > TRAINLOG_DATABASE_SCHEMA_VERSION) { char message[128]; - (void)snprintf( - message, sizeof(message), - "schema version %d is newer than supported version %d", - version, TRAINLOG_DATABASE_SCHEMA_VERSION); - set_application_diagnostic( - output_diagnostic, output_diagnostic_capacity, message); - return - TRAINLOG_STATUS_SCHEMA_UNSUPPORTED; + (void)snprintf(message, + sizeof(message), + "schema version %d is newer than supported version %d", + version, + TRAINLOG_DATABASE_SCHEMA_VERSION); + set_application_diagnostic(output_diagnostic, output_diagnostic_capacity, message); + return TRAINLOG_STATUS_SCHEMA_UNSUPPORTED; } if (version == 0) { - status = - execute_sql( - database, - SCHEMA_V7_SQL_A - ); + status = execute_sql(database, SCHEMA_V7_SQL_A); - if ( - status == - TRAINLOG_STATUS_OK - ) { - status = - execute_sql( - database, - SCHEMA_V7_SQL_B - ); + if (status == TRAINLOG_STATUS_OK) { + status = execute_sql(database, SCHEMA_V7_SQL_B); } if (status == TRAINLOG_STATUS_OK) { status = execute_sql(database, MIGRATE_V7_TO_V8_SQL); @@ -1632,124 +1959,60 @@ static TrainlogStatus initialize_or_validate_schema( status = execute_sql(database, MIGRATE_V9_TO_V10_SQL); } else if (version == 10) { status = TRAINLOG_STATUS_OK; - } else if (version == 11 || version == 12 || version == 13 || version == 14 || - version == 15 || version == 16 || version == 17 || version == 18 || - version == 19 || version == 20 || version == 21 || version == 22 || - version == 23 || version == 24) { + } else if (version == 11 || version == 12 || version == 13 || version == 14 || version == 15 || + version == 16 || version == 17 || version == 18 || version == 19 || version == 20 || + version == 21 || version == 22 || version == 23 || version == 24 || version == 25) { status = TRAINLOG_STATUS_OK; } else { if (version == 1) { - status = - execute_sql( - database, - MIGRATE_V1_TO_V3_SQL - ); + status = execute_sql(database, MIGRATE_V1_TO_V3_SQL); - if ( - status == - TRAINLOG_STATUS_OK - ) { - status = - execute_sql( - database, - MIGRATE_V3_TO_V4_SQL_A - ); + if (status == TRAINLOG_STATUS_OK) { + status = execute_sql(database, MIGRATE_V3_TO_V4_SQL_A); } - if ( - status == - TRAINLOG_STATUS_OK - ) { - status = - execute_sql( - database, - MIGRATE_V3_TO_V4_SQL_B - ); + if (status == TRAINLOG_STATUS_OK) { + status = execute_sql(database, MIGRATE_V3_TO_V4_SQL_B); } } else if (version == 2) { - status = - execute_sql( - database, - MIGRATE_V2_TO_V3_SQL - ); + status = execute_sql(database, MIGRATE_V2_TO_V3_SQL); - if ( - status == - TRAINLOG_STATUS_OK - ) { - status = - execute_sql( - database, - MIGRATE_V3_TO_V4_SQL_A - ); + if (status == TRAINLOG_STATUS_OK) { + status = execute_sql(database, MIGRATE_V3_TO_V4_SQL_A); } - if ( - status == - TRAINLOG_STATUS_OK - ) { - status = - execute_sql( - database, - MIGRATE_V3_TO_V4_SQL_B - ); + if (status == TRAINLOG_STATUS_OK) { + status = execute_sql(database, MIGRATE_V3_TO_V4_SQL_B); } } else if (version == 3) { - status = - execute_sql( - database, - MIGRATE_V3_TO_V4_SQL_A - ); + status = execute_sql(database, MIGRATE_V3_TO_V4_SQL_A); - if ( - status == - TRAINLOG_STATUS_OK - ) { - status = - execute_sql( - database, - MIGRATE_V3_TO_V4_SQL_B - ); + if (status == TRAINLOG_STATUS_OK) { + status = execute_sql(database, MIGRATE_V3_TO_V4_SQL_B); } } else if (version == 4) { - status = - TRAINLOG_STATUS_OK; + status = TRAINLOG_STATUS_OK; } else if (version == 5) { status = TRAINLOG_STATUS_OK; } else if (version == 6) { status = TRAINLOG_STATUS_OK; } else { char message[128]; - (void)snprintf( - message, sizeof(message), - "schema version %d is unsupported (supported through version %d)", - version, TRAINLOG_DATABASE_SCHEMA_VERSION); - set_application_diagnostic( - output_diagnostic, output_diagnostic_capacity, message); - return - TRAINLOG_STATUS_SCHEMA_UNSUPPORTED; + (void)snprintf(message, + sizeof(message), + "schema version %d is unsupported (supported through version %d)", + version, + TRAINLOG_DATABASE_SCHEMA_VERSION); + set_application_diagnostic(output_diagnostic, output_diagnostic_capacity, message); + return TRAINLOG_STATUS_SCHEMA_UNSUPPORTED; } - if ( - status == - TRAINLOG_STATUS_OK - ) { - status = - execute_sql( - database, - MIGRATE_V4_TO_V5_SQL_A - ); + if (status == TRAINLOG_STATUS_OK) { + status = execute_sql(database, MIGRATE_V4_TO_V5_SQL_A); } - if ( - status == - TRAINLOG_STATUS_OK - ) { - status = - execute_sql( - database, - MIGRATE_V4_TO_V5_SQL_B - ); + if (status == TRAINLOG_STATUS_OK) { + status = execute_sql(database, MIGRATE_V4_TO_V5_SQL_B); } if (status == TRAINLOG_STATUS_OK && version < 6) { @@ -1770,7 +2033,9 @@ static TrainlogStatus initialize_or_validate_schema( } if (status == TRAINLOG_STATUS_OK) { - if (version < 11) status = migrate_v10_to_v11(database); + if (version < 11) { + status = migrate_v10_to_v11(database); + } } if (status == TRAINLOG_STATUS_OK && version < 12) { status = execute_sql(database, MIGRATE_V11_TO_V12_SQL); @@ -1817,79 +2082,62 @@ static TrainlogStatus initialize_or_validate_schema( if (status == TRAINLOG_STATUS_OK && version < 24) { status = execute_sql(database, MIGRATE_V23_TO_V24_SQL); } + if (status == TRAINLOG_STATUS_OK && version < 25) { + status = execute_sql(database, MIGRATE_V24_TO_V25_SQL); + } + if (status == TRAINLOG_STATUS_OK) { + status = ensure_v25_preparation_provenance(database); + } if (status == TRAINLOG_STATUS_OK) { status = ensure_v18_ai_draft_publication_state(database); } - if (status == TRAINLOG_STATUS_OK) status = execute_sql(database, "BEGIN IMMEDIATE;"); - if (status == TRAINLOG_STATUS_OK) status = execute_sql(database, ENSURE_V17_PROFILE_REVISIONS_SQL); - if (status == TRAINLOG_STATUS_OK) status = normalize_v17_prototype_profiles(database); - if (status == TRAINLOG_STATUS_OK) status = execute_sql(database, "COMMIT;"); + if (status == TRAINLOG_STATUS_OK) { + status = execute_sql(database, "BEGIN IMMEDIATE;"); + } + if (status == TRAINLOG_STATUS_OK) { + status = execute_sql(database, ENSURE_V17_PROFILE_REVISIONS_SQL); + } + if (status == TRAINLOG_STATUS_OK) { + status = normalize_v17_prototype_profiles(database); + } + if (status == TRAINLOG_STATUS_OK) { + status = execute_sql(database, "COMMIT;"); + } - if ( - status != - TRAINLOG_STATUS_OK - ) { - set_open_diagnostic( - output_diagnostic, - output_diagnostic_capacity, - version == 0 ? "create schema v24" : "migrate database to schema v24", - database->connection, - SQLITE_ERROR - ); - (void)sqlite3_exec( - database->connection, - "ROLLBACK;", - NULL, - NULL, - NULL - ); + if (status != TRAINLOG_STATUS_OK) { + set_open_diagnostic(output_diagnostic, + output_diagnostic_capacity, + version == 0 ? "create schema v25" : "migrate database to schema v25", + database->connection, + SQLITE_ERROR); + (void)sqlite3_exec(database->connection, "ROLLBACK;", NULL, NULL, NULL); /* MIGRATE_V9_TO_V10_SQL disables foreign keys outside its transaction; * a failed statement must not leave the live handle with enforcement * disabled after the rollback. */ - (void)sqlite3_exec( - database->connection, - "PRAGMA foreign_keys = ON;", - NULL, - NULL, - NULL - ); + (void)sqlite3_exec(database->connection, "PRAGMA foreign_keys = ON;", NULL, NULL, NULL); } return status; } -TrainlogStatus trainlog_database_open( - const char *path, - TrainlogDatabase **output_database -) -{ - return trainlog_database_open_with_diagnostic( - path, - output_database, - NULL, - 0U - ); +TrainlogStatus trainlog_database_open(const char *path, TrainlogDatabase **output_database) { + return trainlog_database_open_with_diagnostic(path, output_database, NULL, 0U); } -TrainlogStatus trainlog_database_open_with_diagnostic( - const char *path, - TrainlogDatabase **output_database, - char *output_diagnostic, - size_t output_diagnostic_capacity -) -{ +TrainlogStatus trainlog_database_open_with_diagnostic(const char *path, + TrainlogDatabase **output_database, + char *output_diagnostic, + size_t output_diagnostic_capacity) { TrainlogDatabase *database; int rc; TrainlogStatus status; if (path == NULL || path[0] == '\0' || output_database == NULL) { - set_open_diagnostic( - output_diagnostic, - output_diagnostic_capacity, - "validate database path", - NULL, - SQLITE_MISUSE - ); + set_open_diagnostic(output_diagnostic, + output_diagnostic_capacity, + "validate database path", + NULL, + SQLITE_MISUSE); return TRAINLOG_STATUS_INVALID_ARGUMENT; } @@ -1900,80 +2148,83 @@ TrainlogStatus trainlog_database_open_with_diagnostic( database = calloc(1U, sizeof(*database)); if (database == NULL) { - set_open_diagnostic( - output_diagnostic, - output_diagnostic_capacity, - "allocate database handle", - NULL, - SQLITE_NOMEM - ); + set_open_diagnostic(output_diagnostic, + output_diagnostic_capacity, + "allocate database handle", + NULL, + SQLITE_NOMEM); return TRAINLOG_STATUS_SYSTEM_ERROR; } - rc = sqlite3_open_v2( - path, - &database->connection, - SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX, - NULL - ); + rc = sqlite3_open_v2(path, + &database->connection, + SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE | SQLITE_OPEN_FULLMUTEX, + NULL); if (rc != SQLITE_OK) { - set_open_diagnostic( - output_diagnostic, - output_diagnostic_capacity, - "open database", - database->connection, - rc - ); + set_open_diagnostic(output_diagnostic, + output_diagnostic_capacity, + "open database", + database->connection, + rc); trainlog_database_close(database); return TRAINLOG_STATUS_DATABASE_ERROR; } - if (sqlite3_create_function(database->connection, "trainlog_profile_revision", 4, - SQLITE_UTF8 | SQLITE_DETERMINISTIC, NULL, sqlite_profile_revision, NULL, NULL) != SQLITE_OK) { - set_open_diagnostic(output_diagnostic, output_diagnostic_capacity, - "register profile revision function", database->connection, SQLITE_ERROR); + if (sqlite3_create_function(database->connection, + "trainlog_profile_revision", + 4, + SQLITE_UTF8 | SQLITE_DETERMINISTIC, + NULL, + sqlite_profile_revision, + NULL, + NULL) != SQLITE_OK) { + set_open_diagnostic(output_diagnostic, + output_diagnostic_capacity, + "register profile revision function", + database->connection, + SQLITE_ERROR); trainlog_database_close(database); return TRAINLOG_STATUS_DATABASE_ERROR; } - if (sqlite3_create_function(database->connection, "trainlog_pr1_valid", 4, - SQLITE_UTF8 | SQLITE_DETERMINISTIC, NULL, sqlite_prototype_profile_valid, - NULL, NULL) != SQLITE_OK) { - set_open_diagnostic(output_diagnostic, output_diagnostic_capacity, - "register prototype profile validator", database->connection, SQLITE_ERROR); + if (sqlite3_create_function(database->connection, + "trainlog_pr1_valid", + 4, + SQLITE_UTF8 | SQLITE_DETERMINISTIC, + NULL, + sqlite_prototype_profile_valid, + NULL, + NULL) != SQLITE_OK) { + set_open_diagnostic(output_diagnostic, + output_diagnostic_capacity, + "register prototype profile validator", + database->connection, + SQLITE_ERROR); trainlog_database_close(database); return TRAINLOG_STATUS_DATABASE_ERROR; } if (sqlite3_busy_timeout(database->connection, 5000) != SQLITE_OK) { - set_open_diagnostic( - output_diagnostic, - output_diagnostic_capacity, - "configure database busy timeout", - database->connection, - SQLITE_ERROR - ); + set_open_diagnostic(output_diagnostic, + output_diagnostic_capacity, + "configure database busy timeout", + database->connection, + SQLITE_ERROR); trainlog_database_close(database); return TRAINLOG_STATUS_DATABASE_ERROR; } status = execute_sql(database, "PRAGMA foreign_keys = ON;"); if (status != TRAINLOG_STATUS_OK) { - set_open_diagnostic( - output_diagnostic, - output_diagnostic_capacity, - "enable foreign-key enforcement", - database->connection, - SQLITE_ERROR - ); + set_open_diagnostic(output_diagnostic, + output_diagnostic_capacity, + "enable foreign-key enforcement", + database->connection, + SQLITE_ERROR); trainlog_database_close(database); return status; } - status = initialize_or_validate_schema( - database, - output_diagnostic, - output_diagnostic_capacity - ); + status = initialize_or_validate_schema(database, output_diagnostic, output_diagnostic_capacity); if (status != TRAINLOG_STATUS_OK) { trainlog_database_close(database); return status; @@ -1983,8 +2234,7 @@ TrainlogStatus trainlog_database_open_with_diagnostic( return TRAINLOG_STATUS_OK; } -void trainlog_database_close(TrainlogDatabase *database) -{ +void trainlog_database_close(TrainlogDatabase *database) { if (database == NULL) { return; } @@ -1996,35 +2246,17 @@ void trainlog_database_close(TrainlogDatabase *database) free(database); } -TrainlogStatus trainlog_database_schema_version( - TrainlogDatabase *database, - int *output_version -) -{ - return read_single_int_pragma( - database, - "PRAGMA user_version;", - output_version - ); +TrainlogStatus trainlog_database_schema_version(TrainlogDatabase *database, int *output_version) { + return read_single_int_pragma(database, "PRAGMA user_version;", output_version); } -TrainlogStatus trainlog_database_foreign_keys_enabled( - TrainlogDatabase *database, - int *output_enabled -) -{ - return read_single_int_pragma( - database, - "PRAGMA foreign_keys;", - output_enabled - ); +TrainlogStatus trainlog_database_foreign_keys_enabled(TrainlogDatabase *database, + int *output_enabled) { + return read_single_int_pragma(database, "PRAGMA foreign_keys;", output_enabled); } -TrainlogStatus trainlog_database_create_custom_equipment( - TrainlogDatabase *database, - const TrainlogCustomEquipment *equipment -) -{ +TrainlogStatus trainlog_database_create_custom_equipment(TrainlogDatabase *database, + const TrainlogCustomEquipment *equipment) { sqlite3_stmt *statement = NULL; int rc; @@ -2038,9 +2270,15 @@ TrainlogStatus trainlog_database_create_custom_equipment( return TRAINLOG_STATUS_INVALID_ARGUMENT; } rc = sqlite3_prepare_v2(database->connection, - "INSERT INTO custom_equipment(equipment_id,display_name,label_name,equipment_type,load_semantics) VALUES(?1,?2,?3,?4,?5);", - -1, &statement, NULL); - if (rc != SQLITE_OK) return TRAINLOG_STATUS_DATABASE_ERROR; + "INSERT INTO " + "custom_equipment(equipment_id,display_name,label_name,equipment_type," + "load_semantics) VALUES(?1,?2,?3,?4,?5);", + -1, + &statement, + NULL); + if (rc != SQLITE_OK) { + return TRAINLOG_STATUS_DATABASE_ERROR; + } (void)sqlite3_bind_text(statement, 1, equipment->equipment_id, -1, SQLITE_TRANSIENT); (void)sqlite3_bind_text(statement, 2, equipment->display_name, -1, SQLITE_TRANSIENT); (void)sqlite3_bind_text(statement, 3, equipment->label_name, -1, SQLITE_TRANSIENT); @@ -2051,32 +2289,54 @@ TrainlogStatus trainlog_database_create_custom_equipment( return rc == SQLITE_DONE ? TRAINLOG_STATUS_OK : TRAINLOG_STATUS_DATABASE_ERROR; } -TrainlogStatus trainlog_database_list_custom_equipment( - TrainlogDatabase *database, TrainlogCustomEquipment *output, - size_t capacity, size_t *output_count -) -{ +TrainlogStatus trainlog_database_list_custom_equipment(TrainlogDatabase *database, + TrainlogCustomEquipment *output, + size_t capacity, + size_t *output_count) { sqlite3_stmt *statement = NULL; size_t count = 0U; int rc; - if (database == NULL || output_count == NULL || (capacity > 0U && output == NULL)) + if (database == NULL || output_count == NULL || (capacity > 0U && output == NULL)) { return TRAINLOG_STATUS_INVALID_ARGUMENT; + } *output_count = 0U; - rc = sqlite3_prepare_v2(database->connection, - "SELECT equipment_id,display_name,label_name,equipment_type,load_semantics FROM custom_equipment ce " - "WHERE NOT EXISTS(SELECT 1 FROM sync_causal_state s WHERE s.target_kind='custom_equipment' AND s.target_id=ce.equipment_id AND s.deleted=1) " + rc = sqlite3_prepare_v2( + database->connection, + "SELECT equipment_id,display_name,label_name,equipment_type,load_semantics FROM " + "custom_equipment ce " + "WHERE NOT EXISTS(SELECT 1 FROM sync_causal_state s WHERE s.target_kind='custom_equipment' " + "AND s.target_id=ce.equipment_id AND s.deleted=1) " "ORDER BY display_name COLLATE NOCASE,equipment_id;", - -1, &statement, NULL); - if (rc != SQLITE_OK) return TRAINLOG_STATUS_DATABASE_ERROR; + -1, + &statement, + NULL); + if (rc != SQLITE_OK) { + return TRAINLOG_STATUS_DATABASE_ERROR; + } while ((rc = sqlite3_step(statement)) == SQLITE_ROW) { if (count < capacity) { TrainlogCustomEquipment *item = &output[count]; (void)memset(item, 0, sizeof(*item)); - (void)snprintf(item->equipment_id, sizeof(item->equipment_id), "%s", sqlite3_column_text(statement, 0)); - (void)snprintf(item->display_name, sizeof(item->display_name), "%s", sqlite3_column_text(statement, 1)); - (void)snprintf(item->label_name, sizeof(item->label_name), "%s", sqlite3_column_text(statement, 2)); - (void)snprintf(item->equipment_type, sizeof(item->equipment_type), "%s", sqlite3_column_text(statement, 3)); - (void)snprintf(item->load_semantics, sizeof(item->load_semantics), "%s", sqlite3_column_text(statement, 4)); + (void)snprintf(item->equipment_id, + sizeof(item->equipment_id), + "%s", + sqlite3_column_text(statement, 0)); + (void)snprintf(item->display_name, + sizeof(item->display_name), + "%s", + sqlite3_column_text(statement, 1)); + (void)snprintf(item->label_name, + sizeof(item->label_name), + "%s", + sqlite3_column_text(statement, 2)); + (void)snprintf(item->equipment_type, + sizeof(item->equipment_type), + "%s", + sqlite3_column_text(statement, 3)); + (void)snprintf(item->load_semantics, + sizeof(item->load_semantics), + "%s", + sqlite3_column_text(statement, 4)); } ++count; } @@ -2085,51 +2345,62 @@ TrainlogStatus trainlog_database_list_custom_equipment( return rc == SQLITE_DONE ? TRAINLOG_STATUS_OK : TRAINLOG_STATUS_DATABASE_ERROR; } -static bool copy_custom_equipment_page_column( - sqlite3_stmt *statement, int column, char *output, size_t capacity) -{ +static bool copy_custom_equipment_page_column(sqlite3_stmt *statement, + int column, + char *output, + size_t capacity) { const unsigned char *source; int byte_count; size_t length; - if (sqlite3_column_type(statement, column) != SQLITE_TEXT || - output == NULL || capacity == 0U) return false; + if (sqlite3_column_type(statement, column) != SQLITE_TEXT || output == NULL || capacity == 0U) { + return false; + } source = sqlite3_column_text(statement, column); byte_count = sqlite3_column_bytes(statement, column); - if (source == NULL || byte_count < 0) return false; + if (source == NULL || byte_count < 0) { + return false; + } length = (size_t)byte_count; /* SQLite TEXT may contain embedded NUL bytes; never silently truncate it. */ - if (length >= capacity || memchr(source, '\0', length) != NULL) return false; + if (length >= capacity || memchr(source, '\0', length) != NULL) { + return false; + } (void)memcpy(output, source, length); output[length] = '\0'; return true; } -TrainlogStatus trainlog_database_list_custom_equipment_page( - TrainlogDatabase *database, size_t offset, TrainlogCustomEquipment *output, - size_t capacity, size_t *output_count, bool *output_more) -{ +TrainlogStatus trainlog_database_list_custom_equipment_page(TrainlogDatabase *database, + size_t offset, + TrainlogCustomEquipment *output, + size_t capacity, + size_t *output_count, + bool *output_more) { static const char *const SQL = "SELECT equipment_id,display_name,label_name,equipment_type,load_semantics " - "FROM custom_equipment ce WHERE NOT EXISTS(SELECT 1 FROM sync_causal_state s WHERE s.target_kind='custom_equipment' AND s.target_id=ce.equipment_id AND s.deleted=1) ORDER BY display_name COLLATE NOCASE,equipment_id " + "FROM custom_equipment ce WHERE NOT EXISTS(SELECT 1 FROM sync_causal_state s WHERE " + "s.target_kind='custom_equipment' AND s.target_id=ce.equipment_id AND s.deleted=1) ORDER " + "BY display_name COLLATE NOCASE,equipment_id " "LIMIT ?1 OFFSET ?2;"; sqlite3_stmt *statement = NULL; size_t count = 0U; int rc; - if (output != NULL && output_count != NULL && output_more != NULL && - capacity >= 1U && capacity <= TRAINLOG_CUSTOM_EQUIPMENT_PAGE_MAX) { + if (output != NULL && output_count != NULL && output_more != NULL && capacity >= 1U && + capacity <= TRAINLOG_CUSTOM_EQUIPMENT_PAGE_MAX) { *output_count = 0U; *output_more = false; } - if (database == NULL || output == NULL || output_count == NULL || - output_more == NULL || capacity == 0U || - capacity > TRAINLOG_CUSTOM_EQUIPMENT_PAGE_MAX || + if (database == NULL || output == NULL || output_count == NULL || output_more == NULL || + capacity == 0U || capacity > TRAINLOG_CUSTOM_EQUIPMENT_PAGE_MAX || (uintmax_t)offset > (uintmax_t)INT64_MAX) { return TRAINLOG_STATUS_INVALID_ARGUMENT; } rc = sqlite3_prepare_v2(database->connection, SQL, -1, &statement, NULL); - if (rc != SQLITE_OK) return TRAINLOG_STATUS_DATABASE_ERROR; + if (rc != SQLITE_OK) { + return TRAINLOG_STATUS_DATABASE_ERROR; + } if (sqlite3_bind_int64(statement, 1, (sqlite3_int64)(capacity + 1U)) != SQLITE_OK || sqlite3_bind_int64(statement, 2, (sqlite3_int64)offset) != SQLITE_OK) { (void)sqlite3_finalize(statement); @@ -2138,16 +2409,16 @@ TrainlogStatus trainlog_database_list_custom_equipment_page( while ((rc = sqlite3_step(statement)) == SQLITE_ROW) { TrainlogCustomEquipment item; (void)memset(&item, 0, sizeof(item)); - if (!copy_custom_equipment_page_column(statement, 0, item.equipment_id, - sizeof(item.equipment_id)) || - !copy_custom_equipment_page_column(statement, 1, item.display_name, - sizeof(item.display_name)) || - !copy_custom_equipment_page_column(statement, 2, item.label_name, - sizeof(item.label_name)) || - !copy_custom_equipment_page_column(statement, 3, item.equipment_type, - sizeof(item.equipment_type)) || - !copy_custom_equipment_page_column(statement, 4, item.load_semantics, - sizeof(item.load_semantics))) { + if (!copy_custom_equipment_page_column( + statement, 0, item.equipment_id, sizeof(item.equipment_id)) || + !copy_custom_equipment_page_column( + statement, 1, item.display_name, sizeof(item.display_name)) || + !copy_custom_equipment_page_column( + statement, 2, item.label_name, sizeof(item.label_name)) || + !copy_custom_equipment_page_column( + statement, 3, item.equipment_type, sizeof(item.equipment_type)) || + !copy_custom_equipment_page_column( + statement, 4, item.load_semantics, sizeof(item.load_semantics))) { (void)sqlite3_finalize(statement); return TRAINLOG_STATUS_DATABASE_ERROR; } @@ -2157,48 +2428,72 @@ TrainlogStatus trainlog_database_list_custom_equipment_page( } output[count++] = item; } - if (sqlite3_finalize(statement) != SQLITE_OK) return TRAINLOG_STATUS_DATABASE_ERROR; - if (rc != SQLITE_DONE && rc != SQLITE_ROW) return TRAINLOG_STATUS_DATABASE_ERROR; + if (sqlite3_finalize(statement) != SQLITE_OK) { + return TRAINLOG_STATUS_DATABASE_ERROR; + } + if (rc != SQLITE_DONE && rc != SQLITE_ROW) { + return TRAINLOG_STATUS_DATABASE_ERROR; + } *output_count = count; return TRAINLOG_STATUS_OK; } -TrainlogStatus trainlog_database_resolve_equipment( - TrainlogDatabase *database, const char *equipment_id, - TrainlogResolvedEquipment *output -) -{ +TrainlogStatus trainlog_database_resolve_equipment(TrainlogDatabase *database, + const char *equipment_id, + TrainlogResolvedEquipment *output) { const TrainlogEquipment *supplied; sqlite3_stmt *statement = NULL; int rc; - if (database == NULL || equipment_id == NULL || equipment_id[0] == '\0' || - output == NULL) return TRAINLOG_STATUS_INVALID_ARGUMENT; + if (database == NULL || equipment_id == NULL || equipment_id[0] == '\0' || output == NULL) { + return TRAINLOG_STATUS_INVALID_ARGUMENT; + } (void)memset(output, 0, sizeof(*output)); (void)snprintf(output->equipment_id, sizeof(output->equipment_id), "%s", equipment_id); supplied = trainlog_equipment_catalog_lookup(equipment_id); if (supplied != NULL) { - (void)snprintf(output->display_name, sizeof(output->display_name), "%s", supplied->display_name); + (void)snprintf( + output->display_name, sizeof(output->display_name), "%s", supplied->display_name); (void)snprintf(output->label_name, sizeof(output->label_name), "%s", supplied->label_name); - (void)snprintf(output->equipment_type, sizeof(output->equipment_type), "%s", supplied->equipment_type); - (void)snprintf(output->load_semantics, sizeof(output->load_semantics), "%s", supplied->load_semantics); + (void)snprintf( + output->equipment_type, sizeof(output->equipment_type), "%s", supplied->equipment_type); + (void)snprintf( + output->load_semantics, sizeof(output->load_semantics), "%s", supplied->load_semantics); output->origin = TRAINLOG_EQUIPMENT_SUPPLIED; return TRAINLOG_STATUS_OK; } rc = sqlite3_prepare_v2(database->connection, - "SELECT display_name,label_name,equipment_type,load_semantics FROM custom_equipment WHERE equipment_id=?1;", - -1, &statement, NULL); - if (rc != SQLITE_OK) return TRAINLOG_STATUS_DATABASE_ERROR; + "SELECT display_name,label_name,equipment_type,load_semantics FROM " + "custom_equipment WHERE equipment_id=?1;", + -1, + &statement, + NULL); + if (rc != SQLITE_OK) { + return TRAINLOG_STATUS_DATABASE_ERROR; + } (void)sqlite3_bind_text(statement, 1, equipment_id, -1, SQLITE_TRANSIENT); rc = sqlite3_step(statement); if (rc == SQLITE_ROW) { - (void)snprintf(output->display_name, sizeof(output->display_name), "%s", sqlite3_column_text(statement, 0)); - (void)snprintf(output->label_name, sizeof(output->label_name), "%s", sqlite3_column_text(statement, 1)); - (void)snprintf(output->equipment_type, sizeof(output->equipment_type), "%s", sqlite3_column_text(statement, 2)); - (void)snprintf(output->load_semantics, sizeof(output->load_semantics), "%s", sqlite3_column_text(statement, 3)); + (void)snprintf(output->display_name, + sizeof(output->display_name), + "%s", + sqlite3_column_text(statement, 0)); + (void)snprintf(output->label_name, + sizeof(output->label_name), + "%s", + sqlite3_column_text(statement, 1)); + (void)snprintf(output->equipment_type, + sizeof(output->equipment_type), + "%s", + sqlite3_column_text(statement, 2)); + (void)snprintf(output->load_semantics, + sizeof(output->load_semantics), + "%s", + sqlite3_column_text(statement, 3)); output->origin = TRAINLOG_EQUIPMENT_CUSTOM; } else if (rc == SQLITE_DONE) { /* INVARIANT: historic references survive catalogue changes visibly. */ - (void)snprintf(output->display_name, sizeof(output->display_name), "Inconnu (%s)", equipment_id); + (void)snprintf( + output->display_name, sizeof(output->display_name), "Inconnu (%s)", equipment_id); output->origin = TRAINLOG_EQUIPMENT_UNKNOWN; rc = SQLITE_ROW; } @@ -2206,25 +2501,35 @@ TrainlogStatus trainlog_database_resolve_equipment( return rc == SQLITE_ROW ? TRAINLOG_STATUS_OK : TRAINLOG_STATUS_DATABASE_ERROR; } -TrainlogStatus trainlog_database_list_exercise_equipment( - TrainlogDatabase *database, const char *exercise_id, - TrainlogResolvedEquipment *output, size_t capacity, size_t *output_count -) -{ +TrainlogStatus trainlog_database_list_exercise_equipment(TrainlogDatabase *database, + const char *exercise_id, + TrainlogResolvedEquipment *output, + size_t capacity, + size_t *output_count) { sqlite3_stmt *statement = NULL; size_t count = 0U; int rc; if (database == NULL || exercise_id == NULL || output_count == NULL || - (capacity > 0U && output == NULL)) return TRAINLOG_STATUS_INVALID_ARGUMENT; + (capacity > 0U && output == NULL)) { + return TRAINLOG_STATUS_INVALID_ARGUMENT; + } *output_count = 0U; - rc = sqlite3_prepare_v2(database->connection, - "SELECT DISTINCT se.equipment_id FROM session_exercises se JOIN exercises e ON e.id=se.exercise_row_id WHERE e.exercise_id=?1 AND se.equipment_id IS NOT NULL AND se.equipment_id<>'' ORDER BY se.equipment_id;", - -1, &statement, NULL); - if (rc != SQLITE_OK) return TRAINLOG_STATUS_DATABASE_ERROR; + rc = sqlite3_prepare_v2( + database->connection, + "SELECT DISTINCT se.equipment_id FROM session_exercises se JOIN exercises e ON " + "e.id=se.exercise_row_id WHERE e.exercise_id=?1 AND se.equipment_id IS NOT NULL AND " + "se.equipment_id<>'' ORDER BY se.equipment_id;", + -1, + &statement, + NULL); + if (rc != SQLITE_OK) { + return TRAINLOG_STATUS_DATABASE_ERROR; + } (void)sqlite3_bind_text(statement, 1, exercise_id, -1, SQLITE_TRANSIENT); while ((rc = sqlite3_step(statement)) == SQLITE_ROW) { const char *id = (const char *)sqlite3_column_text(statement, 0); - if (count < capacity && trainlog_database_resolve_equipment(database, id, &output[count]) != TRAINLOG_STATUS_OK) { + if (count < capacity && trainlog_database_resolve_equipment(database, id, &output[count]) != + TRAINLOG_STATUS_OK) { (void)sqlite3_finalize(statement); return TRAINLOG_STATUS_DATABASE_ERROR; } @@ -2235,23 +2540,19 @@ TrainlogStatus trainlog_database_list_exercise_equipment( return rc == SQLITE_DONE ? TRAINLOG_STATUS_OK : TRAINLOG_STATUS_DATABASE_ERROR; } -TrainlogStatus trainlog_database_begin(TrainlogDatabase *database) -{ +TrainlogStatus trainlog_database_begin(TrainlogDatabase *database) { return execute_sql(database, "BEGIN IMMEDIATE;"); } -TrainlogStatus trainlog_database_commit(TrainlogDatabase *database) -{ +TrainlogStatus trainlog_database_commit(TrainlogDatabase *database) { return execute_sql(database, "COMMIT;"); } -TrainlogStatus trainlog_database_rollback(TrainlogDatabase *database) -{ +TrainlogStatus trainlog_database_rollback(TrainlogDatabase *database) { return execute_sql(database, "ROLLBACK;"); } -static const char *tracking_mode_to_sql(TrainlogTrackingMode mode) -{ +static const char *tracking_mode_to_sql(TrainlogTrackingMode mode) { switch (mode) { case TRAINLOG_TRACKING_REPS: return "reps"; @@ -2262,10 +2563,7 @@ static const char *tracking_mode_to_sql(TrainlogTrackingMode mode) } } -static const char *recording_mode_to_sql( - TrainlogRecordingMode mode -) -{ +static const char *recording_mode_to_sql(TrainlogRecordingMode mode) { switch (mode) { case TRAINLOG_RECORDING_SETS: return "sets"; @@ -2276,14 +2574,10 @@ static const char *recording_mode_to_sql( } } -static bool exercise_profile_valid( - TrainlogTrackingMode tracking_mode, - TrainlogRecordingMode recording_mode, - TrainlogExerciseDataFields data_fields -) -{ - if ((data_fields & - ~TRAINLOG_EXERCISE_DATA_KNOWN_MASK) != 0U) { +static bool exercise_profile_valid(TrainlogTrackingMode tracking_mode, + TrainlogRecordingMode recording_mode, + TrainlogExerciseDataFields data_fields) { + if ((data_fields & ~TRAINLOG_EXERCISE_DATA_KNOWN_MASK) != 0U) { return false; } @@ -2291,15 +2585,11 @@ static bool exercise_profile_valid( return tracking_mode == TRAINLOG_TRACKING_DURATION; } - return - recording_mode == TRAINLOG_RECORDING_SETS && - (tracking_mode == TRAINLOG_TRACKING_REPS || - tracking_mode == TRAINLOG_TRACKING_DURATION); + return recording_mode == TRAINLOG_RECORDING_SETS && + (tracking_mode == TRAINLOG_TRACKING_REPS || tracking_mode == TRAINLOG_TRACKING_DURATION); } - -static const char *load_mode_to_sql(TrainlogLoadMode mode) -{ +static const char *load_mode_to_sql(TrainlogLoadMode mode) { switch (mode) { case TRAINLOG_LOAD_NONE: return "none"; @@ -2312,10 +2602,7 @@ static const char *load_mode_to_sql(TrainlogLoadMode mode) } } -static const char *session_type_to_sql( - TrainlogSessionType type -) -{ +static const char *session_type_to_sql(TrainlogSessionType type) { switch (type) { case TRAINLOG_SESSION_TRAINING: return "training"; @@ -2328,40 +2615,27 @@ static const char *session_type_to_sql( } } -TrainlogStatus trainlog_database_insert_exercise_profiled( - TrainlogDatabase *database, - const char *exercise_id, - const char *name, - const char *normalized_name, - TrainlogTrackingMode tracking_mode, - TrainlogRecordingMode recording_mode, - TrainlogExerciseDataFields data_fields -) -{ - static const char *const SQL = - "INSERT INTO exercises(" - "exercise_id, name, normalized_name, tracking_mode, " - "recording_mode, data_fields" - ") VALUES(?1, ?2, ?3, ?4, ?5, ?6);"; +TrainlogStatus trainlog_database_insert_exercise_profiled(TrainlogDatabase *database, + const char *exercise_id, + const char *name, + const char *normalized_name, + TrainlogTrackingMode tracking_mode, + TrainlogRecordingMode recording_mode, + TrainlogExerciseDataFields data_fields) { + static const char *const SQL = "INSERT INTO exercises(" + "exercise_id, name, normalized_name, tracking_mode, " + "recording_mode, data_fields" + ") VALUES(?1, ?2, ?3, ?4, ?5, ?6);"; sqlite3_stmt *statement = NULL; const char *tracking; const char *recording; int rc; - if (database == NULL || - database->connection == NULL || - exercise_id == NULL || - exercise_id[0] == '\0' || - name == NULL || - name[0] == '\0' || - normalized_name == NULL || + if (database == NULL || database->connection == NULL || exercise_id == NULL || + exercise_id[0] == '\0' || name == NULL || name[0] == '\0' || normalized_name == NULL || normalized_name[0] == '\0' || - !exercise_profile_valid( - tracking_mode, - recording_mode, - data_fields - )) { + !exercise_profile_valid(tracking_mode, recording_mode, data_fields)) { return TRAINLOG_STATUS_INVALID_ARGUMENT; } @@ -2372,13 +2646,7 @@ TrainlogStatus trainlog_database_insert_exercise_profiled( return TRAINLOG_STATUS_INVALID_ARGUMENT; } - rc = sqlite3_prepare_v2( - database->connection, - SQL, - -1, - &statement, - NULL - ); + rc = sqlite3_prepare_v2(database->connection, SQL, -1, &statement, NULL); if (rc != SQLITE_OK) { return TRAINLOG_STATUS_DATABASE_ERROR; @@ -2406,43 +2674,29 @@ TrainlogStatus trainlog_database_insert_exercise_profiled( return TRAINLOG_STATUS_DATABASE_ERROR; } - return sqlite3_finalize(statement) == SQLITE_OK - ? TRAINLOG_STATUS_OK - : TRAINLOG_STATUS_DATABASE_ERROR; + return sqlite3_finalize(statement) == SQLITE_OK ? TRAINLOG_STATUS_OK + : TRAINLOG_STATUS_DATABASE_ERROR; } -TrainlogStatus trainlog_database_insert_exercise( - TrainlogDatabase *database, - const char *exercise_id, - const char *name, - const char *normalized_name, - TrainlogTrackingMode tracking_mode -) -{ +TrainlogStatus trainlog_database_insert_exercise(TrainlogDatabase *database, + const char *exercise_id, + const char *name, + const char *normalized_name, + TrainlogTrackingMode tracking_mode) { return trainlog_database_insert_exercise_profiled( - database, - exercise_id, - name, - normalized_name, - tracking_mode, - TRAINLOG_RECORDING_SETS, - 0U - ); + database, exercise_id, name, normalized_name, tracking_mode, TRAINLOG_RECORDING_SETS, 0U); } -TrainlogStatus trainlog_database_update_exercise_profiled( - TrainlogDatabase *database, - const char *exercise_id, - const char *name, - const char *normalized_name, - TrainlogTrackingMode tracking_mode, - TrainlogRecordingMode recording_mode, - TrainlogExerciseDataFields data_fields, - const char *primary_zone_id, - const char *const *secondary_zone_ids, - size_t secondary_count -) -{ +TrainlogStatus trainlog_database_update_exercise_profiled(TrainlogDatabase *database, + const char *exercise_id, + const char *name, + const char *normalized_name, + TrainlogTrackingMode tracking_mode, + TrainlogRecordingMode recording_mode, + TrainlogExerciseDataFields data_fields, + const char *primary_zone_id, + const char *const *secondary_zone_ids, + size_t secondary_count) { sqlite3_stmt *statement = NULL; sqlite3_int64 row_id; TrainlogStatus status; @@ -2450,24 +2704,30 @@ TrainlogStatus trainlog_database_update_exercise_profiled( const char *tracking; const char *recording; - if (database == NULL || exercise_id == NULL || name == NULL || - normalized_name == NULL || name[0] == '\0' || normalized_name[0] == '\0' || - strlen(name) > TRAINLOG_NAME_MAX || - !exercise_profile_valid(tracking_mode, recording_mode, data_fields)) + if (database == NULL || exercise_id == NULL || name == NULL || normalized_name == NULL || + name[0] == '\0' || normalized_name[0] == '\0' || strlen(name) > TRAINLOG_NAME_MAX || + !exercise_profile_valid(tracking_mode, recording_mode, data_fields)) { return TRAINLOG_STATUS_INVALID_ARGUMENT; + } tracking = tracking_mode_to_sql(tracking_mode); recording = recording_mode_to_sql(recording_mode); status = lookup_exercise_row_id(database, exercise_id, &row_id); - if (status != TRAINLOG_STATUS_OK) return status; - if (execute_sql(database, "BEGIN IMMEDIATE;") != TRAINLOG_STATUS_OK) + if (status != TRAINLOG_STATUS_OK) { + return status; + } + if (execute_sql(database, "BEGIN IMMEDIATE;") != TRAINLOG_STATUS_OK) { return TRAINLOG_STATUS_DATABASE_ERROR; + } /* CONTRACT: schema v16 makes tracking semantics occurrence-owned. A * catalogue profile update therefore controls future occurrences only; * this transaction never updates session_exercises snapshots. */ rc = sqlite3_prepare_v2(database->connection, - "UPDATE exercises SET name=?1,normalized_name=?2,tracking_mode=?3," - "recording_mode=?4,data_fields=?5 WHERE id=?6;", -1, &statement, NULL); + "UPDATE exercises SET name=?1,normalized_name=?2,tracking_mode=?3," + "recording_mode=?4,data_fields=?5 WHERE id=?6;", + -1, + &statement, + NULL); if (rc != SQLITE_OK || sqlite3_bind_text(statement, 1, name, -1, SQLITE_TRANSIENT) != SQLITE_OK || sqlite3_bind_text(statement, 2, normalized_name, -1, SQLITE_TRANSIENT) != SQLITE_OK || @@ -2480,8 +2740,8 @@ TrainlogStatus trainlog_database_update_exercise_profiled( } rc = sqlite3_step(statement); if (rc != SQLITE_DONE) { - status = rc == SQLITE_CONSTRAINT ? TRAINLOG_STATUS_CONFLICT : - TRAINLOG_STATUS_DATABASE_ERROR; + status = + rc == SQLITE_CONSTRAINT ? TRAINLOG_STATUS_CONFLICT : TRAINLOG_STATUS_DATABASE_ERROR; goto rollback; } if (sqlite3_finalize(statement) != SQLITE_OK) { @@ -2490,36 +2750,32 @@ TrainlogStatus trainlog_database_update_exercise_profiled( goto rollback; } statement = NULL; - status = trainlog_database_replace_exercise_body_zones(database, exercise_id, - primary_zone_id, secondary_zone_ids, secondary_count); - if (status != TRAINLOG_STATUS_OK) goto rollback; - if (execute_sql(database, "COMMIT;") != TRAINLOG_STATUS_OK) - { + status = trainlog_database_replace_exercise_body_zones( + database, exercise_id, primary_zone_id, secondary_zone_ids, secondary_count); + if (status != TRAINLOG_STATUS_OK) { + goto rollback; + } + if (execute_sql(database, "COMMIT;") != TRAINLOG_STATUS_OK) { (void)sqlite3_exec(database->connection, "ROLLBACK;", NULL, NULL, NULL); return TRAINLOG_STATUS_DATABASE_ERROR; } return TRAINLOG_STATUS_OK; rollback: - if (statement != NULL) (void)sqlite3_finalize(statement); + if (statement != NULL) { + (void)sqlite3_finalize(statement); + } (void)sqlite3_exec(database->connection, "ROLLBACK;", NULL, NULL, NULL); return status; } -static TrainlogStatus count_query( - TrainlogDatabase *database, - const char *sql, - size_t *output_count -) -{ +static TrainlogStatus +count_query(TrainlogDatabase *database, const char *sql, size_t *output_count) { sqlite3_stmt *statement = NULL; sqlite3_int64 count; int rc; - if (database == NULL || - database->connection == NULL || - sql == NULL || - output_count == NULL) { + if (database == NULL || database->connection == NULL || sql == NULL || output_count == NULL) { return TRAINLOG_STATUS_INVALID_ARGUMENT; } @@ -2542,61 +2798,30 @@ static TrainlogStatus count_query( *output_count = (size_t)count; - return sqlite3_finalize(statement) == SQLITE_OK - ? TRAINLOG_STATUS_OK - : TRAINLOG_STATUS_DATABASE_ERROR; + return sqlite3_finalize(statement) == SQLITE_OK ? TRAINLOG_STATUS_OK + : TRAINLOG_STATUS_DATABASE_ERROR; } -TrainlogStatus trainlog_database_exercise_count( - TrainlogDatabase *database, - size_t *output_count -) -{ - return count_query( - database, - "SELECT COUNT(*) FROM exercises;", - output_count - ); +TrainlogStatus trainlog_database_exercise_count(TrainlogDatabase *database, size_t *output_count) { + return count_query(database, "SELECT COUNT(*) FROM exercises;", output_count); } -TrainlogStatus trainlog_database_session_count( - TrainlogDatabase *database, - size_t *output_count -) -{ - return count_query( - database, - "SELECT COUNT(*) FROM sessions;", - output_count - ); +TrainlogStatus trainlog_database_session_count(TrainlogDatabase *database, size_t *output_count) { + return count_query(database, "SELECT COUNT(*) FROM sessions;", output_count); } -static TrainlogTrackingMode tracking_mode_from_sql(const char *text) -{ - return text != NULL && strcmp(text, "duration") == 0 - ? TRAINLOG_TRACKING_DURATION - : TRAINLOG_TRACKING_REPS; +static TrainlogTrackingMode tracking_mode_from_sql(const char *text) { + return text != NULL && strcmp(text, "duration") == 0 ? TRAINLOG_TRACKING_DURATION + : TRAINLOG_TRACKING_REPS; } -static TrainlogRecordingMode recording_mode_from_sql( - const char *text -) -{ - return - text != NULL && - strcmp(text, "continuous") == 0 - ? TRAINLOG_RECORDING_CONTINUOUS - : TRAINLOG_RECORDING_SETS; +static TrainlogRecordingMode recording_mode_from_sql(const char *text) { + return text != NULL && strcmp(text, "continuous") == 0 ? TRAINLOG_RECORDING_CONTINUOUS + : TRAINLOG_RECORDING_SETS; } - -static bool session_type_from_sql( - const char *text, - TrainlogSessionType *output -) -{ - if (text == NULL || - output == NULL) { +static bool session_type_from_sql(const char *text, TrainlogSessionType *output) { + if (text == NULL || output == NULL) { return false; } @@ -2613,39 +2838,27 @@ static bool session_type_from_sql( return false; } -TrainlogStatus trainlog_database_list_exercises( - TrainlogDatabase *database, - TrainlogExercise *output, - size_t capacity, - size_t *output_count -) -{ - static const char *const SQL = - "SELECT exercise_id, name, tracking_mode, " - "recording_mode, data_fields " - "FROM exercises " - "ORDER BY name COLLATE NOCASE, exercise_id;"; +TrainlogStatus trainlog_database_list_exercises(TrainlogDatabase *database, + TrainlogExercise *output, + size_t capacity, + size_t *output_count) { + static const char *const SQL = "SELECT exercise_id, name, tracking_mode, " + "recording_mode, data_fields " + "FROM exercises " + "ORDER BY name COLLATE NOCASE, exercise_id;"; sqlite3_stmt *statement = NULL; size_t count = 0U; int rc; - if (database == NULL || - database->connection == NULL || - output_count == NULL || + if (database == NULL || database->connection == NULL || output_count == NULL || (output == NULL && capacity != 0U)) { return TRAINLOG_STATUS_INVALID_ARGUMENT; } *output_count = 0U; - rc = sqlite3_prepare_v2( - database->connection, - SQL, - -1, - &statement, - NULL - ); + rc = sqlite3_prepare_v2(database->connection, SQL, -1, &statement, NULL); if (rc != SQLITE_OK) { return TRAINLOG_STATUS_DATABASE_ERROR; @@ -2653,61 +2866,35 @@ TrainlogStatus trainlog_database_list_exercises( while ((rc = sqlite3_step(statement)) == SQLITE_ROW) { if (output != NULL && count < capacity) { - const unsigned char *exercise_id = - sqlite3_column_text(statement, 0); - const unsigned char *name = - sqlite3_column_text(statement, 1); - const unsigned char *tracking = - sqlite3_column_text(statement, 2); - const unsigned char *recording = - sqlite3_column_text(statement, 3); - sqlite3_int64 data_fields = - sqlite3_column_int64(statement, 4); + const unsigned char *exercise_id = sqlite3_column_text(statement, 0); + const unsigned char *name = sqlite3_column_text(statement, 1); + const unsigned char *tracking = sqlite3_column_text(statement, 2); + const unsigned char *recording = sqlite3_column_text(statement, 3); + sqlite3_int64 data_fields = sqlite3_column_int64(statement, 4); - if (exercise_id == NULL || - name == NULL || - tracking == NULL || - recording == NULL || - data_fields < 0 || - (uint64_t)data_fields > (uint64_t)UINT32_MAX || - (((TrainlogExerciseDataFields)data_fields) & - ~TRAINLOG_EXERCISE_DATA_KNOWN_MASK) != 0U) { + if (exercise_id == NULL || name == NULL || tracking == NULL || recording == NULL || + data_fields < 0 || (uint64_t)data_fields > (uint64_t)UINT32_MAX || + (((TrainlogExerciseDataFields)data_fields) & ~TRAINLOG_EXERCISE_DATA_KNOWN_MASK) != + 0U) { (void)sqlite3_finalize(statement); return TRAINLOG_STATUS_DATABASE_ERROR; } - (void)memset( - &output[count], - 0, - sizeof(output[count]) - ); + (void)memset(&output[count], 0, sizeof(output[count])); + + (void)snprintf(output[count].exercise_id, + sizeof(output[count].exercise_id), + "%s", + (const char *)exercise_id); (void)snprintf( - output[count].exercise_id, - sizeof(output[count].exercise_id), - "%s", - (const char *)exercise_id - ); + output[count].name, sizeof(output[count].name), "%s", (const char *)name); - (void)snprintf( - output[count].name, - sizeof(output[count].name), - "%s", - (const char *)name - ); + output[count].tracking_mode = tracking_mode_from_sql((const char *)tracking); - output[count].tracking_mode = - tracking_mode_from_sql( - (const char *)tracking - ); + output[count].recording_mode = recording_mode_from_sql((const char *)recording); - output[count].recording_mode = - recording_mode_from_sql( - (const char *)recording - ); - - output[count].data_fields = - (TrainlogExerciseDataFields)data_fields; + output[count].data_fields = (TrainlogExerciseDataFields)data_fields; } ++count; @@ -2731,28 +2918,27 @@ TrainlogStatus trainlog_database_list_exercises( return TRAINLOG_STATUS_OK; } -TrainlogStatus trainlog_database_resolve_exercise_id( - TrainlogDatabase *database, - const char *exercise_id, - char *output_canonical_id, - size_t output_capacity -) -{ - static const char *const SQL = - "SELECT exercise_id FROM exercises WHERE exercise_id=?1 " - "UNION ALL SELECT canonical_exercise_id FROM exercise_aliases " - "WHERE source_exercise_id=?1 LIMIT 1;"; +TrainlogStatus trainlog_database_resolve_exercise_id(TrainlogDatabase *database, + const char *exercise_id, + char *output_canonical_id, + size_t output_capacity) { + static const char *const SQL = "SELECT exercise_id FROM exercises WHERE exercise_id=?1 " + "UNION ALL SELECT canonical_exercise_id FROM exercise_aliases " + "WHERE source_exercise_id=?1 LIMIT 1;"; sqlite3_stmt *statement = NULL; const unsigned char *canonical; int rc; if (database == NULL || database->connection == NULL || exercise_id == NULL || exercise_id[0] == '\0' || output_canonical_id == NULL || - output_capacity < TRAINLOG_ID_MAX + 1U) + output_capacity < TRAINLOG_ID_MAX + 1U) { return TRAINLOG_STATUS_INVALID_ARGUMENT; + } rc = sqlite3_prepare_v2(database->connection, SQL, -1, &statement, NULL); - if (rc != SQLITE_OK || sqlite3_bind_text(statement, 1, exercise_id, -1, - SQLITE_TRANSIENT) != SQLITE_OK) { - if (statement != NULL) (void)sqlite3_finalize(statement); + if (rc != SQLITE_OK || + sqlite3_bind_text(statement, 1, exercise_id, -1, SQLITE_TRANSIENT) != SQLITE_OK) { + if (statement != NULL) { + (void)sqlite3_finalize(statement); + } return TRAINLOG_STATUS_DATABASE_ERROR; } rc = sqlite3_step(statement); @@ -2766,42 +2952,37 @@ TrainlogStatus trainlog_database_resolve_exercise_id( return TRAINLOG_STATUS_DATABASE_ERROR; } (void)snprintf(output_canonical_id, output_capacity, "%s", canonical); - return sqlite3_finalize(statement) == SQLITE_OK ? TRAINLOG_STATUS_OK : - TRAINLOG_STATUS_DATABASE_ERROR; + return sqlite3_finalize(statement) == SQLITE_OK ? TRAINLOG_STATUS_OK + : TRAINLOG_STATUS_DATABASE_ERROR; } -static TrainlogStatus merge_bind_ids( - TrainlogDatabase *database, - const char *sql, - sqlite3_int64 source_row_id, - sqlite3_int64 canonical_row_id -) -{ +static TrainlogStatus merge_bind_ids(TrainlogDatabase *database, + const char *sql, + sqlite3_int64 source_row_id, + sqlite3_int64 canonical_row_id) { sqlite3_stmt *statement = NULL; int rc = sqlite3_prepare_v2(database->connection, sql, -1, &statement, NULL); if (rc != SQLITE_OK || sqlite3_bind_parameter_count(statement) < 1 || sqlite3_bind_int64(statement, 1, source_row_id) != SQLITE_OK || (sqlite3_bind_parameter_count(statement) >= 2 && sqlite3_bind_int64(statement, 2, canonical_row_id) != SQLITE_OK)) { - if (statement != NULL) (void)sqlite3_finalize(statement); + if (statement != NULL) { + (void)sqlite3_finalize(statement); + } return TRAINLOG_STATUS_DATABASE_ERROR; } rc = sqlite3_step(statement); if (rc != SQLITE_DONE) { (void)sqlite3_finalize(statement); - return rc == SQLITE_CONSTRAINT ? TRAINLOG_STATUS_CONFLICT : - TRAINLOG_STATUS_DATABASE_ERROR; + return rc == SQLITE_CONSTRAINT ? TRAINLOG_STATUS_CONFLICT : TRAINLOG_STATUS_DATABASE_ERROR; } - return sqlite3_finalize(statement) == SQLITE_OK ? TRAINLOG_STATUS_OK : - TRAINLOG_STATUS_DATABASE_ERROR; + return sqlite3_finalize(statement) == SQLITE_OK ? TRAINLOG_STATUS_OK + : TRAINLOG_STATUS_DATABASE_ERROR; } -TrainlogStatus trainlog_database_merge_exercises( - TrainlogDatabase *database, - const char *source_exercise_id, - const char *canonical_exercise_id -) -{ +TrainlogStatus trainlog_database_merge_exercises(TrainlogDatabase *database, + const char *source_exercise_id, + const char *canonical_exercise_id) { static const char *const PROFILE_SQL = "SELECT s.id,c.id,s.tracking_mode,c.tracking_mode," "s.recording_mode,c.recording_mode,s.data_fields,c.data_fields," @@ -2816,20 +2997,29 @@ TrainlogStatus trainlog_database_merge_exercises( char primary[TRAINLOG_ZONE_ID_MAX + 1U] = ""; TrainlogStatus status = TRAINLOG_STATUS_DATABASE_ERROR; int rc; - if (database == NULL || database->connection == NULL || - source_exercise_id == NULL || canonical_exercise_id == NULL || - source_exercise_id[0] == '\0' || canonical_exercise_id[0] == '\0' || - strcmp(source_exercise_id, canonical_exercise_id) == 0) + if (database == NULL || database->connection == NULL || source_exercise_id == NULL || + canonical_exercise_id == NULL || source_exercise_id[0] == '\0' || + canonical_exercise_id[0] == '\0' || + strcmp(source_exercise_id, canonical_exercise_id) == 0) { return TRAINLOG_STATUS_INVALID_ARGUMENT; - if (execute_sql(database, "BEGIN IMMEDIATE;") != TRAINLOG_STATUS_OK) + } + if (execute_sql(database, "BEGIN IMMEDIATE;") != TRAINLOG_STATUS_OK) { return TRAINLOG_STATUS_DATABASE_ERROR; + } rc = sqlite3_prepare_v2(database->connection, PROFILE_SQL, -1, &statement, NULL); if (rc != SQLITE_OK || sqlite3_bind_text(statement, 1, source_exercise_id, -1, SQLITE_TRANSIENT) != SQLITE_OK || - sqlite3_bind_text(statement, 2, canonical_exercise_id, -1, SQLITE_TRANSIENT) != SQLITE_OK) goto rollback; + sqlite3_bind_text(statement, 2, canonical_exercise_id, -1, SQLITE_TRANSIENT) != SQLITE_OK) { + goto rollback; + } rc = sqlite3_step(statement); - if (rc == SQLITE_DONE) { status = TRAINLOG_STATUS_NOT_FOUND; goto rollback; } - if (rc != SQLITE_ROW) goto rollback; + if (rc == SQLITE_DONE) { + status = TRAINLOG_STATUS_NOT_FOUND; + goto rollback; + } + if (rc != SQLITE_ROW) { + goto rollback; + } source_row_id = sqlite3_column_int64(statement, 0); canonical_row_id = sqlite3_column_int64(statement, 1); if (strcmp((const char *)sqlite3_column_text(statement, 2), @@ -2837,96 +3027,137 @@ TrainlogStatus trainlog_database_merge_exercises( strcmp((const char *)sqlite3_column_text(statement, 4), (const char *)sqlite3_column_text(statement, 5)) != 0 || sqlite3_column_int64(statement, 6) != sqlite3_column_int64(statement, 7)) { - status = TRAINLOG_STATUS_CONFLICT; goto rollback; + status = TRAINLOG_STATUS_CONFLICT; + goto rollback; } - if (sqlite3_column_type(statement, 8) != SQLITE_NULL) + if (sqlite3_column_type(statement, 8) != SQLITE_NULL) { source_primary = (const char *)sqlite3_column_text(statement, 8); - if (sqlite3_column_type(statement, 9) != SQLITE_NULL) + } + if (sqlite3_column_type(statement, 9) != SQLITE_NULL) { canonical_primary = (const char *)sqlite3_column_text(statement, 9); + } if (source_primary != NULL && canonical_primary != NULL && strcmp(source_primary, canonical_primary) != 0) { - status = TRAINLOG_STATUS_CONFLICT; goto rollback; + status = TRAINLOG_STATUS_CONFLICT; + goto rollback; + } + if (canonical_primary != NULL || source_primary != NULL) { + (void)snprintf(primary, + sizeof(primary), + "%s", + canonical_primary != NULL ? canonical_primary : source_primary); + } + if (sqlite3_finalize(statement) != SQLITE_OK) { + statement = NULL; + goto rollback; } - if (canonical_primary != NULL || source_primary != NULL) - (void)snprintf(primary, sizeof(primary), "%s", - canonical_primary != NULL ? canonical_primary : source_primary); - if (sqlite3_finalize(statement) != SQLITE_OK) { statement = NULL; goto rollback; } statement = NULL; /* INVARIANT: promote the chosen primary before unioning secondaries, so a * former secondary cannot mask it through the (exercise,zone) key. */ if (primary[0] != '\0') { status = merge_bind_ids(database, - "DELETE FROM exercise_body_zones WHERE exercise_row_id=?2 " - "AND zone_id=(SELECT zone_id FROM exercise_body_zones " - "WHERE exercise_row_id=?1 AND role='primary');", - source_row_id, canonical_row_id); - if (status != TRAINLOG_STATUS_OK) goto rollback; - rc = sqlite3_prepare_v2(database->connection, + "DELETE FROM exercise_body_zones WHERE exercise_row_id=?2 " + "AND zone_id=(SELECT zone_id FROM exercise_body_zones " + "WHERE exercise_row_id=?1 AND role='primary');", + source_row_id, + canonical_row_id); + if (status != TRAINLOG_STATUS_OK) { + goto rollback; + } + rc = sqlite3_prepare_v2( + database->connection, "INSERT OR REPLACE INTO exercise_body_zones(exercise_row_id,zone_id,role) " - "VALUES(?1,?2,'primary');", -1, &statement, NULL); + "VALUES(?1,?2,'primary');", + -1, + &statement, + NULL); if (rc != SQLITE_OK || sqlite3_bind_int64(statement, 1, canonical_row_id) != SQLITE_OK || sqlite3_bind_text(statement, 2, primary, -1, SQLITE_TRANSIENT) != SQLITE_OK || sqlite3_step(statement) != SQLITE_DONE || sqlite3_finalize(statement) != SQLITE_OK) { - statement = NULL; goto rollback; + statement = NULL; + goto rollback; } statement = NULL; } - status = merge_bind_ids(database, - "INSERT OR IGNORE INTO exercise_body_zones(exercise_row_id,zone_id,role) " - "SELECT ?2,zone_id,'secondary' FROM exercise_body_zones " - "WHERE exercise_row_id=?1 AND role='secondary' " - "AND zone_id<>(SELECT COALESCE((SELECT zone_id FROM exercise_body_zones " - "WHERE exercise_row_id=?2 AND role='primary'),''));", - source_row_id, canonical_row_id); - if (status != TRAINLOG_STATUS_OK) goto rollback; - status = merge_bind_ids(database, - "UPDATE session_exercises SET exercise_row_id=?2 WHERE exercise_row_id=?1;", - source_row_id, canonical_row_id); - if (status != TRAINLOG_STATUS_OK) goto rollback; + status = + merge_bind_ids(database, + "INSERT OR IGNORE INTO exercise_body_zones(exercise_row_id,zone_id,role) " + "SELECT ?2,zone_id,'secondary' FROM exercise_body_zones " + "WHERE exercise_row_id=?1 AND role='secondary' " + "AND zone_id<>(SELECT COALESCE((SELECT zone_id FROM exercise_body_zones " + "WHERE exercise_row_id=?2 AND role='primary'),''));", + source_row_id, + canonical_row_id); + if (status != TRAINLOG_STATUS_OK) { + goto rollback; + } + status = + merge_bind_ids(database, + "UPDATE session_exercises SET exercise_row_id=?2 WHERE exercise_row_id=?1;", + source_row_id, + canonical_row_id); + if (status != TRAINLOG_STATUS_OK) { + goto rollback; + } rc = sqlite3_prepare_v2(database->connection, - "UPDATE exercise_aliases SET canonical_exercise_id=?2 " - "WHERE canonical_exercise_id=?1;", -1, &statement, NULL); + "UPDATE exercise_aliases SET canonical_exercise_id=?2 " + "WHERE canonical_exercise_id=?1;", + -1, + &statement, + NULL); if (rc != SQLITE_OK || sqlite3_bind_text(statement, 1, source_exercise_id, -1, SQLITE_TRANSIENT) != SQLITE_OK || sqlite3_bind_text(statement, 2, canonical_exercise_id, -1, SQLITE_TRANSIENT) != SQLITE_OK || sqlite3_step(statement) != SQLITE_DONE || sqlite3_finalize(statement) != SQLITE_OK) { - statement = NULL; goto rollback; + statement = NULL; + goto rollback; } statement = NULL; - rc = sqlite3_prepare_v2(database->connection, - "INSERT INTO exercise_aliases(source_exercise_id,canonical_exercise_id) " - "VALUES(?1,?2);", -1, &statement, NULL); + rc = + sqlite3_prepare_v2(database->connection, + "INSERT INTO exercise_aliases(source_exercise_id,canonical_exercise_id) " + "VALUES(?1,?2);", + -1, + &statement, + NULL); if (rc != SQLITE_OK || sqlite3_bind_text(statement, 1, source_exercise_id, -1, SQLITE_TRANSIENT) != SQLITE_OK || sqlite3_bind_text(statement, 2, canonical_exercise_id, -1, SQLITE_TRANSIENT) != SQLITE_OK || sqlite3_step(statement) != SQLITE_DONE || sqlite3_finalize(statement) != SQLITE_OK) { - statement = NULL; goto rollback; + statement = NULL; + goto rollback; } statement = NULL; status = merge_bind_ids(database, - "DELETE FROM exercise_body_zone_sync WHERE exercise_row_id IN(?1,?2);", - source_row_id, canonical_row_id); - if (status != TRAINLOG_STATUS_OK) goto rollback; - status = merge_bind_ids(database, "DELETE FROM exercises WHERE id=?1;", - source_row_id, canonical_row_id); - if (status != TRAINLOG_STATUS_OK) goto rollback; - if (execute_sql(database, "COMMIT;") != TRAINLOG_STATUS_OK) goto rollback; + "DELETE FROM exercise_body_zone_sync WHERE exercise_row_id IN(?1,?2);", + source_row_id, + canonical_row_id); + if (status != TRAINLOG_STATUS_OK) { + goto rollback; + } + status = merge_bind_ids( + database, "DELETE FROM exercises WHERE id=?1;", source_row_id, canonical_row_id); + if (status != TRAINLOG_STATUS_OK) { + goto rollback; + } + if (execute_sql(database, "COMMIT;") != TRAINLOG_STATUS_OK) { + goto rollback; + } return TRAINLOG_STATUS_OK; rollback: - if (statement != NULL) (void)sqlite3_finalize(statement); + if (statement != NULL) { + (void)sqlite3_finalize(statement); + } (void)sqlite3_exec(database->connection, "ROLLBACK;", NULL, NULL, NULL); return status; } -TrainlogStatus trainlog_database_preview_exercise_merge( - TrainlogDatabase *database, - const char *source_exercise_id, - TrainlogExerciseMergePreview *output -) -{ +TrainlogStatus trainlog_database_preview_exercise_merge(TrainlogDatabase *database, + const char *source_exercise_id, + TrainlogExerciseMergePreview *output) { static const char *const SQL = "SELECT " "(SELECT COUNT(*) FROM session_exercises se WHERE se.exercise_row_id=e.id)," @@ -2944,13 +3175,16 @@ TrainlogStatus trainlog_database_preview_exercise_merge( sqlite3_stmt *statement = NULL; TrainlogExerciseMergePreview preview = {0}; int rc; - if (database == NULL || database->connection == NULL || - source_exercise_id == NULL || source_exercise_id[0] == '\0' || - output == NULL) return TRAINLOG_STATUS_INVALID_ARGUMENT; + if (database == NULL || database->connection == NULL || source_exercise_id == NULL || + source_exercise_id[0] == '\0' || output == NULL) { + return TRAINLOG_STATUS_INVALID_ARGUMENT; + } rc = sqlite3_prepare_v2(database->connection, SQL, -1, &statement, NULL); - if (rc != SQLITE_OK || sqlite3_bind_text(statement, 1, source_exercise_id, - -1, SQLITE_TRANSIENT) != SQLITE_OK) { - if (statement != NULL) (void)sqlite3_finalize(statement); + if (rc != SQLITE_OK || + sqlite3_bind_text(statement, 1, source_exercise_id, -1, SQLITE_TRANSIENT) != SQLITE_OK) { + if (statement != NULL) { + (void)sqlite3_finalize(statement); + } return TRAINLOG_STATUS_DATABASE_ERROR; } rc = sqlite3_step(statement); @@ -2968,39 +3202,29 @@ TrainlogStatus trainlog_database_preview_exercise_merge( preview.max_results = (size_t)sqlite3_column_int64(statement, 3); preview.associated_equipment = (size_t)sqlite3_column_int64(statement, 4); preview.body_zones = (size_t)sqlite3_column_int64(statement, 5); - if (sqlite3_finalize(statement) != SQLITE_OK) + if (sqlite3_finalize(statement) != SQLITE_OK) { return TRAINLOG_STATUS_DATABASE_ERROR; + } *output = preview; return TRAINLOG_STATUS_OK; } -static TrainlogStatus lookup_exercise_row_id( - TrainlogDatabase *database, - const char *exercise_id, - sqlite3_int64 *output_row_id -) -{ +static TrainlogStatus lookup_exercise_row_id(TrainlogDatabase *database, + const char *exercise_id, + sqlite3_int64 *output_row_id) { sqlite3_stmt *statement = NULL; int rc; - rc = sqlite3_prepare_v2( - database->connection, - "SELECT id FROM exercises WHERE exercise_id = ?1;", - -1, - &statement, - NULL - ); + rc = sqlite3_prepare_v2(database->connection, + "SELECT id FROM exercises WHERE exercise_id = ?1;", + -1, + &statement, + NULL); if (rc != SQLITE_OK) { return TRAINLOG_STATUS_DATABASE_ERROR; } - if (sqlite3_bind_text( - statement, - 1, - exercise_id, - -1, - SQLITE_TRANSIENT - ) != SQLITE_OK) { + if (sqlite3_bind_text(statement, 1, exercise_id, -1, SQLITE_TRANSIENT) != SQLITE_OK) { (void)sqlite3_finalize(statement); return TRAINLOG_STATUS_DATABASE_ERROR; } @@ -3017,32 +3241,33 @@ static TrainlogStatus lookup_exercise_row_id( *output_row_id = sqlite3_column_int64(statement, 0); - return sqlite3_finalize(statement) == SQLITE_OK - ? TRAINLOG_STATUS_OK - : TRAINLOG_STATUS_DATABASE_ERROR; + return sqlite3_finalize(statement) == SQLITE_OK ? TRAINLOG_STATUS_OK + : TRAINLOG_STATUS_DATABASE_ERROR; } -static bool assignable_body_zone(const char *zone_id) -{ +static bool assignable_body_zone(const char *zone_id) { const TrainlogBodyZone *zone; - if (zone_id == NULL || zone_id[0] == '\0' || - strlen(zone_id) > TRAINLOG_ZONE_ID_MAX) return false; + if (zone_id == NULL || zone_id[0] == '\0' || strlen(zone_id) > TRAINLOG_ZONE_ID_MAX) { + return false; + } zone = trainlog_body_zone_catalog_lookup(zone_id); return zone != NULL && !zone->is_group; } -static TrainlogStatus insert_body_zone_relation( - TrainlogDatabase *database, - sqlite3_int64 exercise_row_id, - const char *zone_id, - const char *role -) -{ +static TrainlogStatus insert_body_zone_relation(TrainlogDatabase *database, + sqlite3_int64 exercise_row_id, + const char *zone_id, + const char *role) { sqlite3_stmt *statement = NULL; int rc = sqlite3_prepare_v2(database->connection, - "INSERT INTO exercise_body_zones(exercise_row_id,zone_id,role) " - "VALUES(?1,?2,?3);", -1, &statement, NULL); - if (rc != SQLITE_OK) return TRAINLOG_STATUS_DATABASE_ERROR; + "INSERT INTO exercise_body_zones(exercise_row_id,zone_id,role) " + "VALUES(?1,?2,?3);", + -1, + &statement, + NULL); + if (rc != SQLITE_OK) { + return TRAINLOG_STATUS_DATABASE_ERROR; + } if (sqlite3_bind_int64(statement, 1, exercise_row_id) != SQLITE_OK || sqlite3_bind_text(statement, 2, zone_id, -1, SQLITE_TRANSIENT) != SQLITE_OK || sqlite3_bind_text(statement, 3, role, -1, SQLITE_STATIC) != SQLITE_OK) { @@ -3051,19 +3276,16 @@ static TrainlogStatus insert_body_zone_relation( } rc = sqlite3_step(statement); (void)sqlite3_finalize(statement); - return rc == SQLITE_DONE ? TRAINLOG_STATUS_OK : - rc == SQLITE_CONSTRAINT ? TRAINLOG_STATUS_CONFLICT : - TRAINLOG_STATUS_DATABASE_ERROR; + return rc == SQLITE_DONE ? TRAINLOG_STATUS_OK + : rc == SQLITE_CONSTRAINT ? TRAINLOG_STATUS_CONFLICT + : TRAINLOG_STATUS_DATABASE_ERROR; } -TrainlogStatus trainlog_database_replace_exercise_body_zones( - TrainlogDatabase *database, - const char *exercise_id, - const char *primary_zone_id, - const char *const *secondary_zone_ids, - size_t secondary_count -) -{ +TrainlogStatus trainlog_database_replace_exercise_body_zones(TrainlogDatabase *database, + const char *exercise_id, + const char *primary_zone_id, + const char *const *secondary_zone_ids, + size_t secondary_count) { sqlite3_int64 row_id; sqlite3_stmt *statement = NULL; bool owns_transaction; @@ -3073,8 +3295,7 @@ TrainlogStatus trainlog_database_replace_exercise_body_zones( if (database == NULL || database->connection == NULL || exercise_id == NULL || exercise_id[0] == '\0' || secondary_count > trainlog_body_zone_catalog_count() || (secondary_count > 0U && secondary_zone_ids == NULL) || - (secondary_count > 0U && - (primary_zone_id == NULL || primary_zone_id[0] == '\0')) || + (secondary_count > 0U && (primary_zone_id == NULL || primary_zone_id[0] == '\0')) || (primary_zone_id != NULL && primary_zone_id[0] != '\0' && !assignable_body_zone(primary_zone_id))) { return TRAINLOG_STATUS_INVALID_ARGUMENT; @@ -3086,22 +3307,30 @@ TrainlogStatus trainlog_database_replace_exercise_body_zones( return TRAINLOG_STATUS_INVALID_ARGUMENT; } for (earlier = 0U; earlier < index; ++earlier) { - if (strcmp(secondary_zone_ids[earlier], secondary_zone_ids[index]) == 0) + if (strcmp(secondary_zone_ids[earlier], secondary_zone_ids[index]) == 0) { return TRAINLOG_STATUS_INVALID_ARGUMENT; + } } } status = lookup_exercise_row_id(database, exercise_id, &row_id); - if (status != TRAINLOG_STATUS_OK) return status; + if (status != TRAINLOG_STATUS_OK) { + return status; + } owns_transaction = sqlite3_get_autocommit(database->connection) != 0; - if (owns_transaction && execute_sql(database, "BEGIN IMMEDIATE;") != TRAINLOG_STATUS_OK) + if (owns_transaction && execute_sql(database, "BEGIN IMMEDIATE;") != TRAINLOG_STATUS_OK) { return TRAINLOG_STATUS_DATABASE_ERROR; + } if (sqlite3_prepare_v2(database->connection, - "DELETE FROM exercise_body_zones WHERE exercise_row_id=?1;", - -1, &statement, NULL) != SQLITE_OK || + "DELETE FROM exercise_body_zones WHERE exercise_row_id=?1;", + -1, + &statement, + NULL) != SQLITE_OK || sqlite3_bind_int64(statement, 1, row_id) != SQLITE_OK || sqlite3_step(statement) != SQLITE_DONE) { - if (statement != NULL) (void)sqlite3_finalize(statement); + if (statement != NULL) { + (void)sqlite3_finalize(statement); + } status = TRAINLOG_STATUS_DATABASE_ERROR; goto finish; } @@ -3113,12 +3342,16 @@ TrainlogStatus trainlog_database_replace_exercise_body_zones( statement = NULL; if (primary_zone_id != NULL && primary_zone_id[0] != '\0') { status = insert_body_zone_relation(database, row_id, primary_zone_id, "primary"); - if (status != TRAINLOG_STATUS_OK) goto finish; + if (status != TRAINLOG_STATUS_OK) { + goto finish; + } } for (index = 0U; index < secondary_count; ++index) { - status = insert_body_zone_relation(database, row_id, - secondary_zone_ids[index], "secondary"); - if (status != TRAINLOG_STATUS_OK) goto finish; + status = + insert_body_zone_relation(database, row_id, secondary_zone_ids[index], "secondary"); + if (status != TRAINLOG_STATUS_OK) { + goto finish; + } } status = TRAINLOG_STATUS_OK; @@ -3136,14 +3369,11 @@ finish: return status; } -TrainlogStatus trainlog_database_list_exercise_body_zones( - TrainlogDatabase *database, - const char *exercise_id, - TrainlogExerciseBodyZone *output, - size_t capacity, - size_t *output_count -) -{ +TrainlogStatus trainlog_database_list_exercise_body_zones(TrainlogDatabase *database, + const char *exercise_id, + TrainlogExerciseBodyZone *output, + size_t capacity, + size_t *output_count) { sqlite3_stmt *statement = NULL; sqlite3_int64 row_id; size_t count = 0U; @@ -3151,83 +3381,95 @@ TrainlogStatus trainlog_database_list_exercise_body_zones( TrainlogStatus status; int rc; if (database == NULL || exercise_id == NULL || output_count == NULL || - (capacity > 0U && output == NULL)) return TRAINLOG_STATUS_INVALID_ARGUMENT; + (capacity > 0U && output == NULL)) { + return TRAINLOG_STATUS_INVALID_ARGUMENT; + } *output_count = 0U; status = lookup_exercise_row_id(database, exercise_id, &row_id); - if (status != TRAINLOG_STATUS_OK) return status; + if (status != TRAINLOG_STATUS_OK) { + return status; + } rc = sqlite3_prepare_v2(database->connection, - "SELECT zone_id,role FROM exercise_body_zones WHERE exercise_row_id=?1 " - "ORDER BY CASE role WHEN 'primary' THEN 0 ELSE 1 END,zone_id;", - -1, &statement, NULL); + "SELECT zone_id,role FROM exercise_body_zones WHERE exercise_row_id=?1 " + "ORDER BY CASE role WHEN 'primary' THEN 0 ELSE 1 END,zone_id;", + -1, + &statement, + NULL); if (rc != SQLITE_OK || sqlite3_bind_int64(statement, 1, row_id) != SQLITE_OK) { - if (statement != NULL) (void)sqlite3_finalize(statement); + if (statement != NULL) { + (void)sqlite3_finalize(statement); + } return TRAINLOG_STATUS_DATABASE_ERROR; } while ((rc = sqlite3_step(statement)) == SQLITE_ROW) { const char *zone_id = (const char *)sqlite3_column_text(statement, 0); const char *role = (const char *)sqlite3_column_text(statement, 1); const TrainlogBodyZone *zone = trainlog_body_zone_catalog_lookup(zone_id); - if (zone_id == NULL || role == NULL || - zone == NULL || zone->is_group || + if (zone_id == NULL || role == NULL || zone == NULL || zone->is_group || (strcmp(role, "primary") != 0 && strcmp(role, "secondary") != 0)) { (void)sqlite3_finalize(statement); return TRAINLOG_STATUS_DATABASE_ERROR; } if (count < capacity) { (void)memset(&output[count], 0, sizeof(output[count])); - (void)snprintf(output[count].zone_id, sizeof(output[count].zone_id), - "%s", zone_id); - output[count].role = strcmp(role, "primary") == 0 - ? TRAINLOG_BODY_ZONE_PRIMARY : TRAINLOG_BODY_ZONE_SECONDARY; + (void)snprintf(output[count].zone_id, sizeof(output[count].zone_id), "%s", zone_id); + output[count].role = strcmp(role, "primary") == 0 ? TRAINLOG_BODY_ZONE_PRIMARY + : TRAINLOG_BODY_ZONE_SECONDARY; + } + if (strcmp(role, "primary") == 0) { + has_primary = true; } - if (strcmp(role, "primary") == 0) has_primary = true; ++count; } - if (rc != SQLITE_DONE || sqlite3_finalize(statement) != SQLITE_OK) + if (rc != SQLITE_DONE || sqlite3_finalize(statement) != SQLITE_OK) { return TRAINLOG_STATUS_DATABASE_ERROR; + } /* INVARIANT: the explicitly unclassified state has no relations. A * secondary-only raw SQLite state is corruption, not partial metadata. */ - if (count > 0U && !has_primary) return TRAINLOG_STATUS_DATABASE_ERROR; + if (count > 0U && !has_primary) { + return TRAINLOG_STATUS_DATABASE_ERROR; + } *output_count = count; return count > capacity ? TRAINLOG_STATUS_INVALID_ARGUMENT : TRAINLOG_STATUS_OK; } -static bool exercise_row_matches_body_zone( - TrainlogDatabase *database, - sqlite3_int64 row_id, - const char *zone_id, - bool include_descendants, - bool primary_only, - bool unclassified_only, - bool *output_match -) -{ +static bool exercise_row_matches_body_zone(TrainlogDatabase *database, + sqlite3_int64 row_id, + const char *zone_id, + bool include_descendants, + bool primary_only, + bool unclassified_only, + bool *output_match) { sqlite3_stmt *statement = NULL; int rc; bool has_relation = false; bool has_primary = false; bool match = false; if (sqlite3_prepare_v2(database->connection, - "SELECT zone_id,role FROM exercise_body_zones WHERE exercise_row_id=?1;", - -1, &statement, NULL) != SQLITE_OK || + "SELECT zone_id,role FROM exercise_body_zones WHERE exercise_row_id=?1;", + -1, + &statement, + NULL) != SQLITE_OK || sqlite3_bind_int64(statement, 1, row_id) != SQLITE_OK) { - if (statement != NULL) (void)sqlite3_finalize(statement); + if (statement != NULL) { + (void)sqlite3_finalize(statement); + } return false; } while ((rc = sqlite3_step(statement)) == SQLITE_ROW) { const char *candidate = (const char *)sqlite3_column_text(statement, 0); const char *role = (const char *)sqlite3_column_text(statement, 1); const TrainlogBodyZone *zone = trainlog_body_zone_catalog_lookup(candidate); - if (candidate == NULL || role == NULL || - zone == NULL || zone->is_group || + if (candidate == NULL || role == NULL || zone == NULL || zone->is_group || (strcmp(role, "primary") != 0 && strcmp(role, "secondary") != 0)) { (void)sqlite3_finalize(statement); return false; } has_relation = true; - if (strcmp(role, "primary") == 0) has_primary = true; - if (!unclassified_only && - (!primary_only || strcmp(role, "primary") == 0) && + if (strcmp(role, "primary") == 0) { + has_primary = true; + } + if (!unclassified_only && (!primary_only || strcmp(role, "primary") == 0) && (strcmp(candidate, zone_id) == 0 || (include_descendants && trainlog_body_zone_catalog_is_descendant(candidate, zone_id)))) { @@ -3235,54 +3477,63 @@ static bool exercise_row_matches_body_zone( } } if (rc != SQLITE_DONE || sqlite3_finalize(statement) != SQLITE_OK || - (has_relation && !has_primary)) return false; + (has_relation && !has_primary)) { + return false; + } *output_match = unclassified_only ? !has_relation : match; return true; } -TrainlogStatus trainlog_database_list_exercises_filtered( - TrainlogDatabase *database, - const char *normalized_prefix, - const char *zone_id, - bool include_descendants, - bool primary_only, - bool unclassified_only, - TrainlogExercise *output, - size_t capacity, - size_t *output_count -) -{ +TrainlogStatus trainlog_database_list_exercises_filtered(TrainlogDatabase *database, + const char *normalized_prefix, + const char *zone_id, + bool include_descendants, + bool primary_only, + bool unclassified_only, + TrainlogExercise *output, + size_t capacity, + size_t *output_count) { static const char *const SQL = "SELECT id,exercise_id,name,tracking_mode,recording_mode,data_fields,normalized_name " - "FROM exercises e WHERE NOT EXISTS(SELECT 1 FROM sync_causal_state s WHERE s.target_kind='exercise' AND s.target_id=e.exercise_id AND s.deleted=1) ORDER BY name COLLATE NOCASE,exercise_id;"; + "FROM exercises e WHERE NOT EXISTS(SELECT 1 FROM sync_causal_state s WHERE " + "s.target_kind='exercise' AND s.target_id=e.exercise_id AND s.deleted=1) ORDER BY name " + "COLLATE NOCASE,exercise_id;"; sqlite3_stmt *statement = NULL; size_t count = 0U; size_t prefix_length; int rc; if (database == NULL || normalized_prefix == NULL || output_count == NULL || - (capacity > 0U && output == NULL) || - (unclassified_only && zone_id != NULL) || + (capacity > 0U && output == NULL) || (unclassified_only && zone_id != NULL) || (!unclassified_only && zone_id != NULL && - trainlog_body_zone_catalog_lookup(zone_id) == NULL)) + trainlog_body_zone_catalog_lookup(zone_id) == NULL)) { return TRAINLOG_STATUS_INVALID_ARGUMENT; + } prefix_length = strlen(normalized_prefix); *output_count = 0U; - if (sqlite3_prepare_v2(database->connection, SQL, -1, &statement, NULL) != SQLITE_OK) + if (sqlite3_prepare_v2(database->connection, SQL, -1, &statement, NULL) != SQLITE_OK) { return TRAINLOG_STATUS_DATABASE_ERROR; + } while ((rc = sqlite3_step(statement)) == SQLITE_ROW) { sqlite3_int64 row_id = sqlite3_column_int64(statement, 0); const char *normalized = (const char *)sqlite3_column_text(statement, 6); bool matches = zone_id == NULL && !unclassified_only; sqlite3_int64 data_fields = sqlite3_column_int64(statement, 5); - if (normalized == NULL || strncmp(normalized, normalized_prefix, prefix_length) != 0) + if (normalized == NULL || strncmp(normalized, normalized_prefix, prefix_length) != 0) { continue; - if (!matches && !exercise_row_matches_body_zone(database, row_id, - zone_id != NULL ? zone_id : "", include_descendants, - primary_only, unclassified_only, &matches)) { + } + if (!matches && !exercise_row_matches_body_zone(database, + row_id, + zone_id != NULL ? zone_id : "", + include_descendants, + primary_only, + unclassified_only, + &matches)) { (void)sqlite3_finalize(statement); return TRAINLOG_STATUS_DATABASE_ERROR; } - if (!matches) continue; + if (!matches) { + continue; + } if (data_fields < 0 || (uint64_t)data_fields > UINT32_MAX) { (void)sqlite3_finalize(statement); return TRAINLOG_STATUS_DATABASE_ERROR; @@ -3290,59 +3541,50 @@ TrainlogStatus trainlog_database_list_exercises_filtered( if (count < capacity) { TrainlogExercise *item = &output[count]; (void)memset(item, 0, sizeof(*item)); - (void)snprintf(item->exercise_id, sizeof(item->exercise_id), "%s", - (const char *)sqlite3_column_text(statement, 1)); - (void)snprintf(item->name, sizeof(item->name), "%s", - (const char *)sqlite3_column_text(statement, 2)); - item->tracking_mode = tracking_mode_from_sql( - (const char *)sqlite3_column_text(statement, 3)); - item->recording_mode = recording_mode_from_sql( - (const char *)sqlite3_column_text(statement, 4)); + (void)snprintf(item->exercise_id, + sizeof(item->exercise_id), + "%s", + (const char *)sqlite3_column_text(statement, 1)); + (void)snprintf(item->name, + sizeof(item->name), + "%s", + (const char *)sqlite3_column_text(statement, 2)); + item->tracking_mode = + tracking_mode_from_sql((const char *)sqlite3_column_text(statement, 3)); + item->recording_mode = + recording_mode_from_sql((const char *)sqlite3_column_text(statement, 4)); item->data_fields = (TrainlogExerciseDataFields)data_fields; } ++count; } - if (rc != SQLITE_DONE || sqlite3_finalize(statement) != SQLITE_OK) + if (rc != SQLITE_DONE || sqlite3_finalize(statement) != SQLITE_OK) { return TRAINLOG_STATUS_DATABASE_ERROR; + } *output_count = count; return count > capacity ? TRAINLOG_STATUS_INVALID_ARGUMENT : TRAINLOG_STATUS_OK; } -static TrainlogStatus lookup_session_row_id( - TrainlogDatabase *database, - const char *session_id, - sqlite3_int64 *output_row_id -) -{ +static TrainlogStatus lookup_session_row_id(TrainlogDatabase *database, + const char *session_id, + sqlite3_int64 *output_row_id) { sqlite3_stmt *statement = NULL; int rc; - if (database == NULL || - database->connection == NULL || - session_id == NULL || - session_id[0] == '\0' || - output_row_id == NULL) { + if (database == NULL || database->connection == NULL || session_id == NULL || + session_id[0] == '\0' || output_row_id == NULL) { return TRAINLOG_STATUS_INVALID_ARGUMENT; } - rc = sqlite3_prepare_v2( - database->connection, - "SELECT id FROM sessions WHERE session_id = ?1;", - -1, - &statement, - NULL - ); + rc = sqlite3_prepare_v2(database->connection, + "SELECT id FROM sessions WHERE session_id = ?1;", + -1, + &statement, + NULL); if (rc != SQLITE_OK) { return TRAINLOG_STATUS_DATABASE_ERROR; } - rc = sqlite3_bind_text( - statement, - 1, - session_id, - -1, - SQLITE_TRANSIENT - ); + rc = sqlite3_bind_text(statement, 1, session_id, -1, SQLITE_TRANSIENT); if (rc != SQLITE_OK) { (void)sqlite3_finalize(statement); return TRAINLOG_STATUS_DATABASE_ERROR; @@ -3360,108 +3602,54 @@ static TrainlogStatus lookup_session_row_id( return TRAINLOG_STATUS_DATABASE_ERROR; } - *output_row_id = - sqlite3_column_int64(statement, 0); + *output_row_id = sqlite3_column_int64(statement, 0); - return sqlite3_finalize(statement) == SQLITE_OK - ? TRAINLOG_STATUS_OK - : TRAINLOG_STATUS_DATABASE_ERROR; + return sqlite3_finalize(statement) == SQLITE_OK ? TRAINLOG_STATUS_OK + : TRAINLOG_STATUS_DATABASE_ERROR; } -static TrainlogStatus insert_session_header( - TrainlogDatabase *database, - const TrainlogSessionInput *session, - sqlite3_int64 *output_row_id -) -{ - static const char *const SQL = - "INSERT INTO sessions(" - "session_id, started_at, ended_at, session_type, notes" - ") VALUES(?1, ?2, ?3, ?4, ?5);"; +static TrainlogStatus insert_session_header(TrainlogDatabase *database, + const TrainlogSessionInput *session, + sqlite3_int64 *output_row_id) { + static const char *const SQL = "INSERT INTO sessions(" + "session_id, started_at, ended_at, session_type, notes" + ") VALUES(?1, ?2, ?3, ?4, ?5);"; sqlite3_stmt *statement = NULL; const char *session_type; int rc; - session_type = - session_type_to_sql( - session->session_type - ); + session_type = session_type_to_sql(session->session_type); if (session_type == NULL) { return TRAINLOG_STATUS_INVALID_ARGUMENT; } - rc = sqlite3_prepare_v2( - database->connection, - SQL, - -1, - &statement, - NULL - ); + rc = sqlite3_prepare_v2(database->connection, SQL, -1, &statement, NULL); if (rc != SQLITE_OK) { return TRAINLOG_STATUS_DATABASE_ERROR; } - rc = sqlite3_bind_text( - statement, - 1, - session->session_id, - -1, - SQLITE_TRANSIENT - ); + rc = sqlite3_bind_text(statement, 1, session->session_id, -1, SQLITE_TRANSIENT); if (rc == SQLITE_OK) { - rc = sqlite3_bind_text( - statement, - 2, - session->started_at, - -1, - SQLITE_TRANSIENT - ); + rc = sqlite3_bind_text(statement, 2, session->started_at, -1, SQLITE_TRANSIENT); } if (rc == SQLITE_OK) { - rc = - session->ended_at[0] != '\0' - ? sqlite3_bind_text( - statement, - 3, - session->ended_at, - -1, - SQLITE_TRANSIENT - ) - : sqlite3_bind_null( - statement, - 3 - ); + rc = session->ended_at[0] != '\0' + ? sqlite3_bind_text(statement, 3, session->ended_at, -1, SQLITE_TRANSIENT) + : sqlite3_bind_null(statement, 3); } if (rc == SQLITE_OK) { - rc = sqlite3_bind_text( - statement, - 4, - session_type, - -1, - SQLITE_STATIC - ); + rc = sqlite3_bind_text(statement, 4, session_type, -1, SQLITE_STATIC); } if (rc == SQLITE_OK) { - rc = - session->notes != NULL && - session->notes[0] != '\0' - ? sqlite3_bind_text( - statement, - 5, - session->notes, - -1, - SQLITE_TRANSIENT - ) - : sqlite3_bind_null( - statement, - 5 - ); + rc = session->notes != NULL && session->notes[0] != '\0' + ? sqlite3_bind_text(statement, 5, session->notes, -1, SQLITE_TRANSIENT) + : sqlite3_bind_null(statement, 5); } if (rc != SQLITE_OK) { @@ -3485,22 +3673,16 @@ static TrainlogStatus insert_session_header( return TRAINLOG_STATUS_DATABASE_ERROR; } - *output_row_id = - sqlite3_last_insert_rowid( - database->connection - ); + *output_row_id = sqlite3_last_insert_rowid(database->connection); return TRAINLOG_STATUS_OK; } -static TrainlogStatus insert_session_exercise( - TrainlogDatabase *database, - sqlite3_int64 session_row_id, - size_t position, - const TrainlogSessionExerciseInput *input, - sqlite3_int64 *output_row_id -) -{ +static TrainlogStatus insert_session_exercise(TrainlogDatabase *database, + sqlite3_int64 session_row_id, + size_t position, + const TrainlogSessionExerciseInput *input, + sqlite3_int64 *output_row_id) { sqlite3_stmt *statement = NULL; sqlite3_int64 exercise_row_id; const char *load_mode; @@ -3511,10 +3693,8 @@ static TrainlogStatus insert_session_exercise( int rc; TrainlogStatus status; - if (input == NULL || - output_row_id == NULL || - (input->data_fields & - ~TRAINLOG_EXERCISE_DATA_KNOWN_MASK) != 0U) { + if (input == NULL || output_row_id == NULL || + (input->data_fields & ~TRAINLOG_EXERCISE_DATA_KNOWN_MASK) != 0U) { return TRAINLOG_STATUS_INVALID_ARGUMENT; } @@ -3529,8 +3709,7 @@ static TrainlogStatus insert_session_exercise( * one occurrence. Local TUI creation therefore allocates it exactly once * at persistence, while imported V2 identities pass through unchanged. */ if (input->entry_id[0] == '\0') { - status = trainlog_id_generate("sxe", generated_entry_id, - sizeof(generated_entry_id)); + status = trainlog_id_generate("sxe", generated_entry_id, sizeof(generated_entry_id)); if (status != TRAINLOG_STATUS_OK) { return status; } @@ -3539,134 +3718,82 @@ static TrainlogStatus insert_session_exercise( entry_id = input->entry_id; } - load_mode = - load_mode_to_sql( - input->load_mode - ); + load_mode = load_mode_to_sql(input->load_mode); - recording_mode = - recording_mode_to_sql( - input->recording_mode - ); + recording_mode = recording_mode_to_sql(input->recording_mode); tracking_mode = tracking_mode_to_sql(input->tracking_mode); - if (load_mode == NULL || - recording_mode == NULL || - tracking_mode == NULL) { + if (load_mode == NULL || recording_mode == NULL || tracking_mode == NULL) { return TRAINLOG_STATUS_INVALID_ARGUMENT; } if (input->has_max_weight) { - if (!isfinite(input->max_weight_kg) || - input->max_weight_kg <= 0.0 || - input->rest_seconds != 0 || - input->target_sets != 0 || - input->target_reps != 0 || - input->target_duration_seconds != 0 || - input->target_has_weight || - input->set_count != 0U || - input->continuous_duration_seconds != 0 || - input->continuous_has_speed || - input->continuous_has_distance) { + if (!isfinite(input->max_weight_kg) || input->max_weight_kg <= 0.0 || + input->rest_seconds != 0 || input->target_sets != 0 || input->target_reps != 0 || + input->target_duration_seconds != 0 || input->target_has_weight || + input->set_count != 0U || input->continuous_duration_seconds != 0 || + input->continuous_has_speed || input->continuous_has_distance) { return TRAINLOG_STATUS_INVALID_ARGUMENT; } - } else if (input->recording_mode == - TRAINLOG_RECORDING_CONTINUOUS) { - bool speed_required = - (input->data_fields & - TRAINLOG_EXERCISE_DATA_SPEED_KMH) != 0U; + } else if (input->recording_mode == TRAINLOG_RECORDING_CONTINUOUS) { + bool speed_required = (input->data_fields & TRAINLOG_EXERCISE_DATA_SPEED_KMH) != 0U; - bool distance_required = - (input->data_fields & - TRAINLOG_EXERCISE_DATA_DISTANCE_KM) != 0U; + bool distance_required = (input->data_fields & TRAINLOG_EXERCISE_DATA_DISTANCE_KM) != 0U; - if (input->load_mode != TRAINLOG_LOAD_NONE || - input->rest_seconds != 0 || - input->target_sets != 0 || - input->target_reps != 0 || - input->target_duration_seconds != 0 || - input->target_has_weight || - input->set_count != 0U || - input->continuous_duration_seconds <= 0 || + if (input->load_mode != TRAINLOG_LOAD_NONE || input->rest_seconds != 0 || + input->target_sets != 0 || input->target_reps != 0 || + input->target_duration_seconds != 0 || input->target_has_weight || + input->set_count != 0U || input->continuous_duration_seconds <= 0 || input->continuous_has_speed != speed_required || input->continuous_has_distance != distance_required || - (input->continuous_has_speed && - input->continuous_speed_kmh <= 0.0) || - (input->continuous_has_distance && - input->continuous_distance_km <= 0.0)) { + (input->continuous_has_speed && input->continuous_speed_kmh <= 0.0) || + (input->continuous_has_distance && input->continuous_distance_km <= 0.0)) { return TRAINLOG_STATUS_INVALID_ARGUMENT; } - } else if ( - input->recording_mode == - TRAINLOG_RECORDING_SETS - ) { - bool has_target_sets = - input->target_sets > 0; + } else if (input->recording_mode == TRAINLOG_RECORDING_SETS) { + bool has_target_sets = input->target_sets > 0; - bool has_target_reps = - input->target_reps > 0; + bool has_target_reps = input->target_reps > 0; - bool has_target_duration = - input->target_duration_seconds > 0; + bool has_target_duration = input->target_duration_seconds > 0; - bool any_target = - has_target_sets || - has_target_reps || - has_target_duration; + bool any_target = has_target_sets || has_target_reps || has_target_duration; bool target_valid = - !any_target || - ( - has_target_sets && - ( - has_target_reps != - has_target_duration - ) - ); + !any_target || (has_target_sets && (has_target_reps != has_target_duration)); - if ( - input->target_sets < 0 || - input->target_reps < 0 || - input->target_duration_seconds < 0 || - !target_valid || - input->continuous_duration_seconds != 0 || - input->continuous_has_speed || - input->continuous_has_distance - ) { + if (input->target_sets < 0 || input->target_reps < 0 || + input->target_duration_seconds < 0 || !target_valid || + input->continuous_duration_seconds != 0 || input->continuous_has_speed || + input->continuous_has_distance) { return TRAINLOG_STATUS_INVALID_ARGUMENT; } } else { return TRAINLOG_STATUS_INVALID_ARGUMENT; } - status = lookup_exercise_row_id( - database, - input->exercise_id, - &exercise_row_id - ); + status = lookup_exercise_row_id(database, input->exercise_id, &exercise_row_id); if (status != TRAINLOG_STATUS_OK) { return status; } - rc = sqlite3_prepare_v2( - database->connection, - "INSERT INTO session_exercises(" - "entry_id, session_row_id, exercise_row_id, " - "recording_mode, tracking_mode, data_fields, " - "position, load_mode, rest_seconds, " - "target_sets, target_reps, " - "target_duration_seconds, " - "target_weight_kg, notes, equipment_id" - ") VALUES(" - "?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, " - "?9, ?10, ?11, ?12, ?13, ?14, ?15" - ");", - -1, - &statement, - NULL - ); + rc = sqlite3_prepare_v2(database->connection, + "INSERT INTO session_exercises(" + "entry_id, session_row_id, exercise_row_id, " + "recording_mode, tracking_mode, data_fields, " + "position, load_mode, rest_seconds, " + "target_sets, target_reps, " + "target_duration_seconds, " + "target_weight_kg, notes, equipment_id" + ") VALUES(" + "?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, " + "?9, ?10, ?11, ?12, ?13, ?14, ?15" + ");", + -1, + &statement, + NULL); if (rc != SQLITE_OK) { return TRAINLOG_STATUS_DATABASE_ERROR; @@ -3675,29 +3802,15 @@ static TrainlogStatus insert_session_exercise( rc = sqlite3_bind_text(statement, 1, entry_id, -1, SQLITE_TRANSIENT); if (rc == SQLITE_OK) { - rc = sqlite3_bind_int64( - statement, - 2, - session_row_id - ); + rc = sqlite3_bind_int64(statement, 2, session_row_id); } if (rc == SQLITE_OK) { - rc = sqlite3_bind_int64( - statement, - 3, - exercise_row_id - ); + rc = sqlite3_bind_int64(statement, 3, exercise_row_id); } if (rc == SQLITE_OK) { - rc = sqlite3_bind_text( - statement, - 4, - recording_mode, - -1, - SQLITE_STATIC - ); + rc = sqlite3_bind_text(statement, 4, recording_mode, -1, SQLITE_STATIC); } if (rc == SQLITE_OK) { @@ -3705,113 +3818,52 @@ static TrainlogStatus insert_session_exercise( } if (rc == SQLITE_OK) { - rc = sqlite3_bind_int64( - statement, - 6, - (sqlite3_int64)input->data_fields - ); + rc = sqlite3_bind_int64(statement, 6, (sqlite3_int64)input->data_fields); } if (rc == SQLITE_OK) { - rc = sqlite3_bind_int64( - statement, - 7, - (sqlite3_int64)position - ); + rc = sqlite3_bind_int64(statement, 7, (sqlite3_int64)position); } if (rc == SQLITE_OK) { - rc = sqlite3_bind_text( - statement, - 8, - load_mode, - -1, - SQLITE_STATIC - ); + rc = sqlite3_bind_text(statement, 8, load_mode, -1, SQLITE_STATIC); } if (rc == SQLITE_OK) { - rc = sqlite3_bind_int( - statement, - 9, - input->rest_seconds - ); + rc = sqlite3_bind_int(statement, 9, input->rest_seconds); } if (rc == SQLITE_OK) { - rc = input->target_sets > 0 - ? sqlite3_bind_int( - statement, - 10, - input->target_sets - ) - : sqlite3_bind_null( - statement, - 10 - ); + rc = input->target_sets > 0 ? sqlite3_bind_int(statement, 10, input->target_sets) + : sqlite3_bind_null(statement, 10); } if (rc == SQLITE_OK) { - rc = input->target_reps > 0 - ? sqlite3_bind_int( - statement, - 11, - input->target_reps - ) - : sqlite3_bind_null( - statement, - 11 - ); + rc = input->target_reps > 0 ? sqlite3_bind_int(statement, 11, input->target_reps) + : sqlite3_bind_null(statement, 11); } if (rc == SQLITE_OK) { rc = input->target_duration_seconds > 0 - ? sqlite3_bind_int( - statement, - 12, - input->target_duration_seconds - ) - : sqlite3_bind_null( - statement, - 12 - ); + ? sqlite3_bind_int(statement, 12, input->target_duration_seconds) + : sqlite3_bind_null(statement, 12); } if (rc == SQLITE_OK) { - rc = input->target_has_weight - ? sqlite3_bind_double( - statement, - 13, - input->target_weight_kg - ) - : sqlite3_bind_null( - statement, - 13 - ); + rc = input->target_has_weight ? sqlite3_bind_double(statement, 13, input->target_weight_kg) + : sqlite3_bind_null(statement, 13); } if (rc == SQLITE_OK) { - rc = - input->notes != NULL && - input->notes[0] != '\0' - ? sqlite3_bind_text( - statement, - 14, - input->notes, - -1, - SQLITE_TRANSIENT - ) - : sqlite3_bind_null( - statement, - 14 - ); + rc = input->notes != NULL && input->notes[0] != '\0' + ? sqlite3_bind_text(statement, 14, input->notes, -1, SQLITE_TRANSIENT) + : sqlite3_bind_null(statement, 14); } if (rc == SQLITE_OK) { rc = input->equipment_id[0] != '\0' - ? sqlite3_bind_text(statement, 15, input->equipment_id, -1, - SQLITE_TRANSIENT) - : sqlite3_bind_null(statement, 15); + ? sqlite3_bind_text(statement, 15, input->equipment_id, -1, SQLITE_TRANSIENT) + : sqlite3_bind_null(statement, 15); } if (rc != SQLITE_OK) { @@ -3835,85 +3887,53 @@ static TrainlogStatus insert_session_exercise( return TRAINLOG_STATUS_DATABASE_ERROR; } - *output_row_id = - sqlite3_last_insert_rowid( - database->connection - ); + *output_row_id = sqlite3_last_insert_rowid(database->connection); return TRAINLOG_STATUS_OK; } -static TrainlogStatus insert_continuous_activity( - TrainlogDatabase *database, - sqlite3_int64 session_exercise_row_id, - const TrainlogSessionExerciseInput *input -) -{ +static TrainlogStatus insert_continuous_activity(TrainlogDatabase *database, + sqlite3_int64 session_exercise_row_id, + const TrainlogSessionExerciseInput *input) { sqlite3_stmt *statement = NULL; int rc; - if (database == NULL || - database->connection == NULL || - input == NULL || + if (database == NULL || database->connection == NULL || input == NULL || input->recording_mode != TRAINLOG_RECORDING_CONTINUOUS || input->continuous_duration_seconds <= 0) { return TRAINLOG_STATUS_INVALID_ARGUMENT; } - rc = sqlite3_prepare_v2( - database->connection, - "INSERT INTO continuous_activity(" - "session_exercise_row_id, " - "duration_seconds, speed_kmh, " - "distance_km" - ") VALUES(?1, ?2, ?3, ?4);", - -1, - &statement, - NULL - ); + rc = sqlite3_prepare_v2(database->connection, + "INSERT INTO continuous_activity(" + "session_exercise_row_id, " + "duration_seconds, speed_kmh, " + "distance_km" + ") VALUES(?1, ?2, ?3, ?4);", + -1, + &statement, + NULL); if (rc != SQLITE_OK) { return TRAINLOG_STATUS_DATABASE_ERROR; } - rc = sqlite3_bind_int64( - statement, - 1, - session_exercise_row_id - ); + rc = sqlite3_bind_int64(statement, 1, session_exercise_row_id); if (rc == SQLITE_OK) { - rc = sqlite3_bind_int( - statement, - 2, - input->continuous_duration_seconds - ); + rc = sqlite3_bind_int(statement, 2, input->continuous_duration_seconds); } if (rc == SQLITE_OK) { rc = input->continuous_has_speed - ? sqlite3_bind_double( - statement, - 3, - input->continuous_speed_kmh - ) - : sqlite3_bind_null( - statement, - 3 - ); + ? sqlite3_bind_double(statement, 3, input->continuous_speed_kmh) + : sqlite3_bind_null(statement, 3); } if (rc == SQLITE_OK) { rc = input->continuous_has_distance - ? sqlite3_bind_double( - statement, - 4, - input->continuous_distance_km - ) - : sqlite3_bind_null( - statement, - 4 - ); + ? sqlite3_bind_double(statement, 4, input->continuous_distance_km) + : sqlite3_bind_null(statement, 4); } if (rc != SQLITE_OK) { @@ -3933,39 +3953,32 @@ static TrainlogStatus insert_continuous_activity( return TRAINLOG_STATUS_DATABASE_ERROR; } - return sqlite3_finalize(statement) == SQLITE_OK - ? TRAINLOG_STATUS_OK - : TRAINLOG_STATUS_DATABASE_ERROR; + return sqlite3_finalize(statement) == SQLITE_OK ? TRAINLOG_STATUS_OK + : TRAINLOG_STATUS_DATABASE_ERROR; } -static TrainlogStatus insert_performed_set( - TrainlogDatabase *database, - sqlite3_int64 session_exercise_row_id, - size_t position, - const TrainlogSetInput *input -) -{ +static TrainlogStatus insert_performed_set(TrainlogDatabase *database, + sqlite3_int64 session_exercise_row_id, + size_t position, + const TrainlogSetInput *input) { sqlite3_stmt *statement = NULL; int rc; if (database == NULL || database->connection == NULL || input == NULL || position > (size_t)INT64_MAX || - (input->has_weight && - (!isfinite(input->weight_kg) || input->weight_kg < 0.0))) { + (input->has_weight && (!isfinite(input->weight_kg) || input->weight_kg < 0.0))) { /* CONTRACT: blank is represented by has_weight=false/SQL NULL; * explicit zero is a finite, observed load and must survive exactly. */ return TRAINLOG_STATUS_INVALID_ARGUMENT; } - rc = sqlite3_prepare_v2( - database->connection, - "INSERT INTO performed_sets(" - "session_exercise_row_id, position, reps, duration_seconds, weight_kg" - ") VALUES(?1, ?2, ?3, ?4, ?5);", - -1, - &statement, - NULL - ); + rc = sqlite3_prepare_v2(database->connection, + "INSERT INTO performed_sets(" + "session_exercise_row_id, position, reps, duration_seconds, weight_kg" + ") VALUES(?1, ?2, ?3, ?4, ?5);", + -1, + &statement, + NULL); if (rc != SQLITE_OK) { return TRAINLOG_STATUS_DATABASE_ERROR; } @@ -3975,19 +3988,16 @@ static TrainlogStatus insert_performed_set( rc = sqlite3_bind_int64(statement, 2, (sqlite3_int64)position); } if (rc == SQLITE_OK) { - rc = input->duration_seconds > 0 - ? sqlite3_bind_null(statement, 3) - : sqlite3_bind_int(statement, 3, input->reps); + rc = input->duration_seconds > 0 ? sqlite3_bind_null(statement, 3) + : sqlite3_bind_int(statement, 3, input->reps); } if (rc == SQLITE_OK) { - rc = input->duration_seconds > 0 - ? sqlite3_bind_int(statement, 4, input->duration_seconds) - : sqlite3_bind_null(statement, 4); + rc = input->duration_seconds > 0 ? sqlite3_bind_int(statement, 4, input->duration_seconds) + : sqlite3_bind_null(statement, 4); } if (rc == SQLITE_OK) { - rc = input->has_weight - ? sqlite3_bind_double(statement, 5, input->weight_kg) - : sqlite3_bind_null(statement, 5); + rc = input->has_weight ? sqlite3_bind_double(statement, 5, input->weight_kg) + : sqlite3_bind_null(statement, 5); } if (rc != SQLITE_OK) { @@ -4005,31 +4015,27 @@ static TrainlogStatus insert_performed_set( return TRAINLOG_STATUS_DATABASE_ERROR; } - return sqlite3_finalize(statement) == SQLITE_OK - ? TRAINLOG_STATUS_OK - : TRAINLOG_STATUS_DATABASE_ERROR; + return sqlite3_finalize(statement) == SQLITE_OK ? TRAINLOG_STATUS_OK + : TRAINLOG_STATUS_DATABASE_ERROR; } -static TrainlogStatus insert_max_result( - TrainlogDatabase *database, - sqlite3_int64 session_exercise_row_id, - double max_weight_kg -) -{ +static TrainlogStatus insert_max_result(TrainlogDatabase *database, + sqlite3_int64 session_exercise_row_id, + double max_weight_kg) { sqlite3_stmt *statement = NULL; int rc; - if (database == NULL || database->connection == NULL || - !isfinite(max_weight_kg) || max_weight_kg <= 0.0) { + if (database == NULL || database->connection == NULL || !isfinite(max_weight_kg) || + max_weight_kg <= 0.0) { return TRAINLOG_STATUS_INVALID_ARGUMENT; } - rc = sqlite3_prepare_v2( - database->connection, - "INSERT INTO max_results(session_exercise_row_id,max_weight_kg) " - "VALUES(?1,?2);", - -1, &statement, NULL - ); + rc = sqlite3_prepare_v2(database->connection, + "INSERT INTO max_results(session_exercise_row_id,max_weight_kg) " + "VALUES(?1,?2);", + -1, + &statement, + NULL); if (rc == SQLITE_OK) { rc = sqlite3_bind_int64(statement, 1, session_exercise_row_id); } @@ -4041,78 +4047,52 @@ static TrainlogStatus insert_max_result( } if (rc != SQLITE_DONE) { (void)sqlite3_finalize(statement); - return rc == SQLITE_CONSTRAINT - ? TRAINLOG_STATUS_CONFLICT - : TRAINLOG_STATUS_DATABASE_ERROR; + return rc == SQLITE_CONSTRAINT ? TRAINLOG_STATUS_CONFLICT : TRAINLOG_STATUS_DATABASE_ERROR; } - return sqlite3_finalize(statement) == SQLITE_OK - ? TRAINLOG_STATUS_OK - : TRAINLOG_STATUS_DATABASE_ERROR; + return sqlite3_finalize(statement) == SQLITE_OK ? TRAINLOG_STATUS_OK + : TRAINLOG_STATUS_DATABASE_ERROR; } -static TrainlogStatus insert_session_children( - TrainlogDatabase *database, - sqlite3_int64 session_row_id, - TrainlogSessionType session_type, - const TrainlogSessionExerciseInput *exercises, - size_t exercise_count -) -{ +static TrainlogStatus insert_session_children(TrainlogDatabase *database, + sqlite3_int64 session_row_id, + TrainlogSessionType session_type, + const TrainlogSessionExerciseInput *exercises, + size_t exercise_count) { size_t exercise_index; - if (database == NULL || - database->connection == NULL || - (exercise_count > 0U && - exercises == NULL)) { + if (database == NULL || database->connection == NULL || + (exercise_count > 0U && exercises == NULL)) { return TRAINLOG_STATUS_INVALID_ARGUMENT; } - for (exercise_index = 0U; - exercise_index < exercise_count; - ++exercise_index) { - const TrainlogSessionExerciseInput *exercise = - &exercises[exercise_index]; + for (exercise_index = 0U; exercise_index < exercise_count; ++exercise_index) { + const TrainlogSessionExerciseInput *exercise = &exercises[exercise_index]; sqlite3_int64 session_exercise_row_id; size_t set_index; TrainlogStatus status; - if (exercise->has_max_weight && - session_type != TRAINLOG_SESSION_MAX_TEST) { + if (exercise->has_max_weight && session_type != TRAINLOG_SESSION_MAX_TEST) { return TRAINLOG_STATUS_INVALID_ARGUMENT; } status = insert_session_exercise( - database, - session_row_id, - exercise_index, - exercise, - &session_exercise_row_id - ); + database, session_row_id, exercise_index, exercise, &session_exercise_row_id); if (status != TRAINLOG_STATUS_OK) { return status; } if (exercise->has_max_weight) { - status = insert_max_result( - database, - session_exercise_row_id, - exercise->max_weight_kg - ); + status = insert_max_result(database, session_exercise_row_id, exercise->max_weight_kg); if (status != TRAINLOG_STATUS_OK) { return status; } continue; } - if (exercise->recording_mode == - TRAINLOG_RECORDING_CONTINUOUS) { - status = insert_continuous_activity( - database, - session_exercise_row_id, - exercise - ); + if (exercise->recording_mode == TRAINLOG_RECORDING_CONTINUOUS) { + status = insert_continuous_activity(database, session_exercise_row_id, exercise); if (status != TRAINLOG_STATUS_OK) { return status; @@ -4121,19 +4101,13 @@ static TrainlogStatus insert_session_children( continue; } - for (set_index = 0U; - set_index < exercise->set_count; - ++set_index) { + for (set_index = 0U; set_index < exercise->set_count; ++set_index) { if (exercise->sets == NULL) { return TRAINLOG_STATUS_INVALID_ARGUMENT; } status = insert_performed_set( - database, - session_exercise_row_id, - set_index, - &exercise->sets[set_index] - ); + database, session_exercise_row_id, set_index, &exercise->sets[set_index]); if (status != TRAINLOG_STATUS_OK) { return status; @@ -4144,22 +4118,16 @@ static TrainlogStatus insert_session_children( return TRAINLOG_STATUS_OK; } -static TrainlogStatus validate_actual_set_weights( - const TrainlogSessionExerciseInput *exercises, - size_t exercise_count -) -{ +static TrainlogStatus validate_actual_set_weights(const TrainlogSessionExerciseInput *exercises, + size_t exercise_count) { size_t exercise_index; - if ((exercise_count > 0U && exercises == NULL) || - exercise_count > (size_t)INT64_MAX) { + if ((exercise_count > 0U && exercises == NULL) || exercise_count > (size_t)INT64_MAX) { return TRAINLOG_STATUS_INVALID_ARGUMENT; } - for (exercise_index = 0U; exercise_index < exercise_count; - ++exercise_index) { - const TrainlogSessionExerciseInput *exercise = - &exercises[exercise_index]; + for (exercise_index = 0U; exercise_index < exercise_count; ++exercise_index) { + const TrainlogSessionExerciseInput *exercise = &exercises[exercise_index]; size_t set_index; if (exercise->set_count > (size_t)INT64_MAX || @@ -4168,8 +4136,7 @@ static TrainlogStatus validate_actual_set_weights( } for (set_index = 0U; set_index < exercise->set_count; ++set_index) { const TrainlogSetInput *set = &exercise->sets[set_index]; - if (set->has_weight && - (!isfinite(set->weight_kg) || set->weight_kg < 0.0)) { + if (set->has_weight && (!isfinite(set->weight_kg) || set->weight_kg < 0.0)) { return TRAINLOG_STATUS_INVALID_ARGUMENT; } } @@ -4178,28 +4145,18 @@ static TrainlogStatus validate_actual_set_weights( return TRAINLOG_STATUS_OK; } -TrainlogStatus trainlog_database_insert_session( - TrainlogDatabase *database, - const TrainlogSessionInput *session -) -{ +TrainlogStatus trainlog_database_insert_session(TrainlogDatabase *database, + const TrainlogSessionInput *session) { sqlite3_int64 session_row_id; TrainlogStatus status; - if (database == NULL || - database->connection == NULL || - session == NULL || - session->session_id[0] == '\0' || - session->started_at[0] == '\0' || - (session->exercise_count > 0U && - session->exercises == NULL)) { + if (database == NULL || database->connection == NULL || session == NULL || + session->session_id[0] == '\0' || session->started_at[0] == '\0' || + (session->exercise_count > 0U && session->exercises == NULL)) { return TRAINLOG_STATUS_INVALID_ARGUMENT; } - status = validate_actual_set_weights( - session->exercises, - session->exercise_count - ); + status = validate_actual_set_weights(session->exercises, session->exercise_count); if (status != TRAINLOG_STATUS_OK) { return status; } @@ -4209,20 +4166,14 @@ TrainlogStatus trainlog_database_insert_session( return status; } - status = insert_session_header( - database, - session, - &session_row_id - ); + status = insert_session_header(database, session, &session_row_id); if (status == TRAINLOG_STATUS_OK) { - status = insert_session_children( - database, - session_row_id, - session->session_type, - session->exercises, - session->exercise_count - ); + status = insert_session_children(database, + session_row_id, + session->session_type, + session->exercises, + session->exercise_count); } if (status != TRAINLOG_STATUS_OK) { @@ -4239,24 +4190,19 @@ TrainlogStatus trainlog_database_insert_session( return status; } -TrainlogStatus trainlog_database_replace_session_exercises( - TrainlogDatabase *database, - const char *session_id, - const TrainlogSessionExerciseInput *exercises, - size_t exercise_count -) -{ +TrainlogStatus +trainlog_database_replace_session_exercises(TrainlogDatabase *database, + const char *session_id, + const TrainlogSessionExerciseInput *exercises, + size_t exercise_count) { sqlite3_int64 session_row_id; sqlite3_stmt *statement = NULL; TrainlogSessionType session_type; TrainlogStatus status; int rc; - if (database == NULL || - database->connection == NULL || - session_id == NULL || - session_id[0] == '\0' || - (exercise_count > 0U && exercises == NULL)) { + if (database == NULL || database->connection == NULL || session_id == NULL || + session_id[0] == '\0' || (exercise_count > 0U && exercises == NULL)) { return TRAINLOG_STATUS_INVALID_ARGUMENT; } @@ -4270,22 +4216,18 @@ TrainlogStatus trainlog_database_replace_session_exercises( return status; } - status = lookup_session_row_id( - database, - session_id, - &session_row_id - ); + status = lookup_session_row_id(database, session_id, &session_row_id); if (status != TRAINLOG_STATUS_OK) { (void)trainlog_database_rollback(database); return status; } - rc = sqlite3_prepare_v2( - database->connection, - "SELECT session_type FROM sessions WHERE id=?1;", - -1, &statement, NULL - ); + rc = sqlite3_prepare_v2(database->connection, + "SELECT session_type FROM sessions WHERE id=?1;", + -1, + &statement, + NULL); if (rc == SQLITE_OK) { rc = sqlite3_bind_int64(statement, 1, session_row_id); } @@ -4293,9 +4235,7 @@ TrainlogStatus trainlog_database_replace_session_exercises( rc = sqlite3_step(statement); } if (rc != SQLITE_ROW || - !session_type_from_sql( - (const char *)sqlite3_column_text(statement, 0), - &session_type)) { + !session_type_from_sql((const char *)sqlite3_column_text(statement, 0), &session_type)) { (void)sqlite3_finalize(statement); (void)trainlog_database_rollback(database); return TRAINLOG_STATUS_DATABASE_ERROR; @@ -4313,25 +4253,25 @@ TrainlogStatus trainlog_database_replace_session_exercises( * removed entries lose their parent-owned rows and new entries inherit * nothing. INVARIANT: the temporary snapshot and reattachment are inside * this transaction, so every failure restores the original graph. */ - rc = sqlite3_exec( - database->connection, - "CREATE TEMP TABLE trainlog_correction_feedback(" - "feedback_id TEXT PRIMARY KEY,entry_id TEXT NOT NULL," - "observed_at TEXT NOT NULL,raw_text TEXT NOT NULL);", - NULL, NULL, NULL - ); + rc = sqlite3_exec(database->connection, + "CREATE TEMP TABLE trainlog_correction_feedback(" + "feedback_id TEXT PRIMARY KEY,entry_id TEXT NOT NULL," + "observed_at TEXT NOT NULL,raw_text TEXT NOT NULL);", + NULL, + NULL, + NULL); if (rc == SQLITE_OK) { - rc = sqlite3_prepare_v2( - database->connection, - "INSERT INTO trainlog_correction_feedback" - "(feedback_id,entry_id,observed_at,raw_text) " - "SELECT f.feedback_id,se.entry_id,f.observed_at,f.raw_text " - "FROM exercise_feedback AS f " - "JOIN session_exercises AS se " - "ON se.id=f.session_exercise_row_id " - "WHERE se.session_row_id=?1;", - -1, &statement, NULL - ); + rc = sqlite3_prepare_v2(database->connection, + "INSERT INTO trainlog_correction_feedback" + "(feedback_id,entry_id,observed_at,raw_text) " + "SELECT f.feedback_id,se.entry_id,f.observed_at,f.raw_text " + "FROM exercise_feedback AS f " + "JOIN session_exercises AS se " + "ON se.id=f.session_exercise_row_id " + "WHERE se.session_row_id=?1;", + -1, + &statement, + NULL); } if (rc == SQLITE_OK) { rc = sqlite3_bind_int64(statement, 1, session_row_id); @@ -4339,8 +4279,7 @@ TrainlogStatus trainlog_database_replace_session_exercises( if (rc == SQLITE_OK) { rc = sqlite3_step(statement); } - if (statement != NULL && sqlite3_finalize(statement) != SQLITE_OK && - rc == SQLITE_DONE) { + if (statement != NULL && sqlite3_finalize(statement) != SQLITE_OK && rc == SQLITE_DONE) { rc = SQLITE_ERROR; } statement = NULL; @@ -4348,37 +4287,31 @@ TrainlogStatus trainlog_database_replace_session_exercises( (void)trainlog_database_rollback(database); return TRAINLOG_STATUS_DATABASE_ERROR; } - rc = sqlite3_exec( - database->connection, - "CREATE TEMP TABLE trainlog_correction_feedback_revisions(" - "revision_id TEXT PRIMARY KEY,feedback_id TEXT NOT NULL," - "created_at TEXT NOT NULL,raw_text TEXT NOT NULL);" - "INSERT INTO trainlog_correction_feedback_revisions " - "SELECT r.revision_id,r.feedback_id,r.created_at,r.raw_text " - "FROM exercise_feedback_revisions r JOIN trainlog_correction_feedback f " - "ON f.feedback_id=r.feedback_id;", - NULL, NULL, NULL - ); + rc = sqlite3_exec(database->connection, + "CREATE TEMP TABLE trainlog_correction_feedback_revisions(" + "revision_id TEXT PRIMARY KEY,feedback_id TEXT NOT NULL," + "created_at TEXT NOT NULL,raw_text TEXT NOT NULL);" + "INSERT INTO trainlog_correction_feedback_revisions " + "SELECT r.revision_id,r.feedback_id,r.created_at,r.raw_text " + "FROM exercise_feedback_revisions r JOIN trainlog_correction_feedback f " + "ON f.feedback_id=r.feedback_id;", + NULL, + NULL, + NULL); if (rc != SQLITE_OK) { (void)trainlog_database_rollback(database); return TRAINLOG_STATUS_DATABASE_ERROR; } - rc = sqlite3_prepare_v2( - database->connection, - "DELETE FROM session_exercises " - "WHERE session_row_id = ?1;", - -1, - &statement, - NULL - ); + rc = sqlite3_prepare_v2(database->connection, + "DELETE FROM session_exercises " + "WHERE session_row_id = ?1;", + -1, + &statement, + NULL); if (rc == SQLITE_OK) { - rc = sqlite3_bind_int64( - statement, - 1, - session_row_id - ); + rc = sqlite3_bind_int64(statement, 1, session_row_id); } if (rc == SQLITE_OK) { @@ -4398,37 +4331,31 @@ TrainlogStatus trainlog_database_replace_session_exercises( statement = NULL; - status = insert_session_children( - database, - session_row_id, - session_type, - exercises, - exercise_count - ); + status = + insert_session_children(database, session_row_id, session_type, exercises, exercise_count); if (status != TRAINLOG_STATUS_OK) { (void)trainlog_database_rollback(database); return status; } - rc = sqlite3_prepare_v2( - database->connection, - "INSERT INTO exercise_feedback" - "(feedback_id,session_exercise_row_id,observed_at,raw_text) " - "SELECT saved.feedback_id,se.id,saved.observed_at,saved.raw_text " - "FROM trainlog_correction_feedback AS saved " - "JOIN session_exercises AS se ON se.entry_id=saved.entry_id " - "WHERE se.session_row_id=?1;", - -1, &statement, NULL - ); + rc = sqlite3_prepare_v2(database->connection, + "INSERT INTO exercise_feedback" + "(feedback_id,session_exercise_row_id,observed_at,raw_text) " + "SELECT saved.feedback_id,se.id,saved.observed_at,saved.raw_text " + "FROM trainlog_correction_feedback AS saved " + "JOIN session_exercises AS se ON se.entry_id=saved.entry_id " + "WHERE se.session_row_id=?1;", + -1, + &statement, + NULL); if (rc == SQLITE_OK) { rc = sqlite3_bind_int64(statement, 1, session_row_id); } if (rc == SQLITE_OK) { rc = sqlite3_step(statement); } - if (statement != NULL && sqlite3_finalize(statement) != SQLITE_OK && - rc == SQLITE_DONE) { + if (statement != NULL && sqlite3_finalize(statement) != SQLITE_OK && rc == SQLITE_DONE) { rc = SQLITE_ERROR; } statement = NULL; @@ -4439,15 +4366,16 @@ TrainlogStatus trainlog_database_replace_session_exercises( "SELECT r.revision_id,r.feedback_id,r.created_at,r.raw_text " "FROM trainlog_correction_feedback_revisions r " "JOIN exercise_feedback f ON f.feedback_id=r.feedback_id;", - NULL, NULL, NULL - ); + NULL, + NULL, + NULL); } - if (rc != SQLITE_OK || sqlite3_exec( - database->connection, - "DROP TABLE trainlog_correction_feedback_revisions;" - "DROP TABLE trainlog_correction_feedback;", - NULL, NULL, NULL - ) != SQLITE_OK) { + if (rc != SQLITE_OK || sqlite3_exec(database->connection, + "DROP TABLE trainlog_correction_feedback_revisions;" + "DROP TABLE trainlog_correction_feedback;", + NULL, + NULL, + NULL) != SQLITE_OK) { (void)trainlog_database_rollback(database); return TRAINLOG_STATUS_DATABASE_ERROR; } @@ -4458,14 +4386,26 @@ TrainlogStatus trainlog_database_replace_session_exercises( * cannot be modified or silently reactivated by this writer. */ { sqlite3_stmt *causal = NULL; - rc = sqlite3_prepare_v2(database->connection, - "INSERT INTO sync_causal_state(target_kind,target_id,current_revision_id,deleted,operation_id) " + rc = sqlite3_prepare_v2( + database->connection, + "INSERT INTO " + "sync_causal_state(target_kind,target_id,current_revision_id,deleted,operation_id) " "VALUES('session',?1,'mu_'||lower(hex(randomblob(16))),0,NULL) " - "ON CONFLICT(target_kind,target_id) DO UPDATE SET current_revision_id=excluded.current_revision_id " - "WHERE sync_causal_state.deleted=0;", -1, &causal, NULL); - if (rc == SQLITE_OK) rc = sqlite3_bind_text(causal, 1, session_id, -1, SQLITE_TRANSIENT); - if (rc == SQLITE_OK) rc = sqlite3_step(causal); - if (causal != NULL && sqlite3_finalize(causal) != SQLITE_OK && rc == SQLITE_DONE) rc = SQLITE_ERROR; + "ON CONFLICT(target_kind,target_id) DO UPDATE SET " + "current_revision_id=excluded.current_revision_id " + "WHERE sync_causal_state.deleted=0;", + -1, + &causal, + NULL); + if (rc == SQLITE_OK) { + rc = sqlite3_bind_text(causal, 1, session_id, -1, SQLITE_TRANSIENT); + } + if (rc == SQLITE_OK) { + rc = sqlite3_step(causal); + } + if (causal != NULL && sqlite3_finalize(causal) != SQLITE_OK && rc == SQLITE_DONE) { + rc = SQLITE_ERROR; + } if (rc != SQLITE_DONE || sqlite3_changes(database->connection) != 1) { (void)trainlog_database_rollback(database); return rc == SQLITE_DONE ? TRAINLOG_STATUS_CONFLICT : TRAINLOG_STATUS_DATABASE_ERROR; @@ -4481,13 +4421,10 @@ TrainlogStatus trainlog_database_replace_session_exercises( return status; } -TrainlogStatus trainlog_database_list_sessions( - TrainlogDatabase *database, - TrainlogSessionSummary *output, - size_t capacity, - size_t *output_count -) -{ +TrainlogStatus trainlog_database_list_sessions(TrainlogDatabase *database, + TrainlogSessionSummary *output, + size_t capacity, + size_t *output_count) { /* WHY: imported and historically persisted actual work can legitimately * have no ended_at, while a planned/empty occurrence is not observed work. * CONTRACT: observable session history contains a session exactly once @@ -4520,81 +4457,51 @@ TrainlogStatus trainlog_database_list_sessions( size_t count = 0U; int rc; - if (database == NULL || - database->connection == NULL || - output_count == NULL || + if (database == NULL || database->connection == NULL || output_count == NULL || (capacity > 0U && output == NULL)) { return TRAINLOG_STATUS_INVALID_ARGUMENT; } - rc = sqlite3_prepare_v2( - database->connection, - SQL, - -1, - &statement, - NULL - ); + rc = sqlite3_prepare_v2(database->connection, SQL, -1, &statement, NULL); if (rc != SQLITE_OK) { return TRAINLOG_STATUS_DATABASE_ERROR; } while ((rc = sqlite3_step(statement)) == SQLITE_ROW) { if (count < capacity) { - const unsigned char *id = - sqlite3_column_text(statement, 0); + const unsigned char *id = sqlite3_column_text(statement, 0); - const unsigned char *started = - sqlite3_column_text(statement, 1); + const unsigned char *started = sqlite3_column_text(statement, 1); - const unsigned char *ended = - sqlite3_column_text(statement, 2); + const unsigned char *ended = sqlite3_column_text(statement, 2); - const unsigned char *session_type = - sqlite3_column_text(statement, 3); + const unsigned char *session_type = sqlite3_column_text(statement, 3); - sqlite3_int64 exercise_count = - sqlite3_column_int64(statement, 4); + sqlite3_int64 exercise_count = sqlite3_column_int64(statement, 4); - if (id == NULL || - started == NULL || - ended == NULL || - session_type == NULL || + if (id == NULL || started == NULL || ended == NULL || session_type == NULL || exercise_count < 0) { (void)sqlite3_finalize(statement); return TRAINLOG_STATUS_DATABASE_ERROR; } (void)snprintf( - output[count].session_id, - sizeof(output[count].session_id), - "%s", - (const char *)id - ); + output[count].session_id, sizeof(output[count].session_id), "%s", (const char *)id); + + (void)snprintf(output[count].started_at, + sizeof(output[count].started_at), + "%s", + (const char *)started); (void)snprintf( - output[count].started_at, - sizeof(output[count].started_at), - "%s", - (const char *)started - ); + output[count].ended_at, sizeof(output[count].ended_at), "%s", (const char *)ended); - (void)snprintf( - output[count].ended_at, - sizeof(output[count].ended_at), - "%s", - (const char *)ended - ); - - if (!session_type_from_sql( - (const char *)session_type, - &output[count].session_type - )) { + if (!session_type_from_sql((const char *)session_type, &output[count].session_type)) { (void)sqlite3_finalize(statement); return TRAINLOG_STATUS_DATABASE_ERROR; } - output[count].exercise_count = - (size_t)exercise_count; + output[count].exercise_count = (size_t)exercise_count; } ++count; @@ -4609,31 +4516,19 @@ TrainlogStatus trainlog_database_list_sessions( return TRAINLOG_STATUS_DATABASE_ERROR; } - *output_count = - count < capacity - ? count - : capacity; + *output_count = count < capacity ? count : capacity; return TRAINLOG_STATUS_OK; } -static int bind_optional_double( - sqlite3_stmt *statement, - int index, - bool present, - double value -) -{ - return present - ? sqlite3_bind_double(statement, index, value) - : sqlite3_bind_null(statement, index); +static int bind_optional_double(sqlite3_stmt *statement, int index, bool present, double value) { + return present ? sqlite3_bind_double(statement, index, value) + : sqlite3_bind_null(statement, index); } -TrainlogStatus trainlog_database_insert_body_observation( - TrainlogDatabase *database, - const TrainlogBodyObservationInput *observation -) -{ +TrainlogStatus +trainlog_database_insert_body_observation(TrainlogDatabase *database, + const TrainlogBodyObservationInput *observation) { static const char *const SQL = "INSERT INTO body_observations(" "observation_id, observed_at, session_row_id, body_weight_kg, " @@ -4649,29 +4544,17 @@ TrainlogStatus trainlog_database_insert_body_observation( bool any_metric; int rc; - if (database == NULL || - database->connection == NULL || - observation == NULL || - observation->observation_id[0] == '\0' || - observation->observed_at[0] == '\0') { + if (database == NULL || database->connection == NULL || observation == NULL || + observation->observation_id[0] == '\0' || observation->observed_at[0] == '\0') { return TRAINLOG_STATUS_INVALID_ARGUMENT; } - any_metric = - observation->has_body_weight || - observation->has_neck || - observation->has_shoulders || - observation->has_chest || - observation->has_waist || - observation->has_hips || - observation->has_left_arm || - observation->has_right_arm || - observation->has_left_forearm || - observation->has_right_forearm || - observation->has_left_thigh || - observation->has_right_thigh || - observation->has_left_calf || - observation->has_right_calf; + any_metric = observation->has_body_weight || observation->has_neck || + observation->has_shoulders || observation->has_chest || observation->has_waist || + observation->has_hips || observation->has_left_arm || observation->has_right_arm || + observation->has_left_forearm || observation->has_right_forearm || + observation->has_left_thigh || observation->has_right_thigh || + observation->has_left_calf || observation->has_right_calf; if (!any_metric) { return TRAINLOG_STATUS_INVALID_ARGUMENT; @@ -4682,46 +4565,22 @@ TrainlogStatus trainlog_database_insert_body_observation( return TRAINLOG_STATUS_DATABASE_ERROR; } - rc = sqlite3_bind_text( - statement, - 1, - observation->observation_id, - -1, - SQLITE_TRANSIENT - ); + rc = sqlite3_bind_text(statement, 1, observation->observation_id, -1, SQLITE_TRANSIENT); if (rc == SQLITE_OK) { - rc = sqlite3_bind_text( - statement, - 2, - observation->observed_at, - -1, - SQLITE_TRANSIENT - ); + rc = sqlite3_bind_text(statement, 2, observation->observed_at, -1, SQLITE_TRANSIENT); } if (rc == SQLITE_OK) { - rc = observation->session_id != NULL && - observation->session_id[0] != '\0' - ? sqlite3_bind_text( - statement, - 3, - observation->session_id, - -1, - SQLITE_TRANSIENT - ) - : sqlite3_bind_null(statement, 3); + rc = observation->session_id != NULL && observation->session_id[0] != '\0' + ? sqlite3_bind_text(statement, 3, observation->session_id, -1, SQLITE_TRANSIENT) + : sqlite3_bind_null(statement, 3); } -#define BIND_METRIC(index_, flag_, value_) \ - do { \ - if (rc == SQLITE_OK) { \ - rc = bind_optional_double( \ - statement, \ - (index_), \ - (flag_), \ - (value_) \ - ); \ - } \ +#define BIND_METRIC(index_, flag_, value_) \ + do { \ + if (rc == SQLITE_OK) { \ + rc = bind_optional_double(statement, (index_), (flag_), (value_)); \ + } \ } while (0) BIND_METRIC(4, observation->has_body_weight, observation->body_weight_kg); @@ -4732,16 +4591,8 @@ TrainlogStatus trainlog_database_insert_body_observation( BIND_METRIC(9, observation->has_hips, observation->hips_cm); BIND_METRIC(10, observation->has_left_arm, observation->left_arm_cm); BIND_METRIC(11, observation->has_right_arm, observation->right_arm_cm); - BIND_METRIC( - 12, - observation->has_left_forearm, - observation->left_forearm_cm - ); - BIND_METRIC( - 13, - observation->has_right_forearm, - observation->right_forearm_cm - ); + BIND_METRIC(12, observation->has_left_forearm, observation->left_forearm_cm); + BIND_METRIC(13, observation->has_right_forearm, observation->right_forearm_cm); BIND_METRIC(14, observation->has_left_thigh, observation->left_thigh_cm); BIND_METRIC(15, observation->has_right_thigh, observation->right_thigh_cm); BIND_METRIC(16, observation->has_left_calf, observation->left_calf_cm); @@ -4751,14 +4602,8 @@ TrainlogStatus trainlog_database_insert_body_observation( if (rc == SQLITE_OK) { rc = observation->notes != NULL && observation->notes[0] != '\0' - ? sqlite3_bind_text( - statement, - 18, - observation->notes, - -1, - SQLITE_TRANSIENT - ) - : sqlite3_bind_null(statement, 18); + ? sqlite3_bind_text(statement, 18, observation->notes, -1, SQLITE_TRANSIENT) + : sqlite3_bind_null(statement, 18); } if (rc != SQLITE_OK) { @@ -4776,39 +4621,31 @@ TrainlogStatus trainlog_database_insert_body_observation( return TRAINLOG_STATUS_DATABASE_ERROR; } - return sqlite3_finalize(statement) == SQLITE_OK - ? TRAINLOG_STATUS_OK - : TRAINLOG_STATUS_DATABASE_ERROR; + return sqlite3_finalize(statement) == SQLITE_OK ? TRAINLOG_STATUS_OK + : TRAINLOG_STATUS_DATABASE_ERROR; } -TrainlogStatus trainlog_database_list_weight_points( - TrainlogDatabase *database, - TrainlogWeightPoint *output, - size_t capacity, - size_t *output_count -) -{ +TrainlogStatus trainlog_database_list_weight_points(TrainlogDatabase *database, + TrainlogWeightPoint *output, + size_t capacity, + size_t *output_count) { sqlite3_stmt *statement = NULL; size_t count = 0U; int rc; - if (database == NULL || - database->connection == NULL || - output_count == NULL || + if (database == NULL || database->connection == NULL || output_count == NULL || (capacity > 0U && output == NULL)) { return TRAINLOG_STATUS_INVALID_ARGUMENT; } - rc = sqlite3_prepare_v2( - database->connection, - "SELECT observed_at, body_weight_kg " - "FROM body_observations " - "WHERE body_weight_kg IS NOT NULL " - "ORDER BY observed_at ASC, id ASC;", - -1, - &statement, - NULL - ); + rc = sqlite3_prepare_v2(database->connection, + "SELECT observed_at, body_weight_kg " + "FROM body_observations " + "WHERE body_weight_kg IS NOT NULL " + "ORDER BY observed_at ASC, id ASC;", + -1, + &statement, + NULL); if (rc != SQLITE_OK) { return TRAINLOG_STATUS_DATABASE_ERROR; } @@ -4821,14 +4658,11 @@ TrainlogStatus trainlog_database_list_weight_points( return TRAINLOG_STATUS_DATABASE_ERROR; } - (void)snprintf( - output[count].observed_at, - sizeof(output[count].observed_at), - "%s", - (const char *)observed - ); - output[count].body_weight_kg = - sqlite3_column_double(statement, 1); + (void)snprintf(output[count].observed_at, + sizeof(output[count].observed_at), + "%s", + (const char *)observed); + output[count].body_weight_kg = sqlite3_column_double(statement, 1); } ++count; } @@ -4848,8 +4682,7 @@ TrainlogStatus trainlog_database_list_weight_points( /* TRAINLOG_SESSION_DETAILS_IMPLEMENTATION */ -static TrainlogLoadMode detail_load_mode_from_text(const char *text) -{ +static TrainlogLoadMode detail_load_mode_from_text(const char *text) { if (text != NULL && strcmp(text, "external") == 0) { return TRAINLOG_LOAD_EXTERNAL; } @@ -4861,31 +4694,18 @@ static TrainlogLoadMode detail_load_mode_from_text(const char *text) return TRAINLOG_LOAD_NONE; } -static TrainlogStatus detail_append_text( - char *output, - size_t output_size, - size_t *used, - const char *text -) -{ +static TrainlogStatus +detail_append_text(char *output, size_t output_size, size_t *used, const char *text) { size_t remaining; int written; - if (output == NULL || - used == NULL || - text == NULL || - *used >= output_size) { + if (output == NULL || used == NULL || text == NULL || *used >= output_size) { return TRAINLOG_STATUS_INVALID_ARGUMENT; } remaining = output_size - *used; - written = snprintf( - output + *used, - remaining, - "%s", - text - ); + written = snprintf(output + *used, remaining, "%s", text); if (written < 0) { return TRAINLOG_STATUS_DATABASE_ERROR; @@ -4907,17 +4727,13 @@ static TrainlogStatus detail_append_text( return TRAINLOG_STATUS_OK; } -static TrainlogStatus detail_fill_sets( - TrainlogDatabase *database, - sqlite3_int64 session_exercise_row_id, - TrainlogPersistedExerciseDetail *detail -) -{ - static const char *const SQL = - "SELECT reps, duration_seconds, weight_kg " - "FROM performed_sets " - "WHERE session_exercise_row_id = ?1 " - "ORDER BY position ASC;"; +static TrainlogStatus detail_fill_sets(TrainlogDatabase *database, + sqlite3_int64 session_exercise_row_id, + TrainlogPersistedExerciseDetail *detail) { + static const char *const SQL = "SELECT reps, duration_seconds, weight_kg " + "FROM performed_sets " + "WHERE session_exercise_row_id = ?1 " + "ORDER BY position ASC;"; sqlite3_stmt *statement = NULL; sqlite3_stmt *count_statement = NULL; @@ -4926,11 +4742,13 @@ static TrainlogStatus detail_fill_sets( int rc; rc = sqlite3_prepare_v2(database->connection, - "SELECT COUNT(*) FROM performed_sets " - "WHERE session_exercise_row_id = ?1;", -1, &count_statement, NULL); + "SELECT COUNT(*) FROM performed_sets " + "WHERE session_exercise_row_id = ?1;", + -1, + &count_statement, + NULL); if (rc != SQLITE_OK || - sqlite3_bind_int64(count_statement, 1, session_exercise_row_id) != - SQLITE_OK || + sqlite3_bind_int64(count_statement, 1, session_exercise_row_id) != SQLITE_OK || sqlite3_step(count_statement) != SQLITE_ROW) { (void)sqlite3_finalize(count_statement); return TRAINLOG_STATUS_DATABASE_ERROR; @@ -4938,8 +4756,7 @@ static TrainlogStatus detail_fill_sets( { sqlite3_int64 persisted_count = sqlite3_column_int64(count_statement, 0); if (persisted_count < 0 || - (uint64_t)persisted_count > - (uint64_t)(SIZE_MAX / sizeof(*detail->actual_sets))) { + (uint64_t)persisted_count > (uint64_t)(SIZE_MAX / sizeof(*detail->actual_sets))) { (void)sqlite3_finalize(count_statement); return TRAINLOG_STATUS_DATABASE_ERROR; } @@ -4961,13 +4778,7 @@ static TrainlogStatus detail_fill_sets( detail->actual_set_count = count; count = 0U; - rc = sqlite3_prepare_v2( - database->connection, - SQL, - -1, - &statement, - NULL - ); + rc = sqlite3_prepare_v2(database->connection, SQL, -1, &statement, NULL); if (rc != SQLITE_OK) { free(detail->actual_sets); detail->actual_sets = NULL; @@ -4975,11 +4786,7 @@ static TrainlogStatus detail_fill_sets( return TRAINLOG_STATUS_DATABASE_ERROR; } - rc = sqlite3_bind_int64( - statement, - 1, - session_exercise_row_id - ); + rc = sqlite3_bind_int64(statement, 1, session_exercise_row_id); if (rc != SQLITE_OK) { (void)sqlite3_finalize(statement); free(detail->actual_sets); @@ -4993,12 +4800,9 @@ static TrainlogStatus detail_fill_sets( while ((rc = sqlite3_step(statement)) == SQLITE_ROW) { char fragment[96]; int written; - int has_reps = - sqlite3_column_type(statement, 0) != SQLITE_NULL; - int has_duration = - sqlite3_column_type(statement, 1) != SQLITE_NULL; - int has_weight = - sqlite3_column_type(statement, 2) != SQLITE_NULL; + int has_reps = sqlite3_column_type(statement, 0) != SQLITE_NULL; + int has_duration = sqlite3_column_type(statement, 1) != SQLITE_NULL; + int has_weight = sqlite3_column_type(statement, 2) != SQLITE_NULL; /* INVARIANT: COUNT and SELECT observe the same connection operation; * any mismatch is corruption/concurrent mutation, never truncation. */ @@ -5012,28 +4816,16 @@ static TrainlogStatus detail_fill_sets( /* INVARIANT: array index is persisted position order. Nullable load * stays explicit instead of inheriting occurrence target metadata. */ - detail->actual_sets[count].reps = - has_reps != 0 - ? sqlite3_column_int(statement, 0) - : 0; + detail->actual_sets[count].reps = has_reps != 0 ? sqlite3_column_int(statement, 0) : 0; detail->actual_sets[count].duration_seconds = - has_duration != 0 - ? sqlite3_column_int(statement, 1) - : 0; - detail->actual_sets[count].has_weight = - has_weight != 0; + has_duration != 0 ? sqlite3_column_int(statement, 1) : 0; + detail->actual_sets[count].has_weight = has_weight != 0; detail->actual_sets[count].weight_kg = - has_weight != 0 - ? sqlite3_column_double(statement, 2) - : 0.0; + has_weight != 0 ? sqlite3_column_double(statement, 2) : 0.0; if (count > 0U) { TrainlogStatus status = detail_append_text( - detail->actual_summary, - sizeof(detail->actual_summary), - &used, - " / " - ); + detail->actual_summary, sizeof(detail->actual_summary), &used, " / "); if (status != TRAINLOG_STATUS_OK) { (void)sqlite3_finalize(statement); @@ -5048,31 +4840,21 @@ static TrainlogStatus detail_fill_sets( int reps = sqlite3_column_int(statement, 0); if (has_weight != 0) { - written = snprintf( - fragment, - sizeof(fragment), - "%d@%.1f", - reps, - sqlite3_column_double(statement, 2) - ); + written = snprintf(fragment, + sizeof(fragment), + "%d@%.1f", + reps, + sqlite3_column_double(statement, 2)); } else { - written = snprintf( - fragment, - sizeof(fragment), - "%d", - reps - ); + written = snprintf(fragment, sizeof(fragment), "%d", reps); } } else if (has_duration != 0) { int duration = sqlite3_column_int(statement, 1); char duration_text[64]; - if (trainlog_duration_format( - duration, - duration_text, - sizeof(duration_text) - ) != TRAINLOG_STATUS_OK) { + if (trainlog_duration_format(duration, duration_text, sizeof(duration_text)) != + TRAINLOG_STATUS_OK) { (void)sqlite3_finalize(statement); free(detail->actual_sets); detail->actual_sets = NULL; @@ -5081,20 +4863,13 @@ static TrainlogStatus detail_fill_sets( } if (has_weight != 0) { - written = snprintf( - fragment, - sizeof(fragment), - "%s@%.1f", - duration_text, - sqlite3_column_double(statement, 2) - ); + written = snprintf(fragment, + sizeof(fragment), + "%s@%.1f", + duration_text, + sqlite3_column_double(statement, 2)); } else { - written = snprintf( - fragment, - sizeof(fragment), - "%s", - duration_text - ); + written = snprintf(fragment, sizeof(fragment), "%s", duration_text); } } else { (void)sqlite3_finalize(statement); @@ -5104,8 +4879,7 @@ static TrainlogStatus detail_fill_sets( return TRAINLOG_STATUS_DATABASE_ERROR; } - if (written < 0 || - (size_t)written >= sizeof(fragment)) { + if (written < 0 || (size_t)written >= sizeof(fragment)) { (void)sqlite3_finalize(statement); free(detail->actual_sets); detail->actual_sets = NULL; @@ -5115,11 +4889,7 @@ static TrainlogStatus detail_fill_sets( { TrainlogStatus status = detail_append_text( - detail->actual_summary, - sizeof(detail->actual_summary), - &used, - fragment - ); + detail->actual_summary, sizeof(detail->actual_summary), &used, fragment); if (status != TRAINLOG_STATUS_OK) { (void)sqlite3_finalize(statement); @@ -5157,21 +4927,14 @@ static TrainlogStatus detail_fill_sets( if (count == 0U) { (void)snprintf( - detail->actual_summary, - sizeof(detail->actual_summary), - "%s", - "aucune série réalisée" - ); + detail->actual_summary, sizeof(detail->actual_summary), "%s", "aucune série réalisée"); } return TRAINLOG_STATUS_OK; } -void trainlog_database_free_session_details( - TrainlogPersistedExerciseDetail *exercises, - size_t exercise_count -) -{ +void trainlog_database_free_session_details(TrainlogPersistedExerciseDetail *exercises, + size_t exercise_count) { size_t index; if (exercises == NULL) { @@ -5184,45 +4947,41 @@ void trainlog_database_free_session_details( } } -TrainlogStatus trainlog_database_get_session_details( - TrainlogDatabase *database, - const char *session_id, - TrainlogSessionSummary *output_session, - TrainlogPersistedExerciseDetail *output_exercises, - size_t exercise_capacity, - size_t *output_exercise_count -) -{ - static const char *const HEADER_SQL = - "SELECT started_at, COALESCE(ended_at, ''), session_type " - "FROM sessions " - "WHERE session_id = ?1;"; +TrainlogStatus +trainlog_database_get_session_details(TrainlogDatabase *database, + const char *session_id, + TrainlogSessionSummary *output_session, + TrainlogPersistedExerciseDetail *output_exercises, + size_t exercise_capacity, + size_t *output_exercise_count) { + static const char *const HEADER_SQL = "SELECT started_at, COALESCE(ended_at, ''), session_type " + "FROM sessions " + "WHERE session_id = ?1;"; - static const char *const EXERCISE_SQL = - "SELECT " - "e.name, se.tracking_mode, " - "se.recording_mode, se.data_fields, " - "se.load_mode, se.rest_seconds, " - "COALESCE(se.target_sets, 0), " - "COALESCE(se.target_reps, 0), " - "COALESCE(se.target_duration_seconds, 0), " - "se.target_weight_kg, " - "ca.duration_seconds, ca.speed_kmh, ca.distance_km, " - "mr.max_weight_kg, " - "se.equipment_id, " - "se.entry_id, " - "se.id " - "FROM session_exercises AS se " - "JOIN sessions AS s " - " ON s.id = se.session_row_id " - "JOIN exercises AS e " - " ON e.id = se.exercise_row_id " - "LEFT JOIN continuous_activity AS ca " - " ON ca.session_exercise_row_id = se.id " - "LEFT JOIN max_results AS mr " - " ON mr.session_exercise_row_id = se.id " - "WHERE s.session_id = ?1 " - "ORDER BY se.position ASC;"; + static const char *const EXERCISE_SQL = "SELECT " + "e.name, se.tracking_mode, " + "se.recording_mode, se.data_fields, " + "se.load_mode, se.rest_seconds, " + "COALESCE(se.target_sets, 0), " + "COALESCE(se.target_reps, 0), " + "COALESCE(se.target_duration_seconds, 0), " + "se.target_weight_kg, " + "ca.duration_seconds, ca.speed_kmh, ca.distance_km, " + "mr.max_weight_kg, " + "se.equipment_id, " + "se.entry_id, " + "se.id " + "FROM session_exercises AS se " + "JOIN sessions AS s " + " ON s.id = se.session_row_id " + "JOIN exercises AS e " + " ON e.id = se.exercise_row_id " + "LEFT JOIN continuous_activity AS ca " + " ON ca.session_exercise_row_id = se.id " + "LEFT JOIN max_results AS mr " + " ON mr.session_exercise_row_id = se.id " + "WHERE s.session_id = ?1 " + "ORDER BY se.position ASC;"; sqlite3_stmt *header = NULL; sqlite3_stmt *exercises = NULL; @@ -5234,42 +4993,21 @@ TrainlogStatus trainlog_database_get_session_details( *output_exercise_count = 0U; } - if (database == NULL || - database->connection == NULL || - session_id == NULL || - session_id[0] == '\0' || - output_session == NULL || - output_exercise_count == NULL || - (exercise_capacity > 0U && - output_exercises == NULL)) { + if (database == NULL || database->connection == NULL || session_id == NULL || + session_id[0] == '\0' || output_session == NULL || output_exercise_count == NULL || + (exercise_capacity > 0U && output_exercises == NULL)) { return TRAINLOG_STATUS_INVALID_ARGUMENT; } - (void)memset( - output_session, - 0, - sizeof(*output_session) - ); + (void)memset(output_session, 0, sizeof(*output_session)); - rc = sqlite3_prepare_v2( - database->connection, - HEADER_SQL, - -1, - &header, - NULL - ); + rc = sqlite3_prepare_v2(database->connection, HEADER_SQL, -1, &header, NULL); if (rc != SQLITE_OK) { return TRAINLOG_STATUS_DATABASE_ERROR; } - rc = sqlite3_bind_text( - header, - 1, - session_id, - -1, - SQLITE_TRANSIENT - ); + rc = sqlite3_bind_text(header, 1, session_id, -1, SQLITE_TRANSIENT); if (rc != SQLITE_OK) { (void)sqlite3_finalize(header); @@ -5289,47 +5027,29 @@ TrainlogStatus trainlog_database_get_session_details( } { - const unsigned char *started = - sqlite3_column_text(header, 0); + const unsigned char *started = sqlite3_column_text(header, 0); - const unsigned char *ended = - sqlite3_column_text(header, 1); + const unsigned char *ended = sqlite3_column_text(header, 1); - const unsigned char *session_type = - sqlite3_column_text(header, 2); + const unsigned char *session_type = sqlite3_column_text(header, 2); - if (started == NULL || - ended == NULL || - session_type == NULL) { + if (started == NULL || ended == NULL || session_type == NULL) { (void)sqlite3_finalize(header); return TRAINLOG_STATUS_DATABASE_ERROR; } (void)snprintf( - output_session->session_id, - sizeof(output_session->session_id), - "%s", - session_id - ); + output_session->session_id, sizeof(output_session->session_id), "%s", session_id); + + (void)snprintf(output_session->started_at, + sizeof(output_session->started_at), + "%s", + (const char *)started); (void)snprintf( - output_session->started_at, - sizeof(output_session->started_at), - "%s", - (const char *)started - ); + output_session->ended_at, sizeof(output_session->ended_at), "%s", (const char *)ended); - (void)snprintf( - output_session->ended_at, - sizeof(output_session->ended_at), - "%s", - (const char *)ended - ); - - if (!session_type_from_sql( - (const char *)session_type, - &output_session->session_type - )) { + if (!session_type_from_sql((const char *)session_type, &output_session->session_type)) { (void)sqlite3_finalize(header); return TRAINLOG_STATUS_DATABASE_ERROR; } @@ -5339,25 +5059,13 @@ TrainlogStatus trainlog_database_get_session_details( return TRAINLOG_STATUS_DATABASE_ERROR; } - rc = sqlite3_prepare_v2( - database->connection, - EXERCISE_SQL, - -1, - &exercises, - NULL - ); + rc = sqlite3_prepare_v2(database->connection, EXERCISE_SQL, -1, &exercises, NULL); if (rc != SQLITE_OK) { return TRAINLOG_STATUS_DATABASE_ERROR; } - rc = sqlite3_bind_text( - exercises, - 1, - session_id, - -1, - SQLITE_TRANSIENT - ); + rc = sqlite3_bind_text(exercises, 1, session_id, -1, SQLITE_TRANSIENT); if (rc != SQLITE_OK) { (void)sqlite3_finalize(exercises); @@ -5366,55 +5074,39 @@ TrainlogStatus trainlog_database_get_session_details( while ((rc = sqlite3_step(exercises)) == SQLITE_ROW) { if (copied < exercise_capacity) { - TrainlogPersistedExerciseDetail *detail = - &output_exercises[copied]; + TrainlogPersistedExerciseDetail *detail = &output_exercises[copied]; - const unsigned char *name = - sqlite3_column_text(exercises, 0); + const unsigned char *name = sqlite3_column_text(exercises, 0); - const unsigned char *tracking = - sqlite3_column_text(exercises, 1); + const unsigned char *tracking = sqlite3_column_text(exercises, 1); - const unsigned char *recording = - sqlite3_column_text(exercises, 2); + const unsigned char *recording = sqlite3_column_text(exercises, 2); - sqlite3_int64 data_fields = - sqlite3_column_int64(exercises, 3); + sqlite3_int64 data_fields = sqlite3_column_int64(exercises, 3); - const unsigned char *load = - sqlite3_column_text(exercises, 4); + const unsigned char *load = sqlite3_column_text(exercises, 4); - sqlite3_int64 session_exercise_row_id = - sqlite3_column_int64(exercises, 16); - const unsigned char *equipment_id = - sqlite3_column_text(exercises, 14); + sqlite3_int64 session_exercise_row_id = sqlite3_column_int64(exercises, 16); + const unsigned char *equipment_id = sqlite3_column_text(exercises, 14); const unsigned char *entry_id = sqlite3_column_text(exercises, 15); TrainlogStatus status; - if (name == NULL || - tracking == NULL || - recording == NULL || - load == NULL || - data_fields < 0 || - (uint64_t)data_fields > - (uint64_t)UINT32_MAX || - (((TrainlogExerciseDataFields)data_fields) & - ~TRAINLOG_EXERCISE_DATA_KNOWN_MASK) != 0U) { + if (name == NULL || tracking == NULL || recording == NULL || load == NULL || + data_fields < 0 || (uint64_t)data_fields > (uint64_t)UINT32_MAX || + (((TrainlogExerciseDataFields)data_fields) & ~TRAINLOG_EXERCISE_DATA_KNOWN_MASK) != + 0U) { (void)sqlite3_finalize(exercises); trainlog_database_free_session_details(output_exercises, copied); return TRAINLOG_STATUS_DATABASE_ERROR; } - (void)memset( - detail, - 0, - sizeof(*detail) - ); + (void)memset(detail, 0, sizeof(*detail)); if (equipment_id != NULL) { (void)snprintf(detail->equipment_id, - sizeof(detail->equipment_id), "%s", + sizeof(detail->equipment_id), + "%s", (const char *)equipment_id); } if (entry_id == NULL) { @@ -5422,148 +5114,72 @@ TrainlogStatus trainlog_database_get_session_details( trainlog_database_free_session_details(output_exercises, copied); return TRAINLOG_STATUS_DATABASE_ERROR; } - (void)snprintf(detail->entry_id, sizeof(detail->entry_id), "%s", - (const char *)entry_id); - (void)snprintf( - detail->name, - sizeof(detail->name), - "%s", - (const char *)name - ); + detail->entry_id, sizeof(detail->entry_id), "%s", (const char *)entry_id); - detail->tracking_mode = - strcmp( - (const char *)tracking, - "duration" - ) == 0 - ? TRAINLOG_TRACKING_DURATION - : TRAINLOG_TRACKING_REPS; + (void)snprintf(detail->name, sizeof(detail->name), "%s", (const char *)name); - detail->recording_mode = - recording_mode_from_sql( - (const char *)recording - ); + detail->tracking_mode = strcmp((const char *)tracking, "duration") == 0 + ? TRAINLOG_TRACKING_DURATION + : TRAINLOG_TRACKING_REPS; - detail->data_fields = - (TrainlogExerciseDataFields) - data_fields; + detail->recording_mode = recording_mode_from_sql((const char *)recording); - detail->load_mode = - detail_load_mode_from_text( - (const char *)load - ); + detail->data_fields = (TrainlogExerciseDataFields)data_fields; - detail->rest_seconds = - sqlite3_column_int( - exercises, - 5 - ); + detail->load_mode = detail_load_mode_from_text((const char *)load); - detail->target_sets = - sqlite3_column_int( - exercises, - 6 - ); + detail->rest_seconds = sqlite3_column_int(exercises, 5); - detail->target_reps = - sqlite3_column_int( - exercises, - 7 - ); + detail->target_sets = sqlite3_column_int(exercises, 6); - detail->target_duration_seconds = - sqlite3_column_int( - exercises, - 8 - ); + detail->target_reps = sqlite3_column_int(exercises, 7); - detail->has_target_weight = - sqlite3_column_type( - exercises, - 9 - ) != SQLITE_NULL; + detail->target_duration_seconds = sqlite3_column_int(exercises, 8); + + detail->has_target_weight = sqlite3_column_type(exercises, 9) != SQLITE_NULL; detail->target_weight_kg = - detail->has_target_weight != 0 - ? sqlite3_column_double( - exercises, - 9 - ) - : 0.0; + detail->has_target_weight != 0 ? sqlite3_column_double(exercises, 9) : 0.0; - detail->has_max_weight = - sqlite3_column_type(exercises, 13) != SQLITE_NULL; + detail->has_max_weight = sqlite3_column_type(exercises, 13) != SQLITE_NULL; detail->max_weight_kg = - detail->has_max_weight != 0 - ? sqlite3_column_double(exercises, 13) - : 0.0; + detail->has_max_weight != 0 ? sqlite3_column_double(exercises, 13) : 0.0; if (detail->has_max_weight != 0) { detail->actual_set_count = 0U; - (void)snprintf( - detail->actual_summary, - sizeof(detail->actual_summary), - "Max %.2f kg", - detail->max_weight_kg - ); - } else if (detail->recording_mode == - TRAINLOG_RECORDING_CONTINUOUS) { + (void)snprintf(detail->actual_summary, + sizeof(detail->actual_summary), + "Max %.2f kg", + detail->max_weight_kg); + } else if (detail->recording_mode == TRAINLOG_RECORDING_CONTINUOUS) { bool speed_required = - (detail->data_fields & - TRAINLOG_EXERCISE_DATA_SPEED_KMH) != 0U; + (detail->data_fields & TRAINLOG_EXERCISE_DATA_SPEED_KMH) != 0U; bool distance_required = - (detail->data_fields & - TRAINLOG_EXERCISE_DATA_DISTANCE_KM) != 0U; + (detail->data_fields & TRAINLOG_EXERCISE_DATA_DISTANCE_KM) != 0U; - if (sqlite3_column_type( - exercises, - 10 - ) == SQLITE_NULL) { + if (sqlite3_column_type(exercises, 10) == SQLITE_NULL) { (void)sqlite3_finalize(exercises); trainlog_database_free_session_details(output_exercises, copied); return TRAINLOG_STATUS_DATABASE_ERROR; } - detail->continuous_duration_seconds = - sqlite3_column_int( - exercises, - 10 - ); + detail->continuous_duration_seconds = sqlite3_column_int(exercises, 10); - detail->has_continuous_speed = - sqlite3_column_type( - exercises, - 11 - ) != SQLITE_NULL; + detail->has_continuous_speed = sqlite3_column_type(exercises, 11) != SQLITE_NULL; detail->continuous_speed_kmh = - detail->has_continuous_speed != 0 - ? sqlite3_column_double( - exercises, - 11 - ) - : 0.0; + detail->has_continuous_speed != 0 ? sqlite3_column_double(exercises, 11) : 0.0; - detail->has_continuous_distance = - sqlite3_column_type( - exercises, - 12 - ) != SQLITE_NULL; + detail->has_continuous_distance = sqlite3_column_type(exercises, 12) != SQLITE_NULL; - detail->continuous_distance_km = - detail->has_continuous_distance != 0 - ? sqlite3_column_double( - exercises, - 12 - ) - : 0.0; + detail->continuous_distance_km = detail->has_continuous_distance != 0 + ? sqlite3_column_double(exercises, 12) + : 0.0; - if ((detail->has_continuous_speed != 0) != - speed_required || - (detail->has_continuous_distance != 0) != - distance_required) { + if ((detail->has_continuous_speed != 0) != speed_required || + (detail->has_continuous_distance != 0) != distance_required) { (void)sqlite3_finalize(exercises); trainlog_database_free_session_details(output_exercises, copied); return TRAINLOG_STATUS_DATABASE_ERROR; @@ -5571,18 +5187,12 @@ TrainlogStatus trainlog_database_get_session_details( detail->actual_set_count = 0U; - (void)snprintf( - detail->actual_summary, - sizeof(detail->actual_summary), - "%s", - "activité continue" - ); + (void)snprintf(detail->actual_summary, + sizeof(detail->actual_summary), + "%s", + "activité continue"); } else { - status = detail_fill_sets( - database, - session_exercise_row_id, - detail - ); + status = detail_fill_sets(database, session_exercise_row_id, detail); if (status != TRAINLOG_STATUS_OK) { (void)sqlite3_finalize(exercises); @@ -5616,8 +5226,7 @@ TrainlogStatus trainlog_database_get_session_details( /* TRAINLOG_BODY_METRIC_HISTORY_IMPLEMENTATION */ -static const char *body_metric_column(TrainlogBodyMetric metric) -{ +static const char *body_metric_column(TrainlogBodyMetric metric) { switch (metric) { case TRAINLOG_BODY_METRIC_WEIGHT: return "body_weight_kg"; @@ -5653,14 +5262,11 @@ static const char *body_metric_column(TrainlogBodyMetric metric) } } -TrainlogStatus trainlog_database_list_body_metric_points( - TrainlogDatabase *database, - TrainlogBodyMetric metric, - TrainlogBodyMetricPoint *output, - size_t capacity, - size_t *output_count -) -{ +TrainlogStatus trainlog_database_list_body_metric_points(TrainlogDatabase *database, + TrainlogBodyMetric metric, + TrainlogBodyMetricPoint *output, + size_t capacity, + size_t *output_count) { const char *column; char sql[256]; sqlite3_stmt *statement = NULL; @@ -5668,9 +5274,7 @@ TrainlogStatus trainlog_database_list_body_metric_points( int written; int rc; - if (database == NULL || - database->connection == NULL || - output_count == NULL || + if (database == NULL || database->connection == NULL || output_count == NULL || (capacity > 0U && output == NULL)) { return TRAINLOG_STATUS_INVALID_ARGUMENT; } @@ -5680,52 +5284,39 @@ TrainlogStatus trainlog_database_list_body_metric_points( return TRAINLOG_STATUS_INVALID_ARGUMENT; } - written = snprintf( - sql, - sizeof(sql), - "SELECT observed_at, %s " - "FROM body_observations " - "WHERE %s IS NOT NULL " - "ORDER BY observed_at ASC, id ASC;", - column, - column - ); + written = snprintf(sql, + sizeof(sql), + "SELECT observed_at, %s " + "FROM body_observations " + "WHERE %s IS NOT NULL " + "ORDER BY observed_at ASC, id ASC;", + column, + column); - if (written < 0 || - (size_t)written >= sizeof(sql)) { + if (written < 0 || (size_t)written >= sizeof(sql)) { return TRAINLOG_STATUS_SYSTEM_ERROR; } - rc = sqlite3_prepare_v2( - database->connection, - sql, - -1, - &statement, - NULL - ); + rc = sqlite3_prepare_v2(database->connection, sql, -1, &statement, NULL); if (rc != SQLITE_OK) { return TRAINLOG_STATUS_DATABASE_ERROR; } while ((rc = sqlite3_step(statement)) == SQLITE_ROW) { if (count < capacity) { - const unsigned char *observed = - sqlite3_column_text(statement, 0); + const unsigned char *observed = sqlite3_column_text(statement, 0); if (observed == NULL) { (void)sqlite3_finalize(statement); return TRAINLOG_STATUS_DATABASE_ERROR; } - (void)snprintf( - output[count].observed_at, - sizeof(output[count].observed_at), - "%s", - (const char *)observed - ); + (void)snprintf(output[count].observed_at, + sizeof(output[count].observed_at), + "%s", + (const char *)observed); - output[count].value = - sqlite3_column_double(statement, 1); + output[count].value = sqlite3_column_double(statement, 1); } ++count; @@ -5740,21 +5331,15 @@ TrainlogStatus trainlog_database_list_body_metric_points( return TRAINLOG_STATUS_DATABASE_ERROR; } - *output_count = - count < capacity - ? count - : capacity; + *output_count = count < capacity ? count : capacity; return TRAINLOG_STATUS_OK; } -TrainlogStatus trainlog_database_latest_body_pair( - TrainlogDatabase *database, - TrainlogBodyMetric left_metric, - TrainlogBodyMetric right_metric, - TrainlogBodyPairPoint *output -) -{ +TrainlogStatus trainlog_database_latest_body_pair(TrainlogDatabase *database, + TrainlogBodyMetric left_metric, + TrainlogBodyMetric right_metric, + TrainlogBodyPairPoint *output) { const char *left_column; const char *right_column; char sql[384]; @@ -5762,48 +5347,36 @@ TrainlogStatus trainlog_database_latest_body_pair( int written; int rc; - if (database == NULL || - database->connection == NULL || - output == NULL) { + if (database == NULL || database->connection == NULL || output == NULL) { return TRAINLOG_STATUS_INVALID_ARGUMENT; } left_column = body_metric_column(left_metric); right_column = body_metric_column(right_metric); - if (left_column == NULL || - right_column == NULL) { + if (left_column == NULL || right_column == NULL) { return TRAINLOG_STATUS_INVALID_ARGUMENT; } (void)memset(output, 0, sizeof(*output)); - written = snprintf( - sql, - sizeof(sql), - "SELECT observed_at, %s, %s " - "FROM body_observations " - "WHERE %s IS NOT NULL AND %s IS NOT NULL " - "ORDER BY observed_at DESC, id DESC " - "LIMIT 1;", - left_column, - right_column, - left_column, - right_column - ); + written = snprintf(sql, + sizeof(sql), + "SELECT observed_at, %s, %s " + "FROM body_observations " + "WHERE %s IS NOT NULL AND %s IS NOT NULL " + "ORDER BY observed_at DESC, id DESC " + "LIMIT 1;", + left_column, + right_column, + left_column, + right_column); - if (written < 0 || - (size_t)written >= sizeof(sql)) { + if (written < 0 || (size_t)written >= sizeof(sql)) { return TRAINLOG_STATUS_SYSTEM_ERROR; } - rc = sqlite3_prepare_v2( - database->connection, - sql, - -1, - &statement, - NULL - ); + rc = sqlite3_prepare_v2(database->connection, sql, -1, &statement, NULL); if (rc != SQLITE_OK) { return TRAINLOG_STATUS_DATABASE_ERROR; } @@ -5822,8 +5395,7 @@ TrainlogStatus trainlog_database_latest_body_pair( } { - const unsigned char *observed = - sqlite3_column_text(statement, 0); + const unsigned char *observed = sqlite3_column_text(statement, 0); if (observed == NULL) { (void)sqlite3_finalize(statement); @@ -5833,53 +5405,37 @@ TrainlogStatus trainlog_database_latest_body_pair( output->found = true; (void)snprintf( - output->observed_at, - sizeof(output->observed_at), - "%s", - (const char *)observed - ); + output->observed_at, sizeof(output->observed_at), "%s", (const char *)observed); - output->left_value = - sqlite3_column_double(statement, 1); + output->left_value = sqlite3_column_double(statement, 1); - output->right_value = - sqlite3_column_double(statement, 2); + output->right_value = sqlite3_column_double(statement, 2); } - return sqlite3_finalize(statement) == SQLITE_OK - ? TRAINLOG_STATUS_OK - : TRAINLOG_STATUS_DATABASE_ERROR; + return sqlite3_finalize(statement) == SQLITE_OK ? TRAINLOG_STATUS_OK + : TRAINLOG_STATUS_DATABASE_ERROR; } /* TRAINLOG_EXERCISE_PERFORMANCE_IMPLEMENTATION */ -static TrainlogLoadMode performance_load_mode_from_sql( - const char *text -) -{ - if (text != NULL && - strcmp(text, "external") == 0) { +static TrainlogLoadMode performance_load_mode_from_sql(const char *text) { + if (text != NULL && strcmp(text, "external") == 0) { return TRAINLOG_LOAD_EXTERNAL; } - if (text != NULL && - strcmp(text, "assistance") == 0) { + if (text != NULL && strcmp(text, "assistance") == 0) { return TRAINLOG_LOAD_ASSISTANCE; } return TRAINLOG_LOAD_NONE; } -static bool performance_candidate_better( - const TrainlogExercisePerformancePoint *current, - TrainlogLoadMode load_mode, - int metric_value, - int has_weight, - double weight_kg -) -{ - if (current == NULL || - metric_value <= 0) { +static bool performance_candidate_better(const TrainlogExercisePerformancePoint *current, + TrainlogLoadMode load_mode, + int metric_value, + int has_weight, + double weight_kg) { + if (current == NULL || metric_value <= 0) { return false; } @@ -5897,9 +5453,7 @@ static bool performance_candidate_better( return true; } - return - weight_kg == current->weight_kg && - metric_value > current->metric_value; + return weight_kg == current->weight_kg && metric_value > current->metric_value; case TRAINLOG_LOAD_ASSISTANCE: if (has_weight == 0) { @@ -5910,9 +5464,7 @@ static bool performance_candidate_better( return true; } - return - weight_kg == current->weight_kg && - metric_value > current->metric_value; + return weight_kg == current->weight_kg && metric_value > current->metric_value; case TRAINLOG_LOAD_NONE: default: @@ -5921,41 +5473,37 @@ static bool performance_candidate_better( } /* TRAINLOG_MEASURED_MAX_SESSION_TYPE_V1 */ -TrainlogStatus trainlog_database_list_exercise_performance( - TrainlogDatabase *database, - const char *exercise_id, - TrainlogExercisePerformancePoint *output, - size_t capacity, - size_t *output_count -) -{ - static const char *const SQL = - "SELECT " - "s.session_id, " - "s.started_at, " - "s.session_type, " - "se.tracking_mode, " - "se.load_mode, " - "ps.id, " - "ps.reps, " - "ps.duration_seconds, " - "ps.weight_kg, " - "mr.max_weight_kg, " - "COALESCE(se.equipment_id, '') " - "FROM session_exercises AS se " - "JOIN sessions AS s " - " ON s.id = se.session_row_id " - "JOIN exercises AS e " - " ON e.id = se.exercise_row_id " - "LEFT JOIN performed_sets AS ps " - " ON ps.session_exercise_row_id = se.id " - "LEFT JOIN max_results AS mr " - " ON mr.session_exercise_row_id = se.id " - "WHERE e.exercise_id = ?1 " - "ORDER BY " - "s.started_at DESC, " - "s.id DESC, " - "se.position ASC, ps.position ASC;"; +TrainlogStatus trainlog_database_list_exercise_performance(TrainlogDatabase *database, + const char *exercise_id, + TrainlogExercisePerformancePoint *output, + size_t capacity, + size_t *output_count) { + static const char *const SQL = "SELECT " + "s.session_id, " + "s.started_at, " + "s.session_type, " + "se.tracking_mode, " + "se.load_mode, " + "ps.id, " + "ps.reps, " + "ps.duration_seconds, " + "ps.weight_kg, " + "mr.max_weight_kg, " + "COALESCE(se.equipment_id, '') " + "FROM session_exercises AS se " + "JOIN sessions AS s " + " ON s.id = se.session_row_id " + "JOIN exercises AS e " + " ON e.id = se.exercise_row_id " + "LEFT JOIN performed_sets AS ps " + " ON ps.session_exercise_row_id = se.id " + "LEFT JOIN max_results AS mr " + " ON mr.session_exercise_row_id = se.id " + "WHERE e.exercise_id = ?1 " + "ORDER BY " + "s.started_at DESC, " + "s.id DESC, " + "se.position ASC, ps.position ASC;"; sqlite3_stmt *statement = NULL; TrainlogExercisePerformancePoint *current = NULL; @@ -5963,180 +5511,89 @@ TrainlogStatus trainlog_database_list_exercise_performance( size_t copied = 0U; int rc; - if ( - database == NULL || - database->connection == NULL || - exercise_id == NULL || - exercise_id[0] == '\0' || - output_count == NULL || - ( - capacity > 0U && - output == NULL - ) - ) { - return - TRAINLOG_STATUS_INVALID_ARGUMENT; + if (database == NULL || database->connection == NULL || exercise_id == NULL || + exercise_id[0] == '\0' || output_count == NULL || (capacity > 0U && output == NULL)) { + return TRAINLOG_STATUS_INVALID_ARGUMENT; } *output_count = 0U; current_session_id[0] = '\0'; - rc = sqlite3_prepare_v2( - database->connection, - SQL, - -1, - &statement, - NULL - ); + rc = sqlite3_prepare_v2(database->connection, SQL, -1, &statement, NULL); if (rc != SQLITE_OK) { - return - TRAINLOG_STATUS_DATABASE_ERROR; + return TRAINLOG_STATUS_DATABASE_ERROR; } - rc = sqlite3_bind_text( - statement, - 1, - exercise_id, - -1, - SQLITE_TRANSIENT - ); + rc = sqlite3_bind_text(statement, 1, exercise_id, -1, SQLITE_TRANSIENT); if (rc != SQLITE_OK) { - (void)sqlite3_finalize( - statement - ); + (void)sqlite3_finalize(statement); - return - TRAINLOG_STATUS_DATABASE_ERROR; + return TRAINLOG_STATUS_DATABASE_ERROR; } - while ( - (rc = sqlite3_step(statement)) == - SQLITE_ROW - ) { - const unsigned char *session_id = - sqlite3_column_text( - statement, - 0 - ); + while ((rc = sqlite3_step(statement)) == SQLITE_ROW) { + const unsigned char *session_id = sqlite3_column_text(statement, 0); - const unsigned char *started_at = - sqlite3_column_text( - statement, - 1 - ); + const unsigned char *started_at = sqlite3_column_text(statement, 1); - const unsigned char *session_type = - sqlite3_column_text( - statement, - 2 - ); + const unsigned char *session_type = sqlite3_column_text(statement, 2); - const unsigned char *tracking_mode = - sqlite3_column_text( - statement, - 3 - ); + const unsigned char *tracking_mode = sqlite3_column_text(statement, 3); - const unsigned char *load_mode = - sqlite3_column_text( - statement, - 4 - ); + const unsigned char *load_mode = sqlite3_column_text(statement, 4); bool new_session; - const unsigned char *equipment_id = - sqlite3_column_text(statement, 10); + const unsigned char *equipment_id = sqlite3_column_text(statement, 10); - if ( - session_id == NULL || - started_at == NULL || - session_type == NULL || - tracking_mode == NULL || - load_mode == NULL - || equipment_id == NULL - || (size_t)sqlite3_column_bytes(statement, 10) > TRAINLOG_ID_MAX - ) { - (void)sqlite3_finalize( - statement - ); + if (session_id == NULL || started_at == NULL || session_type == NULL || + tracking_mode == NULL || load_mode == NULL || equipment_id == NULL || + (size_t)sqlite3_column_bytes(statement, 10) > TRAINLOG_ID_MAX) { + (void)sqlite3_finalize(statement); - return - TRAINLOG_STATUS_DATABASE_ERROR; + return TRAINLOG_STATUS_DATABASE_ERROR; } - new_session = - current_session_id[0] == '\0' || - strcmp( - current_session_id, - (const char *)session_id - ) != 0; + new_session = current_session_id[0] == '\0' || + strcmp(current_session_id, (const char *)session_id) != 0; if (new_session) { (void)snprintf( - current_session_id, - sizeof(current_session_id), - "%s", - (const char *)session_id - ); + current_session_id, sizeof(current_session_id), "%s", (const char *)session_id); current = NULL; if (copied < capacity) { - current = - &output[copied]; + current = &output[copied]; - (void)memset( - current, - 0, - sizeof(*current) - ); + (void)memset(current, 0, sizeof(*current)); - (void)snprintf( - current->session_id, - sizeof(current->session_id), - "%s", - (const char *)session_id - ); + (void)snprintf(current->session_id, + sizeof(current->session_id), + "%s", + (const char *)session_id); - (void)snprintf( - current->started_at, - sizeof(current->started_at), - "%s", - (const char *)started_at - ); + (void)snprintf(current->started_at, + sizeof(current->started_at), + "%s", + (const char *)started_at); - if ( - !session_type_from_sql( - (const char *)session_type, - ¤t->session_type - ) - ) { - (void)sqlite3_finalize( - statement - ); + if (!session_type_from_sql((const char *)session_type, ¤t->session_type)) { + (void)sqlite3_finalize(statement); - return - TRAINLOG_STATUS_DATABASE_ERROR; + return TRAINLOG_STATUS_DATABASE_ERROR; } - current->tracking_mode = - tracking_mode_from_sql( - (const char *)tracking_mode - ); + current->tracking_mode = tracking_mode_from_sql((const char *)tracking_mode); - current->load_mode = - performance_load_mode_from_sql( - (const char *)load_mode - ); + current->load_mode = performance_load_mode_from_sql((const char *)load_mode); ++copied; } } - if (current != NULL && - sqlite3_column_type(statement, 9) != SQLITE_NULL) { + if (current != NULL && sqlite3_column_type(statement, 9) != SQLITE_NULL) { double max_weight = sqlite3_column_double(statement, 9); if (!isfinite(max_weight) || max_weight <= 0.0) { (void)sqlite3_finalize(statement); @@ -6145,8 +5602,7 @@ TrainlogStatus trainlog_database_list_exercise_performance( /* Explicit max results participate in measured-max history without * synthesizing a rep/set count. Repeated movement occurrences in * one session reduce to the greatest recorded max. */ - if (current->has_performance == 0 || - max_weight > current->weight_kg) { + if (current->has_performance == 0 || max_weight > current->weight_kg) { current->load_mode = TRAINLOG_LOAD_EXTERNAL; current->has_performance = 1; current->has_explicit_max = 1; @@ -6154,103 +5610,54 @@ TrainlogStatus trainlog_database_list_exercise_performance( current->has_weight = 1; current->weight_kg = max_weight; (void)snprintf(current->equipment_id, - sizeof(current->equipment_id), "%s", - (const char *)equipment_id); + sizeof(current->equipment_id), + "%s", + (const char *)equipment_id); } - } else if ( - current != NULL && - sqlite3_column_type( - statement, - 5 - ) != SQLITE_NULL - ) { + } else if (current != NULL && sqlite3_column_type(statement, 5) != SQLITE_NULL) { int metric_value; int has_weight; double weight_kg; ++current->actual_set_count; - if ( - current->tracking_mode == - TRAINLOG_TRACKING_REPS - ) { - metric_value = - sqlite3_column_type( - statement, - 6 - ) != SQLITE_NULL - ? sqlite3_column_int( - statement, - 6 - ) - : 0; + if (current->tracking_mode == TRAINLOG_TRACKING_REPS) { + metric_value = sqlite3_column_type(statement, 6) != SQLITE_NULL + ? sqlite3_column_int(statement, 6) + : 0; } else { - metric_value = - sqlite3_column_type( - statement, - 7 - ) != SQLITE_NULL - ? sqlite3_column_int( - statement, - 7 - ) - : 0; + metric_value = sqlite3_column_type(statement, 7) != SQLITE_NULL + ? sqlite3_column_int(statement, 7) + : 0; } - has_weight = - sqlite3_column_type( - statement, - 8 - ) != SQLITE_NULL; + has_weight = sqlite3_column_type(statement, 8) != SQLITE_NULL; - weight_kg = - has_weight != 0 - ? sqlite3_column_double( - statement, - 8 - ) - : 0.0; + weight_kg = has_weight != 0 ? sqlite3_column_double(statement, 8) : 0.0; - if ( - performance_candidate_better( - current, - current->load_mode, - metric_value, - has_weight, - weight_kg - ) - ) { + if (performance_candidate_better( + current, current->load_mode, metric_value, has_weight, weight_kg)) { current->has_performance = 1; current->has_explicit_max = 0; - current->metric_value = - metric_value; - current->has_weight = - has_weight; - current->weight_kg = - weight_kg; + current->metric_value = metric_value; + current->has_weight = has_weight; + current->weight_kg = weight_kg; (void)snprintf(current->equipment_id, - sizeof(current->equipment_id), "%s", - (const char *)equipment_id); + sizeof(current->equipment_id), + "%s", + (const char *)equipment_id); } } } if (rc != SQLITE_DONE) { - (void)sqlite3_finalize( - statement - ); + (void)sqlite3_finalize(statement); - return - TRAINLOG_STATUS_DATABASE_ERROR; + return TRAINLOG_STATUS_DATABASE_ERROR; } - if ( - sqlite3_finalize( - statement - ) != SQLITE_OK - ) { - return - TRAINLOG_STATUS_DATABASE_ERROR; + if (sqlite3_finalize(statement) != SQLITE_OK) { + return TRAINLOG_STATUS_DATABASE_ERROR; } *output_count = copied; @@ -6258,25 +5665,22 @@ TrainlogStatus trainlog_database_list_exercise_performance( return TRAINLOG_STATUS_OK; } -TrainlogStatus trainlog_database_load_session_editable( - TrainlogDatabase *database, - const char *session_id, - TrainlogSessionSummary *output_session, - TrainlogEditableExerciseRecord *output_exercises, - size_t exercise_capacity, - size_t *output_exercise_count, - TrainlogSetInput *output_sets, - size_t set_capacity, - size_t *output_set_count -) -{ - static const char *const HEADER_SQL = - "SELECT " - "started_at, " - "COALESCE(ended_at, ''), " - "session_type " - "FROM sessions " - "WHERE session_id = ?1;"; +TrainlogStatus +trainlog_database_load_session_editable(TrainlogDatabase *database, + const char *session_id, + TrainlogSessionSummary *output_session, + TrainlogEditableExerciseRecord *output_exercises, + size_t exercise_capacity, + size_t *output_exercise_count, + TrainlogSetInput *output_sets, + size_t set_capacity, + size_t *output_set_count) { + static const char *const HEADER_SQL = "SELECT " + "started_at, " + "COALESCE(ended_at, ''), " + "session_type " + "FROM sessions " + "WHERE session_id = ?1;"; static const char *const EXERCISE_SQL = "SELECT " @@ -6306,11 +5710,10 @@ TrainlogStatus trainlog_database_load_session_editable( "WHERE s.session_id = ?1 " "ORDER BY se.position ASC;"; - static const char *const SET_SQL = - "SELECT reps, duration_seconds, weight_kg " - "FROM performed_sets " - "WHERE session_exercise_row_id = ?1 " - "ORDER BY position ASC;"; + static const char *const SET_SQL = "SELECT reps, duration_seconds, weight_kg " + "FROM performed_sets " + "WHERE session_exercise_row_id = ?1 " + "ORDER BY position ASC;"; sqlite3_stmt *header = NULL; sqlite3_stmt *exercise_statement = NULL; @@ -6319,44 +5722,21 @@ TrainlogStatus trainlog_database_load_session_editable( size_t set_count = 0U; int rc; - if (database == NULL || - database->connection == NULL || - session_id == NULL || - session_id[0] == '\0' || - output_session == NULL || - output_exercise_count == NULL || - output_set_count == NULL || - (exercise_capacity > 0U && - output_exercises == NULL) || - (set_capacity > 0U && - output_sets == NULL)) { + if (database == NULL || database->connection == NULL || session_id == NULL || + session_id[0] == '\0' || output_session == NULL || output_exercise_count == NULL || + output_set_count == NULL || (exercise_capacity > 0U && output_exercises == NULL) || + (set_capacity > 0U && output_sets == NULL)) { return TRAINLOG_STATUS_INVALID_ARGUMENT; } *output_exercise_count = 0U; *output_set_count = 0U; - (void)memset( - output_session, - 0, - sizeof(*output_session) - ); + (void)memset(output_session, 0, sizeof(*output_session)); - rc = sqlite3_prepare_v2( - database->connection, - HEADER_SQL, - -1, - &header, - NULL - ); + rc = sqlite3_prepare_v2(database->connection, HEADER_SQL, -1, &header, NULL); if (rc == SQLITE_OK) { - rc = sqlite3_bind_text( - header, - 1, - session_id, - -1, - SQLITE_TRANSIENT - ); + rc = sqlite3_bind_text(header, 1, session_id, -1, SQLITE_TRANSIENT); } if (rc != SQLITE_OK) { @@ -6377,47 +5757,29 @@ TrainlogStatus trainlog_database_load_session_editable( } { - const unsigned char *started = - sqlite3_column_text(header, 0); + const unsigned char *started = sqlite3_column_text(header, 0); - const unsigned char *ended = - sqlite3_column_text(header, 1); + const unsigned char *ended = sqlite3_column_text(header, 1); - const unsigned char *session_type = - sqlite3_column_text(header, 2); + const unsigned char *session_type = sqlite3_column_text(header, 2); - if (started == NULL || - ended == NULL || - session_type == NULL) { + if (started == NULL || ended == NULL || session_type == NULL) { (void)sqlite3_finalize(header); return TRAINLOG_STATUS_DATABASE_ERROR; } (void)snprintf( - output_session->session_id, - sizeof(output_session->session_id), - "%s", - session_id - ); + output_session->session_id, sizeof(output_session->session_id), "%s", session_id); + + (void)snprintf(output_session->started_at, + sizeof(output_session->started_at), + "%s", + (const char *)started); (void)snprintf( - output_session->started_at, - sizeof(output_session->started_at), - "%s", - (const char *)started - ); + output_session->ended_at, sizeof(output_session->ended_at), "%s", (const char *)ended); - (void)snprintf( - output_session->ended_at, - sizeof(output_session->ended_at), - "%s", - (const char *)ended - ); - - if (!session_type_from_sql( - (const char *)session_type, - &output_session->session_type - )) { + if (!session_type_from_sql((const char *)session_type, &output_session->session_type)) { (void)sqlite3_finalize(header); return TRAINLOG_STATUS_DATABASE_ERROR; } @@ -6429,22 +5791,10 @@ TrainlogStatus trainlog_database_load_session_editable( header = NULL; - rc = sqlite3_prepare_v2( - database->connection, - EXERCISE_SQL, - -1, - &exercise_statement, - NULL - ); + rc = sqlite3_prepare_v2(database->connection, EXERCISE_SQL, -1, &exercise_statement, NULL); if (rc == SQLITE_OK) { - rc = sqlite3_bind_text( - exercise_statement, - 1, - session_id, - -1, - SQLITE_TRANSIENT - ); + rc = sqlite3_bind_text(exercise_statement, 1, session_id, -1, SQLITE_TRANSIENT); } if (rc != SQLITE_OK) { @@ -6467,68 +5817,29 @@ TrainlogStatus trainlog_database_load_session_editable( return TRAINLOG_STATUS_INVALID_ARGUMENT; } - record = - &output_exercises[exercise_count]; + record = &output_exercises[exercise_count]; - session_exercise_row_id = - sqlite3_column_int64( - exercise_statement, - 0 - ); + session_exercise_row_id = sqlite3_column_int64(exercise_statement, 0); - exercise_id = - sqlite3_column_text( - exercise_statement, - 1 - ); + exercise_id = sqlite3_column_text(exercise_statement, 1); - name = - sqlite3_column_text( - exercise_statement, - 2 - ); + name = sqlite3_column_text(exercise_statement, 2); - tracking = - sqlite3_column_text( - exercise_statement, - 3 - ); + tracking = sqlite3_column_text(exercise_statement, 3); - load = - sqlite3_column_text( - exercise_statement, - 4 - ); + load = sqlite3_column_text(exercise_statement, 4); - notes = - sqlite3_column_text( - exercise_statement, - 10 - ); + notes = sqlite3_column_text(exercise_statement, 10); - note_bytes = - sqlite3_column_bytes( - exercise_statement, - 10 - ); + note_bytes = sqlite3_column_bytes(exercise_statement, 10); - if (exercise_id == NULL || - name == NULL || - tracking == NULL || - load == NULL || - notes == NULL || - note_bytes < 0 || - (size_t)note_bytes > - TRAINLOG_NOTE_MAX) { + if (exercise_id == NULL || name == NULL || tracking == NULL || load == NULL || + notes == NULL || note_bytes < 0 || (size_t)note_bytes > TRAINLOG_NOTE_MAX) { (void)sqlite3_finalize(exercise_statement); return TRAINLOG_STATUS_DATABASE_ERROR; } - (void)memset( - record, - 0, - sizeof(*record) - ); + (void)memset(record, 0, sizeof(*record)); if (sqlite3_column_type(exercise_statement, 11) != SQLITE_NULL) { const unsigned char *equipment_id = sqlite3_column_text(exercise_statement, 11); @@ -6536,101 +5847,59 @@ TrainlogStatus trainlog_database_load_session_editable( (void)sqlite3_finalize(exercise_statement); return TRAINLOG_STATUS_DATABASE_ERROR; } - (void)snprintf(record->equipment_id, sizeof(record->equipment_id), - "%s", (const char *)equipment_id); + (void)snprintf(record->equipment_id, + sizeof(record->equipment_id), + "%s", + (const char *)equipment_id); } if (sqlite3_column_type(exercise_statement, 12) == SQLITE_NULL) { (void)sqlite3_finalize(exercise_statement); return TRAINLOG_STATUS_DATABASE_ERROR; } - (void)snprintf(record->entry_id, sizeof(record->entry_id), "%s", + (void)snprintf(record->entry_id, + sizeof(record->entry_id), + "%s", (const char *)sqlite3_column_text(exercise_statement, 12)); (void)snprintf( - record->exercise_id, - sizeof(record->exercise_id), - "%s", - (const char *)exercise_id - ); + record->exercise_id, sizeof(record->exercise_id), "%s", (const char *)exercise_id); - (void)snprintf( - record->name, - sizeof(record->name), - "%s", - (const char *)name - ); + (void)snprintf(record->name, sizeof(record->name), "%s", (const char *)name); - record->tracking_mode = - tracking_mode_from_sql( - (const char *)tracking - ); + record->tracking_mode = tracking_mode_from_sql((const char *)tracking); - record->recording_mode = recording_mode_from_sql( - (const char *)sqlite3_column_text(exercise_statement, 14)); + record->recording_mode = + recording_mode_from_sql((const char *)sqlite3_column_text(exercise_statement, 14)); { - sqlite3_int64 data_fields = - sqlite3_column_int64(exercise_statement, 15); - if (data_fields < 0 || - (uint64_t)data_fields > (uint64_t)UINT32_MAX || - (((TrainlogExerciseDataFields)data_fields) & - ~TRAINLOG_EXERCISE_DATA_KNOWN_MASK) != 0U) { + sqlite3_int64 data_fields = sqlite3_column_int64(exercise_statement, 15); + if (data_fields < 0 || (uint64_t)data_fields > (uint64_t)UINT32_MAX || + (((TrainlogExerciseDataFields)data_fields) & ~TRAINLOG_EXERCISE_DATA_KNOWN_MASK) != + 0U) { (void)sqlite3_finalize(exercise_statement); return TRAINLOG_STATUS_DATABASE_ERROR; } - record->data_fields = - (TrainlogExerciseDataFields)data_fields; + record->data_fields = (TrainlogExerciseDataFields)data_fields; } - record->load_mode = - detail_load_mode_from_text( - (const char *)load - ); + record->load_mode = detail_load_mode_from_text((const char *)load); - record->rest_seconds = - sqlite3_column_int( - exercise_statement, - 5 - ); + record->rest_seconds = sqlite3_column_int(exercise_statement, 5); - record->target_sets = - sqlite3_column_int( - exercise_statement, - 6 - ); + record->target_sets = sqlite3_column_int(exercise_statement, 6); - record->target_reps = - sqlite3_column_int( - exercise_statement, - 7 - ); + record->target_reps = sqlite3_column_int(exercise_statement, 7); - record->target_duration_seconds = - sqlite3_column_int( - exercise_statement, - 8 - ); + record->target_duration_seconds = sqlite3_column_int(exercise_statement, 8); - record->has_target_weight = - sqlite3_column_type( - exercise_statement, - 9 - ) != SQLITE_NULL; + record->has_target_weight = sqlite3_column_type(exercise_statement, 9) != SQLITE_NULL; record->target_weight_kg = - record->has_target_weight != 0 - ? sqlite3_column_double( - exercise_statement, - 9 - ) - : 0.0; + record->has_target_weight != 0 ? sqlite3_column_double(exercise_statement, 9) : 0.0; - record->has_max_weight = - sqlite3_column_type(exercise_statement, 13) != SQLITE_NULL; + record->has_max_weight = sqlite3_column_type(exercise_statement, 13) != SQLITE_NULL; record->max_weight_kg = - record->has_max_weight != 0 - ? sqlite3_column_double(exercise_statement, 13) - : 0.0; + record->has_max_weight != 0 ? sqlite3_column_double(exercise_statement, 13) : 0.0; /* * CONTRACT: persisted-session editing must round-trip a continuous @@ -6638,45 +5907,27 @@ TrainlogStatus trainlog_database_load_session_editable( * could discard or invalidate its warm-up entry. */ if (sqlite3_column_type(exercise_statement, 16) != SQLITE_NULL) { - record->continuous_duration_seconds = - sqlite3_column_int(exercise_statement, 16); + record->continuous_duration_seconds = sqlite3_column_int(exercise_statement, 16); record->has_continuous_speed = sqlite3_column_type(exercise_statement, 17) != SQLITE_NULL; - record->continuous_speed_kmh = - record->has_continuous_speed != 0 - ? sqlite3_column_double(exercise_statement, 17) - : 0.0; + record->continuous_speed_kmh = record->has_continuous_speed != 0 + ? sqlite3_column_double(exercise_statement, 17) + : 0.0; record->has_continuous_distance = sqlite3_column_type(exercise_statement, 18) != SQLITE_NULL; - record->continuous_distance_km = - record->has_continuous_distance != 0 - ? sqlite3_column_double(exercise_statement, 18) - : 0.0; + record->continuous_distance_km = record->has_continuous_distance != 0 + ? sqlite3_column_double(exercise_statement, 18) + : 0.0; } - (void)snprintf( - record->notes, - sizeof(record->notes), - "%s", - (const char *)notes - ); + (void)snprintf(record->notes, sizeof(record->notes), "%s", (const char *)notes); record->set_offset = set_count; - rc = sqlite3_prepare_v2( - database->connection, - SET_SQL, - -1, - &set_statement, - NULL - ); + rc = sqlite3_prepare_v2(database->connection, SET_SQL, -1, &set_statement, NULL); if (rc == SQLITE_OK) { - rc = sqlite3_bind_int64( - set_statement, - 1, - session_exercise_row_id - ); + rc = sqlite3_bind_int64(set_statement, 1, session_exercise_row_id); } if (rc != SQLITE_OK) { @@ -6698,17 +5949,9 @@ TrainlogStatus trainlog_database_load_session_editable( set = &output_sets[set_count]; - has_reps = - sqlite3_column_type( - set_statement, - 0 - ) != SQLITE_NULL; + has_reps = sqlite3_column_type(set_statement, 0) != SQLITE_NULL; - has_duration = - sqlite3_column_type( - set_statement, - 1 - ) != SQLITE_NULL; + has_duration = sqlite3_column_type(set_statement, 1) != SQLITE_NULL; if (has_reps == has_duration) { (void)sqlite3_finalize(set_statement); @@ -6716,39 +5959,17 @@ TrainlogStatus trainlog_database_load_session_editable( return TRAINLOG_STATUS_DATABASE_ERROR; } - (void)memset( - set, - 0, - sizeof(*set) - ); + (void)memset(set, 0, sizeof(*set)); if (has_reps != 0) { - set->reps = - sqlite3_column_int( - set_statement, - 0 - ); + set->reps = sqlite3_column_int(set_statement, 0); } else { - set->duration_seconds = - sqlite3_column_int( - set_statement, - 1 - ); + set->duration_seconds = sqlite3_column_int(set_statement, 1); } - set->has_weight = - sqlite3_column_type( - set_statement, - 2 - ) != SQLITE_NULL; + set->has_weight = sqlite3_column_type(set_statement, 2) != SQLITE_NULL; - set->weight_kg = - set->has_weight - ? sqlite3_column_double( - set_statement, - 2 - ) - : 0.0; + set->weight_kg = set->has_weight ? sqlite3_column_double(set_statement, 2) : 0.0; ++set_count; ++record->set_count; @@ -6778,263 +5999,143 @@ TrainlogStatus trainlog_database_load_session_editable( return TRAINLOG_STATUS_DATABASE_ERROR; } - output_session->exercise_count = - exercise_count; + output_session->exercise_count = exercise_count; - *output_exercise_count = - exercise_count; + *output_exercise_count = exercise_count; - *output_set_count = - set_count; + *output_set_count = set_count; return TRAINLOG_STATUS_OK; } /* TRAINLOG_BODY_OBSERVATION_RECORD_IMPLEMENTATION */ -static bool body_record_fill( - sqlite3_stmt *statement, - TrainlogBodyObservationRecord *record -) -{ +static bool body_record_fill(sqlite3_stmt *statement, TrainlogBodyObservationRecord *record) { const unsigned char *observation_id; const unsigned char *observed_at; const unsigned char *session_id; const unsigned char *notes; int note_bytes; - if (statement == NULL || - record == NULL) { + if (statement == NULL || record == NULL) { return false; } - observation_id = - sqlite3_column_text(statement, 0); + observation_id = sqlite3_column_text(statement, 0); - observed_at = - sqlite3_column_text(statement, 1); + observed_at = sqlite3_column_text(statement, 1); - session_id = - sqlite3_column_text(statement, 2); + session_id = sqlite3_column_text(statement, 2); - notes = - sqlite3_column_text(statement, 17); + notes = sqlite3_column_text(statement, 17); - note_bytes = - sqlite3_column_bytes(statement, 17); + note_bytes = sqlite3_column_bytes(statement, 17); - if (observation_id == NULL || - observed_at == NULL || - session_id == NULL || - notes == NULL || - note_bytes < 0 || - (size_t)note_bytes > TRAINLOG_NOTE_MAX) { + if (observation_id == NULL || observed_at == NULL || session_id == NULL || notes == NULL || + note_bytes < 0 || (size_t)note_bytes > TRAINLOG_NOTE_MAX) { return false; } - (void)memset( - record, - 0, - sizeof(*record) - ); + (void)memset(record, 0, sizeof(*record)); (void)snprintf( - record->observation_id, - sizeof(record->observation_id), - "%s", - (const char *)observation_id - ); + record->observation_id, sizeof(record->observation_id), "%s", (const char *)observation_id); (void)snprintf( - record->observed_at, - sizeof(record->observed_at), - "%s", - (const char *)observed_at - ); + record->observed_at, sizeof(record->observed_at), "%s", (const char *)observed_at); - (void)snprintf( - record->session_id, - sizeof(record->session_id), - "%s", - (const char *)session_id - ); + (void)snprintf(record->session_id, sizeof(record->session_id), "%s", (const char *)session_id); -#define READ_BODY_VALUE(column_, flag_, value_) \ - do { \ - (flag_) = \ - sqlite3_column_type( \ - statement, \ - (column_) \ - ) != SQLITE_NULL; \ - (value_) = \ - (flag_) \ - ? sqlite3_column_double( \ - statement, \ - (column_) \ - ) \ - : 0.0; \ +#define READ_BODY_VALUE(column_, flag_, value_) \ + do { \ + (flag_) = sqlite3_column_type(statement, (column_)) != SQLITE_NULL; \ + (value_) = (flag_) ? sqlite3_column_double(statement, (column_)) : 0.0; \ } while (0) - READ_BODY_VALUE( - 3, - record->has_body_weight, - record->body_weight_kg - ); + READ_BODY_VALUE(3, record->has_body_weight, record->body_weight_kg); - READ_BODY_VALUE( - 4, - record->has_neck, - record->neck_cm - ); + READ_BODY_VALUE(4, record->has_neck, record->neck_cm); - READ_BODY_VALUE( - 5, - record->has_shoulders, - record->shoulders_cm - ); + READ_BODY_VALUE(5, record->has_shoulders, record->shoulders_cm); - READ_BODY_VALUE( - 6, - record->has_chest, - record->chest_cm - ); + READ_BODY_VALUE(6, record->has_chest, record->chest_cm); - READ_BODY_VALUE( - 7, - record->has_waist, - record->waist_cm - ); + READ_BODY_VALUE(7, record->has_waist, record->waist_cm); - READ_BODY_VALUE( - 8, - record->has_hips, - record->hips_cm - ); + READ_BODY_VALUE(8, record->has_hips, record->hips_cm); - READ_BODY_VALUE( - 9, - record->has_left_arm, - record->left_arm_cm - ); + READ_BODY_VALUE(9, record->has_left_arm, record->left_arm_cm); - READ_BODY_VALUE( - 10, - record->has_right_arm, - record->right_arm_cm - ); + READ_BODY_VALUE(10, record->has_right_arm, record->right_arm_cm); - READ_BODY_VALUE( - 11, - record->has_left_forearm, - record->left_forearm_cm - ); + READ_BODY_VALUE(11, record->has_left_forearm, record->left_forearm_cm); - READ_BODY_VALUE( - 12, - record->has_right_forearm, - record->right_forearm_cm - ); + READ_BODY_VALUE(12, record->has_right_forearm, record->right_forearm_cm); - READ_BODY_VALUE( - 13, - record->has_left_thigh, - record->left_thigh_cm - ); + READ_BODY_VALUE(13, record->has_left_thigh, record->left_thigh_cm); - READ_BODY_VALUE( - 14, - record->has_right_thigh, - record->right_thigh_cm - ); + READ_BODY_VALUE(14, record->has_right_thigh, record->right_thigh_cm); - READ_BODY_VALUE( - 15, - record->has_left_calf, - record->left_calf_cm - ); + READ_BODY_VALUE(15, record->has_left_calf, record->left_calf_cm); - READ_BODY_VALUE( - 16, - record->has_right_calf, - record->right_calf_cm - ); + READ_BODY_VALUE(16, record->has_right_calf, record->right_calf_cm); #undef READ_BODY_VALUE - (void)snprintf( - record->notes, - sizeof(record->notes), - "%s", - (const char *)notes - ); + (void)snprintf(record->notes, sizeof(record->notes), "%s", (const char *)notes); return true; } -static const char *const BODY_RECORD_SELECT = - "SELECT " - "bo.observation_id, " - "bo.observed_at, " - "COALESCE(s.session_id, ''), " - "bo.body_weight_kg, " - "bo.neck_cm, " - "bo.shoulders_cm, " - "bo.chest_cm, " - "bo.waist_cm, " - "bo.hips_cm, " - "bo.left_arm_cm, " - "bo.right_arm_cm, " - "bo.left_forearm_cm, " - "bo.right_forearm_cm, " - "bo.left_thigh_cm, " - "bo.right_thigh_cm, " - "bo.left_calf_cm, " - "bo.right_calf_cm, " - "COALESCE(bo.notes, '') " - "FROM body_observations AS bo " - "LEFT JOIN sessions AS s " - "ON s.id = bo.session_row_id "; +static const char *const BODY_RECORD_SELECT = "SELECT " + "bo.observation_id, " + "bo.observed_at, " + "COALESCE(s.session_id, ''), " + "bo.body_weight_kg, " + "bo.neck_cm, " + "bo.shoulders_cm, " + "bo.chest_cm, " + "bo.waist_cm, " + "bo.hips_cm, " + "bo.left_arm_cm, " + "bo.right_arm_cm, " + "bo.left_forearm_cm, " + "bo.right_forearm_cm, " + "bo.left_thigh_cm, " + "bo.right_thigh_cm, " + "bo.left_calf_cm, " + "bo.right_calf_cm, " + "COALESCE(bo.notes, '') " + "FROM body_observations AS bo " + "LEFT JOIN sessions AS s " + "ON s.id = bo.session_row_id "; -TrainlogStatus trainlog_database_list_body_observations( - TrainlogDatabase *database, - TrainlogBodyObservationRecord *output, - size_t capacity, - size_t *output_count -) -{ +TrainlogStatus trainlog_database_list_body_observations(TrainlogDatabase *database, + TrainlogBodyObservationRecord *output, + size_t capacity, + size_t *output_count) { char sql[1024]; sqlite3_stmt *statement = NULL; size_t count = 0U; int written; int rc; - if (database == NULL || - database->connection == NULL || - output_count == NULL || + if (database == NULL || database->connection == NULL || output_count == NULL || (capacity > 0U && output == NULL)) { return TRAINLOG_STATUS_INVALID_ARGUMENT; } - written = snprintf( - sql, - sizeof(sql), - "%s" - "ORDER BY bo.observed_at DESC, bo.id DESC;", - BODY_RECORD_SELECT - ); + written = snprintf(sql, + sizeof(sql), + "%s" + "ORDER BY bo.observed_at DESC, bo.id DESC;", + BODY_RECORD_SELECT); - if (written < 0 || - (size_t)written >= sizeof(sql)) { + if (written < 0 || (size_t)written >= sizeof(sql)) { return TRAINLOG_STATUS_SYSTEM_ERROR; } - rc = sqlite3_prepare_v2( - database->connection, - sql, - -1, - &statement, - NULL - ); + rc = sqlite3_prepare_v2(database->connection, sql, -1, &statement, NULL); if (rc != SQLITE_OK) { return TRAINLOG_STATUS_DATABASE_ERROR; @@ -7042,10 +6143,7 @@ TrainlogStatus trainlog_database_list_body_observations( while ((rc = sqlite3_step(statement)) == SQLITE_ROW) { if (count < capacity) { - if (!body_record_fill( - statement, - &output[count] - )) { + if (!body_record_fill(statement, &output[count])) { (void)sqlite3_finalize(statement); return TRAINLOG_STATUS_DATABASE_ERROR; } @@ -7063,62 +6161,38 @@ TrainlogStatus trainlog_database_list_body_observations( return TRAINLOG_STATUS_DATABASE_ERROR; } - *output_count = - count < capacity - ? count - : capacity; + *output_count = count < capacity ? count : capacity; return TRAINLOG_STATUS_OK; } -TrainlogStatus trainlog_database_get_body_observation( - TrainlogDatabase *database, - const char *observation_id, - TrainlogBodyObservationRecord *output -) -{ +TrainlogStatus trainlog_database_get_body_observation(TrainlogDatabase *database, + const char *observation_id, + TrainlogBodyObservationRecord *output) { char sql[1024]; sqlite3_stmt *statement = NULL; int written; int rc; - if (database == NULL || - database->connection == NULL || - observation_id == NULL || - observation_id[0] == '\0' || - output == NULL) { + if (database == NULL || database->connection == NULL || observation_id == NULL || + observation_id[0] == '\0' || output == NULL) { return TRAINLOG_STATUS_INVALID_ARGUMENT; } - written = snprintf( - sql, - sizeof(sql), - "%s" - "WHERE bo.observation_id = ?1;", - BODY_RECORD_SELECT - ); + written = snprintf(sql, + sizeof(sql), + "%s" + "WHERE bo.observation_id = ?1;", + BODY_RECORD_SELECT); - if (written < 0 || - (size_t)written >= sizeof(sql)) { + if (written < 0 || (size_t)written >= sizeof(sql)) { return TRAINLOG_STATUS_SYSTEM_ERROR; } - rc = sqlite3_prepare_v2( - database->connection, - sql, - -1, - &statement, - NULL - ); + rc = sqlite3_prepare_v2(database->connection, sql, -1, &statement, NULL); if (rc == SQLITE_OK) { - rc = sqlite3_bind_text( - statement, - 1, - observation_id, - -1, - SQLITE_TRANSIENT - ); + rc = sqlite3_bind_text(statement, 1, observation_id, -1, SQLITE_TRANSIENT); } if (rc != SQLITE_OK) { @@ -7138,209 +6212,107 @@ TrainlogStatus trainlog_database_get_body_observation( return TRAINLOG_STATUS_DATABASE_ERROR; } - if (!body_record_fill( - statement, - output - )) { + if (!body_record_fill(statement, output)) { (void)sqlite3_finalize(statement); return TRAINLOG_STATUS_DATABASE_ERROR; } - return sqlite3_finalize(statement) == SQLITE_OK - ? TRAINLOG_STATUS_OK - : TRAINLOG_STATUS_DATABASE_ERROR; + return sqlite3_finalize(statement) == SQLITE_OK ? TRAINLOG_STATUS_OK + : TRAINLOG_STATUS_DATABASE_ERROR; } -TrainlogStatus trainlog_database_update_body_observation( - TrainlogDatabase *database, - const TrainlogBodyObservationInput *observation -) -{ - static const char *const SQL = - "UPDATE body_observations SET " - "body_weight_kg = ?1, " - "neck_cm = ?2, " - "shoulders_cm = ?3, " - "chest_cm = ?4, " - "waist_cm = ?5, " - "hips_cm = ?6, " - "left_arm_cm = ?7, " - "right_arm_cm = ?8, " - "left_forearm_cm = ?9, " - "right_forearm_cm = ?10, " - "left_thigh_cm = ?11, " - "right_thigh_cm = ?12, " - "left_calf_cm = ?13, " - "right_calf_cm = ?14, " - "notes = ?15 " - "WHERE observation_id = ?16;"; +TrainlogStatus +trainlog_database_update_body_observation(TrainlogDatabase *database, + const TrainlogBodyObservationInput *observation) { + static const char *const SQL = "UPDATE body_observations SET " + "body_weight_kg = ?1, " + "neck_cm = ?2, " + "shoulders_cm = ?3, " + "chest_cm = ?4, " + "waist_cm = ?5, " + "hips_cm = ?6, " + "left_arm_cm = ?7, " + "right_arm_cm = ?8, " + "left_forearm_cm = ?9, " + "right_forearm_cm = ?10, " + "left_thigh_cm = ?11, " + "right_thigh_cm = ?12, " + "left_calf_cm = ?13, " + "right_calf_cm = ?14, " + "notes = ?15 " + "WHERE observation_id = ?16;"; sqlite3_stmt *statement = NULL; bool any_metric; int rc; - if (database == NULL || - database->connection == NULL || - observation == NULL || + if (database == NULL || database->connection == NULL || observation == NULL || observation->observation_id[0] == '\0') { return TRAINLOG_STATUS_INVALID_ARGUMENT; } - any_metric = - observation->has_body_weight || - observation->has_neck || - observation->has_shoulders || - observation->has_chest || - observation->has_waist || - observation->has_hips || - observation->has_left_arm || - observation->has_right_arm || - observation->has_left_forearm || - observation->has_right_forearm || - observation->has_left_thigh || - observation->has_right_thigh || - observation->has_left_calf || - observation->has_right_calf; + any_metric = observation->has_body_weight || observation->has_neck || + observation->has_shoulders || observation->has_chest || observation->has_waist || + observation->has_hips || observation->has_left_arm || observation->has_right_arm || + observation->has_left_forearm || observation->has_right_forearm || + observation->has_left_thigh || observation->has_right_thigh || + observation->has_left_calf || observation->has_right_calf; if (!any_metric) { return TRAINLOG_STATUS_INVALID_ARGUMENT; } - rc = sqlite3_prepare_v2( - database->connection, - SQL, - -1, - &statement, - NULL - ); + rc = sqlite3_prepare_v2(database->connection, SQL, -1, &statement, NULL); if (rc != SQLITE_OK) { return TRAINLOG_STATUS_DATABASE_ERROR; } -#define UPDATE_BODY_VALUE(index_, flag_, value_) \ - do { \ - if (rc == SQLITE_OK) { \ - rc = bind_optional_double( \ - statement, \ - (index_), \ - (flag_), \ - (value_) \ - ); \ - } \ +#define UPDATE_BODY_VALUE(index_, flag_, value_) \ + do { \ + if (rc == SQLITE_OK) { \ + rc = bind_optional_double(statement, (index_), (flag_), (value_)); \ + } \ } while (0) - UPDATE_BODY_VALUE( - 1, - observation->has_body_weight, - observation->body_weight_kg - ); + UPDATE_BODY_VALUE(1, observation->has_body_weight, observation->body_weight_kg); - UPDATE_BODY_VALUE( - 2, - observation->has_neck, - observation->neck_cm - ); + UPDATE_BODY_VALUE(2, observation->has_neck, observation->neck_cm); - UPDATE_BODY_VALUE( - 3, - observation->has_shoulders, - observation->shoulders_cm - ); + UPDATE_BODY_VALUE(3, observation->has_shoulders, observation->shoulders_cm); - UPDATE_BODY_VALUE( - 4, - observation->has_chest, - observation->chest_cm - ); + UPDATE_BODY_VALUE(4, observation->has_chest, observation->chest_cm); - UPDATE_BODY_VALUE( - 5, - observation->has_waist, - observation->waist_cm - ); + UPDATE_BODY_VALUE(5, observation->has_waist, observation->waist_cm); - UPDATE_BODY_VALUE( - 6, - observation->has_hips, - observation->hips_cm - ); + UPDATE_BODY_VALUE(6, observation->has_hips, observation->hips_cm); - UPDATE_BODY_VALUE( - 7, - observation->has_left_arm, - observation->left_arm_cm - ); + UPDATE_BODY_VALUE(7, observation->has_left_arm, observation->left_arm_cm); - UPDATE_BODY_VALUE( - 8, - observation->has_right_arm, - observation->right_arm_cm - ); + UPDATE_BODY_VALUE(8, observation->has_right_arm, observation->right_arm_cm); - UPDATE_BODY_VALUE( - 9, - observation->has_left_forearm, - observation->left_forearm_cm - ); + UPDATE_BODY_VALUE(9, observation->has_left_forearm, observation->left_forearm_cm); - UPDATE_BODY_VALUE( - 10, - observation->has_right_forearm, - observation->right_forearm_cm - ); + UPDATE_BODY_VALUE(10, observation->has_right_forearm, observation->right_forearm_cm); - UPDATE_BODY_VALUE( - 11, - observation->has_left_thigh, - observation->left_thigh_cm - ); + UPDATE_BODY_VALUE(11, observation->has_left_thigh, observation->left_thigh_cm); - UPDATE_BODY_VALUE( - 12, - observation->has_right_thigh, - observation->right_thigh_cm - ); + UPDATE_BODY_VALUE(12, observation->has_right_thigh, observation->right_thigh_cm); - UPDATE_BODY_VALUE( - 13, - observation->has_left_calf, - observation->left_calf_cm - ); + UPDATE_BODY_VALUE(13, observation->has_left_calf, observation->left_calf_cm); - UPDATE_BODY_VALUE( - 14, - observation->has_right_calf, - observation->right_calf_cm - ); + UPDATE_BODY_VALUE(14, observation->has_right_calf, observation->right_calf_cm); #undef UPDATE_BODY_VALUE if (rc == SQLITE_OK) { - rc = - observation->notes != NULL && - observation->notes[0] != '\0' - ? sqlite3_bind_text( - statement, - 15, - observation->notes, - -1, - SQLITE_TRANSIENT - ) - : sqlite3_bind_null( - statement, - 15 - ); + rc = observation->notes != NULL && observation->notes[0] != '\0' + ? sqlite3_bind_text(statement, 15, observation->notes, -1, SQLITE_TRANSIENT) + : sqlite3_bind_null(statement, 15); } if (rc == SQLITE_OK) { - rc = sqlite3_bind_text( - statement, - 16, - observation->observation_id, - -1, - SQLITE_TRANSIENT - ); + rc = sqlite3_bind_text(statement, 16, observation->observation_id, -1, SQLITE_TRANSIENT); } if (rc != SQLITE_OK) { @@ -7353,51 +6325,56 @@ TrainlogStatus trainlog_database_update_body_observation( if (rc != SQLITE_DONE) { (void)sqlite3_finalize(statement); - return rc == SQLITE_CONSTRAINT - ? TRAINLOG_STATUS_CONFLICT - : TRAINLOG_STATUS_DATABASE_ERROR; + return rc == SQLITE_CONSTRAINT ? TRAINLOG_STATUS_CONFLICT : TRAINLOG_STATUS_DATABASE_ERROR; } - if (sqlite3_changes( - database->connection - ) != 1) { + if (sqlite3_changes(database->connection) != 1) { (void)sqlite3_finalize(statement); return TRAINLOG_STATUS_NOT_FOUND; } - return sqlite3_finalize(statement) == SQLITE_OK - ? TRAINLOG_STATUS_OK - : TRAINLOG_STATUS_DATABASE_ERROR; + return sqlite3_finalize(statement) == SQLITE_OK ? TRAINLOG_STATUS_OK + : TRAINLOG_STATUS_DATABASE_ERROR; } /* TRAINING_KNOWLEDGE_RUNTIME_READ_V1 */ -static bool knowledge_copy_column(sqlite3_stmt *statement, int column, char *output, size_t capacity) -{ +static bool +knowledge_copy_column(sqlite3_stmt *statement, int column, char *output, size_t capacity) { const unsigned char *value; size_t length; if (statement == NULL || sqlite3_column_type(statement, column) != SQLITE_TEXT || - output == NULL || capacity == 0U) return false; + output == NULL || capacity == 0U) { + return false; + } value = sqlite3_column_text(statement, column); - if (value == NULL) return false; + if (value == NULL) { + return false; + } length = (size_t)sqlite3_column_bytes(statement, column); - if (length >= capacity) return false; + if (length >= capacity) { + return false; + } (void)memcpy(output, value, length + 1U); return true; } -static bool knowledge_numeric_column(sqlite3_stmt *statement, int column) -{ +static bool knowledge_numeric_column(sqlite3_stmt *statement, int column) { int type = sqlite3_column_type(statement, column); return type == SQLITE_INTEGER || type == SQLITE_FLOAT; } -static bool knowledge_bounded_string(const char *value, size_t capacity, size_t *length) -{ +static bool knowledge_bounded_string(const char *value, size_t capacity, size_t *length) { const char *end; - if (value == NULL || capacity == 0U) return false; + if (value == NULL || capacity == 0U) { + return false; + } end = memchr(value, '\0', capacity); - if (end == NULL || end == value) return false; - if (length != NULL) *length = (size_t)(end - value); + if (end == NULL || end == value) { + return false; + } + if (length != NULL) { + *length = (size_t)(end - value); + } return true; } @@ -7412,29 +6389,38 @@ typedef struct KnowledgeTemporalCandidate { TrainlogTimestampKey timestamp; } KnowledgeTemporalCandidate; -static void knowledge_temporal_candidate_release(KnowledgeTemporalCandidate *candidate) -{ - if (candidate == NULL) return; +static void knowledge_temporal_candidate_release(KnowledgeTemporalCandidate *candidate) { + if (candidate == NULL) { + return; + } free(candidate->started_at); free(candidate->session_id); free(candidate->entry_id); (void)memset(candidate, 0, sizeof(*candidate)); } -static TrainlogStatus knowledge_copy_dynamic_column( - sqlite3_stmt *statement, int column, char **output, size_t *output_length) -{ +static TrainlogStatus knowledge_copy_dynamic_column(sqlite3_stmt *statement, + int column, + char **output, + size_t *output_length) { const unsigned char *source; size_t length; char *copy; - if (sqlite3_column_type(statement, column) != SQLITE_TEXT) return TRAINLOG_STATUS_DATABASE_ERROR; - source = sqlite3_column_text(statement, column); - if (source == NULL) return TRAINLOG_STATUS_DATABASE_ERROR; - length = (size_t)sqlite3_column_bytes(statement, column); - if (length == 0U || memchr(source, '\0', length) != NULL || length == SIZE_MAX) + if (sqlite3_column_type(statement, column) != SQLITE_TEXT) { return TRAINLOG_STATUS_DATABASE_ERROR; + } + source = sqlite3_column_text(statement, column); + if (source == NULL) { + return TRAINLOG_STATUS_DATABASE_ERROR; + } + length = (size_t)sqlite3_column_bytes(statement, column); + if (length == 0U || memchr(source, '\0', length) != NULL || length == SIZE_MAX) { + return TRAINLOG_STATUS_DATABASE_ERROR; + } copy = malloc(length + 1U); - if (copy == NULL) return TRAINLOG_STATUS_SYSTEM_ERROR; + if (copy == NULL) { + return TRAINLOG_STATUS_SYSTEM_ERROR; + } (void)memcpy(copy, source, length); copy[length] = '\0'; *output = copy; @@ -7442,43 +6428,58 @@ static TrainlogStatus knowledge_copy_dynamic_column( return TRAINLOG_STATUS_OK; } -static int knowledge_bytes_compare( - const char *left, size_t left_length, const char *right, size_t right_length) -{ +static int knowledge_bytes_compare(const char *left, + size_t left_length, + const char *right, + size_t right_length) { size_t common = left_length < right_length ? left_length : right_length; int result = memcmp(left, right, common); - if (result != 0) return result; - if (left_length == right_length) return 0; + if (result != 0) { + return result; + } + if (left_length == right_length) { + return 0; + } return left_length < right_length ? -1 : 1; } /* INVARIANT: This is the single ordering relation used for selection and the * exclusive cursor. Source timestamp spelling never participates in a tie. */ -static int knowledge_temporal_candidate_compare( - const KnowledgeTemporalCandidate *left, const KnowledgeTemporalCandidate *right) -{ +static int knowledge_temporal_candidate_compare(const KnowledgeTemporalCandidate *left, + const KnowledgeTemporalCandidate *right) { int result = trainlog_timestamp_compare(&left->timestamp, &right->timestamp); - if (result != 0) return result; - result = knowledge_bytes_compare(left->session_id, left->session_id_length, - right->session_id, right->session_id_length); - if (result != 0) return result; - return knowledge_bytes_compare(left->entry_id, left->entry_id_length, - right->entry_id, right->entry_id_length); + if (result != 0) { + return result; + } + result = knowledge_bytes_compare( + left->session_id, left->session_id_length, right->session_id, right->session_id_length); + if (result != 0) { + return result; + } + return knowledge_bytes_compare( + left->entry_id, left->entry_id_length, right->entry_id, right->entry_id_length); } -static TrainlogStatus knowledge_temporal_candidate_read( - sqlite3_stmt *statement, KnowledgeTemporalCandidate *output) -{ +static TrainlogStatus knowledge_temporal_candidate_read(sqlite3_stmt *statement, + KnowledgeTemporalCandidate *output) { TrainlogStatus status; (void)memset(output, 0, sizeof(*output)); - if (sqlite3_column_type(statement, 0) != SQLITE_INTEGER) return TRAINLOG_STATUS_DATABASE_ERROR; - status = knowledge_copy_dynamic_column(statement, 1, &output->session_id, &output->session_id_length); - if (status == TRAINLOG_STATUS_OK) - status = knowledge_copy_dynamic_column(statement, 2, &output->entry_id, &output->entry_id_length); - if (status == TRAINLOG_STATUS_OK) - status = knowledge_copy_dynamic_column(statement, 3, &output->started_at, &output->started_at_length); - if (status != TRAINLOG_STATUS_OK || - !trainlog_timestamp_parse(output->started_at, output->started_at_length, &output->timestamp)) { + if (sqlite3_column_type(statement, 0) != SQLITE_INTEGER) { + return TRAINLOG_STATUS_DATABASE_ERROR; + } + status = knowledge_copy_dynamic_column( + statement, 1, &output->session_id, &output->session_id_length); + if (status == TRAINLOG_STATUS_OK) { + status = knowledge_copy_dynamic_column( + statement, 2, &output->entry_id, &output->entry_id_length); + } + if (status == TRAINLOG_STATUS_OK) { + status = knowledge_copy_dynamic_column( + statement, 3, &output->started_at, &output->started_at_length); + } + if (status != TRAINLOG_STATUS_OK || !trainlog_timestamp_parse(output->started_at, + output->started_at_length, + &output->timestamp)) { knowledge_temporal_candidate_release(output); return status == TRAINLOG_STATUS_OK ? TRAINLOG_STATUS_DATABASE_ERROR : status; } @@ -7488,88 +6489,144 @@ static TrainlogStatus knowledge_temporal_candidate_read( /* Retain a descending prefix. Capacity is at most page limit + one, so a * sorted bounded array keeps memory independent of history cardinality. */ -static void knowledge_temporal_candidate_insert( - KnowledgeTemporalCandidate *items, size_t *count, size_t capacity, - KnowledgeTemporalCandidate *candidate) -{ +static void knowledge_temporal_candidate_insert(KnowledgeTemporalCandidate *items, + size_t *count, + size_t capacity, + KnowledgeTemporalCandidate *candidate) { size_t position = 0U; size_t index; while (position < *count && - knowledge_temporal_candidate_compare(candidate, &items[position]) <= 0) ++position; + knowledge_temporal_candidate_compare(candidate, &items[position]) <= 0) { + ++position; + } if (position >= capacity) { knowledge_temporal_candidate_release(candidate); return; } - if (*count == capacity) knowledge_temporal_candidate_release(&items[capacity - 1U]); - else ++*count; - for (index = *count - 1U; index > position; --index) items[index] = items[index - 1U]; + if (*count == capacity) { + knowledge_temporal_candidate_release(&items[capacity - 1U]); + } else { + ++*count; + } + for (index = *count - 1U; index > position; --index) { + items[index] = items[index - 1U]; + } items[position] = *candidate; (void)memset(candidate, 0, sizeof(*candidate)); } -static bool knowledge_tracking_mode(const char *value, TrainlogTrackingMode *output) -{ - if (value == NULL || output == NULL) return false; - if (strcmp(value,"reps")==0) {*output=TRAINLOG_TRACKING_REPS;return true;} - if (strcmp(value,"duration")==0) {*output=TRAINLOG_TRACKING_DURATION;return true;} +static bool knowledge_tracking_mode(const char *value, TrainlogTrackingMode *output) { + if (value == NULL || output == NULL) { + return false; + } + if (strcmp(value, "reps") == 0) { + *output = TRAINLOG_TRACKING_REPS; + return true; + } + if (strcmp(value, "duration") == 0) { + *output = TRAINLOG_TRACKING_DURATION; + return true; + } return false; } -static bool knowledge_recording_mode(const char *value, TrainlogRecordingMode *output) -{ - if (value == NULL || output == NULL) return false; - if (strcmp(value,"sets")==0) {*output=TRAINLOG_RECORDING_SETS;return true;} - if (strcmp(value,"continuous")==0) {*output=TRAINLOG_RECORDING_CONTINUOUS;return true;} +static bool knowledge_recording_mode(const char *value, TrainlogRecordingMode *output) { + if (value == NULL || output == NULL) { + return false; + } + if (strcmp(value, "sets") == 0) { + *output = TRAINLOG_RECORDING_SETS; + return true; + } + if (strcmp(value, "continuous") == 0) { + *output = TRAINLOG_RECORDING_CONTINUOUS; + return true; + } return false; } -static bool knowledge_load_mode(const char *value, TrainlogLoadMode *output) -{ - if (value == NULL || output == NULL) return false; - if (strcmp(value,"none")==0) {*output=TRAINLOG_LOAD_NONE;return true;} - if (strcmp(value,"external")==0) {*output=TRAINLOG_LOAD_EXTERNAL;return true;} - if (strcmp(value,"assistance")==0) {*output=TRAINLOG_LOAD_ASSISTANCE;return true;} +static bool knowledge_load_mode(const char *value, TrainlogLoadMode *output) { + if (value == NULL || output == NULL) { + return false; + } + if (strcmp(value, "none") == 0) { + *output = TRAINLOG_LOAD_NONE; + return true; + } + if (strcmp(value, "external") == 0) { + *output = TRAINLOG_LOAD_EXTERNAL; + return true; + } + if (strcmp(value, "assistance") == 0) { + *output = TRAINLOG_LOAD_ASSISTANCE; + return true; + } return false; } -TrainlogStatus trainlog_database_get_exercise_profile( - TrainlogDatabase *database, const char *exercise_id, TrainlogExercise *output) -{ +TrainlogStatus trainlog_database_get_exercise_profile(TrainlogDatabase *database, + const char *exercise_id, + TrainlogExercise *output) { sqlite3_stmt *statement = NULL; int rc; - if (database == NULL || database->connection == NULL || exercise_id == NULL || exercise_id[0] == '\0' || - output == NULL) return TRAINLOG_STATUS_INVALID_ARGUMENT; + if (database == NULL || database->connection == NULL || exercise_id == NULL || + exercise_id[0] == '\0' || output == NULL) { + return TRAINLOG_STATUS_INVALID_ARGUMENT; + } (void)memset(output, 0, sizeof(*output)); rc = sqlite3_prepare_v2(database->connection, - "SELECT exercise_id,name,tracking_mode,recording_mode,data_fields FROM exercises WHERE exercise_id=?1;", - -1, &statement, NULL); - if (rc == SQLITE_OK) rc = sqlite3_bind_text(statement, 1, exercise_id, -1, SQLITE_TRANSIENT); - if (rc != SQLITE_OK) { (void)sqlite3_finalize(statement); return TRAINLOG_STATUS_DATABASE_ERROR; } - rc = sqlite3_step(statement); - if (rc == SQLITE_DONE) { (void)sqlite3_finalize(statement); return TRAINLOG_STATUS_NOT_FOUND; } - if (rc != SQLITE_ROW || !knowledge_copy_column(statement, 0, output->exercise_id, sizeof(output->exercise_id)) || - !knowledge_copy_column(statement, 1, output->name, sizeof(output->name)) || - sqlite3_column_type(statement, 4) != SQLITE_INTEGER) { - (void)sqlite3_finalize(statement); return TRAINLOG_STATUS_DATABASE_ERROR; + "SELECT exercise_id,name,tracking_mode,recording_mode,data_fields FROM " + "exercises WHERE exercise_id=?1;", + -1, + &statement, + NULL); + if (rc == SQLITE_OK) { + rc = sqlite3_bind_text(statement, 1, exercise_id, -1, SQLITE_TRANSIENT); } - if (sqlite3_column_type(statement,2)!=SQLITE_TEXT || sqlite3_column_type(statement,3)!=SQLITE_TEXT || - !knowledge_tracking_mode((const char *)sqlite3_column_text(statement,2),&output->tracking_mode) || - !knowledge_recording_mode((const char *)sqlite3_column_text(statement,3),&output->recording_mode)) { - (void)sqlite3_finalize(statement); return TRAINLOG_STATUS_DATABASE_ERROR; + if (rc != SQLITE_OK) { + (void)sqlite3_finalize(statement); + return TRAINLOG_STATUS_DATABASE_ERROR; + } + rc = sqlite3_step(statement); + if (rc == SQLITE_DONE) { + (void)sqlite3_finalize(statement); + return TRAINLOG_STATUS_NOT_FOUND; + } + if (rc != SQLITE_ROW || + !knowledge_copy_column(statement, 0, output->exercise_id, sizeof(output->exercise_id)) || + !knowledge_copy_column(statement, 1, output->name, sizeof(output->name)) || + sqlite3_column_type(statement, 4) != SQLITE_INTEGER) { + (void)sqlite3_finalize(statement); + return TRAINLOG_STATUS_DATABASE_ERROR; + } + if (sqlite3_column_type(statement, 2) != SQLITE_TEXT || + sqlite3_column_type(statement, 3) != SQLITE_TEXT || + !knowledge_tracking_mode((const char *)sqlite3_column_text(statement, 2), + &output->tracking_mode) || + !knowledge_recording_mode((const char *)sqlite3_column_text(statement, 3), + &output->recording_mode)) { + (void)sqlite3_finalize(statement); + return TRAINLOG_STATUS_DATABASE_ERROR; } if ((sqlite3_column_int64(statement, 4) < 0) || - ((sqlite3_uint64)sqlite3_column_int64(statement, 4) > UINT32_MAX)) { - (void)sqlite3_finalize(statement); return TRAINLOG_STATUS_DATABASE_ERROR; + ((sqlite3_uint64)sqlite3_column_int64(statement, 4) > UINT32_MAX)) { + (void)sqlite3_finalize(statement); + return TRAINLOG_STATUS_DATABASE_ERROR; } output->data_fields = (TrainlogExerciseDataFields)sqlite3_column_int64(statement, 4); - return sqlite3_finalize(statement) == SQLITE_OK ? TRAINLOG_STATUS_OK : TRAINLOG_STATUS_DATABASE_ERROR; + return sqlite3_finalize(statement) == SQLITE_OK ? TRAINLOG_STATUS_OK + : TRAINLOG_STATUS_DATABASE_ERROR; } -TrainlogStatus trainlog_database_list_exercise_occurrences_page( - TrainlogDatabase *database, const char *exercise_id, const TrainlogExerciseOccurrenceCursor *after, - size_t limit, TrainlogExerciseOccurrence *output, size_t *output_count, bool *output_has_more, - TrainlogExerciseOccurrenceCursor *output_next) -{ +TrainlogStatus +trainlog_database_list_exercise_occurrences_page(TrainlogDatabase *database, + const char *exercise_id, + const TrainlogExerciseOccurrenceCursor *after, + size_t limit, + TrainlogExerciseOccurrence *output, + size_t *output_count, + bool *output_has_more, + TrainlogExerciseOccurrenceCursor *output_next) { static const char *const SCAN_SQL = "SELECT se.id,s.session_id,se.entry_id,s.started_at FROM session_exercises se " "JOIN sessions s ON s.id=se.session_row_id JOIN exercises e ON e.id=se.exercise_row_id " @@ -7591,45 +6648,78 @@ TrainlogStatus trainlog_database_list_exercise_occurrences_page( size_t cursor_started_length = 0U, cursor_session_length = 0U, cursor_entry_length = 0U; int rc; bool snapshot = false; - if (output_count != NULL) *output_count = 0U; - if (output_has_more != NULL) *output_has_more = false; - if (database == NULL || database->connection == NULL || exercise_id == NULL || exercise_id[0] == '\0' || - limit == 0U || limit > TRAINLOG_OCCURRENCE_PAGE_MAX || output == NULL || output_count == NULL || - output_has_more == NULL || output_next == NULL || - (after != NULL && (!knowledge_bounded_string(after->started_at, sizeof(after->started_at), - &cursor_started_length) || - !knowledge_bounded_string(after->session_id, sizeof(after->session_id), &cursor_session_length) || - !knowledge_bounded_string(after->entry_id, sizeof(after->entry_id), &cursor_entry_length) || - !trainlog_timestamp_parse(after->started_at, cursor_started_length, &cursor.timestamp)))) + if (output_count != NULL) { + *output_count = 0U; + } + if (output_has_more != NULL) { + *output_has_more = false; + } + if (database == NULL || database->connection == NULL || exercise_id == NULL || + exercise_id[0] == '\0' || limit == 0U || limit > TRAINLOG_OCCURRENCE_PAGE_MAX || + output == NULL || output_count == NULL || output_has_more == NULL || output_next == NULL || + (after != NULL && + (!knowledge_bounded_string( + after->started_at, sizeof(after->started_at), &cursor_started_length) || + !knowledge_bounded_string( + after->session_id, sizeof(after->session_id), &cursor_session_length) || + !knowledge_bounded_string( + after->entry_id, sizeof(after->entry_id), &cursor_entry_length) || + !trainlog_timestamp_parse( + after->started_at, cursor_started_length, &cursor.timestamp)))) { return TRAINLOG_STATUS_INVALID_ARGUMENT; + } if (after != NULL) { after_value = *after; - cursor.started_at = after_value.started_at; cursor.started_at_length = cursor_started_length; - cursor.session_id = after_value.session_id; cursor.session_id_length = cursor_session_length; - cursor.entry_id = after_value.entry_id; cursor.entry_id_length = cursor_entry_length; - if (!trainlog_timestamp_parse(cursor.started_at, cursor_started_length, &cursor.timestamp)) + cursor.started_at = after_value.started_at; + cursor.started_at_length = cursor_started_length; + cursor.session_id = after_value.session_id; + cursor.session_id_length = cursor_session_length; + cursor.entry_id = after_value.entry_id; + cursor.entry_id_length = cursor_entry_length; + if (!trainlog_timestamp_parse( + cursor.started_at, cursor_started_length, &cursor.timestamp)) { return TRAINLOG_STATUS_INVALID_ARGUMENT; + } } status = trainlog_database_read_snapshot_begin(database); - if (status != TRAINLOG_STATUS_OK) return status; + if (status != TRAINLOG_STATUS_OK) { + return status; + } snapshot = true; status = trainlog_database_get_exercise_profile(database, exercise_id, &profile); - if (status != TRAINLOG_STATUS_OK) goto done; + if (status != TRAINLOG_STATUS_OK) { + goto done; + } (void)memset(output_next, 0, sizeof(*output_next)); rc = sqlite3_prepare_v2(database->connection, SCAN_SQL, -1, &statement, NULL); - if (rc == SQLITE_OK) rc = sqlite3_bind_text(statement, 1, exercise_id, -1, SQLITE_TRANSIENT); - if (rc != SQLITE_OK) { status = TRAINLOG_STATUS_DATABASE_ERROR; goto done; } + if (rc == SQLITE_OK) { + rc = sqlite3_bind_text(statement, 1, exercise_id, -1, SQLITE_TRANSIENT); + } + if (rc != SQLITE_OK) { + status = TRAINLOG_STATUS_DATABASE_ERROR; + goto done; + } while ((rc = sqlite3_step(statement)) == SQLITE_ROW) { KnowledgeTemporalCandidate candidate; status = knowledge_temporal_candidate_read(statement, &candidate); - if (status != TRAINLOG_STATUS_OK) goto done; - if (after == NULL || knowledge_temporal_candidate_compare(&candidate, &cursor) < 0) + if (status != TRAINLOG_STATUS_OK) { + goto done; + } + if (after == NULL || knowledge_temporal_candidate_compare(&candidate, &cursor) < 0) { knowledge_temporal_candidate_insert(selected, &selected_count, limit + 1U, &candidate); + } knowledge_temporal_candidate_release(&candidate); } - if (rc != SQLITE_DONE) { status = TRAINLOG_STATUS_DATABASE_ERROR; goto done; } - rc = sqlite3_finalize(statement); statement = NULL; - if (rc != SQLITE_OK) { status = TRAINLOG_STATUS_DATABASE_ERROR; goto done; } + if (rc != SQLITE_DONE) { + status = TRAINLOG_STATUS_DATABASE_ERROR; + goto done; + } + rc = sqlite3_finalize(statement); + statement = NULL; + if (rc != SQLITE_OK) { + status = TRAINLOG_STATUS_DATABASE_ERROR; + goto done; + } statement = NULL; *output_has_more = selected_count > limit; count = *output_has_more ? limit : selected_count; @@ -7638,155 +6728,270 @@ TrainlogStatus trainlog_database_list_exercise_occurrences_page( sqlite3_int64 fields; sqlite3_int64 sets; rc = sqlite3_prepare_v2(database->connection, HYDRATE_SQL, -1, &statement, NULL); - if (rc == SQLITE_OK) rc = sqlite3_bind_int64(statement, 1, selected[index].row_id); - if (rc == SQLITE_OK) rc = sqlite3_step(statement); - if (rc != SQLITE_ROW) { status = TRAINLOG_STATUS_DATABASE_ERROR; goto done; } - item = &output[index]; (void)memset(item, 0, sizeof(*item)); - if (!knowledge_copy_column(statement,0,item->session_id,sizeof(item->session_id)) || - !knowledge_copy_column(statement,1,item->entry_id,sizeof(item->entry_id)) || - !knowledge_copy_column(statement,2,item->exercise_id,sizeof(item->exercise_id)) || - !knowledge_copy_column(statement,3,item->started_at,sizeof(item->started_at)) || - !knowledge_copy_column(statement,4,item->equipment_id,sizeof(item->equipment_id)) || - sqlite3_column_type(statement,5)!=SQLITE_TEXT || - !session_type_from_sql((const char *)sqlite3_column_text(statement,5),&item->session_type)) { - status = TRAINLOG_STATUS_DATABASE_ERROR; goto done; + if (rc == SQLITE_OK) { + rc = sqlite3_bind_int64(statement, 1, selected[index].row_id); } - if(sqlite3_column_type(statement,6)!=SQLITE_TEXT || sqlite3_column_type(statement,7)!=SQLITE_TEXT || - sqlite3_column_type(statement,9)!=SQLITE_TEXT || - !knowledge_tracking_mode((const char *)sqlite3_column_text(statement,6),&item->tracking_mode) || - !knowledge_recording_mode((const char *)sqlite3_column_text(statement,7),&item->recording_mode) || - !knowledge_load_mode((const char *)sqlite3_column_text(statement,9),&item->load_mode)) { - status = TRAINLOG_STATUS_DATABASE_ERROR; goto done; + if (rc == SQLITE_OK) { + rc = sqlite3_step(statement); } - if (sqlite3_column_type(statement,8)!=SQLITE_INTEGER || sqlite3_column_type(statement,10)!=SQLITE_INTEGER) { - status = TRAINLOG_STATUS_DATABASE_ERROR; goto done; + if (rc != SQLITE_ROW) { + status = TRAINLOG_STATUS_DATABASE_ERROR; + goto done; } - fields=sqlite3_column_int64(statement,8); sets=sqlite3_column_int64(statement,10); - if (fields<0 || (sqlite3_uint64)fields>UINT32_MAX || sets<0 || (sqlite3_uint64)sets>SIZE_MAX) { - status = TRAINLOG_STATUS_DATABASE_ERROR; goto done; + item = &output[index]; + (void)memset(item, 0, sizeof(*item)); + if (!knowledge_copy_column(statement, 0, item->session_id, sizeof(item->session_id)) || + !knowledge_copy_column(statement, 1, item->entry_id, sizeof(item->entry_id)) || + !knowledge_copy_column(statement, 2, item->exercise_id, sizeof(item->exercise_id)) || + !knowledge_copy_column(statement, 3, item->started_at, sizeof(item->started_at)) || + !knowledge_copy_column(statement, 4, item->equipment_id, sizeof(item->equipment_id)) || + sqlite3_column_type(statement, 5) != SQLITE_TEXT || + !session_type_from_sql((const char *)sqlite3_column_text(statement, 5), + &item->session_type)) { + status = TRAINLOG_STATUS_DATABASE_ERROR; + goto done; } - item->data_fields=(TrainlogExerciseDataFields)fields; - item->set_count=(size_t)sets; - if (sqlite3_column_type(statement,11)!=SQLITE_NULL) { + if (sqlite3_column_type(statement, 6) != SQLITE_TEXT || + sqlite3_column_type(statement, 7) != SQLITE_TEXT || + sqlite3_column_type(statement, 9) != SQLITE_TEXT || + !knowledge_tracking_mode((const char *)sqlite3_column_text(statement, 6), + &item->tracking_mode) || + !knowledge_recording_mode((const char *)sqlite3_column_text(statement, 7), + &item->recording_mode) || + !knowledge_load_mode((const char *)sqlite3_column_text(statement, 9), + &item->load_mode)) { + status = TRAINLOG_STATUS_DATABASE_ERROR; + goto done; + } + if (sqlite3_column_type(statement, 8) != SQLITE_INTEGER || + sqlite3_column_type(statement, 10) != SQLITE_INTEGER) { + status = TRAINLOG_STATUS_DATABASE_ERROR; + goto done; + } + fields = sqlite3_column_int64(statement, 8); + sets = sqlite3_column_int64(statement, 10); + if (fields < 0 || (sqlite3_uint64)fields > UINT32_MAX || sets < 0 || + (sqlite3_uint64)sets > SIZE_MAX) { + status = TRAINLOG_STATUS_DATABASE_ERROR; + goto done; + } + item->data_fields = (TrainlogExerciseDataFields)fields; + item->set_count = (size_t)sets; + if (sqlite3_column_type(statement, 11) != SQLITE_NULL) { sqlite3_int64 duration; - if (sqlite3_column_type(statement,11)!=SQLITE_INTEGER) { - status = TRAINLOG_STATUS_DATABASE_ERROR; goto done; + if (sqlite3_column_type(statement, 11) != SQLITE_INTEGER) { + status = TRAINLOG_STATUS_DATABASE_ERROR; + goto done; } - duration=sqlite3_column_int64(statement,11); - if (duration<=0 || duration>INT_MAX) { - status = TRAINLOG_STATUS_DATABASE_ERROR; goto done; + duration = sqlite3_column_int64(statement, 11); + if (duration <= 0 || duration > INT_MAX) { + status = TRAINLOG_STATUS_DATABASE_ERROR; + goto done; } - item->continuous_duration_seconds=(int)duration; + item->continuous_duration_seconds = (int)duration; } - item->continuous_has_speed=sqlite3_column_type(statement,12)!=SQLITE_NULL; - if (item->continuous_has_speed && !knowledge_numeric_column(statement,12)) { - status = TRAINLOG_STATUS_DATABASE_ERROR; goto done; + item->continuous_has_speed = sqlite3_column_type(statement, 12) != SQLITE_NULL; + if (item->continuous_has_speed && !knowledge_numeric_column(statement, 12)) { + status = TRAINLOG_STATUS_DATABASE_ERROR; + goto done; } - item->continuous_speed_kmh=sqlite3_column_double(statement,12); - item->continuous_has_distance=sqlite3_column_type(statement,13)!=SQLITE_NULL; - if (item->continuous_has_distance && !knowledge_numeric_column(statement,13)) { - status = TRAINLOG_STATUS_DATABASE_ERROR; goto done; + item->continuous_speed_kmh = sqlite3_column_double(statement, 12); + item->continuous_has_distance = sqlite3_column_type(statement, 13) != SQLITE_NULL; + if (item->continuous_has_distance && !knowledge_numeric_column(statement, 13)) { + status = TRAINLOG_STATUS_DATABASE_ERROR; + goto done; } - item->continuous_distance_km=sqlite3_column_double(statement,13); + item->continuous_distance_km = sqlite3_column_double(statement, 13); if ((item->continuous_has_speed && !isfinite(item->continuous_speed_kmh)) || - (item->continuous_has_distance && !isfinite(item->continuous_distance_km))) { - status = TRAINLOG_STATUS_DATABASE_ERROR; goto done; + (item->continuous_has_distance && !isfinite(item->continuous_distance_km))) { + status = TRAINLOG_STATUS_DATABASE_ERROR; + goto done; } rc = sqlite3_step(statement); - if (rc != SQLITE_DONE) { status = TRAINLOG_STATUS_DATABASE_ERROR; goto done; } - rc = sqlite3_finalize(statement); statement = NULL; - if (rc != SQLITE_OK) { status = TRAINLOG_STATUS_DATABASE_ERROR; goto done; } + if (rc != SQLITE_DONE) { + status = TRAINLOG_STATUS_DATABASE_ERROR; + goto done; + } + rc = sqlite3_finalize(statement); + statement = NULL; + if (rc != SQLITE_OK) { + status = TRAINLOG_STATUS_DATABASE_ERROR; + goto done; + } } - *output_count=count; + *output_count = count; if (count > 0U) { - (void)snprintf(output_next->started_at,sizeof(output_next->started_at),"%s",output[count-1U].started_at); - (void)snprintf(output_next->session_id,sizeof(output_next->session_id),"%s",output[count-1U].session_id); - (void)snprintf(output_next->entry_id,sizeof(output_next->entry_id),"%s",output[count-1U].entry_id); + (void)snprintf(output_next->started_at, + sizeof(output_next->started_at), + "%s", + output[count - 1U].started_at); + (void)snprintf(output_next->session_id, + sizeof(output_next->session_id), + "%s", + output[count - 1U].session_id); + (void)snprintf(output_next->entry_id, + sizeof(output_next->entry_id), + "%s", + output[count - 1U].entry_id); } status = TRAINLOG_STATUS_OK; done: (void)sqlite3_finalize(statement); - for (index = 0U; index < selected_count; ++index) + for (index = 0U; index < selected_count; ++index) { knowledge_temporal_candidate_release(&selected[index]); + } if (snapshot) { - TrainlogStatus end_status = trainlog_database_read_snapshot_end(database, status == TRAINLOG_STATUS_OK); - if (status == TRAINLOG_STATUS_OK) status = end_status; + TrainlogStatus end_status = + trainlog_database_read_snapshot_end(database, status == TRAINLOG_STATUS_OK); + if (status == TRAINLOG_STATUS_OK) { + status = end_status; + } } return status; } -TrainlogStatus trainlog_database_list_occurrence_sets_page( - TrainlogDatabase *database, const char *entry_id, int after_position, size_t limit, - TrainlogOccurrenceSet *output, size_t *output_count, bool *output_has_more, int *output_next_position) -{ +TrainlogStatus trainlog_database_list_occurrence_sets_page(TrainlogDatabase *database, + const char *entry_id, + int after_position, + size_t limit, + TrainlogOccurrenceSet *output, + size_t *output_count, + bool *output_has_more, + int *output_next_position) { sqlite3_stmt *statement = NULL; sqlite3_stmt *identity = NULL; - size_t count=0U; + size_t count = 0U; int rc; - if (output_count != NULL) *output_count=0U; - if (output_has_more != NULL) *output_has_more=false; - if (database==NULL || database->connection==NULL || entry_id==NULL || entry_id[0]=='\0' || - after_position < -1 || limit==0U || limit>TRAINLOG_OCCURRENCE_SET_PAGE_MAX || output==NULL || - output_count==NULL || output_has_more==NULL || output_next_position==NULL) + if (output_count != NULL) { + *output_count = 0U; + } + if (output_has_more != NULL) { + *output_has_more = false; + } + if (database == NULL || database->connection == NULL || entry_id == NULL || + entry_id[0] == '\0' || after_position < -1 || limit == 0U || + limit > TRAINLOG_OCCURRENCE_SET_PAGE_MAX || output == NULL || output_count == NULL || + output_has_more == NULL || output_next_position == NULL) { return TRAINLOG_STATUS_INVALID_ARGUMENT; - *output_next_position=after_position; - rc=sqlite3_prepare_v2(database->connection,"SELECT 1 FROM session_exercises WHERE entry_id=?1;",-1,&identity,NULL); - if(rc==SQLITE_OK)rc=sqlite3_bind_text(identity,1,entry_id,-1,SQLITE_TRANSIENT); - if(rc!=SQLITE_OK){(void)sqlite3_finalize(identity);return TRAINLOG_STATUS_DATABASE_ERROR;} - rc=sqlite3_step(identity); - if(rc==SQLITE_DONE){(void)sqlite3_finalize(identity);return TRAINLOG_STATUS_NOT_FOUND;} - if(rc!=SQLITE_ROW || sqlite3_finalize(identity)!=SQLITE_OK)return TRAINLOG_STATUS_DATABASE_ERROR; - rc=sqlite3_prepare_v2(database->connection, + } + *output_next_position = after_position; + rc = sqlite3_prepare_v2(database->connection, + "SELECT 1 FROM session_exercises WHERE entry_id=?1;", + -1, + &identity, + NULL); + if (rc == SQLITE_OK) { + rc = sqlite3_bind_text(identity, 1, entry_id, -1, SQLITE_TRANSIENT); + } + if (rc != SQLITE_OK) { + (void)sqlite3_finalize(identity); + return TRAINLOG_STATUS_DATABASE_ERROR; + } + rc = sqlite3_step(identity); + if (rc == SQLITE_DONE) { + (void)sqlite3_finalize(identity); + return TRAINLOG_STATUS_NOT_FOUND; + } + if (rc != SQLITE_ROW || sqlite3_finalize(identity) != SQLITE_OK) { + return TRAINLOG_STATUS_DATABASE_ERROR; + } + rc = sqlite3_prepare_v2( + database->connection, "SELECT ps.position,ps.reps,ps.duration_seconds,ps.weight_kg FROM performed_sets ps " - "JOIN session_exercises se ON se.id=ps.session_exercise_row_id WHERE se.entry_id=?1 AND ps.position>?2 " - "ORDER BY ps.position ASC LIMIT ?3;",-1,&statement,NULL); - if(rc==SQLITE_OK) rc=sqlite3_bind_text(statement,1,entry_id,-1,SQLITE_TRANSIENT); - if(rc==SQLITE_OK) rc=sqlite3_bind_int(statement,2,after_position); - if(rc==SQLITE_OK) rc=sqlite3_bind_int64(statement,3,(sqlite3_int64)(limit+1U)); - if(rc!=SQLITE_OK){(void)sqlite3_finalize(statement);return TRAINLOG_STATUS_DATABASE_ERROR;} - while((rc=sqlite3_step(statement))==SQLITE_ROW){ TrainlogOccurrenceSet *item; sqlite3_int64 position; - if(count==limit){*output_has_more=true;break;} item=&output[count];(void)memset(item,0,sizeof(*item)); - if(sqlite3_column_type(statement,0)!=SQLITE_INTEGER){(void)sqlite3_finalize(statement);return TRAINLOG_STATUS_DATABASE_ERROR;} - position=sqlite3_column_int64(statement,0); if(position<0 || position>INT_MAX){(void)sqlite3_finalize(statement);return TRAINLOG_STATUS_DATABASE_ERROR;} - item->position=(size_t)position; item->has_reps=sqlite3_column_type(statement,1)!=SQLITE_NULL; - item->has_duration=sqlite3_column_type(statement,2)!=SQLITE_NULL; + "JOIN session_exercises se ON se.id=ps.session_exercise_row_id WHERE se.entry_id=?1 AND " + "ps.position>?2 " + "ORDER BY ps.position ASC LIMIT ?3;", + -1, + &statement, + NULL); + if (rc == SQLITE_OK) { + rc = sqlite3_bind_text(statement, 1, entry_id, -1, SQLITE_TRANSIENT); + } + if (rc == SQLITE_OK) { + rc = sqlite3_bind_int(statement, 2, after_position); + } + if (rc == SQLITE_OK) { + rc = sqlite3_bind_int64(statement, 3, (sqlite3_int64)(limit + 1U)); + } + if (rc != SQLITE_OK) { + (void)sqlite3_finalize(statement); + return TRAINLOG_STATUS_DATABASE_ERROR; + } + while ((rc = sqlite3_step(statement)) == SQLITE_ROW) { + TrainlogOccurrenceSet *item; + sqlite3_int64 position; + if (count == limit) { + *output_has_more = true; + break; + } + item = &output[count]; + (void)memset(item, 0, sizeof(*item)); + if (sqlite3_column_type(statement, 0) != SQLITE_INTEGER) { + (void)sqlite3_finalize(statement); + return TRAINLOG_STATUS_DATABASE_ERROR; + } + position = sqlite3_column_int64(statement, 0); + if (position < 0 || position > INT_MAX) { + (void)sqlite3_finalize(statement); + return TRAINLOG_STATUS_DATABASE_ERROR; + } + item->position = (size_t)position; + item->has_reps = sqlite3_column_type(statement, 1) != SQLITE_NULL; + item->has_duration = sqlite3_column_type(statement, 2) != SQLITE_NULL; if (item->has_reps) { sqlite3_int64 reps; - if (sqlite3_column_type(statement,1)!=SQLITE_INTEGER) { - (void)sqlite3_finalize(statement); return TRAINLOG_STATUS_DATABASE_ERROR; + if (sqlite3_column_type(statement, 1) != SQLITE_INTEGER) { + (void)sqlite3_finalize(statement); + return TRAINLOG_STATUS_DATABASE_ERROR; } - reps=sqlite3_column_int64(statement,1); - if (reps<0 || reps>INT_MAX) { - (void)sqlite3_finalize(statement); return TRAINLOG_STATUS_DATABASE_ERROR; + reps = sqlite3_column_int64(statement, 1); + if (reps < 0 || reps > INT_MAX) { + (void)sqlite3_finalize(statement); + return TRAINLOG_STATUS_DATABASE_ERROR; } - item->reps=(int)reps; + item->reps = (int)reps; } if (item->has_duration) { sqlite3_int64 duration; - if (sqlite3_column_type(statement,2)!=SQLITE_INTEGER) { - (void)sqlite3_finalize(statement); return TRAINLOG_STATUS_DATABASE_ERROR; + if (sqlite3_column_type(statement, 2) != SQLITE_INTEGER) { + (void)sqlite3_finalize(statement); + return TRAINLOG_STATUS_DATABASE_ERROR; } - duration=sqlite3_column_int64(statement,2); - if (duration<=0 || duration>INT_MAX) { - (void)sqlite3_finalize(statement); return TRAINLOG_STATUS_DATABASE_ERROR; + duration = sqlite3_column_int64(statement, 2); + if (duration <= 0 || duration > INT_MAX) { + (void)sqlite3_finalize(statement); + return TRAINLOG_STATUS_DATABASE_ERROR; } - item->duration_seconds=(int)duration; + item->duration_seconds = (int)duration; } - item->has_weight=sqlite3_column_type(statement,3)!=SQLITE_NULL; - if(item->has_weight && !knowledge_numeric_column(statement,3)){(void)sqlite3_finalize(statement);return TRAINLOG_STATUS_DATABASE_ERROR;} - item->weight_kg=sqlite3_column_double(statement,3); - if(item->has_weight && !isfinite(item->weight_kg)){(void)sqlite3_finalize(statement);return TRAINLOG_STATUS_DATABASE_ERROR;} - *output_next_position=(int)position; ++count; + item->has_weight = sqlite3_column_type(statement, 3) != SQLITE_NULL; + if (item->has_weight && !knowledge_numeric_column(statement, 3)) { + (void)sqlite3_finalize(statement); + return TRAINLOG_STATUS_DATABASE_ERROR; + } + item->weight_kg = sqlite3_column_double(statement, 3); + if (item->has_weight && !isfinite(item->weight_kg)) { + (void)sqlite3_finalize(statement); + return TRAINLOG_STATUS_DATABASE_ERROR; + } + *output_next_position = (int)position; + ++count; } - if(rc!=SQLITE_DONE && rc!=SQLITE_ROW){(void)sqlite3_finalize(statement);return TRAINLOG_STATUS_DATABASE_ERROR;} - if(sqlite3_finalize(statement)!=SQLITE_OK)return TRAINLOG_STATUS_DATABASE_ERROR; - *output_count=count; return TRAINLOG_STATUS_OK; + if (rc != SQLITE_DONE && rc != SQLITE_ROW) { + (void)sqlite3_finalize(statement); + return TRAINLOG_STATUS_DATABASE_ERROR; + } + if (sqlite3_finalize(statement) != SQLITE_OK) { + return TRAINLOG_STATUS_DATABASE_ERROR; + } + *output_count = count; + return TRAINLOG_STATUS_OK; } -static TrainlogStatus database_latest_explicit_max_context( - TrainlogDatabase *database, const char *exercise_id, const char *equipment_id, - TrainlogLatestExplicitMax *output) -{ +static TrainlogStatus database_latest_explicit_max_context(TrainlogDatabase *database, + const char *exercise_id, + const char *equipment_id, + TrainlogLatestExplicitMax *output) { static const char *const SCAN_SQL = "SELECT se.id,s.session_id,se.entry_id,s.started_at FROM max_results mr " "JOIN session_exercises se ON se.id=mr.session_exercise_row_id " @@ -7794,80 +6999,132 @@ static TrainlogStatus database_latest_explicit_max_context( "WHERE e.exercise_id=?1 AND s.session_type='max_test' " "AND (?2 IS NULL OR se.equipment_id=?2);"; static const char *const HYDRATE_SQL = - "SELECT s.session_id,se.entry_id,s.started_at,COALESCE(se.equipment_id,''),se.load_mode,mr.max_weight_kg " + "SELECT " + "s.session_id,se.entry_id,s.started_at,COALESCE(se.equipment_id,''),se.load_mode,mr.max_" + "weight_kg " "FROM max_results mr JOIN session_exercises se ON se.id=mr.session_exercise_row_id " "JOIN sessions s ON s.id=se.session_row_id WHERE se.id=?1;"; - sqlite3_stmt *statement=NULL; + sqlite3_stmt *statement = NULL; KnowledgeTemporalCandidate selected[1] = {{0}}; size_t selected_count = 0U; int rc; TrainlogExercise profile; TrainlogStatus status; bool snapshot = false; - if(database==NULL || database->connection==NULL || exercise_id==NULL || exercise_id[0]=='\0' || output==NULL) + if (database == NULL || database->connection == NULL || exercise_id == NULL || + exercise_id[0] == '\0' || output == NULL) { return TRAINLOG_STATUS_INVALID_ARGUMENT; - (void)memset(output,0,sizeof(*output)); + } + (void)memset(output, 0, sizeof(*output)); status = trainlog_database_read_snapshot_begin(database); - if (status != TRAINLOG_STATUS_OK) return status; + if (status != TRAINLOG_STATUS_OK) { + return status; + } snapshot = true; - status=trainlog_database_get_exercise_profile(database,exercise_id,&profile); - if(status!=TRAINLOG_STATUS_OK)goto done; - rc=sqlite3_prepare_v2(database->connection,SCAN_SQL,-1,&statement,NULL); - if(rc==SQLITE_OK)rc=sqlite3_bind_text(statement,1,exercise_id,-1,SQLITE_TRANSIENT); - if(rc==SQLITE_OK)rc=equipment_id==NULL ? sqlite3_bind_null(statement,2) - : sqlite3_bind_text(statement,2,equipment_id,-1,SQLITE_TRANSIENT); - if(rc!=SQLITE_OK){status=TRAINLOG_STATUS_DATABASE_ERROR;goto done;} - while((rc=sqlite3_step(statement))==SQLITE_ROW){ + status = trainlog_database_get_exercise_profile(database, exercise_id, &profile); + if (status != TRAINLOG_STATUS_OK) { + goto done; + } + rc = sqlite3_prepare_v2(database->connection, SCAN_SQL, -1, &statement, NULL); + if (rc == SQLITE_OK) { + rc = sqlite3_bind_text(statement, 1, exercise_id, -1, SQLITE_TRANSIENT); + } + if (rc == SQLITE_OK) { + rc = equipment_id == NULL + ? sqlite3_bind_null(statement, 2) + : sqlite3_bind_text(statement, 2, equipment_id, -1, SQLITE_TRANSIENT); + } + if (rc != SQLITE_OK) { + status = TRAINLOG_STATUS_DATABASE_ERROR; + goto done; + } + while ((rc = sqlite3_step(statement)) == SQLITE_ROW) { KnowledgeTemporalCandidate candidate; - status=knowledge_temporal_candidate_read(statement,&candidate); - if(status!=TRAINLOG_STATUS_OK)goto done; - knowledge_temporal_candidate_insert(selected,&selected_count,1U,&candidate); + status = knowledge_temporal_candidate_read(statement, &candidate); + if (status != TRAINLOG_STATUS_OK) { + goto done; + } + knowledge_temporal_candidate_insert(selected, &selected_count, 1U, &candidate); knowledge_temporal_candidate_release(&candidate); } - if(rc!=SQLITE_DONE){status=TRAINLOG_STATUS_DATABASE_ERROR;goto done;} - rc=sqlite3_finalize(statement); statement=NULL; - if(rc!=SQLITE_OK){status=TRAINLOG_STATUS_DATABASE_ERROR;goto done;} - if(selected_count==0U){status=TRAINLOG_STATUS_OK;goto done;} - rc=sqlite3_prepare_v2(database->connection,HYDRATE_SQL,-1,&statement,NULL); - if(rc==SQLITE_OK)rc=sqlite3_bind_int64(statement,1,selected[0].row_id); - if(rc==SQLITE_OK)rc=sqlite3_step(statement); - if(rc!=SQLITE_ROW || !knowledge_copy_column(statement,0,output->session_id,sizeof(output->session_id)) || - !knowledge_copy_column(statement,1,output->entry_id,sizeof(output->entry_id)) || - !knowledge_copy_column(statement,2,output->started_at,sizeof(output->started_at)) || - !knowledge_copy_column(statement,3,output->equipment_id,sizeof(output->equipment_id))){status=TRAINLOG_STATUS_DATABASE_ERROR;goto done;} - if(sqlite3_column_type(statement,4)!=SQLITE_TEXT || - !knowledge_load_mode((const char *)sqlite3_column_text(statement,4),&output->load_mode)){ - status=TRAINLOG_STATUS_DATABASE_ERROR;goto done; + if (rc != SQLITE_DONE) { + status = TRAINLOG_STATUS_DATABASE_ERROR; + goto done; } - if(!knowledge_numeric_column(statement,5)){status=TRAINLOG_STATUS_DATABASE_ERROR;goto done;} - output->max_weight_kg=sqlite3_column_double(statement,5); - if(!isfinite(output->max_weight_kg) || output->max_weight_kg<=0.0){status=TRAINLOG_STATUS_DATABASE_ERROR;goto done;} - output->found=true; - rc=sqlite3_step(statement); - if(rc!=SQLITE_DONE){status=TRAINLOG_STATUS_DATABASE_ERROR;goto done;} - rc=sqlite3_finalize(statement); statement=NULL; - status=rc==SQLITE_OK?TRAINLOG_STATUS_OK:TRAINLOG_STATUS_DATABASE_ERROR; + rc = sqlite3_finalize(statement); + statement = NULL; + if (rc != SQLITE_OK) { + status = TRAINLOG_STATUS_DATABASE_ERROR; + goto done; + } + if (selected_count == 0U) { + status = TRAINLOG_STATUS_OK; + goto done; + } + rc = sqlite3_prepare_v2(database->connection, HYDRATE_SQL, -1, &statement, NULL); + if (rc == SQLITE_OK) { + rc = sqlite3_bind_int64(statement, 1, selected[0].row_id); + } + if (rc == SQLITE_OK) { + rc = sqlite3_step(statement); + } + if (rc != SQLITE_ROW || + !knowledge_copy_column(statement, 0, output->session_id, sizeof(output->session_id)) || + !knowledge_copy_column(statement, 1, output->entry_id, sizeof(output->entry_id)) || + !knowledge_copy_column(statement, 2, output->started_at, sizeof(output->started_at)) || + !knowledge_copy_column(statement, 3, output->equipment_id, sizeof(output->equipment_id))) { + status = TRAINLOG_STATUS_DATABASE_ERROR; + goto done; + } + if (sqlite3_column_type(statement, 4) != SQLITE_TEXT || + !knowledge_load_mode((const char *)sqlite3_column_text(statement, 4), &output->load_mode)) { + status = TRAINLOG_STATUS_DATABASE_ERROR; + goto done; + } + if (!knowledge_numeric_column(statement, 5)) { + status = TRAINLOG_STATUS_DATABASE_ERROR; + goto done; + } + output->max_weight_kg = sqlite3_column_double(statement, 5); + if (!isfinite(output->max_weight_kg) || output->max_weight_kg <= 0.0) { + status = TRAINLOG_STATUS_DATABASE_ERROR; + goto done; + } + output->found = true; + rc = sqlite3_step(statement); + if (rc != SQLITE_DONE) { + status = TRAINLOG_STATUS_DATABASE_ERROR; + goto done; + } + rc = sqlite3_finalize(statement); + statement = NULL; + status = rc == SQLITE_OK ? TRAINLOG_STATUS_OK : TRAINLOG_STATUS_DATABASE_ERROR; done: (void)sqlite3_finalize(statement); knowledge_temporal_candidate_release(&selected[0]); - if(snapshot){ - TrainlogStatus end_status=trainlog_database_read_snapshot_end(database,status==TRAINLOG_STATUS_OK); - if(status==TRAINLOG_STATUS_OK)status=end_status; + if (snapshot) { + TrainlogStatus end_status = + trainlog_database_read_snapshot_end(database, status == TRAINLOG_STATUS_OK); + if (status == TRAINLOG_STATUS_OK) { + status = end_status; + } } return status; } -TrainlogStatus trainlog_database_latest_explicit_max_context( - TrainlogDatabase *database, const char *exercise_id, TrainlogLatestExplicitMax *output) -{ +TrainlogStatus trainlog_database_latest_explicit_max_context(TrainlogDatabase *database, + const char *exercise_id, + TrainlogLatestExplicitMax *output) { return database_latest_explicit_max_context(database, exercise_id, NULL, output); } -TrainlogStatus trainlog_database_latest_explicit_max_equipment_context( - TrainlogDatabase *database, const char *exercise_id, const char *equipment_id, - TrainlogLatestExplicitMax *output) -{ - if (equipment_id == NULL || equipment_id[0] == '\0') +TrainlogStatus +trainlog_database_latest_explicit_max_equipment_context(TrainlogDatabase *database, + const char *exercise_id, + const char *equipment_id, + TrainlogLatestExplicitMax *output) { + if (equipment_id == NULL || equipment_id[0] == '\0') { return TRAINLOG_STATUS_INVALID_ARGUMENT; + } return database_latest_explicit_max_context(database, exercise_id, equipment_id, output); } diff --git a/tui/src/id.c b/tui/src/id.c index 70605fd..2078c3f 100644 --- a/tui/src/id.c +++ b/tui/src/id.c @@ -20,7 +20,7 @@ static bool prefix_is_supported(const char *prefix) { return strcmp(prefix, "ex") == 0 || strcmp(prefix, "se") == 0 || strcmp(prefix, "bo") == 0 || strcmp(prefix, "sy") == 0 || strcmp(prefix, "sxe") == 0 || strcmp(prefix, "sp") == 0 || strcmp(prefix, "spr") == 0 || strcmp(prefix, "spe") == 0 || strcmp(prefix, "spd") == 0 || - strcmp(prefix, "spw") == 0; + strcmp(prefix, "spw") == 0 || strcmp(prefix, "pgr") == 0; } TrainlogStatus trainlog_id_generate(const char *prefix, char *output, size_t output_size) { diff --git a/tui/src/web_server.c b/tui/src/web_server.c index 7570e1a..5557f61 100644 --- a/tui/src/web_server.c +++ b/tui/src/web_server.c @@ -22,6 +22,7 @@ #include "trainlog/web_dashboard.h" #include "trainlog/web_prepared_items.h" +#include "trainlog/web_programs.h" #include "trainlog/web_sessions.h" #include "trainlog/dashboard_layout.h" #include "trainlog/web_preferences.h" @@ -31,7 +32,7 @@ #define TRAINLOG_HTTP_PER_IP_LIMIT 32U #define TRAINLOG_HTTP_CONNECTION_MEMORY_LIMIT (16U * 1024U) #define TRAINLOG_HTTP_HEADER_LIMIT (8U * 1024U) -#define TRAINLOG_HTTP_BODY_LIMIT (128U * 1024U) +#define TRAINLOG_HTTP_BODY_LIMIT TRAINLOG_WEB_PROGRAM_BYTES_MAX #define TRAINLOG_HTTP_LEGACY_BODY_LIMIT (4U * 1024U) #define TRAINLOG_HTTP_TIMEOUT_SECONDS 10U #define TRAINLOG_HTTP_BACKLOG 32U @@ -564,6 +565,234 @@ static enum MHD_Result queue_owned_sessions_json(struct MHD_Connection *connecti return result; } +static enum MHD_Result queue_owned_programs_json(struct MHD_Connection *connection, + TrainlogStatus status, + char *json, + size_t json_size) { + if (status == TRAINLOG_STATUS_NOT_FOUND) { + free(json); + return queue_json(connection, MHD_HTTP_NOT_FOUND, "{\"error\":\"not_found\"}\n", NULL); + } + if (status == TRAINLOG_STATUS_CONFLICT) { + free(json); + return queue_json( + connection, MHD_HTTP_CONFLICT, "{\"error\":\"program_conflict\"}\n", NULL); + } + if (status == TRAINLOG_STATUS_INVALID_ARGUMENT) { + free(json); + return queue_json( + connection, MHD_HTTP_UNPROCESSABLE_CONTENT, "{\"error\":\"invalid_program\"}\n", NULL); + } + return queue_owned_sessions_json(connection, status, json, json_size); +} + +static bool program_mutation_allowed(TrainlogWebContext *context, + struct MHD_Connection *connection) { + const char *origin = MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "Origin"); + const char *csrf = + MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "X-Trainlog-CSRF-Token"); + const char *content_type = + MHD_lookup_connection_value(connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_CONTENT_TYPE); + + return valid_origin(context, origin) && valid_csrf(context, csrf) && content_type != NULL && + strcmp(content_type, "application/json") == 0; +} + +static enum MHD_Result +handle_program_list(TrainlogWebContext *context, struct MHD_Connection *connection, bool is_get) { + const char *search; + const char *state; + char *json = NULL; + size_t json_size = 0U; + size_t offset; + size_t limit; + TrainlogStatus status; + + if (!is_get) { + return queue_json( + connection, MHD_HTTP_METHOD_NOT_ALLOWED, "{\"error\":\"method_not_allowed\"}\n", "GET"); + } + search = MHD_lookup_connection_value(connection, MHD_GET_ARGUMENT_KIND, "search"); + state = MHD_lookup_connection_value(connection, MHD_GET_ARGUMENT_KIND, "state"); + if (!parse_page_number(MHD_lookup_connection_value(connection, MHD_GET_ARGUMENT_KIND, "offset"), + 0U, + 1000000U, + &offset) || + !parse_page_number(MHD_lookup_connection_value(connection, MHD_GET_ARGUMENT_KIND, "limit"), + 24U, + TRAINLOG_WEB_SESSIONS_PAGE_MAX, + &limit) || + limit == 0U) { + return queue_json(connection, MHD_HTTP_BAD_REQUEST, "{\"error\":\"invalid_page\"}\n", NULL); + } + status = trainlog_web_programs_list_json( + context->database, search, state, offset, limit, &json, &json_size); + return queue_owned_programs_json(connection, status, json, json_size); +} + +static enum MHD_Result handle_program_import(TrainlogWebContext *context, + struct MHD_Connection *connection, + const char *url, + const char *method, + TrainlogHttpRequestState *request) { + char *json = NULL; + size_t json_size = 0U; + bool commit = strcmp(url, "/api/v1/sessions/programs/import") == 0; + TrainlogStatus status; + + if (strcmp(method, MHD_HTTP_METHOD_POST) != 0) { + return queue_json(connection, + MHD_HTTP_METHOD_NOT_ALLOWED, + "{\"error\":\"method_not_allowed\"}\n", + "POST"); + } + if (!program_mutation_allowed(context, connection)) { + return queue_json( + connection, MHD_HTTP_FORBIDDEN, "{\"error\":\"mutation_forbidden\"}\n", NULL); + } + status = trainlog_web_programs_import_json( + context->database, request->body, request->body_size, commit, &json, &json_size); + return queue_owned_programs_json(connection, status, json, json_size); +} + +static bool +copy_path_component(char *output, size_t output_capacity, const char *input, size_t input_length) { + if (input_length == 0U || input_length >= output_capacity) { + return false; + } + (void)memcpy(output, input, input_length); + output[input_length] = '\0'; + return true; +} + +static bool parse_quoted_revision(const char *header, char *output, size_t output_capacity) { + size_t length = header == NULL ? 0U : strlen(header); + + if (length < 3U || header[0] != '"' || header[length - 1U] != '"') { + return false; + } + return copy_path_component(output, output_capacity, header + 1, length - 2U); +} + +static enum MHD_Result handle_program_archive(TrainlogWebContext *context, + struct MHD_Connection *connection, + const char *identity, + size_t identity_length, + const char *method) { + static const char SUFFIX[] = "/archive"; + const char *request_id = + MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "X-Trainlog-Request-ID"); + const char *if_match = + MHD_lookup_connection_value(connection, MHD_HEADER_KIND, MHD_HTTP_HEADER_IF_MATCH); + char program_id[TRAINLOG_ID_MAX + 1U]; + char revision[TRAINLOG_ID_MAX + 1U]; + char *json = NULL; + size_t json_size = 0U; + size_t program_length = identity_length - strlen(SUFFIX); + TrainlogStatus status; + + if (strcmp(method, MHD_HTTP_METHOD_POST) != 0) { + return queue_json(connection, + MHD_HTTP_METHOD_NOT_ALLOWED, + "{\"error\":\"method_not_allowed\"}\n", + "POST"); + } + if (!program_mutation_allowed(context, connection) || request_id == NULL || + request_id[0] == '\0' || + !copy_path_component(program_id, sizeof(program_id), identity, program_length) || + !parse_quoted_revision(if_match, revision, sizeof(revision))) { + return queue_json(connection, + MHD_HTTP_PRECONDITION_REQUIRED, + "{\"error\":\"precondition_required\"}\n", + NULL); + } + status = trainlog_web_programs_archive_json( + context->database, program_id, revision, request_id, &json, &json_size); + return queue_owned_programs_json(connection, status, json, json_size); +} + +static enum MHD_Result handle_program_prepare(TrainlogWebContext *context, + struct MHD_Connection *connection, + const char *identity, + const char *sessions_marker, + const char *method) { + static const char SUFFIX[] = "/prepare"; + const char *request_id = + MHD_lookup_connection_value(connection, MHD_HEADER_KIND, "X-Trainlog-Request-ID"); + const char *session_id = sessions_marker + strlen("/sessions/"); + size_t session_length = strlen(session_id); + char program_id[TRAINLOG_ID_MAX + 1U]; + char program_session_id[TRAINLOG_ID_MAX + 1U]; + char *json = NULL; + size_t json_size = 0U; + TrainlogStatus status; + + if (session_length <= strlen(SUFFIX) || + strcmp(session_id + session_length - strlen(SUFFIX), SUFFIX) != 0 || + strcmp(method, MHD_HTTP_METHOD_POST) != 0) { + return queue_json(connection, + MHD_HTTP_METHOD_NOT_ALLOWED, + "{\"error\":\"method_not_allowed\"}\n", + "POST"); + } + session_length -= strlen(SUFFIX); + if (!program_mutation_allowed(context, connection) || request_id == NULL || + request_id[0] == '\0' || + !copy_path_component( + program_id, sizeof(program_id), identity, (size_t)(sessions_marker - identity)) || + !copy_path_component( + program_session_id, sizeof(program_session_id), session_id, session_length)) { + return queue_json( + connection, MHD_HTTP_FORBIDDEN, "{\"error\":\"mutation_forbidden\"}\n", NULL); + } + status = trainlog_web_programs_prepare_json( + context->database, program_id, program_session_id, request_id, &json, &json_size); + return queue_owned_programs_json(connection, status, json, json_size); +} + +static enum MHD_Result handle_program_request(TrainlogWebContext *context, + struct MHD_Connection *connection, + const char *url, + const char *method, + bool is_get, + TrainlogHttpRequestState *request) { + static const char PREFIX[] = "/api/v1/sessions/program/"; + static const char ARCHIVE_SUFFIX[] = "/archive"; + const char *identity; + const char *sessions_marker; + char *json = NULL; + size_t json_size = 0U; + size_t identity_length; + TrainlogStatus status; + + if (strcmp(url, "/api/v1/sessions/programs") == 0) { + return handle_program_list(context, connection, is_get); + } + if (strcmp(url, "/api/v1/sessions/programs/validate") == 0 || + strcmp(url, "/api/v1/sessions/programs/import") == 0) { + return handle_program_import(context, connection, url, method, request); + } + if (strncmp(url, PREFIX, strlen(PREFIX)) != 0) { + return queue_json(connection, MHD_HTTP_NOT_FOUND, "{\"error\":\"not_found\"}\n", NULL); + } + identity = url + strlen(PREFIX); + identity_length = strlen(identity); + sessions_marker = strstr(identity, "/sessions/"); + if (identity_length > strlen(ARCHIVE_SUFFIX) && + strcmp(identity + identity_length - strlen(ARCHIVE_SUFFIX), ARCHIVE_SUFFIX) == 0) { + return handle_program_archive(context, connection, identity, identity_length, method); + } + if (sessions_marker != NULL) { + return handle_program_prepare(context, connection, identity, sessions_marker, method); + } + if (!is_get || identity_length == 0U || identity_length > TRAINLOG_ID_MAX) { + return queue_json( + connection, MHD_HTTP_METHOD_NOT_ALLOWED, "{\"error\":\"method_not_allowed\"}\n", "GET"); + } + status = trainlog_web_programs_detail_json(context->database, identity, &json, &json_size); + return queue_owned_programs_json(connection, status, json, json_size); +} + static enum MHD_Result handle_request(void *closure, struct MHD_Connection *connection, const char *url, @@ -782,6 +1011,9 @@ static enum MHD_Result handle_request(void *closure, } return queue_json(connection, MHD_HTTP_OK, json, NULL); } + if (strncmp(url, "/api/v1/sessions/program", strlen("/api/v1/sessions/program")) == 0) { + return handle_program_request(context, connection, url, method, is_get, state); + } if (strncmp(url, "/api/v1/sessions", strlen("/api/v1/sessions")) == 0) { static const struct { const char *path; @@ -985,12 +1217,8 @@ static enum MHD_Result handle_request(void *closure, if (is_get) { preferences_result = trainlog_web_preferences_load(&preferences, &source); if (preferences_result != TRAINLOG_WEB_PREFERENCES_OK || - !trainlog_web_preferences_serialize(&preferences, - source, - true, - json, - sizeof(json), - &json_size)) { + !trainlog_web_preferences_serialize( + &preferences, source, true, json, sizeof(json), &json_size)) { return queue_json(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "{\"error\":\"preferences_unavailable\"}\n", @@ -1002,11 +1230,8 @@ static enum MHD_Result handle_request(void *closure, "Trainlog Web : préférences persistées invalides, défaut utilisé\n"); context->invalid_preferences_reported = true; } - return queue_layout_json(connection, - MHD_HTTP_OK, - json, - preferences.revision, - context->csrf_token); + return queue_layout_json( + connection, MHD_HTTP_OK, json, preferences.revision, context->csrf_token); } if (strcmp(method, MHD_HTTP_METHOD_PUT) != 0) { return queue_json(connection, @@ -1040,15 +1265,11 @@ static enum MHD_Result handle_request(void *closure, } preferences_result = trainlog_web_preferences_parse(state->body, state->body_size, &preferences); - if (preferences_result != TRAINLOG_WEB_PREFERENCES_OK || - preferences.revision != expected) { - return queue_json(connection, - MHD_HTTP_BAD_REQUEST, - "{\"error\":\"invalid_preferences\"}\n", - NULL); + if (preferences_result != TRAINLOG_WEB_PREFERENCES_OK || preferences.revision != expected) { + return queue_json( + connection, MHD_HTTP_BAD_REQUEST, "{\"error\":\"invalid_preferences\"}\n", NULL); } - preferences_result = - trainlog_web_preferences_save(&preferences, expected, &saved); + preferences_result = trainlog_web_preferences_save(&preferences, expected, &saved); if (preferences_result == TRAINLOG_WEB_PREFERENCES_CONFLICT) { return queue_json(connection, MHD_HTTP_PRECONDITION_FAILED, @@ -1056,12 +1277,8 @@ static enum MHD_Result handle_request(void *closure, NULL); } if (preferences_result != TRAINLOG_WEB_PREFERENCES_OK || - !trainlog_web_preferences_serialize(&saved, - TRAINLOG_WEB_PREFERENCES_PERSISTED, - true, - json, - sizeof(json), - &json_size)) { + !trainlog_web_preferences_serialize( + &saved, TRAINLOG_WEB_PREFERENCES_PERSISTED, true, json, sizeof(json), &json_size)) { return queue_json(connection, MHD_HTTP_INTERNAL_SERVER_ERROR, "{\"error\":\"preferences_save_failed\"}\n", diff --git a/tui/src/web_sessions.c b/tui/src/web_sessions.c index 56a4738..9af978b 100644 --- a/tui/src/web_sessions.c +++ b/tui/src/web_sessions.c @@ -23,7 +23,8 @@ static const ListDefinition LISTS[] = { "FROM session_preparations p JOIN session_preparation_revisions r ON " "r.revision_id=p.current_revision_id " "LEFT JOIN session_preparation_entries e ON e.revision_id=r.revision_id " - "WHERE p.withdrawn_at IS NULL AND (?1='' OR r.title LIKE '%'||?1||'%' COLLATE NOCASE OR EXISTS(SELECT 1 FROM " + "WHERE p.withdrawn_at IS NULL AND (?1='' OR r.title LIKE '%'||?1||'%' COLLATE NOCASE OR " + "EXISTS(SELECT 1 FROM " "session_preparation_entries pe JOIN exercises x ON x.exercise_id=pe.exercise_id WHERE " "pe.revision_id=r.revision_id AND x.name LIKE '%'||?1||'%' COLLATE NOCASE)) " "GROUP BY p.preparation_id ORDER BY " @@ -194,22 +195,22 @@ static TrainlogStatus detail_for_revision(TrainlogDatabase *database, bool proposal, yyjson_mut_doc *document, yyjson_mut_val *root) { - const char *sql = proposal - ? "SELECT " - "d.draft_id,COALESCE(d.title,''),d.planned_for,d.session_type,d.notes," - "i.payload_sha256,d.draft_id,CASE WHEN d.published_at IS NULL THEN " - "'local' ELSE 'published' END FROM ai_session_drafts d LEFT JOIN " - "ai_session_draft_imports i ON i.draft_row_id=d.id WHERE d.draft_id=?1" - : "SELECT " - "p.preparation_id,r.title,r.planned_for,r.session_type,r.notes," - "COALESCE(p.source_payload_sha256,''),p.current_revision_id," - "CASE WHEN p.withdrawn_at IS NULL THEN p.delivery_state ELSE 'withdrawn' END," - "p.source_proposal_id,source.title,p.withdrawn_at,w.withdrawal_id," - "w.acknowledged_at FROM session_preparations p JOIN " - "session_preparation_revisions r ON r.revision_id=p.current_revision_id " - "LEFT JOIN ai_session_drafts source ON source.draft_id=p.source_proposal_id " - "LEFT JOIN session_preparation_withdrawals w ON " - "w.preparation_id=p.preparation_id WHERE p.preparation_id=?1"; + const char *sql = + proposal ? "SELECT " + "d.draft_id,COALESCE(d.title,''),d.planned_for,d.session_type,d.notes," + "i.payload_sha256,d.draft_id,CASE WHEN d.published_at IS NULL THEN " + "'local' ELSE 'published' END FROM ai_session_drafts d LEFT JOIN " + "ai_session_draft_imports i ON i.draft_row_id=d.id WHERE d.draft_id=?1" + : "SELECT " + "p.preparation_id,r.title,r.planned_for,r.session_type,r.notes," + "COALESCE(p.source_payload_sha256,''),p.current_revision_id," + "CASE WHEN p.withdrawn_at IS NULL THEN p.delivery_state ELSE 'withdrawn' END," + "p.source_proposal_id,source.title,p.withdrawn_at,w.withdrawal_id," + "w.acknowledged_at FROM session_preparations p JOIN " + "session_preparation_revisions r ON r.revision_id=p.current_revision_id " + "LEFT JOIN ai_session_drafts source ON source.draft_id=p.source_proposal_id " + "LEFT JOIN session_preparation_withdrawals w ON " + "w.preparation_id=p.preparation_id WHERE p.preparation_id=?1"; const char *entry_sql = proposal ? "SELECT " @@ -254,16 +255,13 @@ static TrainlogStatus detail_for_revision(TrainlogDatabase *database, } if (!proposal) { static const char *const provenance_keys[] = {"source_proposal_id", - "source_proposal_title", - "withdrawn_at", - "withdrawal_id", - "withdrawal_acknowledged_at"}; + "source_proposal_title", + "withdrawn_at", + "withdrawal_id", + "withdrawal_acknowledged_at"}; for (column = 0; column < 5; ++column) { - if (!add_nullable_text(document, - root, - provenance_keys[column], - statement, - column + 8)) { + if (!add_nullable_text( + document, root, provenance_keys[column], statement, column + 8)) { (void)sqlite3_finalize(statement); return TRAINLOG_STATUS_DATABASE_ERROR; } @@ -717,6 +715,96 @@ static bool bind_nullable_text(sqlite3_stmt *statement, int index, yyjson_val *v SQLITE_TRANSIENT) == SQLITE_OK; } +static bool program_session_exists(TrainlogDatabase *database, + const char *program_id, + const char *program_session_id) { + sqlite3_stmt *statement = NULL; + int result; + bool found = false; + + result = sqlite3_prepare_v2(database->connection, + "SELECT 1 FROM program_sessions s JOIN programs p ON " + "p.program_id=s.program_id WHERE p.program_id=?1 AND " + "s.program_session_id=?2", + -1, + &statement, + NULL); + if (result == SQLITE_OK) { + result = sqlite3_bind_text(statement, 1, program_id, -1, SQLITE_TRANSIENT); + } + if (result == SQLITE_OK) { + result = sqlite3_bind_text(statement, 2, program_session_id, -1, SQLITE_TRANSIENT); + } + if (result == SQLITE_OK) { + found = sqlite3_step(statement) == SQLITE_ROW; + } + if (statement != NULL) { + (void)sqlite3_finalize(statement); + } + return found; +} + +static TrainlogStatus insert_preparation_root(TrainlogDatabase *database, + const char *preparation_id, + const char *revision_id, + const char *created_at, + const char *editing_state, + const char *source_proposal_id, + const char *source_payload_sha256, + const char *source_program_id, + const char *source_program_session_id) { + static const char INSERT_SQL[] = + "INSERT INTO session_preparations(" + "preparation_id,current_revision_id,created_at,updated_at,editing_state," + "delivery_state,source_proposal_id,source_payload_sha256,withdrawn_at," + "source_program_id,source_program_session_id) " + "VALUES(?1,?2,?3,?3,?4,'local',?5,?6,NULL,?7,?8)"; + sqlite3_stmt *statement = NULL; + int result; + + result = sqlite3_prepare_v2(database->connection, INSERT_SQL, -1, &statement, NULL); + if (result == SQLITE_OK) { + result = sqlite3_bind_text(statement, 1, preparation_id, -1, SQLITE_TRANSIENT); + } + if (result == SQLITE_OK) { + result = sqlite3_bind_text(statement, 2, revision_id, -1, SQLITE_TRANSIENT); + } + if (result == SQLITE_OK) { + result = sqlite3_bind_text(statement, 3, created_at, -1, SQLITE_TRANSIENT); + } + if (result == SQLITE_OK) { + result = sqlite3_bind_text(statement, 4, editing_state, -1, SQLITE_TRANSIENT); + } + if (result == SQLITE_OK) { + result = source_proposal_id == NULL + ? sqlite3_bind_null(statement, 5) + : sqlite3_bind_text(statement, 5, source_proposal_id, -1, SQLITE_TRANSIENT); + } + if (result == SQLITE_OK) { + result = source_payload_sha256 == NULL + ? sqlite3_bind_null(statement, 6) + : sqlite3_bind_text(statement, 6, source_payload_sha256, -1, SQLITE_TRANSIENT); + } + if (result == SQLITE_OK) { + result = source_program_id == NULL + ? sqlite3_bind_null(statement, 7) + : sqlite3_bind_text(statement, 7, source_program_id, -1, SQLITE_TRANSIENT); + } + if (result == SQLITE_OK) { + result = + source_program_session_id == NULL + ? sqlite3_bind_null(statement, 8) + : sqlite3_bind_text(statement, 8, source_program_session_id, -1, SQLITE_TRANSIENT); + } + if (result == SQLITE_OK) { + result = sqlite3_step(statement); + } + if (statement != NULL && sqlite3_finalize(statement) != SQLITE_OK) { + return TRAINLOG_STATUS_DATABASE_ERROR; + } + return result == SQLITE_DONE ? TRAINLOG_STATUS_OK : TRAINLOG_STATUS_DATABASE_ERROR; +} + static TrainlogStatus insert_preparation_entry(TrainlogDatabase *database, const char *revision_id, yyjson_val *entry, @@ -854,7 +942,9 @@ TrainlogStatus trainlog_web_sessions_save_json(TrainlogDatabase *database, "editing_state", "occurrences", "source_proposal_id", - "source_payload_sha256"}; + "source_payload_sha256", + "source_program_id", + "source_program_session_id"}; yyjson_doc *input = NULL; yyjson_val *root; yyjson_val *occurrences; @@ -868,6 +958,8 @@ TrainlogStatus trainlog_web_sessions_save_json(TrainlogDatabase *database, const char *editing_state; const char *source_proposal = NULL; const char *source_fingerprint = NULL; + const char *source_program = NULL; + const char *source_program_session = NULL; size_t index; TrainlogStatus status = TRAINLOG_STATUS_OK; yyjson_mut_doc *response; @@ -912,6 +1004,21 @@ TrainlogStatus trainlog_web_sessions_save_json(TrainlogDatabase *database, yyjson_doc_free(input); return TRAINLOG_STATUS_INVALID_ARGUMENT; } + if (yyjson_obj_get(root, "source_program_id") != NULL && + (!bounded_json_string( + yyjson_obj_get(root, "source_program_id"), TRAINLOG_ID_MAX + 1U, &source_program) || + !bounded_json_string(yyjson_obj_get(root, "source_program_session_id"), + TRAINLOG_ID_MAX + 1U, + &source_program_session))) { + yyjson_doc_free(input); + return TRAINLOG_STATUS_INVALID_ARGUMENT; + } + if ((source_program == NULL) != (source_program_session == NULL) || + (preparation_id != NULL && preparation_id[0] != '\0' && source_program != NULL) || + (source_proposal != NULL && source_program != NULL)) { + yyjson_doc_free(input); + return TRAINLOG_STATUS_INVALID_ARGUMENT; + } if (actual_preparation == NULL || actual_preparation[0] == '\0') { if (expected_revision[0] != '\0' || trainlog_id_generate("sp", new_preparation, sizeof(new_preparation)) != @@ -948,6 +1055,12 @@ TrainlogStatus trainlog_web_sessions_save_json(TrainlogDatabase *database, (void)sqlite3_finalize(statement); statement = NULL; } + if ((preparation_id == NULL || preparation_id[0] == '\0') && source_program != NULL) { + if (!program_session_exists(database, source_program, source_program_session)) { + status = TRAINLOG_STATUS_CONFLICT; + goto rollback; + } + } if ((preparation_id == NULL || preparation_id[0] == '\0') && source_proposal != NULL) { if (sqlite3_prepare_v2( database->connection, @@ -987,31 +1100,19 @@ TrainlogStatus trainlog_web_sessions_save_json(TrainlogDatabase *database, } (void)sqlite3_finalize(statement); statement = NULL; - } else if (sqlite3_prepare_v2( - database->connection, - "INSERT INTO session_preparations(" - "preparation_id,current_revision_id,created_at,updated_at,editing_state," - "delivery_state,source_proposal_id,source_payload_sha256,withdrawn_at) " - "VALUES(?1,?2,?3,?3,?4,'local',?5,?6,NULL)", - -1, - &statement, - NULL) != SQLITE_OK || - sqlite3_bind_text(statement, 1, actual_preparation, -1, SQLITE_TRANSIENT) != - SQLITE_OK || - sqlite3_bind_text(statement, 2, revision, -1, SQLITE_TRANSIENT) != SQLITE_OK || - sqlite3_bind_text(statement, 3, now, -1, SQLITE_TRANSIENT) != SQLITE_OK || - sqlite3_bind_text(statement, 4, editing_state, -1, SQLITE_TRANSIENT) != SQLITE_OK || - (source_proposal == NULL - ? sqlite3_bind_null(statement, 5) - : sqlite3_bind_text(statement, 5, source_proposal, -1, SQLITE_TRANSIENT)) != - SQLITE_OK || - (source_fingerprint == NULL - ? sqlite3_bind_null(statement, 6) - : sqlite3_bind_text(statement, 6, source_fingerprint, -1, SQLITE_TRANSIENT)) != - SQLITE_OK || - sqlite3_step(statement) != SQLITE_DONE) { - status = TRAINLOG_STATUS_DATABASE_ERROR; - goto rollback; + } else { + status = insert_preparation_root(database, + actual_preparation, + revision, + now, + editing_state, + source_proposal, + source_fingerprint, + source_program, + source_program_session); + if (status != TRAINLOG_STATUS_OK) { + goto rollback; + } } if (statement != NULL) { (void)sqlite3_finalize(statement); @@ -1372,10 +1473,8 @@ TrainlogStatus trainlog_web_sessions_withdraw_json(TrainlogDatabase *database, (void)yyjson_mut_obj_add_strcpy(response, root, "preparation_id", preparation_id); (void)yyjson_mut_obj_add_strcpy(response, root, "revision_id", expected_revision); (void)yyjson_mut_obj_add_strcpy(response, root, "withdrawal_id", withdrawal_id); - (void)yyjson_mut_obj_add_strcpy(response, - root, - "android_cancellation", - has_deliveries ? "pending" : "not_required"); + (void)yyjson_mut_obj_add_strcpy( + response, root, "android_cancellation", has_deliveries ? "pending" : "not_required"); status = write_document(response, output_json, output_size); response = NULL; if (status != TRAINLOG_STATUS_OK || diff --git a/tui/tests/test_schema_v21_migration.c b/tui/tests/test_schema_v21_migration.c index be4b279..e49e244 100644 --- a/tui/tests/test_schema_v21_migration.c +++ b/tui/tests/test_schema_v21_migration.c @@ -7,38 +7,82 @@ #include "trainlog/database.h" -#define CHECK(x) do { if (!(x)) { fprintf(stderr,"CHECK failed at %s:%d: %s\n",__FILE__,__LINE__,#x); return 1; } } while (0) +#define CHECK(expression) \ + do { \ + if (!(expression)) { \ + (void)fprintf(stderr, "CHECK failed at %s:%d: %s\n", __FILE__, __LINE__, #expression); \ + return 1; \ + } \ + } while (0) -static int scalar(sqlite3 *database, const char *sql) -{ - sqlite3_stmt *statement=NULL;int value=-1; - if(sqlite3_prepare_v2(database,sql,-1,&statement,NULL)==SQLITE_OK && sqlite3_step(statement)==SQLITE_ROW)value=sqlite3_column_int(statement,0); - (void)sqlite3_finalize(statement);return value; +static int scalar(sqlite3 *database, const char *sql) { + sqlite3_stmt *statement = NULL; + int value = -1; + + if (sqlite3_prepare_v2(database, sql, -1, &statement, NULL) == SQLITE_OK && + sqlite3_step(statement) == SQLITE_ROW) { + value = sqlite3_column_int(statement, 0); + } + (void)sqlite3_finalize(statement); + return value; } -int main(void) -{ - char path[]="/tmp/trainlog-schema-v21-XXXXXX"; - int fd=mkstemp(path);TrainlogDatabase *database=NULL;sqlite3 *raw=NULL;size_t exercises=0U; - CHECK(fd>=0);CHECK(close(fd)==0); - CHECK(trainlog_database_open(path,&database)==TRAINLOG_STATUS_OK); +int main(void) { + static const char DOWNGRADE_TO_V20[] = "PRAGMA foreign_keys=OFF;" + "DROP TABLE program_requests;" + "DROP TABLE program_session_entries;" + "DROP TABLE program_sessions;" + "DROP TABLE programs;" + "DROP TABLE session_preparation_withdrawals;" + "DROP TABLE session_preparation_deliveries;" + "DROP TABLE session_preparation_requests;" + "DROP TABLE session_preparation_entries;" + "DROP TABLE session_preparation_revisions;" + "DROP TABLE session_preparations;" + "DROP TABLE sync_generation_archives;" + "DROP TABLE sync_causal_publications;" + "DROP TABLE sync_acknowledgements;" + "DROP TABLE sync_consumed_generations;" + "DROP TABLE sync_generation_artifacts;" + "DROP TABLE sync_generations;" + "DROP TABLE sync_peer_identity;" + "PRAGMA user_version=20;"; + char path[] = "/tmp/trainlog-schema-v21-XXXXXX"; + int file_descriptor = mkstemp(path); + TrainlogDatabase *database = NULL; + sqlite3 *raw = NULL; + size_t exercises = 0U; + + CHECK(file_descriptor >= 0); + CHECK(close(file_descriptor) == 0); + CHECK(trainlog_database_open(path, &database) == TRAINLOG_STATUS_OK); CHECK(trainlog_database_insert_exercise_profiled(database, - "ex_aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa","Migration sentinel","migration sentinel", - TRAINLOG_TRACKING_REPS,TRAINLOG_RECORDING_SETS,0U)==TRAINLOG_STATUS_OK); - trainlog_database_close(database);database=NULL; - CHECK(sqlite3_open(path,&raw)==SQLITE_OK); - CHECK(sqlite3_exec(raw,"PRAGMA foreign_keys=OFF;DROP TABLE session_preparation_withdrawals;DROP TABLE session_preparation_deliveries;DROP TABLE session_preparation_requests;DROP TABLE session_preparation_entries;DROP TABLE session_preparation_revisions;DROP TABLE session_preparations;DROP TABLE sync_generation_archives;DROP TABLE sync_causal_publications;DROP TABLE sync_acknowledgements;DROP TABLE sync_consumed_generations;DROP TABLE sync_generation_artifacts;DROP TABLE sync_generations;DROP TABLE sync_peer_identity;PRAGMA user_version=20;",NULL,NULL,NULL)==SQLITE_OK); - CHECK(sqlite3_close(raw)==SQLITE_OK); - CHECK(trainlog_database_open(path,&database)==TRAINLOG_STATUS_OK); - CHECK(trainlog_database_exercise_count(database,&exercises)==TRAINLOG_STATUS_OK);CHECK(exercises>0U); - trainlog_database_close(database);database=NULL; - CHECK(sqlite3_open(path,&raw)==SQLITE_OK); - CHECK(scalar(raw,"PRAGMA user_version")==TRAINLOG_DATABASE_SCHEMA_VERSION); - CHECK(scalar(raw,"SELECT COUNT(*) FROM session_preparation_withdrawals")==0); - CHECK(scalar(raw,"SELECT COUNT(*) FROM sync_generations")==0); - CHECK(scalar(raw,"SELECT COUNT(*) FROM sync_consumed_generations")==0); - CHECK(scalar(raw,"SELECT COUNT(*) FROM sync_acknowledgements")==0); - CHECK(scalar(raw,"SELECT COUNT(*) FROM sync_generation_archives")==0); - CHECK(sqlite3_close(raw)==SQLITE_OK);CHECK(unlink(path)==0); + "ex_aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa", + "Migration sentinel", + "migration sentinel", + TRAINLOG_TRACKING_REPS, + TRAINLOG_RECORDING_SETS, + 0U) == TRAINLOG_STATUS_OK); + trainlog_database_close(database); + database = NULL; + + CHECK(sqlite3_open(path, &raw) == SQLITE_OK); + CHECK(sqlite3_exec(raw, DOWNGRADE_TO_V20, NULL, NULL, NULL) == SQLITE_OK); + CHECK(sqlite3_close(raw) == SQLITE_OK); + CHECK(trainlog_database_open(path, &database) == TRAINLOG_STATUS_OK); + CHECK(trainlog_database_exercise_count(database, &exercises) == TRAINLOG_STATUS_OK); + CHECK(exercises > 0U); + trainlog_database_close(database); + database = NULL; + + CHECK(sqlite3_open(path, &raw) == SQLITE_OK); + CHECK(scalar(raw, "PRAGMA user_version") == TRAINLOG_DATABASE_SCHEMA_VERSION); + CHECK(scalar(raw, "SELECT COUNT(*) FROM session_preparation_withdrawals") == 0); + CHECK(scalar(raw, "SELECT COUNT(*) FROM sync_generations") == 0); + CHECK(scalar(raw, "SELECT COUNT(*) FROM sync_consumed_generations") == 0); + CHECK(scalar(raw, "SELECT COUNT(*) FROM sync_acknowledgements") == 0); + CHECK(scalar(raw, "SELECT COUNT(*) FROM sync_generation_archives") == 0); + CHECK(sqlite3_close(raw) == SQLITE_OK); + CHECK(unlink(path) == 0); return 0; } diff --git a/tui/tests/test_schema_v7_migration.c b/tui/tests/test_schema_v7_migration.c index 20ee01f..6cb3568 100644 --- a/tui/tests/test_schema_v7_migration.c +++ b/tui/tests/test_schema_v7_migration.c @@ -13,45 +13,72 @@ #include "trainlog/database.h" -#define CHECK(condition) \ - do { \ - if (!(condition)) { \ - (void)fprintf(stderr, "CHECK failed at %s:%d: %s\n", \ - __FILE__, __LINE__, #condition); \ - return false; \ - } \ +#define CHECK(condition) \ + do { \ + if (!(condition)) { \ + (void)fprintf(stderr, "CHECK failed at %s:%d: %s\n", __FILE__, __LINE__, #condition); \ + return false; \ + } \ } while (0) static const char *const V7_FIXTURE_SQL = "PRAGMA foreign_keys=ON;" - "CREATE TABLE exercises(id INTEGER PRIMARY KEY,exercise_id TEXT NOT NULL UNIQUE,name TEXT NOT NULL,normalized_name TEXT NOT NULL UNIQUE,tracking_mode TEXT NOT NULL,recording_mode TEXT NOT NULL,data_fields INTEGER NOT NULL);" - "CREATE TABLE sessions(id INTEGER PRIMARY KEY,session_id TEXT NOT NULL UNIQUE,started_at TEXT NOT NULL,ended_at TEXT,session_type TEXT NOT NULL,notes TEXT);" - "CREATE TABLE session_exercises(id INTEGER PRIMARY KEY,entry_id TEXT NOT NULL UNIQUE,session_row_id INTEGER NOT NULL REFERENCES sessions(id),exercise_row_id INTEGER NOT NULL REFERENCES exercises(id),recording_mode TEXT NOT NULL,data_fields INTEGER NOT NULL,position INTEGER NOT NULL,load_mode TEXT NOT NULL,rest_seconds INTEGER NOT NULL,target_sets INTEGER,target_reps INTEGER,target_duration_seconds INTEGER,target_weight_kg REAL,equipment_id TEXT,notes TEXT,UNIQUE(session_row_id,position));" - "CREATE TABLE performed_sets(id INTEGER PRIMARY KEY,session_exercise_row_id INTEGER NOT NULL REFERENCES session_exercises(id),position INTEGER NOT NULL,reps INTEGER,duration_seconds INTEGER,weight_kg REAL,UNIQUE(session_exercise_row_id,position));" - "CREATE TABLE continuous_activity(id INTEGER PRIMARY KEY,session_exercise_row_id INTEGER NOT NULL UNIQUE REFERENCES session_exercises(id),duration_seconds INTEGER NOT NULL,speed_kmh REAL,distance_km REAL);" - "CREATE TABLE body_observations(id INTEGER PRIMARY KEY,observation_id TEXT NOT NULL UNIQUE,observed_at TEXT NOT NULL,session_row_id INTEGER UNIQUE REFERENCES sessions(id),body_weight_kg REAL,neck_cm REAL,shoulders_cm REAL,chest_cm REAL,waist_cm REAL,hips_cm REAL,left_arm_cm REAL,right_arm_cm REAL,left_forearm_cm REAL,right_forearm_cm REAL,left_thigh_cm REAL,right_thigh_cm REAL,left_calf_cm REAL,right_calf_cm REAL,notes TEXT);" + "CREATE TABLE exercises(id INTEGER PRIMARY KEY,exercise_id TEXT NOT NULL UNIQUE,name TEXT NOT " + "NULL,normalized_name TEXT NOT NULL UNIQUE,tracking_mode TEXT NOT NULL,recording_mode TEXT NOT " + "NULL,data_fields INTEGER NOT NULL);" + "CREATE TABLE sessions(id INTEGER PRIMARY KEY,session_id TEXT NOT NULL UNIQUE,started_at TEXT " + "NOT NULL,ended_at TEXT,session_type TEXT NOT NULL,notes TEXT);" + "CREATE TABLE session_exercises(id INTEGER PRIMARY KEY,entry_id TEXT NOT NULL " + "UNIQUE,session_row_id INTEGER NOT NULL REFERENCES sessions(id),exercise_row_id INTEGER NOT " + "NULL REFERENCES exercises(id),recording_mode TEXT NOT NULL,data_fields INTEGER NOT " + "NULL,position INTEGER NOT NULL,load_mode TEXT NOT NULL,rest_seconds INTEGER NOT " + "NULL,target_sets INTEGER,target_reps INTEGER,target_duration_seconds INTEGER,target_weight_kg " + "REAL,equipment_id TEXT,notes TEXT,UNIQUE(session_row_id,position));" + "CREATE TABLE performed_sets(id INTEGER PRIMARY KEY,session_exercise_row_id INTEGER NOT NULL " + "REFERENCES session_exercises(id),position INTEGER NOT NULL,reps INTEGER,duration_seconds " + "INTEGER,weight_kg REAL,UNIQUE(session_exercise_row_id,position));" + "CREATE TABLE continuous_activity(id INTEGER PRIMARY KEY,session_exercise_row_id INTEGER NOT " + "NULL UNIQUE REFERENCES session_exercises(id),duration_seconds INTEGER NOT NULL,speed_kmh " + "REAL,distance_km REAL);" + "CREATE TABLE body_observations(id INTEGER PRIMARY KEY,observation_id TEXT NOT NULL " + "UNIQUE,observed_at TEXT NOT NULL,session_row_id INTEGER UNIQUE REFERENCES " + "sessions(id),body_weight_kg REAL,neck_cm REAL,shoulders_cm REAL,chest_cm REAL,waist_cm " + "REAL,hips_cm REAL,left_arm_cm REAL,right_arm_cm REAL,left_forearm_cm REAL,right_forearm_cm " + "REAL,left_thigh_cm REAL,right_thigh_cm REAL,left_calf_cm REAL,right_calf_cm REAL,notes TEXT);" "INSERT INTO exercises VALUES(11,'ex_fixture_sets','Squat','squat','reps','sets',0);" "INSERT INTO exercises VALUES(12,'ex_fixture_run','Run','run','duration','continuous',3);" - "INSERT INTO sessions VALUES(21,'se_fixture','2026-09-08T06:00:00Z','2026-09-08T07:00:00Z','training','fixture session');" - "INSERT INTO session_exercises VALUES(31,'sxe_fixture_sets',21,11,'sets',0,0,'external',90,2,5,NULL,82.5,'eq_fixture_rack','sets note');" - "INSERT INTO session_exercises VALUES(32,'sxe_fixture_run',21,12,'continuous',3,1,'none',0,NULL,NULL,NULL,NULL,NULL,'run note');" + "INSERT INTO sessions " + "VALUES(21,'se_fixture','2026-09-08T06:00:00Z','2026-09-08T07:00:00Z','training','fixture " + "session');" + "INSERT INTO session_exercises " + "VALUES(31,'sxe_fixture_sets',21,11,'sets',0,0,'external',90,2,5,NULL,82.5,'eq_fixture_rack','" + "sets note');" + "INSERT INTO session_exercises " + "VALUES(32,'sxe_fixture_run',21,12,'continuous',3,1,'none',0,NULL,NULL,NULL,NULL,NULL,'run " + "note');" "INSERT INTO performed_sets VALUES(41,31,0,5,NULL,80.25);" "INSERT INTO performed_sets VALUES(42,31,1,4,NULL,82.5);" "INSERT INTO continuous_activity VALUES(51,32,1234,10.75,3.67);" - "INSERT INTO body_observations(id,observation_id,observed_at,session_row_id,body_weight_kg,waist_cm,notes) VALUES(61,'bo_fixture','2026-09-08T07:05:00Z',21,74.25,81.5,'body note');" + "INSERT INTO " + "body_observations(id,observation_id,observed_at,session_row_id,body_weight_kg,waist_cm,notes) " + "VALUES(61,'bo_fixture','2026-09-08T07:05:00Z',21,74.25,81.5,'body note');" "PRAGMA user_version=7;"; -static bool verify_preserved_values(const char *path) -{ +static bool verify_preserved_values(const char *path) { sqlite3 *connection = NULL; sqlite3_stmt *statement = NULL; CHECK(sqlite3_open_v2(path, &connection, SQLITE_OPEN_READONLY, NULL) == SQLITE_OK); - CHECK(sqlite3_prepare_v2(connection, - "SELECT s.session_id,se.entry_id,se.position,se.equipment_id,ps.position,ps.reps,ps.weight_kg " - "FROM sessions s JOIN session_exercises se ON se.session_row_id=s.id " - "JOIN performed_sets ps ON ps.session_exercise_row_id=se.id ORDER BY ps.position;", - -1, &statement, NULL) == SQLITE_OK); + CHECK( + sqlite3_prepare_v2( + connection, + "SELECT " + "s.session_id,se.entry_id,se.position,se.equipment_id,ps.position,ps.reps,ps.weight_kg " + "FROM sessions s JOIN session_exercises se ON se.session_row_id=s.id " + "JOIN performed_sets ps ON ps.session_exercise_row_id=se.id ORDER BY ps.position;", + -1, + &statement, + NULL) == SQLITE_OK); CHECK(sqlite3_step(statement) == SQLITE_ROW); CHECK(strcmp((const char *)sqlite3_column_text(statement, 0), "se_fixture") == 0); CHECK(strcmp((const char *)sqlite3_column_text(statement, 1), "sxe_fixture_sets") == 0); @@ -68,10 +95,16 @@ static bool verify_preserved_values(const char *path) CHECK(sqlite3_finalize(statement) == SQLITE_OK); CHECK(sqlite3_prepare_v2(connection, - "SELECT se.entry_id,se.position,ca.duration_seconds,ca.speed_kmh,ca.distance_km,bo.observation_id,bo.body_weight_kg,bo.waist_cm,bo.notes " - "FROM session_exercises se JOIN continuous_activity ca ON ca.session_exercise_row_id=se.id " - "JOIN sessions s ON s.id=se.session_row_id JOIN body_observations bo ON bo.session_row_id=s.id;", - -1, &statement, NULL) == SQLITE_OK); + "SELECT " + "se.entry_id,se.position,ca.duration_seconds,ca.speed_kmh,ca.distance_" + "km,bo.observation_id,bo.body_weight_kg,bo.waist_cm,bo.notes " + "FROM session_exercises se JOIN continuous_activity ca ON " + "ca.session_exercise_row_id=se.id " + "JOIN sessions s ON s.id=se.session_row_id JOIN body_observations bo " + "ON bo.session_row_id=s.id;", + -1, + &statement, + NULL) == SQLITE_OK); CHECK(sqlite3_step(statement) == SQLITE_ROW); CHECK(strcmp((const char *)sqlite3_column_text(statement, 0), "sxe_fixture_run") == 0); CHECK(sqlite3_column_int(statement, 1) == 1); @@ -88,12 +121,12 @@ static bool verify_preserved_values(const char *path) return true; } -static bool test_v7_migrates_and_current_reopens(void) -{ +static bool test_v7_migrates_and_current_reopens(void) { char path[] = "/tmp/trainlog-schema-v7-XXXXXX"; sqlite3 *raw = NULL; TrainlogDatabase *database = NULL; int version = 0; + char diagnostic[256]; int fd = mkstemp(path); CHECK(fd >= 0); @@ -101,22 +134,29 @@ static bool test_v7_migrates_and_current_reopens(void) CHECK(sqlite3_open(path, &raw) == SQLITE_OK); CHECK(sqlite3_exec(raw, V7_FIXTURE_SQL, NULL, NULL, NULL) == SQLITE_OK); CHECK(sqlite3_close(raw) == SQLITE_OK); - CHECK(trainlog_database_open(path, &database) == TRAINLOG_STATUS_OK); + if (trainlog_database_open_with_diagnostic(path, &database, diagnostic, sizeof(diagnostic)) != + TRAINLOG_STATUS_OK) { + fprintf(stderr, "migration diagnostic: %s\n", diagnostic); + return false; + } CHECK(trainlog_database_schema_version(database, &version) == TRAINLOG_STATUS_OK); CHECK(version == TRAINLOG_DATABASE_SCHEMA_VERSION); trainlog_database_close(database); CHECK(verify_preserved_values(path)); database = NULL; - CHECK(trainlog_database_open(path, &database) == TRAINLOG_STATUS_OK); + if (trainlog_database_open_with_diagnostic(path, &database, diagnostic, sizeof(diagnostic)) != + TRAINLOG_STATUS_OK) { + fprintf(stderr, "reopen diagnostic: %s\n", diagnostic); + return false; + } trainlog_database_close(database); CHECK(verify_preserved_values(path)); CHECK(unlink(path) == 0); return true; } -static bool test_v7_migration_sqlite_failure_has_diagnostic(void) -{ +static bool test_v7_migration_sqlite_failure_has_diagnostic(void) { char path[] = "/tmp/trainlog-schema-v7-failure-XXXXXX"; char diagnostic[256]; sqlite3 *raw = NULL; @@ -128,14 +168,18 @@ static bool test_v7_migration_sqlite_failure_has_diagnostic(void) CHECK(sqlite3_open(path, &raw) == SQLITE_OK); CHECK(sqlite3_exec(raw, V7_FIXTURE_SQL, NULL, NULL, NULL) == SQLITE_OK); /* Synthetic collision: only the v8 migration target is pre-created. */ - CHECK(sqlite3_exec(raw, "CREATE TABLE custom_equipment (equipment_id TEXT PRIMARY KEY);", - NULL, NULL, NULL) == SQLITE_OK); + CHECK(sqlite3_exec(raw, + "CREATE TABLE custom_equipment (equipment_id TEXT PRIMARY KEY);", + NULL, + NULL, + NULL) == SQLITE_OK); CHECK(sqlite3_close(raw) == SQLITE_OK); - CHECK(trainlog_database_open_with_diagnostic(path, &database, diagnostic, - sizeof(diagnostic)) == TRAINLOG_STATUS_DATABASE_ERROR); + CHECK(trainlog_database_open_with_diagnostic(path, &database, diagnostic, sizeof(diagnostic)) == + TRAINLOG_STATUS_DATABASE_ERROR); CHECK(database == NULL); - CHECK(strncmp(diagnostic, "migrate database to schema v24: SQLite rc=", - strlen("migrate database to schema v24: SQLite rc=")) == 0); + CHECK(strncmp(diagnostic, + "migrate database to schema v25: SQLite rc=", + strlen("migrate database to schema v25: SQLite rc=")) == 0); CHECK(strstr(diagnostic, "extended_rc=") != NULL); CHECK(strstr(diagnostic, "custom_equipment") != NULL); CHECK(strstr(diagnostic, "already exists") != NULL); @@ -143,8 +187,7 @@ static bool test_v7_migration_sqlite_failure_has_diagnostic(void) return true; } -static bool test_newer_schema_has_application_diagnostic(void) -{ +static bool test_newer_schema_has_application_diagnostic(void) { char path[] = "/tmp/trainlog-schema-v17-XXXXXX"; char diagnostic[256]; sqlite3 *raw = NULL; @@ -154,19 +197,18 @@ static bool test_newer_schema_has_application_diagnostic(void) CHECK(fd >= 0); CHECK(close(fd) == 0); CHECK(sqlite3_open(path, &raw) == SQLITE_OK); - CHECK(sqlite3_exec(raw, "PRAGMA user_version=25;", NULL, NULL, NULL) == SQLITE_OK); + CHECK(sqlite3_exec(raw, "PRAGMA user_version=26;", NULL, NULL, NULL) == SQLITE_OK); CHECK(sqlite3_close(raw) == SQLITE_OK); - CHECK(trainlog_database_open_with_diagnostic(path, &database, diagnostic, - sizeof(diagnostic)) == TRAINLOG_STATUS_SCHEMA_UNSUPPORTED); + CHECK(trainlog_database_open_with_diagnostic(path, &database, diagnostic, sizeof(diagnostic)) == + TRAINLOG_STATUS_SCHEMA_UNSUPPORTED); CHECK(database == NULL); - CHECK(strstr(diagnostic, "schema version 25 is newer") != NULL); + CHECK(strstr(diagnostic, "schema version 26 is newer") != NULL); CHECK(strstr(diagnostic, "SQLite") == NULL); CHECK(unlink(path) == 0); return true; } -static bool test_unrecognized_historic_schema_has_application_diagnostic(void) -{ +static bool test_unrecognized_historic_schema_has_application_diagnostic(void) { char path[] = "/tmp/trainlog-schema-invalid-XXXXXX"; char diagnostic[256]; sqlite3 *raw = NULL; @@ -178,8 +220,8 @@ static bool test_unrecognized_historic_schema_has_application_diagnostic(void) CHECK(sqlite3_open(path, &raw) == SQLITE_OK); CHECK(sqlite3_exec(raw, "PRAGMA user_version=-1;", NULL, NULL, NULL) == SQLITE_OK); CHECK(sqlite3_close(raw) == SQLITE_OK); - CHECK(trainlog_database_open_with_diagnostic(path, &database, diagnostic, - sizeof(diagnostic)) == TRAINLOG_STATUS_SCHEMA_UNSUPPORTED); + CHECK(trainlog_database_open_with_diagnostic(path, &database, diagnostic, sizeof(diagnostic)) == + TRAINLOG_STATUS_SCHEMA_UNSUPPORTED); CHECK(database == NULL); CHECK(strstr(diagnostic, "schema version -1 is unsupported") != NULL); CHECK(strstr(diagnostic, "SQLite") == NULL); @@ -187,12 +229,13 @@ static bool test_unrecognized_historic_schema_has_application_diagnostic(void) return true; } -int main(void) -{ +int main(void) { if (!test_v7_migrates_and_current_reopens() || !test_v7_migration_sqlite_failure_has_diagnostic() || !test_newer_schema_has_application_diagnostic() || - !test_unrecognized_historic_schema_has_application_diagnostic()) return 1; + !test_unrecognized_historic_schema_has_application_diagnostic()) { + return 1; + } (void)printf("PASS schema_v7_migration\n"); return 0; } diff --git a/tui/tests/test_schema_v9_migration.c b/tui/tests/test_schema_v9_migration.c index d0a530b..b32973a 100644 --- a/tui/tests/test_schema_v9_migration.c +++ b/tui/tests/test_schema_v9_migration.c @@ -13,13 +13,13 @@ #include "trainlog/database.h" -#define CHECK(condition) do { \ - if (!(condition)) { \ - (void)fprintf(stderr, "CHECK failed at %s:%d: %s\n", \ - __FILE__, __LINE__, #condition); \ - return false; \ - } \ -} while (0) +#define CHECK(condition) \ + do { \ + if (!(condition)) { \ + (void)fprintf(stderr, "CHECK failed at %s:%d: %s\n", __FILE__, __LINE__, #condition); \ + return false; \ + } \ + } while (0) static const char *const V9_FIXTURE_SQL = "PRAGMA foreign_keys=ON;" @@ -36,7 +36,8 @@ static const char *const V9_FIXTURE_SQL = "target_reps INTEGER,target_duration_seconds INTEGER,target_weight_kg REAL," "equipment_id TEXT,notes TEXT,UNIQUE(session_row_id,position));" "CREATE TABLE performed_sets(id INTEGER PRIMARY KEY,session_exercise_row_id INTEGER NOT NULL " - "REFERENCES session_exercises(id) ON DELETE CASCADE,position INTEGER NOT NULL CHECK(position>=0)," + "REFERENCES session_exercises(id) ON DELETE CASCADE,position INTEGER NOT NULL " + "CHECK(position>=0)," "reps INTEGER CHECK(reps>=0),duration_seconds INTEGER CHECK(duration_seconds>0)," "weight_kg REAL CHECK(weight_kg>0.0),UNIQUE(session_exercise_row_id,position)," "CHECK((reps IS NOT NULL AND duration_seconds IS NULL) OR " @@ -46,13 +47,13 @@ static const char *const V9_FIXTURE_SQL = "CHECK(max_weight_kg>0.0));" "INSERT INTO exercises VALUES(7,'ex_fixture','Fixture','fixture','reps','sets',0);" "INSERT INTO sessions VALUES(11,'se_fixture','2026-09-09T10:00:00+02:00',NULL,'training',NULL);" - "INSERT INTO session_exercises VALUES(13,'sxe_fixture',11,7,'sets',0,4,'none',0,NULL,NULL,NULL,NULL,NULL,NULL);" + "INSERT INTO session_exercises " + "VALUES(13,'sxe_fixture',11,7,'sets',0,4,'none',0,NULL,NULL,NULL,NULL,NULL,NULL);" "INSERT INTO performed_sets VALUES(17,13,2,8,NULL,32.5);" "INSERT INTO performed_sets VALUES(19,13,5,7,NULL,NULL);" "PRAGMA user_version=9;"; -static bool scalar_text_is(sqlite3 *db, const char *sql, const char *expected) -{ +static bool scalar_text_is(sqlite3 *db, const char *sql, const char *expected) { sqlite3_stmt *statement = NULL; bool matches = false; if (sqlite3_prepare_v2(db, sql, -1, &statement, NULL) == SQLITE_OK && @@ -64,8 +65,7 @@ static bool scalar_text_is(sqlite3 *db, const char *sql, const char *expected) return matches; } -static bool test_v9_to_current_is_lossless(void) -{ +static bool test_v9_to_current_is_lossless(void) { char path[] = "/tmp/trainlog-schema-v9-current-XXXXXX"; sqlite3 *raw = NULL; sqlite3_stmt *rows = NULL; @@ -96,9 +96,13 @@ static bool test_v9_to_current_is_lossless(void) CHECK(sqlite3_step(rows) == SQLITE_DONE); CHECK(sqlite3_finalize(rows) == SQLITE_OK); rows = NULL; - CHECK(sqlite3_prepare_v2(raw, - "SELECT id,session_exercise_row_id,position,reps,duration_seconds,weight_kg " - "FROM performed_sets ORDER BY position;", -1, &rows, NULL) == SQLITE_OK); + CHECK(sqlite3_prepare_v2( + raw, + "SELECT id,session_exercise_row_id,position,reps,duration_seconds,weight_kg " + "FROM performed_sets ORDER BY position;", + -1, + &rows, + NULL) == SQLITE_OK); CHECK(sqlite3_step(rows) == SQLITE_ROW); CHECK(sqlite3_column_int64(rows, 0) == 17); CHECK(sqlite3_column_int64(rows, 1) == 13); @@ -113,25 +117,24 @@ static bool test_v9_to_current_is_lossless(void) CHECK(sqlite3_finalize(rows) == SQLITE_OK); rows = NULL; - CHECK(sqlite3_exec(raw, - "INSERT INTO performed_sets VALUES(23,13,6,6,NULL,0.0);", - NULL, NULL, NULL) == SQLITE_OK); - CHECK(sqlite3_exec(raw, - "INSERT INTO performed_sets VALUES(29,13,7,5,NULL,-0.5);", - NULL, NULL, NULL) == SQLITE_CONSTRAINT); - CHECK(sqlite3_exec(raw, - "INSERT INTO performed_sets VALUES(31,13,8,4,3,NULL);", - NULL, NULL, NULL) == SQLITE_CONSTRAINT); - CHECK(sqlite3_exec(raw, - "INSERT INTO performed_sets VALUES(37,999,9,4,NULL,NULL);", - NULL, NULL, NULL) == SQLITE_CONSTRAINT); + CHECK(sqlite3_exec( + raw, "INSERT INTO performed_sets VALUES(23,13,6,6,NULL,0.0);", NULL, NULL, NULL) == + SQLITE_OK); + CHECK(sqlite3_exec( + raw, "INSERT INTO performed_sets VALUES(29,13,7,5,NULL,-0.5);", NULL, NULL, NULL) == + SQLITE_CONSTRAINT); + CHECK(sqlite3_exec( + raw, "INSERT INTO performed_sets VALUES(31,13,8,4,3,NULL);", NULL, NULL, NULL) == + SQLITE_CONSTRAINT); + CHECK(sqlite3_exec( + raw, "INSERT INTO performed_sets VALUES(37,999,9,4,NULL,NULL);", NULL, NULL, NULL) == + SQLITE_CONSTRAINT); CHECK(sqlite3_close(raw) == SQLITE_OK); CHECK(unlink(path) == 0); return true; } -static bool test_v9_to_v10_failure_rolls_back(void) -{ +static bool test_v9_to_v10_failure_rolls_back(void) { char path[] = "/tmp/trainlog-schema-v9-v10-failure-XXXXXX"; char diagnostic[256]; sqlite3 *raw = NULL; @@ -142,29 +145,28 @@ static bool test_v9_to_v10_failure_rolls_back(void) CHECK(close(fd) == 0); CHECK(sqlite3_open(path, &raw) == SQLITE_OK); CHECK(sqlite3_exec(raw, V9_FIXTURE_SQL, NULL, NULL, NULL) == SQLITE_OK); - CHECK(sqlite3_exec(raw, - "CREATE TABLE performed_sets_v9(collision INTEGER);", - NULL, NULL, NULL) == SQLITE_OK); + CHECK( + sqlite3_exec(raw, "CREATE TABLE performed_sets_v9(collision INTEGER);", NULL, NULL, NULL) == + SQLITE_OK); CHECK(sqlite3_close(raw) == SQLITE_OK); raw = NULL; - CHECK(trainlog_database_open_with_diagnostic(path, &database, diagnostic, - sizeof(diagnostic)) == TRAINLOG_STATUS_DATABASE_ERROR); + CHECK(trainlog_database_open_with_diagnostic(path, &database, diagnostic, sizeof(diagnostic)) == + TRAINLOG_STATUS_DATABASE_ERROR); CHECK(database == NULL); - CHECK(strstr(diagnostic, "migrate database to schema v24") != NULL); + CHECK(strstr(diagnostic, "migrate database to schema v25") != NULL); CHECK(strstr(diagnostic, "performed_sets_v9") != NULL); CHECK(sqlite3_open(path, &raw) == SQLITE_OK); CHECK(scalar_text_is(raw, "PRAGMA integrity_check;", "ok")); { sqlite3_stmt *statement = NULL; - CHECK(sqlite3_prepare_v2(raw, "PRAGMA user_version;", -1, - &statement, NULL) == SQLITE_OK); + CHECK(sqlite3_prepare_v2(raw, "PRAGMA user_version;", -1, &statement, NULL) == SQLITE_OK); CHECK(sqlite3_step(statement) == SQLITE_ROW); CHECK(sqlite3_column_int(statement, 0) == 9); CHECK(sqlite3_finalize(statement) == SQLITE_OK); - CHECK(sqlite3_prepare_v2(raw, "SELECT COUNT(*) FROM performed_sets;", - -1, &statement, NULL) == SQLITE_OK); + CHECK(sqlite3_prepare_v2( + raw, "SELECT COUNT(*) FROM performed_sets;", -1, &statement, NULL) == SQLITE_OK); CHECK(sqlite3_step(statement) == SQLITE_ROW); CHECK(sqlite3_column_int(statement, 0) == 2); CHECK(sqlite3_finalize(statement) == SQLITE_OK); @@ -174,10 +176,10 @@ static bool test_v9_to_v10_failure_rolls_back(void) return true; } -int main(void) -{ - if (!test_v9_to_current_is_lossless() || - !test_v9_to_v10_failure_rolls_back()) return 1; +int main(void) { + if (!test_v9_to_current_is_lossless() || !test_v9_to_v10_failure_rolls_back()) { + return 1; + } (void)printf("PASS schema_v9_migration\n"); return 0; }