From 9e7079148ae3f28b73a171ac72cc2ab6263f071c Mon Sep 17 00:00:00 2001 From: Martin Georgiev Date: Thu, 31 Jul 2025 11:01:03 +0300 Subject: [PATCH 1/2] feat(#401): improve JSON field extraction by adding index support --- .../ORM/Query/AST/Functions/JsonGetField.php | 29 +++++++++++++++++-- .../AST/Functions/JsonGetFieldAsInteger.php | 8 +++-- .../AST/Functions/JsonGetFieldAsText.php | 8 +++-- .../Functions/JsonGetFieldAsIntegerTest.php | 10 +++++-- .../AST/Functions/JsonGetFieldAsTextTest.php | 10 +++++-- .../Query/AST/Functions/JsonGetFieldTest.php | 6 ++++ 6 files changed, 59 insertions(+), 12 deletions(-) diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonGetField.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonGetField.php index c9de4ca0..62650e58 100644 --- a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonGetField.php +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonGetField.php @@ -4,9 +4,19 @@ namespace MartinGeorgiev\Doctrine\ORM\Query\AST\Functions; +use Doctrine\ORM\Query\Lexer; +use Doctrine\ORM\Query\Parser; +use Doctrine\ORM\Query\QueryException; +use Doctrine\ORM\Query\TokenType; +use MartinGeorgiev\Utils\DoctrineOrm; + /** * Implementation of PostgreSQL json field retrieval, filtered by key (using ->). * + * Supports both string keys for object property access and integer indices for array element access: + * - JSON_GET_FIELD(json_column, 'property_name') -> json_column->'property_name' + * - JSON_GET_FIELD(json_column, 0) -> json_column->0 + * * @see https://www.postgresql.org/docs/9.4/static/functions-json.html * @since 0.1 * @@ -17,7 +27,22 @@ class JsonGetField extends BaseFunction protected function customizeFunction(): void { $this->setFunctionPrototype('(%s -> %s)'); - $this->addNodeMapping('StringPrimary'); - $this->addNodeMapping('StringPrimary'); + } + + protected function feedParserWithNodes(Parser $parser): void + { + $shouldUseLexer = DoctrineOrm::isPre219(); + + // Parse first parameter (always StringPrimary for the JSON column) + $this->nodes[0] = $parser->StringPrimary(); + $parser->match($shouldUseLexer ? Lexer::T_COMMA : TokenType::T_COMMA); + + // Parse second parameter - try ArithmeticPrimary first, then StringPrimary + try { + $this->nodes[1] = $parser->ArithmeticPrimary(); + } catch (QueryException) { + // If ArithmeticPrimary fails (e.g., when encountering a string), try StringPrimary + $this->nodes[1] = $parser->StringPrimary(); + } } } diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonGetFieldAsInteger.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonGetFieldAsInteger.php index c5421df9..c6bff679 100644 --- a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonGetFieldAsInteger.php +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonGetFieldAsInteger.php @@ -7,17 +7,19 @@ /** * Implementation of PostgreSQL json field retrieval as integer, filtered by key (using ->> and type casting to BIGINT). * + * Supports both string keys for object property access and integer indices for array element access: + * - JSON_GET_FIELD_AS_INTEGER(json_column, 'property_name') -> CAST(json_column->>'property_name' as BIGINT) + * - JSON_GET_FIELD_AS_INTEGER(json_column, 0) -> CAST(json_column->>0 as BIGINT) + * * @see https://www.postgresql.org/docs/9.4/static/functions-json.html * @since 0.3 * * @author Martin Georgiev */ -class JsonGetFieldAsInteger extends BaseFunction +class JsonGetFieldAsInteger extends JsonGetField { protected function customizeFunction(): void { $this->setFunctionPrototype('CAST(%s ->> %s as BIGINT)'); - $this->addNodeMapping('StringPrimary'); - $this->addNodeMapping('StringPrimary'); } } diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonGetFieldAsText.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonGetFieldAsText.php index 87d9e27c..e3b7424e 100644 --- a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonGetFieldAsText.php +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonGetFieldAsText.php @@ -7,17 +7,19 @@ /** * Implementation of PostgreSQL json field retrieval as text, filtered by key (using ->>). * + * Supports both string keys for object property access and integer indices for array element access: + * - JSON_GET_FIELD_AS_TEXT(json_column, 'property_name') -> json_column->>'property_name' + * - JSON_GET_FIELD_AS_TEXT(json_column, 0) -> json_column->>0 + * * @see https://www.postgresql.org/docs/9.4/static/functions-json.html * @since 0.1 * * @author Martin Georgiev */ -class JsonGetFieldAsText extends BaseFunction +class JsonGetFieldAsText extends JsonGetField { protected function customizeFunction(): void { $this->setFunctionPrototype('(%s ->> %s)'); - $this->addNodeMapping('StringPrimary'); - $this->addNodeMapping('StringPrimary'); } } diff --git a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonGetFieldAsIntegerTest.php b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonGetFieldAsIntegerTest.php index 77a0079d..df8a9791 100644 --- a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonGetFieldAsIntegerTest.php +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonGetFieldAsIntegerTest.php @@ -5,6 +5,7 @@ namespace Tests\Unit\MartinGeorgiev\Doctrine\ORM\Query\AST\Functions; use Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsJsons; +use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonGetField; use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonGetFieldAsInteger; class JsonGetFieldAsIntegerTest extends TestCase @@ -12,6 +13,7 @@ class JsonGetFieldAsIntegerTest extends TestCase protected function getStringFunctions(): array { return [ + 'JSON_GET_FIELD' => JsonGetField::class, 'JSON_GET_FIELD_AS_INTEGER' => JsonGetFieldAsInteger::class, ]; } @@ -19,14 +21,18 @@ protected function getStringFunctions(): array protected function getExpectedSqlStatements(): array { return [ - "SELECT CAST(c0_.object1 ->> 'rank' as BIGINT) AS sclr_0 FROM ContainsJsons c0_", + 'extracts field as integer' => "SELECT CAST(c0_.object1 ->> 'rank' as BIGINT) AS sclr_0 FROM ContainsJsons c0_", + 'extracts array element as integer' => 'SELECT CAST(c0_.object1 ->> 0 as BIGINT) AS sclr_0 FROM ContainsJsons c0_', + 'extracts nested array element as integer' => "SELECT CAST((c0_.object1 -> 'scores') ->> 1 as BIGINT) AS sclr_0 FROM ContainsJsons c0_", ]; } protected function getDqlStatements(): array { return [ - \sprintf("SELECT JSON_GET_FIELD_AS_INTEGER(e.object1, 'rank') FROM %s e", ContainsJsons::class), + 'extracts field as integer' => \sprintf("SELECT JSON_GET_FIELD_AS_INTEGER(e.object1, 'rank') FROM %s e", ContainsJsons::class), + 'extracts array element as integer' => \sprintf('SELECT JSON_GET_FIELD_AS_INTEGER(e.object1, 0) FROM %s e', ContainsJsons::class), + 'extracts nested array element as integer' => \sprintf("SELECT JSON_GET_FIELD_AS_INTEGER(JSON_GET_FIELD(e.object1, 'scores'), 1) FROM %s e", ContainsJsons::class), ]; } } diff --git a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonGetFieldAsTextTest.php b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonGetFieldAsTextTest.php index f0a651a0..d76866ea 100644 --- a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonGetFieldAsTextTest.php +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonGetFieldAsTextTest.php @@ -5,6 +5,7 @@ namespace Tests\Unit\MartinGeorgiev\Doctrine\ORM\Query\AST\Functions; use Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsJsons; +use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonGetField; use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonGetFieldAsText; class JsonGetFieldAsTextTest extends TestCase @@ -12,6 +13,7 @@ class JsonGetFieldAsTextTest extends TestCase protected function getStringFunctions(): array { return [ + 'JSON_GET_FIELD' => JsonGetField::class, 'JSON_GET_FIELD_AS_TEXT' => JsonGetFieldAsText::class, ]; } @@ -19,14 +21,18 @@ protected function getStringFunctions(): array protected function getExpectedSqlStatements(): array { return [ - "SELECT (c0_.object1 ->> 'country') AS sclr_0 FROM ContainsJsons c0_", + 'extracts field as text' => "SELECT (c0_.object1 ->> 'country') AS sclr_0 FROM ContainsJsons c0_", + 'extracts array element as text' => 'SELECT (c0_.object1 ->> 0) AS sclr_0 FROM ContainsJsons c0_', + 'extracts nested array element as text' => "SELECT ((c0_.object1 -> 'tags') ->> 1) AS sclr_0 FROM ContainsJsons c0_", ]; } protected function getDqlStatements(): array { return [ - \sprintf("SELECT JSON_GET_FIELD_AS_TEXT(e.object1, 'country') FROM %s e", ContainsJsons::class), + 'extracts field as text' => \sprintf("SELECT JSON_GET_FIELD_AS_TEXT(e.object1, 'country') FROM %s e", ContainsJsons::class), + 'extracts array element as text' => \sprintf('SELECT JSON_GET_FIELD_AS_TEXT(e.object1, 0) FROM %s e', ContainsJsons::class), + 'extracts nested array element as text' => \sprintf("SELECT JSON_GET_FIELD_AS_TEXT(JSON_GET_FIELD(e.object1, 'tags'), 1) FROM %s e", ContainsJsons::class), ]; } } diff --git a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonGetFieldTest.php b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonGetFieldTest.php index 24e3471a..c629dc28 100644 --- a/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonGetFieldTest.php +++ b/tests/Unit/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonGetFieldTest.php @@ -21,6 +21,9 @@ protected function getExpectedSqlStatements(): array return [ 'extracts top-level field from json' => "SELECT (c0_.object1 -> 'key') AS sclr_0 FROM ContainsJsons c0_", 'extracts nested field from json' => "SELECT ((c0_.object1 -> 'nested') -> 'key') AS sclr_0 FROM ContainsJsons c0_", + 'extracts array element by index' => 'SELECT (c0_.object1 -> 0) AS sclr_0 FROM ContainsJsons c0_', + 'extracts nested array element by index' => "SELECT ((c0_.object1 -> 'tags') -> 1) AS sclr_0 FROM ContainsJsons c0_", + 'extracts field from array element by index' => "SELECT ((c0_.object1 -> 0) -> 'name') AS sclr_0 FROM ContainsJsons c0_", ]; } @@ -29,6 +32,9 @@ protected function getDqlStatements(): array return [ 'extracts top-level field from json' => \sprintf("SELECT JSON_GET_FIELD(e.object1, 'key') FROM %s e", ContainsJsons::class), 'extracts nested field from json' => \sprintf("SELECT JSON_GET_FIELD(JSON_GET_FIELD(e.object1, 'nested'), 'key') FROM %s e", ContainsJsons::class), + 'extracts array element by index' => \sprintf('SELECT JSON_GET_FIELD(e.object1, 0) FROM %s e', ContainsJsons::class), + 'extracts nested array element by index' => \sprintf("SELECT JSON_GET_FIELD(JSON_GET_FIELD(e.object1, 'tags'), 1) FROM %s e", ContainsJsons::class), + 'extracts field from array element by index' => \sprintf("SELECT JSON_GET_FIELD(JSON_GET_FIELD(e.object1, 0), 'name') FROM %s e", ContainsJsons::class), ]; } } From 8cbd4604ab6c109103f80ea7688a239cc8dffcbd Mon Sep 17 00:00:00 2001 From: Martin Georgiev Date: Thu, 31 Jul 2025 11:31:22 +0300 Subject: [PATCH 2/2] how about some integration tests, martin :)? --- .../ORM/Query/AST/Functions/JsonGetField.php | 14 +++-- .../Functions/JsonGetFieldAsIntegerTest.php | 16 ++++- .../AST/Functions/JsonGetFieldAsTextTest.php | 54 +++++++++++++++++ .../Query/AST/Functions/JsonGetFieldTest.php | 59 +++++++++++++++++++ 4 files changed, 136 insertions(+), 7 deletions(-) create mode 100644 tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonGetFieldAsTextTest.php create mode 100644 tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonGetFieldTest.php diff --git a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonGetField.php b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonGetField.php index 62650e58..af68998f 100644 --- a/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonGetField.php +++ b/src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonGetField.php @@ -33,16 +33,18 @@ protected function feedParserWithNodes(Parser $parser): void { $shouldUseLexer = DoctrineOrm::isPre219(); - // Parse first parameter (always StringPrimary for the JSON column) - $this->nodes[0] = $parser->StringPrimary(); + $nodeForJsonDocumentName = $parser->StringPrimary(); $parser->match($shouldUseLexer ? Lexer::T_COMMA : TokenType::T_COMMA); - // Parse second parameter - try ArithmeticPrimary first, then StringPrimary + // Second parameter can be either an index or a property name try { - $this->nodes[1] = $parser->ArithmeticPrimary(); + $nodeForJsonIndexOrPropertyName = $parser->ArithmeticPrimary(); } catch (QueryException) { - // If ArithmeticPrimary fails (e.g., when encountering a string), try StringPrimary - $this->nodes[1] = $parser->StringPrimary(); + // If ArithmeticPrimary fails (e.g., when encountering a property name rather than an index), try StringPrimary + $nodeForJsonIndexOrPropertyName = $parser->StringPrimary(); } + + /* @phpstan-ignore-next-line assign.propertyType */ + $this->nodes = [$nodeForJsonDocumentName, $nodeForJsonIndexOrPropertyName]; } } diff --git a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonGetFieldAsIntegerTest.php b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonGetFieldAsIntegerTest.php index 22e523f7..29ffa044 100644 --- a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonGetFieldAsIntegerTest.php +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonGetFieldAsIntegerTest.php @@ -4,6 +4,7 @@ namespace Tests\Integration\MartinGeorgiev\Doctrine\ORM\Query\AST\Functions; +use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonGetField; use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonGetFieldAsInteger; class JsonGetFieldAsIntegerTest extends JsonTestCase @@ -11,6 +12,7 @@ class JsonGetFieldAsIntegerTest extends JsonTestCase protected function getStringFunctions(): array { return [ + 'JSON_GET_FIELD' => JsonGetField::class, 'JSON_GET_FIELD_AS_INTEGER' => JsonGetFieldAsInteger::class, ]; } @@ -22,6 +24,18 @@ public function test_json_get_field_as_integer(): void $this->assertSame(30, $result[0]['result']); } + public function test_json_get_field_as_integer_with_index(): void + { + // First, let's insert test data with numeric arrays + $this->connection->executeStatement( + \sprintf("UPDATE %s.containsjsons SET object1 = '{\"scores\": [85, 92, 78]}' WHERE id = 1", self::DATABASE_SCHEMA) + ); + + $dql = "SELECT JSON_GET_FIELD_AS_INTEGER(JSON_GET_FIELD(t.object1, 'scores'), 1) as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsJsons t WHERE t.id = 1"; + $result = $this->executeDqlQuery($dql); + $this->assertSame(92, $result[0]['result']); + } + public function test_json_get_field_as_integer_empty_object(): void { $dql = "SELECT JSON_GET_FIELD_AS_INTEGER(t.object1, 'age') as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsJsons t WHERE t.id = 4"; @@ -36,7 +50,7 @@ public function test_json_get_field_as_integer_null_value(): void $this->assertNull($result[0]['result']); } - public function test_json_get_field_as_integer_nonexistent_field(): void + public function test_json_get_field_as_integer_nonexistent_property_name(): void { $dql = "SELECT JSON_GET_FIELD_AS_INTEGER(t.object1, 'nonexistent') as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsJsons t WHERE t.id = 1"; $result = $this->executeDqlQuery($dql); diff --git a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonGetFieldAsTextTest.php b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonGetFieldAsTextTest.php new file mode 100644 index 00000000..b9c0fbbb --- /dev/null +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonGetFieldAsTextTest.php @@ -0,0 +1,54 @@ + JsonGetField::class, + 'JSON_GET_FIELD_AS_TEXT' => JsonGetFieldAsText::class, + ]; + } + + public function test_json_get_field_as_text_with_property_name(): void + { + $dql = "SELECT JSON_GET_FIELD_AS_TEXT(t.object1, 'name') as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsJsons t WHERE t.id = 1"; + $result = $this->executeDqlQuery($dql); + $this->assertSame('John', $result[0]['result']); + } + + public function test_json_get_field_as_text_with_index(): void + { + $dql = "SELECT JSON_GET_FIELD_AS_TEXT(JSON_GET_FIELD(t.object1, 'tags'), 0) as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsJsons t WHERE t.id = 1"; + $result = $this->executeDqlQuery($dql); + $this->assertSame('developer', $result[0]['result']); + } + + public function test_json_get_field_as_text_nested_access(): void + { + $dql = "SELECT JSON_GET_FIELD_AS_TEXT(JSON_GET_FIELD(t.object1, 'address'), 'city') as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsJsons t WHERE t.id = 1"; + $result = $this->executeDqlQuery($dql); + $this->assertSame('New York', $result[0]['result']); + } + + public function test_json_get_field_as_text_with_null_value(): void + { + $dql = "SELECT JSON_GET_FIELD_AS_TEXT(t.object1, 'age') as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsJsons t WHERE t.id = 5"; + $result = $this->executeDqlQuery($dql); + $this->assertNull($result[0]['result']); + } + + public function test_json_get_field_as_text_with_nonexistent_index(): void + { + $dql = "SELECT JSON_GET_FIELD_AS_TEXT(JSON_GET_FIELD(t.object1, 'tags'), 10) as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsJsons t WHERE t.id = 1"; + $result = $this->executeDqlQuery($dql); + $this->assertNull($result[0]['result']); + } +} diff --git a/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonGetFieldTest.php b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonGetFieldTest.php new file mode 100644 index 00000000..1133f1f8 --- /dev/null +++ b/tests/Integration/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonGetFieldTest.php @@ -0,0 +1,59 @@ + JsonGetField::class, + ]; + } + + public function test_json_get_field_with_property_name(): void + { + $dql = "SELECT JSON_GET_FIELD(t.object1, 'name') as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsJsons t WHERE t.id = 1"; + $result = $this->executeDqlQuery($dql); + $this->assertSame('"John"', $result[0]['result']); + } + + public function test_json_get_field_with_index(): void + { + $dql = "SELECT JSON_GET_FIELD(JSON_GET_FIELD(t.object1, 'tags'), 0) as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsJsons t WHERE t.id = 1"; + $result = $this->executeDqlQuery($dql); + $this->assertSame('"developer"', $result[0]['result']); + } + + public function test_json_get_field_nested_object_access(): void + { + $dql = "SELECT JSON_GET_FIELD(JSON_GET_FIELD(t.object1, 'address'), 'city') as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsJsons t WHERE t.id = 1"; + $result = $this->executeDqlQuery($dql); + $this->assertSame('"New York"', $result[0]['result']); + } + + public function test_json_get_field_with_empty_array(): void + { + $dql = "SELECT JSON_GET_FIELD(JSON_GET_FIELD(t.object1, 'tags'), 0) as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsJsons t WHERE t.id = 3"; + $result = $this->executeDqlQuery($dql); + $this->assertNull($result[0]['result']); + } + + public function test_json_get_field_with_nonexistent_index(): void + { + $dql = "SELECT JSON_GET_FIELD(JSON_GET_FIELD(t.object1, 'tags'), 10) as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsJsons t WHERE t.id = 1"; + $result = $this->executeDqlQuery($dql); + $this->assertNull($result[0]['result']); + } + + public function test_json_get_field_with_nonexistent_property_name(): void + { + $dql = "SELECT JSON_GET_FIELD(t.object1, 'nonexistent') as result FROM Fixtures\\MartinGeorgiev\\Doctrine\\Entity\\ContainsJsons t WHERE t.id = 1"; + $result = $this->executeDqlQuery($dql); + $this->assertNull($result[0]['result']); + } +}