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