PYTHON-472 - Add a RawBSONDocument class that decodes its comprising bytes only on-demand.
This provides an API for inserting and returning raw BSON.
This commit is contained in:
parent
504d4b206c
commit
e4d3392f90
@ -31,7 +31,8 @@ from bson.binary import (Binary, OLD_UUID_SUBTYPE,
|
||||
JAVA_LEGACY, CSHARP_LEGACY,
|
||||
UUIDLegacy)
|
||||
from bson.code import Code
|
||||
from bson.codec_options import CodecOptions, DEFAULT_CODEC_OPTIONS
|
||||
from bson.codec_options import (
|
||||
CodecOptions, DEFAULT_CODEC_OPTIONS, _raw_document_class)
|
||||
from bson.dbref import DBRef
|
||||
from bson.errors import (InvalidBSON,
|
||||
InvalidDocument,
|
||||
@ -132,6 +133,10 @@ def _get_object(data, position, obj_end, opts):
|
||||
raise InvalidBSON("bad eoo")
|
||||
if end >= obj_end:
|
||||
raise InvalidBSON("invalid object length")
|
||||
if _raw_document_class(opts.document_class):
|
||||
return (opts.document_class(data[position:end + 1], opts),
|
||||
position + obj_size)
|
||||
|
||||
obj = _elements_to_dict(data, position + 4, end, opts)
|
||||
|
||||
position += obj_size
|
||||
@ -147,6 +152,7 @@ def _get_array(data, position, obj_end, opts):
|
||||
end = position + size - 1
|
||||
if data[end:end + 1] != b"\x00":
|
||||
raise InvalidBSON("bad eoo")
|
||||
|
||||
position += 4
|
||||
end -= 1
|
||||
result = []
|
||||
@ -304,14 +310,21 @@ def _element_to_dict(data, position, obj_end, opts):
|
||||
value, position = _ELEMENT_GETTER[element_type](data,
|
||||
position, obj_end, opts)
|
||||
return element_name, value, position
|
||||
if _USE_C:
|
||||
_element_to_dict = _cbson._element_to_dict
|
||||
|
||||
|
||||
def _iterate_elements(data, position, obj_end, opts):
|
||||
end = obj_end - 1
|
||||
while position < end:
|
||||
(key, value, position) = _element_to_dict(data, position, obj_end, opts)
|
||||
yield key, value
|
||||
|
||||
|
||||
def _elements_to_dict(data, position, obj_end, opts):
|
||||
"""Decode a BSON document."""
|
||||
result = opts.document_class()
|
||||
end = obj_end - 1
|
||||
while position < end:
|
||||
(key, value, position) = _element_to_dict(data, position, obj_end, opts)
|
||||
for key, value in _iterate_elements(data, position, obj_end, opts):
|
||||
result[key] = value
|
||||
return result
|
||||
|
||||
@ -327,6 +340,8 @@ def _bson_to_dict(data, opts):
|
||||
if data[obj_size - 1:obj_size] != b"\x00":
|
||||
raise InvalidBSON("bad eoo")
|
||||
try:
|
||||
if _raw_document_class(opts.document_class):
|
||||
return opts.document_class(data, opts)
|
||||
return _elements_to_dict(data, 4, obj_size - 1, opts)
|
||||
except InvalidBSON:
|
||||
raise
|
||||
@ -429,6 +444,8 @@ else:
|
||||
|
||||
def _encode_mapping(name, value, check_keys, opts):
|
||||
"""Encode a mapping type."""
|
||||
if _raw_document_class(value):
|
||||
return b'\x03' + name + value.raw
|
||||
data = b"".join([_element_to_bson(key, val, check_keys, opts)
|
||||
for key, val in iteritems(value)])
|
||||
return b"\x03" + name + _PACK_INT(len(data) + 5) + data + b"\x00"
|
||||
@ -694,6 +711,8 @@ def _element_to_bson(key, value, check_keys, opts):
|
||||
|
||||
def _dict_to_bson(doc, check_keys, opts, top_level=True):
|
||||
"""Encode a document to BSON."""
|
||||
if _raw_document_class(doc):
|
||||
return doc.raw
|
||||
try:
|
||||
elements = []
|
||||
if top_level and "_id" in doc:
|
||||
@ -751,6 +770,7 @@ def decode_all(data, codec_options=DEFAULT_CODEC_OPTIONS):
|
||||
docs = []
|
||||
position = 0
|
||||
end = len(data) - 1
|
||||
use_raw = _raw_document_class(codec_options.document_class)
|
||||
try:
|
||||
while position < end:
|
||||
obj_size = _UNPACK_INT(data[position:position + 4])[0]
|
||||
@ -759,10 +779,15 @@ def decode_all(data, codec_options=DEFAULT_CODEC_OPTIONS):
|
||||
obj_end = position + obj_size - 1
|
||||
if data[obj_end:position + obj_size] != b"\x00":
|
||||
raise InvalidBSON("bad eoo")
|
||||
docs.append(_elements_to_dict(data,
|
||||
position + 4,
|
||||
obj_end,
|
||||
codec_options))
|
||||
if use_raw:
|
||||
docs.append(
|
||||
codec_options.document_class(
|
||||
data[position:obj_end + 1], codec_options))
|
||||
else:
|
||||
docs.append(_elements_to_dict(data,
|
||||
position + 4,
|
||||
obj_end,
|
||||
codec_options))
|
||||
position += obj_size
|
||||
return docs
|
||||
except InvalidBSON:
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -52,12 +52,20 @@ typedef int Py_ssize_t;
|
||||
#define STRCAT(dest, n, src) strcat((dest), (src))
|
||||
#endif
|
||||
|
||||
#if PY_MAJOR_VERSION >= 3
|
||||
#define BYTES_FORMAT_STRING "y#"
|
||||
#else
|
||||
#define BYTES_FORMAT_STRING "s#"
|
||||
#endif
|
||||
|
||||
typedef struct codec_options_t {
|
||||
PyObject* document_class;
|
||||
unsigned char tz_aware;
|
||||
unsigned char uuid_rep;
|
||||
char* unicode_decode_error_handler;
|
||||
PyObject* tzinfo;
|
||||
PyObject* options_obj;
|
||||
unsigned char is_raw_bson;
|
||||
} codec_options_t;
|
||||
|
||||
/* C API functions */
|
||||
|
||||
@ -23,6 +23,14 @@ from bson.binary import (ALL_UUID_REPRESENTATIONS,
|
||||
PYTHON_LEGACY,
|
||||
UUID_REPRESENTATION_NAMES)
|
||||
|
||||
_RAW_BSON_DOCUMENT_MARKER = 101
|
||||
|
||||
|
||||
def _raw_document_class(document_class):
|
||||
"""Determine if a document_class is a RawBSONDocument class."""
|
||||
marker = getattr(document_class, '_type_marker', None)
|
||||
return marker == _RAW_BSON_DOCUMENT_MARKER
|
||||
|
||||
|
||||
_options_base = namedtuple(
|
||||
'CodecOptions',
|
||||
@ -61,9 +69,11 @@ class CodecOptions(_options_base):
|
||||
tz_aware=False, uuid_representation=PYTHON_LEGACY,
|
||||
unicode_decode_error_handler="strict",
|
||||
tzinfo=None):
|
||||
if not issubclass(document_class, MutableMapping):
|
||||
raise TypeError("document_class must be dict, bson.son.SON, or "
|
||||
"another subclass of collections.MutableMapping")
|
||||
if not (issubclass(document_class, MutableMapping) or
|
||||
_raw_document_class(document_class)):
|
||||
raise TypeError("document_class must be dict, bson.son.SON, "
|
||||
"bson.raw_bson_document.RawBSONDocument, or a "
|
||||
"sublass of collections.MutableMapping")
|
||||
if not isinstance(tz_aware, bool):
|
||||
raise TypeError("tz_aware must be True or False")
|
||||
if uuid_representation not in ALL_UUID_REPRESENTATIONS:
|
||||
|
||||
92
bson/raw_bson.py
Normal file
92
bson/raw_bson.py
Normal file
@ -0,0 +1,92 @@
|
||||
# Copyright 2015 MongoDB, Inc.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
"""Tools for representing raw BSON documents.
|
||||
"""
|
||||
|
||||
import collections
|
||||
|
||||
from bson import _UNPACK_INT, _iterate_elements
|
||||
from bson.py3compat import iteritems
|
||||
from bson.codec_options import (
|
||||
CodecOptions, DEFAULT_CODEC_OPTIONS, _RAW_BSON_DOCUMENT_MARKER)
|
||||
|
||||
|
||||
class RawBSONDocument(collections.Mapping):
|
||||
"""Representation for a MongoDB document that provides access to the raw
|
||||
BSON bytes that compose it.
|
||||
|
||||
Only when a field is accessed or modified within the document does
|
||||
RawBSONDocument decode its bytes.
|
||||
"""
|
||||
|
||||
__slots__ = ('__raw', '__inflated_doc', '__codec_options')
|
||||
_type_marker = _RAW_BSON_DOCUMENT_MARKER
|
||||
|
||||
def __init__(self, bson_bytes, codec_options=DEFAULT_CODEC_OPTIONS):
|
||||
"""Create a new :class:`RawBSONDocument`.
|
||||
|
||||
:Parameters:
|
||||
- `bson_bytes`: the BSON bytes that compose this document
|
||||
- `codec_options` (optional): An instance of
|
||||
:class:`~bson.codec_options.CodecOptions`.
|
||||
"""
|
||||
self.__raw = bson_bytes
|
||||
self.__inflated_doc = None
|
||||
# Always decode documents to their lazy representations.
|
||||
co = codec_options
|
||||
self.__codec_options = CodecOptions(
|
||||
tz_aware=co.tz_aware,
|
||||
document_class=RawBSONDocument,
|
||||
uuid_representation=co.uuid_representation,
|
||||
unicode_decode_error_handler=co.unicode_decode_error_handler,
|
||||
tzinfo=co.tzinfo)
|
||||
|
||||
@property
|
||||
def raw(self):
|
||||
"""The raw BSON bytes composing this document."""
|
||||
return self.__raw
|
||||
|
||||
def items(self):
|
||||
"""Lazily decode and iterate elements in this document."""
|
||||
return iteritems(self.__inflated)
|
||||
|
||||
@property
|
||||
def __inflated(self):
|
||||
if self.__inflated_doc is None:
|
||||
# We already validated the object's size when this document was
|
||||
# created, so no need to do that again.
|
||||
self.__inflated_doc = dict(
|
||||
element for element in _iterate_elements(
|
||||
self.__raw, 4, _UNPACK_INT(self.__raw[:4])[0] - 1,
|
||||
self.__codec_options))
|
||||
return self.__inflated_doc
|
||||
|
||||
def __getitem__(self, item):
|
||||
return self.__inflated[item]
|
||||
|
||||
def __iter__(self):
|
||||
return iter(self.__inflated)
|
||||
|
||||
def __len__(self):
|
||||
return len(self.__inflated)
|
||||
|
||||
def __eq__(self, other):
|
||||
if isinstance(other, RawBSONDocument):
|
||||
return self.__raw == other.raw
|
||||
return NotImplemented
|
||||
|
||||
def __repr__(self):
|
||||
return ("RawBSONDocument(%r, codec_options=%r)"
|
||||
% (self.raw, self.__codec_options))
|
||||
@ -19,9 +19,10 @@
|
||||
|
||||
from bson.objectid import ObjectId
|
||||
from bson.py3compat import u
|
||||
from bson.raw_bson import RawBSONDocument
|
||||
from bson.son import SON
|
||||
from pymongo.common import (validate_is_mapping,
|
||||
validate_is_mutable_mapping,
|
||||
validate_is_document_type,
|
||||
validate_ok_for_replace,
|
||||
validate_ok_for_update)
|
||||
from pymongo.errors import (BulkWriteError,
|
||||
@ -213,9 +214,9 @@ class _Bulk(object):
|
||||
def add_insert(self, document):
|
||||
"""Add an insert document to the list of ops.
|
||||
"""
|
||||
validate_is_mutable_mapping("document", document)
|
||||
validate_is_document_type("document", document)
|
||||
# Generate ObjectId client side.
|
||||
if '_id' not in document:
|
||||
if not (isinstance(document, RawBSONDocument) or '_id' in document):
|
||||
document['_id'] = ObjectId()
|
||||
self.ops.append((_INSERT, document))
|
||||
|
||||
|
||||
@ -24,6 +24,7 @@ from bson.py3compat import (_unicode,
|
||||
integer_types,
|
||||
string_type,
|
||||
u)
|
||||
from bson.raw_bson import RawBSONDocument
|
||||
from bson.codec_options import CodecOptions
|
||||
from bson.son import SON
|
||||
from pymongo import (common,
|
||||
@ -489,7 +490,7 @@ class Collection(common.BaseObject):
|
||||
"""Internal helper for inserting a single document."""
|
||||
if manipulate:
|
||||
doc = self.__database._apply_incoming_manipulators(doc, self)
|
||||
if '_id' not in doc:
|
||||
if not isinstance(doc, RawBSONDocument) and '_id' not in doc:
|
||||
doc['_id'] = ObjectId()
|
||||
doc = self.__database._apply_incoming_copying_manipulators(doc,
|
||||
self)
|
||||
@ -516,13 +517,14 @@ class Collection(common.BaseObject):
|
||||
sock_info, 'insert', command, acknowledged, op_id,
|
||||
bypass_doc_val, message.insert, self.__full_name, [doc],
|
||||
check_keys, acknowledged, concern, False, self.codec_options)
|
||||
return doc.get('_id')
|
||||
if not isinstance(doc, RawBSONDocument):
|
||||
return doc.get('_id')
|
||||
|
||||
def _insert(self, sock_info, docs, ordered=True, check_keys=True,
|
||||
manipulate=False, write_concern=None, op_id=None,
|
||||
bypass_doc_val=False):
|
||||
"""Internal insert helper."""
|
||||
if isinstance(docs, collections.MutableMapping):
|
||||
if isinstance(docs, collections.Mapping):
|
||||
return self._insert_one(
|
||||
sock_info, docs, ordered,
|
||||
check_keys, manipulate, write_concern, op_id, bypass_doc_val)
|
||||
@ -540,7 +542,7 @@ class Collection(common.BaseObject):
|
||||
# operations is required for backwards compatibility,
|
||||
# see PYTHON-709.
|
||||
doc = _db._apply_incoming_manipulators(doc, self)
|
||||
if '_id' not in doc:
|
||||
if not (isinstance(doc, RawBSONDocument) or '_id' in doc):
|
||||
doc['_id'] = ObjectId()
|
||||
|
||||
doc = _db._apply_incoming_copying_manipulators(doc, self)
|
||||
@ -550,7 +552,9 @@ class Collection(common.BaseObject):
|
||||
def gen():
|
||||
"""Generator that only tracks existing _ids."""
|
||||
for doc in docs:
|
||||
ids.append(doc.get('_id'))
|
||||
# Don't inflate RawBSONDocument by touching fields.
|
||||
if not isinstance(doc, RawBSONDocument):
|
||||
ids.append(doc.get('_id'))
|
||||
yield doc
|
||||
|
||||
concern = (write_concern or self.write_concern).document
|
||||
@ -612,8 +616,8 @@ class Collection(common.BaseObject):
|
||||
|
||||
.. versionadded:: 3.0
|
||||
"""
|
||||
common.validate_is_mutable_mapping("document", document)
|
||||
if "_id" not in document:
|
||||
common.validate_is_document_type("document", document)
|
||||
if not (isinstance(document, RawBSONDocument) or "_id" in document):
|
||||
document["_id"] = ObjectId()
|
||||
with self._socket_for_writes() as sock_info:
|
||||
return InsertOneResult(
|
||||
@ -663,10 +667,11 @@ class Collection(common.BaseObject):
|
||||
def gen():
|
||||
"""A generator that validates documents and handles _ids."""
|
||||
for document in documents:
|
||||
common.validate_is_mutable_mapping("document", document)
|
||||
if "_id" not in document:
|
||||
document["_id"] = ObjectId()
|
||||
inserted_ids.append(document["_id"])
|
||||
common.validate_is_document_type("document", document)
|
||||
if not isinstance(document, RawBSONDocument):
|
||||
if "_id" not in document:
|
||||
document["_id"] = ObjectId()
|
||||
inserted_ids.append(document["_id"])
|
||||
yield (message._INSERT, document)
|
||||
|
||||
blk = _Bulk(self, ordered, bypass_document_validation)
|
||||
@ -1189,6 +1194,8 @@ class Collection(common.BaseObject):
|
||||
with self._socket_for_reads() as (sock_info, slave_ok):
|
||||
res = self._command(sock_info, cmd, slave_ok,
|
||||
allowable_errors=["ns missing"],
|
||||
codec_options=self.codec_options._replace(
|
||||
document_class=dict),
|
||||
read_concern=self.read_concern)
|
||||
if res.get("errmsg", "") == "ns missing":
|
||||
return 0
|
||||
@ -2158,14 +2165,14 @@ class Collection(common.BaseObject):
|
||||
"""
|
||||
warnings.warn("save is deprecated. Use insert_one or replace_one "
|
||||
"instead", DeprecationWarning, stacklevel=2)
|
||||
common.validate_is_mutable_mapping("to_save", to_save)
|
||||
common.validate_is_document_type("to_save", to_save)
|
||||
|
||||
write_concern = None
|
||||
if kwargs:
|
||||
write_concern = WriteConcern(**kwargs)
|
||||
|
||||
with self._socket_for_writes() as sock_info:
|
||||
if "_id" not in to_save:
|
||||
if not (isinstance(to_save, RawBSONDocument) or "_id" in to_save):
|
||||
return self._insert(sock_info, to_save, True,
|
||||
check_keys, manipulate, write_concern)
|
||||
else:
|
||||
|
||||
@ -22,6 +22,7 @@ from bson.binary import (STANDARD, PYTHON_LEGACY,
|
||||
JAVA_LEGACY, CSHARP_LEGACY)
|
||||
from bson.codec_options import CodecOptions
|
||||
from bson.py3compat import string_type, integer_types, iteritems
|
||||
from bson.raw_bson import RawBSONDocument
|
||||
from pymongo.auth import MECHANISMS
|
||||
from pymongo.errors import ConfigurationError
|
||||
from pymongo.monitoring import _validate_event_listeners
|
||||
@ -359,8 +360,9 @@ def validate_auth_mechanism_properties(option, value):
|
||||
|
||||
def validate_document_class(option, value):
|
||||
"""Validate the document_class option."""
|
||||
if not issubclass(value, collections.MutableMapping):
|
||||
raise TypeError("%s must be dict, bson.son.SON, or another "
|
||||
if not issubclass(value, (collections.MutableMapping, RawBSONDocument)):
|
||||
raise TypeError("%s must be dict, bson.son.SON, "
|
||||
"bson.raw_bson.RawBSONDocument, or a "
|
||||
"sublass of collections.MutableMapping" % (option,))
|
||||
return value
|
||||
|
||||
@ -373,11 +375,12 @@ def validate_is_mapping(option, value):
|
||||
"collections.Mapping" % (option,))
|
||||
|
||||
|
||||
def validate_is_mutable_mapping(option, value):
|
||||
"""Validate the type of method arguments that expect a mutable document."""
|
||||
if not isinstance(value, collections.MutableMapping):
|
||||
raise TypeError("%s must be an instance of dict, bson.son.SON, or "
|
||||
"other type that inherits from "
|
||||
def validate_is_document_type(option, value):
|
||||
"""Validate the type of method arguments that expect a MongoDB document."""
|
||||
if not isinstance(value, (collections.MutableMapping, RawBSONDocument)):
|
||||
raise TypeError("%s must be an instance of dict, bson.son.SON, "
|
||||
"bson.raw_bson.RawBSONDocument, or "
|
||||
"a type that inherits from "
|
||||
"collections.MutableMapping" % (option,))
|
||||
|
||||
|
||||
@ -385,7 +388,7 @@ def validate_ok_for_replace(replacement):
|
||||
"""Validate a replacement document."""
|
||||
validate_is_mapping("replacement", replacement)
|
||||
# Replacement can be {}
|
||||
if replacement:
|
||||
if replacement and not isinstance(replacement, RawBSONDocument):
|
||||
first = next(iter(replacement))
|
||||
if first.startswith('$'):
|
||||
raise ValueError('replacement can not include $ operators')
|
||||
|
||||
@ -275,6 +275,15 @@ class Database(common.BaseObject):
|
||||
self, name, False, codec_options, read_preference,
|
||||
write_concern, read_concern)
|
||||
|
||||
def _collection_default_options(self, name, **kargs):
|
||||
"""Get a Collection instance with the default settings."""
|
||||
wc = (self.write_concern
|
||||
if self.write_concern.acknowledged else WriteConcern())
|
||||
return self.get_collection(
|
||||
name, codec_options=DEFAULT_CODEC_OPTIONS,
|
||||
read_preference=ReadPreference.PRIMARY,
|
||||
write_concern=wc)
|
||||
|
||||
def create_collection(self, name, codec_options=None,
|
||||
read_preference=None, write_concern=None,
|
||||
read_concern=None, **kwargs):
|
||||
@ -824,7 +833,9 @@ class Database(common.BaseObject):
|
||||
def _legacy_add_user(self, name, password, read_only, **kwargs):
|
||||
"""Uses v1 system to add users, i.e. saving to system.users.
|
||||
"""
|
||||
user = self.system.users.find_one({"user": name}) or {"user": name}
|
||||
# Use a Collection with the default codec_options.
|
||||
system_users = self._collection_default_options('system.users')
|
||||
user = system_users.find_one({"user": name}) or {"user": name}
|
||||
if password is not None:
|
||||
user["pwd"] = auth._password_digest(name, password)
|
||||
if read_only is not None:
|
||||
@ -834,11 +845,8 @@ class Database(common.BaseObject):
|
||||
# We don't care what the _id is, only that it has one
|
||||
# for the replace_one call below.
|
||||
user.setdefault("_id", ObjectId())
|
||||
coll = self.system.users
|
||||
if not self.write_concern.acknowledged:
|
||||
coll = coll.with_options(write_concern=WriteConcern())
|
||||
try:
|
||||
coll.replace_one({"_id": user["_id"]}, user, True)
|
||||
system_users.replace_one({"_id": user["_id"]}, user, True)
|
||||
except OperationFailure as exc:
|
||||
# First admin user add fails gle in MongoDB >= 2.1.2
|
||||
# See SERVER-4225 for more information.
|
||||
@ -930,9 +938,7 @@ class Database(common.BaseObject):
|
||||
except OperationFailure as exc:
|
||||
# See comment in add_user try / except above.
|
||||
if exc.code in common.COMMAND_NOT_FOUND_CODES:
|
||||
coll = self.system.users
|
||||
if not self.write_concern.acknowledged:
|
||||
coll = coll.with_options(write_concern=WriteConcern())
|
||||
coll = self._collection_default_options('system.users')
|
||||
coll.delete_one({"user": name})
|
||||
return
|
||||
raise
|
||||
|
||||
@ -38,6 +38,7 @@ import warnings
|
||||
import weakref
|
||||
from collections import defaultdict
|
||||
|
||||
from bson.codec_options import DEFAULT_CODEC_OPTIONS
|
||||
from bson.py3compat import (integer_types,
|
||||
string_type)
|
||||
from bson.son import SON
|
||||
@ -64,6 +65,7 @@ from pymongo.server_type import SERVER_TYPE
|
||||
from pymongo.topology import Topology
|
||||
from pymongo.topology_description import TOPOLOGY_TYPE
|
||||
from pymongo.settings import TopologySettings
|
||||
from pymongo.write_concern import WriteConcern
|
||||
|
||||
|
||||
class MongoClient(common.BaseObject):
|
||||
@ -1002,9 +1004,8 @@ class MongoClient(common.BaseObject):
|
||||
def database_names(self):
|
||||
"""Get a list of the names of all databases on the connected server."""
|
||||
return [db["name"] for db in
|
||||
self.admin.command(
|
||||
"listDatabases",
|
||||
read_preference=ReadPreference.PRIMARY)["databases"]]
|
||||
self._database_default_options('admin').command(
|
||||
"listDatabases")["databases"]]
|
||||
|
||||
def drop_database(self, name_or_database):
|
||||
"""Drop a database.
|
||||
@ -1089,13 +1090,20 @@ class MongoClient(common.BaseObject):
|
||||
self, name, codec_options, read_preference,
|
||||
write_concern, read_concern)
|
||||
|
||||
def _database_default_options(self, name):
|
||||
"""Get a Database instance with the default settings."""
|
||||
return self.get_database(
|
||||
name, codec_options=DEFAULT_CODEC_OPTIONS,
|
||||
read_preference=ReadPreference.PRIMARY,
|
||||
write_concern=WriteConcern())
|
||||
|
||||
@property
|
||||
def is_locked(self):
|
||||
"""Is this server locked? While locked, all write operations
|
||||
are blocked, although read operations may still be allowed.
|
||||
Use :meth:`unlock` to unlock.
|
||||
"""
|
||||
ops = self.admin.current_op()
|
||||
ops = self._database_default_options('admin').current_op()
|
||||
return bool(ops.get('fsyncLock', 0))
|
||||
|
||||
def fsync(self, **kwargs):
|
||||
|
||||
@ -24,8 +24,12 @@ from collections import defaultdict
|
||||
|
||||
sys.path[0:0] = [""]
|
||||
|
||||
import bson
|
||||
|
||||
from bson.raw_bson import RawBSONDocument
|
||||
from bson.regex import Regex
|
||||
from bson.code import Code
|
||||
from bson.codec_options import CodecOptions
|
||||
from bson.objectid import ObjectId
|
||||
from bson.py3compat import u, itervalues
|
||||
from bson.son import SON
|
||||
@ -656,6 +660,12 @@ class TestCollection(IntegrationTest):
|
||||
# The insert failed duplicate key...
|
||||
wait_until(lambda: 2 == db.test.count(), 'forcing duplicate key error')
|
||||
|
||||
document = RawBSONDocument(
|
||||
bson.BSON.encode({'_id': ObjectId(), 'foo': 'bar'}))
|
||||
result = db.test.insert_one(document)
|
||||
self.assertTrue(isinstance(result, InsertOneResult))
|
||||
self.assertEqual(result.inserted_id, None)
|
||||
|
||||
def test_insert_many(self):
|
||||
db = self.db
|
||||
db.test.drop()
|
||||
@ -684,13 +694,20 @@ class TestCollection(IntegrationTest):
|
||||
self.assertEqual(1, db.test.count({"_id": _id}))
|
||||
self.assertTrue(result.acknowledged)
|
||||
|
||||
docs = [RawBSONDocument(bson.BSON.encode({"_id": i + 5}))
|
||||
for i in range(5)]
|
||||
result = db.test.insert_many(docs)
|
||||
self.assertTrue(isinstance(result, InsertManyResult))
|
||||
self.assertTrue(isinstance(result.inserted_ids, list))
|
||||
self.assertEqual([], result.inserted_ids)
|
||||
|
||||
db = db.client.get_database(db.name,
|
||||
write_concern=WriteConcern(w=0))
|
||||
docs = [{} for _ in range(5)]
|
||||
result = db.test.insert_many(docs)
|
||||
self.assertTrue(isinstance(result, InsertManyResult))
|
||||
self.assertFalse(result.acknowledged)
|
||||
self.assertEqual(15, db.test.count())
|
||||
self.assertEqual(20, db.test.count())
|
||||
|
||||
def test_delete_one(self):
|
||||
self.db.test.drop()
|
||||
@ -1160,6 +1177,17 @@ class TestCollection(IntegrationTest):
|
||||
self.assertEqual(0, db.test.count({"x": 1}))
|
||||
self.assertEqual(db.test.find_one(id1)["y"], 1)
|
||||
|
||||
replacement = RawBSONDocument(bson.BSON.encode({"_id": id1, "z": 1}))
|
||||
result = db.test.replace_one({"y": 1}, replacement, True)
|
||||
self.assertTrue(isinstance(result, UpdateResult))
|
||||
self.assertEqual(1, result.matched_count)
|
||||
self.assertTrue(result.modified_count in (None, 1))
|
||||
self.assertIsNone(result.upserted_id)
|
||||
self.assertTrue(result.acknowledged)
|
||||
self.assertEqual(1, db.test.count({"z": 1}))
|
||||
self.assertEqual(0, db.test.count({"y": 1}))
|
||||
self.assertEqual(db.test.find_one(id1)["z"], 1)
|
||||
|
||||
result = db.test.replace_one({"x": 2}, {"y": 2}, True)
|
||||
self.assertTrue(isinstance(result, UpdateResult))
|
||||
self.assertEqual(0, result.matched_count)
|
||||
@ -1354,6 +1382,23 @@ class TestCollection(IntegrationTest):
|
||||
self.assertTrue(isinstance(result, CommandCursor))
|
||||
self.assertEqual([{'foo': [1, 2]}], list(result))
|
||||
|
||||
def test_aggregate_raw_bson(self):
|
||||
db = self.db
|
||||
db.drop_collection("test")
|
||||
db.test.insert_one({'foo': [1, 2]})
|
||||
|
||||
self.assertRaises(TypeError, db.test.aggregate, "wow")
|
||||
|
||||
pipeline = {"$project": {"_id": False, "foo": True}}
|
||||
result = db.get_collection(
|
||||
'test',
|
||||
codec_options=CodecOptions(document_class=RawBSONDocument)
|
||||
).aggregate([pipeline], useCursor=False)
|
||||
self.assertTrue(isinstance(result, CommandCursor))
|
||||
first_result = next(result)
|
||||
self.assertIsInstance(first_result, RawBSONDocument)
|
||||
self.assertEqual([1, 2], list(first_result['foo']))
|
||||
|
||||
@client_context.require_version_min(2, 5, 1)
|
||||
def test_aggregation_cursor_validation(self):
|
||||
db = self.db
|
||||
|
||||
109
test/test_raw_bson.py
Normal file
109
test/test_raw_bson.py
Normal file
@ -0,0 +1,109 @@
|
||||
import datetime
|
||||
import uuid
|
||||
|
||||
import pymongo
|
||||
|
||||
from bson import BSON
|
||||
from bson.binary import JAVA_LEGACY
|
||||
from bson.codec_options import CodecOptions
|
||||
from bson.raw_bson import RawBSONDocument
|
||||
from test import client_context, unittest, pair
|
||||
|
||||
|
||||
class TestRawBSONDocument(unittest.TestCase):
|
||||
|
||||
# {u'_id': ObjectId('556df68b6e32ab21a95e0785'),
|
||||
# u'name': u'Sherlock',
|
||||
# u'addresses': [{u'street': u'Baker Street'}]}
|
||||
bson_string = (
|
||||
b'Z\x00\x00\x00\x07_id\x00Um\xf6\x8bn2\xab!\xa9^\x07\x85\x02name\x00\t'
|
||||
b'\x00\x00\x00Sherlock\x00\x04addresses\x00&\x00\x00\x00\x030\x00\x1e'
|
||||
b'\x00\x00\x00\x02street\x00\r\x00\x00\x00Baker Street\x00\x00\x00\x00'
|
||||
)
|
||||
document = RawBSONDocument(bson_string)
|
||||
|
||||
def tearDown(self):
|
||||
if client_context.connected:
|
||||
client_context.client.pymongo_test.test_raw.drop()
|
||||
|
||||
def test_decode(self):
|
||||
self.assertEqual('Sherlock', self.document['name'])
|
||||
first_address = self.document['addresses'][0]
|
||||
self.assertIsInstance(first_address, RawBSONDocument)
|
||||
self.assertEqual('Baker Street', first_address['street'])
|
||||
|
||||
def test_raw(self):
|
||||
self.assertEqual(self.bson_string, self.document.raw)
|
||||
|
||||
@client_context.require_connection
|
||||
def test_round_trip(self):
|
||||
client = pymongo.MongoClient(pair, document_class=RawBSONDocument)
|
||||
client.pymongo_test.test_raw.insert_one(self.document)
|
||||
result = client.pymongo_test.test_raw.find_one(self.document['_id'])
|
||||
self.assertIsInstance(result, RawBSONDocument)
|
||||
self.assertEqual(dict(self.document.items()), dict(result.items()))
|
||||
|
||||
def test_with_codec_options(self):
|
||||
# {u'date': datetime.datetime(2015, 6, 3, 18, 40, 50, 826000),
|
||||
# u'_id': UUID('026fab8f-975f-4965-9fbf-85ad874c60ff')}
|
||||
# encoded with JAVA_LEGACY uuid representation.
|
||||
bson_string = (
|
||||
b'-\x00\x00\x00\x05_id\x00\x10\x00\x00\x00\x03eI_\x97\x8f\xabo\x02'
|
||||
b'\xff`L\x87\xad\x85\xbf\x9f\tdate\x00\x8a\xd6\xb9\xbaM'
|
||||
b'\x01\x00\x00\x00'
|
||||
)
|
||||
document = RawBSONDocument(
|
||||
bson_string,
|
||||
codec_options=CodecOptions(uuid_representation=JAVA_LEGACY))
|
||||
|
||||
self.assertEqual(uuid.UUID('026fab8f-975f-4965-9fbf-85ad874c60ff'),
|
||||
document['_id'])
|
||||
|
||||
@client_context.require_connection
|
||||
def test_round_trip_codec_options(self):
|
||||
doc = {
|
||||
'date': datetime.datetime(2015, 6, 3, 18, 40, 50, 826000),
|
||||
'_id': uuid.UUID('026fab8f-975f-4965-9fbf-85ad874c60ff')
|
||||
}
|
||||
db = pymongo.MongoClient(pair).pymongo_test
|
||||
coll = db.get_collection(
|
||||
'test_raw',
|
||||
codec_options=CodecOptions(uuid_representation=JAVA_LEGACY))
|
||||
coll.insert_one(doc)
|
||||
raw_java_legacy = CodecOptions(uuid_representation=JAVA_LEGACY,
|
||||
document_class=RawBSONDocument)
|
||||
coll = db.get_collection('test_raw', codec_options=raw_java_legacy)
|
||||
self.assertEqual(
|
||||
RawBSONDocument(BSON.encode(doc, codec_options=raw_java_legacy)),
|
||||
coll.find_one())
|
||||
|
||||
@client_context.require_connection
|
||||
def test_raw_bson_document_embedded(self):
|
||||
doc = {'embedded': self.document}
|
||||
db = client_context.client.pymongo_test
|
||||
db.test_raw.insert_one(doc)
|
||||
result = db.test_raw.find_one()
|
||||
self.assertEqual(BSON(self.document.raw).decode(), result['embedded'])
|
||||
|
||||
# Make sure that CodecOptions are preserved.
|
||||
# {'embedded': [
|
||||
# {u'date': datetime.datetime(2015, 6, 3, 18, 40, 50, 826000),
|
||||
# u'_id': UUID('026fab8f-975f-4965-9fbf-85ad874c60ff')}
|
||||
# ]}
|
||||
# encoded with JAVA_LEGACY uuid representation.
|
||||
bson_string = (
|
||||
b'D\x00\x00\x00\x04embedded\x005\x00\x00\x00\x030\x00-\x00\x00\x00'
|
||||
b'\tdate\x00\x8a\xd6\xb9\xbaM\x01\x00\x00\x05_id\x00\x10\x00\x00'
|
||||
b'\x00\x03eI_\x97\x8f\xabo\x02\xff`L\x87\xad\x85\xbf\x9f\x00\x00'
|
||||
b'\x00'
|
||||
)
|
||||
rbd = RawBSONDocument(
|
||||
bson_string,
|
||||
codec_options=CodecOptions(uuid_representation=JAVA_LEGACY))
|
||||
|
||||
db.test_raw.drop()
|
||||
db.test_raw.insert_one(rbd)
|
||||
result = db.get_collection('test_raw', codec_options=CodecOptions(
|
||||
uuid_representation=JAVA_LEGACY)).find_one()
|
||||
self.assertEqual(rbd['embedded'][0]['_id'],
|
||||
result['embedded'][0]['_id'])
|
||||
Loading…
Reference in New Issue
Block a user