From 2ee354a36c3925598eead43bf00f0f51e824ca77 Mon Sep 17 00:00:00 2001 From: Phil Kuang Date: Thu, 22 Jan 2026 11:28:38 -0500 Subject: [PATCH] refactor(pg-search): return numberOfResults with queries Signed-off-by: Phil Kuang --- .changeset/lucky-masks-doubt.md | 5 +++++ plugins/search-backend-module-pg/report.api.md | 1 + .../src/PgSearchEngine/PgSearchEngine.test.ts | 6 ++++++ .../src/PgSearchEngine/PgSearchEngine.ts | 7 ++++++- .../src/database/DatabaseDocumentStore.test.ts | 12 ++++++++++++ .../src/database/DatabaseDocumentStore.ts | 1 + .../search-backend-module-pg/src/database/types.ts | 4 ++++ 7 files changed, 35 insertions(+), 1 deletion(-) create mode 100644 .changeset/lucky-masks-doubt.md diff --git a/.changeset/lucky-masks-doubt.md b/.changeset/lucky-masks-doubt.md new file mode 100644 index 0000000000..b20633308f --- /dev/null +++ b/.changeset/lucky-masks-doubt.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-search-backend-module-pg': patch +--- + +Return `numberOfResults` count with search query responses diff --git a/plugins/search-backend-module-pg/report.api.md b/plugins/search-backend-module-pg/report.api.md index 67e60b7822..317547cbdc 100644 --- a/plugins/search-backend-module-pg/report.api.md +++ b/plugins/search-backend-module-pg/report.api.md @@ -89,6 +89,7 @@ export interface DocumentResultRow { document: IndexableDocument; // (undocumented) highlight: IndexableDocument; + total_count: string; // (undocumented) type: string; } diff --git a/plugins/search-backend-module-pg/src/PgSearchEngine/PgSearchEngine.test.ts b/plugins/search-backend-module-pg/src/PgSearchEngine/PgSearchEngine.test.ts index bc3df76692..9c77a04e44 100644 --- a/plugins/search-backend-module-pg/src/PgSearchEngine/PgSearchEngine.test.ts +++ b/plugins/search-backend-module-pg/src/PgSearchEngine/PgSearchEngine.test.ts @@ -193,6 +193,7 @@ describe('PgSearchEngine', () => { database.transaction.mockImplementation(fn => fn(tx)); database.query.mockResolvedValue([ { + total_count: '1', document: { title: 'Hello World', text: 'Lorem Ipsum', @@ -212,6 +213,7 @@ describe('PgSearchEngine', () => { }); expect(results).toEqual({ + numberOfResults: 1, results: [ { document: { @@ -251,6 +253,7 @@ describe('PgSearchEngine', () => { Array(30) .fill(0) .map((_, i) => ({ + total_count: '30', document: { title: 'Hello World', text: 'Lorem Ipsum', @@ -270,6 +273,7 @@ describe('PgSearchEngine', () => { }); expect(results).toEqual({ + numberOfResults: 30, results: Array(25) .fill(0) .map((_, i) => ({ @@ -309,6 +313,7 @@ describe('PgSearchEngine', () => { Array(30) .fill(0) .map((_, i) => ({ + total_count: '30', document: { title: 'Hello World', text: 'Lorem Ipsum', @@ -330,6 +335,7 @@ describe('PgSearchEngine', () => { }); expect(results).toEqual({ + numberOfResults: 30, results: Array(30) .fill(0) .map((_, i) => ({ diff --git a/plugins/search-backend-module-pg/src/PgSearchEngine/PgSearchEngine.ts b/plugins/search-backend-module-pg/src/PgSearchEngine/PgSearchEngine.ts index 1a024be33c..ea75b1a739 100644 --- a/plugins/search-backend-module-pg/src/PgSearchEngine/PgSearchEngine.ts +++ b/plugins/search-backend-module-pg/src/PgSearchEngine/PgSearchEngine.ts @@ -269,7 +269,12 @@ export class PgSearchEngine implements SearchEngine { }), ); - return { results, nextPageCursor, previousPageCursor }; + return { + results, + numberOfResults: rows.length > 0 ? parseInt(rows[0].total_count, 10) : 0, + nextPageCursor, + previousPageCursor, + }; } } diff --git a/plugins/search-backend-module-pg/src/database/DatabaseDocumentStore.test.ts b/plugins/search-backend-module-pg/src/database/DatabaseDocumentStore.test.ts index ef249fa33d..7ef48d8398 100644 --- a/plugins/search-backend-module-pg/src/database/DatabaseDocumentStore.test.ts +++ b/plugins/search-backend-module-pg/src/database/DatabaseDocumentStore.test.ts @@ -274,6 +274,7 @@ describe('DatabaseDocumentStore', () => { expect(rows).toEqual([ { + total_count: '2', document: { location: 'LOCATION-1', text: 'Hello World', @@ -320,6 +321,7 @@ describe('DatabaseDocumentStore', () => { expect(rows).toEqual([ { + total_count: '2', document: { location: 'LOCATION-1', text: 'Around the world', @@ -329,6 +331,7 @@ describe('DatabaseDocumentStore', () => { type: 'test', }, { + total_count: '2', document: { location: 'LOCATION-1', text: 'Hello World', @@ -382,6 +385,7 @@ describe('DatabaseDocumentStore', () => { expect(rows).toEqual([ { + total_count: '1', document: { location: 'LOCATION-1', text: 'Hello World', @@ -436,6 +440,7 @@ describe('DatabaseDocumentStore', () => { expect(rows).toEqual([ { + total_count: '1', document: { location: 'LOCATION-1', text: 'Hello World', @@ -503,6 +508,7 @@ describe('DatabaseDocumentStore', () => { expect(rows).toEqual([ { + total_count: '4', document: { location: 'LOCATION-1', text: 'Hello World', @@ -513,6 +519,7 @@ describe('DatabaseDocumentStore', () => { type: 'my-type', }, { + total_count: '4', document: { location: 'LOCATION-1', text: 'Hello World', @@ -523,6 +530,7 @@ describe('DatabaseDocumentStore', () => { type: 'my-type', }, { + total_count: '4', document: { location: 'LOCATION-1', text: 'Hello World', @@ -533,6 +541,7 @@ describe('DatabaseDocumentStore', () => { type: 'my-type', }, { + total_count: '4', document: { location: 'LOCATION-1', text: 'Hello World', @@ -585,6 +594,7 @@ describe('DatabaseDocumentStore', () => { expect(rows).toEqual([ { + total_count: '1', document: { location: 'LOCATION-1', text: 'Hello World', @@ -635,6 +645,7 @@ describe('DatabaseDocumentStore', () => { expect(rows).toEqual([ { + total_count: '2', document: { title: 'Lorem Ipsum', text: 'Hello World', @@ -645,6 +656,7 @@ describe('DatabaseDocumentStore', () => { type: 'my-type', }, { + total_count: '2', document: { title: 'Dolor sit amet', text: 'Hello World', diff --git a/plugins/search-backend-module-pg/src/database/DatabaseDocumentStore.ts b/plugins/search-backend-module-pg/src/database/DatabaseDocumentStore.ts index 21512361a9..bd63756b06 100644 --- a/plugins/search-backend-module-pg/src/database/DatabaseDocumentStore.ts +++ b/plugins/search-backend-module-pg/src/database/DatabaseDocumentStore.ts @@ -215,6 +215,7 @@ export class DatabaseDocumentStore implements DatabaseStore { } query.select('type', 'document'); + query.select(tx.raw('COUNT(*) OVER() AS total_count')); if (pgTerm && options.useHighlight) { const headlineOptions = `MaxWords=${options.maxWords}, MinWords=${options.minWords}, ShortWord=${options.shortWord}, HighlightAll=${options.highlightAll}, MaxFragments=${options.maxFragments}, FragmentDelimiter=${options.fragmentDelimiter}, StartSel=${options.preTag}, StopSel=${options.postTag}`; diff --git a/plugins/search-backend-module-pg/src/database/types.ts b/plugins/search-backend-module-pg/src/database/types.ts index f8529fbebe..5012e1d792 100644 --- a/plugins/search-backend-module-pg/src/database/types.ts +++ b/plugins/search-backend-module-pg/src/database/types.ts @@ -58,6 +58,10 @@ export interface RawDocumentRow { /** @public */ export interface DocumentResultRow { + /** + * Total number of documents matching the query, regardless of pagination. + */ + total_count: string; document: IndexableDocument; type: string; highlight: IndexableDocument;