summaryrefslogtreecommitdiff
path: root/internal/db/bundb
diff options
context:
space:
mode:
Diffstat (limited to 'internal/db/bundb')
-rw-r--r--internal/db/bundb/account.go30
-rw-r--r--internal/db/bundb/bundb.go45
-rw-r--r--internal/db/bundb/conn.go41
-rw-r--r--internal/db/bundb/domain.go1
-rw-r--r--internal/db/bundb/hook.go (renamed from internal/db/bundb/trace.go)46
-rw-r--r--internal/db/bundb/mention.go46
-rw-r--r--internal/db/bundb/notification.go95
-rw-r--r--internal/db/bundb/status.go22
-rw-r--r--internal/db/bundb/timeline.go136
9 files changed, 236 insertions, 226 deletions
diff --git a/internal/db/bundb/account.go b/internal/db/bundb/account.go
index 30510bb8e..cf02ad100 100644
--- a/internal/db/bundb/account.go
+++ b/internal/db/bundb/account.go
@@ -35,8 +35,9 @@ import (
)
type accountDB struct {
- conn *DBConn
- cache *cache.AccountCache
+ conn *DBConn
+ cache *cache.AccountCache
+ status *statusDB
}
func (a *accountDB) newAccountQ(account *gtsmodel.Account) *bun.SelectQuery {
@@ -232,11 +233,12 @@ func (a *accountDB) CountAccountStatuses(ctx context.Context, accountID string)
}
func (a *accountDB) GetAccountStatuses(ctx context.Context, accountID string, limit int, excludeReplies bool, excludeReblogs bool, maxID string, minID string, pinnedOnly bool, mediaOnly bool, publicOnly bool) ([]*gtsmodel.Status, db.Error) {
- statuses := []*gtsmodel.Status{}
+ statusIDs := []string{}
q := a.conn.
NewSelect().
- Model(&statuses).
+ Table("statuses").
+ Column("id").
Order("id DESC")
if accountID != "" {
@@ -295,14 +297,30 @@ func (a *accountDB) GetAccountStatuses(ctx context.Context, accountID string, li
q = q.Where("visibility = ?", gtsmodel.VisibilityPublic)
}
- if err := q.Scan(ctx); err != nil {
+ if err := q.Scan(ctx, &statusIDs); err != nil {
return nil, a.conn.ProcessError(err)
}
- if len(statuses) == 0 {
+ // Catch case of no statuses early
+ if len(statusIDs) == 0 {
return nil, db.ErrNoEntries
}
+ // Allocate return slice (will be at most len statusIDS)
+ statuses := make([]*gtsmodel.Status, 0, len(statusIDs))
+
+ for _, id := range statusIDs {
+ // Fetch from status from database by ID
+ status, err := a.status.GetStatusByID(ctx, id)
+ if err != nil {
+ logrus.Errorf("GetAccountStatuses: error getting status %q: %v", id, err)
+ continue
+ }
+
+ // Append to return slice
+ statuses = append(statuses, status)
+ }
+
return statuses, nil
}
diff --git a/internal/db/bundb/bundb.go b/internal/db/bundb/bundb.go
index 6328ca34f..d92318afd 100644
--- a/internal/db/bundb/bundb.go
+++ b/internal/db/bundb/bundb.go
@@ -31,7 +31,6 @@ import (
"strings"
"time"
- "github.com/ReneKroon/ttlcache"
"github.com/jackc/pgx/v4"
"github.com/jackc/pgx/v4/stdlib"
"github.com/sirupsen/logrus"
@@ -46,6 +45,7 @@ import (
"github.com/uptrace/bun/dialect/sqlitedialect"
"github.com/uptrace/bun/migrate"
+ grufcache "codeberg.org/gruf/go-cache/v2"
"modernc.org/sqlite"
)
@@ -136,11 +136,8 @@ func NewBunDBService(ctx context.Context) (db.DB, error) {
return nil, fmt.Errorf("database type %s not supported for bundb", dbType)
}
- // add a hook to log queries and the time they take
- // only do this for logging where performance isn't 1st concern
- if logrus.GetLevel() >= logrus.DebugLevel && config.GetLogDbQueries() {
- conn.DB.AddQueryHook(newDebugQueryHook())
- }
+ // Add database query hook
+ conn.DB.AddQueryHook(queryHook{})
// table registration is needed for many-to-many, see:
// https://bun.uptrace.dev/orm/many-to-many-relation/
@@ -155,7 +152,27 @@ func NewBunDBService(ctx context.Context) (db.DB, error) {
return nil, fmt.Errorf("db migration error: %s", err)
}
+ // Create DB structs that require ptrs to each other
accounts := &accountDB{conn: conn, cache: cache.NewAccountCache()}
+ status := &statusDB{conn: conn, cache: cache.NewStatusCache()}
+ timeline := &timelineDB{conn: conn}
+
+ // Setup DB cross-referencing
+ accounts.status = status
+ status.accounts = accounts
+ timeline.status = status
+
+ // Prepare mentions cache
+ // TODO: move into internal/cache
+ mentionCache := grufcache.New[string, *gtsmodel.Mention]()
+ mentionCache.SetTTL(time.Minute*5, false)
+ mentionCache.Start(time.Second * 10)
+
+ // Prepare notifications cache
+ // TODO: move into internal/cache
+ notifCache := grufcache.New[string, *gtsmodel.Notification]()
+ notifCache.SetTTL(time.Minute*5, false)
+ notifCache.Start(time.Second * 10)
ps := &bunDBService{
Account: accounts,
@@ -179,11 +196,11 @@ func NewBunDBService(ctx context.Context) (db.DB, error) {
},
Mention: &mentionDB{
conn: conn,
- cache: ttlcache.NewCache(),
+ cache: mentionCache,
},
Notification: &notificationDB{
conn: conn,
- cache: ttlcache.NewCache(),
+ cache: notifCache,
},
Relationship: &relationshipDB{
conn: conn,
@@ -191,15 +208,9 @@ func NewBunDBService(ctx context.Context) (db.DB, error) {
Session: &sessionDB{
conn: conn,
},
- Status: &statusDB{
- conn: conn,
- cache: cache.NewStatusCache(),
- accounts: accounts,
- },
- Timeline: &timelineDB{
- conn: conn,
- },
- conn: conn,
+ Status: status,
+ Timeline: timeline,
+ conn: conn,
}
// we can confidently return this useable service now
diff --git a/internal/db/bundb/conn.go b/internal/db/bundb/conn.go
index baa0baeae..1c85f6f6f 100644
--- a/internal/db/bundb/conn.go
+++ b/internal/db/bundb/conn.go
@@ -11,13 +11,11 @@ import (
// DBConn wrapps a bun.DB conn to provide SQL-type specific additional functionality
type DBConn struct {
- // TODO: move *Config here, no need to be in each struct type
-
errProc func(error) db.Error // errProc is the SQL-type specific error processor
*bun.DB // DB is the underlying bun.DB connection
}
-// WrapDBConn @TODO
+// WrapDBConn wraps a bun DB connection to provide our own error processing dependent on DB dialect.
func WrapDBConn(dbConn *bun.DB) *DBConn {
var errProc func(error) db.Error
switch dbConn.Dialect().Name() {
@@ -36,21 +34,31 @@ func WrapDBConn(dbConn *bun.DB) *DBConn {
// RunInTx wraps execution of the supplied transaction function.
func (conn *DBConn) RunInTx(ctx context.Context, fn func(bun.Tx) error) db.Error {
- // Acquire a new transaction
- tx, err := conn.BeginTx(ctx, nil)
- if err != nil {
- return conn.ProcessError(err)
- }
+ return conn.ProcessError(func() error {
+ // Acquire a new transaction
+ tx, err := conn.BeginTx(ctx, nil)
+ if err != nil {
+ return err
+ }
- // Perform supplied transaction
- if err = fn(tx); err != nil {
- tx.Rollback() //nolint
- return conn.ProcessError(err)
- }
+ var done bool
+
+ defer func() {
+ if !done {
+ _ = tx.Rollback()
+ }
+ }()
+
+ // Perform supplied transaction
+ if err := fn(tx); err != nil {
+ return err
+ }
- // Finally, commit transaction
- err = tx.Commit()
- return conn.ProcessError(err)
+ // Finally, commit
+ err = tx.Commit()
+ done = true
+ return err
+ }())
}
// ProcessError processes an error to replace any known values with our own db.Error types,
@@ -83,7 +91,6 @@ func (conn *DBConn) Exists(ctx context.Context, query *bun.SelectQuery) (bool, d
// NotExists is the functional opposite of conn.Exists()
func (conn *DBConn) NotExists(ctx context.Context, query *bun.SelectQuery) (bool, db.Error) {
- // Simply inverse of conn.exists()
exists, err := conn.Exists(ctx, query)
return !exists, err
}
diff --git a/internal/db/bundb/domain.go b/internal/db/bundb/domain.go
index ee7fed6a9..fadb6dcf9 100644
--- a/internal/db/bundb/domain.go
+++ b/internal/db/bundb/domain.go
@@ -74,6 +74,5 @@ func (d *domainDB) AreURIsBlocked(ctx context.Context, uris []*url.URL) (bool, d
for _, uri := range uris {
domains = append(domains, uri.Hostname())
}
-
return d.AreDomainsBlocked(ctx, domains)
}
diff --git a/internal/db/bundb/trace.go b/internal/db/bundb/hook.go
index 27b5e22ac..6f9935272 100644
--- a/internal/db/bundb/trace.go
+++ b/internal/db/bundb/hook.go
@@ -26,35 +26,33 @@ import (
"github.com/uptrace/bun"
)
-func newDebugQueryHook() bun.QueryHook {
- return &debugQueryHook{}
-}
-
-// debugQueryHook implements bun.QueryHook
-type debugQueryHook struct {
-}
+// queryHook implements bun.QueryHook
+type queryHook struct{}
-func (q *debugQueryHook) BeforeQuery(ctx context.Context, _ *bun.QueryEvent) context.Context {
- // do nothing
- return ctx
+func (queryHook) BeforeQuery(ctx context.Context, _ *bun.QueryEvent) context.Context {
+ return ctx // do nothing
}
// AfterQuery logs the time taken to query, the operation (select, update, etc), and the query itself as translated by bun.
-func (q *debugQueryHook) AfterQuery(_ context.Context, event *bun.QueryEvent) {
- dur := time.Since(event.StartTime).Round(time.Microsecond)
- l := logrus.WithFields(logrus.Fields{
- "duration": dur,
- "operation": event.Operation(),
- })
-
- if dur > 1*time.Second {
- l.Warnf("SLOW DATABASE QUERY [%s] %s", dur, event.Query)
- return
+func (queryHook) AfterQuery(_ context.Context, event *bun.QueryEvent) {
+ // Get the DB query duration
+ dur := time.Since(event.StartTime)
+
+ log := func(lvl logrus.Level, msg string) {
+ logrus.WithFields(logrus.Fields{
+ "duration": dur,
+ "operation": event.Operation(),
+ "query": event.Query,
+ }).Log(lvl, msg)
}
- if logrus.GetLevel() == logrus.TraceLevel {
- l.Tracef("[%s] %s", dur, event.Query)
- } else {
- l.Debugf("[%s] %s", dur, event.Operation())
+ switch {
+ // Warn on slow database queries
+ case dur > time.Second:
+ log(logrus.WarnLevel, "SLOW DATABASE QUERY")
+
+ // On trace, we log query information
+ case logrus.GetLevel() == logrus.TraceLevel:
+ log(logrus.TraceLevel, "database query")
}
}
diff --git a/internal/db/bundb/mention.go b/internal/db/bundb/mention.go
index 1c1c25c4b..067f0d676 100644
--- a/internal/db/bundb/mention.go
+++ b/internal/db/bundb/mention.go
@@ -21,7 +21,8 @@ package bundb
import (
"context"
- "github.com/ReneKroon/ttlcache"
+ "codeberg.org/gruf/go-cache/v2"
+ "github.com/sirupsen/logrus"
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/uptrace/bun"
@@ -29,7 +30,7 @@ import (
type mentionDB struct {
conn *DBConn
- cache *ttlcache.Cache
+ cache cache.Cache[string, *gtsmodel.Mention]
}
func (m *mentionDB) newMentionQ(i interface{}) *bun.SelectQuery {
@@ -41,40 +42,24 @@ func (m *mentionDB) newMentionQ(i interface{}) *bun.SelectQuery {
Relation("TargetAccount")
}
-func (m *mentionDB) getMentionCached(id string) (*gtsmodel.Mention, bool) {
- v, ok := m.cache.Get(id)
- if !ok {
- return nil, false
- }
-
- mention, ok := v.(*gtsmodel.Mention)
- if !ok {
- panic("mention cache entry was not a mention")
- }
-
- return mention, true
-}
-
-func (m *mentionDB) putMentionCache(mention *gtsmodel.Mention) {
- m.cache.Set(mention.ID, mention)
-}
-
func (m *mentionDB) getMentionDB(ctx context.Context, id string) (*gtsmodel.Mention, db.Error) {
- mention := &gtsmodel.Mention{}
+ mention := gtsmodel.Mention{}
- q := m.newMentionQ(mention).
+ q := m.newMentionQ(&mention).
Where("mention.id = ?", id)
if err := q.Scan(ctx); err != nil {
return nil, m.conn.ProcessError(err)
}
- m.putMentionCache(mention)
- return mention, nil
+ copy := mention
+ m.cache.Set(mention.ID, &copy)
+
+ return &mention, nil
}
func (m *mentionDB) GetMention(ctx context.Context, id string) (*gtsmodel.Mention, db.Error) {
- if mention, cached := m.getMentionCached(id); cached {
+ if mention, ok := m.cache.Get(id); ok {
return mention, nil
}
return m.getMentionDB(ctx, id)
@@ -84,16 +69,11 @@ func (m *mentionDB) GetMentions(ctx context.Context, ids []string) ([]*gtsmodel.
mentions := make([]*gtsmodel.Mention, 0, len(ids))
for _, id := range ids {
- // Attempt fetch from cache
- mention, cached := m.getMentionCached(id)
- if cached {
- mentions = append(mentions, mention)
- }
-
// Attempt fetch from DB
- mention, err := m.getMentionDB(ctx, id)
+ mention, err := m.GetMention(ctx, id)
if err != nil {
- return nil, err
+ logrus.Errorf("GetMentions: error getting mention %q: %v", id, err)
+ continue
}
// Append mention
diff --git a/internal/db/bundb/notification.go b/internal/db/bundb/notification.go
index d01bb9067..f5ea099de 100644
--- a/internal/db/bundb/notification.go
+++ b/internal/db/bundb/notification.go
@@ -21,37 +21,39 @@ package bundb
import (
"context"
- "github.com/ReneKroon/ttlcache"
+ "codeberg.org/gruf/go-cache/v2"
+ "github.com/sirupsen/logrus"
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
- "github.com/uptrace/bun"
)
type notificationDB struct {
conn *DBConn
- cache *ttlcache.Cache
-}
-
-func (n *notificationDB) newNotificationQ(i interface{}) *bun.SelectQuery {
- return n.conn.
- NewSelect().
- Model(i).
- Relation("OriginAccount").
- Relation("TargetAccount").
- Relation("Status")
+ cache cache.Cache[string, *gtsmodel.Notification]
}
func (n *notificationDB) GetNotification(ctx context.Context, id string) (*gtsmodel.Notification, db.Error) {
- if notification, cached := n.getNotificationCache(id); cached {
+ if notification, ok := n.cache.Get(id); ok {
return notification, nil
}
- notif := &gtsmodel.Notification{}
- err := n.getNotificationDB(ctx, id, notif)
- if err != nil {
- return nil, err
+ dst := gtsmodel.Notification{ID: id}
+
+ q := n.conn.NewSelect().
+ Model(&dst).
+ Relation("OriginAccount").
+ Relation("TargetAccount").
+ Relation("Status").
+ WherePK()
+
+ if err := q.Scan(ctx); err != nil {
+ return nil, n.conn.ProcessError(err)
}
- return notif, nil
+
+ copy := dst
+ n.cache.Set(id, &copy)
+
+ return &dst, nil
}
func (n *notificationDB) GetNotifications(ctx context.Context, accountID string, limit int, maxID string, sinceID string) ([]*gtsmodel.Notification, db.Error) {
@@ -61,11 +63,11 @@ func (n *notificationDB) GetNotifications(ctx context.Context, accountID string,
}
// Make a guess for slice size
- notifications := make([]*gtsmodel.Notification, 0, limit)
+ notifIDs := make([]string, 0, limit)
q := n.conn.
NewSelect().
- Model(&notifications).
+ Table("notifications").
Column("id")
if maxID != "" {
@@ -84,56 +86,25 @@ func (n *notificationDB) GetNotifications(ctx context.Context, accountID string,
q = q.Limit(limit)
}
- err := q.Scan(ctx)
- if err != nil {
+ if err := q.Scan(ctx, &notifIDs); err != nil {
return nil, n.conn.ProcessError(err)
}
+ notifs := make([]*gtsmodel.Notification, 0, limit)
+
// now we have the IDs, select the notifs one by one
// reason for this is that for each notif, we can instead get it from our cache if it's cached
- for i, notif := range notifications {
- // Check cache for notification
- nn, cached := n.getNotificationCache(notif.ID)
- if cached {
- notifications[i] = nn
- continue
- }
-
- // Check DB for notification
- err := n.getNotificationDB(ctx, notif.ID, notif)
+ for _, id := range notifIDs {
+ // Attempt fetch from DB
+ notif, err := n.GetNotification(ctx, id)
if err != nil {
- return nil, err
+ logrus.Errorf("GetNotifications: error getting notification %q: %v", id, err)
+ continue
}
- }
-
- return notifications, nil
-}
-
-func (n *notificationDB) getNotificationCache(id string) (*gtsmodel.Notification, bool) {
- v, ok := n.cache.Get(id)
- if !ok {
- return nil, false
- }
- notif, ok := v.(*gtsmodel.Notification)
- if !ok {
- panic("notification cache entry was not a notification")
- }
-
- return notif, true
-}
-
-func (n *notificationDB) putNotificationCache(notif *gtsmodel.Notification) {
- n.cache.Set(notif.ID, notif)
-}
-
-func (n *notificationDB) getNotificationDB(ctx context.Context, id string, dst *gtsmodel.Notification) error {
- q := n.newNotificationQ(dst).WherePK()
-
- if err := q.Scan(ctx); err != nil {
- return n.conn.ProcessError(err)
+ // Append notification
+ notifs = append(notifs, notif)
}
- n.putNotificationCache(dst)
- return nil
+ return notifs, nil
}
diff --git a/internal/db/bundb/status.go b/internal/db/bundb/status.go
index 4e670f59b..74a24ebaa 100644
--- a/internal/db/bundb/status.go
+++ b/internal/db/bundb/status.go
@@ -21,6 +21,7 @@ package bundb
import (
"container/list"
"context"
+ "database/sql"
"time"
"github.com/sirupsen/logrus"
@@ -219,21 +220,32 @@ func (s *statusDB) GetStatusChildren(ctx context.Context, status *gtsmodel.Statu
}
func (s *statusDB) statusChildren(ctx context.Context, status *gtsmodel.Status, foundStatuses *list.List, onlyDirect bool, minID string) {
- immediateChildren := []*gtsmodel.Status{}
+ childIDs := []string{}
q := s.conn.
NewSelect().
- Model(&immediateChildren).
+ Table("statuses").
+ Column("id").
Where("in_reply_to_id = ?", status.ID)
if minID != "" {
- q = q.Where("status.id > ?", minID)
+ q = q.Where("id > ?", minID)
}
- if err := q.Scan(ctx); err != nil {
+ if err := q.Scan(ctx, &childIDs); err != nil {
+ if err != sql.ErrNoRows {
+ logrus.Errorf("statusChildren: error getting children for %q: %v", status.ID, err)
+ }
return
}
- for _, child := range immediateChildren {
+ for _, id := range childIDs {
+ // Fetch child with ID from database
+ child, err := s.GetStatusByID(ctx, id)
+ if err != nil {
+ logrus.Errorf("statusChildren: error getting child status %q: %v", id, err)
+ continue
+ }
+
insertLoop:
for e := foundStatuses.Front(); e != nil; e = e.Next() {
entry, ok := e.Value.(*gtsmodel.Status)
diff --git a/internal/db/bundb/timeline.go b/internal/db/bundb/timeline.go
index ca5922532..3c0d6d7e4 100644
--- a/internal/db/bundb/timeline.go
+++ b/internal/db/bundb/timeline.go
@@ -20,55 +20,52 @@ package bundb
import (
"context"
- "database/sql"
- "sort"
+ "github.com/sirupsen/logrus"
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/uptrace/bun"
+ "golang.org/x/exp/slices"
)
type timelineDB struct {
- conn *DBConn
+ conn *DBConn
+ status *statusDB
}
func (t *timelineDB) GetHomeTimeline(ctx context.Context, accountID string, maxID string, sinceID string, minID string, limit int, local bool) ([]*gtsmodel.Status, db.Error) {
- // Ensure reasonable
- if limit < 0 {
- limit = 0
- }
-
// Make educated guess for slice size
- statuses := make([]*gtsmodel.Status, 0, limit)
+ statusIDs := make([]string, 0, limit)
q := t.conn.
NewSelect().
- Model(&statuses)
+ Table("statuses").
- q = q.ColumnExpr("status.*").
+ // Select only IDs from table
+ Column("statuses.id").
// Find out who accountID follows.
- Join("LEFT JOIN follows AS f ON f.target_account_id = status.account_id").
+ Join("LEFT JOIN follows ON follows.target_account_id = statuses.account_id AND follows.account_id = ?", accountID).
// Sort by highest ID (newest) to lowest ID (oldest)
- Order("status.id DESC")
+ Order("statuses.id DESC")
if maxID != "" {
// return only statuses LOWER (ie., older) than maxID
- q = q.Where("status.id < ?", maxID)
+ q = q.Where("statuses.id < ?", maxID)
}
if sinceID != "" {
// return only statuses HIGHER (ie., newer) than sinceID
- q = q.Where("status.id > ?", sinceID)
+ q = q.Where("statuses.id > ?", sinceID)
}
if minID != "" {
// return only statuses HIGHER (ie., newer) than minID
- q = q.Where("status.id > ?", minID)
+ q = q.Where("statuses.id > ?", minID)
}
if local {
// return only statuses posted by local account havers
- q = q.Where("status.local = ?", local)
+ q = q.Where("statuses.local = ?", local)
}
if limit > 0 {
@@ -83,15 +80,30 @@ func (t *timelineDB) GetHomeTimeline(ctx context.Context, accountID string, maxI
// See: https://bun.uptrace.dev/guide/queries.html#select
whereGroup := func(*bun.SelectQuery) *bun.SelectQuery {
return q.
- WhereOr("f.account_id = ?", accountID).
- WhereOr("status.account_id = ?", accountID)
+ WhereOr("follows.account_id = ?", accountID).
+ WhereOr("statuses.account_id = ?", accountID)
}
q = q.WhereGroup(" AND ", whereGroup)
- if err := q.Scan(ctx); err != nil {
+ if err := q.Scan(ctx, &statusIDs); err != nil {
return nil, t.conn.ProcessError(err)
}
+
+ statuses := make([]*gtsmodel.Status, 0, len(statusIDs))
+
+ for _, id := range statusIDs {
+ // Fetch status from db for ID
+ status, err := t.status.GetStatusByID(ctx, id)
+ if err != nil {
+ logrus.Errorf("GetHomeTimeline: error fetching status %q: %v", id, err)
+ continue
+ }
+
+ // Append status to slice
+ statuses = append(statuses, status)
+ }
+
return statuses, nil
}
@@ -102,40 +114,56 @@ func (t *timelineDB) GetPublicTimeline(ctx context.Context, accountID string, ma
}
// Make educated guess for slice size
- statuses := make([]*gtsmodel.Status, 0, limit)
+ statusIDs := make([]string, 0, limit)
q := t.conn.
NewSelect().
- Model(&statuses).
- Where("visibility = ?", gtsmodel.VisibilityPublic).
- WhereGroup(" AND ", whereEmptyOrNull("in_reply_to_id")).
- WhereGroup(" AND ", whereEmptyOrNull("in_reply_to_uri")).
- WhereGroup(" AND ", whereEmptyOrNull("boost_of_id")).
- Order("status.id DESC")
+ Table("statuses").
+ Column("statuses.id").
+ Where("statuses.visibility = ?", gtsmodel.VisibilityPublic).
+ WhereGroup(" AND ", whereEmptyOrNull("statuses.in_reply_to_id")).
+ WhereGroup(" AND ", whereEmptyOrNull("statuses.in_reply_to_uri")).
+ WhereGroup(" AND ", whereEmptyOrNull("statuses.boost_of_id")).
+ Order("statuses.id DESC")
if maxID != "" {
- q = q.Where("status.id < ?", maxID)
+ q = q.Where("statuses.id < ?", maxID)
}
if sinceID != "" {
- q = q.Where("status.id > ?", sinceID)
+ q = q.Where("statuses.id > ?", sinceID)
}
if minID != "" {
- q = q.Where("status.id > ?", minID)
+ q = q.Where("statuses.id > ?", minID)
}
if local {
- q = q.Where("status.local = ?", local)
+ q = q.Where("statuses.local = ?", local)
}
if limit > 0 {
q = q.Limit(limit)
}
- if err := q.Scan(ctx); err != nil {
+ if err := q.Scan(ctx, &statusIDs); err != nil {
return nil, t.conn.ProcessError(err)
}
+
+ statuses := make([]*gtsmodel.Status, 0, len(statusIDs))
+
+ for _, id := range statusIDs {
+ // Fetch status from db for ID
+ status, err := t.status.GetStatusByID(ctx, id)
+ if err != nil {
+ logrus.Errorf("GetPublicTimeline: error fetching status %q: %v", id, err)
+ continue
+ }
+
+ // Append status to slice
+ statuses = append(statuses, status)
+ }
+
return statuses, nil
}
@@ -170,46 +198,32 @@ func (t *timelineDB) GetFavedTimeline(ctx context.Context, accountID string, max
err := fq.Scan(ctx)
if err != nil {
- if err == sql.ErrNoRows {
- return nil, "", "", db.ErrNoEntries
- }
- return nil, "", "", err
+ return nil, "", "", t.conn.ProcessError(err)
}
if len(faves) == 0 {
return nil, "", "", db.ErrNoEntries
}
- // map[statusID]faveID -- we need this to sort statuses by fave ID rather than status ID
- statusesFavesMap := make(map[string]string, len(faves))
- statusIDs := make([]string, 0, len(faves))
- for _, f := range faves {
- statusesFavesMap[f.StatusID] = f.ID
- statusIDs = append(statusIDs, f.StatusID)
- }
+ // Sort by favourite ID rather than status ID
+ slices.SortFunc(faves, func(a, b *gtsmodel.StatusFave) bool {
+ return a.ID < b.ID
+ })
- statuses := make([]*gtsmodel.Status, 0, len(statusIDs))
+ statuses := make([]*gtsmodel.Status, 0, len(faves))
- err = t.conn.
- NewSelect().
- Model(&statuses).
- Where("id IN (?)", bun.In(statusIDs)).
- Scan(ctx)
- if err != nil {
- return nil, "", "", t.conn.ProcessError(err)
- }
+ for _, fave := range faves {
+ // Fetch status from db for corresponding favourite
+ status, err := t.status.GetStatusByID(ctx, fave.StatusID)
+ if err != nil {
+ logrus.Errorf("GetFavedTimeline: error fetching status for fave %q: %v", fave.ID, err)
+ continue
+ }
- if len(statuses) == 0 {
- return nil, "", "", db.ErrNoEntries
+ // Append status to slice
+ statuses = append(statuses, status)
}
- // arrange statuses by fave ID
- sort.Slice(statuses, func(i int, j int) bool {
- statusI := statuses[i]
- statusJ := statuses[j]
- return statusesFavesMap[statusI.ID] < statusesFavesMap[statusJ.ID]
- })
-
nextMaxID := faves[len(faves)-1].ID
prevMinID := faves[0].ID
return statuses, nextMaxID, prevMinID, nil