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 }