Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions sql/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,16 @@ go 1.25.0

replace github.com/getsentry/sentry-go => ../

require github.com/getsentry/sentry-go v0.46.0
require (
github.com/getsentry/sentry-go v0.46.0
github.com/stretchr/testify v1.11.1
)

require (
github.com/stretchr/testify v1.11.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/sys v0.18.0 // indirect
golang.org/x/text v0.14.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
10 changes: 10 additions & 0 deletions sql/go.sum
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-errors/errors v1.4.2 h1:J6MZopCL4uSllY1OfXM374weqZFFItUbrImctkmUxIA=
github.com/go-errors/errors v1.4.2/go.mod h1:sIVyrIiJhuEF+Pj9Ebtd6P/rEYROXFi3BopGUQ5a5Og=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0=
github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
github.com/pingcap/errors v0.11.4/go.mod h1:Oi8TUi2kEtXXLMJk9l1cGmz20kV3TaQ0usTwv5KuLY8=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8=
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
Expand All @@ -18,5 +25,8 @@ golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
130 changes: 130 additions & 0 deletions sql/internal/fakedriver/conn.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package fakedriver

import (
"context"
"database/sql/driver"
"io"
)

// ctxConn exposes the full context-aware interface set.
type ctxConn struct {
drv *CtxDriver
}

func (c *ctxConn) Prepare(query string) (driver.Stmt, error) {
return &ctxStmt{drv: c.drv, query: query}, nil
}

func (c *ctxConn) PrepareContext(_ context.Context, query string) (driver.Stmt, error) {
return &ctxStmt{drv: c.drv, query: query}, nil
}

func (c *ctxConn) Close() error { return nil }
func (c *ctxConn) Begin() (driver.Tx, error) { return fakeTx{}, nil }
func (c *ctxConn) BeginTx(_ context.Context, _ driver.TxOptions) (driver.Tx, error) {
return fakeTx{}, nil
}
func (c *ctxConn) Ping(_ context.Context) error { return nil }

func (c *ctxConn) ExecContext(_ context.Context, _ string, _ []driver.NamedValue) (driver.Result, error) {
if c.drv.fail != nil {
return nil, c.drv.fail
}
return fakeResult{}, nil
}

func (c *ctxConn) QueryContext(_ context.Context, _ string, _ []driver.NamedValue) (driver.Rows, error) {
if c.drv.fail != nil {
return nil, c.drv.fail
}
return &fakeRows{}, nil
}

// legacyConn exposes only the pre-context interface set.
type legacyConn struct {
drv *LegacyDriver
}

func (c *legacyConn) Prepare(query string) (driver.Stmt, error) {
return &legacyStmt{drv: c.drv, query: query}, nil
}
func (c *legacyConn) Close() error { return nil }
func (c *legacyConn) Begin() (driver.Tx, error) { return fakeTx{}, nil }

//nolint:staticcheck // intentional legacy driver.Execer implementation.
func (c *legacyConn) Exec(_ string, _ []driver.Value) (driver.Result, error) {
if c.drv.fail != nil {
return nil, c.drv.fail
}
return fakeResult{}, nil
}

//nolint:staticcheck // intentional legacy driver.Queryer implementation.
func (c *legacyConn) Query(_ string, _ []driver.Value) (driver.Rows, error) {
if c.drv.fail != nil {
return nil, c.drv.fail
}
return &fakeRows{}, nil
}

// minimalConn implements only the required driver.Conn methods. It is used to
// force database/sql down the Prepare + Stmt.Exec/Stmt.Query path, exercising
// the wrapper's deepest fallback branches.
type minimalConn struct {
drv *MinimalDriver
}

func (c *minimalConn) Prepare(query string) (driver.Stmt, error) {
return &minimalStmt{drv: c.drv, query: query}, nil
}
func (c *minimalConn) Close() error { return nil }
func (c *minimalConn) Begin() (driver.Tx, error) { return fakeTx{}, nil }

// Compile-time interface guarantees.
var (
_ driver.Conn = (*ctxConn)(nil)
_ driver.ConnPrepareContext = (*ctxConn)(nil)
_ driver.ConnBeginTx = (*ctxConn)(nil)
_ driver.ExecerContext = (*ctxConn)(nil)
_ driver.QueryerContext = (*ctxConn)(nil)
_ driver.Pinger = (*ctxConn)(nil)

_ driver.Conn = (*legacyConn)(nil)
//nolint:staticcheck
_ driver.Execer = (*legacyConn)(nil)
//nolint:staticcheck
_ driver.Queryer = (*legacyConn)(nil)

_ driver.Conn = (*minimalConn)(nil)
)

// fakeTx, fakeResult, and fakeRows are returned by the connection methods
// above. Keeping them here means a reader inspecting what Conn hands out
// doesn't need to jump files.

type fakeTx struct{}

func (fakeTx) Commit() error { return nil }
func (fakeTx) Rollback() error { return nil }

type fakeResult struct{}

func (fakeResult) LastInsertId() (int64, error) { return 0, nil }
func (fakeResult) RowsAffected() (int64, error) { return 0, nil }

type fakeRows struct {
exhausted bool
}

func (r *fakeRows) Columns() []string { return []string{"n"} }
func (r *fakeRows) Close() error { return nil }
func (r *fakeRows) Next(dest []driver.Value) error {
if r.exhausted {
return io.EOF
}
r.exhausted = true
if len(dest) > 0 {
dest[0] = int64(1)
}
return nil
}
164 changes: 164 additions & 0 deletions sql/internal/fakedriver/driver.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
// Package fakedriver is a minimal in-memory database/sql driver used by the
// sentrysql tests. It provides three distinct driver shapes:
//
// - CtxDriver: implements driver.Driver and driver.DriverContext; connections
// satisfy ExecerContext, QueryerContext, ConnPrepareContext, ConnBeginTx,
// and Pinger.
// - LegacyDriver: implements only driver.Driver (no DriverContext); connections
// implement the pre-context Execer, Queryer, Conn.Begin, and Conn.Prepare.
// - MinimalDriver: implements only driver.Driver; connections implement only
// the required driver.Conn methods (Prepare, Close, Begin). No Execer or
// Queryer, which forces database/sql to fall back to Prepare + Stmt.Exec/
// Stmt.Query, exercising the deepest wrapper fallback paths.
//
// Use NewCtx / NewLegacy / NewMinimal to construct each shape and Register to
// expose it by name via database/sql.
package fakedriver

import (
"context"
"database/sql"
"database/sql/driver"
"errors"
"sync"
"sync/atomic"
)

// CtxDriver is a modern driver that implements driver.DriverContext in addition
// to driver.Driver. Connections it returns satisfy the full context-aware
// interface set.
type CtxDriver struct {
fail error
}

// NewCtx returns a new CtxDriver.
func NewCtx() *CtxDriver { return &CtxDriver{} }

// SetFailure makes subsequent Exec/Query calls return err. Pass nil to clear.
func (d *CtxDriver) SetFailure(err error) { d.fail = err }

// Open implements driver.Driver.
func (d *CtxDriver) Open(_ string) (driver.Conn, error) {
return &ctxConn{drv: d}, nil
}

// OpenConnector implements driver.DriverContext.
func (d *CtxDriver) OpenConnector(_ string) (driver.Connector, error) {
return &ctxConnector{drv: d}, nil
}

type ctxConnector struct{ drv *CtxDriver }

func (c *ctxConnector) Connect(_ context.Context) (driver.Conn, error) { return c.drv.Open("") }
func (c *ctxConnector) Driver() driver.Driver { return c.drv }

// LegacyDriver implements only the pre-context driver.Driver interface.
type LegacyDriver struct {
fail error
}

// NewLegacy returns a new LegacyDriver.
func NewLegacy() *LegacyDriver { return &LegacyDriver{} }

// SetFailure makes subsequent Exec/Query calls return err. Pass nil to clear.
func (d *LegacyDriver) SetFailure(err error) { d.fail = err }

// Open implements driver.Driver.
func (d *LegacyDriver) Open(_ string) (driver.Conn, error) {
return &legacyConn{drv: d}, nil
}

// MinimalDriver implements only driver.Driver. Connections it returns
// implement only the required driver.Conn methods — no Execer, Queryer,
// ConnBeginTx, ConnPrepareContext, or Pinger. Used to exercise the wrapper's
// deepest fallback path where ExecContext returns driver.ErrSkip and
// database/sql falls back to Prepare + Stmt.Exec/Stmt.Query.
type MinimalDriver struct {
fail error
}

// NewMinimal returns a new MinimalDriver.
func NewMinimal() *MinimalDriver { return &MinimalDriver{} }

// SetFailure makes subsequent Stmt.Exec/Stmt.Query calls return err. Pass nil
// to clear.
func (d *MinimalDriver) SetFailure(err error) { d.fail = err }

// Open implements driver.Driver.
func (d *MinimalDriver) Open(_ string) (driver.Conn, error) {
return &minimalConn{drv: d}, nil
}

// CtxConnector is an exported connector wrapper backed by a CtxDriver. Unlike
// the internal ctxConnector returned by CtxDriver.OpenConnector, this one
// implements io.Closer with an observable close counter so tests can verify
// that the sentrysql wrapper propagates DB.Close through to inner connectors.
type CtxConnector struct {
drv *CtxDriver
count atomic.Int32
}

// NewCtxConnector constructs a CtxConnector.
func NewCtxConnector(drv *CtxDriver) *CtxConnector { return &CtxConnector{drv: drv} }

// Connect implements driver.Connector.
func (c *CtxConnector) Connect(_ context.Context) (driver.Conn, error) { return c.drv.Open("") }

// Driver implements driver.Connector.
func (c *CtxConnector) Driver() driver.Driver { return c.drv }

// Close implements io.Closer. The counter it increments is observable via
// CloseCount for verification in tests.
func (c *CtxConnector) Close() error { c.count.Add(1); return nil }

// CloseCount reports how many times Close was invoked on this connector.
func (c *CtxConnector) CloseCount() int { return int(c.count.Load()) }

// LegacyConnector wraps a LegacyDriver as a driver.Connector so tests can
// exercise the sentrysql wrapper's behavior when a connector's Driver() does
// not implement driver.DriverContext. It does not implement io.Closer.
type LegacyConnector struct {
drv *LegacyDriver
}

// NewLegacyConnector constructs a LegacyConnector.
func NewLegacyConnector(drv *LegacyDriver) *LegacyConnector { return &LegacyConnector{drv: drv} }

// Connect implements driver.Connector.
func (c *LegacyConnector) Connect(_ context.Context) (driver.Conn, error) { return c.drv.Open("") }

// Driver implements driver.Connector.
func (c *LegacyConnector) Driver() driver.Driver { return c.drv }

// Compile-time interface guarantees.
var (
_ driver.Driver = (*CtxDriver)(nil)
_ driver.DriverContext = (*CtxDriver)(nil)

_ driver.Driver = (*LegacyDriver)(nil)

_ driver.Driver = (*MinimalDriver)(nil)

_ driver.Connector = (*CtxConnector)(nil)
_ driver.Connector = (*LegacyConnector)(nil)
)

var (
registerMu sync.Mutex
registered = map[string]struct{}{}
)

// Register registers d under name with database/sql. Safe to call multiple
// times with the same name.
func Register(name string, d driver.Driver) {
registerMu.Lock()
defer registerMu.Unlock()
if _, ok := registered[name]; ok {
return
}
sql.Register(name, d)
registered[name] = struct{}{}
}

// ErrDriver is a reusable error for failure-injection tests.
var ErrDriver = errors.New("fakedriver: error")
Loading
Loading