mullinsms opened a new issue, #3971:
URL: https://github.com/apache/arrow-adbc/issues/3971
### What feature or improvement would you like to see?
## Summary
The ADBC PostgreSQL driver currently supports discovery of databases,
schemas, tables (regular, views, materialized views, foreign, partitioned),
columns, primary/foreign/unique/check constraints, and table statistics via
`GetStatistics()`. However, PostgreSQL has a rich object model that data
development tools need — functions, procedures, indexes, triggers, sequences,
custom types, and more. This issue requests expanding metadata discovery to
cover these gaps.
We build a collaborative data operations platform and currently query
PostgreSQL system catalogs (`pg_catalog`) directly via JDBC for comprehensive
metadata extraction. We are evaluating the ADBC driver as a modern Arrow-native
replacement but cannot adopt it without coverage for the object categories
listed below.
Each section describes what we need, why it matters, what metadata
properties are required, and the exact `pg_catalog` queries to obtain it.
---
## Table of Contents
1. [Functions (Scalar)](#1-functions-scalar)
2. [Table-Returning Functions](#2-table-returning-functions)
3. [Aggregate Functions](#3-aggregate-functions)
4. [Stored Procedures (PG 12+)](#4-stored-procedures-pg-12)
5. [Function & Procedure Parameters](#5-function--procedure-parameters)
6. [Indexes](#6-indexes)
7. [Triggers](#7-triggers)
8. [Sequences](#8-sequences)
9. [View & Materialized View
Definitions](#9-view--materialized-view-definitions)
10. [Exclusion Constraints](#10-exclusion-constraints)
11. [Check Constraint Expressions](#11-check-constraint-expressions)
12. [Foreign Key Actions &
Deferrability](#12-foreign-key-actions--deferrability)
13. [Reverse Foreign Keys (Referenced
By)](#13-reverse-foreign-keys-referenced-by)
14. [Partition Hierarchy](#14-partition-hierarchy)
15. [Custom Types (Enums, Composites, Domains,
Ranges)](#15-custom-types-enums-composites-domains-ranges)
16. [Extensions](#16-extensions)
17. [Foreign Data Wrappers & Foreign
Servers](#17-foreign-data-wrappers--foreign-servers)
18. [Row-Level Security Policies](#18-row-level-security-policies)
19. [Rules](#19-rules)
20. [Database & Schema Properties](#20-database--schema-properties)
---
## 1. Functions (Scalar)
### Motivation
User-defined functions are a core part of PostgreSQL's extensibility. They
support SQL, PL/pgSQL, PL/Python, PL/Perl, C, and other languages. Functions
can be overloaded (same name, different argument types). Data development tools
need to enumerate them for autocomplete, signature help, documentation, and
catalog browsing.
### Required Metadata
| Property | Description | Source |
|---|---|---|
| OID | Unique function identifier (needed for overload resolution) |
`pg_proc.oid` |
| Schema | Containing schema | `pg_namespace.nspname` |
| Function name | Function identifier | `pg_proc.proname` |
| Owner | Function owner | `pg_roles.rolname` via `pg_proc.proowner` |
| Language | SQL, plpgsql, plpython3u, C, etc. | `pg_language.lanname` via
`pg_proc.prolang` |
| Return type | Return data type | `format_type(pg_proc.prorettype, NULL)` |
| Volatility | VOLATILE, STABLE, IMMUTABLE | `pg_proc.provolatile` |
| Is strict | Returns NULL on NULL input | `pg_proc.proisstrict` |
| Security definer | Runs as definer vs invoker | `pg_proc.prosecdef` |
| Source code | Full CREATE FUNCTION statement |
`pg_get_functiondef(pg_proc.oid)` |
| Description | User-provided comment | `pg_description` |
| Parallel safety | SAFE, RESTRICTED, UNSAFE | `pg_proc.proparallel` (PG
9.6+) |
| Cost | Estimated execution cost | `pg_proc.procost` |
| Rows | Estimated rows returned (set-returning) | `pg_proc.prorows` |
### How to Obtain
```sql
SELECT
p.oid,
p.proname AS name,
n.nspname AS schema,
r.rolname AS owner,
l.lanname AS language,
format_type(p.prorettype, NULL) AS return_type,
p.provolatile,
p.proisstrict,
p.prosecdef,
p.proparallel,
p.procost,
pg_get_functiondef(p.oid) AS definition,
d.description
FROM pg_catalog.pg_proc p
INNER JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace
INNER JOIN pg_catalog.pg_language l ON l.oid = p.prolang
LEFT JOIN pg_catalog.pg_roles r ON r.oid = p.proowner
LEFT JOIN pg_catalog.pg_description d
ON d.objoid = p.oid
AND d.classoid = 'pg_proc'::regclass
AND d.objsubid = 0
WHERE n.nspname = $1
AND p.prokind = 'f' -- PG 11+; for older: NOT p.proisagg AND
p.prorettype != 0
AND NOT p.proretset -- scalar only (not set-returning)
ORDER BY p.proname;
```
### Overload Handling
PostgreSQL allows multiple functions with the same name but different
argument types. The driver should:
1. Use `pg_proc.oid` as the unique identifier
2. Include argument type signatures to distinguish overloads
3. Group or label overloads clearly in results
---
## 2. Table-Returning Functions
### Motivation
Set-returning functions (table functions) return tabular results and are
used in FROM clauses. They need to be distinguished from scalar functions
because they produce a result set with named columns.
### Required Metadata
Same as scalar functions (section 1), plus:
| Property | Description | Source |
|---|---|---|
| Returns set | Always true for table functions | `pg_proc.proretset` |
| Result columns | Column names and types | Derived from return type or OUT
params |
### How to Identify
```sql
-- Table functions: prokind='f' AND proretset=true
WHERE p.prokind = 'f' AND p.proretset = true
```
Result columns come from:
- `pg_proc.proallargtypes` + `pg_proc.proargmodes` — OUT and TABLE
parameters define result columns
- Or from the return composite type via `pg_type` + `pg_attribute` if
returning a composite type
---
## 3. Aggregate Functions
### Motivation
PostgreSQL has a rich aggregate function system supporting custom aggregates
with transition functions, final functions, combine functions (for parallel
aggregation), moving aggregates, and ordered-set/hypothetical-set aggregates.
Tools need to display these alongside regular functions.
### Required Metadata
| Property | Description | Source |
|---|---|---|
| All function properties | Same as section 1 | `pg_proc` |
| Aggregate kind | Normal, ordered-set, hypothetical-set |
`pg_aggregate.aggkind` |
| Transition function | State transition function |
`pg_aggregate.aggtransfn` |
| Final function | Finalization function | `pg_aggregate.aggfinalfn` |
| Combine function | Parallel combine function | `pg_aggregate.aggcombinefn`
|
| Serialization function | Parallel state serialization |
`pg_aggregate.aggserialfn` |
| Deserialization function | Parallel state deserialization |
`pg_aggregate.aggdeserialfn` |
| Internal state type | Transition state data type |
`format_type(pg_aggregate.aggtranstype, NULL)` |
| Initial value | Transition state initial value | `pg_aggregate.agginitval`
|
| Sort operator | Sort operator for min/max aggregates |
`pg_operator.oprname` via `pg_aggregate.aggsortop` |
| Moving transition function | For moving aggregates (window mode) |
`pg_aggregate.aggmtransfn` |
| Moving inverse function | Inverse transition for moving aggregates |
`pg_aggregate.aggminvtransfn` |
| Moving final function | Final function for moving aggregates |
`pg_aggregate.aggmfinalfn` |
| Number of direct args | For ordered-set aggregates |
`pg_aggregate.aggnumdirectargs` |
### How to Obtain
```sql
SELECT
p.oid,
p.proname,
a.aggkind,
a.aggtransfn::regproc AS transition_function,
a.aggfinalfn::regproc AS final_function,
a.aggcombinefn::regproc AS combine_function,
a.aggserialfn::regproc AS serialization_function,
a.aggdeserialfn::regproc AS deserialization_function,
format_type(a.aggtranstype, NULL) AS internal_state_type,
a.agginitval AS initial_value,
a.aggkind,
a.aggnumdirectargs,
a.aggmtransfn::regproc AS moving_transition_function,
a.aggminvtransfn::regproc AS moving_inverse_function,
a.aggmfinalfn::regproc AS moving_final_function,
format_type(a.aggmtranstype, NULL) AS moving_state_type,
a.aggminitval AS moving_initial_value
FROM pg_catalog.pg_proc p
INNER JOIN pg_catalog.pg_aggregate a ON a.aggfnoid = p.oid
INNER JOIN pg_catalog.pg_namespace n ON n.oid = p.pronamespace
WHERE n.nspname = $1
AND p.prokind IN ('a', 'w') -- aggregate or window
ORDER BY p.proname;
```
---
## 4. Stored Procedures (PG 12+)
### Motivation
PostgreSQL 12 introduced true stored procedures (distinct from functions)
with transaction control capabilities (`COMMIT`/`ROLLBACK`). Tools need to
distinguish procedures from functions and display them appropriately.
### Required Metadata
Same as scalar functions (section 1), but:
| Property | Description | Source |
|---|---|---|
| Procedure kind | Always 'p' | `pg_proc.prokind = 'p'` |
| Support for CALL | Invoked via CALL, not SELECT | Implicit from kind |
| OUT parameters | PG 14+ supports OUT args in procedures |
`pg_proc.proargmodes` |
### How to Identify
```sql
WHERE p.prokind = 'p' -- PG 11+ only
```
### Version Considerations
- PG < 11: No `prokind` column. Use `proisagg` and return type heuristics
- PG 11: `prokind` added but procedures are PG 12+
- PG 14: OUT parameters in procedures fully supported
---
## 5. Function & Procedure Parameters
### Motivation
Parameter metadata enables signature help, call template generation, and
documentation. PostgreSQL functions support IN, OUT, INOUT, and VARIADIC
parameter modes.
### Required Metadata
| Property | Description | Source |
|---|---|---|
| Parameter name | May be unnamed | `pg_proc.proargnames[i]` |
| Parameter type | Data type OID | `pg_proc.proargtypes[i]` (IN only) or
`pg_proc.proallargtypes[i]` (all) |
| Parameter mode | IN, OUT, INOUT, VARIADIC, TABLE |
`pg_proc.proargmodes[i]` |
| Ordinal position | 1-based | Array index |
| Has default | Whether parameter has a default | `pg_proc.pronargdefaults >
0` |
### How to Obtain
Parameters are stored as arrays on `pg_proc`:
```sql
SELECT
p.oid,
p.proname,
p.proargtypes, -- IN argument type OIDs (oidvector)
p.proallargtypes, -- All argument type OIDs (including OUT)
p.proargnames, -- Argument names (text[])
p.proargmodes, -- Argument modes: i=IN, o=OUT, b=INOUT,
v=VARIADIC, t=TABLE
p.pronargdefaults, -- Number of arguments with defaults
pg_get_function_arguments(p.oid) AS argument_list, -- Formatted
argument list
pg_get_function_result(p.oid) AS result_type -- Formatted
return type
FROM pg_catalog.pg_proc p
WHERE p.oid = $1;
```
The arrays must be unnested in application code or via `UNNEST()`:
- `proargtypes` only includes IN parameters
- `proallargtypes` includes all parameters (IN, OUT, INOUT, VARIADIC, TABLE)
— NULL if only IN params
- `proargmodes` — NULL if all parameters are IN
- `proargnames` — NULL entries for unnamed parameters
---
## 6. Indexes
### Motivation
Index metadata is critical for query performance analysis, database
optimization, and schema documentation. PostgreSQL supports multiple access
methods (btree, hash, gin, gist, spgist, brin) with rich options (partial
indexes, expression indexes, included columns, etc.).
### Required Metadata
| Property | Description | Source |
|---|---|---|
| Index name | Index identifier | `pg_class.relname` (index class) |
| Table name | Indexed table | `pg_class.relname` (table class) |
| Access method | btree, hash, gin, gist, spgist, brin | `pg_am.amname` |
| Is unique | Whether the index enforces uniqueness | `pg_index.indisunique`
|
| Is primary | Whether it backs the primary key | `pg_index.indisprimary` |
| Is clustered | Whether the table is clustered on this index |
`pg_index.indisclustered` |
| Is valid | Whether the index is valid (concurrent build may leave invalid)
| `pg_index.indisvalid` |
| Is exclusion | Whether it backs an exclusion constraint |
`pg_index.indisexclusion` |
| Column count | Number of index columns | `pg_class.relnatts` |
| Column expressions | Expression per column position |
`pg_get_indexdef(indexrelid, col, true)` |
| Column options | ASC/DESC, NULLS FIRST/LAST per column |
`pg_index.indoption` |
| Partial predicate | WHERE clause for partial indexes |
`pg_get_expr(pg_index.indpred, indrelid)` |
| Included columns | Columns stored but not indexed (PG 11+) | Columns
beyond `indnkeyatts` |
| Full DDL | Complete CREATE INDEX statement | `pg_get_indexdef(indexrelid)`
|
| Tablespace | Tablespace storing the index | `pg_indexes.tablespace` |
| Description | User-provided comment | `pg_description` |
| Size | Index size in bytes | `pg_relation_size(indexrelid)` |
### How to Obtain
```sql
SELECT
ic.relname AS index_name,
tc.relname AS table_name,
am.amname AS access_method,
i.indisunique AS is_unique,
i.indisprimary AS is_primary,
i.indisclustered AS is_clustered,
i.indisvalid AS is_valid,
i.indisexclusion AS is_exclusion,
ic.relnatts AS column_count,
i.indnkeyatts AS key_column_count, -- PG 11+
i.indkey AS column_numbers,
i.indoption AS column_options,
pg_get_expr(i.indpred, i.indrelid) AS predicate,
pg_get_indexdef(i.indexrelid) AS definition,
pg_indexes.tablespace,
d.description,
pg_relation_size(i.indexrelid) AS size_bytes
FROM pg_catalog.pg_index i
INNER JOIN pg_catalog.pg_class ic ON ic.oid = i.indexrelid
INNER JOIN pg_catalog.pg_class tc ON tc.oid = i.indrelid
INNER JOIN pg_catalog.pg_namespace n ON n.oid = tc.relnamespace
INNER JOIN pg_catalog.pg_am am ON am.oid = ic.relam
LEFT JOIN pg_catalog.pg_indexes
ON pg_indexes.schemaname = n.nspname
AND pg_indexes.tablename = tc.relname
AND pg_indexes.indexname = ic.relname
LEFT JOIN pg_catalog.pg_description d
ON d.objoid = i.indexrelid
AND d.classoid = 'pg_class'::regclass
WHERE n.nspname = $1 AND tc.relname = $2
ORDER BY ic.relname;
```
Per-column expressions:
```sql
SELECT pg_get_indexdef(indexrelid, column_number, true) AS column_expression
```
---
## 7. Triggers
### Motivation
Triggers are essential for understanding data modification side effects,
audit logging, constraint enforcement, and event-driven logic. Tools need to
display trigger metadata for schema comprehension and debugging.
### Required Metadata
| Property | Description | Source |
|---|---|---|
| Trigger name | Trigger identifier | `pg_trigger.tgname` |
| Table name | Table the trigger is on | `pg_class.relname` |
| Timing | BEFORE, AFTER, INSTEAD OF | Decoded from `pg_trigger.tgtype` bits
|
| Events | INSERT, UPDATE, DELETE, TRUNCATE | Decoded from
`pg_trigger.tgtype` bits |
| Row/statement level | FOR EACH ROW vs FOR EACH STATEMENT | Decoded from
`pg_trigger.tgtype` bit |
| Function name | Trigger function called | `pg_proc.proname` via
`pg_trigger.tgfoid` |
| Function schema | Schema of trigger function | `pg_namespace.nspname` |
| Enabled state | O=origin, D=disabled, R=replica, A=always |
`pg_trigger.tgenabled` |
| Deferrable | Whether the trigger is deferrable | `pg_trigger.tgdeferrable`
|
| Initially deferred | Default deferral state | `pg_trigger.tginitdeferred` |
| Is internal | System-generated (for constraint FK triggers) |
`pg_trigger.tgisinternal` |
| When condition | Conditional execution expression | `pg_get_triggerdef()`
contains WHEN clause |
| Columns | Specific columns for UPDATE OF | `pg_trigger.tgattr` |
| Old table alias | REFERENCING OLD TABLE AS name | `pg_trigger.tgoldtable` |
| New table alias | REFERENCING NEW TABLE AS name | `pg_trigger.tgnewtable` |
| Constraint name | For constraint triggers | `pg_trigger.tgconstrname` |
| Referenced table | For constraint triggers (FK table) | `pg_class.relname`
via `pg_trigger.tgconstrrelid` |
| Parent trigger | For partition triggers (PG 13+) | `pg_trigger.tgparentid`
|
| Full DDL | Complete CREATE TRIGGER statement |
`pg_get_triggerdef(pg_trigger.oid)` |
| Description | User-provided comment | `pg_description` |
### How to Obtain
```sql
SELECT
t.oid,
t.tgname AS trigger_name,
tc.relname AS table_name,
pg_get_triggerdef(t.oid) AS definition,
t.tgtype,
t.tgenabled,
t.tgdeferrable,
t.tginitdeferred,
t.tgisinternal,
t.tgattr AS update_columns,
t.tgoldtable AS old_table_alias,
t.tgnewtable AS new_table_alias,
f.proname AS function_name,
fn.nspname AS function_schema,
t.tgconstrname AS constraint_name,
rt.relname AS referenced_table,
rn.nspname AS referenced_schema,
pt.tgname AS parent_trigger_name,
d.description
FROM pg_catalog.pg_trigger t
INNER JOIN pg_catalog.pg_class tc ON tc.oid = t.tgrelid
INNER JOIN pg_catalog.pg_namespace tn ON tn.oid = tc.relnamespace
LEFT JOIN pg_catalog.pg_proc f ON f.oid = t.tgfoid
LEFT JOIN pg_catalog.pg_namespace fn ON fn.oid = f.pronamespace
LEFT JOIN pg_catalog.pg_class rt ON rt.oid = t.tgconstrrelid
LEFT JOIN pg_catalog.pg_namespace rn ON rn.oid = rt.relnamespace
LEFT JOIN pg_catalog.pg_trigger pt ON pt.oid = t.tgparentid
LEFT JOIN pg_catalog.pg_description d
ON d.objoid = t.oid
AND d.classoid = 'pg_trigger'::regclass
WHERE tn.nspname = $1 AND tc.relname = $2
ORDER BY t.tgname;
```
### Decoding `tgtype`
The `tgtype` column is a bitmask:
- Bit 0 (1): ROW-level (vs STATEMENT)
- Bit 1 (2): BEFORE (vs AFTER)
- Bit 2 (4): INSERT
- Bit 3 (8): DELETE
- Bit 4 (16): UPDATE
- Bit 5 (32): TRUNCATE
- Bit 6 (64): INSTEAD OF
---
## 8. Sequences
### Motivation
Sequences generate unique numeric identifiers and are referenced by
`SERIAL`/`BIGSERIAL` columns and `GENERATED AS IDENTITY` columns. Tools need to
display sequence properties for schema comprehension.
### Required Metadata
| Property | Description | Source |
|---|---|---|
| Sequence name | Sequence identifier | `pg_class.relname` (relkind='S') |
| Schema | Containing schema | `pg_namespace.nspname` |
| Data type | smallint, integer, bigint | `format_type(pg_sequence.seqtypid,
NULL)` |
| Start value | Initial value | `pg_sequence.seqstart` |
| Increment | Step value | `pg_sequence.seqincrement` |
| Minimum value | Lower bound | `pg_sequence.seqmin` |
| Maximum value | Upper bound | `pg_sequence.seqmax` |
| Cache size | Pre-allocated values | `pg_sequence.seqcache` |
| Cycle | Whether sequence wraps around | `pg_sequence.seqcycle` |
| Last value | Most recently used value | `pg_sequences.last_value` |
| Owner | Sequence owner | `pg_roles.rolname` |
| Owned by column | Owning column (for SERIAL) | `pg_depend` → table.column |
| Description | User-provided comment | `pg_description` |
### How to Obtain
```sql
SELECT
c.relname AS sequence_name,
n.nspname AS schema_name,
format_type(s.seqtypid, NULL) AS data_type,
s.seqstart AS start_value,
s.seqincrement AS increment_by,
s.seqmin AS min_value,
s.seqmax AS max_value,
s.seqcache AS cache_size,
s.seqcycle AS is_cycle,
r.rolname AS owner,
d.description
FROM pg_catalog.pg_sequence s
INNER JOIN pg_catalog.pg_class c ON c.oid = s.seqrelid
INNER JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
LEFT JOIN pg_catalog.pg_roles r ON r.oid = c.relowner
LEFT JOIN pg_catalog.pg_description d
ON d.objoid = c.oid
AND d.classoid = 'pg_class'::regclass
AND d.objsubid = 0
WHERE n.nspname = $1
ORDER BY c.relname;
```
For current last value (requires `pg_sequences` view, PG 10+):
```sql
SELECT last_value FROM pg_sequences WHERE schemaname = $1 AND sequencename =
$2;
```
---
## 9. View & Materialized View Definitions
### Motivation
View SQL is essential for understanding data transformations, debugging
query behavior, migration, and documentation. The driver currently returns
views as table objects but does not include their defining query.
### Required Metadata
| Property | Description | Source |
|---|---|---|
| View name | View identifier | `pg_class.relname` |
| Definition | The SELECT statement | `pg_views.definition` or
`pg_get_viewdef()` |
| Is materialized | Regular view vs materialized | `pg_class.relkind` ('v'
vs 'm') |
| Check option | CASCADED, LOCAL, or NONE | `pg_views.definition` contains
`WITH CHECK OPTION` |
| Tablespace | Storage location (materialized views only) |
`pg_tablespace.spcname` |
### How to Obtain
```sql
-- Views
SELECT viewname, definition
FROM pg_catalog.pg_views
WHERE schemaname = $1;
-- Materialized views
SELECT c.relname, pg_get_viewdef(c.oid, true) AS definition,
t.spcname AS tablespace
FROM pg_catalog.pg_class c
INNER JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
LEFT JOIN pg_catalog.pg_tablespace t ON t.oid = c.reltablespace
WHERE n.nspname = $1 AND c.relkind = 'm';
```
### Suggested Approach
Add an optional `table_definition` (utf8) field to `TABLE_SCHEMA` in the
`GetObjects` result, populated for views and materialized views.
---
## 10. Exclusion Constraints
### Motivation
Exclusion constraints (`EXCLUDE USING`) are a PostgreSQL-specific constraint
type that prevents overlapping values (commonly used with range types and GiST
indexes). The driver currently supports PRIMARY KEY, FOREIGN KEY, UNIQUE, and
CHECK constraints but not exclusion constraints.
### Required Metadata
| Property | Description | Source |
|---|---|---|
| Constraint name | Constraint identifier | `pg_constraint.conname` |
| Constraint type | EXCLUDE | `pg_constraint.contype = 'x'` |
| Column names | Columns in the constraint | `pg_constraint.conkey` →
`pg_attribute.attname` |
| Access method | Index access method (typically gist) | `pg_am.amname` via
index |
| Full definition | Complete constraint SQL | `pg_get_constraintdef(oid,
true)` |
| Index name | Backing index | `pg_class.relname` via
`pg_constraint.conindid` |
### How to Obtain
```sql
SELECT
con.conname AS constraint_name,
pg_get_constraintdef(con.oid, true) AS definition,
ic.relname AS index_name,
am.amname AS access_method
FROM pg_catalog.pg_constraint con
INNER JOIN pg_catalog.pg_class tc ON tc.oid = con.conrelid
INNER JOIN pg_catalog.pg_namespace n ON n.oid = con.connamespace
LEFT JOIN pg_catalog.pg_class ic ON ic.oid = con.conindid
LEFT JOIN pg_catalog.pg_am am ON am.oid = ic.relam
WHERE n.nspname = $1 AND tc.relname = $2
AND con.contype = 'x';
```
### Note
The existing ADBC `CONSTRAINT_SCHEMA` would need a new `constraint_type`
value (`"EXCLUDE"`) and ideally a `constraint_expression` field to carry the
full definition.
---
## 11. Check Constraint Expressions
### Motivation
The driver currently returns check constraints with column names but not the
actual expression. The expression is the most important part — it tells users
what validation rule is being enforced (e.g., `price > 0`, `status IN
('active', 'inactive')`).
### Required Metadata
| Property | Description | Source |
|---|---|---|
| Expression | The CHECK expression text | `pg_get_constraintdef(oid, true)`
|
### How to Obtain
```sql
SELECT
con.conname,
pg_get_constraintdef(con.oid, true) AS definition
FROM pg_catalog.pg_constraint con
WHERE con.contype = 'c' AND con.conrelid = $1::regclass;
```
Returns text like: `CHECK ((price > 0))` or `CHECK ((status = ANY
(ARRAY['active'::text, 'inactive'::text])))`.
### Suggested Approach
Add a `constraint_expression` (utf8) field to `CONSTRAINT_SCHEMA` in the
ADBC spec, or encode the expression in the existing `constraint_column_names`
field as a single-element list.
---
## 12. Foreign Key Actions & Deferrability
### Motivation
The driver currently returns foreign key references (referenced table and
columns) but not the ON UPDATE/ON DELETE actions or deferrability settings.
These are essential for understanding cascading behavior and constraint timing.
### Required Metadata
| Property | Description | Source |
|---|---|---|
| On update action | NO ACTION, RESTRICT, CASCADE, SET NULL, SET DEFAULT |
`pg_constraint.confupdtype` |
| On delete action | NO ACTION, RESTRICT, CASCADE, SET NULL, SET DEFAULT |
`pg_constraint.confdeltype` |
| Match type | FULL, PARTIAL, SIMPLE | `pg_constraint.confmatchtype` |
| Is deferrable | Whether the constraint can be deferred |
`pg_constraint.condeferrable` |
| Initially deferred | Default deferral state | `pg_constraint.condeferred` |
### Action Code Mapping
| Code | Action |
|---|---|
| `a` | NO ACTION |
| `r` | RESTRICT |
| `c` | CASCADE |
| `n` | SET NULL |
| `d` | SET DEFAULT |
### How to Obtain
These columns are already available on `pg_constraint`, which the driver
already queries for FK constraints. They just need to be included in the result.
---
## 13. Reverse Foreign Keys (Referenced By)
### Motivation
When inspecting a table, users need to see not only which tables it
references but also which tables reference it. This is essential for impact
analysis — understanding what would break if a table or column is modified.
### Required Metadata
| Property | Description |
|---|---|
| Referencing schema | Schema of the table holding the FK |
| Referencing table | Table that holds the FK |
| FK constraint name | Foreign key constraint name |
| Referencing columns | Columns in the referencing table |
| Referenced columns | Columns in the target table |
### How to Obtain
```sql
SELECT
rn.nspname AS referencing_schema,
rc.relname AS referencing_table,
con.conname AS constraint_name,
con.conkey AS referencing_column_nums,
con.confkey AS referenced_column_nums
FROM pg_catalog.pg_constraint con
INNER JOIN pg_catalog.pg_class rc ON rc.oid = con.conrelid
INNER JOIN pg_catalog.pg_namespace rn ON rn.oid = rc.relnamespace
WHERE con.contype = 'f'
AND con.confrelid = $1::regclass -- the target table OID
ORDER BY rn.nspname, rc.relname, con.conname;
```
---
## 14. Partition Hierarchy
### Motivation
PostgreSQL 10+ supports declarative partitioning. Tools need to display the
partition hierarchy — the parent partitioned table and its child partitions,
including partition bounds and strategy.
### Required Metadata
| Property | Description | Source |
|---|---|---|
| Parent table | Partitioned table name | `pg_class.relname` (relkind='p') |
| Partition strategy | RANGE, LIST, HASH | `pg_partitioned_table.partstrat` |
| Partition key | Key columns/expressions | `pg_partitioned_table.partattrs`
+ `pg_partitioned_table.partexprs` |
| Child partitions | List of partition table names | `pg_inherits.inhrelid`
→ `pg_class.relname` |
| Partition bounds | Bound specification per child |
`pg_get_expr(pg_class.relpartbound, oid)` |
### How to Obtain
```sql
-- Partition strategy and key
SELECT
pt.partstrat,
pt.partnatts,
pt.partattrs,
pg_get_partkeydef(c.oid) AS partition_key_def
FROM pg_catalog.pg_partitioned_table pt
INNER JOIN pg_catalog.pg_class c ON c.oid = pt.partrelid
WHERE c.oid = $1::regclass;
-- Child partitions with bounds
SELECT
child.relname AS partition_name,
pg_get_expr(child.relpartbound, child.oid) AS partition_bound
FROM pg_catalog.pg_inherits i
INNER JOIN pg_catalog.pg_class child ON child.oid = i.inhrelid
WHERE i.inhparent = $1::regclass
ORDER BY child.relname;
```
---
## 15. Custom Types (Enums, Composites, Domains, Ranges)
### Motivation
PostgreSQL's extensible type system is a major feature. Custom types appear
in column definitions, function signatures, and domain constraints. Tools need
to enumerate and describe them.
### Required Metadata
#### Enum Types
| Property | Description | Source |
|---|---|---|
| Type name | Enum type name | `pg_type.typname` (typtype='e') |
| Schema | Containing schema | `pg_namespace.nspname` |
| Labels | Ordered list of enum values | `pg_enum.enumlabel` ordered by
`enumsortorder` |
```sql
SELECT t.typname, n.nspname, e.enumlabel
FROM pg_catalog.pg_type t
INNER JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace
INNER JOIN pg_catalog.pg_enum e ON e.enumtypid = t.oid
WHERE n.nspname = $1 AND t.typtype = 'e'
ORDER BY t.typname, e.enumsortorder;
```
#### Composite Types
| Property | Description | Source |
|---|---|---|
| Type name | Composite type name | `pg_type.typname` (typtype='c') |
| Fields | Ordered list of field names and types | `pg_attribute` for
`pg_type.typrelid` |
```sql
SELECT t.typname, a.attname, format_type(a.atttypid, a.atttypmod) AS
field_type, a.attnum
FROM pg_catalog.pg_type t
INNER JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace
INNER JOIN pg_catalog.pg_attribute a ON a.attrelid = t.typrelid
WHERE n.nspname = $1 AND t.typtype = 'c'
AND a.attnum > 0 AND NOT a.attisdropped
AND NOT EXISTS (SELECT 1 FROM pg_class c WHERE c.oid = t.typrelid AND
c.relkind IN ('r','v','m','p','f'))
ORDER BY t.typname, a.attnum;
```
#### Domain Types
| Property | Description | Source |
|---|---|---|
| Domain name | Domain type name | `pg_type.typname` (typtype='d') |
| Base type | Underlying data type | `format_type(pg_type.typbasetype,
pg_type.typtypmod)` |
| Not null | Whether domain is NOT NULL | `pg_type.typnotnull` |
| Default | Default expression | `pg_type.typdefault` |
| Check constraints | Domain constraints | `pg_constraint` for domain OID |
```sql
SELECT t.typname, format_type(t.typbasetype, t.typtypmod) AS base_type,
t.typnotnull, t.typdefault,
pg_get_constraintdef(con.oid) AS constraint_def
FROM pg_catalog.pg_type t
INNER JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace
LEFT JOIN pg_catalog.pg_constraint con ON con.contypid = t.oid
WHERE n.nspname = $1 AND t.typtype = 'd'
ORDER BY t.typname;
```
#### Range Types
| Property | Description | Source |
|---|---|---|
| Range name | Range type name | `pg_type.typname` (typtype='r') |
| Subtype | Element type of range | `format_type(pg_range.rngsubtype, NULL)`
|
| Canonical function | Canonicalization function | `pg_range.rngcanonical` |
| Subtype diff function | Difference function | `pg_range.rngsubdiff` |
```sql
SELECT t.typname, format_type(r.rngsubtype, NULL) AS subtype,
r.rngcanonical::regproc, r.rngsubdiff::regproc
FROM pg_catalog.pg_type t
INNER JOIN pg_catalog.pg_namespace n ON n.oid = t.typnamespace
INNER JOIN pg_catalog.pg_range r ON r.rngtypid = t.oid
WHERE n.nspname = $1
ORDER BY t.typname;
```
---
## 16. Extensions
### Motivation
Extensions package functions, types, operators, and other objects into
installable modules. Tools need to show installed extensions for schema
comprehension and dependency tracking.
### Required Metadata
| Property | Description | Source |
|---|---|---|
| Extension name | Extension identifier | `pg_extension.extname` |
| Version | Installed version | `pg_extension.extversion` |
| Schema | Installation schema | `pg_namespace.nspname` via
`pg_extension.extnamespace` |
| Relocatable | Whether extension can be moved |
`pg_extension.extrelocatable` |
| Description | Extension description | `pg_description` or
`pg_available_extensions.comment` |
### How to Obtain
```sql
SELECT
e.extname,
e.extversion,
n.nspname AS schema,
e.extrelocatable,
ae.comment AS description
FROM pg_catalog.pg_extension e
INNER JOIN pg_catalog.pg_namespace n ON n.oid = e.extnamespace
LEFT JOIN pg_catalog.pg_available_extensions ae ON ae.name = e.extname
ORDER BY e.extname;
```
---
## 17. Foreign Data Wrappers & Foreign Servers
### Motivation
Foreign data wrappers (FDWs) enable access to external data sources. Tools
need to display FDW and foreign server configurations to understand external
data integration.
### Required Metadata
#### Foreign Data Wrappers
| Property | Description | Source |
|---|---|---|
| FDW name | Wrapper identifier | `pg_foreign_data_wrapper.fdwname` |
| Handler function | FDW handler | `pg_proc.proname` via `fdwhandler` |
| Validator function | FDW validator | `pg_proc.proname` via `fdwvalidator` |
| Options | Key-value options | `pg_foreign_data_wrapper.fdwoptions` |
| Owner | FDW owner | `pg_roles.rolname` |
#### Foreign Servers
| Property | Description | Source |
|---|---|---|
| Server name | Server identifier | `pg_foreign_server.srvname` |
| FDW name | Associated wrapper | `pg_foreign_data_wrapper.fdwname` |
| Type | Server type string | `pg_foreign_server.srvtype` |
| Version | Server version string | `pg_foreign_server.srvversion` |
| Options | Connection options (host, port, dbname, etc.) |
`pg_foreign_server.srvoptions` |
| Owner | Server owner | `pg_roles.rolname` |
### How to Obtain
```sql
-- Foreign data wrappers
SELECT fdw.fdwname, h.proname AS handler, v.proname AS validator,
fdw.fdwoptions, r.rolname AS owner
FROM pg_catalog.pg_foreign_data_wrapper fdw
LEFT JOIN pg_catalog.pg_proc h ON h.oid = fdw.fdwhandler
LEFT JOIN pg_catalog.pg_proc v ON v.oid = fdw.fdwvalidator
LEFT JOIN pg_catalog.pg_roles r ON r.oid = fdw.fdwowner;
-- Foreign servers
SELECT s.srvname, fdw.fdwname, s.srvtype, s.srvversion,
s.srvoptions, r.rolname AS owner
FROM pg_catalog.pg_foreign_server s
INNER JOIN pg_catalog.pg_foreign_data_wrapper fdw ON fdw.oid = s.srvfdw
LEFT JOIN pg_catalog.pg_roles r ON r.oid = s.srvowner;
```
---
## 18. Row-Level Security Policies
### Motivation
Row-level security (RLS) policies control which rows are visible or
modifiable by different roles. Tools should display these policies for security
audit and documentation.
### Required Metadata
| Property | Description | Source |
|---|---|---|
| Policy name | Policy identifier | `pg_policy.polname` |
| Table name | Table the policy applies to | `pg_class.relname` |
| Command | ALL, SELECT, INSERT, UPDATE, DELETE | `pg_policy.polcmd` |
| Permissive/Restrictive | Policy type | `pg_policy.polpermissive` |
| Roles | Roles the policy applies to | `pg_policy.polroles` |
| USING expression | Visibility filter | `pg_get_expr(pg_policy.polqual,
polrelid)` |
| WITH CHECK expression | Modification filter |
`pg_get_expr(pg_policy.polwithcheck, polrelid)` |
### How to Obtain
```sql
SELECT
pol.polname AS policy_name,
c.relname AS table_name,
pol.polcmd AS command,
pol.polpermissive AS is_permissive,
pol.polroles AS roles,
pg_get_expr(pol.polqual, pol.polrelid) AS using_expression,
pg_get_expr(pol.polwithcheck, pol.polrelid) AS with_check_expression
FROM pg_catalog.pg_policy pol
INNER JOIN pg_catalog.pg_class c ON c.oid = pol.polrelid
INNER JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
WHERE n.nspname = $1
ORDER BY c.relname, pol.polname;
```
---
## 19. Rules
### Motivation
Rules rewrite queries before execution. While triggers are generally
preferred for most use cases, rules are used for view UPDATE/INSERT/DELETE
support and some legacy patterns. Tools should display them when present.
### Required Metadata
| Property | Description | Source |
|---|---|---|
| Rule name | Rule identifier | `pg_rewrite.rulename` |
| Table/view name | Object the rule applies to | `pg_class.relname` |
| Event | SELECT, INSERT, UPDATE, DELETE | `pg_rewrite.ev_type` |
| Is instead | INSTEAD rule vs ALSO | `pg_rewrite.is_instead` |
| Definition | Full rule SQL | `pg_get_ruledef(oid)` |
### How to Obtain
```sql
SELECT
rw.rulename,
c.relname AS table_name,
rw.ev_type,
rw.is_instead,
pg_get_ruledef(rw.oid) AS definition
FROM pg_catalog.pg_rewrite rw
INNER JOIN pg_catalog.pg_class c ON c.oid = rw.ev_class
INNER JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
WHERE n.nspname = $1
AND rw.rulename != '_RETURN' -- exclude default view rules
ORDER BY c.relname, rw.rulename;
```
---
## 20. Database & Schema Properties
### Motivation
The driver currently returns database and schema names but no additional
properties. Tools need descriptions, owners, and configuration for context
display.
### Required Metadata
#### Databases
| Property | Description | Source |
|---|---|---|
| Database name | Database identifier | `pg_database.datname` |
| Owner | Database owner | `pg_roles.rolname` via `pg_database.datdba` |
| Encoding | Character encoding |
`pg_encoding_to_char(pg_database.encoding)` |
| Collation | Default collation | `pg_database.datcollate` |
| Tablespace | Default tablespace | `pg_tablespace.spcname` via
`pg_database.dattablespace` |
| Connection limit | Max concurrent connections | `pg_database.datconnlimit`
|
| Description | User-provided comment | `pg_shdescription` |
#### Schemas
| Property | Description | Source |
|---|---|---|
| Schema name | Schema identifier | `pg_namespace.nspname` |
| Owner | Schema owner | `pg_roles.rolname` via `pg_namespace.nspowner` |
| Description | User-provided comment | `pg_description` |
### How to Obtain
```sql
-- Databases
SELECT d.datname, r.rolname AS owner,
pg_encoding_to_char(d.encoding) AS encoding,
d.datcollate, t.spcname AS tablespace,
d.datconnlimit, sd.description
FROM pg_catalog.pg_database d
LEFT JOIN pg_catalog.pg_roles r ON r.oid = d.datdba
LEFT JOIN pg_catalog.pg_tablespace t ON t.oid = d.dattablespace
LEFT JOIN pg_catalog.pg_shdescription sd
ON sd.objoid = d.oid AND sd.classoid = 'pg_database'::regclass
WHERE d.datname != 'template0';
-- Schemas
SELECT n.nspname, r.rolname AS owner, d.description
FROM pg_catalog.pg_namespace n
LEFT JOIN pg_catalog.pg_roles r ON r.oid = n.nspowner
LEFT JOIN pg_catalog.pg_description d
ON d.objoid = n.oid AND d.classoid = 'pg_namespace'::regclass
WHERE n.nspname NOT LIKE 'pg_temp_%' AND n.nspname NOT LIKE 'pg_toast_%';
```
---
## Summary Priority Table
| # | Category | pg_catalog Source | Impact |
|---|---|---|---|
| 1 | Scalar Functions | `pg_proc` (prokind='f') | High — core SQL
development |
| 2 | Table Functions | `pg_proc` (proretset=true) | High — SQL development |
| 3 | Aggregate Functions | `pg_proc` + `pg_aggregate` | High — SQL
development |
| 4 | Stored Procedures (PG 12+) | `pg_proc` (prokind='p') | High — SQL
development |
| 5 | Function/Procedure Parameters | `pg_proc` arrays | High — signature
help |
| 6 | Indexes | `pg_index` + `pg_am` | High — performance tuning |
| 7 | Triggers | `pg_trigger` | High — schema comprehension |
| 8 | Sequences | `pg_sequence` | Medium — schema browsing |
| 9 | View/MV Definitions | `pg_views` / `pg_get_viewdef()` | High — SQL
development |
| 10 | Exclusion Constraints | `pg_constraint` (contype='x') | Medium —
constraint completeness |
| 11 | Check Constraint Expressions | `pg_get_constraintdef()` | Medium —
constraint detail |
| 12 | FK Actions & Deferrability | `pg_constraint` columns | Medium —
constraint detail |
| 13 | Reverse Foreign Keys | `pg_constraint` (reversed) | Medium — impact
analysis |
| 14 | Partition Hierarchy | `pg_partitioned_table` + `pg_inherits` | Medium
— PG 10+ |
| 15 | Custom Types | `pg_type` + `pg_enum` + `pg_range` | Medium — type
system |
| 16 | Extensions | `pg_extension` | Low — admin context |
| 17 | Foreign Data Wrappers | `pg_foreign_data_wrapper` +
`pg_foreign_server` | Low — external data |
| 18 | Row-Level Security | `pg_policy` | Low — security audit |
| 19 | Rules | `pg_rewrite` | Low — legacy feature |
| 20 | Database & Schema Properties | `pg_database` + `pg_namespace` | Low —
enriched context |
### Note on GetStatistics
The ADBC PostgreSQL driver already implements `GetStatistics()` using
`pg_stats`, which is a capability the other ADBC drivers (Databricks,
Snowflake, BigQuery) lack. This is a strength of the PostgreSQL driver.
We're happy to provide further details on any of these categories.
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]