github.com/authzed/spicedb@v1.32.1-0.20240520085336-ebda56537386/internal/datastore/memdb/readwrite.go (about) 1 package memdb 2 3 import ( 4 "context" 5 "fmt" 6 "strings" 7 8 v1 "github.com/authzed/authzed-go/proto/authzed/api/v1" 9 "github.com/hashicorp/go-memdb" 10 "github.com/jzelinskie/stringz" 11 "google.golang.org/protobuf/proto" 12 13 "github.com/authzed/spicedb/internal/datastore/common" 14 "github.com/authzed/spicedb/pkg/datastore" 15 "github.com/authzed/spicedb/pkg/datastore/options" 16 core "github.com/authzed/spicedb/pkg/proto/core/v1" 17 "github.com/authzed/spicedb/pkg/tuple" 18 ) 19 20 type memdbReadWriteTx struct { 21 memdbReader 22 newRevision datastore.Revision 23 } 24 25 func (rwt *memdbReadWriteTx) WriteRelationships(_ context.Context, mutations []*core.RelationTupleUpdate) error { 26 rwt.mustLock() 27 defer rwt.Unlock() 28 29 tx, err := rwt.txSource() 30 if err != nil { 31 return err 32 } 33 34 return rwt.write(tx, mutations...) 35 } 36 37 // Caller must already hold the concurrent access lock! 38 func (rwt *memdbReadWriteTx) write(tx *memdb.Txn, mutations ...*core.RelationTupleUpdate) error { 39 // Apply the mutations 40 for _, mutation := range mutations { 41 rel := &relationship{ 42 mutation.Tuple.ResourceAndRelation.Namespace, 43 mutation.Tuple.ResourceAndRelation.ObjectId, 44 mutation.Tuple.ResourceAndRelation.Relation, 45 mutation.Tuple.Subject.Namespace, 46 mutation.Tuple.Subject.ObjectId, 47 mutation.Tuple.Subject.Relation, 48 rwt.toCaveatReference(mutation), 49 } 50 51 found, err := tx.First( 52 tableRelationship, 53 indexID, 54 rel.namespace, 55 rel.resourceID, 56 rel.relation, 57 rel.subjectNamespace, 58 rel.subjectObjectID, 59 rel.subjectRelation, 60 ) 61 if err != nil { 62 return fmt.Errorf("error loading existing relationship: %w", err) 63 } 64 65 var existing *relationship 66 if found != nil { 67 existing = found.(*relationship) 68 } 69 70 switch mutation.Operation { 71 case core.RelationTupleUpdate_CREATE: 72 if existing != nil { 73 rt, err := existing.RelationTuple() 74 if err != nil { 75 return err 76 } 77 return common.NewCreateRelationshipExistsError(rt) 78 } 79 if err := tx.Insert(tableRelationship, rel); err != nil { 80 return fmt.Errorf("error inserting relationship: %w", err) 81 } 82 83 case core.RelationTupleUpdate_TOUCH: 84 if existing != nil { 85 rt, err := existing.RelationTuple() 86 if err != nil { 87 return err 88 } 89 if tuple.MustString(rt) == tuple.MustString(mutation.Tuple) { 90 continue 91 } 92 } 93 94 if err := tx.Insert(tableRelationship, rel); err != nil { 95 return fmt.Errorf("error inserting relationship: %w", err) 96 } 97 case core.RelationTupleUpdate_DELETE: 98 if existing != nil { 99 if err := tx.Delete(tableRelationship, existing); err != nil { 100 return fmt.Errorf("error deleting relationship: %w", err) 101 } 102 } 103 default: 104 return fmt.Errorf("unknown tuple mutation operation type: %s", mutation.Operation) 105 } 106 } 107 108 return nil 109 } 110 111 func (rwt *memdbReadWriteTx) toCaveatReference(mutation *core.RelationTupleUpdate) *contextualizedCaveat { 112 var cr *contextualizedCaveat 113 if mutation.Tuple.Caveat != nil { 114 cr = &contextualizedCaveat{ 115 caveatName: mutation.Tuple.Caveat.CaveatName, 116 context: mutation.Tuple.Caveat.Context.AsMap(), 117 } 118 } 119 return cr 120 } 121 122 func (rwt *memdbReadWriteTx) DeleteRelationships(_ context.Context, filter *v1.RelationshipFilter, opts ...options.DeleteOptionsOption) (bool, error) { 123 rwt.mustLock() 124 defer rwt.Unlock() 125 126 tx, err := rwt.txSource() 127 if err != nil { 128 return false, err 129 } 130 131 delOpts := options.NewDeleteOptionsWithOptionsAndDefaults(opts...) 132 var delLimit uint64 133 if delOpts.DeleteLimit != nil && *delOpts.DeleteLimit > 0 { 134 delLimit = *delOpts.DeleteLimit 135 } 136 137 return rwt.deleteWithLock(tx, filter, delLimit) 138 } 139 140 // caller must already hold the concurrent access lock 141 func (rwt *memdbReadWriteTx) deleteWithLock(tx *memdb.Txn, filter *v1.RelationshipFilter, limit uint64) (bool, error) { 142 // Create an iterator to find the relevant tuples 143 dsFilter, err := datastore.RelationshipsFilterFromPublicFilter(filter) 144 if err != nil { 145 return false, err 146 } 147 148 bestIter, err := iteratorForFilter(tx, dsFilter) 149 if err != nil { 150 return false, err 151 } 152 filteredIter := memdb.NewFilterIterator(bestIter, relationshipFilterFilterFunc(filter)) 153 154 // Collect the tuples into a slice of mutations for the changelog 155 var mutations []*core.RelationTupleUpdate 156 var counter uint64 157 158 metLimit := false 159 for row := filteredIter.Next(); row != nil; row = filteredIter.Next() { 160 rt, err := row.(*relationship).RelationTuple() 161 if err != nil { 162 return false, err 163 } 164 mutations = append(mutations, tuple.Delete(rt)) 165 counter++ 166 167 if limit > 0 && counter == limit { 168 metLimit = true 169 break 170 } 171 } 172 173 return metLimit, rwt.write(tx, mutations...) 174 } 175 176 func (rwt *memdbReadWriteTx) WriteNamespaces(_ context.Context, newConfigs ...*core.NamespaceDefinition) error { 177 rwt.mustLock() 178 defer rwt.Unlock() 179 180 tx, err := rwt.txSource() 181 if err != nil { 182 return err 183 } 184 185 for _, newConfig := range newConfigs { 186 serialized, err := proto.Marshal(newConfig) 187 if err != nil { 188 return err 189 } 190 191 newConfigEntry := &namespace{newConfig.Name, serialized, rwt.newRevision} 192 193 err = tx.Insert(tableNamespace, newConfigEntry) 194 if err != nil { 195 return err 196 } 197 } 198 199 return nil 200 } 201 202 func (rwt *memdbReadWriteTx) DeleteNamespaces(_ context.Context, nsNames ...string) error { 203 rwt.mustLock() 204 defer rwt.Unlock() 205 206 tx, err := rwt.txSource() 207 if err != nil { 208 return err 209 } 210 211 for _, nsName := range nsNames { 212 foundRaw, err := tx.First(tableNamespace, indexID, nsName) 213 if err != nil { 214 return err 215 } 216 217 if foundRaw == nil { 218 return fmt.Errorf("unable to find namespace to delete") 219 } 220 221 if err := tx.Delete(tableNamespace, foundRaw); err != nil { 222 return err 223 } 224 225 // Delete the relationships from the namespace 226 if _, err := rwt.deleteWithLock(tx, &v1.RelationshipFilter{ 227 ResourceType: nsName, 228 }, 0); err != nil { 229 return fmt.Errorf("unable to delete relationships from deleted namespace: %w", err) 230 } 231 } 232 233 return nil 234 } 235 236 func (rwt *memdbReadWriteTx) BulkLoad(ctx context.Context, iter datastore.BulkWriteRelationshipSource) (uint64, error) { 237 updates := []*core.RelationTupleUpdate{{ 238 Operation: core.RelationTupleUpdate_CREATE, 239 }} 240 241 var numCopied uint64 242 var next *core.RelationTuple 243 var err error 244 for next, err = iter.Next(ctx); next != nil && err == nil; next, err = iter.Next(ctx) { 245 updates[0].Tuple = next 246 if err := rwt.WriteRelationships(ctx, updates); err != nil { 247 return 0, err 248 } 249 numCopied++ 250 } 251 252 return numCopied, err 253 } 254 255 func relationshipFilterFilterFunc(filter *v1.RelationshipFilter) func(interface{}) bool { 256 return func(tupleRaw interface{}) bool { 257 tuple := tupleRaw.(*relationship) 258 259 // If it doesn't match one of the resource filters, filter it. 260 switch { 261 case filter.ResourceType != "" && filter.ResourceType != tuple.namespace: 262 return true 263 case filter.OptionalResourceId != "" && filter.OptionalResourceId != tuple.resourceID: 264 return true 265 case filter.OptionalResourceIdPrefix != "" && !strings.HasPrefix(tuple.resourceID, filter.OptionalResourceIdPrefix): 266 return true 267 case filter.OptionalRelation != "" && filter.OptionalRelation != tuple.relation: 268 return true 269 } 270 271 // If it doesn't match one of the subject filters, filter it. 272 if subjectFilter := filter.OptionalSubjectFilter; subjectFilter != nil { 273 switch { 274 case subjectFilter.SubjectType != tuple.subjectNamespace: 275 return true 276 case subjectFilter.OptionalSubjectId != "" && subjectFilter.OptionalSubjectId != tuple.subjectObjectID: 277 return true 278 case subjectFilter.OptionalRelation != nil && 279 stringz.DefaultEmpty(subjectFilter.OptionalRelation.Relation, datastore.Ellipsis) != tuple.subjectRelation: 280 return true 281 } 282 } 283 284 return false 285 } 286 } 287 288 var _ datastore.ReadWriteTransaction = &memdbReadWriteTx{}