`PlanScan did not find a plan` when using `sql.Scanner` with float4 column (v5)
MattBrittan opened this issue · comments
Describe the bug
PlanScan did not find a plan
error when scanning float
into sql.Scanner
struct. This works with v4
but fails on v5
.
To Reproduce
Database setup:
CREATE TABLE pgxtest (
testcol float4
);
insert into pgxtest (testcol) values (1.0)
package main
import (
"context"
"fmt"
"os"
"github.com/jackc/pgx/v5"
)
func main() {
conn, err := pgx.Connect(context.Background(), `postgres://username:password@localhost:5432/database_name`)
if err != nil {
fmt.Fprintf(os.Stderr, "Unable to connect to database: %v\n", err)
os.Exit(1)
}
defer conn.Close(context.Background())
// This works OK
var testVal int64
err = conn.QueryRow(context.Background(), "select testcol from pgxtest").Scan(&testVal)
if err != nil {
fmt.Fprintf(os.Stderr, "QueryRow failed: %v\n", err)
os.Exit(1)
}
fmt.Println(testVal)
// This does not
var testVal2 scanner
err = conn.QueryRow(context.Background(), "select testcol from pgxtest").Scan(&testVal2)
if err != nil {
fmt.Fprintf(os.Stderr, "QueryRow failed: %v\n", err) // QueryRow failed: can't scan into dest[0]: PlanScan did not find a plan
os.Exit(1)
}
fmt.Println(testVal2)
}
type scanner struct {
val float64
}
func (s *scanner) Scan(src any) error {
if b, ok := src.(float64); ok {
s.val = b
return nil
}
return fmt.Errorf("unsupported input %T", src)
}
Expected behavior
Expected that scanner.Scan
would be called and passed an float64
.
Actual behavior
Fails before Scan
is called with error: QueryRow failed: can't scan into dest[0]: PlanScan did not find a plan
Version
go version go1.22.0 windows/amd64
PostgreSQL 15.5 on x86_64-pc-linux-musl, compiled by gcc (Alpine 13.2.1_git20231014) 13.2.1 20231014, 64-bit
github.com/jackc/pgx/v5 v5.5.3
Additional context
If I change to V4 (just change github.com/jackc/pgx/v5
to github.com/jackc/pgx/v4
) then this runs as expected.
I've looked through the code and it appears that the issue is in pgtype/float4
; in DecodeDatabaseSQLValue
it passes a *float64
into codecScan
which then calls Float4Codec.PlanScan
but that only supports *float32
or Float64Scanner
. I may be wrong about this as I'm not familiar with the code.
As a workaround I've added *float64
to (Float4Codec) PlanScan
and this appears to work OK. Happy to submit a PR but suspect this may have been excluded intentionally? e.g.
case BinaryFormatCode:
switch target.(type) {
case *float32:
return scanPlanBinaryFloat4ToFloat32{}
case *float64:
return scanPlanBinaryFloat4ToFloat64{}
case Float64Scanner:
return scanPlanBinaryFloat4ToFloat64Scanner{}
case Int64Scanner:
return scanPlanBinaryFloat4ToInt64Scanner{}
case TextScanner:
return scanPlanBinaryFloat4ToTextScanner{}
}
It may be possible to fix it in (Float4Codec) PlanScan
, but I think it is more direct to fix it in DecodeDatabaseSQLValue
by scanning into a float32
that is converted to a float64
on return.
Fixed in 85f15c4.