Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,43 @@

namespace MartinGeorgiev\Doctrine\ORM\Query\AST\Functions;

use Doctrine\ORM\Query\AST\Node;
use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Exception\InvalidArgumentForVariadicFunctionException;
use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Traits\BooleanValidationTrait;

/**
* Implementation of PostgreSQL ARRAY_TO_JSON().
*
* @see https://www.postgresql.org/docs/9.6/static/functions-json.html
* Returns the array as a JSON array. A PostgreSQL multidimensional array becomes a JSON array of arrays.
* Line feeds will be added between dimension-1 elements if pretty_bool is true.
*
* @see https://www.postgresql.org/docs/16/functions-json.html
* @since 0.10
*
* @author Martin Georgiev <martin.georgiev@gmail.com>
*
* @example Using it in DQL: "SELECT ARRAY_TO_JSON(e.array1) FROM Entity e"
* @example Using it in DQL with pretty_bool: "SELECT ARRAY_TO_JSON(e.array1, 'true') FROM Entity e"
*/
class ArrayToJson extends BaseFunction
class ArrayToJson extends BaseVariadicFunction
{
use BooleanValidationTrait;

protected function customizeFunction(): void
{
$this->setFunctionPrototype('array_to_json(%s)');
$this->addNodeMapping('StringPrimary');
}

protected function validateArguments(Node ...$arguments): void
{
$argumentCount = \count($arguments);
if ($argumentCount < 1 || $argumentCount > 2) {
throw InvalidArgumentForVariadicFunctionException::between('array_to_json', 1, 2);
}

// Validate that the second parameter is a valid boolean if provided
if ($argumentCount === 2) {
$this->validateBoolean($arguments[1], 'ARRAY_TO_JSON');
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,43 @@

namespace MartinGeorgiev\Doctrine\ORM\Query\AST\Functions;

use Doctrine\ORM\Query\AST\Node;
use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Exception\InvalidArgumentForVariadicFunctionException;
use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Traits\BooleanValidationTrait;

/**
* Implementation of PostgreSQL JSONB_INSERT().
*
* @see https://www.postgresql.org/docs/9.6/static/functions-array.html
* Inserts a new value into a JSONB field at the specified path.
* If the path already exists, the value is not changed unless the last parameter is true.
*
* @see https://www.postgresql.org/docs/16/functions-json.html
* @since 0.10
*
* @author Martin Georgiev <martin.georgiev@gmail.com>
*
* @example Using it in DQL with path and value: "SELECT JSONB_INSERT(e.jsonbData, '{country}', '{\"iso_3166_a3_code\":\"BGR\"}') FROM Entity e"
* @example Using it in DQL with create_if_missing flag: "SELECT JSONB_INSERT(e.jsonbData, '{country}', '{\"iso_3166_a3_code\":\"BGR\"}', true) FROM Entity e"
*/
class JsonbInsert extends BaseFunction
class JsonbInsert extends BaseVariadicFunction
{
use BooleanValidationTrait;

protected function customizeFunction(): void
{
$this->setFunctionPrototype('jsonb_insert(%s, %s, %s)');
$this->addNodeMapping('StringPrimary');
$this->addNodeMapping('StringPrimary');
$this->addNodeMapping('StringPrimary');
$this->setFunctionPrototype('jsonb_insert(%s)');
}

protected function validateArguments(Node ...$arguments): void
{
$argumentCount = \count($arguments);
if ($argumentCount < 3 || $argumentCount > 4) {
throw InvalidArgumentForVariadicFunctionException::between('jsonb_insert', 3, 4);
}

// Validate that the fourth parameter is a valid boolean if provided
if ($argumentCount === 4) {
$this->validateBoolean($arguments[3], 'JSONB_INSERT');
}
}
}
38 changes: 32 additions & 6 deletions src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/JsonbSet.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,47 @@

namespace MartinGeorgiev\Doctrine\ORM\Query\AST\Functions;

use Doctrine\ORM\Query\AST\Node;
use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Exception\InvalidArgumentForVariadicFunctionException;
use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Traits\BooleanValidationTrait;

/**
* Implementation of PostgreSQL JSONB_SET().
*
* @see https://www.postgresql.org/docs/9.6/static/functions-array.html
* Returns the target jsonb with the section designated by path replaced by the new value,
* or with the new value added if create_missing is true (default is true) and the item
* designated by path does not exist.
*
* As with the path orientated operators, negative integers that appear in path count from the end
* of JSON arrays.
*
* @see https://www.postgresql.org/docs/16/functions-json.html
* @since 0.10
*
* @author Martin Georgiev <martin.georgiev@gmail.com>
*
* @example Using it in DQL with path and value: "SELECT JSONB_SET(e.jsonbData, '{address,city}', '\"Sofia\"') FROM Entity e"
* @example Using it in DQL with create_if_missing flag: "SELECT JSONB_SET(e.jsonbData, '{address,city}', '\"Sofia\"', false) FROM Entity e"
*/
class JsonbSet extends BaseFunction
class JsonbSet extends BaseVariadicFunction
{
use BooleanValidationTrait;

protected function customizeFunction(): void
{
$this->setFunctionPrototype('jsonb_set(%s, %s, %s)');
$this->addNodeMapping('StringPrimary');
$this->addNodeMapping('StringPrimary');
$this->addNodeMapping('StringPrimary');
$this->setFunctionPrototype('jsonb_set(%s)');
}

protected function validateArguments(Node ...$arguments): void
{
$argumentCount = \count($arguments);
if ($argumentCount < 3 || $argumentCount > 4) {
throw InvalidArgumentForVariadicFunctionException::between('jsonb_set', 3, 4);
}

// Validate that the fourth parameter is a valid boolean if provided
if ($argumentCount === 4) {
$this->validateBoolean($arguments[3], 'JSONB_SET');
}
}
}
29 changes: 26 additions & 3 deletions src/MartinGeorgiev/Doctrine/ORM/Query/AST/Functions/RowToJson.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,42 @@

namespace MartinGeorgiev\Doctrine\ORM\Query\AST\Functions;

use Doctrine\ORM\Query\AST\Node;
use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Exception\InvalidArgumentForVariadicFunctionException;
use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Traits\BooleanValidationTrait;

/**
* Implementation of PostgreSQL ROW_TO_JSON().
*
* @see https://www.postgresql.org/docs/9.6/static/functions-json.html
* Returns the row as a JSON object. Line feeds will be added between level-1 elements if pretty_bool is true.
*
* @see https://www.postgresql.org/docs/16/functions-json.html
* @since 0.10
*
* @author Martin Georgiev <martin.georgiev@gmail.com>
*
* @example Using it in DQL: "SELECT ROW_TO_JSON(e.row) FROM Entity e"
* @example Using it in DQL with pretty_bool: "SELECT ROW_TO_JSON(e.row, 'true') FROM Entity e"
*/
class RowToJson extends BaseFunction
class RowToJson extends BaseVariadicFunction
{
use BooleanValidationTrait;

protected function customizeFunction(): void
{
$this->setFunctionPrototype('row_to_json(%s)');
$this->addNodeMapping('StringPrimary');
}

protected function validateArguments(Node ...$arguments): void
{
$argumentCount = \count($arguments);
if ($argumentCount < 1 || $argumentCount > 2) {
throw InvalidArgumentForVariadicFunctionException::between('row_to_json', 1, 2);
}

// Validate that the second parameter is a valid boolean if provided
if ($argumentCount === 2) {
$this->validateBoolean($arguments[1], 'ROW_TO_JSON');
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@

use Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsArrays;
use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\ArrayToJson;
use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Exception\InvalidArgumentForVariadicFunctionException;
use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Exception\InvalidBooleanException;

class ArrayToJsonTest extends TestCase
{
Expand All @@ -20,13 +22,33 @@ protected function getExpectedSqlStatements(): array
{
return [
'converts array to json' => 'SELECT array_to_json(c0_.array1) AS sclr_0 FROM ContainsArrays c0_',
'converts array to json with pretty print' => "SELECT array_to_json(c0_.array1, 'true') AS sclr_0 FROM ContainsArrays c0_",
];
}

protected function getDqlStatements(): array
{
return [
'converts array to json' => \sprintf('SELECT ARRAY_TO_JSON(e.array1) FROM %s e', ContainsArrays::class),
'converts array to json with pretty print' => \sprintf("SELECT ARRAY_TO_JSON(e.array1, 'true') FROM %s e", ContainsArrays::class),
];
}

public function test_invalid_boolean_throws_exception(): void
{
$this->expectException(InvalidBooleanException::class);
$this->expectExceptionMessage('Invalid boolean value "invalid" provided for ARRAY_TO_JSON. Must be "true" or "false".');

$dql = \sprintf("SELECT ARRAY_TO_JSON(e.array1, 'invalid') FROM %s e", ContainsArrays::class);
$this->buildEntityManager()->createQuery($dql)->getSQL();
}

public function test_too_many_arguments_throws_exception(): void
{
$this->expectException(InvalidArgumentForVariadicFunctionException::class);
$this->expectExceptionMessage('array_to_json() requires between 1 and 2 arguments');

$dql = \sprintf("SELECT ARRAY_TO_JSON(e.array1, 'true', 'extra_arg') FROM %s e", ContainsArrays::class);
$this->buildEntityManager()->createQuery($dql)->getSQL();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
namespace Tests\MartinGeorgiev\Doctrine\ORM\Query\AST\Functions;

use Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsJsons;
use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Exception\InvalidArgumentForVariadicFunctionException;
use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Exception\InvalidBooleanException;
use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbInsert;

class JsonbInsertTest extends TestCase
Expand All @@ -19,14 +21,43 @@ protected function getStringFunctions(): array
protected function getExpectedSqlStatements(): array
{
return [
"SELECT jsonb_insert(c0_.object1, '{country}', '{\"iso_3166_a3_code\":\"BGR\"}') AS sclr_0 FROM ContainsJsons c0_",
'basic usage' => "SELECT jsonb_insert(c0_.object1, '{country}', '{\"iso_3166_a3_code\":\"BGR\"}') AS sclr_0 FROM ContainsJsons c0_",
'with create-if-missing parameter' => "SELECT jsonb_insert(c0_.object1, '{country}', '{\"iso_3166_a3_code\":\"BGR\"}', 'true') AS sclr_0 FROM ContainsJsons c0_",
];
}

protected function getDqlStatements(): array
{
return [
\sprintf("SELECT JSONB_INSERT(e.object1, '{country}', '{\"iso_3166_a3_code\":\"BGR\"}') FROM %s e", ContainsJsons::class),
'basic usage' => \sprintf("SELECT JSONB_INSERT(e.object1, '{country}', '{\"iso_3166_a3_code\":\"BGR\"}') FROM %s e", ContainsJsons::class),
'with create-if-missing parameter' => \sprintf("SELECT JSONB_INSERT(e.object1, '{country}', '{\"iso_3166_a3_code\":\"BGR\"}', 'true') FROM %s e", ContainsJsons::class),
];
}

public function test_invalid_boolean_throws_exception(): void
{
$this->expectException(InvalidBooleanException::class);
$this->expectExceptionMessage('Invalid boolean value "invalid" provided for JSONB_INSERT. Must be "true" or "false".');

$dql = \sprintf("SELECT JSONB_INSERT(e.object1, '{country}', '{\"iso_3166_a3_code\":\"bgr\"}', 'invalid') FROM %s e", ContainsJsons::class);
$this->buildEntityManager()->createQuery($dql)->getSQL();
}

public function test_too_few_arguments_throws_exception(): void
{
$this->expectException(InvalidArgumentForVariadicFunctionException::class);
$this->expectExceptionMessage('jsonb_insert() requires between 3 and 4 arguments');

$dql = \sprintf('SELECT JSONB_INSERT(e.object1) FROM %s e', ContainsJsons::class);
$this->buildEntityManager()->createQuery($dql)->getSQL();
}

public function test_too_many_arguments_throws_exception(): void
{
$this->expectException(InvalidArgumentForVariadicFunctionException::class);
$this->expectExceptionMessage('jsonb_insert() requires between 3 and 4 arguments');

$dql = \sprintf("SELECT JSONB_INSERT(e.object1, '{country}', '{\"iso_3166_a3_code\":\"bgr\"}', 'true', 'extra_arg') FROM %s e", ContainsJsons::class);
$this->buildEntityManager()->createQuery($dql)->getSQL();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
namespace Tests\MartinGeorgiev\Doctrine\ORM\Query\AST\Functions;

use Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsJsons;
use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Exception\InvalidArgumentForVariadicFunctionException;
use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Exception\InvalidBooleanException;
use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\JsonbSet;

class JsonbSetTest extends TestCase
Expand All @@ -19,14 +21,43 @@ protected function getStringFunctions(): array
protected function getExpectedSqlStatements(): array
{
return [
"SELECT jsonb_set(c0_.object1, '{country}', '{\"iso_3166_a3_code\":\"BGR\"}') AS sclr_0 FROM ContainsJsons c0_",
'basic usage' => "SELECT jsonb_set(c0_.object1, '{country}', '{\"iso_3166_a3_code\":\"BGR\"}') AS sclr_0 FROM ContainsJsons c0_",
'with create-if-missing parameter' => "SELECT jsonb_set(c0_.object1, '{country}', '{\"iso_3166_a3_code\":\"BGR\"}', 'false') AS sclr_0 FROM ContainsJsons c0_",
];
}

protected function getDqlStatements(): array
{
return [
\sprintf("SELECT JSONB_SET(e.object1, '{country}', '{\"iso_3166_a3_code\":\"BGR\"}') FROM %s e", ContainsJsons::class),
'basic usage' => \sprintf("SELECT JSONB_SET(e.object1, '{country}', '{\"iso_3166_a3_code\":\"BGR\"}') FROM %s e", ContainsJsons::class),
'with create-if-missing parameter' => \sprintf("SELECT JSONB_SET(e.object1, '{country}', '{\"iso_3166_a3_code\":\"BGR\"}', 'false') FROM %s e", ContainsJsons::class),
];
}

public function test_invalid_boolean_throws_exception(): void
{
$this->expectException(InvalidBooleanException::class);
$this->expectExceptionMessage('Invalid boolean value "invalid" provided for JSONB_SET. Must be "true" or "false".');

$dql = \sprintf("SELECT JSONB_SET(e.object1, '{country}', '{\"iso_3166_a3_code\":\"bgr\"}', 'invalid') FROM %s e", ContainsJsons::class);
$this->buildEntityManager()->createQuery($dql)->getSQL();
}

public function test_too_few_arguments_throws_exception(): void
{
$this->expectException(InvalidArgumentForVariadicFunctionException::class);
$this->expectExceptionMessage('jsonb_set() requires between 3 and 4 arguments');

$dql = \sprintf('SELECT JSONB_SET(e.object1) FROM %s e', ContainsJsons::class);
$this->buildEntityManager()->createQuery($dql)->getSQL();
}

public function test_too_many_arguments_throws_exception(): void
{
$this->expectException(InvalidArgumentForVariadicFunctionException::class);
$this->expectExceptionMessage('jsonb_set() requires between 3 and 4 arguments');

$dql = \sprintf("SELECT JSONB_SET(e.object1, '{country}', '{\"iso_3166_a3_code\":\"bgr\"}', 'true', 'extra_arg') FROM %s e", ContainsJsons::class);
$this->buildEntityManager()->createQuery($dql)->getSQL();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
namespace Tests\MartinGeorgiev\Doctrine\ORM\Query\AST\Functions;

use Fixtures\MartinGeorgiev\Doctrine\Entity\ContainsTexts;
use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Exception\InvalidArgumentForVariadicFunctionException;
use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\Exception\InvalidBooleanException;
use MartinGeorgiev\Doctrine\ORM\Query\AST\Functions\RowToJson;

class RowToJsonTest extends TestCase
Expand All @@ -21,6 +23,7 @@ protected function getExpectedSqlStatements(): array
return [
'converts row to json' => 'SELECT row_to_json(c0_.text1) AS sclr_0 FROM ContainsTexts c0_',
'converts row with expression to json' => 'SELECT row_to_json(UPPER(c0_.text1)) AS sclr_0 FROM ContainsTexts c0_',
'converts row to json with pretty print' => "SELECT row_to_json(c0_.text1, 'true') AS sclr_0 FROM ContainsTexts c0_",
];
}

Expand All @@ -29,6 +32,25 @@ protected function getDqlStatements(): array
return [
'converts row to json' => \sprintf('SELECT ROW_TO_JSON(e.text1) FROM %s e', ContainsTexts::class),
'converts row with expression to json' => \sprintf('SELECT ROW_TO_JSON(UPPER(e.text1)) FROM %s e', ContainsTexts::class),
'converts row to json with pretty print' => \sprintf("SELECT ROW_TO_JSON(e.text1, 'true') FROM %s e", ContainsTexts::class),
];
}

public function test_invalid_boolean_throws_exception(): void
{
$this->expectException(InvalidBooleanException::class);
$this->expectExceptionMessage('Invalid boolean value "invalid" provided for ROW_TO_JSON. Must be "true" or "false".');

$dql = \sprintf("SELECT ROW_TO_JSON(e.text1, 'invalid') FROM %s e", ContainsTexts::class);
$this->buildEntityManager()->createQuery($dql)->getSQL();
}

public function test_too_many_arguments_throws_exception(): void
{
$this->expectException(InvalidArgumentForVariadicFunctionException::class);
$this->expectExceptionMessage('row_to_json() requires between 1 and 2 arguments');

$dql = \sprintf("SELECT ROW_TO_JSON(e.text1, 'true', 'extra_arg') FROM %s e", ContainsTexts::class);
$this->buildEntityManager()->createQuery($dql)->getSQL();
}
}
Loading