From c02b1c19cce63f39373753c182954e3b0cb5e4cd Mon Sep 17 00:00:00 2001 From: "Gavin M. Roy" Date: Wed, 1 Apr 2026 09:53:42 -0400 Subject: [PATCH 1/2] Implement live database schema introspection via pg_catalog Add optional --database-url / -d flag (also reads DATABASE_URL env var) to connect to a PostgreSQL database on startup and load schema information into the workspace index. Queries pg_catalog for: - Schemas (pg_namespace) - Tables, views, materialized views, foreign tables (pg_class) with their columns (pg_attribute + format_type) - Functions and procedures (pg_proc) with argument signatures and return types - Custom types, enums, ranges, domains (pg_type) - Sequences (pg_class relkind='S') Database symbols are stored under the synthetic URI pg-catalog://database and merged into the workspace index alongside file-sourced symbols, providing completion, hover, and go-to-definition for database objects even when no SQL files define them. Also adds WorkspaceIndex::load_symbols() for inserting pre-built symbols. Co-Authored-By: Claude Sonnet 4.6 (1M context) --- crates/pg-analysis/src/index.rs | 21 +++ crates/pg-lsp/Cargo.toml | 3 +- crates/pg-lsp/src/main.rs | 22 ++- crates/pg-lsp/src/server.rs | 16 ++- crates/pg-schema/src/catalog.rs | 233 +++++++++++++++++++++++++++++++- crates/pg-schema/src/lib.rs | 28 ++++ 6 files changed, 316 insertions(+), 7 deletions(-) diff --git a/crates/pg-analysis/src/index.rs b/crates/pg-analysis/src/index.rs index ab7b067..c0d164d 100644 --- a/crates/pg-analysis/src/index.rs +++ b/crates/pg-analysis/src/index.rs @@ -71,6 +71,27 @@ impl WorkspaceIndex { self.references.insert(uri.to_string(), ref_arcs); } + /// Insert pre-built symbols (e.g., from database introspection). + /// Uses the provided URI to group them, allowing removal via `remove_file`. + pub fn load_symbols(&self, uri: &str, symbols: Vec) { + self.remove_file(uri); + + let symbol_arcs: Vec> = symbols.into_iter().map(Arc::new).collect(); + + for sym in &symbol_arcs { + let key = (sym.kind, sym.name.name.to_lowercase()); + self.by_name.entry(key).or_default().push(Arc::clone(sym)); + + for child in &sym.children { + let child_arc = Arc::new(child.clone()); + let child_key = (child.kind, child.name.name.to_lowercase()); + self.by_name.entry(child_key).or_default().push(child_arc); + } + } + + self.definitions.insert(uri.to_string(), symbol_arcs); + } + /// Remove all entries for a file. pub fn remove_file(&self, uri: &str) { if let Some((_, old_symbols)) = self.definitions.remove(uri) { diff --git a/crates/pg-lsp/Cargo.toml b/crates/pg-lsp/Cargo.toml index 4fd14bb..a1337d1 100644 --- a/crates/pg-lsp/Cargo.toml +++ b/crates/pg-lsp/Cargo.toml @@ -13,6 +13,7 @@ path = "src/main.rs" pg-parse = { path = "../pg-parse" } pg-analysis = { path = "../pg-analysis" } pg-format = { path = "../pg-format" } +pg-schema = { path = "../pg-schema" } tree-sitter.workspace = true tree-sitter-postgres.workspace = true tower-lsp.workspace = true @@ -23,4 +24,4 @@ serde.workspace = true serde_json.workspace = true tracing.workspace = true tracing-subscriber.workspace = true -clap = { version = "4", features = ["derive"] } +clap = { version = "4", features = ["derive", "env"] } diff --git a/crates/pg-lsp/src/main.rs b/crates/pg-lsp/src/main.rs index 384fa34..62c1eba 100644 --- a/crates/pg-lsp/src/main.rs +++ b/crates/pg-lsp/src/main.rs @@ -21,11 +21,23 @@ struct Cli { help = "Formatting style: river, mozilla, aweber, dbt, gitlab, kickstarter, mattmc3" )] format_style: Style, + + /// PostgreSQL connection URL for live schema introspection + #[arg( + long, + short = 'd', + env = "DATABASE_URL", + help = "e.g., postgres://user:pass@localhost/dbname" + )] + database_url: Option, } fn parse_style(s: &str) -> Result { - s.parse::