Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix bug to query header search attributes correctly in visibility #6163

Merged
merged 5 commits into from
Jul 10, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion common/definition/indexedKeys.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ const (
// Attr is prefix of custom search attributes
Attr = "Attr"
// HeaderFormat is the format of context headers in search attributes
HeaderFormat = "Header.%s"
HeaderFormat = "Header_%s"
)

// defaultIndexedKeys defines all searchable keys
Expand Down
47 changes: 46 additions & 1 deletion common/elasticsearch/validator/queryValidator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import (

"github.com/uber/cadence/common/definition"
"github.com/uber/cadence/common/dynamicconfig"
"github.com/uber/cadence/common/types"
)

func TestValidateQuery(t *testing.T) {
Expand All @@ -35,6 +36,7 @@ func TestValidateQuery(t *testing.T) {
query string
validated string
err string
dcValid map[string]interface{}
}{
{
msg: "empty query",
Expand Down Expand Up @@ -126,11 +128,54 @@ func TestValidateQuery(t *testing.T) {
query: "WorkflowID = 'wid' union select * from dummy",
err: "Invalid select query.",
},
{
msg: "valid custom search attribute",
query: "CustomStringField = 'value'",
validated: "`Attr.CustomStringField` = 'value'",
},
{
msg: "custom search attribute can contain underscore",
query: "Custom_Field = 'value'",
validated: "`Attr.Custom_Field` = 'value'",
dcValid: map[string]interface{}{
"Custom_Field": types.IndexedValueTypeString,
},
},
{
msg: "custom search attribute can contain number",
query: "Custom_0 = 'value'",
validated: "`Attr.Custom_0` = 'value'",
dcValid: map[string]interface{}{
"Custom_0": types.IndexedValueTypeString,
},
},
{
msg: "customg search attribute cannot contain dot",
query: "Custom.Field = 'value'",
err: "invalid search attribute \"Field\"",
dcValid: map[string]interface{}{
"Custom.Field": types.IndexedValueTypeString,
},
},
{
msg: "custom search attribute cannot contain dash",
query: "Custom-Field = 'value'",
err: "invalid comparison expression",
dcValid: map[string]interface{}{
"Custom-Field": types.IndexedValueTypeString,
},
},
}

for _, tt := range tests {
t.Run(tt.msg, func(t *testing.T) {
validSearchAttr := dynamicconfig.GetMapPropertyFn(definition.GetDefaultIndexedKeys())
validSearchAttr := func(opts ...dynamicconfig.FilterOption) map[string]interface{} {
valid := definition.GetDefaultIndexedKeys()
for k, v := range tt.dcValid {
valid[k] = v
}
return valid
}
validateSearchAttr := dynamicconfig.GetBoolPropertyFn(true)
qv := NewQueryValidator(validSearchAttr, validateSearchAttr)
validated, err := qv.ValidateQuery(tt.query)
Expand Down
8 changes: 4 additions & 4 deletions service/history/task/transfer_active_task_executor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1543,7 +1543,7 @@ func (s *transferActiveTaskExecutorSuite) TestProcessRecordWorkflowStartedTaskWi
s.mockShard.GetConfig().EnableContextHeaderInVisibility = func(domain string) bool { return true }
s.mockShard.GetConfig().ValidSearchAttributes = func(opts ...dc.FilterOption) map[string]interface{} {
return map[string]interface{}{
"Header.contextKey": struct{}{},
"Header_context_key": struct{}{},
}
}

Expand Down Expand Up @@ -1626,7 +1626,7 @@ func (s *transferActiveTaskExecutorSuite) TestProcessUpsertWorkflowSearchAttribu
s.mockShard.GetConfig().EnableContextHeaderInVisibility = func(domain string) bool { return true }
s.mockShard.GetConfig().ValidSearchAttributes = func(opts ...dc.FilterOption) map[string]interface{} {
return map[string]interface{}{
"Header.contextKey": struct{}{},
"Header_context_key": struct{}{},
}
}

Expand Down Expand Up @@ -1782,7 +1782,7 @@ func createRecordWorkflowExecutionStartedRequest(
t.Fatal(err)
}
searchAttributes = map[string][]byte{
"Header.contextKey": contextValueJSONString,
"Header_context_key": contextValueJSONString,
}
}
return &persistence.RecordWorkflowExecutionStartedRequest{
Expand Down Expand Up @@ -1937,7 +1937,7 @@ func createUpsertWorkflowSearchAttributesRequest(
t.Fatal(err)
}
searchAttributes = map[string][]byte{
"Header.contextKey": contextValueJSONString,
"Header_context_key": contextValueJSONString,
}
}

Expand Down
4 changes: 2 additions & 2 deletions service/history/task/transfer_standby_task_executor_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -815,7 +815,7 @@ func (s *transferStandbyTaskExecutorSuite) TestProcessRecordWorkflowStartedTaskW
}
s.mockShard.GetConfig().ValidSearchAttributes = func(opts ...dc.FilterOption) map[string]interface{} {
return map[string]interface{}{
"Header.contextKey": struct{}{},
"Header_context_key": struct{}{},
}
}

Expand Down Expand Up @@ -906,7 +906,7 @@ func (s *transferStandbyTaskExecutorSuite) TestProcessUpsertWorkflowSearchAttrib
}
s.mockShard.GetConfig().ValidSearchAttributes = func(opts ...dc.FilterOption) map[string]interface{} {
return map[string]interface{}{
"Header.contextKey": struct{}{},
"Header_context_key": struct{}{},
}
}

Expand Down
10 changes: 9 additions & 1 deletion service/history/task/transfer_task_executor_base.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import (
"context"
"encoding/json"
"fmt"
"regexp"
"time"

"github.com/uber/cadence/client/matching"
Expand All @@ -50,6 +51,8 @@ const (
defaultDomainName = "defaultDomainName"
)

var nonAlphanumericRegex = regexp.MustCompile(`[^a-zA-Z0-9]+`)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it's better to not spread these visibility store constraints across the codebase. can you move sanitizedHeaderKey to common folder or somewhere in visibility folders?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

make sense. Now it's in the common folder


type (
transferTaskExecutorBase struct {
shard shard.Context
Expand Down Expand Up @@ -402,7 +405,7 @@ func getWorkflowMemo(

func appendContextHeaderToSearchAttributes(attr, context map[string][]byte, allowedKeys map[string]interface{}) (map[string][]byte, error) {
for k, v := range context {
key := fmt.Sprintf(definition.HeaderFormat, k)
key := sanitizedHeaderKey(k)
if _, ok := attr[key]; ok { // skip if key already exists
continue
}
Expand Down Expand Up @@ -443,3 +446,8 @@ func isWorkflowNotExistError(err error) bool {
_, ok := err.(*types.EntityNotExistsError)
return ok
}

func sanitizedHeaderKey(key string) string {
sanitizedKey := nonAlphanumericRegex.ReplaceAllString(key, "_")
return fmt.Sprintf(definition.HeaderFormat, sanitizedKey)
}
4 changes: 2 additions & 2 deletions service/history/testing/workflow_util.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,8 @@ func StartWorkflow(
ExecutionStartToCloseTimeoutSeconds: common.Int32Ptr(2),
TaskStartToCloseTimeoutSeconds: common.Int32Ptr(1),
Header: &types.Header{Fields: map[string][]byte{
"contextKey": []byte("contextValue"),
"invalidContextKey": []byte("invalidContextValue"),
"context-key": []byte("contextValue"),
"invalid-context-key": []byte("invalidContextValue"),
}},
},
PartitionConfig: map[string]string{"userid": uuid.New()},
Expand Down
2 changes: 1 addition & 1 deletion tools/cli/admin_cluster_commands.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import (

// An indirection for the prompt function so that it can be mocked in the unit tests
var promptFn = prompt
var validSearchAttributeKey = regexp.MustCompile(`^[a-zA-Z][a-zA-Z_.-0-9]*$`)
var validSearchAttributeKey = regexp.MustCompile(`^[a-zA-Z][a-zA-Z_0-9]*$`)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here. this regex should live close to other search attribute handling code in a central place.


// AdminAddSearchAttribute to whitelist search attribute
func AdminAddSearchAttribute(c *cli.Context) {
Expand Down
2 changes: 1 addition & 1 deletion tools/cli/admin_cluster_commands_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,8 +126,8 @@ func TestValidSearchAttributeKey(t *testing.T) {
assert.NoError(t, validateSearchAttributeKey("cityId"))
assert.NoError(t, validateSearchAttributeKey("paymentProfileUUID"))
assert.NoError(t, validateSearchAttributeKey("job_type"))
assert.NoError(t, validateSearchAttributeKey("Header.ctx-tenancy"))

assert.Error(t, validateSearchAttributeKey("payments-biling-invoices-TransactionUUID"))
assert.Error(t, validateSearchAttributeKey("9lives"))
assert.Error(t, validateSearchAttributeKey("tax%"))
}
Loading