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

runtime error: comparing uncomparable type mssql.Error #56

Open
tochka opened this issue Jun 4, 2024 · 7 comments
Open

runtime error: comparing uncomparable type mssql.Error #56

tochka opened this issue Jun 4, 2024 · 7 comments

Comments

@tochka
Copy link

tochka commented Jun 4, 2024

I've got a panic when using your library (2.1.0) with github.com/microsoft/[email protected] 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/[email protected]/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/[email protected]/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/[email protected]/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
@qustavo
Copy link
Owner

qustavo commented Jun 4, 2024

can you provide the code you are using?

@tochka
Copy link
Author

tochka commented Jun 5, 2024

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
}

@tochka
Copy link
Author

tochka commented Jun 26, 2024

@qustavo
Do you have any news about the panic?

@qustavo
Copy link
Owner

qustavo commented Jun 26, 2024

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?

@pavelpatrin
Copy link

@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)
}

https://go.dev/play/p/SBtKap4-Yki

@gstarikov
Copy link

gstarikov commented Oct 16, 2024

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()
}

@mdelapenya
Copy link

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 🙏

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

No branches or pull requests

5 participants