fix(review): address PR #94 review #37/#38 reviewer comments

Parser (src/database_flatfile_parser.c):
- ff_jid_to_dir: drop GString+step1, in-place mutate str_replace result
- ff_ensure_dir: g_file_test(G_FILE_TEST_IS_SYMLINK) instead of g_lstat
- ff_unescape_*: early-continue for non-escape branch (less nesting)
- ff_readline: strdup("") instead of malloc(1)+'\0' for skip-line return
- ff_write_line: auto_char safe_msg + single fprintf (drop GString full_line)
- ff_parse_line: early return on missing closing bracket;
  extract metadata-parts loop into _ff_parse_meta_parts helper

Verify (src/database_flatfile_verify.c):
- Split monolithic ff_verify_integrity into _collect_contact_dirs,
  _check_permissions, _first_pass, _lmc_pass, _verify_contact_dir
- _issue_new constructor; g_new0 throughout
- Separate seen_stanza_ids and seen_archive_ids tables (no cross-kind
  duplicate confusion)
- ff_skip_bom in second pass (drop manual fgetc x3)
- log_debug when skipping a contact dir without history.log
- Doc comment on ff_verify_integrity in header

SQLite (src/database_sqlite.c):
- Doc block on ChatLogs schema (replaces_db_id <-> replaced_by_db_id
  invariant maintained by AFTER INSERT trigger)
- log_error when _get_db_filename returns NULL during init

Flatfile dispatch (src/database_flatfile.c):
- Replace if/else with is_outgoing ternary for log-path contact pick

Tests:
- 6 new invalid-date parser tests: empty / partial ISO / garbage /
  impossible calendar / negative year / far future timestamp
- New functional test message_db_history_multi_resource verifying
  same-JID-different-resource history rehydration preserves resources
- test_database_stress.c LMC chain loop: auto_char buf, drop redundant
  comment/empty pre-filter (ff_parse_line handles those)
This commit is contained in:
2026-04-30 10:53:03 +03:00
parent 7ba7e53291
commit 8868d58920
12 changed files with 621 additions and 400 deletions

View File

@@ -128,6 +128,8 @@ _sqlite_init(ProfAccount* account)
auto_char char* filename = _get_db_filename(account);
if (!filename) {
log_error("Error initializing SQLite database: could not derive chatlog.db path for account '%s'.",
account && account->jid ? account->jid : "(unknown)");
sqlite3_shutdown();
return FALSE;
}
@@ -147,6 +149,28 @@ _sqlite_init(ProfAccount* account)
return TRUE;
}
// ChatLogs schema overview:
// id AUTOINCREMENT primary key. Used internally as the
// anchor for LMC (Last Message Correction) chains.
// from_jid/to_jid Bare JIDs (NOT NULL). Used for window resolution.
// from_resource Resource of the sender (NULLable for legacy rows).
// to_resource Resource of the recipient (NULLable).
// message Plaintext after decryption / format normalisation.
// timestamp ISO-8601 (UTC). Indexed for range queries.
// type "chat" | "muc" | "mucpm" (mirrors prof_msg_type_t).
// stanza_id XEP-0359 unique-and-stable id, used for dedup +
// LMC sender lookup.
// archive_id MAM (XEP-0313) archive id; dedupes rebroadcast.
// encryption "none" | "omemo" | "otr" | "pgp" | "ox"
// (mirrors prof_enc_t).
// marked_read 0/1 — for unread-message badge bookkeeping.
// replace_id XEP-0308 message-correction stanza-id of the
// original message this row replaces.
// replaces_db_id Local FK back-link: id of the row this row
// corrects. Set on insert when replace_id resolves.
// replaced_by_db_id Inverse FK: id of the most recent correction.
// Maintained by the AFTER INSERT trigger below so
// readers can follow the chain forward in O(1).
const char* query = "CREATE TABLE IF NOT EXISTS `ChatLogs` ("
"`id` INTEGER PRIMARY KEY AUTOINCREMENT, "
"`from_jid` TEXT NOT NULL, "