id: getting-started title: Getting started

A database with the extension, in one command

git clone https://github.com/sajonaro/pg_describe
cd pg_describe
docker compose up -d          # PGPORT=5433 docker compose up -d  if 5432 is taken

The image builds the extension and creates it in the demo database and in template1, so any database you create afterwards has it too.

psql -h localhost -U postgres -d pg_describe_demo \
     -c "SELECT * FROM pg_describe('SELECT 1 AS n')"

Other ways to install — PGXN, building from source, version requirements — are in Installation.

Calling the function

It is a set-returning function: one row per parameter, then one row per result column.

What are this statement’s parameters?

SELECT ord, type_name
FROM pg_describe('UPDATE users SET email = $2 WHERE id = $1')
WHERE kind = 'param';
 ord | type_name
-----+-----------
   1 | integer
   2 | text

$2 appears in the text before $1, and the numbering still follows the parameter rather than the position. Note also that nothing ran: no row was updated.

What does it return?

SELECT ord, name, type_name, result_not_null
FROM pg_describe('INSERT INTO orders (customer_id, total) VALUES ($1, $2)
                  RETURNING id, placed_at')
WHERE kind = 'column';
 ord |   name    |        type_name         | result_not_null
-----+-----------+--------------------------+-----------------
   1 | id        | bigint                   | t
   2 | placed_at | timestamp with time zone | t

INSERT/UPDATE/DELETE describe their RETURNING list. Without one they describe no columns at all, which is how a caller distinguishes “returns rows” from “returns a count”.

Where did each column come from, and can it be NULL?

SELECT ord, name, source_table::text, base_not_null, result_not_null
FROM pg_describe('SELECT o.id, c.email FROM orders o
                  LEFT JOIN customers c ON c.id = o.customer_id');
 ord | name  | source_table | base_not_null | result_not_null
-----+-------+--------------+---------------+-----------------
   1 | id    | orders       | t             | t
   2 | email | customers    | t             | f

This is the case worth understanding before you build anything on top of the function — see Nullability.

Errors

A parse or analysis failure is an ordinary PostgreSQL error, with the caret pointing inside your query rather than at the pg_describe( call:

SELECT name, type_name FROM pg_describe($$SELECT id, emial FROM users$$);
ERROR:  column "emial" does not exist
LINE 1: SELECT id, emial FROM users
                   ^
HINT:  Perhaps you meant to reference the column "users.email".
QUERY:  SELECT id, emial FROM users

Errors abort the surrounding transaction, so a tool describing many queries should send each in its own round trip and collect the failures rather than stopping at the first. That is what pg-describe-gen does.

Generating TypeScript

npm install --save-dev pg-describe-gen
// pg-describe.json
{
  "queries": "queries",
  "output": "src/generated/queries.ts"
}
npx pg-describe-gen

The end-to-end example walks the whole loop — schema, queries, generated types, and a build that fails when the schema drifts.