Skip to content

Commit

Permalink
feat: Replace FieldDescription.RelationType with IsPrimary (#2288)
Browse files Browse the repository at this point in the history
## Relevant issue(s)

Resolves #2287 #1772

## Description

Replaces `FieldDescription.RelationType` with an `IsPrimary` boolean.

One notable effect of this that may not be easily visible in the
code-diff is that the schema ID will not change depending on whether it
is the primary side of a one-one or on the one side of a one-many - now
the schema only cares about the shape of the data, not what is on the
other side of the relationship.
  • Loading branch information
AndrewSisley authored Feb 7, 2024
1 parent 86b59c6 commit 36315e4
Show file tree
Hide file tree
Showing 57 changed files with 496 additions and 1,113 deletions.
32 changes: 5 additions & 27 deletions client/descriptions.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,9 @@ func (col CollectionDescription) GetFieldByRelation(
schema *SchemaDescription,
) (FieldDescription, bool) {
for _, field := range schema.Fields {
if field.RelationName == relationName && !(col.Name.Value() == otherCollectionName && otherFieldName == field.Name) {
if field.RelationName == relationName &&
!(col.Name.Value() == otherCollectionName && otherFieldName == field.Name) &&
field.Kind != FieldKind_DocID {
return field, true
}
}
Expand Down Expand Up @@ -250,18 +252,6 @@ var FieldKindStringToEnumMapping = map[string]FieldKind{
// RelationType describes the type of relation between two types.
type RelationType uint8

// Note: These values are serialized and persisted in the database, avoid modifying existing values
const (
Relation_Type_ONE RelationType = 1 // 0b0000 0001
Relation_Type_MANY RelationType = 2 // 0b0000 0010
Relation_Type_ONEONE RelationType = 4 // 0b0000 0100
Relation_Type_ONEMANY RelationType = 8 // 0b0000 1000
Relation_Type_MANYMANY RelationType = 16 // 0b0001 0000
_ RelationType = 32 // 0b0010 0000
Relation_Type_INTERNAL_ID RelationType = 64 // 0b0100 0000
Relation_Type_Primary RelationType = 128 // 0b1000 0000 Primary reference entity on relation
)

// FieldID is a unique identifier for a field in a schema.
type FieldID uint32

Expand Down Expand Up @@ -302,14 +292,7 @@ type FieldDescription struct {
// It is currently immutable.
Typ CType

// RelationType contains the relationship type if this field is a relation field. Otherwise this
// will be empty.
RelationType RelationType
}

// IsInternal returns true if this field is internally generated.
func (f FieldDescription) IsInternal() bool {
return (f.Name == request.DocIDFieldName) || f.RelationType&Relation_Type_INTERNAL_ID != 0
IsPrimaryRelation bool
}

// IsObject returns true if this field is an object type.
Expand All @@ -323,14 +306,9 @@ func (f FieldDescription) IsObjectArray() bool {
return (f.Kind == FieldKind_FOREIGN_OBJECT_ARRAY)
}

// IsPrimaryRelation returns true if this field is a relation, and is the primary side.
func (f FieldDescription) IsPrimaryRelation() bool {
return f.RelationType > 0 && f.RelationType&Relation_Type_Primary != 0
}

// IsRelation returns true if this field is a relation.
func (f FieldDescription) IsRelation() bool {
return f.RelationType > 0
return f.RelationName != ""
}

// IsArray returns true if this field is an array type which includes inline arrays as well
Expand Down
98 changes: 23 additions & 75 deletions db/collection.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,13 +169,12 @@ func (db *db) updateSchema(
}

for _, field := range schema.Fields {
if field.RelationType.IsSet(client.Relation_Type_ONE) {
if field.Kind == client.FieldKind_FOREIGN_OBJECT {
idFieldName := field.Name + "_id"
if _, ok := schema.GetField(idFieldName); !ok {
schema.Fields = append(schema.Fields, client.FieldDescription{
Name: idFieldName,
Kind: client.FieldKind_DocID,
RelationType: client.Relation_Type_INTERNAL_ID,
RelationName: field.RelationName,
})
}
Expand Down Expand Up @@ -316,40 +315,11 @@ func validateUpdateSchemaFields(
return false, NewErrSchemaNotFound(proposedField.Name, proposedField.Schema)
}

if proposedField.Kind == client.FieldKind_FOREIGN_OBJECT {
if !proposedField.RelationType.IsSet(client.Relation_Type_ONE) ||
!(proposedField.RelationType.IsSet(client.Relation_Type_ONEONE) ||
proposedField.RelationType.IsSet(client.Relation_Type_ONEMANY)) {
return false, NewErrRelationalFieldInvalidRelationType(
proposedField.Name,
fmt.Sprintf(
"%v and %v or %v, with optionally %v",
client.Relation_Type_ONE,
client.Relation_Type_ONEONE,
client.Relation_Type_ONEMANY,
client.Relation_Type_Primary,
),
proposedField.RelationType,
)
}
}

if proposedField.Kind == client.FieldKind_FOREIGN_OBJECT_ARRAY {
if !proposedField.RelationType.IsSet(client.Relation_Type_MANY) ||
!proposedField.RelationType.IsSet(client.Relation_Type_ONEMANY) {
return false, NewErrRelationalFieldInvalidRelationType(
proposedField.Name,
client.Relation_Type_MANY|client.Relation_Type_ONEMANY,
proposedField.RelationType,
)
}
}

if proposedField.RelationName == "" {
return false, NewErrRelationalFieldMissingRelationName(proposedField.Name)
}

if proposedField.RelationType.IsSet(client.Relation_Type_Primary) {
if proposedField.IsPrimaryRelation {
if proposedField.Kind == client.FieldKind_FOREIGN_OBJECT_ARRAY {
return false, NewErrPrimarySideOnMany(proposedField.Name)
}
Expand All @@ -363,14 +333,6 @@ func validateUpdateSchemaFields(
return false, NewErrRelationalFieldIDInvalidType(idField.Name, client.FieldKind_DocID, idField.Kind)
}

if idField.RelationType != client.Relation_Type_INTERNAL_ID {
return false, NewErrRelationalFieldInvalidRelationType(
idField.Name,
client.Relation_Type_INTERNAL_ID,
idField.RelationType,
)
}

if idField.RelationName == "" {
return false, NewErrRelationalFieldMissingRelationName(idField.Name)
}
Expand All @@ -381,7 +343,7 @@ func validateUpdateSchemaFields(
var relatedField client.FieldDescription
for _, field := range relatedDesc.Fields {
if field.RelationName == proposedField.RelationName &&
!field.RelationType.IsSet(client.Relation_Type_INTERNAL_ID) &&
field.Kind != client.FieldKind_DocID &&
!(relatedDesc.Name == proposedDesc.Name && field.Name == proposedField.Name) {
relatedFieldFound = true
relatedField = field
Expand All @@ -393,43 +355,13 @@ func validateUpdateSchemaFields(
return false, client.NewErrRelationOneSided(proposedField.Name, proposedField.Schema)
}

if !(proposedField.RelationType.IsSet(client.Relation_Type_Primary) ||
relatedField.RelationType.IsSet(client.Relation_Type_Primary)) {
if !(proposedField.IsPrimaryRelation || relatedField.IsPrimaryRelation) {
return false, NewErrPrimarySideNotDefined(proposedField.RelationName)
}

if proposedField.RelationType.IsSet(client.Relation_Type_Primary) &&
relatedField.RelationType.IsSet(client.Relation_Type_Primary) {
if proposedField.IsPrimaryRelation && relatedField.IsPrimaryRelation {
return false, NewErrBothSidesPrimary(proposedField.RelationName)
}

if proposedField.RelationType.IsSet(client.Relation_Type_ONEONE) &&
relatedField.Kind != client.FieldKind_FOREIGN_OBJECT {
return false, NewErrRelatedFieldKindMismatch(
proposedField.RelationName,
client.FieldKind_FOREIGN_OBJECT,
relatedField.Kind,
)
}

if proposedField.RelationType.IsSet(client.Relation_Type_ONEMANY) &&
proposedField.Kind == client.FieldKind_FOREIGN_OBJECT &&
relatedField.Kind != client.FieldKind_FOREIGN_OBJECT_ARRAY {
return false, NewErrRelatedFieldKindMismatch(
proposedField.RelationName,
client.FieldKind_FOREIGN_OBJECT_ARRAY,
relatedField.Kind,
)
}

if proposedField.RelationType.IsSet(client.Relation_Type_ONEONE) &&
!relatedField.RelationType.IsSet(client.Relation_Type_ONEONE) {
return false, NewErrRelatedFieldRelationTypeMismatch(
proposedField.RelationName,
client.Relation_Type_ONEONE,
relatedField.RelationType,
)
}
}

if _, isDuplicate := newFieldNames[proposedField.Name]; isDuplicate {
Expand Down Expand Up @@ -1099,7 +1031,7 @@ func (c *collection) validateOneToOneLinkDoesntAlreadyExist(
fieldDescription client.FieldDescription,
value any,
) error {
if !fieldDescription.RelationType.IsSet(client.Relation_Type_INTERNAL_ID) {
if fieldDescription.Kind != client.FieldKind_DocID {
return nil
}

Expand All @@ -1111,7 +1043,23 @@ func (c *collection) validateOneToOneLinkDoesntAlreadyExist(
if !ok {
return client.NewErrFieldNotExist(strings.TrimSuffix(fieldDescription.Name, request.RelatedObjectID))
}
if !objFieldDescription.RelationType.IsSet(client.Relation_Type_ONEONE) {
if objFieldDescription.Kind != client.FieldKind_FOREIGN_OBJECT {
return nil
}

otherCol, err := c.db.getCollectionByName(ctx, txn, objFieldDescription.Schema)
if err != nil {
return err
}
otherSchema := otherCol.Schema()
otherObjFieldDescription, _ := otherCol.Description().GetFieldByRelation(
fieldDescription.RelationName,
c.Name().Value(),
objFieldDescription.Name,
&otherSchema,
)
if otherObjFieldDescription.Kind != client.FieldKind_FOREIGN_OBJECT {
// If the other field is not an object field then this is not a one to one relation and we can continue
return nil
}

Expand Down
4 changes: 2 additions & 2 deletions db/collection_update.go
Original file line number Diff line number Diff line change
Expand Up @@ -292,14 +292,14 @@ func (c *collection) updateWithFilter(

// isSecondaryIDField returns true if the given field description represents a secondary relation field ID.
func (c *collection) isSecondaryIDField(fieldDesc client.FieldDescription) (client.FieldDescription, bool) {
if fieldDesc.RelationType != client.Relation_Type_INTERNAL_ID {
if fieldDesc.RelationName == "" || fieldDesc.Kind != client.FieldKind_DocID {
return client.FieldDescription{}, false
}

relationFieldDescription, valid := c.Schema().GetField(
strings.TrimSuffix(fieldDesc.Name, request.RelatedObjectID),
)
return relationFieldDescription, valid && !relationFieldDescription.IsPrimaryRelation()
return relationFieldDescription, valid && !relationFieldDescription.IsPrimaryRelation
}

// patchPrimaryDoc patches the (primary) document linked to from the document of the given DocID via the
Expand Down
22 changes: 0 additions & 22 deletions db/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -253,15 +253,6 @@ func NewErrRelationalFieldMissingSchema(name string, kind client.FieldKind) erro
)
}

func NewErrRelationalFieldInvalidRelationType(name string, expected any, actual client.RelationType) error {
return errors.New(
errRelationalFieldInvalidRelationType,
errors.NewKV("Field", name),
errors.NewKV("Expected", expected),
errors.NewKV("Actual", actual),
)
}

func NewErrRelationalFieldMissingIDField(name string, expectedName string) error {
return errors.New(
errRelationalFieldMissingIDField,
Expand Down Expand Up @@ -307,19 +298,6 @@ func NewErrRelatedFieldKindMismatch(relationName string, expected client.FieldKi
)
}

func NewErrRelatedFieldRelationTypeMismatch(
relationName string,
expected client.RelationType,
actual client.RelationType,
) error {
return errors.New(
errRelatedFieldRelationTypeMismatch,
errors.NewKV("RelationName", relationName),
errors.NewKV("Expected", expected),
errors.NewKV("Actual", actual),
)
}

func NewErrRelationalFieldIDInvalidType(name string, expected, actual client.FieldKind) error {
return errors.New(
errRelationalFieldIDInvalidType,
Expand Down
3 changes: 3 additions & 0 deletions docs/data_format_changes/i2288-relation-type-is-primary.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# ReplaceRelation type with IsPrimary

The FieldDescription struct has changed, this has affected schema version ids, and commit cids of documents as they are based off the schema version id.
4 changes: 2 additions & 2 deletions planner/mapper/mapper.go
Original file line number Diff line number Diff line change
Expand Up @@ -717,7 +717,7 @@ func getCollectionName(
}

hostFieldDesc, parentHasField := parentCollection.Schema().GetField(selectRequest.Name)
if parentHasField && hostFieldDesc.RelationType != 0 {
if parentHasField && hostFieldDesc.RelationName != "" {
// If this field exists on the parent, and it is a child object
// then this collection name is the collection name of the child.
return hostFieldDesc.Schema, nil
Expand Down Expand Up @@ -1018,7 +1018,7 @@ func resolveSecondaryRelationIDs(
continue
}

if !fieldDesc.RelationType.IsSet(client.Relation_Type_INTERNAL_ID) {
if fieldDesc.Kind != client.FieldKind_DocID {
continue
}

Expand Down
14 changes: 4 additions & 10 deletions planner/type_join.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import (
"github.com/sourcenetwork/defradb/db/base"
"github.com/sourcenetwork/defradb/planner/filter"
"github.com/sourcenetwork/defradb/planner/mapper"
"github.com/sourcenetwork/defradb/request/graphql/schema"
)

/*
Expand Down Expand Up @@ -86,10 +85,9 @@ func (p *Planner) makeTypeIndexJoin(
return nil, client.NewErrFieldNotExist(subType.Name)
}

meta := typeFieldDesc.RelationType
if schema.IsOne(meta) { // One-to-One, or One side of One-to-Many
if typeFieldDesc.Kind == client.FieldKind_FOREIGN_OBJECT { // One-to-One, or One side of One-to-Many
joinPlan, err = p.makeTypeJoinOne(parent, source, subType)
} else if schema.IsOneToMany(meta) { // Many side of One-to-Many
} else if typeFieldDesc.Kind == client.FieldKind_FOREIGN_OBJECT_ARRAY { // Many side of One-to-Many
joinPlan, err = p.makeTypeJoinMany(parent, source, subType)
} else { // more to come, Many-to-Many, Embedded?
return nil, ErrUnknownRelationType
Expand Down Expand Up @@ -249,10 +247,6 @@ func (p *Planner) makeTypeJoinOne(
return nil, client.NewErrFieldNotExist(subType.Name)
}

// determine relation direction (primary or secondary?)
// check if the field we're querying is the primary side of the relation
isPrimary := subTypeFieldDesc.RelationType.IsSet(client.Relation_Type_Primary)

subTypeCol, err := p.db.GetCollectionByName(p.ctx, subType.CollectionName)
if err != nil {
return nil, err
Expand All @@ -270,7 +264,7 @@ func (p *Planner) makeTypeJoinOne(
}

var secondaryFieldIndex immutable.Option[int]
if !isPrimary {
if !subTypeFieldDesc.IsPrimaryRelation {
idFieldName := subTypeFieldDesc.Name + request.RelatedObjectID
secondaryFieldIndex = immutable.Some(
parent.documentMapping.FirstIndexOfName(idFieldName),
Expand All @@ -292,7 +286,7 @@ func (p *Planner) makeTypeJoinOne(
subSelect: subType,
rootName: subTypeField.Name,
subTypeName: subType.Name,
isSecondary: !isPrimary,
isSecondary: !subTypeFieldDesc.IsPrimaryRelation,
secondaryFieldIndex: secondaryFieldIndex,
secondaryFetchLimit: 1,
dir: dir,
Expand Down
Loading

0 comments on commit 36315e4

Please sign in to comment.