From 08ce2a3fd264257423b73488f4824e4c1bb0ecfc Mon Sep 17 00:00:00 2001 From: iman tung Date: Mon, 19 Oct 2020 10:42:37 +0700 Subject: [PATCH] support mysql dialect for entity generation --- .../repository}/song_repo.go | 23 +- .../repository}/song_repo_test.go | 39 ++-- internal/app/data_access/mysqldb/types.go | 11 +- .../app/domain/mymusic/service/song_svc.go | 13 +- .../domain/mymusic/service/song_svc_test.go | 45 ++-- internal/generated/mysqldb_repo/song_repo.go | 221 ++++++++++++++++++ .../mysqldb_repo_mock}/song_repo.go | 6 +- internal/generated/typical/ctor_annotated.go | 14 +- pkg/typrepo/entity_annotation.go | 4 +- pkg/typrepo/mysql_template.go | 190 +++++++++++++++ 10 files changed, 491 insertions(+), 75 deletions(-) rename {internal/app/data_access/mysqldb => EXPERIMENT/repository}/song_repo.go (81%) rename {internal/app/data_access/mysqldb => EXPERIMENT/repository}/song_repo_test.go (93%) create mode 100755 internal/generated/mysqldb_repo/song_repo.go rename internal/{app/data_access/mysqldb_mock => generated/mysqldb_repo_mock}/song_repo.go (95%) create mode 100644 pkg/typrepo/mysql_template.go diff --git a/internal/app/data_access/mysqldb/song_repo.go b/EXPERIMENT/repository/song_repo.go similarity index 81% rename from internal/app/data_access/mysqldb/song_repo.go rename to EXPERIMENT/repository/song_repo.go index 92d89de1..c3b318f9 100644 --- a/internal/app/data_access/mysqldb/song_repo.go +++ b/EXPERIMENT/repository/song_repo.go @@ -1,4 +1,4 @@ -package mysqldb +package repository import ( "context" @@ -6,6 +6,7 @@ import ( "time" sq "github.com/Masterminds/squirrel" + "github.com/typical-go/typical-rest-server/internal/app/data_access/mysqldb" "github.com/typical-go/typical-rest-server/pkg/dbkit" "github.com/typical-go/typical-rest-server/pkg/dbtxn" "go.uber.org/dig" @@ -34,11 +35,11 @@ type ( // SongRepo to get song data from database // @mock SongRepo interface { - Find(context.Context, ...dbkit.SelectOption) ([]*Song, error) - Create(context.Context, *Song) (int64, error) + Find(context.Context, ...dbkit.SelectOption) ([]*mysqldb.Song, error) + Create(context.Context, *mysqldb.Song) (int64, error) Delete(context.Context, dbkit.DeleteOption) (int64, error) - Update(context.Context, *Song, dbkit.UpdateOption) (int64, error) - Patch(context.Context, *Song, dbkit.UpdateOption) (int64, error) + Update(context.Context, *mysqldb.Song, dbkit.UpdateOption) (int64, error) + Patch(context.Context, *mysqldb.Song, dbkit.UpdateOption) (int64, error) } // SongRepoImpl is implementation song repository SongRepoImpl struct { @@ -54,7 +55,7 @@ func NewSongRepo(impl SongRepoImpl) SongRepo { } // Find song -func (r *SongRepoImpl) Find(ctx context.Context, opts ...dbkit.SelectOption) (list []*Song, err error) { +func (r *SongRepoImpl) Find(ctx context.Context, opts ...dbkit.SelectOption) (list []*mysqldb.Song, err error) { builder := sq. Select( SongTable.ID, @@ -77,9 +78,9 @@ func (r *SongRepoImpl) Find(ctx context.Context, opts ...dbkit.SelectOption) (li return } - list = make([]*Song, 0) + list = make([]*mysqldb.Song, 0) for rows.Next() { - song := new(Song) + song := new(mysqldb.Song) if err = rows.Scan( &song.ID, &song.Title, @@ -95,7 +96,7 @@ func (r *SongRepoImpl) Find(ctx context.Context, opts ...dbkit.SelectOption) (li } // Create song -func (r *SongRepoImpl) Create(ctx context.Context, song *Song) (int64, error) { +func (r *SongRepoImpl) Create(ctx context.Context, song *mysqldb.Song) (int64, error) { txn, err := dbtxn.Use(ctx, r.DB) if err != nil { return -1, err @@ -151,7 +152,7 @@ func (r *SongRepoImpl) Delete(ctx context.Context, opt dbkit.DeleteOption) (int6 } // Update song -func (r *SongRepoImpl) Update(ctx context.Context, song *Song, opt dbkit.UpdateOption) (int64, error) { +func (r *SongRepoImpl) Update(ctx context.Context, song *mysqldb.Song, opt dbkit.UpdateOption) (int64, error) { txn, err := dbtxn.Use(ctx, r.DB) if err != nil { return -1, err @@ -178,7 +179,7 @@ func (r *SongRepoImpl) Update(ctx context.Context, song *Song, opt dbkit.UpdateO } // Patch song to update field of song if available -func (r *SongRepoImpl) Patch(ctx context.Context, song *Song, opt dbkit.UpdateOption) (int64, error) { +func (r *SongRepoImpl) Patch(ctx context.Context, song *mysqldb.Song, opt dbkit.UpdateOption) (int64, error) { txn, err := dbtxn.Use(ctx, r.DB) if err != nil { return -1, err diff --git a/internal/app/data_access/mysqldb/song_repo_test.go b/EXPERIMENT/repository/song_repo_test.go similarity index 93% rename from internal/app/data_access/mysqldb/song_repo_test.go rename to EXPERIMENT/repository/song_repo_test.go index 117ef270..b99868ef 100644 --- a/internal/app/data_access/mysqldb/song_repo_test.go +++ b/EXPERIMENT/repository/song_repo_test.go @@ -1,4 +1,4 @@ -package mysqldb_test +package repository_test import ( "context" @@ -11,26 +11,27 @@ import ( sqlmock "github.com/DATA-DOG/go-sqlmock" sq "github.com/Masterminds/squirrel" "github.com/stretchr/testify/require" + "github.com/typical-go/typical-rest-server/EXPERIMENT/repository" "github.com/typical-go/typical-rest-server/internal/app/data_access/mysqldb" "github.com/typical-go/typical-rest-server/pkg/dbkit" "github.com/typical-go/typical-rest-server/pkg/dbtxn" ) -type bookRepoFn func(sqlmock.Sqlmock) +type songRepoFn func(sqlmock.Sqlmock) -func createSongRepo(fn bookRepoFn) (mysqldb.SongRepo, *sql.DB) { +func createSongRepo(fn songRepoFn) (repository.SongRepo, *sql.DB) { db, mock, _ := sqlmock.New() if fn != nil { fn(mock) } - return mysqldb.NewSongRepo(mysqldb.SongRepoImpl{DB: db}), db + return repository.NewSongRepo(repository.SongRepoImpl{DB: db}), db } func TestSongRepoImpl_Create(t *testing.T) { testcases := []struct { TestName string Song *mysqldb.Song - SongRepoFn bookRepoFn + SongRepoFn songRepoFn Expected int64 ExpectedErr string }{ @@ -92,7 +93,7 @@ func TestSongRepoImpl_Update(t *testing.T) { testcases := []struct { TestName string Song *mysqldb.Song - SongRepoFn bookRepoFn + SongRepoFn songRepoFn Opt dbkit.UpdateOption ExpectedErr string Expected int64 @@ -100,7 +101,7 @@ func TestSongRepoImpl_Update(t *testing.T) { { TestName: "update error", Song: &mysqldb.Song{Title: "new-title", Artist: "new-artist"}, - Opt: dbkit.Equal(mysqldb.SongTable.ID, 888), + Opt: dbkit.Equal(repository.SongTable.ID, 888), ExpectedErr: "dbtxn: begin-error", Expected: -1, SongRepoFn: func(mock sqlmock.Sqlmock) { @@ -110,7 +111,7 @@ func TestSongRepoImpl_Update(t *testing.T) { { TestName: "update error", Song: &mysqldb.Song{Title: "new-title", Artist: "new-artist"}, - Opt: dbkit.Equal(mysqldb.SongTable.ID, 888), + Opt: dbkit.Equal(repository.SongTable.ID, 888), SongRepoFn: func(mock sqlmock.Sqlmock) { mock.ExpectBegin() mock.ExpectExec(regexp.QuoteMeta(`UPDATE songs SET title = ?, artist = ?, updated_at = ? WHERE id = ?`)). @@ -137,7 +138,7 @@ func TestSongRepoImpl_Update(t *testing.T) { { TestName: "success", Song: &mysqldb.Song{Title: "new-title", Artist: "new-artist"}, - Opt: dbkit.Equal(mysqldb.SongTable.ID, 888), + Opt: dbkit.Equal(repository.SongTable.ID, 888), SongRepoFn: func(mock sqlmock.Sqlmock) { mock.ExpectBegin() mock.ExpectExec(regexp.QuoteMeta(`UPDATE songs SET title = ?, artist = ?, updated_at = ? WHERE id = ?`)). @@ -149,7 +150,7 @@ func TestSongRepoImpl_Update(t *testing.T) { { TestName: "success empty artist", Song: &mysqldb.Song{Title: "new-title"}, - Opt: dbkit.Equal(mysqldb.SongTable.ID, 888), + Opt: dbkit.Equal(repository.SongTable.ID, 888), SongRepoFn: func(mock sqlmock.Sqlmock) { mock.ExpectBegin() mock.ExpectExec(regexp.QuoteMeta(`UPDATE songs SET title = ?, artist = ?, updated_at = ? WHERE id = ?`)). @@ -161,7 +162,7 @@ func TestSongRepoImpl_Update(t *testing.T) { { TestName: "success empty title", Song: &mysqldb.Song{Artist: "new-artist"}, - Opt: dbkit.Equal(mysqldb.SongTable.ID, 888), + Opt: dbkit.Equal(repository.SongTable.ID, 888), SongRepoFn: func(mock sqlmock.Sqlmock) { mock.ExpectBegin() mock.ExpectExec(regexp.QuoteMeta(`UPDATE songs SET title = ?, artist = ?, updated_at = ? WHERE id = ?`)). @@ -194,7 +195,7 @@ func TestSongRepoImpl_Patch(t *testing.T) { testcases := []struct { TestName string Song *mysqldb.Song - SongRepoFn bookRepoFn + SongRepoFn songRepoFn Opt dbkit.UpdateOption ExpectedErr string Expected int64 @@ -202,7 +203,7 @@ func TestSongRepoImpl_Patch(t *testing.T) { { TestName: "begin error", Song: &mysqldb.Song{Title: "new-title", Artist: "new-artist"}, - Opt: dbkit.Equal(mysqldb.SongTable.ID, 888), + Opt: dbkit.Equal(repository.SongTable.ID, 888), SongRepoFn: func(mock sqlmock.Sqlmock) { mock.ExpectBegin().WillReturnError(errors.New("begin-error")) }, @@ -212,7 +213,7 @@ func TestSongRepoImpl_Patch(t *testing.T) { { TestName: "update error", Song: &mysqldb.Song{Title: "new-title", Artist: "new-artist"}, - Opt: dbkit.Equal(mysqldb.SongTable.ID, 888), + Opt: dbkit.Equal(repository.SongTable.ID, 888), SongRepoFn: func(mock sqlmock.Sqlmock) { mock.ExpectBegin() mock.ExpectExec(regexp.QuoteMeta(`UPDATE songs SET title = ?, artist = ?, updated_at = ? WHERE id = ?`)). @@ -240,7 +241,7 @@ func TestSongRepoImpl_Patch(t *testing.T) { { TestName: "success", Song: &mysqldb.Song{Title: "new-title", Artist: "new-artist"}, - Opt: dbkit.Equal(mysqldb.SongTable.ID, 888), + Opt: dbkit.Equal(repository.SongTable.ID, 888), SongRepoFn: func(mock sqlmock.Sqlmock) { mock.ExpectBegin() mock.ExpectExec(regexp.QuoteMeta(`UPDATE songs SET title = ?, artist = ?, updated_at = ? WHERE id = ?`)). @@ -252,7 +253,7 @@ func TestSongRepoImpl_Patch(t *testing.T) { { TestName: "success empty artist", Song: &mysqldb.Song{Title: "new-title"}, - Opt: dbkit.Equal(mysqldb.SongTable.ID, 888), + Opt: dbkit.Equal(repository.SongTable.ID, 888), SongRepoFn: func(mock sqlmock.Sqlmock) { mock.ExpectBegin() mock.ExpectExec(regexp.QuoteMeta(`UPDATE songs SET title = ?, updated_at = ? WHERE id = ?`)). @@ -264,7 +265,7 @@ func TestSongRepoImpl_Patch(t *testing.T) { { TestName: "success empty title", Song: &mysqldb.Song{Artist: "new-artist"}, - Opt: dbkit.Equal(mysqldb.SongTable.ID, 888), + Opt: dbkit.Equal(repository.SongTable.ID, 888), SongRepoFn: func(mock sqlmock.Sqlmock) { mock.ExpectBegin() mock.ExpectExec(regexp.QuoteMeta(`UPDATE songs SET artist = ?, updated_at = ? WHERE id = ?`)). @@ -302,7 +303,7 @@ func TestSongRepoImpl_Retrieve(t *testing.T) { Opts []dbkit.SelectOption Expected []*mysqldb.Song ExpectedErr string - SongRepoFn bookRepoFn + SongRepoFn songRepoFn }{ { TestName: "sql error", @@ -368,7 +369,7 @@ func TestSongRepoImpl_Delete(t *testing.T) { testcases := []struct { TestName string Opt dbkit.DeleteOption - SongRepoFn bookRepoFn + SongRepoFn songRepoFn ExpectedErr string Expected int64 }{ diff --git a/internal/app/data_access/mysqldb/types.go b/internal/app/data_access/mysqldb/types.go index b790aca2..bd5d8b8c 100644 --- a/internal/app/data_access/mysqldb/types.go +++ b/internal/app/data_access/mysqldb/types.go @@ -4,11 +4,12 @@ import "time" type ( // Song entity + // @entity (table:"songs" dialect:"mysql" ctor_db:"mysql") Song struct { - ID int64 `json:"id"` - Title string `json:"title" validate:"required"` - Artist string `json:"artist" validate:"required"` - UpdatedAt time.Time `json:"update_at"` - CreatedAt time.Time `json:"created_at"` + ID int64 `column:"id" option:"pk" json:"id"` + Title string `column:"title" json:"title" validate:"required"` + Artist string `column:"artist" json:"artist" validate:"required"` + UpdatedAt time.Time `column:"updated_at" option:"now" json:"update_at"` + CreatedAt time.Time `column:"created_at" option:"now,no_update" json:"created_at"` } ) diff --git a/internal/app/domain/mymusic/service/song_svc.go b/internal/app/domain/mymusic/service/song_svc.go index 53c9729a..4e46f615 100644 --- a/internal/app/domain/mymusic/service/song_svc.go +++ b/internal/app/domain/mymusic/service/song_svc.go @@ -6,6 +6,7 @@ import ( "strconv" "github.com/typical-go/typical-rest-server/internal/app/data_access/mysqldb" + "github.com/typical-go/typical-rest-server/internal/generated/mysqldb_repo" "github.com/typical-go/typical-rest-server/pkg/dbkit" "github.com/typical-go/typical-rest-server/pkg/typrest" "go.uber.org/dig" @@ -26,7 +27,7 @@ type ( // SongSvcImpl is implementation of SongSvc SongSvcImpl struct { dig.In - mysqldb.SongRepo + mysqldb_repo.SongRepo } ) @@ -60,7 +61,7 @@ func (b *SongSvcImpl) FindOne(ctx context.Context, paramID string) (*mysqldb.Son return nil, typrest.NewValidErr("paramID is missing") } - books, err := b.SongRepo.Find(ctx, dbkit.Equal(mysqldb.SongTable.ID, id)) + books, err := b.SongRepo.Find(ctx, dbkit.Equal(mysqldb_repo.SongTable.ID, id)) if err != nil { return nil, err } else if len(books) < 1 { @@ -70,7 +71,7 @@ func (b *SongSvcImpl) FindOne(ctx context.Context, paramID string) (*mysqldb.Son } func (b *SongSvcImpl) findOne(ctx context.Context, id int64) (*mysqldb.Song, error) { - books, err := b.SongRepo.Find(ctx, dbkit.Equal(mysqldb.SongTable.ID, id)) + books, err := b.SongRepo.Find(ctx, dbkit.Equal(mysqldb_repo.SongTable.ID, id)) if err != nil { return nil, err } else if len(books) < 1 { @@ -85,7 +86,7 @@ func (b *SongSvcImpl) Delete(ctx context.Context, paramID string) error { if id < 1 { return typrest.NewValidErr("paramID is missing") } - _, err := b.SongRepo.Delete(ctx, dbkit.Equal(mysqldb.SongTable.ID, id)) + _, err := b.SongRepo.Delete(ctx, dbkit.Equal(mysqldb_repo.SongTable.ID, id)) return err } @@ -99,7 +100,7 @@ func (b *SongSvcImpl) Update(ctx context.Context, paramID string, book *mysqldb. if err != nil { return nil, typrest.NewValidErr(err.Error()) } - affectedRow, err := b.SongRepo.Update(ctx, book, dbkit.Equal(mysqldb.SongTable.ID, id)) + affectedRow, err := b.SongRepo.Update(ctx, book, dbkit.Equal(mysqldb_repo.SongTable.ID, id)) if err != nil { return nil, err } @@ -115,7 +116,7 @@ func (b *SongSvcImpl) Patch(ctx context.Context, paramID string, book *mysqldb.S if id < 1 { return nil, typrest.NewValidErr("paramID is missing") } - affectedRow, err := b.SongRepo.Patch(ctx, book, dbkit.Equal(mysqldb.SongTable.ID, id)) + affectedRow, err := b.SongRepo.Patch(ctx, book, dbkit.Equal(mysqldb_repo.SongTable.ID, id)) if err != nil { return nil, err } diff --git a/internal/app/domain/mymusic/service/song_svc_test.go b/internal/app/domain/mymusic/service/song_svc_test.go index 9b81c187..060ff05f 100644 --- a/internal/app/domain/mymusic/service/song_svc_test.go +++ b/internal/app/domain/mymusic/service/song_svc_test.go @@ -8,16 +8,17 @@ import ( "github.com/golang/mock/gomock" "github.com/stretchr/testify/require" "github.com/typical-go/typical-rest-server/internal/app/data_access/mysqldb" - "github.com/typical-go/typical-rest-server/internal/app/data_access/mysqldb_mock" "github.com/typical-go/typical-rest-server/internal/app/domain/mymusic/service" + "github.com/typical-go/typical-rest-server/internal/generated/mysqldb_repo" + "github.com/typical-go/typical-rest-server/internal/generated/mysqldb_repo_mock" "github.com/typical-go/typical-rest-server/pkg/dbkit" ) -type songSvcFn func(mockRepo *mysqldb_mock.MockSongRepo) +type songSvcFn func(mockRepo *mysqldb_repo_mock.MockSongRepo) func createSongSvc(t *testing.T, fn songSvcFn) (*service.SongSvcImpl, *gomock.Controller) { mock := gomock.NewController(t) - mockRepo := mysqldb_mock.NewMockSongRepo(mock) + mockRepo := mysqldb_repo_mock.NewMockSongRepo(mock) if fn != nil { fn(mockRepo) } @@ -44,7 +45,7 @@ func TestSongSvc_Create(t *testing.T) { testName: "create error", song: &mysqldb.Song{Artist: "some-artist", Title: "some-title"}, expectedErr: "create-error", - songSvcFn: func(mockRepo *mysqldb_mock.MockSongRepo) { + songSvcFn: func(mockRepo *mysqldb_repo_mock.MockSongRepo) { mockRepo.EXPECT(). Create(gomock.Any(), &mysqldb.Song{Artist: "some-artist", Title: "some-title"}). Return(int64(-1), errors.New("create-error")) @@ -54,7 +55,7 @@ func TestSongSvc_Create(t *testing.T) { testName: "Find error", song: &mysqldb.Song{Artist: "some-artist", Title: "some-title"}, expectedErr: "Find-error", - songSvcFn: func(mockRepo *mysqldb_mock.MockSongRepo) { + songSvcFn: func(mockRepo *mysqldb_repo_mock.MockSongRepo) { mockRepo.EXPECT(). Create(gomock.Any(), &mysqldb.Song{Artist: "some-artist", Title: "some-title"}). Return(int64(1), nil) @@ -69,7 +70,7 @@ func TestSongSvc_Create(t *testing.T) { Title: "some-title", }, expected: &mysqldb.Song{Artist: "some-artist", Title: "some-title"}, - songSvcFn: func(mockRepo *mysqldb_mock.MockSongRepo) { + songSvcFn: func(mockRepo *mysqldb_repo_mock.MockSongRepo) { mockRepo.EXPECT(). Create(gomock.Any(), &mysqldb.Song{Artist: "some-artist", Title: "some-title"}). Return(int64(1), nil) @@ -110,7 +111,7 @@ func TestSongSvc_RetrieveOne(t *testing.T) { }, { paramID: "1", - songSvcFn: func(mockRepo *mysqldb_mock.MockSongRepo) { + songSvcFn: func(mockRepo *mysqldb_repo_mock.MockSongRepo) { mockRepo.EXPECT(). Find(gomock.Any(), dbkit.Equal("id", int64(1))). Return(nil, errors.New("some-error")) @@ -119,7 +120,7 @@ func TestSongSvc_RetrieveOne(t *testing.T) { }, { paramID: "1", - songSvcFn: func(mockRepo *mysqldb_mock.MockSongRepo) { + songSvcFn: func(mockRepo *mysqldb_repo_mock.MockSongRepo) { mockRepo.EXPECT(). Find(gomock.Any(), dbkit.Equal("id", int64(1))). Return([]*mysqldb.Song{ @@ -189,26 +190,26 @@ func TestSongSvc_Delete(t *testing.T) { { paramID: "1", expectedErr: `some-error`, - songSvcFn: func(mockRepo *mysqldb_mock.MockSongRepo) { + songSvcFn: func(mockRepo *mysqldb_repo_mock.MockSongRepo) { mockRepo.EXPECT(). - Delete(gomock.Any(), dbkit.Equal(mysqldb.SongTable.ID, int64(1))). + Delete(gomock.Any(), dbkit.Equal(mysqldb_repo.SongTable.ID, int64(1))). Return(int64(0), errors.New("some-error")) }, }, { paramID: "1", - songSvcFn: func(mockRepo *mysqldb_mock.MockSongRepo) { + songSvcFn: func(mockRepo *mysqldb_repo_mock.MockSongRepo) { mockRepo.EXPECT(). - Delete(gomock.Any(), dbkit.Equal(mysqldb.SongTable.ID, int64(1))). + Delete(gomock.Any(), dbkit.Equal(mysqldb_repo.SongTable.ID, int64(1))). Return(int64(1), nil) }, }, { testName: "success even if no affected row (idempotent)", paramID: "1", - songSvcFn: func(mockRepo *mysqldb_mock.MockSongRepo) { + songSvcFn: func(mockRepo *mysqldb_repo_mock.MockSongRepo) { mockRepo.EXPECT(). - Delete(gomock.Any(), dbkit.Equal(mysqldb.SongTable.ID, int64(1))). + Delete(gomock.Any(), dbkit.Equal(mysqldb_repo.SongTable.ID, int64(1))). Return(int64(0), nil) }, }, @@ -258,7 +259,7 @@ func TestSongSvc_Update(t *testing.T) { paramID: "1", song: &mysqldb.Song{Artist: "some-artist", Title: "some-title"}, expectedErr: "update error", - songSvcFn: func(mockRepo *mysqldb_mock.MockSongRepo) { + songSvcFn: func(mockRepo *mysqldb_repo_mock.MockSongRepo) { mockRepo.EXPECT(). Update(gomock.Any(), &mysqldb.Song{Artist: "some-artist", Title: "some-title"}, dbkit.Equal("id", int64(1))). Return(int64(-1), errors.New("update error")) @@ -269,7 +270,7 @@ func TestSongSvc_Update(t *testing.T) { paramID: "1", song: &mysqldb.Song{Artist: "some-artist", Title: "some-title"}, expectedErr: "sql: no rows in result set", - songSvcFn: func(mockRepo *mysqldb_mock.MockSongRepo) { + songSvcFn: func(mockRepo *mysqldb_repo_mock.MockSongRepo) { mockRepo.EXPECT(). Update(gomock.Any(), &mysqldb.Song{Artist: "some-artist", Title: "some-title"}, dbkit.Equal("id", int64(1))). Return(int64(0), nil) @@ -280,7 +281,7 @@ func TestSongSvc_Update(t *testing.T) { paramID: "1", song: &mysqldb.Song{Artist: "some-artist", Title: "some-title"}, expectedErr: "Find-error", - songSvcFn: func(mockRepo *mysqldb_mock.MockSongRepo) { + songSvcFn: func(mockRepo *mysqldb_repo_mock.MockSongRepo) { mockRepo.EXPECT(). Update(gomock.Any(), &mysqldb.Song{Artist: "some-artist", Title: "some-title"}, dbkit.Equal("id", int64(1))). Return(int64(1), nil) @@ -294,7 +295,7 @@ func TestSongSvc_Update(t *testing.T) { paramID: "1", song: &mysqldb.Song{Artist: "some-artist", Title: "some-title"}, expectedErr: "Find-error", - songSvcFn: func(mockRepo *mysqldb_mock.MockSongRepo) { + songSvcFn: func(mockRepo *mysqldb_repo_mock.MockSongRepo) { mockRepo.EXPECT(). Update(gomock.Any(), &mysqldb.Song{Artist: "some-artist", Title: "some-title"}, dbkit.Equal("id", int64(1))). Return(int64(1), nil) @@ -344,7 +345,7 @@ func TestSongSvc_Patch(t *testing.T) { paramID: "1", song: &mysqldb.Song{Artist: "some-artist", Title: "some-title"}, expectedErr: "patch-error", - songSvcFn: func(mockRepo *mysqldb_mock.MockSongRepo) { + songSvcFn: func(mockRepo *mysqldb_repo_mock.MockSongRepo) { mockRepo.EXPECT(). Patch(gomock.Any(), &mysqldb.Song{Artist: "some-artist", Title: "some-title"}, dbkit.Equal("id", int64(1))). Return(int64(-1), errors.New("patch-error")) @@ -355,7 +356,7 @@ func TestSongSvc_Patch(t *testing.T) { paramID: "1", song: &mysqldb.Song{Artist: "some-artist", Title: "some-title"}, expectedErr: "sql: no rows in result set", - songSvcFn: func(mockRepo *mysqldb_mock.MockSongRepo) { + songSvcFn: func(mockRepo *mysqldb_repo_mock.MockSongRepo) { mockRepo.EXPECT(). Patch(gomock.Any(), &mysqldb.Song{Artist: "some-artist", Title: "some-title"}, dbkit.Equal("id", int64(1))). Return(int64(0), nil) @@ -366,7 +367,7 @@ func TestSongSvc_Patch(t *testing.T) { paramID: "1", song: &mysqldb.Song{Artist: "some-artist", Title: "some-title"}, expectedErr: "Find-error", - songSvcFn: func(mockRepo *mysqldb_mock.MockSongRepo) { + songSvcFn: func(mockRepo *mysqldb_repo_mock.MockSongRepo) { mockRepo.EXPECT(). Patch(gomock.Any(), &mysqldb.Song{Artist: "some-artist", Title: "some-title"}, dbkit.Equal("id", int64(1))). Return(int64(1), nil) @@ -379,7 +380,7 @@ func TestSongSvc_Patch(t *testing.T) { paramID: "1", song: &mysqldb.Song{Artist: "some-artist", Title: "some-title"}, expected: &mysqldb.Song{Artist: "some-artist", Title: "some-title"}, - songSvcFn: func(mockRepo *mysqldb_mock.MockSongRepo) { + songSvcFn: func(mockRepo *mysqldb_repo_mock.MockSongRepo) { mockRepo.EXPECT(). Patch(gomock.Any(), &mysqldb.Song{Artist: "some-artist", Title: "some-title"}, dbkit.Equal("id", int64(1))). Return(int64(1), nil) diff --git a/internal/generated/mysqldb_repo/song_repo.go b/internal/generated/mysqldb_repo/song_repo.go new file mode 100755 index 00000000..6f440e9b --- /dev/null +++ b/internal/generated/mysqldb_repo/song_repo.go @@ -0,0 +1,221 @@ +package mysqldb_repo + +import ( + "context" + "database/sql" + "time" + + sq "github.com/Masterminds/squirrel" + "github.com/typical-go/typical-go/pkg/typapp" + "github.com/typical-go/typical-rest-server/internal/app/data_access/mysqldb" + "github.com/typical-go/typical-rest-server/pkg/dbkit" + "github.com/typical-go/typical-rest-server/pkg/dbtxn" + "github.com/typical-go/typical-rest-server/pkg/reflectkit" + "go.uber.org/dig" +) + +var ( + // SongTableName is table name for songs entity + SongTableName = "songs" + // SongTable is columns for songs entity + SongTable = struct { + ID string + Title string + Artist string + UpdatedAt string + CreatedAt string + }{ + ID: "id", + Title: "title", + Artist: "artist", + UpdatedAt: "updated_at", + CreatedAt: "created_at", + } +) + +type ( + // SongRepo to get songs data from database + // @mock + SongRepo interface { + Find(context.Context, ...dbkit.SelectOption) ([]*mysqldb.Song, error) + Create(context.Context, *mysqldb.Song) (int64, error) + Delete(context.Context, dbkit.DeleteOption) (int64, error) + Update(context.Context, *mysqldb.Song, dbkit.UpdateOption) (int64, error) + Patch(context.Context, *mysqldb.Song, dbkit.UpdateOption) (int64, error) + } + // SongRepoImpl is implementation songs repository + SongRepoImpl struct { + dig.In + *sql.DB `name:"mysql"` + } +) + +func init() { + typapp.AppendCtor(&typapp.Constructor{Name: "", Fn: NewSongRepo}) +} + +// NewSongRepo return new instance of SongRepo +func NewSongRepo(impl SongRepoImpl) SongRepo { + return &impl +} + +// Find songs +func (r *SongRepoImpl) Find(ctx context.Context, opts ...dbkit.SelectOption) (list []*mysqldb.Song, err error) { + builder := sq. + Select( + SongTable.ID, + SongTable.Title, + SongTable.Artist, + SongTable.UpdatedAt, + SongTable.CreatedAt, + ). + From(SongTableName). + RunWith(r) + + for _, opt := range opts { + if builder, err = opt.CompileSelect(builder); err != nil { + return nil, err + } + } + + rows, err := builder.QueryContext(ctx) + if err != nil { + return + } + + list = make([]*mysqldb.Song, 0) + for rows.Next() { + ent := new(mysqldb.Song) + if err = rows.Scan( + &ent.ID, + &ent.Title, + &ent.Artist, + &ent.UpdatedAt, + &ent.CreatedAt, + ); err != nil { + return + } + list = append(list, ent) + } + return +} + +// Create songs +func (r *SongRepoImpl) Create(ctx context.Context, ent *mysqldb.Song) (int64, error) { + txn, err := dbtxn.Use(ctx, r.DB) + if err != nil { + return -1, err + } + + res, err := sq. + Insert(SongTableName). + Columns( + SongTable.Title, + SongTable.Artist, + SongTable.UpdatedAt, + SongTable.CreatedAt, + ). + Values( + ent.Title, + ent.Artist, + time.Now(), + time.Now(), + ). + RunWith(txn.DB). + ExecContext(ctx) + + if err != nil { + txn.SetError(err) + return -1, err + } + + lastInsertID, err := res.LastInsertId() + txn.SetError(err) + return lastInsertID, err +} + +// Update songs +func (r *SongRepoImpl) Update(ctx context.Context, ent *mysqldb.Song, opt dbkit.UpdateOption) (int64, error) { + txn, err := dbtxn.Use(ctx, r.DB) + if err != nil { + return -1, err + } + + builder := sq. + Update(SongTableName). + Set(SongTable.Title, ent.Title). + Set(SongTable.Artist, ent.Artist). + Set(SongTable.UpdatedAt, time.Now()). + RunWith(txn.DB) + + if builder, err = opt.CompileUpdate(builder); err != nil { + txn.SetError(err) + return -1, err + } + + res, err := builder.ExecContext(ctx) + if err != nil { + txn.SetError(err) + return -1, err + } + affectedRow, err := res.RowsAffected() + txn.SetError(err) + return affectedRow, err +} + +// Patch songs +func (r *SongRepoImpl) Patch(ctx context.Context, ent *mysqldb.Song, opt dbkit.UpdateOption) (int64, error) { + txn, err := dbtxn.Use(ctx, r.DB) + if err != nil { + return -1, err + } + + builder := sq.Update(SongTableName).RunWith(txn.DB) + + if !reflectkit.IsZero(ent.Title) { + builder = builder.Set(SongTable.Title, ent.Title) + } + if !reflectkit.IsZero(ent.Artist) { + builder = builder.Set(SongTable.Artist, ent.Artist) + } + builder = builder.Set(SongTable.UpdatedAt, time.Now()) + + if builder, err = opt.CompileUpdate(builder); err != nil { + txn.SetError(err) + return -1, err + } + + res, err := builder.ExecContext(ctx) + if err != nil { + txn.SetError(err) + return -1, err + } + + affectedRow, err := res.RowsAffected() + txn.SetError(err) + return affectedRow, err +} + +// Delete songs +func (r *SongRepoImpl) Delete(ctx context.Context, opt dbkit.DeleteOption) (int64, error) { + txn, err := dbtxn.Use(ctx, r.DB) + if err != nil { + return -1, err + } + + builder := sq.Delete(SongTableName).RunWith(txn.DB) + if builder, err = opt.CompileDelete(builder); err != nil { + txn.SetError(err) + return -1, err + } + + res, err := builder.ExecContext(ctx) + if err != nil { + txn.SetError(err) + return -1, err + } + + affectedRow, err := res.RowsAffected() + txn.SetError(err) + return affectedRow, err +} diff --git a/internal/app/data_access/mysqldb_mock/song_repo.go b/internal/generated/mysqldb_repo_mock/song_repo.go similarity index 95% rename from internal/app/data_access/mysqldb_mock/song_repo.go rename to internal/generated/mysqldb_repo_mock/song_repo.go index 581ad969..30ea448b 100644 --- a/internal/app/data_access/mysqldb_mock/song_repo.go +++ b/internal/generated/mysqldb_repo_mock/song_repo.go @@ -1,8 +1,8 @@ // Code generated by MockGen. DO NOT EDIT. -// Source: github.com/typical-go/typical-rest-server/internal/app/data_access/mysqldb (interfaces: SongRepo) +// Source: github.com/typical-go/typical-rest-server/internal/generated/mysqldb_repo (interfaces: SongRepo) -// Package mysqldb_mock is a generated GoMock package. -package mysqldb_mock +// Package mysqldb_repo_mock is a generated GoMock package. +package mysqldb_repo_mock import ( context "context" diff --git a/internal/generated/typical/ctor_annotated.go b/internal/generated/typical/ctor_annotated.go index 1f32a76d..7e4a844e 100755 --- a/internal/generated/typical/ctor_annotated.go +++ b/internal/generated/typical/ctor_annotated.go @@ -11,17 +11,15 @@ Help: import ( "github.com/typical-go/typical-go/pkg/typapp" - a "github.com/typical-go/typical-rest-server/internal/app/data_access/mysqldb" - b "github.com/typical-go/typical-rest-server/internal/app/domain/mylibrary/service" - c "github.com/typical-go/typical-rest-server/internal/app/domain/mymusic/service" - d "github.com/typical-go/typical-rest-server/internal/app/infra" + a "github.com/typical-go/typical-rest-server/internal/app/domain/mylibrary/service" + b "github.com/typical-go/typical-rest-server/internal/app/domain/mymusic/service" + c "github.com/typical-go/typical-rest-server/internal/app/infra" ) func init() { typapp.AppendCtor( - &typapp.Constructor{Name: "", Fn: a.NewSongRepo}, - &typapp.Constructor{Name: "", Fn: b.NewBookSvc}, - &typapp.Constructor{Name: "", Fn: c.NewSongSvc}, - &typapp.Constructor{Name: "", Fn: d.Setup}, + &typapp.Constructor{Name: "", Fn: a.NewBookSvc}, + &typapp.Constructor{Name: "", Fn: b.NewSongSvc}, + &typapp.Constructor{Name: "", Fn: c.Setup}, ) } diff --git a/pkg/typrepo/entity_annotation.go b/pkg/typrepo/entity_annotation.go index 791175a3..5224f79b 100644 --- a/pkg/typrepo/entity_annotation.go +++ b/pkg/typrepo/entity_annotation.go @@ -63,7 +63,7 @@ func (m *EntityAnnotation) Annotate(c *typast.Context) error { annots, _ := typast.FindAnnot(c, m.getTagName(), typast.EqualStruct) for _, a := range annots { if err := m.process(a); err != nil { - fmt.Fprintf(Stdout, "WARN: %s: %s\n", a.GetName(), err.Error()) + fmt.Fprintf(Stdout, "WARN: Failed process @entity at '%s': %s\n", a.GetName(), err.Error()) } } return nil @@ -93,6 +93,8 @@ func getTemplate(dialect string) (string, error) { switch strings.ToLower(dialect) { case "postgres": return postgresTmpl, nil + case "mysql": + return mysqlTmpl, nil } return "", fmt.Errorf("Unknown dialect: %s", dialect) } diff --git a/pkg/typrepo/mysql_template.go b/pkg/typrepo/mysql_template.go new file mode 100644 index 00000000..c7504341 --- /dev/null +++ b/pkg/typrepo/mysql_template.go @@ -0,0 +1,190 @@ +package typrepo + +const mysqlTmpl = `package {{.Package}}_repo + +import({{range $pkg, $alias := .Imports}} + {{$alias}} "{{$pkg}}"{{end}} +) + +var ( + // {{.Name}}TableName is table name for {{.Table}} entity + {{.Name}}TableName = "{{.Table}}" + // {{.Name}}Table is columns for {{.Table}} entity + {{.Name}}Table = struct { + {{range .Fields}}{{.Name}} string + {{end}} + }{ + {{range .Fields}}{{.Name}}: "{{.Column}}", + {{end}} + } +) + +type ( + // {{.Name}}Repo to get {{.Table}} data from database + // @mock + {{.Name}}Repo interface { + Find(context.Context, ...dbkit.SelectOption) ([]*{{.Package}}.{{.Name}}, error) + Create(context.Context, *{{.Package}}.{{.Name}}) (int64, error) + Delete(context.Context, dbkit.DeleteOption) (int64, error) + Update(context.Context, *{{.Package}}.{{.Name}}, dbkit.UpdateOption) (int64, error) + Patch(context.Context, *{{.Package}}.{{.Name}}, dbkit.UpdateOption) (int64, error) + } + // {{.Name}}RepoImpl is implementation {{.Table}} repository + {{.Name}}RepoImpl struct { + dig.In + *sql.DB {{.CtorDB}} + } +) + +func init() { + typapp.AppendCtor(&typapp.Constructor{Name: "", Fn: New{{.Name}}Repo}) +} + +// New{{.Name}}Repo return new instance of {{.Name}}Repo +func New{{.Name}}Repo(impl {{.Name}}RepoImpl) {{.Name}}Repo { + return &impl +} + +// Find {{.Table}} +func (r *{{.Name}}RepoImpl) Find(ctx context.Context, opts ...dbkit.SelectOption) (list []*{{.Package}}.{{.Name}}, err error) { + builder := sq. + Select( + {{range .Fields}}{{$.Name}}Table.{{.Name}}, + {{end}} + ). + From({{.Name}}TableName). + RunWith(r) + + for _, opt := range opts { + if builder, err = opt.CompileSelect(builder); err != nil { + return nil, err + } + } + + rows, err := builder.QueryContext(ctx) + if err != nil { + return + } + + list = make([]*{{.Package}}.{{.Name}}, 0) + for rows.Next() { + ent := new({{.Package}}.{{.Name}}) + if err = rows.Scan({{range .Fields}} + &ent.{{.Name}},{{end}} + ); err != nil { + return + } + list = append(list, ent) + } + return +} + +// Create {{.Table}} +func (r *{{.Name}}RepoImpl) Create(ctx context.Context, ent *{{.Package}}.{{.Name}}) (int64, error) { + txn, err := dbtxn.Use(ctx, r.DB) + if err != nil { + return -1, err + } + + res, err := sq. + Insert({{$.Name}}TableName). + Columns({{range .Fields}}{{if not .PrimaryKey}} {{$.Name}}Table.{{.Name}},{{end}} + {{end}}). + Values({{range .Fields}}{{if .DefaultValue}} {{.DefaultValue}},{{else if not .PrimaryKey}} ent.{{.Name}},{{end}} + {{end}}). + RunWith(txn.DB). + ExecContext(ctx) + + if err != nil { + txn.SetError(err) + return -1, err + } + + lastInsertID, err := res.LastInsertId() + txn.SetError(err) + return lastInsertID, err +} + + +// Update {{.Table}} +func (r *{{.Name}}RepoImpl) Update(ctx context.Context, ent *{{.Package}}.{{.Name}}, opt dbkit.UpdateOption) (int64, error) { + txn, err := dbtxn.Use(ctx, r.DB) + if err != nil { + return -1, err + } + + builder := sq. + Update({{.Name}}TableName).{{range .Fields}}{{if and (not .PrimaryKey) (not .SkipUpdate)}} + Set({{$.Name}}Table.{{.Name}},{{if .DefaultValue}}{{.DefaultValue}}{{else}}ent.{{.Name}},{{end}}).{{end}}{{end}} + RunWith(txn.DB) + + if builder, err = opt.CompileUpdate(builder); err != nil { + txn.SetError(err) + return -1, err + } + + res, err := builder.ExecContext(ctx) + if err != nil { + txn.SetError(err) + return -1, err + } + affectedRow, err := res.RowsAffected() + txn.SetError(err) + return affectedRow, err +} + +// Patch {{.Table}} +func (r *{{.Name}}RepoImpl) Patch(ctx context.Context, ent *{{.Package}}.{{.Name}}, opt dbkit.UpdateOption) (int64, error) { + txn, err := dbtxn.Use(ctx, r.DB) + if err != nil { + return -1, err + } + + builder := sq.Update({{.Name}}TableName).RunWith(txn.DB) + {{range .Fields}}{{if and (not .PrimaryKey) (not .SkipUpdate)}}{{if .DefaultValue}} + builder = builder.Set({{$.Name}}Table.UpdatedAt, {{.DefaultValue}}){{else}} + if !reflectkit.IsZero(ent.{{.Name}}) { + builder = builder.Set({{$.Name}}Table.{{.Name}}, ent.{{.Name}}) + }{{end}}{{end}}{{end}} + + if builder, err = opt.CompileUpdate(builder); err != nil { + txn.SetError(err) + return -1, err + } + + res, err := builder.ExecContext(ctx) + if err != nil { + txn.SetError(err) + return -1, err + } + + affectedRow, err := res.RowsAffected() + txn.SetError(err) + return affectedRow, err +} + + +// Delete {{.Table}} +func (r *{{.Name}}RepoImpl) Delete(ctx context.Context, opt dbkit.DeleteOption) (int64, error) { + txn, err := dbtxn.Use(ctx, r.DB) + if err != nil { + return -1, err + } + + builder := sq.Delete({{.Name}}TableName).RunWith(txn.DB) + if builder, err = opt.CompileDelete(builder); err != nil { + txn.SetError(err) + return -1, err + } + + res, err := builder.ExecContext(ctx) + if err != nil { + txn.SetError(err) + return -1, err + } + + affectedRow, err := res.RowsAffected() + txn.SetError(err) + return affectedRow, err +} +`