go.temporal.io/server/chasm/visibility.go
409 LOC · 170 covered · 239 uncovered · 55 ranges · 1230 concepts · 34 introducers · 586 tests
File neighbourhood
The centred file is linked to every concept that introduces one of its ranges, every test that runs code from the file, and the gray connector concepts standing between those tests and the file's own introducer concepts. Undirected links join concepts to every file where they introduce source and concepts to the tests they introduce; arrows show specialization between the displayed concepts and bridge only concepts omitted from this view. Concept colors match the source ranges below; connector concepts have no source color and are shown in gray.
Focused file, its introducer and connector concepts, their introduced files, and tests that run code from the file
In the embedded map, ordinary wheel input scrolls the page; use the visible controls to zoom and drag to pan. Open the full-screen map for canvas navigation: wheel pans, Ctrl/Command plus wheel zooms, and arrow keys pan when this region is focused. On touch screens, open the full-screen map to pan or pinch. If JavaScript or WebGL is unavailable, use the related-file, concept, and source links on this page.
Graph controls are ready.
Interactive rendering requires JavaScript and WebGL. Use the related-file, concept, and source links on this page while the interactive map is unavailable.
package chasm
import (
"context"
"fmt"
"maps"
"strings"
commonpb "go.temporal.io/api/common/v1"
enumspb "go.temporal.io/api/enums/v1"
"go.temporal.io/api/serviceerror"
persistencespb "go.temporal.io/server/api/persistence/v1"
"go.temporal.io/server/common/payload"
"go.temporal.io/server/common/searchattribute/sadefs"
"google.golang.org/protobuf/proto"
)
const (
UserMemoKey = "__user__"
ChasmMemoKey = "__chasm__"
visibilityComponentType = "core.vis"
visibilityTaskType = "core.visTask"
)
var (
visibilityComponentTypeID = GenerateTypeID(visibilityComponentType)
visibilityTaskTypeID = GenerateTypeID(visibilityTaskType)
)
// VisibilitySearchAttributesProvider if implemented by the root Component,
// allows the CHASM framework to automatically determine, at the end of
// a transaction, if a visibility task needs to be generated to update the
// visibility record with the returned search attributes.
type VisibilitySearchAttributesProvider interface {
SearchAttributes(Context) []SearchAttributeKeyValue
}
// VisibilityMemoProvider if implemented by the root Component,
// allows the CHASM framework to automatically determine, at the end of
// a transaction, if a visibility task needs to be generated to update the
// visibility record with the returned memo.
type VisibilityMemoProvider interface {
Memo(Context) proto.Message
}
// VisibilitySearchAttributesMapper is a mapper for CHASM search attributes.
type VisibilitySearchAttributesMapper struct {
// map from CHASM and predefined search attribute aliases to field names.
aliasToField map[string]string
fieldToAlias map[string]string
saTypeMap map[string]enumspb.IndexedValueType
// systemAliasToField maps a CHASM search attribute alias to a system field
// (e.g. "ScheduleId" -> "WorkflowId"). Used to resolve system search attribute aliases,
// including the businessID alias configured via WithBusinessIDAlias.
systemAliasToField map[string]string
// overriddenSystemFields records system search attribute fields (e.g. ExecutionTime, TaskQueue)
// this archetype overrides with its own value, stored in the dedicated system column. Value is
// the field's indexed value type.
overriddenSystemFields map[string]enumspb.IndexedValueType
}
// newVisibilitySearchAttributesMapper returns a mapper with all maps initialized.
func newVisibilitySearchAttributesMapper() *VisibilitySearchAttributesMapper {
visibility.go ×1
return &VisibilitySearchAttributesMapper{
aliasToField: make(map[string]string),
fieldToAlias: make(map[string]string),
saTypeMap: make(map[string]enumspb.IndexedValueType),
systemAliasToField: make(map[string]string),
overriddenSystemFields: make(map[string]enumspb.IndexedValueType),
}
}
// Alias returns the alias for a given field.
func (v *VisibilitySearchAttributesMapper) Alias(field string) (string, error) {
if v == nil {
return "", serviceerror.NewInvalidArgument("visibility search attributes mapper not defined")
}
alias, ok := v.fieldToAlias[field]
if !ok {
return "", serviceerror.NewInvalidArgumentf(
"visibility search attributes mapper has no registered field %q",
field,
)
}
return alias, nil
}
// Field returns the field for a given alias.
func (v *VisibilitySearchAttributesMapper) Field(alias string) (string, error) {
visibility.go ×2
if v == nil {
return "", serviceerror.NewInvalidArgument("visibility search attributes mapper not defined")
}
}
return field, nil
}
return "", serviceerror.NewInvalidArgument(fmt.Sprintf("visibility search attributes mapper has no registered alias %q", alias))
visibility.go ×3
}
// resolveSystemAlias resolves a system search attribute alias to its field name.
// It handles the `Temporal` prefix variations (e.g., "ScheduleId" and "TemporalScheduleId").
func (v *VisibilitySearchAttributesMapper) resolveSystemAlias(alias string) (string, bool) {
visibility.go ×3
if v.systemAliasToField == nil {
}
return field, true
}
// Try without the `Temporal` prefix.
withoutPrefix := alias[len(sadefs.ReservedPrefix):]
if field, ok := v.systemAliasToField[withoutPrefix]; ok {
return field, true
}
// Try with the `Temporal` prefix.
withPrefix := sadefs.ReservedPrefix + alias
if field, ok := v.systemAliasToField[withPrefix]; ok {
return field, true
}
}
}
// SATypeMap returns the type map for the CHASM search attributes.
func (v *VisibilitySearchAttributesMapper) SATypeMap() map[string]enumspb.IndexedValueType {
visibility.go ×1
if v == nil {
}
}
// IsSystemOverride returns true if this archetype overrides the given system search attribute
// field with its own value (written to the dedicated system column).
func (v *VisibilitySearchAttributesMapper) IsSystemOverride(field string) bool {
visibility.go ×2
if v == nil {
return false
}
return ok
}
// OverriddenSystemFields returns the system search attribute fields this archetype overrides,
// keyed by field name with the field's indexed value type as the value.
func (v *VisibilitySearchAttributesMapper) OverriddenSystemFields() map[string]enumspb.IndexedValueType {
visibility.go ×1
if v == nil {
}
}
// ValueType returns the type of a CHASM search attribute field.
// Returns an error if the field is not found in the type map.
func (v *VisibilitySearchAttributesMapper) ValueType(fieldName string) (enumspb.IndexedValueType, error) {
visibility.go ×3
if v == nil {
return enumspb.INDEXED_VALUE_TYPE_UNSPECIFIED, serviceerror.NewInvalidArgument("visibility search attributes mapper not defined")
}
if !ok {
return enumspb.INDEXED_VALUE_TYPE_UNSPECIFIED, serviceerror.NewInvalidArgumentf("visibility search attributes mapper has no registered field %q", fieldName)
visibility.go ×1
}
}
type Visibility struct {
UnimplementedComponent
Data *persistencespb.ChasmVisibilityData
// Do NOT access those fields directly.
// Use the provided getters and setters instead.
SA Field[*commonpb.SearchAttributes]
Memo Field[*commonpb.Memo]
}
func NewVisibility(
mutableContext MutableContext,
visibility := &Visibility{
Data: &persistencespb.ChasmVisibilityData{
TransitionCount: 0,
},
}
visibility.generateTask(mutableContext)
return visibility
}
func NewVisibilityWithData(
mutableContext MutableContext,
customSearchAttributes map[string]*commonpb.Payload,
customMemo map[string]*commonpb.Payload,
visibility := &Visibility{
Data: &persistencespb.ChasmVisibilityData{
TransitionCount: 0,
},
}
// Filter out nil/empty payload values for search attributes.
filteredSA := payload.MergeMapOfPayload(nil, customSearchAttributes)
if len(filteredSA) != 0 {
mutableContext,
&commonpb.SearchAttributes{IndexedFields: filteredSA},
)
}
// Filter out nil/empty payload values for memo.
if len(filteredMemo) != 0 {
mutableContext,
&commonpb.Memo{Fields: filteredMemo},
)
}
return visibility
}
return LifecycleStateRunning
}
// CustomSearchAttributes returns the stored custom search attribute fields.
// Nil is returned if there are none.
//
// Returned map is a shallow copy: callers may add, delete, or reassign keys without
// affecting the stored data, but the *commonpb.Payload values are shared.
func (v *Visibility) CustomSearchAttributes(
chasmContext Context,
sa, _ := v.SA.TryGet(chasmContext)
// nil check handled by the proto getter.
return maps.Clone(sa.GetIndexedFields())
}
// MergeCustomSearchAttributes merges the provided custom search attribute fields into the existing ones.
// - If a key in `customSearchAttributes` already exists,
// the value in `customSearchAttributes` replaces the existing value.
// - If a key in `customSearchAttributes` has nil or empty slice payload value,
// the key is deleted from the existing search attributes if it exists.
// If all search attributes are removed, the underlying search attributes node is deleted.
// - If `customSearchAttributes` is empty, this is a no-op.
func (v *Visibility) MergeCustomSearchAttributes(
mutableContext MutableContext,
customSearchAttributes map[string]*commonpb.Payload,
if len(customSearchAttributes) == 0 {
}
if !ok {
currentSA = &commonpb.SearchAttributes{}
v.SA = NewDataField(mutableContext, currentSA)
}
currentSA.GetIndexedFields(),
customSearchAttributes,
)
if len(currentSA.IndexedFields) == 0 {
}
}
// ReplaceCustomSearchAttributes replaces the existing custom search attribute fields with the provided ones.
// Nil/empty payload values are filtered.
// If `customSearchAttributes` is empty or all values are nil after filtering, the underlying search attributes node is deleted.
func (v *Visibility) ReplaceCustomSearchAttributes(
mutableContext MutableContext,
customSearchAttributes map[string]*commonpb.Payload,
// Filter out nil/empty payload values.
filteredSA := payload.MergeMapOfPayload(nil, customSearchAttributes)
if len(filteredSA) == 0 {
_, ok := v.SA.TryGet(mutableContext)
if !ok {
// Already empty, no-op
return
}
} else {
v.SA = NewDataField(
mutableContext,
&commonpb.SearchAttributes{IndexedFields: filteredSA},
)
}
}
// CustomMemo returns the stored custom memo fields.
// Nil is returned if there are none.
//
// Returned map is a shallow copy: callers may add, delete, or reassign keys without
// affecting the stored data, but the *commonpb.Payload values are shared.
func (v *Visibility) CustomMemo(
chasmContext Context,
memo, _ := v.Memo.TryGet(chasmContext)
// nil check handled by the proto getter.
return maps.Clone(memo.GetFields())
}
// MergeCustomMemo merges the provided custom memo fields into the existing ones.
// - If a key in `customMemo` already exists,
// the value in `customMemo` replaces the existing value.
// - If a key in `customMemo` has nil or empty slice payload value,
// the key is deleted from the existing memo if it exists.
// If all memo fields are removed, the underlying memo node is deleted.
// - If `customMemo` is empty, this is a no-op.
func (v *Visibility) MergeCustomMemo(
mutableContext MutableContext,
customMemo map[string]*commonpb.Payload,
if len(customMemo) == 0 {
}
if !ok {
currentMemo = &commonpb.Memo{}
v.Memo = NewDataField(mutableContext, currentMemo)
}
currentMemo.GetFields(),
customMemo,
)
if len(currentMemo.Fields) == 0 {
}
}
// ReplaceCustomMemo replaces the existing custom memo fields with the provided ones.
// If `customMemo` is empty, the underlying memo node is deleted.
func (v *Visibility) ReplaceCustomMemo(
mutableContext MutableContext,
customMemo map[string]*commonpb.Payload,
// Filter out nil/empty payload values for memo.
filteredMemo := payload.MergeMapOfPayload(nil, customMemo)
if len(filteredMemo) == 0 {
if !ok {
// Already empty, no-op
return
}
v.Memo = NewDataField(
mutableContext,
&commonpb.Memo{Fields: filteredMemo},
)
}
}
func (v *Visibility) generateTask(
mutableContext MutableContext,
v.Data.TransitionCount++
mutableContext.AddTask(
v,
TaskAttributes{},
&persistencespb.ChasmVisibilityTaskData{TransitionCount: v.Data.TransitionCount},
)
}
type visibilityTaskHandler struct {
SideEffectTaskHandlerBase[*persistencespb.ChasmVisibilityTaskData]
}
var defaultVisibilityTaskHandler = &visibilityTaskHandler{}
func (v *visibilityTaskHandler) Validate(
_ Context,
component *Visibility,
_ TaskInvocation,
task *persistencespb.ChasmVisibilityTaskData,
return task.TransitionCount == component.Data.TransitionCount, nil
}
func (v *visibilityTaskHandler) Execute(
_ context.Context,
_ ComponentRef,
_ TaskAttributes,
_ *persistencespb.ChasmVisibilityTaskData,
) error {
//nolint:forbidigo
panic("chasm visibilityTaskHandler should not be called directly")
}