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