use crate::catalog::schema::TableSchema;
use crate::catalog::types::{ColumnType, Row, Value};
use crate::query::error::QueryError;
use crate::query::plan::{Order, Query};
use crate::storage::encoded_key::EncodedKey;
use crate::storage::keyspace::{KeyspaceSnapshot, TableData};
use std::ops::Bound;
use super::CursorToken;
use super::pagination::PageWindow;
pub(super) fn pk_encoded_order_matches_value_order(col_type: Option<&ColumnType>) -> bool {
matches!(
col_type,
Some(
ColumnType::Integer
| ColumnType::U8
| ColumnType::U64
| ColumnType::Timestamp
| ColumnType::Text
)
)
}
pub(super) fn cursor_value_matches_pk_type(value: &Value, col_type: &ColumnType) -> bool {
matches!(
(value, col_type),
(Value::Integer(_), ColumnType::Integer)
| (Value::U8(_), ColumnType::U8)
| (Value::U64(_), ColumnType::U64)
| (Value::Timestamp(_), ColumnType::Timestamp)
| (Value::Text(_), ColumnType::Text)
)
}
pub(super) struct PkOrderedScanRequest<'a> {
pub snapshot: &'a KeyspaceSnapshot,
pub schema: &'a TableSchema,
pub query: &'a Query,
pub table: Option<&'a TableData>,
pub cursor_state: &'a Option<CursorToken>,
pub page_window: &'a PageWindow,
pub max_scan_rows: usize,
}
pub(super) struct PkOrderedScan {
pub rows: Vec<Row>,
}
pub(super) fn pk_ordered_scan_for_query(
request: PkOrderedScanRequest<'_>,
) -> Result<Option<PkOrderedScan>, QueryError> {
let query = request.query;
if query.predicate.is_some()
|| query.distinct
|| !query.group_by.is_empty()
|| !query.aggregates.is_empty()
|| query.having.is_some()
|| query.limit.is_none_or(|limit| limit == 0)
{
return Ok(None);
}
let [(order_col, order)] = query.order_by.as_slice() else {
return Ok(None);
};
if request.schema.primary_key.len() != 1 || request.schema.primary_key[0] != *order_col {
return Ok(None);
}
let pk_type = request
.schema
.columns
.iter()
.find(|c| c.name == *order_col)
.map(|c| &c.col_type);
if !pk_encoded_order_matches_value_order(pk_type) {
return Ok(None);
}
let Some(table) = request.table else {
return Ok(Some(PkOrderedScan { rows: Vec::new() }));
};
if !table.row_segments.is_empty() || !table.row_tombstones.is_empty() {
return Ok(None);
}
let reverse = matches!(order, Order::Desc);
let bounds: (Bound<EncodedKey>, Bound<EncodedKey>) = match request.cursor_state {
None => (Bound::Unbounded, Bound::Unbounded),
Some(cursor) => {
if table.rows.len() > request.max_scan_rows {
return Err(QueryError::ScanBoundExceeded {
estimated_rows: table.rows.len() as u64,
max_scan_rows: request.max_scan_rows as u64,
});
}
if request.page_window.row_offset_count > 0
|| cursor.last_sort_key.len() != 1
|| !cursor_value_matches_pk_type(
&cursor.last_sort_key[0],
pk_type.expect("checked by order gate"),
)
{
return Ok(None);
}
let key = EncodedKey::from_values(&cursor.last_sort_key);
if reverse {
(Bound::Unbounded, Bound::Excluded(key))
} else {
(Bound::Excluded(key), Bound::Unbounded)
}
}
};
let offset = match request.cursor_state {
Some(_) => 0,
None => request.page_window.row_offset_count,
};
let limit = request.page_window.page_read_limit;
let mut rows = Vec::with_capacity(limit.min(1024));
let range = table.rows.range(bounds);
let stored_rows: Box<dyn Iterator<Item = &crate::storage::keyspace::StoredRow>> = if reverse {
Box::new(range.rev().map(|(_, stored)| stored))
} else {
Box::new(range.map(|(_, stored)| stored))
};
let mut skipped = 0usize;
for stored in stored_rows {
if rows.len() >= limit {
break;
}
if skipped < offset {
skipped += 1;
continue;
}
rows.push(request.snapshot.materialize_row(stored)?.into_owned());
}
Ok(Some(PkOrderedScan { rows }))
}