snapshot of https://github.com/mattermost/mattermost.git (master, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Server CI / Postgres (shard 0) (push) Has been skipped
Server CI / Postgres (shard 3) (push) Has been skipped
BuildEnv Docker Image / build-image (push) Failing after 4s
BuildEnv Docker Image / build-image-fips (push) Failing after 4s
Migration Review & Release Notes / Generate migration review & release notes (push) Failing after 3s
Server CI / Build Environment (push) Failing after 3s
Server CI / Postgres (shard 1) (push) Has been skipped
Tools CI / check-style (mattermost-govet) (push) Failing after 4s
Server CI / Postgres (shard 2) (push) Has been skipped
Tools CI / check-style (mmgotool) (push) Failing after 3s
Server CI / Elasticsearch v8 Compatibility (push) Has been skipped
Server CI / OpenSearch v2 Compatibility (push) Has been skipped
Server CI / Postgres FIPS (shard 0) (push) Has been skipped
Tools CI / Test (mattermost-govet) (push) Failing after 3s
Server CI / Postgres FIPS (shard 1) (push) Has been skipped
Server CI / Postgres FIPS (shard 2) (push) Has been skipped
Tools CI / Test (mmgotool) (push) Failing after 3s
Server CI / Postgres FIPS (shard 3) (push) Has been skipped
Server CI / Run mmctl tests (push) Has been skipped
Server CI / Run mmctl tests (FIPS) (push) Has been skipped
Server CI / Merge Postgres Test Results (push) Has been skipped
Server CI / Merge Postgres FIPS Test Results (push) Has been skipped
Server CI / Check backport migrations (push) Has been skipped
Server CI / Check generated files (push) Has been skipped
Server CI / check-style (push) Has been skipped
Server CI / Vet API (push) Has been skipped
Server CI / Build mattermost server app (push) Has been skipped
Server CI / Server CI Complete (push) Failing after 2s
Server CI / Server CI Artifacts (push) Has been skipped
Server CI / Sentry Upload (push) Has been skipped
Server CI / Server CI Report (push) Failing after 5m1s
Server CI Nightly Race / Race Detector (push) Has been skipped
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Webapp CI Cache Warm / Warm Webapp node_modules Cache (push) Has been cancelled
E2E CI Cache Warm / Warm E2E npm Registry Cache (push) Has been cancelled
API / build (push) Has been cancelled
Web App CI / check-types (push) Has been cancelled
Web App CI / check-lint (push) Has been cancelled
Web App CI / check-i18n (push) Has been cancelled
Web App CI / check-external-links (push) Has been cancelled
Web App CI / test (platform) (push) Has been cancelled
Web App CI / test (mattermost-redux) (push) Has been cancelled
Web App CI / test (channels shard 1/4) (push) Has been cancelled
Web App CI / test (channels shard 2/4) (push) Has been cancelled
Web App CI / test (channels shard 3/4) (push) Has been cancelled
Web App CI / test (channels shard 4/4) (push) Has been cancelled
Web App CI / upload-coverage (push) Has been cancelled
Web App CI / build (push) Has been cancelled
YAML Lint / yamllint (push) Has been cancelled
Push mirrored docker images / cd/Push mirrored docker images (push) Has been cancelled
docs-cd / Build and deploy docs (push) Has been cancelled
Docs CI / build (push) Has been cancelled
E2E Cache Warm: Cypress deps / Warm Cypress deps cache (push) Has been cancelled
Scorecards supply-chain security / Scorecard analysis (push) Has been cancelled
Server CI Weekly / Postgres FIPS (push) Has been skipped
Server CI Weekly / Run mmctl tests (FIPS) (push) Has been skipped
Server CI Weekly / Postgres with binary parameters (push) Has been skipped
Server CI Weekly / Build Environment (push) Failing after 1s
Server CI Cache Warm / Warm Go Module and Build Cache (push) Failing after 2s
Server CI Nightly Race / Build Environment (push) Failing after 1s
Some checks failed
Server CI / Postgres (shard 0) (push) Has been skipped
Server CI / Postgres (shard 3) (push) Has been skipped
BuildEnv Docker Image / build-image (push) Failing after 4s
BuildEnv Docker Image / build-image-fips (push) Failing after 4s
Migration Review & Release Notes / Generate migration review & release notes (push) Failing after 3s
Server CI / Build Environment (push) Failing after 3s
Server CI / Postgres (shard 1) (push) Has been skipped
Tools CI / check-style (mattermost-govet) (push) Failing after 4s
Server CI / Postgres (shard 2) (push) Has been skipped
Tools CI / check-style (mmgotool) (push) Failing after 3s
Server CI / Elasticsearch v8 Compatibility (push) Has been skipped
Server CI / OpenSearch v2 Compatibility (push) Has been skipped
Server CI / Postgres FIPS (shard 0) (push) Has been skipped
Tools CI / Test (mattermost-govet) (push) Failing after 3s
Server CI / Postgres FIPS (shard 1) (push) Has been skipped
Server CI / Postgres FIPS (shard 2) (push) Has been skipped
Tools CI / Test (mmgotool) (push) Failing after 3s
Server CI / Postgres FIPS (shard 3) (push) Has been skipped
Server CI / Run mmctl tests (push) Has been skipped
Server CI / Run mmctl tests (FIPS) (push) Has been skipped
Server CI / Merge Postgres Test Results (push) Has been skipped
Server CI / Merge Postgres FIPS Test Results (push) Has been skipped
Server CI / Check backport migrations (push) Has been skipped
Server CI / Check generated files (push) Has been skipped
Server CI / check-style (push) Has been skipped
Server CI / Vet API (push) Has been skipped
Server CI / Build mattermost server app (push) Has been skipped
Server CI / Server CI Complete (push) Failing after 2s
Server CI / Server CI Artifacts (push) Has been skipped
Server CI / Sentry Upload (push) Has been skipped
Server CI / Server CI Report (push) Failing after 5m1s
Server CI Nightly Race / Race Detector (push) Has been skipped
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Webapp CI Cache Warm / Warm Webapp node_modules Cache (push) Has been cancelled
E2E CI Cache Warm / Warm E2E npm Registry Cache (push) Has been cancelled
API / build (push) Has been cancelled
Web App CI / check-types (push) Has been cancelled
Web App CI / check-lint (push) Has been cancelled
Web App CI / check-i18n (push) Has been cancelled
Web App CI / check-external-links (push) Has been cancelled
Web App CI / test (platform) (push) Has been cancelled
Web App CI / test (mattermost-redux) (push) Has been cancelled
Web App CI / test (channels shard 1/4) (push) Has been cancelled
Web App CI / test (channels shard 2/4) (push) Has been cancelled
Web App CI / test (channels shard 3/4) (push) Has been cancelled
Web App CI / test (channels shard 4/4) (push) Has been cancelled
Web App CI / upload-coverage (push) Has been cancelled
Web App CI / build (push) Has been cancelled
YAML Lint / yamllint (push) Has been cancelled
Push mirrored docker images / cd/Push mirrored docker images (push) Has been cancelled
docs-cd / Build and deploy docs (push) Has been cancelled
Docs CI / build (push) Has been cancelled
E2E Cache Warm: Cypress deps / Warm Cypress deps cache (push) Has been cancelled
Scorecards supply-chain security / Scorecard analysis (push) Has been cancelled
Server CI Weekly / Postgres FIPS (push) Has been skipped
Server CI Weekly / Run mmctl tests (FIPS) (push) Has been skipped
Server CI Weekly / Postgres with binary parameters (push) Has been skipped
Server CI Weekly / Build Environment (push) Failing after 1s
Server CI Cache Warm / Warm Go Module and Build Cache (push) Failing after 2s
Server CI Nightly Race / Build Environment (push) Failing after 1s
This commit is contained in:
316
server/channels/store/sqlstore/plugin_store.go
Normal file
316
server/channels/store/sqlstore/plugin_store.go
Normal file
@@ -0,0 +1,316 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
|
||||
sq "github.com/mattermost/squirrel"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
"github.com/mattermost/mattermost/server/v8/channels/store"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultPluginKeyFetchLimit = 10
|
||||
)
|
||||
|
||||
type SqlPluginStore struct {
|
||||
*SqlStore
|
||||
}
|
||||
|
||||
func newSqlPluginStore(sqlStore *SqlStore) store.PluginStore {
|
||||
return &SqlPluginStore{sqlStore}
|
||||
}
|
||||
|
||||
func (ps SqlPluginStore) SaveOrUpdate(kv *model.PluginKeyValue) (*model.PluginKeyValue, error) {
|
||||
if err := kv.IsValid(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if kv.Value == nil {
|
||||
// Setting a key to nil is the same as removing it
|
||||
err := ps.Delete(kv.PluginId, kv.Key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return kv, nil
|
||||
}
|
||||
|
||||
query := ps.getQueryBuilder().
|
||||
Insert("PluginKeyValueStore").
|
||||
Columns("PluginId", "PKey", "PValue", "ExpireAt").
|
||||
Values(kv.PluginId, kv.Key, kv.Value, kv.ExpireAt)
|
||||
query = query.SuffixExpr(sq.Expr("ON CONFLICT (pluginid, pkey) DO UPDATE SET PValue = ?, ExpireAt = ?", kv.Value, kv.ExpireAt))
|
||||
|
||||
queryString, args, err := query.ToSql()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "plugin_tosql")
|
||||
}
|
||||
|
||||
if _, err := ps.GetMaster().Exec(queryString, args...); err != nil {
|
||||
return nil, errors.Wrap(err, "failed to upsert PluginKeyValue")
|
||||
}
|
||||
|
||||
return kv, nil
|
||||
}
|
||||
|
||||
func (ps SqlPluginStore) CompareAndSet(kv *model.PluginKeyValue, oldValue []byte) (bool, error) {
|
||||
if err := kv.IsValid(); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if kv.Value == nil {
|
||||
// Setting a key to nil is the same as removing it
|
||||
return ps.CompareAndDelete(kv, oldValue)
|
||||
}
|
||||
|
||||
if oldValue == nil {
|
||||
// Delete any existing, expired value.
|
||||
query := ps.getQueryBuilder().
|
||||
Delete("PluginKeyValueStore").
|
||||
Where(sq.Eq{"PluginId": kv.PluginId}).
|
||||
Where(sq.Eq{"PKey": kv.Key}).
|
||||
Where(sq.NotEq{"ExpireAt": int(0)}).
|
||||
Where(sq.Lt{"ExpireAt": model.GetMillis()})
|
||||
|
||||
queryString, args, err := query.ToSql()
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "plugin_tosql")
|
||||
}
|
||||
|
||||
if _, err = ps.GetMaster().Exec(queryString, args...); err != nil {
|
||||
return false, errors.Wrap(err, "failed to delete PluginKeyValue")
|
||||
}
|
||||
|
||||
// Insert if oldValue is nil
|
||||
queryString, args, err = ps.getQueryBuilder().
|
||||
Insert("PluginKeyValueStore").
|
||||
Columns("PluginId", "PKey", "PValue", "ExpireAt").
|
||||
Values(kv.PluginId, kv.Key, kv.Value, kv.ExpireAt).ToSql()
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "plugin_tosql")
|
||||
}
|
||||
|
||||
if _, err := ps.GetMaster().Exec(queryString, args...); err != nil {
|
||||
// If the error is from unique constraints violation, it's the result of a
|
||||
// race condition, return false and no error. Otherwise we have a real error and
|
||||
// need to return it.
|
||||
if IsUniqueConstraintError(err, []string{"PRIMARY", "PluginId", "Key", "PKey", "pkey"}) {
|
||||
return false, nil
|
||||
}
|
||||
return false, errors.Wrap(err, "failed to insert PluginKeyValue")
|
||||
}
|
||||
} else {
|
||||
currentTime := model.GetMillis()
|
||||
|
||||
// Update if oldValue is not nil
|
||||
query := ps.getQueryBuilder().
|
||||
Update("PluginKeyValueStore").
|
||||
Set("PValue", kv.Value).
|
||||
Set("ExpireAt", kv.ExpireAt).
|
||||
Where(sq.Eq{"PluginId": kv.PluginId}).
|
||||
Where(sq.Eq{"PKey": kv.Key}).
|
||||
Where(sq.Eq{"PValue": oldValue}).
|
||||
Where(sq.Or{
|
||||
sq.Eq{"ExpireAt": int(0)},
|
||||
sq.Gt{"ExpireAt": currentTime},
|
||||
})
|
||||
|
||||
queryString, args, err := query.ToSql()
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "plugin_tosql")
|
||||
}
|
||||
|
||||
updateResult, err := ps.GetMaster().Exec(queryString, args...)
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "failed to update PluginKeyValue")
|
||||
}
|
||||
|
||||
if rowsAffected, err := updateResult.RowsAffected(); err != nil {
|
||||
// Failed to update
|
||||
return false, errors.Wrap(err, "unable to get rows affected")
|
||||
} else if rowsAffected == 0 {
|
||||
// No rows were affected by the update, where condition was not satisfied,
|
||||
// return false, but no error.
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (ps SqlPluginStore) CompareAndDelete(kv *model.PluginKeyValue, oldValue []byte) (bool, error) {
|
||||
if err := kv.IsValid(); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if oldValue == nil {
|
||||
// nil can't be stored. Return showing that we didn't do anything
|
||||
return false, nil
|
||||
}
|
||||
|
||||
query := ps.getQueryBuilder().
|
||||
Delete("PluginKeyValueStore").
|
||||
Where(sq.Eq{"PluginId": kv.PluginId}).
|
||||
Where(sq.Eq{"PKey": kv.Key}).
|
||||
Where(sq.Eq{"PValue": oldValue}).
|
||||
Where(sq.Or{
|
||||
sq.Eq{"ExpireAt": int(0)},
|
||||
sq.Gt{"ExpireAt": model.GetMillis()},
|
||||
})
|
||||
|
||||
queryString, args, err := query.ToSql()
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "plugin_tosql")
|
||||
}
|
||||
|
||||
deleteResult, err := ps.GetMaster().Exec(queryString, args...)
|
||||
if err != nil {
|
||||
return false, errors.Wrap(err, "failed to delete PluginKeyValue")
|
||||
}
|
||||
|
||||
if rowsAffected, err := deleteResult.RowsAffected(); err != nil {
|
||||
return false, errors.Wrap(err, "unable to get rows affected")
|
||||
} else if rowsAffected == 0 {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
return true, nil
|
||||
}
|
||||
|
||||
func (ps SqlPluginStore) SetWithOptions(pluginId string, key string, value []byte, opt model.PluginKVSetOptions) (bool, error) {
|
||||
if err := opt.IsValid(); err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
kv, err := model.NewPluginKeyValueFromOptions(pluginId, key, value, opt)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if opt.Atomic {
|
||||
return ps.CompareAndSet(kv, opt.OldValue)
|
||||
}
|
||||
|
||||
savedKv, nErr := ps.SaveOrUpdate(kv)
|
||||
if nErr != nil {
|
||||
return false, nErr
|
||||
}
|
||||
|
||||
return savedKv != nil, nil
|
||||
}
|
||||
|
||||
func (ps SqlPluginStore) Get(pluginId, key string) (*model.PluginKeyValue, error) {
|
||||
currentTime := model.GetMillis()
|
||||
query := ps.getQueryBuilder().Select("PluginId, PKey, PValue, ExpireAt").
|
||||
From("PluginKeyValueStore").
|
||||
Where(sq.Eq{"PluginId": pluginId}).
|
||||
Where(sq.Eq{"PKey": key}).
|
||||
Where(sq.Or{sq.Eq{"ExpireAt": 0}, sq.Gt{"ExpireAt": currentTime}})
|
||||
queryString, args, err := query.ToSql()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "plugin_tosql")
|
||||
}
|
||||
|
||||
row := ps.GetReplica().QueryRow(queryString, args...)
|
||||
var kv model.PluginKeyValue
|
||||
if err := row.Scan(&kv.PluginId, &kv.Key, &kv.Value, &kv.ExpireAt); err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, store.NewErrNotFound("PluginKeyValue", fmt.Sprintf("pluginId=%s, key=%s", pluginId, key))
|
||||
}
|
||||
return nil, errors.Wrapf(err, "failed to get PluginKeyValue with pluginId=%s and key=%s", pluginId, key)
|
||||
}
|
||||
|
||||
return &kv, nil
|
||||
}
|
||||
|
||||
func (ps SqlPluginStore) Delete(pluginId, key string) error {
|
||||
query := ps.getQueryBuilder().
|
||||
Delete("PluginKeyValueStore").
|
||||
Where(sq.Eq{"PluginId": pluginId}).
|
||||
Where(sq.Eq{"Pkey": key})
|
||||
|
||||
queryString, args, err := query.ToSql()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "plugin_tosql")
|
||||
}
|
||||
|
||||
if _, err := ps.GetMaster().Exec(queryString, args...); err != nil {
|
||||
return errors.Wrapf(err, "failed to delete PluginKeyValue with pluginId=%s and key=%s", pluginId, key)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ps SqlPluginStore) DeleteAllForPlugin(pluginId string) error {
|
||||
query := ps.getQueryBuilder().
|
||||
Delete("PluginKeyValueStore").
|
||||
Where(sq.Eq{"PluginId": pluginId})
|
||||
|
||||
queryString, args, err := query.ToSql()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "plugin_tosql")
|
||||
}
|
||||
|
||||
if _, err := ps.GetMaster().Exec(queryString, args...); err != nil {
|
||||
return errors.Wrapf(err, "failed to get all PluginKeyValues with pluginId=%s ", pluginId)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ps SqlPluginStore) DeleteAllExpired() error {
|
||||
currentTime := model.GetMillis()
|
||||
query := ps.getQueryBuilder().
|
||||
Delete("PluginKeyValueStore").
|
||||
Where(sq.NotEq{"ExpireAt": 0}).
|
||||
Where(sq.Lt{"ExpireAt": currentTime})
|
||||
|
||||
queryString, args, err := query.ToSql()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "plugin_tosql")
|
||||
}
|
||||
|
||||
if _, err := ps.GetMaster().Exec(queryString, args...); err != nil {
|
||||
return errors.Wrap(err, "failed to delete all expired PluginKeyValues")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ps SqlPluginStore) List(pluginId string, offset int, limit int) ([]string, error) {
|
||||
if limit <= 0 {
|
||||
limit = defaultPluginKeyFetchLimit
|
||||
}
|
||||
|
||||
if offset <= 0 {
|
||||
offset = 0
|
||||
}
|
||||
|
||||
query := ps.getQueryBuilder().
|
||||
Select("Pkey").
|
||||
From("PluginKeyValueStore").
|
||||
Where(sq.Eq{"PluginId": pluginId}).
|
||||
Where(sq.Or{
|
||||
sq.Eq{"ExpireAt": int(0)},
|
||||
sq.Gt{"ExpireAt": model.GetMillis()},
|
||||
}).
|
||||
OrderBy("PKey").
|
||||
Limit(uint64(limit)).
|
||||
Offset(uint64(offset))
|
||||
|
||||
queryString, args, err := query.ToSql()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "plugin_tosql")
|
||||
}
|
||||
|
||||
keys := []string{}
|
||||
err = ps.GetReplica().Select(&keys, queryString, args...)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to get PluginKeyValues with pluginId=%s", pluginId)
|
||||
}
|
||||
|
||||
return keys, nil
|
||||
}
|
||||
Reference in New Issue
Block a user