Easy EdgeDB · Chapter 8

# Dracula takes the boat to England​

Multiple InheritancePolymorphism

We are finally away from Castle Dracula. Here is what happens in this chapter:

Far away from Castle Dracula, a boat leaves from the city of Varna in Bulgaria, sailing into the Black Sea. It has a captain, first mate, second mate, cook, and five crew. Dracula is also on the ship inside a coffin, but the crew don’t know that he’s there. Every night Dracula leaves his coffin, and every night one of the men disappears. They become afraid but don’t know what is happening or what to do. One crewman tells the others that he saw a strange man walking around the deck, but the others don’t believe him.

The days go by and more and more sailors disappear…

Finally it’s the last day before the ship reaches the city of Whitby in England, but the captain is alone - all the others have disappeared. The captain knows the truth now. He ties his hands to the wheel so that the ship will go straight even if Dracula finds him.

The next day the people in Whitby see a ship hit the beach, and a wolf jumps off and runs onto the shore - it’s Dracula disguised as a wolf. People find the dead captain tied to the wheel with a notebook in his hand and start to read the story.

Meanwhile, Mina and her friend Lucy are in Whitby on vacation…

While Dracula arrives at Whitby, let’s learn about multiple inheritance. We know that you can `extend` a type on another, and we have done this many times: `Person` on `NPC`, `Place` on `City`, etc. Multiple inheritance means to do this with more than one type at the same time.

We’ll try some multiple inheritance with the ship’s crew. The book doesn’t give them any names, so we will give them numbers instead. Most `Person` types won’t need a number, so we’ll create an abstract type called `HasNumber` only for types that need a number:

Copy
```abstract type HasNumber {
required number: int16;
}
```

Now let’s put the `Crewman` type together, which will use multiple inheritance. Multiple inheritance is very simple: just add a comma between every type you want to extend.

Copy
```type Crewman extending HasNumber, Person;
```

However, here we have a problem: we never learn the names of the crewmen in the book, but the property `name` on `Person` is required. We know that every `Crewman` object will have a `number`, so it would be nice to use this to give them a name like “Crewman 1”, “Crewman 2”, and so on. How can we make this happen?

We can make this happen by giving `name` a default for the `Crewman` type. To give a default value, just add `{}` after the parameter and then give an expression for the default value. So far that gives us a `Crewman` type that looks like the code below. However, a migration won’t quite work yet. Can you guess why?

Copy
```type Crewman extending HasNumber, Person {
name: str {
default := '' ++ <str>.number;
}
}
```

Fortunately, the error message tells us exactly what to do.

```error: property 'name' of object type 'default::Crewman' must be declared using the `overloaded` keyword because it is defined in the following ancestor(s): default::Person
┌─ c:\rust\easy-edgedb\dbschema\default.esdl:6:5
│
6 │ ╭     name: str {
7 │ │       default := '' ++ <str>.number;
8 │ │     }
│ ╰─────^ error
```

In other words, `Person` is where the definition for `name` is, and this definition doesn’t include any information about a default value. We still want to use `name`, but in a slightly different way. That’s where the word `overloaded` comes in. Adding `overloaded` will keep the other rules for `name` (that it must be a `str`, and that it must be `exclusive`) and will add our new specification that it will have a `default` value for the `Crewman` type. So just add `overloaded` and our work is done!

Copy
```type Crewman extending HasNumber, Person {
default := 'Crewman ' ++ <str>.number;
}
}
```

Next is the `Sailor` type. The sailors have ranks, so first we will make an enum for that:

Copy
```scalar type Rank extending enum<Captain, FirstMate, SecondMate, Cook>;
```

And then we will make a `Sailor` type that uses `Person` and this `Rank` enum. The sailors in the book all have their own names, so we don’t need to overload their `name` keyword.

Copy
```type Sailor extending Person {
rank: Rank;
}
```

Then we will make a `Ship` type to hold them all. As we saw in this chapter, a `Ship` object can move on its own even if all of its sailors and crewmen are dead, so we won’t make its sailors or crew `required`.

Copy
```type Ship {
required name: str;
multi sailors: Sailor;
multi crew: Crewman;
}
```

With all those changes done, let’s do a migration.

Now that we have the `Crewman` type and it doesn’t need a name, it’s super easy to insert our crewmen thanks to `count()`. We just do this five times:

Copy
```with next_number := count(Crewman) + 1,
insert Crewman {
number := next_number
};
```

So if there are no `Crewman` types, he will get the number 1. The next will get 2, and so on. After the five inserts we can `select Crewman {name, number};` to see the result. It gives us:

```{
default::Crewman {name: 'Crewman 1', number: 1},
default::Crewman {name: 'Crewman 2', number: 2},
default::Crewman {name: 'Crewman 3', number: 3},
default::Crewman {name: 'Crewman 4', number: 4},
default::Crewman {name: 'Crewman 5', number: 5},
}
```

Now to insert the sailors we just give them each a name and choose a rank from the enum. Let’s mix up the enums between `str` and proper enum input to remind ourselves that EdgeDB will accept either one.

Copy
```insert Sailor {
name := 'The Captain',
rank := 'Captain'
};

insert Sailor {
name := 'Petrofsky',
rank := 'FirstMate'
};

insert Sailor {
name := 'The Second Mate',
rank := Rank.SecondMate
};

insert Sailor {
name := 'The Cook',
rank := Rank.Cook
};
```

Finally we have to insert a `Ship` to hold them all. This `insert` is easy because right now every `Sailor` and every `Crewman` type is part of this ship - we don’t need to `filter` anywhere.

Copy
```insert Ship {
name := 'The Demeter',
sailors := Sailor,
crew := Crewman
};
```

Then we can look up the `Ship` to make sure that the whole crew is there:

Copy
```select Ship {
name,
sailors: {
name,
rank,
},
crew: {
name,
number
},
};
```

The result is:

```{
default::Ship {
name: 'The Demeter',
sailors: {
default::Sailor {name: 'The Captain', rank: Captain},
default::Sailor {name: 'Petrofsky', rank: FirstMate},
default::Sailor {name: 'The Second Mate', rank: SecondMate},
default::Sailor {name: 'The Cook', rank: Cook},
},
crew: {
default::Crewman {name: 'Crewman 1', number: 1},
default::Crewman {name: 'Crewman 2', number: 2},
default::Crewman {name: 'Crewman 3', number: 3},
default::Crewman {name: 'Crewman 4', number: 4},
default::Crewman {name: 'Crewman 5', number: 5},
},
},
}
```

We now have quite a few types that extend the `Person` type, many with their own properties. The `Crewman` type has a property `number`, while the `NPC` type has a property called `age`. But since the `Person` type itself doesn’t have the properties `age` or `number`, we can’t just make a `Person` shape that includes them:

Copy
```select Person {
name,
age,
number,
};
```

The error is:

```error: InvalidReferenceError: object type 'default::Person'
has no link or property 'age'
```

It looks like the only property of the three that we can put in this query is `name`. That feels pretty limiting!

Copy
```select Person {
name
};
```

So is there a way to include `age` if the type is an `NPC` and `number` if the type is a `Crewman`? Yes, there is! We can use the `is` keyword inside square brackets to specify the type. Here’s how it works in our query on `Person` objects:

• `.name`: this stays the same, because `Person` has this property

• `.age`: this belongs to the `NPC` type, so change it to `[is NPC].age`

• `.number`: this belongs to the `Crewman` type, so change it to `[is Crewman].number`

Now it will work:

Copy
```select Person {
name,
[is NPC].age,
[is Crewman].number,
};
```

The output is now quite large, so here’s just a part of it. You’ll notice that types that don’t have a property or a link will return an empty set: `{}`. So the `Crewman` objects have an `age: {}` while other objects have a `number: {}`.

```{
# ... /snip
default::Crewman {name: 'Crewman 4', age: {}, number: 4},
default::Crewman {name: 'Crewman 5', age: {}, number: 5},
default::PC {name: 'Emil Sinclair', age: {}, number: {}},
default::NPC {name: 'The innkeeper', age: 30, number: {}},
default::NPC {name: 'Mina Murray', age: {}, number: {}},
default::NPC {name: 'Jonathan Harker', age: {}, number: {}},
}
```

This is officially called a polymorphic query, and is one of the best reasons to use abstract types in your schema.

Let’s do a quick experiment with the same query as above, except with the `<json>` cast. What differences do you notice?

Copy
```select <json>Person {
name,
[is NPC].age,
[is Crewman].number,
};
```

Here is part of the output:

```{"age": null, "name": "Emil Sinclair", "number": null}
{"age": null, "name": "Vampire Woman 1", "number": null}
{"age": null, "name": "The Captain", "number": null}
{"age": null, "name": null, "number": 1}
```

The type information is all gone! This makes sense because a JSON object is just a bunch of keys and values, and with a concrete query like `PC` this would be no problem. But this query includes various object types extending `Person` and it’s hard to tell which type is which. Fortunately, we can put the type information by adding `__type__` which is used to refer to an object’s own type:

Copy
```select <json>Person {
name,
[is NPC].age,
[is Crewman].number,
__type__
};
```

The result is close to what we want, but not quite. Take a look at two of the results:

```{
"age": null,
"name": "Emil Sinclair",
"number": null,
"__type__": {"id": "4a007f07-f91f-11ed-8096-7bf54ff85912"}
}
{
"age": null,
"name": "The Captain",
"number": null,
"__type__": {"id": "48b9bb2f-faaa-11ed-966c-6fc3482a7805"}
}
```

The `id` property shows us that they are two different types, but the type name isn’t readable. To fix this, we can add the `name` property after `__type__` to display this instead of the id.

Copy
```select <json>Person {
name,
[is NPC].age,
[is Crewman].number,
__type__: {
name
}
};
```

And now the two objects from out previous output have human-readble names.

```{"age": null, "name": "Emil Sinclair", "number": null, "__type__": {"name": "default::PC"}}
{"age": null, "name": "The Captain", "number": null, "__type__": {"name": "default::Sailor"}}
```

So what is `__type__`, exactly? Well, it’s a link that all objects have that are used to describe it. You can see this if you type `describe type PC as text;` (or with any other object in the schema). Inside the description returned you’ll see this:

```required single link __type__: schema::ObjectType {
};
```

Interesting! So it’s just an object that can be queried like any other. Let’s give it a try with `PC` and the splat operator to see everything inside:

Copy
```select PC.__type__ {*};
```

This will show all of the properties for `ObjectType`, including the `name`:

```{
schema::ObjectType {
id: c7c1983a-268c-11ee-8c82-c79bbe432a02,
name: 'default::PC',
internal: false,
builtin: false,
computed_fields: [],
final: false,
is_final: false,
abstract: false,
is_abstract: false,
inherited_fields: [],
from_alias: false,
is_from_alias: false,
expr: {},
compound_type: false,
is_compound_type: false,
},
}
```

So that can be pretty useful.

But if you really want to understand the inner workings of EdgeDB, try the same query with the double splat operator:

```select PC.__type__ {**};
```

This will return pages and pages of information. You’ll see a link called `pointers` that points to just about everything: a link to `__type__`, a link to `strength`, a link to `is_single` and that it is a computable made from the expression `not exists .lover`…and so on and so on. If you want to get a good feel for how EdgeDB works on the inside, definitely grab a cup of coffee and give this query a try!

The official name for a type that gets extended by another type is a `supertype` (meaning ‘above type’). The types that extend them are their `subtypes` (‘below types’). You can visualize it like this:

• abstract type Person (supertype, above)

• ↳ type PC (subtype, under)

• ↳ type NPC (subtype, under)

Because inheriting a type gives you all of its features, a query on objects with `subtype is supertype` will always return `{true}`. In our schema a `PC` object is always a `Person`, and an `NPC` object is always a `Person`.

Conversely, `supertype is subtype` will return `{true}` or `{false}` depending on the concrete type of each object returned. A `Person` object might be a `PC` object, and it might be an `NPC` object.

To make a query that will check this, just add a shape query with the computed property `Person is PC` and EdgeDB will tell you:

Copy
```select Person {
name,
is_PC := Person is PC,
};
```

The output will look like this:

```{"name": "Emil Sinclair", "is_PC": true}
{"name": "Vampire Woman 1", "is_PC": false}
{"name": "Vampire Woman 2", "is_PC": false}
# ... and so on
```

Now how about the simpler scalar types? It’s nice that EdgeDB is strict about type safety and has different types for integers, floats and so on, but what if you just want to know if a number is an integer or a float? We could check to see if an integer is one of any integer types, but this makes for a pretty awkward query:

Copy
```with year := 1893,
select year is int16 or year is int32 or year is int64;
```

Output: `{true}`.

But fortunately these types all extend from abstract types too, and we can use them. These abstract types all start with `any`, and are: `anytype`, `anyscalar`, `anyenum`, `anytuple`, `anyint`, `anyfloat`, `anyreal`. The only one with an unclear name is `anyreal`: this one means any real number, so both integers and floats, plus the `decimal` type.

So with that you can change the above input to `select 1893 is anyint;` and get `{true}`.

Practice Time
1. How would you select all the `Place` types and their names, plus the `doors` property if it’s a `Castle`?

2. How would you select `Place` types with `city_name` for `name` if it’s a `City` and `country_name` for `name` if it’s a `Country`?

3. How would you do the same but only showing the results of `City` and `Country` types?

4. How would you display all the `Person` types that don’t have `lover`s, with their names and their type names?

5. What needs to be fixed in this query? Hint: two things definitely need to be fixed, while one more should probably be changed to make it more readable.

Copy
```select Place {
__type__,
name
[is Castle]doors
};
```