Skip to content

Commit f0f3c9f

Browse files
feat: add support for json_exists, json_query, json_scalar, json_serialize and json_value
1 parent 877d929 commit f0f3c9f

File tree

10 files changed

+312
-0
lines changed

10 files changed

+312
-0
lines changed
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace MartinGeorgiev\Doctrine\ORM\Query\AST\Functions;
6+
7+
/**
8+
* Implementation of PostgreSQL JSON_EXISTS().
9+
*
10+
* @see https://www.postgresql.org/docs/17/functions-json.html
11+
* @since 2.10
12+
*
13+
* @author Martin Georgiev <martin.georgiev@gmail.com>
14+
*/
15+
class JsonExists extends BaseFunction
16+
{
17+
protected function customizeFunction(): void
18+
{
19+
$this->setFunctionPrototype('json_exists(%s, %s)');
20+
$this->addNodeMapping('StringPrimary');
21+
$this->addNodeMapping('StringPrimary');
22+
}
23+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace MartinGeorgiev\Doctrine\ORM\Query\AST\Functions;
6+
7+
/**
8+
* Implementation of PostgreSQL JSON_QUERY().
9+
*
10+
* @see https://www.postgresql.org/docs/17/functions-json.html
11+
* @since 2.10
12+
*
13+
* @author Martin Georgiev <martin.georgiev@gmail.com>
14+
*/
15+
class JsonQuery extends BaseFunction
16+
{
17+
protected function customizeFunction(): void
18+
{
19+
$this->setFunctionPrototype('json_query(%s, %s)');
20+
$this->addNodeMapping('StringPrimary');
21+
$this->addNodeMapping('StringPrimary');
22+
}
23+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace MartinGeorgiev\Doctrine\ORM\Query\AST\Functions;
6+
7+
/**
8+
* Implementation of PostgreSQL JSON_SCALAR().
9+
*
10+
* @see https://www.postgresql.org/docs/17/functions-json.html
11+
* @since 2.10
12+
*
13+
* @author Martin Georgiev <martin.georgiev@gmail.com>
14+
*/
15+
class JsonScalar extends BaseFunction
16+
{
17+
protected function customizeFunction(): void
18+
{
19+
$this->setFunctionPrototype('json_scalar(%s)');
20+
$this->addNodeMapping('StringPrimary');
21+
}
22+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace MartinGeorgiev\Doctrine\ORM\Query\AST\Functions;
6+
7+
use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Exception\InvalidArgumentForVariadicFunctionException;
8+
9+
/**
10+
* Implementation of PostgreSQL JSON_SERIALIZE().
11+
*
12+
* Supports basic form:
13+
* - json_serialize(expression)
14+
*
15+
* @see https://www.postgresql.org/docs/17/functions-json.html
16+
* @since 2.10
17+
*
18+
* @author Martin Georgiev <martin.georgiev@gmail.com>
19+
*/
20+
class JsonSerialize extends BaseVariadicFunction
21+
{
22+
protected function customizeFunction(): void
23+
{
24+
$this->setFunctionPrototype('json_serialize(%s)');
25+
}
26+
27+
protected function validateArguments(array $arguments): void
28+
{
29+
if (\count($arguments) !== 1) {
30+
throw InvalidArgumentForVariadicFunctionException::exact('json_serialize', 1);
31+
}
32+
}
33+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace MartinGeorgiev\Doctrine\ORM\Query\AST\Functions;
6+
7+
/**
8+
* Implementation of PostgreSQL JSON_VALUE().
9+
*
10+
* @see https://www.postgresql.org/docs/17/functions-json.html
11+
* @since 2.10
12+
*
13+
* @author Martin Georgiev <martin.georgiev@gmail.com>
14+
*/
15+
class JsonValue extends BaseFunction
16+
{
17+
protected function customizeFunction(): void
18+
{
19+
$this->setFunctionPrototype('json_value(%s, %s)');
20+
$this->addNodeMapping('StringPrimary');
21+
$this->addNodeMapping('StringPrimary');
22+
}
23+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tests\MartinGeorgiev\Doctrine\ORM\Query\AST\Functions;
6+
7+
use Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsJsons;
8+
use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonExists;
9+
10+
class JsonExistsTest extends TestCase
11+
{
12+
protected function getStringFunctions(): array
13+
{
14+
return [
15+
'JSON_EXISTS' => JsonExists::class,
16+
];
17+
}
18+
19+
protected function getExpectedSqlStatements(): array
20+
{
21+
return [
22+
// Basic usage
23+
"SELECT json_exists(c0_.object1, '$.name') AS sclr_0 FROM ContainsJsons c0_",
24+
// Nested path
25+
"SELECT json_exists(c0_.object1, '$.address.city') AS sclr_0 FROM ContainsJsons c0_",
26+
// Array element
27+
"SELECT json_exists(c0_.object1, '$.items[0]') AS sclr_0 FROM ContainsJsons c0_",
28+
];
29+
}
30+
31+
protected function getDqlStatements(): array
32+
{
33+
return [
34+
\sprintf("SELECT JSON_EXISTS(e.object1, '$.name') FROM %s e", ContainsJsons::class),
35+
\sprintf("SELECT JSON_EXISTS(e.object1, '$.address.city') FROM %s e", ContainsJsons::class),
36+
\sprintf("SELECT JSON_EXISTS(e.object1, '$.items[0]') FROM %s e", ContainsJsons::class),
37+
];
38+
}
39+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tests\MartinGeorgiev\Doctrine\ORM\Query\AST\Functions;
6+
7+
use Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsJsons;
8+
use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonQuery;
9+
10+
class JsonQueryTest extends TestCase
11+
{
12+
protected function getStringFunctions(): array
13+
{
14+
return [
15+
'JSON_QUERY' => JsonQuery::class,
16+
];
17+
}
18+
19+
protected function getExpectedSqlStatements(): array
20+
{
21+
return [
22+
"SELECT json_query(c0_.object1, '$.items[*]') AS sclr_0 FROM ContainsJsons c0_",
23+
"SELECT json_query(c0_.object1, '$.address') AS sclr_0 FROM ContainsJsons c0_",
24+
// Additional test cases for important scenarios
25+
"SELECT json_query(c0_.object1, '$.store.book[*].author') AS sclr_0 FROM ContainsJsons c0_",
26+
"SELECT json_query(c0_.object1, '$.store.book[0 to 2]') AS sclr_0 FROM ContainsJsons c0_",
27+
"SELECT json_query(c0_.object1, '$.store.book[*]?(@.price > 10)') AS sclr_0 FROM ContainsJsons c0_",
28+
];
29+
}
30+
31+
protected function getDqlStatements(): array
32+
{
33+
return [
34+
\sprintf("SELECT JSON_QUERY(e.object1, '$.items[*]') FROM %s e", ContainsJsons::class),
35+
\sprintf("SELECT JSON_QUERY(e.object1, '$.address') FROM %s e", ContainsJsons::class),
36+
// Additional test cases matching the SQL statements above
37+
\sprintf("SELECT JSON_QUERY(e.object1, '$.store.book[*].author') FROM %s e", ContainsJsons::class),
38+
\sprintf("SELECT JSON_QUERY(e.object1, '$.store.book[0 to 2]') FROM %s e", ContainsJsons::class),
39+
\sprintf("SELECT JSON_QUERY(e.object1, '$.store.book[*]?(@.price > 10)') FROM %s e", ContainsJsons::class),
40+
];
41+
}
42+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tests\MartinGeorgiev\Doctrine\ORM\Query\AST\Functions;
6+
7+
use Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsJsons;
8+
use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonScalar;
9+
10+
class JsonScalarTest extends TestCase
11+
{
12+
protected function getStringFunctions(): array
13+
{
14+
return [
15+
'JSON_SCALAR' => JsonScalar::class,
16+
];
17+
}
18+
19+
protected function getExpectedSqlStatements(): array
20+
{
21+
return [
22+
'SELECT json_scalar(c0_.object1) AS sclr_0 FROM ContainsJsons c0_',
23+
];
24+
}
25+
26+
protected function getDqlStatements(): array
27+
{
28+
return [
29+
\sprintf('SELECT JSON_SCALAR(e.object1) FROM %s e', ContainsJsons::class),
30+
];
31+
}
32+
}
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tests\MartinGeorgiev\Doctrine\ORM\Query\AST\Functions;
6+
7+
use Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsJsons;
8+
use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonSerialize;
9+
10+
class JsonSerializeTest extends TestCase
11+
{
12+
protected function getStringFunctions(): array
13+
{
14+
return [
15+
'JSON_SERIALIZE' => JsonSerialize::class,
16+
];
17+
}
18+
19+
protected function getExpectedSqlStatements(): array
20+
{
21+
return [
22+
// Basic serialization
23+
'SELECT json_serialize(c0_.object1) AS sclr_0 FROM ContainsJsons c0_',
24+
// With expression
25+
'SELECT json_serialize(UPPER(c0_.object1)) AS sclr_0 FROM ContainsJsons c0_',
26+
// With literal
27+
"SELECT json_serialize('{\"key\": \"value\"}') AS sclr_0 FROM ContainsJsons c0_",
28+
];
29+
}
30+
31+
protected function getDqlStatements(): array
32+
{
33+
return [
34+
\sprintf('SELECT JSON_SERIALIZE(e.object1) FROM %s e', ContainsJsons::class),
35+
\sprintf('SELECT JSON_SERIALIZE(UPPER(e.object1)) FROM %s e', ContainsJsons::class),
36+
\sprintf("SELECT JSON_SERIALIZE('{\"key\": \"value\"}') FROM %s e", ContainsJsons::class),
37+
];
38+
}
39+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Tests\MartinGeorgiev\Doctrine\ORM\Query\AST\Functions;
6+
7+
use Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsJsons;
8+
use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonValue;
9+
10+
class JsonValueTest extends TestCase
11+
{
12+
protected function getStringFunctions(): array
13+
{
14+
return [
15+
'JSON_VALUE' => JsonValue::class,
16+
];
17+
}
18+
19+
protected function getExpectedSqlStatements(): array
20+
{
21+
return [
22+
"SELECT json_value(c0_.object1, '$.name') AS sclr_0 FROM ContainsJsons c0_",
23+
"SELECT json_value(c0_.object1, '$.address.city') AS sclr_0 FROM ContainsJsons c0_",
24+
"SELECT json_value(c0_.object1, '$.items[0]') AS sclr_0 FROM ContainsJsons c0_",
25+
];
26+
}
27+
28+
protected function getDqlStatements(): array
29+
{
30+
return [
31+
\sprintf("SELECT JSON_VALUE(e.object1, '$.name') FROM %s e", ContainsJsons::class),
32+
\sprintf("SELECT JSON_VALUE(e.object1, '$.address.city') FROM %s e", ContainsJsons::class),
33+
\sprintf("SELECT JSON_VALUE(e.object1, '$.items[0]') FROM %s e", ContainsJsons::class),
34+
];
35+
}
36+
}

0 commit comments

Comments
 (0)