Skip to content

TRY_CONVERT - #901

Closed
robozmey wants to merge 8 commits into
apache:mainfrom
robozmey:try_convert-in-cb
Closed

TRY_CONVERT#901
robozmey wants to merge 8 commits into
apache:mainfrom
robozmey:try_convert-in-cb

Conversation

@robozmey

@robozmey robozmey commented Feb 3, 2025

Copy link
Copy Markdown
Contributor

Extension TRY_CONVERT adds function TRY_CONVERT

TRY_CONVERT was made to use like TRY_CAST from SQL-Server

Due polymorphism have strange signature (some_type, anyelement) -> anyelement

First parament is SOURCE_VALUE

Second parameter is DEFAULT_VALUE::TARGET_TYPE

TRY_CONVERT('42'::text, NULL::int2) -- returns 42::int2
TRY_CONVERT('42d'::text, NULL::int2) -- returns NULL::int2
TRY_CONVERT('42d'::text, 1234::int2) -- returns 1234::int2

We cannot have (any1, any2) -> any2 signature with different anyelement types, but we can create multiple (_, any) -> any functions in contrib/try_convert/try_convert--1.0.sql or use select add_type_for_try_convert('date'::regtype); to add types.

In init we can add all supported types, but with add_type_for_try_convert user can add whatever type he wants

ERROR HANDLING

Error handles by PG_TRY() PG_CATCH()

But better way is use "soft" error handling from Postgres 17 (postgres/postgres@ccff2d2) it also realising OPENGPDB (open-gpdb/gpdb@21be368). Of course it needs to convert datatype function to support "soft" error handling

Fixes #ISSUE_Number

What does this PR do?

Type of Change

  • Bug fix (non-breaking change)
  • New feature (non-breaking change)
  • Breaking change (fix or feature with breaking changes)
  • Documentation update

Breaking Changes

Test Plan

  • Unit tests added/updated
  • Integration tests added/updated
  • Passed make installcheck
  • Passed make -C src/test installcheck-cbdb-parallel

Impact

Performance:

User-facing changes:

Dependencies:

Checklist

Additional Context

CI Skip Instructions


Comment thread contrib/try_convert/try_convert.c
Comment thread contrib/try_convert/try_convert.c Outdated
Comment thread contrib/try_convert/try_convert.c Outdated
Comment on lines +49 to +59
if (OidIsValid(sourceTypeId))
sourceTypeId = getBaseType(sourceTypeId);
if (OidIsValid(targetTypeId))
targetTypeId = getBaseType(targetTypeId);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if the source or target type is invalid?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All will happend like in

if (OidIsValid(sourceTypeId))
(My conversion algorithm duplicates internal one)

We will look in pg_cast table - will find nothing, will try to get TypeCategory and will get assert in TypeCategory(targetTypeId)

Comment thread contrib/try_convert/try_convert.c Outdated

*funcId = castForm->castfunc;
ReleaseSysCache(tuple);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better add else branch here.

}

Datum
convert_from_function(Datum value, int32 typmod, Oid funcId, bool *is_failed)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is the error handling undetermined?

@robozmey
robozmey force-pushed the try_convert-in-cb branch from 7c1d1e5 to c3b4c2b Compare June 11, 2025 09:21
Comment thread contrib/try_convert/Makefile
Comment thread contrib/try_convert/scripts/check_test.py
Comment thread contrib/try_convert/try_convert--1.0.sql
Comment thread contrib/try_convert/try_convert.c
@robozmey
robozmey force-pushed the try_convert-in-cb branch from 737228d to fde465f Compare July 16, 2025 08:57
@@ -0,0 +1,92 @@
# TRY_CONVERT

TRY_CONVERT is Greenplum extension, which adds function for error-safe type cast like [TRY_CAST from SQL-Server](https://learn.microsoft.com/ru-ru/sql/t-sql/functions/try-cast-transact-sql?view=sql-server-ver16)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
TRY_CONVERT is Greenplum extension, which adds function for error-safe type cast like [TRY_CAST from SQL-Server](https://learn.microsoft.com/ru-ru/sql/t-sql/functions/try-cast-transact-sql?view=sql-server-ver16)
TRY_CONVERT is Greenplum/Cloudberry extension, which adds function for error-safe type cast like [TRY_CAST from SQL-Server](https://learn.microsoft.com/ru-ru/sql/t-sql/functions/try-cast-transact-sql?view=sql-server-ver16)


## Why signature is so strange?

Greenplum function polymorphism accept to have polymorphic functions only one any type in signature.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Greenplum function polymorphism accept to have polymorphic functions only one any type in signature.
Greenplum/Cloudberry function polymorphism accept to have polymorphic functions only one any type in signature.

@tuhaihe

tuhaihe commented Jul 17, 2025

Copy link
Copy Markdown
Member

Hi @robozmey thanks for your updates! Now the license headers look good to me. Please make sure these files are originally created by our community members, not cherry-picked or copied from other projects.

Before merging, welcome to squash your commits into one and revise the commit message body, you can take this template as a reference.

Alena0704 added a commit to Alena0704/cloudberry that referenced this pull request Jul 30, 2026
TRY_CONVERT(source_value, default_value) casts source_value to the type
of default_value and returns default_value whenever the cast fails,
which is the behaviour of TRY_CAST in SQL Server.  Without it a single
malformed value makes the whole query fail, and the plpgsql workarounds
that catch the error per row are several times slower.

    TRY_CONVERT('42'::text, NULL::int2)   -- returns 42::int2
    TRY_CONVERT('42d'::text, NULL::int2)  -- returns NULL::int2
    TRY_CONVERT('42d'::text, 1234::int2)  -- returns 1234::int2

The conversion to use is resolved the same way the parser resolves an
explicit cast in coerce_type(): a pg_cast entry, an I/O conversion or a
binary-compatible relabel, followed by the length coercion of the
target type.  Casts between array types and casts to domain types are
not supported and are reported as query errors, the same as a cast that
does not exist at all; only a failure caused by the converted data is
turned into the default value.

The cast itself runs inside a PG_TRY() block, since the datatype input
functions of Cloudberry cannot yet report a conversion failure without
throwing.  Query cancellation and assertion failures are re-thrown
rather than swallowed, following what plpgsql does for "EXCEPTION WHEN
others".  Once the input functions are converted to the soft error
handling infrastructure of PostgreSQL 17, the PG_TRY() block can go
away.

Polymorphism only accepts a single "any" type per signature, hence the
default value carrying the target type.  The built-in types are
registered by the extension script; add_type_for_try_convert(regtype)
registers additional ones, for example the hstore and citext types.

The extension is marked trusted, so the owner of a database can install
it without being a superuser.

The regression test is derived from the catalog files and from the
sample values in contrib/try_convert/data/, so "make installcheck"
generates it instead of the tree carrying it.  The ic-contrib job of the
build workflows runs it.

The extension was written by Vladimir Rachkin and proposed in PR#901;
this commit rebases that work and addresses the review comments
left on it.

Co-authored-by: Vladimir Rachkin <vova@kpnn.ru>
See: PR#901 <apache#901>
Alena0704 added a commit to Alena0704/cloudberry that referenced this pull request Jul 30, 2026
TRY_CONVERT(source_value, default_value) casts source_value to the type
of default_value and returns default_value whenever the cast fails,
which is the behaviour of TRY_CAST in SQL Server.  Without it a single
malformed value makes the whole query fail, and the plpgsql workarounds
that catch the error per row are several times slower.

    TRY_CONVERT('42'::text, NULL::int2)   -- returns 42::int2
    TRY_CONVERT('42d'::text, NULL::int2)  -- returns NULL::int2
    TRY_CONVERT('42d'::text, 1234::int2)  -- returns 1234::int2

The conversion to use is resolved the same way the parser resolves an
explicit cast in coerce_type(): a pg_cast entry, an I/O conversion or a
binary-compatible relabel, followed by the length coercion of the
target type.  Casts between array types and casts to domain types are
not supported and are reported as query errors, the same as a cast that
does not exist at all; only a failure caused by the converted data is
turned into the default value.

The cast itself runs inside a PG_TRY() block, since the datatype input
functions of Cloudberry cannot yet report a conversion failure without
throwing.  Query cancellation and assertion failures are re-thrown
rather than swallowed, following what plpgsql does for "EXCEPTION WHEN
others".  Once the input functions are converted to the soft error
handling infrastructure of PostgreSQL 17, the PG_TRY() block can go
away.

Polymorphism only accepts a single "any" type per signature, hence the
default value carrying the target type.  The built-in types are
registered by the extension script; add_type_for_try_convert(regtype)
registers additional ones, for example the hstore and citext types.

The extension is marked trusted, so the owner of a database can install
it without being a superuser.

The regression test is derived from the catalog files and from the
sample values in contrib/try_convert/data/, so "make installcheck"
generates it instead of the tree carrying it.  The ic-contrib job of the
build workflows runs it.

The extension was written by Vladimir Rachkin and proposed in PR#901;
this commit rebases that work and addresses the review comments
left on it.

Co-authored-by: Vladimir Rachkin <vova@kpnn.ru>
See: PR#901 <apache#901>
Alena0704 added a commit to Alena0704/cloudberry that referenced this pull request Jul 30, 2026
TRY_CONVERT(source_value, default_value) casts source_value to the type
of default_value and returns default_value whenever the cast fails,
which is the behaviour of TRY_CAST in SQL Server.  Without it a single
malformed value makes the whole query fail, and the plpgsql workarounds
that catch the error per row are several times slower.

    TRY_CONVERT('42'::text, NULL::int2)   -- returns 42::int2
    TRY_CONVERT('42d'::text, NULL::int2)  -- returns NULL::int2
    TRY_CONVERT('42d'::text, 1234::int2)  -- returns 1234::int2

The conversion to use is resolved the same way the parser resolves an
explicit cast in coerce_type(): a pg_cast entry, an I/O conversion or a
binary-compatible relabel, followed by the length coercion of the
target type.  Casts between array types and casts to domain types are
not supported and are reported as query errors, the same as a cast that
does not exist at all; only a failure caused by the converted data is
turned into the default value.

The cast itself runs inside a PG_TRY() block, since the datatype input
functions of Cloudberry cannot yet report a conversion failure without
throwing.

The extension is marked trusted, so the owner of a database can install
it without being a superuser.

Co-authored-by: Vladimir Rachkin <vova@kpnn.ru>
See: PR#901 <apache#901>
Alena0704 added a commit to Alena0704/cloudberry that referenced this pull request Jul 30, 2026
TRY_CONVERT(source_value, default_value) casts source_value to the type
of default_value and returns default_value whenever the cast fails,
which is the behaviour of TRY_CAST in SQL Server.  Without it a single
malformed value makes the whole query fail, and the plpgsql workarounds
that catch the error per row are several times slower.

    TRY_CONVERT('42'::text, NULL::int2)   -- returns 42::int2
    TRY_CONVERT('42d'::text, NULL::int2)  -- returns NULL::int2
    TRY_CONVERT('42d'::text, 1234::int2)  -- returns 1234::int2

The conversion to use is resolved the same way the parser resolves an
explicit cast in coerce_type(): a pg_cast entry, an I/O conversion or a
binary-compatible relabel, followed by the length coercion of the
target type.  Casts between array types and casts to domain types are
not supported and are reported as query errors, the same as a cast that
does not exist at all; only a failure caused by the converted data is
turned into the default value.

The cast itself runs inside a PG_TRY() block, since the datatype input
functions of Cloudberry cannot yet report a conversion failure without
throwing.

The extension is marked trusted, so the owner of a database can install
it without being a superuser.

Co-authored-by: Vladimir Rachkin <vova@kpnn.ru>
See: PR#901 <apache#901>
@Alena0704 Alena0704 mentioned this pull request Jul 30, 2026
13 tasks
@Alena0704

Alena0704 commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

I don't have push access to the original fork, and the head branch of an existing PR can't be changed, so instead of updating this one I opened a separate PR #1872 with the rebased version.

All credit for the extension goes to @robozmey - the commit lists him as a co-author.

On top of the rebase I addressed the open review comments, pulled in the follow-up fixes the extension got in open-gpdb since this PR was opened, added the module to the ic-contrib CI matrix (nothing was running its tests before), and marked the extension trusted so that a non-superuser with CREATE privilege on the database can install it.

Alena0704 added a commit to Alena0704/cloudberry that referenced this pull request Jul 30, 2026
TRY_CONVERT(source_value, default_value) casts source_value to the type
of default_value and returns default_value whenever the cast fails,
which is the behaviour of TRY_CAST in SQL Server.  Without it a single
malformed value makes the whole query fail, and the plpgsql workarounds
that catch the error per row are several times slower.

    TRY_CONVERT('42'::text, NULL::int2)   -- returns 42::int2
    TRY_CONVERT('42d'::text, NULL::int2)  -- returns NULL::int2
    TRY_CONVERT('42d'::text, 1234::int2)  -- returns 1234::int2

The conversion to use is resolved the same way the parser resolves an
explicit cast in coerce_type(): a pg_cast entry, an I/O conversion or a
binary-compatible relabel, followed by the length coercion of the
target type.  Casts between array types and casts to domain types are
not supported and are reported as query errors, the same as a cast that
does not exist at all; only a failure caused by the converted data is
turned into the default value.

The cast itself runs inside a PG_TRY() block, since the datatype input
functions of Cloudberry cannot yet report a conversion failure without
throwing.

The extension is marked trusted, so the owner of a database can install
it without being a superuser.

Co-authored-by: Vladimir Rachkin <vova@kpnn.ru>
See: PR#901 <apache#901>
@tuhaihe

tuhaihe commented Jul 31, 2026

Copy link
Copy Markdown
Member

Maybe we can close this PR since we have a new one.

@tuhaihe tuhaihe closed this Jul 31, 2026
tuhaihe pushed a commit to Alena0704/cloudberry that referenced this pull request Jul 31, 2026
TRY_CONVERT(source_value, default_value) casts source_value to the type
of default_value and returns default_value whenever the cast fails,
which is the behaviour of TRY_CAST in SQL Server.  Without it a single
malformed value makes the whole query fail, and the plpgsql workarounds
that catch the error per row are several times slower.

    TRY_CONVERT('42'::text, NULL::int2)   -- returns 42::int2
    TRY_CONVERT('42d'::text, NULL::int2)  -- returns NULL::int2
    TRY_CONVERT('42d'::text, 1234::int2)  -- returns 1234::int2

The conversion to use is resolved the same way the parser resolves an
explicit cast in coerce_type(): a pg_cast entry, an I/O conversion or a
binary-compatible relabel, followed by the length coercion of the
target type.  Casts between array types and casts to domain types are
not supported and are reported as query errors, the same as a cast that
does not exist at all; only a failure caused by the converted data is
turned into the default value.

The cast itself runs inside a PG_TRY() block, since the datatype input
functions of Cloudberry cannot yet report a conversion failure without
throwing.

The extension is marked trusted, so the owner of a database can install
it without being a superuser.

Co-authored-by: Vladimir Rachkin <vova@kpnn.ru>
See: PR#901 <apache#901>
tuhaihe pushed a commit that referenced this pull request Aug 1, 2026
TRY_CONVERT(source_value, default_value) casts source_value to the type
of default_value and returns default_value whenever the cast fails,
which is the behaviour of TRY_CAST in SQL Server.  Without it a single
malformed value makes the whole query fail, and the plpgsql workarounds
that catch the error per row are several times slower.

    TRY_CONVERT('42'::text, NULL::int2)   -- returns 42::int2
    TRY_CONVERT('42d'::text, NULL::int2)  -- returns NULL::int2
    TRY_CONVERT('42d'::text, 1234::int2)  -- returns 1234::int2

The conversion to use is resolved the same way the parser resolves an
explicit cast in coerce_type(): a pg_cast entry, an I/O conversion or a
binary-compatible relabel, followed by the length coercion of the
target type.  Casts between array types and casts to domain types are
not supported and are reported as query errors, the same as a cast that
does not exist at all; only a failure caused by the converted data is
turned into the default value.

The cast itself runs inside a PG_TRY() block, since the datatype input
functions of Cloudberry cannot yet report a conversion failure without
throwing.

The extension is marked trusted, so the owner of a database can install
it without being a superuser.

Co-authored-by: Vladimir Rachkin <vova@kpnn.ru>
See: PR#901 <#901>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants