From af6a26fb4a7ac3107d13ae5d3d529416f8b8666f Mon Sep 17 00:00:00 2001 From: Xnerv Wang Date: Wed, 11 Nov 2026 09:25:55 +0000 Subject: [PATCH v2] Fix pg_input_is_valid() reusing a stale type in PL/pgSQL pg_input_is_valid_common() parses the type name only once if get_fn_expr_arg_stable() says the argument is stable, but that function also returns true for an external Param. PL/pgSQL passes variables that way, and a simple expression keeps its ExprState, and hence fn_extra, across evaluations within a transaction. So a loop calling pg_input_is_valid(val, typ) kept using the type from the first iteration. pg_input_error_info() has the same problem. Continue to parse a Const type name only once. For an external Param, remember the type name last parsed and parse it again when it changes. Arguments not known to be stable are still parsed on each call. Oversight in commit 1939d26282b. --- src/backend/utils/adt/misc.c | 51 +++++++++++++++---- .../plpgsql/src/expected/plpgsql_simple.out | 20 ++++++++ src/pl/plpgsql/src/sql/plpgsql_simple.sql | 15 ++++++ 3 files changed, 76 insertions(+), 10 deletions(-) diff --git a/src/backend/utils/adt/misc.c b/src/backend/utils/adt/misc.c index c033e68ba15..5484e78198a 100644 --- a/src/backend/utils/adt/misc.c +++ b/src/backend/utils/adt/misc.c @@ -57,7 +57,10 @@ typedef struct ValidIOData { Oid typoid; int32 typmod; - bool typname_constant; + bool typname_constant; /* typname argument is a Const */ + bool typname_stable; /* ... or an external Param */ + char *typname; /* last name parsed, if stable but not Const */ + int typname_len; Oid typiofunc; Oid typioparam; FmgrInfo inputproc; @@ -750,30 +753,50 @@ pg_input_is_valid_common(FunctionCallInfo fcinfo, my_extra = (ValidIOData *) fcinfo->flinfo->fn_extra; if (my_extra == NULL) { + Node *expr = fcinfo->flinfo->fn_expr; + fcinfo->flinfo->fn_extra = - MemoryContextAlloc(fcinfo->flinfo->fn_mcxt, - sizeof(ValidIOData)); + MemoryContextAllocZero(fcinfo->flinfo->fn_mcxt, + sizeof(ValidIOData)); my_extra = (ValidIOData *) fcinfo->flinfo->fn_extra; - my_extra->typoid = InvalidOid; - /* Detect whether typname argument is constant. */ - my_extra->typname_constant = get_fn_expr_arg_stable(fcinfo->flinfo, 1); + /* Detect whether typname argument is stable, and whether a Const. */ + my_extra->typname_stable = get_fn_expr_arg_stable(fcinfo->flinfo, 1); + my_extra->typname_constant = my_extra->typname_stable && + IsA(expr, FuncExpr) && + IsA(lsecond(((FuncExpr *) expr)->args), Const); } /* - * If the typname argument is constant, we only need to parse it the first - * time through. + * A constant type name only needs to be parsed the first time through. + * Any other stable type name is parsed again when its value changes: an + * external Param counts as stable, but plpgsql can supply a new value for + * it each time a simple expression is evaluated. */ - if (my_extra->typoid == InvalidOid || !my_extra->typname_constant) + if (my_extra->typoid == InvalidOid || !my_extra->typname_stable || + (!my_extra->typname_constant && + (my_extra->typname == NULL || + my_extra->typname_len != VARSIZE_ANY_EXHDR(typname) || + memcmp(my_extra->typname, VARDATA_ANY(typname), + my_extra->typname_len) != 0))) { char *typnamestr = text_to_cstring(typname); Oid typoid; + int32 typmod; + + /* Free the old name now, so that an error below forces a re-parse */ + if (my_extra->typname) + { + pfree(my_extra->typname); + my_extra->typname = NULL; + } /* Parse type-name argument to obtain type OID and encoded typmod. */ - (void) parseTypeString(typnamestr, &typoid, &my_extra->typmod, NULL); + (void) parseTypeString(typnamestr, &typoid, &typmod, NULL); /* Update type-specific info if typoid changed. */ if (my_extra->typoid != typoid) { + my_extra->typoid = InvalidOid; getTypeInputInfo(typoid, &my_extra->typiofunc, &my_extra->typioparam); @@ -781,6 +804,14 @@ pg_input_is_valid_common(FunctionCallInfo fcinfo, fcinfo->flinfo->fn_mcxt); my_extra->typoid = typoid; } + my_extra->typmod = typmod; + + if (my_extra->typname_stable && !my_extra->typname_constant) + { + my_extra->typname = MemoryContextStrdup(fcinfo->flinfo->fn_mcxt, + typnamestr); + my_extra->typname_len = strlen(typnamestr); + } } /* Now we can try to perform the conversion. */ diff --git a/src/pl/plpgsql/src/expected/plpgsql_simple.out b/src/pl/plpgsql/src/expected/plpgsql_simple.out index ccf15ea2200..4de9aec2300 100644 --- a/src/pl/plpgsql/src/expected/plpgsql_simple.out +++ b/src/pl/plpgsql/src/expected/plpgsql_simple.out @@ -148,3 +148,23 @@ end $$; ERROR: bigint out of range CONTEXT: SQL statement "select x*x*x" PL/pgSQL function inline_code_block line 5 at SQL statement +-- Check case where the type name passed to pg_input_is_valid() and +-- pg_input_error_info() changes between evaluations +do $$ +declare r record; +begin + for r in select * from (values ('42', 'integer'), ('hello', 'text'), + ('100000', 'smallint'), + ('abcdefg', 'varchar(5)'), + ('abcdefg', 'varchar(10)'), + ('abcdefghijk', 'varchar(10)')) as v(val, typ) loop + raise notice '% as %: % %', r.val, r.typ, pg_input_is_valid(r.val, r.typ), + (pg_input_error_info(r.val, r.typ)).message; + end loop; +end $$; +NOTICE: 42 as integer: t +NOTICE: hello as text: t +NOTICE: 100000 as smallint: f value "100000" is out of range for type smallint +NOTICE: abcdefg as varchar(5): f value too long for type character varying(5) +NOTICE: abcdefg as varchar(10): t +NOTICE: abcdefghijk as varchar(10): f value too long for type character varying(10) diff --git a/src/pl/plpgsql/src/sql/plpgsql_simple.sql b/src/pl/plpgsql/src/sql/plpgsql_simple.sql index d64e791800b..3dcbf722619 100644 --- a/src/pl/plpgsql/src/sql/plpgsql_simple.sql +++ b/src/pl/plpgsql/src/sql/plpgsql_simple.sql @@ -131,3 +131,18 @@ begin -- overflow during expression evaluation step does get an extra context line select x*x*x into y; end $$; + +-- Check case where the type name passed to pg_input_is_valid() and +-- pg_input_error_info() changes between evaluations +do $$ +declare r record; +begin + for r in select * from (values ('42', 'integer'), ('hello', 'text'), + ('100000', 'smallint'), + ('abcdefg', 'varchar(5)'), + ('abcdefg', 'varchar(10)'), + ('abcdefghijk', 'varchar(10)')) as v(val, typ) loop + raise notice '% as %: % %', r.val, r.typ, pg_input_is_valid(r.val, r.typ), + (pg_input_error_info(r.val, r.typ)).message; + end loop; +end $$; -- 2.47.3