Re: CAST(... ON DEFAULT) - WIP build on top of Error-Safe User Functions

From: Henson Choi <assam258(at)gmail(dot)com>
To: Corey Huinker <corey(dot)huinker(at)gmail(dot)com>, jian he <jian(dot)universality(at)gmail(dot)com>
Cc: Vik Fearing <vik(at)postgresfriends(dot)org>, Amul Sul <sulamul(at)gmail(dot)com>, Kirill Reshke <reshkekirill(at)gmail(dot)com>, Isaac Morland <isaac(dot)morland(at)gmail(dot)com>, pgsql-hackers(at)lists(dot)postgresql(dot)org
Subject: Re: CAST(... ON DEFAULT) - WIP build on top of Error-Safe User Functions
Date: 2026-06-30 07:07:23
Message-ID: CAAAe_zAi=PGO4j89w5Hu_Gu+njKeWY9YeX0hp91qvWVMXt0igw@mail.gmail.com
Views: Whole Thread | Raw Message | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

Hi hackers,

While reviewing the error-safe text->regclass cast -- the first patch,
"error safe for casting text to other types per pg_cast" -- I built it and
ran a few cases under ON CONVERSION ERROR to see how the cast behaves.
Three behaviors stand out, and together they surface one inconsistency and
a policy question I'd like to raise. The concrete site and fix below are in
that patch, independently of the main DEFAULT-syntax patch; ON CONVERSION
ERROR is only the lens that makes the cast's soft/hard behavior observable.
(Examples use ::text so they exercise the text_regclass cast function rather
than the regclass input function.)

* Observed behaviors (built from this patch, tested)

(i) A value-conversion error is caught and replaced by the DEFAULT, as
intended:

SELECT CAST('x' AS integer DEFAULT 0 ON CONVERSION ERROR); -- 0

(ii) The DEFAULT expression itself is evaluated without the soft-error net
(expr2 being the "non-error-safe typecast", as described early in this
thread). For a value type that never matters, but for a binding type like
regclass a DEFAULT that names a missing object hard-aborts:

SELECT CAST('public.nosuchA'::text AS regclass
DEFAULT 'public.nosuchB'::text ON CONVERSION ERROR);
-- ERROR: relation "public.nosuchb" does not exist (DEFAULT NULL is
fine)

This is by design; I note it only because a binding-type DEFAULT can itself
fail, unlike a value-type DEFAULT.

(iii) A "not found" lookup failure is mostly treated as soft. A missing
relation folds into the DEFAULT, and the regclass input function already
reports a missing schema softly (as "relation does not exist", 42P01):

SELECT CAST('public.nosuchtable'::text AS regclass
DEFAULT 999 ON CONVERSION ERROR); -- 999
SELECT message, sql_error_code
FROM pg_input_error_info('nosuch_schema.foo','regclass');
-- relation "nosuch_schema.foo" does not exist | 42P01

I couldn't find a point in the thread where it was decided whether a
lookup/not-found failure should be caught by ON CONVERSION ERROR at all --
the reg* casts seem to have been swept into the error-safe batch alongside
value casts, without distinguishing a binding failure from a value
conversion. The de-facto behavior, though, is soft.

* The inconsistency

Against that mostly-soft behavior, one not-found is hard: a missing SCHEMA
in a schema-qualified name. text_regclass() calls

RangeVarGetRelidExtendedSafe(rv, NoLock, 0, NULL, NULL, fcinfo->context)

with flags=0 (so missing_ok=false), and the schema-resolution step
LookupExplicitNamespace() -> get_namespace_oid() is not threaded with
escontext, so it raises ERRCODE_UNDEFINED_SCHEMA as a hard ereport:

SELECT CAST('nosuch_schema.foo'::text AS regclass
DEFAULT NULL ON CONVERSION ERROR);
-- ERROR: schema "nosuch_schema" does not exist (get_namespace_oid,
namespace.c)

So a missing schema hard-aborts, while the parallel missing relation one
branch below is soft (and regclassin softens the same missing-schema input).

* The question

This reads less like a localized slip and more like a policy decision that
was never made: under ON CONVERSION ERROR, should a "not found"
(lookup/binding) failure be hard or soft?

- If soft (consistent with the current majority behavior and with
regclassin), then LookupExplicitNamespace()/get_namespace_oid() need
escontext threaded on the not-found path -- equivalently text_regclass()
could pass RVR_MISSING_OK as regclassin does -- while the USAGE-permission
check stays hard, which it correctly does today:

-- as a role lacking USAGE on schema s1:
SELECT CAST('s1.t'::text AS regclass DEFAULT NULL ON CONVERSION
ERROR);
-- ERROR: permission denied for schema s1 (stays hard, not folded)

- If hard, then the inconsistency runs the other way: the relation-not-found
softening and regclassin's behavior would need revisiting, and whether
catalog-resolving reg* casts belong in ON CONVERSION ERROR at all deserves
a second look.

Either way, the current schema-vs-relation split is inconsistent. I'd
rather see the policy settled than just patch the one site.

Best regards,
Henson

In response to

Browse pgsql-hackers by date

  From Date Subject
Next Message Chao Li 2026-06-30 07:45:43 Fix RLS checks for UPDATE/DELETE FOR PORTION OF leftover rows
Previous Message Ewan Young 2026-06-30 07:01:31 JSON_VALUE/JSON_TABLE DEFAULT expression ignores RETURNING typmod