1 #include "spec.h"
2 #include "field_spec.h"
3 #include "document.h"
4 #include "rmutil/rm_assert.h"
5 #include "util/dict.h"
6 #include "query_node.h"
7 #include "search_options.h"
8 #include "query_internal.h"
9 #include "numeric_filter.h"
10 #include "query.h"
11 #include "indexer.h"
12 #include "extension.h"
13 #include "ext/default.h"
14 #include <float.h>
15 #include "rwlock.h"
16 #include "fork_gc.h"
17 #include "module.h"
18 
RediSearch_GetCApiVersion()19 int RediSearch_GetCApiVersion() {
20   return REDISEARCH_CAPI_VERSION;
21 }
22 
RediSearch_CreateIndex(const char * name,const RSIndexOptions * options)23 IndexSpec* RediSearch_CreateIndex(const char* name, const RSIndexOptions* options) {
24   RSIndexOptions opts_s = {.gcPolicy = GC_POLICY_FORK, .stopwordsLen = -1};
25   if (!options) {
26     options = &opts_s;
27   }
28   IndexSpec* spec = NewIndexSpec(name);
29   IndexSpec_MakeKeyless(spec);
30   spec->flags |= Index_Temporary;  // temporary is so that we will not use threads!!
31   if (!spec->indexer) {
32     spec->indexer = NewIndexer(spec);
33   }
34 
35   if (options->score || options->lang) {
36     spec->rule = rm_calloc(1, sizeof *spec->rule);
37     spec->rule->score_default = options->score ? options->score : DEFAULT_SCORE;
38     spec->rule->lang_default = options->lang ? options->lang : DEFAULT_LANGUAGE;
39   }
40 
41   spec->getValue = options->gvcb;
42   spec->getValueCtx = options->gvcbData;
43   if (options->flags & RSIDXOPT_DOCTBLSIZE_UNLIMITED) {
44     spec->docs.maxSize = DOCID_MAX;
45   }
46   if (options->gcPolicy != GC_POLICY_NONE) {
47     IndexSpec_StartGCFromSpec(spec, GC_DEFAULT_HZ, options->gcPolicy);
48   }
49   if (options->stopwordsLen != -1) {
50     // replace default list which is a global so no need to free anything.
51     spec->stopwords = NewStopWordListCStr((const char **)options->stopwords,
52                                                          options->stopwordsLen);
53   }
54   return spec;
55 }
56 
RediSearch_DropIndex(IndexSpec * sp)57 void RediSearch_DropIndex(IndexSpec* sp) {
58   RWLOCK_ACQUIRE_WRITE();
59   IndexSpec_FreeInternals(sp);
60   RWLOCK_RELEASE();
61 }
62 
RediSearch_IndexGetStopwords(IndexSpec * sp,size_t * size)63 char **RediSearch_IndexGetStopwords(IndexSpec* sp, size_t *size) {
64   return GetStopWordsList(sp->stopwords, size);
65 }
66 
RediSearch_IndexGetScore(IndexSpec * sp)67 double RediSearch_IndexGetScore(IndexSpec* sp) {
68   if (sp->rule) {
69     return sp->rule->score_default;
70   }
71   return DEFAULT_SCORE;
72 }
73 
RediSearch_IndexGetLanguage(IndexSpec * sp)74 const char *RediSearch_IndexGetLanguage(IndexSpec* sp) {
75   if (sp->rule) {
76     return RSLanguage_ToString(sp->rule->lang_default);
77   }
78   return RSLanguage_ToString(DEFAULT_LANGUAGE);
79 }
80 
RediSearch_CreateField(IndexSpec * sp,const char * name,unsigned types,unsigned options)81 RSFieldID RediSearch_CreateField(IndexSpec* sp, const char* name, unsigned types,
82                                  unsigned options) {
83   RS_LOG_ASSERT(types, "types should not be RSFLDTYPE_DEFAULT");
84   RWLOCK_ACQUIRE_WRITE();
85 
86   FieldSpec* fs = IndexSpec_CreateField(sp, name);
87   int numTypes = 0;
88 
89   if (types & RSFLDTYPE_FULLTEXT) {
90     numTypes++;
91     int txtId = IndexSpec_CreateTextId(sp);
92     if (txtId < 0) {
93       RWLOCK_RELEASE();
94       return RSFIELD_INVALID;
95     }
96     fs->ftId = txtId;
97     FieldSpec_Initialize(fs, INDEXFLD_T_FULLTEXT);
98   }
99 
100   if (types & RSFLDTYPE_NUMERIC) {
101     numTypes++;
102     FieldSpec_Initialize(fs, INDEXFLD_T_NUMERIC);
103   }
104   if (types & RSFLDTYPE_GEO) {
105     FieldSpec_Initialize(fs, INDEXFLD_T_GEO);
106     numTypes++;
107   }
108   if (types & RSFLDTYPE_TAG) {
109     FieldSpec_Initialize(fs, INDEXFLD_T_TAG);
110     numTypes++;
111   }
112 
113   if (numTypes > 1) {
114     fs->options |= FieldSpec_Dynamic;
115   }
116 
117   if (options & RSFLDOPT_NOINDEX) {
118     fs->options |= FieldSpec_NotIndexable;
119   }
120   if (options & RSFLDOPT_SORTABLE) {
121     fs->options |= FieldSpec_Sortable;
122     fs->sortIdx = RSSortingTable_Add(&sp->sortables, fs->name, fieldTypeToValueType(fs->types));
123   }
124   if (options & RSFLDOPT_TXTNOSTEM) {
125     fs->options |= FieldSpec_NoStemming;
126   }
127   if (options & RSFLDOPT_TXTPHONETIC) {
128     fs->options |= FieldSpec_Phonetics;
129     sp->flags |= Index_HasPhonetic;
130   }
131 
132   RWLOCK_RELEASE();
133   return fs->index;
134 }
135 
RediSearch_TextFieldSetWeight(IndexSpec * sp,RSFieldID id,double w)136 void RediSearch_TextFieldSetWeight(IndexSpec* sp, RSFieldID id, double w) {
137   FieldSpec* fs = sp->fields + id;
138   RS_LOG_ASSERT(FIELD_IS(fs, INDEXFLD_T_FULLTEXT), "types should be INDEXFLD_T_FULLTEXT");
139   fs->ftWeight = w;
140 }
141 
RediSearch_TagFieldSetSeparator(IndexSpec * sp,RSFieldID id,char sep)142 void RediSearch_TagFieldSetSeparator(IndexSpec* sp, RSFieldID id, char sep) {
143   FieldSpec* fs = sp->fields + id;
144   RS_LOG_ASSERT(FIELD_IS(fs, INDEXFLD_T_TAG), "types should be INDEXFLD_T_TAG");
145   fs->tagSep = sep;
146 }
147 
RediSearch_TagFieldSetCaseSensitive(IndexSpec * sp,RSFieldID id,int enable)148 void RediSearch_TagFieldSetCaseSensitive(IndexSpec* sp, RSFieldID id, int enable) {
149   FieldSpec* fs = sp->fields + id;
150   RS_LOG_ASSERT(FIELD_IS(fs, INDEXFLD_T_TAG), "types should be INDEXFLD_T_TAG");
151   if (enable) {
152     fs->tagFlags |= TagField_CaseSensitive;
153   } else {
154     fs->tagFlags &= ~TagField_CaseSensitive;
155   }
156 }
157 
RediSearch_CreateDocument(const void * docKey,size_t len,double score,const char * lang)158 RSDoc* RediSearch_CreateDocument(const void* docKey, size_t len, double score, const char* lang) {
159   RedisModuleString* docKeyStr = RedisModule_CreateString(NULL, docKey, len);
160   RSLanguage language = lang ? RSLanguage_Find(lang) : DEFAULT_LANGUAGE;
161   Document* ret = rm_calloc(1, sizeof(*ret));
162   Document_Init(ret, docKeyStr, score, language);
163   Document_MakeStringsOwner(ret);
164   RedisModule_FreeString(RSDummyContext, docKeyStr);
165   return ret;
166 }
167 
RediSearch_CreateDocument2(const void * docKey,size_t len,IndexSpec * sp,double score,const char * lang)168 RSDoc* RediSearch_CreateDocument2(const void* docKey, size_t len, IndexSpec* sp,
169                                   double score, const char* lang) {
170   RedisModuleString* docKeyStr = RedisModule_CreateString(NULL, docKey, len);
171 
172   RSLanguage language = lang ? RSLanguage_Find(lang) :
173              (sp && sp->rule) ? sp->rule->lang_default : DEFAULT_LANGUAGE;
174   double docScore = !isnan(score) ? score :
175              (sp && sp->rule) ? sp->rule->score_default : DEFAULT_SCORE;
176 
177   Document* ret = rm_calloc(1, sizeof(*ret));
178   Document_Init(ret, docKeyStr, docScore, language);
179   Document_MakeStringsOwner(ret);
180   RedisModule_FreeString(RSDummyContext, docKeyStr);
181   return ret;
182 }
183 
RediSearch_FreeDocument(RSDoc * doc)184 void RediSearch_FreeDocument(RSDoc* doc) {
185   Document_Free(doc);
186   rm_free(doc);
187 }
188 
RediSearch_DeleteDocument(IndexSpec * sp,const void * docKey,size_t len)189 int RediSearch_DeleteDocument(IndexSpec* sp, const void* docKey, size_t len) {
190   RWLOCK_ACQUIRE_WRITE();
191   int rc = REDISMODULE_OK;
192   t_docId id = DocTable_GetId(&sp->docs, docKey, len);
193   if (id == 0) {
194     rc = REDISMODULE_ERR;
195   } else {
196     if (DocTable_Delete(&sp->docs, docKey, len)) {
197       // Delete returns true/false, not RM_{OK,ERR}
198       sp->stats.numDocuments--;
199       if (sp->gc) {
200         GCContext_OnDelete(sp->gc);
201       }
202     } else {
203       rc = REDISMODULE_ERR;
204     }
205   }
206 
207   RWLOCK_RELEASE();
208   return rc;
209 }
210 
RediSearch_DocumentAddField(Document * d,const char * fieldName,RedisModuleString * value,unsigned as)211 void RediSearch_DocumentAddField(Document* d, const char* fieldName, RedisModuleString* value,
212                                  unsigned as) {
213   Document_AddField(d, fieldName, value, as);
214 }
215 
RediSearch_DocumentAddFieldString(Document * d,const char * fieldname,const char * s,size_t n,unsigned as)216 void RediSearch_DocumentAddFieldString(Document* d, const char* fieldname, const char* s, size_t n,
217                                        unsigned as) {
218   Document_AddFieldC(d, fieldname, s, n, as);
219 }
220 
RediSearch_DocumentAddFieldNumber(Document * d,const char * fieldname,double n,unsigned as)221 void RediSearch_DocumentAddFieldNumber(Document* d, const char* fieldname, double n, unsigned as) {
222   char buf[512];
223   size_t len = sprintf(buf, "%lf", n);
224   Document_AddFieldC(d, fieldname, buf, len, as);
225 }
226 
RediSearch_DocumentAddFieldGeo(Document * d,const char * fieldname,double lat,double lon,unsigned as)227 int RediSearch_DocumentAddFieldGeo(Document* d, const char* fieldname,
228                                     double lat, double lon, unsigned as) {
229   if (lat > GEO_LAT_MAX || lat < GEO_LAT_MIN || lon > GEO_LONG_MAX || lon < GEO_LONG_MIN) {
230     // out of range
231     return REDISMODULE_ERR;
232   }
233   // The format for a geospacial point is "lon,lat"
234   char buf[24];
235   size_t len = sprintf(buf, "%.6lf,%.6lf", lon, lat);
236   Document_AddFieldC(d, fieldname, buf, len, as);
237   return REDISMODULE_OK;
238 }
239 
240 typedef struct {
241   char** s;
242   int hasErr;
243 } RSError;
244 
RediSearch_AddDocDone(RSAddDocumentCtx * aCtx,RedisModuleCtx * ctx,void * err)245 void RediSearch_AddDocDone(RSAddDocumentCtx* aCtx, RedisModuleCtx* ctx, void* err) {
246   RSError* ourErr = err;
247   if (QueryError_HasError(&aCtx->status)) {
248     if (ourErr->s) {
249       *ourErr->s = rm_strdup(QueryError_GetError(&aCtx->status));
250     }
251     ourErr->hasErr = aCtx->status.code;
252   }
253 }
254 
RediSearch_IndexAddDocument(IndexSpec * sp,Document * d,int options,char ** errs)255 int RediSearch_IndexAddDocument(IndexSpec* sp, Document* d, int options, char** errs) {
256   RWLOCK_ACQUIRE_WRITE();
257 
258   RSError err = {.s = errs};
259   QueryError status = {0};
260   RSAddDocumentCtx* aCtx = NewAddDocumentCtx(sp, d, &status);
261   if (aCtx == NULL) {
262     if (status.detail) {
263       QueryError_ClearError(&status);
264     }
265     RWLOCK_RELEASE();
266     return REDISMODULE_ERR;
267   }
268   aCtx->donecb = RediSearch_AddDocDone;
269   aCtx->donecbData = &err;
270   RedisSearchCtx sctx = {.redisCtx = NULL, .spec = sp};
271   int exists = !!DocTable_GetIdR(&sp->docs, d->docKey);
272   if (exists) {
273     if (options & REDISEARCH_ADD_REPLACE) {
274       options |= DOCUMENT_ADD_REPLACE;
275     } else {
276       if (errs) {
277         *errs = rm_strdup("Document already exists");
278       }
279       AddDocumentCtx_Free(aCtx);
280       RWLOCK_RELEASE();
281       return REDISMODULE_ERR;
282     }
283   }
284 
285   options |= DOCUMENT_ADD_NOSAVE;
286   aCtx->stateFlags |= ACTX_F_NOBLOCK;
287   AddDocumentCtx_Submit(aCtx, &sctx, options);
288   rm_free(d);
289 
290   RWLOCK_RELEASE();
291   return err.hasErr ? REDISMODULE_ERR : REDISMODULE_OK;
292 }
293 
RediSearch_CreateTokenNode(IndexSpec * sp,const char * fieldName,const char * token)294 QueryNode* RediSearch_CreateTokenNode(IndexSpec* sp, const char* fieldName, const char* token) {
295   if (StopWordList_Contains(sp->stopwords, token, strlen(token))) {
296     return NULL;
297   }
298   QueryNode* ret = NewQueryNode(QN_TOKEN);
299 
300   ret->tn = (QueryTokenNode){
301       .str = (char*)rm_strdup(token), .len = strlen(token), .expanded = 0, .flags = 0};
302   if (fieldName) {
303     ret->opts.fieldMask = IndexSpec_GetFieldBit(sp, fieldName, strlen(fieldName));
304   }
305   return ret;
306 }
307 
RediSearch_CreateNumericNode(IndexSpec * sp,const char * field,double max,double min,int includeMax,int includeMin)308 QueryNode* RediSearch_CreateNumericNode(IndexSpec* sp, const char* field, double max, double min,
309                                         int includeMax, int includeMin) {
310   QueryNode* ret = NewQueryNode(QN_NUMERIC);
311   ret->nn.nf = NewNumericFilter(min, max, includeMin, includeMax);
312   ret->nn.nf->fieldName = rm_strdup(field);
313   ret->opts.fieldMask = IndexSpec_GetFieldBit(sp, field, strlen(field));
314   return ret;
315 }
316 
RediSearch_CreateGeoNode(IndexSpec * sp,const char * field,double lat,double lon,double radius,RSGeoDistance unitType)317 QueryNode* RediSearch_CreateGeoNode(IndexSpec* sp, const char* field, double lat, double lon,
318                                         double radius, RSGeoDistance unitType) {
319   QueryNode* ret = NewQueryNode(QN_GEO);
320   ret->opts.fieldMask = IndexSpec_GetFieldBit(sp, field, strlen(field));
321 
322   GeoFilter *flt = rm_malloc(sizeof(*flt));
323   flt->lat = lat;
324   flt->lon = lon;
325   flt->radius = radius;
326   flt->numericFilters = NULL;
327   flt->property = rm_strdup(field);
328   flt->unitType = (GeoDistance)unitType;
329 
330   ret->gn.gf = flt;
331 
332   return ret;
333 }
334 
RediSearch_CreatePrefixNode(IndexSpec * sp,const char * fieldName,const char * s)335 QueryNode* RediSearch_CreatePrefixNode(IndexSpec* sp, const char* fieldName, const char* s) {
336   QueryNode* ret = NewQueryNode(QN_PREFX);
337   ret->pfx =
338       (QueryPrefixNode){.str = (char*)rm_strdup(s), .len = strlen(s), .expanded = 0, .flags = 0};
339   if (fieldName) {
340     ret->opts.fieldMask = IndexSpec_GetFieldBit(sp, fieldName, strlen(fieldName));
341   }
342   return ret;
343 }
344 
RediSearch_CreateLexRangeNode(IndexSpec * sp,const char * fieldName,const char * begin,const char * end,int includeBegin,int includeEnd)345 QueryNode* RediSearch_CreateLexRangeNode(IndexSpec* sp, const char* fieldName, const char* begin,
346                                          const char* end, int includeBegin, int includeEnd) {
347   QueryNode* ret = NewQueryNode(QN_LEXRANGE);
348   if (begin) {
349     ret->lxrng.begin = begin ? rm_strdup(begin) : NULL;
350     ret->lxrng.includeBegin = includeBegin;
351   }
352   if (end) {
353     ret->lxrng.end = end ? rm_strdup(end) : NULL;
354     ret->lxrng.includeEnd = includeEnd;
355   }
356   if (fieldName) {
357     ret->opts.fieldMask = IndexSpec_GetFieldBit(sp, fieldName, strlen(fieldName));
358   }
359   return ret;
360 }
361 
RediSearch_CreateTagNode(IndexSpec * sp,const char * field)362 QueryNode* RediSearch_CreateTagNode(IndexSpec* sp, const char* field) {
363   QueryNode* ret = NewQueryNode(QN_TAG);
364   ret->tag.fieldName = rm_strdup(field);
365   ret->tag.len = strlen(field);
366   ret->opts.fieldMask = IndexSpec_GetFieldBit(sp, field, strlen(field));
367   return ret;
368 }
369 
RediSearch_CreateIntersectNode(IndexSpec * sp,int exact)370 QueryNode* RediSearch_CreateIntersectNode(IndexSpec* sp, int exact) {
371   QueryNode* ret = NewQueryNode(QN_PHRASE);
372   ret->pn.exact = exact;
373   return ret;
374 }
375 
RediSearch_CreateUnionNode(IndexSpec * sp)376 QueryNode* RediSearch_CreateUnionNode(IndexSpec* sp) {
377   return NewQueryNode(QN_UNION);
378 }
379 
RediSearch_CreateEmptyNode(IndexSpec * sp)380 QueryNode* RediSearch_CreateEmptyNode(IndexSpec* sp) {
381   return NewQueryNode(QN_NULL);
382 }
383 
RediSearch_CreateNotNode(IndexSpec * sp)384 QueryNode* RediSearch_CreateNotNode(IndexSpec* sp) {
385   return NewQueryNode(QN_NOT);
386 }
387 
RediSearch_QueryNodeGetFieldMask(QueryNode * qn)388 int RediSearch_QueryNodeGetFieldMask(QueryNode* qn) {
389   return qn->opts.fieldMask;
390 }
391 
RediSearch_QueryNodeAddChild(QueryNode * parent,QueryNode * child)392 void RediSearch_QueryNodeAddChild(QueryNode* parent, QueryNode* child) {
393   QueryNode_AddChild(parent, child);
394 }
395 
RediSearch_QueryNodeClearChildren(QueryNode * qn)396 void RediSearch_QueryNodeClearChildren(QueryNode* qn) {
397   QueryNode_ClearChildren(qn, 1);
398 }
399 
RediSearch_QueryNodeGetChild(const QueryNode * qn,size_t ix)400 QueryNode* RediSearch_QueryNodeGetChild(const QueryNode* qn, size_t ix) {
401   return QueryNode_GetChild(qn, ix);
402 }
403 
RediSearch_QueryNodeNumChildren(const QueryNode * qn)404 size_t RediSearch_QueryNodeNumChildren(const QueryNode* qn) {
405   return QueryNode_NumChildren(qn);
406 }
407 
408 typedef struct RS_ApiIter {
409   IndexIterator* internal;
410   RSIndexResult* res;
411   const RSDocumentMetadata* lastmd;
412   ScoringFunctionArgs scargs;
413   RSScoringFunction scorer;
414   RSFreeFunction scorerFree;
415   double minscore;  // Used for scoring
416   QueryAST qast;    // Used for string queries..
417 } RS_ApiIter;
418 
419 #define QUERY_INPUT_STRING 1
420 #define QUERY_INPUT_NODE 2
421 
422 typedef struct {
423   int qtype;
424   union {
425     struct {
426       const char* qs;
427       size_t n;
428     } s;
429     QueryNode* qn;
430   } u;
431 } QueryInput;
432 
handleIterCommon(IndexSpec * sp,QueryInput * input,char ** error)433 static RS_ApiIter* handleIterCommon(IndexSpec* sp, QueryInput* input, char** error) {
434   // here we only take the read lock and we will free it when the iterator will be freed
435   RWLOCK_ACQUIRE_READ();
436 
437   RedisSearchCtx sctx = SEARCH_CTX_STATIC(NULL, sp);
438   RSSearchOptions options = {0};
439   QueryError status = {0};
440   RSSearchOptions_Init(&options);
441   RS_ApiIter* it = rm_calloc(1, sizeof(*it));
442 
443   if (input->qtype == QUERY_INPUT_STRING) {
444     if (QAST_Parse(&it->qast, &sctx, &options, input->u.s.qs, input->u.s.n, &status) !=
445         REDISMODULE_OK) {
446       goto end;
447     }
448   } else {
449     it->qast.root = input->u.qn;
450   }
451 
452   if (QAST_Expand(&it->qast, NULL, &options, &sctx, &status) != REDISMODULE_OK) {
453     goto end;
454   }
455 
456   it->internal = QAST_Iterate(&it->qast, &options, &sctx, NULL);
457   if (!it->internal) {
458     goto end;
459   }
460 
461   IndexSpec_GetStats(sp, &it->scargs.indexStats);
462   ExtScoringFunctionCtx* scoreCtx = Extensions_GetScoringFunction(&it->scargs, DEFAULT_SCORER_NAME);
463   RS_LOG_ASSERT(scoreCtx, "GetScoringFunction failed");
464   it->scorer = scoreCtx->sf;
465   it->scorerFree = scoreCtx->ff;
466   it->minscore = DBL_MAX;
467 
468   // dummy statement for goto
469   ;
470 end:
471 
472   if (QueryError_HasError(&status) || it->internal == NULL) {
473     if (it) {
474       RediSearch_ResultsIteratorFree(it);
475       it = NULL;
476     }
477     if (error) {
478       *error = rm_strdup(QueryError_GetError(&status));
479     }
480   }
481   QueryError_ClearError(&status);
482   return it;
483 }
484 
RediSearch_DocumentExists(IndexSpec * sp,const void * docKey,size_t len)485 int RediSearch_DocumentExists(IndexSpec* sp, const void* docKey, size_t len) {
486   return DocTable_GetId(&sp->docs, docKey, len) != 0;
487 }
488 
RediSearch_IterateQuery(IndexSpec * sp,const char * s,size_t n,char ** error)489 RS_ApiIter* RediSearch_IterateQuery(IndexSpec* sp, const char* s, size_t n, char** error) {
490   QueryInput input = {.qtype = QUERY_INPUT_STRING, .u = {.s = {.qs = s, .n = n}}};
491   return handleIterCommon(sp, &input, error);
492 }
493 
RediSearch_GetResultsIterator(QueryNode * qn,IndexSpec * sp)494 RS_ApiIter* RediSearch_GetResultsIterator(QueryNode* qn, IndexSpec* sp) {
495   QueryInput input = {.qtype = QUERY_INPUT_NODE, .u = {.qn = qn}};
496   return handleIterCommon(sp, &input, NULL);
497 }
498 
RediSearch_QueryNodeFree(QueryNode * qn)499 void RediSearch_QueryNodeFree(QueryNode* qn) {
500   QueryNode_Free(qn);
501 }
502 
RediSearch_QueryNodeType(QueryNode * qn)503 int RediSearch_QueryNodeType(QueryNode* qn) {
504   return qn->type;
505 }
506 
RediSearch_ResultsIteratorNext(RS_ApiIter * iter,IndexSpec * sp,size_t * len)507 const void* RediSearch_ResultsIteratorNext(RS_ApiIter* iter, IndexSpec* sp, size_t* len) {
508   while (iter->internal->Read(iter->internal->ctx, &iter->res) != INDEXREAD_EOF) {
509     const RSDocumentMetadata* md = DocTable_Get(&sp->docs, iter->res->docId);
510     if (md == NULL || ((md)->flags & Document_Deleted)) {
511       continue;
512     }
513     iter->lastmd = md;
514     if (len) {
515       *len = sdslen(md->keyPtr);
516     }
517     return md->keyPtr;
518   }
519   return NULL;
520 }
521 
RediSearch_ResultsIteratorGetScore(const RS_ApiIter * it)522 double RediSearch_ResultsIteratorGetScore(const RS_ApiIter* it) {
523   return it->scorer(&it->scargs, it->res, it->lastmd, 0);
524 }
525 
RediSearch_ResultsIteratorFree(RS_ApiIter * iter)526 void RediSearch_ResultsIteratorFree(RS_ApiIter* iter) {
527   if (iter->internal) {
528     iter->internal->Free(iter->internal);
529   } else {
530     printf("Not freeing internal iterator. internal iterator is null\n");
531   }
532   if (iter->scorerFree) {
533     iter->scorerFree(iter->scargs.extdata);
534   }
535   QAST_Destroy(&iter->qast);
536   rm_free(iter);
537 
538   RWLOCK_RELEASE();
539 }
540 
RediSearch_ResultsIteratorReset(RS_ApiIter * iter)541 void RediSearch_ResultsIteratorReset(RS_ApiIter* iter) {
542   iter->internal->Rewind(iter->internal->ctx);
543 }
544 
RediSearch_CreateIndexOptions()545 RSIndexOptions* RediSearch_CreateIndexOptions() {
546   RSIndexOptions* ret = rm_calloc(1, sizeof(RSIndexOptions));
547   ret->gcPolicy = GC_POLICY_NONE;
548   ret->stopwordsLen = -1;
549   return ret;
550 }
551 
RediSearch_FreeIndexOptions(RSIndexOptions * options)552 void RediSearch_FreeIndexOptions(RSIndexOptions* options) {
553   if (options->stopwordsLen > 0) {
554     for (int i = 0; i < options->stopwordsLen; i++) {
555       rm_free(options->stopwords[i]);
556     }
557     rm_free(options->stopwords);
558   }
559   rm_free(options);
560 }
561 
RediSearch_IndexOptionsSetGetValueCallback(RSIndexOptions * options,RSGetValueCallback cb,void * ctx)562 void RediSearch_IndexOptionsSetGetValueCallback(RSIndexOptions* options, RSGetValueCallback cb,
563                                                 void* ctx) {
564   options->gvcb = cb;
565   options->gvcbData = ctx;
566 }
567 
RediSearch_IndexOptionsSetStopwords(RSIndexOptions * opts,const char ** stopwords,int stopwordsLen)568 void RediSearch_IndexOptionsSetStopwords(RSIndexOptions* opts, const char **stopwords, int stopwordsLen) {
569   if (opts->stopwordsLen > 0) {
570     for (int i = 0; i < opts->stopwordsLen; i++) {
571       rm_free(opts->stopwords[i]);
572     }
573     rm_free(opts->stopwords);
574   }
575 
576   opts->stopwords = NULL;
577 
578   if (stopwordsLen > 0) {
579     opts->stopwords = rm_malloc(sizeof(*opts->stopwords) * stopwordsLen);
580     for (int i = 0; i < stopwordsLen; i++) {
581       opts->stopwords[i] = rm_strdup(stopwords[i]);
582     }
583   }
584   opts->stopwordsLen = stopwordsLen;
585 }
586 
RediSearch_IndexOptionsSetFlags(RSIndexOptions * options,uint32_t flags)587 void RediSearch_IndexOptionsSetFlags(RSIndexOptions* options, uint32_t flags) {
588   options->flags = flags;
589 }
590 
RediSearch_IndexOptionsSetGCPolicy(RSIndexOptions * options,int policy)591 void RediSearch_IndexOptionsSetGCPolicy(RSIndexOptions* options, int policy) {
592   options->gcPolicy = policy;
593 }
594 
595 #define REGISTER_API(name)                                                          \
596   if (RedisModule_ExportSharedAPI(ctx, "RediSearch_" #name, RediSearch_##name) !=   \
597       REDISMODULE_OK) {                                                             \
598     RedisModule_Log(ctx, "warning", "could not register RediSearch_" #name "\r\n"); \
599     return REDISMODULE_ERR;                                                         \
600   }
601 
RediSearch_ExportCapi(RedisModuleCtx * ctx)602 int RediSearch_ExportCapi(RedisModuleCtx* ctx) {
603   if (RedisModule_ExportSharedAPI == NULL) {
604     RedisModule_Log(ctx, "warning", "Upgrade redis-server to use Redis Search's C API");
605     return REDISMODULE_ERR;
606   }
607   RS_XAPIFUNC(REGISTER_API)
608   return REDISMODULE_OK;
609 }
610 
RediSearch_SetCriteriaTesterThreshold(size_t num)611 void RediSearch_SetCriteriaTesterThreshold(size_t num) {
612   if (num == 0) {
613     RSGlobalConfig.maxResultsToUnsortedMode = DEFAULT_MAX_RESULTS_TO_UNSORTED_MODE;
614   } else {
615     RSGlobalConfig.maxResultsToUnsortedMode = num;
616   }
617 }
618 
RediSearch_StopwordsList_Contains(RSIndex * idx,const char * term,size_t len)619 int RediSearch_StopwordsList_Contains(RSIndex* idx, const char *term, size_t len) {
620   return StopWordList_Contains(idx->stopwords, term, len);
621 }
622