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

news/2024/10/19 2:19:09/

上一节我们基于官方示例构建了一个具有基本功能的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: ethereumname: GravatarRegistrynetwork: goerlisource:address: "0x964F658FC863BAceFC719b85e8730fbc11c86ce4"abi: GravatarRegistrystartBlock: 8266411mapping:kind: ethereum/eventsapiVersion: 0.0.7language: wasm/assemblyscriptentities:- Gravatar- Transactionabis:- name: GravatarRegistryfile: ./abis/GravatarRegistry.jsoncallHandlers:- function: createGravatar(string,string)handler: handleCreateGravatar# eventHandlers:#   - event: NewGravatar(uint256,address,string,string)#     handler: handleNewGravatar#   - event: UpdatedGravatar(uint256,address,string,string)#     handler: handleUpdatedGravatarfile: ./src/gravatar-registry.ts

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

schema.graphql:

type Transaction @entity(immutable: true) {id: String!displayName: String! # stringimageUrl: 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._displayNametransaction.imageUrl = call.inputs._imageUrltransaction.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: ethereumname: GravatarRegistrynetwork: goerlisource:address: "0x964F658FC863BAceFC719b85e8730fbc11c86ce4"abi: GravatarRegistrystartBlock: 8266411mapping:kind: ethereum/eventsapiVersion: 0.0.7language: wasm/assemblyscriptentities:- Gravatar- Transaction- Blockabis:- name: GravatarRegistryfile: ./abis/GravatarRegistry.jsoneventHandlers:- event: NewGravatar(uint256,address,string,string)handler: handleNewGravatar- event: UpdatedGravatar(uint256,address,string,string)handler: handleUpdatedGravatarcallHandlers:- function: createGravatar(string,string)handler: handleCreateGravatarblockHandlers:- handler: handleBlockWithCallToContractfilter:kind: callfile: ./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.numberentity.blockTimestamp = block.timestampentity.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! # addressdisplayName: String! # stringimageUrl: String! # stringtransaction: Transaction
}type Transaction @entity(immutable: true) {id: Bytes!block: BlockgasPrice: 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.ownergravatar.displayName = event.params.displayNamegravatar.imageUrl = event.params.imageUrlgravatar.transaction = event.transaction.hashgravatar.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.ownergravatar.displayName = event.params.displayNamegravatar.imageUrl = event.params.imageUrlgravatar.transaction = event.transaction.hashgravatar.save()
}export function handleCreateGravatar(call: CreateGravatarCall): void {let transaction = new Transaction(call.transaction.hash)transaction.gasPrice = call.transaction.gasPricetransaction.block = call.block.hashtransaction.save()
}export function handleBlockWithCallToContract(block: ethereum.Block): void {let entity = new Block(block.hash)entity.blockNumber = block.numberentity.blockTimestamp = block.timestampentity.save()
}

重新发布后作如下查询:

{gravatars(first: 5) {idownerdisplayNameimageUrltransaction{idgasPriceblock {idblockNumber}}}
}

结果如下:

{"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: enalgorithm: rankinclude: [{ entity: "Gravatar", fields: [{ name: "displayName" }, { name: "description" }] }])type Gravatar @entity(immutable: false) {id: String!owner: Bytes! # addressdisplayName: String! # stringdescription: String! # stringimageUrl: String! # stringtransaction: 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: ethereumname: GravatarRegistrynetwork: goerlisource:address: "0x964F658FC863BAceFC719b85e8730fbc11c86ce4"abi: GravatarRegistrystartBlock: 8266411mapping:kind: ethereum/eventsapiVersion: 0.0.7language: wasm/assemblyscriptentities:- Gravatar- Transaction- Blockabis:- name: GravatarRegistryfile: ./abis/GravatarRegistry.jsoneventHandlers:- event: NewGravatar(uint256,address,string,string)handler: handleNewGravatar- event: UpdatedGravatar(uint256,address,string,string)handler: handleUpdatedGravatarcallHandlers:- function: createGravatar(string,string)handler: handleCreateGravatarblockHandlers:- handler: handleBlockWithCallToContractfilter:kind: callfile: ./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.ownergravatar.displayName = event.params.displayNamegravatar.imageUrl = event.params.imageUrlgravatar.description = event.params.imageUrl.replaceAll("/"," ");gravatar.transaction = event.transaction.hashgravatar.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.ownergravatar.displayName = event.params.displayNamegravatar.description = event.params.imageUrl.replaceAll("/"," ");gravatar.imageUrl = event.params.imageUrlgravatar.transaction = event.transaction.hashgravatar.save()
}

重新发布后查询结果如下:

query MyQuery {gravatarSearch(text: " Carl | https") {iddisplayNameimageUrldescription}
}
{"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


http://www.ppmy.cn/news/248237.html

相关文章

2022年4月3日-4月15日(方案A,ogremain源码抄写+ue4视频学习,共22小时,合计1270小时,剩8730小时)

截至2022年4月1日&#xff0c;ogreMain剩下4533行&#xff08;含注释&#xff09;&#xff0c;纯代码2646行 周二时学完了ue第五套视频教程编辑器1&#xff0c;good 接下来 UE4视频教程进行到了mysql(1.1)&#xff0c;tf1(2.1),oss(4.2),simpleThread(1.2),editor2(未开始) 清…

企业综合安防管理平台

企业综合安防管理平台 平台概述 长期以来各厂家以市场为导向&#xff0c;专注于具备自身特长的单一系统产品&#xff0c;造成目前在技防领域出现的众多分项系统各自为政的局面&#xff0c;如单一的视频监控系统、门禁系统、访客系统、停车管理系统、报警系统等。各厂家单一业务…

nginx+tomcat 负载均衡、动静分离集群

文章目录 一、NginxTomcat负载均衡的组合原因1.1 Nginx实现负载均衡的原理1.2 Nginx实现负载均衡的主要配置项1.3 NginxTomcat负载均衡的组合的优点1.4 NginxTomcat负载均衡的实验设计 二、动静分离部署2.1 部署TOMCAT后端服务器2.2部署nginx服务器2.3安装nginx动态服务器 一、…

关于酷派8730“移动版”手机无法将应用安装在外置SD卡的解决办法和获取ROOT权限方法

酷派8730“移动版”手机的rom版本号是“039”&#xff0c;具体可通过输入*#9527*#&#xff0c;进入工厂模式查看。 039版是无法将应用安装在外置SD卡里的&#xff0c;解决办法是升级至049版&#xff1b;即从039-->048-->049&#xff0c;下面是具体做法&#xff1a; 1.在…

Oracle Database 23c新特性之CASE语句/表达式增强

Oracle database 23c 改进了 PL/SQL 程序中的简单 CASE 语句和 CASE 表达式&#xff0c;支持悬空谓词&#xff08;dangling predicate&#xff09;和单个 WHEN 分支中的多项匹配。这个增强是为了更加符合 SQL 标准。 示例表 本文将会使用以下简单示例表&#xff1a; drop ta…

java中try-with-resources自动关闭io流

在传统的输入输出流处理中&#xff0c;我们一般使用的结构如下所示&#xff0c;使用try - catch - finally结构捕获相关异常&#xff0c;最后不管是否有异常&#xff0c;我们都将流进行关闭处理&#xff1a; try {//todo } catch (IOException e) {log.error("read xxx f…

Tomcat优化与动静分离

Tomcat优化 一、Tomcat配置文件参数优化二、负载均衡&#xff0c;动静分离七层代理配置四层代理配置 Tomcat 默认安装下的缺省配置并不适合生产环境&#xff0c;它会频繁出现假死现象需要重启&#xff0c;只有通过不断压测优化才能让它最高效率稳定的运行。优化主要包括三方面&…