Skip to content

Commit

Permalink
Support arrays and tuples in set constructor (#307)
Browse files Browse the repository at this point in the history
* Support implicit casting of arrays and tuples

* Fix lint
  • Loading branch information
colinhacks committed Mar 24, 2022
1 parent 621eb28 commit 8d74f80
Show file tree
Hide file tree
Showing 7 changed files with 215 additions and 60 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "edgedb",
"version": "0.19.15",
"version": "0.19.16",
"description": "The official Node.js client library for EdgeDB",
"homepage": "https://edgedb.com/docs",
"author": "EdgeDB <info@edgedb.com>",
Expand Down
20 changes: 7 additions & 13 deletions qb/playground.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,22 +16,16 @@ async function run() {
console.log(asd[3]);

const {client} = await setupTests();
const query = e
.insert(e.Movie, {
title: "The Avengers",
rating: 11,
})
.unlessConflict(movie => ({
on: e.tuple([movie.title, movie.profile, movie.id]),
else: e.update(movie, () => ({
set: {
rating: 11,
},
})),
}));
const query = e.set(
e.tuple({a: 1, b: "asdf", c: e.int16(214)}),
e.tuple({a: 3, b: "asdf", c: e.int64(5)})
);

console.log(query.toEdgeQL());
const result = await query.run(client);
console.log(result);

// e.literal(e.tuple({a: e.int16}), )
}

run();
Expand Down
65 changes: 64 additions & 1 deletion qb/test/sets.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,20 @@
import {$} from "edgedb";
import {$, Client} from "edgedb";
import {tc} from "./setupTeardown";
import e, {$infer} from "../dbschema/edgeql-js";
import {setupTests, teardownTests, TestData} from "./setupTeardown";
import {TypeKind} from "edgedb/dist/reflection";

let client: Client;
let data: TestData;

beforeAll(async () => {
const setup = await setupTests();
({client, data} = setup);
});

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

test("empty sets", () => {
expect(e.set()).toEqual(null);
Expand Down Expand Up @@ -112,4 +126,53 @@ test("enums", () => {
expect(query.toEdgeQL()).toEqual(
`{ default::Genre.Action, default::Genre.Horror }`
);

expect(() => e.set(e.Genre.Action, e.sys.VersionStage.dev as any)).toThrow();
});

test("tuples", async () => {
const q1 = e.set(
e.tuple([1, "asdf", e.int16(214)]),
e.tuple([3, "asdf", e.int64(5)])
);
expect(q1.__element__.__kind__).toEqual(TypeKind.tuple);
expect(q1.__element__.__items__[0].__name__).toEqual("std::number");
expect(q1.__element__.__items__[1].__name__).toEqual("std::str");
expect(await q1.run(client)).toMatchObject([
[1, "asdf", 214],
[3, "asdf", 5],
]);

expect(() => e.set(e.tuple([1]), e.tuple([1, 2]))).toThrow();
expect(() => e.set(e.tuple([1]), e.tuple(["asdf"]))).toThrow();
});

test("named tuples", async () => {
const q1 = e.set(
e.tuple({a: 1, b: "asdf", c: e.int16(214)}),
e.tuple({a: 3, b: "asdf", c: e.int64(5)})
);
expect(q1.__element__.__kind__).toEqual(TypeKind.namedtuple);
expect(await q1.run(client)).toMatchObject([
{a: 1, b: "asdf", c: 214},
{a: 3, b: "asdf", c: 5},
]);

expect(() => e.set(e.tuple({a: 1}), e.tuple({a: "asfd"}))).toThrow();
expect(() => e.set(e.tuple({a: 1}), e.tuple({a: "asfd", b: "qwer"})));
expect(() =>
e.set(e.tuple({a: "asfd", b: "qwer"}), e.tuple({a: 1}))
).toThrow();
expect(() => e.set(e.tuple({a: 1}), e.tuple({b: "asfd"}))).toThrow();
});

test("array", async () => {
const q1 = e.set(e.array([e.int16(5), e.int64(67)]), e.array([6]));
expect(q1.__element__.__kind__).toEqual(TypeKind.array);

expect(await q1.run(client)).toEqual([[5, 67], [6]]);

expect(() =>
e.set(e.array([e.int16(5)]), e.array(["asdf"]) as any)
).toThrow();
});
57 changes: 16 additions & 41 deletions src/reflection/generators/generateSetImpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ import type {
LooseTypeSet,
} from "./set";`,
]);
code.addImport({getSharedParent: true}, "./set", true, ["ts", "js"]);

code.nl();
code.writeln([
Expand Down Expand Up @@ -159,50 +160,24 @@ import type {
const exprs`,
ts`: TypeSet[]`,
r` = _exprs.map(expr => castMaps.literalToTypeSet(expr));
if (exprs.every((expr) => expr.__element__.__kind__ === TypeKind.object)) {
// merge object types;
return $expressionify({
__kind__: ExpressionKind.Set,
__element__: exprs
.map((expr) => expr.__element__`,
ts` as any`,
r`)
.reduce($mergeObjectTypes),
__cardinality__: cardinalityUtil.mergeCardinalitiesVariadic(
exprs.map((expr) => expr.__cardinality__)`,
ts` as any`,
r`
),
__exprs__: exprs,
})`,
ts` as any`,
r`;
}
if (exprs.every((expr) => expr.__element__.__kind__ !== TypeKind.object)) {
return $expressionify({
__kind__: ExpressionKind.Set,
__element__: exprs
.map((expr) => expr.__element__`,
return $expressionify({
__kind__: ExpressionKind.Set,
__element__: exprs
.map(expr => expr.__element__`,
ts` as any`,
r`)
.reduce(castMaps.getSharedParentScalar),
__cardinality__: cardinalityUtil.mergeCardinalitiesVariadic(
exprs.map((expr) => expr.__cardinality__)`,
`)
.reduce(getSharedParent),
__cardinality__: cardinalityUtil.mergeCardinalitiesVariadic(
exprs.map(expr => expr.__cardinality__)`,
ts` as any`,
r`
),
__exprs__: exprs,
})`,
`
),
__exprs__: exprs,
})`,
ts` as any`,
r`;
}
throw new Error(
\`Invalid arguments to set constructor: \${(_exprs`,
ts` as TypeSet[]`,
r`)
.map((expr) => expr.__element__.__name__)
.join(", ")}\`
);
`;
}`,
]);

Expand Down
11 changes: 11 additions & 0 deletions src/reflection/hydrate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
ObjectTypePointers,
LinkDesc,
PropertyDesc,
TupleType,
} from "./typesystem";

import {typeutil, util} from "./util/util";
Expand Down Expand Up @@ -266,3 +267,13 @@ export function $mergeObjectTypes<A extends ObjectType, B extends ObjectType>(
};
return obj as any;
}

export function $mergeTupleTypes<A extends TupleType, B extends TupleType>(
a: A,
b: B
): TupleType {
if (a.__items__.length !== b.__items__.length) {
throw new Error("Incompatible tuple types; lengths differ.");
}
return {} as TupleType;
}
8 changes: 8 additions & 0 deletions src/reflection/typesystem.ts
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,14 @@ export type PropertyTypes =
| TupleType
| NamedTupleType;

export type SomeType =
| ScalarType
| EnumType
| ArrayType
| TupleType
| ObjectType
| NamedTupleType;

export interface PropertyDesc<
Type extends BaseType = BaseType,
Card extends Cardinality = Cardinality,
Expand Down
112 changes: 108 additions & 4 deletions src/syntax/set.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,115 @@ import type {
ObjectType,
Cardinality,
getPrimitiveBaseType,
SomeType,
} from "../reflection";
import {TypeKind, $mergeObjectTypes} from "../reflection";

// "@generated/" path gets replaced during generation step
// @ts-ignore
import {getSharedParentScalar} from "../castMaps";
import * as castMaps from "../castMaps";

export function getSharedParent(a: SomeType, b: SomeType): SomeType {
if (a.__kind__ !== b.__kind__) {
throw new Error(
`Incompatible array types: ${a.__name__} and ${b.__name__}`
);
}
if (a.__kind__ === TypeKind.scalar && b.__kind__ === TypeKind.scalar) {
return castMaps.getSharedParentScalar(a, b);
} else if (
a.__kind__ === TypeKind.object &&
b.__kind__ === TypeKind.object
) {
return $mergeObjectTypes(a, b);
} else if (a.__kind__ === TypeKind.tuple && b.__kind__ === TypeKind.tuple) {
if (a.__items__.length !== b.__items__.length) {
throw new Error(
`Incompatible tuple types: ${a.__name__} and ${b.__name__}`
);
}
try {
const items = a.__items__.map((_, i) => {
if (!a.__items__[i] || !b.__items__[i]) {
throw new Error();
}
return getSharedParent(
a.__items__[i] as SomeType,
b.__items__[i] as SomeType
);
});

return {
__kind__: TypeKind.tuple,
__name__: `tuple<${items.map(item => item.__name__).join(", ")}>`,
__items__: items as BaseTypeTuple,
};
} catch (err) {
throw new Error(
`Incompatible tuple types: ${a.__name__} and ${b.__name__}`
);
}
} else if (
a.__kind__ === TypeKind.namedtuple &&
b.__kind__ === TypeKind.namedtuple
) {
const aKeys = Object.keys(a);
const bKeys = new Set(Object.keys(b));
const sameKeys =
aKeys.length === bKeys.size && aKeys.every(k => bKeys.has(k));
if (!sameKeys) {
throw new Error(
`Incompatible tuple types: ${a.__name__} and ${b.__name__}`
);
}
try {
const items: {[k: string]: BaseType} = {};
for (const [i] of Object.entries(a.__shape__)) {
if (!a.__shape__[i] || !b.__shape__[i]) {
throw new Error();
}
items[i] = getSharedParent(
a.__shape__[i] as SomeType,
b.__shape__[i] as SomeType
);
}

return {
__kind__: TypeKind.namedtuple,
__name__: `tuple<${Object.entries(items)
.map(([key, val]: [string, any]) => `${key}: ${val.__name__}`)
.join(", ")}>`,
__shape__: items,
};
} catch (err) {
throw new Error(
`Incompatible tuple types: ${a.__name__} and ${b.__name__}`
);
}
} else if (a.__kind__ === TypeKind.array && b.__kind__ === TypeKind.array) {
try {
const mergedEl: any = getSharedParent(a.__element__, b.__element__);
return {
__kind__: TypeKind.array,
__name__: a.__name__,
__element__: mergedEl,
} as ArrayType;
} catch (err) {
throw new Error(
`Incompatible array types: ${a.__name__} and ${b.__name__}`
);
}
} else if (a.__kind__ === TypeKind.enum && b.__kind__ === TypeKind.enum) {
if (a.__name__ === b.__name__) return a;
throw new Error(
`Incompatible array types: ${a.__name__} and ${b.__name__}`
);
} else {
throw new Error(
`Incompatible array types: ${a.__name__} and ${b.__name__}`
);
}
}

// @ts-ignore
export {set} from "./setImpl";
Expand All @@ -43,12 +147,12 @@ export type getSharedParentPrimitive<A, B> = A extends undefined
? A
: A extends ArrayType<infer AEl>
? B extends ArrayType<infer BEl>
? ArrayType<getSharedParentScalar<AEl, BEl>>
? ArrayType<castMaps.getSharedParentScalar<AEl, BEl>>
: never
: A extends NamedTupleType<infer AShape>
? B extends NamedTupleType<infer BShape>
? NamedTupleType<{
[k in keyof AShape & keyof BShape]: getSharedParentScalar<
[k in keyof AShape & keyof BShape]: castMaps.getSharedParentScalar<
AShape[k],
BShape[k]
>;
Expand All @@ -60,7 +164,7 @@ export type getSharedParentPrimitive<A, B> = A extends undefined
? TupleType<mergeTypeTuples<AItems, BItems>>
: never
: never
: getSharedParentScalar<A, B>;
: castMaps.getSharedParentScalar<A, B>;

type _getSharedParentPrimitiveVariadic<Types extends [any, ...any[]]> =
Types extends [infer U]
Expand Down

0 comments on commit 8d74f80

Please sign in to comment.