github.com/authzed/spicedb@v1.32.1-0.20240520085336-ebda56537386/internal/datastore/crdb/reader.go (about) 1 package crdb 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "time" 8 9 sq "github.com/Masterminds/squirrel" 10 "github.com/jackc/pgx/v5" 11 12 "github.com/authzed/spicedb/internal/datastore/common" 13 pgxcommon "github.com/authzed/spicedb/internal/datastore/postgres/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 const ( 21 errUnableToReadConfig = "unable to read namespace config: %w" 22 errUnableToListNamespaces = "unable to list namespaces: %w" 23 ) 24 25 var ( 26 queryReadNamespace = psql.Select(colConfig, colTimestamp) 27 28 queryTuples = psql.Select( 29 colNamespace, 30 colObjectID, 31 colRelation, 32 colUsersetNamespace, 33 colUsersetObjectID, 34 colUsersetRelation, 35 colCaveatContextName, 36 colCaveatContext, 37 ) 38 39 schema = common.NewSchemaInformation( 40 colNamespace, 41 colObjectID, 42 colRelation, 43 colUsersetNamespace, 44 colUsersetObjectID, 45 colUsersetRelation, 46 colCaveatContextName, 47 common.ExpandedLogicComparison, 48 ) 49 ) 50 51 type crdbReader struct { 52 query pgxcommon.DBFuncQuerier 53 executor common.QueryExecutor 54 keyer overlapKeyer 55 overlapKeySet keySet 56 fromBuilder func(query sq.SelectBuilder, fromStr string) sq.SelectBuilder 57 } 58 59 func (cr *crdbReader) ReadNamespaceByName( 60 ctx context.Context, 61 nsName string, 62 ) (*core.NamespaceDefinition, datastore.Revision, error) { 63 config, timestamp, err := cr.loadNamespace(ctx, cr.query, nsName) 64 if err != nil { 65 if errors.As(err, &datastore.ErrNamespaceNotFound{}) { 66 return nil, datastore.NoRevision, err 67 } 68 return nil, datastore.NoRevision, fmt.Errorf(errUnableToReadConfig, err) 69 } 70 71 return config, revisions.NewHLCForTime(timestamp), nil 72 } 73 74 func (cr *crdbReader) ListAllNamespaces(ctx context.Context) ([]datastore.RevisionedNamespace, error) { 75 nsDefs, err := loadAllNamespaces(ctx, cr.query, cr.fromBuilder) 76 if err != nil { 77 return nil, fmt.Errorf(errUnableToListNamespaces, err) 78 } 79 return nsDefs, nil 80 } 81 82 func (cr *crdbReader) LookupNamespacesWithNames(ctx context.Context, nsNames []string) ([]datastore.RevisionedNamespace, error) { 83 if len(nsNames) == 0 { 84 return nil, nil 85 } 86 nsDefs, err := cr.lookupNamespaces(ctx, cr.query, nsNames) 87 if err != nil { 88 return nil, fmt.Errorf(errUnableToListNamespaces, err) 89 } 90 return nsDefs, nil 91 } 92 93 func (cr *crdbReader) QueryRelationships( 94 ctx context.Context, 95 filter datastore.RelationshipsFilter, 96 opts ...options.QueryOptionsOption, 97 ) (iter datastore.RelationshipIterator, err error) { 98 query := cr.fromBuilder(queryTuples, tableTuple) 99 qBuilder, err := common.NewSchemaQueryFilterer(schema, query).FilterWithRelationshipsFilter(filter) 100 if err != nil { 101 return nil, err 102 } 103 104 return cr.executor.ExecuteQuery(ctx, qBuilder, opts...) 105 } 106 107 func (cr *crdbReader) ReverseQueryRelationships( 108 ctx context.Context, 109 subjectsFilter datastore.SubjectsFilter, 110 opts ...options.ReverseQueryOptionsOption, 111 ) (iter datastore.RelationshipIterator, err error) { 112 query := cr.fromBuilder(queryTuples, tableTuple) 113 qBuilder, err := common.NewSchemaQueryFilterer(schema, query). 114 FilterWithSubjectsSelectors(subjectsFilter.AsSelector()) 115 if err != nil { 116 return nil, err 117 } 118 119 queryOpts := options.NewReverseQueryOptionsWithOptions(opts...) 120 121 if queryOpts.ResRelation != nil { 122 qBuilder = qBuilder. 123 FilterToResourceType(queryOpts.ResRelation.Namespace). 124 FilterToRelation(queryOpts.ResRelation.Relation) 125 } 126 127 return cr.executor.ExecuteQuery( 128 ctx, 129 qBuilder, 130 options.WithLimit(queryOpts.LimitForReverse), 131 options.WithAfter(queryOpts.AfterForReverse), 132 options.WithSort(queryOpts.SortForReverse)) 133 } 134 135 func (cr crdbReader) loadNamespace(ctx context.Context, tx pgxcommon.DBFuncQuerier, nsName string) (*core.NamespaceDefinition, time.Time, error) { 136 query := cr.fromBuilder(queryReadNamespace, tableNamespace).Where(sq.Eq{colNamespace: nsName}) 137 138 sql, args, err := query.ToSql() 139 if err != nil { 140 return nil, time.Time{}, err 141 } 142 143 var config []byte 144 var timestamp time.Time 145 146 err = tx.QueryRowFunc(ctx, func(ctx context.Context, row pgx.Row) error { 147 return row.Scan(&config, ×tamp) 148 }, sql, args...) 149 if err != nil { 150 if errors.Is(err, pgx.ErrNoRows) { 151 err = datastore.NewNamespaceNotFoundErr(nsName) 152 } 153 return nil, time.Time{}, err 154 } 155 156 loaded := &core.NamespaceDefinition{} 157 if err := loaded.UnmarshalVT(config); err != nil { 158 return nil, time.Time{}, err 159 } 160 161 return loaded, timestamp, nil 162 } 163 164 func (cr crdbReader) lookupNamespaces(ctx context.Context, tx pgxcommon.DBFuncQuerier, nsNames []string) ([]datastore.RevisionedNamespace, error) { 165 clause := sq.Or{} 166 for _, nsName := range nsNames { 167 clause = append(clause, sq.Eq{colNamespace: nsName}) 168 } 169 170 query := cr.fromBuilder(queryReadNamespace, tableNamespace).Where(clause) 171 172 sql, args, err := query.ToSql() 173 if err != nil { 174 return nil, err 175 } 176 177 var nsDefs []datastore.RevisionedNamespace 178 179 err = tx.QueryFunc(ctx, func(ctx context.Context, rows pgx.Rows) error { 180 for rows.Next() { 181 var config []byte 182 var timestamp time.Time 183 if err := rows.Scan(&config, ×tamp); err != nil { 184 return err 185 } 186 187 loaded := &core.NamespaceDefinition{} 188 if err := loaded.UnmarshalVT(config); err != nil { 189 return fmt.Errorf(errUnableToReadConfig, err) 190 } 191 192 nsDefs = append(nsDefs, datastore.RevisionedNamespace{ 193 Definition: loaded, 194 LastWrittenRevision: revisions.NewHLCForTime(timestamp), 195 }) 196 } 197 198 if rows.Err() != nil { 199 return fmt.Errorf(errUnableToReadConfig, rows.Err()) 200 } 201 return nil 202 }, sql, args...) 203 if err != nil { 204 return nil, err 205 } 206 207 return nsDefs, nil 208 } 209 210 func loadAllNamespaces(ctx context.Context, tx pgxcommon.DBFuncQuerier, fromBuilder func(sq.SelectBuilder, string) sq.SelectBuilder) ([]datastore.RevisionedNamespace, error) { 211 query := fromBuilder(queryReadNamespace, tableNamespace) 212 213 sql, args, err := query.ToSql() 214 if err != nil { 215 return nil, err 216 } 217 218 var nsDefs []datastore.RevisionedNamespace 219 220 err = tx.QueryFunc(ctx, func(ctx context.Context, rows pgx.Rows) error { 221 for rows.Next() { 222 var config []byte 223 var timestamp time.Time 224 if err := rows.Scan(&config, ×tamp); err != nil { 225 return err 226 } 227 228 loaded := &core.NamespaceDefinition{} 229 if err := loaded.UnmarshalVT(config); err != nil { 230 return fmt.Errorf(errUnableToReadConfig, err) 231 } 232 233 nsDefs = append(nsDefs, datastore.RevisionedNamespace{ 234 Definition: loaded, 235 LastWrittenRevision: revisions.NewHLCForTime(timestamp), 236 }) 237 } 238 239 if rows.Err() != nil { 240 return fmt.Errorf(errUnableToReadConfig, rows.Err()) 241 } 242 return nil 243 }, sql, args...) 244 if err != nil { 245 return nil, err 246 } 247 248 return nsDefs, nil 249 } 250 251 func (cr *crdbReader) addOverlapKey(namespace string) { 252 cr.keyer.addKey(cr.overlapKeySet, namespace) 253 } 254 255 var _ datastore.Reader = &crdbReader{}