go.temporal.io/server@v1.23.0/common/searchattribute/mapper.go (about) 1 // The MIT License 2 // 3 // Copyright (c) 2020 Temporal Technologies Inc. All rights reserved. 4 // 5 // Copyright (c) 2020 Uber Technologies, Inc. 6 // 7 // Permission is hereby granted, free of charge, to any person obtaining a copy 8 // of this software and associated documentation files (the "Software"), to deal 9 // in the Software without restriction, including without limitation the rights 10 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 11 // copies of the Software, and to permit persons to whom the Software is 12 // furnished to do so, subject to the following conditions: 13 // 14 // The above copyright notice and this permission notice shall be included in 15 // all copies or substantial portions of the Software. 16 // 17 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 18 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 19 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 20 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 21 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 22 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 23 // THE SOFTWARE. 24 25 //go:generate mockgen -copyright_file ../../LICENSE -package $GOPACKAGE -source $GOFILE -destination mapper_mock.go 26 27 package searchattribute 28 29 import ( 30 commonpb "go.temporal.io/api/common/v1" 31 "go.temporal.io/api/serviceerror" 32 "go.temporal.io/server/common/namespace" 33 ) 34 35 type ( 36 // Mapper interface allows overriding custom search attribute names with aliases per namespace. 37 // Create an instance of a Mapper interface and pass it to the temporal.NewServer using temporal.WithSearchAttributesMapper. 38 // Returned error must be from the serviceerror package. 39 Mapper interface { 40 GetAlias(fieldName string, namespace string) (string, error) 41 GetFieldName(alias string, namespace string) (string, error) 42 } 43 44 noopMapper struct{} 45 46 // This mapper is to be backwards compatible with versions before v1.20. 47 // Users using standard visibility might have registered custom search attributes. 48 // Those search attributes won't be searchable, as they weren't before version v1.20. 49 // Thus, this mapper will allow those search attributes to be used without being alised. 50 backCompMapper_v1_20 struct { 51 mapper Mapper 52 emptyStringNameTypeMap NameTypeMap 53 } 54 55 MapperProvider interface { 56 GetMapper(nsName namespace.Name) (Mapper, error) 57 } 58 59 mapperProviderImpl struct { 60 customMapper Mapper 61 namespaceRegistry namespace.Registry 62 searchAttributesProvider Provider 63 enableMapperFromNamespace bool 64 } 65 ) 66 67 var _ Mapper = (*noopMapper)(nil) 68 var _ Mapper = (*backCompMapper_v1_20)(nil) 69 var _ Mapper = (*namespace.CustomSearchAttributesMapper)(nil) 70 var _ MapperProvider = (*mapperProviderImpl)(nil) 71 72 func (m *noopMapper) GetAlias(fieldName string, _ string) (string, error) { 73 return fieldName, nil 74 } 75 76 func (m *noopMapper) GetFieldName(alias string, _ string) (string, error) { 77 return alias, nil 78 } 79 80 func (m *backCompMapper_v1_20) GetAlias(fieldName string, namespaceName string) (string, error) { 81 alias, firstErr := m.mapper.GetAlias(fieldName, namespaceName) 82 if firstErr != nil { 83 _, err := m.emptyStringNameTypeMap.getType(fieldName, customCategory) 84 if err != nil { 85 return "", firstErr 86 } 87 // this is custom search attribute registered in pre-v1.20 88 return fieldName, nil 89 } 90 return alias, nil 91 } 92 93 func (m *backCompMapper_v1_20) GetFieldName(alias string, namespaceName string) (string, error) { 94 fieldName, firstErr := m.mapper.GetFieldName(alias, namespaceName) 95 if firstErr != nil { 96 _, err := m.emptyStringNameTypeMap.getType(alias, customCategory) 97 if err != nil { 98 return "", firstErr 99 } 100 // this is custom search attribute registered in pre-v1.20 101 return alias, nil 102 } 103 return fieldName, nil 104 } 105 106 func NewMapperProvider( 107 customMapper Mapper, 108 namespaceRegistry namespace.Registry, 109 searchAttributesProvider Provider, 110 enableMapperFromNamespace bool, 111 ) MapperProvider { 112 return &mapperProviderImpl{ 113 customMapper: customMapper, 114 namespaceRegistry: namespaceRegistry, 115 searchAttributesProvider: searchAttributesProvider, 116 enableMapperFromNamespace: enableMapperFromNamespace, 117 } 118 } 119 120 func (m *mapperProviderImpl) GetMapper(nsName namespace.Name) (Mapper, error) { 121 if m.customMapper != nil { 122 return m.customMapper, nil 123 } 124 if !m.enableMapperFromNamespace { 125 return &noopMapper{}, nil 126 } 127 saMapper, err := m.namespaceRegistry.GetCustomSearchAttributesMapper(nsName) 128 if err != nil { 129 return nil, err 130 } 131 // if there's an error, it returns an empty object, which is expected here 132 emptyStringNameTypeMap, _ := m.searchAttributesProvider.GetSearchAttributes("", false) 133 return &backCompMapper_v1_20{ 134 mapper: &saMapper, 135 emptyStringNameTypeMap: emptyStringNameTypeMap, 136 }, nil 137 } 138 139 // AliasFields returns SearchAttributes struct where each search attribute name is replaced with alias. 140 // If no replacement where made, it returns nil which means that original SearchAttributes struct should be used. 141 func AliasFields( 142 mapperProvider MapperProvider, 143 searchAttributes *commonpb.SearchAttributes, 144 namespaceName string, 145 ) (*commonpb.SearchAttributes, error) { 146 mapper, err := mapperProvider.GetMapper(namespace.Name(namespaceName)) 147 if err != nil { 148 return nil, err 149 } 150 151 if len(searchAttributes.GetIndexedFields()) == 0 || mapper == nil { 152 return nil, nil 153 } 154 155 newIndexedFields := make(map[string]*commonpb.Payload, len(searchAttributes.GetIndexedFields())) 156 mapped := false 157 for saName, saPayload := range searchAttributes.GetIndexedFields() { 158 if !IsMappable(saName) { 159 newIndexedFields[saName] = saPayload 160 continue 161 } 162 163 aliasName, err := mapper.GetAlias(saName, namespaceName) 164 if err != nil { 165 if _, isInvalidArgument := err.(*serviceerror.InvalidArgument); isInvalidArgument { 166 // Silently ignore serviceerror.InvalidArgument because it indicates unmapped field (alias was deleted, for example). 167 // IMPORTANT: AliasFields should never return serviceerror.InvalidArgument because it is used by Poll API and the error 168 // goes through up to SDK, which shutdowns worker when it receives serviceerror.InvalidArgument as poll response. 169 continue 170 } 171 return nil, err 172 } 173 if aliasName != saName { 174 mapped = true 175 } 176 newIndexedFields[aliasName] = saPayload 177 } 178 179 // If no field name was mapped, return nil to save on clone operation on caller side. 180 if !mapped { 181 return nil, nil 182 } 183 return &commonpb.SearchAttributes{IndexedFields: newIndexedFields}, nil 184 } 185 186 // UnaliasFields returns SearchAttributes struct where each search attribute alias is replaced with field name. 187 // If no replacement where made, it returns nil which means that original SearchAttributes struct should be used. 188 func UnaliasFields( 189 mapperProvider MapperProvider, 190 searchAttributes *commonpb.SearchAttributes, 191 namespaceName string, 192 ) (*commonpb.SearchAttributes, error) { 193 mapper, err := mapperProvider.GetMapper(namespace.Name(namespaceName)) 194 if err != nil { 195 return nil, err 196 } 197 198 if len(searchAttributes.GetIndexedFields()) == 0 || mapper == nil { 199 return nil, nil 200 } 201 202 newIndexedFields := make(map[string]*commonpb.Payload, len(searchAttributes.GetIndexedFields())) 203 mapped := false 204 for saName, saPayload := range searchAttributes.GetIndexedFields() { 205 if !IsMappable(saName) { 206 newIndexedFields[saName] = saPayload 207 continue 208 } 209 210 fieldName, err := mapper.GetFieldName(saName, namespaceName) 211 if err != nil { 212 return nil, err 213 } 214 if fieldName != saName { 215 mapped = true 216 } 217 newIndexedFields[fieldName] = saPayload 218 } 219 220 // If no alias was mapped, return nil to save on clone operation on caller side. 221 if !mapped { 222 return nil, nil 223 } 224 225 return &commonpb.SearchAttributes{IndexedFields: newIndexedFields}, nil 226 }