summaryrefslogtreecommitdiffstats
path: root/store
diff options
context:
space:
mode:
Diffstat (limited to 'store')
-rw-r--r--store/layered_store.go4
-rw-r--r--store/sqlstore/plugin_store.go92
-rw-r--r--store/sqlstore/plugin_store_test.go14
-rw-r--r--store/sqlstore/store.go1
-rw-r--r--store/sqlstore/supplier.go7
-rw-r--r--store/store.go7
-rw-r--r--store/storetest/mocks/LayeredStoreDatabaseLayer.go16
-rw-r--r--store/storetest/mocks/PluginStore.go62
-rw-r--r--store/storetest/mocks/SqlStore.go30
-rw-r--r--store/storetest/mocks/Store.go16
-rw-r--r--store/storetest/plugin_store.go69
-rw-r--r--store/storetest/store.go3
12 files changed, 321 insertions, 0 deletions
diff --git a/store/layered_store.go b/store/layered_store.go
index 7e6a06086..ecf02864c 100644
--- a/store/layered_store.go
+++ b/store/layered_store.go
@@ -153,6 +153,10 @@ func (s *LayeredStore) UserAccessToken() UserAccessTokenStore {
return s.DatabaseLayer.UserAccessToken()
}
+func (s *LayeredStore) Plugin() PluginStore {
+ return s.DatabaseLayer.Plugin()
+}
+
func (s *LayeredStore) MarkSystemRanUnitTests() {
s.DatabaseLayer.MarkSystemRanUnitTests()
}
diff --git a/store/sqlstore/plugin_store.go b/store/sqlstore/plugin_store.go
new file mode 100644
index 000000000..c7c075d8a
--- /dev/null
+++ b/store/sqlstore/plugin_store.go
@@ -0,0 +1,92 @@
+// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package sqlstore
+
+import (
+ "database/sql"
+ "fmt"
+ "net/http"
+
+ "github.com/mattermost/mattermost-server/model"
+ "github.com/mattermost/mattermost-server/store"
+)
+
+type SqlPluginStore struct {
+ SqlStore
+}
+
+func NewSqlPluginStore(sqlStore SqlStore) store.PluginStore {
+ s := &SqlPluginStore{sqlStore}
+
+ for _, db := range sqlStore.GetAllConns() {
+ table := db.AddTableWithName(model.PluginKeyValue{}, "PluginKeyValueStore").SetKeys(false, "PluginId", "Key")
+ table.ColMap("Value").SetMaxSize(8192)
+ }
+
+ return s
+}
+
+func (ps SqlPluginStore) CreateIndexesIfNotExists() {
+}
+
+func (ps SqlPluginStore) SaveOrUpdate(kv *model.PluginKeyValue) store.StoreChannel {
+ return store.Do(func(result *store.StoreResult) {
+ if result.Err = kv.IsValid(); result.Err != nil {
+ return
+ }
+
+ if ps.DriverName() == model.DATABASE_DRIVER_POSTGRES {
+ // Unfortunately PostgreSQL pre-9.5 does not have an atomic upsert, so we use
+ // separate update and insert queries to accomplish our upsert
+ if rowsAffected, err := ps.GetMaster().Update(kv); err != nil {
+ result.Err = model.NewAppError("SqlPluginStore.SaveOrUpdate", "store.sql_plugin_store.save.app_error", nil, err.Error(), http.StatusInternalServerError)
+ return
+ } else if rowsAffected == 0 {
+ // No rows were affected by the update, so let's try an insert
+ if err := ps.GetMaster().Insert(kv); err != nil {
+ // If the error is from unique constraints violation, it's the result of a
+ // valid race and we can report success. Otherwise we have a real error and
+ // need to return it
+ if !IsUniqueConstraintError(err, []string{"PRIMARY", "PluginId", "Key", "PKey"}) {
+ result.Err = model.NewAppError("SqlPluginStore.SaveOrUpdate", "store.sql_plugin_store.save.app_error", nil, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ }
+ }
+ } else if ps.DriverName() == model.DATABASE_DRIVER_MYSQL {
+ if _, err := ps.GetMaster().Exec("INSERT INTO PluginKeyValueStore (PluginId, PKey, PValue) VALUES(:PluginId, :Key, :Value) ON DUPLICATE KEY UPDATE PValue = :Value", map[string]interface{}{"PluginId": kv.PluginId, "Key": kv.Key, "Value": kv.Value}); err != nil {
+ result.Err = model.NewAppError("SqlPluginStore.SaveOrUpdate", "store.sql_plugin_store.save.app_error", nil, err.Error(), http.StatusInternalServerError)
+ return
+ }
+ }
+
+ result.Data = kv
+ })
+}
+
+func (ps SqlPluginStore) Get(pluginId, key string) store.StoreChannel {
+ return store.Do(func(result *store.StoreResult) {
+ var kv *model.PluginKeyValue
+
+ if err := ps.GetReplica().SelectOne(&kv, "SELECT * FROM PluginKeyValueStore WHERE PluginId = :PluginId AND PKey = :Key", map[string]interface{}{"PluginId": pluginId, "Key": key}); err != nil {
+ if err == sql.ErrNoRows {
+ result.Err = model.NewAppError("SqlPluginStore.Get", "store.sql_plugin_store.get.app_error", nil, fmt.Sprintf("plugin_id=%v, key=%v, err=%v", pluginId, key, err.Error()), http.StatusNotFound)
+ } else {
+ result.Err = model.NewAppError("SqlPluginStore.Get", "store.sql_plugin_store.get.app_error", nil, fmt.Sprintf("plugin_id=%v, key=%v, err=%v", pluginId, key, err.Error()), http.StatusInternalServerError)
+ }
+ } else {
+ result.Data = kv
+ }
+ })
+}
+
+func (ps SqlPluginStore) Delete(pluginId, key string) store.StoreChannel {
+ return store.Do(func(result *store.StoreResult) {
+ if _, err := ps.GetMaster().Exec("DELETE FROM PluginKeyValueStore WHERE PluginId = :PluginId AND PKey = :Key", map[string]interface{}{"PluginId": pluginId, "Key": key}); err != nil {
+ result.Err = model.NewAppError("SqlPluginStore.Delete", "store.sql_plugin_store.delete.app_error", nil, fmt.Sprintf("plugin_id=%v, key=%v, err=%v", pluginId, key, err.Error()), http.StatusInternalServerError)
+ } else {
+ result.Data = true
+ }
+ })
+}
diff --git a/store/sqlstore/plugin_store_test.go b/store/sqlstore/plugin_store_test.go
new file mode 100644
index 000000000..d2e883235
--- /dev/null
+++ b/store/sqlstore/plugin_store_test.go
@@ -0,0 +1,14 @@
+// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package sqlstore
+
+import (
+ "testing"
+
+ "github.com/mattermost/mattermost-server/store/storetest"
+)
+
+func TestPluginStore(t *testing.T) {
+ StoreTest(t, storetest.TestPluginStore)
+}
diff --git a/store/sqlstore/store.go b/store/sqlstore/store.go
index cc43778f5..cfdd7a552 100644
--- a/store/sqlstore/store.go
+++ b/store/sqlstore/store.go
@@ -85,5 +85,6 @@ type SqlStore interface {
FileInfo() store.FileInfoStore
Reaction() store.ReactionStore
Job() store.JobStore
+ Plugin() store.PluginStore
UserAccessToken() store.UserAccessTokenStore
}
diff --git a/store/sqlstore/supplier.go b/store/sqlstore/supplier.go
index a90ea6388..dbe4aa92c 100644
--- a/store/sqlstore/supplier.go
+++ b/store/sqlstore/supplier.go
@@ -84,6 +84,7 @@ type SqlSupplierOldStores struct {
reaction store.ReactionStore
job store.JobStore
userAccessToken store.UserAccessTokenStore
+ plugin store.PluginStore
}
type SqlSupplier struct {
@@ -129,6 +130,7 @@ func NewSqlSupplier(settings model.SqlSettings, metrics einterfaces.MetricsInter
supplier.oldStores.fileInfo = NewSqlFileInfoStore(supplier, metrics)
supplier.oldStores.job = NewSqlJobStore(supplier)
supplier.oldStores.userAccessToken = NewSqlUserAccessTokenStore(supplier)
+ supplier.oldStores.plugin = NewSqlPluginStore(supplier)
initSqlSupplierReactions(supplier)
@@ -161,6 +163,7 @@ func NewSqlSupplier(settings model.SqlSettings, metrics einterfaces.MetricsInter
supplier.oldStores.fileInfo.(*SqlFileInfoStore).CreateIndexesIfNotExists()
supplier.oldStores.job.(*SqlJobStore).CreateIndexesIfNotExists()
supplier.oldStores.userAccessToken.(*SqlUserAccessTokenStore).CreateIndexesIfNotExists()
+ supplier.oldStores.plugin.(*SqlPluginStore).CreateIndexesIfNotExists()
supplier.oldStores.preference.(*SqlPreferenceStore).DeleteUnusedFeatures()
@@ -798,6 +801,10 @@ func (ss *SqlSupplier) UserAccessToken() store.UserAccessTokenStore {
return ss.oldStores.userAccessToken
}
+func (ss *SqlSupplier) Plugin() store.PluginStore {
+ return ss.oldStores.plugin
+}
+
func (ss *SqlSupplier) DropAllTables() {
ss.master.TruncateTables()
}
diff --git a/store/store.go b/store/store.go
index eada8f395..7997000ec 100644
--- a/store/store.go
+++ b/store/store.go
@@ -63,6 +63,7 @@ type Store interface {
Reaction() ReactionStore
Job() JobStore
UserAccessToken() UserAccessTokenStore
+ Plugin() PluginStore
MarkSystemRanUnitTests()
Close()
DropAllTables()
@@ -440,3 +441,9 @@ type UserAccessTokenStore interface {
UpdateTokenEnable(tokenId string) StoreChannel
UpdateTokenDisable(tokenId string) StoreChannel
}
+
+type PluginStore interface {
+ SaveOrUpdate(keyVal *model.PluginKeyValue) StoreChannel
+ Get(pluginId, key string) StoreChannel
+ Delete(pluginId, key string) StoreChannel
+}
diff --git a/store/storetest/mocks/LayeredStoreDatabaseLayer.go b/store/storetest/mocks/LayeredStoreDatabaseLayer.go
index 1eb09c343..c3b8bbb60 100644
--- a/store/storetest/mocks/LayeredStoreDatabaseLayer.go
+++ b/store/storetest/mocks/LayeredStoreDatabaseLayer.go
@@ -221,6 +221,22 @@ func (_m *LayeredStoreDatabaseLayer) OAuth() store.OAuthStore {
return r0
}
+// Plugin provides a mock function with given fields:
+func (_m *LayeredStoreDatabaseLayer) Plugin() store.PluginStore {
+ ret := _m.Called()
+
+ var r0 store.PluginStore
+ if rf, ok := ret.Get(0).(func() store.PluginStore); ok {
+ r0 = rf()
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(store.PluginStore)
+ }
+ }
+
+ return r0
+}
+
// Post provides a mock function with given fields:
func (_m *LayeredStoreDatabaseLayer) Post() store.PostStore {
ret := _m.Called()
diff --git a/store/storetest/mocks/PluginStore.go b/store/storetest/mocks/PluginStore.go
new file mode 100644
index 000000000..920b0f63c
--- /dev/null
+++ b/store/storetest/mocks/PluginStore.go
@@ -0,0 +1,62 @@
+// Code generated by mockery v1.0.0
+
+// Regenerate this file using `make store-mocks`.
+
+package mocks
+
+import mock "github.com/stretchr/testify/mock"
+import model "github.com/mattermost/mattermost-server/model"
+import store "github.com/mattermost/mattermost-server/store"
+
+// PluginStore is an autogenerated mock type for the PluginStore type
+type PluginStore struct {
+ mock.Mock
+}
+
+// Delete provides a mock function with given fields: pluginId, key
+func (_m *PluginStore) Delete(pluginId string, key string) store.StoreChannel {
+ ret := _m.Called(pluginId, key)
+
+ var r0 store.StoreChannel
+ if rf, ok := ret.Get(0).(func(string, string) store.StoreChannel); ok {
+ r0 = rf(pluginId, key)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(store.StoreChannel)
+ }
+ }
+
+ return r0
+}
+
+// Get provides a mock function with given fields: pluginId, key
+func (_m *PluginStore) Get(pluginId string, key string) store.StoreChannel {
+ ret := _m.Called(pluginId, key)
+
+ var r0 store.StoreChannel
+ if rf, ok := ret.Get(0).(func(string, string) store.StoreChannel); ok {
+ r0 = rf(pluginId, key)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(store.StoreChannel)
+ }
+ }
+
+ return r0
+}
+
+// SaveOrUpdate provides a mock function with given fields: keyVal
+func (_m *PluginStore) SaveOrUpdate(keyVal *model.PluginKeyValue) store.StoreChannel {
+ ret := _m.Called(keyVal)
+
+ var r0 store.StoreChannel
+ if rf, ok := ret.Get(0).(func(*model.PluginKeyValue) store.StoreChannel); ok {
+ r0 = rf(keyVal)
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(store.StoreChannel)
+ }
+ }
+
+ return r0
+}
diff --git a/store/storetest/mocks/SqlStore.go b/store/storetest/mocks/SqlStore.go
index eda8cb39e..b9b962101 100644
--- a/store/storetest/mocks/SqlStore.go
+++ b/store/storetest/mocks/SqlStore.go
@@ -143,6 +143,20 @@ func (_m *SqlStore) CreateColumnIfNotExists(tableName string, columnName string,
return r0
}
+// CreateCompositeIndexIfNotExists provides a mock function with given fields: indexName, tableName, columnNames
+func (_m *SqlStore) CreateCompositeIndexIfNotExists(indexName string, tableName string, columnNames []string) bool {
+ ret := _m.Called(indexName, tableName, columnNames)
+
+ var r0 bool
+ if rf, ok := ret.Get(0).(func(string, string, []string) bool); ok {
+ r0 = rf(indexName, tableName, columnNames)
+ } else {
+ r0 = ret.Get(0).(bool)
+ }
+
+ return r0
+}
+
// CreateFullTextIndexIfNotExists provides a mock function with given fields: indexName, tableName, columnName
func (_m *SqlStore) CreateFullTextIndexIfNotExists(indexName string, tableName string, columnName string) bool {
ret := _m.Called(indexName, tableName, columnName)
@@ -404,6 +418,22 @@ func (_m *SqlStore) OAuth() store.OAuthStore {
return r0
}
+// Plugin provides a mock function with given fields:
+func (_m *SqlStore) Plugin() store.PluginStore {
+ ret := _m.Called()
+
+ var r0 store.PluginStore
+ if rf, ok := ret.Get(0).(func() store.PluginStore); ok {
+ r0 = rf()
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(store.PluginStore)
+ }
+ }
+
+ return r0
+}
+
// Post provides a mock function with given fields:
func (_m *SqlStore) Post() store.PostStore {
ret := _m.Called()
diff --git a/store/storetest/mocks/Store.go b/store/storetest/mocks/Store.go
index 166b5b98a..85ed10d35 100644
--- a/store/storetest/mocks/Store.go
+++ b/store/storetest/mocks/Store.go
@@ -203,6 +203,22 @@ func (_m *Store) OAuth() store.OAuthStore {
return r0
}
+// Plugin provides a mock function with given fields:
+func (_m *Store) Plugin() store.PluginStore {
+ ret := _m.Called()
+
+ var r0 store.PluginStore
+ if rf, ok := ret.Get(0).(func() store.PluginStore); ok {
+ r0 = rf()
+ } else {
+ if ret.Get(0) != nil {
+ r0 = ret.Get(0).(store.PluginStore)
+ }
+ }
+
+ return r0
+}
+
// Post provides a mock function with given fields:
func (_m *Store) Post() store.PostStore {
ret := _m.Called()
diff --git a/store/storetest/plugin_store.go b/store/storetest/plugin_store.go
new file mode 100644
index 000000000..3d7d0ec05
--- /dev/null
+++ b/store/storetest/plugin_store.go
@@ -0,0 +1,69 @@
+// Copyright (c) 2017-present Mattermost, Inc. All Rights Reserved.
+// See License.txt for license information.
+
+package storetest
+
+import (
+ "testing"
+
+ "github.com/mattermost/mattermost-server/model"
+ "github.com/mattermost/mattermost-server/store"
+ "github.com/stretchr/testify/assert"
+)
+
+func TestPluginStore(t *testing.T, ss store.Store) {
+ t.Run("PluginSaveGet", func(t *testing.T) { testPluginSaveGet(t, ss) })
+ t.Run("PluginDelete", func(t *testing.T) { testPluginDelete(t, ss) })
+}
+
+func testPluginSaveGet(t *testing.T, ss store.Store) {
+ kv := &model.PluginKeyValue{
+ PluginId: model.NewId(),
+ Key: model.NewId(),
+ Value: []byte(model.NewId()),
+ }
+
+ if result := <-ss.Plugin().SaveOrUpdate(kv); result.Err != nil {
+ t.Fatal(result.Err)
+ }
+
+ defer func() {
+ <-ss.Plugin().Delete(kv.PluginId, kv.Key)
+ }()
+
+ if result := <-ss.Plugin().Get(kv.PluginId, kv.Key); result.Err != nil {
+ t.Fatal(result.Err)
+ } else {
+ received := result.Data.(*model.PluginKeyValue)
+ assert.Equal(t, kv.PluginId, received.PluginId)
+ assert.Equal(t, kv.Key, received.Key)
+ assert.Equal(t, kv.Value, received.Value)
+ }
+
+ // Try inserting when already exists
+ kv.Value = []byte(model.NewId())
+ if result := <-ss.Plugin().SaveOrUpdate(kv); result.Err != nil {
+ t.Fatal(result.Err)
+ }
+
+ if result := <-ss.Plugin().Get(kv.PluginId, kv.Key); result.Err != nil {
+ t.Fatal(result.Err)
+ } else {
+ received := result.Data.(*model.PluginKeyValue)
+ assert.Equal(t, kv.PluginId, received.PluginId)
+ assert.Equal(t, kv.Key, received.Key)
+ assert.Equal(t, kv.Value, received.Value)
+ }
+}
+
+func testPluginDelete(t *testing.T, ss store.Store) {
+ kv := store.Must(ss.Plugin().SaveOrUpdate(&model.PluginKeyValue{
+ PluginId: model.NewId(),
+ Key: model.NewId(),
+ Value: []byte(model.NewId()),
+ })).(*model.PluginKeyValue)
+
+ if result := <-ss.Plugin().Delete(kv.PluginId, kv.Key); result.Err != nil {
+ t.Fatal(result.Err)
+ }
+}
diff --git a/store/storetest/store.go b/store/storetest/store.go
index 7201df6ec..55545decb 100644
--- a/store/storetest/store.go
+++ b/store/storetest/store.go
@@ -41,6 +41,7 @@ type Store struct {
ReactionStore mocks.ReactionStore
JobStore mocks.JobStore
UserAccessTokenStore mocks.UserAccessTokenStore
+ PluginStore mocks.PluginStore
}
func (s *Store) Team() store.TeamStore { return &s.TeamStore }
@@ -65,6 +66,7 @@ func (s *Store) FileInfo() store.FileInfoStore { return &s.FileI
func (s *Store) Reaction() store.ReactionStore { return &s.ReactionStore }
func (s *Store) Job() store.JobStore { return &s.JobStore }
func (s *Store) UserAccessToken() store.UserAccessTokenStore { return &s.UserAccessTokenStore }
+func (s *Store) Plugin() store.PluginStore { return &s.PluginStore }
func (s *Store) MarkSystemRanUnitTests() { /* do nothing */ }
func (s *Store) Close() { /* do nothing */ }
func (s *Store) DropAllTables() { /* do nothing */ }
@@ -96,5 +98,6 @@ func (s *Store) AssertExpectations(t mock.TestingT) bool {
&s.ReactionStore,
&s.JobStore,
&s.UserAccessTokenStore,
+ &s.PluginStore,
)
}