-
Notifications
You must be signed in to change notification settings - Fork 5.9k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
parser: fix the compatibility problem of UNION
statement
#6335
Changes from 21 commits
dc2868d
aa67355
b85bb2d
e6bf537
de21834
84edf85
74c27c2
61e2c5d
0a5569c
5a4048c
24975c2
b67a426
64fcd32
5f66d0e
7c06a20
a0bc389
a180997
b95d9db
a374b36
500d82f
d3a2dac
ae6ebef
b815e95
06363c3
e9d5880
5cc74fb
5b0ed35
c5129e3
4ff00f9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -949,6 +949,8 @@ func (s *testSuite) TestUnion(c *C) { | |
tk.MustExec("create table t(a int)") | ||
tk.MustExec("insert into t value(0),(0)") | ||
tk.MustQuery("select 1 from (select a from t union all select a from t) tmp").Check(testkit.Rows("1", "1", "1", "1")) | ||
tk.MustQuery("select 10 as a from dual union select a from t order by a desc limit 1 ").Check(testkit.Rows("10")) | ||
tk.MustQuery("select -10 as a from dual union select a from t order by a limit 1 ").Check(testkit.Rows("-10")) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This test will cause a CI error. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @ciscoxll The test failure seems not caused by this test. I can pass test on my dev machine using |
||
tk.MustQuery("select count(1) from (select a from t union all select a from t) tmp").Check(testkit.Rows("4")) | ||
|
||
_, err := tk.Exec("select 1 from (select a from t limit 1 union all select a from t limit 1) tmp") | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -699,6 +699,9 @@ import ( | |
SelectStmtFieldList "SELECT statement field list" | ||
SelectStmtLimit "SELECT statement optional LIMIT clause" | ||
SelectStmtOpts "Select statement options" | ||
SelectStmtPart1 "SELECT statement part 1" | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What's the difference between part1/2/3? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. They are syntax segments that abstracted from There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How about changing to a better name? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @tiancaiamao agree |
||
SelectStmtPart2 "SELECT statement part 2" | ||
SelectStmtPart3 "SELECT statement part 3" | ||
SelectStmtGroup "SELECT statement optional GROUP BY clause" | ||
ShowTargetFilterable "Show target that can be filtered by WHERE or LIKE" | ||
ShowDatabaseNameOpt "Show tables/columns statement database name option" | ||
|
@@ -4075,96 +4078,107 @@ RollbackStmt: | |
$$ = &ast.RollbackStmt{} | ||
} | ||
|
||
SelectStmt: | ||
"SELECT" SelectStmtOpts SelectStmtFieldList OrderByOptional SelectStmtLimit SelectLockOpt | ||
SelectStmtPart1: | ||
"SELECT" SelectStmtOpts SelectStmtFieldList | ||
{ | ||
st := &ast.SelectStmt { | ||
SelectStmtOpts: $2.(*ast.SelectStmtOpts), | ||
Distinct: $2.(*ast.SelectStmtOpts).Distinct, | ||
Fields: $3.(*ast.FieldList), | ||
LockTp: $6.(ast.SelectLockType), | ||
} | ||
$$ = st | ||
} | ||
|
||
SelectStmtPart2: | ||
SelectStmtPart1 FromDual WhereClauseOptional | ||
{ | ||
st := $1.(*ast.SelectStmt) | ||
lastField := st.Fields.Fields[len(st.Fields.Fields)-1] | ||
if lastField.Expr != nil && lastField.AsName.O == "" { | ||
src := parser.src | ||
var lastEnd int | ||
if $4 != nil { | ||
lastEnd = yyS[yypt-2].offset-1 | ||
} else if $5 != nil { | ||
lastEnd = yyS[yypt-1].offset-1 | ||
} else if $6 != ast.SelectLockNone { | ||
lastEnd = yyS[yypt].offset-1 | ||
} else { | ||
lastEnd = len(src) | ||
if src[lastEnd-1] == ';' { | ||
lastEnd-- | ||
} | ||
} | ||
lastField.SetText(src[lastField.Offset:lastEnd]) | ||
} | ||
if $4 != nil { | ||
st.OrderBy = $4.(*ast.OrderByClause) | ||
lastEnd := yyS[yypt-1].offset-1 | ||
lastField.SetText(parser.src[lastField.Offset:lastEnd]) | ||
} | ||
if $5 != nil { | ||
st.Limit = $5.(*ast.Limit) | ||
if $3 != nil { | ||
st.Where = $3.(ast.ExprNode) | ||
} | ||
$$ = st | ||
} | ||
| "SELECT" SelectStmtOpts SelectStmtFieldList FromDual WhereClauseOptional SelectStmtLimit SelectLockOpt | ||
|
||
|
||
SelectStmtPart3: | ||
SelectStmtPart1 "FROM" | ||
TableRefsClause WhereClauseOptional SelectStmtGroup HavingClause | ||
{ | ||
st := &ast.SelectStmt { | ||
SelectStmtOpts: $2.(*ast.SelectStmtOpts), | ||
Distinct: $2.(*ast.SelectStmtOpts).Distinct, | ||
Fields: $3.(*ast.FieldList), | ||
LockTp: $7.(ast.SelectLockType), | ||
st := $1.(*ast.SelectStmt) | ||
st.From = $3.(*ast.TableRefsClause) | ||
if st.SelectStmtOpts.TableHints != nil { | ||
st.TableHints = st.SelectStmtOpts.TableHints | ||
} | ||
lastField := st.Fields.Fields[len(st.Fields.Fields)-1] | ||
if lastField.Expr != nil && lastField.AsName.O == "" { | ||
lastEnd := yyS[yypt-3].offset-1 | ||
lastEnd := parser.endOffset(&yyS[yypt-4]) | ||
lastField.SetText(parser.src[lastField.Offset:lastEnd]) | ||
} | ||
if $4 != nil { | ||
st.Where = $4.(ast.ExprNode) | ||
} | ||
if $5 != nil { | ||
st.Where = $5.(ast.ExprNode) | ||
st.GroupBy = $5.(*ast.GroupByClause) | ||
} | ||
if $6 != nil { | ||
st.Limit = $6.(*ast.Limit) | ||
st.Having = $6.(*ast.HavingClause) | ||
} | ||
$$ = st | ||
} | ||
| "SELECT" SelectStmtOpts SelectStmtFieldList "FROM" | ||
TableRefsClause WhereClauseOptional SelectStmtGroup HavingClause OrderByOptional | ||
SelectStmtLimit SelectLockOpt | ||
|
||
SelectStmt: | ||
SelectStmtPart1 OrderByOptional SelectStmtLimit SelectLockOpt | ||
{ | ||
opts := $2.(*ast.SelectStmtOpts) | ||
st := &ast.SelectStmt{ | ||
SelectStmtOpts: $2.(*ast.SelectStmtOpts), | ||
Distinct: opts.Distinct, | ||
Fields: $3.(*ast.FieldList), | ||
From: $5.(*ast.TableRefsClause), | ||
LockTp: $11.(ast.SelectLockType), | ||
} | ||
if opts.TableHints != nil { | ||
st.TableHints = opts.TableHints | ||
} | ||
st := $1.(*ast.SelectStmt) | ||
st.LockTp = $4.(ast.SelectLockType) | ||
lastField := st.Fields.Fields[len(st.Fields.Fields)-1] | ||
if lastField.Expr != nil && lastField.AsName.O == "" { | ||
lastEnd := parser.endOffset(&yyS[yypt-7]) | ||
lastField.SetText(parser.src[lastField.Offset:lastEnd]) | ||
src := parser.src | ||
var lastEnd int | ||
if $2 != nil { | ||
lastEnd = yyS[yypt-2].offset-1 | ||
} else if $3 != nil { | ||
lastEnd = yyS[yypt-1].offset-1 | ||
} else if $4 != ast.SelectLockNone { | ||
lastEnd = yyS[yypt].offset-1 | ||
} else { | ||
lastEnd = len(src) | ||
if src[lastEnd-1] == ';' { | ||
lastEnd-- | ||
} | ||
} | ||
lastField.SetText(src[lastField.Offset:lastEnd]) | ||
} | ||
if $6 != nil { | ||
st.Where = $6.(ast.ExprNode) | ||
if $2 != nil { | ||
st.OrderBy = $2.(*ast.OrderByClause) | ||
} | ||
if $7 != nil { | ||
st.GroupBy = $7.(*ast.GroupByClause) | ||
if $3 != nil { | ||
st.Limit = $3.(*ast.Limit) | ||
} | ||
if $8 != nil { | ||
st.Having = $8.(*ast.HavingClause) | ||
$$ = st | ||
} | ||
| SelectStmtPart2 SelectStmtLimit SelectLockOpt | ||
{ | ||
st := $1.(*ast.SelectStmt) | ||
st.LockTp = $3.(ast.SelectLockType) | ||
if $2 != nil { | ||
st.Limit = $2.(*ast.Limit) | ||
} | ||
if $9 != nil { | ||
st.OrderBy = $9.(*ast.OrderByClause) | ||
$$ = st | ||
} | ||
| SelectStmtPart3 OrderByOptional SelectStmtLimit SelectLockOpt | ||
{ | ||
st := $1.(*ast.SelectStmt) | ||
st.LockTp = $4.(ast.SelectLockType) | ||
if $2 != nil { | ||
st.OrderBy = $2.(*ast.OrderByClause) | ||
} | ||
if $10 != nil { | ||
st.Limit = $10.(*ast.Limit) | ||
if $3 != nil { | ||
st.Limit = $3.(*ast.Limit) | ||
} | ||
$$ = st | ||
} | ||
|
@@ -4591,14 +4605,53 @@ SelectLockOpt: | |
|
||
// See https://dev.mysql.com/doc/refman/5.7/en/union.html | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why remove this comment? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. By mistake.. |
||
UnionStmt: | ||
UnionClauseList "UNION" UnionOpt SelectStmt | ||
UnionClauseList "UNION" UnionOpt SelectStmtPart1 OrderByOptional SelectStmtLimit SelectLockOpt | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Shall we extract a common There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
{ | ||
st := $4.(*ast.SelectStmt) | ||
union := $1.(*ast.UnionStmt) | ||
union.Distinct = union.Distinct || $3.(bool) | ||
lastSelect := union.SelectList.Selects[len(union.SelectList.Selects)-1] | ||
endOffset := parser.endOffset(&yyS[yypt-2]) | ||
endOffset := parser.endOffset(&yyS[yypt-5]) | ||
parser.setLastSelectFieldText(lastSelect, endOffset) | ||
union.SelectList.Selects = append(union.SelectList.Selects, $4.(*ast.SelectStmt)) | ||
union.SelectList.Selects = append(union.SelectList.Selects, st) | ||
if $5 != nil { | ||
union.OrderBy = $5.(*ast.OrderByClause) | ||
} | ||
if $6 != nil { | ||
union.Limit = $6.(*ast.Limit) | ||
} | ||
$$ = union | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. $7 is not used? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes. Since the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we need to add SelectLockOpt on both side of the union in this case. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @tiancaiamao what if the left There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. begin txn1 begin txn2 commit txn1 (If for update works on union, this operation should fail in TiDB) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should promote to lock mode either The select lock have something to do with transaction isolation level. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
@tiancaiamao what this mean ? Fail to get your point..... @.@ BTW, I test these cases in MySQL 5.7. mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from t union select * from t2 for update;
+----+----+---------+
| id | tn | type |
+----+----+---------+
| 1 | 5 | cat |
| 2 | 6 | dog |
| 3 | 7 | duck |
| 4 | 29 | chicken |
| 4 | 99 | chicken |
+----+----+---------+
5 rows in set (0.01 sec) Then, execute this in the second tx: mysql> update t set tn = 31 where id = 4; Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> update t2 set tn = 31 where id = 4;
ERROR 1205 (HY000): Lock wait timeout exceeded; try restarting transaction the Another condition: mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> select * from t union select * from t2 limit 100 for update; +----+-----+---------+
| id | tn | type |
+----+-----+---------+
| 1 | 5 | cat |
| 2 | 6 | dog |
| 3 | 7 | duck |
| 4 | 187 | chicken |
+----+-----+---------+
4 rows in set (0.01 sec) Then, execute this in the second tx: mysql> begin;
Query OK, 0 rows affected (0.00 sec)
mysql> update t2 set tn = 1 where id = 1; Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> update t set tn = 1 where id = 1;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0 Both Update works. The There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Okay, there is some subtle difference about the transaction isolation between TiDB and MySQL. |
||
} | ||
| UnionClauseList "UNION" UnionOpt SelectStmtPart2 SelectStmtLimit SelectLockOpt | ||
{ | ||
st := $4.(*ast.SelectStmt) | ||
union := $1.(*ast.UnionStmt) | ||
union.Distinct = union.Distinct || $3.(bool) | ||
lastSelect := union.SelectList.Selects[len(union.SelectList.Selects)-1] | ||
endOffset := parser.endOffset(&yyS[yypt-4]) | ||
parser.setLastSelectFieldText(lastSelect, endOffset) | ||
union.SelectList.Selects = append(union.SelectList.Selects, st) | ||
if $5 != nil { | ||
union.Limit = $5.(*ast.Limit) | ||
} | ||
$$ = union | ||
} | ||
| UnionClauseList "UNION" UnionOpt SelectStmtPart3 OrderByOptional | ||
SelectStmtLimit SelectLockOpt | ||
{ | ||
st := $4.(*ast.SelectStmt) | ||
union := $1.(*ast.UnionStmt) | ||
union.Distinct = union.Distinct || $3.(bool) | ||
lastSelect := union.SelectList.Selects[len(union.SelectList.Selects)-1] | ||
endOffset := parser.endOffset(&yyS[yypt-5]) | ||
parser.setLastSelectFieldText(lastSelect, endOffset) | ||
union.SelectList.Selects = append(union.SelectList.Selects, st) | ||
if $5 != nil { | ||
union.OrderBy = $5.(*ast.OrderByClause) | ||
} | ||
if $6 != nil { | ||
union.Limit = $6.(*ast.Limit) | ||
} | ||
$$ = union | ||
} | ||
| UnionClauseList "UNION" UnionOpt '(' SelectStmt ')' OrderByOptional SelectStmtLimit | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The bug this PR fixed is that,
order by a desc limit 1
belongs to union statement, but our old code thoughtit belongs to the second select statement.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, the last
order by a desc limit 1
is expected to belong to the whole union statement.