-
-
Notifications
You must be signed in to change notification settings - Fork 36
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
19 changed files
with
914 additions
and
53 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Yiisoft\Db\Expression; | ||
|
||
use Traversable; | ||
use Yiisoft\Db\Schema\Column\ColumnSchemaInterface; | ||
|
||
use function array_key_exists; | ||
use function array_keys; | ||
use function get_object_vars; | ||
use function is_object; | ||
use function iterator_to_array; | ||
|
||
/** | ||
* Represents a structured type SQL expression. | ||
* | ||
* @see https://en.wikipedia.org/wiki/Structured_type | ||
* | ||
* For example: | ||
* | ||
* ```php | ||
* new StructuredExpression(['price' => 10, 'currency_code' => 'USD']); | ||
* ``` | ||
* | ||
* Will be encoded to `ROW(10, USD)` in PostgreSQL. | ||
*/ | ||
final class StructuredExpression implements ExpressionInterface | ||
{ | ||
/** | ||
* @param array|object $value The content of the structured type. It can be represented as | ||
* - an associative `array` of column names and values; | ||
* - an indexed `array` of column values in the order of structured type columns; | ||
* - an `iterable` object that can be converted to an `array` using `iterator_to_array()`; | ||
* - an `object` that can be converted to an `array` using `get_object_vars()`; | ||
* - an `ExpressionInterface` object that represents a SQL expression. | ||
* @param string|null $type The structured database type name. Defaults to `null` which means the type is not | ||
* explicitly specified. Note that in the case where a type is not specified explicitly and DBMS cannot guess it | ||
* from the context, SQL error will be raised. | ||
* @param ColumnSchemaInterface[] $columns The structured type columns that are used for value normalization and type | ||
* casting. | ||
* | ||
* @psalm-param array<string, ColumnSchemaInterface> $columns | ||
*/ | ||
public function __construct( | ||
private array|object $value, | ||
private string|null $type = null, | ||
private array $columns = [], | ||
) { | ||
} | ||
|
||
/** | ||
* The structured type name. | ||
* | ||
* Defaults to `null` which means the type is not explicitly specified. | ||
* | ||
* Note that in the case where a type is not specified explicitly and DBMS cannot guess it from the context, | ||
* SQL error will be raised. | ||
*/ | ||
public function getType(): string|null | ||
{ | ||
return $this->type; | ||
} | ||
|
||
/** | ||
* The structured type columns that are used for value normalization and type casting. | ||
* | ||
* @return ColumnSchemaInterface[] | ||
*/ | ||
public function getColumns(): array | ||
{ | ||
return $this->columns; | ||
} | ||
|
||
/** | ||
* The content of the structured type. It can be represented as | ||
* - an associative `array` of column names and values; | ||
* - an indexed `array` of column values in the order of structured type columns; | ||
* - an `iterable` object that can be converted to an `array` using `iterator_to_array()`; | ||
* - an `object` that can be converted to an `array` using `get_object_vars()`; | ||
* - an `ExpressionInterface` object that represents a SQL expression. | ||
*/ | ||
public function getValue(): array|object | ||
{ | ||
return $this->value; | ||
} | ||
|
||
/** | ||
* Returns the normalized value of the structured type, where: | ||
* - values sorted according to the order of structured type columns; | ||
* - indexed keys are replaced with column names; | ||
* - missing elements are filled in with default values; | ||
* - excessive elements are removed. | ||
* | ||
* If the structured type columns are not specified or the value is an `ExpressionInterface` object, | ||
* it will be returned as is. | ||
*/ | ||
public function getNormalizedValue(): array|object | ||
{ | ||
$value = $this->value; | ||
|
||
if (empty($this->columns) || $value instanceof ExpressionInterface) { | ||
return $value; | ||
} | ||
|
||
if (is_object($value)) { | ||
$value = $value instanceof Traversable | ||
? iterator_to_array($value) | ||
: get_object_vars($value); | ||
} | ||
|
||
$normalized = []; | ||
$columnsNames = array_keys($this->columns); | ||
|
||
foreach ($columnsNames as $i => $columnsName) { | ||
$normalized[$columnsName] = match (true) { | ||
array_key_exists($columnsName, $value) => $value[$columnsName], | ||
array_key_exists($i, $value) => $value[$i], | ||
default => $this->columns[$columnsName]->getDefaultValue(), | ||
}; | ||
} | ||
|
||
return $normalized; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,175 @@ | ||
<?php | ||
|
||
declare(strict_types=1); | ||
|
||
namespace Yiisoft\Db\Schema\Column; | ||
|
||
use Traversable; | ||
use Yiisoft\Db\Constant\ColumnType; | ||
use Yiisoft\Db\Constant\PhpType; | ||
use Yiisoft\Db\Exception\NotSupportedException; | ||
use Yiisoft\Db\Expression\ArrayExpression; | ||
use Yiisoft\Db\Expression\ExpressionInterface; | ||
use Yiisoft\Db\Syntax\ParserToArrayInterface; | ||
|
||
use function array_map; | ||
use function array_walk_recursive; | ||
use function is_array; | ||
use function is_iterable; | ||
use function is_string; | ||
use function iterator_to_array; | ||
|
||
class ArrayColumnSchema extends AbstractColumnSchema | ||
{ | ||
/** | ||
* @var ColumnSchemaInterface|null The column of an array item. | ||
*/ | ||
private ColumnSchemaInterface|null $column = null; | ||
|
||
/** | ||
* @var int The dimension of array, must be greater than 0. | ||
*/ | ||
private int $dimension = 1; | ||
|
||
/** | ||
* Returns the parser for the column value. | ||
*/ | ||
protected function getParser(): ParserToArrayInterface | ||
{ | ||
throw new NotSupportedException(__METHOD__ . '() is not supported. Use concrete DBMS implementation.'); | ||
} | ||
|
||
/** | ||
* @psalm-param ColumnType::* $type | ||
*/ | ||
public function __construct( | ||
string $type = ColumnType::ARRAY, | ||
) { | ||
parent::__construct($type); | ||
} | ||
|
||
/** | ||
* Set column of an array item. | ||
*/ | ||
public function column(ColumnSchemaInterface|null $column): static | ||
{ | ||
$this->column = $column; | ||
return $this; | ||
} | ||
|
||
/** | ||
* @return ColumnSchemaInterface the column of an array item. | ||
*/ | ||
public function getColumn(): ColumnSchemaInterface | ||
{ | ||
if ($this->column === null) { | ||
$this->column = new StringColumnSchema(); | ||
$this->column->dbType($this->getDbType()); | ||
$this->column->enumValues($this->getEnumValues()); | ||
$this->column->scale($this->getScale()); | ||
$this->column->size($this->getSize()); | ||
} | ||
|
||
return $this->column; | ||
} | ||
|
||
/** | ||
* Set dimension of an array, must be greater than 0. | ||
*/ | ||
public function dimension(int $dimension): static | ||
{ | ||
$this->dimension = $dimension; | ||
return $this; | ||
} | ||
|
||
/** | ||
* @return int the dimension of the array. | ||
*/ | ||
public function getDimension(): int | ||
{ | ||
return $this->dimension; | ||
} | ||
|
||
public function getPhpType(): string | ||
{ | ||
return PhpType::ARRAY; | ||
} | ||
|
||
public function dbTypecast(mixed $value): ExpressionInterface|null | ||
{ | ||
if ($value === null || $value instanceof ExpressionInterface) { | ||
return $value; | ||
} | ||
|
||
if ($this->dimension === 1 && is_array($value)) { | ||
$value = array_map($this->getColumn()->dbTypecast(...), $value); | ||
} else { | ||
$value = $this->dbTypecastArray($value, $this->dimension); | ||
} | ||
|
||
return new ArrayExpression($value, $this->getDbType() ?? $this->getColumn()->getDbType(), $this->dimension); | ||
} | ||
|
||
public function phpTypecast(mixed $value): array|null | ||
{ | ||
if (is_string($value)) { | ||
$value = $this->getParser()->parse($value); | ||
} | ||
|
||
if (!is_array($value)) { | ||
return null; | ||
} | ||
|
||
$column = $this->getColumn(); | ||
|
||
if ($column->getType() === ColumnType::STRING) { | ||
return $value; | ||
} | ||
|
||
if ($this->dimension === 1 && $column->getType() !== ColumnType::JSON) { | ||
return array_map($column->phpTypecast(...), $value); | ||
} | ||
|
||
array_walk_recursive($value, function (string|null &$val) use ($column): void { | ||
$val = $column->phpTypecast($val); | ||
}); | ||
|
||
return $value; | ||
} | ||
|
||
/** | ||
* Recursively converts array values for use in a db query. | ||
* | ||
* @param mixed $value The array or iterable object. | ||
* @param int $dimension The array dimension. Should be more than 0. | ||
* | ||
* @return array|null Converted values. | ||
*/ | ||
protected function dbTypecastArray(mixed $value, int $dimension): array|null | ||
{ | ||
if ($value === null) { | ||
return null; | ||
} | ||
|
||
if (!is_iterable($value)) { | ||
return []; | ||
} | ||
|
||
if ($dimension <= 1) { | ||
return array_map( | ||
$this->getColumn()->dbTypecast(...), | ||
$value instanceof Traversable | ||
? iterator_to_array($value, false) | ||
: $value | ||
); | ||
} | ||
|
||
$items = []; | ||
|
||
foreach ($value as $val) { | ||
$items[] = $this->dbTypecastArray($val, $dimension - 1); | ||
} | ||
|
||
return $items; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.