jackc / pgx

PostgreSQL driver and toolkit for Go

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

`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.