diff --git a/exec/exec.go b/exec/exec.go index ec9dcf2..09889fd 100644 --- a/exec/exec.go +++ b/exec/exec.go @@ -287,7 +287,7 @@ func prepareParams(src any, t schema.Table) []any { var params []any for _, col := range t.Columns { _, ok := col.Modifiers["pk"] - if ok { + if ok && t.IsPkAuto() { continue } field, ok := t.Type.FieldByName(col.FieldName) diff --git a/repository/repository.go b/repository/repository.go new file mode 100644 index 0000000..2799381 --- /dev/null +++ b/repository/repository.go @@ -0,0 +1,129 @@ +package repository + +import ( + "reflect" + "strings" + + "git.gdulai.com/gdulai/simpleorm" + "git.gdulai.com/gdulai/simpleorm/exec" + log "gitlab.com/gdulai/simpleloglvl" +) + +type HasPK interface { + IsInsertable() bool +} + +type Repository[T HasPK] struct { + conn *simpleorm.DBConnection + orm *simpleorm.ORM +} + +func NewRepository[T HasPK](conn *simpleorm.DBConnection, orm *simpleorm.ORM) *Repository[T] { + return &Repository[T]{conn: conn, orm: orm} +} + +func (r *Repository[T]) SelectAll() []*T { + selectExec, err := exec.CreateSelect[T](r.orm, "") + if err != nil { + log.LogError("Failed to create select execution: %s", err) + return []*T{} + } + + selecRes, err := selectExec.Execute(r.conn) + if err != nil { + log.LogError("Failed to load projects: %s", err) + return []*T{} + } + + res := make([]*T, 0, len(selecRes)) + for i := range selecRes { + res = append(res, &selecRes[i]) + } + + return res +} + +func (r *Repository[T]) SelectByPk(pks ...any) *T { + table, ok := r.orm.Cache().Get(reflect.TypeFor[T]().Name()) + if !ok { + log.LogError("Could not get cached Table!") + return nil + } + + var whereStmtBuilder strings.Builder + + for _, constr := range table.Constraints { + if constr.Type != "pk" { + continue + } + + for i, col := range constr.Columns { + if i != 0 { + whereStmtBuilder.WriteString(" AND ") + } + + whereStmtBuilder.WriteString(col.Name + " = ?") + } + + } + + selectExec, err := exec.CreateSelect[T](r.orm, whereStmtBuilder.String(), pks) + if err != nil { + log.LogError("Failed to create select execution: %s", err) + return nil + } + + res, err := selectExec.Execute(r.conn) + if err != nil { + log.LogError("Failed to load entity: %s", err) + return nil + } + + if len(res) != 1 { + log.LogError("Invalid result number of results: %s", len(res)) + return nil + } + + return &res[0] +} + +func (r *Repository[T]) Save(entity T) { + // Insert + if entity.IsInsertable() { + insertExec, err := exec.NewInsert[T](r.orm, entity) + if err != nil { + log.LogError("Failed to create insert execution: %s", err) + return + } + if _, err := insertExec.Execute(r.conn); err != nil { + log.LogError("Insert failed for entity: %s", entity) + } + + return + } + + updateExec, err := exec.NewUpdate[T](r.orm, entity) + if err != nil { + log.LogError("Failed to create update execution: %s", err) + return + } + + _, err = updateExec.Execute(r.conn) + if err != nil { + log.LogError("Update failed: %s", err) + return + } +} + +func (r *Repository[T]) Delete(entity T) { + deleteExec, err := exec.NewDelete[T](r.orm, []T{entity}) + if err != nil { + log.LogError("Failed to create delete execution: %s", err) + return + } + _, err = deleteExec.Execute(r.conn) + if err != nil { + log.LogError("Delete failed: %s", err) + return + } +} diff --git a/schema/table.go b/schema/table.go index 76bd660..9ad1ff8 100644 --- a/schema/table.go +++ b/schema/table.go @@ -66,7 +66,7 @@ func (t Table) GetInsertDML(count int) (string, error) { effectiveColumnsLen := 0 for i, col := range t.Columns { _, ok := col.Modifiers["pk"] - if ok { + if ok && t.IsPkAuto() { continue } @@ -116,7 +116,9 @@ func (t Table) GetUpdateDML() (string, error) { _, ok := modifiers["pk"] if ok { pkColumns = append(pkColumns, col.Name) - continue + if t.IsPkAuto() { + continue + } } colDml, err := col.GetUpdateDML() @@ -129,7 +131,6 @@ func (t Table) GetUpdateDML() (string, error) { } else { dml.WriteString(colDml + " ") } - } dml.WriteString("WHERE ") @@ -174,3 +175,14 @@ func (t Table) GetDeleteDML(count int) (string, error) { return dml.String(), nil } + +func (t Table) IsPkAuto() bool { + for _, constr := range t.Constraints { + if constr.Type != "pk" { + continue + } + + return len(constr.Columns) == 1 + } + return true +} diff --git a/exec_test.go b/test/exec_test.go similarity index 88% rename from exec_test.go rename to test/exec_test.go index 0ae7fa0..99a9b04 100644 --- a/exec_test.go +++ b/test/exec_test.go @@ -105,6 +105,61 @@ func TestInsertAndSelectSingle(t *testing.T) { } +func TestInsertSelectWithCompositePk(t *testing.T) { + // GIVEN + conn := simpleorm.OpenConnection("sqlite3", "file:test.db") + defer cleanUp("test.db", conn) + orm := simpleorm.NewORM(Test{}, TestWithFk{}, TestWithFkAndFkId{}, + TestWithCompositePk{}, TestWithCompositeFk{}) + + exec.ExecuteDDL(conn, orm) + + testObj := TestWithCompositePk{ID: 12, Name: "Test"} + + insertExec, err := exec.NewInsert(orm, testObj) + if err != nil { + log.LogError("TestInsertSelectWithCompositePk setup failed: %s", err) + t.Fail() + return + } + + selectExec, err := exec.CreateSelect[TestWithCompositePk](orm, "") + if err != nil { + log.LogError("TestInsertSelectWithCompositePk setup failed: %s", err) + t.Fail() + return + } + + // WHEN + _, err = insertExec.Execute(conn) + if err != nil { + log.LogError("TestInsertSelectWithCompositePk insert failed: %s", err) + t.Fail() + return + } + + res, err := selectExec.Execute(conn) + + // THEN + if err != nil { + log.LogError("TestInsertSelectWithCompositePk failure. %s", err) + t.Fail() + return + } + if len(res) != 1 { + log.LogError("TestInsertSelectWithCompositePk test failed. Expected: 1, Actual: %s", len(res)) + t.Fail() + return + } + + singleRes := res[0] + if singleRes.ID != 12 || singleRes.Name != "Test" { + log.LogError("TestInsertSelectWithCompositePk invalid result. Expected: %s, Actual: %s", testObj, singleRes) + t.Fail() + } + +} + func TestInsertMultipleAndSelectWithParam(t *testing.T) { // GIVEN conn := simpleorm.OpenConnection("sqlite3", "file:test.db") diff --git a/main_test.go b/test/main_test.go similarity index 85% rename from main_test.go rename to test/main_test.go index 371c8fd..eb0680c 100644 --- a/main_test.go +++ b/test/main_test.go @@ -15,6 +15,10 @@ type Test struct { StringField string `sql:"nn"` } +func (t Test) IsInsertable() bool { + return t.ID == 0 +} + type TestWithFk struct { ID int `sql:"pk"` TestID int `sql:"nn;fk=Test.ID"` @@ -26,10 +30,14 @@ type TestWithFkAndFkId struct { } type TestWithCompositePk struct { - ID int `sql:"nn;pk"` + ID int `sql:"nn;pk;"` Name string `sql:"nn;pk"` } +func (t TestWithCompositePk) IsInsertable() bool { + return t.ID == 0 && t.Name == "" +} + type TestWithCompositeFk struct { ID int `sql:"nn;pk"` CompositeFkId int `sql:"nn;fk=TestWithCompositePk.ID;"` diff --git a/test/repository_test.go b/test/repository_test.go new file mode 100644 index 0000000..b5f8d90 --- /dev/null +++ b/test/repository_test.go @@ -0,0 +1,148 @@ +package simpleorm_test + +import ( + "testing" + + simpleorm "git.gdulai.com/gdulai/simpleorm" + "git.gdulai.com/gdulai/simpleorm/exec" + "git.gdulai.com/gdulai/simpleorm/repository" + log "gitlab.com/gdulai/simpleloglvl" +) + +func repoTestSetup() (*simpleorm.ORM, *simpleorm.DBConnection) { + conn := simpleorm.OpenConnection("sqlite3", "file:test.db") + + orm := simpleorm.NewORM(Test{}, TestWithFk{}, TestWithFkAndFkId{}, + TestWithCompositePk{}, TestWithCompositeFk{}) + + exec.ExecuteDDL(conn, orm) + + return orm, conn +} + +func TestRepoSelectAll(t *testing.T) { + // GIVEN + orm, conn := repoTestSetup() + defer cleanUp("test.db", conn) + + testObj := Test{Int64Field: 54, IntField: 12, StringField: "asdf"} + insertExec, err := exec.NewInsert(orm, testObj) + if err != nil { + log.LogError("TestRepoSelectAll setup failed: %s", err) + t.Fail() + return + } + _, err = insertExec.Execute(conn) + repo := repository.NewRepository[Test](conn, orm) + + // WHEN + res := repo.SelectAll() + + // THEN + if len(res) != 1 { + log.LogError("TestRepoSelectAll test failed. Expected: 1, Actual: %s", len(res)) + t.Fail() + return + } +} + +func TestRepoSelectByPk(t *testing.T) { + // GIVEN + orm, conn := repoTestSetup() + defer cleanUp("test.db", conn) + + testObj := Test{Int64Field: 54, IntField: 12, StringField: "asdf"} + insertExec, err := exec.NewInsert(orm, testObj) + if err != nil { + log.LogError("TestRepoSelectByPk setup failed: %s", err) + t.Fail() + return + } + _, err = insertExec.Execute(conn) + repo := repository.NewRepository[Test](conn, orm) + + // WHEN + res := repo.SelectByPk(1) + + // THEN + if res == nil { + log.LogError("TestRepoSelectByPk test failed. Result is nil!") + t.Fail() + return + } +} + +func TestRepoSelectByCompundPk(t *testing.T) { + // GIVEN + orm, conn := repoTestSetup() + defer cleanUp("test.db", conn) + + testObj := TestWithCompositePk{ID: 42069, Name: "Test"} + insertExec, err := exec.NewInsert(orm, testObj) + if err != nil { + log.LogError("TestRepoSelectByCompundPk setup failed: %s", err) + t.Fail() + return + } + _, err = insertExec.Execute(conn) + repo := repository.NewRepository[TestWithCompositePk](conn, orm) + + // WHEN + res := repo.SelectByPk(42069, "Test") + + // THEN + if res == nil { + log.LogError("TestRepoSelectByCompundPk test failed. Result is nil!") + t.Fail() + return + } +} + +func TestRepoSave(t *testing.T) { + // GIVEN + orm, conn := repoTestSetup() + defer cleanUp("test.db", conn) + + testObj := Test{Int64Field: 54, IntField: 12, StringField: "asdf"} + repo := repository.NewRepository[Test](conn, orm) + + // WHEN + repo.Save(testObj) + + // THEN + res := repo.SelectByPk(1) + if res == nil { + log.LogError("TestRepoSave test failed. Result is nil!") + t.Fail() + return + } +} + +func TestRepoDelete(t *testing.T) { + // GIVEN + orm, conn := repoTestSetup() + defer cleanUp("test.db", conn) + + testObj := Test{Int64Field: 54, IntField: 12, StringField: "asdf"} + repo := repository.NewRepository[Test](conn, orm) + + repo.Save(testObj) + + res := repo.SelectByPk(1) + if res == nil { + log.LogError("TestRepoDelete setup failed! Test data not saved!") + t.Fail() + return + } + + // WHEN + repo.Delete(*res) + + // THEN + res = repo.SelectByPk(1) + if res != nil { + log.LogError("TestRepoDelete test failed. Result is not nil!") + t.Fail() + return + } +} diff --git a/schema_test.go b/test/schema_test.go similarity index 100% rename from schema_test.go rename to test/schema_test.go