Skip to content

Commit 6f5df11

Browse files
antiguruclaude
andcommitted
compute: columnar Constant output (P2)
Build the `Constant` arm of `render_plan_expr` into a `Column` and return a `CollectionEdge::Columnar`, flipping the constant literal source to emit the columnar edge. The err collection stays row-based (out of scope). The rows go through a `ConsolidatingColumnBuilder` via `to_stream_with_builder` rather than a direct columnar build. The planner (`FoldConstants`) consolidates constant rows by `(row, time)` at optimization time, but this arm then advances every time to `as_of`, which can collapse distinct original times onto one time and so reintroduce duplicates at the same `(row, time)`. Consolidating here folds those within the batch, matching the standing producer rule. The constant rows are already owned, so the give is a move into staging, not a new allocation. The `as_of` advancement and `until` filtering are unchanged. Test: extends degenerate.slt with a constant carrying duplicate rows (asserting multiplicity survives the columnar edge) and a constant feeding an indexed view and an aggregate, exercising the columnar producer against ArrangeBy and Reduce consumers. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent c420261 commit 6f5df11

2 files changed

Lines changed: 61 additions & 17 deletions

File tree

src/compute/src/render.rs

Lines changed: 28 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,7 @@ use mz_timely_util::scope_label::ScopeExt;
146146
use timely::PartialOrder;
147147
use timely::container::CapacityContainerBuilder;
148148
use timely::dataflow::channels::pact::Pipeline;
149+
use timely::dataflow::operators::core::to_stream::ToStreamBuilder;
149150
use timely::dataflow::operators::vec::ToStream;
150151
use timely::dataflow::operators::vec::{BranchWhen, Filter};
151152
use timely::dataflow::operators::{Capability, Operator, Probe, probe};
@@ -169,6 +170,7 @@ use crate::render::context::{ArrangementFlavor, Context};
169170
use crate::render::errors::DataflowErrorSer;
170171
use crate::typedefs::{ErrBatcher, ErrBuilder, ErrSpine, KeyBatcher, MzTimestamp};
171172
use mz_row_spine::{DatumSeq, RowRowBatcher, RowRowBuilder};
173+
use mz_timely_util::columnar::consolidate::ConsolidatingColumnBuilder;
172174

173175
pub(crate) mod columnar;
174176
pub mod context;
@@ -1177,22 +1179,31 @@ impl<'scope, T: RenderTimestamp + MaybeBucketByTime> Context<'scope, T> {
11771179
// We should advance times in constant collections to start from `as_of`.
11781180
let as_of_frontier = self.as_of_frontier.clone();
11791181
let until = self.until.clone();
1180-
let ok_collection = rows
1181-
.into_iter()
1182-
.filter_map(move |(row, mut time, diff)| {
1183-
time.advance_by(as_of_frontier.borrow());
1184-
if !until.less_equal(&time) {
1185-
Some((
1186-
row,
1187-
<T as Refines<mz_repr::Timestamp>>::to_inner(time),
1188-
diff,
1189-
))
1190-
} else {
1191-
None
1192-
}
1193-
})
1194-
.to_stream(self.scope)
1195-
.as_collection();
1182+
// Build the ok rows columnar, so this literal source emits the
1183+
// columnar edge. Advancing times to `as_of` can collapse
1184+
// distinct original times onto one time, so rows the planner
1185+
// left distinct may become duplicates here; a
1186+
// `ConsolidatingColumnBuilder` folds those within the batch (the
1187+
// rows are already owned, so the give is a move into staging).
1188+
let ok_collection = CollectionEdge::Columnar(
1189+
rows.into_iter()
1190+
.filter_map(move |(row, mut time, diff)| {
1191+
time.advance_by(as_of_frontier.borrow());
1192+
if !until.less_equal(&time) {
1193+
Some((
1194+
row,
1195+
<T as Refines<mz_repr::Timestamp>>::to_inner(time),
1196+
diff,
1197+
))
1198+
} else {
1199+
None
1200+
}
1201+
})
1202+
.to_stream_with_builder::<_, ConsolidatingColumnBuilder<Row, T, Diff>>(
1203+
self.scope,
1204+
)
1205+
.as_collection(),
1206+
);
11961207

11971208
let mut error_time: mz_repr::Timestamp = Timestamp::minimum();
11981209
error_time.advance_by(self.as_of_frontier.borrow());
@@ -1208,7 +1219,7 @@ impl<'scope, T: RenderTimestamp + MaybeBucketByTime> Context<'scope, T> {
12081219
.to_stream(self.scope)
12091220
.as_collection();
12101221

1211-
CollectionBundle::from_collections(ok_collection, err_collection)
1222+
CollectionBundle::from_edge(ok_collection, err_collection)
12121223
}
12131224
Get { id, keys, plan } => {
12141225
// Recover the collection from `self` and then apply `mfp` to it.

test/sqllogictest/degenerate.slt

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,36 @@ INSERT INTO t1 VALUES (NULL)
1919
query I
2020
SELECT * FROM t1 WHERE NOT (f1 = f1)
2121
----
22+
23+
# Constant collections render into a columnar edge (node P2). Duplicate constant
24+
# rows land at the same (advanced) time, so the producer folds them within the
25+
# batch; the query result must still show the correct multiplicity.
26+
query I rowsort
27+
SELECT a FROM (VALUES (1), (1), (2)) t(a)
28+
----
29+
1
30+
1
31+
2
32+
33+
# A constant feeding an indexed view exercises the columnar producer against an
34+
# ArrangeBy consumer, and the aggregate below against a Reduce consumer.
35+
statement ok
36+
CREATE VIEW cv AS SELECT a FROM (VALUES (1), (1), (2), (3)) t(a)
37+
38+
statement ok
39+
CREATE DEFAULT INDEX ON cv
40+
41+
query II rowsort
42+
SELECT a, count(*) FROM cv GROUP BY a
43+
----
44+
1
45+
2
46+
2
47+
1
48+
3
49+
1
50+
51+
query I
52+
SELECT sum(a) FROM cv
53+
----
54+
7

0 commit comments

Comments
 (0)