MySQL bug in the DELETE clause - mysql

Has anyone experienced a situation like this?
I accidentally typed the DELETE command in the wrong syntax (MySQL version 8.0.22). The command should never have worked, but it not only worked, it also deleted all data from the table:
Syntax: DELETE FROM test WHERE 123456;
Note that neither the column name nor the conditional operator was specified, but even so the command was executed without errors and deleted all data from the table.
The code 123456 is an example but can be any code.
Test it on any version of MySQL:
CREATE TABLE `test` (
`cod` int NOT NULL AUTO_INCREMENT,
`name` varchar(50),
PRIMARY KEY (`cod`),
KEY `ix_tmp_autoinc` (`cod`)
);
INSERT INTO `test`
(`name`)
VALUES
('MySQL bug');
INSERT INTO `test`
(`name`)
VALUES
('MySQL bug 2');
DELETE FROM test WHERE 123456;
SELECT COUNT(*) FROM test;

This is a perfectly valid statement, absolutely in accordance with the syntax for the DELETE command described at DELETE statement. On the DELETE statement page it says that WHERE must be followed by a where_condition, which is described on the SELECT statement page. There we find that a where_condition can be either a Function and Operator, or it can be an Expression. Looking at the Expression page we find the following hierarchy:
expr
|
boolean_primary
|
predicate
|
bit_expr
|
simple_expr
|
literal
So a where_condition can be a literal, which is exactly what you gave it. It may not have been what you meant, and it may not have done what you intended, but from the standpoint of MySQL syntax it's perfectly legal.

What is playing out here are MySQL complex casting rules. When you ran the following query:
DELETE FROM test WHERE 123456;
MySQL expected a boolean expression following WHERE. It didn't find that, but it did instead find an integer literal. It turns out that MySQL will treat an integer literal as being "truthy," and so it will evaluate to true.

Related

A variable is cut in a wrong way but now sure why?

I'm writing a script that locates all branches of a specific repo that haven't received any commits for more than 6 months and deletes them (after notifying committers).
This script will run from Jenkins every week, will store all these branches in some MySQL database and then in the next run (after 1 week), will pull the relevant branch names from the database and will delete them.
I want to make sure that if for some reason the script is run twice on the same day, relevant branches will not get added again to the database, so I check it using a SQL query:
def insert_data(branch_name):
try:
connection = mysql.connector.connect(user=db_user,
host=db_host,
database=db_name,
passwd=db_pass)
cursor = connection.cursor(buffered=True)
insert_query = """insert into {0}
(
branch_name
)
VALUES
(
\"{1}\"
) where not exists (select 1 from {0} where branch_name = \"{1}\" and deletion_date is NULL) ;""".format(
db_table,
branch_name
)
cursor.execute(insert_query, multi=True)
connection.commit()
except Exception as ex:
print(ex)
finally:
cursor.close()
connection.close()
When I run the script, for some reason, the branch_name variable is cut in the middle and then the query that checks if the branch name already exists in the database fails:
1064 (42000): You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'where not exists (select 1 from branches_to_delete where branch_name = `AUT-1868' at line 8
So instead of checking for 'AUT-18681_designer_create_new_name_if_illegal_char_exist' it checks for 'AUT-1868' which doesn't exist in the database.
I've tried the following:
'{1}'
"{1}"
{1}
But to no avail.
What am I doing wrong?
Using WHERE statement in INSERT INTO query is illegal:
INSERT INTO `some_table`(`some_column`)
VALUES ('some_value') WHERE [some_condition]
So, the above example is not valid MySQL query. For prevent duplication of branch_name you should add unique index on your table like:
ALTER TABLE `table` ADD UNIQUE INDEX `unique_branch_name` (`branch_name`);
And after this you can use next query:
INSERT INTO `table` (`branch_name`) VALUES ('branch_name_1')
ON DUPLICATE KEY UPDATE `branch_name` = `branch_name`;
Pay attention: If your table have auto-increment id, it will be incremented on each insert attempt
Since MySQL 8.0 you can use JASON_TABLE function for generate pseudo table from your values filter it from already exists values and use it fro insert. Look here for example
I don't see anything wrong assuming the source of the branch_name is safe (you are not open to SQL Injection attacks), but as an experiment you might try:
insert_query = f"""insert into {db_table}(branch_name) VALUES(%s) where not exists
(select 1 from {db_table} where branch_name = %s and deletion_date is NULL)"""
cursor.execute(insert_query, (branch_name, branch_name))
I am using a prepared statement (which is also SQL Injection-attack safe) and thus passing the branch_name as a parameters to the execute method and have also removed the multi=True parameter.
Update
I feel like a bit of a dummy for missing what is clearly an illegal WHERE clause. Nevertheless, the rest of the answer suggesting the use of a prepared statement is advice worth following, so I will keep this posted.

MySQL 8 does not detect functional dependencies in select distinct queries

I create a table using this query:
create table a (
`id` int not null auto_increment,
b varchar(10),
primary key (`id`)
);
Executing
select distinct `id` from a order by `b`;
results in this error:
ERROR 3065 (HY000): Expression #1 of ORDER BY clause is not in SELECT list, references column 'portal.a.b' which is not in SELECT list; this is incompatible with DISTINCT
But if I change the query to
select `id` from a group by `id` order by `b`;
which is logically equivalent, it succeeds.
I'm using the official Docker image for MySQL and mysql --version displays
mysql Ver 8.0.12 for Linux on x86_64 (MySQL Community Server - GPL)
It seems that MySQL does not still detect functional dependencies in select distinct queries. Am I right? Are MySQL's developers going to fix this?
The opposite behaviour was actually reported as a bug and fixed in MySQL 5.7.5:
Several issues related to the ONLY_FULL_GROUP_BY SQL mode were corrected:
With ONLY_FULL_GROUP_BY enabled, some valid queries were rejected if the accessed table was replaced by a view.
Queries of the form SELECT DISTINCT col1 ... ORDER BY col2 qualify as forbidden by SQL2003 (hidden ORDER BY columns combined with DISTINCT), but were not rejected with the ONLY_FULL_GROUP_BY SQL mode enabled.
Also, the documentation explicitly states that this is the intended behaviour:
To prevent this problem, a query that has DISTINCT and ORDER BY is rejected as invalid if any ORDER BY expression does not satisfy at least one of these conditions:
The expression is equal to one in the select list
All columns referenced by the expression and belonging to the query's selected tables are elements of the select list
without mentioning functional dependencies. In contrast to group by, the relevant error message does not reference functional dependencies either.
While the optional feature T301 Functional dependencies in the sql standard does modify the conformity rules for group by (and others), it doesn't change any restriction on order by plus distinct, which means it is still forbidden.
This comes from the introduction of a new SQL MODE from MySQL 5.7 : ONLY_FULL_GROUP_BY
The goal of this mode was to make MySQL behave as the SQL standard on GROUP BY queries.
This mode is activated on your database. You can either disable it, or adapt your queries to respect standards, which is probably the best thing to do.
This will throw your error:
SET sql_mode = 'ONLY_FULL_GROUP_BY';
drop table a;
create table a (
`id` int not null auto_increment,
b varchar(10),
primary key (`id`)
);
INSERT INTO a VALUES (NULL, 'aaaa');
INSERT INTO a VALUES (NULL, 'bbbb');
select distinct `id` from a order by `b`;
If you remove the ONLY_FULL_GROUP_BY mode, you get your results :
SET sql_mode = '';
drop table a;
create table a (
`id` int not null auto_increment,
b varchar(10),
primary key (`id`)
);
INSERT INTO a VALUES (NULL, 'aaaa');
INSERT INTO a VALUES (NULL, 'bbbb');
select distinct `id` from a order by `b`;
You can see it live on Rextester

What does mean \g at the end of a MySQL statement? And how do I fix queries not running?

I have an auto-generated SQL script to run on 5.6.17-157.2.
It worked fine on 5.5.33-1.17.1.
Every SQL statement shows \g at the end. For example
CREATE TABLE articoli
(
ID INT,
titolo LONGTEXT,
sottotitolo LONGTEXT,
descrizione LONGTEXT,
note LONGTEXT,
nomeopzione1 LONGTEXT,
nomeopzione2 LONGTEXT,
nomeopzione3 LONGTEXT,
pagina CHAR(100),
sottopagina SMALLINT,
plain_titolo CHAR(200),
plain_sottotitolo CHAR(200),
nomeopzione4 LONGTEXT,
KEY (ID),
KEY (pagina),
KEY (sottopagina)
);\g
What changed between the two version to break query execution? How can I tell 5.6 to accept \g and don't care?
I can't just change the SQL. It's auto-generated code that must run as final step of a monstrous software abomination "daily update" (https://serverfault.com/questions/458340/euro-character-messed-up-during-ftp-transfer)
[Update] Better change the question: it's not enough to know what is that. I need to get the queries running.
MySQL already does accept \g, but it must follow a SQL statement.
The \g is basically the same as ; That is, it is the terminator for a statement and that means send it to the server for parsing and execution.
Your sample shows a create table statement terminated by both a semicolon and \g. This results in the create table statement running, because it has a semicolon. But then it tries to run another statement terminator without a statement.
Try this:
mysql> ;
ERROR:
No query specified
Of course there was no query specified, this just shows a semicolon with no query.
It's the same with a line with nothing but \g:
mysql> \g
ERROR:
No query specified
And if you run a real query, and then a redundant terminator of either type, you get something similar. It runs the first query, then fails on the empty query:
mysql> select 123; ;
+-----+
| 123 |
+-----+
| 123 |
+-----+
ERROR:
No query specified
mysql> select 123; \g
+-----+
| 123 |
+-----+
| 123 |
+-----+
ERROR:
No query specified
I don't know what you mean about this code is generated and you can't change it. You'll have to, because what you've got won't work.
I would suggest you strip out the \g from your file before trying to run it. Here's an example of a file containing the bad empty-query pattern, and using sed to remove the redundant \g:
$ cat > bad.sql
select 123; \g
$ sed -e 's/\\g//g' bad.sql
select 123;
http://dev.mysql.com/doc/refman/5.1/en/mysql-commands.html
go - (\g) - Send command to mysql server.
(\g) -> go
Send command to mysql server.

MySQL: unexpected SELECT result after INSERT into table with autoincrement column

I'm seeing a weird behavior when I INSERT some data into a table and then run a SELECT query on the same table. This table has an auto-increment primary key (uid), and this problem occurs when I try to then select results where 'uid IS NULL'.
I've golfed this down to the following SQL commands:
DROP TABLE IF EXISTS test_users;
CREATE TABLE test_users (uid INTEGER PRIMARY KEY AUTO_INCREMENT, name varchar(20) NOT NULL);
INSERT INTO test_users(name) values('foo');
SELECT uid FROM test_users WHERE uid IS NULL;
SELECT uid FROM test_users WHERE uid IS NULL; -- no output from this query
I'd expect SELECT uid FROM test_users WHERE uid IS NULL to never return anything, but it does, sometimes. Here's what I've found:
Version of MySQL/MariaDB seems to matter. The machine having this problem is running MySQL 5.1.73 (CentOS 6.5, both 32-bit and 64-bit). My other machine running 5.5.37-MariaDB (Fedora 19, 64-bit). Both running default configs, aside from being configured to use MyISAM tables.
Only the first SELECT query after the INSERT is affected.
If I specify a value for uid rather than let it auto-increment, then it's fine.
If I disconnect and reconnect between the INSERT and SELECT, then I get the expected no results. This is easiest to see in something like Perl where I manage the connection object. I have a test script demonstrating this at https://gist.github.com/avuserow/1c20cc03c007eda43c82
This behavior is by design.
It's evidently equivalent to SELECT * FROM t1 WHERE id = LAST_INSERT_ID(); which would also work only from the connection where you just did the insert, exactly as you described.
It's apparently a workaround that you can use in some environments that make it difficult to fetch the last inserted (by your connection) row's auto-increment value in a more conventional way.
To be precise, it's actually the auto_increment value assigned to the first row inserted by your connection's last insert statement. That's the same thing when you only inserted one row, but it's not the same thing when you insert multiple rows with a single insert statement.
http://dev.mysql.com/doc/connector-odbc/en/connector-odbc-usagenotes-functionality-last-insert-id.html

Why does this table name require back-ticks?

I am seeing a curious name dependency in the following MySQL table definition. When I code the table as shown, it seems to break MySQL. When I perform "select * from dictionary_pair_join" from MySQLQueryBrowser, the status bar says "No resultset returned" -- no column names and no errors. When I insert a row into the table, the status bar says "1 row affected by the last command, no resultset returned", and subsequent selects give the same "No resultset returned" response.
When I enclose the tablename in backticks in the "select" statement, all works fine. Surely there are no mysql entities named "dictionary_pair_join"!
Here is the table definition:
DROP TABLE IF EXISTS dictionary_pair_join;
CREATE TABLE dictionary_pair_join (
version_id int(11) UNSIGNED NOT NULL default '0',
pair_id int(11) UNSIGNED default NULL,
KEY (version_id),
KEY (pair_id)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
Here is the broken select statement:
select * from dictionary_pair_join;
Here is its working counterpart:
select * from `dictionary_pair_join`;
Why are backticks required in the select statement?
Update: This also fails in the Python mysqldb interface, which is why I started looking at it. I can put the backticks into my Python "select" generators, but I was hoping this was some stupid and easily-changed nit. I suppose I can also find a different name.
I've uprated the comments from Quassnoi and Software Guy, together they've persuaded me that it's just a bug in mysql/mysqldb/mysqlquerybrowser.
I changed the table name (to "dictionary_pair_cons") and the problem went away, in both the query browser and mysqldb.