mirror of
https://gitee.com/johng/gf
synced 2026-06-06 16:21:40 +08:00
This pull request significantly improves PostgreSQL array type handling and conversion in the `pgsql` driver, providing more accurate type mapping and conversion logic, especially for array types. It introduces comprehensive documentation, refactors conversion logic to use the `pq` package for array types, and adds extensive unit tests to ensure correctness and error handling. Additionally, minor enhancements and clarifications are made to upsert formatting and table field queries. ### PostgreSQL Array Type Handling and Conversion * Refactored `CheckLocalTypeForField` and `ConvertValueForLocal` methods in `contrib/drivers/pgsql/pgsql_convert.go` to accurately map PostgreSQL array types (such as `_int2`, `_int4`, `_int8`, `_float4`, `_float8`, `_bool`, `_varchar`, `_text`, `_char`, `_bpchar`, `_numeric`, `_decimal`, `_money`, `_bytea`) to their corresponding Go types, using the `pq` package for conversion. Added detailed documentation and mapping tables for supported types. [[1]](diffhunk://#diff-a3b1e68bfa29fbcfda7c703bbe875fa82e958f6c3ad942ef82193a9dd8ad67e2R46-R63) [[2]](diffhunk://#diff-a3b1e68bfa29fbcfda7c703bbe875fa82e958f6c3ad942ef82193a9dd8ad67e2L56-R103) [[3]](diffhunk://#diff-a3b1e68bfa29fbcfda7c703bbe875fa82e958f6c3ad942ef82193a9dd8ad67e2R112-R209) * Added comprehensive unit tests in `contrib/drivers/pgsql/pgsql_z_unit_convert_test.go` to verify type mapping and conversion for all supported array types, including error cases for invalid input. ### Utility and API Improvements * Added a new `Bools()` method to the `gvar.Var` type in `container/gvar/gvar_slice.go` for converting values to `[]bool`, with corresponding unit tests in `container/gvar/gvar_z_unit_slice_test.go`. [[1]](diffhunk://#diff-32e887e540e0170f785508d105cb794e4d54d854b53b6950973c80022973c490R11-R15) [[2]](diffhunk://#diff-01453eca4d4b3e35d07ca105cb924c6441d0cd9df6cbcc337a89832c8d53057fR24-R41) ### SQL Formatting and Documentation * Improved documentation and formatting in the upsert logic of `contrib/drivers/pgsql/pgsql_format_upsert.go` to clarify the use of `EXCLUDED` in PostgreSQL's `ON CONFLICT DO UPDATE`. * Enhanced readability of the table field query in `contrib/drivers/pgsql/pgsql_table_fields.go` by reformatting SQL and clarifying field extraction. --------- Co-authored-by: hailaz <739476267@qq.com> Co-authored-by: houseme <housemecn@gmail.com>
410 lines
12 KiB
Go
410 lines
12 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 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)
|
|
})
|
|
}
|
|
|
|
// 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"]`)
|
|
})
|
|
}
|