Skip to content
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

planner: do not convert update to point get if the expr has sub-query #47454

Merged
merged 6 commits into from
Oct 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pkg/parser/ast/expressions.go
Original file line number Diff line number Diff line change
Expand Up @@ -981,7 +981,7 @@ type ParamMarkerExpr interface {
SetOrder(int)
}

// ParenthesesExpr is the parentheses expression.
// ParenthesesExpr is the parentheses' expression.
type ParenthesesExpr struct {
exprNode
// Expr is the expression in parentheses.
Expand Down
16 changes: 16 additions & 0 deletions pkg/planner/core/plan_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -755,6 +755,22 @@ func TestIssue40535(t *testing.T) {
require.Empty(t, tk.Session().LastMessage())
}

func TestIssue47445(t *testing.T) {
store, _ := testkit.CreateMockStoreAndDomain(t)
tk := testkit.NewTestKit(t, store)
tk.MustExec("use test;")
tk.MustExec("CREATE TABLE golang1 ( `fcbpdt` CHAR (8) COLLATE utf8_general_ci NOT NULL, `fcbpsq` VARCHAR (20) COLLATE utf8_general_ci NOT NULL, `procst` char (4) COLLATE utf8_general_ci DEFAULT NULL,`cipstx` VARCHAR (105) COLLATE utf8_general_ci DEFAULT NULL, `cipsst` CHAR (4) COLLATE utf8_general_ci DEFAULT NULL, `dyngtg` VARCHAR(4) COLLATE utf8_general_ci DEFAULT NULL, `blncdt` VARCHAR (8) COLLATE utf8_general_ci DEFAULT NULL, PRIMARY KEY ( fcbpdt, fcbpsq ))")
tk.MustExec("insert into golang1 values('20230925','12023092502158016','abc','','','','')")
tk.MustExec("create table golang2 (`sysgrp` varchar(20) NOT NULL,`procst` varchar(8) NOT NULL,`levlid` int(11) NOT NULL,PRIMARY key (procst));")
tk.MustExec("insert into golang2 VALUES('COMMON','ACSC',90)")
tk.MustExec("insert into golang2 VALUES('COMMON','abc',8)")
tk.MustExec("insert into golang2 VALUES('COMMON','CH02',6)")
tk.MustExec("UPDATE golang1 a SET procst =(CASE WHEN ( SELECT levlid FROM golang2 b WHERE b.sysgrp = 'COMMON' AND b.procst = 'ACSC' ) > ( SELECT levlid FROM golang2 c WHERE c.sysgrp = 'COMMON' AND c.procst = a.procst ) THEN 'ACSC' ELSE a.procst END ), cipstx = 'CI010000', cipsst = 'ACSC', dyngtg = 'EAYT', blncdt= '20230925' WHERE fcbpdt = '20230925' AND fcbpsq = '12023092502158016'")
tk.MustQuery("select * from golang1").Check(testkit.Rows("20230925 12023092502158016 ACSC CI010000 ACSC EAYT 20230925"))
tk.MustExec("UPDATE golang1 a SET procst= (SELECT 1 FROM golang2 c WHERE c.procst = a.procst) WHERE fcbpdt = '20230925' AND fcbpsq = '12023092502158016'")
tk.MustQuery("select * from golang1").Check(testkit.Rows("20230925 12023092502158016 1 CI010000 ACSC EAYT 20230925"))
}

func TestExplainValuesStatement(t *testing.T) {
store, _ := testkit.CreateMockStoreAndDomain(t)
tk := testkit.NewTestKit(t, store)
Expand Down
55 changes: 50 additions & 5 deletions pkg/planner/core/point_get_plan.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import (
"sort"
"strconv"
"strings"
"sync"
"unsafe"

"github.com/pingcap/errors"
Expand Down Expand Up @@ -1552,13 +1553,57 @@ func findInPairs(colName string, pairs []nameValuePair) int {
return -1
}

func tryUpdatePointPlan(ctx sessionctx.Context, updateStmt *ast.UpdateStmt) Plan {
// avoid using the point_get when assignment_list contains the subquery in the UPDATE.
for _, list := range updateStmt.List {
if _, ok := list.Expr.(*ast.SubqueryExpr); ok {
return nil
// Use cache to avoid allocating memory every time.
var subQueryCheckerPool = &sync.Pool{New: func() any { return &subQueryChecker{} }}

type subQueryChecker struct {
hasSubQuery bool
}

func (s *subQueryChecker) Enter(in ast.Node) (node ast.Node, skipChildren bool) {
if s.hasSubQuery {
return in, true
}

if _, ok := in.(*ast.SubqueryExpr); ok {
s.hasSubQuery = true
return in, true
}

return in, false
}

func (s *subQueryChecker) Leave(in ast.Node) (ast.Node, bool) {
// Before we enter the sub-query, we should keep visiting its children.
return in, !s.hasSubQuery
}

func isExprHasSubQuery(expr ast.Node) bool {
checker := subQueryCheckerPool.Get().(*subQueryChecker)
defer func() {
// Do not forget to reset the flag.
checker.hasSubQuery = false
subQueryCheckerPool.Put(checker)
}()
expr.Accept(checker)
return checker.hasSubQuery
}

func checkIfAssignmentListHasSubQuery(list []*ast.Assignment) bool {
for _, a := range list {
if isExprHasSubQuery(a) {
return true
}
}
return false
}

func tryUpdatePointPlan(ctx sessionctx.Context, updateStmt *ast.UpdateStmt) Plan {
// Avoid using the point_get when assignment_list contains the sub-query in the UPDATE.
if checkIfAssignmentListHasSubQuery(updateStmt.List) {
return nil
}

selStmt := &ast.SelectStmt{
Fields: &ast.FieldList{},
From: updateStmt.TableRefs,
Expand Down