Search
ctrl/
Ask AI
Light
Dark
System

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.

Let’s a create a Person.friends link with a strength property corresponding to the strength of the friendship.

Copy
type Person {
  required name: str { constraint exclusive };

  multi friends: Person {
    strength: float64;
  }
}
Copy
type Person {
  required name: str { constraint exclusive };

  multi friends: Person {
    strength: float64;
    constraint expression on (
      __subject__@strength >= 0
    );
  }
}

To index on a link property, you must declare an abstract link and extend it.

Copy
abstract link friendship {
  strength: float64;
  index on (__subject__@strength);
}

type Person {
  required name: str { constraint exclusive };
  multi friends: Person {
    extending friendship;
  };
}

The @strength property is specified in the shape of the select subquery. This is only valid in a subquery inside an insert statement.

Copy
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.

Copy
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:

Copy
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
  }
};
Copy
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.

Copy
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:

Copy
# 🚫 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:

Copy
# ✅ 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:

Copy
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:

Copy
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:

Copy
select Person {
  name,
  followers := .<follows[is Person] {
    name,
    followed := @followed
  }
};