From 7288baa9593053bc769f6037dd50517079892852 Mon Sep 17 00:00:00 2001 From: Arsen Mirzaev Tatyano-Muradovich Date: Wed, 24 Feb 2021 08:22:04 +1000 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=BB=D0=B5?= =?UTF-8?q?=D0=BD=20=D0=BE=D0=BF=D0=B5=D1=80=D0=B0=D1=82=D0=BE=D1=80=20"!?= =?UTF-8?q?=3D",=20=D0=BF=D0=B5=D1=80=D0=B5=D1=80=D0=B0=D0=B1=D0=BE=D1=82?= =?UTF-8?q?=D0=BA=D0=B0,=20=D0=BE=D0=BF=D1=82=D0=B8=D0=BC=D0=B8=D0=B7?= =?UTF-8?q?=D0=B0=D1=86=D0=B8=D1=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.RU.md | 4 +- README.md | 4 +- mirzaev/yii2-arangodb/ActiveQuery.php | 2 +- mirzaev/yii2-arangodb/ActiveRecord.php | 3 +- mirzaev/yii2-arangodb/Query.php | 323 ++++++++++-------- .../console/controllers/MigrateController.php | 9 +- .../panels/arangodb/ArangoDbPanel.php | 12 +- 7 files changed, 193 insertions(+), 164 deletions(-) diff --git a/README.RU.md b/README.RU.md index 53b910c..8d11bf6 100644 --- a/README.RU.md +++ b/README.RU.md @@ -57,7 +57,7 @@ use explosivebit\arangodb\Query; $query = new Query; // compose the query $query->select(['name', 'status']) - ->from('customer') + ->collection('customer') ->limit(10); // execute the query $rows = $query->all(); @@ -103,7 +103,7 @@ use yii\data\ActiveDataProvider; use explosivebit\arangodb\Query; $query = new Query; -$query->from('customer')->where(['status' => 2]); +$query->collection('customer')->where(['status' => 2]); $provider = new ActiveDataProvider([ 'query' => $query, 'pagination' => [ diff --git a/README.md b/README.md index cf3e23b..11528ea 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ use explosivebit\arangodb\Query; $query = new Query; // compose the query $query->select(['name', 'status']) - ->from('customer') + ->collection('customer') ->limit(10); // execute the query $rows = $query->all(); @@ -108,7 +108,7 @@ use yii\data\ActiveDataProvider; use explosivebit\arangodb\Query; $query = new Query; -$query->from('customer')->where(['status' => 2]); +$query->collection('customer')->where(['status' => 2]); $provider = new ActiveDataProvider([ 'query' => $query, 'pagination' => [ diff --git a/mirzaev/yii2-arangodb/ActiveQuery.php b/mirzaev/yii2-arangodb/ActiveQuery.php index 4a7d114..60be2c2 100644 --- a/mirzaev/yii2-arangodb/ActiveQuery.php +++ b/mirzaev/yii2-arangodb/ActiveQuery.php @@ -20,7 +20,7 @@ class ActiveQuery extends Query implements ActiveQueryInterface parent::__construct($config); } - protected function genQuery($query = null, $params = []) + protected function genQuery($query = null, array $params = []) { if ($this->primaryModel !== null) { // lazy loading diff --git a/mirzaev/yii2-arangodb/ActiveRecord.php b/mirzaev/yii2-arangodb/ActiveRecord.php index ed10516..26cb641 100644 --- a/mirzaev/yii2-arangodb/ActiveRecord.php +++ b/mirzaev/yii2-arangodb/ActiveRecord.php @@ -102,9 +102,8 @@ abstract class ActiveRecord extends BaseActiveRecord * // FOR customer IN customer FILTER customer.age>30 RETURN customer * $customers = Customer::find()->where('age>30')->all(); * - * @return ActiveQueryInterface the newly created [[ActiveQueryInterface|ActiveQuery]] instance. */ - public static function find() + public static function find(): ActiveQuery { /** @var ActiveQuery $query */ $query = Yii::createObject(ActiveQuery::class, [get_called_class()]); diff --git a/mirzaev/yii2-arangodb/Query.php b/mirzaev/yii2-arangodb/Query.php index cfadb87..63cc11e 100644 --- a/mirzaev/yii2-arangodb/Query.php +++ b/mirzaev/yii2-arangodb/Query.php @@ -4,7 +4,7 @@ namespace mirzaev\yii2\arangodb; use Yii; use yii\base\Component; -use yii\base\InvalidParamException; +use yii\base\InvalidArgumentException; use yii\base\NotSupportedException; use yii\db\QueryInterface; use yii\helpers\ArrayHelper; @@ -67,6 +67,18 @@ class Query extends Component implements QueryInterface public $offset; + /** + * Поиск + * + * [свойство => его значение] + */ + public array $search; + + /** + * Тип поиска + */ + public string $searchType = 'START'; + public $orderBy; public $indexBy; @@ -94,7 +106,7 @@ class Query extends Component implements QueryInterface * @param $params * @return array [$aql, $params] */ - private static function prepareBindVars($aql, $params) + private static function prepareBindVars($aql, array $params) { $search = []; $replace = []; @@ -169,7 +181,7 @@ class Query extends Component implements QueryInterface * @param $fields * @return $this */ - public function select($fields) + public function select($fields): self { $this->select = $fields; @@ -180,7 +192,7 @@ class Query extends Component implements QueryInterface * @param $collection * @return $this */ - public function collection(string $collection) + public function collection(string $collection): self { $this->collection = $collection; @@ -189,7 +201,7 @@ class Query extends Component implements QueryInterface /** */ - public function for(string|array $for) + public function for(string|array $for): self { $this->for = $for; @@ -198,13 +210,23 @@ class Query extends Component implements QueryInterface /** */ - public function in(string|array $in) + 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; + } + /** * Обойти коллекцию вершин по направлению @@ -216,12 +238,12 @@ class Query extends Component implements QueryInterface * @param mixed $vertex Коллекция вершин из которой требуется обход * @param string $direction Направление ('INBOUND', 'OUTBOUND', 'ANY') */ - public function traversal(string $vertex, string $direction = 'INBOUND'): static + public function traversal(string $vertex, string $direction = 'ANY'): static { $this->traversals[] = [ - match ($direction) { + match (strtoupper($direction)) { 'INBOUND', 'OUTBOUND', 'ANY' => $direction, - default => null + default => 'ANY' } => $vertex ]; @@ -260,7 +282,7 @@ class Query extends Component implements QueryInterface * 1. "IN account" * 2. "IN INBOUND supply account_edge_supply" */ - protected static function genIn(string|array|null $in, array $traversals = null): string + protected static function genIn(string|array|null $in, array $traversals = []): string { if (is_array($in)) { // Если передан массив, то конвертировать в строку @@ -334,11 +356,10 @@ class Query extends Component implements QueryInterface * @param $params * @return string */ - protected function genWhere($condition, &$params) + protected function genWhere($condition, array &$params) { - $where = $this->genCondition($condition, $params); - return $where ? 'FILTER ' . $where : ''; + return ($where = $this->genCondition($condition, $params)) ? 'FILTER ' . $where : ''; } /** @@ -348,7 +369,7 @@ class Query extends Component implements QueryInterface * * @todo Разобраться с этим говном */ - protected function genCondition($condition, &$params) + protected function genCondition($condition, array &$params) { if (!is_array($condition)) { return (string) $condition; @@ -356,16 +377,23 @@ class Query extends Component implements QueryInterface return ''; } - if (isset($condition[0])) { // operator format: operator, operand 1, operand 2, ... + /** + * @todo Переписать под новую архитектуру + */ + if (isset($condition[0]) && is_string($condition[0])) { + // Формат: 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 InvalidParamException('Found unknown operator in query: ' . $operator); + throw new InvalidArgumentException('Found unknown operator in query: ' . $operator); } - } else { // hash format: 'column1' => 'value1', 'column2' => 'value2', ... + } else { + // hash format: 'column1' => 'value1', 'column2' => 'value2', ... + return $this->genHashCondition($condition, $params); } } @@ -376,27 +404,92 @@ class Query extends Component implements QueryInterface * @return string * @throws Exception */ - protected function genHashCondition($condition, &$params) + protected function genHashCondition(array $condition, array &$params) { $parts = []; - foreach ($condition as $column => $value) { - if (is_array($value) || $value instanceof Query) { - // IN condition - $parts[] = $this->genInCondition('IN', [$column, $value], $params); + + 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_array($value)) { + // Многомерный массив + + // Рекурсивный поиск выражений + goto recursive_expressions_search; + } + + // Защита алгоритма от использования параметров как продолжения выражения + $break = true; + + // Генерация + $this->filterHashCondition($key, $value, $params, $parts, $operator); + } } else { - if (strpos($column, '(') === false) { - $column = $this->quoteColumnName($column); - } - if ($value === null) { - $parts[] = "$column == null"; - } else { - $phName = self::PARAM_PREFIX . count($params); - $parts[] = "$column==@$phName"; - $params[$phName] = $value; + // Ассоциативный массив (простой режим) + + 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 (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; } } - return count($parts) === 1 ? $parts[0] : '(' . implode(') && (', $parts) . ')'; } /** @@ -432,7 +525,7 @@ class Query extends Component implements QueryInterface protected function genNotCondition($operator, $operands, &$params) { if (count($operands) != 1) { - throw new InvalidParamException("Operator '$operator' requires exactly one operand."); + throw new InvalidArgumentException("Operator '$operator' requires exactly one operand."); } $operand = reset($operands); @@ -468,7 +561,7 @@ class Query extends Component implements QueryInterface if ($values instanceof Query) { // sub-query list($sql, $params) = $this->genQuery($values, $params); - $column = (array)$column; + $column = $column; if (is_array($column)) { foreach ($column as $i => $col) { if (strpos($col, '(') === false) { @@ -477,6 +570,7 @@ class Query extends Component implements QueryInterface } return '(' . implode(', ', $column) . ") {$this->conditionMap[$operator]} ($sql)"; } else { + if (strpos($column, '(') === false) { $column = $this->quoteColumnName($column); } @@ -556,12 +650,12 @@ class Query extends Component implements QueryInterface * 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 InvalidParamException if wrong number of operands have been given. + * @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 InvalidParamException("Operator '$operator' requires three operands."); + throw new InvalidArgumentException("Operator '$operator' requires three operands."); } list($column, $value1, $value2) = $operands; @@ -586,7 +680,7 @@ class Query extends Component implements QueryInterface protected function genLikeCondition($operator, $condition, &$params) { if (!(isset($condition[0]) && isset($condition[1]))) { - throw new InvalidParamException("You must set 'column' and 'pattern' params"); + throw new InvalidArgumentException("You must set 'column' and 'pattern' params"); } $caseInsensitive = isset($condition[2]) ? (bool)$condition[2] : false; return $this->conditionMap[$operator] @@ -671,18 +765,21 @@ class Query extends Component implements QueryInterface * @param null $query * @param array $params * @return array + * + * @todo Оптимизировать и создать регулируемую очередь выполнения */ - protected function genQuery($query = null, $params = []) + protected function genQuery($query = null, array $params = []) { - $query = isset($query) ? $query : $this; + isset($query) ? $query : $query = $this; - $params = empty($params) ? $query->params : array_merge($params, $query->params); + $params = array_merge($params, $query->params); $clauses = [ static::genFor($query->for ?? $query->collection), static::genIn($query->in ?? $query->collection, $query->traversals), static::genLet($query->vars), $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), @@ -848,75 +945,6 @@ class Query extends Component implements QueryInterface true; } - /** - * Представление - * - * Работа с представлениями - * - * @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 Connection $db - */ - public function view(string $collection, string|array|null $vars = null, array $expression = null, string $type = null, array $params = [], $db = null): array - { - $this->collection = $collection; - $clauses = [ - $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)); - - $params = ArrayHelper::merge( - $params, - [ - 'query' => $aql, - 'bindVars' => $params, - ] - ); - - $statement = $this->getStatement($params, $db); - $token = $this->getRawAql($statement); - Yii::info($token, 'mirzaev\yii2\arangodb\Query::search'); - try { - Yii::beginProfile($token, 'mirzaev\yii2\arangodb\Query::search'); - $cursor = $statement->execute(); - Yii::endProfile($token, 'mirzaev\yii2\arangodb\Query::search'); - } catch (Exception $ex) { - Yii::endProfile($token, 'mirzaev\yii2\arangodb\Query::search'); - throw new Exception($ex->getMessage(), (int) $ex->getCode(), $ex); - } - 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 @@ -1026,10 +1054,10 @@ class Query extends Component implements QueryInterface $condition = '"' . $value . '"'; } - $result .= 'LET ' . $name . ' = ' . $condition . ', '; + $result .= 'LET ' . $name . ' = ' . $condition . ' '; } - return trim($result, ', '); + return trim($result); } /** @@ -1171,13 +1199,14 @@ class Query extends Component implements QueryInterface } /** - * @param array|string $condition - * @param array $params - * @return $this|static + * @param array $expression */ - public function where($condition) + public function where($expression) { - $this->where = $condition; + $this->where = match (true) { + is_null($this->where) => $expression, + default => ['AND', $this->where, $expression] + }; return $this; } @@ -1198,12 +1227,12 @@ class Query extends Component implements QueryInterface */ public function andWhere($condition, $params = []) { - if ($this->where === null) { + if (is_null($this->where)) { $this->where = $condition; } else { $this->where = ['AND', $this->where, $condition]; } - $this->addParams($params); + $this->params($params); return $this; } @@ -1219,7 +1248,7 @@ class Query extends Component implements QueryInterface } else { $this->where = ['OR', $this->where, $condition]; } - $this->addParams($params); + $this->params($params); return $this; } @@ -1261,12 +1290,9 @@ class Query extends Component implements QueryInterface public function filterStartsWith(array $expression): string { - // Инициализация - $return = []; - // Генерация foreach ($expression as $key => $value) { - if ($return) { + if (isset($return)) { $return .= ' OR STARTS_WITH(' . $this->quoteCollectionName($this->collection) . ".$key, \"$value\")"; } else { $return = 'STARTS_WITH(' . $this->quoteCollectionName($this->collection) . ".$key, \"$value\")"; @@ -1495,35 +1521,36 @@ class Query extends Component implements QueryInterface * @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 addParams() - */ - public function params($params) - { - $this->params = $params; - return $this; - } - - /** - * Adds additional 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 addParams($params) + public function params(array ...$params) { - if (!empty($params)) { - if (empty($this->params)) { - $this->params = $params; - } else { - foreach ($params as $name => $value) { - if (is_integer($name)) { - $this->params[] = $value; - } else { - $this->params[$name] = $value; + 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; diff --git a/mirzaev/yii2-arangodb/console/controllers/MigrateController.php b/mirzaev/yii2-arangodb/console/controllers/MigrateController.php index f69a4f6..26532a6 100644 --- a/mirzaev/yii2-arangodb/console/controllers/MigrateController.php +++ b/mirzaev/yii2-arangodb/console/controllers/MigrateController.php @@ -17,15 +17,18 @@ class MigrateController extends BaseMigrateController * @var string the name of the collection for keeping applied migration information. */ public $migrationCollection = 'migration'; + /** * @var string the directory storing the migration classes. This can be either * a path alias or a directory. */ public $migrationPath = '@app/migrations/arangodb'; + /** * @inheritdoc */ - public $templateFile = '@explosivebit/arangodb/views/migration.php'; + public $templateFile = '@mirzaev/yii2/arangodb/views/migration.php'; + /** * @var Connection|string the DB connection object or the application * component ID of the DB connection. @@ -55,7 +58,7 @@ class MigrateController extends BaseMigrateController if (parent::beforeAction($action)) { if ($action->id !== 'create') { if (is_string($this->db)) { - $this->db = \Yii::$app->get($this->db); + $this->db = yii::$app->get($this->db); } if (!$this->db instanceof Connection) { throw new Exception("The 'db' option must refer to the application component ID of a ArangoDB connection."); @@ -104,7 +107,7 @@ class MigrateController extends BaseMigrateController { $query = new Query; $rows = $query->select(['version' => 'version', 'apply_time' => 'apply_time']) - ->from($this->migrationCollection) + ->collection($this->migrationCollection) ->orderBy('version DESC') ->limit($limit) ->all($this->db); diff --git a/mirzaev/yii2-arangodb/panels/arangodb/ArangoDbPanel.php b/mirzaev/yii2-arangodb/panels/arangodb/ArangoDbPanel.php index 5664021..e2dbffe 100644 --- a/mirzaev/yii2-arangodb/panels/arangodb/ArangoDbPanel.php +++ b/mirzaev/yii2-arangodb/panels/arangodb/ArangoDbPanel.php @@ -3,7 +3,7 @@ namespace mirzaev\yii2\arangodb\panels\arangodb; use mirzaev\yii2\arangodb\panels\arangodb\models\ArangoDb; -use Yii; +use yii; use yii\debug\Panel; use yii\log\Logger; @@ -48,7 +48,7 @@ class ArangoDbPanel extends Panel protected function calculateTimings() { if ($this->_timings === null) { - $this->_timings = Yii::getLogger()->calculateTimings($this->data['arango-messages']); + $this->_timings = yii::getLogger()->calculateTimings($this->data['arango-messages']); } return $this->_timings; @@ -82,8 +82,8 @@ class ArangoDbPanel extends Panel $queryCount = count($timings); $queryTime = number_format($this->getTotalQueryTime($timings) * 1000) . ' ms'; - return \Yii::$app->view->render( - '@explosivebit/arangodb/panels/arangodb/views/summary', + return \yii::$app->view->render( + '@mirzaev/yii2/arangodb/panels/arangodb/views/summary', [ 'timings' => $this->calculateTimings(), 'queryCount' => $queryCount, @@ -99,9 +99,9 @@ class ArangoDbPanel extends Panel public function getDetail() { $searchModel = new ArangoDb(); - $dataProvider = $searchModel->search(Yii::$app->request->getQueryParams(), $this->getModels()); + $dataProvider = $searchModel->search(yii::$app->request->getQueryParams(), $this->getModels()); - return Yii::$app->view->render('@explosivebit/arangodb/panels/arangodb/views/detail', [ + return yii::$app->view->render('@mirzaev/yii2/arangodb/panels/arangodb/views/detail', [ 'panel' => $this, 'dataProvider' => $dataProvider, 'searchModel' => $searchModel,