mirror of
https://github.com/cerc-io/watcher-ts
synced 2025-02-02 16:22:50 +00:00
Use lateral query for time travel queries using latest entity tables (#233)
* Avoid joins in id only queries * Use lateral query for time travel queries using latest entity tables
This commit is contained in:
parent
a084b4e40c
commit
74741184ee
@ -378,14 +378,25 @@ export class Database {
|
|||||||
const latestEntity = this._entityToLatestEntityMap.get(entity);
|
const latestEntity = this._entityToLatestEntityMap.get(entity);
|
||||||
|
|
||||||
if (latestEntity) {
|
if (latestEntity) {
|
||||||
if (!Object.keys(block).length) {
|
if (Object.keys(block).length) {
|
||||||
|
// Use lateral query for entities with latest entity table.
|
||||||
|
entities = await this.getEntitiesLateral(
|
||||||
|
queryRunner,
|
||||||
|
entity,
|
||||||
|
latestEntity,
|
||||||
|
block,
|
||||||
|
where,
|
||||||
|
queryOptions
|
||||||
|
);
|
||||||
|
} else {
|
||||||
// Use latest entity tables if block height not passed.
|
// Use latest entity tables if block height not passed.
|
||||||
entities = await this.getEntitiesLatest(
|
entities = await this.getEntitiesLatest(
|
||||||
queryRunner,
|
queryRunner,
|
||||||
entity,
|
entity,
|
||||||
latestEntity,
|
latestEntity,
|
||||||
where,
|
where,
|
||||||
queryOptions
|
queryOptions,
|
||||||
|
selections
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -831,17 +842,35 @@ export class Database {
|
|||||||
entity: new () => Entity,
|
entity: new () => Entity,
|
||||||
latestEntity: new () => any,
|
latestEntity: new () => any,
|
||||||
where: Where = {},
|
where: Where = {},
|
||||||
queryOptions: QueryOptions = {}
|
queryOptions: QueryOptions = {},
|
||||||
|
selections: ReadonlyArray<SelectionNode> = []
|
||||||
): Promise<Entity[]> {
|
): Promise<Entity[]> {
|
||||||
const repo = queryRunner.manager.getRepository(entity);
|
const entityRepo = queryRunner.manager.getRepository(entity);
|
||||||
const { tableName } = repo.metadata;
|
const latestEntityRepo = queryRunner.manager.getRepository(latestEntity);
|
||||||
|
const latestEntityFields = latestEntityRepo.metadata.columns.map(column => column.propertyName);
|
||||||
|
|
||||||
let selectQueryBuilder = repo.createQueryBuilder(tableName)
|
const selectionNotInLatestEntity = selections.filter(selection => selection.kind === 'Field' && selection.name.value !== '__typename')
|
||||||
|
.some(selection => {
|
||||||
|
assert(selection.kind === 'Field');
|
||||||
|
|
||||||
|
return !latestEntityFields.includes(selection.name.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Use latest entity table for faster query.
|
||||||
|
let repo = latestEntityRepo;
|
||||||
|
let selectQueryBuilder = repo.createQueryBuilder('latest');
|
||||||
|
|
||||||
|
if (selectionNotInLatestEntity) {
|
||||||
|
// Join with latest entity table if selection field doesn't exist in latest entity.
|
||||||
|
repo = entityRepo;
|
||||||
|
|
||||||
|
selectQueryBuilder = repo.createQueryBuilder(repo.metadata.tableName)
|
||||||
.innerJoin(
|
.innerJoin(
|
||||||
latestEntity,
|
latestEntity,
|
||||||
'latest',
|
'latest',
|
||||||
`latest.id = ${tableName}.id AND latest.blockHash = ${tableName}.blockHash`
|
`latest.id = ${repo.metadata.tableName}.id AND latest.blockHash = ${repo.metadata.tableName}.blockHash`
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
|
||||||
selectQueryBuilder = this._baseDatabase.buildQuery(repo, selectQueryBuilder, where, 'latest');
|
selectQueryBuilder = this._baseDatabase.buildQuery(repo, selectQueryBuilder, where, 'latest');
|
||||||
|
|
||||||
@ -862,6 +891,70 @@ export class Database {
|
|||||||
return selectQueryBuilder.getMany();
|
return selectQueryBuilder.getMany();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getEntitiesLateral<Entity> (
|
||||||
|
queryRunner: QueryRunner,
|
||||||
|
entity: new () => Entity,
|
||||||
|
latestEntity: new () => any,
|
||||||
|
block: BlockHeight,
|
||||||
|
where: Where = {},
|
||||||
|
queryOptions: QueryOptions = {}
|
||||||
|
): Promise<Entity[]> {
|
||||||
|
const entityRepo = queryRunner.manager.getRepository(entity);
|
||||||
|
const latestEntityRepo = queryRunner.manager.getRepository(latestEntity);
|
||||||
|
|
||||||
|
let subQuery = entityRepo.createQueryBuilder('subTable')
|
||||||
|
.where('latest.id = subTable.id')
|
||||||
|
.orderBy('subTable.block_number', 'DESC')
|
||||||
|
.limit(1);
|
||||||
|
|
||||||
|
if (block.hash) {
|
||||||
|
const { canonicalBlockNumber, blockHashes } = await this._baseDatabase.getFrothyRegion(queryRunner, block.hash);
|
||||||
|
|
||||||
|
subQuery = subQuery
|
||||||
|
.andWhere(new Brackets(qb => {
|
||||||
|
qb.where('subTable.block_hash IN (:...blockHashes)', { blockHashes })
|
||||||
|
.orWhere('subTable.block_number <= :canonicalBlockNumber', { canonicalBlockNumber });
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (block.number) {
|
||||||
|
subQuery = subQuery.andWhere('subTable.block_number <= :blockNumber', { blockNumber: block.number });
|
||||||
|
}
|
||||||
|
|
||||||
|
let selectQueryBuilder = latestEntityRepo.createQueryBuilder('latest')
|
||||||
|
.select('*')
|
||||||
|
.from(
|
||||||
|
qb => {
|
||||||
|
// https://stackoverflow.com/a/72026555/10026807
|
||||||
|
qb.getQuery = () => `LATERAL (${subQuery.getQuery()})`;
|
||||||
|
qb.setParameters(subQuery.getParameters());
|
||||||
|
return qb;
|
||||||
|
},
|
||||||
|
'result'
|
||||||
|
);
|
||||||
|
|
||||||
|
selectQueryBuilder = this._baseDatabase.buildQuery(latestEntityRepo, selectQueryBuilder, where, 'latest');
|
||||||
|
|
||||||
|
if (queryOptions.orderBy) {
|
||||||
|
selectQueryBuilder = this._baseDatabase.orderQuery(latestEntityRepo, selectQueryBuilder, queryOptions, '', 'latest');
|
||||||
|
}
|
||||||
|
|
||||||
|
selectQueryBuilder = this._baseDatabase.orderQuery(latestEntityRepo, selectQueryBuilder, { ...queryOptions, orderBy: 'id' }, '', 'latest');
|
||||||
|
|
||||||
|
if (queryOptions.skip) {
|
||||||
|
selectQueryBuilder = selectQueryBuilder.offset(queryOptions.skip);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (queryOptions.limit) {
|
||||||
|
selectQueryBuilder = selectQueryBuilder.limit(queryOptions.limit);
|
||||||
|
}
|
||||||
|
|
||||||
|
let entities = await selectQueryBuilder.getRawMany();
|
||||||
|
entities = await this.transformResults(queryRunner, entityRepo.createQueryBuilder('subTable'), entities);
|
||||||
|
|
||||||
|
return entities as Entity[];
|
||||||
|
}
|
||||||
|
|
||||||
async loadEntitiesRelations<Entity> (
|
async loadEntitiesRelations<Entity> (
|
||||||
queryRunner: QueryRunner,
|
queryRunner: QueryRunner,
|
||||||
block: BlockHeight,
|
block: BlockHeight,
|
||||||
|
Loading…
Reference in New Issue
Block a user