MOTOR-969 Support for Range Indexes (#195)

This commit is contained in:
Julius Park 2023-03-15 15:32:46 -07:00 committed by GitHub
parent f77fa40ec5
commit f4422ddaaf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 146 additions and 5 deletions

View File

@ -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
-----------

View File

@ -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,

View File

@ -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,

View File

@ -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()

View File

@ -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__":