Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Replace FieldDescription.RelationType with IsPrimary #2288

Merged
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
Loading