> On Nov 11, 2026, at 16:04, Chao Li <[email protected]> wrote:
> 
> 
> 
>> On Nov 10, 2026, at 16:11, Xnerv Wang <[email protected]> wrote:
>> 
>> Hi hackers,
>> 
>> One of our tests checks exported values with pg_input_is_valid() in a
>> PL/pgSQL loop and it returned wrong results. I can reproduce the
>> problem on master and 19rc1:
>> 
>> DO $$
>> DECLARE r record;
>> BEGIN
>>   FOR r IN SELECT * FROM (VALUES ('42', 'integer'), ('hello', 'text'),
>>                                  ('100000', 'smallint')) 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 <NULL>
>> NOTICE:  hello as text: f invalid input syntax for type integer: "hello"
>> NOTICE:  100000 as smallint: t <NULL>
>> 
>> The second and third calls should return t and f, but both validate
>> against integer, the type from the first call. pg_input_error_info()
>> has the same problem. The typmod is reused too: varchar(5) followed
>> by varchar(10) still checks against varchar(5).
>> 
>> In misc.c, pg_input_is_valid_common() parses the type name only on the
>> first call when get_fn_expr_arg_stable() says the argument is stable.
>> That function returns true for a PARAM_EXTERN Param, which is how
>> PL/pgSQL passes variables. But a PL/pgSQL simple expression keeps its
>> ExprState, and so fn_extra, across evaluations within a transaction
>> while the variable changes. This logic came in with commit 1939d26282b,
>> in PostgreSQL 16.
>> 
>> For a stable argument, the attached patch keeps the last type name
>> string it parsed and parses again when a later call passes a different
>> one. The new test in plpgsql_simple.sql (a similar loop plus varchar
>> typmod cases) fails without the fix, and check-world passes with it. A
>> constant type name now costs one extra name comparison per call: 5
>> million calls with 'int4' took 722 ms instead of 691 ms (-O2, no
>> assertions, median of 11 runs).
>> 
>> Dropping typname_constant and always comparing names would be simpler
>> and would also fix this bug. A type name from a column would then be
>> parsed only when its text changes, rather than on every call as today,
>> so it would behave like a constant name does now. If the text stays
>> the same, a search_path change between two FETCHes from a cursor would
>> go unnoticed. I kept the flag so that non-constant names behave as
>> before, and can send the simpler version if others prefer it.
>> 
>> Thanks,
>> Xnerv Wang
>> Amazon Web Services
>> <v1-0001-Fix-pg_input_is_valid-reusing-a-stale-type-in-PL-.patch>
> 
> I can reproduce the issue with a slightly simpler example:
> ```
> evantest=# do $$
> evantest$# declare
> evantest$#   typ text;
> evantest$#   result boolean;
> evantest$# begin
> evantest$#   for i in 1..2 loop
> evantest$#     if i = 1 then
> evantest$#        typ := 'integer';
> evantest$#     else
> evantest$#        typ := 'text';
> evantest$#     end if;
> evantest$#     result := pg_input_is_valid('abc', typ);
> evantest$#     raise notice 'type=%, valid=%', typ, result;
> evantest$#   end loop;
> evantest$# end $$;
> NOTICE:  type=integer, valid=f
> NOTICE:  type=text, valid=f
> ```
> 
> After some debugging, I think the root cause is that get_fn_expr_arg_stable() 
> returns true for both Const and PARAM_EXTERN. While an external parameter is 
> stable within a single query execution, its value can change between 
> evaluations of a cached PL/pgSQL expression.
> 
> n pg_input_is_valid_common(), we have no info to distinguish whether the 
> function is being called from PL/pgSQL. As you mentioned, the proposed fix 
> introduces some overhead for genuine Const arguments, since their values 
> cannot change.
> 
> Since we have already added two fields to ValidIOData, I'm wondering if we 
> could add another boolean field, say typname_stable, to store the result of 
> get_fn_expr_arg_stable(), and use the existing typname_constant field to 
> indicate whether the argument is a genuine Const. With this distinction, we 
> could retain the original caching behavior for genuine constants, without 
> comparing the type name on subsequent calls. We wouldn't even need to store 
> the type name in ValidIOData for Const arguments, which would help mitigate 
> the overhead.
> 

I just had another thought. Comparing type names might not completely resolve 
the caching problem.

For example, if the type is a domain, a subsequent DDL could drop and recreate 
the domain with a different underlying type. When execution reaches the same 
pg_input_is_valid() call again, the type name is unchanged, but it would 
resolve to a different type OID, leaving the cache stale.

I don't think this is a practical use case, but it seems theoretically 
possible, assuming the cached FmgrInfo survives the DDL operation. I wouldn't 
want to block this patch over this concern. I'm just mentioning it for your 
consideration.

Best regards,
--
Chao Li (Evan)
HighGo Software Co., Ltd.
https://www.highgo.com/






Reply via email to