PrepareStatement got incorrect paramsCount in response packet from mock mysql server.
win5do opened this issue · comments
I use this lib to write some tests with https://github.com/jinzhu/gorm@v1.9.16 and https://github.com/go-sql-driver/mysql@v1.6.0.
A panic "sql: expected 0 arguments, got 1" occurred in grom's AutoMigrate func.
But there is no problem when I changed to a real mysql@5.7 started by docker.
After comparison, it is found that the paramsCount returned by the server is inconsistent.
// Param count [16 bit uint]
stmt.paramCount = int(binary.LittleEndian.Uint16(data[7:9]))
The SQL is:
SHOW TABLES FROM `gotest` WHERE `Tables_in_gotest` = ?
I write a test based on the code in dolthub/vitess:
func TestStmtPrepare(t *testing.T) {
statement, err := sqlparser.Parse("SHOW TABLES FROM `gotest` WHERE `Tables_in_gotest` = ?")
require.NoError(t, err)
paramsCount := uint16(0)
_ = sqlparser.Walk(func(node sqlparser.SQLNode) (bool, error) {
switch node := node.(type) {
case *sqlparser.SQLVal:
if strings.HasPrefix(string(node.Val), ":v") {
paramsCount++
}
}
return true, nil
}, statement)
assert.Equal(t, uint16(1), paramsCount)
}
Got unexpected output:
Error: Not equal:
expected: 0x1
actual : 0x0
This is a good bug and repro. We've seen some bugs with native prepared implementation in Dolt so this may be the cause. We will fix.
Thanks for your work, this SQL is indeed not a very common case.
I temporarily solved the problem with go-mysql-driver's “interpolateParams=true” parameter.
Hey @win5do, thanks for opening this issue and for providing the nice repro case. I debugged through this and you're absolutely right that the param count is being calculated in the vitess layer by looking for types of sqlparser.SQLVal
; unfortunately the grammar we use doesn't consistently use sqlparser.SQLVal
in every place where a prepared param value is valid, so that's why the go-mysql-server layer is detecting 1 param and the Vitess layer is detecting 0 params.
I'm going to try out a few ideas to fix this...
- One idea is updating our grammar to consistently use
sqlparser.SQLVal
in every spot where a prepared parameter is valid. This feels like it'll be tricky to get 100% correct and to enforce as we continue to grow the grammar. - A second idea is changing the interface between GMS and Vitess so that GMS can just tell Vitess how many prepared params it found, instead of having code in Vitess and GMS that calculates the param count differently. This feels like a more invasive change, but also seems like a faster path to having this calculation be correct and stay correct over time.
Either way, we'll get this prepared query working correctly. Glad to hear you have a good workaround with interpolateParams=true
in the meantime.
I debugged through this a little deeper and realized that we actually do have the right sqlparser.SQLVal
instances in the parsed statement, but... our code to recursively walk the SHOW TABLES
statement wasn't following those struct members, so that's why the code in conn.go
never found those params.
I've got a fix for this statement coded up locally and passing the repro test you provided. I'm going to tidy it up a little bit, do another round of debugging and testing to make sure I didn't miss anything, and then get the fix out for review.
Thanks again for taking the time to report this so we could fix it! 🙏
@win5do – the fix for this issue is now available in main for go-mysql-server. Thank you for taking the time to report this and for providing such an easy repro case! 🙏 Let us know if you hit any other snags with go-mysql-server and we'll be happy to investigate.