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  }