runtime error: comparing uncomparable type mssql.Error
tochka opened this issue · 7 comments
I've got a panic when using your library (2.1.0) with github.com/microsoft/go-mssqldb@v1.7.2
sql driver.
Stack trace
/usr/local/go/src/runtime/panic.go:770 +0x124
github.com/qustavo/sqlhooks/v2.composed.OnError({0x14000a5a650?, 0x2?, 0x104ad1740?}, {0x1051a44d8, 0x140009884e0}, {0x10517a120, 0x14000acade0}, {0x1043e2b1a, 0x23}, {0x1400073c940, ...})
/Users/vvinogradov/go/pkg/mod/github.com/qustavo/sqlhooks/v2@v2.1.0/compose.go:52 +0xfc
github.com/qustavo/sqlhooks/v2.handlerErr({0x1051a44d8?, 0x140009884e0?}, {0x1051851f0?, 0x14000915908?}, {0x10517a120, 0x14000acade0}, {0x1043e2b1a?, 0x23?}, {0x1400073c940?, 0x2?, ...})
/Users/vvinogradov/go/pkg/mod/github.com/qustavo/sqlhooks/v2@v2.1.0/sqlhooks.go:32 +0x8c
github.com/qustavo/sqlhooks/v2.(*Stmt).QueryContext(0x140009884b0, {0x1051a4430, 0x106abf780}, {0x14000534a50, 0x2, 0x2})
/Users/vvinogradov/go/pkg/mod/github.com/qustavo/sqlhooks/v2@v2.1.0/sqlhooks.go:312 +0x1c4
database/sql.ctxDriverStmtQuery({0x1051a4430, 0x106abf780}, {0x1051a56c8, 0x140009884b0}, {0x14000534a50, 0x2, 0x2?})
/usr/local/go/src/database/sql/ctxutil.go:82 +0x9c
database/sql.rowsiFromStatement({0x1051a4430, 0x106abf780}, {0x1051935e0, 0x1400073c900}, 0x1400095d640, {0x14000a91828, 0x2, 0x2})
/usr/local/go/src/database/sql/sql.go:2836 +0x118
database/sql.(*DB).queryDC(0x14000856a90?, {0x1051a4430, 0x106abf780}, {0x0, 0x0}, 0x14000d8abd0, 0x14000c71e40, {0x1043e2b1a, 0x23}, {0x14000a91828, ...})
/usr/local/go/src/database/sql/sql.go:1806 +0x26c
database/sql.(*DB).query(0x14000856a90, {0x1051a4430, 0x106abf780}, {0x1043e2b1a, 0x23}, {0x14000a91828, 0x2, 0x2}, 0x0?)
/usr/local/go/src/database/sql/sql.go:1754 +0xb4
database/sql.(*DB).QueryContext.func1(0xf8?)
/usr/local/go/src/database/sql/sql.go:1732 +0x54
database/sql.(*DB).retry(0x58?, 0x14000a91630)
/usr/local/go/src/database/sql/sql.go:1566 +0x4c
database/sql.(*DB).QueryContext(0x104e3c2e0?, {0x1051a4430?, 0x106abf780?}, {0x1043e2b1a?, 0x1400014af00?}, {0x14000a91828?, 0x14000a91718?, 0x104312878?})
/usr/local/go/src/database/sql/sql.go:1731 +0x94
database/sql.(*DB).Query(...)
/usr/local/go/src/database/sql/sql.go:1745
can you provide the code you are using?
package mssql_test
import (
"context"
"database/sql"
"database/sql/driver"
"fmt"
"net"
"testing"
"github.com/docker/docker/api/types/container"
"github.com/docker/go-connections/nat"
gomssql "github.com/microsoft/go-mssqldb"
"github.com/qustavo/sqlhooks/v2"
"github.com/stretchr/testify/require"
"github.com/testcontainers/testcontainers-go"
"github.com/testcontainers/testcontainers-go/wait"
)
const ddlTestFunc = `create procedure test_proc AS
begin
DECLARE @ErrorMsg varchar(7000)
SET NOCOUNT ON;
select @ErrorMsg = 'Test error'
raiserror(@ErrorMsg, 16, 1)
return
end;`
func TestMsSQL(t *testing.T) {
ctx := context.Background()
req := testcontainers.ContainerRequest{
Image: "mcr.microsoft.com/mssql/server:2019-latest",
ExposedPorts: []string{"1433/tcp"},
SkipReaper: true,
Env: map[string]string{
"ACCEPT_EULA": "Y",
"MSSQL_PID": "Evaluation",
"MSSQL_SA_PASSWORD": "Test87654321",
},
WaitingFor: wait.ForSQL("1433/tcp", "sqlserver", func(host string, port nat.Port) string {
return fmt.Sprintf("sqlserver://sa:Test87654321@%s/master", net.JoinHostPort(host, port.Port()))
}),
HostConfigModifier: func(hc *container.HostConfig) {
hc.AutoRemove = true
},
}
container, err := testcontainers.GenericContainer(ctx, testcontainers.GenericContainerRequest{
ContainerRequest: req,
Started: true,
Logger: testcontainers.TestLogger(t),
})
require.NoError(t, err)
t.Cleanup(func() {
container.Stop(ctx, nil)
})
hostPort, err := container.PortEndpoint(ctx, "1433/tcp", "")
require.NoError(t, err)
connStr := fmt.Sprintf("sqlserver://sa:Test87654321@%s/master", hostPort)
t.Log(connStr)
db := openDB(connStr, []sqlhooks.Hooks{simpleHook{}})
_, err = db.Exec(ddlTestFunc)
require.NoError(t, err)
_, err = db.Exec(`EXEC test_proc`)
require.Error(t, err, "error is: %v", err)
}
type simpleHook struct{}
func (h simpleHook) Before(ctx context.Context, query string, args ...interface{}) (context.Context, error) {
return ctx, nil
}
func (h simpleHook) After(ctx context.Context, query string, args ...interface{}) (context.Context, error) {
return ctx, nil
}
func (h simpleHook) OnError(ctx context.Context, err error, query string, args ...interface{}) error {
return err
}
// openDB opens new sql DB pool with an optional hooks and optional tracing.
func openDB(dsn string, hooks []sqlhooks.Hooks) *sql.DB {
var drv driver.Driver = &gomssql.Driver{}
if len(hooks) > 0 {
drv = sqlhooks.Wrap(drv, sqlhooks.Compose(hooks...))
}
// Open DB connections pool.
return sql.OpenDB(&connector{
driver: drv, dsn: dsn,
})
}
// connector is a connector with dsn and driver.
type connector struct {
driver driver.Driver
dsn string
}
// Connect returns a connection to the database.
func (c *connector) Connect(_ context.Context) (driver.Conn, error) {
return c.driver.Open(c.dsn)
}
// Driver returns the underlying Driver of the Connector.
func (c *connector) Driver() driver.Driver {
return c.driver
}
sorry @tochka for the late reply. Unfortunately I haven't had the capacity nor the possibility to experiment with mssql. Have you tried to debug it?
@qustavo just test this code
package main
import (
"fmt"
)
type testError struct {
data []string
}
func (e testError) Error() string {
return "test error"
}
func main() {
var err1 error = testError{}
var err2 error = testError{}
fmt.Println(err1 == err2)
}
And more complex example
https://go.dev/play/p/HdVjUopwCRe
Will reproduce with any error containing a slice or map, passed by value, and if sqlhooks.Compose is used.
package main
import (
"context"
"database/sql"
"database/sql/driver"
"fmt"
"github.com/qustavo/sqlhooks/v2"
)
// similar to https://github.com/microsoft/go-mssqldb/blob/main/error.go#L13
type mssqlError struct{
All []string
}
func (m mssqlError) Error() string {
return ""
}
// fakeHook satisfies the sqlhook.fakeHook interface
type fakeHook struct{}
func (h *fakeHook) OnError(ctx context.Context, err error, query string, args ...interface{}) error {
return mssqlError{}
}
// Before hook will print the query with it's args and return the context with the timestamp
func (h *fakeHook) Before(ctx context.Context, query string, args ...interface{}) (context.Context, error) {
return ctx, nil
}
// After hook will get the timestamp registered on the Before hook and print the elapsed time
func (h *fakeHook) After(ctx context.Context, query string, args ...interface{}) (context.Context, error) {
return ctx, nil
}
var _ driver.Connector = (*fakeConnector)(nil)
var _ sqlhooks.Hooks = (*fakeHook)(nil)
var _ sqlhooks.OnErrorer = (*fakeHook)(nil)
type fakeConnector struct {
driver driver.Driver
}
func (f fakeConnector) Connect(ctx context.Context) (driver.Conn, error) {
return f.driver.Open("fake")
}
func (f fakeConnector) Driver() driver.Driver {
//TODO implement me
panic("implement me")
}
type fakeDriver struct{}
func (f fakeDriver) Open(name string) (driver.Conn, error) {
//TODO implement me
return &fakeConn{}, nil
}
var _ driver.Driver = (*fakeDriver)(nil)
type fakeTx struct{}
func (f fakeTx) Commit() error {
//TODO implement me
panic("implement me")
}
func (f fakeTx) Rollback() error {
//TODO implement me
panic("implement me")
}
var _ driver.Tx = (*fakeTx)(nil)
type fakeConn struct{}
func (f fakeConn) Query(query string, args []driver.Value) (driver.Rows, error) {
return nil, mssqlError{}
}
func (f fakeConn) Ping(ctx context.Context) error {
//TODO implement me
return nil
}
func (f fakeConn) Prepare(query string) (driver.Stmt, error) {
//TODO implement me
return fakeStmt{}, nil
}
func (f fakeConn) Close() error {
//TODO implement me
return nil
}
func (f fakeConn) Begin() (driver.Tx, error) {
//TODO implement me
panic("implement me")
}
func (f fakeConn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) {
//TODO implement me
panic("implement me")
}
var _ driver.Conn = (*fakeConn)(nil)
var _ driver.ConnBeginTx = (*fakeConn)(nil)
var _ driver.Queryer = (*fakeConn)(nil)
type fakeStmt struct{}
func (f fakeStmt) Close() error {
//TODO implement me
return nil
}
func (f fakeStmt) NumInput() int {
//TODO implement me
return 0
}
func (f fakeStmt) Exec(args []driver.Value) (driver.Result, error) {
//TODO implement me
panic("implement me")
}
func (f fakeStmt) Query(args []driver.Value) (driver.Rows, error) {
//TODO implement me
return nil, mssqlError{}
}
var _ driver.Stmt = (*fakeStmt)(nil)
func main() {
drv := sqlhooks.Wrap(fakeDriver{}, sqlhooks.Compose(&fakeHook{}))
// Open DB connections pool.
db := sql.OpenDB(&fakeConnector{
driver: drv,
})
func() {
defer func() {
if recovered := recover(); recovered == nil {
fmt.Println("sqlhooks bug is already fixed. check codebase and upgrade. original issue https://github.com/qustavo/sqlhooks/issues/56")
} else {
fmt.Println("panic as expected: ", recovered)
}
}()
_, _ = db.Query("select 1")
}()
_ = db.Close()
}
Hi from Testcontainers for Go 👋
I think it could be useful to allow developers to run the integration tests while developing this project. I saw they are skipped unless the DSN env var is set. Using tc-go would help in providing the same experience running the tests on CI and locally.
Please let us know if we can help here 🙏