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