Merge branch 'master' into copilot/fix-3931

This commit is contained in:
hailaz
2025-09-29 17:33:02 +08:00
committed by GitHub
29 changed files with 2278 additions and 109 deletions

View File

@ -181,7 +181,7 @@ type DB interface {
// GetArray executes a query and returns the first column of all rows.
// It's useful for queries like SELECT id FROM table.
GetArray(ctx context.Context, sql string, args ...any) ([]Value, error)
GetArray(ctx context.Context, sql string, args ...any) (Array, error)
// GetCount executes a COUNT query and returns the result as an integer.
// It's a convenience method for counting rows.
@ -673,6 +673,9 @@ type (
// Value is the field value type.
Value = *gvar.Var
// Array is the field value array type.
Array = gvar.Vars
// Record is the row record of the table.
Record map[string]Value

View File

@ -12,6 +12,7 @@ import (
"database/sql"
"fmt"
"reflect"
"sort"
"strings"
"github.com/gogf/gf/v2/container/gmap"
@ -174,7 +175,7 @@ func (c *Core) GetOne(ctx context.Context, sql string, args ...any) (Record, err
// GetArray queries and returns data values as slice from database.
// Note that if there are multiple columns in the result, it returns just one column values randomly.
func (c *Core) GetArray(ctx context.Context, sql string, args ...any) ([]Value, error) {
func (c *Core) GetArray(ctx context.Context, sql string, args ...any) (Array, error) {
all, err := c.db.DoSelect(ctx, nil, sql, args...)
if err != nil {
return nil, err
@ -446,25 +447,30 @@ func (c *Core) DoInsert(ctx context.Context, link Link, table string, list List,
// It here uses ListMap to keep sequence for data inserting.
// ============================================================================================
var keyListMap = gmap.NewListMap()
var tmpkeyListMap = make(map[string]List)
for _, item := range list {
var (
tmpKeys = make([]string, 0)
tmpKeysInSequenceStr string
)
mapLen := len(item)
if mapLen == 0 {
continue
}
tmpKeys := make([]string, 0, mapLen)
for k := range item {
tmpKeys = append(tmpKeys, k)
}
keys, err = c.fieldsToSequence(ctx, table, tmpKeys)
if err != nil {
return nil, err
if mapLen > 1 {
sort.Strings(tmpKeys)
}
tmpKeysInSequenceStr = gstr.Join(keys, ",")
if !keyListMap.Contains(tmpKeysInSequenceStr) {
keyListMap.Set(tmpKeysInSequenceStr, make(List, 0))
keys = tmpKeys // for fieldsToSequence
tmpKeysInSequenceStr := gstr.Join(tmpKeys, ",")
if tmpkeyListMapItem, ok := tmpkeyListMap[tmpKeysInSequenceStr]; ok {
tmpkeyListMap[tmpKeysInSequenceStr] = append(tmpkeyListMapItem, item)
} else {
tmpkeyListMap[tmpKeysInSequenceStr] = List{item}
}
tmpKeysInSequenceList := keyListMap.Get(tmpKeysInSequenceStr).(List)
tmpKeysInSequenceList = append(tmpKeysInSequenceList, item)
keyListMap.Set(tmpKeysInSequenceStr, tmpKeysInSequenceList)
}
for tmpKeysInSequenceStr, itemList := range tmpkeyListMap {
keyListMap.Set(tmpKeysInSequenceStr, itemList)
}
if keyListMap.Size() > 1 {
var (
@ -488,6 +494,15 @@ func (c *Core) DoInsert(ctx context.Context, link Link, table string, list List,
return &sqlResult, err
}
keys, err = c.fieldsToSequence(ctx, table, keys)
if err != nil {
return nil, err
}
if len(keys) == 0 {
return nil, gerror.NewCode(gcode.CodeInvalidParameter, "no valid data fields found in table")
}
// Prepare the batch result pointer.
var (
charL, charR = c.db.GetChars()

View File

@ -251,15 +251,47 @@ func AddDefaultConfigNode(node ConfigNode) error {
}
// AddDefaultConfigGroup adds multiple node configurations to configuration of default group.
//
// Deprecated: Use SetDefaultConfigGroup instead.
func AddDefaultConfigGroup(nodes ConfigGroup) error {
return SetConfigGroup(DefaultGroupName, nodes)
}
// SetDefaultConfigGroup sets multiple node configurations to configuration of default group.
func SetDefaultConfigGroup(nodes ConfigGroup) error {
return SetConfigGroup(DefaultGroupName, nodes)
}
// GetConfig retrieves and returns the configuration of given group.
//
// Deprecated: Use GetConfigGroup instead.
func GetConfig(group string) ConfigGroup {
configGroup, _ := GetConfigGroup(group)
return configGroup
}
// GetConfigGroup retrieves and returns the configuration of given group.
// It returns an error if the group does not exist, or an empty slice if the group exists but has no nodes.
func GetConfigGroup(group string) (ConfigGroup, error) {
configs.RLock()
defer configs.RUnlock()
return configs.config[group]
configGroup, exists := configs.config[group]
if !exists {
return nil, gerror.NewCodef(
gcode.CodeInvalidParameter,
`configuration group "%s" not found`,
group,
)
}
return configGroup, nil
}
// GetAllConfig retrieves and returns all configurations.
func GetAllConfig() Config {
configs.RLock()
defer configs.RUnlock()
return configs.config
}
// SetDefaultGroup sets the group name for default configuration.

View File

@ -159,6 +159,10 @@ func (h *HookSelectInput) Next(ctx context.Context) (result Result, err error) {
if err != nil {
return
}
h.Model.db.GetCore().schema = h.Schema
defer func() {
h.Model.db.GetCore().schema = h.originalSchemaName.String()
}()
}
return h.Model.db.DoSelect(ctx, h.link, toBeCommittedSql, h.Args...)
}
@ -195,6 +199,10 @@ func (h *HookInsertInput) Next(ctx context.Context) (result sql.Result, err erro
if err != nil {
return
}
h.Model.db.GetCore().schema = h.Schema
defer func() {
h.Model.db.GetCore().schema = h.originalSchemaName.String()
}()
}
return h.Model.db.DoInsert(ctx, h.link, h.Table, h.Data, h.Option)
}
@ -238,6 +246,10 @@ func (h *HookUpdateInput) Next(ctx context.Context) (result sql.Result, err erro
if err != nil {
return
}
h.Model.db.GetCore().schema = h.Schema
defer func() {
h.Model.db.GetCore().schema = h.originalSchemaName.String()
}()
}
return h.Model.db.DoUpdate(ctx, h.link, h.Table, h.Data, h.Condition, h.Args...)
}
@ -281,6 +293,10 @@ func (h *HookDeleteInput) Next(ctx context.Context) (result sql.Result, err erro
if err != nil {
return
}
h.Model.db.GetCore().schema = h.Schema
defer func() {
h.Model.db.GetCore().schema = h.originalSchemaName.String()
}()
}
return h.Model.db.DoDelete(ctx, h.link, h.Table, h.Condition, h.Args...)
}

View File

@ -126,7 +126,7 @@ func (m *Model) One(where ...any) (Record, error) {
// If the optional parameter `fieldsAndWhere` is given, the fieldsAndWhere[0] is the selected fields
// and fieldsAndWhere[1:] is treated as where condition fields.
// Also see Model.Fields and Model.Where functions.
func (m *Model) Array(fieldsAndWhere ...any) ([]Value, error) {
func (m *Model) Array(fieldsAndWhere ...any) (Array, error) {
if len(fieldsAndWhere) > 0 {
if len(fieldsAndWhere) > 2 {
return m.Fields(gconv.String(fieldsAndWhere[0])).Where(fieldsAndWhere[1], fieldsAndWhere[2:]...).Array()

View File

@ -76,8 +76,8 @@ func (r Result) List() List {
// Array retrieves and returns specified column values as slice.
// The parameter `field` is optional is the column field is only one.
// The default `field` is the first field name of the first item in `Result` if parameter `field` is not given.
func (r Result) Array(field ...string) []Value {
array := make([]Value, len(r))
func (r Result) Array(field ...string) Array {
array := make(Array, len(r))
if len(r) == 0 {
return array
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,204 @@
// 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 gdb
import (
"testing"
"time"
"github.com/gogf/gf/v2/container/gtype"
"github.com/gogf/gf/v2/os/glog"
"github.com/gogf/gf/v2/test/gtest"
)
func Test_Core_SetDebug_GetDebug(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
// Save original config and restore after test
originalConfig := configs.config
defer func() {
configs.config = originalConfig
}()
// Create a test configuration
configs.config = make(Config)
testNode := ConfigNode{
Host: "127.0.0.1",
Port: "3306",
User: "root",
Pass: "123456",
Name: "test_db",
Type: "mysql",
}
err := AddConfigNode("test_group", testNode)
t.AssertNil(err)
// Create Core instance
node, err := GetConfigGroup("test_group")
t.AssertNil(err)
core := &Core{
group: "test_group",
config: &node[0],
debug: gtype.NewBool(false),
}
// Test default value
result := core.GetDebug()
t.Assert(result, false)
// Test setting debug to true
core.SetDebug(true)
result = core.GetDebug()
t.Assert(result, true)
// Test setting debug to false
core.SetDebug(false)
result = core.GetDebug()
t.Assert(result, false)
})
}
func Test_Core_SetDryRun_GetDryRun(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
// Save original config and restore after test
originalConfig := configs.config
defer func() {
configs.config = originalConfig
}()
// Create a test configuration
configs.config = make(Config)
testNode := ConfigNode{
Host: "127.0.0.1",
Port: "3306",
User: "root",
Pass: "123456",
Name: "test_db",
Type: "mysql",
DryRun: false,
}
err := AddConfigNode("test_group", testNode)
t.AssertNil(err)
// Create Core instance
node, err := GetConfigGroup("test_group")
t.AssertNil(err)
core := &Core{
group: "test_group",
config: &node[0],
}
// Test default value
result := core.GetDryRun()
t.Assert(result, false)
// Test setting dry run to true
core.SetDryRun(true)
result = core.GetDryRun()
t.Assert(result, true)
// Test setting dry run to false
core.SetDryRun(false)
result = core.GetDryRun()
t.Assert(result, false)
})
}
func Test_Core_SetLogger_GetLogger(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
// Create Core instance
core := &Core{}
// Test setting custom logger
customLogger := glog.New()
core.SetLogger(customLogger)
result := core.GetLogger()
t.Assert(result, customLogger)
// Test setting nil logger
core.SetLogger(nil)
result = core.GetLogger()
t.Assert(result, nil)
})
}
func Test_Core_SetMaxConnections(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
// Create Core instance
core := &Core{}
// Test SetMaxIdleConnCount
core.SetMaxIdleConnCount(10)
t.Assert(core.dynamicConfig.MaxIdleConnCount, 10)
// Test SetMaxOpenConnCount
core.SetMaxOpenConnCount(20)
t.Assert(core.dynamicConfig.MaxOpenConnCount, 20)
// Test SetMaxConnLifeTime
testDuration := time.Hour
core.SetMaxConnLifeTime(testDuration)
t.Assert(core.dynamicConfig.MaxConnLifeTime, testDuration)
})
}
func Test_Core_GetCache(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
// Create Core instance
core := &Core{}
cache := core.GetCache()
// Cache might be nil if not initialized, so we just test that the call doesn't panic
_ = cache
})
}
func Test_Core_GetGroup(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
// Create Core instance
core := &Core{
group: "test_group",
}
group := core.GetGroup()
t.Assert(group, "test_group")
})
}
func Test_Core_GetPrefix(t *testing.T) {
gtest.C(t, func(t *gtest.T) {
// Save original config and restore after test
originalConfig := configs.config
defer func() {
configs.config = originalConfig
}()
// Create a test configuration
configs.config = make(Config)
testNode := ConfigNode{
Host: "127.0.0.1",
Port: "3306",
User: "root",
Pass: "123456",
Name: "test_db",
Type: "mysql",
Prefix: "gf_",
}
err := AddConfigNode("test_group", testNode)
t.AssertNil(err)
// Create Core instance
node, err := GetConfigGroup("test_group")
t.AssertNil(err)
core := &Core{
group: "test_group",
config: &node[0],
}
prefix := core.GetPrefix()
t.Assert(prefix, "gf_")
})
}