Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/workflows/db-mariadb.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ jobs:
env:
COMPOSER_ROOT_VERSION: dev-master
EXTENSIONS: pdo, pdo_mysql
YII_MYSQL_TYPE: 'mariadb'

runs-on: ubuntu-latest

Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
7 changes: 7 additions & 0 deletions UPGRADE.md
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
7 changes: 2 additions & 5 deletions src/Schema/Column/AbstractColumnFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -29,7 +28,7 @@
* unsigned?: bool
* }
*/
class ColumnDefinitionParser
abstract class AbstractColumnDefinitionParser implements ColumnDefinitionParserInterface
{
/**
* Parses column definition string.
Expand All @@ -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<string>}
* 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<string>, 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";
Expand Down Expand Up @@ -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],
Expand Down
36 changes: 36 additions & 0 deletions src/Syntax/ColumnDefinitionParserInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Db\Syntax;

/**
* Parses column definition string. For example, `string(255)` or `int unsigned`.
*/
interface ColumnDefinitionParserInterface
{
/**
* Parses column definition string.
*
* @param string $definition The column definition string. For example, `string(255)` or `int unsigned`.
*
* @return array The column information.
*
* @psalm-return array{
* check?: string,
* collation?: string,
* comment?: string,
* defaultValueRaw?: string,
* dimension?: positive-int,
* enumValues?: list<string>,
* extra?: string,
* notNull?: bool,
* scale?: int,
* size?: int,
* type: lowercase-string,
* unique?: bool,
* unsigned?: bool,
* }
*/
public function parse(string $definition): array;
}
10 changes: 2 additions & 8 deletions tests/Common/CommonColumnDefinitionParserTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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')]
Expand All @@ -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;
}
5 changes: 4 additions & 1 deletion tests/Common/CommonColumnTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down
22 changes: 20 additions & 2 deletions tests/Db/Schema/Column/ColumnFactoryTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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());
}

Expand Down
2 changes: 0 additions & 2 deletions tests/Provider/ColumnDefinitionParserProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -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]],
];
Expand Down
15 changes: 15 additions & 0 deletions tests/Support/Stub/StubColumnDefinitionParser.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Db\Tests\Support\Stub;

use Yiisoft\Db\Syntax\AbstractColumnDefinitionParser;

final class StubColumnDefinitionParser extends AbstractColumnDefinitionParser
{
protected function parseTypeParams(string $type, string $params): array
{
return [];
Comment on lines +11 to +13
Copy link

Copilot AI Nov 25, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The StubColumnDefinitionParser returns an empty array from parseTypeParams(), which means it doesn't parse type parameters like size and scale. However, test data in ColumnFactoryTest.php includes definitions with parameters (e.g., 'char(1)', 'decimal(10,2)' at lines 90-91) that won't be parsed.

Consider implementing basic parameter parsing in the stub:

protected function parseTypeParams(string $type, string $params): array
{
    return $this->parseSizeInfo($params);
}

This would make the stub more realistic and allow proper testing of size/scale parsing.

Copilot uses AI. Check for mistakes.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not to use return $this->parseSizeInfo($params); as AI suggests?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It depends by type. Different types requires different parsing. This is DBMS-specific thing.

}
}
6 changes: 6 additions & 0 deletions tests/Support/Stub/StubColumnFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

use Yiisoft\Db\Constant\ColumnType;
use Yiisoft\Db\Schema\Column\AbstractColumnFactory;
use Yiisoft\Db\Syntax\ColumnDefinitionParserInterface;

class StubColumnFactory extends AbstractColumnFactory
{
Expand All @@ -26,4 +27,9 @@ protected function isDbType(string $dbType): bool
default => $this->isType($dbType),
};
}

protected function columnDefinitionParser(): ColumnDefinitionParserInterface
{
return new StubColumnDefinitionParser();
}
}