The Graph 3 subGraph的callHandler,blockhandler,实体关系和全文索引

上一节我们基于官方示例构建了一个具有基本功能的subgraph,这一节我们介绍其他的一些特性。

callHandler

虽然eventHandler提供了一种有效的方法来收集合约状态的相关更改,但许多合约避免生成event以优化gas成本。在这些情况下,subgraph可以订阅对指定合约方法的调用。这是通过定义引用函数签名的callHandler和处理此函数调用的映射处理程序来实现的。为了处理这些调用, mapping handler将接收一个ethereum.Call作为参数,其中包含调用输入和输出。

callHandler只会在以下两种情况之一触发:当指定的函数由合约本身以外的帐户调用时,或者当它在Solidity中标记为external ,并作为同一合约中另一个函数调用时。

callHandler目前依赖于Parity tracing API。某些网络,如BNB chain和Arbitrum,不支持此API。遇到这些网络,subgraph的开发人员应该使用eventHandle。它们比调用callHandler的性能要好得多,并且在每个evm网络上都受到支持。

定义callHandler,只需在想要订阅的数据源下添加callHandlers数组。

subgraph.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

这次我们以在上一章的示例上稍作修改,重新定义一个实体,使用每次交易的hash值作为id,记录每一次交易的基本信息。

schema.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()
}

重新发布subgraph:

graph codegen
graph build
yarn deploy

查询结果如下:

blockHandlers

除了订阅合约事件或函数调用外,subgraph可能还希望在新区块被追加到链中时更新其数据。为了实现这一点,子图可以在每个块或匹配预定义过滤器的块之后运行一个函数。

所谓的预定义过滤器如下:

filter:
  kind: call

如果块中包含对指定合约的调用,定义的handler函数将会被触发。

同callHandler一样,blockHandlers的filter依赖于Parity tracing API。某些网络,如BNB chain和Arbitrum,不支持此API。

subgraph.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

这次我们以在上一章的示例上稍作修改,重新定义一个实体,使用每次交易的hash值作为id,记录每一次交易的基本信息。

schema.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()
}

重新发布subgraph:

graph codegen
graph build
yarn deploy

查询结果如下:

实体关系

一个实体可能与schema中的一个或多个其他实体有关系。在查询中可以遍历这些关系。The Graph中的关系是单向的,通过在关系的“一端”定义一个单向关系,也可以模拟双向关系。在实体上定义关系就像其他字段一样,只是指定的类型是另一个实体的类型。

上面的示例我们设及到三个实体,Gravatar,Transaction还有Block,他们的关系如下:

由上图可知Gravatar和Transaction是一对一的关系,Block和Transaction是一对多的关系,我们重新定义一下entity的字段:

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()
}

重新发布后作如下查询:

{
  gravatars(first: 5) {
    id
    owner
    displayName
    imageUrl
    transaction{
      id
      gasPrice
      block {
        id
        blockNumber
      }
    }
  }
}

结果如下:

{
  "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
      }
    ]
  }
}

我们观察到有些transaction为null,这是由于我们的callHandler并没有对update函数作捕获,所以相应的transaction并没有保存到graph节点上。

反向查找

对于一对多关系,我们的预期是可以通过“one”端看到“many”端中保存的数组。而我们目前的entity结构,并没有达到这样的预期。

这里可以利用@derivedFrom注解,用于定义实体关系之间的派生。

修改Block实体定义如下:

type Block @entity(immutable: true) {
  id: Bytes!
  blockNumber: BigInt!
  blockTimestamp: BigInt!
  transactions: [Transaction!] @derivedFrom(field: "block")
}

重新发布后的效果如下:

官方这里做了解释:

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个字符)。

https://github.com/ziyiyu/subgraph-example

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值