How pgrust Manages User-Defined Types and Domains: A Deep Dive into the Type Cache System

pgrust represents all PostgreSQL user-defined types—including composite, enum, range, and domain types—through a centralized type-cache abstraction (TypeCacheEntry) that resolves metadata via the typcache seam crate, while domain-specific constraint validation and I/O operations are handled in a dedicated module that wraps base type functions with constraint checking.

The malisper/pgrust project reimplements PostgreSQL's core server logic in Rust, including a complete type system for handling user-defined objects. Understanding how pgrust manages user-defined types and domains requires examining the typcache subsystem and the specialized domain handling routines that enforce constraints while delegating to underlying base types.

The Type Cache Architecture

At the heart of pgrust's type system lies the typcache subsystem, which eliminates repeated catalog lookups by caching metadata for every user-defined type.

TypeCacheEntry and Metadata Resolution

When the engine encounters a user-defined type, it invokes typcache_seams::lookup_type_cache to retrieve a TypeCacheEntry structure. This cache entry contains critical metadata including the type OID, base type references for domains, associated operators, and function pointers. According to the backend-utils-cache-typcache seam crate implementation, this abstraction decouples the core logic from direct PostgreSQL catalog access while preserving semantic compatibility.

Composite Types and TupleDesc

For user-defined composite types (row types), the typcache provides a TupleDesc (tuple descriptor) that describes the field structure. The function lookup_rowtype_tupdesc returns this descriptor when given a composite type OID. This same mechanism applies to domain-over-composite types, where the domain wrapper preserves the underlying composite structure while adding constraints.

Domain Types: Constraints and Validation

Domain types in pgrust wrap existing base types—whether built-in or user-defined—and layer NOT NULL and CHECK constraints on top. The implementation lives in crates/backend/utils/adt/misc2/src/domains.rs, which provides four distinct entry points: domain_in and domain_recv for input conversion, plus domain_check and domain_check_safe for validation.

Base Type Resolution and I/O Functions

Domain input processing begins with domain_get_base_input_info, which queries the typcache to obtain the underlying type's I/O functions. The flow proceeds as follows:

  1. Base type lookup – The system calls typcache_seams::domain_get_base_input_info to retrieve the base type's input function OID and type information.
  2. Input conversion – The engine invokes fmgr_seams::input_function_call (or oid_receive_function_call for binary input) to convert the raw input string into a Datum using the base type's logic.

This delegation ensures that domains inherit all base type parsing behavior while remaining positioned to validate the result.

Constraint Checking (NOT NULL and CHECK)

After obtaining the base value, pgrust validates it against domain constraints through typcache_seams::domain_check_input. This function checks:

  • NOT NULL constraints – Rejecting null values for non-nullable domains
  • CHECK constraints – Evaluating domain-specific expression constraints

The validation occurs in crates/backend/utils/adt/misc2/src/domains.rs, where the domain_check_input seam bridges to the constraint enforcement logic.

Hard vs. Soft Error Handling

pgrust distinguishes between hard errors (fatal exceptions) and soft errors (captured diagnostics) through the SoftErrorContext mechanism:

  • Hard error path – Used by domain_recv and standard domain_in calls, allowing malformed input to raise a fatal PgError that aborts the operation.
  • Soft error path – Available via domain_check_safe, which accepts a mutable SoftErrorContext reference and returns Ok(Datum::null()) for constraint violations instead of panicking.

This dual-mode handling supports both strict SQL semantics and incremental validation scenarios.

Working with Domains in Practice

The following examples demonstrate how to interact with user-defined domain types in pgrust code.

Reading Text Input into a Domain

This example converts a text string into a domain type using hard error semantics:

use mcx::Mcx;
use types_error::PgResult;
use types_tuple::heaptuple::Datum;

/// Read a text value into a user-defined domain (e.g., positive_int)
fn read_domain_value(mcx: Mcx<'_>, txt: &str, domain_oid: u32) -> PgResult<Datum<'_>> {
    // Hard error on invalid input - no soft error context provided
    pgrust::crates::backend::utils::adt::misc2::domains::domain_in(
        mcx,
        Some(txt),
        domain_oid,
        -1,  // typmod
        None, // No soft error sink
    )
}

Soft Validation of Existing Datums

This example validates an existing datum against domain constraints without raising fatal errors:

use mcx::Mcx;
use types_error::{PgResult, SoftErrorContext};
use types_tuple::heaptuple::Datum;

/// Validate a datum against domain constraints, capturing errors softly
fn validate_domain_soft(
    mcx: Mcx<'_>,
    datum: &Datum<'_>,
    is_null: bool,
    domain_oid: u32,
) -> PgResult<bool> {
    let mut soft_ctx = SoftErrorContext::default();
    pgrust::crates::backend::utils::adt::misc2::domains::domain_check_safe(
        mcx,
        datum,
        is_null,
        domain_oid,
        Some(&mut soft_ctx),
    )
}

Summary

  • Type caching – pgrust uses TypeCacheEntry structures via the typcache_seams crate to cache metadata for all user-defined types, eliminating repeated catalog lookups.
  • Domain delegation – Domain input functions in crates/backend/utils/adt/misc2/src/domains.rs delegate to base type I/O functions through fmgr_seams, then apply constraint checks.
  • Constraint enforcement – domain_check_input validates NOT NULL and CHECK constraints, supporting both hard-error and soft-error execution modes.
  • Composite support – lookup_rowtype_tupdesc provides TupleDesc structures for composite types and domain-over-composite types.
  • Seam architecture – The implementation relies on seam crates (typcache_seams, fmgr_seams) to maintain pure-Rust code while preserving PostgreSQL semantics.

Frequently Asked Questions

How does pgrust handle user-defined composite types?

pgrust handles composite types through the typcache subsystem by calling lookup_rowtype_tupdesc, which returns a TupleDesc containing field names and types. This descriptor is cached in the TypeCacheEntry for the composite type OID, allowing efficient repeated access to the row structure without re-parsing system catalogs.

What is the difference between hard and soft error contexts in domain validation?

Hard error contexts (used by domain_in and domain_recv) immediately raise a PgError when constraint validation fails, aborting the current operation. Soft error contexts (used by domain_check_safe) capture validation failures in a SoftErrorContext structure and return a null datum, allowing the caller to handle constraint violations programmatically without transaction abort.

Where does pgrust store metadata for user-defined types?

Metadata resides in the typcache (type cache), implemented in the backend-utils-cache-typcache seam crate. The lookup_type_cache function retrieves TypeCacheEntry structures containing OIDs, base type references, operator information, and constraint details for domains, all cached to avoid repeated system catalog queries.

How does domain type inheritance work in pgrust?

Domains inherit their underlying base type's I/O behavior through delegation. When processing domain input, domain_get_base_input_info retrieves the base type's input function from the typcache, and input_function_call executes that function. After obtaining the base value, pgrust applies domain-specific constraints, effectively layering behavioral restrictions on top of the inherited type's characteristics.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →