Skip to content

Commit f55ad02

Browse files
authored
[core] Add primary-key vector read kernel (#517)
1 parent aaf8a5c commit f55ad02

9 files changed

Lines changed: 3362 additions & 44 deletions

File tree

crates/paimon/src/table/data_file_reader.rs

Lines changed: 136 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,35 @@ impl DataFileReader {
7171
self
7272
}
7373

74+
// These three accessors exist for the sibling `pk_vector_position_read`,
75+
// `pk_vector_indexed_split_read`, and `pk_vector_orchestrator` modules. The
76+
// read path that drives that chain lands in a later change, so under clippy
77+
// -D warnings they read as dead_code until then.
78+
/// Return a copy with a replaced read-type. Used by `pk_vector_position_read`
79+
/// to inject the internal `_ROW_ID` column for physical-position recovery.
80+
#[allow(dead_code)]
81+
pub(super) fn with_read_type(mut self, read_type: Vec<DataField>) -> Self {
82+
self.read_type = read_type;
83+
self
84+
}
85+
86+
/// The effective read-type (requested output fields) of this reader.
87+
/// Exposed for the sibling `pk_vector_position_read` module.
88+
#[allow(dead_code)]
89+
pub(super) fn read_type(&self) -> &[DataField] {
90+
&self.read_type
91+
}
92+
93+
/// True if any configured predicate can actually drop rows. A lone
94+
/// `Predicate::AlwaysTrue` keeps every row in order and is not row-filtering,
95+
/// matching `reject_row_id_with_predicate`'s notion.
96+
#[allow(dead_code)]
97+
pub(super) fn has_row_filtering_predicate(&self) -> bool {
98+
self.predicates
99+
.iter()
100+
.any(|p| !matches!(p, Predicate::AlwaysTrue))
101+
}
102+
74103
/// Reject projecting `_ROW_ID` alongside a data predicate. `_ROW_ID` is
75104
/// assigned positionally from post-filter batch row counts, so a residual
76105
/// filter that drops rows would desync it. (`_ROW_ID` predicates travel via
@@ -111,35 +140,16 @@ impl DataFileReader {
111140
Ok(try_stream! {
112141
for split in splits {
113142
// Create DV factory for this split only.
114-
let dv_factory = if split
115-
.data_deletion_files()
116-
.is_some_and(|files| files.iter().any(Option::is_some))
117-
{
118-
Some(
119-
DeletionVectorFactory::new(
120-
&reader.file_io,
121-
split.data_files(),
122-
split.data_deletion_files(),
123-
)
124-
.await?,
125-
)
126-
} else {
127-
None
128-
};
143+
let dv_factory = reader.build_split_dv_factory(&split).await?;
129144

130145
for file_meta in split.data_files().to_vec() {
131-
let dv = dv_factory
132-
.as_ref()
133-
.and_then(|factory| factory.get_deletion_vector(&file_meta.file_name))
134-
.cloned();
146+
let dv = DataFileReader::deletion_vector_for_file(
147+
dv_factory.as_ref(),
148+
&file_meta.file_name,
149+
);
135150

136151
// Load data file's schema if it differs from the table schema.
137-
let data_fields: Option<Vec<DataField>> = if file_meta.schema_id != reader.table_schema_id {
138-
let data_schema = reader.schema_manager.schema(file_meta.schema_id).await?;
139-
Some(data_schema.fields().to_vec())
140-
} else {
141-
None
142-
};
152+
let data_fields = reader.derive_data_fields(&file_meta).await?;
143153

144154
let mut stream = reader.read_single_file_stream(
145155
&split,
@@ -157,6 +167,55 @@ impl DataFileReader {
157167
.boxed())
158168
}
159169

170+
/// Build the deletion-vector factory for a split, or `None` when the split
171+
/// carries no deletion files. One factory per split (not per file), matching
172+
/// `read`. Shared with `pk_vector_indexed_split_read` so that path reuses the
173+
/// exact production derivation instead of duplicating it.
174+
pub(super) async fn build_split_dv_factory(
175+
&self,
176+
split: &DataSplit,
177+
) -> crate::Result<Option<DeletionVectorFactory>> {
178+
if split
179+
.data_deletion_files()
180+
.is_some_and(|files| files.iter().any(Option::is_some))
181+
{
182+
Ok(Some(
183+
DeletionVectorFactory::new(
184+
&self.file_io,
185+
split.data_files(),
186+
split.data_deletion_files(),
187+
)
188+
.await?,
189+
))
190+
} else {
191+
Ok(None)
192+
}
193+
}
194+
195+
/// Look up the deletion vector for one file from a split-level factory.
196+
pub(super) fn deletion_vector_for_file(
197+
factory: Option<&DeletionVectorFactory>,
198+
file_name: &str,
199+
) -> Option<Arc<DeletionVector>> {
200+
factory
201+
.and_then(|factory| factory.get_deletion_vector(file_name))
202+
.cloned()
203+
}
204+
205+
/// Load the data file's own schema fields when its `schema_id` differs from
206+
/// the table schema id (schema evolution); `None` when they match.
207+
pub(super) async fn derive_data_fields(
208+
&self,
209+
file_meta: &DataFileMeta,
210+
) -> crate::Result<Option<Vec<DataField>>> {
211+
if file_meta.schema_id != self.table_schema_id {
212+
let data_schema = self.schema_manager.schema(file_meta.schema_id).await?;
213+
Ok(Some(data_schema.fields().to_vec()))
214+
} else {
215+
Ok(None)
216+
}
217+
}
218+
160219
/// Read a single parquet file from a split, returning a lazy stream of batches.
161220
/// Optionally applies a deletion vector.
162221
///
@@ -994,6 +1053,58 @@ mod tests {
9941053
use roaring::RoaringBitmap;
9951054
use std::io;
9961055

1056+
#[test]
1057+
fn test_accessors_expose_read_type_and_row_filtering_predicate() {
1058+
use crate::spec::{DataField, DataType, IntType};
1059+
let fields = vec![DataField::new(
1060+
0,
1061+
"id".to_string(),
1062+
DataType::Int(IntType::new()),
1063+
)];
1064+
let file_io = crate::io::FileIOBuilder::new("memory").build().unwrap();
1065+
let schema_manager = SchemaManager::new(file_io.clone(), "memory:/acc".to_string());
1066+
1067+
let no_pred = DataFileReader::new(
1068+
file_io.clone(),
1069+
schema_manager.clone(),
1070+
1,
1071+
fields.clone(),
1072+
fields.clone(),
1073+
vec![],
1074+
);
1075+
assert_eq!(no_pred.read_type().len(), 1);
1076+
assert!(!no_pred.has_row_filtering_predicate());
1077+
1078+
let always_true = DataFileReader::new(
1079+
file_io.clone(),
1080+
schema_manager.clone(),
1081+
1,
1082+
fields.clone(),
1083+
fields.clone(),
1084+
vec![crate::spec::Predicate::AlwaysTrue],
1085+
);
1086+
assert!(
1087+
!always_true.has_row_filtering_predicate(),
1088+
"AlwaysTrue is not row-filtering"
1089+
);
1090+
1091+
let filtering = PredicateBuilder::new(&fields)
1092+
.equal("id", crate::spec::Datum::Int(10))
1093+
.unwrap();
1094+
let with_filter = DataFileReader::new(
1095+
file_io.clone(),
1096+
schema_manager.clone(),
1097+
1,
1098+
fields.clone(),
1099+
fields.clone(),
1100+
vec![filtering],
1101+
);
1102+
assert!(
1103+
with_filter.has_row_filtering_predicate(),
1104+
"a real (non-AlwaysTrue) predicate is row-filtering"
1105+
);
1106+
}
1107+
9971108
struct MemOutputFile {
9981109
data: Vec<u8>,
9991110
}

crates/paimon/src/table/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,9 @@ mod lumina_index_build_builder;
5555
pub(crate) mod merge_tree_split_generator;
5656
mod partition_filter;
5757
mod partition_stat;
58+
mod pk_vector_indexed_split_read;
59+
mod pk_vector_orchestrator;
60+
mod pk_vector_position_read;
5861
mod postpone_file_writer;
5962
mod prepared_files;
6063
mod read_builder;

0 commit comments

Comments
 (0)