github.com/weaviate/weaviate@v1.24.6/adapters/handlers/graphql/local/aggregate/aggregate.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/adapters/handlers/graphql/local/common_filters" 20 "github.com/weaviate/weaviate/adapters/handlers/graphql/utils" 21 "github.com/weaviate/weaviate/entities/aggregation" 22 "github.com/weaviate/weaviate/entities/models" 23 "github.com/weaviate/weaviate/entities/schema" 24 "github.com/weaviate/weaviate/usecases/config" 25 ) 26 27 type ModulesProvider interface { 28 AggregateArguments(class *models.Class) map[string]*graphql.ArgumentConfig 29 ExtractSearchParams(arguments map[string]interface{}, className string) map[string]interface{} 30 } 31 32 // Build the Aggregate Kinds schema 33 func Build(dbSchema *schema.Schema, config config.Config, 34 modulesProvider ModulesProvider, 35 ) (*graphql.Field, error) { 36 if len(dbSchema.Objects.Classes) == 0 { 37 return nil, utils.ErrEmptySchema 38 } 39 40 var err error 41 var localAggregateObjects *graphql.Object 42 if len(dbSchema.Objects.Classes) > 0 { 43 localAggregateObjects, err = classFields(dbSchema.Objects.Classes, config, modulesProvider) 44 if err != nil { 45 return nil, err 46 } 47 } 48 49 field := graphql.Field{ 50 Name: "Aggregate", 51 Description: descriptions.AggregateWhere, 52 Type: localAggregateObjects, 53 Resolve: passThroughResolver, 54 } 55 56 return &field, nil 57 } 58 59 func classFields(databaseSchema []*models.Class, 60 config config.Config, modulesProvider ModulesProvider, 61 ) (*graphql.Object, error) { 62 fields := graphql.Fields{} 63 64 for _, class := range databaseSchema { 65 field, err := classField(class, class.Description, config, modulesProvider) 66 if err != nil { 67 return nil, err 68 } 69 70 fields[class.Class] = field 71 } 72 73 return graphql.NewObject(graphql.ObjectConfig{ 74 Name: "AggregateObjectsObj", 75 Fields: fields, 76 Description: descriptions.AggregateObjectsObj, 77 }), nil 78 } 79 80 func classField(class *models.Class, description string, 81 config config.Config, modulesProvider ModulesProvider, 82 ) (*graphql.Field, error) { 83 metaClassName := fmt.Sprintf("Aggregate%s", class.Class) 84 85 fields := graphql.ObjectConfig{ 86 Name: metaClassName, 87 Fields: (graphql.FieldsThunk)(func() graphql.Fields { 88 fields, err := classPropertyFields(class) 89 if err != nil { 90 // we cannot return an error in this FieldsThunk and have to panic unfortunately 91 panic(fmt.Sprintf("Failed to assemble single Local Aggregate Class field: %s", err)) 92 } 93 94 return fields 95 }), 96 Description: description, 97 } 98 99 fieldsObject := graphql.NewObject(fields) 100 fieldsField := &graphql.Field{ 101 Type: graphql.NewList(fieldsObject), 102 Description: description, 103 Args: graphql.FieldConfigArgument{ 104 "limit": &graphql.ArgumentConfig{ 105 Description: descriptions.First, 106 Type: graphql.Int, 107 }, 108 "where": &graphql.ArgumentConfig{ 109 Description: descriptions.GetWhere, 110 Type: graphql.NewInputObject( 111 graphql.InputObjectConfig{ 112 Name: fmt.Sprintf("AggregateObjects%sWhereInpObj", class.Class), 113 Fields: common_filters.BuildNew(fmt.Sprintf("AggregateObjects%s", class.Class)), 114 Description: descriptions.GetWhereInpObj, 115 }, 116 ), 117 }, 118 "groupBy": &graphql.ArgumentConfig{ 119 Description: descriptions.GroupBy, 120 Type: graphql.NewList(graphql.String), 121 }, 122 "nearVector": nearVectorArgument(class.Class), 123 "nearObject": nearObjectArgument(class.Class), 124 "objectLimit": &graphql.ArgumentConfig{ 125 Description: descriptions.First, 126 Type: graphql.Int, 127 }, 128 "hybrid": hybridArgument(fieldsObject, class, modulesProvider), 129 }, 130 Resolve: makeResolveClass(modulesProvider, class), 131 } 132 133 if modulesProvider != nil { 134 for name, argument := range modulesProvider.AggregateArguments(class) { 135 fieldsField.Args[name] = argument 136 } 137 } 138 139 if schema.MultiTenancyEnabled(class) { 140 fieldsField.Args["tenant"] = tenantArgument() 141 } 142 143 return fieldsField, nil 144 } 145 146 func classPropertyFields(class *models.Class) (graphql.Fields, error) { 147 fields := graphql.Fields{} 148 for _, property := range class.Properties { 149 propertyType, err := schema.GetPropertyDataType(class, property.Name) 150 if err != nil { 151 return nil, fmt.Errorf("%s.%s: %s", class.Class, property.Name, err) 152 } 153 154 convertedDataType, err := classPropertyField(*propertyType, class, property) 155 if err != nil { 156 return nil, err 157 } 158 159 fields[property.Name] = convertedDataType 160 } 161 162 // Special case: meta { count } appended to all regular props 163 fields["meta"] = &graphql.Field{ 164 Description: descriptions.LocalMetaObj, 165 Type: metaObject(fmt.Sprintf("Aggregate%s", class.Class)), 166 Resolve: func(p graphql.ResolveParams) (interface{}, error) { 167 // pass-through 168 return p.Source, nil 169 }, 170 } 171 172 // Always append Grouped By field 173 fields["groupedBy"] = &graphql.Field{ 174 Description: descriptions.AggregateGroupedBy, 175 Type: groupedByProperty(class), 176 Resolve: func(p graphql.ResolveParams) (interface{}, error) { 177 switch typed := p.Source.(type) { 178 case aggregation.Group: 179 return typed.GroupedBy, nil 180 case map[string]interface{}: 181 return typed["groupedBy"], nil 182 default: 183 return nil, fmt.Errorf("groupedBy: unsupported type %T", p.Source) 184 } 185 }, 186 } 187 188 return fields, nil 189 } 190 191 func metaObject(prefix string) *graphql.Object { 192 return graphql.NewObject(graphql.ObjectConfig{ 193 Name: fmt.Sprintf("%sMetaObject", prefix), 194 Fields: graphql.Fields{ 195 "count": &graphql.Field{ 196 Type: graphql.Int, 197 Resolve: func(p graphql.ResolveParams) (interface{}, error) { 198 group, ok := p.Source.(aggregation.Group) 199 if !ok { 200 return nil, fmt.Errorf("meta count: expected aggregation.Group, got %T", p.Source) 201 } 202 203 return group.Count, nil 204 }, 205 }, 206 }, 207 }) 208 } 209 210 func classPropertyField(dataType schema.DataType, class *models.Class, property *models.Property) (*graphql.Field, error) { 211 switch dataType { 212 case schema.DataTypeText: 213 return makePropertyField(class, property, stringPropertyFields) 214 case schema.DataTypeInt: 215 return makePropertyField(class, property, numericPropertyFields) 216 case schema.DataTypeNumber: 217 return makePropertyField(class, property, numericPropertyFields) 218 case schema.DataTypeBoolean: 219 return makePropertyField(class, property, booleanPropertyFields) 220 case schema.DataTypeDate: 221 return makePropertyField(class, property, datePropertyFields) 222 case schema.DataTypeCRef: 223 return makePropertyField(class, property, referencePropertyFields) 224 case schema.DataTypeGeoCoordinates: 225 // simply skip for now, see gh-729 226 return nil, nil 227 case schema.DataTypePhoneNumber: 228 // skipping for now, see gh-1088 where it was outscoped 229 return nil, nil 230 case schema.DataTypeBlob: 231 return makePropertyField(class, property, stringPropertyFields) 232 case schema.DataTypeTextArray: 233 return makePropertyField(class, property, stringPropertyFields) 234 case schema.DataTypeIntArray, schema.DataTypeNumberArray: 235 return makePropertyField(class, property, numericPropertyFields) 236 case schema.DataTypeBooleanArray: 237 return makePropertyField(class, property, booleanPropertyFields) 238 case schema.DataTypeDateArray: 239 return makePropertyField(class, property, datePropertyFields) 240 case schema.DataTypeUUID, schema.DataTypeUUIDArray: 241 // not aggregatable 242 return nil, nil 243 case schema.DataTypeObject, schema.DataTypeObjectArray: 244 // TODO: check if it's aggregable, skip for now 245 return nil, nil 246 default: 247 return nil, fmt.Errorf(schema.ErrorNoSuchDatatype+": %s", dataType) 248 } 249 } 250 251 type propertyFieldMaker func(class *models.Class, 252 property *models.Property, prefix string) *graphql.Object 253 254 func makePropertyField(class *models.Class, property *models.Property, 255 fieldMaker propertyFieldMaker, 256 ) (*graphql.Field, error) { 257 prefix := "Aggregate" 258 return &graphql.Field{ 259 Description: fmt.Sprintf(`%s"%s"`, descriptions.AggregateProperty, property.Name), 260 Type: fieldMaker(class, property, prefix), 261 Resolve: func(p graphql.ResolveParams) (interface{}, error) { 262 switch typed := p.Source.(type) { 263 case aggregation.Group: 264 res, ok := typed.Properties[property.Name] 265 if !ok { 266 return nil, fmt.Errorf("missing property '%s'", property.Name) 267 } 268 269 return res, nil 270 271 default: 272 return nil, fmt.Errorf("property %s, unsupported type %T", property.Name, p.Source) 273 } 274 }, 275 }, nil 276 } 277 278 func passThroughResolver(p graphql.ResolveParams) (interface{}, error) { 279 // bubble up root resolver 280 return p.Source, nil 281 }