Skip to content

Commit 96df8f4

Browse files
committed
new: allow Routers to return multiple indexes
Thanks to Hugo Chargois (@hchargois) for the patch Closes django-haystack#1337 Closes django-haystack#934
1 parent f27e469 commit 96df8f4

File tree

5 files changed

+46
-20
lines changed

5 files changed

+46
-20
lines changed

docs/multiple_index.rst

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,15 @@ Automatic Routing
6969

7070
To make the selection of the correct index easier, Haystack (like Django) has
7171
the concept of "routers". All provided routers are checked whenever a read or
72-
write happens, stopping at the first router that knows how to handle it.
72+
write happens, in the order in which they are defined.
73+
74+
For read operations (when a search query is executed), the ``for_read`` method
75+
of each router is called, until one of them returns an index, which is used for
76+
the read operation.
77+
78+
For write operations (when a delete or update is executed), the ``for_write``
79+
method of each router is called, and the results are aggregated. All of the
80+
indexes that were returned are then updated.
7381

7482
Haystack ships with a ``DefaultRouter`` enabled. It looks like::
7583

@@ -80,13 +88,18 @@ Haystack ships with a ``DefaultRouter`` enabled. It looks like::
8088
def for_write(self, **hints):
8189
return DEFAULT_ALIAS
8290

83-
On a read (when a search query is executed), the ``DefaultRouter.for_read``
84-
method is checked & returns the ``DEFAULT_ALIAS`` (which is ``default``),
85-
telling whatever requested it that it should perform the query against the
86-
``default`` connection. The same process is followed for writes.
91+
This means that the default index is used for all read and write operations.
92+
93+
If the ``for_read`` or ``for_write`` method doesn't exist or returns ``None``,
94+
that indicates that the current router can't handle the data. The next router
95+
is then checked.
8796

88-
If the ``for_read`` or ``for_write`` method returns ``None``, that indicates
89-
that the current router can't handle the data. The next router is then checked.
97+
The ``for_write`` method can return either a single string representing an
98+
index name, or an iterable of such index names. For example::
99+
100+
class UpdateEverythingRouter(BaseRouter):
101+
def for_write(self, **hints):
102+
return ('myindex1', 'myindex2')
90103

91104
The ``hints`` passed can be anything that helps the router make a decision. This
92105
data should always be considered optional & be guarded against. At current,
@@ -99,7 +112,6 @@ You may provide as many routers as you like by overriding the
99112

100113
HAYSTACK_ROUTERS = ['myapp.routers.MasterRouter', 'myapp.routers.SlaveRouter', 'haystack.routers.DefaultRouter']
101114

102-
103115
Master-Slave Example
104116
--------------------
105117

haystack/query.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,10 +54,6 @@ def _determine_backend(self):
5454

5555
backend_alias = connection_router.for_read(**hints)
5656

57-
if isinstance(backend_alias, (list, tuple)) and len(backend_alias):
58-
# We can only effectively read from one engine.
59-
backend_alias = backend_alias[0]
60-
6157
# The ``SearchQuery`` might swap itself out for a different variant
6258
# here.
6359
if self.query:

haystack/utils/loading.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010

1111
from django.conf import settings
1212
from django.core.exceptions import ImproperlyConfigured
13+
from django.utils import six
1314
from django.utils.module_loading import module_has_submodule
1415

1516
from haystack.exceptions import NotHandled, SearchFieldError
@@ -140,7 +141,7 @@ def routers(self):
140141
self._routers.append(router_class())
141142
return self._routers
142143

143-
def for_action(self, action, **hints):
144+
def _for_action(self, action, many, **hints):
144145
conns = []
145146

146147
for router in self.routers:
@@ -149,15 +150,20 @@ def for_action(self, action, **hints):
149150
connection_to_use = action_callable(**hints)
150151

151152
if connection_to_use is not None:
152-
conns.append(connection_to_use)
153+
if isinstance(connection_to_use, six.string_types):
154+
conns.append(connection_to_use)
155+
else:
156+
conns.extend(connection_to_use)
157+
if not many:
158+
break
153159

154160
return conns
155161

156162
def for_write(self, **hints):
157-
return self.for_action('for_write', **hints)
163+
return self._for_action('for_write', True, **hints)
158164

159165
def for_read(self, **hints):
160-
return self.for_action('for_read', **hints)
166+
return self._for_action('for_read', False, **hints)[0]
161167

162168

163169
class UnifiedIndex(object):

test_haystack/mocks.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@ def for_write(self, **hints):
3232
return None
3333

3434

35+
class MockMultiRouter(BaseRouter):
36+
def for_write(self, **hints):
37+
return ['multi1', 'multi2']
38+
39+
3540
class MockSearchResult(SearchResult):
3641
def __init__(self, app_label, model_name, pk, score, **kwargs):
3742
super(MockSearchResult, self).__init__(app_label, model_name, pk, score, **kwargs)

test_haystack/test_loading.py

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -111,25 +111,32 @@ def test_router_override3(self):
111111
def test_actions1(self):
112112
del settings.HAYSTACK_ROUTERS
113113
cr = loading.ConnectionRouter()
114-
self.assertEqual(cr.for_read(), ['default'])
114+
self.assertEqual(cr.for_read(), 'default')
115115
self.assertEqual(cr.for_write(), ['default'])
116116

117117
@override_settings(HAYSTACK_ROUTERS=['test_haystack.mocks.MockMasterSlaveRouter', 'haystack.routers.DefaultRouter'])
118118
def test_actions2(self):
119119
cr = loading.ConnectionRouter()
120-
self.assertEqual(cr.for_read(), ['slave', 'default'])
120+
self.assertEqual(cr.for_read(), 'slave')
121121
self.assertEqual(cr.for_write(), ['master', 'default'])
122122

123123
@override_settings(HAYSTACK_ROUTERS=['test_haystack.mocks.MockPassthroughRouter', 'test_haystack.mocks.MockMasterSlaveRouter', 'haystack.routers.DefaultRouter'])
124124
def test_actions3(self):
125125
cr = loading.ConnectionRouter()
126126
# Demonstrate pass-through
127-
self.assertEqual(cr.for_read(), ['slave', 'default'])
127+
self.assertEqual(cr.for_read(), 'slave')
128128
self.assertEqual(cr.for_write(), ['master', 'default'])
129129
# Demonstrate that hinting can change routing.
130-
self.assertEqual(cr.for_read(pass_through=False), ['pass', 'slave', 'default'])
130+
self.assertEqual(cr.for_read(pass_through=False), 'pass')
131131
self.assertEqual(cr.for_write(pass_through=False), ['pass', 'master', 'default'])
132132

133+
@override_settings(HAYSTACK_ROUTERS=['test_haystack.mocks.MockMultiRouter', 'haystack.routers.DefaultRouter'])
134+
def test_actions4(self):
135+
cr = loading.ConnectionRouter()
136+
# Demonstrate that a router can return multiple backends in the "for_write" method
137+
self.assertEqual(cr.for_read(), 'default')
138+
self.assertEqual(cr.for_write(), ['multi1', 'multi2', 'default'])
139+
133140

134141
class MockNotAModel(object):
135142
pass

0 commit comments

Comments
 (0)