Using link properties
Links can contain properties. These are distinct from links themselves (which we refer to as simply “links”) and are used to store metadata about a link. Due to how they’re persisted under the hood, link properties have a few additional constraints: they’re always single and optional.
In thinking about how to use link properties, keep in mind that they are link properties, not link links. This means they can contain only primitive data (scalars, enums, arrays, or tuples).
In practice, link properties are best used with many-to-many relationships
(multi
links without any exclusive constraints). For one-to-one,
one-to-many, and many-to-one relationships the same data should be stored in
object properties instead.
Declaration
Let’s a create a Person.friends
link with a strength
property
corresponding to the strength of the friendship.
type Person {
required name: str { constraint exclusive };
multi friends: Person {
strength: float64;
}
}
Constraints
type Person {
required name: str { constraint exclusive };
multi friends: Person {
strength: float64;
constraint expression on (
__subject__@strength >= 0
);
}
}
Indexes
To index on a link property, you must declare an abstract link and extend it.
abstract link friendship {
strength: float64;
index on (__subject__@strength);
}
type Person {
required name: str { constraint exclusive };
multi friends: Person {
extending friendship;
};
}
Conceptualizing link properties
A good way to conceptualize the difference between a regular property and a link property for object types is that regular properties are used to construct expressions that return object types, while link properties are appended to expressions that return object types to qualify the link.
For example, the properties name
and email
are used to construct a
Person
object that is inserted, also returning the same Person
object.
insert Person {
name := "Jane",
email := "jane@jane.com"
}
If this Person
object is inserted while linking it to another Person
object we are inserting, we can append a @strength
property to the link.
@strength
is not used to construct a Person
, but to quantify the link.
insert Person {
name := "Bob",
email := "bob@bob.com",
friends := (
insert Person {
name := "Jane",
email := "jane@jane.com",
@strength := 3.14
}
)
}
Keep this in mind when reading through the following examples.
Inserting
The @strength
property is specified in the shape of the select
subquery. This is only valid in a subquery inside an insert
statement.
insert Person {
name := "Bob",
friends := (
select detached Person {
@strength := 3.14
}
filter .name = "Alice"
)
}
We are using the detached
operator to unbind the
Person
reference from the scope of the insert
query.
When doing a nested insert, link properties can be directly included in the
inner insert
subquery. The query below creates a link to a Person
object that is being inserted in the same query, along with a link property
strength
that has a value of 3.14.
insert Person {
name := "Bob",
friends := (
insert Person {
name := "Jane",
@strength := 3.14
}
)
}
Similarly, with
can be used to capture an expression returning an
object type, after which a link property can be added when linking it to
another object type:
with
_friends := (
insert Person {
name := "Alice"
} unless conflict on .name
else (select Person filter .name = "Alice" limit 1 )
)
insert Person {
name := "Bob",
friends := _friends {
@strength := 3.14
}
};
Updating
update Person
filter .name = "Bob"
set {
friends += (
select .friends {
@strength := 3.7
}
filter .name = "Alice"
)
};
The example updates the @strength
property of Bob’s friends link to
Alice to 3.7.
In the context of multi links the += operator works like an an insert/update operator.
To update one or more links in a multi link, you can select from the current
linked objects, as the example does. Use a detached
selection if you
want to insert/update a wider selection of linked objects instead.
Querying
edgedb> ....... ....... ....... ....... ....... .......
select Person {
name,
friends: {
name,
@strength
}
};
{ default::Person {name: 'Alice', friends: {}}, default::Person { name: 'Bob', friends: { default::Person {name: 'Alice', @strength: 3.7} } }, }
A link property cannot be referenced in a set union except in the case of a for loop. That means this will not work:
# 🚫 Does not work
insert Movie {
title := 'The Incredible Hulk',
actors := {(
select Person {
@character_name := 'The Hulk'
} filter .name = 'Mark Ruffalo'
),
(
select Person {
@character_name := 'Abomination'
} filter .name = 'Tim Roth'
)}
};
That query will produce an error: QueryError: invalid reference to link
property in top level shape
You can use this workaround instead:
# ✅ Works!
insert Movie {
title := 'The Incredible Hulk',
actors := assert_distinct((
with characters := {
('The Hulk', 'Mark Ruffalo'),
('Abomination', 'Tim Roth')
},
for character in characters union (
select Person {
@character_name := character.0
} filter .name = character.1
)
))
};
Note that we are also required to wrap the actors
query with
assert_distinct()
here to assure the compiler that the result set
is distinct.
Specifying link properties of a computed backlink in your shape is supported as of EdgeDB 3.0.
If you have this schema:
type Person {
required name: str;
multi follows: Person {
followed: datetime {
default := datetime_of_statement();
};
};
multi link followers := .<follows[is Person];
}
this query will work as of EdgeDB 3.0:
select Person {
name,
followers: {
name,
@followed
}
};
even though @followed
is a link property of follows
and we are
accessing is through the computed backlink followers
instead.
If you need link properties on backlinks in earlier versions of EdgeDB, you can use this workaround:
select Person {
name,
followers := .<follows[is Person] {
name,
followed := @followed
}
};