skip counting

Signed-off-by: Fredrik Adelöw <freben@gmail.com>
This commit is contained in:
Fredrik Adelöw
2024-12-06 10:05:28 +01:00
parent 5e5cc8f5b7
commit 6df4a705a7
5 changed files with 85 additions and 9 deletions
@@ -203,6 +203,7 @@ export interface QueryEntitiesInitialRequest {
term: string;
fields?: string[];
};
skipTotalItems?: boolean;
}
/**
@@ -1478,6 +1478,52 @@ describe('DefaultEntitiesCatalog', () => {
},
);
it.each(databases.eachSupportedId())(
'can skip totalItems, %p',
async databaseId => {
await createDatabase(databaseId);
await Promise.all(
Array(15)
.fill(0)
.map(() =>
addEntityToSearch({
apiVersion: 'a',
kind: 'k',
metadata: { name: v4() },
}),
),
);
const catalog = new DefaultEntitiesCatalog({
database: knex,
logger: mockServices.logger.mock(),
stitcher,
});
const request: QueryEntitiesInitialRequest = {
limit: 10,
credentials: mockCredentials.none(),
skipTotalItems: true,
};
let response = await catalog.queryEntities(request);
expect(response).toEqual({
totalItems: 0,
items: expect.objectContaining({ length: 10 }),
pageInfo: { nextCursor: expect.anything() },
});
response = await catalog.queryEntities({
...request,
cursor: response.pageInfo.nextCursor!,
});
expect(response).toEqual({
totalItems: 0,
items: expect.objectContaining({ length: 5 }),
pageInfo: { prevCursor: expect.anything() },
});
},
);
it.each(databases.eachSupportedId())(
'should paginate results accordingly in case of clashing items, %p',
async databaseId => {
@@ -331,6 +331,7 @@ export class DefaultEntitiesCatalog implements EntitiesCatalog {
const cursor: Omit<Cursor, 'orderFieldValues'> & {
orderFieldValues?: (string | null)[];
skipTotalItems: boolean;
} = {
orderFields: [],
isPrevious: false,
@@ -341,7 +342,8 @@ export class DefaultEntitiesCatalog implements EntitiesCatalog {
// request. The result is then embedded into the cursor for subsequent
// requests. Threfore this can be undefined here, but will then get
// populated further down.
const shouldComputeTotalItems = cursor.totalItems === undefined;
const shouldComputeTotalItems =
cursor.totalItems === undefined && !cursor.skipTotalItems;
const isFetchingBackwards = cursor.isPrevious;
if (cursor.orderFields.length > 1) {
@@ -532,8 +534,16 @@ export class DefaultEntitiesCatalog implements EntitiesCatalog {
const rows = shouldComputeTotalItems || limit > 0 ? await dbQuery : [];
const totalItems =
cursor.totalItems ?? (rows.length ? Number(rows[0].count) : 0);
let totalItems: number;
if (cursor.totalItems !== undefined) {
totalItems = cursor.totalItems;
} else if (cursor.skipTotalItems) {
totalItems = 0;
} else if (rows.length) {
totalItems = Number(rows[0].count);
} else {
totalItems = 0;
}
if (isFetchingBackwards) {
rows.reverse();
@@ -809,15 +819,26 @@ export const cursorParser: z.ZodSchema<Cursor> = z.object({
function parseCursorFromRequest(
request?: QueryEntitiesRequest,
): Partial<Cursor> {
): Partial<Cursor> & { skipTotalItems: boolean } {
if (isQueryEntitiesInitialRequest(request)) {
const { filter, orderFields: sortFields = [], fullTextFilter } = request;
return { filter, orderFields: sortFields, fullTextFilter };
const {
filter,
orderFields: sortFields = [],
fullTextFilter,
skipTotalItems = false,
} = request;
return { filter, orderFields: sortFields, fullTextFilter, skipTotalItems };
}
if (isQueryEntitiesCursorRequest(request)) {
return request.cursor;
return {
...request.cursor,
// Doesn't matter here
skipTotalItems: false,
};
}
return {};
return {
skipTotalItems: false,
};
}
function invertOrder(order: EntityOrder['order']) {
@@ -173,6 +173,7 @@ describe('createRouter readonly disabled', () => {
},
limit: 10000,
credentials: mockCredentials.user(),
skipTotalItems: true,
});
});
});
@@ -192,7 +192,14 @@ export async function createRouter(
do {
const result = await entitiesCatalog.queryEntities(
!cursor
? { credentials, fields, limit, filter, orderFields: order }
? {
credentials,
fields,
limit,
filter,
orderFields: order,
skipTotalItems: true,
}
: { credentials, fields, limit, cursor },
);