From a23b04c8fa6625b6d89e059d0ce1e6471f827d45 Mon Sep 17 00:00:00 2001 From: Tom Moor Date: Tue, 26 May 2026 20:10:49 -0400 Subject: [PATCH] fix: Prevent ISE when tsquery tail interleaves operator and escape chars (#12475) When a user query produces a pg-tsquery output ending in mixed `&` and `\` characters (e.g. `"plugins"&\`), stripping them with separate single-char regexes in a fixed order could leave a dangling `&` operator, causing Postgres to reject the query with "no operand in tsquery". Co-authored-by: Claude Opus 4.7 --- .../search-postgres/server/PostgresSearchProvider.test.ts | 8 ++++++++ plugins/search-postgres/server/PostgresSearchProvider.ts | 8 ++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/plugins/search-postgres/server/PostgresSearchProvider.test.ts b/plugins/search-postgres/server/PostgresSearchProvider.test.ts index ad7fba7cd1..dd16a3121b 100644 --- a/plugins/search-postgres/server/PostgresSearchProvider.test.ts +++ b/plugins/search-postgres/server/PostgresSearchProvider.test.ts @@ -1412,5 +1412,13 @@ describe("PostgresSearchProvider", () => { `"this<->is<->a<->test"` ); }); + it("should strip interleaved trailing operator and escape characters", () => { + // pg-tsquery reorders trailing operators and may emit a tail like + // `&\` that, if stripped one character at a time in the wrong order, + // leaves a dangling `&` and triggers "no operand in tsquery". + expect(PostgresSearchProvider.webSearchQuery(`"plugins"\\&`)).toBe( + `"plugins"` + ); + }); }); }); diff --git a/plugins/search-postgres/server/PostgresSearchProvider.ts b/plugins/search-postgres/server/PostgresSearchProvider.ts index c00bc091e4..8eca93125a 100644 --- a/plugins/search-postgres/server/PostgresSearchProvider.ts +++ b/plugins/search-postgres/server/PostgresSearchProvider.ts @@ -861,10 +861,10 @@ export default class PostgresSearchProvider extends BaseSearchProvider { // spaces. Ref: https://github.com/caub/pg-tsquery/issues/27 quotedSearch ? limitedQuery.trim() : `${limitedQuery.trim()}*` ) - // Remove any trailing join characters - .replace(/&$/, "") - // Remove any trailing escape characters - .replace(/\\$/, "") + // Strip any trailing join (&) or escape (\) characters, in any + // combination, so we never hand to_tsquery an operator with no + // operand (e.g. a tail of "&\" would leave a dangling "&"). + .replace(/[&\\]+$/, "") ); }