Repository navigation
Add range and multirange columns support #307
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
41 commits
Select commit
Hold shift + click to select a range
5b96d65
Range and MultiRange parser
Gerych1984 96868f9
Merge remote-tracking branch 'origin/master' into fork/Gerych1984/ran…
vjik 72f9dd3
Merge remote-tracking branch 'origin/master' into fork/Gerych1984/ran…
vjik a979f65
range
vjik f4be518
avoid extra check in `phpTypecast()`
vjik 5ecd609
Apply PHP CS Fixer and Rector changes (CI)
vjik 17306ec
improve
vjik 837f139
Merge remote-tracking branch 'Gerych1984/range-parser' into fork/Gery…
vjik 39ccc9f
Don't set extra data for columns
vjik b3f8ff5
Introduce `RangeBoundColumnFactory`
vjik 60da246
Add default values for `*RangeValue`
vjik 229723a
Separated classes for each range type
vjik 5ca6de2
fix
vjik 4d7f787
replace `?float` to `int|float|null`
vjik a63012e
Update src/Column/AbstractRangeColumn.php
vjik 1c7d231
Improve + Tests
vjik b421cb4
Apply PHP CS Fixer and Rector changes (CI)
vjik 5b08a04
fix namespaces
vjik 67d7930
improve tests
vjik 2ef31f3
multirange
vjik 77bf0e8
Apply PHP CS Fixer and Rector changes (CI)
vjik 63b5d07
improve
vjik 9c2ce00
improve
vjik 4155e8c
improve
vjik 0ec9cea
improve
vjik 92b7540
Apply PHP CS Fixer and Rector changes (CI)
vjik e118367
fix
vjik eb5647e
Merge remote-tracking branch 'Gerych1984/range-parser' into range-parser
vjik 01ea19d
improve
vjik d25a435
improve tests
vjik 925d6a2
Apply PHP CS Fixer and Rector changes (CI)
vjik 8cc3a4d
Fix multirange regexp + More tests
vjik 3f6cf28
Update src/Column/Int8MultiRangeColumn.php
vjik f18ff80
Update src/Column/DateMultiRangeColumn.php
vjik 0f5f575
Update src/Column/Int4MultiRangeColumn.php
vjik 2573e2d
Update src/Column/NumMultiRangeColumn.php
vjik edfec08
Update src/Column/TsMultiRangeColumn.php
vjik 9cc045a
Update src/Column/TsTzMultiRangeColumn.php
vjik 634b603
fix
vjik 4951364
improve
vjik 4d60541
Update src/Column/AbstractMultiRangeColumn.php
vjik File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or 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 hidden or 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,74 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| namespace Yiisoft\Db\Pgsql\Column; | ||
|
|
||
| use Yiisoft\Db\Exception\NotSupportedException; | ||
| use Yiisoft\Db\Expression\ExpressionInterface; | ||
| use Yiisoft\Db\Pgsql\Expression\MultiRangeValue; | ||
| use Yiisoft\Db\Schema\Column\AbstractColumn; | ||
| use Yiisoft\Db\Schema\Column\ColumnInterface; | ||
|
|
||
| use function gettype; | ||
| use function is_array; | ||
| use function is_string; | ||
|
|
||
| abstract class AbstractMultiRangeColumn extends AbstractColumn | ||
| { | ||
| public function dbTypecast(mixed $value): mixed | ||
| { | ||
| if ($value === null | ||
| || is_string($value) | ||
| || $value instanceof ExpressionInterface) { | ||
| return $value; | ||
| } | ||
|
|
||
| if (!is_array($value)) { | ||
| $this->throwWrongTypeException(gettype($value)); | ||
| } | ||
|
|
||
| $rangeColumn = $this->getRangeColumn(); | ||
| $ranges = []; | ||
| foreach ($value as $rawRange) { | ||
| $range = $rangeColumn->dbTypecast($rawRange); | ||
| if (!is_string($range) | ||
| && !$range instanceof ExpressionInterface) { | ||
| $this->throwWrongTypeException(gettype($rawRange)); | ||
| } | ||
| $ranges[] = $range; | ||
| } | ||
|
|
||
| return new MultiRangeValue(...$ranges); | ||
| } | ||
|
|
||
| public function phpTypecast(mixed $value): mixed | ||
| { | ||
| /** | ||
| * @var string|null $value We expect `phpTypecast()` to only receive the value that the database returns, which | ||
| * in this case is `null` or a `string`. To avoid extra checks. | ||
| */ | ||
|
|
||
| if ($value === null) { | ||
| return null; | ||
| } | ||
|
|
||
| if ($value === '{}') { | ||
| return []; | ||
| } | ||
|
|
||
| if (preg_match('/^{([\[\(][^,]*,[^\)\]]*[\)\]])(?:,(?1))*}$/', $value) !== 1) { | ||
| throw new NotSupportedException('Unsupported multirange format'); | ||
| } | ||
|
|
||
| preg_match_all('/[\[\(][^,]*,[^\)\]]*[\)\]]/', $value, $matches); | ||
|
|
||
| $rangeColumn = $this->getRangeColumn(); | ||
| return array_map( | ||
| $rangeColumn->phpTypecast(...), | ||
| $matches[0], | ||
| ); | ||
| } | ||
|
|
||
| abstract protected function getRangeColumn(): ColumnInterface; | ||
| } |
This file contains hidden or 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,101 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| namespace Yiisoft\Db\Pgsql\Column; | ||
|
|
||
| use InvalidArgumentException; | ||
| use Yiisoft\Db\Exception\NotSupportedException; | ||
| use Yiisoft\Db\Expression\ExpressionInterface; | ||
| use Yiisoft\Db\Schema\Column\AbstractColumn; | ||
| use Yiisoft\Db\Schema\Column\ColumnInterface; | ||
|
|
||
| use function gettype; | ||
| use function is_array; | ||
| use function is_string; | ||
| use function sprintf; | ||
|
|
||
| abstract class AbstractRangeColumn extends AbstractColumn | ||
| { | ||
| public function dbTypecast(mixed $value): mixed | ||
| { | ||
| if ($value === null | ||
| || is_string($value) | ||
| || $value instanceof ExpressionInterface) { | ||
| return $value; | ||
| } | ||
|
|
||
| if (!is_array($value)) { | ||
| $this->throwWrongTypeException(gettype($value)); | ||
| } | ||
|
|
||
| if (array_keys($value) !== [0, 1]) { | ||
| throw new InvalidArgumentException( | ||
| 'Value of range should be an array with two elements: lower and upper bounds.', | ||
| ); | ||
| } | ||
|
|
||
| [$lower, $upper] = $value; | ||
| $boundColumn = $this->getBoundColumn(); | ||
|
|
||
| return '[' | ||
| . $this->prepareBoundValue($boundColumn->dbTypecast($lower)) | ||
| . ',' | ||
| . $this->prepareBoundValue($boundColumn->dbTypecast($upper)) | ||
| . ']'; | ||
| } | ||
|
|
||
| public function phpTypecast(mixed $value): mixed | ||
| { | ||
| /** | ||
| * @var string|null $value We expect `phpTypecast()` to only receive the value that the database returns, which | ||
| * in this case is `null` or a `string`. To avoid extra checks. | ||
| */ | ||
|
|
||
| if ($value === null || $value === 'empty') { | ||
| return null; | ||
| } | ||
|
|
||
| if (!preg_match('/^(?P<open>\[|\()(?P<lower>[^,]*),(?P<upper>[^\)\]]*)(?P<close>\)|\])$/', $value, $matches)) { | ||
| throw new NotSupportedException('Unsupported range format.'); | ||
| } | ||
|
|
||
| return $this->createRangeValue( | ||
| $matches['lower'] === '' ? null : trim($matches['lower'], '"'), | ||
| $matches['upper'] === '' ? null : trim($matches['upper'], '"'), | ||
| $matches['open'] === '[', | ||
| $matches['close'] === ']', | ||
| ); | ||
| } | ||
|
|
||
| abstract protected function getBoundColumn(): ColumnInterface; | ||
|
|
||
| /** | ||
| * @throws NotSupportedException | ||
| */ | ||
| abstract protected function createRangeValue( | ||
| ?string $lower, | ||
| ?string $upper, | ||
| bool $includeLower, | ||
| bool $includeUpper, | ||
| ): ExpressionInterface; | ||
|
|
||
| /** | ||
| * @throws NotSupportedException | ||
| */ | ||
| private function prepareBoundValue(mixed $value): string | ||
| { | ||
| return match (gettype($value)) { | ||
|
Check warning on line 88 in src/Column/AbstractRangeColumn.php
|
||
| 'NULL' => '', | ||
| 'double', | ||
| 'integer' => (string) $value, | ||
| 'string' => $value, | ||
| default => throw new NotSupportedException( | ||
| sprintf( | ||
| 'Range bound supports only string, int, float and null values. Got %s.', | ||
| get_debug_type($value), | ||
| ), | ||
| ), | ||
| }; | ||
| } | ||
| } | ||
This file contains hidden or 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 hidden or 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 |
|---|---|---|
|
|
@@ -6,6 +6,7 @@ | |
|
|
||
| use Yiisoft\Db\Constant\ColumnType; | ||
| use Yiisoft\Db\Expression\Expression; | ||
| use Yiisoft\Db\Pgsql\Constant\PgsqlColumnType; | ||
| use Yiisoft\Db\Schema\Column\AbstractColumnFactory; | ||
| use Yiisoft\Db\Schema\Column\ColumnInterface; | ||
|
|
||
|
|
@@ -23,8 +24,7 @@ | |
| * | ||
| * @link https://www.postgresql.org/docs/current/datatype.html#DATATYPE-TABLE | ||
| * | ||
| * @var string[] | ||
| * @psalm-var array<string, ColumnType::*> | ||
| * @inheritdoc | ||
| */ | ||
| protected const TYPE_MAP = [ | ||
| 'bool' => ColumnType::BOOLEAN, | ||
|
|
@@ -91,6 +91,18 @@ | |
| 'xml' => ColumnType::STRING, | ||
| 'json' => ColumnType::JSON, | ||
| 'jsonb' => ColumnType::JSON, | ||
| 'int4range' => PgsqlColumnType::INT4RANGE, | ||
| 'int8range' => PgsqlColumnType::INT8RANGE, | ||
| 'numrange' => PgsqlColumnType::NUMRANGE, | ||
| 'tsrange' => PgsqlColumnType::TSRANGE, | ||
| 'tstzrange' => PgsqlColumnType::TSTZRANGE, | ||
| 'daterange' => PgsqlColumnType::DATERANGE, | ||
| 'int4multirange' => PgsqlColumnType::INT4MULTIRANGE, | ||
| 'int8multirange' => PgsqlColumnType::INT8MULTIRANGE, | ||
| 'nummultirange' => PgsqlColumnType::NUMMULTIRANGE, | ||
| 'tsmultirange' => PgsqlColumnType::TSMULTIRANGE, | ||
| 'tstzmultirange' => PgsqlColumnType::TSTZMULTIRANGE, | ||
| 'datemultirange' => PgsqlColumnType::DATEMULTIRANGE, | ||
| ]; | ||
|
|
||
| public function fromType(string $type, array $info = []): ColumnInterface | ||
|
|
@@ -129,17 +141,48 @@ | |
| ColumnType::BINARY => BinaryColumn::class, | ||
| ColumnType::ARRAY => ArrayColumn::class, | ||
| ColumnType::STRUCTURED => StructuredColumn::class, | ||
| PgsqlColumnType::INT4RANGE => Int4RangeColumn::class, | ||
| PgsqlColumnType::INT8RANGE => Int8RangeColumn::class, | ||
| PgsqlColumnType::NUMRANGE => NumRangeColumn::class, | ||
| PgsqlColumnType::TSRANGE => TsRangeColumn::class, | ||
| PgsqlColumnType::TSTZRANGE => TsTzRangeColumn::class, | ||
| PgsqlColumnType::DATERANGE => DateRangeColumn::class, | ||
| PgsqlColumnType::INT4MULTIRANGE => Int4MultiRangeColumn::class, | ||
| PgsqlColumnType::INT8MULTIRANGE => Int8MultiRangeColumn::class, | ||
| PgsqlColumnType::NUMMULTIRANGE => NumMultiRangeColumn::class, | ||
| PgsqlColumnType::TSMULTIRANGE => TsMultiRangeColumn::class, | ||
| PgsqlColumnType::TSTZMULTIRANGE => TsTzMultiRangeColumn::class, | ||
| PgsqlColumnType::DATEMULTIRANGE => DateMultiRangeColumn::class, | ||
| default => parent::getColumnClass($type, $info), | ||
| }; | ||
| } | ||
|
|
||
| protected function isType(string $type): bool | ||
| { | ||
| return match ($type) { | ||
| PgsqlColumnType::INT4RANGE, | ||
| PgsqlColumnType::INT8RANGE, | ||
| PgsqlColumnType::NUMRANGE, | ||
| PgsqlColumnType::TSRANGE, | ||
| PgsqlColumnType::TSTZRANGE, | ||
| PgsqlColumnType::DATERANGE, | ||
| PgsqlColumnType::INT4MULTIRANGE, | ||
| PgsqlColumnType::INT8MULTIRANGE, | ||
| PgsqlColumnType::NUMMULTIRANGE, | ||
| PgsqlColumnType::TSMULTIRANGE, | ||
| PgsqlColumnType::TSTZMULTIRANGE, | ||
| PgsqlColumnType::DATEMULTIRANGE => true, | ||
| default => parent::isType($type), | ||
| }; | ||
| } | ||
|
|
||
| protected function normalizeNotNullDefaultValue(string $defaultValue, ColumnInterface $column): mixed | ||
| { | ||
| /** @var string $value */ | ||
| $value = preg_replace("/::[^:']+$/", '$1', $defaultValue); | ||
|
|
||
| if (str_starts_with($value, "B'") && $value[-1] === "'") { | ||
| return $column->phpTypecast(substr($value, 2, -1)); | ||
|
Check warning on line 185 in src/Column/ColumnFactory.php
|
||
| } | ||
|
|
||
| $value = parent::normalizeNotNullDefaultValue($value, $column); | ||
|
|
||
This file contains hidden or 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,18 @@ | ||
| <?php | ||
|
|
||
| declare(strict_types=1); | ||
|
|
||
| namespace Yiisoft\Db\Pgsql\Column; | ||
|
|
||
| use Yiisoft\Db\Pgsql\Constant\PgsqlColumnType; | ||
| use Yiisoft\Db\Schema\Column\ColumnInterface; | ||
|
|
||
| final class DateMultiRangeColumn extends AbstractMultiRangeColumn | ||
| { | ||
| protected const DEFAULT_TYPE = PgsqlColumnType::DATEMULTIRANGE; | ||
|
|
||
| protected function getRangeColumn(): ColumnInterface | ||
| { | ||
| return new DateRangeColumn(); | ||
| } | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.