diff --git a/generator/config/expression/convert.yaml b/generator/config/expression/convert.yaml
index 36475be..a76311e 100644
--- a/generator/config/expression/convert.yaml
+++ b/generator/config/expression/convert.yaml
@@ -33,3 +33,50 @@ arguments:
description: |
The value to return if the input is null or missing. The arguments can be any valid expression.
If unspecified, $convert returns null if the input is null or missing.
+tests:
+ -
+ name: 'Example'
+ link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/convert/#example'
+ pipeline:
+ -
+ $addFields:
+ convertedPrice:
+ $convert:
+ input: '$price'
+ to: 'decimal'
+ onError: 'Error'
+ onNull: !bson_decimal128 '0'
+ convertedQty:
+ $convert:
+ input: '$qty'
+ to: 'int'
+ onError:
+ $concat:
+ - 'Could not convert '
+ -
+ $toString: '$qty'
+ - ' to type integer.'
+ onNull: 0
+ -
+ $project:
+ totalPrice:
+ $switch:
+ branches:
+ -
+ case:
+ $eq:
+ -
+ $type: '$convertedPrice'
+ - 'string'
+ then: 'NaN'
+ -
+ case:
+ $eq:
+ -
+ $type: '$convertedQty'
+ - 'string'
+ then: 'NaN'
+ default:
+ $multiply:
+ - '$convertedPrice'
+ - '$convertedQty'
diff --git a/generator/config/expression/isNumber.yaml b/generator/config/expression/isNumber.yaml
index 4bce308..3bce99e 100644
--- a/generator/config/expression/isNumber.yaml
+++ b/generator/config/expression/isNumber.yaml
@@ -13,4 +13,63 @@ arguments:
name: expression
type:
- expression
- variadic: array
+tests:
+ -
+ name: 'Use $isNumber to Check if a Field is Numeric'
+ link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/isNumber/#use--isnumber-to-check-if-a-field-is-numeric'
+ pipeline:
+ -
+ $addFields:
+ isNumber:
+ $isNumber: '$reading'
+ hasType:
+ $type: '$reading'
+ -
+ name: 'Conditionally Modify Fields using $isNumber'
+ link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/isNumber/#conditionally-modify-fields-using--isnumber'
+ pipeline:
+ -
+ $addFields:
+ points:
+ $cond:
+ if:
+ $isNumber: '$grade'
+ then: '$grade'
+ else:
+ $switch:
+ branches:
+ -
+ case:
+ $eq:
+ - '$grade'
+ - 'A'
+ then: 4
+ -
+ case:
+ $eq:
+ - '$grade'
+ - 'B'
+ then: 3
+ -
+ case:
+ $eq:
+ - '$grade'
+ - 'C'
+ then: 2
+ -
+ case:
+ $eq:
+ - '$grade'
+ - 'D'
+ then: 1
+ -
+ case:
+ $eq:
+ - '$grade'
+ - 'F'
+ then: 0
+ -
+ $group:
+ _id: '$student_id'
+ GPA:
+ $avg: '$points'
diff --git a/generator/config/expression/toBool.yaml b/generator/config/expression/toBool.yaml
index eaf8ca2..7f771ec 100644
--- a/generator/config/expression/toBool.yaml
+++ b/generator/config/expression/toBool.yaml
@@ -12,3 +12,30 @@ arguments:
name: expression
type:
- expression
+tests:
+ -
+ name: 'Example'
+ link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/toBool/#example'
+ pipeline:
+ -
+ $addFields:
+ convertedShippedFlag:
+ $switch:
+ branches:
+ -
+ case:
+ $eq:
+ - '$shipped'
+ - 'false'
+ then: false
+ -
+ case:
+ $eq:
+ - '$shipped'
+ - ''
+ then: false
+ default:
+ $toBool: '$shipped'
+ -
+ $match:
+ convertedShippedFlag: false
diff --git a/generator/config/expression/toDecimal.yaml b/generator/config/expression/toDecimal.yaml
index 59db31a..2f35883 100644
--- a/generator/config/expression/toDecimal.yaml
+++ b/generator/config/expression/toDecimal.yaml
@@ -12,3 +12,12 @@ arguments:
name: expression
type:
- expression
+tests:
+ -
+ name: 'Example'
+ link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/toDecimal/#example'
+ pipeline:
+ -
+ $addFields:
+ convertedPrice:
+ $toDecimal: '$price'
diff --git a/generator/config/expression/toDouble.yaml b/generator/config/expression/toDouble.yaml
index 6fc87f6..f34c36e 100644
--- a/generator/config/expression/toDouble.yaml
+++ b/generator/config/expression/toDouble.yaml
@@ -12,3 +12,16 @@ arguments:
name: expression
type:
- expression
+tests:
+ -
+ name: 'Example'
+ link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/toDouble/#example'
+ pipeline:
+ -
+ $addFields:
+ degrees:
+ $toDouble:
+ $substrBytes:
+ - '$temp'
+ - 0
+ - 4
diff --git a/generator/config/expression/toInt.yaml b/generator/config/expression/toInt.yaml
index cf34364..2b02399 100644
--- a/generator/config/expression/toInt.yaml
+++ b/generator/config/expression/toInt.yaml
@@ -12,3 +12,12 @@ arguments:
name: expression
type:
- expression
+tests:
+ -
+ name: 'Example'
+ link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/toInt/#example'
+ pipeline:
+ -
+ $addFields:
+ convertedQty:
+ $toInt: '$qty'
diff --git a/generator/config/expression/toLong.yaml b/generator/config/expression/toLong.yaml
index 9687e47..3168ad9 100644
--- a/generator/config/expression/toLong.yaml
+++ b/generator/config/expression/toLong.yaml
@@ -12,3 +12,15 @@ arguments:
name: expression
type:
- expression
+tests:
+ -
+ name: 'Example'
+ link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/toLong/#example'
+ pipeline:
+ -
+ $addFields:
+ convertedQty:
+ $toLong: '$qty'
+ -
+ $sort:
+ convertedQty: -1
diff --git a/generator/config/expression/toObjectId.yaml b/generator/config/expression/toObjectId.yaml
index e78ee28..803f7ca 100644
--- a/generator/config/expression/toObjectId.yaml
+++ b/generator/config/expression/toObjectId.yaml
@@ -12,3 +12,15 @@ arguments:
name: expression
type:
- expression
+tests:
+ -
+ name: 'Example'
+ link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/toObjectId/#example'
+ pipeline:
+ -
+ $addFields:
+ convertedId:
+ $toObjectId: '$_id'
+ -
+ $sort:
+ convertedId: -1
diff --git a/generator/config/expression/type.yaml b/generator/config/expression/type.yaml
index f9aa73f..c1f63db 100644
--- a/generator/config/expression/type.yaml
+++ b/generator/config/expression/type.yaml
@@ -11,3 +11,12 @@ arguments:
name: expression
type:
- expression
+tests:
+ -
+ name: 'Example'
+ link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/type/#example'
+ pipeline:
+ -
+ $project:
+ a:
+ $type: '$a'
diff --git a/generator/js2yaml.html b/generator/js2yaml.html
index 8e11abb..89e0dd5 100644
--- a/generator/js2yaml.html
+++ b/generator/js2yaml.html
@@ -74,6 +74,18 @@
Convert JS examples into Yaml
return new TaggedValue('bson_binary', value);
}
+ function Decimal128(value) {
+ return new TaggedValue('bson_decimal128', value)
+ }
+
+ function Int32(value) {
+ return parseInt(value);
+ }
+
+ function Int64(value) {
+ return new TaggedValue('bson_int64', value)
+ }
+
function convert(jsString) {
try {
return toYaml(eval(jsString), 1);
diff --git a/src/Builder/Expression/FactoryTrait.php b/src/Builder/Expression/FactoryTrait.php
index 2c16a6a..cadcf01 100644
--- a/src/Builder/Expression/FactoryTrait.php
+++ b/src/Builder/Expression/FactoryTrait.php
@@ -970,14 +970,13 @@ public static function isArray(
* New in MongoDB 4.4.
*
* @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/isNumber/
- * @no-named-arguments
- * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass ...$expression
+ * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression
*/
public static function isNumber(
- Type|ExpressionInterface|stdClass|array|bool|float|int|null|string ...$expression,
+ Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression,
): IsNumberOperator
{
- return new IsNumberOperator(...$expression);
+ return new IsNumberOperator($expression);
}
/**
diff --git a/src/Builder/Expression/IsNumberOperator.php b/src/Builder/Expression/IsNumberOperator.php
index 2eb1e1e..8ccc977 100644
--- a/src/Builder/Expression/IsNumberOperator.php
+++ b/src/Builder/Expression/IsNumberOperator.php
@@ -12,11 +12,8 @@
use MongoDB\Builder\Type\Encode;
use MongoDB\Builder\Type\ExpressionInterface;
use MongoDB\Builder\Type\OperatorInterface;
-use MongoDB\Exception\InvalidArgumentException;
use stdClass;
-use function array_is_list;
-
/**
* Returns boolean true if the specified expression resolves to an integer, decimal, double, or long.
* Returns boolean false if the expression resolves to any other BSON type, null, or a missing field.
@@ -28,23 +25,14 @@ class IsNumberOperator implements ResolvesToBool, OperatorInterface
{
public const ENCODE = Encode::Single;
- /** @var list $expression */
- public readonly array $expression;
+ /** @var ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression */
+ public readonly Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression;
/**
- * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass ...$expression
- * @no-named-arguments
+ * @param ExpressionInterface|Type|array|bool|float|int|non-empty-string|null|stdClass $expression
*/
- public function __construct(Type|ExpressionInterface|stdClass|array|bool|float|int|null|string ...$expression)
+ public function __construct(Type|ExpressionInterface|stdClass|array|bool|float|int|null|string $expression)
{
- if (\count($expression) < 1) {
- throw new \InvalidArgumentException(\sprintf('Expected at least %d values for $expression, got %d.', 1, \count($expression)));
- }
-
- if (! array_is_list($expression)) {
- throw new InvalidArgumentException('Expected $expression arguments to be a list (array), named arguments are not supported');
- }
-
$this->expression = $expression;
}
diff --git a/tests/Builder/Expression/ConvertOperatorTest.php b/tests/Builder/Expression/ConvertOperatorTest.php
new file mode 100644
index 0000000..f284889
--- /dev/null
+++ b/tests/Builder/Expression/ConvertOperatorTest.php
@@ -0,0 +1,75 @@
+assertSamePipeline(Pipelines::ConvertExample, $pipeline);
+ }
+}
diff --git a/tests/Builder/Expression/IsNumberOperatorTest.php b/tests/Builder/Expression/IsNumberOperatorTest.php
new file mode 100644
index 0000000..2fb81a1
--- /dev/null
+++ b/tests/Builder/Expression/IsNumberOperatorTest.php
@@ -0,0 +1,96 @@
+assertSamePipeline(Pipelines::IsNumberConditionallyModifyFieldsUsingIsNumber, $pipeline);
+ }
+
+ public function testUseIsNumberToCheckIfAFieldIsNumeric(): void
+ {
+ $pipeline = new Pipeline(
+ Stage::addFields(
+ isNumber: Expression::isNumber(
+ Expression::fieldPath('reading'),
+ ),
+ hasType: Expression::type(
+ Expression::fieldPath('reading'),
+ ),
+ ),
+ );
+
+ $this->assertSamePipeline(Pipelines::IsNumberUseIsNumberToCheckIfAFieldIsNumeric, $pipeline);
+ }
+}
diff --git a/tests/Builder/Expression/Pipelines.php b/tests/Builder/Expression/Pipelines.php
index e41135c..37fe784 100644
--- a/tests/Builder/Expression/Pipelines.php
+++ b/tests/Builder/Expression/Pipelines.php
@@ -745,6 +745,86 @@ enum Pipelines: string
]
JSON;
+ /**
+ * Example
+ *
+ * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/convert/#example
+ */
+ case ConvertExample = <<<'JSON'
+ [
+ {
+ "$addFields": {
+ "convertedPrice": {
+ "$convert": {
+ "input": "$price",
+ "to": "decimal",
+ "onError": "Error",
+ "onNull": {
+ "$numberDecimal": "0"
+ }
+ }
+ },
+ "convertedQty": {
+ "$convert": {
+ "input": "$qty",
+ "to": "int",
+ "onError": {
+ "$concat": [
+ "Could not convert ",
+ {
+ "$toString": "$qty"
+ },
+ " to type integer."
+ ]
+ },
+ "onNull": {
+ "$numberInt": "0"
+ }
+ }
+ }
+ }
+ },
+ {
+ "$project": {
+ "totalPrice": {
+ "$switch": {
+ "branches": [
+ {
+ "case": {
+ "$eq": [
+ {
+ "$type": "$convertedPrice"
+ },
+ "string"
+ ]
+ },
+ "then": "NaN"
+ },
+ {
+ "case": {
+ "$eq": [
+ {
+ "$type": "$convertedQty"
+ },
+ "string"
+ ]
+ },
+ "then": "NaN"
+ }
+ ],
+ "default": {
+ "$multiply": [
+ "$convertedPrice",
+ "$convertedQty"
+ ]
+ }
+ }
+ }
+ }
+ }
+ ]
+ JSON;
+
/**
* Example
*
@@ -2254,6 +2334,117 @@ enum Pipelines: string
]
JSON;
+ /**
+ * Use $isNumber to Check if a Field is Numeric
+ *
+ * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/isNumber/#use--isnumber-to-check-if-a-field-is-numeric
+ */
+ case IsNumberUseIsNumberToCheckIfAFieldIsNumeric = <<<'JSON'
+ [
+ {
+ "$addFields": {
+ "isNumber": {
+ "$isNumber": "$reading"
+ },
+ "hasType": {
+ "$type": "$reading"
+ }
+ }
+ }
+ ]
+ JSON;
+
+ /**
+ * Conditionally Modify Fields using $isNumber
+ *
+ * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/isNumber/#conditionally-modify-fields-using--isnumber
+ */
+ case IsNumberConditionallyModifyFieldsUsingIsNumber = <<<'JSON'
+ [
+ {
+ "$addFields": {
+ "points": {
+ "$cond": {
+ "if": {
+ "$isNumber": "$grade"
+ },
+ "then": "$grade",
+ "else": {
+ "$switch": {
+ "branches": [
+ {
+ "case": {
+ "$eq": [
+ "$grade",
+ "A"
+ ]
+ },
+ "then": {
+ "$numberInt": "4"
+ }
+ },
+ {
+ "case": {
+ "$eq": [
+ "$grade",
+ "B"
+ ]
+ },
+ "then": {
+ "$numberInt": "3"
+ }
+ },
+ {
+ "case": {
+ "$eq": [
+ "$grade",
+ "C"
+ ]
+ },
+ "then": {
+ "$numberInt": "2"
+ }
+ },
+ {
+ "case": {
+ "$eq": [
+ "$grade",
+ "D"
+ ]
+ },
+ "then": {
+ "$numberInt": "1"
+ }
+ },
+ {
+ "case": {
+ "$eq": [
+ "$grade",
+ "F"
+ ]
+ },
+ "then": {
+ "$numberInt": "0"
+ }
+ }
+ ]
+ }
+ }
+ }
+ }
+ }
+ },
+ {
+ "$group": {
+ "_id": "$student_id",
+ "GPA": {
+ "$avg": "$points"
+ }
+ }
+ }
+ ]
+ JSON;
+
/**
* Example
*
@@ -4912,6 +5103,52 @@ enum Pipelines: string
]
JSON;
+ /**
+ * Example
+ *
+ * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/toBool/#example
+ */
+ case ToBoolExample = <<<'JSON'
+ [
+ {
+ "$addFields": {
+ "convertedShippedFlag": {
+ "$switch": {
+ "branches": [
+ {
+ "case": {
+ "$eq": [
+ "$shipped",
+ "false"
+ ]
+ },
+ "then": false
+ },
+ {
+ "case": {
+ "$eq": [
+ "$shipped",
+ ""
+ ]
+ },
+ "then": false
+ }
+ ],
+ "default": {
+ "$toBool": "$shipped"
+ }
+ }
+ }
+ }
+ },
+ {
+ "$match": {
+ "convertedShippedFlag": false
+ }
+ }
+ ]
+ JSON;
+
/**
* Example
*
@@ -4936,6 +5173,50 @@ enum Pipelines: string
]
JSON;
+ /**
+ * Example
+ *
+ * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/toDecimal/#example
+ */
+ case ToDecimalExample = <<<'JSON'
+ [
+ {
+ "$addFields": {
+ "convertedPrice": {
+ "$toDecimal": "$price"
+ }
+ }
+ }
+ ]
+ JSON;
+
+ /**
+ * Example
+ *
+ * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/toDouble/#example
+ */
+ case ToDoubleExample = <<<'JSON'
+ [
+ {
+ "$addFields": {
+ "degrees": {
+ "$toDouble": {
+ "$substrBytes": [
+ "$temp",
+ {
+ "$numberInt": "0"
+ },
+ {
+ "$numberInt": "4"
+ }
+ ]
+ }
+ }
+ }
+ }
+ ]
+ JSON;
+
/**
* Example
*
@@ -4960,6 +5241,47 @@ enum Pipelines: string
]
JSON;
+ /**
+ * Example
+ *
+ * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/toInt/#example
+ */
+ case ToIntExample = <<<'JSON'
+ [
+ {
+ "$addFields": {
+ "convertedQty": {
+ "$toInt": "$qty"
+ }
+ }
+ }
+ ]
+ JSON;
+
+ /**
+ * Example
+ *
+ * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/toLong/#example
+ */
+ case ToLongExample = <<<'JSON'
+ [
+ {
+ "$addFields": {
+ "convertedQty": {
+ "$toLong": "$qty"
+ }
+ }
+ },
+ {
+ "$sort": {
+ "convertedQty": {
+ "$numberInt": "-1"
+ }
+ }
+ }
+ ]
+ JSON;
+
/**
* Example
*
@@ -4980,6 +5302,30 @@ enum Pipelines: string
]
JSON;
+ /**
+ * Example
+ *
+ * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/toObjectId/#example
+ */
+ case ToObjectIdExample = <<<'JSON'
+ [
+ {
+ "$addFields": {
+ "convertedId": {
+ "$toObjectId": "$_id"
+ }
+ }
+ },
+ {
+ "$sort": {
+ "convertedId": {
+ "$numberInt": "-1"
+ }
+ }
+ }
+ ]
+ JSON;
+
/**
* Example
*
@@ -5140,6 +5486,23 @@ enum Pipelines: string
]
JSON;
+ /**
+ * Example
+ *
+ * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/type/#example
+ */
+ case TypeExample = <<<'JSON'
+ [
+ {
+ "$project": {
+ "a": {
+ "$type": "$a"
+ }
+ }
+ }
+ ]
+ JSON;
+
/**
* Remove Fields that Contain Periods
*
diff --git a/tests/Builder/Expression/ToBoolOperatorTest.php b/tests/Builder/Expression/ToBoolOperatorTest.php
new file mode 100644
index 0000000..6d6b855
--- /dev/null
+++ b/tests/Builder/Expression/ToBoolOperatorTest.php
@@ -0,0 +1,52 @@
+assertSamePipeline(Pipelines::ToBoolExample, $pipeline);
+ }
+}
diff --git a/tests/Builder/Expression/ToDecimalOperatorTest.php b/tests/Builder/Expression/ToDecimalOperatorTest.php
new file mode 100644
index 0000000..d12a727
--- /dev/null
+++ b/tests/Builder/Expression/ToDecimalOperatorTest.php
@@ -0,0 +1,29 @@
+assertSamePipeline(Pipelines::ToDecimalExample, $pipeline);
+ }
+}
diff --git a/tests/Builder/Expression/ToDoubleOperatorTest.php b/tests/Builder/Expression/ToDoubleOperatorTest.php
new file mode 100644
index 0000000..d0819e7
--- /dev/null
+++ b/tests/Builder/Expression/ToDoubleOperatorTest.php
@@ -0,0 +1,33 @@
+assertSamePipeline(Pipelines::ToDoubleExample, $pipeline);
+ }
+}
diff --git a/tests/Builder/Expression/ToIntOperatorTest.php b/tests/Builder/Expression/ToIntOperatorTest.php
new file mode 100644
index 0000000..cc88ca6
--- /dev/null
+++ b/tests/Builder/Expression/ToIntOperatorTest.php
@@ -0,0 +1,29 @@
+assertSamePipeline(Pipelines::ToIntExample, $pipeline);
+ }
+}
diff --git a/tests/Builder/Expression/ToLongOperatorTest.php b/tests/Builder/Expression/ToLongOperatorTest.php
new file mode 100644
index 0000000..c21fef6
--- /dev/null
+++ b/tests/Builder/Expression/ToLongOperatorTest.php
@@ -0,0 +1,36 @@
+assertSamePipeline(Pipelines::ToLongExample, $pipeline);
+ }
+}
diff --git a/tests/Builder/Expression/ToObjectIdOperatorTest.php b/tests/Builder/Expression/ToObjectIdOperatorTest.php
new file mode 100644
index 0000000..856c765
--- /dev/null
+++ b/tests/Builder/Expression/ToObjectIdOperatorTest.php
@@ -0,0 +1,36 @@
+assertSamePipeline(Pipelines::ToObjectIdExample, $pipeline);
+ }
+}
diff --git a/tests/Builder/Expression/TypeOperatorTest.php b/tests/Builder/Expression/TypeOperatorTest.php
new file mode 100644
index 0000000..797536c
--- /dev/null
+++ b/tests/Builder/Expression/TypeOperatorTest.php
@@ -0,0 +1,29 @@
+assertSamePipeline(Pipelines::TypeExample, $pipeline);
+ }
+}