Skip to content

Commit 09555a6

Browse files
committed
metadata: add online metadata counts
Add online counting for: - quotes - thread replies - direct replies - total reactions only for kind1 and longform for now Signed-off-by: William Casarin <[email protected]>
1 parent 3a8b60f commit 09555a6

File tree

8 files changed

+1008
-162
lines changed

8 files changed

+1008
-162
lines changed

TODO

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,4 @@
11
test write +metadata
22
(B) migrate reply stats +metadata
33
(B) migrate quote stats +metadata
4-
online quote stat calculation +metadata
5-
online reply stats calculation +metadata
64
(B) migrate reaction stats +metadata
7-
online reaction stats +metadata
8-
test reply stats +metadata
9-
test reaction stats +metadata
10-
test quote stats +metadata

src/metadata.c

Lines changed: 157 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,55 +1,7 @@
11

22
#include "nostrdb.h"
33
#include "binmoji.h"
4-
5-
// these must be byte-aligned, they are directly accessing the serialized data
6-
// representation
7-
#pragma pack(push, 1)
8-
9-
// 16 bytes
10-
struct ndb_note_meta_entry {
11-
// 4 byte entry header
12-
uint16_t type;
13-
uint16_t flags;
14-
15-
// additional 4 bytes of aux storage for payloads that are >8 bytes
16-
//
17-
// for reactions types, this is used for counts
18-
// normally this would have been padding but we make use of it
19-
// in our manually packed structure
20-
uint32_t aux;
21-
22-
// 8 byte metadata payload
23-
union {
24-
uint64_t value;
25-
26-
struct {
27-
uint32_t offset;
28-
uint32_t padding;
29-
} offset;
30-
31-
// the reaction binmoji[1] for reaction, count is stored in aux
32-
union ndb_reaction_str reaction_str;
33-
} payload;
34-
};
35-
STATIC_ASSERT(sizeof(struct ndb_note_meta_entry) == 16, note_meta_entry_should_be_16_bytes);
36-
37-
/* newtype wrapper around the header entry */
38-
struct ndb_note_meta {
39-
// 4 bytes
40-
uint8_t version;
41-
uint8_t padding;
42-
uint16_t count;
43-
44-
// 4 bytes
45-
uint32_t data_table_size;
46-
47-
// 8 bytes
48-
uint64_t flags;
49-
};
50-
STATIC_ASSERT(sizeof(struct ndb_note_meta) == 16, note_meta_entry_should_be_16_bytes);
51-
52-
#pragma pack(pop)
4+
#include "metadata.h"
535

546
int ndb_reaction_str_is_emoji(union ndb_reaction_str str)
557
{
@@ -96,6 +48,19 @@ static int ndb_reaction_set_str(union ndb_reaction_str *reaction, const char *st
9648
return 0;
9749
}
9850

51+
const char *ndb_reaction_to_str(union ndb_reaction_str *str, char buf[128])
52+
{
53+
struct binmoji binmoji;
54+
55+
if (ndb_reaction_str_is_emoji(*str)) {
56+
binmoji_decode(str->binmoji, &binmoji);
57+
binmoji_to_string(&binmoji, buf, 128);
58+
return (const char *)buf;
59+
} else {
60+
return (const char *)str->packed.str;
61+
}
62+
}
63+
9964
/* set the value of an ndb_reaction_str to an emoji or small string */
10065
int ndb_reaction_set(union ndb_reaction_str *reaction, const char *str)
10166
{
@@ -215,9 +180,16 @@ static int compare_entries(const void *a, const void *b)
215180
struct ndb_note_meta_entry *ndb_note_meta_entries(struct ndb_note_meta *meta)
216181
{
217182
/* entries start at the end of the header record */
218-
return (struct ndb_note_meta_entry *)((uint8_t*)meta + sizeof(*meta));
183+
return (struct ndb_note_meta_entry *)((unsigned char*)meta + sizeof(*meta));
219184
}
220185

186+
struct ndb_note_meta_entry *ndb_note_meta_entry_at(struct ndb_note_meta *meta, int i)
187+
{
188+
if (i >= ndb_note_meta_entries_count(meta))
189+
return NULL;
190+
191+
return &ndb_note_meta_entries(meta)[i];
192+
}
221193
void ndb_note_meta_build(struct ndb_note_meta_builder *builder, struct ndb_note_meta **meta)
222194
{
223195
/* sort entries */
@@ -227,18 +199,27 @@ void ndb_note_meta_build(struct ndb_note_meta_builder *builder, struct ndb_note_
227199
/* not initialized */
228200
assert(builder->cursor.start != builder->cursor.p);
229201

230-
if (header->count > 0) {
202+
if (header->count > 1) {
231203
entries = ndb_note_meta_entries(header);
204+
/*assert(entries);*/
232205

233206
/* ensure entries are always sorted so bsearch is possible for large metadata
234207
* entries. probably won't need that for awhile though */
208+
209+
/* this also ensures our counts entry is near the front, which will be a very
210+
* hot and common entry to hit */
235211
qsort(entries, header->count, sizeof(struct ndb_note_meta_entry), compare_entries);
236212
}
237213

238214
*meta = header;
239215
return;
240216
}
241217

218+
uint16_t *ndb_note_meta_entry_type(struct ndb_note_meta_entry *entry)
219+
{
220+
return &entry->type;
221+
}
222+
242223
/* find a metadata entry, optionally matching a payload */
243224
struct ndb_note_meta_entry *ndb_note_meta_find_entry(struct ndb_note_meta *meta, uint16_t type, uint64_t *payload)
244225
{
@@ -249,12 +230,19 @@ struct ndb_note_meta_entry *ndb_note_meta_find_entry(struct ndb_note_meta *meta,
249230
return NULL;
250231

251232
entries = ndb_note_meta_entries(meta);
233+
assert(((intptr_t)entries - (intptr_t)meta) == 16);
252234

253235
for (i = 0; i < meta->count; i++) {
254236
entry = &entries[i];
237+
assert(((uintptr_t)entry % 8) == 0);
238+
/*
239+
assert(entry->type < 100);
240+
printf("finding %d/%d q:%d q:%"PRIx64" entry_type:%d entry:%"PRIx64"\n",
241+
i+1, (int)meta->count, type, payload ? *payload : 0, entry->type, entry->payload.value);
242+
*/
255243
if (entry->type != type)
256244
continue;
257-
if (payload && *payload != entry->payload.value)
245+
if (payload && (*payload != entry->payload.value))
258246
continue;
259247
return entry;
260248
}
@@ -266,28 +254,135 @@ void ndb_note_meta_reaction_set(struct ndb_note_meta_entry *entry, uint32_t coun
266254
{
267255
entry->type = NDB_NOTE_META_REACTION;
268256
entry->flags = 0;
269-
entry->aux = count;
257+
entry->aux.value = count;
270258
entry->payload.reaction_str = str;
271259
}
272260

273261
/* sets the quote repost count for this note */
274-
void ndb_note_meta_quotes_set(struct ndb_note_meta_entry *entry, uint32_t count)
262+
void ndb_note_meta_counts_set(struct ndb_note_meta_entry *entry,
263+
uint32_t total_reactions,
264+
uint16_t quotes,
265+
uint16_t direct_replies,
266+
uint32_t thread_replies)
275267
{
276-
entry->type = NDB_NOTE_META_QUOTES;
277-
entry->flags = 0;
278-
entry->aux = count;
279-
/* unused */
280-
entry->payload.value = 0;
268+
entry->type = NDB_NOTE_META_COUNTS;
269+
entry->aux.total_reactions = total_reactions;
270+
entry->payload.counts.quotes = quotes;
271+
entry->payload.counts.direct_replies = direct_replies;
272+
entry->payload.counts.thread_replies = thread_replies;
273+
}
274+
275+
/* clones a metadata, either adding a new entry of a specific type, or returing
276+
* a reference to it
277+
*
278+
* [in/out] meta: pointer to an existing meta entry, can but overwritten to
279+
* [out] entry: pointer to the added entry
280+
*
281+
* */
282+
enum ndb_meta_clone_result ndb_note_meta_clone_with_entry(
283+
struct ndb_note_meta **meta,
284+
struct ndb_note_meta_entry **entry,
285+
uint16_t type,
286+
uint64_t *payload,
287+
unsigned char *buf,
288+
size_t bufsize)
289+
{
290+
size_t size, offset;
291+
struct ndb_note_meta_builder builder;
292+
293+
if (*meta == NULL) {
294+
ndb_note_meta_builder_init(&builder, buf, bufsize);
295+
*entry = ndb_note_meta_add_entry(&builder);
296+
*meta = (struct ndb_note_meta*)buf;
297+
298+
assert(*entry);
299+
300+
ndb_note_meta_build(&builder, meta);
301+
return NDB_META_CLONE_NEW_ENTRY;
302+
} else if ((size = ndb_note_meta_total_size(*meta)) > bufsize) {
303+
ndb_debug("buf size too small (%d < %d) for metadata entry\n", bufsize, size);
304+
goto fail;
305+
} else if ((*entry = ndb_note_meta_find_entry(*meta, type, payload))) {
306+
offset = (unsigned char *)(*entry) - (unsigned char *)(*meta);
307+
308+
/* we have an existing entry. simply memcpy and return the new entry position */
309+
assert(offset < size);
310+
assert((offset % 16) == 0);
311+
assert(((uintptr_t)buf % 8) == 0);
312+
313+
memcpy(buf, *meta, size);
314+
*meta = (struct ndb_note_meta*)buf;
315+
*entry = (struct ndb_note_meta_entry*)(((unsigned char *)(*meta)) + offset);
316+
return NDB_META_CLONE_EXISTING_ENTRY;
317+
} else if (size + sizeof(*entry) > bufsize) {
318+
/* if we don't have an existing entry, make sure we have room to add one */
319+
320+
ndb_debug("note metadata is too big (%d > %d) to clone with entry\n",
321+
(int)(len + sizeof(*entry)), (int)scratch_size);
322+
/* no room. this is bad, if this happens we should fix it */
323+
goto fail;
324+
} else {
325+
/* we need to add a new entry */
326+
ndb_note_meta_builder_init(&builder, buf, bufsize);
327+
328+
memcpy(buf, *meta, size);
329+
builder.cursor.p = buf + size;
330+
331+
*entry = ndb_note_meta_add_entry(&builder);
332+
assert(*entry);
333+
(*entry)->type = type;
334+
(*entry)->payload.value = payload? *payload : 0;
335+
336+
*meta = (struct ndb_note_meta*)buf;
337+
338+
assert(*entry);
339+
assert(*meta);
340+
341+
ndb_note_meta_build(&builder, meta);
342+
343+
/* we re-find here since it could have been sorted */
344+
*entry = ndb_note_meta_find_entry(*meta, type, payload);
345+
assert(*entry);
346+
assert(*ndb_note_meta_entry_type(*entry) == type);
347+
348+
return NDB_META_CLONE_NEW_ENTRY;
349+
}
350+
351+
assert(!"should be impossible to get here");
352+
fail:
353+
*entry = NULL;
354+
*meta = NULL;
355+
return 0;
356+
}
357+
358+
uint32_t *ndb_note_meta_reaction_count(struct ndb_note_meta_entry *entry)
359+
{
360+
return &entry->aux.value;
361+
}
362+
363+
uint16_t *ndb_note_meta_counts_direct_replies(struct ndb_note_meta_entry *entry)
364+
{
365+
return &entry->payload.counts.direct_replies;
366+
}
367+
368+
uint32_t *ndb_note_meta_counts_total_reactions(struct ndb_note_meta_entry *entry)
369+
{
370+
return &entry->aux.total_reactions;
371+
}
372+
373+
uint32_t *ndb_note_meta_counts_thread_replies(struct ndb_note_meta_entry *entry)
374+
{
375+
return &entry->payload.counts.thread_replies;
281376
}
282377

283-
uint32_t ndb_note_meta_reaction_count(struct ndb_note_meta_entry *entry)
378+
uint16_t *ndb_note_meta_counts_quotes(struct ndb_note_meta_entry *entry)
284379
{
285-
return entry->aux;
380+
return &entry->payload.counts.quotes;
286381
}
287382

288383
void ndb_note_meta_reaction_set_count(struct ndb_note_meta_entry *entry, uint32_t count)
289384
{
290-
entry->aux = count;
385+
entry->aux.value = count;
291386
}
292387

293388
union ndb_reaction_str ndb_note_meta_reaction_str(struct ndb_note_meta_entry *entry)

src/metadata.h

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
#ifndef NDB_METADATA_H
2+
#define NDB_METADATA_H
3+
4+
#include "nostrdb.h"
5+
6+
enum ndb_meta_clone_result {
7+
NDB_META_CLONE_FAILED,
8+
NDB_META_CLONE_EXISTING_ENTRY,
9+
NDB_META_CLONE_NEW_ENTRY,
10+
};
11+
12+
enum ndb_meta_clone_result ndb_note_meta_clone_with_entry(
13+
struct ndb_note_meta **meta,
14+
struct ndb_note_meta_entry **entry,
15+
uint16_t type,
16+
uint64_t *payload,
17+
unsigned char *buf,
18+
size_t bufsize);
19+
20+
// these must be byte-aligned, they are directly accessing the serialized data
21+
// representation
22+
#pragma pack(push, 1)
23+
24+
// 16 bytes
25+
struct ndb_note_meta_entry {
26+
// 4 byte entry header
27+
uint16_t type;
28+
uint16_t flags;
29+
30+
// additional 4 bytes of aux storage for payloads that are >8 bytes
31+
//
32+
// for reactions types, this is used for counts
33+
// normally this would have been padding but we make use of it
34+
// in our manually packed structure
35+
union {
36+
uint32_t value;
37+
38+
/* if this is a thread root, this counts the total replies in the thread */
39+
uint32_t total_reactions;
40+
} aux;
41+
42+
// 8 byte metadata payload
43+
union {
44+
uint64_t value;
45+
46+
struct {
47+
uint32_t offset;
48+
uint32_t padding;
49+
} offset;
50+
51+
struct {
52+
/* number of direct replies */
53+
uint16_t direct_replies;
54+
uint16_t quotes;
55+
56+
/* number of replies in this thread */
57+
uint32_t thread_replies;
58+
} counts;
59+
60+
// the reaction binmoji[1] for reaction, count is stored in aux
61+
union ndb_reaction_str reaction_str;
62+
} payload;
63+
};
64+
STATIC_ASSERT(sizeof(struct ndb_note_meta_entry) == 16, note_meta_entry_should_be_16_bytes);
65+
66+
struct ndb_note_meta {
67+
// 4 bytes
68+
uint8_t version;
69+
uint8_t padding;
70+
uint16_t count;
71+
72+
// 4 bytes
73+
uint32_t data_table_size;
74+
75+
// 8 bytes
76+
uint64_t flags;
77+
};
78+
STATIC_ASSERT(sizeof(struct ndb_note_meta) == 16, note_meta_entry_should_be_16_bytes);
79+
80+
#pragma pack(pop)
81+
82+
#endif /* NDB_METADATA_H */

0 commit comments

Comments
 (0)