Skip to content

Commit b732828

Browse files
updated exists and not exists method
1 parent 086052d commit b732828

File tree

4 files changed

+111
-13
lines changed

4 files changed

+111
-13
lines changed

src/Database/Adapter/Mongo.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2438,7 +2438,9 @@ protected function buildFilter(Query $query): array
24382438
} elseif ($operator === '$regex' && $query->getMethod() === Query::TYPE_NOT_ENDS_WITH) {
24392439
$filter[$attribute] = ['$not' => $this->createSafeRegex($value, '%s$')];
24402440
} elseif ($operator === '$exists') {
2441-
$filter[$attribute][$operator] = $value;
2441+
foreach ($query->getValues() as $attribute) {
2442+
$filter['$or'][] = [$attribute => [$operator => $value]];
2443+
}
24422444
} else {
24432445
$filter[$attribute][$operator] = $value;
24442446
}

src/Database/Query.php

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1188,22 +1188,22 @@ public static function vectorEuclidean(string $attribute, array $vector): self
11881188
/**
11891189
* Helper method to create Query with exists method
11901190
*
1191-
* @param string $attribute
1191+
* @param array<string> $attribute
11921192
* @return Query
11931193
*/
1194-
public static function exists(string $attribute): self
1194+
public static function exists(array $attributes): self
11951195
{
1196-
return new self(self::TYPE_EXISTS, $attribute);
1196+
return new self(self::TYPE_EXISTS, '', $attributes);
11971197
}
11981198

11991199
/**
12001200
* Helper method to create Query with notExists method
12011201
*
1202-
* @param string $attribute
1202+
* @param string|int|float|bool|array<mixed,mixed> $attribute
12031203
* @return Query
12041204
*/
1205-
public static function notExists(string $attribute): self
1205+
public static function notExists(string|int|float|bool|array $attribute): self
12061206
{
1207-
return new self(self::TYPE_NOT_EXISTS, $attribute);
1207+
return new self(self::TYPE_NOT_EXISTS, '', is_array($attribute) ? $attribute : [$attribute]);
12081208
}
12091209
}

src/Database/Validator/Query/Filter.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -312,6 +312,8 @@ public function isValid($value): bool
312312
case Query::TYPE_EQUAL:
313313
case Query::TYPE_CONTAINS:
314314
case Query::TYPE_NOT_CONTAINS:
315+
case Query::TYPE_EXISTS:
316+
case Query::TYPE_NOT_EXISTS:
315317
if ($this->isEmpty($value->getValues())) {
316318
$this->message = \ucfirst($method) . ' queries require at least one value.';
317319
return false;
@@ -358,8 +360,6 @@ public function isValid($value): bool
358360

359361
case Query::TYPE_IS_NULL:
360362
case Query::TYPE_IS_NOT_NULL:
361-
case Query::TYPE_EXISTS:
362-
case Query::TYPE_NOT_EXISTS:
363363
return $this->isValidAttributeAndValues($attribute, $value->getValues(), $method);
364364

365365
case Query::TYPE_VECTOR_DOT:

tests/e2e/Adapter/Scopes/SchemalessTests.php

Lines changed: 100 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1188,7 +1188,7 @@ public function testSchemalessExists(): void
11881188

11891189
// Test exists - should return documents where optionalField exists (even if null)
11901190
$documents = $database->find($colName, [
1191-
Query::exists('optionalField'),
1191+
Query::exists(['optionalField']),
11921192
]);
11931193

11941194
$this->assertEquals(3, count($documents)); // doc1, doc2, doc4
@@ -1205,13 +1205,68 @@ public function testSchemalessExists(): void
12051205

12061206
// Test exists with another attribute
12071207
$documents = $database->find($colName, [
1208-
Query::exists('name'),
1208+
Query::exists(['name']),
12091209
]);
12101210
$this->assertEquals(5, count($documents)); // All documents have 'name'
12111211

12121212
// Test exists with non-existent attribute
12131213
$documents = $database->find($colName, [
1214-
Query::exists('nonExistentField'),
1214+
Query::exists(['nonExistentField']),
1215+
]);
1216+
$this->assertEquals(0, count($documents));
1217+
1218+
// Multiple attributes in a single exists query (OR semantics)
1219+
$documents = $database->find($colName, [
1220+
Query::exists(['optionalField', 'name']),
1221+
]);
1222+
// All documents have "name", some also have "optionalField"
1223+
$this->assertEquals(5, count($documents));
1224+
1225+
// Multiple attributes where only one exists on some documents
1226+
$documents = $database->find($colName, [
1227+
Query::exists(['optionalField', 'nonExistentField']),
1228+
]);
1229+
// Only documents where optionalField exists should be returned
1230+
$this->assertEquals(3, count($documents)); // doc1, doc2, doc4
1231+
1232+
// Multiple attributes where none exist should return empty
1233+
$documents = $database->find($colName, [
1234+
Query::exists(['nonExistentField', 'alsoMissing']),
1235+
]);
1236+
$this->assertEquals(0, count($documents));
1237+
1238+
// Multiple attributes including one present on all docs still returns all (OR)
1239+
$documents = $database->find($colName, [
1240+
Query::exists(['name', 'nonExistentField', 'alsoMissing']),
1241+
]);
1242+
$this->assertEquals(5, count($documents));
1243+
1244+
// Multiple exists queries (AND semantics)
1245+
$documents = $database->find($colName, [
1246+
Query::exists(['optionalField']),
1247+
Query::exists(['name']),
1248+
]);
1249+
// Documents must have both attributes
1250+
$this->assertEquals(3, count($documents)); // doc1, doc2, doc4
1251+
1252+
// Nested OR with exists (optionalField OR nonExistentField) AND name
1253+
$documents = $database->find($colName, [
1254+
Query::and([
1255+
Query::or([
1256+
Query::exists(['optionalField']),
1257+
Query::exists(['nonExistentField']),
1258+
]),
1259+
Query::exists(['name']),
1260+
]),
1261+
]);
1262+
$this->assertEquals(3, count($documents)); // doc1, doc2, doc4
1263+
1264+
// Nested OR with only missing attributes should yield empty
1265+
$documents = $database->find($colName, [
1266+
Query::or([
1267+
Query::exists(['nonExistentField']),
1268+
Query::exists(['alsoMissing']),
1269+
]),
12151270
]);
12161271
$this->assertEquals(0, count($documents));
12171272

@@ -1273,13 +1328,54 @@ public function testSchemalessNotExists(): void
12731328
]);
12741329
$this->assertEquals(5, count($documents)); // All documents don't have this field
12751330

1331+
// Multiple attributes in a single notExists query (OR semantics) - both missing
1332+
$documents = $database->find($colName, [
1333+
Query::notExists(['nonExistentField', 'alsoMissing']),
1334+
]);
1335+
$this->assertEquals(5, count($documents));
1336+
1337+
// Multiple attributes (OR) where only some documents miss one of them
1338+
$documents = $database->find($colName, [
1339+
Query::notExists(['name', 'optionalField']),
1340+
]);
1341+
$this->assertEquals(2, count($documents)); // doc3, doc5
1342+
1343+
// Multiple notExists queries (AND semantics) - must miss both
1344+
$documents = $database->find($colName, [
1345+
Query::notExists(['optionalField']),
1346+
Query::notExists(['nonExistentField']),
1347+
]);
1348+
$this->assertEquals(2, count($documents)); // doc3, doc5
1349+
12761350
// Test combination of exists and notExists
12771351
$documents = $database->find($colName, [
1278-
Query::exists('name'),
1352+
Query::exists(['name']),
12791353
Query::notExists('optionalField'),
12801354
]);
12811355
$this->assertEquals(2, count($documents)); // doc3, doc5
12821356

1357+
// Nested OR/AND with notExists: (notExists optionalField OR notExists nonExistent) AND name
1358+
$documents = $database->find($colName, [
1359+
Query::and([
1360+
Query::or([
1361+
Query::notExists(['optionalField']),
1362+
Query::notExists(['nonExistentField']),
1363+
]),
1364+
Query::exists(['name']),
1365+
]),
1366+
]);
1367+
// notExists(nonExistentField) matches all docs, so OR is always true; AND with name returns all
1368+
$this->assertEquals(5, count($documents)); // all docs match due to nonExistentField
1369+
1370+
// Nested OR with notExists where all attributes exist => empty
1371+
$documents = $database->find($colName, [
1372+
Query::or([
1373+
Query::notExists(['name']),
1374+
Query::notExists(['optionalField']),
1375+
]),
1376+
]);
1377+
$this->assertEquals(2, count($documents)); // only ones missing optionalField (doc3, doc5)
1378+
12831379
$database->deleteCollection($colName);
12841380
}
12851381
}

0 commit comments

Comments
 (0)