Missing `QueryTracer` `TraceQueryEnd()` call in conn `Query()` func
renanferr opened this issue · comments
Describe the bug
The Query Tracer is not working properly with the Query()
connection func. The current unit tests are using QueryRow()
and actually there are missing calls for the TraceQueryEnd()
func for several exec modes.
To Reproduce
Just run conn.Query()
with an active query tracer and check if TraceQueryEnd()
was called.
I wrote a unit test for this case in #1821 to better demonstrate.
Runnable example to reproduce:
package main
import (
"context"
"log"
"os"
"github.com/jackc/pgx/v5"
)
type testTracer struct {
traceQueryStart func(ctx context.Context, conn *pgx.Conn, data pgx.TraceQueryStartData) context.Context
traceQueryEnd func(ctx context.Context, conn *pgx.Conn, data pgx.TraceQueryEndData)
traceBatchStart func(ctx context.Context, conn *pgx.Conn, data pgx.TraceBatchStartData) context.Context
traceBatchQuery func(ctx context.Context, conn *pgx.Conn, data pgx.TraceBatchQueryData)
traceBatchEnd func(ctx context.Context, conn *pgx.Conn, data pgx.TraceBatchEndData)
traceCopyFromStart func(ctx context.Context, conn *pgx.Conn, data pgx.TraceCopyFromStartData) context.Context
traceCopyFromEnd func(ctx context.Context, conn *pgx.Conn, data pgx.TraceCopyFromEndData)
tracePrepareStart func(ctx context.Context, conn *pgx.Conn, data pgx.TracePrepareStartData) context.Context
tracePrepareEnd func(ctx context.Context, conn *pgx.Conn, data pgx.TracePrepareEndData)
traceConnectStart func(ctx context.Context, data pgx.TraceConnectStartData) context.Context
traceConnectEnd func(ctx context.Context, data pgx.TraceConnectEndData)
}
func (tt *testTracer) TraceQueryStart(ctx context.Context, conn *pgx.Conn, data pgx.TraceQueryStartData) context.Context {
if tt.traceQueryStart != nil {
return tt.traceQueryStart(ctx, conn, data)
}
return ctx
}
func (tt *testTracer) TraceQueryEnd(ctx context.Context, conn *pgx.Conn, data pgx.TraceQueryEndData) {
if tt.traceQueryEnd != nil {
tt.traceQueryEnd(ctx, conn, data)
}
}
func (tt *testTracer) TraceBatchStart(ctx context.Context, conn *pgx.Conn, data pgx.TraceBatchStartData) context.Context {
if tt.traceBatchStart != nil {
return tt.traceBatchStart(ctx, conn, data)
}
return ctx
}
func (tt *testTracer) TraceBatchQuery(ctx context.Context, conn *pgx.Conn, data pgx.TraceBatchQueryData) {
if tt.traceBatchQuery != nil {
tt.traceBatchQuery(ctx, conn, data)
}
}
func (tt *testTracer) TraceBatchEnd(ctx context.Context, conn *pgx.Conn, data pgx.TraceBatchEndData) {
if tt.traceBatchEnd != nil {
tt.traceBatchEnd(ctx, conn, data)
}
}
func (tt *testTracer) TraceCopyFromStart(ctx context.Context, conn *pgx.Conn, data pgx.TraceCopyFromStartData) context.Context {
if tt.traceCopyFromStart != nil {
return tt.traceCopyFromStart(ctx, conn, data)
}
return ctx
}
func (tt *testTracer) TraceCopyFromEnd(ctx context.Context, conn *pgx.Conn, data pgx.TraceCopyFromEndData) {
if tt.traceCopyFromEnd != nil {
tt.traceCopyFromEnd(ctx, conn, data)
}
}
func (tt *testTracer) TracePrepareStart(ctx context.Context, conn *pgx.Conn, data pgx.TracePrepareStartData) context.Context {
if tt.tracePrepareStart != nil {
return tt.tracePrepareStart(ctx, conn, data)
}
return ctx
}
func (tt *testTracer) TracePrepareEnd(ctx context.Context, conn *pgx.Conn, data pgx.TracePrepareEndData) {
if tt.tracePrepareEnd != nil {
tt.tracePrepareEnd(ctx, conn, data)
}
}
func (tt *testTracer) TraceConnectStart(ctx context.Context, data pgx.TraceConnectStartData) context.Context {
if tt.traceConnectStart != nil {
return tt.traceConnectStart(ctx, data)
}
return ctx
}
func (tt *testTracer) TraceConnectEnd(ctx context.Context, data pgx.TraceConnectEndData) {
if tt.traceConnectEnd != nil {
tt.traceConnectEnd(ctx, data)
}
}
func main() {
var calledStart bool
var calledEnd bool
cfg, err := pgx.ParseConfig(os.Getenv("DATABASE_URL"))
cfg.Tracer = &testTracer{
traceQueryStart: func(ctx context.Context, conn *pgx.Conn, data pgx.TraceQueryStartData) context.Context {
calledStart = true
return ctx
},
traceQueryEnd: func(ctx context.Context, conn *pgx.Conn, data pgx.TraceQueryEndData) {
calledEnd = true
},
}
conn, err := pgx.ConnectConfig(context.Background(), cfg)
if err != nil {
log.Fatal(err)
}
defer conn.Close(context.Background())
rows, err := conn.Query(context.Background(), "select 1")
if err != nil {
log.Fatal(err)
}
if rows.Err() != nil {
log.Fatal(rows.Err())
}
if !rows.Next() {
log.Fatal("no rows")
}
var d int
if err := rows.Scan(&d); err != nil {
log.Fatal(err)
}
log.Printf("result %d", d)
log.Printf("calledStart %v", calledStart)
log.Printf("calledEnd %v", calledEnd)
}
Expected behavior
The connection Query()
func should call TracerQueryStart()
and TracerQueryEnd()
just like QueryRow()
, Exec()
, etc.
Actual behavior
Query()
func calls TracerQueryStart()
but only calls TracerQueryEnd()
if this err check doesn't pass. Otherwise, no other calls are made neither in the conn Query()
func or the PgConn
ExecParams()
or ExecPrepared()
funcs.
Version
- Go:
go version go1.21.0 darwin/arm64
- PostgreSQL:
PostgreSQL 14.8 (Homebrew) on aarch64-apple-darwin22.4.0, compiled by Apple clang version 14.0.3 (clang-1403.0.22.14.1), 64-bit
- pgx:
v5.5.0
Additional context
I was working on a dd-trace contrib package to integrate datadog with pgx and noticed my tests were failing due to a pgx bug whenever I was working with the Query()
func.
Since I already have a branch with unit tests on it I can just fix it as well. Just wanted to make sure this is an actual bug.
The query is never finished in your example. Either Rows.Close()
needs to be called explicitly or Rows.Next()
needs to be called until it returns false.
Here is your example when it finishes the query.
package main
import (
"context"
"log"
"os"
"github.com/jackc/pgx/v5"
)
type testTracer struct {
traceQueryStart func(ctx context.Context, conn *pgx.Conn, data pgx.TraceQueryStartData) context.Context
traceQueryEnd func(ctx context.Context, conn *pgx.Conn, data pgx.TraceQueryEndData)
traceBatchStart func(ctx context.Context, conn *pgx.Conn, data pgx.TraceBatchStartData) context.Context
traceBatchQuery func(ctx context.Context, conn *pgx.Conn, data pgx.TraceBatchQueryData)
traceBatchEnd func(ctx context.Context, conn *pgx.Conn, data pgx.TraceBatchEndData)
traceCopyFromStart func(ctx context.Context, conn *pgx.Conn, data pgx.TraceCopyFromStartData) context.Context
traceCopyFromEnd func(ctx context.Context, conn *pgx.Conn, data pgx.TraceCopyFromEndData)
tracePrepareStart func(ctx context.Context, conn *pgx.Conn, data pgx.TracePrepareStartData) context.Context
tracePrepareEnd func(ctx context.Context, conn *pgx.Conn, data pgx.TracePrepareEndData)
traceConnectStart func(ctx context.Context, data pgx.TraceConnectStartData) context.Context
traceConnectEnd func(ctx context.Context, data pgx.TraceConnectEndData)
}
func (tt *testTracer) TraceQueryStart(ctx context.Context, conn *pgx.Conn, data pgx.TraceQueryStartData) context.Context {
if tt.traceQueryStart != nil {
return tt.traceQueryStart(ctx, conn, data)
}
return ctx
}
func (tt *testTracer) TraceQueryEnd(ctx context.Context, conn *pgx.Conn, data pgx.TraceQueryEndData) {
if tt.traceQueryEnd != nil {
tt.traceQueryEnd(ctx, conn, data)
}
}
func (tt *testTracer) TraceBatchStart(ctx context.Context, conn *pgx.Conn, data pgx.TraceBatchStartData) context.Context {
if tt.traceBatchStart != nil {
return tt.traceBatchStart(ctx, conn, data)
}
return ctx
}
func (tt *testTracer) TraceBatchQuery(ctx context.Context, conn *pgx.Conn, data pgx.TraceBatchQueryData) {
if tt.traceBatchQuery != nil {
tt.traceBatchQuery(ctx, conn, data)
}
}
func (tt *testTracer) TraceBatchEnd(ctx context.Context, conn *pgx.Conn, data pgx.TraceBatchEndData) {
if tt.traceBatchEnd != nil {
tt.traceBatchEnd(ctx, conn, data)
}
}
func (tt *testTracer) TraceCopyFromStart(ctx context.Context, conn *pgx.Conn, data pgx.TraceCopyFromStartData) context.Context {
if tt.traceCopyFromStart != nil {
return tt.traceCopyFromStart(ctx, conn, data)
}
return ctx
}
func (tt *testTracer) TraceCopyFromEnd(ctx context.Context, conn *pgx.Conn, data pgx.TraceCopyFromEndData) {
if tt.traceCopyFromEnd != nil {
tt.traceCopyFromEnd(ctx, conn, data)
}
}
func (tt *testTracer) TracePrepareStart(ctx context.Context, conn *pgx.Conn, data pgx.TracePrepareStartData) context.Context {
if tt.tracePrepareStart != nil {
return tt.tracePrepareStart(ctx, conn, data)
}
return ctx
}
func (tt *testTracer) TracePrepareEnd(ctx context.Context, conn *pgx.Conn, data pgx.TracePrepareEndData) {
if tt.tracePrepareEnd != nil {
tt.tracePrepareEnd(ctx, conn, data)
}
}
func (tt *testTracer) TraceConnectStart(ctx context.Context, data pgx.TraceConnectStartData) context.Context {
if tt.traceConnectStart != nil {
return tt.traceConnectStart(ctx, data)
}
return ctx
}
func (tt *testTracer) TraceConnectEnd(ctx context.Context, data pgx.TraceConnectEndData) {
if tt.traceConnectEnd != nil {
tt.traceConnectEnd(ctx, data)
}
}
func main() {
var calledStart bool
var calledEnd bool
cfg, err := pgx.ParseConfig(os.Getenv("DATABASE_URL"))
cfg.Tracer = &testTracer{
traceQueryStart: func(ctx context.Context, conn *pgx.Conn, data pgx.TraceQueryStartData) context.Context {
calledStart = true
return ctx
},
traceQueryEnd: func(ctx context.Context, conn *pgx.Conn, data pgx.TraceQueryEndData) {
calledEnd = true
},
}
conn, err := pgx.ConnectConfig(context.Background(), cfg)
if err != nil {
log.Fatal(err)
}
defer conn.Close(context.Background())
rows, err := conn.Query(context.Background(), "select 1")
if err != nil {
log.Fatal(err)
}
rowCount := 0
for rows.Next() {
var d int
if err := rows.Scan(&d); err != nil {
log.Fatal(err)
}
log.Printf("result %d", d)
rowCount++
}
if rows.Err() != nil {
log.Fatal(rows.Err())
}
if rowCount == 0 {
log.Fatal("no rows")
}
log.Printf("calledStart %v", calledStart)
log.Printf("calledEnd %v", calledEnd)
}
Results:
jack@glados ~/dev/pgx_issues/pgx-1820 ±master⚡ » go run .
2023/12/02 09:03:13 result 1
2023/12/02 09:03:13 calledStart true
2023/12/02 09:03:13 calledEnd true
Ohh, I didn't figure you had to call Next()
or Close()
to actually trigger the query end. Thank you very much for your quick response!