Polymorphic splats in QB
Polymorphic types are not friendly to plain * splats, as those will only include the common fields. So when dealing with polymorphic types we need to be able to use subtype-specific splats.
Consider this schema:
abstract type Foo {
prop: str;
}
type Bar extending Foo {
bar_prop: str;
}
type Baz extending Foo {
baz_prop: str;
}
type Kek {
pek: Foo;
}
Let's say we want the equivalent to select Kek{**}. In EdgeQL it would be something like this:
select Kek {
*,
pek: {
*,
[is Bar].*,
[is Baz].*,
}
}
We can't omit the * in pek, because without it both of the polymorphic splats try to include the common properties and that causes a clash and an error.
So the QB equivalent is:
from models import default
q = default.Kek.select(
"*",
pek=lambda k: k.pek.select(
"*",
*default.Bar,
*default.Baz,
)
)
This works OK for the above case, but not in general. This form only works when the sub-types don't share any property names and thus don't cause clashes.
If we had a slightly different schema:
abstract type Foo {
prop: str;
}
type Bar extending Foo {
extra_prop: str;
}
type Baz extending Foo {
extra_prop: str;
}
type Kek {
pek: Foo;
}
We would have an issue with extra_prop clashing and so we cannot use a splat here. In EdgeQL, we would have to resolve this manually with a coalesce:
select Kek {
*,
pek: {
*,
extra_prop :=
[is Bar].extra_prop ??
[is Baz].extra_prop,
}
}
This means that the QB needs to support referring to polymorpshic fields individually as part of expressions or as fields included in the select. I propose that we need a method for type intersection [is Bar] and we can call it when_type as a shorter and more intuitive form of "type intersection".
So this clash can be resolved like this:
q = default.Kek.select(
"*",
pek=lambda k: k.pek.select(
"*",
extra_prop=lambda foo: std.coalesce(
foo.when_type(default.Bar).extra_prop,
foo.when_type(default.Baz).extra_prop,
),
)
)
For the original schema, the query can be rewritten without splats:
q = default.Kek.select(
"*",
pek=lambda k: k.pek.select(
"*",
default.Bar.bar_prop,
default.Baz.baz_prop,
)
)
However, if we have when_type, then maybe we can also write this instead:
q = default.Kek.select(
"*",
pek=lambda k: k.pek.select(
"*",
k.when_type(default.Bar).bar_prop,
k.when_type(default.Baz).baz_prop,
)
)