diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/RegexpCount.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/RegexpCount.php new file mode 100644 index 00000000..0d601e49 --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/RegexpCount.php @@ -0,0 +1,44 @@ + + * + * @example Using it in DQL: "SELECT REGEXP_COUNT(e.text, '\d\d\d', 1, 'i') FROM Entity e" + */ +class RegexpCount extends BaseVariadicFunction +{ + protected function getNodeMappingPattern(): array + { + return [ + 'StringPrimary,StringPrimary,ArithmeticPrimary,StringPrimary', + 'StringPrimary,StringPrimary,ArithmeticPrimary', + 'StringPrimary,StringPrimary', + ]; + } + + protected function getFunctionName(): string + { + return 'regexp_count'; + } + + protected function getMinArgumentCount(): int + { + return 2; + } + + protected function getMaxArgumentCount(): int + { + return 4; + } +} diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/RegexpInstr.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/RegexpInstr.php new file mode 100644 index 00000000..18989a82 --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/RegexpInstr.php @@ -0,0 +1,48 @@ + + * + * @example Using it in DQL: "SELECT REGEXP_INSTR(e.text, 'c(.)(..)', 1, 1, 0, 'i', 2) FROM Entity e" + */ +class RegexpInstr extends BaseVariadicFunction +{ + protected function getNodeMappingPattern(): array + { + return [ + 'StringPrimary,StringPrimary,ArithmeticPrimary,ArithmeticPrimary,ArithmeticPrimary,StringPrimary,ArithmeticPrimary', + 'StringPrimary,StringPrimary,ArithmeticPrimary,ArithmeticPrimary,ArithmeticPrimary,StringPrimary', + 'StringPrimary,StringPrimary,ArithmeticPrimary,ArithmeticPrimary,ArithmeticPrimary', + 'StringPrimary,StringPrimary,ArithmeticPrimary,ArithmeticPrimary', + 'StringPrimary,StringPrimary,ArithmeticPrimary', + 'StringPrimary,StringPrimary', + ]; + } + + protected function getFunctionName(): string + { + return 'regexp_instr'; + } + + protected function getMinArgumentCount(): int + { + return 2; + } + + protected function getMaxArgumentCount(): int + { + return 7; + } +} diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/RegexpReplace.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/RegexpReplace.php index 5276fb6a..2a426fb2 100644 --- a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/RegexpReplace.php +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/RegexpReplace.php @@ -7,20 +7,41 @@ /** * Implementation of PostgreSQL REGEXP_REPLACE(). * - * @see https://www.postgresql.org/docs/15/functions-matching.html#FUNCTIONS-POSIX-REGEXP + * Replaces substring(s) matching a POSIX regular expression pattern with a replacement string. + * + * @see https://www.postgresql.org/docs/17/functions-matching.html#FUNCTIONS-POSIX-REGEXP * @since 2.5 * * @author Colin Doig + * + * @example Using it in DQL: "SELECT REGEXP_REPLACE(e.text, 'pattern', 'replacement', 3, 2, 'i') FROM Entity e" */ -class RegexpReplace extends BaseRegexpFunction +class RegexpReplace extends BaseVariadicFunction { + protected function getNodeMappingPattern(): array + { + 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', + ]; + } + protected function getFunctionName(): string { return 'regexp_replace'; } - protected function getParameterCount(): int + protected function getMinArgumentCount(): int { return 3; } + + protected function getMaxArgumentCount(): int + { + return 6; + } } diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/RegexpSubstr.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/RegexpSubstr.php new file mode 100644 index 00000000..72441d80 --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/RegexpSubstr.php @@ -0,0 +1,47 @@ + + * + * @example Using it in DQL: "SELECT REGEXP_SUBSTR(e.text, 'c(.)(..)', 1, 1, 'i', 2) FROM Entity e" + */ +class RegexpSubstr extends BaseVariadicFunction +{ + protected function getNodeMappingPattern(): array + { + return [ + 'StringPrimary,StringPrimary,ArithmeticPrimary,ArithmeticPrimary,StringPrimary,ArithmeticPrimary', + 'StringPrimary,StringPrimary,ArithmeticPrimary,ArithmeticPrimary,StringPrimary', + 'StringPrimary,StringPrimary,ArithmeticPrimary,ArithmeticPrimary', + 'StringPrimary,StringPrimary,ArithmeticPrimary', + 'StringPrimary,StringPrimary', + ]; + } + + protected function getFunctionName(): string + { + return 'regexp_substr'; + } + + protected function getMinArgumentCount(): int + { + return 2; + } + + protected function getMaxArgumentCount(): int + { + return 6; + } +} diff --git a/tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/RegexpCountTest.php b/tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/RegexpCountTest.php new file mode 100644 index 00000000..88b871b2 --- /dev/null +++ b/tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/RegexpCountTest.php @@ -0,0 +1,63 @@ + RegexpCount::class, + ]; + } + + protected function getExpectedSqlStatements(): array + { + return [ + 'counts digits' => "SELECT regexp_count(c0_.text1, '\\d\\d\\d') AS sclr_0 FROM ContainsTexts c0_", + 'counts words' => "SELECT regexp_count(c0_.text1, '\\w+') AS sclr_0 FROM ContainsTexts c0_", + 'with start position' => "SELECT regexp_count(c0_.text1, '\\d\\d\\d', 1) AS sclr_0 FROM ContainsTexts c0_", + 'with flags' => "SELECT regexp_count(c0_.text1, '\\d\\d\\d', 1, 'i') AS sclr_0 FROM ContainsTexts c0_", + ]; + } + + protected function getDqlStatements(): array + { + return [ + 'counts digits' => \sprintf("SELECT REGEXP_COUNT(e.text1, '\\d\\d\\d') FROM %s e", ContainsTexts::class), + 'counts words' => \sprintf("SELECT REGEXP_COUNT(e.text1, '\\w+') FROM %s e", ContainsTexts::class), + 'with start position' => \sprintf("SELECT REGEXP_COUNT(e.text1, '\\d\\d\\d', 1) FROM %s e", ContainsTexts::class), + 'with flags' => \sprintf("SELECT REGEXP_COUNT(e.text1, '\\d\\d\\d', 1, 'i') FROM %s e", ContainsTexts::class), + ]; + } + + public function test_too_few_arguments_throws_exception(): void + { + $this->expectException(InvalidArgumentForVariadicFunctionException::class); + $this->expectExceptionMessage('regexp_count() requires at least 2 arguments'); + + $dql = \sprintf('SELECT REGEXP_COUNT(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_count() requires between 2 and 4 arguments'); + + $dql = \sprintf("SELECT REGEXP_COUNT(e.text1, '\\d+', 1, 'i', 'extra_arg') FROM %s e", ContainsTexts::class); + $this->buildEntityManager()->createQuery($dql)->getSQL(); + } +} diff --git a/tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/RegexpInstrTest.php b/tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/RegexpInstrTest.php new file mode 100644 index 00000000..9961699b --- /dev/null +++ b/tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/RegexpInstrTest.php @@ -0,0 +1,69 @@ + RegexpInstr::class, + ]; + } + + protected function getExpectedSqlStatements(): array + { + return [ + 'finds position of pattern' => "SELECT regexp_instr(c0_.text1, 'c(.)(...)') AS sclr_0 FROM ContainsTexts c0_", + 'finds position of digits' => "SELECT regexp_instr(c0_.text1, '\\d+') AS sclr_0 FROM ContainsTexts c0_", + 'with start position' => "SELECT regexp_instr(c0_.text1, '\\d+', 1) AS sclr_0 FROM ContainsTexts c0_", + 'with start and occurrence' => "SELECT regexp_instr(c0_.text1, '\\d+', 1, 2) AS sclr_0 FROM ContainsTexts c0_", + 'with start, occurrence and return_option' => "SELECT regexp_instr(c0_.text1, '\\d+', 1, 2, 0) AS sclr_0 FROM ContainsTexts c0_", + 'with flags' => "SELECT regexp_instr(c0_.text1, '\\d+', 1, 2, 0, 'i') AS sclr_0 FROM ContainsTexts c0_", + 'with subexpr' => "SELECT regexp_instr(c0_.text1, '\\d+', 1, 2, 0, 'i', 2) AS sclr_0 FROM ContainsTexts c0_", + ]; + } + + protected function getDqlStatements(): array + { + return [ + 'finds position of pattern' => \sprintf("SELECT REGEXP_INSTR(e.text1, 'c(.)(...)') FROM %s e", ContainsTexts::class), + 'finds position of digits' => \sprintf("SELECT REGEXP_INSTR(e.text1, '\\d+') FROM %s e", ContainsTexts::class), + 'with start position' => \sprintf("SELECT REGEXP_INSTR(e.text1, '\\d+', 1) FROM %s e", ContainsTexts::class), + 'with start and occurrence' => \sprintf("SELECT REGEXP_INSTR(e.text1, '\\d+', 1, 2) FROM %s e", ContainsTexts::class), + 'with start, occurrence and return_option' => \sprintf("SELECT REGEXP_INSTR(e.text1, '\\d+', 1, 2, 0) FROM %s e", ContainsTexts::class), + 'with flags' => \sprintf("SELECT REGEXP_INSTR(e.text1, '\\d+', 1, 2, 0, 'i') FROM %s e", ContainsTexts::class), + 'with subexpr' => \sprintf("SELECT REGEXP_INSTR(e.text1, '\\d+', 1, 2, 0, 'i', 2) FROM %s e", ContainsTexts::class), + ]; + } + + public function test_too_few_arguments_throws_exception(): void + { + $this->expectException(InvalidArgumentForVariadicFunctionException::class); + $this->expectExceptionMessage('regexp_instr() requires at least 2 arguments'); + + $dql = \sprintf('SELECT REGEXP_INSTR(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_instr() requires between 2 and 7 arguments'); + + $dql = \sprintf("SELECT REGEXP_INSTR(e.text1, 'c(.)(..)', 1, 1, 0, 'i', 2, '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 4e8d46a5..17ea390d 100644 --- a/tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/RegexpReplaceTest.php +++ b/tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/RegexpReplaceTest.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\RegexpReplace; -class RegexpReplaceTest extends TestCase +class RegexpReplaceTest extends BaseVariadicFunctionTestCase { + protected function createFixture(): BaseVariadicFunction + { + return new RegexpReplace('REGEXP_REPLACE'); + } + protected function getStringFunctions(): array { return [ @@ -19,14 +26,42 @@ protected function getStringFunctions(): array protected function getExpectedSqlStatements(): array { return [ - "SELECT regexp_replace(c0_.text1, 'pattern', 'replacement') AS sclr_0 FROM ContainsTexts c0_", + '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_", ]; } protected function getDqlStatements(): array { return [ - \sprintf("SELECT REGEXP_REPLACE(e.text1, 'pattern', 'replacement') FROM %s e", ContainsTexts::class), + '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), ]; } + + public function test_too_few_arguments_throws_exception(): void + { + $this->expectException(InvalidArgumentForVariadicFunctionException::class); + $this->expectExceptionMessage('regexp_replace() requires at least 3 arguments'); + + $dql = \sprintf("SELECT REGEXP_REPLACE(e.text1, 'pattern') 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_replace() requires between 3 and 6 arguments'); + + $dql = \sprintf("SELECT REGEXP_REPLACE(e.text1, 'pattern', 'replacement', 3, 2, 'i', 'extra_arg') FROM %s e", ContainsTexts::class); + $this->buildEntityManager()->createQuery($dql)->getSQL(); + } } diff --git a/tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/RegexpSubstrTest.php b/tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/RegexpSubstrTest.php new file mode 100644 index 00000000..8abb2f28 --- /dev/null +++ b/tests/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/RegexpSubstrTest.php @@ -0,0 +1,65 @@ + RegexpSubstr::class, + ]; + } + + protected function getExpectedSqlStatements(): array + { + return [ + 'extracts pattern' => "SELECT regexp_substr(c0_.text1, 'c(.)(...)') AS sclr_0 FROM ContainsTexts c0_", + 'extracts digits' => "SELECT regexp_substr(c0_.text1, '\\d+') AS sclr_0 FROM ContainsTexts c0_", + 'extracts digits with start and N parameters' => "SELECT regexp_substr(c0_.text1, '\\d+', 1, 4) AS sclr_0 FROM ContainsTexts c0_", + 'extracts digits with start and N parameters and flags' => "SELECT regexp_substr(c0_.text1, '\\d+', 1, 4, 'i') AS sclr_0 FROM ContainsTexts c0_", + 'extracts digits with start, N parameters, flags and subexpr parameter' => "SELECT regexp_substr(c0_.text1, '\\d+', 1, 4, 'i', 3) AS sclr_0 FROM ContainsTexts c0_", + ]; + } + + protected function getDqlStatements(): array + { + return [ + 'extracts pattern' => \sprintf("SELECT REGEXP_SUBSTR(e.text1, 'c(.)(...)') FROM %s e", ContainsTexts::class), + 'extracts digits' => \sprintf("SELECT REGEXP_SUBSTR(e.text1, '\\d+') FROM %s e", ContainsTexts::class), + 'extracts digits with start and N parameters' => \sprintf("SELECT REGEXP_SUBSTR(e.text1, '\\d+', 1, 4) FROM %s e", ContainsTexts::class), + 'extracts digits with start and N parameters and flags' => \sprintf("SELECT REGEXP_SUBSTR(e.text1, '\\d+', 1, 4, 'i') FROM %s e", ContainsTexts::class), + 'extracts digits with start, N parameters, flags and subexpr parameter' => \sprintf("SELECT REGEXP_SUBSTR(e.text1, '\\d+', 1, 4, 'i', 3) FROM %s e", ContainsTexts::class), + ]; + } + + public function test_too_few_arguments_throws_exception(): void + { + $this->expectException(InvalidArgumentForVariadicFunctionException::class); + $this->expectExceptionMessage('regexp_substr() requires at least 2 arguments'); + + $dql = \sprintf('SELECT REGEXP_SUBSTR(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_substr() requires between 2 and 6 arguments'); + + $dql = \sprintf("SELECT REGEXP_SUBSTR(e.text1, 'c(.)(..)', 1, 1, 'i', 1, 'extra_arg') FROM %s e", ContainsTexts::class); + $this->buildEntityManager()->createQuery($dql)->getSQL(); + } +}