go.temporal.io/server@v1.23.0/common/persistence/visibility/store/elasticsearch/query_interceptors.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 package elasticsearch 26 27 import ( 28 "strconv" 29 "time" 30 31 enumspb "go.temporal.io/api/enums/v1" 32 33 "go.temporal.io/server/common/namespace" 34 "go.temporal.io/server/common/persistence/visibility/store/query" 35 "go.temporal.io/server/common/searchattribute" 36 ) 37 38 type ( 39 nameInterceptor struct { 40 namespace namespace.Name 41 index string 42 searchAttributesTypeMap searchattribute.NameTypeMap 43 searchAttributesMapperProvider searchattribute.MapperProvider 44 seenNamespaceDivision bool 45 } 46 47 valuesInterceptor struct { 48 namespace namespace.Name 49 searchAttributesTypeMap searchattribute.NameTypeMap 50 searchAttributesMapperProvider searchattribute.MapperProvider 51 } 52 ) 53 54 func newNameInterceptor( 55 namespaceName namespace.Name, 56 index string, 57 saTypeMap searchattribute.NameTypeMap, 58 searchAttributesMapperProvider searchattribute.MapperProvider, 59 ) *nameInterceptor { 60 return &nameInterceptor{ 61 namespace: namespaceName, 62 index: index, 63 searchAttributesTypeMap: saTypeMap, 64 searchAttributesMapperProvider: searchAttributesMapperProvider, 65 } 66 } 67 68 func NewValuesInterceptor( 69 namespaceName namespace.Name, 70 saTypeMap searchattribute.NameTypeMap, 71 searchAttributesMapperProvider searchattribute.MapperProvider, 72 ) *valuesInterceptor { 73 return &valuesInterceptor{ 74 namespace: namespaceName, 75 searchAttributesTypeMap: saTypeMap, 76 searchAttributesMapperProvider: searchAttributesMapperProvider, 77 } 78 } 79 80 func (ni *nameInterceptor) Name(name string, usage query.FieldNameUsage) (string, error) { 81 fieldName := name 82 if searchattribute.IsMappable(name) { 83 mapper, err := ni.searchAttributesMapperProvider.GetMapper(ni.namespace) 84 if err != nil { 85 return "", err 86 } 87 if mapper != nil { 88 fieldName, err = mapper.GetFieldName(name, ni.namespace.String()) 89 if err != nil { 90 return "", err 91 } 92 } 93 } 94 95 fieldType, err := ni.searchAttributesTypeMap.GetType(fieldName) 96 if err != nil { 97 return "", query.NewConverterError("invalid search attribute: %s", name) 98 } 99 100 switch usage { 101 case query.FieldNameFilter: 102 if fieldName == searchattribute.TemporalNamespaceDivision { 103 ni.seenNamespaceDivision = true 104 } 105 case query.FieldNameSorter: 106 if fieldType == enumspb.INDEXED_VALUE_TYPE_TEXT { 107 return "", query.NewConverterError( 108 "unable to sort by field of %s type, use field of type %s", 109 enumspb.INDEXED_VALUE_TYPE_TEXT.String(), 110 enumspb.INDEXED_VALUE_TYPE_KEYWORD.String(), 111 ) 112 } 113 case query.FieldNameGroupBy: 114 if fieldName != searchattribute.ExecutionStatus { 115 return "", query.NewConverterError( 116 "'group by' clause is only supported for %s search attribute", 117 searchattribute.ExecutionStatus, 118 ) 119 } 120 } 121 122 return fieldName, nil 123 } 124 125 func (vi *valuesInterceptor) Values(fieldName string, values ...interface{}) ([]interface{}, error) { 126 fieldType, err := vi.searchAttributesTypeMap.GetType(fieldName) 127 if err != nil { 128 return nil, query.NewConverterError("invalid search attribute: %s", fieldName) 129 } 130 131 name := fieldName 132 if searchattribute.IsMappable(fieldName) { 133 mapper, err := vi.searchAttributesMapperProvider.GetMapper(vi.namespace) 134 if err != nil { 135 return nil, err 136 } 137 if mapper != nil { 138 name, err = mapper.GetAlias(fieldName, vi.namespace.String()) 139 if err != nil { 140 return nil, err 141 } 142 } 143 } 144 145 var result []interface{} 146 for _, value := range values { 147 value, err = parseSystemSearchAttributeValues(fieldName, value) 148 if err != nil { 149 return nil, err 150 } 151 value, err = validateValueType(name, value, fieldType) 152 if err != nil { 153 return nil, err 154 } 155 result = append(result, value) 156 } 157 return result, nil 158 } 159 160 func parseSystemSearchAttributeValues(name string, value any) (any, error) { 161 switch name { 162 case searchattribute.StartTime, searchattribute.CloseTime, searchattribute.ExecutionTime: 163 if nanos, isNumber := value.(int64); isNumber { 164 value = time.Unix(0, nanos).UTC().Format(time.RFC3339Nano) 165 } 166 case searchattribute.ExecutionStatus: 167 if status, isNumber := value.(int64); isNumber { 168 if _, ok := enumspb.WorkflowExecutionStatus_name[int32(status)]; !ok { 169 return nil, query.NewConverterError("invalid value for search attribute %s: %v", name, value) 170 } 171 value = enumspb.WorkflowExecutionStatus(status).String() 172 } 173 case searchattribute.ExecutionDuration: 174 if durationStr, isString := value.(string); isString { 175 duration, err := query.ParseExecutionDurationStr(durationStr) 176 if err != nil { 177 return nil, query.NewConverterError( 178 "invalid value for search attribute %s: %v (%v)", name, value, err) 179 } 180 value = duration.Nanoseconds() 181 } 182 default: 183 } 184 return value, nil 185 } 186 187 func validateValueType(name string, value any, fieldType enumspb.IndexedValueType) (any, error) { 188 switch fieldType { 189 case enumspb.INDEXED_VALUE_TYPE_INT, enumspb.INDEXED_VALUE_TYPE_DOUBLE: 190 switch v := value.(type) { 191 case int64, float64: 192 // nothing to do 193 case string: 194 // ES can do implicit casting if the value is numeric 195 if _, err := strconv.ParseFloat(v, 64); err != nil { 196 return nil, query.NewConverterError( 197 "invalid value for search attribute %s of type %s: %#v", name, fieldType.String(), value) 198 } 199 default: 200 return nil, query.NewConverterError( 201 "invalid value for search attribute %s of type %s: %#v", name, fieldType.String(), value) 202 } 203 case enumspb.INDEXED_VALUE_TYPE_BOOL: 204 switch value.(type) { 205 case bool: 206 // nothing to do 207 default: 208 return nil, query.NewConverterError( 209 "invalid value for search attribute %s of type %s: %#v", name, fieldType.String(), value) 210 } 211 case enumspb.INDEXED_VALUE_TYPE_DATETIME: 212 switch v := value.(type) { 213 case int64: 214 value = time.Unix(0, v).UTC().Format(time.RFC3339Nano) 215 case string: 216 if _, err := time.Parse(time.RFC3339Nano, v); err != nil { 217 return nil, query.NewConverterError( 218 "invalid value for search attribute %s of type %s: %#v", name, fieldType.String(), value) 219 } 220 default: 221 return nil, query.NewConverterError( 222 "invalid value for search attribute %s of type %s: %#v", name, fieldType.String(), value) 223 } 224 } 225 return value, nil 226 }