use super::super::input::Source;
use super::super::table::RawCsv;
use super::super::typing::{inferred_type_keyword, ColumnInference, IdInference};
use crate::datatypes::values::ColumnType;
use indexmap::IndexMap;
use std::collections::HashMap;
pub(super) struct Prepared<'a> {
pub resolved: IndexMap<String, String>,
pub resolved_ids: IndexMap<String, ColumnType>,
pub chunks: Box<dyn Iterator<Item = Result<RawCsv, String>> + 'a>,
pub extra_pass: bool,
}
pub(super) fn prepare_chunks<'a, F>(
source: &'a dyn Source,
chunk_size: usize,
declared: &HashMap<String, String>,
id_columns: &[String],
row_preserving: bool,
mut prepare: F,
) -> Result<Prepared<'a>, String>
where
F: FnMut(&mut RawCsv) -> Vec<String>,
{
let mut stream = source.chunks(chunk_size)?;
let Some(first) = stream.next() else {
return Ok(Prepared {
resolved: IndexMap::new(),
resolved_ids: IndexMap::new(),
chunks: Box::new(std::iter::empty()),
extra_pass: false,
});
};
let mut first = first?;
let keep = prepare(&mut first);
let mut inferences: IndexMap<String, ColumnInference> = IndexMap::new();
observe(&mut inferences, &first, &keep, declared);
let mut ids: IndexMap<String, IdInference> = IndexMap::new();
observe_ids(&mut ids, &first, id_columns);
if inferences.is_empty() && ids.is_empty() {
return Ok(Prepared {
resolved: IndexMap::new(),
resolved_ids: IndexMap::new(),
chunks: Box::new(std::iter::once(Ok(first)).chain(stream)),
extra_pass: false,
});
}
let Some(second) = stream.next() else {
return Ok(Prepared {
resolved: keywords(inferences),
resolved_ids: id_types(ids),
chunks: Box::new(std::iter::once(Ok(first))),
extra_pass: false,
});
};
drop(first);
if row_preserving {
scan_remaining(source, &mut inferences, &mut ids)?;
} else {
let mut pending = Some(second);
while let Some(chunk) = pending.take().or_else(|| stream.next()) {
if settled(&inferences, &ids) {
break;
}
let mut raw = chunk?;
let keep = prepare(&mut raw);
observe(&mut inferences, &raw, &keep, declared);
observe_ids(&mut ids, &raw, id_columns);
}
}
Ok(Prepared {
resolved: keywords(inferences),
resolved_ids: id_types(ids),
chunks: source.chunks(chunk_size)?,
extra_pass: true,
})
}
fn scan_remaining(
source: &dyn Source,
inferences: &mut IndexMap<String, ColumnInference>,
ids: &mut IndexMap<String, IdInference>,
) -> Result<(), String> {
let names: Vec<String> = inferences.keys().chain(ids.keys()).cloned().collect();
let refs: Vec<&str> = names.iter().map(String::as_str).collect();
let split = inferences.len();
let mut values: Vec<ColumnInference> = inferences.values().copied().collect();
let mut id_states: Vec<IdInference> = ids.values().copied().collect();
let mut unsettled = values.iter().filter(|s| !s.is_settled()).count()
+ id_states.iter().filter(|s| !s.is_settled()).count();
source.scan_columns(&refs, &mut |slot, cell| {
if slot < split {
let state = &mut values[slot];
if !state.is_settled() {
state.observe(cell);
if state.is_settled() {
unsettled -= 1;
}
}
} else {
let state = &mut id_states[slot - split];
if !state.is_settled() {
state.observe(cell);
if state.is_settled() {
unsettled -= 1;
}
}
}
unsettled > 0
})?;
for (state, slot) in values.into_iter().zip(inferences.values_mut()) {
*slot = state;
}
for (state, slot) in id_states.into_iter().zip(ids.values_mut()) {
*slot = state;
}
Ok(())
}
fn observe(
inferences: &mut IndexMap<String, ColumnInference>,
raw: &RawCsv,
keep: &[String],
declared: &HashMap<String, String>,
) {
for name in keep {
if declared.contains_key(name) {
continue;
}
let Some(idx) = raw.col_index(name) else {
continue;
};
inferences
.entry(name.clone())
.or_default()
.observe_column(raw, idx);
}
}
fn settled(
inferences: &IndexMap<String, ColumnInference>,
ids: &IndexMap<String, IdInference>,
) -> bool {
inferences.values().all(|i| i.is_settled()) && ids.values().all(|i| i.is_settled())
}
fn observe_ids(ids: &mut IndexMap<String, IdInference>, raw: &RawCsv, columns: &[String]) {
for name in columns {
let Some(idx) = raw.col_index(name) else {
continue;
};
let state = ids.entry(name.clone()).or_default();
for (r, row) in raw.rows.iter().enumerate() {
if state.is_settled() {
break;
}
if raw.nulls[r][idx] {
continue;
}
state.observe(&row[idx]);
}
}
}
fn id_types(ids: IndexMap<String, IdInference>) -> IndexMap<String, ColumnType> {
ids.into_iter().map(|(k, v)| (k, v.resolve())).collect()
}
fn keywords(inferences: IndexMap<String, ColumnInference>) -> IndexMap<String, String> {
inferences
.into_iter()
.filter_map(|(name, inference)| {
inferred_type_keyword(&inference.resolve()).map(|kw| (name, kw.to_string()))
})
.collect()
}
pub(super) fn prepass_warning(where_: &str, prepared: &Prepared<'_>) -> Option<String> {
if !prepared.extra_pass || prepared.resolved.is_empty() {
return None;
}
let cols: Vec<&str> = prepared.resolved.keys().map(String::as_str).collect();
Some(format!(
"{where_}: {} column(s) have no declared type ({}), so the loader read the input twice \
— once to infer them over every row, once to load. Declaring them keeps the type \
stable and skips the extra pass.",
cols.len(),
cols.join(", ")
))
}