From 3c2a3b59935d15ec06bf08849015d6c963c96b65 Mon Sep 17 00:00:00 2001 From: nabarun Date: Tue, 23 Nov 2021 15:36:35 +0530 Subject: [PATCH 1/7] Handle subgraph schema field with derivedFrom directive --- packages/graph-node/src/database.ts | 40 ++++++++++++------- .../subgraph/example1/generated/schema.ts | 38 +++++++++++++----- .../test/subgraph/example1/schema.graphql | 5 ++- .../test/subgraph/example1/src/mapping.ts | 12 +++--- .../src/entity/ExampleEntity.ts | 5 +-- .../src/entity/RelatedEntity.ts | 3 ++ packages/graph-test-watcher/src/indexer.ts | 17 ++++++-- packages/graph-test-watcher/src/schema.gql | 5 ++- 8 files changed, 82 insertions(+), 43 deletions(-) diff --git a/packages/graph-node/src/database.ts b/packages/graph-node/src/database.ts index 8fffc1bd0..51d1b7af5 100644 --- a/packages/graph-node/src/database.ts +++ b/packages/graph-node/src/database.ts @@ -106,27 +106,37 @@ export class Database { // TODO: Implement query for nested relations. Object.entries(relations).forEach(([field, data], index) => { - const { entity: relatedEntity, isArray } = data; + const { entity: relatedEntity, isArray, isDerived, field: derivedField } = data; const alias = `relatedEntity${index}`; + let condition: string; + + if (isDerived) { + // For derived relational field. + condition = `${alias}.${derivedField} = entity.id AND ${alias}.block_number <= entity.block_number`; + } else { + if (isArray) { + // For one to many relational field. + condition = `${alias}.id IN (SELECT unnest(entity.${field})) AND ${alias}.block_number <= entity.block_number`; + } else { + // For one to one relational field. + condition = `entity.${field} = ${alias}.id AND ${alias}.block_number <= entity.block_number`; + } + } 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'); + `entity.${field}`, + relatedEntity, + alias, + condition + ).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'); + `entity.${field}`, + relatedEntity, + alias, + condition + ).addOrderBy(`${alias}.block_number`, 'DESC'); } }); diff --git a/packages/graph-node/test/subgraph/example1/generated/schema.ts b/packages/graph-node/test/subgraph/example1/generated/schema.ts index 7a2182454..25123d01a 100644 --- a/packages/graph-node/test/subgraph/example1/generated/schema.ts +++ b/packages/graph-node/test/subgraph/example1/generated/schema.ts @@ -64,6 +64,23 @@ export class RelatedEntity extends Entity { set bigIntArray(value: Array) { this.set("bigIntArray", Value.fromBigIntArray(value)); } + + get example(): string | null { + let value = this.get("example"); + if (!value || value.kind == ValueKind.NULL) { + return null; + } else { + return value.toString(); + } + } + + set example(value: string | null) { + if (!value) { + this.unset("example"); + } else { + this.set("example", Value.fromString(value)); + } + } } export class ExampleEntity extends Entity { @@ -78,8 +95,7 @@ export class ExampleEntity extends Entity { 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))); + this.set("manyRelateds", Value.fromStringArray(new Array(0))); } save(): void { @@ -171,22 +187,22 @@ export class ExampleEntity extends Entity { this.set("paramBigDecimal", Value.fromBigDecimal(value)); } - get related(): string { - let value = this.get("related"); - return value!.toString(); + get relateds(): Array { + let value = this.get("relateds"); + return value!.toStringArray(); } - set related(value: string) { - this.set("related", Value.fromString(value)); + set relateds(value: Array) { + this.set("relateds", Value.fromStringArray(value)); } - get manyRelated(): Array { - let value = this.get("manyRelated"); + get manyRelateds(): Array { + let value = this.get("manyRelateds"); return value!.toStringArray(); } - set manyRelated(value: Array) { - this.set("manyRelated", Value.fromStringArray(value)); + set manyRelateds(value: Array) { + this.set("manyRelateds", Value.fromStringArray(value)); } } diff --git a/packages/graph-node/test/subgraph/example1/schema.graphql b/packages/graph-node/test/subgraph/example1/schema.graphql index 34bb57545..5bf09ba2d 100644 --- a/packages/graph-node/test/subgraph/example1/schema.graphql +++ b/packages/graph-node/test/subgraph/example1/schema.graphql @@ -7,6 +7,7 @@ type RelatedEntity @entity { id: ID! paramBigInt: BigInt! bigIntArray: [BigInt!]! + example: ExampleEntity } type ExampleEntity @entity { @@ -18,8 +19,8 @@ type ExampleEntity @entity { paramBytes: Bytes! paramEnum: EnumType! paramBigDecimal: BigDecimal! - related: RelatedEntity! - manyRelated: [ManyRelatedEntity!]! + relateds: [RelatedEntity!]! @derivedFrom(field: "example") + manyRelateds: [ManyRelatedEntity!]! } type ManyRelatedEntity @entity { diff --git a/packages/graph-node/test/subgraph/example1/src/mapping.ts b/packages/graph-node/test/subgraph/example1/src/mapping.ts index b4f3bde7f..02a4483b3 100644 --- a/packages/graph-node/test/subgraph/example1/src/mapping.ts +++ b/packages/graph-node/test/subgraph/example1/src/mapping.ts @@ -37,27 +37,27 @@ export function handleTest (event: Test): void { entity.paramEnum = 'choice1'; entity.paramBigDecimal = BigDecimal.fromString('123'); - let relatedEntity = RelatedEntity.load(event.params.param1); + let relatedEntity = RelatedEntity.load(entity.count.toString()); if (!relatedEntity) { - relatedEntity = new RelatedEntity(event.params.param1); + relatedEntity = new RelatedEntity(entity.count.toString()); relatedEntity.paramBigInt = BigInt.fromString('123'); } const bigIntArray = relatedEntity.bigIntArray; bigIntArray.push(entity.count); relatedEntity.bigIntArray = bigIntArray; + relatedEntity.example = entity.id; 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; + const manyRelateds = entity.manyRelateds; + manyRelateds.push(manyRelatedEntity.id); + entity.manyRelateds = manyRelateds; // Entities can be written to the store with `.save()` entity.save(); diff --git a/packages/graph-test-watcher/src/entity/ExampleEntity.ts b/packages/graph-test-watcher/src/entity/ExampleEntity.ts index 758cbb677..0997f3fb7 100644 --- a/packages/graph-test-watcher/src/entity/ExampleEntity.ts +++ b/packages/graph-test-watcher/src/entity/ExampleEntity.ts @@ -48,9 +48,6 @@ export class ExampleEntity { @Column('numeric', { default: 0, transformer: decimalTransformer }) paramBigDecimal!: Decimal - @Column('varchar') - related!: string; - @Column('varchar', { array: true }) - manyRelated!: string[] + manyRelateds!: string[] } diff --git a/packages/graph-test-watcher/src/entity/RelatedEntity.ts b/packages/graph-test-watcher/src/entity/RelatedEntity.ts index 2dbe958cf..d732a2764 100644 --- a/packages/graph-test-watcher/src/entity/RelatedEntity.ts +++ b/packages/graph-test-watcher/src/entity/RelatedEntity.ts @@ -22,4 +22,7 @@ export class RelatedEntity { @Column('bigint', { transformer: bigintArrayTransformer, array: true }) bigIntArray!: bigint[]; + + @Column('varchar') + example!: string; } diff --git a/packages/graph-test-watcher/src/indexer.ts b/packages/graph-test-watcher/src/indexer.ts index 067190250..2c90b4664 100644 --- a/packages/graph-test-watcher/src/indexer.ts +++ b/packages/graph-test-watcher/src/indexer.ts @@ -750,15 +750,26 @@ export class Indexer implements IndexerInterface { _populateRelationsMap (): void { // Needs to be generated by codegen. this._relationsMap.set(ExampleEntity, { - related: { + relateds: { entity: RelatedEntity, - isArray: false + isDerived: true, + isArray: true, + field: 'example' }, - manyRelated: { + manyRelateds: { entity: ManyRelatedEntity, + isDerived: false, isArray: true } }); + + this._relationsMap.set(RelatedEntity, { + example: { + entity: ExampleEntity, + isDerived: false, + isArray: false + } + }); } async _fetchAndSaveEvents ({ cid: blockCid, blockHash }: DeepPartial): Promise { diff --git a/packages/graph-test-watcher/src/schema.gql b/packages/graph-test-watcher/src/schema.gql index 7bf2fded4..f31049c27 100644 --- a/packages/graph-test-watcher/src/schema.gql +++ b/packages/graph-test-watcher/src/schema.gql @@ -93,6 +93,7 @@ type RelatedEntity { id: ID! paramBigInt: BigInt! bigIntArray: [BigInt!]! + example: ExampleEntity } type ManyRelatedEntity { @@ -109,8 +110,8 @@ type ExampleEntity { paramBytes: Bytes! paramEnum: EnumType! paramBigDecimal: BigDecimal! - related: RelatedEntity! - manyRelated: [ManyRelatedEntity!]! + relateds: [RelatedEntity!]! + manyRelateds: [ManyRelatedEntity!]! } type Mutation { From f111409098c90bd2034f29695f3b678af4fd922c Mon Sep 17 00:00:00 2001 From: nabarun Date: Tue, 23 Nov 2021 16:16:49 +0530 Subject: [PATCH 2/7] Handle derivedFrom directive in eden-watcher --- packages/eden-watcher/src/entity/Account.ts | 6 -- packages/eden-watcher/src/entity/Epoch.ts | 3 - packages/eden-watcher/src/entity/Slot.ts | 3 - packages/eden-watcher/src/indexer.ts | 75 +++++++++++++++++---- 4 files changed, 63 insertions(+), 24 deletions(-) diff --git a/packages/eden-watcher/src/entity/Account.ts b/packages/eden-watcher/src/entity/Account.ts index 9da06987b..701cc0c80 100644 --- a/packages/eden-watcher/src/entity/Account.ts +++ b/packages/eden-watcher/src/entity/Account.ts @@ -23,10 +23,4 @@ export class Account { @Column('bigint', { transformer: bigintTransformer }) totalSlashed!: bigint; - - @ManyToOne(() => Claim) - claims!: Claim; - - @ManyToOne(() => Slash) - slashes!: Slash; } diff --git a/packages/eden-watcher/src/entity/Epoch.ts b/packages/eden-watcher/src/entity/Epoch.ts index 37c351b7b..7ef9087d5 100644 --- a/packages/eden-watcher/src/entity/Epoch.ts +++ b/packages/eden-watcher/src/entity/Epoch.ts @@ -39,7 +39,4 @@ export class Epoch { @Column('numeric', { default: 0, transformer: decimalTransformer }) producerBlocksRatio!: Decimal; - - @ManyToOne(() => ProducerEpoch) - producerRewards!: ProducerEpoch; } diff --git a/packages/eden-watcher/src/entity/Slot.ts b/packages/eden-watcher/src/entity/Slot.ts index 000e7bfd7..5a505d914 100644 --- a/packages/eden-watcher/src/entity/Slot.ts +++ b/packages/eden-watcher/src/entity/Slot.ts @@ -40,7 +40,4 @@ export class Slot { @Column('numeric', { default: 0, transformer: decimalTransformer }) taxRatePerDay!: Decimal; - - @ManyToOne(() => SlotClaim) - claims!: SlotClaim; } diff --git a/packages/eden-watcher/src/indexer.ts b/packages/eden-watcher/src/indexer.ts index 5eb80dde3..ac5bd9ae0 100644 --- a/packages/eden-watcher/src/indexer.ts +++ b/packages/eden-watcher/src/indexer.ts @@ -80,6 +80,15 @@ const ROLEADMINCHANGED_EVENT = 'RoleAdminChanged'; const ROLEGRANTED_EVENT = 'RoleGranted'; const ROLEREVOKED_EVENT = 'RoleRevoked'; +interface Relations { + [key: string]: { + entity: any; + isArray: boolean; + isDerived: boolean; + field?: string; + } +} + export type ResultEvent = { block: { cid: string; @@ -1154,78 +1163,120 @@ export class Indexer implements IndexerInterface { this._relationsMap.set(ProducerSet, { producers: { entity: Producer, - isArray: true + isArray: true, + isDerived: false } }); this._relationsMap.set(RewardSchedule, { rewardScheduleEntries: { entity: RewardScheduleEntry, - isArray: true + isArray: true, + isDerived: false }, activeRewardScheduleEntry: { entity: RewardScheduleEntry, - isArray: false + isArray: false, + isDerived: false } }); this._relationsMap.set(ProducerEpoch, { epoch: { entity: Epoch, - isArray: false + isArray: false, + isDerived: false } }); this._relationsMap.set(Epoch, { startBlock: { entity: Block, - isArray: false + isArray: false, + isDerived: false }, endBlock: { entity: Block, - isArray: false + isArray: false, + isDerived: false + }, + producerRewards: { + entity: ProducerEpoch, + isArray: true, + isDerived: true, + field: 'epoch' } }); this._relationsMap.set(SlotClaim, { slot: { entity: Slot, - isArray: false + isArray: false, + isDerived: false } }); this._relationsMap.set(Network, { stakers: { entity: Staker, - isArray: true + isArray: true, + isDerived: false } }); this._relationsMap.set(Distributor, { currentDistribution: { entity: Distribution, - isArray: false + isArray: false, + isDerived: false } }); this._relationsMap.set(Distribution, { distributor: { entity: Distributor, - isArray: false + isArray: false, + isDerived: false } }); this._relationsMap.set(Claim, { account: { entity: Account, - isArray: false + isArray: false, + isDerived: false } }); this._relationsMap.set(Slash, { 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' } }); } From 47a8701d5aa9cbbb1cce95e05ff823e3a6bb25f9 Mon Sep 17 00:00:00 2001 From: nabarun Date: Tue, 23 Nov 2021 20:33:26 +0530 Subject: [PATCH 3/7] Fix 1 to N relation error by removing limit from query --- packages/eden-watcher/src/entity/Account.ts | 5 ++--- packages/eden-watcher/src/entity/Epoch.ts | 3 +-- packages/eden-watcher/src/entity/Slot.ts | 4 +--- packages/eden-watcher/src/indexer.ts | 9 --------- packages/graph-node/README.md | 10 +++++----- packages/graph-node/src/database.ts | 3 +-- .../src/gql/queries/exampleEntity.gql | 4 ++-- .../src/gql/queries/relatedEntity.gql | 3 +++ 8 files changed, 15 insertions(+), 26 deletions(-) diff --git a/packages/eden-watcher/src/entity/Account.ts b/packages/eden-watcher/src/entity/Account.ts index 701cc0c80..1f06b93db 100644 --- a/packages/eden-watcher/src/entity/Account.ts +++ b/packages/eden-watcher/src/entity/Account.ts @@ -2,9 +2,8 @@ // Copyright 2021 Vulcanize, Inc. // -import { Entity, PrimaryColumn, Column, ManyToOne } from 'typeorm'; -import { Claim } from './Claim'; -import { Slash } from './Slash'; +import { Entity, PrimaryColumn, Column } from 'typeorm'; + import { bigintTransformer } from '@vulcanize/util'; @Entity() diff --git a/packages/eden-watcher/src/entity/Epoch.ts b/packages/eden-watcher/src/entity/Epoch.ts index 7ef9087d5..e8bb3b245 100644 --- a/packages/eden-watcher/src/entity/Epoch.ts +++ b/packages/eden-watcher/src/entity/Epoch.ts @@ -2,10 +2,9 @@ // Copyright 2021 Vulcanize, Inc. // -import { Entity, PrimaryColumn, Column, ManyToOne } from 'typeorm'; +import { Entity, PrimaryColumn, Column } from 'typeorm'; import Decimal from 'decimal.js'; -import { ProducerEpoch } from './ProducerEpoch'; import { bigintTransformer, decimalTransformer } from '@vulcanize/util'; @Entity() diff --git a/packages/eden-watcher/src/entity/Slot.ts b/packages/eden-watcher/src/entity/Slot.ts index 5a505d914..3758c1d74 100644 --- a/packages/eden-watcher/src/entity/Slot.ts +++ b/packages/eden-watcher/src/entity/Slot.ts @@ -2,13 +2,11 @@ // Copyright 2021 Vulcanize, Inc. // -import { Entity, PrimaryColumn, Column, ManyToOne } from 'typeorm'; +import { Entity, PrimaryColumn, Column } from 'typeorm'; import Decimal from 'decimal.js'; import { bigintTransformer, decimalTransformer } from '@vulcanize/util'; -import { SlotClaim } from './SlotClaim'; - @Entity() export class Slot { @PrimaryColumn('varchar') diff --git a/packages/eden-watcher/src/indexer.ts b/packages/eden-watcher/src/indexer.ts index ac5bd9ae0..1bec3a875 100644 --- a/packages/eden-watcher/src/indexer.ts +++ b/packages/eden-watcher/src/indexer.ts @@ -80,15 +80,6 @@ const ROLEADMINCHANGED_EVENT = 'RoleAdminChanged'; const ROLEGRANTED_EVENT = 'RoleGranted'; const ROLEREVOKED_EVENT = 'RoleRevoked'; -interface Relations { - [key: string]: { - entity: any; - isArray: boolean; - isDerived: boolean; - field?: string; - } -} - export type ResultEvent = { block: { cid: string; diff --git a/packages/graph-node/README.md b/packages/graph-node/README.md index ba37303d5..5a204e4b6 100644 --- a/packages/graph-node/README.md +++ b/packages/graph-node/README.md @@ -40,7 +40,7 @@ ## Run * Compare query results from two different GQL endpoints: - + * In a config file (sample: `environments/compare-cli-config.toml`): * Specify the two GQL endpoints in the endpoints config. @@ -53,7 +53,7 @@ [endpoints] gqlEndpoint1 = "http://localhost:8000/subgraphs/name/example1" gqlEndpoint2 = "http://localhost:3008/graphql" - + [queries] queryDir = "../graph-test-watcher/src/gql/queries" ``` @@ -70,11 +70,11 @@ * `block-hash`(alias: `b`): Block hash (required). * `entity-id`(alias: `i`): Entity Id (required). * `raw-json`(alias: `j`): Whether to print out a raw diff object (default: `false`). - + Example: ```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 exampleEntity --block-hash 0xceed7ee9d3de97c99db12e42433cae9115bb311c516558539fb7114fa17d545b --entity-id 0xdc7d7a8920c8eecc098da5b7522a5f31509b5bfc ``` - + * The program will exit with code `1` if the query results are not equal. diff --git a/packages/graph-node/src/database.ts b/packages/graph-node/src/database.ts index 51d1b7af5..aa584805b 100644 --- a/packages/graph-node/src/database.ts +++ b/packages/graph-node/src/database.ts @@ -83,8 +83,7 @@ export class Database { let selectQueryBuilder = repo.createQueryBuilder('entity'); selectQueryBuilder = selectQueryBuilder.where('entity.id = :id', { id }) - .orderBy('entity.block_number', 'DESC') - .limit(1); + .orderBy('entity.block_number', 'DESC'); // Use blockHash if provided. if (blockHash) { diff --git a/packages/graph-test-watcher/src/gql/queries/exampleEntity.gql b/packages/graph-test-watcher/src/gql/queries/exampleEntity.gql index def9ac313..ef988360a 100644 --- a/packages/graph-test-watcher/src/gql/queries/exampleEntity.gql +++ b/packages/graph-test-watcher/src/gql/queries/exampleEntity.gql @@ -8,12 +8,12 @@ query exampleEntity($id: String!, $blockHash: Bytes!){ paramBytes paramEnum paramBigDecimal - related { + relateds { id paramBigInt bigIntArray } - manyRelated { + manyRelateds { id count } diff --git a/packages/graph-test-watcher/src/gql/queries/relatedEntity.gql b/packages/graph-test-watcher/src/gql/queries/relatedEntity.gql index 5039e52b8..cf885971c 100644 --- a/packages/graph-test-watcher/src/gql/queries/relatedEntity.gql +++ b/packages/graph-test-watcher/src/gql/queries/relatedEntity.gql @@ -3,5 +3,8 @@ query relatedEntity($id: String!, $blockHash: Bytes!){ id paramBigInt bigIntArray + example { + id + } } } From d1700dec11c124059310c059b2dcbf595d5b47a5 Mon Sep 17 00:00:00 2001 From: nabarun Date: Wed, 24 Nov 2021 11:14:08 +0530 Subject: [PATCH 4/7] Order by id for derivedFrom relations to match graph-node --- packages/graph-node/src/database.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/graph-node/src/database.ts b/packages/graph-node/src/database.ts index aa584805b..71cef56da 100644 --- a/packages/graph-node/src/database.ts +++ b/packages/graph-node/src/database.ts @@ -112,6 +112,8 @@ export class Database { if (isDerived) { // For derived relational field. condition = `${alias}.${derivedField} = entity.id AND ${alias}.block_number <= entity.block_number`; + + selectQueryBuilder = selectQueryBuilder.addOrderBy(`${alias}.id`); } else { if (isArray) { // For one to many relational field. @@ -120,6 +122,8 @@ export class Database { // For one to one relational field. condition = `entity.${field} = ${alias}.id AND ${alias}.block_number <= entity.block_number`; } + + selectQueryBuilder = selectQueryBuilder.addOrderBy(`${alias}.block_number`, 'DESC'); } if (isArray) { @@ -128,14 +132,14 @@ export class Database { relatedEntity, alias, condition - ).addOrderBy(`${alias}.block_number`, 'DESC'); + ); } else { selectQueryBuilder = selectQueryBuilder.leftJoinAndMapOne( `entity.${field}`, relatedEntity, alias, condition - ).addOrderBy(`${alias}.block_number`, 'DESC'); + ); } }); From 5f728b885472be362881f9aceba0a065f8f8d2cc Mon Sep 17 00:00:00 2001 From: nabarun Date: Wed, 24 Nov 2021 15:26:19 +0530 Subject: [PATCH 5/7] Refactor example subgraph schema entities --- packages/graph-node/README.md | 2 +- packages/graph-node/src/loader.ts | 8 +- .../subgraph/example1/generated/schema.ts | 187 +++++++++--------- .../test/subgraph/example1/schema.graphql | 32 +-- .../test/subgraph/example1/src/mapping.ts | 64 +++--- .../src/cli/reset-cmds/state.ts | 8 +- .../entity/{ExampleEntity.ts => Author.ts} | 26 +-- .../graph-test-watcher/src/entity/Blog.ts | 43 ++++ .../{ManyRelatedEntity.ts => Category.ts} | 5 +- .../src/entity/RelatedEntity.ts | 28 --- .../src/gql/queries/author.gql | 16 ++ .../src/gql/queries/blog.gql | 17 ++ .../src/gql/queries/category.gql | 7 + .../src/gql/queries/exampleEntity.gql | 21 -- .../src/gql/queries/manyRelatedEntity.gql | 6 - .../src/gql/queries/relatedEntity.gql | 10 - packages/graph-test-watcher/src/indexer.ts | 30 +-- packages/graph-test-watcher/src/resolvers.ts | 24 +-- packages/graph-test-watcher/src/schema.gql | 38 ++-- 19 files changed, 287 insertions(+), 285 deletions(-) rename packages/graph-test-watcher/src/entity/{ExampleEntity.ts => Author.ts} (61%) create mode 100644 packages/graph-test-watcher/src/entity/Blog.ts rename packages/graph-test-watcher/src/entity/{ManyRelatedEntity.ts => Category.ts} (86%) delete mode 100644 packages/graph-test-watcher/src/entity/RelatedEntity.ts create mode 100644 packages/graph-test-watcher/src/gql/queries/author.gql create mode 100644 packages/graph-test-watcher/src/gql/queries/blog.gql create mode 100644 packages/graph-test-watcher/src/gql/queries/category.gql delete mode 100644 packages/graph-test-watcher/src/gql/queries/exampleEntity.gql delete mode 100644 packages/graph-test-watcher/src/gql/queries/manyRelatedEntity.gql delete mode 100644 packages/graph-test-watcher/src/gql/queries/relatedEntity.gql diff --git a/packages/graph-node/README.md b/packages/graph-node/README.md index 5a204e4b6..03c0e9ede 100644 --- a/packages/graph-node/README.md +++ b/packages/graph-node/README.md @@ -74,7 +74,7 @@ Example: ```bash - yarn compare-entity --config-file environments/compare-cli-config.toml --query-name exampleEntity --block-hash 0xceed7ee9d3de97c99db12e42433cae9115bb311c516558539fb7114fa17d545b --entity-id 0xdc7d7a8920c8eecc098da5b7522a5f31509b5bfc + 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. diff --git a/packages/graph-node/src/loader.ts b/packages/graph-node/src/loader.ts index 8d58920a9..f5bc03d25 100644 --- a/packages/graph-node/src/loader.ts +++ b/packages/graph-node/src/loader.ts @@ -53,7 +53,13 @@ export interface Context { } } -export const instantiate = async (database: Database, indexer: IndexerInterface, context: Context, filePath: string, data: GraphData = {}): Promise => { +export const instantiate = async ( + database: Database, + indexer: IndexerInterface, + context: Context, + filePath: string, + data: GraphData = {} +): Promise => { const { abis = {}, dataSource } = data; const buffer = await fs.readFile(filePath); const provider = getDefaultProvider(NETWORK_URL); diff --git a/packages/graph-node/test/subgraph/example1/generated/schema.ts b/packages/graph-node/test/subgraph/example1/generated/schema.ts index 25123d01a..866bf97ab 100644 --- a/packages/graph-node/test/subgraph/example1/generated/schema.ts +++ b/packages/graph-node/test/subgraph/example1/generated/schema.ts @@ -12,30 +12,33 @@ import { BigDecimal } from "@graphprotocol/graph-ts"; -export class RelatedEntity extends Entity { +export class Blog extends Entity { constructor(id: string) { super(); this.set("id", Value.fromString(id)); - this.set("paramBigInt", Value.fromBigInt(BigInt.zero())); - this.set("bigIntArray", Value.fromBigIntArray(new Array(0))); + this.set("kind", Value.fromString("")); + 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 { 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) { assert( 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.' ); - store.set("RelatedEntity", id.toString(), this); + store.set("Blog", id.toString(), this); } } - static load(id: string): RelatedEntity | null { - return changetype(store.get("RelatedEntity", id)); + static load(id: string): Blog | null { + return changetype(store.get("Blog", id)); } get id(): string { @@ -47,72 +50,79 @@ export class RelatedEntity extends Entity { this.set("id", Value.fromString(value)); } - get paramBigInt(): BigInt { - let value = this.get("paramBigInt"); - return value!.toBigInt(); + get kind(): string { + let value = this.get("kind"); + return value!.toString(); + } + + set kind(value: string) { + this.set("kind", Value.fromString(value)); + } + + get isActive(): boolean { + let value = this.get("isActive"); + return value!.toBoolean(); } - set paramBigInt(value: BigInt) { - this.set("paramBigInt", Value.fromBigInt(value)); + set isActive(value: boolean) { + this.set("isActive", Value.fromBoolean(value)); } - get bigIntArray(): Array { - let value = this.get("bigIntArray"); + get reviews(): Array { + let value = this.get("reviews"); return value!.toBigIntArray(); } - set bigIntArray(value: Array) { - this.set("bigIntArray", Value.fromBigIntArray(value)); + set reviews(value: Array) { + this.set("reviews", Value.fromBigIntArray(value)); } - get example(): string | null { - let value = this.get("example"); - if (!value || value.kind == ValueKind.NULL) { - return null; - } else { - return value.toString(); - } + get author(): string { + let value = this.get("author"); + return value!.toString(); } - set example(value: string | null) { - if (!value) { - this.unset("example"); - } else { - this.set("example", Value.fromString(value)); - } + set author(value: string) { + this.set("author", Value.fromString(value)); + } + + get categories(): Array { + let value = this.get("categories"); + return value!.toStringArray(); + } + + set categories(value: Array) { + this.set("categories", Value.fromStringArray(value)); } } -export class ExampleEntity extends Entity { +export class Author extends Entity { constructor(id: string) { super(); this.set("id", Value.fromString(id)); - this.set("count", Value.fromBigInt(BigInt.zero())); - this.set("paramString", Value.fromString("")); + this.set("blogCount", Value.fromBigInt(BigInt.zero())); + this.set("name", Value.fromString("")); + this.set("rating", Value.fromBigDecimal(BigDecimal.zero())); this.set("paramInt", Value.fromI32(0)); - this.set("paramBoolean", Value.fromBoolean(false)); this.set("paramBytes", Value.fromBytes(Bytes.empty())); - this.set("paramEnum", Value.fromString("")); - this.set("paramBigDecimal", Value.fromBigDecimal(BigDecimal.zero())); - this.set("manyRelateds", Value.fromStringArray(new Array(0))); } save(): void { 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) { assert( 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.' ); - store.set("ExampleEntity", id.toString(), this); + store.set("Author", id.toString(), this); } } - static load(id: string): ExampleEntity | null { - return changetype(store.get("ExampleEntity", id)); + static load(id: string): Author | null { + return changetype(store.get("Author", id)); } get id(): string { @@ -124,22 +134,31 @@ export class ExampleEntity extends Entity { this.set("id", Value.fromString(value)); } - get count(): BigInt { - let value = this.get("count"); + get blogCount(): BigInt { + let value = this.get("blogCount"); return value!.toBigInt(); } - set count(value: BigInt) { - this.set("count", Value.fromBigInt(value)); + set blogCount(value: BigInt) { + this.set("blogCount", Value.fromBigInt(value)); } - get paramString(): string { - let value = this.get("paramString"); + get name(): string { + let value = this.get("name"); return value!.toString(); } - set paramString(value: string) { - this.set("paramString", Value.fromString(value)); + set name(value: string) { + 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 { @@ -151,15 +170,6 @@ export class ExampleEntity extends Entity { 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 { let value = this.get("paramBytes"); return value!.toBytes(); @@ -169,68 +179,40 @@ export class ExampleEntity extends Entity { this.set("paramBytes", Value.fromBytes(value)); } - get paramEnum(): string { - let value = this.get("paramEnum"); - 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 relateds(): Array { - let value = this.get("relateds"); - return value!.toStringArray(); - } - - set relateds(value: Array) { - this.set("relateds", Value.fromStringArray(value)); - } - - get manyRelateds(): Array { - let value = this.get("manyRelateds"); + get blogs(): Array { + let value = this.get("blogs"); return value!.toStringArray(); } - set manyRelateds(value: Array) { - this.set("manyRelateds", Value.fromStringArray(value)); + set blogs(value: Array) { + this.set("blogs", Value.fromStringArray(value)); } } -export class ManyRelatedEntity extends Entity { +export class Category extends Entity { constructor(id: string) { super(); this.set("id", Value.fromString(id)); + this.set("name", Value.fromString("")); this.set("count", Value.fromBigInt(BigInt.zero())); } save(): void { 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) { assert( 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.' ); - store.set("ManyRelatedEntity", id.toString(), this); + store.set("Category", id.toString(), this); } } - static load(id: string): ManyRelatedEntity | null { - return changetype( - store.get("ManyRelatedEntity", id) - ); + static load(id: string): Category | null { + return changetype(store.get("Category", id)); } get id(): string { @@ -242,6 +224,15 @@ export class ManyRelatedEntity extends Entity { 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 { let value = this.get("count"); return value!.toBigInt(); diff --git a/packages/graph-node/test/subgraph/example1/schema.graphql b/packages/graph-node/test/subgraph/example1/schema.graphql index 5bf09ba2d..c36ee617b 100644 --- a/packages/graph-node/test/subgraph/example1/schema.graphql +++ b/packages/graph-node/test/subgraph/example1/schema.graphql @@ -1,29 +1,29 @@ -enum EnumType { - choice1 - choice2 +enum BlogKind { + short + long } -type RelatedEntity @entity { +type Blog @entity { id: ID! - paramBigInt: BigInt! - bigIntArray: [BigInt!]! - example: ExampleEntity + kind: BlogKind! + isActive: Boolean! + reviews: [BigInt!]! + author: Author! + categories: [Category!]! } -type ExampleEntity @entity { +type Author @entity { id: ID! - count: BigInt! - paramString: String! # string + blogCount: BigInt! + name: String! # string + rating: BigDecimal! paramInt: Int! # uint8 - paramBoolean: Boolean! paramBytes: Bytes! - paramEnum: EnumType! - paramBigDecimal: BigDecimal! - relateds: [RelatedEntity!]! @derivedFrom(field: "example") - manyRelateds: [ManyRelatedEntity!]! + blogs: [Blog!]! @derivedFrom(field: "author") } -type ManyRelatedEntity @entity { +type Category @entity { id: ID! + name: String! count: BigInt! } diff --git a/packages/graph-node/test/subgraph/example1/src/mapping.ts b/packages/graph-node/test/subgraph/example1/src/mapping.ts index 02a4483b3..959fd2be4 100644 --- a/packages/graph-node/test/subgraph/example1/src/mapping.ts +++ b/packages/graph-node/test/subgraph/example1/src/mapping.ts @@ -4,7 +4,7 @@ import { Example1, Test } from '../generated/Example1/Example1'; -import { ExampleEntity, ManyRelatedEntity, RelatedEntity } from '../generated/schema'; +import { Author, Blog, Category } from '../generated/schema'; export function handleTest (event: Test): void { 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 // 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; // `null` checks allow to create entities on demand - if (!entity) { - entity = new ExampleEntity(event.transaction.from.toHex()); + if (!author) { + author = new Author(event.transaction.from.toHex()); // Entity fields can be set using simple assignments - entity.count = BigInt.fromString('0'); + author.blogCount = BigInt.fromString('0'); } // 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.paramString = event.params.param1; - entity.paramInt = event.params.param2; - entity.paramBoolean = true; - entity.paramBytes = event.address; - entity.paramEnum = 'choice1'; - entity.paramBigDecimal = BigDecimal.fromString('123'); - - let relatedEntity = RelatedEntity.load(entity.count.toString()); - - if (!relatedEntity) { - relatedEntity = new RelatedEntity(entity.count.toString()); - relatedEntity.paramBigInt = BigInt.fromString('123'); + author.name = event.params.param1; + author.paramInt = event.params.param2; + author.paramBytes = event.address; + author.rating = BigDecimal.fromString('4'); + + // Entities can be written to the store with `.save()` + author.save(); + + let category = Category.load(author.blogCount.toString()); + + if (!category) { + category = new Category(author.blogCount.toString()); + category.name = event.params.param1; } - const bigIntArray = relatedEntity.bigIntArray; - bigIntArray.push(entity.count); - relatedEntity.bigIntArray = bigIntArray; - relatedEntity.example = entity.id; + category.count = category.count + BigInt.fromString('1'); + category.save(); - relatedEntity.save(); + const blog = new Blog(event.transaction.hash.toHexString()); + blog.kind = 'long'; + blog.isActive = true; - const manyRelatedEntity = new ManyRelatedEntity(event.transaction.hash.toHexString()); - manyRelatedEntity.count = entity.count; - manyRelatedEntity.save(); + const blogReviews = blog.reviews; + blogReviews.push(BigInt.fromString('4')); + blog.reviews = blogReviews; - const manyRelateds = entity.manyRelateds; - manyRelateds.push(manyRelatedEntity.id); - entity.manyRelateds = manyRelateds; + blog.author = author.id; - // Entities can be written to the store with `.save()` - entity.save(); + const categories = blog.categories; + categories.push(category.id); + blog.categories = categories; + + blog.save(); const contractAddress = dataSource.address(); const contract = Example1.bind(contractAddress); diff --git a/packages/graph-test-watcher/src/cli/reset-cmds/state.ts b/packages/graph-test-watcher/src/cli/reset-cmds/state.ts index 6e15d69b4..0eeff659a 100644 --- a/packages/graph-test-watcher/src/cli/reset-cmds/state.ts +++ b/packages/graph-test-watcher/src/cli/reset-cmds/state.ts @@ -16,9 +16,9 @@ import { BlockProgress } from '../../entity/BlockProgress'; import { GetMethod } from '../../entity/GetMethod'; import { _Test } from '../../entity/_Test'; -import { ExampleEntity } from '../../entity/ExampleEntity'; -import { RelatedEntity } from '../../entity/RelatedEntity'; -import { ManyRelatedEntity } from '../../entity/ManyRelatedEntity'; +import { Author } from '../../entity/Author'; +import { Blog } from '../../entity/Blog'; +import { Category } from '../../entity/Category'; const log = debug('vulcanize:reset-state'); @@ -65,7 +65,7 @@ export const handler = async (argv: any): Promise => { const dbTx = await db.createTransactionRunner(); try { - const entities = [BlockProgress, GetMethod, _Test, ExampleEntity, ManyRelatedEntity, RelatedEntity]; + const entities = [BlockProgress, GetMethod, _Test, Author, Category, Blog]; const removeEntitiesPromise = entities.map(async entityClass => { return db.removeEntities(dbTx, entityClass, { blockNumber: MoreThan(argv.blockNumber) }); diff --git a/packages/graph-test-watcher/src/entity/ExampleEntity.ts b/packages/graph-test-watcher/src/entity/Author.ts similarity index 61% rename from packages/graph-test-watcher/src/entity/ExampleEntity.ts rename to packages/graph-test-watcher/src/entity/Author.ts index 0997f3fb7..5981e743a 100644 --- a/packages/graph-test-watcher/src/entity/ExampleEntity.ts +++ b/packages/graph-test-watcher/src/entity/Author.ts @@ -7,13 +7,8 @@ import Decimal from 'decimal.js'; import { bigintTransformer, decimalTransformer } from '@vulcanize/util'; -enum EnumType { - choice1 = 'choice1', - choice2 = 'choice2' -} - @Entity() -export class ExampleEntity { +export class Author { @PrimaryColumn('varchar') id!: string; @@ -24,30 +19,17 @@ export class ExampleEntity { blockNumber!: number; @Column('bigint', { transformer: bigintTransformer }) - count!: bigint; + blogCount!: bigint; @Column('varchar') - paramString!: string + name!: string @Column('integer') paramInt!: number - @Column('boolean') - paramBoolean!: boolean - @Column('varchar') paramBytes!: string - @Column({ - type: 'enum', - enum: EnumType, - default: EnumType.choice1 - }) - paramEnum!: EnumType - @Column('numeric', { default: 0, transformer: decimalTransformer }) - paramBigDecimal!: Decimal - - @Column('varchar', { array: true }) - manyRelateds!: string[] + rating!: Decimal } diff --git a/packages/graph-test-watcher/src/entity/Blog.ts b/packages/graph-test-watcher/src/entity/Blog.ts new file mode 100644 index 000000000..40ce50bb5 --- /dev/null +++ b/packages/graph-test-watcher/src/entity/Blog.ts @@ -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[] +} diff --git a/packages/graph-test-watcher/src/entity/ManyRelatedEntity.ts b/packages/graph-test-watcher/src/entity/Category.ts similarity index 86% rename from packages/graph-test-watcher/src/entity/ManyRelatedEntity.ts rename to packages/graph-test-watcher/src/entity/Category.ts index e8fce624f..29c7aed04 100644 --- a/packages/graph-test-watcher/src/entity/ManyRelatedEntity.ts +++ b/packages/graph-test-watcher/src/entity/Category.ts @@ -7,7 +7,7 @@ import { Entity, PrimaryColumn, Column } from 'typeorm'; import { bigintTransformer } from '@vulcanize/util'; @Entity() -export class ManyRelatedEntity { +export class Category { @PrimaryColumn('varchar') id!: string; @@ -19,4 +19,7 @@ export class ManyRelatedEntity { @Column('bigint', { transformer: bigintTransformer }) count!: bigint; + + @Column('varchar') + name!: string; } diff --git a/packages/graph-test-watcher/src/entity/RelatedEntity.ts b/packages/graph-test-watcher/src/entity/RelatedEntity.ts deleted file mode 100644 index d732a2764..000000000 --- a/packages/graph-test-watcher/src/entity/RelatedEntity.ts +++ /dev/null @@ -1,28 +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[]; - - @Column('varchar') - example!: string; -} diff --git a/packages/graph-test-watcher/src/gql/queries/author.gql b/packages/graph-test-watcher/src/gql/queries/author.gql new file mode 100644 index 000000000..ff3644dfe --- /dev/null +++ b/packages/graph-test-watcher/src/gql/queries/author.gql @@ -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 + } + } +} diff --git a/packages/graph-test-watcher/src/gql/queries/blog.gql b/packages/graph-test-watcher/src/gql/queries/blog.gql new file mode 100644 index 000000000..313ca9fc6 --- /dev/null +++ b/packages/graph-test-watcher/src/gql/queries/blog.gql @@ -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 + } + } +} diff --git a/packages/graph-test-watcher/src/gql/queries/category.gql b/packages/graph-test-watcher/src/gql/queries/category.gql new file mode 100644 index 000000000..d3e24e23e --- /dev/null +++ b/packages/graph-test-watcher/src/gql/queries/category.gql @@ -0,0 +1,7 @@ +query category($id: String!, $blockHash: Bytes!){ + category(id: $id, block: { hash: $blockHash }){ + id + count + name + } +} diff --git a/packages/graph-test-watcher/src/gql/queries/exampleEntity.gql b/packages/graph-test-watcher/src/gql/queries/exampleEntity.gql deleted file mode 100644 index ef988360a..000000000 --- a/packages/graph-test-watcher/src/gql/queries/exampleEntity.gql +++ /dev/null @@ -1,21 +0,0 @@ -query exampleEntity($id: String!, $blockHash: Bytes!){ - exampleEntity(id: $id, block: { hash: $blockHash }){ - id - count - paramString - paramInt - paramBoolean - paramBytes - paramEnum - paramBigDecimal - relateds { - id - paramBigInt - bigIntArray - } - manyRelateds { - id - count - } - } -} diff --git a/packages/graph-test-watcher/src/gql/queries/manyRelatedEntity.gql b/packages/graph-test-watcher/src/gql/queries/manyRelatedEntity.gql deleted file mode 100644 index 95ce0ed50..000000000 --- a/packages/graph-test-watcher/src/gql/queries/manyRelatedEntity.gql +++ /dev/null @@ -1,6 +0,0 @@ -query manyRelatedEntity($id: String!, $blockHash: Bytes!){ - manyRelatedEntity(id: $id, block: { hash: $blockHash }){ - id - count - } -} diff --git a/packages/graph-test-watcher/src/gql/queries/relatedEntity.gql b/packages/graph-test-watcher/src/gql/queries/relatedEntity.gql deleted file mode 100644 index cf885971c..000000000 --- a/packages/graph-test-watcher/src/gql/queries/relatedEntity.gql +++ /dev/null @@ -1,10 +0,0 @@ -query relatedEntity($id: String!, $blockHash: Bytes!){ - relatedEntity(id: $id, block: { hash: $blockHash }){ - id - paramBigInt - bigIntArray - example { - id - } - } -} diff --git a/packages/graph-test-watcher/src/indexer.ts b/packages/graph-test-watcher/src/indexer.ts index 2c90b4664..72d5517c6 100644 --- a/packages/graph-test-watcher/src/indexer.ts +++ b/packages/graph-test-watcher/src/indexer.ts @@ -29,9 +29,9 @@ import { IPLDBlock } from './entity/IPLDBlock'; import artifacts from './artifacts/Example.json'; import { createInitialCheckpoint, handleEvent, createStateDiff, createStateCheckpoint } from './hooks'; import { IPFSClient } from './ipfs'; -import { ExampleEntity } from './entity/ExampleEntity'; -import { RelatedEntity } from './entity/RelatedEntity'; -import { ManyRelatedEntity } from './entity/ManyRelatedEntity'; +import { Author } from './entity/Author'; +import { Blog } from './entity/Blog'; +import { Category } from './entity/Category'; const log = debug('vulcanize:indexer'); @@ -749,25 +749,25 @@ export class Indexer implements IndexerInterface { _populateRelationsMap (): void { // Needs to be generated by codegen. - this._relationsMap.set(ExampleEntity, { - relateds: { - entity: RelatedEntity, + this._relationsMap.set(Author, { + blogs: { + entity: Blog, isDerived: true, isArray: true, - field: 'example' - }, - manyRelateds: { - entity: ManyRelatedEntity, - isDerived: false, - isArray: true + field: 'author' } }); - this._relationsMap.set(RelatedEntity, { - example: { - entity: ExampleEntity, + this._relationsMap.set(Blog, { + author: { + entity: Author, isDerived: false, isArray: false + }, + categories: { + entity: Category, + isDerived: false, + isArray: true } }); } diff --git a/packages/graph-test-watcher/src/resolvers.ts b/packages/graph-test-watcher/src/resolvers.ts index 80915fd77..1eedf4e9f 100644 --- a/packages/graph-test-watcher/src/resolvers.ts +++ b/packages/graph-test-watcher/src/resolvers.ts @@ -11,9 +11,9 @@ import { ValueResult, BlockHeight } from '@vulcanize/util'; import { Indexer } from './indexer'; import { EventWatcher } from './events'; -import { ExampleEntity } from './entity/ExampleEntity'; -import { RelatedEntity } from './entity/RelatedEntity'; -import { ManyRelatedEntity } from './entity/ManyRelatedEntity'; +import { Author } from './entity/Author'; +import { Blog } from './entity/Blog'; +import { Category } from './entity/Category'; const log = debug('vulcanize:resolver'); @@ -56,22 +56,22 @@ export const createResolvers = async (indexer: Indexer, eventWatcher: EventWatch return indexer._test(blockHash, contractAddress); }, - relatedEntity: async (_: any, { id, block = {} }: { id: string, block: BlockHeight }): Promise => { - log('relatedEntity', id, block); + blog: async (_: any, { id, block = {} }: { id: string, block: BlockHeight }): Promise => { + log('blog', id, block); - return indexer.getSubgraphEntity(RelatedEntity, id, block.hash); + return indexer.getSubgraphEntity(Blog, id, block.hash); }, - manyRelatedEntity: async (_: any, { id, block = {} }: { id: string, block: BlockHeight }): Promise => { - log('relatedEntity', id, block); + category: async (_: any, { id, block = {} }: { id: string, block: BlockHeight }): Promise => { + log('category', id, block); - return indexer.getSubgraphEntity(ManyRelatedEntity, id, block.hash); + return indexer.getSubgraphEntity(Category, id, block.hash); }, - exampleEntity: async (_: any, { id, block = {} }: { id: string, block: BlockHeight }): Promise => { - log('exampleEntity', id, block); + author: async (_: any, { id, block = {} }: { id: string, block: BlockHeight }): Promise => { + log('author', id, block); - return indexer.getSubgraphEntity(ExampleEntity, id, block.hash); + return indexer.getSubgraphEntity(Author, id, block.hash); }, events: async (_: any, { blockHash, contractAddress, name }: { blockHash: string, contractAddress: string, name?: string }) => { diff --git a/packages/graph-test-watcher/src/schema.gql b/packages/graph-test-watcher/src/schema.gql index f31049c27..99ee2d998 100644 --- a/packages/graph-test-watcher/src/schema.gql +++ b/packages/graph-test-watcher/src/schema.gql @@ -77,41 +77,41 @@ type Query { eventsInRange(fromBlockNumber: Int!, toBlockNumber: Int!): [ResultEvent!] getMethod(blockHash: String!, contractAddress: String!): ResultString! _test(blockHash: String!, contractAddress: String!): ResultBigInt! - relatedEntity(id: String!, block: Block_height): RelatedEntity! - exampleEntity(id: String!, block: Block_height): ExampleEntity! - manyRelatedEntity(id: String!, block: Block_height): ManyRelatedEntity! + blog(id: String!, block: Block_height): Blog! + author(id: String!, block: Block_height): Author! + category(id: String!, block: Block_height): Category! getStateByCID(cid: String!): ResultIPLDBlock getState(blockHash: String!, contractAddress: String!, kind: String): ResultIPLDBlock } -enum EnumType { - choice1 - choice2 +enum BlogKind { + short + long } -type RelatedEntity { +type Blog { id: ID! - paramBigInt: BigInt! - bigIntArray: [BigInt!]! - example: ExampleEntity + kind: BlogKind! + isActive: Boolean! + reviews: [BigInt!]! + author: Author! + categories: [Category!]! } -type ManyRelatedEntity { +type Category { id: ID! count: BigInt! + name: String! } -type ExampleEntity { +type Author { id: ID! - count: BigInt! - paramString: String! + blogCount: BigInt! + name: String! + rating: BigDecimal! paramInt: Int! - paramBoolean: Boolean! paramBytes: Bytes! - paramEnum: EnumType! - paramBigDecimal: BigDecimal! - relateds: [RelatedEntity!]! - manyRelateds: [ManyRelatedEntity!]! + blogs: [Blog!]! } type Mutation { From 0e63cd7c24e247751d43bc9f091d71c1daf090b6 Mon Sep 17 00:00:00 2001 From: nabarun Date: Fri, 26 Nov 2021 12:58:16 +0530 Subject: [PATCH 6/7] Fix watcher queries to return correct relation field values --- packages/graph-node/src/database.ts | 123 +++++++++++-------- packages/graph-node/src/watcher.ts | 6 +- packages/graph-test-watcher/src/indexer.ts | 6 +- packages/graph-test-watcher/src/resolvers.ts | 6 +- 4 files changed, 79 insertions(+), 62 deletions(-) diff --git a/packages/graph-node/src/database.ts b/packages/graph-node/src/database.ts index 71cef56da..9c59b5282 100644 --- a/packages/graph-node/src/database.ts +++ b/packages/graph-node/src/database.ts @@ -6,10 +6,12 @@ import assert from 'assert'; import { Connection, ConnectionOptions, - FindOneOptions + FindOneOptions, + LessThanOrEqual } from 'typeorm'; import { + BlockHeight, Database as BaseDatabase } from '@vulcanize/util'; @@ -74,76 +76,91 @@ export class Database { } } - async getEntityWithRelations (entity: (new () => Entity) | string, id: string, relations: { [key: string]: any }, blockHash?: string): Promise { + async getEntityWithRelations (entity: (new () => Entity) | string, id: string, relations: { [key: string]: any }, block: BlockHeight = {}): Promise { const queryRunner = this._conn.createQueryRunner(); + let { hash: blockHash, number: blockNumber } = block; try { const repo = queryRunner.manager.getRepository(entity); - let selectQueryBuilder = repo.createQueryBuilder('entity'); + const whereOptions: any = { id }; - selectQueryBuilder = selectQueryBuilder.where('entity.id = :id', { id }) - .orderBy('entity.block_number', 'DESC'); + if (blockNumber) { + whereOptions.blockNumber = LessThanOrEqual(blockNumber); + } - // 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 }); + whereOptions.blockHash = blockHash; + const block = await this._baseDatabase.getBlockProgress(queryRunner.manager.getRepository('block_progress'), blockHash); + blockNumber = block?.blockNumber; + } + + const findOptions = { + where: whereOptions, + order: { + blockNumber: 'DESC' } + }; + + let entityData: any = await repo.findOne(findOptions as FindOneOptions); + + if (!entityData && findOptions.where.blockHash) { + entityData = await this._baseDatabase.getPrevEntityVersion(queryRunner, repo, findOptions); } - // TODO: Implement query for nested relations. - Object.entries(relations).forEach(([field, data], index) => { - const { entity: relatedEntity, isArray, isDerived, field: derivedField } = data; - const alias = `relatedEntity${index}`; - let condition: string; + 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'); + } 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 (isDerived) { - // For derived relational field. - condition = `${alias}.${derivedField} = entity.id AND ${alias}.block_number <= entity.block_number`; + if (blockNumber) { + selectQueryBuilder = selectQueryBuilder.andWhere( + 'entity.block_number <= :blockNumber', + { blockNumber } + ); + } - selectQueryBuilder = selectQueryBuilder.addOrderBy(`${alias}.id`); - } else { if (isArray) { - // For one to many relational field. - condition = `${alias}.id IN (SELECT unnest(entity.${field})) AND ${alias}.block_number <= entity.block_number`; + entityData[field] = await selectQueryBuilder.getMany(); } else { - // For one to one relational field. - condition = `entity.${field} = ${alias}.id AND ${alias}.block_number <= entity.block_number`; + entityData[field] = await selectQueryBuilder.getOne(); } + }); - selectQueryBuilder = selectQueryBuilder.addOrderBy(`${alias}.block_number`, 'DESC'); - } - - if (isArray) { - selectQueryBuilder = selectQueryBuilder.leftJoinAndMapMany( - `entity.${field}`, - relatedEntity, - alias, - condition - ); - } else { - selectQueryBuilder = selectQueryBuilder.leftJoinAndMapOne( - `entity.${field}`, - relatedEntity, - alias, - condition - ); - } - }); + await Promise.all(relationQueryPromises); + } - return selectQueryBuilder.getOne(); + return entityData; } finally { await queryRunner.release(); } diff --git a/packages/graph-node/src/watcher.ts b/packages/graph-node/src/watcher.ts index e2130f1ba..86937d91a 100644 --- a/packages/graph-node/src/watcher.ts +++ b/packages/graph-node/src/watcher.ts @@ -11,7 +11,7 @@ import { ContractInterface, utils } from 'ethers'; import { ResultObject } from '@vulcanize/assemblyscript/lib/loader'; 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 { Context, instantiate } from './loader'; @@ -177,9 +177,9 @@ export class GraphWatcher { this._indexer = indexer; } - async getEntity (entity: new () => Entity, id: string, relations: { [key: string]: any }, blockHash?: string): Promise { + async getEntity (entity: new () => Entity, id: string, relations: { [key: string]: any }, block?: BlockHeight): Promise { // 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. return resolveEntityFieldConflicts(result); diff --git a/packages/graph-test-watcher/src/indexer.ts b/packages/graph-test-watcher/src/indexer.ts index 72d5517c6..182967038 100644 --- a/packages/graph-test-watcher/src/indexer.ts +++ b/packages/graph-test-watcher/src/indexer.ts @@ -16,7 +16,7 @@ import { BaseProvider } from '@ethersproject/providers'; import * as codec from '@ipld/dag-cbor'; import { EthClient } from '@vulcanize/ipld-eth-client'; import { StorageLayout } from '@vulcanize/solidity-mapper'; -import { EventInterface, Indexer as BaseIndexer, IndexerInterface, ValueResult, UNKNOWN_EVENT_NAME, ServerConfig, updateStateForElementaryType } from '@vulcanize/util'; +import { EventInterface, Indexer as BaseIndexer, IndexerInterface, ValueResult, UNKNOWN_EVENT_NAME, ServerConfig, updateStateForElementaryType, BlockHeight } from '@vulcanize/util'; import { GraphWatcher } from '@vulcanize/graph-node'; import { Database } from './database'; @@ -546,10 +546,10 @@ export class Indexer implements IndexerInterface { return (ipfsAddr !== undefined && ipfsAddr !== null && ipfsAddr !== ''); } - async getSubgraphEntity (entity: new () => Entity, id: string, blockHash?: string): Promise { + async getSubgraphEntity (entity: new () => Entity, id: string, block: BlockHeight): Promise { 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; } diff --git a/packages/graph-test-watcher/src/resolvers.ts b/packages/graph-test-watcher/src/resolvers.ts index 1eedf4e9f..283cf79f6 100644 --- a/packages/graph-test-watcher/src/resolvers.ts +++ b/packages/graph-test-watcher/src/resolvers.ts @@ -59,19 +59,19 @@ export const createResolvers = async (indexer: Indexer, eventWatcher: EventWatch blog: async (_: any, { id, block = {} }: { id: string, block: BlockHeight }): Promise => { log('blog', id, block); - return indexer.getSubgraphEntity(Blog, id, block.hash); + return indexer.getSubgraphEntity(Blog, id, block); }, category: async (_: any, { id, block = {} }: { id: string, block: BlockHeight }): Promise => { log('category', id, block); - return indexer.getSubgraphEntity(Category, id, block.hash); + return indexer.getSubgraphEntity(Category, id, block); }, author: async (_: any, { id, block = {} }: { id: string, block: BlockHeight }): Promise => { log('author', id, block); - return indexer.getSubgraphEntity(Author, id, block.hash); + return indexer.getSubgraphEntity(Author, id, block); }, events: async (_: any, { blockHash, contractAddress, name }: { blockHash: string, contractAddress: string, name?: string }) => { From ada643963365a69aff501919779b649c9a38acf7 Mon Sep 17 00:00:00 2001 From: nabarun Date: Fri, 26 Nov 2021 13:03:20 +0530 Subject: [PATCH 7/7] Fix hierarchical query for getting two entities at same block --- packages/eden-watcher/src/indexer.ts | 6 ++--- packages/eden-watcher/src/resolvers.ts | 36 +++++++++++++------------- packages/graph-node/src/database.ts | 18 ++++++++++++- packages/util/src/database.ts | 4 ++- 4 files changed, 41 insertions(+), 23 deletions(-) diff --git a/packages/eden-watcher/src/indexer.ts b/packages/eden-watcher/src/indexer.ts index 1bec3a875..201dfd858 100644 --- a/packages/eden-watcher/src/indexer.ts +++ b/packages/eden-watcher/src/indexer.ts @@ -16,7 +16,7 @@ import { BaseProvider } from '@ethersproject/providers'; import * as codec from '@ipld/dag-cbor'; import { EthClient } from '@vulcanize/ipld-eth-client'; import { StorageLayout } from '@vulcanize/solidity-mapper'; -import { EventInterface, Indexer as BaseIndexer, IndexerInterface, UNKNOWN_EVENT_NAME, ServerConfig } from '@vulcanize/util'; +import { EventInterface, Indexer as BaseIndexer, IndexerInterface, UNKNOWN_EVENT_NAME, ServerConfig, BlockHeight } from '@vulcanize/util'; import { GraphWatcher } from '@vulcanize/graph-node'; import { Database } from './database'; @@ -549,10 +549,10 @@ export class Indexer implements IndexerInterface { return (ipfsAddr !== undefined && ipfsAddr !== null && ipfsAddr !== ''); } - async getSubgraphEntity (entity: new () => Entity, id: string, blockHash?: string): Promise { + async getSubgraphEntity (entity: new () => Entity, id: string, block?: BlockHeight): Promise { 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; } diff --git a/packages/eden-watcher/src/resolvers.ts b/packages/eden-watcher/src/resolvers.ts index e0c561344..f91f1ee16 100644 --- a/packages/eden-watcher/src/resolvers.ts +++ b/packages/eden-watcher/src/resolvers.ts @@ -64,109 +64,109 @@ export const createResolvers = async (indexer: Indexer, eventWatcher: EventWatch producer: async (_: any, { id, block = {} }: { id: string, block: BlockHeight }) => { 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 }) => { 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 }) => { 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 }) => { 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 }) => { 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 }) => { 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 }) => { 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 }) => { 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 }) => { 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 }) => { 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 }) => { 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 }) => { 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 }) => { 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 }) => { 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 }) => { 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 }) => { 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 }) => { 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 }) => { 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 }) => { diff --git a/packages/graph-node/src/database.ts b/packages/graph-node/src/database.ts index 9c59b5282..64951ebb9 100644 --- a/packages/graph-node/src/database.ts +++ b/packages/graph-node/src/database.ts @@ -43,7 +43,6 @@ export class Database { } async getEntity (entity: (new () => Entity) | string, id: string, blockHash?: string): Promise { - // TODO: Take block number as an optional argument const queryRunner = this._conn.createQueryRunner(); try { @@ -134,6 +133,23 @@ export class Database { 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] }) diff --git a/packages/util/src/database.ts b/packages/util/src/database.ts index 02bcdf249..6ccddb416 100644 --- a/packages/util/src/database.ts +++ b/packages/util/src/database.ts @@ -456,7 +456,9 @@ export class Database { FROM block_progress b LEFT JOIN - ${repo.metadata.tableName} e ON e.block_hash = b.block_hash + ${repo.metadata.tableName} e + ON e.block_hash = b.block_hash + AND e.id = $2 WHERE b.block_hash = $1 UNION ALL