use std::{
collections::{BTreeSet, HashSet},
sync::Arc,
};
use arrow::{
array::new_empty_array,
datatypes::{Schema, SchemaRef},
record_batch::{RecordBatch, RecordBatchOptions},
};
use crate::error::{QueryError, QueryErrorCode, Result};
mod diagnostics;
mod footer_facts;
mod location;
mod parquet;
mod predicate;
mod read_policy;
mod reader_predicate_literals;
mod validation;
pub(crate) use diagnostics::parquet_source_stable_id_fact;
pub use diagnostics::{
ByteRangeDisposition, PartitionColumnDisposition, PredicateDisposition, RejectedSourceFeature,
ScanLimitDisposition, SchemaMappingDisposition, SelectedRowGroupStats, SourceDiagnostics,
ZeroColumnCountDisposition,
};
#[doc(hidden)]
pub use diagnostics::{
local_parquet_file_row_group_metadata_count, parquet_file_path_fact, parquet_file_stable_id_fact,
parquet_source_path_fact, redacted_object_store_path_diagnostic, redacted_path_diagnostic,
};
pub use footer_facts::SourceFooterFacts;
pub use location::ParquetSourceLocation;
pub use parquet::{
GRAPH_SIMPLE_DIRECTED_EDGE_TOPOLOGY_PARQUET_METADATA_KEY, GRAPH_VERTEX_UPPER_BOUND_PARQUET_METADATA_KEY,
GraphSimpleDirectedEdgeTopology, LocalParquetFile, LocalParquetSource, MissingColumn, ParquetReaderDecimalType,
ParquetReaderDecimalTypeId, PartitionColumn, SourceColumnRef, ZERO_COLUMN_COUNT_COLUMN,
};
pub use read_policy::{
CredentialMode, LakehouseCacheFillMode, LakehouseCacheReadMode, LakehouseCacheStatus, ParquetReadStrategy,
ResolvedParquetInput, ResolvedParquetSource,
};
#[doc(hidden)]
pub use reader_predicate_literals::{
ReaderPredicateDecimalScaleError, reader_predicate_expr_references_decimal_column,
reader_predicate_integer_literal_as, reader_predicate_integer_literal_as_decimal_raw,
reader_predicate_rescale_decimal_raw, reader_predicate_unsigned_literal_as,
};
pub(crate) use reader_predicate_literals::{
reader_predicate_decimal_raw_for_integer_value, reader_predicate_integer_literal_raw,
reader_predicate_unsigned_literal_raw,
};
#[doc(hidden)]
pub use validation::validate_literal_batch_source;
pub use validation::validate_local_parquet_source;
pub(crate) fn local_parquet_execution_frame_columns(source: &LocalParquetSource) -> BTreeSet<String> {
local_parquet_execution_frame_schema(source).map_or_else(
|_| source.projected_output_columns.iter().chain(&source.pure_filter_columns).cloned().collect(),
|schema| schema.fields().iter().map(|field| field.name().clone()).collect(),
)
}
#[doc(hidden)]
pub fn local_parquet_execution_frame_schema(source: &LocalParquetSource) -> Result<SchemaRef> {
if source.is_zero_column_count_source() {
return Ok(Arc::clone(&source.schema));
}
if source.is_count_star_probe_source() {
return Ok(Arc::clone(source.output_schema()));
}
let mut fields = Vec::with_capacity(source.projected_output_columns.len() + source.pure_filter_columns.len());
let mut seen = HashSet::with_capacity(fields.capacity());
for column in source.projected_output_columns.iter().chain(&source.pure_filter_columns) {
if !seen.insert(column.as_str()) {
continue;
}
let field = source.schema.field_with_name(column).map_err(|error| {
QueryError::new(
QueryErrorCode::InvalidPlan,
"query_engine.local_parquet_source.execution_frame_schema",
"local parquet execution frame column is missing from the source schema",
)
.with_fact("column", column.as_str())
.with_fact("source_fields", source.schema.fields().len())
.with_source(error)
})?;
fields.push(field.clone());
}
Ok(Arc::new(Schema::new(fields)))
}
#[derive(Debug, Clone, PartialEq)]
#[expect(clippy::large_enum_variant, reason = "SourceNode variants represent disjoint planning sources")]
pub enum SourceNode {
LocalParquet(LocalParquetSource),
GpuResident(GpuResidentSource),
LiteralBatch(LiteralBatchSource),
}
impl SourceNode {
pub(crate) fn output_schema(&self) -> &SchemaRef {
match self {
Self::LocalParquet(source) => source.output_schema(),
Self::GpuResident(source) => &source.schema,
Self::LiteralBatch(source) => &source.schema,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct LiteralBatchSource {
pub schema: SchemaRef,
pub batches: Vec<RecordBatch>,
}
impl LiteralBatchSource {
pub fn new(schema: SchemaRef, batches: Vec<RecordBatch>) -> Self {
Self { schema, batches }
}
pub fn placeholder_row() -> Self {
let schema = Arc::new(Schema::empty());
let batch = RecordBatch::try_new_with_options(
Arc::clone(&schema),
vec![],
&RecordBatchOptions::new().with_row_count(Some(1)),
)
.expect("one-row empty RecordBatch is valid");
Self::new(schema, vec![batch])
}
pub fn empty(schema: SchemaRef) -> Self {
let columns = schema.fields().iter().map(|field| new_empty_array(field.data_type())).collect::<Vec<_>>();
let batch = RecordBatch::try_new_with_options(
Arc::clone(&schema),
columns,
&RecordBatchOptions::new().with_row_count(Some(0)),
)
.expect("zero-row RecordBatch must match its schema");
Self::new(schema, vec![batch])
}
pub fn row_count(&self) -> usize {
self.batches.iter().map(RecordBatch::num_rows).sum()
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct GpuResidentSource {
pub input_name: String,
pub schema: SchemaRef,
pub row_count_upper_bound: Option<u64>,
pub graph_vertex_upper_bound: Option<u64>,
pub(crate) unique_key_columns: Option<BTreeSet<String>>,
}
impl GpuResidentSource {
pub fn new(input_name: impl Into<String>, schema: SchemaRef) -> Self {
Self {
input_name: input_name.into(),
schema,
row_count_upper_bound: None,
graph_vertex_upper_bound: None,
unique_key_columns: None,
}
}
pub fn with_row_count_upper_bound(mut self, row_count_upper_bound: u64) -> Self {
self.row_count_upper_bound = Some(row_count_upper_bound);
self
}
pub fn with_graph_vertex_upper_bound(mut self, graph_vertex_upper_bound: u64) -> Self {
self.graph_vertex_upper_bound = Some(graph_vertex_upper_bound);
self
}
pub fn with_unique_key_columns(mut self, unique_key_columns: BTreeSet<String>) -> Self {
self.unique_key_columns = Some(unique_key_columns);
self
}
}
#[cfg(test)]
mod tests {
use arrow::datatypes::{DataType, Field, Schema};
use super::GpuResidentSource;
#[test]
fn gpu_resident_source_keeps_row_and_graph_vertex_bounds_distinct() {
let source = GpuResidentSource::new(
"edges",
std::sync::Arc::new(Schema::new(vec![Field::new("src", DataType::Int64, false)])),
)
.with_row_count_upper_bound(7)
.with_graph_vertex_upper_bound(11);
assert_eq!(source.row_count_upper_bound, Some(7));
assert_eq!(source.graph_vertex_upper_bound, Some(11));
}
}