mirror of
https://gitee.com/johng/gf
synced 2026-06-06 16:21:40 +08:00
This pull request introduces significant improvements to the handling of the `Replace` and `Save` operations for multiple database drivers, especially for MSSQL and PostgreSQL. The changes ensure that these operations now auto-detect primary keys when conflict columns are not explicitly provided, improving usability and aligning behavior across drivers. Additionally, the pull request updates related tests to reflect these enhancements and includes some minor documentation and code cleanup. **Key changes:** ### Enhanced Replace/Save Logic for Database Drivers * **MSSQL Driver:** - `Replace` and `Save` operations now auto-detect primary keys if `OnConflict` is not specified, using the `MERGE` statement for upsert functionality. If no primary key is found in the data, a detailed error is returned. [[1]](diffhunk://#diff-87815aa559a927e2de09bd05148f9841dfc06a1b5f3ecc5e3d5fcb80323a87f8L23-R61) [[2]](diffhunk://#diff-87815aa559a927e2de09bd05148f9841dfc06a1b5f3ecc5e3d5fcb80323a87f8L43-L59) - Updated tests to verify that `Replace` correctly updates or inserts records, and that missing conflict columns are properly handled. [[1]](diffhunk://#diff-bdbde9d7d6ee14c795343767b414740c4396f4dd3e97788b1f9d4e615405a42dL141-R151) [[2]](diffhunk://#diff-26338e93e473300b1313936eb0f6826546473793442f24715fa294b595f7a805L2661-R2707) * **PostgreSQL Driver:** - Similar to MSSQL, `Replace` and `Save` now auto-detect primary keys for conflict resolution if `OnConflict` is not set, and treat `Replace` as a `Save` operation. - Adjusted tests to ensure `Save` and `Replace` work as expected, including verifying data replacement and insertion. [[1]](diffhunk://#diff-c22703c37ebb6836c332f7cd2ada570577ba4564fe39886db02f7c2d0e7a2048L93-R93) [[2]](diffhunk://#diff-c22703c37ebb6836c332f7cd2ada570577ba4564fe39886db02f7c2d0e7a2048R102) [[3]](diffhunk://#diff-c22703c37ebb6836c332f7cd2ada570577ba4564fe39886db02f7c2d0e7a2048L110-R130) * **DM Driver:** - Improved conflict detection: now checks that at least one primary key exists in the provided data when `OnConflict` is not specified, and provides clearer error messages. - Refactored to use the core method for primary key detection and removed redundant code. ### Minor Improvements and Documentation * Added clarifying comments to `DoInsert` methods for ClickHouse, DM, MSSQL, Oracle, and PostgreSQL drivers, specifying that the input list must have at least one validated record. [[1]](diffhunk://#diff-f2e003895041ed3c52b91bb8c270696adc3528d77c39d2f7137af3396267444cR19) [[2]](diffhunk://#diff-f51b30e3f0b0f1284b905385a89992efd0de2fe9ff8c5a4062344dfab17d428eR23) [[3]](diffhunk://#diff-87815aa559a927e2de09bd05148f9841dfc06a1b5f3ecc5e3d5fcb80323a87f8L23-R61) [[4]](diffhunk://#diff-f61dac3fcfd5df4a3936cd8743499c8c0fc45f4f5d0f5398ed84a0cb1603202cR24) [[5]](diffhunk://#diff-c1dfed79aaa3a432057d2bd74d270e4b4094ebcf72984f1161d4972bea009410R16-R72) * Minor code and comment cleanups, including improved formatting and error handling. [[1]](diffhunk://#diff-f61dac3fcfd5df4a3936cd8743499c8c0fc45f4f5d0f5398ed84a0cb1603202cR37) [[2]](diffhunk://#diff-f61dac3fcfd5df4a3936cd8743499c8c0fc45f4f5d0f5398ed84a0cb1603202cL96-R98) [[3]](diffhunk://#diff-f61dac3fcfd5df4a3936cd8743499c8c0fc45f4f5d0f5398ed84a0cb1603202cL106-L116) [[4]](diffhunk://#diff-a17b44c76aaac53d1f164a2bb9440a5531659f4355e7ccfabdadff8dc8633c09L170-R171) [[5]](diffhunk://#diff-56189fa9ae1df51716b50d34d7fe56bfe67a330e8ac2c6b0de7b958db6817ed5R83-R98) ### Workflow and Documentation Updates * Updated example Docker commands in the CI workflow for consistency and clarity. [[1]](diffhunk://#diff-a1a3cb9bdeb5541d148091d973cf266aa3b317e6415a86630e816cbe27cf8b9cL57-R57) [[2]](diffhunk://#diff-a1a3cb9bdeb5541d148091d973cf266aa3b317e6415a86630e816cbe27cf8b9cL78-R78) [[3]](diffhunk://#diff-a1a3cb9bdeb5541d148091d973cf266aa3b317e6415a86630e816cbe27cf8b9cL92-R92) [[4]](diffhunk://#diff-a1a3cb9bdeb5541d148091d973cf266aa3b317e6415a86630e816cbe27cf8b9cL106-R106) [[5]](diffhunk://#diff-a1a3cb9bdeb5541d148091d973cf266aa3b317e6415a86630e816cbe27cf8b9cL153-R153) [[6]](diffhunk://#diff-a1a3cb9bdeb5541d148091d973cf266aa3b317e6415a86630e816cbe27cf8b9cL164-R164) * Removed outdated note about `Replace` support from the SQLite driver documentation. These changes improve the consistency, reliability, and developer experience when performing upsert operations across different database backends. --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: Lance Add <1196661499@qq.com>
955 lines
28 KiB
Go
955 lines
28 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 (
|
|
"fmt"
|
|
"testing"
|
|
|
|
"github.com/google/uuid"
|
|
|
|
"github.com/gogf/gf/v2/frame/g"
|
|
"github.com/gogf/gf/v2/test/gtest"
|
|
)
|
|
|
|
// Test_TableFields tests the TableFields method for retrieving table field information
|
|
func Test_TableFields(t *testing.T) {
|
|
table := createAllTypesTable()
|
|
defer dropTable(table)
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
fields, err := db.TableFields(ctx, table)
|
|
t.AssertNil(err)
|
|
t.Assert(len(fields) > 0, true)
|
|
|
|
// Test primary key field
|
|
t.Assert(fields["id"].Name, "id")
|
|
t.Assert(fields["id"].Key, "pri")
|
|
|
|
// Test integer types
|
|
t.Assert(fields["col_int2"].Name, "col_int2")
|
|
t.Assert(fields["col_int4"].Name, "col_int4")
|
|
t.Assert(fields["col_int8"].Name, "col_int8")
|
|
|
|
// Test float types
|
|
t.Assert(fields["col_float4"].Name, "col_float4")
|
|
t.Assert(fields["col_float8"].Name, "col_float8")
|
|
t.Assert(fields["col_numeric"].Name, "col_numeric")
|
|
|
|
// Test character types
|
|
t.Assert(fields["col_char"].Name, "col_char")
|
|
t.Assert(fields["col_varchar"].Name, "col_varchar")
|
|
t.Assert(fields["col_text"].Name, "col_text")
|
|
|
|
// Test boolean type
|
|
t.Assert(fields["col_bool"].Name, "col_bool")
|
|
|
|
// Test date/time types
|
|
t.Assert(fields["col_date"].Name, "col_date")
|
|
t.Assert(fields["col_timestamp"].Name, "col_timestamp")
|
|
|
|
// Test JSON types
|
|
t.Assert(fields["col_json"].Name, "col_json")
|
|
t.Assert(fields["col_jsonb"].Name, "col_jsonb")
|
|
|
|
// Test array types
|
|
t.Assert(fields["col_int2_arr"].Name, "col_int2_arr")
|
|
t.Assert(fields["col_int4_arr"].Name, "col_int4_arr")
|
|
t.Assert(fields["col_varchar_arr"].Name, "col_varchar_arr")
|
|
})
|
|
}
|
|
|
|
// Test_TableFields_Types tests field type information
|
|
func Test_TableFields_Types(t *testing.T) {
|
|
table := createAllTypesTable()
|
|
defer dropTable(table)
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
fields, err := db.TableFields(ctx, table)
|
|
t.AssertNil(err)
|
|
|
|
// Test integer type names
|
|
t.Assert(fields["col_int2"].Type, "int2(16)")
|
|
t.Assert(fields["col_int4"].Type, "int4(32)")
|
|
t.Assert(fields["col_int8"].Type, "int8(64)")
|
|
|
|
// Test float type names
|
|
t.Assert(fields["col_float4"].Type, "float4(24)")
|
|
t.Assert(fields["col_float8"].Type, "float8(53)")
|
|
t.Assert(fields["col_numeric"].Type, "numeric(10)")
|
|
|
|
// Test character type names
|
|
t.Assert(fields["col_char"].Type, "bpchar(10)")
|
|
t.Assert(fields["col_varchar"].Type, "varchar(100)")
|
|
t.Assert(fields["col_text"].Type, "text")
|
|
|
|
// Test boolean type name
|
|
t.Assert(fields["col_bool"].Type, "bool")
|
|
|
|
// Test date/time type names
|
|
t.Assert(fields["col_date"].Type, "date")
|
|
t.Assert(fields["col_timestamp"].Type, "timestamp")
|
|
t.Assert(fields["col_timestamptz"].Type, "timestamptz")
|
|
|
|
// Test JSON type names
|
|
t.Assert(fields["col_json"].Type, "json")
|
|
t.Assert(fields["col_jsonb"].Type, "jsonb")
|
|
|
|
// Test array type names (PostgreSQL uses _ prefix for array types)
|
|
t.Assert(fields["col_int2_arr"].Type, "_int2")
|
|
t.Assert(fields["col_int4_arr"].Type, "_int4")
|
|
t.Assert(fields["col_int8_arr"].Type, "_int8")
|
|
t.Assert(fields["col_float4_arr"].Type, "_float4")
|
|
t.Assert(fields["col_float8_arr"].Type, "_float8")
|
|
t.Assert(fields["col_numeric_arr"].Type, "_numeric")
|
|
t.Assert(fields["col_varchar_arr"].Type, "_varchar")
|
|
t.Assert(fields["col_text_arr"].Type, "_text")
|
|
t.Assert(fields["col_bool_arr"].Type, "_bool")
|
|
})
|
|
}
|
|
|
|
// Test_TableFields_Nullable tests field nullable information
|
|
func Test_TableFields_Nullable(t *testing.T) {
|
|
table := createAllTypesTable()
|
|
defer dropTable(table)
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
fields, err := db.TableFields(ctx, table)
|
|
t.AssertNil(err)
|
|
|
|
// NOT NULL fields should have Null = false
|
|
t.Assert(fields["col_int2"].Null, false)
|
|
t.Assert(fields["col_int4"].Null, false)
|
|
t.Assert(fields["col_numeric"].Null, false)
|
|
t.Assert(fields["col_varchar"].Null, false)
|
|
t.Assert(fields["col_bool"].Null, false)
|
|
t.Assert(fields["col_varchar_arr"].Null, false)
|
|
|
|
// Nullable fields should have Null = true
|
|
t.Assert(fields["col_int8"].Null, true)
|
|
t.Assert(fields["col_text"].Null, true)
|
|
t.Assert(fields["col_json"].Null, true)
|
|
})
|
|
}
|
|
|
|
// Test_TableFields_Comments tests field comment information
|
|
func Test_TableFields_Comments(t *testing.T) {
|
|
table := createAllTypesTable()
|
|
defer dropTable(table)
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
fields, err := db.TableFields(ctx, table)
|
|
t.AssertNil(err)
|
|
|
|
// Test fields with comments
|
|
t.Assert(fields["id"].Comment, "Primary key ID")
|
|
t.Assert(fields["col_int2"].Comment, "int2 type (smallint)")
|
|
t.Assert(fields["col_int4"].Comment, "int4 type (integer)")
|
|
t.Assert(fields["col_int8"].Comment, "int8 type (bigint)")
|
|
t.Assert(fields["col_numeric"].Comment, "numeric type with precision")
|
|
t.Assert(fields["col_varchar"].Comment, "varchar type")
|
|
t.Assert(fields["col_bool"].Comment, "boolean type")
|
|
t.Assert(fields["col_timestamp"].Comment, "timestamp type")
|
|
t.Assert(fields["col_json"].Comment, "json type")
|
|
t.Assert(fields["col_jsonb"].Comment, "jsonb type")
|
|
|
|
// Test array field comments
|
|
t.Assert(fields["col_int2_arr"].Comment, "int2 array type (_int2)")
|
|
t.Assert(fields["col_int4_arr"].Comment, "int4 array type (_int4)")
|
|
t.Assert(fields["col_int8_arr"].Comment, "int8 array type (_int8)")
|
|
t.Assert(fields["col_numeric_arr"].Comment, "numeric array type (_numeric)")
|
|
t.Assert(fields["col_varchar_arr"].Comment, "varchar array type (_varchar)")
|
|
t.Assert(fields["col_text_arr"].Comment, "text array type (_text)")
|
|
})
|
|
}
|
|
|
|
// Test_Field_Type_Conversion tests type conversion for various PostgreSQL types
|
|
func Test_Field_Type_Conversion(t *testing.T) {
|
|
table := createInitAllTypesTable()
|
|
defer dropTable(table)
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
// Query a single record
|
|
one, err := db.Model(table).Where("id", 1).One()
|
|
t.AssertNil(err)
|
|
t.Assert(one.IsEmpty(), false)
|
|
|
|
// Test integer type conversions
|
|
t.Assert(one["col_int2"].Int(), 1)
|
|
t.Assert(one["col_int4"].Int(), 10)
|
|
t.Assert(one["col_int8"].Int64(), int64(100))
|
|
|
|
// Test float type conversions
|
|
t.Assert(one["col_float4"].Float32() > 0, true)
|
|
t.Assert(one["col_float8"].Float64() > 0, true)
|
|
|
|
// Test string type conversions
|
|
t.AssertNE(one["col_varchar"].String(), "")
|
|
t.AssertNE(one["col_text"].String(), "")
|
|
|
|
// Test boolean type conversion
|
|
t.Assert(one["col_bool"].Bool(), false) // i=1, 1%2==0 is false
|
|
})
|
|
}
|
|
|
|
// Test_Field_Array_Type_Conversion tests array type conversion
|
|
func Test_Field_Array_Type_Conversion(t *testing.T) {
|
|
table := createInitAllTypesTable()
|
|
defer dropTable(table)
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
// Query a single record
|
|
one, err := db.Model(table).Where("id", 1).One()
|
|
t.AssertNil(err)
|
|
t.Assert(one.IsEmpty(), false)
|
|
|
|
// Test integer array type conversions
|
|
int2Arr := one["col_int2_arr"].Ints()
|
|
t.Assert(len(int2Arr), 3)
|
|
t.Assert(int2Arr[0], 1)
|
|
t.Assert(int2Arr[1], 2)
|
|
t.Assert(int2Arr[2], 1)
|
|
|
|
int4Arr := one["col_int4_arr"].Ints()
|
|
t.Assert(len(int4Arr), 3)
|
|
t.Assert(int4Arr[0], 10)
|
|
t.Assert(int4Arr[1], 20)
|
|
t.Assert(int4Arr[2], 1)
|
|
|
|
int8Arr := one["col_int8_arr"].Int64s()
|
|
t.Assert(len(int8Arr), 3)
|
|
t.Assert(int8Arr[0], int64(100))
|
|
t.Assert(int8Arr[1], int64(200))
|
|
t.Assert(int8Arr[2], int64(1))
|
|
|
|
// Test string array type conversions
|
|
varcharArr := one["col_varchar_arr"].Strings()
|
|
t.Assert(len(varcharArr), 3)
|
|
t.Assert(varcharArr[0], "a")
|
|
t.Assert(varcharArr[1], "b")
|
|
t.Assert(varcharArr[2], "c1")
|
|
|
|
textArr := one["col_text_arr"].Strings()
|
|
t.Assert(len(textArr), 3)
|
|
t.Assert(textArr[0], "x")
|
|
t.Assert(textArr[1], "y")
|
|
t.Assert(textArr[2], "z1")
|
|
|
|
// Test boolean array type conversions
|
|
// col_bool_arr is '{true, false, %t}' where %t = i%2==0, for i=1 it's false
|
|
boolArr := one["col_bool_arr"].Bools()
|
|
t.Assert(len(boolArr), 3)
|
|
t.Assert(boolArr[0], true) // literal true
|
|
t.Assert(boolArr[1], false) // literal false
|
|
t.Assert(boolArr[2], false) // i=1, 1%2==0 is false
|
|
})
|
|
}
|
|
|
|
// Test_Field_Array_Insert tests inserting array data
|
|
func Test_Field_Array_Insert(t *testing.T) {
|
|
table := createAllTypesTable()
|
|
defer dropTable(table)
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
// Insert with array values
|
|
_, err := db.Model(table).Data(g.Map{
|
|
"col_int2": 1,
|
|
"col_int4": 10,
|
|
"col_numeric": 99.99,
|
|
"col_varchar": "test",
|
|
"col_bool": true,
|
|
"col_int2_arr": []int{1, 2, 3},
|
|
"col_int4_arr": []int{10, 20, 30},
|
|
"col_varchar_arr": []string{"a", "b", "c"},
|
|
}).Insert()
|
|
t.AssertNil(err)
|
|
|
|
// Query and verify
|
|
one, err := db.Model(table).OrderDesc("id").One()
|
|
t.AssertNil(err)
|
|
|
|
t.Assert(one["col_int2"].Int(), 1)
|
|
t.Assert(one["col_varchar"].String(), "test")
|
|
t.Assert(one["col_bool"].Bool(), true)
|
|
|
|
int2Arr := one["col_int2_arr"].Ints()
|
|
t.Assert(len(int2Arr), 3)
|
|
t.Assert(int2Arr[0], 1)
|
|
t.Assert(int2Arr[1], 2)
|
|
t.Assert(int2Arr[2], 3)
|
|
|
|
varcharArr := one["col_varchar_arr"].Strings()
|
|
t.Assert(len(varcharArr), 3)
|
|
t.Assert(varcharArr[0], "a")
|
|
t.Assert(varcharArr[1], "b")
|
|
t.Assert(varcharArr[2], "c")
|
|
})
|
|
}
|
|
|
|
// Test_Field_Array_Update tests updating array data
|
|
func Test_Field_Array_Update(t *testing.T) {
|
|
table := createInitAllTypesTable()
|
|
defer dropTable(table)
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
// Update array values
|
|
_, err := db.Model(table).Where("id", 1).Data(g.Map{
|
|
"col_int2_arr": []int{100, 200, 300},
|
|
"col_varchar_arr": []string{"x", "y", "z"},
|
|
}).Update()
|
|
t.AssertNil(err)
|
|
|
|
// Query and verify
|
|
one, err := db.Model(table).Where("id", 1).One()
|
|
t.AssertNil(err)
|
|
|
|
int2Arr := one["col_int2_arr"].Ints()
|
|
t.Assert(len(int2Arr), 3)
|
|
t.Assert(int2Arr[0], 100)
|
|
t.Assert(int2Arr[1], 200)
|
|
t.Assert(int2Arr[2], 300)
|
|
|
|
varcharArr := one["col_varchar_arr"].Strings()
|
|
t.Assert(len(varcharArr), 3)
|
|
t.Assert(varcharArr[0], "x")
|
|
t.Assert(varcharArr[1], "y")
|
|
t.Assert(varcharArr[2], "z")
|
|
})
|
|
}
|
|
|
|
// Test_Field_JSON_Type tests JSON/JSONB type handling
|
|
func Test_Field_JSON_Type(t *testing.T) {
|
|
table := createAllTypesTable()
|
|
defer dropTable(table)
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
// Insert with JSON values
|
|
testData := g.Map{
|
|
"name": "test",
|
|
"value": 123,
|
|
"items": []string{"a", "b", "c"},
|
|
}
|
|
_, err := db.Model(table).Data(g.Map{
|
|
"col_int2": 1,
|
|
"col_int4": 10,
|
|
"col_numeric": 99.99,
|
|
"col_varchar": "test",
|
|
"col_bool": true,
|
|
"col_json": testData,
|
|
"col_jsonb": testData,
|
|
}).Insert()
|
|
t.AssertNil(err)
|
|
|
|
// Query and verify
|
|
one, err := db.Model(table).OrderDesc("id").One()
|
|
t.AssertNil(err)
|
|
|
|
// Test JSON field
|
|
jsonMap := one["col_json"].Map()
|
|
t.Assert(jsonMap["name"], "test")
|
|
t.Assert(jsonMap["value"], 123)
|
|
|
|
// Test JSONB field
|
|
jsonbMap := one["col_jsonb"].Map()
|
|
t.Assert(jsonbMap["name"], "test")
|
|
t.Assert(jsonbMap["value"], 123)
|
|
})
|
|
}
|
|
|
|
// Test_Field_Scan_To_Struct tests scanning results to struct
|
|
func Test_Field_Scan_To_Struct(t *testing.T) {
|
|
table := createInitAllTypesTable()
|
|
defer dropTable(table)
|
|
|
|
type TestRecord struct {
|
|
Id int64 `json:"id"`
|
|
ColInt2 int16 `json:"col_int2"`
|
|
ColInt4 int32 `json:"col_int4"`
|
|
ColInt8 int64 `json:"col_int8"`
|
|
ColVarchar string `json:"col_varchar"`
|
|
ColBool bool `json:"col_bool"`
|
|
ColInt2Arr []int `json:"col_int2_arr"`
|
|
ColInt4Arr []int `json:"col_int4_arr"`
|
|
ColInt8Arr []int64 `json:"col_int8_arr"`
|
|
ColTextArr []string `json:"col_text_arr"`
|
|
}
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
var record TestRecord
|
|
err := db.Model(table).Where("id", 1).Scan(&record)
|
|
t.AssertNil(err)
|
|
|
|
t.Assert(record.Id, int64(1))
|
|
t.Assert(record.ColInt2, int16(1))
|
|
t.Assert(record.ColInt4, int32(10))
|
|
t.Assert(record.ColInt8, int64(100))
|
|
t.AssertNE(record.ColVarchar, "")
|
|
t.Assert(record.ColBool, false)
|
|
|
|
// Test array fields scanned to struct
|
|
t.Assert(len(record.ColInt2Arr), 3)
|
|
t.Assert(record.ColInt2Arr[0], 1)
|
|
t.Assert(record.ColInt2Arr[1], 2)
|
|
t.Assert(record.ColInt2Arr[2], 1)
|
|
|
|
t.Assert(len(record.ColTextArr), 3)
|
|
t.Assert(record.ColTextArr[0], "x")
|
|
t.Assert(record.ColTextArr[1], "y")
|
|
t.Assert(record.ColTextArr[2], "z1")
|
|
})
|
|
}
|
|
|
|
// Test_Field_Scan_To_Struct_Slice tests scanning multiple results to struct slice
|
|
func Test_Field_Scan_To_Struct_Slice(t *testing.T) {
|
|
table := createInitAllTypesTable()
|
|
defer dropTable(table)
|
|
|
|
type TestRecord struct {
|
|
Id int64 `json:"id"`
|
|
ColInt2 int16 `json:"col_int2"`
|
|
ColVarchar string `json:"col_varchar"`
|
|
ColInt2Arr []int `json:"col_int2_arr"`
|
|
ColTextArr []string `json:"col_text_arr"`
|
|
}
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
var records []TestRecord
|
|
err := db.Model(table).OrderAsc("id").Limit(5).Scan(&records)
|
|
t.AssertNil(err)
|
|
|
|
t.Assert(len(records), 5)
|
|
|
|
// Verify first record
|
|
t.Assert(records[0].Id, int64(1))
|
|
t.Assert(records[0].ColInt2, int16(1))
|
|
t.Assert(len(records[0].ColInt2Arr), 3)
|
|
|
|
// Verify last record
|
|
t.Assert(records[4].Id, int64(5))
|
|
t.Assert(records[4].ColInt2, int16(5))
|
|
})
|
|
}
|
|
|
|
// Test_Field_Empty_Array tests handling empty arrays
|
|
func Test_Field_Empty_Array(t *testing.T) {
|
|
table := createAllTypesTable()
|
|
defer dropTable(table)
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
// Insert with empty array values (using default)
|
|
_, err := db.Model(table).Data(g.Map{
|
|
"col_int2": 1,
|
|
"col_int4": 10,
|
|
"col_numeric": 99.99,
|
|
"col_varchar": "test",
|
|
"col_bool": true,
|
|
}).Insert()
|
|
t.AssertNil(err)
|
|
|
|
// Query and verify empty arrays
|
|
one, err := db.Model(table).OrderDesc("id").One()
|
|
t.AssertNil(err)
|
|
|
|
// Default empty arrays
|
|
int2Arr := one["col_int2_arr"].Ints()
|
|
t.Assert(len(int2Arr), 0)
|
|
|
|
varcharArr := one["col_varchar_arr"].Strings()
|
|
t.Assert(len(varcharArr), 0)
|
|
})
|
|
}
|
|
|
|
// Test_Field_Null_Values tests handling NULL values
|
|
func Test_Field_Null_Values(t *testing.T) {
|
|
table := createAllTypesTable()
|
|
defer dropTable(table)
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
// Insert minimal required fields, leaving nullable fields as NULL
|
|
_, err := db.Model(table).Data(g.Map{
|
|
"col_int2": 1,
|
|
"col_int4": 10,
|
|
"col_numeric": 99.99,
|
|
"col_varchar": "test",
|
|
"col_bool": true,
|
|
"col_varchar_arr": []string{},
|
|
}).Insert()
|
|
t.AssertNil(err)
|
|
|
|
// Query and verify NULL handling
|
|
one, err := db.Model(table).OrderDesc("id").One()
|
|
t.AssertNil(err)
|
|
|
|
// Nullable fields should return appropriate zero values
|
|
t.Assert(one["col_text"].IsNil() || one["col_text"].IsEmpty(), true)
|
|
t.Assert(one["col_int8_arr"].IsNil() || one["col_int8_arr"].IsEmpty(), true)
|
|
})
|
|
}
|
|
|
|
// Test_Field_Float_Array_Type_Conversion tests float array type conversion (_float4, _float8)
|
|
func Test_Field_Float_Array_Type_Conversion(t *testing.T) {
|
|
table := createInitAllTypesTable()
|
|
defer dropTable(table)
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
// Query a single record
|
|
one, err := db.Model(table).Where("id", 1).One()
|
|
t.AssertNil(err)
|
|
t.Assert(one.IsEmpty(), false)
|
|
|
|
// Test float4 array type conversions
|
|
float4Arr := one["col_float4_arr"].Float32s()
|
|
t.Assert(len(float4Arr), 3)
|
|
t.Assert(float4Arr[0] > 0, true)
|
|
t.Assert(float4Arr[1] > 0, true)
|
|
|
|
// Test float8 array type conversions
|
|
float8Arr := one["col_float8_arr"].Float64s()
|
|
t.Assert(len(float8Arr), 3)
|
|
t.Assert(float8Arr[0] > 0, true)
|
|
t.Assert(float8Arr[1] > 0, true)
|
|
})
|
|
}
|
|
|
|
// Test_Field_Numeric_Array_Type_Conversion tests numeric/decimal array type conversion
|
|
func Test_Field_Numeric_Array_Type_Conversion(t *testing.T) {
|
|
table := createInitAllTypesTable()
|
|
defer dropTable(table)
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
// Query a single record
|
|
one, err := db.Model(table).Where("id", 1).One()
|
|
t.AssertNil(err)
|
|
t.Assert(one.IsEmpty(), false)
|
|
|
|
// Test numeric array type conversions
|
|
numericArr := one["col_numeric_arr"].Float64s()
|
|
t.Assert(len(numericArr), 3)
|
|
t.Assert(numericArr[0] > 0, true)
|
|
t.Assert(numericArr[1] > 0, true)
|
|
|
|
// Test decimal array type conversions
|
|
decimalArr := one["col_decimal_arr"].Float64s()
|
|
if !one["col_decimal_arr"].IsNil() {
|
|
t.Assert(len(decimalArr) > 0, true)
|
|
}
|
|
})
|
|
}
|
|
|
|
// Test_Field_Bool_Array_Type_Conversion tests bool array type conversion more thoroughly
|
|
func Test_Field_Bool_Array_Type_Conversion(t *testing.T) {
|
|
table := createAllTypesTable()
|
|
defer dropTable(table)
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
// Insert with specific bool array values
|
|
_, err := db.Model(table).Data(g.Map{
|
|
"col_int2": 1,
|
|
"col_int4": 10,
|
|
"col_numeric": 99.99,
|
|
"col_varchar": "test",
|
|
"col_bool": true,
|
|
"col_bool_arr": []bool{true, false, true},
|
|
}).Insert()
|
|
t.AssertNil(err)
|
|
|
|
// Query and verify
|
|
one, err := db.Model(table).OrderDesc("id").One()
|
|
t.AssertNil(err)
|
|
|
|
// Test bool array
|
|
boolArr := one["col_bool_arr"].Bools()
|
|
t.Assert(len(boolArr), 3)
|
|
t.Assert(boolArr[0], true)
|
|
t.Assert(boolArr[1], false)
|
|
t.Assert(boolArr[2], true)
|
|
})
|
|
}
|
|
|
|
// Test_Field_Char_Array_Type tests char array type (_char)
|
|
func Test_Field_Char_Array_Type(t *testing.T) {
|
|
table := createAllTypesTable()
|
|
defer dropTable(table)
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
// Insert with char array values
|
|
_, err := db.Model(table).Data(g.Map{
|
|
"col_int2": 1,
|
|
"col_int4": 10,
|
|
"col_numeric": 99.99,
|
|
"col_varchar": "test",
|
|
"col_bool": true,
|
|
"col_char_arr": []string{"a", "b", "c"},
|
|
"col_varchar_arr": []string{},
|
|
}).Insert()
|
|
t.AssertNil(err)
|
|
|
|
// Query and verify
|
|
one, err := db.Model(table).OrderDesc("id").One()
|
|
t.AssertNil(err)
|
|
|
|
// Test char array
|
|
charArr := one["col_char_arr"].Strings()
|
|
t.Assert(len(charArr), 3)
|
|
})
|
|
}
|
|
|
|
// Test_Field_Bytea_Type tests bytea (binary) type conversion
|
|
func Test_Field_Bytea_Type(t *testing.T) {
|
|
table := createAllTypesTable()
|
|
defer dropTable(table)
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
// Insert with binary data
|
|
binaryData := []byte{0x48, 0x65, 0x6c, 0x6c, 0x6f} // "Hello" in hex
|
|
_, err := db.Model(table).Data(g.Map{
|
|
"col_int2": 1,
|
|
"col_int4": 10,
|
|
"col_numeric": 99.99,
|
|
"col_varchar": "test",
|
|
"col_bool": true,
|
|
"col_bytea": binaryData,
|
|
"col_varchar_arr": []string{},
|
|
}).Insert()
|
|
t.AssertNil(err)
|
|
|
|
// Query and verify
|
|
one, err := db.Model(table).OrderDesc("id").One()
|
|
t.AssertNil(err)
|
|
|
|
// Test bytea field
|
|
result := one["col_bytea"].Bytes()
|
|
t.Assert(len(result), 5)
|
|
t.Assert(result[0], 0x48) // 'H'
|
|
})
|
|
}
|
|
|
|
// Test_Field_Bytea_Array_Type tests bytea array type (_bytea)
|
|
func Test_Field_Bytea_Array_Type(t *testing.T) {
|
|
table := createAllTypesTable()
|
|
defer dropTable(table)
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
// Insert with bytea array values using raw SQL
|
|
// PostgreSQL bytea array literal format: ARRAY[E'\\x010203', E'\\x040506']::bytea[]
|
|
_, err := db.Exec(ctx, fmt.Sprintf(`
|
|
INSERT INTO %s (col_int2, col_int4, col_numeric, col_varchar, col_bool, col_varchar_arr, col_bytea_arr)
|
|
VALUES (1, 10, 99.99, 'test', true, '{}', ARRAY[E'\\x010203', E'\\x040506']::bytea[])
|
|
`, table))
|
|
t.AssertNil(err)
|
|
|
|
// Query and verify bytea array
|
|
one, err := db.Model(table).OrderDesc("id").One()
|
|
t.AssertNil(err)
|
|
|
|
// Test bytea array field - should be converted to [][]byte
|
|
byteaArrVal := one["col_bytea_arr"]
|
|
t.Assert(byteaArrVal.IsNil(), false)
|
|
|
|
// Verify the array contains the expected data
|
|
byteaArr := byteaArrVal.Interfaces()
|
|
t.Assert(len(byteaArr), 2)
|
|
})
|
|
}
|
|
|
|
// Test_Field_Date_Array_Type tests date array type (_date)
|
|
func Test_Field_Date_Array_Type(t *testing.T) {
|
|
table := createAllTypesTable()
|
|
defer dropTable(table)
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
// Note: PostgreSQL _date array is not yet mapped in the driver
|
|
// This test documents the limitation but can be extended when support is added
|
|
|
|
_, err := db.Model(table).Data(g.Map{
|
|
"col_int2": 1,
|
|
"col_int4": 10,
|
|
"col_numeric": 99.99,
|
|
"col_varchar": "test",
|
|
"col_bool": true,
|
|
"col_varchar_arr": []string{},
|
|
}).Insert()
|
|
t.AssertNil(err)
|
|
|
|
// Query and verify NULL date array is handled gracefully
|
|
one, err := db.Model(table).OrderDesc("id").One()
|
|
t.AssertNil(err)
|
|
// date array should be nil or empty
|
|
t.Assert(one["col_date_arr"].IsNil() || one["col_date_arr"].IsEmpty(), true)
|
|
})
|
|
}
|
|
|
|
// Test_Field_Timestamp_Array_Type tests timestamp array type (_timestamp)
|
|
func Test_Field_Timestamp_Array_Type(t *testing.T) {
|
|
table := createAllTypesTable()
|
|
defer dropTable(table)
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
// Note: PostgreSQL _timestamp array is not yet mapped in the driver
|
|
// This test documents the limitation but can be extended when support is added
|
|
|
|
_, err := db.Model(table).Data(g.Map{
|
|
"col_int2": 1,
|
|
"col_int4": 10,
|
|
"col_numeric": 99.99,
|
|
"col_varchar": "test",
|
|
"col_bool": true,
|
|
"col_varchar_arr": []string{},
|
|
}).Insert()
|
|
t.AssertNil(err)
|
|
|
|
// Query and verify NULL timestamp array is handled gracefully
|
|
one, err := db.Model(table).OrderDesc("id").One()
|
|
t.AssertNil(err)
|
|
// timestamp array should be nil or empty
|
|
t.Assert(one["col_timestamp_arr"].IsNil() || one["col_timestamp_arr"].IsEmpty(), true)
|
|
})
|
|
}
|
|
|
|
// Test_Field_JSONB_Array_Type tests JSONB array type (_jsonb)
|
|
func Test_Field_JSONB_Array_Type(t *testing.T) {
|
|
table := createAllTypesTable()
|
|
defer dropTable(table)
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
// Note: PostgreSQL _jsonb array is not yet mapped in the driver
|
|
// This test documents the limitation but can be extended when support is added
|
|
|
|
_, err := db.Model(table).Data(g.Map{
|
|
"col_int2": 1,
|
|
"col_int4": 10,
|
|
"col_numeric": 99.99,
|
|
"col_varchar": "test",
|
|
"col_bool": true,
|
|
"col_varchar_arr": []string{},
|
|
}).Insert()
|
|
t.AssertNil(err)
|
|
|
|
// Query and verify NULL jsonb array is handled gracefully
|
|
one, err := db.Model(table).OrderDesc("id").One()
|
|
t.AssertNil(err)
|
|
// jsonb array should be nil or empty
|
|
t.Assert(one["col_jsonb_arr"].IsNil() || one["col_jsonb_arr"].IsEmpty(), true)
|
|
})
|
|
}
|
|
|
|
// Test_Field_UUID_Array_Type tests UUID array type (_uuid)
|
|
func Test_Field_UUID_Array_Type(t *testing.T) {
|
|
table := createAllTypesTable()
|
|
defer dropTable(table)
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
// Insert with UUID array values using raw SQL
|
|
// PostgreSQL uuid array literal format: ARRAY['uuid1', 'uuid2']::uuid[]
|
|
uuid1 := "550e8400-e29b-41d4-a716-446655440000"
|
|
uuid2 := "6ba7b810-9dad-11d1-80b4-00c04fd430c8"
|
|
uuid3 := "6ba7b811-9dad-11d1-80b4-00c04fd430c8"
|
|
_, err := db.Exec(ctx, fmt.Sprintf(`
|
|
INSERT INTO %s (col_int2, col_int4, col_numeric, col_varchar, col_bool, col_varchar_arr, col_uuid_arr)
|
|
VALUES (1, 10, 99.99, 'test', true, '{}', ARRAY['%s', '%s', '%s']::uuid[])
|
|
`, table, uuid1, uuid2, uuid3))
|
|
t.AssertNil(err)
|
|
|
|
// Query and verify UUID array
|
|
one, err := db.Model(table).OrderDesc("id").One()
|
|
t.AssertNil(err)
|
|
|
|
// Test UUID array field - should be converted to []uuid.UUID
|
|
uuidArrVal := one["col_uuid_arr"]
|
|
t.Assert(uuidArrVal.IsNil(), false)
|
|
|
|
// Verify the array contains the expected data as []uuid.UUID
|
|
uuidArr := uuidArrVal.Interfaces()
|
|
t.Assert(len(uuidArr), 3)
|
|
|
|
// Verify each element is uuid.UUID type
|
|
u1, ok := uuidArr[0].(uuid.UUID)
|
|
t.Assert(ok, true)
|
|
t.Assert(u1.String(), uuid1)
|
|
|
|
u2, ok := uuidArr[1].(uuid.UUID)
|
|
t.Assert(ok, true)
|
|
t.Assert(u2.String(), uuid2)
|
|
|
|
u3, ok := uuidArr[2].(uuid.UUID)
|
|
t.Assert(ok, true)
|
|
t.Assert(u3.String(), uuid3)
|
|
})
|
|
}
|
|
|
|
// Test_Field_UUID_Type tests UUID type
|
|
func Test_Field_UUID_Type(t *testing.T) {
|
|
table := createInitAllTypesTable()
|
|
defer dropTable(table)
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
// Query and verify UUID field
|
|
one, err := db.Model(table).OrderAsc("id").One()
|
|
t.AssertNil(err)
|
|
|
|
// Test UUID field - should be converted to uuid.UUID
|
|
uuidVal := one["col_uuid"]
|
|
t.Assert(uuidVal.IsNil(), false)
|
|
|
|
// Verify the value is uuid.UUID type
|
|
uuidObj, ok := uuidVal.Val().(uuid.UUID)
|
|
t.Assert(ok, true)
|
|
|
|
// Verify the UUID format
|
|
uuidStr := uuidObj.String()
|
|
t.Assert(len(uuidStr) > 0, true)
|
|
// UUID should contain the pattern from insert: 550e8400-e29b-41d4-a716-44665544000X
|
|
t.Assert(uuidStr, "550e8400-e29b-41d4-a716-446655440001")
|
|
|
|
// Also verify we can still get string representation via .String()
|
|
t.Assert(uuidVal.String(), "550e8400-e29b-41d4-a716-446655440001")
|
|
})
|
|
}
|
|
|
|
// Test_Field_Bytea_Array_Type_Scan tests bytea array type and scanning
|
|
func Test_Field_Bytea_Array_Type_Scan(t *testing.T) {
|
|
table := createInitAllTypesTable()
|
|
defer dropTable(table)
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
// Query and verify bytea array field
|
|
one, err := db.Model(table).OrderAsc("id").One()
|
|
t.AssertNil(err)
|
|
|
|
// Test bytea array field
|
|
byteaArrVal := one["col_bytea_arr"]
|
|
// bytea array should not be nil since we inserted data
|
|
t.Assert(byteaArrVal.IsNil(), false)
|
|
})
|
|
}
|
|
|
|
// Test_Field_Date_Array_Type_Scan tests date array type and scanning
|
|
func Test_Field_Date_Array_Type_Scan(t *testing.T) {
|
|
table := createInitAllTypesTable()
|
|
defer dropTable(table)
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
// Query and verify date array field
|
|
one, err := db.Model(table).OrderAsc("id").One()
|
|
t.AssertNil(err)
|
|
|
|
// Test date array field
|
|
dateArrVal := one["col_date_arr"]
|
|
t.Assert(dateArrVal.IsNil(), false)
|
|
|
|
// Verify the array contains the expected data
|
|
dateArr := dateArrVal.Strings()
|
|
t.Assert(len(dateArr) > 0, true)
|
|
})
|
|
}
|
|
|
|
// Test_Field_Timestamp_Array_Type_Scan tests timestamp array type and scanning
|
|
func Test_Field_Timestamp_Array_Type_Scan(t *testing.T) {
|
|
table := createInitAllTypesTable()
|
|
defer dropTable(table)
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
// Query and verify timestamp array field
|
|
one, err := db.Model(table).OrderAsc("id").One()
|
|
t.AssertNil(err)
|
|
|
|
// Test timestamp array field
|
|
timestampArrVal := one["col_timestamp_arr"]
|
|
t.Assert(timestampArrVal.IsNil(), false)
|
|
|
|
// Verify the array contains the expected data
|
|
timestampArr := timestampArrVal.Strings()
|
|
t.Assert(len(timestampArr) > 0, true)
|
|
})
|
|
}
|
|
|
|
// Test_Field_JSONB_Array_Type_Scan tests JSONB array type and scanning
|
|
func Test_Field_JSONB_Array_Type_Scan(t *testing.T) {
|
|
table := createInitAllTypesTable()
|
|
defer dropTable(table)
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
// Query and verify JSONB array field
|
|
one, err := db.Model(table).OrderAsc("id").One()
|
|
t.AssertNil(err)
|
|
|
|
// Test JSONB array field
|
|
jsonbArrVal := one["col_jsonb_arr"]
|
|
t.Assert(jsonbArrVal.IsNil(), false)
|
|
})
|
|
}
|
|
|
|
// Test_Field_UUID_Query tests querying by UUID field
|
|
func Test_Field_UUID_Query(t *testing.T) {
|
|
table := createInitAllTypesTable()
|
|
defer dropTable(table)
|
|
|
|
gtest.C(t, func(t *gtest.T) {
|
|
// Test 1: Query by UUID string
|
|
uuidStr := "550e8400-e29b-41d4-a716-446655440001"
|
|
one, err := db.Model(table).Where("col_uuid", uuidStr).One()
|
|
t.AssertNil(err)
|
|
t.Assert(one.IsEmpty(), false)
|
|
t.Assert(one["id"].Int(), 1)
|
|
|
|
// Verify the returned UUID is correct
|
|
uuidObj, ok := one["col_uuid"].Val().(uuid.UUID)
|
|
t.Assert(ok, true)
|
|
t.Assert(uuidObj.String(), uuidStr)
|
|
|
|
// Test 2: Query by uuid.UUID type directly
|
|
uuidVal, err := uuid.Parse("550e8400-e29b-41d4-a716-446655440002")
|
|
t.AssertNil(err)
|
|
one, err = db.Model(table).Where("col_uuid", uuidVal).One()
|
|
t.AssertNil(err)
|
|
t.Assert(one.IsEmpty(), false)
|
|
t.Assert(one["id"].Int(), 2)
|
|
|
|
// Test 3: Query by UUID string using g.Map
|
|
one, err = db.Model(table).Where(g.Map{
|
|
"col_uuid": "550e8400-e29b-41d4-a716-446655440003",
|
|
}).One()
|
|
t.AssertNil(err)
|
|
t.Assert(one.IsEmpty(), false)
|
|
t.Assert(one["id"].Int(), 3)
|
|
|
|
// Test 4: Query by uuid.UUID type using g.Map
|
|
uuidVal, err = uuid.Parse("550e8400-e29b-41d4-a716-446655440004")
|
|
t.AssertNil(err)
|
|
one, err = db.Model(table).Where(g.Map{
|
|
"col_uuid": uuidVal,
|
|
}).One()
|
|
t.AssertNil(err)
|
|
t.Assert(one.IsEmpty(), false)
|
|
t.Assert(one["id"].Int(), 4)
|
|
|
|
// Test 5: Query non-existent UUID
|
|
one, err = db.Model(table).Where("col_uuid", "00000000-0000-0000-0000-000000000000").One()
|
|
t.AssertNil(err)
|
|
t.Assert(one.IsEmpty(), true)
|
|
|
|
// Test 6: Query multiple records by UUID IN clause with strings
|
|
all, err := db.Model(table).WhereIn("col_uuid", g.Slice{
|
|
"550e8400-e29b-41d4-a716-446655440001",
|
|
"550e8400-e29b-41d4-a716-446655440002",
|
|
}).OrderAsc("id").All()
|
|
t.AssertNil(err)
|
|
t.Assert(len(all), 2)
|
|
t.Assert(all[0]["id"].Int(), 1)
|
|
t.Assert(all[1]["id"].Int(), 2)
|
|
|
|
// Test 7: Query multiple records by UUID IN clause with uuid.UUID types
|
|
uuid1, _ := uuid.Parse("550e8400-e29b-41d4-a716-446655440003")
|
|
uuid2, _ := uuid.Parse("550e8400-e29b-41d4-a716-446655440004")
|
|
all, err = db.Model(table).WhereIn("col_uuid", g.Slice{uuid1, uuid2}).OrderAsc("id").All()
|
|
t.AssertNil(err)
|
|
t.Assert(len(all), 2)
|
|
t.Assert(all[0]["id"].Int(), 3)
|
|
t.Assert(all[1]["id"].Int(), 4)
|
|
})
|
|
}
|