Skip to content

Commit

Permalink
Use price in cursor for offers
Browse files Browse the repository at this point in the history
  • Loading branch information
charlie-wasp committed Jun 4, 2019
1 parent 424f557 commit bfa45aa
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 12 deletions.
7 changes: 6 additions & 1 deletion src/orm/entities/offer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,12 @@ export class Offer {
@Column({ name: "lastmodified" })
lastModified: number;

public static parsePagingToken(token: string) {
const [id, price] = token.split("-");
return { id, price };
}

public get paging_token() {
return this.id;
return `${this.id}-${this.price}`;
}
}
4 changes: 3 additions & 1 deletion src/schema/resolvers/account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,9 @@ export default {
qb.andWhere("offers.buying = :buying", { buying: AssetTransformer.to(buying) });
}

return makeConnection<Offer>(await paginate(qb, paging, "offers.id"));
const offers = await paginate(qb, paging, "offers.id", Offer.parsePagingToken);

return makeConnection<Offer>(offers);
},
inflationDestination: resolvers.account
},
Expand Down
4 changes: 3 additions & 1 deletion src/schema/resolvers/offer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,9 @@ export default {
.setParameter("selling", AssetTransformer.to(selling))
.setParameter("buying", AssetTransformer.to(buying));

return makeConnection<Offer>(await paginate(qb, paging, "offers.id"));
const offers = await paginate(qb, paging, ["offers.price", "offers.id"], Offer.parsePagingToken);

return makeConnection<Offer>(offers);
},
tick: async (root: any, args: any, ctx: IApolloContext, info: any) => {
const repo = getCustomRepository(OfferRepository);
Expand Down
77 changes: 68 additions & 9 deletions src/util/paging.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,17 +46,76 @@ export function properlyOrdered(records: any[], pagingParams: PagingParams): any
export async function paginate(
queryBuilder: SelectQueryBuilder<any>,
pagingParams: PagingParams,
cursorCol: string
): Promise<any[]> {
const { limit, order } = parseCursorPagination(pagingParams);
cursorCols: string | string[],
cursorParser?: (token: string) => { [name: string]: string }
) {
return new Pager(queryBuilder, pagingParams, cursorCols, cursorParser).paginate();
}

class Pager {
private cursorCols: string[];
private cursorParameters: string[];

queryBuilder.orderBy(cursorCol, order.toUpperCase() as "ASC" | "DESC").take(limit);
constructor(
private queryBuilder: SelectQueryBuilder<any>,
private pagingParams: PagingParams,
cursorCols: string | string[],
private cursorParser?: (token: string) => { [name: string]: string }
) {
this.cursorCols = typeof cursorCols === "string" ? [cursorCols] : cursorCols.sort();

if (pagingParams.after) {
queryBuilder.andWhere(`${cursorCol} > :cursor`, { cursor: pagingParams.after });
} else if (pagingParams.before) {
queryBuilder.andWhere(`${cursorCol} < :cursor`, { cursor: pagingParams.before });
// Strip tablename prefix
this.cursorParameters = this.cursorCols.map(col => col.replace(/^\w+\./, ""));
}

return properlyOrdered(await queryBuilder.getMany(), pagingParams);
public async paginate(): Promise<any[]> {
const { limit, cursor, order } = parseCursorPagination(this.pagingParams);

this.cursorCols.forEach(col => {
this.queryBuilder.addOrderBy(col, order.toUpperCase() as "ASC" | "DESC");
});

this.queryBuilder.take(limit);

if (cursor) {
this.applyCursor(cursor);
}

return properlyOrdered(await this.queryBuilder.getMany(), this.pagingParams);
}

private applyCursor(cursor: string) {
if (this.pagingParams.after) {
this.queryBuilder.andWhere(this.buildWhereExpression(">"));
} else if (this.pagingParams.before) {
this.queryBuilder.andWhere(this.buildWhereExpression("<"));
}

if (!this.cursorParser) {
this.queryBuilder.setParameter(this.cursorParameters[0], cursor);
} else {
const cursorVals = this.cursorParser(cursor);

this.checkCursorParser(cursorVals);

for (const name of this.cursorParameters) {
this.queryBuilder.setParameter(name, cursorVals[name]);
}
}
}

private checkCursorParser(cursorVals: { [name: string]: string }): void {
const keys = Object.keys(cursorVals).sort();

if (JSON.stringify(keys) !== JSON.stringify(this.cursorParameters)) {
throw new Error(`cursorParser returned unsuitable values, got [${keys}], need [${this.cursorParameters}]`);
}
}

private buildWhereExpression(op: ">" | "<") {
const columnNames = this.cursorCols.join(", ");
const placeholders = this.cursorParameters.map(cp => `:${cp}`).join(", ");
// (col1, col2) > (:col1, :col2)
return `(${columnNames}) ${op} (${placeholders})`;
}
}

0 comments on commit bfa45aa

Please sign in to comment.