Files
mostlymatter/plugin/helpers.go
Agniva De Sarker 4b95d47923 DB driver implementation via RPC (#17779)
This PR builds up on the pass-through DB driver to a fully functioning DB driver implementation via our RPC layer.

To keep things separate from the plugin RPC API, and have the ability to move fast with changes, a separate field Driver is added to MattermostPlugin. Typically the field which is required to be compatible are the API and Helpers. It would be well-documented that Driver is purely for internal use by Mattermost plugins.

A new Driver interface was created which would have a client and server implementation. Every object (connection, statement, etc.) is created and added to a map on the server side. On the client side, the wrapper structs hold the object id, and communicate via the RPC API using this id.

When the server gets the object id, it picks up the appropriate object from its map and performs the operation, and sends back the data.

Some things that need to be handled are errors. Typical error types like pq.Error and mysql.MySQLError are registered with encoding/gob. But for error variables like sql.ErrNoRows, a special integer is encoded with the ErrorString struct. And on the cilent side, the integer is checked, and the appropriate error variable is returned.

Some pending things:

- Context support. This is tricky. Since context.Context is an interface, it's not possible to marshal it. We have to find a way to get the timeout value from the context and pass it.
- RowsColumnScanType(rowsID string, index int) reflect.Type API. Again, reflect.Type is an interface.
- Master/Replica API support.
2021-06-17 08:53:52 +05:30

152 строки
6.0 KiB
Go

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package plugin
import (
"database/sql/driver"
"github.com/mattermost/mattermost-server/v5/model"
)
// Helpers provide a common patterns plugins use.
//
// Plugins obtain access to the Helpers by embedding MattermostPlugin.
type Helpers interface {
// EnsureBot either returns an existing bot user matching the given bot, or creates a bot user from the given bot.
// A profile image or icon image may be optionally passed in to be set for the existing or newly created bot.
// Returns the id of the resulting bot.
//
// Minimum server version: 5.10
EnsureBot(bot *model.Bot, options ...EnsureBotOption) (string, error)
// KVSetJSON stores a key-value pair, unique per plugin, marshalling the given value as a JSON string.
//
// Deprecated: Use p.API.KVSetWithOptions instead.
//
// Minimum server version: 5.2
KVSetJSON(key string, value interface{}) error
// KVCompareAndSetJSON updates a key-value pair, unique per plugin, but only if the current value matches the given oldValue after marshalling as a JSON string.
// Inserts a new key if oldValue == nil.
// Returns (false, err) if DB error occurred
// Returns (false, nil) if current value != oldValue or key already exists when inserting
// Returns (true, nil) if current value == oldValue or new key is inserted
//
// Deprecated: Use p.API.KVSetWithOptions instead.
//
// Minimum server version: 5.12
KVCompareAndSetJSON(key string, oldValue interface{}, newValue interface{}) (bool, error)
// KVCompareAndDeleteJSON deletes a key-value pair, unique per plugin, but only if the current value matches the given oldValue after marshalling as a JSON string.
// Returns (false, err) if DB error occurred
// Returns (false, nil) if current value != oldValue or the key was already deleted
// Returns (true, nil) if current value == oldValue
//
// Minimum server version: 5.16
KVCompareAndDeleteJSON(key string, oldValue interface{}) (bool, error)
// KVGetJSON retrieves a value based on the key, unique per plugin, unmarshalling the previously set JSON string into the given value. Returns true if the key exists.
//
// Minimum server version: 5.2
KVGetJSON(key string, value interface{}) (bool, error)
// KVSetWithExpiryJSON stores a key-value pair with an expiry time, unique per plugin, marshalling the given value as a JSON string.
//
// Deprecated: Use p.API.KVSetWithOptions instead.
//
// Minimum server version: 5.6
KVSetWithExpiryJSON(key string, value interface{}, expireInSeconds int64) error
// KVListWithOptions returns all keys that match the given options. If no options are provided then all keys are returned.
//
// Minimum server version: 5.6
KVListWithOptions(options ...KVListOption) ([]string, error)
// CheckRequiredServerConfiguration checks if the server is configured according to
// plugin requirements.
//
// Minimum server version: 5.2
CheckRequiredServerConfiguration(req *model.Config) (bool, error)
// ShouldProcessMessage returns if the message should be processed by a message hook.
//
// Use this method to avoid processing unnecessary messages in a MessageHasBeenPosted
// or MessageWillBePosted hook, and indeed in some cases avoid an infinite loop between
// two automated bots or plugins.
//
// The behaviour is customizable using the given options, since plugin needs may vary.
// By default, system messages and messages from bots will be skipped.
//
// Minimum server version: 5.2
ShouldProcessMessage(post *model.Post, options ...ShouldProcessMessageOption) (bool, error)
// InstallPluginFromURL installs the plugin from the provided url.
//
// Minimum server version: 5.18
InstallPluginFromURL(downloadURL string, replace bool) (*model.Manifest, error)
// GetPluginAssetURL builds a URL to the given asset in the assets directory.
// Use this URL to link to assets from the webapp, or for third-party integrations with your plugin.
//
// Minimum server version: 5.2
GetPluginAssetURL(pluginID, asset string) (string, error)
}
// HelpersImpl implements the helpers interface with an API that retrieves data on behalf of the plugin.
type HelpersImpl struct {
API API
}
// ResultContainer contains the output from the LastInsertID
// and RowsAffected methods for a given set of rows.
// It is used to embed another round-trip to the server,
// and helping to avoid tracking results on the server.
type ResultContainer struct {
LastID int64
LastIDError error
RowsAffected int64
RowsAffectedError error
}
type Driver interface {
// Connection
Conn() (string, error)
ConnPing(connID string) error
ConnClose(connID string) error
ConnQuery(connID, q string, args []driver.NamedValue) (string, error) // rows
ConnExec(connID, q string, args []driver.NamedValue) (ResultContainer, error) // result
// Transaction
Tx(connID string, opts driver.TxOptions) (string, error)
TxCommit(txID string) error
TxRollback(txID string) error
// Statement
Stmt(connID, q string) (string, error)
StmtClose(stID string) error
StmtNumInput(stID string) int
StmtQuery(stID string, args []driver.NamedValue) (string, error) // rows
StmtExec(stID string, args []driver.NamedValue) (ResultContainer, error) // result
// Rows
RowsColumns(rowsID string) []string
RowsClose(rowsID string) error
RowsNext(rowsID string, dest []driver.Value) error
RowsHasNextResultSet(rowsID string) bool
RowsNextResultSet(rowsID string) error
RowsColumnTypeDatabaseTypeName(rowsID string, index int) string
RowsColumnTypePrecisionScale(rowsID string, index int) (int64, int64, bool)
// TODO: add this
// RowsColumnScanType(rowsID string, index int) reflect.Type
// Note: the following cannot be implemented because either MySQL or PG
// does not support it. So this implementation has to be a common subset
// of both DB implementations.
// RowsColumnTypeLength(rowsID string, index int) (int64, bool)
// RowsColumnTypeNullable(rowsID string, index int) (bool, bool)
// ResetSession(ctx context.Context) error
// IsValid() bool
}