Skip to content

Sum Types

Sum types (also known as tagged unions, or enums) is a type that allows to hold a value that could take on several different types that are known ahead of time.

In Postgres context, this allows one to return values of different types in a single query column, or store different values in a column where maintaining separate columns or tables is excessive.

Defining a sum type

One can define it using omni_types.sum_type function, passing the intended type name and the list of variant types.

Below, let's create a unified geometric type1:

omni_types=# select omni_types.sum_type('geom', 'point', 'line', 'lseg', 'box', 'path', 'polygon', 'circle');
sum_type
----------
 geom
(1 row)

We can now see it's been created:

omni_types=# \dT geom
list of data types
 schema | name | Description
--------+------+-------------
 public | geom |
(1 row)

omni_types=# table omni_types.sum_types;
oid  |                 variants
-------+-------------------------------------------
 16397 | {point,line,lseg,box,path,polygon,circle}
(1 row) 

Textual representation

Sum type can be initialized using textual representatin, with the variant name used to indicate the type:

omni_types=# select 'point(10,10)'::geom;
geom
----------------
 point((10,10))
(1 row)

By the virtue of seeing the above output, we know that it also converts back to a textual representation using the underlying variant's representation.

Conversion and casting

Sum types can be casted from and to their variants.

omni_types=# select '<(10,10),10>'::circle::geom;
geom
----------------------
 circle(<(10,10),10>)
(1 row)

omni_types=# select '<(10,10),10>'::circle::geom::circle;
circle
--------------
 <(10,10),10>
(1 row)

They can also be converted using functions following the pattern of <type>_from_<type>:

omni_types=# select geom_from_point('10,10');
geom_from_point
-----------------
 point((10,10))
(1 row)

omni_types=# select point_from_geom(geom_from_point('10,10'));
point_from_geom
-----------------
 (10,10)
(1 row)

If one attempts to cast or convert to a wrong variant, null will be returned:

omni_types=# select '<(10,10),10>'::circle::geom::point;
point
-------
 null
(1 row)
omni_types=# select point_from_geom('<(10,10),10>'::circle::geom);
point_from_geom
-----------------
 null
(1 row)
Caveat: casting domains

Due to the way domains work, casting is impossible as they are rather thin layers over their base types.

That being said, the functions described above can be used to accomplish the same:

omni_types=# create domain my_point as point;
CREATE DOMAIN

omni_types=# select omni_types.sum_type('my_geom','my_point');
sum_type
----------
 my_geom
(1 row)

omni_types=# select my_geom_from_my_point('1,1');
 my_geom_from_my_point
-----------------------
 my_point((1,1))
(1 row)

omni_types=# select my_point_from_my_geom(my_geom_from_my_point('1,1'));
 my_point_from_my_geom
-----------------------
 (1,1)
(1 row)

Retrieving the variant type

One can determine the type of the variant to advise further processing:

omni_types=# select omni_types.variant('point(10,10)'::geom);
variant
---------
 point
(1 row)

Changing the variant type

Sometimes it may be desirable to change the definition of a sum type. In most cases, it would be prudent to define a new type and migrate to it. However, this may be undesirable to due involved complexity. Luckily, under certain constraints, variant types can be changed:

  • Only adding new variants is permitted
  • For fixed-size sum types (those not containing variable-sized variants), the size of the new variant may not be larger than that of the largest existing variant. 2
select omni_types.add_variant('geom', 'my_box');

  1. PostGIS defines it is own geometry type. Our definition is used to showcase a generalized approach. 

  2. This is done so that Postgres would not try to read existing values using an updated, larger size, which is erroneous.