Skip to content

Commit bc60323

Browse files
authored
PHPLIB-831: Allow $$exists within $$unsetOrMatches (mongodb#909)
1 parent 3370bfc commit bc60323

File tree

2 files changed

+35
-0
lines changed

2 files changed

+35
-0
lines changed

tests/UnifiedSpecTests/Constraint/Matches.php

+15
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
use function is_float;
2525
use function is_int;
2626
use function is_object;
27+
use function ltrim;
2728
use function PHPUnit\Framework\assertIsBool;
2829
use function PHPUnit\Framework\assertIsString;
2930
use function PHPUnit\Framework\assertMatchesRegularExpression;
@@ -37,6 +38,7 @@
3738
use function range;
3839
use function sprintf;
3940
use function strpos;
41+
use function strrchr;
4042

4143
use const PHP_INT_SIZE;
4244

@@ -240,6 +242,19 @@ private function assertMatchesOperator(BSONDocument $operator, $actual, string $
240242
{
241243
$name = self::getOperatorName($operator);
242244

245+
if ($name === '$$exists') {
246+
assertIsBool($operator['$$exists'], '$$exists requires bool');
247+
248+
/* If we get to this point, the field itself must already exist so
249+
* we need only fail if $$exists is false. */
250+
if ($operator['$$exists'] === false) {
251+
$key = ltrim(strrchr($keyPath, '.'), '.');
252+
self::failAt(sprintf('$actual has unexpected key "%s"', $key), $keyPath);
253+
}
254+
255+
return;
256+
}
257+
243258
if ($name === '$$type') {
244259
assertThat(
245260
$operator['$$type'],

tests/UnifiedSpecTests/Constraint/MatchesTest.php

+20
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,26 @@ public function testOperatorUnsetOrMatches(): void
119119
$this->assertResult(false, $c, ['x' => ['y' => 1, 'z' => 2]], 'value does not match (embedded)');
120120
}
121121

122+
public function testOperatorUnsetOrMatchesWithNestedOperator(): void
123+
{
124+
// Nested $$unsetOrMatches is redundant, but should behave the same as if it was omitted
125+
$c = new Matches(['x' => ['$$unsetOrMatches' => ['$$unsetOrMatches' => ['y' => 1]]]]);
126+
$this->assertResult(true, $c, new stdClass(), 'missing value is considered unset (embedded)');
127+
$this->assertResult(false, $c, ['x' => null], 'null value is not considered unset (embedded)');
128+
$this->assertResult(true, $c, ['x' => ['y' => 1]], 'value matches (embedded)');
129+
$this->assertResult(false, $c, ['x' => ['y' => 1, 'z' => 2]], 'value does not match (embedded)');
130+
131+
$c = new Matches(['x' => ['$$unsetOrMatches' => ['$$exists' => true]]]);
132+
$this->assertResult(true, $c, new stdClass(), 'missing value is considered unset (embedded)');
133+
$this->assertResult(true, $c, ['x' => null], 'null value is not considered unset (embedded)');
134+
$this->assertResult(true, $c, ['x' => ['y' => 1]], 'non-null value is not considered unset (embedded)');
135+
136+
$c = new Matches(['x' => ['$$unsetOrMatches' => ['$$exists' => false]]]);
137+
$this->assertResult(true, $c, new stdClass(), 'missing value is considered unset (embedded)');
138+
$this->assertResult(false, $c, ['x' => null], 'null value is not considered unset (embedded)');
139+
$this->assertResult(false, $c, ['x' => ['y' => 1]], 'non-null value is not considered unset (embedded)');
140+
}
141+
122142
public function testOperatorSessionLsid(): void
123143
{
124144
$session = $this->manager->startSession();

0 commit comments

Comments
 (0)