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