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

     1  package datastore
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"slices"
     7  	"sort"
     8  	"strings"
     9  	"time"
    10  
    11  	"github.com/rs/zerolog"
    12  
    13  	"github.com/authzed/spicedb/pkg/tuple"
    14  
    15  	v1 "github.com/authzed/authzed-go/proto/authzed/api/v1"
    16  
    17  	"github.com/authzed/spicedb/pkg/datastore/options"
    18  	core "github.com/authzed/spicedb/pkg/proto/core/v1"
    19  )
    20  
    21  var Engines []string
    22  
    23  // SortedEngineIDs returns the full set of engine IDs, sorted.
    24  func SortedEngineIDs() []string {
    25  	engines := append([]string{}, Engines...)
    26  	sort.Strings(engines)
    27  	return engines
    28  }
    29  
    30  // EngineOptions returns the full set of engine IDs, sorted and quoted into a string.
    31  func EngineOptions() string {
    32  	ids := SortedEngineIDs()
    33  	quoted := make([]string, 0, len(ids))
    34  	for _, id := range ids {
    35  		quoted = append(quoted, `"`+id+`"`)
    36  	}
    37  	return strings.Join(quoted, ", ")
    38  }
    39  
    40  // Ellipsis is a special relation that is assumed to be valid on the right
    41  // hand side of a tuple.
    42  const Ellipsis = "..."
    43  
    44  // FilterMaximumIDCount is the maximum number of resource IDs or subject IDs that can be sent into
    45  // a filter.
    46  const FilterMaximumIDCount uint16 = 100
    47  
    48  // RevisionChanges represents the changes in a single transaction.
    49  type RevisionChanges struct {
    50  	Revision Revision
    51  
    52  	// RelationshipChanges are any relationships that were changed at this revision.
    53  	RelationshipChanges []*core.RelationTupleUpdate
    54  
    55  	// ChangedDefinitions are any definitions that were added or changed at this revision.
    56  	ChangedDefinitions []SchemaDefinition
    57  
    58  	// DeletedNamespaces are any namespaces that were deleted.
    59  	DeletedNamespaces []string
    60  
    61  	// DeletedCaveats are any caveats that were deleted.
    62  	DeletedCaveats []string
    63  
    64  	// IsCheckpoint, if true, indicates that the datastore has reported all changes
    65  	// up until and including the Revision and that no additional schema updates can
    66  	// have occurred before this point.
    67  	IsCheckpoint bool
    68  }
    69  
    70  func (rc *RevisionChanges) MarshalZerologObject(e *zerolog.Event) {
    71  	e.Str("revision", rc.Revision.String())
    72  	e.Bool("is-checkpoint", rc.IsCheckpoint)
    73  	e.Array("deleted-namespaces", strArray(rc.DeletedNamespaces))
    74  	e.Array("deleted-caveats", strArray(rc.DeletedCaveats))
    75  
    76  	changedNames := make([]string, 0, len(rc.ChangedDefinitions))
    77  	for _, cd := range rc.ChangedDefinitions {
    78  		changedNames = append(changedNames, fmt.Sprintf("%T:%s", cd, cd.GetName()))
    79  	}
    80  
    81  	e.Array("changed-definitions", strArray(changedNames))
    82  	e.Int("num-changed-relationships", len(rc.RelationshipChanges))
    83  }
    84  
    85  // RelationshipsFilter is a filter for relationships.
    86  type RelationshipsFilter struct {
    87  	// OptionalResourceType is the namespace/type for the resources to be found.
    88  	OptionalResourceType string
    89  
    90  	// OptionalResourceIds are the IDs of the resources to find. If nil empty, any resource ID will be allowed.
    91  	// Cannot be used with OptionalResourceIDPrefix.
    92  	OptionalResourceIds []string
    93  
    94  	// OptionalResourceIDPrefix is the prefix to use for resource IDs. If empty, any prefix is allowed.
    95  	// Cannot be used with OptionalResourceIds.
    96  	OptionalResourceIDPrefix string
    97  
    98  	// OptionalResourceRelation is the relation of the resource to find. If empty, any relation is allowed.
    99  	OptionalResourceRelation string
   100  
   101  	// OptionalSubjectsSelectors is the selectors to use for subjects of the relationship. If nil, all subjects are allowed.
   102  	// If specified, relationships matching *any* selector will be returned.
   103  	OptionalSubjectsSelectors []SubjectsSelector
   104  
   105  	// OptionalCaveatName is the filter to use for caveated relationships, filtering by a specific caveat name.
   106  	// If nil, all caveated and non-caveated relationships are allowed
   107  	OptionalCaveatName string
   108  }
   109  
   110  // Test returns true iff the given relationship is matched by this filter.
   111  func (rf RelationshipsFilter) Test(relationship *core.RelationTuple) bool {
   112  	if rf.OptionalResourceType != "" && rf.OptionalResourceType != relationship.ResourceAndRelation.Namespace {
   113  		return false
   114  	}
   115  
   116  	if len(rf.OptionalResourceIds) > 0 && !slices.Contains(rf.OptionalResourceIds, relationship.ResourceAndRelation.ObjectId) {
   117  		return false
   118  	}
   119  
   120  	if rf.OptionalResourceIDPrefix != "" && !strings.HasPrefix(relationship.ResourceAndRelation.ObjectId, rf.OptionalResourceIDPrefix) {
   121  		return false
   122  	}
   123  
   124  	if rf.OptionalResourceRelation != "" && rf.OptionalResourceRelation != relationship.ResourceAndRelation.Relation {
   125  		return false
   126  	}
   127  
   128  	if len(rf.OptionalSubjectsSelectors) > 0 {
   129  		for _, selector := range rf.OptionalSubjectsSelectors {
   130  			if selector.Test(relationship.Subject) {
   131  				return true
   132  			}
   133  		}
   134  		return false
   135  	}
   136  
   137  	if rf.OptionalCaveatName != "" {
   138  		if relationship.Caveat == nil || relationship.Caveat.CaveatName != rf.OptionalCaveatName {
   139  			return false
   140  		}
   141  	}
   142  
   143  	return true
   144  }
   145  
   146  // RelationshipsFilterFromPublicFilter constructs a datastore RelationshipsFilter from an API-defined RelationshipFilter.
   147  func RelationshipsFilterFromPublicFilter(filter *v1.RelationshipFilter) (RelationshipsFilter, error) {
   148  	var resourceIds []string
   149  	if filter.OptionalResourceId != "" {
   150  		resourceIds = []string{filter.OptionalResourceId}
   151  	}
   152  
   153  	var subjectsSelectors []SubjectsSelector
   154  	if filter.OptionalSubjectFilter != nil {
   155  		var subjectIds []string
   156  		if filter.OptionalSubjectFilter.OptionalSubjectId != "" {
   157  			subjectIds = []string{filter.OptionalSubjectFilter.OptionalSubjectId}
   158  		}
   159  
   160  		relationFilter := SubjectRelationFilter{}
   161  
   162  		if filter.OptionalSubjectFilter.OptionalRelation != nil {
   163  			relation := filter.OptionalSubjectFilter.OptionalRelation.GetRelation()
   164  			if relation != "" {
   165  				relationFilter = relationFilter.WithNonEllipsisRelation(relation)
   166  			} else {
   167  				relationFilter = relationFilter.WithEllipsisRelation()
   168  			}
   169  		}
   170  
   171  		subjectsSelectors = append(subjectsSelectors, SubjectsSelector{
   172  			OptionalSubjectType: filter.OptionalSubjectFilter.SubjectType,
   173  			OptionalSubjectIds:  subjectIds,
   174  			RelationFilter:      relationFilter,
   175  		})
   176  	}
   177  
   178  	if filter.OptionalResourceId != "" && filter.OptionalResourceIdPrefix != "" {
   179  		return RelationshipsFilter{}, fmt.Errorf("cannot specify both OptionalResourceId and OptionalResourceIDPrefix")
   180  	}
   181  
   182  	if filter.ResourceType == "" && filter.OptionalRelation == "" && len(resourceIds) == 0 && filter.OptionalResourceIdPrefix == "" && len(subjectsSelectors) == 0 {
   183  		return RelationshipsFilter{}, fmt.Errorf("at least one filter field must be set")
   184  	}
   185  
   186  	return RelationshipsFilter{
   187  		OptionalResourceType:      filter.ResourceType,
   188  		OptionalResourceIds:       resourceIds,
   189  		OptionalResourceIDPrefix:  filter.OptionalResourceIdPrefix,
   190  		OptionalResourceRelation:  filter.OptionalRelation,
   191  		OptionalSubjectsSelectors: subjectsSelectors,
   192  	}, nil
   193  }
   194  
   195  // SubjectsSelector is a selector for subjects.
   196  type SubjectsSelector struct {
   197  	// OptionalSubjectType is the namespace/type for the subjects to be found, if any.
   198  	OptionalSubjectType string
   199  
   200  	// OptionalSubjectIds are the IDs of the subjects to find. If nil or empty, any subject ID will be allowed.
   201  	OptionalSubjectIds []string
   202  
   203  	// RelationFilter is the filter to use for the relation(s) of the subjects. If neither field
   204  	// is set, any relation is allowed.
   205  	RelationFilter SubjectRelationFilter
   206  }
   207  
   208  // Test returns true iff the given subject is matched by this filter.
   209  func (ss SubjectsSelector) Test(subject *core.ObjectAndRelation) bool {
   210  	if ss.OptionalSubjectType != "" && ss.OptionalSubjectType != subject.Namespace {
   211  		return false
   212  	}
   213  
   214  	if len(ss.OptionalSubjectIds) > 0 && !slices.Contains(ss.OptionalSubjectIds, subject.ObjectId) {
   215  		return false
   216  	}
   217  
   218  	if !ss.RelationFilter.IsEmpty() {
   219  		if ss.RelationFilter.IncludeEllipsisRelation && subject.Relation == tuple.Ellipsis {
   220  			return true
   221  		}
   222  
   223  		if ss.RelationFilter.NonEllipsisRelation != "" && ss.RelationFilter.NonEllipsisRelation != subject.Relation {
   224  			return false
   225  		}
   226  
   227  		if ss.RelationFilter.OnlyNonEllipsisRelations && subject.Relation == tuple.Ellipsis {
   228  			return false
   229  		}
   230  	}
   231  
   232  	return true
   233  }
   234  
   235  // SubjectRelationFilter is the filter to use for relation(s) of subjects being queried.
   236  type SubjectRelationFilter struct {
   237  	// NonEllipsisRelation is the relation of the subject type to find. If empty,
   238  	// IncludeEllipsisRelation must be true.
   239  	NonEllipsisRelation string
   240  
   241  	// IncludeEllipsisRelation, if true, indicates that the ellipsis relation
   242  	// should be included as an option.
   243  	IncludeEllipsisRelation bool
   244  
   245  	// OnlyNonEllipsisRelations, if true, indicates that only non-ellipsis relations
   246  	// should be included.
   247  	OnlyNonEllipsisRelations bool
   248  }
   249  
   250  // WithOnlyNonEllipsisRelations indicates that only non-ellipsis relations should be included.
   251  func (sf SubjectRelationFilter) WithOnlyNonEllipsisRelations() SubjectRelationFilter {
   252  	sf.OnlyNonEllipsisRelations = true
   253  	sf.NonEllipsisRelation = ""
   254  	sf.IncludeEllipsisRelation = false
   255  	return sf
   256  }
   257  
   258  // WithEllipsisRelation indicates that the subject filter should include the ellipsis relation
   259  // as an option for the subjects' relation.
   260  func (sf SubjectRelationFilter) WithEllipsisRelation() SubjectRelationFilter {
   261  	sf.IncludeEllipsisRelation = true
   262  	sf.OnlyNonEllipsisRelations = false
   263  	return sf
   264  }
   265  
   266  // WithNonEllipsisRelation indicates that the specified non-ellipsis relation should be included as an
   267  // option for the subjects' relation.
   268  func (sf SubjectRelationFilter) WithNonEllipsisRelation(relation string) SubjectRelationFilter {
   269  	sf.NonEllipsisRelation = relation
   270  	sf.OnlyNonEllipsisRelations = false
   271  	return sf
   272  }
   273  
   274  // WithRelation indicates that the specified relation should be included as an
   275  // option for the subjects' relation.
   276  func (sf SubjectRelationFilter) WithRelation(relation string) SubjectRelationFilter {
   277  	if relation == tuple.Ellipsis {
   278  		return sf.WithEllipsisRelation()
   279  	}
   280  	return sf.WithNonEllipsisRelation(relation)
   281  }
   282  
   283  // IsEmpty returns true if the subject relation filter is empty.
   284  func (sf SubjectRelationFilter) IsEmpty() bool {
   285  	return !sf.IncludeEllipsisRelation && sf.NonEllipsisRelation == "" && !sf.OnlyNonEllipsisRelations
   286  }
   287  
   288  // SubjectsFilter is a filter for subjects.
   289  type SubjectsFilter struct {
   290  	// SubjectType is the namespace/type for the subjects to be found.
   291  	SubjectType string
   292  
   293  	// OptionalSubjectIds are the IDs of the subjects to find. If nil or empty, any subject ID will be allowed.
   294  	OptionalSubjectIds []string
   295  
   296  	// RelationFilter is the filter to use for the relation(s) of the subjects. If neither field
   297  	// is set, any relation is allowed.
   298  	RelationFilter SubjectRelationFilter
   299  }
   300  
   301  func (sf SubjectsFilter) AsSelector() SubjectsSelector {
   302  	return SubjectsSelector{
   303  		OptionalSubjectType: sf.SubjectType,
   304  		OptionalSubjectIds:  sf.OptionalSubjectIds,
   305  		RelationFilter:      sf.RelationFilter,
   306  	}
   307  }
   308  
   309  // SchemaDefinition represents a namespace or caveat definition under a schema.
   310  type SchemaDefinition interface {
   311  	GetName() string
   312  }
   313  
   314  // RevisionedDefinition holds a schema definition and its last updated revision.
   315  type RevisionedDefinition[T SchemaDefinition] struct {
   316  	// Definition is the namespace or caveat definition.
   317  	Definition T
   318  
   319  	// LastWrittenRevision is the revision at which the namespace or caveat was last updated.
   320  	LastWrittenRevision Revision
   321  }
   322  
   323  func (rd RevisionedDefinition[T]) GetLastWrittenRevision() Revision {
   324  	return rd.LastWrittenRevision
   325  }
   326  
   327  // RevisionedNamespace is a revisioned version of a namespace definition.
   328  type RevisionedNamespace = RevisionedDefinition[*core.NamespaceDefinition]
   329  
   330  // Reader is an interface for reading relationships from the datastore.
   331  type Reader interface {
   332  	CaveatReader
   333  
   334  	// QueryRelationships reads relationships, starting from the resource side.
   335  	QueryRelationships(
   336  		ctx context.Context,
   337  		filter RelationshipsFilter,
   338  		options ...options.QueryOptionsOption,
   339  	) (RelationshipIterator, error)
   340  
   341  	// ReverseQueryRelationships reads relationships, starting from the subject.
   342  	ReverseQueryRelationships(
   343  		ctx context.Context,
   344  		subjectsFilter SubjectsFilter,
   345  		options ...options.ReverseQueryOptionsOption,
   346  	) (RelationshipIterator, error)
   347  
   348  	// ReadNamespaceByName reads a namespace definition and the revision at which it was created or
   349  	// last written. It returns an instance of ErrNamespaceNotFound if not found.
   350  	ReadNamespaceByName(ctx context.Context, nsName string) (ns *core.NamespaceDefinition, lastWritten Revision, err error)
   351  
   352  	// ListAllNamespaces lists all namespaces defined.
   353  	ListAllNamespaces(ctx context.Context) ([]RevisionedNamespace, error)
   354  
   355  	// LookupNamespacesWithNames finds all namespaces with the matching names.
   356  	LookupNamespacesWithNames(ctx context.Context, nsNames []string) ([]RevisionedNamespace, error)
   357  }
   358  
   359  type ReadWriteTransaction interface {
   360  	Reader
   361  	CaveatStorer
   362  
   363  	// WriteRelationships takes a list of tuple mutations and applies them to the datastore.
   364  	WriteRelationships(ctx context.Context, mutations []*core.RelationTupleUpdate) error
   365  
   366  	// DeleteRelationships deletes relationships that match the provided filter, with
   367  	// the optional limit. If a limit is provided and reached, the method will return
   368  	// true as the first return value. Otherwise, the boolean can be ignored.
   369  	DeleteRelationships(ctx context.Context, filter *v1.RelationshipFilter,
   370  		options ...options.DeleteOptionsOption,
   371  	) (bool, error)
   372  
   373  	// WriteNamespaces takes proto namespace definitions and persists them.
   374  	WriteNamespaces(ctx context.Context, newConfigs ...*core.NamespaceDefinition) error
   375  
   376  	// DeleteNamespaces deletes namespaces including associated relationships.
   377  	DeleteNamespaces(ctx context.Context, nsNames ...string) error
   378  
   379  	// BulkLoad takes a relationship source iterator, and writes all of the
   380  	// relationships to the backing datastore in an optimized fashion. This
   381  	// method can and will omit checks and otherwise cut corners in the
   382  	// interest of performance, and should not be relied upon for OLTP-style
   383  	// workloads.
   384  	BulkLoad(ctx context.Context, iter BulkWriteRelationshipSource) (uint64, error)
   385  }
   386  
   387  // TxUserFunc is a type for the function that users supply when they invoke a read-write transaction.
   388  type TxUserFunc func(context.Context, ReadWriteTransaction) error
   389  
   390  // ReadyState represents the ready state of the datastore.
   391  type ReadyState struct {
   392  	// Message is a human-readable status message for the current state.
   393  	Message string
   394  
   395  	// IsReady indicates whether the datastore is ready.
   396  	IsReady bool
   397  }
   398  
   399  // BulkWriteRelationshipSource is an interface for transferring relationships
   400  // to a backing datastore with a zero-copy methodology.
   401  type BulkWriteRelationshipSource interface {
   402  	// Next Returns a pointer to a relation tuple if one is available, or nil if
   403  	// there are no more or there was an error.
   404  	//
   405  	// Note: sources may re-use the same memory address for every tuple, data
   406  	// may change on every call to next even if the pointer has not changed.
   407  	Next(ctx context.Context) (*core.RelationTuple, error)
   408  }
   409  
   410  type WatchContent int
   411  
   412  const (
   413  	WatchRelationships WatchContent = 1 << 0
   414  	WatchSchema        WatchContent = 1 << 1
   415  	WatchCheckpoints   WatchContent = 1 << 2
   416  )
   417  
   418  // WatchOptions are options for a Watch call.
   419  type WatchOptions struct {
   420  	// Content is the content to watch.
   421  	Content WatchContent
   422  
   423  	// CheckpointInterval is the interval to use for checkpointing in the watch.
   424  	// If given the zero value, the datastore's default will be used. If smaller
   425  	// than the datastore's minimum, the minimum will be used.
   426  	CheckpointInterval time.Duration
   427  
   428  	// WatchBufferLength is the length of the buffer for the watch channel. If
   429  	// given the zero value, the datastore's default will be used.
   430  	WatchBufferLength uint16
   431  
   432  	// WatchBufferWriteTimeout is the timeout for writing to the watch channel.
   433  	// If given the zero value, the datastore's default will be used.
   434  	WatchBufferWriteTimeout time.Duration
   435  }
   436  
   437  // WatchJustRelationships returns watch options for just relationships.
   438  func WatchJustRelationships() WatchOptions {
   439  	return WatchOptions{
   440  		Content: WatchRelationships,
   441  	}
   442  }
   443  
   444  // WatchJustSchema returns watch options for just schema.
   445  func WatchJustSchema() WatchOptions {
   446  	return WatchOptions{
   447  		Content: WatchSchema,
   448  	}
   449  }
   450  
   451  // WithCheckpointInterval sets the checkpoint interval on a watch options, returning
   452  // an updated options struct.
   453  func (wo WatchOptions) WithCheckpointInterval(interval time.Duration) WatchOptions {
   454  	return WatchOptions{
   455  		Content:            wo.Content,
   456  		CheckpointInterval: interval,
   457  	}
   458  }
   459  
   460  // Datastore represents tuple access for a single namespace.
   461  type Datastore interface {
   462  	// SnapshotReader creates a read-only handle that reads the datastore at the specified revision.
   463  	// Any errors establishing the reader will be returned by subsequent calls.
   464  	SnapshotReader(Revision) Reader
   465  
   466  	// ReadWriteTx starts a read/write transaction, which will be committed if no error is
   467  	// returned and rolled back if an error is returned.
   468  	ReadWriteTx(context.Context, TxUserFunc, ...options.RWTOptionsOption) (Revision, error)
   469  
   470  	// OptimizedRevision gets a revision that will likely already be replicated
   471  	// and will likely be shared amongst many queries.
   472  	OptimizedRevision(ctx context.Context) (Revision, error)
   473  
   474  	// HeadRevision gets a revision that is guaranteed to be at least as fresh as
   475  	// right now.
   476  	HeadRevision(ctx context.Context) (Revision, error)
   477  
   478  	// CheckRevision checks the specified revision to make sure it's valid and
   479  	// hasn't been garbage collected.
   480  	CheckRevision(ctx context.Context, revision Revision) error
   481  
   482  	// RevisionFromString will parse the revision text and return the specific type of Revision
   483  	// used by the specific datastore implementation.
   484  	RevisionFromString(serialized string) (Revision, error)
   485  
   486  	// Watch notifies the caller about changes to the datastore, based on the specified options.
   487  	//
   488  	// All events following afterRevision will be sent to the caller.
   489  	Watch(ctx context.Context, afterRevision Revision, options WatchOptions) (<-chan *RevisionChanges, <-chan error)
   490  
   491  	// ReadyState returns a state indicating whether the datastore is ready to accept data.
   492  	// Datastores that require database schema creation will return not-ready until the migrations
   493  	// have been run to create the necessary tables.
   494  	ReadyState(ctx context.Context) (ReadyState, error)
   495  
   496  	// Features returns an object representing what features this
   497  	// datastore can support.
   498  	Features(ctx context.Context) (*Features, error)
   499  
   500  	// Statistics returns relevant values about the data contained in this cluster.
   501  	Statistics(ctx context.Context) (Stats, error)
   502  
   503  	// Close closes the data store.
   504  	Close() error
   505  }
   506  
   507  type strArray []string
   508  
   509  // MarshalZerologArray implements zerolog array marshalling.
   510  func (strs strArray) MarshalZerologArray(a *zerolog.Array) {
   511  	for _, val := range strs {
   512  		a.Str(val)
   513  	}
   514  }
   515  
   516  // StartableDatastore is an optional extension to the datastore interface that, when implemented,
   517  // provides the ability for callers to start background operations on the datastore.
   518  type StartableDatastore interface {
   519  	Datastore
   520  
   521  	// Start starts any background operations on the datastore. The context provided, if canceled, will
   522  	// also cancel the background operation(s) on the datastore.
   523  	Start(ctx context.Context) error
   524  }
   525  
   526  // RepairOperation represents a single kind of repair operation that can be run in a repairable
   527  // datastore.
   528  type RepairOperation struct {
   529  	// Name is the command-line name for the repair operation.
   530  	Name string
   531  
   532  	// Description is the human-readable description for the repair operation.
   533  	Description string
   534  }
   535  
   536  // RepairableDatastore is an optional extension to the datastore interface that, when implemented,
   537  // provides the ability for callers to repair the datastore's data in some fashion.
   538  type RepairableDatastore interface {
   539  	Datastore
   540  
   541  	// Repair runs the repair operation on the datastore.
   542  	Repair(ctx context.Context, operationName string, outputProgress bool) error
   543  
   544  	// RepairOperations returns the available repair operations for the datastore.
   545  	RepairOperations() []RepairOperation
   546  }
   547  
   548  // UnwrappableDatastore represents a datastore that can be unwrapped into the underlying
   549  // datastore.
   550  type UnwrappableDatastore interface {
   551  	// Unwrap returns the wrapped datastore.
   552  	Unwrap() Datastore
   553  }
   554  
   555  // UnwrapAs recursively attempts to unwrap the datastore into the specified type
   556  // In none of the layers of the datastore implement the specified type, nil is returned.
   557  func UnwrapAs[T any](datastore Datastore) T {
   558  	var ds T
   559  	uwds := datastore
   560  
   561  	for {
   562  		var ok bool
   563  		ds, ok = uwds.(T)
   564  		if ok {
   565  			break
   566  		}
   567  
   568  		wds, ok := uwds.(UnwrappableDatastore)
   569  		if !ok {
   570  			break
   571  		}
   572  
   573  		uwds = wds.Unwrap()
   574  	}
   575  
   576  	return ds
   577  }
   578  
   579  // Feature represents a capability that a datastore can support, plus an
   580  // optional message explaining the feature is available (or not).
   581  type Feature struct {
   582  	Enabled bool
   583  	Reason  string
   584  }
   585  
   586  // Features holds values that represent what features a database can support.
   587  type Features struct {
   588  	// Watch is enabled if the underlying datastore can support the Watch api.
   589  	Watch Feature
   590  }
   591  
   592  // ObjectTypeStat represents statistics for a single object type (namespace).
   593  type ObjectTypeStat struct {
   594  	// NumRelations is the number of relations defined in a single object type.
   595  	NumRelations uint32
   596  
   597  	// NumPermissions is the number of permissions defined in a single object type.
   598  	NumPermissions uint32
   599  }
   600  
   601  // Stats represents statistics for the entire datastore.
   602  type Stats struct {
   603  	// UniqueID is a unique string for a single datastore.
   604  	UniqueID string
   605  
   606  	// EstimatedRelationshipCount is a best-guess estimate of the number of relationships
   607  	// in the datastore. Computing it should use a lightweight method such as reading
   608  	// table statistics.
   609  	EstimatedRelationshipCount uint64
   610  
   611  	// ObjectTypeStatistics returns a slice element for each object type (namespace)
   612  	// stored in the datastore.
   613  	ObjectTypeStatistics []ObjectTypeStat
   614  }
   615  
   616  // RelationshipIterator is an iterator over matched tuples.
   617  type RelationshipIterator interface {
   618  	// Next returns the next tuple in the result set.
   619  	Next() *core.RelationTuple
   620  
   621  	// Cursor returns a cursor that can be used to resume reading of relationships
   622  	// from the last relationship returned. Only applies if a sort ordering was
   623  	// requested.
   624  	Cursor() (options.Cursor, error)
   625  
   626  	// Err after receiving a nil response, the caller must check for an error.
   627  	Err() error
   628  
   629  	// Close cancels the query and closes any open connections.
   630  	Close()
   631  }
   632  
   633  // Revision is an interface for a comparable revision type that can be different for
   634  // each datastore implementation.
   635  type Revision interface {
   636  	fmt.Stringer
   637  
   638  	// Equal returns whether the revisions should be considered equal.
   639  	Equal(Revision) bool
   640  
   641  	// GreaterThan returns whether the receiver is probably greater than the right hand side.
   642  	GreaterThan(Revision) bool
   643  
   644  	// LessThan returns whether the receiver is probably less than the right hand side.
   645  	LessThan(Revision) bool
   646  }
   647  
   648  type nilRevision struct{}
   649  
   650  func (nilRevision) Equal(rhs Revision) bool {
   651  	return rhs == NoRevision
   652  }
   653  
   654  func (nilRevision) GreaterThan(_ Revision) bool {
   655  	return false
   656  }
   657  
   658  func (nilRevision) LessThan(_ Revision) bool {
   659  	return true
   660  }
   661  
   662  func (nilRevision) String() string {
   663  	return "nil"
   664  }
   665  
   666  // NoRevision is a zero type for the revision that will make changing the
   667  // revision type in the future a bit easier if necessary. Implementations
   668  // should use any time they want to signal an empty/error revision.
   669  var NoRevision Revision = nilRevision{}