From bfccf4e450d22d9a3fc0c58d94ed728207d305e0 Mon Sep 17 00:00:00 2001 From: tobi <31960611+tsmethurst@users.noreply.github.com> Date: Thu, 2 Mar 2023 16:58:23 +0100 Subject: [PATCH] [bugfix] add ON CONFLICT statements to status updates (#1580) --- internal/db/bundb/status.go | 18 +++++++++++++----- internal/db/bundb/status_test.go | 31 ++++++++++++++++++++++++++++++- 2 files changed, 43 insertions(+), 6 deletions(-) diff --git a/internal/db/bundb/status.go b/internal/db/bundb/status.go index 8f1df2886..72e44068d 100644 --- a/internal/db/bundb/status.go +++ b/internal/db/bundb/status.go @@ -200,7 +200,9 @@ func (s *statusDB) PutStatus(ctx context.Context, status *gtsmodel.Status) db.Er Model(>smodel.StatusToEmoji{ StatusID: status.ID, EmojiID: i, - }).Exec(ctx); err != nil { + }). + 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 @@ -215,7 +217,9 @@ func (s *statusDB) PutStatus(ctx context.Context, status *gtsmodel.Status) db.Er Model(>smodel.StatusToTag{ StatusID: status.ID, TagID: i, - }).Exec(ctx); err != nil { + }). + 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 @@ -261,7 +265,9 @@ func (s *statusDB) UpdateStatus(ctx context.Context, status *gtsmodel.Status, co Model(>smodel.StatusToEmoji{ StatusID: status.ID, EmojiID: i, - }).Exec(ctx); err != nil { + }). + 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 @@ -276,7 +282,9 @@ func (s *statusDB) UpdateStatus(ctx context.Context, status *gtsmodel.Status, co Model(>smodel.StatusToTag{ StatusID: status.ID, TagID: i, - }).Exec(ctx); err != nil { + }). + 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 @@ -300,7 +308,7 @@ func (s *statusDB) UpdateStatus(ctx context.Context, status *gtsmodel.Status, co } } - // Finally, insert the status + // Finally, update the status _, err := tx. NewUpdate(). Model(status). diff --git a/internal/db/bundb/status_test.go b/internal/db/bundb/status_test.go index d86e0bcf9..2335c75a9 100644 --- a/internal/db/bundb/status_test.go +++ b/internal/db/bundb/status_test.go @@ -26,6 +26,7 @@ "github.com/stretchr/testify/suite" "github.com/superseriousbusiness/gotosocial/internal/db" + "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" ) type StatusTestSuite struct { @@ -176,7 +177,10 @@ func (suite *StatusTestSuite) TestGetStatusChildren() { } func (suite *StatusTestSuite) TestDeleteStatus() { - targetStatus := suite.testStatuses["admin_account_status_1"] + // Take a copy of the status. + targetStatus := >smodel.Status{} + *targetStatus = *suite.testStatuses["admin_account_status_1"] + err := suite.db.DeleteStatusByID(context.Background(), targetStatus.ID) suite.NoError(err) @@ -184,6 +188,31 @@ func (suite *StatusTestSuite) TestDeleteStatus() { suite.ErrorIs(err, db.ErrNoEntries) } +// This test was added specifically to ensure that Postgres wasn't getting upset +// about trying to use a transaction in which an error has already occurred, which +// was previously leading to errors like 'current transaction is aborted, commands +// ignored until end of transaction block' when updating a status that already had +// emojis or tags set on it. +// +// To run this test for postgres specifically, start a postgres container on localhost +// and then run: +// +// GTS_DB_TYPE=postgres GTS_DB_ADDRESS=localhost go test ./internal/db/bundb -run '^TestStatusTestSuite$' -testify.m '^(TestUpdateStatus)$' github.com/superseriousbusiness/gotosocial/internal/db/bundb +func (suite *StatusTestSuite) TestUpdateStatus() { + // Take a copy of the status. + targetStatus := >smodel.Status{} + *targetStatus = *suite.testStatuses["admin_account_status_1"] + + targetStatus.PinnedAt = time.Time{} + + err := suite.db.UpdateStatus(context.Background(), targetStatus, "pinned_at") + suite.NoError(err) + + updated, err := suite.db.GetStatusByID(context.Background(), targetStatus.ID) + suite.NoError(err) + suite.True(updated.PinnedAt.IsZero()) +} + func TestStatusTestSuite(t *testing.T) { suite.Run(t, new(StatusTestSuite)) }