tweak home timeline query to use colossal IN statement
This commit is contained in:
parent
4ae16bce8c
commit
5da8160d31
|
@ -19,11 +19,13 @@ package bundb
|
|||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/superseriousbusiness/gotosocial/internal/db"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtscontext"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/id"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/log"
|
||||
|
@ -101,22 +103,39 @@ func (t *timelineDB) GetHomeTimeline(ctx context.Context, accountID string, maxI
|
|||
q = q.Order("status.id ASC")
|
||||
}
|
||||
|
||||
// Subquery to select target (followed) account
|
||||
// IDs from follows owned by given accountID.
|
||||
subQ := t.db.
|
||||
NewSelect().
|
||||
TableExpr("? AS ?", bun.Ident("follows"), bun.Ident("follow")).
|
||||
Column("follow.target_account_id").
|
||||
Where("? = ?", bun.Ident("follow.account_id"), accountID)
|
||||
// As this is the home timeline, it should be
|
||||
// populated by statuses from accounts followed
|
||||
// by accountID, and posts from accountID itself.
|
||||
//
|
||||
// So, begin by seeing who accountID follows.
|
||||
// It should be a little cheaper to do this in
|
||||
// a separate query like this, rather than using
|
||||
// a join, since followIDs are cached in memory.
|
||||
follows, err := t.state.DB.GetAccountFollows(
|
||||
gtscontext.SetBarebones(ctx),
|
||||
accountID,
|
||||
)
|
||||
if err != nil && !errors.Is(err, db.ErrNoEntries) {
|
||||
return nil, gtserror.Newf("db error getting follows for account %s: %w", accountID, err)
|
||||
}
|
||||
|
||||
// Use the subquery in a WhereGroup here to specify that we want EITHER
|
||||
// - statuses posted by accountID itself OR
|
||||
// - statuses posted by accounts that accountID follows
|
||||
q = q.WhereGroup(" AND ", func(q *bun.SelectQuery) *bun.SelectQuery {
|
||||
return q.
|
||||
Where("? = ?", bun.Ident("status.account_id"), accountID).
|
||||
WhereOr("? IN (?)", bun.Ident("status.account_id"), subQ)
|
||||
})
|
||||
// Extract just the accountID from each follow.
|
||||
targetAccountIDs := make([]string, len(follows)+1)
|
||||
for i, f := range follows {
|
||||
targetAccountIDs[i] = f.TargetAccountID
|
||||
}
|
||||
|
||||
// Add accountID itself as a pseudo follow so that
|
||||
// accountID can see its own posts in the timeline.
|
||||
targetAccountIDs[len(targetAccountIDs)-1] = accountID
|
||||
|
||||
// Select only statuses authored by
|
||||
// accounts with IDs in the slice.
|
||||
q = q.Where(
|
||||
"? IN (?)",
|
||||
bun.Ident("status.account_id"),
|
||||
bun.In(targetAccountIDs),
|
||||
)
|
||||
|
||||
if err := q.Scan(ctx, &statusIDs); err != nil {
|
||||
return nil, err
|
||||
|
|
|
@ -24,6 +24,7 @@ import (
|
|||
|
||||
"github.com/stretchr/testify/suite"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/ap"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtscontext"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/id"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/util"
|
||||
|
@ -156,6 +157,38 @@ func (suite *TimelineTestSuite) TestGetHomeTimeline() {
|
|||
suite.checkStatuses(s, id.Highest, id.Lowest, 16)
|
||||
}
|
||||
|
||||
func (suite *TimelineTestSuite) TestGetHomeTimelineNoFollowing() {
|
||||
var (
|
||||
ctx = context.Background()
|
||||
viewingAccount = suite.testAccounts["local_account_1"]
|
||||
)
|
||||
|
||||
// Remove all of viewingAccount's follows.
|
||||
follows, err := suite.state.DB.GetAccountFollows(
|
||||
gtscontext.SetBarebones(ctx),
|
||||
viewingAccount.ID,
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
||||
for _, f := range follows {
|
||||
if err := suite.state.DB.DeleteFollowByID(ctx, f.ID); err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
// Query should work fine; though far
|
||||
// fewer statuses will be returned ofc.
|
||||
s, err := suite.db.GetHomeTimeline(ctx, viewingAccount.ID, "", "", "", 20, false)
|
||||
if err != nil {
|
||||
suite.FailNow(err.Error())
|
||||
}
|
||||
|
||||
suite.checkStatuses(s, id.Highest, id.Lowest, 5)
|
||||
}
|
||||
|
||||
func (suite *TimelineTestSuite) TestGetHomeTimelineWithFutureStatus() {
|
||||
var (
|
||||
ctx = context.Background()
|
||||
|
|
Loading…
Reference in New Issue