1686 lines
49 KiB
PHP
1686 lines
49 KiB
PHP
<?php
|
||
|
||
namespace mirzaev\yii2\arangodb;
|
||
|
||
use Yii;
|
||
use yii\base\Component;
|
||
use yii\base\InvalidArgumentException;
|
||
use yii\base\NotSupportedException;
|
||
use yii\db\QueryInterface;
|
||
use yii\helpers\ArrayHelper;
|
||
use yii\helpers\Json;
|
||
|
||
use ArangoDBClient\Document;
|
||
use ArangoDBClient\Statement;
|
||
|
||
use Exception;
|
||
|
||
|
||
class Query extends Component implements QueryInterface
|
||
{
|
||
const PARAM_PREFIX = 'qp';
|
||
const DEBUG = true;
|
||
|
||
public $separator = " ";
|
||
|
||
protected $conditionBuilders = [
|
||
'NOT' => 'genNotCondition',
|
||
'AND' => 'genAndCondition',
|
||
'OR' => 'genAndCondition',
|
||
'IN' => 'genInCondition',
|
||
'LIKE' => 'genLikeCondition',
|
||
'BETWEEN' => 'genBetweenCondition'
|
||
];
|
||
|
||
protected $conditionMap = [
|
||
'NOT' => '!',
|
||
'AND' => '&&',
|
||
'OR' => '||',
|
||
'IN' => 'in',
|
||
'LIKE' => 'LIKE',
|
||
];
|
||
|
||
public $select = [];
|
||
|
||
public string|array $for;
|
||
|
||
public string|array $in;
|
||
|
||
public string $collection;
|
||
|
||
public array $lets = [];
|
||
|
||
/**
|
||
* Массив коллекций вершин и направлений для их обхода
|
||
*
|
||
* [
|
||
* ["INBOUND" => "collection1"],
|
||
* ["OUTBOUND" => "collection2"],
|
||
* ["ANY" => "collection2"]
|
||
* ]
|
||
*/
|
||
public array $traversals = [];
|
||
|
||
public $foreach = [];
|
||
|
||
public $where = [];
|
||
|
||
public $limit;
|
||
|
||
public $offset;
|
||
|
||
/**
|
||
* Поиск
|
||
*
|
||
* [свойство => его значение]
|
||
*/
|
||
public array $search;
|
||
|
||
/**
|
||
* Тип поиска
|
||
*/
|
||
public string $searchType = 'START';
|
||
|
||
public $orderBy;
|
||
|
||
public $indexBy;
|
||
|
||
public $params = [];
|
||
|
||
public $options = [];
|
||
|
||
/**
|
||
* @param array $options
|
||
* @param null|Connection $db
|
||
* @return null|Statement
|
||
*/
|
||
private function getStatement($options = [], $db = null)
|
||
{
|
||
if ($db === null) {
|
||
$db = Yii::$app->get('arangodb');
|
||
}
|
||
|
||
return $db->getStatement($options);
|
||
}
|
||
|
||
/**
|
||
* @param $aql
|
||
* @param $params
|
||
* @return array [$aql, $params]
|
||
*/
|
||
private static function prepareBindVars($aql, array $params)
|
||
{
|
||
$search = [];
|
||
$replace = [];
|
||
|
||
foreach ($params as $key => $value) {
|
||
if (is_array($value)) {
|
||
$search[] = "@$key";
|
||
$replace[] = json_encode($value);
|
||
unset($params[$key]);
|
||
}
|
||
}
|
||
|
||
if (count($search)) {
|
||
$aql = str_replace($search, $replace, $aql);
|
||
}
|
||
|
||
return [$aql, $params];
|
||
}
|
||
|
||
/**
|
||
* @param null|Connection $db
|
||
* @param array $options
|
||
* @return null|Statement
|
||
*/
|
||
public function createCommand($db = null, $options = [])
|
||
{
|
||
list($aql, $params) = $this->genQuery($this);
|
||
|
||
$options = ArrayHelper::merge(
|
||
$options,
|
||
[
|
||
'query' => $aql,
|
||
'bindVars' => $params,
|
||
]
|
||
);
|
||
|
||
return $this->getStatement($options, $db);
|
||
}
|
||
|
||
/**
|
||
* @param $aql
|
||
* @param array $bindValues
|
||
* @param array $params
|
||
* @return array
|
||
* @throws Exception
|
||
*/
|
||
public function execute($aql, $bindValues = [], $params = [])
|
||
{
|
||
list($aql, $bindValues) = self::prepareBindVars($aql, $bindValues);
|
||
|
||
$options = [
|
||
'query' => $aql,
|
||
'bindVars' => $bindValues,
|
||
];
|
||
|
||
$options = ArrayHelper::merge($params, $options);
|
||
$statement = $this->getStatement($options);
|
||
$token = $this->getRawAql($statement);
|
||
Yii::info($token, 'mirzaev\yii2\arangodb\Query::query');
|
||
try {
|
||
Yii::beginProfile($token, 'mirzaev\yii2\arangodb\Query::query');
|
||
$cursor = $statement->execute();
|
||
Yii::endProfile($token, 'mirzaev\yii2\arangodb\Query::query');
|
||
} catch (Exception $ex) {
|
||
Yii::endProfile($token, 'mirzaev\yii2\arangodb\Query::query');
|
||
throw new Exception($ex->getMessage(), (int) $ex->getCode(), $ex);
|
||
}
|
||
return $this->prepareResult($cursor->getAll());
|
||
}
|
||
|
||
/**
|
||
* @param $fields
|
||
* @return $this
|
||
*/
|
||
public function select($fields): self
|
||
{
|
||
$this->select = $fields;
|
||
|
||
return $this;
|
||
}
|
||
|
||
/**
|
||
* @param $collection
|
||
* @return $this
|
||
*/
|
||
public function collection(string $collection): self
|
||
{
|
||
$this->collection = $collection;
|
||
|
||
return $this;
|
||
}
|
||
|
||
/**
|
||
*/
|
||
public function for(string|array $for): self
|
||
{
|
||
$this->for = $for;
|
||
|
||
return $this;
|
||
}
|
||
|
||
/**
|
||
*/
|
||
public function in(string|array $in): self
|
||
{
|
||
$this->in = $in;
|
||
|
||
return $this;
|
||
}
|
||
|
||
/**
|
||
*/
|
||
public function search(array $text, string $type = 'START'): self
|
||
{
|
||
$this->search = $text;
|
||
$this->searchType = $type;
|
||
|
||
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 = 'ANY'): static
|
||
{
|
||
$this->traversals[] = [
|
||
match (strtoupper($direction)) {
|
||
'INBOUND', 'OUTBOUND', 'ANY' => $direction,
|
||
default => 'ANY'
|
||
}
|
||
=> $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 = []): 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 static function quoteCollectionName(string|array $name): string
|
||
{
|
||
if (strpos($name, '(') !== false || strpos($name, '{{') !== false) {
|
||
return $name;
|
||
}
|
||
if (strpos($name, '.') === false) {
|
||
return $name;
|
||
}
|
||
$parts = explode('.', $name);
|
||
foreach ($parts as $i => $part) {
|
||
$parts[$i] = $part;
|
||
}
|
||
|
||
return implode('.', $parts);
|
||
}
|
||
|
||
/**
|
||
* @param $name
|
||
* @return string
|
||
*/
|
||
public function quoteColumnName(string $name)
|
||
{
|
||
if (strpos($name, '(') !== false || strpos($name, '[[') !== false || strpos($name, '{{') !== false) {
|
||
return $name;
|
||
}
|
||
if (($pos = strrpos($name, '.')) !== false) {
|
||
$prefix = substr($name, 0, $pos);
|
||
$prefix = $this->quoteCollectionName($prefix) . '.';
|
||
$name = substr($name, $pos + 1);
|
||
} else {
|
||
$prefix = $this->quoteCollectionName($this->collection) . '.';
|
||
}
|
||
|
||
return $prefix . $name;
|
||
}
|
||
|
||
/**
|
||
* Генерация AQL кода "FOR IN"
|
||
*
|
||
* @param $condition
|
||
* @param $params
|
||
* @return string
|
||
*/
|
||
protected function genForeach(array $conditions): ?string
|
||
{
|
||
// Инициализация
|
||
$aql = '';
|
||
|
||
foreach ($conditions as $condition) {
|
||
// Перебор выражений
|
||
|
||
genForeach_recursion:
|
||
|
||
foreach ($condition as $FOR => $IN) {
|
||
// Инициализация операндов
|
||
|
||
if (is_int($FOR) && is_array($IN)) {
|
||
// Вложенный массив (неожиданные входные данные)
|
||
|
||
// Реинициализация
|
||
$condition = $IN;
|
||
|
||
// Перебор вложенного массива
|
||
goto genForeach_recursion;
|
||
}
|
||
|
||
$aql .= "FOR $FOR IN $IN ";
|
||
}
|
||
}
|
||
|
||
// Постобработка
|
||
$aql = trim($aql);
|
||
|
||
return $aql;
|
||
}
|
||
|
||
/**
|
||
* @param $condition
|
||
* @param $params
|
||
* @return string
|
||
*/
|
||
protected function genWhere($condition, array &$params)
|
||
{
|
||
return ($where = $this->genCondition($condition, $params)) ? 'FILTER ' . $where : '';
|
||
}
|
||
|
||
/**
|
||
* @param $condition
|
||
* @param $params
|
||
* @return string
|
||
*
|
||
* @todo Разобраться с этим говном
|
||
*/
|
||
protected function genCondition($condition, array &$params)
|
||
{
|
||
if (!is_array($condition)) {
|
||
return (string) $condition;
|
||
} elseif (empty($condition)) {
|
||
return '';
|
||
}
|
||
|
||
/**
|
||
* @todo Переписать под новую архитектуру
|
||
*/
|
||
if (isset($condition[0]) && is_string($condition[0]) && count($condition) > 1) {
|
||
// Формат: operator, operand 1, operand 2, ...
|
||
|
||
$operator = strtoupper($condition[0]);
|
||
if (isset($this->conditionBuilders[$operator])) {
|
||
$method = $this->conditionBuilders[$operator];
|
||
array_shift($condition);
|
||
return $this->$method($operator, $condition, $params);
|
||
} else {
|
||
throw new InvalidArgumentException('Found unknown operator in query: ' . $operator);
|
||
}
|
||
} else {
|
||
// hash format: 'column1' => 'value1', 'column2' => 'value2', ...
|
||
|
||
return $this->genHashCondition($condition, $params);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @param $condition
|
||
* @param $params
|
||
* @return string
|
||
* @throws Exception
|
||
*/
|
||
protected function genHashCondition(array $condition, array &$params)
|
||
{
|
||
$parts = [];
|
||
|
||
foreach ($condition as $key => $value) {
|
||
// Перебор выражений
|
||
|
||
// Инициализация
|
||
$operator = $condition['operator'] ?? '==';
|
||
|
||
if (is_int($key) && is_array($value)) {
|
||
// Обычный массив (вложенное выражение)
|
||
|
||
// Начало рекурсивного поиска выражений
|
||
recursive_expressions_search:
|
||
|
||
// Реинициализация
|
||
$operator = $value['operator'] ?? $operator;
|
||
|
||
foreach ($value as $key => $value) {
|
||
// Перебор выражений в сложном режиме
|
||
|
||
if (is_int($key) && is_string($value)) {
|
||
|
||
// $key = $value;
|
||
// $value = null;
|
||
} else if (is_int($key) && is_array($value)) {
|
||
// Многомерный массив
|
||
|
||
// Рекурсивный поиск выражений
|
||
goto recursive_expressions_search;
|
||
}
|
||
|
||
// Защита алгоритма от использования параметров как продолжения выражения
|
||
$break = true;
|
||
|
||
// Генерация
|
||
$this->filterHashCondition($key, $value, $params, $parts, $operator);
|
||
}
|
||
} else {
|
||
// Ассоциативный массив (простой режим)
|
||
|
||
if (isset($break)) {
|
||
// Обработка завершена дальше в цикле идут параметры
|
||
|
||
break;
|
||
}
|
||
|
||
// Генерация
|
||
$this->filterHashCondition($key, $value, $params, $parts, $operator);
|
||
}
|
||
}
|
||
|
||
return count($parts) === 1 ? $parts[0] : '(' . implode(') && (', $parts) . ')';
|
||
}
|
||
|
||
/**
|
||
* @todo Можно добавить проверку операторов
|
||
*/
|
||
protected function filterHashCondition(mixed $column, mixed $value, array &$params, array &$parts, string $operator = '==')
|
||
{
|
||
if (is_array($value) || $value instanceof Query) {
|
||
// Правый операнд передан как массив или инстанция Query
|
||
|
||
// IN condition
|
||
$parts[] = $this->genInCondition('IN', [$column, $value], $params);
|
||
} else if (is_int($column) && is_string($value)) {
|
||
// Передача без ключа массива (строка с готовым выражением)
|
||
|
||
$parts[] = $value;
|
||
} else {
|
||
// Правый операнд передан как строка (подразумевается)
|
||
|
||
if (strpos($column, '(') === false) {
|
||
$column = $this->quoteColumnName($column);
|
||
}
|
||
|
||
if ($value === null) {
|
||
// Конвертация null
|
||
|
||
// Генерация
|
||
$parts[] = "$column $operator null";
|
||
} else {
|
||
// Обычная обработка параметра
|
||
|
||
$phName = self::PARAM_PREFIX . count($params);
|
||
|
||
// Генерация
|
||
$parts[] = "$column $operator @$phName";
|
||
|
||
$params[$phName] = $value;
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @param $operator
|
||
* @param $operands
|
||
* @param $params
|
||
* @return string
|
||
*/
|
||
protected function genAndCondition($operator, $operands, &$params)
|
||
{
|
||
$parts = [];
|
||
foreach ($operands as $operand) {
|
||
if (is_array($operand)) {
|
||
$operand = $this->genCondition($operand, $params);
|
||
}
|
||
if ($operand !== '') {
|
||
$parts[] = $operand;
|
||
}
|
||
}
|
||
if (!empty($parts)) {
|
||
return '(' . implode(") {$this->conditionMap[$operator]} (", $parts) . ')';
|
||
} else {
|
||
return '';
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @param $operator
|
||
* @param $operands
|
||
* @param $params
|
||
* @return string
|
||
*/
|
||
protected function genNotCondition($operator, $operands, &$params)
|
||
{
|
||
if (count($operands) != 1) {
|
||
throw new InvalidArgumentException("Operator '$operator' requires exactly one operand.");
|
||
}
|
||
|
||
$operand = reset($operands);
|
||
if (is_array($operand)) {
|
||
$operand = $this->genCondition($operand, $params);
|
||
}
|
||
if ($operand === '') {
|
||
return '';
|
||
}
|
||
|
||
return "{$this->conditionMap[$operator]} ($operand)";
|
||
}
|
||
|
||
/**
|
||
* @param $operator
|
||
* @param $operands
|
||
* @param $params
|
||
* @return string
|
||
* @throws Exception
|
||
*/
|
||
protected function genInCondition($operator, $operands, &$params)
|
||
{
|
||
if (!isset($operands[0], $operands[1])) {
|
||
throw new Exception("Operator '$operator' requires two operands.");
|
||
}
|
||
|
||
list($column, $values) = $operands;
|
||
|
||
if ($values === [] || $column === []) {
|
||
return $operator === 'IN' ? '0==1' : '';
|
||
}
|
||
|
||
if ($values instanceof Query) {
|
||
// sub-query
|
||
list($sql, $params) = $this->genQuery($values, $params);
|
||
$column = $column;
|
||
if (is_array($column)) {
|
||
foreach ($column as $i => $col) {
|
||
if (strpos($col, '(') === false) {
|
||
$column[$i] = $this->quoteColumnName($col);
|
||
}
|
||
}
|
||
return '(' . implode(', ', $column) . ") {$this->conditionMap[$operator]} ($sql)";
|
||
} else {
|
||
|
||
if (strpos($column, '(') === false) {
|
||
$column = $this->quoteColumnName($column);
|
||
}
|
||
return "$column {$this->conditionMap[$operator]} ($sql)";
|
||
}
|
||
}
|
||
|
||
$values = (array) $values;
|
||
|
||
if (count($column) > 1) {
|
||
return $this->genCompositeInCondition($operator, $column, $values, $params);
|
||
}
|
||
|
||
if (is_array($column)) {
|
||
$column = reset($column);
|
||
}
|
||
foreach ($values as $i => $value) {
|
||
if (is_array($value)) {
|
||
$value = isset($value[$column]) ? $value[$column] : null;
|
||
}
|
||
if ($value === null) {
|
||
$values[$i] = 'null';
|
||
} else {
|
||
$phName = self::PARAM_PREFIX . count($params);
|
||
$params[$phName] = $value;
|
||
$values[$i] = "@$phName";
|
||
}
|
||
}
|
||
if (strpos($column, '(') === false) {
|
||
$column = $this->quoteColumnName($column);
|
||
}
|
||
|
||
if (count($values) > 1) {
|
||
return "$column {$this->conditionMap[$operator]} [" . implode(', ', $values) . ']';
|
||
} else {
|
||
$operator = $operator === 'IN' ? '==' : '!=';
|
||
return $column . $operator . reset($values);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @param $operator
|
||
* @param $columns
|
||
* @param $values
|
||
* @param $params
|
||
* @return string
|
||
*/
|
||
protected function genCompositeInCondition($operator, $columns, $values, &$params)
|
||
{
|
||
$vss = [];
|
||
foreach ($values as $value) {
|
||
$vs = [];
|
||
foreach ($columns as $column) {
|
||
if (isset($value[$column])) {
|
||
$phName = self::PARAM_PREFIX . count($params);
|
||
$params[$phName] = $value[$column];
|
||
$vs[] = "@$phName";
|
||
} else {
|
||
$vs[] = 'null';
|
||
}
|
||
}
|
||
$vss[] = '(' . implode(', ', $vs) . ')';
|
||
}
|
||
foreach ($columns as $i => $column) {
|
||
if (strpos($column, '(') === false) {
|
||
$columns[$i] = $this->quoteColumnName($column);
|
||
}
|
||
}
|
||
|
||
return '(' . implode(', ', $columns) . ") {$this->conditionMap[$operator]} [" . implode(', ', $vss) . ']';
|
||
}
|
||
|
||
/**
|
||
* Creates an SQL expressions with the `BETWEEN` operator.
|
||
* @param string $operator the operator to use
|
||
* @param array $operands the first operand is the column name. The second and third operands
|
||
* describe the interval that column value should be in.
|
||
* @param array $params the binding parameters to be populated
|
||
* @return string the generated AQL expression
|
||
* @throws InvalidArgumentException if wrong number of operands have been given.
|
||
*/
|
||
public function genBetweenCondition($operator, $operands, &$params)
|
||
{
|
||
if (!isset($operands[0], $operands[1], $operands[2])) {
|
||
throw new InvalidArgumentException("Operator '$operator' requires three operands.");
|
||
}
|
||
|
||
list($column, $value1, $value2) = $operands;
|
||
|
||
if (strpos($column, '(') === false) {
|
||
$column = $this->quoteColumnName($column);
|
||
}
|
||
$phName1 = self::PARAM_PREFIX . count($params);
|
||
$params[$phName1] = $value1;
|
||
$phName2 = self::PARAM_PREFIX . count($params);
|
||
$params[$phName2] = $value2;
|
||
|
||
return "$column >= @$phName1 && $column <= @$phName2";
|
||
}
|
||
|
||
/**
|
||
* @param $operator
|
||
* @param $condition
|
||
* @param $params
|
||
* @return string
|
||
*/
|
||
protected function genLikeCondition($operator, $condition, &$params)
|
||
{
|
||
if (!(isset($condition[0]) && isset($condition[1]))) {
|
||
throw new InvalidArgumentException("You must set 'column' and 'pattern' params");
|
||
}
|
||
$caseInsensitive = isset($condition[2]) ? (bool)$condition[2] : false;
|
||
return $this->conditionMap[$operator]
|
||
. '('
|
||
. $this->quoteColumnName($condition[0])
|
||
. ', "'
|
||
. $condition[1]
|
||
. '", '
|
||
. ($caseInsensitive ? 'TRUE' : 'FALSE')
|
||
. ')';
|
||
}
|
||
|
||
/**
|
||
* @param $columns
|
||
* @return string
|
||
*/
|
||
protected function genOrderBy($columns)
|
||
{
|
||
if (empty($columns)) {
|
||
return '';
|
||
}
|
||
$orders = [];
|
||
foreach ($columns as $name => $direction) {
|
||
$orders[] = $this->quoteColumnName($name) . ($direction === SORT_DESC ? ' DESC' : '');
|
||
}
|
||
|
||
return 'SORT ' . implode(', ', $orders);
|
||
}
|
||
|
||
/**
|
||
* @param $limit
|
||
* @return bool
|
||
*/
|
||
protected function hasLimit($limit)
|
||
{
|
||
return is_string($limit) && ctype_digit($limit) || is_integer($limit) && $limit >= 0;
|
||
}
|
||
|
||
/**
|
||
* @param $offset
|
||
* @return bool
|
||
*/
|
||
protected function hasOffset($offset)
|
||
{
|
||
return is_integer($offset) && $offset > 0 || is_string($offset) && ctype_digit($offset) && $offset !== '0';
|
||
}
|
||
|
||
/**
|
||
* @param $limit
|
||
* @param $offset
|
||
* @return string
|
||
*/
|
||
protected function genLimit($limit, $offset)
|
||
{
|
||
$aql = '';
|
||
if ($this->hasLimit($limit)) {
|
||
$aql = 'LIMIT ' . ($this->hasOffset($offset) ? $offset : '0') . ',' . $limit;
|
||
}
|
||
|
||
return $aql;
|
||
}
|
||
|
||
/**
|
||
* @param $columns
|
||
* @param $params
|
||
* @return string
|
||
*/
|
||
protected function genSelect($columns, &$params) // А нахуй здесь params ещё и ссылкой? Потом проверить
|
||
{
|
||
if ($columns === null || empty($columns)) {
|
||
return 'RETURN ' . $this->collection;
|
||
}
|
||
|
||
if (!is_array($columns)) {
|
||
return 'RETURN ' . $columns;
|
||
}
|
||
|
||
return 'RETURN ' . self::convertArray2Aql($columns, $this->collection);
|
||
}
|
||
|
||
/**
|
||
* @param null $query
|
||
* @param array $params
|
||
* @return array
|
||
*
|
||
* @todo Оптимизировать и создать регулируемую очередь выполнения
|
||
*/
|
||
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;
|
||
|
||
$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->select, $params),
|
||
];
|
||
|
||
$aql = implode($query->separator, array_filter($clauses));
|
||
|
||
return self::prepareBindVars($aql, $params);
|
||
}
|
||
|
||
/**
|
||
* @param Statement $statement
|
||
* @return string
|
||
*/
|
||
protected static function getRawAql($statement)
|
||
{
|
||
$query = $statement->getQuery();
|
||
$values = $statement->getBindVars();
|
||
|
||
$search = [];
|
||
$replace = [];
|
||
foreach ($values as $key => $value) {
|
||
$search[] = "/@\b$key\b/";
|
||
$replace[] = is_string($value) ? "\"$value\"" : json_encode($value);
|
||
}
|
||
|
||
if (count($search)) {
|
||
$query = preg_replace($search, $replace, $query);
|
||
}
|
||
|
||
return $query;
|
||
}
|
||
|
||
/**
|
||
* @param null $db
|
||
* @return array
|
||
* @throws Exception
|
||
*/
|
||
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();
|
||
Yii::endProfile($token, 'mirzaev\yii2\arangodb\Query::query');
|
||
} catch (Exception $ex) {
|
||
Yii::endProfile($token, 'mirzaev\yii2\arangodb\Query::query');
|
||
throw new Exception($ex->getMessage(), (int) $ex->getCode(), $ex);
|
||
}
|
||
return $this->prepareResult($cursor->getAll());
|
||
}
|
||
|
||
/**
|
||
* @param null $db
|
||
*/
|
||
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();
|
||
Yii::endProfile($token, 'mirzaev\yii2\arangodb\Query::query');
|
||
} catch (Exception $ex) {
|
||
Yii::endProfile($token, 'mirzaev\yii2\arangodb\Query::query');
|
||
throw new Exception($ex->getMessage(), (int) $ex->getCode(), $ex);
|
||
}
|
||
$result = $this->prepareResult($cursor->getAll());
|
||
return empty($result) ? false : $result[0];
|
||
}
|
||
|
||
/**
|
||
* @param $columns
|
||
* @param array $params
|
||
* @param null $db
|
||
*
|
||
* @return bool
|
||
*
|
||
* @throws Exception
|
||
*/
|
||
public function insert($columns, $params = [], $db = null)
|
||
{
|
||
// Инициализация
|
||
$this->in ?? (isset($this->collection) ? $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)}",
|
||
$this->genOptions(),
|
||
];
|
||
|
||
$aql = implode($this->separator, array_filter($clauses));
|
||
|
||
$params = ArrayHelper::merge(
|
||
$params,
|
||
[
|
||
'query' => $aql,
|
||
]
|
||
);
|
||
|
||
$statement = $this->getStatement($params, $db);
|
||
$token = $this->getRawAql($statement);
|
||
|
||
Yii::info($token, 'mirzaev\yii2\arangodb\Query::insert');
|
||
try {
|
||
Yii::beginProfile($token, 'mirzaev\yii2\arangodb\Query::insert');
|
||
$cursor = $statement->execute();
|
||
Yii::endProfile($token, 'mirzaev\yii2\arangodb\Query::insert');
|
||
} catch (Exception $ex) {
|
||
Yii::endProfile($token, 'mirzaev\yii2\arangodb\Query::insert');
|
||
throw new Exception($ex->getMessage(), (int) $ex->getCode(), $ex);
|
||
}
|
||
return true;
|
||
}
|
||
|
||
/**
|
||
* @param $columns
|
||
* @param array $params
|
||
* @param null $db
|
||
*
|
||
* @return bool
|
||
*
|
||
* @throws Exception
|
||
*/
|
||
public function update($columns, $params = [], $db = null)
|
||
{
|
||
// Инициализация
|
||
$this->in ?? (isset($this->collection) ? $this->in = $this->collection : throw new Exception('Не найдена коллекция'));
|
||
$this->for ?? $this->for = $this->in;
|
||
$this->collection ?? $this->collection = $this->in;
|
||
|
||
$clauses = [
|
||
static::genFor($this->for ?? $this->collection),
|
||
static::genIn($this->in ?? $this->collection, $this->traversals),
|
||
$this->genWhere($this->where, $params),
|
||
$this->genUpdate($this->collection, $columns),
|
||
$this->genOptions(),
|
||
];
|
||
|
||
$aql = implode($this->separator, array_filter($clauses));
|
||
|
||
$params = ArrayHelper::merge(
|
||
$params,
|
||
[
|
||
'query' => $aql,
|
||
'bindVars' => $params,
|
||
]
|
||
);
|
||
|
||
$statement = $this->getStatement($params, $db);
|
||
$token = $this->getRawAql($statement);
|
||
|
||
Yii::info($token, 'mirzaev\yii2\arangodb\Query::update');
|
||
|
||
try {
|
||
Yii::beginProfile($token, 'mirzaev\yii2\arangodb\Query::update');
|
||
$cursor = $statement->execute();
|
||
Yii::endProfile($token, 'mirzaev\yii2\arangodb\Query::update');
|
||
} catch (Exception $ex) {
|
||
Yii::endProfile($token, 'mirzaev\yii2\arangodb\Query::update');
|
||
throw new Exception($ex->getMessage(), (int) $ex->getCode(), $ex);
|
||
}
|
||
$meta = $cursor->getMetadata();
|
||
return isset($meta['extra']['operations']['executed']) ?
|
||
$meta['extra']['operations']['executed'] :
|
||
true;
|
||
}
|
||
|
||
/**
|
||
* @param $collection
|
||
* @param array $condition
|
||
* @param array $params
|
||
* @param null $db
|
||
* @return bool
|
||
* @throws Exception
|
||
*/
|
||
public function remove($params = [], $db = null)
|
||
{
|
||
// Инициализация
|
||
$this->in ?? (isset($this->collection) ? $this->in = $this->collection : throw new Exception('Не найдена коллекция'));
|
||
$this->for ?? $this->for = $this->in;
|
||
$this->collection ?? $this->collection = $this->in;
|
||
|
||
$clauses = [
|
||
static::genFor($this->for ?? $this->collection),
|
||
static::genIn($this->in ?? $this->collection, $this->traversals),
|
||
$this->genWhere($this->where, $params),
|
||
$this->genRemove($this->in ?? $this->collection),
|
||
$this->genOptions(),
|
||
];
|
||
|
||
$aql = implode($this->separator, array_filter($clauses));
|
||
|
||
$params = ArrayHelper::merge(
|
||
$params,
|
||
[
|
||
'query' => $aql,
|
||
'bindVars' => $params,
|
||
]
|
||
);
|
||
|
||
$statement = $this->getStatement($params, $db);
|
||
$token = $this->getRawAql($statement);
|
||
Yii::info($token, 'mirzaev\yii2\arangodb\Query::remove');
|
||
try {
|
||
Yii::beginProfile($token, 'mirzaev\yii2\arangodb\Query::remove');
|
||
$cursor = $statement->execute();
|
||
Yii::endProfile($token, 'mirzaev\yii2\arangodb\Query::remove');
|
||
} catch (Exception $ex) {
|
||
Yii::endProfile($token, 'mirzaev\yii2\arangodb\Query::remove');
|
||
throw new Exception($ex->getMessage(), (int) $ex->getCode(), $ex);
|
||
}
|
||
$meta = $cursor->getMetadata();
|
||
return isset($meta['extra']['operations']['executed']) ?
|
||
$meta['extra']['operations']['executed'] :
|
||
true;
|
||
}
|
||
|
||
/**
|
||
* @param $collection
|
||
* @param $columns
|
||
* @return string
|
||
*/
|
||
protected function genUpdate($collection, $columns)
|
||
{
|
||
return 'UPDATE ' . $collection . ' WITH '
|
||
. Serializer::encode($columns) . ' IN '
|
||
. $this->quoteCollectionName($collection);
|
||
}
|
||
|
||
/**
|
||
* @param $collection
|
||
* @return string
|
||
*/
|
||
protected function genRemove($collection)
|
||
{
|
||
return 'REMOVE ' . $collection . ' IN ' . $collection;
|
||
}
|
||
|
||
/**
|
||
* @param $collection
|
||
* @param $columns
|
||
* @return string
|
||
*/
|
||
protected function genSearch(array $expression, string $type = 'START'): string
|
||
{
|
||
$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)
|
||
};
|
||
}
|
||
|
||
/**
|
||
* Присвоение переменной значения
|
||
*
|
||
* Генерация 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 genOptions()
|
||
{
|
||
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
|
||
*/
|
||
public function prepareResult($rows)
|
||
{
|
||
$result = [];
|
||
if (isset($rows[0]) && $rows[0] instanceof Document) {
|
||
if ($this->indexBy === null) {
|
||
foreach ($rows as $row) {
|
||
$result[] = $row->getAll();
|
||
}
|
||
} else {
|
||
foreach ($rows as $row) {
|
||
if (is_string($this->indexBy)) {
|
||
$key = $row->{$this->indexBy};
|
||
} else {
|
||
$key = call_user_func($this->indexBy, $row);
|
||
}
|
||
$result[$key] = $row->getAll();
|
||
}
|
||
}
|
||
} else {
|
||
$result = $rows;
|
||
}
|
||
return $result;
|
||
}
|
||
|
||
/**
|
||
* @param string $q
|
||
* @param null $db
|
||
* @return int
|
||
* @throws Exception
|
||
* @throws \triagens\ArangoDb\ClientException
|
||
*/
|
||
public function count($q = '*', $db = null)
|
||
{
|
||
$this->select = '1';
|
||
$this->limit(1);
|
||
$this->offset(0);
|
||
$statement = $this->createCommand($db);
|
||
$statement->setFullCount(true);
|
||
$statement->setBatchSize(1);
|
||
|
||
$token = $this->getRawAql($statement);
|
||
|
||
Yii::info($token, 'mirzaev\yii2\arangodb\Query::query');
|
||
try {
|
||
Yii::beginProfile($token, 'mirzaev\yii2\arangodb\Query::query');
|
||
$cursor = $statement->execute();
|
||
Yii::endProfile($token, 'mirzaev\yii2\arangodb\Query::query');
|
||
} catch (Exception $ex) {
|
||
Yii::endProfile($token, 'mirzaev\yii2\arangodb\Query::query');
|
||
throw new Exception($ex->getMessage(), (int) $ex->getCode(), $ex);
|
||
}
|
||
|
||
return $cursor->getFullCount();
|
||
}
|
||
|
||
/**
|
||
* @param null $db
|
||
* @return bool
|
||
* @throws Exception
|
||
*/
|
||
public function exists($db = null)
|
||
{
|
||
$record = $this->one($db);
|
||
return !empty($record);
|
||
}
|
||
|
||
/**
|
||
* @param callable|string $column
|
||
* @return $this|static
|
||
*/
|
||
public function indexBy($column)
|
||
{
|
||
$this->indexBy = $column;
|
||
|
||
return $this;
|
||
}
|
||
|
||
/**
|
||
* Перебор
|
||
*
|
||
* FOR НАЗВАНИЕ IN ПЕРЕМЕННАЯ
|
||
*
|
||
* @param array $expression ['НАЗВАНИЕ' => 'ПЕРЕМЕННАЯ']
|
||
*/
|
||
public function foreach(array $expression)
|
||
{
|
||
$this->foreach = match (true) {
|
||
empty($this->foreach) => [$expression],
|
||
default => $this->foreach []= [$expression]
|
||
};
|
||
|
||
return $this;
|
||
}
|
||
|
||
/**
|
||
* @param array $expression
|
||
*/
|
||
public function where($expression, $operator = 'AND')
|
||
{
|
||
$this->where = match (true) {
|
||
is_null($this->where) => $expression,
|
||
default => [$operator, $this->where, $expression]
|
||
};
|
||
|
||
return $this;
|
||
}
|
||
|
||
/**
|
||
*/
|
||
public function let(string $name, mixed $value): static
|
||
{
|
||
$this->lets[$name] = $value;
|
||
|
||
return $this;
|
||
}
|
||
|
||
/**
|
||
* @param array|string $condition
|
||
* @param array $params
|
||
* @return $this|static
|
||
*/
|
||
public function andWhere($condition, $params = [])
|
||
{
|
||
if (is_null($this->where)) {
|
||
$this->where = $condition;
|
||
} else {
|
||
$this->where = ['AND', $this->where, $condition];
|
||
}
|
||
$this->params($params);
|
||
return $this;
|
||
}
|
||
|
||
/**
|
||
* @param array|string $condition
|
||
* @param array $params
|
||
* @return $this|static
|
||
*/
|
||
public function orWhere($condition, $params = [])
|
||
{
|
||
if ($this->where === null) {
|
||
$this->where = $condition;
|
||
} else {
|
||
$this->where = ['OR', $this->where, $condition];
|
||
}
|
||
$this->params($params);
|
||
return $this;
|
||
}
|
||
|
||
/**
|
||
* Sets the WHERE part of the query but ignores [[isEmpty()|empty operands]].
|
||
*
|
||
* 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 gening query conditions based on filter values entered by users.
|
||
*
|
||
* The following code shows the difference between this method and [[where()]]:
|
||
*
|
||
* ```php
|
||
* // WHERE `age`=:age
|
||
* $query->filterWhere(['name' => null, 'age' => 20]);
|
||
* // WHERE `age`=:age
|
||
* $query->where(['age' => 20]);
|
||
* // WHERE `name` IS NULL AND `age`=:age
|
||
* $query->where(['name' => null, 'age' => 20]);
|
||
* ```
|
||
*
|
||
* Note that unlike [[where()]], you cannot pass binding parameters to this method.
|
||
*
|
||
* @param array $condition the conditions that should be put in the WHERE part.
|
||
* See [[where()]] on how to specify this parameter.
|
||
* @return static the query object itself.
|
||
* @see where()
|
||
* @see andFilterWhere()
|
||
* @see orFilterWhere()
|
||
*/
|
||
public function filterWhere(array $condition)
|
||
{
|
||
$condition = $this->filterCondition($condition);
|
||
if ($condition !== []) {
|
||
$this->where($condition);
|
||
}
|
||
return $this;
|
||
}
|
||
|
||
public function filterStartsWith(array $expression): string
|
||
{
|
||
// Генерация
|
||
foreach ($expression as $key => $value) {
|
||
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 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%\")";
|
||
}
|
||
}
|
||
|
||
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.
|
||
*
|
||
* 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 gening 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.
|
||
* @return static the query object itself.
|
||
* @see filterWhere()
|
||
* @see orFilterWhere()
|
||
*/
|
||
public function andFilterWhere(array $condition)
|
||
{
|
||
$condition = $this->filterCondition($condition);
|
||
if ($condition !== []) {
|
||
$this->andWhere($condition);
|
||
}
|
||
return $this;
|
||
}
|
||
|
||
/**
|
||
* 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 'OR' operator.
|
||
*
|
||
* 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 gening 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.
|
||
* @return static the query object itself.
|
||
* @see filterWhere()
|
||
* @see andFilterWhere()
|
||
*/
|
||
public function orFilterWhere(array $condition)
|
||
{
|
||
$condition = $this->filterCondition($condition);
|
||
if ($condition !== []) {
|
||
$this->orWhere($condition);
|
||
}
|
||
return $this;
|
||
}
|
||
|
||
/**
|
||
* Returns a value indicating whether the give value is "empty".
|
||
*
|
||
* The value is considered "empty", if one of the following conditions is satisfied:
|
||
*
|
||
* - it is `null`,
|
||
* - an empty string (`''`),
|
||
* - a string containing only whitespace characters,
|
||
* - or an empty array.
|
||
*
|
||
* @param mixed $value
|
||
* @return boolean if the value is empty
|
||
*/
|
||
protected function isEmpty($value)
|
||
{
|
||
return $value === '' || $value === [] || $value === null || is_string($value) && (trim($value) === '' || trim($value, '%') === '');
|
||
}
|
||
|
||
/**
|
||
* Removes [[isEmpty()|empty operands]] from the given query condition.
|
||
*
|
||
* @param array $condition the original condition
|
||
* @return array the condition with [[isEmpty()|empty operands]] removed.
|
||
* @throws NotSupportedException if the condition operator is not supported
|
||
*/
|
||
protected function filterCondition($condition)
|
||
{
|
||
if (!is_array($condition)) {
|
||
return $condition;
|
||
}
|
||
|
||
if (!isset($condition[0])) {
|
||
// hash format: 'column1' => 'value1', 'column2' => 'value2', ...
|
||
foreach ($condition as $name => $value) {
|
||
if ($this->isEmpty($value)) {
|
||
unset($condition[$name]);
|
||
}
|
||
}
|
||
return $condition;
|
||
}
|
||
|
||
// operator format: operator, operand 1, operand 2, ...
|
||
|
||
$operator = array_shift($condition);
|
||
|
||
switch (strtoupper($operator)) {
|
||
case 'NOT':
|
||
case 'AND':
|
||
case 'OR':
|
||
foreach ($condition as $i => $operand) {
|
||
$subCondition = $this->filterCondition($operand);
|
||
if ($this->isEmpty($subCondition)) {
|
||
unset($condition[$i]);
|
||
} else {
|
||
$condition[$i] = $subCondition;
|
||
}
|
||
}
|
||
|
||
if (empty($condition)) {
|
||
return [];
|
||
}
|
||
break;
|
||
case 'IN':
|
||
case 'LIKE':
|
||
if (array_key_exists(1, $condition) && $this->isEmpty($condition[1])) {
|
||
return [];
|
||
}
|
||
break;
|
||
case 'BETWEEN':
|
||
if ((array_key_exists(1, $condition) && $this->isEmpty($condition[1]))
|
||
|| (array_key_exists(2, $condition) && $this->isEmpty($condition[2]))
|
||
) {
|
||
return [];
|
||
}
|
||
break;
|
||
default:
|
||
throw new NotSupportedException("Operator not supported: $operator");
|
||
}
|
||
|
||
array_unshift($condition, $operator);
|
||
|
||
return $condition;
|
||
}
|
||
|
||
/**
|
||
* @param array|string $columns
|
||
* @return $this|static
|
||
*/
|
||
public function orderBy($columns)
|
||
{
|
||
$this->orderBy = $this->normalizeOrderBy($columns);
|
||
return $this;
|
||
}
|
||
|
||
/**
|
||
* @param array|string $columns
|
||
* @return $this|static
|
||
*/
|
||
public function addOrderBy($columns)
|
||
{
|
||
$columns = $this->normalizeOrderBy($columns);
|
||
if ($this->orderBy === null) {
|
||
$this->orderBy = $columns;
|
||
} else {
|
||
$this->orderBy = array_merge($this->orderBy, $columns);
|
||
}
|
||
return $this;
|
||
}
|
||
|
||
/**
|
||
* @param int $limit
|
||
* @return $this|static
|
||
*/
|
||
public function limit($limit)
|
||
{
|
||
// Если $limit === 0 то $limit = null
|
||
$limit === 0 and $limit = null;
|
||
|
||
$this->limit = $limit;
|
||
|
||
return $this;
|
||
}
|
||
|
||
/**
|
||
* @param int $offset
|
||
* @return $this|static
|
||
*/
|
||
public function offset($offset)
|
||
{
|
||
$this->offset = $offset;
|
||
return $this;
|
||
}
|
||
|
||
/**
|
||
* @param $columns
|
||
* @return array
|
||
*/
|
||
protected function normalizeOrderBy($columns)
|
||
{
|
||
if (is_array($columns)) {
|
||
return $columns;
|
||
} else {
|
||
$columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY);
|
||
$result = [];
|
||
foreach ($columns as $column) {
|
||
if (preg_match('/^(.*?)\s+(asc|desc)$/i', $column, $matches)) {
|
||
$result[$matches[1]] = strcasecmp($matches[2], 'desc') ? SORT_ASC : SORT_DESC;
|
||
} else {
|
||
$result[$column] = SORT_ASC;
|
||
}
|
||
}
|
||
return $result;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Sets the parameters to be bound to the query.
|
||
* @param array $params list of query parameter values indexed by parameter placeholders.
|
||
* For example, `[':name' => 'Dan', ':age' => 31]`.
|
||
* @return static the query object itself
|
||
* @see params()
|
||
*/
|
||
public function params(array ...$params)
|
||
{
|
||
foreach ($params as $params) {
|
||
// Перебор параметров
|
||
|
||
$this->params = match (true) {
|
||
empty($this->params) => $params,
|
||
default => (function ($params) {
|
||
// Инициализация
|
||
$return = [];
|
||
|
||
foreach ($params as $name => $value) {
|
||
// Перебор параметров
|
||
|
||
if (is_integer($name)) {
|
||
// Обычный массив
|
||
|
||
$return[] = $value;
|
||
} else {
|
||
// Ассоциативный массив
|
||
|
||
$return[$name] = $value;
|
||
}
|
||
}
|
||
|
||
return array_merge($this->params, $return);
|
||
})($params)
|
||
};
|
||
}
|
||
|
||
return $this;
|
||
}
|
||
|
||
/**
|
||
* @param $options
|
||
* @return $this
|
||
*/
|
||
public function options($options)
|
||
{
|
||
$this->options = $options;
|
||
return $this;
|
||
}
|
||
|
||
/**
|
||
* @param $options
|
||
* @return $this
|
||
*/
|
||
public function addOptions($options)
|
||
{
|
||
if (!empty($options)) {
|
||
if (empty($this->options)) {
|
||
$this->params = $options;
|
||
} else {
|
||
foreach ($options as $name => $value) {
|
||
if (is_integer($name)) {
|
||
$this->options[] = $value;
|
||
} else {
|
||
$this->options[$name] = $value;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
return $this;
|
||
}
|
||
|
||
public function emulateExecution($value = true)
|
||
{
|
||
}
|
||
}
|