github.com/weaviate/weaviate@v1.24.6/usecases/objects/references_update.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 objects
    13  
    14  import (
    15  	"context"
    16  	"errors"
    17  	"fmt"
    18  
    19  	"github.com/go-openapi/strfmt"
    20  	"github.com/weaviate/weaviate/entities/additional"
    21  	"github.com/weaviate/weaviate/entities/models"
    22  	"github.com/weaviate/weaviate/entities/schema"
    23  	"github.com/weaviate/weaviate/entities/schema/crossref"
    24  	"github.com/weaviate/weaviate/usecases/objects/validation"
    25  )
    26  
    27  // PutReferenceInput represents required inputs to add a reference to an existing object.
    28  type PutReferenceInput struct {
    29  	// Class name
    30  	Class string
    31  	// ID of an object
    32  	ID strfmt.UUID
    33  	// Property name
    34  	Property string
    35  	// Ref cross reference
    36  	Refs models.MultipleRef
    37  }
    38  
    39  // UpdateObjectReferences of a specific data object. If the class contains a network
    40  // ref, it has a side-effect on the schema: The schema will be updated to
    41  // include this particular network ref class.
    42  func (m *Manager) UpdateObjectReferences(ctx context.Context, principal *models.Principal,
    43  	input *PutReferenceInput, repl *additional.ReplicationProperties, tenant string,
    44  ) *Error {
    45  	m.metrics.UpdateReferenceInc()
    46  	defer m.metrics.UpdateReferenceDec()
    47  
    48  	res, err := m.getObjectFromRepo(ctx, input.Class, input.ID, additional.Properties{}, nil, tenant)
    49  	if err != nil {
    50  		errnf := ErrNotFound{}
    51  		if errors.As(err, &errnf) {
    52  			if input.Class == "" { // for backward comp reasons
    53  				return &Error{"source object deprecated", StatusBadRequest, err}
    54  			}
    55  			return &Error{"source object", StatusNotFound, err}
    56  		} else if errors.As(err, &ErrMultiTenancy{}) {
    57  			return &Error{"source object", StatusUnprocessableEntity, err}
    58  		}
    59  		return &Error{"source object", StatusInternalServerError, err}
    60  	}
    61  	input.Class = res.ClassName
    62  
    63  	path := fmt.Sprintf("objects/%s/%s", input.Class, input.ID)
    64  	if err := m.authorizer.Authorize(principal, "update", path); err != nil {
    65  		return &Error{path, StatusForbidden, err}
    66  	}
    67  
    68  	unlock, err := m.locks.LockSchema()
    69  	if err != nil {
    70  		return &Error{"cannot lock", StatusInternalServerError, err}
    71  	}
    72  	defer unlock()
    73  
    74  	validator := validation.New(m.vectorRepo.Exists, m.config, repl)
    75  	parsedTargetRefs, err := input.validate(ctx, principal, validator, m.schemaManager, tenant)
    76  	if err != nil {
    77  		if errors.As(err, &ErrMultiTenancy{}) {
    78  			return &Error{"bad inputs", StatusUnprocessableEntity, err}
    79  		}
    80  		return &Error{"bad inputs", StatusBadRequest, err}
    81  	}
    82  
    83  	for i := range input.Refs {
    84  		if parsedTargetRefs[i].Class == "" {
    85  			toClass, toBeacon, replace, err := m.autodetectToClass(ctx, principal, input.Class, input.Property, parsedTargetRefs[i])
    86  			if err != nil {
    87  				return err
    88  			}
    89  
    90  			if replace {
    91  				input.Refs[i].Class = toClass
    92  				input.Refs[i].Beacon = toBeacon
    93  				parsedTargetRefs[i].Class = string(toClass)
    94  			}
    95  		}
    96  		if err := input.validateExistence(ctx, validator, tenant, parsedTargetRefs[i]); err != nil {
    97  			return &Error{"validate existence", StatusBadRequest, err}
    98  		}
    99  
   100  	}
   101  
   102  	obj := res.Object()
   103  	if obj.Properties == nil {
   104  		obj.Properties = map[string]interface{}{input.Property: input.Refs}
   105  	} else {
   106  		obj.Properties.(map[string]interface{})[input.Property] = input.Refs
   107  	}
   108  	obj.LastUpdateTimeUnix = m.timeSource.Now()
   109  	err = m.vectorRepo.PutObject(ctx, obj, res.Vector, res.Vectors, repl)
   110  	if err != nil {
   111  		return &Error{"repo.putobject", StatusInternalServerError, err}
   112  	}
   113  	return nil
   114  }
   115  
   116  func (req *PutReferenceInput) validate(
   117  	ctx context.Context,
   118  	principal *models.Principal,
   119  	v *validation.Validator,
   120  	sm schemaManager, tenant string,
   121  ) ([]*crossref.Ref, error) {
   122  	if err := validateReferenceName(req.Class, req.Property); err != nil {
   123  		return nil, err
   124  	}
   125  	refs, err := v.ValidateMultipleRef(ctx, req.Refs, "validate references", tenant)
   126  	if err != nil {
   127  		return nil, err
   128  	}
   129  
   130  	schema, err := sm.GetSchema(principal)
   131  	if err != nil {
   132  		return nil, err
   133  	}
   134  	return refs, validateReferenceSchema(req.Class, req.Property, schema)
   135  }
   136  
   137  func (req *PutReferenceInput) validateExistence(
   138  	ctx context.Context,
   139  	v *validation.Validator, tenant string, ref *crossref.Ref,
   140  ) error {
   141  	return v.ValidateExistence(ctx, ref, "validate reference", tenant)
   142  }
   143  
   144  // validateNames validates class and property names
   145  func validateReferenceName(class, property string) error {
   146  	if _, err := schema.ValidateClassName(class); err != nil {
   147  		return err
   148  	}
   149  
   150  	if err := schema.ValidateReservedPropertyName(property); err != nil {
   151  		return err
   152  	}
   153  
   154  	if _, err := schema.ValidatePropertyName(property); err != nil {
   155  		return err
   156  	}
   157  
   158  	return nil
   159  }
   160  
   161  func validateReferenceSchema(class, property string, sch schema.Schema) error {
   162  	prop, err := sch.GetProperty(schema.ClassName(class), schema.PropertyName(property))
   163  	if err != nil {
   164  		return err
   165  	}
   166  
   167  	dt, err := sch.FindPropertyDataType(prop.DataType)
   168  	if err != nil {
   169  		return err
   170  	}
   171  
   172  	if !dt.IsReference() {
   173  		return fmt.Errorf("property '%s' is not a reference-type", property)
   174  	}
   175  
   176  	return nil
   177  }