Query automatically does ToSlice. Use QueryIterator if you need an

iterator.
This commit is contained in:
Asaf Gartner 2021-12-15 03:36:37 +02:00
parent c84b6842e2
commit b5eb718615
14 changed files with 41 additions and 48 deletions

View File

@ -231,7 +231,16 @@ func followPathThroughStructs(structPtrVal reflect.Value, path []int) (reflect.V
return val, field return val, field
} }
func Query(ctx context.Context, conn ConnOrTx, destExample interface{}, query string, args ...interface{}) (*StructQueryIterator, error) { func Query(ctx context.Context, conn ConnOrTx, destExample interface{}, query string, args ...interface{}) ([]interface{}, error) {
it, err := QueryIterator(ctx, conn, destExample, query, args...)
if err != nil {
return nil, err
} else {
return it.ToSlice(), nil
}
}
func QueryIterator(ctx context.Context, conn ConnOrTx, destExample interface{}, query string, args ...interface{}) (*StructQueryIterator, error) {
destType := reflect.TypeOf(destExample) destType := reflect.TypeOf(destExample)
columnNames, fieldPaths, err := getColumnNamesAndPaths(destType, nil, "") columnNames, fieldPaths, err := getColumnNamesAndPaths(destType, nil, "")
if err != nil { if err != nil {
@ -347,7 +356,7 @@ result but find nothing.
var NotFound = errors.New("not found") var NotFound = errors.New("not found")
func QueryOne(ctx context.Context, conn ConnOrTx, destExample interface{}, query string, args ...interface{}) (interface{}, error) { func QueryOne(ctx context.Context, conn ConnOrTx, destExample interface{}, query string, args ...interface{}) (interface{}, error) {
rows, err := Query(ctx, conn, destExample, query, args...) rows, err := QueryIterator(ctx, conn, destExample, query, args...)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -408,7 +408,7 @@ func (bot *botInstance) doSender(ctx context.Context) {
} }
defer tx.Rollback(ctx) defer tx.Rollback(ctx)
itMessages, err := db.Query(ctx, tx, models.DiscordOutgoingMessage{}, ` msgs, err := db.Query(ctx, tx, models.DiscordOutgoingMessage{}, `
SELECT $columns SELECT $columns
FROM discord_outgoingmessages FROM discord_outgoingmessages
ORDER BY id ASC ORDER BY id ASC
@ -418,7 +418,6 @@ func (bot *botInstance) doSender(ctx context.Context) {
return return
} }
msgs := itMessages.ToSlice()
for _, imsg := range msgs { for _, imsg := range msgs {
msg := imsg.(*models.DiscordOutgoingMessage) msg := imsg.(*models.DiscordOutgoingMessage)
if time.Now().After(msg.ExpiresAt) { if time.Now().After(msg.ExpiresAt) {

View File

@ -64,7 +64,7 @@ func fetchMissingContent(ctx context.Context, dbConn *pgxpool.Pool) {
type query struct { type query struct {
Message models.DiscordMessage `db:"msg"` Message models.DiscordMessage `db:"msg"`
} }
result, err := db.Query(ctx, dbConn, query{}, imessagesWithoutContent, err := db.Query(ctx, dbConn, query{},
` `
SELECT $columns SELECT $columns
FROM FROM
@ -82,7 +82,6 @@ func fetchMissingContent(ctx context.Context, dbConn *pgxpool.Pool) {
log.Error().Err(err).Msg("failed to check for messages without content") log.Error().Err(err).Msg("failed to check for messages without content")
return return
} }
imessagesWithoutContent := result.ToSlice()
if len(imessagesWithoutContent) > 0 { if len(imessagesWithoutContent) > 0 {
log.Info().Msgf("There are %d Discord messages without content, fetching their content now...", len(imessagesWithoutContent)) log.Info().Msgf("There are %d Discord messages without content, fetching their content now...", len(imessagesWithoutContent))

View File

@ -749,7 +749,7 @@ func UpdateSnippetTagsIfAny(ctx context.Context, dbConn db.ConnOrTx, msg *Messag
type tagsRow struct { type tagsRow struct {
Tag models.Tag `db:"tags"` Tag models.Tag `db:"tags"`
} }
itUserTags, err := db.Query(ctx, tx, tagsRow{}, iUserTags, err := db.Query(ctx, tx, tagsRow{},
` `
SELECT $columns SELECT $columns
FROM FROM
@ -764,7 +764,6 @@ func UpdateSnippetTagsIfAny(ctx context.Context, dbConn db.ConnOrTx, msg *Messag
if err != nil { if err != nil {
return oops.New(err, "failed to fetch tags for user projects") return oops.New(err, "failed to fetch tags for user projects")
} }
iUserTags := itUserTags.ToSlice()
var tagIDs []int var tagIDs []int
for _, itag := range iUserTags { for _, itag := range iUserTags {
@ -805,7 +804,7 @@ var RESnippetableUrl = regexp.MustCompile(`^https?://(youtu\.be|(www\.)?youtube\
func getSnippetAssetOrUrl(ctx context.Context, tx db.ConnOrTx, msg *models.DiscordMessage) (*uuid.UUID, *string, error) { func getSnippetAssetOrUrl(ctx context.Context, tx db.ConnOrTx, msg *models.DiscordMessage) (*uuid.UUID, *string, error) {
// Check attachments // Check attachments
itAttachments, err := db.Query(ctx, tx, models.DiscordMessageAttachment{}, attachments, err := db.Query(ctx, tx, models.DiscordMessageAttachment{},
` `
SELECT $columns SELECT $columns
FROM handmade_discordmessageattachment FROM handmade_discordmessageattachment
@ -816,14 +815,13 @@ func getSnippetAssetOrUrl(ctx context.Context, tx db.ConnOrTx, msg *models.Disco
if err != nil { if err != nil {
return nil, nil, oops.New(err, "failed to fetch message attachments") return nil, nil, oops.New(err, "failed to fetch message attachments")
} }
attachments := itAttachments.ToSlice()
for _, iattachment := range attachments { for _, iattachment := range attachments {
attachment := iattachment.(*models.DiscordMessageAttachment) attachment := iattachment.(*models.DiscordMessageAttachment)
return &attachment.AssetID, nil, nil return &attachment.AssetID, nil, nil
} }
// Check embeds // Check embeds
itEmbeds, err := db.Query(ctx, tx, models.DiscordMessageEmbed{}, embeds, err := db.Query(ctx, tx, models.DiscordMessageEmbed{},
` `
SELECT $columns SELECT $columns
FROM handmade_discordmessageembed FROM handmade_discordmessageembed
@ -834,7 +832,6 @@ func getSnippetAssetOrUrl(ctx context.Context, tx db.ConnOrTx, msg *models.Disco
if err != nil { if err != nil {
return nil, nil, oops.New(err, "failed to fetch discord embeds") return nil, nil, oops.New(err, "failed to fetch discord embeds")
} }
embeds := itEmbeds.ToSlice()
for _, iembed := range embeds { for _, iembed := range embeds {
embed := iembed.(*models.DiscordMessageEmbed) embed := iembed.(*models.DiscordMessageEmbed)
if embed.VideoID != nil { if embed.VideoID != nil {

View File

@ -145,11 +145,10 @@ func FetchProjects(
} }
// Do the query // Do the query
itProjects, err := db.Query(ctx, dbConn, projectRow{}, qb.String(), qb.Args()...) iprojects, err := db.Query(ctx, dbConn, projectRow{}, qb.String(), qb.Args()...)
if err != nil { if err != nil {
return nil, oops.New(err, "failed to fetch projects") return nil, oops.New(err, "failed to fetch projects")
} }
iprojects := itProjects.ToSlice()
// Fetch project owners to do permission checks // Fetch project owners to do permission checks
projectIds := make([]int, len(iprojects)) projectIds := make([]int, len(iprojects))
@ -340,7 +339,7 @@ func FetchMultipleProjectsOwners(
UserID int `db:"user_id"` UserID int `db:"user_id"`
ProjectID int `db:"project_id"` ProjectID int `db:"project_id"`
} }
it, err := db.Query(ctx, tx, userProject{}, iuserprojects, err := db.Query(ctx, tx, userProject{},
` `
SELECT $columns SELECT $columns
FROM handmade_user_projects FROM handmade_user_projects
@ -351,7 +350,6 @@ func FetchMultipleProjectsOwners(
if err != nil { if err != nil {
return nil, oops.New(err, "failed to fetch project IDs") return nil, oops.New(err, "failed to fetch project IDs")
} }
iuserprojects := it.ToSlice()
// Get the unique user IDs from this set and fetch the users from the db // Get the unique user IDs from this set and fetch the users from the db
var userIds []int var userIds []int
@ -368,7 +366,7 @@ func FetchMultipleProjectsOwners(
userIds = append(userIds, userProject.UserID) userIds = append(userIds, userProject.UserID)
} }
} }
it, err = db.Query(ctx, tx, models.User{}, iusers, err := db.Query(ctx, tx, models.User{},
` `
SELECT $columns SELECT $columns
FROM auth_user FROM auth_user
@ -380,7 +378,6 @@ func FetchMultipleProjectsOwners(
if err != nil { if err != nil {
return nil, oops.New(err, "failed to fetch users for projects") return nil, oops.New(err, "failed to fetch users for projects")
} }
iusers := it.ToSlice()
// Build the final result set with real user data // Build the final result set with real user data
res := make([]ProjectOwners, len(projectIds)) res := make([]ProjectOwners, len(projectIds))

View File

@ -47,7 +47,7 @@ func FetchSnippets(
type snippetIDRow struct { type snippetIDRow struct {
SnippetID int `db:"snippet_id"` SnippetID int `db:"snippet_id"`
} }
itSnippetIDs, err := db.Query(ctx, tx, snippetIDRow{}, iSnippetIDs, err := db.Query(ctx, tx, snippetIDRow{},
` `
SELECT DISTINCT snippet_id SELECT DISTINCT snippet_id
FROM FROM
@ -61,7 +61,6 @@ func FetchSnippets(
if err != nil { if err != nil {
return nil, oops.New(err, "failed to get snippet IDs for tag") return nil, oops.New(err, "failed to get snippet IDs for tag")
} }
iSnippetIDs := itSnippetIDs.ToSlice()
// special early-out: no snippets found for these tags at all // special early-out: no snippets found for these tags at all
if len(iSnippetIDs) == 0 { if len(iSnippetIDs) == 0 {
@ -125,11 +124,10 @@ func FetchSnippets(
DiscordMessage *models.DiscordMessage `db:"discord_message"` DiscordMessage *models.DiscordMessage `db:"discord_message"`
} }
it, err := db.Query(ctx, tx, resultRow{}, qb.String(), qb.Args()...) iresults, err := db.Query(ctx, tx, resultRow{}, qb.String(), qb.Args()...)
if err != nil { if err != nil {
return nil, oops.New(err, "failed to fetch threads") return nil, oops.New(err, "failed to fetch threads")
} }
iresults := it.ToSlice()
result := make([]SnippetAndStuff, len(iresults)) // allocate extra space because why not result := make([]SnippetAndStuff, len(iresults)) // allocate extra space because why not
snippetIDs := make([]int, len(iresults)) snippetIDs := make([]int, len(iresults))
@ -151,7 +149,7 @@ func FetchSnippets(
SnippetID int `db:"snippet_tags.snippet_id"` SnippetID int `db:"snippet_tags.snippet_id"`
Tag *models.Tag `db:"tags"` Tag *models.Tag `db:"tags"`
} }
itSnippetTags, err := db.Query(ctx, tx, snippetTagRow{}, iSnippetTags, err := db.Query(ctx, tx, snippetTagRow{},
` `
SELECT $columns SELECT $columns
FROM FROM
@ -165,7 +163,6 @@ func FetchSnippets(
if err != nil { if err != nil {
return nil, oops.New(err, "failed to fetch tags for snippets") return nil, oops.New(err, "failed to fetch tags for snippets")
} }
iSnippetTags := itSnippetTags.ToSlice()
// associate tags with snippets // associate tags with snippets
resultBySnippetId := make(map[int]*SnippetAndStuff) resultBySnippetId := make(map[int]*SnippetAndStuff)

View File

@ -40,11 +40,10 @@ func FetchTags(ctx context.Context, dbConn db.ConnOrTx, q TagQuery) ([]*models.T
qb.Add(`LIMIT $? OFFSET $?`, q.Limit, q.Offset) qb.Add(`LIMIT $? OFFSET $?`, q.Limit, q.Offset)
} }
it, err := db.Query(ctx, dbConn, models.Tag{}, qb.String(), qb.Args()...) itags, err := db.Query(ctx, dbConn, models.Tag{}, qb.String(), qb.Args()...)
if err != nil { if err != nil {
return nil, oops.New(err, "failed to fetch tags") return nil, oops.New(err, "failed to fetch tags")
} }
itags := it.ToSlice()
res := make([]*models.Tag, len(itags)) res := make([]*models.Tag, len(itags))
for i, itag := range itags { for i, itag := range itags {

View File

@ -143,11 +143,10 @@ func FetchThreads(
ForumLastReadTime *time.Time `db:"slri.lastread"` ForumLastReadTime *time.Time `db:"slri.lastread"`
} }
it, err := db.Query(ctx, dbConn, resultRow{}, qb.String(), qb.Args()...) iresults, err := db.Query(ctx, dbConn, resultRow{}, qb.String(), qb.Args()...)
if err != nil { if err != nil {
return nil, oops.New(err, "failed to fetch threads") return nil, oops.New(err, "failed to fetch threads")
} }
iresults := it.ToSlice()
result := make([]ThreadAndStuff, len(iresults)) result := make([]ThreadAndStuff, len(iresults))
for i, iresult := range iresults { for i, iresult := range iresults {
@ -398,11 +397,10 @@ func FetchPosts(
qb.Add(`LIMIT $? OFFSET $?`, q.Limit, q.Offset) qb.Add(`LIMIT $? OFFSET $?`, q.Limit, q.Offset)
} }
it, err := db.Query(ctx, dbConn, resultRow{}, qb.String(), qb.Args()...) iresults, err := db.Query(ctx, dbConn, resultRow{}, qb.String(), qb.Args()...)
if err != nil { if err != nil {
return nil, oops.New(err, "failed to fetch posts") return nil, oops.New(err, "failed to fetch posts")
} }
iresults := it.ToSlice()
result := make([]PostAndStuff, len(iresults)) result := make([]PostAndStuff, len(iresults))
for i, iresult := range iresults { for i, iresult := range iresults {
@ -856,7 +854,7 @@ func CreatePostVersion(ctx context.Context, tx pgx.Tx, postId int, unparsedConte
var values [][]interface{} var values [][]interface{}
for _, asset := range assetResult.ToSlice() { for _, asset := range assetResult {
values = append(values, []interface{}{postId, asset.(*assetId).AssetID}) values = append(values, []interface{}{postId, asset.(*assetId).AssetID})
} }
@ -892,7 +890,7 @@ func FixThreadPostIds(ctx context.Context, tx pgx.Tx, threadId int) error {
} }
var firstPost, lastPost *models.Post var firstPost, lastPost *models.Post
for _, ipost := range postsIter.ToSlice() { for _, ipost := range postsIter {
post := ipost.(*models.Post) post := ipost.(*models.Post)
if firstPost == nil || post.PostDate.Before(firstPost.PostDate) { if firstPost == nil || post.PostDate.Before(firstPost.PostDate) {

View File

@ -47,7 +47,7 @@ func GetFullSubforumTree(ctx context.Context, conn *pgxpool.Pool) SubforumTree {
type subforumRow struct { type subforumRow struct {
Subforum Subforum `db:"sf"` Subforum Subforum `db:"sf"`
} }
rows, err := db.Query(ctx, conn, subforumRow{}, rowsSlice, err := db.Query(ctx, conn, subforumRow{},
` `
SELECT $columns SELECT $columns
FROM FROM
@ -59,7 +59,6 @@ func GetFullSubforumTree(ctx context.Context, conn *pgxpool.Pool) SubforumTree {
panic(oops.New(err, "failed to fetch subforum tree")) panic(oops.New(err, "failed to fetch subforum tree"))
} }
rowsSlice := rows.ToSlice()
sfTreeMap := make(map[int]*SubforumTreeNode, len(rowsSlice)) sfTreeMap := make(map[int]*SubforumTreeNode, len(rowsSlice))
for _, row := range rowsSlice { for _, row := range rowsSlice {
sf := row.(*subforumRow).Subforum sf := row.(*subforumRow).Subforum

View File

@ -253,7 +253,7 @@ func fetchUnapprovedPosts(c *RequestContext) ([]*UnapprovedPost, error) {
return nil, oops.New(err, "failed to fetch unapproved posts") return nil, oops.New(err, "failed to fetch unapproved posts")
} }
var res []*UnapprovedPost var res []*UnapprovedPost
for _, iresult := range it.ToSlice() { for _, iresult := range it {
res = append(res, iresult.(*UnapprovedPost)) res = append(res, iresult.(*UnapprovedPost))
} }
return res, nil return res, nil
@ -285,7 +285,7 @@ func deleteAllPostsForUser(ctx context.Context, conn *pgxpool.Pool, userId int)
return oops.New(err, "failed to fetch posts to delete for user") return oops.New(err, "failed to fetch posts to delete for user")
} }
for _, iResult := range it.ToSlice() { for _, iResult := range it {
row := iResult.(*toDelete) row := iResult.(*toDelete)
hmndata.DeletePost(ctx, tx, row.ThreadID, row.PostID) hmndata.DeletePost(ctx, tx, row.ThreadID, row.PostID)
} }

View File

@ -157,7 +157,7 @@ func DiscordShowcaseBacklog(c *RequestContext) ResponseData {
type messageIdQuery struct { type messageIdQuery struct {
MessageID string `db:"msg.id"` MessageID string `db:"msg.id"`
} }
itMsgIds, err := db.Query(c.Context(), c.Conn, messageIdQuery{}, iMsgIDs, err := db.Query(c.Context(), c.Conn, messageIdQuery{},
` `
SELECT $columns SELECT $columns
FROM FROM
@ -169,7 +169,9 @@ func DiscordShowcaseBacklog(c *RequestContext) ResponseData {
duser.UserID, duser.UserID,
config.Config.Discord.ShowcaseChannelID, config.Config.Discord.ShowcaseChannelID,
) )
iMsgIDs := itMsgIds.ToSlice() if err != nil {
return c.ErrorResponse(http.StatusInternalServerError, err)
}
var msgIDs []string var msgIDs []string
for _, imsgId := range iMsgIDs { for _, imsgId := range iMsgIDs {

View File

@ -573,7 +573,7 @@ func FetchPodcast(c *RequestContext, projectId int, fetchEpisodes bool, episodeG
if err != nil { if err != nil {
return result, oops.New(err, "failed to fetch podcast episodes") return result, oops.New(err, "failed to fetch podcast episodes")
} }
for _, episodeRow := range podcastEpisodeQueryResult.ToSlice() { for _, episodeRow := range podcastEpisodeQueryResult {
result.Episodes = append(result.Episodes, &episodeRow.(*podcastEpisodeQuery).Episode) result.Episodes = append(result.Episodes, &episodeRow.(*podcastEpisodeQuery).Episode)
} }
} else { } else {

View File

@ -323,15 +323,15 @@ func ProjectHomepage(c *RequestContext) ResponseData {
} }
} }
for _, screenshot := range screenshotQueryResult.ToSlice() { for _, screenshot := range screenshotQueryResult {
templateData.Screenshots = append(templateData.Screenshots, hmnurl.BuildUserFile(screenshot.(*screenshotQuery).Filename)) templateData.Screenshots = append(templateData.Screenshots, hmnurl.BuildUserFile(screenshot.(*screenshotQuery).Filename))
} }
for _, link := range projectLinkResult.ToSlice() { for _, link := range projectLinkResult {
templateData.ProjectLinks = append(templateData.ProjectLinks, templates.LinkToTemplate(&link.(*projectLinkQuery).Link)) templateData.ProjectLinks = append(templateData.ProjectLinks, templates.LinkToTemplate(&link.(*projectLinkQuery).Link))
} }
for _, post := range postQueryResult.ToSlice() { for _, post := range postQueryResult {
templateData.RecentActivity = append(templateData.RecentActivity, PostToTimelineItem( templateData.RecentActivity = append(templateData.RecentActivity, PostToTimelineItem(
c.UrlContext, c.UrlContext,
lineageBuilder, lineageBuilder,
@ -801,7 +801,7 @@ func updateProject(ctx context.Context, tx pgx.Tx, user *models.User, payload *P
} }
} }
ownerResult, err := db.Query(ctx, tx, models.User{}, ownerRows, err := db.Query(ctx, tx, models.User{},
` `
SELECT $columns SELECT $columns
FROM auth_user FROM auth_user
@ -812,7 +812,6 @@ func updateProject(ctx context.Context, tx pgx.Tx, user *models.User, payload *P
if err != nil { if err != nil {
return oops.New(err, "Failed to query users") return oops.New(err, "Failed to query users")
} }
ownerRows := ownerResult.ToSlice()
_, err = tx.Exec(ctx, _, err = tx.Exec(ctx,
` `

View File

@ -85,7 +85,7 @@ func UserProfile(c *RequestContext) ResponseData {
type userLinkQuery struct { type userLinkQuery struct {
UserLink models.Link `db:"link"` UserLink models.Link `db:"link"`
} }
userLinkQueryResult, err := db.Query(c.Context(), c.Conn, userLinkQuery{}, userLinksSlice, err := db.Query(c.Context(), c.Conn, userLinkQuery{},
` `
SELECT $columns SELECT $columns
FROM FROM
@ -99,7 +99,6 @@ func UserProfile(c *RequestContext) ResponseData {
if err != nil { if err != nil {
return c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to fetch links for user: %s", username)) return c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to fetch links for user: %s", username))
} }
userLinksSlice := userLinkQueryResult.ToSlice()
profileUserLinks := make([]templates.Link, 0, len(userLinksSlice)) profileUserLinks := make([]templates.Link, 0, len(userLinksSlice))
for _, l := range userLinksSlice { for _, l := range userLinksSlice {
profileUserLinks = append(profileUserLinks, templates.LinkToTemplate(&l.(*userLinkQuery).UserLink)) profileUserLinks = append(profileUserLinks, templates.LinkToTemplate(&l.(*userLinkQuery).UserLink))
@ -223,7 +222,7 @@ func UserSettings(c *RequestContext) ResponseData {
DiscordShowcaseBacklogUrl string DiscordShowcaseBacklogUrl string
} }
ilinks, err := db.Query(c.Context(), c.Conn, models.Link{}, links, err := db.Query(c.Context(), c.Conn, models.Link{},
` `
SELECT $columns SELECT $columns
FROM handmade_links FROM handmade_links
@ -235,7 +234,6 @@ func UserSettings(c *RequestContext) ResponseData {
if err != nil { if err != nil {
return c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to fetch user links")) return c.ErrorResponse(http.StatusInternalServerError, oops.New(err, "failed to fetch user links"))
} }
links := ilinks.ToSlice()
linksText := "" linksText := ""
for _, ilink := range links { for _, ilink := range links {