Skip to content

Commit

Permalink
Add more documentation for @dataplan/pg; minor fixes (#372)
Browse files Browse the repository at this point in the history
  • Loading branch information
benjie committed Jun 14, 2023
2 parents d83c9ba + 8bb14f2 commit b583ee1
Show file tree
Hide file tree
Showing 26 changed files with 1,451 additions and 30 deletions.
6 changes: 6 additions & 0 deletions .changeset/dirty-jeans-reflect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
"@dataplan/pg": patch
---

Remove pgSelectSingle.expression; use the equivalent method
pgSelectSingle.select instead.
5 changes: 5 additions & 0 deletions .changeset/four-squids-deny.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"graphile-export": patch
---

Optimize away even more IIFEs
8 changes: 7 additions & 1 deletion grafast/dataplan-pg/src/datasource.ts
Original file line number Diff line number Diff line change
Expand Up @@ -806,27 +806,31 @@ export class PgResource<
return this.executor.context();
}

/** @internal */
public executeWithCache<TInput = any, TOutput = any>(
values: GrafastValuesList<PgExecutorInput<TInput>>,
options: PgExecutorOptions,
): Promise<{ values: GrafastValuesList<ReadonlyArray<TOutput>> }> {
return this.executor.executeWithCache(values, options);
}

/** @internal */
public executeWithoutCache<TInput = any, TOutput = any>(
values: GrafastValuesList<PgExecutorInput<TInput>>,
options: PgExecutorOptions,
): Promise<{ values: GrafastValuesList<ReadonlyArray<TOutput>> }> {
return this.executor.executeWithoutCache(values, options);
}

/** @internal */
public executeStream<TInput = any, TOutput = any>(
values: GrafastValuesList<PgExecutorInput<TInput>>,
options: PgExecutorOptions,
): Promise<{ streams: GrafastResultStreamList<TOutput> }> {
return this.executor.executeStream(values, options);
}

/** @internal */
public executeMutation<TData>(
options: PgExecutorMutationOptions,
): Promise<PgClientResult<TData>> {
Expand All @@ -838,6 +842,8 @@ export class PgResource<
* non-null and `'false'` or `null` otherwise.
*
* @see {@link PgCodec.notNullExpression}
*
* @internal
*/
public getNullCheckExpression(alias: SQL): SQL | null {
if (this.codec.notNullExpression) {
Expand Down Expand Up @@ -1294,7 +1300,7 @@ exportAs("@dataplan/pg", makeRegistryBuilder, "makeRegistryBuilder");
export function makePgResourceOptions<
const TResourceOptions extends PgResourceOptions<any, any, any, any>,
>(options: TResourceOptions) {
return { ...options };
return options;
}

exportAs("@dataplan/pg", makePgResourceOptions, "makePgResourceOptions");
11 changes: 11 additions & 0 deletions grafast/dataplan-pg/src/steps/pgSelect.ts
Original file line number Diff line number Diff line change
Expand Up @@ -863,13 +863,17 @@ export class PgSelectStep<

/**
* @experimental Please use `singleRelation` or `manyRelation` instead.
*
* @internal
*/
public join(spec: PgSelectPlanJoin) {
this.joins.push(spec);
}

/**
* Select an SQL fragment, returning the index the result will have.
*
* @internal
*/
public selectAndReturnIndex(fragment: SQL): number {
if (!this.isArgumentsFinalized) {
Expand All @@ -895,6 +899,7 @@ export class PgSelectStep<
}

private nullCheckIndex: number | null | undefined = undefined;
/** @internal */
public getNullCheckIndex(): number | null {
// PERF: if this isn't coming from a function _and_ it's not being inlined
// via a left-join or similar then we shouldn't need this and should be
Expand All @@ -917,11 +922,14 @@ export class PgSelectStep<
* Finalizes this instance and returns a mutable clone; useful for
* connections/etc (e.g. copying `where` conditions but adding more, or
* pagination, or grouping, aggregates, etc)
*
* @internal
*/
clone(mode?: PgSelectMode): PgSelectStep<TResource> {
return new PgSelectStep(this, mode);
}

/** @internal */
connectionClone(
$connection: ConnectionStep<any, any, any, any>,
mode?: PgSelectMode,
Expand Down Expand Up @@ -1152,6 +1160,7 @@ and ${sql.indent(sql.parens(condition(i + 1)))}`}
this.where(finalCondition);
}

/** @internal */
parseCursor(
$cursorPlan: __InputStaticLeafStep<string>,
): PgSelectParsedCursorStep | null {
Expand All @@ -1174,6 +1183,7 @@ and ${sql.indent(sql.parens(condition(i + 1)))}`}
this.beforeStepId = this.addDependency($parsedCursorPlan);
}

/** @internal */
public pageInfo(
$connectionPlan: ConnectionStep<any, PgSelectParsedCursorStep, this, any>,
): PgPageInfoStep<this> {
Expand Down Expand Up @@ -2334,6 +2344,7 @@ ${lateralText};`;
});
}

/** @internal */
public deduplicatedWith(replacement: PgSelectStep<TResource>): void {
if (
typeof this.symbol === "symbol" &&
Expand Down
20 changes: 5 additions & 15 deletions grafast/dataplan-pg/src/steps/pgSelectSingle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,9 @@ export class PgSelectSingleStep<
return colPlan as any;
}

/**
* Returns a plan representing the result of an expression.
*/
public select<TExpressionCodec extends PgCodec>(
fragment: SQL,
codec: TExpressionCodec,
Expand Down Expand Up @@ -392,19 +395,6 @@ export class PgSelectSingleStep<
)`${this.getClassStep().alias}`;
}

/**
* Returns a plan representing the result of an expression.
*/
expression<TExpressionCodec extends PgCodec>(
expression: SQL,
codec: TExpressionCodec,
): PgClassExpressionStep<TExpressionCodec, TResource> {
return pgClassExpression<TExpressionCodec, TResource>(
this,
codec,
)`${expression}`;
}

/**
* @internal
* For use by PgCursorStep
Expand All @@ -421,10 +411,10 @@ export class PgSelectSingleStep<
o,
this.getClassStep().resource.codec,
);
return this.expression(frag, codec);
return this.select(frag, codec);
})
: // No ordering; so use row number
[this.expression(sql`row_number() over (partition by 1)`, TYPES.int)],
[this.select(sql`row_number() over (partition by 1)`, TYPES.int)],
);
return [digest, step];
}
Expand Down
40 changes: 38 additions & 2 deletions grafast/website/grafast/step-library/dataplan-pg/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,42 @@ sidebar_position: 10
# @dataplan/pg

This collection of steps gives incredible performance when dealing with
Postgres.
Postgres directly.

TODO: document the steps!
To operate, `@dataplan/pg` requires an understanding of your database. To do
so, it uses the concepts of [codecs](./registry/codecs) (which represent the types in
your database - both scalar and composite), [resources](./registry/resources) (which
represent the sources of data inside your database - tables, views, functions,
etc), and [relations](./registry/relations) (which represent links between codecs and
the resources they relate to). These three things together combine into the
[registry](./registry/).

Once you have your registry, you can utilise it to fetch rows from tables/views
(via `resource.get()` (one) or `resource.find()` (many)), call database
functions (via `resource.execute()`), or perform mutations (via
`pgInsertSingle()`, `pgUpdateSingle()` or `pgDeleteSingle()`).

Thanks to Gra*fast*'s planning system and `@dataplan/pg`'s understanding of
your database, as you use the methods on the steps that represent your
tables to access their attributes, traverse their relations, set their
columns, etc; `@dataplan/pg` can look at the actions you're taking and compile
the most efficient SQL query or queries in order fulfill your requirements with
minimal database effort. This all happens behind the scenes without you having
to think about it (though should you wish to dig deeper, for example to deal
with a performance edge-case, we do give you the tools to influence it).

`@dataplan/pg` was designed to be easy to work with; although it isn't an ORM
(dealing with "steps" that represent the nodes in a Gra*fast* plan diagram,
rather than dealing with the concrete runtime data) it has helpers for all
the most common actions you'll need, plus APIs to allow you to add your own
SQL expressions and sources should you need to.

:::important

Reference to `sql` in code examples is a reference to `import { sql } from
"pg-sql2";` - `@dataplan/pg` makes heavy use of this performant, type-safe,
injection-proof SQL builder.

:::

Lets get started by looking at building the registry.
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
sidebar_position: 3
---

# pgClassExpression

A pgClassExpression represents the value of an SQL expression extracted from a
`pgSelectSingle`, `pgUnionAllSingle` or similar step.

You won't construct a pgClassExpression directly, normally you'll get it from
`$pgSelectSingle.select(...)`, `resource.execute(...)` or similar methods.

## pgClassExpression.get(attr)

The most commonly used method on a `pgClassExpression`, this gets a step
representing the value of the given attribute from the expression; only
applicable when the expression represents a composite type.

## Not opaque

Unlike a `pgSelectSingle`, a `pgClassExpression` is _not_ opaque, so it's fine
to use it directly as a dependency of any other step.
48 changes: 48 additions & 0 deletions grafast/website/grafast/step-library/dataplan-pg/pgCondition.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
# pgCondition

This "modifier step" (**not** an ExecutableStep) is commonly acquired from
`$pgSelect.wherePlan()`, `$pgSelect.havingPlan()`, or similar methods. It's
useful for building up a condition (`WHERE` or `HAVING` clause) bit by bit.

:::tip

This is an advanced step, you probably will never use it unless you're building
advanced filtering capabilities into your GraphQL schema.

:::

pgConditions are created with a parent (a PgConditionCapableStep - typically another PgConditionStep, a PgSelectStep, or similar) and a mode:

- `PASS_THRU` - passes conditions directly up to the parent
- `AND` - combines conditons with `AND` and passes the result up to the parent
- `OR` - combines the conditions with `OR` and passes the result up to the parent
- `NOT` - combines the conditons with `AND`, groups them together and does a `NOT` of the result, which is then passed up to the parent
- `EXISTS` - builds an `EXISTS(...)` expression utilising the conditions and passes it up to the parent

## $pgCondition.orPlan()

Returns a child pgCondition in `OR` mode.

## $pgCondition.andPlan()

Returns a child pgCondition in `AND` mode.

## $pgCondition.notPlan()

Returns a child pgCondition in `NOT` mode.

## $pgCondition.existsPlan(options)

Returns a child pgCondition in `EXISTS` mode with the given options.

## $pgCondition.where(condition)

Adds `condition` to the list of conditions. Cannot be used in "having" mode.

## $pgCondition.having(condition)

Adds `condition` to the list of conditions. Cannot be used unless in "having" mode.

## $pgCondition.placeholder($step, codec)

Equivalent to `$pgSelect.placeholder($step, codec)`
24 changes: 24 additions & 0 deletions grafast/website/grafast/step-library/dataplan-pg/pgDeleteSingle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# pgDeleteSingle

Deletes a single row from the given `resource`, identified by the given unique
spec (similar to the spec you'd pass to `resource.get(...)`).

```ts
const $deletedUser = pgDeleteSingle(usersResource, {
id: $id,
});
```

## $pgDeleteSingle.get(attr)

Returns a PgClassExpressionStep representing the given attribute from the
deleted row. This is achieved by selecting the value using the
`DELETE FROM ... WHERE ... RETURNING ...` syntax.

```ts
const $username = $deletedUser.get("username");
```

## $pgDeleteSingle.record()

Returns a PgClassExpressionStep representing the full record that was deleted.
43 changes: 43 additions & 0 deletions grafast/website/grafast/step-library/dataplan-pg/pgInsertSingle.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# pgInsertSingle

Inserts a single row into the given `resource`, setting the given attributes (if any).

```ts
const $insertedUser = pgInsertSingle(usersResource, {
username: $username,
});
```

## $pgInsertSingle.set(attr, $value)

Adds another attribute to be inserted:

```ts
const $insertedUser = pgInsertSingle(usersResource);
$insertedUser.set("username", $username);
$insertedUser.set("bio", $bio);

// Roughly equivalent to:
// `INSERT INTO users (username, bio) VALUES ($1, $2);`
```

## $pgInsertSingle.setPlan()

Returns a `SetterStep` (a "modifier step", rather than an `ExecutableStep`)
that can be useful when combined with `applyPlan` plan resolvers in arguments
and input fields to build up the attributes to set on the inserted row bit by
bit.

## $pgInsertSingle.get(attr)

Returns a PgClassExpressionStep representing the given attribute from the
inserted row. This is achieved by selecting the value using the
`INSERT INTO ... RETURNING ...` syntax.

```ts
const $id = $insertedUser.get("id");
```

## $pgInsertSingle.record()

Returns a PgClassExpressionStep representing the full record that was inserted.
Loading

0 comments on commit b583ee1

Please sign in to comment.