Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support DB caching for multiple and array return types in eth_call queries #372

Merged
merged 8 commits into from
Apr 27, 2023
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