Generate relations and entity types maps in indexer (#88)

This commit is contained in:
prathamesh0 2021-12-22 20:07:00 +05:30 committed by nabarun
parent d96cc095bb
commit 18f73e57c0
6 changed files with 159 additions and 26 deletions

View File

@ -11,6 +11,7 @@ import { Writable } from 'stream';
import { getTsForSol, getPgForTs, getTsForGql } from './utils/type-mappings';
import { Param } from './utils/types';
import { getFieldType } from './utils/subgraph';
const TEMPLATE_FILE = './templates/entity-template.handlebars';
const TABLES_DIR = './data/entities';
@ -334,7 +335,7 @@ export class Entity {
columnType: 'Column'
};
const { typeName, array, nullable } = this._getFieldType(field.type);
const { typeName, array, nullable } = getFieldType(field.type);
let tsType = getTsForGql(typeName);
if (!tsType) {
@ -396,19 +397,4 @@ export class Entity {
return entityObject;
}
_getFieldType (typeNode: any): { typeName: string, array: boolean, nullable: boolean } {
if (typeNode.kind === 'ListType') {
return { typeName: this._getFieldType(typeNode.type).typeName, array: true, nullable: true };
}
if (typeNode.kind === 'NonNullType') {
const fieldType = this._getFieldType(typeNode.type);
return { typeName: fieldType.typeName, array: fieldType.array, nullable: false };
}
// If 'NamedType'.
return { typeName: typeNode.name.value, array: false, nullable: true };
}
}

View File

@ -12,17 +12,20 @@ import _ from 'lodash';
import { getTsForSol } from './utils/type-mappings';
import { Param } from './utils/types';
import { MODE_ETH_CALL, MODE_STORAGE } from './utils/constants';
import { getFieldType } from './utils/subgraph';
const TEMPLATE_FILE = './templates/indexer-template.handlebars';
export class Indexer {
_queries: Array<any>;
_events: Array<any>;
_subgraphEntities: Array<any>;
_templateString: string;
constructor () {
this._queries = [];
this._events = [];
this._subgraphEntities = [];
this._templateString = fs.readFileSync(path.resolve(__dirname, TEMPLATE_FILE)).toString();
}
@ -99,6 +102,70 @@ export class Indexer {
this._events.push(eventObject);
}
addSubgraphEntities (subgraphSchemaDocument: any): void {
// Add subgraph entities for creating the relations and entity types maps in the indexer.
const subgraphTypeDefs = subgraphSchemaDocument.definitions;
subgraphTypeDefs.forEach((def: any) => {
if (def.kind !== 'ObjectTypeDefinition') {
return;
}
let entityObject: any = {
className: def.name.value,
columns: [],
relations: []
};
entityObject = this._addSubgraphColumns(subgraphTypeDefs, entityObject, def);
this._subgraphEntities.push(entityObject);
});
}
_addSubgraphColumns (subgraphTypeDefs: any, entityObject: any, def: any): any {
// Process each field of the entity type def.
def.fields.forEach((field: any) => {
const columnObject: any = {
name: field.name.value,
type: '',
isRelation: false,
isArray: false
};
// Process field properties.
const { typeName, array } = getFieldType(field.type);
columnObject.type = typeName;
columnObject.isArray = array;
// Add a relation if the type is a object type available in subgraph type defs.
const columnType = subgraphTypeDefs.find((def: any) => {
return def.name.value === typeName && def.kind === 'ObjectTypeDefinition';
});
if (columnType) {
columnObject.isRelation = true;
// Process the derivedFrom directive for the relation field.
const { isDerived, derivedFromField } = this._getDerivedFrom(field.directives);
if (isDerived) {
columnObject.isDerived = true;
columnObject.derivedFromField = derivedFromField;
} else {
columnObject.isDerived = false;
}
entityObject.relations.push(columnObject);
}
entityObject.columns.push(columnObject);
});
return entityObject;
}
/**
* Writes the indexer file generated from a template to a stream.
* @param outStream A writable output stream to write the indexer file to.
@ -110,6 +177,7 @@ export class Indexer {
const obj = {
inputFileNames,
queries: this._queries,
subgraphEntities: this._subgraphEntities,
constants: {
MODE_ETH_CALL,
MODE_STORAGE
@ -120,4 +188,21 @@ export class Indexer {
const indexer = template(obj);
outStream.write(indexer);
}
_getDerivedFrom (directives: any): { isDerived: boolean, derivedFromField: string } {
const derivedFromDirective = directives.find((directive: any) => {
return directive.name.value === 'derivedFrom';
});
let isDerived = false;
let derivedFromField = '';
// Get the derivedFrom field name if derivedFrom directive is present.
if (derivedFromDirective) {
isDerived = true;
derivedFromField = derivedFromDirective.arguments[0].value.value;
}
return { isDerived, derivedFromField };
}
}

View File

@ -42,6 +42,10 @@ import { HookStatus } from './entity/HookStatus';
import { BlockProgress } from './entity/BlockProgress';
import { IPLDBlock } from './entity/IPLDBlock';
{{#each subgraphEntities as | subgraphEntity |}}
import { {{subgraphEntity.className}} } from './entity/{{subgraphEntity.className}}';
{{/each}}
const log = debug('vulcanize:indexer');
{{#each events as | event |}}
@ -129,7 +133,10 @@ export class Indexer implements IndexerInterface {
this._contract = new ethers.utils.Interface(this._abi);
this._entityTypesMap = new Map();
this._populateEntityTypesMap();
this._relationsMap = new Map();
this._populateRelationsMap();
}
async init (): Promise<void> {
@ -563,6 +570,48 @@ export class Indexer implements IndexerInterface {
return this._baseIndexer.getAncestorAtDepth(blockHash, depth);
}
getEntityTypesMap (): Map<string, { [key: string]: string }> {
return this._entityTypesMap;
}
_populateEntityTypesMap (): void {
{{#each subgraphEntities as | subgraphEntity |}}
this._entityTypesMap.set('{{subgraphEntity.className}}', {
{{#each subgraphEntity.columns as | column |}}
{{#unless column.isDerived}}
{{~#unless @first}},
{{/unless}}
{{column.name}}: '{{column.type}}'
{{~/unless}}
{{/each}}
});
{{/each}}
}
_populateRelationsMap (): void {
{{#each subgraphEntities as | subgraphEntity |}}
{{#if subgraphEntity.relations}}
this._relationsMap.set({{subgraphEntity.className}}, {
{{#each subgraphEntity.relations as | relation |}}
{{~#unless @first}},
{{/unless}}
{{relation.name}}: {
entity: {{relation.type}},
isArray: {{relation.isArray}},
isDerived: {{relation.isDerived}}
{{~#if relation.isDerived}},
field: '{{relation.derivedFromField}}'
{{~/if}}
}
{{~/each}}
});
{{/if}}
{{/each}}
}
async _fetchAndSaveEvents ({ cid: blockCid, blockHash }: DeepPartial<BlockProgress>): Promise<BlockProgress> {
assert(blockHash);

View File

@ -20,9 +20,6 @@ export function parseSubgraphSchema (subgraphPath: string): any {
const subgraphTypeDefs = subgraphSchemaDocument.definitions;
subgraphTypeDefs.forEach((def: any) => {
// Remove type directives.
def.directives = [];
if (def.kind === 'ObjectTypeDefinition') {
def.fields.forEach((field: any) => {
// Parse the field type.
@ -37,6 +34,21 @@ export function parseSubgraphSchema (subgraphPath: string): any {
return subgraphSchemaDocument;
}
export function getFieldType (typeNode: any): { typeName: string, array: boolean, nullable: boolean } {
if (typeNode.kind === 'ListType') {
return { typeName: getFieldType(typeNode.type).typeName, array: true, nullable: true };
}
if (typeNode.kind === 'NonNullType') {
const fieldType = getFieldType(typeNode.type);
return { typeName: fieldType.typeName, array: fieldType.array, nullable: false };
}
// If 'NamedType'.
return { typeName: typeNode.name.value, array: false, nullable: true };
}
function parseType (typeNode: any): any {
// Check if 'NamedType' is reached.
if (typeNode.kind === 'NamedType') {

View File

@ -15,6 +15,7 @@ _solToTs.set('uint16', 'number');
_solToTs.set('uint64', 'bigint');
_solToTs.set('uint128', 'bigint');
_solToTs.set('uint256', 'bigint');
_solToTs.set('uint', 'bigint');
_solToTs.set('address', 'string');
_solToTs.set('bool', 'boolean');
_solToTs.set('bytes', 'string');

View File

@ -76,6 +76,7 @@ export class Visitor {
stateVariableDeclarationVisitor (node: any): void {
// TODO Handle multiples variables in a single line.
// TODO Handle array types.
// TODO Handle user defined type .
const variable = node.variables[0];
const name: string = variable.name;
const stateVariableType: string = variable.typeName.type;
@ -83,13 +84,6 @@ export class Visitor {
const params: Param[] = [];
let typeName = variable.typeName;
// TODO Handle user defined type.
if (typeName.type === 'UserDefinedTypeName') {
// Skip in case of UserDefinedTypeName.
return;
}
let numParams = 0;
// If the variable type is mapping, extract key as a param:
@ -100,6 +94,11 @@ export class Visitor {
numParams++;
}
if (['UserDefinedTypeName', 'ArrayTypeName'].includes(typeName.type)) {
// Skip in case of UserDefinedTypeName | ArrayTypeName.
return;
}
const returnType = typeName.name;
this._schema.addQuery(name, params, returnType);
@ -138,6 +137,7 @@ export class Visitor {
this._entity.addSubgraphEntities(subgraphSchemaDocument);
this._resolvers.addSubgraphResolvers(subgraphSchemaDocument);
this._reset.addSubgraphEntities(subgraphSchemaDocument);
this._indexer.addSubgraphEntities(subgraphSchemaDocument);
}
/**