Skip to content

Commit 2605f5a

Browse files
feat: add limited support for json_build_object and jsonb_build_object (#268)
1 parent 7c64742 commit 2605f5a

File tree

9 files changed

+142
-1
lines changed

9 files changed

+142
-1
lines changed

docs/AVAILABLE-FUNCTIONS-AND-OPERATORS.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
| int8range | INT8RANGE | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Int8range` |
5151
| json_agg | JSON_AGG | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonAgg` |
5252
| json_array_length | JSON_ARRAY_LENGTH | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonArrayLength` |
53+
| json_build_object | JSON_BUILD_OBJECT | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonBuildObject` |
5354
| json_each | JSON_EACH | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonEach` |
5455
| json_each_text | JSON_EACH_TEXT | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonEachText` |
5556
| json_object_agg | JSON_OBJECT_AGG | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonObjectAgg` |
@@ -60,6 +61,7 @@
6061
| jsonb_agg | JSONB_AGG | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbAgg` |
6162
| jsonb_array_elements_text | JSONB_ARRAY_ELEMENTS_TEXT | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbArrayElementsText` |
6263
| jsonb_array_length | JSONB_ARRAY_LENGTH | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbArrayLength` |
64+
| jsonb_build_object | JSONB_BUILD_OBJECT | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbBuildObject` |
6365
| jsonb_each | JSONB_EACH | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbEach` |
6466
| jsonb_each_text | JSONB_EACH_TEXT | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbEachText` |
6567
| jsonb_exists | JSONB_EXISTS | `MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbExists` |

docs/INTEGRATING-WITH-DOCTRINE.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,10 @@ use Doctrine\ORM\Configuration;
3232

3333
$configuration = new Configuration();
3434

35+
# Register json functions
36+
$configuration->addCustomStringFunction('JSON_BUILD_OBJECT', MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonBuildObject::class);
37+
$configuration->addCustomStringFunction('JSONB_BUILD_OBJECT', MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbBuildObject::class);
38+
3539
# Register text search functions
3640
$configuration->addCustomStringFunction('TO_TSQUERY', MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ToTsquery::class);
3741
$configuration->addCustomStringFunction('TO_TSVECTOR', MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ToTsvector::class);

docs/INTEGRATING-WITH-LARAVEL.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,9 @@ return [
108108
# json specific functions
109109
'JSON_AGG' => MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonAgg::class,
110110
'JSON_ARRAY_LENGTH' => MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonArrayLength::class,
111+
'JSON_BUILD_OBJECT' => MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonBuildObject::class,
112+
'JSON_EACH' => MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonEach::class,
113+
'JSON_EACH_TEXT' => MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonEachText::class,
111114
'JSON_GET_FIELD' => MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonGetField::class,
112115
'JSON_GET_FIELD_AS_TEXT' => MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonGetFieldAsText::class,
113116
'JSON_GET_FIELD_AS_INTEGER' => MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonGetFieldAsInteger::class,
@@ -124,6 +127,7 @@ return [
124127
'JSONB_ARRAY_ELEMENTS' => MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbArrayElements::class,
125128
'JSONB_ARRAY_ELEMENTS_TEXT' => MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbArrayElementsText::class,
126129
'JSONB_ARRAY_LENGTH' => MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbArrayLength::class,
130+
'JSONB_BUILD_OBJECT' => MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbBuildObject::class,
127131
'JSONB_EACH' => MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbEach::class,
128132
'JSONB_EACH_TEXT' => MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbEachText::class,
129133
'JSONB_EXISTS' => MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbExists::class,

docs/INTEGRATING-WITH-SYMFONY.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,7 @@ doctrine:
101101
# json specific functions
102102
JSON_AGG: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonAgg
103103
JSON_ARRAY_LENGTH: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonArrayLength
104+
JSON_BUILD_OBJECT: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonBuildObject
104105
JSON_EACH: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonEach
105106
JSON_EACH_TEXT: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonEachText
106107
JSON_GET_FIELD: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonGetField
@@ -120,6 +121,7 @@ doctrine:
120121
JSONB_ARRAY_ELEMENTS: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbArrayElements
121122
JSONB_ARRAY_ELEMENTS_TEXT: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbArrayElementsText
122123
JSONB_ARRAY_LENGTH: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbArrayLength
124+
JSONB_BUILD_OBJECT: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbBuildObject
123125
JSONB_EACH: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbEach
124126
JSONB_EACH_TEXT: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbEachText
125127
JSONB_EXISTS: MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbExists

docs/USE-CASES-AND-EXAMPLES.md

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,28 @@ FROM EmailEntity e
1111
WHERE e.subject ILIKE 'Test email'
1212
```
1313

14-
Boolean expression that will parse and is equavelnt to teh above DQL:
14+
Boolean expression that will parse and is equivalent to the above DQL:
1515
```sql
1616
SELECT e
1717
FROM EmailEntity e
1818
WHERE ILIKE(e.subject, 'Test email') = TRUE
1919
```
20+
21+
Using JSON_BUILD_OBJECT and JSONB_BUILD_OBJECT
22+
---
23+
24+
These functions currently only support string literals and object references as arguments. Here are some valid examples:
25+
26+
```sql
27+
-- Basic usage with string literals and entity properties
28+
SELECT JSON_BUILD_OBJECT('name', e.userName, 'email', e.userEmail) FROM User e
29+
30+
-- Multiple key-value pairs
31+
SELECT JSONB_BUILD_OBJECT('id', e.id, 'status', 'active', 'type', e.userType) FROM Employee e
32+
33+
-- Invalid usage (will not work):
34+
SELECT JSON_BUILD_OBJECT('count', COUNT(*)) -- Aggregate functions not supported
35+
SELECT JSONB_BUILD_OBJECT('number', 123) -- All number types, NULL and boolean values not supported currently
36+
```
37+
38+
Note: Keys must always be string literals, while values can be either string literals or object property references.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace MartinGeorgiev\Doctrine\ORM\Query\AST\Functions;
6+
7+
/**
8+
* Implementation of PostgreSql JSON_BUILD_OBJECT().
9+
*
10+
* @see https://www.postgresql.org/docs/17/functions-json.html
11+
* @since 2.9.0
12+
*/
13+
class JsonBuildObject extends BaseVariadicFunction
14+
{
15+
protected function customiseFunction(): void
16+
{
17+
$this->setFunctionPrototype('json_build_object(%s)');
18+
}
19+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace MartinGeorgiev\Doctrine\ORM\Query\AST\Functions;
6+
7+
/**
8+
* Implementation of PostgreSql JSONB_BUILD_OBJECT().
9+
*
10+
* @see https://www.postgresql.org/docs/17/functions-json.html
11+
* @since 2.9.0
12+
*/
13+
class JsonbBuildObject extends BaseVariadicFunction
14+
{
15+
protected function customiseFunction(): void
16+
{
17+
$this->setFunctionPrototype('jsonb_build_object(%s)');
18+
}
19+
}
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\JsonBuildObject;
9+
10+
class JsonBuildObjectTest extends TestCase
11+
{
12+
protected function getStringFunctions(): array
13+
{
14+
return [
15+
'JSON_BUILD_OBJECT' => JsonBuildObject::class,
16+
];
17+
}
18+
19+
protected function getExpectedSqlStatements(): array
20+
{
21+
return [
22+
"SELECT json_build_object('key1', c0_.object1) AS sclr_0 FROM ContainsJsons c0_",
23+
"SELECT json_build_object('key1', UPPER('value1'), 'key2', 'value2') AS sclr_0 FROM ContainsJsons c0_",
24+
"SELECT json_build_object('key1', c0_.object1, 'key2', c0_.object2) AS sclr_0 FROM ContainsJsons c0_",
25+
];
26+
}
27+
28+
protected function getDqlStatements(): array
29+
{
30+
return [
31+
\sprintf("SELECT JSON_BUILD_OBJECT('key1', e.object1) FROM %s e", ContainsJsons::class),
32+
\sprintf("SELECT JSON_BUILD_OBJECT('key1', UPPER('value1'), 'key2', 'value2') FROM %s e", ContainsJsons::class),
33+
\sprintf("SELECT JSON_BUILD_OBJECT('key1', e.object1, 'key2', e.object2) FROM %s e", ContainsJsons::class),
34+
];
35+
}
36+
}
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\JsonbBuildObject;
9+
10+
class JsonbBuildObjectTest extends TestCase
11+
{
12+
protected function getStringFunctions(): array
13+
{
14+
return [
15+
'JSONB_BUILD_OBJECT' => JsonbBuildObject::class,
16+
];
17+
}
18+
19+
protected function getExpectedSqlStatements(): array
20+
{
21+
return [
22+
"SELECT jsonb_build_object('key1', c0_.object1) AS sclr_0 FROM ContainsJsons c0_",
23+
"SELECT jsonb_build_object('key1', UPPER('value1'), 'key2', 'value2') AS sclr_0 FROM ContainsJsons c0_",
24+
"SELECT jsonb_build_object('key1', c0_.object1, 'key2', c0_.object2) AS sclr_0 FROM ContainsJsons c0_",
25+
];
26+
}
27+
28+
protected function getDqlStatements(): array
29+
{
30+
return [
31+
\sprintf("SELECT JSONB_BUILD_OBJECT('key1', e.object1) FROM %s e", ContainsJsons::class),
32+
\sprintf("SELECT JSONB_BUILD_OBJECT('key1', UPPER('value1'), 'key2', 'value2') FROM %s e", ContainsJsons::class),
33+
\sprintf("SELECT JSONB_BUILD_OBJECT('key1', e.object1, 'key2', e.object2) FROM %s e", ContainsJsons::class),
34+
];
35+
}
36+
}

0 commit comments

Comments
 (0)