yii2-gii icon indicating copy to clipboard operation
yii2-gii copied to clipboard

Mysql store Boolean as TinyInt(1)

Open WinterSilence opened this issue 4 years ago • 30 comments
trafficstars

What steps will reproduce the problem?

Create table in mysql8.* with boolean column.

What's expected?

Generator work with column tinyint(1) as is.

What do you get instead?

case Schema::TYPE_TINYINT:
    if ($this->getDbDriverName() === 'mysql' && $column->size === 1 && in_array($column->defaultValue, [0, 1])) {
        $type = 'bool';
    } else {
        $type = 'int';
    }
    break;

Additional info

Q A
Yii version 2.0.40, gii 2.2.1
PHP version 7.3
Database version msql8
Operating system Win10

WinterSilence avatar Feb 28 '21 21:02 WinterSilence

Your suggestion no certain. As detour can be using enum(Yes/No), but in Gii PR frozen #434 . In Giiant enum realized

uldisn avatar Mar 01 '21 05:03 uldisn

I'm not sure what is the problem here.

bizley avatar Mar 01 '21 08:03 bizley

No any way, how recognize column as boolean, except add to column name something like bool. For example: is_expired_bool

uldisn avatar Mar 01 '21 09:03 uldisn

@uldisn you are talking about different problem than @WinterSilence right?

bizley avatar Mar 01 '21 09:03 bizley

@bizley yes, @uldisn add quick fix for me, but I can't replace tinyint to enum in db without BC. Field type determinate validation rules and html fields in CRUD views. I want display that's columns as boolean - checkbox and value 0/1 or true/false (in strict mode too).

WinterSilence avatar Mar 01 '21 10:03 WinterSilence

So the value is not cast to boolean?

bizley avatar Mar 01 '21 10:03 bizley

@bizley enum's too work strange: in form - select, in model - string without rules as in_array()

WinterSilence avatar Mar 01 '21 10:03 WinterSilence

@bizley cast where? in yii code or in db?

WinterSilence avatar Mar 01 '21 10:03 WinterSilence

What is the type of such attribute when you fetch the model from a database?

bizley avatar Mar 01 '21 10:03 bizley

@bizley https://dev.mysql.com/doc/refman/5.7/en/numeric-type-syntax.html it's stored as tinyint but in sql you can set as true/false

WinterSilence avatar Mar 01 '21 10:03 WinterSilence

I'm not asking how it's stored, MySQL stores booleans as tinyInts. I'm asking what are you getting in the model when you fetch it from DB.

bizley avatar Mar 01 '21 10:03 bizley

For me actually is problem with booleans:

  • model generator can not recognize from table schema
  • if model genera recognize boolean, it can generate isExpiredTrue(), setExpiredTrue(). what is helpfully for writing logic
  • crud generator can generate boolean columns and check boxes in forms Main problem is recognizing boolean from table scheme

uldisn avatar Mar 01 '21 10:03 uldisn

@uldisn this problem has been mentioned several times already in different issues. Main problem here is to properly handle enums in all database engines that Yii supports. So far we don't have one elegant solution. Please don't hijack this issue.

bizley avatar Mar 01 '21 10:03 bizley

@bizley after my fix:

/**
 * @property bool|null $is_bool
 */
class TblTest1 extends \yii\db\ActiveRecord
{ 
    /**
     * {@inheritDoc}
     */
    public function rules(): array
    {
        return [
            ...
            [['is_bool'], 'boolean'],
           ...
        ]

WinterSilence avatar Mar 01 '21 11:03 WinterSilence

@bizley i talk about boolean, not enum. gii use fix for postgresql without any problem

WinterSilence avatar Mar 01 '21 11:03 WinterSilence

Oh, I think I understand now. Generating model from MySQL DB is not preparing boolean rule validator for that field, right? Hmm, I was quite sure tinyint(1) is cast as boolean in that case but looks like not. @samdark could you refresh my memory about that? There is this code in MySQL Schema class but there is bit there... Hm...

bizley avatar Mar 01 '21 11:03 bizley

In recent MySQL versions these two types aren't equivalent:

  • https://dev.mysql.com/doc/refman/8.0/en/bit-type.html
  • https://dev.mysql.com/doc/refman/8.0/en/integer-types.html

But overall, we can add an alias, I see no problem with it.

@WinterSilence are we correct that you expect a field with tinyint(1) or bit to have boolean in validation rule?

samdark avatar Mar 01 '21 14:03 samdark

@samdark To cover more cases, value may be any value converting to boolean/int(to BC) before validation. Boolean values are stored as integers 0 (false) and 1 (true). I check it's valid for most popular db: mysql, postgres, mssql, sqllite. Form field determinate by type?

Database Boolean? Use Instead
Oracle No NUMBER(1)
SQL Server No BIT
MySQL No BIT or TINYINT
SQLite No TINYINT
PostgreSQL Yes  

WinterSilence avatar Mar 01 '21 15:03 WinterSilence

We can do it for Gii for sure. As for doing it at schema level, it may break things.

samdark avatar Mar 01 '21 16:03 samdark

@samdark I'm extended class yii\gii\generators\model\Generator:

protected function generateProperties($table)
{
    $properties = [];
    $driverName = $this->getDbDriverName();
    foreach ($table->columns as $column) {
        switch ($column->type) {
            case Schema::TYPE_SMALLINT:
            case Schema::TYPE_INTEGER:
            case Schema::TYPE_BIGINT:
                $type = 'int';
                break;
            case Schema::TYPE_TINYINT:
                if ($driverName !== 'pgsql' && $column->size === 1 && in_array($column->defaultValue, [0, 1])) {
                    $type = 'bool';
                } else {
                    $type = 'int';
                }
                break;

can be possible set 2 types for column?

public function generateRules($table)
{
    $types = [];
    $lengths = [];
    $driverName = $this->getDbDriverName();
    foreach ($table->columns as $column) {
        if ($column->autoIncrement) {
            continue;
        }
        if (!$column->allowNull && $column->defaultValue === null) {
            $types['required'][] = $column->name;
        }
        switch ($column->type) {
            case Schema::TYPE_SMALLINT:
            case Schema::TYPE_INTEGER:
            case Schema::TYPE_BIGINT:
                $types['integer'][] = $column->name;
                break;
            case Schema::TYPE_TINYINT:
                if ($driverName !== 'pgsql' && $column->size === 1 && in_array($column->defaultValue, [0, 1])) {
                    // maybe add extra rule [$column->name, 'in', 'range' => [0, 1, true, false, null], 'strict' => true]
                    $types['boolean'][] = $column->name;
                } else {
                    $types['integer'][] = $column->name;
                }

off top: display column from related model as select in form. it can be possible?

WinterSilence avatar Mar 01 '21 16:03 WinterSilence

can be possible set 2 types for column?

2 types at the same time? Ummm... no? But you've got overall idea alright.

samdark avatar Mar 01 '21 17:03 samdark

@samdark how you validate multi type values?

/**
 * Checks type of field value.
 *
 * @param mixed $value Field value.
 * @param string ...$types Valid value types.
 * @return bool
 */
public static function type($value, string ...$types): bool
{
    $type = gettype($value);
    if (in_array($type, $types, true)) {
        return true;
    }
    // Check extra types as "numeric", "iterable", "countable" and etc.
    foreach ($types as $type) {
        $func = 'is_' . $type;
        if (function_exists($func) && $func($value)) {
            return true;
        }
    }
    return false;
}

yii have validation rules like that?

WinterSilence avatar Mar 01 '21 18:03 WinterSilence

Not really.

samdark avatar Mar 01 '21 20:03 samdark

@samdark

Not really.

what do you mean?

WinterSilence avatar Mar 01 '21 20:03 WinterSilence

I mean there are no such built-in validators.

samdark avatar Mar 03 '21 00:03 samdark

We have modified gii to do this instead about a years ago, and we've never looked back. It works as expected this way.

This is the same as @WinterSilence has shown.

                case Schema::TYPE_TINYINT:
                    if ($column->size == 1) {
                        $types['boolean'][] = $column->name;
                    } else {
                        $types['integer'][] = $column->name;
                    }
                    break;

It even has a benefit that you'll still be able to use $object->enabled = false and $object->enabled = 0. Both work fine.

michaelarnauts avatar May 28 '21 09:05 michaelarnauts

@michaelarnauts in_array($column->defaultValue, [0, 1]) is not strict check i.e. $column->defaultValue can be bool, string or numeric, it's added to mysql 5.*

WinterSilence avatar Mar 20 '22 12:03 WinterSilence

How are you going to know from the schema that a tinyint(1) column should be "just" a bolean? As mysql accepts 255 different values for a tinyint(1), such cols can be booleans but also "something" else.

Example: a tinyint(1) col for different states that can be 1,2,3,4,... is totally valid, but clearly not a boolean.

(My) conclusion: The reason why one want a boolean validator for this in many cases is quite clear to me, but I don't think it's a good idea to "guess" that it's a boolean based on a DB type that can also be "something else".

handcode avatar Oct 31 '23 08:10 handcode

How are you going to know from the schema that a tinyint(1) column should be "just" a bolean? As mysql accepts 255 different values for a tinyint(1), such cols can be booleans but also "something" else.

Example: a tinyint(1) col for different states that can be 1,2,3,4,... is totally valid, but clearly not a boolean.

(My) conclusion: The reason why one want a boolean validator for this in many cases is quite clear to me, but I don't think it's a good idea to "guess" that it's a boolean based on a DB type that can also be "something else".

Something else:

  • additional input field, where wrtite boolean fieldnames seperated by comma;
  • in boolena field use defined prefix or suffix.

uldisn avatar Oct 31 '23 08:10 uldisn

If you look at the mysql documentation, you can see that this data type can be -128...127 signed and 0...255 unsigned. tinyint(1) doesn't equal the boolean type and this change will potentially break a lot of projects

https://dev.mysql.com/doc/refman/8.0/en/integer-types.html

In gii generator model screen add input field, where write comma separated boolean field names

uldisn avatar Oct 31 '23 12:10 uldisn