1717import  warnings 
1818
1919from  bson .code  import  Code 
20- from  bson .codec_options  import  CodecOptions ,  DEFAULT_CODEC_OPTIONS 
20+ from  bson .codec_options  import  DEFAULT_CODEC_OPTIONS 
2121from  bson .dbref  import  DBRef 
22- from  bson .objectid  import  ObjectId 
2322from  bson .py3compat  import  iteritems , string_type , _unicode 
2423from  bson .son  import  SON 
2524from  pymongo  import  auth , common 
3534from  pymongo .write_concern  import  WriteConcern 
3635
3736
37+ _INDEX_REGEX  =  {"name" : {"$regex" : "^(?!.*\$)" }}
38+ _SYSTEM_FILTER  =  {"filter" : {"name" : {"$regex" : "^(?!system\.)" }}}
39+ 
40+ 
3841def  _check_name (name ):
3942    """Check if a database name is valid. 
4043    """ 
@@ -543,56 +546,73 @@ def command(self, command, value=1, check=True,
543546                                 check , allowable_errors , read_preference ,
544547                                 codec_options , session = session , ** kwargs )
545548
546-     def  _list_collections (self , sock_info , slave_okay , filter = None ,
547-                          session = None ):
549+     def  _list_collections (self , sock_info , slave_okay , session = None , ** kwargs ):
548550        """Internal listCollections helper.""" 
549-         filter  =  filter  or  {}
550-         cmd  =  SON ([("listCollections" , 1 ), ("cursor" , {})])
551- 
552-         if  filter :
553-             cmd ["filter" ] =  filter 
554551
552+         coll  =  self ["$cmd" ]
555553        if  sock_info .max_wire_version  >  2 :
556-             coll  =  self ["$cmd" ]
557-             with  self .__client ._tmp_session (session , close = False ) as  s :
554+             cmd  =  SON ([("listCollections" , 1 ),
555+                        ("cursor" , {})])
556+             cmd .update (kwargs )
557+             with  self .__client ._tmp_session (
558+                     session , close = False ) as  tmp_session :
558559                cursor  =  self ._command (
559-                     sock_info , cmd , slave_okay , session = s )["cursor" ]
560-                 return  CommandCursor (coll , cursor , sock_info .address , session = s ,
561-                                      explicit_session = session  is  not   None )
560+                     sock_info , cmd , slave_okay , session = tmp_session )["cursor" ]
561+                 return  CommandCursor (
562+                     coll ,
563+                     cursor ,
564+                     sock_info .address ,
565+                     session = tmp_session ,
566+                     explicit_session = session  is  not   None )
562567        else :
563-             coll  =  self ["system.namespaces" ]
564-             if  "name"  in  filter :
565-                 if  not  isinstance (filter ["name" ], string_type ):
566-                     raise  TypeError ("filter['name'] must be a string on MongoDB 2.6" )
567-                 filter ["name" ] =  coll .database .name  +  "."  +  filter ["name" ]
568-             res  =  _first_batch (sock_info , coll .database .name , coll .name ,
569-                                filter , 0 , slave_okay ,
570-                                CodecOptions (), ReadPreference .PRIMARY , cmd ,
571-                                self .client ._event_listeners , session = None )
572-             data  =  res ["data" ]
573-             cursor  =  {
574-                 "id" : res ["cursor_id" ],
575-                 "firstBatch" : data ,
576-                 "ns" : coll .full_name ,
577-             }
568+             match  =  _INDEX_REGEX 
569+             if  "filter"  in  kwargs :
570+                 match  =  {"$and" : [_INDEX_REGEX , kwargs ["filter" ]]}
571+             dblen  =  len (self .name .encode ("utf8" ) +  b"." )
572+             pipeline  =  [
573+                 {"$project" : {"name" : {"$substr" : ["$name" , dblen , - 1 ]},
574+                               "options" : 1 }},
575+                 {"$match" : match }
576+             ]
577+             cmd  =  SON ([("aggregate" , "system.namespaces" ),
578+                        ("pipeline" , pipeline ),
579+                        ("cursor" , kwargs .get ("cursor" , {}))])
580+             cursor  =  self ._command (sock_info , cmd , slave_okay )["cursor" ]
578581            return  CommandCursor (coll , cursor , sock_info .address )
579582
580-     def  list_collections (self , filter = None , session = None ):
581-         """Get info about the collections in this database.""" 
583+     def  list_collections (self , session = None , ** kwargs ):
584+         """Get a cursor over the collectons of this database. 
585+ 
586+         :Parameters: 
587+           - `session` (optional): a 
588+             :class:`~pymongo.client_session.ClientSession`. 
589+           - `**kwargs` (optional): Optional parameters of the 
590+             `listCollections command 
591+             <https://docs.mongodb.com/manual/reference/command/listCollections/>`_ 
592+             can be passed as keyword arguments to this method. The supported 
593+             options differ by server version. 
594+ 
595+         :Returns: 
596+           An instance of :class:`~pymongo.command_cursor.CommandCursor`. 
597+ 
598+         .. versionadded:: 3.6 
599+         """ 
582600        with  self .__client ._socket_for_reads (
583601                ReadPreference .PRIMARY ) as  (sock_info , slave_okay ):
602+             return  self ._list_collections (
603+                 sock_info , slave_okay , session = session , ** kwargs )
604+ 
605+     def  list_collection_names (self , session = None ):
606+         """Get a list of all the collection names in this database. 
584607
585-              wire_version  =  sock_info .max_wire_version 
586-              results  =  self ._list_collections (sock_info , slave_okay , filter = filter ,
587-                                               session = session )
588-         for  result  in  results :
589-             if  wire_version  <=  2 :
590-                 name  =  result ["name" ]
591-                 if  "$"  in  name :
592-                     continue 
593-                 result ["name" ] =  name .split ("." , 1 )[1 ]
594-             yield  result 
608+         :Parameters: 
609+           - `session` (optional): a 
610+             :class:`~pymongo.client_session.ClientSession`. 
595611
612+         .. versionadded:: 3.6 
613+         """ 
614+         return  [result ["name" ]
615+                 for  result  in  self .list_collections (session = session )]
596616
597617    def  collection_names (self , include_system_collections = True ,
598618                         session = None ):
@@ -607,17 +627,9 @@ def collection_names(self, include_system_collections=True,
607627        .. versionchanged:: 3.6 
608628           Added ``session`` parameter. 
609629        """ 
610- 
611-         results  =  self .list_collections (session = session )
612- 
613-         # Iterating the cursor to completion may require a socket for getmore. 
614-         # Ensure we do that outside the "with" block so we don't require more 
615-         # than one socket at a time. 
616-         names  =  [result ["name" ] for  result  in  results ]
617- 
618-         if  not  include_system_collections :
619-             names  =  [name  for  name  in  names  if  not  name .startswith ("system." )]
620-         return  names 
630+         kws  =  {} if  include_system_collections  else  _SYSTEM_FILTER 
631+         return  [result ["name" ]
632+                 for  result  in  self .list_collections (session = session , ** kws )]
621633
622634    def  drop_collection (self , name_or_collection , session = None ):
623635        """Drop a collection. 
0 commit comments