MOTOR-969 Support for Range Indexes (#195)
This commit is contained in:
parent
f77fa40ec5
commit
f4422ddaaf
@ -6,8 +6,9 @@ Changelog
|
||||
Motor 3.2.0
|
||||
-----------
|
||||
|
||||
Motor 3.2 adds support for Queryable Encryption helper :meth:`~motor.core.MotorClientEncryption.create_encrypted_collection`,
|
||||
part of the Queryable Encryption beta. Backwards-breaking changes may be made before the final release.
|
||||
Motor 3.2 adds support for Queryable Encryption helpers :meth:`~motor.core.MotorClientEncryption.create_encrypted_collection` and
|
||||
:meth:`~motor.core.MotorClientEncryption.encrypt_expression`, which are part of the Queryable Encryption beta.
|
||||
Backwards-breaking changes may be made before the final release.
|
||||
|
||||
Motor 3.1.1
|
||||
-----------
|
||||
|
||||
@ -1979,6 +1979,8 @@ class AgnosticClientEncryption(AgnosticBase):
|
||||
add_key_alt_name = AsyncCommand()
|
||||
get_key_by_alt_name = AsyncCommand()
|
||||
remove_key_alt_name = AsyncCommand()
|
||||
if hasattr(ClientEncryption, "encrypt_expression"):
|
||||
encrypt_expression = AsyncCommand()
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
|
||||
@ -776,6 +776,7 @@ class ClientEncryption(Synchro):
|
||||
__delegate_class__ = motor.MotorClientEncryption
|
||||
|
||||
_enc_col = Sync("create_encrypted_collection")
|
||||
encrypt_expression = Sync("encrypt_expression")
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
|
||||
@ -19,16 +19,22 @@ import sys
|
||||
import traceback
|
||||
import unittest
|
||||
from test.asyncio_tests import AsyncIOTestCase, asyncio_test
|
||||
from test.test_environment import env
|
||||
from test.utils import ignore_deprecations
|
||||
|
||||
import pymongo
|
||||
from bson import CodecOptions
|
||||
from bson.binary import JAVA_LEGACY
|
||||
from pymongo import ReadPreference, WriteConcern
|
||||
from pymongo.encryption import Algorithm, QueryType
|
||||
from pymongo.errors import BulkWriteError, DuplicateKeyError, OperationFailure
|
||||
from pymongo.read_concern import ReadConcern
|
||||
from pymongo.read_preferences import Secondary
|
||||
|
||||
from motor.motor_asyncio import AsyncIOMotorCollection
|
||||
from motor.motor_asyncio import AsyncIOMotorClientEncryption, AsyncIOMotorCollection
|
||||
|
||||
if pymongo.version_tuple >= (4, 4, 0):
|
||||
from pymongo.encryption_options import RangeOpts
|
||||
|
||||
|
||||
class TestAsyncIOCollection(AsyncIOTestCase):
|
||||
@ -260,6 +266,79 @@ class TestAsyncIOCollection(AsyncIOTestCase):
|
||||
self.assertEqual(read_preference, c.read_preference)
|
||||
self.assertEqual(codec_options, c.codec_options)
|
||||
|
||||
@env.require_version_min(6, 2, -1, -1)
|
||||
@asyncio_test
|
||||
async def test_async_create_encrypted_collection(self):
|
||||
if pymongo.version_tuple < (4, 4, 0):
|
||||
raise unittest.SkipTest("Requires PyMongo 4.4+")
|
||||
c = self.collection
|
||||
KMS_PROVIDERS = {"local": {"key": b"\x00" * 96}}
|
||||
self.cx.drop_database("db")
|
||||
async with AsyncIOMotorClientEncryption(
|
||||
KMS_PROVIDERS, "keyvault.datakeys", c, CodecOptions()
|
||||
) as client_encryption:
|
||||
coll, ef = await client_encryption.create_encrypted_collection(
|
||||
database=self.db,
|
||||
name="testing1",
|
||||
encrypted_fields={"fields": [{"path": "ssn", "bsonType": "string", "keyId": None}]},
|
||||
kms_provider="local",
|
||||
)
|
||||
with self.assertRaises(pymongo.errors.WriteError) as exc:
|
||||
await coll.insert_one({"ssn": "123-45-6789"})
|
||||
self.assertEqual(exc.exception.code, 121)
|
||||
await self.db.drop_collection("testing1", encrypted_fields=ef)
|
||||
|
||||
@asyncio_test
|
||||
async def test_async_encrypt_expression(self):
|
||||
if pymongo.version_tuple < (4, 4, 0):
|
||||
raise unittest.SkipTest("Requires PyMongo 4.4+")
|
||||
c = self.collection
|
||||
KMS_PROVIDERS = {"local": {"key": b"\x00" * 96}}
|
||||
self.cx.drop_database("db")
|
||||
async with AsyncIOMotorClientEncryption(
|
||||
KMS_PROVIDERS, "keyvault.datakeys", c, CodecOptions()
|
||||
) as client_encryption:
|
||||
data_key = await client_encryption.create_data_key(
|
||||
"local", key_alt_names=["pymongo_encryption_example_1"]
|
||||
)
|
||||
name = "DoubleNoPrecision"
|
||||
range_opts = RangeOpts(sparsity=1)
|
||||
for i in [6.0, 30.0, 200.0]:
|
||||
insert_payload = await client_encryption.encrypt(
|
||||
float(i),
|
||||
key_id=data_key,
|
||||
algorithm=Algorithm.RANGEPREVIEW,
|
||||
contention_factor=0,
|
||||
range_opts=range_opts,
|
||||
)
|
||||
self.collection.insert_one(
|
||||
{
|
||||
f"encrypted{name}": insert_payload,
|
||||
}
|
||||
)
|
||||
self.assertEqual(await client_encryption.decrypt(insert_payload), i)
|
||||
|
||||
find_payload = await client_encryption.encrypt_expression(
|
||||
expression={
|
||||
"$and": [
|
||||
{f"encrypted{name}": {"$gte": 6.0}},
|
||||
{f"encrypted{name}": {"$lte": 200.0}},
|
||||
]
|
||||
},
|
||||
key_id=data_key,
|
||||
algorithm=Algorithm.RANGEPREVIEW,
|
||||
query_type=QueryType.RANGEPREVIEW,
|
||||
contention_factor=0,
|
||||
range_opts=range_opts,
|
||||
)
|
||||
|
||||
sorted_find = sorted(
|
||||
await self.collection.explicit_encryption.find(find_payload).to_list(3),
|
||||
key=lambda x: x["_id"],
|
||||
)
|
||||
for elem, expected in zip(sorted_find, [6.0, 30.0, 200.0]):
|
||||
self.assertEqual(elem[f"encrypted{name}"], expected)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@ -17,6 +17,7 @@
|
||||
import sys
|
||||
import traceback
|
||||
import unittest
|
||||
from test.test_environment import env
|
||||
from test.tornado_tests import MotorTest
|
||||
from test.utils import ignore_deprecations
|
||||
|
||||
@ -24,6 +25,7 @@ import pymongo.errors
|
||||
from bson import CodecOptions
|
||||
from bson.binary import JAVA_LEGACY
|
||||
from pymongo import ReadPreference, WriteConcern
|
||||
from pymongo.encryption import Algorithm, QueryType
|
||||
from pymongo.errors import BulkWriteError, DuplicateKeyError, OperationFailure
|
||||
from pymongo.read_concern import ReadConcern
|
||||
from pymongo.read_preferences import Secondary
|
||||
@ -33,6 +35,9 @@ from tornado.testing import gen_test
|
||||
import motor
|
||||
import motor.motor_tornado
|
||||
|
||||
if pymongo.version_tuple >= (4, 4, 0):
|
||||
from pymongo.encryption_options import RangeOpts
|
||||
|
||||
|
||||
class MotorCollectionTest(MotorTest):
|
||||
@gen_test
|
||||
@ -262,10 +267,12 @@ class MotorCollectionTest(MotorTest):
|
||||
self.assertEqual(read_preference, c.read_preference)
|
||||
self.assertEqual(codec_options, c.codec_options)
|
||||
|
||||
@env.require_version_min(6, 2, -1, -1)
|
||||
@gen_test
|
||||
async def test_async_create_encrypted_collection(self):
|
||||
if pymongo.version_tuple < (4, 4, 0):
|
||||
raise unittest.SkipTest("Requires PyMongo 4.4+")
|
||||
await self.db.drop_collection("test_collection")
|
||||
c = self.collection
|
||||
KMS_PROVIDERS = {"local": {"key": b"\x00" * 96}}
|
||||
self.cx.drop_database("db")
|
||||
@ -279,9 +286,60 @@ class MotorCollectionTest(MotorTest):
|
||||
kms_provider="local",
|
||||
)
|
||||
with self.assertRaises(pymongo.errors.WriteError) as exc:
|
||||
coll.insert_one({"ssn": "123-45-6789"})
|
||||
self.addCleanup(self.db.drop_collection, "testing1", encrypted_fields=ef)
|
||||
await coll.insert_one({"ssn": "123-45-6789"})
|
||||
self.assertEqual(exc.exception.code, 121)
|
||||
await self.db.drop_collection("testing1", encrypted_fields=ef)
|
||||
|
||||
@gen_test
|
||||
async def test_async_encrypt_expression(self):
|
||||
if pymongo.version_tuple < (4, 4, 0):
|
||||
raise unittest.SkipTest("Requires PyMongo 4.4+")
|
||||
c = self.collection
|
||||
KMS_PROVIDERS = {"local": {"key": b"\x00" * 96}}
|
||||
self.cx.drop_database("db")
|
||||
async with motor.MotorClientEncryption(
|
||||
KMS_PROVIDERS, "keyvault.datakeys", c, CodecOptions()
|
||||
) as client_encryption:
|
||||
data_key = await client_encryption.create_data_key(
|
||||
"local", key_alt_names=["pymongo_encryption_example_1"]
|
||||
)
|
||||
name = "DoubleNoPrecision"
|
||||
range_opts = RangeOpts(sparsity=1)
|
||||
for i in [6.0, 30.0, 200.0]:
|
||||
insert_payload = await client_encryption.encrypt(
|
||||
float(i),
|
||||
key_id=data_key,
|
||||
algorithm=Algorithm.RANGEPREVIEW,
|
||||
contention_factor=0,
|
||||
range_opts=range_opts,
|
||||
)
|
||||
self.collection.insert_one(
|
||||
{
|
||||
f"encrypted{name}": insert_payload,
|
||||
}
|
||||
)
|
||||
self.assertEqual(await client_encryption.decrypt(insert_payload), i)
|
||||
|
||||
find_payload = await client_encryption.encrypt_expression(
|
||||
expression={
|
||||
"$and": [
|
||||
{f"encrypted{name}": {"$gte": 6.0}},
|
||||
{f"encrypted{name}": {"$lte": 200.0}},
|
||||
]
|
||||
},
|
||||
key_id=data_key,
|
||||
algorithm=Algorithm.RANGEPREVIEW,
|
||||
query_type=QueryType.RANGEPREVIEW,
|
||||
contention_factor=0,
|
||||
range_opts=range_opts,
|
||||
)
|
||||
|
||||
sorted_find = sorted(
|
||||
await self.collection.explicit_encryption.find(find_payload).to_list(3),
|
||||
key=lambda x: x["_id"],
|
||||
)
|
||||
for elem, expected in zip(sorted_find, [6.0, 30.0, 200.0]):
|
||||
self.assertEqual(elem[f"encrypted{name}"], expected)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Loading…
Reference in New Issue
Block a user