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