diff --git a/CHANGELOG.md b/CHANGELOG.md index 6f84beb42..e1dbc55fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -55,6 +55,7 @@ - Enh #442: Refactor `DMLQueryBuilder::upsert()` method (@Tigrov) - Enh #444: Improve `ArrayExpressionBuilder` and `JsonExpressionBuilder` classes (@Tigrov) - Chg #447: Update expression namespaces according to changes in `yiisoft/db` package (@Tigrov) +- Bug #456: Fix typecasting bit columns' values with big size (@Tigrov) ## 1.3.0 March 21, 2024 diff --git a/src/Column/BigBitColumn.php b/src/Column/BigBitColumn.php new file mode 100644 index 000000000..af9a1863d --- /dev/null +++ b/src/Column/BigBitColumn.php @@ -0,0 +1,25 @@ +getSize()); + } + + public function phpTypecast(mixed $value): ?string + { + /** @var string|null $value */ + return $value; + } +} diff --git a/src/Column/BitColumn.php b/src/Column/BitColumn.php index 66dfdcd6f..9722242e8 100644 --- a/src/Column/BitColumn.php +++ b/src/Column/BitColumn.php @@ -4,26 +4,19 @@ namespace Yiisoft\Db\Pgsql\Column; +use Yiisoft\Db\Constant\ColumnType; use Yiisoft\Db\Expression\ExpressionInterface; -use Yiisoft\Db\Schema\Column\BitColumn as BaseBitColumn; +use Yiisoft\Db\Schema\Column\AbstractColumn; use function bindec; -use function decbin; -use function gettype; -use function str_pad; -final class BitColumn extends BaseBitColumn +final class BitColumn extends AbstractColumn { - /** @psalm-suppress RedundantCast */ + protected const DEFAULT_TYPE = ColumnType::BIT; + public function dbTypecast(mixed $value): string|ExpressionInterface|null { - return match (gettype($value)) { - 'integer', 'double' => str_pad(decbin((int) $value), (int) $this->getSize(), '0', STR_PAD_LEFT), - 'NULL' => null, - 'boolean' => $value ? '1' : '0', - 'string' => $value === '' ? null : $value, - default => $value instanceof ExpressionInterface ? $value : (string) $value, - }; + return BitColumnInternal::dbTypecast($value, $this->getSize()); } public function phpTypecast(mixed $value): ?int diff --git a/src/Column/BitColumnInternal.php b/src/Column/BitColumnInternal.php new file mode 100644 index 000000000..d682b10f3 --- /dev/null +++ b/src/Column/BitColumnInternal.php @@ -0,0 +1,49 @@ + */ + public static function className(?int $size): string + { + return !empty($size) && ($size > 63 || PHP_INT_SIZE !== 8 && $size > 31) + ? BigBitColumn::class + : BitColumn::class; + } + + /** @psalm-suppress RedundantCast */ + public static function dbTypecast(mixed $value, ?int $size): string|ExpressionInterface|null + { + return match (gettype($value)) { + 'integer', 'double' => self::addZero(decbin((int) $value), $size), + 'NULL' => null, + 'boolean' => self::addZero($value ? '1' : '0', $size), + 'string' => $value === '' ? null : $value, + default => $value instanceof ExpressionInterface ? $value : (string) $value, + }; + } + + private static function addZero(string $value, ?int $size): string + { + return str_pad($value, (int) $size, '0', STR_PAD_LEFT); + } +} diff --git a/src/Column/ColumnBuilder.php b/src/Column/ColumnBuilder.php index 704a61c0f..d99451211 100644 --- a/src/Column/ColumnBuilder.php +++ b/src/Column/ColumnBuilder.php @@ -14,9 +14,12 @@ public static function boolean(): BooleanColumn return new BooleanColumn(ColumnType::BOOLEAN); } - public static function bit(?int $size = null): BitColumn + public static function bit(?int $size = null): BitColumn|BigBitColumn { - return new BitColumn(ColumnType::BIT, size: $size); + $className = BitColumnInternal::className($size); + + /** @psalm-suppress UnsafeInstantiation */ + return new $className(ColumnType::BIT, size: $size); } public static function tinyint(?int $size = null): IntegerColumn diff --git a/src/Column/ColumnFactory.php b/src/Column/ColumnFactory.php index d474e897e..6e4a9e6ba 100644 --- a/src/Column/ColumnFactory.php +++ b/src/Column/ColumnFactory.php @@ -142,7 +142,7 @@ protected function getColumnClass(string $type, array $info = []): string { return match ($type) { ColumnType::BOOLEAN => BooleanColumn::class, - ColumnType::BIT => BitColumn::class, + ColumnType::BIT => BitColumnInternal::className($info['size'] ?? null), ColumnType::TINYINT => IntegerColumn::class, ColumnType::SMALLINT => IntegerColumn::class, ColumnType::INTEGER => IntegerColumn::class, diff --git a/tests/ColumnTest.php b/tests/ColumnTest.php index e7ff9a11e..93a590538 100644 --- a/tests/ColumnTest.php +++ b/tests/ColumnTest.php @@ -13,6 +13,7 @@ use Yiisoft\Db\Expression\Expression; use Yiisoft\Db\Expression\Value\JsonValue; use Yiisoft\Db\Pgsql\Column\ArrayColumn; +use Yiisoft\Db\Pgsql\Column\BigBitColumn; use Yiisoft\Db\Pgsql\Column\BigIntColumn; use Yiisoft\Db\Pgsql\Column\BinaryColumn; use Yiisoft\Db\Pgsql\Column\BitColumn; @@ -283,6 +284,7 @@ public function testColumnInstance() $this->assertInstanceOf(BinaryColumn::class, $tableSchema->getColumn('blob_col')); $this->assertInstanceOf(BooleanColumn::class, $tableSchema->getColumn('bool_col')); $this->assertInstanceOf(BitColumn::class, $tableSchema->getColumn('bit_col')); + $this->assertInstanceOf(BigBitColumn::class, $tableSchema->getColumn('bigbit_col')); $this->assertInstanceOf(ArrayColumn::class, $tableSchema->getColumn('intarray_col')); $this->assertInstanceOf(IntegerColumn::class, $tableSchema->getColumn('intarray_col')->getColumn()); $this->assertInstanceOf(JsonColumn::class, $tableSchema->getColumn('json_col')); diff --git a/tests/Provider/ColumnBuilderProvider.php b/tests/Provider/ColumnBuilderProvider.php index 6f5c9ec85..d0247118e 100644 --- a/tests/Provider/ColumnBuilderProvider.php +++ b/tests/Provider/ColumnBuilderProvider.php @@ -4,7 +4,9 @@ namespace Yiisoft\Db\Pgsql\Tests\Provider; +use Yiisoft\Db\Constant\ColumnType; use Yiisoft\Db\Pgsql\Column\ArrayColumn; +use Yiisoft\Db\Pgsql\Column\BigBitColumn; use Yiisoft\Db\Pgsql\Column\BinaryColumn; use Yiisoft\Db\Pgsql\Column\BitColumn; use Yiisoft\Db\Pgsql\Column\BooleanColumn; @@ -27,6 +29,7 @@ public static function buildingMethods(): array $values['boolean()'][2] = BooleanColumn::class; $values['bit()'][2] = BitColumn::class; $values['bit(1)'][2] = BitColumn::class; + $values['bit(64)'] = ['bit', [64], BigBitColumn::class, ColumnType::BIT, ['getSize' => 64]]; $values['tinyint()'][2] = IntegerColumn::class; $values['tinyint(1)'][2] = IntegerColumn::class; $values['smallint()'][2] = IntegerColumn::class; diff --git a/tests/Provider/ColumnFactoryProvider.php b/tests/Provider/ColumnFactoryProvider.php index 58243c406..3b8928ec6 100644 --- a/tests/Provider/ColumnFactoryProvider.php +++ b/tests/Provider/ColumnFactoryProvider.php @@ -7,6 +7,7 @@ use Yiisoft\Db\Constant\ColumnType; use Yiisoft\Db\Expression\Expression; use Yiisoft\Db\Pgsql\Column\ArrayColumn; +use Yiisoft\Db\Pgsql\Column\BigBitColumn; use Yiisoft\Db\Pgsql\Column\BinaryColumn; use Yiisoft\Db\Pgsql\Column\BitColumn; use Yiisoft\Db\Pgsql\Column\BooleanColumn; @@ -95,6 +96,7 @@ public static function definitions(): array $definitions['bigint UNSIGNED'][1] = new IntegerColumn(ColumnType::BIGINT, dbType: 'bigint', unsigned: true); $definitions['integer[]'][1] = new ArrayColumn(dbType: 'integer', column: new IntegerColumn(dbType: 'integer')); $definitions['string(126)[][]'][1] = new ArrayColumn(size: 126, dimension: 2, column: new StringColumn(size: 126)); + $definitions['bit(64)'] = ['bit(64)', new BigBitColumn(dbType: 'bit', size: 64)]; return $definitions; } diff --git a/tests/Provider/ColumnProvider.php b/tests/Provider/ColumnProvider.php index bdae4a0e4..367e86499 100644 --- a/tests/Provider/ColumnProvider.php +++ b/tests/Provider/ColumnProvider.php @@ -9,6 +9,7 @@ use Yiisoft\Db\Expression\Expression; use Yiisoft\Db\Pgsql\Column\ArrayColumn; use Yiisoft\Db\Pgsql\Column\ArrayLazyColumn; +use Yiisoft\Db\Pgsql\Column\BigBitColumn; use Yiisoft\Db\Pgsql\Column\BigIntColumn; use Yiisoft\Db\Pgsql\Column\BinaryColumn; use Yiisoft\Db\Pgsql\Column\BitColumn; @@ -45,15 +46,30 @@ public static function dbTypecastColumns(): array $values['binary'][0] = new BinaryColumn(); $values['boolean'][0] = new BooleanColumn(); $values['bit'] = [ - new BitColumn(), + new BitColumn(size: 4), [ [null, null], [null, ''], ['1001', 0b1001], ['1001', '1001'], - ['1', 1.0], - ['1', true], - ['0', false], + ['0001', 1.0], + ['0001', true], + ['0000', false], + [$expression = new Expression('1001'), $expression], + ], + ]; + $values['bigbit'] = [ + new BigBitColumn(size: 64), + [ + [null, null], + [null, ''], + ['0000000000000000000000000000000000000000000000000000000000001001', 0b1001], + ['0000000000000000000000000000000000000000000000000000000000000001', 1.0], + ['0000000000000000000000000000000000000000000000000000000000000001', true], + ['0000000000000000000000000000000000000000000000000000000000000000', false], + ['1100000100011100100110001011000010100000001011001101111011100000', '1100000100011100100110001011000010100000001011001101111011100000'], + ['1001', '1001'], + ['13915164833036950442', '13915164833036950442'], [$expression = new Expression('1001'), $expression], ], ]; @@ -91,6 +107,14 @@ public static function phpTypecastColumns(): array return [ ...$values, + 'bigbit' => [ + new BigBitColumn(size: 64), + [ + [null, null], + ['1001', '1001'], + ['1100000100011100100110001011000010100000001011001101111011100000', '1100000100011100100110001011000010100000001011001101111011100000'], + ], + ], 'array' => [ (new ArrayColumn())->column(new IntegerColumn()), [ diff --git a/tests/Provider/SchemaProvider.php b/tests/Provider/SchemaProvider.php index 5fdf74156..56fd4de35 100644 --- a/tests/Provider/SchemaProvider.php +++ b/tests/Provider/SchemaProvider.php @@ -9,6 +9,7 @@ use Yiisoft\Db\Constant\ColumnType; use Yiisoft\Db\Expression\Expression; use Yiisoft\Db\Pgsql\Column\ArrayColumn; +use Yiisoft\Db\Pgsql\Column\BigBitColumn; use Yiisoft\Db\Pgsql\Column\BinaryColumn; use Yiisoft\Db\Pgsql\Column\BitColumn; use Yiisoft\Db\Pgsql\Column\BooleanColumn; @@ -119,6 +120,10 @@ public static function columns(): array notNull: true, defaultValue: 0b100, // 4 ), + 'bigbit_col' => new BigBitColumn( + dbType: 'varbit', + size: 64, + ), 'bigint_col' => new IntegerColumn( ColumnType::BIGINT, dbType: 'int8', @@ -397,6 +402,16 @@ public static function resultColumns(): array 'len' => -1, 'precision' => 8, ]], + [new BigBitColumn(dbType: 'bit', name: 'bigbit_col', size: 64), [ + 'pgsql:oid' => 1560, + 'pgsql:table_oid' => 40133105, + 'table' => 'type', + 'native_type' => 'bit', + 'pdo_type' => 2, + 'name' => 'bigbit_col', + 'len' => -1, + 'precision' => 64, + ]], [new ArrayColumn(dbType: 'int4', name: 'intarray_col', dimension: 1, column: new IntegerColumn(dbType: 'int4', name: 'intarray_col')), [ 'pgsql:oid' => 1007, 'pgsql:table_oid' => 40133105, diff --git a/tests/Support/Fixture/pgsql.sql b/tests/Support/Fixture/pgsql.sql index ebe84e1be..de2a9f360 100644 --- a/tests/Support/Fixture/pgsql.sql +++ b/tests/Support/Fixture/pgsql.sql @@ -158,6 +158,7 @@ CREATE TABLE "type" ( bool_col2 boolean DEFAULT TRUE, bit_col BIT(8) NOT NULL DEFAULT B'10000010', -- 130 varbit_col VARBIT NOT NULL DEFAULT '100'::bit, -- 4 + bigbit_col VARBIT(64), bigint_col BIGINT, intarray_col integer[], numericarray_col numeric(5,2)[],