nostrdb

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

commit 14e6b44d7a2504a91669bac92cac3c8d56460e15
parent 5266c5d59b2b46e1a351c7b4421fc54b359def2a
Author: William Casarin <jb55@jb55.com>
Date:   Sat,  1 Nov 2025 13:02:33 -0700

metadata: count reposts

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

Diffstat:
Msrc/metadata.c | 18+++++++++++++-----
Msrc/metadata.h | 6+++++-
Msrc/nostrdb.c | 153++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------
Msrc/nostrdb.h | 4+++-
4 files changed, 150 insertions(+), 31 deletions(-)

diff --git a/src/metadata.c b/src/metadata.c @@ -138,9 +138,9 @@ int ndb_note_meta_builder_init(struct ndb_note_meta_builder *builder, unsigned c } /* note flags are stored in the header entry */ -uint32_t ndb_note_meta_flags(struct ndb_note_meta *meta) +uint64_t *ndb_note_meta_flags(struct ndb_note_meta *meta) { - return meta->flags; + return &meta->flags; } /* note flags are stored in the header entry */ @@ -271,7 +271,7 @@ struct ndb_note_meta_entry *ndb_note_meta_builder_find_entry( void ndb_note_meta_reaction_set(struct ndb_note_meta_entry *entry, uint32_t count, union ndb_reaction_str str) { entry->type = NDB_NOTE_META_REACTION; - entry->flags = 0; + entry->aux2.flags = 0; entry->aux.value = count; entry->payload.reaction_str = str; } @@ -281,10 +281,12 @@ void ndb_note_meta_counts_set(struct ndb_note_meta_entry *entry, uint32_t total_reactions, uint16_t quotes, uint16_t direct_replies, - uint32_t thread_replies) + uint32_t thread_replies, + uint16_t reposts) { entry->type = NDB_NOTE_META_COUNTS; entry->aux.total_reactions = total_reactions; + entry->aux2.reposts = reposts; entry->payload.counts.quotes = quotes; entry->payload.counts.direct_replies = direct_replies; entry->payload.counts.thread_replies = thread_replies; @@ -398,6 +400,11 @@ uint16_t *ndb_note_meta_counts_quotes(struct ndb_note_meta_entry *entry) return &entry->payload.counts.quotes; } +uint16_t *ndb_note_meta_counts_reposts(struct ndb_note_meta_entry *entry) +{ + return &entry->aux2.reposts; +} + void ndb_note_meta_reaction_set_count(struct ndb_note_meta_entry *entry, uint32_t count) { entry->aux.value = count; @@ -428,7 +435,8 @@ void print_note_meta(struct ndb_note_meta *meta) printf("%s%d ", strbuf, *ndb_note_meta_reaction_count(entry)); break; case NDB_NOTE_META_COUNTS: - printf("quotes %d\treplies %d\tall_replies %d\treactions %d\t", + printf("reposts %d\tquotes %d\treplies %d\tall_replies %d\treactions %d\t", + *ndb_note_meta_counts_reposts(entry), *ndb_note_meta_counts_quotes(entry), *ndb_note_meta_counts_direct_replies(entry), *ndb_note_meta_counts_thread_replies(entry), diff --git a/src/metadata.h b/src/metadata.h @@ -25,7 +25,11 @@ enum ndb_meta_clone_result ndb_note_meta_clone_with_entry( struct ndb_note_meta_entry { // 4 byte entry header uint16_t type; - uint16_t flags; + + union { + uint16_t flags; + uint16_t reposts; + } aux2; // additional 4 bytes of aux storage for payloads that are >8 bytes // diff --git a/src/nostrdb.c b/src/nostrdb.c @@ -2252,6 +2252,61 @@ cleanup: return 1; } +static int ndb_count_reposts(struct ndb_txn *txn, const unsigned char *note_id, uint16_t *count) +{ + MDB_val k, v; + MDB_cursor *cur; + MDB_dbi db; + int rc; + unsigned char *keybuf; + struct ndb_note *note; + uint64_t note_key, kind; + char buffer[41]; /* 1 + 32 + 8 */ + + *count = 0; + db = txn->lmdb->dbs[NDB_DB_NOTE_TAGS]; + + /* we will iterate q tags for this particular id */ + if ((rc = mdb_cursor_open(txn->mdb_txn, db, &cur))) { + fprintf(stderr, "ndb_count_quotes: 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) != 0) + break; + if (!(note = ndb_get_note_by_key(txn, note_key, NULL))) + continue; + kind = ndb_note_kind(note); + if (!(kind == 6 || kind == 16)) + continue; + (*count)++; + } while (mdb_cursor_get(cur, &k, &v, MDB_NEXT) == 0); + +cleanup: + mdb_cursor_close(cur); + return 1; +} + /* count all of the quote reposts for a note id */ static int ndb_count_quotes(struct ndb_txn *txn, const unsigned char *note_id, uint16_t *count) { @@ -2291,6 +2346,11 @@ static int ndb_count_quotes(struct ndb_txn *txn, const unsigned char *note_id, u break; if (memcmp(&keybuf[1], note_id, 32) != 0) break; + /* TODO(jb55): technically we should check to see if this is a kind 1. + * there could be other kinds with q tags that reference this note + * + * Starting to think we should have tag-kind index + */ (*count)++; if (mdb_cursor_get(cur, &k, &v, MDB_NEXT)) @@ -2309,9 +2369,9 @@ static int ndb_note_meta_builder_counts(struct ndb_txn *txn, struct ndb_note_meta_builder *builder) { uint32_t thread_replies, total_reactions; - uint16_t direct_replies, quotes; + uint16_t direct_replies, quotes, reposts; struct ndb_note_meta_entry *entry; - int rcs[3]; + int rcs[4]; quotes = 0; direct_replies = 0; @@ -2321,13 +2381,14 @@ static int ndb_note_meta_builder_counts(struct ndb_txn *txn, rcs[0] = ndb_rebuild_reaction_metadata(txn, note_id, builder, &total_reactions); rcs[1] = ndb_count_quotes(txn, note_id, &quotes); rcs[2] = ndb_count_replies(txn, note_id, &direct_replies, &thread_replies); + rcs[3] = ndb_count_reposts(txn, note_id, &reposts); - if (!rcs[0] && !rcs[1] && !rcs[2]) { + if (!rcs[0] && !rcs[1] && !rcs[2] && !rcs[3]) { return 0; } /* no entry needed */ - if (quotes == 0 && direct_replies == 0 && thread_replies == 0 && quotes == 0) { + if (quotes == 0 && direct_replies == 0 && thread_replies == 0 && quotes == 0 && reposts == 0) { return 0; } @@ -2335,7 +2396,7 @@ static int ndb_note_meta_builder_counts(struct ndb_txn *txn, return 0; } - ndb_note_meta_counts_set(entry, total_reactions, quotes, direct_replies, thread_replies); + ndb_note_meta_counts_set(entry, total_reactions, quotes, direct_replies, thread_replies, reposts); return 1; } @@ -3785,21 +3846,6 @@ struct ndb_note_meta *ndb_get_note_meta(struct ndb_txn *txn, const unsigned char return v.mv_data; } -static void print_meta_data(struct ndb_note_meta *meta) -{ - struct ndb_note_meta_entry *entry; - int i; - - printf("<metadata ver:%d count:%d dt_size:%d flags:%ld>\n", - meta->version, meta->count, meta->data_table_size, meta->flags); - - for (i = 0; i < meta->count; i++) { - entry = ndb_note_meta_entry_at(meta, i); - printf("<meta_entry type:%d flags:%d aux:%d payload:%" PRIx64 "\n", - entry->type, entry->flags, entry->aux.value, entry->payload.value); - } -} - /* write reaction stats if its a valid reaction */ static int ndb_process_reaction( struct ndb_txn *txn, @@ -3894,7 +3940,7 @@ static int ndb_increment_total_reactions( case NDB_META_CLONE_FAILED: return 0; case NDB_META_CLONE_NEW_ENTRY: - ndb_note_meta_counts_set(entry, 1, 0, 0, 0); + ndb_note_meta_counts_set(entry, 1, 0, 0, 0, 0); break; case NDB_META_CLONE_EXISTING_ENTRY: total_reactions = ndb_note_meta_counts_total_reactions(entry); @@ -5832,7 +5878,7 @@ static int ndb_increment_quote_metadata( case NDB_META_CLONE_FAILED: return 0; case NDB_META_CLONE_NEW_ENTRY: - ndb_note_meta_counts_set(entry, 0, 1, 0, 0); + ndb_note_meta_counts_set(entry, 0, 1, 0, 0, 0); break; case NDB_META_CLONE_EXISTING_ENTRY: quotes = ndb_note_meta_counts_quotes(entry); @@ -5879,7 +5925,7 @@ static int ndb_increment_direct_reply_metadata( case NDB_META_CLONE_FAILED: return 0; case NDB_META_CLONE_NEW_ENTRY: - ndb_note_meta_counts_set(entry, 0, 0, 1, 0); + ndb_note_meta_counts_set(entry, 0, 0, 1, 0, 0); break; case NDB_META_CLONE_EXISTING_ENTRY: direct_replies = ndb_note_meta_counts_direct_replies(entry); @@ -5926,7 +5972,7 @@ static int ndb_increment_thread_reply_metadata( case NDB_META_CLONE_FAILED: return 0; case NDB_META_CLONE_NEW_ENTRY: - ndb_note_meta_counts_set(entry, 0, 0, 0, 1); + ndb_note_meta_counts_set(entry, 0, 0, 0, 1, 0); break; case NDB_META_CLONE_EXISTING_ENTRY: replies = ndb_note_meta_counts_thread_replies(entry); @@ -5949,6 +5995,63 @@ static int ndb_increment_thread_reply_metadata( return 1; } +/* update reply count metadata for a specific note id */ +static int ndb_increment_repost_metadata( + struct ndb_txn *txn, + unsigned char *id, + unsigned char *scratch, + size_t scratch_size) +{ + MDB_val key, val; + uint16_t *reposts; + struct ndb_note_meta *meta; + struct ndb_note_meta_entry *entry; + int rc; + + meta = ndb_get_note_meta(txn, id); + rc = ndb_note_meta_clone_with_entry(&meta, &entry, + NDB_NOTE_META_COUNTS, + NULL, /* payload to match. only relevant for reactions */ + scratch, + scratch_size); + + switch (rc) { + case NDB_META_CLONE_FAILED: + return 0; + case NDB_META_CLONE_NEW_ENTRY: + ndb_note_meta_counts_set(entry, 0, 0, 0, 0, 1); + break; + case NDB_META_CLONE_EXISTING_ENTRY: + reposts = ndb_note_meta_counts_reposts(entry); + (*reposts)++; + break; + } + + key.mv_data = id; + key.mv_size = 32; + + val.mv_data = meta; + val.mv_size = ndb_note_meta_total_size(meta); + assert((val.mv_size % 8) == 0); + + if ((rc = mdb_put(txn->mdb_txn, txn->lmdb->dbs[NDB_DB_META], &key, &val, 0))) { + ndb_debug("write reaction stats to db failed: %s\n", mdb_strerror(rc)); + return 0; + } + + return 1; +} + +static void ndb_process_repost_stats(struct ndb_txn *txn, struct ndb_note *note, unsigned char *scratch, size_t scratch_size) +{ + unsigned char *reposted_note_id; + reposted_note_id = ndb_note_first_tag_id(note, 'e'); + + /* find q tag to see if we are quoting anything */ + if (reposted_note_id) { + ndb_increment_repost_metadata(txn, reposted_note_id, scratch, scratch_size); + } +} /* process quote and reply count metadata */ static void ndb_process_note_stats( @@ -6044,6 +6147,8 @@ static uint64_t ndb_write_note(struct ndb_txn *txn, ndb_process_note_stats(txn, note->note, scratch, scratch_size); } else if (kind == 7 && !ndb_flag_set(ndb_flags, NDB_FLAG_NO_STATS)) { ndb_write_reaction_stats(txn, note->note, scratch, scratch_size); + } else if (kind == 6 || kind == 16) { + ndb_process_repost_stats(txn, note->note, scratch, scratch_size); } return note_key; diff --git a/src/nostrdb.h b/src/nostrdb.h @@ -665,10 +665,12 @@ struct ndb_note_meta_entry *ndb_note_meta_entries(struct ndb_note_meta *meta); struct ndb_note_meta_entry *ndb_note_meta_add_entry(struct ndb_note_meta_builder *builder); size_t ndb_note_meta_total_size(struct ndb_note_meta *meta); void ndb_note_meta_reaction_set(struct ndb_note_meta_entry *entry, uint32_t count, union ndb_reaction_str str); -void ndb_note_meta_counts_set(struct ndb_note_meta_entry *entry, uint32_t total_reactions, uint16_t quotes, uint16_t direct_replies, uint32_t thread_replies); +void ndb_note_meta_counts_set(struct ndb_note_meta_entry *entry, uint32_t total_reactions, uint16_t quotes, uint16_t direct_replies, uint32_t thread_replies, uint16_t reposts); uint32_t *ndb_note_meta_reaction_count(struct ndb_note_meta_entry *entry); struct ndb_note_meta_entry *ndb_note_meta_find_entry(struct ndb_note_meta *meta, uint16_t type, uint64_t *payload); uint16_t *ndb_note_meta_counts_quotes(struct ndb_note_meta_entry *entry); +uint16_t *ndb_note_meta_counts_reposts(struct ndb_note_meta_entry *entry); +uint64_t *ndb_note_meta_flags(struct ndb_note_meta *meta); uint32_t *ndb_note_meta_counts_total_reactions(struct ndb_note_meta_entry *entry); uint16_t *ndb_note_meta_counts_direct_replies(struct ndb_note_meta_entry *entry); uint32_t *ndb_note_meta_counts_thread_replies(struct ndb_note_meta_entry *entry);