github.com/weaviate/weaviate@v1.24.6/entities/filters/filters_validator.go (about) 1 // _ _ 2 // __ _____ __ ___ ___ __ _| |_ ___ 3 // \ \ /\ / / _ \/ _` \ \ / / |/ _` | __/ _ \ 4 // \ V V / __/ (_| |\ V /| | (_| | || __/ 5 // \_/\_/ \___|\__,_| \_/ |_|\__,_|\__\___| 6 // 7 // Copyright © 2016 - 2024 Weaviate B.V. All rights reserved. 8 // 9 // CONTACT: hello@weaviate.io 10 // 11 12 package filters 13 14 import ( 15 "fmt" 16 "strings" 17 18 "github.com/pkg/errors" 19 "github.com/weaviate/weaviate/entities/schema" 20 ) 21 22 // string and stringArray are deprecated as of v1.19 23 // however they are allowed in filters and considered aliases 24 // for text and textArray 25 var deprecatedDataTypeAliases map[schema.DataType]schema.DataType = map[schema.DataType]schema.DataType{ 26 schema.DataTypeString: schema.DataTypeText, 27 schema.DataTypeStringArray: schema.DataTypeTextArray, 28 } 29 30 func ValidateFilters(sch schema.Schema, filters *LocalFilter) error { 31 if filters == nil { 32 return errors.New("empty where") 33 } 34 cw := newClauseWrapper(filters.Root) 35 if err := validateClause(sch, cw); err != nil { 36 return err 37 } 38 cw.updateClause() 39 return nil 40 } 41 42 func validateClause(sch schema.Schema, cw *clauseWrapper) error { 43 // check if nested 44 if cw.getOperands() != nil { 45 var errs []error 46 47 for i, child := range cw.getOperands() { 48 if err := validateClause(sch, child); err != nil { 49 errs = append(errs, errors.Wrapf(err, "child operand at position %d", i)) 50 } 51 } 52 53 if len(errs) > 0 { 54 return mergeErrs(errs) 55 } 56 return nil 57 } 58 59 // validate current 60 61 className := cw.getClassName() 62 propName := cw.getPropertyName() 63 64 if IsInternalProperty(propName) { 65 return validateInternalPropertyClause(propName, cw) 66 } 67 68 class := sch.FindClassByName(className) 69 if class == nil { 70 return errors.Errorf("class %q does not exist in schema", 71 className) 72 } 73 74 propNameTyped := string(propName) 75 lengthPropName, isPropLengthFilter := schema.IsPropertyLength(propNameTyped, 0) 76 if isPropLengthFilter { 77 propName = schema.PropertyName(lengthPropName) 78 } 79 80 prop, err := sch.GetProperty(className, propName) 81 if err != nil { 82 return err 83 } 84 85 if cw.getOperator() == OperatorIsNull { 86 if !cw.isType(schema.DataTypeBoolean) { 87 return errors.Errorf("operator IsNull requires a booleanValue, got %q instead", 88 cw.getValueNameFromType()) 89 } 90 return nil 91 } 92 93 if isPropLengthFilter { 94 if !cw.isType(schema.DataTypeInt) { 95 return errors.Errorf("Filtering for property length requires IntValue, got %q instead", 96 cw.getValueNameFromType()) 97 } 98 switch op := cw.getOperator(); op { 99 case OperatorEqual, OperatorNotEqual, OperatorGreaterThan, OperatorGreaterThanEqual, 100 OperatorLessThan, OperatorLessThanEqual: 101 // ok 102 default: 103 return errors.Errorf("Filtering for property length supports operators (not) equal and greater/less than (equal), got %q instead", 104 op) 105 } 106 if val := cw.getValue(); val.(int) < 0 { 107 return errors.Errorf("Can only filter for positive property length got %v instead", val) 108 } 109 return nil 110 } 111 112 if isUUIDType(prop.DataType[0]) { 113 return validateUUIDType(propName, cw) 114 } 115 116 if schema.IsRefDataType(prop.DataType) { 117 // bit of an edge case, directly on refs (i.e. not on a primitive prop of a 118 // ref) we only allow valueInt which is what's used to count references 119 if cw.isType(schema.DataTypeInt) { 120 return nil 121 } 122 return errors.Errorf("Property %q is a ref prop to the class %q. Only "+ 123 "\"valueInt\" can be used on a ref prop directly to count the number of refs. "+ 124 "Or did you mean to filter on a primitive prop of the referenced class? "+ 125 "In this case make sure your path contains 3 elements in the form of "+ 126 "[<propName>, <ClassNameOfReferencedClass>, <primitvePropOnClass>]", 127 propName, prop.DataType[0]) 128 } else if baseType, ok := schema.IsArrayType(schema.DataType(prop.DataType[0])); ok { 129 if !cw.isType(baseType) { 130 return errors.Errorf("data type filter cannot use %q on type %q, use %q instead", 131 cw.getValueNameFromType(), 132 schema.DataType(prop.DataType[0]), 133 valueNameFromDataType(baseType)) 134 } 135 } else if !cw.isType(schema.DataType(prop.DataType[0])) { 136 return errors.Errorf("data type filter cannot use %q on type %q, use %q instead", 137 cw.getValueNameFromType(), 138 schema.DataType(prop.DataType[0]), 139 valueNameFromDataType(schema.DataType(prop.DataType[0]))) 140 } 141 142 return nil 143 } 144 145 func valueNameFromDataType(dt schema.DataType) string { 146 return "value" + strings.ToUpper(string(dt[0])) + string(dt[1:]) 147 } 148 149 func mergeErrs(errs []error) error { 150 msgs := make([]string, len(errs)) 151 for i, err := range errs { 152 msgs[i] = err.Error() 153 } 154 155 return errors.Errorf("%s", strings.Join(msgs, ", ")) 156 } 157 158 func IsInternalProperty(propName schema.PropertyName) bool { 159 switch propName { 160 case InternalPropBackwardsCompatID, 161 InternalPropID, 162 InternalPropCreationTimeUnix, 163 InternalPropLastUpdateTimeUnix: 164 return true 165 default: 166 return false 167 } 168 } 169 170 func validateInternalPropertyClause(propName schema.PropertyName, cw *clauseWrapper) error { 171 switch propName { 172 case InternalPropBackwardsCompatID, InternalPropID: 173 if cw.isType(schema.DataTypeText) { 174 return nil 175 } 176 return errors.Errorf( 177 `using ["_id"] to filter by uuid: must use "valueText" to specify the id`) 178 case InternalPropCreationTimeUnix, InternalPropLastUpdateTimeUnix: 179 if cw.isType(schema.DataTypeDate) || cw.isType(schema.DataTypeText) { 180 return nil 181 } 182 return errors.Errorf( 183 `using ["%s"] to filter by timestamp: must use "valueText" or "valueDate"`, propName) 184 default: 185 return errors.Errorf("unsupported internal property: %s", propName) 186 } 187 } 188 189 func isUUIDType(dtString string) bool { 190 dt := schema.DataType(dtString) 191 return dt == schema.DataTypeUUID || dt == schema.DataTypeUUIDArray 192 } 193 194 func validateUUIDType(propName schema.PropertyName, cw *clauseWrapper) error { 195 if cw.isType(schema.DataTypeText) { 196 return validateUUIDOperators(propName, cw) 197 } 198 199 return fmt.Errorf("property %q is of type \"uuid\" or \"uuid[]\": "+ 200 "specify uuid as string using \"valueText\"", propName) 201 } 202 203 func validateUUIDOperators(propName schema.PropertyName, cw *clauseWrapper) error { 204 op := cw.getOperator() 205 206 switch op { 207 case OperatorEqual, OperatorNotEqual, OperatorLessThan, OperatorLessThanEqual, 208 OperatorGreaterThan, OperatorGreaterThanEqual, ContainsAll, ContainsAny: 209 return nil 210 default: 211 return fmt.Errorf("operator %q cannot be used on uuid/uuid[] props", op.Name()) 212 } 213 } 214 215 type clauseWrapper struct { 216 clause *Clause 217 origType schema.DataType 218 aliasType schema.DataType 219 operands []*clauseWrapper 220 } 221 222 func newClauseWrapper(clause *Clause) *clauseWrapper { 223 w := &clauseWrapper{clause: clause} 224 if clause.Operands != nil { 225 w.operands = make([]*clauseWrapper, len(clause.Operands)) 226 for i := range clause.Operands { 227 w.operands[i] = newClauseWrapper(&clause.Operands[i]) 228 } 229 } else { 230 w.origType = clause.Value.Type 231 w.aliasType = deprecatedDataTypeAliases[clause.Value.Type] 232 } 233 return w 234 } 235 236 func (w *clauseWrapper) isType(dt schema.DataType) bool { 237 if w.operands != nil { 238 return false 239 } 240 return dt == w.origType || (dt == w.aliasType && w.aliasType != "") 241 } 242 243 func (w *clauseWrapper) getValueNameFromType() string { 244 return valueNameFromDataType(w.origType) 245 } 246 247 func (w *clauseWrapper) getOperands() []*clauseWrapper { 248 return w.operands 249 } 250 251 func (w *clauseWrapper) getOperator() Operator { 252 return w.clause.Operator 253 } 254 255 func (w *clauseWrapper) getValue() interface{} { 256 return w.clause.Value.Value 257 } 258 259 func (w *clauseWrapper) getClassName() schema.ClassName { 260 if w.operands != nil { 261 return "" 262 } 263 return w.clause.On.GetInnerMost().Class 264 } 265 266 func (w *clauseWrapper) getPropertyName() schema.PropertyName { 267 if w.operands != nil { 268 return "" 269 } 270 return w.clause.On.GetInnerMost().Property 271 } 272 273 func (w *clauseWrapper) updateClause() { 274 if w.operands != nil { 275 for i := range w.operands { 276 w.operands[i].updateClause() 277 } 278 } else { 279 if w.aliasType != "" { 280 w.clause.Value.Type = w.aliasType 281 } 282 } 283 }