@@ -19,16 +19,17 @@ use Exception;
class Query extends Component implements QueryInterface
{
const PARAM_PREFIX = 'qp' ;
const DEBUG = true ;
public $separator = " " ;
protected $conditionBuilders = [
'NOT' => 'build NotCondition' ,
'AND' => 'build AndCondition' ,
'OR' => 'build AndCondition' ,
'IN' => 'build InCondition' ,
'LIKE' => 'build LikeCondition' ,
'BETWEEN' => 'build BetweenCondition' ,
'NOT' => 'gen NotCondition' ,
'AND' => 'gen AndCondition' ,
'OR' => 'gen AndCondition' ,
'IN' => 'gen InCondition' ,
'LIKE' => 'gen LikeCondition' ,
'BETWEEN' => 'gen BetweenCondition'
];
protected $conditionMap = [
@@ -41,9 +42,26 @@ class Query extends Component implements QueryInterface
public $select = [];
public $from ;
public string | array $for ;
public $where ;
public string | array $in ;
public string $collection ;
public array $vars = [];
/**
* Массив коллекций вершин и направлений для их обхода
*
* [
* ["INBOUND" => "collection1"],
* ["OUTBOUND" => "collection2"],
* ["ANY" => "collection2"]
* ]
*/
public array $traversals = [];
public $where = [];
public $limit ;
@@ -103,7 +121,7 @@ class Query extends Component implements QueryInterface
*/
public function createCommand ( $db = null , $options = [])
{
list ( $aql , $params ) = $this -> build Query( $this );
list ( $aql , $params ) = $this -> gen Query( $this );
$options = ArrayHelper :: merge (
$options ,
@@ -162,28 +180,120 @@ class Query extends Component implements QueryInterface
* @param $collection
* @return $this
*/
public function from ( $collection )
public function collection ( string $collection )
{
$this -> from = $collection ;
$this -> collection = $collection ;
return $this ;
}
/**
* @param $collection
* @return string
*/
protected function buildFrom ( $collection )
public function for ( string | array $for )
{
$collection = trim ( $collection ) ;
return $collection ? " FOR $collection IN $collection " : '' ;
$this -> for = $for ;
return $this ;
}
/**
* @param $name
*/
public function in ( string | array $in )
{
$this -> in = $in ;
return $this ;
}
/**
* Обойти коллекцию вершин по направлению
*
* Генерация AQL выражения
*
* @see https://www.arangodb.com/docs/3.7/aql/operations-let.html
*
* @param mixed $vertex Коллекция вершин из которой требуется обход
* @param string $direction Направление ('INBOUND', 'OUTBOUND', 'ANY')
*/
public function traversal ( string $vertex , string $direction = 'INBOUND' ) : static
{
$this -> traversals [] = [
match ( $direction ) {
'INBOUND' , 'OUTBOUND' , 'ANY' => $direction ,
default => null
}
=> $vertex
];
return $this ;
}
/**
* Генерация AQL конструкции "FOR"
*
* Примеры:
* 1. "FOR account"
* 2. "FOR account, account_edge_supply"
*
*/
protected static function genFor ( string | array $for ) : string
{
if ( is_array ( $for )) {
// Если передан массив, то конвертировать в строку
// Очистка элементов через trim()
array_walk ( $for , 'trim' );
// Конвертация
$for = implode ( " , " , $for );
}
// Генерация
return " FOR $for " ;
}
/**
* Генерация AQL конструкции "IN"
*
* Примеры:
* 1. "IN account"
* 2. "IN INBOUND supply account_edge_supply"
*/
protected static function genIn ( string | array | null $in , array $traversals = null ) : string
{
if ( is_array ( $in )) {
// Если передан массив, то конвертировать в строку
// Очистка элементов через trim()
array_walk ( $in , 'trim' );
// Конвертация
$in = implode ( " , " , $in );
}
$expression = '' ;
foreach ( $traversals as $traversal ) {
foreach ( $traversal as $direction => $vertex ) {
if ( $aql = static :: genTraversal ( $direction , $vertex )) {
$expression .= $aql . ', ' ;
}
}
}
$expression = trim ( $expression , ', ' );
// Если сгенерированное выражение не пустое, то добавить пробел
$expression = ! empty ( $expression ) ? $expression . ' ' : null ;
// Генерация
return " IN $expression " . $in ;
}
/**
* @param string|array $name Название
* @return string
*/
public function quoteCollectionName ( $name )
public static function quoteCollectionName ( string | array $name ) : string
{
if ( strpos ( $name , '(' ) !== false || strpos ( $name , '{{' ) !== false ) {
return $name ;
@@ -203,7 +313,7 @@ class Query extends Component implements QueryInterface
* @param $name
* @return string
*/
public function quoteColumnName ( $name )
public function quoteColumnName ( string $name )
{
if ( strpos ( $name , '(' ) !== false || strpos ( $name , '[[' ) !== false || strpos ( $name , '{{' ) !== false ) {
return $name ;
@@ -213,7 +323,7 @@ class Query extends Component implements QueryInterface
$prefix = $this -> quoteCollectionName ( $prefix ) . '.' ;
$name = substr ( $name , $pos + 1 );
} else {
$prefix = $this -> quoteCollectionName ( $this -> from ) . '.' ;
$prefix = $this -> quoteCollectionName ( $this -> collection ) . '.' ;
}
return $prefix . $name ;
@@ -224,19 +334,21 @@ class Query extends Component implements QueryInterface
* @param $params
* @return string
*/
protected function build Where( $condition , & $params )
protected function gen Where( $condition , & $params )
{
$where = $this -> build Condition( $condition , $params );
$where = $this -> gen Condition( $condition , $params );
return $where === '' ? '' : 'FILTER ' . $where ;
return $where ? 'FILTER ' . $where : '' ;
}
/**
* @param $condition
* @param $params
* @return string
*
* @todo Разобраться с этим говном
*/
protected function build Condition( $condition , & $params )
protected function gen Condition( $condition , & $params )
{
if ( ! is_array ( $condition )) {
return ( string ) $condition ;
@@ -254,7 +366,7 @@ class Query extends Component implements QueryInterface
throw new InvalidParamException ( 'Found unknown operator in query: ' . $operator );
}
} else { // hash format: 'column1' => 'value1', 'column2' => 'value2', ...
return $this -> build HashCondition( $condition , $params );
return $this -> gen HashCondition( $condition , $params );
}
}
@@ -264,13 +376,13 @@ class Query extends Component implements QueryInterface
* @return string
* @throws Exception
*/
protected function build HashCondition( $condition , & $params )
protected function gen HashCondition( $condition , & $params )
{
$parts = [];
foreach ( $condition as $column => $value ) {
if ( is_array ( $value ) || $value instanceof Query ) {
// IN condition
$parts [] = $this -> build InCondition( 'IN' , [ $column , $value ], $params );
$parts [] = $this -> gen InCondition( 'IN' , [ $column , $value ], $params );
} else {
if ( strpos ( $column , '(' ) === false ) {
$column = $this -> quoteColumnName ( $column );
@@ -293,12 +405,12 @@ class Query extends Component implements QueryInterface
* @param $params
* @return string
*/
protected function build AndCondition( $operator , $operands , & $params )
protected function gen AndCondition( $operator , $operands , & $params )
{
$parts = [];
foreach ( $operands as $operand ) {
if ( is_array ( $operand )) {
$operand = $this -> build Condition( $operand , $params );
$operand = $this -> gen Condition( $operand , $params );
}
if ( $operand !== '' ) {
$parts [] = $operand ;
@@ -317,7 +429,7 @@ class Query extends Component implements QueryInterface
* @param $params
* @return string
*/
protected function build NotCondition( $operator , $operands , & $params )
protected function gen NotCondition( $operator , $operands , & $params )
{
if ( count ( $operands ) != 1 ) {
throw new InvalidParamException ( " Operator ' $operator ' requires exactly one operand. " );
@@ -325,7 +437,7 @@ class Query extends Component implements QueryInterface
$operand = reset ( $operands );
if ( is_array ( $operand )) {
$operand = $this -> build Condition( $operand , $params );
$operand = $this -> gen Condition( $operand , $params );
}
if ( $operand === '' ) {
return '' ;
@@ -341,7 +453,7 @@ class Query extends Component implements QueryInterface
* @return string
* @throws Exception
*/
protected function build InCondition( $operator , $operands , & $params )
protected function gen InCondition( $operator , $operands , & $params )
{
if ( ! isset ( $operands [ 0 ], $operands [ 1 ])) {
throw new Exception ( " Operator ' $operator ' requires two operands. " );
@@ -355,7 +467,7 @@ class Query extends Component implements QueryInterface
if ( $values instanceof Query ) {
// sub-query
list ( $sql , $params ) = $this -> build Query( $values , $params );
list ( $sql , $params ) = $this -> gen Query( $values , $params );
$column = ( array ) $column ;
if ( is_array ( $column )) {
foreach ( $column as $i => $col ) {
@@ -375,7 +487,7 @@ class Query extends Component implements QueryInterface
$values = ( array ) $values ;
if ( count ( $column ) > 1 ) {
return $this -> build CompositeInCondition( $operator , $column , $values , $params );
return $this -> gen CompositeInCondition( $operator , $column , $values , $params );
}
if ( is_array ( $column )) {
@@ -412,7 +524,7 @@ class Query extends Component implements QueryInterface
* @param $params
* @return string
*/
protected function build CompositeInCondition( $operator , $columns , $values , & $params )
protected function gen CompositeInCondition( $operator , $columns , $values , & $params )
{
$vss = [];
foreach ( $values as $value ) {
@@ -446,7 +558,7 @@ class Query extends Component implements QueryInterface
* @return string the generated AQL expression
* @throws InvalidParamException if wrong number of operands have been given.
*/
public function build BetweenCondition( $operator , $operands , & $params )
public function gen BetweenCondition( $operator , $operands , & $params )
{
if ( ! isset ( $operands [ 0 ], $operands [ 1 ], $operands [ 2 ])) {
throw new InvalidParamException ( " Operator ' $operator ' requires three operands. " );
@@ -471,7 +583,7 @@ class Query extends Component implements QueryInterface
* @param $params
* @return string
*/
protected function build LikeCondition( $operator , $condition , & $params )
protected function gen LikeCondition( $operator , $condition , & $params )
{
if ( ! ( isset ( $condition [ 0 ]) && isset ( $condition [ 1 ]))) {
throw new InvalidParamException ( " You must set 'column' and 'pattern' params " );
@@ -491,7 +603,7 @@ class Query extends Component implements QueryInterface
* @param $columns
* @return string
*/
protected function build OrderBy( $columns )
protected function gen OrderBy( $columns )
{
if ( empty ( $columns )) {
return '' ;
@@ -527,7 +639,7 @@ class Query extends Component implements QueryInterface
* @param $offset
* @return string
*/
protected function build Limit( $limit , $offset )
protected function gen Limit( $limit , $offset )
{
$aql = '' ;
if ( $this -> hasLimit ( $limit )) {
@@ -542,22 +654,17 @@ class Query extends Component implements QueryInterface
* @param $params
* @return string
*/
protected function build Select( $columns , & $params ) // А нахуй здесь params ещё и ссылкой? Потом проверить
protected function gen Select( $columns , & $params ) // А нахуй здесь params ещё и ссылкой? Потом проверить
{
if ( $columns === null || empty ( $columns )) {
return 'RETURN ' . $this -> from ;
return 'RETURN ' . $this -> collection ;
}
if ( ! is_array ( $columns )) {
return 'RETURN ' . $columns ;
}
$names = '' ;
foreach ( $columns as $name => $column ) {
$names .= " \" $name\ " : $this -> from . $column , " ;
}
return 'RETURN { ' . trim( $names , ', ') . '}';
return 'RETURN ' . self :: convertArray2Aql ( $columns , $this -> collection ) ;
}
/**
@@ -565,24 +672,20 @@ class Query extends Component implements QueryInterface
* @param array $params
* @return array
*/
protected function build Query( $query = null, $params = [])
protected function gen Query ( $query = null , $params = [])
{
$query = isset ( $query ) ? $query : $this ;
if ( $query->where === null) {
$where = [];
} else {
$where = $query->where ;
}
$params = empty ( $params ) ? $query -> params : array_merge ( $params , $query -> params );
$clauses = [
$this->buildFrom ( $query->from ),
$this->buildWhere ( $where , $param s ),
$this->buildOrderBy ( $query->orderBy , $param s ),
$this->buildLimit ( $query->limit , $query->offset , $params ),
$this->buildSelect ( $query->select , $params ),
static :: genFor ( $query-> for ? ? $query -> collection ),
static :: genIn ( $query -> in ? ? $query -> collection , $query -> traversal s ),
static :: genLet ( $query-> var s ),
$this-> genWhere ( $query-> where , $params ),
$this-> genOrderBy ( $query-> orderBy , $params ),
$this -> genLimit ( $query -> limit , $query -> offset , $params ),
$this -> genSelect ( $query -> select , $params ),
];
$aql = implode ( $query -> separator , array_filter ( $clauses ));
@@ -622,6 +725,7 @@ class Query extends Component implements QueryInterface
{
$statement = $this->createCommand ( $db );
$token = $this->getRawAql ( $statement );
Yii::info( $token , 'mirzaev \ yii2 \ arangodb \ Query::query');
try {
Yii::beginProfile( $token , 'mirzaev \ yii2 \ arangodb \ Query::query');
@@ -671,7 +775,7 @@ class Query extends Component implements QueryInterface
$clauses = [
" INSERT $doc IN { $this -> quoteCollectionName ( $collection )} " ,
$this->build Options (),
$this->gen Options (),
];
$aql = implode( $this->separator , array_filter( $clauses ));
@@ -708,12 +812,13 @@ class Query extends Component implements QueryInterface
*/
public function update( $collection , $columns , $condition = [], $params = [], $db = null)
{
$this->from ( $collection ) ;
$this->collection = $collection ;
$clauses = [
$this->buildFrom ( $collection ),
$this->buildWhere ( $condition , $params ),
$this->buildUpdat e ( $collec tion , $column s ),
$this->buildOptio ns ( ),
$this->genFor ( $collection ),
$this->genIn ( $collection ),
$this->genWher e ( $condi tion , $param s ),
$this->genUpdate ( $collection , $colum ns ),
$this->genOptions (),
];
$aql = implode( $this->separator , array_filter( $clauses ));
@@ -744,31 +849,33 @@ class Query extends Component implements QueryInterface
}
/**
* @param $collection
* @param $columns
* @param array $condition
* Представление
*
* Работа с представлениями
*
* @see https://www.arangodb.com/docs/3.7/http/views.html
*
* @param string $collection
* @param string|array $vars
* @param array $expression
* @param string $type
* @param array $params
* @param null $db
* @return bool
* @throws Exception
* @param Connection $db
*/
public function search (string|array $collection , string|array $vars , array $expression , int $type = 0, $params = [], $db = null): array
public function view (string $collection , string|array|null $vars = null , array $expression = null, str ing $type = null, array $params = [], $db = null): array
{
$this->from ( $collection ) ;
$this->collection = $collection ;
$clauses = [
$this->buildFrom ( $collection ),
$this->buildSearch ( $expression , $type ),
$this->buildLimit ( $this->limit , 0 ),
$this->buildOptions ( ),
$this->buildSelect ( $vars , $params )
$this->genFor ( $collection ),
$this->genIn ( $collection ),
$this->genSearch ( $expression , $type ),
$this->genLimit ( $this->limit , 0 ),
$this->genOptions (),
$this->genSelect ( $vars , $params )
];
$aql = implode( $this->separator , array_filter( $clauses ));
$fp = fopen('debug.txt', 'a');
fwrite( $fp , print_r( $aql , true) . PHP_EOL);
fclose( $fp );
$params = ArrayHelper::merge(
$params ,
[
@@ -791,6 +898,25 @@ class Query extends Component implements QueryInterface
return $this->prepareResult ( $cursor->getAll ());
}
/**
* Поиск р е б р а
*
* Работа с представлениями
*/
// public function searchEdge(string $collection , string $_from , string $_to , string|array|null $vars = null, string $direction = 'INBOUND', array $expression = null, array $params = [], $db = null)
// {
// $this->collection = $collection ;
// $clauses = [
// $this->genFor ( $_from ),
// $this->genLet ( $collection , $this->genFor ([ $_from , $collection ], [ $_to , $collection ], $direction ), $params ),
// $this->genLimit ( $this->limit , 0),
// $this->genOptions (),
// $this->genSelect ( $vars , $params )
// ];
// $aql = implode( $this->separator , array_filter( $clauses ));
// }
/**
* @param $collection
* @param array $condition
@@ -801,12 +927,13 @@ class Query extends Component implements QueryInterface
*/
public function remove( $collection , $condition = [], $params = [], $db = null)
{
$this->from ( $collection ) ;
$this->collection = $collection ;
$clauses = [
$this->buildFrom ( $collection ),
$this->buildWhere ( $condition , $params ),
$this->buildRemove ( $collection ),
$this->buildOp tions ( ),
$this->genFor ( $collection ),
$this->genIn ( $collection ),
$this->genWhere ( $condition , $params ),
$this->genRemove ( $collec tion ),
$this->genOptions (),
];
$aql = implode( $this->separator , array_filter( $clauses ));
@@ -841,7 +968,7 @@ class Query extends Component implements QueryInterface
* @param $columns
* @return string
*/
protected function build Update( $collection , $columns )
protected function gen Update( $collection , $columns )
{
return 'UPDATE ' . $collection . ' WITH '
. Serializer::encode( $columns ) . ' IN '
@@ -852,7 +979,7 @@ class Query extends Component implements QueryInterface
* @param $collection
* @return string
*/
protected function build Remove( $collection )
protected function gen Remove( $collection )
{
return 'REMOVE ' . $collection . ' IN ' . $collection ;
}
@@ -862,24 +989,108 @@ class Query extends Component implements QueryInterface
* @param $columns
* @return string
*/
protected function build Search(array $expression , int $type = 0 ): string
protected function gen Search(array $expression , str ing $type = 'START' ): string
{
$query = 'SEARCH ';
return match ( $type ) {
1 => $query . $this->filterStartsWith ( $expression ),
return match (strtoupper ( $type )) {
'START', 'STARTS', 'STARTS_WITH' => $query . $this->filterStartsWith ( $expression ),
'CONTAINS', 'LIKE' => $query . $this->filterContains ( $expression ),
default => $query . Serializer::encode( $expression )
};
}
/**
* Присвоение переменной значения
*
* Генерация AQL выражения
*
* @see https://www.arangodb.com/docs/3.7/aql/operations-let.html
*
* @param array $vars Ключ - переменная, значение - её значение
*/
protected static function genLet(array $vars ): ?string
{
// Инициализация
$result = '';
// Конвертация
foreach ( $vars as $name => $value ) {
if (
$value[0] === '('
|| $value[0] === ' " ' && substr($value, -1) === ' " '
|| $value[0] === " '" && substr($value, -1) === "' "
) {
$condition = $value ;
} else {
$condition = ' " ' . $value . ' " ';
}
$result .= 'LET ' . $name . ' = ' . $condition . ', ';
}
return trim( $result , ', ');
}
/**
* Обойти коллекцию вершин по направлению
*
* Генерация AQL выражения
*
* @see https://www.arangodb.com/docs/3.7/aql/operations-let.html
*
* @param string $direction Направление
* @param mixed $vertex Коллекция вершин из которой требуется обход
*/
protected static function genTraversal(string $direction , string $vertex ): ?string
{
return $direction . ' ' . $vertex ;
}
/**
* @return string
*/
protected function build Options()
protected function gen Options()
{
return empty( $this->options ) ? '' : ' OPTIONS ' . Json::encode( $this->options );
}
/**
* Конвертер Array -> AQL (string)
*
* Примеры:
* 1. { " id " : product_search._key, " catn " : product_search.catn}
* 2. { " name " : login, " phone " : number}
* 3. { " users " : users}
* 4. { }
*
* @param array $target Массив для конвертации
* @param string|null Название коллекции к которой привязывать значения массива
*/
protected static function convertArray2Aql(array $target , string|null $collection = null): string
{
// Инициализация
$result = '';
// Конвертация
foreach ( $target as $name => $value ) {
$result .= " \ " $name\ " : " ;
if (is_null( $collection )) {
// Коллекция не отправлена
$result .= " $value , " ;
} else {
// Коллекция отправлена
$result .= " $collection . $value , " ;
}
}
return ' { ' . trim( $result , ', ') . '}';
}
/**
* @param Document[] $rows
* @return array
@@ -955,6 +1166,7 @@ class Query extends Component implements QueryInterface
public function indexBy( $column )
{
$this->indexBy = $column ;
return $this ;
}
@@ -963,10 +1175,19 @@ class Query extends Component implements QueryInterface
* @param array $params
* @return $this |static
*/
public function where( $condition , $params = [] )
public function where( $condition )
{
$this->where = $condition ;
$this->addParams ( $params );
return $this ;
}
/**
*/
public function let(string $name , mixed $value ): static
{
$this->vars [ $name ] = $value ;
return $this ;
}
@@ -1007,7 +1228,7 @@ class Query extends Component implements QueryInterface
*
* This method is similar to [[where()]]. The main difference is that this method will
* remove [[isEmpty()|empty query operands]]. As a result, this method is best suited
* for build ing query conditions based on filter values entered by users.
* for gen ing query conditions based on filter values entered by users.
*
* The following code shows the difference between this method and [[where()]]:
*
@@ -1046,9 +1267,26 @@ class Query extends Component implements QueryInterface
// Генерация
foreach ( $expression as $key => $value ) {
if ( $return ) {
$return .= ' OR STARTS_WITH(' . $this->quoteCollectionName ( $this->from ) . " . $key , \ " $value\ " ) " ;
$return .= ' OR STARTS_WITH(' . $this->quoteCollectionName ( $this->collection ) . " . $key , \ " $value\ " ) " ;
} else {
$return = 'STARTS_WITH(' . $this->quoteCollectionName ( $this->from ) . " . $key , \ " $value\ " ) " ;
$return = 'STARTS_WITH(' . $this->quoteCollectionName ( $this->collection ) . " . $key , \ " $value\ " ) " ;
}
}
return $return ;
}
public function filterContains(array $expression ): string
{
// Инициализация
$return = [];
// Генерация
foreach ( $expression as $key => $value ) {
if ( $return ) {
$return .= ' OR LIKE(' . $this->quoteCollectionName ( $this->collection ) . " . $key , \ " % $value % \" ) " ;
} else {
$return = 'LIKE(' . $this -> quoteCollectionName ( $this -> collection ) . " . $key , \" % $value % \" ) " ;
}
}
@@ -1061,7 +1299,7 @@ class Query extends Component implements QueryInterface
*
* This method is similar to [[andWhere()]]. The main difference is that this method will
* remove [[isEmpty()|empty query operands]]. As a result, this method is best suited
* for build ing query conditions based on filter values entered by users.
* for gen ing query conditions based on filter values entered by users.
*
* @param array $condition the new WHERE condition. Please refer to [[where()]]
* on how to specify this parameter.
@@ -1084,7 +1322,7 @@ class Query extends Component implements QueryInterface
*
* This method is similar to [[orWhere()]]. The main difference is that this method will
* remove [[isEmpty()|empty query operands]]. As a result, this method is best suited
* for build ing query conditions based on filter values entered by users.
* for gen ing query conditions based on filter values entered by users.
*
* @param array $condition the new WHERE condition. Please refer to [[where()]]
* on how to specify this parameter.
@@ -1287,6 +1525,7 @@ class Query extends Component implements QueryInterface
}
}
}
return $this ;
}