|
|
|
@ -214,75 +214,255 @@ class Collection extends Object
|
|
|
|
|
protected function normalizeConditionKeyword($key) |
|
|
|
|
{ |
|
|
|
|
static $map = [ |
|
|
|
|
'or' => '$or', |
|
|
|
|
'OR' => '$or', |
|
|
|
|
'>' => '$gt', |
|
|
|
|
'>=' => '$gte', |
|
|
|
|
'<' => '$lt', |
|
|
|
|
'<=' => '$lte', |
|
|
|
|
'!=' => '$ne', |
|
|
|
|
'<>' => '$ne', |
|
|
|
|
'in' => '$in', |
|
|
|
|
'not in' => '$nin', |
|
|
|
|
'all' => '$all', |
|
|
|
|
'size' => '$size', |
|
|
|
|
'type' => '$type', |
|
|
|
|
'exists' => '$exists', |
|
|
|
|
'notexists' => '$exists', |
|
|
|
|
'elemmatch' => '$elemMatch', |
|
|
|
|
'mod' => '$mod', |
|
|
|
|
'IN' => '$in', |
|
|
|
|
'NOT IN' => '$nin', |
|
|
|
|
'ALL' => '$all', |
|
|
|
|
'SIZE' => '$size', |
|
|
|
|
'TYPE' => '$type', |
|
|
|
|
'EXISTS' => '$exists', |
|
|
|
|
'NOTEXISTS' => '$exists', |
|
|
|
|
'ELEMMATCH' => '$elemMatch', |
|
|
|
|
'MOD' => '$mod', |
|
|
|
|
'%' => '$mod', |
|
|
|
|
'=' => '$$eq', |
|
|
|
|
'==' => '$$eq', |
|
|
|
|
'where' => '$where' |
|
|
|
|
'WHERE' => '$where' |
|
|
|
|
]; |
|
|
|
|
$key = strtolower($key); |
|
|
|
|
if (array_key_exists($key, $map)) { |
|
|
|
|
return $map[$key]; |
|
|
|
|
$matchKey = strtoupper($key); |
|
|
|
|
if (array_key_exists($matchKey, $map)) { |
|
|
|
|
return $map[$matchKey]; |
|
|
|
|
} else { |
|
|
|
|
return $key; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Builds up Mongo condition from user friendly condition. |
|
|
|
|
* @param array $condition raw condition. |
|
|
|
|
* @return array normalized Mongo condition. |
|
|
|
|
* @throws \yii\base\InvalidParamException on invalid condition given. |
|
|
|
|
* Converts given value into [[MongoId]] instance. |
|
|
|
|
* If array given, each element of it will be processed. |
|
|
|
|
* @param mixed $rawId raw id(s). |
|
|
|
|
* @return array|\MongoId normalized id(s). |
|
|
|
|
*/ |
|
|
|
|
protected function ensureMongoId($rawId) |
|
|
|
|
{ |
|
|
|
|
if (is_array($rawId)) { |
|
|
|
|
$result = []; |
|
|
|
|
foreach ($rawId as $key => $value) { |
|
|
|
|
$result[$key] = $this->ensureMongoId($value); |
|
|
|
|
} |
|
|
|
|
return $result; |
|
|
|
|
} elseif (is_object($rawId)) { |
|
|
|
|
if ($rawId instanceof \MongoId) { |
|
|
|
|
return $rawId; |
|
|
|
|
} else { |
|
|
|
|
$rawId = (string)$rawId; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
return new \MongoId($rawId); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Parses the condition specification and generates the corresponding Mongo condition. |
|
|
|
|
* @param array $condition the condition specification. Please refer to [[Query::where()]] |
|
|
|
|
* on how to specify a condition. |
|
|
|
|
* @return array the generated Mongo condition |
|
|
|
|
* @throws InvalidParamException if the condition is in bad format |
|
|
|
|
*/ |
|
|
|
|
public function buildCondition($condition) |
|
|
|
|
{ |
|
|
|
|
static $builders = [ |
|
|
|
|
'AND' => 'buildAndCondition', |
|
|
|
|
'OR' => 'buildOrCondition', |
|
|
|
|
'BETWEEN' => 'buildBetweenCondition', |
|
|
|
|
'NOT BETWEEN' => 'buildBetweenCondition', |
|
|
|
|
'IN' => 'buildInCondition', |
|
|
|
|
'NOT IN' => 'buildInCondition', |
|
|
|
|
'LIKE' => 'buildLikeCondition', |
|
|
|
|
]; |
|
|
|
|
|
|
|
|
|
if (!is_array($condition)) { |
|
|
|
|
throw new InvalidParamException('Condition should be an array.'); |
|
|
|
|
} elseif (empty($condition)) { |
|
|
|
|
return []; |
|
|
|
|
} |
|
|
|
|
$result = []; |
|
|
|
|
foreach ($condition as $key => $value) { |
|
|
|
|
if (is_array($value)) { |
|
|
|
|
$actualValue = $this->buildCondition($value); |
|
|
|
|
if (isset($condition[0])) { // operator format: operator, operand 1, operand 2, ... |
|
|
|
|
$operator = strtoupper($condition[0]); |
|
|
|
|
if (isset($builders[$operator])) { |
|
|
|
|
$method = $builders[$operator]; |
|
|
|
|
array_shift($condition); |
|
|
|
|
return $this->$method($operator, $condition); |
|
|
|
|
} else { |
|
|
|
|
$actualValue = $value; |
|
|
|
|
throw new InvalidParamException('Found unknown operator in query: ' . $operator); |
|
|
|
|
} |
|
|
|
|
if (is_numeric($key)) { |
|
|
|
|
$result[] = $actualValue; |
|
|
|
|
} else { |
|
|
|
|
// hash format: 'column1' => 'value1', 'column2' => 'value2', ... |
|
|
|
|
return $this->buildHashCondition($condition); |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Creates a condition based on column-value pairs. |
|
|
|
|
* @param array $condition the condition specification. |
|
|
|
|
* @return array the generated Mongo condition. |
|
|
|
|
*/ |
|
|
|
|
public function buildHashCondition($condition) |
|
|
|
|
{ |
|
|
|
|
$result = []; |
|
|
|
|
foreach ($condition as $name => $value) { |
|
|
|
|
$name = $this->normalizeConditionKeyword($name); |
|
|
|
|
if (strncmp('$', $name, 1) === 0) { |
|
|
|
|
// Native Mongo condition: |
|
|
|
|
$result[$name] = $value; |
|
|
|
|
} else { |
|
|
|
|
$key = $this->normalizeConditionKeyword($key); |
|
|
|
|
if (strncmp('$', $key, 1) !== 0 && is_array($actualValue) && array_key_exists(0, $actualValue)) { |
|
|
|
|
// shortcut for IN condition |
|
|
|
|
if ($key == '_id') { |
|
|
|
|
foreach ($actualValue as &$actualValuePart) { |
|
|
|
|
if (!is_object($actualValuePart)) { |
|
|
|
|
$actualValuePart = new \MongoId($actualValuePart); |
|
|
|
|
} |
|
|
|
|
if (is_array($value)) { |
|
|
|
|
if (array_key_exists(0, $value)) { |
|
|
|
|
// Quick IN condition: |
|
|
|
|
$result = array_merge($result, $this->buildInCondition('IN', [$name, $value])); |
|
|
|
|
} else { |
|
|
|
|
// Normalize possible verbose condition: |
|
|
|
|
$actualValue = []; |
|
|
|
|
foreach ($value as $k => $v) { |
|
|
|
|
$actualValue[$this->normalizeConditionKeyword($k)] = $v; |
|
|
|
|
} |
|
|
|
|
$result[$name] = $actualValue; |
|
|
|
|
} |
|
|
|
|
$result[$key]['$in'] = $actualValue; |
|
|
|
|
} else { |
|
|
|
|
if ($key == '_id' && !is_object($actualValue)) { |
|
|
|
|
$actualValue = new \MongoId($actualValue); |
|
|
|
|
// Direct match: |
|
|
|
|
if ($name == '_id') { |
|
|
|
|
$value = $this->ensureMongoId($value); |
|
|
|
|
} |
|
|
|
|
$result[$key] = $actualValue; |
|
|
|
|
$result[$name] = $value; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
return $result; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Connects two or more conditions with the `AND` operator. |
|
|
|
|
* @param string $operator the operator to use for connecting the given operands |
|
|
|
|
* @param array $operands the Mongo conditions to connect. |
|
|
|
|
* @return array the generated Mongo condition. |
|
|
|
|
*/ |
|
|
|
|
public function buildAndCondition($operator, $operands) |
|
|
|
|
{ |
|
|
|
|
$result = []; |
|
|
|
|
foreach ($operands as $operand) { |
|
|
|
|
$condition = $this->buildCondition($operand); |
|
|
|
|
$result = array_merge_recursive($result, $condition); |
|
|
|
|
} |
|
|
|
|
return $result; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Connects two or more conditions with the `OR` operator. |
|
|
|
|
* @param string $operator the operator to use for connecting the given operands |
|
|
|
|
* @param array $operands the Mongo conditions to connect. |
|
|
|
|
* @return array the generated Mongo condition. |
|
|
|
|
*/ |
|
|
|
|
public function buildOrCondition($operator, $operands) |
|
|
|
|
{ |
|
|
|
|
$operator = $this->normalizeConditionKeyword($operator); |
|
|
|
|
$parts = []; |
|
|
|
|
foreach ($operands as $operand) { |
|
|
|
|
$parts[] = $this->buildCondition($operand); |
|
|
|
|
} |
|
|
|
|
return [$operator => $parts]; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Creates an Mongo condition, which emulates 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. |
|
|
|
|
* @return array the generated Mongo condition. |
|
|
|
|
* @throws InvalidParamException if wrong number of operands have been given. |
|
|
|
|
*/ |
|
|
|
|
public function buildBetweenCondition($operator, $operands) |
|
|
|
|
{ |
|
|
|
|
if (!isset($operands[0], $operands[1], $operands[2])) { |
|
|
|
|
throw new InvalidParamException("Operator '$operator' requires three operands."); |
|
|
|
|
} |
|
|
|
|
list($column, $value1, $value2) = $operands; |
|
|
|
|
if (strncmp('NOT', $operator, 3) === 0) { |
|
|
|
|
return [ |
|
|
|
|
$column => [ |
|
|
|
|
'$lt' => $value1, |
|
|
|
|
'$gt' => $value2, |
|
|
|
|
] |
|
|
|
|
]; |
|
|
|
|
} else { |
|
|
|
|
return [ |
|
|
|
|
$column => [ |
|
|
|
|
'$gte' => $value1, |
|
|
|
|
'$lte' => $value2, |
|
|
|
|
] |
|
|
|
|
]; |
|
|
|
|
} |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Creates an Mongo condition with the `IN` operator. |
|
|
|
|
* @param string $operator the operator to use (e.g. `IN` or `NOT IN`) |
|
|
|
|
* @param array $operands the first operand is the column name. If it is an array |
|
|
|
|
* a composite IN condition will be generated. |
|
|
|
|
* The second operand is an array of values that column value should be among. |
|
|
|
|
* @return array the generated Mongo condition. |
|
|
|
|
* @throws InvalidParamException if wrong number of operands have been given. |
|
|
|
|
*/ |
|
|
|
|
public function buildInCondition($operator, $operands) |
|
|
|
|
{ |
|
|
|
|
if (!isset($operands[0], $operands[1])) { |
|
|
|
|
throw new InvalidParamException("Operator '$operator' requires two operands."); |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
list($column, $values) = $operands; |
|
|
|
|
|
|
|
|
|
$values = (array)$values; |
|
|
|
|
|
|
|
|
|
if (!is_array($column)) { |
|
|
|
|
$columns = [$column]; |
|
|
|
|
$values = [$column => $values]; |
|
|
|
|
} elseif (count($column) < 2) { |
|
|
|
|
$columns = $column; |
|
|
|
|
$values = [$column[0] => $values]; |
|
|
|
|
} else { |
|
|
|
|
$columns = $column; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
$operator = $this->normalizeConditionKeyword($operator); |
|
|
|
|
$result = []; |
|
|
|
|
foreach ($columns as $column) { |
|
|
|
|
if ($column == '_id') { |
|
|
|
|
$inValues = $this->ensureMongoId($values[$column]); |
|
|
|
|
} else { |
|
|
|
|
$inValues = $values[$column]; |
|
|
|
|
} |
|
|
|
|
$result[$column][$operator] = $inValues; |
|
|
|
|
} |
|
|
|
|
return $result; |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
* Creates a Mongo condition, which emulates the `LIKE` operator. |
|
|
|
|
* @param string $operator the operator to use |
|
|
|
|
* @param array $operands the first operand is the column name. |
|
|
|
|
* The second operand is a single value that column value should be compared with. |
|
|
|
|
* @return array the generated Mongo condition. |
|
|
|
|
* @throws InvalidParamException if wrong number of operands have been given. |
|
|
|
|
*/ |
|
|
|
|
public function buildLikeCondition($operator, $operands) |
|
|
|
|
{ |
|
|
|
|
if (!isset($operands[0], $operands[1])) { |
|
|
|
|
throw new InvalidParamException("Operator '$operator' requires two operands."); |
|
|
|
|
} |
|
|
|
|
list($column, $value) = $operands; |
|
|
|
|
return [$column => '/' . $value . '/']; |
|
|
|
|
} |
|
|
|
|
} |