Skip to content

Commit

Permalink
Add JSON_EXTRACT for MySQL
Browse files Browse the repository at this point in the history
  • Loading branch information
jdecool committed May 2, 2022
1 parent 3d6abcb commit 7447f3e
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 1 deletion.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ MySQL, Oracle, PostgreSQL and SQLite.

| DB | Functions |
|:--:|:---------:|
| MySQL | `ACOS, ADDTIME, AES_DECRYPT, AES_ENCRYPT, ANY_VALUE, ASCII, ASIN, ATAN, ATAN2, BINARY, BIT_COUNT, BIT_XOR, CAST, CEIL, CHAR_LENGTH, COLLATE, CONCAT_WS, CONVERT_TZ, COS, COT, COUNTIF, CRC32, DATE, DATE_FORMAT, DATEADD, DATEDIFF, DATESUB, DAY, DAYNAME, DAYOFWEEK, DAYOFYEAR, DEGREES, DIV, EXP, EXTRACT, FIELD, FIND_IN_SET, FLOOR, FORMAT, FROM_BASE64, FROM_UNIXTIME, GREATEST, GROUP_CONCAT, HEX, HOUR, IFELSE, IFNULL, INET_ATON, INET_NTOA, INET6_ATON, INET6_NTOA, INSTR, IS_IPV4, IS_IPV4_COMPAT, IS_IPV4_MAPPED, IS_IPV6, JSON_CONTAINS, JSON_DEPTH, JSON_LENGTH, LAG, LAST_DAY, LEAD, LEAST, LOG, LOG10, LOG2, LPAD, MAKEDATE, MATCH, MD5, MINUTE, MONTH, MONTHNAME, NOW, NULLIF, OVER, PERIOD_DIFF, PI, POWER, QUARTER, RADIANS, RAND, REGEXP, REPLACE, ROUND, RPAD, SECOND, SECTOTIME, SHA1, SHA2, SIN, SOUNDEX, STD, STDDEV, STRTODATE, STR_TO_DATE, SUBSTRING_INDEX, TAN, TIME, TIMEDIFF, TIMESTAMPADD, TIMESTAMPDIFF, TIMETOSEC, TRUNCATE, UNHEX, UNIX_TIMESTAMP, UTC_TIMESTAMP, UUID_SHORT, VARIANCE, WEEK, WEEKDAY, WEEKOFYEAR, YEAR, YEARMONTH, YEARWEEK` |
| MySQL | `ACOS, ADDTIME, AES_DECRYPT, AES_ENCRYPT, ANY_VALUE, ASCII, ASIN, ATAN, ATAN2, BINARY, BIT_COUNT, BIT_XOR, CAST, CEIL, CHAR_LENGTH, COLLATE, CONCAT_WS, CONVERT_TZ, COS, COT, COUNTIF, CRC32, DATE, DATE_FORMAT, DATEADD, DATEDIFF, DATESUB, DAY, DAYNAME, DAYOFWEEK, DAYOFYEAR, DEGREES, DIV, EXP, EXTRACT, FIELD, FIND_IN_SET, FLOOR, FORMAT, FROM_BASE64, FROM_UNIXTIME, GREATEST, GROUP_CONCAT, HEX, HOUR, IFELSE, IFNULL, INET_ATON, INET_NTOA, INET6_ATON, INET6_NTOA, INSTR, IS_IPV4, IS_IPV4_COMPAT, IS_IPV4_MAPPED, IS_IPV6, JSON_CONTAINS, JSON_DEPTH, JSON_LENGTH, JSON_EXTRACT, LAG, LAST_DAY, LEAD, LEAST, LOG, LOG10, LOG2, LPAD, MAKEDATE, MATCH, MD5, MINUTE, MONTH, MONTHNAME, NOW, NULLIF, OVER, PERIOD_DIFF, PI, POWER, QUARTER, RADIANS, RAND, REGEXP, REPLACE, ROUND, RPAD, SECOND, SECTOTIME, SHA1, SHA2, SIN, SOUNDEX, STD, STDDEV, STRTODATE, STR_TO_DATE, SUBSTRING_INDEX, TAN, TIME, TIMEDIFF, TIMESTAMPADD, TIMESTAMPDIFF, TIMETOSEC, TRUNCATE, UNHEX, UNIX_TIMESTAMP, UTC_TIMESTAMP, UUID_SHORT, VARIANCE, WEEK, WEEKDAY, WEEKOFYEAR, YEAR, YEARMONTH, YEARWEEK` |
| Oracle | `CEIL, DAY, FLOOR, HOUR, LISTAGG, MINUTE, MONTH, NVL, SECOND, TO_CHAR, TO_DATE, TRUNC, YEAR` |
| SQLite | `CASE WHEN THEN ELSE END, DATE, DATE_FORMAT*, DAY, HOUR, IFNULL, JULIANDAY, MINUTE, MONTH, REPLACE, ROUND, SECOND, STRFTIME, WEEK, WEEKDAY, YEAR` |
| PostgreSQL | `AT_TIME_ZONE, COUNT_FILTER, DATE, DATE_PART, DATE_TRUNC, DAY, EXTRACT, GREATEST, HOUR, LEAST, MINUTE, MONTH, REGEXP_REPLACE, SECOND, STRING_AGG, TO_CHAR, TO_DATE, YEAR` |
Expand Down
1 change: 1 addition & 0 deletions config/mysql.yml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ doctrine:
floor: DoctrineExtensions\Query\Mysql\Floor
json_contains: DoctrineExtensions\Query\Mysql\JsonContains
json_depth: DoctrineExtensions\Query\Mysql\JsonDepth
json_extract: DoctrineExtensions\Query\Mysql\JsonExtract
json_length: DoctrineExtensions\Query\Mysql\JsonLength
log: DoctrineExtensions\Query\Mysql\Log
log10: DoctrineExtensions\Query\Mysql\Log10
Expand Down
43 changes: 43 additions & 0 deletions src/Query/Mysql/JsonExtract.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
<?php

namespace DoctrineExtensions\Query\Mysql;

use Doctrine\ORM\Query\AST\Functions\FunctionNode;
use Doctrine\ORM\Query\Lexer;
use Doctrine\ORM\Query\Parser;
use Doctrine\ORM\Query\SqlWalker;

class JsonExtract extends FunctionNode
{
protected $target;

protected $paths = [];

public function parse(Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);

$this->target = $parser->StringExpression();

$parser->match(Lexer::T_COMMA);

$this->paths[] = $parser->StringPrimary();

while ($parser->getLexer()->isNextToken(Lexer::T_COMMA)) {
$parser->match(Lexer::T_COMMA);

$this->paths[] = $parser->StringPrimary();
}

$parser->match(Lexer::T_CLOSE_PARENTHESIS);
}

public function getSql(SqlWalker $sqlWalker)
{
$target = $sqlWalker->walkStringPrimary($this->target);
$paths = array_map([$sqlWalker, 'walkStringPrimary'], $this->paths);

return sprintf('JSON_EXTRACT(%s, %s)', $target, implode(', ', $paths));
}
}
33 changes: 33 additions & 0 deletions tests/Query/Mysql/JsonExtractTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

namespace DoctrineExtensions\Tests\Query\Mysql;

use DoctrineExtensions\Tests\Query\MysqlTestCase;

class JsonExtractTest extends MysqlTestCase
{
public function testJsonExtractWithSinglePath(): void
{
$this->assertDqlProducesSql(
"SELECT JSON_EXTRACT('{}', :param) from DoctrineExtensions\Tests\Entities\Blank as blank",
"SELECT JSON_EXTRACT('{}', ?) AS sclr_0 FROM Blank b0_",
['param' => '']
);
}

public function testJsonContainsWithMultiplePaths(): void
{
$this->assertDqlProducesSql(
"SELECT JSON_EXTRACT('{}', :path1, :path2) from DoctrineExtensions\Tests\Entities\Blank as blank",
"SELECT JSON_EXTRACT('{}', ?, ?) AS sclr_0 FROM Blank b0_"
);
}

public function testJsonContainsWithPathAsExplicit(): void
{
$this->assertDqlProducesSql(
"SELECT JSON_EXTRACT('{}', '$[0]') from DoctrineExtensions\Tests\Entities\Blank as blank",
"SELECT JSON_EXTRACT('{}', '$[0]') AS sclr_0 FROM Blank b0_"
);
}
}

0 comments on commit 7447f3e

Please sign in to comment.