From eb9f2ed2ea9f06bb74429be8b0c435533816649f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=A9r=C3=B4me=20Tamarelle?= Date: Fri, 1 Aug 2025 13:24:49 +0200 Subject: [PATCH] Support regular expressions in $replaceAll search string and $split delimiter --- generator/config/expression/replaceAll.yaml | 11 +++++ generator/config/expression/split.yaml | 10 +++++ src/Builder/Expression/FactoryTrait.php | 12 +++--- src/Builder/Expression/ReplaceAllOperator.php | 9 ++-- src/Builder/Expression/SplitOperator.php | 13 +++--- tests/Builder/Expression/Pipelines.php | 43 +++++++++++++++++++ .../Expression/ReplaceAllOperatorTest.php | 16 +++++++ .../Builder/Expression/SplitOperatorTest.php | 14 ++++++ 8 files changed, 114 insertions(+), 14 deletions(-) diff --git a/generator/config/expression/replaceAll.yaml b/generator/config/expression/replaceAll.yaml index 74d479cb7..b13c58884 100644 --- a/generator/config/expression/replaceAll.yaml +++ b/generator/config/expression/replaceAll.yaml @@ -21,6 +21,7 @@ arguments: type: - resolvesToString - resolvesToNull + - resolvesToRegex description: | The string to search for within the given input. Can be any valid expression that resolves to a string or a null. If find refers to a field that is missing, $replaceAll returns null. - @@ -42,3 +43,13 @@ tests: input: '$item' find: 'blue paint' replacement: 'red paint' + - + name: 'Support regex search string' + pipeline: + - + $project: + item: + $replaceAll: + input: '123-456-7890' + find: !bson_regex '\d{3}' + replacement: 'xxx' diff --git a/generator/config/expression/split.yaml b/generator/config/expression/split.yaml index 1c6169910..98739c4ec 100644 --- a/generator/config/expression/split.yaml +++ b/generator/config/expression/split.yaml @@ -17,6 +17,7 @@ arguments: name: delimiter type: - resolvesToString + - resolvesToRegex description: | The delimiter to use when splitting the string expression. delimiter can be any valid expression as long as it resolves to a string. tests: @@ -46,3 +47,12 @@ tests: - $sort: total_qty: -1 + - + name: 'Support regex delimiter' + pipeline: + - + $project: + split: + $split: + - 'abc' + - !bson_regex 'b' diff --git a/src/Builder/Expression/FactoryTrait.php b/src/Builder/Expression/FactoryTrait.php index 9eab9328d..3948411c3 100644 --- a/src/Builder/Expression/FactoryTrait.php +++ b/src/Builder/Expression/FactoryTrait.php @@ -1521,12 +1521,12 @@ public static function regexMatch( * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/replaceAll/ * @param ResolvesToNull|ResolvesToString|null|string $input The string on which you wish to apply the find. Can be any valid expression that resolves to a string or a null. If input refers to a field that is missing, $replaceAll returns null. - * @param ResolvesToNull|ResolvesToString|null|string $find The string to search for within the given input. Can be any valid expression that resolves to a string or a null. If find refers to a field that is missing, $replaceAll returns null. + * @param Regex|ResolvesToNull|ResolvesToRegex|ResolvesToString|null|string $find The string to search for within the given input. Can be any valid expression that resolves to a string or a null. If find refers to a field that is missing, $replaceAll returns null. * @param ResolvesToNull|ResolvesToString|null|string $replacement The string to use to replace all matched instances of find in input. Can be any valid expression that resolves to a string or a null. */ public static function replaceAll( ResolvesToNull|ResolvesToString|null|string $input, - ResolvesToNull|ResolvesToString|null|string $find, + Regex|ResolvesToNull|ResolvesToRegex|ResolvesToString|null|string $find, ResolvesToNull|ResolvesToString|null|string $replacement, ): ReplaceAllOperator { return new ReplaceAllOperator($input, $find, $replacement); @@ -1767,10 +1767,12 @@ public static function sortArray( * * @see https://www.mongodb.com/docs/manual/reference/operator/aggregation/split/ * @param ResolvesToString|string $string The string to be split. string expression can be any valid expression as long as it resolves to a string. - * @param ResolvesToString|string $delimiter The delimiter to use when splitting the string expression. delimiter can be any valid expression as long as it resolves to a string. + * @param Regex|ResolvesToRegex|ResolvesToString|string $delimiter The delimiter to use when splitting the string expression. delimiter can be any valid expression as long as it resolves to a string. */ - public static function split(ResolvesToString|string $string, ResolvesToString|string $delimiter): SplitOperator - { + public static function split( + ResolvesToString|string $string, + Regex|ResolvesToRegex|ResolvesToString|string $delimiter, + ): SplitOperator { return new SplitOperator($string, $delimiter); } diff --git a/src/Builder/Expression/ReplaceAllOperator.php b/src/Builder/Expression/ReplaceAllOperator.php index 14cfb17ea..5cede16d4 100644 --- a/src/Builder/Expression/ReplaceAllOperator.php +++ b/src/Builder/Expression/ReplaceAllOperator.php @@ -8,6 +8,7 @@ namespace MongoDB\Builder\Expression; +use MongoDB\BSON\Regex; use MongoDB\Builder\Type\Encode; use MongoDB\Builder\Type\OperatorInterface; @@ -28,20 +29,20 @@ final class ReplaceAllOperator implements ResolvesToString, OperatorInterface /** @var ResolvesToNull|ResolvesToString|null|string $input The string on which you wish to apply the find. Can be any valid expression that resolves to a string or a null. If input refers to a field that is missing, $replaceAll returns null. */ public readonly ResolvesToNull|ResolvesToString|null|string $input; - /** @var ResolvesToNull|ResolvesToString|null|string $find The string to search for within the given input. Can be any valid expression that resolves to a string or a null. If find refers to a field that is missing, $replaceAll returns null. */ - public readonly ResolvesToNull|ResolvesToString|null|string $find; + /** @var Regex|ResolvesToNull|ResolvesToRegex|ResolvesToString|null|string $find The string to search for within the given input. Can be any valid expression that resolves to a string or a null. If find refers to a field that is missing, $replaceAll returns null. */ + public readonly Regex|ResolvesToNull|ResolvesToRegex|ResolvesToString|null|string $find; /** @var ResolvesToNull|ResolvesToString|null|string $replacement The string to use to replace all matched instances of find in input. Can be any valid expression that resolves to a string or a null. */ public readonly ResolvesToNull|ResolvesToString|null|string $replacement; /** * @param ResolvesToNull|ResolvesToString|null|string $input The string on which you wish to apply the find. Can be any valid expression that resolves to a string or a null. If input refers to a field that is missing, $replaceAll returns null. - * @param ResolvesToNull|ResolvesToString|null|string $find The string to search for within the given input. Can be any valid expression that resolves to a string or a null. If find refers to a field that is missing, $replaceAll returns null. + * @param Regex|ResolvesToNull|ResolvesToRegex|ResolvesToString|null|string $find The string to search for within the given input. Can be any valid expression that resolves to a string or a null. If find refers to a field that is missing, $replaceAll returns null. * @param ResolvesToNull|ResolvesToString|null|string $replacement The string to use to replace all matched instances of find in input. Can be any valid expression that resolves to a string or a null. */ public function __construct( ResolvesToNull|ResolvesToString|null|string $input, - ResolvesToNull|ResolvesToString|null|string $find, + Regex|ResolvesToNull|ResolvesToRegex|ResolvesToString|null|string $find, ResolvesToNull|ResolvesToString|null|string $replacement, ) { $this->input = $input; diff --git a/src/Builder/Expression/SplitOperator.php b/src/Builder/Expression/SplitOperator.php index 2e54ef5a5..30d306259 100644 --- a/src/Builder/Expression/SplitOperator.php +++ b/src/Builder/Expression/SplitOperator.php @@ -8,6 +8,7 @@ namespace MongoDB\Builder\Expression; +use MongoDB\BSON\Regex; use MongoDB\Builder\Type\Encode; use MongoDB\Builder\Type\OperatorInterface; @@ -26,15 +27,17 @@ final class SplitOperator implements ResolvesToArray, OperatorInterface /** @var ResolvesToString|string $string The string to be split. string expression can be any valid expression as long as it resolves to a string. */ public readonly ResolvesToString|string $string; - /** @var ResolvesToString|string $delimiter The delimiter to use when splitting the string expression. delimiter can be any valid expression as long as it resolves to a string. */ - public readonly ResolvesToString|string $delimiter; + /** @var Regex|ResolvesToRegex|ResolvesToString|string $delimiter The delimiter to use when splitting the string expression. delimiter can be any valid expression as long as it resolves to a string. */ + public readonly Regex|ResolvesToRegex|ResolvesToString|string $delimiter; /** * @param ResolvesToString|string $string The string to be split. string expression can be any valid expression as long as it resolves to a string. - * @param ResolvesToString|string $delimiter The delimiter to use when splitting the string expression. delimiter can be any valid expression as long as it resolves to a string. + * @param Regex|ResolvesToRegex|ResolvesToString|string $delimiter The delimiter to use when splitting the string expression. delimiter can be any valid expression as long as it resolves to a string. */ - public function __construct(ResolvesToString|string $string, ResolvesToString|string $delimiter) - { + public function __construct( + ResolvesToString|string $string, + Regex|ResolvesToRegex|ResolvesToString|string $delimiter, + ) { $this->string = $string; $this->delimiter = $delimiter; } diff --git a/tests/Builder/Expression/Pipelines.php b/tests/Builder/Expression/Pipelines.php index ab995bf68..e5bdd225e 100644 --- a/tests/Builder/Expression/Pipelines.php +++ b/tests/Builder/Expression/Pipelines.php @@ -4220,6 +4220,28 @@ enum Pipelines: string ] JSON; + /** Support regex search string */ + case ReplaceAllSupportRegexSearchString = <<<'JSON' + [ + { + "$project": { + "item": { + "$replaceAll": { + "input": "123-456-7890", + "find": { + "$regularExpression": { + "pattern": "\\d{3}", + "options": "" + } + }, + "replacement": "xxx" + } + } + } + } + ] + JSON; + /** * Example * @@ -5015,6 +5037,27 @@ enum Pipelines: string ] JSON; + /** Support regex delimiter */ + case SplitSupportRegexDelimiter = <<<'JSON' + [ + { + "$project": { + "split": { + "$split": [ + "abc", + { + "$regularExpression": { + "pattern": "b", + "options": "" + } + } + ] + } + } + } + ] + JSON; + /** * Example * diff --git a/tests/Builder/Expression/ReplaceAllOperatorTest.php b/tests/Builder/Expression/ReplaceAllOperatorTest.php index 1d1fa6ada..d2247daec 100644 --- a/tests/Builder/Expression/ReplaceAllOperatorTest.php +++ b/tests/Builder/Expression/ReplaceAllOperatorTest.php @@ -4,6 +4,7 @@ namespace MongoDB\Tests\Builder\Expression; +use MongoDB\BSON\Regex; use MongoDB\Builder\Expression; use MongoDB\Builder\Pipeline; use MongoDB\Builder\Stage; @@ -28,4 +29,19 @@ public function testExample(): void $this->assertSamePipeline(Pipelines::ReplaceAllExample, $pipeline); } + + public function testSupportRegexSearchString(): void + { + $pipeline = new Pipeline( + Stage::project( + item: Expression::replaceAll( + input: '123-456-7890', + find: new Regex('\d{3}'), + replacement: 'xxx', + ), + ), + ); + + $this->assertSamePipeline(Pipelines::ReplaceAllSupportRegexSearchString, $pipeline); + } } diff --git a/tests/Builder/Expression/SplitOperatorTest.php b/tests/Builder/Expression/SplitOperatorTest.php index c302f4e9d..ee997d539 100644 --- a/tests/Builder/Expression/SplitOperatorTest.php +++ b/tests/Builder/Expression/SplitOperatorTest.php @@ -50,4 +50,18 @@ public function testExample(): void $this->assertSamePipeline(Pipelines::SplitExample, $pipeline); } + + public function testSupportRegexDelimiter(): void + { + $pipeline = new Pipeline( + Stage::project( + split: Expression::split( + string: 'abc', + delimiter: new Regex('b'), + ), + ), + ); + + $this->assertSamePipeline(Pipelines::SplitSupportRegexDelimiter, $pipeline); + } }