1 #include <inttypes.h>
2 #include <stdio.h>
3 #include <stdlib.h>
4 #include <string.h>
5 #include <sys/param.h>
6 
7 #include "geo_index.h"
8 #include "index.h"
9 #include "query.h"
10 #include "config.h"
11 #include "query_parser/parser.h"
12 #include "redis_index.h"
13 #include "tokenize.h"
14 #include "util/logging.h"
15 #include "extension.h"
16 #include "ext/default.h"
17 #include "rmutil/sds.h"
18 #include "tag_index.h"
19 #include "err.h"
20 #include "concurrent_ctx.h"
21 #include "numeric_index.h"
22 #include "numeric_filter.h"
23 #include "util/strconv.h"
24 #include "util/arr.h"
25 #include "module.h"
26 
27 #define EFFECTIVE_FIELDMASK(q_, qn_) ((qn_)->opts.fieldMask & (q)->opts->fieldmask)
28 
QueryTokenNode_Free(QueryTokenNode * tn)29 static void QueryTokenNode_Free(QueryTokenNode *tn) {
30 
31   if (tn->str) rm_free(tn->str);
32 }
33 
QueryTagNode_Free(QueryTagNode * tag)34 static void QueryTagNode_Free(QueryTagNode *tag) {
35   rm_free((char *)tag->fieldName);
36 }
37 
QueryLexRangeNode_Free(QueryLexRangeNode * lx)38 static void QueryLexRangeNode_Free(QueryLexRangeNode *lx) {
39   if (lx->begin) rm_free(lx->begin);
40   if (lx->end) rm_free(lx->end);
41 }
42 
43 // void _queryNumericNode_Free(QueryNumericNode *nn) { free(nn->nf); }
44 
QueryNode_Free(QueryNode * n)45 void QueryNode_Free(QueryNode *n) {
46   if (!n) return;
47 
48   if (n->children) {
49     for (size_t ii = 0; ii < QueryNode_NumChildren(n); ++ii) {
50       QueryNode_Free(n->children[ii]);
51     }
52     array_free(n->children);
53     n->children = NULL;
54   }
55 
56   switch (n->type) {
57     case QN_TOKEN:
58       QueryTokenNode_Free(&n->tn);
59       break;
60     case QN_NUMERIC:
61       NumericFilter_Free((void *)n->nn.nf);
62 
63       break;  //
64     case QN_PREFX:
65       QueryTokenNode_Free(&n->pfx);
66       break;
67     case QN_GEO:
68       if (n->gn.gf) {
69         GeoFilter_Free((void *)n->gn.gf);
70       }
71       break;
72     case QN_FUZZY:
73       QueryTokenNode_Free(&n->fz.tok);
74       break;
75     case QN_LEXRANGE:
76       QueryLexRangeNode_Free(&n->lxrng);
77       break;
78     case QN_WILDCARD:
79     case QN_IDS:
80       break;
81 
82     case QN_TAG:
83       QueryTagNode_Free(&n->tag);
84       break;
85 
86     case QN_UNION:
87     case QN_NOT:
88     case QN_OPTIONAL:
89     case QN_NULL:
90     case QN_PHRASE:
91       break;
92   }
93   rm_free(n);
94 }
95 
NewQueryNode(QueryNodeType type)96 QueryNode *NewQueryNode(QueryNodeType type) {
97   QueryNode *s = rm_calloc(1, sizeof(QueryNode));
98   s->type = type;
99   s->opts = (QueryNodeOptions){
100       .fieldMask = RS_FIELDMASK_ALL,
101       .flags = 0,
102       .maxSlop = -1,
103       .inOrder = 0,
104       .weight = 1,
105   };
106   return s;
107 }
108 
NewQueryNodeChildren(QueryNodeType type,QueryNode ** children,size_t n)109 QueryNode *NewQueryNodeChildren(QueryNodeType type, QueryNode **children, size_t n) {
110   QueryNode *ret = NewQueryNode(type);
111   ret->children = array_ensure_append(ret->children, children, n, QueryNode *);
112   return ret;
113 }
114 
NewTokenNodeExpanded(QueryAST * q,const char * s,size_t len,RSTokenFlags flags)115 QueryNode *NewTokenNodeExpanded(QueryAST *q, const char *s, size_t len, RSTokenFlags flags) {
116   QueryNode *ret = NewQueryNode(QN_TOKEN);
117   q->numTokens++;
118   ret->tn = (QueryTokenNode){.str = (char *)s, .len = len, .expanded = 1, .flags = flags};
119   return ret;
120 }
121 
NewTokenNode(QueryParseCtx * q,const char * s,size_t len)122 QueryNode *NewTokenNode(QueryParseCtx *q, const char *s, size_t len) {
123   if (len == (size_t)-1) {
124     len = strlen(s);
125   }
126 
127   QueryNode *ret = NewQueryNode(QN_TOKEN);
128   q->numTokens++;
129 
130   ret->tn = (QueryTokenNode){.str = (char *)s, .len = len, .expanded = 0, .flags = 0};
131   return ret;
132 }
133 
NewPrefixNode(QueryParseCtx * q,const char * s,size_t len)134 QueryNode *NewPrefixNode(QueryParseCtx *q, const char *s, size_t len) {
135   QueryNode *ret = NewQueryNode(QN_PREFX);
136   q->numTokens++;
137 
138   ret->pfx = (QueryPrefixNode){.str = (char *)s, .len = len, .expanded = 0, .flags = 0};
139   return ret;
140 }
141 
NewFuzzyNode(QueryParseCtx * q,const char * s,size_t len,int maxDist)142 QueryNode *NewFuzzyNode(QueryParseCtx *q, const char *s, size_t len, int maxDist) {
143   QueryNode *ret = NewQueryNode(QN_FUZZY);
144   q->numTokens++;
145 
146   ret->fz = (QueryFuzzyNode){
147       .tok =
148           (RSToken){
149               .str = (char *)s,
150               .len = len,
151               .expanded = 0,
152               .flags = 0,
153           },
154       .maxDist = maxDist,
155   };
156   return ret;
157 }
158 
NewPhraseNode(int exact)159 QueryNode *NewPhraseNode(int exact) {
160   QueryNode *ret = NewQueryNode(QN_PHRASE);
161   ret->pn.exact = exact;
162   return ret;
163 }
164 
NewTagNode(const char * field,size_t len)165 QueryNode *NewTagNode(const char *field, size_t len) {
166 
167   QueryNode *ret = NewQueryNode(QN_TAG);
168   ret->tag.fieldName = field;
169   ret->tag.len = len;
170   return ret;
171 }
172 
NewNumericNode(const NumericFilter * flt)173 QueryNode *NewNumericNode(const NumericFilter *flt) {
174   QueryNode *ret = NewQueryNode(QN_NUMERIC);
175   ret->nn = (QueryNumericNode){.nf = (NumericFilter *)flt};
176 
177   return ret;
178 }
179 
NewGeofilterNode(const GeoFilter * flt)180 QueryNode *NewGeofilterNode(const GeoFilter *flt) {
181   QueryNode *ret = NewQueryNode(QN_GEO);
182   ret->gn.gf = flt;
183   return ret;
184 }
185 
setFilterNode(QueryAST * q,QueryNode * n)186 static void setFilterNode(QueryAST *q, QueryNode *n) {
187   if (q->root == NULL || n == NULL) return;
188 
189   // for a simple phrase node we just add the numeric node
190   if (q->root->type == QN_PHRASE) {
191     // we usually want the numeric range as the "leader" iterator.
192     q->root->children = array_ensure_prepend(q->root->children, &n, 1, QueryNode *);
193     q->numTokens++;
194   } else {  // for other types, we need to create a new phrase node
195     QueryNode *nr = NewPhraseNode(0);
196     QueryNode_AddChild(nr, n);
197     QueryNode_AddChild(nr, q->root);
198     q->numTokens++;
199     q->root = nr;
200   }
201 }
202 
QAST_SetGlobalFilters(QueryAST * ast,const QAST_GlobalFilterOptions * options)203 void QAST_SetGlobalFilters(QueryAST *ast, const QAST_GlobalFilterOptions *options) {
204   if (options->numeric) {
205     QueryNode *n = NewQueryNode(QN_NUMERIC);
206     n->nn.nf = (NumericFilter *)options->numeric;
207     setFilterNode(ast, n);
208   }
209   if (options->geo) {
210     QueryNode *n = NewQueryNode(QN_GEO);
211     n->gn.gf = options->geo;
212     setFilterNode(ast, n);
213   }
214   if (options->ids) {
215     QueryNode *n = NewQueryNode(QN_IDS);
216     n->fn.ids = options->ids;
217     n->fn.len = options->nids;
218     setFilterNode(ast, n);
219   }
220 }
221 
QueryNode_Expand(RSQueryTokenExpander expander,RSQueryExpanderCtx * expCtx,QueryNode ** pqn)222 static void QueryNode_Expand(RSQueryTokenExpander expander, RSQueryExpanderCtx *expCtx,
223                              QueryNode **pqn) {
224 
225   QueryNode *qn = *pqn;
226   // Do not expand verbatim nodes
227   if (qn->opts.flags & QueryNode_Verbatim) {
228     return;
229   }
230 
231   int expandChildren = 0;
232 
233   if (qn->type == QN_TOKEN) {
234     expCtx->currentNode = pqn;
235     expander(expCtx, &qn->tn);
236   } else if (qn->type == QN_UNION ||
237              (qn->type == QN_PHRASE && !qn->pn.exact)) {  // do not expand exact phrases
238     expandChildren = 1;
239   }
240   if (expandChildren) {
241     for (size_t ii = 0; ii < QueryNode_NumChildren(qn); ++ii) {
242       QueryNode_Expand(expander, expCtx, &qn->children[ii]);
243     }
244   }
245 }
246 
Query_EvalTokenNode(QueryEvalCtx * q,QueryNode * qn)247 IndexIterator *Query_EvalTokenNode(QueryEvalCtx *q, QueryNode *qn) {
248   if (qn->type != QN_TOKEN) {
249     return NULL;
250   }
251 
252   // if there's only one word in the query and no special field filtering,
253   // and we are not paging beyond MAX_SCOREINDEX_SIZE
254   // we can just use the optimized score index
255   int isSingleWord = q->numTokens == 1 && q->opts->fieldmask == RS_FIELDMASK_ALL;
256 
257   RSQueryTerm *term = NewQueryTerm(&qn->tn, q->tokenId++);
258 
259   // printf("Opening reader.. `%s` FieldMask: %llx\n", term->str, EFFECTIVE_FIELDMASK(q, qn));
260 
261   IndexReader *ir = Redis_OpenReader(q->sctx, term, q->docTable, isSingleWord,
262                                      EFFECTIVE_FIELDMASK(q, qn), q->conc, qn->opts.weight);
263   if (ir == NULL) {
264     Term_Free(term);
265     return NULL;
266   }
267 
268   return NewReadIterator(ir);
269 }
270 
iterateExpandedTerms(QueryEvalCtx * q,Trie * terms,const char * str,size_t len,int maxDist,int prefixMode,QueryNodeOptions * opts)271 static IndexIterator *iterateExpandedTerms(QueryEvalCtx *q, Trie *terms, const char *str,
272                                            size_t len, int maxDist, int prefixMode,
273                                            QueryNodeOptions *opts) {
274   TrieIterator *it = Trie_Iterate(terms, str, len, maxDist, prefixMode);
275   if (!it) return NULL;
276 
277   size_t itsSz = 0, itsCap = 8;
278   IndexIterator **its = rm_calloc(itsCap, sizeof(*its));
279 
280   rune *rstr = NULL;
281   t_len slen = 0;
282   float score = 0;
283   int dist = 0;
284 
285   // an upper limit on the number of expansions is enforced to avoid stuff like "*"
286   while (TrieIterator_Next(it, &rstr, &slen, NULL, &score, &dist) &&
287          (itsSz < RSGlobalConfig.maxPrefixExpansions)) {
288 
289     // Create a token for the reader
290     RSToken tok = (RSToken){
291         .expanded = 0,
292         .flags = 0,
293         .len = 0,
294     };
295     tok.str = runesToStr(rstr, slen, &tok.len);
296     if (q->sctx && q->sctx->redisCtx) {
297       RedisModule_Log(q->sctx->redisCtx, "debug", "Found fuzzy expansion: %s %f", tok.str, score);
298     }
299 
300     RSQueryTerm *term = NewQueryTerm(&tok, q->tokenId++);
301 
302     // Open an index reader
303     IndexReader *ir = Redis_OpenReader(q->sctx, term, &q->sctx->spec->docs, 0,
304                                        q->opts->fieldmask & opts->fieldMask, q->conc, 1);
305 
306     rm_free(tok.str);
307     if (!ir) {
308       Term_Free(term);
309       continue;
310     }
311 
312     // Add the reader to the iterator array
313     its[itsSz++] = NewReadIterator(ir);
314     if (itsSz == itsCap) {
315       itsCap *= 2;
316       its = rm_realloc(its, itsCap * sizeof(*its));
317     }
318   }
319 
320   DFAFilter_Free(it->ctx);
321   rm_free(it->ctx);
322   TrieIterator_Free(it);
323   // printf("Expanded %d terms!\n", itsSz);
324   if (itsSz == 0) {
325     rm_free(its);
326     return NULL;
327   }
328   return NewUnionIterator(its, itsSz, q->docTable, 1, opts->weight);
329 }
330 /* Ealuate a prefix node by expanding all its possible matches and creating one big UNION on all
331  * of them */
Query_EvalPrefixNode(QueryEvalCtx * q,QueryNode * qn)332 static IndexIterator *Query_EvalPrefixNode(QueryEvalCtx *q, QueryNode *qn) {
333   assert(qn->type == QN_PREFX);
334 
335   // we allow a minimum of 2 letters in the prefx by default (configurable)
336   if (qn->pfx.len < RSGlobalConfig.minTermPrefix) {
337     return NULL;
338   }
339   Trie *terms = q->sctx->spec->terms;
340 
341   if (!terms) return NULL;
342 
343   return iterateExpandedTerms(q, terms, qn->pfx.str, qn->pfx.len, 0, 1, &qn->opts);
344 }
345 
346 typedef struct {
347   IndexIterator **its;
348   size_t nits;
349   size_t cap;
350   QueryEvalCtx *q;
351   QueryNodeOptions *opts;
352   double weight;
353 } LexRangeCtx;
354 
rangeItersAddIterator(LexRangeCtx * ctx,IndexReader * ir)355 static void rangeItersAddIterator(LexRangeCtx *ctx, IndexReader *ir) {
356   ctx->its[ctx->nits++] = NewReadIterator(ir);
357   if (ctx->nits == ctx->cap) {
358     ctx->cap *= 2;
359     ctx->its = rm_realloc(ctx->its, ctx->cap * sizeof(*ctx->its));
360   }
361 }
362 
rangeIterCbStrs(const char * r,size_t n,void * p,void * invidx)363 static void rangeIterCbStrs(const char *r, size_t n, void *p, void *invidx) {
364   LexRangeCtx *ctx = p;
365   QueryEvalCtx *q = ctx->q;
366   RSToken tok = {0};
367   tok.str = (char *)r;
368   tok.len = n;
369   RSQueryTerm *term = NewQueryTerm(&tok, ctx->q->tokenId++);
370   IndexReader *ir = NewTermIndexReader(invidx, q->sctx->spec, RS_FIELDMASK_ALL, term, ctx->weight);
371   if (!ir) {
372     Term_Free(term);
373     return;
374   }
375 
376   rangeItersAddIterator(ctx, ir);
377 }
378 
rangeIterCb(const rune * r,size_t n,void * p)379 static void rangeIterCb(const rune *r, size_t n, void *p) {
380   LexRangeCtx *ctx = p;
381   QueryEvalCtx *q = ctx->q;
382   RSToken tok = {0};
383   tok.str = runesToStr(r, n, &tok.len);
384   RSQueryTerm *term = NewQueryTerm(&tok, ctx->q->tokenId++);
385   IndexReader *ir = Redis_OpenReader(q->sctx, term, &q->sctx->spec->docs, 0,
386                                      q->opts->fieldmask & ctx->opts->fieldMask, q->conc, 1);
387   rm_free(tok.str);
388   if (!ir) {
389     Term_Free(term);
390     return;
391   }
392 
393   rangeItersAddIterator(ctx, ir);
394 }
395 
Query_EvalLexRangeNode(QueryEvalCtx * q,QueryNode * lx)396 static IndexIterator *Query_EvalLexRangeNode(QueryEvalCtx *q, QueryNode *lx) {
397   Trie *t = q->sctx->spec->terms;
398   LexRangeCtx ctx = {.q = q, .opts = &lx->opts};
399 
400   if (!t) {
401     return NULL;
402   }
403 
404   ctx.cap = 8;
405   ctx.its = rm_malloc(sizeof(*ctx.its) * ctx.cap);
406   ctx.nits = 0;
407 
408   rune *begin = NULL, *end = NULL;
409   size_t nbegin, nend;
410   if (lx->lxrng.begin) {
411     begin = strToFoldedRunes(lx->lxrng.begin, &nbegin);
412   }
413   if (lx->lxrng.end) {
414     end = strToFoldedRunes(lx->lxrng.end, &nend);
415   }
416 
417   TrieNode_IterateRange(t->root, begin, begin ? nbegin : -1, lx->lxrng.includeBegin, end,
418                         end ? nend : -1, lx->lxrng.includeEnd, rangeIterCb, &ctx);
419   rm_free(begin);
420   rm_free(end);
421   if (!ctx.its || ctx.nits == 0) {
422     rm_free(ctx.its);
423     return NULL;
424   } else {
425     return NewUnionIterator(ctx.its, ctx.nits, q->docTable, 1, lx->opts.weight);
426   }
427 }
428 
Query_EvalFuzzyNode(QueryEvalCtx * q,QueryNode * qn)429 static IndexIterator *Query_EvalFuzzyNode(QueryEvalCtx *q, QueryNode *qn) {
430   assert(qn->type == QN_FUZZY);
431 
432   Trie *terms = q->sctx->spec->terms;
433 
434   if (!terms) return NULL;
435 
436   return iterateExpandedTerms(q, terms, qn->pfx.str, qn->pfx.len, qn->fz.maxDist, 0, &qn->opts);
437 }
438 
Query_EvalPhraseNode(QueryEvalCtx * q,QueryNode * qn)439 static IndexIterator *Query_EvalPhraseNode(QueryEvalCtx *q, QueryNode *qn) {
440   if (qn->type != QN_PHRASE) {
441     // printf("Not a phrase node!\n");
442     return NULL;
443   }
444   QueryPhraseNode *node = &qn->pn;
445   // an intersect stage with one child is the same as the child, so we just
446   // return it
447   if (QueryNode_NumChildren(qn) == 1) {
448     qn->children[0]->opts.fieldMask &= qn->opts.fieldMask;
449     return Query_EvalNode(q, qn->children[0]);
450   }
451 
452   // recursively eval the children
453   IndexIterator **iters = rm_calloc(QueryNode_NumChildren(qn), sizeof(IndexIterator *));
454   for (size_t ii = 0; ii < QueryNode_NumChildren(qn); ++ii) {
455     qn->children[ii]->opts.fieldMask &= qn->opts.fieldMask;
456     iters[ii] = Query_EvalNode(q, qn->children[ii]);
457   }
458   IndexIterator *ret;
459 
460   if (node->exact) {
461     ret = NewIntersecIterator(iters, QueryNode_NumChildren(qn), q->docTable,
462                               EFFECTIVE_FIELDMASK(q, qn), 0, 1, qn->opts.weight);
463   } else {
464     // Let the query node override the slop/order parameters
465     int slop = qn->opts.maxSlop;
466     if (slop == -1) slop = q->opts->slop;
467 
468     // Let the query node override the inorder of the whole query
469     int inOrder = q->opts->flags & Search_InOrder;
470     if (qn->opts.inOrder) inOrder = 1;
471 
472     // If in order was specified and not slop, set slop to maximum possible value.
473     // Otherwise we can't check if the results are in order
474     if (inOrder && slop == -1) {
475       slop = __INT_MAX__;
476     }
477 
478     ret = NewIntersecIterator(iters, QueryNode_NumChildren(qn), q->docTable,
479                               EFFECTIVE_FIELDMASK(q, qn), slop, inOrder, qn->opts.weight);
480   }
481   return ret;
482 }
483 
Query_EvalWildcardNode(QueryEvalCtx * q,QueryNode * qn)484 static IndexIterator *Query_EvalWildcardNode(QueryEvalCtx *q, QueryNode *qn) {
485   if (qn->type != QN_WILDCARD || !q->docTable) {
486     return NULL;
487   }
488 
489   return NewWildcardIterator(q->docTable->maxDocId);
490 }
491 
Query_EvalNotNode(QueryEvalCtx * q,QueryNode * qn)492 static IndexIterator *Query_EvalNotNode(QueryEvalCtx *q, QueryNode *qn) {
493   if (qn->type != QN_NOT) {
494     return NULL;
495   }
496   QueryNotNode *node = &qn->inverted;
497 
498   return NewNotIterator(QueryNode_NumChildren(qn) ? Query_EvalNode(q, qn->children[0]) : NULL,
499                         q->docTable->maxDocId, qn->opts.weight);
500 }
501 
Query_EvalOptionalNode(QueryEvalCtx * q,QueryNode * qn)502 static IndexIterator *Query_EvalOptionalNode(QueryEvalCtx *q, QueryNode *qn) {
503   if (qn->type != QN_OPTIONAL) {
504     return NULL;
505   }
506   QueryOptionalNode *node = &qn->opt;
507 
508   return NewOptionalIterator(QueryNode_NumChildren(qn) ? Query_EvalNode(q, qn->children[0]) : NULL,
509                              q->docTable->maxDocId, qn->opts.weight);
510 }
511 
Query_EvalNumericNode(QueryEvalCtx * q,QueryNumericNode * node)512 static IndexIterator *Query_EvalNumericNode(QueryEvalCtx *q, QueryNumericNode *node) {
513 
514   const FieldSpec *fs =
515       IndexSpec_GetField(q->sctx->spec, node->nf->fieldName, strlen(node->nf->fieldName));
516   if (!fs || !FIELD_IS(fs, INDEXFLD_T_NUMERIC)) {
517     return NULL;
518   }
519 
520   return NewNumericFilterIterator(q->sctx, node->nf, q->conc);
521 }
522 
Query_EvalGeofilterNode(QueryEvalCtx * q,QueryGeofilterNode * node,double weight)523 static IndexIterator *Query_EvalGeofilterNode(QueryEvalCtx *q, QueryGeofilterNode *node,
524                                               double weight) {
525 
526   const FieldSpec *fs =
527       IndexSpec_GetField(q->sctx->spec, node->gf->property, strlen(node->gf->property));
528   if (!fs || !FIELD_IS(fs, INDEXFLD_T_GEO)) {
529     return NULL;
530   }
531 
532   GeoIndex gi = {.ctx = q->sctx, .sp = fs};
533   return NewGeoRangeIterator(&gi, node->gf, weight);
534 }
535 
Query_EvalIdFilterNode(QueryEvalCtx * q,QueryIdFilterNode * node)536 static IndexIterator *Query_EvalIdFilterNode(QueryEvalCtx *q, QueryIdFilterNode *node) {
537   return NewIdListIterator(node->ids, node->len, 1);
538 }
539 
Query_EvalUnionNode(QueryEvalCtx * q,QueryNode * qn)540 static IndexIterator *Query_EvalUnionNode(QueryEvalCtx *q, QueryNode *qn) {
541   if (qn->type != QN_UNION) {
542     return NULL;
543   }
544 
545   // a union stage with one child is the same as the child, so we just return it
546   if (QueryNode_NumChildren(qn) == 1) {
547     return Query_EvalNode(q, qn->children[0]);
548   }
549 
550   // recursively eval the children
551   IndexIterator **iters = rm_calloc(QueryNode_NumChildren(qn), sizeof(IndexIterator *));
552   int n = 0;
553   for (size_t i = 0; i < QueryNode_NumChildren(qn); ++i) {
554     qn->children[i]->opts.fieldMask &= qn->opts.fieldMask;
555     IndexIterator *it = Query_EvalNode(q, qn->children[i]);
556     if (it) {
557       iters[n++] = it;
558     }
559   }
560   if (n == 0) {
561     rm_free(iters);
562     return NULL;
563   }
564 
565   if (n == 1) {
566     IndexIterator *ret = iters[0];
567     rm_free(iters);
568     return ret;
569   }
570 
571   IndexIterator *ret = NewUnionIterator(iters, n, q->docTable, 0, qn->opts.weight);
572   return ret;
573 }
574 
575 typedef IndexIterator **IndexIteratorArray;
576 
Query_EvalTagLexRangeNode(QueryEvalCtx * q,TagIndex * idx,QueryNode * qn,IndexIteratorArray * iterout,double weight)577 static IndexIterator *Query_EvalTagLexRangeNode(QueryEvalCtx *q, TagIndex *idx, QueryNode *qn,
578                                                 IndexIteratorArray *iterout, double weight) {
579   TrieMap *t = idx->values;
580   LexRangeCtx ctx = {.q = q, .opts = &qn->opts, .weight = weight};
581 
582   if (!t) {
583     return NULL;
584   }
585 
586   ctx.cap = 8;
587   ctx.its = rm_malloc(sizeof(*ctx.its) * ctx.cap);
588   ctx.nits = 0;
589 
590   const char *begin = qn->lxrng.begin, *end = qn->lxrng.end;
591   int nbegin = begin ? strlen(begin) : -1, nend = end ? strlen(end) : -1;
592 
593   TrieMap_IterateRange(t, begin, nbegin, qn->lxrng.includeBegin, end, nend, qn->lxrng.includeEnd,
594                        rangeIterCbStrs, &ctx);
595   if (ctx.nits == 0) {
596     rm_free(ctx.its);
597     return NULL;
598   } else {
599     return NewUnionIterator(ctx.its, ctx.nits, q->docTable, 1, qn->opts.weight);
600   }
601 }
602 
603 /* Evaluate a tag prefix by expanding it with a lookup on the tag index */
Query_EvalTagPrefixNode(QueryEvalCtx * q,TagIndex * idx,QueryNode * qn,IndexIteratorArray * iterout,double weight)604 static IndexIterator *Query_EvalTagPrefixNode(QueryEvalCtx *q, TagIndex *idx, QueryNode *qn,
605                                               IndexIteratorArray *iterout, double weight) {
606   if (qn->type != QN_PREFX) {
607     return NULL;
608   }
609 
610   // we allow a minimum of 2 letters in the prefx by default (configurable)
611   if (qn->pfx.len < RSGlobalConfig.minTermPrefix) {
612     return NULL;
613   }
614   if (!idx || !idx->values) return NULL;
615 
616   TrieMapIterator *it = TrieMap_Iterate(idx->values, qn->pfx.str, qn->pfx.len);
617   if (!it) return NULL;
618 
619   size_t itsSz = 0, itsCap = 8;
620   IndexIterator **its = rm_calloc(itsCap, sizeof(*its));
621 
622   // an upper limit on the number of expansions is enforced to avoid stuff like "*"
623   char *s;
624   tm_len_t sl;
625   void *ptr;
626 
627   // Find all completions of the prefix
628   while (TrieMapIterator_Next(it, &s, &sl, &ptr) &&
629          (itsSz < RSGlobalConfig.maxPrefixExpansions)) {
630     IndexIterator *ret = TagIndex_OpenReader(idx, q->sctx->spec, s, sl, 1);
631     if (!ret) continue;
632 
633     // Add the reader to the iterator array
634     its[itsSz++] = ret;
635     if (itsSz == itsCap) {
636       itsCap *= 2;
637       its = rm_realloc(its, itsCap * sizeof(*its));
638     }
639   }
640 
641   TrieMapIterator_Free(it);
642 
643   // printf("Expanded %d terms!\n", itsSz);
644   if (itsSz == 0) {
645     rm_free(its);
646     return NULL;
647   }
648 
649   *iterout = array_ensure_append(*iterout, its, itsSz, IndexIterator *);
650   return NewUnionIterator(its, itsSz, q->docTable, 1, weight);
651 }
652 
query_EvalSingleTagNode(QueryEvalCtx * q,TagIndex * idx,QueryNode * n,IndexIteratorArray * iterout,double weight)653 static IndexIterator *query_EvalSingleTagNode(QueryEvalCtx *q, TagIndex *idx, QueryNode *n,
654                                               IndexIteratorArray *iterout, double weight) {
655   IndexIterator *ret = NULL;
656   switch (n->type) {
657     case QN_TOKEN: {
658       ret = TagIndex_OpenReader(idx, q->sctx->spec, n->tn.str, n->tn.len, weight);
659       break;
660     }
661     case QN_PREFX:
662       return Query_EvalTagPrefixNode(q, idx, n, iterout, weight);
663 
664     case QN_LEXRANGE:
665       return Query_EvalTagLexRangeNode(q, idx, n, iterout, weight);
666 
667     case QN_PHRASE: {
668       char *terms[QueryNode_NumChildren(n)];
669       for (size_t i = 0; i < QueryNode_NumChildren(n); ++i) {
670         if (n->children[i]->type == QN_TOKEN) {
671           terms[i] = n->children[i]->tn.str;
672         } else {
673           terms[i] = "";
674         }
675       }
676 
677       sds s = sdsjoin(terms, QueryNode_NumChildren(n), " ");
678 
679       ret = TagIndex_OpenReader(idx, q->sctx->spec, s, sdslen(s), weight);
680       sdsfree(s);
681       break;
682     }
683 
684     default:
685       return NULL;
686   }
687 
688   if (ret) {
689     *array_ensure_tail(iterout, IndexIterator *) = ret;
690   }
691   return ret;
692 }
693 
Query_EvalTagNode(QueryEvalCtx * q,QueryNode * qn)694 static IndexIterator *Query_EvalTagNode(QueryEvalCtx *q, QueryNode *qn) {
695   if (qn->type != QN_TAG) {
696     return NULL;
697   }
698   QueryTagNode *node = &qn->tag;
699   RedisModuleKey *k = NULL;
700   const FieldSpec *fs =
701       IndexSpec_GetFieldCase(q->sctx->spec, node->fieldName, strlen(node->fieldName));
702   if (!fs) {
703     return NULL;
704   }
705   RedisModuleString *kstr = IndexSpec_GetFormattedKey(q->sctx->spec, fs, INDEXFLD_T_TAG);
706   TagIndex *idx = TagIndex_Open(q->sctx, kstr, 0, &k);
707 
708   IndexIterator **total_its = NULL;
709   IndexIterator *ret = NULL;
710 
711   if (!idx) {
712     goto done;
713   }
714   // a union stage with one child is the same as the child, so we just return it
715   if (QueryNode_NumChildren(qn) == 1) {
716     ret = query_EvalSingleTagNode(q, idx, qn->children[0], &total_its, qn->opts.weight);
717     if (ret) {
718       if (q->conc) {
719         TagIndex_RegisterConcurrentIterators(idx, q->conc, k, kstr, (array_t *)total_its);
720         k = NULL;  // we passed ownershit
721       } else {
722         array_free(total_its);
723       }
724     }
725     goto done;
726   }
727 
728   // recursively eval the children
729   IndexIterator **iters = rm_calloc(QueryNode_NumChildren(qn), sizeof(IndexIterator *));
730   size_t n = 0;
731   for (size_t i = 0; i < QueryNode_NumChildren(qn); i++) {
732     IndexIterator *it =
733         query_EvalSingleTagNode(q, idx, qn->children[i], &total_its, qn->opts.weight);
734     if (it) {
735       iters[n++] = it;
736     }
737   }
738   if (n == 0) {
739     rm_free(iters);
740     goto done;
741   }
742 
743   if (total_its) {
744     if (q->conc) {
745       TagIndex_RegisterConcurrentIterators(idx, q->conc, k, kstr, (array_t *)total_its);
746       k = NULL;  // we passed ownershit
747     } else {
748       array_free(total_its);
749     }
750   }
751 
752   ret = NewUnionIterator(iters, n, q->docTable, 0, qn->opts.weight);
753 
754 done:
755   if (k) {
756     RedisModule_CloseKey(k);
757   }
758   return ret;
759 }
760 
Query_EvalNode(QueryEvalCtx * q,QueryNode * n)761 IndexIterator *Query_EvalNode(QueryEvalCtx *q, QueryNode *n) {
762   switch (n->type) {
763     case QN_TOKEN:
764       return Query_EvalTokenNode(q, n);
765     case QN_PHRASE:
766       return Query_EvalPhraseNode(q, n);
767     case QN_UNION:
768       return Query_EvalUnionNode(q, n);
769     case QN_TAG:
770       return Query_EvalTagNode(q, n);
771     case QN_NOT:
772       return Query_EvalNotNode(q, n);
773     case QN_PREFX:
774       return Query_EvalPrefixNode(q, n);
775     case QN_LEXRANGE:
776       return Query_EvalLexRangeNode(q, n);
777     case QN_FUZZY:
778       return Query_EvalFuzzyNode(q, n);
779     case QN_NUMERIC:
780       return Query_EvalNumericNode(q, &n->nn);
781     case QN_OPTIONAL:
782       return Query_EvalOptionalNode(q, n);
783     case QN_GEO:
784       return Query_EvalGeofilterNode(q, &n->gn, n->opts.weight);
785     case QN_IDS:
786       return Query_EvalIdFilterNode(q, &n->fn);
787     case QN_WILDCARD:
788       return Query_EvalWildcardNode(q, n);
789     case QN_NULL:
790       return NewEmptyIterator();
791   }
792 
793   return NULL;
794 }
795 
796 QueryNode *RSQuery_ParseRaw(QueryParseCtx *);
797 
QAST_Parse(QueryAST * dst,const RedisSearchCtx * sctx,const RSSearchOptions * opts,const char * q,size_t n,QueryError * status)798 int QAST_Parse(QueryAST *dst, const RedisSearchCtx *sctx, const RSSearchOptions *opts,
799                const char *q, size_t n, QueryError *status) {
800   if (!dst->query) {
801     dst->query = rm_strndup(q, n);
802     dst->nquery = n;
803   }
804   QueryParseCtx qpCtx = {// force multiline
805                          .raw = dst->query,
806                          .len = dst->nquery,
807                          .sctx = (RedisSearchCtx *)sctx,
808                          .opts = opts,
809                          .status = status};
810   dst->root = RSQuery_ParseRaw(&qpCtx);
811   // printf("Parsed %.*s. Error (Y/N): %d. Root: %p\n", (int)n, q, QueryError_HasError(status),
812   //  dst->root);
813   if (!dst->root) {
814     if (QueryError_HasError(status)) {
815       return REDISMODULE_ERR;
816     } else {
817       dst->root = NewQueryNode(QN_NULL);
818     }
819   }
820   if (QueryError_HasError(status)) {
821     if (dst->root) {
822       QueryNode_Free(dst->root);
823       dst->root = NULL;
824     }
825     return REDISMODULE_ERR;
826   }
827   dst->numTokens = qpCtx.numTokens;
828   return REDISMODULE_OK;
829 }
830 
QAST_Iterate(const QueryAST * qast,const RSSearchOptions * opts,RedisSearchCtx * sctx,ConcurrentSearchCtx * conc)831 IndexIterator *QAST_Iterate(const QueryAST *qast, const RSSearchOptions *opts, RedisSearchCtx *sctx,
832                             ConcurrentSearchCtx *conc) {
833   QueryEvalCtx qectx = {
834       .conc = conc,
835       .opts = opts,
836       .numTokens = qast->numTokens,
837       .docTable = &sctx->spec->docs,
838       .sctx = sctx,
839   };
840   IndexIterator *root = Query_EvalNode(&qectx, qast->root);
841   if (!root) {
842     // Return the dummy iterator
843     root = NewEmptyIterator();
844   }
845   return root;
846 }
847 
QAST_Destroy(QueryAST * q)848 void QAST_Destroy(QueryAST *q) {
849   QueryNode_Free(q->root);
850   q->root = NULL;
851   q->numTokens = 0;
852   rm_free(q->query);
853   q->nquery = 0;
854   q->query = NULL;
855 }
856 
QAST_Expand(QueryAST * q,const char * expander,RSSearchOptions * opts,RedisSearchCtx * sctx,QueryError * status)857 int QAST_Expand(QueryAST *q, const char *expander, RSSearchOptions *opts, RedisSearchCtx *sctx,
858                 QueryError *status) {
859   if (!q->root) {
860     return REDISMODULE_OK;
861   }
862   RSQueryExpanderCtx expCtx = {
863       .qast = q, .language = opts->language, .handle = sctx, .status = status};
864 
865   ExtQueryExpanderCtx *xpc =
866       Extensions_GetQueryExpander(&expCtx, expander ? expander : DEFAULT_EXPANDER_NAME);
867   if (xpc && xpc->exp) {
868     QueryNode_Expand(xpc->exp, &expCtx, &q->root);
869     if (xpc->ff) {
870       xpc->ff(expCtx.privdata);
871     }
872   }
873   if (QueryError_HasError(status)) {
874     return REDISMODULE_ERR;
875   }
876   return REDISMODULE_OK;
877   ;
878 }
879 
880 /* Set the field mask recursively on a query node. This is called by the parser to handle
881  * situations like @foo:(bar baz|gaz), where a complex tree is being applied a field mask */
QueryNode_SetFieldMask(QueryNode * n,t_fieldMask mask)882 void QueryNode_SetFieldMask(QueryNode *n, t_fieldMask mask) {
883   if (!n) return;
884   n->opts.fieldMask &= mask;
885   for (size_t ii = 0; ii < QueryNode_NumChildren(n); ++ii) {
886     QueryNode_SetFieldMask(n->children[ii], mask);
887   }
888 }
889 
QueryNode_AddChildren(QueryNode * n,QueryNode ** children,size_t nchildren)890 void QueryNode_AddChildren(QueryNode *n, QueryNode **children, size_t nchildren) {
891   if (n->type == QN_TAG) {
892     for (size_t ii = 0; ii < nchildren; ++ii) {
893       if (children[ii]->type == QN_TOKEN || children[ii]->type == QN_PHRASE ||
894           children[ii]->type == QN_PREFX || children[ii]->type == QN_LEXRANGE) {
895         n->children = array_ensure_append(n->children, children + ii, 1, QueryNode *);
896       }
897     }
898   } else {
899     array_ensure_append(n->children, children, nchildren, QueryNode *);
900   }
901 }
902 
QueryNode_AddChild(QueryNode * n,QueryNode * ch)903 void QueryNode_AddChild(QueryNode *n, QueryNode *ch) {
904   QueryNode_AddChildren(n, &ch, 1);
905 }
906 
QueryNode_ClearChildren(QueryNode * n,int shouldFree)907 void QueryNode_ClearChildren(QueryNode *n, int shouldFree) {
908   if (shouldFree) {
909     for (size_t ii = 0; ii < QueryNode_NumChildren(n); ++ii) {
910       QueryNode_Free(n->children[ii]);
911     }
912   }
913   if (QueryNode_NumChildren(n)) {
914     array_clear(n->children);
915   }
916 }
917 
918 /* Set the concurrent mode of the query. By default it's on, setting here to 0 will turn it off,
919  * resulting in the query not performing context switches */
920 // void Query_SetConcurrentMode(QueryPlan *q, int concurrent) {
921 //   q->concurrentMode = concurrent;
922 // }
923 
doPad(sds s,int len)924 static sds doPad(sds s, int len) {
925   if (!len) return s;
926 
927   char buf[len * 2 + 1];
928   memset(buf, ' ', len * 2);
929   buf[len * 2] = 0;
930   return sdscat(s, buf);
931 }
932 
933 static sds QueryNode_DumpSds(sds s, const IndexSpec *spec, const QueryNode *qs, int depth);
934 
935 static sds QueryNode_DumpChildren(sds s, const IndexSpec *spec, const QueryNode *qs, int depth);
936 
QueryNode_DumpSds(sds s,const IndexSpec * spec,const QueryNode * qs,int depth)937 static sds QueryNode_DumpSds(sds s, const IndexSpec *spec, const QueryNode *qs, int depth) {
938   s = doPad(s, depth);
939 
940   if (qs->opts.fieldMask == 0) {
941     s = sdscat(s, "@NULL:");
942   }
943 
944   if (qs->opts.fieldMask && qs->opts.fieldMask != RS_FIELDMASK_ALL && qs->type != QN_NUMERIC &&
945       qs->type != QN_GEO && qs->type != QN_IDS) {
946     if (!spec) {
947       s = sdscatprintf(s, "@%" PRIu64, (uint64_t)qs->opts.fieldMask);
948     } else {
949       s = sdscat(s, "@");
950       t_fieldMask fm = qs->opts.fieldMask;
951       int i = 0, n = 0;
952       while (fm) {
953         t_fieldMask bit = (fm & 1) << i;
954         if (bit) {
955           const char *f = GetFieldNameByBit(spec, bit);
956           s = sdscatprintf(s, "%s%s", n ? "|" : "", f ? f : "n/a");
957           n++;
958         }
959         fm = fm >> 1;
960         i++;
961       }
962     }
963     s = sdscat(s, ":");
964   }
965 
966   switch (qs->type) {
967     case QN_PHRASE:
968       s = sdscatprintf(s, "%s {\n", qs->pn.exact ? "EXACT" : "INTERSECT");
969       for (size_t ii = 0; ii < QueryNode_NumChildren(qs); ++ii) {
970         s = QueryNode_DumpSds(s, spec, qs->children[ii], depth + 1);
971       }
972       s = doPad(s, depth);
973 
974       break;
975     case QN_TOKEN:
976       s = sdscatprintf(s, "%s%s", (char *)qs->tn.str, qs->tn.expanded ? "(expanded)" : "");
977       if (qs->opts.weight != 1) {
978         s = sdscatprintf(s, " => {$weight: %g;}", qs->opts.weight);
979       }
980       s = sdscat(s, "\n");
981       return s;
982 
983     case QN_PREFX:
984       s = sdscatprintf(s, "PREFIX{%s*", (char *)qs->pfx.str);
985       break;
986 
987     case QN_LEXRANGE:
988       s = sdscatprintf(s, "LEXRANGE{%s...%s", qs->lxrng.begin ? qs->lxrng.begin : "",
989                        qs->lxrng.end ? qs->lxrng.end : "");
990       break;
991 
992     case QN_NOT:
993       s = sdscat(s, "NOT{\n");
994       s = QueryNode_DumpChildren(s, spec, qs, depth + 1);
995       s = doPad(s, depth);
996       break;
997 
998     case QN_OPTIONAL:
999       s = sdscat(s, "OPTIONAL{\n");
1000       s = QueryNode_DumpChildren(s, spec, qs, depth + 1);
1001       s = doPad(s, depth);
1002       break;
1003 
1004     case QN_NUMERIC: {
1005       const NumericFilter *f = qs->nn.nf;
1006       s = sdscatprintf(s, "NUMERIC {%f %s @%s %s %f", f->min, f->inclusiveMin ? "<=" : "<",
1007                        f->fieldName, f->inclusiveMax ? "<=" : "<", f->max);
1008     } break;
1009     case QN_UNION:
1010       s = sdscat(s, "UNION {\n");
1011       s = QueryNode_DumpChildren(s, spec, qs, depth + 1);
1012       s = doPad(s, depth);
1013       break;
1014     case QN_TAG:
1015       s = sdscatprintf(s, "TAG:@%.*s {\n", (int)qs->tag.len, qs->tag.fieldName);
1016       s = QueryNode_DumpChildren(s, spec, qs, depth + 1);
1017       s = doPad(s, depth);
1018       break;
1019     case QN_GEO:
1020 
1021       s = sdscatprintf(s, "GEO %s:{%f,%f --> %f %s", qs->gn.gf->property, qs->gn.gf->lon,
1022                        qs->gn.gf->lat, qs->gn.gf->radius,
1023                        GeoDistance_ToString(qs->gn.gf->unitType));
1024       break;
1025     case QN_IDS:
1026 
1027       s = sdscat(s, "IDS { ");
1028       for (int i = 0; i < qs->fn.len; i++) {
1029         s = sdscatprintf(s, "%llu,", (unsigned long long)qs->fn.ids[i]);
1030       }
1031       break;
1032     case QN_WILDCARD:
1033 
1034       s = sdscat(s, "<WILDCARD>");
1035       break;
1036     case QN_FUZZY:
1037       s = sdscatprintf(s, "FUZZY{%s}\n", qs->fz.tok.str);
1038       return s;
1039 
1040     case QN_NULL:
1041       s = sdscat(s, "<empty>");
1042   }
1043 
1044   s = sdscat(s, "}");
1045   // print attributes if not the default
1046   if (qs->opts.weight != 1 || qs->opts.maxSlop != -1 || qs->opts.inOrder) {
1047     s = sdscat(s, " => {");
1048     if (qs->opts.weight != 1) {
1049       s = sdscatprintf(s, " $weight: %g;", qs->opts.weight);
1050     }
1051     if (qs->opts.maxSlop != -1) {
1052       s = sdscatprintf(s, " $slop: %d;", qs->opts.maxSlop);
1053     }
1054     if (qs->opts.inOrder || qs->opts.maxSlop != -1) {
1055       s = sdscatprintf(s, " $inorder: %s;", qs->opts.inOrder ? "true" : "false");
1056     }
1057     s = sdscat(s, " }");
1058   }
1059   s = sdscat(s, "\n");
1060   return s;
1061 }
1062 
QueryNode_DumpChildren(sds s,const IndexSpec * spec,const QueryNode * qs,int depth)1063 static sds QueryNode_DumpChildren(sds s, const IndexSpec *spec, const QueryNode *qs, int depth) {
1064   for (size_t ii = 0; ii < QueryNode_NumChildren(qs); ++ii) {
1065     s = QueryNode_DumpSds(s, spec, qs->children[ii], depth);
1066   }
1067   return s;
1068 }
1069 
1070 /* Return a string representation of the query parse tree. The string should be freed by the
1071  * caller
1072  */
QAST_DumpExplain(const QueryAST * q,const IndexSpec * spec)1073 char *QAST_DumpExplain(const QueryAST *q, const IndexSpec *spec) {
1074   // empty query
1075   if (!q || !q->root) {
1076     return rm_strdup("NULL");
1077   }
1078 
1079   sds s = QueryNode_DumpSds(sdsnew(""), spec, q->root, 0);
1080   char *ret = rm_strndup(s, sdslen(s));
1081   sdsfree(s);
1082   return ret;
1083 }
1084 
QAST_Print(const QueryAST * ast,const IndexSpec * spec)1085 void QAST_Print(const QueryAST *ast, const IndexSpec *spec) {
1086   sds s = QueryNode_DumpSds(sdsnew(""), spec, ast->root, 0);
1087   printf("%s\n", s);
1088   sdsfree(s);
1089 }
1090 
QueryNode_ForEach(QueryNode * q,QueryNode_ForEachCallback callback,void * ctx,int reverse)1091 int QueryNode_ForEach(QueryNode *q, QueryNode_ForEachCallback callback, void *ctx, int reverse) {
1092 #define INITIAL_ARRAY_NODE_SIZE 5
1093   QueryNode **nodes = array_new(QueryNode *, INITIAL_ARRAY_NODE_SIZE);
1094   nodes = array_append(nodes, q);
1095   int retVal = 1;
1096   while (array_len(nodes) > 0) {
1097     QueryNode *curr = array_pop(nodes);
1098     if (!callback(curr, q, ctx)) {
1099       retVal = 0;
1100       break;
1101     }
1102     if (reverse) {
1103       for (size_t ii = QueryNode_NumChildren(curr); ii; --ii) {
1104         nodes = array_append(nodes, curr->children[ii - 1]);
1105       }
1106     } else {
1107       for (size_t ii = 0; ii < QueryNode_NumChildren(curr); ++ii) {
1108         nodes = array_append(nodes, curr->children[ii]);
1109       }
1110     }
1111   }
1112 
1113   array_free(nodes);
1114   return retVal;
1115 }
1116 
QueryNode_ApplyAttribute(QueryNode * qn,QueryAttribute * attr,QueryError * status)1117 static int QueryNode_ApplyAttribute(QueryNode *qn, QueryAttribute *attr, QueryError *status) {
1118 
1119 #define MK_INVALID_VALUE()                                                         \
1120   QueryError_SetErrorFmt(status, QUERY_ESYNTAX, "Invalid value (%.*s) for `%.*s`", \
1121                          (int)attr->vallen, attr->value, (int)attr->namelen, attr->name)
1122 
1123   // Apply slop: [-1 ... INF]
1124   if (STR_EQCASE(attr->name, attr->namelen, "slop")) {
1125     long long n;
1126     if (!ParseInteger(attr->value, &n) || n < -1) {
1127       MK_INVALID_VALUE();
1128       return 0;
1129     }
1130     qn->opts.maxSlop = n;
1131 
1132   } else if (STR_EQCASE(attr->name, attr->namelen, "inorder")) {
1133     // Apply inorder: true|false
1134     int b;
1135     if (!ParseBoolean(attr->value, &b)) {
1136       MK_INVALID_VALUE();
1137       return 0;
1138     }
1139     qn->opts.inOrder = b;
1140 
1141   } else if (STR_EQCASE(attr->name, attr->namelen, "weight")) {
1142     // Apply weight: [0  ... INF]
1143     double d;
1144     if (!ParseDouble(attr->value, &d) || d < 0) {
1145       MK_INVALID_VALUE();
1146       return 0;
1147     }
1148     qn->opts.weight = d;
1149 
1150   } else if (STR_EQCASE(attr->name, attr->namelen, "phonetic")) {
1151     // Apply phonetic: true|false
1152     int b;
1153     if (!ParseBoolean(attr->value, &b)) {
1154       MK_INVALID_VALUE();
1155       return 0;
1156     }
1157     if (b) {
1158       qn->opts.phonetic = PHONETIC_ENABLED;  // means we specifically asked for phonetic matching
1159     } else {
1160       qn->opts.phonetic =
1161           PHONETIC_DESABLED;  // means we specifically asked no for phonetic matching
1162     }
1163     // qn->opts.noPhonetic = PHONETIC_DEFAULT -> means no special asks regarding phonetics
1164     //                                          will be enable if field was declared phonetic
1165 
1166   } else {
1167     QueryError_SetErrorFmt(status, QUERY_ENOOPTION, "Invalid attribute %.*s", (int)attr->namelen,
1168                            attr->name);
1169     return 0;
1170   }
1171 
1172   return 1;
1173 }
1174 
QueryNode_ApplyAttributes(QueryNode * qn,QueryAttribute * attrs,size_t len,QueryError * status)1175 int QueryNode_ApplyAttributes(QueryNode *qn, QueryAttribute *attrs, size_t len,
1176                               QueryError *status) {
1177   for (size_t i = 0; i < len; i++) {
1178     if (!QueryNode_ApplyAttribute(qn, &attrs[i], status)) {
1179       return 0;
1180     }
1181   }
1182   return 1;
1183 }
1184