jackc / jackc/pgx

Inconsistent Parameter Handling Between QueryExecModeCacheStatement and QueryExecModeSimpleProtocol

Open
#2,157 1 comment 0 reactions 0 assignees View on GitHub
bug
Dominant language
Go
Stars
14.3k
Forks
1.1k
Avg merge
6d 9h
Merged PRs (30d)
11

Description

I'm not sure if this is a bug or more just a consequence of how parameter handling is implemented in the pgx library, especially when using custom types. But i've just noticed that using `QueryExecModeCacheStatement` vs `QueryExecModeSimpleProtocol` when building and executing a psql query results in two different sql queries, (and in my case one of which is wrong and causes an error):

**To Reproduce**

```go
package main

import (
"context"
"log"

"github.com/jackc/pgx/v5"
)

type SomeFancyNumber int

const (
FancyNumber1 SomeFancyNumber = iota
FancyNumber2
)

func (s SomeFancyNumber) String() string {
switch s {
case FancyNumber1:
return "fancy_number_1"
case FancyNumber2:
return "fancy_number_2"
}
return "unknown"
}

func main() {
simpleConn, err := pgx.Connect(context.Background(), "host=postgres port=5432 user=auser password=apassword dbname=adbname sslmode=disable default_query_exec_mode=simple_protocol")
if err != nil {
log.Fatal(err)
}
defer simpleConn.Close(context.Background())

cacheStatementConn, err := pgx.Connect(context.Background(), "host=postgres port=5432 user=auser password=apassword dbname=adbname sslmode=disable default_query_exec_mode=cache_statement")
if err != nil {
log.Fatal(err)
}
defer cacheStatementConn.Close(context.Background())

// Create a table and put one record in it
//
_, err = simpleConn.Exec(context.Background(), `CREATE TABLE IF NOT EXISTS test (id SERIAL PRIMARY KEY, anumber INT NOT NULL);`)
if err != nil {
log.Fatalf("Failed to create table: %v", err)
}
log.Println("Table 'test' created successfully!")

_, err = simpleConn.Exec(context.Background(), `INSERT INTO test (anumber) VALUES ($1);`, FancyNumber1) // Replace 42 with the desired value
if err != nil {
log.Printf("Simple protocol exec resulted in error %v", err)
} else {
log.Println("Record inserted successfully into 'test' table using simple protocol mode!")
}

_, err = cacheStatementConn.Exec(context.Background(), `INSERT INTO test (anumber) VALUES ($1);`, FancyNumber1) // Replace 42 with the desired value
if err != nil {
log.Printf("Cache statement exec resulted in error %v", err)
} else {
log.Println("Record inserted successfully into 'test' table using cache statement mode!")
}
}

```

**Expected behavior**

identical or equivalent resolved queries irregardless of the execution modes of `simple_protocol` and `cache_statement`

i.e a log entry of both
```
Record inserted successfully into 'test' table using simple protocol mode!
Record inserted successfully into 'test' table using cache statement mode!
```

**Actual behavior**

The queries are resolved differently for the different execution modes.

For `default_query_exec_mode=simple_protocol`; the query resolves to:

```sql
INSERT INTO test (anumber) VALUES ("fancy_number_1");
```

For `default_query_exec_mode=cache_statement`; the query resolves to:

```sql
INSERT INTO test (anumber) VALUES (0);
```

**Version**
- Go: `go1.22 darwin/amd64`
- PostgreSQL: `container image: postgres:16-alpine`
- pgx: `v5.7.1`

**Additional context**

This can be worked around by explicitly converting `SomeFancyNumber` to an `int` before passing it to the Exec function. While this may not a bug in the strictest sense, it does represent a design quirk in the library that may be helpful to document in the pgx library usage guidelines. Users switching modes between `cache_statement` and `simple_protocol` might not expect that some of their queries need to be tweaked/refactor.

Contributor guide

Open the contributing guide

Research direction

Start by running the provided Go reproduction against PostgreSQL with default_query_exec_mode set to simple_protocol and cache_statement, then compare how SomeFancyNumber is encoded. Trace parameter handling for both execution modes and check whether the differing results are intentional. Done means the behavior is aligned or the mode-specific limitation is documented with a clear workaround.

Written by the indexing model from the issue text.

Assessment

Tech stack
go, postgresql
Domain
databases
Issue type
Bug
Difficulty
3/5
Estimated time
1-2 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.