Skip to content

Commit

Permalink
Support DB caching for multiple and array return types in eth_call
Browse files Browse the repository at this point in the history
…queries (#372)

* Generate indexer and entities with DB caching for multiple return types

* Generate database file code for multiple return types

* Remove returnType from client codegen as not required

* Changes for fixing lint warnings

* Handle mapping type values in storage mode

* Generate entities for array return type queries

* Refactor visitor for DB caching of array return types

* Fix saving bigint array type in DB
  • Loading branch information
nikugogoi committed Apr 27, 2023
1 parent 26c1607 commit f2595d7
Show file tree
Hide file tree
Showing 7 changed files with 82 additions and 94 deletions.
16 changes: 2 additions & 14 deletions packages/codegen/src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import { gqlGenerate } from 'gql-generator';

import { getGqlForSol, getTsForGql } from './utils/type-mappings';
import { Param } from './utils/types';
import { getBaseType } from './utils/helpers';

const TEMPLATE_FILE = './templates/client-template.handlebars';

Expand All @@ -29,22 +28,17 @@ export class Client {
* Stores the query to be passed to the template.
* @param name Name of the query.
* @param params Parameters to the query.
* @param returnType Return type for the query.
*/
addQuery (name: string, params: Array<Param>, typeName: any): void {
addQuery (name: string, params: Array<Param>): void {
// Check if the query is already added.
if (this._queries.some(query => query.name === name)) {
return;
}

const returnType = getBaseType(typeName);
assert(returnType);

const queryObject = {
name,
getQueryName: '',
params: _.cloneDeep(params),
returnType
params: _.cloneDeep(params)
};

queryObject.getQueryName = (name.charAt(0) === '_')
Expand All @@ -60,12 +54,6 @@ export class Client {
return param;
});

const gqlReturnType = getGqlForSol(returnType);
assert(gqlReturnType);
const tsReturnType = getTsForGql(gqlReturnType);
assert(tsReturnType);
queryObject.returnType = tsReturnType;

this._queries.push(queryObject);
}

Expand Down
16 changes: 4 additions & 12 deletions packages/codegen/src/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ import Handlebars from 'handlebars';
import { Writable } from 'stream';
import _ from 'lodash';

import { VariableDeclaration } from '@solidity-parser/parser/dist/src/ast-types';

import { getGqlForSol, getTsForGql } from './utils/type-mappings';
import { Param } from './utils/types';
import { getBaseType } from './utils/helpers';

const TEMPLATE_FILE = './templates/database-template.handlebars';

Expand All @@ -32,22 +33,19 @@ export class Database {
* @param params Parameters to the query.
* @param returnType Return type for the query.
*/
addQuery (name: string, params: Array<Param>, typeName: any): void {
addQuery (name: string, params: Array<Param>, returnParameters: VariableDeclaration[]): void {
// Check if the query is already added.
if (this._queries.some(query => query.name === name)) {
return;
}

const returnType = getBaseType(typeName);
assert(returnType);

const queryObject = {
name,
entityName: '',
getQueryName: '',
saveQueryName: '',
params: _.cloneDeep(params),
returnType
returnParameters
};

// eth_call mode: Capitalize first letter of entity name (balanceOf -> BalanceOf, getBalanceOf, saveBalanceOf).
Expand All @@ -73,12 +71,6 @@ export class Database {
return param;
});

const gqlReturnType = getGqlForSol(returnType);
assert(gqlReturnType);
const tsReturnType = getTsForGql(gqlReturnType);
assert(tsReturnType);

queryObject.returnType = tsReturnType;
this._queries.push(queryObject);
}

Expand Down
59 changes: 42 additions & 17 deletions packages/codegen/src/entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@ import yaml from 'js-yaml';
import Handlebars from 'handlebars';
import { Writable } from 'stream';

import { VariableDeclaration } from '@solidity-parser/parser/dist/src/ast-types';

import { getPgForTs, getTsForGql, getGqlForSol } from './utils/type-mappings';
import { Param } from './utils/types';
import { getFieldType } from './utils/subgraph';
import { getBaseType } from './utils/helpers';
import { getBaseType, isArrayType } from './utils/helpers';

const TEMPLATE_FILE = './templates/entity-template.handlebars';
const TABLES_DIR = './data/entities';
Expand All @@ -32,7 +34,7 @@ export class Entity {
* @param params Parameters to the query.
* @param returnType Return type for the query.
*/
addQuery (name: string, params: Array<Param>, typeName: any): void {
addQuery (name: string, params: Array<Param>, returnParameters: VariableDeclaration[]): void {
// Check if the query is already added.
if (this._entities.some(entity => entity.className.toLowerCase() === name.toLowerCase())) {
return;
Expand Down Expand Up @@ -138,23 +140,46 @@ export class Entity {
})
);

const baseType = getBaseType(typeName);
assert(baseType);
entityObject.columns = entityObject.columns.concat(
returnParameters.map((returnParameter, index) => {
let typeName = returnParameter.typeName;
assert(typeName);

const gqlReturnType = getGqlForSol(baseType);
assert(gqlReturnType);
const tsReturnType = getTsForGql(gqlReturnType);
assert(tsReturnType);
const pgReturnType = getPgForTs(tsReturnType);
assert(pgReturnType);
// Handle Mapping type for state variable queries
while (typeName.type === 'Mapping') {
typeName = typeName.valueType;
}

entityObject.columns.push({
name: 'value',
pgType: pgReturnType,
tsType: tsReturnType,
columnType: 'Column',
columnOptions: []
});
const baseType = getBaseType(typeName);
assert(baseType);
const gqlReturnType = getGqlForSol(baseType);
assert(gqlReturnType);
let tsReturnType = getTsForGql(gqlReturnType);
assert(tsReturnType);
const pgReturnType = getPgForTs(tsReturnType);
assert(pgReturnType);

const columnOptions = [];
const isArray = isArrayType(typeName);

if (isArray) {
tsReturnType = tsReturnType.concat('[]');

columnOptions.push({
option: 'array',
value: true
});
}

return {
name: returnParameters.length > 1 ? `value${index}` : 'value',
pgType: pgReturnType,
tsType: tsReturnType,
columnType: 'Column',
columnOptions
};
})
);

entityObject.columns.push({
name: 'proof',
Expand Down
7 changes: 1 addition & 6 deletions packages/codegen/src/indexer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,6 @@ export class Indexer {
return;
}

// Disable DB caching if more than 1 return params.
let disableCaching = returnParameters.length > 1;

const returnTypes = returnParameters.map(returnParameter => {
let typeName = returnParameter.typeName;
assert(typeName);
Expand All @@ -78,7 +75,6 @@ export class Indexer {

const isArray = isArrayType(typeName);
if (isArray) {
disableCaching = true;
tsReturnType = tsReturnType.concat('[]');
}

Expand All @@ -94,8 +90,7 @@ export class Indexer {
returnTypes,
mode,
stateVariableType,
contract,
disableCaching
contract
};

if (name.charAt(0) === '_') {
Expand Down
6 changes: 2 additions & 4 deletions packages/codegen/src/templates/database-template.handlebars
Original file line number Diff line number Diff line change
Expand Up @@ -105,11 +105,9 @@ export class Database implements DatabaseInterface {
{{#if (reservedNameCheck query.entityName) }}
// eslint-disable-next-line @typescript-eslint/ban-types
{{/if}}
async {{query.saveQueryName}} ({ blockHash, blockNumber, contractAddress
{{~#each query.params}}, {{this.name~}} {{/each}}, value, proof }: DeepPartial<{{query.entityName}}>): Promise<{{query.entityName}}> {
async {{query.saveQueryName}} ({ blockHash, blockNumber, contractAddress,{{#each query.params}} {{this.name~}},{{/each}}{{#each query.returnParameters}}{{~#if (compare query.returnParameters.length 1 operator=">")}} value{{@index}},{{else}} value,{{/if}}{{/each}} proof }: DeepPartial<{{query.entityName}}>): Promise<{{query.entityName}}> {
const repo = this._conn.getRepository({{query.entityName}});
const entity = repo.create({ blockHash, blockNumber, contractAddress
{{~#each query.params}}, {{this.name~}} {{/each}}, value, proof });
const entity = repo.create({ blockHash, blockNumber, contractAddress,{{#each query.params}} {{this.name~}},{{/each}}{{#each query.returnParameters}}{{~#if (compare query.returnParameters.length 1 operator=">")}} value{{@index}},{{else}} value,{{/if}}{{/each}} proof });
return repo.save(entity);
}

Expand Down
15 changes: 9 additions & 6 deletions packages/codegen/src/templates/indexer-template.handlebars
Original file line number Diff line number Diff line change
Expand Up @@ -178,22 +178,28 @@ export class Indexer implements IndexerInterface {
{{else~}}
): Promise<ValueResult> {
{{/if}}
{{#unless query.disableCaching}}
const entity = await this._db.{{query.getQueryName}}({ blockHash, contractAddress
{{~#each query.params}}, {{this.name~}} {{~/each}} });
if (entity) {
log('{{query.name}}: db hit.');

return {
{{#if (compare query.returnTypes.length 1 operator=">")}}
value: {
{{#each query.returnTypes}}
value{{@index}}: entity.value{{@index}}{{#unless @last}},{{/unless}}
{{/each}}
},
{{else}}
value: entity.value,
{{/if}}
proof: JSON.parse(entity.proof)
};
}

const { block: { number } } = await this._ethClient.getBlockByHash(blockHash);
const blockNumber = ethers.BigNumber.from(number).toNumber();

{{/unless}}
log('{{query.name}}: db miss, fetching from upstream server');

{{#if (compare query.mode @root.constants.MODE_ETH_CALL)}}
Expand Down Expand Up @@ -248,11 +254,8 @@ export class Indexer implements IndexerInterface {
);
{{/if}}

{{#unless disableCaching}}
await this._db.{{query.saveQueryName}}({ blockHash, blockNumber, contractAddress
{{~#each query.params}}, {{this.name~}} {{/each}}, value: result.value, proof: JSONbigNative.stringify(result.proof) });
await this._db.{{query.saveQueryName}}({ blockHash, blockNumber, contractAddress,{{~#each query.params}} {{this.name~}},{{/each}}{{#each query.returnTypes}}{{~#if (compare query.returnTypes.length 1 operator=">")}} value{{@index}}: value.value{{@index}},{{else}} value: result.value,{{/if}}{{/each}} proof: JSONbigNative.stringify(result.proof) });

{{/unless}}
{{#if query.stateVariableType}}
{{#if (compare query.stateVariableType 'Mapping')}}
if (diff) {
Expand Down
57 changes: 22 additions & 35 deletions packages/codegen/src/visitor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,42 +69,30 @@ export class Visitor {
return { name: item.name, type: item.typeName.name };
});

let errorMessage = '';

const typeName = node.returnParameters[0].typeName;
assert(typeName);

switch (typeName.type) {
case 'ElementaryTypeName':
this._entity.addQuery(name, params, typeName);
this._database.addQuery(name, params, typeName);
this._client.addQuery(name, params, typeName);
// falls through

case 'ArrayTypeName':
this._schema.addQuery(name, params, node.returnParameters);
this._resolvers.addQuery(name, params);

assert(this._contract);
this._indexer.addQuery(this._contract.name, MODE_ETH_CALL, name, params, node.returnParameters);
break;
// Check for unhandled return type params
node.returnParameters.forEach(returnParameter => {
assert(returnParameter.typeName);
const isTypeHandled = ['ElementaryTypeName', 'ArrayTypeName'].includes(returnParameter.typeName.type);

case 'UserDefinedTypeName':
errorMessage = `No support in codegen for user defined return type from method "${node.name}"`;
break;
if (!isTypeHandled) {
const errorMessage = `No support in codegen for type ${returnParameter.typeName.type} from method "${node.name}"`;

default:
errorMessage = `No support in codegen for return type "${typeName.type}" from method "${node.name}"`;
}
if (this._continueOnError) {
console.log(errorMessage);
return;
}

if (errorMessage !== '') {
if (this._continueOnError) {
console.log(errorMessage);
return;
throw new Error(errorMessage);
}
});

throw new Error(errorMessage);
}
this._schema.addQuery(name, params, node.returnParameters);
this._resolvers.addQuery(name, params);
assert(this._contract);
this._indexer.addQuery(this._contract.name, MODE_ETH_CALL, name, params, node.returnParameters);
this._entity.addQuery(name, params, node.returnParameters);
this._database.addQuery(name, params, node.returnParameters);
this._client.addQuery(name, params);
}

/**
Expand Down Expand Up @@ -149,12 +137,11 @@ export class Visitor {
case 'ElementaryTypeName': {
this._schema.addQuery(name, params, [variable]);
this._resolvers.addQuery(name, params);
this._entity.addQuery(name, params, typeName);
this._database.addQuery(name, params, typeName);
this._client.addQuery(name, params, typeName);

assert(this._contract);
this._indexer.addQuery(this._contract.name, MODE_STORAGE, name, params, [variable], stateVariableType);
this._entity.addQuery(name, params, [variable]);
this._database.addQuery(name, params, [variable]);
this._client.addQuery(name, params);

break;
}
Expand Down

0 comments on commit f2595d7

Please sign in to comment.