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 }