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
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ All changes that a user can see are in this file. The version is 0.M.patch, wher
- `ALTER DATABASE ... SET` and `RESET`, with the settings applied at the start of each session of the database and shown in `pg_db_role_setting` (#440).
- `COPY ... FROM 'file'`, which reads a file of the server, for a superuser (#441).
- `VACUUM` and `ANALYZE`, which check their options, relations and columns and then end, because rupg has no statistics yet (#442).
- `CREATE TABLE AS` and `SELECT ... INTO`, which make a table from the columns of a query and fill it with the rows of the query (#443).

## 0.1.51 (2026-10-20)

Expand Down
38 changes: 37 additions & 1 deletion crates/rupg-exec/src/scan.rs
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,39 @@ impl Tables {
.collect())
}

/// The number of the rows that each relation that reads other relations has now, which [`Tables::compact`] takes later.
fn marks(&self) -> Vec<usize> {
self.grown.iter().map(|g| g.borrow().len()).collect()
}

/// Frees the rows that the runs added after `marks` and that no tuple of `tuples` uses, and gives the tuples the new row numbers. A nested loop runs a relation that reads other relations once for each outer row, and most of the rows of a run join no row. Without this, the rows of all the runs stay until the query ends.
fn compact(&self, marks: &[usize], tuples: &mut [Vec<usize>]) {
for (relation, &mark) in marks.iter().enumerate() {
let mut grown = self.grown[relation].borrow_mut();
if grown.len() == mark {
continue;
}
let mut used: Vec<usize> = tuples
.iter()
.map(|t| t[relation])
.filter(|&row| row != NONE && row >= mark)
.collect();
used.sort_unstable();
used.dedup();
let mut tail = grown.split_off(mark);
grown.extend(used.iter().map(|&row| std::mem::take(&mut tail[row - mark])));
for tuple in tuples.iter_mut() {
let row = tuple[relation];
if row != NONE
&& row >= mark
&& let Ok(at) = used.binary_search(&row)
{
tuple[relation] = mark + at;
}
}
}
}

/// The index of a column of a relation that reads no other relation. The first call makes it and the next calls use it again.
fn index(&self, var: Var) -> Rc<Index> {
let column = (var.relation, var.attnum);
Expand Down Expand Up @@ -608,9 +641,12 @@ impl Scan<'_, '_> {
let mut out = Vec::new();
for l in left {
let context = merged(context, &l);
let marks = self.tables.marks();
let right = self.item_tuples(item, conds.clone(), &context)?;
let input = JoinInput { left: vec![l], right, ..input };
out.extend(join(input, self.tables, self.test)?);
let mut tuples = join(input, self.tables, self.test)?;
self.tables.compact(&marks, &mut tuples);
out.extend(tuples);
}
Ok(out)
}
Expand Down
6 changes: 6 additions & 0 deletions crates/rupg-session/src/connection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3553,6 +3553,12 @@ mod tests {
big_stack(|| script(include_str!("connection/create_database.test")));
}

/// The runs of a `LATERAL` relation in a nested loop: a join that keeps a few rows of many runs, an outer join with runs that give no row, a whole-row reference, two `LATERAL` relations in a chain, and an aggregate over the joined rows. `connection/lateral_runs.test` is the output of PostgreSQL 19 for the same script in a new database.
#[test]
fn lateral_runs() {
big_stack(|| script(include_str!("connection/lateral_runs.test")));
}

/// `CREATE TABLE AS` and `SELECT INTO`: the types, typmods and collations of the columns, a list of column names, `IF NOT EXISTS`, `WITH NO DATA`, the options of the table, the errors of the query and of the table, a query that fails at run time, and a transaction block. `connection/create_table_as.test` is the output of PostgreSQL 19 for the same script in a new database.
#[test]
fn create_table_as() {
Expand Down
44 changes: 44 additions & 0 deletions crates/rupg-session/src/connection/lateral_runs.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
!details
> SET TimeZone = 'UTC'
SET
> CREATE TABLE a (x int)
CREATE TABLE
> INSERT INTO a SELECT generate_series(1, 300)
INSERT 0 300
> CREATE TABLE b (y int, s text)
CREATE TABLE
> INSERT INTO b SELECT g, 's' || g FROM generate_series(1, 300) g
INSERT 0 300
> SELECT count(*) FROM a, b JOIN LATERAL (VALUES (a.x)) ss(v) ON b.y = ss.v
fields count:20:8:-1
row 300
SELECT 1
> SELECT a.x, b.s, ss FROM a, b JOIN LATERAL (VALUES (a.x, a.x * 2)) ss(v, w) ON b.y = ss.v WHERE a.x % 100 = 7 ORDER BY a.x
fields x@a.1:23:4:-1,s@b.2:25:-1:-1,ss:2249:-1:-1
row 7,s7,(7,14)
row 107,s107,(107,214)
row 207,s207,(207,414)
SELECT 3
> SELECT a.x, ss.y, ss.t FROM a LEFT JOIN LATERAL (SELECT b.y, b.s || '!' AS t FROM b WHERE b.y = a.x * 3) ss ON true WHERE a.x > 97 AND a.x < 103 ORDER BY a.x
fields x@a.1:23:4:-1,y@b.1:23:4:-1,t:25:-1:-1
row 98,294,s294!
row 99,297,s297!
row 100,300,s300!
row 101,NULL,NULL
row 102,NULL,NULL
SELECT 5
> SELECT a.x, sum(ss.z) FROM a LEFT JOIN LATERAL (SELECT a.x + b.y AS z, b.y FROM b) ss ON a.x = ss.y GROUP BY a.x HAVING a.x < 4 ORDER BY a.x
fields x@a.1:23:4:-1,sum:20:8:-1
row 1,2
row 2,4
row 3,6
SELECT 3
> SELECT a.x, s1.p, s2.q FROM a, LATERAL (SELECT a.x * 10 AS p) s1, LATERAL (SELECT s1.p + b.y AS q FROM b WHERE b.y = a.x) s2 WHERE a.x IN (5, 250) ORDER BY a.x
fields x@a.1:23:4:-1,p:23:4:-1,q:23:4:-1
row 5,50,55
row 250,2500,2750
SELECT 2
> SELECT count(*), sum(q) FROM a JOIN LATERAL (SELECT b.y AS q FROM b WHERE b.y <= a.x) ss ON ss.q % 50 = 0
fields count:20:8:-1,sum:20:8:-1
row 756,88550
SELECT 1
Loading