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