Implement filters for uni-info-watcher resolvers. (#180)

Co-authored-by: nabarun <nabarun@deepstacksoft.com>
This commit is contained in:
Ashwin Phatak 2021-08-03 11:56:25 +05:30 committed by GitHub
parent d9366017da
commit eea3a5864b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 147 additions and 45 deletions

View File

@ -28,6 +28,23 @@ import { SyncStatus } from './entity/SyncStatus';
const DEFAULT_LIMIT = 100;
const DEFAULT_SKIP = 0;
const OPERATOR_MAP = {
equals: '=',
gt: '>',
lt: '<',
gte: '>=',
lte: '<=',
in: 'IN',
contains: 'LIKE',
starts: 'LIKE',
ends: 'LIKE'
};
export interface BlockHeight {
number?: number;
hash?: string;
}
export enum OrderDirection {
asc = 'asc',
desc = 'desc'
@ -40,6 +57,14 @@ export interface QueryOptions {
orderDirection?: OrderDirection;
}
interface Where {
[key: string]: {
value: any;
not: boolean;
operator: keyof typeof OPERATOR_MAP;
}
}
export class Database {
_config: ConnectionOptions
_conn!: Connection
@ -230,7 +255,8 @@ export class Database {
where: whereOptions,
order: {
blockNumber: 'DESC'
}
},
relations: ['pool']
};
let entity = await repo.findOne(findOptions as FindOneOptions<PoolDayData>);
@ -362,17 +388,16 @@ export class Database {
return entity;
}
async getEntities<Entity> (entity: new () => Entity, where: { [key: string]: any } = {}, queryOptions: QueryOptions = {}, relations: string[] = []): Promise<Entity[]> {
const { blockHash, blockNumber, ...filter } = where;
async getEntities<Entity> (entity: new () => Entity, block: BlockHeight, where: Where = {}, queryOptions: QueryOptions = {}, relations: string[] = []): Promise<Entity[]> {
const repo = this._conn.getRepository(entity);
const { tableName } = repo.metadata;
let subQuery = await repo.createQueryBuilder('subTable')
let subQuery = repo.createQueryBuilder('subTable')
.select('MAX(subTable.block_number)')
.where(`subTable.id = ${tableName}.id`);
if (blockHash) {
const { canonicalBlockNumber, blockHashes } = await this._getBranchInfo(blockHash);
if (block.hash) {
const { canonicalBlockNumber, blockHashes } = await this._getBranchInfo(block.hash);
subQuery = subQuery
.andWhere(new Brackets(qb => {
@ -381,8 +406,8 @@ export class Database {
}));
}
if (blockNumber) {
subQuery = subQuery.andWhere('subTable.block_number <= :blockNumber', { blockNumber });
if (block.number) {
subQuery = subQuery.andWhere('subTable.block_number <= :blockNumber', { blockNumber: block.number });
}
let selectQueryBuilder = repo.createQueryBuilder(tableName)
@ -393,8 +418,40 @@ export class Database {
selectQueryBuilder = selectQueryBuilder.leftJoinAndSelect(`${repo.metadata.tableName}.${relation}`, relation);
});
Object.entries(filter).forEach(([field, value]) => {
selectQueryBuilder = selectQueryBuilder.andWhere(`${tableName}.${field} = :value`, { value });
Object.entries(where).forEach(([field, filter]) => {
// Form the where clause.
const { not, operator, value } = filter;
const columnMetadata = repo.metadata.findColumnWithPropertyName(field);
assert(columnMetadata);
let whereClause = `${tableName}.${columnMetadata.propertyAliasName} `;
if (not) {
if (operator === 'equals') {
whereClause += '!';
} else {
whereClause += 'NOT ';
}
}
whereClause += `${OPERATOR_MAP[operator]} `;
if (['contains', 'starts'].some(el => el === operator)) {
whereClause += '%:';
} else if (operator === 'in') {
whereClause += '(:...';
} else {
whereClause += ':';
}
whereClause += 'value';
if (['contains', 'ends'].some(el => el === operator)) {
whereClause += '%';
} else if (operator === 'in') {
whereClause += ')';
}
selectQueryBuilder = selectQueryBuilder.andWhere(whereClause, { value });
});
const { limit = DEFAULT_LIMIT, orderBy, orderDirection, skip = DEFAULT_SKIP } = queryOptions;
@ -404,7 +461,9 @@ export class Database {
.limit(limit);
if (orderBy) {
selectQueryBuilder = selectQueryBuilder.orderBy(`${tableName}.${orderBy}`, orderDirection === 'desc' ? 'DESC' : 'ASC');
const columnMetadata = repo.metadata.findColumnWithPropertyName(orderBy);
assert(columnMetadata);
selectQueryBuilder = selectQueryBuilder.orderBy(`${tableName}.${columnMetadata.propertyAliasName}`, orderDirection === 'desc' ? 'DESC' : 'ASC');
}
return selectQueryBuilder.getMany();

View File

@ -14,7 +14,7 @@ import { convertTokenToDecimal, loadTransaction, safeDiv } from './utils';
import { createTick } from './utils/tick';
import Decimal from 'decimal.js';
import { Position } from './entity/Position';
import { Database, QueryOptions, OrderDirection } from './database';
import { Database, QueryOptions, OrderDirection, BlockHeight } from './database';
import { Event } from './entity/Event';
import { ResultEvent, Block, Transaction, PoolCreatedEvent, InitializeEvent, MintEvent, BurnEvent, SwapEvent, IncreaseLiquidityEvent, DecreaseLiquidityEvent, CollectEvent, TransferEvent } from './events';
import { Factory } from './entity/Factory';
@ -26,7 +26,6 @@ import { Swap } from './entity/Swap';
import { PositionSnapshot } from './entity/PositionSnapshot';
import { SyncStatus } from './entity/SyncStatus';
import { BlockProgress } from './entity/BlockProgress';
import { BlockHeight } from './resolvers';
const log = debug('vulcanize:indexer');
@ -37,7 +36,7 @@ export interface ValueResult {
}
}
export { OrderDirection };
export { OrderDirection, BlockHeight };
export class Indexer {
_db: Database
@ -203,8 +202,31 @@ export class Indexer {
return this._db.getToken({ id, blockHash: block.hash, blockNumber: block.number });
}
async getEntities<Entity> (entity: new () => Entity, where: Partial<Entity>, queryOptions: QueryOptions, relations?: string[]): Promise<Entity[]> {
const res = await this._db.getEntities(entity, where, queryOptions, relations);
async getEntities<Entity> (entity: new () => Entity, block: BlockHeight, where: { [key: string]: any } = {}, queryOptions: QueryOptions, relations?: string[]): Promise<Entity[]> {
where = Object.entries(where).reduce((acc: { [key: string]: any }, [fieldWithSuffix, value]) => {
const [field, ...suffix] = fieldWithSuffix.split('_');
acc[field] = {
value,
not: false,
operator: 'equals'
};
let operator = suffix.shift();
if (operator === 'not') {
acc[field].not = true;
operator = suffix.shift();
}
if (operator) {
acc[field].operator = operator;
}
return acc;
}, {});
const res = await this._db.getEntities(entity, block, where, queryOptions, relations);
return res;
}
@ -368,7 +390,7 @@ export class Indexer {
// TODO: In subgraph factory is fetched by hardcoded factory address.
// Currently fetching first factory in database as only one exists.
const [factory] = await this._db.getEntities(Factory, { blockHash: block.hash }, { limit: 1 });
const [factory] = await this._db.getEntities(Factory, { hash: block.hash }, {}, { limit: 1 });
const token0 = pool.token0;
const token1 = pool.token1;
@ -504,7 +526,7 @@ export class Indexer {
// TODO: In subgraph factory is fetched by hardcoded factory address.
// Currently fetching first factory in database as only one exists.
const [factory] = await this._db.getEntities(Factory, { blockHash: block.hash }, { limit: 1 });
const [factory] = await this._db.getEntities(Factory, { hash: block.hash }, {}, { limit: 1 });
const token0 = pool.token0;
const token1 = pool.token1;
@ -622,7 +644,7 @@ export class Indexer {
// TODO: In subgraph factory is fetched by hardcoded factory address.
// Currently fetching first factory in database as only one exists.
const [factory] = await this._db.getEntities(Factory, { blockHash: block.hash }, { limit: 1 });
const [factory] = await this._db.getEntities(Factory, { hash: block.hash }, {}, { limit: 1 });
const pool = await this._db.getPool({ id: contractAddress, blockHash: block.hash });
assert(pool);

View File

@ -2,7 +2,7 @@ import assert from 'assert';
import BigInt from 'apollo-type-bigint';
import debug from 'debug';
import { Indexer, OrderDirection } from './indexer';
import { Indexer, OrderDirection, BlockHeight } from './indexer';
import { Burn } from './entity/Burn';
import { Bundle } from './entity/Bundle';
import { Factory } from './entity/Factory';
@ -12,13 +12,14 @@ import { Pool } from './entity/Pool';
import { Swap } from './entity/Swap';
import { Tick } from './entity/Tick';
import { Token } from './entity/Token';
import { TokenDayData } from './entity/TokenDayData';
import { TokenHourData } from './entity/TokenHourData';
import { Transaction } from './entity/Transaction';
import { UniswapDayData } from './entity/UniswapDayData';
const log = debug('vulcanize:resolver');
export interface BlockHeight {
number?: number;
hash?: string;
}
export { BlockHeight };
export const createResolvers = async (indexer: Indexer): Promise<any> => {
assert(indexer);
@ -36,25 +37,25 @@ export const createResolvers = async (indexer: Indexer): Promise<any> => {
bundles: async (_: any, { block = {}, first }: { first: number, block: BlockHeight }) => {
log('bundles', block, first);
return indexer.getEntities(Bundle, { blockHash: block.hash, blockNumber: block.number }, { limit: first });
return indexer.getEntities(Bundle, block, {}, { limit: first });
},
burns: async (_: any, { first, orderBy, orderDirection, where }: { first: number, orderBy: string, orderDirection: OrderDirection, where: Partial<Burn> }) => {
burns: async (_: any, { first, orderBy, orderDirection, where }: { first: number, orderBy: string, orderDirection: OrderDirection, where: { [key: string]: any } }) => {
log('burns', first, orderBy, orderDirection, where);
return indexer.getEntities(Burn, where, { limit: first, orderBy, orderDirection }, ['pool', 'transaction']);
return indexer.getEntities(Burn, {}, where, { limit: first, orderBy, orderDirection }, ['pool', 'transaction']);
},
factories: async (_: any, { block = {}, first }: { first: number, block: BlockHeight }) => {
log('factories', block, first);
return indexer.getEntities(Factory, { blockHash: block.hash, blockNumber: block.number }, { limit: first });
return indexer.getEntities(Factory, block, {}, { limit: first });
},
mints: async (_: any, { first, orderBy, orderDirection, where }: { first: number, orderBy: string, orderDirection: OrderDirection, where: Partial<Burn> }) => {
mints: async (_: any, { first, orderBy, orderDirection, where }: { first: number, orderBy: string, orderDirection: OrderDirection, where: { [key: string]: any } }) => {
log('burns', first, orderBy, orderDirection, where);
return indexer.getEntities(Mint, where, { limit: first, orderBy, orderDirection }, ['pool', 'transaction']);
return indexer.getEntities(Mint, {}, where, { limit: first, orderBy, orderDirection }, ['pool', 'transaction']);
},
pool: async (_: any, { id, block = {} }: { id: string, block: BlockHeight }) => {
@ -63,32 +64,28 @@ export const createResolvers = async (indexer: Indexer): Promise<any> => {
return indexer.getPool(id, block);
},
poolDayDatas: async (_: any, { first, skip, orderBy, orderDirection, where }: { first: number, skip: number, orderBy: string, orderDirection: OrderDirection, where: Partial<Burn> }) => {
poolDayDatas: async (_: any, { first, skip, orderBy, orderDirection, where }: { first: number, skip: number, orderBy: string, orderDirection: OrderDirection, where: { [key: string]: any } }) => {
log('poolDayDatas', first, skip, orderBy, orderDirection, where);
return indexer.getEntities(PoolDayData, where, { limit: first, skip, orderBy, orderDirection });
return indexer.getEntities(PoolDayData, {}, where, { limit: first, skip, orderBy, orderDirection });
},
pools: async (_: any, { block = {}, first, orderBy, orderDirection, where = {} }: { block: BlockHeight, first: number, orderBy: string, orderDirection: OrderDirection, where: Partial<Burn> }) => {
pools: async (_: any, { block = {}, first, orderBy, orderDirection, where = {} }: { block: BlockHeight, first: number, orderBy: string, orderDirection: OrderDirection, where: { [key: string]: any } }) => {
log('burns', block, first, orderBy, orderDirection, where);
where.blockHash = block.hash;
where.blockNumber = block.number;
return indexer.getEntities(Pool, where, { limit: first, orderBy, orderDirection }, ['token0', 'token1']);
return indexer.getEntities(Pool, block, where, { limit: first, orderBy, orderDirection }, ['token0', 'token1']);
},
swaps: async (_: any, { first, orderBy, orderDirection, where }: { first: number, orderBy: string, orderDirection: OrderDirection, where: Partial<Burn> }) => {
swaps: async (_: any, { first, orderBy, orderDirection, where }: { first: number, orderBy: string, orderDirection: OrderDirection, where: { [key: string]: any } }) => {
log('swaps', first, orderBy, orderDirection, where);
return indexer.getEntities(Swap, where, { limit: first, orderBy, orderDirection }, ['pool', 'transaction']);
return indexer.getEntities(Swap, {}, where, { limit: first, orderBy, orderDirection }, ['pool', 'transaction']);
},
ticks: async (_: any, { block = {}, first, skip, where = {} }: { block: BlockHeight, first: number, skip: number, where: Partial<Tick> }) => {
ticks: async (_: any, { block = {}, first, skip, where = {} }: { block: BlockHeight, first: number, skip: number, where: { [key: string]: any } }) => {
log('ticks', block, first, skip, where);
where.blockHash = block.hash;
where.blockNumber = block.number;
return indexer.getEntities(Tick, where, { limit: first, skip });
return indexer.getEntities(Tick, block, where, { limit: first, skip });
},
token: async (_: any, { id, block = {} }: { id: string, block: BlockHeight }) => {
@ -97,10 +94,34 @@ export const createResolvers = async (indexer: Indexer): Promise<any> => {
return indexer.getToken(id, block);
},
tokens: async (_: any, { orderBy, orderDirection, where }: { orderBy: string, orderDirection: OrderDirection, where: Partial<Token> }) => {
tokens: async (_: any, { orderBy, orderDirection, where }: { orderBy: string, orderDirection: OrderDirection, where: { [key: string]: any } }) => {
log('tokens', orderBy, orderDirection, where);
return indexer.getEntities(Token, where, { orderBy, orderDirection });
return indexer.getEntities(Token, {}, where, { orderBy, orderDirection });
},
tokenDayDatas: async (_: any, { first, skip, orderBy, orderDirection, where }: { first: number, skip: number, orderBy: string, orderDirection: OrderDirection, where: { [key: string]: any } }) => {
log('tokenDayDatas', first, skip, orderBy, orderDirection, where);
return indexer.getEntities(TokenDayData, {}, where, { limit: first, skip, orderBy, orderDirection });
},
tokenHourDatas: async (_: any, { first, skip, orderBy, orderDirection, where }: { first: number, skip: number, orderBy: string, orderDirection: OrderDirection, where: { [key: string]: any } }) => {
log('tokenDayDatas', first, skip, orderBy, orderDirection, where);
return indexer.getEntities(TokenHourData, {}, where, { limit: first, skip, orderBy, orderDirection });
},
transactions: async (_: any, { first, orderBy, orderDirection }: { first: number, orderBy: string, orderDirection: OrderDirection}) => {
log('transactions', first, orderBy, orderDirection);
return indexer.getEntities(Transaction, {}, {}, { limit: first, orderBy, orderDirection }, ['burns', 'mints', 'swaps']);
},
uniswapDayDatas: async (_: any, { first, skip, orderBy, orderDirection, where }: { first: number, skip: number, orderBy: string, orderDirection: OrderDirection, where: { [key: string]: any } }) => {
log('uniswapDayDatas', first, skip, orderBy, orderDirection, where);
return indexer.getEntities(UniswapDayData, {}, where, { limit: first, skip, orderBy, orderDirection });
}
}
};

View File

@ -21,7 +21,7 @@ export const updateUniswapDayData = async (db: Database, event: { contractAddres
// TODO: In subgraph factory is fetched by hardcoded factory address.
// Currently fetching first factory in database as only one exists.
const [factory] = await db.getEntities(Factory, { blockHash: block.hash }, { limit: 1 });
const [factory] = await db.getEntities(Factory, { hash: block.hash }, {}, { limit: 1 });
const dayID = Math.floor(block.timestamp / 86400); // Rounded.
const dayStartTimestamp = dayID * 86400;