github.com/weaviate/weaviate@v1.24.6/adapters/repos/db/shard_geo_props.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 db 13 14 import ( 15 "context" 16 "fmt" 17 18 "github.com/pkg/errors" 19 "github.com/weaviate/weaviate/adapters/repos/db/propertyspecific" 20 "github.com/weaviate/weaviate/adapters/repos/db/vector/geo" 21 "github.com/weaviate/weaviate/entities/models" 22 "github.com/weaviate/weaviate/entities/schema" 23 "github.com/weaviate/weaviate/entities/storagestate" 24 "github.com/weaviate/weaviate/entities/storobj" 25 ) 26 27 func (s *Shard) initGeoProp(prop *models.Property) error { 28 // starts geo props cycles if actual geo property is present 29 // (safe to start multiple times) 30 s.index.cycleCallbacks.geoPropsCommitLoggerCycle.Start() 31 s.index.cycleCallbacks.geoPropsTombstoneCleanupCycle.Start() 32 33 idx, err := geo.NewIndex(geo.Config{ 34 ID: geoPropID(prop.Name), 35 RootPath: s.path(), 36 CoordinatesForID: s.makeCoordinatesForID(prop.Name), 37 DisablePersistence: false, 38 Logger: s.index.logger, 39 }, 40 s.cycleCallbacks.geoPropsCommitLoggerCallbacks, 41 s.cycleCallbacks.geoPropsTombstoneCleanupCallbacks, 42 s.cycleCallbacks.compactionCallbacks, 43 s.cycleCallbacks.flushCallbacks, 44 ) 45 if err != nil { 46 return errors.Wrapf(err, "create geo index for prop %q", prop.Name) 47 } 48 49 s.propertyIndicesLock.Lock() 50 s.propertyIndices[prop.Name] = propertyspecific.Index{ 51 Type: schema.DataTypeGeoCoordinates, 52 GeoIndex: idx, 53 Name: prop.Name, 54 } 55 s.propertyIndicesLock.Unlock() 56 57 idx.PostStartup() 58 59 return nil 60 } 61 62 func (s *Shard) makeCoordinatesForID(propName string) geo.CoordinatesForID { 63 return func(ctx context.Context, id uint64) (*models.GeoCoordinates, error) { 64 obj, err := s.objectByIndexID(ctx, id, true) 65 if err != nil { 66 return nil, storobj.NewErrNotFoundf(id, "retrieve object") 67 } 68 69 if obj.Properties() == nil { 70 return nil, storobj.NewErrNotFoundf(id, 71 "object has no properties") 72 } 73 74 prop, ok := obj.Properties().(map[string]interface{})[propName] 75 if !ok { 76 return nil, storobj.NewErrNotFoundf(id, 77 "object has no property %q", propName) 78 } 79 80 geoProp, ok := prop.(*models.GeoCoordinates) 81 if !ok { 82 return nil, fmt.Errorf("expected property to be of type %T, got: %T", 83 &models.GeoCoordinates{}, prop) 84 } 85 86 return geoProp, nil 87 } 88 } 89 90 func geoPropID(propName string) string { 91 return fmt.Sprintf("geo.%s", propName) 92 } 93 94 func (s *Shard) updatePropertySpecificIndices(object *storobj.Object, 95 status objectInsertStatus, 96 ) error { 97 if s.isReadOnly() { 98 return storagestate.ErrStatusReadOnly 99 } 100 s.propertyIndicesLock.RLock() 101 defer s.propertyIndicesLock.RUnlock() 102 103 for propName, propIndex := range s.propertyIndices { 104 if err := s.updatePropertySpecificIndex(propName, propIndex, 105 object, status); err != nil { 106 return errors.Wrapf(err, "property %q", propName) 107 } 108 } 109 110 return nil 111 } 112 113 func (s *Shard) updatePropertySpecificIndex(propName string, 114 index propertyspecific.Index, obj *storobj.Object, 115 status objectInsertStatus, 116 ) error { 117 if index.Type != schema.DataTypeGeoCoordinates { 118 return fmt.Errorf("unsupported per-property index type %q", index.Type) 119 } 120 121 // currently the only property-specific index we support 122 return s.updateGeoIndex(propName, index, obj, status) 123 } 124 125 func (s *Shard) updateGeoIndex(propName string, index propertyspecific.Index, 126 obj *storobj.Object, status objectInsertStatus, 127 ) error { 128 if s.isReadOnly() { 129 return storagestate.ErrStatusReadOnly 130 } 131 132 // geo props were not changed 133 if status.docIDPreserved || status.skipUpsert { 134 return nil 135 } 136 137 if status.docIDChanged { 138 if err := s.deleteFromGeoIndex(index, status.oldDocID); err != nil { 139 return errors.Wrap(err, "delete old doc id from geo index") 140 } 141 } 142 143 return s.addToGeoIndex(propName, index, obj, status) 144 } 145 146 func (s *Shard) addToGeoIndex(propName string, index propertyspecific.Index, 147 obj *storobj.Object, status objectInsertStatus, 148 ) error { 149 if s.isReadOnly() { 150 return storagestate.ErrStatusReadOnly 151 } 152 153 if obj.Properties() == nil { 154 return nil 155 } 156 157 asMap := obj.Properties().(map[string]interface{}) 158 propValue, ok := asMap[propName] 159 if !ok { 160 return nil 161 } 162 163 // geo coordinates is the only supported one at the moment 164 asGeo, ok := propValue.(*models.GeoCoordinates) 165 if !ok { 166 return fmt.Errorf("expected prop to be of type %T, but got: %T", 167 &models.GeoCoordinates{}, propValue) 168 } 169 170 if err := index.GeoIndex.Add(status.docID, asGeo); err != nil { 171 return errors.Wrapf(err, "insert into geo index") 172 } 173 174 return nil 175 } 176 177 func (s *Shard) deleteFromGeoIndex(index propertyspecific.Index, 178 docID uint64, 179 ) error { 180 if s.isReadOnly() { 181 return storagestate.ErrStatusReadOnly 182 } 183 184 if err := index.GeoIndex.Delete(docID); err != nil { 185 return errors.Wrapf(err, "delete from geo index") 186 } 187 188 return nil 189 }