1# -*- coding: utf-8 -*-
2
3import redis
4import unittest
5from hotels import hotels
6import random
7import time
8from RLTest import Env
9from includes import *
10
11def testAdd(env):
12    if env.is_cluster():
13        raise unittest.SkipTest()
14
15    r = env
16    env.assertOk(r.execute_command(
17        'ft.create', 'idx', 'schema', 'title', 'text', 'body', 'text'))
18    env.assertTrue(r.exists('idx:idx'))
19    env.assertOk(r.execute_command('ft.add', 'idx', 'doc1', 1.0, 'fields',
20                                    'title', 'hello world',
21                                    'body', 'lorem ist ipsum'))
22
23    for _ in r.retry_with_rdb_reload():
24        prefix = 'ft'
25        env.assertExists(prefix + ':idx/hello')
26        env.assertExists(prefix + ':idx/world')
27        env.assertExists(prefix + ':idx/lorem')
28
29def testAddErrors(env):
30    env.expect('ft.create idx schema foo text bar numeric sortable').equal('OK')
31    env.expect('ft.add idx doc1 1 redis 4').error().contains('Unknown keyword')
32    env.expect('ft.add idx doc1').error().contains("wrong number of arguments")
33    env.expect('ft.add idx doc1 42').error().contains("Score must be between 0 and 1")
34    env.expect('ft.add idx doc1 1.0').error().contains("No field list found")
35    env.expect('ft.add fake_idx doc1 1.0 fields foo bar').error().contains("Unknown index name")
36
37def assertEqualIgnoreCluster(env, val1, val2):
38    # todo: each test that uses this function should be switch back to env.assertEqual once fix
39    # issues on coordinator
40    if env.isCluster():
41        return
42    env.assertEqual(val1, val2)
43
44def testConditionalUpdate(env):
45    env.assertOk(env.cmd(
46        'ft.create', 'idx', 'schema', 'foo', 'text', 'bar', 'numeric', 'sortable'))
47    env.assertOk(env.cmd('ft.add', 'idx', '1', '1',
48                           'fields', 'foo', 'hello', 'bar', '123'))
49    env.assertOk(env.cmd('ft.add', 'idx', '1', '1', 'replace', 'if',
50                           '@foo == "hello"', 'fields', 'foo', 'world', 'bar', '123'))
51    env.assertEqual('NOADD', env.cmd('ft.add', 'idx', '1', '1', 'replace',
52                                       'if', '@foo == "hello"', 'fields', 'foo', 'world', 'bar', '123'))
53    env.assertEqual('NOADD', env.cmd('ft.add', 'idx', '1', '1', 'replace',
54                                       'if', '1 == 2', 'fields', 'foo', 'world', 'bar', '123'))
55    env.assertOk(env.cmd('ft.add', 'idx', '1', '1', 'replace', 'partial', 'if',
56                           '@foo == "world"', 'fields', 'bar', '234'))
57    env.assertOk(env.cmd('ft.add', 'idx', '1', '1', 'replace', 'if',
58                           '@bar == 234', 'fields', 'foo', 'hello', 'bar', '123'))
59
60    # Ensure that conditionals are ignored if the document doesn't exist
61    env.assertOk(env.cmd('FT.ADD', 'idx', '666', '1',
62                           'IF', '@bar > 42', 'FIELDS', 'bar', '15'))
63    # Ensure that it fails if we try again, because it already exists
64    env.assertEqual('NOADD', env.cmd('FT.ADD', 'idx', '666', '1',
65                                       'REPLACE', 'IF', '@bar > 42', 'FIELDS', 'bar', '15'))
66    # Ensure that it fails because we're not using 'REPLACE'
67    with env.assertResponseError():
68        env.assertOk(env.cmd('FT.ADD', 'idx', '666', '1',
69                               'IF', '@bar > 42', 'FIELDS', 'bar', '15'))
70
71def testUnionIdList(env):
72    # Regression test for https://github.com/RediSearch/RediSearch/issues/306
73    r = env
74    N = 100
75    env.assertOk(r.execute_command(
76        "ft.create", "test", "SCHEMA",  "tags", "TAG", "waypoint", "GEO"))
77    env.assertOk(r.execute_command(
78        "ft.add", "test", "1", "1", "FIELDS", "tags", "alberta", "waypoint", "-113.524,53.5244"))
79    env.assertOk(r.execute_command(
80        "ft.add", "test", "2", "1", "FIELDS", "tags", "ontario", "waypoint", "-79.395,43.661667"))
81
82    r.cmd('ft.search', 'test', '@tags:{ontario}')
83
84    res = r.execute_command(
85        'ft.search', 'test', "@waypoint:[-113.52 53.52 20 mi]|@tags:{ontario}", 'nocontent')
86    env.assertEqual(res, [2, '2', '1'])
87
88def testAttributes(env):
89    env.assertOk(env.cmd('ft.create', 'idx', 'schema',
90                           'title', 'text', 'body', 'text'))
91    env.assertOk(env.cmd('ft.add', 'idx', 'doc1', 1.0, 'fields',
92                                            'title', 't1 t2', 'body', 't3 t4 t5'))
93    env.assertOk(env.cmd('ft.add', 'idx', 'doc2', 1.0, 'fields',
94                           'body', 't1 t2', 'title', 't3 t5'))
95
96    res = env.cmd(
97        'ft.search', 'idx', '(@title:(t1 t2) => {$weight: 0.2}) |(@body:(t1 t2) => {$weight: 0.5})', 'nocontent')
98    env.assertListEqual([2L, 'doc2', 'doc1'], res)
99    res = env.cmd(
100        'ft.search', 'idx', '(@title:(t1 t2) => {$weight: 2.5}) |(@body:(t1 t2) => {$weight: 0.5})', 'nocontent')
101    env.assertListEqual([2L, 'doc1', 'doc2'], res)
102
103    res = env.cmd(
104        'ft.search', 'idx', '(t3 t5) => {$slop: 4}', 'nocontent')
105    env.assertListEqual([2L, 'doc2', 'doc1'], res)
106    res = env.cmd(
107        'ft.search', 'idx', '(t5 t3) => {$slop: 0}', 'nocontent')
108    env.assertListEqual([1L, 'doc2'], res)
109    res = env.cmd(
110        'ft.search', 'idx', '(t5 t3) => {$slop: 0; $inorder:true}', 'nocontent')
111    env.assertListEqual([0], res)
112
113def testUnion(env):
114    N = 100
115    r = env
116    env.assertOk(r.execute_command(
117        'ft.create', 'idx', 'schema', 'f', 'text'))
118    for i in range(N):
119
120        env.assertOk(r.execute_command('ft.add', 'idx', 'doc%d' % i, 1.0, 'fields',
121                                        'f', 'hello world' if i % 2 == 0 else 'hallo werld'))
122
123    for _ in r.retry_with_rdb_reload():
124        res = r.execute_command(
125            'ft.search', 'idx', 'hello|hallo', 'nocontent', 'limit', '0', '100')
126        env.assertEqual(N + 1, len(res))
127        env.assertEqual(N, res[0])
128
129        res = r.execute_command(
130            'ft.search', 'idx', 'hello|world', 'nocontent', 'limit', '0', '100')
131        env.assertEqual(51, len(res))
132        env.assertEqual(50, res[0])
133
134        res = r.execute_command('ft.search', 'idx', '(hello|hello)(world|world)',
135                                'nocontent', 'verbatim', 'limit', '0', '100')
136        env.assertEqual(51, len(res))
137        env.assertEqual(50, res[0])
138
139        res = r.execute_command(
140            'ft.search', 'idx', '(hello|hallo)(werld|world)', 'nocontent', 'verbatim', 'limit', '0', '100')
141        env.assertEqual(101, len(res))
142        env.assertEqual(100, res[0])
143
144        res = r.execute_command(
145            'ft.search', 'idx', '(hallo|hello)(world|werld)', 'nocontent', 'verbatim', 'limit', '0', '100')
146        env.assertEqual(101, len(res))
147        env.assertEqual(100, res[0])
148
149        res = r.execute_command(
150            'ft.search', 'idx', '(hello|werld)(hallo|world)', 'nocontent', 'verbatim', 'limit', '0', '100')
151        env.assertEqual(101, len(res))
152        env.assertEqual(100, res[0])
153
154        res = r.execute_command(
155            'ft.search', 'idx', '(hello|hallo) world', 'nocontent', 'verbatim', 'limit', '0', '100')
156        env.assertEqual(51, len(res))
157        env.assertEqual(50, res[0])
158
159        res = r.execute_command(
160            'ft.search', 'idx', '(hello world)|((hello world)|(hallo world|werld) | hello world werld)', 'nocontent', 'verbatim', 'limit', '0', '100')
161        env.assertEqual(101, len(res))
162        env.assertEqual(100, res[0])
163
164def testSearch(env):
165    r = env
166    env.assertOk(r.execute_command(
167        'ft.create', 'idx', 'schema', 'title', 'text', 'weight', 10.0, 'body', 'text'))
168    env.assertOk(r.execute_command('ft.add', 'idx', 'doc1', 0.5, 'fields',
169                                    'title', 'hello world',
170                                    'body', 'lorem ist ipsum'))
171    env.assertOk(r.execute_command('ft.add', 'idx', 'doc2', 1.0, 'fields',
172                                    'title', 'hello another world',
173                                    'body', 'lorem ist ipsum lorem lorem'))
174    for _ in r.retry_with_rdb_reload():
175
176        res = r.execute_command('ft.search', 'idx', 'hello')
177
178        env.assertTrue(len(res) == 5)
179        env.assertEqual(res[0], 2L)
180        env.assertEqual(res[1], "doc2")
181        env.assertTrue(isinstance(res[2], list))
182        env.assertTrue('title' in res[2])
183        env.assertTrue('hello another world' in res[2])
184        env.assertEqual(res[3], "doc1")
185        env.assertTrue('hello world' in res[4])
186
187        # Test empty query
188        res = r.execute_command('ft.search', 'idx', '')
189        env.assertListEqual([0], res)
190
191        # Test searching with no content
192        res = r.execute_command(
193            'ft.search', 'idx', 'hello', 'nocontent')
194        env.assertTrue(len(res) == 3)
195        env.assertEqual(res[0], 2L)
196        env.assertEqual(res[1], "doc2")
197        env.assertEqual(res[2], "doc1")
198
199        # Test searching WITHSCORES
200        res = r.execute_command(
201            'ft.search', 'idx', 'hello', 'WITHSCORES')
202        env.assertEqual(len(res), 7)
203        env.assertEqual(res[0], 2L)
204        env.assertEqual(res[1], "doc2")
205        env.assertTrue(float(res[2]) > 0)
206        env.assertEqual(res[4], "doc1")
207        env.assertTrue(float(res[5]) > 0)
208
209        # Test searching WITHSCORES NOCONTENT
210        res = r.execute_command(
211            'ft.search', 'idx', 'hello', 'WITHSCORES', 'NOCONTENT')
212        env.assertEqual(len(res), 5)
213        env.assertEqual(res[0], 2L)
214        env.assertEqual(res[1], "doc2")
215        env.assertTrue(float(res[2]) > 0)
216        env.assertEqual(res[3], "doc1")
217        env.assertTrue(float(res[4]) > 0)
218
219def testSearchNosave(env):
220    # Check to see what happens when we try to return unsaved documents
221    env.cmd('ft.create', 'idx', 'SCHEMA', 'f1', 'text')
222    # Add 3 documents
223    for x in range(3):
224        env.cmd('ft.add', 'idx', 'doc{}'.format(x),
225                 1.0, 'NOSAVE', 'FIELDS', 'f1', 'value')
226
227    # Now query the results
228    res = env.cmd('ft.search', 'idx', 'value')
229    env.assertEqual(3, res[0])
230    for content in res[2::2]:
231        env.assertEqual([], content)
232
233def testGet(env):
234    r = env
235    env.assertOk(r.execute_command(
236        'ft.create', 'idx', 'schema', 'foo', 'text', 'bar', 'text'))
237
238    env.expect('ft.get').error().contains("wrong number of arguments")
239    env.expect('ft.get', 'idx').error().contains("wrong number of arguments")
240    env.expect('ft.get', 'idx', 'foo', 'bar').error().contains("wrong number of arguments")
241    env.expect('ft.mget').error().contains("wrong number of arguments")
242    env.expect('ft.mget', 'idx').error().contains("wrong number of arguments")
243    env.expect('ft.mget', 'fake_idx').error().contains("wrong number of arguments")
244
245    env.expect('ft.get fake_idx foo').error().contains("Unknown Index name")
246    env.expect('ft.mget fake_idx foo').error().contains("Unknown Index name")
247
248    for i in range(100):
249        env.assertOk(r.execute_command('ft.add', 'idx', 'doc%d' % i, 1.0, 'fields',
250                                        'foo', 'hello world', 'bar', 'wat wat'))
251
252    for i in range(100):
253        res = r.execute_command('ft.get', 'idx', 'doc%d' % i)
254        env.assertIsNotNone(res)
255        env.assertListEqual(
256            ['foo', 'hello world', 'bar', 'wat wat'], res)
257        env.assertIsNone(r.execute_command(
258            'ft.get', 'idx', 'doc%dsdfsd' % i))
259    env.expect('ft.get', 'no_idx', 'doc0').error().contains("Unknown Index name")
260
261    rr = r.execute_command(
262        'ft.mget', 'idx', *('doc%d' % i for i in range(100)))
263    env.assertEqual(len(rr), 100)
264    for res in rr:
265        env.assertIsNotNone(res)
266        env.assertListEqual(
267            ['foo', 'hello world', 'bar', 'wat wat'], res)
268    rr = r.execute_command(
269        'ft.mget', 'idx', *('doc-%d' % i for i in range(100)))
270    env.assertEqual(len(rr), 100)
271    for res in rr:
272        env.assertIsNone(res)
273
274    # Verify that when a document is deleted, GET returns NULL
275    r.cmd('ft.del', 'idx', 'doc10') # But we still keep the document
276    r.cmd('ft.del', 'idx', 'doc11')
277    r.cmd('ft.del', 'idx', 'coverage')
278    res = r.cmd('ft.get', 'idx', 'doc10')
279    r.assertEqual(None, res)
280    res = r.cmd('ft.mget', 'idx', 'doc10')
281    r.assertEqual([None], res)
282    res = r.cmd('ft.mget', 'idx', 'doc10', 'doc11', 'doc12')
283    r.assertIsNone(res[0])
284    r.assertIsNone(res[1])
285    r.assertTrue(not not res[2])
286
287def testDelete(env):
288    r = env
289    env.assertOk(r.execute_command(
290        'ft.create', 'idx', 'schema', 'f', 'text'))
291
292    for i in range(100):
293        env.assertOk(r.execute_command('ft.add', 'idx', 'doc%d' % i, 1.0, 'fields',
294                                        'f', 'hello world'))
295
296    env.expect('ft.del', 'fake_idx', 'doc1').error()
297
298    for i in range(100):
299        # the doc hash should exist now
300        r.expect('ft.get', 'idx', 'doc%d' % i).notRaiseError()
301        # Delete the actual docs only half of the time
302        env.assertEqual(1, r.execute_command(
303            'ft.del', 'idx', 'doc%d' % i, 'DD' if i % 2 == 0 else ''))
304        # second delete should return 0
305        env.assertEqual(0, r.execute_command(
306            'ft.del', 'idx', 'doc%d' % i))
307
308        # After del with DD the doc hash should not exist
309        if i % 2 == 0:
310            env.assertFalse(r.exists('doc%d' % i))
311        else:
312            r.expect('ft.get', 'idx', 'doc%d' % i).notRaiseError()
313        res = r.execute_command(
314            'ft.search', 'idx', 'hello', 'nocontent', 'limit', 0, 100)
315        env.assertNotIn('doc%d' % i, res)
316        env.assertEqual(res[0], 100 - i - 1)
317        env.assertEqual(len(res), 100 - i)
318
319        # test reinsertion
320        env.assertOk(r.execute_command('ft.add', 'idx', 'doc%d' % i, 1.0, 'fields',
321                                        'f', 'hello world'))
322        res = r.execute_command(
323            'ft.search', 'idx', 'hello', 'nocontent', 'limit', 0, 100)
324        env.assertIn('doc%d' % i, res)
325        env.assertEqual(1, r.execute_command(
326            'ft.del', 'idx', 'doc%d' % i))
327    for _ in r.retry_with_rdb_reload():
328        did = 'rrrr'
329        env.assertOk(r.execute_command('ft.add', 'idx', did, 1, 'fields',
330                                        'f', 'hello world'))
331        env.assertEqual(1, r.execute_command('ft.del', 'idx', did))
332        env.assertEqual(0, r.execute_command('ft.del', 'idx', did))
333        env.assertOk(r.execute_command('ft.add', 'idx', did, 1, 'fields',
334                                        'f', 'hello world'))
335        env.assertEqual(1, r.execute_command('ft.del', 'idx', did))
336        env.assertEqual(0, r.execute_command('ft.del', 'idx', did))
337
338def testReplace(env):
339    r = env
340
341    env.assertOk(r.execute_command(
342        'ft.create', 'idx', 'schema', 'f', 'text'))
343
344    env.assertOk(r.execute_command('ft.add', 'idx', 'doc1', 1.0, 'fields',
345                                    'f', 'hello world'))
346    env.assertOk(r.execute_command('ft.add', 'idx', 'doc2', 1.0, 'fields',
347                                    'f', 'hello world'))
348    res = r.execute_command(
349        'ft.search', 'idx', 'hello world')
350    env.assertEqual(2, res[0])
351
352    with env.assertResponseError():
353        # make sure we can't insert a doc twice
354        res = r.execute_command('ft.add', 'idx', 'doc1', 1.0, 'fields',
355                                'f', 'hello world')
356
357    # now replace doc1 with a different content
358    env.assertOk(r.execute_command('ft.add', 'idx', 'doc1', 1.0, 'replace', 'fields',
359                                    'f', 'goodbye universe'))
360
361    for _ in r.retry_with_rdb_reload():
362        # make sure the query for hello world does not return the replaced
363        # document
364        res = r.execute_command(
365            'ft.search', 'idx', 'hello world', 'nocontent')
366        env.assertEqual(1, res[0])
367        env.assertEqual('doc2', res[1])
368
369        # search for the doc's new content
370        res = r.execute_command(
371            'ft.search', 'idx', 'goodbye universe', 'nocontent')
372        env.assertEqual(1, res[0])
373        env.assertEqual('doc1', res[1])
374
375def testDrop(env):
376    r = env
377    env.assertOk(r.execute_command(
378        'ft.create', 'idx', 'schema', 'f', 'text', 'n', 'numeric', 't', 'tag', 'g', 'geo'))
379
380    for i in range(100):
381        env.assertOk(r.execute_command('ft.add', 'idx', 'doc%d' % i, 1.0, 'fields',
382                                        'f', 'hello world', 'n', 666, 't', 'foo bar',
383                                        'g', '19.04,47.497'))
384    keys = r.keys('*')
385    env.assertGreaterEqual(len(keys), 100)
386
387    env.assertOk(r.execute_command('ft.drop', 'idx'))
388    keys = r.keys('*')
389    env.assertEqual(0, len(keys))
390
391    # Now do the same with KEEPDOCS
392    env.assertOk(r.execute_command(
393        'ft.create', 'idx', 'schema', 'f', 'text', 'n', 'numeric', 't', 'tag', 'g', 'geo'))
394
395    for i in range(100):
396        env.assertOk(r.execute_command('ft.add', 'idx', 'doc%d' % i, 1.0, 'fields',
397                                        'f', 'hello world', 'n', 666, 't', 'foo bar',
398                                        'g', '19.04,47.497'))
399    keys = r.keys('*')
400    env.assertGreaterEqual(len(keys), 100)
401
402    if not env.is_cluster():
403        env.assertOk(r.execute_command('ft.drop', 'idx', 'KEEPDOCS'))
404        keys = r.keys('*')
405        env.assertListEqual(['doc0', 'doc1', 'doc10', 'doc11', 'doc12', 'doc13', 'doc14', 'doc15', 'doc16', 'doc17', 'doc18', 'doc19', 'doc2', 'doc20', 'doc21', 'doc22', 'doc23', 'doc24', 'doc25', 'doc26', 'doc27', 'doc28', 'doc29', 'doc3', 'doc30', 'doc31', 'doc32', 'doc33', 'doc34', 'doc35', 'doc36', 'doc37', 'doc38', 'doc39', 'doc4', 'doc40', 'doc41', 'doc42', 'doc43', 'doc44', 'doc45', 'doc46', 'doc47', 'doc48', 'doc49', 'doc5', 'doc50', 'doc51', 'doc52', 'doc53',
406                              'doc54', 'doc55', 'doc56', 'doc57', 'doc58', 'doc59', 'doc6', 'doc60', 'doc61', 'doc62', 'doc63', 'doc64', 'doc65', 'doc66', 'doc67', 'doc68', 'doc69', 'doc7', 'doc70', 'doc71', 'doc72', 'doc73', 'doc74', 'doc75', 'doc76', 'doc77', 'doc78', 'doc79', 'doc8', 'doc80', 'doc81', 'doc82', 'doc83', 'doc84', 'doc85', 'doc86', 'doc87', 'doc88', 'doc89', 'doc9', 'doc90', 'doc91', 'doc92', 'doc93', 'doc94', 'doc95', 'doc96', 'doc97', 'doc98', 'doc99'], sorted(keys))
407
408    env.expect('FT.DROP', 'idx', 'KEEPDOCS', '666').error().contains("wrong number of arguments")
409
410def testCustomStopwords(env):
411    r = env
412    # Index with default stopwords
413    env.assertOk(r.execute_command(
414        'ft.create', 'idx', 'schema', 'foo', 'text'))
415
416    # Index with custom stopwords
417    env.assertOk(r.execute_command('ft.create', 'idx2', 'stopwords', 2, 'hello', 'world',
418                                    'schema', 'foo', 'text'))
419    if not env.isCluster:
420        res = env.cmd('ft.info', 'idx2')
421        env.assertEqual(res[39], ['hello', 'world'])
422
423    # Index with NO stopwords
424    env.assertOk(r.execute_command('ft.create', 'idx3', 'stopwords', 0,
425                                    'schema', 'foo', 'text'))
426
427    if not env.isCluster:
428        res = env.cmd('ft.info', 'idx3')
429        env.assertEqual(res[39], [])
430
431    for idx in ('idx', 'idx2', 'idx3'):
432        env.assertOk(r.execute_command(
433            'ft.add', idx, 'doc1', 1.0, 'fields', 'foo', 'hello world'))
434        env.assertOk(r.execute_command(
435            'ft.add', idx, 'doc2', 1.0, 'fields', 'foo', 'to be or not to be'))
436
437    for _ in r.retry_with_rdb_reload():
438        # Normal index should return results just for 'hello world'
439        env.assertEqual([1, 'doc1'],  r.execute_command(
440            'ft.search', 'idx', 'hello world', 'nocontent'))
441        env.assertEqual([0],  r.execute_command(
442            'ft.search', 'idx', 'to be or not', 'nocontent'))
443
444        # Custom SW index should return results just for 'to be or not'
445        env.assertEqual([0],  r.execute_command(
446            'ft.search', 'idx2', 'hello world', 'nocontent'))
447        env.assertEqual([1, 'doc2'],  r.execute_command(
448            'ft.search', 'idx2', 'to be or not', 'nocontent'))
449
450        # No SW index should return results for both
451        env.assertEqual([1, 'doc1'],  r.execute_command(
452            'ft.search', 'idx3', 'hello world', 'nocontent'))
453        env.assertEqual([1, 'doc2'],  r.execute_command(
454            'ft.search', 'idx3', 'to be or not', 'nocontent'))
455
456def testStopwords(env):
457    # This test was taken from Python's tests, and failed due to some changes
458    # made earlier
459    env.cmd('ft.create', 'idx', 'stopwords', 3, 'foo',
460             'bar', 'baz', 'schema', 'txt', 'text')
461    env.cmd('ft.add', 'idx', 'doc1', 1.0, 'fields', 'txt', 'foo bar')
462    env.cmd('ft.add', 'idx', 'doc2', 1.0, 'fields', 'txt', 'hello world')
463
464    r1 = env.cmd('ft.search', 'idx', 'foo bar', 'nocontent')
465    r2 = env.cmd('ft.search', 'idx', 'foo bar hello world', 'nocontent')
466    env.assertEqual(0, r1[0])
467    env.assertEqual(1, r2[0])
468
469def testNoStopwords(env):
470    # This test taken from Java's test suite
471    env.cmd('ft.create', 'idx', 'schema', 'title', 'text')
472    for i in range(100):
473        env.cmd('ft.add', 'idx', 'doc{}'.format(i), 1.0, 'fields',
474                 'title', 'hello world' if i % 2 == 0 else 'hello worlds')
475
476    res = env.cmd('ft.search', 'idx', 'hello a world', 'NOCONTENT')
477    env.assertEqual(100, res[0])
478
479    res = env.cmd('ft.search', 'idx', 'hello a world',
480                   'VERBATIM', 'NOCONTENT')
481    env.assertEqual(50, res[0])
482
483    res = env.cmd('ft.search', 'idx', 'hello a world', 'NOSTOPWORDS')
484    env.assertEqual(0, res[0])
485
486def testOptional(env):
487    r = env
488    env.assertOk(r.execute_command(
489        'ft.create', 'idx', 'schema', 'foo', 'text'))
490    env.assertOk(r.execute_command('ft.add', 'idx',
491                                    'doc1', 1.0, 'fields', 'foo', 'hello wat woot'))
492    env.assertOk(r.execute_command('ft.add', 'idx', 'doc2',
493                                    1.0, 'fields', 'foo', 'hello world woot'))
494    env.assertOk(r.execute_command('ft.add', 'idx', 'doc3',
495                                    1.0, 'fields', 'foo', 'hello world werld'))
496
497    res = r.execute_command('ft.search', 'idx', 'hello', 'nocontent')
498    env.assertEqual([3L, 'doc3', 'doc2', 'doc1'], res)
499    res = r.execute_command(
500        'ft.search', 'idx', 'hello world', 'nocontent', 'scorer', 'DISMAX')
501    env.assertEqual([2L, 'doc3', 'doc2'], res)
502    res = r.execute_command(
503        'ft.search', 'idx', 'hello ~world', 'nocontent', 'scorer', 'DISMAX')
504    env.assertEqual([3L, 'doc3', 'doc2', 'doc1'], res)
505    res = r.execute_command(
506        'ft.search', 'idx', 'hello ~world ~werld', 'nocontent', 'scorer', 'DISMAX')
507    env.assertEqual([3L, 'doc3', 'doc2', 'doc1'], res)
508    res = r.execute_command(
509        'ft.search', 'idx', '~world ~werld hello', 'nocontent', 'scorer', 'DISMAX')
510    env.assertEqual([3L, 'doc3', 'doc2', 'doc1'], res)
511
512def testExplain(env):
513
514    r = env
515    env.assertOk(r.execute_command(
516        'ft.create', 'idx', 'schema', 'foo', 'text', 'bar', 'numeric', 'sortable'))
517    q = '(hello world) "what what" hello|world @bar:[10 100]|@bar:[200 300]'
518    res = r.execute_command('ft.explain', 'idx', q)
519    # print res.replace('\n', '\\n')
520    # expected = """INTERSECT {\n  UNION {\n    hello\n    +hello(expanded)\n  }\n  UNION {\n    world\n    +world(expanded)\n  }\n  EXACT {\n    what\n    what\n  }\n  UNION {\n    UNION {\n      hello\n      +hello(expanded)\n    }\n    UNION {\n      world\n      +world(expanded)\n    }\n  }\n  UNION {\n    NUMERIC {10.000000 <= @bar <= 100.000000}\n    NUMERIC {200.000000 <= @bar <= 300.000000}\n  }\n}\n"""
521    # expected = """INTERSECT {\n  UNION {\n    hello\n    <HL(expanded)\n    +hello(expanded)\n  }\n  UNION {\n    world\n    <ARLT(expanded)\n    +world(expanded)\n  }\n  EXACT {\n    what\n    what\n  }\n  UNION {\n    UNION {\n      hello\n      <HL(expanded)\n      +hello(expanded)\n    }\n    UNION {\n      world\n      <ARLT(expanded)\n      +world(expanded)\n    }\n  }\n  UNION {\n    NUMERIC {10.000000 <= @bar <= 100.000000}\n    NUMERIC {200.000000 <= @bar <= 300.000000}\n  }\n}\n"""
522    expected = """INTERSECT {\n  UNION {\n    hello\n    +hello(expanded)\n  }\n  UNION {\n    world\n    +world(expanded)\n  }\n  EXACT {\n    what\n    what\n  }\n  UNION {\n    UNION {\n      hello\n      +hello(expanded)\n    }\n    UNION {\n      world\n      +world(expanded)\n    }\n  }\n  UNION {\n    NUMERIC {10.000000 <= @bar <= 100.000000}\n    NUMERIC {200.000000 <= @bar <= 300.000000}\n  }\n}\n"""
523    env.assertEqual(res, expected)
524
525
526    # expected = ['INTERSECT {', '  UNION {', '    hello', '    <HL(expanded)', '    +hello(expanded)', '  }', '  UNION {', '    world', '    <ARLT(expanded)', '    +world(expanded)', '  }', '  EXACT {', '    what', '    what', '  }', '  UNION {', '    UNION {', '      hello', '      <HL(expanded)', '      +hello(expanded)', '    }', '    UNION {', '      world', '      <ARLT(expanded)', '      +world(expanded)', '    }', '  }', '  UNION {', '    NUMERIC {10.000000 <= @bar <= 100.000000}', '    NUMERIC {200.000000 <= @bar <= 300.000000}', '  }', '}', '']
527    if env.is_cluster():
528        raise unittest.SkipTest()
529    res = env.cmd('ft.explainCli', 'idx', q)
530    expected = ['INTERSECT {', '  UNION {', '    hello', '    +hello(expanded)', '  }', '  UNION {', '    world', '    +world(expanded)', '  }', '  EXACT {', '    what', '    what', '  }', '  UNION {', '    UNION {', '      hello', '      +hello(expanded)', '    }', '    UNION {', '      world', '      +world(expanded)', '    }', '  }', '  UNION {', '    NUMERIC {10.000000 <= @bar <= 100.000000}', '    NUMERIC {200.000000 <= @bar <= 300.000000}', '  }', '}', '']
531    env.assertEqual(expected, res)
532
533def testNoIndex(env):
534    r = env
535    env.assertOk(r.execute_command(
536        'ft.create', 'idx', 'schema',
537        'foo', 'text',
538        'num', 'numeric', 'sortable', 'noindex',
539        'extra', 'text', 'noindex', 'sortable'))
540
541    if not env.isCluster():
542        # to specific check on cluster, todo : change it to be generic enough
543        res = env.cmd('ft.info', 'idx')
544        env.assertEqual(res[5][1][4], 'NOINDEX')
545        env.assertEqual(res[5][2][6], 'NOINDEX')
546
547    env.assertOk(r.execute_command('ft.add', 'idx', 'doc1', '0.1', 'fields',
548                                    'foo', 'hello world', 'num', 1, 'extra', 'hello lorem ipsum'))
549    res = r.execute_command(
550        'ft.search', 'idx', 'hello world', 'nocontent')
551    env.assertListEqual([1, 'doc1'], res)
552    res = r.execute_command(
553        'ft.search', 'idx', 'lorem ipsum', 'nocontent')
554    env.assertListEqual([0], res)
555    res = r.execute_command(
556        'ft.search', 'idx', '@extra:hello', 'nocontent')
557    env.assertListEqual([0], res)
558    res = r.execute_command(
559        'ft.search', 'idx', '@num:[1 1]', 'nocontent')
560    env.assertListEqual([0], res)
561
562def testPartial(env):
563    r = env
564    env.assertOk(r.execute_command(
565        'ft.create', 'idx', 'schema',
566        'foo', 'text',
567        'num', 'numeric', 'sortable', 'noindex',
568        'extra', 'text', 'noindex'))
569    # print r.execute_command('ft.info', 'idx')
570
571    env.assertOk(r.execute_command('ft.add', 'idx', 'doc1', '0.1', 'fields',
572                                    'foo', 'hello world', 'num', 1, 'extra', 'lorem ipsum'))
573    env.assertOk(r.execute_command('ft.add', 'idx', 'doc2', '0.1', 'fields',
574                                    'foo', 'hello world', 'num', 2, 'extra', 'abba'))
575    res = r.execute_command('ft.search', 'idx', 'hello world',
576                            'sortby', 'num', 'asc', 'nocontent', 'withsortkeys')
577    env.assertListEqual([2L, 'doc1', '#1', 'doc2', '#2'], res)
578    res = r.execute_command('ft.search', 'idx', 'hello world',
579                            'sortby', 'num', 'desc', 'nocontent', 'withsortkeys')
580    env.assertListEqual([2L, 'doc2', '#2', 'doc1', '#1'], res)
581
582    # Updating non indexed fields doesn't affect search results
583    env.assertOk(r.execute_command('ft.add', 'idx', 'doc1', '0.1', 'replace', 'partial',
584                                    'fields', 'num', 3, 'extra', 'jorem gipsum'))
585    env.expect('ft.add', 'idx', 'doc12', '0.1', 'replace', 'partial',
586                                    'fields', 'num1', 'redis').equal('OK')
587
588    res = r.execute_command(
589        'ft.search', 'idx', 'hello world', 'sortby', 'num', 'desc',)
590    assertResultsEqual(env, [2L, 'doc1', ['foo', 'hello world', 'num', '3', 'extra', 'jorem gipsum'],
591        'doc2', ['foo', 'hello world', 'num', '2', 'extra', 'abba']], res)
592    res = r.execute_command(
593        'ft.search', 'idx', 'hello', 'nocontent', 'withscores')
594    # Updating only indexed field affects search results
595    env.assertOk(r.execute_command('ft.add', 'idx', 'doc1', '0.1', 'replace', 'partial',
596                                    'fields', 'foo', 'wat wet'))
597    res = r.execute_command(
598        'ft.search', 'idx', 'hello world', 'nocontent')
599    env.assertListEqual([1L, 'doc2'], res)
600    res = r.execute_command('ft.search', 'idx', 'wat', 'nocontent')
601    env.assertListEqual([1L, 'doc1'], res)
602
603    # Test updating of score and no fields
604    res = r.execute_command(
605        'ft.search', 'idx', 'wat', 'nocontent', 'withscores')
606    env.assertLess(float(res[2]), 1)
607    # env.assertListEqual([1L, 'doc1'], res)
608    env.assertOk(r.execute_command('ft.add', 'idx',
609                                    'doc1', '1.0', 'replace', 'partial', 'fields'))
610    res = r.execute_command(
611        'ft.search', 'idx', 'wat', 'nocontent', 'withscores')
612    env.assertGreater(float(res[2]), 1)
613
614    # Test updating payloads
615    res = r.execute_command(
616        'ft.search', 'idx', 'wat', 'nocontent', 'withpayloads')
617    env.assertIsNone(res[2])
618    env.assertOk(r.execute_command('ft.add', 'idx', 'doc1', '1.0',
619                                    'replace', 'partial', 'payload', 'foobar', 'fields'))
620    res = r.execute_command(
621        'ft.search', 'idx', 'wat', 'nocontent', 'withpayloads')
622    env.assertEqual('foobar', res[2])
623
624def testPaging(env):
625    r = env
626    env.assertOk(r.execute_command(
627        'ft.create', 'idx', 'schema', 'foo', 'text', 'bar', 'numeric', 'sortable'))
628    N = 100
629    for i in range(N):
630        env.assertOk(r.execute_command('ft.add', 'idx', '%d' % i, 1, 'fields',
631                                        'foo', 'hello', 'bar', i))
632
633    chunk = 7
634    offset = 0
635    while True:
636
637        res = r.execute_command(
638            'ft.search', 'idx', 'hello', 'nocontent', 'sortby', 'bar', 'desc', 'limit', offset, chunk)
639        env.assertEqual(res[0], N)
640
641        if offset + chunk > N:
642            env.assertTrue(len(res) - 1 <= chunk)
643            break
644        env.assertEqual(len(res), chunk + 1)
645        for n, id in enumerate(res[1:]):
646            env.assertEqual(int(id), N - 1 - (offset + n))
647        offset += chunk
648        chunk = random.randrange(1, 10)
649    res = r.execute_command(
650        'ft.search', 'idx', 'hello', 'nocontent', 'sortby', 'bar', 'asc', 'limit', N, 10)
651    env.assertEqual(res[0], N)
652    env.assertEqual(len(res), 1)
653
654    with env.assertResponseError():
655        r.execute_command(
656            'ft.search', 'idx', 'hello', 'nocontent', 'limit', 0, -1)
657    with env.assertResponseError():
658        r.execute_command(
659            'ft.search', 'idx', 'hello', 'nocontent', 'limit', -1, 10)
660    with env.assertResponseError():
661        r.execute_command(
662            'ft.search', 'idx', 'hello', 'nocontent', 'limit', 0, 2000000)
663
664def testPrefix(env):
665    r = env
666    env.assertOk(r.execute_command(
667        'ft.create', 'idx', 'schema', 'foo', 'text'))
668    N = 100
669    for i in range(N):
670        env.assertOk(r.execute_command('ft.add', 'idx', 'doc%d' % i, 1.0, 'fields',
671                                        'foo', 'constant term%d' % (random.randrange(0, 5))))
672    for _ in r.retry_with_rdb_reload():
673        res = r.execute_command(
674            'ft.search', 'idx', 'constant term', 'nocontent')
675        env.assertEqual([0], res)
676        res = r.execute_command(
677            'ft.search', 'idx', 'constant term*', 'nocontent')
678        env.assertEqual(N, res[0])
679        res = r.execute_command(
680            'ft.search', 'idx', 'const* term*', 'nocontent')
681        env.assertEqual(N, res[0])
682        res = r.execute_command(
683            'ft.search', 'idx', 'constant term1*', 'nocontent')
684        env.assertGreater(res[0], 2)
685        res = r.execute_command(
686            'ft.search', 'idx', 'const* -term*', 'nocontent')
687        env.assertEqual([0], res)
688        res = r.execute_command(
689            'ft.search', 'idx', 'constant term9*', 'nocontent')
690        env.assertEqual([0], res)
691
692def testSortBy(env):
693    r = env
694    env.assertOk(r.execute_command(
695        'ft.create', 'idx', 'schema', 'foo', 'text', 'sortable', 'bar', 'numeric', 'sortable'))
696    N = 100
697    for i in range(N):
698        env.assertOk(r.execute_command('ft.add', 'idx', 'doc%d' % i, 1.0, 'fields',
699                                        'foo', 'hello%03d world' % i, 'bar', 100 - i))
700    for _ in r.retry_with_rdb_reload():
701
702        res = r.execute_command(
703            'ft.search', 'idx', 'world', 'nocontent', 'sortby', 'foo')
704        env.assertEqual([100L, 'doc0', 'doc1', 'doc2', 'doc3',
705                          'doc4', 'doc5', 'doc6', 'doc7', 'doc8', 'doc9'], res)
706        res = r.execute_command(
707            'ft.search', 'idx', 'world', 'nocontent', 'sortby', 'foo', 'desc')
708        env.assertEqual([100L, 'doc99', 'doc98', 'doc97', 'doc96',
709                          'doc95', 'doc94', 'doc93', 'doc92', 'doc91', 'doc90'], res)
710        res = r.execute_command(
711            'ft.search', 'idx', 'world', 'nocontent', 'sortby', 'bar', 'desc')
712        env.assertEqual([100L, 'doc0', 'doc1', 'doc2', 'doc3',
713                          'doc4', 'doc5', 'doc6', 'doc7', 'doc8', 'doc9'], res)
714        res = r.execute_command(
715            'ft.search', 'idx', 'world', 'nocontent', 'sortby', 'bar', 'asc')
716        env.assertEqual([100L, 'doc99', 'doc98', 'doc97', 'doc96',
717                          'doc95', 'doc94', 'doc93', 'doc92', 'doc91', 'doc90'], res)
718
719        res = r.execute_command('ft.search', 'idx', 'world', 'nocontent',
720                                'sortby', 'bar', 'desc', 'withscores', 'limit', '2', '5')
721        env.assertEqual(
722            [100L, 'doc2', '0', 'doc3', '0', 'doc4', '0', 'doc5', '0', 'doc6', '0'], res)
723
724        res = r.execute_command('ft.search', 'idx', 'world', 'nocontent',
725                                'sortby', 'bar', 'desc', 'withsortkeys', 'limit', 0, 5)
726        env.assertListEqual(
727            [100L, 'doc0', '#100', 'doc1', '#99', 'doc2', '#98', 'doc3', '#97', 'doc4', '#96'], res)
728        res = r.execute_command('ft.search', 'idx', 'world', 'nocontent',
729                                'sortby', 'foo', 'desc', 'withsortkeys', 'limit', 0, 5)
730        env.assertListEqual([100L, 'doc99', '$hello099 world', 'doc98', '$hello098 world', 'doc97', '$hello097 world', 'doc96',
731                              '$hello096 world', 'doc95', '$hello095 world'], res)
732
733def testNot(env):
734    r = env
735    env.assertOk(r.execute_command(
736        'ft.create', 'idx', 'schema', 'foo', 'text'))
737    N = 10
738    for i in range(N):
739        env.assertOk(r.execute_command('ft.add', 'idx', 'doc%d' % i, 1.0, 'fields',
740                                        'foo', 'constant term%d' % (random.randrange(0, 5))))
741
742    for i in range(5):
743        inclusive = r.execute_command(
744            'ft.search', 'idx', 'constant term%d' % i, 'nocontent', 'limit', 0, N)
745
746        exclusive = r.execute_command(
747            'ft.search', 'idx', 'constant -term%d' % i, 'nocontent', 'limit', 0, N)
748        exclusive2 = r.execute_command(
749            'ft.search', 'idx', '-(term%d)' % i, 'nocontent', 'limit', 0, N)
750        exclusive3 = r.execute_command(
751            'ft.search', 'idx', '(-term%d) (constant)' % i, 'nocontent', 'limit', 0, N)
752
753        env.assertNotEqual(inclusive[0], N)
754        env.assertEqual(inclusive[0] + exclusive[0], N)
755        env.assertEqual(exclusive3[0], exclusive2[0])
756        env.assertEqual(exclusive3[0], exclusive[0])
757
758        s1, s2, s3, s4 = set(inclusive[1:]), set(
759            exclusive[1:]), set(exclusive2[1:]), set(exclusive3[1:])
760        env.assertTrue(s1.difference(s2) == s1)
761        env.assertTrue(s1.difference(s3) == s1)
762        env.assertTrue(s1.difference(s4) == s1)
763        env.assertTrue(s2 == s3)
764        env.assertTrue(s2 == s4)
765        env.assertTrue(s2.intersection(s1) == set())
766        env.assertTrue(s3.intersection(s1) == set())
767        env.assertTrue(s4.intersection(s1) == set())
768
769    # NOT on a non existing term
770    env.assertEqual(r.execute_command(
771        'ft.search', 'idx', 'constant -dasdfasdf', 'nocontent')[0], N)
772    # not on env term
773    env.assertEqual(r.execute_command(
774        'ft.search', 'idx', 'constant -constant', 'nocontent'), [0])
775
776    env.assertEqual(r.execute_command(
777        'ft.search', 'idx', 'constant -(term0|term1|term2|term3|term4|nothing)', 'nocontent'), [0])
778    # env.assertEqual(r.execute_command('ft.search', 'idx', 'constant -(term1 term2)', 'nocontent')[0], N)
779
780def testNestedIntersection(env):
781    r = env
782    env.assertOk(r.execute_command(
783        'ft.create', 'idx', 'schema', 'a', 'text', 'b', 'text', 'c', 'text', 'd', 'text'))
784    for i in range(20):
785        env.assertOk(r.execute_command('ft.add', 'idx', 'doc%d' % i, 1.0, 'fields',
786                                        'a', 'foo', 'b', 'bar', 'c', 'baz', 'd', 'gaz'))
787    res = [
788        r.execute_command('ft.search', 'idx',
789                          'foo bar baz gaz', 'nocontent'),
790        r.execute_command('ft.search', 'idx',
791                          '@a:foo @b:bar @c:baz @d:gaz', 'nocontent'),
792        r.execute_command('ft.search', 'idx',
793                          '@b:bar @a:foo @c:baz @d:gaz', 'nocontent'),
794        r.execute_command('ft.search', 'idx',
795                          '@c:baz @b:bar @a:foo @d:gaz', 'nocontent'),
796        r.execute_command('ft.search', 'idx',
797                          '@d:gaz @c:baz @b:bar @a:foo', 'nocontent'),
798        r.execute_command(
799            'ft.search', 'idx', '@a:foo (@b:bar (@c:baz @d:gaz))', 'nocontent'),
800        r.execute_command(
801            'ft.search', 'idx', '@c:baz (@a:foo (@b:bar (@c:baz @d:gaz)))', 'nocontent'),
802        r.execute_command(
803            'ft.search', 'idx', '@b:bar (@a:foo (@c:baz @d:gaz))', 'nocontent'),
804        r.execute_command(
805            'ft.search', 'idx', '@d:gaz (@a:foo (@c:baz @b:bar))', 'nocontent'),
806        r.execute_command('ft.search', 'idx',
807                          'foo (bar baz gaz)', 'nocontent'),
808        r.execute_command('ft.search', 'idx',
809                          'foo (bar (baz gaz))', 'nocontent'),
810        r.execute_command('ft.search', 'idx',
811                          'foo (bar (foo bar) (foo bar))', 'nocontent'),
812        r.execute_command('ft.search', 'idx',
813                          'foo (foo (bar baz (gaz)))', 'nocontent'),
814        r.execute_command('ft.search', 'idx', 'foo (foo (bar (baz (gaz (foo bar (gaz))))))', 'nocontent')]
815
816    for i, r in enumerate(res):
817        # print i, res[0], r
818        env.assertListEqual(res[0], r)
819
820def testInKeys(env):
821    r = env
822    env.assertOk(r.execute_command(
823        'ft.create', 'idx', 'schema', 'foo', 'text'))
824
825    for i in range(200):
826        env.assertOk(r.execute_command('ft.add', 'idx', 'doc%d' % i, 1.0, 'fields',
827                                        'foo', 'hello world'))
828
829    for _ in r.retry_with_rdb_reload():
830
831        for keys in (
832            ['doc%d' % i for i in range(10)], ['doc%d' % i for i in range(0, 30, 2)], [
833                'doc%d' % i for i in range(99, 0, -5)]
834        ):
835            res = r.execute_command(
836                'ft.search', 'idx', 'hello world', 'NOCONTENT', 'LIMIT', 0, 100, 'INKEYS', len(keys), *keys)
837            env.assertEqual(len(keys), res[0])
838            env.assertTrue(all((k in res for k in keys)))
839
840        env.assertEqual(0, r.execute_command(
841            'ft.search', 'idx', 'hello world', 'NOCONTENT', 'LIMIT', 0, 100, 'INKEYS', 3, 'foo', 'bar', 'baz')[0])
842
843    with env.assertResponseError():
844        env.cmd('ft.search', 'idx', 'hello', 'INKEYS', 99)
845    with env.assertResponseError():
846        env.cmd('ft.search', 'idx', 'hello', 'INKEYS', -1)
847    with env.assertResponseError():
848        env.cmd('ft.search', 'idx', 'hello', 'inkeys', 4, 'foo')
849
850def testSlopInOrder(env):
851    r = env
852    env.assertOk(r.execute_command(
853        'ft.create', 'idx', 'schema', 'title', 'text'))
854    env.assertOk(r.execute_command('ft.add', 'idx', 'doc1', 1, 'fields',
855                                    'title', 't1 t2'))
856    env.assertOk(r.execute_command('ft.add', 'idx', 'doc2', 1, 'fields',
857                                    'title', 't1 t3 t2'))
858    env.assertOk(r.execute_command('ft.add', 'idx', 'doc3', 1, 'fields',
859                                    'title', 't1 t3 t4 t2'))
860    env.assertOk(r.execute_command('ft.add', 'idx', 'doc4', 1, 'fields',
861                                    'title', 't1 t3 t4 t5 t2'))
862
863    res = r.execute_command(
864        'ft.search', 'idx', 't1|t4 t3|t2', 'slop', '0', 'inorder', 'nocontent')
865    env.assertEqual({'doc3', 'doc4', 'doc2', 'doc1'}, set(res[1:]))
866    res = r.execute_command(
867        'ft.search', 'idx', 't2 t1', 'slop', '0', 'nocontent')
868    env.assertEqual(1, res[0])
869    env.assertEqual('doc1', res[1])
870    env.assertEqual(0, r.execute_command(
871        'ft.search', 'idx', 't2 t1', 'slop', '0', 'inorder')[0])
872    env.assertEqual(1, r.execute_command(
873        'ft.search', 'idx', 't1 t2', 'slop', '0', 'inorder')[0])
874
875    env.assertEqual(2, r.execute_command(
876        'ft.search', 'idx', 't1 t2', 'slop', '1', 'inorder')[0])
877    env.assertEqual(3, r.execute_command(
878        'ft.search', 'idx', 't1 t2', 'slop', '2', 'inorder')[0])
879    env.assertEqual(4, r.execute_command(
880        'ft.search', 'idx', 't1 t2', 'slop', '3', 'inorder')[0])
881    env.assertEqual(4, r.execute_command(
882        'ft.search', 'idx', 't1 t2', 'inorder')[0])
883    env.assertEqual(0, r.execute_command(
884        'ft.search', 'idx', 't t1', 'inorder')[0])
885    env.assertEqual(2, r.execute_command(
886        'ft.search', 'idx', 't1 t2 t3 t4')[0])
887    env.assertEqual(0, r.execute_command(
888        'ft.search', 'idx', 't1 t2 t3 t4', 'inorder')[0])
889
890def testExact(env):
891    r = env
892    env.assertOk(r.execute_command(
893        'ft.create', 'idx', 'schema', 'title', 'text', 'weight', 10.0, 'body', 'text'))
894    env.assertOk(r.execute_command('ft.add', 'idx', 'doc1', 0.5, 'fields',
895                                    'title', 'hello world',
896                                    'body', 'lorem ist ipsum'))
897    env.assertOk(r.execute_command('ft.add', 'idx', 'doc2', 1.0, 'fields',
898                                    'title', 'hello another world',
899                                    'body', 'lorem ist ipsum lorem lorem'))
900
901    res = r.execute_command(
902        'ft.search', 'idx', '"hello world"', 'verbatim')
903    env.assertEqual(3, len(res))
904    env.assertEqual(1, res[0])
905    env.assertEqual("doc1", res[1])
906
907    res = r.execute_command(
908        'ft.search', 'idx', "hello \"another world\"", 'verbatim')
909    env.assertEqual(3, len(res))
910    env.assertEqual(1, res[0])
911    env.assertEqual("doc2", res[1])
912
913
914def testGeoErrors(env):
915    env.expect('flushall')
916    env.expect('ft.create idx schema name text location geo').equal('OK')
917    env.expect('ft.add idx hotel 1.0 fields name hill location -0.1757,51.5156').equal('OK')
918    env.expect('ft.search idx hilton geofilter location -0.1757 51.5156 1 km').equal([0L])
919
920    # Insert error
921    env.expect('ft.add', 'idx', 'hotel1', 1, 'fields', 'name', '_hotel1', 'location', '1, 1').error()   \
922            .contains('Could not index geo value')
923
924    # Query errors
925    env.expect('ft.search idx hilton geofilter location lon 51.5156 1 km').error()   \
926            .contains('Bad arguments for <lon>: Could not convert argument to expected type')
927    env.expect('ft.search idx hilton geofilter location 51.5156 lat 1 km').error()   \
928            .contains('Bad arguments for <lat>: Could not convert argument to expected type')
929    env.expect('ft.search idx hilton geofilter location -0.1757 51.5156 radius km').error()   \
930            .contains('Bad arguments for <radius>: Could not convert argument to expected type')
931    env.expect('ft.search idx hilton geofilter location -0.1757 51.5156 1 fake').error()   \
932            .contains('Unknown distance unit fake')
933    env.expect('ft.search idx hilton geofilter location -0.1757 51.5156 1').error()   \
934            .contains('GEOFILTER requires 5 arguments')
935
936    if not env.isCluster():
937        env.expect('flushall')
938        env.expect('set geo:idx/location foo').equal('OK')
939        env.expect('ft.create idx schema name text location geo').equal('OK')
940        env.expect('ft.add idx hotel 1.0 fields name hill location -0.1757,51.5156').error() \
941                .contains('Could not index geo value')
942
943def testGeo(env):
944    r = env
945    gsearch = lambda query, lon, lat, dist, unit='km': r.execute_command(
946        'ft.search', 'idx', query, 'geofilter', 'location', lon, lat, dist, unit)
947
948    gsearch_inline = lambda query, lon, lat, dist, unit='km': r.execute_command(
949        'ft.search', 'idx', '{} @location:[{} {} {} {}]'.format(query,  lon, lat, dist, unit))
950
951    env.assertOk(r.execute_command('ft.create', 'idx',
952                                    'schema', 'name', 'text', 'location', 'geo'))
953
954    for i, hotel in enumerate(hotels):
955        env.assertOk(r.execute_command('ft.add', 'idx', 'hotel{}'.format(i), 1.0, 'fields', 'name',
956                                        hotel[0], 'location', '{},{}'.format(hotel[2], hotel[1])))
957
958    for _ in r.retry_with_rdb_reload():
959        res = r.execute_command('ft.search', 'idx', 'hilton')
960        env.assertEqual(len(hotels), res[0])
961
962        res = gsearch('hilton', "-0.1757", "51.5156", '1')
963        print res
964        env.assertEqual(3, res[0])
965        env.assertEqual('hotel2', res[5])
966        env.assertEqual('hotel21', res[3])
967        env.assertEqual('hotel79', res[1])
968        res2 = gsearch_inline('hilton', "-0.1757", "51.5156", '1')
969        env.assertListEqual(res, res2)
970
971        res = gsearch('hilton', "-0.1757", "51.5156", '10')
972        env.assertEqual(14, res[0])
973        env.assertEqual('hotel93', res[1])
974        env.assertEqual('hotel92', res[3])
975        env.assertEqual('hotel79', res[5])
976
977        res2 = gsearch('hilton', "-0.1757", "51.5156", '10000', 'm')
978        env.assertListEqual(res, res2)
979        res2 = gsearch_inline('hilton', "-0.1757", "51.5156", '10')
980        env.assertListEqual(res, res2)
981
982        res = gsearch('heathrow', -0.44155, 51.45865, '10', 'm')
983        env.assertEqual(1, res[0])
984        env.assertEqual('hotel94', res[1])
985        res2 = gsearch_inline(
986            'heathrow', -0.44155, 51.45865, '10', 'm')
987        env.assertListEqual(res, res2)
988
989        res = gsearch('heathrow', -0.44155, 51.45865, '10', 'km')
990        env.assertEqual(5, res[0])
991        env.assertIn('hotel94', res)
992        res2 = gsearch_inline(
993            'heathrow', -0.44155, 51.45865, '10', 'km')
994        env.assertListEqual(res, res2)
995
996        res = gsearch('heathrow', -0.44155, 51.45865, '5', 'km')
997        env.assertEqual(3, res[0])
998        env.assertIn('hotel94', res)
999        res2 = gsearch_inline(
1000            'heathrow', -0.44155, 51.45865, '5', 'km')
1001        env.assertListEqual(res, res2)
1002
1003def testTagErrors(env):
1004    env.expect("ft.create", "test", "SCHEMA",  "tags", "TAG").equal('OK')
1005    env.expect("ft.add", "test", "1", "1", "FIELDS", "tags", "alberta").equal('OK')
1006    env.expect("ft.add", "test", "2", "1", "FIELDS", "tags", "ontario. alberta").equal('OK')
1007
1008def testGeoDeletion(env):
1009    if env.is_cluster():
1010        raise unittest.SkipTest()
1011        # Can't properly test if deleted on cluster
1012
1013    env.cmd('ft.create', 'idx', 'schema',
1014            'g1', 'geo', 'g2', 'geo', 't1', 'text')
1015    env.cmd('ft.add', 'idx', 'doc1', 1.0, 'fields',
1016            'g1', "-0.1757,51.5156",
1017            'g2', "-0.1757,51.5156",
1018            't1', "hello")
1019    env.cmd('ft.add', 'idx', 'doc2', 1.0, 'fields',
1020            'g1', "-0.1757,51.5156",
1021            'g2', "-0.1757,51.5156",
1022            't1', "hello")
1023
1024    # keys are: "geo:idx/g1" and "geo:idx/g2"
1025    env.assertEqual(2, env.cmd('zcard', 'geo:idx/g1'))
1026    env.assertEqual(2, env.cmd('zcard', 'geo:idx/g2'))
1027
1028    # Remove the first doc
1029    env.cmd('ft.del', 'idx', 'doc1')
1030    env.assertEqual(1, env.cmd('zcard', 'geo:idx/g1'))
1031    env.assertEqual(1, env.cmd('zcard', 'geo:idx/g2'))
1032
1033    # Replace the other one:
1034    env.cmd('ft.add', 'idx', 'doc2', 1.0,
1035            'replace', 'fields',
1036            't1', 'just text here')
1037    env.assertEqual(0, env.cmd('zcard', 'geo:idx/g1'))
1038    env.assertEqual(0, env.cmd('zcard', 'geo:idx/g2'))
1039
1040def testAddHash(env):
1041    if env.is_cluster():
1042        raise unittest.SkipTest()
1043
1044    r = env
1045    env.assertOk(r.execute_command('ft.create', 'idx', 'schema',
1046                                    'title', 'text', 'weight', 10.0, 'body', 'text', 'price', 'numeric'))
1047
1048    env.assertTrue(
1049        r.hmset('doc1', {"title": "hello world", "body": "lorem ipsum", "price": 2}))
1050    env.assertTrue(
1051        r.hmset('doc2', {"title": "hello werld", "body": "lorem ipsum", "price": 5}))
1052
1053    env.assertOk(r.execute_command('ft.addhash', 'idx', 'doc1', 1.0))
1054    env.assertOk(r.execute_command('ft.addhash', 'idx', 'doc2', 1.0))
1055    env.expect('ft.addhash', 'idx', 'doc3', 1.0, 1.0).error().contains('Unknown keyword: `1.0`')
1056
1057    res = r.execute_command('ft.search', 'idx', "hello", "nocontent")
1058    env.assertEqual(3, len(res))
1059    env.assertEqual(2, res[0])
1060    env.assertEqual("doc1", res[2])
1061    env.assertEqual("doc2", res[1])
1062
1063    res = r.execute_command(
1064        'ft.search', 'idx',
1065        "hello",
1066        "filter", "price", "0", "3"
1067        )
1068    env.assertEqual(3, len(res))
1069    env.assertEqual(1, res[0])
1070    env.assertEqual("doc1", res[1])
1071    env.assertListEqual(
1072        ['body', 'lorem ipsum', 'price', '2', 'title', 'hello world'], res[2])
1073
1074    res = r.execute_command(
1075        'ft.search', 'idx', "hello werld", "nocontent")
1076    env.assertEqual(2, len(res))
1077    env.assertEqual(1, res[0])
1078    env.assertEqual("doc2", res[1])
1079
1080def testSafeAddHash(env):
1081    if env.is_cluster():
1082        raise unittest.SkipTest()
1083
1084    r = env
1085    env.assertOk(r.execute_command('ft.create', 'idx', 'schema',
1086                                    'title', 'text', 'weight', 10.0, 'body', 'text', 'price', 'numeric'))
1087
1088    env.assertTrue(
1089        r.hmset('doc1', {"title": "hello world", "body": "lorem ipsum", "price": 2}))
1090    env.assertTrue(
1091        r.hmset('doc2', {"title": "hello werld", "body": "lorem ipsum", "price": 5}))
1092
1093    env.expect('ft.safeaddhash idx doc1 1.0').equal('OK')
1094    env.expect('ft.safeaddhash idx doc2 1.0').equal('OK')
1095    env.expect('ft.safeaddhash idx').error().contains("wrong number of arguments for 'ft.safeaddhash' command")
1096    env.expect('ft.safeaddhash idx doc3 2.0').error().contains('Score must be between 0 and 1')
1097    env.expect('ft.safeaddhash idx doc3 -2.0').error().contains('Score must be between 0 and 1')
1098    env.expect('ft.safeaddhash idx doc3 1.0 1.0').error().contains('Unknown keyword: `1.0`')
1099    env.expect('ft.safeaddhash idx doc3 not_a_number').error().contains('Could not parse document score')
1100    env.expect('ft.safeaddhash idx doc3 1.0 LANGUAGE RediSearch').error().contains('Unknown language: `RediSearch`')
1101    env.expect('ft.safeaddhash idx doc3 1.0 LANGUAGE RediSearch not_an_arg').error().contains("Unknown keyword: `not_an_arg`")
1102    #env.expect('ft.safeaddhash', 'idx', 'doc3', '1.0', 'LANGUAGE', 'RediSearch, ""').error().contains("Error parsing arguments for `%s`: %s")
1103    env.expect('ft.safeaddhash not_idx doc3 1.0').error().contains('Unknown Index name')
1104    res = r.execute_command('ft.search', 'idx', "hello", "nocontent")
1105    env.assertEqual(3, len(res))
1106    env.assertEqual(2, res[0])
1107    env.assertEqual("doc1", res[2])
1108    env.assertEqual("doc2", res[1])
1109
1110    res = r.execute_command(
1111        'ft.search', 'idx',
1112        "hello",
1113        "filter", "price", "0", "3"
1114        )
1115    env.assertEqual(3, len(res))
1116    env.assertEqual(1, res[0])
1117    env.assertEqual("doc1", res[1])
1118    env.assertListEqual(
1119        ['body', 'lorem ipsum', 'price', '2', 'title', 'hello world'], res[2])
1120
1121    res = r.execute_command(
1122        'ft.search', 'idx', "hello werld", "nocontent")
1123    env.assertEqual(2, len(res))
1124    env.assertEqual(1, res[0])
1125    env.assertEqual("doc2", res[1])
1126
1127def testInfields(env):
1128    r = env
1129    env.assertOk(r.execute_command(
1130        'ft.create', 'idx', 'schema', 'title', 'text', 'weight', 10.0, 'body', 'text', 'weight', 1.0))
1131    env.assertOk(r.execute_command('ft.add', 'idx', 'doc1', 0.5, 'fields',
1132                                    'title', 'hello world',
1133                                    'body', 'lorem ipsum'))
1134
1135    env.assertOk(r.execute_command('ft.add', 'idx', 'doc2', 1.0, 'fields',
1136                                    'title', 'hello world lorem ipsum',
1137                                    'body', 'hello world'))
1138
1139    res = r.execute_command(
1140        'ft.search', 'idx', 'hello world', 'verbatim', "infields", 1, "title", "nocontent")
1141    env.assertEqual(3, len(res))
1142    env.assertEqual(2, res[0])
1143    env.assertEqual("doc2", res[1])
1144    env.assertEqual("doc1", res[2])
1145
1146    res = r.execute_command(
1147        'ft.search', 'idx', 'hello world', 'verbatim', "infields", 1, "body", "nocontent")
1148    env.assertEqual(2, len(res))
1149    env.assertEqual(1, res[0])
1150    env.assertEqual("doc2", res[1])
1151
1152    res = r.execute_command(
1153        'ft.search', 'idx', 'hello', 'verbatim', "infields", 1, "body", "nocontent")
1154    env.assertEqual(2, len(res))
1155    env.assertEqual(1, res[0])
1156    env.assertEqual("doc2", res[1])
1157
1158    res = r.execute_command(
1159        'ft.search', 'idx',  '\"hello world\"', 'verbatim', "infields", 1, "body", "nocontent")
1160
1161    env.assertEqual(2, len(res))
1162    env.assertEqual(1, res[0])
1163    env.assertEqual("doc2", res[1])
1164
1165    res = r.execute_command(
1166        'ft.search', 'idx', '\"lorem ipsum\"', 'verbatim', "infields", 1, "body", "nocontent")
1167    env.assertEqual(2, len(res))
1168    env.assertEqual(1, res[0])
1169    env.assertEqual("doc1", res[1])
1170
1171    res = r.execute_command(
1172        'ft.search', 'idx', 'lorem ipsum', "infields", 2, "body", "title", "nocontent")
1173    env.assertEqual(3, len(res))
1174    env.assertEqual(2, res[0])
1175    env.assertEqual("doc2", res[1])
1176    env.assertEqual("doc1", res[2])
1177
1178def testScorerSelection(env):
1179    r = env
1180    env.assertOk(r.execute_command(
1181        'ft.create', 'idx', 'schema', 'title', 'text', 'body', 'text'))
1182
1183    # this is the default scorer
1184    res = r.execute_command(
1185        'ft.search', 'idx', 'foo', 'scorer', 'TFIDF')
1186    env.assertEqual(res, [0])
1187    with env.assertResponseError():
1188        res = r.execute_command(
1189            'ft.search', 'idx', 'foo', 'scorer', 'NOSUCHSCORER')
1190
1191def testFieldSelectors(env):
1192    r = env
1193    env.assertOk(r.execute_command(
1194        'ft.create', 'idx', 'schema', 'TiTle', 'text', 'BoDy', 'text', "יוניקוד", 'text', 'field.with,punct', 'text'))
1195    env.assertOk(r.execute_command('ft.add', 'idx', 'doc1', 1, 'fields',
1196                                    'title', 'hello world', 'body', 'foo bar', 'יוניקוד', 'unicode', 'field.with,punct', 'punt'))
1197    env.assertOk(r.execute_command('ft.add', 'idx', 'doc2', 0.5, 'fields',
1198                                    'body', 'hello world', 'title', 'foo bar', 'יוניקוד', 'unicode', 'field.with,punct', 'punt'))
1199
1200    res = r.execute_command(
1201        'ft.search', 'idx', '@title:hello world', 'nocontent')
1202    env.assertEqual(res, [1, 'doc1'])
1203    res = r.execute_command(
1204        'ft.search', 'idx', '@body:hello world', 'nocontent')
1205    env.assertEqual(res, [1, 'doc2'])
1206
1207    res = r.execute_command(
1208        'ft.search', 'idx', '@body:hello @title:world', 'nocontent')
1209    env.assertEqual(res, [0])
1210
1211    res = r.execute_command(
1212        'ft.search', 'idx', '@body:hello world @title:world', 'nocontent')
1213    env.assertEqual(res, [0])
1214    res = r.execute_command(
1215        'ft.search', 'idx', '@BoDy:(hello|foo) @Title:(world|bar)', 'nocontent')
1216    env.assertEqual(res, [2, 'doc1', 'doc2'])
1217
1218    res = r.execute_command(
1219        'ft.search', 'idx', '@body:(hello|foo world|bar)', 'nocontent')
1220    env.assertEqual(res, [2, 'doc1', 'doc2'])
1221
1222    res = r.execute_command(
1223        'ft.search', 'idx', '@body|title:(hello world)', 'nocontent')
1224    env.assertEqual(res, [2, 'doc1', 'doc2'])
1225
1226    res = r.execute_command(
1227        'ft.search', 'idx', '@יוניקוד:(unicode)', 'nocontent')
1228    env.assertEqual(res, [2, 'doc1', 'doc2'])
1229
1230    res = r.execute_command(
1231        'ft.search', 'idx', '@field\\.with\\,punct:(punt)', 'nocontent')
1232    env.assertEqual(res, [2, 'doc1', 'doc2'])
1233
1234def testStemming(env):
1235    r = env
1236    env.assertOk(r.execute_command(
1237        'ft.create', 'idx', 'schema', 'title', 'text'))
1238    env.assertOk(r.execute_command('ft.add', 'idx', 'doc1', 0.5, 'fields',
1239                                    'title', 'hello kitty'))
1240    env.assertOk(r.execute_command('ft.add', 'idx', 'doc2', 1.0, 'fields',
1241                                    'title', 'hello kitties'))
1242
1243    res = r.execute_command(
1244        'ft.search', 'idx', 'hello kitty', "nocontent")
1245    env.assertEqual(3, len(res))
1246    env.assertEqual(2, res[0])
1247
1248    res = r.execute_command(
1249        'ft.search', 'idx', 'hello kitty', "nocontent", "verbatim")
1250    env.assertEqual(2, len(res))
1251    env.assertEqual(1, res[0])
1252
1253    # test for unknown language
1254    with env.assertResponseError():
1255        res = r.execute_command(
1256            'ft.search', 'idx', 'hello kitty', "nocontent", "language", "foofoofian")
1257
1258def testExpander(env):
1259    r = env
1260    env.assertOk(r.execute_command(
1261        'ft.create', 'idx', 'schema', 'title', 'text'))
1262    env.assertOk(r.execute_command('ft.add', 'idx', 'doc1', 0.5, 'fields',
1263                                    'title', 'hello kitty'))
1264    res = r.execute_command(
1265        'ft.search', 'idx', 'kitties',
1266        "nocontent",
1267        "expander", "SBSTEM"
1268        )
1269    env.assertEqual(2, len(res))
1270    env.assertEqual(1, res[0])
1271
1272    res = r.execute_command(
1273        'ft.search', 'idx', 'kitties', "nocontent", "expander", "noexpander")
1274    env.assertEqual(1, len(res))
1275    env.assertEqual(0, res[0])
1276
1277    res = r.execute_command(
1278        'ft.search', 'idx', 'kitti', "nocontent")
1279    env.assertEqual(2, len(res))
1280    env.assertEqual(1, res[0])
1281
1282    res = r.execute_command(
1283        'ft.search', 'idx', 'kitti', "nocontent", 'verbatim')
1284    env.assertEqual(1, len(res))
1285    env.assertEqual(0, res[0])
1286
1287    # Calling a stem directly works even with VERBATIM.
1288    # You need to use the + prefix escaped
1289    res = r.execute_command(
1290        'ft.search', 'idx', '\\+kitti', "nocontent", 'verbatim')
1291    env.assertEqual(2, len(res))
1292    env.assertEqual(1, res[0])
1293
1294def testNumericRange(env):
1295    r = env
1296    env.assertOk(r.execute_command(
1297        'ft.create', 'idx', 'schema', 'title', 'text', 'score', 'numeric', 'price', 'numeric'))
1298
1299    env.expect('ft.search', 'idx', 'hello kitty', 'filter', 'score', 5).error().contains("FILTER requires 3 arguments")
1300    env.expect('ft.search', 'idx', 'hello kitty', 'filter', 'score', 5, 'inf').error().contains("Bad upper range: inf")
1301    env.expect('ft.search', 'idx', 'hello kitty', 'filter', 'score', 'inf', 5).error().contains("Bad lower range: inf")
1302
1303    for i in xrange(100):
1304        env.assertOk(r.execute_command('ft.add', 'idx', 'doc%d' % i, 1, 'fields',
1305                                        'title', 'hello kitty', 'score', i, 'price', 100 + 10 * i))
1306
1307    for _ in r.retry_with_rdb_reload():
1308        res = r.execute_command('ft.search', 'idx', 'hello kitty', "nocontent",
1309                                "filter", "score", 0, 100)
1310
1311        env.assertEqual(11, len(res))
1312        env.assertEqual(100, res[0])
1313
1314        res = r.execute_command('ft.search', 'idx', 'hello kitty', "nocontent",
1315                                "filter", "score", 0, 50)
1316        env.assertEqual(51, res[0])
1317
1318        res = r.execute_command('ft.search', 'idx', 'hello kitty', 'verbatim', "nocontent", "limit", 0, 100,
1319                                "filter", "score", "(0", "(50")
1320
1321        env.assertEqual(49, res[0])
1322        res = r.execute_command('ft.search', 'idx', 'hello kitty', "nocontent",
1323                                "filter", "score", "-inf", "+inf")
1324        env.assertEqual(100, res[0])
1325
1326        # test multi filters
1327        scrange = (19, 90)
1328        prrange = (290, 385)
1329        res = r.execute_command('ft.search', 'idx', 'hello kitty',
1330                                "filter", "score", scrange[
1331                                    0], scrange[1],
1332                                "filter", "price", prrange[0], prrange[1])
1333
1334        # print res
1335        for doc in res[2::2]:
1336
1337            sc = int(doc[doc.index('score') + 1])
1338            pr = int(doc[doc.index('price') + 1])
1339
1340            env.assertTrue(sc >= scrange[0] and sc <= scrange[1])
1341            env.assertGreaterEqual(pr, prrange[0])
1342            env.assertLessEqual(pr, prrange[1])
1343
1344        env.assertEqual(10, res[0])
1345
1346        res = r.execute_command('ft.search', 'idx', 'hello kitty',
1347                                "filter", "score", "19", "90",
1348                                "filter", "price", "90", "185")
1349
1350        env.assertEqual(0, res[0])
1351
1352        # Test numeric ranges as part of query syntax
1353        res = r.execute_command(
1354            'ft.search', 'idx', 'hello kitty @score:[0 100]', "nocontent")
1355
1356        env.assertEqual(11, len(res))
1357        env.assertEqual(100, res[0])
1358
1359        res = r.execute_command(
1360            'ft.search', 'idx', 'hello kitty  @score:[0 50]', "nocontent")
1361        env.assertEqual(51, res[0])
1362        res = r.execute_command(
1363            'ft.search', 'idx', 'hello kitty @score:[(0 (50]', 'verbatim', "nocontent")
1364        env.assertEqual(49, res[0])
1365        res = r.execute_command(
1366            'ft.search', 'idx', '@score:[(0 (50]', 'verbatim', "nocontent")
1367        env.assertEqual(49, res[0])
1368        res = r.execute_command(
1369            'ft.search', 'idx', 'hello kitty -@score:[(0 (50]', 'verbatim', "nocontent")
1370        env.assertEqual(51, res[0])
1371        res = r.execute_command(
1372            'ft.search', 'idx', 'hello kitty @score:[-inf +inf]', "nocontent")
1373        env.assertEqual(100, res[0])
1374
1375def testSuggestions(env):
1376    r = env
1377    env.assertEqual(1, r.execute_command(
1378        'ft.SUGADD', 'ac', 'hello world', 1))
1379    env.assertEqual(1, r.execute_command(
1380        'ft.SUGADD', 'ac', 'hello world', 1, 'INCR'))
1381
1382    res = r.execute_command("FT.SUGGET", "ac", "hello")
1383    env.assertEqual(1, len(res))
1384    env.assertEqual("hello world", res[0])
1385
1386    terms = ["hello werld", "hallo world",
1387             "yellow world", "wazzup", "herp", "derp"]
1388    sz = 2
1389    for term in terms:
1390        env.assertEqual(sz, r.execute_command(
1391            'ft.SUGADD', 'ac', term, sz - 1))
1392        sz += 1
1393
1394    for _ in r.retry_with_rdb_reload():
1395
1396        env.assertEqual(7, r.execute_command('ft.SUGLEN', 'ac'))
1397
1398        # search not fuzzy
1399        env.assertEqual(["hello world", "hello werld"],
1400                         r.execute_command("ft.SUGGET", "ac", "hello"))
1401
1402        # print  r.execute_command("ft.SUGGET", "ac", "hello", "FUZZY", "MAX", "1", "WITHSCORES")
1403        # search fuzzy - shuold yield more results
1404        env.assertEqual(['hello world', 'hello werld', 'yellow world', 'hallo world'],
1405                         r.execute_command("ft.SUGGET", "ac", "hello", "FUZZY"))
1406
1407        # search fuzzy with limit of 1
1408        env.assertEqual(['hello world'],
1409                         r.execute_command("ft.SUGGET", "ac", "hello", "FUZZY", "MAX", "1"))
1410
1411        # scores should return on WITHSCORES
1412        rc = r.execute_command(
1413            "ft.SUGGET", "ac", "hello", "WITHSCORES")
1414        env.assertEqual(4, len(rc))
1415        env.assertTrue(float(rc[1]) > 0)
1416        env.assertTrue(float(rc[3]) > 0)
1417
1418    rc = r.execute_command("ft.SUGDEL", "ac", "hello world")
1419    env.assertEqual(1L, rc)
1420    rc = r.execute_command("ft.SUGDEL", "ac", "world")
1421    env.assertEqual(0L, rc)
1422
1423    rc = r.execute_command("ft.SUGGET", "ac", "hello")
1424    env.assertEqual(['hello werld'], rc)
1425
1426def testSuggestErrors(env):
1427    env.expect('ft.SUGADD ac olah 1').equal(1)
1428    env.expect('ft.SUGADD ac olah 1 INCR').equal(1)
1429    env.expect('ft.SUGADD ac missing').error().contains("wrong number of arguments")
1430    env.expect('ft.SUGADD ac olah not_a_number').error().contains("invalid score")
1431    env.expect('ft.SUGADD ac olah 1 PAYLOAD').error().contains('Invalid payload: Expected an argument, but none provided')
1432    env.expect('ft.SUGADD ac olah 1 REDIS PAYLOAD payload').error().contains('Unknown argument `REDIS`')
1433    env.expect('ft.SUGGET ac olah FUZZ').error().contains("Unrecognized argument: FUZZ")
1434    query = 'verylongquery'
1435    for _ in range(3):
1436        query += query
1437    env.expect('ft.SUGGET ac', query).error().contains("Invalid query")
1438    env.expect('ft.SUGGET ac', query + query).error().contains("Invalid query length")
1439
1440def testSuggestPayload(env):
1441    r = env
1442    env.assertEqual(1, r.execute_command(
1443        'ft.SUGADD', 'ac', 'hello world', 1, 'PAYLOAD', 'foo'))
1444    env.assertEqual(2, r.execute_command(
1445        'ft.SUGADD', 'ac', 'hello werld', 1, 'PAYLOAD', 'bar'))
1446    env.assertEqual(3, r.execute_command(
1447        'ft.SUGADD', 'ac', 'hello nopayload', 1, 'PAYLOAD', ''))
1448    env.assertEqual(4, r.execute_command(
1449        'ft.SUGADD', 'ac', 'hello nopayload2', 1))
1450
1451    res = r.execute_command("FT.SUGGET", "ac", "hello", 'WITHPAYLOADS')
1452    env.assertListEqual(['hello world', 'foo', 'hello werld', 'bar', 'hello nopayload', None, 'hello nopayload2', None],
1453                         res)
1454    res = r.execute_command("FT.SUGGET", "ac", "hello")
1455    env.assertListEqual(['hello world',  'hello werld', 'hello nopayload', 'hello nopayload2'],
1456                         res)
1457    res = r.execute_command(
1458        "FT.SUGGET", "ac", "hello", 'WITHPAYLOADS', 'WITHSCORES')
1459    # we don't compare the scores beause they may change
1460    env.assertEqual(12, len(res))
1461
1462def testPayload(env):
1463    r = env
1464    env.assertOk(r.execute_command(
1465        'ft.create', 'idx', 'schema', 'f', 'text'))
1466    for i in range(10):
1467
1468        env.assertOk(r.execute_command('ft.add', 'idx', '%d' % i, 1.0,
1469                                        'payload', 'payload %d' % i,
1470                                        'fields', 'f', 'hello world'))
1471
1472    for x in r.retry_with_rdb_reload():
1473
1474        res = r.execute_command(
1475            'ft.search', 'idx', 'hello world')
1476        env.assertEqual(21, len(res))
1477
1478        res = r.execute_command(
1479            'ft.search', 'idx', 'hello world', 'withpayloads')
1480
1481        env.assertEqual(31, len(res))
1482        env.assertEqual(10, res[0])
1483        for i in range(1, 30, 3):
1484            env.assertEqual(res[i + 1], 'payload %s' % res[i])
1485
1486def testGarbageCollector(env):
1487    env.skipOnCluster()
1488    if env.moduleArgs is not None and 'GC_POLICY FORK' in env.moduleArgs[0]:
1489        # this test is not relevent for fork gc cause its not cleaning the last block
1490        raise unittest.SkipTest()
1491    N = 100
1492    r = env
1493    env.assertOk(r.execute_command(
1494        'ft.create', 'idx', 'schema', 'foo', 'text'))
1495    for i in range(N):
1496
1497        env.assertOk(r.execute_command('ft.add', 'idx', 'doc%d' % i, 1.0,
1498                                        'fields', 'foo', ' '.join(('term%d' % random.randrange(0, 10) for i in range(10)))))
1499
1500    def get_stats(r):
1501        res = r.execute_command('ft.info', 'idx')
1502        d = {res[i]: res[i + 1] for i in range(0, len(res), 2)}
1503        gc_stats = {d['gc_stats'][x]: float(
1504            d['gc_stats'][x + 1]) for x in range(0, len(d['gc_stats']), 2)}
1505        d['gc_stats'] = gc_stats
1506        return d
1507
1508    stats = get_stats(r)
1509    if 'current_hz' in stats['gc_stats']:
1510        env.assertGreater(stats['gc_stats']['current_hz'], 8)
1511    env.assertEqual(0, stats['gc_stats']['bytes_collected'])
1512    env.assertGreater(int(stats['num_records']), 0)
1513
1514    initialIndexSize = float(stats['inverted_sz_mb']) * 1024 * 1024
1515    for i in range(N):
1516        env.assertEqual(1, r.execute_command(
1517            'ft.del', 'idx', 'doc%d' % i))
1518
1519    for _ in range(100):
1520        # gc is random so we need to do it long enough times for it to work
1521        env.cmd('ft.debug', 'GC_FORCEINVOKE', 'idx')
1522
1523    stats = get_stats(r)
1524
1525    env.assertEqual(0, int(stats['num_docs']))
1526    env.assertEqual(0, int(stats['num_records']))
1527    if not env.is_cluster():
1528        env.assertEqual(100, int(stats['max_doc_id']))
1529        if 'current_hz' in stats['gc_stats']:
1530            env.assertGreater(stats['gc_stats']['current_hz'], 30)
1531        currentIndexSize = float(stats['inverted_sz_mb']) * 1024 * 1024
1532        # print initialIndexSize, currentIndexSize,
1533        # stats['gc_stats']['bytes_collected']
1534        env.assertGreater(initialIndexSize, currentIndexSize)
1535        env.assertGreater(stats['gc_stats'][
1536            'bytes_collected'], currentIndexSize)
1537
1538    for i in range(10):
1539
1540        res = r.execute_command('ft.search', 'idx', 'term%d' % i)
1541        env.assertEqual([0], res)
1542
1543def testReturning(env):
1544    env.assertCmdOk('ft.create', 'idx', 'schema',
1545                     'f1', 'text',
1546                     'f2', 'text',
1547                     'n1', 'numeric', 'sortable',
1548                     'f3', 'text')
1549    for i in range(10):
1550        env.assertCmdOk('ft.add', 'idx', 'DOC_{0}'.format(i), 1.0, 'fields',
1551                         'f2', 'val2', 'f1', 'val1', 'f3', 'val3',
1552                         'n1', i)
1553
1554    # RETURN 0. Simplest case
1555    for x in env.retry_with_reload():
1556        res = env.cmd('ft.search', 'idx', 'val*', 'return', '0')
1557        env.assertEqual(11, len(res))
1558        env.assertEqual(10, res[0])
1559        for r in res[1:]:
1560            env.assertTrue(r.startswith('DOC_'))
1561
1562    for field in ('f1', 'f2', 'f3', 'n1'):
1563        res = env.cmd('ft.search', 'idx', 'val*', 'return', 1, field)
1564        env.assertEqual(21, len(res))
1565        env.assertEqual(10, res[0])
1566        for pair in grouper(res[1:], 2):
1567            docname, fields = pair
1568            env.assertEqual(2, len(fields))
1569            env.assertEqual(field, fields[0])
1570            env.assertTrue(docname.startswith('DOC_'))
1571
1572    # Test that we don't return SORTBY fields if they weren't specified
1573    # also in RETURN
1574    res = env.cmd('ft.search', 'idx', 'val*', 'return', 1, 'f1',
1575        'sortby', 'n1', 'ASC')
1576    row = res[2]
1577    # get the first result
1578    env.assertEqual(['f1', 'val1'], row)
1579
1580    # Test when field is not found
1581    res = env.cmd('ft.search', 'idx', 'val*', 'return', 1, 'nonexist')
1582    env.assertEqual(21, len(res))
1583    env.assertEqual(10, res[0])
1584
1585    # # Test that we don't crash if we're given the wrong number of fields
1586    with env.assertResponseError():
1587        res = env.cmd('ft.search', 'idx', 'val*', 'return', 700, 'nonexist')
1588
1589def _test_create_options_real(env, *options):
1590    options = [x for x in options if x]
1591    has_offsets = 'NOOFFSETS' not in options
1592    has_fields = 'NOFIELDS' not in options
1593    has_freqs = 'NOFREQS' not in options
1594
1595    try:
1596        env.cmd('ft.drop', 'idx')
1597    except:
1598        pass
1599
1600    options = ['idx'] + options + ['schema', 'f1', 'text', 'f2', 'text']
1601    env.assertCmdOk('ft.create', *options)
1602    for i in range(10):
1603        env.assertCmdOk('ft.add', 'idx', 'doc{}'.format(
1604            i), 0.5, 'fields', 'f1', 'value for {}'.format(i))
1605
1606    # Query
1607#     res = env.cmd('ft.search', 'idx', "value for 3")
1608#     if not has_offsets:
1609#         env.assertIsNone(res)
1610#     else:
1611#         env.assertIsNotNone(res)
1612
1613    # Frequencies:
1614    env.assertCmdOk('ft.add', 'idx', 'doc100',
1615                     1.0, 'fields', 'f1', 'foo bar')
1616    env.assertCmdOk('ft.add', 'idx', 'doc200', 1.0,
1617                     'fields', 'f1', ('foo ' * 10) + ' bar')
1618    res = env.cmd('ft.search', 'idx', 'foo')
1619    env.assertEqual(2, res[0])
1620    if has_offsets:
1621        docname = res[1]
1622        if has_freqs:
1623            env.assertEqual('doc200', docname)
1624        else:
1625            env.assertEqual('doc100', docname)
1626
1627    env.assertCmdOk('ft.add', 'idx', 'doc300',
1628                     1.0, 'fields', 'f1', 'Hello')
1629    res = env.cmd('ft.search', 'idx', '@f2:Hello')
1630    if has_fields:
1631        env.assertEqual(1, len(res))
1632    else:
1633        env.assertEqual(3, len(res))
1634
1635def testCreationOptions(env):
1636    from itertools import combinations
1637    for x in range(1, 5):
1638        for combo in combinations(('NOOFFSETS', 'NOFREQS', 'NOFIELDS', ''), x):
1639            _test_create_options_real(env, *combo)
1640
1641    env.expect('ft.create', 'idx').error()
1642
1643def testInfoCommand(env):
1644    from itertools import combinations
1645    r = env
1646    env.assertOk(r.execute_command(
1647        'ft.create', 'idx', 'NOFIELDS', 'schema', 'title', 'text'))
1648    N = 50
1649    for i in xrange(N):
1650        env.assertOk(r.execute_command('ft.add', 'idx', 'doc%d' % i, 1, 'replace', 'fields',
1651                                        'title', 'hello term%d' % i))
1652    for _ in r.retry_with_rdb_reload():
1653
1654        res = r.execute_command('ft.info', 'idx')
1655        d = {res[i]: res[i + 1] for i in range(0, len(res), 2)}
1656
1657        env.assertEqual(d['index_name'], 'idx')
1658        env.assertEqual(d['index_options'], ['NOFIELDS'])
1659        env.assertListEqual(
1660            d['fields'], [['title', 'type', 'TEXT', 'WEIGHT', '1']])
1661
1662        if not env.is_cluster():
1663            env.assertEquals(int(d['num_docs']), N)
1664            env.assertEquals(int(d['num_terms']), N + 1)
1665            env.assertEquals(int(d['max_doc_id']), N)
1666            env.assertEquals(int(d['records_per_doc_avg']), 2)
1667            env.assertEquals(int(d['num_records']), N * 2)
1668
1669            env.assertGreater(float(d['offset_vectors_sz_mb']), 0)
1670            env.assertGreater(float(d['key_table_size_mb']), 0)
1671            env.assertGreater(float(d['inverted_sz_mb']), 0)
1672            env.assertGreater(float(d['bytes_per_record_avg']), 0)
1673            env.assertGreater(float(d['doc_table_size_mb']), 0)
1674
1675    for x in range(1, 5):
1676        for combo in combinations(('NOOFFSETS', 'NOFREQS', 'NOFIELDS', ''), x):
1677            combo = list(filter(None, combo))
1678            options = combo + ['schema', 'f1', 'text']
1679            try:
1680                env.cmd('ft.drop', 'idx')
1681            except:
1682                pass
1683            env.assertCmdOk('ft.create', 'idx', *options)
1684            info = env.cmd('ft.info', 'idx')
1685            ix = info.index('index_options')
1686            env.assertFalse(ix == -1)
1687
1688            opts = info[ix + 1]
1689            # make sure that an empty opts string returns no options in
1690            # info
1691            if not combo:
1692                env.assertListEqual([], opts)
1693
1694            for option in filter(None, combo):
1695                env.assertTrue(option in opts)
1696
1697def testNoStem(env):
1698    env.cmd('ft.create', 'idx', 'schema', 'body',
1699             'text', 'name', 'text', 'nostem')
1700    if not env.isCluster():
1701        # todo: change it to be more generic to pass on is_cluster
1702        res = env.cmd('ft.info', 'idx')
1703        env.assertEqual(res[5][1][5], 'NOSTEM')
1704    for _ in env.retry_with_reload():
1705        try:
1706            env.cmd('ft.del', 'idx', 'doc')
1707        except redis.ResponseError:
1708            pass
1709
1710        # Insert a document
1711        env.assertCmdOk('ft.add', 'idx', 'doc', 1.0, 'fields',
1712                         'body', "located",
1713                         'name', "located")
1714
1715        # Now search for the fields
1716        res_body = env.cmd('ft.search', 'idx', '@body:location')
1717        res_name = env.cmd('ft.search', 'idx', '@name:location')
1718        env.assertEqual(0, res_name[0])
1719        env.assertEqual(1, res_body[0])
1720
1721def testSearchNonexistField(env):
1722    # GH Issue 133
1723    env.cmd('ft.create', 'idx', 'schema', 'title', 'text',
1724             'weight', 5.0, 'body', 'text', 'url', 'text')
1725    env.cmd('ft.add', 'idx', 'd1', 1.0, 'nosave', 'fields', 'title',
1726             'hello world', 'body', 'lorem dipsum', 'place', '-77.0366 38.8977')
1727    env.cmd('ft.search', 'idx', 'Foo', 'GEOFILTER',
1728             'place', '-77.0366', '38.8977', '1', 'km')
1729
1730def testSortbyMissingField(env):
1731    # GH Issue 131
1732    env.cmd('ft.create', 'ix', 'schema', 'txt',
1733             'text', 'num', 'numeric', 'sortable')
1734    env.cmd('ft.add', 'ix', 'doc1', 1.0, 'fields', 'txt', 'foo')
1735    env.cmd('ft.search', 'ix', 'foo', 'sortby', 'num')
1736
1737def testParallelIndexing(env):
1738    # GH Issue 207
1739    env.cmd('ft.create', 'idx', 'schema', 'txt', 'text')
1740    from threading import Thread
1741    env.getConnection()
1742    ndocs = 100
1743
1744    def runner(tid):
1745        cli = env.getConnection()
1746        for num in range(ndocs):
1747            cli.execute_command('ft.add', 'idx', 'doc{}_{}'.format(tid, num), 1.0,
1748                                'fields', 'txt', 'hello world' * 20)
1749    ths = []
1750    for tid in range(10):
1751        ths.append(Thread(target=runner, args=(tid,)))
1752
1753    [th.start() for th in ths]
1754    [th.join() for th in ths]
1755    res = env.cmd('ft.info', 'idx')
1756    d = {res[i]: res[i + 1] for i in range(0, len(res), 2)}
1757    env.assertEqual(1000, int(d['num_docs']))
1758
1759def testDoubleAdd(env):
1760    # Tests issue #210
1761    env.cmd('ft.create', 'idx', 'schema', 'txt', 'text')
1762    env.cmd('ft.add', 'idx', 'doc1', 1.0, 'fields', 'txt', 'hello world')
1763    with env.assertResponseError():
1764        env.cmd('ft.add', 'idx', 'doc1', 1.0,
1765                 'fields', 'txt', 'goodbye world')
1766
1767    env.assertEqual('hello world', env.cmd('ft.get', 'idx', 'doc1')[1])
1768    env.assertEqual(0, env.cmd('ft.search', 'idx', 'goodbye')[0])
1769    env.assertEqual(1, env.cmd('ft.search', 'idx', 'hello')[0])
1770
1771    # Now with replace
1772    env.cmd('ft.add', 'idx', 'doc1', 1.0, 'replace',
1773             'fields', 'txt', 'goodbye world')
1774    env.assertEqual(1, env.cmd('ft.search', 'idx', 'goodbye')[0])
1775    env.assertEqual(0, env.cmd('ft.search', 'idx', 'hello')[0])
1776    env.assertEqual('goodbye world', env.cmd('ft.get', 'idx', 'doc1')[1])
1777
1778def testConcurrentErrors(env):
1779    from multiprocessing import Process
1780    import random
1781
1782    env.cmd('ft.create', 'idx', 'schema', 'txt', 'text')
1783    docs_per_thread = 100
1784    num_threads = 50
1785
1786    docIds = ['doc{}'.format(x) for x in range(docs_per_thread)]
1787
1788    def thrfn():
1789        myIds = docIds[::]
1790        random.shuffle(myIds)
1791        cli = env.getConnection()
1792        with cli.pipeline(transaction=False) as pl:
1793            for x in myIds:
1794                pl.execute_command('ft.add', 'idx', x, 1.0,
1795                                   'fields', 'txt', ' hello world ' * 50)
1796            try:
1797                pl.execute()
1798            except Exception as e:
1799                pass
1800                # print e
1801
1802    thrs = [Process(target=thrfn) for x in range(num_threads)]
1803    [th.start() for th in thrs]
1804    [th.join() for th in thrs]
1805    res = env.cmd('ft.info', 'idx')
1806    d = {res[i]: res[i + 1] for i in range(0, len(res), 2)}
1807    env.assertEqual(100, int(d['num_docs']))
1808
1809def testBinaryKeys(env):
1810    env.cmd('ft.create', 'idx', 'schema', 'txt', 'text')
1811    # Insert a document
1812    env.cmd('ft.add', 'idx', 'Hello', 1.0, 'fields', 'txt', 'NoBin match')
1813    env.cmd('ft.add', 'idx', 'Hello\x00World', 1.0, 'fields', 'txt', 'Bin match')
1814    for _ in env.reloading_iterator():
1815        exp = [2L, 'Hello\x00World', ['txt', 'Bin match'], 'Hello', ['txt', 'NoBin match']]
1816        res = env.cmd('ft.search', 'idx', 'match')
1817        env.assertEqual(exp, res)
1818
1819def testNonDefaultDb(env):
1820    if env.is_cluster():
1821        raise unittest.SkipTest()
1822
1823    # Should be ok
1824    env.cmd('FT.CREATE', 'idx1', 'schema', 'txt', 'text')
1825    try:
1826        env.cmd('SELECT 1')
1827    except redis.ResponseError:
1828        return
1829
1830    # Should fail
1831    with env.assertResponseError():
1832        env.cmd('FT.CREATE', 'idx2', 'schema', 'txt', 'text')
1833
1834def testDuplicateNonspecFields(env):
1835    env.cmd('FT.CREATE', 'idx', 'schema', 'txt', 'text')
1836    env.cmd('FT.ADD', 'idx', 'doc', 1.0, 'fields',
1837             'f1', 'f1val', 'f1', 'f1val2', 'F1', 'f1Val3')
1838
1839    res = env.cmd('ft.get', 'idx', 'doc')
1840    res = {res[i]: res[i + 1] for i in range(0, len(res), 2)}
1841    env.assertTrue(res['f1'] in ('f1val', 'f1val2'))
1842    env.assertEqual('f1Val3', res['F1'])
1843
1844def testDuplicateFields(env):
1845    env.cmd('FT.CREATE', 'idx', 'SCHEMA', 'txt',
1846             'TEXT', 'num', 'NUMERIC', 'SORTABLE')
1847    for _ in env.retry_with_reload():
1848        # Ensure the index assignment is correct after an rdb load
1849        with env.assertResponseError():
1850            env.cmd('FT.ADD', 'idx', 'doc', 1.0, 'FIELDS',
1851                     'txt', 'foo', 'txt', 'bar', 'txt', 'baz')
1852
1853        # Try add hash
1854        env.hmset('newDoc', {'txt': 'foo', 'Txt': 'bar', 'txT': 'baz'})
1855        # Get the actual value:
1856
1857        from redis import ResponseError
1858        if not env.is_cluster():
1859            with env.assertResponseError(contained='twice'):
1860                env.cmd('FT.ADDHASH', 'idx', 'newDoc', 1.0)
1861
1862        # Try with REPLACE
1863        with env.assertResponseError():
1864            env.cmd('FT.ADD', 'idx', 'doc2', 1.0, 'REPLACE', 'FIELDS',
1865                     'txt', 'foo', 'txt', 'bar')
1866
1867        # With replace partial
1868        env.cmd('FT.ADD', 'idx', 'doc2', 1.0, 'REPLACE',
1869                 'PARTIAL', 'FIELDS', 'num', 42)
1870        with env.assertResponseError():
1871            env.cmd('FT.ADD', 'idx', 'doc2', 1.0, 'REPLACE',
1872                     'PARTIAL', 'FIELDS', 'num', 42, 'num', 32)
1873
1874def testDuplicateSpec(env):
1875    with env.assertResponseError():
1876        env.cmd('FT.CREATE', 'idx', 'SCHEMA', 'f1',
1877                 'text', 'n1', 'numeric', 'f1', 'text')
1878
1879def testSortbyMissingFieldSparse(env):
1880    # Note, the document needs to have one present sortable field in
1881    # order for the indexer to give it a sort vector
1882    env.cmd('ft.create', 'idx', 'SCHEMA', 'lastName', 'text',
1883             'SORTABLE', 'firstName', 'text', 'SORTABLE')
1884    env.cmd('ft.add', 'idx', 'doc1', 1.0, 'fields', 'lastName', 'mark')
1885    res = env.cmd('ft.search', 'idx', 'mark', 'WITHSORTKEYS', "SORTBY",
1886                   "firstName", "ASC", "limit", 0, 100)
1887    # commented because we don't filter out exclusive sortby fields
1888    # env.assertEqual([1L, 'doc1', None, ['lastName', 'mark']], res)
1889
1890def testLuaAndMulti(env):
1891    if env.is_cluster():
1892        raise unittest.SkipTest()
1893    # Ensure we can work in Lua and Multi environments without crashing
1894    env.cmd('FT.CREATE', 'idx', 'SCHEMA', 'f1', 'text', 'n1', 'numeric')
1895    env.cmd('HMSET', 'hashDoc', 'f1', 'v1', 'n1', 4)
1896    env.cmd('HMSET', 'hashDoc2', 'f1', 'v1', 'n1', 5)
1897
1898    r = env.getConnection()
1899
1900    r.eval("return redis.call('ft.add', 'idx', 'doc1', 1.0, 'fields', 'f1', 'bar')", "0")
1901    r.eval("return redis.call('ft.addhash', 'idx', 'hashDoc', 1.0)", 0)
1902
1903    # Try in a pipeline:
1904    with r.pipeline(transaction=True) as pl:
1905        pl.execute_command('ft.add', 'idx', 'doc2',
1906                           1.0, 'fields', 'f1', 'v3')
1907        pl.execute_command('ft.add', 'idx', 'doc3',
1908                           1.0, 'fields', 'f1', 'v4')
1909        pl.execute_command('ft.addhash', 'idx', 'hashdoc2', 1.0)
1910    pl.execute()
1911
1912def testLanguageField(env):
1913    env.cmd('FT.CREATE', 'idx', 'SCHEMA', 'language', 'TEXT')
1914    env.cmd('FT.ADD', 'idx', 'doc1', 1.0,
1915             'FIELDS', 'language', 'gibberish')
1916    res = env.cmd('FT.SEARCH', 'idx', 'gibberish')
1917    env.assertEqual([1L, 'doc1', ['language', 'gibberish']], res)
1918    # The only way I can verify that LANGUAGE is parsed twice is ensuring we
1919    # provide a wrong language. This is much easier to test than trying to
1920    # figure out how a given word is stemmed
1921    with env.assertResponseError():
1922        env.cmd('FT.ADD', 'idx', 'doc1', 1.0, 'LANGUAGE',
1923                 'blah', 'FIELDS', 'language', 'gibber')
1924
1925def testUninitSortvector(env):
1926    # This would previously crash
1927    env.cmd('FT.CREATE', 'idx', 'SCHEMA', 'f1', 'TEXT')
1928    for x in range(2000):
1929        env.cmd('FT.ADD', 'idx', 'doc{}'.format(
1930            x), 1.0, 'FIELDS', 'f1', 'HELLO')
1931
1932    env.broadcast('SAVE')
1933    for x in range(10):
1934        env.broadcast('DEBUG RELOAD')
1935
1936
1937def normalize_row(row):
1938    return to_dict(row)
1939
1940
1941def assertAggrowsEqual(env, exp, got):
1942    env.assertEqual(exp[0], got[0])
1943    env.assertEqual(len(exp), len(got))
1944
1945    # and now, it's just free form:
1946    exp = sorted(to_dict(x) for x in exp[1:])
1947    got = sorted(to_dict(x) for x in got[1:])
1948    env.assertEqual(exp, got)
1949
1950def assertResultsEqual(env, exp, got, inorder=True):
1951    from pprint import pprint
1952    # pprint(exp)
1953    # pprint(got)
1954    env.assertEqual(exp[0], got[0])
1955    env.assertEqual(len(exp), len(got))
1956
1957    exp = list(grouper(exp[1:], 2))
1958    got = list(grouper(got[1:], 2))
1959
1960    for x in range(len(exp)):
1961        exp_did, exp_fields = exp[x]
1962        got_did, got_fields = got[x]
1963        env.assertEqual(exp_did, got_did, message="at position {}".format(x))
1964        got_fields = to_dict(got_fields)
1965        exp_fields = to_dict(exp_fields)
1966        env.assertEqual(exp_fields, got_fields, message="at position {}".format(x))
1967
1968
1969def testAlterIndex(env):
1970    env.cmd('FT.CREATE', 'idx', 'SCHEMA', 'f1', 'TEXT')
1971    env.cmd('FT.ADD', 'idx', 'doc1', 1.0, 'FIELDS', 'f1', 'hello', 'f2', 'world')
1972    env.cmd('FT.ALTER', 'idx', 'SCHEMA', 'ADD', 'f2', 'TEXT')
1973    env.cmd('FT.ADD', 'idx', 'doc2', 1.0, 'FIELDS', 'f1', 'hello', 'f2', 'world')
1974    for _ in env.retry_with_reload():
1975        ret = env.cmd('FT.SEARCH', 'idx', 'world')
1976        env.assertEqual([1, 'doc2', ['f1', 'hello', 'f2', 'world']], ret)
1977
1978    env.cmd('FT.ALTER', 'idx', 'SCHEMA', 'ADD', 'f3', 'TEXT', 'SORTABLE')
1979    for x in range(10):
1980        env.cmd('FT.ADD', 'idx', 'doc{}'.format(x + 3), 1.0,
1981                 'FIELDS', 'f1', 'hello', 'f3', 'val{}'.format(x))
1982
1983    for _ in env.retry_with_reload():
1984        # Test that sortable works
1985        res = env.cmd('FT.SEARCH', 'idx', 'hello', 'SORTBY', 'f3', 'DESC')
1986        exp = [12, 'doc12', ['f1', 'hello', 'f3', 'val9'], 'doc11', ['f1', 'hello', 'f3', 'val8'], 'doc10', ['f1', 'hello', 'f3', 'val7'], 'doc9', ['f1', 'hello', 'f3', 'val6'], 'doc8', ['f1', 'hello', 'f3', 'val5'], 'doc7', [
1987                'f1', 'hello', 'f3', 'val4'], 'doc6', ['f1', 'hello', 'f3', 'val3'], 'doc5', ['f1', 'hello', 'f3', 'val2'], 'doc4', ['f1', 'hello', 'f3', 'val1'], 'doc3', ['f1', 'hello', 'f3', 'val0']]
1988
1989        assertResultsEqual(env, exp, res)
1990
1991    # Test that we can add a numeric field
1992    env.cmd('FT.ALTER', 'idx', 'SCHEMA', 'ADD', 'n1', 'NUMERIC')
1993    env.cmd('FT.ADD', 'idx', 'docN1', 1.0, 'FIELDS', 'n1', 50)
1994    env.cmd('FT.ADD', 'idx', 'docN2', 1.0, 'FIELDS', 'n1', 250)
1995    for _ in env.retry_with_reload():
1996        res = env.cmd('FT.SEARCH', 'idx', '@n1:[0 100]')
1997        env.assertEqual([1, 'docN1', ['n1', '50']], res)
1998
1999    env.expect('FT.ALTER', 'idx', 'SCHEMA', 'NOT_ADD', 'f2', 'TEXT').error()
2000    env.expect('FT.ALTER', 'idx', 'SCHEMA', 'ADD').error()
2001    env.expect('FT.ALTER', 'idx', 'SCHEMA', 'ADD', 'f2').error()
2002    env.expect('FT.ALTER', 'idx', 'ADD', 'SCHEMA', 'f2', 'TEXT').error()
2003    env.expect('FT.ALTER', 'idx', 'f2', 'TEXT').error()
2004
2005def testAlterValidation(env):
2006    # Test that constraints for ALTER comand
2007    env.cmd('FT.CREATE', 'idx1', 'SCHEMA', 'f0', 'TEXT')
2008    for x in range(1, 32):
2009        env.cmd('FT.ALTER', 'idx1', 'SCHEMA', 'ADD', 'f{}'.format(x), 'TEXT')
2010    # OK for now.
2011
2012    # Should be too many indexes
2013    env.assertRaises(redis.ResponseError, env.cmd, 'FT.ALTER',
2014                      'idx1', 'SCHEMA', 'ADD', 'tooBig', 'TEXT')
2015
2016    env.cmd('FT.CREATE', 'idx2', 'MAXTEXTFIELDS', 'SCHEMA', 'f0', 'TEXT')
2017    # print env.cmd('FT.INFO', 'idx2')
2018    for x in range(1, 50):
2019        env.cmd('FT.ALTER', 'idx2', 'SCHEMA', 'ADD', 'f{}'.format(x + 1), 'TEXT')
2020
2021    env.cmd('FT.ADD', 'idx2', 'doc1', 1.0, 'FIELDS', 'f50', 'hello')
2022    for _ in env.retry_with_reload():
2023        ret = env.cmd('FT.SEARCH', 'idx2', '@f50:hello')
2024        env.assertEqual([1, 'doc1', ['f50', 'hello']], ret)
2025
2026    env.cmd('FT.CREATE', 'idx3', 'SCHEMA', 'f0', 'text')
2027    # Try to alter the index with garbage
2028    env.assertRaises(redis.ResponseError, env.cmd, 'FT.ALTER', 'idx3',
2029                      'SCHEMA', 'ADD', 'f1', 'TEXT', 'f2', 'garbage')
2030    ret = to_dict(env.cmd('ft.info', 'idx3'))
2031    env.assertEqual(1, len(ret['fields']))
2032
2033    env.assertRaises(redis.ResponseError, env.cmd, 'FT.ALTER',
2034                      'nonExist', 'SCHEMA', 'ADD', 'f1', 'TEXT')
2035
2036    # test with no fields!
2037    env.assertRaises(redis.ResponseError, env.cmd, 'FT.ALTER', 'idx2', 'SCHEMA', 'ADD')
2038
2039def testIssue366_1(env):
2040    if env.is_cluster():
2041        raise unittest.SkipTest('ADDHASH unsupported!')
2042    # Test random RDB regressions, see GH 366
2043    env.cmd('FT.CREATE', 'idx1', 'SCHEMA', 'textfield', 'TEXT', 'numfield', 'NUMERIC')
2044    env.hmset('foo', {'textfield': 'blah', 'numfield': 1})
2045    env.cmd('FT.ADDHASH', 'idx1', 'foo', 1, 'replace')
2046    env.cmd('FT.DEL', 'idx1', 'foo')
2047    for _ in env.retry_with_reload():
2048        pass  # --just ensure it doesn't crash
2049
2050def testIssue366_2(env):
2051    # FT.CREATE atest SCHEMA textfield TEXT numfield NUMERIC
2052    # FT.ADD atest anId 1 PAYLOAD '{"hello":"world"}' FIELDS textfield sometext numfield 1234
2053    # FT.ADD atest anId 1 PAYLOAD '{"hello":"world2"}' REPLACE PARTIAL FIELDS numfield 1111
2054    # shutdown
2055    env.cmd('FT.CREATE', 'idx1', 'SCHEMA', 'textfield', 'TEXT', 'numfield', 'NUMERIC')
2056    env.cmd('FT.ADD', 'idx1', 'doc1', 1, 'PAYLOAD', '{"hello":"world"}',
2057             'FIELDS', 'textfield', 'sometext', 'numfield', 1234)
2058    env.cmd('ft.add', 'idx1', 'doc1', 1,
2059             'PAYLOAD', '{"hello":"world2"}',
2060             'REPLACE', 'PARTIAL',
2061             'FIELDS', 'textfield', 'sometext', 'numfield', 1111)
2062    for _ in env.retry_with_reload():
2063        pass  #
2064
2065def testIssue654(env):
2066    # Crashes during FILTER
2067    env.cmd('ft.create', 'idx', 'schema', 'id', 'numeric')
2068    env.cmd('ft.add', 'idx', 1, 1, 'fields', 'id', 1)
2069    env.cmd('ft.add', 'idx', 2, 1, 'fields', 'id', 2)
2070    res = env.cmd('ft.search', 'idx', '*', 'filter', '@version', 0, 2)
2071
2072def testReplaceReload(env):
2073    env.cmd('FT.CREATE', 'idx2', 'SCHEMA', 'textfield', 'TEXT', 'numfield', 'NUMERIC')
2074    # Create a document and then replace it.
2075    env.cmd('FT.ADD', 'idx2', 'doc2', 1.0, 'FIELDS', 'textfield', 's1', 'numfield', 99)
2076    env.cmd('FT.ADD', 'idx2', 'doc2', 1.0, 'REPLACE', 'PARTIAL',
2077             'FIELDS', 'textfield', 's100', 'numfield', 990)
2078    env.dump_and_reload()
2079    # RDB Should still be fine
2080
2081    env.cmd('FT.ADD', 'idx2', 'doc2', 1.0, 'REPLACE', 'PARTIAL',
2082             'FIELDS', 'textfield', 's200', 'numfield', 1090)
2083    doc = to_dict(env.cmd('FT.GET', 'idx2', 'doc2'))
2084    env.assertEqual('s200', doc['textfield'])
2085    env.assertEqual('1090', doc['numfield'])
2086
2087
2088# command = 'FT.CREATE idx SCHEMA '
2089# for i in range(255):
2090#     command += 't%d NUMERIC SORTABLE ' % i
2091# command = command[:-1]
2092# r.execute_command(command)
2093# r.execute_command('save')
2094# // reload from ...
2095# r.execute_command('FT.ADD idx doc1 1.0 FIELDS t0 1')
2096def testIssue417(env):
2097    command = ['ft.create', 'idx', 'schema']
2098    for x in range(255):
2099        command += ['t{}'.format(x), 'numeric', 'sortable']
2100    command = command[:-1]
2101    env.cmd(*command)
2102    for _ in env.reloading_iterator():
2103        try:
2104            env.execute_command('FT.ADD', 'idx', 'doc1', '1.0', 'FIELDS', 't0', '1')
2105        except redis.ResponseError as e:
2106            env.assertTrue('already' in e.message.lower())
2107
2108# >FT.CREATE myIdx SCHEMA title TEXT WEIGHT 5.0 body TEXT url TEXT
2109# >FT.ADD myIdx doc1 1.0 FIELDS title "hello world" body "lorem ipsum" url "www.google.com"
2110# >FT.SEARCH myIdx "no-as"
2111# Could not connect to Redis at 127.0.0.1:6379: Connection refused
2112# >FT.SEARCH myIdx "no-as"
2113# (error) Unknown Index name
2114def testIssue422(env):
2115    env.cmd('ft.create', 'myIdx', 'schema',
2116             'title', 'TEXT', 'WEIGHT', '5.0',
2117             'body', 'TEXT',
2118             'url', 'TEXT')
2119    env.cmd('ft.add', 'myIdx', 'doc1', '1.0', 'FIELDS', 'title', 'hello world', 'bod', 'lorem ipsum', 'url', 'www.google.com')
2120    rv = env.cmd('ft.search', 'myIdx', 'no-as')
2121    env.assertEqual([0], rv)
2122
2123def testIssue446(env):
2124    env.cmd('ft.create', 'myIdx', 'schema',
2125             'title', 'TEXT', 'SORTABLE')
2126    env.cmd('ft.add', 'myIdx', 'doc1', '1.0', 'fields', 'title', 'hello world', 'body', 'lorem ipsum', 'url', '"www.google.com')
2127    rv = env.cmd('ft.search', 'myIdx', 'hello', 'limit', '0', '0')
2128    env.assertEqual([1], rv)
2129
2130    # Related - issue 635
2131    env.cmd('ft.add', 'myIdx', 'doc2', '1.0', 'fields', 'title', 'hello')
2132    rv = env.cmd('ft.search', 'myIdx', 'hello', 'limit', '0', '0')
2133    env.assertEqual([2], rv)
2134
2135
2136def testTimeoutSettings(env):
2137    env.cmd('ft.create', 'idx', 'schema', 't1', 'text')
2138    env.expect('ft.search', 'idx', '*', 'ON_TIMEOUT', 'BLAHBLAH').raiseError()
2139    env.expect('ft.search', 'idx', '*', 'ON_TIMEOUT', 'RETURN').notRaiseError()
2140    env.expect('ft.search', 'idx', '*', 'ON_TIMEOUT', 'FAIL').notRaiseError()
2141
2142def testAlias(env):
2143    env.cmd('ft.create', 'idx', 'schema', 't1', 'text')
2144    env.cmd('ft.create', 'idx2', 'schema', 't1', 'text')
2145
2146    env.expect('ft.aliasAdd', 'myIndex').raiseError()
2147    env.expect('ft.aliasupdate', 'fake_alias', 'imaginary_alias', 'Too_many_args').raiseError()
2148    env.cmd('ft.aliasAdd', 'myIndex', 'idx')
2149    env.cmd('ft.add', 'myIndex', 'doc1', 1.0, 'fields', 't1', 'hello')
2150    r = env.cmd('ft.search', 'idx', 'hello')
2151    env.assertEqual([1, 'doc1', ['t1', 'hello']], r)
2152    r2 = env.cmd('ft.search', 'myIndex', 'hello')
2153    env.assertEqual(r, r2)
2154
2155    # try to add the same alias again; should be an error
2156    env.expect('ft.aliasAdd', 'myIndex', 'idx2').raiseError()
2157    env.expect('ft.aliasAdd', 'alias2', 'idx').notRaiseError()
2158    # now delete the index
2159    env.cmd('ft.drop', 'myIndex')
2160
2161    # index list should be cleared now. This can be tested by trying to alias
2162    # the old alias to different index
2163    env.cmd('ft.aliasAdd', 'myIndex', 'idx2')
2164    env.cmd('ft.aliasAdd', 'alias2', 'idx2')
2165    env.cmd('ft.add', 'myIndex', 'doc2', 1.0, 'fields', 't1', 'hello')
2166    r = env.cmd('ft.search', 'alias2', 'hello')
2167    env.assertEqual([1L, 'doc2', ['t1', 'hello']], r)
2168
2169    # check that aliasing one alias to another returns an error. This will
2170    # end up being confusing
2171    env.expect('ft.aliasAdd', 'alias3', 'myIndex').raiseError()
2172
2173    # check that deleting the alias works as expected
2174    env.expect('ft.aliasDel', 'myIndex').notRaiseError()
2175    env.expect('ft.search', 'myIndex', 'foo').raiseError()
2176
2177    # create a new index and see if we can use the old name
2178    env.cmd('ft.create', 'idx3', 'schema', 't1', 'text')
2179    env.cmd('ft.add', 'idx3', 'doc3', 1.0, 'fields', 't1', 'foo')
2180    env.cmd('ft.aliasAdd', 'myIndex', 'idx3')
2181    # also, check that this works in rdb save
2182    for _ in env.retry_with_rdb_reload():
2183        r = env.cmd('ft.search', 'myIndex', 'foo')
2184        env.assertEqual([1L, 'doc3', ['t1', 'foo']], r)
2185
2186    # Check that we can move an alias from one index to another
2187    env.cmd('ft.aliasUpdate', 'myIndex', 'idx2')
2188    r = env.cmd('ft.search', 'myIndex', "hello")
2189    env.assertEqual([1L, 'doc2', ['t1', 'hello']], r)
2190
2191    # Test that things like ft.get, ft.aggregate, etc. work
2192    r = env.cmd('ft.get', 'myIndex', 'doc2')
2193    env.assertEqual(['t1', 'hello'], r)
2194
2195    r = env.cmd('ft.aggregate', 'myIndex', 'hello', 'LOAD', '1', '@t1')
2196    env.assertEqual([1, ['t1', 'hello']], r)
2197
2198    # Test update
2199    env.expect('ft.aliasAdd', 'updateIndex', 'idx3')
2200    env.expect('ft.aliasUpdate', 'updateIndex', 'fake_idx')
2201
2202    r = env.cmd('ft.del', 'idx2', 'doc2')
2203    env.assertEqual(1, r)
2204    env.expect('ft.aliasdel').raiseError()
2205    env.expect('ft.aliasdel', 'myIndex', 'yourIndex').raiseError()
2206    env.expect('ft.aliasdel', 'non_existing_alias').raiseError()
2207
2208
2209def testNoCreate(env):
2210    env.cmd('ft.create', 'idx', 'schema', 'f1', 'text')
2211    env.expect('ft.add', 'idx', 'schema', 'f1').raiseError()
2212    env.expect('ft.add', 'idx', 'doc1', 1, 'nocreate', 'fields', 'f1', 'hello').raiseError()
2213    env.expect('ft.add', 'idx', 'doc1', 1, 'replace', 'nocreate', 'fields', 'f1', 'hello').raiseError()
2214    env.expect('ft.add', 'idx', 'doc1', 1, 'replace', 'fields', 'f1', 'hello').notRaiseError()
2215    env.expect('ft.add', 'idx', 'doc1', 1, 'replace', 'nocreate', 'fields', 'f1', 'world').notRaiseError()
2216
2217def testSpellCheck(env):
2218    env.cmd('FT.CREATE', 'idx', 'SCHEMA', 'report', 'TEXT')
2219    env.cmd('FT.ADD', 'idx', 'doc1', 1.0, 'FIELDS', 'report', 'report content')
2220    rv = env.cmd('FT.SPELLCHECK', 'idx', '111111')
2221    env.assertEqual([['TERM', '111111', []]], rv)
2222    if not env.isCluster():
2223        rv = env.cmd('FT.SPELLCHECK', 'idx', '111111', 'FULLSCOREINFO')
2224        env.assertEqual([1L, ['TERM', '111111', []]], rv)
2225
2226# Standalone functionality
2227def testIssue484(env):
2228# Issue with split
2229# 127.0.0.1:6379> ft.drop productSearch1
2230# OK
2231# 127.0.0.1:6379> "FT.CREATE" "productSearch1" "NOSCOREIDX" "SCHEMA" "productid" "TEXT" "categoryid" "TEXT"  "color" "TEXT" "timestamp" "NUMERIC"
2232# OK
2233# 127.0.0.1:6379> "FT.ADD" "productSearch1" "GUID1" "1.0" "REPLACE" "FIELDS" "productid" "1" "categoryid" "cars" "color" "blue" "categoryType" 0
2234# OK
2235# 127.0.0.1:6379> "FT.ADD" "productSearch1" "GUID2" "1.0" "REPLACE" "FIELDS" "productid" "1" "categoryid" "small cars" "color" "white" "categoryType" 0
2236# OK
2237# 127.0.0.1:6379> "FT.ADD" "productSearch1" "GUID3" "1.0" "REPLACE" "FIELDS" "productid" "2" "categoryid" "Big cars" "color" "white" "categoryType" 0
2238# OK
2239# 127.0.0.1:6379> "FT.ADD" "productSearch1" "GUID4" "1.0" "REPLACE" "FIELDS" "productid" "2" "categoryid" "Big cars" "color" "green" "categoryType" 0
2240# OK
2241# 127.0.0.1:6379> "FT.ADD" "productSearch1" "GUID5" "1.0" "REPLACE" "FIELDS" "productid" "3" "categoryid" "cars" "color" "blue" "categoryType" 0
2242# OK
2243# 127.0.0.1:6379>  FT.AGGREGATE productSearch1 * load 2 @color @categoryid APPLY "split(format(\"%s-%s\",@color,@categoryid),\"-\")" as value GROUPBY 1 @value REDUCE COUNT 0 as value_count
2244    env.cmd('ft.create', 'productSearch1', 'noscoreidx', 'schema', 'productid',
2245            'text', 'categoryid', 'text', 'color', 'text', 'timestamp', 'numeric')
2246    env.cmd('ft.add', 'productSearch1', 'GUID1', '1.0', 'REPLACE', 'FIELDS', 'productid', '1', 'categoryid', 'cars', 'color', 'blue', 'categoryType', 0)
2247    env.cmd('ft.add', 'productSearch1', 'GUID2', '1.0', 'REPLACE', 'FIELDS', 'productid', '1', 'categoryid', 'small cars', 'color', 'white', 'categoryType', 0)
2248    env.cmd('ft.add', 'productSearch1', 'GUID3', '1.0', 'REPLACE', 'FIELDS', 'productid', '2', 'categoryid', 'Big cars', 'color', 'white', 'categoryType', 0)
2249    env.cmd('ft.add', 'productSearch1', 'GUID4', '1.0', 'REPLACE', 'FIELDS', 'productid', '2', 'categoryid', 'Big cars', 'color', 'green', 'categoryType', 0)
2250    env.cmd('ft.add', 'productSearch1', 'GUID5', '1.0', 'REPLACE', 'FIELDS', 'productid', '3', 'categoryid', 'cars', 'color', 'blue', 'categoryType', 0)
2251    res = env.cmd('FT.AGGREGATE', 'productSearch1', '*',
2252        'load', '2', '@color', '@categoryid',
2253        'APPLY', 'split(format("%s-%s",@color,@categoryid),"-")', 'as', 'value',
2254        'GROUPBY', '1', '@value',
2255        'REDUCE', 'COUNT', '0', 'as', 'value_count',
2256        'SORTBY', '4', '@value_count', 'DESC', '@value', 'ASC')
2257    expected = [6, ['value', 'white', 'value_count', '2'], ['value', 'cars', 'value_count', '2'], ['value', 'small cars', 'value_count', '1'], ['value', 'blue', 'value_count', '2'], ['value', 'Big cars', 'value_count', '2'], ['value', 'green', 'value_count', '1']]
2258    assertAggrowsEqual(env, expected, res)
2259    for var in expected:
2260        env.assertIn(var, res)
2261
2262def testIssue501(env):
2263    env.cmd('FT.CREATE', 'incidents', 'SCHEMA', 'report', 'TEXT')
2264    env.cmd('FT.ADD', 'incidents', 'doc1', 1.0, 'FIELDS', 'report', 'report content')
2265    env.cmd('FT.DICTADD', 'slang', 'timmies', 'toque', 'toonie', 'serviette', 'kerfuffle', 'chesterfield')
2266    rv = env.cmd('FT.SPELLCHECK', 'incidents', 'qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq',
2267        'TERMS', 'INCLUDE', 'slang', 'TERMS', 'EXCLUDE', 'slang')
2268    env.assertEqual("qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq", rv[0][1])
2269    env.assertEqual([], rv[0][2])
2270
2271    env.expect('FT.SPELLCHECK', 'incidents', 'qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq',
2272        'TERMS', 'FAKE_COMMAND', 'slang').error()
2273
2274def testIssue589(env):
2275    env.cmd('FT.CREATE', 'incidents', 'SCHEMA', 'report', 'TEXT')
2276    env.cmd('FT.ADD', 'incidents', 'doc1', 1.0, 'FIELDS', 'report', 'report content')
2277    env.expect('FT.SPELLCHECK', 'incidents', 'report :').error().contains("Syntax error at offset")
2278
2279def testIssue621(env):
2280    env.expect('ft.create', 'test', 'SCHEMA', 'uuid', 'TAG', 'title', 'TEXT').equal('OK')
2281    env.expect('ft.add', 'test', 'a', '1', 'REPLACE', 'PARTIAL', 'FIELDS', 'uuid', 'foo', 'title', 'bar').equal('OK')
2282    env.expect('ft.add', 'test', 'a', '1', 'REPLACE', 'PARTIAL', 'FIELDS', 'title', 'bar').equal('OK')
2283    env.expect('ft.search', 'test', '@uuid:{foo}').equal([1L, 'a', ['uuid', 'foo', 'title', 'bar']])
2284
2285# Server crash on doc names that conflict with index keys #666
2286def testIssue666(env):
2287    # We cannot reliably determine that any error will occur in cluster mode
2288    # because of the key name
2289    env.skipOnCluster()
2290
2291    env.cmd('ft.create', 'foo', 'schema', 'bar', 'text')
2292    env.cmd('ft.add', 'foo', 'mydoc', 1, 'fields', 'bar', 'one two three')
2293
2294    # crashes here
2295    with env.assertResponseError():
2296        env.cmd('ft.add', 'foo', 'ft:foo/two', '1', 'fields', 'bar', 'four five six')
2297    # try with replace:
2298    with env.assertResponseError():
2299        env.cmd('ft.add', 'foo', 'ft:foo/two', '1', 'REPLACE',
2300            'FIELDS', 'bar', 'four five six')
2301    with env.assertResponseError():
2302        env.cmd('ft.add', 'foo', 'idx:foo', '1', 'REPLACE',
2303            'FIELDS', 'bar', 'four five six')
2304
2305    env.cmd('ft.add', 'foo', 'mydoc1', 1, 'fields', 'bar', 'four five six')
2306
2307# 127.0.0.1:6379> flushdb
2308# OK
2309# 127.0.0.1:6379> ft.create foo SCHEMA bar text
2310# OK
2311# 127.0.0.1:6379> ft.add foo mydoc 1 FIELDS bar "one two three"
2312# OK
2313# 127.0.0.1:6379> keys *
2314# 1) "mydoc"
2315# 2) "ft:foo/one"
2316# 3) "idx:foo"
2317# 4) "ft:foo/two"
2318# 5) "ft:foo/three"
2319# 127.0.0.1:6379> ft.add foo "ft:foo/two" 1 FIELDS bar "four five six"
2320# Could not connect to Redis at 127.0.0.1:6379: Connection refused
2321
2322def testPrefixDeletedExpansions(env):
2323    env.skipOnCluster()
2324
2325    env.cmd('ft.create', 'idx', 'schema', 'txt1', 'text', 'tag1', 'tag')
2326    # get the number of maximum expansions
2327    maxexpansions = int(env.cmd('ft.config', 'get', 'MAXEXPANSIONS')[0][1])
2328
2329    for x in range(maxexpansions):
2330        env.cmd('ft.add', 'idx', 'doc{}'.format(x), 1, 'fields',
2331                'txt1', 'term{}'.format(x), 'tag1', 'tag{}'.format(x))
2332
2333    for x in range(maxexpansions):
2334        env.cmd('ft.del', 'idx', 'doc{}'.format(x))
2335
2336    env.cmd('ft.add', 'idx', 'doc_XXX', 1, 'fields', 'txt1', 'termZZZ', 'tag1', 'tagZZZ')
2337
2338    # r = env.cmd('ft.search', 'idx', 'term*')
2339    # print(r)
2340    # r = env.cmd('ft.search', 'idx', '@tag1:{tag*}')
2341    # print(r)
2342
2343    tmax = time.time() + 0.5  # 250ms max
2344    iters = 0
2345    while time.time() < tmax:
2346        iters += 1
2347        env.cmd('ft.debug', 'gc_forceinvoke', 'idx')
2348        r = env.cmd('ft.search', 'idx', '@txt1:term* @tag1:{tag*}')
2349        if r[0]:
2350            break
2351
2352    print 'did {} iterations'.format(iters)
2353    r = env.cmd('ft.search', 'idx', '@txt1:term* @tag1:{tag*}')
2354    env.assertEqual([1, 'doc_XXX', ['txt1', 'termZZZ', 'tag1', 'tagZZZ']], r)
2355
2356
2357def testOptionalFilter(env):
2358    env.cmd('ft.create', 'idx', 'schema', 't1', 'text')
2359    for x in range(100):
2360        env.cmd('ft.add', 'idx', 'doc_{}'.format(x), 1, 'fields', 't1', 'hello world word{}'.format(x))
2361
2362    env.cmd('ft.explain', 'idx', '(~@t1:word20)')
2363    # print(r)
2364
2365    r = env.cmd('ft.search', 'idx', '~(word20 => {$weight: 2.0})')
2366
2367
2368def testIssue736(env):
2369    # 1. create the schema, we need a tag field
2370    env.cmd('ft.create', 'idx', 'schema', 't1', 'text', 'n2', 'numeric', 't2', 'tag')
2371    # 2. create a single document to initialize at least one RSAddDocumentCtx
2372    env.cmd('ft.add', 'idx', 'doc1', 1, 'fields', 't1', 'hello', 't2', 'foo, bar')
2373    # 3. create a second document with many filler fields to force a realloc:
2374    extra_fields = []
2375    for x in range(20):
2376        extra_fields += ['nidx_fld{}'.format(x), 'val{}'.format(x)]
2377    extra_fields += ['n2', 'not-a-number', 't2', 'random, junk']
2378    with env.assertResponseError():
2379        env.cmd('ft.add', 'idx', 'doc2', 1, 'fields', *extra_fields)
2380
2381def testCriteriaTesterDeactivated():
2382    env = Env(moduleArgs='_MAX_RESULTS_TO_UNSORTED_MODE 1')
2383    env.cmd('ft.create', 'idx', 'schema', 't1', 'text')
2384    env.cmd('ft.add', 'idx', 'doc1', 1, 'fields', 't1', 'hello1 hey hello2')
2385    env.cmd('ft.add', 'idx', 'doc2', 1, 'fields', 't1', 'hello2 hey')
2386    env.cmd('ft.add', 'idx', 'doc3', 1, 'fields', 't1', 'hey')
2387    res = env.execute_command('ft.search', 'idx', '(hey hello1)|(hello2 hey)')
2388    expected = [2L, 'doc1', ['t1', 'hello1 hey hello2'], 'doc2', ['t1', 'hello2 hey']]
2389    env.assertEqual(sorted(res), sorted(expected))
2390
2391def testIssue828(env):
2392    env.cmd('ft.create', 'beers', 'SCHEMA',
2393        'name', 'TEXT', 'PHONETIC', 'dm:en',
2394        'style', 'TAG', 'SORTABLE',
2395        'abv', 'NUMERIC', 'SORTABLE')
2396    rv = env.cmd("FT.ADD", "beers", "802", "1.0",
2397        "FIELDS", "index", "25", "abv", "0.049",
2398        "name", "Hell or High Watermelon Wheat (2009)",
2399        "style", "Fruit / Vegetable Beer")
2400    env.assertEqual('OK', rv)
2401
2402def testIssue862(env):
2403    env.cmd('ft.create', 'idx', 'SCHEMA', 'test', 'TEXT', 'SORTABLE')
2404    rv = env.cmd("FT.ADD", "idx", "doc1", "1.0", "FIELDS", "test", "foo")
2405    env.assertEqual('OK', rv)
2406    env.cmd("FT.SEARCH", "idx", "foo", 'WITHSORTKEYS')
2407    env.assertTrue(env.isUp())
2408
2409def testIssue_884(env):
2410    env.expect('FT.create', 'idx', 'STOPWORDS', '0', 'SCHEMA', 'title', 'text', 'weight',
2411               '50', 'subtitle', 'text', 'weight', '10', 'author', 'text', 'weight',
2412               '10', 'description', 'text', 'weight', '20').equal('OK')
2413
2414    env.expect('FT.ADD', 'idx', 'doc4', '1.0', 'FIELDS', 'title', 'mohsin conversation the conversation tahir').equal('OK')
2415    env.expect('FT.ADD', 'idx', 'doc3', '1.0', 'FIELDS', 'title', 'Fareham Civilization Church - Sermons and conversations mohsin conversation the').equal('OK')
2416    env.expect('FT.ADD', 'idx', 'doc2', '1.0', 'FIELDS', 'title', 'conversation the conversation - a drama about conversation, the science of conversation.').equal('OK')
2417    env.expect('FT.ADD', 'idx', 'doc1', '1.0', 'FIELDS', 'title', 'mohsin conversation with the mohsin').equal('OK')
2418
2419    expected = [2L, 'doc2', ['title', 'conversation the conversation - a drama about conversation, the science of conversation.'], 'doc4', ['title', 'mohsin conversation the conversation tahir']]
2420    res = env.cmd('FT.SEARCH', 'idx', '@title:(conversation) (@title:(conversation the conversation))=>{$inorder: true;$slop: 0}')
2421    env.assertEquals(len(expected), len(res))
2422    for v in expected:
2423        env.assertContains(v, res)
2424
2425def testIssue_866(env):
2426    env.expect('ft.sugadd', 'sug', 'test123', '1').equal(1)
2427    env.expect('ft.sugadd', 'sug', 'test456', '1').equal(2)
2428    env.expect('ft.sugdel', 'sug', 'test').equal(0)
2429    env.expect('ft.sugget', 'sug', '').equal(['test123', 'test456'])
2430
2431def testIssue_848(env):
2432    env.expect('FT.CREATE', 'idx', 'SCHEMA', 'test1', 'TEXT', 'SORTABLE').equal('OK')
2433    env.expect('FT.ADD', 'idx', 'doc1', '1.0', 'FIELDS', 'test1', 'foo').equal('OK')
2434    env.expect('FT.ALTER', 'idx', 'SCHEMA', 'ADD', 'test2', 'TEXT', 'SORTABLE').equal('OK')
2435    env.expect('FT.ADD', 'idx', 'doc2', '1.0', 'FIELDS', 'test1', 'foo', 'test2', 'bar').equal('OK')
2436    env.expect('FT.SEARCH', 'idx', 'foo', 'SORTBY', 'test2', 'ASC').equal([2L, 'doc1', ['test1', 'foo'], 'doc2', ['test2', 'bar', 'test1', 'foo']])
2437
2438def testMod_309(env):
2439    env.expect('FT.CREATE', 'idx', 'SCHEMA', 'test', 'TEXT', 'SORTABLE').equal('OK')
2440    for i in range(100000):
2441        env.expect('FT.ADD', 'idx', 'doc%d'%i, '1.0', 'FIELDS', 'test', 'foo').equal('OK')
2442    res = env.cmd('FT.AGGREGATE', 'idx', 'foo')
2443    env.assertEqual(len(res), 100001)
2444
2445def testIssue_865(env):
2446    env.expect('FT.CREATE', 'idx', 'SCHEMA', '1', 'TEXT', 'SORTABLE').equal('OK')
2447    env.expect('ft.add', 'idx', 'doc1', '1.0', 'FIELDS', '1', 'foo1').equal('OK')
2448    env.expect('ft.add', 'idx', 'doc2', '1.0', 'FIELDS', '1', 'foo2').equal('OK')
2449    env.expect('ft.search', 'idx', 'foo*', 'SORTBY', '1', 'ASC').equal([2, 'doc1', ['1', 'foo1'], 'doc2', ['1', 'foo2']])
2450    env.expect('ft.search', 'idx', 'foo*', 'SORTBY', '1', 'DESC').equal([2, 'doc2', ['1', 'foo2'], 'doc1', ['1', 'foo1']])
2451    env.expect('ft.search', 'idx', 'foo*', 'SORTBY', '1', 'bad').error()
2452    env.expect('ft.search', 'idx', 'foo*', 'SORTBY', 'bad', 'bad').error()
2453    env.expect('ft.search', 'idx', 'foo*', 'SORTBY', 'bad').error()
2454    env.expect('ft.search', 'idx', 'foo*', 'SORTBY').error()
2455
2456def testIssue_779(env):
2457    # FT.ADD should return NOADD and not change the doc if value < same_value, but it returns OK and makes the change.
2458    # Note that "greater than" ">" does not have the same bug.
2459
2460    env.cmd('FT.CREATE idx2 SCHEMA ot1 TAG')
2461    env.cmd('FT.ADD idx2 doc2 1.0 FIELDS newf CAT ot1 4001')
2462    env.expect('FT.GET idx2 doc2').equal(["newf", "CAT", "ot1", "4001"])
2463
2464    # NOADD is expected since 4001 is not < 4000, and no updates to the doc2 is expected as a result
2465    env.expect('FT.ADD idx2 doc2 1.0 REPLACE PARTIAL if @ot1<4000 FIELDS newf DOG ot1 4000', 'NOADD')
2466    env.expect('FT.GET idx2 doc2').equal(["newf", "CAT", "ot1", "4001"])
2467
2468    # OK is expected since 4001 < 4002 and the doc2 is updated
2469    env.expect('FT.ADD idx2 doc2 1.0 REPLACE PARTIAL if @ot1<4002 FIELDS newf DOG ot1 4002').equal('OK')
2470    env.expect('FT.GET idx2 doc2').equal(["newf", "DOG", "ot1", "4002"])
2471
2472    # OK is NOT expected since 4002 is not < 4002
2473    # We expect NOADD and doc2 update; however, we get OK and doc2 updated
2474    # After fix, @ot1 implicitly converted to a number, thus we expect NOADD
2475    env.expect('FT.ADD idx2 doc2 1.0 REPLACE PARTIAL if @ot1<4002 FIELDS newf FISH ot1 4002').equal('NOADD')
2476    env.expect('FT.ADD idx2 doc2 1.0 REPLACE PARTIAL if to_number(@ot1)<4002 FIELDS newf FISH ot1 4002').equal('NOADD')
2477    env.expect('FT.ADD idx2 doc2 1.0 REPLACE PARTIAL if @ot1<to_str(4002) FIELDS newf FISH ot1 4002').equal('NOADD')
2478    env.expect('FT.GET idx2 doc2').equal(["newf", "DOG", "ot1", "4002"])
2479
2480    # OK and doc2 update is expected since 4002 < 4003
2481    env.expect('FT.ADD idx2 doc2 1.0 REPLACE PARTIAL if @ot1<4003 FIELDS newf HORSE ot1 4003').equal('OK')
2482    env.expect('FT.GET idx2 doc2').equal(["newf", "HORSE", "ot1", "4003"])
2483
2484    # Expect NOADD since 4003 is not > 4003
2485    env.expect('FT.ADD idx2 doc2 1.0 REPLACE PARTIAL if @ot1>4003 FIELDS newf COW ot1 4003').equal('NOADD')
2486    env.expect('FT.ADD idx2 doc2 1.0 REPLACE PARTIAL if 4003<@ot1 FIELDS newf COW ot1 4003').equal('NOADD')
2487
2488    # Expect OK and doc2 updated since 4003 > 4002
2489    env.expect('FT.ADD idx2 doc2 1.0 REPLACE PARTIAL if @ot1>4002 FIELDS newf PIG ot1 4002').equal('OK')
2490    env.expect('FT.GET idx2 doc2').equal(["newf", "PIG", "ot1", "4002"])
2491
2492    # Syntax errors
2493    env.expect('FT.ADD idx2 doc2 1.0 REPLACE PARTIAL if @ot1<4-002 FIELDS newf DOG ot1 4002').contains('Syntax error')
2494    env.expect('FT.ADD idx2 doc2 1.0 REPLACE PARTIAL if @ot1<to_number(4-002) FIELDS newf DOG ot1 4002').contains('Syntax error')
2495
2496def testUnknownSymbolErrorOnConditionalAdd(env):
2497    env.expect('FT.CREATE idx SCHEMA f1 TAG f2 NUMERIC NOINDEX f3 TAG NOINDEX').ok()
2498    env.expect('ft.add idx doc1 1.0 REPLACE PARTIAL IF @f1<awfwaf FIELDS f1 foo f2 1 f3 boo').ok()
2499    env.expect('ft.add idx doc1 1.0 REPLACE PARTIAL IF @f1<awfwaf FIELDS f1 foo f2 1 f3 boo').error()
2500
2501def testDelIndexExternally(env):
2502    env.skipOnCluster() # todo: remove once fix on coordinator
2503    env.expect('FT.CREATE idx SCHEMA num NUMERIC t TAG g GEO').equal('OK')
2504    env.expect('ft.add idx doc1 1.0 FIELDS num 3 t my_tag g', "1,1").equal('OK')
2505
2506    env.expect('set nm:idx/num 1').equal('OK')
2507    env.expect('ft.add idx doc2 1.0 FIELDS num 3').equal('Could not open numeric index for indexing')
2508
2509    env.expect('set tag:idx/t 1').equal('OK')
2510    env.expect('ft.add idx doc3 1.0 FIELDS t 3').equal('Could not open tag index for indexing')
2511
2512    env.expect('set geo:idx/g 1').equal('OK')
2513    env.expect('ft.add idx doc4 1.0 FIELDS g "1,1"').equal('Could not index geo value')
2514
2515def testWrongResultsReturnedBySkipOptimization(env):
2516    env.expect('FT.CREATE', 'idx', 'SCHEMA', 'f1', 'TEXT', 'f2', 'TEXT').equal('OK')
2517    env.expect('ft.add', 'idx', 'doc1', '1.0', 'FIELDS', 'f1', 'foo', 'f2', 'bar').equal('OK')
2518    env.expect('ft.add', 'idx', 'doc2', '1.0', 'FIELDS', 'f1', 'moo', 'f2', 'foo').equal('OK')
2519    env.expect('ft.search', 'idx', 'foo @f2:moo').debugPrint().equal([0L])
2520
2521def testErrorWithApply(env):
2522    env.expect('FT.CREATE', 'idx', 'SCHEMA', 'test', 'TEXT', 'SORTABLE').equal('OK')
2523    env.expect('FT.ADD', 'idx', 'doc1', '1.0', 'FIELDS', 'test', 'foo bar').equal('OK')
2524    err = env.cmd('FT.AGGREGATE', 'idx', '*', 'LOAD', '1', '@test', 'APPLY', 'split()')[1]
2525    env.assertEqual(str(err[0]), 'Invalid number of arguments for split')
2526
2527def testSummerizeWithAggregateRaiseError(env):
2528    env.expect('FT.CREATE', 'idx', 'SCHEMA', 'test', 'TEXT', 'SORTABLE').equal('OK')
2529    env.expect('ft.add', 'idx', 'doc1', '1.0', 'FIELDS', 'test', 'foo1').equal('OK')
2530    env.expect('ft.add', 'idx', 'doc2', '1.0', 'FIELDS', 'test', 'foo2').equal('OK')
2531    env.expect('ft.aggregate', 'idx', 'foo2', 'SUMMARIZE', 'FIELDS', '1', 'test',
2532               'GROUPBY', '1', '@test', 'REDUCE', 'COUNT', '0').error()
2533
2534def testSummerizeHighlightParseError(env):
2535    env.expect('FT.CREATE', 'idx', 'SCHEMA', 'test', 'TEXT', 'SORTABLE').equal('OK')
2536    env.expect('ft.add', 'idx', 'doc1', '1.0', 'FIELDS', 'test', 'foo1').equal('OK')
2537    env.expect('ft.add', 'idx', 'doc2', '1.0', 'FIELDS', 'test', 'foo2').equal('OK')
2538    env.expect('ft.search', 'idx', 'foo2', 'SUMMARIZE', 'FIELDS', 'WITHSCORES').error()
2539    env.expect('ft.search', 'idx', 'foo2', 'HIGHLIGHT', 'FIELDS', 'WITHSCORES').error()
2540
2541def testCursorBadArgument(env):
2542    env.expect('FT.CREATE', 'idx', 'SCHEMA', 'test', 'TEXT', 'SORTABLE').equal('OK')
2543    env.expect('ft.add', 'idx', 'doc1', '1.0', 'FIELDS', 'test', 'foo1').equal('OK')
2544    env.expect('ft.add', 'idx', 'doc2', '1.0', 'FIELDS', 'test', 'foo2').equal('OK')
2545    env.expect('ft.aggregate', 'idx', '*',
2546               'GROUPBY', '1', '@test', 'REDUCE', 'COUNT', '0',
2547               'WITHCURSOR', 'COUNT', 'BAD').error()
2548
2549def testLimitBadArgument(env):
2550    env.expect('FT.CREATE', 'idx', 'SCHEMA', 'test', 'TEXT', 'SORTABLE').equal('OK')
2551    env.expect('ft.add', 'idx', 'doc1', '1.0', 'FIELDS', 'test', 'foo1').equal('OK')
2552    env.expect('ft.add', 'idx', 'doc2', '1.0', 'FIELDS', 'test', 'foo2').equal('OK')
2553    env.expect('ft.search', 'idx', '*', 'LIMIT', '1').error()
2554
2555def testOnTimeoutBadArgument(env):
2556    env.expect('FT.CREATE', 'idx', 'SCHEMA', 'test', 'TEXT', 'SORTABLE').equal('OK')
2557    env.expect('ft.add', 'idx', 'doc1', '1.0', 'FIELDS', 'test', 'foo1').equal('OK')
2558    env.expect('ft.add', 'idx', 'doc2', '1.0', 'FIELDS', 'test', 'foo2').equal('OK')
2559    env.expect('ft.search', 'idx', '*', 'ON_TIMEOUT', 'bad').error()
2560
2561def testAggregateSortByWrongArgument(env):
2562    env.expect('FT.CREATE', 'idx', 'SCHEMA', 'test', 'TEXT', 'SORTABLE').equal('OK')
2563    env.expect('ft.add', 'idx', 'doc1', '1.0', 'FIELDS', 'test', 'foo1').equal('OK')
2564    env.expect('ft.add', 'idx', 'doc2', '1.0', 'FIELDS', 'test', 'foo2').equal('OK')
2565    env.expect('ft.aggregate', 'idx', '*', 'SORTBY', 'bad').error()
2566
2567def testAggregateSortByMaxNumberOfFields(env):
2568    env.expect('FT.CREATE', 'idx', 'SCHEMA',
2569               'test1', 'TEXT', 'SORTABLE',
2570               'test2', 'TEXT', 'SORTABLE',
2571               'test3', 'TEXT', 'SORTABLE',
2572               'test4', 'TEXT', 'SORTABLE',
2573               'test5', 'TEXT', 'SORTABLE',
2574               'test6', 'TEXT', 'SORTABLE',
2575               'test7', 'TEXT', 'SORTABLE',
2576               'test8', 'TEXT', 'SORTABLE',
2577               'test9', 'TEXT', 'SORTABLE'
2578               ).equal('OK')
2579    env.expect('ft.add', 'idx', 'doc1', '1.0', 'FIELDS', 'test', 'foo1').equal('OK')
2580    env.expect('ft.add', 'idx', 'doc2', '1.0', 'FIELDS', 'test', 'foo2').equal('OK')
2581    env.expect('ft.aggregate', 'idx', '*', 'SORTBY', '9', *['@test%d' % (i + 1) for i in range(9)]).error()
2582    args = ['@test%d' % (i + 1) for i in range(8)] + ['bad']
2583    env.expect('ft.aggregate', 'idx', '*', 'SORTBY', '9', *args).error()
2584    args = ['@test%d' % (i + 1) for i in range(8)] + ['ASC', 'MAX', 'bad']
2585    env.expect('ft.aggregate', 'idx', '*', 'SORTBY', '9', *args).error()
2586    args = ['@test%d' % (i + 1) for i in range(8)] + ['ASC', 'MAX']
2587    env.expect('ft.aggregate', 'idx', '*', 'SORTBY', '9', *args).error()
2588
2589def testNumericFilterError(env):
2590    env.expect('FT.CREATE', 'idx', 'SCHEMA', 'test', 'NUMERIC', 'SORTABLE').equal('OK')
2591    env.expect('ft.add', 'idx', 'doc1', '1.0', 'FIELDS', 'test', '1').equal('OK')
2592    env.expect('ft.search', 'idx', '*', 'FILTER', 'test', 'bad', '2').error()
2593    env.expect('ft.search', 'idx', '*', 'FILTER', 'test', '0', 'bad').error()
2594    env.expect('ft.search', 'idx', '*', 'FILTER', 'test', '0').error()
2595    env.expect('ft.search', 'idx', '*', 'FILTER', 'test', 'bad').error()
2596    env.expect('ft.search', 'idx', '*', 'FILTER', 'test', '0', '2', 'FILTER', 'test', '0', 'bla').error()
2597
2598def testGeoFilterError(env):
2599    env.expect('FT.CREATE', 'idx', 'SCHEMA', 'test', 'NUMERIC', 'SORTABLE').equal('OK')
2600    env.expect('ft.add', 'idx', 'doc1', '1.0', 'FIELDS', 'test', '1').equal('OK')
2601    env.expect('ft.search', 'idx', '*', 'GEOFILTER', 'test', '1').error()
2602    env.expect('ft.search', 'idx', '*', 'GEOFILTER', 'test', 'bad' , '2', '3', 'km').error()
2603    env.expect('ft.search', 'idx', '*', 'GEOFILTER', 'test', '1' , 'bad', '3', 'km').error()
2604    env.expect('ft.search', 'idx', '*', 'GEOFILTER', 'test', '1' , '2', 'bad', 'km').error()
2605    env.expect('ft.search', 'idx', '*', 'GEOFILTER', 'test', '1' , '2', '3', 'bad').error()
2606
2607def testReducerError(env):
2608    env.expect('FT.CREATE', 'idx', 'SCHEMA', 'test', 'NUMERIC', 'SORTABLE').equal('OK')
2609    env.expect('ft.add', 'idx', 'doc1', '1.0', 'FIELDS', 'test', '1').equal('OK')
2610    env.expect('ft.aggregate', 'idx', '*', 'GROUPBY', '1', '@test', 'REDUCE', 'COUNT', 'bad').error()
2611    env.expect('ft.aggregate', 'idx', '*', 'GROUPBY', '1', '@test', 'REDUCE', 'COUNT', '0', 'as').error()
2612
2613def testGroupbyError(env):
2614    env.expect('FT.CREATE', 'idx', 'SCHEMA', 'test', 'NUMERIC', 'SORTABLE').equal('OK')
2615    env.expect('ft.add', 'idx', 'doc1', '1.0', 'FIELDS', 'test', '1').equal('OK')
2616    env.expect('ft.aggregate', 'idx', '*', 'GROUPBY', '1', '@test', 'REDUCE').error()
2617    if not env.isCluster(): # todo: remove once fix on coordinator
2618        env.expect('ft.aggregate', 'idx', '*', 'GROUPBY', '1', '@test1').error()
2619    env.expect('ft.aggregate', 'idx', '*', 'GROUPBY', '1', '@test', 'REDUCE', 'bad', '0').error()
2620    if not env.isCluster(): # todo: remove once fix on coordinator
2621        env.expect('ft.aggregate', 'idx', '*', 'GROUPBY', '1', '@test', 'REDUCE', 'SUM', '1', '@test1').error()
2622
2623def testGroupbyWithSort(env):
2624    env.expect('FT.CREATE', 'idx', 'SCHEMA', 'test', 'NUMERIC', 'SORTABLE').equal('OK')
2625    env.expect('ft.add', 'idx', 'doc1', '1.0', 'FIELDS', 'test', '1').equal('OK')
2626    env.expect('ft.add', 'idx', 'doc2', '1.0', 'FIELDS', 'test', '1').equal('OK')
2627    env.expect('ft.add', 'idx', 'doc3', '1.0', 'FIELDS', 'test', '2').equal('OK')
2628    env.expect('ft.aggregate', 'idx', '*', 'SORTBY', '2', '@test', 'ASC',
2629               'GROUPBY', '1', '@test', 'REDUCE', 'COUNT', '0', 'as', 'count').equal([2L, ['test', '2', 'count', '1'], ['test', '1', 'count', '2']])
2630
2631def testApplyError(env):
2632    env.expect('FT.CREATE', 'idx', 'SCHEMA', 'test', 'TEXT', 'SORTABLE').equal('OK')
2633    env.expect('ft.add', 'idx', 'doc1', '1.0', 'FIELDS', 'test', 'foo').equal('OK')
2634    env.expect('ft.aggregate', 'idx', '*', 'APPLY', 'split(@test)', 'as').error()
2635
2636def testLoadError(env):
2637    env.expect('FT.CREATE', 'idx', 'SCHEMA', 'test', 'TEXT', 'SORTABLE').equal('OK')
2638    env.expect('ft.add', 'idx', 'doc1', '1.0', 'FIELDS', 'test', 'foo').equal('OK')
2639    env.expect('ft.aggregate', 'idx', '*', 'LOAD', 'bad').error()
2640    env.expect('ft.aggregate', 'idx', '*', 'LOAD', 'bad', 'test').error()
2641    env.expect('ft.aggregate', 'idx', '*', 'LOAD', '2', 'test').error()
2642    env.expect('ft.aggregate', 'idx', '*', 'LOAD', '2', '@test').error()
2643
2644def testMissingArgsError(env):
2645    env.expect('FT.CREATE', 'idx', 'SCHEMA', 'test', 'TEXT', 'SORTABLE').equal('OK')
2646    env.expect('ft.add', 'idx', 'doc1', '1.0', 'FIELDS', 'test', 'foo').equal('OK')
2647    env.expect('ft.aggregate', 'idx').error()
2648
2649def testUnexistsScorer(env):
2650    env.expect('FT.CREATE', 'idx', 'SCHEMA', 'test', 'TEXT', 'SORTABLE').equal('OK')
2651    env.expect('ft.add', 'idx', 'doc1', '1.0', 'FIELDS', 'test', 'foo').equal('OK')
2652    env.expect('ft.search', 'idx', '*', 'SCORER', 'bad').error()
2653
2654def testHighlightWithUnknowsProperty(env):
2655    env.expect('FT.CREATE', 'idx', 'SCHEMA', 'test', 'TEXT').equal('OK')
2656    env.expect('ft.add', 'idx', 'doc1', '1.0', 'FIELDS', 'test', 'foo').equal('OK')
2657    env.expect('ft.aggregate', 'idx', '*', 'HIGHLIGHT', 'FIELDS', '1', 'test1').error()
2658
2659def testBadFilterExpression(env):
2660    env.expect('FT.CREATE', 'idx', 'SCHEMA', 'test', 'TEXT').equal('OK')
2661    env.expect('ft.add', 'idx', 'doc1', '1.0', 'FIELDS', 'test', 'foo').equal('OK')
2662    env.expect('ft.aggregate', 'idx', '*', 'LOAD', '1', '@test', 'FILTER', 'blabla').error()
2663    if not env.isCluster(): # todo: remove once fix on coordinator
2664        env.expect('ft.aggregate', 'idx', '*', 'LOAD', '1', '@test', 'FILTER', '@test1 > 1').error()
2665
2666def testWithSortKeysOnNoneSortableValue(env):
2667    env.expect('FT.CREATE', 'idx', 'SCHEMA', 'test', 'TEXT').equal('OK')
2668    env.expect('ft.add', 'idx', 'doc1', '1.0', 'FIELDS', 'test', 'foo').equal('OK')
2669    env.expect('ft.search', 'idx', '*', 'WITHSORTKEYS', 'SORTBY', 'test').equal([1L, 'doc1', '$foo', ['test', 'foo']])
2670
2671def testWithWithRawIds(env):
2672    env.skipOnCluster() # todo: remove once fix on coordinator
2673    env.expect('FT.CREATE', 'idx', 'SCHEMA', 'test', 'TEXT').equal('OK')
2674    env.expect('ft.add', 'idx', 'doc1', '1.0', 'FIELDS', 'test', 'foo').equal('OK')
2675    env.expect('ft.search', 'idx', '*', 'WITHRAWIDS').equal([1L, 'doc1', 1L, ['test', 'foo']])
2676
2677def testUnkownIndex(env):
2678    env.skipOnCluster() # todo: remove once fix on coordinator
2679    env.expect('ft.aggregate').error()
2680    env.expect('ft.aggregate', 'idx', '*').error()
2681    env.expect('ft.aggregate', 'idx', '*', 'WITHCURSOR').error()
2682
2683def testExplainError(env):
2684    env.expect('FT.CREATE', 'idx', 'SCHEMA', 'test', 'TEXT').equal('OK')
2685    env.expect('FT.EXPLAIN', 'idx', '(').error()
2686
2687def testBadCursor(env):
2688    env.expect('FT.CURSOR', 'READ', 'idx').error()
2689    env.expect('FT.CURSOR', 'READ', 'idx', '1111').error()
2690    env.expect('FT.CURSOR', 'READ', 'idx', 'bad').error()
2691    env.expect('FT.CURSOR', 'DROP', 'idx', '1111').error()
2692    env.expect('FT.CURSOR', 'bad', 'idx', '1111').error()
2693
2694def testGroupByWithApplyError(env):
2695    env.expect('FT.CREATE', 'idx', 'SCHEMA', 'test', 'TEXT').equal('OK')
2696    env.expect('ft.add', 'idx', 'doc1', '1.0', 'FIELDS', 'test', 'foo').equal('OK')
2697    err = env.cmd('FT.AGGREGATE', 'idx', '*', 'APPLY', 'split()', 'GROUPBY', '1', '@test', 'REDUCE', 'COUNT', '0', 'AS', 'count')[1]
2698    assertEqualIgnoreCluster(env, str(err[0]), 'Invalid number of arguments for split')
2699
2700def testSubStrErrors(env):
2701    env.expect('FT.CREATE', 'idx', 'SCHEMA', 'test', 'TEXT').equal('OK')
2702    env.expect('ft.add', 'idx', 'doc1', '1.0', 'FIELDS', 'test', 'foo').equal('OK')
2703
2704    err = env.cmd('ft.aggregate', 'idx', '*', 'LOAD', '1', '@test', 'APPLY', 'matched_terms()', 'as', 'a', 'APPLY', 'substr(@a,0,4)')[1]
2705    assertEqualIgnoreCluster(env, type(err[0]), redis.exceptions.ResponseError)
2706
2707    env.cmd('ft.aggregate', 'idx', '*', 'LOAD', '1', '@test2', 'APPLY', 'substr("test",3,-2)', 'as', 'a')
2708    env.cmd('ft.aggregate', 'idx', '*', 'LOAD', '1', '@test2', 'APPLY', 'substr("test",3,1000)', 'as', 'a')
2709    env.cmd('ft.aggregate', 'idx', '*', 'LOAD', '1', '@test2', 'APPLY', 'substr("test",-1,2)', 'as', 'a')
2710    env.cmd('ft.aggregate', 'idx', '*', 'LOAD', '1', '@test2', 'APPLY', 'substr("test")', 'as', 'a')
2711    env.cmd('ft.aggregate', 'idx', '*', 'LOAD', '1', '@test2', 'APPLY', 'substr(1)', 'as', 'a')
2712    env.cmd('ft.aggregate', 'idx', '*', 'LOAD', '1', '@test2', 'APPLY', 'substr("test", "test")', 'as', 'a')
2713    env.cmd('ft.aggregate', 'idx', '*', 'LOAD', '1', '@test2', 'APPLY', 'substr("test", "test", "test")', 'as', 'a')
2714    env.cmd('ft.aggregate', 'idx', '*', 'LOAD', '1', '@test2', 'APPLY', 'substr("test", "-1", "-1")', 'as', 'a')
2715    env.assertTrue(env.isUp())
2716
2717def testToUpperLower(env):
2718    env.expect('FT.CREATE', 'idx', 'SCHEMA', 'test', 'TEXT').equal('OK')
2719    env.expect('ft.add', 'idx', 'doc1', '1.0', 'FIELDS', 'test', 'foo').equal('OK')
2720    env.expect('ft.aggregate', 'idx', '*', 'LOAD', '1', '@test', 'APPLY', 'lower(@test)', 'as', 'a').equal([1L, ['test', 'foo', 'a', 'foo']])
2721    env.expect('ft.aggregate', 'idx', '*', 'LOAD', '1', '@test', 'APPLY', 'lower("FOO")', 'as', 'a').equal([1L, ['test', 'foo', 'a', 'foo']])
2722    env.expect('ft.aggregate', 'idx', '*', 'LOAD', '1', '@test', 'APPLY', 'upper(@test)', 'as', 'a').equal([1L, ['test', 'foo', 'a', 'FOO']])
2723    env.expect('ft.aggregate', 'idx', '*', 'LOAD', '1', '@test', 'APPLY', 'upper("foo")', 'as', 'a').equal([1L, ['test', 'foo', 'a', 'FOO']])
2724
2725    err = env.cmd('ft.aggregate', 'idx', '*', 'LOAD', '1', '@test', 'APPLY', 'upper()', 'as', 'a')[1]
2726    assertEqualIgnoreCluster(env, type(err[0]), redis.exceptions.ResponseError)
2727    err = env.cmd('ft.aggregate', 'idx', '*', 'LOAD', '1', '@test', 'APPLY', 'lower()', 'as', 'a')[1]
2728    assertEqualIgnoreCluster(env, type(err[0]), redis.exceptions.ResponseError)
2729
2730    env.expect('ft.aggregate', 'idx', '*', 'LOAD', '1', '@test', 'APPLY', 'upper(1)', 'as', 'a').equal([1L, ['test', 'foo', 'a', None]])
2731    env.expect('ft.aggregate', 'idx', '*', 'LOAD', '1', '@test', 'APPLY', 'lower(1)', 'as', 'a').equal([1L, ['test', 'foo', 'a', None]])
2732
2733    assertEqualIgnoreCluster(env, type(err[0]), redis.exceptions.ResponseError)
2734    err = env.cmd('ft.aggregate', 'idx', '*', 'LOAD', '1', '@test', 'APPLY', 'upper(1,2)', 'as', 'a')[1]
2735    assertEqualIgnoreCluster(env, type(err[0]), redis.exceptions.ResponseError)
2736    err = env.cmd('ft.aggregate', 'idx', '*', 'LOAD', '1', '@test', 'APPLY', 'lower(1,2)', 'as', 'a')[1]
2737    assertEqualIgnoreCluster(env, type(err[0]), redis.exceptions.ResponseError)
2738
2739def testMatchedTerms(env):
2740    env.expect('FT.CREATE', 'idx', 'SCHEMA', 'test', 'TEXT').equal('OK')
2741    env.expect('ft.add', 'idx', 'doc1', '1.0', 'FIELDS', 'test', 'foo').equal('OK')
2742    env.expect('ft.aggregate', 'idx', '*', 'LOAD', '1', '@test', 'APPLY', 'matched_terms()', 'as', 'a').equal([1L, ['test', 'foo', 'a', None]])
2743    env.expect('ft.aggregate', 'idx', 'foo', 'LOAD', '1', '@test', 'APPLY', 'matched_terms()', 'as', 'a').equal([1L, ['test', 'foo', 'a', ['foo']]])
2744    env.expect('ft.aggregate', 'idx', 'foo', 'LOAD', '1', '@test', 'APPLY', 'matched_terms(100)', 'as', 'a').equal([1L, ['test', 'foo', 'a', ['foo']]])
2745    env.expect('ft.aggregate', 'idx', 'foo', 'LOAD', '1', '@test', 'APPLY', 'matched_terms(-100)', 'as', 'a').equal([1L, ['test', 'foo', 'a', ['foo']]])
2746    env.expect('ft.aggregate', 'idx', 'foo', 'LOAD', '1', '@test', 'APPLY', 'matched_terms("test")', 'as', 'a').equal([1L, ['test', 'foo', 'a', ['foo']]])
2747
2748def testStrFormatError(env):
2749    env.expect('FT.CREATE', 'idx', 'SCHEMA', 'test', 'TEXT').equal('OK')
2750    env.expect('ft.add', 'idx', 'doc1', '1.0', 'FIELDS', 'test', 'foo').equal('OK')
2751    err = env.cmd('ft.aggregate', 'idx', 'foo', 'LOAD', '1', '@test', 'APPLY', 'format()', 'as', 'a')[1]
2752    assertEqualIgnoreCluster(env, type(err[0]), redis.exceptions.ResponseError)
2753
2754    err = env.cmd('ft.aggregate', 'idx', 'foo', 'LOAD', '1', '@test', 'APPLY', 'format("%s")', 'as', 'a')[1]
2755    assertEqualIgnoreCluster(env, type(err[0]), redis.exceptions.ResponseError)
2756
2757    err = env.cmd('ft.aggregate', 'idx', 'foo', 'LOAD', '1', '@test', 'APPLY', 'format("%", "test")', 'as', 'a')[1]
2758    assertEqualIgnoreCluster(env, type(err[0]), redis.exceptions.ResponseError)
2759
2760    err = env.cmd('ft.aggregate', 'idx', 'foo', 'LOAD', '1', '@test', 'APPLY', 'format("%b", "test")', 'as', 'a')[1]
2761    assertEqualIgnoreCluster(env, type(err[0]), redis.exceptions.ResponseError)
2762
2763    err = env.cmd('ft.aggregate', 'idx', 'foo', 'LOAD', '1', '@test', 'APPLY', 'format(5)', 'as', 'a')[1]
2764    assertEqualIgnoreCluster(env, type(err[0]), redis.exceptions.ResponseError)
2765
2766    env.expect('ft.aggregate', 'idx', 'foo', 'LOAD', '1', '@test', 'APPLY', 'upper(1)', 'as', 'b', 'APPLY', 'format("%s", @b)', 'as', 'a').equal([1L, ['test', 'foo', 'b', None, 'a', '(null)']])
2767
2768    # working example
2769    env.expect('ft.aggregate', 'idx', 'foo', 'APPLY', 'format("%%s-test", "test")', 'as', 'a').equal([1L, ['a', '%s-test']])
2770    env.expect('ft.aggregate', 'idx', 'foo', 'APPLY', 'format("%s-test", "test")', 'as', 'a').equal([1L, ['a', 'test-test']])
2771
2772def testTimeFormatError(env):
2773    env.expect('FT.CREATE', 'idx', 'SCHEMA', 'test', 'NUMERIC').equal('OK')
2774    env.expect('ft.add', 'idx', 'doc1', '1.0', 'FIELDS', 'test', '12234556').equal('OK')
2775
2776    err = env.cmd('ft.aggregate', 'idx', '@test:[0..inf]', 'LOAD', '1', '@test', 'APPLY', 'timefmt()', 'as', 'a')[1]
2777    assertEqualIgnoreCluster(env, type(err[0]), redis.exceptions.ResponseError)
2778
2779    if not env.isCluster(): # todo: remove once fix on coordinator
2780        env.expect('ft.aggregate', 'idx', '@test:[0..inf]', 'LOAD', '1', '@test', 'APPLY', 'timefmt(@test1)', 'as', 'a').error()
2781
2782    env.cmd('ft.aggregate', 'idx', '@test:[0..inf]', 'LOAD', '1', '@test', 'APPLY', 'timefmt(@test)', 'as', 'a')
2783
2784    env.assertTrue(env.isUp())
2785
2786    err = env.cmd('ft.aggregate', 'idx', '@test:[0..inf]', 'LOAD', '1', '@test', 'APPLY', 'timefmt(@test, 4)', 'as', 'a')[1]
2787    assertEqualIgnoreCluster(env, type(err[0]), redis.exceptions.ResponseError)
2788
2789    env.expect('ft.aggregate', 'idx', '@test:[0..inf]', 'LOAD', '1', '@test', 'APPLY', 'timefmt("awfawf")', 'as', 'a').equal([1L, ['test', '12234556', 'a', None]])
2790
2791    env.expect('ft.aggregate', 'idx', '@test:[0..inf]', 'LOAD', '1', '@test', 'APPLY', 'timefmt(235325153152356426246246246254)', 'as', 'a').equal([1L, ['test', '12234556', 'a', None]])
2792
2793    env.expect('ft.aggregate', 'idx', '@test:[0..inf]', 'LOAD', '1', '@test', 'APPLY', 'timefmt(@test, "%s")' % ('d' * 2048), 'as', 'a').equal([1L, ['test', '12234556', 'a', None]])
2794
2795    env.expect('ft.aggregate', 'idx', '@test:[0..inf]', 'LOAD', '1', '@test', 'APPLY', 'hour("not_number")', 'as', 'a').equal([1L, ['test', '12234556', 'a', None]])
2796    env.expect('ft.aggregate', 'idx', '@test:[0..inf]', 'LOAD', '1', '@test', 'APPLY', 'minute("not_number")', 'as', 'a').equal([1L, ['test', '12234556', 'a', None]])
2797    env.expect('ft.aggregate', 'idx', '@test:[0..inf]', 'LOAD', '1', '@test', 'APPLY', 'day("not_number")', 'as', 'a').equal([1L, ['test', '12234556', 'a', None]])
2798    env.expect('ft.aggregate', 'idx', '@test:[0..inf]', 'LOAD', '1', '@test', 'APPLY', 'month("not_number")', 'as', 'a').equal([1L, ['test', '12234556', 'a', None]])
2799    env.expect('ft.aggregate', 'idx', '@test:[0..inf]', 'LOAD', '1', '@test', 'APPLY', 'dayofweek("not_number")', 'as', 'a').equal([1L, ['test', '12234556', 'a', None]])
2800    env.expect('ft.aggregate', 'idx', '@test:[0..inf]', 'LOAD', '1', '@test', 'APPLY', 'dayofmonth("not_number")', 'as', 'a').equal([1L, ['test', '12234556', 'a', None]])
2801    env.expect('ft.aggregate', 'idx', '@test:[0..inf]', 'LOAD', '1', '@test', 'APPLY', 'dayofyear("not_number")', 'as', 'a').equal([1L, ['test', '12234556', 'a', None]])
2802    env.expect('ft.aggregate', 'idx', '@test:[0..inf]', 'LOAD', '1', '@test', 'APPLY', 'year("not_number")', 'as', 'a').equal([1L, ['test', '12234556', 'a', None]])
2803    env.expect('ft.aggregate', 'idx', '@test:[0..inf]', 'LOAD', '1', '@test', 'APPLY', 'monthofyear("not_number")', 'as', 'a').equal([1L, ['test', '12234556', 'a', None]])
2804
2805def testMonthOfYear(env):
2806    env.expect('FT.CREATE', 'idx', 'SCHEMA', 'test', 'NUMERIC').equal('OK')
2807    env.expect('ft.add', 'idx', 'doc1', '1.0', 'FIELDS', 'test', '12234556').equal('OK')
2808
2809    env.expect('ft.aggregate', 'idx', '@test:[0..inf]', 'LOAD', '1', '@test', 'APPLY', 'monthofyear(@test)', 'as', 'a').equal([1L, ['test', '12234556', 'a', '4']])
2810
2811    err = env.cmd('ft.aggregate', 'idx', '@test:[0..inf]', 'LOAD', '1', '@test', 'APPLY', 'monthofyear(@test, 112)', 'as', 'a')[1]
2812    assertEqualIgnoreCluster(env, type(err[0]), redis.exceptions.ResponseError)
2813
2814    err = env.cmd('ft.aggregate', 'idx', '@test:[0..inf]', 'LOAD', '1', '@test', 'APPLY', 'monthofyear()', 'as', 'a')[1]
2815    assertEqualIgnoreCluster(env, type(err[0]), redis.exceptions.ResponseError)
2816
2817    env.expect('ft.aggregate', 'idx', '@test:[0..inf]', 'LOAD', '1', '@test', 'APPLY', 'monthofyear("bad")', 'as', 'a').equal([1L, ['test', '12234556', 'a', None]])
2818
2819def testParseTimeErrors(env):
2820    env.expect('FT.CREATE', 'idx', 'SCHEMA', 'test', 'NUMERIC').equal('OK')
2821    env.expect('ft.add', 'idx', 'doc1', '1.0', 'FIELDS', 'test', '12234556').equal('OK')
2822
2823    err = env.cmd('ft.aggregate', 'idx', '@test:[0..inf]', 'LOAD', '1', '@test', 'APPLY', 'parse_time()', 'as', 'a')[1]
2824    assertEqualIgnoreCluster(env, type(err[0]), redis.exceptions.ResponseError)
2825
2826    err = env.cmd('ft.aggregate', 'idx', '@test:[0..inf]', 'LOAD', '1', '@test', 'APPLY', 'parse_time(11)', 'as', 'a')[1]
2827    assertEqualIgnoreCluster(env, type(err[0]), redis.exceptions.ResponseError)
2828
2829    err = env.cmd('ft.aggregate', 'idx', '@test:[0..inf]', 'LOAD', '1', '@test', 'APPLY', 'parse_time(11,22)', 'as', 'a')[1]
2830    assertEqualIgnoreCluster(env, type(err[0]), redis.exceptions.ResponseError)
2831
2832    env.expect('ft.aggregate', 'idx', '@test:[0..inf]', 'LOAD', '1', '@test', 'APPLY', 'parse_time("%s", "%s")' % ('d' * 2048, 'd' * 2048), 'as', 'a').equal([1L, ['test', '12234556', 'a', None]])
2833
2834    env.expect('ft.aggregate', 'idx', '@test:[0..inf]', 'LOAD', '1', '@test', 'APPLY', 'parse_time("test", "%s")' % ('d' * 2048), 'as', 'a').equal([1L, ['test', '12234556', 'a', None]])
2835
2836def testMathFunctions(env):
2837    env.expect('FT.CREATE', 'idx', 'SCHEMA', 'test', 'NUMERIC').equal('OK')
2838    env.expect('ft.add', 'idx', 'doc1', '1.0', 'FIELDS', 'test', '12234556').equal('OK')
2839
2840    env.expect('ft.aggregate', 'idx', '@test:[0..inf]', 'LOAD', '1', '@test', 'APPLY', 'exp(@test)', 'as', 'a').equal([1L, ['test', '12234556', 'a', 'inf']])
2841    env.expect('ft.aggregate', 'idx', '@test:[0..inf]', 'LOAD', '1', '@test', 'APPLY', 'ceil(@test)', 'as', 'a').equal([1L, ['test', '12234556', 'a', '12234556']])
2842
2843def testErrorOnOpperation(env):
2844    env.expect('FT.CREATE', 'idx', 'SCHEMA', 'test', 'NUMERIC').equal('OK')
2845    env.expect('ft.add', 'idx', 'doc1', '1.0', 'FIELDS', 'test', '12234556').equal('OK')
2846
2847    err = env.cmd('ft.aggregate', 'idx', '@test:[0..inf]', 'LOAD', '1', '@test', 'APPLY', '1 + split()', 'as', 'a')[1]
2848    assertEqualIgnoreCluster(env, type(err[0]), redis.exceptions.ResponseError)
2849
2850    err = env.cmd('ft.aggregate', 'idx', '@test:[0..inf]', 'LOAD', '1', '@test', 'APPLY', 'split() + 1', 'as', 'a')[1]
2851    assertEqualIgnoreCluster(env, type(err[0]), redis.exceptions.ResponseError)
2852
2853    err = env.cmd('ft.aggregate', 'idx', '@test:[0..inf]', 'LOAD', '1', '@test', 'APPLY', '"bad" + "bad"', 'as', 'a')[1]
2854    assertEqualIgnoreCluster(env, type(err[0]), redis.exceptions.ResponseError)
2855
2856    err = env.cmd('ft.aggregate', 'idx', '@test:[0..inf]', 'LOAD', '1', '@test', 'APPLY', 'split("bad" + "bad")', 'as', 'a')[1]
2857    assertEqualIgnoreCluster(env, type(err[0]), redis.exceptions.ResponseError)
2858
2859    err = env.cmd('ft.aggregate', 'idx', '@test:[0..inf]', 'LOAD', '1', '@test', 'APPLY', '!(split("bad" + "bad"))', 'as', 'a')[1]
2860    assertEqualIgnoreCluster(env, type(err[0]), redis.exceptions.ResponseError)
2861
2862    err = env.cmd('ft.aggregate', 'idx', '@test:[0..inf]', 'APPLY', '!@test', 'as', 'a')[1]
2863    assertEqualIgnoreCluster(env, type(err[0]), redis.exceptions.ResponseError)
2864
2865
2866def testSortkeyUnsortable(env):
2867    env.cmd('ft.create', 'idx', 'schema', 'test', 'text')
2868    env.cmd('ft.add', 'idx', 'doc1', 1, 'fields', 'test', 'foo')
2869    rv = env.cmd('ft.aggregate', 'idx', 'foo', 'withsortkeys',
2870        'load', '1', '@test',
2871        'sortby', '1', '@test')
2872    env.assertEqual([1, '$foo', ['test', 'foo']], rv)
2873
2874
2875def testIssue919(env):
2876    # This only works if the missing field has a lower sortable index
2877    # than the present field..
2878    env.cmd('ft.create', 'idx', 'schema', 't1', 'text', 'sortable', 'n1', 'numeric', 'sortable')
2879    env.cmd('ft.add', 'idx', 'doc1', 1, 'fields', 'n1', 42)
2880    rv = env.cmd('ft.search', 'idx', '*', 'sortby', 't1', 'desc')
2881    env.assertEqual([1L, 'doc1', ['n1', '42']], rv)
2882
2883
2884def testIssue1074(env):
2885    # Ensure that sortable fields are returned in their string form from the
2886    # document
2887    env.cmd('ft.create', 'idx', 'schema', 't1', 'text', 'n1', 'numeric', 'sortable')
2888    env.cmd('ft.add', 'idx', 'doc1', 1, 'fields', 't1', 'hello', 'n1', 1581011976800)
2889    rv = env.cmd('ft.search', 'idx', '*', 'sortby', 'n1')
2890    env.assertEqual([1L, 'doc1', ['n1', '1581011976800', 't1', 'hello']], rv)
2891
2892def testIssue1085(env):
2893    env.skipOnCluster()
2894    env.cmd('FT.CREATE issue1085 SCHEMA foo TEXT SORTABLE bar NUMERIC SORTABLE')
2895    for i in range(1, 10):
2896        env.cmd('FT.ADD issue1085 document_%d 1 REPLACE FIELDS foo foo%d bar %d' % (i, i, i))
2897    env.expect('FT.SEARCH', 'issue1085', '@bar:[8 8]').equal([1L, 'document_8', ['foo', 'foo8', 'bar', '8']])
2898
2899    for i in range(1, 10):
2900        env.cmd('FT.ADD issue1085 document_8 1 REPLACE FIELDS foo foo8 bar 8')
2901
2902    env.expect('ft.debug GC_FORCEINVOKE issue1085').equal('DONE')
2903
2904    env.expect('FT.SEARCH', 'issue1085', '@bar:[8 8]').equal([1, 'document_8', ['foo', 'foo8', 'bar', '8']])
2905
2906
2907def grouper(iterable, n, fillvalue=None):
2908    "Collect data into fixed-length chunks or blocks"
2909    from itertools import izip_longest
2910    # grouper('ABCDEFG', 3, 'x') --> ABC DEF Gxx
2911    args = [iter(iterable)] * n
2912    return izip_longest(fillvalue=fillvalue, *args)
2913
2914
2915def to_dict(r):
2916    return {r[i]: r[i + 1] for i in range(0, len(r), 2)}
2917
2918def testOptimize(env):
2919    env.skipOnCluster()
2920    env.cmd('ft.create', 'idx', 'SCHEMA', 'test', 'TEXT', 'SORTABLE')
2921    env.cmd('FT.ADD', 'idx', 'doc1', '1.0', 'FIELDS', 'test', 'foo')
2922    env.assertEqual(0, env.cmd('FT.OPTIMIZE', 'idx'))
2923    with env.assertResponseError():
2924        env.assertOk(env.cmd('FT.OPTIMIZE', 'idx', '666'))
2925    env.expect('FT.OPTIMIZE', 'fake_idx').error()
2926
2927def testInfoError(env):
2928    env.expect('ft.info', 'no_idx').error()
2929
2930def testSetPayload(env):
2931    env.skipOnCluster()
2932    env.expect('flushall')
2933    env.expect('ft.create idx schema name text').equal('OK')
2934    env.expect('ft.add idx hotel 1.0 fields name hilton').equal('OK')
2935    env.expect('FT.SETPAYLOAD idx hotel payload').equal('OK')
2936    env.expect('FT.SETPAYLOAD idx hotel payload').equal('OK')
2937    env.expect('FT.SETPAYLOAD idx fake_hotel').error()          \
2938            .contains("wrong number of arguments for 'FT.SETPAYLOAD' command")
2939    env.expect('FT.SETPAYLOAD fake_idx hotel payload').error().contains('Unknown Index name')
2940    env.expect('FT.SETPAYLOAD idx fake_hotel payload').error().contains('Document not in index')
2941
2942def testIndexNotRemovedFromCursorListAfterRecreated(env):
2943    env.expect('FT.CREATE idx SCHEMA f1 TEXT').ok()
2944    env.expect('FT.AGGREGATE idx * WITHCURSOR').equal([[0], 0])
2945    env.expect('FT.CREATE idx SCHEMA f1 TEXT').error()
2946    env.expect('FT.AGGREGATE idx * WITHCURSOR').equal([[0], 0])
2947
2948def testSearchNotExistsTagValue(env):
2949    # this test basically make sure we are not leaking
2950    env.expect('FT.CREATE idx SCHEMA t TAG SORTABLE').ok()
2951    env.expect('FT.SEARCH idx @t:{val}').equal([0])
2952
2953def testUnseportedSortableTypeErrorOnTags(env):
2954    env.skipOnCluster()
2955    env.expect('FT.CREATE idx SCHEMA f1 TEXT SORTABLE f2 NUMERIC SORTABLE NOINDEX f3 TAG SORTABLE NOINDEX f4 TEXT SORTABLE NOINDEX').ok()
2956    env.expect('FT.ADD idx doc1 1.0 FIELDS f1 foo1 f2 1 f3 foo1 f4 foo1').ok()
2957    env.expect('FT.ADD idx doc1 1.0 REPLACE PARTIAL FIELDS f2 2 f3 foo2 f4 foo2').ok()
2958    env.expect('HGETALL doc1').equal(['f1', 'foo1', 'f2', '2', 'f3', 'foo2', 'f4', 'foo2'])
2959    env.expect('FT.SEARCH idx *').equal([1L, 'doc1', ['f1', 'foo1', 'f2', '2', 'f3', 'foo2', 'f4', 'foo2']])
2960
2961
2962def testIssue1158(env):
2963    env.cmd('FT.CREATE idx SCHEMA txt1 TEXT txt2 TEXT txt3 TEXT')
2964
2965    env.cmd('FT.ADD idx doc1 1.0 FIELDS txt1 10 txt2 num1')
2966    env.expect('FT.GET idx doc1').equal(['txt1', '10', 'txt2', 'num1'])
2967
2968    # only 1st checked (2nd returns an error)
2969    env.expect('FT.ADD idx doc1 1.0 REPLACE PARTIAL if @txt1||to_number(@txt2)<5 FIELDS txt1 5').equal('OK')
2970    env.expect('FT.ADD idx doc1 1.0 REPLACE PARTIAL if @txt3&&to_number(@txt2)<5 FIELDS txt1 5').equal('NOADD')
2971
2972    # both are checked
2973    env.expect('FT.ADD idx doc1 1.0 REPLACE PARTIAL if to_number(@txt1)>11||to_number(@txt1)>42 FIELDS txt2 num2').equal('NOADD')
2974    env.expect('FT.ADD idx doc1 1.0 REPLACE PARTIAL if to_number(@txt1)>11||to_number(@txt1)<42 FIELDS txt2 num2').equal('OK')
2975    env.expect('FT.ADD idx doc1 1.0 REPLACE PARTIAL if to_number(@txt1)>11&&to_number(@txt1)>42 FIELDS txt2 num2').equal('NOADD')
2976    env.expect('FT.ADD idx doc1 1.0 REPLACE PARTIAL if to_number(@txt1)>11&&to_number(@txt1)<42 FIELDS txt2 num2').equal('NOADD')
2977    env.expect('FT.GET idx doc1').equal(['txt1', '5', 'txt2', 'num2'])
2978
2979def testIssue1159(env):
2980    env.cmd('FT.CREATE idx SCHEMA f1 TAG')
2981    for i in range(1000):
2982        env.cmd('FT.add idx doc%d 1.0 FIELDS f1 foo' % i)
2983
2984def testIssue1169(env):
2985    env.cmd('FT.CREATE idx SCHEMA txt1 TEXT txt2 TEXT')
2986    env.cmd('FT.ADD idx doc1 1.0 FIELDS txt1 foo')
2987
2988    env.expect('FT.AGGREGATE idx foo GROUPBY 1 @txt1 REDUCE FIRST_VALUE 1 @txt2 as test').equal([1L, ['txt1', 'foo', 'test', None]])
2989
2990def testIssue1184(env):
2991    env.skipOnCluster()
2992    field_types = ['TEXT', 'NUMERIC', 'TAG']
2993
2994    for ft in field_types:
2995        env.assertOk(env.execute_command('FT.CREATE idx SCHEMA  field ' + ft))
2996
2997        res = env.execute_command('ft.info', 'idx')
2998        d = {res[i]: res[i + 1] for i in range(0, len(res), 2)}
2999        env.assertEqual(d['inverted_sz_mb'], '0')
3000        env.assertEqual(d['num_records'], '0')
3001
3002
3003        value = '42'
3004        env.assertOk(env.execute_command('FT.ADD idx doc0 1 FIELD field ' + value))
3005        doc = env.cmd('FT.SEARCH idx *')
3006        env.assertEqual(doc, [1L, 'doc0', ['field', value]])
3007
3008        res = env.execute_command('ft.info', 'idx')
3009        d = {res[i]: res[i + 1] for i in range(0, len(res), 2)}
3010        env.assertGreater(d['inverted_sz_mb'], '0')
3011        env.assertEqual(d['num_records'], '1')
3012
3013        env.assertEqual(env.execute_command('FT.DEL idx doc0'), 1)
3014        env.cmd('ft.debug', 'GC_FORCEINVOKE', 'idx')
3015
3016        res = env.execute_command('ft.info', 'idx')
3017        d = {res[i]: res[i + 1] for i in range(0, len(res), 2)}
3018        env.assertEqual(d['inverted_sz_mb'], '0')
3019        env.assertEqual(d['num_records'], '0')
3020
3021        env.cmd('FT.DROP idx')
3022
3023def test_MOD_865(env):
3024  args_list = ['FT.CREATE', 'idx', 'SCHEMA']
3025  # We have a limit on number of text fields so we split between TEXT and NUMERIC
3026  for i in range(128):
3027    args_list.extend([i, 'TEXT', 'SORTABLE'])
3028  for i in range(128, 256):
3029    args_list.extend([i, 'NUMERIC', 'SORTABLE'])
3030  env.expect(*args_list).error().contains('Too many SORTABLE fields in schema')
3031
3032def testIssue1208(env):
3033    env.cmd('FT.CREATE idx SCHEMA n NUMERIC')
3034    env.cmd('FT.ADD idx doc1 1 FIELDS n 1.0321e5')
3035    env.cmd('FT.ADD idx doc2 1 FIELDS n 101.11')
3036    env.cmd('FT.ADD idx doc3 1 FIELDS n 0.0011')
3037    env.expect('FT.SEARCH', 'idx', '@n:[1.1432E3 inf]').equal([1L, 'doc1', ['n', '1.0321e5']])
3038    env.expect('FT.SEARCH', 'idx', '@n:[-1.12E-3 1.12E-1]').equal([1L, 'doc3', ['n', '0.0011']])
3039    res = [3L, 'doc3', ['n', '0.0011'], 'doc2', ['n', '101.11'], 'doc1', ['n', '1.0321e5']]
3040    env.expect('FT.SEARCH', 'idx', '@n:[-inf inf]').equal(res)
3041
3042    env.expect('FT.ADD idx doc3 1 REPLACE PARTIAL IF @n>42e3 FIELDS n 100').equal('NOADD')
3043    env.expect('FT.ADD idx doc3 1 REPLACE PARTIAL IF @n<42e3 FIELDS n 100').ok()
3044
3045def testRED47209(env):
3046    env.expect('FT.CREATE idx SCHEMA t TEXT').ok()
3047    env.expect('FT.ADD idx doc1 1 FIELDS t foo')
3048    if env.isCluster():
3049        # on cluster we have WITHSCORES set unconditionally for FT.SEARCH
3050        res = [1L, 'doc1', ['t', 'foo']]
3051    else:
3052        res = [1L, 'doc1', None, ['t', 'foo']]
3053    env.expect('FT.SEARCH idx foo WITHSORTKEYS LIMIT 0 1').equal(res)
3054
3055def testSuggestMax(env):
3056  for i in range(10):
3057    env.expect('ft.sugadd', 'sug', 'test%d' % i, i + 1).equal(i + 1)
3058  #  for j in range(i + 1):
3059  #env.expect('ft.sugadd', 'sug', 'test10', '1', 'INCR').equal(i + 1)
3060
3061  expected_res = ['test9', '7.0710678100585938', 'test8', '6.3639612197875977', 'test7', '5.6568541526794434',
3062                  'test6', '4.9497475624084473', 'test5', '4.242640495300293', 'test4', '3.5355339050292969',
3063                  'test3', '2.8284270763397217', 'test2', '2.1213202476501465', 'test1', '1.4142135381698608',
3064                  'test0', '0.70710676908493042']
3065  for i in range(1,11):
3066    env.expect('FT.SUGGET', 'sug', 'test', 'MAX', i, 'WITHSCORES').equal(expected_res[0:i*2])
3067  env.expect('FT.SUGGET', 'sug', 'test', 'MAX', 10, 'WITHSCORES').equal(expected_res)
3068
3069def testSuggestMax2(env):
3070  for i in range(10):
3071    env.expect('ft.sugadd', 'sug', 'test %d' % i, 1).equal(i + 1)
3072
3073  expected_res = ['test 0', 'test 1', 'test 2', 'test 3', 'test 4', 'test 5']
3074  for i in range(1,7):
3075    res = env.cmd('FT.SUGGET', 'sug', 'test ', 'MAX', i)
3076    for item in res:
3077        env.assertIn(item, expected_res[0:i])
3078