From 1219706189f2eedb13dc2ccf44a2a7913563c943 Mon Sep 17 00:00:00 2001 From: Ti Chi Robot Date: Wed, 15 Jan 2025 17:37:10 +0800 Subject: [PATCH] executor: fix prepared protocol charset (#58872) (#58906) close pingcap/tidb#58870 --- pkg/executor/prepared.go | 21 ++++++++++++++------- pkg/executor/prepared_test.go | 24 ++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 7 deletions(-) diff --git a/pkg/executor/prepared.go b/pkg/executor/prepared.go index 2a73f4be57dbf..aa22f2ffdd20d 100644 --- a/pkg/executor/prepared.go +++ b/pkg/executor/prepared.go @@ -59,6 +59,8 @@ type PrepareExec struct { // If it's generated from executing "prepare stmt from '...'", the process is parse -> plan -> executor // If it's generated from the prepare protocol, the process is session.PrepareStmt -> NewPrepareExec // They both generate a PrepareExec struct, but the second case needs to reset the statement context while the first already do that. + // Also, the second case need charset_client param since SQL is directly passed from clients. + // While the text-prepare already transformed charset by parser. needReset bool } @@ -84,23 +86,28 @@ func (e *PrepareExec) Next(ctx context.Context, _ *chunk.Chunk) error { return nil } } - charset, collation := vars.GetCharsetInfo() var ( stmts []ast.StmtNode err error ) + var params []parser.ParseParam + if e.needReset { + params = vars.GetParseParams() + } else { + var paramsArr [2]parser.ParseParam + charset, collation := vars.GetCharsetInfo() + paramsArr[0] = parser.CharsetConnection(charset) + paramsArr[1] = parser.CollationConnection(collation) + params = paramsArr[:] + } if sqlParser, ok := e.Ctx().(sqlexec.SQLParser); ok { // FIXME: ok... yet another parse API, may need some api interface clean. - stmts, _, err = sqlParser.ParseSQL(ctx, e.sqlText, - parser.CharsetConnection(charset), - parser.CollationConnection(collation)) + stmts, _, err = sqlParser.ParseSQL(ctx, e.sqlText, params...) } else { p := parser.New() p.SetParserConfig(vars.BuildParserConfig()) var warns []error - stmts, warns, err = p.ParseSQL(e.sqlText, - parser.CharsetConnection(charset), - parser.CollationConnection(collation)) + stmts, warns, err = p.ParseSQL(e.sqlText, params...) for _, warn := range warns { e.Ctx().GetSessionVars().StmtCtx.AppendWarning(util.SyntaxWarn(warn)) } diff --git a/pkg/executor/prepared_test.go b/pkg/executor/prepared_test.go index 0cba4e45bebc8..b5b7054700049 100644 --- a/pkg/executor/prepared_test.go +++ b/pkg/executor/prepared_test.go @@ -15,6 +15,7 @@ package executor_test import ( + "context" "fmt" "strconv" "strings" @@ -1273,3 +1274,26 @@ func TestMaxPreparedStmtCount(t *testing.T) { err := tk.ExecToErr("prepare stmt3 from 'select ? as num from dual'") require.True(t, terror.ErrorEqual(err, variable.ErrMaxPreparedStmtCountReached)) } + +func TestIssue58870(t *testing.T) { + store := testkit.CreateMockStore(t) + tk := testkit.NewTestKit(t, store) + + tk.MustExec("use test") + tk.MustExec("set names GBK") + tk.MustExec(`CREATE TABLE tsecurity ( + security_id int(11) NOT NULL DEFAULT 0, + mkt_id smallint(6) NOT NULL DEFAULT 0, + security_code varchar(64) CHARACTER SET gbk COLLATE gbk_bin NOT NULL DEFAULT ' ', + security_name varchar(128) CHARACTER SET gbk COLLATE gbk_bin NOT NULL DEFAULT ' ', + PRIMARY KEY (security_id) USING BTREE +) ENGINE = InnoDB CHARACTER SET = gbk COLLATE = gbk_bin ROW_FORMAT = Compact;`) + tk.MustExec("INSERT INTO tsecurity (security_id, security_code, mkt_id, security_name) VALUES (1, '1', 1 ,'\xB2\xE2')") + tk.MustExec("PREPARE a FROM 'INSERT INTO tsecurity (security_id, security_code, mkt_id, security_name) VALUES (2, 2, 2 ,\"\xB2\xE2\")'") + tk.MustExec("EXECUTE a") + stmt, _, _, err := tk.Session().PrepareStmt("INSERT INTO tsecurity (security_id, security_code, mkt_id, security_name) VALUES (3, 3, 3 ,\"\xB2\xE2\")") + require.Nil(t, err) + rs, err := tk.Session().ExecutePreparedStmt(context.TODO(), stmt, nil) + require.Nil(t, err) + require.Nil(t, rs) +}