As far as I was able to find out, it is currently not possible in PostgreSQL v9.5 (and above?) to obtain implicit casting with custom range types. To illustrate, when I define the following demo setup:
drop schema if exists tac cascade;
create schema tac;
create domain tac.usascii_small_letters
as text
check ( value ~ '^[a-z]+$' );
create domain tac.ucid as integer
check ( value between x'0'::integer and x'10ffff'::integer );
create function tac.ucid_rng_diff( cid_1 tac.ucid, date_2 tac.ucid )
returns double precision
language sql
immutable
as $$
select cast( cid_1 - date_2 as double precision );
$$;
-- A ----------------------------------------------------------------
create type tac.ucid_rng as range (
subtype = tac.ucid,
subtype_diff = tac.ucid_rng_diff
);
-- ----------------------------------------------------------------
-- -- B ----------------------------------------------------------------
-- set role dba; -- !!!!!!!!!!!!!!!!!!!!
-- create type tac.ucid_rng;
-- create function tac.ucid_rng_canonical( x tac.ucid_rng )
-- returns tac.ucid_rng
-- language plpgsql
-- as $$
-- begin
-- if not lower_inc(x) then
-- x := tac.ucid_rng(lower(x) + 1, upper(x), '[]');
-- end if;
-- if not upper_inc(x) then
-- x := tac.ucid_rng(lower(x), upper(x) - 1, '[]');
-- end if;
-- return x;
-- end;
-- $$;
-- create type tac.ucid_rng as range (
-- subtype = tac.ucid
-- , subtype_diff = tac.ucid_rng_diff
-- , canonical = tac.ucid_rng_canonical
-- );
-- reset role; -- !!!!!!!!!!!!!!!!!!!!
-- -- ----------------------------------------------------------------
create table tac.words (
word tac.usascii_small_letters,
cid tac.ucid,
cid_range tac.ucid_rng
);
insert into tac.words values
( 'foo', 1, '[11,21]' ),
( 'bar', 2, '[12,22]' ),
( 'zip', 3, '[13,23]' ),
( 'dat', 4, '[14,24]' ),
( 'baz', 5, '[15,25]' );
select * from tac.words where word between 'a' and 'c';
select * from tac.words where word between 'a'::tac.usascii_small_letters and 'c';
select * from tac.words where cid between 3 and 5;
select * from tac.words where cid_range @> '[17,23]';
select * from tac.words where cid_range @> '[17,23]'::tac.ucid_rng;
select * from tac.words where cid_range @> 23::tac.ucid;
select * from tac.words where cid_range @> 23;
and run it as is with block A but without block B, I get
ERROR: operator does not exist: integer <@ tac.ucid_rng
LINE 1: select * from tac.words where cid_range @> 23;
HINT: No operator matches the given name and argument type(s).
You might need to add explicit type casts.
The tests show that a lot of things using custom domains / types do work with implicit casting. Indeed, I can do a kind-of range check against my custom string data type, and even cid_range @> '[17,23]'
works. It is only cid_range @> 23
that mysteriously fails.
Now I tried to implement a suitable canonicalization function for my tac.cid_rng
type; however, as remarked in https://stackoverflow.com/a/29939205, that's not gonna fly:
NOTICE: argument type tac.ucid_rng is only a shell
NOTICE: return type tac.ucid_rng is only a shell
ERROR: PL/pgSQL functions cannot return type tac.ucid_rng
So it would seem that it's still impossible in PostgreSQL 9.5 to have a custom range type with implicit casting in point-inclusion queries, although the type itself is defined quite transparently over a set of contiguous integers and similar queries work fine without any extra coding.
I'd like to know whether I missed something here, and what recommended ways there are to deal with the situation. I think I could just live with the explicit cast, or else write a function that does it for me.
The thing I don't get is why cid_range @> '[17,23]'
works but cid_range @> 23
fails—after all, a cast from generic / suggestive literal to actual data type has to be performed in both cases. It would appear from the tests that implicit casting from integer literal into the tac.ucid
domain work as well, so it's hard to see what exactly is missing that necessitates a piece of C boiler plate code that does nothing but a generic check and integer increment / decrement.