The Graph QL Schema
Reading time: 11 min
The schema for your subgraph is in the file schema.graphql
. GraphQL schemas are defined using the GraphQL interface definition language.
Note: If you've never written a GraphQL schema, it is recommended that you check out this primer on the GraphQL type system. Reference documentation for GraphQL schemas can be found in the section.
Before defining entities, it is important to take a step back and think about how your data is structured and linked.
- All queries will be made against the data model defined in the subgraph schema. As a result, the design of the subgraph schema should be informed by the queries that your application will need to perform.
- It may be useful to imagine entities as "objects containing data", rather than as events or functions.
- You define entity types in
schema.graphql
, and Graph Node will generate top-level fields for querying single instances and collections of that entity type. - Each type that should be an entity is required to be annotated with an
@entity
directive. - By default, entities are mutable, meaning that mappings can load existing entities, modify them and store a new version of that entity.
- Mutability comes at a price, so for entity types that will never be modified, such as those containing data extracted verbatim from the chain, it is recommended to mark them as immutable with
@entity(immutable: true)
. - If changes happen in the same block in which the entity was created, then mappings can make changes to immutable entities. Immutable entities are much faster to write and to query so they should be used whenever possible.
- Mutability comes at a price, so for entity types that will never be modified, such as those containing data extracted verbatim from the chain, it is recommended to mark them as immutable with
The following Gravatar
entity is structured around a Gravatar object and is a good example of how an entity could be defined.
type Gravatar @entity {id: Id!owner: BytesdisplayName: StringimageUrl: Stringaccepted: Boolean}
The following example GravatarAccepted
and GravatarDeclined
entities are based around events. It is not recommended to map events or function calls to entities 1:1.
type GravatarAccepted @entity {id: Bytes!owner: BytesdisplayName: StringimageUrl: String}type GravatarDeclined @entity {id: Bytes!owner: BytesdisplayName: StringimageUrl: String}
Entity fields can be defined as required or optional. Required fields are indicated by the !
in the schema. If the field is a scalar field, you get an error when you try to store the entity. If the field references another entity then you get this error:
Null value resolved for non-null field 'name'
Each entity must have an id
field, which must be of type Bytes!
or String!
. It is generally recommended to use Bytes!
, unless the id
contains human-readable text, since entities with Bytes!
id's will be faster to write and query as those with a String!
id
. The id
field serves as the primary key, and needs to be unique among all entities of the same type. For historical reasons, the type ID!
is also accepted and is a synonym for String!
.
For some entity types the id
for Bytes!
is constructed from the id's of two other entities; that is possible using concat
, e.g., let id = left.id.concat(right.id)
to form the id from the id's of left
and right
. Similarly, to construct an id from the id of an existing entity and a counter count
, let id = left.id.concatI32(count)
can be used. The concatenation is guaranteed to produce unique id's as long as the length of left
is the same for all such entities, for example, because left.id
is an Address
.
The following scalars are supported in the GraphQL API:
类型 | 描述 |
---|---|
Bytes | 字节数组,表示为十六进制字符串。 通常用于以太坊hash和地址。 |
String | Scalar for string values. Null characters are not supported and are automatically removed. |
Boolean | Scalar for boolean values. |
Int | The GraphQL spec defines Int to be a signed 32-bit integer. |
Int8 | An 8-byte signed integer, also known as a 64-bit signed integer, can store values in the range from -9,223,372,036,854,775,808 to 9,223,372,036,854,775,807. Prefer using this to represent i64 from ethereum. |
BigInt | Large integers. Used for Ethereum's uint32 , int64 , uint64 , ..., uint256 types. Note: Everything below uint32 , such as int32 , uint24 or int8 is represented as i32 . |
BigDecimal | BigDecimal High precision decimals represented as a significand and an exponent. The exponent range is from −6143 to +6144. Rounded to 34 significant digits. |
Timestamp | It is an i64 value in microseconds. Commonly used for timestamp fields for timeseries and aggregations. |
您还可以在模式中创建枚举类型。 枚举类型具有以下语法:
enum TokenStatus {OriginalOwnerSecondOwnerThirdOwner}
Once the enum is defined in the schema, you can use the string representation of the enum value to set an enum field on an entity. For example, you can set the tokenStatus
to SecondOwner
by first defining your entity and subsequently setting the field with entity.tokenStatus = "SecondOwner"
. The example below demonstrates what the Token entity would look like with an enum field:
More detail on writing enums can be found in the .
一个实体可能与模式中的一个或多个其他实体发生联系。 您可以在您的查询中遍历这些联系。 Graph 中的联系是单向的。 可以通过在关系的任一“端”上定义单向关系来模拟双向关系。
关系是在实体上定义的,就像任何其他字段一样,除了指定的类型是另一个实体类型。
Define a Transaction
entity type with an optional one-to-one relationship with a TransactionReceipt
entity type:
type Transaction @entity(immutable: true) {id: Bytes!transactionReceipt: TransactionReceipt}type TransactionReceipt @entity(immutable: true) {id: Bytes!transaction: Transaction}
Define a TokenBalance
entity type with a required one-to-many relationship with a Token entity type:
type Token @entity(immutable: true) {id: Bytes!}type TokenBalance @entity {id: Bytes!amount: Int!token: Token!}
Reverse lookups can be defined on an entity through the @derivedFrom
field. This creates a virtual field on the entity that may be queried but cannot be set manually through the mappings API. Rather, it is derived from the relationship defined on the other entity. For such relationships, it rarely makes sense to store both sides of the relationship, and both indexing and query performance will be better when only one side is stored and the other is derived.
对于一对多关系,关系应始终存储在“一”端,而“多”端应始终派生。 以这种方式存储关系,而不是在“多”端存储实体数组,将大大提高索引和查询子图的性能。 通常,应尽可能避免存储实体数组。
We can make the balances for a token accessible from the token by deriving a tokenBalances
field:
type Token @entity(immutable: true) {id: Bytes!tokenBalances: [TokenBalance!]! @derivedFrom(field: "token")}type TokenBalance @entity {id: Bytes!amount: Int!token: Token!}
对于多对多关系,例如每个可能属于任意数量的组织的用户,对关系建模的最直接,但通常不是最高效的方法,是在所涉及的两个实体中的每一个中定义数组。 如果关系是对称的,则只需要存储关系的一侧联系,就可以导出另一侧。
Define a reverse lookup from a User
entity type to an Organization
entity type. In the example below, this is achieved by looking up the members
attribute from within the Organization
entity. In queries, the organizations
field on User
will be resolved by finding all Organization
entities that include the user's ID.
type Organization @entity {id: Bytes!name: String!members: [User!]!}type User @entity {id: Bytes!name: String!organizations: [Organization!]! @derivedFrom(field: "members")}
A more performant way to store this relationship is through a mapping table that has one entry for each User
/ Organization
pair with a schema like
type Organization @entity {id: Bytes!name: String!members: [UserOrganization!]! @derivedFrom(field: "organization")}type User @entity {id: Bytes!name: String!organizations: [UserOrganization!] @derivedFrom(field: "user")}type UserOrganization @entity {id: Bytes! # Set to `user.id.concat(organization.id)`user: User!organization: Organization!}
这种方法要求查询下降一个额外的级别来检索,例如,用户的组织:
query usersWithOrganizations {users {organizations {# this is a UserOrganization entityorganization {name}}}}
这种存储多对多关系的更精细的方式将导致为子图存储的数据更少,因此子图的索引和查询速度通常会大大加快。
As per GraphQL spec, comments can be added above schema entity attributes using the hash symbol #
. This is illustrated in the example below:
type MyFirstEntity @entity {# unique identifier and primary key of the entityid: Bytes!address: Bytes!}
全文搜索查询根据文本搜索输入来过滤和排列实体。 通过在与索引文本数据进行比较之前,将查询文本输入处理到词干中,全文查询能够返回相似词的匹配项。
全文查询定义包括查询名称、用于处理文本字段的语言词典、用于对结果进行排序的排序算法,以及搜索中包含的字段。 每个全文查询可能跨越多个字段,但所有包含的字段必须来自单个实体类型。
To add a fulltext query, include a _Schema_
type with a fulltext directive in the GraphQL schema.
type _Schema_@fulltext(name: "bandSearch"language: enalgorithm: rankinclude: [{ entity: "Band", fields: [{ name: "name" }, { name: "description" }, { name: "bio" }] }])type Band @entity {id: Bytes!name: String!description: String!bio: Stringwallet: Addresslabels: [Label!]!discography: [Album!]!members: [Musician!]!}
The example bandSearch
field can be used in queries to filter Band
entities based on the text documents in the name
, description
, and bio
fields. Jump to for a description of the fulltext search API and more example usage.
query {bandSearch(text: "breaks & electro & detroit") {idnamedescriptionwallet}}
: From specVersion
0.0.4
and onwards, fullTextSearch
must be declared under the features
section in the subgraph manifest.
选择不同的语言将对全文搜索 API 产生明确的(尽管有时是微妙的)影响。 全文查询字段涵盖的字段将会在所选语言的内容中进行检查,因此分析和搜索查询产生的词位因语言而异。 例如:当使用支持的土耳其语词典时,“token”的词干为“toke”,而英语词典当然会认为其词干为“token”。
支持的语言词典:
Code | 词典 |
---|---|
simple | General |
da | Danish |
nl | Dutch |
en | English |
fi | Finnish |
fr | French |
de | German |
hu | Hungarian |
it | Italian |
no | Norwegian |
pt | 葡萄牙语 |
ro | Romanian |
ru | Russian |
es | Spanish |
sv | Swedish |
tr | Turkish |
支持的排序结果算法:
Algorithm | Description |
---|---|
rank | 使用全文查询的匹配质量 (0-1) 对结果进行排序。 |
proximityRank | Similar to rank but also includes the proximity of the matches. |