@@ -74,12 +74,19 @@ class Query extends Component implements QueryInterface
*
* [свойство => е г о значение]
*/
public array $search ;
public array | string $search ;
/**
* Тип поиска
* Фильтр
*
* [свойство => е г о значение]
*/
public string $searchType = 'START' ;
public array | string $filter ;
/**
* Отладка
*/
public bool $debug = false ;
public $orderBy ;
@@ -145,6 +152,11 @@ class Query extends Component implements QueryInterface
]
);
if ( $this -> debug ) {
var_dump ( $aql );
die ;
}
return $this -> getStatement ( $options , $db );
}
@@ -221,22 +233,27 @@ class Query extends Component implements QueryInterface
/**
*/
public function search ( array $text , string $type = 'START' ) : self
public function search ( array | string $expressions ) : self
{
$this -> search = $t ext ;
$this -> searchType = $type ;
$this -> search = $expressions ;
return $this ;
}
/**
*/
public function filter ( array | string $expressions ) : self
{
$this -> filter = $expressions ;
return $this ;
}
/**
* Обойти коллекцию вершин по направлению
*
* Генерация AQL выражения
*
* @see https://www.arangodb.com/docs/3.7/aql/operations-let.html
*
* @param mixed $vertex Коллекция вершин из которой требуется обход
* @param string $direction Направление ('INBOUND', 'OUTBOUND', 'ANY')
*/
@@ -253,24 +270,43 @@ class Query extends Component implements QueryInterface
return $this ;
}
/**
* Проверка типа и конвертация
*/
protected static function checkArrayAndConvert ( string | array $text ) : string
{
if ( is_array ( $text )) {
return self :: convertArrayToString ( $text );
}
return $text ;
}
/**
* Конвертация в строку
*/
protected static function convertArrayToString ( array $text ) : string
{
// Очистка
array_walk ( $text , 'trim' );
// Конвертация
return implode ( " , " , $text );
}
/**
* Генерация 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 );
$for = self :: convertArrayToString ( $for );
}
// Генерация
@@ -368,19 +404,17 @@ class Query extends Component implements QueryInterface
foreach ( $conditions as $condition ) {
// Перебор выражений
genForeach_recursion :
foreach ( $condition as $FOR => $IN ) {
// Инициализация операндов
if ( is_int ( $FOR ) && is_array ( $IN )) {
// Вложенный массив (неожиданные входные данные)
// Реинициализация
$condition = $IN ;
// !!! Вход в рекурсию !!!
// Обработка вложенного массива
$aql .= ' ' . $this -> genForeach ([ $IN ]);
// Перебор вложенного массива
goto genForeach_recursion ;
continue ;
}
$aql .= " FOR $FOR IN $IN " ;
@@ -751,8 +785,13 @@ class Query extends Component implements QueryInterface
return '' ;
}
$orders = [];
foreach ( $columns as $name => $direction ) {
$orders [] = $this -> quoteColumnName ( $name ) . ( $direction === SORT_DESC ? ' DESC' : '' );
$orders [] = $this -> quoteColumnName ( $name ) . match ( $direction ) {
SORT_DESC => ' DESC' ,
SORT_ASC => ' ASC' ,
default => ''
};
}
return 'SORT ' . implode ( ', ' , $orders );
@@ -819,27 +858,33 @@ class Query extends Component implements QueryInterface
protected function genQuery ( $query = null , array $params = [])
{
// Инициализация
isset ( $query ) ? $query : $query = $this ;
$this -> in ? ? ( isset ( $this -> collection ) ? $this -> in = $this -> collection : throw new Exception ( 'Н е найдена коллекция' )) ;
$this -> for ? ? $this -> for = $this -> in ;
$this -> collection ? ? $this -> collection = $this -> in ;
$query ? ? $query = $this ;
$query -> in ? ? $query -> in = $query -> collection ? ? throw new Exception ( 'Н е найдена коллекция' );
$query -> for ? ? $query -> for = $query -> in ;
$query -> collection ? ? $query -> collection = self :: checkArrayAndConvert ( $query -> for ) ;
$params = array_merge ( $params , $query -> params );
$clauses = [
static :: genFor ( $query -> for ? ? $query -> collection ),
static :: genIn ( $query -> in ? ? $query -> collection , $query -> traversals ),
static :: genLet ( $query -> lets ),
$this -> genForeach ( $query -> foreach ),
$this -> genWhere ( $query -> where , $params ) ,
isset ( $this -> search ) ? $this -> genSearch ( $this -> search , $this -> searchType ) : null ,
$this -> genOrderBy ( $query -> orderBy , $params ),
$this -> genLimit ( $query -> limit , $query -> offset , $params ),
$this -> genSelect ( $query -> selec t, $params ),
$query :: genFor ( $query -> for ),
$query :: genIn ( $query -> in , $query -> traversals ),
$query :: genLet ( $query -> lets ),
$query -> genForeach ( $query -> foreach ),
isset ( $query -> search ) ? $query -> genSearch ( $query -> search ) : null ,
isset ( $query -> filter ) ? $query -> genFilter ( $query -> filter ) : null ,
$query -> genWhere ( $query -> where , $params ),
$query -> genOrderBy ( $query -> orderBy , $params ),
$query -> genLimit ( $query -> limit , $query -> offse t, $params ),
$query -> genSelect ( $query -> select , $params ),
];
$aql = implode ( $query -> separator , array_filter ( $clauses ));
if ( $query -> debug ) {
var_dump ( $aql );
die ;
}
return self :: prepareBindVars ( $aql , $params );
}
@@ -874,9 +919,11 @@ class Query extends Component implements QueryInterface
public function all( $db = null)
{
$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');
$cursor = $statement->execute ();
@@ -885,6 +932,7 @@ class Query extends Component implements QueryInterface
Yii::endProfile( $token , 'mirzaev \ yii2 \ arangodb \ Query::query');
throw new Exception( $ex->getMessage (), (int) $ex->getCode (), $ex );
}
return $this->prepareResult ( $cursor->getAll ());
}
@@ -894,10 +942,14 @@ class Query extends Component implements QueryInterface
public function one( $db = null)
{
$this->limit (1);
$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');
$cursor = $statement->execute ();
@@ -922,13 +974,13 @@ class Query extends Component implements QueryInterface
public function insert( $columns , $params = [], $db = null)
{
// Инициализация
$this->in ?? (isset( $this->collection ) ? $this->in = $this->collection : throw new Exception('Н е найдена коллекция')) ;
$this->in ?? $this->in = $this->collection ?? throw new Exception('Н е найдена коллекция');
$this->collection ?? $this->collection = $this->in ;
$data = Serializer::encode( $columns );
$clauses = [
" INSERT $data IN { $this -> quoteCollectionName ( $this -> in ? ? $this -> collection)} " ,
" INSERT $data IN { $this -> quoteCollectionName ( $this -> collection )} " ,
$this->genOptions (),
];
@@ -968,15 +1020,15 @@ class Query extends Component implements QueryInterface
public function update( $columns , $params = [], $db = null)
{
// Инициализация
$this->in ?? (isset( $this->collection ) ? $this->in = $this->collection : throw new Exception('Н е найдена коллекция')) ;
$this->in ?? $this->in = $this->collection ?? throw new Exception('Н е найдена коллекция');
$this->for ?? $this->for = $this->in ;
$this->collection ?? $this->collection = $this->in ;
$this->collection ?? $this->collection = self::checkArrayAndConvert( $this->for ) ;
$clauses = [
static::genFor( $this->for ?? $this->collection ),
static::genIn( $this->in ?? $this->collection , $this->traversals ),
static::genFor( $this->for ),
static::genIn( $this->in , $this->traversals ),
$this->genWhere ( $this->where , $params ),
$this->genUpdate ( $this->collectio n , $columns ),
$this->genUpdate ( $this->i n , $columns ),
$this->genOptions (),
];
@@ -1020,15 +1072,15 @@ class Query extends Component implements QueryInterface
public function remove( $params = [], $db = null)
{
// Инициализация
$this->in ?? (isset( $this->collection ) ? $this->in = $this->collection : throw new Exception('Н е найдена коллекция')) ;
$this->in ?? $this->in = $this->collection ?? throw new Exception('Н е найдена коллекция');
$this->for ?? $this->for = $this->in ;
$this->collection ?? $this->collection = $this->in ;
$this->collection ?? $this->collection = self::checkArrayAndConvert( $this->for ) ;
$clauses = [
static::genFor( $this->for ?? $this->collection ),
static::genIn( $this->in ?? $this->collection , $this->traversals ),
static::genFor( $this->for ),
static::genIn( $this->in , $this->traversals ),
$this->genWhere ( $this->where , $params ),
$this->genRemove ( $this->in ?? $this->collection ),
$this->genRemove ( $this->in ),
$this->genOptions (),
];
@@ -1085,17 +1137,65 @@ class Query extends Component implements QueryInterface
* @param $columns
* @return string
*/
protected function genSearch(array $expression , string $type = 'START' ): string
protected function genSearch(array|string $expressions ): string
{
if (is_string( $expressions )) return $expressions ;
// Инициализация строки запроса
$query = 'SEARCH ';
return match (strtoupper( $type )) {
'START', 'STARTS', 'STARTS_WITH' => $query . $this->filterStartsWith ( $expression ),
'CONTAINS', 'LIKE' => $query . $this->filterContains ( $expression ),
default => $query . Serializer::encode( $expression )
foreach ( $expressions as $expression ) {
// Перебор выражений
// Инициализация оператора
$operator = isset( $expression['operator'] ) ? ' ' . $expression['operator'] . ' ' : '';
// Генерация строки запроса
$query .= match (strtoupper( $expression['type'] )) {
'START', 'STARTS', 'STARTS_WITH' => $operator . $this->filterStartsWith ( $expression['condition'] ),
'START_SENSETIVE' => $operator . $this->filterStartsWith ( $expression['condition'] , sensetive: true),
'CONTAINS', 'LIKE' => $operator . $this->filterContains ( $expression['condition'] ),
'LEVENSHTEIN' => $operator . $this->filterLevenshtein ( $expression['condition'] , ... $expression['parameters'] ),
'PHRASE' => $operator . $this->filterPhrase ( $expression['condition'] , ... $expression['parameters'] ),
default => $operator . Serializer::encode( $expression['condition'] )
};
}
return $query ;
}
/**
* @param $collection
* @param $columns
* @return string
*/
protected function genFilter(array|string $expressions ): string
{
if (isString( $expressions )) return $expressions ;
// Инициализация строки запроса
$query = 'FILTER ';
foreach ( $expressions as $expression ) {
// Перебор выражений
// Инициализация оператора
$operator = isset( $expression['operator'] ) ? ' ' . $expression['operator'] . ' ' : '';
// Генерация строки запроса
$query .= match (strtoupper( $expression['type'] )) {
'START', 'STARTS', 'STARTS_WITH' => $operator . $this->filterStartsWith ( $expression['condition'] ),
'START_SENSETIVE' => $operator . $this->filterStartsWith ( $expression['condition'] , sensetive: true),
'CONTAINS', 'LIKE' => $operator . $this->filterContains ( $expression['condition'] ),
'LEVENSHTEIN' => $operator . $this->filterLevenshtein ( $expression['condition'] , ... $expression['parameters'] ),
'PHRASE' => $operator . $this->filterPhrase ( $expression['condition'] , ... $expression['parameters'] ),
default => $operator . Serializer::encode( $expression['condition'] )
};
}
return $query ;
}
/**
* Присвоение переменной значения
*
@@ -1133,8 +1233,6 @@ class Query extends Component implements QueryInterface
*
* Генерация AQL выражения
*
* @see https://www.arangodb.com/docs/3.7/aql/operations-let.html
*
* @param string $direction Направление
* @param mixed $vertex Коллекция вершин из которой требуется обход
*/
@@ -1298,6 +1396,17 @@ class Query extends Component implements QueryInterface
return $this ;
}
/**
* @param array $expression
*/
public function debug(bool $status = true)
{
$this->debug = $status ;
return $this ;
}
/**
*/
public function let(string $name , mixed $value ): static
@@ -1375,20 +1484,38 @@ class Query extends Component implements QueryInterface
return $this ;
}
public function filterStartsWith(array $expression ): string
public function filterStartsWith(array $expression , bool $sensetive = false ): string
{
// Генерация
foreach ( $expression as $key => $value ) {
if ( $sensetive ) {
if (isset( $return )) {
$return .= ' OR STARTS_WITH(' . $this->filterLower ( $this->quoteCollectionName ( $this->collection ) . " . $key " ) . " , " . $this->filterLower ( " \ " $value\ " " ) . " ) " ;
} else {
$return = 'STARTS_WITH(' . $this->filterLower ( $this->quoteCollectionName ( $this->collection ) . " . $key " ) . " , " . $this->filterLower ( " \ " $value\ " " ) . " ) " ;
}
} else {
if (isset( $return )) {
$return .= ' OR STARTS_WITH(' . $this->quoteCollectionName ( $this->collection ) . " . $key , \ " $value\ " ) " ;
} else {
$return = 'STARTS_WITH(' . $this->quoteCollectionName ( $this->collection ) . " . $key , \ " $value\ " ) " ;
}
}
}
return $return ;
}
public function filterUpper(string $target ): string
{
return " UPPER ( $target ) " ;
}
public function filterLower(string $target ): string
{
return " LOWER ( $target ) " ;
}
public function filterContains(array $expression ): string
{
// Инициализация
@@ -1406,6 +1533,44 @@ class Query extends Component implements QueryInterface
return $return ;
}
public function filterPhrase ( array $expression , string $analyzer = 'text_en' ) : string
{
// Инициализация
$return = [];
foreach ( $expression as $key => $value ) {
// Перебор выражений
if ( $return ) {
$return .= ' OR PHRASE(' . $this -> quoteCollectionName ( $this -> collection ) . " . $key , \" $value\ " , \ " $analyzer\ " ) " ;
} else {
$return = 'PHRASE(' . $this->quoteCollectionName ( $this->collection ) . " . $key , \ " $value\ " , \ " $analyzer\ " ) " ;
}
}
return $return ;
}
public function filterLevenshtein(array $expression , string $analyzer = 'text_en', int $distance = 3, bool $transpositions = true): string
{
// Инициализация
$return = [];
$transpositions = $transpositions ? 'true' : 'false';
foreach ( $expression as $key => $value ) {
// Перебор выражений
if ( $return ) {
$return .= ' OR ANALYZER(LEVENSHTEIN_MATCH(' . $this->quoteCollectionName ( $this->collection ) . " . $key , \ " $value\ " , $distance , $transpositions ), \ " $analyzer\ " ) " ;
} else {
$return = 'ANALYZER(LEVENSHTEIN_MATCH(' . $this->quoteCollectionName ( $this->collection ) . " . $key , \ " $value\ " , $distance , $transpositions ), \ " $analyzer\ " ) " ;
}
}
return $return ;
}
/**
* Adds an additional WHERE condition to the existing one but ignores [[isEmpty()|empty operands]].
* The new condition and the existing one will be joined using the 'AND' operator.
@@ -1603,6 +1768,7 @@ class Query extends Component implements QueryInterface
$result[$column] = SORT_ASC;
}
}
return $result ;
}
}