From e48e7a7189354fd7d2990b8abc2c40dfe5ee4c9f Mon Sep 17 00:00:00 2001 From: Jack Christensen Date: Fri, 20 Jan 2023 18:38:11 -0600 Subject: [PATCH] Fix scanning json column into **string refs https://github.com/jackc/pgx/issues/1470 --- pgtype/json.go | 17 ++++++++++++++--- pgtype/json_test.go | 15 +++++++++++++++ 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/pgtype/json.go b/pgtype/json.go index 9fef2fb6..69861bf8 100644 --- a/pgtype/json.go +++ b/pgtype/json.go @@ -95,11 +95,22 @@ func (JSONCodec) PlanScan(m *Map, oid uint32, format int16, target any) ScanPlan // https://github.com/jackc/pgx/issues/1418 case sql.Scanner: return &scanPlanSQLScanner{formatCode: format} - - default: - return scanPlanJSONToJSONUnmarshal{} } + // 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{} } type scanPlanAnyToString struct{} diff --git a/pgtype/json_test.go b/pgtype/json_test.go index 8ef6fb31..d72cfb24 100644 --- a/pgtype/json_test.go +++ b/pgtype/json_test.go @@ -101,6 +101,21 @@ func TestJSONCodecUnmarshalSQLNull(t *testing.T) { }) } +// https://github.com/jackc/pgx/issues/1470 +func TestJSONCodecPointerToPointerToString(t *testing.T) { + defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.Conn) { + var s *string + err := conn.QueryRow(ctx, "select '{}'::json").Scan(&s) + require.NoError(t, err) + require.NotNil(t, s) + require.Equal(t, "{}", *s) + + err = conn.QueryRow(ctx, "select null::json").Scan(&s) + require.NoError(t, err) + require.Nil(t, s) + }) +} + func TestJSONCodecClearExistingValueBeforeUnmarshal(t *testing.T) { defaultConnTestRunner.RunTest(context.Background(), t, func(ctx context.Context, t testing.TB, conn *pgx.Conn) { m := map[string]any{}