pg_catalog_rs is a PostgreSQL system catalog compatibility layer for Apache DataFusion.
It enables PostgreSQL clients (e.g., DBeaver, pgAdmin, JDBC, ODBC, BI platforms) to connect to a DataFusion-backed database by emulating the behavior of PostgreSQL's pg_catalog schema.
This makes it possible to support tooling that expects PostgreSQL system metadata, even if the underlying engine is not Postgres.
Note: This is WIP heavily and API can change.
-
Emulates core system tables from the
pg_catalogschema:pg_classpg_attributepg_namespacepg_typepg_indexpg_proc
-
Supports PostgreSQL-specific built-in functions:
pg_get_constraintdef(oid)current_database()has_schema_privilege(...)
-
Allows standard metadata queries used by:
- DBeaver, DataGrip, and other GUI tools
- BI tools using JDBC or ODBC
- Postgres CLI (
psql)
Catalog coverage: see
CATALOG-REFERENCE.mdfor the full list of catalog tables and views, how each is populated (seed vs. runtime registration), and per-view working/partial/broken status.
-
Compatible with
pgwirefor Postgres wire protocol handling -
Fully in-memory and extensible via DataFusion APIs
Add the crate from GitHub to your Cargo.toml:
[dependencies]
datafusion_pg_catalog = { git = "https://github.com/ybrs/pg_catalog" }Create a SessionContext preloaded with the catalog tables and register your own schema:
use std::collections::BTreeMap;
use datafusion_pg_catalog::{
get_base_session_context, register_user_database, register_schema,
register_user_tables, register_user_index, ColumnDef,
};
let (ctx, _log) = get_base_session_context(
Some("pg_catalog_data/pg_schema"),
"pgtry".to_string(),
"public".to_string(),
None, // optional current_schemas getter
).await?;
register_user_database(&ctx, "crm").await?;
register_schema(&ctx, "crm", "public").await?;
let mut cols = BTreeMap::new();
cols.insert(
"id".to_string(),
ColumnDef { col_type: "int".to_string(), nullable: false },
);
register_user_tables(&ctx, "crm", "public", "users", vec![cols]).await?;
// Optionally register an index so pg_indexes / pg_get_indexdef can describe it.
// Args: schema, index name, table, key column attnums, is_unique, is_primary.
register_user_index(&ctx, "public", "users_pkey", "users", vec![1], true, true).await?;Then you can run queries like:
SELECT oid, relname FROM pg_class WHERE relnamespace = 2200;
SELECT attname FROM pg_attribute WHERE attrelid = 12345;Or use DBeaver/psql to introspect the schema.
When executing SQL, call dispatch_query to automatically
route catalog queries to the internal handler while forwarding all other
statements to your application logic.
The handler you pass receives (ctx, sql, params, param_types) and returns the
result rows together with their Arrow schema, so dispatch_query can hand back a
uniform (Vec<RecordBatch>, Arc<Schema>) whether the query was answered by the
catalog or by your handler.
use std::sync::Arc;
use datafusion_pg_catalog::router::dispatch_query;
let (batches, schema) = dispatch_query(
&ctx,
"SELECT * FROM pg_class",
None, // bind parameter values
None, // bind parameter types
|ctx, sql, _params, _param_types| async move {
let df = ctx.sql(sql).await?;
let schema = Arc::new(df.schema().as_arrow().clone());
let batches = df.collect().await?;
Ok((batches, schema))
},
).await?;- Works with the
pgwirecrate for wire protocol emulation - Can be combined with custom
MemTableor real storage backends (Parquet, Arrow, etc.) - Designed to be embedded in hybrid SQL engines or compatibility layers
Launch a PostgreSQL-compatible endpoint using the provided helper:
use std::sync::Arc;
use datafusion_pg_catalog::{get_base_session_context, start_server};
#[tokio::main]
async fn main() -> anyhow::Result<()> {
let (ctx, _log) = get_base_session_context(
Some("pg_catalog_data/pg_schema"),
"pgtry".to_string(),
"public".to_string(),
None,
).await?;
start_server(Arc::new(ctx), "127.0.0.1:5433", "pgtry", "public", None).await?;
Ok(())
}If you already expose your own SQL server you can integrate pg_catalog_rs without
starting the bundled pgwire endpoint. Build a SessionContext and register your
tables, then call dispatch_query from your handler. Catalog
queries will be executed internally and all other statements are forwarded.
use std::sync::Arc;
use datafusion_pg_catalog::router::dispatch_query;
async fn handle_request(ctx: &SessionContext, sql: &str) -> DFResult<Vec<RecordBatch>> {
let (batches, _schema) = dispatch_query(
ctx,
sql,
None,
None,
|ctx, sql, _params, _param_types| async move {
let df = ctx.sql(sql).await?;
let schema = Arc::new(df.schema().as_arrow().clone());
let batches = df.collect().await?;
Ok((batches, schema))
},
).await?;
Ok(batches)
}Many pg_catalog views call server-runtime functions a static catalog cannot compute -
per-object statistics (pg_stat_get_numscans, ...), live session/lock state
(pg_stat_get_activity, pg_lock_status, ...), WAL/replication state, and more. They are
all registered, so those views are real, queryable views; with no callback installed each
returns its empty default (SQL NULL, or no rows), so the views work out of the box.
Supply values through typed resolver callbacks. Each function has its own explicit,
compile-time-checked setter named set_<function>_resolver:
use std::sync::Arc;
use datafusion_pg_catalog::{
set_pg_stat_get_numscans_resolver, // scalar: Fn(oid) -> Option<i64>
set_pg_lock_status_resolver, // set-returning: Fn() -> Vec<PgLockStatusRow>
PgLockStatusRow,
};
set_pg_stat_get_numscans_resolver(Arc::new(|relation_oid: i64| Some(scans_for(relation_oid))));
set_pg_lock_status_resolver(Arc::new(|| vec![
PgLockStatusRow { locktype: Some("relation".into()), pid: Some(42), granted: Some(true),
..Default::default() },
]));See docs/runtime-functions.md for the full guide (the two resolver shapes, the generated row structs, session identity, and caveats), and docs/runtime-functions-reference.md for the copy-pasteable reference: every setter's exact signature and every row struct's full field list, in Rust.
- No persistence - catalog is in-memory only
- Partial function support (more can be added)
- Schema reflection based on user-defined tables must be manually tracked
- No write-back support to catalog (read-only)
- Hook into
CREATE TABLEto auto-populate metadata -
pg_indexregistration (eagerregister_user_index+ lazyLazyCatalogSource::indexes()) - Add remaining catalog tables for user objects (
pg_constraint,pg_attrdef, ...) - Deparse for
pg_get_indexdef/pg_get_viewdef/pg_get_ruledef - Catalog persistence to disk or external store
- Enhanced type inference and function overloads
Functional tests are written in Python and executed with pytest.
After installing the dependencies from requirements.txt, run:
pytestLicensed under either of:
at your option.
Pull requests are welcome. Contributions that improve PostgreSQL compatibility or support new clients are especially appreciated.
Before submitting a change please run the Rust unit tests and the Python functional tests:
cargo test
pytestThis project is inspired by:
- PostgreSQL's system catalog architecture
- Apache DataFusion as the execution backend
pgwirefor wire protocol support