前面的链接:
上一节中我们讲到了查询条件中的逻辑表达式节点:LogicNode。逻辑符写链接的是什么呢?
除了not有可能与常量结合,其它的都是谓词表达式。而谓词表达式有一堆运算符。同样,我们将这些定义也放在Symbols类中了。
/**
* @var array
*/
public static $operators = [
'eq' => '=',
'ne' => '<>',
'gt' => '>',
'ge' => '>=',
'lt' => '<',
'le' => '<=',
'is' => 'is',
'in' => 'in',
'out' => 'not in',
'like' => 'like',
'between' => 'between',
'contains' => 'contains'
];
复制代码
以上就是所有的谓词操作符。数组中的Key,就是RQL的函数名,可以说,真的相当简单。
比如,eq(a,b) 就是 a = b 。
那么我们来看看谓词节点的类:
declare(strict_types=1);
/*
* This file is part of the ByteFerry/Rql-Parser package.
*
* (c) BardoQi <67158925@qq.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace ByteFerry\RqlParser\AstBuilder;
use ByteFerry\RqlParser\Lexer\Symbols;
use ByteFerry\RqlParser\Exceptions\ParseException;
/**
* Class PredicateNode
*
* @package ByteFerry\RqlParser\Ast
*/
class PredicateNode extends AstNode implements NodeInterface
{
/**
* @return mixed
* 这里是对 between谓词的特殊操作
*/
public function between(){
[$a, $b, $c] = $this->stage;
$paramaterRegister =ParamaterRegister::getInstance();
$paramaterRegister->add($a . '_from',$b);
$paramaterRegister->add($a . '_to',$c); // 我们拆成了两个变量, xx_from xx_to
$this->output[0] = sprintf(' %s BETWEEN %s and %s ', $a, $b, $c);
return $this->output[0];
}
/**
* @return mixed
*/
public function build(){
$this->buildChildren();
$operator = Symbols::$operators[$this->operator]??null;
$paramaterRegister =ParamaterRegister::getInstance();
/**
* for extend other predicate
* 目前会调用between,但未来想增加也是可以的
*/
if(method_exists($this,$operator)){
return $this->$operator();
}
[$a, $b] = $this->stage; //取出预编译的结果
if(null === $operator){
throw new ParseException('The operators ' . $this->symbol . ' is not defined in the parser! ');
}
$paramaterRegister->add($a,$b); // 添加变量到注册表
$this->output[0] = sprintf(' %s %s %s ', $a,$operator,$b); // 格式化成 a 谓词 b的形式。
return $this->output[0];
}
}
复制代码
那么谓词节点的子节点是什么呢? 常量节点
常量,实际上是基本不用编译的,常量有以下几类,变量名(字段名),常量值,还有特殊常量。我们来看源码:
declare(strict_types=1);
/*
* This file is part of the ByteFerry/Rql-Parser package.
*
* (c) BardoQi <67158925@qq.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace ByteFerry\RqlParser\AstBuilder;
use ByteFerry\RqlParser\Lexer\ListLexer;
/**
* Class ValueNode
*
* @package ByteFerry\RqlParser\AstBuilder
*/
class ConstantNode extends AstNode implements NodeInterface
{
/**
* @return int
*/
protected function true(){
return 1;
}
/**
* @return int
*/
protected function false(){
return 0;
}
/**
* @return string
*/
protected function null(){
return 'null';
}
/**
* @return string
*/
protected function empty(){
return '""';
}
/**
* @param \ByteFerry\RqlParser\Lexer\ListLexer $ListLexer
*
* @return int|void
*/
public function load(ListLexer $ListLexer){
return $ListLexer->getNextIndex(); //直接跳到下一个token
}
/**
* @return mixed
*/
public function build(){
$symbol = $this->symbol;
if(method_exists($this,$symbol)){
$this->output[0] = $this->$symbol();
return $this->output[0];
}
$this->output[0] = $symbol;
return $this->output[0];
}
}
复制代码
可以看出,这里定义了true(), false(), null(), empty() ,其它都是原样返回
谓词操作符中,有in和out,它们的第二个参数都是数组。比如:
in(age,(13,25,37))
这一情况下,我们需要有一个数组节点。代码如下:
declare(strict_types=1);
/*
* This file is part of the ByteFerry/Rql-Parser package.
*
* (c) BardoQi <67158925@qq.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace ByteFerry\RqlParser\AstBuilder;
/**
* Class ArrayNode
*
* @package ByteFerry\RqlParser\AstBuilder
*/
class ArrayNode extends AstNode implements NodeInterface
{
/**
* @return mixed
*/
public function build(){
$this->buildChildren();
$this->output[0] = ' (' . implode(', ', $this->stage) . ') ';
return $this->output[0];
}
}
复制代码
可以看出,它就是把子节点再变回在括号内的逗号分隔字符串。
前面都是些基本查询,对于聚合查询,那一定有一些聚合函数。对于聚合函数,仍是按原样返回。请看代码:
declare(strict_types=1);
/*
* This file is part of the ByteFerry/Rql-Parser package.
*
* (c) BardoQi <67158925@qq.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace ByteFerry\RqlParser\AstBuilder;
/**
* Class AggregateNode
*
* @package ByteFerry\RqlParser\AstBuilder
*/
class AggregateNode extends AstNode implements NodeInterface
{
/**
* @return mixed
*/
public function build(){
$this->buildChildren();
$this->output[0] = $this->operator . '(' . $this->stage[0]. ')';
return $this->output[0];
}
}
复制代码
至于有哪些聚合函数,我们在文档中有说明,请看文档:
byteferry.github.io/rql-parser/…
到此,我们还剩下4个节点,
- 用作排序的,SortNode,
- 用于分页的, LimitNode,
- 用作搜索的:SearchNode,
- 用于写操作的:DataNode,
排序节点:RQL示例:sort(+age, -score)
这是指,age顺序,score倒序。可见,RQL相当简洁。
以下是代码
/*
* This file is part of the ByteFerry/Rql-Parser package.
*
* (c) BardoQi <67158925@qq.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace ByteFerry\RqlParser\AstBuilder;
use ByteFerry\RqlParser\Lexer\Symbols;
use ByteFerry\RqlParser\Lexer\ListLexer;
/**
* Class SortNode
*
* @package ByteFerry\RqlParser\Ast
*/
class SortNode extends AstNode implements NodeInterface
{
/**
* @return string
*/
public function getNodeType()
{
return 'sort';
}
/**
* @param \ByteFerry\RqlParser\Lexer\ListLexer $ListLexer
* 此函数是提供给load函数调用,返回要排序的哪个属性(列),以及方向。
* @return \Generator|void
*/
protected function getArguments(ListLexer $ListLexer){
$token = $ListLexer->consume(); // 先消费一个token
// the sort node will end with ')'
// we only need consume the word token
while(!$token->isClose()){
$property = $token->getSymbol();
$direction = 'ASC'; //默认给定 ASC
// with prev type we could know the direction.
if($token->getPrevType() === Symbols::T_MINUS){ 前一个类型如果是T_MINUS,则就是DESC
$direction = 'DESC';
}
yield [$property, $direction]; ,返回一个控代器给LOAD
$token = $ListLexer->consume();
}
}
/**
* @param \ByteFerry\RqlParser\Lexer\ListLexer $ListLexer
*
* @return int|void
*/
public function load(ListLexer $ListLexer){
/**
* set resource_name value
* 这里只负责写入 stage
*/
foreach($this->getArguments($ListLexer) as $argument){
$this->stage[] = $argument;
}
// return the $index, perhaps there are other queries still.
return $ListLexer->getNextIndex();
}
/**
* @return mixed
*/
public function build(){
$this->output = [$this->getNodeType() => $this->stage];
return $this->output;
}
}
复制代码
可见,排序重载了load函数,这里又一次看到,Token中保存上一个类型,下一个类型的方便性了。
分页节点 LimitNode,
declare(strict_types=1);
/*
* This file is part of the ByteFerry/Rql-Parser package.
*
* (c) BardoQi <67158925@qq.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace ByteFerry\RqlParser\AstBuilder;
/**
* Class LimitNode
*
* @package ByteFerry\RqlParser\Ast
*/
class LimitNode extends AstNode implements NodeInterface
{
/**
* @return mixed
*/
public function build(){
$this->buildChildren();
$this->output = ['limit'=>[$this->stage[0],$this->stage[1]??0]] ;
return $this->output;
}
}
复制代码
分页本身也就上一到两个数字子节点,是常时子节点。至于这两个数字是offser和perPage还是page和perPage,我们不管,我们只把limit交出去就行。这样的好处是,自由度高一些。
搜索节点:SearchNode, 为什么要这个节点呢?因为,有时搜索是在多个字段中同时找的。所以,用SearchNode,单独传查询参数是一个好想法。同样,像Laravel框架中一些插件,可以让你定义Searchable,即哪些字段要查询。这就大大减少了代码量。
/*
* This file is part of the ByteFerry/Rql-Parser package.
*
* (c) BardoQi <67158925@qq.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
declare(strict_types=1);
namespace ByteFerry\RqlParser\AstBuilder;
/**
* Class SearchNode
*
* @package ByteFerry\RqlParser\Ast
*/
class SearchNode extends AstNode implements NodeInterface
{
/**
* @return string
*/
public function getNodeType()
{
return 'search';
}
/**
* @return mixed
*/
public function build(){
$this->buildChildren();
$query = trim($this->stage[0],'\'"'); //常量节点会返回带引号的值,所以,变量名去掉引号
if(trim($query,'%')===$query){
$query .='%'; //没有通配符的,也加上
}
$this->output = [$this->getNodeType() =>$query];
return $this->output;
}
}
复制代码
写操作用的数据节点,DataNode,目前流行的RQL并没有写操作,当然,我现在也不清楚oracle的RQL是否支持写操作。数据节点的表示方式示例:data(age:12,score:89)
declare(strict_types=1);
/*
* This file is part of the ByteFerry/Rql-Parser package.
*
* (c) BardoQi <67158925@qq.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace ByteFerry\RqlParser\AstBuilder;
/**
* Class DataNode
*
* @package ByteFerry\RqlParser\Ast
*/
class DataNode extends AstNode implements nodeInterface
{
/**
* @return mixed
*/
public function build(){
$this->buildChildren();
foreach($this->pair() as $item){ //使用父类的pair迭代器,一次取两个
[$property, $value] = $item;
$this->output[$property] = trim($value,'""'); //写成数组
}
return ['data' => $this->output];
}
}
复制代码
到目前为此,抽象语法树中的节点类我们都讲解完了。再复习一下程序的流程
1、拿到字符串以后,先用Lexer将其变成Token数组,放到ListLexer中,然后,Parser通过NodeVisitor创建节点,调用Load方法,实现数据加载为抽象语法树,同时也完成预编译。
接下来调用顶层build方法,完成编译,获得结果。build是深度优先的。所以,一定是最先编译叶节点,逐步向上返回。
下一节,我们要讲一些辅助的类,以及关于代码的单元测试了。(待续)
继续阅读: