pgmi Session API Reference#

Core model: pgmi doesn’t interpret your SQL — it loads your files into PostgreSQL session tables and hands control to deploy.sql, which decides what to do with them.

The session API surface: Go writes internal pgmi* temp tables, your deploy.sql reads the stable pgmi_*_view views and test functions

How pgmi Actually Works#

When you run pgmi deploy ./myproject, pgmi connects, prepares the session objects shown in the diagram above (with --verbose it also runs SET client_min_messages = 'debug', enabling RAISE DEBUG output), and then executes your deploy.sql — which queries the views and runs files directly:

FOR v_file IN (SELECT * FROM pgmi_plan_view ORDER BY execution_order)
LOOP
    EXECUTE v_file.content;
END LOOP;

The key insight: deploy.sql is the deployment script. It queries pgmi_plan_view and uses EXECUTE to run files directly. You control the deployment logic — transactions, ordering, conditionals, error handling.

Connection requirement: Because everything depends on pg_temp tables surviving for the entire session, pgmi requires a direct connection or a pooler in session mode. Transaction-mode poolers (PgBouncer, RDS Proxy, etc.) will silently break deployments by reassigning the backend connection. See Production Guide — Connection Requirements .


Two-Tier API Design#

pgmi uses a two-tier naming convention for session objects:

TierNamingPurposeExample
Internal_pgmi_* prefixUsed by pgmi Go code_pgmi_source, _pgmi_parameter
Public*_view suffixStable API for deploy.sqlpgmi_source_view, pgmi_plan_view

Why this matters:

  • Internal tables may change between versions
  • Public views provide a stable contract for deploy.sql
  • Always use views (pgmi_source_view, pgmi_plan_view) in your SQL

Public Interface (Stable API)#

These views and functions are the stable API for deploy.sql. Use these instead of querying internal tables directly.

Which View Should I Use?#

Use CaseViewWhy
Deploying filespgmi_plan_viewPre-sorted by execution order, includes metadata
Introspection/debuggingpgmi_source_viewRaw file access, all columns available
Custom orderingpgmi_source_viewApply your own ORDER BY logic
Metadata-driven deploymentpgmi_plan_viewRespects <pgmi-meta> sort keys

Rule of thumb: Use pgmi_plan_view for deployment loops. Use pgmi_source_view when you need raw access or custom filtering beyond what the plan provides.

File Access#

pgmi_source_view#

All project source files (excludes the root deploy.sql and __test__/ files).

This view provides direct access to all discovered files. For most use cases, prefer pgmi_plan_view which adds execution ordering via metadata.

Only the root deploy.sql is excluded. pgmi executes that one itself, so it never appears here. A deploy.sql anywhere else — examples/deploy.sql, sub-project/deploy.sql — loads like any other file, with is_sql_file = true, and a plan loop will execute it. If you keep nested projects inside a project, filter them out by path. See what gets loaded .

ColumnTypeDescription
pathtextNormalized path (always starts with ./)
nametextFilename without directory
directorytextDirectory path ending with /
extensiontextFile extension (e.g., .sql)
depthintegerNesting level (0 = root)
contenttextFull file content
size_bytesbigintFile size in bytes
checksumtextSHA-256 of original content
pgmi_checksumtextSHA-256 of normalized content
path_partstext[]Path split by /
is_sql_filebooleanTrue for .sql, .ddl, .dml, .dql, .dcl, .psql, .pgsql, .plpgsql (case-insensitive). The extension decides, not the directory — 001.sql.bak is false
is_test_filebooleanAlways false in this view — files matching __test__/ are routed to pgmi_test_source_view instead
parent_folder_nametextImmediate parent directory name
-- List all SQL files in migrations/
SELECT path, name FROM pg_temp.pgmi_source_view
WHERE directory = './migrations/' AND is_sql_file
ORDER BY path;

pgmi_plan_view#

Pre-computed execution plan with metadata.

This view joins _pgmi_source with _pgmi_source_metadata and provides a clean interface for file access:

ColumnTypeDescription
pathtextFile path
contenttextFile content
checksumtextNormalized checksum (≠ pgmi_source_view.checksum which is raw) — see Two checksums
generic_iduuidAuto-generated UUID from path
iduuidExplicit ID from <pgmi-meta> (NULL if none)
idempotentbooleanWhether file can be re-executed (defaults to true for files without metadata)
descriptiontextFrom <pgmi-meta> (defaults to '' for files without metadata, never NULL)
sort_keytextExecution ordering key
execution_orderbigintSequential execution number

This view holds every loaded file, not only SQL. README.md, pgmi.yaml and editor leftovers (001.sql~, .bak, .orig) are all in it, and a directory filter does not exclude them — a backup saved beside the migration you were editing matches './migrations/%' and executes as a migration, with the deploy still exiting 0. is_sql_file lives on pgmi_source_view, so every execute loop below joins back to it. That join is the guard, not decoration.

Recommended usage:

-- Deploy files in metadata-driven order using direct execution
DO $$
DECLARE v_file RECORD;
BEGIN
    FOR v_file IN (
        SELECT p.path, p.content
        FROM pg_temp.pgmi_plan_view p
        JOIN pg_temp.pgmi_source_view s ON s.path = p.path
        WHERE s.is_sql_file AND p.path LIKE './migrations/%'
        ORDER BY p.execution_order
    ) LOOP
        RAISE NOTICE 'Executing: %', v_file.path;
        EXECUTE v_file.content;
    END LOOP;
END $$;

Two checksums, and which to track against#

pgmi_source_view carries two checksum columns per file:

ColumnAlgorithm
checksumSHA-256 of the raw file bytes
pgmi_checksumSHA-256 after normalization: comments stripped, whitespace collapsed, and case folded only where SQL ignores it — keywords and unquoted identifiers. Literals, quoted identifiers and dollar-quoted bodies keep their case, so "Users"/"users" and 'Production'/'production' stay distinct

The naming trap: pgmi_plan_view.checksum is the normalized checksum, not the raw one. The column name is the same, the value is different. Code that tracks pgmi_source_view.checksum into a history table and later compares against pgmi_plan_view.checksum will never match.

Which to use:

  • Track against pgmi_checksum (or pgmi_plan_view.checksum) when you want reformatting, re-indentation, and comment edits to be free — a touched-up file won’t re-execute.
  • Track against pgmi_source_view.checksum when you need byte-exact provenance — any edit, including a comment, triggers re-execution.

Honest cost: a change that touches only comments is invisible to pgmi_checksum. If your comments carry operational meaning (e.g., planner hints via pg_hint_plan, or <pgmi-meta> blocks that don’t affect the normalized body), the normalized checksum won’t catch the edit.

See Highlights §5 and Checksum-based change detection for usage examples.

Parameters#

CLI parameters (passed via --param key=value) are accessible in multiple ways. This section consolidates all parameter access patterns.

pgmi automatically sets session variables with the pgmi. prefix. This is the simplest and most common approach:

-- Get parameter with default (the true argument prevents errors if not set)
v_env := COALESCE(current_setting('pgmi.env', true), 'development');

-- In conditional logic
IF COALESCE(current_setting('pgmi.env', true), 'dev') = 'production' THEN
    -- Production-specific logic
END IF;

-- Check if parameter was provided
IF current_setting('pgmi.feature_flag', true) IS NOT NULL THEN
    -- Parameter was explicitly set
END IF;

Important: Always pass true as the second argument to current_setting(). This returns NULL instead of raising an error when the variable is not set.

Method 2: pgmi_parameter_view (Introspection)#

For iterating over parameters or building dynamic logic:

ColumnTypeDescription
keytextParameter name, always lower-cased on load--param apiVersion=2 is stored as apiversion
valuetextParameter value (always text, cast as needed)
typetextDeclared type hint (text, int, boolean, etc.)
requiredbooleanWhether parameter was marked required
default_valuetextDefault value if not provided
descriptiontextHuman-readable description

Compare lower-cased. key mirrors the session variable pgmi set, and PostgreSQL’s GUC namespace is case-insensitive — so the two access methods disagree for a mixed-case parameter, and the view disagrees silently:

-- with --param apiVersion=2
current_setting('pgmi.apiVersion', true)                             -- '2'
SELECT value FROM pgmi_parameter_view WHERE key = 'apiVersion'       -- no row
SELECT value FROM pgmi_parameter_view WHERE key = 'apiversion'       -- '2'
SELECT value FROM pgmi_parameter_view WHERE key = lower('apiVersion') -- '2', safe either way

Storing the original case instead would make key name something the session variable does not, which trades a visible mismatch for an invisible one.

-- List all parameters
SELECT key, value, description FROM pg_temp.pgmi_parameter_view;

-- Iterate over parameters dynamically.
-- Redact secret-like keys by default — values may be passwords, tokens, etc.
DO $$
DECLARE
    v_param RECORD;
BEGIN
    FOR v_param IN SELECT key, value FROM pg_temp.pgmi_parameter_view LOOP
        RAISE NOTICE 'Parameter: % = %',
            v_param.key,
            CASE WHEN lower(v_param.key) ~ '(password|secret|token|key|credential|auth)'
                 THEN '********' ELSE v_param.value END;
    END LOOP;
END $$;

Method 3: deployment_setting() Helper (Advanced Template Only)#

Scope: advanced template only. This is SQL that pgmi init --template advanced copied into your project, not behaviour of the pgmi binary.

The advanced template provides a helper function with error handling:

-- Get required parameter (raises exception if missing)
v_admin_role := pg_temp.deployment_setting('database_admin_role');

-- Get optional parameter (returns NULL if missing)
v_optional := pg_temp.deployment_setting('optional_key', false);

Note: This function uses a deployment. prefix internally and normalizes key names. It’s defined in the advanced template’s deploy.sql, not in pgmi core.

Parameter Precedence#

Parameters merge from multiple sources (later wins):

pgmi.yaml params < --params-file < --param CLI flag

See Configuration Reference for details.

Type Coercion#

All parameter values are stored as text. Cast them as needed:

-- Boolean
v_enabled := COALESCE(current_setting('pgmi.feature_enabled', true), 'false')::boolean;

-- Integer
v_limit := COALESCE(current_setting('pgmi.max_rows', true), '100')::int;

-- Timestamp
v_cutoff := COALESCE(current_setting('pgmi.since', true), '2024-01-01')::timestamp;

Template Responsibility#

pgmi core provides raw parameter storage. Templates handle:

  • Declaring expected parameters (advanced template uses session.xml)
  • Validating required parameters
  • Providing default values
  • Type validation and coercion

Direct Execution Pattern#

pgmi uses a direct execution model: your deploy.sql queries pgmi_plan_view and uses EXECUTE to run files. This gives you full control over transaction boundaries, execution order, and conditional logic.

Basic pattern:

DO $$
DECLARE v_file RECORD;
BEGIN
    -- Transaction control is in your hands
    FOR v_file IN (
        SELECT p.path, p.content
        FROM pg_temp.pgmi_plan_view p
        JOIN pg_temp.pgmi_source_view s ON s.path = p.path
        WHERE s.is_sql_file AND p.path LIKE './schemas/%'
        ORDER BY p.execution_order
    ) LOOP
        RAISE NOTICE 'Executing: %', v_file.path;
        EXECUTE v_file.content;
    END LOOP;
END $$;

With explicit transaction boundaries:

DO $$
DECLARE v_file RECORD;
BEGIN
    -- Phase 1: Schema changes in one transaction
    BEGIN
        FOR v_file IN (
            SELECT p.path, p.content
            FROM pg_temp.pgmi_plan_view p
            JOIN pg_temp.pgmi_source_view s ON s.path = p.path
            WHERE s.is_sql_file AND p.path LIKE './schemas/%'
            ORDER BY p.execution_order
        ) LOOP
            EXECUTE v_file.content;
        END LOOP;
    EXCEPTION WHEN OTHERS THEN
        RAISE EXCEPTION 'Schema phase failed: %', SQLERRM;
    END;

    -- Phase 2: Migrations
    FOR v_file IN (
        SELECT p.path, p.content
        FROM pg_temp.pgmi_plan_view p
        JOIN pg_temp.pgmi_source_view s ON s.path = p.path
        WHERE s.is_sql_file AND p.path LIKE './migrations/%'
        ORDER BY p.execution_order
    ) LOOP
        EXECUTE v_file.content;
    END LOOP;
END $$;

Conditional execution:

DO $$
DECLARE
    v_file RECORD;
    v_env TEXT := COALESCE(current_setting('pgmi.env', true), 'development');
BEGIN
    FOR v_file IN (
        SELECT p.path, p.content
        FROM pg_temp.pgmi_plan_view p
        JOIN pg_temp.pgmi_source_view s ON s.path = p.path
        WHERE s.is_sql_file AND p.path LIKE './migrations/%'
        ORDER BY p.execution_order
    ) LOOP
        EXECUTE v_file.content;
    END LOOP;

    -- Only seed data in development
    IF v_env = 'development' THEN
        FOR v_file IN (
            SELECT p.path, p.content
            FROM pg_temp.pgmi_plan_view p
            JOIN pg_temp.pgmi_source_view s ON s.path = p.path
            WHERE s.is_sql_file AND p.path LIKE './seeds/%'
            ORDER BY p.execution_order
        ) LOOP
            EXECUTE v_file.content;
        END LOOP;
    END IF;
END $$;

Metadata#

pgmi_source_metadata_view#

Parsed <pgmi-meta> XML blocks from SQL files.

Files without metadata are not in this view (use pgmi_plan_view which handles fallbacks).

ColumnTypeDescription
pathtextFile path (references pgmi_source_view.path)
iduuidExplicit script UUID from metadata
idempotentbooleanWhether script can be re-executed safely
sort_keystext[]Array of execution ordering keys
descriptiontextHuman-readable description
-- List files with metadata
SELECT path, id, idempotent, sort_keys
FROM pg_temp.pgmi_source_metadata_view;

-- Find non-idempotent migrations
SELECT path FROM pg_temp.pgmi_source_metadata_view
WHERE NOT idempotent;

See Metadata Guide for syntax and usage patterns.

Test Views#

pgmi_test_source_view#

Test file content from __test__/ or __tests__/ directories.

ColumnTypeDescription
pathtextFull path to test file
directorytextParent test directory (ending with /)
filenametextFilename without directory
contenttextFull file content
is_fixturebooleanTrue for _setup.sql files
-- List all test files
SELECT path, is_fixture FROM pg_temp.pgmi_test_source_view
ORDER BY directory, filename;

-- Get fixture files only
SELECT path FROM pg_temp.pgmi_test_source_view
WHERE is_fixture;

pgmi_test_directory_view#

Hierarchical test directory structure.

ColumnTypeDescription
pathtextDirectory path (ending with /)
parent_pathtextParent directory path (NULL for root)
depthintegerNesting level (0 = root __test__/)
-- See test directory hierarchy
SELECT path, parent_path, depth
FROM pg_temp.pgmi_test_directory_view
ORDER BY depth, path;

-- Find nested test directories
SELECT path FROM pg_temp.pgmi_test_directory_view
WHERE depth > 0;

Testing#

CALL pgmi_test() Preprocessor Macro#

Executes tests with automatic savepoint isolation.

The CALL pgmi_test() is a preprocessor macro that Go expands before sending SQL to PostgreSQL:

-- Run all tests with default callback
CALL pgmi_test();

-- Run tests matching a pattern (POSIX regex)
CALL pgmi_test('.*/integration/.*');
CALL pgmi_test('.*_critical\.sql$');

-- Run tests with custom callback function
CALL pgmi_test('.*/auth/.*', 'pg_temp.my_custom_callback');

Automatic behavior:

  • Creates SAVEPOINTs before each _setup.sql
  • Executes tests in lexicographic order
  • Rolls back transactional test changes to the SAVEPOINT; sequence advances and external effects are not rolled back
  • Includes ancestor _setup.sql files needed by matching tests
  • Calls pgmi_test_generate() internally to produce inline SQL

pgmi_test_plan(pattern) Function#

Returns the test execution plan as a table (for introspection).

This is a TABLE-returning function (not a view). Files from __test__/ or __tests__/ directories are automatically organized into a depth-first execution plan with fixture/test/teardown lifecycle.

ColumnTypeDescription
ordinalintegerSequential execution order (1-based)
step_typetext'fixture', 'test', or 'teardown'
script_pathtextPath to test file (NULL for teardown)
directorytextTest directory containing the script
depthintegerNesting level (0 = root __test__/)
-- See what tests would run
SELECT * FROM pg_temp.pgmi_test_plan();

-- Filter by pattern (POSIX regex on script_path)
SELECT * FROM pg_temp.pgmi_test_plan('.*/auth/.*');

Test execution emits notices:

  • NOTICE: [pgmi] Test suite started
  • NOTICE: [pgmi] Fixture: ./path/to/_setup.sql
  • NOTICE: [pgmi] Test: ./path/to/test_example.sql
  • NOTICE: [pgmi] Test suite completed (N steps)

With --verbose, DEBUG messages show rollback and teardown events ([pgmi] Rollback: ..., [pgmi] Teardown: ...).

pgmi_test_generate(pattern, callback) Function#

Generates the SQL code for pgmi_test() macro expansion.

This is an internal function called by the Go preprocessor. It returns the complete SQL text that replaces the CALL pgmi_test() macro.

-- See what SQL the macro generates (for debugging)
SELECT pg_temp.pgmi_test_generate();
SELECT pg_temp.pgmi_test_generate('.*/auth/.*', 'pg_temp.my_callback');

Critical implementation detail: The generated SQL uses top-level SAVEPOINT commands, not PL/pgSQL savepoints. PostgreSQL’s PL/pgSQL does not support SAVEPOINT, ROLLBACK TO SAVEPOINT, or RELEASE SAVEPOINT commands directly — they must be issued as top-level SQL statements.

The generated structure looks like:

SAVEPOINT __pgmi_d1__;              -- Directory savepoint (top-level SQL)
DO $$ ... EXECUTE fixture ... $$;   -- Fixture content via EXECUTE
SAVEPOINT __pgmi_t2__;              -- Per-directory test savepoint (top-level SQL)
DO $$ ... EXECUTE test ... $$;      -- Test content via EXECUTE
ROLLBACK TO SAVEPOINT __pgmi_t2__;  -- Undoes test side effects
DO $$ ... EXECUTE test2 ... $$;     -- Next test in same directory
ROLLBACK TO SAVEPOINT __pgmi_t2__;  -- Undoes test2 side effects
ROLLBACK TO SAVEPOINT __pgmi_d1__;  -- Teardown: undoes fixture
RELEASE SAVEPOINT __pgmi_d1__;      -- Clean up savepoint

This is why CALL pgmi_test() must appear at the top level of your deploy.sql, not inside a DO block.

Custom Callbacks#

The second argument to pgmi_test() is the name of a callback function that receives lifecycle events. Your function must accept a single pg_temp.pgmi_test_event argument and return void:

The name must be schema-qualified. pgmi interpolates it verbatim into SELECT <name>(ROW(...)), and PostgreSQL never searches pg_temp for function names however the search_path is set — so 'my_callback' fails with ERROR: function my_callback(pgmi_test_event) does not exist (SQLSTATE 42883) while 'pg_temp.my_callback' resolves.

CREATE FUNCTION pg_temp.my_callback(e pg_temp.pgmi_test_event)
RETURNS void LANGUAGE plpgsql AS $$
BEGIN
    CASE e.event
        WHEN 'suite_start'    THEN RAISE NOTICE 'Starting test suite';
        WHEN 'suite_end'      THEN RAISE NOTICE 'Completed % steps', e.ordinal;
        WHEN 'test_start'     THEN RAISE NOTICE 'Running: %', e.path;
        WHEN 'test_end'       THEN RAISE NOTICE 'Passed: %', e.path;
        ELSE NULL;
    END CASE;
END $$;

pgmi_test_event fields:

FieldTypeDescription
eventtextEvent name (see below)
pathtextScript path (NULL for suite and teardown events)
directorytextTest directory containing the script
depthintegerNesting level (0 = root __test__/)
ordinalintegerExecution order (1-based)
contextjsonbExtensible payload for custom data

Event types: suite_start, fixture_start, fixture_end, test_start, test_end, rollback, teardown_start, teardown_end, suite_end.

The default callback (pgmi_test_callback) emits NOTICE for fixtures and tests, DEBUG for rollback and teardown. You can call it from your custom callback for events you don’t want to handle specially.

pgmi_persist_test_plan(schema, pattern) Function#

Exports the test plan to a permanent table for external tooling.

-- Create a permanent copy of the test plan
SELECT pg_temp.pgmi_persist_test_plan('public', NULL);
-- Creates: public.pgmi_test_plan
ParameterTypeDescription
schematextTarget schema for the snapshot table
patterntextOptional POSIX regex filter (NULL = all tests)

This is useful for CI/CD pipelines that need to inspect the test plan before running, or for generating test reports.


The Direct Execution Model (Critical Concept)#

This is the most important thing to understand about pgmi: pgmi’s setup phase creates the temp tables holding your files; the execution phase is entirely your deploy.sql querying those views and running files with EXECUTE.

Your deploy.sql has full control. You query pgmi_plan_view, loop through files, and use EXECUTE to run them. Transaction boundaries, error handling, execution order—all in your hands.

The Basic Pattern#

DO $$
DECLARE v_file RECORD;
BEGIN
    FOR v_file IN (
        SELECT p.path, p.content
        FROM pg_temp.pgmi_plan_view p
        JOIN pg_temp.pgmi_source_view s ON s.path = p.path
        WHERE s.is_sql_file AND p.path LIKE './migrations/%'
        ORDER BY p.execution_order
    ) LOOP
        RAISE NOTICE 'Executing: %', v_file.path;
        EXECUTE v_file.content;
    END LOOP;
END $$;

What happens:

  1. pgmi loads your files into internal tables and creates public views
  2. Your deploy.sql queries pgmi_plan_view (or pgmi_source_view) and executes files directly with EXECUTE

Common Patterns#

Phased Deployment#

DO $$
DECLARE v_file RECORD;
BEGIN
    -- Phase 1: Schema changes
    RAISE NOTICE '=== Phase 1: Schema ===';
    FOR v_file IN (
        SELECT p.path, p.content
        FROM pg_temp.pgmi_plan_view p
        JOIN pg_temp.pgmi_source_view s ON s.path = p.path
        WHERE s.is_sql_file AND p.path LIKE './schemas/%'
        ORDER BY p.execution_order
    ) LOOP
        RAISE NOTICE 'Executing: %', v_file.path;
        EXECUTE v_file.content;
    END LOOP;

    -- Phase 2: Migrations
    RAISE NOTICE '=== Phase 2: Migrations ===';
    FOR v_file IN (
        SELECT p.path, p.content
        FROM pg_temp.pgmi_plan_view p
        JOIN pg_temp.pgmi_source_view s ON s.path = p.path
        WHERE s.is_sql_file AND p.path LIKE './migrations/%'
        ORDER BY p.execution_order
    ) LOOP
        RAISE NOTICE 'Executing: %', v_file.path;
        EXECUTE v_file.content;
    END LOOP;
END $$;

Conditional Deployment#

CALL pgmi_test() is a preprocessor macro that expands to top-level SQL (including SAVEPOINT commands), so it must appear at the top level of deploy.sql — never inside a DO block. Structure your deploy.sql with the DO block for migrations and CALL pgmi_test() as a separate top-level statement:

BEGIN;

-- Phase 1: Migrations and seeds (inside DO block)
DO $$
DECLARE
    v_file RECORD;
    v_env TEXT := COALESCE(current_setting('pgmi.env', true), 'development');
BEGIN
    -- Always run migrations
    FOR v_file IN (
        SELECT p.path, p.content
        FROM pg_temp.pgmi_plan_view p
        JOIN pg_temp.pgmi_source_view s ON s.path = p.path
        WHERE s.is_sql_file AND p.path LIKE './migrations/%'
        ORDER BY p.execution_order
    ) LOOP
        EXECUTE v_file.content;
    END LOOP;

    -- Only seed data in development
    IF v_env = 'development' THEN
        FOR v_file IN (
            SELECT p.path, p.content
            FROM pg_temp.pgmi_plan_view p
            JOIN pg_temp.pgmi_source_view s ON s.path = p.path
            WHERE s.is_sql_file AND p.path LIKE './seeds/%'
            ORDER BY p.execution_order
        ) LOOP
            EXECUTE v_file.content;
        END LOOP;
    END IF;
END $$;

-- Phase 2: Tests (top-level — expands to SAVEPOINT commands)
CALL pgmi_test();

COMMIT;

Dynamic File Selection#

-- Deploy SQL files from a specific directory
DO $$
DECLARE v_file RECORD;
BEGIN
    FOR v_file IN (
        SELECT p.path, p.content
        FROM pg_temp.pgmi_plan_view p
        JOIN pg_temp.pgmi_source_view s ON s.path = p.path
        WHERE s.is_sql_file AND p.path LIKE './migrations/v2/%'
        ORDER BY p.execution_order
    ) LOOP
        EXECUTE v_file.content;
    END LOOP;
END $$;

-- Or use POSIX regex for complex patterns
DO $$
DECLARE v_file RECORD;
BEGIN
    FOR v_file IN (
        SELECT path, content FROM pg_temp.pgmi_source_view
        WHERE path ~ '.*/v2/.*' AND is_sql_file
        ORDER BY path
    ) LOOP
        EXECUTE v_file.content;
    END LOOP;
END $$;

Test Isolation with Savepoints#

BEGIN;

DO $$
DECLARE v_file RECORD;
BEGIN
    -- Deploy your schema
    FOR v_file IN (
        SELECT p.path, p.content
        FROM pg_temp.pgmi_plan_view p
        JOIN pg_temp.pgmi_source_view s ON s.path = p.path
        WHERE s.is_sql_file AND p.path LIKE './schemas/%'
        ORDER BY p.execution_order
    ) LOOP
        EXECUTE v_file.content;
    END LOOP;

END $$;

-- Run tests (preprocessor macro handles savepoint isolation)
CALL pgmi_test();

COMMIT;

Internal Tables (Implementation Details)#

These tables are the underlying storage for the session API. Users should use the public views above rather than querying these internal tables directly.

Naming convention: Internal tables use underscore prefix (_pgmi_*)

pg_temp._pgmi_source#

Raw file storage.

ColumnTypeDescription
pathtextNormalized path (always starts with ./)
nametextFilename without directory
directorytextDirectory path ending with /
extensiontextFile extension (e.g., .sql)
depthintegerNesting level (0 = root)
contenttextFull file content
size_bytesbigintFile size
checksumtextSHA-256 of original content
pgmi_checksumtextSHA-256 of normalized content (for idempotency)
path_partstext[]Path split by /
is_sql_filebooleanTrue for SQL file extensions
is_test_filebooleanAlways false — a CHECK constraint routes test files to _pgmi_test_source instead
parent_folder_nametextImmediate parent directory name

pg_temp._pgmi_parameter#

Raw parameter storage.

ColumnTypeDescription
keytextParameter name
valuetextParameter value
typetextDeclared type hint
requiredbooleanWhether parameter is required
default_valuetextDefault if not provided
descriptiontextHuman-readable description

pg_temp._pgmi_source_metadata#

Parsed XML metadata from <pgmi-meta> blocks.

ColumnTypeDescription
pathtextFile path (FK to _pgmi_source.path)
iduuidExplicit script UUID
idempotentbooleanWhether script can be re-executed
sort_keystext[]Execution ordering keys (defaults to {})
descriptiontextHuman-readable description

pg_temp._pgmi_test_directory#

Test directory hierarchy.

ColumnTypeDescription
pathtextDirectory path (ending with /)
parent_pathtextParent directory (NULL for root)
depthintegerNesting level

pg_temp._pgmi_test_source#

Test file content.

ColumnTypeDescription
pathtextFull file path
directorytextParent test directory (FK to _pgmi_test_directory.path)
filenametextFilename only
contenttextFull file content
is_fixturebooleanTrue for _setup.sql files

Introspection Examples#

See What Files Are Loaded#

-- Use the view for clean access
SELECT path, execution_order, idempotent
FROM pg_temp.pgmi_plan_view
ORDER BY execution_order;

See What Parameters Are Available#

SELECT key, value, type, required, default_value, description
FROM pg_temp.pgmi_parameter_view;

Preview the Execution Plan#

-- See files in execution order
SELECT execution_order, path, left(content, 80) AS preview
FROM pg_temp.pgmi_plan_view
ORDER BY execution_order;

See Available Tests#

SELECT step_type, script_path
FROM pg_temp.pgmi_test_plan()
ORDER BY ordinal;

Philosophy: Why This Design?#

pgmi is not a migration framework. It’s an execution fabric.

Traditional Migration Toolpgmi
Decides execution orderYour SQL queries and filters the plan
Controls transactionsYour SQL controls transactions
Provides retry logicYour SQL provides retry logic (EXCEPTION blocks)
Has migration history tableYou implement tracking however you want
Black boxTransparent — session state is queryable

pgmi’s job is to:

  1. Connect to PostgreSQL
  2. Load your files into session tables
  3. Run your deploy.sql

Your job is to:

  1. Query pgmi_plan_view to find your files
  2. Use EXECUTE to run them directly
  3. Control transaction boundaries and error handling
  4. Decide which files run in what order

The result: Full control over deployment logic. pgmi handles infrastructure (file loading, metadata parsing, preprocessing); your SQL handles everything else. PostgreSQL is the deployment engine.


See Also#

  • Testing Guide — Database testing with savepoint isolation and deploy gates
  • Metadata Guide — Script tracking and execution ordering
  • MCP Integration — Model Context Protocol for AI assistants (advanced template)