跳至主要内容

pgSelect 和 pgSelectSingle

虽然您通常不会手动创建 pgSelect()pgSelectSingle() 步骤(而是使用相关资源上的 .get().find().execute() 方法),但它们是您在 @dataplan/pg 中最常与之交互的步骤类。

pgSelect

pgSelect() 代表从数据库中选择的一行。您可能会在这个例子中获得一个 pgSelect 实例,例如 $pgSelect

// `pgSelect()` step representing all the messages in forum 1:
const $pgSelect = registry.pgResources.messages.find({ forum_id: constant(1) });

一旦您可以访问 pgSelect() 步骤,您就可以使用它的各种方法来添加条件、重新排序、添加分页限制等等。

信息

您只能在创建 pgSelect() 的字段规划时进行这些更改。一旦该字段规划完成(通过 Grafastdeduplicate 生命周期事件),对条件、排序等的任何更改都可能导致您的模式违反 GraphQL 规范,因此这些操作是被禁止的。

危险

如果您在突变计划解析器中使用 pgSelect,并且该 pgSelect 具有副作用(例如调用 VOLATILE 数据库函数),那么请确保代表该函数的 资源 正确配置了 isMutation: true,并且您是通过 resource.execute() 创建该步骤的。(如果未执行此操作,则在某些情况下,该步骤可能会被树状摇动,因此副作用可能永远不会发生。)

(或者,如果直接创建 pgSelect,则使用 mode="mutation" 创建,或设置 $pgSelect.hasSideEffects = true;。)

下面我们记录了最常用的属性和方法,完整的文档请通过 TypeScript 探索方法及其类型。

请注意,通常在内部使用或通过辅助步骤(如 connection())使用的方法没有记录,因为您通常不会自己调用这些方法。

$pgSelect.alias

由于同一个表可能在最终查询中出现多次(在查询被内联/合并后),每个 $pgSelect 都被赋予自己的别名。别名是表示底层表的 SQL 片段,方便您引用表的列或类似内容。

const $users = usersResource.find();
const tbl = $users.alias;
$users.where(sql`${tbl}.username = 'Benjie'`);

$pgSelect.single()

如果此计划可能只返回一条记录,您可以使用 $pgSelect.single() 返回一个解析为该记录(在复合类型的情况下)或底层标量(在编解码器没有属性的资源的情况下)的计划。

注意:如果您调用此方法,而数据库实际上可能返回多条记录,那么您可能会遇到糟糕的情况。

// Instead of:
// const $user = usersResource.get({ id: constant(1) });
// you could do:
const $user = usersResource.find({ id: constant(1) }).single();

$pgSelect.singleAsRecord()

您很可能想要使用 .single();只有在您特别需要 PgSelectSingleStep 实例,即使您正在查询标量时,才使用此方法。

$pgSelect.row($row)

您可以使用此方法根据 $pgSelect 中的任意条目创建 pgSelectSingle 实例,例如,要从 $pgSelect 中获取第一行并将其设置为 pgSelectSingle,您可以执行以下操作

const $row = $pgSelect.row(first($pgSelect));

$pgSelect.orderBy(spec)

在查询中添加排序子句。可以多次调用此方法以添加更多排序子句,这些子句始终附加到现有列表中。

参数可以是属性规范,一个具有以下属性的对象

  • attribute(必需) - 要排序的属性的名称,必须是 $pgSelect 表示的编解码器上的属性之一
  • callback(可选) - 用于将属性包装在表达式中
  • direction(必需) - "ASC""DESC"
  • nulls(可选) - "FIRST""LAST"null

或片段规范,一个具有以下属性的对象

  • fragment(必需) - 要排序的 SQL 表达式
  • codec (必需) - 与 fragment 中表达式类型匹配的编解码器
  • direction(必需) - "ASC""DESC"
  • nulls(可选) - "FIRST""LAST"null

示例

// Get a pgSelect:
const $users = usersResource.find();

// Sort by username length
$users.orderBy({
attribute: "username",
callback(usernameExpression, codec) {
return [sql`length(${usernameExpression})`, TYPES.int];
},
direction: "ASC",
nulls: "LAST",
});

// Sort within that by user id
$users.orderBy({ attribute: "id", direction: "ASC" });

// Result is something like:
// `SELECT ... FROM users ORDER BY length(username) ASC NULLS LAST, id ASC`

$pgSelect.setOrderIsUnique()

如果您确定您指定的顺序足以确保排序没有歧义(即它是稳定的),请调用此方法。如果您不这样做,我们可能会(如果需要唯一顺序,例如用于游标分页)将主键或类似的唯一约束添加到排序中,以使其稳定。

$pgSelect.where(condition)

向查询添加 WHERE 子句,可以多次调用,条件将使用 AND 追加。

const $users = usersResource.find();
const tbl = $users.alias;
$users.where(sql`${tbl}.username = 'Benjie'`);

$pgSelect.placeholder($step, codec)

占位符接受任意步骤和表示其 SQL 类型应是什么的编解码器(如果步骤已包含编解码器的详细信息,则可选),并返回一个 SQL 表达式,允许在 SQL 查询中引用步骤的值。

const $users = usersResource.find();
const tbl = $users.alias;

const $username = fieldArgs.get("username");
const frag = $users.placeholder($username, TYPES.citext);

$users.where(sql`${tbl}.username = ${frag}`);

$pgSelect.singleRelation(relationName)

强制 $pgSelect 对给定关系进行左连接,并为您提供一个表示此连接的别名;这对于构建相关表上的条件很有用。

请注意,pgSelect(集合)上的此方法与 pgSelectSingle(行)上的同名方法不同,后者返回一个表示相关记录的步骤。

示例:“返回所有论坛未归档的帖子”

const $posts = postsResource.find();
const forumAlias = $posts.singleRelation("forum");
$posts.where(sql`${forumAlias}.is_archived = false`);
return $posts;

// Result is something like:
// `SELECT ... FROM posts LEFT JOIN forums ON (...) WHERE forums.is_archived = false`
信息

如果关系不唯一,则会抛出错误。

$pgSelect.wherePlan()

这种高级方法不会直接添加条件,而是返回一个 PgConditionStep(一个“修饰符步骤”),允许以不同的方式构建条件。如果您正在构建深度过滤参数,使用 applyPlan 计划解析器处理参数和输入字段,这将特别有用。

$pgSelect.setFirst($n)

限制返回的记录数量。

目前,此方法必须使用输入步骤调用(即,它必须来自 GraphQL 字段参数或是一个常量)。

$pgSelect.setLast($n)

限制返回的记录数量,但不是取列表中的第一个条目,而是取最后一个条目。

目前,此方法必须使用输入步骤调用(即,它必须来自 GraphQL 字段参数或是一个常量)。

$pgSelect.setOffset($n)

跳过给定数量的记录,不能与 setLast 结合使用。

目前,此方法必须使用输入步骤调用(即,它必须来自 GraphQL 字段参数或是一个常量)。

$pgSelect.groupBy(spec)

指示查询添加 GROUP BY 子句。目前,spec 必须是一个具有以下属性的对象

  • fragment(必需) - 一个 SQL 片段,用于指示要 GROUP BY 的内容

$pgSelect.having(condition)

类似于 $pgSelect.where(condition),但用于分组查询的 HAVING 子句,并且只支持 SQL 片段条件形式。

TODO: 此方法未经测试!

$pgSelect.havingPlan()

类似于 $pgSelect.wherePlan(),但用于 HAVING 子句。

TODO: 此方法未经测试!

$pgSelect.setUnique()

仅在最多可能存在一行匹配行时调用此方法。如果您在不满足此条件的情况下将此设置为 true,则在内联期间可能会得到意外的结果;如有疑问,请将其保留为默认值。

$pgSelect.hasMore()

返回一个步骤,指示是否存在下一页(通过选择 1 行额外行并将其丢弃)。通常,这由 connection() 步骤的 pageInfo 逻辑使用,您不会自己调用它。

$pgSelect.setInliningForbidden()

如果您希望阻止此查询内联到其父级中,请调用此方法。您可能希望这样做来解决性能问题,或提高步骤的可缓存性。

$pgSelect.setTrusted()

默认情况下,所有 pgSelect 步骤都会应用其资源指定的授权检查,例如应用类似于行级安全性的条件。如果您希望绕过此行为,例如您知道条件会通过因为父级是可见的,则调用此方法。

const plans = {
Forum: {
posts($forum) {
const $posts = postsResource.find({ forum_id: $forum.get("id") });

// If we can see the forum, then we can see all the posts inside the
// forum, so don't bother adding the access conditions:
$posts.setTrusted();

return $posts;
},
},
};

pgSelectSingle

pgSelectSingle() 代表 pgSelect() 集合中的单个行。通常您不会直接构建此实例,而是从 resource.get()resource.execute()(对于 isUnique 函数)、从 pgSelect 中列表的条目或通过 $pgSelect.single() 获取它。

以下是一些常用的方法

$pgSelectSingle.get(attr)

pgSelectSingle 上最常用的方法,它获取一个代表行中给定属性值的步骤。

信息

您必须始终使用 $pgSelectSingle.get(attr) 而不是 access($pgSelectSingle, attr) 或类似方法,原因如下

  1. pgSelectSingle 包含的数据是一个不可预测的元组(参见下面的 不透明步骤),
  2. .get(attr) 会告诉相关的 pgSelect 步骤将 attr 添加到要 SELECT 的表达式列表中

$pgSelectSingle.select(fragment, codec)

返回一个代表给定 SQL 表达式值的 PgClassExpressionStep。

const $user = usersResource.find({ id: constant(1) });
const $usernameLength = $user.select(sql`length(username)`, TYPES.int);

$pgSelectSingle.placeholder($step, codec)

与底层 pgSelect 步骤上的 $pgSelect.placeholder($step, codec) 相同。

$pgSelectSingle.singleRelation(relationName)

返回一个代表通过 relationName 关系相关的单个记录的 pgSelectSingle

不要与 $pgSelect.singleRelation 混淆。

$pgSelectSingle.manyRelation(relationName)

返回一个代表通过 relationName 关系相关的记录的 pgSelect

$pgSelectSingle.record()

返回一个表示整个表的 PgClassExpressionStep,用于调试或与 pgSelectSingleFromRecord 一起使用。

$pgSelectSingle.cursor()

返回一个表示此行游标的步骤,通常用于连接边中的 cursor 字段。

$pgSelectSingle.getClassStep()

返回此 $pgSelectSingle 所来自的 PgSelectStep。用于获取 alias 等信息。

pgSelectFromRecords(resource, $records)

构建一个 pgSelect 步骤,表示 $records 中的多行数据,就好像它们来自 resource 资源一样。

pgSelectFromRecord(resource, $record)

使用 $record 中的单行数据构建一个 pgSelect 步骤,就好像它来自 resource 资源一样。

pgSelectSingleFromRecord(resource, $record)

等效于 pgSelectFromRecord(resource, $record).single()

不透明步骤

pgSelectpgSelectSingle 是我们所说的“不透明步骤” - 也就是说,您不打算直接使用它们的基础数据,而是使用它们的方法来提取您需要与其他步骤一起使用的数据。

目前,pgSelectSingle 不使用您可能期望的对象表示,而是使用一个元组,其中包含每个选定属性的条目。此元组的构成将根据您请求的属性以及顺序而有所不同,因此您不能依赖其结构。要获取属性,您应该使用 $pgSelectSingle.get(attr) 或类似方法。