1 #include "debug_commads.h"
2 #include "inverted_index.h"
3 #include "index.h"
4 #include "redis_index.h"
5 #include "tag_index.h"
6 #include "numeric_index.h"
7 #include "phonetic_manager.h"
8 #include "gc.h"
9 #include "module.h"
10 
11 #define DUMP_PHONETIC_HASH "DUMP_PHONETIC_HASH"
12 
13 #define DEBUG_COMMAND(name) static int name(RedisModuleCtx *ctx, RedisModuleString **argv, int argc)
14 
15 #define GET_SEARCH_CTX(name)                                        \
16   RedisSearchCtx *sctx = NewSearchCtx(ctx, name, true);             \
17   if (!sctx) {                                                      \
18     RedisModule_ReplyWithError(ctx, "Can not create a search ctx"); \
19     return REDISMODULE_OK;                                          \
20   }
21 
22 #define REPLY_WITH_LONG_LONG(name, val, len)                  \
23   RedisModule_ReplyWithStringBuffer(ctx, name, strlen(name)); \
24   RedisModule_ReplyWithLongLong(ctx, val);                    \
25   len += 2;
26 
27 #define REPLY_WITH_Str(name, val)                             \
28   RedisModule_ReplyWithStringBuffer(ctx, name, strlen(name)); \
29   RedisModule_ReplyWithStringBuffer(ctx, val, strlen(val));   \
30   bulkLen += 2;
31 
ReplyReaderResults(IndexReader * reader,RedisModuleCtx * ctx)32 static void ReplyReaderResults(IndexReader *reader, RedisModuleCtx *ctx) {
33   IndexIterator *iter = NewReadIterator(reader);
34   RSIndexResult *r;
35   size_t resultSize = 0;
36   RedisModule_ReplyWithArray(ctx, REDISMODULE_POSTPONED_ARRAY_LEN);
37   while (iter->Read(iter->ctx, &r) != INDEXREAD_EOF) {
38     RedisModule_ReplyWithLongLong(ctx, r->docId);
39     ++resultSize;
40   }
41   RedisModule_ReplySetArrayLength(ctx, resultSize);
42   ReadIterator_Free(iter);
43 }
44 
getFieldKeyName(IndexSpec * spec,RedisModuleString * fieldNameRS,FieldType t)45 static RedisModuleString *getFieldKeyName(IndexSpec *spec, RedisModuleString *fieldNameRS,
46                                           FieldType t) {
47   const char *fieldName = RedisModule_StringPtrLen(fieldNameRS, NULL);
48   const FieldSpec *fieldSpec = IndexSpec_GetField(spec, fieldName, strlen(fieldName));
49   if (!fieldSpec) {
50     return NULL;
51   }
52   return IndexSpec_GetFormattedKey(spec, fieldSpec, t);
53 }
54 
DEBUG_COMMAND(DumpTerms)55 DEBUG_COMMAND(DumpTerms) {
56   if (argc != 1) {
57     return RedisModule_WrongArity(ctx);
58   }
59   GET_SEARCH_CTX(argv[0])
60 
61   rune *rstr = NULL;
62   t_len slen = 0;
63   float score = 0;
64   int dist = 0;
65   size_t termLen;
66 
67   RedisModule_ReplyWithArray(ctx, sctx->spec->terms->size);
68 
69   TrieIterator *it = Trie_Iterate(sctx->spec->terms, "", 0, 0, 1);
70   while (TrieIterator_Next(it, &rstr, &slen, NULL, &score, &dist)) {
71     char *res = runesToStr(rstr, slen, &termLen);
72     RedisModule_ReplyWithStringBuffer(ctx, res, termLen);
73     rm_free(res);
74   }
75   DFAFilter_Free(it->ctx);
76   rm_free(it->ctx);
77   TrieIterator_Free(it);
78 
79   SearchCtx_Free(sctx);
80   return REDISMODULE_OK;
81 }
82 
DEBUG_COMMAND(InvertedIndexSummary)83 DEBUG_COMMAND(InvertedIndexSummary) {
84   if (argc != 2) {
85     return RedisModule_WrongArity(ctx);
86   }
87   GET_SEARCH_CTX(argv[0])
88   RedisModuleKey *keyp = NULL;
89   size_t len;
90   const char *invIdxName = RedisModule_StringPtrLen(argv[1], &len);
91   InvertedIndex *invidx = Redis_OpenInvertedIndexEx(sctx, invIdxName, len, 0, &keyp);
92   if (!invidx) {
93     RedisModule_ReplyWithError(sctx->redisCtx, "Can not find the inverted index");
94     goto end;
95   }
96   size_t invIdxBulkLen = 0;
97   RedisModule_ReplyWithArray(ctx, REDISMODULE_POSTPONED_ARRAY_LEN);
98 
99   REPLY_WITH_LONG_LONG("numDocs", invidx->numDocs, invIdxBulkLen);
100   REPLY_WITH_LONG_LONG("lastId", invidx->lastId, invIdxBulkLen);
101   REPLY_WITH_LONG_LONG("flags", invidx->flags, invIdxBulkLen);
102   REPLY_WITH_LONG_LONG("numberOfBlocks", invidx->size, invIdxBulkLen);
103 
104   RedisModule_ReplyWithStringBuffer(ctx, "blocks", strlen("blocks"));
105 
106   for (uint32_t i = 0; i < invidx->size; ++i) {
107     size_t blockBulkLen = 0;
108     IndexBlock *block = invidx->blocks + i;
109     RedisModule_ReplyWithArray(ctx, REDISMODULE_POSTPONED_ARRAY_LEN);
110 
111     REPLY_WITH_LONG_LONG("firstId", block->firstId, blockBulkLen);
112     REPLY_WITH_LONG_LONG("lastId", block->lastId, blockBulkLen);
113     REPLY_WITH_LONG_LONG("numDocs", block->numDocs, blockBulkLen);
114 
115     RedisModule_ReplySetArrayLength(ctx, blockBulkLen);
116   }
117 
118   invIdxBulkLen += 2;
119 
120   RedisModule_ReplySetArrayLength(ctx, invIdxBulkLen);
121 
122 end:
123   if (keyp) {
124     RedisModule_CloseKey(keyp);
125   }
126   SearchCtx_Free(sctx);
127   return REDISMODULE_OK;
128 }
129 
DEBUG_COMMAND(DumpInvertedIndex)130 DEBUG_COMMAND(DumpInvertedIndex) {
131   if (argc != 2) {
132     return RedisModule_WrongArity(ctx);
133   }
134   GET_SEARCH_CTX(argv[0])
135   RedisModuleKey *keyp = NULL;
136   size_t len;
137   const char *invIdxName = RedisModule_StringPtrLen(argv[1], &len);
138   InvertedIndex *invidx = Redis_OpenInvertedIndexEx(sctx, invIdxName, len, 0, &keyp);
139   if (!invidx) {
140     RedisModule_ReplyWithError(sctx->redisCtx, "Can not find the inverted index");
141     goto end;
142   }
143   IndexReader *reader = NewTermIndexReader(invidx, NULL, RS_FIELDMASK_ALL, NULL, 1);
144   ReplyReaderResults(reader, sctx->redisCtx);
145 end:
146   if (keyp) {
147     RedisModule_CloseKey(keyp);
148   }
149   SearchCtx_Free(sctx);
150   return REDISMODULE_OK;
151 }
152 
DEBUG_COMMAND(NumericIndexSummary)153 DEBUG_COMMAND(NumericIndexSummary) {
154   if (argc != 2) {
155     return RedisModule_WrongArity(ctx);
156   }
157   GET_SEARCH_CTX(argv[0])
158   RedisModuleKey *keyp = NULL;
159   RedisModuleString *keyName = getFieldKeyName(sctx->spec, argv[1], INDEXFLD_T_NUMERIC);
160   if (!keyName) {
161     RedisModule_ReplyWithError(sctx->redisCtx, "Could not find given field in index spec");
162     goto end;
163   }
164   NumericRangeTree *rt = OpenNumericIndex(sctx, keyName, &keyp);
165   if (!rt) {
166     RedisModule_ReplyWithError(sctx->redisCtx, "can not open numeric field");
167     goto end;
168   }
169 
170   size_t invIdxBulkLen = 0;
171   RedisModule_ReplyWithArray(ctx, REDISMODULE_POSTPONED_ARRAY_LEN);
172 
173   REPLY_WITH_LONG_LONG("numRanges", rt->numRanges, invIdxBulkLen);
174   REPLY_WITH_LONG_LONG("numEntries", rt->numEntries, invIdxBulkLen);
175   REPLY_WITH_LONG_LONG("lastDocId", rt->lastDocId, invIdxBulkLen);
176   REPLY_WITH_LONG_LONG("revisionId", rt->revisionId, invIdxBulkLen);
177 
178   RedisModule_ReplySetArrayLength(ctx, invIdxBulkLen);
179 
180 end:
181   if (keyp) {
182     RedisModule_CloseKey(keyp);
183   }
184   SearchCtx_Free(sctx);
185   return REDISMODULE_OK;
186 }
187 
DEBUG_COMMAND(DumpNumericIndex)188 DEBUG_COMMAND(DumpNumericIndex) {
189   if (argc != 2) {
190     return RedisModule_WrongArity(ctx);
191   }
192   GET_SEARCH_CTX(argv[0])
193   RedisModuleKey *keyp = NULL;
194   RedisModuleString *keyName = getFieldKeyName(sctx->spec, argv[1], INDEXFLD_T_NUMERIC);
195   if (!keyName) {
196     RedisModule_ReplyWithError(sctx->redisCtx, "Could not find given field in index spec");
197     goto end;
198   }
199   NumericRangeTree *rt = OpenNumericIndex(sctx, keyName, &keyp);
200   if (!rt) {
201     RedisModule_ReplyWithError(sctx->redisCtx, "can not open numeric field");
202     goto end;
203   }
204   NumericRangeNode *currNode;
205   NumericRangeTreeIterator *iter = NumericRangeTreeIterator_New(rt);
206   size_t resultSize = 0;
207   RedisModule_ReplyWithArray(sctx->redisCtx, REDISMODULE_POSTPONED_ARRAY_LEN);
208   while ((currNode = NumericRangeTreeIterator_Next(iter))) {
209     NumericRange *range = currNode->range;
210     if (range) {
211       IndexReader *reader = NewNumericReader(NULL, range->entries, NULL, range->minVal, range->maxVal);
212       ReplyReaderResults(reader, sctx->redisCtx);
213       ++resultSize;
214     }
215   }
216   RedisModule_ReplySetArrayLength(sctx->redisCtx, resultSize);
217   NumericRangeTreeIterator_Free(iter);
218 end:
219   if (keyp) {
220     RedisModule_CloseKey(keyp);
221   }
222   SearchCtx_Free(sctx);
223   return REDISMODULE_OK;
224 }
225 
DEBUG_COMMAND(DumpTagIndex)226 DEBUG_COMMAND(DumpTagIndex) {
227   if (argc != 2) {
228     return RedisModule_WrongArity(ctx);
229   }
230   GET_SEARCH_CTX(argv[0])
231   RedisModuleKey *keyp = NULL;
232   RedisModuleString *keyName = getFieldKeyName(sctx->spec, argv[1], INDEXFLD_T_TAG);
233   if (!keyName) {
234     RedisModule_ReplyWithError(sctx->redisCtx, "Could not find given field in index spec");
235     goto end;
236   }
237   TagIndex *tagIndex = TagIndex_Open(sctx, keyName, false, &keyp);
238   if (!tagIndex) {
239     RedisModule_ReplyWithError(sctx->redisCtx, "can not open tag field");
240     goto end;
241   }
242 
243   TrieMapIterator *iter = TrieMap_Iterate(tagIndex->values, "", 0);
244 
245   char *tag;
246   tm_len_t len;
247   InvertedIndex *iv;
248 
249   size_t resultSize = 0;
250   RedisModule_ReplyWithArray(sctx->redisCtx, REDISMODULE_POSTPONED_ARRAY_LEN);
251   while (TrieMapIterator_Next(iter, &tag, &len, (void **)&iv)) {
252     RedisModule_ReplyWithArray(sctx->redisCtx, 2);
253     RedisModule_ReplyWithStringBuffer(sctx->redisCtx, tag, len);
254     IndexReader *reader = NewTermIndexReader(iv, NULL, RS_FIELDMASK_ALL, NULL, 1);
255     ReplyReaderResults(reader, sctx->redisCtx);
256     ++resultSize;
257   }
258   RedisModule_ReplySetArrayLength(sctx->redisCtx, resultSize);
259   TrieMapIterator_Free(iter);
260 
261 end:
262   if (keyp) {
263     RedisModule_CloseKey(keyp);
264   }
265   SearchCtx_Free(sctx);
266   return REDISMODULE_OK;
267 }
268 
DEBUG_COMMAND(IdToDocId)269 DEBUG_COMMAND(IdToDocId) {
270   if (argc != 2) {
271     return RedisModule_WrongArity(ctx);
272   }
273   GET_SEARCH_CTX(argv[0])
274   long long id;
275   if (RedisModule_StringToLongLong(argv[1], &id) != REDISMODULE_OK) {
276     RedisModule_ReplyWithError(sctx->redisCtx, "bad id given");
277     goto end;
278   }
279   RSDocumentMetadata *doc = DocTable_Get(&sctx->spec->docs, id);
280   if (!doc || (doc->flags & Document_Deleted)) {
281     RedisModule_ReplyWithError(sctx->redisCtx, "document was removed");
282   } else {
283     RedisModule_ReplyWithStringBuffer(sctx->redisCtx, doc->keyPtr, strlen(doc->keyPtr));
284   }
285 end:
286   SearchCtx_Free(sctx);
287   return REDISMODULE_OK;
288 }
289 
DEBUG_COMMAND(DocIdToId)290 DEBUG_COMMAND(DocIdToId) {
291   if (argc != 2) {
292     return RedisModule_WrongArity(ctx);
293   }
294   GET_SEARCH_CTX(argv[0])
295   size_t n;
296   const char *key = RedisModule_StringPtrLen(argv[1], &n);
297   t_docId id = DocTable_GetId(&sctx->spec->docs, key, n);
298   RedisModule_ReplyWithLongLong(sctx->redisCtx, id);
299   SearchCtx_Free(sctx);
300   return REDISMODULE_OK;
301 }
302 
DEBUG_COMMAND(DumpPhoneticHash)303 DEBUG_COMMAND(DumpPhoneticHash) {
304   if (argc != 1) {
305     return RedisModule_WrongArity(ctx);
306   }
307   size_t len;
308   const char *term_c = RedisModule_StringPtrLen(argv[0], &len);
309 
310   char *primary = NULL;
311   char *secondary = NULL;
312 
313   PhoneticManager_ExpandPhonetics(NULL, term_c, len, &primary, &secondary);
314 
315   RedisModule_ReplyWithArray(ctx, 2);
316   RedisModule_ReplyWithStringBuffer(ctx, primary, strlen(primary));
317   RedisModule_ReplyWithStringBuffer(ctx, secondary, strlen(secondary));
318 
319   rm_free(primary);
320   rm_free(secondary);
321   return REDISMODULE_OK;
322 }
323 
GCForceInvokeReply(RedisModuleCtx * ctx,RedisModuleString ** argv,int argc)324 static int GCForceInvokeReply(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
325 #define REPLY "DONE"
326   RedisModule_ReplyWithStringBuffer(ctx, REPLY, strlen(REPLY));
327   return REDISMODULE_OK;
328 }
329 
GCForceInvokeReplyTimeout(RedisModuleCtx * ctx,RedisModuleString ** argv,int argc)330 static int GCForceInvokeReplyTimeout(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
331 #define ERROR_REPLY "INVOCATION FAILED"
332   RedisModule_ReplyWithError(ctx, ERROR_REPLY);
333   return REDISMODULE_OK;
334 }
335 
DEBUG_COMMAND(GCForceInvoke)336 DEBUG_COMMAND(GCForceInvoke) {
337 #define INVOKATION_TIMEOUT 30000  // gc invocation timeout ms
338   if (argc < 1) {
339     return RedisModule_WrongArity(ctx);
340   }
341   IndexSpec *sp = IndexSpec_Load(ctx, RedisModule_StringPtrLen(argv[0], NULL), 0);
342   if (!sp) {
343     RedisModule_ReplyWithError(ctx, "Unknown index name");
344     return REDISMODULE_OK;
345   }
346   RedisModuleBlockedClient *bc = RedisModule_BlockClient(
347       ctx, GCForceInvokeReply, GCForceInvokeReplyTimeout, NULL, INVOKATION_TIMEOUT);
348   GCContext_ForceInvoke(sp->gc, bc);
349   return REDISMODULE_OK;
350 }
351 
DEBUG_COMMAND(GCForceBGInvoke)352 DEBUG_COMMAND(GCForceBGInvoke) {
353   if (argc < 1) {
354     return RedisModule_WrongArity(ctx);
355   }
356   IndexSpec *sp = IndexSpec_Load(ctx, RedisModule_StringPtrLen(argv[0], NULL), 0);
357   if (!sp) {
358     RedisModule_ReplyWithError(ctx, "Unknown index name");
359     return REDISMODULE_OK;
360   }
361   GCContext_ForceBGInvoke(sp->gc);
362   RedisModule_ReplyWithSimpleString(ctx, "OK");
363   return REDISMODULE_OK;
364 }
365 
DEBUG_COMMAND(ttl)366 DEBUG_COMMAND(ttl) {
367   if (argc < 1) {
368     return RedisModule_WrongArity(ctx);
369   }
370   IndexLoadOptions lopts = {.flags = INDEXSPEC_LOAD_NOTIMERUPDATE,
371                             .name = {.cstring = RedisModule_StringPtrLen(argv[0], NULL)}};
372   lopts.flags |= INDEXSPEC_LOAD_KEYLESS;
373   IndexSpec *sp = IndexSpec_LoadEx(ctx, &lopts);
374 
375   if (!sp) {
376     RedisModule_ReplyWithError(ctx, "Unknown index name");
377     return REDISMODULE_OK;
378   }
379 
380   if (!(sp->flags & Index_Temporary)) {
381     RedisModule_ReplyWithError(ctx, "Index is not temporary");
382     return REDISMODULE_OK;
383   }
384 
385   uint64_t remaining = 0;
386   if (RedisModule_GetTimerInfo(RSDummyContext, sp->timerId, &remaining, NULL) != REDISMODULE_OK) {
387     RedisModule_ReplyWithLongLong(ctx, 0);  // timer was called but free operation is async so its
388                                             // gone be free each moment. lets return 0 timeout.
389     return REDISMODULE_OK;
390   }
391   RedisModule_ReplyWithLongLong(ctx, remaining / 1000);  // return the results in seconds
392   return REDISMODULE_OK;
393 }
394 
DEBUG_COMMAND(GitSha)395 DEBUG_COMMAND(GitSha) {
396 #ifdef RS_GIT_SHA
397   RedisModule_ReplyWithStringBuffer(ctx, RS_GIT_SHA, strlen(RS_GIT_SHA));
398 #else
399   RedisModule_ReplyWithError(ctx, "GIT SHA was not defined on compilation");
400 #endif
401   return REDISMODULE_OK;
402 }
403 
404 typedef struct {
405   // Whether to enumerate the number of docids per entry
406   int countValueEntries;
407 
408   // Whether to enumerate the *actual* document IDs in the entry
409   int dumpIdEntries;
410 
411   // offset and limit for the tag entry
412   unsigned offset, limit;
413 
414   // only inspect this value
415   const char *prefix;
416 } DumpOptions;
417 
seekTagIterator(TrieMapIterator * it,size_t offset)418 static void seekTagIterator(TrieMapIterator *it, size_t offset) {
419   char *tag;
420   tm_len_t len;
421   InvertedIndex *iv;
422 
423   for (size_t n = 0; n < offset; n++) {
424     if (!TrieMapIterator_Next(it, &tag, &len, (void **)&iv)) {
425       break;
426     }
427   }
428 }
429 
430 /**
431  * INFO_TAGIDX <index> <field> [OPTIONS...]
432  */
DEBUG_COMMAND(InfoTagIndex)433 DEBUG_COMMAND(InfoTagIndex) {
434   if (argc < 2) {
435     return RedisModule_WrongArity(ctx);
436   }
437   GET_SEARCH_CTX(argv[0]);
438   DumpOptions options = {0};
439   ACArgSpec argspecs[] = {
440       {.name = "count_value_entries",
441        .type = AC_ARGTYPE_BOOLFLAG,
442        .target = &options.countValueEntries},
443       {.name = "dump_id_entries", .type = AC_ARGTYPE_BOOLFLAG, .target = &options.dumpIdEntries},
444       {.name = "prefix", .type = AC_ARGTYPE_STRING, .target = &options.prefix},
445       {.name = "offset", .type = AC_ARGTYPE_UINT, .target = &options.offset},
446       {.name = "limit", .type = AC_ARGTYPE_UINT, .target = &options.limit},
447       {NULL}};
448   RedisModuleKey *keyp = NULL;
449   ArgsCursor ac = {0};
450   ACArgSpec *errSpec = NULL;
451   ArgsCursor_InitRString(&ac, argv + 2, argc - 2);
452   int rv = AC_ParseArgSpec(&ac, argspecs, &errSpec);
453   if (rv != AC_OK) {
454     RedisModule_ReplyWithError(ctx, "Could not parse argument (argspec fixme)");
455     goto end;
456   }
457 
458   RedisModuleString *keyName = getFieldKeyName(sctx->spec, argv[1], INDEXFLD_T_TAG);
459   if (!keyName) {
460     RedisModule_ReplyWithError(sctx->redisCtx, "Could not find given field in index spec");
461     goto end;
462   }
463 
464   const TagIndex *idx = TagIndex_Open(sctx, keyName, false, &keyp);
465   if (!idx) {
466     RedisModule_ReplyWithError(sctx->redisCtx, "can not open tag field");
467     goto end;
468   }
469 
470   size_t nelem = 0;
471   RedisModule_ReplyWithArray(ctx, REDISMODULE_POSTPONED_ARRAY_LEN);
472   RedisModule_ReplyWithSimpleString(ctx, "num_values");
473   RedisModule_ReplyWithLongLong(ctx, idx->values->cardinality);
474   nelem += 2;
475 
476   if (options.dumpIdEntries) {
477     options.countValueEntries = 1;
478   }
479   int shouldDescend = options.countValueEntries || options.dumpIdEntries;
480   if (!shouldDescend) {
481     goto reply_done;
482   }
483 
484   size_t limit = options.limit ? options.limit : 0;
485   TrieMapIterator *iter = TrieMap_Iterate(idx->values, "", 0);
486   char *tag;
487   tm_len_t len;
488   InvertedIndex *iv;
489 
490   nelem += 2;
491   RedisModule_ReplyWithSimpleString(ctx, "values");
492   RedisModule_ReplyWithArray(ctx, REDISMODULE_POSTPONED_ARRAY_LEN);
493 
494   seekTagIterator(iter, options.offset);
495   size_t nvalues = 0;
496   while (nvalues++ < limit && TrieMapIterator_Next(iter, &tag, &len, (void **)&iv)) {
497     size_t nsubelem = 8;
498     if (!options.dumpIdEntries) {
499       nsubelem -= 2;
500     }
501     RedisModule_ReplyWithArray(ctx, REDISMODULE_POSTPONED_ARRAY_LEN);
502 
503     RedisModule_ReplyWithSimpleString(ctx, "value");
504     RedisModule_ReplyWithStringBuffer(ctx, tag, len);
505 
506     RedisModule_ReplyWithSimpleString(ctx, "num_entries");
507     RedisModule_ReplyWithLongLong(ctx, iv->numDocs);
508 
509     RedisModule_ReplyWithSimpleString(ctx, "num_blocks");
510     RedisModule_ReplyWithLongLong(ctx, iv->size);
511 
512     if (options.dumpIdEntries) {
513       RedisModule_ReplyWithSimpleString(ctx, "entries");
514       IndexReader *reader = NewTermIndexReader(iv, NULL, RS_FIELDMASK_ALL, NULL, 1);
515       ReplyReaderResults(reader, sctx->redisCtx);
516     }
517 
518     RedisModule_ReplySetArrayLength(ctx, nsubelem);
519   }
520   TrieMapIterator_Free(iter);
521   RedisModule_ReplySetArrayLength(ctx, nvalues - 1);
522 
523 reply_done:
524   RedisModule_ReplySetArrayLength(ctx, nelem);
525 
526 end:
527   if (keyp) {
528     RedisModule_CloseKey(keyp);
529   }
530   SearchCtx_Free(sctx);
531   return REDISMODULE_OK;
532 }
533 
replyDocFlags(const RSDocumentMetadata * dmd,RedisModuleCtx * ctx)534 static void replyDocFlags(const RSDocumentMetadata *dmd, RedisModuleCtx *ctx) {
535   char buf[1024] = {0};
536   sprintf(buf, "(0x%x):", dmd->flags);
537   if (dmd->flags & Document_Deleted) {
538     strcat(buf, "Deleted,");
539   }
540   if (dmd->flags & Document_HasPayload) {
541     strcat(buf, "HasPayload,");
542   }
543   if (dmd->flags & Document_HasSortVector) {
544     strcat(buf, "HasSortVector,");
545   }
546   if (dmd->flags & Document_HasOffsetVector) {
547     strcat(buf, "HasOffsetVector,");
548   }
549   RedisModule_ReplyWithSimpleString(ctx, buf);
550 }
551 
replySortVector(const RSDocumentMetadata * dmd,RedisSearchCtx * sctx)552 static void replySortVector(const RSDocumentMetadata *dmd, RedisSearchCtx *sctx) {
553   RSSortingVector *sv = dmd->sortVector;
554   RedisModule_ReplyWithArray(sctx->redisCtx, REDISMODULE_POSTPONED_ARRAY_LEN);
555   size_t nelem = 0;
556   for (size_t ii = 0; ii < sv->len; ++ii) {
557     if (!sv->values[ii]) {
558       continue;
559     }
560     RedisModule_ReplyWithArray(sctx->redisCtx, 6);
561     RedisModule_ReplyWithSimpleString(sctx->redisCtx, "index");
562     RedisModule_ReplyWithLongLong(sctx->redisCtx, ii);
563     RedisModule_ReplyWithSimpleString(sctx->redisCtx, "field");
564     const FieldSpec *fs = IndexSpec_GetFieldBySortingIndex(sctx->spec, ii);
565     RedisModule_ReplyWithPrintf(sctx->redisCtx, "%s AS %s", fs ? fs->path : "!!!", fs ? fs->name : "???");
566     RedisModule_ReplyWithSimpleString(sctx->redisCtx, "value");
567     RSValue_SendReply(sctx->redisCtx, sv->values[ii], 0);
568     nelem++;
569   }
570   RedisModule_ReplySetArrayLength(sctx->redisCtx, nelem);
571 }
572 
573 /**
574  * FT.DEBUG DOC_INFO <index> <doc>
575  */
DEBUG_COMMAND(DocInfo)576 DEBUG_COMMAND(DocInfo) {
577   if (argc < 2) {
578     return RedisModule_WrongArity(ctx);
579   }
580   GET_SEARCH_CTX(argv[0]);
581 
582   const RSDocumentMetadata *dmd = DocTable_GetByKeyR(&sctx->spec->docs, argv[1]);
583   if (!dmd) {
584     SearchCtx_Free(sctx);
585     return RedisModule_ReplyWithError(ctx, "Document not found in index");
586   }
587 
588   size_t nelem = 0;
589   RedisModule_ReplyWithArray(ctx, REDISMODULE_POSTPONED_ARRAY_LEN);
590   RedisModule_ReplyWithSimpleString(ctx, "internal_id");
591   RedisModule_ReplyWithLongLong(ctx, dmd->id);
592   nelem += 2;
593   RedisModule_ReplyWithSimpleString(ctx, "flags");
594   replyDocFlags(dmd, ctx);
595   nelem += 2;
596   RedisModule_ReplyWithSimpleString(ctx, "score");
597   RedisModule_ReplyWithDouble(ctx, dmd->score);
598   nelem += 2;
599   RedisModule_ReplyWithSimpleString(ctx, "num_tokens");
600   RedisModule_ReplyWithLongLong(ctx, dmd->len);
601   nelem += 2;
602   RedisModule_ReplyWithSimpleString(ctx, "max_freq");
603   RedisModule_ReplyWithLongLong(ctx, dmd->maxFreq);
604   nelem += 2;
605   RedisModule_ReplyWithSimpleString(ctx, "refcount");
606   RedisModule_ReplyWithLongLong(ctx, dmd->ref_count);
607   nelem += 2;
608   if (dmd->sortVector) {
609     RedisModule_ReplyWithSimpleString(ctx, "sortables");
610     replySortVector(dmd, sctx);
611     nelem += 2;
612   }
613   RedisModule_ReplySetArrayLength(ctx, nelem);
614   SearchCtx_Free(sctx);
615   return REDISMODULE_OK;
616 }
617 
618 typedef struct DebugCommandType {
619   char *name;
620   int (*callback)(RedisModuleCtx *ctx, RedisModuleString **argv, int argc);
621 } DebugCommandType;
622 
623 DebugCommandType commands[] = {{"DUMP_INVIDX", DumpInvertedIndex},
624                                {"DUMP_NUMIDX", DumpNumericIndex},
625                                {"DUMP_TAGIDX", DumpTagIndex},
626                                {"INFO_TAGIDX", InfoTagIndex},
627                                {"IDTODOCID", IdToDocId},
628                                {"DOCIDTOID", DocIdToId},
629                                {"DOCINFO", DocInfo},
630                                {"DUMP_PHONETIC_HASH", DumpPhoneticHash},
631                                {"DUMP_TERMS", DumpTerms},
632                                {"INVIDX_SUMMARY", InvertedIndexSummary},
633                                {"NUMIDX_SUMMARY", NumericIndexSummary},
634                                {"GC_FORCEINVOKE", GCForceInvoke},
635                                {"GC_FORCEBGINVOKE", GCForceBGInvoke},
636                                {"GIT_SHA", GitSha},
637                                {"TTL", ttl},
638                                {NULL, NULL}};
639 
DebugCommand(RedisModuleCtx * ctx,RedisModuleString ** argv,int argc)640 int DebugCommand(RedisModuleCtx *ctx, RedisModuleString **argv, int argc) {
641 
642   if (argc < 2) {
643     return RedisModule_WrongArity(ctx);
644   }
645 
646   const char *subCommand = RedisModule_StringPtrLen(argv[1], NULL);
647 
648   if (strcasecmp(subCommand, "help") == 0) {
649     size_t len = 0;
650     RedisModule_ReplyWithArray(ctx, REDISMODULE_POSTPONED_ARRAY_LEN);
651     for (DebugCommandType *c = &commands[0]; c->name != NULL; c++) {
652       RedisModule_ReplyWithStringBuffer(ctx, c->name, strlen(c->name));
653       ++len;
654     }
655     RedisModule_ReplySetArrayLength(ctx, len);
656     return REDISMODULE_OK;
657   }
658 
659   for (DebugCommandType *c = &commands[0]; c->name != NULL; c++) {
660     if (strcasecmp(c->name, subCommand) == 0) {
661       return c->callback(ctx, argv + 2, argc - 2);
662     }
663   }
664 
665   RedisModule_ReplyWithError(ctx, "subcommand was not found");
666 
667   return REDISMODULE_OK;
668 }
669