github.com/weaviate/weaviate@v1.24.6/adapters/handlers/graphql/local/get/class_builder.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 get
    13  
    14  import (
    15  	"fmt"
    16  
    17  	"github.com/weaviate/weaviate/adapters/handlers/graphql/local/common_filters"
    18  
    19  	"github.com/pkg/errors"
    20  	"github.com/sirupsen/logrus"
    21  	"github.com/tailor-inc/graphql"
    22  	"github.com/weaviate/weaviate/adapters/handlers/graphql/descriptions"
    23  	"github.com/weaviate/weaviate/entities/models"
    24  	"github.com/weaviate/weaviate/entities/schema"
    25  )
    26  
    27  type classBuilder struct {
    28  	schema          *schema.Schema
    29  	knownClasses    map[string]*graphql.Object
    30  	beaconClass     *graphql.Object
    31  	logger          logrus.FieldLogger
    32  	modulesProvider ModulesProvider
    33  }
    34  
    35  func newClassBuilder(schema *schema.Schema, logger logrus.FieldLogger,
    36  	modulesProvider ModulesProvider,
    37  ) *classBuilder {
    38  	b := &classBuilder{}
    39  
    40  	b.logger = logger
    41  	b.schema = schema
    42  	b.modulesProvider = modulesProvider
    43  
    44  	b.initKnownClasses()
    45  	b.initBeaconClass()
    46  
    47  	return b
    48  }
    49  
    50  func (b *classBuilder) initKnownClasses() {
    51  	b.knownClasses = map[string]*graphql.Object{}
    52  }
    53  
    54  func (b *classBuilder) initBeaconClass() {
    55  	b.beaconClass = graphql.NewObject(graphql.ObjectConfig{
    56  		Name: "Beacon",
    57  		Fields: graphql.Fields{
    58  			"beacon": &graphql.Field{
    59  				Type: graphql.String,
    60  			},
    61  		},
    62  	})
    63  }
    64  
    65  func (b *classBuilder) objects() (*graphql.Object, error) {
    66  	return b.kinds(b.schema.Objects)
    67  }
    68  
    69  func (b *classBuilder) kinds(kindSchema *models.Schema) (*graphql.Object, error) {
    70  	// needs to be defined outside the individual class as there can only be one definition of an enum
    71  	fusionAlgoEnum := graphql.NewEnum(graphql.EnumConfig{
    72  		Name: "FusionEnum",
    73  		Values: graphql.EnumValueConfigMap{
    74  			"rankedFusion": &graphql.EnumValueConfig{
    75  				Value: common_filters.HybridRankedFusion,
    76  			},
    77  			"relativeScoreFusion": &graphql.EnumValueConfig{
    78  				Value: common_filters.HybridRelativeScoreFusion,
    79  			},
    80  		},
    81  	})
    82  	classFields := graphql.Fields{}
    83  	for _, class := range kindSchema.Classes {
    84  		classField, err := b.classField(class, fusionAlgoEnum)
    85  		if err != nil {
    86  			return nil, fmt.Errorf("Could not build class for %s", class.Class)
    87  		}
    88  		classFields[class.Class] = classField
    89  	}
    90  
    91  	classes := graphql.NewObject(graphql.ObjectConfig{
    92  		Name:        "GetObjectsObj",
    93  		Fields:      classFields,
    94  		Description: descriptions.GetObjectsActionsObj,
    95  	})
    96  
    97  	return classes, nil
    98  }
    99  
   100  func (b *classBuilder) classField(class *models.Class, fusionEnum *graphql.Enum) (*graphql.Field, error) {
   101  	classObject := b.classObject(class)
   102  	b.knownClasses[class.Class] = classObject
   103  	classField := buildGetClassField(classObject, class, b.modulesProvider, fusionEnum)
   104  	return &classField, nil
   105  }
   106  
   107  func (b *classBuilder) classObject(class *models.Class) *graphql.Object {
   108  	return graphql.NewObject(graphql.ObjectConfig{
   109  		Name: class.Class,
   110  		Fields: (graphql.FieldsThunk)(func() graphql.Fields {
   111  			classProperties := graphql.Fields{}
   112  			for _, property := range class.Properties {
   113  				propertyType, err := b.schema.FindPropertyDataType(property.DataType)
   114  				if err != nil {
   115  					if errors.Is(err, schema.ErrRefToNonexistentClass) {
   116  						// This is a common case when a class which is referenced
   117  						// by another class is deleted, leaving the referencing
   118  						// class with an invalid reference property. Panicking
   119  						// is not necessary here
   120  						b.logger.WithField("action", "graphql_rebuild").
   121  							Warnf("ignoring ref prop %q on class %q, because it contains reference to nonexistent class %q",
   122  								property.Name, class.Class, property.DataType)
   123  
   124  						continue
   125  					} else {
   126  						// We can't return an error in this FieldsThunk function, so we need to panic
   127  						panic(fmt.Sprintf("buildGetClass: wrong propertyType for %s.%s; %s",
   128  							class.Class, property.Name, err.Error()))
   129  					}
   130  				}
   131  
   132  				if propertyType.IsPrimitive() {
   133  					classProperties[property.Name] = b.primitiveField(propertyType, property,
   134  						class.Class)
   135  				} else if propertyType.IsNested() {
   136  					classProperties[property.Name] = b.nestedField(propertyType, property,
   137  						class.Class)
   138  				} else {
   139  					classProperties[property.Name] = b.referenceField(propertyType, property,
   140  						class.Class)
   141  				}
   142  			}
   143  
   144  			b.additionalFields(classProperties, class)
   145  
   146  			return classProperties
   147  		}),
   148  		Description: class.Description,
   149  	})
   150  }
   151  
   152  func (b *classBuilder) additionalFields(classProperties graphql.Fields, class *models.Class) {
   153  	additionalProperties := graphql.Fields{}
   154  	additionalProperties["classification"] = b.additionalClassificationField(class)
   155  	additionalProperties["certainty"] = b.additionalCertaintyField(class)
   156  	additionalProperties["distance"] = b.additionalDistanceField(class)
   157  	additionalProperties["vector"] = b.additionalVectorField(class)
   158  	additionalProperties["vectors"] = b.additionalVectorsField(class)
   159  	additionalProperties["id"] = b.additionalIDField()
   160  	additionalProperties["creationTimeUnix"] = b.additionalCreationTimeUnix()
   161  	additionalProperties["lastUpdateTimeUnix"] = b.additionalLastUpdateTimeUnix()
   162  	additionalProperties["score"] = b.additionalScoreField()
   163  	additionalProperties["explainScore"] = b.additionalExplainScoreField()
   164  	additionalProperties["group"] = b.additionalGroupField(classProperties, class)
   165  	if replicationEnabled(class) {
   166  		additionalProperties["isConsistent"] = b.isConsistentField()
   167  	}
   168  	// module specific additional properties
   169  	if b.modulesProvider != nil {
   170  		for name, field := range b.modulesProvider.GetAdditionalFields(class) {
   171  			additionalProperties[name] = field
   172  		}
   173  	}
   174  	classProperties["_additional"] = &graphql.Field{
   175  		Type: graphql.NewObject(graphql.ObjectConfig{
   176  			Name:   fmt.Sprintf("%sAdditional", class.Class),
   177  			Fields: additionalProperties,
   178  		}),
   179  	}
   180  }
   181  
   182  func (b *classBuilder) additionalIDField() *graphql.Field {
   183  	return &graphql.Field{
   184  		Description: descriptions.GetClassUUID,
   185  		Type:        graphql.String,
   186  	}
   187  }
   188  
   189  func (b *classBuilder) additionalClassificationField(class *models.Class) *graphql.Field {
   190  	return &graphql.Field{
   191  		Type: graphql.NewObject(graphql.ObjectConfig{
   192  			Name: fmt.Sprintf("%sAdditionalClassification", class.Class),
   193  			Fields: graphql.Fields{
   194  				"id":               &graphql.Field{Type: graphql.String},
   195  				"basedOn":          &graphql.Field{Type: graphql.NewList(graphql.String)},
   196  				"scope":            &graphql.Field{Type: graphql.NewList(graphql.String)},
   197  				"classifiedFields": &graphql.Field{Type: graphql.NewList(graphql.String)},
   198  				"completed":        &graphql.Field{Type: graphql.String},
   199  			},
   200  		}),
   201  	}
   202  }
   203  
   204  func (b *classBuilder) additionalCertaintyField(class *models.Class) *graphql.Field {
   205  	return &graphql.Field{
   206  		Type: graphql.Float,
   207  	}
   208  }
   209  
   210  func (b *classBuilder) additionalDistanceField(class *models.Class) *graphql.Field {
   211  	return &graphql.Field{
   212  		Type: graphql.Float,
   213  	}
   214  }
   215  
   216  func (b *classBuilder) additionalVectorField(class *models.Class) *graphql.Field {
   217  	return &graphql.Field{
   218  		Type: graphql.NewList(graphql.Float),
   219  	}
   220  }
   221  
   222  func (b *classBuilder) additionalVectorsField(class *models.Class) *graphql.Field {
   223  	if len(class.VectorConfig) > 0 {
   224  		fields := graphql.Fields{}
   225  		for targetVector := range class.VectorConfig {
   226  			fields[targetVector] = &graphql.Field{
   227  				Name: fmt.Sprintf("%sAdditionalVectors%s", class.Class, targetVector),
   228  				Type: graphql.NewList(graphql.Float),
   229  			}
   230  		}
   231  		return &graphql.Field{
   232  			Type: graphql.NewObject(
   233  				graphql.ObjectConfig{
   234  					Name:   fmt.Sprintf("%sAdditionalVectors", class.Class),
   235  					Fields: fields,
   236  				},
   237  			),
   238  		}
   239  	}
   240  	return nil
   241  }
   242  
   243  func (b *classBuilder) additionalCreationTimeUnix() *graphql.Field {
   244  	return &graphql.Field{
   245  		Type: graphql.String,
   246  	}
   247  }
   248  
   249  func (b *classBuilder) additionalScoreField() *graphql.Field {
   250  	return &graphql.Field{
   251  		Type: graphql.String,
   252  	}
   253  }
   254  
   255  func (b *classBuilder) additionalExplainScoreField() *graphql.Field {
   256  	return &graphql.Field{
   257  		Type: graphql.String,
   258  	}
   259  }
   260  
   261  func (b *classBuilder) additionalLastUpdateTimeUnix() *graphql.Field {
   262  	return &graphql.Field{
   263  		Type: graphql.String,
   264  	}
   265  }
   266  
   267  func (b *classBuilder) isConsistentField() *graphql.Field {
   268  	return &graphql.Field{
   269  		Type: graphql.Boolean,
   270  	}
   271  }
   272  
   273  func (b *classBuilder) additionalGroupField(classProperties graphql.Fields, class *models.Class) *graphql.Field {
   274  	hitsFields := graphql.Fields{
   275  		"_additional": &graphql.Field{
   276  			Type: graphql.NewObject(
   277  				graphql.ObjectConfig{
   278  					Name: fmt.Sprintf("%sAdditionalGroupHitsAdditional", class.Class),
   279  					Fields: graphql.Fields{
   280  						"id":       &graphql.Field{Type: graphql.String},
   281  						"vector":   &graphql.Field{Type: graphql.NewList(graphql.Float)},
   282  						"distance": &graphql.Field{Type: graphql.Float},
   283  					},
   284  				},
   285  			),
   286  		},
   287  	}
   288  	for name, field := range classProperties {
   289  		hitsFields[name] = field
   290  	}
   291  	return &graphql.Field{
   292  		Type: graphql.NewObject(graphql.ObjectConfig{
   293  			Name: fmt.Sprintf("%sAdditionalGroup", class.Class),
   294  			Fields: graphql.Fields{
   295  				"id": &graphql.Field{Type: graphql.Int},
   296  				"groupedBy": &graphql.Field{
   297  					Type: graphql.NewObject(graphql.ObjectConfig{
   298  						Name: fmt.Sprintf("%sAdditionalGroupGroupedBy", class.Class),
   299  						Fields: graphql.Fields{
   300  							"path": &graphql.Field{
   301  								Type: graphql.NewList(graphql.String),
   302  							},
   303  							"value": &graphql.Field{
   304  								Type: graphql.String,
   305  							},
   306  						},
   307  					}),
   308  				},
   309  
   310  				"minDistance": &graphql.Field{Type: graphql.Float},
   311  				"maxDistance": &graphql.Field{Type: graphql.Float},
   312  				"count":       &graphql.Field{Type: graphql.Int},
   313  				"hits": &graphql.Field{
   314  					Type: graphql.NewList(graphql.NewObject(
   315  						graphql.ObjectConfig{
   316  							Name:   fmt.Sprintf("%sAdditionalGroupHits", class.Class),
   317  							Fields: hitsFields,
   318  						},
   319  					)),
   320  				},
   321  			},
   322  		}),
   323  	}
   324  }