Skip to content

Commit 7ae6341

Browse files
Support json index type (#130)
Co-authored-by: rafie <rafi@redislabs.com>
1 parent 2d56693 commit 7ae6341

File tree

5 files changed

+108
-30
lines changed

5 files changed

+108
-30
lines changed

.circleci/config.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ jobs:
9191
default: latest
9292
docker:
9393
- image: circleci/python:<<parameters.python_version >>
94-
- image: redislabs/redisearch:edge
94+
- image: redislabs/redismod:edge
9595
steps:
9696
- build_and_test
9797

pyproject.toml

+1
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ python = ">=2.7,<=2.9.0 || >= 3.5.0"
2626
redis = "^3.5.3"
2727
six = "^1.16.0"
2828
rmtest = {git = "https://github.com/RedisLabs/rmtest"}
29+
rejson = "^0.5.4"
2930
hiredis = [
3031
{version = "1.1.0", python = "~2.7"},
3132
{version = "^2.0.0", python = "^3.6"},

redisearch/client.py

+24-20
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
from .query import Query
99
from ._util import to_string
1010
from .aggregation import AggregateRequest, AggregateResult, Cursor
11+
from enum import Enum
1112

1213

1314
class Field(object):
@@ -87,53 +88,56 @@ def __init__(self, name, separator=',', **kwargs):
8788
Field.__init__(self, name, args=[Field.TAG, self.SEPARATOR, separator], **kwargs)
8889

8990

91+
class IndexType(Enum):
92+
"""
93+
Enum of the currently supported index types.
94+
"""
95+
HASH = 1
96+
JSON = 2
97+
9098
class IndexDefinition(object):
9199
"""
92-
IndexDefinition is used to define a index definition for automatic indexing on Hash update
100+
IndexDefinition is used to define a index definition for automatic indexing on Hash or Json update.
93101
"""
94102

95-
ON = 'ON'
96-
HASH = 'HASH'
97-
PREFIX = 'PREFIX'
98-
FILTER = 'FILTER'
99-
LANGUAGE_FIELD = 'LANGUAGE_FIELD'
100-
LANGUAGE = 'LANGUAGE'
101-
SCORE_FIELD = 'SCORE_FIELD'
102-
SCORE = 'SCORE'
103-
PAYLOAD_FIELD = 'PAYLOAD_FIELD'
104-
105-
def __init__(self, prefix=[], filter=None, language_field=None, language=None, score_field=None, score=1.0, payload_field=None):
103+
def __init__(self, prefix=[], filter=None, language_field=None, language=None, score_field=None, score=1.0, payload_field=None, index_type=None):
104+
args = []
106105

107-
args = [self.ON, self.HASH]
106+
if index_type is IndexType.HASH:
107+
args.extend(['ON', 'HASH'])
108+
elif index_type is IndexType.JSON:
109+
args.extend(['ON', 'JSON'])
110+
elif index_type is not None:
111+
raise RuntimeError("index_type must be one of {}".format(list(IndexType)))
108112

109113
if len(prefix) > 0:
110-
args.append(self.PREFIX)
114+
args.append('PREFIX')
111115
args.append(len(prefix))
112116
for p in prefix:
113117
args.append(p)
114118

115119
if filter is not None:
116-
args.append(self.FILTER)
120+
args.append('FILTER')
117121
args.append(filter)
118122

119123
if language_field is not None:
120-
args.append(self.LANGUAGE_FIELD)
124+
args.append('LANGUAGE_FIELD')
121125
args.append(language_field)
122126

123127
if language is not None:
124-
args.append(self.LANGUAGE)
128+
args.append('LANGUAGE')
125129
args.append(language)
126130

127131
if score_field is not None:
128-
args.append(self.SCORE_FIELD)
132+
args.append('SCORE_FIELD')
129133
args.append(score_field)
130134

131135
if score is not None:
132-
args.append(self.SCORE)
136+
args.append('SCORE')
133137
args.append(score)
134138

135139
if payload_field is not None:
136-
args.append(self.PAYLOAD_FIELD)
140+
args.append('PAYLOAD_FIELD')
137141
args.append(payload_field)
138142

139143
self.args = args

redisearch/result.py

+9-2
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@
33
from .document import Document
44
from ._util import to_string
55

6+
7+
68
class Result(object):
79
"""
810
Represents the result of a search query, and has an array of Document objects
911
"""
1012

11-
def __init__(self, res, hascontent, duration=0, has_payload = False, with_scores = False):
13+
def __init__(self, res, hascontent, duration=0, has_payload=False, with_scores=False):
1214
"""
1315
- **snippets**: An optional dictionary of the form {field: snippet_size} for snippet formatting
1416
"""
@@ -45,9 +47,14 @@ def __init__(self, res, hascontent, duration=0, has_payload = False, with_scores
4547
except KeyError:
4648
pass
4749

50+
try:
51+
fields['json'] = fields['$']
52+
del fields['$']
53+
except KeyError:
54+
pass
55+
4856
doc = Document(id, score=score, payload=payload, **fields) if with_scores else Document(id, payload=payload, **fields)
4957
self.docs.append(doc)
5058

5159
def __repr__(self):
52-
5360
return 'Result{%d total, docs: %s}' % (self.total, self.docs)

test/test.py

+73-7
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1+
import json
12
import os, sys
3+
4+
25
sys.path.insert(0, os.path.join(os.path.dirname(__file__), '..'))
36

47
from rmtest import ModuleTestCase
@@ -12,8 +15,10 @@
1215
import six
1316

1417
from redisearch import *
18+
from redisearch.client import IndexType
1519
import redisearch.aggregation as aggregations
1620
import redisearch.reducers as reducers
21+
import rejson
1722

1823
WILL_PLAY_TEXT = os.path.abspath(os.path.dirname(__file__)) + '/will_play_text.csv.bz2'
1924

@@ -487,6 +492,7 @@ def testAutoComplete(self):
487492
def testNoIndex(self):
488493
# Creating a client with a given index name
489494
client = self.getCleanClient('idx')
495+
client.redis.flushdb()
490496

491497
client.create_index(
492498
(TextField('field'),
@@ -946,7 +952,10 @@ def testAggregations(self):
946952
self.assertEqual('RediSearch', res[23])
947953
self.assertEqual(2, len(res[25]))
948954

949-
def testIndexDefiniontion(self):
955+
def testIndexDefinition(self):
956+
"""
957+
Create definition and test its args
958+
"""
950959
conn = self.redis()
951960

952961
with conn as r:
@@ -955,19 +964,24 @@ def testIndexDefiniontion(self):
955964
return
956965
client = Client('test', port=conn.port)
957966

967+
self.assertRaises(RuntimeError, IndexDefinition, prefix=['hset:', 'henry'], index_type='json')
968+
958969
definition = IndexDefinition(prefix=['hset:', 'henry'],
959970
filter='@f1==32', language='English', language_field='play',
960-
score_field='chapter', score=0.5, payload_field='txt' )
971+
score_field='chapter', score=0.5, payload_field='txt', index_type=IndexType.JSON)
961972

962-
self.assertEqual(['ON','HASH', 'PREFIX',2,'hset:','henry',
963-
'FILTER','@f1==32','LANGUAGE_FIELD','play','LANGUAGE','English',
964-
'SCORE_FIELD','chapter','SCORE',0.5,'PAYLOAD_FIELD','txt'],
973+
self.assertEqual(['ON', 'JSON', 'PREFIX', 2, 'hset:', 'henry',
974+
'FILTER', '@f1==32', 'LANGUAGE_FIELD', 'play', 'LANGUAGE', 'English',
975+
'SCORE_FIELD', 'chapter', 'SCORE', 0.5, 'PAYLOAD_FIELD', 'txt'],
965976
definition.args)
966977

967978
self.createIndex(client, num_docs=500, definition=definition)
968979

969-
970-
def testCreateClientDefiniontion(self):
980+
def testCreateClientDefinition(self):
981+
"""
982+
Create definition with no index type provided,
983+
and use hset to test the client definition (the default is HASH).
984+
"""
971985
conn = self.redis()
972986

973987
with conn as r:
@@ -987,5 +1001,57 @@ def testCreateClientDefiniontion(self):
9871001
info = client.info()
9881002
self.assertEqual(495, int(info['num_docs']))
9891003

1004+
def testCreateClientDefinitionHash(self):
1005+
"""
1006+
Create definition with IndexType.HASH as index type (ON HASH),
1007+
and use hset to test the client definition.
1008+
"""
1009+
conn = self.redis()
1010+
1011+
with conn as r:
1012+
r.flushdb()
1013+
if not check_version(r, 20000):
1014+
return
1015+
client = Client('test', port=conn.port)
1016+
1017+
definition = IndexDefinition(prefix=['hset:', 'henry'], index_type=IndexType.HASH)
1018+
self.createIndex(client, num_docs=500, definition=definition)
1019+
1020+
info = client.info()
1021+
self.assertEqual(494, int(info['num_docs']))
1022+
1023+
r.hset('hset:1', 'f1', 'v1');
1024+
1025+
info = client.info()
1026+
self.assertEqual(495, int(info['num_docs']))
1027+
1028+
def testCreateClientDefinitionJson(self):
1029+
"""
1030+
Create definition with IndexType.JSON as index type (ON JSON),
1031+
and use json client to test it.
1032+
"""
1033+
conn = self.redis()
1034+
1035+
with conn as r:
1036+
r.flushdb()
1037+
if not check_version(r, 20200):
1038+
return
1039+
1040+
client = Client('json1', port=conn.port)
1041+
1042+
definition = IndexDefinition(prefix=['king:'], index_type=IndexType.JSON)
1043+
client.create_index((TextField('$.name'),), definition=definition)
1044+
1045+
rj = rejson.Client(host='localhost', port=conn.port, decode_responses=True)
1046+
rj.jsonset('king:1', rejson.Path.rootPath(), {'name': 'henry'})
1047+
rj.jsonset('king:2', rejson.Path.rootPath(), {'name': 'james'})
1048+
1049+
res = client.search('henry')
1050+
self.assertEqual(res.docs[0].id, 'king:1')
1051+
self.assertIsNone(res.docs[0].payload)
1052+
self.assertEqual(res.docs[0].json, '{"name":"henry"}')
1053+
self.assertEqual(res.total, 1)
1054+
1055+
9901056
if __name__ == '__main__':
9911057
unittest.main()

0 commit comments

Comments
 (0)