From f1a621bf12715105baa56b160cae70a9183fecb0 Mon Sep 17 00:00:00 2001 From: Tam Nguyen Duc <1218621+tamnd@users.noreply.github.com> Date: Tue, 20 Oct 2026 13:58:46 +0700 Subject: [PATCH 1/2] Add the changelog entry for #443 --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8f67c4a7..5eea016a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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) From 1605a41097a8eff43dcb2067bca9e129439779a3 Mon Sep 17 00:00:00 2001 From: Tam Nguyen Duc <1218621+tamnd@users.noreply.github.com> Date: Tue, 20 Oct 2026 14:02:04 +0700 Subject: [PATCH 2/2] Free the rows of each run of a LATERAL relation that no tuple uses --- crates/rupg-exec/src/scan.rs | 38 +++++++++++++++- crates/rupg-session/src/connection.rs | 6 +++ .../src/connection/lateral_runs.test | 44 +++++++++++++++++++ 3 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 crates/rupg-session/src/connection/lateral_runs.test diff --git a/crates/rupg-exec/src/scan.rs b/crates/rupg-exec/src/scan.rs index 6d636c29..cba912eb 100644 --- a/crates/rupg-exec/src/scan.rs +++ b/crates/rupg-exec/src/scan.rs @@ -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 { + 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]) { + for (relation, &mark) in marks.iter().enumerate() { + let mut grown = self.grown[relation].borrow_mut(); + if grown.len() == mark { + continue; + } + let mut used: Vec = 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 { let column = (var.relation, var.attnum); @@ -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) } diff --git a/crates/rupg-session/src/connection.rs b/crates/rupg-session/src/connection.rs index 97dba111..e4b01470 100644 --- a/crates/rupg-session/src/connection.rs +++ b/crates/rupg-session/src/connection.rs @@ -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() { diff --git a/crates/rupg-session/src/connection/lateral_runs.test b/crates/rupg-session/src/connection/lateral_runs.test new file mode 100644 index 00000000..3c40b1b1 --- /dev/null +++ b/crates/rupg-session/src/connection/lateral_runs.test @@ -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