Adds the main Property System Architecture components (#29644)
* Adds the main Property System Architecture components This change adds the necessary migrations for the Property Groups, Fields and Values tables to be created, the store layer and a Property Service that can be used from the app layer. * Update property field type to use user instead of person * Update PropertyFields to allow for unique nondeleted fields and remove redundant indexes * Update PropertyValues to allow for unique nondeleted fields and remove redundant indexes * Use StringMap instead of the map[string]any on property fields * Add i18n strings * Revert "Use StringMap instead of the map[string]any on property fields" This reverts commit e2735ab0f8589d2524d636419ca0cb144575c4d6. * Cast JSON binary data to string and add todo note for StringMap use * Add mocks to the retrylayer tests * Cast JSON binary data to string in property value store * Check for binary parameter instead of casting to string for JSON data * Check property field type is one of the allowed ones * Avoid reusing err variable to be explicit about the returned value * Merge Property System Migrations into one file * Adds NOT NULL to timestamps at the DB level * Update stores to use tableSelectQuery instead of a slice var * Update PropertyField model translations to be more explicit and avoid repetition * Update PropertyValue model translations to be more explicit and avoid repetition * Use ExecBuilder instead of ToSql&Exec * Update property field errors to add context * Ensure PerPage is greater than zero * Update store errors to give more context * Use ExecBuilder in the property stores where possible * Add an on conflict suffix to the group register to avoid race conditions * Remove badly used translation string * Remove unused get in register group method --------- Co-authored-by: Mattermost Build <build@mattermost.com>
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
d2b334e605
Коммит
ecdce71fc4
321
server/channels/store/sqlstore/property_field_store.go
Обычный файл
321
server/channels/store/sqlstore/property_field_store.go
Обычный файл
@@ -0,0 +1,321 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"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"
|
||||
)
|
||||
|
||||
func (s *SqlPropertyFieldStore) propertyFieldToInsertMap(field *model.PropertyField) (map[string]any, error) {
|
||||
attrsJSON, err := json.Marshal(field.Attrs)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "property_field_to_insert_map_marshal_attrs")
|
||||
}
|
||||
if s.IsBinaryParamEnabled() {
|
||||
attrsJSON = AppendBinaryFlag(attrsJSON)
|
||||
}
|
||||
|
||||
return map[string]any{
|
||||
"ID": field.ID,
|
||||
"GroupID": field.GroupID,
|
||||
"Name": field.Name,
|
||||
"Type": field.Type,
|
||||
"Attrs": attrsJSON,
|
||||
"TargetID": field.TargetID,
|
||||
"TargetType": field.TargetType,
|
||||
"CreateAt": field.CreateAt,
|
||||
"UpdateAt": field.UpdateAt,
|
||||
"DeleteAt": field.DeleteAt,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *SqlPropertyFieldStore) propertyFieldToUpdateMap(field *model.PropertyField) (map[string]any, error) {
|
||||
attrsJSON, err := json.Marshal(field.Attrs)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "property_field_to_update_map_marshal_attrs")
|
||||
}
|
||||
if s.IsBinaryParamEnabled() {
|
||||
attrsJSON = AppendBinaryFlag(attrsJSON)
|
||||
}
|
||||
|
||||
return map[string]any{
|
||||
"Name": field.Name,
|
||||
"Type": field.Type,
|
||||
"Attrs": attrsJSON,
|
||||
"TargetID": field.TargetID,
|
||||
"TargetType": field.TargetType,
|
||||
"UpdateAt": field.UpdateAt,
|
||||
"DeleteAt": field.DeleteAt,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func propertyFieldsFromRows(rows *sql.Rows) ([]*model.PropertyField, error) {
|
||||
results := []*model.PropertyField{}
|
||||
|
||||
for rows.Next() {
|
||||
var field model.PropertyField
|
||||
var attrsJSON string
|
||||
|
||||
err := rows.Scan(
|
||||
&field.ID,
|
||||
&field.GroupID,
|
||||
&field.Name,
|
||||
&field.Type,
|
||||
&attrsJSON,
|
||||
&field.TargetID,
|
||||
&field.TargetType,
|
||||
&field.CreateAt,
|
||||
&field.UpdateAt,
|
||||
&field.DeleteAt,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := json.Unmarshal([]byte(attrsJSON), &field.Attrs); err != nil {
|
||||
return nil, errors.Wrap(err, "property_fields_from_rows_unmarshal_attrs")
|
||||
}
|
||||
|
||||
results = append(results, &field)
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func propertyFieldFromRows(rows *sql.Rows) (*model.PropertyField, error) {
|
||||
fields, err := propertyFieldsFromRows(rows)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(fields) > 0 {
|
||||
return fields[0], nil
|
||||
}
|
||||
|
||||
return nil, sql.ErrNoRows
|
||||
}
|
||||
|
||||
type SqlPropertyFieldStore struct {
|
||||
*SqlStore
|
||||
|
||||
tableSelectQuery sq.SelectBuilder
|
||||
}
|
||||
|
||||
func newPropertyFieldStore(sqlStore *SqlStore) store.PropertyFieldStore {
|
||||
s := SqlPropertyFieldStore{SqlStore: sqlStore}
|
||||
|
||||
s.tableSelectQuery = s.getQueryBuilder().
|
||||
Select("ID", "GroupID", "Name", "Type", "Attrs", "TargetID", "TargetType", "CreateAt", "UpdateAt", "DeleteAt").
|
||||
From("PropertyFields")
|
||||
|
||||
return &s
|
||||
}
|
||||
|
||||
func (s *SqlPropertyFieldStore) Create(field *model.PropertyField) (*model.PropertyField, error) {
|
||||
if field.ID != "" {
|
||||
return nil, store.NewErrInvalidInput("PropertyField", "id", field.ID)
|
||||
}
|
||||
|
||||
field.PreSave()
|
||||
|
||||
if err := field.IsValid(); err != nil {
|
||||
return nil, errors.Wrap(err, "property_field_create_isvalid")
|
||||
}
|
||||
|
||||
insertMap, err := s.propertyFieldToInsertMap(field)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
builder := s.getQueryBuilder().
|
||||
Insert("PropertyFields").
|
||||
SetMap(insertMap)
|
||||
|
||||
if _, err := s.GetMaster().ExecBuilder(builder); err != nil {
|
||||
return nil, errors.Wrap(err, "property_field_create_insert")
|
||||
}
|
||||
|
||||
return field, nil
|
||||
}
|
||||
|
||||
func (s *SqlPropertyFieldStore) Get(id string) (*model.PropertyField, error) {
|
||||
queryString, args, err := s.tableSelectQuery.
|
||||
Where(sq.Eq{"id": id}).
|
||||
ToSql()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "property_field_get_tosql")
|
||||
}
|
||||
|
||||
rows, err := s.GetReplica().Query(queryString, args...)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "property_field_get_select")
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
field, err := propertyFieldFromRows(rows)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "property_field_get_propertyfieldfromrows")
|
||||
}
|
||||
|
||||
return field, nil
|
||||
}
|
||||
|
||||
func (s *SqlPropertyFieldStore) GetMany(ids []string) ([]*model.PropertyField, error) {
|
||||
queryString, args, err := s.tableSelectQuery.
|
||||
Where(sq.Eq{"id": ids}).
|
||||
ToSql()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "property_field_get_many_tosql")
|
||||
}
|
||||
|
||||
rows, err := s.GetReplica().Query(queryString, args...)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "property_field_get_many_query")
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
fields, err := propertyFieldsFromRows(rows)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "property_field_get_many_propertyfieldfromrows")
|
||||
}
|
||||
|
||||
if len(fields) < len(ids) {
|
||||
return nil, fmt.Errorf("missmatch results: got %d results of the %d ids passed", len(fields), len(ids))
|
||||
}
|
||||
|
||||
return fields, nil
|
||||
}
|
||||
|
||||
func (s *SqlPropertyFieldStore) SearchPropertyFields(opts model.PropertyFieldSearchOpts) ([]*model.PropertyField, error) {
|
||||
if opts.Page < 0 {
|
||||
return nil, errors.New("page must be positive integer")
|
||||
}
|
||||
|
||||
if opts.PerPage < 1 {
|
||||
return nil, errors.New("per page must be positive integer greater than zero")
|
||||
}
|
||||
|
||||
query := s.tableSelectQuery.
|
||||
OrderBy("CreateAt ASC").
|
||||
Offset(uint64(opts.Page * opts.PerPage)).
|
||||
Limit(uint64(opts.PerPage))
|
||||
|
||||
if !opts.IncludeDeleted {
|
||||
query = query.Where(sq.Eq{"DeleteAt": 0})
|
||||
}
|
||||
|
||||
if opts.GroupID != "" {
|
||||
query = query.Where(sq.Eq{"GroupID": opts.GroupID})
|
||||
}
|
||||
|
||||
if opts.TargetType != "" {
|
||||
query = query.Where(sq.Eq{"TargetType": opts.TargetType})
|
||||
}
|
||||
|
||||
if opts.TargetID != "" {
|
||||
query = query.Where(sq.Eq{"TargetID": opts.TargetID})
|
||||
}
|
||||
|
||||
queryString, args, err := query.ToSql()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "property_field_search_tosql")
|
||||
}
|
||||
|
||||
rows, err := s.GetReplica().Query(queryString, args...)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "property_field_search_query")
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
fields, err := propertyFieldsFromRows(rows)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "property_field_search_propertyfieldfromrows")
|
||||
}
|
||||
|
||||
return fields, nil
|
||||
}
|
||||
|
||||
func (s *SqlPropertyFieldStore) Update(fields []*model.PropertyField) (_ []*model.PropertyField, err error) {
|
||||
if len(fields) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
transaction, err := s.GetMaster().Beginx()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "property_field_update_begin_transaction")
|
||||
}
|
||||
defer finalizeTransactionX(transaction, &err)
|
||||
|
||||
updateTime := model.GetMillis()
|
||||
for _, field := range fields {
|
||||
field.UpdateAt = updateTime
|
||||
|
||||
if vErr := field.IsValid(); vErr != nil {
|
||||
return nil, errors.Wrap(vErr, "property_field_update_isvalid")
|
||||
}
|
||||
|
||||
updateMap, err := s.propertyFieldToUpdateMap(field)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
queryString, args, err := s.getQueryBuilder().
|
||||
Update("PropertyFields").
|
||||
SetMap(updateMap).
|
||||
Where(sq.Eq{"id": field.ID}).
|
||||
ToSql()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "property_field_update_tosql")
|
||||
}
|
||||
|
||||
result, err := transaction.Exec(queryString, args...)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to update property field with id: %s", field.ID)
|
||||
}
|
||||
|
||||
count, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "property_field_update_rowsaffected")
|
||||
}
|
||||
if count == 0 {
|
||||
return nil, store.NewErrNotFound("PropertyField", field.ID)
|
||||
}
|
||||
}
|
||||
|
||||
if err := transaction.Commit(); err != nil {
|
||||
return nil, errors.Wrap(err, "property_field_update_commit")
|
||||
}
|
||||
|
||||
return fields, nil
|
||||
}
|
||||
|
||||
func (s *SqlPropertyFieldStore) Delete(id string) error {
|
||||
builder := s.getQueryBuilder().
|
||||
Update("PropertyFields").
|
||||
Set("DeleteAt", model.GetMillis()).
|
||||
Where(sq.Eq{"id": id})
|
||||
|
||||
result, err := s.GetMaster().ExecBuilder(builder)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to delete property field with id: %s", id)
|
||||
}
|
||||
|
||||
count, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "property_field_delete_rowsaffected")
|
||||
}
|
||||
if count == 0 {
|
||||
return store.NewErrNotFound("PropertyField", id)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
14
server/channels/store/sqlstore/property_field_store_test.go
Обычный файл
14
server/channels/store/sqlstore/property_field_store_test.go
Обычный файл
@@ -0,0 +1,14 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/mattermost/mattermost/server/v8/channels/store/storetest"
|
||||
)
|
||||
|
||||
func TestPropertyFieldStore(t *testing.T) {
|
||||
StoreTestWithSqlStore(t, storetest.TestPropertyFieldStore)
|
||||
}
|
||||
78
server/channels/store/sqlstore/property_group_store.go
Обычный файл
78
server/channels/store/sqlstore/property_group_store.go
Обычный файл
@@ -0,0 +1,78 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
sq "github.com/mattermost/squirrel"
|
||||
"github.com/pkg/errors"
|
||||
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
"github.com/mattermost/mattermost/server/v8/channels/store"
|
||||
)
|
||||
|
||||
var propertyGroupColumns = []string{"ID", "Name"}
|
||||
|
||||
type SqlPropertyGroupStore struct {
|
||||
*SqlStore
|
||||
}
|
||||
|
||||
func newPropertyGroupStore(sqlStore *SqlStore) store.PropertyGroupStore {
|
||||
return &SqlPropertyGroupStore{sqlStore}
|
||||
}
|
||||
|
||||
func (s *SqlPropertyGroupStore) Register(name string) (*model.PropertyGroup, error) {
|
||||
if name == "" {
|
||||
return nil, store.NewErrInvalidInput("PropertyGroup", "name", name)
|
||||
}
|
||||
|
||||
group := &model.PropertyGroup{Name: name}
|
||||
group.PreSave()
|
||||
|
||||
builder := s.getQueryBuilder().
|
||||
Insert("PropertyGroups").
|
||||
Columns("ID", "Name").
|
||||
Values(group.ID, group.Name)
|
||||
|
||||
if s.DriverName() == model.DatabaseDriverMysql {
|
||||
builder = builder.SuffixExpr(sq.Expr("ON DUPLICATE KEY UPDATE Name=Name"))
|
||||
} else {
|
||||
builder = builder.SuffixExpr(sq.Expr("ON CONFLICT (Name) DO NOTHING"))
|
||||
}
|
||||
|
||||
r, err := s.GetMaster().ExecBuilder(builder)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "property_group_register_insert")
|
||||
}
|
||||
|
||||
rowsAffected, err := r.RowsAffected()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "property_group_register_rows_affected")
|
||||
}
|
||||
|
||||
// there was a conflict during the insert, so we need to fetch the
|
||||
// group to get its data
|
||||
if rowsAffected == 0 {
|
||||
return s.Get(name)
|
||||
}
|
||||
|
||||
return group, nil
|
||||
}
|
||||
|
||||
func (s *SqlPropertyGroupStore) Get(name string) (*model.PropertyGroup, error) {
|
||||
queryString, args, err := s.getQueryBuilder().
|
||||
Select(propertyGroupColumns...).
|
||||
From("PropertyGroups").
|
||||
Where(sq.Eq{"Name": name}).
|
||||
ToSql()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "property_group_get_tosql")
|
||||
}
|
||||
|
||||
var propertyGroup model.PropertyGroup
|
||||
if err := s.GetReplica().Get(&propertyGroup, queryString, args...); err != nil {
|
||||
return nil, store.NewErrNotFound("PropertyGroup", name)
|
||||
}
|
||||
|
||||
return &propertyGroup, nil
|
||||
}
|
||||
14
server/channels/store/sqlstore/property_group_store_test.go
Обычный файл
14
server/channels/store/sqlstore/property_group_store_test.go
Обычный файл
@@ -0,0 +1,14 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/mattermost/mattermost/server/v8/channels/store/storetest"
|
||||
)
|
||||
|
||||
func TestPropertyGroupStore(t *testing.T) {
|
||||
StoreTestWithSqlStore(t, storetest.TestPropertyGroupStore)
|
||||
}
|
||||
332
server/channels/store/sqlstore/property_value_store.go
Обычный файл
332
server/channels/store/sqlstore/property_value_store.go
Обычный файл
@@ -0,0 +1,332 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"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"
|
||||
)
|
||||
|
||||
func (s *SqlPropertyValueStore) propertyValueToInsertMap(value *model.PropertyValue) (map[string]any, error) {
|
||||
valueJSON, err := json.Marshal(value.Value)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "property_value_to_insert_map_marshal_value")
|
||||
}
|
||||
if s.IsBinaryParamEnabled() {
|
||||
valueJSON = AppendBinaryFlag(valueJSON)
|
||||
}
|
||||
|
||||
return map[string]any{
|
||||
"ID": value.ID,
|
||||
"TargetID": value.TargetID,
|
||||
"TargetType": value.TargetType,
|
||||
"GroupID": value.GroupID,
|
||||
"FieldID": value.FieldID,
|
||||
"Value": valueJSON,
|
||||
"CreateAt": value.CreateAt,
|
||||
"UpdateAt": value.UpdateAt,
|
||||
"DeleteAt": value.DeleteAt,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *SqlPropertyValueStore) propertyValueToUpdateMap(value *model.PropertyValue) (map[string]any, error) {
|
||||
valueJSON, err := json.Marshal(value.Value)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "property_value_to_udpate_map_marshal_value")
|
||||
}
|
||||
if s.IsBinaryParamEnabled() {
|
||||
valueJSON = AppendBinaryFlag(valueJSON)
|
||||
}
|
||||
|
||||
return map[string]any{
|
||||
"Value": valueJSON,
|
||||
"UpdateAt": value.UpdateAt,
|
||||
"DeleteAt": value.DeleteAt,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func propertyValuesFromRows(rows *sql.Rows) ([]*model.PropertyValue, error) {
|
||||
results := []*model.PropertyValue{}
|
||||
|
||||
for rows.Next() {
|
||||
var value model.PropertyValue
|
||||
var valueJSON string
|
||||
|
||||
err := rows.Scan(
|
||||
&value.ID,
|
||||
&value.TargetID,
|
||||
&value.TargetType,
|
||||
&value.GroupID,
|
||||
&value.FieldID,
|
||||
&valueJSON,
|
||||
&value.CreateAt,
|
||||
&value.UpdateAt,
|
||||
&value.DeleteAt,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := json.Unmarshal([]byte(valueJSON), &value.Value); err != nil {
|
||||
return nil, errors.Wrap(err, "property_values_from_rows_unmarshal_value")
|
||||
}
|
||||
|
||||
results = append(results, &value)
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func propertyValueFromRows(rows *sql.Rows) (*model.PropertyValue, error) {
|
||||
values, err := propertyValuesFromRows(rows)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(values) > 0 {
|
||||
return values[0], nil
|
||||
}
|
||||
|
||||
return nil, sql.ErrNoRows
|
||||
}
|
||||
|
||||
type SqlPropertyValueStore struct {
|
||||
*SqlStore
|
||||
|
||||
tableSelectQuery sq.SelectBuilder
|
||||
}
|
||||
|
||||
func newPropertyValueStore(sqlStore *SqlStore) store.PropertyValueStore {
|
||||
s := SqlPropertyValueStore{SqlStore: sqlStore}
|
||||
|
||||
s.tableSelectQuery = s.getQueryBuilder().
|
||||
Select("ID", "TargetID", "TargetType", "GroupID", "FieldID", "Value", "CreateAt", "UpdateAt", "DeleteAt").
|
||||
From("PropertyValues")
|
||||
|
||||
return &s
|
||||
}
|
||||
|
||||
func (s *SqlPropertyValueStore) Create(value *model.PropertyValue) (*model.PropertyValue, error) {
|
||||
if value.ID != "" {
|
||||
return nil, store.NewErrInvalidInput("PropertyValue", "id", value.ID)
|
||||
}
|
||||
|
||||
value.PreSave()
|
||||
|
||||
if err := value.IsValid(); err != nil {
|
||||
return nil, errors.Wrap(err, "property_value_create_isvalid")
|
||||
}
|
||||
|
||||
insertMap, err := s.propertyValueToInsertMap(value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
builder := s.getQueryBuilder().
|
||||
Insert("PropertyValues").
|
||||
SetMap(insertMap)
|
||||
|
||||
if _, err := s.GetMaster().ExecBuilder(builder); err != nil {
|
||||
return nil, errors.Wrap(err, "property_value_create_insert")
|
||||
}
|
||||
|
||||
return value, nil
|
||||
}
|
||||
|
||||
func (s *SqlPropertyValueStore) Get(id string) (*model.PropertyValue, error) {
|
||||
queryString, args, err := s.tableSelectQuery.
|
||||
Where(sq.Eq{"id": id}).
|
||||
ToSql()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "property_value_get_tosql")
|
||||
}
|
||||
|
||||
rows, err := s.GetReplica().Query(queryString, args...)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "property_value_get_select")
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
value, err := propertyValueFromRows(rows)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "property_value_get_propertyvaluefromrows")
|
||||
}
|
||||
|
||||
return value, nil
|
||||
}
|
||||
|
||||
func (s *SqlPropertyValueStore) GetMany(ids []string) ([]*model.PropertyValue, error) {
|
||||
queryString, args, err := s.tableSelectQuery.
|
||||
Where(sq.Eq{"id": ids}).
|
||||
ToSql()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "property_value_get_many_tosql")
|
||||
}
|
||||
|
||||
rows, err := s.GetReplica().Query(queryString, args...)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "property_value_get_many_query")
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
values, err := propertyValuesFromRows(rows)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "property_value_get_many_propertyvaluesfromrows")
|
||||
}
|
||||
|
||||
if len(values) < len(ids) {
|
||||
return nil, fmt.Errorf("missmatch results: got %d results of the %d ids passed", len(values), len(ids))
|
||||
}
|
||||
|
||||
return values, nil
|
||||
}
|
||||
|
||||
func (s *SqlPropertyValueStore) SearchPropertyValues(opts model.PropertyValueSearchOpts) ([]*model.PropertyValue, error) {
|
||||
if opts.Page < 0 {
|
||||
return nil, errors.New("page must be positive integer")
|
||||
}
|
||||
|
||||
if opts.PerPage < 1 {
|
||||
return nil, errors.New("per page must be positive integer greater than zero")
|
||||
}
|
||||
|
||||
query := s.tableSelectQuery.
|
||||
OrderBy("CreateAt ASC").
|
||||
Offset(uint64(opts.Page * opts.PerPage)).
|
||||
Limit(uint64(opts.PerPage))
|
||||
|
||||
if !opts.IncludeDeleted {
|
||||
query = query.Where(sq.Eq{"DeleteAt": 0})
|
||||
}
|
||||
|
||||
if opts.GroupID != "" {
|
||||
query = query.Where(sq.Eq{"GroupID": opts.GroupID})
|
||||
}
|
||||
|
||||
if opts.TargetType != "" {
|
||||
query = query.Where(sq.Eq{"TargetType": opts.TargetType})
|
||||
}
|
||||
|
||||
if opts.TargetID != "" {
|
||||
query = query.Where(sq.Eq{"TargetID": opts.TargetID})
|
||||
}
|
||||
|
||||
if opts.FieldID != "" {
|
||||
query = query.Where(sq.Eq{"FieldID": opts.FieldID})
|
||||
}
|
||||
|
||||
queryString, args, err := query.ToSql()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "property_value_search_tosql")
|
||||
}
|
||||
|
||||
rows, err := s.GetReplica().Query(queryString, args...)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "property_value_search_query")
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
values, err := propertyValuesFromRows(rows)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "property_value_search_propertyvaluesfromrows")
|
||||
}
|
||||
|
||||
return values, nil
|
||||
}
|
||||
|
||||
func (s *SqlPropertyValueStore) Update(values []*model.PropertyValue) (_ []*model.PropertyValue, err error) {
|
||||
if len(values) == 0 {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
transaction, err := s.GetMaster().Beginx()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "property_value_update_begin_transaction")
|
||||
}
|
||||
defer finalizeTransactionX(transaction, &err)
|
||||
|
||||
updateTime := model.GetMillis()
|
||||
for _, value := range values {
|
||||
value.UpdateAt = updateTime
|
||||
|
||||
if err := value.IsValid(); err != nil {
|
||||
return nil, errors.Wrap(err, "property_value_update_isvalid")
|
||||
}
|
||||
|
||||
updateMap, err := s.propertyValueToUpdateMap(value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
queryString, args, err := s.getQueryBuilder().
|
||||
Update("PropertyValues").
|
||||
SetMap(updateMap).
|
||||
Where(sq.Eq{"id": value.ID}).
|
||||
ToSql()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "property_value_update_tosql")
|
||||
}
|
||||
|
||||
result, err := transaction.Exec(queryString, args...)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to update property value with id: %s", value.ID)
|
||||
}
|
||||
|
||||
count, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "property_value_update_rowsaffected")
|
||||
}
|
||||
if count == 0 {
|
||||
return nil, store.NewErrNotFound("PropertyValue", value.ID)
|
||||
}
|
||||
}
|
||||
|
||||
if err := transaction.Commit(); err != nil {
|
||||
return nil, errors.Wrap(err, "property_value_update_commit")
|
||||
}
|
||||
|
||||
return values, nil
|
||||
}
|
||||
|
||||
func (s *SqlPropertyValueStore) Delete(id string) error {
|
||||
builder := s.getQueryBuilder().
|
||||
Update("PropertyValues").
|
||||
Set("DeleteAt", model.GetMillis()).
|
||||
Where(sq.Eq{"id": id})
|
||||
|
||||
result, err := s.GetMaster().ExecBuilder(builder)
|
||||
if err != nil {
|
||||
return errors.Wrapf(err, "failed to delete property value with id: %s", id)
|
||||
}
|
||||
|
||||
count, err := result.RowsAffected()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "property_value_delete_rowsaffected")
|
||||
}
|
||||
if count == 0 {
|
||||
return store.NewErrNotFound("PropertyValue", id)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *SqlPropertyValueStore) DeleteForField(fieldID string) error {
|
||||
builder := s.getQueryBuilder().
|
||||
Update("PropertyValues").
|
||||
Set("DeleteAt", model.GetMillis()).
|
||||
Where(sq.Eq{"FieldID": fieldID})
|
||||
|
||||
if _, err := s.GetMaster().ExecBuilder(builder); err != nil {
|
||||
return errors.Wrap(err, "property_value_delete_for_field_exec")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
14
server/channels/store/sqlstore/property_value_store_test.go
Обычный файл
14
server/channels/store/sqlstore/property_value_store_test.go
Обычный файл
@@ -0,0 +1,14 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/mattermost/mattermost/server/v8/channels/store/storetest"
|
||||
)
|
||||
|
||||
func TestPropertyValueStore(t *testing.T) {
|
||||
StoreTestWithSqlStore(t, storetest.TestPropertyValueStore)
|
||||
}
|
||||
@@ -115,6 +115,9 @@ type SqlStoreStores struct {
|
||||
desktopTokens store.DesktopTokensStore
|
||||
channelBookmarks store.ChannelBookmarkStore
|
||||
scheduledPost store.ScheduledPostStore
|
||||
propertyGroup store.PropertyGroupStore
|
||||
propertyField store.PropertyFieldStore
|
||||
propertyValue store.PropertyValueStore
|
||||
}
|
||||
|
||||
type SqlStore struct {
|
||||
@@ -257,6 +260,9 @@ func New(settings model.SqlSettings, logger mlog.LoggerIFace, metrics einterface
|
||||
store.stores.desktopTokens = newSqlDesktopTokensStore(store, metrics)
|
||||
store.stores.channelBookmarks = newSqlChannelBookmarkStore(store)
|
||||
store.stores.scheduledPost = newScheduledPostStore(store)
|
||||
store.stores.propertyGroup = newPropertyGroupStore(store)
|
||||
store.stores.propertyField = newPropertyFieldStore(store)
|
||||
store.stores.propertyValue = newPropertyValueStore(store)
|
||||
|
||||
store.stores.preference.(*SqlPreferenceStore).deleteUnusedFeatures()
|
||||
|
||||
@@ -1060,6 +1066,18 @@ func (ss *SqlStore) ChannelBookmark() store.ChannelBookmarkStore {
|
||||
return ss.stores.channelBookmarks
|
||||
}
|
||||
|
||||
func (ss *SqlStore) PropertyGroup() store.PropertyGroupStore {
|
||||
return ss.stores.propertyGroup
|
||||
}
|
||||
|
||||
func (ss *SqlStore) PropertyField() store.PropertyFieldStore {
|
||||
return ss.stores.propertyField
|
||||
}
|
||||
|
||||
func (ss *SqlStore) PropertyValue() store.PropertyValueStore {
|
||||
return ss.stores.propertyValue
|
||||
}
|
||||
|
||||
func (ss *SqlStore) DropAllTables() {
|
||||
if ss.DriverName() == model.DatabaseDriverPostgres {
|
||||
ss.masterX.Exec(`DO
|
||||
|
||||
Ссылка в новой задаче
Block a user