github.com/ecodeclub/eorm@v0.0.2-0.20231001112437-dae71da914d0/update_test.go (about)

     1  // Copyright 2021 ecodeclub
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  // http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package eorm
    16  
    17  import (
    18  	"context"
    19  	"database/sql"
    20  	"errors"
    21  	"fmt"
    22  	"testing"
    23  
    24  	"github.com/ecodeclub/eorm/internal/datasource/single"
    25  
    26  	"github.com/DATA-DOG/go-sqlmock"
    27  	"github.com/ecodeclub/eorm/internal/errs"
    28  	"github.com/stretchr/testify/require"
    29  
    30  	"github.com/stretchr/testify/assert"
    31  )
    32  
    33  func TestUpdater_Set(t *testing.T) {
    34  	tm := &TestModel{
    35  		Id:        12,
    36  		FirstName: "Tom",
    37  		Age:       18,
    38  		LastName:  &sql.NullString{String: "Jerry", Valid: true},
    39  	}
    40  	db := memoryDB()
    41  	testCases := []CommonTestCase{
    42  		{
    43  			name:     "no set and update",
    44  			builder:  NewUpdater[TestModel](db),
    45  			wantSql:  "UPDATE `test_model` SET `id`=?,`first_name`=?,`age`=?,`last_name`=?;",
    46  			wantArgs: []interface{}{int64(0), "", int8(0), (*sql.NullString)(nil)},
    47  		},
    48  		{
    49  			name: "no set",
    50  			builder: NewUpdater[TestModel](db).Update(&TestModel{
    51  				Id:        12,
    52  				FirstName: "Tom",
    53  				Age:       18,
    54  			}),
    55  			wantSql:  "UPDATE `test_model` SET `id`=?,`first_name`=?,`age`=?,`last_name`=?;",
    56  			wantArgs: []interface{}{int64(12), "Tom", int8(18), (*sql.NullString)(nil)},
    57  		},
    58  		{
    59  			name:     "set columns",
    60  			builder:  NewUpdater[TestModel](db).Update(tm).Set(Columns("FirstName", "Age")),
    61  			wantSql:  "UPDATE `test_model` SET `first_name`=?,`age`=?;",
    62  			wantArgs: []interface{}{"Tom", int8(18)},
    63  		},
    64  		{
    65  			name:    "set invalid columns",
    66  			builder: NewUpdater[TestModel](db).Update(tm).Set(Columns("FirstNameInvalid", "Age")),
    67  			wantErr: errs.NewInvalidFieldError("FirstNameInvalid"),
    68  		},
    69  		{
    70  			name:     "set c2",
    71  			builder:  NewUpdater[TestModel](db).Update(tm).Set(C("FirstName"), C("Age")),
    72  			wantSql:  "UPDATE `test_model` SET `first_name`=?,`age`=?;",
    73  			wantArgs: []interface{}{"Tom", int8(18)},
    74  		},
    75  		{
    76  			name:    "set invalid c2",
    77  			builder: NewUpdater[TestModel](db).Update(tm).Set(C("FirstNameInvalid"), C("Age")),
    78  			wantErr: errs.NewInvalidFieldError("FirstNameInvalid"),
    79  		},
    80  		{
    81  			name:     "set assignment",
    82  			builder:  NewUpdater[TestModel](db).Update(tm).Set(C("FirstName"), Assign("Age", 30)),
    83  			wantSql:  "UPDATE `test_model` SET `first_name`=?,`age`=?;",
    84  			wantArgs: []interface{}{"Tom", 30},
    85  		},
    86  		{
    87  			name:    "set invalid assignment",
    88  			builder: NewUpdater[TestModel](db).Update(tm).Set(C("FirstName"), Assign("InvalidAge", 30)),
    89  			wantErr: errs.NewInvalidFieldError("InvalidAge"),
    90  		},
    91  		{
    92  			name:     "set age+1",
    93  			builder:  NewUpdater[TestModel](db).Update(tm).Set(C("FirstName"), Assign("Age", C("Age").Add(1))),
    94  			wantSql:  "UPDATE `test_model` SET `first_name`=?,`age`=(`age`+?);",
    95  			wantArgs: []interface{}{"Tom", 1},
    96  		},
    97  		{
    98  			name:     "set age=id+1",
    99  			builder:  NewUpdater[TestModel](db).Update(tm).Set(C("FirstName"), Assign("Age", C("Id").Add(10))),
   100  			wantSql:  "UPDATE `test_model` SET `first_name`=?,`age`=(`id`+?);",
   101  			wantArgs: []interface{}{"Tom", 10},
   102  		},
   103  		{
   104  			name:     "set age=id+(age*100)+10",
   105  			builder:  NewUpdater[TestModel](db).Update(tm).Set(C("FirstName"), Assign("Age", C("Id").Add(C("Age").Multi(100)).Add(10))),
   106  			wantSql:  "UPDATE `test_model` SET `first_name`=?,`age`=((`id`+(`age`*?))+?);",
   107  			wantArgs: []interface{}{"Tom", 100, 10},
   108  		},
   109  		{
   110  			name:     "set age=(id+(age*100))*110",
   111  			builder:  NewUpdater[TestModel](db).Update(tm).Set(C("FirstName"), Assign("Age", C("Id").Add(C("Age").Multi(100)).Multi(110))),
   112  			wantSql:  "UPDATE `test_model` SET `first_name`=?,`age`=((`id`+(`age`*?))*?);",
   113  			wantArgs: []interface{}{"Tom", 100, 110},
   114  		},
   115  		{
   116  			name:     "not nil columns",
   117  			builder:  NewUpdater[TestModel](db).Update(&TestModel{Id: 13}).SkipNilValue(),
   118  			wantSql:  "UPDATE `test_model` SET `id`=?,`first_name`=?,`age`=?;",
   119  			wantArgs: []interface{}{int64(13), "", int8(0)},
   120  		},
   121  		{
   122  			name:     "not zero columns",
   123  			builder:  NewUpdater[TestModel](db).Update(&TestModel{Id: 13}).SkipZeroValue(),
   124  			wantSql:  "UPDATE `test_model` SET `id`=?;",
   125  			wantArgs: []interface{}{int64(13)},
   126  		},
   127  	}
   128  
   129  	for _, tc := range testCases {
   130  		c := tc
   131  		t.Run(c.name, func(t *testing.T) {
   132  			query, err := tc.builder.Build()
   133  			assert.Equal(t, err, c.wantErr)
   134  			if err != nil {
   135  				return
   136  			}
   137  			assert.Equal(t, c.wantSql, query.SQL)
   138  			assert.Equal(t, c.wantArgs, query.Args)
   139  		})
   140  	}
   141  }
   142  
   143  func TestUpdater_SetForCombination(t *testing.T) {
   144  	u := &User{
   145  		Id: 12,
   146  		Person: Person{
   147  			FirstName: "Tom",
   148  			Age:       18,
   149  			LastName:  &sql.NullString{String: "Jerry", Valid: true},
   150  		},
   151  	}
   152  	db := memoryDB()
   153  	testCases := []CommonTestCase{
   154  		{
   155  			name:     "no set",
   156  			builder:  NewUpdater[User](db).Update(u),
   157  			wantSql:  "UPDATE `user` SET `id`=?,`first_name`=?,`age`=?,`last_name`=?;",
   158  			wantArgs: []interface{}{int64(12), "Tom", int8(18), &sql.NullString{String: "Jerry", Valid: true}},
   159  		},
   160  		{
   161  			name:     "set columns",
   162  			builder:  NewUpdater[User](db).Update(u).Set(Columns("FirstName", "Age")),
   163  			wantSql:  "UPDATE `user` SET `first_name`=?,`age`=?;",
   164  			wantArgs: []interface{}{"Tom", int8(18)},
   165  		},
   166  		{
   167  			name:    "set invalid columns",
   168  			builder: NewUpdater[User](db).Update(u).Set(Columns("FirstNameInvalid", "Age")),
   169  			wantErr: errs.NewInvalidFieldError("FirstNameInvalid"),
   170  		},
   171  		{
   172  			name:     "set c2",
   173  			builder:  NewUpdater[User](db).Update(u).Set(C("FirstName"), C("Age")),
   174  			wantSql:  "UPDATE `user` SET `first_name`=?,`age`=?;",
   175  			wantArgs: []interface{}{"Tom", int8(18)},
   176  		},
   177  
   178  		{
   179  			name:    "set invalid c2",
   180  			builder: NewUpdater[User](db).Update(u).Set(C("FirstNameInvalid"), C("Age")),
   181  			wantErr: errs.NewInvalidFieldError("FirstNameInvalid"),
   182  		},
   183  
   184  		{
   185  			name:     "set assignment",
   186  			builder:  NewUpdater[User](db).Update(u).Set(C("FirstName"), Assign("Age", 30)),
   187  			wantSql:  "UPDATE `user` SET `first_name`=?,`age`=?;",
   188  			wantArgs: []interface{}{"Tom", 30},
   189  		},
   190  		{
   191  			name:    "set invalid assignment",
   192  			builder: NewUpdater[User](db).Update(u).Set(C("FirstName"), Assign("InvalidAge", 30)),
   193  			wantErr: errs.NewInvalidFieldError("InvalidAge"),
   194  		},
   195  		{
   196  			name:     "set age+1",
   197  			builder:  NewUpdater[User](db).Update(u).Set(C("FirstName"), Assign("Age", C("Age").Add(1))),
   198  			wantSql:  "UPDATE `user` SET `first_name`=?,`age`=(`age`+?);",
   199  			wantArgs: []interface{}{"Tom", 1},
   200  		},
   201  		{
   202  			name:     "set age=id+1",
   203  			builder:  NewUpdater[User](db).Update(u).Set(C("FirstName"), Assign("Age", C("Id").Add(10))),
   204  			wantSql:  "UPDATE `user` SET `first_name`=?,`age`=(`id`+?);",
   205  			wantArgs: []interface{}{"Tom", 10},
   206  		},
   207  		{
   208  			name:     "set age=id+(age*100)",
   209  			builder:  NewUpdater[User](db).Update(u).Set(C("FirstName"), Assign("Age", C("Id").Add(C("Age").Multi(100)))),
   210  			wantSql:  "UPDATE `user` SET `first_name`=?,`age`=(`id`+(`age`*?));",
   211  			wantArgs: []interface{}{"Tom", 100},
   212  		},
   213  		{
   214  			name:     "set age=(id+(age*100))*110",
   215  			builder:  NewUpdater[User](db).Update(u).Set(C("FirstName"), Assign("Age", C("Id").Add(C("Age").Multi(100)).Multi(110))),
   216  			wantSql:  "UPDATE `user` SET `first_name`=?,`age`=((`id`+(`age`*?))*?);",
   217  			wantArgs: []interface{}{"Tom", 100, 110},
   218  		},
   219  		{
   220  			name:     "not nil columns",
   221  			builder:  NewUpdater[User](db).Update(&User{Id: 13}).SkipNilValue(),
   222  			wantSql:  "UPDATE `user` SET `id`=?,`first_name`=?,`age`=?;",
   223  			wantArgs: []interface{}{int64(13), "", int8(0)},
   224  		},
   225  		{
   226  			name:     "not zero columns",
   227  			builder:  NewUpdater[User](db).Update(&User{Id: 13}).SkipZeroValue(),
   228  			wantSql:  "UPDATE `user` SET `id`=?;",
   229  			wantArgs: []interface{}{int64(13)},
   230  		},
   231  	}
   232  
   233  	for _, tc := range testCases {
   234  		c := tc
   235  		t.Run(c.name, func(t *testing.T) {
   236  			query, err := tc.builder.Build()
   237  			assert.Equal(t, err, c.wantErr)
   238  			if err != nil {
   239  				return
   240  			}
   241  			assert.Equal(t, c.wantSql, query.SQL)
   242  			assert.Equal(t, c.wantArgs, query.Args)
   243  		})
   244  	}
   245  }
   246  
   247  func TestUpdater_Exec(t *testing.T) {
   248  	tm := &TestModel{
   249  		Id:        12,
   250  		FirstName: "Tom",
   251  		Age:       18,
   252  		LastName:  &sql.NullString{String: "Jerry", Valid: true},
   253  	}
   254  	testCases := []struct {
   255  		name      string
   256  		u         *Updater[TestModel]
   257  		update    func(*DB, *testing.T) Result
   258  		wantErr   error
   259  		mockOrder func(mock sqlmock.Sqlmock)
   260  		wantVal   sql.Result
   261  	}{
   262  		{
   263  			name: "update err",
   264  			update: func(db *DB, t *testing.T) Result {
   265  				updater := NewUpdater[TestModel](db).Set(Assign("Age", 12))
   266  				result := updater.Exec(context.Background())
   267  				return result
   268  			},
   269  			mockOrder: func(mock sqlmock.Sqlmock) {
   270  				mock.ExpectExec("UPDATE `test_model` SET `age`=").
   271  					WithArgs(int64(12)).
   272  					WillReturnError(errors.New("no such table: test_model"))
   273  			},
   274  			wantErr: errors.New("no such table: test_model"),
   275  		},
   276  		{
   277  			name: "specify columns",
   278  			update: func(db *DB, t *testing.T) Result {
   279  				updater := NewUpdater[TestModel](db).Update(tm).Set(Columns("FirstName"))
   280  				result := updater.Exec(context.Background())
   281  				return result
   282  			},
   283  			mockOrder: func(mock sqlmock.Sqlmock) {
   284  				mock.ExpectExec("UPDATE `test_model` SET `first_name`=").
   285  					WithArgs("Tom").WillReturnResult(sqlmock.NewResult(100, 1))
   286  			},
   287  			wantVal: sqlmock.NewResult(100, 1),
   288  		},
   289  	}
   290  
   291  	for _, tc := range testCases {
   292  		t.Run(tc.name, func(t *testing.T) {
   293  			mockDB, mock, err := sqlmock.New()
   294  			if err != nil {
   295  				t.Fatal(err)
   296  			}
   297  			db, err := OpenDS("mysql", single.NewDB(mockDB))
   298  			defer func(db *DB) { _ = db.Close() }(db)
   299  			if err != nil {
   300  				t.Fatal(err)
   301  			}
   302  			tc.mockOrder(mock)
   303  
   304  			res := tc.update(db, t)
   305  			if res.Err() != nil {
   306  				assert.Equal(t, tc.wantErr, res.Err())
   307  				return
   308  			}
   309  			assert.Nil(t, tc.wantErr)
   310  			rowsAffectedExpect, err := tc.wantVal.RowsAffected()
   311  			require.NoError(t, err)
   312  			rowsAffected, err := res.RowsAffected()
   313  			require.NoError(t, err)
   314  
   315  			lastInsertIdExpected, err := tc.wantVal.LastInsertId()
   316  			require.NoError(t, err)
   317  			lastInsertId, err := res.LastInsertId()
   318  			require.NoError(t, err)
   319  			assert.Equal(t, lastInsertIdExpected, lastInsertId)
   320  			assert.Equal(t, rowsAffectedExpect, rowsAffected)
   321  
   322  			if err = mock.ExpectationsWereMet(); err != nil {
   323  				t.Error(err)
   324  			}
   325  		})
   326  	}
   327  }
   328  
   329  func ExampleUpdater_SkipNilValue() {
   330  	db := memoryDB()
   331  	query, _ := NewUpdater[TestModel](db).Update(&TestModel{Id: 13}).SkipNilValue().Build()
   332  	fmt.Println(query.String())
   333  	// Output:
   334  	// SQL: UPDATE `test_model` SET `id`=?,`first_name`=?,`age`=?;
   335  	// Args: []interface {}{13, "", 0}
   336  }
   337  
   338  func ExampleUpdater_SkipZeroValue() {
   339  	db := memoryDB()
   340  	query, _ := NewUpdater[TestModel](db).Update(&TestModel{Id: 13}).SkipZeroValue().Build()
   341  	fmt.Println(query.String())
   342  	// Output:
   343  	// SQL: UPDATE `test_model` SET `id`=?;
   344  	// Args: []interface {}{13}
   345  }
   346  
   347  type Person struct {
   348  	FirstName string
   349  	Age       int8
   350  	LastName  *sql.NullString
   351  }
   352  type User struct {
   353  	Id int64 `eorm:"auto_increment,primary_key"`
   354  	Person
   355  }