nostrdb

an unfairly fast embedded nostr database backed by lmdb
git clone git://jb55.com/nostrdb
Log | Files | Refs | Submodules | README | LICENSE

commit 4c179987772f6d4d47b972b48391f1a99e1c9b5e
parent e90d933c6730b37bf420f1a57344a6cdc15ff03b
Author: William Casarin <jb55@jb55.com>
Date:   Wed, 29 Oct 2025 16:21:44 -0700

migration: implement ndb_count_reactions

also verify it matches the online counting

Diffstat:
Msrc/nostrdb.c | 113++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------
Mtest.c | 12++++++++++++
2 files changed, 95 insertions(+), 30 deletions(-)

diff --git a/src/nostrdb.c b/src/nostrdb.c @@ -2002,14 +2002,94 @@ cleanup: int ndb_cursor_start(MDB_cursor *cur, MDB_val *k, MDB_val *v); +// find the last id tag in a note (e, p, etc) +static unsigned char *ndb_note_last_id_tag(struct ndb_note *note, char type) +{ + unsigned char *last = NULL; + struct ndb_iterator iter; + struct ndb_str str; + + // get the liked event id (last id) + ndb_tags_iterate_start(note, &iter); + + while (ndb_tags_iterate_next(&iter)) { + if (iter.tag->count < 2) + continue; + + str = ndb_tag_str(note, iter.tag, 0); + + // assign liked to the last e tag + if (str.flag == NDB_PACKED_STR && str.str[0] == type) { + str = ndb_tag_str(note, iter.tag, 1); + if (str.flag == NDB_PACKED_ID) + last = str.id; + } + } + + return last; +} + + static int ndb_count_replies(struct ndb_txn *txn, const unsigned char *note_id, uint16_t *direct_replies, uint32_t *thread_replies) { return 1; } /* count all of the reactions for a note */ -static int ndb_count_reactions(struct ndb_txn *txn, const unsigned char *note_id, uint32_t *count) +int ndb_count_reactions(struct ndb_txn *txn, const unsigned char *note_id, uint32_t *count) { + MDB_val k, v; + MDB_cursor *cur; + MDB_dbi db; + + int rc; + uint64_t note_key; + size_t size; + struct ndb_note *note; + unsigned char *keybuf, *last_id; + char buffer[41]; /* 1 + 32 + 8 */ + *count = 0; + + db = txn->lmdb->dbs[NDB_DB_NOTE_TAGS]; + if ((rc = mdb_cursor_open(txn->mdb_txn, db, &cur))) { + fprintf(stderr, "ndb_count_reactions: mdb_cursor_open failed, error %d\n", rc); + return 0; + } + + buffer[0] = 'e'; + memcpy(&buffer[1], note_id, 32); + memset(&buffer[33], 0x00, 8); + + k.mv_data = buffer; + k.mv_size = sizeof(buffer); + v.mv_data = NULL; + v.mv_size = 0; + + if (mdb_cursor_get(cur, &k, &v, MDB_SET_RANGE)) + goto cleanup; + + do { + keybuf = (unsigned char *)k.mv_data; + note_key = *((uint64_t*)v.mv_data); + if (k.mv_size < sizeof(buffer)) + break; + if (keybuf[0] != 'e') + break; + if (memcmp(&keybuf[1], note_id, 32)) + break; + if (!(note = ndb_get_note_by_key(txn, note_key, &size))) + continue; + if (ndb_note_kind(note) != 7) + continue; + if (!(last_id = ndb_note_last_id_tag(note, 'e'))) + continue; + if (memcmp(last_id, note_id, 32)) + continue; + (*count)++; + } while (mdb_cursor_get(cur, &k, &v, MDB_NEXT) == 0); + +cleanup: + mdb_cursor_close(cur); return 1; } @@ -2108,7 +2188,7 @@ static void ndb_note_meta_builder_count_reactions(struct ndb_txn *txn, struct nd // /* switch from flatbuffer stats to custom v2 */ -static int ndb_migrate_reaction_stats(struct ndb_txn *txn) +static int ndb_migrate_metadata(struct ndb_txn *txn) { MDB_val k, k2, v, v2; MDB_cursor *cur; @@ -2491,7 +2571,7 @@ static struct ndb_migration MIGRATIONS[] = { { .fn = ndb_migrate_lower_user_search_indices }, { .fn = ndb_migrate_utf8_profile_names }, { .fn = ndb_migrate_profile_indices }, - //{ .fn = ndb_migrate_reaction_stats }, + //{ .fn = ndb_migrate_metadata }, }; @@ -3477,33 +3557,6 @@ static int ndb_write_profile(struct ndb_txn *txn, return 1; } -// find the last id tag in a note (e, p, etc) -static unsigned char *ndb_note_last_id_tag(struct ndb_note *note, char type) -{ - unsigned char *last = NULL; - struct ndb_iterator iter; - struct ndb_str str; - - // get the liked event id (last id) - ndb_tags_iterate_start(note, &iter); - - while (ndb_tags_iterate_next(&iter)) { - if (iter.tag->count < 2) - continue; - - str = ndb_tag_str(note, iter.tag, 0); - - // assign liked to the last e tag - if (str.flag == NDB_PACKED_STR && str.str[0] == type) { - str = ndb_tag_str(note, iter.tag, 1); - if (str.flag == NDB_PACKED_ID) - last = str.id; - } - } - - return last; -} - int ndb_set_note_meta(struct ndb *ndb, const unsigned char *id, struct ndb_note_meta *meta) { struct ndb_writer_msg msg; diff --git a/test.c b/test.c @@ -30,6 +30,8 @@ static void delete_test_db() { unlink(TEST_DIR "/data.lock"); } +int ndb_count_reactions(struct ndb_txn *txn, const unsigned char *note_id, uint32_t *count); + static void db_load_events(struct ndb *ndb, const char *filename) { size_t filesize; @@ -132,7 +134,17 @@ static void test_count_metadata() assert(reactions > 0); assert(total_reactions == reactions); + + ndb_end_query(&txn); + + ndb_begin_query(ndb, &txn); + /* this is used in the migration code, + * let's make sure it matches the online logic */ + ndb_count_reactions(&txn, id, &reactions); + printf("\t# after-counted reactions %d\n", reactions); + assert(reactions == total_reactions); ndb_end_query(&txn); + ndb_destroy(ndb); delete_test_db();