summaryrefslogtreecommitdiff
path: root/internal/db
diff options
context:
space:
mode:
Diffstat (limited to 'internal/db')
-rw-r--r--internal/db/bundb/account.go31
-rw-r--r--internal/db/bundb/bundb_test.go2
-rw-r--r--internal/db/bundb/emoji.go51
-rw-r--r--internal/db/bundb/errors.go3
-rw-r--r--internal/db/bundb/media.go29
-rw-r--r--internal/db/bundb/mention.go29
-rw-r--r--internal/db/bundb/migrations/20230511181430_add_status_fetched_at.go47
-rw-r--r--internal/db/bundb/notification.go107
-rw-r--r--internal/db/bundb/relationship_block.go90
-rw-r--r--internal/db/bundb/relationship_follow.go97
-rw-r--r--internal/db/bundb/relationship_follow_req.go136
-rw-r--r--internal/db/bundb/report.go28
-rw-r--r--internal/db/bundb/status.go170
-rw-r--r--internal/db/bundb/statusfave.go86
-rw-r--r--internal/db/bundb/tombstone.go16
-rw-r--r--internal/db/bundb/user.go50
16 files changed, 608 insertions, 364 deletions
diff --git a/internal/db/bundb/account.go b/internal/db/bundb/account.go
index 56d46a232..f7e243f47 100644
--- a/internal/db/bundb/account.go
+++ b/internal/db/bundb/account.go
@@ -302,7 +302,7 @@ func (a *accountDB) UpdateAccount(ctx context.Context, account *gtsmodel.Account
columns = append(columns, "updated_at")
}
- err := a.state.Caches.GTS.Account().Store(account, func() error {
+ return a.state.Caches.GTS.Account().Store(account, func() error {
// It is safe to run this database transaction within cache.Store
// as the cache does not attempt a mutex lock until AFTER hook.
//
@@ -338,15 +338,23 @@ func (a *accountDB) UpdateAccount(ctx context.Context, account *gtsmodel.Account
return err
})
})
- if err != nil {
- return err
- }
-
- return nil
}
func (a *accountDB) DeleteAccount(ctx context.Context, id string) db.Error {
- if err := a.conn.RunInTx(ctx, func(tx bun.Tx) error {
+ defer a.state.Caches.GTS.Account().Invalidate("ID", id)
+
+ // Load account into cache before attempting a delete,
+ // as we need it cached in order to trigger the invalidate
+ // callback. This in turn invalidates others.
+ _, err := a.GetAccountByID(gtscontext.SetBarebones(ctx), id)
+ if err != nil && !errors.Is(err, db.ErrNoEntries) {
+ // NOTE: even if db.ErrNoEntries is returned, we
+ // still run the below transaction to ensure related
+ // objects are appropriately deleted.
+ return err
+ }
+
+ return a.conn.RunInTx(ctx, func(tx bun.Tx) error {
// clear out any emoji links
if _, err := tx.
NewDelete().
@@ -363,14 +371,7 @@ func (a *accountDB) DeleteAccount(ctx context.Context, id string) db.Error {
Where("? = ?", bun.Ident("account.id"), id).
Exec(ctx)
return err
- }); err != nil {
- return err
- }
-
- // Invalidate account from database lookups.
- a.state.Caches.GTS.Account().Invalidate("ID", id)
-
- return nil
+ })
}
func (a *accountDB) GetAccountLastPosted(ctx context.Context, accountID string, webOnly bool) (time.Time, db.Error) {
diff --git a/internal/db/bundb/bundb_test.go b/internal/db/bundb/bundb_test.go
index e6d482ac1..2566be2ba 100644
--- a/internal/db/bundb/bundb_test.go
+++ b/internal/db/bundb/bundb_test.go
@@ -66,9 +66,9 @@ func (suite *BunDBStandardTestSuite) SetupSuite() {
}
func (suite *BunDBStandardTestSuite) SetupTest() {
- suite.state.Caches.Init()
testrig.InitTestConfig()
testrig.InitTestLog()
+ suite.state.Caches.Init()
suite.db = testrig.NewTestDB(&suite.state)
testrig.StandardDBSetup(suite.db, suite.testAccounts)
}
diff --git a/internal/db/bundb/emoji.go b/internal/db/bundb/emoji.go
index 0c72be9d3..60b8fc12b 100644
--- a/internal/db/bundb/emoji.go
+++ b/internal/db/bundb/emoji.go
@@ -19,10 +19,12 @@ package bundb
import (
"context"
+ "errors"
"strings"
"time"
"github.com/superseriousbusiness/gotosocial/internal/db"
+ "github.com/superseriousbusiness/gotosocial/internal/gtscontext"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/log"
"github.com/superseriousbusiness/gotosocial/internal/state"
@@ -56,24 +58,46 @@ func (e *emojiDB) PutEmoji(ctx context.Context, emoji *gtsmodel.Emoji) db.Error
}
func (e *emojiDB) UpdateEmoji(ctx context.Context, emoji *gtsmodel.Emoji, columns ...string) (*gtsmodel.Emoji, db.Error) {
- // Update the emoji's last-updated
emoji.UpdatedAt = time.Now()
+ if len(columns) > 0 {
+ // If we're updating by column, ensure "updated_at" is included.
+ columns = append(columns, "updated_at")
+ }
- if _, err := e.conn.
- NewUpdate().
- Model(emoji).
- Where("? = ?", bun.Ident("emoji.id"), emoji.ID).
- Column(columns...).
- Exec(ctx); err != nil {
- return nil, e.conn.ProcessError(err)
+ err := e.state.Caches.GTS.Emoji().Store(emoji, func() error {
+ _, err := e.conn.
+ NewUpdate().
+ Model(emoji).
+ Where("? = ?", bun.Ident("emoji.id"), emoji.ID).
+ Column(columns...).
+ Exec(ctx)
+ return e.conn.ProcessError(err)
+ })
+ if err != nil {
+ return nil, err
}
- e.state.Caches.GTS.Emoji().Invalidate("ID", emoji.ID)
return emoji, nil
}
func (e *emojiDB) DeleteEmojiByID(ctx context.Context, id string) db.Error {
- if err := e.conn.RunInTx(ctx, func(tx bun.Tx) error {
+ defer e.state.Caches.GTS.Emoji().Invalidate("ID", id)
+
+ // Load emoji into cache before attempting a delete,
+ // as we need it cached in order to trigger the invalidate
+ // callback. This in turn invalidates others.
+ _, err := e.GetEmojiByID(
+ gtscontext.SetBarebones(ctx),
+ id,
+ )
+ if err != nil && !errors.Is(err, db.ErrNoEntries) {
+ // NOTE: even if db.ErrNoEntries is returned, we
+ // still run the below transaction to ensure related
+ // objects are appropriately deleted.
+ return err
+ }
+
+ return e.conn.RunInTx(ctx, func(tx bun.Tx) error {
// delete links between this emoji and any statuses that use it
if _, err := tx.
NewDelete().
@@ -101,12 +125,7 @@ func (e *emojiDB) DeleteEmojiByID(ctx context.Context, id string) db.Error {
}
return nil
- }); err != nil {
- return err
- }
-
- e.state.Caches.GTS.Emoji().Invalidate("ID", id)
- return nil
+ })
}
func (e *emojiDB) GetEmojis(ctx context.Context, domain string, includeDisabled bool, includeEnabled bool, shortcode string, maxShortcodeDomain string, minShortcodeDomain string, limit int) ([]*gtsmodel.Emoji, db.Error) {
diff --git a/internal/db/bundb/errors.go b/internal/db/bundb/errors.go
index 8de42b6aa..6236b82d8 100644
--- a/internal/db/bundb/errors.go
+++ b/internal/db/bundb/errors.go
@@ -52,7 +52,8 @@ func processSQLiteError(err error) db.Error {
// Handle supplied error code:
switch sqliteErr.Code() {
- case sqlite3.SQLITE_CONSTRAINT_UNIQUE, sqlite3.SQLITE_CONSTRAINT_PRIMARYKEY:
+ case sqlite3.SQLITE_CONSTRAINT_UNIQUE,
+ sqlite3.SQLITE_CONSTRAINT_PRIMARYKEY:
return db.ErrAlreadyExists
default:
return err
diff --git a/internal/db/bundb/media.go b/internal/db/bundb/media.go
index d17d64b35..b64447beb 100644
--- a/internal/db/bundb/media.go
+++ b/internal/db/bundb/media.go
@@ -19,9 +19,11 @@ package bundb
import (
"context"
+ "errors"
"time"
"github.com/superseriousbusiness/gotosocial/internal/db"
+ "github.com/superseriousbusiness/gotosocial/internal/gtscontext"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/log"
"github.com/superseriousbusiness/gotosocial/internal/state"
@@ -103,17 +105,26 @@ func (m *mediaDB) UpdateAttachment(ctx context.Context, media *gtsmodel.MediaAtt
}
func (m *mediaDB) DeleteAttachment(ctx context.Context, id string) error {
- // Attempt to delete from database.
- if _, err := m.conn.NewDelete().
- TableExpr("? AS ?", bun.Ident("media_attachments"), bun.Ident("media_attachment")).
- Where("? = ?", bun.Ident("media_attachment.id"), id).
- Exec(ctx); err != nil {
- return m.conn.ProcessError(err)
+ defer m.state.Caches.GTS.Media().Invalidate("ID", id)
+
+ // Load media into cache before attempting a delete,
+ // as we need it cached in order to trigger the invalidate
+ // callback. This in turn invalidates others.
+ _, err := m.GetAttachmentByID(gtscontext.SetBarebones(ctx), id)
+ if err != nil {
+ if errors.Is(err, db.ErrNoEntries) {
+ // not an issue.
+ err = nil
+ }
+ return err
}
- // Invalidate this media item from the cache.
- m.state.Caches.GTS.Media().Invalidate("ID", id)
- return nil
+ // Finally delete media from DB.
+ _, err = m.conn.NewDelete().
+ TableExpr("? AS ?", bun.Ident("media_attachments"), bun.Ident("media_attachment")).
+ Where("? = ?", bun.Ident("media_attachment.id"), id).
+ Exec(ctx)
+ return m.conn.ProcessError(err)
}
func (m *mediaDB) GetRemoteOlderThan(ctx context.Context, olderThan time.Time, limit int) ([]*gtsmodel.MediaAttachment, db.Error) {
diff --git a/internal/db/bundb/mention.go b/internal/db/bundb/mention.go
index e64d6dac4..9a41eb3b8 100644
--- a/internal/db/bundb/mention.go
+++ b/internal/db/bundb/mention.go
@@ -19,6 +19,7 @@ package bundb
import (
"context"
+ "errors"
"fmt"
"github.com/superseriousbusiness/gotosocial/internal/db"
@@ -109,16 +110,24 @@ func (m *mentionDB) PutMention(ctx context.Context, mention *gtsmodel.Mention) e
}
func (m *mentionDB) DeleteMentionByID(ctx context.Context, id string) error {
- if _, err := m.conn.
- NewDelete().
- Table("mentions").
- Where("? = ?", bun.Ident("id"), id).
- Exec(ctx); err != nil {
- return m.conn.ProcessError(err)
- }
+ defer m.state.Caches.GTS.Mention().Invalidate("ID", id)
- // Invalidate mention from the lookup cache.
- m.state.Caches.GTS.Mention().Invalidate("ID", id)
+ // Load mention into cache before attempting a delete,
+ // as we need it cached in order to trigger the invalidate
+ // callback. This in turn invalidates others.
+ _, err := m.GetMention(gtscontext.SetBarebones(ctx), id)
+ if err != nil {
+ if errors.Is(err, db.ErrNoEntries) {
+ // not an issue.
+ err = nil
+ }
+ return err
+ }
- return nil
+ // Finally delete mention from DB.
+ _, err = m.conn.NewDelete().
+ Table("mentions").
+ Where("? = ?", bun.Ident("id"), id).
+ Exec(ctx)
+ return m.conn.ProcessError(err)
}
diff --git a/internal/db/bundb/migrations/20230511181430_add_status_fetched_at.go b/internal/db/bundb/migrations/20230511181430_add_status_fetched_at.go
new file mode 100644
index 000000000..cc41b518e
--- /dev/null
+++ b/internal/db/bundb/migrations/20230511181430_add_status_fetched_at.go
@@ -0,0 +1,47 @@
+// GoToSocial
+// Copyright (C) GoToSocial Authors admin@gotosocial.org
+// SPDX-License-Identifier: AGPL-3.0-or-later
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+package migrations
+
+import (
+ "context"
+ "strings"
+
+ "github.com/uptrace/bun"
+)
+
+func init() {
+ up := func(ctx context.Context, db *bun.DB) error {
+ return db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error {
+ _, err := tx.ExecContext(ctx, "ALTER TABLE ? ADD COLUMN ? TIMESTAMPTZ", bun.Ident("statuses"), bun.Ident("fetched_at"))
+ if err != nil && !(strings.Contains(err.Error(), "already exists") || strings.Contains(err.Error(), "duplicate column name") || strings.Contains(err.Error(), "SQLSTATE 42701")) {
+ return err
+ }
+ return nil
+ })
+ }
+
+ down := func(ctx context.Context, db *bun.DB) error {
+ return db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error {
+ return nil
+ })
+ }
+
+ if err := Migrations.Register(up, down); err != nil {
+ panic(err)
+ }
+}
diff --git a/internal/db/bundb/notification.go b/internal/db/bundb/notification.go
index f2ff60b9a..277a935fd 100644
--- a/internal/db/bundb/notification.go
+++ b/internal/db/bundb/notification.go
@@ -22,6 +22,7 @@ import (
"errors"
"github.com/superseriousbusiness/gotosocial/internal/db"
+ "github.com/superseriousbusiness/gotosocial/internal/gtscontext"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/id"
"github.com/superseriousbusiness/gotosocial/internal/log"
@@ -179,16 +180,26 @@ func (n *notificationDB) PutNotification(ctx context.Context, notif *gtsmodel.No
}
func (n *notificationDB) DeleteNotificationByID(ctx context.Context, id string) db.Error {
- if _, err := n.conn.
- NewDelete().
- TableExpr("? AS ?", bun.Ident("notifications"), bun.Ident("notification")).
- Where("? = ?", bun.Ident("notification.id"), id).
- Exec(ctx); err != nil {
- return n.conn.ProcessError(err)
+ defer n.state.Caches.GTS.Notification().Invalidate("ID", id)
+
+ // Load notif into cache before attempting a delete,
+ // as we need it cached in order to trigger the invalidate
+ // callback. This in turn invalidates others.
+ _, err := n.GetNotificationByID(gtscontext.SetBarebones(ctx), id)
+ if err != nil {
+ if errors.Is(err, db.ErrNoEntries) {
+ // not an issue.
+ err = nil
+ }
+ return err
}
- n.state.Caches.GTS.Notification().Invalidate("ID", id)
- return nil
+ // Finally delete notif from DB.
+ _, err = n.conn.NewDelete().
+ TableExpr("? AS ?", bun.Ident("notifications"), bun.Ident("notification")).
+ Where("? = ?", bun.Ident("notification.id"), id).
+ Exec(ctx)
+ return n.conn.ProcessError(err)
}
func (n *notificationDB) DeleteNotifications(ctx context.Context, types []string, targetAccountID string, originAccountID string) db.Error {
@@ -196,56 +207,88 @@ func (n *notificationDB) DeleteNotifications(ctx context.Context, types []string
return errors.New("DeleteNotifications: one of targetAccountID or originAccountID must be set")
}
- // Capture notification IDs in a RETURNING statement.
- var ids []string
+ var notifIDs []string
q := n.conn.
- NewDelete().
- TableExpr("? AS ?", bun.Ident("notifications"), bun.Ident("notification")).
- Returning("?", bun.Ident("id"))
+ NewSelect().
+ Column("id").
+ Table("notifications")
if len(types) > 0 {
- q = q.Where("? IN (?)", bun.Ident("notification.notification_type"), bun.In(types))
+ q = q.Where("? IN (?)", bun.Ident("notification_type"), bun.In(types))
}
if targetAccountID != "" {
- q = q.Where("? = ?", bun.Ident("notification.target_account_id"), targetAccountID)
+ q = q.Where("? = ?", bun.Ident("target_account_id"), targetAccountID)
}
if originAccountID != "" {
- q = q.Where("? = ?", bun.Ident("notification.origin_account_id"), originAccountID)
+ q = q.Where("? = ?", bun.Ident("origin_account_id"), originAccountID)
}
- if _, err := q.Exec(ctx, &ids); err != nil {
+ if _, err := q.Exec(ctx, &notifIDs); err != nil {
return n.conn.ProcessError(err)
}
- // Invalidate each returned ID.
- for _, id := range ids {
- n.state.Caches.GTS.Notification().Invalidate("ID", id)
+ defer func() {
+ // Invalidate all IDs on return.
+ for _, id := range notifIDs {
+ n.state.Caches.GTS.Notification().Invalidate("ID", id)
+ }
+ }()
+
+ // Load all notif into cache, this *really* isn't great
+ // but it is the only way we can ensure we invalidate all
+ // related caches correctly (e.g. visibility).
+ for _, id := range notifIDs {
+ _, err := n.GetNotificationByID(ctx, id)
+ if err != nil && !errors.Is(err, db.ErrNoEntries) {
+ return err
+ }
}
- return nil
+ // Finally delete all from DB.
+ _, err := n.conn.NewDelete().
+ Table("notifications").
+ Where("? IN (?)", bun.Ident("id"), bun.In(notifIDs)).
+ Exec(ctx)
+ return n.conn.ProcessError(err)
}
func (n *notificationDB) DeleteNotificationsForStatus(ctx context.Context, statusID string) db.Error {
- // Capture notification IDs in a RETURNING statement.
- var ids []string
+ var notifIDs []string
q := n.conn.
- NewDelete().
- TableExpr("? AS ?", bun.Ident("notifications"), bun.Ident("notification")).
- Where("? = ?", bun.Ident("notification.status_id"), statusID).
- Returning("?", bun.Ident("id"))
+ NewSelect().
+ Column("id").
+ Table("notifications").
+ Where("? = ?", bun.Ident("status_id"), statusID)
- if _, err := q.Exec(ctx, &ids); err != nil {
+ if _, err := q.Exec(ctx, &notifIDs); err != nil {
return n.conn.ProcessError(err)
}
- // Invalidate each returned ID.
- for _, id := range ids {
- n.state.Caches.GTS.Notification().Invalidate("ID", id)
+ defer func() {
+ // Invalidate all IDs on return.
+ for _, id := range notifIDs {
+ n.state.Caches.GTS.Notification().Invalidate("ID", id)
+ }
+ }()
+
+ // Load all notif into cache, this *really* isn't great
+ // but it is the only way we can ensure we invalidate all
+ // related caches correctly (e.g. visibility).
+ for _, id := range notifIDs {
+ _, err := n.GetNotificationByID(ctx, id)
+ if err != nil && !errors.Is(err, db.ErrNoEntries) {
+ return err
+ }
}
- return nil
+ // Finally delete all from DB.
+ _, err := n.conn.NewDelete().
+ Table("notifications").
+ Where("? IN (?)", bun.Ident("id"), bun.In(notifIDs)).
+ Exec(ctx)
+ return n.conn.ProcessError(err)
}
diff --git a/internal/db/bundb/relationship_block.go b/internal/db/bundb/relationship_block.go
index 9232ea984..fa68a2e97 100644
--- a/internal/db/bundb/relationship_block.go
+++ b/internal/db/bundb/relationship_block.go
@@ -25,7 +25,6 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtscontext"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
- "github.com/superseriousbusiness/gotosocial/internal/log"
"github.com/uptrace/bun"
)
@@ -142,62 +141,65 @@ func (r *relationshipDB) getBlock(ctx context.Context, lookup string, dbQuery fu
}
func (r *relationshipDB) PutBlock(ctx context.Context, block *gtsmodel.Block) error {
- err := r.state.Caches.GTS.Block().Store(block, func() error {
+ return r.state.Caches.GTS.Block().Store(block, func() error {
_, err := r.conn.NewInsert().Model(block).Exec(ctx)
return r.conn.ProcessError(err)
})
- if err != nil {
- return err
- }
-
- // Invalidate block origin account ID cached visibility.
- r.state.Caches.Visibility.Invalidate("ItemID", block.AccountID)
- r.state.Caches.Visibility.Invalidate("RequesterID", block.AccountID)
-
- // Invalidate block target account ID cached visibility.
- r.state.Caches.Visibility.Invalidate("ItemID", block.TargetAccountID)
- r.state.Caches.Visibility.Invalidate("RequesterID", block.TargetAccountID)
-
- return nil
}
func (r *relationshipDB) DeleteBlockByID(ctx context.Context, id string) error {
- block, err := r.GetBlockByID(gtscontext.SetBarebones(ctx), id)
+ defer r.state.Caches.GTS.Block().Invalidate("ID", id)
+
+ // Load block into cache before attempting a delete,
+ // as we need it cached in order to trigger the invalidate
+ // callback. This in turn invalidates others.
+ _, err := r.GetBlockByID(gtscontext.SetBarebones(ctx), id)
if err != nil {
+ if errors.Is(err, db.ErrNoEntries) {
+ // not an issue.
+ err = nil
+ }
return err
}
- return r.deleteBlock(ctx, block)
+
+ // Finally delete block from DB.
+ _, err = r.conn.NewDelete().
+ Table("blocks").
+ Where("? = ?", bun.Ident("id"), id).
+ Exec(ctx)
+ return r.conn.ProcessError(err)
}
func (r *relationshipDB) DeleteBlockByURI(ctx context.Context, uri string) error {
- block, err := r.GetBlockByURI(gtscontext.SetBarebones(ctx), uri)
+ defer r.state.Caches.GTS.Block().Invalidate("URI", uri)
+
+ // Load block into cache before attempting a delete,
+ // as we need it cached in order to trigger the invalidate
+ // callback. This in turn invalidates others.
+ _, err := r.GetBlockByURI(gtscontext.SetBarebones(ctx), uri)
if err != nil {
+ if errors.Is(err, db.ErrNoEntries) {
+ // not an issue.
+ err = nil
+ }
return err
}
- return r.deleteBlock(ctx, block)
-}
-func (r *relationshipDB) deleteBlock(ctx context.Context, block *gtsmodel.Block) error {
- if _, err := r.conn.
- NewDelete().
+ // Finally delete block from DB.
+ _, err = r.conn.NewDelete().
Table("blocks").
- Where("? = ?", bun.Ident("id"), block.ID).
- Exec(ctx); err != nil {
- return r.conn.ProcessError(err)
- }
-
- // Invalidate block from cache lookups.
- r.state.Caches.GTS.Block().Invalidate("ID", block.ID)
-
- return nil
+ Where("? = ?", bun.Ident("uri"), uri).
+ Exec(ctx)
+ return r.conn.ProcessError(err)
}
func (r *relationshipDB) DeleteAccountBlocks(ctx context.Context, accountID string) error {
var blockIDs []string
+ // Get full list of IDs.
if err := r.conn.NewSelect().
+ Column("id").
Table("blocks").
- ColumnExpr("?", bun.Ident("id")).
WhereOr("? = ? OR ? = ?",
bun.Ident("account_id"),
accountID,
@@ -208,11 +210,27 @@ func (r *relationshipDB) DeleteAccountBlocks(ctx context.Context, accountID stri
return r.conn.ProcessError(err)
}
+ defer func() {
+ // Invalidate all IDs on return.
+ for _, id := range blockIDs {
+ r.state.Caches.GTS.Block().Invalidate("ID", id)
+ }
+ }()
+
+ // Load all blocks into cache, this *really* isn't great
+ // but it is the only way we can ensure we invalidate all
+ // related caches correctly (e.g. visibility).
for _, id := range blockIDs {
- if err := r.DeleteBlockByID(ctx, id); err != nil {
- log.Errorf(ctx, "error deleting block %q: %v", id, err)
+ _, err := r.GetBlockByID(ctx, id)
+ if err != nil && !errors.Is(err, db.ErrNoEntries) {
+ return err
}
}
- return nil
+ // Finally delete all from DB.
+ _, err := r.conn.NewDelete().
+ Table("blocks").
+ Where("? IN (?)", bun.Ident("id"), bun.In(blockIDs)).
+ Exec(ctx)
+ return r.conn.ProcessError(err)
}
diff --git a/internal/db/bundb/relationship_follow.go b/internal/db/bundb/relationship_follow.go
index 1b1de77b1..fe1f26bf1 100644
--- a/internal/db/bundb/relationship_follow.go
+++ b/internal/db/bundb/relationship_follow.go
@@ -171,23 +171,10 @@ func (r *relationshipDB) getFollow(ctx context.Context, lookup string, dbQuery f
}
func (r *relationshipDB) PutFollow(ctx context.Context, follow *gtsmodel.Follow) error {
- err := r.state.Caches.GTS.Follow().Store(follow, func() error {
+ return r.state.Caches.GTS.Follow().Store(follow, func() error {
_, err := r.conn.NewInsert().Model(follow).Exec(ctx)
return r.conn.ProcessError(err)
})
- if err != nil {
- return err
- }
-
- // Invalidate follow origin account ID cached visibility.
- r.state.Caches.Visibility.Invalidate("ItemID", follow.AccountID)
- r.state.Caches.Visibility.Invalidate("RequesterID", follow.AccountID)
-
- // Invalidate follow target account ID cached visibility.
- r.state.Caches.Visibility.Invalidate("ItemID", follow.TargetAccountID)
- r.state.Caches.Visibility.Invalidate("RequesterID", follow.TargetAccountID)
-
- return nil
}
func (r *relationshipDB) UpdateFollow(ctx context.Context, follow *gtsmodel.Follow, columns ...string) error {
@@ -211,38 +198,58 @@ func (r *relationshipDB) UpdateFollow(ctx context.Context, follow *gtsmodel.Foll
}
func (r *relationshipDB) DeleteFollowByID(ctx context.Context, id string) error {
- if _, err := r.conn.NewDelete().
- Table("follows").
- Where("? = ?", bun.Ident("id"), id).
- Exec(ctx); err != nil {
- return r.conn.ProcessError(err)
- }
+ defer r.state.Caches.GTS.Follow().Invalidate("ID", id)
- // Invalidate follow from cache lookups.
- r.state.Caches.GTS.Follow().Invalidate("ID", id)
+ // Load follow into cache before attempting a delete,
+ // as we need it cached in order to trigger the invalidate
+ // callback. This in turn invalidates others.
+ _, err := r.GetFollowByID(gtscontext.SetBarebones(ctx), id)
+ if err != nil {
+ if errors.Is(err, db.ErrNoEntries) {
+ // not an issue.
+ err = nil
+ }
+ return err
+ }
- return nil
+ // Finally delete follow from DB.
+ _, err = r.conn.NewDelete().
+ Table("follows").
+ Where("? = ?", bun.Ident("id"), id).
+ Exec(ctx)
+ return r.conn.ProcessError(err)
}
func (r *relationshipDB) DeleteFollowByURI(ctx context.Context, uri string) error {
- if _, err := r.conn.NewDelete().
- Table("follows").
- Where("? = ?", bun.Ident("uri"), uri).
- Exec(ctx); err != nil {
- return r.conn.ProcessError(err)
- }
+ defer r.state.Caches.GTS.Follow().Invalidate("URI", uri)
- // Invalidate follow from cache lookups.
- r.state.Caches.GTS.Follow().Invalidate("URI", uri)
+ // Load follow into cache before attempting a delete,
+ // as we need it cached in order to trigger the invalidate
+ // callback. This in turn invalidates others.
+ _, err := r.GetFollowByURI(gtscontext.SetBarebones(ctx), uri)
+ if err != nil {
+ if errors.Is(err, db.ErrNoEntries) {
+ // not an issue.
+ err = nil
+ }
+ return err
+ }
- return nil
+ // Finally delete follow from DB.
+ _, err = r.conn.NewDelete().
+ Table("follows").
+ Where("? = ?", bun.Ident("uri"), uri).
+ Exec(ctx)
+ return r.conn.ProcessError(err)
}
func (r *relationshipDB) DeleteAccountFollows(ctx context.Context, accountID string) error {
var followIDs []string
+ // Get full list of IDs.
if _, err := r.conn.
- NewDelete().
+ NewSelect().
+ Column("id").
Table("follows").
WhereOr("? = ? OR ? = ?",
bun.Ident("account_id"),
@@ -250,15 +257,31 @@ func (r *relationshipDB) DeleteAccountFollows(ctx context.Context, accountID str
bun.Ident("target_account_id"),
accountID,
).
- Returning("?", bun.Ident("id")).
Exec(ctx, &followIDs); err != nil {
return r.conn.ProcessError(err)
}
- // Invalidate each returned ID.
+ defer func() {
+ // Invalidate all IDs on return.
+ for _, id := range followIDs {
+ r.state.Caches.GTS.Follow().Invalidate("ID", id)
+ }
+ }()
+
+ // Load all follows into cache, this *really* isn't great
+ // but it is the only way we can ensure we invalidate all
+ // related caches correctly (e.g. visibility).
for _, id := range followIDs {
- r.state.Caches.GTS.Follow().Invalidate("ID", id)
+ _, err := r.GetFollowByID(ctx, id)
+ if err != nil && !errors.Is(err, db.ErrNoEntries) {
+ return err
+ }
}
- return nil
+ // Finally delete all from DB.
+ _, err := r.conn.NewDelete().
+ Table("follows").
+ Where("? IN (?)", bun.Ident("id"), bun.In(followIDs)).
+ Exec(ctx)
+ return r.conn.ProcessError(err)
}
diff --git a/internal/db/bundb/relationship_follow_req.go b/internal/db/bundb/relationship_follow_req.go
index c0f4bce3b..c2ac3d2f1 100644
--- a/internal/db/bundb/relationship_follow_req.go
+++ b/internal/db/bundb/relationship_follow_req.go
@@ -149,23 +149,10 @@ func (r *relationshipDB) getFollowRequest(ctx context.Context, lookup string, db
}
func (r *relationshipDB) PutFollowRequest(ctx context.Context, follow *gtsmodel.FollowRequest) error {
- err := r.state.Caches.GTS.FollowRequest().Store(follow, func() error {
+ return r.state.Caches.GTS.FollowRequest().Store(follow, func() error {
_, err := r.conn.NewInsert().Model(follow).Exec(ctx)
return r.conn.ProcessError(err)
})
- if err != nil {
- return err
- }
-
- // Invalidate follow request origin account ID cached visibility.
- r.state.Caches.Visibility.Invalidate("ItemID", follow.AccountID)
- r.state.Caches.Visibility.Invalidate("RequesterID", follow.AccountID)
-
- // Invalidate follow request target account ID cached visibility.
- r.state.Caches.Visibility.Invalidate("ItemID", follow.TargetAccountID)
- r.state.Caches.Visibility.Invalidate("RequesterID", follow.TargetAccountID)
-
- return nil
}
func (r *relationshipDB) UpdateFollowRequest(ctx context.Context, followRequest *gtsmodel.FollowRequest, columns ...string) error {
@@ -221,6 +208,9 @@ func (r *relationshipDB) AcceptFollowRequest(ctx context.Context, sourceAccountI
return nil, err
}
+ // Invalidate follow request from cache lookups on return.
+ defer r.state.Caches.GTS.FollowRequest().Invalidate("ID", followReq.ID)
+
// Delete original follow request.
if _, err := r.conn.
NewDelete().
@@ -230,9 +220,6 @@ func (r *relationshipDB) AcceptFollowRequest(ctx context.Context, sourceAccountI
return nil, r.conn.ProcessError(err)
}
- // Invalidate follow request from cache lookups
- r.state.Caches.GTS.FollowRequest().Invalidate("ID", followReq.ID)
-
// Delete original follow request notification
if err := r.state.DB.DeleteNotifications(ctx, []string{
string(gtsmodel.NotificationFollowRequest),
@@ -244,15 +231,30 @@ func (r *relationshipDB) AcceptFollowRequest(ctx context.Context, sourceAccountI
}
func (r *relationshipDB) RejectFollowRequest(ctx context.Context, sourceAccountID string, targetAccountID string) db.Error {
- // Get original follow request.
- followReq, err := r.GetFollowRequest(ctx, sourceAccountID, targetAccountID)
+ defer r.state.Caches.GTS.FollowRequest().Invalidate("AccountID.TargetAccountID", sourceAccountID, targetAccountID)
+
+ // Load followreq into cache before attempting a delete,
+ // as we need it cached in order to trigger the invalidate
+ // callback. This in turn invalidates others.
+ _, err := r.GetFollowRequest(gtscontext.SetBarebones(ctx),
+ sourceAccountID,
+ targetAccountID,
+ )
if err != nil {
return err
}
- // Delete original follow request.
- if err := r.DeleteFollowRequestByID(ctx, followReq.ID); err != nil {
- return err
+ // Attempt to delete follow request.
+ if _, err = r.conn.NewDelete().
+ Table("follow_requests").
+ Where("? = ? AND ? = ?",
+ bun.Ident("account_id"),
+ sourceAccountID,
+ bun.Ident("target_account_id"),
+ targetAccountID,
+ ).
+ Exec(ctx); err != nil {
+ return r.conn.ProcessError(err)
}
// Delete original follow request notification
@@ -262,54 +264,90 @@ func (r *relationshipDB) RejectFollowRequest(ctx context.Context, sourceAccountI
}
func (r *relationshipDB) DeleteFollowRequestByID(ctx context.Context, id string) error {
- if _, err := r.conn.NewDelete().
- Table("follow_requests").
- Where("? = ?", bun.Ident("id"), id).
- Exec(ctx); err != nil {
- return r.conn.ProcessError(err)
- }
+ defer r.state.Caches.GTS.FollowRequest().Invalidate("ID", id)
- // Invalidate follow request from cache lookups.
- r.state.Caches.GTS.FollowRequest().Invalidate("ID", id)
+ // Load followreq into cache before attempting a delete,
+ // as we need it cached in order to trigger the invalidate
+ // callback. This in turn invalidates others.
+ _, err := r.GetFollowRequestByID(gtscontext.SetBarebones(ctx), id)
+ if err != nil {
+ if errors.Is(err, db.ErrNoEntries) {
+ // not an issue.
+ err = nil
+ }
+ return err
+ }
- return nil
+ // Finally delete followreq from DB.
+ _, err = r.conn.NewDelete().
+ Table("follow_requests").
+ Where("? = ?", bun.Ident("id"), id).
+ Exec(ctx)
+ return r.conn.ProcessError(err)
}
func (r *relationshipDB) DeleteFollowRequestByURI(ctx context.Context, uri string) error {
- if _, err := r.conn.NewDelete().
- Table("follow_requests").
- Where("? = ?", bun.Ident("uri"), uri).
- Exec(ctx); err != nil {
- return r.conn.ProcessError(err)
- }
+ defer r.state.Caches.GTS.FollowRequest().Invalidate("URI", uri)
- // Invalidate follow request from cache lookups.
- r.state.Caches.GTS.FollowRequest().Invalidate("URI", uri)
+ // Load followreq into cache before attempting a delete,
+ // as we need it cached in order to trigger the invalidate
+ // callback. This in turn invalidates others.
+ _, err := r.GetFollowRequestByURI(gtscontext.SetBarebones(ctx), uri)
+ if err != nil {
+ if errors.Is(err, db.ErrNoEntries) {
+ // not an issue.
+ err = nil
+ }
+ return err
+ }
- return nil
+ // Finally delete followreq from DB.
+ _, err = r.conn.NewDelete().
+ Table("follow_requests").
+ Where("? = ?", bun.Ident("uri"), uri).
+ Exec(ctx)
+ return r.conn.ProcessError(err)
}
func (r *relationshipDB) DeleteAccountFollowRequests(ctx context.Context, accountID string) error {
- var followIDs []string
+ var followReqIDs []string
+ // Get full list of IDs.
if _, err := r.conn.
- NewDelete().
- Table("follow_requests").
+ NewSelect().
+ Column("id").
+ Table("follow_requestss").
WhereOr("? = ? OR ? = ?",
bun.Ident("account_id"),
accountID,
bun.Ident("target_account_id"),
accountID,
).
- Returning("?", bun.Ident("id")).
- Exec(ctx, &followIDs); err != nil {
+ Exec(ctx, &followReqIDs); err != nil {
return r.conn.ProcessError(err)
}
- // Invalidate each returned ID.
- for _, id := range followIDs {
- r.state.Caches.GTS.FollowRequest().Invalidate("ID", id)
+ defer func() {
+ // Invalidate all IDs on return.
+ for _, id := range followReqIDs {
+ r.state.Caches.GTS.FollowRequest().Invalidate("ID", id)
+ }
+ }()
+
+ // Load all followreqs into cache, this *really* isn't
+ // great but it is the only way we can ensure we invalidate
+ // all related caches correctly (e.g. visibility).
+ for _, id := range followReqIDs {
+ _, err := r.GetFollowRequestByID(ctx, id)
+ if err != nil && !errors.Is(err, db.ErrNoEntries) {
+ return err
+ }
}
- return nil
+ // Finally delete all from DB.
+ _, err := r.conn.NewDelete().
+ Table("follow_requests").
+ Where("? IN (?)", bun.Ident("id"), bun.In(followReqIDs)).
+ Exec(ctx)
+ return r.conn.ProcessError(err)
}
diff --git a/internal/db/bundb/report.go b/internal/db/bundb/report.go
index 17e1348b9..e017a8906 100644
--- a/internal/db/bundb/report.go
+++ b/internal/db/bundb/report.go
@@ -19,10 +19,12 @@ package bundb
import (
"context"
+ "errors"
"fmt"
"time"
"github.com/superseriousbusiness/gotosocial/internal/db"
+ "github.com/superseriousbusiness/gotosocial/internal/gtscontext"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/log"
"github.com/superseriousbusiness/gotosocial/internal/state"
@@ -192,14 +194,24 @@ func (r *reportDB) UpdateReport(ctx context.Context, report *gtsmodel.Report, co
}
func (r *reportDB) DeleteReportByID(ctx context.Context, id string) db.Error {
- if _, err := r.conn.
- NewDelete().
- TableExpr("? AS ?", bun.Ident("reports"), bun.Ident("report")).
- Where("? = ?", bun.Ident("report.id"), id).
- Exec(ctx); err != nil {
- return r.conn.ProcessError(err)
+ defer r.state.Caches.GTS.Report().Invalidate("ID", id)
+
+ // Load status into cache before attempting a delete,
+ // as we need it cached in order to trigger the invalidate
+ // callback. This in turn invalidates others.
+ _, err := r.GetReportByID(gtscontext.SetBarebones(ctx), id)
+ if err != nil {
+ if errors.Is(err, db.ErrNoEntries) {
+ // not an issue.
+ err = nil
+ }
+ return err
}
- r.state.Caches.GTS.Report().Invalidate("ID", id)
- return nil
+ // Finally delete report from DB.
+ _, err = r.conn.NewDelete().
+ TableExpr("? AS ?", bun.Ident("reports"), bun.Ident("report")).
+ Where("? = ?", bun.Ident("report.id"), id).
+ Exec(ctx)
+ return r.conn.ProcessError(err)
}
diff --git a/internal/db/bundb/status.go b/internal/db/bundb/status.go
index 49d4f938b..0dffbabcc 100644
--- a/internal/db/bundb/status.go
+++ b/internal/db/bundb/status.go
@@ -244,7 +244,7 @@ func (s *statusDB) PopulateStatus(ctx context.Context, status *gtsmodel.Status)
}
func (s *statusDB) PutStatus(ctx context.Context, status *gtsmodel.Status) db.Error {
- err := s.state.Caches.GTS.Status().Store(status, func() error {
+ return s.state.Caches.GTS.Status().Store(status, func() error {
// It is safe to run this database transaction within cache.Store
// as the cache does not attempt a mutex lock until AFTER hook.
//
@@ -304,21 +304,6 @@ func (s *statusDB) PutStatus(ctx context.Context, status *gtsmodel.Status) db.Er
return err
})
})
- if err != nil {
- return err
- }
-
- for _, id := range status.AttachmentIDs {
- // Invalidate media attachments from cache.
- //
- // NOTE: this is needed due to the way in which
- // we upload status attachments, and only after
- // update them with a known status ID. This is
- // not the case for header/avatar attachments.
- s.state.Caches.GTS.Media().Invalidate("ID", id)
- }
-
- return nil
}
func (s *statusDB) UpdateStatus(ctx context.Context, status *gtsmodel.Status, columns ...string) db.Error {
@@ -328,88 +313,91 @@ func (s *statusDB) UpdateStatus(ctx context.Context, status *gtsmodel.Status, co
columns = append(columns, "updated_at")
}
- if err := s.conn.RunInTx(ctx, func(tx bun.Tx) error {
- // create links between this status and any emojis it uses
- for _, i := range status.EmojiIDs {
- if _, err := tx.
- NewInsert().
- Model(&gtsmodel.StatusToEmoji{
- StatusID: status.ID,
- EmojiID: i,
- }).
- On("CONFLICT (?, ?) DO NOTHING", bun.Ident("status_id"), bun.Ident("emoji_id")).
- Exec(ctx); err != nil {
- err = s.conn.ProcessError(err)
- if !errors.Is(err, db.ErrAlreadyExists) {
- return err
+ return s.state.Caches.GTS.Status().Store(status, func() error {
+ // It is safe to run this database transaction within cache.Store
+ // as the cache does not attempt a mutex lock until AFTER hook.
+ //
+ return s.conn.RunInTx(ctx, func(tx bun.Tx) error {
+ // create links between this status and any emojis it uses
+ for _, i := range status.EmojiIDs {
+ if _, err := tx.
+ NewInsert().
+ Model(&gtsmodel.StatusToEmoji{
+ StatusID: status.ID,
+ EmojiID: i,
+ }).
+ On("CONFLICT (?, ?) DO NOTHING", bun.Ident("status_id"), bun.Ident("emoji_id")).
+ Exec(ctx); err != nil {
+ err = s.conn.ProcessError(err)
+ if !errors.Is(err, db.ErrAlreadyExists) {
+ return err
+ }
}
}
- }
- // create links between this status and any tags it uses
- for _, i := range status.TagIDs {
- if _, err := tx.
- NewInsert().
- Model(&gtsmodel.StatusToTag{
- StatusID: status.ID,
- TagID: i,
- }).
- On("CONFLICT (?, ?) DO NOTHING", bun.Ident("status_id"), bun.Ident("tag_id")).
- Exec(ctx); err != nil {
- err = s.conn.ProcessError(err)
- if !errors.Is(err, db.ErrAlreadyExists) {
- return err
+ // create links between this status and any tags it uses
+ for _, i := range status.TagIDs {
+ if _, err := tx.
+ NewInsert().
+ Model(&gtsmodel.StatusToTag{
+ StatusID: status.ID,
+ TagID: i,
+ }).
+ On("CONFLICT (?, ?) DO NOTHING", bun.Ident("status_id"), bun.Ident("tag_id")).
+ Exec(ctx); err != nil {
+ err = s.conn.ProcessError(err)
+ if !errors.Is(err, db.ErrAlreadyExists) {
+ return err
+ }
}
}
- }
- // change the status ID of the media attachments to the new status
- for _, a := range status.Attachments {
- a.StatusID = status.ID
- a.UpdatedAt = time.Now()
- if _, err := tx.
- NewUpdate().
- Model(a).
- Where("? = ?", bun.Ident("media_attachment.id"), a.ID).
- Exec(ctx); err != nil {
- err = s.conn.ProcessError(err)
- if !errors.Is(err, db.ErrAlreadyExists) {
- return err
+ // change the status ID of the media attachments to the new status
+ for _, a := range status.Attachments {
+ a.StatusID = status.ID
+ a.UpdatedAt = time.Now()
+ if _, err := tx.
+ NewUpdate().
+ Model(a).
+ Where("? = ?", bun.Ident("media_attachment.id"), a.ID).
+ Exec(ctx); err != nil {
+ err = s.conn.ProcessError(err)
+ if !errors.Is(err, db.ErrAlreadyExists) {
+ return err
+ }
}
}
- }
- // Finally, update the status
- _, err := tx.
- NewUpdate().
- Model(status).
- Column(columns...).
- Where("? = ?", bun.Ident("status.id"), status.ID).
- Exec(ctx)
- return err
- }); err != nil {
- // already processed
- return err
- }
+ // Finally, update the status
+ _, err := tx.
+ NewUpdate().
+ Model(status).
+ Column(columns...).
+ Where("? = ?", bun.Ident("status.id"), status.ID).
+ Exec(ctx)
+ return err
+ })
+ })
+}
- // Invalidate status from database lookups.
- s.state.Caches.GTS.Status().Invalidate("ID", status.ID)
+func (s *statusDB) DeleteStatusByID(ctx context.Context, id string) db.Error {
+ defer s.state.Caches.GTS.Status().Invalidate("ID", id)
- for _, id := range status.AttachmentIDs {
- // Invalidate media attachments from cache.
- //
- // NOTE: this is needed due to the way in which
- // we upload status attachments, and only after
- // update them with a known status ID. This is
- // not the case for header/avatar attachments.
- s.state.Caches.GTS.Media().Invalidate("ID", id)
+ // Load status into cache before attempting a delete,
+ // as we need it cached in order to trigger the invalidate
+ // callback. This in turn invalidates others.
+ _, err := s.GetStatusByID(
+ gtscontext.SetBarebones(ctx),
+ id,
+ )
+ if err != nil && !errors.Is(err, db.ErrNoEntries) {
+ // NOTE: even if db.ErrNoEntries is returned, we
+ // still run the below transaction to ensure related
+ // objects are appropriately deleted.
+ return err
}
- return nil
-}
-
-func (s *statusDB) DeleteStatusByID(ctx context.Context, id string) db.Error {
- if err := s.conn.RunInTx(ctx, func(tx bun.Tx) error {
+ return s.conn.RunInTx(ctx, func(tx bun.Tx) error {
// delete links between this status and any emojis it uses
if _, err := tx.
NewDelete().
@@ -438,17 +426,7 @@ func (s *statusDB) DeleteStatusByID(ctx context.Context, id string) db.Error {
}
return nil
- }); err != nil {
- return err
- }
-
- // Invalidate status from database lookups.
- s.state.Caches.GTS.Status().Invalidate("ID", id)
-
- // Invalidate status from all visibility lookups.
- s.state.Caches.Visibility.Invalidate("ItemID", id)
-
- return nil
+ })
}
func (s *statusDB) GetStatusParents(ctx context.Context, status *gtsmodel.Status, onlyDirect bool) ([]*gtsmodel.Status, db.Error) {
diff --git a/internal/db/bundb/statusfave.go b/internal/db/bundb/statusfave.go
index 0f7e5df74..497262530 100644
--- a/internal/db/bundb/statusfave.go
+++ b/internal/db/bundb/statusfave.go
@@ -156,16 +156,26 @@ func (s *statusFaveDB) PutStatusFave(ctx context.Context, fave *gtsmodel.StatusF
}
func (s *statusFaveDB) DeleteStatusFaveByID(ctx context.Context, id string) db.Error {
- if _, err := s.conn.
- NewDelete().
- Table("status_faves").
- Where("? = ?", bun.Ident("id"), id).
- Exec(ctx); err != nil {
- return s.conn.ProcessError(err)
+ defer s.state.Caches.GTS.StatusFave().Invalidate("ID", id)
+
+ // Load fave into cache before attempting a delete,
+ // as we need it cached in order to trigger the invalidate
+ // callback. This in turn invalidates others.
+ _, err := s.GetStatusFaveByID(gtscontext.SetBarebones(ctx), id)
+ if err != nil {
+ if errors.Is(err, db.ErrNoEntries) {
+ // not an issue.
+ err = nil
+ }
+ return err
}
- s.state.Caches.GTS.StatusFave().Invalidate("ID", id)
- return nil
+ // Finally delete fave from DB.
+ _, err = s.conn.NewDelete().
+ Table("status_faves").
+ Where("? = ?", bun.Ident("id"), id).
+ Exec(ctx)
+ return s.conn.ProcessError(err)
}
func (s *statusFaveDB) DeleteStatusFaves(ctx context.Context, targetAccountID string, originAccountID string) db.Error {
@@ -173,13 +183,12 @@ func (s *statusFaveDB) DeleteStatusFaves(ctx context.Context, targetAccountID st
return errors.New("DeleteStatusFaves: one of targetAccountID or originAccountID must be set")
}
- // Capture fave IDs in a RETURNING statement.
var faveIDs []string
q := s.conn.
- NewDelete().
- Table("status_faves").
- Returning("?", bun.Ident("id"))
+ NewSelect().
+ Column("id").
+ Table("status_faves")
if targetAccountID != "" {
q = q.Where("? = ?", bun.Ident("target_account_id"), targetAccountID)
@@ -193,12 +202,29 @@ func (s *statusFaveDB) DeleteStatusFaves(ctx context.Context, targetAccountID st
return s.conn.ProcessError(err)
}
+ defer func() {
+ // Invalidate all IDs on return.
+ for _, id := range faveIDs {
+ s.state.Caches.GTS.StatusFave().Invalidate("ID", id)
+ }
+ }()
+
+ // Load all faves into cache, this *really* isn't great
+ // but it is the only way we can ensure we invalidate all
+ // related caches correctly (e.g. visibility).
for _, id := range faveIDs {
- // Invalidate each of the returned status fave IDs.
- s.state.Caches.GTS.StatusFave().Invalidate("ID", id)
+ _, err := s.GetStatusFaveByID(ctx, id)
+ if err != nil && !errors.Is(err, db.ErrNoEntries) {
+ return err
+ }
}
- return nil
+ // Finally delete all from DB.
+ _, err := s.conn.NewDelete().
+ Table("status_faves").
+ Where("? IN (?)", bun.Ident("id"), bun.In(faveIDs)).
+ Exec(ctx)
+ return s.conn.ProcessError(err)
}
func (s *statusFaveDB) DeleteStatusFavesForStatus(ctx context.Context, statusID string) db.Error {
@@ -206,19 +232,35 @@ func (s *statusFaveDB) DeleteStatusFavesForStatus(ctx context.Context, statusID
var faveIDs []string
q := s.conn.
- NewDelete().
+ NewSelect().
+ Column("id").
Table("status_faves").
- Where("? = ?", bun.Ident("status_id"), statusID).
- Returning("?", bun.Ident("id"))
-
+ Where("? = ?", bun.Ident("status_id"), statusID)
if _, err := q.Exec(ctx, &faveIDs); err != nil {
return s.conn.ProcessError(err)
}
+ defer func() {
+ // Invalidate all IDs on return.
+ for _, id := range faveIDs {
+ s.state.Caches.GTS.StatusFave().Invalidate("ID", id)
+ }
+ }()
+
+ // Load all faves into cache, this *really* isn't great
+ // but it is the only way we can ensure we invalidate all
+ // related caches correctly (e.g. visibility).
for _, id := range faveIDs {
- // Invalidate each of the returned status fave IDs.
- s.state.Caches.GTS.StatusFave().Invalidate("ID", id)
+ _, err := s.GetStatusFaveByID(ctx, id)
+ if err != nil && !errors.Is(err, db.ErrNoEntries) {
+ return err
+ }
}
- return nil
+ // Finally delete all from DB.
+ _, err := s.conn.NewDelete().
+ Table("status_faves").
+ Where("? IN (?)", bun.Ident("id"), bun.In(faveIDs)).
+ Exec(ctx)
+ return s.conn.ProcessError(err)
}
diff --git a/internal/db/bundb/tombstone.go b/internal/db/bundb/tombstone.go
index 393b2e356..668bde1af 100644
--- a/internal/db/bundb/tombstone.go
+++ b/internal/db/bundb/tombstone.go
@@ -67,16 +67,12 @@ func (t *tombstoneDB) PutTombstone(ctx context.Context, tombstone *gtsmodel.Tomb
}
func (t *tombstoneDB) DeleteTombstone(ctx context.Context, id string) db.Error {
- if _, err := t.conn.
- NewDelete().
+ defer t.state.Caches.GTS.Tombstone().Invalidate("ID", id)
+
+ // Delete tombstone from DB.
+ _, err := t.conn.NewDelete().
TableExpr("? AS ?", bun.Ident("tombstones"), bun.Ident("tombstone")).
Where("? = ?", bun.Ident("tombstone.id"), id).
- Exec(ctx); err != nil {
- return t.conn.ProcessError(err)
- }
-
- // Invalidate from cache by ID
- t.state.Caches.GTS.Tombstone().Invalidate("ID", id)
-
- return nil
+ Exec(ctx)
+ return t.conn.ProcessError(err)
}
diff --git a/internal/db/bundb/user.go b/internal/db/bundb/user.go
index b5dae1573..c2ea5a67d 100644
--- a/internal/db/bundb/user.go
+++ b/internal/db/bundb/user.go
@@ -19,9 +19,11 @@ package bundb
import (
"context"
+ "errors"
"time"
"github.com/superseriousbusiness/gotosocial/internal/db"
+ "github.com/superseriousbusiness/gotosocial/internal/gtscontext"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/state"
"github.com/uptrace/bun"
@@ -155,32 +157,36 @@ func (u *userDB) UpdateUser(ctx context.Context, user *gtsmodel.User, columns ..
columns = append(columns, "updated_at")
}
- // Update the user in DB
- _, err := u.conn.
- NewUpdate().
- Model(user).
- Where("? = ?", bun.Ident("user.id"), user.ID).
- Column(columns...).
- Exec(ctx)
- if err != nil {
+ return u.state.Caches.GTS.User().Store(user, func() error {
+ _, err := u.conn.
+ NewUpdate().
+ Model(user).
+ Where("? = ?", bun.Ident("user.id"), user.ID).
+ Column(columns...).
+ Exec(ctx)
return u.conn.ProcessError(err)
- }
-
- // Invalidate user from cache
- u.state.Caches.GTS.User().Invalidate("ID", user.ID)
- return nil
+ })
}
func (u *userDB) DeleteUserByID(ctx context.Context, userID string) db.Error {
- if _, err := u.conn.
- NewDelete().
- TableExpr("? AS ?", bun.Ident("users"), bun.Ident("user")).
- Where("? = ?", bun.Ident("user.id"), userID).
- Exec(ctx); err != nil {
- return u.conn.ProcessError(err)
+ defer u.state.Caches.GTS.User().Invalidate("ID", userID)
+
+ // Load user into cache before attempting a delete,
+ // as we need it cached in order to trigger the invalidate
+ // callback. This in turn invalidates others.
+ _, err := u.GetUserByID(gtscontext.SetBarebones(ctx), userID)
+ if err != nil {
+ if errors.Is(err, db.ErrNoEntries) {
+ // not an issue.
+ err = nil
+ }
+ return err
}
- // Invalidate user from cache
- u.state.Caches.GTS.User().Invalidate("ID", userID)
- return nil
+ // Finally delete user from DB.
+ _, err = u.conn.NewDelete().
+ TableExpr("? AS ?", bun.Ident("users"), bun.Ident("user")).
+ Where("? = ?", bun.Ident("user.id"), userID).
+ Exec(ctx)
+ return u.conn.ProcessError(err)
}