From 1a2ec57908cb773d5f6fadf91ab4b58bf89418df Mon Sep 17 00:00:00 2001 From: Bowen Date: Sat, 6 Sep 2025 22:48:30 +0800 Subject: [PATCH 1/2] fix: [#770] Add a SelectRaw function for the ORM --- tests/go.mod | 2 +- tests/go.sum | 4 ++-- tests/query_test.go | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 35 insertions(+), 3 deletions(-) diff --git a/tests/go.mod b/tests/go.mod index 86da7b0fc..eb69595ee 100644 --- a/tests/go.mod +++ b/tests/go.mod @@ -31,7 +31,7 @@ require ( github.com/dromara/carbon/v2 v2.6.11 // indirect github.com/gabriel-vasile/mimetype v1.4.9 // indirect github.com/go-sql-driver/mysql v1.9.3 // indirect - github.com/go-viper/mapstructure/v2 v2.3.0 // indirect + github.com/go-viper/mapstructure/v2 v2.4.0 // indirect github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect github.com/golang-sql/sqlexp v0.1.0 // indirect github.com/gookit/color v1.5.4 // indirect diff --git a/tests/go.sum b/tests/go.sum index c81170502..28f9d03ba 100644 --- a/tests/go.sum +++ b/tests/go.sum @@ -63,8 +63,8 @@ github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo= github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= -github.com/go-viper/mapstructure/v2 v2.3.0 h1:27XbWsHIqhbdR5TIC911OfYvgSaW93HM+dX7970Q7jk= -github.com/go-viper/mapstructure/v2 v2.3.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs= +github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= github.com/golang-jwt/jwt v3.2.1+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY= github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= diff --git a/tests/query_test.go b/tests/query_test.go index dd968f934..019db45c7 100644 --- a/tests/query_test.go +++ b/tests/query_test.go @@ -3399,6 +3399,38 @@ func (s *QueryTestSuite) TestSelect() { } } +func (s *QueryTestSuite) TestSelectRaw() { + for driver, query := range s.queries { + s.Run(driver, func() { + user := User{Name: "select_user", Avatar: "select_avatar"} + s.Nil(query.Query().Create(&user)) + s.True(user.ID > 0) + + user1 := User{Name: "select_user", Avatar: "select_avatar1"} + s.Nil(query.Query().Create(&user1)) + s.True(user1.ID > 0) + + user2 := User{Name: "select_user1", Avatar: "select_avatar1"} + s.Nil(query.Query().Create(&user2)) + s.True(user2.ID > 0) + + type Result struct { + Name string + Bio string + } + var result []Result + s.Nil(query.Query().Model(&User{}).SelectRaw("name, COALESCE(bio,?) as bio", "a").Where("id in ?", []uint{user.ID, user1.ID, user2.ID}).Get(&result)) + s.Equal(3, len(result)) + s.Equal("select_user", result[0].Name) + s.Equal("a", result[0].Bio) + s.Equal("select_user", result[1].Name) + s.Equal("a", result[1].Bio) + s.Equal("select_user1", result[2].Name) + s.Equal("a", result[2].Bio) + }) + } +} + func (s *QueryTestSuite) TestSharedLock() { for driver, query := range s.queries { if driver == sqlite.Name { From b4afd27f7cc471b00c5b3a8b153e3c8757bec26a Mon Sep 17 00:00:00 2001 From: Bowen Date: Sat, 6 Sep 2025 22:49:16 +0800 Subject: [PATCH 2/2] fix: [#770] Add a SelectRaw function for the ORM --- contracts/database/orm/orm.go | 2 ++ database/gorm/conditions.go | 6 ++++ database/gorm/query.go | 26 ++++++++++++--- mocks/database/orm/Query.go | 59 +++++++++++++++++++++++++++++++++++ 4 files changed, 88 insertions(+), 5 deletions(-) diff --git a/contracts/database/orm/orm.go b/contracts/database/orm/orm.go index 7f1729171..eec3eebdb 100644 --- a/contracts/database/orm/orm.go +++ b/contracts/database/orm/orm.go @@ -162,6 +162,8 @@ type Query interface { Scopes(funcs ...func(Query) Query) Query // Select specifies fields that should be retrieved from the database. Select(columns ...string) Query + // SelectRaw specifies a raw SQL query for selecting fields. + SelectRaw(query any, args ...any) Query // SharedLock locks the selected rows in the table. SharedLock() Query // Sum calculates the sum of a column's values and populates the destination object. diff --git a/database/gorm/conditions.go b/database/gorm/conditions.go index 3ba54c417..0b46dfb5f 100644 --- a/database/gorm/conditions.go +++ b/database/gorm/conditions.go @@ -18,6 +18,7 @@ type Conditions struct { order []any scopes []func(contractsorm.Query) contractsorm.Query selectColumns []string + selectRaw *Select where []contractsdriver.Where with []With distinct bool @@ -27,6 +28,11 @@ type Conditions struct { withTrashed bool } +type Select struct { + query any + args []any +} + type Table struct { name string args []any diff --git a/database/gorm/query.go b/database/gorm/query.go index 4a29d8f0a..f799fae74 100644 --- a/database/gorm/query.go +++ b/database/gorm/query.go @@ -779,6 +779,16 @@ func (r *Query) Select(columns ...string) contractsorm.Query { return r.setConditions(conditions) } +func (r *Query) SelectRaw(query any, args ...any) contractsorm.Query { + conditions := r.conditions + conditions.selectRaw = &Select{ + query: query, + args: args, + } + + return r.setConditions(conditions) +} + func (r *Query) WithContext(ctx context.Context) contractsorm.Query { instance := r.instance.WithContext(ctx) @@ -1225,17 +1235,23 @@ func (r *Query) buildOrder(db *gormio.DB) *gormio.DB { } func (r *Query) buildSelectColumns(db *gormio.DB) *gormio.DB { - if len(r.conditions.selectColumns) == 0 { + if len(r.conditions.selectColumns) == 0 && r.conditions.selectRaw == nil { return db } - var selectColumns []any - for _, column := range r.conditions.selectColumns { - selectColumns = append(selectColumns, column) + if len(r.conditions.selectColumns) > 0 { + var selectColumns []any + for _, column := range r.conditions.selectColumns { + selectColumns = append(selectColumns, column) + } + + db = db.Select(selectColumns[0], selectColumns[1:]...) + } else if r.conditions.selectRaw != nil { + db = db.Select(r.conditions.selectRaw.query, r.conditions.selectRaw.args...) } - db = db.Select(selectColumns[0], selectColumns[1:]...) r.conditions.selectColumns = nil + r.conditions.selectRaw = nil return db } diff --git a/mocks/database/orm/Query.go b/mocks/database/orm/Query.go index 51155d4e9..1c3c2e06e 100644 --- a/mocks/database/orm/Query.go +++ b/mocks/database/orm/Query.go @@ -3183,6 +3183,65 @@ func (_c *Query_Select_Call) RunAndReturn(run func(...string) orm.Query) *Query_ return _c } +// SelectRaw provides a mock function with given fields: query, args +func (_m *Query) SelectRaw(query interface{}, args ...interface{}) orm.Query { + var _ca []interface{} + _ca = append(_ca, query) + _ca = append(_ca, args...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for SelectRaw") + } + + var r0 orm.Query + if rf, ok := ret.Get(0).(func(interface{}, ...interface{}) orm.Query); ok { + r0 = rf(query, args...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(orm.Query) + } + } + + return r0 +} + +// Query_SelectRaw_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'SelectRaw' +type Query_SelectRaw_Call struct { + *mock.Call +} + +// SelectRaw is a helper method to define mock.On call +// - query interface{} +// - args ...interface{} +func (_e *Query_Expecter) SelectRaw(query interface{}, args ...interface{}) *Query_SelectRaw_Call { + return &Query_SelectRaw_Call{Call: _e.mock.On("SelectRaw", + append([]interface{}{query}, args...)...)} +} + +func (_c *Query_SelectRaw_Call) Run(run func(query interface{}, args ...interface{})) *Query_SelectRaw_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]interface{}, len(args)-1) + for i, a := range args[1:] { + if a != nil { + variadicArgs[i] = a.(interface{}) + } + } + run(args[0].(interface{}), variadicArgs...) + }) + return _c +} + +func (_c *Query_SelectRaw_Call) Return(_a0 orm.Query) *Query_SelectRaw_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Query_SelectRaw_Call) RunAndReturn(run func(interface{}, ...interface{}) orm.Query) *Query_SelectRaw_Call { + _c.Call.Return(run) + return _c +} + // SharedLock provides a mock function with no fields func (_m *Query) SharedLock() orm.Query { ret := _m.Called()