Skip to content

Commit 671f86f

Browse files
committed
Added JSON_VALUE for mysql platform
fixes #82
1 parent 9e7752c commit 671f86f

File tree

5 files changed

+134
-1
lines changed

5 files changed

+134
-1
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@
22
/composer.lock
33
/vendor
44
.phpunit.result.cache
5+
.phpunit.cache
56
.phpcs-cache

src/Query/AST/Functions/AbstractJsonFunctionNode.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
abstract class AbstractJsonFunctionNode extends FunctionNode
1818
{
19-
public const FUNCTION_NAME = null;
19+
public const FUNCTION_NAME = '';
2020

2121
protected const ALPHA_NUMERIC = 'alphaNumeric';
2222
protected const STRING_PRIMARY_ARG = 'stringPrimary';
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Scienta\DoctrineJsonFunctions\Query\AST\Functions\Mysql;
6+
7+
use Doctrine\DBAL\Exception;
8+
use Doctrine\ORM\Query\AST\Node;
9+
use Doctrine\ORM\Query\Parser;
10+
use Doctrine\ORM\Query\SqlWalker;
11+
use Doctrine\ORM\Query\TokenType;
12+
13+
use function implode;
14+
use function sprintf;
15+
16+
/**
17+
* "JSON_VALUE" "(" StringPrimary "," StringPrimary "RETURNING" TypeExpression ")"
18+
* @example JSON_VALUE('{"item": "shoes", "price": "49.95"}', '$.price', DECIMAL(4,2))
19+
*/
20+
class JsonValue extends MysqlJsonFunctionNode
21+
{
22+
public const FUNCTION_NAME = 'JSON_VALUE';
23+
24+
/** @var list<Node> */
25+
private array $jsonArguments = [];
26+
27+
private string | null $returningType;
28+
29+
public function parse(Parser $parser): void
30+
{
31+
$parser->match(TokenType::T_IDENTIFIER);
32+
$parser->match(TokenType::T_OPEN_PARENTHESIS);
33+
34+
$this->jsonArguments[] = $parser->StringPrimary();
35+
36+
$parser->match(TokenType::T_COMMA);
37+
38+
$this->jsonArguments[] = $parser->StringPrimary();
39+
40+
$parser->match(TokenType::T_COMMA);
41+
42+
// match complex returning types
43+
$parser->match(TokenType::T_IDENTIFIER);
44+
$this->returningType = $parser->getLexer()->token->value;
45+
46+
if ($parser->getLexer()->isNextToken(TokenType::T_OPEN_PARENTHESIS)) {
47+
$parser->match(TokenType::T_OPEN_PARENTHESIS);
48+
$parameter = $parser->Literal();
49+
$parameters = [$parameter->value];
50+
51+
if ($parser->getLexer()->isNextToken(TokenType::T_COMMA)) {
52+
while ($parser->getLexer()->isNextToken(TokenType::T_COMMA)) {
53+
$parser->match(TokenType::T_COMMA);
54+
$parameter = $parser->Literal();
55+
$parameters[] = $parameter->value;
56+
}
57+
}
58+
59+
$parser->match(TokenType::T_CLOSE_PARENTHESIS);
60+
$this->returningType .= '(' . implode(',', $parameters) . ')';
61+
}
62+
$argumentParsed = $this->parseArguments($parser, $this->requiredArgumentTypes);
63+
64+
if (!empty($this->optionalArgumentTypes)) {
65+
$this->parseOptionalArguments($parser, $argumentParsed);
66+
}
67+
68+
$parser->match(TokenType::T_CLOSE_PARENTHESIS);
69+
}
70+
71+
/**
72+
* @param SqlWalker $sqlWalker
73+
* @return string
74+
* @throws Exception
75+
* @throws \Doctrine\ORM\Query\AST\ASTException
76+
*/
77+
public function getSql(SqlWalker $sqlWalker): string
78+
{
79+
$this->validatePlatform($sqlWalker);
80+
81+
/** @var list<string> $jsonStringArguments */
82+
$jsonStringArguments = [];
83+
foreach ($this->jsonArguments as $jsonArgument) {
84+
if ($jsonArgument === null) {
85+
$jsonStringArguments[] = 'NULL';
86+
} else {
87+
$jsonStringArguments[] = $jsonArgument->dispatch($sqlWalker);
88+
}
89+
}
90+
91+
return sprintf(
92+
'%s(%s RETURNING %s)',
93+
$this->getSQLFunction(),
94+
implode(', ', $jsonStringArguments),
95+
$this->returningType,
96+
);
97+
}
98+
}
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 Query\Functions\Mysql;
6+
7+
use Scienta\DoctrineJsonFunctions\Tests\Query\MysqlTestCase;
8+
9+
class JsonValueTest extends MysqlTestCase
10+
{
11+
public function testJsonValueForData()
12+
{
13+
$this->assertDqlProducesSql(
14+
"SELECT JSON_VALUE(j.jsonData, '$.a', UNSIGNED) FROM Scienta\DoctrineJsonFunctions\Tests\Entities\JsonData j",
15+
"SELECT JSON_VALUE(j0_.jsonData, '$.a' RETURNING UNSIGNED) AS sclr_0 FROM JsonData j0_"
16+
);
17+
}
18+
19+
public function testJsonValueReturningDecimal()
20+
{
21+
$this->assertDqlProducesSql(
22+
"SELECT JSON_VALUE('{\"item\": \"shoes\", \"price\": \"49.95\"}', '$.price', DECIMAL(4,2)) from Scienta\DoctrineJsonFunctions\Tests\Entities\Blank b",
23+
"SELECT JSON_VALUE('{\"item\": \"shoes\", \"price\": \"49.95\"}', '$.price' RETURNING DECIMAL(4,2)) AS sclr_0 FROM Blank b0_"
24+
);
25+
}
26+
public function testJsonValueForDataReturningVarchar()
27+
{
28+
$this->assertDqlProducesSql(
29+
"SELECT JSON_VALUE(j.jsonData, '$.something', CHAR(255)) FROM Scienta\DoctrineJsonFunctions\Tests\Entities\JsonData j",
30+
"SELECT JSON_VALUE(j0_.jsonData, '$.something' RETURNING CHAR(255)) AS sclr_0 FROM JsonData j0_"
31+
);
32+
}
33+
}

tests/Query/MysqlTestCase.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,5 +53,6 @@ public static function loadDqlFunctions(Configuration $configuration)
5353
$configuration->addCustomStringFunction(DqlFunctions\JsonType::FUNCTION_NAME, DqlFunctions\JsonType::class);
5454
$configuration->addCustomStringFunction(DqlFunctions\JsonUnquote::FUNCTION_NAME, DqlFunctions\JsonUnquote::class);
5555
$configuration->addCustomStringFunction(DqlFunctions\JsonValid::FUNCTION_NAME, DqlFunctions\JsonValid::class);
56+
$configuration->addCustomStringFunction(DqlFunctions\JsonValue::FUNCTION_NAME, DqlFunctions\JsonValue::class);
5657
}
5758
}

0 commit comments

Comments
 (0)