-
Notifications
You must be signed in to change notification settings - Fork 246
/
datastore.go
209 lines (168 loc) · 7.1 KB
/
datastore.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
package datastore
import (
"context"
"fmt"
"github.com/authzed/spicedb/internal/datastore/crdb"
"github.com/authzed/spicedb/internal/datastore/postgres"
v0 "github.com/authzed/authzed-go/proto/authzed/api/v0"
v1 "github.com/authzed/authzed-go/proto/authzed/api/v1"
"github.com/shopspring/decimal"
)
// Ellipsis is a special relation that is assumed to be valid on the right
// hand side of a tuple.
const Ellipsis = "..."
// RevisionChanges represents the changes in a single transaction.
type RevisionChanges struct {
Revision Revision
Changes []*v0.RelationTupleUpdate
}
// Datastore represents tuple access for a single namespace.
type Datastore interface {
GraphDatastore
// WriteTuples takes a list of existing tuples that must exist, and a list of
// tuple mutations and applies it to the datastore for the specified
// namespace.
WriteTuples(ctx context.Context, preconditions []*v1.Precondition, mutations []*v1.RelationshipUpdate) (Revision, error)
// DeleteRelationships deletes all Relationships that match the provided
// filter if all preconditions are met.
DeleteRelationships(ctx context.Context, preconditions []*v1.Precondition, filter *v1.RelationshipFilter) (Revision, error)
// Revision gets the currently replicated revision for this datastore.
Revision(ctx context.Context) (Revision, error)
// SyncRevision gets a revision that is guaranteed to be at least as fresh as
// right now.
SyncRevision(ctx context.Context) (Revision, error)
// Watch notifies the caller about all changes to tuples.
//
// All events following afterRevision will be sent to the caller.
Watch(ctx context.Context, afterRevision Revision) (<-chan *RevisionChanges, <-chan error)
// WriteNamespace takes a proto namespace definition and persists it,
// returning the version of the namespace that was created.
WriteNamespace(ctx context.Context, newConfig *v0.NamespaceDefinition) (Revision, error)
// ReadNamespace reads a namespace definition and version and returns it if
// found.
ReadNamespace(ctx context.Context, nsName string) (*v0.NamespaceDefinition, Revision, error)
// DeleteNamespace deletes a namespace and any associated tuples.
DeleteNamespace(ctx context.Context, nsName string) (Revision, error)
// ListNamespaces lists all namespaces defined.
ListNamespaces(ctx context.Context) ([]*v0.NamespaceDefinition, error)
// IsReady returns whether the datastore is ready to accept data. Datastores that require
// database schema creation will return false until the migrations have been run to create
// the necessary tables.
IsReady(ctx context.Context) (bool, error)
// Close closes the data store.
Close() error
}
// GraphDatastore is a subset of the datastore interface that is passed to
// graph resolvers.
type GraphDatastore interface {
// QueryTuples creates a builder for reading tuples from the datastore.
QueryTuples(filter TupleQueryResourceFilter, revision Revision) TupleQuery
// ReverseQueryTuplesFromSubject creates a builder for reading tuples from
// subject onward from the datastore.
ReverseQueryTuplesFromSubject(subject *v0.ObjectAndRelation, revision Revision) ReverseTupleQuery
// ReverseQueryTuplesFromSubjectRelation creates a builder for reading tuples
// from a subject relation onward from the datastore.
ReverseQueryTuplesFromSubjectRelation(subjectNamespace, subjectRelation string, revision Revision) ReverseTupleQuery
// ReverseQueryTuplesFromSubjectNamespace creates a builder for reading
// tuples from a subject namespace onward from the datastore.
ReverseQueryTuplesFromSubjectNamespace(subjectNamespace string, revision Revision) ReverseTupleQuery
// CheckRevision checks the specified revision to make sure it's valid and
// hasn't been garbage collected.
CheckRevision(ctx context.Context, revision Revision) error
}
// TupleQueryResourceFilter are the baseline fields used to filter results when
// querying a datastore for tuples.
//
// OptionalFields are ignored when their value is the empty string.
type TupleQueryResourceFilter struct {
ResourceType string
OptionalResourceID string
OptionalResourceRelation string
}
// CommonTupleQuery is the common interface shared between TupleQuery and
// ReverseTupleQuery.
type CommonTupleQuery interface {
// Execute runs the tuple query and returns a result iterator.
Execute(ctx context.Context) (TupleIterator, error)
// Limit sets a limit on the query.
Limit(limit uint64) CommonTupleQuery
}
// ReverseTupleQuery is a builder for constructing reverse tuple queries.
type ReverseTupleQuery interface {
CommonTupleQuery
// WithObjectRelation filters to tuples with the given object relation on the
// left hand side.
WithObjectRelation(namespace string, relation string) ReverseTupleQuery
}
// TupleQuery is a builder for constructing tuple queries.
type TupleQuery interface {
CommonTupleQuery
// WithSubjectFilter adds a subject filter to the query.
WithSubjectFilter(*v1.SubjectFilter) TupleQuery
// WithUsersets adds multiple userset filters to the query.
WithUsersets(usersets []*v0.ObjectAndRelation) TupleQuery
}
// TupleIterator is an iterator over matched tuples.
type TupleIterator interface {
// Next returns the next tuple in the result set.
Next() *v0.RelationTuple
// After receiving a nil response, the caller must check for an error.
Err() error
// Close cancels the query and closes any open connections.
Close()
}
// Revision is a type alias to make changing the revision type a little bit
// easier if we need to do it in the future. Implementations should code
// directly against decimal.Decimal when creating or parsing.
type Revision = decimal.Decimal
// NoRevision is a zero type for the revision that will make changing the
// revision type in the future a bit easier if necessary. Implementations
// should use any time they want to signal an empty/error revision.
var NoRevision Revision
type DatastoreOptions interface {
Option()
}
type Option interface {
apply(DatastoreOptions)
}
type OptionFunc func(DatastoreOptions)
func (f OptionFunc) apply(o DatastoreOptions) {
f(o)
}
type Kind string
const (
CockroachKind Kind = "cockroach"
PostgresKind Kind = "postgres"
MemoryKind Kind = "memory"
)
// NewDatastore initializes a datastore given the options
func NewDatastore(kind Kind, options ...Option) (Datastore, error) {
// TODO: make this a map lookup so that you can add your own DatastoreKinds?
switch kind {
case CockroachKind:
crdbOpts := make([]crdb.Option, 0, len(options))
for _, o := range options {
crdbOpt, ok := o.(crdb.Option)
if !ok {
return nil, fmt.Errorf("incompatible option")
}
crdbOpts = append(crdbOpts, crdbOpt)
}
// TODO: url should be an option
return crdb.NewCRDBDatastore("", crdbOpts...)
case PostgresKind:
pgOpts:= make([]postgres.Option, 0, len(options))
for _, o := range options {
pgOpt, ok := o.(postgres.Option)
if !ok {
return nil, fmt.Errorf("incompatible option")
}
pgOpts = append(pgOpts, pgOpt)
}
// TODO: url should be an option
return postgres.NewPostgresDatastore("", pgOpts...)
case MemoryKind:
// TODO: memory datastore needs options
}
return nil, fmt.Errorf("unknown datastore kind")
}