mirror of
https://github.com/yiisoft/yii2.git
synced 2026-03-06 23:39:02 +01:00
Fixes #7640: Implemented custom data types support. Added JSON support for MySQL and PostgreSQL, array support for PostgreSQL
This commit is contained in:
committed by
Alexander Makarov
parent
5afe0a0d36
commit
d16586334d
@@ -46,9 +46,9 @@ cache:
|
||||
- $HOME/.composer/cache
|
||||
- $HOME/.npm
|
||||
|
||||
# try running against postgres 9.3
|
||||
# try running against postgres 9.6
|
||||
addons:
|
||||
postgresql: "9.3"
|
||||
postgresql: "9.6"
|
||||
code_climate:
|
||||
repo_token: 2935307212620b0e2228ab67eadd92c9f5501ddb60549d0d86007a354d56915b
|
||||
|
||||
@@ -89,7 +89,7 @@ matrix:
|
||||
addons:
|
||||
code_climate:
|
||||
repo_token: 2935307212620b0e2228ab67eadd92c9f5501ddb60549d0d86007a354d56915b
|
||||
postgresql: "9.3"
|
||||
postgresql: "9.6"
|
||||
apt:
|
||||
packages:
|
||||
- mysql-server-5.6
|
||||
|
||||
@@ -472,7 +472,7 @@ $customer->loadDefaultValues();
|
||||
|
||||
### Attributes Typecasting <span id="attributes-typecasting"></span>
|
||||
|
||||
Being populated by query results [[yii\db\ActiveRecord]] performs automatic typecast for its attribute values, using
|
||||
Being populated by query results, [[yii\db\ActiveRecord]] performs automatic typecast for its attribute values, using
|
||||
information from [database table schema](db-dao.md#database-schema). This allows data retrieved from table column
|
||||
declared as integer to be populated in ActiveRecord instance with PHP integer, boolean with boolean and so on.
|
||||
However, typecasting mechanism has several limitations:
|
||||
@@ -490,7 +490,33 @@ converted during saving process.
|
||||
|
||||
> Tip: you may use [[yii\behaviors\AttributeTypecastBehavior]] to facilitate attribute values typecasting
|
||||
on ActiveRecord validation or saving.
|
||||
|
||||
Since 2.0.14, Yii ActiveRecord supports complex data types, such as JSON or multidimensional arrays.
|
||||
|
||||
#### JSON in MySQL and PostgreSQL
|
||||
|
||||
After data population, the value from JSON column will be automatically decoded from JSON according to standard JSON
|
||||
decoding rules.
|
||||
|
||||
To save attribute value to a JSON column, ActiveRecord will automatically create a [[yii\db\JsonExpression|JsonExpression]] object
|
||||
that will be encoded to a JSON string on [QueryBuilder](db-query-builder.md) level.
|
||||
|
||||
#### Arrays in PostgreSQL
|
||||
|
||||
After data population, the value from Array column will be automatically decoded from PgSQL notation to an [[yii\db\ArrayExpression|ArrayExpression]]
|
||||
object. It implements PHP `ArrayAccess` interface, so you can use it as an array, or call `->getValue()` to get the array itself.
|
||||
|
||||
To save attribute value to an array column, ActiveRecord will automatically create an [[yii\db\ArrayExpression|ArrayExpression]] object
|
||||
that will be encoded by [QueryBuilder](db-query-builder.md) to an PgSQL string representation of array.
|
||||
|
||||
You can also use conditions for JSON columns:
|
||||
|
||||
```php
|
||||
$query->andWhere(['=', 'json', new ArrayExpression(['foo' => 'bar'])
|
||||
```
|
||||
|
||||
To learn more about expressions building system read the [Query Builder – Adding custom Conditions and Expressions](db-query-builder.md#adding-custom-conditions-and-expressions)
|
||||
article.
|
||||
|
||||
### Updating Multiple Rows <span id="updating-multiple-rows"></span>
|
||||
|
||||
|
||||
@@ -160,12 +160,12 @@ are in the ["Quoting Tables" section of the "Database Access Objects" guide](gui
|
||||
### [[yii\db\Query::where()|where()]] <span id="where"></span>
|
||||
|
||||
The [[yii\db\Query::where()|where()]] method specifies the `WHERE` fragment of a SQL query. You can use one of
|
||||
the three formats to specify a `WHERE` condition:
|
||||
the four formats to specify a `WHERE` condition:
|
||||
|
||||
- string format, e.g., `'status=1'`
|
||||
- hash format, e.g. `['status' => 1, 'type' => 2]`
|
||||
- operator format, e.g. `['like', 'name', 'test']`
|
||||
|
||||
- object format, e.g. `new LikeCondition('name', 'LIKE', 'test')`
|
||||
|
||||
#### String Format <span id="string-format"></span>
|
||||
|
||||
@@ -306,6 +306,41 @@ the operator can be one of the following:
|
||||
Using the Operator Format, Yii internally uses parameter binding so in contrast to the [string format](#string-format), here
|
||||
you do not have to add parameters manually.
|
||||
|
||||
#### Object Format <span id="object-format"></span>
|
||||
|
||||
Object Form is available since 2.0.14 and is both most powerful and most complex way to define conditions.
|
||||
You need to follow it either if you want to build your own abstraction over query builder or if you want to implement
|
||||
your own complex conditions.
|
||||
|
||||
Instances of condition classes are immutable. Their only purpose is to store condition data and provide getters
|
||||
for condition builders. Condition builder is a class that holds the logic that transforms data
|
||||
stored in condition into the SQL expression.
|
||||
|
||||
Internally the formats described above are implicitly converted to object format prior to building raw SQL,
|
||||
so it is possible to combine formats in a single condition:
|
||||
|
||||
```php
|
||||
$query->andWhere(new OrCondition([
|
||||
new InCondition('type', 'in', $types),
|
||||
['like', 'name', '%good%'],
|
||||
'disabled=false'
|
||||
]))
|
||||
```
|
||||
|
||||
Conversion from operator format into object format is performed according to
|
||||
[[yii\db\QueryBuilder::conditionClasses|QueryBuilder::conditionClasses]] property, that maps operators names
|
||||
to representative class names:
|
||||
|
||||
- `AND`, `OR` -> `yii\db\conditions\ConjunctionCondition`
|
||||
- `NOT` -> `yii\db\conditions\NotCondition`
|
||||
- `IN`, `NOT IN` -> `yii\db\conditions\InCondition`
|
||||
- `BETWEEN`, `NOT BETWEEN` -> `yii\db\conditions\BetweenCondition`
|
||||
|
||||
And so on.
|
||||
|
||||
Using the object format makes it possible to create your own conditions or to change the way default ones are built.
|
||||
See [Creating Custom Conditions and Expressions](#creating-custom-conditions-and-expressions) chapter to learn more.
|
||||
|
||||
|
||||
#### Appending Conditions <span id="appending-conditions"></span>
|
||||
|
||||
@@ -758,3 +793,170 @@ $unbufferedDb->close();
|
||||
```
|
||||
|
||||
> Note: unbuffered query uses less memory on the PHP-side, but can increase the load on the MySQL server. It is recommended to design your own code with your production practice for extra massive data, [for example, divide the range for integer keys, loop them with Unbuffered Queries](https://github.com/yiisoft/yii2/issues/8420#issuecomment-296109257).
|
||||
|
||||
### Adding custom Conditions and Expressions <span id="adding-custom-conditions-and-expressions"></span>
|
||||
|
||||
As it was mentioned in [Conditions – Object Fromat](#object-format) chapter, is is possible to create custom condition
|
||||
classes. For example, let's create a condition that will check that specific columns are less than some value.
|
||||
Using the operator format, it would look like the following:
|
||||
|
||||
```php
|
||||
[
|
||||
'and',
|
||||
'>', 'posts', $minLimit,
|
||||
'>', 'comments', $minLimit,
|
||||
'>', 'reactions', $minLimit,
|
||||
'>', 'subscriptions', $minLimit
|
||||
]
|
||||
```
|
||||
|
||||
When such condition applied once, it is fine. In case it is used multiple times in a single query it can
|
||||
be optimized a lot. Let's create a custom condition object to demonstrate it.
|
||||
|
||||
Yii has a [[yii\db\conditions\ConditionInterface|ConditionInterface]], that must be used to mark classes, that represent
|
||||
a condition. It requires `fromArrayDefinition()` method implementation, in order to make possible to create condition
|
||||
from array format. In case you don't need it, you can implement this method with exception throwing.
|
||||
|
||||
Since we create our custom condition class, we can build API that suits our task the most.
|
||||
|
||||
```php
|
||||
namespace app\db\conditions;
|
||||
|
||||
class AllGreaterCondition implements \yii\db\conditions\ConditionInterface
|
||||
{
|
||||
private $columns;
|
||||
private $value;
|
||||
|
||||
/**
|
||||
* @param string[] $columns Array of columns that must be greater, than $value
|
||||
* @param mixed $value the value to compare each $column against.
|
||||
*/
|
||||
public function __construct(array $columns, $value)
|
||||
{
|
||||
$this->columns = $columns;
|
||||
$this->value = $value;
|
||||
}
|
||||
|
||||
public static function fromArrayDefinition($operator, $operands)
|
||||
{
|
||||
throw new InvalidParamException('Not implemented yet, but we will do it later');
|
||||
}
|
||||
|
||||
public function getColumns() { return $this->columns; }
|
||||
public function getValue() { return $this->vaule; }
|
||||
}
|
||||
```
|
||||
|
||||
So we can create a condition object:
|
||||
|
||||
```php
|
||||
$conditon = new AllGreaterCondition(['col1', 'col2'], 42);
|
||||
```
|
||||
|
||||
But `QueryBuilder` still does not know, to to make an SQL condition out of this object.
|
||||
Now we need to create a builder for this condition. It must implement [[yii\db\ExpressionBuilderInterface]]
|
||||
that requires us to implement a `build()` method.
|
||||
|
||||
```php
|
||||
namespace app\db\conditions;
|
||||
|
||||
class AllGreaterConditionBuilder implements \yii\db\ExpressionBuilderInterface
|
||||
{
|
||||
use \yii\db\Condition\ExpressionBuilderTrait; // Contains constructor and `queryBuilder` property.
|
||||
|
||||
/**
|
||||
* @param AllGreaterCondition $condition the condition to be built
|
||||
* @param array $params the binding parameters.
|
||||
*/
|
||||
public function build(ConditionInterface $condition, &$params)
|
||||
{
|
||||
$value = $condition->getValue();
|
||||
|
||||
$conditions = [];
|
||||
foreach ($condition->getColumns() as $column) {
|
||||
$conditions[] = new SimpleCondition($column, '>', $value);
|
||||
}
|
||||
|
||||
return $this->queryBuider->buildCondition(new AndCondition($conditions), $params);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Then simple let [[yii\db\QueryBuilder|QueryBuilder]] know about our new condition – add a mapping for it to
|
||||
the `expressionBuilders` array. It could be done right from the application configuration:
|
||||
|
||||
```php
|
||||
'db' => [
|
||||
'class' => 'yii\db\mysql\Connection',
|
||||
// ...
|
||||
'queryBuilder' => [
|
||||
'expressionBuilders' => [
|
||||
'app\db\conditions\AllGreaterCondition' => 'app\db\conditions\AllGreaterConditionBuilder',
|
||||
],
|
||||
],
|
||||
],
|
||||
```
|
||||
|
||||
Now we can use our condition in `where()`:
|
||||
|
||||
```php
|
||||
$query->andWhere(new AllGreaterCondition(['posts', 'comments', 'reactions', 'subscriptions'], $minValue));
|
||||
```
|
||||
|
||||
If we want to make it possible to create our custom condition using operator format, we should declare it in
|
||||
[[yii\db\QueryBuilder::conditionClasses|QueryBuilder::conditionClasses]]:
|
||||
|
||||
```php
|
||||
'db' => [
|
||||
'class' => 'yii\db\mysql\Connection',
|
||||
// ...
|
||||
'queryBuilder' => [
|
||||
'expressionBuilders' => [
|
||||
'app\db\conditions\AllGreaterCondition' => 'app\db\conditions\AllGreaterConditionBuilder',
|
||||
],
|
||||
'conditionClasses' => [
|
||||
'ALL>' => 'app\db\conditions\AllGreaterCondition',
|
||||
],
|
||||
],
|
||||
],
|
||||
```
|
||||
|
||||
And create a real implementation of `AllGreaterCondition::fromArrayDefinition()` method
|
||||
in `app\db\conditions\AllGreaterCondition`:
|
||||
|
||||
```php
|
||||
namespace app\db\conditions;
|
||||
|
||||
class AllGreaterCondition implements \yii\db\conditions\ConditionInterface
|
||||
{
|
||||
// ... see the implementation above
|
||||
|
||||
public static function fromArrayDefinition($operator, $operands)
|
||||
{
|
||||
return new static($operands[0], $operands[1]);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
After that, we can create our custom condition using shorter operator format:
|
||||
|
||||
```php
|
||||
$query->andWhere(['ALL>', ['posts', 'comments', 'reactions', 'subscriptions'], $minValue]);
|
||||
```
|
||||
|
||||
You might notice, that there was two concepts used: Expressions and Conditions. There is a [[yii\db\ExpressionInterface]]
|
||||
that should be used to mark objects, that require an Expression Builder class, that implements
|
||||
[[yii\db\ExpressionBuilderInterface]] to be built. Also there is a [[yii\db\condition\ConditionInterface]], that extends
|
||||
[[yii\db\ExpressionInterface|ExpressionInterface]] and should be used to objects, that can be created from array definition
|
||||
as it was shown above, but require builder as well.
|
||||
|
||||
To summarise:
|
||||
|
||||
- Expression – is a Data Transfer Object (DTO) for a dataset, that can be somehow compiled to some SQL
|
||||
statement (an operator, string, array, JSON, etc).
|
||||
- Condition – is an Expression superset, that aggregates multiple Expressions (or scalar values) that can be compiled
|
||||
to a single SQL condition.
|
||||
|
||||
You can create your own classes that implement [[yii\db\ExpressionInterface|ExpressionInterface]] to hide the complexity
|
||||
of transforming data to SQL statements. You will learn more about other examples of Expressions in the
|
||||
[next article](db-active-record.md);
|
||||
|
||||
@@ -157,6 +157,7 @@ Yii Framework 2 Change Log
|
||||
- Enh #4495: Added closure support in `yii\i18n\Formatter` (developeruz)
|
||||
- Enh #5786: Allowed to use custom constructors in ActiveRecord-based classes (ElisDN, klimov-paul)
|
||||
- Enh #6644: Added `yii\helpers\ArrayHelper::setValue()` (LAV45)
|
||||
- Enh #7640: Implemented custom data types support. Added JSON support for MySQL and PostgreSQL, array support for PostgreSQL (silverfire, cebe)
|
||||
- Enh #7823: Added `yii\filters\AjaxFilter` filter (dmirogin)
|
||||
- Enh #9438: `yii\web\DbSession` now relies on error handler to display errors (samdark)
|
||||
- Enh #9703, #9709: Added `yii\i18n\Formatter::asWeight()` and `::asLength()` formatters (nineinchnick, silverfire)
|
||||
|
||||
@@ -58,6 +58,20 @@ Upgrade from Yii 2.0.13
|
||||
|
||||
* `yii\base\Security::compareString()` is now throwing `yii\base\InvalidParamException` in case non-strings are compared.
|
||||
|
||||
* `yii\db\ExpressionInterface` has been introduced to represent a wider range of SQL expressions. In case you check for
|
||||
`instanceof yii\db\Expression` in your code, you might consider changing that to checking for the interface and use the newly
|
||||
introduced methods to retrieve the expression content.
|
||||
|
||||
* `yii\db\PdoValue` class has been introduced to replace a special syntax that was used to declare PDO parameter type
|
||||
when binding parameters to an SQL command, for example: `['value', \PDO::PARAM_STR]`.
|
||||
You should use `new PdoValue('value', \PDO::PARAM_STR)` instead. Old syntax will be removed in Yii 2.1.
|
||||
|
||||
* `yii\db\QueryBuilder::conditionBuilders` property and method-based condition builders are no longer used.
|
||||
Class-based conditions and builders are introduces instead to provide more flexibility, extensibility and
|
||||
space to customization. In case you rely on that property or override any of default condition builders, follow the
|
||||
special [guide article](http://www.yiiframework.com/doc-2.0/guide-db-query-builder.html#adding-custom-conditions-and-expressions)
|
||||
to update your code.
|
||||
|
||||
* Log targets (like `yii\log\EmailTarget`) are now throwing `yii\log\LogRuntimeException` in case log can not be properly exported.
|
||||
|
||||
Upgrade from Yii 2.0.12
|
||||
|
||||
@@ -10,6 +10,7 @@ namespace yii\caching;
|
||||
use Yii;
|
||||
use yii\base\InvalidConfigException;
|
||||
use yii\db\Connection;
|
||||
use yii\db\PdoValue;
|
||||
use yii\db\Query;
|
||||
use yii\di\Instance;
|
||||
|
||||
@@ -195,7 +196,7 @@ class DbCache extends Cache
|
||||
$command = $db->createCommand()
|
||||
->update($this->cacheTable, [
|
||||
'expire' => $duration > 0 ? $duration + time() : 0,
|
||||
'data' => [$value, \PDO::PARAM_LOB],
|
||||
'data' => new PdoValue($value, \PDO::PARAM_LOB),
|
||||
], ['id' => $key]);
|
||||
return $command->execute();
|
||||
});
|
||||
@@ -228,7 +229,7 @@ class DbCache extends Cache
|
||||
->insert($this->cacheTable, [
|
||||
'id' => $key,
|
||||
'expire' => $duration > 0 ? $duration + time() : 0,
|
||||
'data' => [$value, \PDO::PARAM_LOB],
|
||||
'data' => new PdoValue($value, \PDO::PARAM_LOB),
|
||||
])->execute();
|
||||
});
|
||||
|
||||
|
||||
164
framework/db/ArrayExpression.php
Normal file
164
framework/db/ArrayExpression.php
Normal file
@@ -0,0 +1,164 @@
|
||||
<?php
|
||||
|
||||
namespace yii\db;
|
||||
|
||||
/**
|
||||
* Class ArrayExpression represents an array SQL expression.
|
||||
*
|
||||
* Expressions of this type can be used in conditions as well:
|
||||
*
|
||||
* ```php
|
||||
* $query->andWhere(['@>', 'items', new ArrayExpression([1, 2, 3], 'integer')])
|
||||
* ```
|
||||
*
|
||||
* which, depending on DBMS, will result in a well-prepared condition. For example, in
|
||||
* PostgreSQL it will be compiled to `WHERE "items" @> ARRAY[1, 2, 3]::integer[]`.
|
||||
*
|
||||
* @author Dmytro Naumenko <d.naumenko.a@gmail.com>
|
||||
* @since 2.0.14
|
||||
*/
|
||||
class ArrayExpression implements ExpressionInterface, \ArrayAccess, \Countable
|
||||
{
|
||||
/**
|
||||
* @var null|string the type of the array elements. Defaults to `null` which means the type is
|
||||
* not explicitly specified.
|
||||
*
|
||||
* Note that in case when type is not specified explicitly and DBMS can not guess it from the context,
|
||||
* SQL error will be raised.
|
||||
*/
|
||||
private $type;
|
||||
/**
|
||||
* @var array|QueryInterface|mixed the array content. Either represented as an array of values or a [[Query]] that
|
||||
* returns these values. A single value will be considered as an array containing one element.
|
||||
*/
|
||||
private $value;
|
||||
/**
|
||||
* @var int the number of indices needed to select an element
|
||||
*/
|
||||
private $dimension;
|
||||
|
||||
/**
|
||||
* ArrayExpression constructor.
|
||||
*
|
||||
* @param array|QueryInterface|mixed $value the array content. Either represented as an array of values or a Query that
|
||||
* returns these values. A single value will be considered as an array containing one element.
|
||||
* @param string|null $type the type of the array elements. Defaults to `null` which means the type is
|
||||
* not explicitly specified. In case when type is not specified explicitly and DBMS can not guess it from the context,
|
||||
* SQL error will be raised.
|
||||
* @param int $dimension the number of indices needed to select an element
|
||||
*/
|
||||
public function __construct($value, $type = null, $dimension = 1)
|
||||
{
|
||||
$this->value = $value;
|
||||
$this->type = $type;
|
||||
$this->dimension = $dimension;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return null|string
|
||||
* @see type
|
||||
*/
|
||||
public function getType()
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array|mixed|QueryInterface
|
||||
* @see value
|
||||
*/
|
||||
public function getValue()
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int the number of indices needed to select an element
|
||||
* @see dimensions
|
||||
*/
|
||||
public function getDimension()
|
||||
{
|
||||
return $this->dimension;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether a offset exists
|
||||
*
|
||||
* @link http://php.net/manual/en/arrayaccess.offsetexists.php
|
||||
* @param mixed $offset <p>
|
||||
* An offset to check for.
|
||||
* </p>
|
||||
* @return boolean true on success or false on failure.
|
||||
* </p>
|
||||
* <p>
|
||||
* The return value will be casted to boolean if non-boolean was returned.
|
||||
* @since 5.0.0
|
||||
*/
|
||||
public function offsetExists($offset)
|
||||
{
|
||||
return isset($this->value[$offset]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Offset to retrieve
|
||||
*
|
||||
* @link http://php.net/manual/en/arrayaccess.offsetget.php
|
||||
* @param mixed $offset <p>
|
||||
* The offset to retrieve.
|
||||
* </p>
|
||||
* @return mixed Can return all value types.
|
||||
* @since 5.0.0
|
||||
*/
|
||||
public function offsetGet($offset)
|
||||
{
|
||||
return $this->value[$offset];
|
||||
}
|
||||
|
||||
/**
|
||||
* Offset to set
|
||||
*
|
||||
* @link http://php.net/manual/en/arrayaccess.offsetset.php
|
||||
* @param mixed $offset <p>
|
||||
* The offset to assign the value to.
|
||||
* </p>
|
||||
* @param mixed $value <p>
|
||||
* The value to set.
|
||||
* </p>
|
||||
* @return void
|
||||
* @since 5.0.0
|
||||
*/
|
||||
public function offsetSet($offset, $value)
|
||||
{
|
||||
$this->value[$offset] = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Offset to unset
|
||||
*
|
||||
* @link http://php.net/manual/en/arrayaccess.offsetunset.php
|
||||
* @param mixed $offset <p>
|
||||
* The offset to unset.
|
||||
* </p>
|
||||
* @return void
|
||||
* @since 5.0.0
|
||||
*/
|
||||
public function offsetUnset($offset)
|
||||
{
|
||||
unset($this->value[$offset]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Count elements of an object
|
||||
*
|
||||
* @link http://php.net/manual/en/countable.count.php
|
||||
* @return int The custom count as an integer.
|
||||
* </p>
|
||||
* <p>
|
||||
* The return value is cast to an integer.
|
||||
* @since 5.1.0
|
||||
*/
|
||||
public function count()
|
||||
{
|
||||
return count($this->value);
|
||||
}
|
||||
}
|
||||
@@ -34,7 +34,7 @@ class ColumnSchema extends BaseObject
|
||||
public $type;
|
||||
/**
|
||||
* @var string the PHP type of this column. Possible PHP types include:
|
||||
* `string`, `boolean`, `integer`, `double`.
|
||||
* `string`, `boolean`, `integer`, `double`, `array`.
|
||||
*/
|
||||
public $phpType;
|
||||
/**
|
||||
@@ -114,12 +114,16 @@ class ColumnSchema extends BaseObject
|
||||
*/
|
||||
protected function typecast($value)
|
||||
{
|
||||
if ($value === '' && $this->type !== Schema::TYPE_TEXT && $this->type !== Schema::TYPE_STRING && $this->type !== Schema::TYPE_BINARY && $this->type !== Schema::TYPE_CHAR) {
|
||||
if ($value === '' && !in_array($this->type, [Schema::TYPE_TEXT, Schema::TYPE_STRING, Schema::TYPE_BINARY, Schema::TYPE_CHAR], true)) {
|
||||
return null;
|
||||
}
|
||||
if ($value === null || gettype($value) === $this->phpType || $value instanceof Expression || $value instanceof Query) {
|
||||
if ($value === null || gettype($value) === $this->phpType || $value instanceof ExpressionInterface || $value instanceof Query) {
|
||||
return $value;
|
||||
}
|
||||
if (is_array($value) && count($value) === 2 && isset($value[1]) && in_array($value[1], $this->getPdoParamTypes(), true)) {
|
||||
return new PdoValue($value[0], $value[1]);
|
||||
}
|
||||
|
||||
switch ($this->phpType) {
|
||||
case 'resource':
|
||||
case 'string':
|
||||
@@ -143,4 +147,12 @@ class ColumnSchema extends BaseObject
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int[] array of numbers that represent possible PDO parameter types
|
||||
*/
|
||||
private function getPdoParamTypes()
|
||||
{
|
||||
return [\PDO::PARAM_BOOL, \PDO::PARAM_INT, \PDO::PARAM_STR, \PDO::PARAM_LOB, \PDO::PARAM_NULL, \PDO::PARAM_STMT];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -347,8 +347,8 @@ class Command extends Component
|
||||
* @param array $values the values to be bound. This must be given in terms of an associative
|
||||
* array with array keys being the parameter names, and array values the corresponding parameter values,
|
||||
* e.g. `[':name' => 'John', ':age' => 25]`. By default, the PDO type of each value is determined
|
||||
* by its PHP type. You may explicitly specify the PDO type by using an array: `[value, type]`,
|
||||
* e.g. `[':name' => 'John', ':profile' => [$profile, \PDO::PARAM_LOB]]`.
|
||||
* by its PHP type. You may explicitly specify the PDO type by using a [[yii\db\PdoValue]] class: `new PdoValue(value, type)`,
|
||||
* e.g. `[':name' => 'John', ':profile' => new PdoValue($profile, \PDO::PARAM_LOB)]`.
|
||||
* @return $this the current command being executed
|
||||
*/
|
||||
public function bindValues($values)
|
||||
@@ -359,9 +359,9 @@ class Command extends Component
|
||||
|
||||
$schema = $this->db->getSchema();
|
||||
foreach ($values as $name => $value) {
|
||||
if (is_array($value)) {
|
||||
$this->_pendingParams[$name] = $value;
|
||||
$this->params[$name] = $value[0];
|
||||
if ($value instanceof PdoValue) {
|
||||
$this->_pendingParams[$name] = [$value->getValue(), $value->getType()];
|
||||
$this->params[$name] = $value->getValue();
|
||||
} else {
|
||||
$type = $schema->getPdoType($value);
|
||||
$this->_pendingParams[$name] = [$value, $type];
|
||||
|
||||
@@ -834,6 +834,17 @@ class Connection extends Component
|
||||
return $this->getSchema()->getQueryBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Can be used to set [[QueryBuilder]] configuration via Connection configuration array.
|
||||
*
|
||||
* @param $value
|
||||
* @since 2.0.14
|
||||
*/
|
||||
public function setQueryBuilder($value)
|
||||
{
|
||||
Yii::configure($this->getQueryBuilder(), $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtains the schema information for the named table.
|
||||
* @param string $name table name.
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace yii\db;
|
||||
* @author Qiang Xue <qiang.xue@gmail.com>
|
||||
* @since 2.0
|
||||
*/
|
||||
class Expression extends \yii\base\BaseObject
|
||||
class Expression extends \yii\base\BaseObject implements ExpressionInterface
|
||||
{
|
||||
/**
|
||||
* @var string the DB expression
|
||||
@@ -41,7 +41,6 @@ class Expression extends \yii\base\BaseObject
|
||||
*/
|
||||
public $params = [];
|
||||
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
* @param string $expression the DB expression
|
||||
@@ -57,7 +56,7 @@ class Expression extends \yii\base\BaseObject
|
||||
|
||||
/**
|
||||
* String magic method.
|
||||
* @return string the DB expression
|
||||
* @return string the DB expression.
|
||||
*/
|
||||
public function __toString()
|
||||
{
|
||||
|
||||
24
framework/db/ExpressionBuilder.php
Normal file
24
framework/db/ExpressionBuilder.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace yii\db;
|
||||
|
||||
/**
|
||||
* Interface ExpressionBuilderInterface
|
||||
*
|
||||
* @author Dmitry Naumenko <d.naumenko.a@gmail.com>
|
||||
* @since 2.0.14
|
||||
*/
|
||||
class ExpressionBuilder implements ExpressionBuilderInterface
|
||||
{
|
||||
use ExpressionBuilderTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @param Expression|ExpressionInterface $expression the expression to be built
|
||||
*/
|
||||
public function build(ExpressionInterface $expression, array &$params = [])
|
||||
{
|
||||
$params = array_merge($params, $expression->params);
|
||||
return $expression->__toString();
|
||||
}
|
||||
}
|
||||
23
framework/db/ExpressionBuilderInterface.php
Normal file
23
framework/db/ExpressionBuilderInterface.php
Normal file
@@ -0,0 +1,23 @@
|
||||
<?php
|
||||
|
||||
namespace yii\db;
|
||||
|
||||
/**
|
||||
* Interface ExpressionBuilderInterface is designed to build raw SQL from specific expression
|
||||
* objects that implement [[ExpressionInterface]].
|
||||
*
|
||||
* @author Dmitry Naumenko <d.naumenko.a@gmail.com>
|
||||
* @since 2.0.14
|
||||
*/
|
||||
interface ExpressionBuilderInterface
|
||||
{
|
||||
/**
|
||||
* Method builds the raw SQL from the $expression that will not be additionally
|
||||
* escaped or quoted.
|
||||
*
|
||||
* @param ExpressionInterface $expression the expression to be built.
|
||||
* @param array $params the binding parameters.
|
||||
* @return string the raw SQL that will not be additionally escaped or quoted.
|
||||
*/
|
||||
public function build(ExpressionInterface $expression, array &$params = []);
|
||||
}
|
||||
28
framework/db/ExpressionBuilderTrait.php
Normal file
28
framework/db/ExpressionBuilderTrait.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace yii\db;
|
||||
|
||||
/**
|
||||
* Trait ExpressionBuilderTrait provides common constructor for classes that
|
||||
* should implement [[ExpressionBuilderInterface]]
|
||||
*
|
||||
* @author Dmytro Naumenko <d.naumenko.a@gmail.com>
|
||||
* @since 2.0.14
|
||||
*/
|
||||
trait ExpressionBuilderTrait
|
||||
{
|
||||
/**
|
||||
* @var QueryBuilder
|
||||
*/
|
||||
protected $queryBuilder;
|
||||
|
||||
/**
|
||||
* ExpressionBuilderTrait constructor.
|
||||
*
|
||||
* @param QueryBuilder $queryBuilder
|
||||
*/
|
||||
public function __construct(QueryBuilder $queryBuilder)
|
||||
{
|
||||
$this->queryBuilder = $queryBuilder;
|
||||
}
|
||||
}
|
||||
19
framework/db/ExpressionInterface.php
Normal file
19
framework/db/ExpressionInterface.php
Normal file
@@ -0,0 +1,19 @@
|
||||
<?php
|
||||
|
||||
namespace yii\db;
|
||||
|
||||
/**
|
||||
* Interface ExpressionInterface should be used to mark classes, that should be built
|
||||
* in a special way.
|
||||
*
|
||||
* The database abstraction layer of Yii framework supports objects that implement this
|
||||
* interface and will use [[ExpressionBuilderInterface]] to build them.
|
||||
*
|
||||
* The default implementation is a class [[Expression]].
|
||||
*
|
||||
* @author Dmytro Naumenko <d.naumenko.a@gmail.com>
|
||||
* @since 2.0.14
|
||||
*/
|
||||
interface ExpressionInterface
|
||||
{
|
||||
}
|
||||
68
framework/db/JsonExpression.php
Normal file
68
framework/db/JsonExpression.php
Normal file
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
|
||||
namespace yii\db;
|
||||
|
||||
/**
|
||||
* Class JsonExpression represents data that should be encoded to JSON.
|
||||
*
|
||||
* For example:
|
||||
*
|
||||
* ```php
|
||||
* new JsonExpression(['a' => 1, 'b' => 2]); // will be encoded to '{"a": 1, "b": 2}'
|
||||
* ```
|
||||
*
|
||||
* @author Dmytro Naumenko <d.naumenko.a@gmail.com>
|
||||
* @since 2.0.14
|
||||
*/
|
||||
class JsonExpression implements ExpressionInterface
|
||||
{
|
||||
const TYPE_JSON = 'json';
|
||||
const TYPE_JSONB = 'jsonb';
|
||||
|
||||
/**
|
||||
* @var mixed the value to be encoded to JSON.
|
||||
* The value must be compatible with [\yii\helpers\Json::encode()|Json::encode()]] input requirements.
|
||||
*/
|
||||
protected $value;
|
||||
|
||||
/**
|
||||
* @var string|null Type of JSON, expression should be casted to. Defaults to `null`, meaning
|
||||
* no explicit casting will be performed.
|
||||
* This property will be encountered only for DBMSs that support different types of JSON.
|
||||
* For example, PostgreSQL has `json` and `jsonb` types.
|
||||
*/
|
||||
protected $type;
|
||||
|
||||
/**
|
||||
* JsonExpression constructor.
|
||||
*
|
||||
* @param mixed $value the value to be encoded to JSON.
|
||||
* The value must be compatible with [\yii\helpers\Json::encode()|Json::encode()]] requirements.
|
||||
* @param string|null $type the type of the JSON. See [[JsonExpression::type]]
|
||||
*
|
||||
* @see type
|
||||
*/
|
||||
public function __construct($value, $type = null)
|
||||
{
|
||||
$this->value = $value;
|
||||
$this->type = $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
* @see value
|
||||
*/
|
||||
public function getValue()
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return null|string the type of JSON
|
||||
* @see type
|
||||
*/
|
||||
public function getType()
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
}
|
||||
59
framework/db/PdoValue.php
Normal file
59
framework/db/PdoValue.php
Normal file
@@ -0,0 +1,59 @@
|
||||
<?php
|
||||
|
||||
namespace yii\db;
|
||||
|
||||
/**
|
||||
* Class PdoValue represents a $value that should be bound to PDO with exact $type.
|
||||
*
|
||||
* For example, it will be useful when you need to bind binary data to BLOB column in DBMS:
|
||||
*
|
||||
* ```php
|
||||
* [':name' => 'John', ':profile' => new PdoValue($profile, \PDO::PARAM_LOB)]`.
|
||||
* ```
|
||||
*
|
||||
* To see possible types, check [PDO::PARAM_* constants](http://php.net/manual/en/pdo.constants.php).
|
||||
*
|
||||
* @see http://php.net/manual/en/pdostatement.bindparam.php
|
||||
* @author Dmytro Naumenko <d.naumenko.a@gmail.com>
|
||||
* @since 2.0.14
|
||||
*/
|
||||
final class PdoValue implements ExpressionInterface
|
||||
{
|
||||
/**
|
||||
* @var mixed
|
||||
*/
|
||||
private $value;
|
||||
/**
|
||||
* @var int One of PDO_PARAM_* constants
|
||||
* @see http://php.net/manual/en/pdo.constants.php
|
||||
*/
|
||||
private $type;
|
||||
|
||||
/**
|
||||
* PdoValue constructor.
|
||||
*
|
||||
* @param $value
|
||||
* @param $type
|
||||
*/
|
||||
public function __construct($value, $type)
|
||||
{
|
||||
$this->value = $value;
|
||||
$this->type = $type;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getValue()
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return int
|
||||
*/
|
||||
public function getType()
|
||||
{
|
||||
return $this->type;
|
||||
}
|
||||
}
|
||||
25
framework/db/PdoValueBuilder.php
Normal file
25
framework/db/PdoValueBuilder.php
Normal file
@@ -0,0 +1,25 @@
|
||||
<?php
|
||||
|
||||
namespace yii\db;
|
||||
|
||||
/**
|
||||
* Class PdoValue builder builds object of the [[PdoValue]] expression class.
|
||||
*
|
||||
* @author Dmytro Naumenko <d.naumenko.a@gmail.com>
|
||||
* @since 2.0.14
|
||||
*/
|
||||
class PdoValueBuilder implements ExpressionBuilderInterface
|
||||
{
|
||||
const PARAM_PREFIX = ':pv';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function build(ExpressionInterface $expression, array &$params = [])
|
||||
{
|
||||
$placeholder = static::PARAM_PREFIX . count($params);
|
||||
$params[$placeholder] = $expression;
|
||||
|
||||
return $placeholder;
|
||||
}
|
||||
}
|
||||
@@ -97,7 +97,7 @@ class Query extends Component implements QueryInterface
|
||||
*/
|
||||
public $join;
|
||||
/**
|
||||
* @var string|array|Expression the condition to be applied in the GROUP BY clause.
|
||||
* @var string|array|ExpressionInterface the condition to be applied in the GROUP BY clause.
|
||||
* It can be either a string or an array. Please refer to [[where()]] on how to specify the condition.
|
||||
*/
|
||||
public $having;
|
||||
@@ -414,7 +414,7 @@ class Query extends Component implements QueryInterface
|
||||
/**
|
||||
* Queries a scalar value by setting [[select]] first.
|
||||
* Restores the value of select to make this query reusable.
|
||||
* @param string|Expression $selectExpression
|
||||
* @param string|ExpressionInterface $selectExpression
|
||||
* @param Connection|null $db
|
||||
* @return bool|string
|
||||
*/
|
||||
@@ -567,12 +567,12 @@ PATTERN;
|
||||
|
||||
/**
|
||||
* Sets the SELECT part of the query.
|
||||
* @param string|array|Expression $columns the columns to be selected.
|
||||
* @param string|array|ExpressionInterface $columns the columns to be selected.
|
||||
* Columns can be specified in either a string (e.g. "id, name") or an array (e.g. ['id', 'name']).
|
||||
* Columns can be prefixed with table names (e.g. "user.id") and/or contain column aliases (e.g. "user.id AS user_id").
|
||||
* The method will automatically quote the column names unless a column contains some parenthesis
|
||||
* (which means the column contains a DB expression). A DB expression may also be passed in form of
|
||||
* an [[Expression]] object.
|
||||
* an [[ExpressionInterface]] object.
|
||||
*
|
||||
* Note that if you are selecting an expression like `CONCAT(first_name, ' ', last_name)`, you should
|
||||
* use an array to specify the columns. Otherwise, the expression may be incorrectly split into several parts.
|
||||
@@ -589,7 +589,7 @@ PATTERN;
|
||||
*/
|
||||
public function select($columns, $option = null)
|
||||
{
|
||||
if ($columns instanceof Expression) {
|
||||
if ($columns instanceof ExpressionInterface) {
|
||||
$columns = [$columns];
|
||||
} elseif (!is_array($columns)) {
|
||||
$columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY);
|
||||
@@ -609,14 +609,14 @@ PATTERN;
|
||||
* $query->addSelect(["*", "CONCAT(first_name, ' ', last_name) AS full_name"])->one();
|
||||
* ```
|
||||
*
|
||||
* @param string|array|Expression $columns the columns to add to the select. See [[select()]] for more
|
||||
* @param string|array|ExpressionInterface $columns the columns to add to the select. See [[select()]] for more
|
||||
* details about the format of this parameter.
|
||||
* @return $this the query object itself
|
||||
* @see select()
|
||||
*/
|
||||
public function addSelect($columns)
|
||||
{
|
||||
if ($columns instanceof Expression) {
|
||||
if ($columns instanceof ExpressionInterface) {
|
||||
$columns = [$columns];
|
||||
} elseif (!is_array($columns)) {
|
||||
$columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY);
|
||||
@@ -689,7 +689,7 @@ PATTERN;
|
||||
|
||||
/**
|
||||
* Sets the FROM part of the query.
|
||||
* @param string|array|Expression $tables the table(s) to be selected from. This can be either a string (e.g. `'user'`)
|
||||
* @param string|array|ExpressionInterface $tables the table(s) to be selected from. This can be either a string (e.g. `'user'`)
|
||||
* or an array (e.g. `['user', 'profile']`) specifying one or several table names.
|
||||
* Table names can contain schema prefixes (e.g. `'public.user'`) and/or table aliases (e.g. `'user u'`).
|
||||
* The method will automatically quote the table names unless it contains some parenthesis
|
||||
@@ -701,7 +701,7 @@ PATTERN;
|
||||
* Use a Query object to represent a sub-query. In this case, the corresponding array key will be used
|
||||
* as the alias for the sub-query.
|
||||
*
|
||||
* To specify the `FROM` part in plain SQL, you may pass an instance of [[Expression]].
|
||||
* To specify the `FROM` part in plain SQL, you may pass an instance of [[ExpressionInterface]].
|
||||
*
|
||||
* Here are some examples:
|
||||
*
|
||||
@@ -743,7 +743,7 @@ PATTERN;
|
||||
*
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @param string|array|Expression $condition the conditions that should be put in the WHERE part.
|
||||
* @param string|array|ExpressionInterface $condition the conditions that should be put in the WHERE part.
|
||||
* @param array $params the parameters (name => value) to be bound to the query.
|
||||
* @return $this the query object itself
|
||||
* @see andWhere()
|
||||
@@ -760,7 +760,7 @@ PATTERN;
|
||||
/**
|
||||
* Adds an additional WHERE condition to the existing one.
|
||||
* The new condition and the existing one will be joined using the `AND` operator.
|
||||
* @param string|array|Expression $condition the new WHERE condition. Please refer to [[where()]]
|
||||
* @param string|array|ExpressionInterface $condition the new WHERE condition. Please refer to [[where()]]
|
||||
* on how to specify this parameter.
|
||||
* @param array $params the parameters (name => value) to be bound to the query.
|
||||
* @return $this the query object itself
|
||||
@@ -783,7 +783,7 @@ PATTERN;
|
||||
/**
|
||||
* Adds an additional WHERE condition to the existing one.
|
||||
* The new condition and the existing one will be joined using the `OR` operator.
|
||||
* @param string|array|Expression $condition the new WHERE condition. Please refer to [[where()]]
|
||||
* @param string|array|ExpressionInterface $condition the new WHERE condition. Please refer to [[where()]]
|
||||
* on how to specify this parameter.
|
||||
* @param array $params the parameters (name => value) to be bound to the query.
|
||||
* @return $this the query object itself
|
||||
@@ -949,7 +949,7 @@ PATTERN;
|
||||
|
||||
/**
|
||||
* Sets the GROUP BY part of the query.
|
||||
* @param string|array|Expression $columns the columns to be grouped by.
|
||||
* @param string|array|ExpressionInterface $columns the columns to be grouped by.
|
||||
* Columns can be specified in either a string (e.g. "id, name") or an array (e.g. ['id', 'name']).
|
||||
* The method will automatically quote the column names unless a column contains some parenthesis
|
||||
* (which means the column contains a DB expression).
|
||||
@@ -958,13 +958,13 @@ PATTERN;
|
||||
* to represent the group-by information. Otherwise, the method will not be able to correctly determine
|
||||
* the group-by columns.
|
||||
*
|
||||
* Since version 2.0.7, an [[Expression]] object can be passed to specify the GROUP BY part explicitly in plain SQL.
|
||||
* Since version 2.0.7, an [[ExpressionInterface]] object can be passed to specify the GROUP BY part explicitly in plain SQL.
|
||||
* @return $this the query object itself
|
||||
* @see addGroupBy()
|
||||
*/
|
||||
public function groupBy($columns)
|
||||
{
|
||||
if ($columns instanceof Expression) {
|
||||
if ($columns instanceof ExpressionInterface) {
|
||||
$columns = [$columns];
|
||||
} elseif (!is_array($columns)) {
|
||||
$columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY);
|
||||
@@ -984,13 +984,13 @@ PATTERN;
|
||||
* to represent the group-by information. Otherwise, the method will not be able to correctly determine
|
||||
* the group-by columns.
|
||||
*
|
||||
* Since version 2.0.7, an [[Expression]] object can be passed to specify the GROUP BY part explicitly in plain SQL.
|
||||
* Since version 2.0.7, an [[ExpressionInterface]] object can be passed to specify the GROUP BY part explicitly in plain SQL.
|
||||
* @return $this the query object itself
|
||||
* @see groupBy()
|
||||
*/
|
||||
public function addGroupBy($columns)
|
||||
{
|
||||
if ($columns instanceof Expression) {
|
||||
if ($columns instanceof ExpressionInterface) {
|
||||
$columns = [$columns];
|
||||
} elseif (!is_array($columns)) {
|
||||
$columns = preg_split('/\s*,\s*/', trim($columns), -1, PREG_SPLIT_NO_EMPTY);
|
||||
@@ -1006,7 +1006,7 @@ PATTERN;
|
||||
|
||||
/**
|
||||
* Sets the HAVING part of the query.
|
||||
* @param string|array|Expression $condition the conditions to be put after HAVING.
|
||||
* @param string|array|ExpressionInterface $condition the conditions to be put after HAVING.
|
||||
* Please refer to [[where()]] on how to specify this parameter.
|
||||
* @param array $params the parameters (name => value) to be bound to the query.
|
||||
* @return $this the query object itself
|
||||
@@ -1023,7 +1023,7 @@ PATTERN;
|
||||
/**
|
||||
* Adds an additional HAVING condition to the existing one.
|
||||
* The new condition and the existing one will be joined using the `AND` operator.
|
||||
* @param string|array|Expression $condition the new HAVING condition. Please refer to [[where()]]
|
||||
* @param string|array|ExpressionInterface $condition the new HAVING condition. Please refer to [[where()]]
|
||||
* on how to specify this parameter.
|
||||
* @param array $params the parameters (name => value) to be bound to the query.
|
||||
* @return $this the query object itself
|
||||
@@ -1044,7 +1044,7 @@ PATTERN;
|
||||
/**
|
||||
* Adds an additional HAVING condition to the existing one.
|
||||
* The new condition and the existing one will be joined using the `OR` operator.
|
||||
* @param string|array|Expression $condition the new HAVING condition. Please refer to [[where()]]
|
||||
* @param string|array|ExpressionInterface $condition the new HAVING condition. Please refer to [[where()]]
|
||||
* on how to specify this parameter.
|
||||
* @param array $params the parameters (name => value) to be bound to the query.
|
||||
* @return $this the query object itself
|
||||
|
||||
@@ -9,7 +9,8 @@ namespace yii\db;
|
||||
|
||||
use yii\base\InvalidParamException;
|
||||
use yii\base\NotSupportedException;
|
||||
use yii\helpers\ArrayHelper;
|
||||
use yii\db\conditions\ConditionInterface;
|
||||
use yii\db\conditions\HashCondition;
|
||||
use yii\helpers\StringHelper;
|
||||
|
||||
/**
|
||||
@@ -50,39 +51,55 @@ class QueryBuilder extends \yii\base\BaseObject
|
||||
/**
|
||||
* @var array map of query condition to builder methods.
|
||||
* These methods are used by [[buildCondition]] to build SQL conditions from array syntax.
|
||||
* @deprecated since 2.0.14. Is not used, will be dropped in 2.1.0
|
||||
*/
|
||||
protected $conditionBuilders = [
|
||||
'NOT' => 'buildNotCondition',
|
||||
'AND' => 'buildAndCondition',
|
||||
'OR' => 'buildAndCondition',
|
||||
'BETWEEN' => 'buildBetweenCondition',
|
||||
'NOT BETWEEN' => 'buildBetweenCondition',
|
||||
'IN' => 'buildInCondition',
|
||||
'NOT IN' => 'buildInCondition',
|
||||
'LIKE' => 'buildLikeCondition',
|
||||
'NOT LIKE' => 'buildLikeCondition',
|
||||
'OR LIKE' => 'buildLikeCondition',
|
||||
'OR NOT LIKE' => 'buildLikeCondition',
|
||||
'EXISTS' => 'buildExistsCondition',
|
||||
'NOT EXISTS' => 'buildExistsCondition',
|
||||
];
|
||||
/**
|
||||
* @var array map of chars to their replacements in LIKE conditions.
|
||||
* By default it's configured to escape `%`, `_` and `\` with `\`.
|
||||
* @since 2.0.12.
|
||||
*/
|
||||
protected $likeEscapingReplacements = [
|
||||
'%' => '\%',
|
||||
'_' => '\_',
|
||||
'\\' => '\\\\',
|
||||
];
|
||||
/**
|
||||
* @var string|null character used to escape special characters in LIKE conditions.
|
||||
* By default it's assumed to be `\`.
|
||||
* @since 2.0.12
|
||||
*/
|
||||
protected $likeEscapeCharacter;
|
||||
protected $conditionBuilders = [];
|
||||
|
||||
/**
|
||||
* @var array map of condition aliases to condition classes. For example:
|
||||
*
|
||||
* ```php
|
||||
* return [
|
||||
* 'LIKE' => yii\db\condition\LikeCondition::class,
|
||||
* ];
|
||||
* ```
|
||||
*
|
||||
* This property is used by [[createConditionFromArray]] method.
|
||||
* See default condition classes list in [[defaultConditionClasses()]] method.
|
||||
*
|
||||
* In case you want to add custom conditions support, use the [[setConditionClasses()]] method.
|
||||
*
|
||||
* @see setConditonClasses()
|
||||
* @see defaultConditionClasses()
|
||||
* @since 2.0.14
|
||||
*/
|
||||
protected $conditionClasses = [];
|
||||
|
||||
/**
|
||||
* @var string[]|ExpressionBuilderInterface[] maps expression class to expression builder class.
|
||||
* For example:
|
||||
*
|
||||
* ```php
|
||||
* [
|
||||
* yii\db\Expression::class => yii\db\ExpressionBuilder::class
|
||||
* ]
|
||||
* ```
|
||||
* This property is mainly used by [[buildExpression()]] to build SQL expressions form expression objects.
|
||||
* See default values in [[defaultExpressionBuilders()]] method.
|
||||
*
|
||||
*
|
||||
* To override existing builders or add custom, use [[setExpressionBuilder()]] method. New items will be added
|
||||
* to the end of this array.
|
||||
*
|
||||
* To find a builder, [[buildExpression()]] will check the expression class for its exact presence in this map.
|
||||
* In case it is NOT present, the array will be iterated in reverse direction, checking whether the expression
|
||||
* extends the class, defined in this map.
|
||||
*
|
||||
* @see setExpressionBuilders()
|
||||
* @see defaultExpressionBuilders()
|
||||
* @since 2.0.14
|
||||
*/
|
||||
protected $expressionBuilders = [];
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
@@ -95,8 +112,85 @@ class QueryBuilder extends \yii\base\BaseObject
|
||||
parent::__construct($config);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function init()
|
||||
{
|
||||
parent::init();
|
||||
|
||||
$this->expressionBuilders = array_merge($this->defaultExpressionBuilders(), $this->expressionBuilders);
|
||||
$this->conditionClasses = array_merge($this->defaultConditionClasses(), $this->conditionClasses);
|
||||
}
|
||||
|
||||
/**
|
||||
* Contains array of default condition classes. Extend this method, if you want to change
|
||||
* default condition classes for the query builder. See [[conditionClasses]] docs for details.
|
||||
*
|
||||
* @return array
|
||||
* @see conditionClasses
|
||||
* @since 2.0.14
|
||||
*/
|
||||
protected function defaultConditionClasses()
|
||||
{
|
||||
return [
|
||||
'NOT' => 'yii\db\conditions\NotCondition',
|
||||
'AND' => 'yii\db\conditions\AndCondition',
|
||||
'OR' => 'yii\db\conditions\OrCondition',
|
||||
'BETWEEN' => 'yii\db\conditions\BetweenCondition',
|
||||
'NOT BETWEEN' => 'yii\db\conditions\BetweenCondition',
|
||||
'IN' => 'yii\db\conditions\InCondition',
|
||||
'NOT IN' => 'yii\db\conditions\InCondition',
|
||||
'LIKE' => 'yii\db\conditions\LikeCondition',
|
||||
'NOT LIKE' => 'yii\db\conditions\LikeCondition',
|
||||
'OR LIKE' => 'yii\db\conditions\LikeCondition',
|
||||
'OR NOT LIKE' => 'yii\db\conditions\LikeCondition',
|
||||
'EXISTS' => 'yii\db\conditions\ExistsCondition',
|
||||
'NOT EXISTS' => 'yii\db\conditions\ExistsCondition',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Contains array of default expression builders. Extend this method and override it, if you want to change
|
||||
* default expression builders for this query builder. See [[expressionBuilders]] docs for details.
|
||||
*
|
||||
* @return array
|
||||
* @see $expressionBuilders
|
||||
* @since 2.0.14
|
||||
*/
|
||||
protected function defaultExpressionBuilders()
|
||||
{
|
||||
return [
|
||||
'yii\db\PdoValue' => 'yii\db\PdoValueBuilder',
|
||||
'yii\db\Expression' => 'yii\db\ExpressionBuilder',
|
||||
'yii\db\conditions\ConjunctionCondition' => 'yii\db\conditions\ConjunctionConditionBuilder',
|
||||
'yii\db\conditions\NotCondition' => 'yii\db\conditions\NotConditionBuilder',
|
||||
'yii\db\conditions\AndCondition' => 'yii\db\conditions\ConjunctionConditionBuilder',
|
||||
'yii\db\conditions\OrCondition' => 'yii\db\conditions\ConjunctionConditionBuilder',
|
||||
'yii\db\conditions\BetweenCondition' => 'yii\db\conditions\BetweenConditionBuilder',
|
||||
'yii\db\conditions\InCondition' => 'yii\db\conditions\InConditionBuilder',
|
||||
'yii\db\conditions\LikeCondition' => 'yii\db\conditions\LikeConditionBuilder',
|
||||
'yii\db\conditions\ExistsCondition' => 'yii\db\conditions\ExistsConditionBuilder',
|
||||
'yii\db\conditions\SimpleCondition' => 'yii\db\conditions\SimpleConditionBuilder',
|
||||
'yii\db\conditions\HashCondition' => 'yii\db\conditions\HashConditionBuilder',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* Setter for [[expressionBuilders]] property.
|
||||
*
|
||||
* @param string[] $builders array of builder that should be merged with [[expressionBuilders]]
|
||||
* @since 2.0.14
|
||||
* @see expressionBuilders
|
||||
*/
|
||||
public function setExpressionBuilders($builders)
|
||||
{
|
||||
$this->expressionBuilders = array_merge($this->expressionBuilders, $builders);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a SELECT SQL statement from a [[Query]] object.
|
||||
*
|
||||
* @param Query $query the [[Query]] object from which the SQL statement will be generated.
|
||||
* @param array $params the parameters to be bound to the generated SQL statement. These parameters will
|
||||
* be included in the result with the additional parameters generated during the query building process.
|
||||
@@ -124,15 +218,15 @@ class QueryBuilder extends \yii\base\BaseObject
|
||||
|
||||
if (!empty($query->orderBy)) {
|
||||
foreach ($query->orderBy as $expression) {
|
||||
if ($expression instanceof Expression) {
|
||||
$params = array_merge($params, $expression->params);
|
||||
if ($expression instanceof ExpressionInterface) {
|
||||
$this->buildExpression($expression, $params);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!empty($query->groupBy)) {
|
||||
foreach ($query->groupBy as $expression) {
|
||||
if ($expression instanceof Expression) {
|
||||
$params = array_merge($params, $expression->params);
|
||||
if ($expression instanceof ExpressionInterface) {
|
||||
$this->buildExpression($expression, $params);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -145,6 +239,43 @@ class QueryBuilder extends \yii\base\BaseObject
|
||||
return [$sql, $params];
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds given $expression
|
||||
*
|
||||
* @param ExpressionInterface $expression the expression to be built
|
||||
* @param array $params the parameters to be bound to the generated SQL statement. These parameters will
|
||||
* be included in the result with the additional parameters generated during the expression building process.
|
||||
* @return string the SQL statement that will not be neither quoted nor encoded before passing to DBMS
|
||||
* @see ExpressionInterface
|
||||
* @see ExpressionBuilderInterface
|
||||
* @see expressionBuilders
|
||||
* @since 2.0.14
|
||||
* @throws InvalidParamException when $expression building is not supported by this QueryBuilder.
|
||||
*/
|
||||
public function buildExpression(ExpressionInterface $expression, &$params = [])
|
||||
{
|
||||
$className = get_class($expression);
|
||||
if (!isset($this->expressionBuilders[$className])) {
|
||||
foreach (array_reverse($this->expressionBuilders) as $expressionClass => $builderClass) {
|
||||
if (is_subclass_of($expression, $expressionClass)) {
|
||||
$this->expressionBuilders[$className] = $builderClass;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!isset($this->expressionBuilders[$className])) {
|
||||
throw new InvalidParamException('Expression of class ' . $className . ' can not be built in ' . get_class($this));
|
||||
}
|
||||
}
|
||||
|
||||
if (!is_object($this->expressionBuilders[$className])) {
|
||||
$this->expressionBuilders[$className] = new $this->expressionBuilders[$className]($this);
|
||||
}
|
||||
|
||||
$builder = $this->expressionBuilders[$className];
|
||||
return $builder->build($expression, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an INSERT SQL statement.
|
||||
*
|
||||
@@ -183,18 +314,15 @@ class QueryBuilder extends \yii\base\BaseObject
|
||||
} else {
|
||||
foreach ($columns as $name => $value) {
|
||||
$names[] = $schema->quoteColumnName($name);
|
||||
if ($value instanceof Expression) {
|
||||
$placeholders[] = $value->expression;
|
||||
foreach ($value->params as $n => $v) {
|
||||
$params[$n] = $v;
|
||||
}
|
||||
$value = isset($columnSchemas[$name]) ? $columnSchemas[$name]->dbTypecast($value) : $value;
|
||||
|
||||
if ($value instanceof ExpressionInterface) {
|
||||
$placeholders[] = $this->buildExpression($value, $params);
|
||||
} elseif ($value instanceof \yii\db\Query) {
|
||||
list($sql, $params) = $this->build($value, $params);
|
||||
$placeholders[] = "($sql)";
|
||||
} else {
|
||||
$phName = self::PARAM_PREFIX . count($params);
|
||||
$placeholders[] = $phName;
|
||||
$params[$phName] = !is_array($value) && isset($columnSchemas[$name]) ? $columnSchemas[$name]->dbTypecast($value) : $value;
|
||||
$placeholders[] = $this->bindParam($value, $params);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -276,7 +404,7 @@ class QueryBuilder extends \yii\base\BaseObject
|
||||
foreach ($rows as $row) {
|
||||
$vs = [];
|
||||
foreach ($row as $i => $value) {
|
||||
if (isset($columns[$i], $columnSchemas[$columns[$i]]) && !is_array($value)) {
|
||||
if (isset($columns[$i], $columnSchemas[$columns[$i]])) {
|
||||
$value = $columnSchemas[$columns[$i]]->dbTypecast($value);
|
||||
}
|
||||
if (is_string($value)) {
|
||||
@@ -335,15 +463,14 @@ class QueryBuilder extends \yii\base\BaseObject
|
||||
|
||||
$lines = [];
|
||||
foreach ($columns as $name => $value) {
|
||||
if ($value instanceof Expression) {
|
||||
$lines[] = $this->db->quoteColumnName($name) . '=' . $value->expression;
|
||||
foreach ($value->params as $n => $v) {
|
||||
$params[$n] = $v;
|
||||
}
|
||||
if ($value instanceof ExpressionInterface) {
|
||||
$lines[] = $this->db->quoteColumnName($name) . '=' . $this->buildExpression($value, $params);
|
||||
} else {
|
||||
$phName = self::PARAM_PREFIX . count($params);
|
||||
$phName = $this->bindParam(
|
||||
isset($columnSchemas[$name]) ? $columnSchemas[$name]->dbTypecast($value) : $value,
|
||||
$params
|
||||
);
|
||||
$lines[] = $this->db->quoteColumnName($name) . '=' . $phName;
|
||||
$params[$phName] = !is_array($value) && isset($columnSchemas[$name]) ? $columnSchemas[$name]->dbTypecast($value) : $value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -919,13 +1046,12 @@ class QueryBuilder extends \yii\base\BaseObject
|
||||
}
|
||||
|
||||
foreach ($columns as $i => $column) {
|
||||
if ($column instanceof Expression) {
|
||||
if ($column instanceof ExpressionInterface) {
|
||||
if (is_int($i)) {
|
||||
$columns[$i] = $column->expression;
|
||||
$columns[$i] = $this->buildExpression($column, $params);
|
||||
} else {
|
||||
$columns[$i] = $column->expression . ' AS ' . $this->db->quoteColumnName($i);
|
||||
$columns[$i] = $this->buildExpression($column, $params) . ' AS ' . $this->db->quoteColumnName($i);
|
||||
}
|
||||
$params = array_merge($params, $column->params);
|
||||
} elseif ($column instanceof Query) {
|
||||
list($sql, $params) = $this->build($column, $params);
|
||||
$columns[$i] = "($sql) AS " . $this->db->quoteColumnName($i);
|
||||
@@ -1046,8 +1172,8 @@ class QueryBuilder extends \yii\base\BaseObject
|
||||
return '';
|
||||
}
|
||||
foreach ($columns as $i => $column) {
|
||||
if ($column instanceof Expression) {
|
||||
$columns[$i] = $column->expression;
|
||||
if ($column instanceof ExpressionInterface) {
|
||||
$columns[$i] = $this->buildExpression($column);
|
||||
} elseif (strpos($column, '(') === false) {
|
||||
$columns[$i] = $this->db->quoteColumnName($column);
|
||||
}
|
||||
@@ -1101,8 +1227,8 @@ class QueryBuilder extends \yii\base\BaseObject
|
||||
}
|
||||
$orders = [];
|
||||
foreach ($columns as $name => $direction) {
|
||||
if ($direction instanceof Expression) {
|
||||
$orders[] = $direction->expression;
|
||||
if ($direction instanceof ExpressionInterface) {
|
||||
$orders[] = $this->buildExpression($direction);
|
||||
} else {
|
||||
$orders[] = $this->db->quoteColumnName($name) . ($direction === SORT_DESC ? ' DESC' : '');
|
||||
}
|
||||
@@ -1136,7 +1262,7 @@ class QueryBuilder extends \yii\base\BaseObject
|
||||
*/
|
||||
protected function hasLimit($limit)
|
||||
{
|
||||
return ($limit instanceof Expression) || ctype_digit((string) $limit);
|
||||
return ($limit instanceof ExpressionInterface) || ctype_digit((string) $limit);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1146,7 +1272,7 @@ class QueryBuilder extends \yii\base\BaseObject
|
||||
*/
|
||||
protected function hasOffset($offset)
|
||||
{
|
||||
return ($offset instanceof Expression) || ctype_digit((string) $offset) && (string) $offset !== '0';
|
||||
return ($offset instanceof ExpressionInterface) || ctype_digit((string) $offset) && (string) $offset !== '0';
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1194,8 +1320,8 @@ class QueryBuilder extends \yii\base\BaseObject
|
||||
}
|
||||
}
|
||||
foreach ($columns as $i => $column) {
|
||||
if ($column instanceof Expression) {
|
||||
$columns[$i] = $column->expression;
|
||||
if ($column instanceof ExpressionInterface) {
|
||||
$columns[$i] = $this->buildExpression($column);
|
||||
} elseif (strpos($column, '(') === false) {
|
||||
$columns[$i] = $this->db->quoteColumnName($column);
|
||||
}
|
||||
@@ -1206,38 +1332,51 @@ class QueryBuilder extends \yii\base\BaseObject
|
||||
|
||||
/**
|
||||
* Parses the condition specification and generates the corresponding SQL expression.
|
||||
* @param string|array|Expression $condition the condition specification. Please refer to [[Query::where()]]
|
||||
* @param string|array|ExpressionInterface $condition the condition specification. Please refer to [[Query::where()]]
|
||||
* on how to specify a condition.
|
||||
* @param array $params the binding parameters to be populated
|
||||
* @return string the generated SQL expression
|
||||
*/
|
||||
public function buildCondition($condition, &$params)
|
||||
{
|
||||
if ($condition instanceof Expression) {
|
||||
foreach ($condition->params as $n => $v) {
|
||||
$params[$n] = $v;
|
||||
}
|
||||
|
||||
return $condition->expression;
|
||||
} elseif (!is_array($condition)) {
|
||||
return (string) $condition;
|
||||
} elseif (empty($condition)) {
|
||||
return '';
|
||||
if (is_array($condition)) {
|
||||
$condition = $this->createConditionFromArray($condition);
|
||||
}
|
||||
|
||||
if ($condition instanceof ExpressionInterface) {
|
||||
return $this->buildExpression($condition, $params);
|
||||
}
|
||||
if (empty($condition)) {
|
||||
return '';
|
||||
}
|
||||
return (string) $condition;
|
||||
}
|
||||
|
||||
/**
|
||||
* Transforms $condition defined in array format (as described in [[Query::where()]]
|
||||
* to instance of [[yii\db\condition\ConditionInterface|ConditionInterface]] according to
|
||||
* [[conditionClasses]] map.
|
||||
*
|
||||
* @param string|array $condition
|
||||
* @see conditionClasses
|
||||
* @return ConditionInterface
|
||||
* @since 2.0.14
|
||||
*/
|
||||
public function createConditionFromArray($condition)
|
||||
{
|
||||
if (isset($condition[0])) { // operator format: operator, operand 1, operand 2, ...
|
||||
$operator = strtoupper($condition[0]);
|
||||
if (isset($this->conditionBuilders[$operator])) {
|
||||
$method = $this->conditionBuilders[$operator];
|
||||
$operator = strtoupper(array_shift($condition));
|
||||
if (isset($this->conditionClasses[$operator])) {
|
||||
$className = $this->conditionClasses[$operator];
|
||||
} else {
|
||||
$method = 'buildSimpleCondition';
|
||||
$className = 'yii\db\conditions\SimpleCondition';
|
||||
}
|
||||
array_shift($condition);
|
||||
return $this->$method($operator, $condition, $params);
|
||||
/** @var ConditionInterface $className */
|
||||
return $className::fromArrayDefinition($operator, $condition);
|
||||
}
|
||||
|
||||
// hash format: 'column1' => 'value1', 'column2' => 'value2', ...
|
||||
return $this->buildHashCondition($condition, $params);
|
||||
return new HashCondition($condition);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1245,34 +1384,11 @@ class QueryBuilder extends \yii\base\BaseObject
|
||||
* @param array $condition the condition specification.
|
||||
* @param array $params the binding parameters to be populated
|
||||
* @return string the generated SQL expression
|
||||
* @deprecated since 2.0.14. Use `buildCondition()` instead.
|
||||
*/
|
||||
public function buildHashCondition($condition, &$params)
|
||||
{
|
||||
$parts = [];
|
||||
foreach ($condition as $column => $value) {
|
||||
if (ArrayHelper::isTraversable($value) || $value instanceof Query) {
|
||||
// IN condition
|
||||
$parts[] = $this->buildInCondition('IN', [$column, $value], $params);
|
||||
} else {
|
||||
if (strpos($column, '(') === false) {
|
||||
$column = $this->db->quoteColumnName($column);
|
||||
}
|
||||
if ($value === null) {
|
||||
$parts[] = "$column IS NULL";
|
||||
} elseif ($value instanceof Expression) {
|
||||
$parts[] = "$column=" . $value->expression;
|
||||
foreach ($value->params as $n => $v) {
|
||||
$params[$n] = $v;
|
||||
}
|
||||
} else {
|
||||
$phName = self::PARAM_PREFIX . count($params);
|
||||
$parts[] = "$column=$phName";
|
||||
$params[$phName] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return count($parts) === 1 ? $parts[0] : '(' . implode(') AND (', $parts) . ')';
|
||||
return $this->buildCondition(new HashCondition($condition), $params);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1281,29 +1397,12 @@ class QueryBuilder extends \yii\base\BaseObject
|
||||
* @param array $operands the SQL expressions to connect.
|
||||
* @param array $params the binding parameters to be populated
|
||||
* @return string the generated SQL expression
|
||||
* @deprecated since 2.0.14. Use `buildCondition()` instead.
|
||||
*/
|
||||
public function buildAndCondition($operator, $operands, &$params)
|
||||
{
|
||||
$parts = [];
|
||||
foreach ($operands as $operand) {
|
||||
if (is_array($operand)) {
|
||||
$operand = $this->buildCondition($operand, $params);
|
||||
}
|
||||
if ($operand instanceof Expression) {
|
||||
foreach ($operand->params as $n => $v) {
|
||||
$params[$n] = $v;
|
||||
}
|
||||
$operand = $operand->expression;
|
||||
}
|
||||
if ($operand !== '') {
|
||||
$parts[] = $operand;
|
||||
}
|
||||
}
|
||||
if (!empty($parts)) {
|
||||
return '(' . implode(") $operator (", $parts) . ')';
|
||||
}
|
||||
|
||||
return '';
|
||||
array_unshift($operands, $operator);
|
||||
return $this->buildCondition($operands, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1313,22 +1412,12 @@ class QueryBuilder extends \yii\base\BaseObject
|
||||
* @param array $params the binding parameters to be populated
|
||||
* @return string the generated SQL expression
|
||||
* @throws InvalidParamException if wrong number of operands have been given.
|
||||
* @deprecated since 2.0.14. Use `buildCondition()` instead.
|
||||
*/
|
||||
public function buildNotCondition($operator, $operands, &$params)
|
||||
{
|
||||
if (count($operands) !== 1) {
|
||||
throw new InvalidParamException("Operator '$operator' requires exactly one operand.");
|
||||
}
|
||||
|
||||
$operand = reset($operands);
|
||||
if (is_array($operand) || $operand instanceof Expression) {
|
||||
$operand = $this->buildCondition($operand, $params);
|
||||
}
|
||||
if ($operand === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
return "$operator ($operand)";
|
||||
array_unshift($operands, $operator);
|
||||
return $this->buildCondition($operands, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1339,38 +1428,12 @@ class QueryBuilder extends \yii\base\BaseObject
|
||||
* @param array $params the binding parameters to be populated
|
||||
* @return string the generated SQL expression
|
||||
* @throws InvalidParamException if wrong number of operands have been given.
|
||||
* @deprecated since 2.0.14. Use `buildCondition()` instead.
|
||||
*/
|
||||
public function buildBetweenCondition($operator, $operands, &$params)
|
||||
{
|
||||
if (!isset($operands[0], $operands[1], $operands[2])) {
|
||||
throw new InvalidParamException("Operator '$operator' requires three operands.");
|
||||
}
|
||||
|
||||
list($column, $value1, $value2) = $operands;
|
||||
|
||||
if (strpos($column, '(') === false) {
|
||||
$column = $this->db->quoteColumnName($column);
|
||||
}
|
||||
if ($value1 instanceof Expression) {
|
||||
foreach ($value1->params as $n => $v) {
|
||||
$params[$n] = $v;
|
||||
}
|
||||
$phName1 = $value1->expression;
|
||||
} else {
|
||||
$phName1 = self::PARAM_PREFIX . count($params);
|
||||
$params[$phName1] = $value1;
|
||||
}
|
||||
if ($value2 instanceof Expression) {
|
||||
foreach ($value2->params as $n => $v) {
|
||||
$params[$n] = $v;
|
||||
}
|
||||
$phName2 = $value2->expression;
|
||||
} else {
|
||||
$phName2 = self::PARAM_PREFIX . count($params);
|
||||
$params[$phName2] = $value2;
|
||||
}
|
||||
|
||||
return "$column $operator $phName1 AND $phName2";
|
||||
array_unshift($operands, $operator);
|
||||
return $this->buildCondition($operands, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1384,134 +1447,12 @@ class QueryBuilder extends \yii\base\BaseObject
|
||||
* @param array $params the binding parameters to be populated
|
||||
* @return string the generated SQL expression
|
||||
* @throws Exception if wrong number of operands have been given.
|
||||
* @deprecated since 2.0.14. Use `buildCondition()` instead.
|
||||
*/
|
||||
public function buildInCondition($operator, $operands, &$params)
|
||||
{
|
||||
if (!isset($operands[0], $operands[1])) {
|
||||
throw new Exception("Operator '$operator' requires two operands.");
|
||||
}
|
||||
|
||||
list($column, $values) = $operands;
|
||||
|
||||
if ($column === []) {
|
||||
// no columns to test against
|
||||
return $operator === 'IN' ? '0=1' : '';
|
||||
}
|
||||
|
||||
if ($values instanceof Query) {
|
||||
return $this->buildSubqueryInCondition($operator, $column, $values, $params);
|
||||
}
|
||||
if (!is_array($values) && !$values instanceof \Traversable) {
|
||||
// ensure values is an array
|
||||
$values = (array) $values;
|
||||
}
|
||||
|
||||
if ($column instanceof \Traversable || ((is_array($column) || $column instanceof \Countable) && count($column) > 1)) {
|
||||
return $this->buildCompositeInCondition($operator, $column, $values, $params);
|
||||
} elseif (is_array($column)) {
|
||||
$column = reset($column);
|
||||
}
|
||||
|
||||
$sqlValues = [];
|
||||
foreach ($values as $i => $value) {
|
||||
if (is_array($value) || $value instanceof \ArrayAccess) {
|
||||
$value = isset($value[$column]) ? $value[$column] : null;
|
||||
}
|
||||
if ($value === null) {
|
||||
$sqlValues[$i] = 'NULL';
|
||||
} elseif ($value instanceof Expression) {
|
||||
$sqlValues[$i] = $value->expression;
|
||||
foreach ($value->params as $n => $v) {
|
||||
$params[$n] = $v;
|
||||
}
|
||||
} else {
|
||||
$phName = self::PARAM_PREFIX . count($params);
|
||||
$params[$phName] = $value;
|
||||
$sqlValues[$i] = $phName;
|
||||
}
|
||||
}
|
||||
|
||||
if (empty($sqlValues)) {
|
||||
return $operator === 'IN' ? '0=1' : '';
|
||||
}
|
||||
|
||||
if (strpos($column, '(') === false) {
|
||||
$column = $this->db->quoteColumnName($column);
|
||||
}
|
||||
|
||||
if (count($sqlValues) > 1) {
|
||||
return "$column $operator (" . implode(', ', $sqlValues) . ')';
|
||||
}
|
||||
|
||||
$operator = $operator === 'IN' ? '=' : '<>';
|
||||
return $column . $operator . reset($sqlValues);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds SQL for IN condition.
|
||||
*
|
||||
* @param string $operator
|
||||
* @param array $columns
|
||||
* @param Query $values
|
||||
* @param array $params
|
||||
* @return string SQL
|
||||
*/
|
||||
protected function buildSubqueryInCondition($operator, $columns, $values, &$params)
|
||||
{
|
||||
list($sql, $params) = $this->build($values, $params);
|
||||
if (is_array($columns)) {
|
||||
foreach ($columns as $i => $col) {
|
||||
if (strpos($col, '(') === false) {
|
||||
$columns[$i] = $this->db->quoteColumnName($col);
|
||||
}
|
||||
}
|
||||
|
||||
return '(' . implode(', ', $columns) . ") $operator ($sql)";
|
||||
}
|
||||
|
||||
if (strpos($columns, '(') === false) {
|
||||
$columns = $this->db->quoteColumnName($columns);
|
||||
}
|
||||
|
||||
return "$columns $operator ($sql)";
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds SQL for IN condition.
|
||||
*
|
||||
* @param string $operator
|
||||
* @param array|\Traversable $columns
|
||||
* @param array $values
|
||||
* @param array $params
|
||||
* @return string SQL
|
||||
*/
|
||||
protected function buildCompositeInCondition($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) . ')';
|
||||
}
|
||||
|
||||
if (empty($vss)) {
|
||||
return $operator === 'IN' ? '0=1' : '';
|
||||
}
|
||||
|
||||
$sqlColumns = [];
|
||||
foreach ($columns as $i => $column) {
|
||||
$sqlColumns[] = strpos($column, '(') === false ? $this->db->quoteColumnName($column) : $column;
|
||||
}
|
||||
|
||||
return '(' . implode(', ', $sqlColumns) . ") $operator (" . implode(', ', $vss) . ')';
|
||||
array_unshift($operands, $operator);
|
||||
return $this->buildCondition($operands, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1533,56 +1474,12 @@ class QueryBuilder extends \yii\base\BaseObject
|
||||
* @param array $params the binding parameters to be populated
|
||||
* @return string the generated SQL expression
|
||||
* @throws InvalidParamException if wrong number of operands have been given.
|
||||
* @deprecated since 2.0.14. Use `buildCondition()` instead.
|
||||
*/
|
||||
public function buildLikeCondition($operator, $operands, &$params)
|
||||
{
|
||||
if (!isset($operands[0], $operands[1])) {
|
||||
throw new InvalidParamException("Operator '$operator' requires two operands.");
|
||||
}
|
||||
|
||||
$escape = isset($operands[2]) ? $operands[2] : $this->likeEscapingReplacements;
|
||||
unset($operands[2]);
|
||||
|
||||
if (!preg_match('/^(AND |OR |)(((NOT |))I?LIKE)/', $operator, $matches)) {
|
||||
throw new InvalidParamException("Invalid operator '$operator'.");
|
||||
}
|
||||
$andor = ' ' . (!empty($matches[1]) ? $matches[1] : 'AND ');
|
||||
$not = !empty($matches[3]);
|
||||
$operator = $matches[2];
|
||||
|
||||
list($column, $values) = $operands;
|
||||
|
||||
if (!is_array($values)) {
|
||||
$values = [$values];
|
||||
}
|
||||
|
||||
if (empty($values)) {
|
||||
return $not ? '' : '0=1';
|
||||
}
|
||||
|
||||
if (strpos($column, '(') === false) {
|
||||
$column = $this->db->quoteColumnName($column);
|
||||
}
|
||||
|
||||
$parts = [];
|
||||
foreach ($values as $value) {
|
||||
if ($value instanceof Expression) {
|
||||
foreach ($value->params as $n => $v) {
|
||||
$params[$n] = $v;
|
||||
}
|
||||
$phName = $value->expression;
|
||||
} else {
|
||||
$phName = self::PARAM_PREFIX . count($params);
|
||||
$params[$phName] = empty($escape) ? $value : ('%' . strtr($value, $escape) . '%');
|
||||
}
|
||||
$escapeSql = '';
|
||||
if ($this->likeEscapeCharacter !== null) {
|
||||
$escapeSql = " ESCAPE '{$this->likeEscapeCharacter}'";
|
||||
}
|
||||
$parts[] = "{$column} {$operator} {$phName}{$escapeSql}";
|
||||
}
|
||||
|
||||
return implode($andor, $parts);
|
||||
array_unshift($operands, $operator);
|
||||
return $this->buildCondition($operands, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1592,15 +1489,12 @@ class QueryBuilder extends \yii\base\BaseObject
|
||||
* @param array $params the binding parameters to be populated
|
||||
* @return string the generated SQL expression
|
||||
* @throws InvalidParamException if the operand is not a [[Query]] object.
|
||||
* @deprecated since 2.0.14. Use `buildCondition()` instead.
|
||||
*/
|
||||
public function buildExistsCondition($operator, $operands, &$params)
|
||||
{
|
||||
if ($operands[0] instanceof Query) {
|
||||
list($sql, $params) = $this->build($operands[0], $params);
|
||||
return "$operator ($sql)";
|
||||
}
|
||||
|
||||
throw new InvalidParamException('Subquery for EXISTS operator must be a Query object.');
|
||||
array_unshift($operands, $operator);
|
||||
return $this->buildCondition($operands, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1610,35 +1504,12 @@ class QueryBuilder extends \yii\base\BaseObject
|
||||
* @param array $params the binding parameters to be populated
|
||||
* @return string the generated SQL expression
|
||||
* @throws InvalidParamException if wrong number of operands have been given.
|
||||
* @deprecated since 2.0.14. Use `buildCondition()` instead.
|
||||
*/
|
||||
public function buildSimpleCondition($operator, $operands, &$params)
|
||||
{
|
||||
if (count($operands) !== 2) {
|
||||
throw new InvalidParamException("Operator '$operator' requires two operands.");
|
||||
}
|
||||
|
||||
list($column, $value) = $operands;
|
||||
|
||||
if (strpos($column, '(') === false) {
|
||||
$column = $this->db->quoteColumnName($column);
|
||||
}
|
||||
|
||||
if ($value === null) {
|
||||
return "$column $operator NULL";
|
||||
} elseif ($value instanceof Expression) {
|
||||
foreach ($value->params as $n => $v) {
|
||||
$params[$n] = $v;
|
||||
}
|
||||
|
||||
return "$column $operator {$value->expression}";
|
||||
} elseif ($value instanceof Query) {
|
||||
list($sql, $params) = $this->build($value, $params);
|
||||
return "$column $operator ($sql)";
|
||||
}
|
||||
|
||||
$phName = self::PARAM_PREFIX . count($params);
|
||||
$params[$phName] = $value;
|
||||
return "$column $operator $phName";
|
||||
array_unshift($operands, $operator);
|
||||
return $this->buildCondition($operands, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1651,4 +1522,21 @@ class QueryBuilder extends \yii\base\BaseObject
|
||||
{
|
||||
return 'SELECT EXISTS(' . $rawSql . ')';
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to add $value to $params array using [[PARAM_PREFIX]].
|
||||
*
|
||||
* @param string|null $value
|
||||
* @param array $params passed by reference
|
||||
* @return string the placeholder name in $params array
|
||||
*
|
||||
* @since 2.0.14
|
||||
*/
|
||||
public function bindParam($value, &$params)
|
||||
{
|
||||
$phName = self::PARAM_PREFIX . count($params);
|
||||
$params[$phName] = $value;
|
||||
|
||||
return $phName;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -27,13 +27,13 @@ trait QueryTrait
|
||||
*/
|
||||
public $where;
|
||||
/**
|
||||
* @var int|Expression maximum number of records to be returned. May be an instance of [[Expression]].
|
||||
* @var int|ExpressionInterface maximum number of records to be returned. May be an instance of [[ExpressionInterface]].
|
||||
* If not set or less than 0, it means no limit.
|
||||
*/
|
||||
public $limit;
|
||||
/**
|
||||
* @var int|Expression zero-based offset from where the records are to be returned.
|
||||
* May be an instance of [[Expression]]. If not set or less than 0, it means starting from the beginning.
|
||||
* @var int|ExpressionInterface zero-based offset from where the records are to be returned.
|
||||
* May be an instance of [[ExpressionInterface]]. If not set or less than 0, it means starting from the beginning.
|
||||
*/
|
||||
public $offset;
|
||||
/**
|
||||
@@ -41,7 +41,7 @@ trait QueryTrait
|
||||
* The array keys are the columns to be sorted by, and the array values are the corresponding sort directions which
|
||||
* can be either [SORT_ASC](http://php.net/manual/en/array.constants.php#constant.sort-asc)
|
||||
* or [SORT_DESC](http://php.net/manual/en/array.constants.php#constant.sort-desc).
|
||||
* The array may also contain [[Expression]] objects. If that is the case, the expressions
|
||||
* The array may also contain [[ExpressionInterface]] objects. If that is the case, the expressions
|
||||
* will be converted into strings without any change.
|
||||
*/
|
||||
public $orderBy;
|
||||
@@ -305,7 +305,7 @@ trait QueryTrait
|
||||
|
||||
/**
|
||||
* Sets the ORDER BY part of the query.
|
||||
* @param string|array|Expression $columns the columns (and the directions) to be ordered by.
|
||||
* @param string|array|ExpressionInterface $columns the columns (and the directions) to be ordered by.
|
||||
* Columns can be specified in either a string (e.g. `"id ASC, name DESC"`) or an array
|
||||
* (e.g. `['id' => SORT_ASC, 'name' => SORT_DESC]`).
|
||||
*
|
||||
@@ -316,7 +316,7 @@ trait QueryTrait
|
||||
* to represent the order-by information. Otherwise, the method will not be able to correctly determine
|
||||
* the order-by columns.
|
||||
*
|
||||
* Since version 2.0.7, an [[Expression]] object can be passed to specify the ORDER BY part explicitly in plain SQL.
|
||||
* Since version 2.0.7, an [[ExpressionInterface]] object can be passed to specify the ORDER BY part explicitly in plain SQL.
|
||||
* @return $this the query object itself
|
||||
* @see addOrderBy()
|
||||
*/
|
||||
@@ -328,7 +328,7 @@ trait QueryTrait
|
||||
|
||||
/**
|
||||
* Adds additional ORDER BY columns to the query.
|
||||
* @param string|array|Expression $columns the columns (and the directions) to be ordered by.
|
||||
* @param string|array|ExpressionInterface $columns the columns (and the directions) to be ordered by.
|
||||
* Columns can be specified in either a string (e.g. "id ASC, name DESC") or an array
|
||||
* (e.g. `['id' => SORT_ASC, 'name' => SORT_DESC]`).
|
||||
*
|
||||
@@ -339,7 +339,7 @@ trait QueryTrait
|
||||
* to represent the order-by information. Otherwise, the method will not be able to correctly determine
|
||||
* the order-by columns.
|
||||
*
|
||||
* Since version 2.0.7, an [[Expression]] object can be passed to specify the ORDER BY part explicitly in plain SQL.
|
||||
* Since version 2.0.7, an [[ExpressionInterface]] object can be passed to specify the ORDER BY part explicitly in plain SQL.
|
||||
* @return $this the query object itself
|
||||
* @see orderBy()
|
||||
*/
|
||||
@@ -358,12 +358,12 @@ trait QueryTrait
|
||||
/**
|
||||
* Normalizes format of ORDER BY data.
|
||||
*
|
||||
* @param array|string|Expression $columns the columns value to normalize. See [[orderBy]] and [[addOrderBy]].
|
||||
* @param array|string|ExpressionInterface $columns the columns value to normalize. See [[orderBy]] and [[addOrderBy]].
|
||||
* @return array
|
||||
*/
|
||||
protected function normalizeOrderBy($columns)
|
||||
{
|
||||
if ($columns instanceof Expression) {
|
||||
if ($columns instanceof ExpressionInterface) {
|
||||
return [$columns];
|
||||
} elseif (is_array($columns)) {
|
||||
return $columns;
|
||||
@@ -384,7 +384,7 @@ trait QueryTrait
|
||||
|
||||
/**
|
||||
* Sets the LIMIT part of the query.
|
||||
* @param int|Expression|null $limit the limit. Use null or negative value to disable limit.
|
||||
* @param int|ExpressionInterface|null $limit the limit. Use null or negative value to disable limit.
|
||||
* @return $this the query object itself
|
||||
*/
|
||||
public function limit($limit)
|
||||
@@ -395,7 +395,7 @@ trait QueryTrait
|
||||
|
||||
/**
|
||||
* Sets the OFFSET part of the query.
|
||||
* @param int|Expression|null $offset the offset. Use null or negative value to disable offset.
|
||||
* @param int|ExpressionInterface|null $offset the offset. Use null or negative value to disable offset.
|
||||
* @return $this the query object itself
|
||||
*/
|
||||
public function offset($offset)
|
||||
|
||||
@@ -61,6 +61,7 @@ abstract class Schema extends BaseObject
|
||||
const TYPE_BINARY = 'binary';
|
||||
const TYPE_BOOLEAN = 'boolean';
|
||||
const TYPE_MONEY = 'money';
|
||||
const TYPE_JSON = 'json';
|
||||
/**
|
||||
* Schema cache version, to detect incompatibilities in cached values when the
|
||||
* data format of the cache changes.
|
||||
@@ -557,6 +558,7 @@ abstract class Schema extends BaseObject
|
||||
'float' => 'double',
|
||||
'double' => 'double',
|
||||
'binary' => 'resource',
|
||||
'json' => 'array',
|
||||
];
|
||||
if (isset($typeMap[$column->type])) {
|
||||
if ($column->type === 'bigint') {
|
||||
|
||||
22
framework/db/conditions/AndCondition.php
Normal file
22
framework/db/conditions/AndCondition.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace yii\db\conditions;
|
||||
|
||||
/**
|
||||
* Condition that connects two or more SQL expressions with the `AND` operator.
|
||||
*
|
||||
* @author Dmytro Naumenko <d.naumenko.a@gmail.com>
|
||||
* @since 2.0.14
|
||||
*/
|
||||
class AndCondition extends ConjunctionCondition
|
||||
{
|
||||
/**
|
||||
* Returns the operator that is represented by this condition class, e.g. `AND`, `OR`.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getOperator()
|
||||
{
|
||||
return 'AND';
|
||||
}
|
||||
}
|
||||
92
framework/db/conditions/BetweenCondition.php
Normal file
92
framework/db/conditions/BetweenCondition.php
Normal file
@@ -0,0 +1,92 @@
|
||||
<?php
|
||||
|
||||
namespace yii\db\conditions;
|
||||
|
||||
use yii\base\InvalidParamException;
|
||||
|
||||
/**
|
||||
* Class BetweenCondition represents a `BETWEEN` condition.
|
||||
*
|
||||
* @author Dmytro Naumenko <d.naumenko.a@gmail.com>
|
||||
* @since 2.0.14
|
||||
*/
|
||||
class BetweenCondition implements ConditionInterface
|
||||
{
|
||||
/**
|
||||
* @var string $operator the operator to use (e.g. `BETWEEN` or `NOT BETWEEN`)
|
||||
*/
|
||||
protected $operator;
|
||||
/**
|
||||
* @var mixed the column name to the left of [[operator]]
|
||||
*/
|
||||
protected $column;
|
||||
/**
|
||||
* @var mixed beginning of the interval
|
||||
*/
|
||||
private $intervalStart;
|
||||
/**
|
||||
* @var mixed end of the interval
|
||||
*/
|
||||
private $intervalEnd;
|
||||
|
||||
/**
|
||||
* Creates a condition with the `BETWEEN` operator.
|
||||
*
|
||||
* @param mixed $column the literal to the left of $operator
|
||||
* @param string $operator the operator to use (e.g. `BETWEEN` or `NOT BETWEEN`)
|
||||
* @param mixed $intervalStart beginning of the interval
|
||||
* @param mixed $intervalEnd end of the interval
|
||||
*/
|
||||
public function __construct($column, $operator, $intervalStart, $intervalEnd)
|
||||
{
|
||||
$this->column = $column;
|
||||
$this->operator = $operator;
|
||||
$this->intervalStart = $intervalStart;
|
||||
$this->intervalEnd = $intervalEnd;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getOperator()
|
||||
{
|
||||
return $this->operator;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getColumn()
|
||||
{
|
||||
return $this->column;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getIntervalStart()
|
||||
{
|
||||
return $this->intervalStart;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getIntervalEnd()
|
||||
{
|
||||
return $this->intervalEnd;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @throws InvalidParamException if wrong number of operands have been given.
|
||||
*/
|
||||
public static function fromArrayDefinition($operator, $operands)
|
||||
{
|
||||
if (!isset($operands[0], $operands[1], $operands[2])) {
|
||||
throw new InvalidParamException("Operator '$operator' requires three operands.");
|
||||
}
|
||||
|
||||
return new static($operands[0], $operator, $operands[1], $operands[2]);
|
||||
}
|
||||
}
|
||||
57
framework/db/conditions/BetweenConditionBuilder.php
Normal file
57
framework/db/conditions/BetweenConditionBuilder.php
Normal file
@@ -0,0 +1,57 @@
|
||||
<?php
|
||||
|
||||
namespace yii\db\conditions;
|
||||
|
||||
use yii\db\ExpressionBuilderInterface;
|
||||
use yii\db\ExpressionBuilderTrait;
|
||||
use yii\db\ExpressionInterface;
|
||||
|
||||
/**
|
||||
* Class BetweenConditionBuilder builds objects of [[BetweenCondition]]
|
||||
*
|
||||
* @author Dmytro Naumenko <d.naumenko.a@gmail.com>
|
||||
* @since 2.0.14
|
||||
*/
|
||||
class BetweenConditionBuilder implements ExpressionBuilderInterface
|
||||
{
|
||||
use ExpressionBuilderTrait;
|
||||
|
||||
/**
|
||||
* Method builds the raw SQL from the $expression that will not be additionally
|
||||
* escaped or quoted.
|
||||
*
|
||||
* @param ExpressionInterface|BetweenCondition $expression the expression to be built.
|
||||
* @param array $params the binding parameters.
|
||||
* @return string the raw SQL that will not be additionally escaped or quoted.
|
||||
*/
|
||||
public function build(ExpressionInterface $expression, array &$params = [])
|
||||
{
|
||||
$operator = $expression->getOperator();
|
||||
$column = $expression->getColumn();
|
||||
|
||||
if (strpos($column, '(') === false) {
|
||||
$column = $this->queryBuilder->db->quoteColumnName($column);
|
||||
}
|
||||
|
||||
$phName1 = $this->createPlaceholder($expression->getIntervalStart(), $params);
|
||||
$phName2 = $this->createPlaceholder($expression->getIntervalEnd(), $params);
|
||||
|
||||
return "$column $operator $phName1 AND $phName2";
|
||||
}
|
||||
|
||||
/**
|
||||
* Attaches $value to $params array and returns placeholder.
|
||||
*
|
||||
* @param mixed $value
|
||||
* @param array $params passed by reference
|
||||
* @return string
|
||||
*/
|
||||
protected function createPlaceholder($value, &$params)
|
||||
{
|
||||
if ($value instanceof ExpressionInterface) {
|
||||
return $this->queryBuilder->buildExpression($value, $params);
|
||||
}
|
||||
|
||||
return $this->queryBuilder->bindParam($value, $params);
|
||||
}
|
||||
}
|
||||
28
framework/db/conditions/ConditionInterface.php
Normal file
28
framework/db/conditions/ConditionInterface.php
Normal file
@@ -0,0 +1,28 @@
|
||||
<?php
|
||||
|
||||
namespace yii\db\conditions;
|
||||
|
||||
use yii\base\InvalidParamException;
|
||||
use yii\db\ExpressionInterface;
|
||||
|
||||
/**
|
||||
* Interface ConditionInterface should be implemented by classes that represent a condition
|
||||
* in DBAL of framework.
|
||||
*
|
||||
* @author Dmytro Naumenko <d.naumenko.a@gmail.com>
|
||||
* @since 2.0.14
|
||||
*/
|
||||
interface ConditionInterface extends ExpressionInterface
|
||||
{
|
||||
/**
|
||||
* Creates object by array-definition as described in
|
||||
* [Query Builder – Operator format](guide:db-query-builder#operator-format) guide article.
|
||||
*
|
||||
* @param string $operator operator in uppercase.
|
||||
* @param array $operands array of corresponding operands
|
||||
*
|
||||
* @return $this
|
||||
* @throws InvalidParamException if input parameters are not suitable for this condition
|
||||
*/
|
||||
public static function fromArrayDefinition($operator, $operands);
|
||||
}
|
||||
47
framework/db/conditions/ConjunctionCondition.php
Normal file
47
framework/db/conditions/ConjunctionCondition.php
Normal file
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
namespace yii\db\conditions;
|
||||
|
||||
/**
|
||||
* Class ConjunctionCondition
|
||||
*
|
||||
* @author Dmytro Naumenko <d.naumenko.a@gmail.com>
|
||||
* @since 2.0.14
|
||||
*/
|
||||
abstract class ConjunctionCondition implements ConditionInterface
|
||||
{
|
||||
/**
|
||||
* @var mixed[]
|
||||
*/
|
||||
protected $expressions;
|
||||
|
||||
/**
|
||||
* @param mixed $expressions
|
||||
*/
|
||||
public function __construct($expressions) // TODO: use variadic params when PHP>5.6
|
||||
{
|
||||
$this->expressions = $expressions;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed[]
|
||||
*/
|
||||
public function getExpressions()
|
||||
{
|
||||
return $this->expressions;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the operator that is represented by this condition class, e.g. `AND`, `OR`.
|
||||
* @return string
|
||||
*/
|
||||
abstract public function getOperator();
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function fromArrayDefinition($operator, $operands)
|
||||
{
|
||||
return new static($operands);
|
||||
}
|
||||
}
|
||||
62
framework/db/conditions/ConjunctionConditionBuilder.php
Normal file
62
framework/db/conditions/ConjunctionConditionBuilder.php
Normal file
@@ -0,0 +1,62 @@
|
||||
<?php
|
||||
|
||||
namespace yii\db\conditions;
|
||||
|
||||
use yii\db\ExpressionBuilderInterface;
|
||||
use yii\db\ExpressionBuilderTrait;
|
||||
use yii\db\ExpressionInterface;
|
||||
|
||||
/**
|
||||
* Class ConjunctionConditionBuilder builds objects of abstract class [[ConjunctionCondition]]
|
||||
*
|
||||
* @author Dmytro Naumenko <d.naumenko.a@gmail.com>
|
||||
* @since 2.0.14
|
||||
*/
|
||||
class ConjunctionConditionBuilder implements ExpressionBuilderInterface
|
||||
{
|
||||
use ExpressionBuilderTrait;
|
||||
|
||||
/**
|
||||
* Method builds the raw SQL from the $expression that will not be additionally
|
||||
* escaped or quoted.
|
||||
*
|
||||
* @param ExpressionInterface|ConjunctionCondition $condition the expression to be built.
|
||||
* @param array $params the binding parameters.
|
||||
* @return string the raw SQL that will not be additionally escaped or quoted.
|
||||
*/
|
||||
public function build(ExpressionInterface $condition, array &$params = [])
|
||||
{
|
||||
$parts = $this->buildExpressionsFrom($condition, $params);
|
||||
|
||||
if (!empty($parts)) {
|
||||
return '(' . implode(") {$condition->getOperator()} (", $parts) . ')';
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds expressions, that are stored in $condition
|
||||
*
|
||||
* @param ExpressionInterface|ConjunctionCondition $condition the expression to be built.
|
||||
* @param array $params the binding parameters.
|
||||
* @return string[]
|
||||
*/
|
||||
private function buildExpressionsFrom(ExpressionInterface $condition, &$params = [])
|
||||
{
|
||||
$parts = [];
|
||||
foreach ($condition->getExpressions() as $condition) {
|
||||
if (is_array($condition)) {
|
||||
$condition = $this->queryBuilder->buildCondition($condition, $params);
|
||||
}
|
||||
if ($condition instanceof ExpressionInterface) {
|
||||
$condition = $this->queryBuilder->buildExpression($condition, $params);
|
||||
}
|
||||
if ($condition !== '') {
|
||||
$parts[] = $condition;
|
||||
}
|
||||
}
|
||||
|
||||
return $parts;
|
||||
}
|
||||
}
|
||||
64
framework/db/conditions/ExistsCondition.php
Normal file
64
framework/db/conditions/ExistsCondition.php
Normal file
@@ -0,0 +1,64 @@
|
||||
<?php
|
||||
|
||||
namespace yii\db\conditions;
|
||||
|
||||
use yii\base\InvalidParamException;
|
||||
use yii\db\Query;
|
||||
|
||||
/**
|
||||
* Condition that represents `EXISTS` operator.
|
||||
*
|
||||
* @author Dmytro Naumenko <d.naumenko.a@gmail.com>
|
||||
* @since 2.0.14
|
||||
*/
|
||||
class ExistsCondition implements ConditionInterface
|
||||
{
|
||||
/**
|
||||
* @var string $operator the operator to use (e.g. `EXISTS` or `NOT EXISTS`)
|
||||
*/
|
||||
private $operator;
|
||||
/**
|
||||
* @var Query the [[Query]] object representing the sub-query.
|
||||
*/
|
||||
private $query;
|
||||
|
||||
/**
|
||||
* ExistsCondition constructor.
|
||||
*
|
||||
* @param string $operator the operator to use (e.g. `EXISTS` or `NOT EXISTS`)
|
||||
* @param Query $query the [[Query]] object representing the sub-query.
|
||||
*/
|
||||
public function __construct($operator, $query)
|
||||
{
|
||||
$this->operator = $operator;
|
||||
$this->query = $query;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function fromArrayDefinition($operator, $operands)
|
||||
{
|
||||
if (!isset($operands[0]) || !$operands[0] instanceof Query) {
|
||||
throw new InvalidParamException('Subquery for EXISTS operator must be a Query object.');
|
||||
}
|
||||
|
||||
return new static($operator, $operands[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getOperator()
|
||||
{
|
||||
return $this->operator;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Query
|
||||
*/
|
||||
public function getQuery()
|
||||
{
|
||||
return $this->query;
|
||||
}
|
||||
}
|
||||
36
framework/db/conditions/ExistsConditionBuilder.php
Normal file
36
framework/db/conditions/ExistsConditionBuilder.php
Normal file
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
namespace yii\db\conditions;
|
||||
|
||||
use yii\db\ExpressionBuilderInterface;
|
||||
use yii\db\ExpressionBuilderTrait;
|
||||
use yii\db\ExpressionInterface;
|
||||
|
||||
/**
|
||||
* Class ExistsConditionBuilder builds objects of [[ExistsCondition]]
|
||||
*
|
||||
* @author Dmytro Naumenko <d.naumenko.a@gmail.com>
|
||||
* @since 2.0.14
|
||||
*/
|
||||
class ExistsConditionBuilder implements ExpressionBuilderInterface
|
||||
{
|
||||
use ExpressionBuilderTrait;
|
||||
|
||||
/**
|
||||
* Method builds the raw SQL from the $expression that will not be additionally
|
||||
* escaped or quoted.
|
||||
*
|
||||
* @param ExpressionInterface|ExistsCondition $expression the expression to be built.
|
||||
* @param array $params the binding parameters.
|
||||
* @return string the raw SQL that will not be additionally escaped or quoted.
|
||||
*/
|
||||
public function build(ExpressionInterface $expression, array &$params = [])
|
||||
{
|
||||
$operator = $expression->getOperator();
|
||||
$query = $expression->getQuery();
|
||||
|
||||
list($sql, $params) = $this->queryBuilder->build($query, $params);
|
||||
|
||||
return "$operator ($sql)";
|
||||
}
|
||||
}
|
||||
43
framework/db/conditions/HashCondition.php
Normal file
43
framework/db/conditions/HashCondition.php
Normal file
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace yii\db\conditions;
|
||||
|
||||
/**
|
||||
* Condition based on column-value pairs.
|
||||
*
|
||||
* @author Dmytro Naumenko <d.naumenko.a@gmail.com>
|
||||
* @since 2.0.14
|
||||
*/
|
||||
class HashCondition implements ConditionInterface
|
||||
{
|
||||
/**
|
||||
* @var array|null the condition specification.
|
||||
*/
|
||||
private $hash;
|
||||
|
||||
/**
|
||||
* HashCondition constructor.
|
||||
*
|
||||
* @param array|null $hash
|
||||
*/
|
||||
public function __construct($hash)
|
||||
{
|
||||
$this->hash = $hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array|null
|
||||
*/
|
||||
public function getHash()
|
||||
{
|
||||
return $this->hash;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public static function fromArrayDefinition($operator, $operands)
|
||||
{
|
||||
return new static($operands);
|
||||
}
|
||||
}
|
||||
54
framework/db/conditions/HashConditionBuilder.php
Normal file
54
framework/db/conditions/HashConditionBuilder.php
Normal file
@@ -0,0 +1,54 @@
|
||||
<?php
|
||||
|
||||
namespace yii\db\conditions;
|
||||
|
||||
use yii\db\ExpressionBuilderInterface;
|
||||
use yii\db\ExpressionBuilderTrait;
|
||||
use yii\db\ExpressionInterface;
|
||||
use yii\db\Query;
|
||||
use yii\helpers\ArrayHelper;
|
||||
|
||||
/**
|
||||
* Class HashConditionBuilder builds objects of [[HashCondition]]
|
||||
*
|
||||
* @author Dmytro Naumenko <d.naumenko.a@gmail.com>
|
||||
* @since 2.0.14
|
||||
*/
|
||||
class HashConditionBuilder implements ExpressionBuilderInterface
|
||||
{
|
||||
use ExpressionBuilderTrait;
|
||||
|
||||
/**
|
||||
* Method builds the raw SQL from the $expression that will not be additionally
|
||||
* escaped or quoted.
|
||||
*
|
||||
* @param ExpressionInterface|HashCondition $expression the expression to be built.
|
||||
* @param array $params the binding parameters.
|
||||
* @return string the raw SQL that will not be additionally escaped or quoted.
|
||||
*/
|
||||
public function build(ExpressionInterface $expression, array &$params = [])
|
||||
{
|
||||
$hash = $expression->getHash();
|
||||
$parts = [];
|
||||
foreach ($hash as $column => $value) {
|
||||
if (ArrayHelper::isTraversable($value) || $value instanceof Query) {
|
||||
// IN condition
|
||||
$parts[] = $this->queryBuilder->buildCondition(new InCondition($column, 'IN', $value), $params);
|
||||
} else {
|
||||
if (strpos($column, '(') === false) {
|
||||
$column = $this->queryBuilder->db->quoteColumnName($column);
|
||||
}
|
||||
if ($value === null) {
|
||||
$parts[] = "$column IS NULL";
|
||||
} elseif ($value instanceof ExpressionInterface) {
|
||||
$parts[] = "$column=" . $this->queryBuilder->buildExpression($value, $params);
|
||||
} else {
|
||||
$phName = $this->queryBuilder->bindParam($value, $params);
|
||||
$parts[] = "$column=$phName";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return count($parts) === 1 ? $parts[0] : '(' . implode(') AND (', $parts) . ')';
|
||||
}
|
||||
}
|
||||
84
framework/db/conditions/InCondition.php
Normal file
84
framework/db/conditions/InCondition.php
Normal file
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
namespace yii\db\conditions;
|
||||
|
||||
use yii\base\InvalidParamException;
|
||||
|
||||
/**
|
||||
* Class LikeCondition represents `IN` condition.
|
||||
*
|
||||
* @author Dmytro Naumenko <d.naumenko.a@gmail.com>
|
||||
* @since 2.0.14
|
||||
*/
|
||||
class InCondition implements ConditionInterface
|
||||
{
|
||||
/**
|
||||
* @var string $operator the operator to use (e.g. `IN` or `NOT IN`)
|
||||
*/
|
||||
protected $operator;
|
||||
|
||||
/**
|
||||
* @var string|string[] the column name. If it is an array, a composite `IN` condition
|
||||
* will be generated.
|
||||
*/
|
||||
protected $column;
|
||||
|
||||
/**
|
||||
* @var array an array of values that [[column]] value should be among.
|
||||
* If it is an empty array the generated expression will be a `false` value if
|
||||
* [[operator]] is `IN` and empty if operator is `NOT IN`.
|
||||
*/
|
||||
protected $values;
|
||||
|
||||
/**
|
||||
* SimpleCondition constructor
|
||||
*
|
||||
* @param string|string[] the column name. If it is an array, a composite `IN` condition
|
||||
* will be generated.
|
||||
* @param string $operator the operator to use (e.g. `IN` or `NOT IN`)
|
||||
* @param array an array of values that [[column]] value should be among. If it is an empty array the generated
|
||||
* expression will be a `false` value if [[operator]] is `IN` and empty if operator is `NOT IN`.
|
||||
*/
|
||||
public function __construct($column, $operator, $values)
|
||||
{
|
||||
$this->column = $column;
|
||||
$this->operator = $operator;
|
||||
$this->values = $values;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getOperator()
|
||||
{
|
||||
return $this->operator;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getColumn()
|
||||
{
|
||||
return $this->column;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getValues()
|
||||
{
|
||||
return $this->values;
|
||||
}
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @throws InvalidParamException if wrong number of operands have been given.
|
||||
*/
|
||||
public static function fromArrayDefinition($operator, $operands)
|
||||
{
|
||||
if (!isset($operands[0], $operands[1])) {
|
||||
throw new InvalidParamException("Operator '$operator' requires two operands.");
|
||||
}
|
||||
|
||||
return new static($operands[0], $operator, $operands[1]);
|
||||
}
|
||||
}
|
||||
165
framework/db/conditions/InConditionBuilder.php
Normal file
165
framework/db/conditions/InConditionBuilder.php
Normal file
@@ -0,0 +1,165 @@
|
||||
<?php
|
||||
|
||||
namespace yii\db\conditions;
|
||||
|
||||
use yii\db\ExpressionBuilderInterface;
|
||||
use yii\db\ExpressionBuilderTrait;
|
||||
use yii\db\ExpressionInterface;
|
||||
use yii\db\Query;
|
||||
|
||||
/**
|
||||
* Class InConditionBuilder builds objects of [[InCondition]]
|
||||
*
|
||||
* @author Dmytro Naumenko <d.naumenko.a@gmail.com>
|
||||
* @since 2.0.14
|
||||
*/
|
||||
class InConditionBuilder implements ExpressionBuilderInterface
|
||||
{
|
||||
use ExpressionBuilderTrait;
|
||||
|
||||
/**
|
||||
* Method builds the raw SQL from the $expression that will not be additionally
|
||||
* escaped or quoted.
|
||||
*
|
||||
* @param ExpressionInterface|InCondition $expression the expression to be built.
|
||||
* @param array $params the binding parameters.
|
||||
* @return string the raw SQL that will not be additionally escaped or quoted.
|
||||
*/
|
||||
public function build(ExpressionInterface $expression, array &$params = [])
|
||||
{
|
||||
$operator = $expression->getOperator();
|
||||
$column = $expression->getColumn();
|
||||
$values = $expression->getValues();
|
||||
|
||||
if ($column === []) {
|
||||
// no columns to test against
|
||||
return $operator === 'IN' ? '0=1' : '';
|
||||
}
|
||||
|
||||
if ($values instanceof Query) {
|
||||
return $this->buildSubqueryInCondition($operator, $column, $values, $params);
|
||||
}
|
||||
|
||||
if (!is_array($values) && !$values instanceof \Traversable) {
|
||||
// ensure values is an array
|
||||
$values = (array) $values;
|
||||
}
|
||||
if ($column instanceof \Traversable || ((is_array($column) || $column instanceof \Countable) && count($column) > 1)) {
|
||||
return $this->buildCompositeInCondition($operator, $column, $values, $params);
|
||||
}
|
||||
|
||||
if (is_array($column)) {
|
||||
$column = reset($column);
|
||||
}
|
||||
|
||||
$sqlValues = $this->buildValues($expression, $values, $params);
|
||||
if (empty($sqlValues)) {
|
||||
return $operator === 'IN' ? '0=1' : '';
|
||||
}
|
||||
|
||||
if (strpos($column, '(') === false) {
|
||||
$column = $this->queryBuilder->db->quoteColumnName($column);
|
||||
}
|
||||
if (count($sqlValues) > 1) {
|
||||
return "$column $operator (" . implode(', ', $sqlValues) . ')';
|
||||
}
|
||||
|
||||
$operator = $operator === 'IN' ? '=' : '<>';
|
||||
|
||||
return $column . $operator . reset($sqlValues);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds $values to be used in [[InCondition]]
|
||||
*
|
||||
* @param ConditionInterface|InCondition $condition
|
||||
* @param array $values
|
||||
* @param array $params the binding parameters
|
||||
* @return array of prepared for SQL placeholders
|
||||
*/
|
||||
protected function buildValues(ConditionInterface $condition, $values, &$params)
|
||||
{
|
||||
$sqlValues = [];
|
||||
$column = $condition->getColumn();
|
||||
|
||||
foreach ($values as $i => $value) {
|
||||
if (is_array($value) || $value instanceof \ArrayAccess) {
|
||||
$value = isset($value[$column]) ? $value[$column] : null;
|
||||
}
|
||||
if ($value === null) {
|
||||
$sqlValues[$i] = 'NULL';
|
||||
} elseif ($value instanceof ExpressionInterface) {
|
||||
$sqlValues[$i] = $this->queryBuilder->buildExpression($value, $params);
|
||||
} else {
|
||||
$sqlValues[$i] = $this->queryBuilder->bindParam($value, $params);
|
||||
}
|
||||
}
|
||||
|
||||
return $sqlValues;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds SQL for IN condition.
|
||||
*
|
||||
* @param string $operator
|
||||
* @param array|string $columns
|
||||
* @param Query $values
|
||||
* @param array $params
|
||||
* @return string SQL
|
||||
*/
|
||||
protected function buildSubqueryInCondition($operator, $columns, $values, &$params)
|
||||
{
|
||||
list($sql, $params) = $this->queryBuilder->build($values, $params);
|
||||
if (is_array($columns)) {
|
||||
foreach ($columns as $i => $col) {
|
||||
if (strpos($col, '(') === false) {
|
||||
$columns[$i] = $this->queryBuilder->db->quoteColumnName($col);
|
||||
}
|
||||
}
|
||||
|
||||
return '(' . implode(', ', $columns) . ") $operator ($sql)";
|
||||
}
|
||||
|
||||
if (strpos($columns, '(') === false) {
|
||||
$columns = $this->queryBuilder->db->quoteColumnName($columns);
|
||||
}
|
||||
|
||||
return "$columns $operator ($sql)";
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds SQL for IN condition.
|
||||
*
|
||||
* @param string $operator
|
||||
* @param array|\Traversable $columns
|
||||
* @param array $values
|
||||
* @param array $params
|
||||
* @return string SQL
|
||||
*/
|
||||
protected function buildCompositeInCondition($operator, $columns, $values, &$params)
|
||||
{
|
||||
$vss = [];
|
||||
foreach ($values as $value) {
|
||||
$vs = [];
|
||||
foreach ($columns as $column) {
|
||||
if (isset($value[$column])) {
|
||||
$vs[] = $this->queryBuilder->bindParam($value[$column], $params);
|
||||
} else {
|
||||
$vs[] = 'NULL';
|
||||
}
|
||||
}
|
||||
$vss[] = '(' . implode(', ', $vs) . ')';
|
||||
}
|
||||
|
||||
if (empty($vss)) {
|
||||
return $operator === 'IN' ? '0=1' : '';
|
||||
}
|
||||
|
||||
$sqlColumns = [];
|
||||
foreach ($columns as $i => $column) {
|
||||
$sqlColumns[] = strpos($column, '(') === false ? $this->queryBuilder->db->quoteColumnName($column) : $column;
|
||||
}
|
||||
|
||||
return '(' . implode(', ', $sqlColumns) . ") $operator (" . implode(', ', $vss) . ')';
|
||||
}
|
||||
}
|
||||
71
framework/db/conditions/LikeCondition.php
Normal file
71
framework/db/conditions/LikeCondition.php
Normal file
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
namespace yii\db\conditions;
|
||||
|
||||
use yii\base\InvalidParamException;
|
||||
|
||||
/**
|
||||
* Class LikeCondition represents a `LIKE` condition.
|
||||
*
|
||||
* @author Dmytro Naumenko <d.naumenko.a@gmail.com>
|
||||
* @since 2.0.14
|
||||
*/
|
||||
class LikeCondition extends SimpleCondition
|
||||
{
|
||||
/**
|
||||
* @var array map of chars to their replacements.
|
||||
* By default it's set to `null` meaning responsibility is fully on condition builder.
|
||||
*/
|
||||
protected $escapingReplacements;
|
||||
|
||||
/**
|
||||
* @param string $column the column name.
|
||||
* @param string $operator the operator to use (e.g. `LIKE`, `NOT LIKE`, `OR LIKE` or `OR NOT LIKE`)
|
||||
* @param string[]|string $value single value or an array of values that $column should be compared with.
|
||||
* If it is an empty array the generated expression will be a `false` value if operator is `LIKE` or `OR LIKE`
|
||||
* and empty if operator is `NOT LIKE` or `OR NOT LIKE`.
|
||||
*/
|
||||
public function __construct($column, $operator, $value)
|
||||
{
|
||||
parent::__construct($column, $operator, $value);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method allows to specify how to escape special characters in the value(s).
|
||||
*
|
||||
* @param array an array of mappings from the special characters to their escaped counterparts.
|
||||
* You may use `false` or an empty array to indicate the values are already escaped and no escape
|
||||
* should be applied. Note that when using an escape mapping (or the third operand is not provided),
|
||||
* the values will be automatically enclosed within a pair of percentage characters.
|
||||
*/
|
||||
public function setEscapingReplacements($escapingReplacements)
|
||||
{
|
||||
$this->escapingReplacements = $escapingReplacements;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function getEscapingReplacements()
|
||||
{
|
||||
return $this->escapingReplacements;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @throws InvalidParamException if wrong number of operands have been given.
|
||||
*/
|
||||
public static function fromArrayDefinition($operator, $operands)
|
||||
{
|
||||
if (!isset($operands[0], $operands[1])) {
|
||||
throw new InvalidParamException("Operator '$operator' requires two operands.");
|
||||
}
|
||||
|
||||
$condition = new static($operands[0], $operator, $operands[1]);
|
||||
if (isset($operands[2])) {
|
||||
$condition->escapingReplacements = $operands[2];
|
||||
}
|
||||
|
||||
return $condition;
|
||||
}
|
||||
}
|
||||
105
framework/db/conditions/LikeConditionBuilder.php
Normal file
105
framework/db/conditions/LikeConditionBuilder.php
Normal file
@@ -0,0 +1,105 @@
|
||||
<?php
|
||||
|
||||
namespace yii\db\conditions;
|
||||
|
||||
use yii\base\InvalidParamException;
|
||||
use yii\db\ExpressionBuilderInterface;
|
||||
use yii\db\ExpressionBuilderTrait;
|
||||
use yii\db\ExpressionInterface;
|
||||
|
||||
/**
|
||||
* Class LikeConditionBuilder builds objects of [[LikeCondition]]
|
||||
*
|
||||
* @author Dmytro Naumenko <d.naumenko.a@gmail.com>
|
||||
* @since 2.0.14
|
||||
*/
|
||||
class LikeConditionBuilder implements ExpressionBuilderInterface
|
||||
{
|
||||
use ExpressionBuilderTrait;
|
||||
|
||||
/**
|
||||
* @var array map of chars to their replacements in LIKE conditions.
|
||||
* By default it's configured to escape `%`, `_` and `\` with `\`.
|
||||
*/
|
||||
protected $escapingReplacements = [
|
||||
'%' => '\%',
|
||||
'_' => '\_',
|
||||
'\\' => '\\\\',
|
||||
];
|
||||
/**
|
||||
* @var string|null character used to escape special characters in LIKE conditions.
|
||||
* By default it's assumed to be `\`.
|
||||
*/
|
||||
protected $escapeCharacter;
|
||||
|
||||
/**
|
||||
* Method builds the raw SQL from the $expression that will not be additionally
|
||||
* escaped or quoted.
|
||||
*
|
||||
* @param ExpressionInterface|LikeCondition $expression the expression to be built.
|
||||
* @param array $params the binding parameters.
|
||||
* @return string the raw SQL that will not be additionally escaped or quoted.
|
||||
*/
|
||||
public function build(ExpressionInterface $expression, array &$params = [])
|
||||
{
|
||||
$operator = $expression->getOperator();
|
||||
$column = $expression->getColumn();
|
||||
$values = $expression->getValue();
|
||||
$escape = $expression->getEscapingReplacements() ?: $this->escapingReplacements;
|
||||
|
||||
list($andor, $not, $operator) = $this->parseOperator($operator);
|
||||
|
||||
if (!is_array($values)) {
|
||||
$values = [$values];
|
||||
}
|
||||
|
||||
if (empty($values)) {
|
||||
return $not ? '' : '0=1';
|
||||
}
|
||||
|
||||
if (strpos($column, '(') === false) {
|
||||
$column = $this->queryBuilder->db->quoteColumnName($column);
|
||||
}
|
||||
|
||||
$escapeSql = $this->getEscapeSql();
|
||||
$parts = [];
|
||||
foreach ($values as $value) {
|
||||
if ($value instanceof ExpressionInterface) {
|
||||
$phName = $this->queryBuilder->buildExpression($value, $params);
|
||||
} else {
|
||||
$phName = $this->queryBuilder->bindParam(empty($escape) ? $value : ('%' . strtr($value, $escape) . '%'), $params);
|
||||
}
|
||||
$parts[] = "{$column} {$operator} {$phName}{$escapeSql}";
|
||||
}
|
||||
|
||||
return implode($andor, $parts);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
private function getEscapeSql()
|
||||
{
|
||||
if ($this->escapeCharacter !== null) {
|
||||
return " ESCAPE '{$this->escapeCharacter}'";
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $operator
|
||||
* @return array
|
||||
*/
|
||||
protected function parseOperator($operator)
|
||||
{
|
||||
if (!preg_match('/^(AND |OR |)(((NOT |))I?LIKE)/', $operator, $matches)) {
|
||||
throw new InvalidParamException("Invalid operator '$operator'.");
|
||||
}
|
||||
$andor = ' ' . (!empty($matches[1]) ? $matches[1] : 'AND ');
|
||||
$not = !empty($matches[3]);
|
||||
$operator = $matches[2];
|
||||
|
||||
return [$andor, $not, $operator];
|
||||
}
|
||||
}
|
||||
50
framework/db/conditions/NotCondition.php
Normal file
50
framework/db/conditions/NotCondition.php
Normal file
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
namespace yii\db\conditions;
|
||||
|
||||
use yii\base\InvalidParamException;
|
||||
|
||||
/**
|
||||
* Condition that inverts passed [[condition]].
|
||||
*
|
||||
* @author Dmytro Naumenko <d.naumenko.a@gmail.com>
|
||||
* @since 2.0.14
|
||||
*/
|
||||
class NotCondition implements ConditionInterface
|
||||
{
|
||||
/**
|
||||
* @var mixed the condition to be negated
|
||||
*/
|
||||
protected $condition;
|
||||
|
||||
/**
|
||||
* NotCondition constructor.
|
||||
*
|
||||
* @param mixed $condition the condition to be negated
|
||||
*/
|
||||
public function __construct($condition)
|
||||
{
|
||||
$this->condition = $condition;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getCondition()
|
||||
{
|
||||
return $this->condition;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @throws InvalidParamException if wrong number of operands have been given.
|
||||
*/
|
||||
public static function fromArrayDefinition($operator, $operands)
|
||||
{
|
||||
if (count($operands) !== 1) {
|
||||
throw new InvalidParamException("Operator '$operator' requires exactly one operand.");
|
||||
}
|
||||
|
||||
return new static(array_shift($operands));
|
||||
}
|
||||
}
|
||||
45
framework/db/conditions/NotConditionBuilder.php
Normal file
45
framework/db/conditions/NotConditionBuilder.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace yii\db\conditions;
|
||||
|
||||
use yii\db\ExpressionBuilderInterface;
|
||||
use yii\db\ExpressionBuilderTrait;
|
||||
use yii\db\ExpressionInterface;
|
||||
|
||||
/**
|
||||
* Class NotConditionBuilder builds objects of [[NotCondition]]
|
||||
*
|
||||
* @author Dmytro Naumenko <d.naumenko.a@gmail.com>
|
||||
* @since 2.0.14
|
||||
*/
|
||||
class NotConditionBuilder implements ExpressionBuilderInterface
|
||||
{
|
||||
use ExpressionBuilderTrait;
|
||||
|
||||
/**
|
||||
* Method builds the raw SQL from the $expression that will not be additionally
|
||||
* escaped or quoted.
|
||||
*
|
||||
* @param ExpressionInterface|NotCondition $expression the expression to be built.
|
||||
* @param array $params the binding parameters.
|
||||
* @return string the raw SQL that will not be additionally escaped or quoted.
|
||||
*/
|
||||
public function build(ExpressionInterface $expression, array &$params = [])
|
||||
{
|
||||
$operand = $expression->getCondition();
|
||||
if ($operand === '') {
|
||||
return '';
|
||||
}
|
||||
|
||||
$expession = $this->queryBuilder->buildCondition($operand, $params);
|
||||
return "{$this->getNegationOperator()} ($expession)";
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
protected function getNegationOperator()
|
||||
{
|
||||
return 'NOT';
|
||||
}
|
||||
}
|
||||
22
framework/db/conditions/OrCondition.php
Normal file
22
framework/db/conditions/OrCondition.php
Normal file
@@ -0,0 +1,22 @@
|
||||
<?php
|
||||
|
||||
namespace yii\db\conditions;
|
||||
|
||||
/**
|
||||
* Condition that connects two or more SQL expressions with the `AND` operator.
|
||||
*
|
||||
* @author Dmytro Naumenko <d.naumenko.a@gmail.com>
|
||||
* @since 2.0.14
|
||||
*/
|
||||
class OrCondition extends ConjunctionCondition
|
||||
{
|
||||
/**
|
||||
* Returns the operator that is represented by this condition class, e.g. `AND`, `OR`.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getOperator()
|
||||
{
|
||||
return 'OR';
|
||||
}
|
||||
}
|
||||
78
framework/db/conditions/SimpleCondition.php
Normal file
78
framework/db/conditions/SimpleCondition.php
Normal file
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
namespace yii\db\conditions;
|
||||
|
||||
use yii\base\InvalidParamException;
|
||||
|
||||
/**
|
||||
* Class SimpleCondition represents a simple condition like `"column" operator value`.
|
||||
*
|
||||
* @author Dmytro Naumenko <d.naumenko.a@gmail.com>
|
||||
* @since 2.0.14
|
||||
*/
|
||||
class SimpleCondition implements ConditionInterface
|
||||
{
|
||||
/**
|
||||
* @var string $operator the operator to use. Anything could be used e.g. `>`, `<=`, etc.
|
||||
*/
|
||||
protected $operator;
|
||||
/**
|
||||
* @var mixed the column name to the left of [[operator]]
|
||||
*/
|
||||
protected $column;
|
||||
/**
|
||||
* @var mixed the value to the right of the [[operator]]
|
||||
*/
|
||||
protected $value;
|
||||
|
||||
/**
|
||||
* SimpleCondition constructor
|
||||
*
|
||||
* @param mixed $column the literal to the left of $operator
|
||||
* @param string $operator the operator to use. Anything could be used e.g. `>`, `<=`, etc.
|
||||
* @param mixed $value the literal to the right of $operator
|
||||
*/
|
||||
public function __construct($column, $operator, $value)
|
||||
{
|
||||
$this->column = $column;
|
||||
$this->operator = $operator;
|
||||
$this->value = $value;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string
|
||||
*/
|
||||
public function getOperator()
|
||||
{
|
||||
return $this->operator;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getColumn()
|
||||
{
|
||||
return $this->column;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function getValue()
|
||||
{
|
||||
return $this->value;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @throws InvalidParamException if wrong number of operands have been given.
|
||||
*/
|
||||
public static function fromArrayDefinition($operator, $operands)
|
||||
{
|
||||
if (count($operands) !== 2) {
|
||||
throw new InvalidParamException("Operator '$operator' requires two operands.");
|
||||
}
|
||||
|
||||
return new static($operands[0], $operator, $operands[1]);
|
||||
}
|
||||
}
|
||||
52
framework/db/conditions/SimpleConditionBuilder.php
Normal file
52
framework/db/conditions/SimpleConditionBuilder.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace yii\db\conditions;
|
||||
|
||||
use yii\db\ExpressionBuilderInterface;
|
||||
use yii\db\ExpressionBuilderTrait;
|
||||
use yii\db\ExpressionInterface;
|
||||
use yii\db\Query;
|
||||
|
||||
/**
|
||||
* Class NotConditionBuilder builds objects of [[SimpleCondition]]
|
||||
*
|
||||
* @author Dmytro Naumenko <d.naumenko.a@gmail.com>
|
||||
* @since 2.0.14
|
||||
*/
|
||||
class SimpleConditionBuilder implements ExpressionBuilderInterface
|
||||
{
|
||||
use ExpressionBuilderTrait;
|
||||
|
||||
/**
|
||||
* Method builds the raw SQL from the $expression that will not be additionally
|
||||
* escaped or quoted.
|
||||
*
|
||||
* @param ExpressionInterface|SimpleCondition $expression the expression to be built.
|
||||
* @param array $params the binding parameters.
|
||||
* @return string the raw SQL that will not be additionally escaped or quoted.
|
||||
*/
|
||||
public function build(ExpressionInterface $expression, array &$params = [])
|
||||
{
|
||||
$operator = $expression->getOperator();
|
||||
$column = $expression->getColumn();
|
||||
$value = $expression->getValue();
|
||||
|
||||
if (strpos($column, '(') === false) {
|
||||
$column = $this->queryBuilder->db->quoteColumnName($column);
|
||||
}
|
||||
|
||||
if ($value === null) {
|
||||
return "$column $operator NULL";
|
||||
}
|
||||
if ($value instanceof ExpressionInterface) {
|
||||
return "$column $operator {$this->queryBuilder->buildExpression($value, $params)}";
|
||||
}
|
||||
if ($value instanceof Query) {
|
||||
list($sql, $params) = $this->queryBuilder->build($value, $params);
|
||||
return "$column $operator ($sql)";
|
||||
}
|
||||
|
||||
$phName = $this->queryBuilder->bindParam($value, $params);
|
||||
return "$column $operator $phName";
|
||||
}
|
||||
}
|
||||
@@ -48,16 +48,12 @@ class QueryBuilder extends \yii\db\QueryBuilder
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $likeEscapeCharacter = '!';
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $likeEscapingReplacements = [
|
||||
'%' => '!%',
|
||||
'_' => '!_',
|
||||
'!' => '!!',
|
||||
];
|
||||
|
||||
protected function defaultExpressionBuilders()
|
||||
{
|
||||
return array_merge(parent::defaultExpressionBuilders(), [
|
||||
'yii\db\conditions\LikeCondition' => 'yii\db\cubrid\conditions\LikeConditionBuilder',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a SQL statement for resetting the sequence value of a table's primary key.
|
||||
|
||||
24
framework/db/cubrid/conditions/LikeConditionBuilder.php
Normal file
24
framework/db/cubrid/conditions/LikeConditionBuilder.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
namespace yii\db\cubrid\conditions;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
class LikeConditionBuilder extends \yii\db\conditions\LikeConditionBuilder
|
||||
{
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
protected $escapeCharacter = '!';
|
||||
/**
|
||||
* `\` is initialized in [[buildLikeCondition()]] method since
|
||||
* we need to choose replacement value based on [[\yii\db\Schema::quoteValue()]].
|
||||
* @inheritdoc
|
||||
*/
|
||||
protected $escapingReplacements = [
|
||||
'%' => '!%',
|
||||
'_' => '!_',
|
||||
'!' => '!!',
|
||||
];
|
||||
}
|
||||
@@ -48,14 +48,13 @@ class QueryBuilder extends \yii\db\QueryBuilder
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $likeEscapingReplacements = [
|
||||
'%' => '[%]',
|
||||
'_' => '[_]',
|
||||
'[' => '[[]',
|
||||
']' => '[]]',
|
||||
'\\' => '[\\]',
|
||||
];
|
||||
|
||||
protected function defaultExpressionBuilders()
|
||||
{
|
||||
return array_merge(parent::defaultExpressionBuilders(), [
|
||||
'yii\db\conditions\InCondition' => 'yii\db\mssql\conditions\InConditionBuilder',
|
||||
'yii\db\conditions\LikeCondition' => 'yii\db\mssql\conditions\LikeConditionBuilder',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
@@ -322,52 +321,6 @@ class QueryBuilder extends \yii\db\QueryBuilder
|
||||
return $this->_oldMssql;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @throws NotSupportedException if `$columns` is an array
|
||||
*/
|
||||
protected function buildSubqueryInCondition($operator, $columns, $values, &$params)
|
||||
{
|
||||
if (is_array($columns)) {
|
||||
throw new NotSupportedException(__METHOD__ . ' is not supported by MSSQL.');
|
||||
}
|
||||
|
||||
return parent::buildSubqueryInCondition($operator, $columns, $values, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds SQL for IN condition.
|
||||
*
|
||||
* @param string $operator
|
||||
* @param array $columns
|
||||
* @param array $values
|
||||
* @param array $params
|
||||
* @return string SQL
|
||||
*/
|
||||
protected function buildCompositeInCondition($operator, $columns, $values, &$params)
|
||||
{
|
||||
$quotedColumns = [];
|
||||
foreach ($columns as $i => $column) {
|
||||
$quotedColumns[$i] = strpos($column, '(') === false ? $this->db->quoteColumnName($column) : $column;
|
||||
}
|
||||
$vss = [];
|
||||
foreach ($values as $value) {
|
||||
$vs = [];
|
||||
foreach ($columns as $i => $column) {
|
||||
if (isset($value[$column])) {
|
||||
$phName = self::PARAM_PREFIX . count($params);
|
||||
$params[$phName] = $value[$column];
|
||||
$vs[] = $quotedColumns[$i] . ($operator === 'IN' ? ' = ' : ' != ') . $phName;
|
||||
} else {
|
||||
$vs[] = $quotedColumns[$i] . ($operator === 'IN' ? ' IS' : ' IS NOT') . ' NULL';
|
||||
}
|
||||
}
|
||||
$vss[] = '(' . implode($operator === 'IN' ? ' AND ' : ' OR ', $vs) . ')';
|
||||
}
|
||||
|
||||
return '(' . implode($operator === 'IN' ? ' OR ' : ' AND ', $vss) . ')';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @since 2.0.8
|
||||
@@ -390,8 +343,9 @@ class QueryBuilder extends \yii\db\QueryBuilder
|
||||
foreach ($columns as $name => $value) {
|
||||
// @see https://github.com/yiisoft/yii2/issues/12599
|
||||
if (isset($columnSchemas[$name]) && $columnSchemas[$name]->type === Schema::TYPE_BINARY && $columnSchemas[$name]->dbType === 'varbinary' && is_string($value)) {
|
||||
$phName = self::PARAM_PREFIX . count($params);
|
||||
$columns[$name] = new Expression("CONVERT(VARBINARY, $phName)", [$phName => $value]);
|
||||
$exParams = [];
|
||||
$phName = $this->bindParam($value, $exParams);
|
||||
$columns[$name] = new Expression("CONVERT(VARBINARY, $phName)", $exParams);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
53
framework/db/mssql/conditions/InConditionBuilder.php
Normal file
53
framework/db/mssql/conditions/InConditionBuilder.php
Normal file
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace yii\db\mssql\conditions;
|
||||
|
||||
use yii\base\NotSupportedException;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @author Dmytro Naumenko <d.naumenko.a@gmail.com>
|
||||
* @since 2.0.14
|
||||
*/
|
||||
class InConditionBuilder extends \yii\db\conditions\InConditionBuilder
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @throws NotSupportedException if `$columns` is an array
|
||||
*/
|
||||
protected function buildSubqueryInCondition($operator, $columns, $values, &$params)
|
||||
{
|
||||
if (is_array($columns)) {
|
||||
throw new NotSupportedException(__METHOD__ . ' is not supported by MSSQL.');
|
||||
}
|
||||
|
||||
return parent::buildSubqueryInCondition($operator, $columns, $values, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function buildCompositeInCondition($operator, $columns, $values, &$params)
|
||||
{
|
||||
$quotedColumns = [];
|
||||
foreach ($columns as $i => $column) {
|
||||
$quotedColumns[$i] = strpos($column, '(') === false ? $this->queryBuilder->db->quoteColumnName($column) : $column;
|
||||
}
|
||||
$vss = [];
|
||||
foreach ($values as $value) {
|
||||
$vs = [];
|
||||
foreach ($columns as $i => $column) {
|
||||
if (isset($value[$column])) {
|
||||
$phName = $this->queryBuilder->bindParam($value[$column], $params);
|
||||
$vs[] = $quotedColumns[$i] . ($operator === 'IN' ? ' = ' : ' != ') . $phName;
|
||||
} else {
|
||||
$vs[] = $quotedColumns[$i] . ($operator === 'IN' ? ' IS' : ' IS NOT') . ' NULL';
|
||||
}
|
||||
}
|
||||
$vss[] = '(' . implode($operator === 'IN' ? ' AND ' : ' OR ', $vs) . ')';
|
||||
}
|
||||
|
||||
return '(' . implode($operator === 'IN' ? ' OR ' : ' AND ', $vss) . ')';
|
||||
}
|
||||
}
|
||||
21
framework/db/mssql/conditions/LikeConditionBuilder.php
Normal file
21
framework/db/mssql/conditions/LikeConditionBuilder.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
namespace yii\db\mssql\conditions;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
class LikeConditionBuilder extends \yii\db\conditions\LikeConditionBuilder
|
||||
{
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
protected $escapingReplacements = [
|
||||
'%' => '[%]',
|
||||
'_' => '[_]',
|
||||
'[' => '[[]',
|
||||
']' => '[]]',
|
||||
'\\' => '[\\]',
|
||||
];
|
||||
|
||||
}
|
||||
42
framework/db/mysql/JsonExpressionBuilder.php
Normal file
42
framework/db/mysql/JsonExpressionBuilder.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace yii\db\mysql;
|
||||
|
||||
use yii\db\ExpressionBuilderInterface;
|
||||
use yii\db\ExpressionBuilderTrait;
|
||||
use yii\db\ExpressionInterface;
|
||||
use yii\db\JsonExpression;
|
||||
use yii\db\Query;
|
||||
use yii\helpers\Json;
|
||||
|
||||
/**
|
||||
* Class JsonExpressionBuilder builds [[JsonExpression]] for MySQL DBMS.
|
||||
*
|
||||
* @author Dmytro Naumenko <d.naumenko.a@gmail.com>
|
||||
* @since 2.0.14
|
||||
*/
|
||||
class JsonExpressionBuilder implements ExpressionBuilderInterface
|
||||
{
|
||||
use ExpressionBuilderTrait;
|
||||
|
||||
const PARAM_PREFIX = ':qp';
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @param JsonExpression|ExpressionInterface $expression the expression to be built
|
||||
*/
|
||||
public function build(ExpressionInterface $expression, array &$params = [])
|
||||
{
|
||||
$value = $expression->getValue();
|
||||
|
||||
if ($value instanceof Query) {
|
||||
list ($sql, $params) = $this->queryBuilder->build($value, $params);
|
||||
return "($sql)";
|
||||
}
|
||||
|
||||
$placeholder = static::PARAM_PREFIX . count($params);
|
||||
$params[$placeholder] = Json::encode($value);
|
||||
|
||||
return $placeholder;
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ namespace yii\db\mysql;
|
||||
use yii\base\InvalidParamException;
|
||||
use yii\base\NotSupportedException;
|
||||
use yii\db\Exception;
|
||||
use yii\db\Expression;
|
||||
use yii\db\ExpressionInterface;
|
||||
|
||||
/**
|
||||
* QueryBuilder is the query builder for MySQL databases.
|
||||
@@ -46,6 +46,15 @@ class QueryBuilder extends \yii\db\QueryBuilder
|
||||
Schema::TYPE_MONEY => 'decimal(19,4)',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function defaultExpressionBuilders()
|
||||
{
|
||||
return array_merge(parent::defaultExpressionBuilders(), [
|
||||
'yii\db\JsonExpression' => 'yii\db\mysql\JsonExpressionBuilder',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a SQL statement for renaming a column.
|
||||
@@ -247,18 +256,16 @@ class QueryBuilder extends \yii\db\QueryBuilder
|
||||
} else {
|
||||
foreach ($columns as $name => $value) {
|
||||
$names[] = $schema->quoteColumnName($name);
|
||||
if ($value instanceof Expression) {
|
||||
$placeholders[] = $value->expression;
|
||||
foreach ($value->params as $n => $v) {
|
||||
$params[$n] = $v;
|
||||
}
|
||||
if ($value instanceof ExpressionInterface) {
|
||||
$placeholders[] = $this->buildExpression($value, $params);
|
||||
} elseif ($value instanceof \yii\db\Query) {
|
||||
list($sql, $params) = $this->build($value, $params);
|
||||
$placeholders[] = "($sql)";
|
||||
} else {
|
||||
$phName = self::PARAM_PREFIX . count($params);
|
||||
$placeholders[] = $phName;
|
||||
$params[$phName] = !is_array($value) && isset($columnSchemas[$name]) ? $columnSchemas[$name]->dbTypecast($value) : $value;
|
||||
$placeholders[] = $this->bindParam(
|
||||
isset($columnSchemas[$name]) ? $columnSchemas[$name]->dbTypecast($value) : $value,
|
||||
$params
|
||||
);
|
||||
}
|
||||
}
|
||||
if (empty($names) && $tableSchema !== null) {
|
||||
@@ -350,4 +357,5 @@ class QueryBuilder extends \yii\db\QueryBuilder
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@ use yii\db\Connection;
|
||||
use yii\db\Exception;
|
||||
use yii\db\Expression;
|
||||
use yii\helpers\StringHelper;
|
||||
use yii\db\ExpressionInterface;
|
||||
|
||||
/**
|
||||
* QueryBuilder is the query builder for Oracle databases.
|
||||
@@ -50,18 +51,13 @@ class QueryBuilder extends \yii\db\QueryBuilder
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $likeEscapeCharacter = '!';
|
||||
/**
|
||||
* `\` is initialized in [[buildLikeCondition()]] method since
|
||||
* we need to choose replacement value based on [[\yii\db\Schema::quoteValue()]].
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $likeEscapingReplacements = [
|
||||
'%' => '!%',
|
||||
'_' => '!_',
|
||||
'!' => '!!',
|
||||
];
|
||||
|
||||
protected function defaultExpressionBuilders()
|
||||
{
|
||||
return array_merge(parent::defaultExpressionBuilders(), [
|
||||
'yii\db\conditions\InCondition' => 'yii\db\conditions\oci\InConditionBuilder',
|
||||
'yii\db\conditions\LikeCondition' => 'yii\db\oci\conditions\LikeConditionBuilder',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
@@ -200,18 +196,16 @@ EOD;
|
||||
} else {
|
||||
foreach ($columns as $name => $value) {
|
||||
$names[] = $schema->quoteColumnName($name);
|
||||
if ($value instanceof Expression) {
|
||||
$placeholders[] = $value->expression;
|
||||
foreach ($value->params as $n => $v) {
|
||||
$params[$n] = $v;
|
||||
}
|
||||
if ($value instanceof ExpressionInterface) {
|
||||
$placeholders[] = $this->buildExpression($value, $params);
|
||||
} elseif ($value instanceof \yii\db\Query) {
|
||||
list($sql, $params) = $this->build($value, $params);
|
||||
$placeholders[] = "($sql)";
|
||||
} else {
|
||||
$phName = self::PARAM_PREFIX . count($params);
|
||||
$placeholders[] = $phName;
|
||||
$params[$phName] = !is_array($value) && isset($columnSchemas[$name]) ? $columnSchemas[$name]->dbTypecast($value) : $value;
|
||||
$placeholders[] = $this->bindParam(
|
||||
isset($columnSchemas[$name]) ? $columnSchemas[$name]->dbTypecast($value) : $value,
|
||||
$params
|
||||
);
|
||||
}
|
||||
}
|
||||
if (empty($names) && $tableSchema !== null) {
|
||||
@@ -265,7 +259,7 @@ EOD;
|
||||
foreach ($rows as $row) {
|
||||
$vs = [];
|
||||
foreach ($row as $i => $value) {
|
||||
if (isset($columns[$i], $columnSchemas[$columns[$i]]) && !is_array($value)) {
|
||||
if (isset($columns[$i], $columnSchemas[$columns[$i]])) {
|
||||
$value = $columnSchemas[$columns[$i]]->dbTypecast($value);
|
||||
}
|
||||
if (is_string($value)) {
|
||||
@@ -322,74 +316,4 @@ EOD;
|
||||
{
|
||||
return 'COMMENT ON TABLE ' . $this->db->quoteTableName($table) . " IS ''";
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
public function buildLikeCondition($operator, $operands, &$params)
|
||||
{
|
||||
if (!isset($this->likeEscapingReplacements['\\'])) {
|
||||
/*
|
||||
* Different pdo_oci8 versions may or may not implement PDO::quote(), so
|
||||
* yii\db\Schema::quoteValue() may or may not quote \.
|
||||
*/
|
||||
$this->likeEscapingReplacements['\\'] = substr($this->db->quoteValue('\\'), 1, -1);
|
||||
}
|
||||
|
||||
return parent::buildLikeCondition($operator, $operands, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function buildInCondition($operator, $operands, &$params)
|
||||
{
|
||||
$splitCondition = $this->splitInCondition($operator, $operands, $params);
|
||||
if ($splitCondition !== null) {
|
||||
return $splitCondition;
|
||||
}
|
||||
|
||||
return parent::buildInCondition($operator, $operands, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Oracle DBMS does not support more than 1000 parameters in `IN` condition.
|
||||
* This method splits long `IN` condition into series of smaller ones.
|
||||
*
|
||||
* @param string $operator
|
||||
* @param array $operands
|
||||
* @param array $params
|
||||
* @return null|string null when split is not required. Otherwise - built SQL condition.
|
||||
* @throws Exception
|
||||
* @since 2.0.12
|
||||
*/
|
||||
protected function splitInCondition($operator, $operands, &$params)
|
||||
{
|
||||
if (!isset($operands[0], $operands[1])) {
|
||||
throw new Exception("Operator '$operator' requires two operands.");
|
||||
}
|
||||
|
||||
list($column, $values) = $operands;
|
||||
|
||||
if ($values instanceof \Traversable) {
|
||||
$values = iterator_to_array($values);
|
||||
}
|
||||
|
||||
if (!is_array($values)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$maxParameters = 1000;
|
||||
$count = count($values);
|
||||
if ($count <= $maxParameters) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$condition = [($operator === 'IN') ? 'OR' : 'AND'];
|
||||
for ($i = 0; $i < $count; $i += $maxParameters) {
|
||||
$condition[] = [$operator, $column, array_slice($values, $i, $maxParameters)];
|
||||
}
|
||||
|
||||
return $this->buildCondition(['AND', $condition], $params);
|
||||
}
|
||||
}
|
||||
|
||||
66
framework/db/oci/conditions/InConditionBuilder.php
Normal file
66
framework/db/oci/conditions/InConditionBuilder.php
Normal file
@@ -0,0 +1,66 @@
|
||||
<?php
|
||||
|
||||
namespace yii\db\oci\conditions;
|
||||
|
||||
use yii\db\conditions\InCondition;
|
||||
use yii\db\ExpressionInterface;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
class InConditionBuilder extends \yii\db\conditions\InConditionBuilder
|
||||
{
|
||||
/**
|
||||
* Method builds the raw SQL from the $expression that will not be additionally
|
||||
* escaped or quoted.
|
||||
*
|
||||
* @param ExpressionInterface|InCondition $expression the expression to be built.
|
||||
* @param array $params the binding parameters.
|
||||
* @return string the raw SQL that will not be additionally escaped or quoted.
|
||||
*/
|
||||
public function build(ExpressionInterface $expression, array &$params = [])
|
||||
{
|
||||
$splitCondition = $this->splitCondition($expression, $params);
|
||||
if ($splitCondition !== null) {
|
||||
return $splitCondition;
|
||||
}
|
||||
|
||||
return parent::build($expression, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Oracle DBMS does not support more than 1000 parameters in `IN` condition.
|
||||
* This method splits long `IN` condition into series of smaller ones.
|
||||
*
|
||||
* @param ExpressionInterface|InCondition $condition the expression to be built.
|
||||
* @param array $params the binding parameters.
|
||||
* @return null|string null when split is not required. Otherwise - built SQL condition.
|
||||
*/
|
||||
protected function splitCondition(InCondition $condition, &$params)
|
||||
{
|
||||
$operator = $condition->getOperator();
|
||||
$values = $condition->getValues();
|
||||
$column = $condition->getColumn();
|
||||
|
||||
if ($values instanceof \Traversable) {
|
||||
$values = iterator_to_array($values);
|
||||
}
|
||||
|
||||
if (!is_array($values)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$maxParameters = 1000;
|
||||
$count = count($values);
|
||||
if ($count <= $maxParameters) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$slices = [];
|
||||
for ($i = 0; $i < $count; $i += $maxParameters) {
|
||||
$slices[] = $this->queryBuilder->createConditionFromArray([$operator, $column, array_slice($values, $i, $maxParameters)]);
|
||||
}
|
||||
|
||||
return $this->queryBuilder->buildCondition([($operator === 'IN') ? 'OR' : 'AND', $slices], $params);
|
||||
}
|
||||
}
|
||||
42
framework/db/oci/conditions/LikeConditionBuilder.php
Normal file
42
framework/db/oci/conditions/LikeConditionBuilder.php
Normal file
@@ -0,0 +1,42 @@
|
||||
<?php
|
||||
|
||||
namespace yii\db\oci\conditions;
|
||||
|
||||
use yii\db\ExpressionInterface;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
class LikeConditionBuilder extends \yii\db\conditions\LikeConditionBuilder
|
||||
{
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
protected $escapeCharacter = '!';
|
||||
/**
|
||||
* `\` is initialized in [[buildLikeCondition()]] method since
|
||||
* we need to choose replacement value based on [[\yii\db\Schema::quoteValue()]].
|
||||
* @inheritdoc
|
||||
*/
|
||||
protected $escapingReplacements = [
|
||||
'%' => '!%',
|
||||
'_' => '!_',
|
||||
'!' => '!!',
|
||||
];
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function build(ExpressionInterface $expression, array &$params = [])
|
||||
{
|
||||
if (!isset($this->escapingReplacements['\\'])) {
|
||||
/*
|
||||
* Different pdo_oci8 versions may or may not implement PDO::quote(), so
|
||||
* yii\db\Schema::quoteValue() may or may not quote \.
|
||||
*/
|
||||
$this->escapingReplacements['\\'] = substr($this->queryBuilder->db->quoteValue('\\'), 1, -1);
|
||||
}
|
||||
|
||||
return parent::build($expression, $params);
|
||||
}
|
||||
}
|
||||
143
framework/db/pgsql/ArrayExpressionBuilder.php
Normal file
143
framework/db/pgsql/ArrayExpressionBuilder.php
Normal file
@@ -0,0 +1,143 @@
|
||||
<?php
|
||||
|
||||
namespace yii\db\pgsql;
|
||||
|
||||
use yii\db\ArrayExpression;
|
||||
use yii\db\ExpressionBuilderInterface;
|
||||
use yii\db\ExpressionBuilderTrait;
|
||||
use yii\db\ExpressionInterface;
|
||||
use yii\db\JsonExpression;
|
||||
use yii\db\Query;
|
||||
|
||||
/**
|
||||
* Class ArrayExpressionBuilder builds [[ArrayExpression]] for PostgreSQL DBMS.
|
||||
*
|
||||
* @author Dmytro Naumenko <d.naumenko.a@gmail.com>
|
||||
* @since 2.0.14
|
||||
*/
|
||||
class ArrayExpressionBuilder implements ExpressionBuilderInterface
|
||||
{
|
||||
use ExpressionBuilderTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @param ArrayExpression|ExpressionInterface $expression the expression to be built
|
||||
*/
|
||||
public function build(ExpressionInterface $expression, array &$params = [])
|
||||
{
|
||||
$value = $expression->getValue();
|
||||
|
||||
if ($value instanceof Query) {
|
||||
list ($sql, $params) = $this->queryBuilder->build($value, $params);
|
||||
return $this->buildSubqueryArray($sql, $expression);
|
||||
}
|
||||
|
||||
$placeholders = $this->buildPlaceholders($expression, $params);
|
||||
if (empty($placeholders)) {
|
||||
return "'{}'";
|
||||
}
|
||||
|
||||
return 'ARRAY[' . implode(', ', $placeholders) . ']' . $this->getTypehint($expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds placeholders array out of $expression values
|
||||
* @param ExpressionInterface|ArrayExpression $expression
|
||||
* @param array $params the binding parameters.
|
||||
* @return array
|
||||
*/
|
||||
protected function buildPlaceholders(ExpressionInterface $expression, &$params)
|
||||
{
|
||||
$value = $expression->getValue();
|
||||
|
||||
$placeholders = [];
|
||||
if ($value === null || !is_array($value) && !$value instanceof \Traversable) {
|
||||
return $placeholders;
|
||||
}
|
||||
|
||||
if ($expression->getDimension() > 1) {
|
||||
foreach ($value as $item) {
|
||||
$placeholders[] = $this->build($this->unnestArrayExpression($expression, $item), $params);
|
||||
}
|
||||
return $placeholders;
|
||||
}
|
||||
|
||||
foreach ($value as $item) {
|
||||
if ($item instanceof Query) {
|
||||
list ($sql, $params) = $this->queryBuilder->build($item, $params);
|
||||
$placeholders[] = $this->buildSubqueryArray($sql, $expression);
|
||||
continue;
|
||||
}
|
||||
|
||||
$item = $this->typecastValue($expression, $item);
|
||||
if ($item instanceof ExpressionInterface) {
|
||||
$placeholders[] = $this->queryBuilder->buildExpression($item, $params);
|
||||
continue;
|
||||
}
|
||||
|
||||
$placeholders[] = $this->queryBuilder->bindParam($item, $params);
|
||||
}
|
||||
|
||||
return $placeholders;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ArrayExpression $expression
|
||||
* @param mixed $value
|
||||
* @return ArrayExpression
|
||||
*/
|
||||
private function unnestArrayExpression(ArrayExpression $expression, $value)
|
||||
{
|
||||
$expressionClass = get_class($expression);
|
||||
|
||||
return new $expressionClass($value, $expression->getType(), $expression->getDimension()-1);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ArrayExpression $expression
|
||||
* @return string the typecast expression based on [[type]].
|
||||
*/
|
||||
protected function getTypehint(ArrayExpression $expression)
|
||||
{
|
||||
if ($expression->getType() === null) {
|
||||
return '';
|
||||
}
|
||||
|
||||
$result = '::' . $expression->getType();
|
||||
$result .= str_repeat('[]', $expression->getDimension());
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build an array expression from a subquery SQL.
|
||||
*
|
||||
* @param string $sql the subquery SQL.
|
||||
* @param ArrayExpression $expression
|
||||
* @return string the subquery array expression.
|
||||
*/
|
||||
protected function buildSubqueryArray($sql, ArrayExpression $expression)
|
||||
{
|
||||
return 'ARRAY(' . $sql . ')' . $this->getTypehint($expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* Casts $value to use in $expression
|
||||
*
|
||||
* @param ArrayExpression $expression
|
||||
* @param mixed $value
|
||||
* @return JsonExpression
|
||||
*/
|
||||
protected function typecastValue(ArrayExpression $expression, $value)
|
||||
{
|
||||
if ($value instanceof ExpressionInterface) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
if (in_array($expression->getType(), [Schema::TYPE_JSON, Schema::TYPE_JSONB], true)) {
|
||||
return new JsonExpression($value);
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
}
|
||||
103
framework/db/pgsql/ArrayParser.php
Normal file
103
framework/db/pgsql/ArrayParser.php
Normal file
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
|
||||
namespace yii\db\pgsql;
|
||||
|
||||
/**
|
||||
* The class converts PostgreSQL array representation to PHP array
|
||||
*
|
||||
* @author Sergei Tigrov <rrr-r@ya.ru>
|
||||
* @author Dmytro Naumenko <d.naumenko.a@gmail.com>
|
||||
* @since 2.0.14
|
||||
*/
|
||||
class ArrayParser
|
||||
{
|
||||
/**
|
||||
* @var string Character used in array
|
||||
*/
|
||||
private $delimiter = ',';
|
||||
|
||||
/**
|
||||
* Convert array from PostgreSQL to PHP
|
||||
*
|
||||
* @param string $value string to be converted
|
||||
* @return array|null
|
||||
*/
|
||||
public function parse($value)
|
||||
{
|
||||
if ($value === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($value === '{}') {
|
||||
return [];
|
||||
}
|
||||
|
||||
return $this->parseArray($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Pares PgSQL array encoded in string
|
||||
*
|
||||
* @param string $value
|
||||
* @param int $i parse starting position
|
||||
* @return array
|
||||
*/
|
||||
private function parseArray($value, &$i = 0)
|
||||
{
|
||||
$result = [];
|
||||
$len = strlen($value);
|
||||
for (++$i; $i < $len; ++$i) {
|
||||
switch ($value[$i]) {
|
||||
case '{':
|
||||
$result[] = $this->parseArray($value, $i);
|
||||
break;
|
||||
case '}':
|
||||
break 2;
|
||||
case $this->delimiter:
|
||||
if (empty($result)) { // `{}` case
|
||||
$result[] = null;
|
||||
}
|
||||
if (in_array($value[$i + 1], [$this->delimiter, '}'], true)) { // `{,}` case
|
||||
$result[] = null;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
$result[] = $this->parseString($value, $i);
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses PgSQL encoded string
|
||||
*
|
||||
* @param string $value
|
||||
* @param int $i parse starting position
|
||||
* @return null|string
|
||||
*/
|
||||
private function parseString($value, &$i)
|
||||
{
|
||||
$isQuoted = $value[$i] === '"';
|
||||
$stringEndChars = $isQuoted ? ['"'] : [$this->delimiter, '}'];
|
||||
$result = '';
|
||||
$len = strlen($value);
|
||||
for ($i += $isQuoted ? 1 : 0; $i < $len; ++$i) {
|
||||
if (in_array($value[$i], ['\\', '"'], true) && in_array($value[$i + 1], [$value[$i], '"'], true)) {
|
||||
++$i;
|
||||
} elseif (in_array($value[$i], $stringEndChars, true)) {
|
||||
break;
|
||||
}
|
||||
|
||||
$result .= $value[$i];
|
||||
}
|
||||
|
||||
$i -= $isQuoted ? 0 : 1;
|
||||
|
||||
if (!$isQuoted && $result === 'NULL') {
|
||||
$result = null;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
}
|
||||
106
framework/db/pgsql/ColumnSchema.php
Normal file
106
framework/db/pgsql/ColumnSchema.php
Normal file
@@ -0,0 +1,106 @@
|
||||
<?php
|
||||
|
||||
namespace yii\db\pgsql;
|
||||
|
||||
use yii\db\ArrayExpression;
|
||||
use yii\db\ExpressionInterface;
|
||||
use yii\db\JsonExpression;
|
||||
|
||||
/**
|
||||
* Class ColumnSchema
|
||||
*
|
||||
* @author Dmytro Naumenko <d.naumenko.a@gmail.com>
|
||||
*/
|
||||
class ColumnSchema extends \yii\db\ColumnSchema
|
||||
{
|
||||
/**
|
||||
* @var int the dimension of array. Defaults to 0, means this column is not an array.
|
||||
*/
|
||||
public $dimension = 0;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function dbTypecast($value)
|
||||
{
|
||||
if ($value instanceof ExpressionInterface) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
if ($this->dimension > 0) {
|
||||
return new ArrayExpression($value, $this->dbType, $this->dimension);
|
||||
}
|
||||
if (in_array($this->dbType, [Schema::TYPE_JSON, Schema::TYPE_JSONB], true)) {
|
||||
return new JsonExpression($value, $this->type);
|
||||
}
|
||||
|
||||
return $this->typecast($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
public function phpTypecast($value)
|
||||
{
|
||||
if ($this->dimension > 0) {
|
||||
if (!is_array($value)) {
|
||||
$value = $this->getArrayParser()->parse($value);
|
||||
}
|
||||
if (is_array($value)) {
|
||||
array_walk_recursive($value, function (&$val, $key) {
|
||||
$val = $this->phpTypecastValue($val);
|
||||
});
|
||||
}
|
||||
|
||||
return new ArrayExpression($value, $this->dbType, $this->dimension);
|
||||
}
|
||||
|
||||
return $this->phpTypecastValue($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Casts $value after retrieving from the DBMS to PHP representation.
|
||||
*
|
||||
* @param string|null $value
|
||||
* @return bool|mixed|null
|
||||
*/
|
||||
protected function phpTypecastValue($value)
|
||||
{
|
||||
if ($value === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
switch ($this->type) {
|
||||
case Schema::TYPE_BOOLEAN:
|
||||
switch (strtolower($value)) {
|
||||
case 't':
|
||||
case 'true':
|
||||
return true;
|
||||
case 'f':
|
||||
case 'false':
|
||||
return false;
|
||||
}
|
||||
return (bool) $value;
|
||||
case Schema::TYPE_JSON:
|
||||
return json_decode($value, true);
|
||||
}
|
||||
|
||||
return parent::phpTypecast($value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates instance of ArrayParser
|
||||
*
|
||||
* @return ArrayParser
|
||||
*/
|
||||
protected function getArrayParser()
|
||||
{
|
||||
static $parser = null;
|
||||
|
||||
if ($parser === null) {
|
||||
$parser = new ArrayParser();
|
||||
}
|
||||
|
||||
return $parser;
|
||||
}
|
||||
}
|
||||
52
framework/db/pgsql/JsonExpressionBuilder.php
Normal file
52
framework/db/pgsql/JsonExpressionBuilder.php
Normal file
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
|
||||
namespace yii\db\pgsql;
|
||||
|
||||
use yii\db\ExpressionBuilderInterface;
|
||||
use yii\db\ExpressionBuilderTrait;
|
||||
use yii\db\ExpressionInterface;
|
||||
use yii\db\JsonExpression;
|
||||
use yii\db\Query;
|
||||
use yii\helpers\Json;
|
||||
|
||||
/**
|
||||
* Class JsonExpressionBuilder builds [[JsonExpression]] for PostgreSQL DBMS.
|
||||
*
|
||||
* @author Dmytro Naumenko <d.naumenko.a@gmail.com>
|
||||
* @since 2.0.14
|
||||
*/
|
||||
class JsonExpressionBuilder implements ExpressionBuilderInterface
|
||||
{
|
||||
use ExpressionBuilderTrait;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @param JsonExpression|ExpressionInterface $expression the expression to be built
|
||||
*/
|
||||
public function build(ExpressionInterface $expression, array &$params = [])
|
||||
{
|
||||
$value = $expression->getValue();
|
||||
|
||||
if ($value instanceof Query) {
|
||||
list ($sql, $params) = $this->queryBuilder->build($value, $params);
|
||||
return "($sql)" . $this->getTypecast($expression);
|
||||
}
|
||||
|
||||
$placeholder = $this->queryBuilder->bindParam(Json::encode($value), $params);
|
||||
|
||||
return $placeholder . $this->getTypecast($expression);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param JsonExpression $expression
|
||||
* @return string the typecast expression based on [[type]].
|
||||
*/
|
||||
protected function getTypecast(JsonExpression $expression)
|
||||
{
|
||||
if ($expression->getType() === null) {
|
||||
return '';
|
||||
}
|
||||
|
||||
return '::' . $expression->getType();
|
||||
}
|
||||
}
|
||||
@@ -8,6 +8,9 @@
|
||||
namespace yii\db\pgsql;
|
||||
|
||||
use yii\base\InvalidParamException;
|
||||
use yii\db\ExpressionInterface;
|
||||
use yii\db\PdoValue;
|
||||
use yii\db\Query;
|
||||
use yii\helpers\StringHelper;
|
||||
|
||||
/**
|
||||
@@ -68,32 +71,32 @@ class QueryBuilder extends \yii\db\QueryBuilder
|
||||
Schema::TYPE_BINARY => 'bytea',
|
||||
Schema::TYPE_BOOLEAN => 'boolean',
|
||||
Schema::TYPE_MONEY => 'numeric(19,4)',
|
||||
Schema::TYPE_JSON => 'jsonb'
|
||||
];
|
||||
|
||||
/**
|
||||
* @var array map of query condition to builder methods.
|
||||
* These methods are used by [[buildCondition]] to build SQL conditions from array syntax.
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $conditionBuilders = [
|
||||
'NOT' => 'buildNotCondition',
|
||||
'AND' => 'buildAndCondition',
|
||||
'OR' => 'buildAndCondition',
|
||||
'BETWEEN' => 'buildBetweenCondition',
|
||||
'NOT BETWEEN' => 'buildBetweenCondition',
|
||||
'IN' => 'buildInCondition',
|
||||
'NOT IN' => 'buildInCondition',
|
||||
'LIKE' => 'buildLikeCondition',
|
||||
'ILIKE' => 'buildLikeCondition',
|
||||
'NOT LIKE' => 'buildLikeCondition',
|
||||
'NOT ILIKE' => 'buildLikeCondition',
|
||||
'OR LIKE' => 'buildLikeCondition',
|
||||
'OR ILIKE' => 'buildLikeCondition',
|
||||
'OR NOT LIKE' => 'buildLikeCondition',
|
||||
'OR NOT ILIKE' => 'buildLikeCondition',
|
||||
'EXISTS' => 'buildExistsCondition',
|
||||
'NOT EXISTS' => 'buildExistsCondition',
|
||||
];
|
||||
protected function defaultConditionClasses()
|
||||
{
|
||||
return array_merge(parent::defaultConditionClasses(), [
|
||||
'ILIKE' => 'yii\db\conditions\LikeCondition',
|
||||
'NOT ILIKE' => 'yii\db\conditions\LikeCondition',
|
||||
'OR ILIKE' => 'yii\db\conditions\LikeCondition',
|
||||
'OR NOT ILIKE' => 'yii\db\conditions\LikeCondition',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function defaultExpressionBuilders()
|
||||
{
|
||||
return array_merge(parent::defaultExpressionBuilders(), [
|
||||
'yii\db\ArrayExpression' => 'yii\db\pgsql\ArrayExpressionBuilder',
|
||||
'yii\db\JsonExpression' => 'yii\db\pgsql\JsonExpressionBuilder',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a SQL statement for creating a new index.
|
||||
@@ -274,7 +277,7 @@ class QueryBuilder extends \yii\db\QueryBuilder
|
||||
$columnSchemas = $tableSchema->columns;
|
||||
foreach ($columns as $name => $value) {
|
||||
if (isset($columnSchemas[$name]) && $columnSchemas[$name]->type === Schema::TYPE_BINARY && is_string($value)) {
|
||||
$columns[$name] = [$value, \PDO::PARAM_LOB]; // explicitly setup PDO param type for binary column
|
||||
$columns[$name] = new PdoValue($value, \PDO::PARAM_LOB); // explicitly setup PDO param type for binary column
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -302,7 +305,7 @@ class QueryBuilder extends \yii\db\QueryBuilder
|
||||
foreach ($rows as $row) {
|
||||
$vs = [];
|
||||
foreach ($row as $i => $value) {
|
||||
if (isset($columns[$i], $columnSchemas[$columns[$i]]) && !is_array($value)) {
|
||||
if (isset($columns[$i], $columnSchemas[$columns[$i]])) {
|
||||
$value = $columnSchemas[$columns[$i]]->dbTypecast($value);
|
||||
}
|
||||
if (is_string($value)) {
|
||||
|
||||
@@ -30,10 +30,16 @@ class Schema extends \yii\db\Schema
|
||||
use ViewFinderTrait;
|
||||
use ConstraintFinderTrait;
|
||||
|
||||
const TYPE_JSONB = 'jsonb';
|
||||
|
||||
/**
|
||||
* @var string the default schema used for the current session.
|
||||
*/
|
||||
public $defaultSchema = 'public';
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public $columnSchemaClass = 'yii\db\pgsql\ColumnSchema';
|
||||
/**
|
||||
* @var array mapping from physical column types (keys) to abstract
|
||||
* column types (values)
|
||||
@@ -113,8 +119,8 @@ class Schema extends \yii\db\Schema
|
||||
'unknown' => self::TYPE_STRING,
|
||||
|
||||
'uuid' => self::TYPE_STRING,
|
||||
'json' => self::TYPE_STRING,
|
||||
'jsonb' => self::TYPE_STRING,
|
||||
'json' => self::TYPE_JSON,
|
||||
'jsonb' => self::TYPE_JSON,
|
||||
'xml' => self::TYPE_STRING,
|
||||
];
|
||||
|
||||
@@ -468,14 +474,18 @@ SELECT
|
||||
d.nspname AS table_schema,
|
||||
c.relname AS table_name,
|
||||
a.attname AS column_name,
|
||||
t.typname AS data_type,
|
||||
COALESCE(td.typname, tb.typname, t.typname) AS data_type,
|
||||
COALESCE(td.typtype, tb.typtype, t.typtype) AS type_type,
|
||||
a.attlen AS character_maximum_length,
|
||||
pg_catalog.col_description(c.oid, a.attnum) AS column_comment,
|
||||
a.atttypmod AS modifier,
|
||||
a.attnotnull = false AS is_nullable,
|
||||
CAST(pg_get_expr(ad.adbin, ad.adrelid) AS varchar) AS column_default,
|
||||
coalesce(pg_get_expr(ad.adbin, ad.adrelid) ~ 'nextval',false) AS is_autoinc,
|
||||
array_to_string((select array_agg(enumlabel) from pg_enum where enumtypid=a.atttypid)::varchar[],',') as enum_values,
|
||||
CASE WHEN COALESCE(td.typtype, tb.typtype, t.typtype) = 'e'::char
|
||||
THEN array_to_string((SELECT array_agg(enumlabel) FROM pg_enum WHERE enumtypid = COALESCE(td.oid, tb.oid, a.atttypid))::varchar[], ',')
|
||||
ELSE NULL
|
||||
END AS enum_values,
|
||||
CASE atttypid
|
||||
WHEN 21 /*int2*/ THEN 16
|
||||
WHEN 23 /*int4*/ THEN 32
|
||||
@@ -502,22 +512,24 @@ SELECT
|
||||
information_schema._pg_char_max_length(information_schema._pg_truetypid(a, t), information_schema._pg_truetypmod(a, t))
|
||||
AS numeric
|
||||
) AS size,
|
||||
a.attnum = any (ct.conkey) as is_pkey
|
||||
a.attnum = any (ct.conkey) as is_pkey,
|
||||
COALESCE(NULLIF(a.attndims, 0), NULLIF(t.typndims, 0), (t.typcategory='A')::int) AS dimension
|
||||
FROM
|
||||
pg_class c
|
||||
LEFT JOIN pg_attribute a ON a.attrelid = c.oid
|
||||
LEFT JOIN pg_attrdef ad ON a.attrelid = ad.adrelid AND a.attnum = ad.adnum
|
||||
LEFT JOIN pg_type t ON a.atttypid = t.oid
|
||||
LEFT JOIN pg_type tb ON (a.attndims > 0 OR t.typcategory='A') AND t.typelem > 0 AND t.typelem = tb.oid OR t.typbasetype > 0 AND t.typbasetype = tb.oid
|
||||
LEFT JOIN pg_type td ON t.typndims > 0 AND t.typbasetype > 0 AND tb.typelem = td.oid
|
||||
LEFT JOIN pg_namespace d ON d.oid = c.relnamespace
|
||||
LEFT join pg_constraint ct on ct.conrelid=c.oid and ct.contype='p'
|
||||
LEFT JOIN pg_constraint ct ON ct.conrelid = c.oid AND ct.contype = 'p'
|
||||
WHERE
|
||||
a.attnum > 0 and t.typname != ''
|
||||
and c.relname = {$tableName}
|
||||
and d.nspname = {$schemaName}
|
||||
a.attnum > 0 AND t.typname != ''
|
||||
AND c.relname = {$tableName}
|
||||
AND d.nspname = {$schemaName}
|
||||
ORDER BY
|
||||
a.attnum;
|
||||
SQL;
|
||||
|
||||
$columns = $this->db->createCommand($sql)->queryAll();
|
||||
if (empty($columns)) {
|
||||
return false;
|
||||
@@ -542,7 +554,7 @@ SQL;
|
||||
} elseif (stripos($column->dbType, 'bit') === 0 || stripos($column->dbType, 'varbit') === 0) {
|
||||
$column->defaultValue = bindec(trim($column->defaultValue, 'B\''));
|
||||
} elseif (preg_match("/^'(.*?)'::/", $column->defaultValue, $matches)) {
|
||||
$column->defaultValue = $matches[1];
|
||||
$column->defaultValue = $column->phpTypecast($matches[1]);
|
||||
} elseif (preg_match('/^(\()?(.*?)(?(1)\))(?:::.+)?$/', $column->defaultValue, $matches)) {
|
||||
if ($matches[2] === 'NULL') {
|
||||
$column->defaultValue = null;
|
||||
@@ -565,6 +577,7 @@ SQL;
|
||||
*/
|
||||
protected function loadColumnSchema($info)
|
||||
{
|
||||
/** @var ColumnSchema $column */
|
||||
$column = $this->createColumnSchema();
|
||||
$column->allowNull = $info['is_nullable'];
|
||||
$column->autoIncrement = $info['is_autoinc'];
|
||||
@@ -578,6 +591,7 @@ SQL;
|
||||
$column->precision = $info['numeric_precision'];
|
||||
$column->scale = $info['numeric_scale'];
|
||||
$column->size = $info['size'] === null ? null : (int) $info['size'];
|
||||
$column->dimension = (int)$info['dimension'];
|
||||
if (isset($this->typeMap[$column->dbType])) {
|
||||
$column->type = $this->typeMap[$column->dbType];
|
||||
} else {
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace yii\db\sqlite;
|
||||
use yii\base\InvalidParamException;
|
||||
use yii\base\NotSupportedException;
|
||||
use yii\db\Connection;
|
||||
use yii\db\Expression;
|
||||
use yii\db\ExpressionInterface;
|
||||
use yii\db\Query;
|
||||
use yii\helpers\StringHelper;
|
||||
|
||||
@@ -51,8 +51,13 @@ class QueryBuilder extends \yii\db\QueryBuilder
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected $likeEscapeCharacter = '\\';
|
||||
|
||||
protected function defaultExpressionBuilders()
|
||||
{
|
||||
return array_merge(parent::defaultExpressionBuilders(), [
|
||||
'yii\db\conditions\LikeCondition' => 'yii\db\sqlite\conditions\LikeConditionBuilder',
|
||||
'yii\db\conditions\InCondition' => 'yii\db\sqlite\conditions\InConditionBuilder',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a batch INSERT SQL statement.
|
||||
@@ -98,7 +103,7 @@ class QueryBuilder extends \yii\db\QueryBuilder
|
||||
foreach ($rows as $row) {
|
||||
$vs = [];
|
||||
foreach ($row as $i => $value) {
|
||||
if (!is_array($value) && isset($columnSchemas[$columns[$i]])) {
|
||||
if (isset($columnSchemas[$columns[$i]])) {
|
||||
$value = $columnSchemas[$columns[$i]]->dbTypecast($value);
|
||||
}
|
||||
if (is_string($value)) {
|
||||
@@ -418,52 +423,6 @@ class QueryBuilder extends \yii\db\QueryBuilder
|
||||
return $sql;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @throws NotSupportedException if `$columns` is an array
|
||||
*/
|
||||
protected function buildSubqueryInCondition($operator, $columns, $values, &$params)
|
||||
{
|
||||
if (is_array($columns)) {
|
||||
throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.');
|
||||
}
|
||||
|
||||
return parent::buildSubqueryInCondition($operator, $columns, $values, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds SQL for IN condition.
|
||||
*
|
||||
* @param string $operator
|
||||
* @param array $columns
|
||||
* @param array $values
|
||||
* @param array $params
|
||||
* @return string SQL
|
||||
*/
|
||||
protected function buildCompositeInCondition($operator, $columns, $values, &$params)
|
||||
{
|
||||
$quotedColumns = [];
|
||||
foreach ($columns as $i => $column) {
|
||||
$quotedColumns[$i] = strpos($column, '(') === false ? $this->db->quoteColumnName($column) : $column;
|
||||
}
|
||||
$vss = [];
|
||||
foreach ($values as $value) {
|
||||
$vs = [];
|
||||
foreach ($columns as $i => $column) {
|
||||
if (isset($value[$column])) {
|
||||
$phName = self::PARAM_PREFIX . count($params);
|
||||
$params[$phName] = $value[$column];
|
||||
$vs[] = $quotedColumns[$i] . ($operator === 'IN' ? ' = ' : ' != ') . $phName;
|
||||
} else {
|
||||
$vs[] = $quotedColumns[$i] . ($operator === 'IN' ? ' IS' : ' IS NOT') . ' NULL';
|
||||
}
|
||||
}
|
||||
$vss[] = '(' . implode($operator === 'IN' ? ' AND ' : ' OR ', $vs) . ')';
|
||||
}
|
||||
|
||||
return '(' . implode($operator === 'IN' ? ' OR ' : ' AND ', $vss) . ')';
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
@@ -487,15 +446,15 @@ class QueryBuilder extends \yii\db\QueryBuilder
|
||||
|
||||
if (!empty($query->orderBy)) {
|
||||
foreach ($query->orderBy as $expression) {
|
||||
if ($expression instanceof Expression) {
|
||||
$params = array_merge($params, $expression->params);
|
||||
if ($expression instanceof ExpressionInterface) {
|
||||
$this->buildExpression($expression, $params);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!empty($query->groupBy)) {
|
||||
foreach ($query->groupBy as $expression) {
|
||||
if ($expression instanceof Expression) {
|
||||
$params = array_merge($params, $expression->params);
|
||||
if ($expression instanceof ExpressionInterface) {
|
||||
$this->buildExpression($expression, $params);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
53
framework/db/sqlite/conditions/InConditionBuilder.php
Normal file
53
framework/db/sqlite/conditions/InConditionBuilder.php
Normal file
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
namespace yii\db\sqlite\conditions;
|
||||
|
||||
use yii\base\NotSupportedException;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @author Dmytro Naumenko <d.naumenko.a@gmail.com>
|
||||
* @since 2.0.14
|
||||
*/
|
||||
class InConditionBuilder extends \yii\db\conditions\InConditionBuilder
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @throws NotSupportedException if `$columns` is an array
|
||||
*/
|
||||
protected function buildSubqueryInCondition($operator, $columns, $values, &$params)
|
||||
{
|
||||
if (is_array($columns)) {
|
||||
throw new NotSupportedException(__METHOD__ . ' is not supported by SQLite.');
|
||||
}
|
||||
|
||||
return parent::buildSubqueryInCondition($operator, $columns, $values, $params);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
protected function buildCompositeInCondition($operator, $columns, $values, &$params)
|
||||
{
|
||||
$quotedColumns = [];
|
||||
foreach ($columns as $i => $column) {
|
||||
$quotedColumns[$i] = strpos($column, '(') === false ? $this->queryBuilder->db->quoteColumnName($column) : $column;
|
||||
}
|
||||
$vss = [];
|
||||
foreach ($values as $value) {
|
||||
$vs = [];
|
||||
foreach ($columns as $i => $column) {
|
||||
if (isset($value[$column])) {
|
||||
$phName = $this->queryBuilder->bindParam($value[$column], $params);
|
||||
$vs[] = $quotedColumns[$i] . ($operator === 'IN' ? ' = ' : ' != ') . $phName;
|
||||
} else {
|
||||
$vs[] = $quotedColumns[$i] . ($operator === 'IN' ? ' IS' : ' IS NOT') . ' NULL';
|
||||
}
|
||||
}
|
||||
$vss[] = '(' . implode($operator === 'IN' ? ' AND ' : ' OR ', $vs) . ')';
|
||||
}
|
||||
|
||||
return '(' . implode($operator === 'IN' ? ' OR ' : ' AND ', $vss) . ')';
|
||||
}
|
||||
}
|
||||
14
framework/db/sqlite/conditions/LikeConditionBuilder.php
Normal file
14
framework/db/sqlite/conditions/LikeConditionBuilder.php
Normal file
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
|
||||
namespace yii\db\sqlite\conditions;
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
class LikeConditionBuilder extends \yii\db\conditions\LikeConditionBuilder
|
||||
{
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
protected $escapeCharacter = '\\';
|
||||
}
|
||||
@@ -10,6 +10,7 @@ namespace yii\web;
|
||||
use Yii;
|
||||
use yii\base\InvalidConfigException;
|
||||
use yii\db\Connection;
|
||||
use yii\db\PdoValue;
|
||||
use yii\db\Query;
|
||||
use yii\di\Instance;
|
||||
|
||||
@@ -232,8 +233,8 @@ class DbSession extends MultiFieldSession
|
||||
*/
|
||||
protected function typecastFields($fields)
|
||||
{
|
||||
if (isset($fields['data']) && !is_array($fields['data'])) {
|
||||
$fields['data'] = [$fields['data'], \PDO::PARAM_LOB];
|
||||
if (isset($fields['data']) && is_array($fields['data'] && is_object($fields['data']))) {
|
||||
$fields['data'] = new PdoValue($fields['data'], \PDO::PARAM_LOB);
|
||||
}
|
||||
|
||||
return $fields;
|
||||
|
||||
@@ -135,7 +135,12 @@ CREATE TABLE "type" (
|
||||
bool_col2 boolean DEFAULT TRUE,
|
||||
ts_default TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
bit_col BIT(8) NOT NULL DEFAULT B'10000010',
|
||||
bigint_col BIGINT
|
||||
bigint_col BIGINT,
|
||||
intarray_col integer[],
|
||||
textarray2_col text[][],
|
||||
json_col json DEFAULT '{"a":1}',
|
||||
jsonb_col jsonb,
|
||||
jsonarray_col json[]
|
||||
);
|
||||
|
||||
CREATE TABLE "bool_values" (
|
||||
@@ -281,6 +286,15 @@ CREATE TABLE "bit_values" (
|
||||
|
||||
INSERT INTO "bit_values" (id, val) VALUES (1, '0'), (2, '1');
|
||||
|
||||
DROP TABLE IF EXISTS "array_and_json_types" CASCADE;
|
||||
CREATE TABLE "array_and_json_types" (
|
||||
intarray_col INT[],
|
||||
textarray2_col TEXT[][],
|
||||
json_col JSON,
|
||||
jsonb_col JSONB,
|
||||
jsonarray_col JSON[]
|
||||
);
|
||||
|
||||
CREATE TABLE "T_constraints_1"
|
||||
(
|
||||
"C_id" INT NOT NULL PRIMARY KEY,
|
||||
|
||||
@@ -12,6 +12,7 @@ use yii\behaviors\TimestampBehavior;
|
||||
use yii\db\ActiveRecord;
|
||||
use yii\db\Connection;
|
||||
use yii\db\Expression;
|
||||
use yii\db\ExpressionInterface;
|
||||
use yiiunit\TestCase;
|
||||
|
||||
/**
|
||||
@@ -155,9 +156,9 @@ class TimestampBehaviorTest extends TestCase
|
||||
];
|
||||
$model = new ActiveRecordTimestamp();
|
||||
$model->save(false);
|
||||
if ($expression instanceof Expression) {
|
||||
$this->assertInstanceOf(Expression::className(), $model->created_at);
|
||||
$this->assertInstanceOf(Expression::className(), $model->updated_at);
|
||||
if ($expression instanceof ExpressionInterface) {
|
||||
$this->assertInstanceOf('yii\db\ExpressionInterface', $model->created_at);
|
||||
$this->assertInstanceOf('yii\db\ExpressionInterface', $model->updated_at);
|
||||
$model->refresh();
|
||||
}
|
||||
$this->assertEquals($expected, $model->created_at);
|
||||
|
||||
@@ -118,9 +118,11 @@ abstract class DatabaseTestCase extends TestCase
|
||||
case 'sqlite':
|
||||
return str_replace(['[[', ']]'], '`', $sql);
|
||||
case 'cubrid':
|
||||
case 'pgsql':
|
||||
case 'oci':
|
||||
return str_replace(['[[', ']]'], '"', $sql);
|
||||
case 'pgsql':
|
||||
// more complex replacement needed to not conflict with postgres array syntax
|
||||
return str_replace(['\\[', '\\]'], ['[', ']'], preg_replace('/(\[\[)|((?<!(\[))\]\])/', '"', $sql));
|
||||
case 'sqlsrv':
|
||||
return str_replace(['[[', ']]'], ['[', ']'], $sql);
|
||||
default:
|
||||
|
||||
@@ -485,7 +485,10 @@ abstract class SchemaTest extends DatabaseTestCase
|
||||
$this->assertInternalType('object', $column->defaultValue, "defaultValue of column $name is expected to be an object but it is not.");
|
||||
$this->assertEquals((string) $expected['defaultValue'], (string) $column->defaultValue, "defaultValue of column $name does not match.");
|
||||
} else {
|
||||
$this->assertSame($expected['defaultValue'], $column->defaultValue, "defaultValue of column $name does not match.");
|
||||
$this->assertEquals($expected['defaultValue'], $column->defaultValue, "defaultValue of column $name does not match.");
|
||||
}
|
||||
if (isset($expected['dimension'])) { // PgSQL only
|
||||
$this->assertSame($expected['dimension'], $column->dimension, "dimension of column $name does not match");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,9 @@
|
||||
|
||||
namespace yiiunit\framework\db\mysql;
|
||||
|
||||
use yii\base\DynamicModel;
|
||||
use yii\db\JsonExpression;
|
||||
use yii\db\Query;
|
||||
use yii\db\Schema;
|
||||
|
||||
/**
|
||||
@@ -117,4 +120,34 @@ class QueryBuilderTest extends \yiiunit\framework\db\QueryBuilderTest
|
||||
$sql = $qb->resetSequence('item', 4);
|
||||
$this->assertEquals($expected, $sql);
|
||||
}
|
||||
|
||||
public function conditionProvider()
|
||||
{
|
||||
return array_merge(parent::conditionProvider(), [
|
||||
// json conditions
|
||||
[['=', 'jsoncol', new JsonExpression(['lang' => 'uk', 'country' => 'UA'])], '[[jsoncol]] = :qp0', [':qp0' => '{"lang":"uk","country":"UA"}']],
|
||||
[['=', 'jsoncol', new JsonExpression([false])], '[[jsoncol]] = :qp0', [':qp0' => '[false]']],
|
||||
'object with type. Type is ignored for MySQL' => [
|
||||
['=', 'prices', new JsonExpression(['seeds' => 15, 'apples' => 25], 'jsonb')],
|
||||
'[[prices]] = :qp0', [':qp0' => '{"seeds":15,"apples":25}']
|
||||
],
|
||||
'nested json' => [
|
||||
['=', 'data', new JsonExpression(['user' => ['login' => 'silverfire', 'password' => 'c4ny0ur34d17?'], 'props' => ['mood' => 'good']])],
|
||||
'[[data]] = :qp0', [':qp0' => '{"user":{"login":"silverfire","password":"c4ny0ur34d17?"},"props":{"mood":"good"}}']
|
||||
],
|
||||
'null value' => [['=', 'jsoncol', new JsonExpression(null)], '[[jsoncol]] = :qp0', [':qp0' => 'null']],
|
||||
'null as array value' => [['=', 'jsoncol', new JsonExpression([null])], '[[jsoncol]] = :qp0', [':qp0' => '[null]']],
|
||||
'null as object value' => [['=', 'jsoncol', new JsonExpression(['nil' => null])], '[[jsoncol[[ = :qp0', [':qp0' => '{"nil":null}']],
|
||||
|
||||
[['=', 'jsoncol', new JsonExpression(new DynamicModel(['a' => 1, 'b' => 2]))], '[[jsoncol]] = :qp0', [':qp0' => '{"a":1,"b":2}']],
|
||||
'query' => [
|
||||
['=', 'jsoncol', new JsonExpression((new Query())->select('params')->from('user')->where(['id' => 1]))],
|
||||
'[[jsoncol]] = (SELECT [[params]] FROM [[user]] WHERE [[id]]=:qp0)', [':qp0' => 1]
|
||||
],
|
||||
'query with type, that is ignored in MySQL' => [
|
||||
['=', 'jsoncol', new JsonExpression((new Query())->select('params')->from('user')->where(['id' => 1]), 'jsonb')],
|
||||
'[[jsoncol]] = (SELECT [[params]] FROM [[user]] WHERE [[id]]=:qp0)', [':qp0' => 1]
|
||||
],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,12 @@
|
||||
namespace yiiunit\framework\db\pgsql;
|
||||
|
||||
use yii\behaviors\TimestampBehavior;
|
||||
use yii\db\ArrayExpression;
|
||||
use yii\db\Expression;
|
||||
use yii\db\ExpressionInterface;
|
||||
use yii\db\JsonExpression;
|
||||
use yii\db\pgsql\Schema;
|
||||
use yii\helpers\Json;
|
||||
use yiiunit\data\ar\ActiveRecord;
|
||||
use yiiunit\data\ar\DefaultPk;
|
||||
use yiiunit\framework\ar\ActiveRecordTestTrait;
|
||||
@@ -172,6 +177,76 @@ class ActiveRecordTest extends \yiiunit\framework\db\ActiveRecordTest
|
||||
$record->save(false);
|
||||
$this->assertEquals(5, $record->primaryKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider arrayValuesProvider $attributes
|
||||
*/
|
||||
public function testArrayValues($attributes)
|
||||
{
|
||||
$type = new ArrayAndJsonTypes();
|
||||
foreach ($attributes as $attribute => $expected) {
|
||||
$type->$attribute = $expected[0];
|
||||
}
|
||||
$type->save();
|
||||
|
||||
$type = ArrayAndJsonTypes::find()->one();
|
||||
foreach ($attributes as $attribute => $expected) {
|
||||
$expected = isset($expected[1]) ? $expected[1] : $expected[0];
|
||||
$value = $type->$attribute;
|
||||
|
||||
$this->assertEquals($expected, $value, 'In column ' . $attribute);
|
||||
if ($value instanceof ArrayExpression) {
|
||||
$this->assertInstanceOf('\ArrayAccess', $value);
|
||||
foreach ($type->$attribute as $key => $v) { // testing arrayaccess
|
||||
$this->assertSame($expected[$key], $value[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public function arrayValuesProvider()
|
||||
{
|
||||
return [
|
||||
'simple arrays values' => [[
|
||||
'intarray_col' => [new ArrayExpression([1,-2,null,'42'], 'int4', 1)],
|
||||
'textarray2_col' => [new ArrayExpression([['text'], [null], [1]], 'text', 2)],
|
||||
'json_col' => [['a' => 1, 'b' => null, 'c' => [1,3,5]]],
|
||||
'jsonb_col' => [[null, 'a', 'b', '\"', '{"af"}']],
|
||||
'jsonarray_col' => [new ArrayExpression([[',', 'null', true, 'false', 'f']], 'json')],
|
||||
]],
|
||||
'arrays packed in classes' => [[
|
||||
'intarray_col' => [
|
||||
new ArrayExpression([1,-2,null,'42'], 'int', 1),
|
||||
new ArrayExpression([1,-2,null,'42'], 'int4', 1),
|
||||
],
|
||||
'textarray2_col' => [
|
||||
new ArrayExpression([['text'], [null], [1]], 'text', 2),
|
||||
new ArrayExpression([['text'], [null], [1]], 'text', 2),
|
||||
],
|
||||
'json_col' => [
|
||||
new JsonExpression(['a' => 1, 'b' => null, 'c' => [1,3,5]]),
|
||||
['a' => 1, 'b' => null, 'c' => [1,3,5]]
|
||||
],
|
||||
'jsonb_col' => [
|
||||
new JsonExpression([null, 'a', 'b', '\"', '{"af"}']),
|
||||
[null, 'a', 'b', '\"', '{"af"}']
|
||||
],
|
||||
'jsonarray_col' => [
|
||||
new Expression("array['[\",\",\"null\",true,\"false\",\"f\"]'::json]::json[]"),
|
||||
new ArrayExpression([[',', 'null', true, 'false', 'f']], 'json'),
|
||||
|
||||
]
|
||||
]],
|
||||
'scalars' => [[
|
||||
'json_col' => [
|
||||
'5.8',
|
||||
],
|
||||
'jsonb_col' => [
|
||||
pi()
|
||||
],
|
||||
]]
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
class BoolAR extends ActiveRecord
|
||||
@@ -200,3 +275,15 @@ class UserAR extends ActiveRecord
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* @property array intarray_col
|
||||
* @property array textarray2_col
|
||||
* @property array json_col
|
||||
* @property array jsonb_col
|
||||
* @property array jsonarray_col
|
||||
*/
|
||||
class ArrayAndJsonTypes extends ActiveRecord
|
||||
{
|
||||
}
|
||||
|
||||
45
tests/framework/db/pgsql/ArrayParserTest.php
Normal file
45
tests/framework/db/pgsql/ArrayParserTest.php
Normal file
@@ -0,0 +1,45 @@
|
||||
<?php
|
||||
|
||||
namespace yiiunit\framework\db\pgsql;
|
||||
|
||||
use yii\db\pgsql\ArrayParser;
|
||||
use yiiunit\TestCase;
|
||||
|
||||
class ArrayParserTest extends TestCase
|
||||
{
|
||||
/**
|
||||
* @var ArrayParser
|
||||
*/
|
||||
protected $arrayParser;
|
||||
|
||||
protected function setUp()
|
||||
{
|
||||
parent::setUp();
|
||||
|
||||
$this->arrayParser = new ArrayParser();
|
||||
}
|
||||
|
||||
public function convertProvider()
|
||||
{
|
||||
return [
|
||||
['{}', []],
|
||||
['{,}', [null, null]],
|
||||
['{,,}', [null, null, null]],
|
||||
['{1,2,}', ['1','2',null]],
|
||||
['{{},,1}', [[], null, '1']],
|
||||
['{"{\"key\":\"value\"}",NULL,"NULL","{}"}', ['{"key":"value"}', null, "NULL", '{}']],
|
||||
['{boo,",",,test', ['boo', ',', null, 'test']],
|
||||
['{"string1","str\\\\in\\"g2","str,ing3"}', ['string1','str\\in"g2','str,ing3']],
|
||||
['{{1,2,3},{4,5,6},{7,8,9}}', [['1','2','3'], ['4','5','6'], ['7','8','9']]],
|
||||
['{utf8€,👍}', ['utf8€', '👍']],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @dataProvider convertProvider
|
||||
*/
|
||||
public function testConvert($string, $expected)
|
||||
{
|
||||
$this->assertSame($expected, $this->arrayParser->parse($string));
|
||||
}
|
||||
}
|
||||
@@ -100,5 +100,7 @@ class ConnectionTest extends \yiiunit\framework\db\ConnectionTest
|
||||
$transaction = $connection->beginTransaction();
|
||||
$transaction->setIsolationLevel(Transaction::SERIALIZABLE . ' READ ONLY DEFERRABLE');
|
||||
$transaction->commit();
|
||||
|
||||
$this->assertTrue(true); // No error occurred – assert passed.
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,14 @@
|
||||
|
||||
namespace yiiunit\framework\db\pgsql;
|
||||
|
||||
use yii\base\DynamicModel;
|
||||
use yii\db\Expression;
|
||||
use yii\db\ArrayExpression;
|
||||
use yii\db\JsonExpression;
|
||||
use yii\db\Query;
|
||||
use yii\db\Schema;
|
||||
use yii\helpers\Json;
|
||||
use yiiunit\data\base\TraversableObject;
|
||||
|
||||
/**
|
||||
* @group db
|
||||
@@ -76,6 +83,68 @@ class QueryBuilderTest extends \yiiunit\framework\db\QueryBuilderTest
|
||||
[['not ilike', 'name', ['heyho', 'abc']], '"name" NOT ILIKE :qp0 AND "name" NOT ILIKE :qp1', [':qp0' => '%heyho%', ':qp1' => '%abc%']],
|
||||
[['or ilike', 'name', ['heyho', 'abc']], '"name" ILIKE :qp0 OR "name" ILIKE :qp1', [':qp0' => '%heyho%', ':qp1' => '%abc%']],
|
||||
[['or not ilike', 'name', ['heyho', 'abc']], '"name" NOT ILIKE :qp0 OR "name" NOT ILIKE :qp1', [':qp0' => '%heyho%', ':qp1' => '%abc%']],
|
||||
|
||||
// array condition corner cases
|
||||
[['@>', 'id', new ArrayExpression([1])], '"id" @> ARRAY[:qp0]', [':qp0' => 1]],
|
||||
'scalar can not be converted to array #1' => [['@>', 'id', new ArrayExpression(1)], '"id" @> \'{}\'', []],
|
||||
['scalar can not be converted to array #2' => ['@>', 'id', new ArrayExpression(false)], '"id" @> \'{}\'', []],
|
||||
[['&&', 'price', new ArrayExpression([12, 14], 'float')], '"price" && ARRAY[:qp0, :qp1]::float[]', [':qp0' => 12, ':qp1' => 14]],
|
||||
[['@>', 'id', new ArrayExpression([2, 3])], '"id" @> ARRAY[:qp0, :qp1]', [':qp0' => 2, ':qp1' => 3]],
|
||||
'array of arrays' => [['@>', 'id', new ArrayExpression([[1,2], [3,4]], 'float', 2)], '"id" @> ARRAY[ARRAY[:qp0, :qp1]::float[], ARRAY[:qp2, :qp3]::float[]\\]::float[][]', [':qp0' => 1, ':qp1' => 2, ':qp2' => 3, ':qp3' => 4]],
|
||||
[['@>', 'id', new ArrayExpression([])], '"id" @> \'{}\'', []],
|
||||
'array can contain nulls' => [['@>', 'id', new ArrayExpression([null])], '"id" @> ARRAY[:qp0]', [':qp0' => null]],
|
||||
'traversable objects are supported' => [['@>', 'id', new ArrayExpression(new TraversableObject([1, 2, 3]))], '[[id]] @> ARRAY[:qp0, :qp1, :qp2]', [':qp0' => 1, ':qp1' => 2, ':qp2' => 3]],
|
||||
[['@>', 'time', new ArrayExpression([new Expression('now()')])], '[[time]] @> ARRAY[now()]', []],
|
||||
[['@>', 'id', new ArrayExpression((new Query())->select('id')->from('users')->where(['active' => 1]))], '[[id]] @> ARRAY(SELECT [[id]] FROM [[users]] WHERE [[active]]=:qp0)', [':qp0' => 1]],
|
||||
[['@>', 'id', new ArrayExpression([(new Query())->select('id')->from('users')->where(['active' => 1])], 'integer')], '[[id]] @> ARRAY[ARRAY(SELECT [[id]] FROM [[users]] WHERE [[active]]=:qp0)::integer[]]::integer[]', [':qp0' => 1]],
|
||||
|
||||
// json conditions
|
||||
[['=', 'jsoncol', new JsonExpression(['lang' => 'uk', 'country' => 'UA'])], '[[jsoncol]] = :qp0', [':qp0' => '{"lang":"uk","country":"UA"}']],
|
||||
[['=', 'jsoncol', new JsonExpression([false])], '[[jsoncol]] = :qp0', [':qp0' => '[false]']],
|
||||
[['=', 'prices', new JsonExpression(['seeds' => 15, 'apples' => 25], 'jsonb')], '[[prices]] = :qp0::jsonb', [':qp0' => '{"seeds":15,"apples":25}']],
|
||||
'nested json' => [
|
||||
['=', 'data', new JsonExpression(['user' => ['login' => 'silverfire', 'password' => 'c4ny0ur34d17?'], 'props' => ['mood' => 'good']])],
|
||||
'"data" = :qp0', [':qp0' => '{"user":{"login":"silverfire","password":"c4ny0ur34d17?"},"props":{"mood":"good"}}']
|
||||
],
|
||||
'null value' => [['=', 'jsoncol', new JsonExpression(null)], '"jsoncol" = :qp0', [':qp0' => 'null']],
|
||||
'null as array value' => [['=', 'jsoncol', new JsonExpression([null])], '"jsoncol" = :qp0', [':qp0' => '[null]']],
|
||||
'null as object value' => [['=', 'jsoncol', new JsonExpression(['nil' => null])], '"jsoncol" = :qp0', [':qp0' => '{"nil":null}']],
|
||||
|
||||
[['=', 'jsoncol', new JsonExpression(new DynamicModel(['a' => 1, 'b' => 2]))], '[[jsoncol]] = :qp0', [':qp0' => '{"a":1,"b":2}']],
|
||||
'query' => [['=', 'jsoncol', new JsonExpression((new Query())->select('params')->from('user')->where(['id' => 1]))], '[[jsoncol]] = (SELECT [[params]] FROM [[user]] WHERE [[id]]=:qp0)', [':qp0' => 1]],
|
||||
'query with type' => [['=', 'jsoncol', new JsonExpression((new Query())->select('params')->from('user')->where(['id' => 1]), 'jsonb')], '[[jsoncol]] = (SELECT [[params]] FROM [[user]] WHERE [[id]]=:qp0)::jsonb', [':qp0' => 1]],
|
||||
|
||||
'array of json expressions' => [
|
||||
['=', 'colname', new ArrayExpression([new JsonExpression(['a' => null, 'b' => 123, 'c' => [4, 5]]), new JsonExpression([true])])],
|
||||
'"colname" = ARRAY[:qp0, :qp1]',
|
||||
[':qp0' => '{"a":null,"b":123,"c":[4,5]}', ':qp1' => '[true]']
|
||||
],
|
||||
'Items in ArrayExpression of type json should be casted to Json' => [
|
||||
['=', 'colname', new ArrayExpression([['a' => null, 'b' => 123, 'c' => [4, 5]], [true]], 'json')],
|
||||
'"colname" = ARRAY[:qp0, :qp1]::json[]',
|
||||
[':qp0' => '{"a":null,"b":123,"c":[4,5]}', ':qp1' => '[true]']
|
||||
],
|
||||
'Two dimension array of text' => [
|
||||
['=', 'colname', new ArrayExpression([['text1', 'text2'], ['text3', 'text4'], [null, 'text5']], 'text', 2)],
|
||||
'"colname" = ARRAY[ARRAY[:qp0, :qp1]::text[], ARRAY[:qp2, :qp3]::text[], ARRAY[:qp4, :qp5]::text[]]::text[][]',
|
||||
[':qp0' => 'text1', ':qp1' => 'text2', ':qp2' => 'text3', ':qp3' => 'text4', ':qp4' => null, ':qp5' => 'text5'],
|
||||
],
|
||||
'Three dimension array of booleans' => [
|
||||
['=', 'colname', new ArrayExpression([[[true], [false, null]], [[false], [true], [false]], [['t', 'f']]], 'bool', 3)],
|
||||
'"colname" = ARRAY[ARRAY[ARRAY[:qp0]::bool[], ARRAY[:qp1, :qp2]::bool[]]::bool[][], ARRAY[ARRAY[:qp3]::bool[], ARRAY[:qp4]::bool[], ARRAY[:qp5]::bool[]]::bool[][], ARRAY[ARRAY[:qp6, :qp7]::bool[]]::bool[][]]::bool[][][]',
|
||||
[':qp0' => true, ':qp1' => false, ':qp2' => null, ':qp3' => false, ':qp4' => true, ':qp5' => false, ':qp6' => 't', ':qp7' => 'f'],
|
||||
],
|
||||
|
||||
// Checks to verity that operators work correctly
|
||||
[['@>', 'id', new ArrayExpression([1])], '"id" @> ARRAY[:qp0]', [':qp0' => 1]],
|
||||
[['<@', 'id', new ArrayExpression([1])], '"id" <@ ARRAY[:qp0]', [':qp0' => 1]],
|
||||
[['=', 'id', new ArrayExpression([1])], '"id" = ARRAY[:qp0]', [':qp0' => 1]],
|
||||
[['<>', 'id', new ArrayExpression([1])], '"id" <> ARRAY[:qp0]', [':qp0' => 1]],
|
||||
[['>', 'id', new ArrayExpression([1])], '"id" > ARRAY[:qp0]', [':qp0' => 1]],
|
||||
[['<', 'id', new ArrayExpression([1])], '"id" < ARRAY[:qp0]', [':qp0' => 1]],
|
||||
[['>=', 'id', new ArrayExpression([1])], '"id" >= ARRAY[:qp0]', [':qp0' => 1]],
|
||||
[['<=', 'id', new ArrayExpression([1])], '"id" <= ARRAY[:qp0]', [':qp0' => 1]],
|
||||
[['&&', 'id', new ArrayExpression([1])], '"id" && ARRAY[:qp0]', [':qp0' => 1]],
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
|
||||
namespace yiiunit\framework\db\pgsql;
|
||||
|
||||
use yii\db\conditions\ExistsConditionBuilder;
|
||||
use yii\db\Expression;
|
||||
use yiiunit\data\ar\ActiveRecord;
|
||||
use yiiunit\data\ar\Type;
|
||||
@@ -85,6 +86,71 @@ class SchemaTest extends \yiiunit\framework\db\SchemaTest
|
||||
'scale' => 0,
|
||||
'defaultValue' => null,
|
||||
];
|
||||
$columns['intarray_col'] = [
|
||||
'type' => 'integer',
|
||||
'dbType' => 'int4',
|
||||
'phpType' => 'integer',
|
||||
'allowNull' => true,
|
||||
'autoIncrement' => false,
|
||||
'enumValues' => null,
|
||||
'size' => null,
|
||||
'precision' => null,
|
||||
'scale' => null,
|
||||
'defaultValue' => null,
|
||||
'dimension' => 1
|
||||
];
|
||||
$columns['textarray2_col'] = [
|
||||
'type' => 'text',
|
||||
'dbType' => 'text',
|
||||
'phpType' => 'string',
|
||||
'allowNull' => true,
|
||||
'autoIncrement' => false,
|
||||
'enumValues' => null,
|
||||
'size' => null,
|
||||
'precision' => null,
|
||||
'scale' => null,
|
||||
'defaultValue' => null,
|
||||
'dimension' => 2
|
||||
];
|
||||
$columns['json_col'] = [
|
||||
'type' => 'json',
|
||||
'dbType' => 'json',
|
||||
'phpType' => 'array',
|
||||
'allowNull' => true,
|
||||
'autoIncrement' => false,
|
||||
'enumValues' => null,
|
||||
'size' => null,
|
||||
'precision' => null,
|
||||
'scale' => null,
|
||||
'defaultValue' => ["a" => 1],
|
||||
'dimension' => 0
|
||||
];
|
||||
$columns['jsonb_col'] = [
|
||||
'type' => 'json',
|
||||
'dbType' => 'jsonb',
|
||||
'phpType' => 'array',
|
||||
'allowNull' => true,
|
||||
'autoIncrement' => false,
|
||||
'enumValues' => null,
|
||||
'size' => null,
|
||||
'precision' => null,
|
||||
'scale' => null,
|
||||
'defaultValue' => null,
|
||||
'dimension' => 0
|
||||
];
|
||||
$columns['jsonarray_col'] = [
|
||||
'type' => 'json',
|
||||
'dbType' => 'json',
|
||||
'phpType' => 'array',
|
||||
'allowNull' => true,
|
||||
'autoIncrement' => false,
|
||||
'enumValues' => null,
|
||||
'size' => null,
|
||||
'precision' => null,
|
||||
'scale' => null,
|
||||
'defaultValue' => null,
|
||||
'dimension' => 1
|
||||
];
|
||||
|
||||
return $columns;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user