Skip to content

Commit

Permalink
Rework relation field kinds
Browse files Browse the repository at this point in the history
  • Loading branch information
AndrewSisley committed Aug 26, 2024
1 parent a9c084e commit be403c1
Show file tree
Hide file tree
Showing 34 changed files with 2,479 additions and 546 deletions.
3 changes: 3 additions & 0 deletions client/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,9 @@ type CollectionFetchOptions struct {
// If provided, only collections with schemas of this root will be returned.
SchemaRoot immutable.Option[string]

// If provided, only collections with this root will be returned.
Root immutable.Option[uint32]

// If provided, only collections with this name will be returned.
Name immutable.Option[string]

Expand Down
170 changes: 170 additions & 0 deletions client/definitions.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,15 @@
package client

import (
"context"
"errors"
"fmt"
"strings"

"github.com/sourcenetwork/immutable"

"github.com/sourcenetwork/defradb/client/request"
"github.com/sourcenetwork/defradb/datastore"
)

// CollectionDefinition contains the metadata defining what a Collection is.
Expand Down Expand Up @@ -195,3 +201,167 @@ func (f FieldDefinition) GetSecondaryRelationField(c CollectionDefinition) (Fiel
secondary, valid := c.GetFieldByName(strings.TrimSuffix(f.Name, request.RelatedObjectID))
return secondary, valid && !secondary.IsPrimaryRelation
}

// DefinitionCache is an object providing easy access to cached collection definitions.
type DefinitionCache struct {
// The full set of [CollectionDefinition]s within this cache
Definitions []CollectionDefinition

// The cached Definitions mapped by the Root of their [SchemaDescription]
DefinitionsBySchemaRoot map[string]CollectionDefinition

// The cached Definitions mapped by the Root of their [CollectionDescription]
DefinitionsByCollectionRoot map[uint32]CollectionDefinition
}

// NewDefinitionCache creates a new [DefinitionCache] populated with the given [CollectionDefinition]s.
func NewDefinitionCache(definitions []CollectionDefinition) DefinitionCache {
definitionsBySchemaRoot := make(map[string]CollectionDefinition, len(definitions))
definitionsByCollectionRoot := make(map[uint32]CollectionDefinition, len(definitions))

for _, def := range definitions {
definitionsBySchemaRoot[def.Schema.Root] = def
definitionsByCollectionRoot[def.Description.RootID] = def
}

return DefinitionCache{
Definitions: definitions,
DefinitionsBySchemaRoot: definitionsBySchemaRoot,
DefinitionsByCollectionRoot: definitionsByCollectionRoot,
}
}

// GetDefinition returns the definition that the given [FieldKind] points to, if it is found in the
// given [DefinitionCache].
//
// If the related definition is not found, default and false will be returned.
func GetDefinition(
cache DefinitionCache,
host CollectionDefinition,
kind FieldKind,
) (CollectionDefinition, bool) {
switch typedKind := kind.(type) {
case *NamedKind:
for _, def := range cache.Definitions {
if def.GetName() == typedKind.Name {
return def, true
}
}

return CollectionDefinition{}, false

case *SchemaKind:
def, ok := cache.DefinitionsBySchemaRoot[typedKind.Root]
return def, ok

case *CollectionKind:
def, ok := cache.DefinitionsByCollectionRoot[typedKind.Root]
return def, ok

case *SelfKind:
if host.Description.RootID != 0 {
return host, true
}

if typedKind.RelativeID == "" {
return host, true
}

hostIDBase := strings.Split(host.Schema.Root, "-")[0]
targetID := fmt.Sprintf("%s-%s", hostIDBase, typedKind.RelativeID)

def, ok := cache.DefinitionsBySchemaRoot[targetID]
return def, ok

default:
// no-op
}

return CollectionDefinition{}, false
}

// GetDefinitionUncached returns the definition that the given [FieldKind] points to, if it is found in the given store.
//
// If the related definition is not found, or an error occurs, default and false will be returned.
func GetDefinitionUncached(
ctx context.Context,
store Store,
host CollectionDefinition,
kind FieldKind,
) (CollectionDefinition, bool, error) {
switch typedKind := kind.(type) {
case *NamedKind:
col, err := store.GetCollectionByName(ctx, typedKind.Name)
if errors.Is(datastore.ErrNotFound, err) {
schemas, err := store.GetSchemas(ctx, SchemaFetchOptions{
Name: immutable.Some(typedKind.Name),
})
if len(schemas) == 0 || err != nil {
return CollectionDefinition{}, false, err
}

return CollectionDefinition{
// todo - returning the first is a temporary simplification until
// https://github.com/sourcenetwork/defradb/issues/2934
Schema: schemas[0],
}, true, nil
} else if err != nil {
return CollectionDefinition{}, false, err
}

return col.Definition(), true, nil

case *SchemaKind:
schemas, err := store.GetSchemas(ctx, SchemaFetchOptions{
Root: immutable.Some(typedKind.Root),
})
if len(schemas) == 0 || err != nil {
return CollectionDefinition{}, false, err
}

return CollectionDefinition{
// todo - returning the first is a temporary simplification until
// https://github.com/sourcenetwork/defradb/issues/2934
Schema: schemas[0],
}, true, nil

case *CollectionKind:
cols, err := store.GetCollections(ctx, CollectionFetchOptions{
Root: immutable.Some(typedKind.Root),
})

if len(cols) == 0 || err != nil {
return CollectionDefinition{}, false, err
}

return cols[0].Definition(), true, nil

case *SelfKind:
if host.Description.RootID != 0 {
return host, true, nil
}

if typedKind.RelativeID == "" {
return host, true, nil
}

hostIDBase := strings.Split(host.Schema.Root, "-")[0]
targetID := fmt.Sprintf("%s-%s", hostIDBase, typedKind.RelativeID)

cols, err := store.GetCollections(ctx, CollectionFetchOptions{
SchemaRoot: immutable.Some(targetID),
})
if len(cols) == 0 || err != nil {
return CollectionDefinition{}, false, err
}
def := cols[0].Definition()
def.Description = CollectionDescription{}

return def, true, nil

default:
// no-op
}

return CollectionDefinition{}, false, nil
}
9 changes: 9 additions & 0 deletions client/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ const (
errCanNotNormalizeValue string = "can not normalize value"
errCanNotTurnNormalValueIntoArray string = "can not turn normal value into array"
errCanNotMakeNormalNilFromFieldKind string = "can not make normal nil from field kind"
errFailedToParseKind string = "failed to parse kind"
)

// Errors returnable from this package.
Expand All @@ -57,6 +58,7 @@ var (
ErrCanNotTurnNormalValueIntoArray = errors.New(errCanNotTurnNormalValueIntoArray)
ErrCanNotMakeNormalNilFromFieldKind = errors.New(errCanNotMakeNormalNilFromFieldKind)
ErrCollectionNotFound = errors.New(errCollectionNotFound)
ErrFailedToParseKind = errors.New(errFailedToParseKind)
)

// NewErrFieldNotExist returns an error indicating that the given field does not exist.
Expand Down Expand Up @@ -165,3 +167,10 @@ func NewErrCRDTKindMismatch(cType, kind string) error {
func NewErrInvalidJSONPaylaod(payload string) error {
return errors.New(errInvalidJSONPayload, errors.NewKV("Payload", payload))
}

func NewErrFailedToParseKind(kind []byte) error {
return errors.New(
errCRDTKindMismatch,
errors.NewKV("Kind", kind),
)
}
4 changes: 2 additions & 2 deletions client/normal_value_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1393,8 +1393,8 @@ func TestNormalValue_NewNormalNil(t *testing.T) {
for _, kind := range FieldKindStringToEnumMapping {
fieldKinds = append(fieldKinds, kind)
}
fieldKinds = append(fieldKinds, ObjectKind("Object"))
fieldKinds = append(fieldKinds, ObjectArrayKind("ObjectArr"))
fieldKinds = append(fieldKinds, NewCollectionKind(1, false))
fieldKinds = append(fieldKinds, NewCollectionKind(1, true))

for _, kind := range fieldKinds {
if kind.IsNillable() {
Expand Down
15 changes: 15 additions & 0 deletions client/request/consts.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,12 @@ const (
DeltaArgPriority = "Priority"
DeltaArgDocID = "DocID"

// SelfTypeName is the name given to relation field types that reference the host type.
//
// For example, when a `User` collection contains a relation to the `User` collection the field
// will be of type [SelfTypeName].
SelfTypeName = "Self"

LinksNameFieldName = "name"
LinksCidFieldName = "cid"

Expand All @@ -85,6 +91,15 @@ var (
string(DESC): DESC,
}

// ReservedTypeNames is the set of type names reserved by the system.
//
// Users cannot define types using these names.
//
// For example, collections and schemas may not be defined using these names.
ReservedTypeNames = map[string]struct{}{
SelfTypeName: {},
}

ReservedFields = map[string]struct{}{
TypeNameFieldName: {},
VersionFieldName: {},
Expand Down
Loading

0 comments on commit be403c1

Please sign in to comment.