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