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 类似,但也包括匹配的接近程度。 |