Skip to content

Add subqueries in expressions - #159

Merged
tamnd merged 1 commit into
mainfrom
m2-subqueries
Oct 8, 2026
Merged

tamnd merged 1 commit into
mainfrom
m2-subqueries

Conversation

@tamnd

@tamnd tamnd commented Oct 8, 2026

Copy link
Copy Markdown
Owner

This PR adds subqueries in expressions for milestone M2 (#3). It is a part of the item "SELECT with joins, subqueries, CASE, regclass/regtype casts and pg_get_* functions". The casts and the pg_get_* functions will come in a later PR.

Analyzer

  • crates/rupg-analyze/src/sublink.rs is transformSubLink. It runs the subquery first and then the left side, as PostgreSQL does. It takes EXISTS, IN, NOT IN, op ANY, op ALL, ARRAY(SELECT ...) and a scalar subquery.
  • The errors come from transformSubLink: subquery must return only one column, subquery has too many columns and subquery has too few columns, at the same positions. An operator of ANY or ALL that does not give boolean gives 42804. ARRAY() of a type with no array type gives could not find array type for data type.
  • A Var now has levels_up, as varlevelsup. The analyzer keeps the scopes of the outer queries. A column name and a table name search from the subquery up to the outer queries, and stop at the first level that has the name, as colNameToVar and refnameNamespaceItem do.
  • errorMissingColumn and errorMissingRTE search the relations of all levels. So the hint of a close name can name a column of an outer relation, as in PostgreSQL.
  • The grouping check goes into subqueries with the depth, as substitute_grouped_columns_mutator does. A column of a grouped outer query that is not in GROUP BY gives subquery uses ungrouped column "c.amname" from outer query.
  • LIMIT and OFFSET refuse only the columns of their own query, also inside a subquery. A column of an outer query is a constant there.
  • An aggregate whose arguments read only the columns of an outer query belongs to that query in PostgreSQL. rupg gives 0A000 for it for now.

Executor

  • A subquery that reads no column of an outer query runs once, when the query first needs it, as an InitPlan does. Other subqueries run again for each row, with the rows of the outer queries in a stack of frames.
  • Each query reads the rows of its tables once per statement, also when it runs for each row of an outer query.
  • The WHERE push down and the column marks count only the columns of their own level. A condition such as o.opcmethod = a.oid in a subquery goes down to the scan of o.
  • EXISTS follows simplify_EXISTS_query. With no aggregate, no HAVING, no OFFSET, and a LIMIT that is a constant more than 0 or null, it does not compute the target list. So EXISTS (SELECT 1/0 FROM pg_am WHERE false) is false and EXISTS (SELECT 1/0) is true with no error. OFFSET 0 and an aggregate keep the target list, and the error comes, as in PostgreSQL.
  • A scalar subquery with more than one row gives 21000 more than one row returned by a subquery used as an expression. With no row it gives null. ANY and ALL use the logic of three values of ExecScanSubPlan.

Not in this PR

  • A row on the left of IN or ANY, such as (a, b) IN (SELECT ...).
  • ARRAY() of an array, which makes an array with one more dimension.
  • An aggregate of an outer query, such as SELECT (SELECT max(a.oid) FROM pg_type LIMIT 1) FROM pg_am a.
  • A subquery in FROM.
  • PostgreSQL folds constants before it runs EXISTS (SELECT 1/0 FROM pg_am LIMIT 0), so it gives division by zero. rupg gives false. The constant folding item will fix this.

Tests

  • All the expected values come from the PostgreSQL 19 oracle on server1.
  • subquery_errors in rupg-analyze checks the errors and the positions, the hints of close names in outer relations, and an outer column in LIMIT.
  • subqueries in rupg-exec runs 42 queries on the catalog. They include correlated subqueries at two levels, IN, NOT IN with a null, ANY and ALL with no rows, ARRAY() with nulls, subqueries in JOIN ... ON, HAVING, GROUP BY, ORDER BY and LIMIT, the cases of simplify_EXISTS_query, and the error of a scalar subquery with many rows.
  • cargo clippy --workspace --all-targets -- -D warnings, cargo test --workspace and the cargo xtask checks pass on server1.

@tamnd tamnd added this to the M2: Connect and introspect milestone Oct 8, 2026
@tamnd tamnd added kind/feature It does not do something that it must do. area/sql The grammar, the lexer, the analyzer and the rewriter. area/execution Operators, vectors, morsels, joins, aggregation and spill. labels Oct 8, 2026
@tamnd tamnd self-assigned this Oct 8, 2026
@tamnd
tamnd merged commit 8804439 into main Oct 8, 2026
5 checks passed
@tamnd
tamnd deleted the m2-subqueries branch October 8, 2026 04:13
This was referenced Oct 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/execution Operators, vectors, morsels, joins, aggregation and spill. area/sql The grammar, the lexer, the analyzer and the rewriter. kind/feature It does not do something that it must do.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant