グラフ 3 サブグラフの callHandler、blockhandler、エンティティ関係、およびフルテキスト インデックス

前のセクションでは公式サンプルに基づいて基本的な関数を備えたサブグラフを構築しましたが、このセクションではその他の機能をいくつか紹介します。

コールハンドラ

EventHandler はコントラクトの状態に対する関連する変更を収集する効率的な方法を提供しますが、多くのコントラクトはガスコストを最適化するためにイベントの生成を回避します。このような場合、サブグラフは、指定されたコントラクト メソッドへの呼び出しをサブスクライブできます。これは、関数シグネチャを参照する callHandler と、この関数への呼び出しを処理するマップされたハンドラーを定義することによって実現されます。これらの呼び出しを処理するために、マッピング ハンドラーはパラメーターとして ethereum.Call を受け取ります。これには呼び出しの入力と出力が含まれます。

callHandler は、指定された関数がコントラクト自体以外のアカウントによって呼び出された場合、またはその関数が Solidity で外部としてマークされ、同じコントラクト内の別の関数として呼び出された場合の 2 つの場合のいずれかでのみ起動されます。

callHandler は現在、パリティ トレース API に依存しています。BNB チェーンや Arbitrum などの特定のネットワークは、この API をサポートしていません。これらのネットワークに遭遇した場合、サブグラフ開発者はeventHandleを使用する必要があります。これらは callHandler を呼び出すよりもはるかに優れたパフォーマンスを発揮し、すべての evm ネットワークでサポートされます。

callHandler を定義するには、サブスクライブするデータ ソースの下に callHandlers 配列を追加するだけです。

サブグラフ.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

今回は前章の例を少し修正してエンティティを再定義し、各トランザクションのハッシュ値をidとして使用し、各トランザクションの基本情報を記録します。

スキーマ.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()
}

サブグラフを再公開します。

graph codegen
graph build
yarn deploy

クエリ結果は次のとおりです。

ブロックハンドラー

サブグラフは、コントラクト イベントまたは関数呼び出しをサブスクライブするだけでなく、新しいブロックがチェーンに追加されたときにデータを更新することもできます。これを実現するために、サブグラフは、ブロックごと、または事前定義されたフィルターに一致するブロックごとに関数を実行できます。

いわゆる事前定義フィルターは次のとおりです。

filter:
  kind: call

ブロックに指定されたコントラクトへの呼び出しが含まれている場合、定義されたハンドラー関数がトリガーされます。

callHandler と同様に、blockHandler のフィルターはパリティ トレース API に依存します。BNB チェーンや Arbitrum などの特定のネットワークは、この API をサポートしていません。

サブグラフ.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

今回は前章の例を少し修正してエンティティを再定義し、各トランザクションのハッシュ値をidとして使用し、各トランザクションの基本情報を記録します。

スキーマ.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()
}

サブグラフを再公開します。

graph codegen
graph build
yarn deploy

クエリ結果は次のとおりです。

エンティティ関係

エンティティは、スキーマ内の 1 つ以上の他のエンティティと関係を持つ場合があります。これらの関係はクエリ内で横断できます。グラフ内の関係は一方向ですが、関係の「一端」で一方向の関係を定義することによって、双方向の関係をシミュレートすることもできます。エンティティに対するリレーションシップの定義は、指定されたタイプが他のエンティティのタイプであることを除いて、他のフィールドとまったく同じです。

上の例では、Gravatar、Transaction、Block の 3 つのエンティティが関係しており、それらの関係は次のとおりです。

上の図から、Gravatar と Transaction は 1 対 1 の関係であり、Block と Transaction は 1 対多の関係であることがわかります。エンティティ フィールドを再定義してみましょう。

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

一部のトランザクションが null であることがわかりましたが、これは callHandler が更新関数をキャプチャしなかったため、対応するトランザクションがグラフ ノードに保存されなかったためです。

逆引き

1 対多の関係では、「多」側に保持されている配列が「1」側から見えることが期待されます。しかし、当社の現在の組織構造はそのような期待に応えられていません。

ここで @derivedFrom アノテーションを使用して、エンティティ関係間の派生を定義できます。

ブロック エンティティの定義を次のように変更します。

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

おすすめ

転載: blog.csdn.net/gambool/article/details/128695729