Skip to content

Conversation

@awln-temporal
Copy link
Contributor

@awln-temporal awln-temporal commented Oct 15, 2025

What changed?

Adds CHASM search attribute provider and mapper implementation. To use CHASM search attributes, embed your component with ComponentSearchAttributesProvider and register the search attributes to the CHASM Registry.

To define a component search attribute, define a SearchAttribute as a package/global scoped variable (CHASM search attributes should reference exported variables like SearchAttributeFieldInt01, SearchAttributeFieldDateTime01:

var (
	TestComponentStartTimeSearchAttribute = NewSearchAttributeTime(testComponentStartTimeSAKey, SearchAttributeFieldDateTime01)

	_ VisibilitySearchAttributesProvider = (*TestComponent)(nil)
	_ VisibilityMemoProvider             = (*TestComponent)(nil)
	_ VisibilitySearchAttributesMapper   = (*TestComponent)(nil)
)

To update SearchAttributes during an execution of a CHASM archetype, implement the provider SearchAttributes in the root component, and call the NewValue method on a SearchAttribute within the Provider method to generate a new SearchAttributeValue. On SearchAttribute updates during CHASM transactions, the framework will detect changes and submit a Visibility task for persistence.

type TestComponent struct {
	UnimplementedComponent

	...
	Visibility Field[*Visibility]
}


func (tc *TestComponent) SearchAttributes(ctx Context) []SearchAttributeValue {
	return []SearchAttributeValue{
             TestComponentStartTimeSearchAttribute.NewValue(time)
        }
}

To register the search attribute with the CHASM Registry, WithSearchAttributes must be passed as a RegistrableComponentOption to the root component of the library. This is required to support Visibility queries.

return []*chasm.RegistrableComponent{
		chasm.NewRegistrableComponent[*Scheduler]("scheduler", WIthSearchAttributes([]SearchAttributeDefinition{TestComponentStartTimeSearchAttribute}),
		chasm.NewRegistrableComponent[*Generator]("generator"),
		chasm.NewRegistrableComponent[*Invoker]("invoker"),
		chasm.NewRegistrableComponent[*Backfiller]("backfiller"),
	}

Why?

Required to support CHASM Search Attributes.

How did you test it?

  • built
  • run locally and tested manually
  • covered by existing tests
  • added new unit test(s)
  • added new functional test(s)

Potential risks

TBD

@awln-temporal awln-temporal requested review from a team as code owners October 15, 2025 22:39
@awln-temporal awln-temporal force-pushed the CHASMSearchAttributeComponents branch from 4a8af63 to c07d41e Compare October 17, 2025 17:36
Copy link
Member

@bergundy bergundy left a comment

Choose a reason for hiding this comment

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

Started reviewing but I think we still don't agree on the API, so let's discuss that before and agree before implementing anything.

@awln-temporal awln-temporal force-pushed the CHASMSearchAttributeComponents branch from 560562e to c103f5f Compare October 21, 2025 16:29
@awln-temporal awln-temporal force-pushed the CHASMSearchAttributeComponents branch from acf6008 to cfccb46 Compare October 23, 2025 19:10
Copy link
Member

@bergundy bergundy left a comment

Choose a reason for hiding this comment

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

Overall looks great.

}

// GetAlias returns the search attribute alias for the given field name.
func (rc *RegistrableComponent) GetAlias(field string) (string, error) {
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
func (rc *RegistrableComponent) GetAlias(field string) (string, error) {
func (rc *RegistrableComponent) SearchAttributeAlias(field string) (string, error) {

Copy link
Contributor

Choose a reason for hiding this comment

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

Also replied in the other comment, I think the only reason to have the SearchAttribute prefix in this function name is because it's making RegistrableComponent implement the VisibilitySearchAttributesMapper interface, which I'm not sure I like this approach.

I'd rather if RegistrableComponent had an actual var for the mapper. In fact, I'm wondering if the interface is even needed.

Comment on lines 33 to 34
GetAlias(field string) (string, error)
GetField(alias string) (string, error)
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
GetAlias(field string) (string, error)
GetField(alias string) (string, error)
SearchAttributeAlias(field string) (string, error)
SearchAttributeField(alias string) (string, error)

Copy link
Contributor

Choose a reason for hiding this comment

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

Isn't it redundant to have functions here prefixed with SearchAttribute since the interface is already called VisibilitySearchAttributesMapper? I think GetAlias/GetFieldName are in this case.

Comment on lines 81 to 89
for _, sa := range searchAttributes {
alias := sa.getAlias()
field := sa.getField()
valueType := sa.getValueType()

rc.aliasToField[alias] = field
rc.fieldToAlias[field] = alias
rc.saTypeMap[field] = valueType
}
Copy link
Contributor

@rodrigozhou rodrigozhou Oct 23, 2025

Choose a reason for hiding this comment

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

You cannot create these maps before validating. It's possible to create an invalid mapping that passes the validation. Example: Foo -> Field1, Foo -> Field2, Bar -> Field1

Comment on lines 33 to 34
GetAlias(field string) (string, error)
GetField(alias string) (string, error)
Copy link
Contributor

Choose a reason for hiding this comment

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

Isn't it redundant to have functions here prefixed with SearchAttribute since the interface is already called VisibilitySearchAttributesMapper? I think GetAlias/GetFieldName are in this case.

}

// GetAlias returns the search attribute alias for the given field name.
func (rc *RegistrableComponent) GetAlias(field string) (string, error) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Also replied in the other comment, I think the only reason to have the SearchAttribute prefix in this function name is because it's making RegistrableComponent implement the VisibilitySearchAttributesMapper interface, which I'm not sure I like this approach.

I'd rather if RegistrableComponent had an actual var for the mapper. In fact, I'm wondering if the interface is even needed.

Copy link
Member

@bergundy bergundy left a comment

Choose a reason for hiding this comment

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

This is already very close.
Please document all exported fields from the chasm package.

Comment on lines +41 to +72
func (v *VisibilitySearchAttributesMapper) GetAlias(field string) (string, error) {
if v == nil {
return "", serviceerror.NewInvalidArgument("visibility search attributes mapper is not registered")
}
alias, ok := v.aliasToField[field]
if !ok {
return "", serviceerror.NewInvalidArgument(fmt.Sprintf("visibility search attributes mapper has no registered field %s", field))
}
return alias, nil
}

func (v *VisibilitySearchAttributesMapper) GetField(alias string) (string, error) {
if v == nil {
return "", serviceerror.NewInvalidArgument("visibility search attributes mapper is not registered")
}
field, ok := v.fieldToAlias[alias]
if !ok {
return "", serviceerror.NewInvalidArgument(fmt.Sprintf("visibility search attributes mapper has no registered alias %s", alias))
}
return field, nil
}

func (v *VisibilitySearchAttributesMapper) GetFieldValueType(field string) (enumspb.IndexedValueType, error) {
if v == nil {
return enumspb.INDEXED_VALUE_TYPE_UNSPECIFIED, serviceerror.NewInvalidArgument("visibility search attributes mapper is not registered")
}
saType, ok := v.saTypeMap[field]
if !ok {
return enumspb.INDEXED_VALUE_TYPE_UNSPECIFIED, serviceerror.NewInvalidArgument(fmt.Sprintf("visibility search attributes mapper has no registered field %s", field))
}
return saType, nil
}
Copy link
Member

Choose a reason for hiding this comment

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

Why do you need to export these methods? And please don't use Get as a prefix for getters in Go.

_ SearchAttribute = (*SearchAttributeKeywordList)(nil)
)

type (
Copy link
Member

Choose a reason for hiding this comment

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

nit: don't group all of the types together. The practice in Go is to put the type definition above its constructor followed by all of the methods.

Comment on lines +68 to +72
// alias refers to the user defined name of the search attribute
Alias string
// field refers to a fully formed schema field, which is either a Predefined or CHASM search attribute
Field string
ValueType enumspb.IndexedValueType
Copy link
Member

Choose a reason for hiding this comment

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

Do these fields need to be exported? They're defined on a struct that isn't exported.

)

var (
TestKeywordSearchAttribute = chasm.NewSearchAttributeKeyword(TestKeywordSAFieldName, chasm.SearchAttributeFieldKeyword01)
Copy link
Member

Choose a reason for hiding this comment

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

We need another search attribute to test aliasing.
Also, don't use TemporalScheduledById as the mapped attribute.

You're going to want:

  1. Define SearchAttributeTemporalScheduledById = newSearchAttributeKeywordByField(searchattribute.TemporalScheduledById) in chasm/search_attribute.go
  2. Define a separate search attribute that will map to SearchAttributeFieldKeyword01 for testing aliasing
  3. Register both with the component
  4. Emit both from the component
  5. Test both in functional tests

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants