summaryrefslogtreecommitdiff
path: root/internal/db/bundb/wrap.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/db/bundb/wrap.go')
-rw-r--r--internal/db/bundb/wrap.go258
1 files changed, 0 insertions, 258 deletions
diff --git a/internal/db/bundb/wrap.go b/internal/db/bundb/wrap.go
deleted file mode 100644
index a5039914a..000000000
--- a/internal/db/bundb/wrap.go
+++ /dev/null
@@ -1,258 +0,0 @@
-// 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 bundb
-
-import (
- "context"
- "database/sql"
- "time"
-
- "github.com/superseriousbusiness/gotosocial/internal/db"
- "github.com/superseriousbusiness/gotosocial/internal/gtserror"
- "github.com/uptrace/bun"
- "github.com/uptrace/bun/dialect"
-)
-
-// WrappedDB wraps a bun database instance
-// to provide common per-dialect SQL error
-// conversions to common types, and retries
-// on returned busy errors (SQLite only for now).
-type WrappedDB struct {
- errHook func(error) error
- *bun.DB // underlying conn
-}
-
-// WrapDB wraps a bun database instance in our own WrappedDB type.
-func WrapDB(db *bun.DB) *WrappedDB {
- var errProc func(error) error
- switch name := db.Dialect().Name(); name {
- case dialect.PG:
- errProc = processPostgresError
- case dialect.SQLite:
- errProc = processSQLiteError
- default:
- panic("unknown dialect name: " + name.String())
- }
- return &WrappedDB{
- errHook: errProc,
- DB: db,
- }
-}
-
-// BeginTx wraps bun.DB.BeginTx() with retry-busy timeout.
-func (db *WrappedDB) BeginTx(ctx context.Context, opts *sql.TxOptions) (tx bun.Tx, err error) {
- err = retryOnBusy(ctx, func() error {
- tx, err = db.DB.BeginTx(ctx, opts)
- err = db.ProcessError(err)
- return err
- })
- return
-}
-
-// ExecContext wraps bun.DB.ExecContext() with retry-busy timeout.
-func (db *WrappedDB) ExecContext(ctx context.Context, query string, args ...any) (result sql.Result, err error) {
- err = retryOnBusy(ctx, func() error {
- result, err = db.DB.ExecContext(ctx, query, args...)
- err = db.ProcessError(err)
- return err
- })
- return
-}
-
-// QueryContext wraps bun.DB.QueryContext() with retry-busy timeout.
-func (db *WrappedDB) QueryContext(ctx context.Context, query string, args ...any) (rows *sql.Rows, err error) {
- err = retryOnBusy(ctx, func() error {
- rows, err = db.DB.QueryContext(ctx, query, args...)
- err = db.ProcessError(err)
- return err
- })
- return
-}
-
-// QueryRowContext wraps bun.DB.QueryRowContext() with retry-busy timeout.
-func (db *WrappedDB) QueryRowContext(ctx context.Context, query string, args ...any) (row *sql.Row) {
- _ = retryOnBusy(ctx, func() error {
- row = db.DB.QueryRowContext(ctx, query, args...)
- err := db.ProcessError(row.Err())
- return err
- })
- return
-}
-
-// RunInTx is functionally the same as bun.DB.RunInTx() but with retry-busy timeouts.
-func (db *WrappedDB) RunInTx(ctx context.Context, fn func(bun.Tx) error) error {
- // Attempt to start new transaction.
- tx, err := db.BeginTx(ctx, nil)
- if err != nil {
- return err
- }
-
- var done bool
-
- defer func() {
- if !done {
- // Rollback (with retry-backoff).
- _ = retryOnBusy(ctx, func() error {
- err := tx.Rollback()
- return db.errHook(err)
- })
- }
- }()
-
- // Perform supplied transaction
- if err := fn(tx); err != nil {
- return db.errHook(err)
- }
-
- // Commit (with retry-backoff).
- err = retryOnBusy(ctx, func() error {
- err := tx.Commit()
- return db.errHook(err)
- })
- done = true
- return err
-}
-
-func (db *WrappedDB) NewValues(model interface{}) *bun.ValuesQuery {
- return bun.NewValuesQuery(db.DB, model).Conn(db)
-}
-
-func (db *WrappedDB) NewMerge() *bun.MergeQuery {
- return bun.NewMergeQuery(db.DB).Conn(db)
-}
-
-func (db *WrappedDB) NewSelect() *bun.SelectQuery {
- return bun.NewSelectQuery(db.DB).Conn(db)
-}
-
-func (db *WrappedDB) NewInsert() *bun.InsertQuery {
- return bun.NewInsertQuery(db.DB).Conn(db)
-}
-
-func (db *WrappedDB) NewUpdate() *bun.UpdateQuery {
- return bun.NewUpdateQuery(db.DB).Conn(db)
-}
-
-func (db *WrappedDB) NewDelete() *bun.DeleteQuery {
- return bun.NewDeleteQuery(db.DB).Conn(db)
-}
-
-func (db *WrappedDB) NewRaw(query string, args ...interface{}) *bun.RawQuery {
- return bun.NewRawQuery(db.DB, query, args...).Conn(db)
-}
-
-func (db *WrappedDB) NewCreateTable() *bun.CreateTableQuery {
- return bun.NewCreateTableQuery(db.DB).Conn(db)
-}
-
-func (db *WrappedDB) NewDropTable() *bun.DropTableQuery {
- return bun.NewDropTableQuery(db.DB).Conn(db)
-}
-
-func (db *WrappedDB) NewCreateIndex() *bun.CreateIndexQuery {
- return bun.NewCreateIndexQuery(db.DB).Conn(db)
-}
-
-func (db *WrappedDB) NewDropIndex() *bun.DropIndexQuery {
- return bun.NewDropIndexQuery(db.DB).Conn(db)
-}
-
-func (db *WrappedDB) NewTruncateTable() *bun.TruncateTableQuery {
- return bun.NewTruncateTableQuery(db.DB).Conn(db)
-}
-
-func (db *WrappedDB) NewAddColumn() *bun.AddColumnQuery {
- return bun.NewAddColumnQuery(db.DB).Conn(db)
-}
-
-func (db *WrappedDB) NewDropColumn() *bun.DropColumnQuery {
- return bun.NewDropColumnQuery(db.DB).Conn(db)
-}
-
-// ProcessError processes an error to replace any known values with our own error types,
-// making it easier to catch specific situations (e.g. no rows, already exists, etc)
-func (db *WrappedDB) ProcessError(err error) error {
- if err == nil {
- return nil
- }
- return db.errHook(err)
-}
-
-// Exists checks the results of a SelectQuery for the existence of the data in question, masking ErrNoEntries errors
-func (db *WrappedDB) Exists(ctx context.Context, query *bun.SelectQuery) (bool, error) {
- exists, err := query.Exists(ctx)
- switch err {
- case nil:
- return exists, nil
- case sql.ErrNoRows:
- return false, nil
- default:
- return false, err
- }
-}
-
-// NotExists is the functional opposite of conn.Exists()
-func (db *WrappedDB) NotExists(ctx context.Context, query *bun.SelectQuery) (bool, error) {
- exists, err := db.Exists(ctx, query)
- return !exists, err
-}
-
-// retryOnBusy will retry given function on returned 'errBusy'.
-func retryOnBusy(ctx context.Context, fn func() error) error {
- var backoff time.Duration
-
- for i := 0; ; i++ {
- // Perform func.
- err := fn()
-
- if err != errBusy {
- // May be nil, or may be
- // some other error, either
- // way return here.
- return err
- }
-
- // backoff according to a multiplier of 2ms * 2^2n,
- // up to a maximum possible backoff time of 5 minutes.
- //
- // this works out as the following:
- // 4ms
- // 16ms
- // 64ms
- // 256ms
- // 1.024s
- // 4.096s
- // 16.384s
- // 1m5.536s
- // 4m22.144s
- backoff = 2 * time.Millisecond * (1 << (2*i + 1))
- if backoff >= 5*time.Minute {
- break
- }
-
- select {
- // Context cancelled.
- case <-ctx.Done():
-
- // Backoff for some time.
- case <-time.After(backoff):
- }
- }
-
- return gtserror.Newf("%w (waited > %s)", db.ErrBusyTimeout, backoff)
-}