我在mysql中有一个JSON列。我正在尝试使用它的一个属性作为比较值进行查询。
我尝试做的查询在mysql:中看起来是这样的
SELECT *
FROM event
WHERE body->"$.item.id" = 651;
我的问题是,学说没有理解这一点。所以我决定使用一个自定义函数来扩展它:
扩展看起来像这样:
namespace AppDoctrineExtensions;
use DoctrineORMQueryASTFunctionsFunctionNode;
use DoctrineORMQueryLexer;
use DoctrineORMQueryParser;
use DoctrineORMQueryQueryException;
use DoctrineORMQuerySqlWalker;
class JsonSearch extends FunctionNode
{
private string $column;
private string $fieldPath;
private string $operand;
private string $operator;
/**
* @param Parser $parser
* @throws QueryException
*/
public function parse(Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$this->column = $parser->StringPrimary();
$parser->match(Lexer::T_COMMA);
$this->fieldPath = $parser->StringPrimary();
// TODO: $this->fieldPath must mutch an [a-z]. repeat pattern, no other chars to avoid injections.
$parser->match(Lexer::T_COMMA);
$this->operator = $parser->ComparisonOperator();
$parser->match(Lexer::T_COMMA);
$this->operand = $parser->InputParameter();
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
}
public function getSql(SqlWalker $sqlWalker)
{
return '(' . $this->column . '->"$.' . $this->fieldPath . '"' . $this->operator . $this->operand . ')';
}
}
我希望这个电话看起来像这样:
$queryBuilder = $this->em->createQueryBuilder();
$queryBuilder->select('event');
$queryBuilder->from(Event::class, 'event');
$queryBuilder->where('JSONSEARCH(event.body, item.id, =, :value) = 1');
$queryBuilder->setParameter(':value', $value); // $value = 651
但我有很多问题。首先,它将项视为某个事物的别名,并且不绑定值。我假设它不应该是StringPrimary
,但我找不到任何匹配的内容。我想在那里绑一根简单的绳子。没有别名,没有任何花哨的东西。一根简单的绳子,就像它来的一样。很明显,我有责任确保不可能进行注入(TODO部分(,但首先我需要让它发挥作用。
在不使用本机查询的情况下如何做到这一点?
由于我觉得这是一个会吸引答案或评论的问题,比如";你为什么要这么做"为什么对jsons使用mysql而不使用mongo/其他东西&";,等等:
- 我知道mysql-jsons的低性能->不是问题
- 我知道我需要处理安全问题->在这种情况下并不复杂
- 我知道有更好的系统用于未规范化的数据->对于单个边缘的情况,这将不必要地使整个系统复杂化
- 我知道我可以使用本机查询->我宁愿违约,也不愿意作为最后手段
我发现了这个问题。这是因为我没有打电话给调度($sqlWalker(。之所以会发生这种情况,是因为我将提示的属性键入为字符串,并且将值半错误地转换为字符串(公平地说,原则代码拼写错误;如果您进入StringPrimary函数并检查返回类型,字符串会先出现,所以我认为它会直接返回字符串(。这些值实际上是在某个点上扩展Doctrine\ORM\Query\AST\Node的对象。
这允许我将路径绑定为param(仍然很烦人,我需要这样做,但…可能更糟(。
我最终也实现了JsonExtract以满足我的需求,但JsonSearch应该是类似的。下面是代码,以防有人遇到类似的需求。也许我会创建一个包含所有json函数的lib。。。它们似乎非常稀少。窗口函数更稀缺。。。不像他们已经存在很久了:(。
<?php
namespace AppDoctrineExtensions;
use DoctrineORMQueryASTASTException;
use DoctrineORMQueryASTFunctionsFunctionNode;
use DoctrineORMQueryASTNode;
use DoctrineORMQueryLexer;
use DoctrineORMQueryParser;
use DoctrineORMQueryQueryException;
use DoctrineORMQuerySqlWalker;
class JsonExtract extends FunctionNode
{
private Node $field;
private Node $jsonPath;
/**
* @param Parser $parser
* @throws QueryException
*/
public function parse(Parser $parser)
{
$parser->match(Lexer::T_IDENTIFIER);
$parser->match(Lexer::T_OPEN_PARENTHESIS);
$this->field = $parser->StringPrimary();
$parser->match(Lexer::T_COMMA);
$this->jsonPath = $parser->StringPrimary();
$parser->match(Lexer::T_CLOSE_PARENTHESIS);
}
/**
* @param SqlWalker $sqlWalker
* @return string
* @throws ASTException
*/
public function getSql(SqlWalker $sqlWalker)
{
return 'JSON_EXTRACT(' . $this->field->dispatch($sqlWalker) . ',' . $this->jsonPath->dispatch($sqlWalker) . ')';
}
}