Skip to content

Commit

Permalink
1109 polymorphic types union (#1111)
Browse files Browse the repository at this point in the history
fixes #1109 

We allow union of multiple types in schema computed fields. Some members
of the union can extend some other common types, but not all members
have to.
  • Loading branch information
diksipav authored Oct 7, 2024
1 parent a14e811 commit 7774e24
Show file tree
Hide file tree
Showing 12 changed files with 370 additions and 50 deletions.
31 changes: 31 additions & 0 deletions integration-tests/stable/dbschema/default.esdl
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,19 @@ module default {

type User {
identity: ext::auth::Identity;

multi movies: Movie;
multi shows: Show;
multi documentaries: Documentary;

multi watching_list := (
select .movies union .shows
order by .year
);

multi all_media := (
select .movies union .shows union .documentaries
)
}

type Post {
Expand All @@ -24,4 +37,22 @@ module default {
type WithMultiRange {
required ranges: multirange<std::int32>;
}

abstract type Content {
required year: int16;
required title: str;
}

type Movie extending Content {
required plot: str;
}

type Show extending Content {
required seasons: int16;
}

type Documentary {
required title: str;
required plot: str;
}
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
CREATE MIGRATION m1vk6tojt7l6b3to5psw6kqzrkdfed773ysvabmblc6odywm33ztfa
ONTO m1n37rkzwbdej2nsfr73ujxlwjaum7zhqsaovf7l2sdw2g4dnayfqq
{
};
44 changes: 44 additions & 0 deletions integration-tests/stable/dbschema/migrations/00001-m1xym4i.edgeql
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
CREATE MIGRATION m1xym4iizk6uca3j6edxwxofb3z2ryclmnapjj24apuap2beaqd47a
ONTO initial
{
CREATE EXTENSION pgcrypto VERSION '1.3';
CREATE EXTENSION auth VERSION '1.0';
CREATE TYPE default::Documentary {
CREATE REQUIRED PROPERTY title: std::str;
CREATE REQUIRED PROPERTY plot: std::str;
};
CREATE ABSTRACT TYPE default::Content {
CREATE REQUIRED PROPERTY title: std::str;
CREATE REQUIRED PROPERTY year: std::int16;
};
CREATE TYPE default::Movie EXTENDING default::Content {
CREATE REQUIRED PROPERTY plot: std::str;
};
CREATE TYPE default::Show EXTENDING default::Content {
CREATE REQUIRED PROPERTY seasons: std::int16;
};
CREATE TYPE default::User {
CREATE MULTI LINK documentaries: default::Documentary;
CREATE MULTI LINK movies: default::Movie;
CREATE MULTI LINK shows: default::Show;
CREATE MULTI LINK all_media := (SELECT
((.movies UNION .shows) UNION .documentaries)
);
CREATE MULTI LINK watching_list := (SELECT
(.movies UNION .shows)
ORDER BY
.year ASC
);
CREATE LINK identity: ext::auth::Identity;
};
CREATE TYPE default::CryptoTest {
CREATE PROPERTY hash_sha256: std::bytes;
};
CREATE TYPE default::Post {
CREATE REQUIRED PROPERTY text: std::str;
CREATE INDEX fts::index ON (fts::with_options(.text, language := fts::Language.eng));
};
CREATE TYPE default::WithMultiRange {
CREATE REQUIRED PROPERTY ranges: multirange<std::int32>;
};
};
9 changes: 0 additions & 9 deletions integration-tests/stable/dbschema/migrations/00001.edgeql

This file was deleted.

22 changes: 0 additions & 22 deletions integration-tests/stable/dbschema/migrations/00002.edgeql

This file was deleted.

7 changes: 0 additions & 7 deletions integration-tests/stable/dbschema/migrations/00003.edgeql

This file was deleted.

2 changes: 1 addition & 1 deletion integration-tests/stable/edgedb.toml
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
[edgedb]
server-version = "4"
server-version = "5"
2 changes: 1 addition & 1 deletion integration-tests/stable/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"typecheck": "echo 'Integration tests - stable, skipping typecheck...'",
"build": "echo 'Integration tests, no build output...'",
"generate": "../../packages/generate/dist/cli.js",
"test": "NODE_OPTIONS=\"--experimental-vm-modules\" yarn generate edgeql-js && yarn generate queries --file && yarn generate interfaces && jest --detectOpenHandles --forceExit --passWithNoTests",
"test": "NODE_OPTIONS=\"--experimental-vm-modules\" yarn generate edgeql-js --future && yarn generate queries --file && yarn generate interfaces && jest --detectOpenHandles --forceExit --passWithNoTests",
"ci:integration-test": "tsx ./testRunner.ts"
},
"devDependencies": {
Expand Down
204 changes: 204 additions & 0 deletions integration-tests/stable/select.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
import type * as edgedb from "edgedb";
import assert from "node:assert/strict";

import e, { type $infer } from "./dbschema/edgeql-js";
import { setupTests, teardownTests, tc } from "./setupTeardown";

let client: edgedb.Client;

describe("select", () => {
beforeAll(async () => {
const setup = await setupTests();
({ client } = setup);
});

afterAll(async () => {
await teardownTests(client);
}, 10_000);

test("union of polymorphic types in computed schema field", () => {
const WatchingList = e.shape(e.User.watching_list, () => ({
year: true,
title: true,
...e.is(e.Movie, {
plot: true,
}),
...e.is(e.Show, {
seasons: true,
}),
}));

const q = e.select(e.User, () => ({
watching_list: WatchingList,
}));

type qT = $infer<typeof q>;

tc.assert<
tc.IsExact<
qT,
{
watching_list: (
| {
year: number;
title: string;
plot: string;
__typename: "default::Movie";
}
| {
year: number;
title: string;
seasons: number;
__typename: "default::Show";
}
)[];
}[]
>
>(true);
});

test("union of 3 types in computed schema field - two types extend, one doesn't, query the common field only", async () => {
const q = e.select(e.User, () => ({
all_media: {
title: true,
},
}));

type qT = $infer<typeof q>;

tc.assert<
tc.IsExact<
qT,
{
all_media: {
title: string;
}[];
}[]
>
>(true);

const result = await q.run(client);
const expected = [
{
all_media: [
{
title: "Inception",
},
{
title: "Friends",
},
{
title: "Free Solo",
},
],
},
];
assert.deepEqual(result, expected);
});

// TODO Fix type for Show in the result: Show doesn't have the plot
test("union of 3 types in computed schema field - two types extend, one doesn't", async () => {
const q = e.select(e.User, () => ({
all_media: (all_media) => ({
title: true,
...e.is(e.Content, { year: true }),
...e.is(e.Show, {
seasons: true,
}),
plot: e.op(
all_media.is(e.Movie).plot,
"??",
all_media.is(e.Documentary).plot,
),
}),
}));

type qT = $infer<typeof q>;

tc.assert<
tc.IsExact<
qT,
{
all_media: (
| {
title: string;
plot: string;
__typename: "default::Documentary";
}
| {
title: string;
plot: string;
year: number;
__typename: "default::Movie";
}
| {
title: string;
plot: string;
year: number;
seasons: number;
__typename: "default::Show";
}
)[];
}[]
>
>(true);

const result = await q.run(client);
const expected = [
{
all_media: [
{
__typename: "default::Movie",
plot: "Inception plot",
seasons: null,
title: "Inception",
year: 2010,
},
{
__typename: "default::Show",
plot: null,
seasons: 10,
title: "Friends",
year: 1994,
},
{
__typename: "default::Documentary",
plot: "Free Solo plot",
seasons: null,
title: "Free Solo",
year: null,
},
],
},
];
assert.deepEqual(result, expected);
});

test("a field cannot be selected as common if it exists in only one of the union types", async () => {
const q = e.select(e.User, () => ({
watching_list: {
year: true,
title: true,
plot: true,
},
}));

type qT = $infer<typeof q>;

tc.assert<
tc.IsExact<
qT,
{
watching_list: {
year: number;
title: string;
}[];
}[]
>
>(true);

return assert.rejects(async () => await q.run(client), {
message: `Field "plot" does not exist in default::Movie | default::Show`,
});
});
});
17 changes: 17 additions & 0 deletions integration-tests/stable/setupTeardown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,20 @@ export type TestData = depromisify<ReturnType<typeof setupTests>>["data"];

export async function setupTests() {
const client = createClient();

await client.query(`
insert User {
movies := {
(insert Movie { title := "Inception", year := 2010, plot := "Inception plot" })
},
shows := {
(insert Show { title := "Friends", year := 1994, seasons := 10 })
},
documentaries:= {
(insert Documentary { title := "Free Solo", plot := "Free Solo plot" })
}
}`);

return {
data: null,
client,
Expand All @@ -20,6 +34,9 @@ async function cleanupData(client: Client) {
delete User;
delete Post;
delete WithMultiRange;
delete Movie;
delete Show;
delete Documentary;
`);
}

Expand Down
2 changes: 2 additions & 0 deletions packages/driver/src/reflection/queries/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import type { Cardinality } from "../enums";
import type { UUID } from "./queryTypes";
import { StrictMap } from "../strictMap";

export { UUID };

export type Pointer = {
card: Cardinality;
kind: "link" | "property";
Expand Down
Loading

0 comments on commit 7774e24

Please sign in to comment.