14 分钟
GraphQL 模式
概述
您的子图的模式位于文件 schema.graphql。GraphQL模式是使用GraphQL 接口定义语言定义的。
注意:如果您从未写过GraphQL模式,建议您在GraphQL类型系统上查看此初级读物。 GraphQL模式的参考文档可以在 GraphQL API部分找到。
定义实体
在定义实体之前,重要的是回头一步,思考你的数据是如何架构和联系的。
- 所有查询都将根据子图模式定义的数据模型进行。 因此,子图模式的设计应通知您的应用程序需要执行的查询。
- 将实体想象成“含有数据的对象”,而不是作为事件或功能,可能是有用的。
- 您在
schema.graphql中定义实体类型, Graph节点将生成顶级字段以查询该实体类型的单个实例和集合。 - 每一类型的实体都需要使用
@entity指令注明。 - 默认情况下,实体是可变的,意味着映射可以加载现有实体,修改它们并存储该实体的新版本。
- 易燃性是以一种价格计算的,对于那些永远不会被修改的实体类型,例如那些载有从链中逐字提取的数据的类型, 建议用
@entity(immutable:true)标记为不可变的。 - 如果在创建实体的同一区块中发生更改,则映射可以对不可变的实体进行更改。 不可变的实体可以更快地撰写和查询,以便在可能的情况下使用。
- 易燃性是以一种价格计算的,对于那些永远不会被修改的实体类型,例如那些载有从链中逐字提取的数据的类型, 建议用
好示例
下面的 Gravatar 实体围绕 Gravatar 对象构建,是如何定义实体的一个很好的示例。
1type Gravatar @entity {2 id: Id!3 owner: Bytes4 displayName: String5 imageUrl: String6 accepted: Boolean7}坏榜样
下面的示例中,GravatarAccepted 和 GravatarDeclined 实体都基于事件。 不建议将事件或函数调用以 1:1 的方式映射到实体。
1type GravatarAccepted @entity {2 id: Bytes!3 owner: Bytes4 displayName: String5 imageUrl: String6}78type GravatarDeclined @entity {9 id: Bytes!10 owner: Bytes11 displayName: String12 imageUrl: String13}可选和必选字段
实体字段可以定义为必填字段或可选字段。在模式中,所需字段以 ! 标明。 如果字段是缩放字段,当您尝试存储实体时会出现错误。 如果字段引用了另一个实体,你就会得到这个错误:
1Null value resolved for non-null field 'name'每个实体必须有一个 id 字段,其类型必须是 Bytes!或者String!。通常建议使用Bytes!,除非 id 包含人类可读的文本,因为有Bytes! id的实体比使用String!``id的写入和查询速度会更快。id 字段充当主钥,并且需要在同一类型的所有实体中是唯一的。由于历史原因,类型 ID!也被接受,是 String!的同义词。
对于Bytes!的某些实体类型,id 是由另外两个实体的 id 构成的; 这可以使用 concat,例如,let id = left.id.concat(right.id) 来从left和right的 id 构成 id。类似地,要从现有实体的 id 和count构造 id,可以使用 let id = left.id.concatI32(count) 。只要left的长度对于所有这样的实体都是相同的,这种串联就一定会产生唯一的 id,例如,因为 left.id是一个 Address。
内置标量类型
GraphQL 支持的标量
GraphQL API支持以下缩写:
| 类型 | 描述 |
|---|---|
Bytes | 字节数组,表示为十六进制字符串。 通常用于以太坊hash和地址。 |
String | string值的标量。不支持空字符,将被自动删除。 |
Boolean | boolean 值的标量。 |
Int | GraphQL spec定义Int为一个带符号的32位整数。 |
Int8 | 一个8字节有符号整数,也称为64位有符号整数,可以存储从-9,223,372,036,854,775,808到9,223,372,036,854,775,807的范围内的值。建议使用此类型来表示以太坊中的i64。 |
BigInt | 大整数。 用于以太坊的 uint32, int64, uint64, …, uint256 类型。 注意:uint32以下的所有类型,例如int32, uint24或int8都表示为i32。 |
BigDecimal | BigDecimal表示为有效数字和指数的高精度小数。 指数范围是 -6143 到 +6144。 四舍五入到 34 位有效数字。 |
Timestamp | 它是一个微秒的i64值。通常用于时间序列和聚合的时间戳字段。 |
枚举类型
您还可以在模式中创建枚举类型。 枚举类型具有以下语法:
1enum TokenStatus {2 OriginalOwner3 SecondOwner4 ThirdOwner5}在模式中定义枚举后,您可以使用枚举值的字符串表示形式在实体上设置枚举字段。 例如,您可以将 tokenStatus 设置为 SecondOwner,方法是首先定义您的实体,然后使用 entity.tokenStatus = "SecondOwner" 设置字段。 下面的示例演示了带有枚举字段的 Token 实体:
在GraphQL文档中找到更多关于写入编码的详细信息。
实体关系
一个实体可能与模式中的一个或多个其他实体发生联系。 您可以在您的查询中遍历这些联系。 The Graph 中的联系是单向的。 可以通过在关系的任一“端”上定义单向关系来模拟双向关系。
关系是在实体上定义的,就像任何其他字段一样,除了指定的类型是另一个实体类型。
一对一关系
定义一个 Transaction 实体类型,该类型与一个 TransactionRecipt 实体类型是可选的一对一的关系:
1type Transaction @entity(immutable: true) {2 id: Bytes!3 transactionReceipt: TransactionReceipt4}56type TransactionReceipt @entity(immutable: true) {7 id: Bytes!8 transaction: Transaction9}一对多关系
定义一个 TokenBalance 实体类型,它与代币实体类型具有必备的一对多的关系:
1type Token @entity(immutable: true) {2 id: Bytes!3}45type TokenBalance @entity {6 id: Bytes!7 amount: Int!8 token: Token!9}反向查找
反向查找可以通过 @arotovedFrom 字段定义的实体。 这将在实体上创建一个虚拟字段,可以查询,但不能通过Mappings API手动设置。 相反,它产生于与其他实体界定的关系。 对于这种关系来说,把双方都放在一起是很不合理的。 如果只存储一方而得出另一方,则索引和查询性能都会更好。
对于一对多的关系,这种关系应该始终保存在’one’一边,而’many’一边应该总是被派生出来的。 以这种方式存储关系,而不是将一系列实体存储在’many’侧, 用于索引和查询Subgraph的性能将会大大提高。 一般而言,应尽量避免储存实体阵列。
示例
我们可以从 tokenBalances 中产生一个 `tokenBalances ’ 字段来获取代币:
1type Token @entity(immutable: true) {2 id: Bytes!3 tokenBalances: [TokenBalance!]! @derivedFrom(field: "token")4}56type TokenBalance @entity {7 id: Bytes!8 amount: Int!9 token: Token!10}下面是如何为具有反向查找的子图撰写映射的示例:
1let token = new Token(event.address) // Create Token2token.save() // tokenBalances is derived automatically34let tokenBalance = new TokenBalance(event.address)5tokenBalance.amount = BigInt.fromI32(0)6tokenBalance.token = token.id // Reference stored here7tokenBalance.save()多对多关系
对于多对多关系,例如每个可能属于任意数量的组织的用户,对关系建模的最直接,但通常不是最高效的方法,是在所涉及的两个实体中的每一个中定义数组。 如果关系是对称的,则只需要存储关系的一侧联系,就可以导出另一侧。
示例
定义从User实体类型向Organization实体类型的反向查找。 在下面的例子中,实现这个目标的方法是在 Organization 实体内查找 members 属性。 在查询时,User上的 organization 字段将通过找到所有包含用户ID的 Organization 实体来解决。
1type Organization @entity {2 id: Bytes!3 name: String!4 members: [User!]!5}67type User @entity {8 id: Bytes!9 name: String!10 organizations: [Organization!]! @derivedFrom(field: "members")11}存储这种关系的更加有效的方法是通过一个映射表,这个表为每一个 ’ User’ / ’ Organization’ 配对提供一个条目和一个类似的模式:
1type Organization @entity {2 id: Bytes!3 name: String!4 members: [UserOrganization!]! @derivedFrom(field: "organization")5}67type User @entity {8 id: Bytes!9 name: String!10 organizations: [UserOrganization!] @derivedFrom(field: "user")11}1213type UserOrganization @entity {14 id: Bytes! # Set to `user.id.concat(organization.id)`15 user: User!16 organization: Organization!17}这种方法要求查询下降一个额外的级别来检索,例如,用户的组织:
1query usersWithOrganizations {2 users {3 organizations {4 # this is a UserOrganization entity5 organization {6 name7 }8 }9 }10}这种存储多对多关系的更精细的方式,将导致为子图存储的数据更少,因此,子图的索引和查询速度通常会大大加快。
向模式添加注释
根据 GraphQL 规范,可以使用双引号#在模式实体属性上方添加注释。 这在下面的示例中进行了说明:
1type MyFirstEntity @entity {2 # unique identifier and primary key of the entity3 id: Bytes!4 address: Bytes!5}定义全文搜索字段
全文搜索查询根据文本搜索输入来过滤和排列实体。 通过在与索引文本数据进行比较之前,将查询文本输入处理到词干中,全文查询能够返回相似词的匹配项。
全文查询定义包括查询名称、用于处理文本字段的语言词典、用于对结果进行排序的排序算法,以及搜索中包含的字段。 每个全文查询可能跨越多个字段,但所有包含的字段必须来自单个实体类型。
要添加全文查询,请在 GraphQL 模式中包含带有全文指令的 _Schema_ 类型。
1type _Schema_2 @fulltext(3 name: "bandSearch"4 language: en5 algorithm: rank6 include: [{ entity: "Band", fields: [{ name: "name" }, { name: "description" }, { name: "bio" }] }]7 )89type Band @entity {10 id: Bytes!11 name: String!12 description: String!13 bio: String14 wallet: Address15 labels: [Label!]!16 discography: [Album!]!17 members: [Musician!]!18}示例bandSearch 字段可以用来在查询中根据name、description、和bio字段中的文本文档过滤Band 实体。 跳转到GraphQL API - 查询以了解全文搜索 API 和更多示例用法。
1query {2 bandSearch(text: "breaks & electro & detroit") {3 id4 name5 description6 wallet7 }8}**特征管理:**从 specVersion 0.0.4 及以后, fullTextSearch 必须在子图清单中features 部分下申报。
支持的语言
选择不同的语言将对全文搜索 API 产生明确的(尽管有时是微妙的)影响。 全文查询字段涵盖的字段将会在所选语言的内容中进行检查,因此分析和搜索查询产生的词位因语言而异。 例如:当使用支持的土耳其语词典时,“token”的词干为“toke”,而英语词典当然会认为其词干为“token”。
支持的语言词典:
| 代码 | 词典 |
|---|---|
| simple | 通用 |
| da | 丹麦语 |
| nl | 荷兰语 |
| en | 英语 |
| fi | 芬兰语 |
| fr | French |
| de | 德语 |
| hu | 匈牙利语 |
| it | 意大利语 |
| no | 挪威语 |
| pt | 葡萄牙语 |
| ro | 罗马尼亚语 |
| ru | 俄语 |
| es | 西班牙语 |
| sv | 瑞典语 |
| tr | 土耳其语 |
排序算法
支持的排序结果算法:
| 算法 | 说明 |
|---|---|
| 排名 | 使用全文查询的匹配质量 (0-1) 对结果进行排序。 |
| proximityRank | 与 rank 类似,但也包括匹配的接近程度。 |