edgedb / edgedb-js

The official TypeScript/JS client library and query builder for EdgeDB
https://edgedb.com
Apache License 2.0
513 stars 65 forks source link

Polymorphic shapes that share property names are being shadowed #630

Open scotttrinh opened 1 year ago

scotttrinh commented 1 year ago

In the case that a query has polymorphic shapes where multiple subtypes return a shape that share the same property name, only the last subtype actually gets built into the final query.

Example:

e.select(e.Content, content => ({
  title: true,
  ...e.is(e.Movie, { link: true }),
  ...e.is(e.TVShow, { link: true }),
}));

In the above example, only the e.TVShow polymorphic shape is built into the final expression object that is transformed into EdgeQL.

scotttrinh commented 1 year ago

As an aside, renaming the field using computed doesn't work due to how we look up the type:

https://github.com/edgedb/edgedb-js/blob/464b0988121e7183a8304b37014a096ff0d01895/packages/generate/src/syntax/typesystem.ts#L374-L383

We do not save the source key into the expression, so we match the key to the pointer's key.

So this does not work:

e.select(e.Content, content => ({
  title: true,
  movie_link: e.is(e.Movie, { link: true }).link,
  tv_show_link: e.is(e.TVShow, { link: true }).link,
}));

Since infer will look for e.Movie.movie_link instead of e.Movie.link in this case.

dotlouis commented 1 year ago

Quick workaround to mimic union types and solve the above problem using the coalescing operator. In EdgeQL

Select Content {
  release_year := [is Movie].release_year ?? [is TVShow].release_year
}

In query Builder (TS)

const q = e.select(e.Content, content => ({
        ...e.Content['*'],
        release_year: e.op(
          content.is(e.Movie).release_year,
          '??',
          content.is(e.TVShow).release_year,
        ),
      },
    }))

Other related issue : https://github.com/edgedb/edgedb/issues/1577