14.8.2023

PostgreSQL Composite Types to reduce boilerplate code from you database schema

PostgreSQL is a powerful and flexible open-source relational database management system. One of its most useful features is the ability to define custom composite types (Since Postgres 11), which are user-defined data types that combine multiple fields of different types into a single logical entity. In this article, we'll explore the benefits of using composite types in PostgreSQL and how to use them in your database schema.

Create Composite Type

To define a composite type in PostgreSQL, you use the CREATE TYPE statement, followed by the name of the type and a list of its component fields and types. For example, here's how you might define a audit type:

CREATE TYPE AUDIT AS (
    created_at TIMESTAMPTZ,
    created_by UUID,
    updated_at TIMESTAMPTZ,
    updated_by UUID
);

Create Table using Composite Type

Once you've defined a composite type, you can use it in your database schema just like any other data type. For example, you could create a table that includes a column of type customer_info:

CREATE TABLE customer (
    id UUID PRIMARY KEY,
    audit AUDIT
);

Insert data

To insert data into a table that includes a composite type, you can use the ROW constructor syntax, like this:

INSERT INTO customer(id, audit.created_at, audit.created_by)
VALUES (gen_random_uuid(), NOW(), '36765812-fa07-4227-bb90-e2b6ff00da89');

Query data

To query data from a table that includes a composite type, you can use the dot notation to access individual fields of the composite type, like this:

SELECT (audit).created_at, (audit).created_by FROM customer;

Note: You have to use the Parenthesis around the composite property, otherwise PostgreSQL tries to interpret it as a own table.

Define constraints on composite types using DOMAIN - Types

Composite Types do not support constraints. However, you can create a DOMAIN Type including constraints like:

CREATE DOMAIN AUDIT_DOMAIN AS AUDIT
CHECK (
    (value).created_at IS NOT NULL AND
    (value).created_by IS NOT NULL
);

Now you have to use the DOMAIN Type instead of the composite type

CREATE TABLE customer (
    id UUID PRIMARY KEY,
    audit AUDIT_DOMAIN
);

Inserts or updates violating this constraint will fail:

INSERT INTO customer(id, audit.created_at)
VALUES (gen_random_uuid(), NOW());

--- [23514] ERROR: value for domain audit_domain violates check constraint "audit_domain_check"

Use Composite Types in Hibernate

New Annotation @Struct will be introduced in Hibernate 6.2 (currently development phase).

@Embeddable
@Struct(name = "AUDIT_DOMAIN")
public class Audit {

    @NotNull
    private ZonedDateTime createdAt;
    @NotNull
    private UUID createdBy;
    
    private ZonedDateTime updatedAt;
    private UUID updatedBy;
}

See also: https://thorben-janssen.com/composite-type-with-hibernate/

Simon

Softwareentwickler

Zur Übersicht

Standort Hannover

newcubator GmbH
Bödekerstraße 22
30161 Hannover

Standort Dortmund

newcubator GmbH
Westenhellweg 85-89
44137 Dortmund