mirror of
https://github.com/cerc-io/watcher-ts
synced 2024-11-19 20:36:19 +00:00
Implement gql queries for relation entities similar to subgraph (#53)
* Add implementation for one to one relation * Implement one to many relation in gql queries * Make changes for gql relation queries in eden-watcher * Implement subgraph gql relation queries with joins
This commit is contained in:
parent
44b3fd59e8
commit
f52467f724
@ -31,6 +31,22 @@ import MerkleDistributorArtifacts from './artifacts/MerkleDistributor.json';
|
|||||||
import DistributorGovernanceArtifacts from './artifacts/DistributorGovernance.json';
|
import DistributorGovernanceArtifacts from './artifacts/DistributorGovernance.json';
|
||||||
import { createInitialCheckpoint, handleEvent, createStateDiff, createStateCheckpoint } from './hooks';
|
import { createInitialCheckpoint, handleEvent, createStateDiff, createStateCheckpoint } from './hooks';
|
||||||
import { IPFSClient } from './ipfs';
|
import { IPFSClient } from './ipfs';
|
||||||
|
import { ProducerSet } from './entity/ProducerSet';
|
||||||
|
import { Producer } from './entity/Producer';
|
||||||
|
import { RewardSchedule } from './entity/RewardSchedule';
|
||||||
|
import { RewardScheduleEntry } from './entity/RewardScheduleEntry';
|
||||||
|
import { Network } from './entity/Network';
|
||||||
|
import { Staker } from './entity/Staker';
|
||||||
|
import { ProducerEpoch } from './entity/ProducerEpoch';
|
||||||
|
import { Epoch } from './entity/Epoch';
|
||||||
|
import { Block } from './entity/Block';
|
||||||
|
import { SlotClaim } from './entity/SlotClaim';
|
||||||
|
import { Slot } from './entity/Slot';
|
||||||
|
import { Distributor } from './entity/Distributor';
|
||||||
|
import { Distribution } from './entity/Distribution';
|
||||||
|
import { Claim } from './entity/Claim';
|
||||||
|
import { Account } from './entity/Account';
|
||||||
|
import { Slash } from './entity/Slash';
|
||||||
|
|
||||||
const log = debug('vulcanize:indexer');
|
const log = debug('vulcanize:indexer');
|
||||||
|
|
||||||
@ -117,6 +133,8 @@ export class Indexer implements IndexerInterface {
|
|||||||
|
|
||||||
_ipfsClient: IPFSClient
|
_ipfsClient: IPFSClient
|
||||||
|
|
||||||
|
_relationsMap: Map<any, { [key: string]: any }>
|
||||||
|
|
||||||
constructor (serverConfig: ServerConfig, db: Database, ethClient: EthClient, postgraphileClient: EthClient, ethProvider: BaseProvider, jobQueue: JobQueue, graphWatcher: GraphWatcher) {
|
constructor (serverConfig: ServerConfig, db: Database, ethClient: EthClient, postgraphileClient: EthClient, ethProvider: BaseProvider, jobQueue: JobQueue, graphWatcher: GraphWatcher) {
|
||||||
assert(db);
|
assert(db);
|
||||||
assert(ethClient);
|
assert(ethClient);
|
||||||
@ -160,6 +178,9 @@ export class Indexer implements IndexerInterface {
|
|||||||
this._contractMap.set(KIND_DISTRIBUTORGOVERNANCE, new ethers.utils.Interface(DistributorGovernanceABI));
|
this._contractMap.set(KIND_DISTRIBUTORGOVERNANCE, new ethers.utils.Interface(DistributorGovernanceABI));
|
||||||
|
|
||||||
this._ipfsClient = new IPFSClient(this._serverConfig.ipfsApiAddr);
|
this._ipfsClient = new IPFSClient(this._serverConfig.ipfsApiAddr);
|
||||||
|
|
||||||
|
this._relationsMap = new Map();
|
||||||
|
this._populateRelationsMap();
|
||||||
}
|
}
|
||||||
|
|
||||||
getResultEvent (event: Event): ResultEvent {
|
getResultEvent (event: Event): ResultEvent {
|
||||||
@ -527,7 +548,11 @@ export class Indexer implements IndexerInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getSubgraphEntity<Entity> (entity: new () => Entity, id: string, blockHash: string): Promise<any> {
|
async getSubgraphEntity<Entity> (entity: new () => Entity, id: string, blockHash: string): Promise<any> {
|
||||||
return this._graphWatcher.getEntity(entity, id, blockHash);
|
const relations = this._relationsMap.get(entity) || {};
|
||||||
|
|
||||||
|
const data = await this._graphWatcher.getEntity(entity, id, blockHash, relations);
|
||||||
|
|
||||||
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
async triggerIndexingOnEvent (event: Event): Promise<void> {
|
async triggerIndexingOnEvent (event: Event): Promise<void> {
|
||||||
@ -1097,6 +1122,87 @@ export class Indexer implements IndexerInterface {
|
|||||||
return this._baseIndexer.getAncestorAtDepth(blockHash, depth);
|
return this._baseIndexer.getAncestorAtDepth(blockHash, depth);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_populateRelationsMap (): void {
|
||||||
|
// Needs to be generated by codegen.
|
||||||
|
this._relationsMap.set(ProducerSet, {
|
||||||
|
producers: {
|
||||||
|
entity: Producer,
|
||||||
|
isArray: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this._relationsMap.set(RewardSchedule, {
|
||||||
|
rewardScheduleEntries: {
|
||||||
|
entity: RewardScheduleEntry,
|
||||||
|
isArray: true
|
||||||
|
},
|
||||||
|
activeRewardScheduleEntry: {
|
||||||
|
entity: RewardScheduleEntry,
|
||||||
|
isArray: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this._relationsMap.set(ProducerEpoch, {
|
||||||
|
epoch: {
|
||||||
|
entity: Epoch,
|
||||||
|
isArray: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this._relationsMap.set(Epoch, {
|
||||||
|
startBlock: {
|
||||||
|
entity: Block,
|
||||||
|
isArray: false
|
||||||
|
},
|
||||||
|
endBlock: {
|
||||||
|
entity: Block,
|
||||||
|
isArray: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this._relationsMap.set(SlotClaim, {
|
||||||
|
slot: {
|
||||||
|
entity: Slot,
|
||||||
|
isArray: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this._relationsMap.set(Network, {
|
||||||
|
stakers: {
|
||||||
|
entity: Staker,
|
||||||
|
isArray: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this._relationsMap.set(Distributor, {
|
||||||
|
currentDistribution: {
|
||||||
|
entity: Distribution,
|
||||||
|
isArray: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this._relationsMap.set(Distribution, {
|
||||||
|
distributor: {
|
||||||
|
entity: Distributor,
|
||||||
|
isArray: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this._relationsMap.set(Claim, {
|
||||||
|
account: {
|
||||||
|
entity: Account,
|
||||||
|
isArray: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this._relationsMap.set(Slash, {
|
||||||
|
account: {
|
||||||
|
entity: Account,
|
||||||
|
isArray: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async _fetchAndSaveEvents ({ cid: blockCid, blockHash }: DeepPartial<BlockProgress>): Promise<void> {
|
async _fetchAndSaveEvents ({ cid: blockCid, blockHash }: DeepPartial<BlockProgress>): Promise<void> {
|
||||||
assert(blockHash);
|
assert(blockHash);
|
||||||
let { block, logs } = await this._ethClient.getLogs({ blockHash });
|
let { block, logs } = await this._ethClient.getLogs({ blockHash });
|
||||||
|
@ -73,6 +73,65 @@ export class Database {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getEntityWithRelations<Entity> (entity: (new () => Entity) | string, id: string, blockHash: string, relations: { [key: string]: any }): Promise<Entity | undefined> {
|
||||||
|
const queryRunner = this._conn.createQueryRunner();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const repo = queryRunner.manager.getRepository(entity);
|
||||||
|
|
||||||
|
// Fetching blockHash for previous entity in frothy region.
|
||||||
|
const { blockHash: entityblockHash, blockNumber, id: frothyId } = await this._baseDatabase.getFrothyEntity(queryRunner, repo, { blockHash, id });
|
||||||
|
|
||||||
|
let selectQueryBuilder = repo.createQueryBuilder('entity');
|
||||||
|
|
||||||
|
if (frothyId) {
|
||||||
|
// If entity found in frothy region.
|
||||||
|
selectQueryBuilder = selectQueryBuilder.where('entity.block_hash = :entityblockHash', { entityblockHash });
|
||||||
|
} else {
|
||||||
|
// If entity not in frothy region.
|
||||||
|
const canonicalBlockNumber = blockNumber + 1;
|
||||||
|
|
||||||
|
selectQueryBuilder = selectQueryBuilder.innerJoinAndSelect('block_progress', 'block', 'block.block_hash = entity.block_hash')
|
||||||
|
.where('block.is_pruned = false')
|
||||||
|
.andWhere('entity.block_number <= :canonicalBlockNumber', { canonicalBlockNumber })
|
||||||
|
.orderBy('entity.block_number', 'DESC')
|
||||||
|
.limit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
selectQueryBuilder = selectQueryBuilder.andWhere('entity.id = :id', { id });
|
||||||
|
|
||||||
|
// TODO: Implement query for nested relations.
|
||||||
|
Object.entries(relations).forEach(([field, data], index) => {
|
||||||
|
const { entity: relatedEntity, isArray } = data;
|
||||||
|
const alias = `relatedEntity${index}`;
|
||||||
|
|
||||||
|
if (isArray) {
|
||||||
|
// For one to many relational field.
|
||||||
|
selectQueryBuilder = selectQueryBuilder.leftJoinAndMapMany(
|
||||||
|
`entity.${field}`,
|
||||||
|
relatedEntity,
|
||||||
|
alias,
|
||||||
|
`${alias}.id IN (SELECT unnest(entity.${field})) AND ${alias}.block_number <= entity.block_number`
|
||||||
|
)
|
||||||
|
.addOrderBy(`${alias}.block_number`, 'DESC');
|
||||||
|
} else {
|
||||||
|
// For one to one relational field.
|
||||||
|
selectQueryBuilder = selectQueryBuilder.leftJoinAndMapOne(
|
||||||
|
`entity.${field}`,
|
||||||
|
relatedEntity,
|
||||||
|
alias,
|
||||||
|
`entity.${field} = ${alias}.id AND ${alias}.block_number <= entity.block_number`
|
||||||
|
)
|
||||||
|
.addOrderBy(`${alias}.block_number`, 'DESC');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return selectQueryBuilder.getOne();
|
||||||
|
} finally {
|
||||||
|
await queryRunner.release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async saveEntity (entity: string, data: any): Promise<void> {
|
async saveEntity (entity: string, data: any): Promise<void> {
|
||||||
const repo = this._conn.getRepository(entity);
|
const repo = this._conn.getRepository(entity);
|
||||||
|
|
||||||
|
@ -184,9 +184,9 @@ export class GraphWatcher {
|
|||||||
this._indexer = indexer;
|
this._indexer = indexer;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getEntity<Entity> (entity: new () => Entity, id: string, blockHash: string): Promise<any> {
|
async getEntity<Entity> (entity: new () => Entity, id: string, blockHash: string, relations: { [key: string]: any }): Promise<any> {
|
||||||
// Get entity from the database.
|
// Get entity from the database.
|
||||||
const result = await this._database.getEntity(entity, id, blockHash) as any;
|
const result = await this._database.getEntityWithRelations(entity, id, blockHash, relations) as any;
|
||||||
|
|
||||||
// Resolve any field name conflicts in the entity result.
|
// Resolve any field name conflicts in the entity result.
|
||||||
return resolveEntityFieldConflicts(result);
|
return resolveEntityFieldConflicts(result);
|
||||||
|
@ -18,7 +18,6 @@ export class RelatedEntity extends Entity {
|
|||||||
this.set("id", Value.fromString(id));
|
this.set("id", Value.fromString(id));
|
||||||
|
|
||||||
this.set("paramBigInt", Value.fromBigInt(BigInt.zero()));
|
this.set("paramBigInt", Value.fromBigInt(BigInt.zero()));
|
||||||
this.set("examples", Value.fromStringArray(new Array(0)));
|
|
||||||
this.set("bigIntArray", Value.fromBigIntArray(new Array(0)));
|
this.set("bigIntArray", Value.fromBigIntArray(new Array(0)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -57,15 +56,6 @@ export class RelatedEntity extends Entity {
|
|||||||
this.set("paramBigInt", Value.fromBigInt(value));
|
this.set("paramBigInt", Value.fromBigInt(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
get examples(): Array<string> {
|
|
||||||
let value = this.get("examples");
|
|
||||||
return value!.toStringArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
set examples(value: Array<string>) {
|
|
||||||
this.set("examples", Value.fromStringArray(value));
|
|
||||||
}
|
|
||||||
|
|
||||||
get bigIntArray(): Array<BigInt> {
|
get bigIntArray(): Array<BigInt> {
|
||||||
let value = this.get("bigIntArray");
|
let value = this.get("bigIntArray");
|
||||||
return value!.toBigIntArray();
|
return value!.toBigIntArray();
|
||||||
@ -89,6 +79,7 @@ export class ExampleEntity extends Entity {
|
|||||||
this.set("paramEnum", Value.fromString(""));
|
this.set("paramEnum", Value.fromString(""));
|
||||||
this.set("paramBigDecimal", Value.fromBigDecimal(BigDecimal.zero()));
|
this.set("paramBigDecimal", Value.fromBigDecimal(BigDecimal.zero()));
|
||||||
this.set("related", Value.fromString(""));
|
this.set("related", Value.fromString(""));
|
||||||
|
this.set("manyRelated", Value.fromStringArray(new Array(0)));
|
||||||
}
|
}
|
||||||
|
|
||||||
save(): void {
|
save(): void {
|
||||||
@ -188,4 +179,59 @@ export class ExampleEntity extends Entity {
|
|||||||
set related(value: string) {
|
set related(value: string) {
|
||||||
this.set("related", Value.fromString(value));
|
this.set("related", Value.fromString(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
get manyRelated(): Array<string> {
|
||||||
|
let value = this.get("manyRelated");
|
||||||
|
return value!.toStringArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
set manyRelated(value: Array<string>) {
|
||||||
|
this.set("manyRelated", Value.fromStringArray(value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class ManyRelatedEntity extends Entity {
|
||||||
|
constructor(id: string) {
|
||||||
|
super();
|
||||||
|
this.set("id", Value.fromString(id));
|
||||||
|
|
||||||
|
this.set("count", Value.fromBigInt(BigInt.zero()));
|
||||||
|
}
|
||||||
|
|
||||||
|
save(): void {
|
||||||
|
let id = this.get("id");
|
||||||
|
assert(id != null, "Cannot save ManyRelatedEntity entity without an ID");
|
||||||
|
if (id) {
|
||||||
|
assert(
|
||||||
|
id.kind == ValueKind.STRING,
|
||||||
|
"Cannot save ManyRelatedEntity entity with non-string ID. " +
|
||||||
|
'Considering using .toHex() to convert the "id" to a string.'
|
||||||
|
);
|
||||||
|
store.set("ManyRelatedEntity", id.toString(), this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static load(id: string): ManyRelatedEntity | null {
|
||||||
|
return changetype<ManyRelatedEntity | null>(
|
||||||
|
store.get("ManyRelatedEntity", id)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
get id(): string {
|
||||||
|
let value = this.get("id");
|
||||||
|
return value!.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
set id(value: string) {
|
||||||
|
this.set("id", Value.fromString(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
get count(): BigInt {
|
||||||
|
let value = this.get("count");
|
||||||
|
return value!.toBigInt();
|
||||||
|
}
|
||||||
|
|
||||||
|
set count(value: BigInt) {
|
||||||
|
this.set("count", Value.fromBigInt(value));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,6 @@ enum EnumType {
|
|||||||
type RelatedEntity @entity {
|
type RelatedEntity @entity {
|
||||||
id: ID!
|
id: ID!
|
||||||
paramBigInt: BigInt!
|
paramBigInt: BigInt!
|
||||||
examples: [ExampleEntity!]!
|
|
||||||
bigIntArray: [BigInt!]!
|
bigIntArray: [BigInt!]!
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -20,4 +19,10 @@ type ExampleEntity @entity {
|
|||||||
paramEnum: EnumType!
|
paramEnum: EnumType!
|
||||||
paramBigDecimal: BigDecimal!
|
paramBigDecimal: BigDecimal!
|
||||||
related: RelatedEntity!
|
related: RelatedEntity!
|
||||||
|
manyRelated: [ManyRelatedEntity!]!
|
||||||
|
}
|
||||||
|
|
||||||
|
type ManyRelatedEntity @entity {
|
||||||
|
id: ID!
|
||||||
|
count: BigInt!
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,7 @@ import {
|
|||||||
Example1,
|
Example1,
|
||||||
Test
|
Test
|
||||||
} from '../generated/Example1/Example1';
|
} from '../generated/Example1/Example1';
|
||||||
import { ExampleEntity, RelatedEntity } from '../generated/schema';
|
import { ExampleEntity, ManyRelatedEntity, RelatedEntity } from '../generated/schema';
|
||||||
|
|
||||||
export function handleTest (event: Test): void {
|
export function handleTest (event: Test): void {
|
||||||
log.debug('event.address: {}', [event.address.toHexString()]);
|
log.debug('event.address: {}', [event.address.toHexString()]);
|
||||||
@ -15,12 +15,12 @@ export function handleTest (event: Test): void {
|
|||||||
|
|
||||||
// Entities can be loaded from the store using a string ID; this ID
|
// Entities can be loaded from the store using a string ID; this ID
|
||||||
// needs to be unique across all entities of the same type
|
// needs to be unique across all entities of the same type
|
||||||
let entity = ExampleEntity.load(event.transaction.hash.toHexString());
|
let entity = ExampleEntity.load(event.transaction.from.toHex());
|
||||||
|
|
||||||
// Entities only exist after they have been saved to the store;
|
// Entities only exist after they have been saved to the store;
|
||||||
// `null` checks allow to create entities on demand
|
// `null` checks allow to create entities on demand
|
||||||
if (!entity) {
|
if (!entity) {
|
||||||
entity = new ExampleEntity(event.transaction.hash.toHexString());
|
entity = new ExampleEntity(event.transaction.from.toHex());
|
||||||
|
|
||||||
// Entity fields can be set using simple assignments
|
// Entity fields can be set using simple assignments
|
||||||
entity.count = BigInt.fromString('0');
|
entity.count = BigInt.fromString('0');
|
||||||
@ -37,10 +37,10 @@ export function handleTest (event: Test): void {
|
|||||||
entity.paramEnum = 'choice1';
|
entity.paramEnum = 'choice1';
|
||||||
entity.paramBigDecimal = BigDecimal.fromString('123');
|
entity.paramBigDecimal = BigDecimal.fromString('123');
|
||||||
|
|
||||||
let relatedEntity = RelatedEntity.load(event.transaction.from.toHex());
|
let relatedEntity = RelatedEntity.load(event.params.param1);
|
||||||
|
|
||||||
if (!relatedEntity) {
|
if (!relatedEntity) {
|
||||||
relatedEntity = new RelatedEntity(event.transaction.from.toHex());
|
relatedEntity = new RelatedEntity(event.params.param1);
|
||||||
relatedEntity.paramBigInt = BigInt.fromString('123');
|
relatedEntity.paramBigInt = BigInt.fromString('123');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,14 +48,17 @@ export function handleTest (event: Test): void {
|
|||||||
bigIntArray.push(entity.count);
|
bigIntArray.push(entity.count);
|
||||||
relatedEntity.bigIntArray = bigIntArray;
|
relatedEntity.bigIntArray = bigIntArray;
|
||||||
|
|
||||||
const examples = relatedEntity.examples;
|
|
||||||
examples.push(entity.id);
|
|
||||||
relatedEntity.examples = examples;
|
|
||||||
|
|
||||||
relatedEntity.save();
|
relatedEntity.save();
|
||||||
|
|
||||||
entity.related = relatedEntity.id;
|
entity.related = relatedEntity.id;
|
||||||
|
|
||||||
|
const manyRelatedEntity = new ManyRelatedEntity(event.transaction.hash.toHexString());
|
||||||
|
manyRelatedEntity.count = entity.count;
|
||||||
|
manyRelatedEntity.save();
|
||||||
|
|
||||||
|
const manyRelated = entity.manyRelated;
|
||||||
|
manyRelated.push(manyRelatedEntity.id);
|
||||||
|
entity.manyRelated = manyRelated;
|
||||||
|
|
||||||
// Entities can be written to the store with `.save()`
|
// Entities can be written to the store with `.save()`
|
||||||
entity.save();
|
entity.save();
|
||||||
|
|
||||||
|
@ -50,4 +50,7 @@ export class ExampleEntity {
|
|||||||
|
|
||||||
@Column('varchar')
|
@Column('varchar')
|
||||||
related!: string;
|
related!: string;
|
||||||
|
|
||||||
|
@Column('varchar', { array: true })
|
||||||
|
manyRelated!: string[]
|
||||||
}
|
}
|
||||||
|
22
packages/graph-test-watcher/src/entity/ManyRelatedEntity.ts
Normal file
22
packages/graph-test-watcher/src/entity/ManyRelatedEntity.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
//
|
||||||
|
// Copyright 2021 Vulcanize, Inc.
|
||||||
|
//
|
||||||
|
|
||||||
|
import { Entity, PrimaryColumn, Column } from 'typeorm';
|
||||||
|
|
||||||
|
import { bigintTransformer } from '@vulcanize/util';
|
||||||
|
|
||||||
|
@Entity()
|
||||||
|
export class ManyRelatedEntity {
|
||||||
|
@PrimaryColumn('varchar')
|
||||||
|
id!: string;
|
||||||
|
|
||||||
|
@PrimaryColumn('varchar', { length: 66 })
|
||||||
|
blockHash!: string;
|
||||||
|
|
||||||
|
@Column('integer')
|
||||||
|
blockNumber!: number;
|
||||||
|
|
||||||
|
@Column('bigint', { transformer: bigintTransformer })
|
||||||
|
count!: bigint;
|
||||||
|
}
|
@ -20,9 +20,6 @@ export class RelatedEntity {
|
|||||||
@Column('bigint', { transformer: bigintTransformer })
|
@Column('bigint', { transformer: bigintTransformer })
|
||||||
paramBigInt!: bigint;
|
paramBigInt!: bigint;
|
||||||
|
|
||||||
@Column('varchar', { array: true })
|
|
||||||
examples!: string[];
|
|
||||||
|
|
||||||
@Column('bigint', { transformer: bigintArrayTransformer, array: true })
|
@Column('bigint', { transformer: bigintArrayTransformer, array: true })
|
||||||
bigIntArray!: bigint[];
|
bigIntArray!: bigint[];
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,9 @@ import { IPLDBlock } from './entity/IPLDBlock';
|
|||||||
import artifacts from './artifacts/Example.json';
|
import artifacts from './artifacts/Example.json';
|
||||||
import { createInitialCheckpoint, handleEvent, createStateDiff, createStateCheckpoint } from './hooks';
|
import { createInitialCheckpoint, handleEvent, createStateDiff, createStateCheckpoint } from './hooks';
|
||||||
import { IPFSClient } from './ipfs';
|
import { IPFSClient } from './ipfs';
|
||||||
|
import { ExampleEntity } from './entity/ExampleEntity';
|
||||||
|
import { RelatedEntity } from './entity/RelatedEntity';
|
||||||
|
import { ManyRelatedEntity } from './entity/ManyRelatedEntity';
|
||||||
|
|
||||||
const log = debug('vulcanize:indexer');
|
const log = debug('vulcanize:indexer');
|
||||||
|
|
||||||
@ -87,6 +90,8 @@ export class Indexer implements IndexerInterface {
|
|||||||
|
|
||||||
_ipfsClient: IPFSClient
|
_ipfsClient: IPFSClient
|
||||||
|
|
||||||
|
_relationsMap: Map<any, { [key: string]: any }>
|
||||||
|
|
||||||
constructor (serverConfig: ServerConfig, db: Database, ethClient: EthClient, postgraphileClient: EthClient, ethProvider: BaseProvider, jobQueue: JobQueue, graphWatcher: GraphWatcher) {
|
constructor (serverConfig: ServerConfig, db: Database, ethClient: EthClient, postgraphileClient: EthClient, ethProvider: BaseProvider, jobQueue: JobQueue, graphWatcher: GraphWatcher) {
|
||||||
assert(db);
|
assert(db);
|
||||||
assert(ethClient);
|
assert(ethClient);
|
||||||
@ -111,6 +116,9 @@ export class Indexer implements IndexerInterface {
|
|||||||
this._contract = new ethers.utils.Interface(this._abi);
|
this._contract = new ethers.utils.Interface(this._abi);
|
||||||
|
|
||||||
this._ipfsClient = new IPFSClient(this._serverConfig.ipfsApiAddr);
|
this._ipfsClient = new IPFSClient(this._serverConfig.ipfsApiAddr);
|
||||||
|
|
||||||
|
this._relationsMap = new Map();
|
||||||
|
this._populateRelationsMap();
|
||||||
}
|
}
|
||||||
|
|
||||||
getResultEvent (event: Event): ResultEvent {
|
getResultEvent (event: Event): ResultEvent {
|
||||||
@ -537,7 +545,11 @@ export class Indexer implements IndexerInterface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getSubgraphEntity<Entity> (entity: new () => Entity, id: string, blockHash: string): Promise<Entity | undefined> {
|
async getSubgraphEntity<Entity> (entity: new () => Entity, id: string, blockHash: string): Promise<Entity | undefined> {
|
||||||
return this._graphWatcher.getEntity(entity, id, blockHash);
|
const relations = this._relationsMap.get(entity) || {};
|
||||||
|
|
||||||
|
const data = await this._graphWatcher.getEntity(entity, id, blockHash, relations);
|
||||||
|
|
||||||
|
return data;
|
||||||
}
|
}
|
||||||
|
|
||||||
async triggerIndexingOnEvent (event: Event): Promise<void> {
|
async triggerIndexingOnEvent (event: Event): Promise<void> {
|
||||||
@ -708,6 +720,20 @@ export class Indexer implements IndexerInterface {
|
|||||||
return this._baseIndexer.getAncestorAtDepth(blockHash, depth);
|
return this._baseIndexer.getAncestorAtDepth(blockHash, depth);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_populateRelationsMap (): void {
|
||||||
|
// Needs to be generated by codegen.
|
||||||
|
this._relationsMap.set(ExampleEntity, {
|
||||||
|
related: {
|
||||||
|
entity: RelatedEntity,
|
||||||
|
isArray: false
|
||||||
|
},
|
||||||
|
manyRelated: {
|
||||||
|
entity: ManyRelatedEntity,
|
||||||
|
isArray: true
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
async _fetchAndSaveEvents ({ cid: blockCid, blockHash }: DeepPartial<BlockProgress>): Promise<void> {
|
async _fetchAndSaveEvents ({ cid: blockCid, blockHash }: DeepPartial<BlockProgress>): Promise<void> {
|
||||||
assert(blockHash);
|
assert(blockHash);
|
||||||
let { block, logs } = await this._ethClient.getLogs({ blockHash });
|
let { block, logs } = await this._ethClient.getLogs({ blockHash });
|
||||||
|
@ -13,6 +13,7 @@ import { EventWatcher } from './events';
|
|||||||
|
|
||||||
import { ExampleEntity } from './entity/ExampleEntity';
|
import { ExampleEntity } from './entity/ExampleEntity';
|
||||||
import { RelatedEntity } from './entity/RelatedEntity';
|
import { RelatedEntity } from './entity/RelatedEntity';
|
||||||
|
import { ManyRelatedEntity } from './entity/ManyRelatedEntity';
|
||||||
|
|
||||||
const log = debug('vulcanize:resolver');
|
const log = debug('vulcanize:resolver');
|
||||||
|
|
||||||
@ -56,16 +57,22 @@ export const createResolvers = async (indexer: Indexer, eventWatcher: EventWatch
|
|||||||
return indexer._test(blockHash, contractAddress);
|
return indexer._test(blockHash, contractAddress);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
exampleEntity: async (_: any, { id, blockHash }: { id: string, blockHash: string }): Promise<ExampleEntity | undefined> => {
|
||||||
|
log('exampleEntity', id, blockHash);
|
||||||
|
|
||||||
|
return indexer.getSubgraphEntity(ExampleEntity, id, blockHash);
|
||||||
|
},
|
||||||
|
|
||||||
relatedEntity: async (_: any, { id, blockHash }: { id: string, blockHash: string }): Promise<RelatedEntity | undefined> => {
|
relatedEntity: async (_: any, { id, blockHash }: { id: string, blockHash: string }): Promise<RelatedEntity | undefined> => {
|
||||||
log('relatedEntity', id, blockHash);
|
log('relatedEntity', id, blockHash);
|
||||||
|
|
||||||
return indexer.getSubgraphEntity(RelatedEntity, id, blockHash);
|
return indexer.getSubgraphEntity(RelatedEntity, id, blockHash);
|
||||||
},
|
},
|
||||||
|
|
||||||
exampleEntity: async (_: any, { id, blockHash }: { id: string, blockHash: string }): Promise<ExampleEntity | undefined> => {
|
manyRelatedEntity: async (_: any, { id, blockHash }: { id: string, blockHash: string }): Promise<ManyRelatedEntity | undefined> => {
|
||||||
log('exampleEntity', id, blockHash);
|
log('relatedEntity', id, blockHash);
|
||||||
|
|
||||||
return indexer.getSubgraphEntity(ExampleEntity, id, blockHash);
|
return indexer.getSubgraphEntity(ManyRelatedEntity, id, blockHash);
|
||||||
},
|
},
|
||||||
|
|
||||||
events: async (_: any, { blockHash, contractAddress, name }: { blockHash: string, contractAddress: string, name?: string }) => {
|
events: async (_: any, { blockHash, contractAddress, name }: { blockHash: string, contractAddress: string, name?: string }) => {
|
||||||
|
@ -74,6 +74,7 @@ type Query {
|
|||||||
_test(blockHash: String!, contractAddress: String!): ResultBigInt!
|
_test(blockHash: String!, contractAddress: String!): ResultBigInt!
|
||||||
relatedEntity(id: String!, blockHash: String!): RelatedEntity!
|
relatedEntity(id: String!, blockHash: String!): RelatedEntity!
|
||||||
exampleEntity(id: String!, blockHash: String!): ExampleEntity!
|
exampleEntity(id: String!, blockHash: String!): ExampleEntity!
|
||||||
|
manyRelatedEntity(id: String!, blockHash: String!): ManyRelatedEntity!
|
||||||
getStateByCID(cid: String!): ResultIPLDBlock
|
getStateByCID(cid: String!): ResultIPLDBlock
|
||||||
getState(blockHash: String!, contractAddress: String!, kind: String): ResultIPLDBlock
|
getState(blockHash: String!, contractAddress: String!, kind: String): ResultIPLDBlock
|
||||||
}
|
}
|
||||||
@ -86,10 +87,14 @@ enum EnumType {
|
|||||||
type RelatedEntity {
|
type RelatedEntity {
|
||||||
id: ID!
|
id: ID!
|
||||||
paramBigInt: BigInt!
|
paramBigInt: BigInt!
|
||||||
examples: [ExampleEntity!]!
|
|
||||||
bigIntArray: [BigInt!]!
|
bigIntArray: [BigInt!]!
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type ManyRelatedEntity {
|
||||||
|
id: ID!
|
||||||
|
count: BigInt!
|
||||||
|
}
|
||||||
|
|
||||||
type ExampleEntity {
|
type ExampleEntity {
|
||||||
id: ID!
|
id: ID!
|
||||||
count: BigInt!
|
count: BigInt!
|
||||||
@ -100,6 +105,7 @@ type ExampleEntity {
|
|||||||
paramEnum: EnumType!
|
paramEnum: EnumType!
|
||||||
paramBigDecimal: BigDecimal!
|
paramBigDecimal: BigDecimal!
|
||||||
related: RelatedEntity!
|
related: RelatedEntity!
|
||||||
|
manyRelated: [ManyRelatedEntity!]!
|
||||||
}
|
}
|
||||||
|
|
||||||
type Mutation {
|
type Mutation {
|
||||||
|
@ -420,7 +420,7 @@ export class Database {
|
|||||||
return selectQueryBuilder.getMany();
|
return selectQueryBuilder.getMany();
|
||||||
}
|
}
|
||||||
|
|
||||||
async getPrevEntityVersion<Entity> (queryRunner: QueryRunner, repo: Repository<Entity>, findOptions: { [key: string]: any }): Promise<Entity | undefined> {
|
async getFrothyEntity<Entity> (queryRunner: QueryRunner, repo: Repository<Entity>, data: { blockHash: string, id: string }): Promise<{ blockHash: string, blockNumber: number, id: string }> {
|
||||||
// Hierarchical query for getting the entity in the frothy region.
|
// Hierarchical query for getting the entity in the frothy region.
|
||||||
const heirerchicalQuery = `
|
const heirerchicalQuery = `
|
||||||
WITH RECURSIVE cte_query AS
|
WITH RECURSIVE cte_query AS
|
||||||
@ -466,7 +466,13 @@ export class Database {
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
// Fetching blockHash for previous entity in frothy region.
|
// Fetching blockHash for previous entity in frothy region.
|
||||||
const [{ block_hash: blockHash, block_number: blockNumber, id }] = await queryRunner.query(heirerchicalQuery, [findOptions.where.blockHash, findOptions.where.id, MAX_REORG_DEPTH]);
|
const [{ block_hash: blockHash, block_number: blockNumber, id }] = await queryRunner.query(heirerchicalQuery, [data.blockHash, data.id, MAX_REORG_DEPTH]);
|
||||||
|
|
||||||
|
return { blockHash, blockNumber, id };
|
||||||
|
}
|
||||||
|
|
||||||
|
async getPrevEntityVersion<Entity> (queryRunner: QueryRunner, repo: Repository<Entity>, findOptions: { [key: string]: any }): Promise<Entity | undefined> {
|
||||||
|
const { blockHash, blockNumber, id } = await this.getFrothyEntity(queryRunner, repo, findOptions.where);
|
||||||
|
|
||||||
if (id) {
|
if (id) {
|
||||||
// Entity found in frothy region.
|
// Entity found in frothy region.
|
||||||
|
Loading…
Reference in New Issue
Block a user