edgedb-python icon indicating copy to clipboard operation
edgedb-python copied to clipboard

Polymorphic splats in QB

Open vpetrovykh opened this issue 5 months ago • 0 comments

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,
    )
)

vpetrovykh avatar Jul 07 '25 11:07 vpetrovykh