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

Allow NamedTuple as select expression #767

Closed
wants to merge 15 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
25 changes: 25 additions & 0 deletions integration-tests/lts/select.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -500,6 +500,31 @@ describe("select", () => {
);
});

test("named tuples", async () => {
const namedTuple = e.tuple({ foo: e.str("bar") });
const result = await e.select(namedTuple).run(client);
assert.deepEqual(result, { foo: "bar" });

const pathResult = await e.select(namedTuple.foo).run(client);
assert.deepEqual(pathResult, "bar");

const nestedObjectTuple = e.for(
e.enumerate(e.select(e.Hero)),
(enumeration) =>
e.tuple({
hero: enumeration[1],
index: enumeration[0],
})
);
const nestedObjectQuery = e.select(nestedObjectTuple.hero, (hero) => ({
name: hero.name,
order_by: nestedObjectTuple.index,
}));
const nestedObjectResult = await nestedObjectQuery.run(client);
assert.equal(nestedObjectResult.length, 3);
assert.ok(nestedObjectResult.every((r) => Boolean(r.name)));
});

test("filter by id", async () => {
const result = await e
.select(e.Hero, () => ({
Expand Down
84 changes: 84 additions & 0 deletions integration-tests/stable/fts.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import type { Client } from "edgedb";
import e, { type $infer } from "./dbschema/edgeql-js";
import { setupTests, tc, teardownTests } from "./setupTeardown";
import { $Post } from "./dbschema/edgeql-js/modules/default";

describe("full-text search", () => {
let client: Client;
beforeAll(async () => {
const setup = await setupTests();
({ client } = setup);
});

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

test("basic fts", async () => {
const posts = [
"Full-text search is a technique for searching text content. It works by storing every unique word that appears in a document.",
"To perform a full-text search, the search engine examines all the words in the specified document or set of documents.",
"The process of full-text search begins with the user entering a string of characters (the search string).",
"The search engine then retrieves all instances of the search string in the document or collection of documents.",
"Full-text search can be used in many applications that require search capability, such as web search engines, document management systems, and digital libraries.",
];
const inserted = await e
.params({ posts: e.array(e.str) }, (params) => {
return e.for(e.array_unpack(params.posts), (post) =>
e.insert(e.Post, { text: post })
);
})
.run(client, { posts });

const searchExpr = e.select(e.fts.search(e.Post, "search"));

const allQuery = e.select(searchExpr, () => ({
object: true,
score: true,
}));
const all = await allQuery.run(client);

expect(all.length).toBe(inserted.length);

tc.assert<
tc.IsExact<
$infer<typeof allQuery>,
{
object: { id: string };
score: number;
}[]
>
>(true);

const filteredQuery = e.select(searchExpr, (post) => ({
object: true,
score: true,
filter: e.op(post.score, ">", e.float64(0.81)),
}));
const filtered = await filteredQuery.run(client);

expect(filtered.length).toBe(1);

const noShapeQuery = e.select(searchExpr);
const noShape = await noShapeQuery.run(client);

expect(noShape).toEqual(all);

const objectSelectQuery = e.select(searchExpr.object, (post) => ({
text: post.text,
order_by: searchExpr.score,
}));
const objectSelect = await objectSelectQuery.run(client);
expect(objectSelect).toEqual([
{ text: posts[0] },
{ text: posts[1] },
{ text: posts[2] },
{ text: posts[3] },
{ text: posts[4] },
]);

tc.assert<tc.IsExact<$infer<typeof objectSelectQuery>, { text: string }[]>>(
true
);
});
});
3 changes: 2 additions & 1 deletion integration-tests/stable/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
"jest": "^29.5.0",
"superjson": "^1.12.4",
"ts-jest": "^29.1.0",
"typescript": "^5.2.2"
"typescript": "^5.2.2",
"edgedb": "^1.4.1"
},
"dependencies": {}
}
2 changes: 1 addition & 1 deletion packages/generate/src/edgeql-js/generateObjectTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ export const getStringRepresentation: (
}
if (type.name === "anyobject") {
return {
staticType: [`$.AnyObjectType`],
staticType: frag`${params.anytype ?? "$.AnyObjectType"}`,
runtimeType: [],
};
}
Expand Down
6 changes: 5 additions & 1 deletion packages/generate/src/syntax/hydrate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,11 @@ export function makeType<T extends BaseType>(
): T {
const type = spec.get(id);

if (type.name === "anytype" || type.name === "std::anypoint") {
if (
type.name === "anytype" ||
type.name === "std::anypoint" ||
type.name === "anyobject"
) {
if (anytype) return anytype as unknown as T;
throw new Error("anytype not provided");
}
Expand Down
13 changes: 7 additions & 6 deletions packages/generate/src/syntax/path.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import { $toEdgeQL } from "./toEdgeQL";
import { $queryFunc, $queryFuncJSON } from "./query";

import type {
$expr_TuplePath,
BaseType,
Expression,
LinkDesc,
Expand Down Expand Up @@ -308,20 +309,20 @@ const pathifyProxyHandlers: ProxyHandler<any> = {
},
};

export function $pathify<Root extends TypeSet, Parent extends PathParent>(
_root: Root
): $pathify<Root> {
export function $pathify<Root extends TypeSet>(_root: Root): $pathify<Root> {
if (_root.__element__.__kind__ !== TypeKind.object) {
return _root as any;
return _root as $pathify<Root>;
}

const root: $expr_PathNode<ObjectTypeSet> = _root as any;
const root = _root as unknown as
| $expr_PathNode<ObjectTypeSet>
| $expr_TuplePath<ObjectType>;

let pointers = {
...root.__element__.__pointers__,
};

if (root.__parent__) {
if (root.__parent__ && root.__kind__ !== ExpressionKind.TuplePath) {
const { type, linkName } = root.__parent__;
const parentPointer = type.__element__.__pointers__[linkName];
if (parentPointer?.__kind__ === "link") {
Expand Down
47 changes: 43 additions & 4 deletions packages/generate/src/syntax/select.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@ import type {
BaseType,
ExclusiveTuple,
orLiteralValue,
NamedTupleType,
$expr_TuplePath,
} from "./typesystem";

import {
Expand Down Expand Up @@ -111,7 +113,9 @@ export type exclusivesToFilterSingle<E extends ExclusiveTuple> =
: orLiteralValue<E[j][k]>;
};
}[number];
export type SelectModifiers<T extends ObjectType = ObjectType> = {
export type SelectModifiers<
T extends ObjectType | NamedTupleType = ObjectType
> = {
// export type SelectModifiers = {
filter?: SelectFilterExpression;
filter_single?: // | Partial<
Expand Down Expand Up @@ -143,7 +147,9 @@ export type SelectModifiers<T extends ObjectType = ObjectType> = {
// : never
// : never;
// }>)
exclusivesToFilterSingle<T["__exclusives__"]> | SelectFilterExpression;
T extends ObjectType
? exclusivesToFilterSingle<T["__exclusives__"]> | SelectFilterExpression
: never;

// | (ObjectType extends T
// ? unknown
Expand Down Expand Up @@ -368,7 +374,7 @@ export type InferOffsetLimitCardinality<
// Modifiers
// >;
export type ComputeSelectCardinality<
Expr extends ObjectTypeExpression,
Expr extends ObjectTypeExpression | TypeSet<NamedTupleType, Cardinality>,
Modifiers extends UnknownSelectModifiers
> = InferOffsetLimitCardinality<
undefined extends Modifiers["filter_single"]
Expand Down Expand Up @@ -780,6 +786,21 @@ export type objectTypeToSelectShape<T extends ObjectType = ObjectType> =
: any;
}> & { [k: string]: unknown };

export type namedTupleTypeToSelectShape<
T extends NamedTupleType = NamedTupleType
> = Partial<
{
[k in keyof T["__shape__"]]: T["__shape__"][k] extends PropertyDesc
?
| boolean
| TypeSet<
T["__shape__"][k]["target"],
cardutil.assignable<T["__shape__"][k]["cardinality"]>
>
: any;
} & { [k: string]: unknown }
>;

// incorporate __shape__ (computeds) on selection shapes
// this works but a major rewrite of setToTsType is required
// to incorporate __shape__-based selection shapes into
Expand Down Expand Up @@ -902,6 +923,21 @@ export function select<
>;
__cardinality__: ComputeSelectCardinality<Expr, Modifiers>;
}>;

export function select<
Expr extends TypeSet<NamedTupleType, Cardinality>,
Shape extends namedTupleTypeToSelectShape<Expr["__element__"]> &
SelectModifiers<Expr["__element__"]>,
Modifiers extends UnknownSelectModifiers = Pick<Shape, SelectModifierNames>
>(
expr: Expr,
shape: (
scope: $expr_TuplePath<Expr["__element__"], Cardinality.One>
) => Readonly<Shape>
): $expr_Select<{
__element__: NamedTupleType<Expr["__element__"]["__shape__"]>;
__cardinality__: ComputeSelectCardinality<Expr, Modifiers>;
}>;
/*

For the moment is isn't possible to implement both closure-based and plain
Expand Down Expand Up @@ -1117,7 +1153,10 @@ function resolveShape(
} else {
// for scalar expressions, scope === expr
// shape keys are not allowed
if (expr.__element__.__kind__ !== TypeKind.object) {
if (
expr.__element__.__kind__ !== TypeKind.object &&
expr.__element__.__kind__ !== TypeKind.namedtuple
) {
throw new Error(
`Invalid select shape key '${key}' on scalar expression, ` +
`only modifiers are allowed (filter, order_by, offset and limit)`
Expand Down
5 changes: 4 additions & 1 deletion packages/generate/src/syntax/toEdgeQL.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,13 +149,16 @@ export function $toEdgeQL(this: any) {
if (withVars.has(expr)) {
continue;
}

// ignore unbound leaves, nodes, and intersections
// these should be rendered as is
if (
!refData.boundScope &&
(expr.__kind__ === ExpressionKind.PathLeaf ||
expr.__kind__ === ExpressionKind.PathNode ||
expr.__kind__ === ExpressionKind.TuplePath ||
(expr.__kind__ === ExpressionKind.Select &&
(expr.__expr__ as SomeExpression).__kind__ ===
ExpressionKind.TuplePath) ||
expr.__kind__ === ExpressionKind.TypeIntersection)
) {
continue;
Expand Down