diff --git a/package.json b/package.json index 1702267c7..5095672ba 100644 --- a/package.json +++ b/package.json @@ -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 ", diff --git a/qb/playground.ts b/qb/playground.ts index 3cfac521c..4169452f0 100644 --- a/qb/playground.ts +++ b/qb/playground.ts @@ -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(); diff --git a/qb/test/sets.test.ts b/qb/test/sets.test.ts index a161318f3..6570a92ff 100644 --- a/qb/test/sets.test.ts +++ b/qb/test/sets.test.ts @@ -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); @@ -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(); }); diff --git a/src/reflection/generators/generateSetImpl.ts b/src/reflection/generators/generateSetImpl.ts index 3c1bbc820..31e4c1932 100644 --- a/src/reflection/generators/generateSetImpl.ts +++ b/src/reflection/generators/generateSetImpl.ts @@ -42,6 +42,7 @@ import type { LooseTypeSet, } from "./set";`, ]); + code.addImport({getSharedParent: true}, "./set", true, ["ts", "js"]); code.nl(); code.writeln([ @@ -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(", ")}\` - ); + `; + }`, ]); diff --git a/src/reflection/hydrate.ts b/src/reflection/hydrate.ts index 906b7833a..141503f86 100644 --- a/src/reflection/hydrate.ts +++ b/src/reflection/hydrate.ts @@ -7,6 +7,7 @@ import { ObjectTypePointers, LinkDesc, PropertyDesc, + TupleType, } from "./typesystem"; import {typeutil, util} from "./util/util"; @@ -266,3 +267,13 @@ export function $mergeObjectTypes( }; return obj as any; } + +export function $mergeTupleTypes( + a: A, + b: B +): TupleType { + if (a.__items__.length !== b.__items__.length) { + throw new Error("Incompatible tuple types; lengths differ."); + } + return {} as TupleType; +} diff --git a/src/reflection/typesystem.ts b/src/reflection/typesystem.ts index 00eb6cc01..1826a7718 100644 --- a/src/reflection/typesystem.ts +++ b/src/reflection/typesystem.ts @@ -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, diff --git a/src/syntax/set.ts b/src/syntax/set.ts index 975eb622c..8e8db3d4e 100644 --- a/src/syntax/set.ts +++ b/src/syntax/set.ts @@ -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"; @@ -43,12 +147,12 @@ export type getSharedParentPrimitive = A extends undefined ? A : A extends ArrayType ? B extends ArrayType - ? ArrayType> + ? ArrayType> : never : A extends NamedTupleType ? B extends NamedTupleType ? NamedTupleType<{ - [k in keyof AShape & keyof BShape]: getSharedParentScalar< + [k in keyof AShape & keyof BShape]: castMaps.getSharedParentScalar< AShape[k], BShape[k] >; @@ -60,7 +164,7 @@ export type getSharedParentPrimitive = A extends undefined ? TupleType> : never : never - : getSharedParentScalar; + : castMaps.getSharedParentScalar; type _getSharedParentPrimitiveVariadic = Types extends [infer U]