{"id":19013085,"url":"https://github.com/meowmeowcode/hohin","last_synced_at":"2025-09-06T15:32:02.183Z","repository":{"id":181038732,"uuid":"666116132","full_name":"meowmeowcode/hohin","owner":"meowmeowcode","description":"Database toolkit for Go","archived":false,"fork":false,"pushed_at":"2023-09-20T19:27:38.000Z","size":180,"stargazers_count":4,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2024-11-08T19:23:19.954Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/meowmeowcode.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null}},"created_at":"2023-07-13T18:48:12.000Z","updated_at":"2023-09-30T05:28:41.000Z","dependencies_parsed_at":"2023-07-13T20:04:40.338Z","dependency_job_id":null,"html_url":"https://github.com/meowmeowcode/hohin","commit_stats":null,"previous_names":["meowmeowcode/hohin"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/meowmeowcode%2Fhohin","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/meowmeowcode%2Fhohin/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/meowmeowcode%2Fhohin/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/meowmeowcode%2Fhohin/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/meowmeowcode","download_url":"https://codeload.github.com/meowmeowcode/hohin/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":232130337,"owners_count":18476788,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-08T19:21:45.762Z","updated_at":"2025-01-01T22:12:20.060Z","avatar_url":"https://github.com/meowmeowcode.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Hōhin\n\nHohin is a database toolkit that contains generic implementations of the Repository pattern.\n\n## Supported database systems\n\nAt the moment, ClickHouse, MySQL, PostgreSQL, and SQLite3 are supported.\n\n## Documentation\n\nhttps://pkg.go.dev/github.com/meowmeowcode/hohin\n\n## Usage example\n\n```go\npackage example\n\nimport (\n    \"context\"\n    \"database/sql\"\n    \"encoding/json\"\n    \"fmt\"\n    \"github.com/google/uuid\"\n    _ \"github.com/mattn/go-sqlite3\"\n    \"github.com/meowmeowcode/hohin\"\n    \"github.com/meowmeowcode/hohin/sqldb\"\n    \"github.com/meowmeowcode/hohin/sqlite3\"\n)\n\nfunc Example() {\n    // Suppose we have this entity in our application:\n    type User struct {\n        Id   uuid.UUID\n        Name string\n    }\n\n    // We need to connect to a database and create a table for this entity:\n    pool, err := sql.Open(\"sqlite3\", \":memory:\")\n    if err != nil {\n        panic(err)\n    }\n    defer pool.Close()\n\n    _, err = pool.Exec(`\n        CREATE TABLE users (\n            Id uuid PRIMARY KEY,\n            Name text NOT NULL\n        )\n    `)\n    if err != nil {\n        panic(err)\n    }\n\n    // Everything is set up. Let's see what we can do now.\n\n    // Creating a repository:\n    usersRepo := sqlite3.NewRepo(sqlite3.Conf[User]{Table: \"users\"}).Simple()\n\n    // Saving an entity:\n    db := sqlite3.NewDB(pool).Simple()\n    alice := User{Id: uuid.New(), Name: \"Alice\"}\n    err = usersRepo.Add(db, alice)\n    if err != nil {\n        panic(err)\n    }\n\n    // Saving several entities:\n    bob := User{Id: uuid.New(), Name: \"Bob\"}\n    eve := User{Id: uuid.New(), Name: \"Eve\"}\n    err = usersRepo.AddMany(db, []User{bob, eve})\n    if err != nil {\n        panic(err)\n    }\n\n    // Loading an entity:\n    user, err := usersRepo.Get(db, hohin.Eq(\"Name\", \"Alice\"))\n    if err != nil {\n        panic(err)\n    }\n    fmt.Println(user == alice)\n\n    user, err = usersRepo.Get(db, hohin.Contains(\"Name\", \"o\"))\n    if err != nil {\n        panic(err)\n    }\n    fmt.Println(user == bob)\n\n    user, err = usersRepo.Get(\n        db,\n        hohin.And(hohin.HasSuffix(\"Name\", \"e\"), hohin.HasPrefix(\"Name\", \"E\")),\n    )\n    if err != nil {\n        panic(err)\n    }\n    fmt.Println(user == eve)\n\n    // Loading several entities:\n    users, err := usersRepo.GetMany(\n        db,\n        hohin.Query{Filter: hohin.HasSuffix(\"Name\", \"e\")}.OrderBy(hohin.Asc(\"Name\")),\n    )\n    if err != nil {\n        panic(err)\n    }\n    fmt.Println(len(users) == 2)\n    fmt.Println(users[0] == alice)\n    fmt.Println(users[1] == eve)\n\n    // Updating an entity:\n    bob.Name = \"Robert\"\n    err = usersRepo.Update(db, hohin.Eq(\"Id\", bob.Id), bob)\n    if err != nil {\n        panic(err)\n    }\n    user, err = usersRepo.Get(db, hohin.Eq(\"Id\", bob.Id))\n    if err != nil {\n        panic(err)\n    }\n    fmt.Println(user.Name == \"Robert\")\n\n    // Removing an entity:\n    err = usersRepo.Delete(db, hohin.Eq(\"Name\", \"Robert\"))\n    if err != nil {\n        panic(err)\n    }\n\n    // Using transactions:\n    err = db.Transaction(func(db hohin.SimpleDB) error {\n        alice, err := usersRepo.GetForUpdate(db, hohin.Eq(\"Name\", \"Alice\"))\n        if err != nil {\n            return err\n        }\n        eve, err := usersRepo.GetForUpdate(db, hohin.Eq(\"Name\", \"Eve\"))\n        if err != nil {\n            return err\n        }\n        alice.Name = \"Eve\"\n        eve.Name = \"Alice\"\n        err = usersRepo.Update(db, hohin.Eq(\"Id\", alice.Id), alice)\n        if err != nil {\n            return err\n        }\n        err = usersRepo.Update(db, hohin.Eq(\"Id\", eve.Id), eve)\n        if err != nil {\n            return err\n        }\n        return nil\n    })\n    if err != nil {\n        panic(err)\n    }\n    user, err = usersRepo.Get(db, hohin.Eq(\"Id\", alice.Id))\n    if err != nil {\n        panic(err)\n    }\n    fmt.Println(user.Name == \"Eve\")\n\n    // Using a context:\n    usersRepo2 := sqlite3.NewRepo(sqlite3.Conf[User]{Table: \"users\"})\n    db2 := sqlite3.NewDB(pool)\n    user, err = usersRepo2.Get(context.Background(), db2, hohin.Eq(\"Name\", \"Alice\"))\n    if err != nil {\n        panic(err)\n    }\n\n    // Configuring a repository:\n    //\n    // For this example we need another entity and a couple of tables:\n    type Contact struct {\n        Id     uuid.UUID\n        Name   string\n        Emails []string\n    }\n\n    _, err = pool.Exec(`\n        CREATE TABLE contacts (\n            pk uuid PRIMARY KEY,\n            name text NOT NULL\n        )\n    `)\n    if err != nil {\n        panic(err)\n    }\n\n    _, err = pool.Exec(`\n        CREATE TABLE emails (\n            pk uuid PRIMARY KEY,\n            email text NOT NULL,\n            contact_pk text NOT NULL\n        )\n    `)\n    if err != nil {\n        panic(err)\n    }\n\n    contactsRepo := sqlite3.NewRepo(sqlite3.Conf[Contact]{\n        Table: \"contacts\",\n        Mapping: map[string]string{\n            \"Id\":   \"pk\",\n            \"Name\": \"name\",\n        },\n        Query: `\n            SELECT * FROM (\n                SELECT contacts.pk, contacts.name, json_group_array(emails.email) AS emails\n                FROM contacts\n                LEFT JOIN emails ON emails.contact_pk = contacts.pk\n                GROUP BY contacts.pk, contacts.name\n            ) AS query\n        `,\n        Load: func(s sqlite3.Scanner) (Contact, error) {\n            var entity Contact\n            var emailsData string\n            err := s.Scan(\u0026entity.Id, \u0026entity.Name, \u0026emailsData)\n            err = json.Unmarshal([]byte(emailsData), \u0026entity.Emails)\n            return entity, err\n        },\n        AfterAdd: func(c Contact) []*sqldb.SQL {\n            var qs []*sqldb.SQL\n            for _, e := range c.Emails {\n                q := sqlite3.NewSQL(\"INSERT INTO emails (pk, email, contact_pk) VALUES (\").\n                    JoinParams(\", \", uuid.New(), e, c.Id).\n                    Add(\")\")\n                qs = append(qs, q)\n            }\n            return qs\n        },\n        AfterUpdate: func(c Contact) []*sqldb.SQL {\n            var qs []*sqldb.SQL\n            qs = append(qs, sqlite3.NewSQL(\"DELETE FROM emails WHERE contact_pk = \").Param(c.Id))\n\n            for _, e := range c.Emails {\n                q := sqlite3.NewSQL(\"INSERT INTO emails (id, email, contact_pk) VALUES (\").\n                    JoinParams(\", \", uuid.New(), e, c.Id).\n                    Add(\")\")\n                qs = append(qs, q)\n            }\n            return qs\n        },\n    }).Simple()\n\n    contact := Contact{Id: uuid.New(), Name: \"Bob\", Emails: []string{\"bob@test.com\", \"bob123@test.com\"}}\n    err = contactsRepo.Add(db, contact)\n    if err != nil {\n        panic(err)\n    }\n\n    contact = Contact{Id: uuid.New(), Name: \"Alice\", Emails: []string{\"alice@test.com\"}}\n    err = contactsRepo.Add(db, contact)\n    if err != nil {\n        panic(err)\n    }\n\n    contact, err = contactsRepo.Get(db, hohin.Eq(\"Name\", \"Bob\"))\n    if err != nil {\n        panic(err)\n    }\n    fmt.Println(contact.Name, contact.Emails)\n\n    contact, err = contactsRepo.Get(db, hohin.Eq(\"Name\", \"Alice\"))\n    if err != nil {\n        panic(err)\n    }\n    fmt.Println(contact.Name, contact.Emails)\n\n    // Output:\n    // true\n    // true\n    // true\n    // true\n    // true\n    // true\n    // true\n    // true\n    // Bob [bob@test.com bob123@test.com]\n    // Alice [alice@test.com]\n}\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmeowmeowcode%2Fhohin","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmeowmeowcode%2Fhohin","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmeowmeowcode%2Fhohin/lists"}