Skip to content
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

ColumnSchema classes for performance of typecasting #752

Merged
merged 31 commits into from
May 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
a57729b
Column type classes
Tigrov Sep 1, 2023
a458258
Remove extra tests
Tigrov Sep 1, 2023
0ef31a1
Remove final
Tigrov Sep 1, 2023
eea7d04
Refactor tests
Tigrov Sep 1, 2023
3928a3e
Apply StyleCI fixes
Tigrov Sep 1, 2023
d029f8d
Merge branch 'master' into column_type_classes2
Tigrov Sep 1, 2023
83c6241
Improve params of `createColumnSchema()`
Tigrov Sep 3, 2023
f52b96f
Merge branch 'master' into column_type_classes2
Tigrov Sep 3, 2023
7b91caf
Remove a duplicate test
Tigrov Sep 5, 2023
4cca03b
Add line to CHANGELOG.md
Tigrov Sep 5, 2023
4231047
Merge remote-tracking branch 'origin/master' into column_type_classes2
Tigrov Oct 7, 2023
c46c519
Merge branch 'refs/heads/master' into column_type_classes2
Tigrov Apr 25, 2024
cd27589
Refactor `ColumnSchemaInterface` and typecasting
Tigrov Apr 25, 2024
969714b
Update
Tigrov Apr 27, 2024
ff22127
Merge branch 'refs/heads/master' into column_type_classes2
Tigrov May 4, 2024
56d2848
Improve
Tigrov May 5, 2024
2f705e7
Apply fixes from StyleCI
StyleCIBot May 5, 2024
4bb7de3
Merge branch 'refs/heads/master' into column_type_classes2
Tigrov May 5, 2024
f794df6
Merge branch 'master' into column_type_classes2
vjik May 5, 2024
c882726
Merge branch 'refs/heads/master' into column_type_classes2
Tigrov May 5, 2024
c627ce5
Merge branch 'yiisoft:column_type_classes2' into column_type_classes2
Tigrov May 5, 2024
6041faa
Update UPGRADE.md [skip ci]
Tigrov May 6, 2024
78806d7
Merge branch 'yiisoft:column_type_classes2' into column_type_classes2
Tigrov May 6, 2024
cb0a28b
Improve bigint typecasting
Tigrov May 6, 2024
ece7157
Merge branch 'yiisoft:column_type_classes2' into column_type_classes2
Tigrov May 6, 2024
a26176b
Merge branch 'refs/heads/master' into column_type_classes2
Tigrov May 24, 2024
a7690fe
Merge branch 'yiisoft:column_type_classes2' into column_type_classes2
Tigrov May 24, 2024
d2846c7
Merge branch 'refs/heads/master' into column_type_classes2
Tigrov May 24, 2024
d1e072b
Merge branch 'yiisoft:column_type_classes2' into column_type_classes2
Tigrov May 24, 2024
d36dc53
Update doc [skip ci]
Tigrov May 30, 2024
9400b1d
Merge branch 'yiisoft:column_type_classes2' into column_type_classes2
Tigrov May 30, 2024
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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
- Enh #806: 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)
- Enh #752: Implement `ColumnSchemaInterface` classes according to the data type of database table columns
Tigrov marked this conversation as resolved.
Show resolved Hide resolved
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)
- Enh #834: Refactor `AbstractCommand::insertBatch()`, add `Quoter::getRawTableName()` to `QuoterInterface` (@Tigrov)
Expand Down
23 changes: 22 additions & 1 deletion UPGRADE.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ Add support any scalar values for `$columns` parameter of these methods in your
`QueryBuilderInterface` parameter in the constructor. Each DBMS driver should implement its own expression builder.

`Expression::$params` can contain:
- non-unique placeholder names, they will be replaced with unique names.
- non-unique placeholder names, they will be replaced with unique names;
- `Expression` instances, they will be built when building a query using `QueryBuilder`.

### Rename `batchInsert()` to `insertBatch()`
Expand All @@ -57,6 +57,27 @@ $values = [
$db->createCommand()->insertBatch('user', $values)->execute();
```

### `ColumnSchemaInterface` changes

The interface and the abstract implementation `AbstractColumnSchema` were moved to `Yiisoft\Db\Schema\Column` namespace
and the following changes were made:

- `getName()` method can return `string` or `null`;
- `name(string|null $name)` method is added;
- constructor of `AbstractColumnSchema` class is changed to `__construct(string $type, string|null $phpType = null)`.

### New classes for table columns

Each table column has its own class in the `Yiisoft\Db\Schema\Column` namespace according to the data type:

- `BooleanColumnSchema` for columns with boolean type;
- `IntegerColumnSchema` for columns with integer type (tinyint, smallint, integer, bigint);
- `BigIntColumnSchema` for columns with integer type with range outside `PHP_INT_MIN` and `PHP_INT_MAX`;
- `DoubleColumnSchema` for columns with fractional number type (float, double, decimal, money);
- `StringColumnSchema` for columns with string or datetime type (char, string, text, datetime, timestamp, date, time);
- `BinaryColumnSchema` for columns with binary type;
- `JsonColumnSchema` for columns with json type.

### New methods in `QuoterInterface`

- `QuoterInterface::getRawTableName()` - returns the raw table name without quotes.
Expand Down
63 changes: 55 additions & 8 deletions src/Schema/AbstractSchema.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,14 @@
use Yiisoft\Db\Constraint\Constraint;
use Yiisoft\Db\Constraint\IndexConstraint;
use Yiisoft\Db\Exception\NotSupportedException;
use Yiisoft\Db\Schema\Column\BinaryColumnSchema;
use Yiisoft\Db\Schema\Column\BooleanColumnSchema;
use Yiisoft\Db\Schema\Column\ColumnSchemaInterface;
use Yiisoft\Db\Schema\Column\DoubleColumnSchema;
use Yiisoft\Db\Schema\Column\IntegerColumnSchema;
use Yiisoft\Db\Schema\Column\JsonColumnSchema;
use Yiisoft\Db\Schema\Column\StringColumnSchema;
use Yiisoft\Db\Schema\Column\BigIntColumnSchema;

use function gettype;
use function is_array;
Expand Down Expand Up @@ -365,24 +373,46 @@
}

/**
* Extracts the PHP type from an abstract DB type.
* Creates a column schema for the database.
*
* @param ColumnSchemaInterface $column The column schema information.
* This method may be overridden by child classes to create a DBMS-specific column schema.
*
* @param string $type The abstract data type.
* @param mixed ...$info The column information.
* @psalm-param array{unsigned?: bool} $info The set of parameters may be different for a specific DBMS.
*
* @return ColumnSchemaInterface
*/
protected function createColumnSchema(string $type, mixed ...$info): ColumnSchemaInterface
{
$isUnsigned = !empty($info['unsigned']);
$phpType = $this->getColumnPhpType($type, $isUnsigned);

$column = $this->createColumnSchemaFromPhpType($phpType, $type);
$column->unsigned($isUnsigned);

return $column;
}

/**
* Get the PHP type from an abstract database type.
*
* @param string $type The abstract database type.
*
* @return string The PHP type name.
*/
protected function getColumnPhpType(ColumnSchemaInterface $column): string
protected function getColumnPhpType(string $type, bool $isUnsigned = false): string
{
return match ($column->getType()) {
return match ($type) {
// abstract type => php type
SchemaInterface::TYPE_TINYINT => SchemaInterface::PHP_TYPE_INTEGER,
SchemaInterface::TYPE_SMALLINT => SchemaInterface::PHP_TYPE_INTEGER,
SchemaInterface::TYPE_INTEGER => PHP_INT_SIZE === 4 && $column->isUnsigned()
SchemaInterface::TYPE_INTEGER => PHP_INT_SIZE !== 8 && $isUnsigned
? SchemaInterface::PHP_TYPE_STRING

Check warning on line 411 in src/Schema/AbstractSchema.php

View check run for this annotation

Codecov / codecov/patch

src/Schema/AbstractSchema.php#L411

Added line #L411 was not covered by tests
: SchemaInterface::PHP_TYPE_INTEGER,
SchemaInterface::TYPE_BIGINT => PHP_INT_SIZE !== 8 || $isUnsigned
? SchemaInterface::PHP_TYPE_STRING
: SchemaInterface::PHP_TYPE_INTEGER,
SchemaInterface::TYPE_BIGINT => PHP_INT_SIZE === 8 && !$column->isUnsigned()
? SchemaInterface::PHP_TYPE_INTEGER
: SchemaInterface::PHP_TYPE_STRING,
SchemaInterface::TYPE_BOOLEAN => SchemaInterface::PHP_TYPE_BOOLEAN,
SchemaInterface::TYPE_DECIMAL => SchemaInterface::PHP_TYPE_DOUBLE,
SchemaInterface::TYPE_FLOAT => SchemaInterface::PHP_TYPE_DOUBLE,
Expand All @@ -393,6 +423,23 @@
};
}

protected function createColumnSchemaFromPhpType(string $phpType, string $type): ColumnSchemaInterface
{
return match ($phpType) {
SchemaInterface::PHP_TYPE_STRING => match ($type) {
SchemaInterface::TYPE_INTEGER => new BigIntColumnSchema($type, $phpType),
SchemaInterface::TYPE_BIGINT => new BigIntColumnSchema($type, $phpType),
default => new StringColumnSchema($type, $phpType),
},
SchemaInterface::PHP_TYPE_INTEGER => new IntegerColumnSchema($type, $phpType),
SchemaInterface::PHP_TYPE_DOUBLE => new DoubleColumnSchema($type, $phpType),
SchemaInterface::PHP_TYPE_BOOLEAN => new BooleanColumnSchema($type, $phpType),
SchemaInterface::PHP_TYPE_RESOURCE => new BinaryColumnSchema($type, $phpType),
SchemaInterface::PHP_TYPE_ARRAY => new JsonColumnSchema($type, $phpType),
default => new StringColumnSchema($type, $phpType),
};
}

/**
* Returns the metadata of the given type for all tables in the given schema.
*
Expand Down
2 changes: 2 additions & 0 deletions src/Schema/AbstractTableSchema.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

namespace Yiisoft\Db\Schema;

use Yiisoft\Db\Schema\Column\ColumnSchemaInterface;

use function array_keys;

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,7 @@

declare(strict_types=1);

namespace Yiisoft\Db\Schema;

use Yiisoft\Db\Expression\ExpressionInterface;
use Yiisoft\Db\Helper\DbStringHelper;

use function gettype;
use function in_array;
use function is_bool;
use function is_float;
use function is_resource;
namespace Yiisoft\Db\Schema\Column;

/**
* Represents the metadata of a column in a database table.
Expand Down Expand Up @@ -50,15 +41,16 @@
private array|null $enumValues = null;
private string|null $extra = null;
private bool $isPrimaryKey = false;
private string|null $phpType = null;
private string|null $name = null;
private int|null $precision = null;
private int|null $scale = null;
private int|null $size = null;
private string $type = '';
private bool $unsigned = false;

public function __construct(private string $name)
{
public function __construct(
private string $type,
private string|null $phpType = null,
) {
}

public function allowNull(bool $value): void
Expand Down Expand Up @@ -86,15 +78,6 @@
$this->dbType = $value;
}

public function dbTypecast(mixed $value): mixed
{
/**
* The default implementation does the same as casting for PHP, but it should be possible to override this with
* annotation of an explicit PDO type.
*/
return $this->typecast($value);
}

public function defaultValue(mixed $value): void
{
$this->defaultValue = $value;
Expand Down Expand Up @@ -135,7 +118,7 @@
return $this->extra;
}

public function getName(): string
public function getName(): string|null
{
return $this->name;
}
Expand Down Expand Up @@ -190,14 +173,14 @@
return $this->unsigned;
}

public function phpType(string|null $value): void
public function name(string|null $name): void
{
$this->phpType = $value;
$this->name = $name;
}

public function phpTypecast(mixed $value): mixed
public function phpType(string|null $value): void

Check warning on line 181 in src/Schema/Column/AbstractColumnSchema.php

View check run for this annotation

Codecov / codecov/patch

src/Schema/Column/AbstractColumnSchema.php#L181

Added line #L181 was not covered by tests
{
return $this->typecast($value);
$this->phpType = $value;

Check warning on line 183 in src/Schema/Column/AbstractColumnSchema.php

View check run for this annotation

Codecov / codecov/patch

src/Schema/Column/AbstractColumnSchema.php#L183

Added line #L183 was not covered by tests
}

public function precision(int|null $value): void
Expand Down Expand Up @@ -229,50 +212,4 @@
{
$this->unsigned = $value;
}

/**
* Converts the input value according to {@see phpType} after retrieval from the database.
*
* If the value is null or an {@see Expression}, it won't be converted.
*
* @param mixed $value The value to be converted.
*
* @return mixed The converted value.
*/
protected function typecast(mixed $value): mixed
{
if (
$value === null
|| $value === '' && !in_array($this->type, [
SchemaInterface::TYPE_TEXT,
SchemaInterface::TYPE_STRING,
SchemaInterface::TYPE_BINARY,
SchemaInterface::TYPE_CHAR,
], true)
) {
return null;
}

if ($value instanceof ExpressionInterface) {
return $value;
}

return match ($this->phpType) {
gettype($value) => $value,
SchemaInterface::PHP_TYPE_RESOURCE,
SchemaInterface::PHP_TYPE_STRING
=> match (true) {
is_resource($value) => $value,
/** ensure type cast always has . as decimal separator in all locales */
is_float($value) => DbStringHelper::normalizeFloat($value),
is_bool($value) => $value ? '1' : '0',
default => (string) $value,
},
SchemaInterface::PHP_TYPE_INTEGER => (int) $value,
/** Treating a 0-bit value as false too (@link https://github.com/yiisoft/yii2/issues/9006) */
SchemaInterface::PHP_TYPE_BOOLEAN => $value && $value !== "\0",
SchemaInterface::PHP_TYPE_DOUBLE => (float) $value,
default => $value,
};
}
}
49 changes: 49 additions & 0 deletions src/Schema/Column/BigIntColumnSchema.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Db\Schema\Column;

use Yiisoft\Db\Expression\ExpressionInterface;
use Yiisoft\Db\Schema\SchemaInterface;

use const PHP_INT_MAX;
use const PHP_INT_MIN;

class BigIntColumnSchema extends AbstractColumnSchema
{
public function __construct(
string $type = SchemaInterface::TYPE_BIGINT,
string|null $phpType = SchemaInterface::PHP_TYPE_STRING,
) {
parent::__construct($type, $phpType);
}

public function dbTypecast(mixed $value): int|string|ExpressionInterface|null
{
return match (gettype($value)) {
'string' => $value === '' ? null : (
$value <= PHP_INT_MAX && $value >= PHP_INT_MIN
? (int) $value
: $value
),
'NULL' => null,
'integer' => $value,
'boolean' => $value ? 1 : 0,
default => $value instanceof ExpressionInterface ? $value : (
($val = (string) $value) <= PHP_INT_MAX && $val >= PHP_INT_MIN
vjik marked this conversation as resolved.
Show resolved Hide resolved
? (int) $val
: $val
),
};
}

public function phpTypecast(mixed $value): string|null
vjik marked this conversation as resolved.
Show resolved Hide resolved
{
if ($value === null) {
return null;
}

return (string) $value;
}
}
38 changes: 38 additions & 0 deletions src/Schema/Column/BinaryColumnSchema.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

declare(strict_types=1);

namespace Yiisoft\Db\Schema\Column;

use PDO;
use Yiisoft\Db\Command\Param;
use Yiisoft\Db\Expression\ExpressionInterface;
use Yiisoft\Db\Schema\SchemaInterface;

use function gettype;

class BinaryColumnSchema extends AbstractColumnSchema
{
public function __construct(
string $type = SchemaInterface::TYPE_BINARY,
string|null $phpType = SchemaInterface::PHP_TYPE_RESOURCE,
) {
parent::__construct($type, $phpType);
}

public function dbTypecast(mixed $value): mixed
{
return match (gettype($value)) {
'string' => new Param($value, PDO::PARAM_LOB),
'resource' => $value,
'NULL' => null,
'boolean' => $value ? '1' : '0',
default => $value instanceof ExpressionInterface ? $value : (string) $value,
};
}

public function phpTypecast(mixed $value): mixed
{
return $value;
}
}
Loading
Loading