Skip to content

Commit

Permalink
Select star (#332)
Browse files Browse the repository at this point in the history
* Implement select star
  • Loading branch information
colinhacks committed Apr 20, 2022
1 parent 14e8155 commit 89f621e
Show file tree
Hide file tree
Showing 8 changed files with 107 additions and 26 deletions.
52 changes: 37 additions & 15 deletions docs/select.rst
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
.. _edgedb-js-select:

Select
------
======

The full power of the EdgeQL ``select`` statement is available as a top-level
``e.select`` function. All queries on this page assume the Netflix schema
described on the :ref:`Objects page <edgedb-js-objects>`.

Selecting scalars
^^^^^^^^^^^^^^^^^
-----------------

Any scalar expression be passed into ``e.select``, though it's often
unnecessary, since expressions are ``run``\ able without being wrapped by
Expand All @@ -23,7 +23,7 @@ unnecessary, since expressions are ``run``\ able without being wrapped by
// select 2 + 2;
Selecting free objects
^^^^^^^^^^^^^^^^^^^^^^
----------------------

Select a free object by passing an object into ``e.select``

Expand All @@ -41,7 +41,7 @@ Select a free object by passing an object into ``e.select``
} */
Selecting objects
^^^^^^^^^^^^^^^^^
-----------------

As in EdgeQL, selecting an set of objects without a shape will return their
``id`` property only. This is reflected in the TypeScript type of the result.
Expand Down Expand Up @@ -91,7 +91,6 @@ This is true for all queries on this page.
As you can see, the type of ``runtime`` is ``Duration | undefined`` since it's
an optional property, whereas ``id`` and ``title`` are required.


Passing a ``boolean`` value (as opposed to a ``true`` literal), which will
make the property optional. Passing ``false`` will exclude that property.

Expand All @@ -106,7 +105,31 @@ make the property optional. Passing ``false`` will exclude that property.
const result = await query.run(client);
// {id: string; title: string | undefined; runtime: never}[]
Selecting all properties
^^^^^^^^^^^^^^^^^^^^^^^^

For convenience, the query builder provides a shorthand for selecting all
properties of a given object.

.. code-block:: typescript
e.select(e.Movie, movie => ({
...e.Movie['*']
}));
const result = await query.run(client);
// {id: string; title: string; runtime: Date}[]
This ``*`` property is just a strongly-typed, plain object:

.. code-block::
e.Movie['*'];
// => {id: true, title: true, runtime: true}
Nesting shapes
^^^^^^^^^^^^^^

As in EdgeQL, shapes can be nested to fetch deeply related objects.

Expand All @@ -129,7 +152,7 @@ As in EdgeQL, shapes can be nested to fetch deeply related objects.
Why closures?
^^^^^^^^^^^^^
-------------

In EdgeQL, a ``select`` statement introduces a new *scope*; within the clauses
of a select statement, you can refer to fields of the *elements being
Expand All @@ -151,7 +174,7 @@ computed fields, and other expressions. Let's see it in action.


Filtering
^^^^^^^^^
---------

To add a filtering clause, just include a ``filter`` key in the returned
params object. This should correspond to a boolean expression.
Expand All @@ -176,7 +199,8 @@ params object. This should correspond to a boolean expression.
EdgeDB, there is minimal danger of conflicting with a property or link named
``filter``. All shapes can contain filter clauses, even nested ones.

### Nested filtering
Filters on links
----------------

.. code-block:: typescript
Expand All @@ -191,7 +215,7 @@ params object. This should correspond to a boolean expression.
Ordering
^^^^^^^^
--------

As with ``filter``, you can pass a value with the special ``order_by`` key. To
simply order by a property:
Expand All @@ -202,8 +226,6 @@ simply order by a property:
order_by: movie.title,
}));
.. note::

Unlike ``filter``, ``order_by`` is *not* a reserved word in EdgeDB. Using
Expand Down Expand Up @@ -277,7 +299,7 @@ Pass an array of objects to do multiple ordering.
Pagination
^^^^^^^^^^
----------

Use ``offset`` and ``limit`` to paginate queries. You can pass an expression
with an integer type or a plain JS number.
Expand All @@ -295,7 +317,7 @@ with an integer type or a plain JS number.
*/
Computeds
^^^^^^^^^
---------

To add a computed field, just add it to the returned shape alongside the other
elements. All reflected functions are typesafe, so the output type
Expand Down Expand Up @@ -339,7 +361,7 @@ signatures agree.
.. _ref_qb_polymorphism:

Polymorphism
^^^^^^^^^^^^
------------

EdgeQL supports polymorphic queries using the ``[is type]`` prefix.

Expand Down Expand Up @@ -373,7 +395,7 @@ fact that they will only occur in certain objects.


Detached
^^^^^^^^
--------

Sometimes you need to "detach" a set reference from the current scope. (Read the `reference docs <https://www.edgedb.com/docs/reference/edgeql/with#detached>`_ for details.) You can achieve this in the query builder with the top-level ``e.detached`` function.

Expand Down
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.20.7",
"version": "0.20.8",
"description": "The official Node.js client library for EdgeDB",
"homepage": "https://edgedb.com/docs",
"author": "EdgeDB <info@edgedb.com>",
Expand Down
8 changes: 3 additions & 5 deletions qb/playground.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,9 @@ import e, * as types from "./dbschema/edgeql-js/index";

async function run() {
const {client, data} = await setupTests();
const query = e.update(e.Movie, movie => ({
filter: e.op(movie.id, "=", e.uuid(data.the_avengers.id)),
set: {
title: "The Avngrrs",
},
console.log(e.Movie["*"]);
const query = e.select(e.Movie, movie => ({
...e.Movie["*"],
}));

console.log(query.toEdgeQL());
Expand Down
24 changes: 24 additions & 0 deletions qb/test/objectTypes.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,3 +117,27 @@ test("backlinks", () => {
"default::Movie"
);
});

test("select *", () => {
const movieStar = e.Movie["*"];

expect(movieStar).toEqual({
id: true,
title: true,
genre: true,
rating: true,
release_year: true,
});
tc.assert<
tc.IsExact<
typeof movieStar,
{
id: true;
title: true;
genre: true;
rating: true;
release_year: true;
}
>
>(true);
});
14 changes: 14 additions & 0 deletions qb/test/select.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1120,6 +1120,20 @@ test("filter by sequence", async () => {
await e.op(e.Bag.seqField, "=", 1).run(client);
});

test("select *", async () => {
const allFields = await e
.select(e.Movie, movie => ({
...e.Movie["*"],
filter: e.op(movie.title, "=", data.the_avengers.title),
}))
.run(client);

expect(allFields).toEqual({
...data.the_avengers,
characters: undefined,
});
});

// Modifier methods removed for now, until we can fix typescript inference
// problems / excessively deep errors

Expand Down
6 changes: 4 additions & 2 deletions qb/test/setupTeardown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ interface Movie {
id: string;
title: string;
genre: string;
rating: number | null;
release_year: number;
characters: {id: string}[];
}

Expand Down Expand Up @@ -73,7 +75,7 @@ SELECT (INSERT Movie {
rating := 10,
genre := Genre.Action,
characters := (SELECT Person FILTER .id IN char_ids)
}) {id, title, rating, genre, characters: {id}};`,
}) {id, title, rating, genre, release_year, characters: {id}};`,
{character_ids: [iron_man.id, cap.id]}
);
const civil_war: Movie = await client.queryRequiredSingle(
Expand All @@ -82,7 +84,7 @@ SELECT (INSERT Movie {
rating := 10,
genre := Genre.Action,
characters := (SELECT Hero)
}) {id, title, rating, genre, characters: {id}};`
}) {id, title, rating, genre, release_year, characters: {id}};`
);

return {
Expand Down
11 changes: 10 additions & 1 deletion src/reflection/path.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import {typeutil} from ".";
import {Cardinality, ExpressionKind} from "./enums";
import type {
BaseType,
Expand All @@ -11,6 +10,7 @@ import type {
PropertyShape,
TypeSet,
} from "./typesystem";
import {typeutil} from ".";
import {cardinalityUtil} from "./util/cardinalityUtil";

// get the set representing the result of a path traversal
Expand Down Expand Up @@ -142,6 +142,14 @@ type pathifyLinkProps<
: unknown;
};

export type getPropsShape<T extends ObjectType> = typeutil.flatten<
typeutil.stripNever<{
[k in keyof T["__pointers__"]]: T["__pointers__"][k]["__kind__"] extends "property"
? true
: never;
}>
>;

export type $expr_PathNode<
Root extends ObjectTypeSet = ObjectTypeSet,
Parent extends PathParent | null = PathParent | null,
Expand All @@ -152,6 +160,7 @@ export type $expr_PathNode<
__parent__: Parent;
__kind__: ExpressionKind.PathNode;
__exclusive__: Exclusive;
"*": getPropsShape<Root["__element__"]>;
}>;

export type $expr_TypeIntersection<
Expand Down
16 changes: 14 additions & 2 deletions src/syntax/path.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,26 @@ function PathNode<
exclusive: Exclusive,
scopeRoot: TypeSet | null = null
): $expr_PathNode<Root, Parent, Exclusive> {
return $expressionify({
const obj = {
__kind__: ExpressionKind.PathNode,
__element__: root.__element__,
__cardinality__: root.__cardinality__,
__parent__: parent,
__exclusive__: exclusive,
__scopeRoot__: scopeRoot,
}) as any;
};

const shape: any = {};
Object.entries(obj.__element__.__pointers__).map(([key, ptr]) => {
if (ptr.__kind__ === "property") {
shape[key] = true;
}
});
Object.defineProperty(obj, "*", {
writable: false,
value: shape,
});
return $expressionify(obj) as any;
}

const _pathCache = Symbol();
Expand Down

0 comments on commit 89f621e

Please sign in to comment.