Skip to content
Merged
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@ All changes that a user can see are in this file. The version is 0.M.patch, wher

## Unreleased

### Added

- `EXPLAIN` in the text, JSON, XML and YAML forms, with a planner that makes the plan of a query with the costs of PostgreSQL (#439).

## 0.1.51 (2026-10-20)

### Added
Expand Down
13 changes: 13 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions _typos.toml
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ dispell = "dispell"
fals = "fals"
valu = "valu"
abd = "abd"
# The function `make_ands_implicit` of the planner.
ands = "ands"

# Test inputs that are wrong on purpose.
[default]
Expand Down
5 changes: 5 additions & 0 deletions crates/rupg-exec/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1064,6 +1064,11 @@ fn run_query(
}

impl Plan {
/// The query of the plan after `prepare` and `fold`, which `EXPLAIN` shows.
pub fn query(&self) -> &Query {
&self.query
}

/// The columns of the result. The junk columns of `ORDER BY` do not count. A query that writes has the columns of its `RETURNING`, or none.
pub fn columns(&self) -> &[Target] {
if let Some(write) = &self.query.write {
Expand Down
1 change: 1 addition & 0 deletions crates/rupg-func/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ rupg-analyze.workspace = true
rupg-catalog.workspace = true
rupg-common.workspace = true
rupg-pgcatalog.workspace = true
rupg-plan.workspace = true
rupg-sql.workspace = true
rupg-types.workspace = true
sha2.workspace = true
Expand Down
47 changes: 41 additions & 6 deletions crates/rupg-func/src/deparse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,11 @@
//!
//! The functions read the stored form that `rupg_analyze::node` writes, or the query that the analyzer makes from the text of a view, and show it as PostgreSQL shows its node trees. The rules for parentheses, casts and constants follow `get_rule_expr`. The functions show the objects of the user and the system views. An index or a constraint of a system catalog gives an error that says that the engine cannot show it yet.

mod explain;
mod function;
mod query;

pub use explain::explain;
pub use query::view_statement;

use std::ptr;
Expand Down Expand Up @@ -44,7 +46,7 @@ const NULLS_FIRST: i16 = 2;
const FIRST_USER_OID: u32 = 16384;

/// The names of the relations of one query and of their columns, as `deparse_namespace` with its `deparse_columns`.
#[derive(Debug, Default)]
#[derive(Clone, Debug, Default)]
struct Namespace {
/// The name of each relation of the query, by its index, as `rtable_names`. The name is unique in the query and in the queries outside it.
names: Vec<String>,
Expand All @@ -63,7 +65,7 @@ struct Namespace {
}

/// The names of a join and of its columns, as `deparse_columns` for an entry of a join.
#[derive(Debug, Default)]
#[derive(Clone, Debug, Default)]
struct JoinNames {
/// The name of the join with an alias, which no other relation of the query has, or `None` for a join with no alias.
name: Option<String>,
Expand Down Expand Up @@ -102,9 +104,29 @@ struct Deparser<'a> {
windows: Vec<WindowClause>,
/// The expressions of the targets of the query, which the items of the windows name.
window_targets: Vec<Expr>,
/// For `EXPLAIN`, the subplans of the subqueries in the expressions. A subquery shows the name of its subplan, as `get_rule_expr` shows a `SubPlan`.
subplans: Vec<SubName>,
/// For `EXPLAIN`, the subplan whose columns the test of `ANY` or `ALL` reads now, as `(SubPlan any_1)`.
subplan: Option<String>,
/// For `EXPLAIN`, the name of each window of the query by its number, which a call of a window function shows after `OVER`.
window_names: Vec<Option<String>>,
/// For `EXPLAIN`, a column of a query outside the query always shows the name of its relation, as `get_parameter` shows the value of a parameter.
outer_prefix: bool,
buf: String,
}

/// The name of the subplan of a subquery in an expression, for `EXPLAIN`.
#[derive(Clone, Debug)]
struct SubName {
/// The subquery. The deparse only compares the address.
link: *const SubLink,
name: String,
/// The subplan is an `InitPlan`.
init: bool,
/// The subplan keeps its rows in a hash table.
hashed: bool,
}

impl<'a> Deparser<'a> {
/// A deparse of an expression of one relation, as `deparse_context_for`: a `Var` shows the name of its column with no name of the relation.
fn for_columns(session: &'a dyn Session, columns: Vec<String>, paren: bool) -> Deparser<'a> {
Expand Down Expand Up @@ -135,6 +157,10 @@ impl<'a> Deparser<'a> {
outputs: Vec::new(),
windows: Vec::new(),
window_targets: Vec::new(),
subplans: Vec::new(),
subplan: None,
window_names: Vec::new(),
outer_prefix: false,
buf: String::new(),
}
}
Expand Down Expand Up @@ -169,7 +195,7 @@ impl<'a> Deparser<'a> {
};
let attname =
attname.ok_or_else(|| Error::internal(format!("invalid attnum {}", var.attnum)))?;
let mut prefix = self.prefix;
let mut prefix = self.prefix || (self.outer_prefix && var.levels_up > 0);
if in_order_by && !self.in_group_by && !prefix {
prefix = self.outputs.iter().any(|(name, v)| *name == attname && *v != Some(*var));
}
Expand Down Expand Up @@ -446,9 +472,18 @@ impl<'a> Deparser<'a> {
self.buf.push(')');
}
ExprKind::Window(w) => self.window_func(w)?,
ExprKind::SubLink(sub) => self.sublink(sub)?,
ExprKind::SubColumn(_) => {
return Err(Error::internal("a column of a subquery outside its test"));
ExprKind::SubLink(sub) => {
let link = ptr::from_ref(sub.as_ref());
match self.subplans.iter().find(|s| s.link == link).cloned() {
Some(name) => self.subplan_link(sub, &name)?,
None => self.sublink(sub)?,
}
}
ExprKind::SubColumn(column) => {
let Some(subplan) = &self.subplan else {
return Err(Error::internal("a column of a subquery outside its test"));
};
self.buf.push_str(&format!("{subplan}.col{}", column + 1));
}
ExprKind::JsonValue(v) => {
self.expr(&v.raw, false)?;
Expand Down
Loading
Loading