Skip to content

Commit 11967eb

Browse files
committed
PYTHON-1784 Add filter support to list_collection_names
Adhere to enumerate collection spec for setting nameOnly when filter is provided to allow filtering based on collection options.
1 parent 4169a04 commit 11967eb

File tree

4 files changed

+85
-20
lines changed

4 files changed

+85
-20
lines changed

doc/changelog.rst

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,8 @@ Changes in Version 3.8.0.dev0
8282
:meth:`~pymongo.cursor.Cursor.comment` as the "comment" top-level
8383
command option instead of "$comment". Also, note that "comment" must be a
8484
string.
85-
85+
- Add the ``filter`` parameter to
86+
:meth:`~pymongo.database.Database.list_collection_names`.
8687

8788
Issues Resolved
8889
...............

pymongo/database.py

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -361,7 +361,8 @@ def create_collection(self, name, codec_options=None,
361361
Removed deprecated argument: options
362362
"""
363363
with self.__client._tmp_session(session) as s:
364-
if name in self.list_collection_names(session=s):
364+
if name in self.list_collection_names(
365+
filter={"name": name}, session=s):
365366
raise CollectionInvalid("collection %s already exists" % name)
366367

367368
return Collection(self, name, True, codec_options,
@@ -651,12 +652,14 @@ def _list_collections(self, sock_info, slave_okay, session,
651652
cursor = self._command(sock_info, cmd, slave_okay)["cursor"]
652653
return CommandCursor(coll, cursor, sock_info.address)
653654

654-
def list_collections(self, session=None, **kwargs):
655+
def list_collections(self, session=None, filter=None, **kwargs):
655656
"""Get a cursor over the collectons of this database.
656657
657658
:Parameters:
658659
- `session` (optional): a
659660
:class:`~pymongo.client_session.ClientSession`.
661+
- `filter` (optional): A query document to filter the list of
662+
collections returned from the listCollections command.
660663
- `**kwargs` (optional): Optional parameters of the
661664
`listCollections command
662665
<https://docs.mongodb.com/manual/reference/command/listCollections/>`_
@@ -668,6 +671,8 @@ def list_collections(self, session=None, **kwargs):
668671
669672
.. versionadded:: 3.6
670673
"""
674+
if filter is not None:
675+
kwargs['filter'] = filter
671676
read_pref = ((session and session._txn_read_preference())
672677
or ReadPreference.PRIMARY)
673678
with self.__client._socket_for_reads(
@@ -676,18 +681,42 @@ def list_collections(self, session=None, **kwargs):
676681
sock_info, slave_okay, session, read_preference=read_pref,
677682
**kwargs)
678683

679-
def list_collection_names(self, session=None):
684+
def list_collection_names(self, session=None, filter=None, **kwargs):
680685
"""Get a list of all the collection names in this database.
681686
687+
For example, to list all non-system collections::
688+
689+
filter = {"name": {"$regex": r"^(?!system\.)"}}
690+
db.list_collection_names(filter=filter)
691+
682692
:Parameters:
683693
- `session` (optional): a
684694
:class:`~pymongo.client_session.ClientSession`.
695+
- `filter` (optional): A query document to filter the list of
696+
collections returned from the listCollections command.
697+
- `**kwargs` (optional): Optional parameters of the
698+
`listCollections command
699+
<https://docs.mongodb.com/manual/reference/command/listCollections/>`_
700+
can be passed as keyword arguments to this method. The supported
701+
options differ by server version.
702+
703+
.. versionchanged:: 3.8
704+
Added the ``filter`` and ``**kwargs`` parameters.
685705
686706
.. versionadded:: 3.6
687707
"""
708+
if filter is None:
709+
kwargs["nameOnly"] = True
710+
else:
711+
# The enumerate collections spec states that "drivers MUST NOT set
712+
# nameOnly if a filter specifies any keys other than name."
713+
common.validate_is_mapping("filter", filter)
714+
kwargs["filter"] = filter
715+
if not filter or (len(filter) == 1 and "name" in filter):
716+
kwargs["nameOnly"] = True
717+
688718
return [result["name"]
689-
for result in self.list_collections(session=session,
690-
nameOnly=True)]
719+
for result in self.list_collections(session=session, **kwargs)]
691720

692721
def collection_names(self, include_system_collections=True,
693722
session=None):

test/test_database.py

Lines changed: 45 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,8 @@
5656
rs_or_single_client_noauth,
5757
rs_or_single_client,
5858
server_started_with_auth,
59-
IMPOSSIBLE_WRITE_CONCERN)
59+
IMPOSSIBLE_WRITE_CONCERN,
60+
OvertCommandListener)
6061

6162

6263
if PY3:
@@ -156,7 +157,7 @@ def test_create_collection(self):
156157
self.assertTrue(u"test.foo" in db.list_collection_names())
157158
self.assertRaises(CollectionInvalid, db.create_collection, "test.foo")
158159

159-
def _test_collection_names(self, meth, test_no_system):
160+
def _test_collection_names(self, meth, **no_system_kwargs):
160161
db = Database(self.client, "pymongo_test")
161162
db.test.insert_one({"dummy": u"object"})
162163
db.test.mike.insert_one({"dummy": u"object"})
@@ -167,13 +168,11 @@ def _test_collection_names(self, meth, test_no_system):
167168
for coll in colls:
168169
self.assertTrue("$" not in coll)
169170

170-
if test_no_system:
171-
db.systemcoll.test.insert_one({})
172-
no_system_collections = getattr(
173-
db, meth)(include_system_collections=False)
174-
for coll in no_system_collections:
175-
self.assertTrue(not coll.startswith("system."))
176-
self.assertIn("systemcoll.test", no_system_collections)
171+
db.systemcoll.test.insert_one({})
172+
no_system_collections = getattr(db, meth)(**no_system_kwargs)
173+
for coll in no_system_collections:
174+
self.assertTrue(not coll.startswith("system."))
175+
self.assertIn("systemcoll.test", no_system_collections)
177176

178177
# Force more than one batch.
179178
db = self.client.many_collections
@@ -186,10 +185,45 @@ def _test_collection_names(self, meth, test_no_system):
186185
self.client.drop_database("many_collections")
187186

188187
def test_collection_names(self):
189-
self._test_collection_names('collection_names', True)
188+
self._test_collection_names(
189+
'collection_names', include_system_collections=False)
190190

191191
def test_list_collection_names(self):
192-
self._test_collection_names('list_collection_names', False)
192+
self._test_collection_names(
193+
'list_collection_names', filter={
194+
"name": {"$regex": r"^(?!system\.)"}})
195+
196+
def test_list_collection_names_filter(self):
197+
listener = OvertCommandListener()
198+
results = listener.results
199+
client = rs_or_single_client(event_listeners=[listener])
200+
db = client[self.db.name]
201+
db.capped.drop()
202+
db.create_collection("capped", capped=True, size=4096)
203+
db.capped.insert_one({})
204+
db.non_capped.insert_one({})
205+
self.addCleanup(client.drop_database, db.name)
206+
207+
# Should not send nameOnly.
208+
for filter in ({'options.capped': True},
209+
{'options.capped': True, 'name': 'capped'}):
210+
results.clear()
211+
names = db.list_collection_names(filter=filter)
212+
self.assertEqual(names, ["capped"])
213+
self.assertNotIn("nameOnly", results["started"][0].command)
214+
215+
# Should send nameOnly (except on 2.6).
216+
for filter in (None, {}, {'name': {'$in': ['capped', 'non_capped']}}):
217+
results.clear()
218+
names = db.list_collection_names(filter=filter)
219+
self.assertIn("capped", names)
220+
self.assertIn("non_capped", names)
221+
command = results["started"][0].command
222+
if client_context.version >= (3, 0):
223+
self.assertIn("nameOnly", command)
224+
self.assertTrue(command["nameOnly"])
225+
else:
226+
self.assertNotIn("nameOnly", command)
193227

194228
def test_list_collections(self):
195229
self.client.drop_database("pymongo_test")

test/utils.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -408,9 +408,10 @@ def server_is_master_with_slave(client):
408408

409409

410410
def drop_collections(db):
411-
for coll in db.list_collection_names():
412-
if not coll.startswith('system'):
413-
db.drop_collection(coll)
411+
# Drop all non-system collections in this database.
412+
for coll in db.list_collection_names(
413+
filter={"name": {"$regex": r"^(?!system\.)"}}):
414+
db.drop_collection(coll)
414415

415416

416417
def remove_all_users(db):

0 commit comments

Comments
 (0)