From ada5563bff7fb7390bfbcbb9c4a2863eac0f1bf0 Mon Sep 17 00:00:00 2001 From: Martin Georgiev Date: Tue, 22 Apr 2025 00:54:36 +0100 Subject: [PATCH 1/4] feat: extend support of `REGEXP_LIKE()` and `REGEXP_MATCH()` while deprecating the legacy limited flagged variations of `FlaggedRegexpLike`, `FlaggedRegexpMatch` and `FlaggedRegexpReplace` --- docs/AVAILABLE-FUNCTIONS-AND-OPERATORS.md | 9 ++--- .../Query/AST/Functions/FlaggedRegexpLike.php | 3 +- .../AST/Functions/FlaggedRegexpMatch.php | 3 +- .../AST/Functions/FlaggedRegexpReplace.php | 3 +- .../ORM/Query/AST/Functions/RegexpLike.php | 25 +++++++++++-- .../ORM/Query/AST/Functions/RegexpMatch.php | 25 +++++++++++-- .../AST/Functions/FlaggedRegexpLikeTest.php | 2 +- .../AST/Functions/FlaggedRegexpMatchTest.php | 2 +- .../Functions/FlaggedRegexpReplaceTest.php | 2 +- .../Query/AST/Functions/RegexpLikeTest.php | 37 +++++++++++++++++-- .../Query/AST/Functions/RegexpMatchTest.php | 37 +++++++++++++++++-- 11 files changed, 124 insertions(+), 24 deletions(-) diff --git a/docs/AVAILABLE-FUNCTIONS-AND-OPERATORS.md b/docs/AVAILABLE-FUNCTIONS-AND-OPERATORS.md index 535e0be5..e9ca648b 100644 --- a/docs/AVAILABLE-FUNCTIONS-AND-OPERATORS.md +++ b/docs/AVAILABLE-FUNCTIONS-AND-OPERATORS.md @@ -82,12 +82,9 @@ | least | LEAST | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Least` | | numrange | NUMRANGE | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Numrange` | | overlaps | DATE_OVERLAPS | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\DateOverlaps` | -| regexp_like (with flags) | FLAGGED_REGEXP_LIKE | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\FlaggedRegexpLike` | -| regexp_like (with no flags) | REGEXP_LIKE | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\RegexpLike` | -| regexp_match (with flags) | FLAGGED_REGEXP_MATCH | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\FlaggedRegexpMatch` | -| regexp_match (with no flags) | REGEXP_MATCH | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\RegexpMatch` | -| regexp_replace (with flags) | FLAGGED_REGEXP_REPLACE | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\FlaggedRegexpReplace` | -| regexp_replace (with no flags) | REGEXP_REPLACE | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\RegexpReplace` | +| regexp_like | REGEXP_LIKE | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\RegexpLike` | +| regexp_match | REGEXP_MATCH | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\RegexpMatch` | +| regexp_replace | REGEXP_REPLACE | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\RegexpReplace` | | row | ROW | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Row` | | row_to_json | ROW_TO_JSON | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\RowToJson` | | split_part | SPLIT_PART | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\SplitPart` | diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/FlaggedRegexpLike.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/FlaggedRegexpLike.php index b8bcba74..63853c48 100644 --- a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/FlaggedRegexpLike.php +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/FlaggedRegexpLike.php @@ -5,8 +5,9 @@ namespace MartinGeorgiev\Doctrine\ORM\Query\AST\Functions; /** - * Implementation of PostgreSQL REGEXP_LIKE() with flags. + * @deprecated This function will be dropped in v4.0. Use RegexpLike instead. * + * Implementation of PostgreSQL REGEXP_LIKE() with flags. * @see https://www.postgresql.org/docs/15/functions-matching.html#FUNCTIONS-POSIX-REGEXP * @see https://www.postgresql.org/docs/15/functions-matching.html#POSIX-EMBEDDED-OPTIONS-TABLE * @since 2.0 diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/FlaggedRegexpMatch.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/FlaggedRegexpMatch.php index 2132dd59..27c49ad7 100644 --- a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/FlaggedRegexpMatch.php +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/FlaggedRegexpMatch.php @@ -5,8 +5,9 @@ namespace MartinGeorgiev\Doctrine\ORM\Query\AST\Functions; /** - * Implementation of PostgreSQL REGEXP_MATCH() with flags. + * @deprecated This function will be dropped in v4.0. Use RegexpMatch instead. * + * Implementation of PostgreSQL REGEXP_MATCH() with flags. * @see https://www.postgresql.org/docs/15/functions-matching.html#FUNCTIONS-POSIX-REGEXP * @see https://www.postgresql.org/docs/15/functions-matching.html#POSIX-EMBEDDED-OPTIONS-TABLE * @since 2.0 diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/FlaggedRegexpReplace.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/FlaggedRegexpReplace.php index ec4f99ca..d79edc9f 100644 --- a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/FlaggedRegexpReplace.php +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/FlaggedRegexpReplace.php @@ -5,8 +5,9 @@ namespace MartinGeorgiev\Doctrine\ORM\Query\AST\Functions; /** - * Implementation of PostgreSQL REGEXP_REPLACE(). + * @deprecated This function will be dropped in v4.0. Use RegexpReplace instead. * + * Implementation of PostgreSQL REGEXP_REPLACE(). * @see https://www.postgresql.org/docs/15/functions-matching.html#FUNCTIONS-POSIX-REGEXP * @since 2.5 * diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/RegexpLike.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/RegexpLike.php index 42fa3fb4..d0e48640 100644 --- a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/RegexpLike.php +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/RegexpLike.php @@ -7,20 +7,39 @@ /** * Implementation of PostgreSQL REGEXP_LIKE(). * - * @see https://www.postgresql.org/docs/15/functions-matching.html#FUNCTIONS-POSIX-REGEXP + * Returns true if a string matches a POSIX regular expression pattern, or false if it does not. + * + * @see https://www.postgresql.org/docs/17/functions-matching.html#FUNCTIONS-POSIX-REGEXP * @since 2.0 * * @author Martin Georgiev + * + * @example Using it in DQL: "SELECT REGEXP_LIKE(e.text, 'pattern', 3, 'i') FROM Entity e" */ -class RegexpLike extends BaseRegexpFunction +class RegexpLike extends BaseVariadicFunction { + protected function getNodeMappingPattern(): array + { + return [ + 'StringPrimary,StringPrimary,ArithmeticPrimary,StringPrimary', + 'StringPrimary,StringPrimary,ArithmeticPrimary', + 'StringPrimary,StringPrimary,StringPrimary', + 'StringPrimary,StringPrimary', + ]; + } + protected function getFunctionName(): string { return 'regexp_like'; } - protected function getParameterCount(): int + protected function getMinArgumentCount(): int { return 2; } + + protected function getMaxArgumentCount(): int + { + return 4; + } } diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/RegexpMatch.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/RegexpMatch.php index 22bbe46a..813411d7 100644 --- a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/RegexpMatch.php +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/RegexpMatch.php @@ -7,20 +7,39 @@ /** * Implementation of PostgreSQL REGEXP_MATCH(). * - * @see https://www.postgresql.org/docs/15/functions-matching.html#FUNCTIONS-POSIX-REGEXP + * Returns the first substring(s) that match a POSIX regular expression pattern, or NULL if there is no match. + * + * @see https://www.postgresql.org/docs/17/functions-matching.html#FUNCTIONS-POSIX-REGEXP * @since 2.0 * * @author Martin Georgiev + * + * @example Using it in DQL: "SELECT REGEXP_MATCH(e.text, 'pattern', 3, 'i') FROM Entity e" */ -class RegexpMatch extends BaseRegexpFunction +class RegexpMatch extends BaseVariadicFunction { + protected function getNodeMappingPattern(): array + { + return [ + 'StringPrimary,StringPrimary,ArithmeticPrimary,StringPrimary', + 'StringPrimary,StringPrimary,ArithmeticPrimary', + 'StringPrimary,StringPrimary,StringPrimary', + 'StringPrimary,StringPrimary', + ]; + } + protected function getFunctionName(): string { return 'regexp_match'; } - protected function getParameterCount(): int + protected function getMinArgumentCount(): int { return 2; } + + protected function getMaxArgumentCount(): int + { + return 4; + } } diff --git a/tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/FlaggedRegexpLikeTest.php b/tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/FlaggedRegexpLikeTest.php index 9264fadc..c1d42ec6 100644 --- a/tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/FlaggedRegexpLikeTest.php +++ b/tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/FlaggedRegexpLikeTest.php @@ -12,7 +12,7 @@ class FlaggedRegexpLikeTest extends TestCase protected function getStringFunctions(): array { return [ - 'FLAGGED_REGEXP_LIKE' => FlaggedRegexpLike::class, + 'FLAGGED_REGEXP_LIKE' => FlaggedRegexpLike::class, // @phpstan-ignore-line ]; } diff --git a/tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/FlaggedRegexpMatchTest.php b/tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/FlaggedRegexpMatchTest.php index 5b099e2b..bda8ede4 100644 --- a/tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/FlaggedRegexpMatchTest.php +++ b/tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/FlaggedRegexpMatchTest.php @@ -12,7 +12,7 @@ class FlaggedRegexpMatchTest extends TestCase protected function getStringFunctions(): array { return [ - 'FLAGGED_REGEXP_MATCH' => FlaggedRegexpMatch::class, + 'FLAGGED_REGEXP_MATCH' => FlaggedRegexpMatch::class, // @phpstan-ignore-line ]; } diff --git a/tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/FlaggedRegexpReplaceTest.php b/tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/FlaggedRegexpReplaceTest.php index 76c1b10e..5e7dad3a 100644 --- a/tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/FlaggedRegexpReplaceTest.php +++ b/tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/FlaggedRegexpReplaceTest.php @@ -12,7 +12,7 @@ class FlaggedRegexpReplaceTest extends TestCase protected function getStringFunctions(): array { return [ - 'FLAGGED_REGEXP_REPLACE' => FlaggedRegexpReplace::class, + 'FLAGGED_REGEXP_REPLACE' => FlaggedRegexpReplace::class, // @phpstan-ignore-line ]; } diff --git a/tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/RegexpLikeTest.php b/tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/RegexpLikeTest.php index c525f344..4811c611 100644 --- a/tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/RegexpLikeTest.php +++ b/tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/RegexpLikeTest.php @@ -5,10 +5,17 @@ namespace Tests\MartinGeorgiev\Doctrine\ORM\Query\AST\Functions; use Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsTexts; +use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\BaseVariadicFunction; +use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Exception\InvalidArgumentForVariadicFunctionException; use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\RegexpLike; -class RegexpLikeTest extends TestCase +class RegexpLikeTest extends BaseVariadicFunctionTestCase { + protected function createFixture(): BaseVariadicFunction + { + return new RegexpLike('REGEXP_LIKE'); + } + protected function getStringFunctions(): array { return [ @@ -19,14 +26,38 @@ protected function getStringFunctions(): array protected function getExpectedSqlStatements(): array { return [ - "SELECT regexp_like(c0_.text1, 'pattern') AS sclr_0 FROM ContainsTexts c0_", + 'basic match' => "SELECT regexp_like(c0_.text1, 'pattern') AS sclr_0 FROM ContainsTexts c0_", + 'with flags' => "SELECT regexp_like(c0_.text1, 'pattern', 'i') AS sclr_0 FROM ContainsTexts c0_", + 'with start position' => "SELECT regexp_like(c0_.text1, 'pattern', 3) AS sclr_0 FROM ContainsTexts c0_", + 'with start position and flags' => "SELECT regexp_like(c0_.text1, 'pattern', 3, 'i') AS sclr_0 FROM ContainsTexts c0_", ]; } protected function getDqlStatements(): array { return [ - \sprintf("SELECT REGEXP_LIKE(e.text1, 'pattern') FROM %s e", ContainsTexts::class), + 'basic match' => \sprintf("SELECT REGEXP_LIKE(e.text1, 'pattern') FROM %s e", ContainsTexts::class), + 'with flags' => \sprintf("SELECT REGEXP_LIKE(e.text1, 'pattern', 'i') FROM %s e", ContainsTexts::class), + 'with start position' => \sprintf("SELECT REGEXP_LIKE(e.text1, 'pattern', 3) FROM %s e", ContainsTexts::class), + 'with start position and flags' => \sprintf("SELECT REGEXP_LIKE(e.text1, 'pattern', 3, 'i') FROM %s e", ContainsTexts::class), ]; } + + public function test_too_few_arguments_throws_exception(): void + { + $this->expectException(InvalidArgumentForVariadicFunctionException::class); + $this->expectExceptionMessage('regexp_like() requires at least 2 arguments'); + + $dql = \sprintf('SELECT REGEXP_LIKE(e.text1) FROM %s e', ContainsTexts::class); + $this->buildEntityManager()->createQuery($dql)->getSQL(); + } + + public function test_too_many_arguments_throws_exception(): void + { + $this->expectException(InvalidArgumentForVariadicFunctionException::class); + $this->expectExceptionMessage('regexp_like() requires between 2 and 4 arguments'); + + $dql = \sprintf("SELECT REGEXP_LIKE(e.text1, 'pattern', 3, 'i', 'extra_arg') FROM %s e", ContainsTexts::class); + $this->buildEntityManager()->createQuery($dql)->getSQL(); + } } diff --git a/tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/RegexpMatchTest.php b/tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/RegexpMatchTest.php index fe8d1e57..5b08790d 100644 --- a/tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/RegexpMatchTest.php +++ b/tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/RegexpMatchTest.php @@ -5,10 +5,17 @@ namespace Tests\MartinGeorgiev\Doctrine\ORM\Query\AST\Functions; use Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsTexts; +use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\BaseVariadicFunction; +use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Exception\InvalidArgumentForVariadicFunctionException; use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\RegexpMatch; -class RegexpMatchTest extends TestCase +class RegexpMatchTest extends BaseVariadicFunctionTestCase { + protected function createFixture(): BaseVariadicFunction + { + return new RegexpMatch('REGEXP_MATCH'); + } + protected function getStringFunctions(): array { return [ @@ -19,14 +26,38 @@ protected function getStringFunctions(): array protected function getExpectedSqlStatements(): array { return [ - "SELECT regexp_match(c0_.text1, 'pattern') AS sclr_0 FROM ContainsTexts c0_", + 'basic match' => "SELECT regexp_match(c0_.text1, 'pattern') AS sclr_0 FROM ContainsTexts c0_", + 'with flags' => "SELECT regexp_match(c0_.text1, 'pattern', 'i') AS sclr_0 FROM ContainsTexts c0_", + 'with start position' => "SELECT regexp_match(c0_.text1, 'pattern', 3) AS sclr_0 FROM ContainsTexts c0_", + 'with start position and flags' => "SELECT regexp_match(c0_.text1, 'pattern', 3, 'i') AS sclr_0 FROM ContainsTexts c0_", ]; } protected function getDqlStatements(): array { return [ - \sprintf("SELECT REGEXP_MATCH(e.text1, 'pattern') FROM %s e", ContainsTexts::class), + 'basic match' => \sprintf("SELECT REGEXP_MATCH(e.text1, 'pattern') FROM %s e", ContainsTexts::class), + 'with flags' => \sprintf("SELECT REGEXP_MATCH(e.text1, 'pattern', 'i') FROM %s e", ContainsTexts::class), + 'with start position' => \sprintf("SELECT REGEXP_MATCH(e.text1, 'pattern', 3) FROM %s e", ContainsTexts::class), + 'with start position and flags' => \sprintf("SELECT REGEXP_MATCH(e.text1, 'pattern', 3, 'i') FROM %s e", ContainsTexts::class), ]; } + + public function test_too_few_arguments_throws_exception(): void + { + $this->expectException(InvalidArgumentForVariadicFunctionException::class); + $this->expectExceptionMessage('regexp_match() requires at least 2 arguments'); + + $dql = \sprintf('SELECT REGEXP_MATCH(e.text1) FROM %s e', ContainsTexts::class); + $this->buildEntityManager()->createQuery($dql)->getSQL(); + } + + public function test_too_many_arguments_throws_exception(): void + { + $this->expectException(InvalidArgumentForVariadicFunctionException::class); + $this->expectExceptionMessage('regexp_match() requires between 2 and 4 arguments'); + + $dql = \sprintf("SELECT REGEXP_MATCH(e.text1, 'pattern', 3, 'i', 'extra_arg') FROM %s e", ContainsTexts::class); + $this->buildEntityManager()->createQuery($dql)->getSQL(); + } } From f89c7f674a06641f0e97d13258c510d488768375 Mon Sep 17 00:00:00 2001 From: Martin Georgiev Date: Tue, 22 Apr 2025 14:39:53 +0100 Subject: [PATCH 2/4] no message --- .../Query/AST/Functions/FlaggedRegexpLike.php | 2 +- .../ORM/Query/AST/Functions/RegexpLike.php | 9 +++------ .../ORM/Query/AST/Functions/RegexpMatch.php | 9 +++------ .../ORM/Query/AST/Functions/RegexpReplace.php | 17 +++++++++++------ .../AST/Functions/FlaggedRegexpLikeTest.php | 2 +- .../ORM/Query/AST/Functions/RegexpLikeTest.php | 8 ++------ .../ORM/Query/AST/Functions/RegexpMatchTest.php | 8 ++------ .../Query/AST/Functions/RegexpReplaceTest.php | 2 -- 8 files changed, 23 insertions(+), 34 deletions(-) diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/FlaggedRegexpLike.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/FlaggedRegexpLike.php index 63853c48..65b8b793 100644 --- a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/FlaggedRegexpLike.php +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/FlaggedRegexpLike.php @@ -18,7 +18,7 @@ class FlaggedRegexpLike extends BaseFunction { protected function customizeFunction(): void { - $this->setFunctionPrototype('regexp_like(%s, %s, 1, %s)'); + $this->setFunctionPrototype('regexp_like(%s, %s, %s)'); $this->addNodeMapping('StringPrimary'); $this->addNodeMapping('StringPrimary'); $this->addNodeMapping('StringPrimary'); diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/RegexpLike.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/RegexpLike.php index d0e48640..c928967c 100644 --- a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/RegexpLike.php +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/RegexpLike.php @@ -14,17 +14,14 @@ * * @author Martin Georgiev * - * @example Using it in DQL: "SELECT REGEXP_LIKE(e.text, 'pattern', 3, 'i') FROM Entity e" + * @example Using it in DQL: "SELECT REGEXP_LIKE(e.text, 'pattern', 'i') FROM Entity e" */ class RegexpLike extends BaseVariadicFunction { protected function getNodeMappingPattern(): array { return [ - 'StringPrimary,StringPrimary,ArithmeticPrimary,StringPrimary', - 'StringPrimary,StringPrimary,ArithmeticPrimary', - 'StringPrimary,StringPrimary,StringPrimary', - 'StringPrimary,StringPrimary', + 'StringPrimary', ]; } @@ -40,6 +37,6 @@ protected function getMinArgumentCount(): int protected function getMaxArgumentCount(): int { - return 4; + return 3; } } diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/RegexpMatch.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/RegexpMatch.php index 813411d7..889dc0fc 100644 --- a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/RegexpMatch.php +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/RegexpMatch.php @@ -14,17 +14,14 @@ * * @author Martin Georgiev * - * @example Using it in DQL: "SELECT REGEXP_MATCH(e.text, 'pattern', 3, 'i') FROM Entity e" + * @example Using it in DQL: "SELECT REGEXP_MATCH(e.text, 'pattern', 'i') FROM Entity e" */ class RegexpMatch extends BaseVariadicFunction { protected function getNodeMappingPattern(): array { return [ - 'StringPrimary,StringPrimary,ArithmeticPrimary,StringPrimary', - 'StringPrimary,StringPrimary,ArithmeticPrimary', - 'StringPrimary,StringPrimary,StringPrimary', - 'StringPrimary,StringPrimary', + 'StringPrimary', ]; } @@ -40,6 +37,6 @@ protected function getMinArgumentCount(): int protected function getMaxArgumentCount(): int { - return 4; + return 3; } } diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/RegexpReplace.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/RegexpReplace.php index 2a426fb2..288d406a 100644 --- a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/RegexpReplace.php +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/RegexpReplace.php @@ -13,6 +13,7 @@ * @since 2.5 * * @author Colin Doig + * @author Martin Georgiev * * @example Using it in DQL: "SELECT REGEXP_REPLACE(e.text, 'pattern', 'replacement', 3, 2, 'i') FROM Entity e" */ @@ -20,13 +21,17 @@ class RegexpReplace extends BaseVariadicFunction { protected function getNodeMappingPattern(): array { + /* + * PostgreSQL overloads the 4th argument depending on its type: + * - if the 4th arg is a string, it’s taken as flags. + * - if the 4th arg is an integer, it’s taken as start position. This can be extended with the Nth argument. + */ return [ - 'StringPrimary,StringPrimary,StringPrimary,ArithmeticPrimary,ArithmeticPrimary,StringPrimary', - 'StringPrimary,StringPrimary,StringPrimary,ArithmeticPrimary,ArithmeticPrimary', - 'StringPrimary,StringPrimary,StringPrimary,ArithmeticPrimary,StringPrimary', - 'StringPrimary,StringPrimary,StringPrimary,ArithmeticPrimary', - 'StringPrimary,StringPrimary,StringPrimary,StringPrimary', - 'StringPrimary,StringPrimary,StringPrimary', + 'StringPrimary,StringPrimary,StringPrimary,ArithmeticPrimary,ArithmeticPrimary,StringPrimary', // with start, N and flags: regexp_replace(string, pattern, replacement, 3, 2, 'i') + 'StringPrimary,StringPrimary,StringPrimary,ArithmeticPrimary,ArithmeticPrimary', // with start and N: regexp_replace(string, pattern, replacement, 3, 2) + 'StringPrimary,StringPrimary,StringPrimary,ArithmeticPrimary', // with start: regexp_replace(string, pattern, replacement, 3) + 'StringPrimary,StringPrimary,StringPrimary,StringPrimary', // with flags: regexp_replace(string, pattern, replacement, 'i') + 'StringPrimary,StringPrimary,StringPrimary', // basic replacement: regexp_replace(string, pattern, replacement) ]; } diff --git a/tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/FlaggedRegexpLikeTest.php b/tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/FlaggedRegexpLikeTest.php index c1d42ec6..0504f973 100644 --- a/tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/FlaggedRegexpLikeTest.php +++ b/tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/FlaggedRegexpLikeTest.php @@ -19,7 +19,7 @@ protected function getStringFunctions(): array protected function getExpectedSqlStatements(): array { return [ - "SELECT regexp_like(c0_.text1, 'pattern', 1, 'i') AS sclr_0 FROM ContainsTexts c0_", + "SELECT regexp_like(c0_.text1, 'pattern', 'i') AS sclr_0 FROM ContainsTexts c0_", ]; } diff --git a/tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/RegexpLikeTest.php b/tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/RegexpLikeTest.php index 4811c611..01e8b8c0 100644 --- a/tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/RegexpLikeTest.php +++ b/tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/RegexpLikeTest.php @@ -28,8 +28,6 @@ protected function getExpectedSqlStatements(): array return [ 'basic match' => "SELECT regexp_like(c0_.text1, 'pattern') AS sclr_0 FROM ContainsTexts c0_", 'with flags' => "SELECT regexp_like(c0_.text1, 'pattern', 'i') AS sclr_0 FROM ContainsTexts c0_", - 'with start position' => "SELECT regexp_like(c0_.text1, 'pattern', 3) AS sclr_0 FROM ContainsTexts c0_", - 'with start position and flags' => "SELECT regexp_like(c0_.text1, 'pattern', 3, 'i') AS sclr_0 FROM ContainsTexts c0_", ]; } @@ -38,8 +36,6 @@ protected function getDqlStatements(): array return [ 'basic match' => \sprintf("SELECT REGEXP_LIKE(e.text1, 'pattern') FROM %s e", ContainsTexts::class), 'with flags' => \sprintf("SELECT REGEXP_LIKE(e.text1, 'pattern', 'i') FROM %s e", ContainsTexts::class), - 'with start position' => \sprintf("SELECT REGEXP_LIKE(e.text1, 'pattern', 3) FROM %s e", ContainsTexts::class), - 'with start position and flags' => \sprintf("SELECT REGEXP_LIKE(e.text1, 'pattern', 3, 'i') FROM %s e", ContainsTexts::class), ]; } @@ -55,9 +51,9 @@ public function test_too_few_arguments_throws_exception(): void public function test_too_many_arguments_throws_exception(): void { $this->expectException(InvalidArgumentForVariadicFunctionException::class); - $this->expectExceptionMessage('regexp_like() requires between 2 and 4 arguments'); + $this->expectExceptionMessage('regexp_like() requires between 2 and 3 arguments'); - $dql = \sprintf("SELECT REGEXP_LIKE(e.text1, 'pattern', 3, 'i', 'extra_arg') FROM %s e", ContainsTexts::class); + $dql = \sprintf("SELECT REGEXP_LIKE(e.text1, 'pattern', 'i', 'extra_arg') FROM %s e", ContainsTexts::class); $this->buildEntityManager()->createQuery($dql)->getSQL(); } } diff --git a/tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/RegexpMatchTest.php b/tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/RegexpMatchTest.php index 5b08790d..303b9ea3 100644 --- a/tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/RegexpMatchTest.php +++ b/tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/RegexpMatchTest.php @@ -28,8 +28,6 @@ protected function getExpectedSqlStatements(): array return [ 'basic match' => "SELECT regexp_match(c0_.text1, 'pattern') AS sclr_0 FROM ContainsTexts c0_", 'with flags' => "SELECT regexp_match(c0_.text1, 'pattern', 'i') AS sclr_0 FROM ContainsTexts c0_", - 'with start position' => "SELECT regexp_match(c0_.text1, 'pattern', 3) AS sclr_0 FROM ContainsTexts c0_", - 'with start position and flags' => "SELECT regexp_match(c0_.text1, 'pattern', 3, 'i') AS sclr_0 FROM ContainsTexts c0_", ]; } @@ -38,8 +36,6 @@ protected function getDqlStatements(): array return [ 'basic match' => \sprintf("SELECT REGEXP_MATCH(e.text1, 'pattern') FROM %s e", ContainsTexts::class), 'with flags' => \sprintf("SELECT REGEXP_MATCH(e.text1, 'pattern', 'i') FROM %s e", ContainsTexts::class), - 'with start position' => \sprintf("SELECT REGEXP_MATCH(e.text1, 'pattern', 3) FROM %s e", ContainsTexts::class), - 'with start position and flags' => \sprintf("SELECT REGEXP_MATCH(e.text1, 'pattern', 3, 'i') FROM %s e", ContainsTexts::class), ]; } @@ -55,9 +51,9 @@ public function test_too_few_arguments_throws_exception(): void public function test_too_many_arguments_throws_exception(): void { $this->expectException(InvalidArgumentForVariadicFunctionException::class); - $this->expectExceptionMessage('regexp_match() requires between 2 and 4 arguments'); + $this->expectExceptionMessage('regexp_match() requires between 2 and 3 arguments'); - $dql = \sprintf("SELECT REGEXP_MATCH(e.text1, 'pattern', 3, 'i', 'extra_arg') FROM %s e", ContainsTexts::class); + $dql = \sprintf("SELECT REGEXP_MATCH(e.text1, 'pattern', 'i', 'extra_arg') FROM %s e", ContainsTexts::class); $this->buildEntityManager()->createQuery($dql)->getSQL(); } } diff --git a/tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/RegexpReplaceTest.php b/tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/RegexpReplaceTest.php index 17ea390d..eae39219 100644 --- a/tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/RegexpReplaceTest.php +++ b/tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/RegexpReplaceTest.php @@ -29,7 +29,6 @@ protected function getExpectedSqlStatements(): array 'basic replacement' => "SELECT regexp_replace(c0_.text1, 'pattern', 'replacement') AS sclr_0 FROM ContainsTexts c0_", 'with flags but no start position' => "SELECT regexp_replace(c0_.text1, 'pattern', 'replacement', 'i') AS sclr_0 FROM ContainsTexts c0_", 'with start position' => "SELECT regexp_replace(c0_.text1, 'pattern', 'replacement', 3) AS sclr_0 FROM ContainsTexts c0_", - 'with start position and flags' => "SELECT regexp_replace(c0_.text1, 'pattern', 'replacement', 3, 'i') AS sclr_0 FROM ContainsTexts c0_", 'with occurrence count but no flags' => "SELECT regexp_replace(c0_.text1, 'pattern', 'replacement', 3, 2) AS sclr_0 FROM ContainsTexts c0_", 'with occurrence count and flags' => "SELECT regexp_replace(c0_.text1, 'pattern', 'replacement', 3, 2, 'i') AS sclr_0 FROM ContainsTexts c0_", ]; @@ -41,7 +40,6 @@ protected function getDqlStatements(): array 'basic replacement' => \sprintf("SELECT REGEXP_REPLACE(e.text1, 'pattern', 'replacement') FROM %s e", ContainsTexts::class), 'with flags but no start position' => \sprintf("SELECT REGEXP_REPLACE(e.text1, 'pattern', 'replacement', 'i') FROM %s e", ContainsTexts::class), 'with start position' => \sprintf("SELECT REGEXP_REPLACE(e.text1, 'pattern', 'replacement', 3) FROM %s e", ContainsTexts::class), - 'with start position and flags' => \sprintf("SELECT REGEXP_REPLACE(e.text1, 'pattern', 'replacement', 3, 'i') FROM %s e", ContainsTexts::class), 'with occurrence count but no flags' => \sprintf("SELECT REGEXP_REPLACE(e.text1, 'pattern', 'replacement', 3, 2) FROM %s e", ContainsTexts::class), 'with occurrence count and flags' => \sprintf("SELECT REGEXP_REPLACE(e.text1, 'pattern', 'replacement', 3, 2, 'i') FROM %s e", ContainsTexts::class), ]; From 76339fcd8e6d5057792f28d4584fda8e08e03724 Mon Sep 17 00:00:00 2001 From: Martin Georgiev Date: Tue, 22 Apr 2025 19:17:10 +0100 Subject: [PATCH 3/4] no message --- .../ORM/Query/AST/Functions/BaseVariadicFunctionTestCase.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/BaseVariadicFunctionTestCase.php b/tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/BaseVariadicFunctionTestCase.php index d262b1ff..511ec76f 100644 --- a/tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/BaseVariadicFunctionTestCase.php +++ b/tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/BaseVariadicFunctionTestCase.php @@ -28,7 +28,7 @@ public function throws_an_exception_when_lexer_is_not_populated_with_a_lookahead ->willReturn(new Configuration()); $query = new Query($em); - $query->setDQL('TRUE'); + $query->setDQL('SELECT 1'); $parser = new Parser($query); $parser->getLexer()->moveNext(); From 959a22c6df1933dba8748baeec9334722346bc26 Mon Sep 17 00:00:00 2001 From: Martin Georgiev Date: Tue, 22 Apr 2025 19:22:43 +0100 Subject: [PATCH 4/4] no message --- .../AST/Functions/BaseRegexpFunction.php | 22 ------------------- 1 file changed, 22 deletions(-) delete mode 100644 src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/BaseRegexpFunction.php diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/BaseRegexpFunction.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/BaseRegexpFunction.php deleted file mode 100644 index 5676376c..00000000 --- a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/BaseRegexpFunction.php +++ /dev/null @@ -1,22 +0,0 @@ -getParameterCount() - 1); - $this->setFunctionPrototype($this->getFunctionName().'(%s'.$parameters.')'); - - for ($i = 0; $i < $this->getParameterCount(); $i++) { - $this->addNodeMapping('StringPrimary'); - } - } -}