Handle subgraph schema field with derivedFrom directive (#60)

* Handle subgraph schema field with derivedFrom directive

* Handle derivedFrom directive in eden-watcher

* Fix 1 to N relation error by removing limit from query

* Order by id for derivedFrom relations to match graph-node

* Refactor example subgraph schema entities

* Fix watcher queries to return correct relation field values

* Fix hierarchical query for getting two entities at same block
This commit is contained in:
nikugogoi 2021-11-26 16:37:33 +05:30 committed by nabarun
parent b04f6f2fba
commit 238ad21189
26 changed files with 482 additions and 376 deletions

View File

@ -2,9 +2,8 @@
// Copyright 2021 Vulcanize, Inc. // Copyright 2021 Vulcanize, Inc.
// //
import { Entity, PrimaryColumn, Column, ManyToOne } from 'typeorm'; import { Entity, PrimaryColumn, Column } from 'typeorm';
import { Claim } from './Claim';
import { Slash } from './Slash';
import { bigintTransformer } from '@vulcanize/util'; import { bigintTransformer } from '@vulcanize/util';
@Entity() @Entity()
@ -23,10 +22,4 @@ export class Account {
@Column('bigint', { transformer: bigintTransformer }) @Column('bigint', { transformer: bigintTransformer })
totalSlashed!: bigint; totalSlashed!: bigint;
@ManyToOne(() => Claim)
claims!: Claim;
@ManyToOne(() => Slash)
slashes!: Slash;
} }

View File

@ -2,10 +2,9 @@
// Copyright 2021 Vulcanize, Inc. // Copyright 2021 Vulcanize, Inc.
// //
import { Entity, PrimaryColumn, Column, ManyToOne } from 'typeorm'; import { Entity, PrimaryColumn, Column } from 'typeorm';
import Decimal from 'decimal.js'; import Decimal from 'decimal.js';
import { ProducerEpoch } from './ProducerEpoch';
import { bigintTransformer, decimalTransformer } from '@vulcanize/util'; import { bigintTransformer, decimalTransformer } from '@vulcanize/util';
@Entity() @Entity()
@ -39,7 +38,4 @@ export class Epoch {
@Column('numeric', { default: 0, transformer: decimalTransformer }) @Column('numeric', { default: 0, transformer: decimalTransformer })
producerBlocksRatio!: Decimal; producerBlocksRatio!: Decimal;
@ManyToOne(() => ProducerEpoch)
producerRewards!: ProducerEpoch;
} }

View File

@ -2,13 +2,11 @@
// Copyright 2021 Vulcanize, Inc. // Copyright 2021 Vulcanize, Inc.
// //
import { Entity, PrimaryColumn, Column, ManyToOne } from 'typeorm'; import { Entity, PrimaryColumn, Column } from 'typeorm';
import Decimal from 'decimal.js'; import Decimal from 'decimal.js';
import { bigintTransformer, decimalTransformer } from '@vulcanize/util'; import { bigintTransformer, decimalTransformer } from '@vulcanize/util';
import { SlotClaim } from './SlotClaim';
@Entity() @Entity()
export class Slot { export class Slot {
@PrimaryColumn('varchar') @PrimaryColumn('varchar')
@ -40,7 +38,4 @@ export class Slot {
@Column('numeric', { default: 0, transformer: decimalTransformer }) @Column('numeric', { default: 0, transformer: decimalTransformer })
taxRatePerDay!: Decimal; taxRatePerDay!: Decimal;
@ManyToOne(() => SlotClaim)
claims!: SlotClaim;
} }

View File

@ -16,7 +16,7 @@ import { BaseProvider } from '@ethersproject/providers';
import * as codec from '@ipld/dag-cbor'; import * as codec from '@ipld/dag-cbor';
import { EthClient } from '@vulcanize/ipld-eth-client'; import { EthClient } from '@vulcanize/ipld-eth-client';
import { StorageLayout } from '@vulcanize/solidity-mapper'; import { StorageLayout } from '@vulcanize/solidity-mapper';
import { EventInterface, Indexer as BaseIndexer, IndexerInterface, UNKNOWN_EVENT_NAME, ServerConfig, JobQueue } from '@vulcanize/util'; import { EventInterface, Indexer as BaseIndexer, IndexerInterface, UNKNOWN_EVENT_NAME, ServerConfig, JobQueue, BlockHeight } from '@vulcanize/util';
import { GraphWatcher } from '@vulcanize/graph-node'; import { GraphWatcher } from '@vulcanize/graph-node';
import { Database } from './database'; import { Database } from './database';
@ -549,10 +549,10 @@ export class Indexer implements IndexerInterface {
return (ipfsAddr !== undefined && ipfsAddr !== null && ipfsAddr !== ''); return (ipfsAddr !== undefined && ipfsAddr !== null && ipfsAddr !== '');
} }
async getSubgraphEntity<Entity> (entity: new () => Entity, id: string, blockHash?: string): Promise<any> { async getSubgraphEntity<Entity> (entity: new () => Entity, id: string, block?: BlockHeight): Promise<any> {
const relations = this._relationsMap.get(entity) || {}; const relations = this._relationsMap.get(entity) || {};
const data = await this._graphWatcher.getEntity(entity, id, relations, blockHash); const data = await this._graphWatcher.getEntity(entity, id, relations, block);
return data; return data;
} }
@ -1144,78 +1144,120 @@ export class Indexer implements IndexerInterface {
this._relationsMap.set(ProducerSet, { this._relationsMap.set(ProducerSet, {
producers: { producers: {
entity: Producer, entity: Producer,
isArray: true isArray: true,
isDerived: false
} }
}); });
this._relationsMap.set(RewardSchedule, { this._relationsMap.set(RewardSchedule, {
rewardScheduleEntries: { rewardScheduleEntries: {
entity: RewardScheduleEntry, entity: RewardScheduleEntry,
isArray: true isArray: true,
isDerived: false
}, },
activeRewardScheduleEntry: { activeRewardScheduleEntry: {
entity: RewardScheduleEntry, entity: RewardScheduleEntry,
isArray: false isArray: false,
isDerived: false
} }
}); });
this._relationsMap.set(ProducerEpoch, { this._relationsMap.set(ProducerEpoch, {
epoch: { epoch: {
entity: Epoch, entity: Epoch,
isArray: false isArray: false,
isDerived: false
} }
}); });
this._relationsMap.set(Epoch, { this._relationsMap.set(Epoch, {
startBlock: { startBlock: {
entity: Block, entity: Block,
isArray: false isArray: false,
isDerived: false
}, },
endBlock: { endBlock: {
entity: Block, entity: Block,
isArray: false isArray: false,
isDerived: false
},
producerRewards: {
entity: ProducerEpoch,
isArray: true,
isDerived: true,
field: 'epoch'
} }
}); });
this._relationsMap.set(SlotClaim, { this._relationsMap.set(SlotClaim, {
slot: { slot: {
entity: Slot, entity: Slot,
isArray: false isArray: false,
isDerived: false
} }
}); });
this._relationsMap.set(Network, { this._relationsMap.set(Network, {
stakers: { stakers: {
entity: Staker, entity: Staker,
isArray: true isArray: true,
isDerived: false
} }
}); });
this._relationsMap.set(Distributor, { this._relationsMap.set(Distributor, {
currentDistribution: { currentDistribution: {
entity: Distribution, entity: Distribution,
isArray: false isArray: false,
isDerived: false
} }
}); });
this._relationsMap.set(Distribution, { this._relationsMap.set(Distribution, {
distributor: { distributor: {
entity: Distributor, entity: Distributor,
isArray: false isArray: false,
isDerived: false
} }
}); });
this._relationsMap.set(Claim, { this._relationsMap.set(Claim, {
account: { account: {
entity: Account, entity: Account,
isArray: false isArray: false,
isDerived: false
} }
}); });
this._relationsMap.set(Slash, { this._relationsMap.set(Slash, {
account: { account: {
entity: Account, entity: Account,
isArray: false isArray: false,
isDerived: false
}
});
this._relationsMap.set(Slot, {
claims: {
entity: SlotClaim,
isArray: true,
isDerived: true,
field: 'slot'
}
});
this._relationsMap.set(Account, {
claims: {
entity: Claim,
isArray: true,
isDerived: true,
field: 'account'
},
slashes: {
entity: Slash,
isArray: true,
isDerived: true,
field: 'account'
} }
}); });
} }

View File

@ -65,109 +65,109 @@ export const createResolvers = async (indexer: Indexer, eventWatcher: EventWatch
producer: async (_: any, { id, block = {} }: { id: string, block: BlockHeight }) => { producer: async (_: any, { id, block = {} }: { id: string, block: BlockHeight }) => {
log('producer', id, block); log('producer', id, block);
return indexer.getSubgraphEntity(Producer, id, block.hash); return indexer.getSubgraphEntity(Producer, id, block);
}, },
producerSet: async (_: any, { id, block = {} }: { id: string, block: BlockHeight }) => { producerSet: async (_: any, { id, block = {} }: { id: string, block: BlockHeight }) => {
log('producerSet', id, block); log('producerSet', id, block);
return indexer.getSubgraphEntity(ProducerSet, id, block.hash); return indexer.getSubgraphEntity(ProducerSet, id, block);
}, },
producerSetChange: async (_: any, { id, block = {} }: { id: string, block: BlockHeight }) => { producerSetChange: async (_: any, { id, block = {} }: { id: string, block: BlockHeight }) => {
log('producerSetChange', id, block); log('producerSetChange', id, block);
return indexer.getSubgraphEntity(ProducerSetChange, id, block.hash); return indexer.getSubgraphEntity(ProducerSetChange, id, block);
}, },
producerRewardCollectorChange: async (_: any, { id, block = {} }: { id: string, block: BlockHeight }) => { producerRewardCollectorChange: async (_: any, { id, block = {} }: { id: string, block: BlockHeight }) => {
log('producerRewardCollectorChange', id, block); log('producerRewardCollectorChange', id, block);
return indexer.getSubgraphEntity(ProducerRewardCollectorChange, id, block.hash); return indexer.getSubgraphEntity(ProducerRewardCollectorChange, id, block);
}, },
rewardScheduleEntry: async (_: any, { id, block = {} }: { id: string, block: BlockHeight }) => { rewardScheduleEntry: async (_: any, { id, block = {} }: { id: string, block: BlockHeight }) => {
log('rewardScheduleEntry', id, block); log('rewardScheduleEntry', id, block);
return indexer.getSubgraphEntity(RewardScheduleEntry, id, block.hash); return indexer.getSubgraphEntity(RewardScheduleEntry, id, block);
}, },
rewardSchedule: async (_: any, { id, block = {} }: { id: string, block: BlockHeight }) => { rewardSchedule: async (_: any, { id, block = {} }: { id: string, block: BlockHeight }) => {
log('rewardSchedule', id, block); log('rewardSchedule', id, block);
return indexer.getSubgraphEntity(RewardSchedule, id, block.hash); return indexer.getSubgraphEntity(RewardSchedule, id, block);
}, },
producerEpoch: async (_: any, { id, block = {} }: { id: string, block: BlockHeight }) => { producerEpoch: async (_: any, { id, block = {} }: { id: string, block: BlockHeight }) => {
log('producerEpoch', id, block); log('producerEpoch', id, block);
return indexer.getSubgraphEntity(ProducerEpoch, id, block.hash); return indexer.getSubgraphEntity(ProducerEpoch, id, block);
}, },
block: async (_: any, { id, block = {} }: { id: string, block: BlockHeight }) => { block: async (_: any, { id, block = {} }: { id: string, block: BlockHeight }) => {
log('block', id, block); log('block', id, block);
return indexer.getSubgraphEntity(Block, id, block.hash); return indexer.getSubgraphEntity(Block, id, block);
}, },
epoch: async (_: any, { id, block = {} }: { id: string, block: BlockHeight }) => { epoch: async (_: any, { id, block = {} }: { id: string, block: BlockHeight }) => {
log('epoch', id, block); log('epoch', id, block);
return indexer.getSubgraphEntity(Epoch, id, block.hash); return indexer.getSubgraphEntity(Epoch, id, block);
}, },
slotClaim: async (_: any, { id, block = {} }: { id: string, block: BlockHeight }) => { slotClaim: async (_: any, { id, block = {} }: { id: string, block: BlockHeight }) => {
log('slotClaim', id, block); log('slotClaim', id, block);
return indexer.getSubgraphEntity(SlotClaim, id, block.hash); return indexer.getSubgraphEntity(SlotClaim, id, block);
}, },
slot: async (_: any, { id, block = {} }: { id: string, block: BlockHeight }) => { slot: async (_: any, { id, block = {} }: { id: string, block: BlockHeight }) => {
log('slot', id, block); log('slot', id, block);
return indexer.getSubgraphEntity(Slot, id, block.hash); return indexer.getSubgraphEntity(Slot, id, block);
}, },
staker: async (_: any, { id, block = {} }: { id: string, block: BlockHeight }) => { staker: async (_: any, { id, block = {} }: { id: string, block: BlockHeight }) => {
log('staker', id, block); log('staker', id, block);
return indexer.getSubgraphEntity(Staker, id, block.hash); return indexer.getSubgraphEntity(Staker, id, block);
}, },
network: async (_: any, { id, block = {} }: { id: string, block: BlockHeight }) => { network: async (_: any, { id, block = {} }: { id: string, block: BlockHeight }) => {
log('network', id, block); log('network', id, block);
return indexer.getSubgraphEntity(Network, id, block.hash); return indexer.getSubgraphEntity(Network, id, block);
}, },
distributor: async (_: any, { id, block = {} }: { id: string, block: BlockHeight }) => { distributor: async (_: any, { id, block = {} }: { id: string, block: BlockHeight }) => {
log('distributor', id, block); log('distributor', id, block);
return indexer.getSubgraphEntity(Distributor, id, block.hash); return indexer.getSubgraphEntity(Distributor, id, block);
}, },
distribution: async (_: any, { id, block = {} }: { id: string, block: BlockHeight }) => { distribution: async (_: any, { id, block = {} }: { id: string, block: BlockHeight }) => {
log('distribution', id, block); log('distribution', id, block);
return indexer.getSubgraphEntity(Distribution, id, block.hash); return indexer.getSubgraphEntity(Distribution, id, block);
}, },
claim: async (_: any, { id, block = {} }: { id: string, block: BlockHeight }) => { claim: async (_: any, { id, block = {} }: { id: string, block: BlockHeight }) => {
log('claim', id, block); log('claim', id, block);
return indexer.getSubgraphEntity(Claim, id, block.hash); return indexer.getSubgraphEntity(Claim, id, block);
}, },
slash: async (_: any, { id, block = {} }: { id: string, block: BlockHeight }) => { slash: async (_: any, { id, block = {} }: { id: string, block: BlockHeight }) => {
log('slash', id, block); log('slash', id, block);
return indexer.getSubgraphEntity(Slash, id, block.hash); return indexer.getSubgraphEntity(Slash, id, block);
}, },
account: async (_: any, { id, block = {} }: { id: string, block: BlockHeight }) => { account: async (_: any, { id, block = {} }: { id: string, block: BlockHeight }) => {
log('account', id, block); log('account', id, block);
return indexer.getSubgraphEntity(Account, id, block.hash); return indexer.getSubgraphEntity(Account, id, block);
}, },
events: async (_: any, { blockHash, contractAddress, name }: { blockHash: string, contractAddress: string, name?: string }) => { events: async (_: any, { blockHash, contractAddress, name }: { blockHash: string, contractAddress: string, name?: string }) => {

View File

@ -40,7 +40,7 @@
## Run ## Run
* Compare query results from two different GQL endpoints: * Compare query results from two different GQL endpoints:
* In a config file (sample: `environments/compare-cli-config.toml`): * In a config file (sample: `environments/compare-cli-config.toml`):
* Specify the two GQL endpoints in the endpoints config. * Specify the two GQL endpoints in the endpoints config.
@ -53,7 +53,7 @@
[endpoints] [endpoints]
gqlEndpoint1 = "http://localhost:8000/subgraphs/name/example1" gqlEndpoint1 = "http://localhost:8000/subgraphs/name/example1"
gqlEndpoint2 = "http://localhost:3008/graphql" gqlEndpoint2 = "http://localhost:3008/graphql"
[queries] [queries]
queryDir = "../graph-test-watcher/src/gql/queries" queryDir = "../graph-test-watcher/src/gql/queries"
``` ```
@ -70,11 +70,11 @@
* `block-hash`(alias: `b`): Block hash (required). * `block-hash`(alias: `b`): Block hash (required).
* `entity-id`(alias: `i`): Entity Id (required). * `entity-id`(alias: `i`): Entity Id (required).
* `raw-json`(alias: `j`): Whether to print out a raw diff object (default: `false`). * `raw-json`(alias: `j`): Whether to print out a raw diff object (default: `false`).
Example: Example:
```bash ```bash
yarn compare-entity --config-file environments/compare-cli-config.toml --query-name exampleEntity --block-hash 0xceed7ee9d3de97c99db12e42433cae9115bb311c516558539fb7114fa17d545b --entity-id 0x2886bae64814bd959aec4282f86f3a97bf1e16e4111b39fd7bdd592b516c66c6 yarn compare-entity --config-file environments/compare-cli-config.toml --query-name author --block-hash 0xceed7ee9d3de97c99db12e42433cae9115bb311c516558539fb7114fa17d545b --entity-id 0xdc7d7a8920c8eecc098da5b7522a5f31509b5bfc
``` ```
* The program will exit with code `1` if the query results are not equal. * The program will exit with code `1` if the query results are not equal.

View File

@ -6,10 +6,12 @@ import assert from 'assert';
import { import {
Connection, Connection,
ConnectionOptions, ConnectionOptions,
FindOneOptions FindOneOptions,
LessThanOrEqual
} from 'typeorm'; } from 'typeorm';
import { import {
BlockHeight,
Database as BaseDatabase Database as BaseDatabase
} from '@vulcanize/util'; } from '@vulcanize/util';
@ -41,7 +43,6 @@ export class Database {
} }
async getEntity<Entity> (entity: (new () => Entity) | string, id: string, blockHash?: string): Promise<Entity | undefined> { async getEntity<Entity> (entity: (new () => Entity) | string, id: string, blockHash?: string): Promise<Entity | undefined> {
// TODO: Take block number as an optional argument
const queryRunner = this._conn.createQueryRunner(); const queryRunner = this._conn.createQueryRunner();
try { try {
@ -74,63 +75,108 @@ export class Database {
} }
} }
async getEntityWithRelations<Entity> (entity: (new () => Entity) | string, id: string, relations: { [key: string]: any }, blockHash?: string): Promise<Entity | undefined> { async getEntityWithRelations<Entity> (entity: (new () => Entity) | string, id: string, relations: { [key: string]: any }, block: BlockHeight = {}): Promise<Entity | undefined> {
const queryRunner = this._conn.createQueryRunner(); const queryRunner = this._conn.createQueryRunner();
let { hash: blockHash, number: blockNumber } = block;
try { try {
const repo = queryRunner.manager.getRepository(entity); const repo = queryRunner.manager.getRepository(entity);
let selectQueryBuilder = repo.createQueryBuilder('entity'); const whereOptions: any = { id };
selectQueryBuilder = selectQueryBuilder.where('entity.id = :id', { id }) if (blockNumber) {
.orderBy('entity.block_number', 'DESC') whereOptions.blockNumber = LessThanOrEqual(blockNumber);
.limit(1);
// Use blockHash if provided.
if (blockHash) {
// Fetching blockHash for previous entity in frothy region.
const { blockHash: entityblockHash, blockNumber, id: frothyId } = await this._baseDatabase.getFrothyEntity(queryRunner, repo, { blockHash, id });
if (frothyId) {
// If entity found in frothy region.
selectQueryBuilder = selectQueryBuilder.andWhere('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')
.andWhere('block.is_pruned = false')
.andWhere('entity.block_number <= :canonicalBlockNumber', { canonicalBlockNumber });
}
} }
// TODO: Implement query for nested relations. if (blockHash) {
Object.entries(relations).forEach(([field, data], index) => { whereOptions.blockHash = blockHash;
const { entity: relatedEntity, isArray } = data; const block = await this._baseDatabase.getBlockProgress(queryRunner.manager.getRepository('block_progress'), blockHash);
const alias = `relatedEntity${index}`; blockNumber = block?.blockNumber;
}
if (isArray) { const findOptions = {
// For one to many relational field. where: whereOptions,
selectQueryBuilder = selectQueryBuilder.leftJoinAndMapMany( order: {
`entity.${field}`, blockNumber: 'DESC'
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(); let entityData: any = await repo.findOne(findOptions as FindOneOptions<Entity>);
if (!entityData && findOptions.where.blockHash) {
entityData = await this._baseDatabase.getPrevEntityVersion(queryRunner, repo, findOptions);
}
if (entityData) {
// Populate relational fields.
// TODO: Implement query for nested relations.
const relationQueryPromises = Object.entries(relations).map(async ([field, data]) => {
assert(entityData);
const { entity: relatedEntity, isArray, isDerived, field: derivedField } = data;
const repo = queryRunner.manager.getRepository(relatedEntity);
let selectQueryBuilder = repo.createQueryBuilder('entity');
if (isDerived) {
// For derived relational field.
selectQueryBuilder = selectQueryBuilder.where(`entity.${derivedField} = :id`, { id: entityData.id });
if (isArray) {
selectQueryBuilder = selectQueryBuilder.distinctOn(['entity.id'])
.orderBy('entity.id');
} else {
selectQueryBuilder = selectQueryBuilder.limit(1);
}
} else {
if (isArray) {
// For one to many relational field.
selectQueryBuilder = selectQueryBuilder.where('entity.id IN (:...ids)', { ids: entityData[field] })
.distinctOn(['entity.id'])
.orderBy('entity.id');
// Subquery example if distinctOn is not performant.
//
// SELECT c.*
// FROM
// categories c,
// (
// SELECT id, MAX(block_number) as block_number
// FROM categories
// WHERE
// id IN ('nature', 'tech', 'issues')
// AND
// block_number <= 127
// GROUP BY id
// ) a
// WHERE
// c.id = a.id AND c.block_number = a.block_number
} else {
// For one to one relational field.
selectQueryBuilder = selectQueryBuilder.where('entity.id = :id', { id: entityData[field] })
.limit(1);
}
selectQueryBuilder = selectQueryBuilder.addOrderBy('entity.block_number', 'DESC');
}
if (blockNumber) {
selectQueryBuilder = selectQueryBuilder.andWhere(
'entity.block_number <= :blockNumber',
{ blockNumber }
);
}
if (isArray) {
entityData[field] = await selectQueryBuilder.getMany();
} else {
entityData[field] = await selectQueryBuilder.getOne();
}
});
await Promise.all(relationQueryPromises);
}
return entityData;
} finally { } finally {
await queryRunner.release(); await queryRunner.release();
} }

View File

@ -53,7 +53,13 @@ export interface Context {
} }
} }
export const instantiate = async (database: Database, indexer: IndexerInterface, context: Context, filePath: string, data: GraphData = {}): Promise<loader.ResultObject & { exports: any }> => { export const instantiate = async (
database: Database,
indexer: IndexerInterface,
context: Context,
filePath: string,
data: GraphData = {}
): Promise<loader.ResultObject & { exports: any }> => {
const { abis = {}, dataSource } = data; const { abis = {}, dataSource } = data;
const buffer = await fs.readFile(filePath); const buffer = await fs.readFile(filePath);
const provider = getDefaultProvider(NETWORK_URL); const provider = getDefaultProvider(NETWORK_URL);

View File

@ -11,7 +11,7 @@ import { ContractInterface, utils } from 'ethers';
import { ResultObject } from '@vulcanize/assemblyscript/lib/loader'; import { ResultObject } from '@vulcanize/assemblyscript/lib/loader';
import { EthClient } from '@vulcanize/ipld-eth-client'; import { EthClient } from '@vulcanize/ipld-eth-client';
import { IndexerInterface, getFullBlock } from '@vulcanize/util'; import { IndexerInterface, getFullBlock, BlockHeight } from '@vulcanize/util';
import { createBlock, createEvent, getSubgraphConfig, resolveEntityFieldConflicts } from './utils'; import { createBlock, createEvent, getSubgraphConfig, resolveEntityFieldConflicts } from './utils';
import { Context, instantiate } from './loader'; import { Context, instantiate } from './loader';
@ -177,9 +177,9 @@ export class GraphWatcher {
this._indexer = indexer; this._indexer = indexer;
} }
async getEntity<Entity> (entity: new () => Entity, id: string, relations: { [key: string]: any }, blockHash?: string): Promise<any> { async getEntity<Entity> (entity: new () => Entity, id: string, relations: { [key: string]: any }, block?: BlockHeight): Promise<any> {
// Get entity from the database. // Get entity from the database.
const result = await this._database.getEntityWithRelations(entity, id, relations, blockHash) as any; const result = await this._database.getEntityWithRelations(entity, id, relations, block) 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);

View File

@ -12,30 +12,33 @@ import {
BigDecimal BigDecimal
} from "@graphprotocol/graph-ts"; } from "@graphprotocol/graph-ts";
export class RelatedEntity extends Entity { export class Blog extends Entity {
constructor(id: string) { constructor(id: string) {
super(); super();
this.set("id", Value.fromString(id)); this.set("id", Value.fromString(id));
this.set("paramBigInt", Value.fromBigInt(BigInt.zero())); this.set("kind", Value.fromString(""));
this.set("bigIntArray", Value.fromBigIntArray(new Array(0))); this.set("isActive", Value.fromBoolean(false));
this.set("reviews", Value.fromBigIntArray(new Array(0)));
this.set("author", Value.fromString(""));
this.set("categories", Value.fromStringArray(new Array(0)));
} }
save(): void { save(): void {
let id = this.get("id"); let id = this.get("id");
assert(id != null, "Cannot save RelatedEntity entity without an ID"); assert(id != null, "Cannot save Blog entity without an ID");
if (id) { if (id) {
assert( assert(
id.kind == ValueKind.STRING, id.kind == ValueKind.STRING,
"Cannot save RelatedEntity entity with non-string ID. " + "Cannot save Blog entity with non-string ID. " +
'Considering using .toHex() to convert the "id" to a string.' 'Considering using .toHex() to convert the "id" to a string.'
); );
store.set("RelatedEntity", id.toString(), this); store.set("Blog", id.toString(), this);
} }
} }
static load(id: string): RelatedEntity | null { static load(id: string): Blog | null {
return changetype<RelatedEntity | null>(store.get("RelatedEntity", id)); return changetype<Blog | null>(store.get("Blog", id));
} }
get id(): string { get id(): string {
@ -47,56 +50,79 @@ export class RelatedEntity extends Entity {
this.set("id", Value.fromString(value)); this.set("id", Value.fromString(value));
} }
get paramBigInt(): BigInt { get kind(): string {
let value = this.get("paramBigInt"); let value = this.get("kind");
return value!.toBigInt(); return value!.toString();
} }
set paramBigInt(value: BigInt) { set kind(value: string) {
this.set("paramBigInt", Value.fromBigInt(value)); this.set("kind", Value.fromString(value));
} }
get bigIntArray(): Array<BigInt> { get isActive(): boolean {
let value = this.get("bigIntArray"); let value = this.get("isActive");
return value!.toBoolean();
}
set isActive(value: boolean) {
this.set("isActive", Value.fromBoolean(value));
}
get reviews(): Array<BigInt> {
let value = this.get("reviews");
return value!.toBigIntArray(); return value!.toBigIntArray();
} }
set bigIntArray(value: Array<BigInt>) { set reviews(value: Array<BigInt>) {
this.set("bigIntArray", Value.fromBigIntArray(value)); this.set("reviews", Value.fromBigIntArray(value));
}
get author(): string {
let value = this.get("author");
return value!.toString();
}
set author(value: string) {
this.set("author", Value.fromString(value));
}
get categories(): Array<string> {
let value = this.get("categories");
return value!.toStringArray();
}
set categories(value: Array<string>) {
this.set("categories", Value.fromStringArray(value));
} }
} }
export class ExampleEntity extends Entity { export class Author extends Entity {
constructor(id: string) { constructor(id: string) {
super(); super();
this.set("id", Value.fromString(id)); this.set("id", Value.fromString(id));
this.set("count", Value.fromBigInt(BigInt.zero())); this.set("blogCount", Value.fromBigInt(BigInt.zero()));
this.set("paramString", Value.fromString("")); this.set("name", Value.fromString(""));
this.set("rating", Value.fromBigDecimal(BigDecimal.zero()));
this.set("paramInt", Value.fromI32(0)); this.set("paramInt", Value.fromI32(0));
this.set("paramBoolean", Value.fromBoolean(false));
this.set("paramBytes", Value.fromBytes(Bytes.empty())); this.set("paramBytes", Value.fromBytes(Bytes.empty()));
this.set("paramEnum", Value.fromString(""));
this.set("paramBigDecimal", Value.fromBigDecimal(BigDecimal.zero()));
this.set("related", Value.fromString(""));
this.set("manyRelated", Value.fromStringArray(new Array(0)));
} }
save(): void { save(): void {
let id = this.get("id"); let id = this.get("id");
assert(id != null, "Cannot save ExampleEntity entity without an ID"); assert(id != null, "Cannot save Author entity without an ID");
if (id) { if (id) {
assert( assert(
id.kind == ValueKind.STRING, id.kind == ValueKind.STRING,
"Cannot save ExampleEntity entity with non-string ID. " + "Cannot save Author entity with non-string ID. " +
'Considering using .toHex() to convert the "id" to a string.' 'Considering using .toHex() to convert the "id" to a string.'
); );
store.set("ExampleEntity", id.toString(), this); store.set("Author", id.toString(), this);
} }
} }
static load(id: string): ExampleEntity | null { static load(id: string): Author | null {
return changetype<ExampleEntity | null>(store.get("ExampleEntity", id)); return changetype<Author | null>(store.get("Author", id));
} }
get id(): string { get id(): string {
@ -108,22 +134,31 @@ export class ExampleEntity extends Entity {
this.set("id", Value.fromString(value)); this.set("id", Value.fromString(value));
} }
get count(): BigInt { get blogCount(): BigInt {
let value = this.get("count"); let value = this.get("blogCount");
return value!.toBigInt(); return value!.toBigInt();
} }
set count(value: BigInt) { set blogCount(value: BigInt) {
this.set("count", Value.fromBigInt(value)); this.set("blogCount", Value.fromBigInt(value));
} }
get paramString(): string { get name(): string {
let value = this.get("paramString"); let value = this.get("name");
return value!.toString(); return value!.toString();
} }
set paramString(value: string) { set name(value: string) {
this.set("paramString", Value.fromString(value)); this.set("name", Value.fromString(value));
}
get rating(): BigDecimal {
let value = this.get("rating");
return value!.toBigDecimal();
}
set rating(value: BigDecimal) {
this.set("rating", Value.fromBigDecimal(value));
} }
get paramInt(): i32 { get paramInt(): i32 {
@ -135,15 +170,6 @@ export class ExampleEntity extends Entity {
this.set("paramInt", Value.fromI32(value)); this.set("paramInt", Value.fromI32(value));
} }
get paramBoolean(): boolean {
let value = this.get("paramBoolean");
return value!.toBoolean();
}
set paramBoolean(value: boolean) {
this.set("paramBoolean", Value.fromBoolean(value));
}
get paramBytes(): Bytes { get paramBytes(): Bytes {
let value = this.get("paramBytes"); let value = this.get("paramBytes");
return value!.toBytes(); return value!.toBytes();
@ -153,68 +179,40 @@ export class ExampleEntity extends Entity {
this.set("paramBytes", Value.fromBytes(value)); this.set("paramBytes", Value.fromBytes(value));
} }
get paramEnum(): string { get blogs(): Array<string> {
let value = this.get("paramEnum"); let value = this.get("blogs");
return value!.toString();
}
set paramEnum(value: string) {
this.set("paramEnum", Value.fromString(value));
}
get paramBigDecimal(): BigDecimal {
let value = this.get("paramBigDecimal");
return value!.toBigDecimal();
}
set paramBigDecimal(value: BigDecimal) {
this.set("paramBigDecimal", Value.fromBigDecimal(value));
}
get related(): string {
let value = this.get("related");
return value!.toString();
}
set related(value: string) {
this.set("related", Value.fromString(value));
}
get manyRelated(): Array<string> {
let value = this.get("manyRelated");
return value!.toStringArray(); return value!.toStringArray();
} }
set manyRelated(value: Array<string>) { set blogs(value: Array<string>) {
this.set("manyRelated", Value.fromStringArray(value)); this.set("blogs", Value.fromStringArray(value));
} }
} }
export class ManyRelatedEntity extends Entity { export class Category extends Entity {
constructor(id: string) { constructor(id: string) {
super(); super();
this.set("id", Value.fromString(id)); this.set("id", Value.fromString(id));
this.set("name", Value.fromString(""));
this.set("count", Value.fromBigInt(BigInt.zero())); this.set("count", Value.fromBigInt(BigInt.zero()));
} }
save(): void { save(): void {
let id = this.get("id"); let id = this.get("id");
assert(id != null, "Cannot save ManyRelatedEntity entity without an ID"); assert(id != null, "Cannot save Category entity without an ID");
if (id) { if (id) {
assert( assert(
id.kind == ValueKind.STRING, id.kind == ValueKind.STRING,
"Cannot save ManyRelatedEntity entity with non-string ID. " + "Cannot save Category entity with non-string ID. " +
'Considering using .toHex() to convert the "id" to a string.' 'Considering using .toHex() to convert the "id" to a string.'
); );
store.set("ManyRelatedEntity", id.toString(), this); store.set("Category", id.toString(), this);
} }
} }
static load(id: string): ManyRelatedEntity | null { static load(id: string): Category | null {
return changetype<ManyRelatedEntity | null>( return changetype<Category | null>(store.get("Category", id));
store.get("ManyRelatedEntity", id)
);
} }
get id(): string { get id(): string {
@ -226,6 +224,15 @@ export class ManyRelatedEntity extends Entity {
this.set("id", Value.fromString(value)); this.set("id", Value.fromString(value));
} }
get name(): string {
let value = this.get("name");
return value!.toString();
}
set name(value: string) {
this.set("name", Value.fromString(value));
}
get count(): BigInt { get count(): BigInt {
let value = this.get("count"); let value = this.get("count");
return value!.toBigInt(); return value!.toBigInt();

View File

@ -1,28 +1,29 @@
enum EnumType { enum BlogKind {
choice1 short
choice2 long
} }
type RelatedEntity @entity { type Blog @entity {
id: ID! id: ID!
paramBigInt: BigInt! kind: BlogKind!
bigIntArray: [BigInt!]! isActive: Boolean!
reviews: [BigInt!]!
author: Author!
categories: [Category!]!
} }
type ExampleEntity @entity { type Author @entity {
id: ID! id: ID!
count: BigInt! blogCount: BigInt!
paramString: String! # string name: String! # string
rating: BigDecimal!
paramInt: Int! # uint8 paramInt: Int! # uint8
paramBoolean: Boolean!
paramBytes: Bytes! paramBytes: Bytes!
paramEnum: EnumType! blogs: [Blog!]! @derivedFrom(field: "author")
paramBigDecimal: BigDecimal!
related: RelatedEntity!
manyRelated: [ManyRelatedEntity!]!
} }
type ManyRelatedEntity @entity { type Category @entity {
id: ID! id: ID!
name: String!
count: BigInt! count: BigInt!
} }

View File

@ -4,7 +4,7 @@ import {
Example1, Example1,
Test Test
} from '../generated/Example1/Example1'; } from '../generated/Example1/Example1';
import { ExampleEntity, ManyRelatedEntity, RelatedEntity } from '../generated/schema'; import { Author, Blog, Category } 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,52 +15,54 @@ 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.from.toHex()); let author = Author.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 (!author) {
entity = new ExampleEntity(event.transaction.from.toHex()); author = new Author(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'); author.blogCount = BigInt.fromString('0');
} }
// BigInt and BigDecimal math are supported // BigInt and BigDecimal math are supported
entity.count = entity.count + BigInt.fromString('1'); author.blogCount = author.blogCount + BigInt.fromString('1');
// Entity fields can be set based on event parameters // Entity fields can be set based on event parameters
entity.paramString = event.params.param1; author.name = event.params.param1;
entity.paramInt = event.params.param2; author.paramInt = event.params.param2;
entity.paramBoolean = true; author.paramBytes = event.address;
entity.paramBytes = event.address; author.rating = BigDecimal.fromString('4');
entity.paramEnum = 'choice1';
entity.paramBigDecimal = BigDecimal.fromString('123');
let relatedEntity = RelatedEntity.load(event.params.param1);
if (!relatedEntity) {
relatedEntity = new RelatedEntity(event.params.param1);
relatedEntity.paramBigInt = BigInt.fromString('123');
}
const bigIntArray = relatedEntity.bigIntArray;
bigIntArray.push(entity.count);
relatedEntity.bigIntArray = bigIntArray;
relatedEntity.save();
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(); author.save();
let category = Category.load(author.blogCount.toString());
if (!category) {
category = new Category(author.blogCount.toString());
category.name = event.params.param1;
}
category.count = category.count + BigInt.fromString('1');
category.save();
const blog = new Blog(event.transaction.hash.toHexString());
blog.kind = 'long';
blog.isActive = true;
const blogReviews = blog.reviews;
blogReviews.push(BigInt.fromString('4'));
blog.reviews = blogReviews;
blog.author = author.id;
const categories = blog.categories;
categories.push(category.id);
blog.categories = categories;
blog.save();
const contractAddress = dataSource.address(); const contractAddress = dataSource.address();
const contract = Example1.bind(contractAddress); const contract = Example1.bind(contractAddress);

View File

@ -16,9 +16,9 @@ import { BlockProgress } from '../../entity/BlockProgress';
import { GetMethod } from '../../entity/GetMethod'; import { GetMethod } from '../../entity/GetMethod';
import { _Test } from '../../entity/_Test'; import { _Test } from '../../entity/_Test';
import { ExampleEntity } from '../../entity/ExampleEntity'; import { Author } from '../../entity/Author';
import { RelatedEntity } from '../../entity/RelatedEntity'; import { Blog } from '../../entity/Blog';
import { ManyRelatedEntity } from '../../entity/ManyRelatedEntity'; import { Category } from '../../entity/Category';
const log = debug('vulcanize:reset-state'); const log = debug('vulcanize:reset-state');
@ -74,7 +74,7 @@ export const handler = async (argv: any): Promise<void> => {
const dbTx = await db.createTransactionRunner(); const dbTx = await db.createTransactionRunner();
try { try {
const entities = [BlockProgress, GetMethod, _Test, ExampleEntity, ManyRelatedEntity, RelatedEntity]; const entities = [BlockProgress, GetMethod, _Test, Author, Category, Blog];
const removeEntitiesPromise = entities.map(async entityClass => { const removeEntitiesPromise = entities.map(async entityClass => {
return db.removeEntities<any>(dbTx, entityClass, { blockNumber: MoreThan(argv.blockNumber) }); return db.removeEntities<any>(dbTx, entityClass, { blockNumber: MoreThan(argv.blockNumber) });

View File

@ -7,13 +7,8 @@ import Decimal from 'decimal.js';
import { bigintTransformer, decimalTransformer } from '@vulcanize/util'; import { bigintTransformer, decimalTransformer } from '@vulcanize/util';
enum EnumType {
choice1 = 'choice1',
choice2 = 'choice2'
}
@Entity() @Entity()
export class ExampleEntity { export class Author {
@PrimaryColumn('varchar') @PrimaryColumn('varchar')
id!: string; id!: string;
@ -24,33 +19,17 @@ export class ExampleEntity {
blockNumber!: number; blockNumber!: number;
@Column('bigint', { transformer: bigintTransformer }) @Column('bigint', { transformer: bigintTransformer })
count!: bigint; blogCount!: bigint;
@Column('varchar') @Column('varchar')
paramString!: string name!: string
@Column('integer') @Column('integer')
paramInt!: number paramInt!: number
@Column('boolean')
paramBoolean!: boolean
@Column('varchar') @Column('varchar')
paramBytes!: string paramBytes!: string
@Column({
type: 'enum',
enum: EnumType,
default: EnumType.choice1
})
paramEnum!: EnumType
@Column('numeric', { default: 0, transformer: decimalTransformer }) @Column('numeric', { default: 0, transformer: decimalTransformer })
paramBigDecimal!: Decimal rating!: Decimal
@Column('varchar')
related!: string;
@Column('varchar', { array: true })
manyRelated!: string[]
} }

View File

@ -0,0 +1,43 @@
//
// Copyright 2021 Vulcanize, Inc.
//
import { Entity, PrimaryColumn, Column } from 'typeorm';
import { bigintArrayTransformer } from '@vulcanize/util';
enum BlogType {
short = 'short',
long = 'long'
}
@Entity()
export class Blog {
@PrimaryColumn('varchar')
id!: string;
@PrimaryColumn('varchar', { length: 66 })
blockHash!: string;
@Column('integer')
blockNumber!: number;
@Column({
type: 'enum',
enum: BlogType,
default: BlogType.short
})
kind!: BlogType
@Column('boolean')
isActive!: boolean
@Column('bigint', { transformer: bigintArrayTransformer, array: true })
reviews!: bigint[];
@Column('varchar')
author!: string;
@Column('varchar', { array: true })
categories!: string[]
}

View File

@ -7,7 +7,7 @@ import { Entity, PrimaryColumn, Column } from 'typeorm';
import { bigintTransformer } from '@vulcanize/util'; import { bigintTransformer } from '@vulcanize/util';
@Entity() @Entity()
export class ManyRelatedEntity { export class Category {
@PrimaryColumn('varchar') @PrimaryColumn('varchar')
id!: string; id!: string;
@ -19,4 +19,7 @@ export class ManyRelatedEntity {
@Column('bigint', { transformer: bigintTransformer }) @Column('bigint', { transformer: bigintTransformer })
count!: bigint; count!: bigint;
@Column('varchar')
name!: string;
} }

View File

@ -1,25 +0,0 @@
//
// Copyright 2021 Vulcanize, Inc.
//
import { Entity, PrimaryColumn, Column } from 'typeorm';
import { bigintTransformer, bigintArrayTransformer } from '@vulcanize/util';
@Entity()
export class RelatedEntity {
@PrimaryColumn('varchar')
id!: string;
@PrimaryColumn('varchar', { length: 66 })
blockHash!: string;
@Column('integer')
blockNumber!: number;
@Column('bigint', { transformer: bigintTransformer })
paramBigInt!: bigint;
@Column('bigint', { transformer: bigintArrayTransformer, array: true })
bigIntArray!: bigint[];
}

View File

@ -0,0 +1,16 @@
query author($id: String!, $blockHash: Bytes!){
author(id: $id, block: { hash: $blockHash }){
id
blogCount
name
rating
paramInt
paramBytes
blogs {
id
kind
reviews
isActive
}
}
}

View File

@ -0,0 +1,17 @@
query blog($id: String!, $blockHash: Bytes!){
blog(id: $id, block: { hash: $blockHash }){
id
kind
reviews
isActive
author {
id
name
}
categories {
id
name
count
}
}
}

View File

@ -0,0 +1,7 @@
query category($id: String!, $blockHash: Bytes!){
category(id: $id, block: { hash: $blockHash }){
id
count
name
}
}

View File

@ -1,21 +0,0 @@
query exampleEntity($id: String!, $blockHash: Bytes!){
exampleEntity(id: $id, block: { hash: $blockHash }){
id
count
paramString
paramInt
paramBoolean
paramBytes
paramEnum
paramBigDecimal
related {
id
paramBigInt
bigIntArray
}
manyRelated {
id
count
}
}
}

View File

@ -1,6 +0,0 @@
query manyRelatedEntity($id: String!, $blockHash: Bytes!){
manyRelatedEntity(id: $id, block: { hash: $blockHash }){
id
count
}
}

View File

@ -1,7 +0,0 @@
query relatedEntity($id: String!, $blockHash: Bytes!){
relatedEntity(id: $id, block: { hash: $blockHash }){
id
paramBigInt
bigIntArray
}
}

View File

@ -16,7 +16,7 @@ import { BaseProvider } from '@ethersproject/providers';
import * as codec from '@ipld/dag-cbor'; import * as codec from '@ipld/dag-cbor';
import { EthClient } from '@vulcanize/ipld-eth-client'; import { EthClient } from '@vulcanize/ipld-eth-client';
import { StorageLayout } from '@vulcanize/solidity-mapper'; import { StorageLayout } from '@vulcanize/solidity-mapper';
import { EventInterface, Indexer as BaseIndexer, IndexerInterface, ValueResult, UNKNOWN_EVENT_NAME, ServerConfig, updateStateForElementaryType, JobQueue } from '@vulcanize/util'; import { EventInterface, Indexer as BaseIndexer, IndexerInterface, ValueResult, UNKNOWN_EVENT_NAME, ServerConfig, updateStateForElementaryType, JobQueue, BlockHeight } from '@vulcanize/util';
import { GraphWatcher } from '@vulcanize/graph-node'; import { GraphWatcher } from '@vulcanize/graph-node';
import { Database } from './database'; import { Database } from './database';
@ -29,9 +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 { Author } from './entity/Author';
import { RelatedEntity } from './entity/RelatedEntity'; import { Blog } from './entity/Blog';
import { ManyRelatedEntity } from './entity/ManyRelatedEntity'; import { Category } from './entity/Category';
const log = debug('vulcanize:indexer'); const log = debug('vulcanize:indexer');
@ -546,10 +546,10 @@ export class Indexer implements IndexerInterface {
return (ipfsAddr !== undefined && ipfsAddr !== null && ipfsAddr !== ''); return (ipfsAddr !== undefined && ipfsAddr !== null && ipfsAddr !== '');
} }
async getSubgraphEntity<Entity> (entity: new () => Entity, id: string, blockHash?: string): Promise<Entity | undefined> { async getSubgraphEntity<Entity> (entity: new () => Entity, id: string, block: BlockHeight): Promise<Entity | undefined> {
const relations = this._relationsMap.get(entity) || {}; const relations = this._relationsMap.get(entity) || {};
const data = await this._graphWatcher.getEntity(entity, id, relations, blockHash); const data = await this._graphWatcher.getEntity(entity, id, relations, block);
return data; return data;
} }
@ -739,13 +739,24 @@ export class Indexer implements IndexerInterface {
_populateRelationsMap (): void { _populateRelationsMap (): void {
// Needs to be generated by codegen. // Needs to be generated by codegen.
this._relationsMap.set(ExampleEntity, { this._relationsMap.set(Author, {
related: { blogs: {
entity: RelatedEntity, entity: Blog,
isDerived: true,
isArray: true,
field: 'author'
}
});
this._relationsMap.set(Blog, {
author: {
entity: Author,
isDerived: false,
isArray: false isArray: false
}, },
manyRelated: { categories: {
entity: ManyRelatedEntity, entity: Category,
isDerived: false,
isArray: true isArray: true
} }
}); });

View File

@ -11,9 +11,9 @@ import { ValueResult, BlockHeight } from '@vulcanize/util';
import { Indexer } from './indexer'; import { Indexer } from './indexer';
import { EventWatcher } from './events'; import { EventWatcher } from './events';
import { ExampleEntity } from './entity/ExampleEntity'; import { Author } from './entity/Author';
import { RelatedEntity } from './entity/RelatedEntity'; import { Blog } from './entity/Blog';
import { ManyRelatedEntity } from './entity/ManyRelatedEntity'; import { Category } from './entity/Category';
const log = debug('vulcanize:resolver'); const log = debug('vulcanize:resolver');
@ -57,22 +57,22 @@ export const createResolvers = async (indexer: Indexer, eventWatcher: EventWatch
return indexer._test(blockHash, contractAddress); return indexer._test(blockHash, contractAddress);
}, },
relatedEntity: async (_: any, { id, block = {} }: { id: string, block: BlockHeight }): Promise<RelatedEntity | undefined> => { blog: async (_: any, { id, block = {} }: { id: string, block: BlockHeight }): Promise<Blog | undefined> => {
log('relatedEntity', id, block); log('blog', id, block);
return indexer.getSubgraphEntity(RelatedEntity, id, block.hash); return indexer.getSubgraphEntity(Blog, id, block);
}, },
manyRelatedEntity: async (_: any, { id, block = {} }: { id: string, block: BlockHeight }): Promise<ManyRelatedEntity | undefined> => { category: async (_: any, { id, block = {} }: { id: string, block: BlockHeight }): Promise<Category | undefined> => {
log('relatedEntity', id, block); log('category', id, block);
return indexer.getSubgraphEntity(ManyRelatedEntity, id, block.hash); return indexer.getSubgraphEntity(Category, id, block);
}, },
exampleEntity: async (_: any, { id, block = {} }: { id: string, block: BlockHeight }): Promise<ExampleEntity | undefined> => { author: async (_: any, { id, block = {} }: { id: string, block: BlockHeight }): Promise<Author | undefined> => {
log('exampleEntity', id, block); log('author', id, block);
return indexer.getSubgraphEntity(ExampleEntity, id, block.hash); return indexer.getSubgraphEntity(Author, id, block);
}, },
events: async (_: any, { blockHash, contractAddress, name }: { blockHash: string, contractAddress: string, name?: string }) => { events: async (_: any, { blockHash, contractAddress, name }: { blockHash: string, contractAddress: string, name?: string }) => {

View File

@ -77,40 +77,41 @@ type Query {
eventsInRange(fromBlockNumber: Int!, toBlockNumber: Int!): [ResultEvent!] eventsInRange(fromBlockNumber: Int!, toBlockNumber: Int!): [ResultEvent!]
getMethod(blockHash: String!, contractAddress: String!): ResultString! getMethod(blockHash: String!, contractAddress: String!): ResultString!
_test(blockHash: String!, contractAddress: String!): ResultBigInt! _test(blockHash: String!, contractAddress: String!): ResultBigInt!
relatedEntity(id: String!, block: Block_height): RelatedEntity! blog(id: String!, block: Block_height): Blog!
exampleEntity(id: String!, block: Block_height): ExampleEntity! author(id: String!, block: Block_height): Author!
manyRelatedEntity(id: String!, block: Block_height): ManyRelatedEntity! category(id: String!, block: Block_height): Category!
getStateByCID(cid: String!): ResultIPLDBlock getStateByCID(cid: String!): ResultIPLDBlock
getState(blockHash: String!, contractAddress: String!, kind: String): ResultIPLDBlock getState(blockHash: String!, contractAddress: String!, kind: String): ResultIPLDBlock
} }
enum EnumType { enum BlogKind {
choice1 short
choice2 long
} }
type RelatedEntity { type Blog {
id: ID! id: ID!
paramBigInt: BigInt! kind: BlogKind!
bigIntArray: [BigInt!]! isActive: Boolean!
reviews: [BigInt!]!
author: Author!
categories: [Category!]!
} }
type ManyRelatedEntity { type Category {
id: ID! id: ID!
count: BigInt! count: BigInt!
name: String!
} }
type ExampleEntity { type Author {
id: ID! id: ID!
count: BigInt! blogCount: BigInt!
paramString: String! name: String!
rating: BigDecimal!
paramInt: Int! paramInt: Int!
paramBoolean: Boolean!
paramBytes: Bytes! paramBytes: Bytes!
paramEnum: EnumType! blogs: [Blog!]!
paramBigDecimal: BigDecimal!
related: RelatedEntity!
manyRelated: [ManyRelatedEntity!]!
} }
type Mutation { type Mutation {