9 minutos
The Graph QL Schema
Descripción
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 GraphQL API section.
Defining Entities
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
Un buen ejemplo
The following Gravatar
entity is structured around a Gravatar object and is a good example of how an entity could be defined.
1type Gravatar @entity(immutable: true) {2 id: Bytes!3 owner: Bytes4 displayName: String5 imageUrl: String6 accepted: Boolean7}
Un mal ejemplo
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.
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}
Campos opcionales y obligatorios
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:
1Null 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
.
Tipos de Scalars incorporados
GraphQL admite Scalars
The following scalars are supported in the GraphQL API:
Tipo | Descripción |
---|---|
Bytes | Byte array, representado como un string hexadecimal. Comúnmente utilizado para los hashes y direcciones de Ethereum. |
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. |
Enums
También puedes crear enums dentro de un esquema. Los enums tienen la siguiente sintaxis:
1enum TokenStatus {2 OriginalOwner3 SecondOwner4 ThirdOwner5}
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 GraphQL documentation.
Relaciones entre Entidades
Una entidad puede tener una relación con otra u otras entidades de su esquema. Estas relaciones pueden ser recorridas en sus consultas. Las relaciones en The Graph son unidireccionales. Es posible simular relaciones bidireccionales definiendo una relación unidireccional en cada “extremo” de la relación.
Las relaciones se definen en las entidades como cualquier otro campo, salvo que el tipo especificado es el de otra entidad.
Relaciones Uno a Uno
Define a Transaction
entity type with an optional one-to-one relationship with a TransactionReceipt
entity type:
1type Transaction @entity(immutable: true) {2 id: Bytes!3 transactionReceipt: TransactionReceipt4}56type TransactionReceipt @entity(immutable: true) {7 id: Bytes!8 transaction: Transaction9}
Relaciones one-to-many
Define a TokenBalance
entity type with a required one-to-many relationship with a Token entity type:
1type Token @entity(immutable: true) {2 id: Bytes!3}45type TokenBalance @entity {6 id: Bytes!7 amount: Int!8 token: Token!9}
Búsquedas Inversas
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.
For one-to-many relationships, the relationship should always be stored on the ‘one’ side, and the ‘many’ side should always be derived. Storing the relationship this way, rather than storing an array of entities on the ‘many’ side, will result in dramatically better performance for both indexing and querying the Subgraph. In general, storing arrays of entities should be avoided as much as is practical.
Ejemplo
We can make the balances for a token accessible from the token by deriving a tokenBalances
field:
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}
Here is an example of how to write a mapping for a Subgraph with reverse lookups:
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()
Relaciones de many-to-many
Para las relaciones de many-to-many, como los usuarios pueden pertenecer a cualquier número de organizaciones, la forma más directa, pero generalmente no la más eficaz, de modelar la relación es en un array en cada una de las dos entidades implicadas. Si la relación es simétrica, sólo es necesario almacenar un lado de la relación y el otro puede derivarse.
Ejemplo
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.
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}
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
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}
Este enfoque requiere que las consultas desciendan a un nivel adicional para recuperar, por ejemplo, las organizaciones para los usuarios:
1query usersWithOrganizations {2 users {3 organizations {4 # this is a UserOrganization entity5 organization {6 name7 }8 }9 }10}
This more elaborate way of storing many-to-many relationships will result in less data stored for the Subgraph, and therefore to a Subgraph that is often dramatically faster to index and to query.
Agregar comentarios al esquema
As per GraphQL spec, comments can be added above schema entity attributes using the hash symbol #
. This is illustrated in the example below:
1type MyFirstEntity @entity {2 # unique identifier and primary key of the entity3 id: Bytes!4 address: Bytes!5}
Definición de campos de búsqueda de texto completo
Las consultas de búsqueda de texto completo filtran y clasifican las entidades basándose en una entrada de búsqueda de texto. Las consultas de texto completo pueden devolver coincidencias de palabras similares procesando el texto de la consulta en stems antes de compararlo con los datos del texto indexado.
La definición de una consulta de texto completo incluye el nombre de la consulta, el diccionario lingüístico utilizado para procesar los campos de texto, el algoritmo de clasificación utilizado para ordenar los resultados y los campos incluidos en la búsqueda. Cada consulta de texto completo puede abarcar varios campos, pero todos los campos incluidos deben ser de un solo tipo de entidad.
To add a fulltext query, include a _Schema_
type with a fulltext directive in the 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}
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 GraphQL API - Queries for a description of the fulltext search API and more example usage.
1query {2 bandSearch(text: "breaks & electro & detroit") {3 id4 name5 description6 wallet7 }8}
Feature Management: From specVersion
0.0.4
and onwards, fullTextSearch
must be declared under the features
section in the Subgraph manifest.
Idiomas admitidos
La elección de un idioma diferente tendrá un efecto definitivo, aunque a veces sutil, en la API de búsqueda de texto completo. Los campos cubiertos por un campo de consulta de texto completo se examinan en el contexto de la lengua elegida, por lo que los lexemas producidos por las consultas de análisis y búsqueda varían de un idioma a otro. Por ejemplo: al utilizar el diccionario turco compatible, “token” se convierte en “toke”, mientras que el diccionario inglés lo convierte en “token”.
Diccionarios de idiomas admitidos:
Code | Diccionario |
---|---|
simple | General |
da | Danish |
nl | Dutch |
en | English |
fi | Finnish |
fr | French |
de | German |
hu | Hungarian |
it | Italian |
no | Norwegian |
pt | Portugués |
ro | Romanian |
ru | Russian |
es | Spanish |
sv | Swedish |
tr | Turkish |
Algoritmos de Clasificación
Algoritmos admitidos para ordenar los resultados:
Algorithm | Description |
---|---|
rank | Usa la calidad de coincidencia (0-1) de la consulta de texto completo para ordenar los resultados. |
rango de proximidad | Similar to rank but also includes the proximity of the matches. |