[chore] Migrate accounts to new table, relax uniqueness constraint of actor url and collections

This commit is contained in:
tobi 2025-04-02 12:40:35 +02:00
commit fe352c69b5
37 changed files with 1270 additions and 566 deletions

View file

@ -46,48 +46,22 @@ type accountDB struct {
state *state.State
}
func (a *accountDB) GetAccountByID(ctx context.Context, id string) (*gtsmodel.Account, error) {
return a.getAccount(
ctx,
"ID",
func(account *gtsmodel.Account) error {
return a.db.NewSelect().
Model(account).
Where("? = ?", bun.Ident("account.id"), id).
Scan(ctx)
},
id,
)
}
func (a *accountDB) GetAccountsByIDs(ctx context.Context, ids []string) ([]*gtsmodel.Account, error) {
// Load all input account IDs via cache loader callback.
accounts, err := a.state.Caches.DB.Account.LoadIDs("ID",
ids,
func(uncached []string) ([]*gtsmodel.Account, error) {
// Preallocate expected length of uncached accounts.
accounts := make([]*gtsmodel.Account, 0, len(uncached))
// Perform database query scanning
// the remaining (uncached) account IDs.
if err := a.db.NewSelect().
Model(&accounts).
Where("? IN (?)", bun.Ident("id"), bun.In(uncached)).
Scan(ctx); err != nil {
return nil, err
}
return accounts, nil
},
)
func (a *accountDB) getAccountsBy(
ctx context.Context,
index string,
keys []string,
load func([]string) ([]*gtsmodel.Account, error),
getKey func(*gtsmodel.Account) string,
) ([]*gtsmodel.Account, error) {
// Load all input account keys via cache loader callback.
accounts, err := a.state.Caches.DB.Account.LoadIDs(index, keys, load)
if err != nil {
return nil, err
}
// Reorder the statuses by their
// IDs to ensure in correct order.
getID := func(a *gtsmodel.Account) string { return a.ID }
xslices.OrderBy(accounts, ids, getID)
// keys to ensure in correct order.
xslices.OrderBy(accounts, keys, getKey)
if gtscontext.Barebones(ctx) {
// no need to fully populate.
@ -107,6 +81,75 @@ func (a *accountDB) GetAccountsByIDs(ctx context.Context, ids []string) ([]*gtsm
return accounts, nil
}
func (a *accountDB) getOneAccountBy(
ctx context.Context,
index string,
key string,
load func([]string) ([]*gtsmodel.Account, error),
) (*gtsmodel.Account, error) {
// Get all accounts with the given key.
accounts, err := a.getAccountsBy(
ctx,
index,
[]string{key},
load,
func(a *gtsmodel.Account) string { return key },
)
if err != nil {
return nil, err
}
// Ensure we have one
// and only one account.
l := len(accounts)
if l == 0 {
return nil, db.ErrNoEntries
}
if l > 1 {
return nil, db.ErrMultipleEntries
}
return accounts[0], nil
}
func (a *accountDB) GetAccountByID(ctx context.Context, id string) (*gtsmodel.Account, error) {
return a.getAccount(
ctx,
"ID",
func(account *gtsmodel.Account) error {
return a.db.NewSelect().
Model(account).
Where("? = ?", bun.Ident("account.id"), id).
Scan(ctx)
},
id,
)
}
func (a *accountDB) GetAccountsByIDs(ctx context.Context, ids []string) ([]*gtsmodel.Account, error) {
return a.getAccountsBy(
ctx,
"ID",
ids,
func(uncached []string) ([]*gtsmodel.Account, error) {
// Preallocate expected length of uncached accounts.
accounts := make([]*gtsmodel.Account, 0, len(uncached))
// Perform database query scanning
// the remaining (uncached) accounts.
if err := a.db.NewSelect().
Model(&accounts).
Where("? IN (?)", bun.Ident("account.id"), bun.In(uncached)).
Scan(ctx); err != nil {
return nil, err
}
return accounts, nil
},
func(a *gtsmodel.Account) string { return a.ID },
)
}
func (a *accountDB) GetAccountByURI(ctx context.Context, uri string) (*gtsmodel.Account, error) {
return a.getAccount(
ctx,
@ -121,17 +164,51 @@ func (a *accountDB) GetAccountByURI(ctx context.Context, uri string) (*gtsmodel.
)
}
func (a *accountDB) GetAccountByURL(ctx context.Context, url string) (*gtsmodel.Account, error) {
return a.getAccount(
func (a *accountDB) GetOneAccountByURL(ctx context.Context, url string) (*gtsmodel.Account, error) {
return a.getOneAccountBy(
ctx,
"URL",
func(account *gtsmodel.Account) error {
return a.db.NewSelect().
Model(account).
Where("? = ?", bun.Ident("account.url"), url).
Scan(ctx)
},
url,
func(uncached []string) ([]*gtsmodel.Account, error) {
// Preallocate expected length of uncached accounts.
accounts := make([]*gtsmodel.Account, 0, len(uncached))
// Perform database query scanning
// the remaining (uncached) accounts.
if err := a.db.NewSelect().
Model(&accounts).
Where("? IN (?)", bun.Ident("account.url"), bun.In(uncached)).
Scan(ctx); err != nil {
return nil, err
}
return accounts, nil
},
)
}
func (a *accountDB) GetAccountsByURL(ctx context.Context, url string) ([]*gtsmodel.Account, error) {
// Get all accounts with the given URL.
return a.getAccountsBy(
ctx,
"ID",
[]string{url},
func(uncached []string) ([]*gtsmodel.Account, error) {
// Preallocate expected length of uncached accounts.
accounts := make([]*gtsmodel.Account, 0, len(uncached))
// Perform database query scanning
// the remaining (uncached) accounts.
if err := a.db.NewSelect().
Model(&accounts).
Where("? IN (?)", bun.Ident("account.url"), bun.In(uncached)).
Scan(ctx); err != nil {
return nil, err
}
return accounts, nil
},
func(a *gtsmodel.Account) string { return a.URL },
)
}
@ -184,59 +261,49 @@ func (a *accountDB) GetAccountByPubkeyID(ctx context.Context, id string) (*gtsmo
)
}
func (a *accountDB) GetAccountByInboxURI(ctx context.Context, uri string) (*gtsmodel.Account, error) {
return a.getAccount(
func (a *accountDB) GetOneAccountByInboxURI(ctx context.Context, uri string) (*gtsmodel.Account, error) {
return a.getOneAccountBy(
ctx,
"InboxURI",
func(account *gtsmodel.Account) error {
return a.db.NewSelect().
Model(account).
Where("? = ?", bun.Ident("account.inbox_uri"), uri).
Scan(ctx)
},
uri,
func(uncached []string) ([]*gtsmodel.Account, error) {
// Preallocate expected length of uncached accounts.
accounts := make([]*gtsmodel.Account, 0, len(uncached))
// Perform database query scanning
// the remaining (uncached) accounts.
if err := a.db.NewSelect().
Model(&accounts).
Where("? IN (?)", bun.Ident("account.inbox_uri"), bun.In(uncached)).
Scan(ctx); err != nil {
return nil, err
}
return accounts, nil
},
)
}
func (a *accountDB) GetAccountByOutboxURI(ctx context.Context, uri string) (*gtsmodel.Account, error) {
return a.getAccount(
func (a *accountDB) GetOneAccountByOutboxURI(ctx context.Context, uri string) (*gtsmodel.Account, error) {
return a.getOneAccountBy(
ctx,
"OutboxURI",
func(account *gtsmodel.Account) error {
return a.db.NewSelect().
Model(account).
Where("? = ?", bun.Ident("account.outbox_uri"), uri).
Scan(ctx)
},
uri,
)
}
func(uncached []string) ([]*gtsmodel.Account, error) {
// Preallocate expected length of uncached accounts.
accounts := make([]*gtsmodel.Account, 0, len(uncached))
func (a *accountDB) GetAccountByFollowersURI(ctx context.Context, uri string) (*gtsmodel.Account, error) {
return a.getAccount(
ctx,
"FollowersURI",
func(account *gtsmodel.Account) error {
return a.db.NewSelect().
Model(account).
Where("? = ?", bun.Ident("account.followers_uri"), uri).
Scan(ctx)
},
uri,
)
}
// Perform database query scanning
// the remaining (uncached) accounts.
if err := a.db.NewSelect().
Model(&accounts).
Where("? IN (?)", bun.Ident("account.outbox_uri"), bun.In(uncached)).
Scan(ctx); err != nil {
return nil, err
}
func (a *accountDB) GetAccountByFollowingURI(ctx context.Context, uri string) (*gtsmodel.Account, error) {
return a.getAccount(
ctx,
"FollowingURI",
func(account *gtsmodel.Account) error {
return a.db.NewSelect().
Model(account).
Where("? = ?", bun.Ident("account.following_uri"), uri).
Scan(ctx)
return accounts, nil
},
uri,
)
}
@ -587,7 +654,11 @@ func (a *accountDB) GetAccounts(
return a.state.DB.GetAccountsByIDs(ctx, accountIDs)
}
func (a *accountDB) getAccount(ctx context.Context, lookup string, dbQuery func(*gtsmodel.Account) error, keyParts ...any) (*gtsmodel.Account, error) {
func (a *accountDB) getAccount(
ctx context.Context,
lookup string,
dbQuery func(*gtsmodel.Account) error, keyParts ...any,
) (*gtsmodel.Account, error) {
// Fetch account from database cache with loader callback
account, err := a.state.Caches.DB.Account.LoadOne(lookup, func() (*gtsmodel.Account, error) {
var account gtsmodel.Account