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

PHPLIB-1348 Add tests on Custom Aggregation Expression Operators #35

Merged
merged 1 commit into from
Jan 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 41 additions & 8 deletions generator/config/accumulator/accumulator.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -64,14 +64,29 @@ tests:
avgCopies:
$accumulator:
init:
$code: 'function () { return { count: 0, sum: 0 } }'
$code: |-
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Noted that this has no impact on tests/Builder/Accumulator/Pipelines.php apart from whitespace changes. Likewise for query/where.yaml.

function() {
return { count: 0, sum: 0 }
}
accumulate:
$code: 'function (state, numCopies) { return { count: state.count + 1, sum: state.sum + numCopies } }'
$code: |-
function(state, numCopies) {
return { count: state.count + 1, sum: state.sum + numCopies }
}
accumulateArgs: [ "$copies" ],
merge:
$code: 'function (state1, state2) { return { count: state1.count + state2.count, sum: state1.sum + state2.sum } }'
$code: |-
function(state1, state2) {
return {
count: state1.count + state2.count,
sum: state1.sum + state2.sum
}
}
finalize:
$code: 'function (state) { return (state.sum / state.count) }'
$code: |-
function(state) {
return (state.sum / state.count)
}
lang: 'js'

-
Expand All @@ -85,16 +100,34 @@ tests:
restaurants:
$accumulator:
init:
$code: 'function (city, userProfileCity) { return { max: city === userProfileCity ? 3 : 1, restaurants: [] } }'
$code: |-
function(city, userProfileCity) {
return { max: city === userProfileCity ? 3 : 1, restaurants: [] }
}
initArgs:
- '$city'
- 'Bettles'
accumulate:
$code: 'function (state, restaurantName) { if (state.restaurants.length < state.max) { state.restaurants.push(restaurantName); } return state; }'
$code: |-
function(state, restaurantName) {
if (state.restaurants.length < state.max) {
state.restaurants.push(restaurantName);
}
return state;
}
accumulateArgs:
- '$name'
merge:
$code: 'function (state1, state2) { return { max: state1.max, restaurants: state1.restaurants.concat(state2.restaurants).slice(0, state1.max) } }'
$code: |-
function(state1, state2) {
return {
max: state1.max,
restaurants: state1.restaurants.concat(state2.restaurants).slice(0, state1.max)
}
}
finalize:
$code: 'function (state) { return state.restaurants }'
$code: |-
function(state) {
return state.restaurants
}
lang: 'js'
46 changes: 46 additions & 0 deletions generator/config/expression/function.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,54 @@ arguments:
- array
description: |
Arguments passed to the function body. If the body function does not take an argument, you can specify an empty array [ ].
default: []
-
name: lang
type:
- string
default: js
tests:
-
name: 'Usage Example'
link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/function/#example-1--usage-example'
pipeline:
-
$addFields:
isFound:
$function:
body:
$code: |-
function(name) {
return hex_md5(name) == "15b0a220baa16331e8d80e15367677ad"
}
args:
- '$name'
lang: 'js'
message:
$function:
body:
$code: |-
function(name, scores) {
let total = Array.sum(scores);
return `Hello ${name}. Your total score is ${total}.`
}
args:
- '$name'
- '$scores'
lang: 'js'
-
name: 'Alternative to $where'
link: 'https://www.mongodb.com/docs/manual/reference/operator/aggregation/function/#example-2--alternative-to--where'
pipeline:
-
$match:
$expr:
$function:
body:
$code: |-
function(name) {
return hex_md5(name) == "15b0a220baa16331e8d80e15367677ad";
}
args:
- '$name'
lang: 'js'
15 changes: 10 additions & 5 deletions generator/config/query/where.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,19 @@ tests:
pipeline:
-
$match:
$where: 'function() { return hex_md5(this.name) == "9b53e667f30cd329dca1ec9e6a83e994" }'
$where:
$code: |-
function() {
return hex_md5(this.name) == "9b53e667f30cd329dca1ec9e6a83e994"
}
-
$match:
$expr:
$function:
body: |-
function(name) {
return hex_md5(name) == "9b53e667f30cd329dca1ec9e6a83e994";
}
body:
$code: |-
function(name) {
return hex_md5(name) == "9b53e667f30cd329dca1ec9e6a83e994";
}
args: ['$name']
lang: 'js'
2 changes: 1 addition & 1 deletion generator/config/schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@
},
"default": {
"$comment": "The default value for the argument.",
"type": ["string", "number", "boolean"]
"type": ["array", "boolean", "number", "string"]
}
},
"required": [
Expand Down
12 changes: 12 additions & 0 deletions generator/src/OperatorClassGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace MongoDB\CodeGenerator;

use MongoDB\BSON\Javascript;
use MongoDB\Builder\Type\Encode;
use MongoDB\Builder\Type\OperatorInterface;
use MongoDB\Builder\Type\QueryObject;
Expand Down Expand Up @@ -151,6 +152,17 @@ public function createClass(GeneratorDefinition $definition, OperatorDefinition

PHP);
}

if ($type->javascript) {
$namespace->addUseFunction('is_string');
$namespace->addUse(Javascript::class);
$constuctor->addBody(<<<PHP
if (is_string(\${$argument->name})) {
\${$argument->name} = new Javascript(\${$argument->name});
}

PHP);
}
}

// Set property from constructor argument
Expand Down
2 changes: 2 additions & 0 deletions generator/src/OperatorFactoryGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,8 @@ private function addMethod(GeneratorDefinition $definition, OperatorDefinition $
} else {
if ($argument->optional) {
$parameter->setDefaultValue(new Literal('Optional::Undefined'));
} elseif ($argument->default !== null) {
$parameter->setDefaultValue($argument->default);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this the first time this logic is required? The default value in the schema was previously "string", "number", "boolean" so I would have expected some handling for this already.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was a bug. The default value was set on the operator class contructor, but not on the factory method. There is duplicated code between both generators.

}

$method->addComment('@param ' . $type->doc . ' $' . $argument->name . rtrim(' ' . $argument->description));
Expand Down
6 changes: 5 additions & 1 deletion generator/src/OperatorGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ final protected function getType(string $type): ExpressionDefinition
* Expression types can contain class names, interface, native types or "list".
* PHPDoc types are more precise than native types, so we use them systematically even if redundant.
*
* @return object{native:string,doc:string,use:list<class-string>,list:bool,query:bool}
* @return object{native:string,doc:string,use:list<class-string>,list:bool,query:bool,javascript:bool}
*/
final protected function getAcceptedTypes(ArgumentDefinition $arg): stdClass
{
Expand Down Expand Up @@ -117,6 +117,9 @@ final protected function getAcceptedTypes(ArgumentDefinition $arg): stdClass
// If the argument is a query, we need to convert it to a QueryObject
$isQuery = in_array('query', $arg->type, true);

// If the argument is code, we need to convert it to a Javascript object
$isJavascript = in_array('javascript', $arg->type, true);

// mixed can only be used as a standalone type
if (in_array('mixed', $nativeTypes, true)) {
$nativeTypes = ['mixed'];
Expand All @@ -132,6 +135,7 @@ final protected function getAcceptedTypes(ArgumentDefinition $arg): stdClass
'use' => array_unique($use),
'list' => $listCheck,
'query' => $isQuery,
'javascript' => $isJavascript,
];
}

Expand Down
17 changes: 17 additions & 0 deletions src/Builder/Accumulator/AccumulatorAccumulator.php

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions src/Builder/Expression/FactoryTrait.php

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion src/Builder/Expression/FunctionOperator.php

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions src/Builder/Query/WhereOperator.php

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading