diff options
| -rw-r--r-- | migrations/003_create_posts_table.sql.sql | 3 | ||||
| -rw-r--r-- | migrations/004_create_tags_table.sql.sql | 24 | ||||
| -rw-r--r-- | src/main.go | 2 | ||||
| -rw-r--r-- | src/models/boil_relationship_test.go | 34 | ||||
| -rw-r--r-- | src/models/boil_suites_test.go | 18 | ||||
| -rw-r--r-- | src/models/boil_table_names.go | 12 | ||||
| -rw-r--r-- | src/models/posts.go | 518 | ||||
| -rw-r--r-- | src/models/posts_test.go | 433 | ||||
| -rw-r--r-- | src/models/psql_suites_test.go | 2 | ||||
| -rw-r--r-- | src/models/tags.go | 1469 | ||||
| -rw-r--r-- | src/models/tags_test.go | 1163 | ||||
| -rw-r--r-- | src/models/users.go | 383 | ||||
| -rw-r--r-- | src/models/users_test.go | 307 | ||||
| -rw-r--r-- | src/post/routes.go | 23 | ||||
| -rw-r--r-- | ui/pages/post/get.tmpl.html | 22 |
15 files changed, 4376 insertions, 37 deletions
diff --git a/migrations/003_create_posts_table.sql.sql b/migrations/003_create_posts_table.sql.sql index 81dab8f..584b7b4 100644 --- a/migrations/003_create_posts_table.sql.sql +++ b/migrations/003_create_posts_table.sql.sql @@ -1,8 +1,9 @@ CREATE TABLE posts( id SERIAL NOT NULL PRIMARY KEY, title VARCHAR(100) NOT NULL, - description TEXT NOT NULL, + description TEXT, url VARCHAR(255) UNIQUE, + user_id INT NOT NULL REFERENCES users(id), created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() ); diff --git a/migrations/004_create_tags_table.sql.sql b/migrations/004_create_tags_table.sql.sql new file mode 100644 index 0000000..0b184f8 --- /dev/null +++ b/migrations/004_create_tags_table.sql.sql @@ -0,0 +1,24 @@ +CREATE TABLE tags( + id SERIAL NOT NULL PRIMARY KEY, + tag VARCHAR(30) NOT NULL, + description TEXT NOT NULL, + user_id INT NOT NULL REFERENCES users(id), + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE TABLE post_tags( + post_id INT NOT NULL REFERENCES posts(id) ON DELETE CASCADE, + tag_id INT NOT NULL REFERENCES tags(id) ON DELETE CASCADE, + PRIMARY KEY (post_id, tag_id) +); + +CREATE TRIGGER set_timestamp +BEFORE UPDATE ON tags +FOR EACH ROW +EXECUTE PROCEDURE trigger_set_timestamp(); + +---- create above / drop below ---- + +DROP TABLE post_tags; +DROP TABLE tags; diff --git a/src/main.go b/src/main.go index 4dc2caf..208944f 100644 --- a/src/main.go +++ b/src/main.go @@ -3,6 +3,7 @@ package main import ( "gitlab.com/alexkavon/newsstand/src/conf" "gitlab.com/alexkavon/newsstand/src/db" + "gitlab.com/alexkavon/newsstand/src/post" "gitlab.com/alexkavon/newsstand/src/server" "gitlab.com/alexkavon/newsstand/src/user" ) @@ -18,6 +19,7 @@ func main() { s.BuildUi() routers := []server.Routes{ user.Routes, + post.Routes, } for _, r := range routers { s.RegisterRoutes(r) diff --git a/src/models/boil_relationship_test.go b/src/models/boil_relationship_test.go index c4d831d..191d17c 100644 --- a/src/models/boil_relationship_test.go +++ b/src/models/boil_relationship_test.go @@ -7,7 +7,10 @@ import "testing" // TestToOne tests cannot be run in parallel // or deadlocks can occur. -func TestToOne(t *testing.T) {} +func TestToOne(t *testing.T) { + t.Run("PostToUserUsingUser", testPostToOneUserUsingUser) + t.Run("TagToUserUsingUser", testTagToOneUserUsingUser) +} // TestOneToOne tests cannot be run in parallel // or deadlocks can occur. @@ -15,11 +18,19 @@ func TestOneToOne(t *testing.T) {} // TestToMany tests cannot be run in parallel // or deadlocks can occur. -func TestToMany(t *testing.T) {} +func TestToMany(t *testing.T) { + t.Run("PostToTags", testPostToManyTags) + t.Run("TagToPosts", testTagToManyPosts) + t.Run("UserToPosts", testUserToManyPosts) + t.Run("UserToTags", testUserToManyTags) +} // TestToOneSet tests cannot be run in parallel // or deadlocks can occur. -func TestToOneSet(t *testing.T) {} +func TestToOneSet(t *testing.T) { + t.Run("PostToUserUsingPosts", testPostToOneSetOpUserUsingUser) + t.Run("TagToUserUsingTags", testTagToOneSetOpUserUsingUser) +} // TestToOneRemove tests cannot be run in parallel // or deadlocks can occur. @@ -35,12 +46,23 @@ func TestOneToOneRemove(t *testing.T) {} // TestToManyAdd tests cannot be run in parallel // or deadlocks can occur. -func TestToManyAdd(t *testing.T) {} +func TestToManyAdd(t *testing.T) { + t.Run("PostToTags", testPostToManyAddOpTags) + t.Run("TagToPosts", testTagToManyAddOpPosts) + t.Run("UserToPosts", testUserToManyAddOpPosts) + t.Run("UserToTags", testUserToManyAddOpTags) +} // TestToManySet tests cannot be run in parallel // or deadlocks can occur. -func TestToManySet(t *testing.T) {} +func TestToManySet(t *testing.T) { + t.Run("PostToTags", testPostToManySetOpTags) + t.Run("TagToPosts", testTagToManySetOpPosts) +} // TestToManyRemove tests cannot be run in parallel // or deadlocks can occur. -func TestToManyRemove(t *testing.T) {} +func TestToManyRemove(t *testing.T) { + t.Run("PostToTags", testPostToManyRemoveOpTags) + t.Run("TagToPosts", testTagToManyRemoveOpPosts) +} diff --git a/src/models/boil_suites_test.go b/src/models/boil_suites_test.go index d1e1758..33d14ea 100644 --- a/src/models/boil_suites_test.go +++ b/src/models/boil_suites_test.go @@ -13,87 +13,105 @@ import "testing" // Separating the tests thusly grants avoidance of Postgres deadlocks. func TestParent(t *testing.T) { t.Run("Posts", testPosts) + t.Run("Tags", testTags) t.Run("Users", testUsers) } func TestDelete(t *testing.T) { t.Run("Posts", testPostsDelete) + t.Run("Tags", testTagsDelete) t.Run("Users", testUsersDelete) } func TestQueryDeleteAll(t *testing.T) { t.Run("Posts", testPostsQueryDeleteAll) + t.Run("Tags", testTagsQueryDeleteAll) t.Run("Users", testUsersQueryDeleteAll) } func TestSliceDeleteAll(t *testing.T) { t.Run("Posts", testPostsSliceDeleteAll) + t.Run("Tags", testTagsSliceDeleteAll) t.Run("Users", testUsersSliceDeleteAll) } func TestExists(t *testing.T) { t.Run("Posts", testPostsExists) + t.Run("Tags", testTagsExists) t.Run("Users", testUsersExists) } func TestFind(t *testing.T) { t.Run("Posts", testPostsFind) + t.Run("Tags", testTagsFind) t.Run("Users", testUsersFind) } func TestBind(t *testing.T) { t.Run("Posts", testPostsBind) + t.Run("Tags", testTagsBind) t.Run("Users", testUsersBind) } func TestOne(t *testing.T) { t.Run("Posts", testPostsOne) + t.Run("Tags", testTagsOne) t.Run("Users", testUsersOne) } func TestAll(t *testing.T) { t.Run("Posts", testPostsAll) + t.Run("Tags", testTagsAll) t.Run("Users", testUsersAll) } func TestCount(t *testing.T) { t.Run("Posts", testPostsCount) + t.Run("Tags", testTagsCount) t.Run("Users", testUsersCount) } func TestHooks(t *testing.T) { t.Run("Posts", testPostsHooks) + t.Run("Tags", testTagsHooks) t.Run("Users", testUsersHooks) } func TestInsert(t *testing.T) { t.Run("Posts", testPostsInsert) t.Run("Posts", testPostsInsertWhitelist) + t.Run("Tags", testTagsInsert) + t.Run("Tags", testTagsInsertWhitelist) t.Run("Users", testUsersInsert) t.Run("Users", testUsersInsertWhitelist) } func TestReload(t *testing.T) { t.Run("Posts", testPostsReload) + t.Run("Tags", testTagsReload) t.Run("Users", testUsersReload) } func TestReloadAll(t *testing.T) { t.Run("Posts", testPostsReloadAll) + t.Run("Tags", testTagsReloadAll) t.Run("Users", testUsersReloadAll) } func TestSelect(t *testing.T) { t.Run("Posts", testPostsSelect) + t.Run("Tags", testTagsSelect) t.Run("Users", testUsersSelect) } func TestUpdate(t *testing.T) { t.Run("Posts", testPostsUpdate) + t.Run("Tags", testTagsUpdate) t.Run("Users", testUsersUpdate) } func TestSliceUpdateAll(t *testing.T) { t.Run("Posts", testPostsSliceUpdateAll) + t.Run("Tags", testTagsSliceUpdateAll) t.Run("Users", testUsersSliceUpdateAll) } diff --git a/src/models/boil_table_names.go b/src/models/boil_table_names.go index c88a449..29a98ba 100644 --- a/src/models/boil_table_names.go +++ b/src/models/boil_table_names.go @@ -4,9 +4,13 @@ package models var TableNames = struct { - Posts string - Users string + PostTags string + Posts string + Tags string + Users string }{ - Posts: "posts", - Users: "users", + PostTags: "post_tags", + Posts: "posts", + Tags: "tags", + Users: "users", } diff --git a/src/models/posts.go b/src/models/posts.go index d644268..7fb455c 100644 --- a/src/models/posts.go +++ b/src/models/posts.go @@ -26,8 +26,9 @@ import ( type Post struct { ID int `boil:"id" json:"id" toml:"id" yaml:"id"` Title string `boil:"title" json:"title" toml:"title" yaml:"title"` + Description null.String `boil:"description" json:"description,omitempty" toml:"description" yaml:"description,omitempty"` URL null.String `boil:"url" json:"url,omitempty" toml:"url" yaml:"url,omitempty"` - Description string `boil:"description" json:"description" toml:"description" yaml:"description"` + UserID int `boil:"user_id" json:"user_id" toml:"user_id" yaml:"user_id"` CreatedAt time.Time `boil:"created_at" json:"created_at" toml:"created_at" yaml:"created_at"` UpdatedAt time.Time `boil:"updated_at" json:"updated_at" toml:"updated_at" yaml:"updated_at"` @@ -38,15 +39,17 @@ type Post struct { var PostColumns = struct { ID string Title string - URL string Description string + URL string + UserID string CreatedAt string UpdatedAt string }{ ID: "id", Title: "title", - URL: "url", Description: "description", + URL: "url", + UserID: "user_id", CreatedAt: "created_at", UpdatedAt: "updated_at", } @@ -54,15 +57,17 @@ var PostColumns = struct { var PostTableColumns = struct { ID string Title string - URL string Description string + URL string + UserID string CreatedAt string UpdatedAt string }{ ID: "posts.id", Title: "posts.title", - URL: "posts.url", Description: "posts.description", + URL: "posts.url", + UserID: "posts.user_id", CreatedAt: "posts.created_at", UpdatedAt: "posts.updated_at", } @@ -193,25 +198,34 @@ func (w whereHelpertime_Time) GTE(x time.Time) qm.QueryMod { var PostWhere = struct { ID whereHelperint Title whereHelperstring + Description whereHelpernull_String URL whereHelpernull_String - Description whereHelperstring + UserID whereHelperint CreatedAt whereHelpertime_Time UpdatedAt whereHelpertime_Time }{ ID: whereHelperint{field: "\"posts\".\"id\""}, Title: whereHelperstring{field: "\"posts\".\"title\""}, + Description: whereHelpernull_String{field: "\"posts\".\"description\""}, URL: whereHelpernull_String{field: "\"posts\".\"url\""}, - Description: whereHelperstring{field: "\"posts\".\"description\""}, + UserID: whereHelperint{field: "\"posts\".\"user_id\""}, CreatedAt: whereHelpertime_Time{field: "\"posts\".\"created_at\""}, UpdatedAt: whereHelpertime_Time{field: "\"posts\".\"updated_at\""}, } // PostRels is where relationship names are stored. var PostRels = struct { -}{} + User string + Tags string +}{ + User: "User", + Tags: "Tags", +} // postR is where relationships are stored. type postR struct { + User *User `boil:"User" json:"User" toml:"User" yaml:"User"` + Tags TagSlice `boil:"Tags" json:"Tags" toml:"Tags" yaml:"Tags"` } // NewStruct creates a new relationship struct @@ -219,13 +233,27 @@ func (*postR) NewStruct() *postR { return &postR{} } +func (r *postR) GetUser() *User { + if r == nil { + return nil + } + return r.User +} + +func (r *postR) GetTags() TagSlice { + if r == nil { + return nil + } + return r.Tags +} + // postL is where Load methods for each relationship are stored. type postL struct{} var ( - postAllColumns = []string{"id", "title", "url", "description", "created_at", "updated_at"} - postColumnsWithoutDefault = []string{"title", "description"} - postColumnsWithDefault = []string{"id", "url", "created_at", "updated_at"} + postAllColumns = []string{"id", "title", "description", "url", "user_id", "created_at", "updated_at"} + postColumnsWithoutDefault = []string{"title", "user_id"} + postColumnsWithDefault = []string{"id", "description", "url", "created_at", "updated_at"} postPrimaryKeyColumns = []string{"id"} postGeneratedColumns = []string{} ) @@ -535,6 +563,474 @@ func (q postQuery) Exists(ctx context.Context, exec boil.ContextExecutor) (bool, return count > 0, nil } +// User pointed to by the foreign key. +func (o *Post) User(mods ...qm.QueryMod) userQuery { + queryMods := []qm.QueryMod{ + qm.Where("\"id\" = ?", o.UserID), + } + + queryMods = append(queryMods, mods...) + + return Users(queryMods...) +} + +// Tags retrieves all the tag's Tags with an executor. +func (o *Post) Tags(mods ...qm.QueryMod) tagQuery { + var queryMods []qm.QueryMod + if len(mods) != 0 { + queryMods = append(queryMods, mods...) + } + + queryMods = append(queryMods, + qm.InnerJoin("\"post_tags\" on \"tags\".\"id\" = \"post_tags\".\"tag_id\""), + qm.Where("\"post_tags\".\"post_id\"=?", o.ID), + ) + + return Tags(queryMods...) +} + +// LoadUser allows an eager lookup of values, cached into the +// loaded structs of the objects. This is for an N-1 relationship. +func (postL) LoadUser(ctx context.Context, e boil.ContextExecutor, singular bool, maybePost interface{}, mods queries.Applicator) error { + var slice []*Post + var object *Post + + if singular { + var ok bool + object, ok = maybePost.(*Post) + if !ok { + object = new(Post) + ok = queries.SetFromEmbeddedStruct(&object, &maybePost) + if !ok { + return errors.New(fmt.Sprintf("failed to set %T from embedded struct %T", object, maybePost)) + } + } + } else { + s, ok := maybePost.(*[]*Post) + if ok { + slice = *s + } else { + ok = queries.SetFromEmbeddedStruct(&slice, maybePost) + if !ok { + return errors.New(fmt.Sprintf("failed to set %T from embedded struct %T", slice, maybePost)) + } + } + } + + args := make(map[interface{}]struct{}) + if singular { + if object.R == nil { + object.R = &postR{} + } + args[object.UserID] = struct{}{} + + } else { + for _, obj := range slice { + if obj.R == nil { + obj.R = &postR{} + } + + args[obj.UserID] = struct{}{} + + } + } + + if len(args) == 0 { + return nil + } + + argsSlice := make([]interface{}, len(args)) + i := 0 + for arg := range args { + argsSlice[i] = arg + i++ + } + + query := NewQuery( + qm.From(`users`), + qm.WhereIn(`users.id in ?`, argsSlice...), + ) + if mods != nil { + mods.Apply(query) + } + + results, err := query.QueryContext(ctx, e) + if err != nil { + return errors.Wrap(err, "failed to eager load User") + } + + var resultSlice []*User + if err = queries.Bind(results, &resultSlice); err != nil { + return errors.Wrap(err, "failed to bind eager loaded slice User") + } + + if err = results.Close(); err != nil { + return errors.Wrap(err, "failed to close results of eager load for users") + } + if err = results.Err(); err != nil { + return errors.Wrap(err, "error occurred during iteration of eager loaded relations for users") + } + + if len(userAfterSelectHooks) != 0 { + for _, obj := range resultSlice { + if err := obj.doAfterSelectHooks(ctx, e); err != nil { + return err + } + } + } + + if len(resultSlice) == 0 { + return nil + } + + if singular { + foreign := resultSlice[0] + object.R.User = foreign + if foreign.R == nil { + foreign.R = &userR{} + } + foreign.R.Posts = append(foreign.R.Posts, object) + return nil + } + + for _, local := range slice { + for _, foreign := range resultSlice { + if local.UserID == foreign.ID { + local.R.User = foreign + if foreign.R == nil { + foreign.R = &userR{} + } + foreign.R.Posts = append(foreign.R.Posts, local) + break + } + } + } + + return nil +} + +// LoadTags allows an eager lookup of values, cached into the +// loaded structs of the objects. This is for a 1-M or N-M relationship. +func (postL) LoadTags(ctx context.Context, e boil.ContextExecutor, singular bool, maybePost interface{}, mods queries.Applicator) error { + var slice []*Post + var object *Post + + if singular { + var ok bool + object, ok = maybePost.(*Post) + if !ok { + object = new(Post) + ok = queries.SetFromEmbeddedStruct(&object, &maybePost) + if !ok { + return errors.New(fmt.Sprintf("failed to set %T from embedded struct %T", object, maybePost)) + } + } + } else { + s, ok := maybePost.(*[]*Post) + if ok { + slice = *s + } else { + ok = queries.SetFromEmbeddedStruct(&slice, maybePost) + if !ok { + return errors.New(fmt.Sprintf("failed to set %T from embedded struct %T", slice, maybePost)) + } + } + } + + args := make(map[interface{}]struct{}) + if singular { + if object.R == nil { + object.R = &postR{} + } + args[object.ID] = struct{}{} + } else { + for _, obj := range slice { + if obj.R == nil { + obj.R = &postR{} + } + args[obj.ID] = struct{}{} + } + } + + if len(args) == 0 { + return nil + } + + argsSlice := make([]interface{}, len(args)) + i := 0 + for arg := range args { + argsSlice[i] = arg + i++ + } + + query := NewQuery( + qm.Select("\"tags\".\"id\", \"tags\".\"tag\", \"tags\".\"description\", \"tags\".\"user_id\", \"tags\".\"created_at\", \"tags\".\"updated_at\", \"a\".\"post_id\""), + qm.From("\"tags\""), + qm.InnerJoin("\"post_tags\" as \"a\" on \"tags\".\"id\" = \"a\".\"tag_id\""), + qm.WhereIn("\"a\".\"post_id\" in ?", argsSlice...), + ) + if mods != nil { + mods.Apply(query) + } + + results, err := query.QueryContext(ctx, e) + if err != nil { + return errors.Wrap(err, "failed to eager load tags") + } + + var resultSlice []*Tag + + var localJoinCols []int + for results.Next() { + one := new(Tag) + var localJoinCol int + + err = results.Scan(&one.ID, &one.Tag, &one.Description, &one.UserID, &one.CreatedAt, &one.UpdatedAt, &localJoinCol) + if err != nil { + return errors.Wrap(err, "failed to scan eager loaded results for tags") + } + if err = results.Err(); err != nil { + return errors.Wrap(err, "failed to plebian-bind eager loaded slice tags") + } + + resultSlice = append(resultSlice, one) + localJoinCols = append(localJoinCols, localJoinCol) + } + + if err = results.Close(); err != nil { + return errors.Wrap(err, "failed to close results in eager load on tags") + } + if err = results.Err(); err != nil { + return errors.Wrap(err, "error occurred during iteration of eager loaded relations for tags") + } + + if len(tagAfterSelectHooks) != 0 { + for _, obj := range resultSlice { + if err := obj.doAfterSelectHooks(ctx, e); err != nil { + return err + } + } + } + if singular { + object.R.Tags = resultSlice + for _, foreign := range resultSlice { + if foreign.R == nil { + foreign.R = &tagR{} + } + foreign.R.Posts = append(foreign.R.Posts, object) + } + return nil + } + + for i, foreign := range resultSlice { + localJoinCol := localJoinCols[i] + for _, local := range slice { + if local.ID == localJoinCol { + local.R.Tags = append(local.R.Tags, foreign) + if foreign.R == nil { + foreign.R = &tagR{} + } + foreign.R.Posts = append(foreign.R.Posts, local) + break + } + } + } + + return nil +} + +// SetUser of the post to the related item. +// Sets o.R.User to related. +// Adds o to related.R.Posts. +func (o *Post) SetUser(ctx context.Context, exec boil.ContextExecutor, insert bool, related *User) error { + var err error + if insert { + if err = related.Insert(ctx, exec, boil.Infer()); err != nil { + return errors.Wrap(err, "failed to insert into foreign table") + } + } + + updateQuery := fmt.Sprintf( + "UPDATE \"posts\" SET %s WHERE %s", + strmangle.SetParamNames("\"", "\"", 1, []string{"user_id"}), + strmangle.WhereClause("\"", "\"", 2, postPrimaryKeyColumns), + ) + values := []interface{}{related.ID, o.ID} + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, updateQuery) + fmt.Fprintln(writer, values) + } + if _, err = exec.ExecContext(ctx, updateQuery, values...); err != nil { + return errors.Wrap(err, "failed to update local table") + } + + o.UserID = related.ID + if o.R == nil { + o.R = &postR{ + User: related, + } + } else { + o.R.User = related + } + + if related.R == nil { + related.R = &userR{ + Posts: PostSlice{o}, + } + } else { + related.R.Posts = append(related.R.Posts, o) + } + + return nil +} + +// AddTags adds the given related objects to the existing relationships +// of the post, optionally inserting them as new records. +// Appends related to o.R.Tags. +// Sets related.R.Posts appropriately. +func (o *Post) AddTags(ctx context.Context, exec boil.ContextExecutor, insert bool, related ...*Tag) error { + var err error + for _, rel := range related { + if insert { + if err = rel.Insert(ctx, exec, boil.Infer()); err != nil { + return errors.Wrap(err, "failed to insert into foreign table") + } + } + } + + for _, rel := range related { + query := "insert into \"post_tags\" (\"post_id\", \"tag_id\") values ($1, $2)" + values := []interface{}{o.ID, rel.ID} + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, query) + fmt.Fprintln(writer, values) + } + _, err = exec.ExecContext(ctx, query, values...) + if err != nil { + return errors.Wrap(err, "failed to insert into join table") + } + } + if o.R == nil { + o.R = &postR{ + Tags: related, + } + } else { + o.R.Tags = append(o.R.Tags, related...) + } + + for _, rel := range related { + if rel.R == nil { + rel.R = &tagR{ + Posts: PostSlice{o}, + } + } else { + rel.R.Posts = append(rel.R.Posts, o) + } + } + return nil +} + +// SetTags removes all previously related items of the +// post replacing them completely with the passed +// in related items, optionally inserting them as new records. +// Sets o.R.Posts's Tags accordingly. +// Replaces o.R.Tags with related. +// Sets related.R.Posts's Tags accordingly. +func (o *Post) SetTags(ctx context.Context, exec boil.ContextExecutor, insert bool, related ...*Tag) error { + query := "delete from \"post_tags\" where \"post_id\" = $1" + values := []interface{}{o.ID} + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, query) + fmt.Fprintln(writer, values) + } + _, err := exec.ExecContext(ctx, query, values...) + if err != nil { + return errors.Wrap(err, "failed to remove relationships before set") + } + + removeTagsFromPostsSlice(o, related) + if o.R != nil { + o.R.Tags = nil + } + + return o.AddTags(ctx, exec, insert, related...) +} + +// RemoveTags relationships from objects passed in. +// Removes related items from R.Tags (uses pointer comparison, removal does not keep order) +// Sets related.R.Posts. +func (o *Post) RemoveTags(ctx context.Context, exec boil.ContextExecutor, related ...*Tag) error { + if len(related) == 0 { + return nil + } + + var err error + query := fmt.Sprintf( + "delete from \"post_tags\" where \"post_id\" = $1 and \"tag_id\" in (%s)", + strmangle.Placeholders(dialect.UseIndexPlaceholders, len(related), 2, 1), + ) + values := []interface{}{o.ID} + for _, rel := range related { + values = append(values, rel.ID) + } + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, query) + fmt.Fprintln(writer, values) + } + _, err = exec.ExecContext(ctx, query, values...) + if err != nil { + return errors.Wrap(err, "failed to remove relationships before set") + } + removeTagsFromPostsSlice(o, related) + if o.R == nil { + return nil + } + + for _, rel := range related { + for i, ri := range o.R.Tags { + if rel != ri { + continue + } + + ln := len(o.R.Tags) + if ln > 1 && i < ln-1 { + o.R.Tags[i] = o.R.Tags[ln-1] + } + o.R.Tags = o.R.Tags[:ln-1] + break + } + } + + return nil +} + +func removeTagsFromPostsSlice(o *Post, related []*Tag) { + for _, rel := range related { + if rel.R == nil { + continue + } + for i, ri := range rel.R.Posts { + if o.ID != ri.ID { + continue + } + + ln := len(rel.R.Posts) + if ln > 1 && i < ln-1 { + rel.R.Posts[i] = rel.R.Posts[ln-1] + } + rel.R.Posts = rel.R.Posts[:ln-1] + break + } + } +} + // Posts retrieves all the records using an executor. func Posts(mods ...qm.QueryMod) postQuery { mods = append(mods, qm.From("\"posts\"")) diff --git a/src/models/posts_test.go b/src/models/posts_test.go index 0df4258..578d483 100644 --- a/src/models/posts_test.go +++ b/src/models/posts_test.go @@ -494,6 +494,437 @@ func testPostsInsertWhitelist(t *testing.T) { } } +func testPostToManyTags(t *testing.T) { + var err error + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + + var a Post + var b, c Tag + + seed := randomize.NewSeed() + if err = randomize.Struct(seed, &a, postDBTypes, true, postColumnsWithDefault...); err != nil { + t.Errorf("Unable to randomize Post struct: %s", err) + } + + if err := a.Insert(ctx, tx, boil.Infer()); err != nil { + t.Fatal(err) + } + + if err = randomize.Struct(seed, &b, tagDBTypes, false, tagColumnsWithDefault...); err != nil { + t.Fatal(err) + } + if err = randomize.Struct(seed, &c, tagDBTypes, false, tagColumnsWithDefault...); err != nil { + t.Fatal(err) + } + + if err = b.Insert(ctx, tx, boil.Infer()); err != nil { + t.Fatal(err) + } + if err = c.Insert(ctx, tx, boil.Infer()); err != nil { + t.Fatal(err) + } + + _, err = tx.Exec("insert into \"post_tags\" (\"post_id\", \"tag_id\") values ($1, $2)", a.ID, b.ID) + if err != nil { + t.Fatal(err) + } + _, err = tx.Exec("insert into \"post_tags\" (\"post_id\", \"tag_id\") values ($1, $2)", a.ID, c.ID) + if err != nil { + t.Fatal(err) + } + + check, err := a.Tags().All(ctx, tx) + if err != nil { + t.Fatal(err) + } + + bFound, cFound := false, false + for _, v := range check { + if v.ID == b.ID { + bFound = true + } + if v.ID == c.ID { + cFound = true + } + } + + if !bFound { + t.Error("expected to find b") + } + if !cFound { + t.Error("expected to find c") + } + + slice := PostSlice{&a} + if err = a.L.LoadTags(ctx, tx, false, (*[]*Post)(&slice), nil); err != nil { + t.Fatal(err) + } + if got := len(a.R.Tags); got != 2 { + t.Error("number of eager loaded records wrong, got:", got) + } + + a.R.Tags = nil + if err = a.L.LoadTags(ctx, tx, true, &a, nil); err != nil { + t.Fatal(err) + } + if got := len(a.R.Tags); got != 2 { + t.Error("number of eager loaded records wrong, got:", got) + } + + if t.Failed() { + t.Logf("%#v", check) + } +} + +func testPostToManyAddOpTags(t *testing.T) { + var err error + + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + + var a Post + var b, c, d, e Tag + + seed := randomize.NewSeed() + if err = randomize.Struct(seed, &a, postDBTypes, false, strmangle.SetComplement(postPrimaryKeyColumns, postColumnsWithoutDefault)...); err != nil { + t.Fatal(err) + } + foreigners := []*Tag{&b, &c, &d, &e} + for _, x := range foreigners { + if err = randomize.Struct(seed, x, tagDBTypes, false, strmangle.SetComplement(tagPrimaryKeyColumns, tagColumnsWithoutDefault)...); err != nil { + t.Fatal(err) + } + } + + if err := a.Insert(ctx, tx, boil.Infer()); err != nil { + t.Fatal(err) + } + if err = b.Insert(ctx, tx, boil.Infer()); err != nil { + t.Fatal(err) + } + if err = c.Insert(ctx, tx, boil.Infer()); err != nil { + t.Fatal(err) + } + + foreignersSplitByInsertion := [][]*Tag{ + {&b, &c}, + {&d, &e}, + } + + for i, x := range foreignersSplitByInsertion { + err = a.AddTags(ctx, tx, i != 0, x...) + if err != nil { + t.Fatal(err) + } + + first := x[0] + second := x[1] + + if first.R.Posts[0] != &a { + t.Error("relationship was not added properly to the slice") + } + if second.R.Posts[0] != &a { + t.Error("relationship was not added properly to the slice") + } + + if a.R.Tags[i*2] != first { + t.Error("relationship struct slice not set to correct value") + } + if a.R.Tags[i*2+1] != second { + t.Error("relationship struct slice not set to correct value") + } + + count, err := a.Tags().Count(ctx, tx) + if err != nil { + t.Fatal(err) + } + if want := int64((i + 1) * 2); count != want { + t.Error("want", want, "got", count) + } + } +} + +func testPostToManySetOpTags(t *testing.T) { + var err error + + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + + var a Post + var b, c, d, e Tag + + seed := randomize.NewSeed() + if err = randomize.Struct(seed, &a, postDBTypes, false, strmangle.SetComplement(postPrimaryKeyColumns, postColumnsWithoutDefault)...); err != nil { + t.Fatal(err) + } + foreigners := []*Tag{&b, &c, &d, &e} + for _, x := range foreigners { + if err = randomize.Struct(seed, x, tagDBTypes, false, strmangle.SetComplement(tagPrimaryKeyColumns, tagColumnsWithoutDefault)...); err != nil { + t.Fatal(err) + } + } + + if err = a.Insert(ctx, tx, boil.Infer()); err != nil { + t.Fatal(err) + } + if err = b.Insert(ctx, tx, boil.Infer()); err != nil { + t.Fatal(err) + } + if err = c.Insert(ctx, tx, boil.Infer()); err != nil { + t.Fatal(err) + } + + err = a.SetTags(ctx, tx, false, &b, &c) + if err != nil { + t.Fatal(err) + } + + count, err := a.Tags().Count(ctx, tx) + if err != nil { + t.Fatal(err) + } + if count != 2 { + t.Error("count was wrong:", count) + } + + err = a.SetTags(ctx, tx, true, &d, &e) + if err != nil { + t.Fatal(err) + } + + count, err = a.Tags().Count(ctx, tx) + if err != nil { + t.Fatal(err) + } + if count != 2 { + t.Error("count was wrong:", count) + } + + // The following checks cannot be implemented since we have no handle + // to these when we call Set(). Leaving them here as wishful thinking + // and to let people know there's dragons. + // + // if len(b.R.Posts) != 0 { + // t.Error("relationship was not removed properly from the slice") + // } + // if len(c.R.Posts) != 0 { + // t.Error("relationship was not removed properly from the slice") + // } + if d.R.Posts[0] != &a { + t.Error("relationship was not added properly to the slice") + } + if e.R.Posts[0] != &a { + t.Error("relationship was not added properly to the slice") + } + + if a.R.Tags[0] != &d { + t.Error("relationship struct slice not set to correct value") + } + if a.R.Tags[1] != &e { + t.Error("relationship struct slice not set to correct value") + } +} + +func testPostToManyRemoveOpTags(t *testing.T) { + var err error + + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + + var a Post + var b, c, d, e Tag + + seed := randomize.NewSeed() + if err = randomize.Struct(seed, &a, postDBTypes, false, strmangle.SetComplement(postPrimaryKeyColumns, postColumnsWithoutDefault)...); err != nil { + t.Fatal(err) + } + foreigners := []*Tag{&b, &c, &d, &e} + for _, x := range foreigners { + if err = randomize.Struct(seed, x, tagDBTypes, false, strmangle.SetComplement(tagPrimaryKeyColumns, tagColumnsWithoutDefault)...); err != nil { + t.Fatal(err) + } + } + + if err := a.Insert(ctx, tx, boil.Infer()); err != nil { + t.Fatal(err) + } + + err = a.AddTags(ctx, tx, true, foreigners...) + if err != nil { + t.Fatal(err) + } + + count, err := a.Tags().Count(ctx, tx) + if err != nil { + t.Fatal(err) + } + if count != 4 { + t.Error("count was wrong:", count) + } + + err = a.RemoveTags(ctx, tx, foreigners[:2]...) + if err != nil { + t.Fatal(err) + } + + count, err = a.Tags().Count(ctx, tx) + if err != nil { + t.Fatal(err) + } + if count != 2 { + t.Error("count was wrong:", count) + } + + if len(b.R.Posts) != 0 { + t.Error("relationship was not removed properly from the slice") + } + if len(c.R.Posts) != 0 { + t.Error("relationship was not removed properly from the slice") + } + if d.R.Posts[0] != &a { + t.Error("relationship was not added properly to the foreign struct") + } + if e.R.Posts[0] != &a { + t.Error("relationship was not added properly to the foreign struct") + } + + if len(a.R.Tags) != 2 { + t.Error("should have preserved two relationships") + } + + // Removal doesn't do a stable deletion for performance so we have to flip the order + if a.R.Tags[1] != &d { + t.Error("relationship to d should have been preserved") + } + if a.R.Tags[0] != &e { + t.Error("relationship to e should have been preserved") + } +} + +func testPostToOneUserUsingUser(t *testing.T) { + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + + var local Post + var foreign User + + seed := randomize.NewSeed() + if err := randomize.Struct(seed, &local, postDBTypes, false, postColumnsWithDefault...); err != nil { + t.Errorf("Unable to randomize Post struct: %s", err) + } + if err := randomize.Struct(seed, &foreign, userDBTypes, false, userColumnsWithDefault...); err != nil { + t.Errorf("Unable to randomize User struct: %s", err) + } + + if err := foreign.Insert(ctx, tx, boil.Infer()); err != nil { + t.Fatal(err) + } + + local.UserID = foreign.ID + if err := local.Insert(ctx, tx, boil.Infer()); err != nil { + t.Fatal(err) + } + + check, err := local.User().One(ctx, tx) + if err != nil { + t.Fatal(err) + } + + if check.ID != foreign.ID { + t.Errorf("want: %v, got %v", foreign.ID, check.ID) + } + + ranAfterSelectHook := false + AddUserHook(boil.AfterSelectHook, func(ctx context.Context, e boil.ContextExecutor, o *User) error { + ranAfterSelectHook = true + return nil + }) + + slice := PostSlice{&local} + if err = local.L.LoadUser(ctx, tx, false, (*[]*Post)(&slice), nil); err != nil { + t.Fatal(err) + } + if local.R.User == nil { + t.Error("struct should have been eager loaded") + } + + local.R.User = nil + if err = local.L.LoadUser(ctx, tx, true, &local, nil); err != nil { + t.Fatal(err) + } + if local.R.User == nil { + t.Error("struct should have been eager loaded") + } + + if !ranAfterSelectHook { + t.Error("failed to run AfterSelect hook for relationship") + } +} + +func testPostToOneSetOpUserUsingUser(t *testing.T) { + var err error + + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + + var a Post + var b, c User + + seed := randomize.NewSeed() + if err = randomize.Struct(seed, &a, postDBTypes, false, strmangle.SetComplement(postPrimaryKeyColumns, postColumnsWithoutDefault)...); err != nil { + t.Fatal(err) + } + if err = randomize.Struct(seed, &b, userDBTypes, false, strmangle.SetComplement(userPrimaryKeyColumns, userColumnsWithoutDefault)...); err != nil { + t.Fatal(err) + } + if err = randomize.Struct(seed, &c, userDBTypes, false, strmangle.SetComplement(userPrimaryKeyColumns, userColumnsWithoutDefault)...); err != nil { + t.Fatal(err) + } + + if err := a.Insert(ctx, tx, boil.Infer()); err != nil { + t.Fatal(err) + } + if err = b.Insert(ctx, tx, boil.Infer()); err != nil { + t.Fatal(err) + } + + for i, x := range []*User{&b, &c} { + err = a.SetUser(ctx, tx, i != 0, x) + if err != nil { + t.Fatal(err) + } + + if a.R.User != x { + t.Error("relationship struct not set to correct value") + } + + if x.R.Posts[0] != &a { + t.Error("failed to append to foreign relationship struct") + } + if a.UserID != x.ID { + t.Error("foreign key was wrong value", a.UserID) + } + + zero := reflect.Zero(reflect.TypeOf(a.UserID)) + reflect.Indirect(reflect.ValueOf(&a.UserID)).Set(zero) + + if err = a.Reload(ctx, tx); err != nil { + t.Fatal("failed to reload", err) + } + + if a.UserID != x.ID { + t.Error("foreign key was wrong value", a.UserID, x.ID) + } + } +} + func testPostsReload(t *testing.T) { t.Parallel() @@ -568,7 +999,7 @@ func testPostsSelect(t *testing.T) { } var ( - postDBTypes = map[string]string{`ID`: `integer`, `Title`: `character varying`, `URL`: `character varying`, `Description`: `text`, `CreatedAt`: `timestamp with time zone`, `UpdatedAt`: `timestamp with time zone`} + postDBTypes = map[string]string{`ID`: `integer`, `Title`: `character varying`, `Description`: `text`, `URL`: `character varying`, `UserID`: `integer`, `CreatedAt`: `timestamp with time zone`, `UpdatedAt`: `timestamp with time zone`} _ = bytes.MinRead ) diff --git a/src/models/psql_suites_test.go b/src/models/psql_suites_test.go index c552b41..b49f957 100644 --- a/src/models/psql_suites_test.go +++ b/src/models/psql_suites_test.go @@ -8,5 +8,7 @@ import "testing" func TestUpsert(t *testing.T) { t.Run("Posts", testPostsUpsert) + t.Run("Tags", testTagsUpsert) + t.Run("Users", testUsersUpsert) } diff --git a/src/models/tags.go b/src/models/tags.go new file mode 100644 index 0000000..707aa59 --- /dev/null +++ b/src/models/tags.go @@ -0,0 +1,1469 @@ +// Code generated by SQLBoiler 4.16.1 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package models + +import ( + "context" + "database/sql" + "fmt" + "reflect" + "strconv" + "strings" + "sync" + "time" + + "github.com/friendsofgo/errors" + "github.com/volatiletech/sqlboiler/v4/boil" + "github.com/volatiletech/sqlboiler/v4/queries" + "github.com/volatiletech/sqlboiler/v4/queries/qm" + "github.com/volatiletech/sqlboiler/v4/queries/qmhelper" + "github.com/volatiletech/strmangle" +) + +// Tag is an object representing the database table. +type Tag struct { + ID int `boil:"id" json:"id" toml:"id" yaml:"id"` + Tag string `boil:"tag" json:"tag" toml:"tag" yaml:"tag"` + Description string `boil:"description" json:"description" toml:"description" yaml:"description"` + UserID int `boil:"user_id" json:"user_id" toml:"user_id" yaml:"user_id"` + CreatedAt time.Time `boil:"created_at" json:"created_at" toml:"created_at" yaml:"created_at"` + UpdatedAt time.Time `boil:"updated_at" json:"updated_at" toml:"updated_at" yaml:"updated_at"` + + R *tagR `boil:"-" json:"-" toml:"-" yaml:"-"` + L tagL `boil:"-" json:"-" toml:"-" yaml:"-"` +} + +var TagColumns = struct { + ID string + Tag string + Description string + UserID string + CreatedAt string + UpdatedAt string +}{ + ID: "id", + Tag: "tag", + Description: "description", + UserID: "user_id", + CreatedAt: "created_at", + UpdatedAt: "updated_at", +} + +var TagTableColumns = struct { + ID string + Tag string + Description string + UserID string + CreatedAt string + UpdatedAt string +}{ + ID: "tags.id", + Tag: "tags.tag", + Description: "tags.description", + UserID: "tags.user_id", + CreatedAt: "tags.created_at", + UpdatedAt: "tags.updated_at", +} + +// Generated where + +var TagWhere = struct { + ID whereHelperint + Tag whereHelperstring + Description whereHelperstring + UserID whereHelperint + CreatedAt whereHelpertime_Time + UpdatedAt whereHelpertime_Time +}{ + ID: whereHelperint{field: "\"tags\".\"id\""}, + Tag: whereHelperstring{field: "\"tags\".\"tag\""}, + Description: whereHelperstring{field: "\"tags\".\"description\""}, + UserID: whereHelperint{field: "\"tags\".\"user_id\""}, + CreatedAt: whereHelpertime_Time{field: "\"tags\".\"created_at\""}, + UpdatedAt: whereHelpertime_Time{field: "\"tags\".\"updated_at\""}, +} + +// TagRels is where relationship names are stored. +var TagRels = struct { + User string + Posts string +}{ + User: "User", + Posts: "Posts", +} + +// tagR is where relationships are stored. +type tagR struct { + User *User `boil:"User" json:"User" toml:"User" yaml:"User"` + Posts PostSlice `boil:"Posts" json:"Posts" toml:"Posts" yaml:"Posts"` +} + +// NewStruct creates a new relationship struct +func (*tagR) NewStruct() *tagR { + return &tagR{} +} + +func (r *tagR) GetUser() *User { + if r == nil { + return nil + } + return r.User +} + +func (r *tagR) GetPosts() PostSlice { + if r == nil { + return nil + } + return r.Posts +} + +// tagL is where Load methods for each relationship are stored. +type tagL struct{} + +var ( + tagAllColumns = []string{"id", "tag", "description", "user_id", "created_at", "updated_at"} + tagColumnsWithoutDefault = []string{"tag", "description", "user_id"} + tagColumnsWithDefault = []string{"id", "created_at", "updated_at"} + tagPrimaryKeyColumns = []string{"id"} + tagGeneratedColumns = []string{} +) + +type ( + // TagSlice is an alias for a slice of pointers to Tag. + // This should almost always be used instead of []Tag. + TagSlice []*Tag + // TagHook is the signature for custom Tag hook methods + TagHook func(context.Context, boil.ContextExecutor, *Tag) error + + tagQuery struct { + *queries.Query + } +) + +// Cache for insert, update and upsert +var ( + tagType = reflect.TypeOf(&Tag{}) + tagMapping = queries.MakeStructMapping(tagType) + tagPrimaryKeyMapping, _ = queries.BindMapping(tagType, tagMapping, tagPrimaryKeyColumns) + tagInsertCacheMut sync.RWMutex + tagInsertCache = make(map[string]insertCache) + tagUpdateCacheMut sync.RWMutex + tagUpdateCache = make(map[string]updateCache) + tagUpsertCacheMut sync.RWMutex + tagUpsertCache = make(map[string]insertCache) +) + +var ( + // Force time package dependency for automated UpdatedAt/CreatedAt. + _ = time.Second + // Force qmhelper dependency for where clause generation (which doesn't + // always happen) + _ = qmhelper.Where +) + +var tagAfterSelectMu sync.Mutex +var tagAfterSelectHooks []TagHook + +var tagBeforeInsertMu sync.Mutex +var tagBeforeInsertHooks []TagHook +var tagAfterInsertMu sync.Mutex +var tagAfterInsertHooks []TagHook + +var tagBeforeUpdateMu sync.Mutex +var tagBeforeUpdateHooks []TagHook +var tagAfterUpdateMu sync.Mutex +var tagAfterUpdateHooks []TagHook + +var tagBeforeDeleteMu sync.Mutex +var tagBeforeDeleteHooks []TagHook +var tagAfterDeleteMu sync.Mutex +var tagAfterDeleteHooks []TagHook + +var tagBeforeUpsertMu sync.Mutex +var tagBeforeUpsertHooks []TagHook +var tagAfterUpsertMu sync.Mutex +var tagAfterUpsertHooks []TagHook + +// doAfterSelectHooks executes all "after Select" hooks. +func (o *Tag) doAfterSelectHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range tagAfterSelectHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// doBeforeInsertHooks executes all "before insert" hooks. +func (o *Tag) doBeforeInsertHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range tagBeforeInsertHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// doAfterInsertHooks executes all "after Insert" hooks. +func (o *Tag) doAfterInsertHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range tagAfterInsertHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// doBeforeUpdateHooks executes all "before Update" hooks. +func (o *Tag) doBeforeUpdateHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range tagBeforeUpdateHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// doAfterUpdateHooks executes all "after Update" hooks. +func (o *Tag) doAfterUpdateHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range tagAfterUpdateHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// doBeforeDeleteHooks executes all "before Delete" hooks. +func (o *Tag) doBeforeDeleteHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range tagBeforeDeleteHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// doAfterDeleteHooks executes all "after Delete" hooks. +func (o *Tag) doAfterDeleteHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range tagAfterDeleteHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// doBeforeUpsertHooks executes all "before Upsert" hooks. +func (o *Tag) doBeforeUpsertHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range tagBeforeUpsertHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// doAfterUpsertHooks executes all "after Upsert" hooks. +func (o *Tag) doAfterUpsertHooks(ctx context.Context, exec boil.ContextExecutor) (err error) { + if boil.HooksAreSkipped(ctx) { + return nil + } + + for _, hook := range tagAfterUpsertHooks { + if err := hook(ctx, exec, o); err != nil { + return err + } + } + + return nil +} + +// AddTagHook registers your hook function for all future operations. +func AddTagHook(hookPoint boil.HookPoint, tagHook TagHook) { + switch hookPoint { + case boil.AfterSelectHook: + tagAfterSelectMu.Lock() + tagAfterSelectHooks = append(tagAfterSelectHooks, tagHook) + tagAfterSelectMu.Unlock() + case boil.BeforeInsertHook: + tagBeforeInsertMu.Lock() + tagBeforeInsertHooks = append(tagBeforeInsertHooks, tagHook) + tagBeforeInsertMu.Unlock() + case boil.AfterInsertHook: + tagAfterInsertMu.Lock() + tagAfterInsertHooks = append(tagAfterInsertHooks, tagHook) + tagAfterInsertMu.Unlock() + case boil.BeforeUpdateHook: + tagBeforeUpdateMu.Lock() + tagBeforeUpdateHooks = append(tagBeforeUpdateHooks, tagHook) + tagBeforeUpdateMu.Unlock() + case boil.AfterUpdateHook: + tagAfterUpdateMu.Lock() + tagAfterUpdateHooks = append(tagAfterUpdateHooks, tagHook) + tagAfterUpdateMu.Unlock() + case boil.BeforeDeleteHook: + tagBeforeDeleteMu.Lock() + tagBeforeDeleteHooks = append(tagBeforeDeleteHooks, tagHook) + tagBeforeDeleteMu.Unlock() + case boil.AfterDeleteHook: + tagAfterDeleteMu.Lock() + tagAfterDeleteHooks = append(tagAfterDeleteHooks, tagHook) + tagAfterDeleteMu.Unlock() + case boil.BeforeUpsertHook: + tagBeforeUpsertMu.Lock() + tagBeforeUpsertHooks = append(tagBeforeUpsertHooks, tagHook) + tagBeforeUpsertMu.Unlock() + case boil.AfterUpsertHook: + tagAfterUpsertMu.Lock() + tagAfterUpsertHooks = append(tagAfterUpsertHooks, tagHook) + tagAfterUpsertMu.Unlock() + } +} + +// One returns a single tag record from the query. +func (q tagQuery) One(ctx context.Context, exec boil.ContextExecutor) (*Tag, error) { + o := &Tag{} + + queries.SetLimit(q.Query, 1) + + err := q.Bind(ctx, exec, o) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return nil, sql.ErrNoRows + } + return nil, errors.Wrap(err, "models: failed to execute a one query for tags") + } + + if err := o.doAfterSelectHooks(ctx, exec); err != nil { + return o, err + } + + return o, nil +} + +// All returns all Tag records from the query. +func (q tagQuery) All(ctx context.Context, exec boil.ContextExecutor) (TagSlice, error) { + var o []*Tag + + err := q.Bind(ctx, exec, &o) + if err != nil { + return nil, errors.Wrap(err, "models: failed to assign all query results to Tag slice") + } + + if len(tagAfterSelectHooks) != 0 { + for _, obj := range o { + if err := obj.doAfterSelectHooks(ctx, exec); err != nil { + return o, err + } + } + } + + return o, nil +} + +// Count returns the count of all Tag records in the query. +func (q tagQuery) Count(ctx context.Context, exec boil.ContextExecutor) (int64, error) { + var count int64 + + queries.SetSelect(q.Query, nil) + queries.SetCount(q.Query) + + err := q.Query.QueryRowContext(ctx, exec).Scan(&count) + if err != nil { + return 0, errors.Wrap(err, "models: failed to count tags rows") + } + + return count, nil +} + +// Exists checks if the row exists in the table. +func (q tagQuery) Exists(ctx context.Context, exec boil.ContextExecutor) (bool, error) { + var count int64 + + queries.SetSelect(q.Query, nil) + queries.SetCount(q.Query) + queries.SetLimit(q.Query, 1) + + err := q.Query.QueryRowContext(ctx, exec).Scan(&count) + if err != nil { + return false, errors.Wrap(err, "models: failed to check if tags exists") + } + + return count > 0, nil +} + +// User pointed to by the foreign key. +func (o *Tag) User(mods ...qm.QueryMod) userQuery { + queryMods := []qm.QueryMod{ + qm.Where("\"id\" = ?", o.UserID), + } + + queryMods = append(queryMods, mods...) + + return Users(queryMods...) +} + +// Posts retrieves all the post's Posts with an executor. +func (o *Tag) Posts(mods ...qm.QueryMod) postQuery { + var queryMods []qm.QueryMod + if len(mods) != 0 { + queryMods = append(queryMods, mods...) + } + + queryMods = append(queryMods, + qm.InnerJoin("\"post_tags\" on \"posts\".\"id\" = \"post_tags\".\"post_id\""), + qm.Where("\"post_tags\".\"tag_id\"=?", o.ID), + ) + + return Posts(queryMods...) +} + +// LoadUser allows an eager lookup of values, cached into the +// loaded structs of the objects. This is for an N-1 relationship. +func (tagL) LoadUser(ctx context.Context, e boil.ContextExecutor, singular bool, maybeTag interface{}, mods queries.Applicator) error { + var slice []*Tag + var object *Tag + + if singular { + var ok bool + object, ok = maybeTag.(*Tag) + if !ok { + object = new(Tag) + ok = queries.SetFromEmbeddedStruct(&object, &maybeTag) + if !ok { + return errors.New(fmt.Sprintf("failed to set %T from embedded struct %T", object, maybeTag)) + } + } + } else { + s, ok := maybeTag.(*[]*Tag) + if ok { + slice = *s + } else { + ok = queries.SetFromEmbeddedStruct(&slice, maybeTag) + if !ok { + return errors.New(fmt.Sprintf("failed to set %T from embedded struct %T", slice, maybeTag)) + } + } + } + + args := make(map[interface{}]struct{}) + if singular { + if object.R == nil { + object.R = &tagR{} + } + args[object.UserID] = struct{}{} + + } else { + for _, obj := range slice { + if obj.R == nil { + obj.R = &tagR{} + } + + args[obj.UserID] = struct{}{} + + } + } + + if len(args) == 0 { + return nil + } + + argsSlice := make([]interface{}, len(args)) + i := 0 + for arg := range args { + argsSlice[i] = arg + i++ + } + + query := NewQuery( + qm.From(`users`), + qm.WhereIn(`users.id in ?`, argsSlice...), + ) + if mods != nil { + mods.Apply(query) + } + + results, err := query.QueryContext(ctx, e) + if err != nil { + return errors.Wrap(err, "failed to eager load User") + } + + var resultSlice []*User + if err = queries.Bind(results, &resultSlice); err != nil { + return errors.Wrap(err, "failed to bind eager loaded slice User") + } + + if err = results.Close(); err != nil { + return errors.Wrap(err, "failed to close results of eager load for users") + } + if err = results.Err(); err != nil { + return errors.Wrap(err, "error occurred during iteration of eager loaded relations for users") + } + + if len(userAfterSelectHooks) != 0 { + for _, obj := range resultSlice { + if err := obj.doAfterSelectHooks(ctx, e); err != nil { + return err + } + } + } + + if len(resultSlice) == 0 { + return nil + } + + if singular { + foreign := resultSlice[0] + object.R.User = foreign + if foreign.R == nil { + foreign.R = &userR{} + } + foreign.R.Tags = append(foreign.R.Tags, object) + return nil + } + + for _, local := range slice { + for _, foreign := range resultSlice { + if local.UserID == foreign.ID { + local.R.User = foreign + if foreign.R == nil { + foreign.R = &userR{} + } + foreign.R.Tags = append(foreign.R.Tags, local) + break + } + } + } + + return nil +} + +// LoadPosts allows an eager lookup of values, cached into the +// loaded structs of the objects. This is for a 1-M or N-M relationship. +func (tagL) LoadPosts(ctx context.Context, e boil.ContextExecutor, singular bool, maybeTag interface{}, mods queries.Applicator) error { + var slice []*Tag + var object *Tag + + if singular { + var ok bool + object, ok = maybeTag.(*Tag) + if !ok { + object = new(Tag) + ok = queries.SetFromEmbeddedStruct(&object, &maybeTag) + if !ok { + return errors.New(fmt.Sprintf("failed to set %T from embedded struct %T", object, maybeTag)) + } + } + } else { + s, ok := maybeTag.(*[]*Tag) + if ok { + slice = *s + } else { + ok = queries.SetFromEmbeddedStruct(&slice, maybeTag) + if !ok { + return errors.New(fmt.Sprintf("failed to set %T from embedded struct %T", slice, maybeTag)) + } + } + } + + args := make(map[interface{}]struct{}) + if singular { + if object.R == nil { + object.R = &tagR{} + } + args[object.ID] = struct{}{} + } else { + for _, obj := range slice { + if obj.R == nil { + obj.R = &tagR{} + } + args[obj.ID] = struct{}{} + } + } + + if len(args) == 0 { + return nil + } + + argsSlice := make([]interface{}, len(args)) + i := 0 + for arg := range args { + argsSlice[i] = arg + i++ + } + + query := NewQuery( + qm.Select("\"posts\".\"id\", \"posts\".\"title\", \"posts\".\"description\", \"posts\".\"url\", \"posts\".\"user_id\", \"posts\".\"created_at\", \"posts\".\"updated_at\", \"a\".\"tag_id\""), + qm.From("\"posts\""), + qm.InnerJoin("\"post_tags\" as \"a\" on \"posts\".\"id\" = \"a\".\"post_id\""), + qm.WhereIn("\"a\".\"tag_id\" in ?", argsSlice...), + ) + if mods != nil { + mods.Apply(query) + } + + results, err := query.QueryContext(ctx, e) + if err != nil { + return errors.Wrap(err, "failed to eager load posts") + } + + var resultSlice []*Post + + var localJoinCols []int + for results.Next() { + one := new(Post) + var localJoinCol int + + err = results.Scan(&one.ID, &one.Title, &one.Description, &one.URL, &one.UserID, &one.CreatedAt, &one.UpdatedAt, &localJoinCol) + if err != nil { + return errors.Wrap(err, "failed to scan eager loaded results for posts") + } + if err = results.Err(); err != nil { + return errors.Wrap(err, "failed to plebian-bind eager loaded slice posts") + } + + resultSlice = append(resultSlice, one) + localJoinCols = append(localJoinCols, localJoinCol) + } + + if err = results.Close(); err != nil { + return errors.Wrap(err, "failed to close results in eager load on posts") + } + if err = results.Err(); err != nil { + return errors.Wrap(err, "error occurred during iteration of eager loaded relations for posts") + } + + if len(postAfterSelectHooks) != 0 { + for _, obj := range resultSlice { + if err := obj.doAfterSelectHooks(ctx, e); err != nil { + return err + } + } + } + if singular { + object.R.Posts = resultSlice + for _, foreign := range resultSlice { + if foreign.R == nil { + foreign.R = &postR{} + } + foreign.R.Tags = append(foreign.R.Tags, object) + } + return nil + } + + for i, foreign := range resultSlice { + localJoinCol := localJoinCols[i] + for _, local := range slice { + if local.ID == localJoinCol { + local.R.Posts = append(local.R.Posts, foreign) + if foreign.R == nil { + foreign.R = &postR{} + } + foreign.R.Tags = append(foreign.R.Tags, local) + break + } + } + } + + return nil +} + +// SetUser of the tag to the related item. +// Sets o.R.User to related. +// Adds o to related.R.Tags. +func (o *Tag) SetUser(ctx context.Context, exec boil.ContextExecutor, insert bool, related *User) error { + var err error + if insert { + if err = related.Insert(ctx, exec, boil.Infer()); err != nil { + return errors.Wrap(err, "failed to insert into foreign table") + } + } + + updateQuery := fmt.Sprintf( + "UPDATE \"tags\" SET %s WHERE %s", + strmangle.SetParamNames("\"", "\"", 1, []string{"user_id"}), + strmangle.WhereClause("\"", "\"", 2, tagPrimaryKeyColumns), + ) + values := []interface{}{related.ID, o.ID} + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, updateQuery) + fmt.Fprintln(writer, values) + } + if _, err = exec.ExecContext(ctx, updateQuery, values...); err != nil { + return errors.Wrap(err, "failed to update local table") + } + + o.UserID = related.ID + if o.R == nil { + o.R = &tagR{ + User: related, + } + } else { + o.R.User = related + } + + if related.R == nil { + related.R = &userR{ + Tags: TagSlice{o}, + } + } else { + related.R.Tags = append(related.R.Tags, o) + } + + return nil +} + +// AddPosts adds the given related objects to the existing relationships +// of the tag, optionally inserting them as new records. +// Appends related to o.R.Posts. +// Sets related.R.Tags appropriately. +func (o *Tag) AddPosts(ctx context.Context, exec boil.ContextExecutor, insert bool, related ...*Post) error { + var err error + for _, rel := range related { + if insert { + if err = rel.Insert(ctx, exec, boil.Infer()); err != nil { + return errors.Wrap(err, "failed to insert into foreign table") + } + } + } + + for _, rel := range related { + query := "insert into \"post_tags\" (\"tag_id\", \"post_id\") values ($1, $2)" + values := []interface{}{o.ID, rel.ID} + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, query) + fmt.Fprintln(writer, values) + } + _, err = exec.ExecContext(ctx, query, values...) + if err != nil { + return errors.Wrap(err, "failed to insert into join table") + } + } + if o.R == nil { + o.R = &tagR{ + Posts: related, + } + } else { + o.R.Posts = append(o.R.Posts, related...) + } + + for _, rel := range related { + if rel.R == nil { + rel.R = &postR{ + Tags: TagSlice{o}, + } + } else { + rel.R.Tags = append(rel.R.Tags, o) + } + } + return nil +} + +// SetPosts removes all previously related items of the +// tag replacing them completely with the passed +// in related items, optionally inserting them as new records. +// Sets o.R.Tags's Posts accordingly. +// Replaces o.R.Posts with related. +// Sets related.R.Tags's Posts accordingly. +func (o *Tag) SetPosts(ctx context.Context, exec boil.ContextExecutor, insert bool, related ...*Post) error { + query := "delete from \"post_tags\" where \"tag_id\" = $1" + values := []interface{}{o.ID} + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, query) + fmt.Fprintln(writer, values) + } + _, err := exec.ExecContext(ctx, query, values...) + if err != nil { + return errors.Wrap(err, "failed to remove relationships before set") + } + + removePostsFromTagsSlice(o, related) + if o.R != nil { + o.R.Posts = nil + } + + return o.AddPosts(ctx, exec, insert, related...) +} + +// RemovePosts relationships from objects passed in. +// Removes related items from R.Posts (uses pointer comparison, removal does not keep order) +// Sets related.R.Tags. +func (o *Tag) RemovePosts(ctx context.Context, exec boil.ContextExecutor, related ...*Post) error { + if len(related) == 0 { + return nil + } + + var err error + query := fmt.Sprintf( + "delete from \"post_tags\" where \"tag_id\" = $1 and \"post_id\" in (%s)", + strmangle.Placeholders(dialect.UseIndexPlaceholders, len(related), 2, 1), + ) + values := []interface{}{o.ID} + for _, rel := range related { + values = append(values, rel.ID) + } + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, query) + fmt.Fprintln(writer, values) + } + _, err = exec.ExecContext(ctx, query, values...) + if err != nil { + return errors.Wrap(err, "failed to remove relationships before set") + } + removePostsFromTagsSlice(o, related) + if o.R == nil { + return nil + } + + for _, rel := range related { + for i, ri := range o.R.Posts { + if rel != ri { + continue + } + + ln := len(o.R.Posts) + if ln > 1 && i < ln-1 { + o.R.Posts[i] = o.R.Posts[ln-1] + } + o.R.Posts = o.R.Posts[:ln-1] + break + } + } + + return nil +} + +func removePostsFromTagsSlice(o *Tag, related []*Post) { + for _, rel := range related { + if rel.R == nil { + continue + } + for i, ri := range rel.R.Tags { + if o.ID != ri.ID { + continue + } + + ln := len(rel.R.Tags) + if ln > 1 && i < ln-1 { + rel.R.Tags[i] = rel.R.Tags[ln-1] + } + rel.R.Tags = rel.R.Tags[:ln-1] + break + } + } +} + +// Tags retrieves all the records using an executor. +func Tags(mods ...qm.QueryMod) tagQuery { + mods = append(mods, qm.From("\"tags\"")) + q := NewQuery(mods...) + if len(queries.GetSelect(q)) == 0 { + queries.SetSelect(q, []string{"\"tags\".*"}) + } + + return tagQuery{q} +} + +// FindTag retrieves a single record by ID with an executor. +// If selectCols is empty Find will return all columns. +func FindTag(ctx context.Context, exec boil.ContextExecutor, iD int, selectCols ...string) (*Tag, error) { + tagObj := &Tag{} + + sel := "*" + if len(selectCols) > 0 { + sel = strings.Join(strmangle.IdentQuoteSlice(dialect.LQ, dialect.RQ, selectCols), ",") + } + query := fmt.Sprintf( + "select %s from \"tags\" where \"id\"=$1", sel, + ) + + q := queries.Raw(query, iD) + + err := q.Bind(ctx, exec, tagObj) + if err != nil { + if errors.Is(err, sql.ErrNoRows) { + return nil, sql.ErrNoRows + } + return nil, errors.Wrap(err, "models: unable to select from tags") + } + + if err = tagObj.doAfterSelectHooks(ctx, exec); err != nil { + return tagObj, err + } + + return tagObj, nil +} + +// Insert a single record using an executor. +// See boil.Columns.InsertColumnSet documentation to understand column list inference for inserts. +func (o *Tag) Insert(ctx context.Context, exec boil.ContextExecutor, columns boil.Columns) error { + if o == nil { + return errors.New("models: no tags provided for insertion") + } + + var err error + if !boil.TimestampsAreSkipped(ctx) { + currTime := time.Now().In(boil.GetLocation()) + + if o.CreatedAt.IsZero() { + o.CreatedAt = currTime + } + if o.UpdatedAt.IsZero() { + o.UpdatedAt = currTime + } + } + + if err := o.doBeforeInsertHooks(ctx, exec); err != nil { + return err + } + + nzDefaults := queries.NonZeroDefaultSet(tagColumnsWithDefault, o) + + key := makeCacheKey(columns, nzDefaults) + tagInsertCacheMut.RLock() + cache, cached := tagInsertCache[key] + tagInsertCacheMut.RUnlock() + + if !cached { + wl, returnColumns := columns.InsertColumnSet( + tagAllColumns, + tagColumnsWithDefault, + tagColumnsWithoutDefault, + nzDefaults, + ) + + cache.valueMapping, err = queries.BindMapping(tagType, tagMapping, wl) + if err != nil { + return err + } + cache.retMapping, err = queries.BindMapping(tagType, tagMapping, returnColumns) + if err != nil { + return err + } + if len(wl) != 0 { + cache.query = fmt.Sprintf("INSERT INTO \"tags\" (\"%s\") %%sVALUES (%s)%%s", strings.Join(wl, "\",\""), strmangle.Placeholders(dialect.UseIndexPlaceholders, len(wl), 1, 1)) + } else { + cache.query = "INSERT INTO \"tags\" %sDEFAULT VALUES%s" + } + + var queryOutput, queryReturning string + + if len(cache.retMapping) != 0 { + queryReturning = fmt.Sprintf(" RETURNING \"%s\"", strings.Join(returnColumns, "\",\"")) + } + + cache.query = fmt.Sprintf(cache.query, queryOutput, queryReturning) + } + + value := reflect.Indirect(reflect.ValueOf(o)) + vals := queries.ValuesFromMapping(value, cache.valueMapping) + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, cache.query) + fmt.Fprintln(writer, vals) + } + + if len(cache.retMapping) != 0 { + err = exec.QueryRowContext(ctx, cache.query, vals...).Scan(queries.PtrsFromMapping(value, cache.retMapping)...) + } else { + _, err = exec.ExecContext(ctx, cache.query, vals...) + } + + if err != nil { + return errors.Wrap(err, "models: unable to insert into tags") + } + + if !cached { + tagInsertCacheMut.Lock() + tagInsertCache[key] = cache + tagInsertCacheMut.Unlock() + } + + return o.doAfterInsertHooks(ctx, exec) +} + +// Update uses an executor to update the Tag. +// See boil.Columns.UpdateColumnSet documentation to understand column list inference for updates. +// Update does not automatically update the record in case of default values. Use .Reload() to refresh the records. +func (o *Tag) Update(ctx context.Context, exec boil.ContextExecutor, columns boil.Columns) (int64, error) { + if !boil.TimestampsAreSkipped(ctx) { + currTime := time.Now().In(boil.GetLocation()) + + o.UpdatedAt = currTime + } + + var err error + if err = o.doBeforeUpdateHooks(ctx, exec); err != nil { + return 0, err + } + key := makeCacheKey(columns, nil) + tagUpdateCacheMut.RLock() + cache, cached := tagUpdateCache[key] + tagUpdateCacheMut.RUnlock() + + if !cached { + wl := columns.UpdateColumnSet( + tagAllColumns, + tagPrimaryKeyColumns, + ) + + if !columns.IsWhitelist() { + wl = strmangle.SetComplement(wl, []string{"created_at"}) + } + if len(wl) == 0 { + return 0, errors.New("models: unable to update tags, could not build whitelist") + } + + cache.query = fmt.Sprintf("UPDATE \"tags\" SET %s WHERE %s", + strmangle.SetParamNames("\"", "\"", 1, wl), + strmangle.WhereClause("\"", "\"", len(wl)+1, tagPrimaryKeyColumns), + ) + cache.valueMapping, err = queries.BindMapping(tagType, tagMapping, append(wl, tagPrimaryKeyColumns...)) + if err != nil { + return 0, err + } + } + + values := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(o)), cache.valueMapping) + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, cache.query) + fmt.Fprintln(writer, values) + } + var result sql.Result + result, err = exec.ExecContext(ctx, cache.query, values...) + if err != nil { + return 0, errors.Wrap(err, "models: unable to update tags row") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: failed to get rows affected by update for tags") + } + + if !cached { + tagUpdateCacheMut.Lock() + tagUpdateCache[key] = cache + tagUpdateCacheMut.Unlock() + } + + return rowsAff, o.doAfterUpdateHooks(ctx, exec) +} + +// UpdateAll updates all rows with the specified column values. +func (q tagQuery) UpdateAll(ctx context.Context, exec boil.ContextExecutor, cols M) (int64, error) { + queries.SetUpdate(q.Query, cols) + + result, err := q.Query.ExecContext(ctx, exec) + if err != nil { + return 0, errors.Wrap(err, "models: unable to update all for tags") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: unable to retrieve rows affected for tags") + } + + return rowsAff, nil +} + +// UpdateAll updates all rows with the specified column values, using an executor. +func (o TagSlice) UpdateAll(ctx context.Context, exec boil.ContextExecutor, cols M) (int64, error) { + ln := int64(len(o)) + if ln == 0 { + return 0, nil + } + + if len(cols) == 0 { + return 0, errors.New("models: update all requires at least one column argument") + } + + colNames := make([]string, len(cols)) + args := make([]interface{}, len(cols)) + + i := 0 + for name, value := range cols { + colNames[i] = name + args[i] = value + i++ + } + + // Append all of the primary key values for each column + for _, obj := range o { + pkeyArgs := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(obj)), tagPrimaryKeyMapping) + args = append(args, pkeyArgs...) + } + + sql := fmt.Sprintf("UPDATE \"tags\" SET %s WHERE %s", + strmangle.SetParamNames("\"", "\"", 1, colNames), + strmangle.WhereClauseRepeated(string(dialect.LQ), string(dialect.RQ), len(colNames)+1, tagPrimaryKeyColumns, len(o))) + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, sql) + fmt.Fprintln(writer, args...) + } + result, err := exec.ExecContext(ctx, sql, args...) + if err != nil { + return 0, errors.Wrap(err, "models: unable to update all in tag slice") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: unable to retrieve rows affected all in update all tag") + } + return rowsAff, nil +} + +// Upsert attempts an insert using an executor, and does an update or ignore on conflict. +// See boil.Columns documentation for how to properly use updateColumns and insertColumns. +func (o *Tag) Upsert(ctx context.Context, exec boil.ContextExecutor, updateOnConflict bool, conflictColumns []string, updateColumns, insertColumns boil.Columns, opts ...UpsertOptionFunc) error { + if o == nil { + return errors.New("models: no tags provided for upsert") + } + if !boil.TimestampsAreSkipped(ctx) { + currTime := time.Now().In(boil.GetLocation()) + + if o.CreatedAt.IsZero() { + o.CreatedAt = currTime + } + o.UpdatedAt = currTime + } + + if err := o.doBeforeUpsertHooks(ctx, exec); err != nil { + return err + } + + nzDefaults := queries.NonZeroDefaultSet(tagColumnsWithDefault, o) + + // Build cache key in-line uglily - mysql vs psql problems + buf := strmangle.GetBuffer() + if updateOnConflict { + buf.WriteByte('t') + } else { + buf.WriteByte('f') + } + buf.WriteByte('.') + for _, c := range conflictColumns { + buf.WriteString(c) + } + buf.WriteByte('.') + buf.WriteString(strconv.Itoa(updateColumns.Kind)) + for _, c := range updateColumns.Cols { + buf.WriteString(c) + } + buf.WriteByte('.') + buf.WriteString(strconv.Itoa(insertColumns.Kind)) + for _, c := range insertColumns.Cols { + buf.WriteString(c) + } + buf.WriteByte('.') + for _, c := range nzDefaults { + buf.WriteString(c) + } + key := buf.String() + strmangle.PutBuffer(buf) + + tagUpsertCacheMut.RLock() + cache, cached := tagUpsertCache[key] + tagUpsertCacheMut.RUnlock() + + var err error + + if !cached { + insert, _ := insertColumns.InsertColumnSet( + tagAllColumns, + tagColumnsWithDefault, + tagColumnsWithoutDefault, + nzDefaults, + ) + + update := updateColumns.UpdateColumnSet( + tagAllColumns, + tagPrimaryKeyColumns, + ) + + if updateOnConflict && len(update) == 0 { + return errors.New("models: unable to upsert tags, could not build update column list") + } + + ret := strmangle.SetComplement(tagAllColumns, strmangle.SetIntersect(insert, update)) + + conflict := conflictColumns + if len(conflict) == 0 && updateOnConflict && len(update) != 0 { + if len(tagPrimaryKeyColumns) == 0 { + return errors.New("models: unable to upsert tags, could not build conflict column list") + } + + conflict = make([]string, len(tagPrimaryKeyColumns)) + copy(conflict, tagPrimaryKeyColumns) + } + cache.query = buildUpsertQueryPostgres(dialect, "\"tags\"", updateOnConflict, ret, update, conflict, insert, opts...) + + cache.valueMapping, err = queries.BindMapping(tagType, tagMapping, insert) + if err != nil { + return err + } + if len(ret) != 0 { + cache.retMapping, err = queries.BindMapping(tagType, tagMapping, ret) + if err != nil { + return err + } + } + } + + value := reflect.Indirect(reflect.ValueOf(o)) + vals := queries.ValuesFromMapping(value, cache.valueMapping) + var returns []interface{} + if len(cache.retMapping) != 0 { + returns = queries.PtrsFromMapping(value, cache.retMapping) + } + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, cache.query) + fmt.Fprintln(writer, vals) + } + if len(cache.retMapping) != 0 { + err = exec.QueryRowContext(ctx, cache.query, vals...).Scan(returns...) + if errors.Is(err, sql.ErrNoRows) { + err = nil // Postgres doesn't return anything when there's no update + } + } else { + _, err = exec.ExecContext(ctx, cache.query, vals...) + } + if err != nil { + return errors.Wrap(err, "models: unable to upsert tags") + } + + if !cached { + tagUpsertCacheMut.Lock() + tagUpsertCache[key] = cache + tagUpsertCacheMut.Unlock() + } + + return o.doAfterUpsertHooks(ctx, exec) +} + +// Delete deletes a single Tag record with an executor. +// Delete will match against the primary key column to find the record to delete. +func (o *Tag) Delete(ctx context.Context, exec boil.ContextExecutor) (int64, error) { + if o == nil { + return 0, errors.New("models: no Tag provided for delete") + } + + if err := o.doBeforeDeleteHooks(ctx, exec); err != nil { + return 0, err + } + + args := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(o)), tagPrimaryKeyMapping) + sql := "DELETE FROM \"tags\" WHERE \"id\"=$1" + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, sql) + fmt.Fprintln(writer, args...) + } + result, err := exec.ExecContext(ctx, sql, args...) + if err != nil { + return 0, errors.Wrap(err, "models: unable to delete from tags") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: failed to get rows affected by delete for tags") + } + + if err := o.doAfterDeleteHooks(ctx, exec); err != nil { + return 0, err + } + + return rowsAff, nil +} + +// DeleteAll deletes all matching rows. +func (q tagQuery) DeleteAll(ctx context.Context, exec boil.ContextExecutor) (int64, error) { + if q.Query == nil { + return 0, errors.New("models: no tagQuery provided for delete all") + } + + queries.SetDelete(q.Query) + + result, err := q.Query.ExecContext(ctx, exec) + if err != nil { + return 0, errors.Wrap(err, "models: unable to delete all from tags") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: failed to get rows affected by deleteall for tags") + } + + return rowsAff, nil +} + +// DeleteAll deletes all rows in the slice, using an executor. +func (o TagSlice) DeleteAll(ctx context.Context, exec boil.ContextExecutor) (int64, error) { + if len(o) == 0 { + return 0, nil + } + + if len(tagBeforeDeleteHooks) != 0 { + for _, obj := range o { + if err := obj.doBeforeDeleteHooks(ctx, exec); err != nil { + return 0, err + } + } + } + + var args []interface{} + for _, obj := range o { + pkeyArgs := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(obj)), tagPrimaryKeyMapping) + args = append(args, pkeyArgs...) + } + + sql := "DELETE FROM \"tags\" WHERE " + + strmangle.WhereClauseRepeated(string(dialect.LQ), string(dialect.RQ), 1, tagPrimaryKeyColumns, len(o)) + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, sql) + fmt.Fprintln(writer, args) + } + result, err := exec.ExecContext(ctx, sql, args...) + if err != nil { + return 0, errors.Wrap(err, "models: unable to delete all from tag slice") + } + + rowsAff, err := result.RowsAffected() + if err != nil { + return 0, errors.Wrap(err, "models: failed to get rows affected by deleteall for tags") + } + + if len(tagAfterDeleteHooks) != 0 { + for _, obj := range o { + if err := obj.doAfterDeleteHooks(ctx, exec); err != nil { + return 0, err + } + } + } + + return rowsAff, nil +} + +// Reload refetches the object from the database +// using the primary keys with an executor. +func (o *Tag) Reload(ctx context.Context, exec boil.ContextExecutor) error { + ret, err := FindTag(ctx, exec, o.ID) + if err != nil { + return err + } + + *o = *ret + return nil +} + +// ReloadAll refetches every row with matching primary key column values +// and overwrites the original object slice with the newly updated slice. +func (o *TagSlice) ReloadAll(ctx context.Context, exec boil.ContextExecutor) error { + if o == nil || len(*o) == 0 { + return nil + } + + slice := TagSlice{} + var args []interface{} + for _, obj := range *o { + pkeyArgs := queries.ValuesFromMapping(reflect.Indirect(reflect.ValueOf(obj)), tagPrimaryKeyMapping) + args = append(args, pkeyArgs...) + } + + sql := "SELECT \"tags\".* FROM \"tags\" WHERE " + + strmangle.WhereClauseRepeated(string(dialect.LQ), string(dialect.RQ), 1, tagPrimaryKeyColumns, len(*o)) + + q := queries.Raw(sql, args...) + + err := q.Bind(ctx, exec, &slice) + if err != nil { + return errors.Wrap(err, "models: unable to reload all in TagSlice") + } + + *o = slice + + return nil +} + +// TagExists checks if the Tag row exists. +func TagExists(ctx context.Context, exec boil.ContextExecutor, iD int) (bool, error) { + var exists bool + sql := "select exists(select 1 from \"tags\" where \"id\"=$1 limit 1)" + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, sql) + fmt.Fprintln(writer, iD) + } + row := exec.QueryRowContext(ctx, sql, iD) + + err := row.Scan(&exists) + if err != nil { + return false, errors.Wrap(err, "models: unable to check if tags exists") + } + + return exists, nil +} + +// Exists checks if the Tag row exists. +func (o *Tag) Exists(ctx context.Context, exec boil.ContextExecutor) (bool, error) { + return TagExists(ctx, exec, o.ID) +} diff --git a/src/models/tags_test.go b/src/models/tags_test.go new file mode 100644 index 0000000..16ecfdf --- /dev/null +++ b/src/models/tags_test.go @@ -0,0 +1,1163 @@ +// Code generated by SQLBoiler 4.16.1 (https://github.com/volatiletech/sqlboiler). DO NOT EDIT. +// This file is meant to be re-generated in place and/or deleted at any time. + +package models + +import ( + "bytes" + "context" + "reflect" + "testing" + + "github.com/volatiletech/randomize" + "github.com/volatiletech/sqlboiler/v4/boil" + "github.com/volatiletech/sqlboiler/v4/queries" + "github.com/volatiletech/strmangle" +) + +var ( + // Relationships sometimes use the reflection helper queries.Equal/queries.Assign + // so force a package dependency in case they don't. + _ = queries.Equal +) + +func testTags(t *testing.T) { + t.Parallel() + + query := Tags() + + if query.Query == nil { + t.Error("expected a query, got nothing") + } +} + +func testTagsDelete(t *testing.T) { + t.Parallel() + + seed := randomize.NewSeed() + var err error + o := &Tag{} + if err = randomize.Struct(seed, o, tagDBTypes, true, tagColumnsWithDefault...); err != nil { + t.Errorf("Unable to randomize Tag struct: %s", err) + } + + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + if err = o.Insert(ctx, tx, boil.Infer()); err != nil { + t.Error(err) + } + + if rowsAff, err := o.Delete(ctx, tx); err != nil { + t.Error(err) + } else if rowsAff != 1 { + t.Error("should only have deleted one row, but affected:", rowsAff) + } + + count, err := Tags().Count(ctx, tx) + if err != nil { + t.Error(err) + } + + if count != 0 { + t.Error("want zero records, got:", count) + } +} + +func testTagsQueryDeleteAll(t *testing.T) { + t.Parallel() + + seed := randomize.NewSeed() + var err error + o := &Tag{} + if err = randomize.Struct(seed, o, tagDBTypes, true, tagColumnsWithDefault...); err != nil { + t.Errorf("Unable to randomize Tag struct: %s", err) + } + + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + if err = o.Insert(ctx, tx, boil.Infer()); err != nil { + t.Error(err) + } + + if rowsAff, err := Tags().DeleteAll(ctx, tx); err != nil { + t.Error(err) + } else if rowsAff != 1 { + t.Error("should only have deleted one row, but affected:", rowsAff) + } + + count, err := Tags().Count(ctx, tx) + if err != nil { + t.Error(err) + } + + if count != 0 { + t.Error("want zero records, got:", count) + } +} + +func testTagsSliceDeleteAll(t *testing.T) { + t.Parallel() + + seed := randomize.NewSeed() + var err error + o := &Tag{} + if err = randomize.Struct(seed, o, tagDBTypes, true, tagColumnsWithDefault...); err != nil { + t.Errorf("Unable to randomize Tag struct: %s", err) + } + + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + if err = o.Insert(ctx, tx, boil.Infer()); err != nil { + t.Error(err) + } + + slice := TagSlice{o} + + if rowsAff, err := slice.DeleteAll(ctx, tx); err != nil { + t.Error(err) + } else if rowsAff != 1 { + t.Error("should only have deleted one row, but affected:", rowsAff) + } + + count, err := Tags().Count(ctx, tx) + if err != nil { + t.Error(err) + } + + if count != 0 { + t.Error("want zero records, got:", count) + } +} + +func testTagsExists(t *testing.T) { + t.Parallel() + + seed := randomize.NewSeed() + var err error + o := &Tag{} + if err = randomize.Struct(seed, o, tagDBTypes, true, tagColumnsWithDefault...); err != nil { + t.Errorf("Unable to randomize Tag struct: %s", err) + } + + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + if err = o.Insert(ctx, tx, boil.Infer()); err != nil { + t.Error(err) + } + + e, err := TagExists(ctx, tx, o.ID) + if err != nil { + t.Errorf("Unable to check if Tag exists: %s", err) + } + if !e { + t.Errorf("Expected TagExists to return true, but got false.") + } +} + +func testTagsFind(t *testing.T) { + t.Parallel() + + seed := randomize.NewSeed() + var err error + o := &Tag{} + if err = randomize.Struct(seed, o, tagDBTypes, true, tagColumnsWithDefault...); err != nil { + t.Errorf("Unable to randomize Tag struct: %s", err) + } + + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + if err = o.Insert(ctx, tx, boil.Infer()); err != nil { + t.Error(err) + } + + tagFound, err := FindTag(ctx, tx, o.ID) + if err != nil { + t.Error(err) + } + + if tagFound == nil { + t.Error("want a record, got nil") + } +} + +func testTagsBind(t *testing.T) { + t.Parallel() + + seed := randomize.NewSeed() + var err error + o := &Tag{} + if err = randomize.Struct(seed, o, tagDBTypes, true, tagColumnsWithDefault...); err != nil { + t.Errorf("Unable to randomize Tag struct: %s", err) + } + + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + if err = o.Insert(ctx, tx, boil.Infer()); err != nil { + t.Error(err) + } + + if err = Tags().Bind(ctx, tx, o); err != nil { + t.Error(err) + } +} + +func testTagsOne(t *testing.T) { + t.Parallel() + + seed := randomize.NewSeed() + var err error + o := &Tag{} + if err = randomize.Struct(seed, o, tagDBTypes, true, tagColumnsWithDefault...); err != nil { + t.Errorf("Unable to randomize Tag struct: %s", err) + } + + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + if err = o.Insert(ctx, tx, boil.Infer()); err != nil { + t.Error(err) + } + + if x, err := Tags().One(ctx, tx); err != nil { + t.Error(err) + } else if x == nil { + t.Error("expected to get a non nil record") + } +} + +func testTagsAll(t *testing.T) { + t.Parallel() + + seed := randomize.NewSeed() + var err error + tagOne := &Tag{} + tagTwo := &Tag{} + if err = randomize.Struct(seed, tagOne, tagDBTypes, false, tagColumnsWithDefault...); err != nil { + t.Errorf("Unable to randomize Tag struct: %s", err) + } + if err = randomize.Struct(seed, tagTwo, tagDBTypes, false, tagColumnsWithDefault...); err != nil { + t.Errorf("Unable to randomize Tag struct: %s", err) + } + + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + if err = tagOne.Insert(ctx, tx, boil.Infer()); err != nil { + t.Error(err) + } + if err = tagTwo.Insert(ctx, tx, boil.Infer()); err != nil { + t.Error(err) + } + + slice, err := Tags().All(ctx, tx) + if err != nil { + t.Error(err) + } + + if len(slice) != 2 { + t.Error("want 2 records, got:", len(slice)) + } +} + +func testTagsCount(t *testing.T) { + t.Parallel() + + var err error + seed := randomize.NewSeed() + tagOne := &Tag{} + tagTwo := &Tag{} + if err = randomize.Struct(seed, tagOne, tagDBTypes, false, tagColumnsWithDefault...); err != nil { + t.Errorf("Unable to randomize Tag struct: %s", err) + } + if err = randomize.Struct(seed, tagTwo, tagDBTypes, false, tagColumnsWithDefault...); err != nil { + t.Errorf("Unable to randomize Tag struct: %s", err) + } + + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + if err = tagOne.Insert(ctx, tx, boil.Infer()); err != nil { + t.Error(err) + } + if err = tagTwo.Insert(ctx, tx, boil.Infer()); err != nil { + t.Error(err) + } + + count, err := Tags().Count(ctx, tx) + if err != nil { + t.Error(err) + } + + if count != 2 { + t.Error("want 2 records, got:", count) + } +} + +func tagBeforeInsertHook(ctx context.Context, e boil.ContextExecutor, o *Tag) error { + *o = Tag{} + return nil +} + +func tagAfterInsertHook(ctx context.Context, e boil.ContextExecutor, o *Tag) error { + *o = Tag{} + return nil +} + +func tagAfterSelectHook(ctx context.Context, e boil.ContextExecutor, o *Tag) error { + *o = Tag{} + return nil +} + +func tagBeforeUpdateHook(ctx context.Context, e boil.ContextExecutor, o *Tag) error { + *o = Tag{} + return nil +} + +func tagAfterUpdateHook(ctx context.Context, e boil.ContextExecutor, o *Tag) error { + *o = Tag{} + return nil +} + +func tagBeforeDeleteHook(ctx context.Context, e boil.ContextExecutor, o *Tag) error { + *o = Tag{} + return nil +} + +func tagAfterDeleteHook(ctx context.Context, e boil.ContextExecutor, o *Tag) error { + *o = Tag{} + return nil +} + +func tagBeforeUpsertHook(ctx context.Context, e boil.ContextExecutor, o *Tag) error { + *o = Tag{} + return nil +} + +func tagAfterUpsertHook(ctx context.Context, e boil.ContextExecutor, o *Tag) error { + *o = Tag{} + return nil +} + +func testTagsHooks(t *testing.T) { + t.Parallel() + + var err error + + ctx := context.Background() + empty := &Tag{} + o := &Tag{} + + seed := randomize.NewSeed() + if err = randomize.Struct(seed, o, tagDBTypes, false); err != nil { + t.Errorf("Unable to randomize Tag object: %s", err) + } + + AddTagHook(boil.BeforeInsertHook, tagBeforeInsertHook) + if err = o.doBeforeInsertHooks(ctx, nil); err != nil { + t.Errorf("Unable to execute doBeforeInsertHooks: %s", err) + } + if !reflect.DeepEqual(o, empty) { + t.Errorf("Expected BeforeInsertHook function to empty object, but got: %#v", o) + } + tagBeforeInsertHooks = []TagHook{} + + AddTagHook(boil.AfterInsertHook, tagAfterInsertHook) + if err = o.doAfterInsertHooks(ctx, nil); err != nil { + t.Errorf("Unable to execute doAfterInsertHooks: %s", err) + } + if !reflect.DeepEqual(o, empty) { + t.Errorf("Expected AfterInsertHook function to empty object, but got: %#v", o) + } + tagAfterInsertHooks = []TagHook{} + + AddTagHook(boil.AfterSelectHook, tagAfterSelectHook) + if err = o.doAfterSelectHooks(ctx, nil); err != nil { + t.Errorf("Unable to execute doAfterSelectHooks: %s", err) + } + if !reflect.DeepEqual(o, empty) { + t.Errorf("Expected AfterSelectHook function to empty object, but got: %#v", o) + } + tagAfterSelectHooks = []TagHook{} + + AddTagHook(boil.BeforeUpdateHook, tagBeforeUpdateHook) + if err = o.doBeforeUpdateHooks(ctx, nil); err != nil { + t.Errorf("Unable to execute doBeforeUpdateHooks: %s", err) + } + if !reflect.DeepEqual(o, empty) { + t.Errorf("Expected BeforeUpdateHook function to empty object, but got: %#v", o) + } + tagBeforeUpdateHooks = []TagHook{} + + AddTagHook(boil.AfterUpdateHook, tagAfterUpdateHook) + if err = o.doAfterUpdateHooks(ctx, nil); err != nil { + t.Errorf("Unable to execute doAfterUpdateHooks: %s", err) + } + if !reflect.DeepEqual(o, empty) { + t.Errorf("Expected AfterUpdateHook function to empty object, but got: %#v", o) + } + tagAfterUpdateHooks = []TagHook{} + + AddTagHook(boil.BeforeDeleteHook, tagBeforeDeleteHook) + if err = o.doBeforeDeleteHooks(ctx, nil); err != nil { + t.Errorf("Unable to execute doBeforeDeleteHooks: %s", err) + } + if !reflect.DeepEqual(o, empty) { + t.Errorf("Expected BeforeDeleteHook function to empty object, but got: %#v", o) + } + tagBeforeDeleteHooks = []TagHook{} + + AddTagHook(boil.AfterDeleteHook, tagAfterDeleteHook) + if err = o.doAfterDeleteHooks(ctx, nil); err != nil { + t.Errorf("Unable to execute doAfterDeleteHooks: %s", err) + } + if !reflect.DeepEqual(o, empty) { + t.Errorf("Expected AfterDeleteHook function to empty object, but got: %#v", o) + } + tagAfterDeleteHooks = []TagHook{} + + AddTagHook(boil.BeforeUpsertHook, tagBeforeUpsertHook) + if err = o.doBeforeUpsertHooks(ctx, nil); err != nil { + t.Errorf("Unable to execute doBeforeUpsertHooks: %s", err) + } + if !reflect.DeepEqual(o, empty) { + t.Errorf("Expected BeforeUpsertHook function to empty object, but got: %#v", o) + } + tagBeforeUpsertHooks = []TagHook{} + + AddTagHook(boil.AfterUpsertHook, tagAfterUpsertHook) + if err = o.doAfterUpsertHooks(ctx, nil); err != nil { + t.Errorf("Unable to execute doAfterUpsertHooks: %s", err) + } + if !reflect.DeepEqual(o, empty) { + t.Errorf("Expected AfterUpsertHook function to empty object, but got: %#v", o) + } + tagAfterUpsertHooks = []TagHook{} +} + +func testTagsInsert(t *testing.T) { + t.Parallel() + + seed := randomize.NewSeed() + var err error + o := &Tag{} + if err = randomize.Struct(seed, o, tagDBTypes, true, tagColumnsWithDefault...); err != nil { + t.Errorf("Unable to randomize Tag struct: %s", err) + } + + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + if err = o.Insert(ctx, tx, boil.Infer()); err != nil { + t.Error(err) + } + + count, err := Tags().Count(ctx, tx) + if err != nil { + t.Error(err) + } + + if count != 1 { + t.Error("want one record, got:", count) + } +} + +func testTagsInsertWhitelist(t *testing.T) { + t.Parallel() + + seed := randomize.NewSeed() + var err error + o := &Tag{} + if err = randomize.Struct(seed, o, tagDBTypes, true); err != nil { + t.Errorf("Unable to randomize Tag struct: %s", err) + } + + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + if err = o.Insert(ctx, tx, boil.Whitelist(tagColumnsWithoutDefault...)); err != nil { + t.Error(err) + } + + count, err := Tags().Count(ctx, tx) + if err != nil { + t.Error(err) + } + + if count != 1 { + t.Error("want one record, got:", count) + } +} + +func testTagToManyPosts(t *testing.T) { + var err error + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + + var a Tag + var b, c Post + + seed := randomize.NewSeed() + if err = randomize.Struct(seed, &a, tagDBTypes, true, tagColumnsWithDefault...); err != nil { + t.Errorf("Unable to randomize Tag struct: %s", err) + } + + if err := a.Insert(ctx, tx, boil.Infer()); err != nil { + t.Fatal(err) + } + + if err = randomize.Struct(seed, &b, postDBTypes, false, postColumnsWithDefault...); err != nil { + t.Fatal(err) + } + if err = randomize.Struct(seed, &c, postDBTypes, false, postColumnsWithDefault...); err != nil { + t.Fatal(err) + } + + if err = b.Insert(ctx, tx, boil.Infer()); err != nil { + t.Fatal(err) + } + if err = c.Insert(ctx, tx, boil.Infer()); err != nil { + t.Fatal(err) + } + + _, err = tx.Exec("insert into \"post_tags\" (\"tag_id\", \"post_id\") values ($1, $2)", a.ID, b.ID) + if err != nil { + t.Fatal(err) + } + _, err = tx.Exec("insert into \"post_tags\" (\"tag_id\", \"post_id\") values ($1, $2)", a.ID, c.ID) + if err != nil { + t.Fatal(err) + } + + check, err := a.Posts().All(ctx, tx) + if err != nil { + t.Fatal(err) + } + + bFound, cFound := false, false + for _, v := range check { + if v.ID == b.ID { + bFound = true + } + if v.ID == c.ID { + cFound = true + } + } + + if !bFound { + t.Error("expected to find b") + } + if !cFound { + t.Error("expected to find c") + } + + slice := TagSlice{&a} + if err = a.L.LoadPosts(ctx, tx, false, (*[]*Tag)(&slice), nil); err != nil { + t.Fatal(err) + } + if got := len(a.R.Posts); got != 2 { + t.Error("number of eager loaded records wrong, got:", got) + } + + a.R.Posts = nil + if err = a.L.LoadPosts(ctx, tx, true, &a, nil); err != nil { + t.Fatal(err) + } + if got := len(a.R.Posts); got != 2 { + t.Error("number of eager loaded records wrong, got:", got) + } + + if t.Failed() { + t.Logf("%#v", check) + } +} + +func testTagToManyAddOpPosts(t *testing.T) { + var err error + + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + + var a Tag + var b, c, d, e Post + + seed := randomize.NewSeed() + if err = randomize.Struct(seed, &a, tagDBTypes, false, strmangle.SetComplement(tagPrimaryKeyColumns, tagColumnsWithoutDefault)...); err != nil { + t.Fatal(err) + } + foreigners := []*Post{&b, &c, &d, &e} + for _, x := range foreigners { + if err = randomize.Struct(seed, x, postDBTypes, false, strmangle.SetComplement(postPrimaryKeyColumns, postColumnsWithoutDefault)...); err != nil { + t.Fatal(err) + } + } + + if err := a.Insert(ctx, tx, boil.Infer()); err != nil { + t.Fatal(err) + } + if err = b.Insert(ctx, tx, boil.Infer()); err != nil { + t.Fatal(err) + } + if err = c.Insert(ctx, tx, boil.Infer()); err != nil { + t.Fatal(err) + } + + foreignersSplitByInsertion := [][]*Post{ + {&b, &c}, + {&d, &e}, + } + + for i, x := range foreignersSplitByInsertion { + err = a.AddPosts(ctx, tx, i != 0, x...) + if err != nil { + t.Fatal(err) + } + + first := x[0] + second := x[1] + + if first.R.Tags[0] != &a { + t.Error("relationship was not added properly to the slice") + } + if second.R.Tags[0] != &a { + t.Error("relationship was not added properly to the slice") + } + + if a.R.Posts[i*2] != first { + t.Error("relationship struct slice not set to correct value") + } + if a.R.Posts[i*2+1] != second { + t.Error("relationship struct slice not set to correct value") + } + + count, err := a.Posts().Count(ctx, tx) + if err != nil { + t.Fatal(err) + } + if want := int64((i + 1) * 2); count != want { + t.Error("want", want, "got", count) + } + } +} + +func testTagToManySetOpPosts(t *testing.T) { + var err error + + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + + var a Tag + var b, c, d, e Post + + seed := randomize.NewSeed() + if err = randomize.Struct(seed, &a, tagDBTypes, false, strmangle.SetComplement(tagPrimaryKeyColumns, tagColumnsWithoutDefault)...); err != nil { + t.Fatal(err) + } + foreigners := []*Post{&b, &c, &d, &e} + for _, x := range foreigners { + if err = randomize.Struct(seed, x, postDBTypes, false, strmangle.SetComplement(postPrimaryKeyColumns, postColumnsWithoutDefault)...); err != nil { + t.Fatal(err) + } + } + + if err = a.Insert(ctx, tx, boil.Infer()); err != nil { + t.Fatal(err) + } + if err = b.Insert(ctx, tx, boil.Infer()); err != nil { + t.Fatal(err) + } + if err = c.Insert(ctx, tx, boil.Infer()); err != nil { + t.Fatal(err) + } + + err = a.SetPosts(ctx, tx, false, &b, &c) + if err != nil { + t.Fatal(err) + } + + count, err := a.Posts().Count(ctx, tx) + if err != nil { + t.Fatal(err) + } + if count != 2 { + t.Error("count was wrong:", count) + } + + err = a.SetPosts(ctx, tx, true, &d, &e) + if err != nil { + t.Fatal(err) + } + + count, err = a.Posts().Count(ctx, tx) + if err != nil { + t.Fatal(err) + } + if count != 2 { + t.Error("count was wrong:", count) + } + + // The following checks cannot be implemented since we have no handle + // to these when we call Set(). Leaving them here as wishful thinking + // and to let people know there's dragons. + // + // if len(b.R.Tags) != 0 { + // t.Error("relationship was not removed properly from the slice") + // } + // if len(c.R.Tags) != 0 { + // t.Error("relationship was not removed properly from the slice") + // } + if d.R.Tags[0] != &a { + t.Error("relationship was not added properly to the slice") + } + if e.R.Tags[0] != &a { + t.Error("relationship was not added properly to the slice") + } + + if a.R.Posts[0] != &d { + t.Error("relationship struct slice not set to correct value") + } + if a.R.Posts[1] != &e { + t.Error("relationship struct slice not set to correct value") + } +} + +func testTagToManyRemoveOpPosts(t *testing.T) { + var err error + + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + + var a Tag + var b, c, d, e Post + + seed := randomize.NewSeed() + if err = randomize.Struct(seed, &a, tagDBTypes, false, strmangle.SetComplement(tagPrimaryKeyColumns, tagColumnsWithoutDefault)...); err != nil { + t.Fatal(err) + } + foreigners := []*Post{&b, &c, &d, &e} + for _, x := range foreigners { + if err = randomize.Struct(seed, x, postDBTypes, false, strmangle.SetComplement(postPrimaryKeyColumns, postColumnsWithoutDefault)...); err != nil { + t.Fatal(err) + } + } + + if err := a.Insert(ctx, tx, boil.Infer()); err != nil { + t.Fatal(err) + } + + err = a.AddPosts(ctx, tx, true, foreigners...) + if err != nil { + t.Fatal(err) + } + + count, err := a.Posts().Count(ctx, tx) + if err != nil { + t.Fatal(err) + } + if count != 4 { + t.Error("count was wrong:", count) + } + + err = a.RemovePosts(ctx, tx, foreigners[:2]...) + if err != nil { + t.Fatal(err) + } + + count, err = a.Posts().Count(ctx, tx) + if err != nil { + t.Fatal(err) + } + if count != 2 { + t.Error("count was wrong:", count) + } + + if len(b.R.Tags) != 0 { + t.Error("relationship was not removed properly from the slice") + } + if len(c.R.Tags) != 0 { + t.Error("relationship was not removed properly from the slice") + } + if d.R.Tags[0] != &a { + t.Error("relationship was not added properly to the foreign struct") + } + if e.R.Tags[0] != &a { + t.Error("relationship was not added properly to the foreign struct") + } + + if len(a.R.Posts) != 2 { + t.Error("should have preserved two relationships") + } + + // Removal doesn't do a stable deletion for performance so we have to flip the order + if a.R.Posts[1] != &d { + t.Error("relationship to d should have been preserved") + } + if a.R.Posts[0] != &e { + t.Error("relationship to e should have been preserved") + } +} + +func testTagToOneUserUsingUser(t *testing.T) { + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + + var local Tag + var foreign User + + seed := randomize.NewSeed() + if err := randomize.Struct(seed, &local, tagDBTypes, false, tagColumnsWithDefault...); err != nil { + t.Errorf("Unable to randomize Tag struct: %s", err) + } + if err := randomize.Struct(seed, &foreign, userDBTypes, false, userColumnsWithDefault...); err != nil { + t.Errorf("Unable to randomize User struct: %s", err) + } + + if err := foreign.Insert(ctx, tx, boil.Infer()); err != nil { + t.Fatal(err) + } + + local.UserID = foreign.ID + if err := local.Insert(ctx, tx, boil.Infer()); err != nil { + t.Fatal(err) + } + + check, err := local.User().One(ctx, tx) + if err != nil { + t.Fatal(err) + } + + if check.ID != foreign.ID { + t.Errorf("want: %v, got %v", foreign.ID, check.ID) + } + + ranAfterSelectHook := false + AddUserHook(boil.AfterSelectHook, func(ctx context.Context, e boil.ContextExecutor, o *User) error { + ranAfterSelectHook = true + return nil + }) + + slice := TagSlice{&local} + if err = local.L.LoadUser(ctx, tx, false, (*[]*Tag)(&slice), nil); err != nil { + t.Fatal(err) + } + if local.R.User == nil { + t.Error("struct should have been eager loaded") + } + + local.R.User = nil + if err = local.L.LoadUser(ctx, tx, true, &local, nil); err != nil { + t.Fatal(err) + } + if local.R.User == nil { + t.Error("struct should have been eager loaded") + } + + if !ranAfterSelectHook { + t.Error("failed to run AfterSelect hook for relationship") + } +} + +func testTagToOneSetOpUserUsingUser(t *testing.T) { + var err error + + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + + var a Tag + var b, c User + + seed := randomize.NewSeed() + if err = randomize.Struct(seed, &a, tagDBTypes, false, strmangle.SetComplement(tagPrimaryKeyColumns, tagColumnsWithoutDefault)...); err != nil { + t.Fatal(err) + } + if err = randomize.Struct(seed, &b, userDBTypes, false, strmangle.SetComplement(userPrimaryKeyColumns, userColumnsWithoutDefault)...); err != nil { + t.Fatal(err) + } + if err = randomize.Struct(seed, &c, userDBTypes, false, strmangle.SetComplement(userPrimaryKeyColumns, userColumnsWithoutDefault)...); err != nil { + t.Fatal(err) + } + + if err := a.Insert(ctx, tx, boil.Infer()); err != nil { + t.Fatal(err) + } + if err = b.Insert(ctx, tx, boil.Infer()); err != nil { + t.Fatal(err) + } + + for i, x := range []*User{&b, &c} { + err = a.SetUser(ctx, tx, i != 0, x) + if err != nil { + t.Fatal(err) + } + + if a.R.User != x { + t.Error("relationship struct not set to correct value") + } + + if x.R.Tags[0] != &a { + t.Error("failed to append to foreign relationship struct") + } + if a.UserID != x.ID { + t.Error("foreign key was wrong value", a.UserID) + } + + zero := reflect.Zero(reflect.TypeOf(a.UserID)) + reflect.Indirect(reflect.ValueOf(&a.UserID)).Set(zero) + + if err = a.Reload(ctx, tx); err != nil { + t.Fatal("failed to reload", err) + } + + if a.UserID != x.ID { + t.Error("foreign key was wrong value", a.UserID, x.ID) + } + } +} + +func testTagsReload(t *testing.T) { + t.Parallel() + + seed := randomize.NewSeed() + var err error + o := &Tag{} + if err = randomize.Struct(seed, o, tagDBTypes, true, tagColumnsWithDefault...); err != nil { + t.Errorf("Unable to randomize Tag struct: %s", err) + } + + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + if err = o.Insert(ctx, tx, boil.Infer()); err != nil { + t.Error(err) + } + + if err = o.Reload(ctx, tx); err != nil { + t.Error(err) + } +} + +func testTagsReloadAll(t *testing.T) { + t.Parallel() + + seed := randomize.NewSeed() + var err error + o := &Tag{} + if err = randomize.Struct(seed, o, tagDBTypes, true, tagColumnsWithDefault...); err != nil { + t.Errorf("Unable to randomize Tag struct: %s", err) + } + + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + if err = o.Insert(ctx, tx, boil.Infer()); err != nil { + t.Error(err) + } + + slice := TagSlice{o} + + if err = slice.ReloadAll(ctx, tx); err != nil { + t.Error(err) + } +} + +func testTagsSelect(t *testing.T) { + t.Parallel() + + seed := randomize.NewSeed() + var err error + o := &Tag{} + if err = randomize.Struct(seed, o, tagDBTypes, true, tagColumnsWithDefault...); err != nil { + t.Errorf("Unable to randomize Tag struct: %s", err) + } + + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + if err = o.Insert(ctx, tx, boil.Infer()); err != nil { + t.Error(err) + } + + slice, err := Tags().All(ctx, tx) + if err != nil { + t.Error(err) + } + + if len(slice) != 1 { + t.Error("want one record, got:", len(slice)) + } +} + +var ( + tagDBTypes = map[string]string{`ID`: `integer`, `Tag`: `character varying`, `Description`: `text`, `UserID`: `integer`, `CreatedAt`: `timestamp with time zone`, `UpdatedAt`: `timestamp with time zone`} + _ = bytes.MinRead +) + +func testTagsUpdate(t *testing.T) { + t.Parallel() + + if 0 == len(tagPrimaryKeyColumns) { + t.Skip("Skipping table with no primary key columns") + } + if len(tagAllColumns) == len(tagPrimaryKeyColumns) { + t.Skip("Skipping table with only primary key columns") + } + + seed := randomize.NewSeed() + var err error + o := &Tag{} + if err = randomize.Struct(seed, o, tagDBTypes, true, tagColumnsWithDefault...); err != nil { + t.Errorf("Unable to randomize Tag struct: %s", err) + } + + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + if err = o.Insert(ctx, tx, boil.Infer()); err != nil { + t.Error(err) + } + + count, err := Tags().Count(ctx, tx) + if err != nil { + t.Error(err) + } + + if count != 1 { + t.Error("want one record, got:", count) + } + + if err = randomize.Struct(seed, o, tagDBTypes, true, tagPrimaryKeyColumns...); err != nil { + t.Errorf("Unable to randomize Tag struct: %s", err) + } + + if rowsAff, err := o.Update(ctx, tx, boil.Infer()); err != nil { + t.Error(err) + } else if rowsAff != 1 { + t.Error("should only affect one row but affected", rowsAff) + } +} + +func testTagsSliceUpdateAll(t *testing.T) { + t.Parallel() + + if len(tagAllColumns) == len(tagPrimaryKeyColumns) { + t.Skip("Skipping table with only primary key columns") + } + + seed := randomize.NewSeed() + var err error + o := &Tag{} + if err = randomize.Struct(seed, o, tagDBTypes, true, tagColumnsWithDefault...); err != nil { + t.Errorf("Unable to randomize Tag struct: %s", err) + } + + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + if err = o.Insert(ctx, tx, boil.Infer()); err != nil { + t.Error(err) + } + + count, err := Tags().Count(ctx, tx) + if err != nil { + t.Error(err) + } + + if count != 1 { + t.Error("want one record, got:", count) + } + + if err = randomize.Struct(seed, o, tagDBTypes, true, tagPrimaryKeyColumns...); err != nil { + t.Errorf("Unable to randomize Tag struct: %s", err) + } + + // Remove Primary keys and unique columns from what we plan to update + var fields []string + if strmangle.StringSliceMatch(tagAllColumns, tagPrimaryKeyColumns) { + fields = tagAllColumns + } else { + fields = strmangle.SetComplement( + tagAllColumns, + tagPrimaryKeyColumns, + ) + } + + value := reflect.Indirect(reflect.ValueOf(o)) + typ := reflect.TypeOf(o).Elem() + n := typ.NumField() + + updateMap := M{} + for _, col := range fields { + for i := 0; i < n; i++ { + f := typ.Field(i) + if f.Tag.Get("boil") == col { + updateMap[col] = value.Field(i).Interface() + } + } + } + + slice := TagSlice{o} + if rowsAff, err := slice.UpdateAll(ctx, tx, updateMap); err != nil { + t.Error(err) + } else if rowsAff != 1 { + t.Error("wanted one record updated but got", rowsAff) + } +} + +func testTagsUpsert(t *testing.T) { + t.Parallel() + + if len(tagAllColumns) == len(tagPrimaryKeyColumns) { + t.Skip("Skipping table with only primary key columns") + } + + seed := randomize.NewSeed() + var err error + // Attempt the INSERT side of an UPSERT + o := Tag{} + if err = randomize.Struct(seed, &o, tagDBTypes, true); err != nil { + t.Errorf("Unable to randomize Tag struct: %s", err) + } + + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + if err = o.Upsert(ctx, tx, false, nil, boil.Infer(), boil.Infer()); err != nil { + t.Errorf("Unable to upsert Tag: %s", err) + } + + count, err := Tags().Count(ctx, tx) + if err != nil { + t.Error(err) + } + if count != 1 { + t.Error("want one record, got:", count) + } + + // Attempt the UPDATE side of an UPSERT + if err = randomize.Struct(seed, &o, tagDBTypes, false, tagPrimaryKeyColumns...); err != nil { + t.Errorf("Unable to randomize Tag struct: %s", err) + } + + if err = o.Upsert(ctx, tx, true, nil, boil.Infer(), boil.Infer()); err != nil { + t.Errorf("Unable to upsert Tag: %s", err) + } + + count, err = Tags().Count(ctx, tx) + if err != nil { + t.Error(err) + } + if count != 1 { + t.Error("want one record, got:", count) + } +} diff --git a/src/models/users.go b/src/models/users.go index ca9bcf8..bdb6a31 100644 --- a/src/models/users.go +++ b/src/models/users.go @@ -87,10 +87,17 @@ var UserWhere = struct { // UserRels is where relationship names are stored. var UserRels = struct { -}{} + Posts string + Tags string +}{ + Posts: "Posts", + Tags: "Tags", +} // userR is where relationships are stored. type userR struct { + Posts PostSlice `boil:"Posts" json:"Posts" toml:"Posts" yaml:"Posts"` + Tags TagSlice `boil:"Tags" json:"Tags" toml:"Tags" yaml:"Tags"` } // NewStruct creates a new relationship struct @@ -98,6 +105,20 @@ func (*userR) NewStruct() *userR { return &userR{} } +func (r *userR) GetPosts() PostSlice { + if r == nil { + return nil + } + return r.Posts +} + +func (r *userR) GetTags() TagSlice { + if r == nil { + return nil + } + return r.Tags +} + // userL is where Load methods for each relationship are stored. type userL struct{} @@ -414,6 +435,366 @@ func (q userQuery) Exists(ctx context.Context, exec boil.ContextExecutor) (bool, return count > 0, nil } +// Posts retrieves all the post's Posts with an executor. +func (o *User) Posts(mods ...qm.QueryMod) postQuery { + var queryMods []qm.QueryMod + if len(mods) != 0 { + queryMods = append(queryMods, mods...) + } + + queryMods = append(queryMods, + qm.Where("\"posts\".\"user_id\"=?", o.ID), + ) + + return Posts(queryMods...) +} + +// Tags retrieves all the tag's Tags with an executor. +func (o *User) Tags(mods ...qm.QueryMod) tagQuery { + var queryMods []qm.QueryMod + if len(mods) != 0 { + queryMods = append(queryMods, mods...) + } + + queryMods = append(queryMods, + qm.Where("\"tags\".\"user_id\"=?", o.ID), + ) + + return Tags(queryMods...) +} + +// LoadPosts allows an eager lookup of values, cached into the +// loaded structs of the objects. This is for a 1-M or N-M relationship. +func (userL) LoadPosts(ctx context.Context, e boil.ContextExecutor, singular bool, maybeUser interface{}, mods queries.Applicator) error { + var slice []*User + var object *User + + if singular { + var ok bool + object, ok = maybeUser.(*User) + if !ok { + object = new(User) + ok = queries.SetFromEmbeddedStruct(&object, &maybeUser) + if !ok { + return errors.New(fmt.Sprintf("failed to set %T from embedded struct %T", object, maybeUser)) + } + } + } else { + s, ok := maybeUser.(*[]*User) + if ok { + slice = *s + } else { + ok = queries.SetFromEmbeddedStruct(&slice, maybeUser) + if !ok { + return errors.New(fmt.Sprintf("failed to set %T from embedded struct %T", slice, maybeUser)) + } + } + } + + args := make(map[interface{}]struct{}) + if singular { + if object.R == nil { + object.R = &userR{} + } + args[object.ID] = struct{}{} + } else { + for _, obj := range slice { + if obj.R == nil { + obj.R = &userR{} + } + args[obj.ID] = struct{}{} + } + } + + if len(args) == 0 { + return nil + } + + argsSlice := make([]interface{}, len(args)) + i := 0 + for arg := range args { + argsSlice[i] = arg + i++ + } + + query := NewQuery( + qm.From(`posts`), + qm.WhereIn(`posts.user_id in ?`, argsSlice...), + ) + if mods != nil { + mods.Apply(query) + } + + results, err := query.QueryContext(ctx, e) + if err != nil { + return errors.Wrap(err, "failed to eager load posts") + } + + var resultSlice []*Post + if err = queries.Bind(results, &resultSlice); err != nil { + return errors.Wrap(err, "failed to bind eager loaded slice posts") + } + + if err = results.Close(); err != nil { + return errors.Wrap(err, "failed to close results in eager load on posts") + } + if err = results.Err(); err != nil { + return errors.Wrap(err, "error occurred during iteration of eager loaded relations for posts") + } + + if len(postAfterSelectHooks) != 0 { + for _, obj := range resultSlice { + if err := obj.doAfterSelectHooks(ctx, e); err != nil { + return err + } + } + } + if singular { + object.R.Posts = resultSlice + for _, foreign := range resultSlice { + if foreign.R == nil { + foreign.R = &postR{} + } + foreign.R.User = object + } + return nil + } + + for _, foreign := range resultSlice { + for _, local := range slice { + if local.ID == foreign.UserID { + local.R.Posts = append(local.R.Posts, foreign) + if foreign.R == nil { + foreign.R = &postR{} + } + foreign.R.User = local + break + } + } + } + + return nil +} + +// LoadTags allows an eager lookup of values, cached into the +// loaded structs of the objects. This is for a 1-M or N-M relationship. +func (userL) LoadTags(ctx context.Context, e boil.ContextExecutor, singular bool, maybeUser interface{}, mods queries.Applicator) error { + var slice []*User + var object *User + + if singular { + var ok bool + object, ok = maybeUser.(*User) + if !ok { + object = new(User) + ok = queries.SetFromEmbeddedStruct(&object, &maybeUser) + if !ok { + return errors.New(fmt.Sprintf("failed to set %T from embedded struct %T", object, maybeUser)) + } + } + } else { + s, ok := maybeUser.(*[]*User) + if ok { + slice = *s + } else { + ok = queries.SetFromEmbeddedStruct(&slice, maybeUser) + if !ok { + return errors.New(fmt.Sprintf("failed to set %T from embedded struct %T", slice, maybeUser)) + } + } + } + + args := make(map[interface{}]struct{}) + if singular { + if object.R == nil { + object.R = &userR{} + } + args[object.ID] = struct{}{} + } else { + for _, obj := range slice { + if obj.R == nil { + obj.R = &userR{} + } + args[obj.ID] = struct{}{} + } + } + + if len(args) == 0 { + return nil + } + + argsSlice := make([]interface{}, len(args)) + i := 0 + for arg := range args { + argsSlice[i] = arg + i++ + } + + query := NewQuery( + qm.From(`tags`), + qm.WhereIn(`tags.user_id in ?`, argsSlice...), + ) + if mods != nil { + mods.Apply(query) + } + + results, err := query.QueryContext(ctx, e) + if err != nil { + return errors.Wrap(err, "failed to eager load tags") + } + + var resultSlice []*Tag + if err = queries.Bind(results, &resultSlice); err != nil { + return errors.Wrap(err, "failed to bind eager loaded slice tags") + } + + if err = results.Close(); err != nil { + return errors.Wrap(err, "failed to close results in eager load on tags") + } + if err = results.Err(); err != nil { + return errors.Wrap(err, "error occurred during iteration of eager loaded relations for tags") + } + + if len(tagAfterSelectHooks) != 0 { + for _, obj := range resultSlice { + if err := obj.doAfterSelectHooks(ctx, e); err != nil { + return err + } + } + } + if singular { + object.R.Tags = resultSlice + for _, foreign := range resultSlice { + if foreign.R == nil { + foreign.R = &tagR{} + } + foreign.R.User = object + } + return nil + } + + for _, foreign := range resultSlice { + for _, local := range slice { + if local.ID == foreign.UserID { + local.R.Tags = append(local.R.Tags, foreign) + if foreign.R == nil { + foreign.R = &tagR{} + } + foreign.R.User = local + break + } + } + } + + return nil +} + +// AddPosts adds the given related objects to the existing relationships +// of the user, optionally inserting them as new records. +// Appends related to o.R.Posts. +// Sets related.R.User appropriately. +func (o *User) AddPosts(ctx context.Context, exec boil.ContextExecutor, insert bool, related ...*Post) error { + var err error + for _, rel := range related { + if insert { + rel.UserID = o.ID + if err = rel.Insert(ctx, exec, boil.Infer()); err != nil { + return errors.Wrap(err, "failed to insert into foreign table") + } + } else { + updateQuery := fmt.Sprintf( + "UPDATE \"posts\" SET %s WHERE %s", + strmangle.SetParamNames("\"", "\"", 1, []string{"user_id"}), + strmangle.WhereClause("\"", "\"", 2, postPrimaryKeyColumns), + ) + values := []interface{}{o.ID, rel.ID} + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, updateQuery) + fmt.Fprintln(writer, values) + } + if _, err = exec.ExecContext(ctx, updateQuery, values...); err != nil { + return errors.Wrap(err, "failed to update foreign table") + } + + rel.UserID = o.ID + } + } + + if o.R == nil { + o.R = &userR{ + Posts: related, + } + } else { + o.R.Posts = append(o.R.Posts, related...) + } + + for _, rel := range related { + if rel.R == nil { + rel.R = &postR{ + User: o, + } + } else { + rel.R.User = o + } + } + return nil +} + +// AddTags adds the given related objects to the existing relationships +// of the user, optionally inserting them as new records. +// Appends related to o.R.Tags. +// Sets related.R.User appropriately. +func (o *User) AddTags(ctx context.Context, exec boil.ContextExecutor, insert bool, related ...*Tag) error { + var err error + for _, rel := range related { + if insert { + rel.UserID = o.ID + if err = rel.Insert(ctx, exec, boil.Infer()); err != nil { + return errors.Wrap(err, "failed to insert into foreign table") + } + } else { + updateQuery := fmt.Sprintf( + "UPDATE \"tags\" SET %s WHERE %s", + strmangle.SetParamNames("\"", "\"", 1, []string{"user_id"}), + strmangle.WhereClause("\"", "\"", 2, tagPrimaryKeyColumns), + ) + values := []interface{}{o.ID, rel.ID} + + if boil.IsDebug(ctx) { + writer := boil.DebugWriterFrom(ctx) + fmt.Fprintln(writer, updateQuery) + fmt.Fprintln(writer, values) + } + if _, err = exec.ExecContext(ctx, updateQuery, values...); err != nil { + return errors.Wrap(err, "failed to update foreign table") + } + + rel.UserID = o.ID + } + } + + if o.R == nil { + o.R = &userR{ + Tags: related, + } + } else { + o.R.Tags = append(o.R.Tags, related...) + } + + for _, rel := range related { + if rel.R == nil { + rel.R = &tagR{ + User: o, + } + } else { + rel.R.User = o + } + } + return nil +} + // Users retrieves all the records using an executor. func Users(mods ...qm.QueryMod) userQuery { mods = append(mods, qm.From("\"users\"")) diff --git a/src/models/users_test.go b/src/models/users_test.go index 878ae32..db6ca4c 100644 --- a/src/models/users_test.go +++ b/src/models/users_test.go @@ -494,6 +494,313 @@ func testUsersInsertWhitelist(t *testing.T) { } } +func testUserToManyPosts(t *testing.T) { + var err error + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + + var a User + var b, c Post + + seed := randomize.NewSeed() + if err = randomize.Struct(seed, &a, userDBTypes, true, userColumnsWithDefault...); err != nil { + t.Errorf("Unable to randomize User struct: %s", err) + } + + if err := a.Insert(ctx, tx, boil.Infer()); err != nil { + t.Fatal(err) + } + + if err = randomize.Struct(seed, &b, postDBTypes, false, postColumnsWithDefault...); err != nil { + t.Fatal(err) + } + if err = randomize.Struct(seed, &c, postDBTypes, false, postColumnsWithDefault...); err != nil { + t.Fatal(err) + } + + b.UserID = a.ID + c.UserID = a.ID + + if err = b.Insert(ctx, tx, boil.Infer()); err != nil { + t.Fatal(err) + } + if err = c.Insert(ctx, tx, boil.Infer()); err != nil { + t.Fatal(err) + } + + check, err := a.Posts().All(ctx, tx) + if err != nil { + t.Fatal(err) + } + + bFound, cFound := false, false + for _, v := range check { + if v.UserID == b.UserID { + bFound = true + } + if v.UserID == c.UserID { + cFound = true + } + } + + if !bFound { + t.Error("expected to find b") + } + if !cFound { + t.Error("expected to find c") + } + + slice := UserSlice{&a} + if err = a.L.LoadPosts(ctx, tx, false, (*[]*User)(&slice), nil); err != nil { + t.Fatal(err) + } + if got := len(a.R.Posts); got != 2 { + t.Error("number of eager loaded records wrong, got:", got) + } + + a.R.Posts = nil + if err = a.L.LoadPosts(ctx, tx, true, &a, nil); err != nil { + t.Fatal(err) + } + if got := len(a.R.Posts); got != 2 { + t.Error("number of eager loaded records wrong, got:", got) + } + + if t.Failed() { + t.Logf("%#v", check) + } +} + +func testUserToManyTags(t *testing.T) { + var err error + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + + var a User + var b, c Tag + + seed := randomize.NewSeed() + if err = randomize.Struct(seed, &a, userDBTypes, true, userColumnsWithDefault...); err != nil { + t.Errorf("Unable to randomize User struct: %s", err) + } + + if err := a.Insert(ctx, tx, boil.Infer()); err != nil { + t.Fatal(err) + } + + if err = randomize.Struct(seed, &b, tagDBTypes, false, tagColumnsWithDefault...); err != nil { + t.Fatal(err) + } + if err = randomize.Struct(seed, &c, tagDBTypes, false, tagColumnsWithDefault...); err != nil { + t.Fatal(err) + } + + b.UserID = a.ID + c.UserID = a.ID + + if err = b.Insert(ctx, tx, boil.Infer()); err != nil { + t.Fatal(err) + } + if err = c.Insert(ctx, tx, boil.Infer()); err != nil { + t.Fatal(err) + } + + check, err := a.Tags().All(ctx, tx) + if err != nil { + t.Fatal(err) + } + + bFound, cFound := false, false + for _, v := range check { + if v.UserID == b.UserID { + bFound = true + } + if v.UserID == c.UserID { + cFound = true + } + } + + if !bFound { + t.Error("expected to find b") + } + if !cFound { + t.Error("expected to find c") + } + + slice := UserSlice{&a} + if err = a.L.LoadTags(ctx, tx, false, (*[]*User)(&slice), nil); err != nil { + t.Fatal(err) + } + if got := len(a.R.Tags); got != 2 { + t.Error("number of eager loaded records wrong, got:", got) + } + + a.R.Tags = nil + if err = a.L.LoadTags(ctx, tx, true, &a, nil); err != nil { + t.Fatal(err) + } + if got := len(a.R.Tags); got != 2 { + t.Error("number of eager loaded records wrong, got:", got) + } + + if t.Failed() { + t.Logf("%#v", check) + } +} + +func testUserToManyAddOpPosts(t *testing.T) { + var err error + + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + + var a User + var b, c, d, e Post + + seed := randomize.NewSeed() + if err = randomize.Struct(seed, &a, userDBTypes, false, strmangle.SetComplement(userPrimaryKeyColumns, userColumnsWithoutDefault)...); err != nil { + t.Fatal(err) + } + foreigners := []*Post{&b, &c, &d, &e} + for _, x := range foreigners { + if err = randomize.Struct(seed, x, postDBTypes, false, strmangle.SetComplement(postPrimaryKeyColumns, postColumnsWithoutDefault)...); err != nil { + t.Fatal(err) + } + } + + if err := a.Insert(ctx, tx, boil.Infer()); err != nil { + t.Fatal(err) + } + if err = b.Insert(ctx, tx, boil.Infer()); err != nil { + t.Fatal(err) + } + if err = c.Insert(ctx, tx, boil.Infer()); err != nil { + t.Fatal(err) + } + + foreignersSplitByInsertion := [][]*Post{ + {&b, &c}, + {&d, &e}, + } + + for i, x := range foreignersSplitByInsertion { + err = a.AddPosts(ctx, tx, i != 0, x...) + if err != nil { + t.Fatal(err) + } + + first := x[0] + second := x[1] + + if a.ID != first.UserID { + t.Error("foreign key was wrong value", a.ID, first.UserID) + } + if a.ID != second.UserID { + t.Error("foreign key was wrong value", a.ID, second.UserID) + } + + if first.R.User != &a { + t.Error("relationship was not added properly to the foreign slice") + } + if second.R.User != &a { + t.Error("relationship was not added properly to the foreign slice") + } + + if a.R.Posts[i*2] != first { + t.Error("relationship struct slice not set to correct value") + } + if a.R.Posts[i*2+1] != second { + t.Error("relationship struct slice not set to correct value") + } + + count, err := a.Posts().Count(ctx, tx) + if err != nil { + t.Fatal(err) + } + if want := int64((i + 1) * 2); count != want { + t.Error("want", want, "got", count) + } + } +} +func testUserToManyAddOpTags(t *testing.T) { + var err error + + ctx := context.Background() + tx := MustTx(boil.BeginTx(ctx, nil)) + defer func() { _ = tx.Rollback() }() + + var a User + var b, c, d, e Tag + + seed := randomize.NewSeed() + if err = randomize.Struct(seed, &a, userDBTypes, false, strmangle.SetComplement(userPrimaryKeyColumns, userColumnsWithoutDefault)...); err != nil { + t.Fatal(err) + } + foreigners := []*Tag{&b, &c, &d, &e} + for _, x := range foreigners { + if err = randomize.Struct(seed, x, tagDBTypes, false, strmangle.SetComplement(tagPrimaryKeyColumns, tagColumnsWithoutDefault)...); err != nil { + t.Fatal(err) + } + } + + if err := a.Insert(ctx, tx, boil.Infer()); err != nil { + t.Fatal(err) + } + if err = b.Insert(ctx, tx, boil.Infer()); err != nil { + t.Fatal(err) + } + if err = c.Insert(ctx, tx, boil.Infer()); err != nil { + t.Fatal(err) + } + + foreignersSplitByInsertion := [][]*Tag{ + {&b, &c}, + {&d, &e}, + } + + for i, x := range foreignersSplitByInsertion { + err = a.AddTags(ctx, tx, i != 0, x...) + if err != nil { + t.Fatal(err) + } + + first := x[0] + second := x[1] + + if a.ID != first.UserID { + t.Error("foreign key was wrong value", a.ID, first.UserID) + } + if a.ID != second.UserID { + t.Error("foreign key was wrong value", a.ID, second.UserID) + } + + if first.R.User != &a { + t.Error("relationship was not added properly to the foreign slice") + } + if second.R.User != &a { + t.Error("relationship was not added properly to the foreign slice") + } + + if a.R.Tags[i*2] != first { + t.Error("relationship struct slice not set to correct value") + } + if a.R.Tags[i*2+1] != second { + t.Error("relationship struct slice not set to correct value") + } + + count, err := a.Tags().Count(ctx, tx) + if err != nil { + t.Fatal(err) + } + if want := int64((i + 1) * 2); count != want { + t.Error("want", want, "got", count) + } + } +} + func testUsersReload(t *testing.T) { t.Parallel() diff --git a/src/post/routes.go b/src/post/routes.go index 09457d3..7714920 100644 --- a/src/post/routes.go +++ b/src/post/routes.go @@ -4,9 +4,11 @@ import ( "fmt" "log" "net/http" + "strconv" "github.com/go-chi/chi/v5" "github.com/volatiletech/sqlboiler/v4/boil" + . "github.com/volatiletech/sqlboiler/v4/queries/qm" "gitlab.com/alexkavon/newsstand/src/models" "gitlab.com/alexkavon/newsstand/src/server" "gitlab.com/alexkavon/newsstand/src/sessions" @@ -30,7 +32,7 @@ var Routes = server.Routes{ server.Route{ Name: "Get", Method: "GET", - Path: "/p/{:id}", + Path: "/p/{id}", HandlerFunc: Get, }, } @@ -45,8 +47,8 @@ func Store(s *server.Server) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { var post models.Post post.Title = r.PostFormValue("title") - post.Url = r.PostFormValue("url") - post.Description = r.PostFormValue("description") + post.Description.SetValid(r.PostFormValue("description")) + post.URL.SetValid(r.PostFormValue("url")) // validate post // process post, look for spamminess, bad url @@ -65,6 +67,19 @@ func Store(s *server.Server) http.HandlerFunc { func Get(s *server.Server) http.HandlerFunc { return func(w http.ResponseWriter, r *http.Request) { - post := models.FindPost(r.Context(), s.Db.ToSqlDb(), chi.URLParam(r, "id")) + postId, err := strconv.Atoi(chi.URLParam(r, "id")) + if err != nil { + log.Fatal(err) + } + + post, err := models.Posts( + Where("id = ?", postId), + Load("User"), + Load("Tags"), + ).One(r.Context(), s.Db.ToSqlDb()) + if err != nil { + log.Fatal(err) + } + s.Ui.Render(w, r, "post/get", map[string]any{"post": post}) } } diff --git a/ui/pages/post/get.tmpl.html b/ui/pages/post/get.tmpl.html index f705f65..b32bd1c 100644 --- a/ui/pages/post/get.tmpl.html +++ b/ui/pages/post/get.tmpl.html @@ -1,19 +1,23 @@ -{{define "title"}}{{.Title}}{{end}} +{{define "title"}}{{.post.Title}}{{end}} {{define "main"}} - <a class="title" href="{{.Url}}" target="_blank">{{.Title}}</a> - <span class="post-tags">{{.Tags}}</span> - <span class="post-domain">{{.UrlDomainName}}</span> - <span class="post-author">by <a href="u/{{.Username}}">{{.Username}}</a></span> - <span class="post-date">{{.CreatedAt}}</span> + <a class="title" href="{{.post.URL.String}}" target="_blank">{{.post.Title}}</a> + <span class="post-tags"> + {{range $tag := .post.R.Tags}} + <span class="post-tag-{{$tag.Tag}}">{{$tag.Tag}}</span> + {{end}} + </span> + <span class="post-domain">{{.post.URL.String}} (Pretty URL)</span> + <span class="post-author">by <a href="u/{{.post.R.User.ID}}">{{.post.R.User.Username}}</a></span> + <span class="post-date">{{.post.CreatedAt.Format "Jan 02, 2006 15:04"}}</span> <span class="actions"> <span class="action-flag">Flag</span> <span class="action-hide">Hide</span> </span> - {{ if and .Description not .Url }} - <p>{{.Description}}</p> + {{ if .post.Description.Valid }} + <p>{{.post.Description.String}}</p> {{end}} - <form action="/p/{{.ID}}/comment" method="POST"> + <form action="/p/{{.post.ID}}/comment" method="POST"> <textarea placeholder="This better be good." name="comment"></textarea> <button type="submit">Yup</button> <button class="preview" type="button">Preview</button> |
