Skip to content
This repository was archived by the owner on Aug 30, 2024. It is now read-only.

Commit 5f926a0

Browse files
committed
Add partitioned databases feature.
1 parent 1cb8abf commit 5f926a0

File tree

10 files changed

+410
-30
lines changed

10 files changed

+410
-30
lines changed

CHANGES.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# Unreleased
22

3+
- [NEW] Added partitioned database support.
34
- [IMPROVED] Updated `Result` iteration by paginating with views' `startkey` and
45
queries' `bookmark`.
56
- [FIXED] Bug where document context manager performed remote save despite

src/cloudant/_common_util.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
#!/usr/bin/env python
2-
# Copyright (C) 2015, 2018 IBM Corp. All rights reserved.
2+
# Copyright (C) 2015, 2019 IBM Corp. All rights reserved.
33
#
44
# Licensed under the Apache License, Version 2.0 (the "License");
55
# you may not use this file except in compliance with the License.
@@ -140,7 +140,8 @@
140140
'highlight_post_tag': STRTYPE,
141141
'highlight_number': (int, LONGTYPE, NONETYPE),
142142
'highlight_size': (int, LONGTYPE, NONETYPE),
143-
'include_fields': list
143+
'include_fields': list,
144+
'partition': STRTYPE
144145
}
145146

146147
# Functions

src/cloudant/client.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,7 @@ def all_dbs(self):
267267
resp.raise_for_status()
268268
return response_to_json_dict(resp)
269269

270-
def create_database(self, dbname, **kwargs):
270+
def create_database(self, dbname, partitioned=False, **kwargs):
271271
"""
272272
Creates a new database on the remote server with the name provided
273273
and adds the new database object to the client's locally cached
@@ -279,10 +279,12 @@ def create_database(self, dbname, **kwargs):
279279
:param bool throw_on_exists: Boolean flag dictating whether or
280280
not to throw a CloudantClientException when attempting to
281281
create a database that already exists.
282+
:param bool partitioned: Create as a partitioned database. Defaults to
283+
``False``.
282284
283285
:returns: The newly created database object
284286
"""
285-
new_db = self._DATABASE_CLASS(self, dbname)
287+
new_db = self._DATABASE_CLASS(self, dbname, partitioned=partitioned)
286288
try:
287289
new_db.create(kwargs.get('throw_on_exists', False))
288290
except CloudantDatabaseException as ex:

src/cloudant/database.py

Lines changed: 184 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,9 @@
2626
SEARCH_INDEX_ARGS,
2727
SPECIAL_INDEX_TYPE,
2828
TEXT_INDEX_TYPE,
29+
TYPE_CONVERTERS,
2930
get_docs,
30-
response_to_json_dict,
31-
)
31+
response_to_json_dict)
3232
from .document import Document
3333
from .design_document import DesignDocument
3434
from .security_document import SecurityDocument
@@ -50,13 +50,17 @@ class CouchDatabase(dict):
5050
:param str database_name: Database name used to reference the database.
5151
:param int fetch_limit: Optional fetch limit used to set the max number of
5252
documents to fetch per query during iteration cycles. Defaults to 100.
53+
:param bool partitioned: Create as a partitioned database. Defaults to
54+
``False``.
5355
"""
54-
def __init__(self, client, database_name, fetch_limit=100):
56+
def __init__(self, client, database_name, fetch_limit=100,
57+
partitioned=False):
5558
super(CouchDatabase, self).__init__()
5659
self.client = client
5760
self._database_host = client.server_url
5861
self.database_name = database_name
5962
self._fetch_limit = fetch_limit
63+
self._partitioned = partitioned
6064
self.result = Result(self.all_docs)
6165

6266
@property
@@ -105,6 +109,18 @@ def creds(self):
105109
"user_ctx": session.get('userCtx')
106110
}
107111

112+
def database_partition_url(self, partition_key):
113+
"""
114+
Get the URL of the database partition.
115+
116+
:param str partition_key: Partition key.
117+
:return: URL of the database partition.
118+
:rtype: str
119+
"""
120+
return '/'.join((self.database_url,
121+
'_partition',
122+
url_quote_plus(partition_key)))
123+
108124
def exists(self):
109125
"""
110126
Performs an existence check on the remote database.
@@ -127,6 +143,18 @@ def metadata(self):
127143
resp.raise_for_status()
128144
return response_to_json_dict(resp)
129145

146+
def partition_metadata(self, partition_key):
147+
"""
148+
Retrieves the metadata dictionary for the remote database partition.
149+
150+
:param str partition_key: Partition key.
151+
:returns: Metadata dictionary for the database partition.
152+
:rtype: dict
153+
"""
154+
resp = self.r_session.get(self.database_partition_url(partition_key))
155+
resp.raise_for_status()
156+
return response_to_json_dict(resp)
157+
130158
def doc_count(self):
131159
"""
132160
Retrieves the number of documents in the remote database
@@ -244,6 +272,33 @@ def get_security_document(self):
244272
sdoc.fetch()
245273
return sdoc
246274

275+
def get_partitioned_view_result(self, partition_key, ddoc_id, view_name,
276+
raw_result=False, **kwargs):
277+
"""
278+
Retrieves the partitioned view result based on the design document and
279+
view name.
280+
281+
See :func:`~cloudant.database.CouchDatabase.get_view_result` method for
282+
further details.
283+
284+
:param str partition_key: Partition key.
285+
:param str ddoc_id: Design document id used to get result.
286+
:param str view_name: Name of the view used to get result.
287+
:param bool raw_result: Dictates whether the view result is returned
288+
as a default Result object or a raw JSON response.
289+
Defaults to False.
290+
:param kwargs: See
291+
:func:`~cloudant.database.CouchDatabase.get_view_result` method for
292+
available keyword arguments.
293+
:returns: The result content either wrapped in a QueryResult or
294+
as the raw response JSON content.
295+
:rtype: QueryResult, dict
296+
"""
297+
ddoc = DesignDocument(self, ddoc_id)
298+
view = View(ddoc, view_name, partition_key=partition_key)
299+
300+
return self._get_view_result(view, raw_result, **kwargs)
301+
247302
def get_view_result(self, ddoc_id, view_name, raw_result=False, **kwargs):
248303
"""
249304
Retrieves the view result based on the design document and view name.
@@ -336,7 +391,14 @@ def get_view_result(self, ddoc_id, view_name, raw_result=False, **kwargs):
336391
:returns: The result content either wrapped in a QueryResult or
337392
as the raw response JSON content
338393
"""
339-
view = View(DesignDocument(self, ddoc_id), view_name)
394+
ddoc = DesignDocument(self, ddoc_id)
395+
view = View(ddoc, view_name)
396+
397+
return self._get_view_result(view, raw_result, **kwargs)
398+
399+
@staticmethod
400+
def _get_view_result(view, raw_result, **kwargs):
401+
""" Get view results helper. """
340402
if raw_result:
341403
return view(**kwargs)
342404
if kwargs:
@@ -359,7 +421,9 @@ def create(self, throw_on_exists=False):
359421
if not throw_on_exists and self.exists():
360422
return self
361423

362-
resp = self.r_session.put(self.database_url)
424+
resp = self.r_session.put(self.database_url, params={
425+
'partitioned': TYPE_CONVERTERS.get(bool)(self._partitioned)
426+
})
363427
if resp.status_code == 201 or resp.status_code == 202:
364428
return self
365429

@@ -407,6 +471,29 @@ def all_docs(self, **kwargs):
407471
**kwargs)
408472
return response_to_json_dict(resp)
409473

474+
def partitioned_all_docs(self, partition_key, **kwargs):
475+
"""
476+
Wraps the _all_docs primary index on the database partition, and returns
477+
the results by value.
478+
479+
See :func:`~cloudant.database.CouchDatabase.all_docs` method for further
480+
details.
481+
482+
:param str partition_key: Partition key.
483+
:param kwargs: See :func:`~cloudant.database.CouchDatabase.all_docs`
484+
method for available keyword arguments.
485+
:returns: Raw JSON response content from ``_all_docs`` endpoint.
486+
:rtype: dict
487+
"""
488+
resp = get_docs(self.r_session,
489+
'/'.join([
490+
self.database_partition_url(partition_key),
491+
'_all_docs'
492+
]),
493+
self.client.encoder,
494+
**kwargs)
495+
return response_to_json_dict(resp)
496+
410497
@contextlib.contextmanager
411498
def custom_result(self, **options):
412499
"""
@@ -985,20 +1072,23 @@ def get_query_indexes(self, raw_result=False):
9851072
self,
9861073
data.get('ddoc'),
9871074
data.get('name'),
1075+
partitioned=data.get('partitioned', False),
9881076
**data.get('def', {})
9891077
))
9901078
elif data.get('type') == TEXT_INDEX_TYPE:
9911079
indexes.append(TextIndex(
9921080
self,
9931081
data.get('ddoc'),
9941082
data.get('name'),
1083+
partitioned=data.get('partitioned', False),
9951084
**data.get('def', {})
9961085
))
9971086
elif data.get('type') == SPECIAL_INDEX_TYPE:
9981087
indexes.append(SpecialIndex(
9991088
self,
10001089
data.get('ddoc'),
10011090
data.get('name'),
1091+
partitioned=data.get('partitioned', False),
10021092
**data.get('def', {})
10031093
))
10041094
else:
@@ -1010,6 +1100,7 @@ def create_query_index(
10101100
design_document_id=None,
10111101
index_name=None,
10121102
index_type='json',
1103+
partitioned=False,
10131104
**kwargs
10141105
):
10151106
"""
@@ -1047,9 +1138,11 @@ def create_query_index(
10471138
remote database
10481139
"""
10491140
if index_type == JSON_INDEX_TYPE:
1050-
index = Index(self, design_document_id, index_name, **kwargs)
1141+
index = Index(self, design_document_id, index_name,
1142+
partitioned=partitioned, **kwargs)
10511143
elif index_type == TEXT_INDEX_TYPE:
1052-
index = TextIndex(self, design_document_id, index_name, **kwargs)
1144+
index = TextIndex(self, design_document_id, index_name,
1145+
partitioned=partitioned, **kwargs)
10531146
else:
10541147
raise CloudantArgumentError(103, index_type)
10551148
index.create()
@@ -1074,6 +1167,36 @@ def delete_query_index(self, design_document_id, index_type, index_name):
10741167
raise CloudantArgumentError(103, index_type)
10751168
index.delete()
10761169

1170+
def get_partitioned_query_result(self, partition_key, selector, fields=None,
1171+
raw_result=False, **kwargs):
1172+
"""
1173+
Retrieves the partitioned query result from the specified database based
1174+
on the query parameters provided.
1175+
1176+
See :func:`~cloudant.database.CouchDatabase.get_query_result` method for
1177+
further details.
1178+
1179+
:param str partition_key: Partition key.
1180+
:param str selector: Dictionary object describing criteria used to
1181+
select documents.
1182+
:param list fields: A list of fields to be returned by the query.
1183+
:param bool raw_result: Dictates whether the query result is returned
1184+
wrapped in a QueryResult or if the response JSON is returned.
1185+
Defaults to False.
1186+
:param kwargs: See
1187+
:func:`~cloudant.database.CouchDatabase.get_query_result` method for
1188+
available keyword arguments.
1189+
:returns: The result content either wrapped in a QueryResult or
1190+
as the raw response JSON content.
1191+
:rtype: QueryResult, dict
1192+
"""
1193+
query = Query(self,
1194+
selector=selector,
1195+
fields=fields,
1196+
partition_key=partition_key)
1197+
1198+
return self._get_query_result(query, raw_result, **kwargs)
1199+
10771200
def get_query_result(self, selector, fields=None, raw_result=False,
10781201
**kwargs):
10791202
"""
@@ -1143,10 +1266,15 @@ def get_query_result(self, selector, fields=None, raw_result=False,
11431266
:returns: The result content either wrapped in a QueryResult or
11441267
as the raw response JSON content
11451268
"""
1146-
if fields:
1147-
query = Query(self, selector=selector, fields=fields)
1148-
else:
1149-
query = Query(self, selector=selector)
1269+
query = Query(self,
1270+
selector=selector,
1271+
fields=fields)
1272+
1273+
return self._get_query_result(query, raw_result, **kwargs)
1274+
1275+
@staticmethod
1276+
def _get_query_result(query, raw_result, **kwargs):
1277+
""" Get query results helper. """
11501278
if raw_result:
11511279
return query(**kwargs)
11521280
if kwargs:
@@ -1166,12 +1294,16 @@ class CloudantDatabase(CouchDatabase):
11661294
:param str database_name: Database name used to reference the database.
11671295
:param int fetch_limit: Optional fetch limit used to set the max number of
11681296
documents to fetch per query during iteration cycles. Defaults to 100.
1297+
:param bool partitioned: Create as a partitioned database. Defaults to
1298+
``False``.
11691299
"""
1170-
def __init__(self, client, database_name, fetch_limit=100):
1300+
def __init__(self, client, database_name, fetch_limit=100,
1301+
partitioned=False):
11711302
super(CloudantDatabase, self).__init__(
11721303
client,
11731304
database_name,
1174-
fetch_limit=fetch_limit
1305+
fetch_limit=fetch_limit,
1306+
partitioned=partitioned
11751307
)
11761308

11771309
def security_document(self):
@@ -1280,6 +1412,36 @@ def shards(self):
12801412

12811413
return response_to_json_dict(resp)
12821414

1415+
def get_partitioned_search_result(self, partition_key, ddoc_id, index_name,
1416+
**query_params):
1417+
"""
1418+
Retrieves the raw JSON content from the remote database based on the
1419+
partitioned search index on the server, using the query_params provided
1420+
as query parameters.
1421+
1422+
See :func:`~cloudant.database.CouchDatabase.get_search_result` method
1423+
for further details.
1424+
1425+
:param str partition_key: Partition key.
1426+
:param str ddoc_id: Design document id used to get the search result.
1427+
:param str index_name: Name used in part to identify the index.
1428+
:param query_params: See
1429+
:func:`~cloudant.database.CloudantDatabase.get_search_result` method
1430+
for available keyword arguments.
1431+
:returns: Search query result data in JSON format.
1432+
:rtype: dict
1433+
"""
1434+
ddoc = DesignDocument(self, ddoc_id)
1435+
1436+
return self._get_search_result(
1437+
'/'.join((
1438+
ddoc.document_partition_url(partition_key),
1439+
'_search',
1440+
index_name
1441+
)),
1442+
**query_params
1443+
)
1444+
12831445
def get_search_result(self, ddoc_id, index_name, **query_params):
12841446
"""
12851447
Retrieves the raw JSON content from the remote database based on the
@@ -1380,6 +1542,14 @@ def get_search_result(self, ddoc_id, index_name, **query_params):
13801542
13811543
:returns: Search query result data in JSON format
13821544
"""
1545+
ddoc = DesignDocument(self, ddoc_id)
1546+
return self._get_search_result(
1547+
'/'.join((ddoc.document_url, '_search', index_name)),
1548+
**query_params
1549+
)
1550+
1551+
def _get_search_result(self, query_url, **query_params):
1552+
""" Get search results helper. """
13831553
param_q = query_params.get('q')
13841554
param_query = query_params.get('query')
13851555
# Either q or query parameter is required
@@ -1394,9 +1564,8 @@ def get_search_result(self, ddoc_id, index_name, **query_params):
13941564
raise CloudantArgumentError(106, key, SEARCH_INDEX_ARGS[key])
13951565
# Execute query search
13961566
headers = {'Content-Type': 'application/json'}
1397-
ddoc = DesignDocument(self, ddoc_id)
13981567
resp = self.r_session.post(
1399-
'/'.join([ddoc.document_url, '_search', index_name]),
1568+
query_url,
14001569
headers=headers,
14011570
data=json.dumps(query_params, cls=self.client.encoder)
14021571
)

0 commit comments

Comments
 (0)