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{}