From d58fe2d53c641f267e8a7dbd06a4cde8aaeb9b39 Mon Sep 17 00:00:00 2001 From: Jack Christensen Date: Wed, 19 Jul 2023 20:54:05 -0500 Subject: [PATCH] Fix json scan of non-string pointer to pointer https://github.com/jackc/pgx/issues/1691 --- pgtype/json.go | 30 +++++++++++++++++------------- pgtype/json_test.go | 26 ++++++++++++++++++++++++++ 2 files changed, 43 insertions(+), 13 deletions(-) diff --git a/pgtype/json.go b/pgtype/json.go index b7a7101e..d332dd0d 100644 --- a/pgtype/json.go +++ b/pgtype/json.go @@ -92,6 +92,23 @@ func (JSONCodec) PlanScan(m *Map, oid uint32, format int16, target any) ScanPlan switch target.(type) { case *string: return scanPlanAnyToString{} + + case **string: + // This is to fix **string scanning. It seems wrong to special case **string, but it's not clear what a better + // solution would be. + // + // https://github.com/jackc/pgx/issues/1470 -- **string + // https://github.com/jackc/pgx/issues/1691 -- ** anything else + + if wrapperPlan, nextDst, ok := TryPointerPointerScanPlan(target); ok { + if nextPlan := m.planScan(oid, format, nextDst); nextPlan != nil { + if _, failed := nextPlan.(*scanPlanFail); !failed { + wrapperPlan.SetNext(nextPlan) + return wrapperPlan + } + } + } + case *[]byte: return scanPlanJSONToByteSlice{} case BytesScanner: @@ -104,19 +121,6 @@ func (JSONCodec) PlanScan(m *Map, oid uint32, format int16, target any) ScanPlan return &scanPlanSQLScanner{formatCode: format} } - // This is to fix **string scanning. It seems wrong to special case sql.Scanner and pointer to pointer, but it's not - // clear what a better solution would be. - // - // https://github.com/jackc/pgx/issues/1470 - if wrapperPlan, nextDst, ok := TryPointerPointerScanPlan(target); ok { - if nextPlan := m.planScan(oid, format, nextDst); nextPlan != nil { - if _, failed := nextPlan.(*scanPlanFail); !failed { - wrapperPlan.SetNext(nextPlan) - return wrapperPlan - } - } - } - return scanPlanJSONToJSONUnmarshal{} } diff --git a/pgtype/json_test.go b/pgtype/json_test.go index e171104f..b4f35d06 100644 --- a/pgtype/json_test.go +++ b/pgtype/json_test.go @@ -121,6 +121,32 @@ func TestJSONCodecPointerToPointerToString(t *testing.T) { }) } +// https://github.com/jackc/pgx/issues/1691 +func TestJSONCodecPointerToPointerToInt(t *testing.T) { + defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.Conn) { + n := 44 + p := &n + err := conn.QueryRow(ctx, "select 'null'::jsonb").Scan(&p) + require.NoError(t, err) + require.Nil(t, p) + }) +} + +// https://github.com/jackc/pgx/issues/1691 +func TestJSONCodecPointerToPointerToStruct(t *testing.T) { + defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.Conn) { + type ImageSize struct { + Height int `json:"height"` + Width int `json:"width"` + Str string `json:"str"` + } + is := &ImageSize{Height: 100, Width: 100, Str: "str"} + err := conn.QueryRow(ctx, `select 'null'::jsonb`).Scan(&is) + require.NoError(t, err) + require.Nil(t, is) + }) +} + func TestJSONCodecClearExistingValueBeforeUnmarshal(t *testing.T) { defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.Conn) { m := map[string]any{}