nostrdb

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

commit b81490cda1c6c18459e515c130e88b331dc9fea9
parent 4c179987772f6d4d47b972b48391f1a99e1c9b5e
Author: William Casarin <jb55@jb55.com>
Date:   Thu, 30 Oct 2025 13:31:59 -0700

migration: reply counter + test

Signed-off-by: William Casarin <jb55@jb55.com>

Diffstat:
Msrc/nostrdb.c | 219+++++++++++++++++++++++++++++++++++++++++++++++++++----------------------------
Mtest.c | 23+++++++++++++++--------
2 files changed, 158 insertions(+), 84 deletions(-)

diff --git a/src/nostrdb.c b/src/nostrdb.c @@ -2029,9 +2029,151 @@ static unsigned char *ndb_note_last_id_tag(struct ndb_note *note, char type) return last; } +/* get reply information from a note */ +static void ndb_parse_reply(struct ndb_note *note, struct ndb_note_reply *note_reply) +{ + unsigned char *root, *reply, *mention, *id; + const char *marker; + struct ndb_iterator iter; + struct ndb_str str; + uint16_t count; + int any_marker, first; + + any_marker = 0; + first = 1; + root = NULL; + reply = NULL; + mention = NULL; + + // get the liked event id (last id) + ndb_tags_iterate_start(note, &iter); + while (ndb_tags_iterate_next(&iter)) { + if (root && reply && mention) + break; + + marker = NULL; + count = ndb_tag_count(iter.tag); + + if (count < 2) + continue; + + str = ndb_tag_str(note, iter.tag, 0); + if (!(str.flag == NDB_PACKED_STR && str.str[0] == 'e')) + continue; + + str = ndb_tag_str(note, iter.tag, 1); + if (str.flag != NDB_PACKED_ID) + continue; + id = str.id; + + /* if we have the marker, assign it */ + if (count >= 4) { + str = ndb_tag_str(note, iter.tag, 3); + if (str.flag == NDB_PACKED_STR) + marker = str.str; + } -static int ndb_count_replies(struct ndb_txn *txn, const unsigned char *note_id, uint16_t *direct_replies, uint32_t *thread_replies) + if (marker) { + any_marker = true; + if (!strcmp(marker, "root")) + root = id; + else if (!strcmp(marker, "reply")) + reply = id; + else if (!strcmp(marker, "mention")) + mention = id; + } else if (!any_marker && first) { + root = id; + first = 0; + } else if (!any_marker && !reply) { + reply = id; + } + } + + note_reply->reply = reply; + note_reply->root = root; + note_reply->mention = mention; +} + +static int ndb_is_reply_to_root(struct ndb_note_reply *reply) { + if (reply->root && !reply->reply) + return 0; + else if (reply->root && reply->reply) + return !memcmp(reply->root, reply->reply, 32); + else + return 0; +} + + +int ndb_count_replies(struct ndb_txn *txn, const unsigned char *note_id, uint16_t *direct_replies, uint32_t *thread_replies) +{ + 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, *reply_id; + struct ndb_note_reply reply; + char buffer[41]; /* 1 + 32 + 8 */ + + *direct_replies = 0; + *thread_replies = 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) != 1) + continue; + + ndb_parse_reply(note, &reply); + + if (ndb_is_reply_to_root(&reply)) { + reply_id = reply.root; + } else { + reply_id = reply.reply; + } + + if (reply_id && !memcmp(reply_id, note_id, 32)) { + (*direct_replies)++; + } + + if (reply.root && !memcmp(reply.root, note_id, 32)) { + (*thread_replies)++; + } + + } while (mdb_cursor_get(cur, &k, &v, MDB_NEXT) == 0); + +cleanup: + mdb_cursor_close(cur); return 1; } @@ -5632,81 +5774,6 @@ static unsigned char *ndb_note_first_tag_id(struct ndb_note *note, char tag) return NULL; } -/* get reply information from a note */ -static void ndb_parse_reply(struct ndb_note *note, struct ndb_note_reply *note_reply) -{ - unsigned char *root, *reply, *mention, *id; - const char *marker; - struct ndb_iterator iter; - struct ndb_str str; - uint16_t count; - int any_marker, first; - - any_marker = 0; - first = 1; - root = NULL; - reply = NULL; - mention = NULL; - - // get the liked event id (last id) - ndb_tags_iterate_start(note, &iter); - while (ndb_tags_iterate_next(&iter)) { - if (root && reply && mention) - break; - - marker = NULL; - count = ndb_tag_count(iter.tag); - - if (count < 2) - continue; - - str = ndb_tag_str(note, iter.tag, 0); - if (!(str.flag == NDB_PACKED_STR && str.str[0] == 'e')) - continue; - - str = ndb_tag_str(note, iter.tag, 1); - if (str.flag != NDB_PACKED_ID) - continue; - id = str.id; - - /* if we have the marker, assign it */ - if (count >= 4) { - str = ndb_tag_str(note, iter.tag, 3); - if (str.flag == NDB_PACKED_STR) - marker = str.str; - } - - if (marker) { - any_marker = true; - if (!strcmp(marker, "root")) - root = id; - else if (!strcmp(marker, "reply")) - reply = id; - else if (!strcmp(marker, "mention")) - mention = id; - } else if (!any_marker && first) { - root = id; - first = 0; - } else if (!any_marker && !reply) { - reply = id; - } - } - - note_reply->reply = reply; - note_reply->root = root; - note_reply->mention = mention; -} - -static int ndb_is_reply_to_root(struct ndb_note_reply *reply) -{ - if (reply->root && !reply->reply) - return 0; - else if (reply->root && reply->reply) - return !memcmp(reply->root, reply->reply, 32); - else - return 0; -} - static int ndb_increment_quote_metadata( struct ndb_txn *txn, unsigned char *quoted_note_id, diff --git a/test.c b/test.c @@ -31,6 +31,7 @@ static void delete_test_db() { } int ndb_count_reactions(struct ndb_txn *txn, const unsigned char *note_id, uint32_t *count); +int ndb_count_replies(struct ndb_txn *txn, const unsigned char *note_id, uint16_t *direct_replies, uint32_t *thread_replies); static void db_load_events(struct ndb *ndb, const char *filename) { @@ -78,8 +79,8 @@ static void test_count_metadata() struct ndb_note_meta *meta; //struct ndb_note_meta_entry *counts; struct ndb_note_meta_entry *entry; - uint16_t count; - uint32_t total_reactions, reactions, replies; + uint16_t count, direct_replies[2]; + uint32_t total_reactions, reactions, thread_replies[2]; int i; reactions = 0; @@ -118,13 +119,13 @@ static void test_count_metadata() assert(entry); assert(*ndb_note_meta_counts_quotes(entry) == 2); - replies = *ndb_note_meta_counts_thread_replies(entry); - printf("\t# thread replies %d\n", replies); - assert(replies == 93); + thread_replies[0] = *ndb_note_meta_counts_thread_replies(entry); + printf("\t# thread replies %d\n", thread_replies[0]); + assert(thread_replies[0] == 93); - replies = *ndb_note_meta_counts_direct_replies(entry); - printf("\t# direct replies %d\n", replies); - assert(replies == 14); + direct_replies[0] = *ndb_note_meta_counts_direct_replies(entry); + printf("\t# direct replies %d\n", direct_replies[0]); + assert(direct_replies[0] == 14); total_reactions = *ndb_note_meta_counts_total_reactions(entry); printf("\t# total reactions %d\n", reactions); @@ -143,6 +144,12 @@ static void test_count_metadata() ndb_count_reactions(&txn, id, &reactions); printf("\t# after-counted reactions %d\n", reactions); assert(reactions == total_reactions); + + ndb_count_replies(&txn, id, &direct_replies[1], &thread_replies[1]); + printf("\t# after-counted replies direct:%d thread:%d\n", direct_replies[1], thread_replies[1]); + assert(direct_replies[0] == direct_replies[1]); + assert(thread_replies[0] == thread_replies[1]); + ndb_end_query(&txn); ndb_destroy(ndb);