db_sync: LMDB→SQLite, multi-instance, etcp_api, chatgui integration

- src/db_sync: LMDB replaced with SQLite (separate tables per instance: db_sync_name_id)
  TTL index (author, timestamp), WAL mode, per-instance peers/timers
  opaque DB_SYNC_INSTANCE* handle, insert callback, select API
  new fields: author_signature, delivered_peers, delivery_chain
  transport: etcp_api direct P2P (was etcp_router)

- src/db_sync.h: user-oriented API docs, multi-instance lifecycle

- lib/Makefile.am: liblmdb sources removed
- lib/sqlite3.c+h: SQLite amalgamation added

- topo_node: LMDB→SQLite (src/topo_node_sqlite.c moved from chatgui)

- chatgui: real db_sync.c replaces db_sync_stub.c
  chat_core: db_sync_insert_signed + dual-write insert callback
  utun_node: db_sync_init/destroy replaces chat_sync_init/destroy
  CMakeLists: stubs removed, real db_sync.c added
This commit is contained in:
Evgeny committed 2026-07-14 14:17:43 +03:00
1 parent 37417ba49d
commit f617dcd68e
21 files changed
+284328 -1174

No files matched your search

+32 -28
View File
@@ -36,6 +36,8 @@
static struct UTUN_INSTANCE* inst_a = NULL;
static struct UTUN_INSTANCE* inst_b = NULL;
static struct DB_SYNC_INSTANCE* si_a = NULL;
static struct DB_SYNC_INSTANCE* si_b = NULL;
static struct UASYNC* ua = NULL;
static int test_phase = 0; // 0=running, 1=success, 2=failure
static void* timeout_id = NULL;
@@ -65,12 +67,10 @@ static int create_temp_configs(void) {
snprintf(config_a, sizeof(config_a), "%s/a.conf", temp_dir);
snprintf(config_b, sizeof(config_b), "%s/b.conf", temp_dir);
// Create LMDB directories (parent first, then child)
// Create SQLite db directories (parent dir for sync file)
char db_path[320];
snprintf(db_path, sizeof(db_path), "%s/db_a", temp_dir); utun_mkdir(db_path, 0755);
snprintf(db_path, sizeof(db_path), "%s/db_a/sync", temp_dir); utun_mkdir(db_path, 0755);
snprintf(db_path, sizeof(db_path), "%s/db_b", temp_dir); utun_mkdir(db_path, 0755);
snprintf(db_path, sizeof(db_path), "%s/db_b/sync", temp_dir); utun_mkdir(db_path, 0755);
if (write_file(config_a,
"[global]\n"
@@ -120,16 +120,14 @@ static int create_temp_configs(void) {
static void cleanup_temp_configs(void) {
unlink(config_a); unlink(config_b);
char db_a[320], db_b[320];
snprintf(db_a, sizeof(db_a), "%s/db_a/sync/data.mdb", temp_dir);
snprintf(db_b, sizeof(db_b), "%s/db_b/sync/data.mdb", temp_dir);
unlink(db_a); unlink(db_b);
snprintf(db_a, sizeof(db_a), "%s/db_a/sync/lock.mdb", temp_dir);
snprintf(db_b, sizeof(db_b), "%s/db_b/sync/lock.mdb", temp_dir);
unlink(db_a); unlink(db_b);
char pa[320]; snprintf(pa, sizeof(pa), "%s/db_a/sync", temp_dir); test_rmdir(pa);
char pa[320];
snprintf(pa, sizeof(pa), "%s/db_a/sync", temp_dir); unlink(pa);
snprintf(pa, sizeof(pa), "%s/db_a/sync-wal", temp_dir); unlink(pa);
snprintf(pa, sizeof(pa), "%s/db_a/sync-shm", temp_dir); unlink(pa);
snprintf(pa, sizeof(pa), "%s/db_b/sync", temp_dir); unlink(pa);
snprintf(pa, sizeof(pa), "%s/db_b/sync-wal", temp_dir); unlink(pa);
snprintf(pa, sizeof(pa), "%s/db_b/sync-shm", temp_dir); unlink(pa);
snprintf(pa, sizeof(pa), "%s/db_a", temp_dir); test_rmdir(pa);
snprintf(pa, sizeof(pa), "%s/db_b/sync", temp_dir); test_rmdir(pa);
snprintf(pa, sizeof(pa), "%s/db_b", temp_dir); test_rmdir(pa);
test_rmdir(temp_dir);
}
@@ -159,33 +157,33 @@ static int cond_links_init(void) {
}
static int cond_count_a(uint32_t expected) {
if (!inst_a) return 0;
return db_sync_count(inst_a) == expected;
if (!si_a) return 0;
return db_sync_count(si_a) == expected;
}
static int cond_count_b(uint32_t expected) {
if (!inst_b) return 0;
return db_sync_count(inst_b) == expected;
if (!si_b) return 0;
return db_sync_count(si_b) == expected;
}
static uint32_t count_a_target, count_b_target;
static int _cond_count_a(void) { return cond_count_a(count_a_target); }
static int _cond_count_b(void) { return cond_count_b(count_b_target); }
static int insert_many(struct UTUN_INSTANCE* inst, int start, int count) {
static int insert_many(struct DB_SYNC_INSTANCE* si, int start, int count) {
char buf[128];
for (int i = start; i < start + count && test_phase == 0; i++) {
snprintf(buf, sizeof(buf), "{\"idx\":%d,\"val\":\"data_%d\",\"pad\":\"%s\"}", i, i,
"xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
int ret = db_sync_insert(inst, buf);
int ret = db_sync_insert(si, buf);
if (ret < 0) { fprintf(stderr, "insert_many failed at %d ret=%d\n", i, ret); return -1; }
}
return 0;
}
static int insert_many_batch(struct UTUN_INSTANCE* inst, int start, int count) {
static int insert_many_batch(struct DB_SYNC_INSTANCE* si, int start, int count) {
char buf[256];
for (int i = start; i < start + count && test_phase == 0; i++) {
snprintf(buf, sizeof(buf), "{\"n\":%d,\"text\":\"record_number_%d_abcdefghijklmnopqrstuvwxyz\"}", i, i);
db_sync_insert(inst, buf);
db_sync_insert(si, buf);
}
return 0;
}
@@ -211,28 +209,34 @@ int main(void) {
fprintf(stderr, "instance init failed\n"); cleanup_temp_configs(); return 1;
}
si_a = db_sync_instance_add(inst_a, "test", 1);
si_b = db_sync_instance_add(inst_b, "test", 1);
if (!si_a || !si_b) { fprintf(stderr, "db_sync_instance_add failed\n"); return 1; }
timeout_id = uasync_set_timeout(ua, TEST_TIMEOUT_TB, NULL, test_timeout, "test_timeout");
// ===== Phase 1: базовый CRUD =====
printf("Phase 1: basic CRUD...\n");
if (db_sync_count(inst_a) != 0) { fprintf(stderr, "FAIL: initial count not 0 (got %u)\n", db_sync_count(inst_a)); test_phase=2; }
if (db_sync_count(si_a) != 0) { fprintf(stderr, "FAIL: initial count not 0 (got %u)\n", db_sync_count(si_a)); test_phase=2; }
if (db_sync_insert(inst_a, "{\"key\":\"val1\"}") != 0) { fprintf(stderr,"FAIL: insert 1\n"); test_phase=2; }
if (db_sync_insert(inst_a, "{\"key\":\"val2\"}") != 0) { fprintf(stderr,"FAIL: insert 2\n"); test_phase=2; }
if (db_sync_insert(inst_a, "{\"key\":\"val3\"}") != 0) { fprintf(stderr,"FAIL: insert 3\n"); test_phase=2; }
if (db_sync_count(inst_a) != 3) { fprintf(stderr,"FAIL: count not 3 (got %u)\n", db_sync_count(inst_a)); test_phase=2; }
if (db_sync_insert(si_a, "{\"key\":\"val1\"}") != 0) { fprintf(stderr,"FAIL: insert 1\n"); test_phase=2; }
if (db_sync_insert(si_a, "{\"key\":\"val2\"}") != 0) { fprintf(stderr,"FAIL: insert 2\n"); test_phase=2; }
if (db_sync_insert(si_a, "{\"key\":\"val3\"}") != 0) { fprintf(stderr,"FAIL: insert 3\n"); test_phase=2; }
if (db_sync_count(si_a) != 3) { fprintf(stderr,"FAIL: count not 3 (got %u)\n", db_sync_count(si_a)); test_phase=2; }
// Dedup by (timestamp,datahash): same content at different time = new record
if (db_sync_insert(inst_a, "{\"key\":\"val4\"}") != 0) { fprintf(stderr,"FAIL: insert 4\n"); test_phase=2; }
if (db_sync_count(inst_a) != 4) { fprintf(stderr,"FAIL: count not 4 (got %u)\n", db_sync_count(inst_a)); test_phase=2; }
if (db_sync_insert(si_a, "{\"key\":\"val4\"}") != 0) { fprintf(stderr,"FAIL: insert 4\n"); test_phase=2; }
if (db_sync_count(si_a) != 4) { fprintf(stderr,"FAIL: count not 4 (got %u)\n", db_sync_count(si_a)); test_phase=2; }
if (test_phase == 0) printf("Phase 1: PASS (count=4)\n");
// ===== Phase 2: initial sync A↔B =====
printf("Phase 2: initial sync...\n");
if (test_phase == 0) { count_b_target = 4; if (!wait_for("B count=4", _cond_count_b, PHASE_TIMEOUT_TB)) test_phase=2; }
if (test_phase == 0) printf("Phase 2: PASS (B synced %u records)\n", db_sync_count(inst_b));
if (test_phase == 0) printf("Phase 2: PASS (B synced %u records)\n", db_sync_count(si_b));
// ===== Cleanup =====
if (timeout_id && ua) { uasync_cancel_timeout(ua, timeout_id); timeout_id = NULL; }
if (si_a) { db_sync_instance_remove(si_a); si_a = NULL; }
if (si_b) { db_sync_instance_remove(si_b); si_b = NULL; }
if (inst_a) { inst_a->running = 0; utun_instance_destroy(inst_a); inst_a = NULL; }
if (inst_b) { inst_b->running = 0; utun_instance_destroy(inst_b); inst_b = NULL; }
if (ua) { uasync_destroy(ua, 0); ua = NULL; }