Nice article. I've used composite types with success for similar problems. Some tips:
- Composite types are useful for data validation. Money types and unit-based types are good for composite types.
- Avoid over-using composite types. Most of the time, regular columns are better. Not many tools interact well with composite types, like reporting tools or database libraries.
- Like the article notes, avoid using composite types for data types that may change.
- A JSONB domain type is a good alternative for data types that change often. Note that if you put the validation into a helper function, Postgres will not revalidate the domain if the function definition changes.
- Using composite types is mildly annoying since you must wrap the composite column in parenthesis to access the field.
-- Bad
SELECT package_semver.major FROM package_version;
-- Errors with `missing FROM-clause entry for table "package_semver"`
-- Good
SELECT (package_semver).major FROM package_version;
- When defining a domain type, separate checks into named constraints with ALTER DOMAIN. The Postgres error message for check validation failures is lackluster and provides little beyond "it failed." CREATE DOMAIN item_delta_node AS jsonb NOT NULL;
ALTER DOMAIN item_delta_node
ADD CONSTRAINT item_delta_node_is_object
CHECK (coalesce(jsonb_typeof(value), '') = 'object');
ALTER DOMAIN item_delta_node
ADD CONSTRAINT item_delta_node_item_delta_id_null_or_num
CHECK (coalesce(jsonb_typeof(value['item_delta_id']), 'number') IN ('null', 'number'));
""Using composite types is mildly annoying since you must wrap the composite column in parenthesis to access the field"""
^ yeah that is frustrating
--
if you create domains over jsonb frequently, check out pg_jsonschema[1] as a way to express the constraints more concisely