Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Polymorphic queries -> discriminated union types #1090

Merged
merged 19 commits into from
Sep 30, 2024
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion integration-tests/lts/dbschema/default.esdl
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,11 @@ module default {
property character_name -> str;
}

abstract type Person {
abstract type LivingThing {
age: int32;
}

abstract type Person extending LivingThing {
required property name -> str {
constraint exclusive;
};
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
CREATE MIGRATION m173ddshjvgy5ampp7rzi2g7cwwdkaljvlifemr5vugnkimtbpp6ca
ONTO m1wb2dgjeppqex272zwvqnsdfzdvvppub4iwa5vaxu3xxigyjlruka
{
CREATE ABSTRACT TYPE default::LivingThing {
CREATE PROPERTY age: std::int32;
};
ALTER TYPE default::Person EXTENDING default::LivingThing LAST;
};
1 change: 1 addition & 0 deletions integration-tests/lts/interfaces.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ export interface BaseObject {
}
export interface test_Person extends BaseObject {
name: string;
age?: number | null;
height?: string | null;
isAdult?: boolean | null;
}
Expand Down
1 change: 1 addition & 0 deletions integration-tests/lts/objectTypes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,7 @@ describe("object types", () => {
assert.deepEqual(hero["*"], {
id: true,
name: true,
age: true,
height: true,
isAdult: true,
secret_identity: true,
Expand Down
186 changes: 133 additions & 53 deletions integration-tests/lts/select.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,15 @@ import * as $ from "../../packages/generate/src/syntax/reflection";
import e, { type $infer } from "./dbschema/edgeql-js";
import { setupTests, teardownTests, tc, type TestData } from "./setupTeardown";

declare module "./dbschema/edgeql-js/typesystem" {
export interface SetTypesystemOptions {
future: {
polymorphismAsDiscriminatedUnions: true;
strictTypeNames: true;
};
}
}

let client: edgedb.Client;
let data: TestData;

Expand Down Expand Up @@ -166,12 +175,12 @@ describe("select", () => {
{
id: string;
__type__: {
name: string;
name: "default::Hero";
__type__: {
id: string;
__type__: {
id: string;
name: string;
name: "schema::ObjectType";
};
};
};
Expand Down Expand Up @@ -239,18 +248,25 @@ describe("select", () => {
assert.deepEqual(query.__element__.__kind__, $.TypeKind.object);
assert.equal(query.__element__.__name__, "default::Person");

type result = $.BaseTypeToTsType<(typeof query)["__element__"]>;
type result = $infer<typeof query>[number];
tc.assert<
tc.IsExact<
result,
{
id: string;
name: string;
nemesis: {
name: string;
} | null;
secret_identity: string | null;
}
} & (
| {
__typename: "default::Hero";
secret_identity: string | null;
}
| {
__typename: "default::Villain";
nemesis: {
name: string;
} | null;
}
)
>
>(true);
});
Expand Down Expand Up @@ -287,17 +303,17 @@ describe("select", () => {
}),
}));

type q = $.setToTsType<typeof q>;
tc.assert<
tc.IsExact<
q,
{
id: string;
secret_identity: string | null;
type actual = $infer<typeof q>[number];
type expected = {
id: string;
} & (
| { __typename: "default::Hero"; secret_identity: string | null }
| {
__typename: "default::Villain";
nemesis: { id: string; computable: 1234 } | null;
}[]
>
>(true);
}
);
tc.assert<tc.IsExact<actual, expected>>(true);
});

test("parent type props in polymorphic", () => {
Expand All @@ -313,11 +329,17 @@ describe("select", () => {
tc.assert<
tc.IsExact<
$infer<typeof q>,
{
name: string | null;
secret_identity: string | null;
nemesis: { name: string } | null;
}[]
(
| {
__typename: "default::Hero";
name: string;
secret_identity: string | null;
}
| {
__typename: "default::Villain";
nemesis: { name: string } | null;
}
)[]
>
>(true);
});
Expand All @@ -327,18 +349,23 @@ describe("select", () => {
...e.is(e.Hero, e.Hero["*"]),
name: true,
}));
type result = $infer<typeof q>;

// 'id' is filtered out since it is not valid in a polymorphic expr
tc.assert<
tc.IsExact<
$infer<typeof q>,
{
name: string;
height: string | null;
isAdult: boolean | null;
number_of_movies: number | null;
secret_identity: string | null;
}[]
result,
({ name: string } & (
| { __typename: "default::Villain" }
| {
__typename: "default::Hero";
height: string | null;
age: number | null;
isAdult: boolean | null;
number_of_movies: number | null;
secret_identity: string | null;
}
))[]
>
>(true);

Expand All @@ -350,6 +377,24 @@ describe("select", () => {
tc.assert<tc.IsExact<typeof name, "default::Hero">>(true);
});

test("polymorphic type names", () => {
tc.assert<
tc.IsExact<
typeof e.LivingThing.__element__.__polyTypenames__,
"default::Hero" | "default::Villain"
>
>(true);
tc.assert<
tc.IsExact<
typeof e.Person.__element__.__polyTypenames__,
"default::Hero" | "default::Villain"
>
>(true);
tc.assert<
tc.IsExact<typeof e.Hero.__element__.__polyTypenames__, "default::Hero">
>(true);
});

test("limit/offset inference", () => {
const testSet = e.set(1, 2, 3);

Expand Down Expand Up @@ -790,18 +835,26 @@ describe("select", () => {
tc.assert<
tc.IsExact<
typeof result,
{
({
id: string;
title: string | null;
characters:
| {
} & (
| {
__typename: Exclude<
typeof e.Object.__element__.__polyTypenames__,
"default::Movie"
>;
}
| {
__typename: "default::Movie";
title: string;
characters: {
name: string;
"@character_name": string | null;
char_name: string | null;
person_name: string;
}[]
| null;
}[]
}[];
}
))[]
>
>(true);
});
Expand Down Expand Up @@ -986,6 +1039,7 @@ SELECT __scope_0_defaultVillain {
nemesis: (nemesis) => {
const nameLen = e.len(nemesis.name);
return {
t: nemesis.__type__.name,
name: true,
nameLen,
nameLen2: nameLen,
Expand Down Expand Up @@ -1021,13 +1075,15 @@ SELECT __scope_0_defaultPerson {
}
))
SELECT __scope_2_defaultHero {
single t := __scope_2_defaultHero.__type__.name,
name,
single nameLen := __scope_2_defaultHero.__withVar_3,
single nameLen2 := __scope_2_defaultHero.__withVar_3
}
)
}
)
),
__typename := .__type__.name
}`,
);

Expand All @@ -1036,25 +1092,29 @@ SELECT __scope_0_defaultPerson {
tc.assert<
tc.IsExact<
typeof res,
{
({
id: string;
name: string;
nemesis: {
id: string;
} | null;
secret_identity: string | null;
villains:
| {
} & (
| {
__typename: "default::Villain";
nemesis: { id: string } | null;
}
| {
__typename: "default::Hero";
secret_identity: string | null;
villains: {
id: string;
name: string;
nemesis: {
t: "default::Hero";
name: string;
nameLen: number;
nameLen2: number;
} | null;
}[]
| null;
}[]
}[];
}
))[]
>
>(true);
});
Expand All @@ -1076,10 +1136,12 @@ SELECT __scope_0_defaultPerson {
title: data.the_avengers.title,
characters: [
{
__typename: "default::Hero",
name: data.cap.name,
secret_identity: data.cap.secret_identity,
},
{
__typename: "default::Hero",
name: data.iron_man.name,
secret_identity: data.iron_man.secret_identity,
},
Expand All @@ -1091,10 +1153,15 @@ SELECT __scope_0_defaultPerson {
typeof result,
{
title: string;
characters: {
characters: ({
name: string;
secret_identity: string | null;
}[];
} & (
| { __typename: "default::Villain" }
| {
__typename: "default::Hero";
secret_identity: string | null;
}
))[];
} | null
>
>(true);
Expand Down Expand Up @@ -1351,6 +1418,7 @@ SELECT __scope_0_defaultPerson {

test("portable shape", async () => {
const baseShape = e.shape(e.Movie, (movie) => ({
__typename: movie.__type__.name,
title: true,
rating: true,
filter_single: e.op(movie.title, "=", "The Avengers"),
Expand All @@ -1374,6 +1442,7 @@ SELECT __scope_0_defaultPerson {
tc.IsExact<
ShapeType,
{
__typename: "default::Movie";
title: string;
rating: number | null;
} | null
Expand Down Expand Up @@ -1402,6 +1471,7 @@ SELECT __scope_0_defaultPerson {
tc.IsExact<
Q,
{
__typename: "default::Movie";
title: string;
rating: number | null;
characters: {
Expand Down Expand Up @@ -1513,7 +1583,17 @@ SELECT __scope_0_defaultPerson {
tc.assert<
tc.IsExact<
Result,
{ xy: { a: string | null; b: number | null } | null }[]
{
xy:
| ({ a: string | null } & (
| { __typename: "default::W" | "default::Y" }
| {
__typename: "default::X";
b: number | null;
}
))
| null;
}[]
>
>(true);
});
Expand Down
Loading