diff --git a/.github/workflows/db-mariadb.yml b/.github/workflows/db-mariadb.yml index 134faef92..14495ce77 100644 --- a/.github/workflows/db-mariadb.yml +++ b/.github/workflows/db-mariadb.yml @@ -29,6 +29,7 @@ jobs: env: COMPOSER_ROOT_VERSION: dev-master EXTENSIONS: pdo, pdo_mysql + YII_MYSQL_TYPE: 'mariadb' runs-on: ubuntu-latest diff --git a/CHANGELOG.md b/CHANGELOG.md index 9dcc03227..bb1cfa7d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -177,6 +177,8 @@ - Bug #1103: Fix view names' cache refreshing after "drop table" command execution (@vjik) - Chg #1103: Remove `AbstractCommand::refreshTableSchema()` method (@vjik) - Chg #1106: Remove parameters from `PdoConnectionInterface::getActivePdo()` method (@vjik) +- Chg #1108: Refactor `ColumnDefinitionParser` and rename it to `AbstractColumnDefinitionParser`, + add `ColumnDefinitionParserInterface` (@vjik) ## 1.3.0 March 21, 2024 diff --git a/UPGRADE.md b/UPGRADE.md index 9a48f2862..45d28e21a 100644 --- a/UPGRADE.md +++ b/UPGRADE.md @@ -97,6 +97,12 @@ and the following changes were made: - added `DEFAULT_TYPE` constant to `AbstractColumn` class; - added method chaining. +### `ColumnDefinitionParser` changes + +- Rename to `AbstractColumnDefinitionParser`; +- Make class abstract and add abstract method `parseTypeParams()`; +- Rename method `sizeInfo()` to `parseSizeInfo()`. + ### Changes in constraint classes - Remove `Constraint` class; @@ -293,3 +299,4 @@ Each table column has its own class in the `Yiisoft\Db\Schema\Column` namespace - Change `QueryPartsInterface::withQueries()` parameter to variadic with type `WithQuery`; - In `DQLQueryBuilderInterface::buildWithQueries()` method change first parameter type form `array[]` to `WithQuery[]`; - Remove `AbstractCommand::refreshTableSchema()` method; +- Make method `AbstractColumnFactory::columnDefinitionParser()` abstract; diff --git a/src/Schema/Column/AbstractColumnFactory.php b/src/Schema/Column/AbstractColumnFactory.php index 5f2c217d3..73c07adb1 100644 --- a/src/Schema/Column/AbstractColumnFactory.php +++ b/src/Schema/Column/AbstractColumnFactory.php @@ -8,7 +8,7 @@ use Yiisoft\Db\Constant\ColumnType; use Yiisoft\Db\Constant\PseudoType; use Yiisoft\Db\Expression\Expression; -use Yiisoft\Db\Syntax\ColumnDefinitionParser; +use Yiisoft\Db\Syntax\ColumnDefinitionParserInterface; use function array_diff_key; use function array_key_exists; @@ -221,10 +221,7 @@ public function fromType(string $type, array $info = []): ColumnInterface /** * Returns the column definition parser. */ - protected function columnDefinitionParser(): ColumnDefinitionParser - { - return new ColumnDefinitionParser(); - } + abstract protected function columnDefinitionParser(): ColumnDefinitionParserInterface; /** * @psalm-param ColumnInfo $info diff --git a/src/Syntax/ColumnDefinitionParser.php b/src/Syntax/AbstractColumnDefinitionParser.php similarity index 75% rename from src/Syntax/ColumnDefinitionParser.php rename to src/Syntax/AbstractColumnDefinitionParser.php index 0fcb775be..4cea8722a 100644 --- a/src/Syntax/ColumnDefinitionParser.php +++ b/src/Syntax/AbstractColumnDefinitionParser.php @@ -6,7 +6,6 @@ use function explode; use function preg_match; -use function preg_match_all; use function preg_replace; use function str_replace; use function strlen; @@ -29,7 +28,7 @@ * unsigned?: bool * } */ -class ColumnDefinitionParser +abstract class AbstractColumnDefinitionParser implements ColumnDefinitionParserInterface { /** * Parses column definition string. @@ -56,48 +55,60 @@ class ColumnDefinitionParser */ public function parse(string $definition): array { - preg_match('/^(\w*)(?:\(([^)]+)\))?(\[[\d\[\]]*\])?\s*/', $definition, $matches); + [$type, $typeParams, $dimension, $extraInfo] = $this->parseDefinition($definition); - $type = strtolower($matches[1]); + $type = strtolower($type); $info = ['type' => $type]; - if (isset($matches[2]) && $matches[2] !== '') { - if ($type === 'enum') { - $info += $this->enumInfo($matches[2]); - } else { - $info += $this->sizeInfo($matches[2]); - } + if ($typeParams !== '' && $typeParams !== null) { + $info += $this->parseTypeParams($type, $typeParams); } - if (isset($matches[3])) { + if (isset($dimension)) { /** @psalm-var positive-int */ - $info['dimension'] = substr_count($matches[3], '['); + $info['dimension'] = substr_count($dimension, '['); } - $extra = substr($definition, strlen($matches[0])); + if ($extraInfo !== '' && $extraInfo !== null) { + $info += $this->extraInfo($extraInfo); + } - return $info + $this->extraInfo($extra); + return $info; } /** - * @psalm-return array{enumValues: list} + * Parse the column definition into its components: + * - type + * - type parameters + * - dimension + * - extra information + * + * @psalm-return list{string, string|null, string|null, string|null} */ - protected function enumInfo(string $values): array + protected function parseDefinition(string $definition): array { - preg_match_all("/'([^']*)'/", $values, $matches); + preg_match('/^(\w*)(?:\(([^)]+)\))?(\[[\d\[\]]*\])?\s*/', $definition, $matches); - return ['enumValues' => $matches[1]]; + return [ + $matches[1], + $matches[2] ?? null, + $matches[3] ?? null, + substr($definition, strlen($matches[0])), + ]; } /** + * @psalm-param non-empty-string $params + * @psalm-return array{enumValues?: list, size?: int, scale?: int} + */ + abstract protected function parseTypeParams(string $type, string $params): array; + + /** + * @psalm-param non-empty-string $extra * @psalm-return ExtraInfo */ protected function extraInfo(string $extra): array { - if (empty($extra)) { - return []; - } - $info = []; $bracketsPattern = '(\(((?>[^()]+)|(?-2))*\))'; $defaultPattern = "/\\s*\\bDEFAULT\\s+('(?:[^']|'')*'|\"(?:[^\"]|\"\")*\"|[^(\\s]*$bracketsPattern?\\S*)/i"; @@ -169,9 +180,9 @@ protected function parseBoolValue(string $extra, string $pattern, string $name, /** * @psalm-return array{size: int, scale?: int} */ - protected function sizeInfo(string $size): array + protected function parseSizeInfo(string $params): array { - $values = explode(',', $size); + $values = explode(',', $params); $info = [ 'size' => (int) $values[0], diff --git a/src/Syntax/ColumnDefinitionParserInterface.php b/src/Syntax/ColumnDefinitionParserInterface.php new file mode 100644 index 000000000..f9040acdb --- /dev/null +++ b/src/Syntax/ColumnDefinitionParserInterface.php @@ -0,0 +1,36 @@ +, + * extra?: string, + * notNull?: bool, + * scale?: int, + * size?: int, + * type: lowercase-string, + * unique?: bool, + * unsigned?: bool, + * } + */ + public function parse(string $definition): array; +} diff --git a/tests/Common/CommonColumnDefinitionParserTest.php b/tests/Common/CommonColumnDefinitionParserTest.php index 89b2b9c7b..93c784025 100644 --- a/tests/Common/CommonColumnDefinitionParserTest.php +++ b/tests/Common/CommonColumnDefinitionParserTest.php @@ -6,12 +6,9 @@ use PHPUnit\Framework\Attributes\DataProviderExternal; use PHPUnit\Framework\TestCase; -use Yiisoft\Db\Syntax\ColumnDefinitionParser; +use Yiisoft\Db\Syntax\ColumnDefinitionParserInterface; use Yiisoft\Db\Tests\Provider\ColumnDefinitionParserProvider; -/** - * @group db - */ abstract class CommonColumnDefinitionParserTest extends TestCase { #[DataProviderExternal(ColumnDefinitionParserProvider::class, 'parse')] @@ -22,8 +19,5 @@ public function testParse(string $definition, array $expected): void $this->assertSame($expected, $parser->parse($definition)); } - protected function createColumnDefinitionParser(): ColumnDefinitionParser - { - return new ColumnDefinitionParser(); - } + abstract protected function createColumnDefinitionParser(): ColumnDefinitionParserInterface; } diff --git a/tests/Common/CommonColumnTest.php b/tests/Common/CommonColumnTest.php index f69e087eb..3b74b2393 100644 --- a/tests/Common/CommonColumnTest.php +++ b/tests/Common/CommonColumnTest.php @@ -215,7 +215,10 @@ public function testDateTimeColumn(float|int|string $value, array $expected): vo $values = array_fill_keys(array_keys($expected), $value); - $expected = array_map(static fn(string $value) => new DateTimeImmutable($value, new DateTimeZone('UTC')), $expected); + $expected = array_map( + static fn(string $value) => new DateTimeImmutable($value, new DateTimeZone('UTC')), + $expected, + ); $command->insert(static::DATETIME_COLUMN_TABLE, $values)->execute(); diff --git a/tests/Db/Schema/Column/ColumnFactoryTest.php b/tests/Db/Schema/Column/ColumnFactoryTest.php index c3cb57e84..2b19f6cc6 100644 --- a/tests/Db/Schema/Column/ColumnFactoryTest.php +++ b/tests/Db/Schema/Column/ColumnFactoryTest.php @@ -4,13 +4,17 @@ namespace Yiisoft\Db\Tests\Db\Schema\Column; +use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\DataProviderExternal; use PHPUnit\Framework\TestCase; use Yiisoft\Db\Constant\ColumnType; use Yiisoft\Db\Schema\Column\AbstractArrayColumn; +use Yiisoft\Db\Schema\Column\ArrayColumn; use Yiisoft\Db\Schema\Column\ArrayLazyColumn; +use Yiisoft\Db\Schema\Column\BigIntColumn; use Yiisoft\Db\Schema\Column\ColumnInterface; use Yiisoft\Db\Schema\Column\DateTimeColumn; +use Yiisoft\Db\Schema\Column\DoubleColumn; use Yiisoft\Db\Schema\Column\IntegerColumn; use Yiisoft\Db\Schema\Column\JsonLazyColumn; use Yiisoft\Db\Schema\Column\StringColumn; @@ -76,7 +80,22 @@ public function testFromDbType(string $dbType, string $expectedType, string $exp $this->assertSame($dbType, $column->getDbType()); } - #[DataProviderExternal(ColumnFactoryProvider::class, 'definitions')] + public static function dataFromDefinition(): array + { + return [ + // definition, expected type, expected instance of, expected column method results + '' => ['', new StringColumn(dbType: '')], + 'text' => ['text', new StringColumn(ColumnType::TEXT, dbType: 'text')], + 'text NOT NULL' => ['text NOT NULL', new StringColumn(ColumnType::TEXT, dbType: 'text', notNull: true)], + 'char(1)' => ['char(1)', new StringColumn(ColumnType::CHAR, dbType: 'char')], + 'decimal(10,2)' => ['decimal(10,2)', new DoubleColumn(ColumnType::DECIMAL, dbType: 'decimal')], + 'bigint UNSIGNED' => ['bigint UNSIGNED', new BigIntColumn(dbType: 'bigint', unsigned: true)], + 'integer[]' => ['integer[]', new ArrayColumn(dbType: 'integer', column: new IntegerColumn(dbType: 'integer'))], + 'string(126)[][]' => ['string(126)[][]', new ArrayColumn(dimension: 2, column: new StringColumn())], + ]; + } + + #[DataProvider('dataFromDefinition')] public function testFromDefinition(string $definition, ColumnInterface $expected): void { $columnFactory = new StubColumnFactory(); @@ -115,7 +134,6 @@ public function testFromDefinitionWithExtra(): void $this->assertInstanceOf(StringColumn::class, $column); $this->assertSame('char', $column->getType()); - $this->assertSame(1, $column->getSize()); $this->assertSame('INVISIBLE COLLATE utf8mb4', $column->getExtra()); } diff --git a/tests/Provider/ColumnDefinitionParserProvider.php b/tests/Provider/ColumnDefinitionParserProvider.php index 91ce5a7fe..6fe563200 100644 --- a/tests/Provider/ColumnDefinitionParserProvider.php +++ b/tests/Provider/ColumnDefinitionParserProvider.php @@ -28,8 +28,6 @@ public static function parse(): array ['int DEFAULT (1 + 2)', ['type' => 'int', 'defaultValueRaw' => '(1 + 2)']], ["int COMMENT '''Quoted'' comment'", ['type' => 'int', 'comment' => "'Quoted' comment"]], ['int CHECK (value > (1 + 5))', ['type' => 'int', 'check' => 'value > (1 + 5)']], - ["enum('a','b','c')", ['type' => 'enum', 'enumValues' => ['a', 'b', 'c']]], - ["enum('a','b','c') NOT NULL", ['type' => 'enum', 'enumValues' => ['a', 'b', 'c'], 'notNull' => true]], ['int[]', ['type' => 'int', 'dimension' => 1]], ['string(126)[][]', ['type' => 'string', 'size' => 126, 'dimension' => 2]], ]; diff --git a/tests/Support/Stub/StubColumnDefinitionParser.php b/tests/Support/Stub/StubColumnDefinitionParser.php new file mode 100644 index 000000000..64ccfa817 --- /dev/null +++ b/tests/Support/Stub/StubColumnDefinitionParser.php @@ -0,0 +1,15 @@ + $this->isType($dbType), }; } + + protected function columnDefinitionParser(): ColumnDefinitionParserInterface + { + return new StubColumnDefinitionParser(); + } }