github.com/weaviate/weaviate@v1.24.6/adapters/handlers/graphql/local/aggregate/properties.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 aggregate 13 14 import ( 15 "fmt" 16 17 "github.com/tailor-inc/graphql" 18 "github.com/weaviate/weaviate/adapters/handlers/graphql/descriptions" 19 "github.com/weaviate/weaviate/entities/aggregation" 20 "github.com/weaviate/weaviate/entities/models" 21 ) 22 23 func numericPropertyFields(class *models.Class, property *models.Property, prefix string) *graphql.Object { 24 getMetaIntFields := graphql.Fields{ 25 "sum": &graphql.Field{ 26 Name: fmt.Sprintf("%s%s%sSum", prefix, class.Class, property.Name), 27 Description: descriptions.AggregateSum, 28 Type: graphql.Float, 29 Resolve: makeResolveNumericFieldAggregator("sum"), 30 }, 31 "minimum": &graphql.Field{ 32 Name: fmt.Sprintf("%s%s%sMinimum", prefix, class.Class, property.Name), 33 Description: descriptions.AggregateMin, 34 Type: graphql.Float, 35 Resolve: makeResolveNumericFieldAggregator("minimum"), 36 }, 37 "maximum": &graphql.Field{ 38 Name: fmt.Sprintf("%s%s%sMaximum", prefix, class.Class, property.Name), 39 Description: descriptions.AggregateMax, 40 Type: graphql.Float, 41 Resolve: makeResolveNumericFieldAggregator("maximum"), 42 }, 43 "mean": &graphql.Field{ 44 Name: fmt.Sprintf("%s%s%sMean", prefix, class.Class, property.Name), 45 Description: descriptions.AggregateMean, 46 Type: graphql.Float, 47 Resolve: makeResolveNumericFieldAggregator("mean"), 48 }, 49 "mode": &graphql.Field{ 50 Name: fmt.Sprintf("%s%s%sMode", prefix, class.Class, property.Name), 51 Description: descriptions.AggregateMode, 52 Type: graphql.Float, 53 Resolve: makeResolveNumericFieldAggregator("mode"), 54 }, 55 "median": &graphql.Field{ 56 Name: fmt.Sprintf("%s%s%sMedian", prefix, class.Class, property.Name), 57 Description: descriptions.AggregateMedian, 58 Type: graphql.Float, 59 Resolve: makeResolveNumericFieldAggregator("median"), 60 }, 61 "count": &graphql.Field{ 62 Name: fmt.Sprintf("%s%s%sCount", prefix, class.Class, property.Name), 63 Description: descriptions.AggregateCount, 64 Type: graphql.Int, 65 Resolve: makeResolveNumericFieldAggregator("count"), 66 }, 67 "type": &graphql.Field{ 68 Name: fmt.Sprintf("%s%s%sType", prefix, class.Class, property.Name), 69 Description: descriptions.AggregateCount, 70 Type: graphql.String, 71 Resolve: func(p graphql.ResolveParams) (interface{}, error) { 72 prop, ok := p.Source.(aggregation.Property) 73 if !ok { 74 return nil, fmt.Errorf("numerical: type: expected aggregation.Property, got %T", p.Source) 75 } 76 77 return prop.SchemaType, nil 78 }, 79 }, 80 } 81 82 return graphql.NewObject(graphql.ObjectConfig{ 83 Name: fmt.Sprintf("%s%s%sObj", prefix, class.Class, property.Name), 84 Fields: getMetaIntFields, 85 Description: descriptions.AggregatePropertyObject, 86 }) 87 } 88 89 func datePropertyFields(class *models.Class, 90 property *models.Property, prefix string, 91 ) *graphql.Object { 92 getMetaDateFields := graphql.Fields{ 93 "count": &graphql.Field{ 94 Name: fmt.Sprintf("%s%sCount", prefix, class.Class), 95 Description: descriptions.AggregateCount, 96 Type: graphql.Int, 97 Resolve: makeResolveDateFieldAggregator("count"), 98 }, 99 "minimum": &graphql.Field{ 100 Name: fmt.Sprintf("%s%s%sMinimum", prefix, class.Class, property.Name), 101 Description: descriptions.AggregateMin, 102 Type: graphql.String, 103 Resolve: makeResolveDateFieldAggregator("minimum"), 104 }, 105 "maximum": &graphql.Field{ 106 Name: fmt.Sprintf("%s%s%sMaximum", prefix, class.Class, property.Name), 107 Description: descriptions.AggregateMax, 108 Type: graphql.String, 109 Resolve: makeResolveDateFieldAggregator("maximum"), 110 }, 111 "mode": &graphql.Field{ 112 Name: fmt.Sprintf("%s%s%sMode", prefix, class.Class, property.Name), 113 Description: descriptions.AggregateMode, 114 Type: graphql.String, 115 Resolve: makeResolveDateFieldAggregator("mode"), 116 }, 117 "median": &graphql.Field{ 118 Name: fmt.Sprintf("%s%s%sMedian", prefix, class.Class, property.Name), 119 Description: descriptions.AggregateMedian, 120 Type: graphql.String, 121 Resolve: makeResolveDateFieldAggregator("median"), 122 }, 123 } 124 125 return graphql.NewObject(graphql.ObjectConfig{ 126 Name: fmt.Sprintf("%s%s%sObj", prefix, class.Class, property.Name), 127 Fields: getMetaDateFields, 128 Description: descriptions.AggregatePropertyObject, 129 }) 130 } 131 132 func referencePropertyFields(class *models.Class, 133 property *models.Property, prefix string, 134 ) *graphql.Object { 135 getMetaPointingFields := graphql.Fields{ 136 "type": &graphql.Field{ 137 Name: fmt.Sprintf("%s%sType", prefix, class.Class), 138 Description: descriptions.AggregatePropertyType, 139 Type: graphql.String, 140 Resolve: func(p graphql.ResolveParams) (interface{}, error) { 141 prop, ok := p.Source.(aggregation.Property) 142 if !ok { 143 return nil, fmt.Errorf("ref property type: expected aggregation.Property, got %T", 144 p.Source) 145 } 146 147 return prop.SchemaType, nil 148 }, 149 }, 150 "pointingTo": &graphql.Field{ 151 Name: fmt.Sprintf("%s%sPointingTo", prefix, class.Class), 152 Description: descriptions.AggregateClassPropertyPointingTo, 153 Type: graphql.NewList(graphql.String), 154 Resolve: func(p graphql.ResolveParams) (interface{}, error) { 155 ref, err := extractReferenceAggregation(p.Source) 156 if err != nil { 157 return nil, fmt.Errorf("ref property pointingTo: %v", err) 158 } 159 160 return ref.PointingTo, nil 161 }, 162 DeprecationReason: "Experimental, the format will change", 163 }, 164 } 165 166 return graphql.NewObject(graphql.ObjectConfig{ 167 Name: fmt.Sprintf("%s%s%sObj", prefix, class.Class, property.Name), 168 Fields: getMetaPointingFields, 169 Description: descriptions.AggregatePropertyObject, 170 }) 171 } 172 173 func extractReferenceAggregation(source interface{}) (*aggregation.Reference, error) { 174 property, ok := source.(aggregation.Property) 175 if !ok { 176 return nil, fmt.Errorf("expected aggregation.Property, got %T", source) 177 } 178 179 if property.Type != aggregation.PropertyTypeReference { 180 return nil, fmt.Errorf("expected property to be of type reference, got %s", property.Type) 181 } 182 183 return &property.ReferenceAggregation, nil 184 } 185 186 func booleanPropertyFields(class *models.Class, 187 property *models.Property, prefix string, 188 ) *graphql.Object { 189 getMetaPointingFields := graphql.Fields{ 190 "count": &graphql.Field{ 191 Name: fmt.Sprintf("%s%s%sCount", prefix, class.Class, property.Name), 192 Description: descriptions.AggregatePropertyCount, 193 Type: graphql.Int, 194 Resolve: booleanResolver(func(b aggregation.Boolean) interface{} { return b.Count }), 195 }, 196 "totalTrue": &graphql.Field{ 197 Name: fmt.Sprintf("%s%s%sTotalTrue", prefix, class.Class, property.Name), 198 Description: descriptions.AggregateClassPropertyTotalTrue, 199 Type: graphql.Int, 200 Resolve: booleanResolver(func(b aggregation.Boolean) interface{} { return b.TotalTrue }), 201 }, 202 "percentageTrue": &graphql.Field{ 203 Name: fmt.Sprintf("%s%s%sPercentageTrue", prefix, class.Class, property.Name), 204 Description: descriptions.AggregateClassPropertyPercentageTrue, 205 Type: graphql.Float, 206 Resolve: booleanResolver(func(b aggregation.Boolean) interface{} { return b.PercentageTrue }), 207 }, 208 "totalFalse": &graphql.Field{ 209 Name: fmt.Sprintf("%s%s%sTotalFalse", prefix, class.Class, property.Name), 210 Description: descriptions.AggregateClassPropertyTotalFalse, 211 Type: graphql.Int, 212 Resolve: booleanResolver(func(b aggregation.Boolean) interface{} { return b.TotalFalse }), 213 }, 214 "percentageFalse": &graphql.Field{ 215 Name: fmt.Sprintf("%s%s%sPercentageFalse", prefix, class.Class, property.Name), 216 Description: descriptions.AggregateClassPropertyPercentageFalse, 217 Type: graphql.Float, 218 Resolve: booleanResolver(func(b aggregation.Boolean) interface{} { return b.PercentageFalse }), 219 }, 220 "type": &graphql.Field{ 221 Name: fmt.Sprintf("%s%s%sType", prefix, class.Class, property.Name), 222 Description: descriptions.AggregateCount, 223 Type: graphql.String, 224 Resolve: func(p graphql.ResolveParams) (interface{}, error) { 225 prop, ok := p.Source.(aggregation.Property) 226 if !ok { 227 return nil, fmt.Errorf("boolean: type: expected aggregation.Property, got %T", p.Source) 228 } 229 230 return prop.SchemaType, nil 231 }, 232 }, 233 } 234 235 return graphql.NewObject(graphql.ObjectConfig{ 236 Name: fmt.Sprintf("%s%s%sObj", prefix, class.Class, property.Name), 237 Fields: getMetaPointingFields, 238 Description: descriptions.AggregatePropertyObject, 239 }) 240 } 241 242 type booleanExtractorFunc func(aggregation.Boolean) interface{} 243 244 func booleanResolver(extractor booleanExtractorFunc) func(p graphql.ResolveParams) (interface{}, error) { 245 return func(p graphql.ResolveParams) (interface{}, error) { 246 boolean, err := extractBooleanAggregation(p.Source) 247 if err != nil { 248 return nil, fmt.Errorf("boolean: %v", err) 249 } 250 251 return extractor(*boolean), nil 252 } 253 } 254 255 func extractBooleanAggregation(source interface{}) (*aggregation.Boolean, error) { 256 property, ok := source.(aggregation.Property) 257 if !ok { 258 return nil, fmt.Errorf("expected aggregation.Property, got %T", source) 259 } 260 261 if property.Type != aggregation.PropertyTypeBoolean { 262 return nil, fmt.Errorf("expected property to be of type boolean, got %s", property.Type) 263 } 264 265 return &property.BooleanAggregation, nil 266 } 267 268 func stringPropertyFields(class *models.Class, 269 property *models.Property, prefix string, 270 ) *graphql.Object { 271 getAggregatePointingFields := graphql.Fields{ 272 "count": &graphql.Field{ 273 Name: fmt.Sprintf("%s%sCount", prefix, class.Class), 274 Description: descriptions.AggregatePropertyCount, 275 Type: graphql.Int, 276 Resolve: textResolver(func(text aggregation.Text) (interface{}, error) { 277 return text.Count, nil 278 }), 279 }, 280 "type": &graphql.Field{ 281 Name: fmt.Sprintf("%s%s%sType", prefix, class.Class, property.Name), 282 Description: descriptions.AggregateCount, 283 Type: graphql.String, 284 Resolve: func(p graphql.ResolveParams) (interface{}, error) { 285 prop, ok := p.Source.(aggregation.Property) 286 if !ok { 287 return nil, fmt.Errorf("text type: expected aggregation.Property, got %T", p.Source) 288 } 289 290 return prop.SchemaType, nil 291 }, 292 }, 293 "topOccurrences": &graphql.Field{ 294 Name: fmt.Sprintf("%s%sTopOccurrences", prefix, class.Class), 295 Description: descriptions.AggregatePropertyTopOccurrences, 296 Type: graphql.NewList(stringTopOccurrences(class, property, prefix)), 297 Resolve: textResolver(func(text aggregation.Text) (interface{}, error) { 298 list := make([]interface{}, len(text.Items)) 299 for i, to := range text.Items { 300 list[i] = to 301 } 302 303 return list, nil 304 }), 305 Args: graphql.FieldConfigArgument{ 306 "limit": &graphql.ArgumentConfig{ 307 Description: descriptions.First, 308 Type: graphql.Int, 309 }, 310 }, 311 }, 312 } 313 314 return graphql.NewObject(graphql.ObjectConfig{ 315 Name: fmt.Sprintf("%s%s%sObj", prefix, class.Class, property.Name), 316 Fields: getAggregatePointingFields, 317 Description: descriptions.AggregatePropertyObject, 318 }) 319 } 320 321 type textExtractorFunc func(aggregation.Text) (interface{}, error) 322 323 func textResolver(extractor textExtractorFunc) func(p graphql.ResolveParams) (interface{}, error) { 324 return func(p graphql.ResolveParams) (interface{}, error) { 325 text, err := extractTextAggregation(p.Source) 326 if err != nil { 327 return nil, fmt.Errorf("text: %v", err) 328 } 329 330 return extractor(text) 331 } 332 } 333 334 func stringTopOccurrences(class *models.Class, 335 property *models.Property, prefix string, 336 ) *graphql.Object { 337 getAggregateAggregatePointingFields := graphql.Fields{ 338 "value": &graphql.Field{ 339 Name: fmt.Sprintf("%s%s%sTopOccurrencesValue", prefix, class.Class, property.Name), 340 Description: descriptions.AggregatePropertyTopOccurrencesValue, 341 Type: graphql.String, 342 Resolve: textOccurrenceResolver(func(t aggregation.TextOccurrence) interface{} { return t.Value }), 343 }, 344 "occurs": &graphql.Field{ 345 Name: fmt.Sprintf("%s%s%sTopOccurrencesOccurs", prefix, class.Class, property.Name), 346 Description: descriptions.AggregatePropertyTopOccurrencesOccurs, 347 Type: graphql.Int, 348 Resolve: textOccurrenceResolver(func(t aggregation.TextOccurrence) interface{} { return t.Occurs }), 349 }, 350 } 351 352 getAggregateAggregatePointing := graphql.ObjectConfig{ 353 Name: fmt.Sprintf("%s%s%sTopOccurrencesObj", prefix, class.Class, property.Name), 354 Fields: getAggregateAggregatePointingFields, 355 Description: descriptions.AggregatePropertyTopOccurrences, 356 } 357 358 return graphql.NewObject(getAggregateAggregatePointing) 359 } 360 361 type textOccurrenceExtractorFunc func(aggregation.TextOccurrence) interface{} 362 363 func textOccurrenceResolver(extractor textOccurrenceExtractorFunc) func(p graphql.ResolveParams) (interface{}, error) { 364 return func(p graphql.ResolveParams) (interface{}, error) { 365 textOccurrence, ok := p.Source.(aggregation.TextOccurrence) 366 if !ok { 367 return nil, fmt.Errorf("textOccurrence: %s: expected aggregation.TextOccurrence, but got %T", 368 p.Info.FieldName, p.Source) 369 } 370 371 return extractor(textOccurrence), nil 372 } 373 } 374 375 func extractTextAggregation(source interface{}) (aggregation.Text, error) { 376 property, ok := source.(aggregation.Property) 377 if !ok { 378 return aggregation.Text{}, fmt.Errorf("expected aggregation.Property, got %T", source) 379 } 380 381 if property.Type == aggregation.PropertyTypeNumerical { 382 // in this case we can only use count 383 return aggregation.Text{ 384 Count: property.NumericalAggregations["count"].(int), 385 }, nil 386 } 387 388 if property.Type != aggregation.PropertyTypeText { 389 return aggregation.Text{}, fmt.Errorf("expected property to be of type text, got %s (%#v)", property.Type, property) 390 } 391 392 return property.TextAggregation, nil 393 } 394 395 func groupedByProperty(class *models.Class) *graphql.Object { 396 classProperties := graphql.Fields{ 397 "path": &graphql.Field{ 398 Description: descriptions.AggregateGroupedByGroupedByPath, 399 Type: graphql.NewList(graphql.String), 400 Resolve: groupedByResolver(func(g *aggregation.GroupedBy) interface{} { return g.Path }), 401 }, 402 "value": &graphql.Field{ 403 Description: descriptions.AggregateGroupedByGroupedByValue, 404 Type: graphql.String, 405 Resolve: groupedByResolver(func(g *aggregation.GroupedBy) interface{} { return g.Value }), 406 }, 407 } 408 409 classPropertiesObj := graphql.NewObject(graphql.ObjectConfig{ 410 Name: fmt.Sprintf("Aggregate%sGroupedByObj", class.Class), 411 Fields: classProperties, 412 Description: descriptions.AggregateGroupedByObj, 413 }) 414 415 return classPropertiesObj 416 } 417 418 type groupedByExtractorFunc func(*aggregation.GroupedBy) interface{} 419 420 func groupedByResolver(extractor groupedByExtractorFunc) func(p graphql.ResolveParams) (interface{}, error) { 421 return func(p graphql.ResolveParams) (interface{}, error) { 422 groupedBy, ok := p.Source.(*aggregation.GroupedBy) 423 if !ok { 424 return nil, fmt.Errorf("groupedBy: %s: expected aggregation.GroupedBy, but got %T", 425 p.Info.FieldName, p.Source) 426 } 427 428 return extractor(groupedBy), nil 429 } 430 } 431 432 func makeResolveNumericFieldAggregator(aggregator string) func(p graphql.ResolveParams) (interface{}, error) { 433 return func(p graphql.ResolveParams) (interface{}, error) { 434 num, err := extractNumericAggregation(p.Source) 435 if err != nil { 436 return nil, fmt.Errorf("numerical aggregator %s: %v", aggregator, err) 437 } 438 439 return num[aggregator], nil 440 } 441 } 442 443 func extractNumericAggregation(source interface{}) (map[string]interface{}, error) { 444 property, ok := source.(aggregation.Property) 445 if !ok { 446 return nil, fmt.Errorf("expected aggregation.Property, got %T", source) 447 } 448 449 if property.Type != aggregation.PropertyTypeNumerical { 450 return nil, fmt.Errorf("expected property to be of type numerical, got %s", property.Type) 451 } 452 453 return property.NumericalAggregations, nil 454 } 455 456 func makeResolveDateFieldAggregator(aggregator string) func(p graphql.ResolveParams) (interface{}, error) { 457 return func(p graphql.ResolveParams) (interface{}, error) { 458 date, err := extractDateAggregation(p.Source) 459 if err != nil { 460 return nil, fmt.Errorf("date aggregator %s: %v", aggregator, err) 461 } 462 463 return date[aggregator], nil 464 } 465 } 466 467 func extractDateAggregation(source interface{}) (map[string]interface{}, error) { 468 property, ok := source.(aggregation.Property) 469 if !ok { 470 return nil, fmt.Errorf("expected aggregation.Property, got %T", source) 471 } 472 473 if property.Type != aggregation.PropertyTypeDate { 474 return nil, fmt.Errorf("expected property to be of type date, got %s", property.Type) 475 } 476 477 return property.DateAggregations, nil 478 }