github.com/authzed/spicedb@v1.32.1-0.20240520085336-ebda56537386/internal/datastore/spanner/readwrite.go (about)

     1  package spanner
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"strings"
     7  
     8  	"cloud.google.com/go/spanner"
     9  	sq "github.com/Masterminds/squirrel"
    10  	v1 "github.com/authzed/authzed-go/proto/authzed/api/v1"
    11  	"github.com/jzelinskie/stringz"
    12  	"google.golang.org/protobuf/proto"
    13  
    14  	log "github.com/authzed/spicedb/internal/logging"
    15  	"github.com/authzed/spicedb/pkg/datastore"
    16  	"github.com/authzed/spicedb/pkg/datastore/options"
    17  	core "github.com/authzed/spicedb/pkg/proto/core/v1"
    18  	"github.com/authzed/spicedb/pkg/spiceerrors"
    19  )
    20  
    21  type spannerReadWriteTXN struct {
    22  	spannerReader
    23  	spannerRWT *spanner.ReadWriteTransaction
    24  }
    25  
    26  const inLimit = 10_000 // https://cloud.google.com/spanner/quotas#query-limits
    27  
    28  func (rwt spannerReadWriteTXN) WriteRelationships(ctx context.Context, mutations []*core.RelationTupleUpdate) error {
    29  	var rowCountChange int64
    30  	for _, mutation := range mutations {
    31  		txnMut, countChange, err := spannerMutation(ctx, mutation.Operation, mutation.Tuple)
    32  		if err != nil {
    33  			return fmt.Errorf(errUnableToWriteRelationships, err)
    34  		}
    35  		rowCountChange += countChange
    36  
    37  		if err := rwt.spannerRWT.BufferWrite([]*spanner.Mutation{txnMut}); err != nil {
    38  			return fmt.Errorf(errUnableToWriteRelationships, err)
    39  		}
    40  	}
    41  
    42  	return nil
    43  }
    44  
    45  func spannerMutation(
    46  	ctx context.Context,
    47  	operation core.RelationTupleUpdate_Operation,
    48  	tpl *core.RelationTuple,
    49  ) (txnMut *spanner.Mutation, countChange int64, err error) {
    50  	switch operation {
    51  	case core.RelationTupleUpdate_TOUCH:
    52  		countChange = 1
    53  		txnMut = spanner.InsertOrUpdate(tableRelationship, allRelationshipCols, upsertVals(tpl))
    54  	case core.RelationTupleUpdate_CREATE:
    55  		countChange = 1
    56  		txnMut = spanner.Insert(tableRelationship, allRelationshipCols, upsertVals(tpl))
    57  	case core.RelationTupleUpdate_DELETE:
    58  		countChange = -1
    59  		txnMut = spanner.Delete(tableRelationship, keyFromRelationship(tpl))
    60  	default:
    61  		log.Ctx(ctx).Error().Stringer("operation", operation).Msg("unknown operation type")
    62  		err = fmt.Errorf("unknown mutation operation: %s", operation)
    63  		return
    64  	}
    65  
    66  	return
    67  }
    68  
    69  func (rwt spannerReadWriteTXN) DeleteRelationships(ctx context.Context, filter *v1.RelationshipFilter, opts ...options.DeleteOptionsOption) (bool, error) {
    70  	limitReached, err := deleteWithFilter(ctx, rwt.spannerRWT, filter, opts...)
    71  	if err != nil {
    72  		return false, fmt.Errorf(errUnableToDeleteRelationships, err)
    73  	}
    74  
    75  	return limitReached, nil
    76  }
    77  
    78  func deleteWithFilter(ctx context.Context, rwt *spanner.ReadWriteTransaction, filter *v1.RelationshipFilter, opts ...options.DeleteOptionsOption) (bool, error) {
    79  	delOpts := options.NewDeleteOptionsWithOptionsAndDefaults(opts...)
    80  	var delLimit uint64
    81  	if delOpts.DeleteLimit != nil && *delOpts.DeleteLimit > 0 {
    82  		delLimit = *delOpts.DeleteLimit
    83  		if delLimit > inLimit {
    84  			return false, spiceerrors.MustBugf("delete limit %d exceeds maximum of %d in spanner", delLimit, inLimit)
    85  		}
    86  	}
    87  
    88  	var numDeleted int64
    89  	if delLimit > 0 {
    90  		nu, err := deleteWithFilterAndLimit(ctx, rwt, filter, delLimit)
    91  		if err != nil {
    92  			return false, err
    93  		}
    94  		numDeleted = nu
    95  	} else {
    96  		nu, err := deleteWithFilterAndNoLimit(ctx, rwt, filter)
    97  		if err != nil {
    98  			return false, err
    99  		}
   100  
   101  		numDeleted = nu
   102  	}
   103  
   104  	if delLimit > 0 && uint64(numDeleted) == delLimit {
   105  		return true, nil
   106  	}
   107  
   108  	return false, nil
   109  }
   110  
   111  func deleteWithFilterAndLimit(ctx context.Context, rwt *spanner.ReadWriteTransaction, filter *v1.RelationshipFilter, delLimit uint64) (int64, error) {
   112  	query := queryTuplesForDelete
   113  	filteredQuery, err := applyFilterToQuery(query, filter)
   114  	if err != nil {
   115  		return -1, err
   116  	}
   117  	query = filteredQuery
   118  	query = query.Limit(delLimit)
   119  
   120  	sql, args, err := query.ToSql()
   121  	if err != nil {
   122  		return -1, err
   123  	}
   124  
   125  	mutations := make([]*spanner.Mutation, 0, delLimit)
   126  
   127  	// Load the relationships to be deleted.
   128  	iter := rwt.Query(ctx, statementFromSQL(sql, args))
   129  	defer iter.Stop()
   130  
   131  	if err := iter.Do(func(row *spanner.Row) error {
   132  		nextTuple := &core.RelationTuple{
   133  			ResourceAndRelation: &core.ObjectAndRelation{},
   134  			Subject:             &core.ObjectAndRelation{},
   135  		}
   136  		err := row.Columns(
   137  			&nextTuple.ResourceAndRelation.Namespace,
   138  			&nextTuple.ResourceAndRelation.ObjectId,
   139  			&nextTuple.ResourceAndRelation.Relation,
   140  			&nextTuple.Subject.Namespace,
   141  			&nextTuple.Subject.ObjectId,
   142  			&nextTuple.Subject.Relation,
   143  		)
   144  		if err != nil {
   145  			return err
   146  		}
   147  
   148  		mutations = append(mutations, spanner.Delete(tableRelationship, keyFromRelationship(nextTuple)))
   149  		return nil
   150  	}); err != nil {
   151  		return -1, err
   152  	}
   153  
   154  	// Delete the relationships.
   155  	if err := rwt.BufferWrite(mutations); err != nil {
   156  		return -1, fmt.Errorf(errUnableToWriteRelationships, err)
   157  	}
   158  
   159  	return int64(len(mutations)), nil
   160  }
   161  
   162  func deleteWithFilterAndNoLimit(ctx context.Context, rwt *spanner.ReadWriteTransaction, filter *v1.RelationshipFilter) (int64, error) {
   163  	query := sql.Delete(tableRelationship)
   164  	filteredQuery, err := applyFilterToQuery(query, filter)
   165  	if err != nil {
   166  		return -1, err
   167  	}
   168  	query = filteredQuery
   169  
   170  	sql, args, err := query.ToSql()
   171  	if err != nil {
   172  		return -1, err
   173  	}
   174  
   175  	deleteStatement := statementFromSQL(sql, args)
   176  	return rwt.Update(ctx, deleteStatement)
   177  }
   178  
   179  type builder[T any] interface {
   180  	Where(pred interface{}, args ...interface{}) T
   181  }
   182  
   183  func applyFilterToQuery[T builder[T]](query T, filter *v1.RelationshipFilter) (T, error) {
   184  	// Add clauses for the ResourceFilter
   185  	if filter.ResourceType != "" {
   186  		query = query.Where(sq.Eq{colNamespace: filter.ResourceType})
   187  	}
   188  	if filter.OptionalResourceId != "" {
   189  		query = query.Where(sq.Eq{colObjectID: filter.OptionalResourceId})
   190  	}
   191  	if filter.OptionalRelation != "" {
   192  		query = query.Where(sq.Eq{colRelation: filter.OptionalRelation})
   193  	}
   194  	if filter.OptionalResourceIdPrefix != "" {
   195  		if strings.Contains(filter.OptionalResourceIdPrefix, "%") {
   196  			return query, fmt.Errorf("unable to delete relationships with a prefix containing the %% character")
   197  		}
   198  
   199  		query = query.Where(sq.Like{colObjectID: filter.OptionalResourceIdPrefix + "%"})
   200  	}
   201  
   202  	// Add clauses for the SubjectFilter
   203  	if subjectFilter := filter.OptionalSubjectFilter; subjectFilter != nil {
   204  		query = query.Where(sq.Eq{colUsersetNamespace: subjectFilter.SubjectType})
   205  		if subjectFilter.OptionalSubjectId != "" {
   206  			query = query.Where(sq.Eq{colUsersetObjectID: subjectFilter.OptionalSubjectId})
   207  		}
   208  		if relationFilter := subjectFilter.OptionalRelation; relationFilter != nil {
   209  			query = query.Where(sq.Eq{colUsersetRelation: stringz.DefaultEmpty(relationFilter.Relation, datastore.Ellipsis)})
   210  		}
   211  	}
   212  
   213  	return query, nil
   214  }
   215  
   216  func upsertVals(r *core.RelationTuple) []any {
   217  	key := keyFromRelationship(r)
   218  	key = append(key, spanner.CommitTimestamp)
   219  	key = append(key, caveatVals(r)...)
   220  	return key
   221  }
   222  
   223  func keyFromRelationship(r *core.RelationTuple) spanner.Key {
   224  	return spanner.Key{
   225  		r.ResourceAndRelation.Namespace,
   226  		r.ResourceAndRelation.ObjectId,
   227  		r.ResourceAndRelation.Relation,
   228  		r.Subject.Namespace,
   229  		r.Subject.ObjectId,
   230  		r.Subject.Relation,
   231  	}
   232  }
   233  
   234  func caveatVals(r *core.RelationTuple) []any {
   235  	if r.Caveat == nil {
   236  		return []any{"", nil}
   237  	}
   238  	vals := []any{r.Caveat.CaveatName}
   239  	if r.Caveat.Context != nil {
   240  		vals = append(vals, spanner.NullJSON{Value: r.Caveat.Context, Valid: true})
   241  	} else {
   242  		vals = append(vals, nil)
   243  	}
   244  	return vals
   245  }
   246  
   247  func (rwt spannerReadWriteTXN) WriteNamespaces(_ context.Context, newConfigs ...*core.NamespaceDefinition) error {
   248  	mutations := make([]*spanner.Mutation, 0, len(newConfigs))
   249  	for _, newConfig := range newConfigs {
   250  		serialized, err := proto.Marshal(newConfig)
   251  		if err != nil {
   252  			return fmt.Errorf(errUnableToWriteConfig, err)
   253  		}
   254  
   255  		mutations = append(mutations, spanner.InsertOrUpdate(
   256  			tableNamespace,
   257  			[]string{colNamespaceName, colNamespaceConfig, colTimestamp},
   258  			[]any{newConfig.Name, serialized, spanner.CommitTimestamp},
   259  		))
   260  	}
   261  
   262  	return rwt.spannerRWT.BufferWrite(mutations)
   263  }
   264  
   265  func (rwt spannerReadWriteTXN) DeleteNamespaces(ctx context.Context, nsNames ...string) error {
   266  	for _, nsName := range nsNames {
   267  		relFilter := &v1.RelationshipFilter{ResourceType: nsName}
   268  		if _, err := deleteWithFilter(ctx, rwt.spannerRWT, relFilter); err != nil {
   269  			return fmt.Errorf(errUnableToDeleteConfig, err)
   270  		}
   271  
   272  		err := rwt.spannerRWT.BufferWrite([]*spanner.Mutation{
   273  			spanner.Delete(tableNamespace, spanner.KeySetFromKeys(spanner.Key{nsName})),
   274  		})
   275  		if err != nil {
   276  			return fmt.Errorf(errUnableToDeleteConfig, err)
   277  		}
   278  	}
   279  
   280  	return nil
   281  }
   282  
   283  func (rwt spannerReadWriteTXN) BulkLoad(ctx context.Context, iter datastore.BulkWriteRelationshipSource) (uint64, error) {
   284  	var numLoaded uint64
   285  	var tpl *core.RelationTuple
   286  	var err error
   287  	for tpl, err = iter.Next(ctx); err == nil && tpl != nil; tpl, err = iter.Next(ctx) {
   288  		txnMut, _, err := spannerMutation(ctx, core.RelationTupleUpdate_CREATE, tpl)
   289  		if err != nil {
   290  			return 0, fmt.Errorf(errUnableToBulkLoadRelationships, err)
   291  		}
   292  		numLoaded++
   293  
   294  		if err := rwt.spannerRWT.BufferWrite([]*spanner.Mutation{txnMut}); err != nil {
   295  			return 0, fmt.Errorf(errUnableToBulkLoadRelationships, err)
   296  		}
   297  	}
   298  
   299  	if err != nil {
   300  		return 0, fmt.Errorf(errUnableToBulkLoadRelationships, err)
   301  	}
   302  
   303  	return numLoaded, nil
   304  }
   305  
   306  var _ datastore.ReadWriteTransaction = spannerReadWriteTXN{}