Add dedicated db cache table (#22650)
Some checks failed
Release / call-build-workflow (push) Has been cancelled
Release / Publish Docker :release (push) Has been cancelled
Release / Github & APT (push) Has been cancelled
Release / Demo (push) Has been cancelled
Release / Hassio Addon (push) Has been cancelled

This commit is contained in:
Michael Geers 2025-08-02 10:04:17 +02:00 • committed by GitHub
parent 31d01bccd3
commit a8c16f2853
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 205 additions and 14 deletions

52
cmd/cache-clear.go Normal file
View file

@ -0,0 +1,52 @@
package cmd
import (
"github.com/AlecAivazis/survey/v2"
"github.com/evcc-io/evcc/server/db/cache"
"github.com/spf13/cobra"
)
// cacheClearCmd represents the cache clear command
var cacheClearCmd = &cobra.Command{
Use: "clear",
Short: "Clear all cache entries",
Run: runCacheClear,
}
func init() {
cacheCmd.AddCommand(cacheClearCmd)
cacheClearCmd.Flags().BoolP(flagForce, "f", false, "Force (no confirmation)")
}
func runCacheClear(cmd *cobra.Command, args []string) {
// load config
if err := loadConfigFile(&conf, !cmd.Flag(flagIgnoreDatabase).Changed); err != nil {
log.FATAL.Fatal(err)
}
// setup persistence
if err := configureDatabase(conf.Database); err != nil {
log.FATAL.Fatal(err)
}
confirmation, _ := cmd.Flags().GetBool(flagForce)
if !confirmation {
prompt := &survey.Confirm{
Message: "Clear all cache entries",
}
if err := survey.AskOne(prompt, &confirmation); err != nil {
log.FATAL.Fatal(err)
}
}
if confirmation {
if err := cache.Clear(); err != nil {
log.FATAL.Fatal(err)
}
log.INFO.Println("Cache cleared successfully")
}
// wait for shutdown
<-shutdownDoneC()
}

53
cmd/cache-get.go Normal file
View file

@ -0,0 +1,53 @@
package cmd
import (
"fmt"
"regexp"
"github.com/evcc-io/evcc/server/db/cache"
"github.com/spf13/cobra"
)
// cacheGetCmd represents the cache get command
var cacheGetCmd = &cobra.Command{
Use: "get",
Short: "Get cache entries",
Run: runCacheGet,
Args: cobra.MaximumNArgs(1),
}
func init() {
cacheCmd.AddCommand(cacheGetCmd)
}
func runCacheGet(cmd *cobra.Command, args []string) {
// load config
if err := loadConfigFile(&conf, !cmd.Flag(flagIgnoreDatabase).Changed); err != nil {
log.FATAL.Fatal(err)
}
// setup persistence
if err := configureDatabase(conf.Database); err != nil {
log.FATAL.Fatal(err)
}
var re *regexp.Regexp
if len(args) > 0 {
re = regexp.MustCompile(args[0])
}
entries, err := cache.All()
if err != nil {
log.FATAL.Fatal(err)
}
for _, entry := range entries {
if re != nil && !re.MatchString(entry.Key) {
continue
}
fmt.Printf("%s:\n", entry.Key)
fmt.Println(entry.Value)
fmt.Println("---")
}
}

15
cmd/cache.go Normal file
View file

@ -0,0 +1,15 @@
package cmd
import (
"github.com/spf13/cobra"
)
// cacheCmd represents the cache command
var cacheCmd = &cobra.Command{
Use: "cache",
Short: "Manage cache entries",
}
func init() {
rootCmd.AddCommand(cacheCmd)
}

View file

@ -33,6 +33,7 @@ import (
"github.com/evcc-io/evcc/push" "github.com/evcc-io/evcc/push"
"github.com/evcc-io/evcc/server" "github.com/evcc-io/evcc/server"
"github.com/evcc-io/evcc/server/db" "github.com/evcc-io/evcc/server/db"
"github.com/evcc-io/evcc/server/db/cache"
"github.com/evcc-io/evcc/server/db/settings" "github.com/evcc-io/evcc/server/db/settings"
"github.com/evcc-io/evcc/server/eebus" "github.com/evcc-io/evcc/server/eebus"
"github.com/evcc-io/evcc/server/modbus" "github.com/evcc-io/evcc/server/modbus"
@ -588,6 +589,10 @@ func configureDatabase(conf globalconfig.DB) error {
return err return err
} }
if err := cache.Init(); err != nil {
return err
}
persistSettings := func() { persistSettings := func() {
if err := settings.Persist(); err != nil { if err := settings.Persist(); err != nil {
log.ERROR.Println("cannot save settings:", err) log.ERROR.Println("cannot save settings:", err)

View file

@ -457,7 +457,7 @@
"sessions": "Ladevorgänge", "sessions": "Ladevorgänge",
"sessionsDescription": "Löscht den Verlauf deiner Ladevorgänge.", "sessionsDescription": "Löscht den Verlauf deiner Ladevorgänge.",
"settings": "Konfiguration & Einstellungen", "settings": "Konfiguration & Einstellungen",
"settingsDescription": "Löscht alle konfigurierten Geräte, Dienste, Pläne, usw.", "settingsDescription": "Löscht alle konfigurierten Geräte, Dienste, Pläne, Caches, usw.",
"title": "Zurücksetzen" "title": "Zurücksetzen"
}, },
"restore": { "restore": {

View file

@ -457,7 +457,7 @@
"sessions": "Charging sessions", "sessions": "Charging sessions",
"sessionsDescription": "Deletes your charging session history.", "sessionsDescription": "Deletes your charging session history.",
"settings": "Configuration & settings", "settings": "Configuration & settings",
"settingsDescription": "Deletes all configured devices, services, plans, etc.", "settingsDescription": "Deletes all configured devices, services, plans, caches, etc.",
"title": "Reset" "title": "Reset"
}, },
"restore": { "restore": {

68
server/db/cache/cache.go vendored Normal file
View file

@ -0,0 +1,68 @@
package cache
import (
"cmp"
"encoding/json"
"errors"
"slices"
"github.com/evcc-io/evcc/server/db"
"gorm.io/gorm"
)
var ErrNotFound = errors.New("cache entry not found")
type Cache struct {
Key string `json:"key" gorm:"primarykey"`
Value string `json:"value"`
}
func Init() error {
return db.Instance.AutoMigrate(new(Cache))
}
func Put(key string, value interface{}) error {
data, err := json.Marshal(value)
if err != nil {
return err
}
return db.Instance.Save(&Cache{
Key: key,
Value: string(data),
}).Error
}
func Get(key string, result interface{}) error {
var cacheEntry Cache
err := db.Instance.First(&cacheEntry, "key = ?", key).Error
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return ErrNotFound
}
return err
}
return json.Unmarshal([]byte(cacheEntry.Value), result)
}
func All() ([]Cache, error) {
var entries []Cache
err := db.Instance.Find(&entries).Error
if err != nil {
return nil, err
}
// Sort by key for consistent output
slices.SortFunc(entries, func(i, j Cache) int {
return cmp.Compare(i.Key, j.Key)
})
return entries, nil
}
func Clear() error {
return db.Instance.Delete(&Cache{}, "1=1").Error
}

View file

@ -501,15 +501,13 @@ func resetDatabase(authObject auth.Auth, shutdown func()) http.HandlerFunc {
} }
if req.Settings { if req.Settings {
query := db.Instance.Exec("DELETE FROM settings") tables := []string{"settings", "configs", "caches", "meters"}
if query.Error != nil {
jsonError(w, http.StatusInternalServerError, query.Error) for _, table := range tables {
return if err := db.Instance.Exec("DELETE FROM " + table).Error; err != nil {
} jsonError(w, http.StatusInternalServerError, err)
query = db.Instance.Exec("DELETE FROM configs") return
if query.Error != nil { }
jsonError(w, http.StatusInternalServerError, query.Error)
return
} }
} }

View file

@ -5,7 +5,7 @@ import (
"fmt" "fmt"
"github.com/evcc-io/evcc/api" "github.com/evcc-io/evcc/api"
"github.com/evcc-io/evcc/server/db/settings" "github.com/evcc-io/evcc/server/db/cache"
) )
type cached struct { type cached struct {
@ -18,7 +18,7 @@ func cacheKey(typ string, other map[string]any) string {
} }
func cachePut(key string, typ api.TariffType, rates api.Rates) error { func cachePut(key string, typ api.TariffType, rates api.Rates) error {
return settings.SetJson(key, &cached{ return cache.Put(key, &cached{
Type: typ, Type: typ,
Rates: rates, Rates: rates,
}) })
@ -26,5 +26,5 @@ func cachePut(key string, typ api.TariffType, rates api.Rates) error {
func cacheGet(key string) (*cached, error) { func cacheGet(key string) (*cached, error) {
var res cached var res cached
return &res, settings.Json(key, &res) return &res, cache.Get(key, &res)
} }