1 #include "forward_index.h"
2 #include "index.h"
3 #include "varint.h"
4 #include "spec.h"
5 #include <math.h>
6 #include <limits.h>
7 #include <stdint.h>
8 #include <stdlib.h>
9 #include <sys/param.h>
10 #include "rmalloc.h"
11 #include "rmutil/rm_assert.h"
12 #include "util/heap.h"
13 #include "profile.h"
14
15 static int UI_SkipTo(void *ctx, t_docId docId, RSIndexResult **hit);
16 static int UI_SkipToHigh(void *ctx, t_docId docId, RSIndexResult **hit);
17 static inline int UI_ReadUnsorted(void *ctx, RSIndexResult **hit);
18 static int UI_ReadSorted(void *ctx, RSIndexResult **hit);
19 static int UI_ReadSortedHigh(void *ctx, RSIndexResult **hit);
20 static size_t UI_NumEstimated(void *ctx);
21 static IndexCriteriaTester *UI_GetCriteriaTester(void *ctx);
22 static size_t UI_Len(void *ctx);
23
24 static int II_SkipTo(void *ctx, t_docId docId, RSIndexResult **hit);
25 static int II_ReadUnsorted(void *ctx, RSIndexResult **hit);
26 static IndexCriteriaTester *II_GetCriteriaTester(void *ctx);
27 static int II_ReadSorted(void *ctx, RSIndexResult **hit);
28 static size_t II_NumEstimated(void *ctx);
29 static size_t II_Len(void *ctx);
30 static t_docId II_LastDocId(void *ctx);
31
32 #define CURRENT_RECORD(ii) (ii)->base.current
33
cmpMinId(const void * e1,const void * e2,const void * udata)34 int cmpMinId(const void *e1, const void *e2, const void *udata) {
35 const IndexIterator *it1 = e1, *it2 = e2;
36 if (it1->minId < it2->minId) {
37 return 1;
38 } else if (it1->minId > it2->minId) {
39 return -1;
40 }
41 return 0;
42 }
43
44 typedef struct {
45 IndexIterator base;
46 /**
47 * We maintain two iterator arrays. One is the original iterator list, and
48 * the other is the list of currently active iterators. When an iterator
49 * reaches EOF, it is set to NULL in the `its` list, but is still retained in
50 * the `origits` list, for the purpose of supporting things like Rewind() and
51 * Free()
52 */
53 IndexIterator **its;
54 IndexIterator **origits;
55 uint32_t num;
56 uint32_t norig;
57 uint32_t currIt;
58 t_docId minDocId;
59 heap_t *heapMinId;
60
61 // If set to 1, we exit skips after the first hit found and not merge further results
62 int quickExit;
63 size_t nexpected;
64 double weight;
65 uint64_t len;
66
67 // type of query node UNION,GEO,NUMERIC...
68 QueryNodeType origType;
69 // original string for fuzzy or prefix unions
70 const char *qstr;
71 } UnionIterator;
72
resetMinIdHeap(UnionIterator * ui)73 static void resetMinIdHeap(UnionIterator *ui) {
74 heap_t *hp = ui->heapMinId;
75 heap_clear(hp);
76
77 for (int i = 0; i < ui->num; i++) {
78 heap_offerx(hp, ui->its[i]);
79 }
80 RS_LOG_ASSERT(heap_count(hp) == ui->num,
81 "count should be equal to number of iterators");
82 }
83
UI_HeapAddChildren(UnionIterator * ui,IndexIterator * it)84 static void UI_HeapAddChildren(UnionIterator *ui, IndexIterator *it) {
85 AggregateResult_AddChild(CURRENT_RECORD(ui), IITER_CURRENT_RECORD(it));
86 }
87
UI_LastDocId(void * ctx)88 static inline t_docId UI_LastDocId(void *ctx) {
89 return ((UnionIterator *)ctx)->minDocId;
90 }
91
UI_SyncIterList(UnionIterator * ui)92 static void UI_SyncIterList(UnionIterator *ui) {
93 ui->num = ui->norig;
94 memcpy(ui->its, ui->origits, sizeof(*ui->its) * ui->norig);
95 for (size_t ii = 0; ii < ui->num; ++ii) {
96 ui->its[ii]->minId = 0;
97 }
98 if (ui->heapMinId) {
99 resetMinIdHeap(ui);
100 }
101 }
102
103 /**
104 * Removes the exhausted iterator from the active list, so that future
105 * reads will no longer iterate over it
106 */
UI_RemoveExhausted(UnionIterator * it,size_t badix)107 static size_t UI_RemoveExhausted(UnionIterator *it, size_t badix) {
108 // e.g. assume we have 10 entries, and we want to remove index 8, which means
109 // one more valid entry at the end. This means we use
110 // source: its + 8 + 1
111 // destination: its + 8
112 // number: it->len (10) - (8) - 1 == 1
113 memmove(it->its + badix, it->its + badix + 1, sizeof(*it->its) * (it->num - badix - 1));
114 it->num--;
115 // Repeat the same index again, because we have a new iterator at the same
116 // position
117 return badix - 1;
118 }
119
UI_Abort(void * ctx)120 static void UI_Abort(void *ctx) {
121 UnionIterator *it = ctx;
122 IITER_SET_EOF(&it->base);
123 for (int i = 0; i < it->num; i++) {
124 if (it->its[i]) {
125 it->its[i]->Abort(it->its[i]->ctx);
126 }
127 }
128 }
129
UI_Rewind(void * ctx)130 static void UI_Rewind(void *ctx) {
131 UnionIterator *ui = ctx;
132 IITER_CLEAR_EOF(&ui->base);
133 ui->minDocId = 0;
134 CURRENT_RECORD(ui)->docId = 0;
135
136 UI_SyncIterList(ui);
137
138 // rewind all child iterators
139 for (size_t i = 0; i < ui->num; i++) {
140 ui->its[i]->minId = 0;
141 ui->its[i]->Rewind(ui->its[i]->ctx);
142 }
143 }
144
NewUnionIterator(IndexIterator ** its,int num,DocTable * dt,int quickExit,double weight,QueryNodeType type,const char * qstr)145 IndexIterator *NewUnionIterator(IndexIterator **its, int num, DocTable *dt, int quickExit,
146 double weight, QueryNodeType type, const char *qstr) {
147 // create union context
148 UnionIterator *ctx = rm_calloc(1, sizeof(UnionIterator));
149 ctx->origits = its;
150 ctx->weight = weight;
151 ctx->origType = type;
152 ctx->num = num;
153 ctx->norig = num;
154 IITER_CLEAR_EOF(&ctx->base);
155 CURRENT_RECORD(ctx) = NewUnionResult(num, weight);
156 ctx->len = 0;
157 ctx->quickExit = quickExit;
158 ctx->its = rm_calloc(ctx->num, sizeof(*ctx->its));
159 ctx->nexpected = 0;
160 ctx->currIt = 0;
161 ctx->heapMinId = NULL;
162 ctx->qstr = qstr;
163
164 // bind the union iterator calls
165 IndexIterator *it = &ctx->base;
166 it->mode = MODE_SORTED;
167 it->ctx = ctx;
168 it->type = UNION_ITERATOR;
169 it->GetCriteriaTester = UI_GetCriteriaTester;
170 it->NumEstimated = UI_NumEstimated;
171 it->LastDocId = UI_LastDocId;
172 it->Read = UI_ReadSorted;
173 it->SkipTo = UI_SkipTo;
174 it->HasNext = NULL;
175 it->Free = UnionIterator_Free;
176 it->Len = UI_Len;
177 it->Abort = UI_Abort;
178 it->Rewind = UI_Rewind;
179 UI_SyncIterList(ctx);
180
181 for (size_t i = 0; i < num; ++i) {
182 ctx->nexpected += IITER_NUM_ESTIMATED(its[i]);
183 if (its[i]->mode == MODE_UNSORTED) {
184 it->mode = MODE_UNSORTED;
185 it->Read = UI_ReadUnsorted;
186 }
187 }
188
189 const size_t maxresultsSorted = RSGlobalConfig.maxResultsToUnsortedMode;
190 // this code is normally (and should be) dead.
191 // i.e. the deepest-most IndexIterator does not have a CT
192 // so it will always eventually return NULL CT
193 if (it->mode == MODE_SORTED && ctx->nexpected >= maxresultsSorted) {
194 // make sure all the children support CriteriaTester
195 int ctSupported = 1;
196 for (int i = 0; i < ctx->num; ++i) {
197 IndexCriteriaTester *tester = IITER_GET_CRITERIA_TESTER(ctx->origits[i]);
198 if (!tester) {
199 ctSupported = 0;
200 break;
201 }
202 tester->Free(tester);
203 }
204 if (ctSupported) {
205 it->mode = MODE_UNSORTED;
206 it->Read = UI_ReadUnsorted;
207 }
208 }
209
210 if (it->mode == MODE_SORTED && ctx->norig > RSGlobalConfig.minUnionIterHeap) {
211 it->Read = UI_ReadSortedHigh;
212 it->SkipTo = UI_SkipToHigh;
213 ctx->heapMinId = rm_malloc(heap_sizeof(num));
214 heap_init(ctx->heapMinId, cmpMinId, NULL, num);
215 resetMinIdHeap(ctx);
216 }
217
218 return it;
219 }
220
221 typedef struct {
222 IndexCriteriaTester base;
223 IndexCriteriaTester **children;
224 int nchildren;
225 } UnionCriteriaTester;
226
UI_Test(struct IndexCriteriaTester * ct,t_docId id)227 static int UI_Test(struct IndexCriteriaTester *ct, t_docId id) {
228 UnionCriteriaTester *uct = (UnionCriteriaTester *)ct;
229 for (int i = 0; i < uct->nchildren; ++i) {
230 if (uct->children[i]->Test(uct->children[i], id)) {
231 return 1;
232 }
233 }
234 return 0;
235 }
236
UI_TesterFree(struct IndexCriteriaTester * ct)237 static void UI_TesterFree(struct IndexCriteriaTester *ct) {
238 UnionCriteriaTester *uct = (UnionCriteriaTester *)ct;
239 for (int i = 0; i < uct->nchildren; ++i) {
240 if (uct->children[i]) {
241 uct->children[i]->Free(uct->children[i]);
242 }
243 }
244 rm_free(uct->children);
245 rm_free(uct);
246 }
247
UI_GetCriteriaTester(void * ctx)248 static IndexCriteriaTester *UI_GetCriteriaTester(void *ctx) {
249 UnionIterator *ui = ctx;
250 IndexCriteriaTester **children = rm_malloc(ui->num * sizeof(IndexCriteriaTester *));
251 for (size_t i = 0; i < ui->num; ++i) {
252 children[i] = IITER_GET_CRITERIA_TESTER(ui->origits[i]);
253 if (!children[i]) {
254 for (size_t j = 0; j < i; j++) {
255 children[j]->Free(children[j]);
256 }
257 rm_free(children);
258 return NULL;
259 }
260 }
261 UnionCriteriaTester *ct = rm_malloc(sizeof(*ct));
262 ct->children = children;
263 ct->nchildren = ui->num;
264 ct->base.Test = UI_Test;
265 ct->base.Free = UI_TesterFree;
266 return &ct->base;
267 }
268
UI_NumEstimated(void * ctx)269 static size_t UI_NumEstimated(void *ctx) {
270 UnionIterator *ui = ctx;
271 return ui->nexpected;
272 }
273
UI_ReadUnsorted(void * ctx,RSIndexResult ** hit)274 static inline int UI_ReadUnsorted(void *ctx, RSIndexResult **hit) {
275 UnionIterator *ui = ctx;
276 int rc = INDEXREAD_OK;
277 RSIndexResult *res = NULL;
278 while (ui->currIt < ui->num) {
279 rc = ui->origits[ui->currIt]->Read(ui->origits[ui->currIt]->ctx, &res);
280 if (rc == INDEXREAD_OK) {
281 *hit = res;
282 return rc;
283 }
284 ++ui->currIt;
285 }
286 return INDEXREAD_EOF;
287 }
288
UI_ReadSorted(void * ctx,RSIndexResult ** hit)289 static inline int UI_ReadSorted(void *ctx, RSIndexResult **hit) {
290 UnionIterator *ui = ctx;
291 // nothing to do
292 if (ui->num == 0 || !IITER_HAS_NEXT(&ui->base)) {
293 IITER_SET_EOF(&ui->base);
294 return INDEXREAD_EOF;
295 }
296
297 int numActive = 0;
298 AggregateResult_Reset(CURRENT_RECORD(ui));
299
300 do {
301
302 // find the minimal iterator
303 t_docId minDocId = UINT64_MAX;
304 IndexIterator *minIt = NULL;
305 numActive = 0;
306 int rc = INDEXREAD_EOF;
307 unsigned nits = ui->num;
308
309 for (unsigned i = 0; i < nits; i++) {
310 IndexIterator *it = ui->its[i];
311 RSIndexResult *res = IITER_CURRENT_RECORD(it);
312 rc = INDEXREAD_OK;
313 // if this hit is behind the min id - read the next entry
314 // printf("ui->docIds[%d]: %d, ui->minDocId: %d\n", i, ui->docIds[i], ui->minDocId);
315 while (it->minId <= ui->minDocId && rc != INDEXREAD_EOF) {
316 rc = INDEXREAD_NOTFOUND;
317 // read while we're not at the end and perhaps the flags do not match
318 while (rc == INDEXREAD_NOTFOUND) {
319 rc = it->Read(it->ctx, &res);
320 if (res) {
321 it->minId = res->docId;
322 }
323 }
324 }
325
326 if (rc != INDEXREAD_EOF) {
327 numActive++;
328 } else {
329 // Remove this from the active list
330 i = UI_RemoveExhausted(ui, i);
331 nits = ui->num;
332 continue;
333 }
334
335 if (rc == INDEXREAD_OK && res->docId <= minDocId) {
336 minDocId = res->docId;
337 minIt = it;
338 }
339 }
340
341 // take the minimum entry and collect all results matching to it
342 if (minIt) {
343 UI_SkipTo(ui, minIt->minId, hit);
344 // return INDEXREAD_OK;
345 ui->minDocId = minIt->minId;
346 ui->len++;
347 return INDEXREAD_OK;
348 }
349
350 } while (numActive > 0);
351 IITER_SET_EOF(&ui->base);
352
353 return INDEXREAD_EOF;
354 }
355
356 // UI_Read for iterator with high count of children
UI_ReadSortedHigh(void * ctx,RSIndexResult ** hit)357 static inline int UI_ReadSortedHigh(void *ctx, RSIndexResult **hit) {
358 UnionIterator *ui = ctx;
359 IndexIterator *it = NULL;
360 RSIndexResult *res;
361 heap_t *hp = ui->heapMinId;
362
363 // nothing to do
364 if (!IITER_HAS_NEXT(&ui->base)) {
365 IITER_SET_EOF(&ui->base);
366 return INDEXREAD_EOF;
367 }
368 AggregateResult_Reset(CURRENT_RECORD(ui));
369 t_docId nextValidId = ui->minDocId + 1;
370
371 /*
372 * A min-heap maintains all sub-iterators which are not EOF.
373 * In a loop, the iterator in heap root is checked. If it is valid, it is used,
374 * otherwise, Read() is called on sub-iterator and it is returned into the heap
375 * for future calls.
376 */
377 while (heap_count(hp)) {
378 it = heap_peek(hp);
379 res = IITER_CURRENT_RECORD(it);
380 if (it->minId >= nextValidId && it->minId != 0) {
381 // valid result since id at root of min-heap is higher than union min id
382 break;
383 }
384 // read the next result and if valid, return the iterator into the heap
385 int rc = it->SkipTo(it->ctx, nextValidId, &res);
386
387 // refresh heap with iterator with updated minId
388 it->minId = res->docId;
389 if (rc == INDEXREAD_EOF) {
390 heap_poll(hp);
391 } else {
392 RS_LOG_ASSERT(res, "should not be NULL");
393 heap_replace(hp, it);
394 // after SkipTo, try test again for validity
395 if (ui->quickExit && it->minId == nextValidId) {
396 break;
397 }
398 }
399 }
400
401 if (!heap_count(hp)) {
402 IITER_SET_EOF(&ui->base);
403 return INDEXREAD_EOF;
404 }
405
406 ui->minDocId = it->minId;
407
408 // On quickExit we just return one result.
409 // Otherwise, we collect all the results that equal to the root of the heap.
410 if (ui->quickExit) {
411 AggregateResult_AddChild(CURRENT_RECORD(ui), res);
412 } else {
413 heap_cb_root(hp, (HeapCallback)UI_HeapAddChildren, ui);
414 }
415
416 *hit = CURRENT_RECORD(ui);
417 return INDEXREAD_OK;
418 }
419
420 /**
421 Skip to the given docId, or one place after it
422 @param ctx IndexReader context
423 @param docId docId to seek to
424 @param hit an index hit we put our reads into
425 @return INDEXREAD_OK if found, INDEXREAD_NOTFOUND if not found, INDEXREAD_EOF
426 if
427 at EOF
428 */
UI_SkipTo(void * ctx,t_docId docId,RSIndexResult ** hit)429 static int UI_SkipTo(void *ctx, t_docId docId, RSIndexResult **hit) {
430 UnionIterator *ui = ctx;
431 RS_LOG_ASSERT(ui->base.mode == MODE_SORTED, "union iterator mode is not MODE_SORTED");
432
433 // printf("UI %p skipto %d\n", ui, docId);
434
435 if (docId == 0) {
436 return UI_ReadSorted(ctx, hit);
437 }
438
439 if (!IITER_HAS_NEXT(&ui->base)) {
440 return INDEXREAD_EOF;
441 }
442
443 // reset the current hitf
444 AggregateResult_Reset(CURRENT_RECORD(ui));
445 CURRENT_RECORD(ui)->weight = ui->weight;
446 int numActive = 0;
447 int found = 0;
448 int rc = INDEXREAD_EOF;
449 unsigned num = ui->num;
450 const int quickExit = ui->quickExit;
451 t_docId minDocId = UINT32_MAX;
452 IndexIterator *it;
453 RSIndexResult *res;
454 RSIndexResult *minResult = NULL;
455 // skip all iterators to docId
456 for (unsigned i = 0; i < num; i++) {
457 it = ui->its[i];
458 // this happens for non existent words
459 res = NULL;
460 // If the requested docId is larger than the last read id from the iterator,
461 // we need to read an entry from the iterator, seeking to this docId
462 if (it->minId < docId) {
463 if ((rc = it->SkipTo(it->ctx, docId, &res)) == INDEXREAD_EOF) {
464 i = UI_RemoveExhausted(ui, i);
465 num = ui->num;
466 continue;
467 }
468 if (res) {
469 it->minId = res->docId;
470 }
471 } else {
472 // if the iterator is ahead of docId - we avoid reading the entry
473 // in this case, we are either past or at the requested docId, no need to actually read
474 rc = (it->minId == docId) ? INDEXREAD_OK : INDEXREAD_NOTFOUND;
475 res = IITER_CURRENT_RECORD(it);
476 }
477
478 // if we've read successfully, update the minimal docId we've found
479 if (it->minId && rc != INDEXREAD_EOF) {
480 if (it->minId < minDocId || !minResult) {
481 minResult = res;
482 minDocId = it->minId;
483 }
484 // sminDocId = MIN(ui->docIds[i], minDocId);
485 }
486
487 // we found a hit - continue to all results matching the same docId
488 if (rc == INDEXREAD_OK) {
489
490 // add the result to the aggregate result we are holding
491 if (hit) {
492 AggregateResult_AddChild(CURRENT_RECORD(ui), res ? res : IITER_CURRENT_RECORD(it));
493 }
494 ui->minDocId = it->minId;
495 ++found;
496 }
497 ++numActive;
498 // If we've found a single entry and we are iterating in quick exit mode - exit now
499 if (found && quickExit) break;
500 }
501
502 // all iterators are at the end
503 if (numActive == 0) {
504 IITER_SET_EOF(&ui->base);
505 return INDEXREAD_EOF;
506 }
507
508 // copy our aggregate to the upstream hit
509 *hit = CURRENT_RECORD(ui);
510 if (found > 0) {
511 return INDEXREAD_OK;
512 }
513 if (minResult) {
514 *hit = minResult;
515 AggregateResult_AddChild(CURRENT_RECORD(ui), minResult);
516 }
517 // not found...
518 ui->minDocId = minDocId;
519 return INDEXREAD_NOTFOUND;
520 }
521
522 // UI_SkipTo for iterator with high count of children
UI_SkipToHigh(void * ctx,t_docId docId,RSIndexResult ** hit)523 static int UI_SkipToHigh(void *ctx, t_docId docId, RSIndexResult **hit) {
524 UnionIterator *ui = ctx;
525 RS_LOG_ASSERT(ui->base.mode == MODE_SORTED, "union iterator mode is not MODE_SORTED");
526
527 // printf("UI %p skipto %d\n", ui, docId);
528
529 if (docId == 0) {
530 return UI_ReadSorted(ctx, hit);
531 }
532
533 if (!IITER_HAS_NEXT(&ui->base)) {
534 return INDEXREAD_EOF;
535 }
536
537 AggregateResult_Reset(CURRENT_RECORD(ui));
538 CURRENT_RECORD(ui)->weight = ui->weight;
539 int rc = INDEXREAD_EOF;
540 IndexIterator *it = NULL;
541 RSIndexResult *res;
542 heap_t *hp = ui->heapMinId;
543
544 while (heap_count(hp)) {
545 it = heap_peek(hp);
546 if (it->minId >= docId) {
547 // if the iterator is at or ahead of docId - we avoid reading the entry
548 // in this case, we are either past or at the requested docId, no need to actually read
549 break;
550 }
551
552 rc = it->SkipTo(it->ctx, docId, &res);
553 if (rc == INDEXREAD_EOF) {
554 heap_poll(hp); // return value was already received from heap_peak
555 // iterator is not returned to heap
556 continue;
557 }
558 RS_LOG_ASSERT(res, "should not be NULL");
559
560 // refresh heap with iterator with updated minId
561 it->minId = res->docId;
562 heap_replace(hp, it);
563 if (ui->quickExit && it->minId == docId) {
564 break;
565 }
566 }
567
568 if (heap_count(hp) == 0) {
569 IITER_SET_EOF(&ui->base);
570 return INDEXREAD_EOF;
571 }
572
573 rc = (it->minId == docId) ? INDEXREAD_OK : INDEXREAD_NOTFOUND;
574
575 // On quickExit we just return one result.
576 // Otherwise, we collect all the results that equal to the root of the heap.
577 if (ui->quickExit) {
578 AggregateResult_AddChild(CURRENT_RECORD(ui), IITER_CURRENT_RECORD(it));
579 } else {
580 heap_cb_root(hp, (HeapCallback)UI_HeapAddChildren, ui);
581 }
582
583 *hit = CURRENT_RECORD(ui);
584 return rc;
585 }
586
UnionIterator_Free(IndexIterator * itbase)587 void UnionIterator_Free(IndexIterator *itbase) {
588 if (itbase == NULL) return;
589
590 UnionIterator *ui = itbase->ctx;
591 for (int i = 0; i < ui->norig; i++) {
592 IndexIterator *it = ui->origits[i];
593 if (it) {
594 it->Free(it);
595 }
596 }
597
598 IndexResult_Free(CURRENT_RECORD(ui));
599 if (ui->heapMinId) heap_free(ui->heapMinId);
600 rm_free(ui->its);
601 rm_free(ui->origits);
602 rm_free(ui);
603 }
604
UI_Len(void * ctx)605 static size_t UI_Len(void *ctx) {
606 return ((UnionIterator *)ctx)->len;
607 }
608
609 /* The context used by the intersection methods during iterating an intersect
610 * iterator */
611 typedef struct {
612 IndexIterator base;
613 IndexIterator **its;
614 IndexIterator *bestIt;
615 IndexCriteriaTester **testers;
616 t_docId *docIds;
617 int *rcs;
618 unsigned num;
619 size_t len;
620 int maxSlop;
621 int inOrder;
622 // the last read docId from any child
623 t_docId lastDocId;
624 // the last id that was found on all children
625 t_docId lastFoundId;
626
627 // RSIndexResult *result;
628 DocTable *docTable;
629 t_fieldMask fieldMask;
630 double weight;
631 size_t nexpected;
632 } IntersectIterator;
633
IntersectIterator_Free(IndexIterator * it)634 void IntersectIterator_Free(IndexIterator *it) {
635 if (it == NULL) return;
636 IntersectIterator *ui = it->ctx;
637 for (int i = 0; i < ui->num; i++) {
638 if (ui->its[i] != NULL) {
639 ui->its[i]->Free(ui->its[i]);
640 }
641 // IndexResult_Free(&ui->currentHits[i]);
642 }
643
644 for (int i = 0; i < array_len(ui->testers); i++) {
645 if (ui->testers[i]) {
646 ui->testers[i]->Free(ui->testers[i]);
647 }
648 }
649 if (ui->bestIt) {
650 ui->bestIt->Free(ui->bestIt);
651 }
652
653 rm_free(ui->docIds);
654 rm_free(ui->its);
655 IndexResult_Free(it->current);
656 array_free(ui->testers);
657 rm_free(it);
658 }
659
II_Abort(void * ctx)660 static void II_Abort(void *ctx) {
661 IntersectIterator *it = ctx;
662 it->base.isValid = 0;
663 for (int i = 0; i < it->num; i++) {
664 if (it->its[i]) {
665 it->its[i]->Abort(it->its[i]->ctx);
666 }
667 }
668 }
669
II_Rewind(void * ctx)670 static void II_Rewind(void *ctx) {
671 IntersectIterator *ii = ctx;
672 ii->base.isValid = 1;
673 ii->lastDocId = 0;
674
675 // rewind all child iterators
676 for (int i = 0; i < ii->num; i++) {
677 ii->docIds[i] = 0;
678 if (ii->its[i]) {
679 ii->its[i]->Rewind(ii->its[i]->ctx);
680 }
681 }
682 }
683
684 typedef int (*CompareFunc)(const void *a, const void *b);
cmpIter(IndexIterator ** it1,IndexIterator ** it2)685 static int cmpIter(IndexIterator **it1, IndexIterator **it2) {
686 if (!*it1 && !*it2) return 0;
687 if (!*it1) return -1;
688 if (!*it2) return 1;
689
690 return (int)((*it1)->NumEstimated((*it1)->ctx) - (*it2)->NumEstimated((*it2)->ctx));
691 }
692
II_SortChildren(IntersectIterator * ctx)693 static void II_SortChildren(IntersectIterator *ctx) {
694 /**
695 * 1. Go through all the iterators, ensuring none of them is NULL
696 * (replace with empty if indeed NULL)
697 * 2. If all the iterators are unsorted then set the mode to UNSORTED
698 * 3. If all or any of the iterators are sorted, then remove the
699 * unsorted iterators from the equation, simply adding them to the
700 * tester list
701 */
702 IndexIterator **unsortedIts = NULL;
703 IndexIterator **sortedIts = rm_malloc(sizeof(IndexIterator *) * ctx->num);
704 size_t sortedItsSize = 0;
705 for (size_t i = 0; i < ctx->num; ++i) {
706 IndexIterator *curit = ctx->its[i];
707
708 if (!curit) {
709 // If the current iterator is empty, then the entire
710 // query will fail; just free all the iterators and call it good
711 if (sortedIts) {
712 rm_free(sortedIts);
713 }
714 if (unsortedIts) {
715 array_free(unsortedIts);
716 }
717 ctx->bestIt = NULL;
718 return;
719 }
720
721 size_t amount = IITER_NUM_ESTIMATED(curit);
722 if (amount < ctx->nexpected) {
723 ctx->nexpected = amount;
724 ctx->bestIt = curit;
725 }
726
727 if (curit->mode == MODE_UNSORTED) {
728 unsortedIts = array_ensure_append(unsortedIts, &curit, 1, IndexIterator *);
729 } else {
730 sortedIts[sortedItsSize++] = curit;
731 }
732 }
733
734 if (unsortedIts) {
735 if (array_len(unsortedIts) == ctx->num) {
736 ctx->base.mode = MODE_UNSORTED;
737 ctx->base.Read = II_ReadUnsorted;
738 ctx->num = 1;
739 ctx->its[0] = ctx->bestIt;
740 // The other iterators are also stored in unsortedIts
741 // and because we know that there are no sorted iterators
742 }
743
744 for (size_t ii = 0; ii < array_len(unsortedIts); ++ii) {
745 IndexIterator *cur = unsortedIts[ii];
746 if (ctx->base.mode == MODE_UNSORTED && ctx->bestIt == cur) {
747 continue;
748 }
749 IndexCriteriaTester *tester = IITER_GET_CRITERIA_TESTER(cur);
750 ctx->testers = array_ensure_append(ctx->testers, &tester, 1, IndexCriteriaTester *);
751 cur->Free(cur);
752 }
753 } else {
754 ctx->bestIt = NULL;
755 }
756
757 rm_free(ctx->its);
758 ctx->its = sortedIts;
759 ctx->num = sortedItsSize;
760 array_free(unsortedIts);
761 }
762
NewIntersecIterator(IndexIterator ** its_,size_t num,DocTable * dt,t_fieldMask fieldMask,int maxSlop,int inOrder,double weight)763 IndexIterator *NewIntersecIterator(IndexIterator **its_, size_t num, DocTable *dt,
764 t_fieldMask fieldMask, int maxSlop, int inOrder, double weight) {
765 // printf("Creating new intersection iterator with fieldMask=%llx\n", fieldMask);
766 IntersectIterator *ctx = rm_calloc(1, sizeof(*ctx));
767 ctx->lastDocId = 0;
768 ctx->lastFoundId = 0;
769 ctx->len = 0;
770 ctx->maxSlop = maxSlop;
771 ctx->inOrder = inOrder;
772 ctx->fieldMask = fieldMask;
773 ctx->weight = weight;
774 ctx->docIds = rm_calloc(num, sizeof(t_docId));
775 ctx->docTable = dt;
776 ctx->nexpected = UINT32_MAX;
777
778 ctx->base.isValid = 1;
779 ctx->base.current = NewIntersectResult(num, weight);
780 ctx->its = its_;
781 ctx->num = num;
782
783 // Sort children iterators from low count to high count which reduces the number of iterations.
784 if (!ctx->inOrder) {
785 qsort(ctx->its, ctx->num, sizeof(*ctx->its), (CompareFunc)cmpIter);
786 }
787
788 // bind the iterator calls
789 IndexIterator *it = &ctx->base;
790 it->ctx = ctx;
791 it->type = INTERSECT_ITERATOR;
792 it->LastDocId = II_LastDocId;
793 it->NumEstimated = II_NumEstimated;
794 it->GetCriteriaTester = II_GetCriteriaTester;
795 it->Read = II_ReadSorted;
796 it->SkipTo = II_SkipTo;
797 it->Len = II_Len;
798 it->Free = IntersectIterator_Free;
799 it->Abort = II_Abort;
800 it->Rewind = II_Rewind;
801 it->HasNext = NULL;
802 it->mode = MODE_SORTED;
803 II_SortChildren(ctx);
804 return it;
805 }
806
II_SkipTo(void * ctx,t_docId docId,RSIndexResult ** hit)807 static int II_SkipTo(void *ctx, t_docId docId, RSIndexResult **hit) {
808 /* A seek with docId 0 is equivalent to a read */
809 if (docId == 0) {
810 return II_ReadSorted(ctx, hit);
811 }
812 IntersectIterator *ic = ctx;
813 AggregateResult_Reset(ic->base.current);
814 int nfound = 0;
815
816 int rc = INDEXREAD_EOF;
817 // skip all iterators to docId
818 for (int i = 0; i < ic->num; i++) {
819 IndexIterator *it = ic->its[i];
820
821 if (!it || !IITER_HAS_NEXT(it)) return INDEXREAD_EOF;
822
823 RSIndexResult *res = IITER_CURRENT_RECORD(it);
824 rc = INDEXREAD_OK;
825
826 // only read if we are not already at the seek to position
827 if (ic->docIds[i] != docId) {
828 rc = it->SkipTo(it->ctx, docId, &res);
829 if (rc != INDEXREAD_EOF) {
830 if (res) ic->docIds[i] = res->docId;
831 }
832 }
833
834 if (rc == INDEXREAD_EOF) {
835 // we are at the end!
836 ic->base.isValid = 0;
837 return rc;
838 } else if (rc == INDEXREAD_OK) {
839 // YAY! found!
840 if (res) {
841 AggregateResult_AddChild(ic->base.current, res);
842 }
843 ic->lastDocId = docId;
844
845 ++nfound;
846 } else if (ic->docIds[i] > ic->lastDocId) {
847 ic->lastDocId = ic->docIds[i];
848 break;
849 }
850 }
851
852 // unless we got an EOF - we put the current record into hit
853
854 // if the requested id was found on all children - we return OK
855 if (nfound == ic->num) {
856 // printf("Skipto %d hit @%d\n", docId, ic->current->docId);
857
858 // Update the last found id
859 // if maxSlop == -1 there is no need to verify maxSlop and inorder, otherwise lets verify
860 if (ic->maxSlop == -1 ||
861 IndexResult_IsWithinRange(ic->base.current, ic->maxSlop, ic->inOrder)) {
862 ic->lastFoundId = ic->base.current->docId;
863 if (hit) *hit = ic->base.current;
864 return INDEXREAD_OK;
865 }
866 }
867
868 // Not found - but we need to read the next valid result into hit
869 rc = II_ReadSorted(ic, hit);
870 // this might have brought us to our end, in which case we just terminate
871 if (rc == INDEXREAD_EOF) return INDEXREAD_EOF;
872
873 // otherwise - not found
874 return INDEXREAD_NOTFOUND;
875 }
876
II_ReadUnsorted(void * ctx,RSIndexResult ** hit)877 static int II_ReadUnsorted(void *ctx, RSIndexResult **hit) {
878 IntersectIterator *ic = ctx;
879 int rc = INDEXREAD_OK;
880 RSIndexResult *res = NULL;
881 while (1) {
882 rc = ic->bestIt->Read(ic->bestIt->ctx, &res);
883 if (rc == INDEXREAD_EOF) {
884 return INDEXREAD_EOF;
885 }
886 int isMatch = 1;
887 for (size_t i = 0; i < array_len(ic->testers); ++i) {
888 if (!ic->testers[i]->Test(ic->testers[i], res->docId)) {
889 isMatch = 0;
890 break;
891 }
892 }
893 if (!isMatch) {
894 continue;
895 }
896 *hit = res;
897 return rc;
898 }
899 }
900
901 typedef struct {
902 IndexCriteriaTester base;
903 IndexCriteriaTester **children;
904 } IICriteriaTester;
905
II_Test(struct IndexCriteriaTester * ct,t_docId id)906 static int II_Test(struct IndexCriteriaTester *ct, t_docId id) {
907 IICriteriaTester *ict = (IICriteriaTester *)ct;
908 for (size_t i = 0; i < array_len(ict->children); ++i) {
909 if (!ict->children[i]->Test(ict->children[i], id)) {
910 return 0;
911 }
912 }
913 return 1;
914 }
915
II_TesterFree(struct IndexCriteriaTester * ct)916 static void II_TesterFree(struct IndexCriteriaTester *ct) {
917 IICriteriaTester *ict = (IICriteriaTester *)ct;
918 for (size_t i = 0; i < array_len(ict->children); ++i) {
919 ict->children[i]->Free(ict->children[i]);
920 }
921 array_free(ict->children);
922 rm_free(ict);
923 }
924
II_GetCriteriaTester(void * ctx)925 static IndexCriteriaTester *II_GetCriteriaTester(void *ctx) {
926 IntersectIterator *ic = ctx;
927 for (size_t i = 0; i < ic->num; ++i) {
928 IndexCriteriaTester *tester = NULL;
929 if (ic->its[i]) {
930 tester = IITER_GET_CRITERIA_TESTER(ic->its[i]);
931 }
932 if (!tester) {
933 for (int j = 0; j < i; j++) {
934 ic->testers[j]->Free(ic->testers[j]);
935 }
936 array_free(ic->testers);
937 return NULL;
938 }
939 ic->testers = array_ensure_append(ic->testers, tester, 1, IndexCriteriaTester *);
940 }
941 IICriteriaTester *ict = rm_malloc(sizeof(*ict));
942 ict->children = ic->testers;
943 ic->testers = NULL;
944 ict->base.Test = II_Test;
945 ict->base.Free = II_TesterFree;
946 return &ict->base;
947 }
948
II_NumEstimated(void * ctx)949 static size_t II_NumEstimated(void *ctx) {
950 IntersectIterator *ic = ctx;
951 return ic->nexpected;
952 }
953
II_ReadSorted(void * ctx,RSIndexResult ** hit)954 static int II_ReadSorted(void *ctx, RSIndexResult **hit) {
955 IntersectIterator *ic = ctx;
956 if (ic->num == 0) return INDEXREAD_EOF;
957
958 int nh = 0;
959 int i = 0;
960
961 do {
962 nh = 0;
963 AggregateResult_Reset(ic->base.current);
964
965 for (i = 0; i < ic->num; i++) {
966 IndexIterator *it = ic->its[i];
967
968 if (!it) goto eof;
969
970 RSIndexResult *h = IITER_CURRENT_RECORD(it);
971 // skip to the next
972 int rc = INDEXREAD_OK;
973 if (ic->docIds[i] != ic->lastDocId || ic->lastDocId == 0) {
974
975 if (i == 0 && ic->docIds[i] >= ic->lastDocId) {
976 rc = it->Read(it->ctx, &h);
977 } else {
978 rc = it->SkipTo(it->ctx, ic->lastDocId, &h);
979 }
980 // printf("II %p last docId %d, it %d read docId %d(%d), rc %d\n", ic, ic->lastDocId, i,
981 // h->docId, it->LastDocId(it->ctx), rc);
982
983 if (rc == INDEXREAD_EOF) goto eof;
984 ic->docIds[i] = h->docId;
985 }
986
987 if (ic->docIds[i] > ic->lastDocId) {
988 ic->lastDocId = ic->docIds[i];
989 break;
990 }
991 if (rc == INDEXREAD_OK) {
992 ++nh;
993 AggregateResult_AddChild(ic->base.current, h);
994 } else {
995 ic->lastDocId++;
996 }
997 }
998
999 if (nh == ic->num) {
1000 // printf("II %p HIT @ %d\n", ic, ic->current->docId);
1001 // sum up all hits
1002 if (hit != NULL) {
1003 *hit = ic->base.current;
1004 }
1005 // Update the last valid found id
1006 ic->lastFoundId = ic->base.current->docId;
1007
1008 // advance the doc id so next time we'll read a new record
1009 ic->lastDocId++;
1010
1011 // // make sure the flags are matching.
1012 if ((ic->base.current->fieldMask & ic->fieldMask) == 0) {
1013 // printf("Field masks don't match!\n");
1014 continue;
1015 }
1016
1017 // If we need to match slop and order, we do it now, and possibly skip the result
1018 if (ic->maxSlop >= 0) {
1019 // printf("Checking SLOP... (%d)\n", ic->maxSlop);
1020 if (!IndexResult_IsWithinRange(ic->base.current, ic->maxSlop, ic->inOrder)) {
1021 // printf("Not within range!\n");
1022 continue;
1023 }
1024 }
1025
1026 // for(size_t i = 0 ; i < array_len(ic->testers) ; ++i){
1027 // if(!ic->testers[i]->TextCriteria(ic->testers[i]->ctx, ic->lastFoundId)){
1028 // continue;
1029 // }
1030 // }
1031
1032 ic->len++;
1033 // printf("Returning OK\n");
1034 return INDEXREAD_OK;
1035 }
1036 } while (1);
1037 eof:
1038 ic->base.isValid = 0;
1039 return INDEXREAD_EOF;
1040 }
1041
II_LastDocId(void * ctx)1042 static t_docId II_LastDocId(void *ctx) {
1043 // return last FOUND id, not last read id form any child
1044 return ((IntersectIterator *)ctx)->lastFoundId;
1045 }
1046
II_Len(void * ctx)1047 static size_t II_Len(void *ctx) {
1048 return ((IntersectIterator *)ctx)->len;
1049 }
1050
1051 /* A Not iterator works by wrapping another iterator, and returning OK for misses, and NOTFOUND
1052 * for hits */
1053 typedef struct {
1054 IndexIterator base;
1055 IndexIterator *child;
1056 IndexCriteriaTester *childCT;
1057 t_docId lastDocId;
1058 t_docId maxDocId;
1059 size_t len;
1060 double weight;
1061 } NotIterator, NotContext;
1062
NI_Abort(void * ctx)1063 static void NI_Abort(void *ctx) {
1064 NotContext *nc = ctx;
1065 nc->child->Abort(nc->child->ctx);
1066 }
1067
NI_Rewind(void * ctx)1068 static void NI_Rewind(void *ctx) {
1069 NotContext *nc = ctx;
1070 nc->lastDocId = 0;
1071 nc->base.current->docId = 0;
1072 nc->child->Rewind(nc->child->ctx);
1073 }
1074
NI_Free(IndexIterator * it)1075 static void NI_Free(IndexIterator *it) {
1076
1077 NotContext *nc = it->ctx;
1078 nc->child->Free(nc->child);
1079
1080 if (nc->childCT) {
1081 nc->childCT->Free(nc->childCT);
1082 }
1083 IndexResult_Free(nc->base.current);
1084 rm_free(it);
1085 }
1086
1087 /* SkipTo for NOT iterator. If we have a match - return NOTFOUND. If we don't or we're at the end
1088 * - return OK */
NI_SkipTo(void * ctx,t_docId docId,RSIndexResult ** hit)1089 static int NI_SkipTo(void *ctx, t_docId docId, RSIndexResult **hit) {
1090 NotContext *nc = ctx;
1091
1092 // do not skip beyond max doc id
1093 if (docId > nc->maxDocId) {
1094 return INDEXREAD_EOF;
1095 }
1096
1097 // Get the child's last read docId
1098 t_docId childId = nc->child->LastDocId(nc->child->ctx);
1099
1100 // If the child is ahead of the skipto id, it means the child doesn't have this id.
1101 // So we are okay!
1102 if (childId > docId) {
1103 goto ok;
1104 }
1105
1106 // If the child docId is the one we are looking for, it's an anti match!
1107 if (childId == docId) {
1108 nc->base.current->docId = docId;
1109 nc->lastDocId = docId;
1110 *hit = nc->base.current;
1111 return INDEXREAD_NOTFOUND;
1112 }
1113
1114 // read the next entry from the child
1115 int rc = nc->child->SkipTo(nc->child->ctx, docId, hit);
1116
1117 // OK means not found
1118 if (rc == INDEXREAD_OK) {
1119 return INDEXREAD_NOTFOUND;
1120 }
1121
1122 ok:
1123 // NOT FOUND or end means OK. We need to set the docId on the hit we will bubble up
1124 nc->base.current->docId = docId;
1125 nc->lastDocId = docId;
1126 *hit = nc->base.current;
1127 return INDEXREAD_OK;
1128 }
1129
1130 typedef struct {
1131 IndexCriteriaTester base;
1132 IndexCriteriaTester *child;
1133 } NI_CriteriaTester;
1134
NI_Test(struct IndexCriteriaTester * ct,t_docId id)1135 static int NI_Test(struct IndexCriteriaTester *ct, t_docId id) {
1136 NI_CriteriaTester *nct = (NI_CriteriaTester *)ct;
1137 return !nct->child->Test(nct->child, id);
1138 }
NI_TesterFree(struct IndexCriteriaTester * ct)1139 static void NI_TesterFree(struct IndexCriteriaTester *ct) {
1140 NI_CriteriaTester *nct = (NI_CriteriaTester *)ct;
1141 nct->child->Free(nct->child);
1142 rm_free(nct);
1143 }
1144
NI_GetCriteriaTester(void * ctx)1145 static IndexCriteriaTester *NI_GetCriteriaTester(void *ctx) {
1146 NotContext *nc = ctx;
1147 IndexCriteriaTester *ct = IITER_GET_CRITERIA_TESTER(nc->child);
1148 if (!ct) {
1149 return NULL;
1150 }
1151 NI_CriteriaTester *nct = rm_malloc(sizeof(*nct));
1152 nct->child = ct;
1153 nct->base.Test = NI_Test;
1154 nct->base.Free = NI_TesterFree;
1155 return &nct->base;
1156 }
1157
NI_NumEstimated(void * ctx)1158 static size_t NI_NumEstimated(void *ctx) {
1159 NotContext *nc = ctx;
1160 return nc->maxDocId;
1161 }
1162
NI_ReadUnsorted(void * ctx,RSIndexResult ** hit)1163 static int NI_ReadUnsorted(void *ctx, RSIndexResult **hit) {
1164 NotContext *nc = ctx;
1165 while (nc->lastDocId > nc->maxDocId) {
1166 if (!nc->childCT->Test(nc->childCT, nc->lastDocId)) {
1167 nc->base.current->docId = nc->lastDocId;
1168 *hit = nc->base.current;
1169 ++nc->lastDocId;
1170 return INDEXREAD_OK;
1171 }
1172 ++nc->lastDocId;
1173 }
1174 return INDEXREAD_EOF;
1175 }
1176
1177 /* Read from a NOT iterator. This is applicable only if the only or leftmost node of a query is a
1178 * NOT node. We simply read until max docId, skipping docIds that exist in the child*/
NI_ReadSorted(void * ctx,RSIndexResult ** hit)1179 static int NI_ReadSorted(void *ctx, RSIndexResult **hit) {
1180 NotContext *nc = ctx;
1181 if (nc->lastDocId > nc->maxDocId) return INDEXREAD_EOF;
1182
1183 RSIndexResult *cr = NULL;
1184 // if we have a child, get the latest result from the child
1185 cr = IITER_CURRENT_RECORD(nc->child);
1186
1187 if (cr == NULL || cr->docId == 0) {
1188 nc->child->Read(nc->child->ctx, &cr);
1189 }
1190
1191 // advance our reader by one, and let's test if it's a valid value or not
1192 nc->base.current->docId++;
1193
1194 // If we don't have a child result, or the child result is ahead of the current counter,
1195 // we just increment our virtual result's id until we hit the child result's
1196 // in which case we'll read from the child and bypass it by one.
1197 if (cr == NULL || cr->docId > nc->base.current->docId) {
1198 goto ok;
1199 }
1200
1201 while (cr->docId == nc->base.current->docId) {
1202 // advance our docId to the next possible id
1203 nc->base.current->docId++;
1204
1205 // read the next entry from the child
1206 if (nc->child->Read(nc->child->ctx, &cr) == INDEXREAD_EOF) {
1207 break;
1208 }
1209 }
1210
1211 // make sure we did not overflow
1212 if (nc->base.current->docId > nc->maxDocId) {
1213 return INDEXREAD_EOF;
1214 }
1215
1216 ok:
1217 // Set the next entry and return ok
1218 nc->lastDocId = nc->base.current->docId;
1219 if (hit) *hit = nc->base.current;
1220 ++nc->len;
1221
1222 return INDEXREAD_OK;
1223 }
1224
1225 /* We always have next, in case anyone asks... ;) */
NI_HasNext(void * ctx)1226 static int NI_HasNext(void *ctx) {
1227 NotContext *nc = ctx;
1228
1229 return nc->lastDocId <= nc->maxDocId;
1230 }
1231
1232 /* Our len is the child's len? TBD it might be better to just return 0 */
NI_Len(void * ctx)1233 static size_t NI_Len(void *ctx) {
1234 NotContext *nc = ctx;
1235 return nc->len;
1236 }
1237
1238 /* Last docId */
NI_LastDocId(void * ctx)1239 static t_docId NI_LastDocId(void *ctx) {
1240 NotContext *nc = ctx;
1241
1242 return nc->lastDocId;
1243 }
1244
NewNotIterator(IndexIterator * it,t_docId maxDocId,double weight)1245 IndexIterator *NewNotIterator(IndexIterator *it, t_docId maxDocId, double weight) {
1246 NotContext *nc = rm_malloc(sizeof(*nc));
1247 nc->base.current = NewVirtualResult(weight);
1248 nc->base.current->fieldMask = RS_FIELDMASK_ALL;
1249 nc->base.current->docId = 0;
1250 nc->child = it ? it : NewEmptyIterator();
1251 nc->childCT = NULL;
1252 nc->lastDocId = 0;
1253 nc->maxDocId = maxDocId;
1254 nc->len = 0;
1255 nc->weight = weight;
1256
1257 IndexIterator *ret = &nc->base;
1258 ret->ctx = nc;
1259 ret->type = NOT_ITERATOR;
1260 ret->GetCriteriaTester = NI_GetCriteriaTester;
1261 ret->NumEstimated = NI_NumEstimated;
1262 ret->Free = NI_Free;
1263 ret->HasNext = NI_HasNext;
1264 ret->LastDocId = NI_LastDocId;
1265 ret->Len = NI_Len;
1266 ret->Read = NI_ReadSorted;
1267 ret->SkipTo = NI_SkipTo;
1268 ret->Abort = NI_Abort;
1269 ret->Rewind = NI_Rewind;
1270 ret->mode = MODE_SORTED;
1271
1272 if (nc->child->mode == MODE_UNSORTED) {
1273 nc->childCT = IITER_GET_CRITERIA_TESTER(nc->child);
1274 RS_LOG_ASSERT(nc->childCT, "childCT should not be NULL");
1275 ret->Read = NI_ReadUnsorted;
1276 }
1277
1278 return ret;
1279 }
1280
1281 /**********************************************************
1282 * Optional clause iterator
1283 **********************************************************/
1284
1285 typedef struct {
1286 IndexIterator base;
1287 IndexIterator *child;
1288 IndexCriteriaTester *childCT;
1289 RSIndexResult *virt;
1290 t_fieldMask fieldMask;
1291 t_docId lastDocId;
1292 t_docId maxDocId;
1293 t_docId nextRealId;
1294 double weight;
1295 } OptionalMatchContext, OptionalIterator;
1296
OI_Free(IndexIterator * it)1297 static void OI_Free(IndexIterator *it) {
1298 OptionalMatchContext *nc = it->ctx;
1299 if (nc->child) {
1300 nc->child->Free(nc->child);
1301 }
1302 if (nc->childCT) {
1303 nc->childCT->Free(nc->childCT);
1304 }
1305 IndexResult_Free(nc->virt);
1306 rm_free(it);
1307 }
1308
OI_SkipTo(void * ctx,t_docId docId,RSIndexResult ** hit)1309 static int OI_SkipTo(void *ctx, t_docId docId, RSIndexResult **hit) {
1310 OptionalMatchContext *nc = ctx;
1311 // printf("OI_SkipTo => %llu!. NextReal: %llu. Max: %llu. Last: %llu\n", docId, nc->nextRealId,
1312 // nc->maxDocId, nc->lastDocId);
1313
1314 int found = 0;
1315
1316 // Set the current ID
1317 nc->lastDocId = docId;
1318
1319 if (nc->lastDocId > nc->maxDocId) {
1320 return INDEXREAD_EOF;
1321 }
1322
1323 if (!nc->child) {
1324 nc->virt->docId = docId;
1325 nc->base.current = nc->virt;
1326 return INDEXREAD_OK;
1327 }
1328
1329 if (docId == 0) {
1330 return nc->base.Read(ctx, hit);
1331 }
1332
1333 if (docId == nc->nextRealId) {
1334 // Edge case -- match on the docid we just looked for
1335 found = 1;
1336 // reset current pointer since this might have been a prior
1337 // virt return
1338 nc->base.current = nc->child->current;
1339
1340 } else if (docId > nc->nextRealId) {
1341 int rc = nc->child->SkipTo(nc->child->ctx, docId, &nc->base.current);
1342 if (rc == INDEXREAD_OK) {
1343 found = 1;
1344 }
1345 if (nc->base.current) {
1346 nc->nextRealId = nc->base.current->docId;
1347 }
1348 }
1349
1350 if (found) {
1351 // Has a real hit
1352 RSIndexResult *r = nc->base.current;
1353 } else {
1354 nc->virt->docId = docId;
1355 nc->base.current = nc->virt;
1356 }
1357
1358 *hit = nc->base.current;
1359 return INDEXREAD_OK;
1360 }
1361
OI_Test(struct IndexCriteriaTester * ct,t_docId id)1362 static int OI_Test(struct IndexCriteriaTester *ct, t_docId id) {
1363 return 1;
1364 }
1365
OI_TesterFree(struct IndexCriteriaTester * ct)1366 static void OI_TesterFree(struct IndexCriteriaTester *ct) {
1367 rm_free(ct);
1368 }
1369
OI_GetCriteriaTester(void * ctx)1370 static IndexCriteriaTester *OI_GetCriteriaTester(void *ctx) {
1371 IndexCriteriaTester *tester = rm_malloc(sizeof(*tester));
1372 tester->Test = OI_Test;
1373 tester->Free = OI_TesterFree;
1374 return tester;
1375 }
1376
OI_NumEstimated(void * ctx)1377 static size_t OI_NumEstimated(void *ctx) {
1378 OptionalMatchContext *nc = ctx;
1379 return nc->maxDocId;
1380 }
1381
OI_ReadUnsorted(void * ctx,RSIndexResult ** hit)1382 static int OI_ReadUnsorted(void *ctx, RSIndexResult **hit) {
1383 OptionalMatchContext *nc = ctx;
1384 if (nc->lastDocId >= nc->maxDocId) return INDEXREAD_EOF;
1385 nc->lastDocId++;
1386 nc->base.current = nc->virt;
1387 nc->base.current->docId = nc->lastDocId;
1388 *hit = nc->base.current;
1389 if (nc->childCT->Test(nc->childCT, nc->lastDocId)) {
1390 nc->base.current->weight = nc->weight * 2; // we increase the weight cause we found the id
1391 } else {
1392 nc->base.current->weight = nc->weight * 2; // we do increase the weight cause id was not found
1393 }
1394 return INDEXREAD_OK;
1395 }
1396
1397 /* Read has no meaning in the sense of an OPTIONAL iterator, so we just read the next record from
1398 * our child */
OI_ReadSorted(void * ctx,RSIndexResult ** hit)1399 static int OI_ReadSorted(void *ctx, RSIndexResult **hit) {
1400 OptionalMatchContext *nc = ctx;
1401 if (nc->lastDocId >= nc->maxDocId) {
1402 return INDEXREAD_EOF;
1403 }
1404
1405 // Increase the size by one
1406 nc->lastDocId++;
1407
1408 if (nc->lastDocId > nc->nextRealId) {
1409 int rc = nc->child->Read(nc->child->ctx, &nc->base.current);
1410 if (rc == INDEXREAD_EOF) {
1411 nc->nextRealId = nc->maxDocId + 1;
1412 } else {
1413 nc->nextRealId = nc->base.current->docId;
1414 }
1415 }
1416
1417 if (nc->lastDocId != nc->nextRealId) {
1418 nc->base.current = nc->virt;
1419 nc->base.current->weight = 0;
1420 } else {
1421 nc->base.current->weight = nc->weight;
1422 }
1423
1424 nc->base.current->docId = nc->lastDocId;
1425 *hit = nc->base.current;
1426 return INDEXREAD_OK;
1427 }
1428
1429 /* We always have next, in case anyone asks... ;) */
OI_HasNext(void * ctx)1430 static int OI_HasNext(void *ctx) {
1431 OptionalMatchContext *nc = ctx;
1432 return (nc->lastDocId <= nc->maxDocId);
1433 }
1434
OI_Abort(void * ctx)1435 static void OI_Abort(void *ctx) {
1436 OptionalMatchContext *nc = ctx;
1437 if (nc->child) {
1438 nc->child->Abort(nc->child->ctx);
1439 }
1440 }
1441
1442 /* Our len is the child's len? TBD it might be better to just return 0 */
OI_Len(void * ctx)1443 static size_t OI_Len(void *ctx) {
1444 OptionalMatchContext *nc = ctx;
1445 return nc->child ? nc->child->Len(nc->child->ctx) : 0;
1446 }
1447
1448 /* Last docId */
OI_LastDocId(void * ctx)1449 static t_docId OI_LastDocId(void *ctx) {
1450 OptionalMatchContext *nc = ctx;
1451
1452 return nc->lastDocId;
1453 }
1454
OI_Rewind(void * ctx)1455 static void OI_Rewind(void *ctx) {
1456 OptionalMatchContext *nc = ctx;
1457 nc->lastDocId = 0;
1458 nc->virt->docId = 0;
1459 nc->nextRealId = 0;
1460 if (nc->child) {
1461 nc->child->Rewind(nc->child->ctx);
1462 }
1463 }
1464
NewOptionalIterator(IndexIterator * it,t_docId maxDocId,double weight)1465 IndexIterator *NewOptionalIterator(IndexIterator *it, t_docId maxDocId, double weight) {
1466 OptionalMatchContext *nc = rm_calloc(1, sizeof(*nc));
1467 nc->virt = NewVirtualResult(weight);
1468 nc->virt->fieldMask = RS_FIELDMASK_ALL;
1469 nc->virt->freq = 1;
1470 nc->base.current = nc->virt;
1471 nc->child = it ? it : NewEmptyIterator();
1472 nc->childCT = NULL;
1473 nc->lastDocId = 0;
1474 nc->maxDocId = maxDocId;
1475 nc->weight = weight;
1476 nc->nextRealId = 0;
1477
1478 IndexIterator *ret = &nc->base;
1479 ret->ctx = nc;
1480 ret->type = OPTIONAL_ITERATOR;
1481 ret->GetCriteriaTester = OI_GetCriteriaTester;
1482 ret->NumEstimated = OI_NumEstimated;
1483 ret->Free = OI_Free;
1484 ret->HasNext = OI_HasNext;
1485 ret->LastDocId = OI_LastDocId;
1486 ret->Len = OI_Len;
1487 ret->Read = OI_ReadSorted;
1488 ret->SkipTo = OI_SkipTo;
1489 ret->Abort = OI_Abort;
1490 ret->Rewind = OI_Rewind;
1491 ret->mode = MODE_SORTED;
1492
1493 if (nc->child && nc->child->mode == MODE_UNSORTED) {
1494 nc->childCT = IITER_GET_CRITERIA_TESTER(nc->child);
1495 RS_LOG_ASSERT(nc->childCT, "childCT should not be NULL");
1496 ret->Read = OI_ReadUnsorted;
1497 }
1498
1499 return ret;
1500 }
1501
1502 /* Wildcard iterator, matchin ALL documents in the index. This is used for one thing only -
1503 * purely negative queries. If the root of the query is a negative expression, we cannot process
1504 * it
1505 * without a positive expression. So we create a wildcard iterator that basically just iterates
1506 * all
1507 * the incremental document ids, and matches every skip within its range. */
1508 typedef struct {
1509 IndexIterator base;
1510 t_docId topId;
1511 t_docId current;
1512 } WildcardIterator, WildcardIteratorCtx;
1513
1514 /* Free a wildcard iterator */
WI_Free(IndexIterator * it)1515 static void WI_Free(IndexIterator *it) {
1516
1517 WildcardIteratorCtx *nc = it->ctx;
1518 IndexResult_Free(CURRENT_RECORD(nc));
1519 rm_free(it);
1520 }
1521
1522 /* Read reads the next consecutive id, unless we're at the end */
WI_Read(void * ctx,RSIndexResult ** hit)1523 static int WI_Read(void *ctx, RSIndexResult **hit) {
1524 WildcardIteratorCtx *nc = ctx;
1525 if (nc->current > nc->topId) {
1526 return INDEXREAD_EOF;
1527 }
1528 CURRENT_RECORD(nc)->docId = nc->current++;
1529 if (hit) {
1530 *hit = CURRENT_RECORD(nc);
1531 }
1532 return INDEXREAD_OK;
1533 }
1534
1535 /* Skipto for wildcard iterator - always succeeds, but this should normally not happen as it has
1536 * no
1537 * meaning */
WI_SkipTo(void * ctx,t_docId docId,RSIndexResult ** hit)1538 static int WI_SkipTo(void *ctx, t_docId docId, RSIndexResult **hit) {
1539 // printf("WI_Skipto %d\n", docId);
1540 WildcardIteratorCtx *nc = ctx;
1541
1542 if (nc->current > nc->topId) return INDEXREAD_EOF;
1543
1544 if (docId == 0) return WI_Read(ctx, hit);
1545
1546 nc->current = docId;
1547 CURRENT_RECORD(nc)->docId = docId;
1548 if (hit) {
1549 *hit = CURRENT_RECORD(nc);
1550 }
1551 return INDEXREAD_OK;
1552 }
1553
WI_Abort(void * ctx)1554 static void WI_Abort(void *ctx) {
1555 WildcardIteratorCtx *nc = ctx;
1556 nc->current = nc->topId + 1;
1557 }
1558
1559 /* We always have next, in case anyone asks... ;) */
WI_HasNext(void * ctx)1560 static int WI_HasNext(void *ctx) {
1561 WildcardIteratorCtx *nc = ctx;
1562
1563 return nc->current <= nc->topId;
1564 }
1565
1566 /* Our len is the len of the index... */
WI_Len(void * ctx)1567 static size_t WI_Len(void *ctx) {
1568 WildcardIteratorCtx *nc = ctx;
1569 return nc->topId;
1570 }
1571
1572 /* Last docId */
WI_LastDocId(void * ctx)1573 static t_docId WI_LastDocId(void *ctx) {
1574 WildcardIteratorCtx *nc = ctx;
1575
1576 return nc->current;
1577 }
1578
WI_Rewind(void * p)1579 static void WI_Rewind(void *p) {
1580 WildcardIteratorCtx *ctx = p;
1581 ctx->current = 1;
1582 }
1583
WI_NumEstimated(void * p)1584 static size_t WI_NumEstimated(void *p) {
1585 return SIZE_MAX;
1586 }
1587
1588 /* Create a new wildcard iterator */
NewWildcardIterator(t_docId maxId)1589 IndexIterator *NewWildcardIterator(t_docId maxId) {
1590 WildcardIteratorCtx *c = rm_calloc(1, sizeof(*c));
1591 c->current = 1;
1592 c->topId = maxId;
1593
1594 CURRENT_RECORD(c) = NewVirtualResult(1);
1595 CURRENT_RECORD(c)->freq = 1;
1596 CURRENT_RECORD(c)->fieldMask = RS_FIELDMASK_ALL;
1597
1598 IndexIterator *ret = &c->base;
1599 ret->ctx = c;
1600 ret->type = WILDCARD_ITERATOR;
1601 ret->Free = WI_Free;
1602 ret->HasNext = WI_HasNext;
1603 ret->LastDocId = WI_LastDocId;
1604 ret->Len = WI_Len;
1605 ret->Read = WI_Read;
1606 ret->SkipTo = WI_SkipTo;
1607 ret->Abort = WI_Abort;
1608 ret->Rewind = WI_Rewind;
1609 ret->NumEstimated = WI_NumEstimated;
1610 return ret;
1611 }
1612
EOI_Read(void * p,RSIndexResult ** e)1613 static int EOI_Read(void *p, RSIndexResult **e) {
1614 return INDEXREAD_EOF;
1615 }
EOI_Free(struct indexIterator * self)1616 static void EOI_Free(struct indexIterator *self) {
1617 // Nothing
1618 }
EOI_NumEstimated(void * ctx)1619 static size_t EOI_NumEstimated(void *ctx) {
1620 return 0;
1621 }
EOI_Len(void * ctx)1622 static size_t EOI_Len(void *ctx) {
1623 return 0;
1624 }
EOI_LastDocId(void * ctx)1625 static t_docId EOI_LastDocId(void *ctx) {
1626 return 0;
1627 }
1628
EOI_SkipTo(void * ctx,t_docId docId,RSIndexResult ** hit)1629 static int EOI_SkipTo(void *ctx, t_docId docId, RSIndexResult **hit) {
1630 return INDEXREAD_EOF;
1631 }
EOI_Abort(void * ctx)1632 static void EOI_Abort(void *ctx) {
1633 }
EOI_Rewind(void * ctx)1634 static void EOI_Rewind(void *ctx) {
1635 }
1636
1637 static IndexIterator eofIterator = {.Read = EOI_Read,
1638 .Free = EOI_Free,
1639 .SkipTo = EOI_SkipTo,
1640 .Len = EOI_Len,
1641 .LastDocId = EOI_LastDocId,
1642 .NumEstimated = EOI_NumEstimated,
1643 .Abort = EOI_Abort,
1644 .Rewind = EOI_Rewind,
1645 .mode = MODE_SORTED,
1646 .type = EMPTY_ITERATOR};
1647
NewEmptyIterator(void)1648 IndexIterator *NewEmptyIterator(void) {
1649 return &eofIterator;
1650 }
1651
1652 // LCOV_EXCL_START unused
IndexIterator_GetTypeString(const IndexIterator * it)1653 const char *IndexIterator_GetTypeString(const IndexIterator *it) {
1654 if (it->Free == UnionIterator_Free) {
1655 return "UNION";
1656 } else if (it->Free == IntersectIterator_Free) {
1657 return "INTERSECTION";
1658 } else if (it->Free == OI_Free) {
1659 return "OPTIONAL";
1660 } else if (it->Free == WI_Free) {
1661 return "WILDCARD";
1662 } else if (it->Free == NI_Free) {
1663 return "NOT";
1664 } else if (it->Free == ReadIterator_Free) {
1665 return "IIDX";
1666 } else if (it == &eofIterator) {
1667 return "EMPTY";
1668 } else {
1669 return "Unknown";
1670 }
1671 }
1672 // LCOV_EXCL_STOP
1673
1674
1675 /**********************************************************
1676 * Profile printing functions
1677 **********************************************************/
1678
1679 /* Profile iterator, used for profiling. PI is added between all iterator
1680 */
1681 typedef struct {
1682 IndexIterator base;
1683 IndexIterator *child;
1684 size_t counter;
1685 clock_t cpuTime;
1686 int eof;
1687 } ProfileIterator, ProfileIteratorCtx;
1688
PI_Read(void * ctx,RSIndexResult ** e)1689 static int PI_Read(void *ctx, RSIndexResult **e) {
1690 ProfileIterator *pi = ctx;
1691 pi->counter++;
1692 clock_t begin = clock();
1693 int ret = pi->child->Read(pi->child->ctx, e);
1694 if (ret == INDEXREAD_EOF) pi->eof = 1;
1695 pi->base.current = pi->child->current;
1696 pi->cpuTime += clock() - begin;
1697 return ret;
1698 }
1699
PI_SkipTo(void * ctx,t_docId docId,RSIndexResult ** hit)1700 static int PI_SkipTo(void *ctx, t_docId docId, RSIndexResult **hit) {
1701 ProfileIterator *pi = ctx;
1702 pi->counter++;
1703 clock_t begin = clock();
1704 int ret = pi->child->SkipTo(pi->child->ctx, docId, hit);
1705 if (ret == INDEXREAD_EOF) pi->eof = 1;
1706 pi->base.current = pi->child->current;
1707 pi->cpuTime += clock() - begin;
1708 return ret;
1709 }
1710
PI_Free(IndexIterator * it)1711 static void PI_Free(IndexIterator *it) {
1712 ProfileIterator *pi = (ProfileIterator *)it;
1713 pi->child->Free(pi->child);
1714 rm_free(it);
1715 }
1716
1717 #define PROFILE_ITERATOR_FUNC_SIGN(func, rettype) \
1718 static rettype PI_##func(void *ctx) { \
1719 ProfileIterator *pi = ctx; \
1720 return pi->child->func(pi->child->ctx); \
1721 }
1722
1723 PROFILE_ITERATOR_FUNC_SIGN(Abort, void);
1724 PROFILE_ITERATOR_FUNC_SIGN(Len, size_t);
1725 PROFILE_ITERATOR_FUNC_SIGN(Rewind, void);
1726 PROFILE_ITERATOR_FUNC_SIGN(LastDocId, t_docId);
1727 PROFILE_ITERATOR_FUNC_SIGN(NumEstimated, size_t);
1728
PI_HasNext(void * ctx)1729 static int PI_HasNext(void *ctx) {
1730 ProfileIterator *pi = ctx;
1731 return IITER_HAS_NEXT(pi->child);
1732 }
1733
1734 /* Create a new wildcard iterator */
NewProfileIterator(IndexIterator * child)1735 IndexIterator *NewProfileIterator(IndexIterator *child) {
1736 ProfileIteratorCtx *pc = rm_calloc(1, sizeof(*pc));
1737 pc->child = child;
1738 pc->counter = 0;
1739 pc->cpuTime = 0;
1740 pc->eof = 0;
1741
1742 IndexIterator *ret = &pc->base;
1743 ret->ctx = pc;
1744 ret->type = PROFILE_ITERATOR;
1745 ret->Free = PI_Free;
1746 ret->HasNext = PI_HasNext;
1747 ret->LastDocId = PI_LastDocId;
1748 ret->Len = PI_Len;
1749 ret->Read = PI_Read;
1750 ret->SkipTo = PI_SkipTo;
1751 ret->Abort = PI_Abort;
1752 ret->Rewind = PI_Rewind;
1753 ret->NumEstimated = PI_NumEstimated;
1754 return ret;
1755 }
1756
1757 #define PRINT_PROFILE_FUNC(name) static void name(RedisModuleCtx *ctx, \
1758 IndexIterator *root, \
1759 size_t counter, \
1760 double cpuTime, \
1761 int depth, \
1762 int limited)
1763
PRINT_PROFILE_FUNC(printUnionIt)1764 PRINT_PROFILE_FUNC(printUnionIt) {
1765 UnionIterator *ui = (UnionIterator *)root;
1766 int printFull = !limited || (ui->origType & QN_UNION);
1767
1768 int nlen = 0;
1769 RedisModule_ReplyWithArray(ctx, REDISMODULE_POSTPONED_ARRAY_LEN);
1770
1771 printProfileType("UNION");
1772 nlen += 2;
1773
1774 RedisModule_ReplyWithSimpleString(ctx, "Query type");
1775 char *unionTypeStr;
1776 switch (ui->origType) {
1777 case QN_GEO : unionTypeStr = "GEO"; break;
1778 case QN_TAG : unionTypeStr = "TAG"; break;
1779 case QN_UNION : unionTypeStr = "UNION"; break;
1780 case QN_FUZZY : unionTypeStr = "FUZZY"; break;
1781 case QN_PREFIX : unionTypeStr = "PREFIX"; break;
1782 case QN_NUMERIC : unionTypeStr = "NUMERIC"; break;
1783 case QN_LEXRANGE : unionTypeStr = "LEXRANGE"; break;
1784 default:
1785 RS_LOG_ASSERT(0, "Invalid type for union");
1786 break;
1787 }
1788 if (!ui->qstr) {
1789 RedisModule_ReplyWithSimpleString(ctx, unionTypeStr);
1790 } else {
1791 RedisModule_ReplyWithPrintf(ctx, "%s - %s", unionTypeStr, ui->qstr);
1792 }
1793 nlen += 2;
1794
1795 if (PROFILE_VERBOSE) {
1796 printProfileTime(cpuTime);
1797 nlen += 2;
1798 }
1799
1800 printProfileCounter(counter);
1801 nlen += 2;
1802
1803 // if MAXPREFIXEXPANSIONS reached
1804 if (ui->norig == RSGlobalConfig.maxPrefixExpansions) {
1805 RedisModule_ReplyWithSimpleString(ctx, "Warning");
1806 RedisModule_ReplyWithSimpleString(ctx, "Max prefix expansion reached");
1807 nlen += 2;
1808 }
1809
1810 RedisModule_ReplyWithSimpleString(ctx, "Child iterators");
1811 nlen++;
1812 if (printFull) {
1813 for (int i = 0; i < ui->norig; i++) {
1814 printIteratorProfile(ctx, ui->origits[i], 0, 0, depth + 1, limited);
1815 }
1816 nlen += ui->norig;
1817 } else {
1818 RedisModule_ReplyWithPrintf(ctx, "The number of iterators in the union is %d", ui->norig);
1819 nlen++;
1820 }
1821
1822 RedisModule_ReplySetArrayLength(ctx, nlen);
1823 }
1824
PRINT_PROFILE_FUNC(printIntersectIt)1825 PRINT_PROFILE_FUNC(printIntersectIt) {
1826 IntersectIterator *ii = (IntersectIterator *)root;
1827
1828 size_t nlen = 0;
1829 RedisModule_ReplyWithArray(ctx, REDISMODULE_POSTPONED_ARRAY_LEN);
1830
1831 printProfileType("INTERSECT");
1832 nlen += 2;
1833
1834 if (PROFILE_VERBOSE) {
1835 printProfileTime(cpuTime);
1836 nlen += 2;
1837 }
1838
1839 printProfileCounter(counter);
1840 nlen += 2;
1841
1842 RedisModule_ReplyWithSimpleString(ctx, "Child iterators");
1843 nlen++;
1844 for (int i = 0; i < ii->num; i++) {
1845 if (ii->its[i]) {
1846 printIteratorProfile(ctx, ii->its[i], 0, 0, depth + 1, limited);
1847 } else {
1848 RedisModule_ReplyWithNull(ctx);
1849 }
1850 }
1851 nlen += ii->num;
1852
1853 RedisModule_ReplySetArrayLength(ctx, nlen);
1854 }
1855
1856 #define PRINT_PROFILE_SINGLE(name, iterType, text, hasChild) \
1857 PRINT_PROFILE_FUNC(name) { \
1858 size_t nlen = 0; \
1859 int addChild = hasChild && ((iterType *)root)->child; \
1860 RedisModule_ReplyWithArray(ctx, REDISMODULE_POSTPONED_ARRAY_LEN); \
1861 printProfileType(text); \
1862 nlen += 2; \
1863 if (PROFILE_VERBOSE) { \
1864 printProfileTime(cpuTime); \
1865 nlen += 2; \
1866 } \
1867 printProfileCounter(counter); \
1868 nlen += 2; \
1869 \
1870 if (addChild) { \
1871 RedisModule_ReplyWithSimpleString(ctx, "Child iterator"); \
1872 printIteratorProfile(ctx, ((iterType *)root)->child, 0, 0, depth + 1, limited); \
1873 nlen += 2; \
1874 } \
1875 RedisModule_ReplySetArrayLength(ctx, nlen); \
1876 }
1877
1878 typedef struct {
1879 IndexIterator base;
1880 IndexIterator *child;
1881 } DummyIterator;
1882
1883 PRINT_PROFILE_SINGLE(printNotIt, NotIterator, "NOT", 1);
1884 PRINT_PROFILE_SINGLE(printOptionalIt, OptionalIterator, "OPTIONAL", 1);
1885 PRINT_PROFILE_SINGLE(printWildcardIt, DummyIterator, "WILDCARD", 0);
1886 PRINT_PROFILE_SINGLE(printIdListIt, DummyIterator, "ID-LIST", 0);
1887 PRINT_PROFILE_SINGLE(printEmptyIt, DummyIterator, "EMPTY", 0);
1888
PRINT_PROFILE_FUNC(printProfileIt)1889 PRINT_PROFILE_FUNC(printProfileIt) {
1890 ProfileIterator *pi = (ProfileIterator *)root;
1891 printIteratorProfile(ctx, pi->child, pi->counter - pi->eof, (double)pi->cpuTime / CLOCKS_PER_MILLISEC, depth, limited);
1892 }
1893
printIteratorProfile(RedisModuleCtx * ctx,IndexIterator * root,size_t counter,double cpuTime,int depth,int limited)1894 void printIteratorProfile(RedisModuleCtx *ctx, IndexIterator *root, size_t counter,
1895 double cpuTime, int depth, int limited) {
1896 if (root == NULL) return;
1897
1898 // protect against limit of 7 reply layers
1899 if (depth == REDIS_ARRAY_LIMIT && !isFeatureSupported(NO_REPLY_DEPTH_LIMIT)) {
1900 RedisModule_ReplyWithNull(ctx);
1901 return;
1902 }
1903
1904 switch (root->type) {
1905 // Reader
1906 case READ_ITERATOR: { printReadIt(ctx, root, counter, cpuTime); break; }
1907 // Multi values
1908 case UNION_ITERATOR: { printUnionIt(ctx, root, counter, cpuTime, depth, limited); break; }
1909 case INTERSECT_ITERATOR: { printIntersectIt(ctx, root, counter, cpuTime, depth, limited); break; }
1910 // Single value
1911 case NOT_ITERATOR: { printNotIt(ctx, root, counter, cpuTime, depth, limited); break; }
1912 case OPTIONAL_ITERATOR: { printOptionalIt(ctx, root, counter, cpuTime, depth, limited); break; }
1913 case WILDCARD_ITERATOR: { printWildcardIt(ctx, root, counter, cpuTime, depth, limited); break; }
1914 case EMPTY_ITERATOR: { printEmptyIt(ctx, root, counter, cpuTime, depth, limited); break; }
1915 case ID_LIST_ITERATOR: { printIdListIt(ctx, root, counter, cpuTime, depth, limited); break; }
1916 case PROFILE_ITERATOR: { printProfileIt(ctx, root, 0, 0, depth, limited); break; }
1917 default: { RS_LOG_ASSERT(0, "nope"); break; }
1918 }
1919 }
1920
1921 /** Add Profile iterator before any iterator in the tree */
Profile_AddIters(IndexIterator ** root)1922 void Profile_AddIters(IndexIterator **root) {
1923 UnionIterator *ui;
1924 IntersectIterator *ini;
1925
1926 if (*root == NULL) return;
1927
1928 // Add profile iterator before child iterators
1929 switch((*root)->type) {
1930 case NOT_ITERATOR:
1931 Profile_AddIters(&((NotIterator *)((*root)->ctx))->child);
1932 break;
1933 case OPTIONAL_ITERATOR:
1934 Profile_AddIters(&((OptionalIterator *)((*root)->ctx))->child);
1935 break;
1936 case UNION_ITERATOR:
1937 ui = (*root)->ctx;
1938 for (int i = 0; i < ui->norig; i++) {
1939 Profile_AddIters(&(ui->origits[i]));
1940 }
1941 UI_SyncIterList(ui);
1942 break;
1943 case INTERSECT_ITERATOR:
1944 ini = (*root)->ctx;
1945 for (int i = 0; i < ini->num; i++) {
1946 Profile_AddIters(&(ini->its[i]));
1947 }
1948 break;
1949 case WILDCARD_ITERATOR:
1950 case READ_ITERATOR:
1951 case EMPTY_ITERATOR:
1952 case ID_LIST_ITERATOR:
1953 break;
1954 case PROFILE_ITERATOR:
1955 case MAX_ITERATOR:
1956 RS_LOG_ASSERT(0, "Error");
1957 }
1958
1959 // Create a profile iterator and update outparam pointer
1960 *root = NewProfileIterator(*root);
1961 }
1962