Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,14 @@

## [Unreleased]

### Added

- `get_foreign_keys` and `get_all_foreign_keys_batch` now return `ref_schema`,
the schema of the referenced table. A foreign key into another schema
(`billing.carry_forwards.organization_id → core.organizations.id`) no longer
makes the Related Records panel query `billing.organizations`. Byte-for-byte
parity with the built-in driver (companion to the tabularis host change).

## [1.0.0-rc.14] - 2026-11-10

### Added
Expand Down
9 changes: 9 additions & 0 deletions src/handlers/metadata.rs
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,9 @@ fn row_to_foreign_key(r: &tokio_postgres::Row) -> Value {
let column_name: String = r.try_get("column_name").unwrap_or_default();
let ref_table: String = r.try_get("foreign_table_name").unwrap_or_default();
let ref_column: String = r.try_get("foreign_column_name").unwrap_or_default();
// The referenced table can live in another schema than the source table;
// without this the host qualifies `ref_table` with the source schema.
let ref_schema: Option<String> = r.try_get("foreign_schema_name").ok().flatten();
let on_update: Option<String> = r.try_get("update_rule").ok().flatten();
let on_delete: Option<String> = r.try_get("delete_rule").ok().flatten();
// WITH ORDINALITY yields the position within conkey/confkey — the true
Expand All @@ -449,6 +452,12 @@ fn row_to_foreign_key(r: &tokio_postgres::Row) -> Value {
"on_update": on_update,
});

if let Some(schema) = ref_schema {
fk.as_object_mut()
.unwrap()
.insert("ref_schema".to_string(), json!(schema));
}

if let Some(seq) = seq_in_fk {
fk.as_object_mut()
.unwrap()
Expand Down
41 changes: 41 additions & 0 deletions tests/live_db.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2640,6 +2640,47 @@ fn get_materialized_view_definition_returns_ddl_not_not_implemented() {
);
}

/// A foreign key into another schema reports that schema as `ref_schema`
/// from both `get_foreign_keys` and `get_all_foreign_keys_batch`, so the
/// host doesn't qualify `ref_table` with the source schema.
#[test]
fn foreign_keys_report_the_referenced_schema_for_cross_schema_references() {
let mut plugin = Plugin::spawn();
let params = conn_params();

for query in [
"DROP SCHEMA IF EXISTS live_db_fk_ref CASCADE",
"DROP SCHEMA IF EXISTS live_db_fk_src CASCADE",
"CREATE SCHEMA live_db_fk_ref",
"CREATE SCHEMA live_db_fk_src",
"CREATE TABLE live_db_fk_ref.parent (id INTEGER PRIMARY KEY)",
"CREATE TABLE live_db_fk_src.child \
(id INTEGER PRIMARY KEY, parent_id INTEGER REFERENCES live_db_fk_ref.parent(id))",
] {
plugin.call_ok("execute_query", json!({"params": params, "query": query}));
}

let fks = plugin.call_ok(
"get_foreign_keys",
json!({"params": params, "table": "child", "schema": "live_db_fk_src"}),
);
assert_eq!(fks[0]["ref_table"], json!("parent"));
assert_eq!(fks[0]["ref_schema"], json!("live_db_fk_ref"));

let batch = plugin.call_ok(
"get_all_foreign_keys_batch",
json!({"params": params, "schema": "live_db_fk_src"}),
);
assert_eq!(batch["child"][0]["ref_schema"], json!("live_db_fk_ref"));

for query in [
"DROP SCHEMA IF EXISTS live_db_fk_src CASCADE",
"DROP SCHEMA IF EXISTS live_db_fk_ref CASCADE",
] {
plugin.call_ok("execute_query", json!({"params": params, "query": query}));
}
}

/// A nonexistent materialized view surfaces a clean error, not a silent
/// success or a crash — the `($1::text)::regclass` cast raises a PostgreSQL
/// error that maps to a -32603 response. Mirrors the existing
Expand Down
Loading