mongo-python-driver/test/test_legacy_api.py

1151 lines
42 KiB
Python

# 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.
"""Test various legacy / deprecated API features."""
import itertools
import sys
import threading
import time
import warnings
sys.path[0:0] = [""]
from bson.codec_options import CodecOptions
from bson.dbref import DBRef
from bson.objectid import ObjectId
from bson.py3compat import u
from bson.son import SON
from pymongo import ASCENDING, DESCENDING
from pymongo.errors import (ConfigurationError,
DocumentTooLarge,
DuplicateKeyError,
InvalidDocument,
InvalidOperation,
OperationFailure,
WTimeoutError)
from pymongo.son_manipulator import (AutoReference,
NamespaceInjector,
ObjectIdShuffler,
SONManipulator)
from pymongo.write_concern import WriteConcern
from test import client_context, qcheck, unittest
from test.test_client import IntegrationTest
from test.utils import (joinall,
oid_generated_on_client,
rs_or_single_client,
wait_until)
class TestDeprecations(IntegrationTest):
@classmethod
def setUpClass(cls):
super(TestDeprecations, cls).setUpClass()
cls.warn_context = warnings.catch_warnings()
cls.warn_context.__enter__()
warnings.simplefilter("error", DeprecationWarning)
@classmethod
def tearDownClass(cls):
cls.warn_context.__exit__()
cls.warn_context = None
def test_save_deprecation(self):
self.assertRaises(
DeprecationWarning, lambda: self.db.test.save({}))
def test_insert_deprecation(self):
self.assertRaises(
DeprecationWarning, lambda: self.db.test.insert({}))
def test_update_deprecation(self):
self.assertRaises(
DeprecationWarning, lambda: self.db.test.update({}, {}))
def test_remove_deprecation(self):
self.assertRaises(
DeprecationWarning, lambda: self.db.test.remove({}))
def test_find_and_modify_deprecation(self):
self.assertRaises(
DeprecationWarning,
lambda: self.db.test.find_and_modify({'i': 5}, {}))
def test_add_son_manipulator_deprecation(self):
db = self.client.pymongo_test
self.assertRaises(DeprecationWarning,
lambda: db.add_son_manipulator(AutoReference(db)))
def test_ensure_index_deprecation(self):
try:
self.assertRaises(
DeprecationWarning,
lambda: self.db.test.ensure_index('i'))
finally:
self.db.test.drop()
class TestLegacy(IntegrationTest):
@classmethod
def setUpClass(cls):
super(TestLegacy, cls).setUpClass()
cls.w = client_context.w
cls.warn_context = warnings.catch_warnings()
cls.warn_context.__enter__()
warnings.simplefilter("ignore", DeprecationWarning)
@classmethod
def tearDownClass(cls):
cls.warn_context.__exit__()
cls.warn_context = None
def test_insert_find_one(self):
# Tests legacy insert.
db = self.db
db.test.drop()
self.assertEqual(0, len(list(db.test.find())))
doc = {"hello": u("world")}
_id = db.test.insert(doc)
self.assertEqual(1, len(list(db.test.find())))
self.assertEqual(doc, db.test.find_one())
self.assertEqual(doc["_id"], _id)
self.assertTrue(isinstance(_id, ObjectId))
doc_class = dict
# Work around http://bugs.jython.org/issue1728
if (sys.platform.startswith('java') and
sys.version_info[:3] >= (2, 5, 2)):
doc_class = SON
db = self.client.get_database(
db.name, codec_options=CodecOptions(document_class=doc_class))
def remove_insert_find_one(doc):
db.test.remove({})
db.test.insert(doc)
# SON equality is order sensitive.
return db.test.find_one() == doc.to_dict()
qcheck.check_unittest(self, remove_insert_find_one,
qcheck.gen_mongo_dict(3))
def test_generator_insert(self):
# Only legacy insert currently supports insert from a generator.
db = self.db
db.test.remove({})
self.assertEqual(db.test.find().count(), 0)
db.test.insert(({'a': i} for i in range(5)), manipulate=False)
self.assertEqual(5, db.test.count())
db.test.remove({})
db.test.insert(({'a': i} for i in range(5)), manipulate=True)
self.assertEqual(5, db.test.count())
db.test.remove({})
def test_insert_multiple(self):
# Tests legacy insert.
db = self.db
db.drop_collection("test")
doc1 = {"hello": u("world")}
doc2 = {"hello": u("mike")}
self.assertEqual(db.test.find().count(), 0)
ids = db.test.insert([doc1, doc2])
self.assertEqual(db.test.find().count(), 2)
self.assertEqual(doc1, db.test.find_one({"hello": u("world")}))
self.assertEqual(doc2, db.test.find_one({"hello": u("mike")}))
self.assertEqual(2, len(ids))
self.assertEqual(doc1["_id"], ids[0])
self.assertEqual(doc2["_id"], ids[1])
ids = db.test.insert([{"hello": 1}])
self.assertTrue(isinstance(ids, list))
self.assertEqual(1, len(ids))
self.assertRaises(InvalidOperation, db.test.insert, [])
# Generator that raises StopIteration on first call to next().
self.assertRaises(InvalidOperation, db.test.insert, (i for i in []))
def test_insert_multiple_with_duplicate(self):
# Tests legacy insert.
db = self.db
db.drop_collection("test_insert_multiple_with_duplicate")
collection = db.test_insert_multiple_with_duplicate
collection.ensure_index([('i', ASCENDING)], unique=True)
# No error
collection.insert([{'i': i} for i in range(5, 10)], w=0)
wait_until(lambda: 5 == collection.count(), 'insert 5 documents')
collection.remove()
# No error
collection.insert([{'i': 1}] * 2, w=0)
wait_until(lambda: 1 == collection.count(), 'insert 1 document')
self.assertRaises(
DuplicateKeyError,
lambda: collection.insert([{'i': 2}] * 2),
)
db.drop_collection("test_insert_multiple_with_duplicate")
db = self.client.get_database(
db.name, write_concern=WriteConcern(w=0))
collection = db.test_insert_multiple_with_duplicate
collection.ensure_index([('i', ASCENDING)], unique=True)
# No error.
collection.insert([{'i': 1}] * 2)
wait_until(lambda: 1 == collection.count(), 'insert 1 document')
# Implied acknowledged.
self.assertRaises(
DuplicateKeyError,
lambda: collection.insert([{'i': 2}] * 2, fsync=True),
)
# Explicit acknowledged.
self.assertRaises(
DuplicateKeyError,
lambda: collection.insert([{'i': 2}] * 2, w=1))
db.drop_collection("test_insert_multiple_with_duplicate")
def test_insert_iterables(self):
# Tests legacy insert.
db = self.db
self.assertRaises(TypeError, db.test.insert, 4)
self.assertRaises(TypeError, db.test.insert, None)
self.assertRaises(TypeError, db.test.insert, True)
db.drop_collection("test")
self.assertEqual(db.test.find().count(), 0)
db.test.insert(({"hello": u("world")}, {"hello": u("world")}))
self.assertEqual(db.test.find().count(), 2)
db.drop_collection("test")
self.assertEqual(db.test.find().count(), 0)
db.test.insert(map(lambda x: {"hello": "world"},
itertools.repeat(None, 10)))
self.assertEqual(db.test.find().count(), 10)
def test_insert_manipulate_false(self):
# Test three aspects of legacy insert with manipulate=False:
# 1. The return value is None or [None] as appropriate.
# 2. _id is not set on the passed-in document object.
# 3. _id is not sent to server.
collection = self.db.test_insert_manipulate_false
collection.drop()
oid = ObjectId()
doc = {'a': oid}
# The return value is None.
self.assertTrue(collection.insert(doc, manipulate=False) is None)
# insert() shouldn't set _id on the passed-in document object.
self.assertEqual({'a': oid}, doc)
server_doc = collection.find_one()
# _id is not sent to server, so it's generated server-side.
self.assertFalse(oid_generated_on_client(server_doc['_id']))
# Bulk insert. The return value is a list of None.
self.assertEqual([None], collection.insert([{}], manipulate=False))
ids = collection.insert([{}, {}], manipulate=False)
self.assertEqual([None, None], ids)
collection.drop()
def test_continue_on_error(self):
# Tests legacy insert.
db = self.db
db.drop_collection("test_continue_on_error")
collection = db.test_continue_on_error
oid = collection.insert({"one": 1})
self.assertEqual(1, collection.count())
docs = []
docs.append({"_id": oid, "two": 2}) # Duplicate _id.
docs.append({"three": 3})
docs.append({"four": 4})
docs.append({"five": 5})
with self.assertRaises(DuplicateKeyError):
collection.insert(docs, manipulate=False)
self.assertEqual(1, collection.count())
with self.assertRaises(DuplicateKeyError):
collection.insert(docs, manipulate=False, continue_on_error=True)
self.assertEqual(4, collection.count())
db.drop_collection("test_continue_on_error")
oid = collection.insert({"_id": oid, "one": 1}, w=0)
wait_until(lambda: 1 == collection.count(), 'insert 1 document')
docs[0].pop("_id")
docs[2]["_id"] = oid
with self.assertRaises(DuplicateKeyError):
collection.insert(docs, manipulate=False)
self.assertEqual(3, collection.count())
collection.insert(docs, manipulate=False, continue_on_error=True, w=0)
wait_until(lambda: 6 == collection.count(), 'insert 3 documents')
def test_acknowledged_insert(self):
# Tests legacy insert.
db = self.db
db.drop_collection("test_acknowledged_insert")
collection = db.test_acknowledged_insert
a = {"hello": "world"}
collection.insert(a)
collection.insert(a, w=0)
self.assertRaises(OperationFailure,
collection.insert, a)
def test_insert_adds_id(self):
# Tests legacy insert.
doc = {"hello": "world"}
self.db.test.insert(doc)
self.assertTrue("_id" in doc)
docs = [{"hello": "world"}, {"hello": "world"}]
self.db.test.insert(docs)
for doc in docs:
self.assertTrue("_id" in doc)
def test_insert_large_batch(self):
# Tests legacy insert.
db = self.client.test_insert_large_batch
self.addCleanup(self.client.drop_database, 'test_insert_large_batch')
max_bson_size = self.client.max_bson_size
if client_context.version.at_least(2, 5, 4, -1):
# Write commands are limited to 16MB + 16k per batch
big_string = 'x' * int(max_bson_size / 2)
else:
big_string = 'x' * (max_bson_size - 100)
# Batch insert that requires 2 batches.
successful_insert = [{'x': big_string}, {'x': big_string},
{'x': big_string}, {'x': big_string}]
db.collection_0.insert(successful_insert, w=1)
self.assertEqual(4, db.collection_0.count())
# Test that inserts fail after first error.
insert_second_fails = [{'_id': 'id0', 'x': big_string},
{'_id': 'id0', 'x': big_string},
{'_id': 'id1', 'x': big_string},
{'_id': 'id2', 'x': big_string}]
with self.assertRaises(DuplicateKeyError):
db.collection_1.insert(insert_second_fails)
self.assertEqual(1, db.collection_1.count())
# 2 batches, 2nd insert fails, don't continue on error.
self.assertTrue(db.collection_2.insert(insert_second_fails, w=0))
wait_until(lambda: 1 == db.collection_2.count(),
'insert 1 document', timeout=60)
# 2 batches, ids of docs 0 and 1 are dupes, ids of docs 2 and 3 are
# dupes. Acknowledged, continue on error.
insert_two_failures = [{'_id': 'id0', 'x': big_string},
{'_id': 'id0', 'x': big_string},
{'_id': 'id1', 'x': big_string},
{'_id': 'id1', 'x': big_string}]
with self.assertRaises(OperationFailure) as context:
db.collection_3.insert(insert_two_failures,
continue_on_error=True, w=1)
self.assertIn('id1', str(context.exception))
# Only the first and third documents should be inserted.
self.assertEqual(2, db.collection_3.count())
# 2 batches, 2 errors, unacknowledged, continue on error.
db.collection_4.insert(insert_two_failures, continue_on_error=True, w=0)
# Only the first and third documents are inserted.
wait_until(lambda: 2 == db.collection_4.count(),
'insert 2 documents', timeout=60)
def test_bad_dbref(self):
# Requires the legacy API to test.
c = self.db.test
c.drop()
# Incomplete DBRefs.
self.assertRaises(
InvalidDocument,
c.insert_one, {'ref': {'$ref': 'collection'}})
self.assertRaises(
InvalidDocument,
c.insert_one, {'ref': {'$id': ObjectId()}})
ref_only = {'ref': {'$ref': 'collection'}}
id_only = {'ref': {'$id': ObjectId()}}
# Starting with MongoDB 2.5.2 this is no longer possible
# from insert, update, or findAndModify.
if not client_context.version.at_least(2, 5, 2):
# Force insert of ref without $id.
c.insert(ref_only, check_keys=False)
self.assertEqual(DBRef('collection', id=None),
c.find_one()['ref'])
c.drop()
# DBRef without $ref is decoded as normal subdocument.
c.insert(id_only, check_keys=False)
self.assertEqual(id_only, c.find_one())
def test_update(self):
# Tests legacy update.
db = self.db
db.drop_collection("test")
id1 = db.test.save({"x": 5})
db.test.update({}, {"$inc": {"x": 1}})
self.assertEqual(db.test.find_one(id1)["x"], 6)
id2 = db.test.save({"x": 1})
db.test.update({"x": 6}, {"$inc": {"x": 1}})
self.assertEqual(db.test.find_one(id1)["x"], 7)
self.assertEqual(db.test.find_one(id2)["x"], 1)
def test_update_manipulate(self):
# Tests legacy update.
db = self.db
db.drop_collection("test")
db.test.insert({'_id': 1})
db.test.update({'_id': 1}, {'a': 1}, manipulate=True)
self.assertEqual(
{'_id': 1, 'a': 1},
db.test.find_one())
class AddField(SONManipulator):
def transform_incoming(self, son, dummy):
son['field'] = 'value'
return son
db.add_son_manipulator(AddField())
db.test.update({'_id': 1}, {'a': 2}, manipulate=False)
self.assertEqual(
{'_id': 1, 'a': 2},
db.test.find_one())
db.test.update({'_id': 1}, {'a': 3}, manipulate=True)
self.assertEqual(
{'_id': 1, 'a': 3, 'field': 'value'},
db.test.find_one())
def test_update_nmodified(self):
# Tests legacy update.
db = self.db
db.drop_collection("test")
ismaster = self.client.admin.command('ismaster')
used_write_commands = (ismaster.get("maxWireVersion", 0) > 1)
db.test.insert({'_id': 1})
result = db.test.update({'_id': 1}, {'$set': {'x': 1}})
if used_write_commands:
self.assertEqual(1, result['nModified'])
else:
self.assertFalse('nModified' in result)
# x is already 1.
result = db.test.update({'_id': 1}, {'$set': {'x': 1}})
if used_write_commands:
self.assertEqual(0, result['nModified'])
else:
self.assertFalse('nModified' in result)
def test_multi_update(self):
# Tests legacy update.
db = self.db
db.drop_collection("test")
db.test.save({"x": 4, "y": 3})
db.test.save({"x": 5, "y": 5})
db.test.save({"x": 4, "y": 4})
db.test.update({"x": 4}, {"$set": {"y": 5}}, multi=True)
self.assertEqual(3, db.test.count())
for doc in db.test.find():
self.assertEqual(5, doc["y"])
self.assertEqual(2, db.test.update({"x": 4}, {"$set": {"y": 6}},
multi=True)["n"])
def test_upsert(self):
# Tests legacy update.
db = self.db
db.drop_collection("test")
db.test.update({"page": "/"}, {"$inc": {"count": 1}}, upsert=True)
db.test.update({"page": "/"}, {"$inc": {"count": 1}}, upsert=True)
self.assertEqual(1, db.test.count())
self.assertEqual(2, db.test.find_one()["count"])
def test_acknowledged_update(self):
# Tests legacy update.
db = self.db
db.drop_collection("test_acknowledged_update")
collection = db.test_acknowledged_update
collection.create_index("x", unique=True)
collection.insert({"x": 5})
_id = collection.insert({"x": 4})
self.assertEqual(
None, collection.update({"_id": _id}, {"$inc": {"x": 1}}, w=0))
self.assertRaises(DuplicateKeyError, collection.update,
{"_id": _id}, {"$inc": {"x": 1}})
self.assertEqual(1, collection.update({"_id": _id},
{"$inc": {"x": 2}})["n"])
self.assertEqual(0, collection.update({"_id": "foo"},
{"$inc": {"x": 2}})["n"])
db.drop_collection("test_acknowledged_update")
def test_update_backward_compat(self):
# MongoDB versions >= 2.6.0 don't return the updatedExisting field
# and return upsert _id in an array subdocument. This test should
# pass regardless of server version or type (mongod/s).
# Tests legacy update.
c = self.db.test
c.drop()
oid = ObjectId()
res = c.update({'_id': oid}, {'$set': {'a': 'a'}}, upsert=True)
self.assertFalse(res.get('updatedExisting'))
self.assertEqual(oid, res.get('upserted'))
res = c.update({'_id': oid}, {'$set': {'b': 'b'}})
self.assertTrue(res.get('updatedExisting'))
def test_save(self):
# Tests legacy save.
self.db.drop_collection("test_save")
collection = self.db.test_save
# Save a doc with autogenerated id
_id = collection.save({"hello": "world"})
self.assertEqual(collection.find_one()["_id"], _id)
self.assertTrue(isinstance(_id, ObjectId))
# Save a doc with explicit id
collection.save({"_id": "explicit_id", "hello": "bar"})
doc = collection.find_one({"_id": "explicit_id"})
self.assertEqual(doc['_id'], 'explicit_id')
self.assertEqual(doc['hello'], 'bar')
# Save docs with _id field already present (shouldn't create new docs)
self.assertEqual(2, collection.count())
collection.save({'_id': _id, 'hello': 'world'})
self.assertEqual(2, collection.count())
collection.save({'_id': 'explicit_id', 'hello': 'baz'})
self.assertEqual(2, collection.count())
self.assertEqual(
'baz',
collection.find_one({'_id': 'explicit_id'})['hello']
)
# Acknowledged mode.
collection.create_index("hello", unique=True)
# No exception, even though we duplicate the first doc's "hello" value
collection.save({'_id': 'explicit_id', 'hello': 'world'}, w=0)
self.assertRaises(
DuplicateKeyError,
collection.save,
{'_id': 'explicit_id', 'hello': 'world'})
self.db.drop_collection("test")
def test_save_with_invalid_key(self):
# Tests legacy save.
self.db.drop_collection("test")
self.assertTrue(self.db.test.insert({"hello": "world"}))
doc = self.db.test.find_one()
doc['a.b'] = 'c'
expected = InvalidDocument
if client_context.version.at_least(2, 5, 4, -1):
expected = OperationFailure
self.assertRaises(expected, self.db.test.save, doc)
def test_acknowledged_save(self):
# Tests legacy save.
db = self.db
db.drop_collection("test_acknowledged_save")
collection = db.test_acknowledged_save
collection.create_index("hello", unique=True)
collection.save({"hello": "world"})
collection.save({"hello": "world"}, w=0)
self.assertRaises(DuplicateKeyError, collection.save,
{"hello": "world"})
db.drop_collection("test_acknowledged_save")
def test_save_adds_id(self):
# Tests legacy save.
doc = {"hello": "jesse"}
self.db.test.save(doc)
self.assertTrue("_id" in doc)
def test_save_returns_id(self):
doc = {"hello": "jesse"}
_id = self.db.test.save(doc)
self.assertTrue(isinstance(_id, ObjectId))
self.assertEqual(_id, doc["_id"])
doc["hi"] = "bernie"
_id = self.db.test.save(doc)
self.assertTrue(isinstance(_id, ObjectId))
self.assertEqual(_id, doc["_id"])
def test_remove_one(self):
# Tests legacy remove.
self.db.test.remove()
self.assertEqual(0, self.db.test.count())
self.db.test.insert({"x": 1})
self.db.test.insert({"y": 1})
self.db.test.insert({"z": 1})
self.assertEqual(3, self.db.test.count())
self.db.test.remove(multi=False)
self.assertEqual(2, self.db.test.count())
self.db.test.remove()
self.assertEqual(0, self.db.test.count())
def test_remove_all(self):
# Tests legacy remove.
self.db.test.remove()
self.assertEqual(0, self.db.test.count())
self.db.test.insert({"x": 1})
self.db.test.insert({"y": 1})
self.assertEqual(2, self.db.test.count())
self.db.test.remove()
self.assertEqual(0, self.db.test.count())
def test_remove_non_objectid(self):
# Tests legacy remove.
db = self.db
db.drop_collection("test")
db.test.insert_one({"_id": 5})
self.assertEqual(1, db.test.count())
db.test.remove(5)
self.assertEqual(0, db.test.count())
def test_write_large_document(self):
# Tests legacy insert, save, and update.
max_size = self.db.client.max_bson_size
half_size = int(max_size / 2)
self.assertEqual(max_size, 16777216)
expected = DocumentTooLarge
if client_context.version.at_least(2, 5, 4, -1):
# Document too large handled by the server
expected = OperationFailure
self.assertRaises(expected, self.db.test.insert,
{"foo": "x" * max_size})
self.assertRaises(expected, self.db.test.save,
{"foo": "x" * max_size})
self.assertRaises(expected, self.db.test.insert,
[{"x": 1}, {"foo": "x" * max_size}])
self.db.test.insert([{"foo": "x" * half_size},
{"foo": "x" * half_size}])
self.db.test.insert({"bar": "x"})
# Use w=0 here to test legacy doc size checking in all server versions
self.assertRaises(DocumentTooLarge, self.db.test.update,
{"bar": "x"}, {"bar": "x" * (max_size - 14)}, w=0)
# This will pass with OP_UPDATE or the update command.
self.db.test.update({"bar": "x"}, {"bar": "x" * (max_size - 32)})
def test_last_error_options(self):
# Tests legacy write methods.
self.db.test.save({"x": 1}, w=1, wtimeout=1)
self.db.test.insert({"x": 1}, w=1, wtimeout=1)
self.db.test.remove({"x": 1}, w=1, wtimeout=1)
self.db.test.update({"x": 1}, {"y": 2}, w=1, wtimeout=1)
if client_context.replica_set_name:
# client_context.w is the number of hosts in the replica set
w = client_context.w + 1
# MongoDB 2.8+ raises error code 100, CannotSatisfyWriteConcern,
# if w > number of members. Older versions just time out after 1 ms
# as if they had enough secondaries but some are lagging. They
# return an error with 'wtimeout': True and no code.
def wtimeout_err(f, *args, **kwargs):
try:
f(*args, **kwargs)
except WTimeoutError as exc:
self.assertIsNotNone(exc.details)
except OperationFailure as exc:
self.assertIsNotNone(exc.details)
self.assertEqual(100, exc.code,
"Unexpected error: %r" % exc)
else:
self.fail("%s should have failed" % f)
coll = self.db.test
wtimeout_err(coll.save, {"x": 1}, w=w, wtimeout=1)
wtimeout_err(coll.insert, {"x": 1}, w=w, wtimeout=1)
wtimeout_err(coll.update, {"x": 1}, {"y": 2}, w=w, wtimeout=1)
wtimeout_err(coll.remove, {"x": 1}, w=w, wtimeout=1)
# can't use fsync and j options together
self.assertRaises(ConfigurationError, self.db.test.insert,
{"_id": 1}, j=True, fsync=True)
def test_find_and_modify(self):
c = self.db.test
c.drop()
c.insert({'_id': 1, 'i': 1})
# Test that we raise DuplicateKeyError when appropriate.
# MongoDB doesn't have a code field for DuplicateKeyError
# from commands before 2.2.
if client_context.version.at_least(2, 2):
c.ensure_index('i', unique=True)
self.assertRaises(DuplicateKeyError,
c.find_and_modify, query={'i': 1, 'j': 1},
update={'$set': {'k': 1}}, upsert=True)
c.drop_indexes()
# Test correct findAndModify
self.assertEqual({'_id': 1, 'i': 1},
c.find_and_modify({'_id': 1}, {'$inc': {'i': 1}}))
self.assertEqual({'_id': 1, 'i': 3},
c.find_and_modify({'_id': 1}, {'$inc': {'i': 1}},
new=True))
self.assertEqual({'_id': 1, 'i': 3},
c.find_and_modify({'_id': 1}, remove=True))
self.assertEqual(None, c.find_one({'_id': 1}))
self.assertEqual(None,
c.find_and_modify({'_id': 1}, {'$inc': {'i': 1}}))
# The return value changed in 2.1.2. See SERVER-6226.
if client_context.version.at_least(2, 1, 2):
self.assertEqual(None, c.find_and_modify({'_id': 1},
{'$inc': {'i': 1}},
upsert=True))
else:
self.assertEqual({}, c.find_and_modify({'_id': 1},
{'$inc': {'i': 1}},
upsert=True))
self.assertEqual({'_id': 1, 'i': 2},
c.find_and_modify({'_id': 1}, {'$inc': {'i': 1}},
upsert=True, new=True))
self.assertEqual({'_id': 1, 'i': 2},
c.find_and_modify({'_id': 1}, {'$inc': {'i': 1}},
fields=['i']))
self.assertEqual({'_id': 1, 'i': 4},
c.find_and_modify({'_id': 1}, {'$inc': {'i': 1}},
new=True, fields={'i': 1}))
# Test with full_response=True.
result = c.find_and_modify({'_id': 1}, {'$inc': {'i': 1}},
new=True, upsert=True,
full_response=True,
fields={'i': 1})
self.assertEqual({'_id': 1, 'i': 5}, result["value"])
self.assertEqual(True,
result["lastErrorObject"]["updatedExisting"])
result = c.find_and_modify({'_id': 2}, {'$inc': {'i': 1}},
new=True, upsert=True,
full_response=True,
fields={'i': 1})
self.assertEqual({'_id': 2, 'i': 1}, result["value"])
self.assertEqual(False,
result["lastErrorObject"]["updatedExisting"])
class ExtendedDict(dict):
pass
result = c.find_and_modify({'_id': 1}, {'$inc': {'i': 1}},
new=True, fields={'i': 1})
self.assertFalse(isinstance(result, ExtendedDict))
c = self.db.get_collection(
"test", codec_options=CodecOptions(document_class=ExtendedDict))
result = c.find_and_modify({'_id': 1}, {'$inc': {'i': 1}},
new=True, fields={'i': 1})
self.assertTrue(isinstance(result, ExtendedDict))
def test_find_and_modify_with_sort(self):
c = self.db.test
c.drop()
for j in range(5):
c.insert({'j': j, 'i': 0})
sort = {'j': DESCENDING}
self.assertEqual(4, c.find_and_modify({},
{'$inc': {'i': 1}},
sort=sort)['j'])
sort = {'j': ASCENDING}
self.assertEqual(0, c.find_and_modify({},
{'$inc': {'i': 1}},
sort=sort)['j'])
sort = [('j', DESCENDING)]
self.assertEqual(4, c.find_and_modify({},
{'$inc': {'i': 1}},
sort=sort)['j'])
sort = [('j', ASCENDING)]
self.assertEqual(0, c.find_and_modify({},
{'$inc': {'i': 1}},
sort=sort)['j'])
sort = SON([('j', DESCENDING)])
self.assertEqual(4, c.find_and_modify({},
{'$inc': {'i': 1}},
sort=sort)['j'])
sort = SON([('j', ASCENDING)])
self.assertEqual(0, c.find_and_modify({},
{'$inc': {'i': 1}},
sort=sort)['j'])
try:
from collections import OrderedDict
sort = OrderedDict([('j', DESCENDING)])
self.assertEqual(4, c.find_and_modify({},
{'$inc': {'i': 1}},
sort=sort)['j'])
sort = OrderedDict([('j', ASCENDING)])
self.assertEqual(0, c.find_and_modify({},
{'$inc': {'i': 1}},
sort=sort)['j'])
except ImportError:
pass
# Test that a standard dict with two keys is rejected.
sort = {'j': DESCENDING, 'foo': DESCENDING}
self.assertRaises(TypeError, c.find_and_modify,
{}, {'$inc': {'i': 1}}, sort=sort)
def test_find_and_modify_with_manipulator(self):
class AddCollectionNameManipulator(SONManipulator):
def will_copy(self):
return True
def transform_incoming(self, son, dummy):
copy = SON(son)
if 'collection' in copy:
del copy['collection']
return copy
def transform_outgoing(self, son, collection):
copy = SON(son)
copy['collection'] = collection.name
return copy
db = self.client.pymongo_test
db.add_son_manipulator(AddCollectionNameManipulator())
c = db.test
c.drop()
c.insert({'_id': 1, 'i': 1})
# Test correct findAndModify
# With manipulators
self.assertEqual({'_id': 1, 'i': 1, 'collection': 'test'},
c.find_and_modify({'_id': 1}, {'$inc': {'i': 1}},
manipulate=True))
self.assertEqual({'_id': 1, 'i': 3, 'collection': 'test'},
c.find_and_modify({'_id': 1}, {'$inc': {'i': 1}},
new=True, manipulate=True))
# With out manipulators
self.assertEqual({'_id': 1, 'i': 3},
c.find_and_modify({'_id': 1}, {'$inc': {'i': 1}}))
self.assertEqual({'_id': 1, 'i': 5},
c.find_and_modify({'_id': 1}, {'$inc': {'i': 1}},
new=True))
def test_last_status(self):
# Tests many legacy API elements.
# We must call getlasterror on same socket as the last operation.
db = rs_or_single_client(maxPoolSize=1).pymongo_test
collection = db.test_last_status
collection.remove({})
collection.save({"i": 1})
collection.update({"i": 1}, {"$set": {"i": 2}}, w=0)
self.assertTrue(db.last_status()["updatedExisting"])
collection.update({"i": 1}, {"$set": {"i": 500}}, w=0)
self.assertFalse(db.last_status()["updatedExisting"])
def test_auto_ref_and_deref(self):
# Legacy API.
db = self.client.pymongo_test
db.add_son_manipulator(AutoReference(db))
db.add_son_manipulator(NamespaceInjector())
db.test.a.remove({})
db.test.b.remove({})
db.test.c.remove({})
a = {"hello": u("world")}
db.test.a.save(a)
b = {"test": a}
db.test.b.save(b)
c = {"another test": b}
db.test.c.save(c)
a["hello"] = "mike"
db.test.a.save(a)
self.assertEqual(db.test.a.find_one(), a)
self.assertEqual(db.test.b.find_one()["test"], a)
self.assertEqual(db.test.c.find_one()["another test"]["test"], a)
self.assertEqual(db.test.b.find_one(), b)
self.assertEqual(db.test.c.find_one()["another test"], b)
self.assertEqual(db.test.c.find_one(), c)
def test_auto_ref_and_deref_list(self):
# Legacy API.
db = self.client.pymongo_test
db.add_son_manipulator(AutoReference(db))
db.add_son_manipulator(NamespaceInjector())
db.drop_collection("users")
db.drop_collection("messages")
message_1 = {"title": "foo"}
db.messages.save(message_1)
message_2 = {"title": "bar"}
db.messages.save(message_2)
user = {"messages": [message_1, message_2]}
db.users.save(user)
db.messages.update(message_1, {"title": "buzz"})
self.assertEqual("buzz", db.users.find_one()["messages"][0]["title"])
self.assertEqual("bar", db.users.find_one()["messages"][1]["title"])
def test_object_to_dict_transformer(self):
# PYTHON-709: Some users rely on their custom SONManipulators to run
# before any other checks, so they can insert non-dict objects and
# have them dictified before the _id is inserted or any other
# processing.
# Tests legacy API elements.
class Thing(object):
def __init__(self, value):
self.value = value
class ThingTransformer(SONManipulator):
def transform_incoming(self, thing, dummy):
return {'value': thing.value}
db = self.client.foo
db.add_son_manipulator(ThingTransformer())
t = Thing('value')
db.test.remove()
db.test.insert([t])
out = db.test.find_one()
self.assertEqual('value', out.get('value'))
def test_son_manipulator_outgoing(self):
class Thing(object):
def __init__(self, value):
self.value = value
class ThingTransformer(SONManipulator):
def transform_outgoing(self, doc, collection):
# We don't want this applied to the command return
# value in pymongo.cursor.Cursor.
if 'value' in doc:
return Thing(doc['value'])
return doc
db = self.client.foo
db.add_son_manipulator(ThingTransformer())
db.test.delete_many({})
db.test.insert_one({'value': 'value'})
out = db.test.find_one()
self.assertTrue(isinstance(out, Thing))
self.assertEqual('value', out.value)
if client_context.version.at_least(2, 6):
out = next(db.test.aggregate([], cursor={}))
self.assertTrue(isinstance(out, Thing))
self.assertEqual('value', out.value)
def test_son_manipulator_inheritance(self):
# Tests legacy API elements.
class Thing(object):
def __init__(self, value):
self.value = value
class ThingTransformer(SONManipulator):
def transform_incoming(self, thing, dummy):
return {'value': thing.value}
def transform_outgoing(self, son, dummy):
return Thing(son['value'])
class Child(ThingTransformer):
pass
db = self.client.foo
db.add_son_manipulator(Child())
t = Thing('value')
db.test.remove()
db.test.insert([t])
out = db.test.find_one()
self.assertTrue(isinstance(out, Thing))
self.assertEqual('value', out.value)
def test_disabling_manipulators(self):
class IncByTwo(SONManipulator):
def transform_outgoing(self, son, collection):
if 'foo' in son:
son['foo'] += 2
return son
db = self.client.pymongo_test
db.add_son_manipulator(IncByTwo())
c = db.test
c.drop()
c.insert({'foo': 0})
self.assertEqual(2, c.find_one()['foo'])
self.assertEqual(0, c.find_one(manipulate=False)['foo'])
self.assertEqual(2, c.find_one(manipulate=True)['foo'])
c.drop()
def test_manipulator_properties(self):
db = self.client.foo
self.assertEqual([], db.incoming_manipulators)
self.assertEqual([], db.incoming_copying_manipulators)
self.assertEqual([], db.outgoing_manipulators)
self.assertEqual([], db.outgoing_copying_manipulators)
db.add_son_manipulator(AutoReference(db))
db.add_son_manipulator(NamespaceInjector())
db.add_son_manipulator(ObjectIdShuffler())
self.assertEqual(1, len(db.incoming_manipulators))
self.assertEqual(db.incoming_manipulators, ['NamespaceInjector'])
self.assertEqual(2, len(db.incoming_copying_manipulators))
for name in db.incoming_copying_manipulators:
self.assertTrue(name in ('ObjectIdShuffler', 'AutoReference'))
self.assertEqual([], db.outgoing_manipulators)
self.assertEqual(['AutoReference'], db.outgoing_copying_manipulators)
def test_ensure_index(self):
db = self.db
self.assertRaises(TypeError, db.test.ensure_index, {"hello": 1})
self.assertRaises(TypeError,
db.test.ensure_index, {"hello": 1}, cache_for='foo')
db.test.drop_indexes()
self.assertEqual("goodbye_1",
db.test.ensure_index("goodbye"))
self.assertEqual(None, db.test.ensure_index("goodbye"))
db.test.drop_indexes()
self.assertEqual("foo",
db.test.ensure_index("goodbye", name="foo"))
self.assertEqual(None, db.test.ensure_index("goodbye", name="foo"))
db.test.drop_indexes()
self.assertEqual("goodbye_1",
db.test.ensure_index("goodbye"))
self.assertEqual(None, db.test.ensure_index("goodbye"))
db.test.drop_index("goodbye_1")
self.assertEqual("goodbye_1",
db.test.ensure_index("goodbye"))
self.assertEqual(None, db.test.ensure_index("goodbye"))
db.drop_collection("test")
self.assertEqual("goodbye_1",
db.test.ensure_index("goodbye"))
self.assertEqual(None, db.test.ensure_index("goodbye"))
db.test.drop_index("goodbye_1")
self.assertEqual("goodbye_1",
db.test.ensure_index("goodbye"))
self.assertEqual(None, db.test.ensure_index("goodbye"))
db.test.drop_index("goodbye_1")
self.assertEqual("goodbye_1",
db.test.ensure_index("goodbye", cache_for=1))
time.sleep(1.2)
self.assertEqual("goodbye_1",
db.test.ensure_index("goodbye"))
# Make sure the expiration time is updated.
self.assertEqual(None,
db.test.ensure_index("goodbye"))
# Clean up indexes for later tests
db.test.drop_indexes()
def test_ensure_unique_index_threaded(self):
coll = self.db.test_unique_threaded
coll.drop()
coll.insert_many([{'foo': i} for i in range(10000)])
class Indexer(threading.Thread):
def run(self):
try:
coll.ensure_index('foo', unique=True)
coll.insert_one({'foo': 'bar'})
coll.insert_one({'foo': 'bar'})
except OperationFailure:
pass
threads = []
for _ in range(10):
t = Indexer()
t.setDaemon(True)
threads.append(t)
for i in range(10):
threads[i].start()
joinall(threads)
self.assertEqual(10001, coll.count())
coll.drop()
if __name__ == "__main__":
unittest.main()