From d4a019512d1593adc3474f61e4886233eb15f8b0 Mon Sep 17 00:00:00 2001 From: Martin Georgiev Date: Sat, 6 Sep 2025 01:28:34 +0300 Subject: [PATCH 1/4] feat: add support for functions for the `LTREE` data type --- docs/AVAILABLE-FUNCTIONS-AND-OPERATORS.md | 18 ++ docs/LTREE-TYPE.md | 160 ++++++++++++++++++ .../Doctrine/Entity/ContainsLtrees.php | 20 +++ .../ORM/Query/AST/Functions/Ltree/Index.php | 44 +++++ .../ORM/Query/AST/Functions/Ltree/Lca.php | 44 +++++ .../Query/AST/Functions/Ltree/Ltree2text.php | 29 ++++ .../ORM/Query/AST/Functions/Ltree/Nlevel.php | 29 ++++ .../Query/AST/Functions/Ltree/Subltree.php | 31 ++++ .../ORM/Query/AST/Functions/Ltree/Subpath.php | 45 +++++ .../Query/AST/Functions/Ltree/Text2ltree.php | 29 ++++ .../Query/AST/Functions/Ltree/IndexTest.php | 50 ++++++ .../ORM/Query/AST/Functions/Ltree/LcaTest.php | 58 +++++++ .../AST/Functions/Ltree/Ltree2textTest.php | 42 +++++ .../AST/Functions/Ltree/LtreeTestCase.php | 48 ++++++ .../Query/AST/Functions/Ltree/NlevelTest.php | 42 +++++ .../AST/Functions/Ltree/SubltreeTest.php | 34 ++++ .../Query/AST/Functions/Ltree/SubpathTest.php | 50 ++++++ .../AST/Functions/Ltree/Text2ltreeTest.php | 42 +++++ .../Query/AST/Functions/Ltree/IndexTest.php | 35 ++++ .../ORM/Query/AST/Functions/Ltree/LcaTest.php | 35 ++++ .../AST/Functions/Ltree/Ltree2textTest.php | 33 ++++ .../Query/AST/Functions/Ltree/NlevelTest.php | 33 ++++ .../AST/Functions/Ltree/SubltreeTest.php | 33 ++++ .../Query/AST/Functions/Ltree/SubpathTest.php | 35 ++++ .../AST/Functions/Ltree/Text2ltreeTest.php | 33 ++++ 25 files changed, 1052 insertions(+) create mode 100644 fixtures/MartinGeorgiev/Doctrine/Entity/ContainsLtrees.php create mode 100644 src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/Index.php create mode 100644 src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/Lca.php create mode 100644 src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/Ltree2text.php create mode 100644 src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/Nlevel.php create mode 100644 src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/Subltree.php create mode 100644 src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/Subpath.php create mode 100644 src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/Text2ltree.php create mode 100644 tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/IndexTest.php create mode 100644 tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/LcaTest.php create mode 100644 tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/Ltree2textTest.php create mode 100644 tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/LtreeTestCase.php create mode 100644 tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/NlevelTest.php create mode 100644 tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/SubltreeTest.php create mode 100644 tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/SubpathTest.php create mode 100644 tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/Text2ltreeTest.php create mode 100644 tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/IndexTest.php create mode 100644 tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/LcaTest.php create mode 100644 tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/Ltree2textTest.php create mode 100644 tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/NlevelTest.php create mode 100644 tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/SubltreeTest.php create mode 100644 tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/SubpathTest.php create mode 100644 tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/Text2ltreeTest.php diff --git a/docs/AVAILABLE-FUNCTIONS-AND-OPERATORS.md b/docs/AVAILABLE-FUNCTIONS-AND-OPERATORS.md index f0cdb2f2..e2aa387d 100644 --- a/docs/AVAILABLE-FUNCTIONS-AND-OPERATORS.md +++ b/docs/AVAILABLE-FUNCTIONS-AND-OPERATORS.md @@ -49,6 +49,11 @@ Complete documentation for mathematical operations and utility functions. - **[Mathematical and Utility Functions](MATHEMATICAL-FUNCTIONS.md)** - Includes: Mathematical functions, type conversion functions, formatting functions, utility functions +### **🌳 Ltree Functions** +Complete documentation for PostgreSQL ltree (label tree) operations and hierarchical data processing. +- **[Ltree Functions](LTREE-TYPE.md)** +- Includes: Path manipulation functions, ancestor/descendant operations, type conversion functions + ## 🚀 Quick Reference ### Most Commonly Used Functions @@ -83,6 +88,13 @@ Complete documentation for mathematical operations and utility functions. - `ROUND` - Round numeric values - `RANDOM` - Generate random numbers +**Ltree Operations:** ([Complete documentation](LTREE-TYPE.md)) +- `SUBLTREE` - Extract subpath from ltree +- `SUBPATH` - Extract subpath with offset and length +- `NLEVEL` - Get number of labels in path +- `INDEX` - Find position of ltree in another ltree +- `LCA` - Find longest common ancestor + ## 📋 Summary of Available Function Categories ### **Array & JSON Functions** @@ -111,6 +123,11 @@ Complete documentation for mathematical operations and utility functions. - **Aggregation**: Array and JSON aggregation functions - **Utility Functions**: Random numbers, rounding, type casting +### **Ltree Functions** +- **Path Operations**: Extract subpaths, manipulate hierarchical paths +- **Ancestor Operations**: Find common ancestors, calculate path levels +- **Type Conversion**: Convert between ltree and text types + ### **Operators** - **Array Operators**: Contains, overlaps, element testing - **Spatial Operators**: Bounding box and distance operations @@ -125,6 +142,7 @@ Complete documentation for mathematical operations and utility functions. 4. **JSON functions** support both JSON and JSONB data types → [Array and JSON Functions](ARRAY-AND-JSON-FUNCTIONS.md) 5. **Range functions** provide efficient storage and querying for value ranges → [Range Types](RANGE-TYPES.md) 6. **Mathematical functions** work with numeric types and return appropriate precision → [Mathematical Functions](MATHEMATICAL-FUNCTIONS.md) +7. **Ltree functions** provide efficient hierarchical data operations and path manipulation → [Ltree Functions](LTREE-TYPE.md) --- diff --git a/docs/LTREE-TYPE.md b/docs/LTREE-TYPE.md index 756ed8aa..48ae76b6 100644 --- a/docs/LTREE-TYPE.md +++ b/docs/LTREE-TYPE.md @@ -219,3 +219,163 @@ final readonly class MyEntityOnFlushListener } } ``` + +## Ltree Functions + +This library provides DQL functions for all PostgreSQL ltree operations. These functions allow you to work with ltree data directly in your Doctrine queries. + +### Path Manipulation Functions + +#### `SUBLTREE(ltree, start, end)` +Extracts a subpath from an ltree from position `start` to position `end-1` (counting from 0). + +```php +// DQL +$dql = "SELECT SUBLTREE(e.path, 1, 2) FROM Entity e"; +// SQL: subltree(e.path, 1, 2) +// Example: subltree('Top.Child1.Child2', 1, 2) → 'Child1' +``` + +#### `SUBPATH(ltree, offset, len)` +Extracts a subpath starting at position `offset` with length `len`. Supports negative values. + +```php +// DQL +$dql = "SELECT SUBPATH(e.path, 0, 2) FROM Entity e"; +// SQL: subpath(e.path, 0, 2) +// Example: subpath('Top.Child1.Child2', 0, 2) → 'Top.Child1' + +// With negative offset +$dql = "SELECT SUBPATH(e.path, -2) FROM Entity e"; +// SQL: subpath(e.path, -2) +// Example: subpath('Top.Child1.Child2', -2) → 'Child1.Child2' +``` + +#### `SUBPATH(ltree, offset)` +Extracts a subpath starting at position `offset` extending to the end of the path. + +```php +// DQL +$dql = "SELECT SUBPATH(e.path, 1) FROM Entity e"; +// SQL: subpath(e.path, 1) +// Example: subpath('Top.Child1.Child2', 1) → 'Child1.Child2' +``` + +### Path Information Functions + +#### `NLEVEL(ltree)` +Returns the number of labels in the path. + +```php +// DQL +$dql = "SELECT NLEVEL(e.path) FROM Entity e"; +// SQL: nlevel(e.path) +// Example: nlevel('Top.Child1.Child2') → 3 +``` + +#### `INDEX(a, b)` +Returns the position of the first occurrence of `b` in `a`, or -1 if not found. + +```php +// DQL +$dql = "SELECT INDEX(e.path, 'Child1') FROM Entity e"; +// SQL: index(e.path, 'Child1') +// Example: index('Top.Child1.Child2', 'Child1') → 1 +``` + +#### `INDEX(a, b, offset)` +Returns the position of the first occurrence of `b` in `a` starting at position `offset`. + +```php +// DQL +$dql = "SELECT INDEX(e.path, 'Child1', 1) FROM Entity e"; +// SQL: index(e.path, 'Child1', 1) +// Example: index('Top.Child1.Child2', 'Child1', 1) → 1 +``` + +### Ancestor Functions + +#### `LCA(ltree1, ltree2, ...)` +Computes the longest common ancestor of multiple paths (up to 8 arguments supported). + +```php +// DQL +$dql = "SELECT LCA(e.path1, e.path2) FROM Entity e"; +// SQL: lca(e.path1, e.path2) +// Example: lca('Top.Child1.Child2', 'Top.Child1') → 'Top' + +// With multiple paths +$dql = "SELECT LCA(e.path1, e.path2, e.path3) FROM Entity e"; +// SQL: lca(e.path1, e.path2, e.path3) +// Example: lca('Top.Child1.Child2', 'Top.Child1', 'Top.Child2.Child3') → 'Top' +``` + +### Type Conversion Functions + +#### `TEXT2LTREE(text)` +Casts text to ltree. + +```php +// DQL +$dql = "SELECT TEXT2LTREE('Top.Child1.Child2') FROM Entity e"; +// SQL: text2ltree('Top.Child1.Child2') +// Example: text2ltree('Top.Child1.Child2') → 'Top.Child1.Child2'::ltree +``` + +#### `LTREE2TEXT(ltree)` +Casts ltree to text. + +```php +// DQL +$dql = "SELECT LTREE2TEXT(e.path) FROM Entity e"; +// SQL: ltree2text(e.path) +// Example: ltree2text('Top.Child1.Child2'::ltree) → 'Top.Child1.Child2' +``` + +### Usage Examples + +#### Finding Ancestors and Descendants + +```php +// Find all descendants of a specific path +$dql = "SELECT e FROM Entity e WHERE e.path <@ TEXT2LTREE('Top.Child1')"; + +// Find all ancestors of a specific path +$dql = "SELECT e FROM Entity e WHERE TEXT2LTREE('Top.Child1') <@ e.path"; + +// Find the longest common ancestor of multiple entities +$dql = "SELECT LCA(e1.path, e2.path) FROM Entity e1, Entity e2 WHERE e1.id = 1 AND e2.id = 2"; +``` + +#### Path Analysis + +```php +// Get the depth of a path +$dql = "SELECT NLEVEL(e.path) FROM Entity e"; + +// Extract the parent path (everything except the last label) +$dql = "SELECT SUBPATH(e.path, 0, NLEVEL(e.path) - 1) FROM Entity e"; + +// Extract the root label +$dql = "SELECT SUBPATH(e.path, 0, 1) FROM Entity e"; +``` + +#### Path Manipulation + +```php +// Find entities at a specific level +$dql = "SELECT e FROM Entity e WHERE NLEVEL(e.path) = 2"; + +// Find entities with a specific parent +$dql = "SELECT e FROM Entity e WHERE SUBPATH(e.path, 0, NLEVEL(e.path) - 1) = 'Top.Child1'"; + +// Find entities that contain a specific label +$dql = "SELECT e FROM Entity e WHERE INDEX(e.path, 'Child1') >= 0"; +``` + +### Performance Considerations + +- Use GiST or GIN indexes on ltree columns for optimal performance +- The `@>` and `<@` operators work automatically with ltree types +- Consider using `SUBPATH` with negative offsets for efficient parent path extraction +- `LCA` function is efficient for finding common ancestors in hierarchical data diff --git a/fixtures/MartinGeorgiev/Doctrine/Entity/ContainsLtrees.php b/fixtures/MartinGeorgiev/Doctrine/Entity/ContainsLtrees.php new file mode 100644 index 00000000..b6238993 --- /dev/null +++ b/fixtures/MartinGeorgiev/Doctrine/Entity/ContainsLtrees.php @@ -0,0 +1,20 @@ + + * + * @example Using it in DQL: "SELECT INDEX(e.path, 'Child1') FROM Entity e" + * Returns integer, position of first occurrence or -1 if not found. + */ +class Index extends BaseVariadicFunction +{ + protected function getNodeMappingPattern(): array + { + return ['StringPrimary,StringPrimary,SimpleArithmeticExpression']; + } + + protected function getFunctionName(): string + { + return 'index'; + } + + protected function getMinArgumentCount(): int + { + return 2; + } + + protected function getMaxArgumentCount(): int + { + return 3; + } +} diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/Lca.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/Lca.php new file mode 100644 index 00000000..e965b557 --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/Lca.php @@ -0,0 +1,44 @@ + + * + * @example Using it in DQL: "SELECT LCA(e.path1, e.path2) FROM Entity e" + * Returns ltree, longest common ancestor of paths. + */ +class Lca extends BaseVariadicFunction +{ + protected function getNodeMappingPattern(): array + { + return ['StringPrimary']; + } + + protected function getFunctionName(): string + { + return 'lca'; + } + + protected function getMinArgumentCount(): int + { + return 2; + } + + protected function getMaxArgumentCount(): int + { + return 8; + } +} diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/Ltree2text.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/Ltree2text.php new file mode 100644 index 00000000..7dd52b2f --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/Ltree2text.php @@ -0,0 +1,29 @@ + + * + * @example Using it in DQL: "SELECT LTREE2TEXT(e.path) FROM Entity e" + * Returns text, converted from ltree. + */ +class Ltree2text extends BaseFunction +{ + protected function customizeFunction(): void + { + $this->setFunctionPrototype('ltree2text(%s)'); + $this->addNodeMapping('StringPrimary'); + } +} diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/Nlevel.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/Nlevel.php new file mode 100644 index 00000000..37b04e75 --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/Nlevel.php @@ -0,0 +1,29 @@ + + * + * @example Using it in DQL: "SELECT NLEVEL(e.path) FROM Entity e" + * Returns integer, number of labels in path. + */ +class Nlevel extends BaseFunction +{ + protected function customizeFunction(): void + { + $this->setFunctionPrototype('nlevel(%s)'); + $this->addNodeMapping('StringPrimary'); + } +} diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/Subltree.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/Subltree.php new file mode 100644 index 00000000..7bb9a8c7 --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/Subltree.php @@ -0,0 +1,31 @@ + + * + * @example Using it in DQL: "SELECT SUBLTREE(e.path, 1, 2) FROM Entity e" + * Returns ltree, subpath from position start to position end-1. + */ +class Subltree extends BaseFunction +{ + protected function customizeFunction(): void + { + $this->setFunctionPrototype('subltree(%s, %s, %s)'); + $this->addNodeMapping('StringPrimary'); + $this->addNodeMapping('SimpleArithmeticExpression'); + $this->addNodeMapping('SimpleArithmeticExpression'); + } +} diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/Subpath.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/Subpath.php new file mode 100644 index 00000000..d0e91e86 --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/Subpath.php @@ -0,0 +1,45 @@ + + * + * @example Using it in DQL: "SELECT SUBPATH(e.path, 0, 2) FROM Entity e" + * Returns ltree, subpath starting at position offset, with length len. + */ +class Subpath extends BaseVariadicFunction +{ + protected function getNodeMappingPattern(): array + { + return ['StringPrimary,SimpleArithmeticExpression,SimpleArithmeticExpression']; + } + + protected function getFunctionName(): string + { + return 'subpath'; + } + + protected function getMinArgumentCount(): int + { + return 2; + } + + protected function getMaxArgumentCount(): int + { + return 3; + } +} diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/Text2ltree.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/Text2ltree.php new file mode 100644 index 00000000..a5c0fc1f --- /dev/null +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/Text2ltree.php @@ -0,0 +1,29 @@ + + * + * @example Using it in DQL: "SELECT TEXT2LTREE('Top.Child1.Child2') FROM Entity e" + * Returns ltree, converted from text. + */ +class Text2ltree extends BaseFunction +{ + protected function customizeFunction(): void + { + $this->setFunctionPrototype('text2ltree(%s)'); + $this->addNodeMapping('StringPrimary'); + } +} diff --git a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/IndexTest.php b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/IndexTest.php new file mode 100644 index 00000000..c513ceec --- /dev/null +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/IndexTest.php @@ -0,0 +1,50 @@ + Index::class, + ]; + } + + #[Test] + public function can_find_position_of_ltree_in_another_ltree(): void + { + $dql = 'SELECT INDEX(l.ltree1, l.ltree2) as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsLtrees l WHERE l.id = 1'; + $result = $this->executeDqlQuery($dql); + $this->assertSame(0, $result[0]['result']); + } + + #[Test] + public function returns_negative_one_when_not_found(): void + { + $dql = 'SELECT INDEX(l.ltree2, l.ltree3) as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsLtrees l WHERE l.id = 1'; + $result = $this->executeDqlQuery($dql); + $this->assertSame(-1, $result[0]['result']); + } + + #[Test] + public function finds_position_with_offset(): void + { + $dql = "SELECT INDEX(l.ltree1, 'Child1', 1) as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsLtrees l WHERE l.id = 1"; + $result = $this->executeDqlQuery($dql); + $this->assertSame(1, $result[0]['result']); + } + + #[Test] + public function finds_position_with_negative_offset(): void + { + $dql = "SELECT INDEX(l.ltree1, 'Child1', -2) as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsLtrees l WHERE l.id = 1"; + $result = $this->executeDqlQuery($dql); + $this->assertSame(1, $result[0]['result']); + } +} diff --git a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/LcaTest.php b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/LcaTest.php new file mode 100644 index 00000000..bcf1ddcf --- /dev/null +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/LcaTest.php @@ -0,0 +1,58 @@ + Lca::class, + ]; + } + + #[Test] + public function computes_longest_common_ancestor_of_two_paths(): void + { + $dql = 'SELECT LCA(l.ltree1, l.ltree2) as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsLtrees l WHERE l.id = 1'; + $result = $this->executeDqlQuery($dql); + $this->assertSame('Top', $result[0]['result']); + } + + #[Test] + public function computes_longest_common_ancestor_of_three_paths(): void + { + $dql = 'SELECT LCA(l.ltree1, l.ltree2, l.ltree3) as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsLtrees l WHERE l.id = 1'; + $result = $this->executeDqlQuery($dql); + $this->assertSame('Top', $result[0]['result']); + } + + #[Test] + public function computes_longest_common_ancestor_with_different_paths(): void + { + $dql = 'SELECT LCA(l.ltree1, l.ltree2) as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsLtrees l WHERE l.id = 2'; + $result = $this->executeDqlQuery($dql); + $this->assertSame('A', $result[0]['result']); + } + + #[Test] + public function computes_longest_common_ancestor_with_single_node(): void + { + $dql = 'SELECT LCA(l.ltree1, l.ltree2) as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsLtrees l WHERE l.id = 3'; + $result = $this->executeDqlQuery($dql); + $this->assertSame('', $result[0]['result']); + } + + #[Test] + public function computes_longest_common_ancestor_with_string_literals(): void + { + $dql = "SELECT LCA('Top.Child1.Child2', 'Top.Child1', 'Top.Child2.Child3') as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsLtrees l WHERE l.id = 1"; + $result = $this->executeDqlQuery($dql); + $this->assertSame('Top', $result[0]['result']); + } +} diff --git a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/Ltree2textTest.php b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/Ltree2textTest.php new file mode 100644 index 00000000..c66fc8eb --- /dev/null +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/Ltree2textTest.php @@ -0,0 +1,42 @@ + Ltree2text::class, + ]; + } + + #[Test] + public function casts_ltree_to_text(): void + { + $dql = 'SELECT LTREE2TEXT(l.ltree1) as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsLtrees l WHERE l.id = 1'; + $result = $this->executeDqlQuery($dql); + $this->assertSame('Top.Child1.Child2', $result[0]['result']); + } + + #[Test] + public function casts_single_node_ltree_to_text(): void + { + $dql = 'SELECT LTREE2TEXT(l.ltree1) as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsLtrees l WHERE l.id = 3'; + $result = $this->executeDqlQuery($dql); + $this->assertSame('Root', $result[0]['result']); + } + + #[Test] + public function casts_different_ltree_to_text(): void + { + $dql = 'SELECT LTREE2TEXT(l.ltree2) as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsLtrees l WHERE l.id = 1'; + $result = $this->executeDqlQuery($dql); + $this->assertSame('Top.Child1', $result[0]['result']); + } +} diff --git a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/LtreeTestCase.php b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/LtreeTestCase.php new file mode 100644 index 00000000..876b278c --- /dev/null +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/LtreeTestCase.php @@ -0,0 +1,48 @@ +createTestTableForLtreeFixture(); + $this->insertTestDataForLtreeFixture(); + } + + protected function createTestTableForLtreeFixture(): void + { + $tableName = 'containsltrees'; + + $this->createTestSchema(); + $this->dropTestTableIfItExists($tableName); + + $fullTableName = \sprintf('%s.%s', self::DATABASE_SCHEMA, $tableName); + $sql = \sprintf(' + CREATE TABLE %s ( + id SERIAL PRIMARY KEY, + ltree1 LTREE, + ltree2 LTREE, + ltree3 LTREE + ) + ', $fullTableName); + + $this->connection->executeStatement($sql); + } + + protected function insertTestDataForLtreeFixture(): void + { + $sql = \sprintf(' + INSERT INTO %s.containsltrees (ltree1, ltree2, ltree3) VALUES + (\'Top.Child1.Child2\', \'Top.Child1\', \'Top.Child2.Child3\'), + (\'A.B.C.D\', \'A.B\', \'A.B.C\'), + (\'Root\', \'Root.Leaf\', \'Root.Branch\') + ', self::DATABASE_SCHEMA); + $this->connection->executeStatement($sql); + } +} diff --git a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/NlevelTest.php b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/NlevelTest.php new file mode 100644 index 00000000..984cae27 --- /dev/null +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/NlevelTest.php @@ -0,0 +1,42 @@ + Nlevel::class, + ]; + } + + #[Test] + public function returns_number_of_labels_in_path(): void + { + $dql = 'SELECT NLEVEL(l.ltree1) as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsLtrees l WHERE l.id = 1'; + $result = $this->executeDqlQuery($dql); + $this->assertSame(3, $result[0]['result']); + } + + #[Test] + public function returns_number_of_labels_for_different_paths(): void + { + $dql = 'SELECT NLEVEL(l.ltree2) as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsLtrees l WHERE l.id = 1'; + $result = $this->executeDqlQuery($dql); + $this->assertSame(2, $result[0]['result']); + } + + #[Test] + public function returns_number_of_labels_for_single_node(): void + { + $dql = 'SELECT NLEVEL(l.ltree1) as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsLtrees l WHERE l.id = 3'; + $result = $this->executeDqlQuery($dql); + $this->assertSame(1, $result[0]['result']); + } +} diff --git a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/SubltreeTest.php b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/SubltreeTest.php new file mode 100644 index 00000000..259f6ceb --- /dev/null +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/SubltreeTest.php @@ -0,0 +1,34 @@ + Subltree::class, + ]; + } + + #[Test] + public function extracts_subpath_from_ltree(): void + { + $dql = 'SELECT SUBLTREE(l.ltree1, 1, 2) as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsLtrees l WHERE l.id = 1'; + $result = $this->executeDqlQuery($dql); + $this->assertSame('Child1', $result[0]['result']); + } + + #[Test] + public function extracts_subpath_from_ltree_with_different_positions(): void + { + $dql = 'SELECT SUBLTREE(l.ltree1, 0, 2) as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsLtrees l WHERE l.id = 1'; + $result = $this->executeDqlQuery($dql); + $this->assertSame('Top.Child1', $result[0]['result']); + } +} diff --git a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/SubpathTest.php b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/SubpathTest.php new file mode 100644 index 00000000..e565c1b7 --- /dev/null +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/SubpathTest.php @@ -0,0 +1,50 @@ + Subpath::class, + ]; + } + + #[Test] + public function extracts_subpath_with_offset_and_length(): void + { + $dql = 'SELECT SUBPATH(l.ltree1, 0, 2) as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsLtrees l WHERE l.id = 1'; + $result = $this->executeDqlQuery($dql); + $this->assertSame('Top.Child1', $result[0]['result']); + } + + #[Test] + public function extracts_subpath_with_offset_only(): void + { + $dql = 'SELECT SUBPATH(l.ltree1, 1) as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsLtrees l WHERE l.id = 1'; + $result = $this->executeDqlQuery($dql); + $this->assertSame('Child1.Child2', $result[0]['result']); + } + + #[Test] + public function extracts_subpath_with_negative_offset(): void + { + $dql = 'SELECT SUBPATH(l.ltree1, -2) as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsLtrees l WHERE l.id = 1'; + $result = $this->executeDqlQuery($dql); + $this->assertSame('Child1.Child2', $result[0]['result']); + } + + #[Test] + public function extracts_subpath_with_negative_length(): void + { + $dql = 'SELECT SUBPATH(l.ltree1, 0, -1) as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsLtrees l WHERE l.id = 1'; + $result = $this->executeDqlQuery($dql); + $this->assertSame('Top.Child1', $result[0]['result']); + } +} diff --git a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/Text2ltreeTest.php b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/Text2ltreeTest.php new file mode 100644 index 00000000..6d1ec956 --- /dev/null +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/Text2ltreeTest.php @@ -0,0 +1,42 @@ + Text2ltree::class, + ]; + } + + #[Test] + public function casts_text_to_ltree(): void + { + $dql = "SELECT TEXT2LTREE('Top.Child1.Child2') as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsLtrees l WHERE l.id = 1"; + $result = $this->executeDqlQuery($dql); + $this->assertSame('Top.Child1.Child2', $result[0]['result']); + } + + #[Test] + public function casts_single_node_text_to_ltree(): void + { + $dql = "SELECT TEXT2LTREE('Root') as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsLtrees l WHERE l.id = 1"; + $result = $this->executeDqlQuery($dql); + $this->assertSame('Root', $result[0]['result']); + } + + #[Test] + public function casts_empty_text_to_ltree(): void + { + $dql = "SELECT TEXT2LTREE('') as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsLtrees l WHERE l.id = 1"; + $result = $this->executeDqlQuery($dql); + $this->assertSame('', $result[0]['result']); + } +} diff --git a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/IndexTest.php b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/IndexTest.php new file mode 100644 index 00000000..d314b8ad --- /dev/null +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/IndexTest.php @@ -0,0 +1,35 @@ + Index::class, + ]; + } + + protected function getExpectedSqlStatements(): array + { + return [ + 'finds position of ltree in another ltree' => 'SELECT index(c0_.text1, c0_.text2) AS sclr_0 FROM ContainsTexts c0_', + 'finds position with offset' => 'SELECT index(c0_.text1, c0_.text2, -4) AS sclr_0 FROM ContainsTexts c0_', + ]; + } + + protected function getDqlStatements(): array + { + return [ + 'finds position of ltree in another ltree' => \sprintf('SELECT INDEX(e.text1, e.text2) FROM %s e', ContainsTexts::class), + 'finds position with offset' => \sprintf('SELECT INDEX(e.text1, e.text2, -4) FROM %s e', ContainsTexts::class), + ]; + } +} diff --git a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/LcaTest.php b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/LcaTest.php new file mode 100644 index 00000000..98699cf8 --- /dev/null +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/LcaTest.php @@ -0,0 +1,35 @@ + Lca::class, + ]; + } + + protected function getExpectedSqlStatements(): array + { + return [ + 'computes longest common ancestor of two paths' => 'SELECT lca(c0_.text1, c0_.text2) AS sclr_0 FROM ContainsTexts c0_', + 'computes longest common ancestor of three paths' => "SELECT lca(c0_.text1, c0_.text2, 'Top.Child1') AS sclr_0 FROM ContainsTexts c0_", + ]; + } + + protected function getDqlStatements(): array + { + return [ + 'computes longest common ancestor of two paths' => \sprintf('SELECT LCA(e.text1, e.text2) FROM %s e', ContainsTexts::class), + 'computes longest common ancestor of three paths' => \sprintf("SELECT LCA(e.text1, e.text2, 'Top.Child1') FROM %s e", ContainsTexts::class), + ]; + } +} diff --git a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/Ltree2textTest.php b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/Ltree2textTest.php new file mode 100644 index 00000000..79b48ca2 --- /dev/null +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/Ltree2textTest.php @@ -0,0 +1,33 @@ + Ltree2text::class, + ]; + } + + protected function getExpectedSqlStatements(): array + { + return [ + 'casts ltree to text' => 'SELECT ltree2text(c0_.text1) AS sclr_0 FROM ContainsTexts c0_', + ]; + } + + protected function getDqlStatements(): array + { + return [ + 'casts ltree to text' => \sprintf('SELECT LTREE2TEXT(e.text1) FROM %s e', ContainsTexts::class), + ]; + } +} diff --git a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/NlevelTest.php b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/NlevelTest.php new file mode 100644 index 00000000..6dd0bcbf --- /dev/null +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/NlevelTest.php @@ -0,0 +1,33 @@ + Nlevel::class, + ]; + } + + protected function getExpectedSqlStatements(): array + { + return [ + 'returns number of labels in path' => 'SELECT nlevel(c0_.text1) AS sclr_0 FROM ContainsTexts c0_', + ]; + } + + protected function getDqlStatements(): array + { + return [ + 'returns number of labels in path' => \sprintf('SELECT NLEVEL(e.text1) FROM %s e', ContainsTexts::class), + ]; + } +} diff --git a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/SubltreeTest.php b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/SubltreeTest.php new file mode 100644 index 00000000..dda019e3 --- /dev/null +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/SubltreeTest.php @@ -0,0 +1,33 @@ + Subltree::class, + ]; + } + + protected function getExpectedSqlStatements(): array + { + return [ + 'extracts subpath from ltree' => 'SELECT subltree(c0_.text1, 1, 2) AS sclr_0 FROM ContainsTexts c0_', + ]; + } + + protected function getDqlStatements(): array + { + return [ + 'extracts subpath from ltree' => \sprintf('SELECT SUBLTREE(e.text1, 1, 2) FROM %s e', ContainsTexts::class), + ]; + } +} diff --git a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/SubpathTest.php b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/SubpathTest.php new file mode 100644 index 00000000..496e9795 --- /dev/null +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/SubpathTest.php @@ -0,0 +1,35 @@ + Subpath::class, + ]; + } + + protected function getExpectedSqlStatements(): array + { + return [ + 'extracts subpath with offset and length' => 'SELECT subpath(c0_.text1, 0, 2) AS sclr_0 FROM ContainsTexts c0_', + 'extracts subpath with offset only' => 'SELECT subpath(c0_.text1, 1) AS sclr_0 FROM ContainsTexts c0_', + ]; + } + + protected function getDqlStatements(): array + { + return [ + 'extracts subpath with offset and length' => \sprintf('SELECT SUBPATH(e.text1, 0, 2) FROM %s e', ContainsTexts::class), + 'extracts subpath with offset only' => \sprintf('SELECT SUBPATH(e.text1, 1) FROM %s e', ContainsTexts::class), + ]; + } +} diff --git a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/Text2ltreeTest.php b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/Text2ltreeTest.php new file mode 100644 index 00000000..04e518ea --- /dev/null +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/Text2ltreeTest.php @@ -0,0 +1,33 @@ + Text2ltree::class, + ]; + } + + protected function getExpectedSqlStatements(): array + { + return [ + 'casts text to ltree' => "SELECT text2ltree('Top.Child1.Child2') AS sclr_0 FROM ContainsTexts c0_", + ]; + } + + protected function getDqlStatements(): array + { + return [ + 'casts text to ltree' => \sprintf("SELECT TEXT2LTREE('Top.Child1.Child2') FROM %s e", ContainsTexts::class), + ]; + } +} From 1daf70775020e4de3cbc33896f2769f5a8ba6687 Mon Sep 17 00:00:00 2001 From: Martin Georgiev Date: Sat, 6 Sep 2025 14:46:29 +0300 Subject: [PATCH 2/4] no message --- docs/LTREE-TYPE.md | 5 ----- .../ORM/Query/AST/Functions/Ltree/Index.php | 7 ++++-- .../ORM/Query/AST/Functions/Ltree/Lca.php | 3 +-- .../Query/AST/Functions/Ltree/Ltree2text.php | 2 +- .../ORM/Query/AST/Functions/Ltree/Nlevel.php | 2 +- .../Query/AST/Functions/Ltree/Subltree.php | 2 +- .../ORM/Query/AST/Functions/Ltree/Subpath.php | 7 ++++-- .../Query/AST/Functions/Ltree/Text2ltree.php | 2 +- .../AST/Functions/WebsearchToTsquery.php | 2 +- .../ORM/Query/AST/Functions/Ltree/LcaTest.php | 10 ++++----- .../AST/Functions/Ltree/Text2ltreeTest.php | 6 ++--- .../Query/AST/Functions/Ltree/IndexTest.php | 19 ++++++++++++++++ .../ORM/Query/AST/Functions/Ltree/LcaTest.php | 22 +++++++++++++++++++ .../Query/AST/Functions/Ltree/SubpathTest.php | 22 +++++++++++++++++++ 14 files changed, 87 insertions(+), 24 deletions(-) diff --git a/docs/LTREE-TYPE.md b/docs/LTREE-TYPE.md index 48ae76b6..fb414176 100644 --- a/docs/LTREE-TYPE.md +++ b/docs/LTREE-TYPE.md @@ -300,11 +300,6 @@ Computes the longest common ancestor of multiple paths (up to 8 arguments suppor ```php // DQL -$dql = "SELECT LCA(e.path1, e.path2) FROM Entity e"; -// SQL: lca(e.path1, e.path2) -// Example: lca('Top.Child1.Child2', 'Top.Child1') → 'Top' - -// With multiple paths $dql = "SELECT LCA(e.path1, e.path2, e.path3) FROM Entity e"; // SQL: lca(e.path1, e.path2, e.path3) // Example: lca('Top.Child1.Child2', 'Top.Child1', 'Top.Child2.Child3') → 'Top' diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/Index.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/Index.php index 3d48b036..a5735055 100644 --- a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/Index.php +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/Index.php @@ -12,7 +12,7 @@ * Returns position of first occurrence of b in a, or -1 if not found. * The search starts at position offset; negative offset means start -offset labels from the end of the path. * - * @see https://www.postgresql.org/docs/current/ltree.html#LTREE-FUNCTIONS + * @see https://www.postgresql.org/docs/17/ltree.html#LTREE-FUNCTIONS * @since 3.5 * * @author Martin Georgiev @@ -24,7 +24,10 @@ class Index extends BaseVariadicFunction { protected function getNodeMappingPattern(): array { - return ['StringPrimary,StringPrimary,SimpleArithmeticExpression']; + return [ + 'StringPrimary,StringPrimary,SimpleArithmeticExpression', + 'StringPrimary,StringPrimary', + ]; } protected function getFunctionName(): string diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/Lca.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/Lca.php index e965b557..841521da 100644 --- a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/Lca.php +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/Lca.php @@ -10,9 +10,8 @@ * Implementation of PostgreSQL lca function. * * Computes longest common ancestor of paths (up to 8 arguments are supported). - * Also supports array input: lca(ltree[]). * - * @see https://www.postgresql.org/docs/current/ltree.html#LTREE-FUNCTIONS + * @see https://www.postgresql.org/docs/17/ltree.html#LTREE-FUNCTIONS * @since 3.5 * * @author Martin Georgiev diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/Ltree2text.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/Ltree2text.php index 7dd52b2f..135d8e81 100644 --- a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/Ltree2text.php +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/Ltree2text.php @@ -11,7 +11,7 @@ * * Casts ltree to text. * - * @see https://www.postgresql.org/docs/current/ltree.html#LTREE-FUNCTIONS + * @see https://www.postgresql.org/docs/17/ltree.html#LTREE-FUNCTIONS * @since 3.5 * * @author Martin Georgiev diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/Nlevel.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/Nlevel.php index 37b04e75..9a8602fb 100644 --- a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/Nlevel.php +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/Nlevel.php @@ -11,7 +11,7 @@ * * Returns number of labels in path. * - * @see https://www.postgresql.org/docs/current/ltree.html#LTREE-FUNCTIONS + * @see https://www.postgresql.org/docs/17/ltree.html#LTREE-FUNCTIONS * @since 3.5 * * @author Martin Georgiev diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/Subltree.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/Subltree.php index 7bb9a8c7..c576e077 100644 --- a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/Subltree.php +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/Subltree.php @@ -11,7 +11,7 @@ * * Returns subpath of ltree from position start to position end-1 (counting from 0). * - * @see https://www.postgresql.org/docs/current/ltree.html#LTREE-FUNCTIONS + * @see https://www.postgresql.org/docs/17/ltree.html#LTREE-FUNCTIONS * @since 3.5 * * @author Martin Georgiev diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/Subpath.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/Subpath.php index d0e91e86..5af61e8b 100644 --- a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/Subpath.php +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/Subpath.php @@ -13,7 +13,7 @@ * If offset is negative, subpath starts that far from the end of the path. * If len is negative, leaves that many labels off the end of the path. * - * @see https://www.postgresql.org/docs/current/ltree.html#LTREE-FUNCTIONS + * @see https://www.postgresql.org/docs/17/ltree.html#LTREE-FUNCTIONS * @since 3.5 * * @author Martin Georgiev @@ -25,7 +25,10 @@ class Subpath extends BaseVariadicFunction { protected function getNodeMappingPattern(): array { - return ['StringPrimary,SimpleArithmeticExpression,SimpleArithmeticExpression']; + return [ + 'StringPrimary,SimpleArithmeticExpression,SimpleArithmeticExpression', + 'StringPrimary,SimpleArithmeticExpression', + ]; } protected function getFunctionName(): string diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/Text2ltree.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/Text2ltree.php index a5c0fc1f..23b2be53 100644 --- a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/Text2ltree.php +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/Text2ltree.php @@ -11,7 +11,7 @@ * * Casts text to ltree. * - * @see https://www.postgresql.org/docs/current/ltree.html#LTREE-FUNCTIONS + * @see https://www.postgresql.org/docs/17/ltree.html#LTREE-FUNCTIONS * @since 3.5 * * @author Martin Georgiev diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/WebsearchToTsquery.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/WebsearchToTsquery.php index df6eecc8..f72d2714 100644 --- a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/WebsearchToTsquery.php +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/WebsearchToTsquery.php @@ -7,7 +7,7 @@ /** * Implementation of PostgreSQL WEBSEARCH_TO_TSQUERY(). * - * @see https://www.postgresql.org/docs/current/textsearch-controls.html + * @see https://www.postgresql.org/docs/17/textsearch-controls.html * @since 3.5 * * @author Jan Klan diff --git a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/LcaTest.php b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/LcaTest.php index bcf1ddcf..a33208fc 100644 --- a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/LcaTest.php +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/LcaTest.php @@ -17,7 +17,7 @@ protected function getStringFunctions(): array } #[Test] - public function computes_longest_common_ancestor_of_two_paths(): void + public function can_compute_longest_common_ancestor_of_two_paths(): void { $dql = 'SELECT LCA(l.ltree1, l.ltree2) as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsLtrees l WHERE l.id = 1'; $result = $this->executeDqlQuery($dql); @@ -25,7 +25,7 @@ public function computes_longest_common_ancestor_of_two_paths(): void } #[Test] - public function computes_longest_common_ancestor_of_three_paths(): void + public function can_compute_longest_common_ancestor_of_three_paths(): void { $dql = 'SELECT LCA(l.ltree1, l.ltree2, l.ltree3) as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsLtrees l WHERE l.id = 1'; $result = $this->executeDqlQuery($dql); @@ -33,7 +33,7 @@ public function computes_longest_common_ancestor_of_three_paths(): void } #[Test] - public function computes_longest_common_ancestor_with_different_paths(): void + public function can_compute_longest_common_ancestor_with_different_paths(): void { $dql = 'SELECT LCA(l.ltree1, l.ltree2) as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsLtrees l WHERE l.id = 2'; $result = $this->executeDqlQuery($dql); @@ -41,7 +41,7 @@ public function computes_longest_common_ancestor_with_different_paths(): void } #[Test] - public function computes_longest_common_ancestor_with_single_node(): void + public function can_compute_longest_common_ancestor_with_single_node(): void { $dql = 'SELECT LCA(l.ltree1, l.ltree2) as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsLtrees l WHERE l.id = 3'; $result = $this->executeDqlQuery($dql); @@ -49,7 +49,7 @@ public function computes_longest_common_ancestor_with_single_node(): void } #[Test] - public function computes_longest_common_ancestor_with_string_literals(): void + public function can_compute_longest_common_ancestor_with_string_literals(): void { $dql = "SELECT LCA('Top.Child1.Child2', 'Top.Child1', 'Top.Child2.Child3') as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsLtrees l WHERE l.id = 1"; $result = $this->executeDqlQuery($dql); diff --git a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/Text2ltreeTest.php b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/Text2ltreeTest.php index 6d1ec956..c5783791 100644 --- a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/Text2ltreeTest.php +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/Text2ltreeTest.php @@ -17,7 +17,7 @@ protected function getStringFunctions(): array } #[Test] - public function casts_text_to_ltree(): void + public function can_cast_text_to_ltree(): void { $dql = "SELECT TEXT2LTREE('Top.Child1.Child2') as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsLtrees l WHERE l.id = 1"; $result = $this->executeDqlQuery($dql); @@ -25,7 +25,7 @@ public function casts_text_to_ltree(): void } #[Test] - public function casts_single_node_text_to_ltree(): void + public function can_cast_single_node_text_to_ltree(): void { $dql = "SELECT TEXT2LTREE('Root') as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsLtrees l WHERE l.id = 1"; $result = $this->executeDqlQuery($dql); @@ -33,7 +33,7 @@ public function casts_single_node_text_to_ltree(): void } #[Test] - public function casts_empty_text_to_ltree(): void + public function can_cast_empty_text_to_ltree(): void { $dql = "SELECT TEXT2LTREE('') as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsLtrees l WHERE l.id = 1"; $result = $this->executeDqlQuery($dql); diff --git a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/IndexTest.php b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/IndexTest.php index d314b8ad..804c8eb8 100644 --- a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/IndexTest.php +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/IndexTest.php @@ -5,6 +5,7 @@ namespace Tests\Unit\MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Ltree; use Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsTexts; +use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Exception\InvalidArgumentForVariadicFunctionException; use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Ltree\Index; use Tests\Unit\MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\TestCase; @@ -32,4 +33,22 @@ protected function getDqlStatements(): array 'finds position with offset' => \sprintf('SELECT INDEX(e.text1, e.text2, -4) FROM %s e', ContainsTexts::class), ]; } + + public function test_throws_exception_when_argument_count_is_too_low(): void + { + $this->expectException(InvalidArgumentForVariadicFunctionException::class); + $this->expectExceptionMessage('index() requires at least 2 arguments'); + + $dql = \sprintf('SELECT INDEX(e.text1) FROM %s e', ContainsTexts::class); + $this->buildEntityManager()->createQuery($dql)->getSQL(); + } + + public function test_throws_exception_when_argument_count_is_too_high(): void + { + $this->expectException(InvalidArgumentForVariadicFunctionException::class); + $this->expectExceptionMessage('index() requires between 2 and 3 arguments'); + + $dql = \sprintf('SELECT INDEX(e.text1, e.text2, 0, 1) FROM %s e', ContainsTexts::class); + $this->buildEntityManager()->createQuery($dql)->getSQL(); + } } diff --git a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/LcaTest.php b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/LcaTest.php index 98699cf8..112c1385 100644 --- a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/LcaTest.php +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/LcaTest.php @@ -5,7 +5,9 @@ namespace Tests\Unit\MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Ltree; use Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsTexts; +use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Exception\InvalidArgumentForVariadicFunctionException; use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Ltree\Lca; +use PHPUnit\Framework\Attributes\Test; use Tests\Unit\MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\TestCase; class LcaTest extends TestCase @@ -32,4 +34,24 @@ protected function getDqlStatements(): array 'computes longest common ancestor of three paths' => \sprintf("SELECT LCA(e.text1, e.text2, 'Top.Child1') FROM %s e", ContainsTexts::class), ]; } + + #[Test] + public function throws_exception_when_argument_count_is_too_low(): void + { + $this->expectException(InvalidArgumentForVariadicFunctionException::class); + $this->expectExceptionMessage('lca() requires at least 2 arguments'); + + $dql = \sprintf('SELECT LCA(e.text1) FROM %s e', ContainsTexts::class); + $this->buildEntityManager()->createQuery($dql)->getSQL(); + } + + #[Test] + public function throws_exception_when_argument_count_is_too_high(): void + { + $this->expectException(InvalidArgumentForVariadicFunctionException::class); + $this->expectExceptionMessage('lca() requires between 2 and 8 arguments'); + + $dql = \sprintf('SELECT LCA(e.text1, e.text2, e.text3, e.text4, e.text5, e.text6, e.text7, e.text8, e.text9) FROM %s e', ContainsTexts::class); + $this->buildEntityManager()->createQuery($dql)->getSQL(); + } } diff --git a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/SubpathTest.php b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/SubpathTest.php index 496e9795..62b4816b 100644 --- a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/SubpathTest.php +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/SubpathTest.php @@ -5,7 +5,9 @@ namespace Tests\Unit\MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Ltree; use Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsTexts; +use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Exception\InvalidArgumentForVariadicFunctionException; use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Ltree\Subpath; +use PHPUnit\Framework\Attributes\Test; use Tests\Unit\MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\TestCase; class SubpathTest extends TestCase @@ -32,4 +34,24 @@ protected function getDqlStatements(): array 'extracts subpath with offset only' => \sprintf('SELECT SUBPATH(e.text1, 1) FROM %s e', ContainsTexts::class), ]; } + + #[Test] + public function throws_exception_when_argument_count_is_too_low(): void + { + $this->expectException(InvalidArgumentForVariadicFunctionException::class); + $this->expectExceptionMessage('subpath() requires at least 2 arguments'); + + $dql = \sprintf('SELECT SUBPATH(e.text1) FROM %s e', ContainsTexts::class); + $this->buildEntityManager()->createQuery($dql)->getSQL(); + } + + #[Test] + public function throws_exception_when_argument_count_is_too_high(): void + { + $this->expectException(InvalidArgumentForVariadicFunctionException::class); + $this->expectExceptionMessage('subpath() requires between 2 and 3 arguments'); + + $dql = \sprintf('SELECT SUBPATH(e.text1, 0, 2, 3) FROM %s e', ContainsTexts::class); + $this->buildEntityManager()->createQuery($dql)->getSQL(); + } } From 66871737f25b0ecaff7ccccf85ab0eae03398fa2 Mon Sep 17 00:00:00 2001 From: Martin Georgiev Date: Sat, 6 Sep 2025 14:54:17 +0300 Subject: [PATCH 3/4] no message --- .../AST/Functions/Ltree/Ltree2textTest.php | 18 +----------------- .../Query/AST/Functions/Ltree/NlevelTest.php | 8 -------- .../Query/AST/Functions/Ltree/SubltreeTest.php | 4 ++-- .../Query/AST/Functions/Ltree/SubpathTest.php | 12 ++++++------ 4 files changed, 9 insertions(+), 33 deletions(-) diff --git a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/Ltree2textTest.php b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/Ltree2textTest.php index c66fc8eb..2f0c77e7 100644 --- a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/Ltree2textTest.php +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/Ltree2textTest.php @@ -17,26 +17,10 @@ protected function getStringFunctions(): array } #[Test] - public function casts_ltree_to_text(): void + public function can_cast_ltree_to_text(): void { $dql = 'SELECT LTREE2TEXT(l.ltree1) as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsLtrees l WHERE l.id = 1'; $result = $this->executeDqlQuery($dql); $this->assertSame('Top.Child1.Child2', $result[0]['result']); } - - #[Test] - public function casts_single_node_ltree_to_text(): void - { - $dql = 'SELECT LTREE2TEXT(l.ltree1) as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsLtrees l WHERE l.id = 3'; - $result = $this->executeDqlQuery($dql); - $this->assertSame('Root', $result[0]['result']); - } - - #[Test] - public function casts_different_ltree_to_text(): void - { - $dql = 'SELECT LTREE2TEXT(l.ltree2) as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsLtrees l WHERE l.id = 1'; - $result = $this->executeDqlQuery($dql); - $this->assertSame('Top.Child1', $result[0]['result']); - } } diff --git a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/NlevelTest.php b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/NlevelTest.php index 984cae27..030ed2f0 100644 --- a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/NlevelTest.php +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/NlevelTest.php @@ -24,14 +24,6 @@ public function returns_number_of_labels_in_path(): void $this->assertSame(3, $result[0]['result']); } - #[Test] - public function returns_number_of_labels_for_different_paths(): void - { - $dql = 'SELECT NLEVEL(l.ltree2) as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsLtrees l WHERE l.id = 1'; - $result = $this->executeDqlQuery($dql); - $this->assertSame(2, $result[0]['result']); - } - #[Test] public function returns_number_of_labels_for_single_node(): void { diff --git a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/SubltreeTest.php b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/SubltreeTest.php index 259f6ceb..19600370 100644 --- a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/SubltreeTest.php +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/SubltreeTest.php @@ -17,7 +17,7 @@ protected function getStringFunctions(): array } #[Test] - public function extracts_subpath_from_ltree(): void + public function can_extract_subpath_from_an_arbitrary_position(): void { $dql = 'SELECT SUBLTREE(l.ltree1, 1, 2) as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsLtrees l WHERE l.id = 1'; $result = $this->executeDqlQuery($dql); @@ -25,7 +25,7 @@ public function extracts_subpath_from_ltree(): void } #[Test] - public function extracts_subpath_from_ltree_with_different_positions(): void + public function can_extract_subpath_from_the_beginning(): void { $dql = 'SELECT SUBLTREE(l.ltree1, 0, 2) as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsLtrees l WHERE l.id = 1'; $result = $this->executeDqlQuery($dql); diff --git a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/SubpathTest.php b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/SubpathTest.php index e565c1b7..d5fcdcd4 100644 --- a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/SubpathTest.php +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/SubpathTest.php @@ -17,7 +17,7 @@ protected function getStringFunctions(): array } #[Test] - public function extracts_subpath_with_offset_and_length(): void + public function can_extract_with_offset_and_length(): void { $dql = 'SELECT SUBPATH(l.ltree1, 0, 2) as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsLtrees l WHERE l.id = 1'; $result = $this->executeDqlQuery($dql); @@ -25,7 +25,7 @@ public function extracts_subpath_with_offset_and_length(): void } #[Test] - public function extracts_subpath_with_offset_only(): void + public function can_extract_with_offset_only(): void { $dql = 'SELECT SUBPATH(l.ltree1, 1) as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsLtrees l WHERE l.id = 1'; $result = $this->executeDqlQuery($dql); @@ -33,15 +33,15 @@ public function extracts_subpath_with_offset_only(): void } #[Test] - public function extracts_subpath_with_negative_offset(): void + public function can_extract_with_negative_offset(): void { - $dql = 'SELECT SUBPATH(l.ltree1, -2) as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsLtrees l WHERE l.id = 1'; + $dql = 'SELECT SUBPATH(l.ltree1, -1) as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsLtrees l WHERE l.id = 1'; $result = $this->executeDqlQuery($dql); - $this->assertSame('Child1.Child2', $result[0]['result']); + $this->assertSame('Child2', $result[0]['result']); } #[Test] - public function extracts_subpath_with_negative_length(): void + public function can_extract_with_negative_length(): void { $dql = 'SELECT SUBPATH(l.ltree1, 0, -1) as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsLtrees l WHERE l.id = 1'; $result = $this->executeDqlQuery($dql); From e280a0151ddbbc9f252b1d1380d8d2cb84b70e23 Mon Sep 17 00:00:00 2001 From: Martin Georgiev Date: Sat, 6 Sep 2025 15:03:10 +0300 Subject: [PATCH 4/4] no message --- .../ORM/Query/AST/Functions/Ltree/LcaTest.php | 14 +++----------- .../Query/AST/Functions/Ltree/LtreeTestCase.php | 3 ++- 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/LcaTest.php b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/LcaTest.php index a33208fc..b7042c2f 100644 --- a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/LcaTest.php +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/LcaTest.php @@ -27,21 +27,13 @@ public function can_compute_longest_common_ancestor_of_two_paths(): void #[Test] public function can_compute_longest_common_ancestor_of_three_paths(): void { - $dql = 'SELECT LCA(l.ltree1, l.ltree2, l.ltree3) as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsLtrees l WHERE l.id = 1'; + $dql = 'SELECT LCA(l.ltree1, l.ltree2, l.ltree3, \'1.2.3.456\') as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsLtrees l WHERE l.id = 4'; $result = $this->executeDqlQuery($dql); - $this->assertSame('Top', $result[0]['result']); - } - - #[Test] - public function can_compute_longest_common_ancestor_with_different_paths(): void - { - $dql = 'SELECT LCA(l.ltree1, l.ltree2) as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsLtrees l WHERE l.id = 2'; - $result = $this->executeDqlQuery($dql); - $this->assertSame('A', $result[0]['result']); + $this->assertSame('1.2.3', $result[0]['result']); } #[Test] - public function can_compute_longest_common_ancestor_with_single_node(): void + public function can_compute_longest_common_ancestor_to_be_empty_string_when_one_of_the_paths_has_only_a_root_with_no_leafs(): void { $dql = 'SELECT LCA(l.ltree1, l.ltree2) as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsLtrees l WHERE l.id = 3'; $result = $this->executeDqlQuery($dql); diff --git a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/LtreeTestCase.php b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/LtreeTestCase.php index 876b278c..956be313 100644 --- a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/LtreeTestCase.php +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/Ltree/LtreeTestCase.php @@ -41,7 +41,8 @@ protected function insertTestDataForLtreeFixture(): void INSERT INTO %s.containsltrees (ltree1, ltree2, ltree3) VALUES (\'Top.Child1.Child2\', \'Top.Child1\', \'Top.Child2.Child3\'), (\'A.B.C.D\', \'A.B\', \'A.B.C\'), - (\'Root\', \'Root.Leaf\', \'Root.Branch\') + (\'Root\', \'Root.Leaf\', \'Root.Branch\'), + (\'1.2.3.4.5.6\', \'1.2.3.4.5\', \'1.2.3.12.15.71\') ', self::DATABASE_SCHEMA); $this->connection->executeStatement($sql); }