Skip to content

Convert selecting a named tuple into a free object #776

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

Open
wants to merge 15 commits into
base: master
Choose a base branch
from
Open
3 changes: 2 additions & 1 deletion .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ module.exports = {
// aren't imported as 'import type' in other parts of the generated
// querybuilder, so set this option to ensure we always do that
"@typescript-eslint/consistent-type-imports": "error",
"@typescript-eslint/no-namespace": "off"
"@typescript-eslint/no-namespace": "off",
"@typescript-eslint/no-inferrable-types": "off",
}
};
121 changes: 105 additions & 16 deletions integration-tests/lts/select.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,86 @@ describe("select", () => {
);
});

test("named tuple as free object", async () => {
const namedTuple = e.tuple({
object: e.select(e.Hero, () => ({ limit: 1 })),
score: e.random(),
});

const query = e.select(namedTuple);
type Query = $infer<typeof query>;
tc.assert<
tc.IsExact<
Query,
{
object: { id: string };
score: number;
}[]
>
>(true);
});

test("named tuple as free object with shape", async () => {
const namedTuple = e.tuple({
object: e.select(e.Hero, () => ({ limit: 1 })),
score: e.random(),
});
const withShape = e.select(namedTuple, (q) => {
return {
name: q.object.name,
score: q.score,
};
});

type WithShape = $infer<typeof withShape>;
tc.assert<
tc.IsExact<
WithShape,
{
name: string;
score: number;
}[]
>
>(true);

const result = await withShape.run(client);
assert.equal(result.length, 1);
assert.ok(typeof result[0].name === "string");
assert.ok(typeof result[0].score === "number");
});

test("named tuple to free object", async () => {
const namedTuple = e.tuple({
object: e.select(e.Hero, () => ({ limit: 1 })),
score: e.random(),
});
const freeObject = e.for(e.select(namedTuple), (item) =>
e.select({ object: item.object, score: item.score })
);

const query = e.select(freeObject, (scope) => ({
name: scope.object.name,
score: scope.score,
}));

type Query = $infer<typeof query>;
tc.assert<
tc.IsExact<
Query,
{
name: string;
score: number;
}[]
>
>(true);

const result = await query.run(client);

assert.equal(result.length, 1);
assert.ok(result[0].name);
assert.ok(result[0].score);
});

test("computed only shape", () => {
const query = e.select(e.Hero, (hero) => ({
upper_name: e.str_upper(hero.name),
Expand All @@ -107,25 +187,18 @@ describe("select", () => {
tc.assert<tc.IsExact<$infer<typeof query>, { upper_name: string }[]>>(true);
});

const q1 = e.select(e.Hero, () => ({
id: true,
secret_identity: true,
name: 1 > 0,
villains: {
test("complex shape", () => {
const q1 = e.select(e.Hero, () => ({
id: true,
secret_identity: true,
name: 1 > 0,
villains: {
id: true,
computed: e.str("test"),
},
computed: e.str("test"),
},
computed: e.str("test"),
}));

type q1 = $.setToTsType<typeof q1>;

test("path construction", () => {
const result = e.select(e.default.Hero);
assert.equal(result.villains.nemesis.name.__element__.__name__, "std::str");
});
}));

test("complex shape", () => {
type q1type = $.BaseTypeToTsType<(typeof q1)["__element__"]>;
tc.assert<
tc.IsExact<
Expand All @@ -144,6 +217,11 @@ describe("select", () => {
>(true);
});

test("path construction", () => {
const result = e.select(e.default.Hero);
assert.equal(result.villains.nemesis.name.__element__.__name__, "std::str");
});

test("deep shape", () => {
const deep = e.select(e.Hero, () => ({
id: true,
Expand Down Expand Up @@ -180,6 +258,17 @@ describe("select", () => {
});

test("compositionality", () => {
const q1 = e.select(e.Hero, () => ({
id: true,
secret_identity: true,
name: 1 > 0,
villains: {
id: true,
computed: e.str("test"),
},
computed: e.str("test"),
}));

// selecting a select statement should
// default to { id }
const no_shape = e.select(q1);
Expand Down
97 changes: 97 additions & 0 deletions integration-tests/stable/fts.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
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";

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.",
];
describe("full-text search", () => {
let client: Client;
beforeAll(async () => {
const setup = await setupTests();
({ client } = setup);
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 });
});

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

test("basic fts", async () => {
const searchExpr = e.fts.search(e.Post, "search");

const allQuery = e.select(searchExpr, ($) => ({
text: $.object.text,
score: $.score,
}));
type All = $infer<typeof allQuery>;
const all = await allQuery.run(client);

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

tc.assert<
tc.IsExact<
All,
{
text: string;
score: number;
}[]
>
>(true);

const noShapeQuery = e.select(searchExpr);
type NoShape = $infer<typeof noShapeQuery>;
tc.assert<
tc.IsExact<
NoShape,
{
object: { id: string };
score: number;
}[]
>
>(true);
});

test("fts with filter", async () => {
const searchExpr = e.fts.search(e.Post, "search");
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);
});

test("fts with sub-select and order by", async () => {
const searchExpr = e.fts.search(e.Post, "search");
const objectSelectQuery = e.select(searchExpr, (search) => ({
text: search.object.text,
order_by: search.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
);
});
});
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: [`${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
9 changes: 6 additions & 3 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 @@ -315,13 +316,15 @@ export function $pathify<Root extends TypeSet, Parent extends PathParent>(
return _root as any;
}

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 Expand Up @@ -448,7 +451,7 @@ export function $getScopedExpr<T extends ExpressionRoot>(
expr.__element__.__name__ === "std::FreeObject";

scopedExpr = isFreeObject
? (expr as any as Expression<TypeSet<BaseType, Cardinality>>)
? (expr as unknown as Expression<TypeSet<BaseType, Cardinality>>)
: $expressionify({
...expr,
__cardinality__: Cardinality.One,
Expand Down
Loading