query-for-ptag-graph_test.go raw
1 package database
2
3 import (
4 "context"
5 "testing"
6
7 "next.orly.dev/pkg/nostr/encoders/event"
8 "next.orly.dev/pkg/nostr/encoders/filter"
9 "next.orly.dev/pkg/nostr/encoders/hex"
10 "next.orly.dev/pkg/nostr/encoders/kind"
11 "next.orly.dev/pkg/nostr/encoders/tag"
12 )
13
14 func TestCanUsePTagGraph(t *testing.T) {
15 tests := []struct {
16 name string
17 filter *filter.F
18 expected bool
19 }{
20 {
21 name: "filter with p-tags only",
22 filter: &filter.F{
23 Tags: tag.NewS(
24 tag.NewFromAny("p", "0000000000000000000000000000000000000000000000000000000000000001"),
25 ),
26 },
27 expected: true,
28 },
29 {
30 name: "filter with p-tags and kinds",
31 filter: &filter.F{
32 Kinds: kind.NewS(kind.New(1)),
33 Tags: tag.NewS(
34 tag.NewFromAny("p", "0000000000000000000000000000000000000000000000000000000000000001"),
35 ),
36 },
37 expected: true,
38 },
39 {
40 name: "filter with p-tags and authors (should use traditional index)",
41 filter: &filter.F{
42 Authors: tag.NewFromBytesSlice([]byte("author")),
43 Tags: tag.NewS(
44 tag.NewFromAny("p", "0000000000000000000000000000000000000000000000000000000000000001"),
45 ),
46 },
47 expected: false,
48 },
49 {
50 name: "filter with e-tags only (no p-tags)",
51 filter: &filter.F{
52 Tags: tag.NewS(
53 tag.NewFromAny("e", "someeventid"),
54 ),
55 },
56 expected: false,
57 },
58 {
59 name: "filter with no tags",
60 filter: &filter.F{},
61 expected: false,
62 },
63 }
64
65 for _, tt := range tests {
66 t.Run(tt.name, func(t *testing.T) {
67 result := CanUsePTagGraph(tt.filter)
68 if result != tt.expected {
69 t.Errorf("CanUsePTagGraph() = %v, want %v", result, tt.expected)
70 }
71 })
72 }
73 }
74
75 func TestQueryPTagGraph(t *testing.T) {
76 ctx, cancel := context.WithCancel(context.Background())
77 defer cancel()
78
79 db, err := New(ctx, cancel, t.TempDir(), "info")
80 if err != nil {
81 t.Fatalf("Failed to create database: %v", err)
82 }
83 defer db.Close()
84
85 // Create test events with p-tags
86 authorPubkey, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000001")
87 alicePubkey, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000002")
88 bobPubkey, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000003")
89
90 // Event 1: kind-1 (text note) mentioning Alice
91 eventID1 := make([]byte, 32)
92 eventID1[0] = 1
93 eventSig1 := make([]byte, 64)
94 eventSig1[0] = 1
95
96 ev1 := &event.E{
97 ID: eventID1,
98 Pubkey: authorPubkey,
99 CreatedAt: 1234567890,
100 Kind: 1,
101 Content: []byte("Mentioning Alice"),
102 Sig: eventSig1,
103 Tags: tag.NewS(
104 tag.NewFromAny("p", hex.Enc(alicePubkey)),
105 ),
106 }
107
108 // Event 2: kind-6 (repost) mentioning Alice
109 eventID2 := make([]byte, 32)
110 eventID2[0] = 2
111 eventSig2 := make([]byte, 64)
112 eventSig2[0] = 2
113
114 ev2 := &event.E{
115 ID: eventID2,
116 Pubkey: authorPubkey,
117 CreatedAt: 1234567891,
118 Kind: 6,
119 Content: []byte("Reposting Alice"),
120 Sig: eventSig2,
121 Tags: tag.NewS(
122 tag.NewFromAny("p", hex.Enc(alicePubkey)),
123 ),
124 }
125
126 // Event 3: kind-1 mentioning Bob
127 eventID3 := make([]byte, 32)
128 eventID3[0] = 3
129 eventSig3 := make([]byte, 64)
130 eventSig3[0] = 3
131
132 ev3 := &event.E{
133 ID: eventID3,
134 Pubkey: authorPubkey,
135 CreatedAt: 1234567892,
136 Kind: 1,
137 Content: []byte("Mentioning Bob"),
138 Sig: eventSig3,
139 Tags: tag.NewS(
140 tag.NewFromAny("p", hex.Enc(bobPubkey)),
141 ),
142 }
143
144 // Save all events
145 if _, err := db.SaveEvent(ctx, ev1); err != nil {
146 t.Fatalf("Failed to save event 1: %v", err)
147 }
148 if _, err := db.SaveEvent(ctx, ev2); err != nil {
149 t.Fatalf("Failed to save event 2: %v", err)
150 }
151 if _, err := db.SaveEvent(ctx, ev3); err != nil {
152 t.Fatalf("Failed to save event 3: %v", err)
153 }
154
155 // Test 1: Query for all events mentioning Alice
156 t.Run("query for Alice mentions", func(t *testing.T) {
157 f := &filter.F{
158 Tags: tag.NewS(
159 tag.NewFromAny("p", hex.Enc(alicePubkey)),
160 ),
161 }
162
163 sers, err := db.QueryPTagGraph(f)
164 if err != nil {
165 t.Fatalf("QueryPTagGraph failed: %v", err)
166 }
167
168 if len(sers) != 2 {
169 t.Errorf("Expected 2 events mentioning Alice, got %d", len(sers))
170 }
171 t.Logf("Found %d events mentioning Alice", len(sers))
172 })
173
174 // Test 2: Query for kind-1 events mentioning Alice
175 t.Run("query for kind-1 Alice mentions", func(t *testing.T) {
176 f := &filter.F{
177 Kinds: kind.NewS(kind.New(1)),
178 Tags: tag.NewS(
179 tag.NewFromAny("p", hex.Enc(alicePubkey)),
180 ),
181 }
182
183 sers, err := db.QueryPTagGraph(f)
184 if err != nil {
185 t.Fatalf("QueryPTagGraph failed: %v", err)
186 }
187
188 if len(sers) != 1 {
189 t.Errorf("Expected 1 kind-1 event mentioning Alice, got %d", len(sers))
190 }
191 t.Logf("Found %d kind-1 events mentioning Alice", len(sers))
192 })
193
194 // Test 3: Query for events mentioning Bob
195 t.Run("query for Bob mentions", func(t *testing.T) {
196 f := &filter.F{
197 Tags: tag.NewS(
198 tag.NewFromAny("p", hex.Enc(bobPubkey)),
199 ),
200 }
201
202 sers, err := db.QueryPTagGraph(f)
203 if err != nil {
204 t.Fatalf("QueryPTagGraph failed: %v", err)
205 }
206
207 if len(sers) != 1 {
208 t.Errorf("Expected 1 event mentioning Bob, got %d", len(sers))
209 }
210 t.Logf("Found %d events mentioning Bob", len(sers))
211 })
212
213 // Test 4: Query for non-existent pubkey
214 t.Run("query for non-existent pubkey", func(t *testing.T) {
215 nonExistentPubkey := make([]byte, 32)
216 for i := range nonExistentPubkey {
217 nonExistentPubkey[i] = 0xFF
218 }
219
220 f := &filter.F{
221 Tags: tag.NewS(
222 tag.NewFromAny("p", hex.Enc(nonExistentPubkey)),
223 ),
224 }
225
226 sers, err := db.QueryPTagGraph(f)
227 if err != nil {
228 t.Fatalf("QueryPTagGraph failed: %v", err)
229 }
230
231 if len(sers) != 0 {
232 t.Errorf("Expected 0 events for non-existent pubkey, got %d", len(sers))
233 }
234 t.Logf("Correctly found 0 events for non-existent pubkey")
235 })
236
237 // Test 5: Query for multiple kinds
238 t.Run("query for multiple kinds mentioning Alice", func(t *testing.T) {
239 f := &filter.F{
240 Kinds: kind.NewS(kind.New(1), kind.New(6)),
241 Tags: tag.NewS(
242 tag.NewFromAny("p", hex.Enc(alicePubkey)),
243 ),
244 }
245
246 sers, err := db.QueryPTagGraph(f)
247 if err != nil {
248 t.Fatalf("QueryPTagGraph failed: %v", err)
249 }
250
251 if len(sers) != 2 {
252 t.Errorf("Expected 2 events (kind 1 and 6) mentioning Alice, got %d", len(sers))
253 }
254 t.Logf("Found %d events (kind 1 and 6) mentioning Alice", len(sers))
255 })
256 }
257
258 func TestGetSerialsFromFilterWithPTagOptimization(t *testing.T) {
259 ctx, cancel := context.WithCancel(context.Background())
260 defer cancel()
261
262 db, err := New(ctx, cancel, t.TempDir(), "info")
263 if err != nil {
264 t.Fatalf("Failed to create database: %v", err)
265 }
266 defer db.Close()
267
268 // Create test event with p-tag
269 authorPubkey, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000001")
270 alicePubkey, _ := hex.Dec("0000000000000000000000000000000000000000000000000000000000000002")
271
272 eventID := make([]byte, 32)
273 eventID[0] = 1
274 eventSig := make([]byte, 64)
275 eventSig[0] = 1
276
277 ev := &event.E{
278 ID: eventID,
279 Pubkey: authorPubkey,
280 CreatedAt: 1234567890,
281 Kind: 1,
282 Content: []byte("Mentioning Alice"),
283 Sig: eventSig,
284 Tags: tag.NewS(
285 tag.NewFromAny("p", hex.Enc(alicePubkey)),
286 ),
287 }
288
289 if _, err := db.SaveEvent(ctx, ev); err != nil {
290 t.Fatalf("Failed to save event: %v", err)
291 }
292
293 // Test that GetSerialsFromFilter uses the p-tag graph optimization
294 f := &filter.F{
295 Kinds: kind.NewS(kind.New(1)),
296 Tags: tag.NewS(
297 tag.NewFromAny("p", hex.Enc(alicePubkey)),
298 ),
299 }
300
301 sers, err := db.GetSerialsFromFilter(f)
302 if err != nil {
303 t.Fatalf("GetSerialsFromFilter failed: %v", err)
304 }
305
306 if len(sers) != 1 {
307 t.Errorf("Expected 1 event, got %d", len(sers))
308 }
309
310 t.Logf("GetSerialsFromFilter successfully used p-tag graph optimization, found %d events", len(sers))
311 }
312