diff --git a/CHANGELOG.md b/CHANGELOG.md index 9874d896..f9ba7d0d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,6 +41,7 @@ - Enh #313: Refactor according changes in `db` package (@Tigrov) - New #311: Add `caseSensitive` option to like condition (@vjik) - Enh #315: Remove `getCacheKey()` and `getCacheTag()` methods from `Schema` class (@Tigrov) +- Enh #319: Support `boolean` type (@Tigrov) - Enh #318, #320: Use `DbArrayHelper::arrange()` instead of `DbArrayHelper::index()` method (@Tigrov) ## 1.3.0 March 21, 2024 diff --git a/src/Column/BooleanColumn.php b/src/Column/BooleanColumn.php new file mode 100644 index 00000000..4d036aad --- /dev/null +++ b/src/Column/BooleanColumn.php @@ -0,0 +1,40 @@ + '1', + false => '0', + null, '' => null, + default => $value instanceof ExpressionInterface ? $value : ($value ? '1' : '0'), + }; + } + + /** @psalm-mutation-free */ + public function getPhpType(): string + { + return PhpType::BOOL; + } + + public function phpTypecast(mixed $value): bool|null + { + if ($value === null) { + return null; + } + + return $value && $value !== "\0"; + } +} diff --git a/src/Column/ColumnBuilder.php b/src/Column/ColumnBuilder.php index c48c3e81..9fb6eeb4 100644 --- a/src/Column/ColumnBuilder.php +++ b/src/Column/ColumnBuilder.php @@ -13,6 +13,11 @@ public static function binary(int|null $size = null): BinaryColumn return new BinaryColumn(ColumnType::BINARY, size: $size); } + public static function boolean(): BooleanColumn + { + return new BooleanColumn(ColumnType::BOOLEAN); + } + public static function json(): JsonColumn { return new JsonColumn(ColumnType::JSON); diff --git a/src/Column/ColumnDefinitionBuilder.php b/src/Column/ColumnDefinitionBuilder.php index 68e8e75e..550e07f6 100644 --- a/src/Column/ColumnDefinitionBuilder.php +++ b/src/Column/ColumnDefinitionBuilder.php @@ -56,13 +56,17 @@ protected function buildCheck(ColumnInterface $column): string if (empty($check)) { $name = $column->getName(); - if (empty($name) || version_compare($this->queryBuilder->getServerInfo()->getVersion(), '21', '>=')) { + if (empty($name)) { return ''; } return match ($column->getType()) { ColumnType::ARRAY, ColumnType::STRUCTURED, ColumnType::JSON => - ' CHECK (' . $this->queryBuilder->getQuoter()->quoteSimpleColumnName($name) . ' IS JSON)', + version_compare($this->queryBuilder->getServerInfo()->getVersion(), '21', '<') + ? ' CHECK (' . $this->queryBuilder->getQuoter()->quoteSimpleColumnName($name) . ' IS JSON)' + : '', + ColumnType::BOOLEAN => + ' CHECK (' . $this->queryBuilder->getQuoter()->quoteSimpleColumnName($name) . ' IN (0,1))', default => '', }; } @@ -94,7 +98,7 @@ protected function getDbType(ColumnInterface $column): string return match ($dbType) { default => $dbType, null => match ($column->getType()) { - ColumnType::BOOLEAN => 'number(1)', + ColumnType::BOOLEAN => 'char(1)', ColumnType::BIT => match (true) { $size === null => 'number(38)', $size <= 126 => 'number(' . ceil(log10(2 ** $size)) . ')', diff --git a/src/Column/ColumnFactory.php b/src/Column/ColumnFactory.php index 7b675407..daea5003 100644 --- a/src/Column/ColumnFactory.php +++ b/src/Column/ColumnFactory.php @@ -65,8 +65,18 @@ protected function getType(string $dbType, array $info = []): string }; } - if (isset($info['check'], $info['name']) && strcasecmp($info['check'], '"' . $info['name'] . '" is json') === 0) { - return ColumnType::JSON; + if (isset($info['check'], $info['name'])) { + if (strcasecmp($info['check'], '"' . $info['name'] . '" is json') === 0) { + return ColumnType::JSON; + } + + if (isset($info['size']) + && $dbType === 'char' + && $info['size'] === 1 + && strcasecmp($info['check'], '"' . $info['name'] . '" in (0,1)') === 0 + ) { + return ColumnType::BOOLEAN; + } } if ($dbType === 'interval day to second' && isset($info['scale']) && $info['scale'] === 0) { @@ -80,6 +90,7 @@ protected function getColumnClass(string $type, array $info = []): string { return match ($type) { ColumnType::BINARY => BinaryColumn::class, + ColumnType::BOOLEAN => BooleanColumn::class, ColumnType::JSON => JsonColumn::class, default => parent::getColumnClass($type, $info), }; diff --git a/src/QueryBuilder.php b/src/QueryBuilder.php index 04bdb862..3bc6f400 100644 --- a/src/QueryBuilder.php +++ b/src/QueryBuilder.php @@ -15,6 +15,10 @@ */ final class QueryBuilder extends AbstractQueryBuilder { + protected const FALSE_VALUE = "'0'"; + + protected const TRUE_VALUE = "'1'"; + public function __construct(ConnectionInterface $db) { $quoter = $db->getQuoter(); diff --git a/tests/ColumnTest.php b/tests/ColumnTest.php index be52ebe7..dc169137 100644 --- a/tests/ColumnTest.php +++ b/tests/ColumnTest.php @@ -5,10 +5,12 @@ namespace Yiisoft\Db\Oracle\Tests; use PDO; +use PHPUnit\Framework\Attributes\DataProviderExternal; use Yiisoft\Db\Command\Param; use Yiisoft\Db\Expression\Expression; use Yiisoft\Db\Oracle\Column\BinaryColumn; use Yiisoft\Db\Oracle\Column\JsonColumn; +use Yiisoft\Db\Oracle\Tests\Provider\ColumnProvider; use Yiisoft\Db\Oracle\Tests\Support\TestTrait; use Yiisoft\Db\Query\Query; use Yiisoft\Db\Schema\Column\ColumnInterface; @@ -106,12 +108,18 @@ public function testPredefinedType(string $className, string $type, string $phpT parent::testPredefinedType($className, $type, $phpType); } - /** @dataProvider \Yiisoft\Db\Oracle\Tests\Provider\ColumnProvider::dbTypecastColumns */ + #[DataProviderExternal(ColumnProvider::class, 'dbTypecastColumns')] public function testDbTypecastColumns(ColumnInterface $column, array $values): void { parent::testDbTypecastColumns($column, $values); } + #[DataProviderExternal(ColumnProvider::class, 'phpTypecastColumns')] + public function testPhpTypecastColumns(ColumnInterface $column, array $values): void + { + parent::testPhpTypecastColumns($column, $values); + } + public function testBinaryColumn(): void { $binaryCol = new BinaryColumn(); diff --git a/tests/Provider/ColumnBuilderProvider.php b/tests/Provider/ColumnBuilderProvider.php index 3af84a01..13c4b9e9 100644 --- a/tests/Provider/ColumnBuilderProvider.php +++ b/tests/Provider/ColumnBuilderProvider.php @@ -5,6 +5,7 @@ namespace Yiisoft\Db\Oracle\Tests\Provider; use Yiisoft\Db\Oracle\Column\BinaryColumn; +use Yiisoft\Db\Oracle\Column\BooleanColumn; use Yiisoft\Db\Oracle\Column\JsonColumn; class ColumnBuilderProvider extends \Yiisoft\Db\Tests\Provider\ColumnBuilderProvider @@ -15,6 +16,7 @@ public static function buildingMethods(): array $values['binary()'][2] = BinaryColumn::class; $values['binary(8)'][2] = BinaryColumn::class; + $values['boolean()'][2] = BooleanColumn::class; $values['json()'][2] = JsonColumn::class; return $values; diff --git a/tests/Provider/ColumnFactoryProvider.php b/tests/Provider/ColumnFactoryProvider.php index 30b4083b..cf660528 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\Oracle\Column\BinaryColumn; +use Yiisoft\Db\Oracle\Column\BooleanColumn; use Yiisoft\Db\Oracle\Column\JsonColumn; use Yiisoft\Db\Schema\Column\ArrayColumn; use Yiisoft\Db\Schema\Column\BigIntColumn; @@ -85,6 +86,8 @@ public static function types(): array { $types = parent::types(); + $types['binary'][2] = BinaryColumn::class; + $types['boolean'][2] = BooleanColumn::class; $types['json'][2] = JsonColumn::class; return $types; diff --git a/tests/Provider/ColumnProvider.php b/tests/Provider/ColumnProvider.php index 44cd166f..df191cd3 100644 --- a/tests/Provider/ColumnProvider.php +++ b/tests/Provider/ColumnProvider.php @@ -4,7 +4,10 @@ namespace Yiisoft\Db\Oracle\Tests\Provider; +use Yiisoft\Db\Expression\Expression; use Yiisoft\Db\Oracle\Column\BinaryColumn; +use Yiisoft\Db\Oracle\Column\BooleanColumn; +use Yiisoft\Db\Oracle\Column\JsonColumn; class ColumnProvider extends \Yiisoft\Db\Tests\Provider\ColumnProvider { @@ -12,6 +15,8 @@ public static function predefinedTypes(): array { $values = parent::predefinedTypes(); $values['binary'][0] = BinaryColumn::class; + $values['boolean'][0] = BooleanColumn::class; + $values['json'][0] = JsonColumn::class; return $values; } @@ -20,6 +25,33 @@ public static function dbTypecastColumns(): array { $values = parent::dbTypecastColumns(); $values['binary'][0] = new BinaryColumn(); + $values['boolean'] = [ + new BooleanColumn(), + [ + [null, null], + [null, ''], + ['1', true], + ['1', 1], + ['1', 1.0], + ['1', '1'], + ['0', false], + ['0', 0], + ['0', 0.0], + ['0', '0'], + [$expression = new Expression('expression'), $expression], + ], + ]; + $values['json'][0] = new JsonColumn(); + + return $values; + } + + public static function phpTypecastColumns(): array + { + $values = parent::phpTypecastColumns(); + $values['binary'][0] = new BinaryColumn(); + $values['boolean'][0] = new BooleanColumn(); + $values['json'][0] = new JsonColumn(); return $values; } diff --git a/tests/Provider/CommandProvider.php b/tests/Provider/CommandProvider.php index 22bdf794..85842e4c 100644 --- a/tests/Provider/CommandProvider.php +++ b/tests/Provider/CommandProvider.php @@ -99,6 +99,20 @@ public static function insertVarbinary(): array ]; } + public static function rawSql(): array + { + $rawSql = parent::rawSql(); + + foreach ($rawSql as &$values) { + $values[2] = strtr($values[2], [ + 'FALSE' => "'0'", + 'TRUE' => "'1'", + ]); + } + + return $rawSql; + } + public static function createIndex(): array { return [ diff --git a/tests/Provider/QueryBuilderProvider.php b/tests/Provider/QueryBuilderProvider.php index 37d460f9..d88e2674 100644 --- a/tests/Provider/QueryBuilderProvider.php +++ b/tests/Provider/QueryBuilderProvider.php @@ -271,8 +271,8 @@ public static function buildColumnDefinition(): array $values['bigPrimaryKey(false)'][0] = 'number(20) PRIMARY KEY'; $values['uuidPrimaryKey()'][0] = 'raw(16) DEFAULT sys_guid() PRIMARY KEY'; $values['uuidPrimaryKey(false)'][0] = 'raw(16) PRIMARY KEY'; - $values['boolean()'][0] = 'number(1)'; - $values['boolean(100)'][0] = 'number(1)'; + $values['boolean()'] = ['char(1) CHECK ("boolean_col" IN (0,1))', $values['boolean()'][1]->withName('boolean_col')]; + $values['boolean(100)'] = ['char(1) CHECK ("boolean_100" IN (0,1))', $values['boolean(100)'][1]->withName('boolean_100')]; $values['bit()'][0] = 'number(38)'; $values['bit(1)'][0] = 'number(1)'; $values['bit(8)'][0] = 'number(3)'; @@ -384,6 +384,8 @@ public static function prepareParam(): array { $values = parent::prepareParam(); + $values['true'][0] = "'1'"; + $values['false'][0] = "'0'"; $values['binary'][0] = "HEXTORAW('737472696e67')"; return $values; @@ -393,6 +395,8 @@ public static function prepareValue(): array { $values = parent::prepareValue(); + $values['true'][0] = "'1'"; + $values['false'][0] = "'0'"; $values['binary'][0] = "HEXTORAW('737472696e67')"; $values['paramBinary'][0] = "HEXTORAW('737472696e67')"; diff --git a/tests/Provider/SchemaProvider.php b/tests/Provider/SchemaProvider.php index ab8063cf..f54947a8 100644 --- a/tests/Provider/SchemaProvider.php +++ b/tests/Provider/SchemaProvider.php @@ -8,6 +8,7 @@ use Yiisoft\Db\Constraint\CheckConstraint; use Yiisoft\Db\Expression\Expression; use Yiisoft\Db\Oracle\Column\BinaryColumn; +use Yiisoft\Db\Oracle\Column\BooleanColumn; use Yiisoft\Db\Oracle\Column\JsonColumn; use Yiisoft\Db\Schema\Column\DoubleColumn; use Yiisoft\Db\Schema\Column\IntegerColumn; @@ -102,19 +103,17 @@ public static function columns(): array scale: 1, defaultValue: new Expression("INTERVAL '2 04:56:12' DAY(1) TO SECOND(0)"), ), - 'bool_col' => new StringColumn( - ColumnType::CHAR, + 'bool_col' => new BooleanColumn( dbType: 'char', check: '"bool_col" in (0,1)', notNull: true, size: 1, ), - 'bool_col2' => new StringColumn( - ColumnType::CHAR, + 'bool_col2' => new BooleanColumn( dbType: 'char', check: '"bool_col2" in (0,1)', size: 1, - defaultValue: '1', + defaultValue: true, ), 'ts_default' => new StringColumn( ColumnType::TIMESTAMP,