Updates the property field and value update methods to use a single query for multiple entities (#30198)
* Updates the property field and value update methods to use a single query for multiple entities * Update server/channels/store/sqlstore/property_field_store.go Co-authored-by: Julien Tant <785518+JulienTant@users.noreply.github.com> --------- Co-authored-by: Miguel de la Cruz (aider) <miguel@ctrlz.es> Co-authored-by: Julien Tant <785518+JulienTant@users.noreply.github.com>
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
83ec1d9f7d
Коммит
5ba80d51ae
@@ -128,44 +128,66 @@ func (s *SqlPropertyFieldStore) Update(fields []*model.PropertyField) (_ []*mode
|
|||||||
defer finalizeTransactionX(transaction, &err)
|
defer finalizeTransactionX(transaction, &err)
|
||||||
|
|
||||||
updateTime := model.GetMillis()
|
updateTime := model.GetMillis()
|
||||||
for _, field := range fields {
|
isPostgres := s.DriverName() == model.DatabaseDriverPostgres
|
||||||
field.UpdateAt = updateTime
|
nameCase := sq.Case("id")
|
||||||
|
typeCase := sq.Case("id")
|
||||||
|
attrsCase := sq.Case("id")
|
||||||
|
targetIDCase := sq.Case("id")
|
||||||
|
targetTypeCase := sq.Case("id")
|
||||||
|
deleteAtCase := sq.Case("id")
|
||||||
|
ids := make([]string, len(fields))
|
||||||
|
|
||||||
|
for i, field := range fields {
|
||||||
|
field.UpdateAt = updateTime
|
||||||
if vErr := field.IsValid(); vErr != nil {
|
if vErr := field.IsValid(); vErr != nil {
|
||||||
return nil, errors.Wrap(vErr, "property_field_update_isvalid")
|
return nil, errors.Wrap(vErr, "property_field_update_isvalid")
|
||||||
}
|
}
|
||||||
|
|
||||||
queryString, args, err := s.getQueryBuilder().
|
ids[i] = field.ID
|
||||||
Update("PropertyFields").
|
whenID := sq.Expr("?", field.ID)
|
||||||
Set("Name", field.Name).
|
if isPostgres {
|
||||||
Set("Type", field.Type).
|
nameCase = nameCase.When(whenID, sq.Expr("?::text", field.Name))
|
||||||
Set("Attrs", field.Attrs).
|
typeCase = typeCase.When(whenID, sq.Expr("?::property_field_type", field.Type))
|
||||||
Set("TargetID", field.TargetID).
|
attrsCase = attrsCase.When(whenID, sq.Expr("?::jsonb", field.Attrs))
|
||||||
Set("TargetType", field.TargetType).
|
targetIDCase = targetIDCase.When(whenID, sq.Expr("?::text", field.TargetID))
|
||||||
Set("UpdateAt", field.UpdateAt).
|
targetTypeCase = targetTypeCase.When(whenID, sq.Expr("?::text", field.TargetType))
|
||||||
Set("DeleteAt", field.DeleteAt).
|
deleteAtCase = deleteAtCase.When(whenID, sq.Expr("?::bigint", field.DeleteAt))
|
||||||
Where(sq.Eq{"id": field.ID}).
|
} else {
|
||||||
ToSql()
|
nameCase = nameCase.When(whenID, sq.Expr("?", field.Name))
|
||||||
if err != nil {
|
typeCase = typeCase.When(whenID, sq.Expr("?", field.Type))
|
||||||
return nil, errors.Wrap(err, "property_field_update_tosql")
|
attrsCase = attrsCase.When(whenID, sq.Expr("?", field.Attrs))
|
||||||
}
|
targetIDCase = targetIDCase.When(whenID, sq.Expr("?", field.TargetID))
|
||||||
|
targetTypeCase = targetTypeCase.When(whenID, sq.Expr("?", field.TargetType))
|
||||||
result, err := transaction.Exec(queryString, args...)
|
deleteAtCase = deleteAtCase.When(whenID, sq.Expr("?", field.DeleteAt))
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
builder := s.getQueryBuilder().
|
||||||
|
Update("PropertyFields").
|
||||||
|
Set("Name", nameCase).
|
||||||
|
Set("Type", typeCase).
|
||||||
|
Set("Attrs", attrsCase).
|
||||||
|
Set("TargetID", targetIDCase).
|
||||||
|
Set("TargetType", targetTypeCase).
|
||||||
|
Set("UpdateAt", updateTime).
|
||||||
|
Set("DeleteAt", deleteAtCase).
|
||||||
|
Where(sq.Eq{"id": ids})
|
||||||
|
|
||||||
|
result, err := transaction.ExecBuilder(builder)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "property_field_update_exec")
|
||||||
|
}
|
||||||
|
|
||||||
|
count, err := result.RowsAffected()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "property_field_update_rowsaffected")
|
||||||
|
}
|
||||||
|
if count != int64(len(fields)) {
|
||||||
|
return nil, errors.Errorf("failed to update, some property fields were not found, got %d of %d", count, len(fields))
|
||||||
|
}
|
||||||
|
|
||||||
if err := transaction.Commit(); err != nil {
|
if err := transaction.Commit(); err != nil {
|
||||||
return nil, errors.Wrap(err, "property_field_update_commit")
|
return nil, errors.Wrap(err, "property_field_update_commit_transaction")
|
||||||
}
|
}
|
||||||
|
|
||||||
return fields, nil
|
return fields, nil
|
||||||
|
|||||||
@@ -136,45 +136,54 @@ func (s *SqlPropertyValueStore) Update(values []*model.PropertyValue) (_ []*mode
|
|||||||
defer finalizeTransactionX(transaction, &err)
|
defer finalizeTransactionX(transaction, &err)
|
||||||
|
|
||||||
updateTime := model.GetMillis()
|
updateTime := model.GetMillis()
|
||||||
for _, value := range values {
|
isPostgres := s.DriverName() == model.DatabaseDriverPostgres
|
||||||
value.UpdateAt = updateTime
|
valueCase := sq.Case("id")
|
||||||
|
deleteAtCase := sq.Case("id")
|
||||||
|
ids := make([]string, len(values))
|
||||||
|
|
||||||
if err := value.IsValid(); err != nil {
|
for i, value := range values {
|
||||||
return nil, errors.Wrap(err, "property_value_update_isvalid")
|
value.UpdateAt = updateTime
|
||||||
|
if vErr := value.IsValid(); vErr != nil {
|
||||||
|
return nil, errors.Wrap(vErr, "property_value_update_isvalid")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ids[i] = value.ID
|
||||||
valueJSON := value.Value
|
valueJSON := value.Value
|
||||||
if s.IsBinaryParamEnabled() {
|
if s.IsBinaryParamEnabled() {
|
||||||
valueJSON = AppendBinaryFlag(valueJSON)
|
valueJSON = AppendBinaryFlag(valueJSON)
|
||||||
}
|
}
|
||||||
|
|
||||||
queryString, args, err := s.getQueryBuilder().
|
if isPostgres {
|
||||||
Update("PropertyValues").
|
valueCase = valueCase.When(sq.Expr("?", value.ID), sq.Expr("?::jsonb", valueJSON))
|
||||||
Set("Value", valueJSON).
|
deleteAtCase = deleteAtCase.When(sq.Expr("?", value.ID), sq.Expr("?::bigint", value.DeleteAt))
|
||||||
Set("UpdateAt", value.UpdateAt).
|
} else {
|
||||||
Set("DeleteAt", value.DeleteAt).
|
valueCase = valueCase.When(sq.Expr("?", value.ID), sq.Expr("?", valueJSON))
|
||||||
Where(sq.Eq{"id": value.ID}).
|
deleteAtCase = deleteAtCase.When(sq.Expr("?", value.ID), sq.Expr("?", value.DeleteAt))
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
builder := s.getQueryBuilder().
|
||||||
|
Update("PropertyValues").
|
||||||
|
Set("Value", valueCase).
|
||||||
|
Set("DeleteAt", deleteAtCase).
|
||||||
|
Set("UpdateAt", updateTime).
|
||||||
|
Where(sq.Eq{"id": ids})
|
||||||
|
|
||||||
|
result, err := transaction.ExecBuilder(builder)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "property_value_update_exec")
|
||||||
|
}
|
||||||
|
|
||||||
|
count, err := result.RowsAffected()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "property_value_update_rowsaffected")
|
||||||
|
}
|
||||||
|
if count != int64(len(values)) {
|
||||||
|
return nil, errors.Errorf("failed to update, some property values were not found, got %d of %d", count, len(values))
|
||||||
|
}
|
||||||
|
|
||||||
if err := transaction.Commit(); err != nil {
|
if err := transaction.Commit(); err != nil {
|
||||||
return nil, errors.Wrap(err, "property_value_update_commit")
|
return nil, errors.Wrap(err, "property_value_update_commit_transaction")
|
||||||
}
|
}
|
||||||
|
|
||||||
return values, nil
|
return values, nil
|
||||||
|
|||||||
@@ -146,8 +146,7 @@ func testUpdatePropertyField(t *testing.T, _ request.CTX, ss store.Store) {
|
|||||||
}
|
}
|
||||||
updatedField, err := ss.PropertyField().Update([]*model.PropertyField{field})
|
updatedField, err := ss.PropertyField().Update([]*model.PropertyField{field})
|
||||||
require.Zero(t, updatedField)
|
require.Zero(t, updatedField)
|
||||||
var enf *store.ErrNotFound
|
require.ErrorContains(t, err, "failed to update, some property fields were not found, got 0 of 1")
|
||||||
require.ErrorAs(t, err, &enf)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("should fail if the property field is not valid", func(t *testing.T) {
|
t.Run("should fail if the property field is not valid", func(t *testing.T) {
|
||||||
@@ -280,6 +279,46 @@ func testUpdatePropertyField(t *testing.T, _ request.CTX, ss store.Store) {
|
|||||||
require.Equal(t, groupID, updated2.GroupID)
|
require.Equal(t, groupID, updated2.GroupID)
|
||||||
require.Equal(t, originalUpdateAt2, updated2.UpdateAt)
|
require.Equal(t, originalUpdateAt2, updated2.UpdateAt)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("should not update any fields if one update points to a nonexisting one", func(t *testing.T) {
|
||||||
|
// Create a valid field
|
||||||
|
field1 := &model.PropertyField{
|
||||||
|
GroupID: model.NewId(),
|
||||||
|
Name: "First field",
|
||||||
|
Type: model.PropertyFieldTypeText,
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := ss.PropertyField().Create(field1)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
originalUpdateAt := field1.UpdateAt
|
||||||
|
|
||||||
|
// Try to update both the valid field and a nonexistent one
|
||||||
|
field2 := &model.PropertyField{
|
||||||
|
ID: model.NewId(),
|
||||||
|
GroupID: model.NewId(),
|
||||||
|
Name: "Second field",
|
||||||
|
Type: model.PropertyFieldTypeText,
|
||||||
|
TargetID: model.NewId(),
|
||||||
|
TargetType: "test_type",
|
||||||
|
CreateAt: 1,
|
||||||
|
Attrs: map[string]any{
|
||||||
|
"key": "value",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
field1.Name = "Updated First"
|
||||||
|
|
||||||
|
_, err = ss.PropertyField().Update([]*model.PropertyField{field1, field2})
|
||||||
|
require.Error(t, err)
|
||||||
|
require.ErrorContains(t, err, "failed to update, some property fields were not found")
|
||||||
|
|
||||||
|
// Check that the valid field was not updated
|
||||||
|
updated1, err := ss.PropertyField().Get(field1.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, "First field", updated1.Name)
|
||||||
|
require.Equal(t, originalUpdateAt, updated1.UpdateAt)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func testDeletePropertyField(t *testing.T, _ request.CTX, ss store.Store) {
|
func testDeletePropertyField(t *testing.T, _ request.CTX, ss store.Store) {
|
||||||
|
|||||||
@@ -149,8 +149,7 @@ func testUpdatePropertyValue(t *testing.T, _ request.CTX, ss store.Store) {
|
|||||||
}
|
}
|
||||||
updatedValue, err := ss.PropertyValue().Update([]*model.PropertyValue{value})
|
updatedValue, err := ss.PropertyValue().Update([]*model.PropertyValue{value})
|
||||||
require.Zero(t, updatedValue)
|
require.Zero(t, updatedValue)
|
||||||
var enf *store.ErrNotFound
|
require.ErrorContains(t, err, "failed to update, some property values were not found, got 0 of 1")
|
||||||
require.ErrorAs(t, err, &enf)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("should fail if the property value is not valid", func(t *testing.T) {
|
t.Run("should fail if the property value is not valid", func(t *testing.T) {
|
||||||
@@ -220,7 +219,7 @@ func testUpdatePropertyValue(t *testing.T, _ request.CTX, ss store.Store) {
|
|||||||
require.Greater(t, updated2.UpdateAt, updated2.CreateAt)
|
require.Greater(t, updated2.UpdateAt, updated2.CreateAt)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("should not update any fields if one update is invalid", func(t *testing.T) {
|
t.Run("should not update any values if one update is invalid", func(t *testing.T) {
|
||||||
// Create two valid values
|
// Create two valid values
|
||||||
groupID := model.NewId()
|
groupID := model.NewId()
|
||||||
value1 := &model.PropertyValue{
|
value1 := &model.PropertyValue{
|
||||||
@@ -266,6 +265,45 @@ func testUpdatePropertyValue(t *testing.T, _ request.CTX, ss store.Store) {
|
|||||||
require.Equal(t, groupID, updated2.GroupID)
|
require.Equal(t, groupID, updated2.GroupID)
|
||||||
require.Equal(t, originalUpdateAt2, updated2.UpdateAt)
|
require.Equal(t, originalUpdateAt2, updated2.UpdateAt)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
t.Run("should not update any values if one update points to a nonexisting one", func(t *testing.T) {
|
||||||
|
// Create a valid value
|
||||||
|
value1 := &model.PropertyValue{
|
||||||
|
TargetID: model.NewId(),
|
||||||
|
TargetType: "test_type",
|
||||||
|
GroupID: model.NewId(),
|
||||||
|
FieldID: model.NewId(),
|
||||||
|
Value: json.RawMessage(`"Value 1"`),
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := ss.PropertyValue().Create(value1)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
originalUpdateAt := value1.UpdateAt
|
||||||
|
|
||||||
|
// Try to update both the valid value and a nonexistent one
|
||||||
|
value2 := &model.PropertyValue{
|
||||||
|
ID: model.NewId(),
|
||||||
|
TargetID: model.NewId(),
|
||||||
|
CreateAt: 1,
|
||||||
|
TargetType: "test_type",
|
||||||
|
GroupID: model.NewId(),
|
||||||
|
FieldID: model.NewId(),
|
||||||
|
Value: json.RawMessage(`"Value 2"`),
|
||||||
|
}
|
||||||
|
|
||||||
|
value1.Value = json.RawMessage(`"Updated Value 1"`)
|
||||||
|
|
||||||
|
_, err = ss.PropertyValue().Update([]*model.PropertyValue{value1, value2})
|
||||||
|
require.Error(t, err)
|
||||||
|
require.ErrorContains(t, err, "failed to update, some property values were not found")
|
||||||
|
|
||||||
|
// Check that the valid value was not updated
|
||||||
|
updated1, err := ss.PropertyValue().Get(value1.ID)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, json.RawMessage(`"Value 1"`), updated1.Value)
|
||||||
|
require.Equal(t, originalUpdateAt, updated1.UpdateAt)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func testDeletePropertyValue(t *testing.T, _ request.CTX, ss store.Store) {
|
func testDeletePropertyValue(t *testing.T, _ request.CTX, ss store.Store) {
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user