Enum value is not set, BUT is not null - mysql

I have a row in a table that is an enum type, but can also be NULL by default.
Empty string ('') is not one of the possible enum value, and yet, after some time, I found out that all of the entries I thought to be null were actually set to an empty string.
Fixing this wasn't a problem. But, I'm willing to know how this could even happen in the first place, -to make sure I don't get any of these ever again,- but so far I haven't been able to recreate new entries with an empty string as value.
What could I have that would cause an enum value to be set neither to null nor any of the possible values?

If someone tries to insert a value to the enum that is not in its defined list of values, and strict mode is not enforced, then the value will be truncated to ''.
mysql> create table t (e enum('a','b','c'));
mysql> insert into t set e='d';
ERROR 1265 (01000): Data truncated for column 'e' at row 1
mysql> set session sql_mode=''; -- disable strict mode
mysql> insert into t set e='d';
Query OK, 1 row affected, 1 warning (0.01 sec)
mysql> show warnings;
+---------+------+----------------------------------------+
| Level | Code | Message |
+---------+------+----------------------------------------+
| Warning | 1265 | Data truncated for column 'e' at row 1 |
+---------+------+----------------------------------------+
mysql> select * from t;
+------+
| e |
+------+
| |
+------+
https://dev.mysql.com/doc/refman/5.7/en/sql-mode.html says:
Strict mode produces an error for attempts to create a key that exceeds the maximum key length. When strict mode is not enabled, this results in a warning and truncation of the key to the maximum key length.
It doesn't say so explicitly, but "exceeds the maximum length" also includes "not an element of the enum."

Related

What mean by char(40)?

I have a mysql table which has a data structure as follows,
create table data(
....
name char(40) NULL,
...
)
But I could insert names which has characters more than 40 in to name field. Can someone explain what is the actual meaning of char(40)?
You cannot insert a string of more than 40 characters in a column defined with the type CHAR(40).
If you run MySQL in strict mode, you will get an error if you try to insert a longer string.
mysql> create table mytable ( c char(40) );
Query OK, 0 rows affected (0.01 sec)
mysql> insert into mytable (c) values ('Now is the time for all good men to come to the aid of their country.');
ERROR 1406 (22001): Data too long for column 'c' at row 1
If you run MySQL in non-strict mode, the insert will succeed, but only the first 40 characters of your string is stored in the column. The characters beyond 40 are lost, and you get no error.
mysql> set sql_mode='';
Query OK, 0 rows affected (0.00 sec)
mysql> insert into mytable (c) values ('Now is the time for all good men to come to the aid of their country.');
Query OK, 1 row affected, 1 warning (0.01 sec)
mysql> show warnings;
+---------+------+----------------------------------------+
| Level | Code | Message |
+---------+------+----------------------------------------+
| Warning | 1265 | Data truncated for column 'c' at row 1 |
+---------+------+----------------------------------------+
1 row in set (0.00 sec)
mysql> select c from mytable;
+------------------------------------------+
| c |
+------------------------------------------+
| Now is the time for all good men to come |
+------------------------------------------+
1 row in set (0.00 sec)
I recommend operating MySQL in strict mode (strict mode is the default since MySQL 5.7). I would prefer to get an error instead of losing data.

MYSQL Strict Mode

For STRICT_ALL_TABLES, MySQL returns an error and ignores the rest of the rows. However, because the earlier rows have been inserted or updated, the result is a partial update. To avoid this, use single-row statements, which can be aborted without changing the table.
For STRICT_TRANS_TABLES, MySQL converts an invalid value to the closest valid value for the column and inserts the adjusted value. If a value is missing, MySQL inserts the implicit default value for the column data type. In either case, MySQL generates a warning rather than an error and continues processing the statement. Implicit defaults are described in Section 11.6, “Data Type Default Values”.
I tried to set STRICT_TRANS_TABLE and tried inserting a invalid record without a transaction. Despite as documented it showed a error.
mysql> show create table mydemo;
+--------+-------------------------------------------------------------------------------------------------------------------------------------------------+
| Table | Create Table |
+--------+-------------------------------------------------------------------------------------------------------------------------------------------------+
| mydemo | CREATE TABLE `mydemo` (
`pk` int(20) NOT NULL,
`name` varchar(10) DEFAULT NULL,
PRIMARY KEY (`pk`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1 |
+--------+-------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
mysql> SELECT * FROM mydemo;
+----+------+
| pk | name |
+----+------+
| 1 | Test |
+----+------+
1 row in set (0.00 sec)
mysql> SELECT ##SESSION.sql_mode;
+-------------------------------------------------------------------------------------------------------------------------------------------+
| ##SESSION.sql_mode |
+-------------------------------------------------------------------------------------------------------------------------------------------+
| ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION |
+-------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)
mysql> insert into mydemo values (2, "abcdefghijklmn");
ERROR 1406 (22001): Data too long for column 'name' at row 1
mysql> select ##SESSION.autocommit;
+----------------------+
| ##SESSION.autocommit |
+----------------------+
| 1 |
+----------------------+
1 row in set (0.00 sec)
What do they mean by Non transaction table ?
In the above, why do it shows error instead of warning ?
What if both the modes are set down ?
Your table type is innodb, which is a transactional table type (supports transactions), therefore te paragraph before the two you quoted applies:
For transactional tables, an error occurs for invalid or missing values in a data-change statement when either STRICT_ALL_TABLES or STRICT_TRANS_TABLES is enabled. The statement is aborted and rolled back.
The two paragraphs you quoted applies to non-transactional tables, such as myisam.

strict mode to avoid type conversion

Is there any sql mode that will return an error instead of implicitly converting the string to integer?
mysql> select * from todel ;
+------+--------+
| id | name |
+------+--------+
| 1 | abc |
| 2 | xyz |
| 0 | ABCxyz |
+------+--------+
3 rows in set (0.00 sec)
I expect an error message instead of a row with id 0
mysql> select * from todel where id = 'abc';
+------+--------+
| id | name |
+------+--------+
| 0 | ABCxyz |
+------+--------+
1 row in set, 1 warning (0.00 sec)
mysql> show warnings;
+---------+------+-----------------------------------------+
| Level | Code | Message |
+---------+------+-----------------------------------------+
| Warning | 1292 | Truncated incorrect DOUBLE value: 'abc' |
+---------+------+-----------------------------------------+
1 row in set (0.01 sec)
I understand your concerns, but it's for this very reason you should never have an id set to 0. In the long run I think you should reconsider your table rows before the behavior which isn't a problem in ideal situations. I haven't found anything relevant to this through a little searches, and that's probably because it's probably not a problem unless you make it one.
Apart from that, you could read relevant column data and act accordingly in php/whatev. From the table COLUMNS in information_schema, you can filter by TABLE_SCHEMA (database), TABLE_NAME and COLUMN_NAME to get DATATYPE (double). If the column you're changing has a certain DATATYPE, let the script give error before running the MySQL query.
Another way to do it would simply be to convert input before parsing:
if ( ! is_numeric($id))
$id = 'NULL';
To prevent incorrect INSERTs or UPDATEs, you already have that mode.
In the end I can't come up with many practical ways that this strict mode you're after would benefit the MySQL users.
You can use STRICT_ALL_TABLES sql mode:
set ##GLOBAL.sql_mode = "STRICT_ALL_TABLES";
set ##SESSION.sql_mode = "STRICT_ALL_TABLES";
However it works just on write operations:
MariaDB [(none)]> insert into test.test values ( "abc", "lol" );
--------------
insert into test.test values ( "abc", "lol" )
--------------
ERROR 1366 (22007): Incorrect integer value: 'abc' for column 'id' at row 1
There is no such thing to disable implicit conversions for read queries; instead you can just check if there are warnings and if yes, just free the result, abort the statement, and threat those warnings as errors.

mysql, prepared statements, and automatic type conversion

I am getting different results performing the exact same query using regular statements and prepared statements, and I think it's a type conversion bug.
mysql> show columns from server where field = "vlan";
+-------------+--------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------------+--------+------+-----+---------+-------+
| vlan | int(5) | YES | MUL | NULL | |
+-------------+--------+------+-----+---------+-------+
mysql> select hostname from server where `vlan` = '184.182' limit 1;
Empty set (0.00 sec)
mysql> prepare stupid from "select hostname from server where `vlan` = ? limit 1";
Query OK, 0 rows affected (0.00 sec)
Statement prepared
mysql> set #vlan = '184.182';
Query OK, 0 rows affected (0.00 sec)
mysql> execute stupid using #vlan;
+-------------------+
| hostname |
+-------------------+
| web20.servers.com |
+-------------------+
1 row in set (0.00 sec)
the real value of vlan is 184
it looks like the way mysql is handling type conversions is different for prepared statements and regular statements? does that make sense? how do i fix this?
The expected data type of prepared statement parameters is determined upon statement preparation, and type conversion to that data type takes place prior to statement execution.
In your example, an integer parameter is expected; therefore the provided string is cast to an integer (184) before the statement is executed, and the comparison between the integer column vlan and the parameter is successful for the matching record.
The "regular" statement, by contrast, compares the integer column with a string; therefore the arguments are compared as floating point numbers, and no record has a matching vlan.
To avoid this situation, ensure that the data type cannot be determined upon preparation (or that the determined data type does not lose any information) - for example:
prepare not_so_stupid from
"select hostname from server where `vlan` = CAST(? AS CHAR) limit 1"
;

Mysql 4 vs Mysql 5 auto-increment field on insert

I've learned this along the way but can't figure out where I read it or heard it, as there is nothing I have found online supporting it, but I remember that when upgrading from mysql4.x to mysql5.x, one of the required changes was that the auto-increment field for inserts had to change from '' to NULL if it was included.
I know its not required to have in the insert anyway, but just for point of interest...
Mysql 4.x would allow:
INSERT INTO TABLE (table_id, name, location) VALUES ('', 'john', 'NY');
But mysql 5.x had to have:
INSERT INTO TABLE (table_id, name, location) VALUES (NULL, 'john', 'NY');
I can't find any information on mysql's site to support this, but I know for a fact it throws an error in mysql 5.x and know it worked with '' in 4.x, but where is this documented?
Both the 4.1 and 5.0 docs state that 0 or NULL is required:
No value was specified for the
AUTO_INCREMENT column, so MySQL
assigned sequence numbers
automatically. You can also explicitly
assign NULL or 0 to the column to
generate sequence numbers.
It does not matter, mysql internally still convert to integer
mysql> CREATE TABLE some_test ( id int(10) unsigned NOT NULL auto_increment, primary key(id));
Query OK, 0 rows affected (0.00 sec)
mysql> insert into some_test values ('');
Query OK, 1 row affected, 1 warning (0.00 sec)
mysql> show warnings;
+---------+------+------------------------------------------------------+
| Level | Code | Message |
+---------+------+------------------------------------------------------+
| Warning | 1264 | Out of range value adjusted for column 'id' at row 1 |
+---------+------+------------------------------------------------------+
1 row in set (0.00 sec)
mysql> select * from some_test;
+----+
| id |
+----+
| 1 |
+----+
1 row in set (0.00 sec)
However, I will suggest use 0 to avoid this warning