nostrdb

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

commit f0edc1eb11e58a87e6338cb92973b523c19d3af4
parent b81490cda1c6c18459e515c130e88b331dc9fea9
Author: William Casarin <jb55@jb55.com>
Date:   Thu, 30 Oct 2025 14:39:03 -0700

migration: migrate full reaction stats for all kind1 notes

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

Diffstat:
MTODO | 3---
Mndb.c | 6++++++
Msrc/metadata.c | 52+++++++++++++++++++++++++++++++++++++++++++++++++++-
Msrc/nostrdb.c | 116+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------------------
Msrc/nostrdb.h | 6++++--
Mtest.c | 4++--
6 files changed, 150 insertions(+), 37 deletions(-)

diff --git a/TODO b/TODO @@ -1,4 +1 @@ test write +metadata -(B) migrate reply stats +metadata -(B) migrate quote stats +metadata -(B) migrate reaction stats +metadata diff --git a/ndb.c b/ndb.c @@ -28,6 +28,7 @@ static int usage() printf(" print-tag-keys\n"); printf(" print-relay-kind-index-keys\n"); printf(" print-author-kind-index-keys\n"); + printf(" print-note-metadata\n"); printf(" import <line-delimited json file>\n\n"); printf("settings\n\n"); @@ -108,6 +109,7 @@ int ndb_print_kind_keys(struct ndb_txn *txn); int ndb_print_tag_index(struct ndb_txn *txn); int ndb_print_relay_kind_index(struct ndb_txn *txn); int ndb_print_author_kind_index(struct ndb_txn *txn); +int ndb_print_note_metadata(struct ndb_txn *txn); static void print_note(struct ndb_note *note) { @@ -443,6 +445,10 @@ int main(int argc, char *argv[]) ndb_begin_query(ndb, &txn); ndb_print_author_kind_index(&txn); ndb_end_query(&txn); + } else if (argc == 2 && !strcmp(argv[1], "print-note-metadata")) { + ndb_begin_query(ndb, &txn); + ndb_print_note_metadata(&txn); + ndb_end_query(&txn); } else if (argc == 3 && !strcmp(argv[1], "note-relays")) { struct ndb_note_relay_iterator iter; const char *relay; diff --git a/src/metadata.c b/src/metadata.c @@ -221,7 +221,7 @@ uint16_t *ndb_note_meta_entry_type(struct ndb_note_meta_entry *entry) } /* find a metadata entry, optionally matching a payload */ -struct ndb_note_meta_entry *ndb_note_meta_find_entry(struct ndb_note_meta *meta, uint16_t type, uint64_t *payload) +static struct ndb_note_meta_entry *ndb_note_meta_find_entry_impl(struct ndb_note_meta *meta, uint16_t type, uint64_t *payload, int sorted) { struct ndb_note_meta_entry *entries, *entry; int i; @@ -232,6 +232,8 @@ struct ndb_note_meta_entry *ndb_note_meta_find_entry(struct ndb_note_meta *meta, entries = ndb_note_meta_entries(meta); assert(((intptr_t)entries - (intptr_t)meta) == 16); + /* TODO(jb55): do bsearch for large sorted entries */ + for (i = 0; i < meta->count; i++) { entry = &entries[i]; assert(((uintptr_t)entry % 8) == 0); @@ -250,6 +252,22 @@ struct ndb_note_meta_entry *ndb_note_meta_find_entry(struct ndb_note_meta *meta, return NULL; } +struct ndb_note_meta_entry *ndb_note_meta_find_entry(struct ndb_note_meta *meta, uint16_t type, uint64_t *payload) +{ + int sorted = 1; + return ndb_note_meta_find_entry_impl(meta, type, payload, sorted); +} + +struct ndb_note_meta_entry *ndb_note_meta_builder_find_entry( + struct ndb_note_meta_builder *builder, + uint16_t type, + uint64_t *payload) +{ + /* meta building in progress is not necessarily sorted */ + int sorted = 0; + return ndb_note_meta_find_entry_impl((struct ndb_note_meta *)builder->cursor.start, type, payload, sorted); +} + 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; @@ -389,3 +407,35 @@ union ndb_reaction_str ndb_note_meta_reaction_str(struct ndb_note_meta_entry *en { return entry->payload.reaction_str; } + +void print_note_meta(struct ndb_note_meta *meta) +{ + int count, i; + struct ndb_note_meta_entry *entries, *entry; + union ndb_reaction_str reaction; + char strbuf[128]; + + count = ndb_note_meta_entries_count(meta); + entries = ndb_note_meta_entries(meta); + + for (i = 0; i < count; i++) { + entry = &entries[i]; + switch (entry->type) { + case NDB_NOTE_META_REACTION: + reaction = ndb_note_meta_reaction_str(entry); + + ndb_reaction_to_str(&reaction, strbuf); + 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", + *ndb_note_meta_counts_quotes(entry), + *ndb_note_meta_counts_direct_replies(entry), + *ndb_note_meta_counts_thread_replies(entry), + *ndb_note_meta_counts_total_reactions(entry)); + break; + } + } + + printf("\n"); +} diff --git a/src/nostrdb.c b/src/nostrdb.c @@ -2178,7 +2178,7 @@ cleanup: } /* count all of the reactions for a note */ -int ndb_count_reactions(struct ndb_txn *txn, const unsigned char *note_id, uint32_t *count) +int ndb_rebuild_reaction_metadata(struct ndb_txn *txn, const unsigned char *note_id, struct ndb_note_meta_builder *builder, uint32_t *count) { MDB_val k, v; MDB_cursor *cur; @@ -2189,6 +2189,8 @@ int ndb_count_reactions(struct ndb_txn *txn, const unsigned char *note_id, uint3 size_t size; struct ndb_note *note; unsigned char *keybuf, *last_id; + struct ndb_note_meta_entry *entry; + union ndb_reaction_str reaction_str; char buffer[41]; /* 1 + 32 + 8 */ *count = 0; @@ -2227,6 +2229,21 @@ int ndb_count_reactions(struct ndb_txn *txn, const unsigned char *note_id, uint3 continue; if (memcmp(last_id, note_id, 32)) continue; + + if (builder) { + if (!ndb_reaction_set(&reaction_str, ndb_note_content(note))) + ndb_reaction_set(&reaction_str, "+"); + + if ((entry = ndb_note_meta_builder_find_entry(builder, NDB_NOTE_META_REACTION, &reaction_str.binmoji))) { + (*ndb_note_meta_reaction_count(entry))++; + } else if ((entry = ndb_note_meta_add_entry(builder))) { + ndb_note_meta_reaction_set(entry, 1, reaction_str); + } else { + /* couldn't add reaction entry ? */ + ndb_debug("ndb_rebuild_note_indices: couldn't add reaction count entry to metadata builder\n"); + } + } + (*count)++; } while (mdb_cursor_get(cur, &k, &v, MDB_NEXT) == 0); @@ -2301,19 +2318,22 @@ static int ndb_note_meta_builder_counts(struct ndb_txn *txn, thread_replies = 0; total_reactions = 0; - if (!(entry = ndb_note_meta_add_entry(builder))) - return 0; - - rcs[0] = ndb_count_reactions(txn, note_id, &total_reactions); + 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); - if (!rcs[0] && !rcs[1] && !rcs[2]) + if (!rcs[0] && !rcs[1] && !rcs[2]) { return 0; + } /* no entry needed */ - if (quotes == 0 && direct_replies == 0 && thread_replies == 0 && quotes == 0) - return 1; + if (quotes == 0 && direct_replies == 0 && thread_replies == 0 && quotes == 0) { + return 0; + } + + if (!(entry = ndb_note_meta_add_entry(builder))) { + return 0; + } ndb_note_meta_counts_set(entry, total_reactions, quotes, direct_replies, thread_replies); @@ -2334,49 +2354,64 @@ static int ndb_migrate_metadata(struct ndb_txn *txn) { MDB_val k, k2, v, v2; MDB_cursor *cur; - MDB_dbi db; + MDB_dbi note_db, meta_db; unsigned char *id; - unsigned char buffer[4096]; - int rc; + size_t scratch_size = 1024 * 1024; + unsigned char *buffer = malloc(scratch_size); + int rc, count; struct ndb_note_meta_builder builder; + struct ndb_note *note; struct ndb_note_meta *meta; - db = txn->lmdb->dbs[NDB_DB_META]; + meta_db = txn->lmdb->dbs[NDB_DB_META]; + note_db = txn->lmdb->dbs[NDB_DB_NOTE]; - if ((rc = mdb_cursor_open(txn->mdb_txn, db, &cur))) { - fprintf(stderr, "ndb_migrate_reaction_stats: mdb_cursor_open failed, error %d\n", rc); + /* drop metadata table to avoid issues */ + if (mdb_drop(txn->mdb_txn, meta_db, 0)) { + fprintf(stderr, "ndb_migrate_metadata: mdb_drop failed\n"); return -1; } + if ((rc = mdb_cursor_open(txn->mdb_txn, note_db, &cur))) { + fprintf(stderr, "ndb_migrate_metadata: mdb_cursor_open failed, error %d\n", rc); + return -1; + } + + count = 0; + /* loop through every metadata entry */ while (mdb_cursor_get(cur, &k, &v, MDB_NEXT) == 0) { - ndb_note_meta_builder_init(&builder, buffer, sizeof(buffer)); + ndb_note_meta_builder_init(&builder, buffer, scratch_size); - id = (unsigned char *)k.mv_data; - ndb_note_meta_builder_count_reactions(txn, &builder); - ndb_note_meta_builder_counts(txn, id, &builder); - ndb_note_meta_build(&builder, &meta); + note = (struct ndb_note *)v.mv_data; + id = ndb_note_id(note); + k2.mv_data = (unsigned char *)id; + k2.mv_size = 32; - /* no counts found, just delete this entry */ - if (ndb_note_meta_entries_count(meta) == 0) { - if ((rc = mdb_del(txn->mdb_txn, db, &k, &v))) { - ndb_debug("delete old metadata entry failed: %s\n", mdb_strerror(rc)); - return -1; - } + rc = ndb_note_meta_builder_counts(txn, id, &builder); + if (!rc) { + mdb_del(txn->mdb_txn, meta_db, &k2, NULL); continue; } - k2.mv_data = (unsigned char *)id; - k2.mv_size = 32; + ndb_note_meta_build(&builder, &meta); + assert(ndb_note_meta_entries(meta)->type != 0); + v2.mv_data = meta; v2.mv_size = ndb_note_meta_total_size(meta); /* set entry */ - if ((rc = mdb_put(txn->mdb_txn, db, &k2, &v2, 0))) { + if ((rc = mdb_put(txn->mdb_txn, meta_db, &k2, &v2, 0))) { ndb_debug("migrate metadata entry failed on write: %s\n", mdb_strerror(rc)); } + + count++; } + fprintf(stderr, "nostrdb: migrated %d metadata entries\n", count); + + free(buffer); + mdb_cursor_close(cur); return 1; } @@ -2713,7 +2748,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_metadata }, + { .fn = ndb_migrate_metadata }, }; @@ -8515,6 +8550,29 @@ void ndb_config_set_ingest_filter(struct ndb_config *config, config->filter_context = filter_ctx; } +int ndb_print_note_metadata(struct ndb_txn *txn) +{ + MDB_cursor *cur; + MDB_val k, v; + int i; + + if (mdb_cursor_open(txn->mdb_txn, txn->lmdb->dbs[NDB_DB_META], &cur)) + return 0; + + i = 1; + while (mdb_cursor_get(cur, &k, &v, MDB_NEXT) == 0) { + print_hex(k.mv_data, 32); + printf("\t"); + print_note_meta((struct ndb_note_meta*)v.mv_data); + i++; + } + + mdb_cursor_close(cur); + + return i; +} + + int ndb_print_author_kind_index(struct ndb_txn *txn) { MDB_cursor *cur; diff --git a/src/nostrdb.h b/src/nostrdb.h @@ -49,8 +49,8 @@ struct bolt11; */ enum ndb_metadata_type { NDB_NOTE_META_RESERVED = 0, /* not used */ - NDB_NOTE_META_COUNTS = 2, /* replies, quotes, etc */ - NDB_NOTE_META_REACTION = 4, /* count of all the reactions on a post, grouped by different reaction strings */ + NDB_NOTE_META_COUNTS = 100, /* replies, quotes, etc */ + NDB_NOTE_META_REACTION = 200, /* count of all the reactions on a post, grouped by different reaction strings */ }; // some bindings like swift needs help with forward declared pointers @@ -658,6 +658,7 @@ struct ndb_note_meta *ndb_get_note_meta(struct ndb_txn *txn, const unsigned char int ndb_set_note_meta(struct ndb *ndb, const unsigned char *id, struct ndb_note_meta *meta); size_t ndb_note_meta_total_size(struct ndb_note_meta *header); int ndb_note_meta_builder_init(struct ndb_note_meta_builder *builder, unsigned char *, size_t); +struct ndb_note_meta_entry *ndb_note_meta_builder_find_entry(struct ndb_note_meta_builder *builder, uint16_t type, uint64_t *payload); void ndb_note_meta_build(struct ndb_note_meta_builder *builder, struct ndb_note_meta **meta); uint16_t ndb_note_meta_entries_count(struct ndb_note_meta *meta); struct ndb_note_meta_entry *ndb_note_meta_entries(struct ndb_note_meta *meta); @@ -673,6 +674,7 @@ 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); uint16_t *ndb_note_meta_entry_type(struct ndb_note_meta_entry *entry); struct ndb_note_meta_entry *ndb_note_meta_entry_at(struct ndb_note_meta *meta, int ind); +void print_note_meta(struct ndb_note_meta *meta); // META STRINGS int ndb_reaction_set(union ndb_reaction_str *reaction, const char *str); diff --git a/test.c b/test.c @@ -30,7 +30,7 @@ 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); +int ndb_rebuild_reaction_metadata(struct ndb_txn *txn, const unsigned char *note_id, struct ndb_note_meta_builder *builder, 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) @@ -141,7 +141,7 @@ static void test_count_metadata() 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); + ndb_rebuild_reaction_metadata(&txn, id, NULL, &reactions); printf("\t# after-counted reactions %d\n", reactions); assert(reactions == total_reactions);