55 "math"
66 "testing"
77
8+ "github.com/jackc/pgx/v5/pgtype"
89 "github.com/jackc/pgx/v5/pgxtest"
910)
1011
@@ -22,3 +23,71 @@ func TestQcharTranscode(t *testing.T) {
2223 // Can only test with known OIDs as rune and byte would be considered numbers.
2324 pgxtest .RunValueRoundTripTests (context .Background (), t , defaultConnTestRunner , pgxtest .KnownOIDQueryExecModes , `"char"` , tests )
2425}
26+
27+ // TestQcharCodecPlanScanString is a regression test for https://github.com/jackc/pgx/issues/2314.
28+ //
29+ // Scanning a "char" column (OID 18) into *string used to succeed in TextFormat but
30+ // fail in BinaryFormat with "cannot scan char (OID 18) in binary format into *string".
31+ // Both formats must now produce the same result.
32+ func TestQcharCodecPlanScanString (t * testing.T ) {
33+ m := pgtype .NewMap ()
34+
35+ for _ , tt := range []struct {
36+ name string
37+ format int16
38+ }{
39+ {"text" , pgtype .TextFormatCode },
40+ {"binary" , pgtype .BinaryFormatCode },
41+ } {
42+ t .Run (tt .name , func (t * testing.T ) {
43+ var s string
44+ plan := m .PlanScan (pgtype .QCharOID , tt .format , & s )
45+ if plan == nil {
46+ t .Fatalf ("PlanScan returned nil plan for *string in %s format" , tt .name )
47+ }
48+ if err := plan .Scan ([]byte {'a' }, & s ); err != nil {
49+ t .Fatalf ("Scan failed in %s format: %v" , tt .name , err )
50+ }
51+ if s != "a" {
52+ t .Fatalf ("Scan result mismatch in %s format: got %q want %q" , tt .name , s , "a" )
53+ }
54+ })
55+ }
56+
57+ // Empty src must produce empty string (mirrors *byte / *rune zero-value behavior).
58+ t .Run ("empty-binary" , func (t * testing.T ) {
59+ var s string = "x"
60+ plan := m .PlanScan (pgtype .QCharOID , pgtype .BinaryFormatCode , & s )
61+ if err := plan .Scan ([]byte {}, & s ); err != nil {
62+ t .Fatalf ("Scan failed: %v" , err )
63+ }
64+ if s != "" {
65+ t .Fatalf ("empty src: got %q want %q" , s , "" )
66+ }
67+ })
68+
69+ // 0xC8 (200): a byte >= 128. Both formats must yield the raw 1-byte string
70+ // "\xc8", not the 2-byte UTF-8 encoding "\xc3\x88". This is the case that
71+ // catches string(src[0]) UTF-8-encoding the byte instead of copying it.
72+ t .Run ("non-ascii-byte" , func (t * testing.T ) {
73+ for _ , format := range []int16 {pgtype .TextFormatCode , pgtype .BinaryFormatCode } {
74+ var s string
75+ plan := m .PlanScan (pgtype .QCharOID , format , & s )
76+ if err := plan .Scan ([]byte {0xC8 }, & s ); err != nil {
77+ t .Fatalf ("format %d: scan failed: %v" , format , err )
78+ }
79+ if s != "\xc8 " {
80+ t .Fatalf ("format %d: got %q (% x) want %q" , format , s , s , "\xc8 " )
81+ }
82+ }
83+ })
84+
85+ // Multi-byte src is an invalid "char" payload and must error.
86+ t .Run ("too-long" , func (t * testing.T ) {
87+ var s string
88+ plan := m .PlanScan (pgtype .QCharOID , pgtype .BinaryFormatCode , & s )
89+ if err := plan .Scan ([]byte ("ab" ), & s ); err == nil {
90+ t .Fatalf ("expected error for 2-byte src, got nil (s=%q)" , s )
91+ }
92+ })
93+ }
0 commit comments