uqa_execution/relational/
limit.rs1use super::{
10 compare_sort_key_values, BackwardScanSupport, Batch, ExecError, ExecResult, PhysicalOperator,
11 PhysicalScanDirection, RowSchema, ScalarExpr, SharedExpressionEvaluator, SortKey, Value,
12};
13use crate::PhysicalRow;
14
15struct WithTies<'a> {
16 keys: Vec<SortKey>,
17 evaluator: SharedExpressionEvaluator<'a>,
18 boundary: Option<Vec<Value>>,
19 finished: bool,
20}
21
22#[derive(Clone, Copy)]
23enum DirectionalLimitState {
24 Initial,
25 Rescan,
26 Empty,
27 InWindow,
28 SubplanEof,
29 WindowEnd,
30 WindowEndTies,
31 WindowStart,
32}
33
34pub struct Limit<'a> {
35 child: Box<dyn PhysicalOperator + 'a>,
36 offset: u64,
37 limit: Option<u64>,
38 skipped: u64,
39 emitted: u64,
40 schema: RowSchema,
41 with_ties: Option<WithTies<'a>>,
42 directional_state: DirectionalLimitState,
43 directional_position: u64,
44 directional_current: Option<PhysicalRow>,
45}
46
47impl<'a> Limit<'a> {
48 pub fn new(child: Box<dyn PhysicalOperator + 'a>, offset: u64, limit: Option<u64>) -> Self {
49 let schema = child.row_schema().clone();
50 Self {
51 child,
52 offset,
53 limit,
54 skipped: 0,
55 emitted: 0,
56 schema,
57 with_ties: None,
58 directional_state: DirectionalLimitState::Initial,
59 directional_position: 0,
60 directional_current: None,
61 }
62 }
63
64 pub fn with_ties(
66 child: Box<dyn PhysicalOperator + 'a>,
67 offset: u64,
68 limit: u64,
69 mut keys: Vec<SortKey>,
70 evaluator: SharedExpressionEvaluator<'a>,
71 ) -> Self {
72 let schema = child.row_schema().clone();
73 for key in &mut keys {
74 let expression = std::mem::replace(&mut key.expr, ScalarExpr::Literal(Value::Null));
75 key.expr = evaluator.bind_type_introspection(expression, &schema);
76 }
77 Self {
78 child,
79 offset,
80 limit: Some(limit),
81 skipped: 0,
82 emitted: 0,
83 schema,
84 with_ties: Some(WithTies {
85 keys,
86 evaluator,
87 boundary: None,
88 finished: false,
89 }),
90 directional_state: DirectionalLimitState::Initial,
91 directional_position: 0,
92 directional_current: None,
93 }
94 }
95
96 fn reset_directional_state(&mut self) {
97 self.directional_state = DirectionalLimitState::Rescan;
98 self.directional_position = 0;
99 self.directional_current = None;
100 if let Some(with_ties) = self.with_ties.as_mut() {
101 with_ties.boundary = None;
102 with_ties.finished = false;
103 }
104 }
105
106 fn child_row(&mut self, direction: PhysicalScanDirection) -> ExecResult<Option<PhysicalRow>> {
107 let Some(batch) = self.child.next_direction(direction)? else {
108 return Ok(None);
109 };
110 if batch.schema != self.schema {
111 return Err(ExecError::Other(format!(
112 "directional LIMIT input schema mismatch: expected {:?}, got {:?}",
113 self.schema, batch.schema
114 )));
115 }
116 let mut rows = batch.rows.into_iter();
117 let row = rows.next();
118 if rows.next().is_some() {
119 return Err(ExecError::Other(
120 "directional LIMIT input returned more than one row".into(),
121 ));
122 }
123 Ok(row)
124 }
125
126 fn capture_tie_boundary(&mut self, row: &PhysicalRow) -> ExecResult<()> {
127 let Some(with_ties) = self.with_ties.as_mut() else {
128 return Ok(());
129 };
130 with_ties.boundary = Some(
131 with_ties
132 .keys
133 .iter()
134 .map(|key| {
135 with_ties
136 .evaluator
137 .evaluate_physical(&key.expr, &self.schema, row)
138 })
139 .collect::<ExecResult<Vec<_>>>()?,
140 );
141 Ok(())
142 }
143
144 fn tie_matches(&self, row: &PhysicalRow) -> ExecResult<bool> {
145 let with_ties = self
146 .with_ties
147 .as_ref()
148 .ok_or_else(|| ExecError::Other("LIMIT tie state is absent".into()))?;
149 let values = with_ties
150 .keys
151 .iter()
152 .map(|key| {
153 with_ties
154 .evaluator
155 .evaluate_physical(&key.expr, &self.schema, row)
156 })
157 .collect::<ExecResult<Vec<_>>>()?;
158 let boundary = with_ties
159 .boundary
160 .as_ref()
161 .ok_or_else(|| ExecError::Other("LIMIT tie boundary is absent".into()))?;
162 Ok(
163 compare_sort_key_values(&with_ties.keys, boundary, &values)
164 == std::cmp::Ordering::Equal,
165 )
166 }
167
168 fn directional_batch(&self, row: PhysicalRow) -> Batch {
169 Batch::from_physical_rows(self.schema.clone(), vec![row])
170 }
171
172 #[expect(
173 clippy::too_many_lines,
174 reason = "mirrors PostgreSQL's directional LIMIT state machine"
175 )]
176 fn next_directional(&mut self, direction: PhysicalScanDirection) -> ExecResult<Option<Batch>> {
177 if matches!(self.directional_state, DirectionalLimitState::Initial) {
178 self.reset_directional_state();
179 }
180 loop {
181 match self.directional_state {
182 DirectionalLimitState::Initial => unreachable!(),
183 DirectionalLimitState::Rescan => {
184 if direction == PhysicalScanDirection::Backward {
185 return Ok(None);
186 }
187 if self.limit == Some(0) {
188 self.directional_state = DirectionalLimitState::Empty;
189 return Ok(None);
190 }
191 loop {
192 let Some(row) = self.child_row(PhysicalScanDirection::Forward)? else {
193 self.directional_state = DirectionalLimitState::Empty;
194 return Ok(None);
195 };
196 if self.with_ties.is_some()
197 && self.limit.is_some_and(|limit| {
198 self.directional_position.saturating_sub(self.offset)
199 == limit.saturating_sub(1)
200 })
201 {
202 self.capture_tie_boundary(&row)?;
203 }
204 self.directional_current = Some(row.clone());
205 self.directional_position = self
206 .directional_position
207 .checked_add(1)
208 .ok_or_else(|| ExecError::Other("LIMIT position overflow".into()))?;
209 if self.directional_position > self.offset {
210 self.directional_state = DirectionalLimitState::InWindow;
211 return Ok(Some(self.directional_batch(row)));
212 }
213 }
214 }
215 DirectionalLimitState::Empty => return Ok(None),
216 DirectionalLimitState::InWindow => {
217 if direction == PhysicalScanDirection::Forward {
218 if self.limit.is_some_and(|limit| {
219 self.directional_position.saturating_sub(self.offset) >= limit
220 }) {
221 if self.with_ties.is_none() {
222 self.directional_state = DirectionalLimitState::WindowEnd;
223 return Ok(None);
224 }
225 self.directional_state = DirectionalLimitState::WindowEndTies;
226 continue;
227 }
228 let Some(row) = self.child_row(PhysicalScanDirection::Forward)? else {
229 self.directional_state = DirectionalLimitState::SubplanEof;
230 return Ok(None);
231 };
232 if self.with_ties.is_some()
233 && self.limit.is_some_and(|limit| {
234 self.directional_position.saturating_sub(self.offset)
235 == limit.saturating_sub(1)
236 })
237 {
238 self.capture_tie_boundary(&row)?;
239 }
240 self.directional_current = Some(row.clone());
241 self.directional_position = self
242 .directional_position
243 .checked_add(1)
244 .ok_or_else(|| ExecError::Other("LIMIT position overflow".into()))?;
245 return Ok(Some(self.directional_batch(row)));
246 }
247 if self.directional_position <= self.offset.saturating_add(1) {
248 self.directional_state = DirectionalLimitState::WindowStart;
249 return Ok(None);
250 }
251 let row = self
252 .child_row(PhysicalScanDirection::Backward)?
253 .ok_or_else(|| {
254 ExecError::Other("LIMIT input failed to scan backwards".into())
255 })?;
256 self.directional_current = Some(row.clone());
257 self.directional_position -= 1;
258 return Ok(Some(self.directional_batch(row)));
259 }
260 DirectionalLimitState::WindowEndTies => {
261 if direction == PhysicalScanDirection::Forward {
262 let Some(row) = self.child_row(PhysicalScanDirection::Forward)? else {
263 self.directional_state = DirectionalLimitState::SubplanEof;
264 return Ok(None);
265 };
266 if self.tie_matches(&row)? {
267 self.directional_current = Some(row.clone());
268 self.directional_position =
269 self.directional_position.checked_add(1).ok_or_else(|| {
270 ExecError::Other("LIMIT position overflow".into())
271 })?;
272 return Ok(Some(self.directional_batch(row)));
273 }
274 self.directional_state = DirectionalLimitState::WindowEnd;
275 return Ok(None);
276 }
277 if self.directional_position <= self.offset.saturating_add(1) {
278 self.directional_state = DirectionalLimitState::WindowStart;
279 return Ok(None);
280 }
281 let row = self
282 .child_row(PhysicalScanDirection::Backward)?
283 .ok_or_else(|| {
284 ExecError::Other("LIMIT input failed to scan backwards".into())
285 })?;
286 self.directional_current = Some(row.clone());
287 self.directional_position -= 1;
288 self.directional_state = DirectionalLimitState::InWindow;
289 return Ok(Some(self.directional_batch(row)));
290 }
291 DirectionalLimitState::SubplanEof => {
292 if direction == PhysicalScanDirection::Forward {
293 return Ok(None);
294 }
295 let row = self
296 .child_row(PhysicalScanDirection::Backward)?
297 .ok_or_else(|| {
298 ExecError::Other("LIMIT input failed to leave end position".into())
299 })?;
300 self.directional_current = Some(row.clone());
301 self.directional_state = DirectionalLimitState::InWindow;
302 return Ok(Some(self.directional_batch(row)));
303 }
304 DirectionalLimitState::WindowEnd => {
305 if direction == PhysicalScanDirection::Forward {
306 return Ok(None);
307 }
308 let row = if self.with_ties.is_some() {
309 self.child_row(PhysicalScanDirection::Backward)?
310 .ok_or_else(|| {
311 ExecError::Other("LIMIT input failed to leave tie boundary".into())
312 })?
313 } else {
314 self.directional_current.clone().ok_or_else(|| {
315 ExecError::Other("LIMIT window has no current row".into())
316 })?
317 };
318 self.directional_current = Some(row.clone());
319 self.directional_state = DirectionalLimitState::InWindow;
320 return Ok(Some(self.directional_batch(row)));
321 }
322 DirectionalLimitState::WindowStart => {
323 if direction == PhysicalScanDirection::Backward {
324 return Ok(None);
325 }
326 let row = self.directional_current.clone().ok_or_else(|| {
327 ExecError::Other("LIMIT window has no current row".into())
328 })?;
329 self.directional_state = DirectionalLimitState::InWindow;
330 return Ok(Some(self.directional_batch(row)));
331 }
332 }
333 }
334 }
335}
336
337impl PhysicalOperator for Limit<'_> {
338 fn row_schema(&self) -> &RowSchema {
339 &self.schema
340 }
341
342 fn output_ordering(&self) -> &[crate::PhysicalOrder] {
343 self.child.output_ordering()
344 }
345
346 fn backward_scan_support(&self) -> BackwardScanSupport {
347 if self.child.backward_scan_support() == BackwardScanSupport::Native {
348 BackwardScanSupport::Native
349 } else {
350 BackwardScanSupport::Unsupported
351 }
352 }
353
354 fn open(&mut self) -> ExecResult<()> {
355 self.skipped = 0;
356 self.emitted = 0;
357 if let Some(with_ties) = self.with_ties.as_mut() {
358 with_ties.boundary = None;
359 with_ties.finished = false;
360 }
361 self.directional_state = DirectionalLimitState::Initial;
362 self.directional_position = 0;
363 self.directional_current = None;
364 self.child.open()
365 }
366
367 fn next(&mut self) -> ExecResult<Option<Batch>> {
368 if self.limit == Some(0)
369 || self
370 .with_ties
371 .as_ref()
372 .is_some_and(|with_ties| with_ties.finished)
373 || self.with_ties.is_none() && self.limit.is_some_and(|limit| self.emitted >= limit)
374 {
375 return Ok(None);
376 }
377 loop {
378 let Some(batch) = self.child.next()? else {
379 return Ok(None);
380 };
381 let mut buf = Vec::new();
382 for row in batch.rows {
383 if self.skipped < self.offset {
384 self.skipped += 1;
385 continue;
386 }
387 if let Some(lim) = self.limit {
388 if self.emitted >= lim {
389 let Some(with_ties) = self.with_ties.as_mut() else {
390 return if buf.is_empty() {
391 Ok(None)
392 } else {
393 Ok(Some(Batch::from_physical_rows(self.schema.clone(), buf)))
394 };
395 };
396 let values = with_ties
397 .keys
398 .iter()
399 .map(|key| {
400 with_ties
401 .evaluator
402 .evaluate_physical(&key.expr, &self.schema, &row)
403 })
404 .collect::<ExecResult<Vec<_>>>()?;
405 let boundary = with_ties.boundary.as_ref().ok_or_else(|| {
406 crate::ExecError::Other(
407 "WITH TIES boundary was not captured".to_string(),
408 )
409 })?;
410 if compare_sort_key_values(&with_ties.keys, boundary, &values)
411 != std::cmp::Ordering::Equal
412 {
413 with_ties.finished = true;
414 return if buf.is_empty() {
415 Ok(None)
416 } else {
417 Ok(Some(Batch::from_physical_rows(self.schema.clone(), buf)))
418 };
419 }
420 } else if self.with_ties.is_some() && self.emitted + 1 == lim {
421 let with_ties = self.with_ties.as_mut().expect("checked above");
422 with_ties.boundary = Some(
423 with_ties
424 .keys
425 .iter()
426 .map(|key| {
427 with_ties.evaluator.evaluate_physical(
428 &key.expr,
429 &self.schema,
430 &row,
431 )
432 })
433 .collect::<ExecResult<Vec<_>>>()?,
434 );
435 }
436 }
437 buf.push(row);
438 self.emitted += 1;
439 }
440 if !buf.is_empty() {
441 return Ok(Some(Batch::from_physical_rows(self.schema.clone(), buf)));
442 }
443 }
444 }
445
446 fn next_direction(&mut self, direction: PhysicalScanDirection) -> ExecResult<Option<Batch>> {
447 self.next_directional(direction)
448 }
449
450 fn rewind(&mut self) -> ExecResult<()> {
451 self.reset_directional_state();
452 self.child.rewind()
453 }
454
455 fn close(&mut self) -> ExecResult<()> {
456 self.child.close()
457 }
458}