-
Notifications
You must be signed in to change notification settings - Fork 6.2k
protocol: support query attribute since mysql 8.0.23 #55175
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
base: master
Are you sure you want to change the base?
Changes from all commits
92285cf
b4df1e6
3f7c090
5993b04
df6d253
03ee920
2d565ad
13471df
8347fe6
170fcbe
ac2851c
19f2ec8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -372,6 +372,9 @@ const ( | |||||||||
| NextVal = "nextval" | ||||||||||
| LastVal = "lastval" | ||||||||||
| SetVal = "setval" | ||||||||||
|
|
||||||||||
| // TiDB Query Attribute function | ||||||||||
| QueryAttrString = "mysql_query_attribute_string" | ||||||||||
|
Contributor
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.
Suggested change
|
||||||||||
| ) | ||||||||||
|
|
||||||||||
| type FuncCallExprType int8 | ||||||||||
|
|
||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -66,6 +66,7 @@ import ( | |
| "github.com/pingcap/tidb/pkg/infoschema" | ||
| "github.com/pingcap/tidb/pkg/kv" | ||
| "github.com/pingcap/tidb/pkg/metrics" | ||
| "github.com/pingcap/tidb/pkg/param" | ||
| "github.com/pingcap/tidb/pkg/parser" | ||
| "github.com/pingcap/tidb/pkg/parser/ast" | ||
| "github.com/pingcap/tidb/pkg/parser/auth" | ||
|
|
@@ -1335,6 +1336,7 @@ func (cc *clientConn) dispatch(ctx context.Context, data []byte) error { | |
|
|
||
| cc.server.releaseToken(token) | ||
| cc.lastActive = time.Now() | ||
| cc.ctx.GetSessionVars().QueryAttributes = nil | ||
| }() | ||
|
|
||
| vars := cc.ctx.GetSessionVars() | ||
|
|
@@ -1371,8 +1373,14 @@ func (cc *clientConn) dispatch(ctx context.Context, data []byte) error { | |
| // See http://dev.mysql.com/doc/internals/en/com-query.html | ||
| if len(data) > 0 && data[len(data)-1] == 0 { | ||
| data = data[:len(data)-1] | ||
| dataStr = string(hack.String(data)) | ||
| } | ||
| pos, err := cc.parseQueryAttributes(ctx, data) | ||
| if err != nil { | ||
| return err | ||
| } | ||
| // fix lastPacket for display/log | ||
| cc.lastPacket = append([]byte{cc.lastPacket[0]}, data[pos:]...) | ||
| dataStr = string(hack.String(data[pos:])) | ||
| return cc.handleQuery(ctx, dataStr) | ||
| case mysql.ComFieldList: | ||
| return cc.handleFieldList(ctx, dataStr) | ||
|
|
@@ -1699,6 +1707,64 @@ func (cc *clientConn) audit(eventType plugin.GeneralEvent) { | |
| } | ||
| } | ||
|
|
||
| // parseQueryAttributes support query attributes since mysql 8.0.23 | ||
| // see https://dev.mysql.com/doc/refman/8.0/en/query-attributes.html | ||
| // https://archive.fosdem.org/2021/schedule/event/mysql_protocl/ | ||
| // https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_com_query.html | ||
| // https://dev.mysql.com/doc/dev/mysql-server/latest/page_protocol_com_stmt_execute.html | ||
| func (cc *clientConn) parseQueryAttributes(ctx context.Context, data []byte) (pos int, err error) { | ||
|
Contributor
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 well does this handle the case when the data is somehow truncated? (e.g. only one of two bytes)
Contributor
Author
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 there would be a panic caused by an out-of-bounds slice access here, which would lead to the disconnection of this session.
Contributor
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. Would it be possible to re-use some of the parameter handling code that is used for prepared statements? Basically query attributes and parameters are very similar.
Contributor
Author
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 all internally call parseBinaryParams. Further code reuse is also possible, but it may require passing in more args |
||
| if cc.capability&mysql.ClientQueryAttributes == 0 { | ||
| return | ||
| } | ||
|
|
||
| paraCount, _, np := util2.ParseLengthEncodedInt(data) | ||
| numParams := int(paraCount) | ||
| pos += np | ||
| _, _, np = util2.ParseLengthEncodedInt(data[pos:]) | ||
| pos += np | ||
| ps := make([]param.BinaryParam, numParams) | ||
| names := make([]string, numParams) | ||
| if paraCount > 0 { | ||
| var ( | ||
| nullBitmaps []byte | ||
| paramTypes []byte | ||
| ) | ||
| cc.initInputEncoder(ctx) | ||
| nullBitmapLen := (numParams + 7) >> 3 | ||
| nullBitmaps = data[pos : pos+nullBitmapLen] | ||
| pos += nullBitmapLen | ||
| if data[pos] != 1 { | ||
| return 0, mysql.ErrMalformPacket | ||
| } | ||
|
|
||
| pos++ | ||
| for i := 0; i < numParams; i++ { | ||
| paramTypes = append(paramTypes, data[pos:pos+2]...) | ||
| pos += 2 | ||
| s, _, p, e := util2.ParseLengthEncodedBytes(data[pos:]) | ||
| if e != nil { | ||
| return 0, mysql.ErrMalformPacket | ||
| } | ||
| names[i] = string(hack.String(s)) | ||
| pos += p | ||
| } | ||
|
|
||
| boundParams := make([][]byte, numParams) | ||
| p := 0 | ||
| if p, err = parseBinaryParams(ps, boundParams, nullBitmaps, paramTypes, data[pos:], cc.inputDecoder); err != nil { | ||
| return | ||
| } | ||
|
|
||
| pos += p | ||
| psWithName := make(map[string]param.BinaryParam, numParams) | ||
| for i := range names { | ||
| psWithName[names[i]] = ps[i] | ||
| } | ||
| cc.ctx.GetSessionVars().QueryAttributes = psWithName | ||
| } | ||
| return | ||
| } | ||
|
|
||
| // handleQuery executes the sql query string and writes result set or result ok to the client. | ||
| // As the execution time of this function represents the performance of TiDB, we do time log and metrics here. | ||
| // Some special queries like `load data` that does not return result, which is handled in handleFileTransInConn. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -54,11 +54,13 @@ import ( | |
| "github.com/pingcap/tidb/pkg/server/internal/dump" | ||
| "github.com/pingcap/tidb/pkg/server/internal/parse" | ||
| "github.com/pingcap/tidb/pkg/server/internal/resultset" | ||
| util2 "github.com/pingcap/tidb/pkg/server/internal/util" | ||
| "github.com/pingcap/tidb/pkg/sessionctx/vardef" | ||
| "github.com/pingcap/tidb/pkg/sessiontxn" | ||
| storeerr "github.com/pingcap/tidb/pkg/store/driver/error" | ||
| "github.com/pingcap/tidb/pkg/util/chunk" | ||
| "github.com/pingcap/tidb/pkg/util/execdetails" | ||
| "github.com/pingcap/tidb/pkg/util/hack" | ||
| "github.com/pingcap/tidb/pkg/util/logutil" | ||
| "github.com/pingcap/tidb/pkg/util/memory" | ||
| "github.com/pingcap/tidb/pkg/util/redact" | ||
|
|
@@ -179,7 +181,14 @@ func (cc *clientConn) handleStmtExecute(ctx context.Context, data []byte) (err e | |
| paramValues []byte | ||
| ) | ||
| cc.initInputEncoder(ctx) | ||
| numParams := stmt.NumParams() | ||
| stmtNumParams := stmt.NumParams() | ||
| numParams := stmtNumParams | ||
| clientHasQueryAttr := cc.ctx.GetSessionVars().ClientCapability&mysql.ClientQueryAttributes > 0 | ||
| if clientHasQueryAttr && (numParams > 0 || flag&mysql.ParameterCountAvailable > 0) { | ||
| paraCount, _, np := util2.ParseLengthEncodedInt(data[pos:]) | ||
| numParams = int(paraCount) | ||
| pos += np | ||
| } | ||
| args := make([]param.BinaryParam, numParams) | ||
| if numParams > 0 { | ||
| nullBitmapLen := (numParams + 7) >> 3 | ||
|
|
@@ -188,16 +197,38 @@ func (cc *clientConn) handleStmtExecute(ctx context.Context, data []byte) (err e | |
| } | ||
| nullBitmaps = data[pos : pos+nullBitmapLen] | ||
| pos += nullBitmapLen | ||
| var attributeNames []string | ||
|
|
||
| // new param bound flag | ||
| if data[pos] == 1 { | ||
| pos++ | ||
| if len(data) < (pos + (numParams << 1)) { | ||
| return mysql.ErrMalformPacket | ||
| // For client that has query attribute ability, query attributes' name will also be sent. | ||
| if clientHasQueryAttr { | ||
|
Member
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. Should we also set
Contributor
Author
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 do not think set |
||
| if numParams > stmtNumParams { | ||
| attributeNames = make([]string, 0, numParams-stmt.NumParams()) | ||
| } | ||
| for i := 0; i < numParams; i++ { | ||
| paramTypes = append(paramTypes, data[pos:pos+2]...) | ||
| pos += 2 | ||
| // parse names | ||
| pName, _, p, e := util2.ParseLengthEncodedBytes(data[pos:]) | ||
| if e != nil { | ||
| return mysql.ErrMalformPacket | ||
| } | ||
| // Only the names of the parameters for query attributes will be sent. | ||
| if len(pName) > 0 { | ||
| attributeNames = append(attributeNames, string(hack.String(pName))) | ||
| } | ||
| pos += p | ||
| } | ||
| } else { | ||
| if len(data) < (pos + (numParams << 1)) { | ||
| return mysql.ErrMalformPacket | ||
| } | ||
|
|
||
| paramTypes = data[pos : pos+(numParams<<1)] | ||
| pos += numParams << 1 | ||
| } | ||
|
|
||
| paramTypes = data[pos : pos+(numParams<<1)] | ||
| pos += numParams << 1 | ||
| paramValues = data[pos:] | ||
| // Just the first StmtExecute packet contain parameters type, | ||
| // we need save it for further use. | ||
|
|
@@ -206,7 +237,18 @@ func (cc *clientConn) handleStmtExecute(ctx context.Context, data []byte) (err e | |
| paramValues = data[pos+1:] | ||
| } | ||
|
|
||
| err = parseBinaryParams(args, stmt.BoundParams(), nullBitmaps, stmt.GetParamsType(), paramValues, cc.inputDecoder) | ||
| _, err = parseBinaryParams(args, stmt.BoundParams(), nullBitmaps, stmt.GetParamsType(), paramValues, cc.inputDecoder) | ||
| if len(attributeNames) != 0 { | ||
| if len(attributeNames) != len(args)-stmtNumParams { | ||
| return mysql.ErrMalformPacket | ||
| } | ||
| psWithName := make(map[string]param.BinaryParam, numParams) | ||
| for i := range attributeNames { | ||
| psWithName[attributeNames[i]] = args[i+stmtNumParams] | ||
| } | ||
| cc.ctx.GetSessionVars().QueryAttributes = psWithName | ||
| args = args[:stmtNumParams] | ||
| } | ||
| // This `.Reset` resets the arguments, so it's fine to just ignore the error (and the it'll be reset again in the following routine) | ||
| errReset := stmt.Reset() | ||
| if errReset != nil { | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.