From 28e9ec93ba25a71346a7eb2ad754d3c9e9a15c76 Mon Sep 17 00:00:00 2001 From: Bowen Date: Fri, 14 Feb 2025 23:02:32 +0800 Subject: [PATCH 01/15] feat: [#358] Add Insert Update Delete methods for DB --- contracts/database/db/db.go | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/contracts/database/db/db.go b/contracts/database/db/db.go index b7bfe86a0..0f19238d5 100644 --- a/contracts/database/db/db.go +++ b/contracts/database/db/db.go @@ -5,6 +5,9 @@ type DB interface { } type Query interface { - Where(query any, args ...any) Query + Delete() error Get(dest any) error + Insert() error + Update() error + Where(query any, args ...any) Query } From 975446bde461644b949a02633642bd34c7c57771 Mon Sep 17 00:00:00 2001 From: hwbrzzl <24771476+hwbrzzl@users.noreply.github.com> Date: Fri, 14 Feb 2025 15:03:41 +0000 Subject: [PATCH 02/15] chore: update mocks --- mocks/database/db/Query.go | 135 +++++++++++++++++++++++++++++++++++++ 1 file changed, 135 insertions(+) diff --git a/mocks/database/db/Query.go b/mocks/database/db/Query.go index 106a79033..ba538b599 100644 --- a/mocks/database/db/Query.go +++ b/mocks/database/db/Query.go @@ -20,6 +20,51 @@ func (_m *Query) EXPECT() *Query_Expecter { return &Query_Expecter{mock: &_m.Mock} } +// Delete provides a mock function with no fields +func (_m *Query) Delete() error { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for Delete") + } + + var r0 error + if rf, ok := ret.Get(0).(func() error); ok { + r0 = rf() + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Query_Delete_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Delete' +type Query_Delete_Call struct { + *mock.Call +} + +// Delete is a helper method to define mock.On call +func (_e *Query_Expecter) Delete() *Query_Delete_Call { + return &Query_Delete_Call{Call: _e.mock.On("Delete")} +} + +func (_c *Query_Delete_Call) Run(run func()) *Query_Delete_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *Query_Delete_Call) Return(_a0 error) *Query_Delete_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Query_Delete_Call) RunAndReturn(run func() error) *Query_Delete_Call { + _c.Call.Return(run) + return _c +} + // Get provides a mock function with given fields: dest func (_m *Query) Get(dest interface{}) error { ret := _m.Called(dest) @@ -66,6 +111,96 @@ func (_c *Query_Get_Call) RunAndReturn(run func(interface{}) error) *Query_Get_C return _c } +// Insert provides a mock function with no fields +func (_m *Query) Insert() error { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for Insert") + } + + var r0 error + if rf, ok := ret.Get(0).(func() error); ok { + r0 = rf() + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Query_Insert_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Insert' +type Query_Insert_Call struct { + *mock.Call +} + +// Insert is a helper method to define mock.On call +func (_e *Query_Expecter) Insert() *Query_Insert_Call { + return &Query_Insert_Call{Call: _e.mock.On("Insert")} +} + +func (_c *Query_Insert_Call) Run(run func()) *Query_Insert_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *Query_Insert_Call) Return(_a0 error) *Query_Insert_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Query_Insert_Call) RunAndReturn(run func() error) *Query_Insert_Call { + _c.Call.Return(run) + return _c +} + +// Update provides a mock function with no fields +func (_m *Query) Update() error { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for Update") + } + + var r0 error + if rf, ok := ret.Get(0).(func() error); ok { + r0 = rf() + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Query_Update_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Update' +type Query_Update_Call struct { + *mock.Call +} + +// Update is a helper method to define mock.On call +func (_e *Query_Expecter) Update() *Query_Update_Call { + return &Query_Update_Call{Call: _e.mock.On("Update")} +} + +func (_c *Query_Update_Call) Run(run func()) *Query_Update_Call { + _c.Call.Run(func(args mock.Arguments) { + run() + }) + return _c +} + +func (_c *Query_Update_Call) Return(_a0 error) *Query_Update_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Query_Update_Call) RunAndReturn(run func() error) *Query_Update_Call { + _c.Call.Return(run) + return _c +} + // Where provides a mock function with given fields: query, args func (_m *Query) Where(query interface{}, args ...interface{}) db.Query { var _ca []interface{} From d7999f8e5235337818c4fa87f78376a8e03fdf8f Mon Sep 17 00:00:00 2001 From: Bowen Date: Sat, 15 Feb 2025 12:17:25 +0800 Subject: [PATCH 03/15] implement methods --- database/db/query.go | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/database/db/query.go b/database/db/query.go index b419bea12..15b0101f2 100644 --- a/database/db/query.go +++ b/database/db/query.go @@ -27,6 +27,10 @@ func NewQuery(config database.Config, instance *sqlx.DB, table string) *Query { } } +func (r *Query) Delete() error { + return nil +} + func (r *Query) Where(query any, args ...any) db.Query { r.conditions.where = append(r.conditions.where, Where{ query: query, @@ -47,6 +51,27 @@ func (r *Query) Get(dest any) error { return r.instance.Select(dest, sql, args...) } +func (r *Query) Insert() error { + return nil +} + +func (r *Query) Update() error { + return nil +} + +func (r *Query) buildInsert() (sql string, args []any, err error) { + if r.conditions.table == "" { + return "", nil, errors.DatabaseTableIsRequired + } + + builder := sq.Insert(r.conditions.table) + if r.config.PlaceholderFormat != nil { + builder = builder.PlaceholderFormat(r.config.PlaceholderFormat) + } + + return builder.ToSql() +} + func (r *Query) buildSelect() (sql string, args []any, err error) { if r.conditions.table == "" { return "", nil, errors.DatabaseTableIsRequired @@ -65,3 +90,22 @@ func (r *Query) buildSelect() (sql string, args []any, err error) { return builder.ToSql() } + +func (r *Query) buildUpdate() (sql string, args []any, err error) { + if r.conditions.table == "" { + return "", nil, errors.DatabaseTableIsRequired + } + + builder := sq.Select("*") + if r.config.PlaceholderFormat != nil { + builder = builder.PlaceholderFormat(r.config.PlaceholderFormat) + } + + builder = builder.From(r.conditions.table) + + for _, where := range r.conditions.where { + builder = builder.Where(where.query, where.args...) + } + + return builder.ToSql() +} From a445faec1671c815ac4aa76e6aa2e663754da7c9 Mon Sep 17 00:00:00 2001 From: Bowen Date: Tue, 18 Feb 2025 17:17:25 +0800 Subject: [PATCH 04/15] finish --- contracts/database/db/db.go | 17 ++- database/db/db.go | 10 +- database/db/db_test.go | 67 +++++++++++ database/db/query.go | 112 ++++++++++++------ database/db/query_test.go | 186 +++++++++++++++++++++++++++++ database/db/utils.go | 133 +++++++++++++++++++++ database/db/utils_test.go | 95 +++++++++++++++ errors/list.go | 1 + mocks/database/db/Builder.go | 221 +++++++++++++++++++++++++++++++++++ mocks/database/db/Query.go | 119 +++++++------------ tests/db_test.go | 136 ++++++++++++++++++++- tests/models.go | 10 +- 12 files changed, 979 insertions(+), 128 deletions(-) create mode 100644 database/db/db_test.go create mode 100644 database/db/query_test.go create mode 100644 database/db/utils.go create mode 100644 database/db/utils_test.go create mode 100644 mocks/database/db/Builder.go diff --git a/contracts/database/db/db.go b/contracts/database/db/db.go index 0f19238d5..67ddb5efe 100644 --- a/contracts/database/db/db.go +++ b/contracts/database/db/db.go @@ -1,13 +1,24 @@ package db +import "database/sql" + type DB interface { Table(name string) Query } type Query interface { - Delete() error + First(dest any) error Get(dest any) error - Insert() error - Update() error + Insert(data any) (*Result, error) Where(query any, args ...any) Query } + +type Result struct { + RowsAffected int64 +} + +type Builder interface { + Exec(query string, args ...any) (sql.Result, error) + Get(dest any, query string, args ...any) error + Select(dest any, query string, args ...any) error +} diff --git a/database/db/db.go b/database/db/db.go index 503fe6b7f..31c5aeb85 100644 --- a/database/db/db.go +++ b/database/db/db.go @@ -13,12 +13,12 @@ import ( ) type DB struct { - config database.Config - instance *sqlx.DB + builder db.Builder + config database.Config } -func NewDB(config database.Config, instance *sqlx.DB) db.DB { - return &DB{config: config, instance: instance} +func NewDB(config database.Config, builder db.Builder) db.DB { + return &DB{config: config, builder: builder} } func BuildDB(config config.Config, connection string) (db.DB, error) { @@ -41,5 +41,5 @@ func BuildDB(config config.Config, connection string) (db.DB, error) { } func (r *DB) Table(name string) db.Query { - return NewQuery(r.config, r.instance, name) + return NewQuery(r.config, r.builder, name) } diff --git a/database/db/db_test.go b/database/db/db_test.go new file mode 100644 index 000000000..24541d6d7 --- /dev/null +++ b/database/db/db_test.go @@ -0,0 +1,67 @@ +package db + +import ( + "database/sql" + "testing" + + "github.com/stretchr/testify/assert" + + "github.com/goravel/framework/contracts/database" + contractsdriver "github.com/goravel/framework/contracts/database/driver" + "github.com/goravel/framework/errors" + mocksconfig "github.com/goravel/framework/mocks/config" + mocksdriver "github.com/goravel/framework/mocks/database/driver" +) + +func TestBuildDB(t *testing.T) { + var ( + mockConfig *mocksconfig.Config + mockDriver *mocksdriver.Driver + ) + + tests := []struct { + name string + connection string + setup func() + expectedError error + }{ + { + name: "Success", + connection: "mysql", + setup: func() { + driverCallback := func() (contractsdriver.Driver, error) { + return mockDriver, nil + } + mockConfig.On("Get", "database.connections.mysql.via").Return(driverCallback) + mockDriver.On("DB").Return(&sql.DB{}, nil) + mockDriver.On("Config").Return(database.Config{Driver: "mysql"}) + }, + expectedError: nil, + }, + { + name: "Config Not Found", + connection: "invalid", + setup: func() { + mockConfig.On("Get", "database.connections.invalid.via").Return(nil) + }, + expectedError: errors.DatabaseConfigNotFound, + }, + } + + for _, test := range tests { + t.Run(test.name, func(t *testing.T) { + mockConfig = mocksconfig.NewConfig(t) + mockDriver = mocksdriver.NewDriver(t) + test.setup() + + db, err := BuildDB(mockConfig, test.connection) + if test.expectedError != nil { + assert.Equal(t, test.expectedError, err) + assert.Nil(t, db) + } else { + assert.NoError(t, err) + assert.NotNil(t, db) + } + }) + } +} diff --git a/database/db/query.go b/database/db/query.go index 15b0101f2..ffb4a2da1 100644 --- a/database/db/query.go +++ b/database/db/query.go @@ -2,42 +2,41 @@ package db import ( "fmt" + "sort" sq "github.com/Masterminds/squirrel" - "github.com/jmoiron/sqlx" "github.com/goravel/framework/contracts/database" "github.com/goravel/framework/contracts/database/db" "github.com/goravel/framework/errors" + "github.com/goravel/framework/support/str" ) type Query struct { + builder db.Builder conditions Conditions config database.Config - instance *sqlx.DB } -func NewQuery(config database.Config, instance *sqlx.DB, table string) *Query { +func NewQuery(config database.Config, builder db.Builder, table string) *Query { return &Query{ + builder: builder, conditions: Conditions{ table: table, }, - config: config, - instance: instance, + config: config, } } -func (r *Query) Delete() error { - return nil -} - -func (r *Query) Where(query any, args ...any) db.Query { - r.conditions.where = append(r.conditions.where, Where{ - query: query, - args: args, - }) +func (r *Query) First(dest any) error { + sql, args, err := r.buildSelect() + // TODO: use logger instead of println + fmt.Println(sql, args, err) + if err != nil { + return err + } - return r + return r.builder.Get(dest, sql, args...) } func (r *Query) Get(dest any) error { @@ -48,50 +47,79 @@ func (r *Query) Get(dest any) error { return err } - return r.instance.Select(dest, sql, args...) + return r.builder.Select(dest, sql, args...) } -func (r *Query) Insert() error { - return nil -} - -func (r *Query) Update() error { - return nil -} +func (r *Query) Insert(data any) (*db.Result, error) { + mapData, err := convertToSliceMap(data) + if err != nil { + return nil, err + } -func (r *Query) buildInsert() (sql string, args []any, err error) { - if r.conditions.table == "" { - return "", nil, errors.DatabaseTableIsRequired + sql, args, err := r.buildInsert(mapData) + if err != nil { + return nil, err + } + // TODO: use logger instead of println + fmt.Println(sql, args, err) + result, err := r.builder.Exec(sql, args...) + if err != nil { + return nil, err } - builder := sq.Insert(r.conditions.table) - if r.config.PlaceholderFormat != nil { - builder = builder.PlaceholderFormat(r.config.PlaceholderFormat) + rowsAffected, err := result.RowsAffected() + if err != nil { + return nil, err } - return builder.ToSql() + return &db.Result{ + RowsAffected: rowsAffected, + }, nil } -func (r *Query) buildSelect() (sql string, args []any, err error) { +func (r *Query) Where(query any, args ...any) db.Query { + q := NewQuery(r.config, r.builder, r.conditions.table) + q.conditions = r.conditions + q.conditions.where = append(r.conditions.where, Where{ + query: query, + args: args, + }) + + return q +} + +func (r *Query) buildInsert(data []map[string]any) (sql string, args []any, err error) { if r.conditions.table == "" { return "", nil, errors.DatabaseTableIsRequired } - builder := sq.Select("*") + builder := sq.Insert(r.conditions.table) if r.config.PlaceholderFormat != nil { builder = builder.PlaceholderFormat(r.config.PlaceholderFormat) } - builder = builder.From(r.conditions.table) + first := data[0] + builder = builder.SetMap(first) - for _, where := range r.conditions.where { - builder = builder.Where(where.query, where.args...) + cols := make([]string, 0, len(first)) + for col := range first { + cols = append(cols, col) + } + sort.Strings(cols) + builder = builder.Columns(cols...) + + for _, row := range data { + vals := make([]any, 0, len(first)) + for _, col := range cols { + vals = append(vals, row[col]) + } + builder = builder.Values(vals...) } return builder.ToSql() } -func (r *Query) buildUpdate() (sql string, args []any, err error) { +func (r *Query) buildSelect() (sql string, args []any, err error) { if r.conditions.table == "" { return "", nil, errors.DatabaseTableIsRequired } @@ -104,6 +132,18 @@ func (r *Query) buildUpdate() (sql string, args []any, err error) { builder = builder.From(r.conditions.table) for _, where := range r.conditions.where { + query, ok := where.query.(string) + if ok { + if !str.Of(query).Trim().Contains(" ", "?") { + if len(where.args) > 1 { + builder = builder.Where(sq.Eq{query: where.args}) + } else { + builder = builder.Where(sq.Eq{query: where.args[0]}) + } + continue + } + } + builder = builder.Where(where.query, where.args...) } diff --git a/database/db/query_test.go b/database/db/query_test.go new file mode 100644 index 000000000..b7293472c --- /dev/null +++ b/database/db/query_test.go @@ -0,0 +1,186 @@ +package db + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" + "github.com/stretchr/testify/suite" + + "github.com/goravel/framework/contracts/database" + "github.com/goravel/framework/errors" + mocksdb "github.com/goravel/framework/mocks/database/db" +) + +// TestUser is a test model +type TestUser struct { + ID uint `db:"id"` + Name string `db:"-"` + Age int +} + +type QueryTestSuite struct { + suite.Suite + mockBuilder *mocksdb.Builder + query *Query +} + +func TestQueryTestSuite(t *testing.T) { + suite.Run(t, &QueryTestSuite{}) +} + +func (s *QueryTestSuite) SetupTest() { + s.mockBuilder = mocksdb.NewBuilder(s.T()) + s.query = NewQuery(database.Config{}, s.mockBuilder, "users") +} + +func (s *QueryTestSuite) TestFirst() { + var user TestUser + s.mockBuilder.EXPECT().Get(&user, "SELECT * FROM users WHERE name = ?", "John").Return(nil).Once() + + err := s.query.Where("name", "John").First(&user) + s.Nil(err) +} + +func (s *QueryTestSuite) TestGet() { + var users []TestUser + s.mockBuilder.EXPECT().Select(&users, "SELECT * FROM users WHERE age = ?", 25).Return(nil).Once() + + err := s.query.Where("age", 25).Get(&users) + s.Nil(err) + s.mockBuilder.AssertExpectations(s.T()) +} + +func (s *QueryTestSuite) TestInsert() { + s.Run("single struct", func() { + user := TestUser{ + ID: 1, + Name: "John", + Age: 25, + } + + mockResult := &MockResult{} + mockResult.On("RowsAffected").Return(int64(1), nil) + s.mockBuilder.EXPECT().Exec("INSERT INTO users (id) VALUES (?)", uint(1)).Return(mockResult, nil).Once() + + result, err := s.query.Insert(user) + s.Nil(err) + s.Equal(int64(1), result.RowsAffected) + + mockResult.AssertExpectations(s.T()) + }) + + s.Run("multiple structs", func() { + users := []TestUser{ + {ID: 1, Name: "John", Age: 25}, + {ID: 2, Name: "Jane", Age: 30}, + } + + mockResult := &MockResult{} + mockResult.On("RowsAffected").Return(int64(2), nil) + s.mockBuilder.EXPECT().Exec("INSERT INTO users (id) VALUES (?),(?)", uint(1), uint(2)).Return(mockResult, nil).Once() + + result, err := s.query.Insert(users) + s.Nil(err) + s.Equal(int64(2), result.RowsAffected) + + mockResult.AssertExpectations(s.T()) + }) + + s.Run("single map", func() { + user := map[string]any{ + "id": 1, + "name": "John", + "age": 25, + } + + mockResult := &MockResult{} + mockResult.On("RowsAffected").Return(int64(1), nil) + s.mockBuilder.EXPECT().Exec("INSERT INTO users (age,id,name) VALUES (?,?,?)", 25, 1, "John").Return(mockResult, nil).Once() + + result, err := s.query.Insert(user) + s.Nil(err) + s.Equal(int64(1), result.RowsAffected) + + mockResult.AssertExpectations(s.T()) + }) + + s.Run("multiple maps", func() { + users := []map[string]any{ + {"id": 1, "name": "John", "age": 25}, + {"id": 2, "name": "Jane", "age": 30}, + } + + mockResult := &MockResult{} + mockResult.On("RowsAffected").Return(int64(2), nil) + s.mockBuilder.EXPECT().Exec("INSERT INTO users (age,id,name) VALUES (?,?,?),(?,?,?)", 25, 1, "John", 30, 2, "Jane").Return(mockResult, nil).Once() + + result, err := s.query.Insert(users) + s.Nil(err) + s.Equal(int64(2), result.RowsAffected) + + mockResult.AssertExpectations(s.T()) + }) + + s.Run("unknown type", func() { + user := "unknown" + + _, err := s.query.Insert(user) + s.Equal(errors.DatabaseUnsupportedType.Args("string", "struct, []struct, map[string]any, []map[string]any").SetModule("DB"), err) + }) + + s.Run("failed to exec", func() { + user := TestUser{ + ID: 1, + Name: "John", + Age: 25, + } + + s.mockBuilder.EXPECT().Exec("INSERT INTO users (id) VALUES (?)", uint(1)).Return(nil, assert.AnError).Once() + + result, err := s.query.Insert(user) + s.Nil(result) + s.Equal(assert.AnError, err) + }) +} + +func (s *QueryTestSuite) TestWhere() { + s.Run("simple where condition", func() { + var user TestUser + s.mockBuilder.EXPECT().Get(&user, "SELECT * FROM users WHERE name = ?", "John").Return(nil).Once() + + err := s.query.Where("name", "John").First(&user) + s.Nil(err) + }) + + s.Run("where with multiple arguments", func() { + var users []TestUser + s.mockBuilder.EXPECT().Select(&users, "SELECT * FROM users WHERE age IN (?,?)", 25, 30).Return(nil).Once() + + err := s.query.Where("age", []int{25, 30}).Get(&users) + s.Nil(err) + }) + + s.Run("where with raw query", func() { + var users []TestUser + s.mockBuilder.EXPECT().Select(&users, "SELECT * FROM users WHERE age > ?", 18).Return(nil).Once() + + err := s.query.Where("age > ?", 18).Get(&users) + s.Nil(err) + }) +} + +// MockResult implements sql.Result interface for testing +type MockResult struct { + mock.Mock +} + +func (m *MockResult) LastInsertId() (int64, error) { + arguments := m.Called() + return arguments.Get(0).(int64), arguments.Error(1) +} + +func (m *MockResult) RowsAffected() (int64, error) { + arguments := m.Called() + return arguments.Get(0).(int64), arguments.Error(1) +} diff --git a/database/db/utils.go b/database/db/utils.go new file mode 100644 index 000000000..9402dd9e5 --- /dev/null +++ b/database/db/utils.go @@ -0,0 +1,133 @@ +package db + +import ( + "reflect" + "strings" + + "github.com/goravel/framework/errors" +) + +func convertToSliceMap(data any) ([]map[string]any, error) { + if data == nil { + return nil, nil + } + + if maps, ok := data.([]map[string]any); ok { + return maps, nil + } + + val := reflect.ValueOf(data) + typ := val.Type() + + // Handle pointer + if typ.Kind() == reflect.Ptr { + if val.IsNil() { + return nil, nil + } + val = val.Elem() + typ = val.Type() + } + + // Handle slice + if typ.Kind() == reflect.Slice { + length := val.Len() + if length == 0 { + return []map[string]any{}, nil + } + + result := make([]map[string]any, length) + for i := 0; i < length; i++ { + elem := val.Index(i) + m, err := convertToMap(elem.Interface()) + if err != nil { + return nil, err + } + if m != nil { + result[i] = m + } + } + return result, nil + } + + // Handle single value (struct or map) + m, err := convertToMap(data) + if err != nil { + return nil, err + } + if m != nil { + return []map[string]any{m}, nil + } + return nil, nil +} + +func convertToMap(data any) (map[string]any, error) { + if data == nil { + return nil, nil + } + + if m, ok := data.(map[string]any); ok { + return m, nil + } + + val := reflect.ValueOf(data) + typ := val.Type() + + // Handle pointer + if typ.Kind() == reflect.Ptr { + if val.IsNil() { + return nil, nil + } + val = val.Elem() + typ = val.Type() + } + + if typ.Kind() != reflect.Struct { + return nil, errors.DatabaseUnsupportedType.Args(typ.String(), "struct, []struct, map[string]any, []map[string]any").SetModule("DB") + } + + // Handle struct + result := make(map[string]any) + for i := 0; i < typ.NumField(); i++ { + field := typ.Field(i) + if !field.IsExported() { + continue + } + + // Handle embedded struct + if field.Anonymous { + fieldValue := val.Field(i) + if fieldValue.Kind() == reflect.Ptr && !fieldValue.IsNil() { + fieldValue = fieldValue.Elem() + } + if fieldValue.Kind() == reflect.Struct { + embedded, err := convertToMap(fieldValue.Interface()) + if err != nil { + return nil, err + } + for k, v := range embedded { + result[k] = v + } + } + continue + } + + // Get field name from db tag or use field name + tag := field.Tag.Get("db") + if tag == "" || tag == "-" { + continue + } + var fieldName string + if comma := strings.Index(tag, ","); comma != -1 { + fieldName = tag[:comma] + } else { + fieldName = tag + } + + fieldValue := val.Field(i) + if fieldValue.Kind() == reflect.Ptr && !fieldValue.IsNil() { + fieldValue = fieldValue.Elem() + } + result[fieldName] = fieldValue.Interface() + } + return result, nil +} diff --git a/database/db/utils_test.go b/database/db/utils_test.go new file mode 100644 index 000000000..24d84175e --- /dev/null +++ b/database/db/utils_test.go @@ -0,0 +1,95 @@ +package db + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +type Body struct { + Weight string `db:"weight"` + Height int `db:"-"` + Age uint +} + +type User struct { + ID int `db:"id"` + Name string `db:"-"` + Email string + Body +} + +func TestConvertToSliceMap(t *testing.T) { + tests := []struct { + data any + want []map[string]any + }{ + { + data: nil, + want: nil, + }, + { + data: []User{ + {ID: 1, Name: "John", Email: "john@example.com", Body: Body{Weight: "100kg", Height: 180, Age: 25}}, + {ID: 2, Name: "Jane", Email: "jane@example.com", Body: Body{Weight: "90kg", Height: 170, Age: 20}}, + }, + want: []map[string]any{ + {"id": 1, "weight": "100kg"}, + {"id": 2, "weight": "90kg"}, + }, + }, + { + data: []*User{ + {ID: 1, Name: "John", Email: "john@example.com", Body: Body{Weight: "100kg", Height: 180, Age: 25}}, + {ID: 2, Name: "Jane", Email: "jane@example.com", Body: Body{Weight: "90kg", Height: 170, Age: 20}}, + }, + want: []map[string]any{ + {"id": 1, "weight": "100kg"}, + {"id": 2, "weight": "90kg"}, + }, + }, + { + data: []Body{ + {Weight: "100kg", Height: 180, Age: 25}, + {Weight: "90kg", Height: 170, Age: 20}, + }, + want: []map[string]any{{"weight": "100kg"}, {"weight": "90kg"}}, + }, + { + data: Body{ + Weight: "100kg", + Height: 180, + Age: 25, + }, + want: []map[string]any{{"weight": "100kg"}}, + }, + { + data: &Body{ + Weight: "100kg", + Height: 180, + Age: 25, + }, + want: []map[string]any{{"weight": "100kg"}}, + }, + { + data: map[string]any{ + "weight": "100kg", + "Age": 25, + }, + want: []map[string]any{{"weight": "100kg", "Age": 25}}, + }, + { + data: []map[string]any{ + {"weight": "100kg", "Age": 25}, + {"weight": "90kg", "Age": 20}, + }, + want: []map[string]any{{"weight": "100kg", "Age": 25}, {"weight": "90kg", "Age": 20}}, + }, + } + + for _, test := range tests { + sliceMap, err := convertToSliceMap(test.data) + assert.NoError(t, err) + assert.Equal(t, test.want, sliceMap) + } +} diff --git a/errors/list.go b/errors/list.go index 1515e7ea5..72828b547 100644 --- a/errors/list.go +++ b/errors/list.go @@ -50,6 +50,7 @@ var ( DatabaseForceIsRequiredInProduction = New("application in production use --force to run this command") DatabaseSeederNotFound = New("not found %s seeder") DatabaseFailToRunSeeder = New("fail to run seeder: %v") + DatabaseUnsupportedType = New("unsupported type: %s, expected %s") DockerUnknownContainerType = New("unknown container type") DockerInsufficientDatabaseContainers = New("the number of database container is not enough, expect: %d, got: %d") diff --git a/mocks/database/db/Builder.go b/mocks/database/db/Builder.go new file mode 100644 index 000000000..f348b118c --- /dev/null +++ b/mocks/database/db/Builder.go @@ -0,0 +1,221 @@ +// Code generated by mockery. DO NOT EDIT. + +package db + +import ( + sql "database/sql" + + mock "github.com/stretchr/testify/mock" +) + +// Builder is an autogenerated mock type for the Builder type +type Builder struct { + mock.Mock +} + +type Builder_Expecter struct { + mock *mock.Mock +} + +func (_m *Builder) EXPECT() *Builder_Expecter { + return &Builder_Expecter{mock: &_m.Mock} +} + +// Exec provides a mock function with given fields: query, args +func (_m *Builder) Exec(query string, args ...interface{}) (sql.Result, error) { + 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 Exec") + } + + var r0 sql.Result + var r1 error + if rf, ok := ret.Get(0).(func(string, ...interface{}) (sql.Result, error)); ok { + return rf(query, args...) + } + if rf, ok := ret.Get(0).(func(string, ...interface{}) sql.Result); ok { + r0 = rf(query, args...) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(sql.Result) + } + } + + if rf, ok := ret.Get(1).(func(string, ...interface{}) error); ok { + r1 = rf(query, args...) + } else { + r1 = ret.Error(1) + } + + return r0, r1 +} + +// Builder_Exec_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Exec' +type Builder_Exec_Call struct { + *mock.Call +} + +// Exec is a helper method to define mock.On call +// - query string +// - args ...interface{} +func (_e *Builder_Expecter) Exec(query interface{}, args ...interface{}) *Builder_Exec_Call { + return &Builder_Exec_Call{Call: _e.mock.On("Exec", + append([]interface{}{query}, args...)...)} +} + +func (_c *Builder_Exec_Call) Run(run func(query string, args ...interface{})) *Builder_Exec_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].(string), variadicArgs...) + }) + return _c +} + +func (_c *Builder_Exec_Call) Return(_a0 sql.Result, _a1 error) *Builder_Exec_Call { + _c.Call.Return(_a0, _a1) + return _c +} + +func (_c *Builder_Exec_Call) RunAndReturn(run func(string, ...interface{}) (sql.Result, error)) *Builder_Exec_Call { + _c.Call.Return(run) + return _c +} + +// Get provides a mock function with given fields: dest, query, args +func (_m *Builder) Get(dest interface{}, query string, args ...interface{}) error { + var _ca []interface{} + _ca = append(_ca, dest, query) + _ca = append(_ca, args...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for Get") + } + + var r0 error + if rf, ok := ret.Get(0).(func(interface{}, string, ...interface{}) error); ok { + r0 = rf(dest, query, args...) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Builder_Get_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Get' +type Builder_Get_Call struct { + *mock.Call +} + +// Get is a helper method to define mock.On call +// - dest interface{} +// - query string +// - args ...interface{} +func (_e *Builder_Expecter) Get(dest interface{}, query interface{}, args ...interface{}) *Builder_Get_Call { + return &Builder_Get_Call{Call: _e.mock.On("Get", + append([]interface{}{dest, query}, args...)...)} +} + +func (_c *Builder_Get_Call) Run(run func(dest interface{}, query string, args ...interface{})) *Builder_Get_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]interface{}, len(args)-2) + for i, a := range args[2:] { + if a != nil { + variadicArgs[i] = a.(interface{}) + } + } + run(args[0].(interface{}), args[1].(string), variadicArgs...) + }) + return _c +} + +func (_c *Builder_Get_Call) Return(_a0 error) *Builder_Get_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Builder_Get_Call) RunAndReturn(run func(interface{}, string, ...interface{}) error) *Builder_Get_Call { + _c.Call.Return(run) + return _c +} + +// Select provides a mock function with given fields: dest, query, args +func (_m *Builder) Select(dest interface{}, query string, args ...interface{}) error { + var _ca []interface{} + _ca = append(_ca, dest, query) + _ca = append(_ca, args...) + ret := _m.Called(_ca...) + + if len(ret) == 0 { + panic("no return value specified for Select") + } + + var r0 error + if rf, ok := ret.Get(0).(func(interface{}, string, ...interface{}) error); ok { + r0 = rf(dest, query, args...) + } else { + r0 = ret.Error(0) + } + + return r0 +} + +// Builder_Select_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Select' +type Builder_Select_Call struct { + *mock.Call +} + +// Select is a helper method to define mock.On call +// - dest interface{} +// - query string +// - args ...interface{} +func (_e *Builder_Expecter) Select(dest interface{}, query interface{}, args ...interface{}) *Builder_Select_Call { + return &Builder_Select_Call{Call: _e.mock.On("Select", + append([]interface{}{dest, query}, args...)...)} +} + +func (_c *Builder_Select_Call) Run(run func(dest interface{}, query string, args ...interface{})) *Builder_Select_Call { + _c.Call.Run(func(args mock.Arguments) { + variadicArgs := make([]interface{}, len(args)-2) + for i, a := range args[2:] { + if a != nil { + variadicArgs[i] = a.(interface{}) + } + } + run(args[0].(interface{}), args[1].(string), variadicArgs...) + }) + return _c +} + +func (_c *Builder_Select_Call) Return(_a0 error) *Builder_Select_Call { + _c.Call.Return(_a0) + return _c +} + +func (_c *Builder_Select_Call) RunAndReturn(run func(interface{}, string, ...interface{}) error) *Builder_Select_Call { + _c.Call.Return(run) + return _c +} + +// NewBuilder creates a new instance of Builder. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations. +// The first argument is typically a *testing.T value. +func NewBuilder(t interface { + mock.TestingT + Cleanup(func()) +}) *Builder { + mock := &Builder{} + mock.Mock.Test(t) + + t.Cleanup(func() { mock.AssertExpectations(t) }) + + return mock +} diff --git a/mocks/database/db/Query.go b/mocks/database/db/Query.go index ba538b599..d32f62588 100644 --- a/mocks/database/db/Query.go +++ b/mocks/database/db/Query.go @@ -20,17 +20,17 @@ func (_m *Query) EXPECT() *Query_Expecter { return &Query_Expecter{mock: &_m.Mock} } -// Delete provides a mock function with no fields -func (_m *Query) Delete() error { - ret := _m.Called() +// First provides a mock function with given fields: dest +func (_m *Query) First(dest interface{}) error { + ret := _m.Called(dest) if len(ret) == 0 { - panic("no return value specified for Delete") + panic("no return value specified for First") } var r0 error - if rf, ok := ret.Get(0).(func() error); ok { - r0 = rf() + if rf, ok := ret.Get(0).(func(interface{}) error); ok { + r0 = rf(dest) } else { r0 = ret.Error(0) } @@ -38,29 +38,30 @@ func (_m *Query) Delete() error { return r0 } -// Query_Delete_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Delete' -type Query_Delete_Call struct { +// Query_First_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'First' +type Query_First_Call struct { *mock.Call } -// Delete is a helper method to define mock.On call -func (_e *Query_Expecter) Delete() *Query_Delete_Call { - return &Query_Delete_Call{Call: _e.mock.On("Delete")} +// First is a helper method to define mock.On call +// - dest interface{} +func (_e *Query_Expecter) First(dest interface{}) *Query_First_Call { + return &Query_First_Call{Call: _e.mock.On("First", dest)} } -func (_c *Query_Delete_Call) Run(run func()) *Query_Delete_Call { +func (_c *Query_First_Call) Run(run func(dest interface{})) *Query_First_Call { _c.Call.Run(func(args mock.Arguments) { - run() + run(args[0].(interface{})) }) return _c } -func (_c *Query_Delete_Call) Return(_a0 error) *Query_Delete_Call { +func (_c *Query_First_Call) Return(_a0 error) *Query_First_Call { _c.Call.Return(_a0) return _c } -func (_c *Query_Delete_Call) RunAndReturn(run func() error) *Query_Delete_Call { +func (_c *Query_First_Call) RunAndReturn(run func(interface{}) error) *Query_First_Call { _c.Call.Return(run) return _c } @@ -111,92 +112,60 @@ func (_c *Query_Get_Call) RunAndReturn(run func(interface{}) error) *Query_Get_C return _c } -// Insert provides a mock function with no fields -func (_m *Query) Insert() error { - ret := _m.Called() +// Insert provides a mock function with given fields: data +func (_m *Query) Insert(data interface{}) (*db.Result, error) { + ret := _m.Called(data) if len(ret) == 0 { panic("no return value specified for Insert") } - var r0 error - if rf, ok := ret.Get(0).(func() error); ok { - r0 = rf() - } else { - r0 = ret.Error(0) + var r0 *db.Result + var r1 error + if rf, ok := ret.Get(0).(func(interface{}) (*db.Result, error)); ok { + return rf(data) } - - return r0 -} - -// Query_Insert_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Insert' -type Query_Insert_Call struct { - *mock.Call -} - -// Insert is a helper method to define mock.On call -func (_e *Query_Expecter) Insert() *Query_Insert_Call { - return &Query_Insert_Call{Call: _e.mock.On("Insert")} -} - -func (_c *Query_Insert_Call) Run(run func()) *Query_Insert_Call { - _c.Call.Run(func(args mock.Arguments) { - run() - }) - return _c -} - -func (_c *Query_Insert_Call) Return(_a0 error) *Query_Insert_Call { - _c.Call.Return(_a0) - return _c -} - -func (_c *Query_Insert_Call) RunAndReturn(run func() error) *Query_Insert_Call { - _c.Call.Return(run) - return _c -} - -// Update provides a mock function with no fields -func (_m *Query) Update() error { - ret := _m.Called() - - if len(ret) == 0 { - panic("no return value specified for Update") + if rf, ok := ret.Get(0).(func(interface{}) *db.Result); ok { + r0 = rf(data) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*db.Result) + } } - var r0 error - if rf, ok := ret.Get(0).(func() error); ok { - r0 = rf() + if rf, ok := ret.Get(1).(func(interface{}) error); ok { + r1 = rf(data) } else { - r0 = ret.Error(0) + r1 = ret.Error(1) } - return r0 + return r0, r1 } -// Query_Update_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Update' -type Query_Update_Call struct { +// Query_Insert_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'Insert' +type Query_Insert_Call struct { *mock.Call } -// Update is a helper method to define mock.On call -func (_e *Query_Expecter) Update() *Query_Update_Call { - return &Query_Update_Call{Call: _e.mock.On("Update")} +// Insert is a helper method to define mock.On call +// - data interface{} +func (_e *Query_Expecter) Insert(data interface{}) *Query_Insert_Call { + return &Query_Insert_Call{Call: _e.mock.On("Insert", data)} } -func (_c *Query_Update_Call) Run(run func()) *Query_Update_Call { +func (_c *Query_Insert_Call) Run(run func(data interface{})) *Query_Insert_Call { _c.Call.Run(func(args mock.Arguments) { - run() + run(args[0].(interface{})) }) return _c } -func (_c *Query_Update_Call) Return(_a0 error) *Query_Update_Call { - _c.Call.Return(_a0) +func (_c *Query_Insert_Call) Return(_a0 *db.Result, _a1 error) *Query_Insert_Call { + _c.Call.Return(_a0, _a1) return _c } -func (_c *Query_Update_Call) RunAndReturn(run func() error) *Query_Update_Call { +func (_c *Query_Insert_Call) RunAndReturn(run func(interface{}) (*db.Result, error)) *Query_Insert_Call { _c.Call.Return(run) return _c } diff --git a/tests/db_test.go b/tests/db_test.go index 30cc24992..ec6e186b7 100644 --- a/tests/db_test.go +++ b/tests/db_test.go @@ -3,6 +3,7 @@ package tests import ( "testing" + "github.com/goravel/framework/support/carbon" "github.com/goravel/sqlite" "github.com/stretchr/testify/suite" ) @@ -22,7 +23,7 @@ func TestDBTestSuite(t *testing.T) { func (s *DBTestSuite) SetupSuite() { s.queries = NewTestQueryBuilder().All("", false) for _, query := range s.queries { - query.CreateTable(TestTableUsers) + query.CreateTable(TestTableProducts) } } @@ -34,12 +35,139 @@ func (s *DBTestSuite) TearDownSuite() { } } +func (s *DBTestSuite) TestInsert_First_Get() { + for driver, query := range s.queries { + now := carbon.NewDateTime(carbon.FromDateTime(2025, 1, 2, 3, 4, 5)) + + s.Run(driver, func() { + s.Run("single struct", func() { + result, err := query.DB().Table("products").Insert(Product{ + Name: "model", + Model: Model{ + Timestamps: Timestamps{ + CreatedAt: &now, + UpdatedAt: &now, + }, + }, + }) + + s.NoError(err) + s.Equal(int64(1), result.RowsAffected) + + var product Product + err = query.DB().Table("products").Where("name", "model").Where("deleted_at", nil).First(&product) + s.NoError(err) + s.True(product.ID > 0) + s.Equal("model", product.Name) + s.Equal(&now, product.CreatedAt) + s.Equal(&now, product.UpdatedAt) + s.Nil(product.DeletedAt) + }) + + s.Run("multiple structs", func() { + result, err := query.DB().Table("products").Insert([]Product{ + { + Name: "model1", + Model: Model{ + Timestamps: Timestamps{ + CreatedAt: &now, + UpdatedAt: &now, + }, + }, + }, + { + Name: "model2", + }, + }) + s.NoError(err) + s.Equal(int64(2), result.RowsAffected) + + var products []Product + err = query.DB().Table("products").Where("name", []string{"model1", "model2"}).Where("deleted_at", nil).Get(&products) + s.NoError(err) + s.Equal(2, len(products)) + s.Equal("model1", products[0].Name) + s.Equal("model2", products[1].Name) + }) + + s.Run("single map", func() { + result, err := query.DB().Table("products").Insert(map[string]any{ + "name": "map", + "created_at": now, + "updated_at": &now, + }) + s.NoError(err) + s.Equal(int64(1), result.RowsAffected) + + var product Product + err = query.DB().Table("products").Where("name", "map").Where("deleted_at", nil).First(&product) + s.NoError(err) + s.Equal("map", product.Name) + s.Equal(&now, product.CreatedAt) + s.Equal(&now, product.UpdatedAt) + s.Nil(product.DeletedAt) + }) + + s.Run("multiple map", func() { + result, err := query.DB().Table("products").Insert([]map[string]any{ + { + "name": "map1", + "created_at": now, + "updated_at": &now, + }, + { + "name": "map2", + }, + }) + s.NoError(err) + s.Equal(int64(2), result.RowsAffected) + + var products []Product + err = query.DB().Table("products").Where("name", []string{"map1", "map2"}).Where("deleted_at", nil).Get(&products) + s.NoError(err) + s.Equal(2, len(products)) + s.Equal("map1", products[0].Name) + s.Equal("map2", products[1].Name) + }) + }) + } +} + func (s *DBTestSuite) TestWhere() { for driver, query := range s.queries { s.Run(driver, func() { - var user []User - err := query.DB().Table("users").Where("name = ?", "count_user").Get(&user) - s.NoError(err) + now := carbon.NewDateTime(carbon.FromDateTime(2025, 1, 2, 3, 4, 5)) + query.DB().Table("products").Insert(Product{ + Name: "model", + Model: Model{ + Timestamps: Timestamps{ + CreatedAt: &now, + UpdatedAt: &now, + }, + }, + }) + + s.Run("simple where condition", func() { + var product Product + err := query.DB().Table("products").Where("name", "model").First(&product) + s.NoError(err) + s.Equal("model", product.Name) + }) + + s.Run("where with multiple arguments", func() { + var products []Product + err := query.DB().Table("products").Where("name", []string{"model", "model1"}).Get(&products) + s.NoError(err) + s.Equal(1, len(products)) + s.Equal("model", products[0].Name) + }) + + s.Run("where with raw query", func() { + var product Product + err := query.DB().Table("products").Where("name = ?", "model").First(&product) + s.NoError(err) + s.Equal("model", product.Name) + }) }) } } diff --git a/tests/models.go b/tests/models.go index 7a529cc10..61ed640d8 100644 --- a/tests/models.go +++ b/tests/models.go @@ -22,18 +22,18 @@ type Model struct { } type SoftDeletes struct { - DeletedAt gorm.DeletedAt `gorm:"column:deleted_at" json:"deleted_at" db:"deleted_at"` + DeletedAt *gorm.DeletedAt `gorm:"column:deleted_at" json:"deleted_at" db:"deleted_at"` } type Timestamps struct { - CreatedAt carbon.DateTime `gorm:"autoCreateTime;column:created_at" json:"created_at" db:"created_at"` - UpdatedAt carbon.DateTime `gorm:"autoUpdateTime;column:updated_at" json:"updated_at" db:"updated_at"` + CreatedAt *carbon.DateTime `gorm:"autoCreateTime;column:created_at" json:"created_at" db:"created_at"` + UpdatedAt *carbon.DateTime `gorm:"autoUpdateTime;column:updated_at" json:"updated_at" db:"updated_at"` } type User struct { Model SoftDeletes - Name string + Name string `db:"name"` Bio *string Avatar string Address *Address @@ -446,7 +446,7 @@ type Phone struct { type Product struct { Model SoftDeletes - Name string + Name string `db:"name"` } func (r *Product) Connection() string { From 271798b24e20e728042a1cb14139b837d04800ff Mon Sep 17 00:00:00 2001 From: Bowen Date: Tue, 18 Feb 2025 17:18:39 +0800 Subject: [PATCH 05/15] update mod --- go.mod | 2 +- go.sum | 4 ++-- tests/go.mod | 2 +- tests/go.sum | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 519a5a8cb..0cc74c217 100644 --- a/go.mod +++ b/go.mod @@ -92,7 +92,7 @@ require ( github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 go.opentelemetry.io/otel v1.33.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/net v0.34.0 // indirect + golang.org/x/net v0.35.0 // indirect golang.org/x/sync v0.11.0 // indirect golang.org/x/sys v0.30.0 // indirect golang.org/x/term v0.29.0 // indirect diff --git a/go.sum b/go.sum index 074a72eba..74a964998 100644 --- a/go.sum +++ b/go.sum @@ -251,8 +251,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= -golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= -golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= +golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= +golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= diff --git a/tests/go.mod b/tests/go.mod index 0868dcb0b..c64693cdd 100644 --- a/tests/go.mod +++ b/tests/go.mod @@ -65,7 +65,7 @@ require ( github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect golang.org/x/crypto v0.33.0 // indirect golang.org/x/exp v0.0.0-20250215185904-eff6e970281f // indirect - golang.org/x/net v0.34.0 // indirect + golang.org/x/net v0.35.0 // indirect golang.org/x/sync v0.11.0 // indirect golang.org/x/sys v0.30.0 // indirect golang.org/x/term v0.29.0 // indirect diff --git a/tests/go.sum b/tests/go.sum index c800be7c4..2cd59a462 100644 --- a/tests/go.sum +++ b/tests/go.sum @@ -267,8 +267,8 @@ golang.org/x/net v0.14.0/go.mod h1:PpSgVXXLK0OxS0F31C1/tv6XNguvCrnXIDrFMspZIUI= golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= golang.org/x/net v0.19.0/go.mod h1:CfAk/cbD4CthTvqiEl8NpboMuiuOYsAr/7NOjZJtv1U= golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= -golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0= -golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k= +golang.org/x/net v0.35.0 h1:T5GQRQb2y08kTAByq9L4/bz8cipCdA8FbRTXewonqY8= +golang.org/x/net v0.35.0/go.mod h1:EglIi67kWsHKlRzzVMUD93VMSWGFOMSZgxFjparz1Qk= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= From 15ebb9ccc3bc974cd84e9d2e4dbf6a0ec8bec4fb Mon Sep 17 00:00:00 2001 From: Bowen Date: Tue, 18 Feb 2025 17:21:36 +0800 Subject: [PATCH 06/15] optimize --- tests/models.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/models.go b/tests/models.go index 61ed640d8..4149eeb49 100644 --- a/tests/models.go +++ b/tests/models.go @@ -33,7 +33,7 @@ type Timestamps struct { type User struct { Model SoftDeletes - Name string `db:"name"` + Name string Bio *string Avatar string Address *Address From 54a0f6bc1fd8f9bf343e998c9b46a52211497788 Mon Sep 17 00:00:00 2001 From: Bowen Date: Tue, 18 Feb 2025 17:23:06 +0800 Subject: [PATCH 07/15] change CreatedAt to point --- auth/auth_test.go | 4 ++-- database/factory/factory_test.go | 6 +++--- database/gorm/event_test.go | 4 ++-- database/orm/model.go | 6 +++--- support/database/database_test.go | 4 ++-- 5 files changed, 12 insertions(+), 12 deletions(-) diff --git a/auth/auth_test.go b/auth/auth_test.go index c5a245161..0f8d1b16a 100644 --- a/auth/auth_test.go +++ b/auth/auth_test.go @@ -26,8 +26,8 @@ var testUserGuard = "user" type User struct { ID uint `gorm:"primaryKey" json:"id"` Name string - CreatedAt carbon.DateTime `gorm:"autoCreateTime;column:created_at" json:"created_at"` - UpdatedAt carbon.DateTime `gorm:"autoUpdateTime;column:updated_at" json:"updated_at"` + CreatedAt *carbon.DateTime `gorm:"autoCreateTime;column:created_at" json:"created_at"` + UpdatedAt *carbon.DateTime `gorm:"autoUpdateTime;column:updated_at" json:"updated_at"` } type Context struct { diff --git a/database/factory/factory_test.go b/database/factory/factory_test.go index e915e0e90..8252e2f2d 100644 --- a/database/factory/factory_test.go +++ b/database/factory/factory_test.go @@ -18,12 +18,12 @@ type Model struct { } type SoftDeletes struct { - DeletedAt gormio.DeletedAt `gorm:"column:deleted_at" json:"deleted_at"` + DeletedAt *gormio.DeletedAt `gorm:"column:deleted_at" json:"deleted_at"` } type Timestamps struct { - CreatedAt carbon.DateTime `gorm:"autoCreateTime;column:created_at" json:"created_at"` - UpdatedAt carbon.DateTime `gorm:"autoUpdateTime;column:updated_at" json:"updated_at"` + CreatedAt *carbon.DateTime `gorm:"autoCreateTime;column:created_at" json:"created_at"` + UpdatedAt *carbon.DateTime `gorm:"autoUpdateTime;column:updated_at" json:"updated_at"` } type User struct { diff --git a/database/gorm/event_test.go b/database/gorm/event_test.go index 075e7a74b..588aad774 100644 --- a/database/gorm/event_test.go +++ b/database/gorm/event_test.go @@ -17,8 +17,8 @@ type Model struct { } type Timestamps struct { - CreatedAt carbon.DateTime `gorm:"autoCreateTime;column:created_at" json:"created_at"` - UpdatedAt carbon.DateTime `gorm:"autoUpdateTime;column:updated_at" json:"updated_at"` + CreatedAt *carbon.DateTime `gorm:"autoCreateTime;column:created_at" json:"created_at"` + UpdatedAt *carbon.DateTime `gorm:"autoUpdateTime;column:updated_at" json:"updated_at"` } type TestEventModel struct { diff --git a/database/orm/model.go b/database/orm/model.go index 3cf6ebf18..c9c475aa1 100644 --- a/database/orm/model.go +++ b/database/orm/model.go @@ -15,10 +15,10 @@ type Model struct { } type SoftDeletes struct { - DeletedAt gorm.DeletedAt `gorm:"column:deleted_at" json:"deleted_at"` + DeletedAt *gorm.DeletedAt `gorm:"column:deleted_at" json:"deleted_at"` } type Timestamps struct { - CreatedAt carbon.DateTime `gorm:"autoCreateTime;column:created_at" json:"created_at"` - UpdatedAt carbon.DateTime `gorm:"autoUpdateTime;column:updated_at" json:"updated_at"` + CreatedAt *carbon.DateTime `gorm:"autoCreateTime;column:created_at" json:"created_at"` + UpdatedAt *carbon.DateTime `gorm:"autoUpdateTime;column:updated_at" json:"updated_at"` } diff --git a/support/database/database_test.go b/support/database/database_test.go index 1135b6432..205ce5081 100644 --- a/support/database/database_test.go +++ b/support/database/database_test.go @@ -16,8 +16,8 @@ type Model struct { } type Timestamps struct { - CreatedAt carbon.DateTime `gorm:"autoCreateTime;column:created_at" json:"created_at"` - UpdatedAt carbon.DateTime `gorm:"autoUpdateTime;column:updated_at" json:"updated_at"` + CreatedAt *carbon.DateTime `gorm:"autoCreateTime;column:created_at" json:"created_at"` + UpdatedAt *carbon.DateTime `gorm:"autoUpdateTime;column:updated_at" json:"updated_at"` } type TestStruct struct { From f399f713a3ebbf23b99fa626ea0f355572d9042e Mon Sep 17 00:00:00 2001 From: Bowen Date: Tue, 18 Feb 2025 17:24:26 +0800 Subject: [PATCH 08/15] fix test --- database/gorm/event_test.go | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/database/gorm/event_test.go b/database/gorm/event_test.go index 588aad774..c72c719c4 100644 --- a/database/gorm/event_test.go +++ b/database/gorm/event_test.go @@ -9,6 +9,7 @@ import ( "gorm.io/gorm" "github.com/goravel/framework/support/carbon" + "github.com/goravel/framework/support/convert" ) type Model struct { @@ -38,7 +39,7 @@ var testEventModel = TestEventModel{ Model: Model{ ID: 1, Timestamps: Timestamps{ - CreatedAt: carbon.NewDateTime(carbon.FromStdTime(testNow)), + CreatedAt: convert.Pointer(carbon.NewDateTime(carbon.FromStdTime(testNow))), }, }, Name: "name", @@ -76,8 +77,8 @@ func (s *EventTestSuite) SetupTest() { Model: Model{ ID: 1, Timestamps: Timestamps{ - CreatedAt: carbon.NewDateTime(carbon.FromStdTime(testNow)), - UpdatedAt: carbon.NewDateTime(carbon.FromStdTime(testNow)), + CreatedAt: convert.Pointer(carbon.NewDateTime(carbon.FromStdTime(testNow))), + UpdatedAt: convert.Pointer(carbon.NewDateTime(carbon.FromStdTime(testNow))), }, }, Avatar: "avatar1", IsAdmin: false, IsManage: 1, AdminAt: time.Now(), ManageAt: testNow}), @@ -85,8 +86,8 @@ func (s *EventTestSuite) SetupTest() { Model: Model{ ID: 1, Timestamps: Timestamps{ - CreatedAt: carbon.NewDateTime(carbon.FromStdTime(testNow)), - UpdatedAt: carbon.NewDateTime(carbon.FromStdTime(testNow)), + CreatedAt: convert.Pointer(carbon.NewDateTime(carbon.FromStdTime(testNow))), + UpdatedAt: convert.Pointer(carbon.NewDateTime(carbon.FromStdTime(testNow))), }, }, Avatar: "avatar1", IsAdmin: false, IsManage: 1, AdminAt: time.Now(), ManageAt: testNow}), @@ -162,7 +163,7 @@ func (s *EventTestSuite) TestGetAttribute() { Model: Model{ ID: 2, Timestamps: Timestamps{ - CreatedAt: carbon.NewDateTime(now), + CreatedAt: convert.Pointer(carbon.NewDateTime(now)), }, }, Avatar: "avatar1", From b666073ced628639a71022da4178fdf02854b706 Mon Sep 17 00:00:00 2001 From: Bowen Date: Tue, 18 Feb 2025 17:36:24 +0800 Subject: [PATCH 09/15] fix test --- database/gorm/event_test.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/database/gorm/event_test.go b/database/gorm/event_test.go index c72c719c4..84d1c0dbd 100644 --- a/database/gorm/event_test.go +++ b/database/gorm/event_test.go @@ -184,7 +184,7 @@ func (s *EventTestSuite) TestGetAttribute() { for _, event := range events { s.Equal(testEventModel.ID, event.GetAttribute("ID")) - s.Equal(testEventModel.CreatedAt, event.GetAttribute("CreatedAt")) + s.Equal(*testEventModel.CreatedAt, event.GetAttribute("CreatedAt")) s.Equal(testEventModel.Name, event.GetAttribute("Name")) } } @@ -352,8 +352,8 @@ func (s *EventTestSuite) TestColumnNames() { func TestStructToMap(t *testing.T) { assert.EqualValues(t, map[string]any{ "i_d": testEventModel.ID, - "created_at": testEventModel.CreatedAt, - "updated_at": testEventModel.UpdatedAt, + "created_at": *testEventModel.CreatedAt, + "updated_at": nil, "name": testEventModel.Name, "avatar": testEventModel.Avatar, "is_admin": testEventModel.IsAdmin, From f4dcafa9d39b4f7b529015501eeab1066e8cd8ad Mon Sep 17 00:00:00 2001 From: Bowen Date: Wed, 19 Feb 2025 21:54:07 +0800 Subject: [PATCH 10/15] remove pointer --- auth/auth_test.go | 4 ++-- database/factory/factory_test.go | 6 +++--- database/gorm/event_test.go | 21 ++++++++++----------- database/orm/model.go | 6 +++--- support/database/database_test.go | 4 ++-- tests/db_test.go | 12 ++++++------ tests/models.go | 6 +++--- 7 files changed, 29 insertions(+), 30 deletions(-) diff --git a/auth/auth_test.go b/auth/auth_test.go index 0f8d1b16a..c5a245161 100644 --- a/auth/auth_test.go +++ b/auth/auth_test.go @@ -26,8 +26,8 @@ var testUserGuard = "user" type User struct { ID uint `gorm:"primaryKey" json:"id"` Name string - CreatedAt *carbon.DateTime `gorm:"autoCreateTime;column:created_at" json:"created_at"` - UpdatedAt *carbon.DateTime `gorm:"autoUpdateTime;column:updated_at" json:"updated_at"` + CreatedAt carbon.DateTime `gorm:"autoCreateTime;column:created_at" json:"created_at"` + UpdatedAt carbon.DateTime `gorm:"autoUpdateTime;column:updated_at" json:"updated_at"` } type Context struct { diff --git a/database/factory/factory_test.go b/database/factory/factory_test.go index 8252e2f2d..e915e0e90 100644 --- a/database/factory/factory_test.go +++ b/database/factory/factory_test.go @@ -18,12 +18,12 @@ type Model struct { } type SoftDeletes struct { - DeletedAt *gormio.DeletedAt `gorm:"column:deleted_at" json:"deleted_at"` + DeletedAt gormio.DeletedAt `gorm:"column:deleted_at" json:"deleted_at"` } type Timestamps struct { - CreatedAt *carbon.DateTime `gorm:"autoCreateTime;column:created_at" json:"created_at"` - UpdatedAt *carbon.DateTime `gorm:"autoUpdateTime;column:updated_at" json:"updated_at"` + CreatedAt carbon.DateTime `gorm:"autoCreateTime;column:created_at" json:"created_at"` + UpdatedAt carbon.DateTime `gorm:"autoUpdateTime;column:updated_at" json:"updated_at"` } type User struct { diff --git a/database/gorm/event_test.go b/database/gorm/event_test.go index 84d1c0dbd..057ff30c2 100644 --- a/database/gorm/event_test.go +++ b/database/gorm/event_test.go @@ -9,7 +9,6 @@ import ( "gorm.io/gorm" "github.com/goravel/framework/support/carbon" - "github.com/goravel/framework/support/convert" ) type Model struct { @@ -18,8 +17,8 @@ type Model struct { } type Timestamps struct { - CreatedAt *carbon.DateTime `gorm:"autoCreateTime;column:created_at" json:"created_at"` - UpdatedAt *carbon.DateTime `gorm:"autoUpdateTime;column:updated_at" json:"updated_at"` + CreatedAt carbon.DateTime `gorm:"autoCreateTime;column:created_at" json:"created_at"` + UpdatedAt carbon.DateTime `gorm:"autoUpdateTime;column:updated_at" json:"updated_at"` } type TestEventModel struct { @@ -39,7 +38,7 @@ var testEventModel = TestEventModel{ Model: Model{ ID: 1, Timestamps: Timestamps{ - CreatedAt: convert.Pointer(carbon.NewDateTime(carbon.FromStdTime(testNow))), + CreatedAt: carbon.NewDateTime(carbon.FromStdTime(testNow)), }, }, Name: "name", @@ -77,8 +76,8 @@ func (s *EventTestSuite) SetupTest() { Model: Model{ ID: 1, Timestamps: Timestamps{ - CreatedAt: convert.Pointer(carbon.NewDateTime(carbon.FromStdTime(testNow))), - UpdatedAt: convert.Pointer(carbon.NewDateTime(carbon.FromStdTime(testNow))), + CreatedAt: carbon.NewDateTime(carbon.FromStdTime(testNow)), + UpdatedAt: carbon.NewDateTime(carbon.FromStdTime(testNow)), }, }, Avatar: "avatar1", IsAdmin: false, IsManage: 1, AdminAt: time.Now(), ManageAt: testNow}), @@ -86,8 +85,8 @@ func (s *EventTestSuite) SetupTest() { Model: Model{ ID: 1, Timestamps: Timestamps{ - CreatedAt: convert.Pointer(carbon.NewDateTime(carbon.FromStdTime(testNow))), - UpdatedAt: convert.Pointer(carbon.NewDateTime(carbon.FromStdTime(testNow))), + CreatedAt: carbon.NewDateTime(carbon.FromStdTime(testNow)), + UpdatedAt: carbon.NewDateTime(carbon.FromStdTime(testNow)), }, }, Avatar: "avatar1", IsAdmin: false, IsManage: 1, AdminAt: time.Now(), ManageAt: testNow}), @@ -163,7 +162,7 @@ func (s *EventTestSuite) TestGetAttribute() { Model: Model{ ID: 2, Timestamps: Timestamps{ - CreatedAt: convert.Pointer(carbon.NewDateTime(now)), + CreatedAt: carbon.NewDateTime(now), }, }, Avatar: "avatar1", @@ -184,7 +183,7 @@ func (s *EventTestSuite) TestGetAttribute() { for _, event := range events { s.Equal(testEventModel.ID, event.GetAttribute("ID")) - s.Equal(*testEventModel.CreatedAt, event.GetAttribute("CreatedAt")) + s.Equal(testEventModel.CreatedAt, event.GetAttribute("CreatedAt")) s.Equal(testEventModel.Name, event.GetAttribute("Name")) } } @@ -352,7 +351,7 @@ func (s *EventTestSuite) TestColumnNames() { func TestStructToMap(t *testing.T) { assert.EqualValues(t, map[string]any{ "i_d": testEventModel.ID, - "created_at": *testEventModel.CreatedAt, + "created_at": testEventModel.CreatedAt, "updated_at": nil, "name": testEventModel.Name, "avatar": testEventModel.Avatar, diff --git a/database/orm/model.go b/database/orm/model.go index c9c475aa1..3cf6ebf18 100644 --- a/database/orm/model.go +++ b/database/orm/model.go @@ -15,10 +15,10 @@ type Model struct { } type SoftDeletes struct { - DeletedAt *gorm.DeletedAt `gorm:"column:deleted_at" json:"deleted_at"` + DeletedAt gorm.DeletedAt `gorm:"column:deleted_at" json:"deleted_at"` } type Timestamps struct { - CreatedAt *carbon.DateTime `gorm:"autoCreateTime;column:created_at" json:"created_at"` - UpdatedAt *carbon.DateTime `gorm:"autoUpdateTime;column:updated_at" json:"updated_at"` + CreatedAt carbon.DateTime `gorm:"autoCreateTime;column:created_at" json:"created_at"` + UpdatedAt carbon.DateTime `gorm:"autoUpdateTime;column:updated_at" json:"updated_at"` } diff --git a/support/database/database_test.go b/support/database/database_test.go index 205ce5081..1135b6432 100644 --- a/support/database/database_test.go +++ b/support/database/database_test.go @@ -16,8 +16,8 @@ type Model struct { } type Timestamps struct { - CreatedAt *carbon.DateTime `gorm:"autoCreateTime;column:created_at" json:"created_at"` - UpdatedAt *carbon.DateTime `gorm:"autoUpdateTime;column:updated_at" json:"updated_at"` + CreatedAt carbon.DateTime `gorm:"autoCreateTime;column:created_at" json:"created_at"` + UpdatedAt carbon.DateTime `gorm:"autoUpdateTime;column:updated_at" json:"updated_at"` } type TestStruct struct { diff --git a/tests/db_test.go b/tests/db_test.go index ec6e186b7..b98ef16ff 100644 --- a/tests/db_test.go +++ b/tests/db_test.go @@ -45,8 +45,8 @@ func (s *DBTestSuite) TestInsert_First_Get() { Name: "model", Model: Model{ Timestamps: Timestamps{ - CreatedAt: &now, - UpdatedAt: &now, + CreatedAt: now, + UpdatedAt: now, }, }, }) @@ -70,8 +70,8 @@ func (s *DBTestSuite) TestInsert_First_Get() { Name: "model1", Model: Model{ Timestamps: Timestamps{ - CreatedAt: &now, - UpdatedAt: &now, + CreatedAt: now, + UpdatedAt: now, }, }, }, @@ -141,8 +141,8 @@ func (s *DBTestSuite) TestWhere() { Name: "model", Model: Model{ Timestamps: Timestamps{ - CreatedAt: &now, - UpdatedAt: &now, + CreatedAt: now, + UpdatedAt: now, }, }, }) diff --git a/tests/models.go b/tests/models.go index 4149eeb49..6431a3394 100644 --- a/tests/models.go +++ b/tests/models.go @@ -22,12 +22,12 @@ type Model struct { } type SoftDeletes struct { - DeletedAt *gorm.DeletedAt `gorm:"column:deleted_at" json:"deleted_at" db:"deleted_at"` + DeletedAt gorm.DeletedAt `gorm:"column:deleted_at" json:"deleted_at" db:"deleted_at"` } type Timestamps struct { - CreatedAt *carbon.DateTime `gorm:"autoCreateTime;column:created_at" json:"created_at" db:"created_at"` - UpdatedAt *carbon.DateTime `gorm:"autoUpdateTime;column:updated_at" json:"updated_at" db:"updated_at"` + CreatedAt carbon.DateTime `gorm:"autoCreateTime;column:created_at" json:"created_at" db:"created_at"` + UpdatedAt carbon.DateTime `gorm:"autoUpdateTime;column:updated_at" json:"updated_at" db:"updated_at"` } type User struct { From 76d9a25431aa0d1000c0317c1021ecb85297c74d Mon Sep 17 00:00:00 2001 From: Bowen Date: Wed, 19 Feb 2025 22:00:51 +0800 Subject: [PATCH 11/15] fix test --- database/gorm/event_test.go | 2 +- tests/db_test.go | 12 ++++++------ tests/query.go | 14 +++++++------- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/database/gorm/event_test.go b/database/gorm/event_test.go index 057ff30c2..075e7a74b 100644 --- a/database/gorm/event_test.go +++ b/database/gorm/event_test.go @@ -352,7 +352,7 @@ func TestStructToMap(t *testing.T) { assert.EqualValues(t, map[string]any{ "i_d": testEventModel.ID, "created_at": testEventModel.CreatedAt, - "updated_at": nil, + "updated_at": testEventModel.UpdatedAt, "name": testEventModel.Name, "avatar": testEventModel.Avatar, "is_admin": testEventModel.IsAdmin, diff --git a/tests/db_test.go b/tests/db_test.go index b98ef16ff..94bdc5548 100644 --- a/tests/db_test.go +++ b/tests/db_test.go @@ -59,9 +59,9 @@ func (s *DBTestSuite) TestInsert_First_Get() { s.NoError(err) s.True(product.ID > 0) s.Equal("model", product.Name) - s.Equal(&now, product.CreatedAt) - s.Equal(&now, product.UpdatedAt) - s.Nil(product.DeletedAt) + s.Equal(now, product.CreatedAt) + s.Equal(now, product.UpdatedAt) + s.False(product.DeletedAt.Valid) }) s.Run("multiple structs", func() { @@ -103,9 +103,9 @@ func (s *DBTestSuite) TestInsert_First_Get() { err = query.DB().Table("products").Where("name", "map").Where("deleted_at", nil).First(&product) s.NoError(err) s.Equal("map", product.Name) - s.Equal(&now, product.CreatedAt) - s.Equal(&now, product.UpdatedAt) - s.Nil(product.DeletedAt) + s.Equal(now, product.CreatedAt) + s.Equal(now, product.UpdatedAt) + s.False(product.DeletedAt.Valid) }) s.Run("multiple map", func() { diff --git a/tests/query.go b/tests/query.go index 0b129935b..89fac6820 100644 --- a/tests/query.go +++ b/tests/query.go @@ -124,16 +124,16 @@ func NewTestQueryBuilder() *TestQueryBuilder { } func (r *TestQueryBuilder) All(prefix string, singular bool) map[string]*TestQuery { - postgresTestQuery := r.Postgres(prefix, singular) - mysqlTestQuery := r.Mysql(prefix, singular) - sqlserverTestQuery := r.Sqlserver(prefix, singular) + // postgresTestQuery := r.Postgres(prefix, singular) + // mysqlTestQuery := r.Mysql(prefix, singular) + // sqlserverTestQuery := r.Sqlserver(prefix, singular) sqliteTestQuery := r.Sqlite(prefix, singular) return map[string]*TestQuery{ - postgresTestQuery.Driver().Config().Driver: postgresTestQuery, - mysqlTestQuery.Driver().Config().Driver: mysqlTestQuery, - sqlserverTestQuery.Driver().Config().Driver: sqlserverTestQuery, - sqliteTestQuery.Driver().Config().Driver: sqliteTestQuery, + // postgresTestQuery.Driver().Config().Driver: postgresTestQuery, + // mysqlTestQuery.Driver().Config().Driver: mysqlTestQuery, + // sqlserverTestQuery.Driver().Config().Driver: sqlserverTestQuery, + sqliteTestQuery.Driver().Config().Driver: sqliteTestQuery, } } From 64d4c92ad5269a0cf0433f4bd4ad8c24f8c6da51 Mon Sep 17 00:00:00 2001 From: Bowen Date: Wed, 19 Feb 2025 22:03:35 +0800 Subject: [PATCH 12/15] fix test --- database/db/query.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/database/db/query.go b/database/db/query.go index ffb4a2da1..8919ea7c8 100644 --- a/database/db/query.go +++ b/database/db/query.go @@ -137,7 +137,7 @@ func (r *Query) buildSelect() (sql string, args []any, err error) { if !str.Of(query).Trim().Contains(" ", "?") { if len(where.args) > 1 { builder = builder.Where(sq.Eq{query: where.args}) - } else { + } else if len(where.args) == 1 { builder = builder.Where(sq.Eq{query: where.args[0]}) } continue From 297d8bb78cd95b64a541a12196ebb878e3c0583f Mon Sep 17 00:00:00 2001 From: Bowen Date: Wed, 19 Feb 2025 22:10:07 +0800 Subject: [PATCH 13/15] fix test --- tests/db_test.go | 44 ++++++++++++++++++++++---------------------- tests/query.go | 14 +++++++------- 2 files changed, 29 insertions(+), 29 deletions(-) diff --git a/tests/db_test.go b/tests/db_test.go index 94bdc5548..392d52361 100644 --- a/tests/db_test.go +++ b/tests/db_test.go @@ -42,7 +42,7 @@ func (s *DBTestSuite) TestInsert_First_Get() { s.Run(driver, func() { s.Run("single struct", func() { result, err := query.DB().Table("products").Insert(Product{ - Name: "model", + Name: "single struct", Model: Model{ Timestamps: Timestamps{ CreatedAt: now, @@ -55,7 +55,7 @@ func (s *DBTestSuite) TestInsert_First_Get() { s.Equal(int64(1), result.RowsAffected) var product Product - err = query.DB().Table("products").Where("name", "model").Where("deleted_at", nil).First(&product) + err = query.DB().Table("products").Where("name", "single struct").Where("deleted_at", nil).First(&product) s.NoError(err) s.True(product.ID > 0) s.Equal("model", product.Name) @@ -67,7 +67,7 @@ func (s *DBTestSuite) TestInsert_First_Get() { s.Run("multiple structs", func() { result, err := query.DB().Table("products").Insert([]Product{ { - Name: "model1", + Name: "multiple structs1", Model: Model{ Timestamps: Timestamps{ CreatedAt: now, @@ -76,23 +76,23 @@ func (s *DBTestSuite) TestInsert_First_Get() { }, }, { - Name: "model2", + Name: "multiple structs2", }, }) s.NoError(err) s.Equal(int64(2), result.RowsAffected) var products []Product - err = query.DB().Table("products").Where("name", []string{"model1", "model2"}).Where("deleted_at", nil).Get(&products) + err = query.DB().Table("products").Where("name", []string{"multiple structs1", "multiple structs2"}).Where("deleted_at", nil).Get(&products) s.NoError(err) s.Equal(2, len(products)) - s.Equal("model1", products[0].Name) - s.Equal("model2", products[1].Name) + s.Equal("multiple structs1", products[0].Name) + s.Equal("multiple structs2", products[1].Name) }) s.Run("single map", func() { result, err := query.DB().Table("products").Insert(map[string]any{ - "name": "map", + "name": "single map", "created_at": now, "updated_at": &now, }) @@ -100,9 +100,9 @@ func (s *DBTestSuite) TestInsert_First_Get() { s.Equal(int64(1), result.RowsAffected) var product Product - err = query.DB().Table("products").Where("name", "map").Where("deleted_at", nil).First(&product) + err = query.DB().Table("products").Where("name", "single map").Where("deleted_at", nil).First(&product) s.NoError(err) - s.Equal("map", product.Name) + s.Equal("single map", product.Name) s.Equal(now, product.CreatedAt) s.Equal(now, product.UpdatedAt) s.False(product.DeletedAt.Valid) @@ -111,23 +111,23 @@ func (s *DBTestSuite) TestInsert_First_Get() { s.Run("multiple map", func() { result, err := query.DB().Table("products").Insert([]map[string]any{ { - "name": "map1", + "name": "multiple map1", "created_at": now, "updated_at": &now, }, { - "name": "map2", + "name": "multiple map2", }, }) s.NoError(err) s.Equal(int64(2), result.RowsAffected) var products []Product - err = query.DB().Table("products").Where("name", []string{"map1", "map2"}).Where("deleted_at", nil).Get(&products) + err = query.DB().Table("products").Where("name", []string{"multiple map1", "multiple map2"}).Where("deleted_at", nil).Get(&products) s.NoError(err) s.Equal(2, len(products)) - s.Equal("map1", products[0].Name) - s.Equal("map2", products[1].Name) + s.Equal("multiple map1", products[0].Name) + s.Equal("multiple map2", products[1].Name) }) }) } @@ -138,7 +138,7 @@ func (s *DBTestSuite) TestWhere() { s.Run(driver, func() { now := carbon.NewDateTime(carbon.FromDateTime(2025, 1, 2, 3, 4, 5)) query.DB().Table("products").Insert(Product{ - Name: "model", + Name: "where model", Model: Model{ Timestamps: Timestamps{ CreatedAt: now, @@ -149,24 +149,24 @@ func (s *DBTestSuite) TestWhere() { s.Run("simple where condition", func() { var product Product - err := query.DB().Table("products").Where("name", "model").First(&product) + err := query.DB().Table("products").Where("name", "where model").First(&product) s.NoError(err) - s.Equal("model", product.Name) + s.Equal("where model", product.Name) }) s.Run("where with multiple arguments", func() { var products []Product - err := query.DB().Table("products").Where("name", []string{"model", "model1"}).Get(&products) + err := query.DB().Table("products").Where("name", []string{"where model", "where model1"}).Get(&products) s.NoError(err) s.Equal(1, len(products)) - s.Equal("model", products[0].Name) + s.Equal("where model", products[0].Name) }) s.Run("where with raw query", func() { var product Product - err := query.DB().Table("products").Where("name = ?", "model").First(&product) + err := query.DB().Table("products").Where("name = ?", "where model").First(&product) s.NoError(err) - s.Equal("model", product.Name) + s.Equal("where model", product.Name) }) }) } diff --git a/tests/query.go b/tests/query.go index 89fac6820..0b129935b 100644 --- a/tests/query.go +++ b/tests/query.go @@ -124,16 +124,16 @@ func NewTestQueryBuilder() *TestQueryBuilder { } func (r *TestQueryBuilder) All(prefix string, singular bool) map[string]*TestQuery { - // postgresTestQuery := r.Postgres(prefix, singular) - // mysqlTestQuery := r.Mysql(prefix, singular) - // sqlserverTestQuery := r.Sqlserver(prefix, singular) + postgresTestQuery := r.Postgres(prefix, singular) + mysqlTestQuery := r.Mysql(prefix, singular) + sqlserverTestQuery := r.Sqlserver(prefix, singular) sqliteTestQuery := r.Sqlite(prefix, singular) return map[string]*TestQuery{ - // postgresTestQuery.Driver().Config().Driver: postgresTestQuery, - // mysqlTestQuery.Driver().Config().Driver: mysqlTestQuery, - // sqlserverTestQuery.Driver().Config().Driver: sqlserverTestQuery, - sqliteTestQuery.Driver().Config().Driver: sqliteTestQuery, + postgresTestQuery.Driver().Config().Driver: postgresTestQuery, + mysqlTestQuery.Driver().Config().Driver: mysqlTestQuery, + sqlserverTestQuery.Driver().Config().Driver: sqlserverTestQuery, + sqliteTestQuery.Driver().Config().Driver: sqliteTestQuery, } } From ed2f9cbf4b2e33d36ea53187ac2064e846c7caab Mon Sep 17 00:00:00 2001 From: Bowen Date: Wed, 19 Feb 2025 22:55:08 +0800 Subject: [PATCH 14/15] fix test --- tests/db_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/db_test.go b/tests/db_test.go index 392d52361..5a43eb91c 100644 --- a/tests/db_test.go +++ b/tests/db_test.go @@ -58,7 +58,7 @@ func (s *DBTestSuite) TestInsert_First_Get() { err = query.DB().Table("products").Where("name", "single struct").Where("deleted_at", nil).First(&product) s.NoError(err) s.True(product.ID > 0) - s.Equal("model", product.Name) + s.Equal("single struct", product.Name) s.Equal(now, product.CreatedAt) s.Equal(now, product.UpdatedAt) s.False(product.DeletedAt.Valid) From 19a28242f204526f95852063f5e9eb6b121dd4d7 Mon Sep 17 00:00:00 2001 From: Bowen Date: Wed, 19 Feb 2025 23:04:22 +0800 Subject: [PATCH 15/15] add test --- database/db/query.go | 5 +++++ database/db/query_test.go | 6 ++++++ 2 files changed, 11 insertions(+) diff --git a/database/db/query.go b/database/db/query.go index 8919ea7c8..d15a7e6d7 100644 --- a/database/db/query.go +++ b/database/db/query.go @@ -55,6 +55,11 @@ func (r *Query) Insert(data any) (*db.Result, error) { if err != nil { return nil, err } + if len(mapData) == 0 { + return &db.Result{ + RowsAffected: 0, + }, nil + } sql, args, err := r.buildInsert(mapData) if err != nil { diff --git a/database/db/query_test.go b/database/db/query_test.go index b7293472c..54d71d5c9 100644 --- a/database/db/query_test.go +++ b/database/db/query_test.go @@ -52,6 +52,12 @@ func (s *QueryTestSuite) TestGet() { } func (s *QueryTestSuite) TestInsert() { + s.Run("empty", func() { + result, err := s.query.Insert(nil) + s.Nil(err) + s.Equal(int64(0), result.RowsAffected) + }) + s.Run("single struct", func() { user := TestUser{ ID: 1,