async def command( self, dbname: str, spec: SON, read_preference: Optional[Union[_ALL_READ_PREFERENCES]] = None, codec_options: Optional[CodecOptions] = None, check: bool = True, allowable_errors: Optional[List[str]] = None, check_keys: bool = False, read_concern: ReadConcern = DEFAULT_READ_CONCERN ) -> MutableMapping: if self.max_wire_version < 4 and not read_concern.ok_for_legacy: raise ConfigurationError( 'Read concern of level {} is not valid with max wire version of {}' .format(read_concern.level, self.max_wire_version)) read_preference = read_preference or self.options.read_preference codec_options = codec_options or self.options.codec_options name = next(iter(spec)) ns = dbname + '.$cmd' if read_preference != ReadPreference.PRIMARY: flags = 4 else: flags = 0 if self.is_mongos: spec = message._maybe_add_read_preference(spec, read_preference) if read_concern.level: spec['readConcern'] = read_concern.document # See explanation in perform_operation method request_id = None while request_id is None or request_id in self.__request_futures: request_id, msg, size = message.query(flags, ns, 0, -1, spec, None, codec_options, check_keys) if size > self.max_bson_size + message._COMMAND_OVERHEAD: message._raise_document_too_large( name, size, self.max_bson_size + message._COMMAND_OVERHEAD) response_future = asyncio.Future() self.__request_futures[request_id] = response_future self.send_message(msg) response = await response_future unpacked = helpers._unpack_response(response, codec_options=codec_options) response_doc = unpacked['data'][0] if check: helpers._check_command_response(response_doc, None, allowable_errors) return response_doc
def encrypt(self, database, cmd, check_keys, codec_options): """Encrypt a MongoDB command. :Parameters: - `database`: The database for this command. - `cmd`: A command document. - `check_keys`: If True, check `cmd` for invalid keys. - `codec_options`: The CodecOptions to use while encoding `cmd`. :Returns: The encrypted command to execute. """ self._check_closed() # Workaround for $clusterTime which is incompatible with # check_keys. cluster_time = check_keys and cmd.pop('$clusterTime', None) encoded_cmd = _dict_to_bson(cmd, check_keys, codec_options) max_cmd_size = _MAX_ENC_BSON_SIZE + _COMMAND_OVERHEAD if len(encoded_cmd) > max_cmd_size: raise _raise_document_too_large(next(iter(cmd)), len(encoded_cmd), max_cmd_size) with _wrap_encryption_errors(): encrypted_cmd = self._auto_encrypter.encrypt(database, encoded_cmd) # TODO: PYTHON-1922 avoid decoding the encrypted_cmd. encrypt_cmd = _inflate_bson(encrypted_cmd, DEFAULT_RAW_BSON_OPTIONS) if cluster_time: encrypt_cmd['$clusterTime'] = cluster_time return encrypt_cmd
def command(sock, dbname, spec, slave_ok, is_mongos, read_preference, codec_options, check=True, allowable_errors=None, address=None, check_keys=False, listeners=None, max_bson_size=None, read_concern=DEFAULT_READ_CONCERN): """Execute a command over the socket, or raise socket.error. :Parameters: - `sock`: a raw socket instance - `dbname`: name of the database on which to run the command - `spec`: a command document as a dict, SON, or mapping object - `slave_ok`: whether to set the SlaveOkay wire protocol bit - `is_mongos`: are we connected to a mongos? - `read_preference`: a read preference - `codec_options`: a CodecOptions instance - `check`: raise OperationFailure if there are errors - `allowable_errors`: errors to ignore if `check` is True - `address`: the (host, port) of `sock` - `check_keys`: if True, check `spec` for invalid keys - `listeners`: An instance of :class:`~pymongo.monitoring.EventListeners` - `max_bson_size`: The maximum encoded bson size for this server - `read_concern`: The read concern for this command. """ name = next(iter(spec)) ns = dbname + '.$cmd' flags = 4 if slave_ok else 0 # Publish the original command document. orig = spec if is_mongos: spec = message._maybe_add_read_preference(spec, read_preference) if read_concern.level: spec['readConcern'] = read_concern.document publish = listeners is not None and listeners.enabled_for_commands if publish: start = datetime.datetime.now() request_id, msg, size = message.query(flags, ns, 0, -1, spec, None, codec_options, check_keys) if (max_bson_size is not None and size > max_bson_size + message._COMMAND_OVERHEAD): message._raise_document_too_large( name, size, max_bson_size + message._COMMAND_OVERHEAD) if publish: encoding_duration = datetime.datetime.now() - start listeners.publish_command_start(orig, dbname, request_id, address) start = datetime.datetime.now() try: sock.sendall(msg) response = receive_message(sock, 1, request_id) unpacked = helpers._unpack_response( response, codec_options=codec_options) response_doc = unpacked['data'][0] if check: helpers._check_command_response(response_doc, None, allowable_errors) except Exception as exc: if publish: duration = (datetime.datetime.now() - start) + encoding_duration if isinstance(exc, (NotMasterError, OperationFailure)): failure = exc.details else: failure = message._convert_exception(exc) listeners.publish_command_failure( duration, failure, name, request_id, address) raise if publish: duration = (datetime.datetime.now() - start) + encoding_duration listeners.publish_command_success( duration, response_doc, name, request_id, address) return response_doc
def command(sock, dbname, spec, slave_ok, is_mongos, read_preference, codec_options, session, client, check=True, allowable_errors=None, address=None, check_keys=False, listeners=None, max_bson_size=None, read_concern=None, parse_write_concern_error=False, collation=None, compression_ctx=None, use_op_msg=False, unacknowledged=False): """Execute a command over the socket, or raise socket.error. :Parameters: - `sock`: a raw socket instance - `dbname`: name of the database on which to run the command - `spec`: a command document as an ordered dict type, eg SON. - `slave_ok`: whether to set the SlaveOkay wire protocol bit - `is_mongos`: are we connected to a mongos? - `read_preference`: a read preference - `codec_options`: a CodecOptions instance - `session`: optional ClientSession instance. - `client`: optional MongoClient instance for updating $clusterTime. - `check`: raise OperationFailure if there are errors - `allowable_errors`: errors to ignore if `check` is True - `address`: the (host, port) of `sock` - `check_keys`: if True, check `spec` for invalid keys - `listeners`: An instance of :class:`~pymongo.monitoring.EventListeners` - `max_bson_size`: The maximum encoded bson size for this server - `read_concern`: The read concern for this command. - `parse_write_concern_error`: Whether to parse the ``writeConcernError`` field in the command response. - `collation`: The collation for this command. """ name = next(iter(spec)) ns = dbname + '.$cmd' flags = 4 if slave_ok else 0 # Publish the original command document, perhaps with lsid and $clusterTime. orig = spec if is_mongos and not use_op_msg: spec = message._maybe_add_read_preference(spec, read_preference) if read_concern and not (session and session._in_transaction): if read_concern.level: spec['readConcern'] = read_concern.document if (session and session.options.causal_consistency and session.operation_time is not None): spec.setdefault('readConcern', {})['afterClusterTime'] = session.operation_time if collation is not None: spec['collation'] = collation publish = listeners is not None and listeners.enabled_for_commands if publish: start = datetime.datetime.now() if compression_ctx and name.lower() in _NO_COMPRESSION: compression_ctx = None if use_op_msg: flags = 2 if unacknowledged else 0 request_id, msg, size, max_doc_size = message._op_msg( flags, spec, dbname, read_preference, slave_ok, check_keys, codec_options, ctx=compression_ctx) # If this is an unacknowledged write then make sure the encoded doc(s) # are small enough, otherwise rely on the server to return an error. if (unacknowledged and max_bson_size is not None and max_doc_size > max_bson_size): message._raise_document_too_large(name, size, max_bson_size) else: request_id, msg, size = message.query(flags, ns, 0, -1, spec, None, codec_options, check_keys, compression_ctx) if (max_bson_size is not None and size > max_bson_size + message._COMMAND_OVERHEAD): message._raise_document_too_large( name, size, max_bson_size + message._COMMAND_OVERHEAD) if publish: encoding_duration = datetime.datetime.now() - start listeners.publish_command_start(orig, dbname, request_id, address) start = datetime.datetime.now() try: sock.sendall(msg) if use_op_msg and unacknowledged: # Unacknowledged, fake a successful command response. response_doc = {"ok": 1} else: reply = receive_message(sock, request_id) unpacked_docs = reply.unpack_response(codec_options=codec_options) response_doc = unpacked_docs[0] if client: client._receive_cluster_time(response_doc, session) if check: helpers._check_command_response( response_doc, None, allowable_errors, parse_write_concern_error=parse_write_concern_error) except Exception as exc: if publish: duration = (datetime.datetime.now() - start) + encoding_duration if isinstance(exc, (NotMasterError, OperationFailure)): failure = exc.details else: failure = message._convert_exception(exc) listeners.publish_command_failure(duration, failure, name, request_id, address) raise if publish: duration = (datetime.datetime.now() - start) + encoding_duration listeners.publish_command_success(duration, response_doc, name, request_id, address) return response_doc
def command(sock, dbname, spec, slave_ok, is_mongos, read_preference, codec_options, session, client, check=True, allowable_errors=None, address=None, check_keys=False, listeners=None, max_bson_size=None, read_concern=DEFAULT_READ_CONCERN, parse_write_concern_error=False, collation=None, retryable_write=False): """Execute a command over the socket, or raise socket.error. :Parameters: - `sock`: a raw socket instance - `dbname`: name of the database on which to run the command - `spec`: a command document as a dict, SON, or mapping object - `slave_ok`: whether to set the SlaveOkay wire protocol bit - `is_mongos`: are we connected to a mongos? - `read_preference`: a read preference - `codec_options`: a CodecOptions instance - `session`: optional ClientSession instance. - `client`: optional MongoClient instance for updating $clusterTime. - `check`: raise OperationFailure if there are errors - `allowable_errors`: errors to ignore if `check` is True - `address`: the (host, port) of `sock` - `check_keys`: if True, check `spec` for invalid keys - `listeners`: An instance of :class:`~pymongo.monitoring.EventListeners` - `max_bson_size`: The maximum encoded bson size for this server - `read_concern`: The read concern for this command. - `parse_write_concern_error`: Whether to parse the ``writeConcernError`` field in the command response. - `collation`: The collation for this command. - `retryable_write`: True if this command is a retryable write. """ name = next(iter(spec)) ns = dbname + '.$cmd' flags = 4 if slave_ok else 0 if (client or session) and not isinstance(spec, ORDERED_TYPES): # Ensure command name remains in first place. spec = SON(spec) if session: spec['lsid'] = session._use_lsid() if retryable_write: spec['txnNumber'] = session._transaction_id() if client: client._send_cluster_time(spec, session) # Publish the original command document, perhaps with lsid and $clusterTime. orig = spec if is_mongos: spec = message._maybe_add_read_preference(spec, read_preference) if read_concern.level: spec['readConcern'] = read_concern.document if (session and session.options.causal_consistency and session.operation_time is not None): spec.setdefault('readConcern', {})['afterClusterTime'] = session.operation_time if collation is not None: spec['collation'] = collation publish = listeners is not None and listeners.enabled_for_commands if publish: start = datetime.datetime.now() request_id, msg, size = message.query(flags, ns, 0, -1, spec, None, codec_options, check_keys) if (max_bson_size is not None and size > max_bson_size + message._COMMAND_OVERHEAD): message._raise_document_too_large( name, size, max_bson_size + message._COMMAND_OVERHEAD) if publish: encoding_duration = datetime.datetime.now() - start listeners.publish_command_start(orig, dbname, request_id, address) start = datetime.datetime.now() try: sock.sendall(msg) reply = receive_message(sock, request_id) unpacked_docs = reply.unpack_response(codec_options=codec_options) response_doc = unpacked_docs[0] if client: client._receive_cluster_time(response_doc) if session: session._advance_cluster_time(response_doc.get('$clusterTime')) session._advance_operation_time( response_doc.get('operationTime')) if check: helpers._check_command_response( response_doc, None, allowable_errors, parse_write_concern_error=parse_write_concern_error) except Exception as exc: if publish: duration = (datetime.datetime.now() - start) + encoding_duration if isinstance(exc, (NotMasterError, OperationFailure)): failure = exc.details else: failure = message._convert_exception(exc) listeners.publish_command_failure(duration, failure, name, request_id, address) raise if publish: duration = (datetime.datetime.now() - start) + encoding_duration listeners.publish_command_success(duration, response_doc, name, request_id, address) return response_doc
def command(sock_info, dbname, spec, secondary_ok, is_mongos, read_preference, codec_options, session, client, check=True, allowable_errors=None, address=None, check_keys=False, listeners=None, max_bson_size=None, read_concern=None, parse_write_concern_error=False, collation=None, compression_ctx=None, use_op_msg=False, unacknowledged=False, user_fields=None, exhaust_allowed=False): """Execute a command over the socket, or raise socket.error. :Parameters: - `sock`: a raw socket instance - `dbname`: name of the database on which to run the command - `spec`: a command document as an ordered dict type, eg SON. - `secondary_ok`: whether to set the secondaryOkay wire protocol bit - `is_mongos`: are we connected to a mongos? - `read_preference`: a read preference - `codec_options`: a CodecOptions instance - `session`: optional ClientSession instance. - `client`: optional MongoClient instance for updating $clusterTime. - `check`: raise OperationFailure if there are errors - `allowable_errors`: errors to ignore if `check` is True - `address`: the (host, port) of `sock` - `check_keys`: if True, check `spec` for invalid keys - `listeners`: An instance of :class:`~pymongo.monitoring.EventListeners` - `max_bson_size`: The maximum encoded bson size for this server - `read_concern`: The read concern for this command. - `parse_write_concern_error`: Whether to parse the ``writeConcernError`` field in the command response. - `collation`: The collation for this command. - `compression_ctx`: optional compression Context. - `use_op_msg`: True if we should use OP_MSG. - `unacknowledged`: True if this is an unacknowledged command. - `user_fields` (optional): Response fields that should be decoded using the TypeDecoders from codec_options, passed to bson._decode_all_selective. - `exhaust_allowed`: True if we should enable OP_MSG exhaustAllowed. """ name = next(iter(spec)) ns = dbname + '.$cmd' flags = 4 if secondary_ok else 0 speculative_hello = False # Publish the original command document, perhaps with lsid and $clusterTime. orig = spec if is_mongos and not use_op_msg: spec = message._maybe_add_read_preference(spec, read_preference) if read_concern and not (session and session.in_transaction): if read_concern.level: spec['readConcern'] = read_concern.document if session: session._update_read_concern(spec, sock_info) if collation is not None: spec['collation'] = collation publish = listeners is not None and listeners.enabled_for_commands if publish: start = datetime.datetime.now() speculative_hello = _is_speculative_authenticate(name, spec) if compression_ctx and name.lower() in _NO_COMPRESSION: compression_ctx = None if (client and client._encrypter and not client._encrypter._bypass_auto_encryption): spec = orig = client._encrypter.encrypt( dbname, spec, check_keys, codec_options) # We already checked the keys, no need to do it again. check_keys = False if use_op_msg: flags = _OpMsg.MORE_TO_COME if unacknowledged else 0 flags |= _OpMsg.EXHAUST_ALLOWED if exhaust_allowed else 0 request_id, msg, size, max_doc_size = message._op_msg( flags, spec, dbname, read_preference, secondary_ok, check_keys, codec_options, ctx=compression_ctx) # If this is an unacknowledged write then make sure the encoded doc(s) # are small enough, otherwise rely on the server to return an error. if (unacknowledged and max_bson_size is not None and max_doc_size > max_bson_size): message._raise_document_too_large(name, size, max_bson_size) else: request_id, msg, size = message._query( flags, ns, 0, -1, spec, None, codec_options, check_keys, compression_ctx) if (max_bson_size is not None and size > max_bson_size + message._COMMAND_OVERHEAD): message._raise_document_too_large( name, size, max_bson_size + message._COMMAND_OVERHEAD) if publish: encoding_duration = datetime.datetime.now() - start listeners.publish_command_start(orig, dbname, request_id, address, service_id=sock_info.service_id) start = datetime.datetime.now() try: sock_info.sock.sendall(msg) if use_op_msg and unacknowledged: # Unacknowledged, fake a successful command response. reply = None response_doc = {"ok": 1} else: reply = receive_message(sock_info, request_id) sock_info.more_to_come = reply.more_to_come unpacked_docs = reply.unpack_response( codec_options=codec_options, user_fields=user_fields) response_doc = unpacked_docs[0] if client: client._process_response(response_doc, session) if check: helpers._check_command_response( response_doc, sock_info.max_wire_version, allowable_errors, parse_write_concern_error=parse_write_concern_error) except Exception as exc: if publish: duration = (datetime.datetime.now() - start) + encoding_duration if isinstance(exc, (NotPrimaryError, OperationFailure)): failure = exc.details else: failure = message._convert_exception(exc) listeners.publish_command_failure( duration, failure, name, request_id, address, service_id=sock_info.service_id) raise if publish: duration = (datetime.datetime.now() - start) + encoding_duration listeners.publish_command_success( duration, response_doc, name, request_id, address, service_id=sock_info.service_id, speculative_hello=speculative_hello) if client and client._encrypter and reply: decrypted = client._encrypter.decrypt(reply.raw_command_response()) response_doc = _decode_all_selective(decrypted, codec_options, user_fields)[0] return response_doc
def command(sock, dbname, spec, slave_ok, is_mongos, read_preference, codec_options, check=True, allowable_errors=None, address=None, check_keys=False, listeners=None, max_bson_size=None, read_concern=DEFAULT_READ_CONCERN): """Execute a command over the socket, or raise socket.error. :Parameters: - `sock`: a raw socket instance - `dbname`: name of the database on which to run the command - `spec`: a command document as a dict, SON, or mapping object - `slave_ok`: whether to set the SlaveOkay wire protocol bit - `is_mongos`: are we connected to a mongos? - `read_preference`: a read preference - `codec_options`: a CodecOptions instance - `check`: raise OperationFailure if there are errors - `allowable_errors`: errors to ignore if `check` is True - `address`: the (host, port) of `sock` - `check_keys`: if True, check `spec` for invalid keys - `listeners`: An instance of :class:`~pymongo.monitoring.EventListeners` - `max_bson_size`: The maximum encoded bson size for this server - `read_concern`: The read concern for this command. """ name = next(iter(spec)) ns = dbname + '.$cmd' flags = 4 if slave_ok else 0 # Publish the original command document. orig = spec if is_mongos: spec = message._maybe_add_read_preference(spec, read_preference) if read_concern.level: spec['readConcern'] = read_concern.document publish = listeners is not None and listeners.enabled_for_commands if publish: start = datetime.datetime.now() request_id, msg, size = message.query(flags, ns, 0, -1, spec, None, codec_options, check_keys) if (max_bson_size is not None and size > max_bson_size + message._COMMAND_OVERHEAD): message._raise_document_too_large( name, size, max_bson_size + message._COMMAND_OVERHEAD) if publish: encoding_duration = datetime.datetime.now() - start listeners.publish_command_start(orig, dbname, request_id, address) start = datetime.datetime.now() try: sock.sendall(msg) response = receive_message(sock, 1, request_id) unpacked = helpers._unpack_response(response, codec_options=codec_options) response_doc = unpacked['data'][0] if check: helpers._check_command_response(response_doc, None, allowable_errors) except Exception as exc: if publish: duration = (datetime.datetime.now() - start) + encoding_duration if isinstance(exc, (NotMasterError, OperationFailure)): failure = exc.details else: failure = message._convert_exception(exc) listeners.publish_command_failure(duration, failure, name, request_id, address) raise if publish: duration = (datetime.datetime.now() - start) + encoding_duration listeners.publish_command_success(duration, response_doc, name, request_id, address) return response_doc
async def _do_batched_write_command(self, namespace: str, operation: str, command: SON, docs: Iterable[dict], check_keys: bool, opts: CodecOptions, connection) -> List[Tuple[int, dict]]: # Max BSON object size + 16k - 2 bytes for ending NUL bytes. # Server guarantees there is enough room: SERVER-10643. max_cmd_size = connection.max_bson_size + _COMMAND_OVERHEAD ordered = command.get('ordered', True) buf = BytesIO() # Save space for message length and request id buf.write(_ZERO_64) # responseTo, opCode buf.write(b'\x00\x00\x00\x00\xd4\x07\x00\x00') # No options buf.write(_ZERO_32) # Namespace as C string buf.write(namespace.encode()) buf.write(_ZERO_8) # Skip: 0, Limit: -1 buf.write(_SKIPLIM) # Where to write command document length command_start = buf.tell() buf.write(BSON.encode(command)) # Start of payload buf.seek(-1, 2) # Work around some Jython weirdness. buf.truncate() try: buf.write(_OP_MAP[operation]) except KeyError: raise InvalidOperation('Unknown command') if operation in (_UPDATE, _DELETE): check_keys = False # Where to write list document length list_start = buf.tell() - 4 # If there are multiple batches we'll # merge results in the caller. results = [] idx = 0 idx_offset = 0 has_docs = False for doc in docs: has_docs = True key = str(idx).encode() value = BSON.encode(doc, check_keys, opts) # Send a batch? enough_data = (buf.tell() + len(key) + len(value) + 2) >= max_cmd_size enough_documents = (idx >= connection.max_write_batch_size) if enough_data or enough_documents: if not idx: write_op = 'insert' if operation == _INSERT else None _raise_document_too_large(write_op, len(value), connection.max_bson_size) result = await self._send_message(connection, buf, command_start, list_start) results.append((idx_offset, result)) if ordered and 'writeErrors' in result: return results # Truncate back to the start of list elements buf.seek(list_start + 4) buf.truncate() idx_offset += idx idx = 0 key = b'0' buf.write(_BSONOBJ) buf.write(key) buf.write(_ZERO_8) buf.write(value) idx += 1 if not has_docs: raise InvalidOperation("cannot do an empty bulk write") result = await self._send_message(connection, buf, command_start, list_start) results.append((idx_offset, result)) return results
async def command(self, dbname: str, spec: SON, read_preference: Optional[ Union[_ALL_READ_PREFERENCES]] = None, codec_options: Optional[CodecOptions] = None, check: bool = True, allowable_errors: Optional[List[str]] = None, check_keys: bool = False, read_concern: ReadConcern = DEFAULT_READ_CONCERN, write_concern: Optional[WriteConcern] = None, parse_write_concern_error: bool = False, collation: Optional[Union[Collation, dict]] = None, ignore_connected: bool = False) -> MutableMapping: if self.max_wire_version < 4 and not read_concern.ok_for_legacy: raise ConfigurationError( 'Read concern of level {} is not valid with max wire version of {}' .format(read_concern.level, self.max_wire_version)) if not (write_concern is None or write_concern.acknowledged or collation is None): raise ConfigurationError( 'Collation is unsupported for unacknowledged writes.') if self.max_wire_version >= 5 and write_concern: spec['writeConcern'] = write_concern.document elif self.max_wire_version < 5 and collation is not None: raise ConfigurationError( 'Must be connected to MongoDB 3.4+ to use a collation.') read_preference = read_preference or self.options.read_preference codec_options = codec_options or self.options.codec_options name = next(iter(spec)) ns = dbname + '.$cmd' if read_preference != ReadPreference.PRIMARY: flags = 4 else: flags = 0 if self.is_mongos: spec = message._maybe_add_read_preference(spec, read_preference) if read_concern.level: spec['readConcern'] = read_concern.document if collation: spec['collation'] = collation # See explanation in perform_operation method request_id = None while request_id is None or request_id in self.__request_futures: request_id, msg, size = message.query(flags, ns, 0, -1, spec, None, codec_options, check_keys) if size > self.max_bson_size + message._COMMAND_OVERHEAD: message._raise_document_too_large( name, size, self.max_bson_size + message._COMMAND_OVERHEAD) if not ignore_connected: self._check_connected() response_future = asyncio.Future() self.__request_futures[request_id] = response_future self.send_message(msg) try: response = await response_future except asyncio.CancelledError: if request_id in self.__request_futures: del self.__request_futures[request_id] raise unpacked = helpers._unpack_response(response, codec_options=codec_options) response_doc = unpacked['data'][0] if check: helpers._check_command_response( response_doc, None, allowable_errors, parse_write_concern_error=parse_write_concern_error) return response_doc
def command(sock, dbname, spec, slave_ok, is_mongos, read_preference, codec_options, session, client, check=True, allowable_errors=None, address=None, check_keys=False, listeners=None, max_bson_size=None, read_concern=None, parse_write_concern_error=False, collation=None, compression_ctx=None, use_op_msg=False, unacknowledged=False, user_fields=None): """Execute a command over the socket, or raise socket.error. :Parameters: - `sock`: a raw socket instance - `dbname`: name of the database on which to run the command - `spec`: a command document as an ordered dict type, eg SON. - `slave_ok`: whether to set the SlaveOkay wire protocol bit - `is_mongos`: are we connected to a mongos? - `read_preference`: a read preference - `codec_options`: a CodecOptions instance - `session`: optional ClientSession instance. - `client`: optional MongoClient instance for updating $clusterTime. - `check`: raise OperationFailure if there are errors - `allowable_errors`: errors to ignore if `check` is True - `address`: the (host, port) of `sock` - `check_keys`: if True, check `spec` for invalid keys - `listeners`: An instance of :class:`~pymongo.monitoring.EventListeners` - `max_bson_size`: The maximum encoded bson size for this server - `read_concern`: The read concern for this command. - `parse_write_concern_error`: Whether to parse the ``writeConcernError`` field in the command response. - `collation`: The collation for this command. - `compression_ctx`: optional compression Context. - `use_op_msg`: True if we should use OP_MSG. - `unacknowledged`: True if this is an unacknowledged command. - `user_fields` (optional): Response fields that should be decoded using the TypeDecoders from codec_options, passed to bson._decode_all_selective. """ name = next(iter(spec)) ns = dbname + '.$cmd' flags = 4 if slave_ok else 0 # Publish the original command document, perhaps with lsid and $clusterTime. orig = spec if is_mongos and not use_op_msg: spec = message._maybe_add_read_preference(spec, read_preference) if read_concern and not (session and session._in_transaction): if read_concern.level: spec['readConcern'] = read_concern.document if (session and session.options.causal_consistency and session.operation_time is not None): spec.setdefault( 'readConcern', {})['afterClusterTime'] = session.operation_time if collation is not None: spec['collation'] = collation publish = listeners is not None and listeners.enabled_for_commands if publish: start = datetime.datetime.now() if compression_ctx and name.lower() in _NO_COMPRESSION: compression_ctx = None if use_op_msg: flags = 2 if unacknowledged else 0 request_id, msg, size, max_doc_size = message._op_msg( flags, spec, dbname, read_preference, slave_ok, check_keys, codec_options, ctx=compression_ctx) # If this is an unacknowledged write then make sure the encoded doc(s) # are small enough, otherwise rely on the server to return an error. if (unacknowledged and max_bson_size is not None and max_doc_size > max_bson_size): message._raise_document_too_large(name, size, max_bson_size) else: request_id, msg, size = message.query( flags, ns, 0, -1, spec, None, codec_options, check_keys, compression_ctx) if (max_bson_size is not None and size > max_bson_size + message._COMMAND_OVERHEAD): message._raise_document_too_large( name, size, max_bson_size + message._COMMAND_OVERHEAD) if publish: encoding_duration = datetime.datetime.now() - start listeners.publish_command_start(orig, dbname, request_id, address) start = datetime.datetime.now() try: sock.sendall(msg) if use_op_msg and unacknowledged: # Unacknowledged, fake a successful command response. response_doc = {"ok": 1} else: reply = receive_message(sock, request_id) unpacked_docs = reply.unpack_response( codec_options=codec_options, user_fields=user_fields) response_doc = unpacked_docs[0] if client: client._process_response(response_doc, session) if check: helpers._check_command_response( response_doc, None, allowable_errors, parse_write_concern_error=parse_write_concern_error) except Exception as exc: if publish: duration = (datetime.datetime.now() - start) + encoding_duration if isinstance(exc, (NotMasterError, OperationFailure)): failure = exc.details else: failure = message._convert_exception(exc) listeners.publish_command_failure( duration, failure, name, request_id, address) raise if publish: duration = (datetime.datetime.now() - start) + encoding_duration listeners.publish_command_success( duration, response_doc, name, request_id, address) return response_doc
def command(sock, dbname, spec, slave_ok, is_mongos, read_preference, codec_options, session, client, check=True, allowable_errors=None, address=None, check_keys=False, listeners=None, max_bson_size=None, read_concern=None, parse_write_concern_error=False, collation=None, compression_ctx=None): """Execute a command over the socket, or raise socket.error. :Parameters: - `sock`: a raw socket instance - `dbname`: name of the database on which to run the command - `spec`: a command document as a dict, SON, or mapping object - `slave_ok`: whether to set the SlaveOkay wire protocol bit - `is_mongos`: are we connected to a mongos? - `read_preference`: a read preference - `codec_options`: a CodecOptions instance - `session`: optional ClientSession instance. - `client`: optional MongoClient instance for updating $clusterTime. - `check`: raise OperationFailure if there are errors - `allowable_errors`: errors to ignore if `check` is True - `address`: the (host, port) of `sock` - `check_keys`: if True, check `spec` for invalid keys - `listeners`: An instance of :class:`~pymongo.monitoring.EventListeners` - `max_bson_size`: The maximum encoded bson size for this server - `read_concern`: The read concern for this command. - `parse_write_concern_error`: Whether to parse the ``writeConcernError`` field in the command response. - `collation`: The collation for this command. """ name = next(iter(spec)) ns = dbname + '.$cmd' flags = 4 if slave_ok else 0 # Publish the original command document, perhaps with lsid and $clusterTime. orig = spec if is_mongos: spec = message._maybe_add_read_preference(spec, read_preference) if read_concern: if read_concern.level: spec['readConcern'] = read_concern.document if (session and session.options.causal_consistency and session.operation_time is not None and not session._in_transaction): spec.setdefault( 'readConcern', {})['afterClusterTime'] = session.operation_time if collation is not None: spec['collation'] = collation publish = listeners is not None and listeners.enabled_for_commands if publish: start = datetime.datetime.now() if name.lower() not in _NO_COMPRESSION and compression_ctx: request_id, msg, size = message.query( flags, ns, 0, -1, spec, None, codec_options, check_keys, compression_ctx) else: request_id, msg, size = message.query( flags, ns, 0, -1, spec, None, codec_options, check_keys) if (max_bson_size is not None and size > max_bson_size + message._COMMAND_OVERHEAD): message._raise_document_too_large( name, size, max_bson_size + message._COMMAND_OVERHEAD) if publish: encoding_duration = datetime.datetime.now() - start listeners.publish_command_start(orig, dbname, request_id, address) start = datetime.datetime.now() try: sock.sendall(msg) reply = receive_message(sock, request_id) unpacked_docs = reply.unpack_response(codec_options=codec_options) response_doc = unpacked_docs[0] if client: client._receive_cluster_time(response_doc, session) if check: helpers._check_command_response( response_doc, None, allowable_errors, parse_write_concern_error=parse_write_concern_error) except Exception as exc: if publish: duration = (datetime.datetime.now() - start) + encoding_duration if isinstance(exc, (NotMasterError, OperationFailure)): failure = exc.details else: failure = message._convert_exception(exc) listeners.publish_command_failure( duration, failure, name, request_id, address) raise if publish: duration = (datetime.datetime.now() - start) + encoding_duration listeners.publish_command_success( duration, response_doc, name, request_id, address) return response_doc