From 07aee57af2e0073939300ef081f4cda5675ef545 Mon Sep 17 00:00:00 2001 From: tobi <31960611+tsmethurst@users.noreply.github.com> Date: Mon, 15 Jan 2024 16:39:10 +0100 Subject: [PATCH] [bugfix] Replace named unique constraint on header filter header with generic unique directive (#2525) * [bugfix] Replace named unique constraint on header filter header with generic unique directive * add migration retry * the old fixie uppie * fix constraint name * my goodness --- ...240114112637_postgres_header_filter_fix.go | 115 ++++++++++++++++++ internal/gtsmodel/headerfilter.go | 4 +- 2 files changed, 117 insertions(+), 2 deletions(-) create mode 100644 internal/db/bundb/migrations/20240114112637_postgres_header_filter_fix.go diff --git a/internal/db/bundb/migrations/20240114112637_postgres_header_filter_fix.go b/internal/db/bundb/migrations/20240114112637_postgres_header_filter_fix.go new file mode 100644 index 000000000..92e19fe34 --- /dev/null +++ b/internal/db/bundb/migrations/20240114112637_postgres_header_filter_fix.go @@ -0,0 +1,115 @@ +// 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 . + +package migrations + +import ( + "context" + + "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" + "github.com/uptrace/bun" + "github.com/uptrace/bun/dialect" +) + +func init() { + up := func(ctx context.Context, db *bun.DB) error { + // Run the first bit in a transaction + // since we're not expecting to encounter + // errors, and any we do encounter will + // stop us in our tracks. + err := db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error { + // Drop each of the old versions of the + // header tables. Normally dropping tables + // is a big no-no but this migration happens + // while header filters weren't even in a + // release yet, so let's go for it. + for _, table := range []string{ + "header_filter_allows", + "header_filter_blocks", + } { + _, err := tx.NewDropTable(). + IfExists(). + Table(table). + Exec(ctx) + if err != nil { + return err + } + } + + // Recreate header tables using + // the most up-to-date model. + for _, model := range []any{ + >smodel.HeaderFilterAllow{}, + >smodel.HeaderFilterBlock{}, + } { + _, err := tx.NewCreateTable(). + IfNotExists(). + Model(model). + Exec(ctx) + if err != nil { + return err + } + } + + return nil + }) + if err != nil { + return err + } + + // On Postgres the constraints might still + // be kicking around from a partial failed + // migration, so make sure they're gone now. + // Dropping a constraint will also drop any + // indexes supporting the constraint, as per: + // + // https://www.postgresql.org/docs/16/sql-altertable.html#SQL-ALTERTABLE-DESC-DROP-CONSTRAINT + // + // We run this part outside of a transaction + // because we don't check for errors, and we + // don't want an error in the first query to + // foul the transaction and stop the second + // query from running. + if db.Dialect().Name() == dialect.PG { + for _, table := range []string{ + "public.header_filter_allows", + "public.header_filter_blocks", + } { + // Just swallow any errors + // here, we're not bothered. + _, _ = db.ExecContext( + ctx, + "ALTER TABLE ? DROP CONSTRAINT IF EXISTS ?", + bun.Ident(table), + bun.Safe("header_regex"), + ) + } + } + + 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/gtsmodel/headerfilter.go b/internal/gtsmodel/headerfilter.go index d1fcb146e..291497c5e 100644 --- a/internal/gtsmodel/headerfilter.go +++ b/internal/gtsmodel/headerfilter.go @@ -45,8 +45,8 @@ type HeaderFilterBlock struct{ HeaderFilter } // matching regex, and details about its creation. type HeaderFilter struct { ID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // ID of this item in the database - Header string `bun:",nullzero,notnull,unique:header_regex"` // Request header this filter pertains to - Regex string `bun:",nullzero,notnull,unique:header_regex"` // Request header value matching regular expression + Header string `bun:",nullzero,notnull"` // Canonical request header this filter pertains to. + Regex string `bun:",nullzero,notnull"` // Request header value matching regular expression. AuthorID string `bun:"type:CHAR(26),nullzero,notnull"` // Account ID of the creator of this filter Author *Account `bun:"-"` // Account corresponding to AuthorID CreatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created