Bug #111304 The Query Rewrite Plugin mixes-up LIMIT and OFFSET.

Submitted: 6 Jun 2023 21:15 Modified: 8 Jun 2023 4:00
Reporter: Jean-François Gagné Email Updates:
Status: Verified Impact on me:
None 
Category:MySQL Server: Optimizer Severity:S2 (Serious)
Version:8.0.33, 5.7.42 OS:Any
Assigned to: CPU Architecture:Any

[6 Jun 2023 21:15] Jean-François Gagné
Description:
Hi

(Using the Optimizer category as other bugs for the Rewrite Plugin are under this one, maybe there should be a Query Rewrite Plugin Category)

The Query Rewrite Plugin mixes-up LIMIT and OFFSET.  On a query with LIMIT 1 OFFSET 2, the plugin rewrites to LIMIT 2 OFFSET 1.  See How to repeat for details.

Note that if using the "LIMIT 2,1" syntax instead of "LIMIT 1 OFFSET 2" syntax, things work well, but this means the query to rewrite also needs to be using the "LIMIT 2,1" syntax, which makes impossible to rewrite a query using the "LIMIT 1 OFFSET 2" syntax.

Severity set as S2/Serious because this is an "incorrect query result".

Many thanks for looking into this,

Jean-François Gagné

How to repeat:
# Create a 8.0.33 sandbox.
dbdeployer deploy single mysql_8.0.33

# Load data in the sandbox.
./use <<< "create database test_jfg"
./use test_jfg <<< "create table t(id int primary key, v int)"
./use test_jfg <<< "insert into t values (1,1),(2,2),(3,3)"

# Load the rewrite plugin.
cat ~/opt/mysql/mysql_8.0.33/share/install_rewriter.sql | ./use

# The query below returns 1 row as expected (limit 1).
./use <<< "select id, v from test_jfg.t order by id limit 1 offset 2"
id      v
3       3

# Let's make above query return v+1 instead of v.
./use <<< "
INSERT INTO query_rewrite.rewrite_rules (pattern, replacement)
VALUES('select id, v from test_jfg.t order by id limit ? offset ?', 'select id, v + 1 from test_jfg.t order by id limit ? offset ?');
CALL query_rewrite.flush_rewrite_rules()
"

# Now the query returns 2 rows, which is unexpected.
./use test_jfg <<< "select id, v from test_jfg.t order by id limit 1 offset 2"
id      v + 1
2       3
3       4

# And in mysql client, we can see that LIMIT and OFFSET has been swapped.
mysql [localhost:8033] {msandbox} ((none)) > select id, v from test_jfg.t order by id limit 1 offset 2;
+----+-------+
| id | v + 1 |
+----+-------+
|  2 |     3 |
|  3 |     4 |
+----+-------+
2 rows in set, 1 warning (0.00 sec)

mysql [localhost:8033] {msandbox} ((none)) > show warnings;
+-------+------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Level | Code | Message                                                                                                                                                                  |
+-------+------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Note  | 1105 | Query 'select id, v from test_jfg.t order by id limit 1 offset 2' rewritten to 'select id, v + 1 from test_jfg.t order by id limit 2 offset 1' by a query rewrite plugin |
+-------+------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

---

# Same for 5.7.42.
dbdeployer deploy single mysql_5.7.42

./use <<< "create database test_jfg"
./use test_jfg <<< "create table t(id int primary key, v int)"
./use test_jfg <<< "insert into t values (1,1),(2,2),(3,3)"

cat ~/opt/mysql/mysql_5.7.42/share/install_rewriter.sql | ./use

./use <<< "select id, v from test_jfg.t order by id limit 1 offset 2"
id      v
3       3

./use <<< "
INSERT INTO query_rewrite.rewrite_rules (pattern, replacement)
VALUES('select id, v from test_jfg.t order by id limit ? offset ?', 'select id, v + 1 from test_jfg.t order by id limit ? offset ?');
CALL query_rewrite.flush_rewrite_rules()
"

./use <<< "select id, v from test_jfg.t order by id limit 1 offset 2"
id      v + 1
2       3
3       4

mysql [localhost:5742] {msandbox} ((none)) > select id, v from test_jfg.t order by id limit 1 offset 2;
+----+-------+
| id | v + 1 |
+----+-------+
|  2 |     3 |
|  3 |     4 |
+----+-------+
2 rows in set, 1 warning (0.00 sec)

mysql [localhost:5742] {msandbox} ((none)) > show warnings;
+-------+------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Level | Code | Message                                                                                                                                                                  |
+-------+------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| Note  | 1105 | Query 'select id, v from test_jfg.t order by id limit 1 offset 2' rewritten to 'select id, v + 1 from test_jfg.t order by id limit 2 offset 1' by a query rewrite plugin |
+-------+------+--------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
1 row in set (0.00 sec)

---

# But the other syntax works for both 8.0.33 and 5.7.42.
./use <<< "select id, v from test_jfg.t order by id limit 2,1"
id      v
3       3

./use <<< "
INSERT INTO query_rewrite.rewrite_rules (pattern, replacement)
VALUES('select id, v from test_jfg.t order by id limit ?,?', 'select id, v + 1 from test_jfg.t order by id limit ?,?');
CALL query_rewrite.flush_rewrite_rules()
"

./use <<< "select id, v from test_jfg.t order by id limit 2,1"
id      v + 1
3       4
[6 Jun 2023 21:25] Jean-François Gagné
Note that a work-around for this is to use below rewrite rule (using "limit ?, ?" instead of "limit ? offset ?" in the replacement), I am not sure it is satisfying.

./use <<< "
truncate query_rewrite.rewrite_rules;
INSERT INTO query_rewrite.rewrite_rules (pattern, replacement)
VALUES('select id, v from test_jfg.t order by id limit ? offset ?', 'select id, v + 1 from test_jfg.t order by id limit ?, ?');
CALL query_rewrite.flush_rewrite_rules()
"

./use <<< "select id, v from test_jfg.t order by id limit 1 offset 2"
id      v + 1
3       4
[8 Jun 2023 4:00] MySQL Verification Team
Hello Jean-François,

Thank you for the report and feedback.

regards,
Umesh

Portions of this website are copyright © 2001, 2002 The PHP Group

Page generated in 0.041 sec. using MySQL 8.4.7-u6-cloud

Timestamp references displayed by the system are UTC.
Content reproduced on this site is the property of the respective copyright holders.
It is not reviewed in advance by Oracle and does not necessarily represent the opinion of Oracle or any other party.