github.com/weaviate/weaviate@v1.24.6/usecases/objects/references_add.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  // AddObjectReference to an existing object. If the class contains a network
    28  // ref, it has a side-effect on the schema: The schema will be updated to
    29  // include this particular network ref class.
    30  func (m *Manager) AddObjectReference(ctx context.Context, principal *models.Principal,
    31  	input *AddReferenceInput, repl *additional.ReplicationProperties, tenant string,
    32  ) *Error {
    33  	m.metrics.AddReferenceInc()
    34  	defer m.metrics.AddReferenceDec()
    35  
    36  	deprecatedEndpoint := input.Class == ""
    37  	if deprecatedEndpoint { // for backward compatibility only
    38  		objectRes, err := m.getObjectFromRepo(ctx, "", input.ID,
    39  			additional.Properties{}, nil, tenant)
    40  		if err != nil {
    41  			errnf := ErrNotFound{} // treated as StatusBadRequest for backward comp
    42  			if errors.As(err, &errnf) {
    43  				return &Error{"source object deprecated", StatusBadRequest, err}
    44  			} else if errors.As(err, &ErrMultiTenancy{}) {
    45  				return &Error{"source object deprecated", StatusUnprocessableEntity, err}
    46  			}
    47  			return &Error{"source object deprecated", StatusInternalServerError, err}
    48  		}
    49  		input.Class = objectRes.Object().Class
    50  	}
    51  	path := fmt.Sprintf("objects/%s/%s", input.Class, input.ID)
    52  	if err := m.authorizer.Authorize(principal, "update", path); err != nil {
    53  		return &Error{path, StatusForbidden, err}
    54  	}
    55  
    56  	unlock, err := m.locks.LockSchema()
    57  	if err != nil {
    58  		return &Error{"cannot lock", StatusInternalServerError, err}
    59  	}
    60  	defer unlock()
    61  	validator := validation.New(m.vectorRepo.Exists, m.config, repl)
    62  	targetRef, err := input.validate(principal, validator, m.schemaManager)
    63  	if err != nil {
    64  		if errors.As(err, &ErrMultiTenancy{}) {
    65  			return &Error{"validate inputs", StatusUnprocessableEntity, err}
    66  		}
    67  		return &Error{"validate inputs", StatusBadRequest, err}
    68  	}
    69  
    70  	if input.Class != "" && targetRef.Class == "" {
    71  		toClass, toBeacon, replace, err := m.autodetectToClass(ctx, principal, input.Class, input.Property, targetRef)
    72  		if err != nil {
    73  			return err
    74  		}
    75  		if replace {
    76  			input.Ref.Class = toClass
    77  			input.Ref.Beacon = toBeacon
    78  			targetRef.Class = string(toClass)
    79  		}
    80  	}
    81  
    82  	if err := input.validateExistence(ctx, validator, tenant, targetRef); err != nil {
    83  		return &Error{"validate existence", StatusBadRequest, err}
    84  	}
    85  
    86  	if !deprecatedEndpoint {
    87  		ok, err := m.vectorRepo.Exists(ctx, input.Class, input.ID, repl, tenant)
    88  		if err != nil {
    89  			switch err.(type) {
    90  			case ErrMultiTenancy:
    91  				return &Error{"source object", StatusUnprocessableEntity, err}
    92  			default:
    93  				return &Error{"source object", StatusInternalServerError, err}
    94  			}
    95  		}
    96  		if !ok {
    97  			return &Error{"source object", StatusNotFound, err}
    98  		}
    99  	}
   100  
   101  	source := crossref.NewSource(schema.ClassName(input.Class),
   102  		schema.PropertyName(input.Property), input.ID)
   103  
   104  	target, err := crossref.ParseSingleRef(&input.Ref)
   105  	if err != nil {
   106  		return &Error{"parse target ref", StatusBadRequest, err}
   107  	}
   108  
   109  	if shouldValidateMultiTenantRef(tenant, source, target) {
   110  		err = validateReferenceMultiTenancy(ctx, principal,
   111  			m.schemaManager, m.vectorRepo, source, target, tenant)
   112  		if err != nil {
   113  			return &Error{"multi-tenancy violation", StatusInternalServerError, err}
   114  		}
   115  	}
   116  
   117  	if err := m.vectorRepo.AddReference(ctx, source, target, repl, tenant); err != nil {
   118  		return &Error{"add reference to repo", StatusInternalServerError, err}
   119  	}
   120  
   121  	if err := m.updateRefVector(ctx, principal, input.Class, input.ID, tenant); err != nil {
   122  		return &Error{"update ref vector", StatusInternalServerError, err}
   123  	}
   124  
   125  	return nil
   126  }
   127  
   128  func shouldValidateMultiTenantRef(tenant string, source *crossref.RefSource, target *crossref.Ref) bool {
   129  	return tenant != "" || (source != nil && target != nil && source.Class != "" && target.Class != "")
   130  }
   131  
   132  // AddReferenceInput represents required inputs to add a reference to an existing object.
   133  type AddReferenceInput struct {
   134  	// Class name
   135  	Class string
   136  	// ID of an object
   137  	ID strfmt.UUID
   138  	// Property name
   139  	Property string
   140  	// Ref cross reference
   141  	Ref models.SingleRef
   142  }
   143  
   144  func (req *AddReferenceInput) validate(
   145  	principal *models.Principal,
   146  	v *validation.Validator,
   147  	sm schemaManager,
   148  ) (*crossref.Ref, error) {
   149  	if err := validateReferenceName(req.Class, req.Property); err != nil {
   150  		return nil, err
   151  	}
   152  	ref, err := v.ValidateSingleRef(&req.Ref)
   153  	if err != nil {
   154  		return nil, err
   155  	}
   156  
   157  	schema, err := sm.GetSchema(principal)
   158  	if err != nil {
   159  		return nil, err
   160  	}
   161  	return ref, validateReferenceSchema(req.Class, req.Property, schema)
   162  }
   163  
   164  func (req *AddReferenceInput) validateExistence(
   165  	ctx context.Context,
   166  	v *validation.Validator, tenant string, ref *crossref.Ref,
   167  ) error {
   168  	return v.ValidateExistence(ctx, ref, "validate reference", tenant)
   169  }