En la sección anterior, construimos un subgrafo con funciones básicas basadas en el ejemplo oficial. En esta sección, presentamos algunas otras características.
manejador de llamadas
Si bien los eventHandlers brindan una forma eficiente de recopilar cambios relevantes en el estado del contrato, muchos contratos evitan generar eventos para optimizar los costos de gas. En estos casos, los subgrafos pueden suscribirse a llamadas a métodos de contrato especificados. Esto se logra definiendo un controlador de llamadas que hace referencia a la firma de la función y un controlador asignado que maneja las llamadas a esta función. Para manejar estas llamadas, el controlador de mapeo recibirá un ethereum.Call como parámetro, que contiene la entrada y salida de la llamada.
callHandler solo se activará en uno de dos casos: cuando la función especificada es llamada por una cuenta que no sea el contrato mismo, o cuando está marcada como externa en Solidity y llamada como otra función en el mismo contrato.
callHandler actualmente se basa en la API de seguimiento de Parity. Ciertas redes, como la cadena BNB y Arbitrum, no admiten esta API. Al encontrar estas redes, los desarrolladores de subgrafos deben usar eventHandle. Funcionan mucho mejor que llamar a callHandler y son compatibles con todas las redes de evm.
Para definir callHandler, simplemente agregue la matriz callHandlers debajo de la fuente de datos a la que desea suscribirse.
subgrafo.yaml:
specVersion: 0.0.5
schema:
file: ./schema.graphql
dataSources:
- kind: ethereum
name: GravatarRegistry
network: goerli
source:
address: "0x964F658FC863BAceFC719b85e8730fbc11c86ce4"
abi: GravatarRegistry
startBlock: 8266411
mapping:
kind: ethereum/events
apiVersion: 0.0.7
language: wasm/assemblyscript
entities:
- Gravatar
- Transaction
abis:
- name: GravatarRegistry
file: ./abis/GravatarRegistry.json
callHandlers:
- function: createGravatar(string,string)
handler: handleCreateGravatar
# eventHandlers:
# - event: NewGravatar(uint256,address,string,string)
# handler: handleNewGravatar
# - event: UpdatedGravatar(uint256,address,string,string)
# handler: handleUpdatedGravatar
file: ./src/gravatar-registry.ts
Esta vez modificamos ligeramente el ejemplo del capítulo anterior, redefinimos una entidad, usamos el valor hash de cada transacción como id y registramos la información básica de cada transacción.
esquema.graphql:
type Transaction @entity(immutable: true) {
id: String!
displayName: String! # string
imageUrl: String! # string
}
gravatar-registry.ts:
import {
CreateGravatarCall,
NewGravatar as NewGravatarEvent,
UpdatedGravatar as UpdatedGravatarEvent
} from "../generated/GravatarRegistry/GravatarRegistry"
import { Gravatar,Transaction} from "../generated/schema"
export function handleCreateGravatar(call: CreateGravatarCall): void {
let id = call.transaction.hash.toHexString()
let transaction = new Transaction(id)
transaction.displayName = call.inputs._displayName
transaction.imageUrl = call.inputs._imageUrl
transaction.save()
}
Vuelva a publicar el subgrafo:
graph codegen
graph build
yarn deploy
Los resultados de la consulta son los siguientes:
manejadores de bloques
Además de suscribirse a eventos de contratos o llamadas a funciones, un subgrafo puede desear actualizar sus datos cuando se agregan nuevos bloques a la cadena. Para lograr esto, los subgráficos pueden ejecutar una función después de cada bloque o bloque que coincida con un filtro predefinido.
Los llamados filtros predefinidos son los siguientes:
filter:
kind: call
Si el bloque contiene una llamada al contrato especificado, se activará la función de controlador definida.
Al igual que callHandler, el filtro de blockHandlers se basa en la API de seguimiento de Parity. Ciertas redes, como la cadena BNB y Arbitrum, no admiten esta API.
subgrafo.yaml:
specVersion: 0.0.5
schema:
file: ./schema.graphql
dataSources:
- kind: ethereum
name: GravatarRegistry
network: goerli
source:
address: "0x964F658FC863BAceFC719b85e8730fbc11c86ce4"
abi: GravatarRegistry
startBlock: 8266411
mapping:
kind: ethereum/events
apiVersion: 0.0.7
language: wasm/assemblyscript
entities:
- Gravatar
- Transaction
- Block
abis:
- name: GravatarRegistry
file: ./abis/GravatarRegistry.json
eventHandlers:
- event: NewGravatar(uint256,address,string,string)
handler: handleNewGravatar
- event: UpdatedGravatar(uint256,address,string,string)
handler: handleUpdatedGravatar
callHandlers:
- function: createGravatar(string,string)
handler: handleCreateGravatar
blockHandlers:
- handler: handleBlockWithCallToContract
filter:
kind: call
file: ./src/gravatar-registry.ts
Esta vez modificamos ligeramente el ejemplo del capítulo anterior, redefinimos una entidad, usamos el valor hash de cada transacción como id y registramos la información básica de cada transacción.
esquema.graphql:
type Block @entity(immutable: true) {
id: String!
blockNumber: BigInt!
blockTimestamp: BigInt!
}
gravatar-registry.ts:
import {
CreateGravatarCall,
NewGravatar as NewGravatarEvent,
UpdatedGravatar as UpdatedGravatarEvent
} from "../generated/GravatarRegistry/GravatarRegistry"
import { Gravatar,Transaction,Block} from "../generated/schema"
import { ethereum } from '@graphprotocol/graph-ts'
export function handleBlockWithCallToContract(block: ethereum.Block): void {
let id = block.hash.toHexString()
let entity = new Block(id)
entity.blockNumber = block.number
entity.blockTimestamp = block.timestamp
entity.save()
}
Vuelva a publicar el subgrafo:
graph codegen
graph build
yarn deploy
Los resultados de la consulta son los siguientes:
relación entre entidades
Una entidad puede tener una relación con una o más entidades del esquema. Estas relaciones se pueden atravesar en las consultas. Las relaciones en The Graph son unidireccionales y las relaciones bidireccionales también se pueden simular definiendo una relación unidireccional en "un extremo" de la relación. Definir una relación en una entidad es como cualquier otro campo, excepto que el tipo especificado es el de la otra entidad.
En el ejemplo anterior, involucramos tres entidades, Gravatar, Transaction y Block, y su relación es la siguiente:
En la figura anterior se puede ver que Gravatar y Transaction son relaciones de uno a uno, y Block y Transaction son relaciones de uno a muchos. Redefinamos los campos de entidad:
type Gravatar @entity(immutable: false) {
id: String!
owner: Bytes! # address
displayName: String! # string
imageUrl: String! # string
transaction: Transaction
}
type Transaction @entity(immutable: true) {
id: Bytes!
block: Block
gasPrice: BigInt!
}
type Block @entity(immutable: true) {
id: Bytes!
blockNumber: BigInt!
blockTimestamp: BigInt!
transactions: [Transaction!]
}
gravatar-registry.ts:
import {
CreateGravatarCall,
NewGravatar as NewGravatarEvent,
UpdatedGravatar as UpdatedGravatarEvent
} from "../generated/GravatarRegistry/GravatarRegistry"
import { Gravatar,Transaction,Block} from "../generated/schema"
import { ethereum } from '@graphprotocol/graph-ts'
export function handleNewGravatar(event: NewGravatarEvent): void {
let gravatar = new Gravatar(event.params.id.toString());
gravatar.owner = event.params.owner
gravatar.displayName = event.params.displayName
gravatar.imageUrl = event.params.imageUrl
gravatar.transaction = event.transaction.hash
gravatar.save()
}
export function handleUpdatedGravatar(event: UpdatedGravatarEvent): void {
let id = event.params.id.toString();
let gravatar = Gravatar.load(id)
if (gravatar == null) {
gravatar = new Gravatar(id)
}
gravatar.owner = event.params.owner
gravatar.displayName = event.params.displayName
gravatar.imageUrl = event.params.imageUrl
gravatar.transaction = event.transaction.hash
gravatar.save()
}
export function handleCreateGravatar(call: CreateGravatarCall): void {
let transaction = new Transaction(call.transaction.hash)
transaction.gasPrice = call.transaction.gasPrice
transaction.block = call.block.hash
transaction.save()
}
export function handleBlockWithCallToContract(block: ethereum.Block): void {
let entity = new Block(block.hash)
entity.blockNumber = block.number
entity.blockTimestamp = block.timestamp
entity.save()
}
Después de volver a publicar, haz la siguiente consulta:
{
gravatars(first: 5) {
id
owner
displayName
imageUrl
transaction{
id
gasPrice
block {
id
blockNumber
}
}
}
}
El resultado es el siguiente:
{
"data": {
"gravatars": [
{
"id": "0",
"owner": "0xba8b604410ca76af86bda9b00eb53b65ac4c41ac",
"displayName": "Carl",
"imageUrl": "https://thegraph.com/img/team/team_04.png",
"transaction": {
"id": "0x5b8f57f7b377165f046fc1bcda50846d6eba2500212266752492f0dd0476a7f9",
"gasPrice": "22045257798",
"block": {
"id": "0x98f069280b3105a73f6743997bc460dcf3295860c1ec7ae783938903da412c66",
"blockNumber": "8272525"
}
}
},
{
"id": "1",
"owner": "0xba8b604410ca76af86bda9b00eb53b65ac4c41ac",
"displayName": "gambo2",
"imageUrl": "https://thegraph.com/img/team/bw_Lucas2.jpg",
"transaction": null
},
{
"id": "2",
"owner": "0x04f367f342a3c763f2f337572ec150b2d2b84e37",
"displayName": "gambo017",
"imageUrl": "https://thegraph.com/img/team/team_04.png",
"transaction": null
}
]
}
}
Observamos que algunas transacciones son nulas, esto se debe a que nuestro callHandler no capturó la función de actualización, por lo que la transacción correspondiente no se guardó en el nodo del gráfico.
búsqueda inversa
Para una relación de uno a muchos, nuestra expectativa es que la matriz contenida en el lado "varios" se pueda ver a través del lado "uno". Sin embargo, nuestra estructura de entidad actual no ha cumplido con tales expectativas.
Aquí puede usar la anotación @derivedFrom para definir la derivación entre relaciones de entidad.
Modifique la definición de la entidad Bloque de la siguiente manera:
type Block @entity(immutable: true) {
id: Bytes!
blockNumber: BigInt!
blockTimestamp: BigInt!
transactions: [Transaction!] @derivedFrom(field: "block")
}
El efecto después de volver a publicar es el siguiente:
La explicación oficial está aquí:
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.
对于一对多的关系,其“关联关系”应该存储在“one”端,而“many”端应该被派生出来。通过这种方式存储关联关系,其查询和索引的性能要好过直接存储“many”端的实体数组。一般来说,应该尽可能避免存储实体数组。
个人也不是太明白其表达的意思,暂且理解为,一对多的关系,通过one端查询到的“many”端的数组,并不是简单的数组保存,而是两个实体之间关系的派生。通过实验可以直接通过“many”端中的字段做过滤。读者可自行尝试:
query MyQuery {
blocks(where: {transactions_: {id: "0x5b8f57f7b377165f046fc1bcda50846d6eba2500212266752492f0dd0476a7f9"}}) {
id
}
}
全文搜索字段
全文搜索查询基于文本搜索输入对实体进行筛选和排序。全文查询能够通过将查询文本输入处理为词干,然后将它们与索引文本数据进行比较,从而返回相似单词的匹配。
全文查询定义包括查询名称、用于处理文本字段的语言字典、用于对结果排序的排序算法以及搜索中包含的字段。每个全文查询可以跨越多个字段,但所有包含的字段必须来自同一个实体。
要添加一个全文查询,在schema.graphql中定义一个带有全文指令的_Schema_。这里对之前的Gravatar实体稍作修改
type _Schema_
@fulltext(
name: "gravatarSearch"
language: en
algorithm: rank
include: [{ entity: "Gravatar", fields: [{ name: "displayName" }, { name: "description" }] }]
)
type Gravatar @entity(immutable: false) {
id: String!
owner: Bytes! # address
displayName: String! # string
description: String! # string
imageUrl: String! # string
transaction: Transaction
}
支持的language
Code |
Dictionary |
simple |
General |
da |
Danish |
nl |
Dutch |
en |
English |
fi |
Finnish |
fr |
French |
de |
German |
hu |
Hungarian |
it |
Italian |
no |
Norwegian |
pt |
Portuguese |
ro |
Romanian |
ru |
Russian |
es |
Spanish |
sv |
Swedish |
tr |
Turkish |
支持的排序算法(algorithm)
Algorithm |
Description |
rank |
使用全文查询的匹配质量(0-1)对结果进行排序。 |
proximityRank |
类似于rank,但也包括近似的匹配。 |
From specVersion 0.0.4 and onwards, fullTextSearch must be declared under the features section in the subgraph manifest.
从specVersion 0.0.4开始,fullTextSearch必须在subgraph清单的features部分下声明。
修改subgraph.yaml如下
specVersion: 0.0.5
schema:
file: ./schema.graphql
features:
- fullTextSearch
dataSources:
- kind: ethereum
name: GravatarRegistry
network: goerli
source:
address: "0x964F658FC863BAceFC719b85e8730fbc11c86ce4"
abi: GravatarRegistry
startBlock: 8266411
mapping:
kind: ethereum/events
apiVersion: 0.0.7
language: wasm/assemblyscript
entities:
- Gravatar
- Transaction
- Block
abis:
- name: GravatarRegistry
file: ./abis/GravatarRegistry.json
eventHandlers:
- event: NewGravatar(uint256,address,string,string)
handler: handleNewGravatar
- event: UpdatedGravatar(uint256,address,string,string)
handler: handleUpdatedGravatar
callHandlers:
- function: createGravatar(string,string)
handler: handleCreateGravatar
blockHandlers:
- handler: handleBlockWithCallToContract
filter:
kind: call
file: ./src/gravatar-registry.ts
修改gravatar-registry.ts文件,添加description字段
export function handleNewGravatar(event: NewGravatarEvent): void {
let gravatar = new Gravatar(event.params.id.toString());
gravatar.owner = event.params.owner
gravatar.displayName = event.params.displayName
gravatar.imageUrl = event.params.imageUrl
gravatar.description = event.params.imageUrl.replaceAll("/"," ");
gravatar.transaction = event.transaction.hash
gravatar.save()
}
export function handleUpdatedGravatar(event: UpdatedGravatarEvent): void {
let id = event.params.id.toString();
let gravatar = Gravatar.load(id)
if (gravatar == null) {
gravatar = new Gravatar(id)
}
gravatar.owner = event.params.owner
gravatar.displayName = event.params.displayName
gravatar.description = event.params.imageUrl.replaceAll("/"," ");
gravatar.imageUrl = event.params.imageUrl
gravatar.transaction = event.transaction.hash
gravatar.save()
}
重新发布后查询结果如下:
query MyQuery {
gravatarSearch(text: " Carl | https") {
id
displayName
imageUrl
description
}
}
{
"data": {
"gravatarSearch": [
{
"id": "1",
"displayName": "gambo2",
"imageUrl": "https://thegraph.com/img/team/bw_Lucas2.jpg",
"description": "https: thegraph.com img team bw_Lucas2.jpg"
},
{
"id": "2",
"displayName": "gambo017",
"imageUrl": "https://thegraph.com/img/team/team_04.png",
"description": "https: thegraph.com img team team_04.png"
},
{
"id": "0",
"displayName": "Carl",
"imageUrl": "https://thegraph.com/img/team/team_04.png",
"description": "https: thegraph.com img team team_04.png"
}
]
}
}
其排序结果似乎是根据关联度从低到高,暂时还没有找到降序的方法。
查询语法如下:
Symbol |
Operator |
Description |
& |
And |
用于将多个搜索词组合到包含所有提供的词的实体的过滤器中 |
| |
Or |
使用or操作符分隔的多个搜索词的查询将返回与所提供的任何词匹配的所有实体 |
<-> |
Follow by |
指定两个单词之间的距离。 |
:* |
Prefix |
使用前缀搜索词查找前缀匹配的单词(需要2个字符)。 |