Skip to content

Commit f67e094

Browse files
committed
Improve type of min/max bounds for range queries
1 parent 214999b commit f67e094

File tree

6 files changed

+136
-21
lines changed

6 files changed

+136
-21
lines changed

lib/Doctrine/ODM/MongoDB/Mapping/Annotations/Encrypt.php

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,11 @@
55
namespace Doctrine\ODM\MongoDB\Mapping\Annotations;
66

77
use Attribute;
8+
use DateTimeInterface;
89
use Doctrine\Common\Annotations\Annotation\NamedArgumentConstructor;
9-
use MongoDB\BSON\Type;
10+
use MongoDB\BSON\Decimal128;
11+
use MongoDB\BSON\Int64;
12+
use MongoDB\BSON\UTCDateTime;
1013

1114
/**
1215
* Defines an encrypted field mapping.
@@ -19,6 +22,9 @@
1922
#[Attribute(Attribute::TARGET_CLASS | Attribute::TARGET_PROPERTY)]
2023
final class Encrypt implements Annotation
2124
{
25+
public int|float|Int64|Decimal128|UTCDateTime|null $min;
26+
public int|float|Int64|Decimal128|UTCDateTime|null $max;
27+
2228
/**
2329
* @param EncryptQuery|null $queryType Set the query type for the field, null if not queryable.
2430
* @param int<1, 4>|null $sparsity
@@ -28,12 +34,14 @@ final class Encrypt implements Annotation
2834
*/
2935
public function __construct(
3036
public ?EncryptQuery $queryType = null,
31-
public string|int|Type|null $min = null,
32-
public string|int|Type|null $max = null,
37+
int|float|Int64|Decimal128|UTCDateTime|DateTimeInterface|null $min = null,
38+
int|float|Int64|Decimal128|UTCDateTime|DateTimeInterface|null $max = null,
3339
public ?int $sparsity = null,
3440
public ?int $prevision = null,
3541
public ?int $trimFactor = null,
3642
public ?int $contention = null,
3743
) {
44+
$this->min = $min instanceof DateTimeInterface ? $min : new UTCDateTime($min);
45+
$this->max = $max instanceof DateTimeInterface ? $max : new UTCDateTime($max);
3846
}
3947
}

lib/Doctrine/ODM/MongoDB/Mapping/Driver/XmlDriver.php

Lines changed: 15 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,20 @@
44

55
namespace Doctrine\ODM\MongoDB\Mapping\Driver;
66

7+
use DateTimeImmutable;
78
use Doctrine\ODM\MongoDB\Mapping\Annotations\TimeSeries;
89
use Doctrine\ODM\MongoDB\Mapping\ClassMetadata;
910
use Doctrine\ODM\MongoDB\Mapping\MappingException;
1011
use Doctrine\ODM\MongoDB\Mapping\TimeSeries\Granularity;
12+
use Doctrine\ODM\MongoDB\Types\Type;
1113
use Doctrine\ODM\MongoDB\Utility\CollectionHelper;
1214
use Doctrine\Persistence\Mapping\Driver\FileDriver;
1315
use DOMDocument;
1416
use InvalidArgumentException;
1517
use LibXMLError;
18+
use MongoDB\BSON\Decimal128;
1619
use MongoDB\BSON\Document;
20+
use MongoDB\BSON\UTCDateTime;
1721
use MongoDB\Driver\Exception\UnexpectedValueException;
1822
use SimpleXMLElement;
1923

@@ -313,24 +317,17 @@ public function loadMetadataForClass($className, \Doctrine\Persistence\Mapping\C
313317
if (isset($field->encrypt)) {
314318
$mapping['encrypt'] = [];
315319
foreach ($field->encrypt->attributes() as $encryptKey => $encryptValue) {
316-
switch ($encryptKey) {
317-
case 'queryType':
318-
$mapping['encrypt'][$encryptKey] = (string) $encryptValue;
319-
break;
320-
case 'min':
321-
case 'max':
322-
$mapping['encrypt'][$encryptKey] = match ($mapping['type']) {
323-
'int' => (int) $encryptValue,
324-
'string' => (string) $encryptValue,
325-
};
326-
break;
327-
case 'sparsity':
328-
case 'prevision':
329-
case 'trimFactor':
330-
case 'contention':
331-
$mapping['encrypt'][$encryptKey] = (int) $encryptValue;
332-
break;
333-
}
320+
$mapping['encrypt'][$encryptKey] = match ($encryptKey) {
321+
'queryType' => (string) $encryptValue,
322+
'min', 'max' => match ($mapping['type']) {
323+
Type::INT => (int) $encryptValue,
324+
Type::FLOAT => (float) $encryptValue,
325+
Type::DECIMAL128 => new Decimal128((string) $encryptValue),
326+
Type::DATE, Type::DATE_IMMUTABLE => new UTCDateTime(new DateTimeImmutable((string) $encryptValue)),
327+
default => null, // Invalid
328+
},
329+
'sparsity', 'prevision', 'trimFactor', 'contention' => (int) $encryptValue,
330+
};
334331
}
335332
}
336333

lib/Doctrine/ODM/MongoDB/Mapping/MappingException.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,4 +314,14 @@ public static function rootDocumentCannotBeEncrypted(string $className): self
314314
$className,
315315
));
316316
}
317+
318+
public static function invalidEncryptedQueryRangeType(string $className, string $fieldName, string $type): self
319+
{
320+
return new self(sprintf(
321+
'The field type "%s" for field "%s::%s" is not supported for "range" query on encrypted field.',
322+
$type,
323+
$className,
324+
$fieldName,
325+
));
326+
}
317327
}

tests/Doctrine/ODM/MongoDB/Tests/Mapping/Driver/AbstractDriverTestCase.php

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,19 @@
44

55
namespace Doctrine\ODM\MongoDB\Tests\Mapping\Driver;
66

7+
use DateTimeImmutable;
78
use Doctrine\ODM\MongoDB\Mapping\ClassMetadata;
89
use Doctrine\Persistence\Mapping\Driver\MappingDriver;
910
use Documents\Account;
1011
use Documents\Address;
1112
use Documents\Encryption\ClientCard;
1213
use Documents\Encryption\PatientRecord;
14+
use Documents\Encryption\RangeTypes;
1315
use Documents\Group;
1416
use Documents\Phonenumber;
1517
use Documents\Profile;
18+
use MongoDB\BSON\Decimal128;
19+
use MongoDB\BSON\UTCDateTime;
1620
use MongoDB\Driver\ClientEncryption;
1721
use PHPUnit\Framework\TestCase;
1822
use TestDocuments\EmbeddedDocument;
@@ -557,4 +561,34 @@ public function testEncryptEmbeddedDocumentMapping(): void
557561
self::assertArrayNotHasKey('encrypt', $classMetadata->fieldMappings['type']);
558562
self::assertArrayNotHasKey('encrypt', $classMetadata->fieldMappings['number']);
559563
}
564+
565+
public function testEncryptQueryRangeTypes(): void
566+
{
567+
$classMetadata = new ClassMetadata(RangeTypes::class);
568+
$this->driver->loadMetadataForClass(RangeTypes::class, $classMetadata);
569+
570+
self::assertEquals([
571+
'queryType' => ClientEncryption::QUERY_TYPE_RANGE,
572+
'min' => 5,
573+
'max' => 10,
574+
], $classMetadata->fieldMappings['intField']['encrypt']);
575+
576+
self::assertEquals([
577+
'queryType' => ClientEncryption::QUERY_TYPE_RANGE,
578+
'min' => 5.5,
579+
'max' => 10.5,
580+
], $classMetadata->fieldMappings['floatField']['encrypt']);
581+
582+
self::assertEquals([
583+
'queryType' => ClientEncryption::QUERY_TYPE_RANGE,
584+
'min' => new Decimal128('0.1'),
585+
'max' => new Decimal128('0.2'),
586+
], $classMetadata->fieldMappings['decimalField']['encrypt']);
587+
588+
self::assertEquals([
589+
'queryType' => ClientEncryption::QUERY_TYPE_RANGE,
590+
'min' => new UTCDateTime(new DateTimeImmutable('2000-01-01 00:00:00')),
591+
'max' => new UTCDateTime(new DateTimeImmutable('2100-01-01 00:00:00')),
592+
], $classMetadata->fieldMappings['dateField']['encrypt']);
593+
}
560594
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
3+
<doctrine-mongo-mapping xmlns="http://doctrine-project.org/schemas/odm/doctrine-mongo-mapping"
4+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
5+
xsi:schemaLocation="http://doctrine-project.org/schemas/odm/doctrine-mongo-mapping
6+
http://doctrine-project.org/schemas/odm/doctrine-mongo-mapping.xsd">
7+
8+
<document name="Documents\Encryption\RangeTypes">
9+
<id/>
10+
<field name="intField" type="int">
11+
<encrypt queryType="range" min="5" max="10"/>
12+
</field>
13+
<field name="floatField" type="float">
14+
<encrypt queryType="range" min="5.5" max="10.5"/>
15+
</field>
16+
<field name="decimalField" type="decimal128">
17+
<encrypt queryType="range" min="0.1" max="0.2"/>
18+
</field>
19+
<field name="dateField" type="date_immutable">
20+
<encrypt queryType="range" min="2000-01-01 00:00:00" max="2100-01-01 00:00:00"/>
21+
</field>
22+
<field name="intField" type="int">
23+
<encrypt queryType="range" min="5" max="10"/>
24+
</field>
25+
</document>
26+
27+
</doctrine-mongo-mapping>
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Documents\Encryption;
6+
7+
use DateTimeImmutable;
8+
use Doctrine\ODM\MongoDB\Mapping\Annotations\Encrypt;
9+
use Doctrine\ODM\MongoDB\Mapping\Annotations\EncryptQuery;
10+
use Doctrine\ODM\MongoDB\Mapping\Annotations\Field;
11+
use Doctrine\ODM\MongoDB\Mapping\Annotations\Id;
12+
use MongoDB\BSON\Decimal128;
13+
14+
/**
15+
* Test all supported types for range encrypted queries.
16+
*
17+
* @see https://www.mongodb.com/docs/manual/core/queryable-encryption/reference/supported-operations/#supported-and-unsupported-bson-types
18+
*/
19+
class RangeTypes
20+
{
21+
#[Id]
22+
public string $id;
23+
24+
#[Field]
25+
#[Encrypt(EncryptQuery::Range, min: 5, max: 10)]
26+
public int $intField;
27+
28+
#[Field]
29+
#[Encrypt(EncryptQuery::Range, min: 5.5, max: 10.5)]
30+
public float $floatField;
31+
32+
#[Field]
33+
#[Encrypt(EncryptQuery::Range, min: new Decimal128('0.1'), max: new Decimal128('0.2'))]
34+
public float $decimalField;
35+
36+
#[Field]
37+
#[Encrypt(EncryptQuery::Range, min: new DateTimeImmutable('2000-01-01 00:00:00'), max: new DateTimeImmutable('2100-01-01 00:00:00'))]
38+
public DateTimeImmutable $dateField;
39+
}

0 commit comments

Comments
 (0)