Allows SearchPropertyValues to target master with the context mechanism

This commit is contained in:
Miguel de la Cruz
2026-08-26 17:10:53 +02:00
parent cf056ebcb4
commit ba9a39ef41
8 changed files with 71 additions and 69 deletions
@@ -304,7 +304,7 @@ func (h *AccessControlHook) PostGetPropertyField(rctx request.CTX, field *model.
}
callerID := h.extractCallerID(rctx)
return h.applyFieldReadAccessControl(field, callerID), nil
return h.applyFieldReadAccessControl(rctx, field, callerID), nil
}
// PostGetPropertyFields applies read access control to a list of fields.
@@ -319,7 +319,7 @@ func (h *AccessControlHook) PostGetPropertyFields(rctx request.CTX, fields []*mo
}
callerID := h.extractCallerID(rctx)
return h.applyFieldReadAccessControlToList(fields, callerID), nil
return h.applyFieldReadAccessControlToList(rctx, fields, callerID), nil
}
// Value Pre-Hooks
@@ -516,7 +516,7 @@ func (h *AccessControlHook) PreDeletePropertyValuesForTarget(rctx request.CTX, g
opts.Cursor = cursor
}
values, err := h.propertyService.searchPropertyValues(groupID, opts)
values, err := h.propertyService.searchPropertyValues(rctx, groupID, opts)
if err != nil {
return err
}
@@ -911,7 +911,7 @@ func (h *AccessControlHook) checkValueWriteAccess(field *model.PropertyField, ca
}
// getCallerValuesForField retrieves all property values for the caller on a specific field.
func (h *AccessControlHook) getCallerValuesForField(groupID, fieldID, callerID string) ([]*model.PropertyValue, error) {
func (h *AccessControlHook) getCallerValuesForField(rctx request.CTX, groupID, fieldID, callerID string) ([]*model.PropertyValue, error) {
if callerID == "" {
return []*model.PropertyValue{}, nil
}
@@ -936,7 +936,7 @@ func (h *AccessControlHook) getCallerValuesForField(groupID, fieldID, callerID s
opts.Cursor = cursor
}
values, err := h.propertyService.searchPropertyValues(groupID, opts)
values, err := h.propertyService.searchPropertyValues(rctx, groupID, opts)
if err != nil {
return nil, fmt.Errorf("failed to get caller values for field: %w", err)
}
@@ -1010,8 +1010,8 @@ func (h *AccessControlHook) copyPropertyField(field *model.PropertyField) *model
}
// getCallerOptionIDsForField retrieves the caller's values for a field and extracts all option IDs.
func (h *AccessControlHook) getCallerOptionIDsForField(groupID, fieldID, callerID string, fieldType model.PropertyFieldType) (map[string]struct{}, error) {
callerValues, err := h.getCallerValuesForField(groupID, fieldID, callerID)
func (h *AccessControlHook) getCallerOptionIDsForField(rctx request.CTX, groupID, fieldID, callerID string, fieldType model.PropertyFieldType) (map[string]struct{}, error) {
callerValues, err := h.getCallerValuesForField(rctx, groupID, fieldID, callerID)
if err != nil {
return make(map[string]struct{}), err
}
@@ -1039,16 +1039,16 @@ func (h *AccessControlHook) getCallerOptionIDsForField(groupID, fieldID, callerI
// field exposes every option at or below the caller's own rank ("everything at
// your rank and lower"), so a higher-cleared caller sees the full ladder up to
// their level. See filterSharedOnlyRankFieldOptions.
func (h *AccessControlHook) filterSharedOnlyFieldOptions(field *model.PropertyField, callerID string) *model.PropertyField {
func (h *AccessControlHook) filterSharedOnlyFieldOptions(rctx request.CTX, field *model.PropertyField, callerID string) *model.PropertyField {
if !field.Type.SupportsOptions() {
return field
}
if field.Type == model.PropertyFieldTypeRank {
return h.filterSharedOnlyRankFieldOptions(field, callerID)
return h.filterSharedOnlyRankFieldOptions(rctx, field, callerID)
}
callerOptionIDs, err := h.getCallerOptionIDsForField(field.GroupID, field.ID, callerID, field.Type)
callerOptionIDs, err := h.getCallerOptionIDsForField(rctx, field.GroupID, field.ID, callerID, field.Type)
if err != nil || len(callerOptionIDs) == 0 {
filteredField := h.copyPropertyField(field)
filteredField.Attrs[model.PropertyFieldAttributeOptions] = []any{}
@@ -1092,7 +1092,7 @@ func (h *AccessControlHook) filterSharedOnlyFieldOptions(field *model.PropertyFi
// or below the caller's own rank, rather than the exact-match intersection
// used for select/multiselect. A caller who holds no value for the field (and
// therefore has no rank) sees no options.
func (h *AccessControlHook) filterSharedOnlyRankFieldOptions(field *model.PropertyField, callerID string) *model.PropertyField {
func (h *AccessControlHook) filterSharedOnlyRankFieldOptions(rctx request.CTX, field *model.PropertyField, callerID string) *model.PropertyField {
// Bail out before building the rank map or the caller-rank store lookup when
// there are no options to filter: an absent or malformed options array has
// nothing to hide, so the field is returned untouched.
@@ -1109,7 +1109,7 @@ func (h *AccessControlHook) filterSharedOnlyRankFieldOptions(field *model.Proper
}
rankByID := buildOptionRankMap(field)
callerRank, ok := h.callerRankForField(field, callerID, rankByID)
callerRank, ok := h.callerRankForField(rctx, field, callerID, rankByID)
if !ok {
filteredField := h.copyPropertyField(field)
filteredField.Attrs[model.PropertyFieldAttributeOptions] = []any{}
@@ -1145,8 +1145,8 @@ func (h *AccessControlHook) filterSharedOnlyRankFieldOptions(field *model.Proper
// single value per user), so the caller has at most one option; we take it.
// ok is false when the caller has no value for the field or the option carries
// no rank, in which case the caller has no clearance and sees nothing.
func (h *AccessControlHook) callerRankForField(field *model.PropertyField, callerID string, rankByID map[string]int) (int, bool) {
callerOptionIDs, err := h.getCallerOptionIDsForField(field.GroupID, field.ID, callerID, field.Type)
func (h *AccessControlHook) callerRankForField(rctx request.CTX, field *model.PropertyField, callerID string, rankByID map[string]int) (int, bool) {
callerOptionIDs, err := h.getCallerOptionIDsForField(rctx, field.GroupID, field.ID, callerID, field.Type)
if err != nil || len(callerOptionIDs) == 0 {
return 0, false
}
@@ -1197,16 +1197,16 @@ func buildOptionRankMap(field *model.PropertyField) map[string]int {
// The binary path is what protects scenarios like LDAP/SAML-synced text codenames whose
// existence is itself controlled information: a caller who doesn't hold the same value
// must not see the target's value through any read endpoint.
func (h *AccessControlHook) filterSharedOnlyValue(field *model.PropertyField, value *model.PropertyValue, callerID string) *model.PropertyValue {
func (h *AccessControlHook) filterSharedOnlyValue(rctx request.CTX, field *model.PropertyField, value *model.PropertyValue, callerID string) *model.PropertyValue {
if field.Type == model.PropertyFieldTypeRank {
return h.filterSharedOnlyRankValue(field, value, callerID)
return h.filterSharedOnlyRankValue(rctx, field, value, callerID)
}
if field.Type != model.PropertyFieldTypeSelect && field.Type != model.PropertyFieldTypeMultiselect {
return h.filterSharedOnlyScalarValue(field, value, callerID)
return h.filterSharedOnlyScalarValue(rctx, field, value, callerID)
}
callerOptionIDs, err := h.getCallerOptionIDsForField(field.GroupID, field.ID, callerID, field.Type)
callerOptionIDs, err := h.getCallerOptionIDsForField(rctx, field.GroupID, field.ID, callerID, field.Type)
if err != nil || len(callerOptionIDs) == 0 {
return nil
}
@@ -1261,9 +1261,9 @@ func (h *AccessControlHook) filterSharedOnlyValue(field *model.PropertyField, va
// option match. A caller who holds no value of their own (and therefore has no
// rank) sees nothing. A rank field is select-shaped, so the target has at most
// one option.
func (h *AccessControlHook) filterSharedOnlyRankValue(field *model.PropertyField, value *model.PropertyValue, callerID string) *model.PropertyValue {
func (h *AccessControlHook) filterSharedOnlyRankValue(rctx request.CTX, field *model.PropertyField, value *model.PropertyValue, callerID string) *model.PropertyValue {
rankByID := buildOptionRankMap(field)
callerRank, ok := h.callerRankForField(field, callerID, rankByID)
callerRank, ok := h.callerRankForField(rctx, field, callerID, rankByID)
if !ok {
return nil
}
@@ -1319,12 +1319,12 @@ func (h *AccessControlHook) clampRankValueToRank(value *model.PropertyValue, ran
// returns the value as-is if the caller's own stored value for the same field equals
// the target's value, otherwise nil. Caller and target may legitimately store nothing,
// in which case the value is hidden.
func (h *AccessControlHook) filterSharedOnlyScalarValue(field *model.PropertyField, value *model.PropertyValue, callerID string) *model.PropertyValue {
func (h *AccessControlHook) filterSharedOnlyScalarValue(rctx request.CTX, field *model.PropertyField, value *model.PropertyValue, callerID string) *model.PropertyValue {
if value == nil || len(value.Value) == 0 {
return nil
}
callerValues, err := h.getCallerValuesForField(field.GroupID, field.ID, callerID)
callerValues, err := h.getCallerValuesForField(rctx, field.GroupID, field.ID, callerID)
if err != nil || len(callerValues) == 0 {
return nil
}
@@ -1344,7 +1344,7 @@ func (h *AccessControlHook) filterSharedOnlyScalarValue(field *model.PropertyFie
// - Any access mode when the caller is the field's source plugin: returned as-is
// - Shared-only fields: returned with options filtered using filterSharedOnlyFieldOptions
// - Source-only or unknown access modes: returned with empty options (secure default)
func (h *AccessControlHook) applyFieldReadAccessControl(field *model.PropertyField, callerID string) *model.PropertyField {
func (h *AccessControlHook) applyFieldReadAccessControl(rctx request.CTX, field *model.PropertyField, callerID string) *model.PropertyField {
if h.hasUnrestrictedFieldReadAccess(field, callerID) {
return field
}
@@ -1352,7 +1352,7 @@ func (h *AccessControlHook) applyFieldReadAccessControl(field *model.PropertyFie
accessMode := h.getAccessMode(field)
if accessMode == model.PropertyAccessModeSharedOnly {
return h.filterSharedOnlyFieldOptions(field, callerID)
return h.filterSharedOnlyFieldOptions(rctx, field, callerID)
}
// Source-only or unknown: return with empty options (secure default)
@@ -1364,14 +1364,14 @@ func (h *AccessControlHook) applyFieldReadAccessControl(field *model.PropertyFie
}
// applyFieldReadAccessControlToList applies read access control to a list of fields.
func (h *AccessControlHook) applyFieldReadAccessControlToList(fields []*model.PropertyField, callerID string) []*model.PropertyField {
func (h *AccessControlHook) applyFieldReadAccessControlToList(rctx request.CTX, fields []*model.PropertyField, callerID string) []*model.PropertyField {
if len(fields) == 0 {
return fields
}
filtered := make([]*model.PropertyField, 0, len(fields))
for _, field := range fields {
filtered = append(filtered, h.applyFieldReadAccessControl(field, callerID))
filtered = append(filtered, h.applyFieldReadAccessControl(rctx, field, callerID))
}
return filtered
@@ -1434,7 +1434,7 @@ func (h *AccessControlHook) applyValueReadAccessControl(rctx request.CTX, values
if h.hasUnrestrictedFieldReadAccess(field, callerID) {
filtered = append(filtered, value)
} else if accessMode == model.PropertyAccessModeSharedOnly {
filteredValue := h.filterSharedOnlyValue(field, value, callerID)
filteredValue := h.filterSharedOnlyValue(rctx, field, value, callerID)
if filteredValue != nil {
filtered = append(filtered, filteredValue)
}
@@ -79,11 +79,11 @@ func (ps *PropertyService) getPropertyValues(groupID string, ids []string) ([]*m
return ps.valueStore.GetMany(groupID, ids)
}
func (ps *PropertyService) searchPropertyValues(groupID string, opts model.PropertyValueSearchOpts) ([]*model.PropertyValue, error) {
func (ps *PropertyService) searchPropertyValues(rctx request.CTX, groupID string, opts model.PropertyValueSearchOpts) ([]*model.PropertyValue, error) {
// groupID is part of the search method signature to
// incentivize the use of the database indexes in searches
opts.GroupID = groupID
return ps.valueStore.SearchPropertyValues(opts)
return ps.valueStore.SearchPropertyValues(rctx, opts)
}
func (ps *PropertyService) updatePropertyValue(rctx request.CTX, groupID string, value *model.PropertyValue) (*model.PropertyValue, error) {
@@ -196,7 +196,7 @@ func (ps *PropertyService) GetPropertyValues(rctx request.CTX, groupID string, i
}
func (ps *PropertyService) SearchPropertyValues(rctx request.CTX, groupID string, opts model.PropertyValueSearchOpts) ([]*model.PropertyValue, error) {
values, err := ps.searchPropertyValues(groupID, opts)
values, err := ps.searchPropertyValues(rctx, groupID, opts)
if err != nil {
return nil, fmt.Errorf("SearchPropertyValues: %w", err)
}
@@ -10980,11 +10980,11 @@ func (s *RetryLayerPropertyValueStore) GetMany(groupID string, ids []string) ([]
}
func (s *RetryLayerPropertyValueStore) SearchPropertyValues(opts model.PropertyValueSearchOpts) ([]*model.PropertyValue, error) {
func (s *RetryLayerPropertyValueStore) SearchPropertyValues(rctx request.CTX, opts model.PropertyValueSearchOpts) ([]*model.PropertyValue, error) {
tries := 0
for {
result, err := s.PropertyValueStore.SearchPropertyValues(opts)
result, err := s.PropertyValueStore.SearchPropertyValues(rctx, opts)
if err == nil {
return result, nil
}
@@ -11,6 +11,7 @@ import (
"github.com/pkg/errors"
"github.com/mattermost/mattermost/server/public/model"
"github.com/mattermost/mattermost/server/public/shared/request"
"github.com/mattermost/mattermost/server/v8/channels/store"
)
@@ -134,7 +135,7 @@ func (s *SqlPropertyValueStore) GetMany(groupID string, ids []string) ([]*model.
return values, nil
}
func (s *SqlPropertyValueStore) SearchPropertyValues(opts model.PropertyValueSearchOpts) ([]*model.PropertyValue, error) {
func (s *SqlPropertyValueStore) SearchPropertyValues(rctx request.CTX, opts model.PropertyValueSearchOpts) ([]*model.PropertyValue, error) {
if err := opts.IsValid(); err != nil {
return nil, fmt.Errorf("opts is invalid: %w", err)
}
@@ -204,7 +205,7 @@ func (s *SqlPropertyValueStore) SearchPropertyValues(opts model.PropertyValueSea
}
var values []*model.PropertyValue
if err := s.GetReplica().SelectBuilder(&values, builder); err != nil {
if err := s.DBXFromContext(rctx.Context()).SelectBuilder(&values, builder); err != nil {
return nil, errors.Wrap(err, "property_value_search_query")
}
+1 -1
View File
@@ -1209,7 +1209,7 @@ type PropertyValueStore interface {
CreateMany(values []*model.PropertyValue) ([]*model.PropertyValue, error)
Get(groupID, id string) (*model.PropertyValue, error)
GetMany(groupID string, ids []string) ([]*model.PropertyValue, error)
SearchPropertyValues(opts model.PropertyValueSearchOpts) ([]*model.PropertyValue, error)
SearchPropertyValues(rctx request.CTX, opts model.PropertyValueSearchOpts) ([]*model.PropertyValue, error)
Update(groupID string, values []*model.PropertyValue) ([]*model.PropertyValue, error)
Upsert(values []*model.PropertyValue) ([]*model.PropertyValue, error)
Delete(groupID string, id string) error
@@ -6,6 +6,7 @@ package mocks
import (
model "github.com/mattermost/mattermost/server/public/model"
request "github.com/mattermost/mattermost/server/public/shared/request"
mock "github.com/stretchr/testify/mock"
)
@@ -188,9 +189,9 @@ func (_m *PropertyValueStore) GetMany(groupID string, ids []string) ([]*model.Pr
return r0, r1
}
// SearchPropertyValues provides a mock function with given fields: opts
func (_m *PropertyValueStore) SearchPropertyValues(opts model.PropertyValueSearchOpts) ([]*model.PropertyValue, error) {
ret := _m.Called(opts)
// SearchPropertyValues provides a mock function with given fields: rctx, opts
func (_m *PropertyValueStore) SearchPropertyValues(rctx request.CTX, opts model.PropertyValueSearchOpts) ([]*model.PropertyValue, error) {
ret := _m.Called(rctx, opts)
if len(ret) == 0 {
panic("no return value specified for SearchPropertyValues")
@@ -198,19 +199,19 @@ func (_m *PropertyValueStore) SearchPropertyValues(opts model.PropertyValueSearc
var r0 []*model.PropertyValue
var r1 error
if rf, ok := ret.Get(0).(func(model.PropertyValueSearchOpts) ([]*model.PropertyValue, error)); ok {
return rf(opts)
if rf, ok := ret.Get(0).(func(request.CTX, model.PropertyValueSearchOpts) ([]*model.PropertyValue, error)); ok {
return rf(rctx, opts)
}
if rf, ok := ret.Get(0).(func(model.PropertyValueSearchOpts) []*model.PropertyValue); ok {
r0 = rf(opts)
if rf, ok := ret.Get(0).(func(request.CTX, model.PropertyValueSearchOpts) []*model.PropertyValue); ok {
r0 = rf(rctx, opts)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*model.PropertyValue)
}
}
if rf, ok := ret.Get(1).(func(model.PropertyValueSearchOpts) error); ok {
r1 = rf(opts)
if rf, ok := ret.Get(1).(func(request.CTX, model.PropertyValueSearchOpts) error); ok {
r1 = rf(rctx, opts)
} else {
r1 = ret.Error(1)
}
@@ -97,7 +97,7 @@ func testCreatePropertyValue(t *testing.T, _ request.CTX, ss store.Store) {
})
}
func testCreateManyPropertyValues(t *testing.T, _ request.CTX, ss store.Store) {
func testCreateManyPropertyValues(t *testing.T, rctx request.CTX, ss store.Store) {
t.Run("should return nil when given empty slice", func(t *testing.T) {
values, err := ss.PropertyValue().CreateMany([]*model.PropertyValue{})
require.NoError(t, err)
@@ -126,7 +126,7 @@ func testCreateManyPropertyValues(t *testing.T, _ request.CTX, ss store.Store) {
require.ErrorContains(t, err, "model.property_value.is_valid.app_error")
// Verify no values were created
results, err := ss.PropertyValue().SearchPropertyValues(model.PropertyValueSearchOpts{
results, err := ss.PropertyValue().SearchPropertyValues(rctx, model.PropertyValueSearchOpts{
TargetIDs: []string{validValue.TargetID},
PerPage: 10,
})
@@ -248,7 +248,7 @@ func testCreateManyPropertyValues(t *testing.T, _ request.CTX, ss store.Store) {
require.Zero(t, values)
// Verify the unique value was not created due to transaction rollback
results, err := ss.PropertyValue().SearchPropertyValues(model.PropertyValueSearchOpts{
results, err := ss.PropertyValue().SearchPropertyValues(rctx, model.PropertyValueSearchOpts{
TargetIDs: []string{value3.TargetID},
PerPage: 10,
})
@@ -846,7 +846,7 @@ func testUpdatePropertyValue(t *testing.T, _ request.CTX, ss store.Store) {
})
}
func testUpsertPropertyValue(t *testing.T, _ request.CTX, ss store.Store, s SqlStore) {
func testUpsertPropertyValue(t *testing.T, rctx request.CTX, ss store.Store, s SqlStore) {
t.Run("should fail if the property value is not valid", func(t *testing.T) {
value := &model.PropertyValue{
TargetID: "",
@@ -1000,7 +1000,7 @@ func testUpsertPropertyValue(t *testing.T, _ request.CTX, ss store.Store, s SqlS
require.Equal(t, originalValue.UpdateAt, retrieved.UpdateAt)
// Verify the invalid value was not inserted
results, err := ss.PropertyValue().SearchPropertyValues(model.PropertyValueSearchOpts{
results, err := ss.PropertyValue().SearchPropertyValues(rctx, model.PropertyValueSearchOpts{
TargetIDs: []string{invalidValue.TargetID},
PerPage: 10,
})
@@ -1215,7 +1215,7 @@ func testDeletePropertyValue(t *testing.T, _ request.CTX, ss store.Store) {
})
}
func testSearchPropertyValues(t *testing.T, _ request.CTX, ss store.Store, s SqlStore) {
func testSearchPropertyValues(t *testing.T, rctx request.CTX, ss store.Store, s SqlStore) {
groupID := model.NewId()
targetID := model.NewId()
fieldID := model.NewId()
@@ -1408,7 +1408,7 @@ func testSearchPropertyValues(t *testing.T, _ request.CTX, ss store.Store, s Sql
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
results, err := ss.PropertyValue().SearchPropertyValues(tc.opts)
results, err := ss.PropertyValue().SearchPropertyValues(rctx, tc.opts)
if tc.expectedError {
require.Error(t, err)
return
@@ -1426,7 +1426,7 @@ func testSearchPropertyValues(t *testing.T, _ request.CTX, ss store.Store, s Sql
t.Run("null columns, before createdBy and updatedBy migrations", func(t *testing.T) {
nullGroupID, _, _, nullValueID := insertPropertyValueWithNullColumns(t, ss, s)
results, err := ss.PropertyValue().SearchPropertyValues(model.PropertyValueSearchOpts{
results, err := ss.PropertyValue().SearchPropertyValues(rctx, model.PropertyValueSearchOpts{
GroupID: nullGroupID,
PerPage: 10,
})
@@ -1481,7 +1481,7 @@ func testSearchPropertyValues(t *testing.T, _ request.CTX, ss store.Store, s Sql
t.Run("SinceUpdateAt filters correctly by UpdateAt", func(t *testing.T) {
// `>=` semantics: value1 is included at the boundary,
// plus value3 and the post-update value2.
results, err := ss.PropertyValue().SearchPropertyValues(model.PropertyValueSearchOpts{
results, err := ss.PropertyValue().SearchPropertyValues(rctx, model.PropertyValueSearchOpts{
GroupID: groupID,
SinceUpdateAt: value1.UpdateAt,
PerPage: 10,
@@ -1499,7 +1499,7 @@ func testSearchPropertyValues(t *testing.T, _ request.CTX, ss store.Store, s Sql
t.Run("SinceUpdateAt with boundary condition", func(t *testing.T) {
// `value3.UpdateAt - 1` keeps value3 in the window and value2's
// post-Update timestamp is even later, so both are returned.
results, err := ss.PropertyValue().SearchPropertyValues(model.PropertyValueSearchOpts{
results, err := ss.PropertyValue().SearchPropertyValues(rctx, model.PropertyValueSearchOpts{
GroupID: groupID,
SinceUpdateAt: value3.UpdateAt - 1,
PerPage: 10,
@@ -1517,7 +1517,7 @@ func testSearchPropertyValues(t *testing.T, _ request.CTX, ss store.Store, s Sql
t.Run("SinceUpdateAt at the most recent update returns just that row", func(t *testing.T) {
// `>=` semantics: querying at the highest UpdateAt in the
// group returns the row at exactly that timestamp.
results, err := ss.PropertyValue().SearchPropertyValues(model.PropertyValueSearchOpts{
results, err := ss.PropertyValue().SearchPropertyValues(rctx, model.PropertyValueSearchOpts{
GroupID: groupID,
SinceUpdateAt: updatedValue2.UpdateAt,
PerPage: 10,
@@ -1528,7 +1528,7 @@ func testSearchPropertyValues(t *testing.T, _ request.CTX, ss store.Store, s Sql
})
t.Run("SinceUpdateAt with very recent timestamp", func(t *testing.T) {
results, err := ss.PropertyValue().SearchPropertyValues(model.PropertyValueSearchOpts{
results, err := ss.PropertyValue().SearchPropertyValues(rctx, model.PropertyValueSearchOpts{
GroupID: groupID,
SinceUpdateAt: model.GetMillis(),
PerPage: 10,
@@ -1566,7 +1566,7 @@ func testSearchPropertyValues(t *testing.T, _ request.CTX, ss store.Store, s Sql
require.Equal(t, tieUpdateAt, updated[1].UpdateAt)
require.Equal(t, tieUpdateAt, updated[2].UpdateAt)
page1, err := ss.PropertyValue().SearchPropertyValues(model.PropertyValueSearchOpts{
page1, err := ss.PropertyValue().SearchPropertyValues(rctx, model.PropertyValueSearchOpts{
GroupID: tieGroup,
SinceUpdateAt: tieUpdateAt,
PerPage: 2,
@@ -1575,7 +1575,7 @@ func testSearchPropertyValues(t *testing.T, _ request.CTX, ss store.Store, s Sql
require.Len(t, page1, 2, "boundary row + one more must come back on the first page")
last := page1[len(page1)-1]
page2, err := ss.PropertyValue().SearchPropertyValues(model.PropertyValueSearchOpts{
page2, err := ss.PropertyValue().SearchPropertyValues(rctx, model.PropertyValueSearchOpts{
GroupID: tieGroup,
SinceUpdateAt: tieUpdateAt,
Cursor: model.PropertyValueSearchCursor{
@@ -1649,7 +1649,7 @@ func testSearchPropertyValues(t *testing.T, _ request.CTX, ss store.Store, s Sql
}
t.Run("orders by UpdateAt ASC, Id ASC", func(t *testing.T) {
results, err := ss.PropertyValue().SearchPropertyValues(model.PropertyValueSearchOpts{
results, err := ss.PropertyValue().SearchPropertyValues(rctx, model.PropertyValueSearchOpts{
GroupID: groupID,
SinceUpdateAt: value1.UpdateAt,
PerPage: 10,
@@ -1664,7 +1664,7 @@ func testSearchPropertyValues(t *testing.T, _ request.CTX, ss store.Store, s Sql
t.Run("auto-includes tombstones", func(t *testing.T) {
require.NoError(t, ss.PropertyValue().Delete("", value3.ID))
results, err := ss.PropertyValue().SearchPropertyValues(model.PropertyValueSearchOpts{
results, err := ss.PropertyValue().SearchPropertyValues(rctx, model.PropertyValueSearchOpts{
GroupID: groupID,
SinceUpdateAt: value1.UpdateAt,
PerPage: 10,
@@ -1685,7 +1685,7 @@ func testSearchPropertyValues(t *testing.T, _ request.CTX, ss store.Store, s Sql
t.Run("paginates with cursor UpdateAt", func(t *testing.T) {
// `>=` semantics: first page is value1 (the boundary row),
// cursored to value3, then value2.
first, err := ss.PropertyValue().SearchPropertyValues(model.PropertyValueSearchOpts{
first, err := ss.PropertyValue().SearchPropertyValues(rctx, model.PropertyValueSearchOpts{
GroupID: groupID,
SinceUpdateAt: value1.UpdateAt,
PerPage: 1,
@@ -1693,7 +1693,7 @@ func testSearchPropertyValues(t *testing.T, _ request.CTX, ss store.Store, s Sql
require.NoError(t, err)
require.Equal(t, []string{value1.ID}, idsOf(first))
second, err := ss.PropertyValue().SearchPropertyValues(model.PropertyValueSearchOpts{
second, err := ss.PropertyValue().SearchPropertyValues(rctx, model.PropertyValueSearchOpts{
GroupID: groupID,
SinceUpdateAt: value1.UpdateAt,
Cursor: model.PropertyValueSearchCursor{
@@ -1705,7 +1705,7 @@ func testSearchPropertyValues(t *testing.T, _ request.CTX, ss store.Store, s Sql
require.NoError(t, err)
require.Equal(t, []string{value3.ID}, idsOf(second))
third, err := ss.PropertyValue().SearchPropertyValues(model.PropertyValueSearchOpts{
third, err := ss.PropertyValue().SearchPropertyValues(rctx, model.PropertyValueSearchOpts{
GroupID: groupID,
SinceUpdateAt: value1.UpdateAt,
Cursor: model.PropertyValueSearchCursor{
@@ -1719,7 +1719,7 @@ func testSearchPropertyValues(t *testing.T, _ request.CTX, ss store.Store, s Sql
})
t.Run("directory mode rejects cursor_update_at", func(t *testing.T) {
_, err := ss.PropertyValue().SearchPropertyValues(model.PropertyValueSearchOpts{
_, err := ss.PropertyValue().SearchPropertyValues(rctx, model.PropertyValueSearchOpts{
GroupID: groupID,
Cursor: model.PropertyValueSearchCursor{
PropertyValueID: value1.ID,
@@ -1731,7 +1731,7 @@ func testSearchPropertyValues(t *testing.T, _ request.CTX, ss store.Store, s Sql
})
t.Run("delta mode rejects cursor_create_at", func(t *testing.T) {
_, err := ss.PropertyValue().SearchPropertyValues(model.PropertyValueSearchOpts{
_, err := ss.PropertyValue().SearchPropertyValues(rctx, model.PropertyValueSearchOpts{
GroupID: groupID,
SinceUpdateAt: value1.UpdateAt,
Cursor: model.PropertyValueSearchCursor{
@@ -1795,7 +1795,7 @@ func testCreatePropertyValueWithArray(t *testing.T, _ request.CTX, ss store.Stor
})
}
func testDeleteForField(t *testing.T, _ request.CTX, ss store.Store) {
func testDeleteForField(t *testing.T, rctx request.CTX, ss store.Store) {
t.Run("should delete values with matching fieldID and groupID", func(t *testing.T) {
fieldID := model.NewId()
groupID := model.NewId()
@@ -1927,7 +1927,7 @@ func testDeleteForField(t *testing.T, _ request.CTX, ss store.Store) {
PerPage: 10,
}
values, err := ss.PropertyValue().SearchPropertyValues(opts)
values, err := ss.PropertyValue().SearchPropertyValues(rctx, opts)
require.NoError(t, err)
// Find our two values within the results
@@ -8739,10 +8739,10 @@ func (s *TimerLayerPropertyValueStore) GetMany(groupID string, ids []string) ([]
return result, err
}
func (s *TimerLayerPropertyValueStore) SearchPropertyValues(opts model.PropertyValueSearchOpts) ([]*model.PropertyValue, error) {
func (s *TimerLayerPropertyValueStore) SearchPropertyValues(rctx request.CTX, opts model.PropertyValueSearchOpts) ([]*model.PropertyValue, error) {
start := time.Now()
result, err := s.PropertyValueStore.SearchPropertyValues(opts)
result, err := s.PropertyValueStore.SearchPropertyValues(rctx, opts)
elapsed := float64(time.Since(start)) / float64(time.Second)
if s.Root.Metrics != nil {