Skip to content
2 changes: 1 addition & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
- Enh #806, #964: Build `Expression` instances inside `Expression::$params` when build a query using `QueryBuilder` (@Tigrov)
- Enh #766: Allow `ColumnInterface` as column type. (@Tigrov)
- Bug #828: Fix `float` type when use `AbstractCommand::getRawSql()` method (@Tigrov)
- New #752: Implement `ColumnSchemaInterface` classes according to the data type of database table columns
- New #752, #974: Implement `ColumnSchemaInterface` classes according to the data type of database table columns
for type casting performance (@Tigrov)
- Enh #829: Rename `batchInsert()` to `insertBatch()` in `DMLQueryBuilderInterface` and `CommandInterface`
and change parameters from `$table, $columns, $rows` to `$table, $rows, $columns = []` (@Tigrov)
Expand Down
4 changes: 4 additions & 0 deletions rector.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

use Rector\CodeQuality\Rector\Class_\InlineConstructorDefaultToPropertyRector;
use Rector\Config\RectorConfig;
use Rector\Php80\Rector\Ternary\GetDebugTypeRector;
use Rector\Php81\Rector\Property\ReadOnlyPropertyRector;
use Rector\Php81\Rector\FuncCall\NullToStrictStringFuncCallArgRector;
use Rector\Set\ValueObject\LevelSetList;
Expand All @@ -23,6 +24,9 @@
]);

$rectorConfig->skip([
GetDebugTypeRector::class => [
__DIR__ . '/tests/AbstractColumnTest.php',
],
ReadOnlyPropertyRector::class,
NullToStrictStringFuncCallArgRector::class,
]);
Expand Down
6 changes: 6 additions & 0 deletions src/Schema/Column/AbstractColumn.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace Yiisoft\Db\Schema\Column;

use InvalidArgumentException;
use Yiisoft\Db\Constant\ColumnType;
use Yiisoft\Db\Constant\PhpType;
use Yiisoft\Db\Constraint\ForeignKeyConstraint;
Expand Down Expand Up @@ -376,4 +377,9 @@ public function withName(string|null $name): static
$new->name = $name;
return $new;
}

protected function throwWrongTypeException(string $type): never
{
throw new InvalidArgumentException("Wrong $type value for $this->type column.");
}
}
40 changes: 29 additions & 11 deletions src/Schema/Column/BigIntColumn.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@

namespace Yiisoft\Db\Schema\Column;

use BackedEnum;
use DateTimeInterface;
use Stringable;
use Yiisoft\Db\Constant\ColumnType;
use Yiisoft\Db\Expression\ExpressionInterface;
use Yiisoft\Db\Constant\GettypeResult;
use Yiisoft\Db\Constant\PhpType;

use function gettype;
use function is_int;

use const PHP_INT_MAX;
use const PHP_INT_MIN;
Expand All @@ -23,21 +27,26 @@ class BigIntColumn extends AbstractColumn

public function dbTypecast(mixed $value): int|string|ExpressionInterface|null
{
/** @var ExpressionInterface|int|string|null */
/**
* @var ExpressionInterface|int|string|null
* @psalm-suppress MixedArgument
*/
return match (gettype($value)) {
GettypeResult::STRING => $value === '' ? null : (
$value <= PHP_INT_MAX && $value >= PHP_INT_MIN
? (int) $value
: $value
),
GettypeResult::STRING => $this->dbTypecastString($value),
GettypeResult::NULL => null,
GettypeResult::INTEGER => $value,
GettypeResult::DOUBLE => $this->dbTypecastString((string) $value),
GettypeResult::BOOLEAN => $value ? 1 : 0,
default => $value instanceof ExpressionInterface ? $value : (
($val = (string) $value) <= PHP_INT_MAX && $val >= PHP_INT_MIN
? (int) $val
: $val
),
GettypeResult::OBJECT => match (true) {
$value instanceof ExpressionInterface => $value,
$value instanceof BackedEnum => is_int($value->value)
? $value->value
: $this->dbTypecastString($value->value),
$value instanceof DateTimeInterface => $value->getTimestamp(),
$value instanceof Stringable => $this->dbTypecastString((string) $value),
default => $this->throwWrongTypeException($value::class),
},
default => $this->throwWrongTypeException(gettype($value)),
};
}

Expand All @@ -55,4 +64,13 @@ public function phpTypecast(mixed $value): string|null

return (string) $value;
}

protected function dbTypecastString(string $value): int|string|null
{
if ($value === '') {
return null;
}

return PHP_INT_MAX >= $value && $value >= PHP_INT_MIN ? (int) $value : $value;
Comment thread
Tigrov marked this conversation as resolved.
}
}
12 changes: 11 additions & 1 deletion src/Schema/Column/BinaryColumn.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@

namespace Yiisoft\Db\Schema\Column;

use BackedEnum;
use PDO;
use Stringable;
use Yiisoft\Db\Command\Param;
use Yiisoft\Db\Constant\ColumnType;
use Yiisoft\Db\Expression\ExpressionInterface;
Expand All @@ -25,8 +27,16 @@ public function dbTypecast(mixed $value): mixed
GettypeResult::STRING => new Param($value, PDO::PARAM_LOB),
GettypeResult::RESOURCE => $value,
GettypeResult::NULL => null,
GettypeResult::INTEGER => (string) $value,
GettypeResult::DOUBLE => (string) $value,
GettypeResult::BOOLEAN => $value ? '1' : '0',
default => $value instanceof ExpressionInterface ? $value : (string) $value,
GettypeResult::OBJECT => match (true) {
$value instanceof ExpressionInterface => $value,
$value instanceof BackedEnum => (string) $value->value,
$value instanceof Stringable => (string) $value,
default => $this->throwWrongTypeException($value::class),
},
default => $this->throwWrongTypeException(gettype($value)),
};
}

Expand Down
9 changes: 5 additions & 4 deletions src/Schema/Column/DateTimeColumn.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
use DateTimeImmutable;
use DateTimeInterface;
use DateTimeZone;
use InvalidArgumentException;
use Stringable;
use UnexpectedValueException;
use Yiisoft\Db\Constant\ColumnType;
use Yiisoft\Db\Constant\GettypeResult;
Expand Down Expand Up @@ -104,9 +104,10 @@
$value instanceof DateTimeImmutable => $this->dbTypecastDateTime($value),
$value instanceof DateTimeInterface => $this->dbTypecastDateTime(DateTimeImmutable::createFromInterface($value)),
$value instanceof ExpressionInterface => $value,
default => $this->dbTypecastString((string) $value),
$value instanceof Stringable => $this->dbTypecastString((string) $value),
default => $this->throwWrongTypeException($value::class),
},
default => throw new InvalidArgumentException('Wrong ' . gettype($value) . ' value for ' . $this->getType() . ' column.'),
default => $this->throwWrongTypeException(gettype($value)),
};
}

Expand Down Expand Up @@ -169,7 +170,7 @@
ColumnType::BIGINT => 'U',
ColumnType::FLOAT => 'U.u',
default => throw new UnexpectedValueException(
'Unsupported abstract column type "' . $this->getType() . '" for ' . static::class . ' class.',
'Unsupported abstract column type ' . $this->getType() . ' for ' . static::class . ' class.',

Check warning on line 173 in src/Schema/Column/DateTimeColumn.php

View check run for this annotation

Codecov / codecov/patch

src/Schema/Column/DateTimeColumn.php#L173

Added line #L173 was not covered by tests
),
};
}
Expand Down
33 changes: 24 additions & 9 deletions src/Schema/Column/DoubleColumn.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,16 @@

namespace Yiisoft\Db\Schema\Column;

use BackedEnum;
use DateTimeInterface;
use Stringable;
use Yiisoft\Db\Constant\ColumnType;
use Yiisoft\Db\Constant\GettypeResult;
use Yiisoft\Db\Expression\ExpressionInterface;
use Yiisoft\Db\Constant\PhpType;

use function is_float;
use function gettype;
use function is_int;

/**
* Represents the metadata for a double column.
Expand All @@ -17,15 +22,25 @@ class DoubleColumn extends AbstractColumn
{
protected const DEFAULT_TYPE = ColumnType::DOUBLE;

public function dbTypecast(mixed $value): float|ExpressionInterface|null
public function dbTypecast(mixed $value): ExpressionInterface|float|int|null
{
if (is_float($value)) {
return $value;
}

return match ($value) {
null, '' => null,
default => $value instanceof ExpressionInterface ? $value : (float) $value,
/** @var ExpressionInterface|float|int|null */
return match (gettype($value)) {
GettypeResult::DOUBLE => $value,
GettypeResult::INTEGER => $value,
GettypeResult::NULL => null,
GettypeResult::STRING => $value === '' ? null : (float) $value,
GettypeResult::BOOLEAN => $value ? 1.0 : 0.0,
GettypeResult::OBJECT => match (true) {
$value instanceof ExpressionInterface => $value,
$value instanceof BackedEnum => $value->value === ''
? null
: (is_int($value->value) ? $value->value : (float) $value->value),
$value instanceof DateTimeInterface => (float) $value->format('U.u'),
$value instanceof Stringable => ($val = (string) $value) === '' ? null : (float) $val,
default => $this->throwWrongTypeException($value::class),
},
default => $this->throwWrongTypeException(gettype($value)),
};
}

Expand Down
27 changes: 17 additions & 10 deletions src/Schema/Column/IntegerColumn.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,14 @@
namespace Yiisoft\Db\Schema\Column;

use BackedEnum;
use DateTimeInterface;
use Stringable;
use Yiisoft\Db\Constant\ColumnType;
use Yiisoft\Db\Constant\GettypeResult;
use Yiisoft\Db\Expression\ExpressionInterface;
use Yiisoft\Db\Constant\PhpType;

use function is_int;
use function gettype;

/**
* Represents the schema for an integer column.
Expand All @@ -20,17 +23,21 @@ class IntegerColumn extends AbstractColumn

public function dbTypecast(mixed $value): int|ExpressionInterface|null
{
if (is_int($value)) {
return $value;
}

return match ($value) {
null, '' => null,
default => match (true) {
/** @var ExpressionInterface|int|null */
return match (gettype($value)) {
GettypeResult::INTEGER => $value,
GettypeResult::NULL => null,
GettypeResult::STRING => $value === '' ? null : (int) $value,
GettypeResult::DOUBLE => (int) $value,
GettypeResult::BOOLEAN => $value ? 1 : 0,
GettypeResult::OBJECT => match (true) {
$value instanceof ExpressionInterface => $value,
$value instanceof BackedEnum => (int) $value->value,
default => (int) $value,
$value instanceof BackedEnum => $value->value === '' ? null : (int) $value->value,
$value instanceof DateTimeInterface => $value->getTimestamp(),
$value instanceof Stringable => ($val = (string) $value) === '' ? null : (int) $val,
default => $this->throwWrongTypeException($value::class),
},
default => $this->throwWrongTypeException(gettype($value)),
};
}

Expand Down
8 changes: 6 additions & 2 deletions src/Schema/Column/StringColumn.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace Yiisoft\Db\Schema\Column;

use BackedEnum;
use Stringable;
use Yiisoft\Db\Constant\ColumnType;
use Yiisoft\Db\Expression\ExpressionInterface;
use Yiisoft\Db\Constant\GettypeResult;
Expand All @@ -25,13 +26,16 @@ public function dbTypecast(mixed $value): mixed
GettypeResult::STRING => $value,
GettypeResult::RESOURCE => $value,
GettypeResult::NULL => null,
GettypeResult::INTEGER => (string) $value,
GettypeResult::DOUBLE => (string) $value,
GettypeResult::BOOLEAN => $value ? '1' : '0',
GettypeResult::OBJECT => match (true) {
$value instanceof ExpressionInterface => $value,
$value instanceof BackedEnum => (string) $value->value,
default => (string) $value,
$value instanceof Stringable => (string) $value,
default => $this->throwWrongTypeException($value::class),
},
default => (string) $value,
default => $this->throwWrongTypeException(gettype($value)),
};
}

Expand Down
21 changes: 18 additions & 3 deletions tests/AbstractColumnTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,18 @@

namespace Yiisoft\Db\Tests;

use InvalidArgumentException;
use PHPUnit\Framework\Attributes\DataProviderExternal;
use PHPUnit\Framework\TestCase;
use Yiisoft\Db\Schema\Column\ColumnInterface;
use Yiisoft\Db\Tests\Provider\ColumnProvider;

use function gettype;
use function is_object;

abstract class AbstractColumnTest extends TestCase
{
/** @dataProvider \Yiisoft\Db\Tests\Provider\ColumnProvider::predefinedTypes */
#[DataProviderExternal(ColumnProvider::class, 'predefinedTypes')]
public function testPredefinedType(string $className, string $type, string $phpType)
{
$column = new $className();
Expand All @@ -20,7 +24,7 @@ public function testPredefinedType(string $className, string $type, string $phpT
$this->assertSame($phpType, $column->getPhpType());
}

/** @dataProvider \Yiisoft\Db\Tests\Provider\ColumnProvider::dbTypecastColumns */
#[DataProviderExternal(ColumnProvider::class, 'dbTypecastColumns')]
public function testDbTypecastColumns(ColumnInterface $column, array $values)
{
// Set the timezone for testing purposes, could be any timezone except UTC
Expand All @@ -38,7 +42,18 @@ public function testDbTypecastColumns(ColumnInterface $column, array $values)
date_default_timezone_set($oldDatetime);
}

/** @dataProvider \Yiisoft\Db\Tests\Provider\ColumnProvider::phpTypecastColumns */
#[DataProviderExternal(ColumnProvider::class, 'dbTypecastColumnsWithException')]
public function testDbTypecastColumnsWithException(ColumnInterface $column, mixed $value)
{
$type = is_object($value) ? $value::class : gettype($value);

$this->expectException(InvalidArgumentException::class);
$this->expectExceptionMessage("Wrong $type value for {$column->getType()} column.");

$column->dbTypecast($value);
}

#[DataProviderExternal(ColumnProvider::class, 'phpTypecastColumns')]
public function testPhpTypecastColumns(ColumnInterface $column, array $values)
{
foreach ($values as [$expected, $value]) {
Expand Down
Loading