commit e58d2d81226c4cc4110747305d083b67903d621c
parent dd1a4cd892552b39eb51d9259603d0d6cb498b51
Author: kim <89579420+NyaaaWhatsUpDoc@users.noreply.github.com>
Date: Thu, 8 Dec 2022 17:35:14 +0000
[chore] move caches to a separate State{} structure (#1078)
* move caches to a separate State{} structure
Signed-off-by: kim <grufwub@gmail.com>
* fix call to log.Panic not using formatted call
Signed-off-by: kim <grufwub@gmail.com>
* move caches to use interfaces, to make switchouts easier in future
Signed-off-by: kim <grufwub@gmail.com>
* fix rebase issue
Signed-off-by: kim <grufwub@gmail.com>
* improve code comment
Signed-off-by: kim <grufwub@gmail.com>
* fix further issues after rebase
Signed-off-by: kim <grufwub@gmail.com>
* heh
Signed-off-by: kim <grufwub@gmail.com>
* add missing license text
Signed-off-by: kim <grufwub@gmail.com>
Signed-off-by: kim <grufwub@gmail.com>
Diffstat:
27 files changed, 725 insertions(+), 332 deletions(-)
diff --git a/cmd/gotosocial/action/admin/account/account.go b/cmd/gotosocial/action/admin/account/account.go
@@ -27,17 +27,24 @@ import (
"github.com/superseriousbusiness/gotosocial/cmd/gotosocial/action"
"github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/db/bundb"
+ "github.com/superseriousbusiness/gotosocial/internal/state"
"github.com/superseriousbusiness/gotosocial/internal/validate"
"golang.org/x/crypto/bcrypt"
)
// Create creates a new account in the database using the provided flags.
var Create action.GTSAction = func(ctx context.Context) error {
- dbConn, err := bundb.NewBunDBService(ctx)
+ var state state.State
+ state.Caches.Init()
+
+ dbConn, err := bundb.NewBunDBService(ctx, &state)
if err != nil {
return fmt.Errorf("error creating dbservice: %s", err)
}
+ // Set the state DB connection
+ state.DB = dbConn
+
username := config.GetAdminAccountUsername()
if username == "" {
return errors.New("no username set")
@@ -88,11 +95,17 @@ var Create action.GTSAction = func(ctx context.Context) error {
// Confirm sets a user to Approved, sets Email to the current UnconfirmedEmail value, and sets ConfirmedAt to now.
var Confirm action.GTSAction = func(ctx context.Context) error {
- dbConn, err := bundb.NewBunDBService(ctx)
+ var state state.State
+ state.Caches.Init()
+
+ dbConn, err := bundb.NewBunDBService(ctx, &state)
if err != nil {
return fmt.Errorf("error creating dbservice: %s", err)
}
+ // Set the state DB connection
+ state.DB = dbConn
+
username := config.GetAdminAccountUsername()
if username == "" {
return errors.New("no username set")
@@ -125,11 +138,17 @@ var Confirm action.GTSAction = func(ctx context.Context) error {
// Promote sets a user to admin.
var Promote action.GTSAction = func(ctx context.Context) error {
- dbConn, err := bundb.NewBunDBService(ctx)
+ var state state.State
+ state.Caches.Init()
+
+ dbConn, err := bundb.NewBunDBService(ctx, &state)
if err != nil {
return fmt.Errorf("error creating dbservice: %s", err)
}
+ // Set the state DB connection
+ state.DB = dbConn
+
username := config.GetAdminAccountUsername()
if username == "" {
return errors.New("no username set")
@@ -159,11 +178,17 @@ var Promote action.GTSAction = func(ctx context.Context) error {
// Demote sets admin on a user to false.
var Demote action.GTSAction = func(ctx context.Context) error {
- dbConn, err := bundb.NewBunDBService(ctx)
+ var state state.State
+ state.Caches.Init()
+
+ dbConn, err := bundb.NewBunDBService(ctx, &state)
if err != nil {
return fmt.Errorf("error creating dbservice: %s", err)
}
+ // Set the state DB connection
+ state.DB = dbConn
+
username := config.GetAdminAccountUsername()
if username == "" {
return errors.New("no username set")
@@ -193,11 +218,17 @@ var Demote action.GTSAction = func(ctx context.Context) error {
// Disable sets Disabled to true on a user.
var Disable action.GTSAction = func(ctx context.Context) error {
- dbConn, err := bundb.NewBunDBService(ctx)
+ var state state.State
+ state.Caches.Init()
+
+ dbConn, err := bundb.NewBunDBService(ctx, &state)
if err != nil {
return fmt.Errorf("error creating dbservice: %s", err)
}
+ // Set the state DB connection
+ state.DB = dbConn
+
username := config.GetAdminAccountUsername()
if username == "" {
return errors.New("no username set")
@@ -227,11 +258,17 @@ var Disable action.GTSAction = func(ctx context.Context) error {
// Password sets the password of target account.
var Password action.GTSAction = func(ctx context.Context) error {
- dbConn, err := bundb.NewBunDBService(ctx)
+ var state state.State
+ state.Caches.Init()
+
+ dbConn, err := bundb.NewBunDBService(ctx, &state)
if err != nil {
return fmt.Errorf("error creating dbservice: %s", err)
}
+ // Set the state DB connection
+ state.DB = dbConn
+
username := config.GetAdminAccountUsername()
if username == "" {
return errors.New("no username set")
diff --git a/cmd/gotosocial/action/admin/media/prune/orphaned.go b/cmd/gotosocial/action/admin/media/prune/orphaned.go
@@ -27,12 +27,16 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/db/bundb"
"github.com/superseriousbusiness/gotosocial/internal/log"
"github.com/superseriousbusiness/gotosocial/internal/media"
+ "github.com/superseriousbusiness/gotosocial/internal/state"
gtsstorage "github.com/superseriousbusiness/gotosocial/internal/storage"
)
// Orphaned prunes orphaned media from storage.
var Orphaned action.GTSAction = func(ctx context.Context) error {
- dbService, err := bundb.NewBunDBService(ctx)
+ var state state.State
+ state.Caches.Init()
+
+ dbService, err := bundb.NewBunDBService(ctx, &state)
if err != nil {
return fmt.Errorf("error creating dbservice: %s", err)
}
@@ -54,7 +58,7 @@ var Orphaned action.GTSAction = func(ctx context.Context) error {
return fmt.Errorf("error pruning: %s", err)
}
- if dry {
+ if dry /* dick heyyoooooo */ {
log.Infof("DRY RUN: %d stored items are orphaned and eligible to be pruned", pruned)
} else {
log.Infof("%d stored items were orphaned and pruned", pruned)
diff --git a/cmd/gotosocial/action/admin/trans/export.go b/cmd/gotosocial/action/admin/trans/export.go
@@ -26,16 +26,22 @@ import (
"github.com/superseriousbusiness/gotosocial/cmd/gotosocial/action"
"github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/db/bundb"
+ "github.com/superseriousbusiness/gotosocial/internal/state"
"github.com/superseriousbusiness/gotosocial/internal/trans"
)
// Export exports info from the database into a file
var Export action.GTSAction = func(ctx context.Context) error {
- dbConn, err := bundb.NewBunDBService(ctx)
+ var state state.State
+
+ dbConn, err := bundb.NewBunDBService(ctx, &state)
if err != nil {
return fmt.Errorf("error creating dbservice: %s", err)
}
+ // Set the state DB connection
+ state.DB = dbConn
+
exporter := trans.NewExporter(dbConn)
path := config.GetAdminTransPath()
diff --git a/cmd/gotosocial/action/admin/trans/import.go b/cmd/gotosocial/action/admin/trans/import.go
@@ -26,16 +26,22 @@ import (
"github.com/superseriousbusiness/gotosocial/cmd/gotosocial/action"
"github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/db/bundb"
+ "github.com/superseriousbusiness/gotosocial/internal/state"
"github.com/superseriousbusiness/gotosocial/internal/trans"
)
// Import imports info from a file into the database
var Import action.GTSAction = func(ctx context.Context) error {
- dbConn, err := bundb.NewBunDBService(ctx)
+ var state state.State
+
+ dbConn, err := bundb.NewBunDBService(ctx, &state)
if err != nil {
return fmt.Errorf("error creating dbservice: %s", err)
}
+ // Set the state DB connection
+ state.DB = dbConn
+
importer := trans.NewImporter(dbConn)
path := config.GetAdminTransPath()
diff --git a/cmd/gotosocial/action/server/server.go b/cmd/gotosocial/action/server/server.go
@@ -65,6 +65,7 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/oidc"
"github.com/superseriousbusiness/gotosocial/internal/processing"
"github.com/superseriousbusiness/gotosocial/internal/router"
+ "github.com/superseriousbusiness/gotosocial/internal/state"
gtsstorage "github.com/superseriousbusiness/gotosocial/internal/storage"
"github.com/superseriousbusiness/gotosocial/internal/transport"
"github.com/superseriousbusiness/gotosocial/internal/typeutils"
@@ -73,11 +74,20 @@ import (
// Start creates and starts a gotosocial server
var Start action.GTSAction = func(ctx context.Context) error {
- dbService, err := bundb.NewBunDBService(ctx)
+ var state state.State
+
+ // Initialize caches
+ state.Caches.Init()
+
+ // Open connection to the database
+ dbService, err := bundb.NewBunDBService(ctx, &state)
if err != nil {
return fmt.Errorf("error creating dbservice: %s", err)
}
+ // Set the state DB connection
+ state.DB = dbService
+
if err := dbService.CreateInstanceAccount(ctx); err != nil {
return fmt.Errorf("error creating instance account: %s", err)
}
diff --git a/internal/cache/ap.go b/internal/cache/ap.go
@@ -0,0 +1,44 @@
+/*
+ GoToSocial
+ Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org
+
+ 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 cache
+
+type APCaches interface {
+ // Init will initialize all the ActivityPub caches in this collection.
+ // NOTE: the cache MUST NOT be in use anywhere, this is not thread-safe.
+ Init()
+
+ // Start will attempt to start all of the ActivityPub caches, or panic.
+ Start()
+
+ // Stop will attempt to stop all of the ActivityPub caches, or panic.
+ Stop()
+}
+
+// NewAP returns a new default implementation of APCaches.
+func NewAP() APCaches {
+ return &apCaches{}
+}
+
+type apCaches struct{}
+
+func (c *apCaches) Init() {}
+
+func (c *apCaches) Start() {}
+
+func (c *apCaches) Stop() {}
diff --git a/internal/cache/cache.go b/internal/cache/cache.go
@@ -0,0 +1,60 @@
+/*
+ GoToSocial
+ Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org
+
+ 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 cache
+
+type Caches struct {
+ // GTS provides access to the collection of gtsmodel object caches.
+ GTS GTSCaches
+
+ // AP provides access to the collection of ActivityPub object caches.
+ AP APCaches
+
+ // prevent pass-by-value.
+ _ nocopy
+}
+
+// Init will (re)initialize both the GTS and AP cache collections.
+// NOTE: the cache MUST NOT be in use anywhere, this is not thread-safe.
+func (c *Caches) Init() {
+ if c.GTS == nil {
+ // use default impl
+ c.GTS = NewGTS()
+ }
+
+ if c.AP == nil {
+ // use default impl
+ c.AP = NewAP()
+ }
+
+ // initialize caches
+ c.GTS.Init()
+ c.AP.Init()
+}
+
+// Start will start both the GTS and AP cache collections.
+func (c *Caches) Start() {
+ c.GTS.Start()
+ c.AP.Start()
+}
+
+// Stop will stop both the GTS and AP cache collections.
+func (c *Caches) Stop() {
+ c.GTS.Stop()
+ c.AP.Stop()
+}
diff --git a/internal/cache/gts.go b/internal/cache/gts.go
@@ -0,0 +1,313 @@
+/*
+ GoToSocial
+ Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org
+
+ 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 cache
+
+import (
+ "time"
+
+ "codeberg.org/gruf/go-cache/v3/result"
+ "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
+)
+
+type GTSCaches interface {
+ // Init will initialize all the gtsmodel caches in this collection.
+ // NOTE: the cache MUST NOT be in use anywhere, this is not thread-safe.
+ Init()
+
+ // Start will attempt to start all of the gtsmodel caches, or panic.
+ Start()
+
+ // Stop will attempt to stop all of the gtsmodel caches, or panic.
+ Stop()
+
+ // Account provides access to the gtsmodel Account database cache.
+ Account() *result.Cache[*gtsmodel.Account]
+
+ // Block provides access to the gtsmodel Block (account) database cache.
+ Block() *result.Cache[*gtsmodel.Block]
+
+ // DomainBlock provides access to the gtsmodel DomainBlock database cache.
+ DomainBlock() *result.Cache[*gtsmodel.DomainBlock]
+
+ // Emoji provides access to the gtsmodel Emoji database cache.
+ Emoji() *result.Cache[*gtsmodel.Emoji]
+
+ // EmojiCategory provides access to the gtsmodel EmojiCategory database cache.
+ EmojiCategory() *result.Cache[*gtsmodel.EmojiCategory]
+
+ // Mention provides access to the gtsmodel Mention database cache.
+ Mention() *result.Cache[*gtsmodel.Mention]
+
+ // Notification provides access to the gtsmodel Notification database cache.
+ Notification() *result.Cache[*gtsmodel.Notification]
+
+ // Status provides access to the gtsmodel Status database cache.
+ Status() *result.Cache[*gtsmodel.Status]
+
+ // Tombstone provides access to the gtsmodel Tombstone database cache.
+ Tombstone() *result.Cache[*gtsmodel.Tombstone]
+
+ // User provides access to the gtsmodel User database cache.
+ User() *result.Cache[*gtsmodel.User]
+}
+
+// NewGTS returns a new default implementation of GTSCaches.
+func NewGTS() GTSCaches {
+ return >sCaches{}
+}
+
+type gtsCaches struct {
+ account *result.Cache[*gtsmodel.Account]
+ block *result.Cache[*gtsmodel.Block]
+ domainBlock *result.Cache[*gtsmodel.DomainBlock]
+ emoji *result.Cache[*gtsmodel.Emoji]
+ emojiCategory *result.Cache[*gtsmodel.EmojiCategory]
+ mention *result.Cache[*gtsmodel.Mention]
+ notification *result.Cache[*gtsmodel.Notification]
+ status *result.Cache[*gtsmodel.Status]
+ tombstone *result.Cache[*gtsmodel.Tombstone]
+ user *result.Cache[*gtsmodel.User]
+}
+
+func (c *gtsCaches) Init() {
+ c.initAccount()
+ c.initBlock()
+ c.initDomainBlock()
+ c.initEmoji()
+ c.initEmojiCategory()
+ c.initMention()
+ c.initNotification()
+ c.initStatus()
+ c.initTombstone()
+ c.initUser()
+}
+
+func (c *gtsCaches) Start() {
+ tryUntil("starting gtsmodel.Account cache", 5, func() bool {
+ return c.account.Start(time.Second * 10)
+ })
+ tryUntil("starting gtsmodel.Block cache", 5, func() bool {
+ return c.block.Start(time.Second * 10)
+ })
+ tryUntil("starting gtsmodel.DomainBlock cache", 5, func() bool {
+ return c.domainBlock.Start(time.Second * 10)
+ })
+ tryUntil("starting gtsmodel.Emoji cache", 5, func() bool {
+ return c.emoji.Start(time.Second * 10)
+ })
+ tryUntil("starting gtsmodel.EmojiCategory cache", 5, func() bool {
+ return c.emojiCategory.Start(time.Second * 10)
+ })
+ tryUntil("starting gtsmodel.Mention cache", 5, func() bool {
+ return c.mention.Start(time.Second * 10)
+ })
+ tryUntil("starting gtsmodel.Notification cache", 5, func() bool {
+ return c.notification.Start(time.Second * 10)
+ })
+ tryUntil("starting gtsmodel.Status cache", 5, func() bool {
+ return c.status.Start(time.Second * 10)
+ })
+ tryUntil("starting gtsmodel.Tombstone cache", 5, func() bool {
+ return c.tombstone.Start(time.Second * 10)
+ })
+ tryUntil("starting gtsmodel.User cache", 5, func() bool {
+ return c.user.Start(time.Second * 10)
+ })
+}
+
+func (c *gtsCaches) Stop() {
+ tryUntil("stopping gtsmodel.Account cache", 5, c.account.Stop)
+ tryUntil("stopping gtsmodel.Block cache", 5, c.block.Stop)
+ tryUntil("stopping gtsmodel.DomainBlock cache", 5, c.domainBlock.Stop)
+ tryUntil("stopping gtsmodel.Emoji cache", 5, c.emoji.Stop)
+ tryUntil("stopping gtsmodel.EmojiCategory cache", 5, c.emojiCategory.Stop)
+ tryUntil("stopping gtsmodel.Mention cache", 5, c.mention.Stop)
+ tryUntil("stopping gtsmodel.Notification cache", 5, c.notification.Stop)
+ tryUntil("stopping gtsmodel.Status cache", 5, c.status.Stop)
+ tryUntil("stopping gtsmodel.Tombstone cache", 5, c.tombstone.Stop)
+ tryUntil("stopping gtsmodel.User cache", 5, c.user.Stop)
+}
+
+func (c *gtsCaches) Account() *result.Cache[*gtsmodel.Account] {
+ return c.account
+}
+
+func (c *gtsCaches) Block() *result.Cache[*gtsmodel.Block] {
+ return c.block
+}
+
+func (c *gtsCaches) DomainBlock() *result.Cache[*gtsmodel.DomainBlock] {
+ return c.domainBlock
+}
+
+func (c *gtsCaches) Emoji() *result.Cache[*gtsmodel.Emoji] {
+ return c.emoji
+}
+
+func (c *gtsCaches) EmojiCategory() *result.Cache[*gtsmodel.EmojiCategory] {
+ return c.emojiCategory
+}
+
+func (c *gtsCaches) Mention() *result.Cache[*gtsmodel.Mention] {
+ return c.mention
+}
+
+func (c *gtsCaches) Notification() *result.Cache[*gtsmodel.Notification] {
+ return c.notification
+}
+
+func (c *gtsCaches) Status() *result.Cache[*gtsmodel.Status] {
+ return c.status
+}
+
+func (c *gtsCaches) Tombstone() *result.Cache[*gtsmodel.Tombstone] {
+ return c.tombstone
+}
+
+func (c *gtsCaches) User() *result.Cache[*gtsmodel.User] {
+ return c.user
+}
+
+func (c *gtsCaches) initAccount() {
+ c.account = result.NewSized([]result.Lookup{
+ {Name: "ID"},
+ {Name: "URI"},
+ {Name: "URL"},
+ {Name: "Username.Domain"},
+ {Name: "PublicKeyURI"},
+ }, func(a1 *gtsmodel.Account) *gtsmodel.Account {
+ a2 := new(gtsmodel.Account)
+ *a2 = *a1
+ return a2
+ }, 1000)
+ c.account.SetTTL(time.Minute*5, false)
+}
+
+func (c *gtsCaches) initBlock() {
+ c.block = result.NewSized([]result.Lookup{
+ {Name: "ID"},
+ {Name: "AccountID.TargetAccountID"},
+ {Name: "URI"},
+ }, func(b1 *gtsmodel.Block) *gtsmodel.Block {
+ b2 := new(gtsmodel.Block)
+ *b2 = *b1
+ return b2
+ }, 1000)
+ c.block.SetTTL(time.Minute*5, false)
+}
+
+func (c *gtsCaches) initDomainBlock() {
+ c.domainBlock = result.NewSized([]result.Lookup{
+ {Name: "Domain"},
+ }, func(d1 *gtsmodel.DomainBlock) *gtsmodel.DomainBlock {
+ d2 := new(gtsmodel.DomainBlock)
+ *d2 = *d1
+ return d2
+ }, 1000)
+ c.domainBlock.SetTTL(time.Minute*5, false)
+}
+
+func (c *gtsCaches) initEmoji() {
+ c.emoji = result.NewSized([]result.Lookup{
+ {Name: "ID"},
+ {Name: "URI"},
+ {Name: "Shortcode.Domain"},
+ {Name: "ImageStaticURL"},
+ }, func(e1 *gtsmodel.Emoji) *gtsmodel.Emoji {
+ e2 := new(gtsmodel.Emoji)
+ *e2 = *e1
+ return e2
+ }, 1000)
+ c.emoji.SetTTL(time.Minute*5, false)
+}
+
+func (c *gtsCaches) initEmojiCategory() {
+ c.emojiCategory = result.NewSized([]result.Lookup{
+ {Name: "ID"},
+ {Name: "Name"},
+ }, func(c1 *gtsmodel.EmojiCategory) *gtsmodel.EmojiCategory {
+ c2 := new(gtsmodel.EmojiCategory)
+ *c2 = *c1
+ return c2
+ }, 1000)
+ c.emojiCategory.SetTTL(time.Minute*5, false)
+}
+
+func (c *gtsCaches) initMention() {
+ c.mention = result.NewSized([]result.Lookup{
+ {Name: "ID"},
+ }, func(m1 *gtsmodel.Mention) *gtsmodel.Mention {
+ m2 := new(gtsmodel.Mention)
+ *m2 = *m1
+ return m2
+ }, 1000)
+ c.mention.SetTTL(time.Minute*5, false)
+}
+
+func (c *gtsCaches) initNotification() {
+ c.notification = result.NewSized([]result.Lookup{
+ {Name: "ID"},
+ }, func(n1 *gtsmodel.Notification) *gtsmodel.Notification {
+ n2 := new(gtsmodel.Notification)
+ *n2 = *n1
+ return n2
+ }, 1000)
+ c.notification.SetTTL(time.Minute*5, false)
+}
+
+func (c *gtsCaches) initStatus() {
+ c.status = result.NewSized([]result.Lookup{
+ {Name: "ID"},
+ {Name: "URI"},
+ {Name: "URL"},
+ }, func(s1 *gtsmodel.Status) *gtsmodel.Status {
+ s2 := new(gtsmodel.Status)
+ *s2 = *s1
+ return s2
+ }, 1000)
+ c.status.SetTTL(time.Minute*5, false)
+}
+
+// initTombstone will initialize the gtsmodel.Tombstone cache.
+func (c *gtsCaches) initTombstone() {
+ c.tombstone = result.NewSized([]result.Lookup{
+ {Name: "ID"},
+ {Name: "URI"},
+ }, func(t1 *gtsmodel.Tombstone) *gtsmodel.Tombstone {
+ t2 := new(gtsmodel.Tombstone)
+ *t2 = *t1
+ return t2
+ }, 100)
+ c.tombstone.SetTTL(time.Minute*5, false)
+}
+
+func (c *gtsCaches) initUser() {
+ c.user = result.NewSized([]result.Lookup{
+ {Name: "ID"},
+ {Name: "AccountID"},
+ {Name: "Email"},
+ {Name: "ConfirmationToken"},
+ {Name: "ExternalID"},
+ }, func(u1 *gtsmodel.User) *gtsmodel.User {
+ u2 := new(gtsmodel.User)
+ *u2 = *u1
+ return u2
+ }, 1000)
+ c.user.SetTTL(time.Minute*5, false)
+}
diff --git a/internal/cache/util.go b/internal/cache/util.go
@@ -0,0 +1,36 @@
+/*
+ GoToSocial
+ Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org
+
+ 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 cache
+
+import "github.com/superseriousbusiness/gotosocial/internal/log"
+
+// nocopy when embedded will signal linter to
+// error on pass-by-value of parent struct.
+type nocopy struct{}
+
+func (*nocopy) Lock() {}
+
+func (*nocopy) Unlock() {}
+
+// tryUntil will attempt to call 'do' for 'count' attempts, before panicking with 'msg'.
+func tryUntil(msg string, count int, do func() bool) {
+ for i := 0; i < count && !do(); i++ {
+ }
+ log.Panicf("failed %s after %d tries", msg, count)
+}
diff --git a/internal/db/bundb/account.go b/internal/db/bundb/account.go
@@ -25,39 +25,18 @@ import (
"strings"
"time"
- "codeberg.org/gruf/go-cache/v3/result"
"github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/log"
+ "github.com/superseriousbusiness/gotosocial/internal/state"
"github.com/uptrace/bun"
"github.com/uptrace/bun/dialect"
)
type accountDB struct {
- conn *DBConn
- cache *result.Cache[*gtsmodel.Account]
- emojis *emojiDB
- status *statusDB
-}
-
-func (a *accountDB) init() {
- // Initialize account result cache
- a.cache = result.NewSized([]result.Lookup{
- {Name: "ID"},
- {Name: "URI"},
- {Name: "URL"},
- {Name: "Username.Domain"},
- {Name: "PublicKeyURI"},
- }, func(a1 *gtsmodel.Account) *gtsmodel.Account {
- a2 := new(gtsmodel.Account)
- *a2 = *a1
- return a2
- }, 1000)
-
- // Set cache TTL and start sweep routine
- a.cache.SetTTL(time.Minute*5, false)
- a.cache.Start(time.Second * 10)
+ conn *DBConn
+ state *state.State
}
func (a *accountDB) newAccountQ(account *gtsmodel.Account) *bun.SelectQuery {
@@ -152,7 +131,7 @@ func (a *accountDB) GetInstanceAccount(ctx context.Context, domain string) (*gts
func (a *accountDB) getAccount(ctx context.Context, lookup string, dbQuery func(*gtsmodel.Account) error, keyParts ...any) (*gtsmodel.Account, db.Error) {
// Fetch account from database cache with loader callback
- account, err := a.cache.Load(lookup, func() (*gtsmodel.Account, error) {
+ account, err := a.state.Caches.GTS.Account().Load(lookup, func() (*gtsmodel.Account, error) {
var account gtsmodel.Account
// Not cached! Perform database query
@@ -168,7 +147,7 @@ func (a *accountDB) getAccount(ctx context.Context, lookup string, dbQuery func(
if len(account.EmojiIDs) > 0 {
// Set the account's related emojis
- account.Emojis, err = a.emojis.emojisFromIDs(ctx, account.EmojiIDs)
+ account.Emojis, err = a.state.DB.GetEmojisByIDs(ctx, account.EmojiIDs)
if err != nil {
return nil, fmt.Errorf("error getting account emojis: %w", err)
}
@@ -178,7 +157,7 @@ func (a *accountDB) getAccount(ctx context.Context, lookup string, dbQuery func(
}
func (a *accountDB) PutAccount(ctx context.Context, account *gtsmodel.Account) db.Error {
- return a.cache.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.
//
@@ -204,7 +183,7 @@ func (a *accountDB) UpdateAccount(ctx context.Context, account *gtsmodel.Account
// Update the account's last-updated
account.UpdatedAt = time.Now()
- return a.cache.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.
//
@@ -263,7 +242,7 @@ func (a *accountDB) DeleteAccount(ctx context.Context, id string) db.Error {
return err
}
- a.cache.Invalidate("ID", id)
+ a.state.Caches.GTS.Account().Invalidate("ID", id)
return nil
}
@@ -514,7 +493,7 @@ func (a *accountDB) statusesFromIDs(ctx context.Context, statusIDs []string) ([]
for _, id := range statusIDs {
// Fetch from status from database by ID
- status, err := a.status.GetStatusByID(ctx, id)
+ status, err := a.state.DB.GetStatusByID(ctx, id)
if err != nil {
log.Errorf("statusesFromIDs: error getting status %q: %v", id, err)
continue
diff --git a/internal/db/bundb/admin.go b/internal/db/bundb/admin.go
@@ -34,6 +34,7 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/id"
"github.com/superseriousbusiness/gotosocial/internal/log"
+ "github.com/superseriousbusiness/gotosocial/internal/state"
"github.com/superseriousbusiness/gotosocial/internal/uris"
"github.com/uptrace/bun"
"golang.org/x/crypto/bcrypt"
@@ -43,9 +44,8 @@ import (
const rsaKeyBits = 2048
type adminDB struct {
- conn *DBConn
- accounts *accountDB
- users *userDB
+ conn *DBConn
+ state *state.State
}
func (a *adminDB) IsUsernameAvailable(ctx context.Context, username string) (bool, db.Error) {
@@ -139,7 +139,7 @@ func (a *adminDB) NewSignup(ctx context.Context, username string, reason string,
}
// insert the new account!
- if err := a.accounts.PutAccount(ctx, acct); err != nil {
+ if err := a.state.DB.PutAccount(ctx, acct); err != nil {
return nil, err
}
}
@@ -185,7 +185,7 @@ func (a *adminDB) NewSignup(ctx context.Context, username string, reason string,
}
// insert the user!
- if err := a.users.PutUser(ctx, u); err != nil {
+ if err := a.state.DB.PutUser(ctx, u); err != nil {
return nil, err
}
@@ -241,7 +241,7 @@ func (a *adminDB) CreateInstanceAccount(ctx context.Context) db.Error {
}
// insert the new account!
- if err := a.accounts.PutAccount(ctx, acct); err != nil {
+ if err := a.state.DB.PutAccount(ctx, acct); err != nil {
return err
}
diff --git a/internal/db/bundb/bundb.go b/internal/db/bundb/bundb.go
@@ -40,6 +40,7 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/id"
"github.com/superseriousbusiness/gotosocial/internal/log"
+ "github.com/superseriousbusiness/gotosocial/internal/state"
"github.com/uptrace/bun"
"github.com/uptrace/bun/dialect/pgdialect"
"github.com/uptrace/bun/dialect/sqlitedialect"
@@ -122,7 +123,7 @@ func doMigration(ctx context.Context, db *bun.DB) error {
// NewBunDBService returns a bunDB derived from the provided config, which implements the go-fed DB interface.
// Under the hood, it uses https://github.com/uptrace/bun to create and maintain a database connection.
-func NewBunDBService(ctx context.Context) (db.DB, error) {
+func NewBunDBService(ctx context.Context, state *state.State) (db.DB, error) {
var conn *DBConn
var err error
dbType := strings.ToLower(config.GetDbType())
@@ -158,69 +159,64 @@ func NewBunDBService(ctx context.Context) (db.DB, error) {
return nil, fmt.Errorf("db migration error: %s", err)
}
- // Create DB structs that require ptrs to each other
- account := &accountDB{conn: conn}
- admin := &adminDB{conn: conn}
- domain := &domainDB{conn: conn}
- mention := &mentionDB{conn: conn}
- notif := ¬ificationDB{conn: conn}
- status := &statusDB{conn: conn}
- emoji := &emojiDB{conn: conn}
- relationship := &relationshipDB{conn: conn}
- timeline := &timelineDB{conn: conn}
- tombstone := &tombstoneDB{conn: conn}
- user := &userDB{conn: conn}
-
- // Setup DB cross-referencing
- account.emojis = emoji
- account.status = status
- admin.users = user
- relationship.accounts = account
- status.accounts = account
- status.emojis = emoji
- status.mentions = mention
- timeline.status = status
-
- // Initialize db structs
- account.init()
- domain.init()
- emoji.init()
- mention.init()
- notif.init()
- relationship.init()
- status.init()
- tombstone.init()
- user.init()
-
ps := &DBService{
- Account: account,
+ Account: &accountDB{
+ conn: conn,
+ state: state,
+ },
Admin: &adminDB{
- conn: conn,
- accounts: account,
- users: user,
+ conn: conn,
+ state: state,
},
Basic: &basicDB{
conn: conn,
},
- Domain: domain,
- Emoji: emoji,
+ Domain: &domainDB{
+ conn: conn,
+ state: state,
+ },
+ Emoji: &emojiDB{
+ conn: conn,
+ state: state,
+ },
Instance: &instanceDB{
conn: conn,
},
Media: &mediaDB{
conn: conn,
},
- Mention: mention,
- Notification: notif,
- Relationship: relationship,
+ Mention: &mentionDB{
+ conn: conn,
+ state: state,
+ },
+ Notification: ¬ificationDB{
+ conn: conn,
+ state: state,
+ },
+ Relationship: &relationshipDB{
+ conn: conn,
+ state: state,
+ },
Session: &sessionDB{
conn: conn,
},
- Status: status,
- Timeline: timeline,
- User: user,
- Tombstone: tombstone,
- conn: conn,
+ Status: &statusDB{
+ conn: conn,
+ state: state,
+ },
+ Timeline: &timelineDB{
+ conn: conn,
+ state: state,
+ },
+ User: &userDB{
+ conn: conn,
+ state: state,
+ },
+ Tombstone: &tombstoneDB{
+ conn: conn,
+ state: state,
+ },
+ conn: conn,
}
// we can confidently return this useable service now
diff --git a/internal/db/bundb/bundbnew_test.go b/internal/db/bundb/bundbnew_test.go
@@ -33,7 +33,7 @@ type BundbNewTestSuite struct {
func (suite *BundbNewTestSuite) TestCreateNewDB() {
// create a new db with standard test settings
- db, err := bundb.NewBunDBService(context.Background())
+ db, err := bundb.NewBunDBService(context.Background(), nil)
suite.NoError(err)
suite.NotNil(db)
}
@@ -42,7 +42,7 @@ func (suite *BundbNewTestSuite) TestCreateNewSqliteDBNoAddress() {
// create a new db with no address specified
config.SetDbAddress("")
config.SetDbType("sqlite")
- db, err := bundb.NewBunDBService(context.Background())
+ db, err := bundb.NewBunDBService(context.Background(), nil)
suite.EqualError(err, "'db-address' was not set when attempting to start sqlite")
suite.Nil(db)
}
diff --git a/internal/db/bundb/domain.go b/internal/db/bundb/domain.go
@@ -22,34 +22,18 @@ import (
"context"
"net/url"
"strings"
- "time"
- "codeberg.org/gruf/go-cache/v3/result"
"github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
+ "github.com/superseriousbusiness/gotosocial/internal/state"
"github.com/uptrace/bun"
"golang.org/x/net/idna"
)
type domainDB struct {
conn *DBConn
- cache *result.Cache[*gtsmodel.DomainBlock]
-}
-
-func (d *domainDB) init() {
- // Initialize domain block result cache
- d.cache = result.NewSized([]result.Lookup{
- {Name: "Domain"},
- }, func(d1 *gtsmodel.DomainBlock) *gtsmodel.DomainBlock {
- d2 := new(gtsmodel.DomainBlock)
- *d2 = *d1
- return d2
- }, 1000)
-
- // Set cache TTL and start sweep routine
- d.cache.SetTTL(time.Minute*5, false)
- d.cache.Start(time.Second * 10)
+ state *state.State
}
// normalizeDomain converts the given domain to lowercase
@@ -71,7 +55,7 @@ func (d *domainDB) CreateDomainBlock(ctx context.Context, block *gtsmodel.Domain
return err
}
- return d.cache.Store(block, func() error {
+ return d.state.Caches.GTS.DomainBlock().Store(block, func() error {
_, err := d.conn.NewInsert().
Model(block).
Exec(ctx)
@@ -87,7 +71,7 @@ func (d *domainDB) GetDomainBlock(ctx context.Context, domain string) (*gtsmodel
return nil, err
}
- return d.cache.Load("Domain", func() (*gtsmodel.DomainBlock, error) {
+ return d.state.Caches.GTS.DomainBlock().Load("Domain", func() (*gtsmodel.DomainBlock, error) {
// Check for easy case, domain referencing *us*
if domain == "" || domain == config.GetAccountDomain() {
return nil, db.ErrNoEntries
@@ -125,7 +109,7 @@ func (d *domainDB) DeleteDomainBlock(ctx context.Context, domain string) db.Erro
}
// Clear domain from cache
- d.cache.Invalidate("Domain", domain)
+ d.state.Caches.GTS.DomainBlock().Invalidate(domain)
return nil
}
diff --git a/internal/db/bundb/emoji.go b/internal/db/bundb/emoji.go
@@ -23,50 +23,17 @@ import (
"strings"
"time"
- "codeberg.org/gruf/go-cache/v3/result"
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/log"
+ "github.com/superseriousbusiness/gotosocial/internal/state"
"github.com/uptrace/bun"
"github.com/uptrace/bun/dialect"
)
type emojiDB struct {
- conn *DBConn
- emojiCache *result.Cache[*gtsmodel.Emoji]
- categoryCache *result.Cache[*gtsmodel.EmojiCategory]
-}
-
-func (e *emojiDB) init() {
- // Initialize emoji result cache
- e.emojiCache = result.NewSized([]result.Lookup{
- {Name: "ID"},
- {Name: "URI"},
- {Name: "Shortcode.Domain"},
- {Name: "ImageStaticURL"},
- }, func(e1 *gtsmodel.Emoji) *gtsmodel.Emoji {
- e2 := new(gtsmodel.Emoji)
- *e2 = *e1
- return e2
- }, 1000)
-
- // Set cache TTL and start sweep routine
- e.emojiCache.SetTTL(time.Minute*5, false)
- e.emojiCache.Start(time.Second * 10)
-
- // Initialize category result cache
- e.categoryCache = result.NewSized([]result.Lookup{
- {Name: "ID"},
- {Name: "Name"},
- }, func(c1 *gtsmodel.EmojiCategory) *gtsmodel.EmojiCategory {
- c2 := new(gtsmodel.EmojiCategory)
- *c2 = *c1
- return c2
- }, 1000)
-
- // Set cache TTL and start sweep routine
- e.categoryCache.SetTTL(time.Minute*5, false)
- e.categoryCache.Start(time.Second * 10)
+ conn *DBConn
+ state *state.State
}
func (e *emojiDB) newEmojiQ(emoji *gtsmodel.Emoji) *bun.SelectQuery {
@@ -83,7 +50,7 @@ func (e *emojiDB) newEmojiCategoryQ(emojiCategory *gtsmodel.EmojiCategory) *bun.
}
func (e *emojiDB) PutEmoji(ctx context.Context, emoji *gtsmodel.Emoji) db.Error {
- return e.emojiCache.Store(emoji, func() error {
+ return e.state.Caches.GTS.Emoji().Store(emoji, func() error {
_, err := e.conn.NewInsert().Model(emoji).Exec(ctx)
return e.conn.ProcessError(err)
})
@@ -102,7 +69,7 @@ func (e *emojiDB) UpdateEmoji(ctx context.Context, emoji *gtsmodel.Emoji, column
return nil, e.conn.ProcessError(err)
}
- e.emojiCache.Invalidate("ID", emoji.ID)
+ e.state.Caches.GTS.Emoji().Invalidate("ID", emoji.ID)
return emoji, nil
}
@@ -139,7 +106,7 @@ func (e *emojiDB) DeleteEmojiByID(ctx context.Context, id string) db.Error {
return err
}
- e.emojiCache.Invalidate("ID", id)
+ e.state.Caches.GTS.Emoji().Invalidate("ID", id)
return nil
}
@@ -257,7 +224,7 @@ func (e *emojiDB) GetEmojis(ctx context.Context, domain string, includeDisabled
}
}
- return e.emojisFromIDs(ctx, emojiIDs)
+ return e.GetEmojisByIDs(ctx, emojiIDs)
}
func (e *emojiDB) GetUseableEmojis(ctx context.Context) ([]*gtsmodel.Emoji, db.Error) {
@@ -276,7 +243,7 @@ func (e *emojiDB) GetUseableEmojis(ctx context.Context) ([]*gtsmodel.Emoji, db.E
return nil, e.conn.ProcessError(err)
}
- return e.emojisFromIDs(ctx, emojiIDs)
+ return e.GetEmojisByIDs(ctx, emojiIDs)
}
func (e *emojiDB) GetEmojiByID(ctx context.Context, id string) (*gtsmodel.Emoji, db.Error) {
@@ -338,7 +305,7 @@ func (e *emojiDB) GetEmojiByStaticURL(ctx context.Context, imageStaticURL string
}
func (e *emojiDB) PutEmojiCategory(ctx context.Context, emojiCategory *gtsmodel.EmojiCategory) db.Error {
- return e.categoryCache.Store(emojiCategory, func() error {
+ return e.state.Caches.GTS.EmojiCategory().Store(emojiCategory, func() error {
_, err := e.conn.NewInsert().Model(emojiCategory).Exec(ctx)
return e.conn.ProcessError(err)
})
@@ -357,7 +324,7 @@ func (e *emojiDB) GetEmojiCategories(ctx context.Context) ([]*gtsmodel.EmojiCate
return nil, e.conn.ProcessError(err)
}
- return e.emojiCategoriesFromIDs(ctx, emojiCategoryIDs)
+ return e.GetEmojiCategoriesByIDs(ctx, emojiCategoryIDs)
}
func (e *emojiDB) GetEmojiCategory(ctx context.Context, id string) (*gtsmodel.EmojiCategory, db.Error) {
@@ -383,7 +350,7 @@ func (e *emojiDB) GetEmojiCategoryByName(ctx context.Context, name string) (*gts
}
func (e *emojiDB) getEmoji(ctx context.Context, lookup string, dbQuery func(*gtsmodel.Emoji) error, keyParts ...any) (*gtsmodel.Emoji, db.Error) {
- return e.emojiCache.Load(lookup, func() (*gtsmodel.Emoji, error) {
+ return e.state.Caches.GTS.Emoji().Load(lookup, func() (*gtsmodel.Emoji, error) {
var emoji gtsmodel.Emoji
// Not cached! Perform database query
@@ -395,8 +362,7 @@ func (e *emojiDB) getEmoji(ctx context.Context, lookup string, dbQuery func(*gts
}, keyParts...)
}
-func (e *emojiDB) emojisFromIDs(ctx context.Context, emojiIDs []string) ([]*gtsmodel.Emoji, db.Error) {
- // Catch case of no emojis early
+func (e *emojiDB) GetEmojisByIDs(ctx context.Context, emojiIDs []string) ([]*gtsmodel.Emoji, db.Error) {
if len(emojiIDs) == 0 {
return nil, db.ErrNoEntries
}
@@ -417,7 +383,7 @@ func (e *emojiDB) emojisFromIDs(ctx context.Context, emojiIDs []string) ([]*gtsm
}
func (e *emojiDB) getEmojiCategory(ctx context.Context, lookup string, dbQuery func(*gtsmodel.EmojiCategory) error, keyParts ...any) (*gtsmodel.EmojiCategory, db.Error) {
- return e.categoryCache.Load(lookup, func() (*gtsmodel.EmojiCategory, error) {
+ return e.state.Caches.GTS.EmojiCategory().Load(lookup, func() (*gtsmodel.EmojiCategory, error) {
var category gtsmodel.EmojiCategory
// Not cached! Perform database query
@@ -429,8 +395,7 @@ func (e *emojiDB) getEmojiCategory(ctx context.Context, lookup string, dbQuery f
}, keyParts...)
}
-func (e *emojiDB) emojiCategoriesFromIDs(ctx context.Context, emojiCategoryIDs []string) ([]*gtsmodel.EmojiCategory, db.Error) {
- // Catch case of no emoji categories early
+func (e *emojiDB) GetEmojiCategoriesByIDs(ctx context.Context, emojiCategoryIDs []string) ([]*gtsmodel.EmojiCategory, db.Error) {
if len(emojiCategoryIDs) == 0 {
return nil, db.ErrNoEntries
}
diff --git a/internal/db/bundb/mention.go b/internal/db/bundb/mention.go
@@ -20,33 +20,17 @@ package bundb
import (
"context"
- "time"
- "codeberg.org/gruf/go-cache/v3/result"
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/log"
+ "github.com/superseriousbusiness/gotosocial/internal/state"
"github.com/uptrace/bun"
)
type mentionDB struct {
conn *DBConn
- cache *result.Cache[*gtsmodel.Mention]
-}
-
-func (m *mentionDB) init() {
- // Initialize notification result cache
- m.cache = result.NewSized([]result.Lookup{
- {Name: "ID"},
- }, func(m1 *gtsmodel.Mention) *gtsmodel.Mention {
- m2 := new(gtsmodel.Mention)
- *m2 = *m1
- return m2
- }, 1000)
-
- // Set cache TTL and start sweep routine
- m.cache.SetTTL(time.Minute*5, false)
- m.cache.Start(time.Second * 10)
+ state *state.State
}
func (m *mentionDB) newMentionQ(i interface{}) *bun.SelectQuery {
@@ -59,7 +43,7 @@ func (m *mentionDB) newMentionQ(i interface{}) *bun.SelectQuery {
}
func (m *mentionDB) GetMention(ctx context.Context, id string) (*gtsmodel.Mention, db.Error) {
- return m.cache.Load("ID", func() (*gtsmodel.Mention, error) {
+ return m.state.Caches.GTS.Mention().Load("ID", func() (*gtsmodel.Mention, error) {
var mention gtsmodel.Mention
q := m.newMentionQ(&mention).
diff --git a/internal/db/bundb/notification.go b/internal/db/bundb/notification.go
@@ -20,37 +20,21 @@ package bundb
import (
"context"
- "time"
- "codeberg.org/gruf/go-cache/v3/result"
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/log"
+ "github.com/superseriousbusiness/gotosocial/internal/state"
"github.com/uptrace/bun"
)
type notificationDB struct {
conn *DBConn
- cache *result.Cache[*gtsmodel.Notification]
-}
-
-func (n *notificationDB) init() {
- // Initialize notification result cache
- n.cache = result.NewSized([]result.Lookup{
- {Name: "ID"},
- }, func(n1 *gtsmodel.Notification) *gtsmodel.Notification {
- n2 := new(gtsmodel.Notification)
- *n2 = *n1
- return n2
- }, 1000)
-
- // Set cache TTL and start sweep routine
- n.cache.SetTTL(time.Minute*5, false)
- n.cache.Start(time.Second * 10)
+ state *state.State
}
func (n *notificationDB) GetNotification(ctx context.Context, id string) (*gtsmodel.Notification, db.Error) {
- return n.cache.Load("ID", func() (*gtsmodel.Notification, error) {
+ return n.state.Caches.GTS.Notification().Load("ID", func() (*gtsmodel.Notification, error) {
var notif gtsmodel.Notification
q := n.conn.NewSelect().
@@ -130,6 +114,6 @@ func (n *notificationDB) ClearNotifications(ctx context.Context, accountID strin
return n.conn.ProcessError(err)
}
- n.cache.Clear()
+ n.state.Caches.GTS.Notification().Clear()
return nil
}
diff --git a/internal/db/bundb/relationship.go b/internal/db/bundb/relationship.go
@@ -23,35 +23,16 @@ import (
"database/sql"
"errors"
"fmt"
- "time"
- "codeberg.org/gruf/go-cache/v3/result"
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
+ "github.com/superseriousbusiness/gotosocial/internal/state"
"github.com/uptrace/bun"
)
type relationshipDB struct {
- conn *DBConn
- accounts *accountDB
- blockCache *result.Cache[*gtsmodel.Block]
-}
-
-func (r *relationshipDB) init() {
- // Initialize block result cache
- r.blockCache = result.NewSized([]result.Lookup{
- {Name: "ID"},
- {Name: "AccountID.TargetAccountID"},
- {Name: "URI"},
- }, func(b1 *gtsmodel.Block) *gtsmodel.Block {
- b2 := new(gtsmodel.Block)
- *b2 = *b1
- return b2
- }, 1000)
-
- // Set cache TTL and start sweep routine
- r.blockCache.SetTTL(time.Minute*5, false)
- r.blockCache.Start(time.Second * 10)
+ conn *DBConn
+ state *state.State
}
func (r *relationshipDB) newFollowQ(follow interface{}) *bun.SelectQuery {
@@ -94,13 +75,13 @@ func (r *relationshipDB) GetBlock(ctx context.Context, account1 string, account2
}
// Set the block originating account
- block.Account, err = r.accounts.GetAccountByID(ctx, block.AccountID)
+ block.Account, err = r.state.DB.GetAccountByID(ctx, block.AccountID)
if err != nil {
return nil, err
}
// Set the block target account
- block.TargetAccount, err = r.accounts.GetAccountByID(ctx, block.TargetAccountID)
+ block.TargetAccount, err = r.state.DB.GetAccountByID(ctx, block.TargetAccountID)
if err != nil {
return nil, err
}
@@ -109,7 +90,7 @@ func (r *relationshipDB) GetBlock(ctx context.Context, account1 string, account2
}
func (r *relationshipDB) getBlock(ctx context.Context, account1 string, account2 string) (*gtsmodel.Block, db.Error) {
- return r.blockCache.Load("AccountID.TargetAccountID", func() (*gtsmodel.Block, error) {
+ return r.state.Caches.GTS.Block().Load("AccountID.TargetAccountID", func() (*gtsmodel.Block, error) {
var block gtsmodel.Block
q := r.conn.NewSelect().Model(&block).
@@ -124,7 +105,7 @@ func (r *relationshipDB) getBlock(ctx context.Context, account1 string, account2
}
func (r *relationshipDB) PutBlock(ctx context.Context, block *gtsmodel.Block) db.Error {
- return r.blockCache.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)
})
@@ -140,7 +121,7 @@ func (r *relationshipDB) DeleteBlockByID(ctx context.Context, id string) db.Erro
}
// Drop any old value from cache by this ID
- r.blockCache.Invalidate("ID", id)
+ r.state.Caches.GTS.Block().Invalidate("ID", id)
return nil
}
@@ -154,7 +135,7 @@ func (r *relationshipDB) DeleteBlockByURI(ctx context.Context, uri string) db.Er
}
// Drop any old value from cache by this URI
- r.blockCache.Invalidate("URI", uri)
+ r.state.Caches.GTS.Block().Invalidate("URI", uri)
return nil
}
diff --git a/internal/db/bundb/status.go b/internal/db/bundb/status.go
@@ -26,36 +26,16 @@ import (
"fmt"
"time"
- "codeberg.org/gruf/go-cache/v3/result"
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/log"
+ "github.com/superseriousbusiness/gotosocial/internal/state"
"github.com/uptrace/bun"
)
type statusDB struct {
- conn *DBConn
- cache *result.Cache[*gtsmodel.Status]
- accounts *accountDB
- emojis *emojiDB
- mentions *mentionDB
-}
-
-func (s *statusDB) init() {
- // Initialize status result cache
- s.cache = result.NewSized([]result.Lookup{
- {Name: "ID"},
- {Name: "URI"},
- {Name: "URL"},
- }, func(s1 *gtsmodel.Status) *gtsmodel.Status {
- s2 := new(gtsmodel.Status)
- *s2 = *s1
- return s2
- }, 1000)
-
- // Set cache TTL and start sweep routine
- s.cache.SetTTL(time.Minute*5, false)
- s.cache.Start(time.Second * 10)
+ conn *DBConn
+ state *state.State
}
func (s *statusDB) newStatusQ(status interface{}) *bun.SelectQuery {
@@ -111,7 +91,7 @@ func (s *statusDB) GetStatusByURL(ctx context.Context, url string) (*gtsmodel.St
func (s *statusDB) getStatus(ctx context.Context, lookup string, dbQuery func(*gtsmodel.Status) error, keyParts ...any) (*gtsmodel.Status, db.Error) {
// Fetch status from database cache with loader callback
- status, err := s.cache.Load(lookup, func() (*gtsmodel.Status, error) {
+ status, err := s.state.Caches.GTS.Status().Load(lookup, func() (*gtsmodel.Status, error) {
var status gtsmodel.Status
// Not cached! Perform database query
@@ -149,14 +129,14 @@ func (s *statusDB) getStatus(ctx context.Context, lookup string, dbQuery func(*g
}
// Set the status author account
- status.Account, err = s.accounts.GetAccountByID(ctx, status.AccountID)
+ status.Account, err = s.state.DB.GetAccountByID(ctx, status.AccountID)
if err != nil {
return nil, fmt.Errorf("error getting status account: %w", err)
}
if id := status.BoostOfAccountID; id != "" {
// Set boost of status' author account
- status.BoostOfAccount, err = s.accounts.GetAccountByID(ctx, id)
+ status.BoostOfAccount, err = s.state.DB.GetAccountByID(ctx, id)
if err != nil {
return nil, fmt.Errorf("error getting boosted status account: %w", err)
}
@@ -164,7 +144,7 @@ func (s *statusDB) getStatus(ctx context.Context, lookup string, dbQuery func(*g
if id := status.InReplyToAccountID; id != "" {
// Set in-reply-to status' author account
- status.InReplyToAccount, err = s.accounts.GetAccountByID(ctx, id)
+ status.InReplyToAccount, err = s.state.DB.GetAccountByID(ctx, id)
if err != nil {
return nil, fmt.Errorf("error getting in reply to status account: %w", err)
}
@@ -172,7 +152,7 @@ func (s *statusDB) getStatus(ctx context.Context, lookup string, dbQuery func(*g
if len(status.EmojiIDs) > 0 {
// Fetch status emojis
- status.Emojis, err = s.emojis.emojisFromIDs(ctx, status.EmojiIDs)
+ status.Emojis, err = s.state.DB.GetEmojisByIDs(ctx, status.EmojiIDs)
if err != nil {
return nil, fmt.Errorf("error getting status emojis: %w", err)
}
@@ -180,7 +160,7 @@ func (s *statusDB) getStatus(ctx context.Context, lookup string, dbQuery func(*g
if len(status.MentionIDs) > 0 {
// Fetch status mentions
- status.Mentions, err = s.mentions.GetMentions(ctx, status.MentionIDs)
+ status.Mentions, err = s.state.DB.GetMentions(ctx, status.MentionIDs)
if err != nil {
return nil, fmt.Errorf("error getting status mentions: %w", err)
}
@@ -190,7 +170,7 @@ func (s *statusDB) getStatus(ctx context.Context, lookup string, dbQuery func(*g
}
func (s *statusDB) PutStatus(ctx context.Context, status *gtsmodel.Status) db.Error {
- return s.cache.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.
//
@@ -308,7 +288,7 @@ func (s *statusDB) UpdateStatus(ctx context.Context, status *gtsmodel.Status) db
}
// Drop any old value from cache by this ID
- s.cache.Invalidate("ID", status.ID)
+ s.state.Caches.GTS.Status().Invalidate("ID", status.ID)
return nil
}
@@ -347,7 +327,7 @@ func (s *statusDB) DeleteStatusByID(ctx context.Context, id string) db.Error {
}
// Drop any old value from cache by this ID
- s.cache.Invalidate("ID", id)
+ s.state.Caches.GTS.Status().Invalidate("ID", id)
return nil
}
diff --git a/internal/db/bundb/timeline.go b/internal/db/bundb/timeline.go
@@ -26,13 +26,14 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/id"
"github.com/superseriousbusiness/gotosocial/internal/log"
+ "github.com/superseriousbusiness/gotosocial/internal/state"
"github.com/uptrace/bun"
"golang.org/x/exp/slices"
)
type timelineDB struct {
- conn *DBConn
- status *statusDB
+ conn *DBConn
+ state *state.State
}
func (t *timelineDB) GetHomeTimeline(ctx context.Context, accountID string, maxID string, sinceID string, minID string, limit int, local bool) ([]*gtsmodel.Status, db.Error) {
@@ -111,7 +112,7 @@ func (t *timelineDB) GetHomeTimeline(ctx context.Context, accountID string, maxI
for _, id := range statusIDs {
// Fetch status from db for ID
- status, err := t.status.GetStatusByID(ctx, id)
+ status, err := t.state.DB.GetStatusByID(ctx, id)
if err != nil {
log.Errorf("GetHomeTimeline: error fetching status %q: %v", id, err)
continue
@@ -179,7 +180,7 @@ func (t *timelineDB) GetPublicTimeline(ctx context.Context, maxID string, sinceI
for _, id := range statusIDs {
// Fetch status from db for ID
- status, err := t.status.GetStatusByID(ctx, id)
+ status, err := t.state.DB.GetStatusByID(ctx, id)
if err != nil {
log.Errorf("GetPublicTimeline: error fetching status %q: %v", id, err)
continue
@@ -239,7 +240,7 @@ func (t *timelineDB) GetFavedTimeline(ctx context.Context, accountID string, max
for _, fave := range faves {
// Fetch status from db for corresponding favourite
- status, err := t.status.GetStatusByID(ctx, fave.StatusID)
+ status, err := t.state.DB.GetStatusByID(ctx, fave.StatusID)
if err != nil {
log.Errorf("GetFavedTimeline: error fetching status for fave %q: %v", fave.ID, err)
continue
diff --git a/internal/db/bundb/tombstone.go b/internal/db/bundb/tombstone.go
@@ -20,38 +20,20 @@ package bundb
import (
"context"
- "time"
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
+ "github.com/superseriousbusiness/gotosocial/internal/state"
"github.com/uptrace/bun"
-
- "codeberg.org/gruf/go-cache/v3/result"
)
type tombstoneDB struct {
conn *DBConn
- cache *result.Cache[*gtsmodel.Tombstone]
-}
-
-func (t *tombstoneDB) init() {
- // Initialize tombstone result cache
- t.cache = result.NewSized([]result.Lookup{
- {Name: "ID"},
- {Name: "URI"},
- }, func(t1 *gtsmodel.Tombstone) *gtsmodel.Tombstone {
- t2 := new(gtsmodel.Tombstone)
- *t2 = *t1
- return t2
- }, 100)
-
- // Set cache TTL and start sweep routine
- t.cache.SetTTL(time.Minute*5, false)
- t.cache.Start(time.Second * 10)
+ state *state.State
}
func (t *tombstoneDB) GetTombstoneByURI(ctx context.Context, uri string) (*gtsmodel.Tombstone, db.Error) {
- return t.cache.Load("URI", func() (*gtsmodel.Tombstone, error) {
+ return t.state.Caches.GTS.Tombstone().Load("URI", func() (*gtsmodel.Tombstone, error) {
var tomb gtsmodel.Tombstone
q := t.conn.
@@ -76,7 +58,7 @@ func (t *tombstoneDB) TombstoneExistsWithURI(ctx context.Context, uri string) (b
}
func (t *tombstoneDB) PutTombstone(ctx context.Context, tombstone *gtsmodel.Tombstone) db.Error {
- return t.cache.Store(tombstone, func() error {
+ return t.state.Caches.GTS.Tombstone().Store(tombstone, func() error {
_, err := t.conn.
NewInsert().
Model(tombstone).
@@ -95,7 +77,7 @@ func (t *tombstoneDB) DeleteTombstone(ctx context.Context, id string) db.Error {
}
// Invalidate from cache by ID
- t.cache.Invalidate("ID", id)
+ t.state.Caches.GTS.Tombstone().Invalidate("ID", id)
return nil
}
diff --git a/internal/db/bundb/user.go b/internal/db/bundb/user.go
@@ -22,38 +22,19 @@ import (
"context"
"time"
- "codeberg.org/gruf/go-cache/v3/result"
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
+ "github.com/superseriousbusiness/gotosocial/internal/state"
"github.com/uptrace/bun"
)
type userDB struct {
conn *DBConn
- cache *result.Cache[*gtsmodel.User]
-}
-
-func (u *userDB) init() {
- // Initialize user result cache
- u.cache = result.NewSized([]result.Lookup{
- {Name: "ID"},
- {Name: "AccountID"},
- {Name: "Email"},
- {Name: "ConfirmationToken"},
- {Name: "ExternalID"},
- }, func(u1 *gtsmodel.User) *gtsmodel.User {
- u2 := new(gtsmodel.User)
- *u2 = *u1
- return u2
- }, 1000)
-
- // Set cache TTL and start sweep routine
- u.cache.SetTTL(time.Minute*5, false)
- u.cache.Start(time.Second * 10)
+ state *state.State
}
func (u *userDB) GetUserByID(ctx context.Context, id string) (*gtsmodel.User, db.Error) {
- return u.cache.Load("ID", func() (*gtsmodel.User, error) {
+ return u.state.Caches.GTS.User().Load("ID", func() (*gtsmodel.User, error) {
var user gtsmodel.User
q := u.conn.
@@ -71,7 +52,7 @@ func (u *userDB) GetUserByID(ctx context.Context, id string) (*gtsmodel.User, db
}
func (u *userDB) GetUserByAccountID(ctx context.Context, accountID string) (*gtsmodel.User, db.Error) {
- return u.cache.Load("AccountID", func() (*gtsmodel.User, error) {
+ return u.state.Caches.GTS.User().Load("AccountID", func() (*gtsmodel.User, error) {
var user gtsmodel.User
q := u.conn.
@@ -89,7 +70,7 @@ func (u *userDB) GetUserByAccountID(ctx context.Context, accountID string) (*gts
}
func (u *userDB) GetUserByEmailAddress(ctx context.Context, emailAddress string) (*gtsmodel.User, db.Error) {
- return u.cache.Load("Email", func() (*gtsmodel.User, error) {
+ return u.state.Caches.GTS.User().Load("Email", func() (*gtsmodel.User, error) {
var user gtsmodel.User
q := u.conn.
@@ -105,9 +86,9 @@ func (u *userDB) GetUserByEmailAddress(ctx context.Context, emailAddress string)
return &user, nil
}, emailAddress)
}
-func (u *userDB) GetUserByExternalID(ctx context.Context, id string) (*gtsmodel.User, db.Error) {
- return u.cache.Load("ExternalID", func() (*gtsmodel.User, error) {
+func (u *userDB) GetUserByExternalID(ctx context.Context, id string) (*gtsmodel.User, db.Error) {
+ return u.state.Caches.GTS.User().Load("ExternalID", func() (*gtsmodel.User, error) {
var user gtsmodel.User
q := u.conn.
@@ -125,7 +106,7 @@ func (u *userDB) GetUserByExternalID(ctx context.Context, id string) (*gtsmodel.
}
func (u *userDB) GetUserByConfirmationToken(ctx context.Context, confirmationToken string) (*gtsmodel.User, db.Error) {
- return u.cache.Load("ConfirmationToken", func() (*gtsmodel.User, error) {
+ return u.state.Caches.GTS.User().Load("ConfirmationToken", func() (*gtsmodel.User, error) {
var user gtsmodel.User
q := u.conn.
@@ -143,7 +124,7 @@ func (u *userDB) GetUserByConfirmationToken(ctx context.Context, confirmationTok
}
func (u *userDB) PutUser(ctx context.Context, user *gtsmodel.User) db.Error {
- return u.cache.Store(user, func() error {
+ return u.state.Caches.GTS.User().Store(user, func() error {
_, err := u.conn.
NewInsert().
Model(user).
@@ -172,8 +153,8 @@ func (u *userDB) UpdateUser(ctx context.Context, user *gtsmodel.User, columns ..
return u.conn.ProcessError(err)
}
- // Invalidate in cache
- u.cache.Invalidate("ID", user.ID)
+ // Invalidate user from cache
+ u.state.Caches.GTS.User().Invalidate("ID", user.ID)
return nil
}
@@ -187,6 +168,6 @@ func (u *userDB) DeleteUserByID(ctx context.Context, userID string) db.Error {
}
// Invalidate user from cache
- u.cache.Invalidate("ID", userID)
+ u.state.Caches.GTS.User().Invalidate("ID", userID)
return nil
}
diff --git a/internal/db/emoji.go b/internal/db/emoji.go
@@ -37,6 +37,8 @@ type Emoji interface {
UpdateEmoji(ctx context.Context, emoji *gtsmodel.Emoji, columns ...string) (*gtsmodel.Emoji, Error)
// DeleteEmojiByID deletes one emoji by its database ID.
DeleteEmojiByID(ctx context.Context, id string) Error
+ // GetEmojisByIDs gets emojis for the given IDs.
+ GetEmojisByIDs(ctx context.Context, ids []string) ([]*gtsmodel.Emoji, Error)
// GetUseableEmojis gets all emojis which are useable by accounts on this instance.
GetUseableEmojis(ctx context.Context) ([]*gtsmodel.Emoji, Error)
// GetEmojis gets emojis based on given parameters. Useful for admin actions.
@@ -52,6 +54,8 @@ type Emoji interface {
GetEmojiByStaticURL(ctx context.Context, imageStaticURL string) (*gtsmodel.Emoji, Error)
// PutEmojiCategory puts one new emoji category in the database.
PutEmojiCategory(ctx context.Context, emojiCategory *gtsmodel.EmojiCategory) Error
+ // GetEmojiCategoriesByIDs gets emoji categories for given IDs.
+ GetEmojiCategoriesByIDs(ctx context.Context, ids []string) ([]*gtsmodel.EmojiCategory, Error)
// GetEmojiCategories gets a slice of the names of all existing emoji categories.
GetEmojiCategories(ctx context.Context) ([]*gtsmodel.EmojiCategory, Error)
// GetEmojiCategory gets one emoji category by its id.
diff --git a/internal/state/state.go b/internal/state/state.go
@@ -0,0 +1,49 @@
+/*
+ GoToSocial
+ Copyright (C) 2021-2022 GoToSocial Authors admin@gotosocial.org
+
+ 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 state
+
+import (
+ "github.com/superseriousbusiness/gotosocial/internal/cache"
+ "github.com/superseriousbusiness/gotosocial/internal/db"
+)
+
+// State provides a means of dependency injection and sharing of resources
+// across different subpackages of the GoToSocial codebase. DO NOT assume
+// that any particular field will be initialized if you are accessing this
+// during initialization. A pointer to a State{} is often passed during
+// subpackage initialization, while the returned subpackage type will later
+// then be set and stored within the State{} itself.
+type State struct {
+ // Caches provides access to this state's collection of caches.
+ Caches cache.Caches
+
+ // DB provides access to the database.
+ DB db.DB
+
+ // prevent pass-by-value.
+ _ nocopy
+}
+
+// nocopy when embedded will signal linter to
+// error on pass-by-value of parent struct.
+type nocopy struct{}
+
+func (*nocopy) Lock() {}
+
+func (*nocopy) Unlock() {}
diff --git a/internal/timeline/timeline.go b/internal/timeline/timeline.go
@@ -148,7 +148,8 @@ func NewTimeline(
grabFunction GrabFunction,
filterFunction FilterFunction,
prepareFunction PrepareFunction,
- skipInsertFunction SkipInsertFunction) (Timeline, error) {
+ skipInsertFunction SkipInsertFunction,
+) (Timeline, error) {
return &timeline{
indexedItems: &indexedItems{
skipInsert: skipInsertFunction,
diff --git a/internal/typeutils/internal.go b/internal/typeutils/internal.go
@@ -2,7 +2,6 @@ package typeutils
import (
"context"
- "fmt"
"time"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
@@ -13,7 +12,6 @@ import (
func (c *converter) FollowRequestToFollow(ctx context.Context, f *gtsmodel.FollowRequest) *gtsmodel.Follow {
showReblogs := *f.ShowReblogs
notify := *f.Notify
-
return >smodel.Follow{
ID: f.ID,
CreatedAt: f.CreatedAt,
@@ -33,8 +31,9 @@ func (c *converter) StatusToBoost(ctx context.Context, s *gtsmodel.Status, boost
if err != nil {
return nil, err
}
- boostWrapperStatusURI := fmt.Sprintf("%s/%s", accountURIs.StatusesURI, boostWrapperStatusID)
- boostWrapperStatusURL := fmt.Sprintf("%s/%s", accountURIs.StatusesURL, boostWrapperStatusID)
+
+ boostWrapperStatusURI := accountURIs.StatusesURI + "/" + boostWrapperStatusID
+ boostWrapperStatusURL := accountURIs.StatusesURL + "/" + boostWrapperStatusID
local := true
if boostingAccount.Domain != "" {
diff --git a/testrig/db.go b/testrig/db.go
@@ -28,6 +28,7 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/db/bundb"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/log"
+ "github.com/superseriousbusiness/gotosocial/internal/state"
)
var testModels = []interface{}{
@@ -92,10 +93,16 @@ func NewTestDB() db.DB {
})
}
- testDB, err := bundb.NewBunDBService(context.Background())
+ var state state.State
+ state.Caches.Init()
+
+ testDB, err := bundb.NewBunDBService(context.Background(), &state)
if err != nil {
log.Panic(err)
}
+
+ state.DB = testDB
+
return testDB
}