diff --git a/pgtype/pgtype.go b/pgtype/pgtype.go index 59d833a19..4c2532d23 100644 --- a/pgtype/pgtype.go +++ b/pgtype/pgtype.go @@ -1358,6 +1358,8 @@ var kindToTypes map[reflect.Kind]reflect.Type = map[reflect.Kind]reflect.Type{ reflect.Bool: reflect.TypeOf(false), } +var byteSliceType = reflect.TypeOf([]byte{}) + type underlyingTypeEncodePlan struct { nextValueType reflect.Type next EncodePlan @@ -1372,6 +1374,10 @@ func (plan *underlyingTypeEncodePlan) Encode(value any, buf []byte) (newBuf []by // TryWrapFindUnderlyingTypeEncodePlan tries to convert to a Go builtin type. e.g. If value was of type MyString and // MyString was defined as a string then a wrapper plan would be returned that converts MyString to string. func TryWrapFindUnderlyingTypeEncodePlan(value any) (plan WrappedEncodePlanNextSetter, nextValue any, ok bool) { + if value == nil { + return nil, nil, false + } + if _, ok := value.(driver.Valuer); ok { return nil, nil, false } @@ -1387,6 +1393,15 @@ func TryWrapFindUnderlyingTypeEncodePlan(value any) (plan WrappedEncodePlanNextS return &underlyingTypeEncodePlan{nextValueType: nextValueType}, refValue.Convert(nextValueType).Interface(), true } + // []byte is a special case. It is a slice but we treat it as a scalar type. In the case of a named type like + // json.RawMessage which is defined as []byte the underlying type should be considered as []byte. But any other slice + // does not have a special underlying type. + // + // https://github.com/jackc/pgx/issues/1763 + if refValue.Type() != byteSliceType && refValue.Type().AssignableTo(byteSliceType) { + return &underlyingTypeEncodePlan{nextValueType: byteSliceType}, refValue.Convert(byteSliceType).Interface(), true + } + return nil, nil, false } diff --git a/pgtype/pgtype_test.go b/pgtype/pgtype_test.go index 1262ee8a9..b6e3371ff 100644 --- a/pgtype/pgtype_test.go +++ b/pgtype/pgtype_test.go @@ -4,6 +4,7 @@ import ( "context" "database/sql" "database/sql/driver" + "encoding/json" "errors" "fmt" "net" @@ -417,6 +418,14 @@ func TestMapEncodeByteSliceIntoUnregisteredTypeTextFormat(t *testing.T) { require.Equal(t, []byte(`\x00010203`), buf) } +// https://github.com/jackc/pgx/issues/1763 +func TestMapEncodeNamedTypeOfByteSliceIntoTextTextFormat(t *testing.T) { + m := pgtype.NewMap() + buf, err := m.Encode(pgtype.TextOID, pgtype.TextFormatCode, json.RawMessage(`{"foo": "bar"}`), nil) + require.NoError(t, err) + require.Equal(t, []byte(`{"foo": "bar"}`), buf) +} + // https://github.com/jackc/pgx/issues/1326 func TestMapScanPointerToRenamedType(t *testing.T) { srcBuf := []byte("foo")