Skip to content

Commit

Permalink
Handle fragments in subgraph GQL queries
Browse files Browse the repository at this point in the history
  • Loading branch information
nikugogoi committed May 17, 2024
1 parent 8e2dc56 commit 5d1735d
Show file tree
Hide file tree
Showing 4 changed files with 66 additions and 31 deletions.
10 changes: 5 additions & 5 deletions packages/codegen/src/templates/indexer-template.handlebars
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import JSONbig from 'json-bigint';
{{/if}}
import { ethers, constants } from 'ethers';
{{#if (subgraphPath)}}
import { SelectionNode } from 'graphql';
import { GraphQLResolveInfo } from 'graphql';
{{/if}}

import { JsonFragment } from '@ethersproject/abi';
Expand Down Expand Up @@ -458,9 +458,9 @@ export class Indexer implements IndexerInterface {
entity: new () => Entity,
id: string,
block: BlockHeight,
selections: ReadonlyArray<SelectionNode> = []
queryInfo: GraphQLResolveInfo
): Promise<any> {
const data = await this._graphWatcher.getEntity(entity, id, this._relationsMap, block, selections);
const data = await this._graphWatcher.getEntity(entity, id, this._relationsMap, block, queryInfo);

return data;
}
Expand All @@ -470,9 +470,9 @@ export class Indexer implements IndexerInterface {
block: BlockHeight,
where: { [key: string]: any } = {},
queryOptions: QueryOptions = {},
selections: ReadonlyArray<SelectionNode> = []
queryInfo: GraphQLResolveInfo
): Promise<any[]> {
return this._graphWatcher.getEntities(entity, this._relationsMap, block, where, queryOptions, selections);
return this._graphWatcher.getEntities(entity, this._relationsMap, block, where, queryOptions, queryInfo);
}

{{/if}}
Expand Down
6 changes: 2 additions & 4 deletions packages/codegen/src/templates/resolvers-template.handlebars
Original file line number Diff line number Diff line change
Expand Up @@ -106,12 +106,11 @@ export const createResolvers = async (indexerArg: IndexerInterface, eventWatcher
log('{{this.queryName}}', id, JSON.stringify(block, jsonBigIntStringReplacer));
gqlTotalQueryCount.inc(1);
gqlQueryCount.labels('{{this.queryName}}').inc(1);
assert(info.fieldNodes[0].selectionSet);

// Set cache-control hints
// setGQLCacheHints(info, block, gqlCacheConfig);

return indexer.getSubgraphEntity({{this.entityName}}, id, block, info.fieldNodes[0].selectionSet.selections);
return indexer.getSubgraphEntity({{this.entityName}}, id, block, info);
},

{{this.pluralQueryName}}: async (
Expand All @@ -123,7 +122,6 @@ export const createResolvers = async (indexerArg: IndexerInterface, eventWatcher
log('{{this.pluralQueryName}}', JSON.stringify(block, jsonBigIntStringReplacer), JSON.stringify(where, jsonBigIntStringReplacer), first, skip, orderBy, orderDirection);
gqlTotalQueryCount.inc(1);
gqlQueryCount.labels('{{this.pluralQueryName}}').inc(1);
assert(info.fieldNodes[0].selectionSet);

// Set cache-control hints
// setGQLCacheHints(info, block, gqlCacheConfig);
Expand All @@ -133,7 +131,7 @@ export const createResolvers = async (indexerArg: IndexerInterface, eventWatcher
block,
where,
{ limit: first, skip, orderBy, orderDirection },
info.fieldNodes[0].selectionSet.selections
info
);
},

Expand Down
22 changes: 17 additions & 5 deletions packages/graph-node/src/watcher.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import debug from 'debug';
import path from 'path';
import fs from 'fs';
import { ContractInterface, utils, providers } from 'ethers';
import { SelectionNode } from 'graphql';
import { GraphQLResolveInfo, SelectionNode } from 'graphql';

import { ResultObject } from '@cerc-io/assemblyscript/lib/loader';
import {
Expand Down Expand Up @@ -321,13 +321,15 @@ export class GraphWatcher {
id: string,
relationsMap: Map<any, { [key: string]: any }>,
block: BlockHeight,
selections: ReadonlyArray<SelectionNode> = []
queryInfo: GraphQLResolveInfo
): Promise<any> {
const dbTx = await this._database.createTransactionRunner();

try {
const selections = this._getSelectionsFromGQLInfo(queryInfo);

// Get entity from the database.
const result = await this._database.getEntityWithRelations(dbTx, entity, id, relationsMap, block, selections);
const result = await this._database.getEntityWithRelations(dbTx, entity, id, relationsMap, block, selections, queryInfo);
await dbTx.commitTransaction();

// Resolve any field name conflicts in the entity result.
Expand All @@ -346,7 +348,7 @@ export class GraphWatcher {
block: BlockHeight,
where: { [key: string]: any } = {},
queryOptions: QueryOptions,
selections: ReadonlyArray<SelectionNode> = []
queryInfo: GraphQLResolveInfo
): Promise<any> {
const dbTx = await this._database.createTransactionRunner();

Expand All @@ -357,8 +359,10 @@ export class GraphWatcher {
queryOptions.limit = DEFAULT_LIMIT;
}

const selections = this._getSelectionsFromGQLInfo(queryInfo);

// Get entities from the database.
const entities = await this._database.getEntities(dbTx, entity, relationsMap, block, where, queryOptions, selections);
const entities = await this._database.getEntities(dbTx, entity, relationsMap, block, where, queryOptions, selections, queryInfo);
await dbTx.commitTransaction();

return entities;
Expand Down Expand Up @@ -553,6 +557,14 @@ export class GraphWatcher {
return acc;
}, {});
}

_getSelectionsFromGQLInfo (queryInfo: GraphQLResolveInfo): readonly SelectionNode[] {
const [fieldNode] = queryInfo.fieldNodes;
const selectionSet = fieldNode.selectionSet;
assert(selectionSet, `selectionSet not present in GQL fieldNode ${fieldNode.name}`);

return selectionSet.selections;
}
}

export const getGraphDbAndWatcher = async (
Expand Down
59 changes: 42 additions & 17 deletions packages/util/src/graph/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import {
} from 'typeorm';
import { ColumnMetadata } from 'typeorm/metadata/ColumnMetadata';
import { RawSqlResultsToEntityTransformer } from 'typeorm/query-builder/transformer/RawSqlResultsToEntityTransformer';
import { SelectionNode } from 'graphql';
import { GraphQLResolveInfo, SelectionNode } from 'graphql';
import _ from 'lodash';
import debug from 'debug';

Expand Down Expand Up @@ -221,7 +221,8 @@ export class GraphDatabase {
id: string,
relationsMap: Map<any, { [key: string]: any }>,
block: CanonicalBlockHeight = {},
selections: ReadonlyArray<SelectionNode> = []
selections: ReadonlyArray<SelectionNode> = [],
queryInfo: GraphQLResolveInfo
): Promise<Entity | undefined> {
const { hash: blockHash, number: blockNumber } = block;
const repo = queryRunner.manager.getRepository<Entity>(entityType);
Expand All @@ -248,7 +249,7 @@ export class GraphDatabase {

// Get relational fields
if (entityData) {
entityData = await this.loadEntityRelations(queryRunner, block, relationsMap, entityType, entityData, selections);
entityData = await this.loadEntityRelations(queryRunner, block, relationsMap, entityType, entityData, selections, queryInfo);
}

return entityData;
Expand All @@ -259,7 +260,8 @@ export class GraphDatabase {
block: CanonicalBlockHeight,
relationsMap: Map<any, { [key: string]: any }>,
entityType: new () => Entity, entityData: any,
selections: ReadonlyArray<SelectionNode> = []
selections: ReadonlyArray<SelectionNode> = [],
queryInfo: GraphQLResolveInfo
): Promise<Entity> {
const relations = relationsMap.get(entityType);
if (relations === undefined) {
Expand Down Expand Up @@ -292,7 +294,8 @@ export class GraphDatabase {
block,
where,
{ limit: DEFAULT_LIMIT },
childSelections
childSelections,
queryInfo
);

entityData[field] = relatedEntities;
Expand All @@ -316,7 +319,8 @@ export class GraphDatabase {
block,
where,
{ limit: DEFAULT_LIMIT },
childSelections
childSelections,
queryInfo
);

entityData[field] = relatedEntities;
Expand All @@ -331,7 +335,8 @@ export class GraphDatabase {
entityData[field],
relationsMap,
block,
childSelections
childSelections,
queryInfo
);

entityData[field] = relatedEntity;
Expand All @@ -342,18 +347,33 @@ export class GraphDatabase {
return entityData;
}

_defragmentGQLQuerySelections (selections: ReadonlyArray<SelectionNode>, queryInfo: GraphQLResolveInfo): SelectionNode[] {
return selections.reduce((acc: SelectionNode[], selection) => {
if (selection.kind === 'FragmentSpread') {
const fragmentSelections = queryInfo.fragments[selection.name.value].selectionSet.selections;

return [...acc, ...fragmentSelections];
}

return [...acc, selection];
}, []);
}

async getEntities<Entity extends ObjectLiteral> (
queryRunner: QueryRunner,
entityType: new () => Entity,
relationsMap: Map<any, { [key: string]: any }>,
block: CanonicalBlockHeight = {},
where: Where = {},
queryOptions: QueryOptions = {},
selections: ReadonlyArray<SelectionNode> = []
selections: ReadonlyArray<SelectionNode> = [],
queryInfo: GraphQLResolveInfo
): Promise<Entity[]> {
let entities: Entity[] = [];
const latestEntityType = this._entityToLatestEntityMap.get(entityType);

const defragmentedSelections = this._defragmentGQLQuerySelections(selections, queryInfo);

if (latestEntityType) {
if (Object.keys(block).length) {
// Use lateral query for entities with latest entity table.
Expand All @@ -375,7 +395,7 @@ export class GraphDatabase {
relationsMap,
where,
queryOptions,
selections
defragmentedSelections
);
}
} else {
Expand Down Expand Up @@ -405,7 +425,7 @@ export class GraphDatabase {
return [];
}

entities = await this.loadEntitiesRelations(queryRunner, block, relationsMap, entityType, entities, selections);
entities = await this.loadEntitiesRelations(queryRunner, block, relationsMap, entityType, entities, defragmentedSelections, queryInfo);
// Resolve any field name conflicts in the entity result.
entities = entities.map(entity => resolveEntityFieldConflicts(entity));

Expand Down Expand Up @@ -744,7 +764,8 @@ export class GraphDatabase {
relationsMap: Map<any, { [key: string]: any }>,
entity: new () => Entity,
entities: Entity[],
selections: ReadonlyArray<SelectionNode> = []
selections: ReadonlyArray<SelectionNode> = [],
queryInfo: GraphQLResolveInfo
): Promise<Entity[]> {
const relations = relationsMap.get(entity);
if (relations === undefined) {
Expand All @@ -755,11 +776,11 @@ export class GraphDatabase {

if (this._serverConfig.loadRelationsSequential) {
for (const selection of relationSelections) {
await this.loadRelation(queryRunner, block, relationsMap, relations, entities, selection);
await this.loadRelation(queryRunner, block, relationsMap, relations, entities, selection, queryInfo);
}
} else {
const loadRelationPromises = relationSelections.map(async selection => {
await this.loadRelation(queryRunner, block, relationsMap, relations, entities, selection);
await this.loadRelation(queryRunner, block, relationsMap, relations, entities, selection, queryInfo);
});

await Promise.all(loadRelationPromises);
Expand All @@ -774,7 +795,8 @@ export class GraphDatabase {
relationsMap: Map<any, { [key: string]: any }>,
relations: { [key: string]: any },
entities: Entity[],
selection: SelectionNode
selection: SelectionNode,
queryInfo: GraphQLResolveInfo
): Promise<void> {
assert(selection.kind === 'Field');
const field = selection.name.value;
Expand All @@ -800,7 +822,8 @@ export class GraphDatabase {
block,
where,
{},
childSelections
childSelections,
queryInfo
);

const relatedEntitiesMap = relatedEntities.reduce((acc: {[key:string]: any[]}, entity: any) => {
Expand Down Expand Up @@ -851,7 +874,8 @@ export class GraphDatabase {
block,
where,
{},
childSelections
childSelections,
queryInfo
);

entities.forEach((entity: any) => {
Expand Down Expand Up @@ -902,7 +926,8 @@ export class GraphDatabase {
block,
where,
{},
childSelections
childSelections,
queryInfo
);

const relatedEntitiesMap = relatedEntities.reduce((acc: {[key:string]: any}, entity: any) => {
Expand Down

0 comments on commit 5d1735d

Please sign in to comment.