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

     1  package spanner
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"time"
     7  
     8  	"cloud.google.com/go/spanner"
     9  	"go.opentelemetry.io/otel/attribute"
    10  	"go.opentelemetry.io/otel/trace"
    11  	"google.golang.org/grpc/codes"
    12  
    13  	"github.com/authzed/spicedb/internal/datastore/common"
    14  	"github.com/authzed/spicedb/internal/datastore/revisions"
    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  )
    19  
    20  // The underlying Spanner shared read transaction interface is not exposed, so we re-create
    21  // the subsection of it which we need here.
    22  // https://github.com/googleapis/google-cloud-go/blob/a33861fe46be42ae150d6015ad39dae6e35e04e8/spanner/transaction.go#L55
    23  type readTX interface {
    24  	ReadRow(ctx context.Context, table string, key spanner.Key, columns []string) (*spanner.Row, error)
    25  	Read(ctx context.Context, table string, keys spanner.KeySet, columns []string) *spanner.RowIterator
    26  	Query(ctx context.Context, statement spanner.Statement) *spanner.RowIterator
    27  }
    28  
    29  type txFactory func() readTX
    30  
    31  type spannerReader struct {
    32  	executor common.QueryExecutor
    33  	txSource txFactory
    34  }
    35  
    36  func (sr spannerReader) QueryRelationships(
    37  	ctx context.Context,
    38  	filter datastore.RelationshipsFilter,
    39  	opts ...options.QueryOptionsOption,
    40  ) (iter datastore.RelationshipIterator, err error) {
    41  	qBuilder, err := common.NewSchemaQueryFilterer(schema, queryTuples).FilterWithRelationshipsFilter(filter)
    42  	if err != nil {
    43  		return nil, err
    44  	}
    45  
    46  	return sr.executor.ExecuteQuery(ctx, qBuilder, opts...)
    47  }
    48  
    49  func (sr spannerReader) ReverseQueryRelationships(
    50  	ctx context.Context,
    51  	subjectsFilter datastore.SubjectsFilter,
    52  	opts ...options.ReverseQueryOptionsOption,
    53  ) (iter datastore.RelationshipIterator, err error) {
    54  	qBuilder, err := common.NewSchemaQueryFilterer(schema, queryTuples).
    55  		FilterWithSubjectsSelectors(subjectsFilter.AsSelector())
    56  	if err != nil {
    57  		return nil, err
    58  	}
    59  
    60  	queryOpts := options.NewReverseQueryOptionsWithOptions(opts...)
    61  
    62  	if queryOpts.ResRelation != nil {
    63  		qBuilder = qBuilder.
    64  			FilterToResourceType(queryOpts.ResRelation.Namespace).
    65  			FilterToRelation(queryOpts.ResRelation.Relation)
    66  	}
    67  
    68  	return sr.executor.ExecuteQuery(ctx,
    69  		qBuilder,
    70  		options.WithLimit(queryOpts.LimitForReverse),
    71  		options.WithAfter(queryOpts.AfterForReverse),
    72  		options.WithSort(queryOpts.SortForReverse),
    73  	)
    74  }
    75  
    76  func queryExecutor(txSource txFactory) common.ExecuteQueryFunc {
    77  	return func(ctx context.Context, sql string, args []any) ([]*core.RelationTuple, error) {
    78  		span := trace.SpanFromContext(ctx)
    79  		span.AddEvent("Query issued to database")
    80  		iter := txSource().Query(ctx, statementFromSQL(sql, args))
    81  		defer iter.Stop()
    82  
    83  		var tuples []*core.RelationTuple
    84  
    85  		span.AddEvent("start reading iterator")
    86  		if err := iter.Do(func(row *spanner.Row) error {
    87  			nextTuple := &core.RelationTuple{
    88  				ResourceAndRelation: &core.ObjectAndRelation{},
    89  				Subject:             &core.ObjectAndRelation{},
    90  			}
    91  			var caveatName spanner.NullString
    92  			var caveatCtx spanner.NullJSON
    93  			err := row.Columns(
    94  				&nextTuple.ResourceAndRelation.Namespace,
    95  				&nextTuple.ResourceAndRelation.ObjectId,
    96  				&nextTuple.ResourceAndRelation.Relation,
    97  				&nextTuple.Subject.Namespace,
    98  				&nextTuple.Subject.ObjectId,
    99  				&nextTuple.Subject.Relation,
   100  				&caveatName,
   101  				&caveatCtx,
   102  			)
   103  			if err != nil {
   104  				return err
   105  			}
   106  
   107  			nextTuple.Caveat, err = ContextualizedCaveatFrom(caveatName, caveatCtx)
   108  			if err != nil {
   109  				return err
   110  			}
   111  
   112  			tuples = append(tuples, nextTuple)
   113  
   114  			return nil
   115  		}); err != nil {
   116  			return nil, err
   117  		}
   118  
   119  		span.AddEvent("finished reading iterator", trace.WithAttributes(attribute.Int("tupleCount", len(tuples))))
   120  		span.SetAttributes(attribute.Int("count", len(tuples)))
   121  		return tuples, nil
   122  	}
   123  }
   124  
   125  func (sr spannerReader) ReadNamespaceByName(ctx context.Context, nsName string) (*core.NamespaceDefinition, datastore.Revision, error) {
   126  	nsKey := spanner.Key{nsName}
   127  	row, err := sr.txSource().ReadRow(
   128  		ctx,
   129  		tableNamespace,
   130  		nsKey,
   131  		[]string{colNamespaceConfig, colNamespaceTS},
   132  	)
   133  	if err != nil {
   134  		if spanner.ErrCode(err) == codes.NotFound {
   135  			return nil, datastore.NoRevision, datastore.NewNamespaceNotFoundErr(nsName)
   136  		}
   137  		return nil, datastore.NoRevision, fmt.Errorf(errUnableToReadConfig, err)
   138  	}
   139  
   140  	var serialized []byte
   141  	var updated time.Time
   142  	if err := row.Columns(&serialized, &updated); err != nil {
   143  		return nil, datastore.NoRevision, fmt.Errorf(errUnableToReadConfig, err)
   144  	}
   145  
   146  	ns := &core.NamespaceDefinition{}
   147  	if err := ns.UnmarshalVT(serialized); err != nil {
   148  		return nil, datastore.NoRevision, fmt.Errorf(errUnableToReadConfig, err)
   149  	}
   150  
   151  	return ns, revisions.NewForTime(updated), nil
   152  }
   153  
   154  func (sr spannerReader) ListAllNamespaces(ctx context.Context) ([]datastore.RevisionedNamespace, error) {
   155  	iter := sr.txSource().Read(
   156  		ctx,
   157  		tableNamespace,
   158  		spanner.AllKeys(),
   159  		[]string{colNamespaceConfig, colNamespaceTS},
   160  	)
   161  	defer iter.Stop()
   162  
   163  	allNamespaces, err := readAllNamespaces(iter, trace.SpanFromContext(ctx))
   164  	if err != nil {
   165  		return nil, fmt.Errorf(errUnableToListNamespaces, err)
   166  	}
   167  
   168  	return allNamespaces, nil
   169  }
   170  
   171  func (sr spannerReader) LookupNamespacesWithNames(ctx context.Context, nsNames []string) ([]datastore.RevisionedNamespace, error) {
   172  	if len(nsNames) == 0 {
   173  		return nil, nil
   174  	}
   175  
   176  	keys := make([]spanner.Key, 0, len(nsNames))
   177  	for _, nsName := range nsNames {
   178  		keys = append(keys, spanner.Key{nsName})
   179  	}
   180  
   181  	iter := sr.txSource().Read(
   182  		ctx,
   183  		tableNamespace,
   184  		spanner.KeySetFromKeys(keys...),
   185  		[]string{colNamespaceConfig, colNamespaceTS},
   186  	)
   187  	defer iter.Stop()
   188  
   189  	foundNamespaces, err := readAllNamespaces(iter, trace.SpanFromContext(ctx))
   190  	if err != nil {
   191  		return nil, fmt.Errorf(errUnableToListNamespaces, err)
   192  	}
   193  
   194  	return foundNamespaces, nil
   195  }
   196  
   197  func readAllNamespaces(iter *spanner.RowIterator, span trace.Span) ([]datastore.RevisionedNamespace, error) {
   198  	var allNamespaces []datastore.RevisionedNamespace
   199  	span.AddEvent("start reading iterator")
   200  	if err := iter.Do(func(row *spanner.Row) error {
   201  		var serialized []byte
   202  		var updated time.Time
   203  		if err := row.Columns(&serialized, &updated); err != nil {
   204  			return err
   205  		}
   206  
   207  		ns := &core.NamespaceDefinition{}
   208  		if err := ns.UnmarshalVT(serialized); err != nil {
   209  			return err
   210  		}
   211  
   212  		allNamespaces = append(allNamespaces, datastore.RevisionedNamespace{
   213  			Definition:          ns,
   214  			LastWrittenRevision: revisions.NewForTime(updated),
   215  		})
   216  
   217  		return nil
   218  	}); err != nil {
   219  		return nil, err
   220  	}
   221  	span.AddEvent("finished reading iterator", trace.WithAttributes(attribute.Int("namespaceCount", len(allNamespaces))))
   222  	span.SetAttributes(attribute.Int("count", len(allNamespaces)))
   223  	return allNamespaces, nil
   224  }
   225  
   226  var queryTuples = sql.Select(
   227  	colNamespace,
   228  	colObjectID,
   229  	colRelation,
   230  	colUsersetNamespace,
   231  	colUsersetObjectID,
   232  	colUsersetRelation,
   233  	colCaveatName,
   234  	colCaveatContext,
   235  ).From(tableRelationship)
   236  
   237  var queryTuplesForDelete = sql.Select(
   238  	colNamespace,
   239  	colObjectID,
   240  	colRelation,
   241  	colUsersetNamespace,
   242  	colUsersetObjectID,
   243  	colUsersetRelation,
   244  ).From(tableRelationship)
   245  
   246  var schema = common.NewSchemaInformation(
   247  	colNamespace,
   248  	colObjectID,
   249  	colRelation,
   250  	colUsersetNamespace,
   251  	colUsersetObjectID,
   252  	colUsersetRelation,
   253  	colCaveatName,
   254  	common.ExpandedLogicComparison,
   255  )
   256  
   257  var _ datastore.Reader = spannerReader{}