mirror of
https://gitee.com/johng/gf
synced 2026-06-07 10:22:11 +08:00
## Summary Fix two bytea data corruption issues in the PostgreSQL driver: 1. **READ path** (fixes #4677): `CheckLocalTypeForField` and `ConvertValueForLocal` had no case for plain `bytea` type, causing it to fall through to the Core layer which incorrectly mapped it to `LocalTypeString`. Binary data was then converted to string via `gconv.String()`, corrupting the bytes on retrieval. 2. **WRITE path** (fixes #4231): `ConvertValueForField` applied PostgreSQL array syntax conversion (`[` → `{`, `]` → `}`) to all slice types including `[]byte` for bytea columns, corrupting bytes `0x5B` (`[`) and `0x5D` (`]`) on insertion. ## Changes - **`contrib/drivers/pgsql/pgsql_convert.go`**: - `CheckLocalTypeForField`: Add `case "bytea"` → `LocalTypeBytes` - `ConvertValueForLocal`: Add `case "bytea"` to preserve `[]byte` as-is - `ConvertValueForField`: Skip `[]`→`{}` replacement for `[]byte` with `bytea` field type - **`contrib/drivers/pgsql/pgsql_z_unit_convert_test.go`**: - Add unit tests for `bytea` type in `CheckLocalTypeForField`, `ConvertValueForLocal`, and `ConvertValueForField` - **`contrib/drivers/pgsql/pgsql_z_unit_issue_test.go`**: - Add `Test_Issue4677`: End-to-end round-trip test with various binary data (including 0x00, 0x5B, 0x5D, 0xFF) - Add `Test_Issue4231`: Targeted test for 0x5D byte corruption on write ## Test plan - [x] `Test_CheckLocalTypeForField` - bytea returns `LocalTypeBytes` - [x] `Test_ConvertValueForLocal` - bytea preserves `[]byte` as-is - [x] `Test_ConvertValueForField` - bytea skips array syntax replacement - [x] `Test_Issue4677` - full DB round-trip with binary data - [x] `Test_Issue4231` - write path preserves 0x5B/0x5D bytes - [x] Full pgsql test suite passes with no regressions closes #4677 closes #4231 ref #4689
438 lines
13 KiB
Go
438 lines
13 KiB
Go
// Copyright GoFrame Author(https://goframe.org). All Rights Reserved.
|
|
//
|
|
// This Source Code Form is subject to the terms of the MIT License.
|
|
// If a copy of the MIT was not distributed with this file,
|
|
// You can obtain one at https://github.com/gogf/gf.
|
|
|
|
package pgsql_test
|
|
|
|
import (
|
|
"context"
|
|
"testing"
|
|
|
|
"github.com/google/uuid"
|
|
|
|
"github.com/gogf/gf/v2/database/gdb"
|
|
"github.com/gogf/gf/v2/test/gtest"
|
|
|
|
"github.com/gogf/gf/contrib/drivers/pgsql/v2"
|
|
)
|
|
|
|
// Test_CheckLocalTypeForField tests the CheckLocalTypeForField method
|
|
// for various PostgreSQL types
|
|
func Test_CheckLocalTypeForField(t *testing.T) {
|
|
var (
|
|
ctx = context.Background()
|
|
driver = pgsql.Driver{}
|
|
)
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
// Test basic integer types
|
|
localType, err := driver.CheckLocalTypeForField(ctx, "int2", nil)
|
|
t.AssertNil(err)
|
|
t.Assert(localType, gdb.LocalTypeInt)
|
|
|
|
localType, err = driver.CheckLocalTypeForField(ctx, "int4", nil)
|
|
t.AssertNil(err)
|
|
t.Assert(localType, gdb.LocalTypeInt)
|
|
|
|
localType, err = driver.CheckLocalTypeForField(ctx, "int8", nil)
|
|
t.AssertNil(err)
|
|
t.Assert(localType, gdb.LocalTypeInt64)
|
|
})
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
// Test integer array types
|
|
localType, err := driver.CheckLocalTypeForField(ctx, "_int2", nil)
|
|
t.AssertNil(err)
|
|
t.Assert(localType, gdb.LocalTypeInt32Slice)
|
|
|
|
localType, err = driver.CheckLocalTypeForField(ctx, "_int4", nil)
|
|
t.AssertNil(err)
|
|
t.Assert(localType, gdb.LocalTypeInt32Slice)
|
|
|
|
localType, err = driver.CheckLocalTypeForField(ctx, "_int8", nil)
|
|
t.AssertNil(err)
|
|
t.Assert(localType, gdb.LocalTypeInt64Slice)
|
|
})
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
// Test float array types
|
|
localType, err := driver.CheckLocalTypeForField(ctx, "_float4", nil)
|
|
t.AssertNil(err)
|
|
t.Assert(localType, gdb.LocalTypeFloat32Slice)
|
|
|
|
localType, err = driver.CheckLocalTypeForField(ctx, "_float8", nil)
|
|
t.AssertNil(err)
|
|
t.Assert(localType, gdb.LocalTypeFloat64Slice)
|
|
})
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
// Test boolean array type
|
|
localType, err := driver.CheckLocalTypeForField(ctx, "_bool", nil)
|
|
t.AssertNil(err)
|
|
t.Assert(localType, gdb.LocalTypeBoolSlice)
|
|
})
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
// Test string array types
|
|
localType, err := driver.CheckLocalTypeForField(ctx, "_varchar", nil)
|
|
t.AssertNil(err)
|
|
t.Assert(localType, gdb.LocalTypeStringSlice)
|
|
|
|
localType, err = driver.CheckLocalTypeForField(ctx, "_text", nil)
|
|
t.AssertNil(err)
|
|
t.Assert(localType, gdb.LocalTypeStringSlice)
|
|
|
|
localType, err = driver.CheckLocalTypeForField(ctx, "_char", nil)
|
|
t.AssertNil(err)
|
|
t.Assert(localType, gdb.LocalTypeStringSlice)
|
|
|
|
localType, err = driver.CheckLocalTypeForField(ctx, "_bpchar", nil)
|
|
t.AssertNil(err)
|
|
t.Assert(localType, gdb.LocalTypeStringSlice)
|
|
})
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
// Test numeric array types
|
|
localType, err := driver.CheckLocalTypeForField(ctx, "_numeric", nil)
|
|
t.AssertNil(err)
|
|
t.Assert(localType, gdb.LocalTypeFloat64Slice)
|
|
|
|
localType, err = driver.CheckLocalTypeForField(ctx, "_decimal", nil)
|
|
t.AssertNil(err)
|
|
t.Assert(localType, gdb.LocalTypeFloat64Slice)
|
|
|
|
localType, err = driver.CheckLocalTypeForField(ctx, "_money", nil)
|
|
t.AssertNil(err)
|
|
t.Assert(localType, gdb.LocalTypeFloat64Slice)
|
|
})
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
// Test bytea type
|
|
localType, err := driver.CheckLocalTypeForField(ctx, "bytea", nil)
|
|
t.AssertNil(err)
|
|
t.Assert(localType, gdb.LocalTypeBytes)
|
|
})
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
// Test bytea array type
|
|
localType, err := driver.CheckLocalTypeForField(ctx, "_bytea", nil)
|
|
t.AssertNil(err)
|
|
t.Assert(localType, gdb.LocalTypeBytesSlice)
|
|
})
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
// Test uuid type
|
|
localType, err := driver.CheckLocalTypeForField(ctx, "uuid", nil)
|
|
t.AssertNil(err)
|
|
t.Assert(localType, gdb.LocalTypeUUID)
|
|
})
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
// Test uuid array type
|
|
localType, err := driver.CheckLocalTypeForField(ctx, "_uuid", nil)
|
|
t.AssertNil(err)
|
|
t.Assert(localType, gdb.LocalTypeUUIDSlice)
|
|
})
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
// Test type with precision, e.g., "numeric(10,2)"
|
|
localType, err := driver.CheckLocalTypeForField(ctx, "int2(5)", nil)
|
|
t.AssertNil(err)
|
|
t.Assert(localType, gdb.LocalTypeInt)
|
|
|
|
localType, err = driver.CheckLocalTypeForField(ctx, "int4(10)", nil)
|
|
t.AssertNil(err)
|
|
t.Assert(localType, gdb.LocalTypeInt)
|
|
|
|
localType, err = driver.CheckLocalTypeForField(ctx, "INT8(20)", nil)
|
|
t.AssertNil(err)
|
|
t.Assert(localType, gdb.LocalTypeInt64)
|
|
})
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
// Test uppercase type names
|
|
localType, err := driver.CheckLocalTypeForField(ctx, "INT2", nil)
|
|
t.AssertNil(err)
|
|
t.Assert(localType, gdb.LocalTypeInt)
|
|
|
|
localType, err = driver.CheckLocalTypeForField(ctx, "_INT4", nil)
|
|
t.AssertNil(err)
|
|
t.Assert(localType, gdb.LocalTypeInt32Slice)
|
|
})
|
|
}
|
|
|
|
// Test_ConvertValueForLocal tests the ConvertValueForLocal method
|
|
func Test_ConvertValueForLocal(t *testing.T) {
|
|
var (
|
|
ctx = context.Background()
|
|
driver = pgsql.Driver{}
|
|
)
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
// Test _int2 array conversion
|
|
result, err := driver.ConvertValueForLocal(ctx, "_int2", []byte(`{1,2,3}`))
|
|
t.AssertNil(err)
|
|
t.Assert(result, []int32{1, 2, 3})
|
|
})
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
// Test _int4 array conversion
|
|
result, err := driver.ConvertValueForLocal(ctx, "_int4", []byte(`{10,20,30}`))
|
|
t.AssertNil(err)
|
|
t.Assert(result, []int32{10, 20, 30})
|
|
})
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
// Test _int8 array conversion
|
|
result, err := driver.ConvertValueForLocal(ctx, "_int8", []byte(`{100,200,300}`))
|
|
t.AssertNil(err)
|
|
t.Assert(result, []int64{100, 200, 300})
|
|
})
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
// Test _float4 array conversion
|
|
result, err := driver.ConvertValueForLocal(ctx, "_float4", []byte(`{1.1,2.2,3.3}`))
|
|
t.AssertNil(err)
|
|
resultArr := result.([]float32)
|
|
t.Assert(len(resultArr), 3)
|
|
t.Assert(resultArr[0] > 1.0 && resultArr[0] < 1.2, true)
|
|
t.Assert(resultArr[1] > 2.1 && resultArr[1] < 2.3, true)
|
|
t.Assert(resultArr[2] > 3.2 && resultArr[2] < 3.4, true)
|
|
})
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
// Test _float8 array conversion
|
|
result, err := driver.ConvertValueForLocal(ctx, "_float8", []byte(`{1.11,2.22,3.33}`))
|
|
t.AssertNil(err)
|
|
resultArr := result.([]float64)
|
|
t.Assert(len(resultArr), 3)
|
|
t.Assert(resultArr[0] > 1.1 && resultArr[0] < 1.12, true)
|
|
t.Assert(resultArr[1] > 2.21 && resultArr[1] < 2.23, true)
|
|
t.Assert(resultArr[2] > 3.32 && resultArr[2] < 3.34, true)
|
|
})
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
// Test _bool array conversion
|
|
result, err := driver.ConvertValueForLocal(ctx, "_bool", []byte(`{t,f,t}`))
|
|
t.AssertNil(err)
|
|
t.Assert(result, []bool{true, false, true})
|
|
})
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
// Test _varchar array conversion
|
|
result, err := driver.ConvertValueForLocal(ctx, "_varchar", []byte(`{a,b,c}`))
|
|
t.AssertNil(err)
|
|
t.Assert(result, []string{"a", "b", "c"})
|
|
})
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
// Test _text array conversion
|
|
result, err := driver.ConvertValueForLocal(ctx, "_text", []byte(`{hello,world}`))
|
|
t.AssertNil(err)
|
|
t.Assert(result, []string{"hello", "world"})
|
|
})
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
// Test _char array conversion
|
|
result, err := driver.ConvertValueForLocal(ctx, "_char", []byte(`{x,y,z}`))
|
|
t.AssertNil(err)
|
|
t.Assert(result, []string{"x", "y", "z"})
|
|
})
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
// Test _bpchar array conversion
|
|
result, err := driver.ConvertValueForLocal(ctx, "_bpchar", []byte(`{a,b}`))
|
|
t.AssertNil(err)
|
|
t.Assert(result, []string{"a", "b"})
|
|
})
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
// Test _numeric array conversion
|
|
result, err := driver.ConvertValueForLocal(ctx, "_numeric", []byte(`{1.11,2.22}`))
|
|
t.AssertNil(err)
|
|
resultArr := result.([]float64)
|
|
t.Assert(len(resultArr), 2)
|
|
})
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
// Test _decimal array conversion
|
|
result, err := driver.ConvertValueForLocal(ctx, "_decimal", []byte(`{3.33,4.44}`))
|
|
t.AssertNil(err)
|
|
resultArr := result.([]float64)
|
|
t.Assert(len(resultArr), 2)
|
|
})
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
// Test _money array conversion
|
|
result, err := driver.ConvertValueForLocal(ctx, "_money", []byte(`{5.55,6.66}`))
|
|
t.AssertNil(err)
|
|
resultArr := result.([]float64)
|
|
t.Assert(len(resultArr), 2)
|
|
})
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
// Test _bytea array conversion
|
|
result, err := driver.ConvertValueForLocal(ctx, "_bytea", []byte(`{"\\x68656c6c6f","\\x776f726c64"}`))
|
|
t.AssertNil(err)
|
|
resultArr := result.([][]byte)
|
|
t.Assert(len(resultArr), 2)
|
|
})
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
// Test uuid conversion from []byte
|
|
result, err := driver.ConvertValueForLocal(ctx, "uuid", []byte(`550e8400-e29b-41d4-a716-446655440000`))
|
|
t.AssertNil(err)
|
|
t.Assert(result.(uuid.UUID).String(), "550e8400-e29b-41d4-a716-446655440000")
|
|
})
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
// Test uuid conversion from string
|
|
result, err := driver.ConvertValueForLocal(ctx, "uuid", "550e8400-e29b-41d4-a716-446655440000")
|
|
t.AssertNil(err)
|
|
t.Assert(result.(uuid.UUID).String(), "550e8400-e29b-41d4-a716-446655440000")
|
|
})
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
// Test uuid conversion error case with invalid uuid
|
|
_, err := driver.ConvertValueForLocal(ctx, "uuid", "invalid-uuid")
|
|
t.AssertNE(err, nil)
|
|
})
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
// Test _uuid array conversion
|
|
result, err := driver.ConvertValueForLocal(ctx, "_uuid", []byte(`{550e8400-e29b-41d4-a716-446655440000,6ba7b810-9dad-11d1-80b4-00c04fd430c8}`))
|
|
t.AssertNil(err)
|
|
resultArr := result.([]uuid.UUID)
|
|
t.Assert(len(resultArr), 2)
|
|
t.Assert(resultArr[0].String(), "550e8400-e29b-41d4-a716-446655440000")
|
|
t.Assert(resultArr[1].String(), "6ba7b810-9dad-11d1-80b4-00c04fd430c8")
|
|
})
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
// Test _uuid array conversion error case
|
|
_, err := driver.ConvertValueForLocal(ctx, "_uuid", []byte(`{invalid-uuid}`))
|
|
t.AssertNE(err, nil)
|
|
})
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
// Test error case with invalid data for _int2
|
|
_, err := driver.ConvertValueForLocal(ctx, "_int2", "invalid")
|
|
t.AssertNE(err, nil)
|
|
})
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
// Test error case with invalid data for _int4
|
|
_, err := driver.ConvertValueForLocal(ctx, "_int4", "invalid")
|
|
t.AssertNE(err, nil)
|
|
})
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
// Test error case with invalid data for _int8
|
|
_, err := driver.ConvertValueForLocal(ctx, "_int8", "invalid")
|
|
t.AssertNE(err, nil)
|
|
})
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
// Test error case with invalid data for _float4
|
|
_, err := driver.ConvertValueForLocal(ctx, "_float4", "invalid")
|
|
t.AssertNE(err, nil)
|
|
})
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
// Test error case with invalid data for _float8
|
|
_, err := driver.ConvertValueForLocal(ctx, "_float8", "invalid")
|
|
t.AssertNE(err, nil)
|
|
})
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
// Test error case with invalid data for _bool
|
|
_, err := driver.ConvertValueForLocal(ctx, "_bool", "invalid")
|
|
t.AssertNE(err, nil)
|
|
})
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
// Test error case with invalid data for _varchar
|
|
_, err := driver.ConvertValueForLocal(ctx, "_varchar", 12345)
|
|
t.AssertNE(err, nil)
|
|
})
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
// Test error case with invalid data for _numeric
|
|
_, err := driver.ConvertValueForLocal(ctx, "_numeric", "invalid")
|
|
t.AssertNE(err, nil)
|
|
})
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
// Test error case with invalid data for _bytea
|
|
_, err := driver.ConvertValueForLocal(ctx, "_bytea", "invalid")
|
|
t.AssertNE(err, nil)
|
|
})
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
// Test bytea conversion - should preserve []byte as-is
|
|
input := []byte{0xDE, 0xAD, 0xBE, 0xEF, 0x00, 0x5D, 0x5B}
|
|
result, err := driver.ConvertValueForLocal(ctx, "bytea", input)
|
|
t.AssertNil(err)
|
|
resultBytes, ok := result.([]byte)
|
|
t.Assert(ok, true)
|
|
t.Assert(len(resultBytes), len(input))
|
|
t.Assert(resultBytes, input)
|
|
})
|
|
}
|
|
|
|
// Test_ConvertValueForField tests the ConvertValueForField method
|
|
func Test_ConvertValueForField(t *testing.T) {
|
|
var (
|
|
ctx = context.Background()
|
|
driver = pgsql.Driver{}
|
|
)
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
// Test nil value
|
|
result, err := driver.ConvertValueForField(ctx, "varchar", nil)
|
|
t.AssertNil(err)
|
|
t.Assert(result, nil)
|
|
})
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
// Test slice value for non-json type (should convert [] to {})
|
|
result, err := driver.ConvertValueForField(ctx, "int4[]", []int{1, 2, 3})
|
|
t.AssertNil(err)
|
|
t.Assert(result, "{1,2,3}")
|
|
})
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
// Test slice value for non-json type with strings
|
|
// Note: gconv.String for []string{"a","b","c"} produces ["a","b","c"] which then gets converted to {"a","b","c"}
|
|
result, err := driver.ConvertValueForField(ctx, "varchar[]", []string{"a", "b", "c"})
|
|
t.AssertNil(err)
|
|
t.Assert(result, `{"a","b","c"}`)
|
|
})
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
// Test slice value for json type (should keep [] as is)
|
|
result, err := driver.ConvertValueForField(ctx, "json", []int{1, 2, 3})
|
|
t.AssertNil(err)
|
|
t.Assert(result, "[1,2,3]")
|
|
})
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
// Test slice value for jsonb type (should keep [] as is)
|
|
result, err := driver.ConvertValueForField(ctx, "jsonb", []string{"a", "b"})
|
|
t.AssertNil(err)
|
|
t.Assert(result, `["a","b"]`)
|
|
})
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
// Test []byte value for bytea type (should preserve raw bytes, not do []->{} replacement)
|
|
input := []byte{0xDE, 0xAD, 0x5B, 0x5D, 0xBE, 0xEF}
|
|
result, err := driver.ConvertValueForField(ctx, "bytea", input)
|
|
t.AssertNil(err)
|
|
resultBytes, ok := result.([]byte)
|
|
t.Assert(ok, true)
|
|
t.Assert(resultBytes, input)
|
|
})
|
|
}
|