SubQuery开发者指南丨GraphQL 架构(GraphQL Schema)
定义实体(Defining Entities)
schema.graphql 文件定义了各种 GraphQL 架构。 由于 GraphQL 查询语言的工作方式,模式文件本质上决定了来自 SubQuery 的数据的形状。 要了解有关如何使用 GraphQL 架构语言编写的更多信息,我们建议查看架构和类型( https://graphql.org/learn/schema/#type-language )
重要提示:当您对架构文件(schema file)进行任何更改时,请确保使用 yarn codegen 代码来生成重新生成类型目录。
实体(Entities)
每个实体(entity)都必须定义它的必填字段id与ID的类型!它用作主键,并且在所有相同类型的实体中是唯一的。
实体( entity) 中不可为空的字段由“ ! ”来表示。 请看下面的例子:
type Example @entity { id: ID! # id field is always required and must look like this name: String! # This is a required field address: String # This is an optional field }
支持的标量和类型(Supported scalars and types)
我们目前支持流动标量类型:
• ID
• Int
• String
• BigInt
• Date
• Boolean
• <EntityName> 用于嵌套关系实体,您可以使用定义的实体名称作为字段之一。 请参阅实体关系( https://doc.subquery.network/create/graphql.html#entity-relationships )。
• JSON 也可以存储结构化数据,请参阅 JSON 类型( https://doc.subquery.network/create/graphql.html#json-type )。
按非主键字段进行索引(Indexing by non-primary-key field)
为了提高查询性能,只需在非主键字段上执行 @index 注释即可索引实体字段。
但是,我们不允许用户在任何 JSON 对象上添加 @index 注释。 默认情况下,索引会自动添加到外键和数据库中的 JSON 字段,但只是为了增强查询服务性能。
这有一个例子。
type User @entity { id: ID! name: String! @index(unique: true) # unique can be set to true or false title: Title! # Indexes are automatically added to foreign key field } type Title @entity { id: ID! name: String! @index(unique:true) }
假设我们知道这个用户的名字,但我们不知道确切的 id 值,我们可以在名称字段后面添加@index,而不是提取所有用户然后按名称过滤。 这使得查询速度更快,我们还可以添加 unique: true 以确保唯一性。
如果字段不唯一,则最大结果集为 100
当代码生成运行时,这将自动在 User 模型下创建一个 getByName,然后外键字段 title 将创建一个 getByTitleId 方法,这两者都可以在映射函数中直接访问。
/* Prepare a record for title entity */ INSERT INTO titles (id, name) VALUES ('id_1', 'Captain')
// Handler in mapping function import {User} from "../types/models/User" import {Title} from "../types/models/Title" const jack = await User.getByName('Jack Sparrow'); const captainTitle = await Title.getByName('Captain'); const pirateLords = await User.getByTitleId(captainTitle.id); // List of all Captains
实体关系(Entity Relationships)
一个实体(entity)通常与其他实体(entity)有嵌套关系。 默认情况下,将字段值设置为另一个实体(entity)名称将定义这两个实体(entity)之间的一对一关系。
可以使用以下示例配置不同的实体(entity)关系(一对一、一对多和多对多)。
- 一对一关系(One-to-One Relationships)
当只有一个实体映射到另一个实体时,一对一关系是默认的。
例子:一本护照只属于一个人,一个人只有一本护照(在这个例子中):
type Person @entity { id: ID! } type Passport @entity { id: ID! owner: Person! }
或者
type Person @entity { id: ID! passport: Passport! } type Passport @entity { id: ID! owner: Person! }
- 一对多关系(One-to-Many relationships)
您可以使用方括号表示一个字段类型包括多个实体。
示例:一个人可以拥有多个帐户。
type Person @entity { id: ID! accounts: [Account] } type Account @entity { id: ID! publicAddress: String! }
- 多对多关系(Many-to-Many relationships)
多对多关系可以通过实现一个映射实体(mapping entity)来连接其他两个实体(entity)来实现。
示例:每个人都是多个组 (PersonGroup) 的一部分,并且组有多个不同的人 (PersonGroup)。
type Person @entity { id: ID! name: String! groups: [PersonGroup] } type PersonGroup @entity { id: ID! person: Person! Group: Group! } type Group @entity { id: ID! name: String! persons: [PersonGroup] }
此外,可以在中间实体(entity)的多个字段中创建同一实体(entity)的连接。
例如,一个账户可以实现多次转账,每次转账都有一个源账户和目的地账户。
这将通过 Transfer 层在两个 Accounts(from 和 to)之间建立双向关系。
type Account @entity { id: ID! publicAddress: String! } type Transfer @entity { id: ID! amount: BigInt from: Account! to: Account! }
反向查找(Reverse Lookups)
为了使一个实体(entity)能够反向查询到一个关系,请将 @derivedFrom 附加到该字段并指向另一个实体(entity)的反向查找字段。
这会在可以查询的实体(entity)上创建一个虚拟字段。
通过将 sentTransfer 或 receivedTransfer 设置为从相应的 from 或 to 字段得出的值,可以从帐户实体中访问“来自” 账户的转移。
type Account @entity { id: ID! publicAddress: String! sentTransfers: [Transfer] @derivedFrom(field: "from") receivedTransfers: [Transfer] @derivedFrom(field: "to") } type Transfer @entity { id: ID! amount: BigInt from: Account! to: Account! }
JSON 类型(JSON type)
我们支持将数据保存为 JSON 类型(JSON type),这是一种存储结构化数据的快速方式。 我们将自动生成相应的 JSON 接口来查询这些数据,并节省您定义和管理实体(entities)的时间。
我们建议用户在以下场景中使用 JSON 类型:
• 在单个字段中存储结构化数据比创建多个单独的实体(entities)更易于管理。
• 保存任意键/值用户首选项(其中值可以是布尔值、文本或数字,并且不用为不同的数据类型设置单独的列)
• 架构是不稳定的并且经常变化
定义 JSON 指令(Define JSON directive)
通过在实体中添加 jsonField 注释,将该属性定义为 JSON 类型。 这将自动为您项目中 types/interfaces.ts 下的所有 JSON 对象生成接口,您也可以在映射函数中访问它们。
与实体不同,jsonField 指令对象不需要任何 id 字段。 JSON 对象还可以与其他 JSON 对象嵌套。
type AddressDetail @jsonField { street: String! district: String! } type ContactCard @jsonField { phone: String! address: AddressDetail # Nested JSON } type User @entity { id: ID! contact: [ContactCard] # Store a list of JSON objects }
查询 JSON 字段(Querying JSON fields)
使用 JSON 类型的缺点是过滤时对查询效率的影响很小,因为每次执行文本搜索时,都是针对整个实体来进行的。
但是,在我们的查询服务中,影响仍然可以接受。 下面是一个示例,说明如何在 GraphQL 查询中对 JSON 字段使用 contains 运算符来查找拥有包含“0064”的电话号码的前 5 个用户。
#To find the the first 5 users own phone numbers contains '0064'. query{ user( first: 5, filter: { contactCard: { contains: [{ phone: "0064" }] } }){ nodes{ id contactCard } } }