From e5443140949c18618115044d696c81f6e60ac569 Mon Sep 17 00:00:00 2001 From: Reinaldy Rafli Date: Fri, 18 Oct 2024 17:21:03 +0700 Subject: [PATCH 01/26] feat: initial sentrysql implementation --- go.mod | 3 + go.sum | 14 +++ sentrysql/conn.go | 243 ++++++++++++++++++++++++++++++++++++ sentrysql/driver.go | 52 ++++++++ sentrysql/options.go | 25 ++++ sentrysql/sentrysql.go | 46 +++++++ sentrysql/sentrysql_test.go | 58 +++++++++ sentrysql/stmt.go | 188 ++++++++++++++++++++++++++++ sentrysql/tx.go | 22 ++++ 9 files changed, 651 insertions(+) create mode 100644 sentrysql/conn.go create mode 100644 sentrysql/driver.go create mode 100644 sentrysql/options.go create mode 100644 sentrysql/sentrysql.go create mode 100644 sentrysql/sentrysql_test.go create mode 100644 sentrysql/stmt.go create mode 100644 sentrysql/tx.go diff --git a/go.mod b/go.mod index 7c7a31ea..268b053a 100644 --- a/go.mod +++ b/go.mod @@ -9,8 +9,10 @@ require ( github.com/google/go-cmp v0.5.9 github.com/kataras/iris/v12 v12.2.0 github.com/labstack/echo/v4 v4.10.0 + github.com/lib/pq v1.10.9 github.com/pingcap/errors v0.11.4 github.com/pkg/errors v0.9.1 + github.com/proullon/ramsql v0.1.4 github.com/sirupsen/logrus v1.9.0 github.com/stretchr/testify v1.8.2 github.com/urfave/negroni/v3 v3.1.1 @@ -88,6 +90,7 @@ require ( github.com/yudai/gojsondiff v1.0.0 // indirect github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect golang.org/x/crypto v0.21.0 // indirect + golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 // indirect golang.org/x/net v0.23.0 // indirect golang.org/x/time v0.3.0 // indirect google.golang.org/protobuf v1.33.0 // indirect diff --git a/go.sum b/go.sum index 2144722a..73f7b1bd 100644 --- a/go.sum +++ b/go.sum @@ -39,6 +39,7 @@ github.com/gin-gonic/gin v1.8.1 h1:4+fr/el88TOO3ewCmQr8cx/CtZ/umlIRIs5M4NTNjf8= github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk= 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/go-gorp/gorp v2.2.0+incompatible h1:xAUh4QgEeqPPhK3vxZN+bzrim1z5Av6q837gtjUlshc= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= @@ -71,6 +72,11 @@ github.com/iris-contrib/httpexpect/v2 v2.12.1 h1:3cTZSyBBen/kfjCtgNFoUKi1u0FVXNa github.com/iris-contrib/httpexpect/v2 v2.12.1/go.mod h1:7+RB6W5oNClX7PTwJgJnsQP3ZuUUYB3u61KCqeSgZ88= github.com/iris-contrib/schema v0.0.6 h1:CPSBLyx2e91H2yJzPuhGuifVRnZBBJ3pCOMbOvPZaTw= github.com/iris-contrib/schema v0.0.6/go.mod h1:iYszG0IOsuIsfzjymw1kMzTL8YQcCWlm65f3wX8J5iA= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= +github.com/jackc/pgx/v5 v5.3.1 h1:Fcr8QJ1ZeLi5zsPZqQeUZhNhxfkkKBOgJuYkJHoBOtU= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= @@ -103,6 +109,8 @@ github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8 github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= github.com/mailgun/raymond/v2 v2.0.48 h1:5dmlB680ZkFG2RN/0lvTAghrSxIESeu9/2aeDqACtjw= github.com/mailgun/raymond/v2 v2.0.48/go.mod h1:lsgvL50kgt1ylcFJYZiULi5fjPBkkhNfj4KA0W54Z18= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= @@ -142,6 +150,8 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 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/proullon/ramsql v0.1.4 h1:yTFRTn46gFH/kPbzCx+mGjuFlyTBUeDr3h2ldwxddl0= +github.com/proullon/ramsql v0.1.4/go.mod h1:CFGqeQHQpdRfWqYmWD3yXqPTEaHkF4zgXy1C6qDWc9E= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= @@ -220,6 +230,8 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 h1:MGwJjxBy0HJshjDNfLsYO8xppfqWlA5ZT9OhtUUhTNw= +golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= @@ -289,5 +301,7 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/postgres v1.5.2 h1:ytTDxxEv+MplXOfFe3Lzm7SjG09fcdb3Z/c056DTBx0= +gorm.io/gorm v1.25.2 h1:gs1o6Vsa+oVKG/a9ElL3XgyGfghFfkKA2SInQaCyMho= moul.io/http2curl/v2 v2.3.0 h1:9r3JfDzWPcbIklMOs2TnIFzDYvfAZvjeavG6EzP7jYs= moul.io/http2curl/v2 v2.3.0/go.mod h1:RW4hyBjTWSYDOxapodpNEtX0g5Eb16sxklBqmd2RHcE= diff --git a/sentrysql/conn.go b/sentrysql/conn.go new file mode 100644 index 00000000..4659de63 --- /dev/null +++ b/sentrysql/conn.go @@ -0,0 +1,243 @@ +package sentrysql + +import ( + "context" + "database/sql/driver" + + "github.com/getsentry/sentry-go" +) + +type sentryConn struct { + originalConn driver.Conn + ctx context.Context + config *sentrySqlConfig +} + +func (s *sentryConn) Prepare(query string) (driver.Stmt, error) { + stmt, err := s.originalConn.Prepare(query) + if err != nil { + return nil, err + } + + return &sentryStmt{ + originalStmt: stmt, + query: query, + ctx: s.ctx, + config: s.config, + }, nil +} + +func (s *sentryConn) PrepareContext(ctx context.Context, query string) (driver.Stmt, error) { + // should only be executed if the original driver implements ConnPrepareContext + connPrepareContext, ok := s.originalConn.(driver.ConnPrepareContext) + if !ok { + return nil, driver.ErrSkip + } + + stmt, err := connPrepareContext.PrepareContext(ctx, query) + if err != nil { + return nil, err + } + + return &sentryStmt{ + originalStmt: stmt, + query: query, + ctx: ctx, + config: s.config, + }, nil +} + +func (s *sentryConn) Close() error { + return s.originalConn.Close() +} + +func (s *sentryConn) Begin() (driver.Tx, error) { + tx, err := s.originalConn.Begin() + if err != nil { + return nil, err + } + + return &sentryTx{originalTx: tx, ctx: s.ctx, config: s.config}, nil +} + +func (s *sentryConn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver.Tx, error) { + // should only be executed if the original driver implements ConnBeginTx + connBeginTx, ok := s.originalConn.(driver.ConnBeginTx) + if !ok { + // fallback to the so-called deprecated "Begin" method + return s.Begin() + } + + tx, err := connBeginTx.BeginTx(ctx, opts) + if err != nil { + return nil, err + } + + return &sentryTx{originalTx: tx, ctx: s.ctx, config: s.config}, nil +} + +func (s *sentryConn) Query(query string, args []driver.Value) (driver.Rows, error) { + // should only be executed if the original driver implements Queryer + queryer, ok := s.originalConn.(driver.Queryer) + if !ok { + return nil, driver.ErrSkip + } + + parentSpan := sentry.SpanFromContext(s.ctx) + if parentSpan == nil { + return queryer.Query(query, args) + } + + span := parentSpan.StartChild("db.sql.query", sentry.WithDescription(query)) + if s.config.databaseSystem != "" { + span.SetData("db.system", s.config.databaseSystem) + } + if s.config.databaseName != "" { + span.SetData("db.name", s.config.databaseName) + } + if s.config.serverAddress != "" { + span.SetData("server.address", s.config.serverAddress) + } + if s.config.serverPort != "" { + span.SetData("server.port", s.config.serverPort) + } + defer span.Finish() + + rows, err := queryer.Query(query, args) + if err != nil { + span.Status = sentry.SpanStatusInternalError + return nil, err + } + + span.Status = sentry.SpanStatusOK + return rows, nil +} + +func (s *sentryConn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error) { + // should only be executed if the original driver implements QueryerContext + queryerContext, ok := s.originalConn.(driver.QueryerContext) + if !ok { + return nil, driver.ErrSkip + } + + parentSpan := sentry.SpanFromContext(ctx) + if parentSpan == nil { + return queryerContext.QueryContext(ctx, query, args) + } + + span := parentSpan.StartChild("db.sql.query", sentry.WithDescription(query)) + if s.config.databaseSystem != "" { + span.SetData("db.system", s.config.databaseSystem) + } + if s.config.databaseName != "" { + span.SetData("db.name", s.config.databaseName) + } + if s.config.serverAddress != "" { + span.SetData("server.address", s.config.serverAddress) + } + if s.config.serverPort != "" { + span.SetData("server.port", s.config.serverPort) + } + defer span.Finish() + + rows, err := queryerContext.QueryContext(ctx, query, args) + if err != nil { + span.Status = sentry.SpanStatusInternalError + return nil, err + } + + span.Status = sentry.SpanStatusOK + return rows, nil +} + +func (s *sentryConn) Exec(query string, args []driver.Value) (driver.Result, error) { + // should only be executed if the original driver implements Execer + execer, ok := s.originalConn.(driver.Execer) + if !ok { + return nil, driver.ErrSkip + } + + parentSpan := sentry.SpanFromContext(s.ctx) + if parentSpan == nil { + return execer.Exec(query, args) + } + + span := parentSpan.StartChild("db.sql.exec", sentry.WithDescription(query)) + if s.config.databaseSystem != "" { + span.SetData("db.system", s.config.databaseSystem) + } + if s.config.databaseName != "" { + span.SetData("db.name", s.config.databaseName) + } + if s.config.serverAddress != "" { + span.SetData("server.address", s.config.serverAddress) + } + if s.config.serverPort != "" { + span.SetData("server.port", s.config.serverPort) + } + defer span.Finish() + + rows, err := execer.Exec(query, args) + if err != nil { + span.Status = sentry.SpanStatusInternalError + return nil, err + } + + span.Status = sentry.SpanStatusOK + return rows, nil +} + +func (s *sentryConn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error) { + // should only be executed if the original driver implements ExecerContext { + execerContext, ok := s.originalConn.(driver.ExecerContext) + if !ok { + return nil, driver.ErrSkip + } + + parentSpan := sentry.SpanFromContext(ctx) + if parentSpan == nil { + return execerContext.ExecContext(ctx, query, args) + } + + span := parentSpan.StartChild("db.sql.exec", sentry.WithDescription(query)) + if s.config.databaseSystem != "" { + span.SetData("db.system", s.config.databaseSystem) + } + if s.config.databaseName != "" { + span.SetData("db.name", s.config.databaseName) + } + if s.config.serverAddress != "" { + span.SetData("server.address", s.config.serverAddress) + } + if s.config.serverPort != "" { + span.SetData("server.port", s.config.serverPort) + } + defer span.Finish() + + rows, err := execerContext.ExecContext(ctx, query, args) + if err != nil { + span.Status = sentry.SpanStatusInternalError + return nil, err + } + + span.Status = sentry.SpanStatusOK + return rows, nil +} + +func (s *sentryConn) Ping(ctx context.Context) error { + pinger, ok := s.originalConn.(driver.Pinger) + if !ok { + return driver.ErrSkip + } + + return pinger.Ping(ctx) +} + +func (s *sentryConn) CheckNamedValue(namedValue *driver.NamedValue) error { + namedValueChecker, ok := s.originalConn.(driver.NamedValueChecker) + if !ok { + return driver.ErrSkip + } + + return namedValueChecker.CheckNamedValue(namedValue) +} diff --git a/sentrysql/driver.go b/sentrysql/driver.go new file mode 100644 index 00000000..5d2fac06 --- /dev/null +++ b/sentrysql/driver.go @@ -0,0 +1,52 @@ +package sentrysql + +import ( + "context" + "database/sql/driver" +) + +type sentrySqlDriver struct { + originalDriver driver.Driver + config *sentrySqlConfig +} + +func (s *sentrySqlDriver) OpenConnector(name string) (driver.Connector, error) { + driverContext, ok := s.originalDriver.(driver.DriverContext) + if !ok { + return nil, driver.ErrSkip + } + + connector, err := driverContext.OpenConnector(name) + if err != nil { + return nil, err + } + + return &sentrySqlConnector{originalConnector: connector, config: s.config}, nil +} + +func (s *sentrySqlDriver) Open(name string) (driver.Conn, error) { + conn, err := s.originalDriver.Open(name) + if err != nil { + return nil, err + } + + return &sentryConn{originalConn: conn, config: s.config}, nil +} + +type sentrySqlConnector struct { + originalConnector driver.Connector + config *sentrySqlConfig +} + +func (s *sentrySqlConnector) Connect(ctx context.Context) (driver.Conn, error) { + conn, err := s.originalConnector.Connect(ctx) + if err != nil { + return nil, err + } + + return &sentryConn{originalConn: conn, ctx: ctx, config: s.config}, nil +} + +func (s *sentrySqlConnector) Driver() driver.Driver { + return s.originalConnector.Driver() +} diff --git a/sentrysql/options.go b/sentrysql/options.go new file mode 100644 index 00000000..a4e6ef04 --- /dev/null +++ b/sentrysql/options.go @@ -0,0 +1,25 @@ +package sentrysql + +type SentrySqlTracerOption func(*sentrySqlConfig) + +// WithDatabaseSystem specifies the current database system. +func WithDatabaseSystem(system DatabaseSystem) SentrySqlTracerOption { + return func(config *sentrySqlConfig) { + config.databaseSystem = system + } +} + +// WithDatabaseName specifies the name of the current database. +func WithDatabaseName(name string) SentrySqlTracerOption { + return func(config *sentrySqlConfig) { + config.databaseName = name + } +} + +// WithServerAddress specifies the address and port of the current database server. +func WithServerAddress(address string, port string) SentrySqlTracerOption { + return func(config *sentrySqlConfig) { + config.serverAddress = address + config.serverPort = port + } +} diff --git a/sentrysql/sentrysql.go b/sentrysql/sentrysql.go new file mode 100644 index 00000000..0930fe5a --- /dev/null +++ b/sentrysql/sentrysql.go @@ -0,0 +1,46 @@ +package sentrysql + +import "database/sql/driver" + +// DatabaseSystem points to the list of accepted OpenTelemetry database system. +// The ones defined here are not exhaustive, but are the ones that are supported by Sentry. +// Although you can override the value by creating your own, it will still be sent to Sentry, +// but it most likely will not appear on the Queries Insights page. +type DatabaseSystem string + +const ( + PostgreSQL DatabaseSystem = "postgresql" + MySQL DatabaseSystem = "mysql" + SQLite DatabaseSystem = "sqlite" + Oracle DatabaseSystem = "oracle" + MSSQL DatabaseSystem = "mssql" +) + +type sentrySqlConfig struct { + databaseSystem DatabaseSystem + databaseName string + serverAddress string + serverPort string +} + +// NewSentrySql is a wrapper for driver.Driver that provides tracing for SQL queries. +// The span will only be created if the parent span is available. +func NewSentrySql(driver driver.Driver, options ...SentrySqlTracerOption) driver.Driver { + var config sentrySqlConfig + for _, option := range options { + option(&config) + } + + return &sentrySqlDriver{originalDriver: driver, config: &config} +} + +// NewSentrySqlConnector is a wrapper for driver.Connector that provides tracing for SQL queries. +// The span will only be created if the parent span is available. +func NewSentrySqlConnector(connector driver.Connector, options ...SentrySqlTracerOption) driver.Connector { + var config sentrySqlConfig + for _, option := range options { + option(&config) + } + + return &sentrySqlConnector{originalConnector: connector, config: &config} +} diff --git a/sentrysql/sentrysql_test.go b/sentrysql/sentrysql_test.go new file mode 100644 index 00000000..3001ae7e --- /dev/null +++ b/sentrysql/sentrysql_test.go @@ -0,0 +1,58 @@ +package sentrysql_test + +import ( + "database/sql" + "fmt" + + "github.com/getsentry/sentry-go/sentrysql" + "github.com/lib/pq" + ramsqldriver "github.com/proullon/ramsql/driver" +) + +func ExampleNewSentrySql() { + sql.Register("sentrysql-ramsql", sentrysql.NewSentrySql(ramsqldriver.NewDriver(), sentrysql.WithDatabaseName("TestDriver"), sentrysql.WithDatabaseSystem("ramsql"), sentrysql.WithServerAddress("127.0.0.1", "3306"))) + + db, err := sql.Open("sentrysql-ramsql", "TestDriver") + if err != nil { + panic(err) + } + defer db.Close() + + _, err = db.Exec("CREATE TABLE test (id INT)") + if err != nil { + panic(err) + } + + _, err = db.Exec("INSERT INTO test (id) VALUES (1)") + if err != nil { + panic(err) + } + + rows, err := db.Query("SELECT * FROM test") + if err != nil { + panic(err) + } + defer rows.Close() + + for rows.Next() { + var id int + err = rows.Scan(&id) + if err != nil { + panic(err) + } + + fmt.Println(id) + } +} + +func ExampleNewSentrySqlConnector() { + pqConnector, err := pq.NewConnector("postgres://user:password@localhost:5432/db") + if err != nil { + panic(err) + } + + db := sql.OpenDB(sentrysql.NewSentrySqlConnector(pqConnector, sentrysql.WithDatabaseName("db"), sentrysql.WithDatabaseSystem("postgres"), sentrysql.WithServerAddress("localhost", "5432"))) + defer db.Close() + + // Continue executing PostgreSQL queries +} diff --git a/sentrysql/stmt.go b/sentrysql/stmt.go new file mode 100644 index 00000000..6bb90a0e --- /dev/null +++ b/sentrysql/stmt.go @@ -0,0 +1,188 @@ +package sentrysql + +import ( + "context" + "database/sql/driver" + + "github.com/getsentry/sentry-go" +) + +type sentryStmt struct { + originalStmt driver.Stmt + query string + ctx context.Context + config *sentrySqlConfig +} + +func (s *sentryStmt) Close() error { + return s.originalStmt.Close() +} + +func (s *sentryStmt) NumInput() int { + return s.originalStmt.NumInput() +} + +func (s *sentryStmt) Exec(args []driver.Value) (driver.Result, error) { + parentSpan := sentry.SpanFromContext(s.ctx) + if parentSpan == nil { + return s.originalStmt.Exec(args) + } + + span := parentSpan.StartChild("db.sql.exec", sentry.WithDescription(s.query)) + if s.config.databaseSystem != "" { + span.SetData("db.system", s.config.databaseSystem) + } + if s.config.databaseName != "" { + span.SetData("db.name", s.config.databaseName) + } + if s.config.serverAddress != "" { + span.SetData("server.address", s.config.serverAddress) + } + if s.config.serverPort != "" { + span.SetData("server.port", s.config.serverPort) + } + + result, err := s.originalStmt.Exec(args) + if err != nil { + span.Status = sentry.SpanStatusInternalError + span.Finish() + return nil, err + } + + span.Status = sentry.SpanStatusOK + span.Finish() + + return result, nil +} + +func (s *sentryStmt) Query(args []driver.Value) (driver.Rows, error) { + parentSpan := sentry.SpanFromContext(s.ctx) + if parentSpan == nil { + return s.originalStmt.Query(args) + } + + span := parentSpan.StartChild("db.sql.query", sentry.WithDescription(s.query)) + if s.config.databaseSystem != "" { + span.SetData("db.system", s.config.databaseSystem) + } + if s.config.databaseName != "" { + span.SetData("db.name", s.config.databaseName) + } + if s.config.serverAddress != "" { + span.SetData("server.address", s.config.serverAddress) + } + if s.config.serverPort != "" { + span.SetData("server.port", s.config.serverPort) + } + + rows, err := s.originalStmt.Query(args) + if err != nil { + span.Status = sentry.SpanStatusInternalError + span.Finish() + + return nil, err + } + + span.Status = sentry.SpanStatusOK + span.Finish() + + return rows, nil +} + +func (s *sentryStmt) ExecContext(ctx context.Context, args []driver.NamedValue) (driver.Result, error) { + // should only be executed if the original driver implements StmtExecContext + stmtExecContext, ok := s.originalStmt.(driver.StmtExecContext) + if !ok { + // fallback to the so-called deprecated "Exec" method + var values []driver.Value + for _, nv := range args { + values = append(values, nv.Value) + } + return s.Exec(values) + } + + parentSpan := sentry.SpanFromContext(s.ctx) + if parentSpan == nil { + return stmtExecContext.ExecContext(ctx, args) + } + + span := parentSpan.StartChild("db.sql.exec", sentry.WithDescription(s.query)) + if s.config.databaseSystem != "" { + span.SetData("db.system", s.config.databaseSystem) + } + if s.config.databaseName != "" { + span.SetData("db.name", s.config.databaseName) + } + if s.config.serverAddress != "" { + span.SetData("server.address", s.config.serverAddress) + } + if s.config.serverPort != "" { + span.SetData("server.port", s.config.serverPort) + } + + result, err := stmtExecContext.ExecContext(ctx, args) + if err != nil { + span.Status = sentry.SpanStatusInternalError + span.Finish() + return nil, err + } + + span.Status = sentry.SpanStatusOK + span.Finish() + + return result, nil +} + +func (s *sentryStmt) QueryContext(ctx context.Context, args []driver.NamedValue) (driver.Rows, error) { + // should only be executed if the original driver implements StmtQueryContext + stmtQueryContext, ok := s.originalStmt.(driver.StmtQueryContext) + if !ok { + // fallback to the so-called deprecated "Query" method + var values []driver.Value + for _, nv := range args { + values = append(values, nv.Value) + } + return s.Query(values) + } + + parentSpan := sentry.SpanFromContext(ctx) + if parentSpan == nil { + return stmtQueryContext.QueryContext(ctx, args) + } + + span := parentSpan.StartChild("db.sql.query", sentry.WithDescription(s.query)) + if s.config.databaseSystem != "" { + span.SetData("db.system", s.config.databaseSystem) + } + if s.config.databaseName != "" { + span.SetData("db.name", s.config.databaseName) + } + if s.config.serverAddress != "" { + span.SetData("server.address", s.config.serverAddress) + } + if s.config.serverPort != "" { + span.SetData("server.port", s.config.serverPort) + } + + rows, err := stmtQueryContext.QueryContext(ctx, args) + if err != nil { + span.Status = sentry.SpanStatusInternalError + span.Finish() + + return nil, err + } + + span.Status = sentry.SpanStatusOK + span.Finish() + + return rows, nil +} + +func (s *sentryStmt) CheckNamedValue(namedValue *driver.NamedValue) error { + namedValueChecker, ok := s.originalStmt.(driver.NamedValueChecker) + if !ok { + return driver.ErrSkip + } + + return namedValueChecker.CheckNamedValue(namedValue) +} diff --git a/sentrysql/tx.go b/sentrysql/tx.go new file mode 100644 index 00000000..3b19ccb7 --- /dev/null +++ b/sentrysql/tx.go @@ -0,0 +1,22 @@ +package sentrysql + +import ( + "context" + "database/sql/driver" +) + +type sentryTx struct { + originalTx driver.Tx + ctx context.Context + config *sentrySqlConfig +} + +// Commit implements driver.Tx. +func (s *sentryTx) Commit() error { + return s.originalTx.Commit() +} + +// Rollback implements driver.Tx. +func (s *sentryTx) Rollback() error { + return s.originalTx.Rollback() +} From 92e3a6bfecb38265b88b9613662163db9c90c75c Mon Sep 17 00:00:00 2001 From: Reinaldy Rafli Date: Fri, 18 Oct 2024 17:40:04 +0700 Subject: [PATCH 02/26] chore: resolve a few lint issues --- sentrysql/conn.go | 4 +++- sentrysql/driver.go | 18 +++++++++--------- sentrysql/options.go | 14 +++++++------- sentrysql/sentrysql.go | 14 +++++++------- sentrysql/sentrysql_test.go | 4 ++-- sentrysql/stmt.go | 6 +++++- sentrysql/tx.go | 2 +- 7 files changed, 34 insertions(+), 28 deletions(-) diff --git a/sentrysql/conn.go b/sentrysql/conn.go index 4659de63..29fe88e7 100644 --- a/sentrysql/conn.go +++ b/sentrysql/conn.go @@ -10,7 +10,7 @@ import ( type sentryConn struct { originalConn driver.Conn ctx context.Context - config *sentrySqlConfig + config *sentrySQLConfig } func (s *sentryConn) Prepare(query string) (driver.Stmt, error) { @@ -52,6 +52,7 @@ func (s *sentryConn) Close() error { } func (s *sentryConn) Begin() (driver.Tx, error) { + //nolint:staticcheck We must support legacy clients tx, err := s.originalConn.Begin() if err != nil { return nil, err @@ -152,6 +153,7 @@ func (s *sentryConn) QueryContext(ctx context.Context, query string, args []driv func (s *sentryConn) Exec(query string, args []driver.Value) (driver.Result, error) { // should only be executed if the original driver implements Execer + //nolint:staticcheck We must support legacy clients execer, ok := s.originalConn.(driver.Execer) if !ok { return nil, driver.ErrSkip diff --git a/sentrysql/driver.go b/sentrysql/driver.go index 5d2fac06..284a9608 100644 --- a/sentrysql/driver.go +++ b/sentrysql/driver.go @@ -5,12 +5,12 @@ import ( "database/sql/driver" ) -type sentrySqlDriver struct { +type sentrySQLDriver struct { originalDriver driver.Driver - config *sentrySqlConfig + config *sentrySQLConfig } -func (s *sentrySqlDriver) OpenConnector(name string) (driver.Connector, error) { +func (s *sentrySQLDriver) OpenConnector(name string) (driver.Connector, error) { driverContext, ok := s.originalDriver.(driver.DriverContext) if !ok { return nil, driver.ErrSkip @@ -21,10 +21,10 @@ func (s *sentrySqlDriver) OpenConnector(name string) (driver.Connector, error) { return nil, err } - return &sentrySqlConnector{originalConnector: connector, config: s.config}, nil + return &sentrySQLConnector{originalConnector: connector, config: s.config}, nil } -func (s *sentrySqlDriver) Open(name string) (driver.Conn, error) { +func (s *sentrySQLDriver) Open(name string) (driver.Conn, error) { conn, err := s.originalDriver.Open(name) if err != nil { return nil, err @@ -33,12 +33,12 @@ func (s *sentrySqlDriver) Open(name string) (driver.Conn, error) { return &sentryConn{originalConn: conn, config: s.config}, nil } -type sentrySqlConnector struct { +type sentrySQLConnector struct { originalConnector driver.Connector - config *sentrySqlConfig + config *sentrySQLConfig } -func (s *sentrySqlConnector) Connect(ctx context.Context) (driver.Conn, error) { +func (s *sentrySQLConnector) Connect(ctx context.Context) (driver.Conn, error) { conn, err := s.originalConnector.Connect(ctx) if err != nil { return nil, err @@ -47,6 +47,6 @@ func (s *sentrySqlConnector) Connect(ctx context.Context) (driver.Conn, error) { return &sentryConn{originalConn: conn, ctx: ctx, config: s.config}, nil } -func (s *sentrySqlConnector) Driver() driver.Driver { +func (s *sentrySQLConnector) Driver() driver.Driver { return s.originalConnector.Driver() } diff --git a/sentrysql/options.go b/sentrysql/options.go index a4e6ef04..23d28939 100644 --- a/sentrysql/options.go +++ b/sentrysql/options.go @@ -1,24 +1,24 @@ package sentrysql -type SentrySqlTracerOption func(*sentrySqlConfig) +type SentrySQLOption func(*sentrySQLConfig) // WithDatabaseSystem specifies the current database system. -func WithDatabaseSystem(system DatabaseSystem) SentrySqlTracerOption { - return func(config *sentrySqlConfig) { +func WithDatabaseSystem(system DatabaseSystem) SentrySQLOption { + return func(config *sentrySQLConfig) { config.databaseSystem = system } } // WithDatabaseName specifies the name of the current database. -func WithDatabaseName(name string) SentrySqlTracerOption { - return func(config *sentrySqlConfig) { +func WithDatabaseName(name string) SentrySQLOption { + return func(config *sentrySQLConfig) { config.databaseName = name } } // WithServerAddress specifies the address and port of the current database server. -func WithServerAddress(address string, port string) SentrySqlTracerOption { - return func(config *sentrySqlConfig) { +func WithServerAddress(address string, port string) SentrySQLOption { + return func(config *sentrySQLConfig) { config.serverAddress = address config.serverPort = port } diff --git a/sentrysql/sentrysql.go b/sentrysql/sentrysql.go index 0930fe5a..ed5cf3c8 100644 --- a/sentrysql/sentrysql.go +++ b/sentrysql/sentrysql.go @@ -16,7 +16,7 @@ const ( MSSQL DatabaseSystem = "mssql" ) -type sentrySqlConfig struct { +type sentrySQLConfig struct { databaseSystem DatabaseSystem databaseName string serverAddress string @@ -25,22 +25,22 @@ type sentrySqlConfig struct { // NewSentrySql is a wrapper for driver.Driver that provides tracing for SQL queries. // The span will only be created if the parent span is available. -func NewSentrySql(driver driver.Driver, options ...SentrySqlTracerOption) driver.Driver { - var config sentrySqlConfig +func NewSentrySql(driver driver.Driver, options ...SentrySQLOption) driver.Driver { + var config sentrySQLConfig for _, option := range options { option(&config) } - return &sentrySqlDriver{originalDriver: driver, config: &config} + return &sentrySQLDriver{originalDriver: driver, config: &config} } // NewSentrySqlConnector is a wrapper for driver.Connector that provides tracing for SQL queries. // The span will only be created if the parent span is available. -func NewSentrySqlConnector(connector driver.Connector, options ...SentrySqlTracerOption) driver.Connector { - var config sentrySqlConfig +func NewSentrySqlConnector(connector driver.Connector, options ...SentrySQLOption) driver.Connector { + var config sentrySQLConfig for _, option := range options { option(&config) } - return &sentrySqlConnector{originalConnector: connector, config: &config} + return &sentrySQLConnector{originalConnector: connector, config: &config} } diff --git a/sentrysql/sentrysql_test.go b/sentrysql/sentrysql_test.go index 3001ae7e..bfa5abb6 100644 --- a/sentrysql/sentrysql_test.go +++ b/sentrysql/sentrysql_test.go @@ -10,7 +10,7 @@ import ( ) func ExampleNewSentrySql() { - sql.Register("sentrysql-ramsql", sentrysql.NewSentrySql(ramsqldriver.NewDriver(), sentrysql.WithDatabaseName("TestDriver"), sentrysql.WithDatabaseSystem("ramsql"), sentrysql.WithServerAddress("127.0.0.1", "3306"))) + sql.Register("sentrysql-ramsql", sentrysql.NewSentrySql(ramsqldriver.NewDriver(), sentrysql.WithDatabaseName("TestDriver"), sentrysql.WithDatabaseSystem(sentrysql.DatabaseSystem("ramsql")), sentrysql.WithServerAddress("127.0.0.1", "3306"))) db, err := sql.Open("sentrysql-ramsql", "TestDriver") if err != nil { @@ -51,7 +51,7 @@ func ExampleNewSentrySqlConnector() { panic(err) } - db := sql.OpenDB(sentrysql.NewSentrySqlConnector(pqConnector, sentrysql.WithDatabaseName("db"), sentrysql.WithDatabaseSystem("postgres"), sentrysql.WithServerAddress("localhost", "5432"))) + db := sql.OpenDB(sentrysql.NewSentrySqlConnector(pqConnector, sentrysql.WithDatabaseName("db"), sentrysql.WithDatabaseSystem(sentrysql.PostgreSQL), sentrysql.WithServerAddress("localhost", "5432"))) defer db.Close() // Continue executing PostgreSQL queries diff --git a/sentrysql/stmt.go b/sentrysql/stmt.go index 6bb90a0e..330e6b27 100644 --- a/sentrysql/stmt.go +++ b/sentrysql/stmt.go @@ -11,7 +11,7 @@ type sentryStmt struct { originalStmt driver.Stmt query string ctx context.Context - config *sentrySqlConfig + config *sentrySQLConfig } func (s *sentryStmt) Close() error { @@ -25,6 +25,7 @@ func (s *sentryStmt) NumInput() int { func (s *sentryStmt) Exec(args []driver.Value) (driver.Result, error) { parentSpan := sentry.SpanFromContext(s.ctx) if parentSpan == nil { + //nolint:staticcheck We must support legacy clients return s.originalStmt.Exec(args) } @@ -42,6 +43,7 @@ func (s *sentryStmt) Exec(args []driver.Value) (driver.Result, error) { span.SetData("server.port", s.config.serverPort) } + //nolint:staticcheck We must support legacy clients result, err := s.originalStmt.Exec(args) if err != nil { span.Status = sentry.SpanStatusInternalError @@ -58,6 +60,7 @@ func (s *sentryStmt) Exec(args []driver.Value) (driver.Result, error) { func (s *sentryStmt) Query(args []driver.Value) (driver.Rows, error) { parentSpan := sentry.SpanFromContext(s.ctx) if parentSpan == nil { + //nolint:staticcheck We must support legacy clients return s.originalStmt.Query(args) } @@ -75,6 +78,7 @@ func (s *sentryStmt) Query(args []driver.Value) (driver.Rows, error) { span.SetData("server.port", s.config.serverPort) } + //nolint:staticcheck We must support legacy clients rows, err := s.originalStmt.Query(args) if err != nil { span.Status = sentry.SpanStatusInternalError diff --git a/sentrysql/tx.go b/sentrysql/tx.go index 3b19ccb7..51478669 100644 --- a/sentrysql/tx.go +++ b/sentrysql/tx.go @@ -8,7 +8,7 @@ import ( type sentryTx struct { originalTx driver.Tx ctx context.Context - config *sentrySqlConfig + config *sentrySQLConfig } // Commit implements driver.Tx. From aa4acb2a62142269dfdced27d0864a943b788627 Mon Sep 17 00:00:00 2001 From: Reinaldy Rafli Date: Fri, 18 Oct 2024 17:46:28 +0700 Subject: [PATCH 03/26] chore: another attempt at resolving lint issues --- sentrysql/conn.go | 8 +++----- sentrysql/sentrysql.go | 4 ++-- sentrysql/sentrysql_test.go | 2 +- sentrysql/stmt.go | 12 ++++-------- 4 files changed, 10 insertions(+), 16 deletions(-) diff --git a/sentrysql/conn.go b/sentrysql/conn.go index 29fe88e7..c6af406b 100644 --- a/sentrysql/conn.go +++ b/sentrysql/conn.go @@ -52,8 +52,7 @@ func (s *sentryConn) Close() error { } func (s *sentryConn) Begin() (driver.Tx, error) { - //nolint:staticcheck We must support legacy clients - tx, err := s.originalConn.Begin() + tx, err := s.originalConn.Begin() //nolint:staticcheck We must support legacy clients if err != nil { return nil, err } @@ -79,7 +78,7 @@ func (s *sentryConn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver func (s *sentryConn) Query(query string, args []driver.Value) (driver.Rows, error) { // should only be executed if the original driver implements Queryer - queryer, ok := s.originalConn.(driver.Queryer) + queryer, ok := s.originalConn.(driver.Queryer) //nolint:staticcheck We must support legacy clients if !ok { return nil, driver.ErrSkip } @@ -153,8 +152,7 @@ func (s *sentryConn) QueryContext(ctx context.Context, query string, args []driv func (s *sentryConn) Exec(query string, args []driver.Value) (driver.Result, error) { // should only be executed if the original driver implements Execer - //nolint:staticcheck We must support legacy clients - execer, ok := s.originalConn.(driver.Execer) + execer, ok := s.originalConn.(driver.Execer) //nolint:staticcheck We must support legacy clients if !ok { return nil, driver.ErrSkip } diff --git a/sentrysql/sentrysql.go b/sentrysql/sentrysql.go index ed5cf3c8..752749f7 100644 --- a/sentrysql/sentrysql.go +++ b/sentrysql/sentrysql.go @@ -23,9 +23,9 @@ type sentrySQLConfig struct { serverPort string } -// NewSentrySql is a wrapper for driver.Driver that provides tracing for SQL queries. +// NewSentrySQL is a wrapper for driver.Driver that provides tracing for SQL queries. // The span will only be created if the parent span is available. -func NewSentrySql(driver driver.Driver, options ...SentrySQLOption) driver.Driver { +func NewSentrySQL(driver driver.Driver, options ...SentrySQLOption) driver.Driver { var config sentrySQLConfig for _, option := range options { option(&config) diff --git a/sentrysql/sentrysql_test.go b/sentrysql/sentrysql_test.go index bfa5abb6..3959b3d5 100644 --- a/sentrysql/sentrysql_test.go +++ b/sentrysql/sentrysql_test.go @@ -10,7 +10,7 @@ import ( ) func ExampleNewSentrySql() { - sql.Register("sentrysql-ramsql", sentrysql.NewSentrySql(ramsqldriver.NewDriver(), sentrysql.WithDatabaseName("TestDriver"), sentrysql.WithDatabaseSystem(sentrysql.DatabaseSystem("ramsql")), sentrysql.WithServerAddress("127.0.0.1", "3306"))) + sql.Register("sentrysql-ramsql", sentrysql.NewSentrySQL(ramsqldriver.NewDriver(), sentrysql.WithDatabaseName("TestDriver"), sentrysql.WithDatabaseSystem(sentrysql.DatabaseSystem("ramsql")), sentrysql.WithServerAddress("127.0.0.1", "3306"))) db, err := sql.Open("sentrysql-ramsql", "TestDriver") if err != nil { diff --git a/sentrysql/stmt.go b/sentrysql/stmt.go index 330e6b27..faece512 100644 --- a/sentrysql/stmt.go +++ b/sentrysql/stmt.go @@ -25,8 +25,7 @@ func (s *sentryStmt) NumInput() int { func (s *sentryStmt) Exec(args []driver.Value) (driver.Result, error) { parentSpan := sentry.SpanFromContext(s.ctx) if parentSpan == nil { - //nolint:staticcheck We must support legacy clients - return s.originalStmt.Exec(args) + return s.originalStmt.Exec(args) //nolint:staticcheck We must support legacy clients } span := parentSpan.StartChild("db.sql.exec", sentry.WithDescription(s.query)) @@ -43,8 +42,7 @@ func (s *sentryStmt) Exec(args []driver.Value) (driver.Result, error) { span.SetData("server.port", s.config.serverPort) } - //nolint:staticcheck We must support legacy clients - result, err := s.originalStmt.Exec(args) + result, err := s.originalStmt.Exec(args) //nolint:staticcheck We must support legacy clients if err != nil { span.Status = sentry.SpanStatusInternalError span.Finish() @@ -60,8 +58,7 @@ func (s *sentryStmt) Exec(args []driver.Value) (driver.Result, error) { func (s *sentryStmt) Query(args []driver.Value) (driver.Rows, error) { parentSpan := sentry.SpanFromContext(s.ctx) if parentSpan == nil { - //nolint:staticcheck We must support legacy clients - return s.originalStmt.Query(args) + return s.originalStmt.Query(args) //nolint:staticcheck We must support legacy clients } span := parentSpan.StartChild("db.sql.query", sentry.WithDescription(s.query)) @@ -78,8 +75,7 @@ func (s *sentryStmt) Query(args []driver.Value) (driver.Rows, error) { span.SetData("server.port", s.config.serverPort) } - //nolint:staticcheck We must support legacy clients - rows, err := s.originalStmt.Query(args) + rows, err := s.originalStmt.Query(args) //nolint:staticcheck We must support legacy clients if err != nil { span.Status = sentry.SpanStatusInternalError span.Finish() From a6b5de83f8c136d6dfc903482a4675ac93bc8ce4 Mon Sep 17 00:00:00 2001 From: Reinaldy Rafli Date: Fri, 18 Oct 2024 17:48:46 +0700 Subject: [PATCH 04/26] chore: wrong method name --- sentrysql/sentrysql_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/sentrysql/sentrysql_test.go b/sentrysql/sentrysql_test.go index 3959b3d5..d5d7eea7 100644 --- a/sentrysql/sentrysql_test.go +++ b/sentrysql/sentrysql_test.go @@ -9,7 +9,7 @@ import ( ramsqldriver "github.com/proullon/ramsql/driver" ) -func ExampleNewSentrySql() { +func ExampleNewSentrySQL() { sql.Register("sentrysql-ramsql", sentrysql.NewSentrySQL(ramsqldriver.NewDriver(), sentrysql.WithDatabaseName("TestDriver"), sentrysql.WithDatabaseSystem(sentrysql.DatabaseSystem("ramsql")), sentrysql.WithServerAddress("127.0.0.1", "3306"))) db, err := sql.Open("sentrysql-ramsql", "TestDriver") @@ -45,13 +45,13 @@ func ExampleNewSentrySql() { } } -func ExampleNewSentrySqlConnector() { +func ExampleNewSentrySQLConnector() { pqConnector, err := pq.NewConnector("postgres://user:password@localhost:5432/db") if err != nil { panic(err) } - db := sql.OpenDB(sentrysql.NewSentrySqlConnector(pqConnector, sentrysql.WithDatabaseName("db"), sentrysql.WithDatabaseSystem(sentrysql.PostgreSQL), sentrysql.WithServerAddress("localhost", "5432"))) + db := sql.OpenDB(sentrysql.NewSentrySQLConnector(pqConnector, sentrysql.WithDatabaseName("db"), sentrysql.WithDatabaseSystem(sentrysql.PostgreSQL), sentrysql.WithServerAddress("localhost", "5432"))) defer db.Close() // Continue executing PostgreSQL queries From c6ebbda987ae3e1e8c3e66e4dee650524b381c86 Mon Sep 17 00:00:00 2001 From: Reinaldy Rafli Date: Fri, 18 Oct 2024 17:53:37 +0700 Subject: [PATCH 05/26] chore: another attempt of fixing lint issues --- sentrysql/conn.go | 4 ++++ sentrysql/options.go | 8 ++++---- sentrysql/sentrysql.go | 6 +++--- sentrysql/stmt.go | 2 ++ 4 files changed, 13 insertions(+), 7 deletions(-) diff --git a/sentrysql/conn.go b/sentrysql/conn.go index c6af406b..b7e519f0 100644 --- a/sentrysql/conn.go +++ b/sentrysql/conn.go @@ -76,6 +76,7 @@ func (s *sentryConn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver return &sentryTx{originalTx: tx, ctx: s.ctx, config: s.config}, nil } +//nolint:dupl func (s *sentryConn) Query(query string, args []driver.Value) (driver.Rows, error) { // should only be executed if the original driver implements Queryer queryer, ok := s.originalConn.(driver.Queryer) //nolint:staticcheck We must support legacy clients @@ -113,6 +114,7 @@ func (s *sentryConn) Query(query string, args []driver.Value) (driver.Rows, erro return rows, nil } +//nolint:dupl func (s *sentryConn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error) { // should only be executed if the original driver implements QueryerContext queryerContext, ok := s.originalConn.(driver.QueryerContext) @@ -150,6 +152,7 @@ func (s *sentryConn) QueryContext(ctx context.Context, query string, args []driv return rows, nil } +//nolint:dupl func (s *sentryConn) Exec(query string, args []driver.Value) (driver.Result, error) { // should only be executed if the original driver implements Execer execer, ok := s.originalConn.(driver.Execer) //nolint:staticcheck We must support legacy clients @@ -187,6 +190,7 @@ func (s *sentryConn) Exec(query string, args []driver.Value) (driver.Result, err return rows, nil } +//nolint:dupl func (s *sentryConn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error) { // should only be executed if the original driver implements ExecerContext { execerContext, ok := s.originalConn.(driver.ExecerContext) diff --git a/sentrysql/options.go b/sentrysql/options.go index 23d28939..7772f109 100644 --- a/sentrysql/options.go +++ b/sentrysql/options.go @@ -1,23 +1,23 @@ package sentrysql -type SentrySQLOption func(*sentrySQLConfig) +type Option func(*sentrySQLConfig) // WithDatabaseSystem specifies the current database system. -func WithDatabaseSystem(system DatabaseSystem) SentrySQLOption { +func WithDatabaseSystem(system DatabaseSystem) Option { return func(config *sentrySQLConfig) { config.databaseSystem = system } } // WithDatabaseName specifies the name of the current database. -func WithDatabaseName(name string) SentrySQLOption { +func WithDatabaseName(name string) Option { return func(config *sentrySQLConfig) { config.databaseName = name } } // WithServerAddress specifies the address and port of the current database server. -func WithServerAddress(address string, port string) SentrySQLOption { +func WithServerAddress(address string, port string) Option { return func(config *sentrySQLConfig) { config.serverAddress = address config.serverPort = port diff --git a/sentrysql/sentrysql.go b/sentrysql/sentrysql.go index 752749f7..5faf6f8f 100644 --- a/sentrysql/sentrysql.go +++ b/sentrysql/sentrysql.go @@ -25,7 +25,7 @@ type sentrySQLConfig struct { // NewSentrySQL is a wrapper for driver.Driver that provides tracing for SQL queries. // The span will only be created if the parent span is available. -func NewSentrySQL(driver driver.Driver, options ...SentrySQLOption) driver.Driver { +func NewSentrySQL(driver driver.Driver, options ...Option) driver.Driver { var config sentrySQLConfig for _, option := range options { option(&config) @@ -34,9 +34,9 @@ func NewSentrySQL(driver driver.Driver, options ...SentrySQLOption) driver.Drive return &sentrySQLDriver{originalDriver: driver, config: &config} } -// NewSentrySqlConnector is a wrapper for driver.Connector that provides tracing for SQL queries. +// NewSentrySQLConnector is a wrapper for driver.Connector that provides tracing for SQL queries. // The span will only be created if the parent span is available. -func NewSentrySqlConnector(connector driver.Connector, options ...SentrySQLOption) driver.Connector { +func NewSentrySQLConnector(connector driver.Connector, options ...Option) driver.Connector { var config sentrySQLConfig for _, option := range options { option(&config) diff --git a/sentrysql/stmt.go b/sentrysql/stmt.go index faece512..9cf417fe 100644 --- a/sentrysql/stmt.go +++ b/sentrysql/stmt.go @@ -22,6 +22,7 @@ func (s *sentryStmt) NumInput() int { return s.originalStmt.NumInput() } +//nolint:dupl func (s *sentryStmt) Exec(args []driver.Value) (driver.Result, error) { parentSpan := sentry.SpanFromContext(s.ctx) if parentSpan == nil { @@ -55,6 +56,7 @@ func (s *sentryStmt) Exec(args []driver.Value) (driver.Result, error) { return result, nil } +//nolint:dupl func (s *sentryStmt) Query(args []driver.Value) (driver.Rows, error) { parentSpan := sentry.SpanFromContext(s.ctx) if parentSpan == nil { From 63413f3375fd38f9aeaffb2879f554518d7e4817 Mon Sep 17 00:00:00 2001 From: Reinaldy Rafli Date: Sat, 19 Oct 2024 08:51:41 +0700 Subject: [PATCH 06/26] feat: implement missing bits from driver interfaces --- sentrysql/conn.go | 92 ++++++++++++------------------ sentrysql/driver.go | 20 +++++++ sentrysql/operation.go | 22 +++++++ sentrysql/operation_test.go | 50 ++++++++++++++++ sentrysql/sentrysql.go | 32 ++++++++++- sentrysql/stmt.go | 111 +++++++++++++----------------------- 6 files changed, 200 insertions(+), 127 deletions(-) create mode 100644 sentrysql/operation.go create mode 100644 sentrysql/operation_test.go diff --git a/sentrysql/conn.go b/sentrysql/conn.go index b7e519f0..17bef318 100644 --- a/sentrysql/conn.go +++ b/sentrysql/conn.go @@ -7,12 +7,26 @@ import ( "github.com/getsentry/sentry-go" ) +// sentryConn wraps the original driver.Conn. +// As per the driver's documentation: +// - All Conn implementations should implement the following interfaces: +// Pinger, SessionResetter, and Validator. +// - If named parameters or context are supported, the driver's Conn should +// implement: ExecerContext, QueryerContext, ConnPrepareContext, +// and ConnBeginTx. +// +// On this specific Sentry wrapper, we are not going to implement the Validator +// interface because it does not support ErrSkip, since returning ErrSkip +// is only possible when it's explicitly stated on the driver documentation. type sentryConn struct { originalConn driver.Conn ctx context.Context config *sentrySQLConfig } +// Make sure that sentryConn implements the driver.Conn interface. +var _ driver.Conn = (*sentryConn)(nil) + func (s *sentryConn) Prepare(query string) (driver.Stmt, error) { stmt, err := s.originalConn.Prepare(query) if err != nil { @@ -31,7 +45,8 @@ func (s *sentryConn) PrepareContext(ctx context.Context, query string) (driver.S // should only be executed if the original driver implements ConnPrepareContext connPrepareContext, ok := s.originalConn.(driver.ConnPrepareContext) if !ok { - return nil, driver.ErrSkip + // We can't return driver.ErrSkip here. We should fall back to Prepare without context. + return s.Prepare(query) } stmt, err := connPrepareContext.PrepareContext(ctx, query) @@ -52,7 +67,7 @@ func (s *sentryConn) Close() error { } func (s *sentryConn) Begin() (driver.Tx, error) { - tx, err := s.originalConn.Begin() //nolint:staticcheck We must support legacy clients + tx, err := s.originalConn.Begin() //nolint:staticcheck // We must support legacy clients if err != nil { return nil, err } @@ -64,7 +79,7 @@ func (s *sentryConn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver // should only be executed if the original driver implements ConnBeginTx connBeginTx, ok := s.originalConn.(driver.ConnBeginTx) if !ok { - // fallback to the so-called deprecated "Begin" method + // We can't return driver.ErrSkip here. We should fall back to Begin without context. return s.Begin() } @@ -79,7 +94,7 @@ func (s *sentryConn) BeginTx(ctx context.Context, opts driver.TxOptions) (driver //nolint:dupl func (s *sentryConn) Query(query string, args []driver.Value) (driver.Rows, error) { // should only be executed if the original driver implements Queryer - queryer, ok := s.originalConn.(driver.Queryer) //nolint:staticcheck We must support legacy clients + queryer, ok := s.originalConn.(driver.Queryer) //nolint:staticcheck // We must support legacy clients if !ok { return nil, driver.ErrSkip } @@ -90,18 +105,7 @@ func (s *sentryConn) Query(query string, args []driver.Value) (driver.Rows, erro } span := parentSpan.StartChild("db.sql.query", sentry.WithDescription(query)) - if s.config.databaseSystem != "" { - span.SetData("db.system", s.config.databaseSystem) - } - if s.config.databaseName != "" { - span.SetData("db.name", s.config.databaseName) - } - if s.config.serverAddress != "" { - span.SetData("server.address", s.config.serverAddress) - } - if s.config.serverPort != "" { - span.SetData("server.port", s.config.serverPort) - } + s.config.SetData(span, query) defer span.Finish() rows, err := queryer.Query(query, args) @@ -128,18 +132,7 @@ func (s *sentryConn) QueryContext(ctx context.Context, query string, args []driv } span := parentSpan.StartChild("db.sql.query", sentry.WithDescription(query)) - if s.config.databaseSystem != "" { - span.SetData("db.system", s.config.databaseSystem) - } - if s.config.databaseName != "" { - span.SetData("db.name", s.config.databaseName) - } - if s.config.serverAddress != "" { - span.SetData("server.address", s.config.serverAddress) - } - if s.config.serverPort != "" { - span.SetData("server.port", s.config.serverPort) - } + s.config.SetData(span, query) defer span.Finish() rows, err := queryerContext.QueryContext(ctx, query, args) @@ -155,7 +148,7 @@ func (s *sentryConn) QueryContext(ctx context.Context, query string, args []driv //nolint:dupl func (s *sentryConn) Exec(query string, args []driver.Value) (driver.Result, error) { // should only be executed if the original driver implements Execer - execer, ok := s.originalConn.(driver.Execer) //nolint:staticcheck We must support legacy clients + execer, ok := s.originalConn.(driver.Execer) //nolint:staticcheck // We must support legacy clients if !ok { return nil, driver.ErrSkip } @@ -166,18 +159,7 @@ func (s *sentryConn) Exec(query string, args []driver.Value) (driver.Result, err } span := parentSpan.StartChild("db.sql.exec", sentry.WithDescription(query)) - if s.config.databaseSystem != "" { - span.SetData("db.system", s.config.databaseSystem) - } - if s.config.databaseName != "" { - span.SetData("db.name", s.config.databaseName) - } - if s.config.serverAddress != "" { - span.SetData("server.address", s.config.serverAddress) - } - if s.config.serverPort != "" { - span.SetData("server.port", s.config.serverPort) - } + s.config.SetData(span, query) defer span.Finish() rows, err := execer.Exec(query, args) @@ -195,6 +177,7 @@ func (s *sentryConn) ExecContext(ctx context.Context, query string, args []drive // should only be executed if the original driver implements ExecerContext { execerContext, ok := s.originalConn.(driver.ExecerContext) if !ok { + // ExecContext may return ErrSkip. return nil, driver.ErrSkip } @@ -204,18 +187,7 @@ func (s *sentryConn) ExecContext(ctx context.Context, query string, args []drive } span := parentSpan.StartChild("db.sql.exec", sentry.WithDescription(query)) - if s.config.databaseSystem != "" { - span.SetData("db.system", s.config.databaseSystem) - } - if s.config.databaseName != "" { - span.SetData("db.name", s.config.databaseName) - } - if s.config.serverAddress != "" { - span.SetData("server.address", s.config.serverAddress) - } - if s.config.serverPort != "" { - span.SetData("server.port", s.config.serverPort) - } + s.config.SetData(span, query) defer span.Finish() rows, err := execerContext.ExecContext(ctx, query, args) @@ -231,15 +203,27 @@ func (s *sentryConn) ExecContext(ctx context.Context, query string, args []drive func (s *sentryConn) Ping(ctx context.Context) error { pinger, ok := s.originalConn.(driver.Pinger) if !ok { - return driver.ErrSkip + // We may not return ErrSkip. We should return nil. + return nil } return pinger.Ping(ctx) } +func (s *sentryConn) ResetSession(ctx context.Context) error { + sessionResetter, ok := s.originalConn.(driver.SessionResetter) + if !ok { + // We may not return ErrSkip. We should return nil. + return nil + } + + return sessionResetter.ResetSession(ctx) +} + func (s *sentryConn) CheckNamedValue(namedValue *driver.NamedValue) error { namedValueChecker, ok := s.originalConn.(driver.NamedValueChecker) if !ok { + // We may return ErrSkip. return driver.ErrSkip } diff --git a/sentrysql/driver.go b/sentrysql/driver.go index 284a9608..ed6fc39d 100644 --- a/sentrysql/driver.go +++ b/sentrysql/driver.go @@ -3,13 +3,20 @@ package sentrysql import ( "context" "database/sql/driver" + "io" ) +// sentrySQLDriver wraps the original driver.Driver. +// As per the driver's documentation: +// Drivers should implement driver.Connector and driver.DriverContext interfaces type sentrySQLDriver struct { originalDriver driver.Driver config *sentrySQLConfig } +// Make sure that sentrySQLDriver implements the driver.Driver interface. +var _ driver.Driver = (*sentrySQLDriver)(nil) + func (s *sentrySQLDriver) OpenConnector(name string) (driver.Connector, error) { driverContext, ok := s.originalDriver.(driver.DriverContext) if !ok { @@ -38,6 +45,9 @@ type sentrySQLConnector struct { config *sentrySQLConfig } +// Make sure that sentrySQLConnector implements the driver.Connector interface. +var _ driver.Connector = (*sentrySQLConnector)(nil) + func (s *sentrySQLConnector) Connect(ctx context.Context) (driver.Conn, error) { conn, err := s.originalConnector.Connect(ctx) if err != nil { @@ -50,3 +60,13 @@ func (s *sentrySQLConnector) Connect(ctx context.Context) (driver.Conn, error) { func (s *sentrySQLConnector) Driver() driver.Driver { return s.originalConnector.Driver() } + +func (s *sentrySQLConnector) Close() error { + // driver.Connector should optionally implements io.Closer + closer, ok := s.originalConnector.(io.Closer) + if !ok { + return nil + } + + return closer.Close() +} diff --git a/sentrysql/operation.go b/sentrysql/operation.go new file mode 100644 index 00000000..d08777f9 --- /dev/null +++ b/sentrysql/operation.go @@ -0,0 +1,22 @@ +package sentrysql + +import "strings" + +var knownDatabaseOperations = []string{"SELECT", "INSERT", "DELETE", "UPDATE"} + +func parseDatabaseOperation(query string) string { + // The operation is the first word of the query. + operation := query + if i := strings.Index(query, " "); i >= 0 { + operation = strings.ToUpper(query[:i]) + } + + // Only returns known words. + for _, knownOperation := range knownDatabaseOperations { + if operation == knownOperation { + return operation + } + } + + return "" +} diff --git a/sentrysql/operation_test.go b/sentrysql/operation_test.go new file mode 100644 index 00000000..7aae625f --- /dev/null +++ b/sentrysql/operation_test.go @@ -0,0 +1,50 @@ +package sentrysql + +import "testing" + +func TestParseDatabaseOperation(t *testing.T) { + tests := []struct { + name string + query string + want string + }{ + { + name: "SELECT", + query: "SELECT * FROM users", + want: "SELECT", + }, + { + name: "INSERT", + query: "INSERT INTO users (id, name) VALUES (1, 'John')", + want: "INSERT", + }, + { + name: "DELETE", + query: "DELETE FROM users WHERE id = 1", + want: "DELETE", + }, + { + name: "UPDATE", + query: "UPDATE users SET name = 'John' WHERE id = 1", + want: "UPDATE", + }, + { + name: "findById", + query: "findById", + want: "", + }, + { + name: "Empty", + query: "", + want: "", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := parseDatabaseOperation(tt.query); got != tt.want { + t.Errorf("parseDatabaseOperation() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/sentrysql/sentrysql.go b/sentrysql/sentrysql.go index 5faf6f8f..c1203174 100644 --- a/sentrysql/sentrysql.go +++ b/sentrysql/sentrysql.go @@ -1,6 +1,10 @@ package sentrysql -import "database/sql/driver" +import ( + "database/sql/driver" + + "github.com/getsentry/sentry-go" +) // DatabaseSystem points to the list of accepted OpenTelemetry database system. // The ones defined here are not exhaustive, but are the ones that are supported by Sentry. @@ -23,6 +27,32 @@ type sentrySQLConfig struct { serverPort string } +func (s *sentrySQLConfig) SetData(span *sentry.Span, query string) { + if span == nil { + return + } + + if s.databaseSystem != "" { + span.SetData("db.system", s.databaseSystem) + } + if s.databaseName != "" { + span.SetData("db.name", s.databaseName) + } + if s.serverAddress != "" { + span.SetData("server.address", s.serverAddress) + } + if s.serverPort != "" { + span.SetData("server.port", s.serverPort) + } + + if query != "" { + databaseOperation := parseDatabaseOperation(query) + if databaseOperation != "" { + span.SetData("db.operation", databaseOperation) + } + } +} + // NewSentrySQL is a wrapper for driver.Driver that provides tracing for SQL queries. // The span will only be created if the parent span is available. func NewSentrySQL(driver driver.Driver, options ...Option) driver.Driver { diff --git a/sentrysql/stmt.go b/sentrysql/stmt.go index 9cf417fe..b62abf65 100644 --- a/sentrysql/stmt.go +++ b/sentrysql/stmt.go @@ -3,6 +3,7 @@ package sentrysql import ( "context" "database/sql/driver" + "errors" "github.com/getsentry/sentry-go" ) @@ -14,6 +15,9 @@ type sentryStmt struct { config *sentrySQLConfig } +// Make sure sentryStmt implements driver.Stmt interface. +var _ driver.Stmt = (*sentryStmt)(nil) + func (s *sentryStmt) Close() error { return s.originalStmt.Close() } @@ -26,32 +30,20 @@ func (s *sentryStmt) NumInput() int { func (s *sentryStmt) Exec(args []driver.Value) (driver.Result, error) { parentSpan := sentry.SpanFromContext(s.ctx) if parentSpan == nil { - return s.originalStmt.Exec(args) //nolint:staticcheck We must support legacy clients + return s.originalStmt.Exec(args) //nolint:staticcheck // We must support legacy clients } span := parentSpan.StartChild("db.sql.exec", sentry.WithDescription(s.query)) - if s.config.databaseSystem != "" { - span.SetData("db.system", s.config.databaseSystem) - } - if s.config.databaseName != "" { - span.SetData("db.name", s.config.databaseName) - } - if s.config.serverAddress != "" { - span.SetData("server.address", s.config.serverAddress) - } - if s.config.serverPort != "" { - span.SetData("server.port", s.config.serverPort) - } + s.config.SetData(span, s.query) + defer span.Finish() - result, err := s.originalStmt.Exec(args) //nolint:staticcheck We must support legacy clients + result, err := s.originalStmt.Exec(args) //nolint:staticcheck // We must support legacy clients if err != nil { span.Status = sentry.SpanStatusInternalError - span.Finish() return nil, err } span.Status = sentry.SpanStatusOK - span.Finish() return result, nil } @@ -60,34 +52,20 @@ func (s *sentryStmt) Exec(args []driver.Value) (driver.Result, error) { func (s *sentryStmt) Query(args []driver.Value) (driver.Rows, error) { parentSpan := sentry.SpanFromContext(s.ctx) if parentSpan == nil { - return s.originalStmt.Query(args) //nolint:staticcheck We must support legacy clients + return s.originalStmt.Query(args) //nolint:staticcheck // We must support legacy clients } span := parentSpan.StartChild("db.sql.query", sentry.WithDescription(s.query)) - if s.config.databaseSystem != "" { - span.SetData("db.system", s.config.databaseSystem) - } - if s.config.databaseName != "" { - span.SetData("db.name", s.config.databaseName) - } - if s.config.serverAddress != "" { - span.SetData("server.address", s.config.serverAddress) - } - if s.config.serverPort != "" { - span.SetData("server.port", s.config.serverPort) - } + s.config.SetData(span, s.query) + defer span.Finish() - rows, err := s.originalStmt.Query(args) //nolint:staticcheck We must support legacy clients + rows, err := s.originalStmt.Query(args) //nolint:staticcheck // We must support legacy clients if err != nil { span.Status = sentry.SpanStatusInternalError - span.Finish() - return nil, err } span.Status = sentry.SpanStatusOK - span.Finish() - return rows, nil } @@ -95,10 +73,10 @@ func (s *sentryStmt) ExecContext(ctx context.Context, args []driver.NamedValue) // should only be executed if the original driver implements StmtExecContext stmtExecContext, ok := s.originalStmt.(driver.StmtExecContext) if !ok { - // fallback to the so-called deprecated "Exec" method - var values []driver.Value - for _, nv := range args { - values = append(values, nv.Value) + // We may not return driver.ErrSkip. We should fallback to Exec without context. + values, err := namedValueToValue(args) + if err != nil { + return nil, err } return s.Exec(values) } @@ -109,28 +87,16 @@ func (s *sentryStmt) ExecContext(ctx context.Context, args []driver.NamedValue) } span := parentSpan.StartChild("db.sql.exec", sentry.WithDescription(s.query)) - if s.config.databaseSystem != "" { - span.SetData("db.system", s.config.databaseSystem) - } - if s.config.databaseName != "" { - span.SetData("db.name", s.config.databaseName) - } - if s.config.serverAddress != "" { - span.SetData("server.address", s.config.serverAddress) - } - if s.config.serverPort != "" { - span.SetData("server.port", s.config.serverPort) - } + s.config.SetData(span, s.query) + defer span.Finish() result, err := stmtExecContext.ExecContext(ctx, args) if err != nil { span.Status = sentry.SpanStatusInternalError - span.Finish() return nil, err } span.Status = sentry.SpanStatusOK - span.Finish() return result, nil } @@ -139,10 +105,10 @@ func (s *sentryStmt) QueryContext(ctx context.Context, args []driver.NamedValue) // should only be executed if the original driver implements StmtQueryContext stmtQueryContext, ok := s.originalStmt.(driver.StmtQueryContext) if !ok { - // fallback to the so-called deprecated "Query" method - var values []driver.Value - for _, nv := range args { - values = append(values, nv.Value) + // We may not return driver.ErrSkip. We should fallback to Exec without context. + values, err := namedValueToValue(args) + if err != nil { + return nil, err } return s.Query(values) } @@ -153,34 +119,22 @@ func (s *sentryStmt) QueryContext(ctx context.Context, args []driver.NamedValue) } span := parentSpan.StartChild("db.sql.query", sentry.WithDescription(s.query)) - if s.config.databaseSystem != "" { - span.SetData("db.system", s.config.databaseSystem) - } - if s.config.databaseName != "" { - span.SetData("db.name", s.config.databaseName) - } - if s.config.serverAddress != "" { - span.SetData("server.address", s.config.serverAddress) - } - if s.config.serverPort != "" { - span.SetData("server.port", s.config.serverPort) - } + s.config.SetData(span, s.query) + defer span.Finish() rows, err := stmtQueryContext.QueryContext(ctx, args) if err != nil { span.Status = sentry.SpanStatusInternalError - span.Finish() - return nil, err } span.Status = sentry.SpanStatusOK - span.Finish() - return rows, nil } func (s *sentryStmt) CheckNamedValue(namedValue *driver.NamedValue) error { + // It is allowed to return driver.ErrSkip if the original driver does not + // implement driver.NamedValueChecker. namedValueChecker, ok := s.originalStmt.(driver.NamedValueChecker) if !ok { return driver.ErrSkip @@ -188,3 +142,16 @@ func (s *sentryStmt) CheckNamedValue(namedValue *driver.NamedValue) error { return namedValueChecker.CheckNamedValue(namedValue) } + +// namedValueToValue is an exact copy of +// https://cs.opensource.google/go/go/+/refs/tags/go1.23.2:src/database/sql/ctxutil.go;l=137-146 +func namedValueToValue(named []driver.NamedValue) ([]driver.Value, error) { + dargs := make([]driver.Value, len(named)) + for n, param := range named { + if len(param.Name) > 0 { + return nil, errors.New("sql: driver does not support the use of Named Parameters") + } + dargs[n] = param.Value + } + return dargs, nil +} From b77e39274c0e94eb7572cf4f6814463f22bfeeb8 Mon Sep 17 00:00:00 2001 From: Reinaldy Rafli Date: Sat, 19 Oct 2024 08:54:17 +0700 Subject: [PATCH 07/26] chore: missing a period on comment --- sentrysql/driver.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentrysql/driver.go b/sentrysql/driver.go index ed6fc39d..67c9c30b 100644 --- a/sentrysql/driver.go +++ b/sentrysql/driver.go @@ -8,7 +8,7 @@ import ( // sentrySQLDriver wraps the original driver.Driver. // As per the driver's documentation: -// Drivers should implement driver.Connector and driver.DriverContext interfaces +// Drivers should implement driver.Connector and driver.DriverContext interfaces. type sentrySQLDriver struct { originalDriver driver.Driver config *sentrySQLConfig From 9b593280327174ee590933a82233dc22f318b265 Mon Sep 17 00:00:00 2001 From: Reinaldy Rafli Date: Sat, 19 Oct 2024 09:27:57 +0700 Subject: [PATCH 08/26] test: replace ramsql with in-memory sqlite --- go.mod | 9 ++- go.sum | 28 ++++---- sentrysql/driver.go | 20 +++++- sentrysql/sentrysql_test.go | 135 +++++++++++++++++++++++++++++++++++- 4 files changed, 174 insertions(+), 18 deletions(-) diff --git a/go.mod b/go.mod index 268b053a..de082778 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.18 require ( github.com/gin-gonic/gin v1.8.1 + github.com/glebarez/go-sqlite v1.21.1 github.com/go-errors/errors v1.4.2 github.com/gofiber/fiber/v2 v2.52.2 github.com/google/go-cmp v0.5.9 @@ -12,7 +13,6 @@ require ( github.com/lib/pq v1.10.9 github.com/pingcap/errors v0.11.4 github.com/pkg/errors v0.9.1 - github.com/proullon/ramsql v0.1.4 github.com/sirupsen/logrus v1.9.0 github.com/stretchr/testify v1.8.2 github.com/urfave/negroni/v3 v3.1.1 @@ -31,6 +31,7 @@ require ( github.com/andybalholm/brotli v1.1.0 // indirect github.com/aymerick/douceur v0.2.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dustin/go-humanize v1.0.1 // indirect github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385 // indirect github.com/fatih/structs v1.1.0 // indirect github.com/flosch/pongo2/v4 v4.0.2 // indirect @@ -69,6 +70,7 @@ require ( github.com/nxadm/tail v1.4.11 // indirect github.com/pelletier/go-toml/v2 v2.0.5 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/rivo/uniseg v0.2.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/sanity-io/litter v1.5.5 // indirect @@ -90,12 +92,15 @@ require ( github.com/yudai/gojsondiff v1.0.0 // indirect github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect golang.org/x/crypto v0.21.0 // indirect - golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 // indirect golang.org/x/net v0.23.0 // indirect golang.org/x/time v0.3.0 // indirect google.golang.org/protobuf v1.33.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + modernc.org/libc v1.22.3 // indirect + modernc.org/mathutil v1.5.0 // indirect + modernc.org/memory v1.5.0 // indirect + modernc.org/sqlite v1.21.1 // indirect moul.io/http2curl/v2 v2.3.0 // indirect ) diff --git a/go.sum b/go.sum index 73f7b1bd..d8765842 100644 --- a/go.sum +++ b/go.sum @@ -24,6 +24,8 @@ 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/djherbis/atime v1.1.0/go.mod h1:28OF6Y8s3NQWwacXc5eZTsEsiMzp7LF8MbXE+XJPdBE= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385 h1:clC1lXBpe2kTj2VHdaIu9ajZQe4kcEY9j0NsnDDBZ3o= github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= @@ -37,9 +39,10 @@ github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.8.1 h1:4+fr/el88TOO3ewCmQr8cx/CtZ/umlIRIs5M4NTNjf8= github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk= +github.com/glebarez/go-sqlite v1.21.1 h1:7MZyUPh2XTrHS7xNEHQbrhfMZuPSzhkm2A1qgg0y5NY= +github.com/glebarez/go-sqlite v1.21.1/go.mod h1:ISs8MF6yk5cL4n/43rSOmVMGJJjHYr7L2MbZZ5Q4E2E= 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/go-gorp/gorp v2.2.0+incompatible h1:xAUh4QgEeqPPhK3vxZN+bzrim1z5Av6q837gtjUlshc= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= @@ -60,6 +63,7 @@ github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeN github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= @@ -72,11 +76,6 @@ github.com/iris-contrib/httpexpect/v2 v2.12.1 h1:3cTZSyBBen/kfjCtgNFoUKi1u0FVXNa github.com/iris-contrib/httpexpect/v2 v2.12.1/go.mod h1:7+RB6W5oNClX7PTwJgJnsQP3ZuUUYB3u61KCqeSgZ88= github.com/iris-contrib/schema v0.0.6 h1:CPSBLyx2e91H2yJzPuhGuifVRnZBBJ3pCOMbOvPZaTw= github.com/iris-contrib/schema v0.0.6/go.mod h1:iYszG0IOsuIsfzjymw1kMzTL8YQcCWlm65f3wX8J5iA= -github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= -github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= -github.com/jackc/pgx/v5 v5.3.1 h1:Fcr8QJ1ZeLi5zsPZqQeUZhNhxfkkKBOgJuYkJHoBOtU= -github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= -github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= @@ -150,8 +149,9 @@ github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 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/proullon/ramsql v0.1.4 h1:yTFRTn46gFH/kPbzCx+mGjuFlyTBUeDr3h2ldwxddl0= -github.com/proullon/ramsql v0.1.4/go.mod h1:CFGqeQHQpdRfWqYmWD3yXqPTEaHkF4zgXy1C6qDWc9E= +github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= @@ -230,8 +230,6 @@ golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPh golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= -golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 h1:MGwJjxBy0HJshjDNfLsYO8xppfqWlA5ZT9OhtUUhTNw= -golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= @@ -301,7 +299,13 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -gorm.io/driver/postgres v1.5.2 h1:ytTDxxEv+MplXOfFe3Lzm7SjG09fcdb3Z/c056DTBx0= -gorm.io/gorm v1.25.2 h1:gs1o6Vsa+oVKG/a9ElL3XgyGfghFfkKA2SInQaCyMho= +modernc.org/libc v1.22.3 h1:D/g6O5ftAfavceqlLOFwaZuA5KYafKwmr30A6iSqoyY= +modernc.org/libc v1.22.3/go.mod h1:MQrloYP209xa2zHome2a8HLiLm6k0UT8CoHpV74tOFw= +modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ= +modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds= +modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= +modernc.org/sqlite v1.21.1 h1:GyDFqNnESLOhwwDRaHGdp2jKLDzpyT/rNLglX3ZkMSU= +modernc.org/sqlite v1.21.1/go.mod h1:XwQ0wZPIh1iKb5mkvCJ3szzbhk+tykC8ZWqTRTgYRwI= moul.io/http2curl/v2 v2.3.0 h1:9r3JfDzWPcbIklMOs2TnIFzDYvfAZvjeavG6EzP7jYs= moul.io/http2curl/v2 v2.3.0/go.mod h1:RW4hyBjTWSYDOxapodpNEtX0g5Eb16sxklBqmd2RHcE= diff --git a/sentrysql/driver.go b/sentrysql/driver.go index 67c9c30b..652ca2a8 100644 --- a/sentrysql/driver.go +++ b/sentrysql/driver.go @@ -20,7 +20,10 @@ var _ driver.Driver = (*sentrySQLDriver)(nil) func (s *sentrySQLDriver) OpenConnector(name string) (driver.Connector, error) { driverContext, ok := s.originalDriver.(driver.DriverContext) if !ok { - return nil, driver.ErrSkip + return &sentrySQLConnector{ + originalConnector: dsnConnector{dsn: name, driver: s.originalDriver}, + config: s.config, + }, nil } connector, err := driverContext.OpenConnector(name) @@ -70,3 +73,18 @@ func (s *sentrySQLConnector) Close() error { return closer.Close() } + +// dsnConnector is copied from +// https://cs.opensource.google/go/go/+/refs/tags/go1.23.2:src/database/sql/sql.go;l=795-806 +type dsnConnector struct { + dsn string + driver driver.Driver +} + +func (t dsnConnector) Connect(_ context.Context) (driver.Conn, error) { + return t.driver.Open(t.dsn) +} + +func (t dsnConnector) Driver() driver.Driver { + return t.driver +} diff --git a/sentrysql/sentrysql_test.go b/sentrysql/sentrysql_test.go index d5d7eea7..34af93ae 100644 --- a/sentrysql/sentrysql_test.go +++ b/sentrysql/sentrysql_test.go @@ -1,18 +1,26 @@ package sentrysql_test import ( + "context" "database/sql" "fmt" + "strings" + "testing" + "time" + "github.com/getsentry/sentry-go" + "github.com/getsentry/sentry-go/internal/testutils" "github.com/getsentry/sentry-go/sentrysql" + sqlite "github.com/glebarez/go-sqlite" + "github.com/google/go-cmp/cmp" + "github.com/google/go-cmp/cmp/cmpopts" "github.com/lib/pq" - ramsqldriver "github.com/proullon/ramsql/driver" ) func ExampleNewSentrySQL() { - sql.Register("sentrysql-ramsql", sentrysql.NewSentrySQL(ramsqldriver.NewDriver(), sentrysql.WithDatabaseName("TestDriver"), sentrysql.WithDatabaseSystem(sentrysql.DatabaseSystem("ramsql")), sentrysql.WithServerAddress("127.0.0.1", "3306"))) + sql.Register("sentrysql-sqlite", sentrysql.NewSentrySQL(&sqlite.Driver{}, sentrysql.WithDatabaseName(":memory:"), sentrysql.WithDatabaseSystem(sentrysql.DatabaseSystem("sqlite")))) - db, err := sql.Open("sentrysql-ramsql", "TestDriver") + db, err := sql.Open("sentrysql-sqlite", ":memory:") if err != nil { panic(err) } @@ -56,3 +64,124 @@ func ExampleNewSentrySQLConnector() { // Continue executing PostgreSQL queries } + +func TestIntegration(t *testing.T) { + sql.Register("sentrysql-sqlite", sentrysql.NewSentrySQL(&sqlite.Driver{}, sentrysql.WithDatabaseName("memory"), sentrysql.WithDatabaseSystem(sentrysql.DatabaseSystem("sqlite")), sentrysql.WithServerAddress("localhost", "5432"))) + + db, err := sql.Open("sentrysql-sqlite", ":memory:") + if err != nil { + t.Fatalf("opening sqlite: %v", err) + } + defer db.Close() + + setupQueries := []string{ + "CREATE TABLE exec_test (id INT, name TEXT)", + "CREATE TABLE query_test (id INT, name TEXT, age INT, created_at TEXT)", + "INSERT INTO query_test (id, name, age, created_at) VALUES (1, 'John', 30, '2023-01-01')", + "INSERT INTO query_test (id, name, age, created_at) VALUES (2, 'Jane', 25, '2023-01-02')", + "INSERT INTO query_test (id, name, age, created_at) VALUES (3, 'Bob', 35, '2023-01-03')", + } + + setupCtx, cancelCtx := context.WithTimeout(context.Background(), 30*time.Second) + defer cancelCtx() + + for _, query := range setupQueries { + _, err = db.ExecContext(setupCtx, query) + if err != nil { + t.Fatalf("initializing table on sqlite: %v", err) + } + } + + t.Run("Query", func(t *testing.T) { + tests := []struct { + Query string + Parameters []interface{} + WantSpan *sentry.Span + }{ + { + Query: "SELECT * FROM query_test WHERE id = ?", + Parameters: []interface{}{1}, + WantSpan: &sentry.Span{ + Data: map[string]interface{}{ + "db.system": sentrysql.DatabaseSystem("sqlite"), + "db.name": "memory", + "server.address": "localhost", + "server.port": "5432", + "db.operation": "SELECT", + }, + Description: "SELECT * FROM query_test WHERE id = ?", + Op: "db.sql.query", + Tags: nil, + Origin: "manual", + Sampled: sentry.SampledTrue, + Status: sentry.SpanStatusOK, + }, + }, + } + + spansCh := make(chan []*sentry.Span, len(tests)) + + sentryClient, err := sentry.NewClient(sentry.ClientOptions{ + EnableTracing: true, + TracesSampleRate: 1.0, + BeforeSendTransaction: func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event { + spansCh <- event.Spans + return event + }, + }) + if err != nil { + t.Fatal(err) + } + + for _, tt := range tests { + hub := sentry.NewHub(sentryClient, sentry.NewScope()) + ctx := sentry.SetHubOnContext(context.Background(), hub) + span := sentry.StartSpan(ctx, "fake_parent", sentry.WithTransactionName("Fake Parent")) + ctx = span.Context() + + _, err := db.QueryContext(ctx, tt.Query, tt.Parameters...) + if err != nil { + t.Fatal(err) + } + + span.Finish() + } + + if ok := sentryClient.Flush(testutils.FlushTimeout()); !ok { + t.Fatal("sentry.Flush timed out") + } + close(spansCh) + + var got [][]*sentry.Span + for e := range spansCh { + got = append(got, e) + } + + optstrans := cmp.Options{ + cmpopts.IgnoreFields( + sentry.Span{}, + "TraceID", "SpanID", "ParentSpanID", "StartTime", "EndTime", + "mu", "parent", "sampleRate", "ctx", "dynamicSamplingContext", "recorder", "finishOnce", "collectProfile", "contexts", + ), + } + for i, tt := range tests { + var foundMatch = false + gotSpans := got[i] + + var diffs []string + for _, gotSpan := range gotSpans { + if diff := cmp.Diff(tt.WantSpan, gotSpan, optstrans); diff != "" { + diffs = append(diffs, diff) + } else { + foundMatch = true + break + } + } + + if !foundMatch { + t.Errorf("Span mismatch (-want +got):\n%s", strings.Join(diffs, "\n")) + } + } + }) + +} From 7ecd8686cb04e02f87238c63f6134865c9c535c6 Mon Sep 17 00:00:00 2001 From: Reinaldy Rafli Date: Sat, 19 Oct 2024 10:22:23 +0700 Subject: [PATCH 09/26] test: common queries --- sentrysql/sentrysql_test.go | 932 +++++++++++++++++++++++++++++++++++- sentrysql/stmt.go | 2 +- 2 files changed, 922 insertions(+), 12 deletions(-) diff --git a/sentrysql/sentrysql_test.go b/sentrysql/sentrysql_test.go index 34af93ae..4ff51735 100644 --- a/sentrysql/sentrysql_test.go +++ b/sentrysql/sentrysql_test.go @@ -65,13 +65,15 @@ func ExampleNewSentrySQLConnector() { // Continue executing PostgreSQL queries } -func TestIntegration(t *testing.T) { +//nolint:dupl +func TestNewSentrySQL_Integration(t *testing.T) { sql.Register("sentrysql-sqlite", sentrysql.NewSentrySQL(&sqlite.Driver{}, sentrysql.WithDatabaseName("memory"), sentrysql.WithDatabaseSystem(sentrysql.DatabaseSystem("sqlite")), sentrysql.WithServerAddress("localhost", "5432"))) db, err := sql.Open("sentrysql-sqlite", ":memory:") if err != nil { t.Fatalf("opening sqlite: %v", err) } + db.SetMaxOpenConns(1) defer db.Close() setupQueries := []string{ @@ -92,11 +94,20 @@ func TestIntegration(t *testing.T) { } } - t.Run("Query", func(t *testing.T) { + optstrans := cmp.Options{ + cmpopts.IgnoreFields( + sentry.Span{}, + "TraceID", "SpanID", "ParentSpanID", "StartTime", "EndTime", + "mu", "parent", "sampleRate", "ctx", "dynamicSamplingContext", "recorder", "finishOnce", "collectProfile", "contexts", + ), + } + + t.Run("QueryContext", func(t *testing.T) { tests := []struct { Query string Parameters []interface{} WantSpan *sentry.Span + WantError bool }{ { Query: "SELECT * FROM query_test WHERE id = ?", @@ -117,6 +128,25 @@ func TestIntegration(t *testing.T) { Status: sentry.SpanStatusOK, }, }, + { + Query: "SELECT FROM query_test", + WantError: true, + WantSpan: &sentry.Span{ + Data: map[string]interface{}{ + "db.system": sentrysql.DatabaseSystem("sqlite"), + "db.name": "memory", + "server.address": "localhost", + "server.port": "5432", + "db.operation": "SELECT", + }, + Description: "SELECT FROM query_test", + Op: "db.sql.query", + Tags: nil, + Origin: "manual", + Sampled: sentry.SampledTrue, + Status: sentry.SpanStatusInternalError, + }, + }, } spansCh := make(chan []*sentry.Span, len(tests)) @@ -135,16 +165,22 @@ func TestIntegration(t *testing.T) { for _, tt := range tests { hub := sentry.NewHub(sentryClient, sentry.NewScope()) - ctx := sentry.SetHubOnContext(context.Background(), hub) + ctx, cancel := context.WithTimeout(sentry.SetHubOnContext(context.Background(), hub), 10*time.Second) span := sentry.StartSpan(ctx, "fake_parent", sentry.WithTransactionName("Fake Parent")) ctx = span.Context() - _, err := db.QueryContext(ctx, tt.Query, tt.Parameters...) - if err != nil { + rows, err := db.QueryContext(ctx, tt.Query, tt.Parameters...) + if err != nil && !tt.WantError { + cancel() t.Fatal(err) } + if rows != nil { + _ = rows.Close() + } + span.Finish() + cancel() } if ok := sentryClient.Flush(testutils.FlushTimeout()); !ok { @@ -157,13 +193,154 @@ func TestIntegration(t *testing.T) { got = append(got, e) } - optstrans := cmp.Options{ - cmpopts.IgnoreFields( - sentry.Span{}, - "TraceID", "SpanID", "ParentSpanID", "StartTime", "EndTime", - "mu", "parent", "sampleRate", "ctx", "dynamicSamplingContext", "recorder", "finishOnce", "collectProfile", "contexts", - ), + for i, tt := range tests { + var foundMatch = false + gotSpans := got[i] + + var diffs []string + for _, gotSpan := range gotSpans { + if diff := cmp.Diff(tt.WantSpan, gotSpan, optstrans); diff != "" { + diffs = append(diffs, diff) + } else { + foundMatch = true + break + } + } + + if !foundMatch { + t.Errorf("Span mismatch (-want +got):\n%s", strings.Join(diffs, "\n")) + } } + }) + + t.Run("ExecContext", func(t *testing.T) { + tests := []struct { + Query string + Parameters []interface{} + WantSpan *sentry.Span + WantError bool + }{ + { + Query: "INSERT INTO exec_test (id, name) VALUES (?, ?)", + Parameters: []interface{}{1, "John"}, + WantSpan: &sentry.Span{ + Data: map[string]interface{}{ + "db.system": sentrysql.DatabaseSystem("sqlite"), + "db.name": "memory", + "server.address": "localhost", + "server.port": "5432", + "db.operation": "INSERT", + }, + Description: "INSERT INTO exec_test (id, name) VALUES (?, ?)", + Op: "db.sql.exec", + Tags: nil, + Origin: "manual", + Sampled: sentry.SampledTrue, + Status: sentry.SpanStatusOK, + }, + }, + { + Query: "UPDATE exec_test SET name = ? WHERE id = ?", + Parameters: []interface{}{"Bob", 1}, + WantSpan: &sentry.Span{ + Data: map[string]interface{}{ + "db.system": sentrysql.DatabaseSystem("sqlite"), + "db.name": "memory", + "server.address": "localhost", + "server.port": "5432", + "db.operation": "UPDATE", + }, + Description: "UPDATE exec_test SET name = ? WHERE id = ?", + Op: "db.sql.exec", + Tags: nil, + Origin: "manual", + Sampled: sentry.SampledTrue, + Status: sentry.SpanStatusOK, + }, + }, + { + Query: "DELETE FROM exec_test WHERE name = ?", + Parameters: []interface{}{"Nolan"}, + WantSpan: &sentry.Span{ + Data: map[string]interface{}{ + "db.system": sentrysql.DatabaseSystem("sqlite"), + "db.name": "memory", + "server.address": "localhost", + "server.port": "5432", + "db.operation": "DELETE", + }, + Description: "DELETE FROM exec_test WHERE name = ?", + Op: "db.sql.exec", + Tags: nil, + Origin: "manual", + Sampled: sentry.SampledTrue, + Status: sentry.SpanStatusOK, + }, + }, + { + Query: "INSERT INTO exec_test (id, name) VALUES (?, ?, ?, ?)", + Parameters: []interface{}{ + 1, "John", "Doe", 1, + }, + WantError: true, + WantSpan: &sentry.Span{ + Data: map[string]interface{}{ + "db.system": sentrysql.DatabaseSystem("sqlite"), + "db.name": "memory", + "server.address": "localhost", + "server.port": "5432", + "db.operation": "INSERT", + }, + Description: "INSERT INTO exec_test (id, name) VALUES (?, ?, ?, ?)", + Op: "db.sql.exec", + Tags: nil, + Origin: "manual", + Sampled: sentry.SampledTrue, + Status: sentry.SpanStatusInternalError, + }, + }, + } + + spansCh := make(chan []*sentry.Span, len(tests)) + + sentryClient, err := sentry.NewClient(sentry.ClientOptions{ + EnableTracing: true, + TracesSampleRate: 1.0, + BeforeSendTransaction: func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event { + spansCh <- event.Spans + return event + }, + }) + if err != nil { + t.Fatal(err) + } + + for _, tt := range tests { + hub := sentry.NewHub(sentryClient, sentry.NewScope()) + ctx, cancel := context.WithTimeout(sentry.SetHubOnContext(context.Background(), hub), 10*time.Second) + span := sentry.StartSpan(ctx, "fake_parent", sentry.WithTransactionName("Fake Parent")) + ctx = span.Context() + + _, err := db.ExecContext(ctx, tt.Query, tt.Parameters...) + if err != nil && !tt.WantError { + cancel() + t.Fatal(err) + } + + span.Finish() + cancel() + } + + if ok := sentryClient.Flush(testutils.FlushTimeout()); !ok { + t.Fatal("sentry.Flush timed out") + } + close(spansCh) + + var got [][]*sentry.Span + for e := range spansCh { + got = append(got, e) + } + for i, tt := range tests { var foundMatch = false gotSpans := got[i] @@ -184,4 +361,737 @@ func TestIntegration(t *testing.T) { } }) + t.Run("PrepareContext", func(t *testing.T) { + t.Run("Exec", func(t *testing.T) { + tests := []struct { + Query string + Parameters []interface{} + WantSpan *sentry.Span + WantError bool + }{ + { + Query: "INSERT INTO exec_test (id, name) VALUES (?, ?)", + Parameters: []interface{}{3, "Sarah"}, + WantSpan: &sentry.Span{ + Data: map[string]interface{}{ + "db.system": sentrysql.DatabaseSystem("sqlite"), + "db.name": "memory", + "server.address": "localhost", + "server.port": "5432", + "db.operation": "INSERT", + }, + Description: "INSERT INTO exec_test (id, name) VALUES (?, ?)", + Op: "db.sql.exec", + Tags: nil, + Origin: "manual", + Sampled: sentry.SampledTrue, + Status: sentry.SpanStatusOK, + }, + }, + { + Query: "INSERT INTO exec_test (id, name) VALUES (?, ?, ?, ?)", + Parameters: []interface{}{4, "John", "Doe", "John Doe"}, + WantError: true, + WantSpan: &sentry.Span{ + Data: map[string]interface{}{ + "db.system": sentrysql.DatabaseSystem("sqlite"), + "db.name": "memory", + "server.address": "localhost", + "server.port": "5432", + "db.operation": "INSERT", + }, + Description: "INSERT INTO exec_test (id, name) VALUES (?, ?, ?, ?)", + Op: "db.sql.exec", + Tags: nil, + Origin: "manual", + Sampled: sentry.SampledTrue, + Status: sentry.SpanStatusInternalError, + }, + }, + } + + spansCh := make(chan []*sentry.Span, len(tests)) + + sentryClient, err := sentry.NewClient(sentry.ClientOptions{ + EnableTracing: true, + TracesSampleRate: 1.0, + BeforeSendTransaction: func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event { + spansCh <- event.Spans + return event + }, + }) + if err != nil { + t.Fatal(err) + } + + for _, tt := range tests { + hub := sentry.NewHub(sentryClient, sentry.NewScope()) + ctx, cancel := context.WithTimeout(sentry.SetHubOnContext(context.Background(), hub), 10*time.Second) + span := sentry.StartSpan(ctx, "fake_parent", sentry.WithTransactionName("Fake Parent")) + ctx = span.Context() + + stmt, err := db.PrepareContext(ctx, tt.Query) + if err != nil { + cancel() + t.Fatal(err) + } + + _, err = stmt.Exec(tt.Parameters...) + if err != nil && !tt.WantError { + cancel() + t.Fatal(err) + } + + span.Finish() + cancel() + } + + if ok := sentryClient.Flush(testutils.FlushTimeout()); !ok { + t.Fatal("sentry.Flush timed out") + } + close(spansCh) + + var got [][]*sentry.Span + for e := range spansCh { + got = append(got, e) + } + + for i, tt := range tests { + var foundMatch = false + gotSpans := got[i] + + var diffs []string + for _, gotSpan := range gotSpans { + if diff := cmp.Diff(tt.WantSpan, gotSpan, optstrans); diff != "" { + diffs = append(diffs, diff) + } else { + foundMatch = true + break + } + } + + if !foundMatch { + t.Errorf("Span mismatch (-want +got):\n%s", strings.Join(diffs, "\n")) + } + } + }) + + t.Run("Query", func(t *testing.T) { + tests := []struct { + Query string + Parameters []interface{} + WantSpan *sentry.Span + WantError bool + }{ + { + Query: "SELECT * FROM query_test WHERE id = ?", + Parameters: []interface{}{2}, + WantSpan: &sentry.Span{ + Data: map[string]interface{}{ + "db.system": sentrysql.DatabaseSystem("sqlite"), + "db.name": "memory", + "server.address": "localhost", + "server.port": "5432", + "db.operation": "SELECT", + }, + Description: "SELECT * FROM query_test WHERE id = ?", + Op: "db.sql.query", + Tags: nil, + Origin: "manual", + Sampled: sentry.SampledTrue, + Status: sentry.SpanStatusOK, + }, + }, + { + Query: "SELECT * FROM query_test WHERE id =", + Parameters: []interface{}{1}, + WantError: true, + WantSpan: &sentry.Span{ + Data: map[string]interface{}{ + "db.system": sentrysql.DatabaseSystem("sqlite"), + "db.name": "memory", + "server.address": "localhost", + "server.port": "5432", + "db.operation": "SELECT", + }, + Description: "SELECT * FROM query_test WHERE id =", + Op: "db.sql.query", + Tags: nil, + Origin: "manual", + Sampled: sentry.SampledTrue, + Status: sentry.SpanStatusInternalError, + }, + }, + } + + spansCh := make(chan []*sentry.Span, len(tests)) + + sentryClient, err := sentry.NewClient(sentry.ClientOptions{ + EnableTracing: true, + TracesSampleRate: 1.0, + BeforeSendTransaction: func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event { + spansCh <- event.Spans + return event + }, + }) + if err != nil { + t.Fatal(err) + } + + for _, tt := range tests { + hub := sentry.NewHub(sentryClient, sentry.NewScope()) + ctx, cancel := context.WithTimeout(sentry.SetHubOnContext(context.Background(), hub), 10*time.Second) + span := sentry.StartSpan(ctx, "fake_parent", sentry.WithTransactionName("Fake Parent")) + ctx = span.Context() + + stmt, err := db.PrepareContext(ctx, tt.Query) + if err != nil { + cancel() + t.Fatal(err) + } + + rows, err := stmt.Query(tt.Parameters...) + if err != nil && !tt.WantError { + cancel() + t.Fatal(err) + } + + if rows != nil { + _ = rows.Close() + } + + span.Finish() + cancel() + } + + if ok := sentryClient.Flush(testutils.FlushTimeout()); !ok { + t.Fatal("sentry.Flush timed out") + } + close(spansCh) + + var got [][]*sentry.Span + for e := range spansCh { + got = append(got, e) + } + + for i, tt := range tests { + var foundMatch = false + gotSpans := got[i] + + var diffs []string + for _, gotSpan := range gotSpans { + if diff := cmp.Diff(tt.WantSpan, gotSpan, optstrans); diff != "" { + diffs = append(diffs, diff) + } else { + foundMatch = true + break + } + } + + if !foundMatch { + t.Errorf("Span mismatch (-want +got):\n%s", strings.Join(diffs, "\n")) + } + } + }) + }) + + t.Run("Conn", func(t *testing.T) { + t.Run("QueryContext", func(t *testing.T) { + tests := []struct { + Query string + Parameters []interface{} + WantSpan *sentry.Span + WantError bool + }{ + { + Query: "SELECT * FROM query_test WHERE id = ?", + Parameters: []interface{}{1}, + WantSpan: &sentry.Span{ + Data: map[string]interface{}{ + "db.system": sentrysql.DatabaseSystem("sqlite"), + "db.name": "memory", + "server.address": "localhost", + "server.port": "5432", + "db.operation": "SELECT", + }, + Description: "SELECT * FROM query_test WHERE id = ?", + Op: "db.sql.query", + Tags: nil, + Origin: "manual", + Sampled: sentry.SampledTrue, + Status: sentry.SpanStatusOK, + }, + }, + { + Query: "SELECT FROM query_test", + Parameters: []interface{}{1}, + WantError: true, + WantSpan: &sentry.Span{ + Data: map[string]interface{}{ + "db.system": sentrysql.DatabaseSystem("sqlite"), + "db.name": "memory", + "server.address": "localhost", + "server.port": "5432", + "db.operation": "SELECT", + }, + Description: "SELECT FROM query_test", + Op: "db.sql.query", + Tags: nil, + Origin: "manual", + Sampled: sentry.SampledTrue, + Status: sentry.SpanStatusInternalError, + }, + }, + } + + spansCh := make(chan []*sentry.Span, len(tests)) + + sentryClient, err := sentry.NewClient(sentry.ClientOptions{ + EnableTracing: true, + TracesSampleRate: 1.0, + BeforeSendTransaction: func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event { + spansCh <- event.Spans + return event + }, + }) + if err != nil { + t.Fatal(err) + } + + for _, tt := range tests { + hub := sentry.NewHub(sentryClient, sentry.NewScope()) + ctx, cancel := context.WithTimeout(sentry.SetHubOnContext(context.Background(), hub), 10*time.Second) + span := sentry.StartSpan(ctx, "fake_parent", sentry.WithTransactionName("Fake Parent")) + ctx = span.Context() + + conn, err := db.Conn(ctx) + if err != nil { + cancel() + t.Fatal(err) + } + + rows, err := conn.QueryContext(ctx, tt.Query, tt.Parameters...) + if err != nil && !tt.WantError { + _ = conn.Close() + cancel() + t.Fatal(err) + } + + if rows != nil { + _ = rows.Close() + } + + _ = conn.Close() + + span.Finish() + cancel() + } + + if ok := sentryClient.Flush(testutils.FlushTimeout()); !ok { + t.Fatal("sentry.Flush timed out") + } + close(spansCh) + + var got [][]*sentry.Span + for e := range spansCh { + got = append(got, e) + } + + for i, tt := range tests { + var foundMatch = false + gotSpans := got[i] + + var diffs []string + for _, gotSpan := range gotSpans { + if diff := cmp.Diff(tt.WantSpan, gotSpan, optstrans); diff != "" { + diffs = append(diffs, diff) + } else { + foundMatch = true + break + } + } + + if !foundMatch { + t.Errorf("Span mismatch (-want +got):\n%s", strings.Join(diffs, "\n")) + } + } + }) + + t.Run("ExecContext", func(t *testing.T) { + tests := []struct { + Query string + Parameters []interface{} + WantSpan *sentry.Span + WantError bool + }{ + { + Query: "INSERT INTO exec_test (id, name) VALUES (?, ?)", + Parameters: []interface{}{2, "Peter"}, + WantSpan: &sentry.Span{ + Data: map[string]interface{}{ + "db.system": sentrysql.DatabaseSystem("sqlite"), + "db.name": "memory", + "server.address": "localhost", + "server.port": "5432", + "db.operation": "INSERT", + }, + Description: "INSERT INTO exec_test (id, name) VALUES (?, ?)", + Op: "db.sql.exec", + Tags: nil, + Origin: "manual", + Sampled: sentry.SampledTrue, + Status: sentry.SpanStatusOK, + }, + }, + { + Query: "INSERT INTO exec_test (id, name) VALUES (?, ?, ?, ?)", + Parameters: []interface{}{4, "John", "Doe", "John Doe"}, + WantError: true, + WantSpan: &sentry.Span{ + Data: map[string]interface{}{ + "db.system": sentrysql.DatabaseSystem("sqlite"), + "db.name": "memory", + "server.address": "localhost", + "server.port": "5432", + "db.operation": "INSERT", + }, + Description: "INSERT INTO exec_test (id, name) VALUES (?, ?, ?, ?)", + Op: "db.sql.exec", + Tags: nil, + Origin: "manual", + Sampled: sentry.SampledTrue, + Status: sentry.SpanStatusInternalError, + }, + }, + } + + spansCh := make(chan []*sentry.Span, len(tests)) + + sentryClient, err := sentry.NewClient(sentry.ClientOptions{ + EnableTracing: true, + TracesSampleRate: 1.0, + BeforeSendTransaction: func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event { + spansCh <- event.Spans + return event + }, + }) + if err != nil { + t.Fatal(err) + } + + for _, tt := range tests { + hub := sentry.NewHub(sentryClient, sentry.NewScope()) + ctx, cancel := context.WithTimeout(sentry.SetHubOnContext(context.Background(), hub), 10*time.Second) + span := sentry.StartSpan(ctx, "fake_parent", sentry.WithTransactionName("Fake Parent")) + ctx = span.Context() + + conn, err := db.Conn(ctx) + if err != nil { + cancel() + t.Fatal(err) + } + + _, err = conn.ExecContext(ctx, tt.Query, tt.Parameters...) + if err != nil && !tt.WantError { + _ = conn.Close() + cancel() + t.Fatal(err) + } + + _ = conn.Close() + + span.Finish() + cancel() + } + + if ok := sentryClient.Flush(testutils.FlushTimeout()); !ok { + t.Fatal("sentry.Flush timed out") + } + close(spansCh) + + var got [][]*sentry.Span + for e := range spansCh { + got = append(got, e) + } + + for i, tt := range tests { + var foundMatch = false + gotSpans := got[i] + + var diffs []string + for _, gotSpan := range gotSpans { + if diff := cmp.Diff(tt.WantSpan, gotSpan, optstrans); diff != "" { + diffs = append(diffs, diff) + } else { + foundMatch = true + break + } + } + + if !foundMatch { + t.Errorf("Span mismatch (-want +got):\n%s", strings.Join(diffs, "\n")) + } + } + }) + }) + + t.Run("Ping", func(t *testing.T) { + // Just checking if this works and doesn't panic + err := db.Ping() + if err != nil { + t.Fatal(err) + } + }) + + t.Run("PingContext", func(t *testing.T) { + // Just checking if this works and doesn't panic + err := db.PingContext(context.Background()) + if err != nil { + t.Fatal(err) + } + }) + + t.Run("BeginTx", func(t *testing.T) { + t.Run("Singles", func(t *testing.T) { + tests := []struct { + Query string + Parameters []interface{} + WantSpan *sentry.Span + WantError bool + }{ + { + Query: "INSERT INTO exec_test (id, name) VALUES (?, ?)", + Parameters: []interface{}{2, "Peter"}, + WantSpan: &sentry.Span{ + Data: map[string]interface{}{ + "db.system": sentrysql.DatabaseSystem("sqlite"), + "db.name": "memory", + "server.address": "localhost", + "server.port": "5432", + "db.operation": "INSERT", + }, + Description: "INSERT INTO exec_test (id, name) VALUES (?, ?)", + Op: "db.sql.exec", + Tags: nil, + Origin: "manual", + Sampled: sentry.SampledTrue, + Status: sentry.SpanStatusOK, + }, + }, + { + Query: "INSERT INTO exec_test (id, name) VALUES (?, ?, ?, ?)", + Parameters: []interface{}{4, "John", "Doe", "John Doe"}, + WantError: true, + WantSpan: &sentry.Span{ + Data: map[string]interface{}{ + "db.system": sentrysql.DatabaseSystem("sqlite"), + "db.name": "memory", + "server.address": "localhost", + "server.port": "5432", + "db.operation": "INSERT", + }, + Description: "INSERT INTO exec_test (id, name) VALUES (?, ?, ?, ?)", + Op: "db.sql.exec", + Tags: nil, + Origin: "manual", + Sampled: sentry.SampledTrue, + Status: sentry.SpanStatusInternalError, + }, + }, + } + + spansCh := make(chan []*sentry.Span, len(tests)) + + sentryClient, err := sentry.NewClient(sentry.ClientOptions{ + EnableTracing: true, + TracesSampleRate: 1.0, + BeforeSendTransaction: func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event { + spansCh <- event.Spans + return event + }, + }) + if err != nil { + t.Fatal(err) + } + + for _, tt := range tests { + hub := sentry.NewHub(sentryClient, sentry.NewScope()) + ctx, cancel := context.WithTimeout(sentry.SetHubOnContext(context.Background(), hub), 10*time.Second) + span := sentry.StartSpan(ctx, "fake_parent", sentry.WithTransactionName("Fake Parent")) + ctx = span.Context() + + conn, err := db.Conn(ctx) + if err != nil { + cancel() + t.Fatal(err) + } + + tx, err := conn.BeginTx(ctx, nil) + if err != nil { + cancel() + t.Fatal(err) + } + + _, err = tx.ExecContext(ctx, tt.Query, tt.Parameters...) + if err != nil && !tt.WantError { + _ = conn.Close() + cancel() + t.Fatal(err) + } + + err = tx.Commit() + if err != nil && !tt.WantError { + _ = conn.Close() + cancel() + t.Fatal(err) + } + + _ = tx.Rollback() + + _ = conn.Close() + + span.Finish() + cancel() + } + + if ok := sentryClient.Flush(testutils.FlushTimeout()); !ok { + t.Fatal("sentry.Flush timed out") + } + close(spansCh) + + var got [][]*sentry.Span + for e := range spansCh { + got = append(got, e) + } + + for i, tt := range tests { + var foundMatch = false + gotSpans := got[i] + + var diffs []string + for _, gotSpan := range gotSpans { + if diff := cmp.Diff(tt.WantSpan, gotSpan, optstrans); diff != "" { + diffs = append(diffs, diff) + } else { + foundMatch = true + break + } + } + + if !foundMatch { + t.Errorf("Span mismatch (-want +got):\n%s", strings.Join(diffs, "\n")) + } + } + }) + + t.Run("Multiple Queries", func(t *testing.T) { + spansCh := make(chan []*sentry.Span, 2) + + sentryClient, err := sentry.NewClient(sentry.ClientOptions{ + EnableTracing: true, + TracesSampleRate: 1.0, + BeforeSendTransaction: func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event { + spansCh <- event.Spans + return event + }, + }) + if err != nil { + t.Fatal(err) + } + + hub := sentry.NewHub(sentryClient, sentry.NewScope()) + ctx, cancel := context.WithTimeout(sentry.SetHubOnContext(context.Background(), hub), 10*time.Second) + defer cancel() + span := sentry.StartSpan(ctx, "fake_parent", sentry.WithTransactionName("Fake Parent")) + ctx = span.Context() + + conn, err := db.Conn(ctx) + if err != nil { + t.Fatal(err) + } + + tx, err := conn.BeginTx(ctx, nil) + if err != nil { + t.Fatal(err) + } + defer tx.Rollback() + + var name string + err = tx.QueryRowContext(ctx, "SELECT name FROM query_test WHERE id = ?", 1).Scan(&name) + if err != nil { + tx.Rollback() + conn.Close() + cancel() + t.Fatal(err) + } + + _, err = tx.ExecContext(ctx, "INSERT INTO exec_test (id, name) VALUES (?, ?)", 5, "Catherine") + if err != nil { + tx.Rollback() + conn.Close() + cancel() + t.Fatal(err) + } + + err = tx.Commit() + if err != nil { + conn.Close() + cancel() + t.Fatal(err) + } + + conn.Close() + + span.Finish() + + cancel() + + if ok := sentryClient.Flush(testutils.FlushTimeout()); !ok { + t.Fatal("sentry.Flush timed out") + } + close(spansCh) + + var got []*sentry.Span + for e := range spansCh { + got = append(got, e...) + } + + want := []*sentry.Span{ + { + Data: map[string]interface{}{ + "db.system": sentrysql.DatabaseSystem("sqlite"), + "db.name": "memory", + "server.address": "localhost", + "server.port": "5432", + "db.operation": "SELECT", + }, + Description: "SELECT name FROM query_test WHERE id = ?", + Op: "db.sql.query", + Tags: nil, + Origin: "manual", + Sampled: sentry.SampledTrue, + Status: sentry.SpanStatusOK, + }, + { + Data: map[string]interface{}{ + "db.system": sentrysql.DatabaseSystem("sqlite"), + "db.name": "memory", + "server.address": "localhost", + "server.port": "5432", + "db.operation": "INSERT", + }, + Description: "INSERT INTO exec_test (id, name) VALUES (?, ?)", + Op: "db.sql.exec", + Tags: nil, + Origin: "manual", + Sampled: sentry.SampledTrue, + Status: sentry.SpanStatusOK, + }, + } + + if diff := cmp.Diff(want, got, optstrans); diff != "" { + t.Errorf("Span mismatch (-want +got):\n%s", diff) + } + }) + }) } diff --git a/sentrysql/stmt.go b/sentrysql/stmt.go index b62abf65..51424065 100644 --- a/sentrysql/stmt.go +++ b/sentrysql/stmt.go @@ -113,7 +113,7 @@ func (s *sentryStmt) QueryContext(ctx context.Context, args []driver.NamedValue) return s.Query(values) } - parentSpan := sentry.SpanFromContext(ctx) + parentSpan := sentry.SpanFromContext(s.ctx) if parentSpan == nil { return stmtQueryContext.QueryContext(ctx, args) } From 19c97dfa611c4d70c69b74dac10c262507f3fe98 Mon Sep 17 00:00:00 2001 From: Reinaldy Rafli Date: Sat, 19 Oct 2024 10:31:58 +0700 Subject: [PATCH 10/26] chore: avoid cyclo errors --- sentrysql/sentrysql_test.go | 1283 +++++++++++++++++++---------------- 1 file changed, 684 insertions(+), 599 deletions(-) diff --git a/sentrysql/sentrysql_test.go b/sentrysql/sentrysql_test.go index 4ff51735..ac939a9c 100644 --- a/sentrysql/sentrysql_test.go +++ b/sentrysql/sentrysql_test.go @@ -4,6 +4,7 @@ import ( "context" "database/sql" "fmt" + "os" "strings" "testing" "time" @@ -65,10 +66,22 @@ func ExampleNewSentrySQLConnector() { // Continue executing PostgreSQL queries } -//nolint:dupl -func TestNewSentrySQL_Integration(t *testing.T) { +var optstrans = cmp.Options{ + cmpopts.IgnoreFields( + sentry.Span{}, + "TraceID", "SpanID", "ParentSpanID", "StartTime", "EndTime", + "mu", "parent", "sampleRate", "ctx", "dynamicSamplingContext", "recorder", "finishOnce", "collectProfile", "contexts", + ), +} + +func TestMain(m *testing.M) { sql.Register("sentrysql-sqlite", sentrysql.NewSentrySQL(&sqlite.Driver{}, sentrysql.WithDatabaseName("memory"), sentrysql.WithDatabaseSystem(sentrysql.DatabaseSystem("sqlite")), sentrysql.WithServerAddress("localhost", "5432"))) + os.Exit(m.Run()) +} + +//nolint:dupl +func TestNewSentrySQL_Integration(t *testing.T) { db, err := sql.Open("sentrysql-sqlite", ":memory:") if err != nil { t.Fatalf("opening sqlite: %v", err) @@ -94,14 +107,6 @@ func TestNewSentrySQL_Integration(t *testing.T) { } } - optstrans := cmp.Options{ - cmpopts.IgnoreFields( - sentry.Span{}, - "TraceID", "SpanID", "ParentSpanID", "StartTime", "EndTime", - "mu", "parent", "sampleRate", "ctx", "dynamicSamplingContext", "recorder", "finishOnce", "collectProfile", "contexts", - ), - } - t.Run("QueryContext", func(t *testing.T) { tests := []struct { Query string @@ -361,703 +366,713 @@ func TestNewSentrySQL_Integration(t *testing.T) { } }) - t.Run("PrepareContext", func(t *testing.T) { - t.Run("Exec", func(t *testing.T) { - tests := []struct { - Query string - Parameters []interface{} - WantSpan *sentry.Span - WantError bool - }{ - { - Query: "INSERT INTO exec_test (id, name) VALUES (?, ?)", - Parameters: []interface{}{3, "Sarah"}, - WantSpan: &sentry.Span{ - Data: map[string]interface{}{ - "db.system": sentrysql.DatabaseSystem("sqlite"), - "db.name": "memory", - "server.address": "localhost", - "server.port": "5432", - "db.operation": "INSERT", - }, - Description: "INSERT INTO exec_test (id, name) VALUES (?, ?)", - Op: "db.sql.exec", - Tags: nil, - Origin: "manual", - Sampled: sentry.SampledTrue, - Status: sentry.SpanStatusOK, - }, - }, - { - Query: "INSERT INTO exec_test (id, name) VALUES (?, ?, ?, ?)", - Parameters: []interface{}{4, "John", "Doe", "John Doe"}, - WantError: true, - WantSpan: &sentry.Span{ - Data: map[string]interface{}{ - "db.system": sentrysql.DatabaseSystem("sqlite"), - "db.name": "memory", - "server.address": "localhost", - "server.port": "5432", - "db.operation": "INSERT", - }, - Description: "INSERT INTO exec_test (id, name) VALUES (?, ?, ?, ?)", - Op: "db.sql.exec", - Tags: nil, - Origin: "manual", - Sampled: sentry.SampledTrue, - Status: sentry.SpanStatusInternalError, - }, - }, - } - - spansCh := make(chan []*sentry.Span, len(tests)) - - sentryClient, err := sentry.NewClient(sentry.ClientOptions{ - EnableTracing: true, - TracesSampleRate: 1.0, - BeforeSendTransaction: func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event { - spansCh <- event.Spans - return event - }, - }) - if err != nil { - t.Fatal(err) - } - - for _, tt := range tests { - hub := sentry.NewHub(sentryClient, sentry.NewScope()) - ctx, cancel := context.WithTimeout(sentry.SetHubOnContext(context.Background(), hub), 10*time.Second) - span := sentry.StartSpan(ctx, "fake_parent", sentry.WithTransactionName("Fake Parent")) - ctx = span.Context() - - stmt, err := db.PrepareContext(ctx, tt.Query) - if err != nil { - cancel() - t.Fatal(err) - } - - _, err = stmt.Exec(tt.Parameters...) - if err != nil && !tt.WantError { - cancel() - t.Fatal(err) - } + t.Run("Ping", func(t *testing.T) { + // Just checking if this works and doesn't panic + err := db.Ping() + if err != nil { + t.Fatal(err) + } + }) - span.Finish() - cancel() - } + t.Run("PingContext", func(t *testing.T) { + // Just checking if this works and doesn't panic + err := db.PingContext(context.Background()) + if err != nil { + t.Fatal(err) + } + }) +} - if ok := sentryClient.Flush(testutils.FlushTimeout()); !ok { - t.Fatal("sentry.Flush timed out") - } - close(spansCh) +//nolint:dupl +func TestNewSentrySQL_Conn(t *testing.T) { + db, err := sql.Open("sentrysql-sqlite", ":memory:") + if err != nil { + t.Fatalf("opening sqlite: %v", err) + } + db.SetMaxOpenConns(1) + defer db.Close() - var got [][]*sentry.Span - for e := range spansCh { - got = append(got, e) - } + setupQueries := []string{ + "CREATE TABLE exec_test (id INT, name TEXT)", + "CREATE TABLE query_test (id INT, name TEXT, age INT, created_at TEXT)", + "INSERT INTO query_test (id, name, age, created_at) VALUES (1, 'John', 30, '2023-01-01')", + "INSERT INTO query_test (id, name, age, created_at) VALUES (2, 'Jane', 25, '2023-01-02')", + "INSERT INTO query_test (id, name, age, created_at) VALUES (3, 'Bob', 35, '2023-01-03')", + } - for i, tt := range tests { - var foundMatch = false - gotSpans := got[i] - - var diffs []string - for _, gotSpan := range gotSpans { - if diff := cmp.Diff(tt.WantSpan, gotSpan, optstrans); diff != "" { - diffs = append(diffs, diff) - } else { - foundMatch = true - break - } - } + setupCtx, cancelCtx := context.WithTimeout(context.Background(), 30*time.Second) + defer cancelCtx() - if !foundMatch { - t.Errorf("Span mismatch (-want +got):\n%s", strings.Join(diffs, "\n")) - } - } - }) + for _, query := range setupQueries { + _, err = db.ExecContext(setupCtx, query) + if err != nil { + t.Fatalf("initializing table on sqlite: %v", err) + } + } - t.Run("Query", func(t *testing.T) { - tests := []struct { - Query string - Parameters []interface{} - WantSpan *sentry.Span - WantError bool - }{ - { - Query: "SELECT * FROM query_test WHERE id = ?", - Parameters: []interface{}{2}, - WantSpan: &sentry.Span{ - Data: map[string]interface{}{ - "db.system": sentrysql.DatabaseSystem("sqlite"), - "db.name": "memory", - "server.address": "localhost", - "server.port": "5432", - "db.operation": "SELECT", - }, - Description: "SELECT * FROM query_test WHERE id = ?", - Op: "db.sql.query", - Tags: nil, - Origin: "manual", - Sampled: sentry.SampledTrue, - Status: sentry.SpanStatusOK, + t.Run("QueryContext", func(t *testing.T) { + tests := []struct { + Query string + Parameters []interface{} + WantSpan *sentry.Span + WantError bool + }{ + { + Query: "SELECT * FROM query_test WHERE id = ?", + Parameters: []interface{}{1}, + WantSpan: &sentry.Span{ + Data: map[string]interface{}{ + "db.system": sentrysql.DatabaseSystem("sqlite"), + "db.name": "memory", + "server.address": "localhost", + "server.port": "5432", + "db.operation": "SELECT", }, + Description: "SELECT * FROM query_test WHERE id = ?", + Op: "db.sql.query", + Tags: nil, + Origin: "manual", + Sampled: sentry.SampledTrue, + Status: sentry.SpanStatusOK, }, - { - Query: "SELECT * FROM query_test WHERE id =", - Parameters: []interface{}{1}, - WantError: true, - WantSpan: &sentry.Span{ - Data: map[string]interface{}{ - "db.system": sentrysql.DatabaseSystem("sqlite"), - "db.name": "memory", - "server.address": "localhost", - "server.port": "5432", - "db.operation": "SELECT", - }, - Description: "SELECT * FROM query_test WHERE id =", - Op: "db.sql.query", - Tags: nil, - Origin: "manual", - Sampled: sentry.SampledTrue, - Status: sentry.SpanStatusInternalError, + }, + { + Query: "SELECT FROM query_test", + Parameters: []interface{}{1}, + WantError: true, + WantSpan: &sentry.Span{ + Data: map[string]interface{}{ + "db.system": sentrysql.DatabaseSystem("sqlite"), + "db.name": "memory", + "server.address": "localhost", + "server.port": "5432", + "db.operation": "SELECT", }, + Description: "SELECT FROM query_test", + Op: "db.sql.query", + Tags: nil, + Origin: "manual", + Sampled: sentry.SampledTrue, + Status: sentry.SpanStatusInternalError, }, - } + }, + } - spansCh := make(chan []*sentry.Span, len(tests)) + spansCh := make(chan []*sentry.Span, len(tests)) - sentryClient, err := sentry.NewClient(sentry.ClientOptions{ - EnableTracing: true, - TracesSampleRate: 1.0, - BeforeSendTransaction: func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event { - spansCh <- event.Spans - return event - }, - }) + sentryClient, err := sentry.NewClient(sentry.ClientOptions{ + EnableTracing: true, + TracesSampleRate: 1.0, + BeforeSendTransaction: func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event { + spansCh <- event.Spans + return event + }, + }) + if err != nil { + t.Fatal(err) + } + + for _, tt := range tests { + hub := sentry.NewHub(sentryClient, sentry.NewScope()) + ctx, cancel := context.WithTimeout(sentry.SetHubOnContext(context.Background(), hub), 10*time.Second) + span := sentry.StartSpan(ctx, "fake_parent", sentry.WithTransactionName("Fake Parent")) + ctx = span.Context() + + conn, err := db.Conn(ctx) if err != nil { + cancel() t.Fatal(err) } - for _, tt := range tests { - hub := sentry.NewHub(sentryClient, sentry.NewScope()) - ctx, cancel := context.WithTimeout(sentry.SetHubOnContext(context.Background(), hub), 10*time.Second) - span := sentry.StartSpan(ctx, "fake_parent", sentry.WithTransactionName("Fake Parent")) - ctx = span.Context() + rows, err := conn.QueryContext(ctx, tt.Query, tt.Parameters...) + if err != nil && !tt.WantError { + _ = conn.Close() + cancel() + t.Fatal(err) + } - stmt, err := db.PrepareContext(ctx, tt.Query) - if err != nil { - cancel() - t.Fatal(err) - } + if rows != nil { + _ = rows.Close() + } - rows, err := stmt.Query(tt.Parameters...) - if err != nil && !tt.WantError { - cancel() - t.Fatal(err) - } + _ = conn.Close() - if rows != nil { - _ = rows.Close() - } + span.Finish() + cancel() + } - span.Finish() - cancel() - } + if ok := sentryClient.Flush(testutils.FlushTimeout()); !ok { + t.Fatal("sentry.Flush timed out") + } + close(spansCh) - if ok := sentryClient.Flush(testutils.FlushTimeout()); !ok { - t.Fatal("sentry.Flush timed out") - } - close(spansCh) + var got [][]*sentry.Span + for e := range spansCh { + got = append(got, e) + } - var got [][]*sentry.Span - for e := range spansCh { - got = append(got, e) - } + for i, tt := range tests { + var foundMatch = false + gotSpans := got[i] - for i, tt := range tests { - var foundMatch = false - gotSpans := got[i] - - var diffs []string - for _, gotSpan := range gotSpans { - if diff := cmp.Diff(tt.WantSpan, gotSpan, optstrans); diff != "" { - diffs = append(diffs, diff) - } else { - foundMatch = true - break - } + var diffs []string + for _, gotSpan := range gotSpans { + if diff := cmp.Diff(tt.WantSpan, gotSpan, optstrans); diff != "" { + diffs = append(diffs, diff) + } else { + foundMatch = true + break } + } - if !foundMatch { - t.Errorf("Span mismatch (-want +got):\n%s", strings.Join(diffs, "\n")) - } + if !foundMatch { + t.Errorf("Span mismatch (-want +got):\n%s", strings.Join(diffs, "\n")) } - }) + } }) - t.Run("Conn", func(t *testing.T) { - t.Run("QueryContext", func(t *testing.T) { - tests := []struct { - Query string - Parameters []interface{} - WantSpan *sentry.Span - WantError bool - }{ - { - Query: "SELECT * FROM query_test WHERE id = ?", - Parameters: []interface{}{1}, - WantSpan: &sentry.Span{ - Data: map[string]interface{}{ - "db.system": sentrysql.DatabaseSystem("sqlite"), - "db.name": "memory", - "server.address": "localhost", - "server.port": "5432", - "db.operation": "SELECT", - }, - Description: "SELECT * FROM query_test WHERE id = ?", - Op: "db.sql.query", - Tags: nil, - Origin: "manual", - Sampled: sentry.SampledTrue, - Status: sentry.SpanStatusOK, + t.Run("ExecContext", func(t *testing.T) { + tests := []struct { + Query string + Parameters []interface{} + WantSpan *sentry.Span + WantError bool + }{ + { + Query: "INSERT INTO exec_test (id, name) VALUES (?, ?)", + Parameters: []interface{}{2, "Peter"}, + WantSpan: &sentry.Span{ + Data: map[string]interface{}{ + "db.system": sentrysql.DatabaseSystem("sqlite"), + "db.name": "memory", + "server.address": "localhost", + "server.port": "5432", + "db.operation": "INSERT", }, + Description: "INSERT INTO exec_test (id, name) VALUES (?, ?)", + Op: "db.sql.exec", + Tags: nil, + Origin: "manual", + Sampled: sentry.SampledTrue, + Status: sentry.SpanStatusOK, }, - { - Query: "SELECT FROM query_test", - Parameters: []interface{}{1}, - WantError: true, - WantSpan: &sentry.Span{ - Data: map[string]interface{}{ - "db.system": sentrysql.DatabaseSystem("sqlite"), - "db.name": "memory", - "server.address": "localhost", - "server.port": "5432", - "db.operation": "SELECT", - }, - Description: "SELECT FROM query_test", - Op: "db.sql.query", - Tags: nil, - Origin: "manual", - Sampled: sentry.SampledTrue, - Status: sentry.SpanStatusInternalError, + }, + { + Query: "INSERT INTO exec_test (id, name) VALUES (?, ?, ?, ?)", + Parameters: []interface{}{4, "John", "Doe", "John Doe"}, + WantError: true, + WantSpan: &sentry.Span{ + Data: map[string]interface{}{ + "db.system": sentrysql.DatabaseSystem("sqlite"), + "db.name": "memory", + "server.address": "localhost", + "server.port": "5432", + "db.operation": "INSERT", }, + Description: "INSERT INTO exec_test (id, name) VALUES (?, ?, ?, ?)", + Op: "db.sql.exec", + Tags: nil, + Origin: "manual", + Sampled: sentry.SampledTrue, + Status: sentry.SpanStatusInternalError, }, - } + }, + } - spansCh := make(chan []*sentry.Span, len(tests)) + spansCh := make(chan []*sentry.Span, len(tests)) - sentryClient, err := sentry.NewClient(sentry.ClientOptions{ - EnableTracing: true, - TracesSampleRate: 1.0, - BeforeSendTransaction: func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event { - spansCh <- event.Spans - return event - }, - }) + sentryClient, err := sentry.NewClient(sentry.ClientOptions{ + EnableTracing: true, + TracesSampleRate: 1.0, + BeforeSendTransaction: func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event { + spansCh <- event.Spans + return event + }, + }) + if err != nil { + t.Fatal(err) + } + + for _, tt := range tests { + hub := sentry.NewHub(sentryClient, sentry.NewScope()) + ctx, cancel := context.WithTimeout(sentry.SetHubOnContext(context.Background(), hub), 10*time.Second) + span := sentry.StartSpan(ctx, "fake_parent", sentry.WithTransactionName("Fake Parent")) + ctx = span.Context() + + conn, err := db.Conn(ctx) if err != nil { + cancel() t.Fatal(err) } - for _, tt := range tests { - hub := sentry.NewHub(sentryClient, sentry.NewScope()) - ctx, cancel := context.WithTimeout(sentry.SetHubOnContext(context.Background(), hub), 10*time.Second) - span := sentry.StartSpan(ctx, "fake_parent", sentry.WithTransactionName("Fake Parent")) - ctx = span.Context() - - conn, err := db.Conn(ctx) - if err != nil { - cancel() - t.Fatal(err) - } - - rows, err := conn.QueryContext(ctx, tt.Query, tt.Parameters...) - if err != nil && !tt.WantError { - _ = conn.Close() - cancel() - t.Fatal(err) - } - - if rows != nil { - _ = rows.Close() - } - + _, err = conn.ExecContext(ctx, tt.Query, tt.Parameters...) + if err != nil && !tt.WantError { _ = conn.Close() - - span.Finish() cancel() + t.Fatal(err) } - if ok := sentryClient.Flush(testutils.FlushTimeout()); !ok { - t.Fatal("sentry.Flush timed out") - } - close(spansCh) - - var got [][]*sentry.Span - for e := range spansCh { - got = append(got, e) - } + _ = conn.Close() - for i, tt := range tests { - var foundMatch = false - gotSpans := got[i] - - var diffs []string - for _, gotSpan := range gotSpans { - if diff := cmp.Diff(tt.WantSpan, gotSpan, optstrans); diff != "" { - diffs = append(diffs, diff) - } else { - foundMatch = true - break - } - } + span.Finish() + cancel() + } - if !foundMatch { - t.Errorf("Span mismatch (-want +got):\n%s", strings.Join(diffs, "\n")) + if ok := sentryClient.Flush(testutils.FlushTimeout()); !ok { + t.Fatal("sentry.Flush timed out") + } + close(spansCh) + + var got [][]*sentry.Span + for e := range spansCh { + got = append(got, e) + } + + for i, tt := range tests { + var foundMatch = false + gotSpans := got[i] + + var diffs []string + for _, gotSpan := range gotSpans { + if diff := cmp.Diff(tt.WantSpan, gotSpan, optstrans); diff != "" { + diffs = append(diffs, diff) + } else { + foundMatch = true + break } } - }) - t.Run("ExecContext", func(t *testing.T) { - tests := []struct { - Query string - Parameters []interface{} - WantSpan *sentry.Span - WantError bool - }{ - { - Query: "INSERT INTO exec_test (id, name) VALUES (?, ?)", - Parameters: []interface{}{2, "Peter"}, - WantSpan: &sentry.Span{ - Data: map[string]interface{}{ - "db.system": sentrysql.DatabaseSystem("sqlite"), - "db.name": "memory", - "server.address": "localhost", - "server.port": "5432", - "db.operation": "INSERT", - }, - Description: "INSERT INTO exec_test (id, name) VALUES (?, ?)", - Op: "db.sql.exec", - Tags: nil, - Origin: "manual", - Sampled: sentry.SampledTrue, - Status: sentry.SpanStatusOK, + if !foundMatch { + t.Errorf("Span mismatch (-want +got):\n%s", strings.Join(diffs, "\n")) + } + } + }) +} + +//nolint:dupl +func TestNewSentrySQL_BeginTx(t *testing.T) { + db, err := sql.Open("sentrysql-sqlite", ":memory:") + if err != nil { + t.Fatalf("opening sqlite: %v", err) + } + db.SetMaxOpenConns(1) + defer db.Close() + + setupQueries := []string{ + "CREATE TABLE exec_test (id INT, name TEXT)", + "CREATE TABLE query_test (id INT, name TEXT, age INT, created_at TEXT)", + "INSERT INTO query_test (id, name, age, created_at) VALUES (1, 'John', 30, '2023-01-01')", + "INSERT INTO query_test (id, name, age, created_at) VALUES (2, 'Jane', 25, '2023-01-02')", + "INSERT INTO query_test (id, name, age, created_at) VALUES (3, 'Bob', 35, '2023-01-03')", + } + + setupCtx, cancelCtx := context.WithTimeout(context.Background(), 30*time.Second) + defer cancelCtx() + + for _, query := range setupQueries { + _, err = db.ExecContext(setupCtx, query) + if err != nil { + t.Fatalf("initializing table on sqlite: %v", err) + } + } + + t.Run("Singles", func(t *testing.T) { + tests := []struct { + Query string + Parameters []interface{} + WantSpan *sentry.Span + WantError bool + }{ + { + Query: "INSERT INTO exec_test (id, name) VALUES (?, ?)", + Parameters: []interface{}{2, "Peter"}, + WantSpan: &sentry.Span{ + Data: map[string]interface{}{ + "db.system": sentrysql.DatabaseSystem("sqlite"), + "db.name": "memory", + "server.address": "localhost", + "server.port": "5432", + "db.operation": "INSERT", }, + Description: "INSERT INTO exec_test (id, name) VALUES (?, ?)", + Op: "db.sql.exec", + Tags: nil, + Origin: "manual", + Sampled: sentry.SampledTrue, + Status: sentry.SpanStatusOK, }, - { - Query: "INSERT INTO exec_test (id, name) VALUES (?, ?, ?, ?)", - Parameters: []interface{}{4, "John", "Doe", "John Doe"}, - WantError: true, - WantSpan: &sentry.Span{ - Data: map[string]interface{}{ - "db.system": sentrysql.DatabaseSystem("sqlite"), - "db.name": "memory", - "server.address": "localhost", - "server.port": "5432", - "db.operation": "INSERT", - }, - Description: "INSERT INTO exec_test (id, name) VALUES (?, ?, ?, ?)", - Op: "db.sql.exec", - Tags: nil, - Origin: "manual", - Sampled: sentry.SampledTrue, - Status: sentry.SpanStatusInternalError, + }, + { + Query: "INSERT INTO exec_test (id, name) VALUES (?, ?, ?, ?)", + Parameters: []interface{}{4, "John", "Doe", "John Doe"}, + WantError: true, + WantSpan: &sentry.Span{ + Data: map[string]interface{}{ + "db.system": sentrysql.DatabaseSystem("sqlite"), + "db.name": "memory", + "server.address": "localhost", + "server.port": "5432", + "db.operation": "INSERT", }, + Description: "INSERT INTO exec_test (id, name) VALUES (?, ?, ?, ?)", + Op: "db.sql.exec", + Tags: nil, + Origin: "manual", + Sampled: sentry.SampledTrue, + Status: sentry.SpanStatusInternalError, }, - } + }, + } - spansCh := make(chan []*sentry.Span, len(tests)) + spansCh := make(chan []*sentry.Span, len(tests)) - sentryClient, err := sentry.NewClient(sentry.ClientOptions{ - EnableTracing: true, - TracesSampleRate: 1.0, - BeforeSendTransaction: func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event { - spansCh <- event.Spans - return event - }, - }) + sentryClient, err := sentry.NewClient(sentry.ClientOptions{ + EnableTracing: true, + TracesSampleRate: 1.0, + BeforeSendTransaction: func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event { + spansCh <- event.Spans + return event + }, + }) + if err != nil { + t.Fatal(err) + } + + for _, tt := range tests { + hub := sentry.NewHub(sentryClient, sentry.NewScope()) + ctx, cancel := context.WithTimeout(sentry.SetHubOnContext(context.Background(), hub), 10*time.Second) + span := sentry.StartSpan(ctx, "fake_parent", sentry.WithTransactionName("Fake Parent")) + ctx = span.Context() + + conn, err := db.Conn(ctx) if err != nil { + cancel() t.Fatal(err) } - for _, tt := range tests { - hub := sentry.NewHub(sentryClient, sentry.NewScope()) - ctx, cancel := context.WithTimeout(sentry.SetHubOnContext(context.Background(), hub), 10*time.Second) - span := sentry.StartSpan(ctx, "fake_parent", sentry.WithTransactionName("Fake Parent")) - ctx = span.Context() - - conn, err := db.Conn(ctx) - if err != nil { - cancel() - t.Fatal(err) - } - - _, err = conn.ExecContext(ctx, tt.Query, tt.Parameters...) - if err != nil && !tt.WantError { - _ = conn.Close() - cancel() - t.Fatal(err) - } + tx, err := conn.BeginTx(ctx, nil) + if err != nil { + cancel() + t.Fatal(err) + } + _, err = tx.ExecContext(ctx, tt.Query, tt.Parameters...) + if err != nil && !tt.WantError { _ = conn.Close() - - span.Finish() cancel() + t.Fatal(err) } - if ok := sentryClient.Flush(testutils.FlushTimeout()); !ok { - t.Fatal("sentry.Flush timed out") + err = tx.Commit() + if err != nil && !tt.WantError { + _ = conn.Close() + cancel() + t.Fatal(err) } - close(spansCh) - var got [][]*sentry.Span - for e := range spansCh { - got = append(got, e) - } + _ = tx.Rollback() - for i, tt := range tests { - var foundMatch = false - gotSpans := got[i] - - var diffs []string - for _, gotSpan := range gotSpans { - if diff := cmp.Diff(tt.WantSpan, gotSpan, optstrans); diff != "" { - diffs = append(diffs, diff) - } else { - foundMatch = true - break - } - } + _ = conn.Close() - if !foundMatch { - t.Errorf("Span mismatch (-want +got):\n%s", strings.Join(diffs, "\n")) + span.Finish() + cancel() + } + + if ok := sentryClient.Flush(testutils.FlushTimeout()); !ok { + t.Fatal("sentry.Flush timed out") + } + close(spansCh) + + var got [][]*sentry.Span + for e := range spansCh { + got = append(got, e) + } + + for i, tt := range tests { + var foundMatch = false + gotSpans := got[i] + + var diffs []string + for _, gotSpan := range gotSpans { + if diff := cmp.Diff(tt.WantSpan, gotSpan, optstrans); diff != "" { + diffs = append(diffs, diff) + } else { + foundMatch = true + break } } - }) + + if !foundMatch { + t.Errorf("Span mismatch (-want +got):\n%s", strings.Join(diffs, "\n")) + } + } }) - t.Run("Ping", func(t *testing.T) { - // Just checking if this works and doesn't panic - err := db.Ping() + t.Run("Multiple Queries", func(t *testing.T) { + spansCh := make(chan []*sentry.Span, 2) + + sentryClient, err := sentry.NewClient(sentry.ClientOptions{ + EnableTracing: true, + TracesSampleRate: 1.0, + BeforeSendTransaction: func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event { + spansCh <- event.Spans + return event + }, + }) if err != nil { t.Fatal(err) } - }) - t.Run("PingContext", func(t *testing.T) { - // Just checking if this works and doesn't panic - err := db.PingContext(context.Background()) + hub := sentry.NewHub(sentryClient, sentry.NewScope()) + ctx, cancel := context.WithTimeout(sentry.SetHubOnContext(context.Background(), hub), 10*time.Second) + defer cancel() + span := sentry.StartSpan(ctx, "fake_parent", sentry.WithTransactionName("Fake Parent")) + ctx = span.Context() + + conn, err := db.Conn(ctx) if err != nil { t.Fatal(err) } - }) - t.Run("BeginTx", func(t *testing.T) { - t.Run("Singles", func(t *testing.T) { - tests := []struct { - Query string - Parameters []interface{} - WantSpan *sentry.Span - WantError bool - }{ - { - Query: "INSERT INTO exec_test (id, name) VALUES (?, ?)", - Parameters: []interface{}{2, "Peter"}, - WantSpan: &sentry.Span{ - Data: map[string]interface{}{ - "db.system": sentrysql.DatabaseSystem("sqlite"), - "db.name": "memory", - "server.address": "localhost", - "server.port": "5432", - "db.operation": "INSERT", - }, - Description: "INSERT INTO exec_test (id, name) VALUES (?, ?)", - Op: "db.sql.exec", - Tags: nil, - Origin: "manual", - Sampled: sentry.SampledTrue, - Status: sentry.SpanStatusOK, - }, - }, - { - Query: "INSERT INTO exec_test (id, name) VALUES (?, ?, ?, ?)", - Parameters: []interface{}{4, "John", "Doe", "John Doe"}, - WantError: true, - WantSpan: &sentry.Span{ - Data: map[string]interface{}{ - "db.system": sentrysql.DatabaseSystem("sqlite"), - "db.name": "memory", - "server.address": "localhost", - "server.port": "5432", - "db.operation": "INSERT", - }, - Description: "INSERT INTO exec_test (id, name) VALUES (?, ?, ?, ?)", - Op: "db.sql.exec", - Tags: nil, - Origin: "manual", - Sampled: sentry.SampledTrue, - Status: sentry.SpanStatusInternalError, - }, - }, - } - - spansCh := make(chan []*sentry.Span, len(tests)) + tx, err := conn.BeginTx(ctx, nil) + if err != nil { + t.Fatal(err) + } + defer func() { + _ = tx.Rollback() + }() - sentryClient, err := sentry.NewClient(sentry.ClientOptions{ - EnableTracing: true, - TracesSampleRate: 1.0, - BeforeSendTransaction: func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event { - spansCh <- event.Spans - return event - }, - }) - if err != nil { - t.Fatal(err) - } + var name string + err = tx.QueryRowContext(ctx, "SELECT name FROM query_test WHERE id = ?", 1).Scan(&name) + if err != nil { + _ = tx.Rollback() + _ = conn.Close() + cancel() + t.Fatal(err) + } - for _, tt := range tests { - hub := sentry.NewHub(sentryClient, sentry.NewScope()) - ctx, cancel := context.WithTimeout(sentry.SetHubOnContext(context.Background(), hub), 10*time.Second) - span := sentry.StartSpan(ctx, "fake_parent", sentry.WithTransactionName("Fake Parent")) - ctx = span.Context() + _, err = tx.ExecContext(ctx, "INSERT INTO exec_test (id, name) VALUES (?, ?)", 5, "Catherine") + if err != nil { + _ = tx.Rollback() + _ = conn.Close() + cancel() + t.Fatal(err) + } - conn, err := db.Conn(ctx) - if err != nil { - cancel() - t.Fatal(err) - } + err = tx.Commit() + if err != nil { + _ = conn.Close() + cancel() + t.Fatal(err) + } - tx, err := conn.BeginTx(ctx, nil) - if err != nil { - cancel() - t.Fatal(err) - } + _ = conn.Close() - _, err = tx.ExecContext(ctx, tt.Query, tt.Parameters...) - if err != nil && !tt.WantError { - _ = conn.Close() - cancel() - t.Fatal(err) - } + span.Finish() - err = tx.Commit() - if err != nil && !tt.WantError { - _ = conn.Close() - cancel() - t.Fatal(err) - } + cancel() - _ = tx.Rollback() + if ok := sentryClient.Flush(testutils.FlushTimeout()); !ok { + t.Fatal("sentry.Flush timed out") + } + close(spansCh) - _ = conn.Close() + var got []*sentry.Span + for e := range spansCh { + got = append(got, e...) + } - span.Finish() - cancel() - } + want := []*sentry.Span{ + { + Data: map[string]interface{}{ + "db.system": sentrysql.DatabaseSystem("sqlite"), + "db.name": "memory", + "server.address": "localhost", + "server.port": "5432", + "db.operation": "SELECT", + }, + Description: "SELECT name FROM query_test WHERE id = ?", + Op: "db.sql.query", + Tags: nil, + Origin: "manual", + Sampled: sentry.SampledTrue, + Status: sentry.SpanStatusOK, + }, + { + Data: map[string]interface{}{ + "db.system": sentrysql.DatabaseSystem("sqlite"), + "db.name": "memory", + "server.address": "localhost", + "server.port": "5432", + "db.operation": "INSERT", + }, + Description: "INSERT INTO exec_test (id, name) VALUES (?, ?)", + Op: "db.sql.exec", + Tags: nil, + Origin: "manual", + Sampled: sentry.SampledTrue, + Status: sentry.SpanStatusOK, + }, + } - if ok := sentryClient.Flush(testutils.FlushTimeout()); !ok { - t.Fatal("sentry.Flush timed out") - } - close(spansCh) + if diff := cmp.Diff(want, got, optstrans); diff != "" { + t.Errorf("Span mismatch (-want +got):\n%s", diff) + } + }) +} - var got [][]*sentry.Span - for e := range spansCh { - got = append(got, e) - } +//nolint:dupl +func TestNewSentrySQL_PrepareContext(t *testing.T) { + db, err := sql.Open("sentrysql-sqlite", ":memory:") + if err != nil { + t.Fatalf("opening sqlite: %v", err) + } + db.SetMaxOpenConns(1) + defer db.Close() - for i, tt := range tests { - var foundMatch = false - gotSpans := got[i] - - var diffs []string - for _, gotSpan := range gotSpans { - if diff := cmp.Diff(tt.WantSpan, gotSpan, optstrans); diff != "" { - diffs = append(diffs, diff) - } else { - foundMatch = true - break - } - } + setupQueries := []string{ + "CREATE TABLE exec_test (id INT, name TEXT)", + "CREATE TABLE query_test (id INT, name TEXT, age INT, created_at TEXT)", + "INSERT INTO query_test (id, name, age, created_at) VALUES (1, 'John', 30, '2023-01-01')", + "INSERT INTO query_test (id, name, age, created_at) VALUES (2, 'Jane', 25, '2023-01-02')", + "INSERT INTO query_test (id, name, age, created_at) VALUES (3, 'Bob', 35, '2023-01-03')", + } - if !foundMatch { - t.Errorf("Span mismatch (-want +got):\n%s", strings.Join(diffs, "\n")) - } - } - }) + setupCtx, cancelCtx := context.WithTimeout(context.Background(), 30*time.Second) + defer cancelCtx() - t.Run("Multiple Queries", func(t *testing.T) { - spansCh := make(chan []*sentry.Span, 2) + for _, query := range setupQueries { + _, err = db.ExecContext(setupCtx, query) + if err != nil { + t.Fatalf("initializing table on sqlite: %v", err) + } + } - sentryClient, err := sentry.NewClient(sentry.ClientOptions{ - EnableTracing: true, - TracesSampleRate: 1.0, - BeforeSendTransaction: func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event { - spansCh <- event.Spans - return event + t.Run("Exec", func(t *testing.T) { + tests := []struct { + Query string + Parameters []interface{} + WantSpan *sentry.Span + WantError bool + }{ + { + Query: "INSERT INTO exec_test (id, name) VALUES (?, ?)", + Parameters: []interface{}{3, "Sarah"}, + WantSpan: &sentry.Span{ + Data: map[string]interface{}{ + "db.system": sentrysql.DatabaseSystem("sqlite"), + "db.name": "memory", + "server.address": "localhost", + "server.port": "5432", + "db.operation": "INSERT", + }, + Description: "INSERT INTO exec_test (id, name) VALUES (?, ?)", + Op: "db.sql.exec", + Tags: nil, + Origin: "manual", + Sampled: sentry.SampledTrue, + Status: sentry.SpanStatusOK, }, - }) - if err != nil { - t.Fatal(err) - } + }, + { + Query: "INSERT INTO exec_test (id, name) VALUES (?, ?, ?, ?)", + Parameters: []interface{}{4, "John", "Doe", "John Doe"}, + WantError: true, + WantSpan: &sentry.Span{ + Data: map[string]interface{}{ + "db.system": sentrysql.DatabaseSystem("sqlite"), + "db.name": "memory", + "server.address": "localhost", + "server.port": "5432", + "db.operation": "INSERT", + }, + Description: "INSERT INTO exec_test (id, name) VALUES (?, ?, ?, ?)", + Op: "db.sql.exec", + Tags: nil, + Origin: "manual", + Sampled: sentry.SampledTrue, + Status: sentry.SpanStatusInternalError, + }, + }, + } + + spansCh := make(chan []*sentry.Span, len(tests)) + + sentryClient, err := sentry.NewClient(sentry.ClientOptions{ + EnableTracing: true, + TracesSampleRate: 1.0, + BeforeSendTransaction: func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event { + spansCh <- event.Spans + return event + }, + }) + if err != nil { + t.Fatal(err) + } + for _, tt := range tests { hub := sentry.NewHub(sentryClient, sentry.NewScope()) ctx, cancel := context.WithTimeout(sentry.SetHubOnContext(context.Background(), hub), 10*time.Second) - defer cancel() span := sentry.StartSpan(ctx, "fake_parent", sentry.WithTransactionName("Fake Parent")) ctx = span.Context() - conn, err := db.Conn(ctx) - if err != nil { - t.Fatal(err) - } - - tx, err := conn.BeginTx(ctx, nil) - if err != nil { - t.Fatal(err) - } - defer tx.Rollback() - - var name string - err = tx.QueryRowContext(ctx, "SELECT name FROM query_test WHERE id = ?", 1).Scan(&name) + stmt, err := db.PrepareContext(ctx, tt.Query) if err != nil { - tx.Rollback() - conn.Close() cancel() t.Fatal(err) } - _, err = tx.ExecContext(ctx, "INSERT INTO exec_test (id, name) VALUES (?, ?)", 5, "Catherine") - if err != nil { - tx.Rollback() - conn.Close() + _, err = stmt.Exec(tt.Parameters...) + if err != nil && !tt.WantError { cancel() t.Fatal(err) } - err = tx.Commit() - if err != nil { - conn.Close() - cancel() - t.Fatal(err) - } + span.Finish() + cancel() + } - conn.Close() + if ok := sentryClient.Flush(testutils.FlushTimeout()); !ok { + t.Fatal("sentry.Flush timed out") + } + close(spansCh) - span.Finish() + var got [][]*sentry.Span + for e := range spansCh { + got = append(got, e) + } - cancel() + for i, tt := range tests { + var foundMatch = false + gotSpans := got[i] - if ok := sentryClient.Flush(testutils.FlushTimeout()); !ok { - t.Fatal("sentry.Flush timed out") + var diffs []string + for _, gotSpan := range gotSpans { + if diff := cmp.Diff(tt.WantSpan, gotSpan, optstrans); diff != "" { + diffs = append(diffs, diff) + } else { + foundMatch = true + break + } } - close(spansCh) - var got []*sentry.Span - for e := range spansCh { - got = append(got, e...) + if !foundMatch { + t.Errorf("Span mismatch (-want +got):\n%s", strings.Join(diffs, "\n")) } + } + }) - want := []*sentry.Span{ - { + t.Run("Query", func(t *testing.T) { + tests := []struct { + Query string + Parameters []interface{} + WantSpan *sentry.Span + WantError bool + }{ + { + Query: "SELECT * FROM query_test WHERE id = ?", + Parameters: []interface{}{2}, + WantSpan: &sentry.Span{ Data: map[string]interface{}{ "db.system": sentrysql.DatabaseSystem("sqlite"), "db.name": "memory", @@ -1065,33 +1080,103 @@ func TestNewSentrySQL_Integration(t *testing.T) { "server.port": "5432", "db.operation": "SELECT", }, - Description: "SELECT name FROM query_test WHERE id = ?", + Description: "SELECT * FROM query_test WHERE id = ?", Op: "db.sql.query", Tags: nil, Origin: "manual", Sampled: sentry.SampledTrue, Status: sentry.SpanStatusOK, }, - { + }, + { + Query: "SELECT * FROM query_test WHERE id =", + Parameters: []interface{}{1}, + WantError: true, + WantSpan: &sentry.Span{ Data: map[string]interface{}{ "db.system": sentrysql.DatabaseSystem("sqlite"), "db.name": "memory", "server.address": "localhost", "server.port": "5432", - "db.operation": "INSERT", + "db.operation": "SELECT", }, - Description: "INSERT INTO exec_test (id, name) VALUES (?, ?)", - Op: "db.sql.exec", + Description: "SELECT * FROM query_test WHERE id =", + Op: "db.sql.query", Tags: nil, Origin: "manual", Sampled: sentry.SampledTrue, - Status: sentry.SpanStatusOK, + Status: sentry.SpanStatusInternalError, }, + }, + } + + spansCh := make(chan []*sentry.Span, len(tests)) + + sentryClient, err := sentry.NewClient(sentry.ClientOptions{ + EnableTracing: true, + TracesSampleRate: 1.0, + BeforeSendTransaction: func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event { + spansCh <- event.Spans + return event + }, + }) + if err != nil { + t.Fatal(err) + } + + for _, tt := range tests { + hub := sentry.NewHub(sentryClient, sentry.NewScope()) + ctx, cancel := context.WithTimeout(sentry.SetHubOnContext(context.Background(), hub), 10*time.Second) + span := sentry.StartSpan(ctx, "fake_parent", sentry.WithTransactionName("Fake Parent")) + ctx = span.Context() + + stmt, err := db.PrepareContext(ctx, tt.Query) + if err != nil { + cancel() + t.Fatal(err) } - if diff := cmp.Diff(want, got, optstrans); diff != "" { - t.Errorf("Span mismatch (-want +got):\n%s", diff) + rows, err := stmt.Query(tt.Parameters...) + if err != nil && !tt.WantError { + cancel() + t.Fatal(err) } - }) + + if rows != nil { + _ = rows.Close() + } + + span.Finish() + cancel() + } + + if ok := sentryClient.Flush(testutils.FlushTimeout()); !ok { + t.Fatal("sentry.Flush timed out") + } + close(spansCh) + + var got [][]*sentry.Span + for e := range spansCh { + got = append(got, e) + } + + for i, tt := range tests { + var foundMatch = false + gotSpans := got[i] + + var diffs []string + for _, gotSpan := range gotSpans { + if diff := cmp.Diff(tt.WantSpan, gotSpan, optstrans); diff != "" { + diffs = append(diffs, diff) + } else { + foundMatch = true + break + } + } + + if !foundMatch { + t.Errorf("Span mismatch (-want +got):\n%s", strings.Join(diffs, "\n")) + } + } }) } From d9073aac225738d7033672282f93a6938890ead7 Mon Sep 17 00:00:00 2001 From: Reinaldy Rafli Date: Sat, 19 Oct 2024 10:38:06 +0700 Subject: [PATCH 11/26] test: no parent span --- sentrysql/sentrysql_test.go | 147 ++++++++++++++++++++++++++++++++++++ 1 file changed, 147 insertions(+) diff --git a/sentrysql/sentrysql_test.go b/sentrysql/sentrysql_test.go index ac939a9c..d7d36f8d 100644 --- a/sentrysql/sentrysql_test.go +++ b/sentrysql/sentrysql_test.go @@ -1180,3 +1180,150 @@ func TestNewSentrySQL_PrepareContext(t *testing.T) { } }) } + +//nolint:dupl +func TestNewSentrySQL_NoParentSpan(t *testing.T) { + db, err := sql.Open("sentrysql-sqlite", ":memory:") + if err != nil { + t.Fatalf("opening sqlite: %v", err) + } + db.SetMaxOpenConns(1) + defer db.Close() + + setupQueries := []string{ + "CREATE TABLE exec_test (id INT, name TEXT)", + "CREATE TABLE query_test (id INT, name TEXT, age INT, created_at TEXT)", + "INSERT INTO query_test (id, name, age, created_at) VALUES (1, 'John', 30, '2023-01-01')", + "INSERT INTO query_test (id, name, age, created_at) VALUES (2, 'Jane', 25, '2023-01-02')", + "INSERT INTO query_test (id, name, age, created_at) VALUES (3, 'Bob', 35, '2023-01-03')", + } + + setupCtx, cancelCtx := context.WithTimeout(context.Background(), 30*time.Second) + defer cancelCtx() + + for _, query := range setupQueries { + _, err = db.ExecContext(setupCtx, query) + if err != nil { + t.Fatalf("initializing table on sqlite: %v", err) + } + } + + t.Run("QueryContext", func(t *testing.T) { + tests := []struct { + Query string + Parameters []interface{} + WantSpan *sentry.Span + WantError bool + }{ + { + Query: "SELECT * FROM query_test WHERE id = ?", + Parameters: []interface{}{1}, + WantSpan: nil, + }, + } + + spansCh := make(chan []*sentry.Span, len(tests)) + + sentryClient, err := sentry.NewClient(sentry.ClientOptions{ + EnableTracing: true, + TracesSampleRate: 1.0, + BeforeSendTransaction: func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event { + spansCh <- event.Spans + return event + }, + }) + if err != nil { + t.Fatal(err) + } + + for _, tt := range tests { + hub := sentry.NewHub(sentryClient, sentry.NewScope()) + ctx, cancel := context.WithTimeout(sentry.SetHubOnContext(context.Background(), hub), 10*time.Second) + + rows, err := db.QueryContext(ctx, tt.Query, tt.Parameters...) + if err != nil && !tt.WantError { + cancel() + t.Fatal(err) + } + + if rows != nil { + _ = rows.Close() + } + + cancel() + } + + if ok := sentryClient.Flush(testutils.FlushTimeout()); !ok { + t.Fatal("sentry.Flush timed out") + } + close(spansCh) + + var got [][]*sentry.Span + for e := range spansCh { + got = append(got, e) + } + + // `got` should be empty + if len(got) != 0 { + t.Errorf("got %d spans, want 0", len(got)) + } + }) + + t.Run("ExecContext", func(t *testing.T) { + tests := []struct { + Query string + Parameters []interface{} + WantSpan *sentry.Span + WantError bool + }{ + { + Query: "INSERT INTO exec_test (id, name) VALUES (?, ?)", + Parameters: []interface{}{1, "John"}, + WantSpan: nil, + }, + } + + spansCh := make(chan []*sentry.Span, len(tests)) + + sentryClient, err := sentry.NewClient(sentry.ClientOptions{ + EnableTracing: true, + TracesSampleRate: 1.0, + BeforeSendTransaction: func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event { + spansCh <- event.Spans + return event + }, + }) + if err != nil { + t.Fatal(err) + } + + for _, tt := range tests { + hub := sentry.NewHub(sentryClient, sentry.NewScope()) + ctx, cancel := context.WithTimeout(sentry.SetHubOnContext(context.Background(), hub), 10*time.Second) + + _, err := db.ExecContext(ctx, tt.Query, tt.Parameters...) + if err != nil && !tt.WantError { + cancel() + t.Fatal(err) + } + + cancel() + } + + if ok := sentryClient.Flush(testutils.FlushTimeout()); !ok { + t.Fatal("sentry.Flush timed out") + } + close(spansCh) + + var got [][]*sentry.Span + for e := range spansCh { + got = append(got, e) + } + + // `got` should be empty + if len(got) != 0 { + t.Errorf("got %d spans, want 0", len(got)) + } + }) + +} From 05d699bc5a7a0821d4b5348cec11d9dfa1298148 Mon Sep 17 00:00:00 2001 From: Reinaldy Rafli Date: Sat, 19 Oct 2024 10:42:10 +0700 Subject: [PATCH 12/26] test: db.Driver --- sentrysql/sentrysql_test.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/sentrysql/sentrysql_test.go b/sentrysql/sentrysql_test.go index d7d36f8d..642f64e7 100644 --- a/sentrysql/sentrysql_test.go +++ b/sentrysql/sentrysql_test.go @@ -381,6 +381,14 @@ func TestNewSentrySQL_Integration(t *testing.T) { t.Fatal(err) } }) + + t.Run("Driver", func(t *testing.T) { + // Just checking if this works and doesn't panic + driver := db.Driver() + if driver == nil { + t.Fatal("driver is nil") + } + }) } //nolint:dupl From 0fc642eb9a46355439d2e897dbcc656ee16949a3 Mon Sep 17 00:00:00 2001 From: Reinaldy Rafli Date: Sat, 19 Oct 2024 10:43:32 +0700 Subject: [PATCH 13/26] chore: trailing newline --- sentrysql/sentrysql_test.go | 1 - 1 file changed, 1 deletion(-) diff --git a/sentrysql/sentrysql_test.go b/sentrysql/sentrysql_test.go index 642f64e7..d58dfe16 100644 --- a/sentrysql/sentrysql_test.go +++ b/sentrysql/sentrysql_test.go @@ -1333,5 +1333,4 @@ func TestNewSentrySQL_NoParentSpan(t *testing.T) { t.Errorf("got %d spans, want 0", len(got)) } }) - } From 7b5fbb5bac722b49324e00229c42906cb17bd496 Mon Sep 17 00:00:00 2001 From: Reinaldy Rafli Date: Sat, 19 Oct 2024 12:09:48 +0700 Subject: [PATCH 14/26] test: using mysql server for connector --- go.mod | 27 ++- go.sum | 378 +++++++++++++++++++++++++++++ sentrysql/example_test.go | 110 +++++++++ sentrysql/sentrysql_go1.22_test.go | 371 ++++++++++++++++++++++++++++ sentrysql/sentrysql_test.go | 182 ++++++++++---- 5 files changed, 1016 insertions(+), 52 deletions(-) create mode 100644 sentrysql/example_test.go create mode 100644 sentrysql/sentrysql_go1.22_test.go diff --git a/go.mod b/go.mod index de082778..7b91051a 100644 --- a/go.mod +++ b/go.mod @@ -1,11 +1,16 @@ module github.com/getsentry/sentry-go -go 1.18 +go 1.22 + +toolchain go1.23.2 require ( + github.com/dolthub/go-mysql-server v0.18.1 + github.com/dolthub/vitess v0.0.0-20240404214255-c5a87fc7b325 github.com/gin-gonic/gin v1.8.1 github.com/glebarez/go-sqlite v1.21.1 github.com/go-errors/errors v1.4.2 + github.com/go-sql-driver/mysql v1.7.2-0.20231213112541-0004702b931d github.com/gofiber/fiber/v2 v2.52.2 github.com/google/go-cmp v0.5.9 github.com/kataras/iris/v12 v12.2.0 @@ -22,6 +27,7 @@ require ( ) require ( + filippo.io/edwards25519 v1.1.0 // indirect github.com/BurntSushi/toml v1.2.1 // indirect github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53 // indirect github.com/CloudyKit/jet/v6 v6.2.0 // indirect @@ -30,21 +36,28 @@ require ( github.com/ajg/form v1.5.1 // indirect github.com/andybalholm/brotli v1.1.0 // indirect github.com/aymerick/douceur v0.2.0 // indirect + github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dolthub/flatbuffers/v23 v23.3.3-dh.2 // indirect + github.com/dolthub/go-icu-regex v0.0.0-20230524105445-af7e7991c97e // indirect + github.com/dolthub/jsonpath v0.0.2-0.20240227200619-19675ab05c71 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385 // indirect github.com/fatih/structs v1.1.0 // indirect github.com/flosch/pongo2/v4 v4.0.2 // indirect github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-kit/kit v0.10.0 // indirect github.com/go-playground/locales v0.14.0 // indirect github.com/go-playground/universal-translator v0.18.0 // indirect github.com/go-playground/validator/v10 v10.11.1 // indirect github.com/goccy/go-json v0.9.11 // indirect + github.com/golang/protobuf v1.5.2 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/google/go-querystring v1.1.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gorilla/css v1.0.0 // indirect github.com/gorilla/websocket v1.5.0 // indirect + github.com/hashicorp/golang-lru v0.5.4 // indirect github.com/imkira/go-interpol v1.1.0 // indirect github.com/iris-contrib/httpexpect/v2 v2.12.1 // indirect github.com/iris-contrib/schema v0.0.6 // indirect @@ -58,6 +71,7 @@ require ( github.com/klauspost/compress v1.17.7 // indirect github.com/labstack/gommon v0.4.0 // indirect github.com/leodido/go-urn v1.2.1 // indirect + github.com/lestrrat-go/strftime v1.0.4 // indirect github.com/mailgun/raymond/v2 v2.0.48 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect @@ -76,8 +90,10 @@ require ( github.com/sanity-io/litter v1.5.5 // indirect github.com/schollz/closestmatch v2.1.0+incompatible // indirect github.com/sergi/go-diff v1.0.0 // indirect + github.com/shopspring/decimal v1.3.1 // indirect github.com/tdewolff/minify/v2 v2.12.4 // indirect github.com/tdewolff/parse/v2 v2.6.4 // indirect + github.com/tetratelabs/wazero v1.1.0 // indirect github.com/ugorji/go/codec v1.2.7 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect @@ -91,11 +107,20 @@ require ( github.com/yosssi/ace v0.0.5 // indirect github.com/yudai/gojsondiff v1.0.0 // indirect github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect + go.opentelemetry.io/otel v1.7.0 // indirect + go.opentelemetry.io/otel/trace v1.7.0 // indirect golang.org/x/crypto v0.21.0 // indirect + golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 // indirect + golang.org/x/mod v0.12.0 // indirect golang.org/x/net v0.23.0 // indirect + golang.org/x/sync v0.3.0 // indirect golang.org/x/time v0.3.0 // indirect + golang.org/x/tools v0.13.0 // indirect + google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect + google.golang.org/grpc v1.53.0 // indirect google.golang.org/protobuf v1.33.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect + gopkg.in/src-d/go-errors.v1 v1.0.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect modernc.org/libc v1.22.3 // indirect diff --git a/go.sum b/go.sum index d8765842..c1ab23b4 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,8 @@ +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= +filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53 h1:sR+/8Yb4slttB4vD+b9btVEnWgL3Q00OBTzVT8B9C0c= @@ -8,33 +13,95 @@ github.com/Joker/hpp v1.0.0 h1:65+iuJYdRXv/XyN62C1uEmmOx3432rNG/rKlX6V7Kkc= github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY= github.com/Joker/jade v1.1.3 h1:Qbeh12Vq6BxURXT1qZBRHsDxeURB8ztcL6f3EXSGeHk= github.com/Joker/jade v1.1.3/go.mod h1:T+2WLyt7VH6Lp0TRxQrUYEs64nRc83wkMQrfeIQKduM= +github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/Shopify/goreferrer v0.0.0-20220729165902-8cddb4f5de06 h1:KkH3I3sJuOLP3TjA/dfr4NAY8bghDwnXiU7cTKxQqo0= github.com/Shopify/goreferrer v0.0.0-20220729165902-8cddb4f5de06/go.mod h1:7erjKLwalezA0k99cWs5L11HWOAPNjdUZ6RxH1BXbbM= +github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= +github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= +github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE= +github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= +github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU= github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= +github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= +github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= +github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= +github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= +github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= +github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= +github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= +github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= +github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= +github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= +github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= +github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= +github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= +github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= +github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= +github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927/go.mod h1:h/aW8ynjgkuj+NQRlZcDbAbM1ORAbXjXX77sX7T289U= +github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= +github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= +github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= +github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= +github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/djherbis/atime v1.1.0/go.mod h1:28OF6Y8s3NQWwacXc5eZTsEsiMzp7LF8MbXE+XJPdBE= +github.com/dolthub/flatbuffers/v23 v23.3.3-dh.2 h1:u3PMzfF8RkKd3lB9pZ2bfn0qEG+1Gms9599cr0REMww= +github.com/dolthub/flatbuffers/v23 v23.3.3-dh.2/go.mod h1:mIEZOHnFx4ZMQeawhw9rhsj+0zwQj7adVsnBX7t+eKY= +github.com/dolthub/go-icu-regex v0.0.0-20230524105445-af7e7991c97e h1:kPsT4a47cw1+y/N5SSCkma7FhAPw7KeGmD6c9PBZW9Y= +github.com/dolthub/go-icu-regex v0.0.0-20230524105445-af7e7991c97e/go.mod h1:KPUcpx070QOfJK1gNe0zx4pA5sicIK1GMikIGLKC168= +github.com/dolthub/go-mysql-server v0.18.1 h1:T+mTBfLrZPnOKvVx3iRx66f0oW+0saOnPa+O1OKUklQ= +github.com/dolthub/go-mysql-server v0.18.1/go.mod h1:8zjK76NDWRel1CFdg+DDzy/D5tdOeFOYKBcqf7IB+aA= +github.com/dolthub/jsonpath v0.0.2-0.20240227200619-19675ab05c71 h1:bMGS25NWAGTEtT5tOBsCuCrlYnLRKpbJVJkDbrTRhwQ= +github.com/dolthub/jsonpath v0.0.2-0.20240227200619-19675ab05c71/go.mod h1:2/2zjLQ/JOOSbbSboojeg+cAwcRV0fDLzIiWch/lhqI= +github.com/dolthub/vitess v0.0.0-20240404214255-c5a87fc7b325 h1:MYUzL2faXlBlG+EEBf+55e5RE/9k8O39MvPXGRAhjJQ= +github.com/dolthub/vitess v0.0.0-20240404214255-c5a87fc7b325/go.mod h1:Xy89nzEyIwlMCiFWOJPmlnORpDFz5wFgEdYGfUwbIQ0= +github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= +github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= +github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= +github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385 h1:clC1lXBpe2kTj2VHdaIu9ajZQe4kcEY9j0NsnDDBZ3o= github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM= +github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/flosch/pongo2/v4 v4.0.2 h1:gv+5Pe3vaSVmiJvh/BZa82b7/00YUGm0PIyVVLop0Hw= github.com/flosch/pongo2/v4 v4.0.2/go.mod h1:B5ObFANs/36VwxxlgKpdchIJHMvHB562PW+BWPhwZD8= +github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= +github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= +github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.8.1 h1:4+fr/el88TOO3ewCmQr8cx/CtZ/umlIRIs5M4NTNjf8= @@ -43,6 +110,16 @@ github.com/glebarez/go-sqlite v1.21.1 h1:7MZyUPh2XTrHS7xNEHQbrhfMZuPSzhkm2A1qgg0 github.com/glebarez/go-sqlite v1.21.1/go.mod h1:ISs8MF6yk5cL4n/43rSOmVMGJJjHYr7L2MbZZ5Q4E2E= 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/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-kit/kit v0.10.0 h1:dXFJfIHVvUcpSgDOV+Ne6t7jXri8Tfv2uOLHUZ2XNuo= +github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= +github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= +github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= +github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= @@ -51,35 +128,105 @@ github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/j github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ= github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= +github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= +github.com/go-sql-driver/mysql v1.7.2-0.20231213112541-0004702b931d h1:QQP1nE4qh5aHTGvI1LgOFxZYVxYoGeMfbNHikogPyoA= +github.com/go-sql-driver/mysql v1.7.2-0.20231213112541-0004702b931d/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk= github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/gofiber/fiber/v2 v2.52.2 h1:b0rYH6b06Df+4NyrbdptQL8ifuxw/Tf2DgfkZkDaxEo= github.com/gofiber/fiber/v2 v2.52.2/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ= +github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= +github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= +github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= +github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= 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/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= +github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= +github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= +github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= +github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= +github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= +github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= +github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= +github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= +github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= +github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= +github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= +github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= +github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= +github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= +github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= +github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= +github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= +github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= +github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= +github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/imkira/go-interpol v1.1.0 h1:KIiKr0VSG2CUW1hl1jpiyuzuJeKUUpC8iM1AIE7N1Vk= github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= github.com/iris-contrib/httpexpect/v2 v2.12.1 h1:3cTZSyBBen/kfjCtgNFoUKi1u0FVXNaAjyRJOo6AVS4= github.com/iris-contrib/httpexpect/v2 v2.12.1/go.mod h1:7+RB6W5oNClX7PTwJgJnsQP3ZuUUYB3u61KCqeSgZ88= github.com/iris-contrib/schema v0.0.6 h1:CPSBLyx2e91H2yJzPuhGuifVRnZBBJ3pCOMbOvPZaTw= github.com/iris-contrib/schema v0.0.6/go.mod h1:iYszG0IOsuIsfzjymw1kMzTL8YQcCWlm65f3wX8J5iA= +github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= +github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= +github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kataras/blocks v0.0.7 h1:cF3RDY/vxnSRezc7vLFlQFTYXG/yAr1o7WImJuZbzC4= github.com/kataras/blocks v0.0.7/go.mod h1:UJIU97CluDo0f+zEjbnbkeMRlvYORtmc1304EeyXf4I= github.com/kataras/golog v0.1.8 h1:isP8th4PJH2SrbkciKnylaND9xoTtfxv++NB+DF0l9g= @@ -92,8 +239,12 @@ github.com/kataras/sitemap v0.0.6 h1:w71CRMMKYMJh6LR2wTgnk5hSgjVNB9KL60n5e2KHvLY github.com/kataras/sitemap v0.0.6/go.mod h1:dW4dOCNs896OR1HmG+dMLdT7JjDk7mYBzoIRwuj5jA4= github.com/kataras/tunnel v0.0.4 h1:sCAqWuJV7nPzGrlb0os3j49lk2JhILT0rID38NHNLpA= github.com/kataras/tunnel v0.0.4/go.mod h1:9FkU4LaeifdMWqZu7o20ojmW4B7hdhv2CMLwfnHGpYw= +github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg= github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= @@ -108,68 +259,163 @@ github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8 github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= +github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc h1:RKf14vYWi2ttpEmkA4aQ3j4u9dStX2t4M8UM6qqNsG8= +github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc/go.mod h1:kopuH9ugFRkIXf3YoqHKyrJ9YfUFsckUU9S7B+XP+is= +github.com/lestrrat-go/strftime v1.0.4 h1:T1Rb9EPkAhgxKqbcMIPguPq8glqXTA1koF8n9BHElA8= +github.com/lestrrat-go/strftime v1.0.4/go.mod h1:E1nN3pCbtMSu1yjSVeyuRFVm/U0xoR76fd03sz+Qz4g= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= +github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= +github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/mailgun/raymond/v2 v2.0.48 h1:5dmlB680ZkFG2RN/0lvTAghrSxIESeu9/2aeDqACtjw= github.com/mailgun/raymond/v2 v2.0.48/go.mod h1:lsgvL50kgt1ylcFJYZiULi5fjPBkkhNfj4KA0W54Z18= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/matryer/try v0.0.0-20161228173917-9ac251b645a2/go.mod h1:0KeJpeMD6o+O4hW7qJOT7vyQPKrWmj26uf5wMc/IiIs= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/microcosm-cc/bluemonday v1.0.23 h1:SMZe2IGa0NuHvnVNAZ+6B38gsTbi5e4sViiWJyDDqFY= github.com/microcosm-cc/bluemonday v1.0.23/go.mod h1:mN70sk7UkkF8TUr2IGBpNN0jAgStuPzlK76QuruE/z4= +github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= +github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= +github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= +github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= +github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= +github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= +github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= +github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= +github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= +github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= +github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= +github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= +github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY= github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc= +github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= +github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= +github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.27.1 h1:rfztXRbg6nv/5f+Raen9RcGoSecHIFgBBLQK3Wdj754= +github.com/onsi/gomega v1.27.1/go.mod h1:aHX5xOykVYzWOV4WqQy0sy8BQptgukenXpCXfadcIAw= +github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= +github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= +github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= +github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= +github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= +github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= +github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= +github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= +github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= +github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg= github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas= +github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= +github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= +github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= 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/diff v0.0.0-20200914180035-5b29258ca4f7/go.mod h1:zO8QMzTeZd5cpnIkz/Gn6iK0jDfGicM1nynOkkPIl28= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 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/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= +github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= +github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= +github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= +github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= +github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= +github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= +github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= +github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= +github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= +github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= +github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= +github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= 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/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= +github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= github.com/sanity-io/litter v1.5.5 h1:iE+sBxPBzoK6uaEP5Lt3fHNgpKcHXc/A2HGETy0uJQo= github.com/sanity-io/litter v1.5.5/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U= github.com/schollz/closestmatch v2.1.0+incompatible h1:Uel2GXEpJqOWBrlyI+oY9LTiyyjYS17cCYRqP13/SHk= github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g= +github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= +github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= +github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= +github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= +github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= +github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= +github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= @@ -189,9 +435,14 @@ github.com/tdewolff/parse/v2 v2.6.4 h1:KCkDvNUMof10e3QExio9OPZJT8SbdKojLBumw8YZy github.com/tdewolff/parse/v2 v2.6.4/go.mod h1:woz0cgbLwFdtbjJu8PIKxhW05KplTFQkOdX78o+Jgrs= github.com/tdewolff/test v1.0.7 h1:8Vs0142DmPFW/bQeHRP3MV19m1gvndjUb1sn8yy74LM= github.com/tdewolff/test v1.0.7/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE= +github.com/tetratelabs/wazero v1.1.0 h1:EByoAhC+QcYpwSZJSs/aV0uokxPwBgKxfiokSUwAknQ= +github.com/tetratelabs/wazero v1.1.0/go.mod h1:wYx2gNRg8/WihJfSDxA1TIL8H+GkfLYm+bIfbblu9VQ= +github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= +github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= +github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/negroni/v3 v3.1.1 h1:6MS4nG9Jk/UuCACaUlNXCbiKa0ywF9LXz5dGu09v8hw= github.com/urfave/negroni/v3 v3.1.1/go.mod h1:jWvnX03kcSjDBl/ShB0iHvx5uOs7mAzZXW+JvJ5XYAs= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= @@ -213,6 +464,7 @@ github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHo github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= +github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0 h1:6fRhSjgLCkTD3JnJxvaJ4Sj+TYblw757bqYgZaOq5ZY= github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI= github.com/yosssi/ace v0.0.5 h1:tUkIP/BLdKqrlrPwcmH0shwEEhTRHoGnc1wFIWmaBUA= @@ -222,31 +474,100 @@ github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FB github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M= github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= github.com/yudai/pp v2.0.1+incompatible h1:Q4//iY4pNF6yPLZIigmvcl7k/bPgrcTPIFIcmawg5bI= +github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= +go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= +go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opentelemetry.io/otel v1.7.0 h1:Z2lA3Tdch0iDcrhJXDIlC94XE+bxok1F9B+4Lz/lGsM= +go.opentelemetry.io/otel v1.7.0/go.mod h1:5BdUoMIz5WEs0vt0CUEMtSSaTSHBBVwrhnz7+nrD5xk= +go.opentelemetry.io/otel/trace v1.7.0 h1:O37Iogk1lEkMRXewVtZ1BBTVn5JEp8GrJvP92bJqC6o= +go.opentelemetry.io/otel/trace v1.7.0/go.mod h1:fzLSB9nqR2eXzxPXb2JW9IKE+ScyXA48yyE4TNvoHqU= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 h1:MGwJjxBy0HJshjDNfLsYO8xppfqWlA5ZT9OhtUUhTNw= +golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= +golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= +golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= +golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= +golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -265,33 +586,84 @@ 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/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20201211185031-d93e913c1a58/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= +golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= +golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f h1:BWUVssLB0HVOSY78gIdvk1dTVYtT1y8SBWtPYuTJ/6w= +google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= +google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.53.0 h1:LAv2ds7cmFV/XTS3XG1NneeENYrXGmorPxsBbptIjNc= +google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= +gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/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/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= +gopkg.in/src-d/go-errors.v1 v1.0.0 h1:cooGdZnCjYbeS1zb1s6pVAAimTdKceRrpn7aKOnNIfc= +gopkg.in/src-d/go-errors.v1 v1.0.0/go.mod h1:q1cBlomlw2FnDBDNGlnh6X0jPihy+QxZfMMNxPCbdYg= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= +gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= +gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= @@ -299,6 +671,10 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= modernc.org/libc v1.22.3 h1:D/g6O5ftAfavceqlLOFwaZuA5KYafKwmr30A6iSqoyY= modernc.org/libc v1.22.3/go.mod h1:MQrloYP209xa2zHome2a8HLiLm6k0UT8CoHpV74tOFw= modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ= @@ -309,3 +685,5 @@ modernc.org/sqlite v1.21.1 h1:GyDFqNnESLOhwwDRaHGdp2jKLDzpyT/rNLglX3ZkMSU= modernc.org/sqlite v1.21.1/go.mod h1:XwQ0wZPIh1iKb5mkvCJ3szzbhk+tykC8ZWqTRTgYRwI= moul.io/http2curl/v2 v2.3.0 h1:9r3JfDzWPcbIklMOs2TnIFzDYvfAZvjeavG6EzP7jYs= moul.io/http2curl/v2 v2.3.0/go.mod h1:RW4hyBjTWSYDOxapodpNEtX0g5Eb16sxklBqmd2RHcE= +sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= +sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= diff --git a/sentrysql/example_test.go b/sentrysql/example_test.go new file mode 100644 index 00000000..4a0907af --- /dev/null +++ b/sentrysql/example_test.go @@ -0,0 +1,110 @@ +package sentrysql_test + +import ( + "database/sql" + "fmt" + "net" + + "github.com/getsentry/sentry-go/sentrysql" + sqlite "github.com/glebarez/go-sqlite" + "github.com/go-sql-driver/mysql" + "github.com/lib/pq" +) + +func ExampleNewSentrySQL() { + sql.Register("sentrysql-sqlite", sentrysql.NewSentrySQL( + &sqlite.Driver{}, + sentrysql.WithDatabaseName(":memory:"), + sentrysql.WithDatabaseSystem(sentrysql.DatabaseSystem("sqlite")), + )) + + db, err := sql.Open("sentrysql-sqlite", ":memory:") + if err != nil { + panic(err) + } + defer db.Close() + + _, err = db.Exec("CREATE TABLE test (id INT)") + if err != nil { + panic(err) + } + + _, err = db.Exec("INSERT INTO test (id) VALUES (1)") + if err != nil { + panic(err) + } + + rows, err := db.Query("SELECT * FROM test") + if err != nil { + panic(err) + } + defer rows.Close() + + for rows.Next() { + var id int + err = rows.Scan(&id) + if err != nil { + panic(err) + } + + fmt.Println(id) + } +} + +func ExampleNewSentrySQLConnector_postgres() { + // Create a new PostgreSQL connector that utilizes the `github.com/lib/pq` package. + pqConnector, err := pq.NewConnector("postgres://user:password@localhost:5432/db") + if err != nil { + fmt.Println("creating postgres connector:", err.Error()) + return + } + + // `db` here is an instance of *sql.DB. + db := sql.OpenDB(sentrysql.NewSentrySQLConnector( + pqConnector, + sentrysql.WithDatabaseName("db"), + sentrysql.WithDatabaseSystem(sentrysql.PostgreSQL), + sentrysql.WithServerAddress("localhost", "5432"), + )) + defer func() { + err := db.Close() + if err != nil { + fmt.Println("closing postgres connection:", err.Error()) + } + }() + + // Use the db connection as usual. +} + +func ExampleNewSentrySQLConnector_mysql() { + // Create a new MySQL connector that utilizes the `github.com/go-sql-driver/mysql` package. + config, err := mysql.ParseDSN("user:password@tcp(localhost:3306)/test?parseTime=true") + if err != nil { + fmt.Println("parsing mysql dsn:", err.Error()) + return + } + + mysqlHost, mysqlPort, _ := net.SplitHostPort(config.Addr) + + connector, err := mysql.NewConnector(config) + if err != nil { + fmt.Println("creating mysql connector:", err.Error()) + return + } + + // `db` here is an instance of *sql.DB. + db := sql.OpenDB(sentrysql.NewSentrySQLConnector( + connector, + sentrysql.WithDatabaseName(config.DBName), + sentrysql.WithDatabaseSystem(sentrysql.MySQL), + sentrysql.WithServerAddress(mysqlHost, mysqlPort), + )) + defer func() { + err := db.Close() + if err != nil { + fmt.Println("closing mysql connection:", err.Error()) + } + }() + + // Use the db connection as usual. +} diff --git a/sentrysql/sentrysql_go1.22_test.go b/sentrysql/sentrysql_go1.22_test.go new file mode 100644 index 00000000..d8a3b4be --- /dev/null +++ b/sentrysql/sentrysql_go1.22_test.go @@ -0,0 +1,371 @@ +//go:build go1.22 + +package sentrysql_test + +import ( + "context" + "database/sql" + "fmt" + "strings" + "testing" + "time" + + sqle "github.com/dolthub/go-mysql-server" + "github.com/dolthub/go-mysql-server/memory" + "github.com/dolthub/go-mysql-server/server" + sqlq "github.com/dolthub/go-mysql-server/sql" + "github.com/dolthub/go-mysql-server/sql/types" + "github.com/dolthub/vitess/go/vt/proto/query" + "github.com/getsentry/sentry-go" + "github.com/getsentry/sentry-go/internal/testutils" + "github.com/getsentry/sentry-go/sentrysql" + "github.com/go-sql-driver/mysql" + "github.com/google/go-cmp/cmp" +) + +func createTestDatabase() *memory.DbProvider { + db := memory.NewDatabase("test") + db.BaseDatabase.EnablePrimaryKeyIndexes() + + pro := memory.NewDBProvider(db) + session := memory.NewSession(sqlq.NewBaseSession(), pro) + ctx := sqlq.NewContext(context.Background(), sqlq.WithSession(session)) + + tableName := "users" + table := memory.NewTable(db, "users", sqlq.NewPrimaryKeySchema(sqlq.Schema{ + {Name: "name", Type: types.Text, Nullable: false, Source: tableName, PrimaryKey: false}, + {Name: "email", Type: types.Text, Nullable: false, Source: tableName, PrimaryKey: true}, + {Name: "age", Type: types.Int32, Nullable: false, Source: tableName}, + {Name: "created_at", Type: types.MustCreateDatetimeType(query.Type_DATETIME, 6), Nullable: false, Source: tableName}, + }), db.GetForeignKeyCollection()) + db.AddTable(tableName, table) + + creationTime := time.Unix(0, 1667304000000001000).UTC() + _ = table.Insert(ctx, sqlq.NewRow("Bob Smith", "bob@smith.com", 30, creationTime)) + _ = table.Insert(ctx, sqlq.NewRow("Jane Doe", "jane@doe.com", 25, creationTime)) + _ = table.Insert(ctx, sqlq.NewRow("John Doe", "john@doe.com", 35, creationTime)) + + return pro +} + +//nolint:dupl +func TestNewSentrySQLConnector_Go122(t *testing.T) { + testDatabase := createTestDatabase() + engine := sqle.NewDefault(testDatabase) + session := memory.NewSession(sqlq.NewBaseSession(), testDatabase) + ctx := sqlq.NewContext(context.Background(), sqlq.WithSession(session)) + ctx.SetCurrentDatabase("test") + config := server.Config{ + Protocol: "tcp", + Address: fmt.Sprintf("%s:%d", "127.0.0.1", 3306), + } + s, err := server.NewServer(config, engine, memory.NewSessionBuilder(testDatabase), nil) + if err != nil { + t.Fatalf("creating new server: %s", err.Error()) + } + + go func() { + if err = s.Start(); err != nil { + panic(err) + } + }() + defer func() { + if err := s.Close(); err != nil { + t.Logf("closing server connection: %s", err.Error()) + } + }() + + connector, err := mysql.NewConnector(&mysql.Config{ + User: "root", + Passwd: "", + Net: "tcp", + Addr: "127.0.0.1:3306", + DBName: "test", + AllowCleartextPasswords: true, + AllowFallbackToPlaintext: true, + AllowNativePasswords: true, + ParseTime: true, + }) + if err != nil { + t.Fatalf("creating mysql connector: %s", err.Error()) + } + + db := sql.OpenDB(sentrysql.NewSentrySQLConnector(connector, sentrysql.WithDatabaseName("test"), sentrysql.WithDatabaseSystem(sentrysql.MySQL), sentrysql.WithServerAddress("127.0.0.1", "3306"))) + defer func() { + _ = db.Close() + }() + + t.Run("QueryContext", func(t *testing.T) { + tests := []struct { + Query string + Parameters []interface{} + WantSpan *sentry.Span + WantError bool + }{ + { + Query: "SELECT * FROM users", + WantSpan: &sentry.Span{ + Data: map[string]interface{}{ + "db.system": sentrysql.MySQL, + "db.name": "test", + "server.address": "127.0.0.1", + "server.port": "3306", + "db.operation": "SELECT", + }, + Description: "SELECT * FROM users", + Op: "db.sql.query", + Tags: nil, + Origin: "manual", + Sampled: sentry.SampledTrue, + Status: sentry.SpanStatusOK, + }, + }, + { + Query: "SELECT FROM query_test", + WantError: true, + WantSpan: &sentry.Span{ + Data: map[string]interface{}{ + "db.system": sentrysql.MySQL, + "db.name": "test", + "server.address": "127.0.0.1", + "server.port": "3306", + "db.operation": "SELECT", + }, + Description: "SELECT FROM query_test", + Op: "db.sql.query", + Tags: nil, + Origin: "manual", + Sampled: sentry.SampledTrue, + Status: sentry.SpanStatusInternalError, + }, + }, + } + + spansCh := make(chan []*sentry.Span, len(tests)) + + sentryClient, err := sentry.NewClient(sentry.ClientOptions{ + EnableTracing: true, + TracesSampleRate: 1.0, + BeforeSendTransaction: func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event { + spansCh <- event.Spans + return event + }, + }) + if err != nil { + t.Fatal(err) + } + + for _, tt := range tests { + hub := sentry.NewHub(sentryClient, sentry.NewScope()) + ctx, cancel := context.WithTimeout(sentry.SetHubOnContext(context.Background(), hub), 10*time.Second) + span := sentry.StartSpan(ctx, "fake_parent", sentry.WithTransactionName("Fake Parent")) + ctx = span.Context() + + rows, err := db.QueryContext(ctx, tt.Query, tt.Parameters...) + if err != nil && !tt.WantError { + cancel() + t.Fatal(err) + } + + if rows != nil { + _ = rows.Close() + } + + span.Finish() + cancel() + } + + if ok := sentryClient.Flush(testutils.FlushTimeout()); !ok { + t.Fatal("sentry.Flush timed out") + } + close(spansCh) + + var got [][]*sentry.Span + for e := range spansCh { + got = append(got, e) + } + + for i, tt := range tests { + var foundMatch = false + gotSpans := got[i] + + var diffs []string + for _, gotSpan := range gotSpans { + if diff := cmp.Diff(tt.WantSpan, gotSpan, optstrans); diff != "" { + diffs = append(diffs, diff) + } else { + foundMatch = true + break + } + } + + if !foundMatch { + t.Errorf("Span mismatch (-want +got):\n%s", strings.Join(diffs, "\n")) + } + } + }) + + t.Run("ExecContext", func(t *testing.T) { + tests := []struct { + Query string + Parameters []interface{} + WantSpan *sentry.Span + WantError bool + }{ + { + Query: "INSERT INTO users (name, email, age, created_at) VALUES (?, ?, ?, ?)", + Parameters: []interface{}{"Michael", "michael@example.com", 17, time.Now()}, + WantSpan: &sentry.Span{ + Data: map[string]interface{}{ + "db.system": sentrysql.MySQL, + "db.name": "test", + "server.address": "127.0.0.1", + "server.port": "3306", + "db.operation": "INSERT", + }, + Description: "INSERT INTO users (name, email, age, created_at) VALUES (?, ?, ?, ?)", + Op: "db.sql.exec", + Tags: nil, + Origin: "manual", + Sampled: sentry.SampledTrue, + Status: sentry.SpanStatusOK, + }, + }, + { + Query: "UPDATE users SET name = ? WHERE email = ?", + Parameters: []interface{}{"Michael Jordan", "michael@example.com"}, + WantSpan: &sentry.Span{ + Data: map[string]interface{}{ + "db.system": sentrysql.MySQL, + "db.name": "test", + "server.address": "127.0.0.1", + "server.port": "3306", + "db.operation": "UPDATE", + }, + Description: "UPDATE users SET name = ? WHERE email = ?", + Op: "db.sql.exec", + Tags: nil, + Origin: "manual", + Sampled: sentry.SampledTrue, + Status: sentry.SpanStatusOK, + }, + }, + { + Query: "DELETE FROM users WHERE name = ?", + Parameters: []interface{}{"Nolan"}, + WantSpan: &sentry.Span{ + Data: map[string]interface{}{ + "db.system": sentrysql.MySQL, + "db.name": "test", + "server.address": "127.0.0.1", + "server.port": "3306", + "db.operation": "DELETE", + }, + Description: "DELETE FROM users WHERE name = ?", + Op: "db.sql.exec", + Tags: nil, + Origin: "manual", + Sampled: sentry.SampledTrue, + Status: sentry.SpanStatusOK, + }, + }, + { + Query: "INSERT INTO users (id, name) VALUES (?, ?, ?, ?)", + Parameters: []interface{}{ + 1, "John", "Doe", 1, + }, + WantError: true, + WantSpan: &sentry.Span{ + Data: map[string]interface{}{ + "db.system": sentrysql.MySQL, + "db.name": "test", + "server.address": "127.0.0.1", + "server.port": "3306", + "db.operation": "INSERT", + }, + Description: "INSERT INTO users (id, name) VALUES (?, ?, ?, ?)", + Op: "db.sql.exec", + Tags: nil, + Origin: "manual", + Sampled: sentry.SampledTrue, + Status: sentry.SpanStatusInternalError, + }, + }, + } + + spansCh := make(chan []*sentry.Span, len(tests)) + + sentryClient, err := sentry.NewClient(sentry.ClientOptions{ + EnableTracing: true, + TracesSampleRate: 1.0, + BeforeSendTransaction: func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event { + spansCh <- event.Spans + return event + }, + }) + if err != nil { + t.Fatal(err) + } + + for _, tt := range tests { + hub := sentry.NewHub(sentryClient, sentry.NewScope()) + ctx, cancel := context.WithTimeout(sentry.SetHubOnContext(context.Background(), hub), 10*time.Second) + span := sentry.StartSpan(ctx, "fake_parent", sentry.WithTransactionName("Fake Parent")) + ctx = span.Context() + + _, err := db.ExecContext(ctx, tt.Query, tt.Parameters...) + if err != nil && !tt.WantError { + cancel() + t.Fatal(err) + } + + span.Finish() + cancel() + } + + if ok := sentryClient.Flush(testutils.FlushTimeout()); !ok { + t.Fatal("sentry.Flush timed out") + } + close(spansCh) + + var got [][]*sentry.Span + for e := range spansCh { + got = append(got, e) + } + + for i, tt := range tests { + var foundMatch = false + gotSpans := got[i] + + var diffs []string + for _, gotSpan := range gotSpans { + if diff := cmp.Diff(tt.WantSpan, gotSpan, optstrans); diff != "" { + diffs = append(diffs, diff) + } else { + foundMatch = true + break + } + } + + if !foundMatch { + t.Errorf("Span mismatch (-want +got):\n%s", strings.Join(diffs, "\n")) + } + } + }) + + t.Run("Ping", func(t *testing.T) { + // Just checking if this works and doesn't panic + err := db.Ping() + if err != nil { + t.Fatal(err) + } + }) + + t.Run("PingContext", func(t *testing.T) { + // Just checking if this works and doesn't panic + err := db.PingContext(context.Background()) + if err != nil { + t.Fatal(err) + } + }) +} diff --git a/sentrysql/sentrysql_test.go b/sentrysql/sentrysql_test.go index d58dfe16..c63536db 100644 --- a/sentrysql/sentrysql_test.go +++ b/sentrysql/sentrysql_test.go @@ -3,7 +3,6 @@ package sentrysql_test import ( "context" "database/sql" - "fmt" "os" "strings" "testing" @@ -15,57 +14,8 @@ import ( sqlite "github.com/glebarez/go-sqlite" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" - "github.com/lib/pq" ) -func ExampleNewSentrySQL() { - sql.Register("sentrysql-sqlite", sentrysql.NewSentrySQL(&sqlite.Driver{}, sentrysql.WithDatabaseName(":memory:"), sentrysql.WithDatabaseSystem(sentrysql.DatabaseSystem("sqlite")))) - - db, err := sql.Open("sentrysql-sqlite", ":memory:") - if err != nil { - panic(err) - } - defer db.Close() - - _, err = db.Exec("CREATE TABLE test (id INT)") - if err != nil { - panic(err) - } - - _, err = db.Exec("INSERT INTO test (id) VALUES (1)") - if err != nil { - panic(err) - } - - rows, err := db.Query("SELECT * FROM test") - if err != nil { - panic(err) - } - defer rows.Close() - - for rows.Next() { - var id int - err = rows.Scan(&id) - if err != nil { - panic(err) - } - - fmt.Println(id) - } -} - -func ExampleNewSentrySQLConnector() { - pqConnector, err := pq.NewConnector("postgres://user:password@localhost:5432/db") - if err != nil { - panic(err) - } - - db := sql.OpenDB(sentrysql.NewSentrySQLConnector(pqConnector, sentrysql.WithDatabaseName("db"), sentrysql.WithDatabaseSystem(sentrysql.PostgreSQL), sentrysql.WithServerAddress("localhost", "5432"))) - defer db.Close() - - // Continue executing PostgreSQL queries -} - var optstrans = cmp.Options{ cmpopts.IgnoreFields( sentry.Span{}, @@ -304,6 +254,24 @@ func TestNewSentrySQL_Integration(t *testing.T) { Status: sentry.SpanStatusInternalError, }, }, + { + Query: "CREATE TABLE temporary_test (id INT, name TEXT)", + WantError: false, + WantSpan: &sentry.Span{ + Data: map[string]interface{}{ + "db.system": sentrysql.DatabaseSystem("sqlite"), + "db.name": "memory", + "server.address": "localhost", + "server.port": "5432", + }, + Description: "CREATE TABLE temporary_test (id INT, name TEXT)", + Op: "db.sql.exec", + Tags: nil, + Origin: "manual", + Sampled: sentry.SampledTrue, + Status: sentry.SpanStatusOK, + }, + }, } spansCh := make(chan []*sentry.Span, len(tests)) @@ -657,7 +625,7 @@ func TestNewSentrySQL_Conn(t *testing.T) { }) } -//nolint:dupl +//nolint:dupl,gocyclo func TestNewSentrySQL_BeginTx(t *testing.T) { db, err := sql.Open("sentrysql-sqlite", ":memory:") if err != nil { @@ -927,6 +895,118 @@ func TestNewSentrySQL_BeginTx(t *testing.T) { t.Errorf("Span mismatch (-want +got):\n%s", diff) } }) + + t.Run("Rollback", func(t *testing.T) { + spansCh := make(chan []*sentry.Span, 2) + + sentryClient, err := sentry.NewClient(sentry.ClientOptions{ + EnableTracing: true, + TracesSampleRate: 1.0, + BeforeSendTransaction: func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event { + spansCh <- event.Spans + return event + }, + }) + if err != nil { + t.Fatal(err) + } + + hub := sentry.NewHub(sentryClient, sentry.NewScope()) + ctx, cancel := context.WithTimeout(sentry.SetHubOnContext(context.Background(), hub), 10*time.Second) + defer cancel() + span := sentry.StartSpan(ctx, "fake_parent", sentry.WithTransactionName("Fake Parent")) + ctx = span.Context() + + conn, err := db.Conn(ctx) + if err != nil { + t.Fatal(err) + } + + tx, err := conn.BeginTx(ctx, nil) + if err != nil { + t.Fatal(err) + } + defer func() { + _ = tx.Rollback() + }() + + var name string + err = tx.QueryRowContext(ctx, "SELECT name FROM query_test WHERE id = ?", 1).Scan(&name) + if err != nil { + _ = tx.Rollback() + _ = conn.Close() + cancel() + t.Fatal(err) + } + + _, err = tx.ExecContext(ctx, "INSERT INTO exec_test (id, name) VALUES (?, ?)", 5, "Catherine") + if err != nil { + _ = tx.Rollback() + _ = conn.Close() + cancel() + t.Fatal(err) + } + + err = tx.Rollback() + if err != nil { + _ = conn.Close() + cancel() + t.Fatal(err) + } + + _ = conn.Close() + + span.Finish() + + cancel() + + if ok := sentryClient.Flush(testutils.FlushTimeout()); !ok { + t.Fatal("sentry.Flush timed out") + } + close(spansCh) + + var got []*sentry.Span + for e := range spansCh { + got = append(got, e...) + } + + want := []*sentry.Span{ + { + Data: map[string]interface{}{ + "db.system": sentrysql.DatabaseSystem("sqlite"), + "db.name": "memory", + "server.address": "localhost", + "server.port": "5432", + "db.operation": "SELECT", + }, + Description: "SELECT name FROM query_test WHERE id = ?", + Op: "db.sql.query", + Tags: nil, + Origin: "manual", + Sampled: sentry.SampledTrue, + Status: sentry.SpanStatusOK, + }, + { + Data: map[string]interface{}{ + "db.system": sentrysql.DatabaseSystem("sqlite"), + "db.name": "memory", + "server.address": "localhost", + "server.port": "5432", + "db.operation": "INSERT", + }, + Description: "INSERT INTO exec_test (id, name) VALUES (?, ?)", + Op: "db.sql.exec", + Tags: nil, + Origin: "manual", + Sampled: sentry.SampledTrue, + Status: sentry.SpanStatusOK, + }, + } + + if diff := cmp.Diff(want, got, optstrans); diff != "" { + t.Errorf("Span mismatch (-want +got):\n%s", diff) + } + }) } //nolint:dupl From 93198bd760a742b75f31a91c1bcea166f32929c5 Mon Sep 17 00:00:00 2001 From: Reinaldy Rafli Date: Sat, 19 Oct 2024 12:16:42 +0700 Subject: [PATCH 15/26] test: remove mysql server, stay on go1.18 --- go.mod | 23 +- go.sum | 374 +---------------------------- sentrysql/sentrysql_go1.22_test.go | 371 ---------------------------- 3 files changed, 3 insertions(+), 765 deletions(-) delete mode 100644 sentrysql/sentrysql_go1.22_test.go diff --git a/go.mod b/go.mod index 7b91051a..1e4af137 100644 --- a/go.mod +++ b/go.mod @@ -5,12 +5,10 @@ go 1.22 toolchain go1.23.2 require ( - github.com/dolthub/go-mysql-server v0.18.1 - github.com/dolthub/vitess v0.0.0-20240404214255-c5a87fc7b325 github.com/gin-gonic/gin v1.8.1 github.com/glebarez/go-sqlite v1.21.1 github.com/go-errors/errors v1.4.2 - github.com/go-sql-driver/mysql v1.7.2-0.20231213112541-0004702b931d + github.com/go-sql-driver/mysql v1.8.1 github.com/gofiber/fiber/v2 v2.52.2 github.com/google/go-cmp v0.5.9 github.com/kataras/iris/v12 v12.2.0 @@ -36,28 +34,21 @@ require ( github.com/ajg/form v1.5.1 // indirect github.com/andybalholm/brotli v1.1.0 // indirect github.com/aymerick/douceur v0.2.0 // indirect - github.com/cespare/xxhash/v2 v2.2.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/dolthub/flatbuffers/v23 v23.3.3-dh.2 // indirect - github.com/dolthub/go-icu-regex v0.0.0-20230524105445-af7e7991c97e // indirect - github.com/dolthub/jsonpath v0.0.2-0.20240227200619-19675ab05c71 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385 // indirect github.com/fatih/structs v1.1.0 // indirect github.com/flosch/pongo2/v4 v4.0.2 // indirect github.com/gin-contrib/sse v0.1.0 // indirect - github.com/go-kit/kit v0.10.0 // indirect github.com/go-playground/locales v0.14.0 // indirect github.com/go-playground/universal-translator v0.18.0 // indirect github.com/go-playground/validator/v10 v10.11.1 // indirect github.com/goccy/go-json v0.9.11 // indirect - github.com/golang/protobuf v1.5.2 // indirect github.com/golang/snappy v0.0.4 // indirect github.com/google/go-querystring v1.1.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gorilla/css v1.0.0 // indirect github.com/gorilla/websocket v1.5.0 // indirect - github.com/hashicorp/golang-lru v0.5.4 // indirect github.com/imkira/go-interpol v1.1.0 // indirect github.com/iris-contrib/httpexpect/v2 v2.12.1 // indirect github.com/iris-contrib/schema v0.0.6 // indirect @@ -71,7 +62,6 @@ require ( github.com/klauspost/compress v1.17.7 // indirect github.com/labstack/gommon v0.4.0 // indirect github.com/leodido/go-urn v1.2.1 // indirect - github.com/lestrrat-go/strftime v1.0.4 // indirect github.com/mailgun/raymond/v2 v2.0.48 // indirect github.com/mailru/easyjson v0.7.7 // indirect github.com/mattn/go-colorable v0.1.13 // indirect @@ -90,10 +80,8 @@ require ( github.com/sanity-io/litter v1.5.5 // indirect github.com/schollz/closestmatch v2.1.0+incompatible // indirect github.com/sergi/go-diff v1.0.0 // indirect - github.com/shopspring/decimal v1.3.1 // indirect github.com/tdewolff/minify/v2 v2.12.4 // indirect github.com/tdewolff/parse/v2 v2.6.4 // indirect - github.com/tetratelabs/wazero v1.1.0 // indirect github.com/ugorji/go/codec v1.2.7 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect @@ -107,20 +95,11 @@ require ( github.com/yosssi/ace v0.0.5 // indirect github.com/yudai/gojsondiff v1.0.0 // indirect github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 // indirect - go.opentelemetry.io/otel v1.7.0 // indirect - go.opentelemetry.io/otel/trace v1.7.0 // indirect golang.org/x/crypto v0.21.0 // indirect - golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 // indirect - golang.org/x/mod v0.12.0 // indirect golang.org/x/net v0.23.0 // indirect - golang.org/x/sync v0.3.0 // indirect golang.org/x/time v0.3.0 // indirect - golang.org/x/tools v0.13.0 // indirect - google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f // indirect - google.golang.org/grpc v1.53.0 // indirect google.golang.org/protobuf v1.33.0 // indirect gopkg.in/ini.v1 v1.67.0 // indirect - gopkg.in/src-d/go-errors.v1 v1.0.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect modernc.org/libc v1.22.3 // indirect diff --git a/go.sum b/go.sum index c1ab23b4..9deef0c4 100644 --- a/go.sum +++ b/go.sum @@ -1,8 +1,5 @@ -cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= -cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53 h1:sR+/8Yb4slttB4vD+b9btVEnWgL3Q00OBTzVT8B9C0c= @@ -13,95 +10,33 @@ github.com/Joker/hpp v1.0.0 h1:65+iuJYdRXv/XyN62C1uEmmOx3432rNG/rKlX6V7Kkc= github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY= github.com/Joker/jade v1.1.3 h1:Qbeh12Vq6BxURXT1qZBRHsDxeURB8ztcL6f3EXSGeHk= github.com/Joker/jade v1.1.3/go.mod h1:T+2WLyt7VH6Lp0TRxQrUYEs64nRc83wkMQrfeIQKduM= -github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= github.com/Shopify/goreferrer v0.0.0-20220729165902-8cddb4f5de06 h1:KkH3I3sJuOLP3TjA/dfr4NAY8bghDwnXiU7cTKxQqo0= github.com/Shopify/goreferrer v0.0.0-20220729165902-8cddb4f5de06/go.mod h1:7erjKLwalezA0k99cWs5L11HWOAPNjdUZ6RxH1BXbbM= -github.com/Shopify/sarama v1.19.0/go.mod h1:FVkBWblsNy7DGZRfXLU0O9RCGt5g3g3yEuWXgklEdEo= -github.com/Shopify/toxiproxy v2.1.4+incompatible/go.mod h1:OXgGpZ6Cli1/URJOF1DMxUHB2q5Ap20/P/eIdh4G0pI= -github.com/VividCortex/gohistogram v1.0.0 h1:6+hBz+qvs0JOrrNhhmR7lFxo5sINxBCGXrdtl/UvroE= -github.com/VividCortex/gohistogram v1.0.0/go.mod h1:Pf5mBqqDxYaXu3hDrrU+w6nw50o/4+TcAqDqk/vUH7g= -github.com/afex/hystrix-go v0.0.0-20180502004556-fa1af6a1f4f5/go.mod h1:SkGFH1ia65gfNATL8TAiHDNxPzPdmEL5uirI2Uyuz6c= github.com/ajg/form v1.5.1 h1:t9c7v8JUKu/XxOGBU0yjNpaMloxGEJhUkqFRq0ibGeU= github.com/ajg/form v1.5.1/go.mod h1:uL1WgH+h2mgNtvBq0339dVnzXdBETtL2LeUXaIv25UY= -github.com/alecthomas/template v0.0.0-20160405071501-a0175ee3bccc/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuyumcjzFXgccqObfd/Ljyb9UuFJ6TxHnclSeseNhc= -github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= -github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0= github.com/andybalholm/brotli v1.1.0 h1:eLKJA0d02Lf0mVpIDgYnqXcUn0GqVmEFny3VuID1U3M= github.com/andybalholm/brotli v1.1.0/go.mod h1:sms7XGricyQI9K10gOSf56VKKWS4oLer58Q+mhRPtnY= -github.com/apache/thrift v0.12.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= -github.com/apache/thrift v0.13.0/go.mod h1:cp2SuWMxlEZw2r+iP2GNCdIi4C1qmUzdZFSVb+bacwQ= -github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o= -github.com/armon/go-metrics v0.0.0-20180917152333-f0300d1749da/go.mod h1:Q73ZrmVTwzkszR9V5SSuryQ31EELlFMUz1kKyl939pY= -github.com/armon/go-radix v0.0.0-20180808171621-7fddfc383310/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= -github.com/aryann/difflib v0.0.0-20170710044230-e206f873d14a/go.mod h1:DAHtR1m6lCRdSC2Tm3DSWRPvIPr6xNKyeHdqDQSQT+A= -github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQwij/eHl5CU= -github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= -github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= -github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= -github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= -github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= -github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs= -github.com/casbin/casbin/v2 v2.1.2/go.mod h1:YcPU1XXisHhLzuxH9coDNf2FbKpjGlbCg3n9yuLkIJQ= -github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QHaoyV4aDUVVkXQJJJ3NXXM= -github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= -github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= -github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= -github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/cheekybits/is v0.0.0-20150225183255-68e9c0620927/go.mod h1:h/aW8ynjgkuj+NQRlZcDbAbM1ORAbXjXX77sX7T289U= -github.com/clbanning/x2j v0.0.0-20191024224557-825249438eec/go.mod h1:jMjuTZXRI4dUb/I5gc9Hdhagfvm9+RyrPryS/auMzxE= -github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= -github.com/cockroachdb/datadriven v0.0.0-20190809214429-80d97fb3cbaa/go.mod h1:zn76sxSg3SzpJ0PPJaLDCu+Bu0Lg3sKTORVIj19EIF8= -github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE/e/2PUdi/liOCUjSTXgM1o87ZssimdTWN964YiIeI= -github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/coreos/go-systemd v0.0.0-20180511133405-39ca1b05acc7/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= -github.com/coreos/pkg v0.0.0-20160727233714-3ac0863d7acf/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA= -github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU= -github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v0.0.0-20161028175848-04cdfd42973b/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 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/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/djherbis/atime v1.1.0/go.mod h1:28OF6Y8s3NQWwacXc5eZTsEsiMzp7LF8MbXE+XJPdBE= -github.com/dolthub/flatbuffers/v23 v23.3.3-dh.2 h1:u3PMzfF8RkKd3lB9pZ2bfn0qEG+1Gms9599cr0REMww= -github.com/dolthub/flatbuffers/v23 v23.3.3-dh.2/go.mod h1:mIEZOHnFx4ZMQeawhw9rhsj+0zwQj7adVsnBX7t+eKY= -github.com/dolthub/go-icu-regex v0.0.0-20230524105445-af7e7991c97e h1:kPsT4a47cw1+y/N5SSCkma7FhAPw7KeGmD6c9PBZW9Y= -github.com/dolthub/go-icu-regex v0.0.0-20230524105445-af7e7991c97e/go.mod h1:KPUcpx070QOfJK1gNe0zx4pA5sicIK1GMikIGLKC168= -github.com/dolthub/go-mysql-server v0.18.1 h1:T+mTBfLrZPnOKvVx3iRx66f0oW+0saOnPa+O1OKUklQ= -github.com/dolthub/go-mysql-server v0.18.1/go.mod h1:8zjK76NDWRel1CFdg+DDzy/D5tdOeFOYKBcqf7IB+aA= -github.com/dolthub/jsonpath v0.0.2-0.20240227200619-19675ab05c71 h1:bMGS25NWAGTEtT5tOBsCuCrlYnLRKpbJVJkDbrTRhwQ= -github.com/dolthub/jsonpath v0.0.2-0.20240227200619-19675ab05c71/go.mod h1:2/2zjLQ/JOOSbbSboojeg+cAwcRV0fDLzIiWch/lhqI= -github.com/dolthub/vitess v0.0.0-20240404214255-c5a87fc7b325 h1:MYUzL2faXlBlG+EEBf+55e5RE/9k8O39MvPXGRAhjJQ= -github.com/dolthub/vitess v0.0.0-20240404214255-c5a87fc7b325/go.mod h1:Xy89nzEyIwlMCiFWOJPmlnORpDFz5wFgEdYGfUwbIQ0= -github.com/dustin/go-humanize v0.0.0-20171111073723-bb3d318650d4/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= -github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs= -github.com/eapache/go-xerial-snappy v0.0.0-20180814174437-776d5712da21/go.mod h1:+020luEh2TKB4/GOp8oxxtq0Daoen/Cii55CzbTV6DU= -github.com/eapache/queue v1.1.0/go.mod h1:6eCeP0CKFpHLu8blIFXhExK/dRa7WDZfr6jVFPTqq+I= -github.com/edsrzf/mmap-go v1.0.0/go.mod h1:YO35OhQPt3KJa3ryjFM5Bs14WD66h8eGKpfaBNrHW5M= github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385 h1:clC1lXBpe2kTj2VHdaIu9ajZQe4kcEY9j0NsnDDBZ3o= github.com/eknkc/amber v0.0.0-20171010120322-cdade1c07385/go.mod h1:0vRUJqYpeSZifjYj7uP3BG/gKcuzL9xWVV/Y+cK33KM= -github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= -github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= -github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/fatih/structs v1.1.0 h1:Q7juDM0QtcnhCpeyLGQKyg4TOIghuNXrkL32pHAUMxo= github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M= github.com/flosch/pongo2/v4 v4.0.2 h1:gv+5Pe3vaSVmiJvh/BZa82b7/00YUGm0PIyVVLop0Hw= github.com/flosch/pongo2/v4 v4.0.2/go.mod h1:B5ObFANs/36VwxxlgKpdchIJHMvHB562PW+BWPhwZD8= -github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= -github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= -github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= -github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.8.1 h1:4+fr/el88TOO3ewCmQr8cx/CtZ/umlIRIs5M4NTNjf8= @@ -110,16 +45,6 @@ github.com/glebarez/go-sqlite v1.21.1 h1:7MZyUPh2XTrHS7xNEHQbrhfMZuPSzhkm2A1qgg0 github.com/glebarez/go-sqlite v1.21.1/go.mod h1:ISs8MF6yk5cL4n/43rSOmVMGJJjHYr7L2MbZZ5Q4E2E= 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/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= -github.com/go-kit/kit v0.10.0 h1:dXFJfIHVvUcpSgDOV+Ne6t7jXri8Tfv2uOLHUZ2XNuo= -github.com/go-kit/kit v0.10.0/go.mod h1:xUsJbQ/Fp4kEt7AFgCuvyX4a71u8h9jB8tj/ORgOZ7o= -github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= -github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= -github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= -github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/logr v1.2.3/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= @@ -128,39 +53,15 @@ github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/j github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= github.com/go-playground/validator/v10 v10.11.1 h1:prmOlTVv+YjZjmRmNSF3VmspqJIxJWXmqUsHwfTRRkQ= github.com/go-playground/validator/v10 v10.11.1/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= -github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w= -github.com/go-sql-driver/mysql v1.7.2-0.20231213112541-0004702b931d h1:QQP1nE4qh5aHTGvI1LgOFxZYVxYoGeMfbNHikogPyoA= -github.com/go-sql-driver/mysql v1.7.2-0.20231213112541-0004702b931d/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= -github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-sql-driver/mysql v1.8.1 h1:LedoTUt/eveggdHS9qUFC1EFSa8bU2+1pZjSRpvNJ1Y= +github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/goccy/go-json v0.9.11 h1:/pAaQDLHEoCq/5FFmSKBswWmK6H0e8g4159Kc/X/nqk= github.com/goccy/go-json v0.9.11/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= github.com/gofiber/fiber/v2 v2.52.2 h1:b0rYH6b06Df+4NyrbdptQL8ifuxw/Tf2DgfkZkDaxEo= github.com/gofiber/fiber/v2 v2.52.2/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ= -github.com/gogo/googleapis v1.1.0/go.mod h1:gf4bu3Q80BeJ6H1S1vYPm8/ELATdvryBaNFGgqEef3s= -github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.0/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= -github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= -github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= -github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= -github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= -github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/snappy v0.0.0-20180518054509-2e65f85255db/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= -github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= -github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE= 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/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= @@ -168,65 +69,22 @@ github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= -github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= -github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= -github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg= github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= -github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= -github.com/gorilla/websocket v0.0.0-20170926233335-4201258b820c/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ= github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc= github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= -github.com/grpc-ecosystem/go-grpc-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= -github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk= -github.com/grpc-ecosystem/grpc-gateway v1.9.5/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY= -github.com/hashicorp/consul/api v1.3.0/go.mod h1:MmDNSzIMUjNpY/mQ398R4bk2FnqQLoPndWW5VkKPlCE= -github.com/hashicorp/consul/sdk v0.3.0/go.mod h1:VKf9jXwCTEY1QZP2MOLRhb5i/I/ssyNV1vwHyQBF0x8= -github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= -github.com/hashicorp/go-cleanhttp v0.5.1/go.mod h1:JpRdi6/HCYpAwUzNwuwqhbovhLtngrth3wmdIIUrZ80= -github.com/hashicorp/go-immutable-radix v1.0.0/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60= -github.com/hashicorp/go-msgpack v0.5.3/go.mod h1:ahLV/dePpqEmjfWmKiqvPkv/twdG7iPBM1vqhUKIvfM= -github.com/hashicorp/go-multierror v1.0.0/go.mod h1:dHtQlpGsu+cZNNAkkCN/P3hoUDHhCYQXV3UM06sGGrk= -github.com/hashicorp/go-rootcerts v1.0.0/go.mod h1:K6zTfqpRlCUIjkwsN4Z+hiSfzSTQa6eBIzfwKfwNnHU= -github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerXegt+ozgdvDeDU= -github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4= -github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= -github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc= -github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= -github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64= -github.com/hashicorp/mdns v1.0.0/go.mod h1:tL+uN++7HEJ6SQLQ2/p+z2pH24WQKWjBPkE0mNTz8vQ= -github.com/hashicorp/memberlist v0.1.3/go.mod h1:ajVTdAv/9Im8oMAAj5G31PhhMCZJV2pPBoIllUwCN7I= -github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/JwenrHc= -github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= -github.com/hudl/fargo v1.3.0/go.mod h1:y3CKSmjA+wD2gak7sUSXTAoopbhU08POFhmITJgmKTg= github.com/imkira/go-interpol v1.1.0 h1:KIiKr0VSG2CUW1hl1jpiyuzuJeKUUpC8iM1AIE7N1Vk= github.com/imkira/go-interpol v1.1.0/go.mod h1:z0h2/2T3XF8kyEPpRgJ3kmNv+C43p+I/CoI+jC3w2iA= -github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/influxdata/influxdb1-client v0.0.0-20191209144304-8bf82d3c094d/go.mod h1:qj24IKcXYK6Iy9ceXlo3Tc+vtHo9lIhSX5JddghvEPo= github.com/iris-contrib/httpexpect/v2 v2.12.1 h1:3cTZSyBBen/kfjCtgNFoUKi1u0FVXNaAjyRJOo6AVS4= github.com/iris-contrib/httpexpect/v2 v2.12.1/go.mod h1:7+RB6W5oNClX7PTwJgJnsQP3ZuUUYB3u61KCqeSgZ88= github.com/iris-contrib/schema v0.0.6 h1:CPSBLyx2e91H2yJzPuhGuifVRnZBBJ3pCOMbOvPZaTw= github.com/iris-contrib/schema v0.0.6/go.mod h1:iYszG0IOsuIsfzjymw1kMzTL8YQcCWlm65f3wX8J5iA= -github.com/jmespath/go-jmespath v0.0.0-20180206201540-c2b33e8439af/go.mod h1:Nht3zPeWKUH0NzdCt2Blrr5ys8VGpn0CEB0cQHVjt7k= -github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= -github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= -github.com/json-iterator/go v1.1.7/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= -github.com/json-iterator/go v1.1.8/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= -github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kataras/blocks v0.0.7 h1:cF3RDY/vxnSRezc7vLFlQFTYXG/yAr1o7WImJuZbzC4= github.com/kataras/blocks v0.0.7/go.mod h1:UJIU97CluDo0f+zEjbnbkeMRlvYORtmc1304EeyXf4I= github.com/kataras/golog v0.1.8 h1:isP8th4PJH2SrbkciKnylaND9xoTtfxv++NB+DF0l9g= @@ -239,12 +97,8 @@ github.com/kataras/sitemap v0.0.6 h1:w71CRMMKYMJh6LR2wTgnk5hSgjVNB9KL60n5e2KHvLY github.com/kataras/sitemap v0.0.6/go.mod h1:dW4dOCNs896OR1HmG+dMLdT7JjDk7mYBzoIRwuj5jA4= github.com/kataras/tunnel v0.0.4 h1:sCAqWuJV7nPzGrlb0os3j49lk2JhILT0rID38NHNLpA= github.com/kataras/tunnel v0.0.4/go.mod h1:9FkU4LaeifdMWqZu7o20ojmW4B7hdhv2CMLwfnHGpYw= -github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= -github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= github.com/klauspost/compress v1.17.7 h1:ehO88t2UGzQK66LMdE8tibEd1ErmzZjNEqWkjLAKQQg= github.com/klauspost/compress v1.17.7/go.mod h1:Di0epgTjJY877eYKx5yC51cX2A2Vl2ibi7bDH9ttBbw= -github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= -github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= @@ -259,163 +113,70 @@ github.com/labstack/gommon v0.4.0 h1:y7cvthEAEbU0yHOf4axH8ZG2NH8knB9iNSoTO8dyIk8 github.com/labstack/gommon v0.4.0/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= -github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc h1:RKf14vYWi2ttpEmkA4aQ3j4u9dStX2t4M8UM6qqNsG8= -github.com/lestrrat-go/envload v0.0.0-20180220234015-a3eb8ddeffcc/go.mod h1:kopuH9ugFRkIXf3YoqHKyrJ9YfUFsckUU9S7B+XP+is= -github.com/lestrrat-go/strftime v1.0.4 h1:T1Rb9EPkAhgxKqbcMIPguPq8glqXTA1koF8n9BHElA8= -github.com/lestrrat-go/strftime v1.0.4/go.mod h1:E1nN3pCbtMSu1yjSVeyuRFVm/U0xoR76fd03sz+Qz4g= github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/lightstep/lightstep-tracer-common/golang/gogo v0.0.0-20190605223551-bc2310a04743/go.mod h1:qklhhLq1aX+mtWk9cPHPzaBjWImj5ULL6C7HFJtXQMM= -github.com/lightstep/lightstep-tracer-go v0.18.1/go.mod h1:jlF1pusYV4pidLvZ+XD0UBX0ZE6WURAspgAczcDHrL4= -github.com/lyft/protoc-gen-validate v0.0.13/go.mod h1:XbGvPuh87YZc5TdIa2/I4pLk0QoUACkjt2znoq26NVQ= github.com/mailgun/raymond/v2 v2.0.48 h1:5dmlB680ZkFG2RN/0lvTAghrSxIESeu9/2aeDqACtjw= github.com/mailgun/raymond/v2 v2.0.48/go.mod h1:lsgvL50kgt1ylcFJYZiULi5fjPBkkhNfj4KA0W54Z18= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/matryer/try v0.0.0-20161228173917-9ac251b645a2/go.mod h1:0KeJpeMD6o+O4hW7qJOT7vyQPKrWmj26uf5wMc/IiIs= -github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= -github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= -github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= -github.com/mattn/go-runewidth v0.0.2/go.mod h1:LwmH8dsx7+W8Uxz3IHJYH5QSwggIsqBzpuz5H//U1FU= github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= -github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= github.com/microcosm-cc/bluemonday v1.0.23 h1:SMZe2IGa0NuHvnVNAZ+6B38gsTbi5e4sViiWJyDDqFY= github.com/microcosm-cc/bluemonday v1.0.23/go.mod h1:mN70sk7UkkF8TUr2IGBpNN0jAgStuPzlK76QuruE/z4= -github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg= -github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc= -github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eIhFbs2+BFgRtFPeD/KE+zxI= github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= -github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= -github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= -github.com/mitchellh/mapstructure v0.0.0-20160808181253-ca63d7c062ee/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= -github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U= -github.com/nats-io/jwt v0.3.0/go.mod h1:fRYCDE99xlTsqUzISS1Bi75UBJ6ljOJQOAAu5VglpSg= -github.com/nats-io/jwt v0.3.2/go.mod h1:/euKqTS1ZD+zzjYrY7pseZrTtWQSjujC7xjPc8wL6eU= -github.com/nats-io/nats-server/v2 v2.1.2/go.mod h1:Afk+wRZqkMQs/p45uXdrVLuab3gwv3Z8C4HTBu8GD/k= -github.com/nats-io/nats.go v1.9.1/go.mod h1:ZjDU1L/7fJ09jvUSRVBR2e7+RnLiiIQyqyzEE/Zbp4w= -github.com/nats-io/nkeys v0.1.0/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= -github.com/nats-io/nkeys v0.1.3/go.mod h1:xpnFELMwJABBLVhffcfd1MZx6VsNRFpEugbxziKVo7w= -github.com/nats-io/nuid v1.0.1/go.mod h1:19wcPz3Ph3q0Jbyiqsd0kePYG7A95tJPxeL+1OSON2c= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY= github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc= -github.com/oklog/oklog v0.3.2/go.mod h1:FCV+B7mhrz4o+ueLpx+KqkyXRGMWOYEvfiXtdGtbWGs= -github.com/oklog/run v1.0.0/go.mod h1:dlhp/R75TPv97u0XWUtDeV/lRKWPKSdTuV0TZvrmrQA= -github.com/olekukonko/tablewriter v0.0.0-20170122224234-a0225b3f23b5/go.mod h1:vsDQFd/mU46D+Z4whnwzcISnGGzXWMclvtLoiIKAKIo= -github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/ginkgo v1.7.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= -github.com/onsi/gomega v1.4.3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/onsi/gomega v1.27.1 h1:rfztXRbg6nv/5f+Raen9RcGoSecHIFgBBLQK3Wdj754= github.com/onsi/gomega v1.27.1/go.mod h1:aHX5xOykVYzWOV4WqQy0sy8BQptgukenXpCXfadcIAw= -github.com/op/go-logging v0.0.0-20160315200505-970db520ece7/go.mod h1:HzydrMdWErDVzsI23lYNej1Htcns9BCg93Dk0bBINWk= -github.com/opentracing-contrib/go-observer v0.0.0-20170622124052-a52f23424492/go.mod h1:Ngi6UdF0k5OKD5t5wlmGhe/EDKPoUM3BXZSSfIuJbis= -github.com/opentracing/basictracer-go v1.0.0/go.mod h1:QfBfYuafItcjQuMwinw9GhYKwFXS9KnPs5lxoYwgW74= -github.com/opentracing/opentracing-go v1.0.2/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= -github.com/opentracing/opentracing-go v1.1.0/go.mod h1:UkNAQd3GIcIGf0SeVgPpRdFStlNbqXla1AfSYxPUl2o= -github.com/openzipkin-contrib/zipkin-go-opentracing v0.4.5/go.mod h1:/wsWhb9smxSfWAKL3wpBW7V8scJMt8N8gnaMCS9E/cA= -github.com/openzipkin/zipkin-go v0.1.6/go.mod h1:QgAqvLzwWbR/WpD4A3cGpPtJrZXNIiJc5AZX7/PBEpw= -github.com/openzipkin/zipkin-go v0.2.1/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= -github.com/openzipkin/zipkin-go v0.2.2/go.mod h1:NaW6tEwdmWMaCDZzg8sh+IBNOxHMPnhQw8ySjnjRyN4= -github.com/pact-foundation/pact-go v1.0.4/go.mod h1:uExwJY4kCzNPcHRj+hCR/HBbOOIwwtUjcrb0b5/5kLM= -github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc= -github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k= github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg= github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas= -github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac= -github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc= -github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY= 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/diff v0.0.0-20200914180035-5b29258ca4f7/go.mod h1:zO8QMzTeZd5cpnIkz/Gn6iK0jDfGicM1nynOkkPIl28= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= -github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= -github.com/pkg/profile v1.2.1/go.mod h1:hJw3o1OdXxsrSjjVksARp5W95eeEaEfptyVZyv6JUPA= github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 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/posener/complete v1.1.1/go.mod h1:em0nMJCgc9GFtwrmVmEMR/ZL6WyhyjMBndrE9hABlRI= -github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= -github.com/prometheus/client_golang v0.9.3-0.20190127221311-3c4408c8b829/go.mod h1:p2iRAGwDERtqlqzRXnrOVns+ignqQo//hLXqYxZYVNs= -github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= -github.com/prometheus/client_golang v1.3.0/go.mod h1:hJaj2vgQTGQmVCsAACORcieXFeDPbaTKGT+JTgUa3og= -github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190115171406-56726106282f/go.mod h1:MbSGuTsp3dbXC40dX6PRTWyKYBIrTGTE9sqQNg2J8bo= -github.com/prometheus/client_model v0.0.0-20190129233127-fd36f4220a90/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/client_model v0.1.0/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= -github.com/prometheus/common v0.2.0/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.4.1/go.mod h1:TNfzLD0ON7rHzMJeJkieUDPYmFC7Snx/y86RQel1bk4= -github.com/prometheus/common v0.7.0/go.mod h1:DjGbpBbp5NYNiECxcL/VnbXCCaQpKd3tt26CguLLsqA= -github.com/prometheus/procfs v0.0.0-20181005140218-185b4288413d/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.0-20190117184657-bf6a532e95b1/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= -github.com/prometheus/procfs v0.0.2/go.mod h1:TjEm7ze935MbeOT/UhFTIMYKhuLP4wbCsTZCD3I8kEA= -github.com/prometheus/procfs v0.0.8/go.mod h1:7Qr8sr6344vo1JqZ6HhLceV9o3AJ1Ff+GxbHq6oeK9A= -github.com/rcrowley/go-metrics v0.0.0-20181016184325-3113b8401b8a/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg= -github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= 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/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts= -github.com/samuel/go-zookeeper v0.0.0-20190923202752-2cc03de413da/go.mod h1:gi+0XIa01GRL2eRQVjQkKGqKF3SF9vZR/HnPullcV2E= github.com/sanity-io/litter v1.5.5 h1:iE+sBxPBzoK6uaEP5Lt3fHNgpKcHXc/A2HGETy0uJQo= github.com/sanity-io/litter v1.5.5/go.mod h1:9gzJgR2i4ZpjZHsKvUXIRQVk7P+yM3e+jAF7bU2UI5U= github.com/schollz/closestmatch v2.1.0+incompatible h1:Uel2GXEpJqOWBrlyI+oY9LTiyyjYS17cCYRqP13/SHk= github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g= -github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/sergi/go-diff v1.0.0 h1:Kpca3qRNrduNnOQeazBd0ysaKrUJiIuISHxogkT9RPQ= github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= -github.com/shopspring/decimal v1.3.1 h1:2Usl1nmF/WZucqkFZhnfFYxxxu8LG21F6nPQBE5gKV8= -github.com/shopspring/decimal v1.3.1/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= -github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= -github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= -github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= -github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= -github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= -github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= -github.com/sony/gobreaker v0.4.1/go.mod h1:ZKptC7FHNvhBz7dN2LGjPVBz2sZJmc0/PkyDJOjmxWY= -github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= -github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= -github.com/streadway/amqp v0.0.0-20190404075320-75d898a42a94/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= -github.com/streadway/amqp v0.0.0-20190827072141-edfb9018d271/go.mod h1:AZpEONHx3DKn8O/DFsRAY58/XVQiIPMTMB1SddzLXVw= -github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5JnDBl6z3cMAg/SywNDC5ABu5ApDIw6lUbRmI= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/testify v0.0.0-20161117074351-18a02ba4a312/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= @@ -435,14 +196,9 @@ github.com/tdewolff/parse/v2 v2.6.4 h1:KCkDvNUMof10e3QExio9OPZJT8SbdKojLBumw8YZy github.com/tdewolff/parse/v2 v2.6.4/go.mod h1:woz0cgbLwFdtbjJu8PIKxhW05KplTFQkOdX78o+Jgrs= github.com/tdewolff/test v1.0.7 h1:8Vs0142DmPFW/bQeHRP3MV19m1gvndjUb1sn8yy74LM= github.com/tdewolff/test v1.0.7/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE= -github.com/tetratelabs/wazero v1.1.0 h1:EByoAhC+QcYpwSZJSs/aV0uokxPwBgKxfiokSUwAknQ= -github.com/tetratelabs/wazero v1.1.0/go.mod h1:wYx2gNRg8/WihJfSDxA1TIL8H+GkfLYm+bIfbblu9VQ= -github.com/tmc/grpc-websocket-proxy v0.0.0-20170815181823-89b8d40f7ca8/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U= github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= -github.com/urfave/cli v1.20.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA= -github.com/urfave/cli v1.22.1/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0= github.com/urfave/negroni/v3 v3.1.1 h1:6MS4nG9Jk/UuCACaUlNXCbiKa0ywF9LXz5dGu09v8hw= github.com/urfave/negroni/v3 v3.1.1/go.mod h1:jWvnX03kcSjDBl/ShB0iHvx5uOs7mAzZXW+JvJ5XYAs= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= @@ -464,7 +220,6 @@ github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHo github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= -github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU= github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0 h1:6fRhSjgLCkTD3JnJxvaJ4Sj+TYblw757bqYgZaOq5ZY= github.com/yalp/jsonpath v0.0.0-20180802001716-5cc68e5049a0/go.mod h1:/LWChgwKmvncFJFHJ7Gvn9wZArjbV5/FppcK2fKk/tI= github.com/yosssi/ace v0.0.5 h1:tUkIP/BLdKqrlrPwcmH0shwEEhTRHoGnc1wFIWmaBUA= @@ -477,97 +232,29 @@ github.com/yudai/pp v2.0.1+incompatible h1:Q4//iY4pNF6yPLZIigmvcl7k/bPgrcTPIFIcm github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= -go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= -go.etcd.io/etcd v0.0.0-20191023171146-3cf2f69b5738/go.mod h1:dnLIgRNXwCJa5e+c6mIZCrds/GIG4ncV9HhK5PX7jPg= -go.opencensus.io v0.20.1/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= -go.opencensus.io v0.20.2/go.mod h1:6WKK9ahsWS3RSO+PY9ZHZUfv2irvY6gN279GOPZjmmk= -go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= -go.opentelemetry.io/otel v1.7.0 h1:Z2lA3Tdch0iDcrhJXDIlC94XE+bxok1F9B+4Lz/lGsM= -go.opentelemetry.io/otel v1.7.0/go.mod h1:5BdUoMIz5WEs0vt0CUEMtSSaTSHBBVwrhnz7+nrD5xk= -go.opentelemetry.io/otel/trace v1.7.0 h1:O37Iogk1lEkMRXewVtZ1BBTVn5JEp8GrJvP92bJqC6o= -go.opentelemetry.io/otel/trace v1.7.0/go.mod h1:fzLSB9nqR2eXzxPXb2JW9IKE+ScyXA48yyE4TNvoHqU= -go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= -go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= -go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= -go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= -go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= -go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= -go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= -golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= -golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= -golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= -golang.org/x/crypto v0.0.0-20190701094942-4def268fd1a4/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= -golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1 h1:MGwJjxBy0HJshjDNfLsYO8xppfqWlA5ZT9OhtUUhTNw= -golang.org/x/exp v0.0.0-20230713183714-613f0c0eb8a1/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc= -golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= -golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= -golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= -golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= -golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.5.1/go.mod h1:5OXOZSfqPIIbmVBIIKWRFfZjPR0E5r58TLhUjH0a2Ro= -golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc= -golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181023162649-9b4f9f5ad519/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181201002055-351d144fa1fc/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190125091013-d26f9f9a57f3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190327091125-710a502c58a2/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= -golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= -golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.23.0 h1:7EYJ93RZ9vYSZAIb2x3lnuvqO5zneoD6IvWjuhfxjTs= golang.org/x/net v0.23.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= -golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= -golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= -golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.3.0 h1:ftCYgMx6zT/asHUrPw8BLLscYtGznsLAnjq5RH9P66E= -golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y= -golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181026203630-95b1ffbd15a5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181107165924-66b7b1311ac8/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20190826190057-c7b8b68b1456/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20191220142924-d4481acd189f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -586,84 +273,33 @@ 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/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4= golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= -golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20180828015842-6cd1fcedba52/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= -golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= -golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= -golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= -golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= -golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= -golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= golang.org/x/tools v0.0.0-20201211185031-d93e913c1a58/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= golang.org/x/tools v0.1.9/go.mod h1:nABZi5QlRsZVlzPpHl034qft6wpY4eDcsTt5AaioBiU= -golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ= -golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk= -google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= -google.golang.org/appengine v1.2.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= -google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= -google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= -google.golang.org/genproto v0.0.0-20190530194941-fb225487d101/go.mod h1:z3L6/3dTEVtUr6QSP8miRzeRqwQOioJ9I66odjN4I7s= -google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f h1:BWUVssLB0HVOSY78gIdvk1dTVYtT1y8SBWtPYuTJ/6w= -google.golang.org/genproto v0.0.0-20230110181048-76db0878b65f/go.mod h1:RGgjbofJ8xD9Sq1VVhDM1Vok1vRONV+rg+CjzG4SZKM= -google.golang.org/grpc v1.17.0/go.mod h1:6QZJwpn2B+Zp71q/5VxRsJ6NXXVCE5NRUHRo+f3cWCs= -google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= -google.golang.org/grpc v1.20.0/go.mod h1:chYK+tFQF0nDUGJgXMSgLCQk3phJEuONr2DCgLDdAQM= -google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= -google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= -google.golang.org/grpc v1.22.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.23.1/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= -google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= -google.golang.org/grpc v1.53.0 h1:LAv2ds7cmFV/XTS3XG1NneeENYrXGmorPxsBbptIjNc= -google.golang.org/grpc v1.53.0/go.mod h1:OnIrk0ipVdj4N5d9IUoFUx72/VlD7+jUsHwZgwSMQpw= -google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI= google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos= -gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20200902074654-038fdea0a05b/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/cheggaaa/pb.v1 v1.0.25/go.mod h1:V/YB90LKu/1FcN3WVnfiiE5oMCibMjukxqG/qStrOgw= gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= -gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/gcfg.v1 v1.2.3/go.mod h1:yesOnuUOFQAhST5vPY4nbZsb/huCgGGXlipJsBn0b3o= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= -gopkg.in/resty.v1 v1.12.0/go.mod h1:mDo4pnntr5jdWRML875a/NmxYqAlA73dVijT2AXvQQo= -gopkg.in/src-d/go-errors.v1 v1.0.0 h1:cooGdZnCjYbeS1zb1s6pVAAimTdKceRrpn7aKOnNIfc= -gopkg.in/src-d/go-errors.v1 v1.0.0/go.mod h1:q1cBlomlw2FnDBDNGlnh6X0jPihy+QxZfMMNxPCbdYg= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= -gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= -gopkg.in/yaml.v2 v2.0.0-20170812160011-eb3733d160e7/go.mod h1:JAlM8MvJe8wmxCU4Bli9HhUf9+ttbYbLASfIpnQbh74= -gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= @@ -671,10 +307,6 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= -honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= modernc.org/libc v1.22.3 h1:D/g6O5ftAfavceqlLOFwaZuA5KYafKwmr30A6iSqoyY= modernc.org/libc v1.22.3/go.mod h1:MQrloYP209xa2zHome2a8HLiLm6k0UT8CoHpV74tOFw= modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ= @@ -685,5 +317,3 @@ modernc.org/sqlite v1.21.1 h1:GyDFqNnESLOhwwDRaHGdp2jKLDzpyT/rNLglX3ZkMSU= modernc.org/sqlite v1.21.1/go.mod h1:XwQ0wZPIh1iKb5mkvCJ3szzbhk+tykC8ZWqTRTgYRwI= moul.io/http2curl/v2 v2.3.0 h1:9r3JfDzWPcbIklMOs2TnIFzDYvfAZvjeavG6EzP7jYs= moul.io/http2curl/v2 v2.3.0/go.mod h1:RW4hyBjTWSYDOxapodpNEtX0g5Eb16sxklBqmd2RHcE= -sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= -sourcegraph.com/sourcegraph/appdash v0.0.0-20190731080439-ebfcffb1b5c0/go.mod h1:hI742Nqp5OhwiqlzhgfbWU4mW4yO10fP+LoT9WOswdU= diff --git a/sentrysql/sentrysql_go1.22_test.go b/sentrysql/sentrysql_go1.22_test.go deleted file mode 100644 index d8a3b4be..00000000 --- a/sentrysql/sentrysql_go1.22_test.go +++ /dev/null @@ -1,371 +0,0 @@ -//go:build go1.22 - -package sentrysql_test - -import ( - "context" - "database/sql" - "fmt" - "strings" - "testing" - "time" - - sqle "github.com/dolthub/go-mysql-server" - "github.com/dolthub/go-mysql-server/memory" - "github.com/dolthub/go-mysql-server/server" - sqlq "github.com/dolthub/go-mysql-server/sql" - "github.com/dolthub/go-mysql-server/sql/types" - "github.com/dolthub/vitess/go/vt/proto/query" - "github.com/getsentry/sentry-go" - "github.com/getsentry/sentry-go/internal/testutils" - "github.com/getsentry/sentry-go/sentrysql" - "github.com/go-sql-driver/mysql" - "github.com/google/go-cmp/cmp" -) - -func createTestDatabase() *memory.DbProvider { - db := memory.NewDatabase("test") - db.BaseDatabase.EnablePrimaryKeyIndexes() - - pro := memory.NewDBProvider(db) - session := memory.NewSession(sqlq.NewBaseSession(), pro) - ctx := sqlq.NewContext(context.Background(), sqlq.WithSession(session)) - - tableName := "users" - table := memory.NewTable(db, "users", sqlq.NewPrimaryKeySchema(sqlq.Schema{ - {Name: "name", Type: types.Text, Nullable: false, Source: tableName, PrimaryKey: false}, - {Name: "email", Type: types.Text, Nullable: false, Source: tableName, PrimaryKey: true}, - {Name: "age", Type: types.Int32, Nullable: false, Source: tableName}, - {Name: "created_at", Type: types.MustCreateDatetimeType(query.Type_DATETIME, 6), Nullable: false, Source: tableName}, - }), db.GetForeignKeyCollection()) - db.AddTable(tableName, table) - - creationTime := time.Unix(0, 1667304000000001000).UTC() - _ = table.Insert(ctx, sqlq.NewRow("Bob Smith", "bob@smith.com", 30, creationTime)) - _ = table.Insert(ctx, sqlq.NewRow("Jane Doe", "jane@doe.com", 25, creationTime)) - _ = table.Insert(ctx, sqlq.NewRow("John Doe", "john@doe.com", 35, creationTime)) - - return pro -} - -//nolint:dupl -func TestNewSentrySQLConnector_Go122(t *testing.T) { - testDatabase := createTestDatabase() - engine := sqle.NewDefault(testDatabase) - session := memory.NewSession(sqlq.NewBaseSession(), testDatabase) - ctx := sqlq.NewContext(context.Background(), sqlq.WithSession(session)) - ctx.SetCurrentDatabase("test") - config := server.Config{ - Protocol: "tcp", - Address: fmt.Sprintf("%s:%d", "127.0.0.1", 3306), - } - s, err := server.NewServer(config, engine, memory.NewSessionBuilder(testDatabase), nil) - if err != nil { - t.Fatalf("creating new server: %s", err.Error()) - } - - go func() { - if err = s.Start(); err != nil { - panic(err) - } - }() - defer func() { - if err := s.Close(); err != nil { - t.Logf("closing server connection: %s", err.Error()) - } - }() - - connector, err := mysql.NewConnector(&mysql.Config{ - User: "root", - Passwd: "", - Net: "tcp", - Addr: "127.0.0.1:3306", - DBName: "test", - AllowCleartextPasswords: true, - AllowFallbackToPlaintext: true, - AllowNativePasswords: true, - ParseTime: true, - }) - if err != nil { - t.Fatalf("creating mysql connector: %s", err.Error()) - } - - db := sql.OpenDB(sentrysql.NewSentrySQLConnector(connector, sentrysql.WithDatabaseName("test"), sentrysql.WithDatabaseSystem(sentrysql.MySQL), sentrysql.WithServerAddress("127.0.0.1", "3306"))) - defer func() { - _ = db.Close() - }() - - t.Run("QueryContext", func(t *testing.T) { - tests := []struct { - Query string - Parameters []interface{} - WantSpan *sentry.Span - WantError bool - }{ - { - Query: "SELECT * FROM users", - WantSpan: &sentry.Span{ - Data: map[string]interface{}{ - "db.system": sentrysql.MySQL, - "db.name": "test", - "server.address": "127.0.0.1", - "server.port": "3306", - "db.operation": "SELECT", - }, - Description: "SELECT * FROM users", - Op: "db.sql.query", - Tags: nil, - Origin: "manual", - Sampled: sentry.SampledTrue, - Status: sentry.SpanStatusOK, - }, - }, - { - Query: "SELECT FROM query_test", - WantError: true, - WantSpan: &sentry.Span{ - Data: map[string]interface{}{ - "db.system": sentrysql.MySQL, - "db.name": "test", - "server.address": "127.0.0.1", - "server.port": "3306", - "db.operation": "SELECT", - }, - Description: "SELECT FROM query_test", - Op: "db.sql.query", - Tags: nil, - Origin: "manual", - Sampled: sentry.SampledTrue, - Status: sentry.SpanStatusInternalError, - }, - }, - } - - spansCh := make(chan []*sentry.Span, len(tests)) - - sentryClient, err := sentry.NewClient(sentry.ClientOptions{ - EnableTracing: true, - TracesSampleRate: 1.0, - BeforeSendTransaction: func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event { - spansCh <- event.Spans - return event - }, - }) - if err != nil { - t.Fatal(err) - } - - for _, tt := range tests { - hub := sentry.NewHub(sentryClient, sentry.NewScope()) - ctx, cancel := context.WithTimeout(sentry.SetHubOnContext(context.Background(), hub), 10*time.Second) - span := sentry.StartSpan(ctx, "fake_parent", sentry.WithTransactionName("Fake Parent")) - ctx = span.Context() - - rows, err := db.QueryContext(ctx, tt.Query, tt.Parameters...) - if err != nil && !tt.WantError { - cancel() - t.Fatal(err) - } - - if rows != nil { - _ = rows.Close() - } - - span.Finish() - cancel() - } - - if ok := sentryClient.Flush(testutils.FlushTimeout()); !ok { - t.Fatal("sentry.Flush timed out") - } - close(spansCh) - - var got [][]*sentry.Span - for e := range spansCh { - got = append(got, e) - } - - for i, tt := range tests { - var foundMatch = false - gotSpans := got[i] - - var diffs []string - for _, gotSpan := range gotSpans { - if diff := cmp.Diff(tt.WantSpan, gotSpan, optstrans); diff != "" { - diffs = append(diffs, diff) - } else { - foundMatch = true - break - } - } - - if !foundMatch { - t.Errorf("Span mismatch (-want +got):\n%s", strings.Join(diffs, "\n")) - } - } - }) - - t.Run("ExecContext", func(t *testing.T) { - tests := []struct { - Query string - Parameters []interface{} - WantSpan *sentry.Span - WantError bool - }{ - { - Query: "INSERT INTO users (name, email, age, created_at) VALUES (?, ?, ?, ?)", - Parameters: []interface{}{"Michael", "michael@example.com", 17, time.Now()}, - WantSpan: &sentry.Span{ - Data: map[string]interface{}{ - "db.system": sentrysql.MySQL, - "db.name": "test", - "server.address": "127.0.0.1", - "server.port": "3306", - "db.operation": "INSERT", - }, - Description: "INSERT INTO users (name, email, age, created_at) VALUES (?, ?, ?, ?)", - Op: "db.sql.exec", - Tags: nil, - Origin: "manual", - Sampled: sentry.SampledTrue, - Status: sentry.SpanStatusOK, - }, - }, - { - Query: "UPDATE users SET name = ? WHERE email = ?", - Parameters: []interface{}{"Michael Jordan", "michael@example.com"}, - WantSpan: &sentry.Span{ - Data: map[string]interface{}{ - "db.system": sentrysql.MySQL, - "db.name": "test", - "server.address": "127.0.0.1", - "server.port": "3306", - "db.operation": "UPDATE", - }, - Description: "UPDATE users SET name = ? WHERE email = ?", - Op: "db.sql.exec", - Tags: nil, - Origin: "manual", - Sampled: sentry.SampledTrue, - Status: sentry.SpanStatusOK, - }, - }, - { - Query: "DELETE FROM users WHERE name = ?", - Parameters: []interface{}{"Nolan"}, - WantSpan: &sentry.Span{ - Data: map[string]interface{}{ - "db.system": sentrysql.MySQL, - "db.name": "test", - "server.address": "127.0.0.1", - "server.port": "3306", - "db.operation": "DELETE", - }, - Description: "DELETE FROM users WHERE name = ?", - Op: "db.sql.exec", - Tags: nil, - Origin: "manual", - Sampled: sentry.SampledTrue, - Status: sentry.SpanStatusOK, - }, - }, - { - Query: "INSERT INTO users (id, name) VALUES (?, ?, ?, ?)", - Parameters: []interface{}{ - 1, "John", "Doe", 1, - }, - WantError: true, - WantSpan: &sentry.Span{ - Data: map[string]interface{}{ - "db.system": sentrysql.MySQL, - "db.name": "test", - "server.address": "127.0.0.1", - "server.port": "3306", - "db.operation": "INSERT", - }, - Description: "INSERT INTO users (id, name) VALUES (?, ?, ?, ?)", - Op: "db.sql.exec", - Tags: nil, - Origin: "manual", - Sampled: sentry.SampledTrue, - Status: sentry.SpanStatusInternalError, - }, - }, - } - - spansCh := make(chan []*sentry.Span, len(tests)) - - sentryClient, err := sentry.NewClient(sentry.ClientOptions{ - EnableTracing: true, - TracesSampleRate: 1.0, - BeforeSendTransaction: func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event { - spansCh <- event.Spans - return event - }, - }) - if err != nil { - t.Fatal(err) - } - - for _, tt := range tests { - hub := sentry.NewHub(sentryClient, sentry.NewScope()) - ctx, cancel := context.WithTimeout(sentry.SetHubOnContext(context.Background(), hub), 10*time.Second) - span := sentry.StartSpan(ctx, "fake_parent", sentry.WithTransactionName("Fake Parent")) - ctx = span.Context() - - _, err := db.ExecContext(ctx, tt.Query, tt.Parameters...) - if err != nil && !tt.WantError { - cancel() - t.Fatal(err) - } - - span.Finish() - cancel() - } - - if ok := sentryClient.Flush(testutils.FlushTimeout()); !ok { - t.Fatal("sentry.Flush timed out") - } - close(spansCh) - - var got [][]*sentry.Span - for e := range spansCh { - got = append(got, e) - } - - for i, tt := range tests { - var foundMatch = false - gotSpans := got[i] - - var diffs []string - for _, gotSpan := range gotSpans { - if diff := cmp.Diff(tt.WantSpan, gotSpan, optstrans); diff != "" { - diffs = append(diffs, diff) - } else { - foundMatch = true - break - } - } - - if !foundMatch { - t.Errorf("Span mismatch (-want +got):\n%s", strings.Join(diffs, "\n")) - } - } - }) - - t.Run("Ping", func(t *testing.T) { - // Just checking if this works and doesn't panic - err := db.Ping() - if err != nil { - t.Fatal(err) - } - }) - - t.Run("PingContext", func(t *testing.T) { - // Just checking if this works and doesn't panic - err := db.PingContext(context.Background()) - if err != nil { - t.Fatal(err) - } - }) -} From ab1d3bff5caa7f8a3ea896f3348c67fd0035d61a Mon Sep 17 00:00:00 2001 From: Reinaldy Rafli Date: Sat, 19 Oct 2024 12:19:10 +0700 Subject: [PATCH 16/26] chore: another go1.18 rollback --- go.mod | 4 +--- go.sum | 4 ---- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/go.mod b/go.mod index 1e4af137..12dd3235 100644 --- a/go.mod +++ b/go.mod @@ -1,8 +1,6 @@ module github.com/getsentry/sentry-go -go 1.22 - -toolchain go1.23.2 +go 1.18 require ( github.com/gin-gonic/gin v1.8.1 diff --git a/go.sum b/go.sum index 9deef0c4..053c4606 100644 --- a/go.sum +++ b/go.sum @@ -68,7 +68,6 @@ github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ= -github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= @@ -142,9 +141,7 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLA github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY= github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc= github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= -github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= github.com/onsi/gomega v1.27.1 h1:rfztXRbg6nv/5f+Raen9RcGoSecHIFgBBLQK3Wdj754= -github.com/onsi/gomega v1.27.1/go.mod h1:aHX5xOykVYzWOV4WqQy0sy8BQptgukenXpCXfadcIAw= github.com/pelletier/go-toml/v2 v2.0.5 h1:ipoSadvV8oGUjnUbMub59IDPPwfxF694nG/jwbMiyQg= github.com/pelletier/go-toml/v2 v2.0.5/go.mod h1:OMHamSCAODeSsVrwwvcJOaoN0LIUIaFVNZzmWyNfXas= github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4= @@ -229,7 +226,6 @@ github.com/yudai/gojsondiff v1.0.0/go.mod h1:AY32+k2cwILAkW1fbgxQ5mUmMiZFgLIV+FB github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82 h1:BHyfKlQyqbsFN5p3IfnEUduWvb9is428/nNb5L3U01M= github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDfVJdfcVVdX+jpBxNmX4rDAzaS45IcYoM= github.com/yudai/pp v2.0.1+incompatible h1:Q4//iY4pNF6yPLZIigmvcl7k/bPgrcTPIFIcmawg5bI= -github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZkTdatxwunjIkc= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.4.1/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= From 18d5f66e08b99da19778d5af99b1b637c254c3e8 Mon Sep 17 00:00:00 2001 From: Reinaldy Rafli Date: Sat, 26 Oct 2024 19:12:58 +0700 Subject: [PATCH 17/26] test: NewSentrySQLConnector --- sentrysql/fakedb_test.go | 1289 +++++++++++++++++++++++++ sentrysql/operation.go | 15 +- sentrysql/sentrysql.go | 13 +- sentrysql/sentrysql_connector_test.go | 1289 +++++++++++++++++++++++++ 4 files changed, 2596 insertions(+), 10 deletions(-) create mode 100644 sentrysql/fakedb_test.go create mode 100644 sentrysql/sentrysql_connector_test.go diff --git a/sentrysql/fakedb_test.go b/sentrysql/fakedb_test.go new file mode 100644 index 00000000..cce79d6a --- /dev/null +++ b/sentrysql/fakedb_test.go @@ -0,0 +1,1289 @@ +package sentrysql_test + +// This file is a fork of +// https://cs.opensource.google/go/go/+/refs/tags/go1.23.2:src/database/sql/fakedb_test.go +// +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +// +// Copyright (c) 2009 The Go Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// * Neither the name of Google Inc. nor the names of its +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import ( + "context" + "database/sql" + "database/sql/driver" + "errors" + "fmt" + "io" + "reflect" + "slices" + "strconv" + "strings" + "sync" + "sync/atomic" + "testing" + "time" +) + +// fakeDriver is a fake database that implements Go's driver.Driver +// (and driver.DriverContext for Sentry purposes) interface, just for testing. +// +// It speaks a query language that's semantically similar to but +// syntactically different and simpler than SQL. The syntax is as +// follows: +// +// WIPE +// CREATE||=,=,... +// where types are: "string", [u]int{8,16,32,64}, "bool" +// INSERT||col=val,col2=val2,col3=? +// SELECT||projectcol1,projectcol2|filtercol=?,filtercol2=? +// SELECT||projectcol1,projectcol2|filtercol=?param1,filtercol2=?param2 +// +// Any of these can be preceded by PANIC||, to cause the +// named method on fakeStmt to panic. +// +// Any of these can be proceeded by WAIT||, to cause the +// named method on fakeStmt to sleep for the specified duration. +// +// Multiple of these can be combined when separated with a semicolon. +// +// When opening a fakeDriver's database, it starts empty with no +// tables. All tables and data are stored in memory only. +type fakeDriver struct { + mu sync.Mutex // guards 3 following fields + openCount int // conn opens + closeCount int // conn closes + waitCh chan struct{} + waitingCh chan struct{} + dbs map[string]*fakeDB +} + +type fakeConnector struct { + name string + + waiter func(context.Context) + closed bool +} + +var _ driver.Connector = &fakeConnector{} + +func (c *fakeConnector) Connect(context.Context) (driver.Conn, error) { + conn, err := fdriver.Open(c.name) + conn.(*fakeConn).waiter = c.waiter + return conn, err +} + +func (c *fakeConnector) Driver() driver.Driver { + return fdriver +} + +func (c *fakeConnector) Close() error { + if c.closed { + return errors.New("fakedb: connector is closed") + } + c.closed = true + return nil +} + +type fakeDriverCtx struct { + fakeDriver +} + +var _ driver.DriverContext = &fakeDriverCtx{} + +func (cc *fakeDriverCtx) OpenConnector(name string) (driver.Connector, error) { + return &fakeConnector{name: name}, nil +} + +type fakeDB struct { + name string + + useRawBytes atomic.Bool + + mu sync.Mutex + tables map[string]*table + badConn bool + allowAny bool +} + +type fakeError struct { + Message string + Wrapped error +} + +func (err fakeError) Error() string { + return err.Message +} + +func (err fakeError) Unwrap() error { + return err.Wrapped +} + +type table struct { + mu sync.Mutex + colname []string + coltype []string + rows []*row +} + +func (t *table) columnIndex(name string) int { + return slices.Index(t.colname, name) +} + +type row struct { + cols []any // must be same size as its table colname + coltype +} + +type memToucher interface { + // touchMem reads & writes some memory, to help find data races. + touchMem() +} + +type fakeConn struct { + db *fakeDB // where to return ourselves to + + currTx *fakeTx + + // Every operation writes to line to enable the race detector + // check for data races. + line int64 + + // Stats for tests: + mu sync.Mutex + stmtsMade int + stmtsClosed int + numPrepare int + + // bad connection tests; see isBad() + bad bool + stickyBad bool + + skipDirtySession bool // tests that use Conn should set this to true. + + // dirtySession tests ResetSession, true if a query has executed + // until ResetSession is called. + dirtySession bool + + // The waiter is called before each query. May be used in place of the "WAIT" + // directive. + waiter func(context.Context) +} + +func (c *fakeConn) touchMem() { + c.line++ +} + +func (c *fakeConn) incrStat(v *int) { + c.mu.Lock() + *v++ + c.mu.Unlock() +} + +type fakeTx struct { + c *fakeConn +} + +type boundCol struct { + Column string + Placeholder string + Ordinal int +} + +type fakeStmt struct { + memToucher + c *fakeConn + q string // just for debugging + + cmd string + table string + panic string + wait time.Duration + + next *fakeStmt // used for returning multiple results. + + closed bool + + colName []string // used by CREATE, INSERT, SELECT (selected columns) + colType []string // used by CREATE + colValue []any // used by INSERT (mix of strings and "?" for bound params) + placeholders int // used by INSERT/SELECT: number of ? params + + whereCol []boundCol // used by SELECT (all placeholders) + + placeholderConverter []driver.ValueConverter // used by INSERT +} + +var fdriver driver.Driver = &fakeDriver{} + +type Dummy struct { + driver.Driver +} + +// hook to simulate connection failures +var hookOpenErr struct { + sync.Mutex + fn func() error +} + +func setHookOpenErr(fn func() error) { + hookOpenErr.Lock() + defer hookOpenErr.Unlock() + hookOpenErr.fn = fn +} + +// Supports dsn forms: +// +// +// ; (only currently supported option is `badConn`, +// which causes driver.ErrBadConn to be returned on +// every other conn.Begin()) +func (d *fakeDriver) Open(dsn string) (driver.Conn, error) { + hookOpenErr.Lock() + fn := hookOpenErr.fn + hookOpenErr.Unlock() + if fn != nil { + if err := fn(); err != nil { + return nil, err + } + } + parts := strings.Split(dsn, ";") + if len(parts) < 1 { + return nil, errors.New("fakedb: no database name") + } + name := parts[0] + + db := d.getDB(name) + + d.mu.Lock() + d.openCount++ + d.mu.Unlock() + conn := &fakeConn{db: db} + + if len(parts) >= 2 && parts[1] == "badConn" { + conn.bad = true + } + if d.waitCh != nil { + d.waitingCh <- struct{}{} + <-d.waitCh + d.waitCh = nil + d.waitingCh = nil + } + return conn, nil +} + +func (d *fakeDriver) getDB(name string) *fakeDB { + d.mu.Lock() + defer d.mu.Unlock() + if d.dbs == nil { + d.dbs = make(map[string]*fakeDB) + } + db, ok := d.dbs[name] + if !ok { + db = &fakeDB{name: name} + d.dbs[name] = db + } + return db +} + +func (db *fakeDB) wipe() { + db.mu.Lock() + defer db.mu.Unlock() + db.tables = nil +} + +func (db *fakeDB) createTable(name string, columnNames, columnTypes []string) error { + db.mu.Lock() + defer db.mu.Unlock() + if db.tables == nil { + db.tables = make(map[string]*table) + } + if _, exist := db.tables[name]; exist { + return fmt.Errorf("fakedb: table %q already exists", name) + } + if len(columnNames) != len(columnTypes) { + return fmt.Errorf("fakedb: create table of %q len(names) != len(types): %d vs %d", + name, len(columnNames), len(columnTypes)) + } + db.tables[name] = &table{colname: columnNames, coltype: columnTypes} + return nil +} + +// must be called with db.mu lock held +func (db *fakeDB) table(table string) (*table, bool) { + if db.tables == nil { + return nil, false + } + t, ok := db.tables[table] + return t, ok +} + +func (db *fakeDB) columnType(table, column string) (typ string, ok bool) { + db.mu.Lock() + defer db.mu.Unlock() + t, ok := db.table(table) + if !ok { + return + } + if i := slices.Index(t.colname, column); i != -1 { + return t.coltype[i], true + } + return "", false +} + +func (c *fakeConn) isBad() bool { + if c.stickyBad { + return true + } else if c.bad { + if c.db == nil { + return false + } + // alternate between bad conn and not bad conn + c.db.badConn = !c.db.badConn + return c.db.badConn + } else { + return false + } +} + +func (c *fakeConn) isDirtyAndMark() bool { + if c.skipDirtySession { + return false + } + if c.currTx != nil { + c.dirtySession = true + return false + } + if c.dirtySession { + return true + } + c.dirtySession = true + return false +} + +func (c *fakeConn) Begin() (driver.Tx, error) { + if c.isBad() { + return nil, fakeError{Wrapped: driver.ErrBadConn} + } + if c.currTx != nil { + return nil, errors.New("fakedb: already in a transaction") + } + c.touchMem() + c.currTx = &fakeTx{c: c} + return c.currTx, nil +} + +var hookPostCloseConn struct { + sync.Mutex + fn func(*fakeConn, error) +} + +func setHookpostCloseConn(fn func(*fakeConn, error)) { + hookPostCloseConn.Lock() + defer hookPostCloseConn.Unlock() + hookPostCloseConn.fn = fn +} + +var testStrictClose *testing.T + +// setStrictFakeConnClose sets the t to Errorf on when fakeConn.Close +// fails to close. If nil, the check is disabled. +func setStrictFakeConnClose(t *testing.T) { + testStrictClose = t +} + +func (c *fakeConn) ResetSession(ctx context.Context) error { + c.dirtySession = false + c.currTx = nil + if c.isBad() { + return fakeError{Message: "Reset Session: bad conn", Wrapped: driver.ErrBadConn} + } + return nil +} + +var _ driver.Validator = (*fakeConn)(nil) + +func (c *fakeConn) IsValid() bool { + return !c.isBad() +} + +func (c *fakeConn) Close() (err error) { + drv := fdriver.(*fakeDriver) + defer func() { + if err != nil && testStrictClose != nil { + testStrictClose.Errorf("failed to close a test fakeConn: %v", err) + } + hookPostCloseConn.Lock() + fn := hookPostCloseConn.fn + hookPostCloseConn.Unlock() + if fn != nil { + fn(c, err) + } + if err == nil { + drv.mu.Lock() + drv.closeCount++ + drv.mu.Unlock() + } + }() + c.touchMem() + if c.currTx != nil { + return errors.New("fakedb: can't close fakeConn; in a Transaction") + } + if c.db == nil { + return errors.New("fakedb: can't close fakeConn; already closed") + } + if c.stmtsMade > c.stmtsClosed { + return errors.New("fakedb: can't close; dangling statement(s)") + } + c.db = nil + return nil +} + +func checkSubsetTypes(allowAny bool, args []driver.NamedValue) error { + for _, arg := range args { + switch arg.Value.(type) { + case int64, float64, bool, nil, []byte, string, time.Time: + default: + if !allowAny { + return fmt.Errorf("fakedb: invalid argument ordinal %[1]d: %[2]v, type %[2]T", arg.Ordinal, arg.Value) + } + } + } + return nil +} + +func (c *fakeConn) Exec(query string, args []driver.Value) (driver.Result, error) { + // Ensure that ExecContext is called if available. + panic("ExecContext was not called.") +} + +func (c *fakeConn) ExecContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Result, error) { + // This is an optional interface, but it's implemented here + // just to check that all the args are of the proper types. + // ErrSkip is returned so the caller acts as if we didn't + // implement this at all. + err := checkSubsetTypes(c.db.allowAny, args) + if err != nil { + return nil, err + } + return nil, driver.ErrSkip +} + +func (c *fakeConn) Query(query string, args []driver.Value) (driver.Rows, error) { + // Ensure that ExecContext is called if available. + panic("QueryContext was not called.") +} + +func (c *fakeConn) QueryContext(ctx context.Context, query string, args []driver.NamedValue) (driver.Rows, error) { + // This is an optional interface, but it's implemented here + // just to check that all the args are of the proper types. + // ErrSkip is returned so the caller acts as if we didn't + // implement this at all. + err := checkSubsetTypes(c.db.allowAny, args) + if err != nil { + return nil, err + } + return nil, driver.ErrSkip +} + +func errf(msg string, args ...any) error { + return errors.New("fakedb: " + fmt.Sprintf(msg, args...)) +} + +// parts are table|selectCol1,selectCol2|whereCol=?,whereCol2=? +// (note that where columns must always contain ? marks, +// just a limitation for fakedb) +func (c *fakeConn) prepareSelect(stmt *fakeStmt, parts []string) (*fakeStmt, error) { + if len(parts) != 3 { + stmt.Close() + return nil, errf("invalid SELECT syntax with %d parts; want 3", len(parts)) + } + stmt.table = parts[0] + + stmt.colName = strings.Split(parts[1], ",") + for n, colspec := range strings.Split(parts[2], ",") { + if colspec == "" { + continue + } + nameVal := strings.Split(colspec, "=") + if len(nameVal) != 2 { + stmt.Close() + return nil, errf("SELECT on table %q has invalid column spec of %q (index %d)", stmt.table, colspec, n) + } + column, value := nameVal[0], nameVal[1] + _, ok := c.db.columnType(stmt.table, column) + if !ok { + stmt.Close() + return nil, errf("SELECT on table %q references non-existent column %q", stmt.table, column) + } + if !strings.HasPrefix(value, "?") { + stmt.Close() + return nil, errf("SELECT on table %q has pre-bound value for where column %q; need a question mark", + stmt.table, column) + } + stmt.placeholders++ + stmt.whereCol = append(stmt.whereCol, boundCol{Column: column, Placeholder: value, Ordinal: stmt.placeholders}) + } + return stmt, nil +} + +// parts are table|col=type,col2=type2 +func (c *fakeConn) prepareCreate(stmt *fakeStmt, parts []string) (*fakeStmt, error) { + if len(parts) != 2 { + stmt.Close() + return nil, errf("invalid CREATE syntax with %d parts; want 2", len(parts)) + } + stmt.table = parts[0] + for n, colspec := range strings.Split(parts[1], ",") { + nameType := strings.Split(colspec, "=") + if len(nameType) != 2 { + stmt.Close() + return nil, errf("CREATE table %q has invalid column spec of %q (index %d)", stmt.table, colspec, n) + } + stmt.colName = append(stmt.colName, nameType[0]) + stmt.colType = append(stmt.colType, nameType[1]) + } + return stmt, nil +} + +// parts are table|col=?,col2=val +func (c *fakeConn) prepareInsert(ctx context.Context, stmt *fakeStmt, parts []string) (*fakeStmt, error) { + if len(parts) != 2 { + stmt.Close() + return nil, errf("invalid INSERT syntax with %d parts; want 2", len(parts)) + } + stmt.table = parts[0] + for n, colspec := range strings.Split(parts[1], ",") { + nameVal := strings.Split(colspec, "=") + if len(nameVal) != 2 { + stmt.Close() + return nil, errf("INSERT table %q has invalid column spec of %q (index %d)", stmt.table, colspec, n) + } + column, value := nameVal[0], nameVal[1] + ctype, ok := c.db.columnType(stmt.table, column) + if !ok { + stmt.Close() + return nil, errf("INSERT table %q references non-existent column %q", stmt.table, column) + } + stmt.colName = append(stmt.colName, column) + + if !strings.HasPrefix(value, "?") { + var subsetVal any + // Convert to driver subset type + switch ctype { + case "string": + subsetVal = []byte(value) + case "blob": + subsetVal = []byte(value) + case "int32": + i, err := strconv.Atoi(value) + if err != nil { + stmt.Close() + return nil, errf("invalid conversion to int32 from %q", value) + } + subsetVal = int64(i) // int64 is a subset type, but not int32 + case "table": // For testing cursor reads. + c.skipDirtySession = true + vparts := strings.Split(value, "!") + + substmt, err := c.PrepareContext(ctx, fmt.Sprintf("SELECT|%s|%s|", vparts[0], strings.Join(vparts[1:], ","))) + if err != nil { + return nil, err + } + cursor, err := (substmt.(driver.StmtQueryContext)).QueryContext(ctx, []driver.NamedValue{}) + substmt.Close() + if err != nil { + return nil, err + } + subsetVal = cursor + default: + stmt.Close() + return nil, errf("unsupported conversion for pre-bound parameter %q to type %q", value, ctype) + } + stmt.colValue = append(stmt.colValue, subsetVal) + } else { + stmt.placeholders++ + stmt.placeholderConverter = append(stmt.placeholderConverter, converterForType(ctype)) + stmt.colValue = append(stmt.colValue, value) + } + } + return stmt, nil +} + +// hook to simulate broken connections +var hookPrepareBadConn func() bool + +func (c *fakeConn) Prepare(query string) (driver.Stmt, error) { + panic("use PrepareContext") +} + +func (c *fakeConn) PrepareContext(ctx context.Context, query string) (driver.Stmt, error) { + c.numPrepare++ + if c.db == nil { + panic("nil c.db; conn = " + fmt.Sprintf("%#v", c)) + } + + if c.stickyBad || (hookPrepareBadConn != nil && hookPrepareBadConn()) { + return nil, fakeError{Message: "Prepare: Sticky Bad", Wrapped: driver.ErrBadConn} + } + + c.touchMem() + var firstStmt, prev *fakeStmt + for _, query := range strings.Split(query, ";") { + parts := strings.Split(query, "|") + if len(parts) < 1 { + return nil, errf("empty query") + } + stmt := &fakeStmt{q: query, c: c, memToucher: c} + if firstStmt == nil { + firstStmt = stmt + } + if len(parts) >= 3 { + switch parts[0] { + case "PANIC": + stmt.panic = parts[1] + parts = parts[2:] + case "WAIT": + wait, err := time.ParseDuration(parts[1]) + if err != nil { + return nil, errf("expected section after WAIT to be a duration, got %q %v", parts[1], err) + } + parts = parts[2:] + stmt.wait = wait + } + } + cmd := parts[0] + stmt.cmd = cmd + parts = parts[1:] + + if c.waiter != nil { + c.waiter(ctx) + if err := ctx.Err(); err != nil { + return nil, err + } + } + + if stmt.wait > 0 { + wait := time.NewTimer(stmt.wait) + select { + case <-wait.C: + case <-ctx.Done(): + wait.Stop() + return nil, ctx.Err() + } + } + + c.incrStat(&c.stmtsMade) + var err error + switch cmd { + case "WIPE": + // Nothing + case "USE_RAWBYTES": + c.db.useRawBytes.Store(true) + case "SELECT": + stmt, err = c.prepareSelect(stmt, parts) + case "CREATE": + stmt, err = c.prepareCreate(stmt, parts) + case "INSERT": + stmt, err = c.prepareInsert(ctx, stmt, parts) + case "NOSERT": + // Do all the prep-work like for an INSERT but don't actually insert the row. + // Used for some of the concurrent tests. + stmt, err = c.prepareInsert(ctx, stmt, parts) + default: + stmt.Close() + return nil, errf("unsupported command type %q", cmd) + } + if err != nil { + return nil, err + } + if prev != nil { + prev.next = stmt + } + prev = stmt + } + return firstStmt, nil +} + +func (s *fakeStmt) ColumnConverter(idx int) driver.ValueConverter { + if s.panic == "ColumnConverter" { + panic(s.panic) + } + if len(s.placeholderConverter) == 0 { + return driver.DefaultParameterConverter + } + return s.placeholderConverter[idx] +} + +func (s *fakeStmt) Close() error { + if s.panic == "Close" { + panic(s.panic) + } + if s.c == nil { + panic("nil conn in fakeStmt.Close") + } + if s.c.db == nil { + panic("in fakeStmt.Close, conn's db is nil (already closed)") + } + s.touchMem() + if !s.closed { + s.c.incrStat(&s.c.stmtsClosed) + s.closed = true + } + if s.next != nil { + s.next.Close() + } + return nil +} + +var errClosed = errors.New("fakedb: statement has been closed") + +// hook to simulate broken connections +var hookExecBadConn func() bool + +func (s *fakeStmt) Exec(args []driver.Value) (driver.Result, error) { + panic("Using ExecContext") +} + +var errFakeConnSessionDirty = errors.New("fakedb: session is dirty") + +func (s *fakeStmt) ExecContext(ctx context.Context, args []driver.NamedValue) (driver.Result, error) { + if s.panic == "Exec" { + panic(s.panic) + } + if s.closed { + return nil, errClosed + } + + if s.c.stickyBad || (hookExecBadConn != nil && hookExecBadConn()) { + return nil, fakeError{Message: "Exec: Sticky Bad", Wrapped: driver.ErrBadConn} + } + if s.c.isDirtyAndMark() { + return nil, errFakeConnSessionDirty + } + + err := checkSubsetTypes(s.c.db.allowAny, args) + if err != nil { + return nil, err + } + s.touchMem() + + if s.wait > 0 { + time.Sleep(s.wait) + } + + select { + default: + case <-ctx.Done(): + return nil, ctx.Err() + } + + db := s.c.db + switch s.cmd { + case "WIPE": + db.wipe() + return driver.ResultNoRows, nil + case "USE_RAWBYTES": + s.c.db.useRawBytes.Store(true) + return driver.ResultNoRows, nil + case "CREATE": + if err := db.createTable(s.table, s.colName, s.colType); err != nil { + return nil, err + } + return driver.ResultNoRows, nil + case "INSERT": + return s.execInsert(args, true) + case "NOSERT": + // Do all the prep-work like for an INSERT but don't actually insert the row. + // Used for some of the concurrent tests. + return s.execInsert(args, false) + } + return nil, fmt.Errorf("fakedb: unimplemented statement Exec command type of %q", s.cmd) +} + +func valueFromPlaceholderName(args []driver.NamedValue, name string) driver.Value { + for i := range args { + if args[i].Name == name { + return args[i].Value + } + } + return nil +} + +// When doInsert is true, add the row to the table. +// When doInsert is false do prep-work and error checking, but don't +// actually add the row to the table. +func (s *fakeStmt) execInsert(args []driver.NamedValue, doInsert bool) (driver.Result, error) { + db := s.c.db + if len(args) != s.placeholders { + panic("error in pkg db; should only get here if size is correct") + } + db.mu.Lock() + t, ok := db.table(s.table) + db.mu.Unlock() + if !ok { + return nil, fmt.Errorf("fakedb: table %q doesn't exist", s.table) + } + + t.mu.Lock() + defer t.mu.Unlock() + + var cols []any + if doInsert { + cols = make([]any, len(t.colname)) + } + argPos := 0 + for n, colname := range s.colName { + colidx := t.columnIndex(colname) + if colidx == -1 { + return nil, fmt.Errorf("fakedb: column %q doesn't exist or dropped since prepared statement was created", colname) + } + var val any + if strvalue, ok := s.colValue[n].(string); ok && strings.HasPrefix(strvalue, "?") { + if strvalue == "?" { + val = args[argPos].Value + } else { + // Assign value from argument placeholder name. + if v := valueFromPlaceholderName(args, strvalue[1:]); v != nil { + val = v + } + } + argPos++ + } else { + val = s.colValue[n] + } + if doInsert { + cols[colidx] = val + } + } + + if doInsert { + t.rows = append(t.rows, &row{cols: cols}) + } + return driver.RowsAffected(1), nil +} + +// hook to simulate broken connections +var hookQueryBadConn func() bool + +func (s *fakeStmt) Query(args []driver.Value) (driver.Rows, error) { + panic("Use QueryContext") +} + +func (s *fakeStmt) QueryContext(ctx context.Context, args []driver.NamedValue) (driver.Rows, error) { + if s.panic == "Query" { + panic(s.panic) + } + if s.closed { + return nil, errClosed + } + + if s.c.stickyBad || (hookQueryBadConn != nil && hookQueryBadConn()) { + return nil, fakeError{Message: "Query: Sticky Bad", Wrapped: driver.ErrBadConn} + } + if s.c.isDirtyAndMark() { + return nil, errFakeConnSessionDirty + } + + err := checkSubsetTypes(s.c.db.allowAny, args) + if err != nil { + return nil, err + } + + s.touchMem() + db := s.c.db + if len(args) != s.placeholders { + panic("error in pkg db; should only get here if size is correct") + } + + setMRows := make([][]*row, 0, 1) + setColumns := make([][]string, 0, 1) + setColType := make([][]string, 0, 1) + + for { + db.mu.Lock() + t, ok := db.table(s.table) + db.mu.Unlock() + if !ok { + return nil, fmt.Errorf("fakedb: table %q doesn't exist", s.table) + } + + if s.table == "magicquery" { + if len(s.whereCol) == 2 && s.whereCol[0].Column == "op" && s.whereCol[1].Column == "millis" { + if args[0].Value == "sleep" { + time.Sleep(time.Duration(args[1].Value.(int64)) * time.Millisecond) + } + } + } + if s.table == "tx_status" && s.colName[0] == "tx_status" { + txStatus := "autocommit" + if s.c.currTx != nil { + txStatus = "transaction" + } + cursor := &rowsCursor{ + db: s.c.db, + parentMem: s.c, + posRow: -1, + rows: [][]*row{ + { + { + cols: []any{ + txStatus, + }, + }, + }, + }, + cols: [][]string{ + { + "tx_status", + }, + }, + colType: [][]string{ + { + "string", + }, + }, + errPos: -1, + } + return cursor, nil + } + + t.mu.Lock() + + colIdx := make(map[string]int) // select column name -> column index in table + for _, name := range s.colName { + idx := t.columnIndex(name) + if idx == -1 { + t.mu.Unlock() + return nil, fmt.Errorf("fakedb: unknown column name %q", name) + } + colIdx[name] = idx + } + + mrows := []*row{} + rows: + for _, trow := range t.rows { + // Process the where clause, skipping non-match rows. This is lazy + // and just uses fmt.Sprintf("%v") to test equality. Good enough + // for test code. + for _, wcol := range s.whereCol { + idx := t.columnIndex(wcol.Column) + if idx == -1 { + t.mu.Unlock() + return nil, fmt.Errorf("fakedb: invalid where clause column %q", wcol) + } + tcol := trow.cols[idx] + if bs, ok := tcol.([]byte); ok { + // lazy hack to avoid sprintf %v on a []byte + tcol = string(bs) + } + var argValue any + if wcol.Placeholder == "?" { + argValue = args[wcol.Ordinal-1].Value + } else { + if v := valueFromPlaceholderName(args, wcol.Placeholder[1:]); v != nil { + argValue = v + } + } + if fmt.Sprintf("%v", tcol) != fmt.Sprintf("%v", argValue) { + continue rows + } + } + mrow := &row{cols: make([]any, len(s.colName))} + for seli, name := range s.colName { + mrow.cols[seli] = trow.cols[colIdx[name]] + } + mrows = append(mrows, mrow) + } + + var colType []string + for _, column := range s.colName { + colType = append(colType, t.coltype[t.columnIndex(column)]) + } + + t.mu.Unlock() + + setMRows = append(setMRows, mrows) + setColumns = append(setColumns, s.colName) + setColType = append(setColType, colType) + + if s.next == nil { + break + } + s = s.next + } + + cursor := &rowsCursor{ + db: s.c.db, + parentMem: s.c, + posRow: -1, + rows: setMRows, + cols: setColumns, + colType: setColType, + errPos: -1, + } + return cursor, nil +} + +func (s *fakeStmt) NumInput() int { + if s.panic == "NumInput" { + panic(s.panic) + } + return s.placeholders +} + +// hook to simulate broken connections +var hookCommitBadConn func() bool + +func (tx *fakeTx) Commit() error { + tx.c.currTx = nil + if hookCommitBadConn != nil && hookCommitBadConn() { + return fakeError{Message: "Commit: Hook Bad Conn", Wrapped: driver.ErrBadConn} + } + tx.c.touchMem() + return nil +} + +// hook to simulate broken connections +var hookRollbackBadConn func() bool + +func (tx *fakeTx) Rollback() error { + tx.c.currTx = nil + if hookRollbackBadConn != nil && hookRollbackBadConn() { + return fakeError{Message: "Rollback: Hook Bad Conn", Wrapped: driver.ErrBadConn} + } + tx.c.touchMem() + return nil +} + +type rowsCursor struct { + db *fakeDB + parentMem memToucher + cols [][]string + colType [][]string + posSet int + posRow int + rows [][]*row + closed bool + + // errPos and err are for making Next return early with error. + errPos int + err error + + // a clone of slices to give out to clients, indexed by the + // original slice's first byte address. we clone them + // just so we're able to corrupt them on close. + bytesClone map[*byte][]byte + + // Every operation writes to line to enable the race detector + // check for data races. + // This is separate from the fakeConn.line to allow for drivers that + // can start multiple queries on the same transaction at the same time. + line int64 + + // closeErr is returned when rowsCursor.Close + closeErr error +} + +func (rc *rowsCursor) touchMem() { + rc.parentMem.touchMem() + rc.line++ +} + +func (rc *rowsCursor) Close() error { + rc.touchMem() + rc.parentMem.touchMem() + rc.closed = true + return rc.closeErr +} + +func (rc *rowsCursor) Columns() []string { + return rc.cols[rc.posSet] +} + +func (rc *rowsCursor) ColumnTypeScanType(index int) reflect.Type { + return colTypeToReflectType(rc.colType[rc.posSet][index]) +} + +var rowsCursorNextHook func(dest []driver.Value) error + +func (rc *rowsCursor) Next(dest []driver.Value) error { + if rowsCursorNextHook != nil { + return rowsCursorNextHook(dest) + } + + if rc.closed { + return errors.New("fakedb: cursor is closed") + } + rc.touchMem() + rc.posRow++ + if rc.posRow == rc.errPos { + return rc.err + } + if rc.posRow >= len(rc.rows[rc.posSet]) { + return io.EOF // per interface spec + } + for i, v := range rc.rows[rc.posSet][rc.posRow].cols { + // TODO(bradfitz): convert to subset types? naah, I + // think the subset types should only be input to + // driver, but the sql package should be able to handle + // a wider range of types coming out of drivers. all + // for ease of drivers, and to prevent drivers from + // messing up conversions or doing them differently. + dest[i] = v + + if bs, ok := v.([]byte); ok && !rc.db.useRawBytes.Load() { + if rc.bytesClone == nil { + rc.bytesClone = make(map[*byte][]byte) + } + clone, ok := rc.bytesClone[&bs[0]] + if !ok { + clone = make([]byte, len(bs)) + copy(clone, bs) + rc.bytesClone[&bs[0]] = clone + } + dest[i] = clone + } + } + return nil +} + +func (rc *rowsCursor) HasNextResultSet() bool { + rc.touchMem() + return rc.posSet < len(rc.rows)-1 +} + +func (rc *rowsCursor) NextResultSet() error { + rc.touchMem() + if rc.HasNextResultSet() { + rc.posSet++ + rc.posRow = -1 + return nil + } + return io.EOF // Per interface spec. +} + +// fakeDriverString is like driver.String, but indirects pointers like +// DefaultValueConverter. +// +// This could be surprising behavior to retroactively apply to +// driver.String now that Go1 is out, but this is convenient for +// our TestPointerParamsAndScans. +type fakeDriverString struct{} + +func (fakeDriverString) ConvertValue(v any) (driver.Value, error) { + switch c := v.(type) { + case string, []byte: + return v, nil + case *string: + if c == nil { + return nil, nil + } + return *c, nil + } + return fmt.Sprintf("%v", v), nil +} + +type anyTypeConverter struct{} + +func (anyTypeConverter) ConvertValue(v any) (driver.Value, error) { + return v, nil +} + +func converterForType(typ string) driver.ValueConverter { + switch typ { + case "bool": + return driver.Bool + case "nullbool": + return driver.Null{Converter: driver.Bool} + case "byte", "int16": + return driver.NotNull{Converter: driver.DefaultParameterConverter} + case "int32": + return driver.Int32 + case "nullbyte", "nullint32", "nullint16": + return driver.Null{Converter: driver.DefaultParameterConverter} + case "string": + return driver.NotNull{Converter: fakeDriverString{}} + case "nullstring": + return driver.Null{Converter: fakeDriverString{}} + case "int64": + // TODO(coopernurse): add type-specific converter + return driver.NotNull{Converter: driver.DefaultParameterConverter} + case "nullint64": + // TODO(coopernurse): add type-specific converter + return driver.Null{Converter: driver.DefaultParameterConverter} + case "float64": + // TODO(coopernurse): add type-specific converter + return driver.NotNull{Converter: driver.DefaultParameterConverter} + case "nullfloat64": + // TODO(coopernurse): add type-specific converter + return driver.Null{Converter: driver.DefaultParameterConverter} + case "datetime": + return driver.NotNull{Converter: driver.DefaultParameterConverter} + case "nulldatetime": + return driver.Null{Converter: driver.DefaultParameterConverter} + case "any": + return anyTypeConverter{} + } + panic("invalid fakedb column type of " + typ) +} + +func colTypeToReflectType(typ string) reflect.Type { + switch typ { + case "bool": + return reflect.TypeFor[bool]() + case "nullbool": + return reflect.TypeFor[sql.NullBool]() + case "int16": + return reflect.TypeFor[int16]() + case "nullint16": + return reflect.TypeFor[sql.NullInt16]() + case "int32": + return reflect.TypeFor[int32]() + case "nullint32": + return reflect.TypeFor[sql.NullInt32]() + case "string": + return reflect.TypeFor[string]() + case "nullstring": + return reflect.TypeFor[sql.NullString]() + case "int64": + return reflect.TypeFor[int64]() + case "nullint64": + return reflect.TypeFor[sql.NullInt64]() + case "float64": + return reflect.TypeFor[float64]() + case "nullfloat64": + return reflect.TypeFor[sql.NullFloat64]() + case "datetime": + return reflect.TypeFor[time.Time]() + case "any": + return reflect.TypeFor[any]() + } + panic("invalid fakedb column type of " + typ) +} diff --git a/sentrysql/operation.go b/sentrysql/operation.go index d08777f9..1f339131 100644 --- a/sentrysql/operation.go +++ b/sentrysql/operation.go @@ -2,7 +2,12 @@ package sentrysql import "strings" -var knownDatabaseOperations = []string{"SELECT", "INSERT", "DELETE", "UPDATE"} +var knownDatabaseOperations = map[string]struct{}{ + "SELECT": struct{}{}, + "INSERT": struct{}{}, + "DELETE": struct{}{}, + "UPDATE": struct{}{}, +} func parseDatabaseOperation(query string) string { // The operation is the first word of the query. @@ -12,11 +17,9 @@ func parseDatabaseOperation(query string) string { } // Only returns known words. - for _, knownOperation := range knownDatabaseOperations { - if operation == knownOperation { - return operation - } + if _, ok := knownDatabaseOperations[operation]; !ok { + return "" } - return "" + return operation } diff --git a/sentrysql/sentrysql.go b/sentrysql/sentrysql.go index c1203174..24363451 100644 --- a/sentrysql/sentrysql.go +++ b/sentrysql/sentrysql.go @@ -13,11 +13,16 @@ import ( type DatabaseSystem string const ( + // PostgreSQL specifies the PostgreSQL database system. PostgreSQL DatabaseSystem = "postgresql" - MySQL DatabaseSystem = "mysql" - SQLite DatabaseSystem = "sqlite" - Oracle DatabaseSystem = "oracle" - MSSQL DatabaseSystem = "mssql" + // MySQL specifies the MySQL database system. + MySQL DatabaseSystem = "mysql" + // SQLite specifies the SQLite database system. + SQLite DatabaseSystem = "sqlite" + // Oracle specifies the Oracle database system. + Oracle DatabaseSystem = "oracle" + // MSSQL specifies the Microsoft SQL Server database system. + MSSQL DatabaseSystem = "mssql" ) type sentrySQLConfig struct { diff --git a/sentrysql/sentrysql_connector_test.go b/sentrysql/sentrysql_connector_test.go new file mode 100644 index 00000000..60c92262 --- /dev/null +++ b/sentrysql/sentrysql_connector_test.go @@ -0,0 +1,1289 @@ +package sentrysql_test + +import ( + "context" + "database/sql" + "strings" + "testing" + "time" + + "github.com/getsentry/sentry-go" + "github.com/getsentry/sentry-go/internal/testutils" + "github.com/getsentry/sentry-go/sentrysql" + "github.com/google/go-cmp/cmp" +) + +//nolint:dupl +func TestNewSentrySQLConnector_Integration(t *testing.T) { + db := sql.OpenDB(sentrysql.NewSentrySQLConnector(&fakeConnector{}, sentrysql.WithDatabaseSystem(sentrysql.DatabaseSystem("fakedb")), sentrysql.WithDatabaseName("fake"))) + t.Cleanup(func() { + _, _ = db.Exec("WIPE") + _ = db.Close() + }) + setupQueries := []string{ + "CREATE|exec_test|id=int32,name=string", + "CREATE|query_test|id=int32,name=string,age=int32,created_at=string", + "INSERT|query_test|id=1,name=John,age=30,created_at=2023-01-01", + "INSERT|query_test|id=2,name=Jane,age=25,created_at=2023-01-02", + "INSERT|query_test|id=3,name=Bob,age=35,created_at=2023-01-03", + } + + setupCtx, cancelCtx := context.WithTimeout(context.Background(), 30*time.Second) + defer cancelCtx() + + for _, query := range setupQueries { + _, err := db.ExecContext(setupCtx, query) + if err != nil { + t.Fatalf("initializing table on sqlite: %v", err) + } + } + + t.Run("QueryContext", func(t *testing.T) { + tests := []struct { + Query string + Parameters []interface{} + WantSpan *sentry.Span + WantError bool + }{ + { + Query: "SELECT|query_test|id|id=?", + Parameters: []interface{}{1}, + WantSpan: &sentry.Span{ + Data: map[string]interface{}{ + "db.system": sentrysql.DatabaseSystem("fakedb"), + "db.name": "fake", + }, + Description: "SELECT|query_test|id|id=?", + Op: "db.sql.query", + Tags: nil, + Origin: "manual", + Sampled: sentry.SampledTrue, + Status: sentry.SpanStatusOK, + }, + }, + { + Query: "SELECT FROM query_test", + WantError: true, + WantSpan: &sentry.Span{ + Data: map[string]interface{}{ + "db.system": sentrysql.DatabaseSystem("fakedb"), + "db.name": "fake", + "db.operation": "SELECT", + }, + Description: "SELECT FROM query_test", + Op: "db.sql.query", + Tags: nil, + Origin: "manual", + Sampled: sentry.SampledTrue, + Status: sentry.SpanStatusInternalError, + }, + }, + } + + spansCh := make(chan []*sentry.Span, len(tests)) + + sentryClient, err := sentry.NewClient(sentry.ClientOptions{ + EnableTracing: true, + TracesSampleRate: 1.0, + BeforeSendTransaction: func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event { + spansCh <- event.Spans + return event + }, + }) + if err != nil { + t.Fatal(err) + } + + for _, tt := range tests { + hub := sentry.NewHub(sentryClient, sentry.NewScope()) + ctx, cancel := context.WithTimeout(sentry.SetHubOnContext(context.Background(), hub), 10*time.Second) + span := sentry.StartSpan(ctx, "fake_parent", sentry.WithTransactionName("Fake Parent")) + ctx = span.Context() + + rows, err := db.QueryContext(ctx, tt.Query, tt.Parameters...) + if err != nil && !tt.WantError { + cancel() + t.Fatal(err) + } + + if rows != nil { + _ = rows.Close() + } + + span.Finish() + cancel() + } + + if ok := sentryClient.Flush(testutils.FlushTimeout()); !ok { + t.Fatal("sentry.Flush timed out") + } + close(spansCh) + + var got [][]*sentry.Span + for e := range spansCh { + got = append(got, e) + } + + for i, tt := range tests { + var foundMatch = false + gotSpans := got[i] + + var diffs []string + for _, gotSpan := range gotSpans { + if diff := cmp.Diff(tt.WantSpan, gotSpan, optstrans); diff != "" { + diffs = append(diffs, diff) + } else { + foundMatch = true + break + } + } + + if !foundMatch { + t.Errorf("Span mismatch (-want +got):\n%s", strings.Join(diffs, "\n")) + } + } + }) + + t.Run("ExecContext", func(t *testing.T) { + tests := []struct { + Query string + Parameters []interface{} + WantSpan *sentry.Span + WantError bool + }{ + { + Query: "INSERT|exec_test|id=?,name=?", + Parameters: []interface{}{1, "John"}, + WantSpan: &sentry.Span{ + Data: map[string]interface{}{ + "db.system": sentrysql.DatabaseSystem("fakedb"), + "db.name": "fake", + }, + Description: "INSERT|exec_test|id=?,name=?", + Op: "db.sql.exec", + Tags: nil, + Origin: "manual", + Sampled: sentry.SampledTrue, + Status: sentry.SpanStatusOK, + }, + }, + { + Query: "CREATE|temporary_test|id=int32,name=string", + WantError: false, + WantSpan: &sentry.Span{ + Data: map[string]interface{}{ + "db.system": sentrysql.DatabaseSystem("fakedb"), + "db.name": "fake", + }, + Description: "CREATE|temporary_test|id=int32,name=string", + Op: "db.sql.exec", + Tags: nil, + Origin: "manual", + Sampled: sentry.SampledTrue, + Status: sentry.SpanStatusOK, + }, + }, + } + + spansCh := make(chan []*sentry.Span, len(tests)) + + sentryClient, err := sentry.NewClient(sentry.ClientOptions{ + EnableTracing: true, + TracesSampleRate: 1.0, + BeforeSendTransaction: func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event { + spansCh <- event.Spans + return event + }, + }) + if err != nil { + t.Fatal(err) + } + + for _, tt := range tests { + hub := sentry.NewHub(sentryClient, sentry.NewScope()) + ctx, cancel := context.WithTimeout(sentry.SetHubOnContext(context.Background(), hub), 10*time.Second) + span := sentry.StartSpan(ctx, "fake_parent", sentry.WithTransactionName("Fake Parent")) + ctx = span.Context() + + _, err := db.ExecContext(ctx, tt.Query, tt.Parameters...) + if err != nil && !tt.WantError { + cancel() + t.Fatal(err) + } + + span.Finish() + cancel() + } + + if ok := sentryClient.Flush(testutils.FlushTimeout()); !ok { + t.Fatal("sentry.Flush timed out") + } + close(spansCh) + + var got [][]*sentry.Span + for e := range spansCh { + got = append(got, e) + } + + for i, tt := range tests { + var foundMatch = false + gotSpans := got[i] + + var diffs []string + for _, gotSpan := range gotSpans { + if diff := cmp.Diff(tt.WantSpan, gotSpan, optstrans); diff != "" { + diffs = append(diffs, diff) + } else { + foundMatch = true + break + } + } + + if !foundMatch { + t.Errorf("Span mismatch (-want +got):\n%s", strings.Join(diffs, "\n")) + } + } + }) + + t.Run("Ping", func(t *testing.T) { + // Just checking if this works and doesn't panic + err := db.Ping() + if err != nil { + t.Fatal(err) + } + }) + + t.Run("PingContext", func(t *testing.T) { + // Just checking if this works and doesn't panic + err := db.PingContext(context.Background()) + if err != nil { + t.Fatal(err) + } + }) + + t.Run("Driver", func(t *testing.T) { + // Just checking if this works and doesn't panic + driver := db.Driver() + if driver == nil { + t.Fatal("driver is nil") + } + }) +} + +//nolint:dupl +func TestNewSentrySQLConnector_Conn(t *testing.T) { + db := sql.OpenDB(sentrysql.NewSentrySQLConnector(&fakeConnector{}, sentrysql.WithDatabaseSystem(sentrysql.DatabaseSystem("fakedb")), sentrysql.WithDatabaseName("fake"))) + t.Cleanup(func() { + _, _ = db.Exec("WIPE") + _ = db.Close() + }) + + setupQueries := []string{ + "CREATE|exec_test|id=int32,name=string", + "CREATE|query_test|id=int32,name=string,age=int32,created_at=string", + "INSERT|query_test|id=1,name=John,age=30,created_at=2023-01-01", + "INSERT|query_test|id=2,name=Jane,age=25,created_at=2023-01-02", + "INSERT|query_test|id=3,name=Bob,age=35,created_at=2023-01-03", + } + setupCtx, cancelCtx := context.WithTimeout(context.Background(), 30*time.Second) + defer cancelCtx() + + for _, query := range setupQueries { + _, err := db.ExecContext(setupCtx, query) + if err != nil { + t.Fatalf("initializing table on sqlite: %v", err) + } + } + + t.Run("QueryContext", func(t *testing.T) { + tests := []struct { + Query string + Parameters []interface{} + WantSpan *sentry.Span + WantError bool + }{ + { + Query: "SELECT|query_test|id|id=?", + Parameters: []interface{}{1}, + WantSpan: &sentry.Span{ + Data: map[string]interface{}{ + "db.system": sentrysql.DatabaseSystem("fakedb"), + "db.name": "fake", + }, + Description: "SELECT|query_test|id|id=?", + Op: "db.sql.query", + Tags: nil, + Origin: "manual", + Sampled: sentry.SampledTrue, + Status: sentry.SpanStatusOK, + }, + }, + { + Query: "SELECT FROM query_test", + Parameters: []interface{}{1}, + WantError: true, + WantSpan: &sentry.Span{ + Data: map[string]interface{}{ + "db.system": sentrysql.DatabaseSystem("fakedb"), + "db.name": "fake", + "db.operation": "SELECT", + }, + Description: "SELECT FROM query_test", + Op: "db.sql.query", + Tags: nil, + Origin: "manual", + Sampled: sentry.SampledTrue, + Status: sentry.SpanStatusInternalError, + }, + }, + } + + spansCh := make(chan []*sentry.Span, len(tests)) + + sentryClient, err := sentry.NewClient(sentry.ClientOptions{ + EnableTracing: true, + TracesSampleRate: 1.0, + BeforeSendTransaction: func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event { + spansCh <- event.Spans + return event + }, + }) + if err != nil { + t.Fatal(err) + } + + for _, tt := range tests { + hub := sentry.NewHub(sentryClient, sentry.NewScope()) + ctx, cancel := context.WithTimeout(sentry.SetHubOnContext(context.Background(), hub), 10*time.Second) + span := sentry.StartSpan(ctx, "fake_parent", sentry.WithTransactionName("Fake Parent")) + ctx = span.Context() + + conn, err := db.Conn(ctx) + if err != nil { + cancel() + t.Fatal(err) + } + + rows, err := conn.QueryContext(ctx, tt.Query, tt.Parameters...) + if err != nil && !tt.WantError { + _ = conn.Close() + cancel() + t.Fatal(err) + } + + if rows != nil { + _ = rows.Close() + } + + _ = conn.Close() + + span.Finish() + cancel() + } + + if ok := sentryClient.Flush(testutils.FlushTimeout()); !ok { + t.Fatal("sentry.Flush timed out") + } + close(spansCh) + + var got [][]*sentry.Span + for e := range spansCh { + got = append(got, e) + } + + for i, tt := range tests { + var foundMatch = false + gotSpans := got[i] + + var diffs []string + for _, gotSpan := range gotSpans { + if diff := cmp.Diff(tt.WantSpan, gotSpan, optstrans); diff != "" { + diffs = append(diffs, diff) + } else { + foundMatch = true + break + } + } + + if !foundMatch { + t.Errorf("Span mismatch (-want +got):\n%s", strings.Join(diffs, "\n")) + } + } + }) + + t.Run("ExecContext", func(t *testing.T) { + tests := []struct { + Query string + Parameters []interface{} + WantSpan *sentry.Span + WantError bool + }{ + { + Query: "INSERT|exec_test|id=?,name=?", + Parameters: []interface{}{2, "Peter"}, + WantSpan: &sentry.Span{ + Data: map[string]interface{}{ + "db.system": sentrysql.DatabaseSystem("fakedb"), + "db.name": "fake", + }, + Description: "INSERT|exec_test|id=?,name=?", + Op: "db.sql.exec", + Tags: nil, + Origin: "manual", + Sampled: sentry.SampledTrue, + Status: sentry.SpanStatusOK, + }, + }, + { + Query: "INSERT|exec_test|id=?,name=?", + Parameters: []interface{}{4, "John", "Doe", "John Doe"}, + WantError: true, + WantSpan: &sentry.Span{ + Data: map[string]interface{}{ + "db.system": sentrysql.DatabaseSystem("fakedb"), + "db.name": "fake", + }, + Description: "INSERT|exec_test|id=?,name=?", + Op: "db.sql.exec", + Tags: nil, + Origin: "manual", + Sampled: sentry.SampledTrue, + Status: sentry.SpanStatusInternalError, + }, + }, + } + + spansCh := make(chan []*sentry.Span, len(tests)) + + sentryClient, err := sentry.NewClient(sentry.ClientOptions{ + EnableTracing: true, + TracesSampleRate: 1.0, + BeforeSendTransaction: func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event { + spansCh <- event.Spans + return event + }, + }) + if err != nil { + t.Fatal(err) + } + + for _, tt := range tests { + hub := sentry.NewHub(sentryClient, sentry.NewScope()) + ctx, cancel := context.WithTimeout(sentry.SetHubOnContext(context.Background(), hub), 10*time.Second) + span := sentry.StartSpan(ctx, "fake_parent", sentry.WithTransactionName("Fake Parent")) + ctx = span.Context() + + conn, err := db.Conn(ctx) + if err != nil { + cancel() + t.Fatal(err) + } + + _, err = conn.ExecContext(ctx, tt.Query, tt.Parameters...) + if err != nil && !tt.WantError { + _ = conn.Close() + cancel() + t.Fatal(err) + } + + _ = conn.Close() + + span.Finish() + cancel() + } + + if ok := sentryClient.Flush(testutils.FlushTimeout()); !ok { + t.Fatal("sentry.Flush timed out") + } + close(spansCh) + + var got [][]*sentry.Span + for e := range spansCh { + got = append(got, e) + } + + for i, tt := range tests { + var foundMatch = false + gotSpans := got[i] + + var diffs []string + for _, gotSpan := range gotSpans { + if diff := cmp.Diff(tt.WantSpan, gotSpan, optstrans); diff != "" { + diffs = append(diffs, diff) + } else { + foundMatch = true + break + } + } + + if !foundMatch { + t.Errorf("Span mismatch (-want +got):\n%s", strings.Join(diffs, "\n")) + } + } + }) +} + +//nolint:dupl,gocyclo +func TestNewSentrySQLConnector_BeginTx(t *testing.T) { + t.Skip("fakedb does not implement transactions") + + db := sql.OpenDB(sentrysql.NewSentrySQLConnector(&fakeConnector{}, sentrysql.WithDatabaseSystem(sentrysql.DatabaseSystem("fakedb")), sentrysql.WithDatabaseName("fake"))) + t.Cleanup(func() { + _, _ = db.Exec("WIPE") + _ = db.Close() + }) + + setupQueries := []string{ + "CREATE|exec_test|id=int32,name=string", + "CREATE|query_test|id=int32,name=string,age=int32,created_at=string", + "INSERT|query_test|id=1,name=John,age=30,created_at=2023-01-01", + "INSERT|query_test|id=2,name=Jane,age=25,created_at=2023-01-02", + "INSERT|query_test|id=3,name=Bob,age=35,created_at=2023-01-03", + } + + setupCtx, cancelCtx := context.WithTimeout(context.Background(), 30*time.Second) + defer cancelCtx() + + for _, query := range setupQueries { + _, err := db.ExecContext(setupCtx, query) + if err != nil { + t.Fatalf("initializing table on sqlite: %v", err) + } + } + + t.Run("Singles", func(t *testing.T) { + tests := []struct { + Query string + Parameters []interface{} + WantSpan *sentry.Span + WantError bool + }{ + { + Query: "INSERT|exec_test|id=?,name=?", + Parameters: []interface{}{2, "Peter"}, + WantSpan: &sentry.Span{ + Data: map[string]interface{}{ + "db.system": sentrysql.DatabaseSystem("fakedb"), + "db.name": "fake", + }, + Description: "INSERT|exec_test|id=?,name=?", + Op: "db.sql.exec", + Tags: nil, + Origin: "manual", + Sampled: sentry.SampledTrue, + Status: sentry.SpanStatusOK, + }, + }, + { + Query: "INSERT|exec_test|id=?,name=?", + Parameters: []interface{}{4, "John", "Doe", "John Doe"}, + WantError: true, + WantSpan: &sentry.Span{ + Data: map[string]interface{}{ + "db.system": sentrysql.DatabaseSystem("fakedb"), + "db.name": "fake", + }, + Description: "INSERT|exec_test|id=?,name=?", + Op: "db.sql.exec", + Tags: nil, + Origin: "manual", + Sampled: sentry.SampledTrue, + Status: sentry.SpanStatusInternalError, + }, + }, + } + + spansCh := make(chan []*sentry.Span, len(tests)) + + sentryClient, err := sentry.NewClient(sentry.ClientOptions{ + EnableTracing: true, + TracesSampleRate: 1.0, + BeforeSendTransaction: func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event { + spansCh <- event.Spans + return event + }, + }) + if err != nil { + t.Fatal(err) + } + + for _, tt := range tests { + hub := sentry.NewHub(sentryClient, sentry.NewScope()) + ctx, cancel := context.WithTimeout(sentry.SetHubOnContext(context.Background(), hub), 10*time.Second) + span := sentry.StartSpan(ctx, "fake_parent", sentry.WithTransactionName("Fake Parent")) + ctx = span.Context() + + conn, err := db.Conn(ctx) + if err != nil { + cancel() + t.Fatal(err) + } + + tx, err := conn.BeginTx(ctx, nil) + if err != nil { + cancel() + t.Fatal(err) + } + + _, err = tx.ExecContext(ctx, tt.Query, tt.Parameters...) + if err != nil && !tt.WantError { + _ = conn.Close() + cancel() + t.Fatal(err) + } + + err = tx.Commit() + if err != nil && !tt.WantError { + _ = conn.Close() + cancel() + t.Fatal(err) + } + + _ = tx.Rollback() + + _ = conn.Close() + + span.Finish() + cancel() + } + + if ok := sentryClient.Flush(testutils.FlushTimeout()); !ok { + t.Fatal("sentry.Flush timed out") + } + close(spansCh) + + var got [][]*sentry.Span + for e := range spansCh { + got = append(got, e) + } + + for i, tt := range tests { + var foundMatch = false + gotSpans := got[i] + + var diffs []string + for _, gotSpan := range gotSpans { + if diff := cmp.Diff(tt.WantSpan, gotSpan, optstrans); diff != "" { + diffs = append(diffs, diff) + } else { + foundMatch = true + break + } + } + + if !foundMatch { + t.Errorf("Span mismatch (-want +got):\n%s", strings.Join(diffs, "\n")) + } + } + }) + + t.Run("Multiple Queries", func(t *testing.T) { + spansCh := make(chan []*sentry.Span, 2) + + sentryClient, err := sentry.NewClient(sentry.ClientOptions{ + EnableTracing: true, + TracesSampleRate: 1.0, + BeforeSendTransaction: func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event { + spansCh <- event.Spans + return event + }, + }) + if err != nil { + t.Fatal(err) + } + + hub := sentry.NewHub(sentryClient, sentry.NewScope()) + ctx, cancel := context.WithTimeout(sentry.SetHubOnContext(context.Background(), hub), 10*time.Second) + defer cancel() + span := sentry.StartSpan(ctx, "fake_parent", sentry.WithTransactionName("Fake Parent")) + ctx = span.Context() + + conn, err := db.Conn(ctx) + if err != nil { + t.Fatal(err) + } + + tx, err := conn.BeginTx(ctx, nil) + if err != nil { + t.Fatal(err) + } + defer func() { + _ = tx.Rollback() + }() + + var name string + err = tx.QueryRowContext(ctx, "SELECT|query_test|name|id=?", 1).Scan(&name) + if err != nil { + _ = tx.Rollback() + _ = conn.Close() + cancel() + t.Fatal(err) + } + + _, err = tx.ExecContext(ctx, "INSERT|exec_test|id=?,name=?", 5, "Catherine") + if err != nil { + _ = tx.Rollback() + _ = conn.Close() + cancel() + t.Fatal(err) + } + + err = tx.Commit() + if err != nil { + _ = conn.Close() + cancel() + t.Fatal(err) + } + + _ = conn.Close() + + span.Finish() + + cancel() + + if ok := sentryClient.Flush(testutils.FlushTimeout()); !ok { + t.Fatal("sentry.Flush timed out") + } + close(spansCh) + + var got []*sentry.Span + for e := range spansCh { + got = append(got, e...) + } + + want := []*sentry.Span{ + { + Data: map[string]interface{}{ + "db.system": sentrysql.DatabaseSystem("fakedb"), + "db.name": "fake", + }, + Description: "SELECT|query_test|name|id=?", + Op: "db.sql.query", + Tags: nil, + Origin: "manual", + Sampled: sentry.SampledTrue, + Status: sentry.SpanStatusOK, + }, + { + Data: map[string]interface{}{ + "db.system": sentrysql.DatabaseSystem("fakedb"), + "db.name": "fake", + }, + Description: "INSERT|exec_test|id=?,name=?", + Op: "db.sql.exec", + Tags: nil, + Origin: "manual", + Sampled: sentry.SampledTrue, + Status: sentry.SpanStatusOK, + }, + } + + if diff := cmp.Diff(want, got, optstrans); diff != "" { + t.Errorf("Span mismatch (-want +got):\n%s", diff) + } + }) + + t.Run("Rollback", func(t *testing.T) { + spansCh := make(chan []*sentry.Span, 2) + + sentryClient, err := sentry.NewClient(sentry.ClientOptions{ + EnableTracing: true, + TracesSampleRate: 1.0, + BeforeSendTransaction: func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event { + spansCh <- event.Spans + return event + }, + }) + if err != nil { + t.Fatal(err) + } + + hub := sentry.NewHub(sentryClient, sentry.NewScope()) + ctx, cancel := context.WithTimeout(sentry.SetHubOnContext(context.Background(), hub), 10*time.Second) + defer cancel() + span := sentry.StartSpan(ctx, "fake_parent", sentry.WithTransactionName("Fake Parent")) + ctx = span.Context() + + conn, err := db.Conn(ctx) + if err != nil { + t.Fatal(err) + } + + tx, err := conn.BeginTx(ctx, nil) + if err != nil { + t.Fatal(err) + } + defer func() { + _ = tx.Rollback() + }() + + var name string + err = tx.QueryRowContext(ctx, "SELECT|query_test|name|id=?", 1).Scan(&name) + if err != nil { + _ = tx.Rollback() + _ = conn.Close() + cancel() + t.Fatal(err) + } + + _, err = tx.ExecContext(ctx, "INSERT|exec_test|id=?,name=?", 5, "Catherine") + if err != nil { + _ = tx.Rollback() + _ = conn.Close() + cancel() + t.Fatal(err) + } + + err = tx.Rollback() + if err != nil { + _ = conn.Close() + cancel() + t.Fatal(err) + } + + _ = conn.Close() + + span.Finish() + + cancel() + + if ok := sentryClient.Flush(testutils.FlushTimeout()); !ok { + t.Fatal("sentry.Flush timed out") + } + close(spansCh) + + var got []*sentry.Span + for e := range spansCh { + got = append(got, e...) + } + + want := []*sentry.Span{ + { + Data: map[string]interface{}{ + "db.system": sentrysql.DatabaseSystem("fakedb"), + "db.name": "fake", + }, + Description: "SELECT|query_test|name|id=?", + Op: "db.sql.query", + Tags: nil, + Origin: "manual", + Sampled: sentry.SampledTrue, + Status: sentry.SpanStatusOK, + }, + { + Data: map[string]interface{}{ + "db.system": sentrysql.DatabaseSystem("fakedb"), + "db.name": "fake", + }, + Description: "INSERT|exec_test|id=?,name=?", + Op: "db.sql.exec", + Tags: nil, + Origin: "manual", + Sampled: sentry.SampledTrue, + Status: sentry.SpanStatusOK, + }, + } + + if diff := cmp.Diff(want, got, optstrans); diff != "" { + t.Errorf("Span mismatch (-want +got):\n%s", diff) + } + }) +} + +//nolint:dupl +func TestNewSentrySQLConnector_PrepareContext(t *testing.T) { + db := sql.OpenDB(sentrysql.NewSentrySQLConnector(&fakeConnector{}, sentrysql.WithDatabaseSystem(sentrysql.DatabaseSystem("fakedb")), sentrysql.WithDatabaseName("fake"))) + t.Cleanup(func() { + _, _ = db.Exec("WIPE") + _ = db.Close() + }) + + setupQueries := []string{ + "CREATE|exec_test|id=int32,name=string", + "CREATE|query_test|id=int32,name=string,age=int32,created_at=string", + "INSERT|query_test|id=1,name=John,age=30,created_at=2023-01-01", + "INSERT|query_test|id=2,name=Jane,age=25,created_at=2023-01-02", + "INSERT|query_test|id=3,name=Bob,age=35,created_at=2023-01-03", + } + setupCtx, cancelCtx := context.WithTimeout(context.Background(), 30*time.Second) + defer cancelCtx() + + for _, query := range setupQueries { + _, err := db.ExecContext(setupCtx, query) + if err != nil { + t.Fatalf("initializing table on sqlite: %v", err) + } + } + + t.Run("Exec", func(t *testing.T) { + t.Skip("fakedb does not implement Exec") + + tests := []struct { + Query string + Parameters []interface{} + WantSpan *sentry.Span + WantError bool + }{ + { + Query: "INSERT|exec_test|id=?,name=?", + Parameters: []interface{}{3, "Sarah"}, + WantSpan: &sentry.Span{ + Data: map[string]interface{}{ + "db.system": sentrysql.DatabaseSystem("fakedb"), + "db.name": "fake", + }, + Description: "INSERT|exec_test|id=?,name=?", + Op: "db.sql.exec", + Tags: nil, + Origin: "manual", + Sampled: sentry.SampledTrue, + Status: sentry.SpanStatusOK, + }, + }, + { + Query: "INSERT exec_test (id, name) VALUES (?, ?, ?, ?)", + Parameters: []interface{}{4, "John", "Doe", "John Doe"}, + WantError: true, + WantSpan: &sentry.Span{ + Data: map[string]interface{}{ + "db.system": sentrysql.DatabaseSystem("fakedb"), + "db.name": "fake", + "db.operation": "INSERT", + }, + Description: "INSERT INTO exec_test (id, name) VALUES (?, ?, ?, ?)", + Op: "db.sql.exec", + Tags: nil, + Origin: "manual", + Sampled: sentry.SampledTrue, + Status: sentry.SpanStatusInternalError, + }, + }, + } + + spansCh := make(chan []*sentry.Span, len(tests)) + + sentryClient, err := sentry.NewClient(sentry.ClientOptions{ + EnableTracing: true, + TracesSampleRate: 1.0, + BeforeSendTransaction: func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event { + spansCh <- event.Spans + return event + }, + }) + if err != nil { + t.Fatal(err) + } + + for _, tt := range tests { + hub := sentry.NewHub(sentryClient, sentry.NewScope()) + ctx, cancel := context.WithTimeout(sentry.SetHubOnContext(context.Background(), hub), 10*time.Second) + span := sentry.StartSpan(ctx, "fake_parent", sentry.WithTransactionName("Fake Parent")) + ctx = span.Context() + + stmt, err := db.PrepareContext(ctx, tt.Query) + if err != nil && !tt.WantError { + cancel() + t.Fatal(err) + } + + _, err = stmt.Exec(tt.Parameters...) + if err != nil && !tt.WantError { + cancel() + t.Fatal(err) + } + + span.Finish() + cancel() + } + + if ok := sentryClient.Flush(testutils.FlushTimeout()); !ok { + t.Fatal("sentry.Flush timed out") + } + close(spansCh) + + var got [][]*sentry.Span + for e := range spansCh { + got = append(got, e) + } + + for i, tt := range tests { + var foundMatch = false + gotSpans := got[i] + + var diffs []string + for _, gotSpan := range gotSpans { + if diff := cmp.Diff(tt.WantSpan, gotSpan, optstrans); diff != "" { + diffs = append(diffs, diff) + } else { + foundMatch = true + break + } + } + + if !foundMatch { + t.Errorf("Span mismatch (-want +got):\n%s", strings.Join(diffs, "\n")) + } + } + }) + + t.Run("Query", func(t *testing.T) { + t.Skip("fakedb does not implement Query") + + tests := []struct { + Query string + Parameters []interface{} + WantSpan *sentry.Span + WantError bool + }{ + { + Query: "SELECT|query_test|id,name,age|id=?", + Parameters: []interface{}{2}, + WantSpan: &sentry.Span{ + Data: map[string]interface{}{ + "db.system": sentrysql.DatabaseSystem("fakedb"), + "db.name": "fake", + }, + Description: "SELECT|query_test|id,name,age|id=?", + Op: "db.sql.query", + Tags: nil, + Origin: "manual", + Sampled: sentry.SampledTrue, + Status: sentry.SpanStatusOK, + }, + }, + { + Query: "SELECT * FROM query_test WHERE id =", + Parameters: []interface{}{1}, + WantError: true, + WantSpan: &sentry.Span{ + Data: map[string]interface{}{ + "db.system": sentrysql.DatabaseSystem("fakedb"), + "db.name": "fake", + "server.address": "localhost", + "server.port": "5432", + "db.operation": "SELECT", + }, + Description: "SELECT * FROM query_test WHERE id =", + Op: "db.sql.query", + Tags: nil, + Origin: "manual", + Sampled: sentry.SampledTrue, + Status: sentry.SpanStatusInternalError, + }, + }, + } + + spansCh := make(chan []*sentry.Span, len(tests)) + + sentryClient, err := sentry.NewClient(sentry.ClientOptions{ + EnableTracing: true, + TracesSampleRate: 1.0, + BeforeSendTransaction: func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event { + spansCh <- event.Spans + return event + }, + }) + if err != nil { + t.Fatal(err) + } + + for _, tt := range tests { + hub := sentry.NewHub(sentryClient, sentry.NewScope()) + ctx, cancel := context.WithTimeout(sentry.SetHubOnContext(context.Background(), hub), 10*time.Second) + span := sentry.StartSpan(ctx, "fake_parent", sentry.WithTransactionName("Fake Parent")) + ctx = span.Context() + + stmt, err := db.PrepareContext(ctx, tt.Query) + if err != nil && !tt.WantError { + cancel() + t.Fatal(err) + } + + rows, err := stmt.Query(tt.Parameters...) + if err != nil && !tt.WantError { + cancel() + t.Fatal(err) + } + + if rows != nil { + _ = rows.Close() + } + + span.Finish() + cancel() + } + + if ok := sentryClient.Flush(testutils.FlushTimeout()); !ok { + t.Fatal("sentry.Flush timed out") + } + close(spansCh) + + var got [][]*sentry.Span + for e := range spansCh { + got = append(got, e) + } + + for i, tt := range tests { + var foundMatch = false + gotSpans := got[i] + + var diffs []string + for _, gotSpan := range gotSpans { + if diff := cmp.Diff(tt.WantSpan, gotSpan, optstrans); diff != "" { + diffs = append(diffs, diff) + } else { + foundMatch = true + break + } + } + + if !foundMatch { + t.Errorf("Span mismatch (-want +got):\n%s", strings.Join(diffs, "\n")) + } + } + }) +} + +//nolint:dupl +func TestNewSentrySQLConnector_NoParentSpan(t *testing.T) { + db := sql.OpenDB(sentrysql.NewSentrySQLConnector(&fakeConnector{}, sentrysql.WithDatabaseSystem(sentrysql.DatabaseSystem("fakedb")), sentrysql.WithDatabaseName("fake"))) + t.Cleanup(func() { + _, _ = db.Exec("WIPE") + _ = db.Close() + }) + + setupQueries := []string{ + "CREATE|exec_test|id=int32,name=string", + "CREATE|query_test|id=int32,name=string,age=int32,created_at=string", + "INSERT|query_test|id=1,name=John,age=30,created_at=2023-01-01", + "INSERT|query_test|id=2,name=Jane,age=25,created_at=2023-01-02", + "INSERT|query_test|id=3,name=Bob,age=35,created_at=2023-01-03", + } + setupCtx, cancelCtx := context.WithTimeout(context.Background(), 30*time.Second) + defer cancelCtx() + + for _, query := range setupQueries { + _, err := db.ExecContext(setupCtx, query) + if err != nil { + t.Fatalf("initializing table on sqlite: %v", err) + } + } + + t.Run("QueryContext", func(t *testing.T) { + tests := []struct { + Query string + Parameters []interface{} + WantSpan *sentry.Span + WantError bool + }{ + { + Query: "SELECT|query_test|id,name,age|id=?", + Parameters: []interface{}{1}, + WantSpan: nil, + }, + } + + spansCh := make(chan []*sentry.Span, len(tests)) + + sentryClient, err := sentry.NewClient(sentry.ClientOptions{ + EnableTracing: true, + TracesSampleRate: 1.0, + BeforeSendTransaction: func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event { + spansCh <- event.Spans + return event + }, + }) + if err != nil { + t.Fatal(err) + } + + for _, tt := range tests { + hub := sentry.NewHub(sentryClient, sentry.NewScope()) + ctx, cancel := context.WithTimeout(sentry.SetHubOnContext(context.Background(), hub), 10*time.Second) + + rows, err := db.QueryContext(ctx, tt.Query, tt.Parameters...) + if err != nil && !tt.WantError { + cancel() + t.Fatal(err) + } + + if rows != nil { + _ = rows.Close() + } + + cancel() + } + + if ok := sentryClient.Flush(testutils.FlushTimeout()); !ok { + t.Fatal("sentry.Flush timed out") + } + close(spansCh) + + var got [][]*sentry.Span + for e := range spansCh { + got = append(got, e) + } + + // `got` should be empty + if len(got) != 0 { + t.Errorf("got %d spans, want 0", len(got)) + } + }) + + t.Run("ExecContext", func(t *testing.T) { + tests := []struct { + Query string + Parameters []interface{} + WantSpan *sentry.Span + WantError bool + }{ + { + Query: "INSERT|exec_test|id=?,name=?", + Parameters: []interface{}{1, "John"}, + WantSpan: nil, + }, + } + + spansCh := make(chan []*sentry.Span, len(tests)) + + sentryClient, err := sentry.NewClient(sentry.ClientOptions{ + EnableTracing: true, + TracesSampleRate: 1.0, + BeforeSendTransaction: func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event { + spansCh <- event.Spans + return event + }, + }) + if err != nil { + t.Fatal(err) + } + + for _, tt := range tests { + hub := sentry.NewHub(sentryClient, sentry.NewScope()) + ctx, cancel := context.WithTimeout(sentry.SetHubOnContext(context.Background(), hub), 10*time.Second) + + _, err := db.ExecContext(ctx, tt.Query, tt.Parameters...) + if err != nil && !tt.WantError { + cancel() + t.Fatal(err) + } + + cancel() + } + + if ok := sentryClient.Flush(testutils.FlushTimeout()); !ok { + t.Fatal("sentry.Flush timed out") + } + close(spansCh) + + var got [][]*sentry.Span + for e := range spansCh { + got = append(got, e) + } + + // `got` should be empty + if len(got) != 0 { + t.Errorf("got %d spans, want 0", len(got)) + } + }) +} From 3e31bb55b5935553dcba9c0189ed4cb8e199cced Mon Sep 17 00:00:00 2001 From: Reinaldy Rafli Date: Sat, 26 Oct 2024 19:39:37 +0700 Subject: [PATCH 18/26] chore(example): sql integration example --- _examples/sql/main.go | 197 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 197 insertions(+) create mode 100644 _examples/sql/main.go diff --git a/_examples/sql/main.go b/_examples/sql/main.go new file mode 100644 index 00000000..289d1fcc --- /dev/null +++ b/_examples/sql/main.go @@ -0,0 +1,197 @@ +package main + +import ( + "context" + "database/sql" + "errors" + "fmt" + "time" + + "github.com/getsentry/sentry-go" + "github.com/getsentry/sentry-go/sentrysql" + "github.com/lib/pq" +) + +func init() { + // Registering a custom database driver that's wrapped by sentrysql. + // Later, we can call `sql.Open("sentrysql-postgres", databaseDSN)` to use it. + sql.Register("sentrysql-postgres", sentrysql.NewSentrySQL(&pq.Driver{}, sentrysql.WithDatabaseSystem(sentrysql.PostgreSQL), sentrysql.WithDatabaseName("postgres"), sentrysql.WithServerAddress("write.postgres.internal", "5432"))) +} + +func main() { + err := sentry.Init(sentry.ClientOptions{ + // Either set your DSN here or set the SENTRY_DSN environment variable. + Dsn: "", + // Enable printing of SDK debug messages. + // Useful when getting started or trying to figure something out. + Debug: true, + // EnableTracing must be set to true if you want the SQL queries to be traced. + EnableTracing: true, + TracesSampleRate: 1.0, + }) + if err != nil { + fmt.Printf("failed to initialize sentry: %s\n", err.Error()) + return + } + + // We are going to emulate a scenario where an application requires a read database and a write database. + // This is also to show how to use each `sentrysql.NewSentrySQLConnector` and `sentrysql.NewSentrySQL`. + + // Create a database connection for read database. + connector, err := pq.NewConnector("postgres://postgres:password@read.postgres.internal:5432/postgres") + if err != nil { + fmt.Printf("failed to create a postgres connector: %s\n", err.Error()) + return + } + + sentryWrappedConnector := sentrysql.NewSentrySQLConnector( + connector, + sentrysql.WithDatabaseSystem(sentrysql.PostgreSQL), // required if you want to see the queries on the Queries Insights page + sentrysql.WithDatabaseName("postgres"), + sentrysql.WithServerAddress("read.postgres.internal", "5432"), + ) + + readDatabase := sql.OpenDB(sentryWrappedConnector) + defer func() { + err := readDatabase.Close() + if err != nil { + sentry.CaptureException(err) + } + }() + + // Create a database connection for write database. + writeDatabase, err := sql.Open("sentrysql-postgres", "postgres://postgres:password@write.postgres.internal:5432/postgres") + if err != nil { + fmt.Printf("failed to open write postgres database: %s\n", err.Error()) + return + } + defer func() { + err := writeDatabase.Close() + if err != nil { + sentry.CaptureException(err) + } + }() + + ctx, cancel := context.WithTimeout( + sentry.SetHubOnContext(context.Background(), sentry.CurrentHub().Clone()), + time.Minute, + ) + defer cancel() + + err = ScaffoldDatabase(ctx, writeDatabase) + if err != nil { + fmt.Printf("failed to scaffold database: %s\n", err.Error()) + return + } + + users, err := GetAllUsers(ctx, readDatabase) + if err != nil { + fmt.Printf("failed to get users: %s\n", err.Error()) + return + } + + for _, user := range users { + fmt.Printf("User: %+v\n", user) + } +} + +// ScaffoldDatabase prepares the database to have the users table. +func ScaffoldDatabase(ctx context.Context, db *sql.DB) error { + // A parent span is required to have the queries to be traced. + // Make sure to override the `context.Context` with the parent span's context. + span := sentry.StartSpan(ctx, "ScaffoldDatabase") + ctx = span.Context() + defer span.Finish() + + conn, err := db.Conn(ctx) + if err != nil { + return fmt.Errorf("acquiring connection from pool: %w", err) + } + defer func() { + err := conn.Close() + if err != nil && !errors.Is(err, sql.ErrConnDone) { + if hub := sentry.GetHubFromContext(ctx); hub != nil { + hub.CaptureException(err) + } + } + }() + + tx, err := conn.BeginTx(ctx, &sql.TxOptions{Isolation: sql.LevelSerializable, ReadOnly: false}) + if err != nil { + return fmt.Errorf("beginning transaction: %w", err) + } + defer func() { + err := tx.Rollback() + if err != nil && !errors.Is(err, sql.ErrTxDone) { + if hub := sentry.GetHubFromContext(ctx); hub != nil { + hub.CaptureException(err) + } + } + }() + + _, err = tx.ExecContext(ctx, "CREATE TABLE users (id INTEGER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, name VARCHAR(255), email VARCHAR(255), active BOOLEAN)") + if err != nil { + return fmt.Errorf("creating users table: %w", err) + } + + err = tx.Commit() + if err != nil { + return fmt.Errorf("committing transaction: %w", err) + } + + return nil +} + +// User represents a user in the database. +type User struct { + ID int + Name string + Email string +} + +// GetAllUsers returns all the users from the database. +func GetAllUsers(ctx context.Context, db *sql.DB) ([]User, error) { + // A parent span is required to have the queries to be traced. + // Make sure to override the `context.Context` with the parent span's context. + span := sentry.StartSpan(ctx, "GetAllUsers") + ctx = span.Context() + defer span.Finish() + + conn, err := db.Conn(ctx) + if err != nil { + return nil, fmt.Errorf("acquiring connection from pool: %w", err) + } + defer func() { + err := conn.Close() + if err != nil && !errors.Is(err, sql.ErrConnDone) { + if hub := sentry.GetHubFromContext(ctx); hub != nil { + hub.CaptureException(err) + } + } + }() + + rows, err := conn.QueryContext(ctx, "SELECT id, name, email FROM users WHERE active = $1", true) + if err != nil { + return nil, fmt.Errorf("querying users: %w", err) + } + defer func() { + err := rows.Close() + if err != nil { + if hub := sentry.GetHubFromContext(ctx); hub != nil { + hub.CaptureException(err) + } + } + }() + + var users []User + for rows.Next() { + var user User + err := rows.Scan(&user.ID, &user.Name, &user.Email) + if err != nil { + return nil, fmt.Errorf("scanning user: %w", err) + } + users = append(users, user) + } + + return users, nil +} From fbd4dfc3df80ccd6629d5d31facd8611010cf286 Mon Sep 17 00:00:00 2001 From: Reinaldy Rafli Date: Sat, 26 Oct 2024 19:41:22 +0700 Subject: [PATCH 19/26] chore: don't lint fakedb --- sentrysql/fakedb_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/sentrysql/fakedb_test.go b/sentrysql/fakedb_test.go index cce79d6a..859612d1 100644 --- a/sentrysql/fakedb_test.go +++ b/sentrysql/fakedb_test.go @@ -1,3 +1,4 @@ +//nolint:all package sentrysql_test // This file is a fork of From 1023392bd98b6d0c78c274eb9165c21d6bf3c9ad Mon Sep 17 00:00:00 2001 From: Reinaldy Rafli Date: Sat, 26 Oct 2024 19:44:04 +0700 Subject: [PATCH 20/26] test: backport to go1.18 --- sentrysql/fakedb_test.go | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/sentrysql/fakedb_test.go b/sentrysql/fakedb_test.go index 859612d1..61bce5fa 100644 --- a/sentrysql/fakedb_test.go +++ b/sentrysql/fakedb_test.go @@ -1258,33 +1258,33 @@ func converterForType(typ string) driver.ValueConverter { func colTypeToReflectType(typ string) reflect.Type { switch typ { case "bool": - return reflect.TypeFor[bool]() + return reflect.TypeOf(false) case "nullbool": - return reflect.TypeFor[sql.NullBool]() + return reflect.TypeOf(sql.NullBool{}) case "int16": - return reflect.TypeFor[int16]() + return reflect.TypeOf(int16(0)) case "nullint16": - return reflect.TypeFor[sql.NullInt16]() + return reflect.TypeOf(sql.NullInt16{}) case "int32": - return reflect.TypeFor[int32]() + return reflect.TypeOf(int32(0)) case "nullint32": - return reflect.TypeFor[sql.NullInt32]() + return reflect.TypeOf(sql.NullInt32{}) case "string": - return reflect.TypeFor[string]() + return reflect.TypeOf("") case "nullstring": - return reflect.TypeFor[sql.NullString]() + return reflect.TypeOf(sql.NullString{}) case "int64": - return reflect.TypeFor[int64]() + return reflect.TypeOf(int64(0)) case "nullint64": - return reflect.TypeFor[sql.NullInt64]() + return reflect.TypeOf(sql.NullInt64{}) case "float64": - return reflect.TypeFor[float64]() + return reflect.TypeOf(float64(0)) case "nullfloat64": - return reflect.TypeFor[sql.NullFloat64]() + return reflect.TypeOf(sql.NullFloat64{}) case "datetime": - return reflect.TypeFor[time.Time]() + return reflect.TypeOf(time.Time{}) case "any": - return reflect.TypeFor[any]() + return reflect.TypeOf(new(any)).Elem() } panic("invalid fakedb column type of " + typ) } From 2dc58a59d01ca77d245666bfe9857f12c42c94df Mon Sep 17 00:00:00 2001 From: Reinaldy Rafli Date: Sat, 26 Oct 2024 19:49:19 +0700 Subject: [PATCH 21/26] test: backport to go1.18 --- sentrysql/fakedb_test.go | 78 +++++++++++++++++++--------------------- 1 file changed, 37 insertions(+), 41 deletions(-) diff --git a/sentrysql/fakedb_test.go b/sentrysql/fakedb_test.go index 61bce5fa..c431e2bc 100644 --- a/sentrysql/fakedb_test.go +++ b/sentrysql/fakedb_test.go @@ -44,17 +44,13 @@ import ( "fmt" "io" "reflect" - "slices" "strconv" "strings" "sync" - "sync/atomic" "testing" "time" -) - -// fakeDriver is a fake database that implements Go's driver.Driver -// (and driver.DriverContext for Sentry purposes) interface, just for testing. +) // fakeDriver is a fake database that implements Go's driver.Driver +// interface, just for testing. // // It speaks a query language that's semantically similar to but // syntactically different and simpler than SQL. The syntax is as @@ -93,8 +89,6 @@ type fakeConnector struct { closed bool } -var _ driver.Connector = &fakeConnector{} - func (c *fakeConnector) Connect(context.Context) (driver.Conn, error) { conn, err := fdriver.Open(c.name) conn.(*fakeConn).waiter = c.waiter @@ -126,8 +120,6 @@ func (cc *fakeDriverCtx) OpenConnector(name string) (driver.Connector, error) { type fakeDB struct { name string - useRawBytes atomic.Bool - mu sync.Mutex tables map[string]*table badConn bool @@ -155,7 +147,12 @@ type table struct { } func (t *table) columnIndex(name string) int { - return slices.Index(t.colname, name) + for n, nname := range t.colname { + if name == nname { + return n + } + } + return -1 } type row struct { @@ -243,6 +240,15 @@ type fakeStmt struct { var fdriver driver.Driver = &fakeDriver{} +func contains(list []string, y string) bool { + for _, x := range list { + if x == y { + return true + } + } + return false +} + type Dummy struct { driver.Driver } @@ -352,8 +358,10 @@ func (db *fakeDB) columnType(table, column string) (typ string, ok bool) { if !ok { return } - if i := slices.Index(t.colname, column); i != -1 { - return t.coltype[i], true + for n, cname := range t.colname { + if cname == column { + return t.coltype[n], true + } } return "", false } @@ -519,7 +527,8 @@ func errf(msg string, args ...any) error { // parts are table|selectCol1,selectCol2|whereCol=?,whereCol2=? // (note that where columns must always contain ? marks, -// just a limitation for fakedb) +// +// just a limitation for fakedb) func (c *fakeConn) prepareSelect(stmt *fakeStmt, parts []string) (*fakeStmt, error) { if len(parts) != 3 { stmt.Close() @@ -651,7 +660,7 @@ func (c *fakeConn) PrepareContext(ctx context.Context, query string) (driver.Stm } if c.stickyBad || (hookPrepareBadConn != nil && hookPrepareBadConn()) { - return nil, fakeError{Message: "Prepare: Sticky Bad", Wrapped: driver.ErrBadConn} + return nil, fakeError{Message: "Preapre: Sticky Bad", Wrapped: driver.ErrBadConn} } c.touchMem() @@ -705,8 +714,6 @@ func (c *fakeConn) PrepareContext(ctx context.Context, query string) (driver.Stm switch cmd { case "WIPE": // Nothing - case "USE_RAWBYTES": - c.db.useRawBytes.Store(true) case "SELECT": stmt, err = c.prepareSelect(stmt, parts) case "CREATE": @@ -810,9 +817,6 @@ func (s *fakeStmt) ExecContext(ctx context.Context, args []driver.NamedValue) (d case "WIPE": db.wipe() return driver.ResultNoRows, nil - case "USE_RAWBYTES": - s.c.db.useRawBytes.Store(true) - return driver.ResultNoRows, nil case "CREATE": if err := db.createTable(s.table, s.colName, s.colType); err != nil { return nil, err @@ -828,15 +832,6 @@ func (s *fakeStmt) ExecContext(ctx context.Context, args []driver.NamedValue) (d return nil, fmt.Errorf("fakedb: unimplemented statement Exec command type of %q", s.cmd) } -func valueFromPlaceholderName(args []driver.NamedValue, name string) driver.Value { - for i := range args { - if args[i].Name == name { - return args[i].Value - } - } - return nil -} - // When doInsert is true, add the row to the table. // When doInsert is false do prep-work and error checking, but don't // actually add the row to the table. @@ -871,8 +866,11 @@ func (s *fakeStmt) execInsert(args []driver.NamedValue, doInsert bool) (driver.R val = args[argPos].Value } else { // Assign value from argument placeholder name. - if v := valueFromPlaceholderName(args, strvalue[1:]); v != nil { - val = v + for _, a := range args { + if a.Name == strvalue[1:] { + val = a.Value + break + } } } argPos++ @@ -948,7 +946,6 @@ func (s *fakeStmt) QueryContext(ctx context.Context, args []driver.NamedValue) ( txStatus = "transaction" } cursor := &rowsCursor{ - db: s.c.db, parentMem: s.c, posRow: -1, rows: [][]*row{ @@ -1008,8 +1005,12 @@ func (s *fakeStmt) QueryContext(ctx context.Context, args []driver.NamedValue) ( if wcol.Placeholder == "?" { argValue = args[wcol.Ordinal-1].Value } else { - if v := valueFromPlaceholderName(args, wcol.Placeholder[1:]); v != nil { - argValue = v + // Assign arg value from placeholder name. + for _, a := range args { + if a.Name == wcol.Placeholder[1:] { + argValue = a.Value + break + } } } if fmt.Sprintf("%v", tcol) != fmt.Sprintf("%v", argValue) { @@ -1041,7 +1042,6 @@ func (s *fakeStmt) QueryContext(ctx context.Context, args []driver.NamedValue) ( } cursor := &rowsCursor{ - db: s.c.db, parentMem: s.c, posRow: -1, rows: setMRows, @@ -1084,7 +1084,6 @@ func (tx *fakeTx) Rollback() error { } type rowsCursor struct { - db *fakeDB parentMem memToucher cols [][]string colType [][]string @@ -1107,9 +1106,6 @@ type rowsCursor struct { // This is separate from the fakeConn.line to allow for drivers that // can start multiple queries on the same transaction at the same time. line int64 - - // closeErr is returned when rowsCursor.Close - closeErr error } func (rc *rowsCursor) touchMem() { @@ -1121,7 +1117,7 @@ func (rc *rowsCursor) Close() error { rc.touchMem() rc.parentMem.touchMem() rc.closed = true - return rc.closeErr + return nil } func (rc *rowsCursor) Columns() []string { @@ -1159,7 +1155,7 @@ func (rc *rowsCursor) Next(dest []driver.Value) error { // messing up conversions or doing them differently. dest[i] = v - if bs, ok := v.([]byte); ok && !rc.db.useRawBytes.Load() { + if bs, ok := v.([]byte); ok { if rc.bytesClone == nil { rc.bytesClone = make(map[*byte][]byte) } From c5fa49de994f32da77499c632e8e512724556a95 Mon Sep 17 00:00:00 2001 From: Reinaldy Rafli Date: Sat, 26 Oct 2024 19:51:43 +0700 Subject: [PATCH 22/26] chore: lint --- sentrysql/operation.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/sentrysql/operation.go b/sentrysql/operation.go index 1f339131..4b9591de 100644 --- a/sentrysql/operation.go +++ b/sentrysql/operation.go @@ -3,10 +3,10 @@ package sentrysql import "strings" var knownDatabaseOperations = map[string]struct{}{ - "SELECT": struct{}{}, - "INSERT": struct{}{}, - "DELETE": struct{}{}, - "UPDATE": struct{}{}, + "SELECT": {}, + "INSERT": {}, + "DELETE": {}, + "UPDATE": {}, } func parseDatabaseOperation(query string) string { From 1f38b9f866dfda1ff136ac87884032c2648362ef Mon Sep 17 00:00:00 2001 From: Reinaldy Rafli Date: Sat, 2 Nov 2024 20:36:46 +0700 Subject: [PATCH 23/26] test(sentrysql): test against legacy driver --- sentrysql/conn.go | 6 + sentrysql/legacydb_test.go | 835 ++++++++++++++++++++++++++ sentrysql/sentrysql_connector_test.go | 10 +- sentrysql/sentrysql_legacy_test.go | 702 ++++++++++++++++++++++ sentrysql/stmt.go | 2 + 5 files changed, 1550 insertions(+), 5 deletions(-) create mode 100644 sentrysql/legacydb_test.go create mode 100644 sentrysql/sentrysql_legacy_test.go diff --git a/sentrysql/conn.go b/sentrysql/conn.go index 17bef318..20158af2 100644 --- a/sentrysql/conn.go +++ b/sentrysql/conn.go @@ -123,6 +123,9 @@ func (s *sentryConn) QueryContext(ctx context.Context, query string, args []driv // should only be executed if the original driver implements QueryerContext queryerContext, ok := s.originalConn.(driver.QueryerContext) if !ok { + if s.ctx == nil && ctx != nil { + s.ctx = ctx + } return nil, driver.ErrSkip } @@ -177,6 +180,9 @@ func (s *sentryConn) ExecContext(ctx context.Context, query string, args []drive // should only be executed if the original driver implements ExecerContext { execerContext, ok := s.originalConn.(driver.ExecerContext) if !ok { + if s.ctx == nil && ctx != nil { + s.ctx = ctx + } // ExecContext may return ErrSkip. return nil, driver.ErrSkip } diff --git a/sentrysql/legacydb_test.go b/sentrysql/legacydb_test.go new file mode 100644 index 00000000..96e7b898 --- /dev/null +++ b/sentrysql/legacydb_test.go @@ -0,0 +1,835 @@ +//nolint:all +package sentrysql_test + +// This file is a fork of +// https://cs.opensource.google/go/go/+/refs/tags/go1.7.6:src/database/sql/fakedb_test.go +// +// Copyright 2011 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. +// +// Copyright (c) 2009 The Go Authors. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// - Redistributions of source code must retain the above copyright +// +// notice, this list of conditions and the following disclaimer. +// - Redistributions in binary form must reproduce the above +// +// copyright notice, this list of conditions and the following disclaimer +// in the documentation and/or other materials provided with the +// distribution. +// - Neither the name of Google Inc. nor the names of its +// +// contributors may be used to endorse or promote products derived from +// this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +import ( + "database/sql/driver" + "errors" + "fmt" + "io" + "log" + "strconv" + "strings" + "sync" + "time" +) + +var _ = log.Printf + +// legacyDriver is a fake database that implements Go's driver.Driver +// interface, just for testing. +// +// It speaks a query language that's semantically similar to but +// syntactically different and simpler than SQL. The syntax is as +// follows: +// +// WIPE +// CREATE||=,=,... +// where types are: "string", [u]int{8,16,32,64}, "bool" +// INSERT||col=val,col2=val2,col3=? +// SELECT||projectcol1,projectcol2|filtercol=?,filtercol2=? +// +// Any of these can be preceded by PANIC||, to cause the +// named method on fakeStmt to panic. +// +// When opening a fakeDriver's database, it starts empty with no +// tables. All tables and data are stored in memory only. +type legacyDriver struct { + mu sync.Mutex // guards 3 following fields + openCount int // conn opens + closeCount int // conn closes + waitCh chan struct{} + waitingCh chan struct{} + dbs map[string]*legacyDB +} + +type legacyDB struct { + name string + + mu sync.Mutex + legacyTables map[string]*legacyTable + badConn bool +} + +type legacyTable struct { + mu sync.Mutex + colname []string + coltype []string + legacyRows []*legacyRow +} + +func (t *legacyTable) columnIndex(name string) int { + for n, nname := range t.colname { + if name == nname { + return n + } + } + return -1 +} + +type legacyRow struct { + cols []interface{} // must be same size as its legacyTable colname + coltype +} + +type legacyConn struct { + db *legacyDB // where to return ourselves to + + currTx *legacyTx + + // Stats for tests: + mu sync.Mutex + stmtsMade int + stmtsClosed int + numPrepare int + + // bad connection tests; see isBad() + bad bool + stickyBad bool +} + +func (c *legacyConn) incrStat(v *int) { + c.mu.Lock() + *v++ + c.mu.Unlock() +} + +type legacyTx struct { + c *legacyConn +} + +type legacyStmt struct { + c *legacyConn + q string // just for debugging + + cmd string + legacyTable string + panic string + + closed bool + + colName []string // used by CREATE, INSERT, SELECT (selected columns) + colType []string // used by CREATE + colValue []interface{} // used by INSERT (mix of strings and "?" for bound params) + placeholders int // used by INSERT/SELECT: number of ? params + + whereCol []string // used by SELECT (all placeholders) + + placeholderConverter []driver.ValueConverter // used by INSERT +} + +var ldriver driver.Driver = &legacyDriver{} + +// hook to simulate connection failures +var legacyHookOpenErr struct { + sync.Mutex + fn func() error +} + +// Supports dsn forms: +// +// +// ; (only currently supported option is `badConn`, +// which causes driver.ErrBadConn to be returned on +// every other conn.Begin()) +func (d *legacyDriver) Open(dsn string) (driver.Conn, error) { + legacyHookOpenErr.Lock() + fn := legacyHookOpenErr.fn + legacyHookOpenErr.Unlock() + if fn != nil { + if err := fn(); err != nil { + return nil, err + } + } + parts := strings.Split(dsn, ";") + if len(parts) < 1 { + return nil, errors.New("fakedb: no database name") + } + name := parts[0] + + db := d.getDB(name) + + d.mu.Lock() + d.openCount++ + d.mu.Unlock() + conn := &legacyConn{db: db} + + if len(parts) >= 2 && parts[1] == "badConn" { + conn.bad = true + } + if d.waitCh != nil { + d.waitingCh <- struct{}{} + <-d.waitCh + d.waitCh = nil + d.waitingCh = nil + } + return conn, nil +} + +func (d *legacyDriver) getDB(name string) *legacyDB { + d.mu.Lock() + defer d.mu.Unlock() + if d.dbs == nil { + d.dbs = make(map[string]*legacyDB) + } + db, ok := d.dbs[name] + if !ok { + db = &legacyDB{name: name} + d.dbs[name] = db + } + return db +} + +func (db *legacyDB) wipe() { + db.mu.Lock() + defer db.mu.Unlock() + db.legacyTables = nil +} + +func (db *legacyDB) createTable(name string, columnNames, columnTypes []string) error { + db.mu.Lock() + defer db.mu.Unlock() + if db.legacyTables == nil { + db.legacyTables = make(map[string]*legacyTable) + } + if _, exist := db.legacyTables[name]; exist { + return fmt.Errorf("legacyTable %q already exists", name) + } + if len(columnNames) != len(columnTypes) { + return fmt.Errorf("create legacyTable of %q len(names) != len(types): %d vs %d", + name, len(columnNames), len(columnTypes)) + } + db.legacyTables[name] = &legacyTable{colname: columnNames, coltype: columnTypes} + return nil +} + +// must be called with db.mu lock held +func (db *legacyDB) legacyTable(legacyTable string) (*legacyTable, bool) { + if db.legacyTables == nil { + return nil, false + } + t, ok := db.legacyTables[legacyTable] + return t, ok +} + +func (db *legacyDB) columnType(legacyTable, column string) (typ string, ok bool) { + db.mu.Lock() + defer db.mu.Unlock() + t, ok := db.legacyTable(legacyTable) + if !ok { + return + } + for n, cname := range t.colname { + if cname == column { + return t.coltype[n], true + } + } + return "", false +} + +func (c *legacyConn) isBad() bool { + if c.stickyBad { + return true + } else if c.bad { + // alternate between bad conn and not bad conn + c.db.badConn = !c.db.badConn + return c.db.badConn + } else { + return false + } +} + +func (c *legacyConn) Begin() (driver.Tx, error) { + if c.isBad() { + return nil, driver.ErrBadConn + } + if c.currTx != nil { + return nil, errors.New("already in a transaction") + } + c.currTx = &legacyTx{c: c} + return c.currTx, nil +} + +var legacyHookPostCloseConn struct { + sync.Mutex + fn func(*legacyConn, error) +} + +func (c *legacyConn) Close() (err error) { + drv := ldriver.(*legacyDriver) + defer func() { + if err != nil && testStrictClose != nil { + testStrictClose.Errorf("failed to close a test legacyConn: %v", err) + } + legacyHookPostCloseConn.Lock() + fn := legacyHookPostCloseConn.fn + legacyHookPostCloseConn.Unlock() + if fn != nil { + fn(c, err) + } + if err == nil { + drv.mu.Lock() + drv.closeCount++ + drv.mu.Unlock() + } + }() + if c.currTx != nil { + return errors.New("can't close legacyConn; in a Transaction") + } + if c.db == nil { + return errors.New("can't close legacyConn; already closed") + } + if c.stmtsMade > c.stmtsClosed { + return errors.New("can't close; dangling statement(s)") + } + c.db = nil + return nil +} + +func legacyCheckSubsetTypes(args []driver.Value) error { + for n, arg := range args { + switch arg.(type) { + case int64, float64, bool, nil, []byte, string, time.Time: + default: + return fmt.Errorf("fakedb_test: invalid argument #%d: %v, type %T", n+1, arg, arg) + } + } + return nil +} + +func (c *legacyConn) Exec(query string, args []driver.Value) (driver.Result, error) { + // This is an optional interface, but it's implemented here + // just to check that all the args are of the proper types. + // ErrSkip is returned so the caller acts as if we didn't + // implement this at all. + err := legacyCheckSubsetTypes(args) + if err != nil { + return nil, err + } + return nil, driver.ErrSkip +} + +func (c *legacyConn) Query(query string, args []driver.Value) (driver.Rows, error) { + // This is an optional interface, but it's implemented here + // just to check that all the args are of the proper types. + // ErrSkip is returned so the caller acts as if we didn't + // implement this at all. + err := legacyCheckSubsetTypes(args) + if err != nil { + return nil, err + } + return nil, driver.ErrSkip +} + +// parts are legacyTable|selectCol1,selectCol2|whereCol=?,whereCol2=? +// (note that where columns must always contain ? marks, +// +// just a limitation for fakedb) +func (c *legacyConn) prepareSelect(stmt *legacyStmt, parts []string) (driver.Stmt, error) { + if len(parts) != 3 { + stmt.Close() + return nil, errf("invalid SELECT syntax with %d parts; want 3", len(parts)) + } + stmt.legacyTable = parts[0] + stmt.colName = strings.Split(parts[1], ",") + for n, colspec := range strings.Split(parts[2], ",") { + if colspec == "" { + continue + } + nameVal := strings.Split(colspec, "=") + if len(nameVal) != 2 { + stmt.Close() + return nil, errf("SELECT on legacyTable %q has invalid column spec of %q (index %d)", stmt.legacyTable, colspec, n) + } + column, value := nameVal[0], nameVal[1] + _, ok := c.db.columnType(stmt.legacyTable, column) + if !ok { + stmt.Close() + return nil, errf("SELECT on legacyTable %q references non-existent column %q", stmt.legacyTable, column) + } + if value != "?" { + stmt.Close() + return nil, errf("SELECT on legacyTable %q has pre-bound value for where column %q; need a question mark", + stmt.legacyTable, column) + } + stmt.whereCol = append(stmt.whereCol, column) + stmt.placeholders++ + } + return stmt, nil +} + +// parts are legacyTable|col=type,col2=type2 +func (c *legacyConn) prepareCreate(stmt *legacyStmt, parts []string) (driver.Stmt, error) { + if len(parts) != 2 { + stmt.Close() + return nil, errf("invalid CREATE syntax with %d parts; want 2", len(parts)) + } + stmt.legacyTable = parts[0] + for n, colspec := range strings.Split(parts[1], ",") { + nameType := strings.Split(colspec, "=") + if len(nameType) != 2 { + stmt.Close() + return nil, errf("CREATE legacyTable %q has invalid column spec of %q (index %d)", stmt.legacyTable, colspec, n) + } + stmt.colName = append(stmt.colName, nameType[0]) + stmt.colType = append(stmt.colType, nameType[1]) + } + return stmt, nil +} + +// parts are legacyTable|col=?,col2=val +func (c *legacyConn) prepareInsert(stmt *legacyStmt, parts []string) (driver.Stmt, error) { + if len(parts) != 2 { + stmt.Close() + return nil, errf("invalid INSERT syntax with %d parts; want 2", len(parts)) + } + stmt.legacyTable = parts[0] + for n, colspec := range strings.Split(parts[1], ",") { + nameVal := strings.Split(colspec, "=") + if len(nameVal) != 2 { + stmt.Close() + return nil, errf("INSERT legacyTable %q has invalid column spec of %q (index %d)", stmt.legacyTable, colspec, n) + } + column, value := nameVal[0], nameVal[1] + ctype, ok := c.db.columnType(stmt.legacyTable, column) + if !ok { + stmt.Close() + return nil, errf("INSERT legacyTable %q references non-existent column %q", stmt.legacyTable, column) + } + stmt.colName = append(stmt.colName, column) + + if value != "?" { + var subsetVal interface{} + // Convert to driver subset type + switch ctype { + case "string": + subsetVal = []byte(value) + case "blob": + subsetVal = []byte(value) + case "int32": + i, err := strconv.Atoi(value) + if err != nil { + stmt.Close() + return nil, errf("invalid conversion to int32 from %q", value) + } + subsetVal = int64(i) // int64 is a subset type, but not int32 + default: + stmt.Close() + return nil, errf("unsupported conversion for pre-bound parameter %q to type %q", value, ctype) + } + stmt.colValue = append(stmt.colValue, subsetVal) + } else { + stmt.placeholders++ + stmt.placeholderConverter = append(stmt.placeholderConverter, converterForType(ctype)) + stmt.colValue = append(stmt.colValue, "?") + } + } + return stmt, nil +} + +// hook to simulate broken connections +var legacyHookPrepareBadConn func() bool + +func (c *legacyConn) Prepare(query string) (driver.Stmt, error) { + c.numPrepare++ + if c.db == nil { + panic("nil c.db; conn = " + fmt.Sprintf("%#v", c)) + } + + if c.stickyBad || (legacyHookPrepareBadConn != nil && legacyHookPrepareBadConn()) { + return nil, driver.ErrBadConn + } + + parts := strings.Split(query, "|") + if len(parts) < 1 { + return nil, errf("empty query") + } + stmt := &legacyStmt{q: query, c: c} + if len(parts) >= 3 && parts[0] == "PANIC" { + stmt.panic = parts[1] + parts = parts[2:] + } + cmd := parts[0] + stmt.cmd = cmd + parts = parts[1:] + + c.incrStat(&c.stmtsMade) + switch cmd { + case "WIPE": + // Nothing + case "SELECT": + return c.prepareSelect(stmt, parts) + case "CREATE": + return c.prepareCreate(stmt, parts) + case "INSERT": + return c.prepareInsert(stmt, parts) + case "NOSERT": + // Do all the prep-work like for an INSERT but don't actually insert the legacyRow. + // Used for some of the concurrent tests. + return c.prepareInsert(stmt, parts) + default: + stmt.Close() + return nil, errf("unsupported command type %q", cmd) + } + return stmt, nil +} + +func (s *legacyStmt) ColumnConverter(idx int) driver.ValueConverter { + if s.panic == "ColumnConverter" { + panic(s.panic) + } + if len(s.placeholderConverter) == 0 { + return driver.DefaultParameterConverter + } + return s.placeholderConverter[idx] +} + +func (s *legacyStmt) Close() error { + if s.panic == "Close" { + panic(s.panic) + } + if s.c == nil { + panic("nil conn in legacyStmt.Close") + } + if s.c.db == nil { + panic("in legacyStmt.Close, conn's db is nil (already closed)") + } + if !s.closed { + s.c.incrStat(&s.c.stmtsClosed) + s.closed = true + } + return nil +} + +// hook to simulate broken connections +var legacyHookExecBadConn func() bool + +func (s *legacyStmt) Exec(args []driver.Value) (driver.Result, error) { + if s.panic == "Exec" { + panic(s.panic) + } + if s.closed { + return nil, errClosed + } + + if s.c.stickyBad || (legacyHookExecBadConn != nil && legacyHookExecBadConn()) { + return nil, driver.ErrBadConn + } + + err := legacyCheckSubsetTypes(args) + if err != nil { + return nil, err + } + + db := s.c.db + switch s.cmd { + case "WIPE": + db.wipe() + return driver.ResultNoRows, nil + case "CREATE": + if err := db.createTable(s.legacyTable, s.colName, s.colType); err != nil { + return nil, err + } + return driver.ResultNoRows, nil + case "INSERT": + return s.execInsert(args, true) + case "NOSERT": + // Do all the prep-work like for an INSERT but don't actually insert the legacyRow. + // Used for some of the concurrent tests. + return s.execInsert(args, false) + } + fmt.Printf("EXEC statement, cmd=%q: %#v\n", s.cmd, s) + return nil, fmt.Errorf("unimplemented statement Exec command type of %q", s.cmd) +} + +// When doInsert is true, add the legacyRow to the legacyTable. +// When doInsert is false do prep-work and error checking, but don't +// actually add the legacyRow to the legacyTable. +func (s *legacyStmt) execInsert(args []driver.Value, doInsert bool) (driver.Result, error) { + db := s.c.db + if len(args) != s.placeholders { + panic("error in pkg db; should only get here if size is correct") + } + db.mu.Lock() + t, ok := db.legacyTable(s.legacyTable) + db.mu.Unlock() + if !ok { + return nil, fmt.Errorf("fakedb: legacyTable %q doesn't exist", s.legacyTable) + } + + t.mu.Lock() + defer t.mu.Unlock() + + var cols []interface{} + if doInsert { + cols = make([]interface{}, len(t.colname)) + } + argPos := 0 + for n, colname := range s.colName { + colidx := t.columnIndex(colname) + if colidx == -1 { + return nil, fmt.Errorf("fakedb: column %q doesn't exist or dropped since prepared statement was created", colname) + } + var val interface{} + if strvalue, ok := s.colValue[n].(string); ok && strvalue == "?" { + val = args[argPos] + argPos++ + } else { + val = s.colValue[n] + } + if doInsert { + cols[colidx] = val + } + } + + if doInsert { + t.legacyRows = append(t.legacyRows, &legacyRow{cols: cols}) + } + return driver.RowsAffected(1), nil +} + +// hook to simulate broken connections +var legacyHookQueryBadConn func() bool + +func (s *legacyStmt) Query(args []driver.Value) (driver.Rows, error) { + if s.panic == "Query" { + panic(s.panic) + } + if s.closed { + return nil, errClosed + } + + if s.c.stickyBad || (legacyHookQueryBadConn != nil && legacyHookQueryBadConn()) { + return nil, driver.ErrBadConn + } + + err := legacyCheckSubsetTypes(args) + if err != nil { + return nil, err + } + + db := s.c.db + if len(args) != s.placeholders { + panic("error in pkg db; should only get here if size is correct") + } + + db.mu.Lock() + t, ok := db.legacyTable(s.legacyTable) + db.mu.Unlock() + if !ok { + return nil, fmt.Errorf("fakedb: legacyTable %q doesn't exist", s.legacyTable) + } + + if s.legacyTable == "magicquery" { + if len(s.whereCol) == 2 && s.whereCol[0] == "op" && s.whereCol[1] == "millis" { + if args[0] == "sleep" { + time.Sleep(time.Duration(args[1].(int64)) * time.Millisecond) + } + } + } + + t.mu.Lock() + defer t.mu.Unlock() + + colIdx := make(map[string]int) // select column name -> column index in legacyTable + for _, name := range s.colName { + idx := t.columnIndex(name) + if idx == -1 { + return nil, fmt.Errorf("fakedb: unknown column name %q", name) + } + colIdx[name] = idx + } + + mlegacyRows := []*legacyRow{} +legacyRows: + for _, tlegacyRow := range t.legacyRows { + // Process the where clause, skipping non-match legacyRows. This is lazy + // and just uses fmt.Sprintf("%v") to test equality. Good enough + // for test code. + for widx, wcol := range s.whereCol { + idx := t.columnIndex(wcol) + if idx == -1 { + return nil, fmt.Errorf("db: invalid where clause column %q", wcol) + } + tcol := tlegacyRow.cols[idx] + if bs, ok := tcol.([]byte); ok { + // lazy hack to avoid sprintf %v on a []byte + tcol = string(bs) + } + if fmt.Sprintf("%v", tcol) != fmt.Sprintf("%v", args[widx]) { + continue legacyRows + } + } + mlegacyRow := &legacyRow{cols: make([]interface{}, len(s.colName))} + for seli, name := range s.colName { + mlegacyRow.cols[seli] = tlegacyRow.cols[colIdx[name]] + } + mlegacyRows = append(mlegacyRows, mlegacyRow) + } + + cursor := &legacyRowsCursor{ + pos: -1, + legacyRows: mlegacyRows, + cols: s.colName, + errPos: -1, + } + return cursor, nil +} + +func (s *legacyStmt) NumInput() int { + if s.panic == "NumInput" { + panic(s.panic) + } + return s.placeholders +} + +// hook to simulate broken connections +var legacyHookCommitBadConn func() bool + +func (tx *legacyTx) Commit() error { + tx.c.currTx = nil + if legacyHookCommitBadConn != nil && legacyHookCommitBadConn() { + return driver.ErrBadConn + } + return nil +} + +// hook to simulate broken connections +var legacyHookRollbackBadConn func() bool + +func (tx *legacyTx) Rollback() error { + tx.c.currTx = nil + if legacyHookRollbackBadConn != nil && legacyHookRollbackBadConn() { + return driver.ErrBadConn + } + return nil +} + +type legacyRowsCursor struct { + cols []string + pos int + legacyRows []*legacyRow + closed bool + + // errPos and err are for making Next return early with error. + errPos int + err error + + // a clone of slices to give out to clients, indexed by the + // the original slice's first byte address. we clone them + // just so we're able to corrupt them on close. + bytesClone map[*byte][]byte +} + +func (rc *legacyRowsCursor) Close() error { + if !rc.closed { + for _, bs := range rc.bytesClone { + bs[0] = 255 // first byte corrupted + } + } + rc.closed = true + return nil +} + +func (rc *legacyRowsCursor) Columns() []string { + return rc.cols +} + +var legacyRowsCursorNextHook func(dest []driver.Value) error + +func (rc *legacyRowsCursor) Next(dest []driver.Value) error { + if legacyRowsCursorNextHook != nil { + return legacyRowsCursorNextHook(dest) + } + + if rc.closed { + return errors.New("fakedb: cursor is closed") + } + rc.pos++ + if rc.pos == rc.errPos { + return rc.err + } + if rc.pos >= len(rc.legacyRows) { + return io.EOF // per interface spec + } + for i, v := range rc.legacyRows[rc.pos].cols { + // TODO(bradfitz): convert to subset types? naah, I + // think the subset types should only be input to + // driver, but the sql package should be able to handle + // a wider range of types coming out of drivers. all + // for ease of drivers, and to prevent drivers from + // messing up conversions or doing them differently. + dest[i] = v + + if bs, ok := v.([]byte); ok { + if rc.bytesClone == nil { + rc.bytesClone = make(map[*byte][]byte) + } + clone, ok := rc.bytesClone[&bs[0]] + if !ok { + clone = make([]byte, len(bs)) + copy(clone, bs) + rc.bytesClone[&bs[0]] = clone + } + dest[i] = clone + } + } + return nil +} + +// legacyDriverString is like driver.String, but indirects pointers like +// DefaultValueConverter. +// +// This could be surprising behavior to retroactively apply to +// driver.String now that Go1 is out, but this is convenient for +// our TestPointerParamsAndScans. +type legacyDriverString struct{} + +func (legacyDriverString) ConvertValue(v interface{}) (driver.Value, error) { + switch c := v.(type) { + case string, []byte: + return v, nil + case *string: + if c == nil { + return nil, nil + } + return *c, nil + } + return fmt.Sprintf("%v", v), nil +} diff --git a/sentrysql/sentrysql_connector_test.go b/sentrysql/sentrysql_connector_test.go index 60c92262..9fbe0a19 100644 --- a/sentrysql/sentrysql_connector_test.go +++ b/sentrysql/sentrysql_connector_test.go @@ -34,7 +34,7 @@ func TestNewSentrySQLConnector_Integration(t *testing.T) { for _, query := range setupQueries { _, err := db.ExecContext(setupCtx, query) if err != nil { - t.Fatalf("initializing table on sqlite: %v", err) + t.Fatalf("initializing table on fakedb: %v", err) } } @@ -291,7 +291,7 @@ func TestNewSentrySQLConnector_Conn(t *testing.T) { for _, query := range setupQueries { _, err := db.ExecContext(setupCtx, query) if err != nil { - t.Fatalf("initializing table on sqlite: %v", err) + t.Fatalf("initializing table on fakedb: %v", err) } } @@ -547,7 +547,7 @@ func TestNewSentrySQLConnector_BeginTx(t *testing.T) { for _, query := range setupQueries { _, err := db.ExecContext(setupCtx, query) if err != nil { - t.Fatalf("initializing table on sqlite: %v", err) + t.Fatalf("initializing table on fakedb: %v", err) } } @@ -911,7 +911,7 @@ func TestNewSentrySQLConnector_PrepareContext(t *testing.T) { for _, query := range setupQueries { _, err := db.ExecContext(setupCtx, query) if err != nil { - t.Fatalf("initializing table on sqlite: %v", err) + t.Fatalf("initializing table on fakedb: %v", err) } } @@ -1165,7 +1165,7 @@ func TestNewSentrySQLConnector_NoParentSpan(t *testing.T) { for _, query := range setupQueries { _, err := db.ExecContext(setupCtx, query) if err != nil { - t.Fatalf("initializing table on sqlite: %v", err) + t.Fatalf("initializing table on fakedb: %v", err) } } diff --git a/sentrysql/sentrysql_legacy_test.go b/sentrysql/sentrysql_legacy_test.go new file mode 100644 index 00000000..66f76142 --- /dev/null +++ b/sentrysql/sentrysql_legacy_test.go @@ -0,0 +1,702 @@ +package sentrysql_test + +import ( + "context" + "database/sql" + "strings" + "testing" + "time" + + "github.com/getsentry/sentry-go" + "github.com/getsentry/sentry-go/internal/testutils" + "github.com/getsentry/sentry-go/sentrysql" + "github.com/google/go-cmp/cmp" +) + +func init() { + sql.Register("sentrysql-legacy", sentrysql.NewSentrySQL(ldriver, sentrysql.WithDatabaseSystem(sentrysql.DatabaseSystem("legacydb")), sentrysql.WithDatabaseName("fake"))) + +} + +//nolint:dupl +func TestNewSentrySQLLegacy_Integration(t *testing.T) { + db, err := sql.Open("sentrysql-legacy", "fake") + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + _, _ = db.Exec("WIPE") + _ = db.Close() + }) + setupQueries := []string{ + "CREATE|exec_test|id=int32,name=string", + "CREATE|query_test|id=int32,name=string,age=int32,created_at=string", + "INSERT|query_test|id=1,name=John,age=30,created_at=2023-01-01", + "INSERT|query_test|id=2,name=Jane,age=25,created_at=2023-01-02", + "INSERT|query_test|id=3,name=Bob,age=35,created_at=2023-01-03", + } + + setupCtx, cancelCtx := context.WithTimeout(context.Background(), 30*time.Second) + defer cancelCtx() + + for _, query := range setupQueries { + _, err := db.ExecContext(setupCtx, query) + if err != nil { + t.Fatalf("initializing table on legacydb: %v", err) + } + } + + t.Run("QueryContext", func(t *testing.T) { + tests := []struct { + Query string + Parameters []interface{} + WantSpan *sentry.Span + WantError bool + }{ + { + Query: "SELECT|query_test|id|id=?", + Parameters: []interface{}{1}, + WantSpan: &sentry.Span{ + Data: map[string]interface{}{ + "db.system": sentrysql.DatabaseSystem("fakedb"), + "db.name": "fake", + }, + Description: "SELECT|query_test|id|id=?", + Op: "db.sql.query", + Tags: nil, + Origin: "manual", + Sampled: sentry.SampledTrue, + Status: sentry.SpanStatusOK, + }, + }, + { + Query: "SELECT FROM query_test", + WantError: true, + WantSpan: &sentry.Span{ + Data: map[string]interface{}{ + "db.system": sentrysql.DatabaseSystem("fakedb"), + "db.name": "fake", + "db.operation": "SELECT", + }, + Description: "SELECT FROM query_test", + Op: "db.sql.query", + Tags: nil, + Origin: "manual", + Sampled: sentry.SampledTrue, + Status: sentry.SpanStatusInternalError, + }, + }, + } + + spansCh := make(chan []*sentry.Span, len(tests)) + + sentryClient, err := sentry.NewClient(sentry.ClientOptions{ + EnableTracing: true, + TracesSampleRate: 1.0, + BeforeSendTransaction: func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event { + spansCh <- event.Spans + return event + }, + }) + if err != nil { + t.Fatal(err) + } + + for _, tt := range tests { + hub := sentry.NewHub(sentryClient, sentry.NewScope()) + ctx, cancel := context.WithTimeout(sentry.SetHubOnContext(context.Background(), hub), 10*time.Second) + span := sentry.StartSpan(ctx, "fake_parent", sentry.WithTransactionName("Fake Parent")) + ctx = span.Context() + + rows, err := db.QueryContext(ctx, tt.Query, tt.Parameters...) + if err != nil && !tt.WantError { + cancel() + t.Fatal(err) + } + + if rows != nil { + _ = rows.Close() + } + + span.Finish() + cancel() + } + + if ok := sentryClient.Flush(testutils.FlushTimeout()); !ok { + t.Fatal("sentry.Flush timed out") + } + close(spansCh) + + var got [][]*sentry.Span + for e := range spansCh { + got = append(got, e) + } + + for i, tt := range tests { + var foundMatch = false + gotSpans := got[i] + + var diffs []string + for _, gotSpan := range gotSpans { + if diff := cmp.Diff(tt.WantSpan, gotSpan, optstrans); diff != "" { + diffs = append(diffs, diff) + } else { + foundMatch = true + break + } + } + + if len(diffs) == 0 && !foundMatch { + t.Logf("No span was found for query: %s", tt.Query) + return + } + + if !foundMatch { + t.Errorf("Span mismatch (-want +got):\n%s", strings.Join(diffs, "\n")) + } + } + }) + + t.Run("ExecContext", func(t *testing.T) { + tests := []struct { + Query string + Parameters []interface{} + WantSpan *sentry.Span + WantError bool + }{ + { + Query: "INSERT|exec_test|id=1,name=John", + Parameters: nil, + WantSpan: &sentry.Span{ + Data: map[string]interface{}{ + "db.system": sentrysql.DatabaseSystem("fakedb"), + "db.name": "fake", + }, + Description: "INSERT|exec_test|id=1,name=john", + Op: "db.sql.exec", + Tags: nil, + Origin: "manual", + Sampled: sentry.SampledTrue, + Status: sentry.SpanStatusOK, + }, + }, + { + Query: "CREATE|temporary_test|id=int32,name=string", + WantError: false, + WantSpan: &sentry.Span{ + Data: map[string]interface{}{ + "db.system": sentrysql.DatabaseSystem("fakedb"), + "db.name": "fake", + }, + Description: "CREATE|temporary_test|id=int32,name=string", + Op: "db.sql.exec", + Tags: nil, + Origin: "manual", + Sampled: sentry.SampledTrue, + Status: sentry.SpanStatusOK, + }, + }, + } + + spansCh := make(chan []*sentry.Span, len(tests)) + + sentryClient, err := sentry.NewClient(sentry.ClientOptions{ + EnableTracing: true, + TracesSampleRate: 1.0, + BeforeSendTransaction: func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event { + spansCh <- event.Spans + return event + }, + }) + if err != nil { + t.Fatal(err) + } + + for _, tt := range tests { + hub := sentry.NewHub(sentryClient, sentry.NewScope()) + ctx, cancel := context.WithTimeout(sentry.SetHubOnContext(context.Background(), hub), 10*time.Second) + span := sentry.StartSpan(ctx, "fake_parent", sentry.WithTransactionName("Fake Parent")) + ctx = span.Context() + + _, err := db.ExecContext(ctx, tt.Query, tt.Parameters...) + if err != nil && !tt.WantError { + cancel() + t.Fatal(err) + } + + span.Finish() + cancel() + } + + if ok := sentryClient.Flush(testutils.FlushTimeout()); !ok { + t.Fatal("sentry.Flush timed out") + } + close(spansCh) + + var got [][]*sentry.Span + for e := range spansCh { + got = append(got, e) + } + + for i, tt := range tests { + var foundMatch = false + gotSpans := got[i] + + var diffs []string + for _, gotSpan := range gotSpans { + if diff := cmp.Diff(tt.WantSpan, gotSpan, optstrans); diff != "" { + diffs = append(diffs, diff) + } else { + foundMatch = true + break + } + } + + if len(diffs) == 0 && !foundMatch { + t.Logf("No span was found for query: %s", tt.Query) + return + } + + if !foundMatch { + t.Errorf("Span mismatch (-want +got):\n%s", strings.Join(diffs, "\n")) + } + } + }) + + t.Run("Ping", func(t *testing.T) { + // Just checking if this works and doesn't panic + err := db.Ping() + if err != nil { + t.Fatal(err) + } + }) + + t.Run("PingContext", func(t *testing.T) { + // Just checking if this works and doesn't panic + err := db.PingContext(context.Background()) + if err != nil { + t.Fatal(err) + } + }) + + t.Run("Driver", func(t *testing.T) { + // Just checking if this works and doesn't panic + driver := db.Driver() + if driver == nil { + t.Fatal("driver is nil") + } + }) +} + +//nolint:dupl +func TestNewSentrySQLLegacy_Conn(t *testing.T) { + db, err := sql.Open("sentrysql-legacy", "fake") + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + _, _ = db.Exec("WIPE") + _ = db.Close() + }) + + setupQueries := []string{ + "CREATE|exec_test|id=int32,name=string", + "CREATE|query_test|id=int32,name=string,age=int32,created_at=string", + "INSERT|query_test|id=1,name=John,age=30,created_at=2023-01-01", + "INSERT|query_test|id=2,name=Jane,age=25,created_at=2023-01-02", + "INSERT|query_test|id=3,name=Bob,age=35,created_at=2023-01-03", + } + setupCtx, cancelCtx := context.WithTimeout(context.Background(), 30*time.Second) + defer cancelCtx() + + for _, query := range setupQueries { + _, err := db.ExecContext(setupCtx, query) + if err != nil { + t.Fatalf("initializing table on legacydb: %v", err) + } + } + + t.Run("QueryContext", func(t *testing.T) { + tests := []struct { + Query string + Parameters []interface{} + WantSpan *sentry.Span + WantError bool + }{ + { + Query: "SELECT|query_test|id|id=?", + Parameters: []interface{}{1}, + WantSpan: &sentry.Span{ + Data: map[string]interface{}{ + "db.system": sentrysql.DatabaseSystem("fakedb"), + "db.name": "fake", + }, + Description: "SELECT|query_test|id|id=?", + Op: "db.sql.query", + Tags: nil, + Origin: "manual", + Sampled: sentry.SampledTrue, + Status: sentry.SpanStatusOK, + }, + }, + { + Query: "SELECT FROM query_test", + Parameters: []interface{}{1}, + WantError: true, + WantSpan: &sentry.Span{ + Data: map[string]interface{}{ + "db.system": sentrysql.DatabaseSystem("fakedb"), + "db.name": "fake", + "db.operation": "SELECT", + }, + Description: "SELECT FROM query_test", + Op: "db.sql.query", + Tags: nil, + Origin: "manual", + Sampled: sentry.SampledTrue, + Status: sentry.SpanStatusInternalError, + }, + }, + } + + spansCh := make(chan []*sentry.Span, len(tests)) + + sentryClient, err := sentry.NewClient(sentry.ClientOptions{ + EnableTracing: true, + TracesSampleRate: 1.0, + BeforeSendTransaction: func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event { + spansCh <- event.Spans + return event + }, + }) + if err != nil { + t.Fatal(err) + } + + for _, tt := range tests { + hub := sentry.NewHub(sentryClient, sentry.NewScope()) + ctx, cancel := context.WithTimeout(sentry.SetHubOnContext(context.Background(), hub), 10*time.Second) + span := sentry.StartSpan(ctx, "fake_parent", sentry.WithTransactionName("Fake Parent")) + ctx = span.Context() + + conn, err := db.Conn(ctx) + if err != nil { + cancel() + t.Fatal(err) + } + + rows, err := conn.QueryContext(ctx, tt.Query, tt.Parameters...) + if err != nil && !tt.WantError { + _ = conn.Close() + cancel() + t.Fatal(err) + } + + if rows != nil { + _ = rows.Close() + } + + _ = conn.Close() + + span.Finish() + cancel() + } + + if ok := sentryClient.Flush(testutils.FlushTimeout()); !ok { + t.Fatal("sentry.Flush timed out") + } + close(spansCh) + + var got [][]*sentry.Span + for e := range spansCh { + got = append(got, e) + } + + for i, tt := range tests { + var foundMatch = false + gotSpans := got[i] + + var diffs []string + for _, gotSpan := range gotSpans { + if diff := cmp.Diff(tt.WantSpan, gotSpan, optstrans); diff != "" { + diffs = append(diffs, diff) + } else { + foundMatch = true + break + } + } + + if len(diffs) == 0 && !foundMatch { + t.Logf("No span was found for query: %s", tt.Query) + return + } + + if !foundMatch { + t.Errorf("Span mismatch (-want +got):\n%s", strings.Join(diffs, "\n")) + } + } + }) + + t.Run("ExecContext", func(t *testing.T) { + tests := []struct { + Query string + Parameters []interface{} + WantSpan *sentry.Span + WantError bool + }{ + { + Query: "INSERT|exec_test|id=?,name=?", + Parameters: []interface{}{2, "Peter"}, + WantSpan: &sentry.Span{ + Data: map[string]interface{}{ + "db.system": sentrysql.DatabaseSystem("fakedb"), + "db.name": "fake", + }, + Description: "INSERT|exec_test|id=?,name=?", + Op: "db.sql.exec", + Tags: nil, + Origin: "manual", + Sampled: sentry.SampledTrue, + Status: sentry.SpanStatusOK, + }, + }, + { + Query: "INSERT|exec_test|id=?,name=?", + Parameters: []interface{}{4, "John", "Doe", "John Doe"}, + WantError: true, + WantSpan: &sentry.Span{ + Data: map[string]interface{}{ + "db.system": sentrysql.DatabaseSystem("fakedb"), + "db.name": "fake", + }, + Description: "INSERT|exec_test|id=?,name=?", + Op: "db.sql.exec", + Tags: nil, + Origin: "manual", + Sampled: sentry.SampledTrue, + Status: sentry.SpanStatusInternalError, + }, + }, + } + + spansCh := make(chan []*sentry.Span, len(tests)) + + sentryClient, err := sentry.NewClient(sentry.ClientOptions{ + EnableTracing: true, + TracesSampleRate: 1.0, + BeforeSendTransaction: func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event { + spansCh <- event.Spans + return event + }, + }) + if err != nil { + t.Fatal(err) + } + + for _, tt := range tests { + hub := sentry.NewHub(sentryClient, sentry.NewScope()) + ctx, cancel := context.WithTimeout(sentry.SetHubOnContext(context.Background(), hub), 10*time.Second) + span := sentry.StartSpan(ctx, "fake_parent", sentry.WithTransactionName("Fake Parent")) + ctx = span.Context() + + conn, err := db.Conn(ctx) + if err != nil { + cancel() + t.Fatal(err) + } + + _, err = conn.ExecContext(ctx, tt.Query, tt.Parameters...) + if err != nil && !tt.WantError { + _ = conn.Close() + cancel() + t.Fatal(err) + } + + _ = conn.Close() + + span.Finish() + cancel() + } + + if ok := sentryClient.Flush(testutils.FlushTimeout()); !ok { + t.Fatal("sentry.Flush timed out") + } + close(spansCh) + + var got [][]*sentry.Span + for e := range spansCh { + got = append(got, e) + } + + for i, tt := range tests { + var foundMatch = false + gotSpans := got[i] + + var diffs []string + for _, gotSpan := range gotSpans { + if diff := cmp.Diff(tt.WantSpan, gotSpan, optstrans); diff != "" { + diffs = append(diffs, diff) + } else { + foundMatch = true + break + } + } + + if len(diffs) == 0 && !foundMatch { + t.Logf("No span was found for query: %s", tt.Query) + return + } + + if !foundMatch { + t.Errorf("Span mismatch (-want +got):\n%s", strings.Join(diffs, "\n")) + } + } + }) +} + +//nolint:dupl +func TestNewSentrySQLLegacy_NoParentSpan(t *testing.T) { + db, err := sql.Open("sentrysql-legacy", "fake") + if err != nil { + t.Fatal(err) + } + t.Cleanup(func() { + _, _ = db.Exec("WIPE") + _ = db.Close() + }) + + setupQueries := []string{ + "CREATE|exec_test|id=int32,name=string", + "CREATE|query_test|id=int32,name=string,age=int32,created_at=string", + "INSERT|query_test|id=1,name=John,age=30,created_at=2023-01-01", + "INSERT|query_test|id=2,name=Jane,age=25,created_at=2023-01-02", + "INSERT|query_test|id=3,name=Bob,age=35,created_at=2023-01-03", + } + setupCtx, cancelCtx := context.WithTimeout(context.Background(), 30*time.Second) + defer cancelCtx() + + for _, query := range setupQueries { + _, err := db.ExecContext(setupCtx, query) + if err != nil { + t.Fatalf("initializing table on legacydb: %v", err) + } + } + + t.Run("QueryContext", func(t *testing.T) { + tests := []struct { + Query string + Parameters []interface{} + WantSpan *sentry.Span + WantError bool + }{ + { + Query: "SELECT|query_test|id,name,age|id=?", + Parameters: []interface{}{1}, + WantSpan: nil, + }, + } + + spansCh := make(chan []*sentry.Span, len(tests)) + + sentryClient, err := sentry.NewClient(sentry.ClientOptions{ + EnableTracing: true, + TracesSampleRate: 1.0, + BeforeSendTransaction: func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event { + spansCh <- event.Spans + return event + }, + }) + if err != nil { + t.Fatal(err) + } + + for _, tt := range tests { + hub := sentry.NewHub(sentryClient, sentry.NewScope()) + ctx, cancel := context.WithTimeout(sentry.SetHubOnContext(context.Background(), hub), 10*time.Second) + + rows, err := db.QueryContext(ctx, tt.Query, tt.Parameters...) + if err != nil && !tt.WantError { + cancel() + t.Fatal(err) + } + + if rows != nil { + _ = rows.Close() + } + + cancel() + } + + if ok := sentryClient.Flush(testutils.FlushTimeout()); !ok { + t.Fatal("sentry.Flush timed out") + } + close(spansCh) + + var got [][]*sentry.Span + for e := range spansCh { + got = append(got, e) + } + + // `got` should be empty + if len(got) != 0 { + t.Errorf("got %d spans, want 0", len(got)) + } + }) + + t.Run("ExecContext", func(t *testing.T) { + tests := []struct { + Query string + Parameters []interface{} + WantSpan *sentry.Span + WantError bool + }{ + { + Query: "INSERT|exec_test|id=?,name=?", + Parameters: []interface{}{1, "John"}, + WantSpan: nil, + }, + } + + spansCh := make(chan []*sentry.Span, len(tests)) + + sentryClient, err := sentry.NewClient(sentry.ClientOptions{ + EnableTracing: true, + TracesSampleRate: 1.0, + BeforeSendTransaction: func(event *sentry.Event, hint *sentry.EventHint) *sentry.Event { + spansCh <- event.Spans + return event + }, + }) + if err != nil { + t.Fatal(err) + } + + for _, tt := range tests { + hub := sentry.NewHub(sentryClient, sentry.NewScope()) + ctx, cancel := context.WithTimeout(sentry.SetHubOnContext(context.Background(), hub), 10*time.Second) + + _, err := db.ExecContext(ctx, tt.Query, tt.Parameters...) + if err != nil && !tt.WantError { + cancel() + t.Fatal(err) + } + + cancel() + } + + if ok := sentryClient.Flush(testutils.FlushTimeout()); !ok { + t.Fatal("sentry.Flush timed out") + } + close(spansCh) + + var got [][]*sentry.Span + for e := range spansCh { + got = append(got, e) + } + + // `got` should be empty + if len(got) != 0 { + t.Errorf("got %d spans, want 0", len(got)) + } + }) +} diff --git a/sentrysql/stmt.go b/sentrysql/stmt.go index 51424065..c5fad261 100644 --- a/sentrysql/stmt.go +++ b/sentrysql/stmt.go @@ -78,6 +78,7 @@ func (s *sentryStmt) ExecContext(ctx context.Context, args []driver.NamedValue) if err != nil { return nil, err } + return s.Exec(values) } @@ -110,6 +111,7 @@ func (s *sentryStmt) QueryContext(ctx context.Context, args []driver.NamedValue) if err != nil { return nil, err } + return s.Query(values) } From f173533a24a3b9a0a3934e12eede58e75ce8aa91 Mon Sep 17 00:00:00 2001 From: Reinaldy Rafli Date: Sat, 2 Nov 2024 20:39:37 +0700 Subject: [PATCH 24/26] chore(sentrysql): remove unvisited context check --- sentrysql/conn.go | 6 ------ 1 file changed, 6 deletions(-) diff --git a/sentrysql/conn.go b/sentrysql/conn.go index 20158af2..17bef318 100644 --- a/sentrysql/conn.go +++ b/sentrysql/conn.go @@ -123,9 +123,6 @@ func (s *sentryConn) QueryContext(ctx context.Context, query string, args []driv // should only be executed if the original driver implements QueryerContext queryerContext, ok := s.originalConn.(driver.QueryerContext) if !ok { - if s.ctx == nil && ctx != nil { - s.ctx = ctx - } return nil, driver.ErrSkip } @@ -180,9 +177,6 @@ func (s *sentryConn) ExecContext(ctx context.Context, query string, args []drive // should only be executed if the original driver implements ExecerContext { execerContext, ok := s.originalConn.(driver.ExecerContext) if !ok { - if s.ctx == nil && ctx != nil { - s.ctx = ctx - } // ExecContext may return ErrSkip. return nil, driver.ErrSkip } From bcc99fcb89d774487b5e98dc1a707a8c15ae3160 Mon Sep 17 00:00:00 2001 From: Reinaldy Rafli Date: Sat, 2 Nov 2024 20:42:40 +0700 Subject: [PATCH 25/26] chore: lint --- sentrysql/sentrysql_legacy_test.go | 9 ++------- sentrysql/sentrysql_test.go | 2 ++ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/sentrysql/sentrysql_legacy_test.go b/sentrysql/sentrysql_legacy_test.go index 66f76142..5283f65f 100644 --- a/sentrysql/sentrysql_legacy_test.go +++ b/sentrysql/sentrysql_legacy_test.go @@ -13,12 +13,7 @@ import ( "github.com/google/go-cmp/cmp" ) -func init() { - sql.Register("sentrysql-legacy", sentrysql.NewSentrySQL(ldriver, sentrysql.WithDatabaseSystem(sentrysql.DatabaseSystem("legacydb")), sentrysql.WithDatabaseName("fake"))) - -} - -//nolint:dupl +//nolint:dupl,gocyclo func TestNewSentrySQLLegacy_Integration(t *testing.T) { db, err := sql.Open("sentrysql-legacy", "fake") if err != nil { @@ -288,7 +283,7 @@ func TestNewSentrySQLLegacy_Integration(t *testing.T) { }) } -//nolint:dupl +//nolint:dupl,gocyclo func TestNewSentrySQLLegacy_Conn(t *testing.T) { db, err := sql.Open("sentrysql-legacy", "fake") if err != nil { diff --git a/sentrysql/sentrysql_test.go b/sentrysql/sentrysql_test.go index c63536db..bef2f2d9 100644 --- a/sentrysql/sentrysql_test.go +++ b/sentrysql/sentrysql_test.go @@ -26,6 +26,8 @@ var optstrans = cmp.Options{ func TestMain(m *testing.M) { sql.Register("sentrysql-sqlite", sentrysql.NewSentrySQL(&sqlite.Driver{}, sentrysql.WithDatabaseName("memory"), sentrysql.WithDatabaseSystem(sentrysql.DatabaseSystem("sqlite")), sentrysql.WithServerAddress("localhost", "5432"))) + // sentrysql-legacy is used by `sentrysql_legacy_test.go` + sql.Register("sentrysql-legacy", sentrysql.NewSentrySQL(ldriver, sentrysql.WithDatabaseSystem(sentrysql.DatabaseSystem("legacydb")), sentrysql.WithDatabaseName("fake"))) os.Exit(m.Run()) } From 38ba84c4f120be559990bd16f1e6b2f0d0723c67 Mon Sep 17 00:00:00 2001 From: Reinaldy Rafli Date: Wed, 6 Nov 2024 21:24:21 +0700 Subject: [PATCH 26/26] chore(sentrysql): make sure we implement required interfaces --- sentrysql/conn.go | 18 ++++++++++++++++++ sentrysql/driver.go | 8 +++++++- sentrysql/sentrysql_legacy_test.go | 18 +++++++++--------- sentrysql/stmt.go | 3 +++ 4 files changed, 37 insertions(+), 10 deletions(-) diff --git a/sentrysql/conn.go b/sentrysql/conn.go index 17bef318..e04cef3a 100644 --- a/sentrysql/conn.go +++ b/sentrysql/conn.go @@ -26,6 +26,14 @@ type sentryConn struct { // Make sure that sentryConn implements the driver.Conn interface. var _ driver.Conn = (*sentryConn)(nil) +var _ driver.Pinger = (*sentryConn)(nil) +var _ driver.SessionResetter = (*sentryConn)(nil) +var _ driver.Validator = (*sentryConn)(nil) +var _ driver.ExecerContext = (*sentryConn)(nil) +var _ driver.QueryerContext = (*sentryConn)(nil) +var _ driver.ConnPrepareContext = (*sentryConn)(nil) +var _ driver.ConnBeginTx = (*sentryConn)(nil) +var _ driver.NamedValueChecker = (*sentryConn)(nil) func (s *sentryConn) Prepare(query string) (driver.Stmt, error) { stmt, err := s.originalConn.Prepare(query) @@ -229,3 +237,13 @@ func (s *sentryConn) CheckNamedValue(namedValue *driver.NamedValue) error { return namedValueChecker.CheckNamedValue(namedValue) } + +// IsValid implements driver.Validator. +func (s *sentryConn) IsValid() bool { + validator, ok := s.originalConn.(driver.Validator) + if !ok { + return true + } + + return validator.IsValid() +} diff --git a/sentrysql/driver.go b/sentrysql/driver.go index 652ca2a8..6808d2bb 100644 --- a/sentrysql/driver.go +++ b/sentrysql/driver.go @@ -16,12 +16,13 @@ type sentrySQLDriver struct { // Make sure that sentrySQLDriver implements the driver.Driver interface. var _ driver.Driver = (*sentrySQLDriver)(nil) +var _ driver.DriverContext = (*sentrySQLDriver)(nil) func (s *sentrySQLDriver) OpenConnector(name string) (driver.Connector, error) { driverContext, ok := s.originalDriver.(driver.DriverContext) if !ok { return &sentrySQLConnector{ - originalConnector: dsnConnector{dsn: name, driver: s.originalDriver}, + originalConnector: dsnConnector{dsn: name, driver: s.originalDriver, config: s.config}, config: s.config, }, nil } @@ -50,6 +51,7 @@ type sentrySQLConnector struct { // Make sure that sentrySQLConnector implements the driver.Connector interface. var _ driver.Connector = (*sentrySQLConnector)(nil) +var _ io.Closer = (*sentrySQLConnector)(nil) func (s *sentrySQLConnector) Connect(ctx context.Context) (driver.Conn, error) { conn, err := s.originalConnector.Connect(ctx) @@ -79,8 +81,12 @@ func (s *sentrySQLConnector) Close() error { type dsnConnector struct { dsn string driver driver.Driver + config *sentrySQLConfig } +// Make sure dsnConnector implements driver.Connector. +var _ driver.Connector = (*dsnConnector)(nil) + func (t dsnConnector) Connect(_ context.Context) (driver.Conn, error) { return t.driver.Open(t.dsn) } diff --git a/sentrysql/sentrysql_legacy_test.go b/sentrysql/sentrysql_legacy_test.go index 5283f65f..e0cb1ae9 100644 --- a/sentrysql/sentrysql_legacy_test.go +++ b/sentrysql/sentrysql_legacy_test.go @@ -53,7 +53,7 @@ func TestNewSentrySQLLegacy_Integration(t *testing.T) { Parameters: []interface{}{1}, WantSpan: &sentry.Span{ Data: map[string]interface{}{ - "db.system": sentrysql.DatabaseSystem("fakedb"), + "db.system": sentrysql.DatabaseSystem("legacydb"), "db.name": "fake", }, Description: "SELECT|query_test|id|id=?", @@ -69,7 +69,7 @@ func TestNewSentrySQLLegacy_Integration(t *testing.T) { WantError: true, WantSpan: &sentry.Span{ Data: map[string]interface{}{ - "db.system": sentrysql.DatabaseSystem("fakedb"), + "db.system": sentrysql.DatabaseSystem("legacydb"), "db.name": "fake", "db.operation": "SELECT", }, @@ -164,10 +164,10 @@ func TestNewSentrySQLLegacy_Integration(t *testing.T) { Parameters: nil, WantSpan: &sentry.Span{ Data: map[string]interface{}{ - "db.system": sentrysql.DatabaseSystem("fakedb"), + "db.system": sentrysql.DatabaseSystem("legacydb"), "db.name": "fake", }, - Description: "INSERT|exec_test|id=1,name=john", + Description: "INSERT|exec_test|id=1,name=John", Op: "db.sql.exec", Tags: nil, Origin: "manual", @@ -180,7 +180,7 @@ func TestNewSentrySQLLegacy_Integration(t *testing.T) { WantError: false, WantSpan: &sentry.Span{ Data: map[string]interface{}{ - "db.system": sentrysql.DatabaseSystem("fakedb"), + "db.system": sentrysql.DatabaseSystem("legacydb"), "db.name": "fake", }, Description: "CREATE|temporary_test|id=int32,name=string", @@ -323,7 +323,7 @@ func TestNewSentrySQLLegacy_Conn(t *testing.T) { Parameters: []interface{}{1}, WantSpan: &sentry.Span{ Data: map[string]interface{}{ - "db.system": sentrysql.DatabaseSystem("fakedb"), + "db.system": sentrysql.DatabaseSystem("legacydb"), "db.name": "fake", }, Description: "SELECT|query_test|id|id=?", @@ -340,7 +340,7 @@ func TestNewSentrySQLLegacy_Conn(t *testing.T) { WantError: true, WantSpan: &sentry.Span{ Data: map[string]interface{}{ - "db.system": sentrysql.DatabaseSystem("fakedb"), + "db.system": sentrysql.DatabaseSystem("legacydb"), "db.name": "fake", "db.operation": "SELECT", }, @@ -444,7 +444,7 @@ func TestNewSentrySQLLegacy_Conn(t *testing.T) { Parameters: []interface{}{2, "Peter"}, WantSpan: &sentry.Span{ Data: map[string]interface{}{ - "db.system": sentrysql.DatabaseSystem("fakedb"), + "db.system": sentrysql.DatabaseSystem("legacydb"), "db.name": "fake", }, Description: "INSERT|exec_test|id=?,name=?", @@ -461,7 +461,7 @@ func TestNewSentrySQLLegacy_Conn(t *testing.T) { WantError: true, WantSpan: &sentry.Span{ Data: map[string]interface{}{ - "db.system": sentrysql.DatabaseSystem("fakedb"), + "db.system": sentrysql.DatabaseSystem("legacydb"), "db.name": "fake", }, Description: "INSERT|exec_test|id=?,name=?", diff --git a/sentrysql/stmt.go b/sentrysql/stmt.go index c5fad261..2b986b1c 100644 --- a/sentrysql/stmt.go +++ b/sentrysql/stmt.go @@ -17,6 +17,9 @@ type sentryStmt struct { // Make sure sentryStmt implements driver.Stmt interface. var _ driver.Stmt = (*sentryStmt)(nil) +var _ driver.StmtExecContext = (*sentryStmt)(nil) +var _ driver.StmtQueryContext = (*sentryStmt)(nil) +var _ driver.NamedValueChecker = (*sentryStmt)(nil) func (s *sentryStmt) Close() error { return s.originalStmt.Close()