BUG #19745: tsquery input: 33 nested "!" raise XX000 via elog(), escaping pg_input_is_valid()

From: PG Bug reporting form <noreply(at)postgresql(dot)org>
To: pgsql-bugs(at)lists(dot)postgresql(dot)org
Cc: kehan5800(at)gmail(dot)com
Subject: BUG #19745: tsquery input: 33 nested "!" raise XX000 via elog(), escaping pg_input_is_valid()
Date: 2026-10-04 05:17:04
Message-ID: 19745-39cd7b93a505f9a9@postgresql.org
Views: Whole Thread | Raw Message | Download mbox | Resend email
Thread:
Lists: pgsql-bugs

The following bug has been logged on the website:

Bug reference: 19745
Logged by: Ke
Email address: kehan5800(at)gmail(dot)com
PostgreSQL version: 18.6
Operating system: Ubuntu 22.04.2 x86_64
Description:

makepol() in src/backend/utils/adt/tsquery.c keeps a fixed operator stack
(STACKDEPTH = 32, tsquery.c:627), and pushOpStack() reports overflow as an
internal error:

static void
pushOpStack(OperatorElement *stack, int *lenstack, int8 op, int16
distance)
{
if (*lenstack == STACKDEPTH) /* internal error */
elog(ERROR, "tsquery stack too small");

(tsquery.c:638-639). The prefix operator "!" pushes an entry per occurrence,
so the condition is reachable from plain user input:

SELECT (repeat('!', 32) || 'a')::tsquery; -- ok
SELECT (repeat('!', 33) || 'a')::tsquery;
ERROR: XX000: tsquery stack too small
LOCATION: pushOpStack, tsquery.c:639

SELECT websearch_to_tsquery('simple', repeat('-', 33) || 'a');
ERROR: XX000: tsquery stack too small

SELECT to_tsquery('simple', repeat('!', 33) || 'a');
ERROR: XX000: tsquery stack too small

Because this is elog() rather than ereturn(escontext, ...), it bypasses the
soft-error machinery used since 16:

SELECT pg_input_is_valid(repeat('!', 32) || 'a', 'tsquery'); -- t
SELECT pg_input_is_valid(repeat('!', 33) || 'a', 'tsquery');
ERROR: XX000: tsquery stack too small

and COPY ... (ON_ERROR ignore) aborts the whole load instead of skipping
the row:

CREATE TABLE onerr (q tsquery);
-- onerr.txt: a & b
-- !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!a (33 "!")
-- c & d
\copy onerr from 'onerr.txt' (on_error ignore)
ERROR: XX000: tsquery stack too small
CONTEXT: COPY onerr, line 2, column q:
"!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!a"
SELECT count(*) FROM onerr; -- 0

Expected: an ordinary user-facing error with a proper SQLSTATE (e.g.
54001 statement_too_complex or 54000 program_limit_exceeded), raised through
the soft-error path so that pg_input_is_valid() returns false and
ON_ERROR ignore skips the row. websearch_to_tsquery() is documented for raw
user search input, so an XX000 from 33 hyphens typed into a search box is
the wrong category.

Actual: XX000 internal_error, not catchable by pg_input_is_valid() or
ON_ERROR ignore.

For comparison, intarray's copy of the same parser (contrib/intarray/
_int_bool.c:181-184) was converted when soft errors were introduced:

if (lenstack == STACKDEPTH)
ereturn(state->escontext, ERR,
(errcode(ERRCODE_STATEMENT_TOO_COMPLEX),
errmsg("statement too complex")));

SELECT pg_input_is_valid(repeat('!', 17) || '1', 'query_int'); -- f

The third copy, contrib/ltree/ltxtquery_io.c:252 (elog(ERROR, "stack too
short")), behaves like tsquery: 33 stacked "!" give XX000 and
pg_input_is_valid(..., 'ltxtquery') raises. That one was noted earlier in
https://www.postgresql.org/message-id/749c63a4-ff67-76a8-e6d4-53b293931c81@gmail.com

Suggested fix: pass the TSQueryParserState to pushOpStack() and report the
overflow with errsave(state->escontext, ...) as intarray does (e.g.
ERRCODE_STATEMENT_TOO_COMPLEX, "tsquery is too complex", errdetail naming
the limit), then return; makepol() already checks
SOFT_ERROR_OCCURRED(state->escontext) after each token and returns, so no
other plumbing is needed. The same change applies to ltxtquery_io.c's
makepol().
Raising STACKDEPTH alone would not address the error class or the
soft-error escape.

Responses

Browse pgsql-bugs by date

  From Date Subject
Next Message PG Bug reporting form 2026-10-04 05:18:39 BUG #19746: interval with INT64_MIN microseconds prints a value interval_in rejects
Previous Message PG Bug reporting form 2026-10-04 05:16:30 BUG #19744: contrib/seg output truncates to 6 significant digits, breaking dump/restore