From bfe77a0b79d60161e0d071728b7a567cf3567ec5 Mon Sep 17 00:00:00 2001 From: Surya Asriadie Date: Mon, 17 Oct 2022 23:32:04 +0900 Subject: [PATCH] Generic type Repository wrapper (#318) * Generic type Repository wrapper * tidy * set go version * fix and add more tests * add more test * rename record with entity * Implement entity iterator --- .github/workflows/test.yml | 2 + association_test.go | 32 +- changeset.go | 6 +- collection.go | 4 +- collection_test.go | 70 ++-- document.go | 4 +- document_test.go | 202 +++++------ entity_iterator_go.1.18.go | 33 ++ entity_iterator_go1.18_test.go | 46 +++ entity_repository_go1.18.go | 275 +++++++++++++++ entity_repository_go1.18_test.go | 572 +++++++++++++++++++++++++++++++ errors.go | 4 +- errors_test.go | 2 +- go.mod | 7 +- go.sum | 7 - iterator.go | 18 +- iterator_test.go | 6 +- map.go | 2 +- map_test.go | 2 +- mutation.go | 2 +- mutation_test.go | 50 +-- repository.go | 212 ++++++------ repository_test.go | 8 +- structset.go | 4 +- structset_test.go | 6 +- 25 files changed, 1250 insertions(+), 326 deletions(-) create mode 100644 entity_iterator_go.1.18.go create mode 100644 entity_iterator_go1.18_test.go create mode 100644 entity_repository_go1.18.go create mode 100644 entity_repository_go1.18_test.go diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index d8b1d476..24751722 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -32,6 +32,8 @@ jobs: steps: - name: Set up Go 1.x uses: actions/setup-go@v3 + with: + go-version: 1.19 - name: Check out code into the Go module directory uses: actions/checkout@v2 with: diff --git a/association_test.go b/association_test.go index 8f4bc15c..37075406 100644 --- a/association_test.go +++ b/association_test.go @@ -18,7 +18,7 @@ func TestAssociation_Document(t *testing.T) { ) tests := []struct { - record string + entity string field string data any typ AssociationType @@ -33,7 +33,7 @@ func TestAssociation_Document(t *testing.T) { autoload bool }{ { - record: "Transaction", + entity: "Transaction", field: "Buyer", data: transaction, typ: BelongsTo, @@ -47,7 +47,7 @@ func TestAssociation_Document(t *testing.T) { autoload: true, }, { - record: "Transaction", + entity: "Transaction", field: "Buyer", data: transactionLoaded, typ: BelongsTo, @@ -61,7 +61,7 @@ func TestAssociation_Document(t *testing.T) { autoload: true, }, { - record: "User", + entity: "User", field: "Address", data: user, typ: HasOne, @@ -75,7 +75,7 @@ func TestAssociation_Document(t *testing.T) { autosave: true, }, { - record: "User", + entity: "User", field: "Address", data: userLoaded, typ: HasOne, @@ -89,7 +89,7 @@ func TestAssociation_Document(t *testing.T) { autosave: true, }, { - record: "Address", + entity: "Address", field: "User", data: address, typ: BelongsTo, @@ -102,7 +102,7 @@ func TestAssociation_Document(t *testing.T) { foreignValue: 0, }, { - record: "Address", + entity: "Address", field: "User", data: addressLoaded, typ: BelongsTo, @@ -117,7 +117,7 @@ func TestAssociation_Document(t *testing.T) { } for _, test := range tests { - t.Run(test.record+"."+test.field, func(t *testing.T) { + t.Run(test.entity+"."+test.field, func(t *testing.T) { var ( rv = reflect.ValueOf(test.data) sf, _ = rv.Type().Elem().FieldByName(test.field) @@ -197,7 +197,7 @@ func TestAssociation_Collection(t *testing.T) { ) tests := []struct { - record string + entity string field string data any typ AssociationType @@ -215,7 +215,7 @@ func TestAssociation_Collection(t *testing.T) { autosave bool }{ { - record: "User", + entity: "User", field: "Transactions", data: user, typ: HasMany, @@ -228,7 +228,7 @@ func TestAssociation_Collection(t *testing.T) { foreignValue: nil, }, { - record: "User", + entity: "User", field: "Transactions", data: userLoaded, typ: HasMany, @@ -241,7 +241,7 @@ func TestAssociation_Collection(t *testing.T) { foreignValue: nil, }, { - record: "User", + entity: "User", field: "Roles", data: user, typ: HasMany, @@ -255,7 +255,7 @@ func TestAssociation_Collection(t *testing.T) { through: "user_roles", }, { - record: "Role", + entity: "Role", field: "Users", data: role, typ: HasMany, @@ -269,7 +269,7 @@ func TestAssociation_Collection(t *testing.T) { through: "user_roles", }, { - record: "User", + entity: "User", field: "Followers", data: user, typ: HasMany, @@ -283,7 +283,7 @@ func TestAssociation_Collection(t *testing.T) { through: "followeds", }, { - record: "User", + entity: "User", field: "Followings", data: user, typ: HasMany, @@ -299,7 +299,7 @@ func TestAssociation_Collection(t *testing.T) { } for _, test := range tests { - t.Run(test.record+"."+test.field, func(t *testing.T) { + t.Run(test.entity+"."+test.field, func(t *testing.T) { var ( rv = reflect.ValueOf(test.data) sf, _ = rv.Type().Elem().FieldByName(test.field) diff --git a/changeset.go b/changeset.go index 2a474a40..159d25b2 100644 --- a/changeset.go +++ b/changeset.go @@ -157,9 +157,9 @@ func (c Changeset) applyAssocMany(field string, mut *Mutation) { } } -// NewChangeset returns new changeset mutator for given record. -func NewChangeset(record any) Changeset { - return newChangeset(NewDocument(record)) +// NewChangeset returns new changeset mutator for given entity. +func NewChangeset(entity any) Changeset { + return newChangeset(NewDocument(entity)) } func newChangeset(doc *Document) Changeset { diff --git a/collection.go b/collection.go index 8f545bde..f34ec28d 100644 --- a/collection.go +++ b/collection.go @@ -178,8 +178,8 @@ func (c Collection) Swap(i, j int) { // NewCollection used to create abstraction to work with slice. // COllection can be created using interface or reflect.Value. -func NewCollection(records any, readonly ...bool) *Collection { - switch v := records.(type) { +func NewCollection(entities any, readonly ...bool) *Collection { + switch v := entities.(type) { case *Collection: return v case reflect.Value: diff --git a/collection_test.go b/collection_test.go index f64aab02..d3ed459d 100644 --- a/collection_test.go +++ b/collection_test.go @@ -32,8 +32,8 @@ func (it *Items) PrimaryValues() []any { func TestCollection_ReflectValue(t *testing.T) { var ( - record = []User{} - doc = NewCollection(&record) + entity = []User{} + doc = NewCollection(&entity) ) assert.Equal(t, doc.rv, doc.ReflectValue()) @@ -41,8 +41,8 @@ func TestCollection_ReflectValue(t *testing.T) { func TestCollection_Table(t *testing.T) { var ( - records = []User{} - col = NewCollection(&records) + entities = []User{} + col = NewCollection(&entities) ) // infer table name @@ -51,8 +51,8 @@ func TestCollection_Table(t *testing.T) { func TestCollection_Table_usingInterface(t *testing.T) { var ( - records = Items{} - col = NewCollection(&records) + entities = Items{} + col = NewCollection(&entities) ) // infer table name @@ -61,8 +61,8 @@ func TestCollection_Table_usingInterface(t *testing.T) { func TestCollection_Table_usingElemInterface(t *testing.T) { var ( - records = []Item{} - col = NewCollection(&records) + entities = []Item{} + col = NewCollection(&entities) ) // infer table name @@ -71,12 +71,12 @@ func TestCollection_Table_usingElemInterface(t *testing.T) { func TestCollection_Primary(t *testing.T) { var ( - records = []User{ + entities = []User{ {ID: 1}, {ID: 2}, } - rt = reflect.TypeOf(records).Elem() - col = NewCollection(&records) + rt = reflect.TypeOf(entities).Elem() + col = NewCollection(&entities) ) // infer primary key @@ -87,7 +87,7 @@ func TestCollection_Primary(t *testing.T) { _, cached := primariesCache.Load(rt) assert.True(t, cached) - records[1].ID = 4 + entities[1].ID = 4 // infer primary key using cache assert.Equal(t, "id", col.PrimaryField()) @@ -98,11 +98,11 @@ func TestCollection_Primary(t *testing.T) { func TestCollection_Primary_usingInterface(t *testing.T) { var ( - records = Items{ + entities = Items{ {UUID: "abc123"}, {UUID: "def456"}, } - col = NewCollection(&records) + col = NewCollection(&entities) ) // infer primary key @@ -112,12 +112,12 @@ func TestCollection_Primary_usingInterface(t *testing.T) { func TestCollection_Primary_usingElemInterface(t *testing.T) { var ( - records = []Item{ + entities = []Item{ {UUID: "abc123"}, {UUID: "def456"}, } - rt = reflect.TypeOf(records).Elem() - col = NewCollection(&records) + rt = reflect.TypeOf(entities).Elem() + col = NewCollection(&entities) ) // infer primary key @@ -129,13 +129,13 @@ func TestCollection_Primary_usingElemInterface(t *testing.T) { func TestCollection_Primary_usingElemInterface_ptrElem(t *testing.T) { var ( - records = []*Item{ + entities = []*Item{ {UUID: "abc123"}, {UUID: "def456"}, nil, } - rt = reflect.TypeOf(records).Elem() - col = NewCollection(&records) + rt = reflect.TypeOf(entities).Elem() + col = NewCollection(&entities) ) // infer primary key @@ -147,7 +147,7 @@ func TestCollection_Primary_usingElemInterface_ptrElem(t *testing.T) { func TestCollection_Primary_usingTag(t *testing.T) { var ( - records = []struct { + entities = []struct { ID uint ExternalID int `db:",primary"` Name string @@ -155,7 +155,7 @@ func TestCollection_Primary_usingTag(t *testing.T) { {ExternalID: 1}, {ExternalID: 2}, } - col = NewCollection(&records) + col = NewCollection(&entities) ) // infer primary key @@ -190,11 +190,11 @@ func TestCollection_Primary_composite(t *testing.T) { func TestCollection_Primary_notFound(t *testing.T) { var ( - records = []struct { + entities = []struct { ExternalID int Name string }{} - col = NewCollection(&records) + col = NewCollection(&entities) ) assert.Panics(t, func() { @@ -258,45 +258,45 @@ func TestCollection_Slice(t *testing.T) { func TestCollection(t *testing.T) { tests := []struct { - record any + entity any panics bool }{ { - record: &[]User{}, + entity: &[]User{}, }, { - record: NewCollection(&[]User{}), + entity: NewCollection(&[]User{}), }, { - record: reflect.ValueOf(&[]User{}), + entity: reflect.ValueOf(&[]User{}), }, { - record: reflect.ValueOf([]User{}), + entity: reflect.ValueOf([]User{}), panics: true, }, { - record: reflect.ValueOf(&User{}), + entity: reflect.ValueOf(&User{}), panics: true, }, { - record: reflect.TypeOf(&[]User{}), + entity: reflect.TypeOf(&[]User{}), panics: true, }, { - record: nil, + entity: nil, panics: true, }, } for _, test := range tests { - t.Run(fmt.Sprintf("%T", test.record), func(t *testing.T) { + t.Run(fmt.Sprintf("%T", test.entity), func(t *testing.T) { if test.panics { assert.Panics(t, func() { - NewCollection(test.record) + NewCollection(test.entity) }) } else { assert.NotPanics(t, func() { - NewCollection(test.record) + NewCollection(test.entity) }) } }) diff --git a/document.go b/document.go index 97a0b77d..86e54146 100644 --- a/document.go +++ b/document.go @@ -294,8 +294,8 @@ func (d Document) Flag(flag DocumentFlag) bool { // NewDocument used to create abstraction to work with struct. // Document can be created using interface or reflect.Value. -func NewDocument(record any, readonly ...bool) *Document { - switch v := record.(type) { +func NewDocument(entity any, readonly ...bool) *Document { + switch v := entity.(type) { case *Document: return v case reflect.Value: diff --git a/document_test.go b/document_test.go index 82ee9683..d42dcaee 100644 --- a/document_test.go +++ b/document_test.go @@ -29,8 +29,8 @@ func (i Item) PrimaryValues() []any { func TestDocument_ReflectValue(t *testing.T) { var ( - record = User{} - doc = NewDocument(&record) + entity = User{} + doc = NewDocument(&entity) ) assert.Equal(t, doc.rv, doc.ReflectValue()) @@ -38,8 +38,8 @@ func TestDocument_ReflectValue(t *testing.T) { func TestDocument_Table(t *testing.T) { var ( - record = User{} - doc = NewDocument(&record) + entity = User{} + doc = NewDocument(&entity) ) // infer table name @@ -48,8 +48,8 @@ func TestDocument_Table(t *testing.T) { func TestDocument_Table_usingInterface(t *testing.T) { var ( - record = Item{} - doc = NewDocument(&record) + entity = Item{} + doc = NewDocument(&entity) ) // infer table name @@ -58,15 +58,15 @@ func TestDocument_Table_usingInterface(t *testing.T) { func TestDocument_Primary(t *testing.T) { var ( - record = User{ID: 1} - doc = NewDocument(&record) + entity = User{ID: 1} + doc = NewDocument(&entity) ) // infer primary key assert.Equal(t, "id", doc.PrimaryField()) assert.Equal(t, 1, doc.PrimaryValue()) - record.ID = 2 + entity.ID = 2 // infer primary key using cache assert.Equal(t, "id", doc.PrimaryField()) @@ -80,8 +80,8 @@ func TestDocument_PrimaryEmbedded(t *testing.T) { } var ( - record = UserWrapper{User: User{ID: 1}} - doc = NewDocument(&record) + entity = UserWrapper{User: User{ID: 1}} + doc = NewDocument(&entity) ) assert.Equal(t, "id", doc.PrimaryField()) @@ -90,10 +90,10 @@ func TestDocument_PrimaryEmbedded(t *testing.T) { func TestDocument_Primary_usingInterface(t *testing.T) { var ( - record = Item{ + entity = Item{ UUID: "abc123", } - doc = NewDocument(&record) + doc = NewDocument(&entity) ) // infer primary key @@ -103,14 +103,14 @@ func TestDocument_Primary_usingInterface(t *testing.T) { func TestDocument_Primary_usingTag(t *testing.T) { var ( - record = struct { + entity = struct { ID uint ExternalID int `db:",primary"` Name string }{ ExternalID: 12345, } - doc = NewDocument(&record) + doc = NewDocument(&entity) ) // infer primary key @@ -120,14 +120,14 @@ func TestDocument_Primary_usingTag(t *testing.T) { func TestDocument_Primary_usingTagAmdCustomName(t *testing.T) { var ( - record = struct { + entity = struct { ID uint ExternalID int `db:"partner_id,primary"` Name string }{ ExternalID: 1111, } - doc = NewDocument(&record) + doc = NewDocument(&entity) ) // infer primary key @@ -137,11 +137,11 @@ func TestDocument_Primary_usingTagAmdCustomName(t *testing.T) { func TestDocument_Primary_notFound(t *testing.T) { var ( - record = struct { + entity = struct { ExternalID int Name string }{} - doc = NewDocument(&record) + doc = NewDocument(&entity) ) assert.Panics(t, func() { @@ -173,14 +173,14 @@ func TestDocument_Primary_composite(t *testing.T) { func TestDocument_Fields(t *testing.T) { var ( - record = struct { + entity = struct { A string B *int C []byte `db:",primary"` D bool `db:"D"` E []*float64 `db:"-"` }{} - doc = NewDocument(&record) + doc = NewDocument(&entity) fields = []string{"a", "b", "c", "D"} ) @@ -189,14 +189,14 @@ func TestDocument_Fields(t *testing.T) { func TestDocument_Index(t *testing.T) { var ( - record = struct { + entity = struct { A string B *int C []byte `db:",primary"` D bool `db:"D"` E []*float64 `db:"-"` }{} - doc = NewDocument(&record) + doc = NewDocument(&entity) index = map[string][]int{ "a": {0}, "b": {1}, @@ -217,12 +217,12 @@ func TestDocument_IndexEmbedded(t *testing.T) { D float32 } var ( - record = struct { + entity = struct { FirstEmbedded `db:"first_"` C string *SecondEmbedded }{} - doc = NewDocument(&record) + doc = NewDocument(&entity) index = map[string][]int{ "first_a": {0, 0}, "first_b": {0, 1}, @@ -243,13 +243,13 @@ func TestDocument_IndexFieldEmbedded(t *testing.T) { D float32 } var ( - record = struct { + entity = struct { First FirstEmbedded `db:"first_,embedded"` C string Second SecondEmbedded `db:",embedded"` E int `db:"embedded"` // this field is not embedded, but only called so }{} - doc = NewDocument(&record) + doc = NewDocument(&entity) index = map[string][]int{ "first_a": {0, 0}, "first_b": {0, 1}, @@ -266,19 +266,19 @@ func TestDocument_EmbeddedNameConfict(t *testing.T) { type Embedded struct { Name string } - record := struct { + entity := struct { Embedded Name string }{} assert.Panics(t, func() { - NewDocument(&record) + NewDocument(&entity) }) } func TestDocument_Types(t *testing.T) { var ( - record = struct { + entity = struct { A string B *int C []byte @@ -287,7 +287,7 @@ func TestDocument_Types(t *testing.T) { F userDefined G time.Time }{} - doc = NewDocument(&record) + doc = NewDocument(&entity) types = map[string]reflect.Type{ "a": reflect.TypeOf(""), "b": reflect.TypeOf(0), @@ -309,7 +309,7 @@ func TestDocument_Types(t *testing.T) { func TestDocument_Value(t *testing.T) { var ( address = "address" - record = struct { + entity = struct { ID int Name string Skip bool `db:"-"` @@ -323,7 +323,7 @@ func TestDocument_Value(t *testing.T) { Address: &address, Data: []byte("data"), } - doc = NewDocument(&record) + doc = NewDocument(&entity) values = map[string]any{ "id": 1, "name": "name", @@ -352,11 +352,11 @@ func TestDocument_ValueEmbedded(t *testing.T) { ID int } var ( - record = struct { + entity = struct { *Embedded Name string }{} - doc = NewDocument(&record) + doc = NewDocument(&entity) ) value, ok := doc.Value("id") @@ -382,7 +382,7 @@ func TestDocument_ValueEmbedded(t *testing.T) { func TestDocument_SetValue(t *testing.T) { var ( - record struct { + entity struct { ID int Name string Skip bool `db:"-"` @@ -390,7 +390,7 @@ func TestDocument_SetValue(t *testing.T) { Address *string Data []byte } - doc = NewDocument(&record) + doc = NewDocument(&entity) ) t.Run("ok", func(t *testing.T) { @@ -400,12 +400,12 @@ func TestDocument_SetValue(t *testing.T) { assert.True(t, doc.SetValue("data", []byte("data"))) assert.True(t, doc.SetValue("address", "address")) - assert.Equal(t, 1, record.ID) - assert.Equal(t, "name", record.Name) - assert.Equal(t, false, record.Skip) - assert.Equal(t, 10.5, record.Number) - assert.Equal(t, "address", *record.Address) - assert.Equal(t, []byte("data"), record.Data) + assert.Equal(t, 1, entity.ID) + assert.Equal(t, "name", entity.Name) + assert.Equal(t, false, entity.Skip) + assert.Equal(t, 10.5, entity.Number) + assert.Equal(t, "address", *entity.Address) + assert.Equal(t, []byte("data"), entity.Data) }) t.Run("zero", func(t *testing.T) { @@ -415,25 +415,25 @@ func TestDocument_SetValue(t *testing.T) { assert.True(t, doc.SetValue("data", nil)) assert.True(t, doc.SetValue("address", nil)) - assert.Equal(t, 0, record.ID) - assert.Equal(t, "", record.Name) - assert.Equal(t, float64(0), record.Number) - assert.Equal(t, (*string)(nil), record.Address) - assert.Equal(t, []byte(nil), record.Data) + assert.Equal(t, 0, entity.ID) + assert.Equal(t, "", entity.Name) + assert.Equal(t, float64(0), entity.Number) + assert.Equal(t, (*string)(nil), entity.Address) + assert.Equal(t, []byte(nil), entity.Data) }) t.Run("convert", func(t *testing.T) { assert.True(t, doc.SetValue("id", uint(2))) assert.True(t, doc.SetValue("number", 10)) - assert.Equal(t, 2, record.ID) - assert.Equal(t, float64(10), record.Number) + assert.Equal(t, 2, entity.ID) + assert.Equal(t, float64(10), entity.Number) }) t.Run("reflect", func(t *testing.T) { assert.True(t, doc.SetValue("id", reflect.ValueOf(21))) assert.True(t, doc.SetValue("address", reflect.ValueOf("continassa"))) - assert.Equal(t, 21, record.ID) - assert.Equal(t, "continassa", *record.Address) + assert.Equal(t, 21, entity.ID) + assert.Equal(t, "continassa", *entity.Address) }) t.Run("field not exists", func(t *testing.T) { @@ -449,11 +449,11 @@ func TestDocument_SetValueEmbedded(t *testing.T) { Name string } var ( - record struct { + entity struct { Embedded Number float64 } - doc = NewDocument(&record) + doc = NewDocument(&entity) ) assert.True(t, doc.SetValue("id", 1)) @@ -462,7 +462,7 @@ func TestDocument_SetValueEmbedded(t *testing.T) { func TestDocument_Scanners(t *testing.T) { var ( address = "address" - record = struct { + entity = struct { ID int Name string Skip bool `db:"-"` @@ -476,15 +476,15 @@ func TestDocument_Scanners(t *testing.T) { Address: &address, Data: []byte("data"), } - doc = NewDocument(&record) + doc = NewDocument(&entity) fields = []string{"name", "id", "skip", "data", "number", "address", "not_exist"} scanners = []any{ - Nullable(&record.Name), - Nullable(&record.ID), + Nullable(&entity.Name), + Nullable(&entity.ID), &sql.RawBytes{}, - Nullable(&record.Data), - Nullable(&record.Number), - &record.Address, + Nullable(&entity.Data), + Nullable(&entity.Number), + &entity.Address, &sql.RawBytes{}, } ) @@ -494,7 +494,7 @@ func TestDocument_Scanners(t *testing.T) { func TestDocument_Scanners_withAssoc(t *testing.T) { var ( - record = Transaction{ + entity = Transaction{ ID: 1, BuyerID: 2, Status: "SENT", @@ -506,15 +506,15 @@ func TestDocument_Scanners_withAssoc(t *testing.T) { }, }, } - doc = NewDocument(&record) + doc = NewDocument(&entity) fields = []string{"id", "user_id", "buyer.id", "buyer.name", "buyer.work_address.street", "status", "invalid_assoc.id"} scanners = []any{ - Nullable(&record.ID), - Nullable(&record.BuyerID), - Nullable(&record.Buyer.ID), - Nullable(&record.Buyer.Name), - Nullable(&record.Buyer.WorkAddress.Street), - Nullable(&record.Status), + Nullable(&entity.ID), + Nullable(&entity.BuyerID), + Nullable(&entity.Buyer.ID), + Nullable(&entity.Buyer.Name), + Nullable(&entity.Buyer.WorkAddress.Street), + Nullable(&entity.Status), &sql.RawBytes{}, } ) @@ -524,17 +524,17 @@ func TestDocument_Scanners_withAssoc(t *testing.T) { func TestDocument_Scanners_withUnitializedAssoc(t *testing.T) { var ( - record = Transaction{} - doc = NewDocument(&record) + entity = Transaction{} + doc = NewDocument(&entity) fields = []string{"id", "user_id", "buyer.id", "buyer.name", "status", "buyer.work_address.street"} result = doc.Scanners(fields) expected = []any{ - Nullable(&record.ID), - Nullable(&record.BuyerID), - Nullable(&record.Buyer.ID), - Nullable(&record.Buyer.Name), - Nullable(&record.Status), - Nullable(&record.Buyer.WorkAddress.Street), + Nullable(&entity.ID), + Nullable(&entity.BuyerID), + Nullable(&entity.Buyer.ID), + Nullable(&entity.Buyer.Name), + Nullable(&entity.Status), + Nullable(&entity.Buyer.WorkAddress.Street), } ) @@ -552,14 +552,14 @@ func TestDocument_ScannersInitPointers(t *testing.T) { *Embedded2 } var ( - record = struct { + entity = struct { *Embedded3 }{} - doc = NewDocument(&record) + doc = NewDocument(&entity) _ = doc.Scanners([]string{"id"}) ) - assert.NotNil(t, record.Embedded2) - assert.NotNil(t, record.Embedded2.Embedded1) + assert.NotNil(t, entity.Embedded2) + assert.NotNil(t, entity.Embedded2.Embedded1) } func TestDocument_Slice(t *testing.T) { @@ -578,7 +578,7 @@ func TestDocument_Slice(t *testing.T) { func TestDocument_Association(t *testing.T) { tests := []struct { name string - record any + entity any belongsTo []string hasOne []string hasMany []string @@ -586,38 +586,38 @@ func TestDocument_Association(t *testing.T) { }{ { name: "User", - record: &User{}, + entity: &User{}, hasOne: []string{"address", "work_address"}, hasMany: []string{"transactions", "user_roles", "emails", "roles", "follows", "followeds", "followings", "followers"}, }, { name: "User Cached", - record: &User{}, + entity: &User{}, hasOne: []string{"address", "work_address"}, hasMany: []string{"transactions", "user_roles", "emails", "roles", "follows", "followeds", "followings", "followers"}, }, { name: "Transaction", - record: &Transaction{}, + entity: &Transaction{}, belongsTo: []string{"buyer", "address"}, hasMany: []string{"histories"}, preload: []string{"buyer"}, }, { name: "Address", - record: &Address{}, + entity: &Address{}, belongsTo: []string{"user"}, }, { name: "Item", - record: &Item{}, + entity: &Item{}, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { var ( - doc = NewDocument(test.record) + doc = NewDocument(test.entity) ) assert.Equal(t, test.belongsTo, doc.BelongsTo()) @@ -632,11 +632,11 @@ func TestDocument_AssociationEmbedded(t *testing.T) { var ( testHasOne = []string{"user_address", "user_work_address"} testHasMany = []string{"user_transactions", "user_user_roles", "user_emails", "user_roles", "user_follows", "user_followeds", "user_followings", "user_followers"} - record = struct { + entity = struct { User `db:"user_"` Score float32 }{} - doc = NewDocument(&record) + doc = NewDocument(&entity) ) assert.Equal(t, testHasOne, doc.HasOne()) @@ -655,45 +655,45 @@ func TestDocument_Association_notFOund(t *testing.T) { func TestDocument(t *testing.T) { tests := []struct { - record any + entity any panics bool }{ { - record: &User{}, + entity: &User{}, }, { - record: NewDocument(&User{}), + entity: NewDocument(&User{}), }, { - record: reflect.ValueOf(&User{}), + entity: reflect.ValueOf(&User{}), }, { - record: reflect.ValueOf(User{}), + entity: reflect.ValueOf(User{}), panics: true, }, { - record: reflect.ValueOf(&[]User{}), + entity: reflect.ValueOf(&[]User{}), panics: true, }, { - record: reflect.TypeOf(&User{}), + entity: reflect.TypeOf(&User{}), panics: true, }, { - record: nil, + entity: nil, panics: true, }, } for _, test := range tests { - t.Run(fmt.Sprintf("%T", test.record), func(t *testing.T) { + t.Run(fmt.Sprintf("%T", test.entity), func(t *testing.T) { if test.panics { assert.Panics(t, func() { - NewDocument(test.record) + NewDocument(test.entity) }) } else { assert.NotPanics(t, func() { - NewDocument(test.record) + NewDocument(test.entity) }) } }) diff --git a/entity_iterator_go.1.18.go b/entity_iterator_go.1.18.go new file mode 100644 index 00000000..9b710965 --- /dev/null +++ b/entity_iterator_go.1.18.go @@ -0,0 +1,33 @@ +//go:build go1.18 +// +build go1.18 + +package rel + +import ( + "io" +) + +// EntityIterator allows iterating through all entity in database in batch. +type EntityIterator[T any] interface { + io.Closer + Next() (T, error) +} + +type entityIterator[T any] struct { + iterator Iterator +} + +func (ei *entityIterator[T]) Close() error { + return ei.iterator.Close() +} + +func (ei *entityIterator[T]) Next() (T, error) { + var entity T + return entity, ei.iterator.Next(&entity) +} + +func newEntityIterator[T any](iterator Iterator) EntityIterator[T] { + return &entityIterator[T]{ + iterator: iterator, + } +} diff --git a/entity_iterator_go1.18_test.go b/entity_iterator_go1.18_test.go new file mode 100644 index 00000000..80f289d4 --- /dev/null +++ b/entity_iterator_go1.18_test.go @@ -0,0 +1,46 @@ +//go:build go1.18 +// +build go1.18 + +package rel + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +type testIterator struct { + mock.Mock +} + +func (ti *testIterator) Close() error { + return ti.Called().Error(0) +} + +func (ti *testIterator) Next(entity any) error { + return ti.Called(entity).Error(0) +} + +func TestEntityRepository_Close(t *testing.T) { + var ( + iterator = &testIterator{} + entityIterator = newEntityIterator[User](iterator) + ) + + iterator.On("Close").Return(nil) + assert.Nil(t, entityIterator.Close()) + iterator.AssertExpectations(t) +} + +func TestEntityRepository_Next(t *testing.T) { + var ( + iterator = &testIterator{} + entityIterator = newEntityIterator[User](iterator) + ) + + iterator.On("Next", mock.Anything).Return(nil) + _, err := entityIterator.Next() + assert.Nil(t, err) + iterator.AssertExpectations(t) +} diff --git a/entity_repository_go1.18.go b/entity_repository_go1.18.go new file mode 100644 index 00000000..6de5f613 --- /dev/null +++ b/entity_repository_go1.18.go @@ -0,0 +1,275 @@ +//go:build go1.18 +// +build go1.18 + +package rel + +import ( + "context" + "reflect" +) + +type EntityRepository[T any] interface { + // Repository returns base Repository wrapped by this EntityRepository. + Repository() Repository + + // Iterate through a collection of entities from database in batches. + // This function returns iterator that can be used to loop all entities. + // Limit, Offset and Sort query is automatically ignored. + Iterate(ctx context.Context, query Query, option ...IteratorOption) EntityIterator[T] + + // Aggregate over the given field. + // Supported aggregate: count, sum, avg, max, min. + // Any select, group, offset, limit and sort query will be ignored automatically. + // If complex aggregation is needed, consider using All instead. + Aggregate(ctx context.Context, aggregate string, field string, queriers ...Querier) (int, error) + + // MustAggregate over the given field. + // Supported aggregate: count, sum, avg, max, min. + // Any select, group, offset, limit and sort query will be ignored automatically. + // If complex aggregation is needed, consider using All instead. + // It'll panic if any error eccured. + MustAggregate(ctx context.Context, aggregate string, field string, queriers ...Querier) int + + // Count entities that match the query. + Count(ctx context.Context, queriers ...Querier) (int, error) + + // MustCount entities that match the query. + // It'll panic if any error eccured. + MustCount(ctx context.Context, queriers ...Querier) int + + // Find a entity that match the query. + // If no result found, it'll return not found error. + Find(ctx context.Context, queriers ...Querier) (T, error) + + // MustFind a entity that match the query. + // If no result found, it'll panic. + MustFind(ctx context.Context, queriers ...Querier) T + + // FindAll entities that match the query. + FindAll(ctx context.Context, queriers ...Querier) ([]T, error) + + // MustFindAll entities that match the query. + // It'll panic if any error eccured. + MustFindAll(ctx context.Context, queriers ...Querier) []T + + // FindAndCountAll entities that match the query. + // This is a convenient method that combines FindAll and Count. It's useful when dealing with queries related to pagination. + // Limit and Offset property will be ignored when performing count query. + FindAndCountAll(ctx context.Context, queriers ...Querier) ([]T, int, error) + + // MustFindAndCountAll entities that match the query. + // This is a convenient method that combines FindAll and Count. It's useful when dealing with queries related to pagination. + // Limit and Offset property will be ignored when performing count query. + // It'll panic if any error eccured. + MustFindAndCountAll(ctx context.Context, queriers ...Querier) ([]T, int) + + // Insert a entity to database. + Insert(ctx context.Context, entity *T, mutators ...Mutator) error + + // MustInsert an entity to database. + // It'll panic if any error occurred. + MustInsert(ctx context.Context, entity *T, mutators ...Mutator) + + // InsertAll entities. + // Does not supports application cascade insert. + InsertAll(ctx context.Context, entities *[]T, mutators ...Mutator) error + + // MustInsertAll entities. + // It'll panic if any error occurred. + // Does not supports application cascade insert. + MustInsertAll(ctx context.Context, entities *[]T, mutators ...Mutator) + + // Update a entity in database. + // It'll panic if any error occurred. + Update(ctx context.Context, entity *T, mutators ...Mutator) error + + // MustUpdate a entity in database. + // It'll panic if any error occurred. + MustUpdate(ctx context.Context, entity *T, mutators ...Mutator) + + // Delete a entity. + Delete(ctx context.Context, entity *T, mutators ...Mutator) error + + // MustDelete a entity. + // It'll panic if any error eccured. + MustDelete(ctx context.Context, entity *T, mutators ...Mutator) + + // DeleteAll entities. + // Does not supports application cascade delete. + DeleteAll(ctx context.Context, entities *[]T) error + + // MustDeleteAll entities. + // It'll panic if any error occurred. + // Does not supports application cascade delete. + MustDeleteAll(ctx context.Context, entities *[]T) + + // Preload association with given query. + // If association is already loaded, this will do nothing. + // To force preloading even though association is already loaeded, add `Reload(true)` as query. + Preload(ctx context.Context, entity *T, field string, queriers ...Querier) error + + // MustPreload association with given query. + // It'll panic if any error occurred. + MustPreload(ctx context.Context, entity *T, field string, queriers ...Querier) + + // Preload association with given query. + // If association is already loaded, this will do nothing. + // To force preloading even though association is already loaeded, add `Reload(true)` as query. + PreloadAll(ctx context.Context, entities *[]T, field string, queriers ...Querier) error + + // MustPreload association with given query. + // It'll panic if any error occurred. + MustPreloadAll(ctx context.Context, entities *[]T, field string, queriers ...Querier) + + // Transaction performs transaction with given function argument. + // Transaction scope/connection is automatically passed using context. + Transaction(ctx context.Context, fn func(ctx context.Context) error) error +} + +type entityRepository[T any] struct { + repository Repository +} + +func (er entityRepository[T]) Repository() Repository { + return er.repository +} + +func (er entityRepository[T]) Iterate(ctx context.Context, query Query, option ...IteratorOption) EntityIterator[T] { + if query.Table == "" { + var entity T + query.Table = getDocumentMeta(reflect.TypeOf(entity), true).Table() + } + + return newEntityIterator[T](er.repository.Iterate(ctx, query, option...)) +} + +func (er entityRepository[T]) Aggregate(ctx context.Context, aggregate string, field string, queriers ...Querier) (int, error) { + var ( + entity T + documentMeta = getDocumentMeta(reflect.TypeOf(entity), true) + query = Build(documentMeta.table, queriers...) + ) + + return er.repository.Aggregate(ctx, query, aggregate, field) +} + +func (er entityRepository[T]) MustAggregate(ctx context.Context, aggregate string, field string, queriers ...Querier) int { + result, err := er.Aggregate(ctx, aggregate, field, queriers...) + must(err) + return result +} + +func (er entityRepository[T]) Count(ctx context.Context, queriers ...Querier) (int, error) { + var ( + entity T + documentMeta = getDocumentMeta(reflect.TypeOf(entity), true) + ) + + return er.repository.Count(ctx, documentMeta.Table(), queriers...) +} + +func (er entityRepository[T]) MustCount(ctx context.Context, queriers ...Querier) int { + result, err := er.Count(ctx, queriers...) + must(err) + return result +} + +func (er entityRepository[T]) Find(ctx context.Context, queriers ...Querier) (T, error) { + var entity T + return entity, er.repository.Find(ctx, &entity, queriers...) +} + +func (er entityRepository[T]) MustFind(ctx context.Context, queriers ...Querier) T { + entity, err := er.Find(ctx, queriers...) + must(err) + return entity +} + +func (er entityRepository[T]) FindAll(ctx context.Context, queriers ...Querier) ([]T, error) { + var entities []T + return entities, er.repository.FindAll(ctx, &entities, queriers...) +} + +func (er entityRepository[T]) MustFindAll(ctx context.Context, queriers ...Querier) []T { + entities, err := er.FindAll(ctx, queriers...) + must(err) + return entities +} + +func (er entityRepository[T]) FindAndCountAll(ctx context.Context, queriers ...Querier) ([]T, int, error) { + var entities []T + count, err := er.repository.FindAndCountAll(ctx, &entities, queriers...) + return entities, count, err +} + +func (er entityRepository[T]) MustFindAndCountAll(ctx context.Context, queriers ...Querier) ([]T, int) { + entities, count, err := er.FindAndCountAll(ctx, queriers...) + must(err) + return entities, count +} + +func (er entityRepository[T]) Insert(ctx context.Context, entity *T, mutators ...Mutator) error { + return er.repository.Insert(ctx, entity, mutators...) +} + +func (er entityRepository[T]) MustInsert(ctx context.Context, entity *T, mutators ...Mutator) { + er.repository.MustInsert(ctx, entity, mutators...) +} + +func (er entityRepository[T]) InsertAll(ctx context.Context, entities *[]T, mutators ...Mutator) error { + return er.repository.InsertAll(ctx, entities, mutators...) +} + +func (er entityRepository[T]) MustInsertAll(ctx context.Context, entities *[]T, mutators ...Mutator) { + er.repository.MustInsertAll(ctx, entities, mutators...) +} + +func (er entityRepository[T]) Update(ctx context.Context, entity *T, mutators ...Mutator) error { + return er.repository.Update(ctx, entity, mutators...) +} + +func (er entityRepository[T]) MustUpdate(ctx context.Context, entity *T, mutators ...Mutator) { + er.repository.MustUpdate(ctx, entity, mutators...) +} + +func (er entityRepository[T]) Delete(ctx context.Context, entity *T, mutators ...Mutator) error { + return er.repository.Delete(ctx, entity, mutators...) +} + +func (er entityRepository[T]) MustDelete(ctx context.Context, entity *T, mutators ...Mutator) { + er.repository.MustDelete(ctx, entity, mutators...) +} + +func (er entityRepository[T]) DeleteAll(ctx context.Context, entities *[]T) error { + return er.repository.DeleteAll(ctx, entities) +} + +func (er entityRepository[T]) MustDeleteAll(ctx context.Context, entities *[]T) { + er.repository.MustDeleteAll(ctx, entities) +} + +func (er entityRepository[T]) Preload(ctx context.Context, entity *T, field string, queriers ...Querier) error { + return er.repository.Preload(ctx, entity, field, queriers...) +} + +func (er entityRepository[T]) MustPreload(ctx context.Context, entity *T, field string, queriers ...Querier) { + er.repository.MustPreload(ctx, entity, field, queriers...) +} + +func (er entityRepository[T]) PreloadAll(ctx context.Context, entities *[]T, field string, queriers ...Querier) error { + return er.repository.Preload(ctx, entities, field, queriers...) +} + +func (er entityRepository[T]) MustPreloadAll(ctx context.Context, entities *[]T, field string, queriers ...Querier) { + er.repository.MustPreload(ctx, entities, field, queriers...) +} + +func (er entityRepository[T]) Transaction(ctx context.Context, fn func(ctx context.Context) error) error { + return er.repository.Transaction(ctx, fn) +} + +func NewEntityRepository[T any](repository Repository) EntityRepository[T] { + return entityRepository[T]{ + repository: repository, + } +} diff --git a/entity_repository_go1.18_test.go b/entity_repository_go1.18_test.go new file mode 100644 index 00000000..a0f1c7ba --- /dev/null +++ b/entity_repository_go1.18_test.go @@ -0,0 +1,572 @@ +//go:build go1.18 +// +build go1.18 + +package rel + +import ( + "context" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/mock" +) + +type testRepository struct { + mock.Mock +} + +func (tr *testRepository) Adapter(ctx context.Context) Adapter { + return nil +} + +func (tr *testRepository) Instrumentation(instrumenter Instrumenter) {} + +func (tr *testRepository) Ping(ctx context.Context) error { + return nil +} + +func (tr *testRepository) Iterate(ctx context.Context, query Query, option ...IteratorOption) Iterator { + args := tr.Called(query, option) + return args.Get(0).(Iterator) +} + +func (tr *testRepository) Aggregate(ctx context.Context, query Query, aggregate string, field string) (int, error) { + args := tr.Called(query, aggregate, field) + return args.Int(0), args.Error(1) +} + +func (tr *testRepository) MustAggregate(ctx context.Context, query Query, aggregate string, field string) int { + args := tr.Called(query, aggregate, query, field) + return args.Int(0) +} + +func (tr *testRepository) Count(ctx context.Context, collection string, queriers ...Querier) (int, error) { + args := tr.Called(collection, queriers) + return args.Int(0), args.Error(1) +} + +func (tr *testRepository) MustCount(ctx context.Context, collection string, queriers ...Querier) int { + args := tr.Called(collection, queriers) + return args.Int(0) +} + +func (tr *testRepository) Find(ctx context.Context, entity any, queriers ...Querier) error { + args := tr.Called(entity, queriers) + return args.Error(0) +} + +func (tr *testRepository) MustFind(ctx context.Context, entity any, queriers ...Querier) { + tr.Called(entity, queriers) +} + +func (tr *testRepository) FindAll(ctx context.Context, entities any, queriers ...Querier) error { + args := tr.Called(entities, queriers) + return args.Error(0) +} + +func (tr *testRepository) MustFindAll(ctx context.Context, entities any, queriers ...Querier) { + tr.Called(entities, queriers) +} + +func (tr *testRepository) FindAndCountAll(ctx context.Context, entities any, queriers ...Querier) (int, error) { + args := tr.Called(entities, queriers) + return args.Int(0), args.Error(1) +} + +func (tr *testRepository) MustFindAndCountAll(ctx context.Context, entities any, queriers ...Querier) int { + args := tr.Called(entities, queriers) + return args.Int(0) +} + +func (tr *testRepository) Insert(ctx context.Context, entity any, mutators ...Mutator) error { + args := tr.Called(entity, mutators) + return args.Error(0) +} + +func (tr *testRepository) MustInsert(ctx context.Context, entity any, mutators ...Mutator) { + tr.Called(entity, mutators) +} + +func (tr *testRepository) InsertAll(ctx context.Context, entities any, mutators ...Mutator) error { + args := tr.Called(entities, mutators) + return args.Error(0) +} + +func (tr *testRepository) MustInsertAll(ctx context.Context, entities any, mutators ...Mutator) { + tr.Called(entities, mutators) +} + +func (tr *testRepository) Update(ctx context.Context, entity any, mutators ...Mutator) error { + args := tr.Called(entity, mutators) + return args.Error(0) +} + +func (tr *testRepository) MustUpdate(ctx context.Context, entity any, mutators ...Mutator) { + tr.Called(entity, mutators) +} + +func (tr *testRepository) UpdateAny(ctx context.Context, query Query, mutates ...Mutate) (int, error) { + args := tr.Called(query, mutates) + return args.Int(0), args.Error(1) +} + +func (tr *testRepository) MustUpdateAny(ctx context.Context, query Query, mutates ...Mutate) int { + args := tr.Called(query, mutates) + return args.Int(0) +} + +func (tr *testRepository) Delete(ctx context.Context, entity any, mutators ...Mutator) error { + args := tr.Called(entity, mutators) + return args.Error(0) +} + +func (tr *testRepository) MustDelete(ctx context.Context, entity any, mutators ...Mutator) { + tr.Called(entity, mutators) +} + +func (tr *testRepository) DeleteAll(ctx context.Context, entities any) error { + args := tr.Called(entities) + return args.Error(0) +} + +func (tr *testRepository) MustDeleteAll(ctx context.Context, entities any) { + tr.Called(entities) +} + +func (tr *testRepository) DeleteAny(ctx context.Context, query Query) (int, error) { + args := tr.Called(query) + return args.Int(0), args.Error(1) +} + +func (tr *testRepository) MustDeleteAny(ctx context.Context, query Query) int { + args := tr.Called(query) + return args.Int(0) +} + +func (tr *testRepository) Preload(ctx context.Context, entities any, field string, queriers ...Querier) error { + args := tr.Called(entities, field, queriers) + return args.Error(0) +} + +func (tr *testRepository) MustPreload(ctx context.Context, entities any, field string, queriers ...Querier) { + tr.Called(entities, field, queriers) +} + +func (tr *testRepository) Exec(ctx context.Context, statement string, arg ...any) (int, int, error) { + args := tr.Called(statement, statement, arg) + return args.Int(0), args.Int(1), args.Error(2) +} + +func (tr *testRepository) MustExec(ctx context.Context, statement string, arg ...any) (int, int) { + args := tr.Called(statement, statement, arg) + return args.Int(0), args.Int(1) +} + +func (tr *testRepository) Transaction(ctx context.Context, fn func(ctx context.Context) error) error { + tr.Called() + return fn(ctx) +} + +func TestEntityRepository_Repository(t *testing.T) { + var ( + repo = &testRepository{} + entityRepo = NewEntityRepository[User](repo) + ) + + assert.Equal(t, repo, entityRepo.Repository()) + repo.AssertExpectations(t) +} + +func TestEntityRepository_Iterate(t *testing.T) { + var ( + repo = &testRepository{} + entityRepo = NewEntityRepository[User](repo) + ) + + repo.On("Iterate", From("users").Where(Eq("status", "pending")), []IteratorOption{BatchSize(10)}). + Return(&testIterator{}) + + iterator := entityRepo.Iterate(context.TODO(), Where(Eq("status", "pending")), BatchSize(10)) + assert.NotNil(t, iterator) + + repo.AssertExpectations(t) +} + +func TestEntityRepository_Aggregate(t *testing.T) { + var ( + repo = &testRepository{} + entityRepo = NewEntityRepository[User](repo) + ) + + repo.On("Aggregate", From("users"), "max", "score").Return(1, nil) + + result, err := entityRepo.Aggregate(context.TODO(), "max", "score") + assert.Nil(t, err) + assert.Equal(t, 1, result) + + repo.AssertExpectations(t) +} + +func TestEntityRepository_MustAggregate(t *testing.T) { + var ( + repo = &testRepository{} + entityRepo = NewEntityRepository[User](repo) + ) + + repo.On("Aggregate", From("users"), "max", "score").Return(1, nil) + + result := entityRepo.MustAggregate(context.TODO(), "max", "score") + assert.Equal(t, 1, result) + + repo.AssertExpectations(t) +} + +func TestEntityRepository_Count(t *testing.T) { + var ( + repo = &testRepository{} + entityRepo = NewEntityRepository[User](repo) + ) + + repo.On("Count", "users", []Querier(nil)).Return(1, nil) + + result, err := entityRepo.Count(context.TODO()) + assert.Nil(t, err) + assert.Equal(t, 1, result) + + repo.AssertExpectations(t) +} + +func TestEntityRepository_MustCount(t *testing.T) { + var ( + repo = &testRepository{} + entityRepo = NewEntityRepository[User](repo) + ) + + repo.On("Count", "users", []Querier(nil)).Return(1, nil) + + result := entityRepo.MustCount(context.TODO()) + assert.Equal(t, 1, result) + + repo.AssertExpectations(t) +} + +func TestEntityRepository_Find(t *testing.T) { + var ( + user User + repo = &testRepository{} + entityRepo = NewEntityRepository[User](repo) + query = From("users").Limit(1) + ) + + repo.On("Find", &user, []Querier{query}).Return(nil) + + result, err := entityRepo.Find(context.TODO(), query) + assert.Nil(t, err) + assert.Equal(t, user, result) + + repo.AssertExpectations(t) +} + +func TestEntityRepository_MustFind(t *testing.T) { + var ( + user User + repo = &testRepository{} + entityRepo = NewEntityRepository[User](repo) + query = From("users").Limit(1) + ) + + repo.On("Find", &user, []Querier{query}).Return(nil) + + result := entityRepo.MustFind(context.TODO(), query) + assert.Equal(t, user, result) + + repo.AssertExpectations(t) +} + +func TestEntityRepository_FindAll(t *testing.T) { + var ( + users []User + repo = &testRepository{} + entityRepo = NewEntityRepository[User](repo) + query = From("users").Limit(1) + ) + + repo.On("FindAll", &users, []Querier{query}).Return(nil) + + result, err := entityRepo.FindAll(context.TODO(), query) + assert.Nil(t, err) + assert.Equal(t, users, result) + + repo.AssertExpectations(t) +} + +func TestEntityRepository_MustFindAll(t *testing.T) { + var ( + users []User + repo = &testRepository{} + entityRepo = NewEntityRepository[User](repo) + query = From("users").Limit(1) + ) + + repo.On("FindAll", &users, []Querier{query}).Return(nil) + + result := entityRepo.MustFindAll(context.TODO(), query) + assert.Equal(t, users, result) + + repo.AssertExpectations(t) +} + +func TestEntityRepository_FindAndCountAll(t *testing.T) { + var ( + users []User + repo = &testRepository{} + entityRepo = NewEntityRepository[User](repo) + query = From("users").Limit(1) + ) + + repo.On("FindAndCountAll", &users, []Querier{query}).Return(1, nil) + + result, count, err := entityRepo.FindAndCountAll(context.TODO(), query) + assert.Nil(t, err) + assert.Equal(t, 1, count) + assert.Equal(t, users, result) + + repo.AssertExpectations(t) +} + +func TestEntityRepository_MustFindAndCountAll(t *testing.T) { + var ( + users []User + repo = &testRepository{} + entityRepo = NewEntityRepository[User](repo) + query = From("users").Limit(1) + ) + + repo.On("FindAndCountAll", &users, []Querier{query}).Return(1, nil) + + result, count := entityRepo.MustFindAndCountAll(context.TODO(), query) + assert.Equal(t, 1, count) + assert.Equal(t, users, result) + + repo.AssertExpectations(t) +} + +func TestEntityRepository_Insert(t *testing.T) { + var ( + user User + repo = &testRepository{} + entityRepo = NewEntityRepository[User](repo) + ) + + repo.On("Insert", &user, []Mutator(nil)).Return(nil) + + err := entityRepo.Insert(context.TODO(), &user) + assert.Nil(t, err) + + repo.AssertExpectations(t) +} + +func TestEntityRepository_MustInsert(t *testing.T) { + var ( + user User + repo = &testRepository{} + entityRepo = NewEntityRepository[User](repo) + ) + + repo.On("MustInsert", &user, []Mutator(nil)) + + entityRepo.MustInsert(context.TODO(), &user) + + repo.AssertExpectations(t) +} + +func TestEntityRepository_InsertAll(t *testing.T) { + var ( + users []User + repo = &testRepository{} + entityRepo = NewEntityRepository[User](repo) + ) + + repo.On("InsertAll", &users, []Mutator(nil)).Return(nil) + + err := entityRepo.InsertAll(context.TODO(), &users) + assert.Nil(t, err) + + repo.AssertExpectations(t) +} + +func TestEntityRepository_MustInsertAll(t *testing.T) { + var ( + users []User + repo = &testRepository{} + entityRepo = NewEntityRepository[User](repo) + ) + + repo.On("MustInsertAll", &users, []Mutator(nil)) + + entityRepo.MustInsertAll(context.TODO(), &users) + + repo.AssertExpectations(t) +} + +func TestEntityRepository_Update(t *testing.T) { + var ( + user User + repo = &testRepository{} + entityRepo = NewEntityRepository[User](repo) + ) + + repo.On("Update", &user, []Mutator(nil)).Return(nil) + + err := entityRepo.Update(context.TODO(), &user) + assert.Nil(t, err) + + repo.AssertExpectations(t) +} + +func TestEntityRepository_MustUpdate(t *testing.T) { + var ( + user User + repo = &testRepository{} + entityRepo = NewEntityRepository[User](repo) + ) + + repo.On("MustUpdate", &user, []Mutator(nil)) + + entityRepo.MustUpdate(context.TODO(), &user) + + repo.AssertExpectations(t) +} + +func TestEntityRepository_Delete(t *testing.T) { + var ( + user User + repo = &testRepository{} + entityRepo = NewEntityRepository[User](repo) + ) + + repo.On("Delete", &user, []Mutator(nil)).Return(nil) + + err := entityRepo.Delete(context.TODO(), &user) + assert.Nil(t, err) + + repo.AssertExpectations(t) +} + +func TestEntityRepository_MustDelete(t *testing.T) { + var ( + user User + repo = &testRepository{} + entityRepo = NewEntityRepository[User](repo) + ) + + repo.On("MustDelete", &user, []Mutator(nil)) + + entityRepo.MustDelete(context.TODO(), &user) + + repo.AssertExpectations(t) +} + +func TestEntityRepository_DeleteAll(t *testing.T) { + var ( + users []User + repo = &testRepository{} + entityRepo = NewEntityRepository[User](repo) + ) + + repo.On("DeleteAll", &users).Return(nil) + + err := entityRepo.DeleteAll(context.TODO(), &users) + assert.Nil(t, err) + + repo.AssertExpectations(t) +} + +func TestEntityRepository_MustDeleteAll(t *testing.T) { + var ( + users []User + repo = &testRepository{} + entityRepo = NewEntityRepository[User](repo) + ) + + repo.On("MustDeleteAll", &users) + + entityRepo.MustDeleteAll(context.TODO(), &users) + + repo.AssertExpectations(t) +} + +func TestEntityRepository_Preload(t *testing.T) { + var ( + user User + repo = &testRepository{} + entityRepo = NewEntityRepository[User](repo) + ) + + repo.On("Preload", &user, "address", []Querier(nil)).Return(nil) + + err := entityRepo.Preload(context.TODO(), &user, "address") + assert.Nil(t, err) + + repo.AssertExpectations(t) +} + +func TestEntityRepository_MustPreload(t *testing.T) { + var ( + user User + repo = &testRepository{} + entityRepo = NewEntityRepository[User](repo) + ) + + repo.On("MustPreload", &user, "address", []Querier(nil)) + + entityRepo.MustPreload(context.TODO(), &user, "address") + + repo.AssertExpectations(t) +} + +func TestEntityRepository_PreloadAll(t *testing.T) { + var ( + users []User + repo = &testRepository{} + entityRepo = NewEntityRepository[User](repo) + ) + + repo.On("Preload", &users, "address", []Querier(nil)).Return(nil) + + err := entityRepo.PreloadAll(context.TODO(), &users, "address") + assert.Nil(t, err) + + repo.AssertExpectations(t) +} + +func TestEntityRepository_MustPreloadAll(t *testing.T) { + var ( + users []User + repo = &testRepository{} + entityRepo = NewEntityRepository[User](repo) + ) + + repo.On("MustPreload", &users, "address", []Querier(nil)) + + entityRepo.MustPreloadAll(context.TODO(), &users, "address") + + repo.AssertExpectations(t) +} + +func TestEntityRepository_Transaction(t *testing.T) { + var ( + repo = &testRepository{} + entityRepo = NewEntityRepository[User](repo) + ) + + repo.On("Transaction") + + err := entityRepo.Transaction(context.TODO(), func(ctx context.Context) error { + return nil + }) + + assert.Nil(t, err) + + repo.AssertExpectations(t) +} diff --git a/errors.go b/errors.go index d58e339c..8605bd8d 100644 --- a/errors.go +++ b/errors.go @@ -6,7 +6,7 @@ import ( ) var ( - // ErrNotFound returned when records not found. + // ErrNotFound returned when entities not found. ErrNotFound = NotFoundError{} // ErrCheckConstraint is an auxiliary variable for error handling. @@ -35,7 +35,7 @@ type NotFoundError struct{} // Error message. func (nfe NotFoundError) Error() string { - return "Record not found" + return "entity not found" } // Is returns true when target error is sql.ErrNoRows. diff --git a/errors_test.go b/errors_test.go index 87ae82ad..145701ad 100644 --- a/errors_test.go +++ b/errors_test.go @@ -9,7 +9,7 @@ import ( ) func TestNoResultError(t *testing.T) { - assert.Equal(t, "Record not found", NotFoundError{}.Error()) + assert.Equal(t, "entity not found", NotFoundError{}.Error()) } func TestNotFoundError_Is(t *testing.T) { diff --git a/go.mod b/go.mod index b726573a..3cdc4493 100644 --- a/go.mod +++ b/go.mod @@ -4,12 +4,15 @@ require ( github.com/jinzhu/inflection v1.0.0 github.com/serenize/snaker v0.0.0-20201027110005-a7ad2135616e github.com/stretchr/testify v1.8.0 - github.com/subosito/gotenv v1.4.1 ) require ( + github.com/davecgh/go-spew v1.1.1 // indirect github.com/onsi/ginkgo v1.15.0 // indirect github.com/onsi/gomega v1.10.5 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/stretchr/objx v0.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect ) -go 1.15 +go 1.19 diff --git a/go.sum b/go.sum index ab21e914..75735fc1 100644 --- a/go.sum +++ b/go.sum @@ -10,11 +10,9 @@ github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:x github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= -github.com/golang/protobuf v1.4.2 h1:+Z5KGCizgyZCbGh1KZqA0fcLLkwbsjIzS4aV2v7wJX0= github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0 h1:xsAVV57WRhGj6kEIi8ReJzQlHHqcBYCElAvkovg3B/4= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= @@ -37,11 +35,8 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+ github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.7.5/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= -github.com/subosito/gotenv v1.4.1 h1:jyEFiXpy21Wm81FBN71l9VoMMV8H8jG+qIK3GCpY6Qs= -github.com/subosito/gotenv v1.4.1/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= @@ -76,14 +71,12 @@ golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4f golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= -golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= -google.golang.org/protobuf v1.23.0 h1:4MY060fB1DLGMB/7MBTLnwQUY6+F09GEiz6SsrNqyzM= google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/iterator.go b/iterator.go index c798712e..dcb765db 100644 --- a/iterator.go +++ b/iterator.go @@ -6,10 +6,10 @@ import ( "io" ) -// Iterator allows iterating through all record in database in batch. +// Iterator allows iterating through all entity in database in batch. type Iterator interface { io.Closer - Next(record any) error + Next(entity any) error } // IteratorOption is used to configure iteration behaviour, such as batch size, start id and finish id. @@ -87,9 +87,9 @@ func (i *iterator) Close() error { return nil } -func (i *iterator) Next(record any) error { +func (i *iterator) Next(entity any) error { if i.current%i.batchSize == 0 { - if err := i.fetch(i.ctx, record); err != nil { + if err := i.fetch(i.ctx, entity); err != nil { return err } } @@ -99,7 +99,7 @@ func (i *iterator) Next(record any) error { } var ( - doc = NewDocument(record) + doc = NewDocument(entity) scanners = doc.Scanners(i.fields) ) @@ -107,9 +107,9 @@ func (i *iterator) Next(record any) error { return i.cursor.Scan(scanners...) } -func (i *iterator) fetch(ctx context.Context, record any) error { +func (i *iterator) fetch(ctx context.Context, entity any) error { if i.current == 0 { - i.init(record) + i.init(entity) } else { i.cursor.Close() } @@ -132,9 +132,9 @@ func (i *iterator) fetch(ctx context.Context, record any) error { return nil } -func (i *iterator) init(record any) { +func (i *iterator) init(entity any) { var ( - doc = NewDocument(record) + doc = NewDocument(entity) ) if i.query.Table == "" { diff --git a/iterator_test.go b/iterator_test.go index 1ef5c3e8..2339b9f6 100644 --- a/iterator_test.go +++ b/iterator_test.go @@ -27,7 +27,7 @@ func TestIterator(t *testing.T) { adapter.On("Query", query.Offset(5)).Return(cur2, nil).Once() adapter.On("Query", query.Offset(10)).Return(cur3, nil).Once() - recordsCount := 0 + entitiesCount := 0 for { if err := it.Next(&user); err == io.EOF { break @@ -36,11 +36,11 @@ func TestIterator(t *testing.T) { } assert.NotEqual(t, 0, user.ID) - recordsCount++ + entitiesCount++ } it.Close() - assert.Equal(t, 13, recordsCount) + assert.Equal(t, 13, entitiesCount) // the last next is not called because it's already refetched. // call here to make expectation pass. diff --git a/map.go b/map.go index 617a8554..dd6da9b3 100644 --- a/map.go +++ b/map.go @@ -129,7 +129,7 @@ func applyMaps(maps []Map, assoc Association) ([]Mutation, []any) { // update pID, ok := pIndex[pChange] if !ok { - panic("rel: cannot update has many assoc that is not loaded or doesn't belong to this record") + panic("rel: cannot update has many assoc that is not loaded or doesn't belong to this entity") } if pID != curr { diff --git a/map_test.go b/map_test.go index fa69f576..598f388f 100644 --- a/map_test.go +++ b/map_test.go @@ -193,7 +193,7 @@ func TestMap_hasManyUpdateNotLoaded(t *testing.T) { } ) - assert.PanicsWithValue(t, "rel: cannot update has many assoc that is not loaded or doesn't belong to this record", func() { + assert.PanicsWithValue(t, "rel: cannot update has many assoc that is not loaded or doesn't belong to this entity", func() { Apply(doc, data) }) } diff --git a/mutation.go b/mutation.go index 9b967df1..adbe79fd 100644 --- a/mutation.go +++ b/mutation.go @@ -5,7 +5,7 @@ import ( "reflect" ) -// Mutator is interface for a record mutator. +// Mutator is interface for a entity mutator. type Mutator interface { Apply(doc *Document, mutation *Mutation) } diff --git a/mutation_test.go b/mutation_test.go index e5123ad9..73c0cf68 100644 --- a/mutation_test.go +++ b/mutation_test.go @@ -7,7 +7,7 @@ import ( "github.com/stretchr/testify/assert" ) -type TestRecord struct { +type Testentity struct { Field1 string `db:",primary"` Field2 bool Field3 *string @@ -17,8 +17,8 @@ type TestRecord struct { func TestApply(t *testing.T) { var ( - record = TestRecord{} - doc = NewDocument(&record) + entity = Testentity{} + doc = NewDocument(&entity) mutators = []Mutator{ Set("field1", "string"), Set("field2", true), @@ -42,19 +42,19 @@ func TestApply(t *testing.T) { ) assert.Equal(t, mutation, Apply(doc, mutators...)) - assert.Equal(t, "string", record.Field1) - assert.Equal(t, true, record.Field2) - assert.Equal(t, "string pointer", *record.Field3) + assert.Equal(t, "string", entity.Field1) + assert.Equal(t, true, entity.Field2) + assert.Equal(t, "string pointer", *entity.Field3) // non set op won't update the struct - assert.Equal(t, 0, record.Field4) - assert.Equal(t, 0, record.Field5) + assert.Equal(t, 0, entity.Field4) + assert.Equal(t, 0, entity.Field5) } func TestApply_Options(t *testing.T) { var ( - record = TestRecord{} - doc = NewDocument(&record) + entity = Testentity{} + doc = NewDocument(&entity) mutators = []Mutator{ Unscoped(true), Reload(true), @@ -80,44 +80,44 @@ func TestApply_Options(t *testing.T) { func TestApplyMutation_setValueError(t *testing.T) { var ( - record = TestRecord{} - doc = NewDocument(&record) + entity = Testentity{} + doc = NewDocument(&entity) ) assert.Panics(t, func() { Apply(doc, Set("field1", 1)) }) - assert.Equal(t, "", record.Field1) + assert.Equal(t, "", entity.Field1) } func TestApplyMutation_incValueError(t *testing.T) { var ( - record = TestRecord{} - doc = NewDocument(&record) + entity = Testentity{} + doc = NewDocument(&entity) ) assert.Panics(t, func() { Apply(doc, Inc("field1")) }) - assert.Equal(t, "", record.Field1) + assert.Equal(t, "", entity.Field1) } func TestApplyMutation_unknownFieldValueError(t *testing.T) { var ( - record = TestRecord{} - doc = NewDocument(&record) + entity = Testentity{} + doc = NewDocument(&entity) ) assert.Panics(t, func() { Apply(doc, Dec("field0")) }) - assert.Equal(t, "", record.Field1) + assert.Equal(t, "", entity.Field1) } func TestApplyMutation_Reload(t *testing.T) { var ( - record = TestRecord{} - doc = NewDocument(&record) + entity = Testentity{} + doc = NewDocument(&entity) mutators = []Mutator{ Set("field1", "string"), Reload(true), @@ -132,13 +132,13 @@ func TestApplyMutation_Reload(t *testing.T) { ) assert.Equal(t, mutation, Apply(doc, mutators...)) - assert.Equal(t, "string", record.Field1) + assert.Equal(t, "string", entity.Field1) } func TestApplyMutation_Cascade(t *testing.T) { var ( - record = TestRecord{} - doc = NewDocument(&record) + entity = Testentity{} + doc = NewDocument(&entity) mutators = []Mutator{ Set("field1", "string"), Cascade(false), @@ -152,7 +152,7 @@ func TestApplyMutation_Cascade(t *testing.T) { ) assert.Equal(t, mutation, Apply(doc, mutators...)) - assert.Equal(t, "string", record.Field1) + assert.Equal(t, "string", entity.Field1) } func TestMutator_String(t *testing.T) { diff --git a/repository.go b/repository.go index 39327cbe..af4da9cf 100644 --- a/repository.go +++ b/repository.go @@ -19,8 +19,8 @@ type Repository interface { // Ping database. Ping(ctx context.Context) error - // Iterate through a collection of records from database in batches. - // This function returns iterator that can be used to loop all records. + // Iterate through a collection of entities from database in batches. + // This function returns iterator that can be used to loop all entities. // Limit, Offset and Sort query is automatically ignored. Iterate(ctx context.Context, query Query, option ...IteratorOption) Iterator @@ -37,107 +37,107 @@ type Repository interface { // It'll panic if any error eccured. MustAggregate(ctx context.Context, query Query, aggregate string, field string) int - // Count records that match the query. + // Count entities that match the query. Count(ctx context.Context, collection string, queriers ...Querier) (int, error) - // MustCount records that match the query. + // MustCount entities that match the query. // It'll panic if any error eccured. MustCount(ctx context.Context, collection string, queriers ...Querier) int - // Find a record that match the query. + // Find a entity that match the query. // If no result found, it'll return not found error. - Find(ctx context.Context, record any, queriers ...Querier) error + Find(ctx context.Context, entity any, queriers ...Querier) error - // MustFind a record that match the query. + // MustFind a entity that match the query. // If no result found, it'll panic. - MustFind(ctx context.Context, record any, queriers ...Querier) + MustFind(ctx context.Context, entity any, queriers ...Querier) - // FindAll records that match the query. - FindAll(ctx context.Context, records any, queriers ...Querier) error + // FindAll entities that match the query. + FindAll(ctx context.Context, entities any, queriers ...Querier) error - // MustFindAll records that match the query. + // MustFindAll entities that match the query. // It'll panic if any error eccured. - MustFindAll(ctx context.Context, records any, queriers ...Querier) + MustFindAll(ctx context.Context, entities any, queriers ...Querier) - // FindAndCountAll records that match the query. + // FindAndCountAll entities that match the query. // This is a convenient method that combines FindAll and Count. It's useful when dealing with queries related to pagination. // Limit and Offset property will be ignored when performing count query. - FindAndCountAll(ctx context.Context, records any, queriers ...Querier) (int, error) + FindAndCountAll(ctx context.Context, entities any, queriers ...Querier) (int, error) - // MustFindAndCountAll records that match the query. + // MustFindAndCountAll entities that match the query. // This is a convenient method that combines FindAll and Count. It's useful when dealing with queries related to pagination. // Limit and Offset property will be ignored when performing count query. // It'll panic if any error eccured. - MustFindAndCountAll(ctx context.Context, records any, queriers ...Querier) int + MustFindAndCountAll(ctx context.Context, entities any, queriers ...Querier) int - // Insert a record to database. - Insert(ctx context.Context, record any, mutators ...Mutator) error + // Insert a entity to database. + Insert(ctx context.Context, entity any, mutators ...Mutator) error - // MustInsert an record to database. + // MustInsert an entity to database. // It'll panic if any error occurred. - MustInsert(ctx context.Context, record any, mutators ...Mutator) + MustInsert(ctx context.Context, entity any, mutators ...Mutator) - // InsertAll records. + // InsertAll entities. // Does not supports application cascade insert. - InsertAll(ctx context.Context, records any, mutators ...Mutator) error + InsertAll(ctx context.Context, entities any, mutators ...Mutator) error - // MustInsertAll records. + // MustInsertAll entities. // It'll panic if any error occurred. // Does not supports application cascade insert. - MustInsertAll(ctx context.Context, records any, mutators ...Mutator) + MustInsertAll(ctx context.Context, entities any, mutators ...Mutator) - // Update a record in database. + // Update a entity in database. // It'll panic if any error occurred. - Update(ctx context.Context, record any, mutators ...Mutator) error + Update(ctx context.Context, entity any, mutators ...Mutator) error - // MustUpdate a record in database. + // MustUpdate a entity in database. // It'll panic if any error occurred. - MustUpdate(ctx context.Context, record any, mutators ...Mutator) + MustUpdate(ctx context.Context, entity any, mutators ...Mutator) - // UpdateAny records tha match the query. - // Returns number of updated records and error. + // UpdateAny entities tha match the query. + // Returns number of updated entities and error. UpdateAny(ctx context.Context, query Query, mutates ...Mutate) (int, error) - // MustUpdateAny records that match the query. + // MustUpdateAny entities that match the query. // It'll panic if any error occurred. - // Returns number of updated records. + // Returns number of updated entities. MustUpdateAny(ctx context.Context, query Query, mutates ...Mutate) int - // Delete a record. - Delete(ctx context.Context, record any, mutators ...Mutator) error + // Delete a entity. + Delete(ctx context.Context, entity any, mutators ...Mutator) error - // MustDelete a record. + // MustDelete a entity. // It'll panic if any error eccured. - MustDelete(ctx context.Context, record any, mutators ...Mutator) + MustDelete(ctx context.Context, entity any, mutators ...Mutator) - // DeleteAll records. + // DeleteAll entities. // Does not supports application cascade delete. - DeleteAll(ctx context.Context, records any) error + DeleteAll(ctx context.Context, entities any) error - // MustDeleteAll records. + // MustDeleteAll entities. // It'll panic if any error occurred. // Does not supports application cascade delete. - MustDeleteAll(ctx context.Context, records any) + MustDeleteAll(ctx context.Context, entities any) - // DeleteAny records that match the query. - // Returns number of deleted records and error. + // DeleteAny entities that match the query. + // Returns number of deleted entities and error. DeleteAny(ctx context.Context, query Query) (int, error) - // MustDeleteAny records that match the query. + // MustDeleteAny entities that match the query. // It'll panic if any error eccured. - // Returns number of updated records. + // Returns number of updated entities. MustDeleteAny(ctx context.Context, query Query) int // Preload association with given query. // This function can accepts either a struct or a slice of structs. // If association is already loaded, this will do nothing. // To force preloading even though association is already loaeded, add `Reload(true)` as query. - Preload(ctx context.Context, records any, field string, queriers ...Querier) error + Preload(ctx context.Context, entities any, field string, queriers ...Querier) error // MustPreload association with given query. // This function can accepts either a struct or a slice of structs. // It'll panic if any error occurred. - MustPreload(ctx context.Context, records any, field string, queriers ...Querier) + MustPreload(ctx context.Context, entities any, field string, queriers ...Querier) // Exec raw statement. // Returns last inserted id, rows affected and error. @@ -179,7 +179,7 @@ func (r repository) Iterate(ctx context.Context, query Query, options ...Iterato } func (r repository) Aggregate(ctx context.Context, query Query, aggregate string, field string) (int, error) { - finish := r.instrumenter.Observe(ctx, "rel-aggregate", "aggregating records") + finish := r.instrumenter.Observe(ctx, "rel-aggregate", "aggregating entities") defer finish(nil) var ( @@ -205,7 +205,7 @@ func (r repository) MustAggregate(ctx context.Context, query Query, aggregate st } func (r repository) Count(ctx context.Context, collection string, queriers ...Querier) (int, error) { - finish := r.instrumenter.Observe(ctx, "rel-count", "aggregating records") + finish := r.instrumenter.Observe(ctx, "rel-count", "aggregating entities") defer finish(nil) var ( @@ -221,21 +221,21 @@ func (r repository) MustCount(ctx context.Context, collection string, queriers . return count } -func (r repository) Find(ctx context.Context, record any, queriers ...Querier) error { - finish := r.instrumenter.Observe(ctx, "rel-find", "finding a record") +func (r repository) Find(ctx context.Context, entity any, queriers ...Querier) error { + finish := r.instrumenter.Observe(ctx, "rel-find", "finding a entity") defer finish(nil) var ( cw = fetchContext(ctx, r.rootAdapter) - doc = NewDocument(record) + doc = NewDocument(entity) query = Build(doc.Table(), queriers...).Populate(doc.Meta()) ) return r.find(cw, doc, query) } -func (r repository) MustFind(ctx context.Context, record any, queriers ...Querier) { - must(r.Find(ctx, record, queriers...)) +func (r repository) MustFind(ctx context.Context, entity any, queriers ...Querier) { + must(r.Find(ctx, entity, queriers...)) } func (r repository) find(cw contextWrapper, doc *Document, query Query) error { @@ -245,7 +245,7 @@ func (r repository) find(cw contextWrapper, doc *Document, query Query) error { return err } - finish := r.instrumenter.Observe(cw.ctx, "rel-scan-one", "scanning a record") + finish := r.instrumenter.Observe(cw.ctx, "rel-scan-one", "scanning a entity") if err := scanOne(cur, doc); err != nil { finish(err) return err @@ -261,13 +261,13 @@ func (r repository) find(cw contextWrapper, doc *Document, query Query) error { return nil } -func (r repository) FindAll(ctx context.Context, records any, queriers ...Querier) error { - finish := r.instrumenter.Observe(ctx, "rel-find-all", "finding all records") +func (r repository) FindAll(ctx context.Context, entities any, queriers ...Querier) error { + finish := r.instrumenter.Observe(ctx, "rel-find-all", "finding all entities") defer finish(nil) var ( cw = fetchContext(ctx, r.rootAdapter) - col = NewCollection(records) + col = NewCollection(entities) query = Build(col.Table(), queriers...).Populate(col.Meta()) ) @@ -276,8 +276,8 @@ func (r repository) FindAll(ctx context.Context, records any, queriers ...Querie return r.findAll(cw, col, query) } -func (r repository) MustFindAll(ctx context.Context, records any, queriers ...Querier) { - must(r.FindAll(ctx, records, queriers...)) +func (r repository) MustFindAll(ctx context.Context, entities any, queriers ...Querier) { + must(r.FindAll(ctx, entities, queriers...)) } func (r repository) findAll(cw contextWrapper, col *Collection, query Query) error { @@ -287,7 +287,7 @@ func (r repository) findAll(cw contextWrapper, col *Collection, query Query) err return err } - finish := r.instrumenter.Observe(cw.ctx, "rel-scan-all", "scanning all records") + finish := r.instrumenter.Observe(cw.ctx, "rel-scan-all", "scanning all entities") if err := scanAll(cur, col); err != nil { finish(err) return err @@ -303,13 +303,13 @@ func (r repository) findAll(cw contextWrapper, col *Collection, query Query) err return nil } -func (r repository) FindAndCountAll(ctx context.Context, records any, queriers ...Querier) (int, error) { - finish := r.instrumenter.Observe(ctx, "rel-find-and-count-all", "finding all records") +func (r repository) FindAndCountAll(ctx context.Context, entities any, queriers ...Querier) (int, error) { + finish := r.instrumenter.Observe(ctx, "rel-find-and-count-all", "finding all entities") defer finish(nil) var ( cw = fetchContext(ctx, r.rootAdapter) - col = NewCollection(records) + col = NewCollection(entities) query = Build(col.Table(), queriers...).Populate(col.Meta()) ) @@ -322,24 +322,24 @@ func (r repository) FindAndCountAll(ctx context.Context, records any, queriers . return r.aggregate(cw, r.withDefaultScope(col.meta, query, false), "count", "*") } -func (r repository) MustFindAndCountAll(ctx context.Context, records any, queriers ...Querier) int { - count, err := r.FindAndCountAll(ctx, records, queriers...) +func (r repository) MustFindAndCountAll(ctx context.Context, entities any, queriers ...Querier) int { + count, err := r.FindAndCountAll(ctx, entities, queriers...) must(err) return count } -func (r repository) Insert(ctx context.Context, record any, mutators ...Mutator) error { - finish := r.instrumenter.Observe(ctx, "rel-insert", "inserting a record") +func (r repository) Insert(ctx context.Context, entity any, mutators ...Mutator) error { + finish := r.instrumenter.Observe(ctx, "rel-insert", "inserting a entity") defer finish(nil) - if record == nil { + if entity == nil { return nil } var ( cw = fetchContext(ctx, r.rootAdapter) - doc = NewDocument(record) + doc = NewDocument(entity) mutation = Apply(doc, mutators...) ) @@ -392,21 +392,21 @@ func (r repository) insert(cw contextWrapper, doc *Document, mutation Mutation) return nil } -func (r repository) MustInsert(ctx context.Context, record any, mutators ...Mutator) { - must(r.Insert(ctx, record, mutators...)) +func (r repository) MustInsert(ctx context.Context, entity any, mutators ...Mutator) { + must(r.Insert(ctx, entity, mutators...)) } -func (r repository) InsertAll(ctx context.Context, records any, mutators ...Mutator) error { - finish := r.instrumenter.Observe(ctx, "rel-insert-all", "inserting multiple records") +func (r repository) InsertAll(ctx context.Context, entities any, mutators ...Mutator) error { + finish := r.instrumenter.Observe(ctx, "rel-insert-all", "inserting multiple entities") defer finish(nil) - if records == nil { + if entities == nil { return nil } var ( cw = fetchContext(ctx, r.rootAdapter) - col = NewCollection(records) + col = NewCollection(entities) muts = make([]Mutation, col.Len()) ) @@ -423,8 +423,8 @@ func (r repository) InsertAll(ctx context.Context, records any, mutators ...Muta return r.insertAll(cw, col, muts) } -func (r repository) MustInsertAll(ctx context.Context, records any, mutators ...Mutator) { - must(r.InsertAll(ctx, records, mutators...)) +func (r repository) MustInsertAll(ctx context.Context, entities any, mutators ...Mutator) { + must(r.InsertAll(ctx, entities, mutators...)) } // TODO: support assocs @@ -473,17 +473,17 @@ func (r repository) insertAll(cw contextWrapper, col *Collection, mutation []Mut return nil } -func (r repository) Update(ctx context.Context, record any, mutators ...Mutator) error { - finish := r.instrumenter.Observe(ctx, "rel-update", "updating a record") +func (r repository) Update(ctx context.Context, entity any, mutators ...Mutator) error { + finish := r.instrumenter.Observe(ctx, "rel-update", "updating a entity") defer finish(nil) - if record == nil { + if entity == nil { return nil } var ( cw = fetchContext(ctx, r.rootAdapter) - doc = NewDocument(record) + doc = NewDocument(entity) filter = filterDocument(doc) mutation = Apply(doc, mutators...) ) @@ -576,8 +576,8 @@ func (r repository) applyMutates(cw contextWrapper, doc *Document, mutation Muta return nil } -func (r repository) MustUpdate(ctx context.Context, record any, mutators ...Mutator) { - must(r.Update(ctx, record, mutators...)) +func (r repository) MustUpdate(ctx context.Context, entity any, mutators ...Mutator) { + must(r.Update(ctx, entity, mutators...)) } // TODO: support deletion @@ -773,7 +773,7 @@ func (r repository) saveHasMany(cw contextWrapper, doc *Document, mutation *Muta } func (r repository) UpdateAny(ctx context.Context, query Query, mutates ...Mutate) (int, error) { - finish := r.instrumenter.Observe(ctx, "rel-update-any", "updating multiple records") + finish := r.instrumenter.Observe(ctx, "rel-update-any", "updating multiple entities") defer finish(nil) var ( @@ -800,13 +800,13 @@ func (r repository) MustUpdateAny(ctx context.Context, query Query, mutates ...M return updatedCount } -func (r repository) Delete(ctx context.Context, record any, mutators ...Mutator) error { - finish := r.instrumenter.Observe(ctx, "rel-delete", "deleting a record") +func (r repository) Delete(ctx context.Context, entity any, mutators ...Mutator) error { + finish := r.instrumenter.Observe(ctx, "rel-delete", "deleting a entity") defer finish(nil) var ( cw = fetchContext(ctx, r.rootAdapter) - doc = NewDocument(record) + doc = NewDocument(entity) mutation = applyMutators(nil, false, false, mutators...) ) @@ -932,17 +932,17 @@ func (r repository) deleteHasMany(cw contextWrapper, doc *Document) error { return nil } -func (r repository) MustDelete(ctx context.Context, record any, mutators ...Mutator) { - must(r.Delete(ctx, record, mutators...)) +func (r repository) MustDelete(ctx context.Context, entity any, mutators ...Mutator) { + must(r.Delete(ctx, entity, mutators...)) } -func (r repository) DeleteAll(ctx context.Context, records any) error { - finish := r.instrumenter.Observe(ctx, "rel-delete-all", "deleting records") +func (r repository) DeleteAll(ctx context.Context, entities any) error { + finish := r.instrumenter.Observe(ctx, "rel-delete-all", "deleting entities") defer finish(nil) var ( cw = fetchContext(ctx, r.rootAdapter) - col = NewCollection(records) + col = NewCollection(entities) ) if col.Len() == 0 { @@ -957,12 +957,12 @@ func (r repository) DeleteAll(ctx context.Context, records any) error { return err } -func (r repository) MustDeleteAll(ctx context.Context, records any) { - must(r.DeleteAll(ctx, records)) +func (r repository) MustDeleteAll(ctx context.Context, entities any) { + must(r.DeleteAll(ctx, entities)) } func (r repository) DeleteAny(ctx context.Context, query Query) (int, error) { - finish := r.instrumenter.Observe(ctx, "rel-delete-any", "deleting multiple records") + finish := r.instrumenter.Observe(ctx, "rel-delete-any", "deleting multiple entities") defer finish(nil) var ( @@ -1001,34 +1001,34 @@ func (r repository) deleteAny(cw contextWrapper, flag DocumentFlag, query Query) return cw.adapter.Delete(cw.ctx, query) } -func (r repository) Preload(ctx context.Context, records any, field string, queriers ...Querier) error { +func (r repository) Preload(ctx context.Context, entities any, field string, queriers ...Querier) error { finish := r.instrumenter.Observe(ctx, "rel-preload", "preloading associations") defer finish(nil) var ( sl slice cw = fetchContext(ctx, r.rootAdapter) - rt = reflect.TypeOf(records) + rt = reflect.TypeOf(entities) ) if rt.Kind() != reflect.Ptr { - panic("rel: record parameter must be a pointer.") + panic("rel: entity parameter must be a pointer.") } rt = rt.Elem() if rt.Kind() == reflect.Slice { - sl = NewCollection(records) + sl = NewCollection(entities) } else { - sl = NewDocument(records) + sl = NewDocument(entities) } return r.preload(cw, sl, field, queriers) } -func (r repository) preload(cw contextWrapper, records slice, field string, queriers []Querier) error { +func (r repository) preload(cw contextWrapper, entities slice, field string, queriers []Querier) error { var ( path = strings.Split(field, ".") - targets, table, keyField, keyType, ddata, loaded = r.mapPreloadTargets(records, path) + targets, table, keyField, keyType, ddata, loaded = r.mapPreloadTargets(entities, path) ids = r.targetIDs(targets) inClauseLength = 999 ) @@ -1048,7 +1048,7 @@ func (r repository) preload(cw contextWrapper, records slice, field string, quer idsChunk := ids[0:inClauseLength] ids = ids[inClauseLength:] - query := Build(table, append(queriers, In(keyField, idsChunk...))...).Populate(records.Meta()) + query := Build(table, append(queriers, In(keyField, idsChunk...))...).Populate(entities.Meta()) if len(targets) == 0 || loaded && !bool(query.ReloadQuery) { return nil } @@ -1061,7 +1061,7 @@ func (r repository) preload(cw contextWrapper, records slice, field string, quer return err } - scanFinish := r.instrumenter.Observe(cw.ctx, "rel-scan-multi", "scanning all records to multiple targets") + scanFinish := r.instrumenter.Observe(cw.ctx, "rel-scan-multi", "scanning all entities to multiple targets") // Note: Calling scanMulti multiple times with the same targets works // only if the cursor of each execution only contains a new set of keys. // That is here the case as each select is with a unique set of ids. @@ -1075,8 +1075,8 @@ func (r repository) preload(cw contextWrapper, records slice, field string, quer return nil } -func (r repository) MustPreload(ctx context.Context, records any, field string, queriers ...Querier) { - must(r.Preload(ctx, records, field, queriers...)) +func (r repository) MustPreload(ctx context.Context, entities any, field string, queriers ...Querier) { + must(r.Preload(ctx, entities, field, queriers...)) } func (r repository) mapPreloadTargets(sl slice, path []string) (map[any][]slice, string, string, reflect.Type, DocumentMeta, bool) { diff --git a/repository_test.go b/repository_test.go index c5b12418..71220cf5 100644 --- a/repository_test.go +++ b/repository_test.go @@ -2400,7 +2400,7 @@ func TestRepository_saveHasMany_updateWithReorderInsert(t *testing.T) { user = User{ ID: 1, Emails: []Email{ - {Email: "email1@gmail.com"}, // new record not appended, but prepended/inserted + {Email: "email1@gmail.com"}, // new entity not appended, but prepended/inserted {ID: 1, UserID: 1, Email: "email2@gmail.com"}, }, } @@ -2783,13 +2783,13 @@ func TestRepository_Delete_invalidFieldType(t *testing.T) { var ( adapter = &testAdapter{} repo = New(adapter) - record = InvalidField{ID: 1} - query = From("invalid_fields").Where(Eq("id", record.ID)) + entity = InvalidField{ID: 1} + query = From("invalid_fields").Where(Eq("id", entity.ID)) ) adapter.On("Delete", query).Return(1, nil).Once() - assert.Nil(t, repo.Delete(context.TODO(), &record)) + assert.Nil(t, repo.Delete(context.TODO(), &entity)) adapter.AssertExpectations(t) } diff --git a/structset.go b/structset.go index 944ef384..0f0ffd41 100644 --- a/structset.go +++ b/structset.go @@ -133,6 +133,6 @@ func newStructset(doc *Document, skipZero bool) Structset { } // NewStructset from a struct. -func NewStructset(record any, skipZero bool) Structset { - return newStructset(NewDocument(record), skipZero) +func NewStructset(entity any, skipZero bool) Structset { + return newStructset(NewDocument(entity), skipZero) } diff --git a/structset_test.go b/structset_test.go index 1e381b4c..8ae16eb5 100644 --- a/structset_test.go +++ b/structset_test.go @@ -222,10 +222,10 @@ func TestStructset_uuid(t *testing.T) { // package like https://github.com/google/uuid use [16]byte to represent uuid var ( uuid = [16]byte{1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1} - record = struct { + entity = struct { UUID [16]byte `db:",primary"` }{UUID: uuid} - doc = NewDocument(&record) + doc = NewDocument(&entity) mutation = Mutation{ Cascade: true, Mutates: map[string]Mutate{ @@ -234,5 +234,5 @@ func TestStructset_uuid(t *testing.T) { } ) - assert.Equal(t, mutation, Apply(doc, NewStructset(&record, false))) + assert.Equal(t, mutation, Apply(doc, NewStructset(&entity, false))) }