Skip to content

Types

PostgreSQL supports custom data types including composite types, enums, domains, and ranges.

PostgreSQL Specification

CREATE TYPE Syntax

-- Composite type
CREATE TYPE name AS (
    [ attribute_name data_type [ COLLATE collation ] [, ... ] ]
);

-- Enum type
CREATE TYPE name AS ENUM (
    [ 'label' [, ... ] ]
);

-- Domain type
CREATE DOMAIN name [ AS ] data_type
    [ COLLATE collation ]
    [ DEFAULT expression ]
    [ constraint [ ... ] ]

-- Range type
CREATE TYPE name AS RANGE (
    SUBTYPE = subtype
    [ , SUBTYPE_OPCLASS = subtype_operator_class ]
    [ , COLLATION = collation ]
    [ , CANONICAL = canonical_function ]
    [ , SUBTYPE_DIFF = subtype_diff_function ]
    [ , MULTIRANGE_TYPE_NAME = multirange_type_name ]
);

Reference: PostgreSQL 17 CREATE TYPE

pgdelta Support

✅ Currently Supported

  • CREATE TYPE for composite types
  • CREATE TYPE for enum types
  • CREATE DOMAIN for domain types with base type and constraints
  • DROP TYPE / DROP DOMAIN
  • Type dependency tracking
-- Enum type
CREATE TYPE "public"."user_status" AS ENUM ('active', 'inactive', 'pending');

-- Composite type
CREATE TYPE "public"."address" AS (
    street text,
    city text,
    state text,
    zip_code text
);

-- Domain type
CREATE DOMAIN "public"."positive_int" AS INTEGER CHECK (VALUE > 0);
CREATE DOMAIN "public"."email" AS TEXT CHECK (VALUE ~ '^[^@]+@[^@]+\.[^@]+$');

❌ Not Yet Supported

  • ALTER TYPE operations (planned)
  • Range types
  • Multirange types

🚫 Intentionally Not Supported

  • Base types (requires C code)
  • Shell types (incomplete types)

Usage Examples

Enum Types

target_sql = """
CREATE TYPE user_status AS ENUM ('active', 'inactive', 'pending', 'suspended');

CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    email TEXT NOT NULL,
    status user_status DEFAULT 'pending'
);
"""

Composite Types

target_sql = """
CREATE TYPE address AS (
    street TEXT,
    city TEXT,
    state TEXT,
    zip_code TEXT
);

CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    email TEXT NOT NULL,
    home_address address,
    work_address address
);
"""

Domain Types

target_sql = """
CREATE DOMAIN email AS TEXT
    CHECK (VALUE ~ '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$');

CREATE DOMAIN positive_int AS INTEGER
    CHECK (VALUE > 0);

CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    email_address email NOT NULL,
    age positive_int
);
"""

Implementation Details

Type Models

@dataclass(frozen=True)
class CreateType:
    stable_id: str          # Format: "typ:schema.type_name"
    namespace: str          # Schema name
    typname: str           # Type name
    typtype: str           # Type category (e=enum, c=composite, d=domain)

    # Type-specific fields
    enum_values: list[str] | None = None         # For enum types
    domain_base_type: str | None = None          # For domain types
    domain_constraints: list[str] | None = None  # For domain types
    composite_attributes: list[CompositeAttribute] | None = None  # For composite types

SQL Generation

def generate_create_type_sql(change: CreateType) -> str:
    """Generate CREATE TYPE SQL."""
    return change.type_definition + ";"

Type Categories

Enum Types

-- Status enum
CREATE TYPE order_status AS ENUM ('pending', 'processing', 'shipped', 'delivered');

-- Priority enum
CREATE TYPE priority AS ENUM ('low', 'medium', 'high', 'urgent');

-- Size enum
CREATE TYPE size AS ENUM ('small', 'medium', 'large', 'extra_large');

Use cases: - Status fields with fixed values - Category classifications - Priority levels - Size/grade classifications

Composite Types

-- Address composite
CREATE TYPE address AS (
    street TEXT,
    city TEXT,
    state TEXT,
    zip_code TEXT,
    country TEXT
);

-- Money composite
CREATE TYPE money AS (
    amount DECIMAL(10,2),
    currency TEXT
);

-- Coordinate composite
CREATE TYPE coordinate AS (
    x DOUBLE PRECISION,
    y DOUBLE PRECISION
);

Use cases: - Grouping related fields - Reusable data structures - Complex data modeling

Future Enhancements

Planned Features (v0.2.0)

Range Types

CREATE TYPE price_range AS RANGE (
    SUBTYPE = DECIMAL,
    SUBTYPE_OPCLASS = numeric_ops
);

CREATE TYPE date_range AS RANGE (
    SUBTYPE = DATE,
    SUBTYPE_OPCLASS = date_ops
);

ALTER TYPE Operations

-- Add enum values
ALTER TYPE user_status ADD VALUE 'archived' AFTER 'inactive';

-- Rename enum values
ALTER TYPE user_status RENAME VALUE 'pending' TO 'awaiting_activation';

Best Practices

Type Naming

# Good type names
good_names = [
    "user_status",           # Descriptive enum
    "address",               # Clear composite type
    "priority_level",        # Specific enum
    "geographic_point",      # Descriptive composite
]

# Naming patterns
patterns = {
    "enums": "Use singular nouns describing the category",
    "composites": "Use nouns describing the data structure",
    "domains": "Use descriptive names with constraints implied"
}

Type Design

# Enum design guidelines
enum_guidelines = {
    "stable_values": "Avoid frequently changing enum values",
    "logical_order": "Order values logically (e.g., priority levels)",
    "future_expansion": "Consider future values when designing",
    "avoid_numbers": "Use descriptive labels, not numeric codes"
}

# Composite type guidelines
composite_guidelines = {
    "related_fields": "Group truly related fields together",
    "avoid_large_types": "Keep composite types reasonably sized",
    "consider_normalization": "Sometimes separate tables are better",
    "null_handling": "Consider NULL behavior in composite fields"
}

Performance Considerations

# Type performance impact
performance_impact = {
    "enums": "Very efficient, stored as integers internally",
    "composites": "Some overhead compared to separate columns",
    "domains": "Constraint checking on every value",
    "ranges": "Efficient for range queries and operations"
}

# Optimization tips
optimization_tips = {
    "enum_ordering": "Order enum values by frequency of use",
    "composite_indexing": "Index individual fields, not entire composite",
    "domain_constraints": "Keep domain constraints simple and fast"
}

Common Patterns

# Status enum pattern
status_pattern = """
CREATE TYPE entity_status AS ENUM ('active', 'inactive', 'pending', 'archived');

CREATE TABLE entities (
    id SERIAL PRIMARY KEY,
    name TEXT NOT NULL,
    status entity_status DEFAULT 'pending',
    created_at TIMESTAMP DEFAULT NOW()
);
"""

# Address composite pattern
address_pattern = """
CREATE TYPE address AS (
    street TEXT,
    city TEXT,
    state TEXT,
    zip_code TEXT,
    country TEXT DEFAULT 'USA'
);

CREATE TABLE customers (
    id SERIAL PRIMARY KEY,
    name TEXT NOT NULL,
    billing_address address,
    shipping_address address
);
"""