Skip to content
This repository was archived by the owner on Feb 28, 2025. It is now read-only.

Commit ce1fbdc

Browse files
committed
PHPLIB-1363 PHPLIB-1355 Add enum for $type query and $meta expression
1 parent c0daa64 commit ce1fbdc

File tree

8 files changed

+276
-4
lines changed

8 files changed

+276
-4
lines changed

generator/config/expression/meta.yaml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,29 @@ arguments:
1111
name: keyword
1212
type:
1313
- string
14+
tests:
15+
-
16+
name: 'textScore'
17+
link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/meta/#-meta---textscore-'
18+
pipeline:
19+
-
20+
$match:
21+
$text:
22+
$search: 'cake'
23+
-
24+
$group:
25+
_id:
26+
$meta: 'textScore'
27+
count:
28+
$sum: 1
29+
-
30+
name: 'indexKey'
31+
link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/meta/#-meta---indexkey-'
32+
pipeline:
33+
-
34+
$match:
35+
type: 'apparel'
36+
-
37+
$addFields:
38+
idxKey:
39+
$meta: 'indexKey'

src/Builder/Encoder/OperatorEncoder.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
namespace MongoDB\Builder\Encoder;
66

7+
use BackedEnum;
78
use LogicException;
89
use MongoDB\Builder\Stage\GroupStage;
910
use MongoDB\Builder\Type\Encode;
@@ -65,6 +66,10 @@ public function encode(mixed $value): stdClass
6566
*/
6667
private function encodeAsArray(OperatorInterface $value): stdClass
6768
{
69+
if ($value instanceof BackedEnum) {
70+
return $this->wrap($value, [$value->value]);
71+
}
72+
6873
$result = [];
6974
/** @var mixed $val */
7075
foreach (get_object_vars($value) as $val) {
@@ -142,6 +147,10 @@ private function encodeAsDollarObject(OperatorInterface $value): stdClass
142147
*/
143148
private function encodeAsSingle(OperatorInterface $value): stdClass
144149
{
150+
if ($value instanceof BackedEnum) {
151+
return $this->wrap($value, $value->value);
152+
}
153+
145154
foreach (get_object_vars($value) as $val) {
146155
$result = $this->recursiveEncode($val);
147156

src/Builder/Meta.php

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace MongoDB\Builder;
6+
7+
use MongoDB\Builder\Type\Encode;
8+
use MongoDB\Builder\Type\ExpressionInterface;
9+
use MongoDB\Builder\Type\OperatorInterface;
10+
11+
/**
12+
* Returns the metadata associated with a document, e.g. "textScore" when performing text search.
13+
*
14+
* @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/meta/
15+
*/
16+
enum Meta: string implements OperatorInterface, ExpressionInterface
17+
{
18+
public const ENCODE = Encode::Single;
19+
20+
/**
21+
* Returns the score associated with the corresponding $text query for each
22+
* matching document. The text score signifies how well the document matched
23+
* the search term or terms.
24+
*/
25+
case TextScore = 'textScore';
26+
27+
/**
28+
* Returns an index key for the document if a non-text index is used. The
29+
* { $meta: "indexKey" } expression is for debugging purposes only, and
30+
* not for application logic, and is preferred over cursor.returnKey().
31+
*/
32+
case IndexKey = 'indexKey';
33+
34+
public function getOperator(): string
35+
{
36+
return '$meta';
37+
}
38+
}

src/Builder/Query.php

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
use MongoDB\BSON\Regex;
88
use MongoDB\BSON\Type;
99
use MongoDB\Builder\Query\RegexOperator;
10+
use MongoDB\Builder\Query\TypeOperator;
1011
use MongoDB\Builder\Type\CombinedFieldQuery;
1112
use MongoDB\Builder\Type\FieldQueryInterface;
1213
use MongoDB\Builder\Type\QueryInterface;
@@ -25,6 +26,27 @@ final class Query
2526
{
2627
use Query\FactoryTrait {
2728
regex as private generatedRegex;
29+
type as private generatedType;
30+
}
31+
32+
/**
33+
* Selects documents if a field is of the specified type.
34+
*
35+
* @see https://www.mongodb.com/docs/manual/reference/operator/query/type/
36+
*
37+
* @param int|non-empty-string|QueryType ...$type
38+
*
39+
* @no-named-arguments
40+
*/
41+
public static function type(string|int|QueryType ...$type): TypeOperator
42+
{
43+
foreach ($type as &$value) {
44+
if ($value instanceof QueryType) {
45+
$value = $value->value;
46+
}
47+
}
48+
49+
return self::generatedType(...$type);
2850
}
2951

3052
/**

src/Builder/QueryType.php

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace MongoDB\Builder;
6+
7+
use MongoDB\Builder\Query\TypeOperator;
8+
use MongoDB\Builder\Type\Encode;
9+
use MongoDB\Builder\Type\FieldQueryInterface;
10+
use MongoDB\Builder\Type\OperatorInterface;
11+
12+
/**
13+
* Shortcut for $type field query operator
14+
*
15+
* @see https://www.mongodb.com/docs/manual/reference/operator/query/type/#available-types
16+
* @see TypeOperator
17+
*/
18+
enum QueryType: string implements OperatorInterface, FieldQueryInterface
19+
{
20+
public const ENCODE = Encode::Array;
21+
22+
case Double = 'double';
23+
case String = 'string';
24+
case Object = 'object';
25+
case Array = 'array';
26+
case BinaryData = 'binData';
27+
case ObjectId = 'objectId';
28+
case Boolean = 'bool';
29+
case Date = 'date';
30+
case Null = 'null';
31+
case Regex = 'regex';
32+
case JavaScript = 'javascript';
33+
case Int = 'int';
34+
case Timestamp = 'timestamp';
35+
case Long = 'long';
36+
case Decimal128 = 'decimal';
37+
case MinKey = 'minKey';
38+
case MaxKey = 'maxKey';
39+
/**
40+
* Alias for Double, Int, Long, Decimal
41+
*/
42+
case Number = 'number';
43+
44+
public function getOperator(): string
45+
{
46+
return '$type';
47+
}
48+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace MongoDB\Tests\Builder\Expression;
6+
7+
use MongoDB\Builder\Accumulator;
8+
use MongoDB\Builder\Expression;
9+
use MongoDB\Builder\Meta;
10+
use MongoDB\Builder\Pipeline;
11+
use MongoDB\Builder\Query;
12+
use MongoDB\Builder\Stage;
13+
use MongoDB\Tests\Builder\PipelineTestCase;
14+
15+
/**
16+
* Test $meta expression
17+
*/
18+
class MetaOperatorTest extends PipelineTestCase
19+
{
20+
public function testIndexKey(): void
21+
{
22+
$pipeline = new Pipeline(
23+
Stage::match(
24+
type: 'apparel',
25+
),
26+
Stage::addFields(
27+
idxKey: Expression::meta('indexKey'),
28+
),
29+
);
30+
31+
$this->assertSamePipeline(Pipelines::MetaIndexKey, $pipeline);
32+
}
33+
34+
public function testIndexKeyWithEnum(): void
35+
{
36+
$pipeline = new Pipeline(
37+
Stage::match(
38+
type: 'apparel',
39+
),
40+
Stage::addFields(
41+
idxKey: Meta::IndexKey,
42+
),
43+
);
44+
45+
$this->assertSamePipeline(Pipelines::MetaIndexKey, $pipeline);
46+
}
47+
48+
public function testTextScore(): void
49+
{
50+
$pipeline = new Pipeline(
51+
Stage::match(
52+
Query::text('cake'),
53+
),
54+
Stage::group(
55+
_id: Expression::meta('textScore'),
56+
count: Accumulator::sum(1),
57+
),
58+
);
59+
60+
$this->assertSamePipeline(Pipelines::MetaTextScore, $pipeline);
61+
}
62+
63+
public function testTextScoreWithEnum(): void
64+
{
65+
$pipeline = new Pipeline(
66+
Stage::match(
67+
Query::text('cake'),
68+
),
69+
Stage::group(
70+
_id: Meta::TextScore,
71+
count: Accumulator::sum(1),
72+
),
73+
);
74+
75+
$this->assertSamePipeline(Pipelines::MetaTextScore, $pipeline);
76+
}
77+
}

tests/Builder/Expression/Pipelines.php

Lines changed: 51 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

tests/Builder/Query/TypeOperatorTest.php

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
use MongoDB\Builder\Pipeline;
88
use MongoDB\Builder\Query;
9+
use MongoDB\Builder\QueryType;
910
use MongoDB\Builder\Stage;
1011
use MongoDB\Tests\Builder\PipelineTestCase;
1112

@@ -32,16 +33,16 @@ public function testQueryingByDataType(): void
3233
zipCode: Query::type(2),
3334
),
3435
Stage::match(
35-
zipCode: Query::type('string'),
36+
zipCode: QueryType::String,
3637
),
3738
Stage::match(
3839
zipCode: Query::type(1),
3940
),
4041
Stage::match(
41-
zipCode: Query::type('double'),
42+
zipCode: Query::type(QueryType::Double),
4243
),
4344
Stage::match(
44-
zipCode: Query::type('number'),
45+
zipCode: QueryType::Number,
4546
),
4647
);
4748

@@ -69,7 +70,7 @@ public function testQueryingByMultipleDataType(): void
6970
zipCode: Query::type(2, 1),
7071
),
7172
Stage::match(
72-
zipCode: Query::type('string', 'double'),
73+
zipCode: Query::type(QueryType::String, 'double'),
7374
),
7475
);
7576

0 commit comments

Comments
 (0)