Skip to content

Commit 8ed669e

Browse files
Park JuhyungPark Juhyung
authored andcommitted
Make snapshot query paginable
1 parent 2aa3aa7 commit 8ed669e

File tree

3 files changed

+146
-41
lines changed

3 files changed

+146
-41
lines changed

src/models/logic/utxo.ts

Lines changed: 114 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import { H160, H256, U64 } from "codechain-sdk/lib/core/classes";
2+
import * as _ from "lodash";
23
import * as Sequelize from "sequelize";
4+
import sequelize = require("sequelize");
35
import models from "..";
46
import * as Exception from "../../exception";
57
import { utxoPagination } from "../../routers/pagination";
@@ -447,48 +449,125 @@ export async function getByTxHashIndex(
447449
}
448450
}
449451

450-
export async function getSnapshot(assetType: H256, blockNumber: number) {
452+
export async function getSnapshot(params: {
453+
assetType: H256;
454+
blockNumber: number;
455+
lastEvaluatedKey?: number[] | null;
456+
}) {
457+
const { assetType, blockNumber, lastEvaluatedKey } = params;
458+
const transaction = await models.sequelize.transaction({
459+
isolationLevel:
460+
models.Sequelize.Transaction.ISOLATION_LEVELS.REPEATABLE_READ,
461+
deferrable: models.Sequelize.Deferrable.SET_DEFERRED
462+
});
451463
try {
452-
return models.UTXO.findAll({
453-
where: {
454-
assetType: strip0xPrefix(assetType.value),
455-
usedBlockNumber: {
456-
[Sequelize.Op.or]: [
457-
{ [Sequelize.Op.gt]: blockNumber },
458-
{ [Sequelize.Op.eq]: null }
459-
]
464+
const [
465+
fromBlockNumber,
466+
fromTransactionIndex,
467+
fromTransactionOutputIndex
468+
] = lastEvaluatedKey || [Number.MAX_SAFE_INTEGER, 0, 0];
469+
const itemsPerPage = 100;
470+
471+
const rows = await models.sequelize.query(
472+
`SELECT SUM("UTXO"."quantity") AS "totalAssetQuantity", "UTXO"."address", "UTXO"."assetType",
473+
COUNT("UTXO"."assetType") AS "utxoQuantity"
474+
FROM (SELECT * FROM "UTXOs" WHERE ("blockNumber", "transactionIndex", "transactionOutputIndex")<(:fromBlockNumber, :fromTransactionIndex, :fromTransactionOutputIndex)
475+
AND "assetType"=:assetType
476+
ORDER BY "blockNumber" DESC, "transactionIndex" DESC, "transactionOutputIndex" DESC
477+
LIMIT :itemsPerPage) "UTXO"
478+
WHERE ("UTXO"."usedBlockNumber" > :blockNumber OR "UTXO"."usedBlockNumber" IS NULL)
479+
AND "UTXO"."blockNumber" <= :blockNumber
480+
GROUP BY "UTXO"."address", "UTXO"."assetType"
481+
`,
482+
{
483+
replacements: {
484+
fromBlockNumber,
485+
fromTransactionIndex,
486+
fromTransactionOutputIndex,
487+
blockNumber,
488+
assetType: strip0xPrefix(assetType.toString()),
489+
itemsPerPage
460490
},
461-
blockNumber: {
462-
[Sequelize.Op.lte]: blockNumber
463-
}
464-
},
465-
attributes: [
466-
[
467-
Sequelize.fn("SUM", Sequelize.col("quantity")),
468-
"totalAssetQuantity"
469-
],
470-
"address",
471-
"assetType",
472-
[
473-
Sequelize.fn("COUNT", Sequelize.col("UTXO.assetType")),
474-
"utxoQuantity"
475-
]
476-
],
477-
order: Sequelize.literal(
478-
`"totalAssetQuantity" DESC, "assetType" DESC`
479-
),
480-
include: [
491+
raw: true,
492+
transaction,
493+
type: sequelize.QueryTypes.SELECT
494+
}
495+
);
496+
497+
// The SQL query is copied from the query above.
498+
const hasNextPage =
499+
(await models.sequelize.query(
500+
`SELECT COUNT(*) as count
501+
FROM (SELECT id FROM "UTXOs"
502+
WHERE ("blockNumber", "transactionIndex", "transactionOutputIndex")<(:fromBlockNumber, :fromTransactionIndex, :fromTransactionOutputIndex)
503+
AND "assetType"=:assetType
504+
ORDER BY "blockNumber" DESC, "transactionIndex" DESC, "transactionOutputIndex" DESC
505+
LIMIT :itemsPerPage) "UTXO"`,
481506
{
482-
as: "assetScheme",
483-
model: models.AssetScheme
507+
replacements: {
508+
fromBlockNumber,
509+
fromTransactionIndex,
510+
fromTransactionOutputIndex,
511+
assetType: strip0xPrefix(assetType.toString()),
512+
itemsPerPage: itemsPerPage + 1
513+
},
514+
plain: true,
515+
raw: true,
516+
transaction,
517+
type: sequelize.QueryTypes.SELECT
484518
}
485-
],
486-
group: ["UTXO.address", "UTXO.assetType", "assetScheme.assetType"]
487-
}).then(instances =>
488-
instances.map(instance => instance.get({ plain: true }))
519+
)).count ===
520+
itemsPerPage + 1;
521+
522+
// The SQL query is copied from the query above.
523+
const lastRow = await models.sequelize.query(
524+
`SELECT *
525+
FROM (SELECT * FROM "UTXOs" WHERE ("blockNumber", "transactionIndex", "transactionOutputIndex")<(:fromBlockNumber, :fromTransactionIndex, :fromTransactionOutputIndex)
526+
AND "assetType"=:assetType
527+
ORDER BY "blockNumber" DESC, "transactionIndex" DESC, "transactionOutputIndex" DESC
528+
LIMIT :itemsPerPage) "UTXO"
529+
ORDER BY "blockNumber" ASC, "transactionIndex" ASC, "transactionOutputIndex" ASC
530+
LIMIT 1
531+
`,
532+
{
533+
replacements: {
534+
fromBlockNumber,
535+
fromTransactionIndex,
536+
fromTransactionOutputIndex,
537+
assetType: strip0xPrefix(assetType.toString()),
538+
itemsPerPage
539+
},
540+
plain: true,
541+
raw: true,
542+
transaction,
543+
type: sequelize.QueryTypes.SELECT
544+
}
489545
);
546+
547+
const assetScheme = await AssetSchemeModel.getByAssetType(assetType, {
548+
transaction
549+
});
550+
transaction.commit();
551+
return {
552+
data: _.map(rows, (row: any) => {
553+
row.assetScheme =
554+
assetScheme && assetScheme.get({ plain: true });
555+
return row;
556+
}),
557+
hasNextPage,
558+
hasPreviousPage: null,
559+
firstEvaluatedKey: null,
560+
lastEvaluatedKey: lastRow
561+
? JSON.stringify([
562+
lastRow.blockNumber,
563+
lastRow.transactionIndex,
564+
lastRow.transactionOutputIndex
565+
])
566+
: null
567+
};
490568
} catch (err) {
491569
console.error(err);
570+
transaction.rollback();
492571
throw Exception.DBError();
493572
}
494573
}

src/routers/asset.ts

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
aggsUTXOPaginationSchema,
1717
assetTypeSchema,
1818
paginationSchema,
19+
snapshotPaginationSchema,
1920
snapshotSchema,
2021
utxoPaginationSchema,
2122
utxoSchema,
@@ -513,6 +514,11 @@ export function handle(context: IndexerContext, router: Router) {
513514
* in: query
514515
* required: true
515516
* type: string
517+
* - name: lastEvaluatedKey
518+
* description: the evaulated key of the last item in the previous page. It will be used for the pagination
519+
* in: query
520+
* required: false
521+
* type: string
516522
* responses:
517523
* 200:
518524
* description: snapshot, return null if the block does not exist yet
@@ -523,21 +529,32 @@ export function handle(context: IndexerContext, router: Router) {
523529
* type: integer
524530
* blockHash:
525531
* type: string
526-
* snapshot:
532+
* data:
527533
* type: array
528534
* items:
529535
* $ref: '#/definitions/UTXO'
536+
* hasNextPage:
537+
* type: string
538+
* hasPreviousPage:
539+
* type: string
540+
* firstEvaluatedKey:
541+
* type: string
542+
* lastEvaluatedKey:
543+
* type: string
530544
*/
531545
router.get(
532546
"/snapshot",
547+
parseEvaluatedKey,
533548
validate({
534549
query: {
535-
...snapshotSchema
550+
...snapshotSchema,
551+
...snapshotPaginationSchema
536552
}
537553
}),
538554
async (req, res, next) => {
539555
const assetTypeString = req.query.assetType;
540556
const date = req.query.date;
557+
const lastEvaluatedKey = req.query.lastEvaluatedKey;
541558
try {
542559
const assetType = new H160(assetTypeString);
543560
const snapshotTime = moment(date);
@@ -553,14 +570,15 @@ export function handle(context: IndexerContext, router: Router) {
553570
return;
554571
}
555572

556-
const snapshot = await UTXOModel.getSnapshot(
573+
const snapshot = await UTXOModel.getSnapshot({
557574
assetType,
558-
block.get("number")
559-
);
575+
blockNumber: block.get("number"),
576+
lastEvaluatedKey
577+
});
560578
res.json({
561579
blockHash: block.get("hash"),
562580
blockNumber: block.get("number"),
563-
snapshot
581+
...snapshot
564582
});
565583
} catch (e) {
566584
next(e);

src/routers/validator.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,14 @@ export const pendingTxPaginationSchema = {
103103
lastEvaluatedKey: Joi.array().items(Joi.number())
104104
};
105105

106+
export const snapshotPaginationSchema = {
107+
lastEvaluatedKey: Joi.array().items(
108+
Joi.number(),
109+
Joi.number(),
110+
Joi.number()
111+
)
112+
};
113+
106114
export const txSchema = {
107115
address,
108116
assetType: assetTypeSchema,

0 commit comments

Comments
 (0)