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

Crashed in bindStmtArgs(), when using the Server package to forward Execute statements from sysbench #915

Closed
rfyim opened this issue Sep 9, 2024 · 6 comments · Fixed by #984
Assignees

Comments

@rfyim
Copy link

rfyim commented Sep 9, 2024

I have implemented a proxy for forwarding queries using the Server package. The ServerHandler is implemented as follows:

type ServerHandler struct {
	Conn  *client.Conn
}

func NewServerHandler(cfg Config) (*ServerHandler, error) {
	conn, err := client.Connect(fmt.Sprintf("%s:%d", cfg.Host, cfg.Port), cfg.User, cfg.Password, "")
	if err != nil {
		return nil, err
	}
	if err = conn.Ping(); err != nil {
		return nil, err
	}
	return &ServerHandler{
		Conn: conn,
	}, nil
}

func (h ServerHandler) UseDB(dbName string) error {
	return h.Conn.UseDB(dbName)
}

func (h ServerHandler) HandleQuery(query string) (*mysql.Result, error) {
	return h.Conn.Execute(query)
}

func (h ServerHandler) HandleFieldList(table string, fieldWildcard string) ([]*mysql.Field, error) {
	return h.Conn.FieldList(table, fieldWildcard)
}
func (h ServerHandler) HandleStmtPrepare(query string) (int, int, interface{}, error) {
	stmt, err := h.Conn.Prepare(query)
	if err != nil {
		return 0, 0, nil, err
	}
	return stmt.ParamNum(), stmt.ColumnNum(), stmt, nil
}
func (h ServerHandler) HandleStmtExecute(context interface{}, query string, args []interface{}) (*mysql.Result, error) {
	return context.(*client.Stmt).Execute(args...)
}

func (h ServerHandler) HandleStmtClose(context interface{}) error {
	return context.(*client.Stmt).Close()
}

func (h ServerHandler) HandleOtherCommand(cmd byte, data []byte) error {
	return mysql.NewError(
		mysql.ER_UNKNOWN_ERROR,
		fmt.Sprintf("command %d is not supported now", cmd),
	)
}

Unfortunately, it crashed in (c *Conn) bindStmtArgs() when I was testing with sysbench. It seems that when repeatedly executing the same statement with the same parameters, the EXECUTE message indicates that there is no need to re-bind the parameters, but the implementation of handleStmtExecute does not save the types and values of the parameters from the previous execution of the statement.

@matttm
Copy link

matttm commented Feb 3, 2025

@rfyim Did you ever get this to work?

I happen to be doing the same thing as you, the same way. Mine is hanging on select database() though sent from dbeaver.

func (h ProxyRequestHandler) HandleQuery(query string) (*mysql.Result, error) {
	log.Println("In HandleQuery")
	log.Println(query)
	r, err := h.remoteDb.Execute(query)
	if err != nil {
		return nil, err
	}
	log.Printf("Executed query: %d", r.AffectedRows)
	return r, nil
}

@dveeden dveeden self-assigned this Feb 3, 2025
@dveeden
Copy link
Collaborator

dveeden commented Feb 6, 2025

For testing I'm using this:

package main

import (
	"fmt"
	"net"

	"github.com/go-mysql-org/go-mysql/client"
	"github.com/go-mysql-org/go-mysql/mysql"
	"github.com/go-mysql-org/go-mysql/server"
)

type ServerHandler struct {
	Conn *client.Conn
}

type Config struct {
	Host     string
	Port     int
	User     string
	Password string
}

func NewServerHandler(cfg Config) (*ServerHandler, error) {
	conn, err := client.Connect(fmt.Sprintf("%s:%d", cfg.Host, cfg.Port), cfg.User, cfg.Password, "")
	if err != nil {
		return nil, err
	}
	if err = conn.Ping(); err != nil {
		return nil, err
	}
	return &ServerHandler{
		Conn: conn,
	}, nil
}

func (h ServerHandler) UseDB(dbName string) error {
	return h.Conn.UseDB(dbName)
}

func (h ServerHandler) HandleQuery(query string) (*mysql.Result, error) {
	return h.Conn.Execute(query)
}

func (h ServerHandler) HandleFieldList(table string, fieldWildcard string) ([]*mysql.Field, error) {
	return h.Conn.FieldList(table, fieldWildcard)
}
func (h ServerHandler) HandleStmtPrepare(query string) (int, int, interface{}, error) {
	stmt, err := h.Conn.Prepare(query)
	if err != nil {
		return 0, 0, nil, err
	}
	return stmt.ParamNum(), stmt.ColumnNum(), stmt, nil
}
func (h ServerHandler) HandleStmtExecute(context interface{}, query string, args []interface{}) (*mysql.Result, error) {
	return context.(*client.Stmt).Execute(args...)
}

func (h ServerHandler) HandleStmtClose(context interface{}) error {
	return context.(*client.Stmt).Close()
}

func (h ServerHandler) HandleOtherCommand(cmd byte, data []byte) error {
	return mysql.NewError(
		mysql.ER_UNKNOWN_ERROR,
		fmt.Sprintf("command %d is not supported now", cmd),
	)
}

func main() {
	l, _ := net.Listen("tcp", "127.0.0.1:4000")
	c, _ := l.Accept()
	cfg := Config{
		Host: "127.0.0.1",
		Port: 3306,
		User: "root",
	}
	sh, _ := NewServerHandler(cfg)
	conn, _ := server.NewConn(c, "root", "", sh)
	for {
		if err := conn.HandleCommand(); err != nil {
			panic(err)
		}
	}
}

With MySQL 9.2.0 running on port 3306.

@matttm
Copy link

matttm commented Feb 7, 2025

@dveeden I did try your handler, and I get exactly as far as my code gets, which I did make some progress on since I last wrote.

I can successfully test the connection through dbeaver, and though dbeaver shows a success message, when I go to my proxy terminal, it's dead with the following output.

2025/02/06 19:45:41 new connection: 127.0.0.1:52017
2025/02/06 19:45:41 In HandleQuery
2025/02/06 19:45:41 /* mysql-connector-j-8.2.0 (Revision: 06a1f724497fd81c6a659131fda822c9e5085b6c) */SELECT  @@session.auto_increment_increment AS auto_increment_increment, @@character_set_client AS character_set_client, @@character_set_connection AS character_set_connection, @@character_set_results AS character_set_results, @@character_set_server AS character_set_server, @@collation_server AS collation_server, @@collation_connection AS collation_connection, @@init_connect AS init_connect, @@interactive_timeout AS interactive_timeout, @@license AS license, @@lower_case_table_names AS lower_case_table_names, @@max_allowed_packet AS max_allowed_packet, @@net_write_timeout AS net_write_timeout, @@performance_schema AS performance_schema, @@sql_mode AS sql_mode, @@system_time_zone AS system_time_zone, @@time_zone AS time_zone, @@transaction_isolation AS transaction_isolation, @@wait_timeout AS wait_timeout
2025/02/06 19:45:41 In HandleQuery
2025/02/06 19:45:41 SET NAMES utf8mb4
2025/02/06 19:45:41 In HandleQuery
2025/02/06 19:45:41 SET character_set_results = NULL
2025/02/06 19:45:42 In HandleQuery
2025/02/06 19:45:42 SET autocommit=1
2025/02/06 19:45:42 In HandleQuery
2025/02/06 19:45:42 SET sql_mode='NO_ENGINE_SUBSTITUTION,STRICT_TRANS_TABLES'
2025/02/06 19:45:42 In HandleQuery
2025/02/06 19:45:42 /* ApplicationName=DBeaver 24.3.3 - Main */ SET autocommit=1
2025/02/06 19:45:42 In HandleQuery
2025/02/06 19:45:42 /* ApplicationName=DBeaver 24.3.3 - Main */ SELECT DATABASE()
2025/02/06 19:45:42 In HandleQuery
2025/02/06 19:45:42 /* ApplicationName=DBeaver 24.3.3 - Main */ SHOW ENGINES
2025/02/06 19:45:42 In HandleQuery
2025/02/06 19:45:42 /* ApplicationName=DBeaver 24.3.3 - Main */ SHOW CHARSET
2025/02/06 19:45:42 In HandleQuery
2025/02/06 19:45:42 /* ApplicationName=DBeaver 24.3.3 - Main */ SHOW COLLATION
2025/02/06 19:45:42 In HandleQuery
2025/02/06 19:45:42 /* ApplicationName=DBeaver 24.3.3 - Main */ SELECT @@GLOBAL.character_set_server,@@GLOBAL.collation_server
2025/02/06 19:45:42 In HandleQuery
2025/02/06 19:45:42 /* ApplicationName=DBeaver 24.3.3 - Main */ SHOW PLUGINS
2025/02/06 19:45:42 In HandleQuery
2025/02/06 19:45:42 /* ApplicationName=DBeaver 24.3.3 - Main */ SHOW VARIABLES LIKE 'lower_case_table_names'
2025/02/06 19:45:42 In HandleQuery
2025/02/06 19:45:42 /* ApplicationName=DBeaver 24.3.3 - Main */ show databases
2025/02/06 19:45:42 In HandleQuery
2025/02/06 19:45:42 /* ApplicationName=DBeaver 24.3.3 - Main */ SELECT * FROM information_schema.TABLES t
WHERE
        t.TABLE_SCHEMA = 'information_schema'
        AND t.TABLE_NAME = 'CHECK_CONSTRAINTS'
2025/02/06 19:45:42 Executed query: 0
2025/02/06 19:45:42 connection closed

I am trying to connect to mySQL 8 RDS server

@dveeden
Copy link
Collaborator

dveeden commented Feb 7, 2025

@matttm The code I posted is just to have a full, runnable example. I did some testing with it and found a couple of issues that I'm looking into. I didn't mean to give the impression that the code I posted functions without issue.

@dveeden
Copy link
Collaborator

dveeden commented Feb 7, 2025

I tried the previously listed code with go-mysql with this PR applied: #983

Then the prepare ran ok:

$ sysbench --mysql-host=127.0.0.1 --mysql-port=4000 --mysql-user=root --mysql-db=test oltp_read_only prepare
sysbench 1.0.20 (using system LuaJIT 2.1.1713773202)

Creating table 'sbtest1'...
Inserting 10000 records into 'sbtest1'
Creating a secondary index on 'sbtest1'...

And then the run indeed fails as described

$ sysbench --mysql-host=127.0.0.1 --mysql-port=4000 --mysql-user=root --mysql-db=test oltp_read_only run --max-time=30
WARNING: --max-time is deprecated, use --time instead
sysbench 1.0.20 (using system LuaJIT 2.1.1713773202)

Running the test with following options:
Number of threads: 1
Initializing random number generator from current time


Initializing worker threads...

Threads started!

FATAL: mysql_stmt_execute() returned error 2013 (Lost connection to server during query) for query 'SELECT c FROM sbtest1 WHERE id=?'
FATAL: `thread_run' function failed: /usr/share/sysbench/oltp_common.lua:419: SQL error, errno = 2013, state = 'HY000': Lost connection to server during query

With the server reporting this:

$ go run main.go 
panic: runtime error: index out of range [0] with length 0

goroutine 1 [running]:
github.com/go-mysql-org/go-mysql/server.(*Conn).bindStmtArgs(0xc00002fd18?, 0xc0004a6000, {0xc0004881d2, 0x1, 0x0?}, {0x0, 0x0, 0xc00002fd98?}, {0x0, 0x0, ...})
	/home/dvaneeden/dev/go-mysql/server/stmt.go:192 +0xd10
github.com/go-mysql-org/go-mysql/server.(*Conn).handleStmtExecute(0xc0000bc2c0, {0xc0004881c9, 0x13, 0x17})
	/home/dvaneeden/dev/go-mysql/server/stmt.go:160 +0x1e9
github.com/go-mysql-org/go-mysql/server.(*Conn).dispatch(0xc0000bc2c0, {0xc0004881c8?, 0x14, 0x18})
	/home/dvaneeden/dev/go-mysql/server/command.go:121 +0x43b
github.com/go-mysql-org/go-mysql/server.(*Conn).HandleCommand(0xc0000bc2c0)
	/home/dvaneeden/dev/go-mysql/server/command.go:59 +0xa6
main.main()
	/tmp/issue915/main.go:90 +0x105
exit status 2

@dveeden
Copy link
Collaborator

dveeden commented Feb 7, 2025

Note that this Python code is using prepared statements just fine. I assume that's because it is only executing the statement once.

#!/bin/python3
import mysql.connector

c = mysql.connector.connect(host='127.0.0.1',port=4000,user='root',ssl_disabled=True)
cur = c.cursor(prepared=True)
cur.execute('SELECT ?', ("test",))
for row in cur:
    print(row)
cur.close()
c.close()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

3 participants