Path: blob/main/crates/polars-stream/src/physical_plan/visualization/mod.rs
7884 views
use std::collections::VecDeque;12use polars_core::prelude::SortMultipleOptions;3use polars_ops::frame::{JoinArgs, JoinType};4use polars_plan::dsl::{5FileSinkOptions, JoinTypeOptionsIR, PartitionStrategyIR, PartitionVariantIR,6PartitionedSinkOptionsIR, SinkOptions, SinkTarget, SortColumnIR, UnifiedSinkArgs,7};8use polars_plan::plans::expr_ir::ExprIR;9use polars_plan::prelude::AExpr;10use polars_utils::arena::Arena;11use polars_utils::pl_str::PlSmallStr;12use polars_utils::{IdxSize, format_pl_smallstr};1314pub mod models;15pub use models::{PhysNodeInfo, PhysicalPlanVisualizationData};16use slotmap::{SecondaryMap, SlotMap};1718use crate::physical_plan::visualization::models::{Edge, PhysNodeProperties};19use crate::physical_plan::{PhysNode, PhysNodeKey, PhysNodeKind};2021pub fn generate_visualization_data(22title: PlSmallStr,23roots: &[PhysNodeKey],24phys_sm: &SlotMap<PhysNodeKey, PhysNode>,25expr_arena: &Arena<AExpr>,26) -> PhysicalPlanVisualizationData {27let (nodes_list, edges) = PhysicalPlanVisualizationDataGenerator {28phys_sm,29expr_arena,30queue: VecDeque::from_iter(roots.iter().copied()),31marked_for_visit: SecondaryMap::from_iter(roots.iter().map(|r| (*r, ()))),32nodes_list: vec![],33edges: vec![],34}35.generate();3637PhysicalPlanVisualizationData {38title,39num_roots: roots.len().try_into().unwrap(),40nodes: nodes_list,41edges,42}43}4445struct PhysicalPlanVisualizationDataGenerator<'a> {46phys_sm: &'a SlotMap<PhysNodeKey, PhysNode>,47expr_arena: &'a Arena<AExpr>,48queue: VecDeque<PhysNodeKey>,49marked_for_visit: SecondaryMap<PhysNodeKey, ()>,50nodes_list: Vec<PhysNodeInfo>,51edges: Vec<Edge>,52}5354impl PhysicalPlanVisualizationDataGenerator<'_> {55fn generate(mut self) -> (Vec<PhysNodeInfo>, Vec<Edge>) {56let mut node_inputs: Vec<PhysNodeKey> = vec![];5758while let Some(key) = self.queue.pop_front() {59let node: &PhysNode = self.phys_sm.get(key).unwrap();60let mut phys_node_info = self.get_phys_node_info(node, &mut node_inputs);61let current_node_key: u64 = key.0.as_ffi();62phys_node_info.id = current_node_key;6364for input_node in node_inputs.drain(..) {65self.edges66.push(Edge::new(current_node_key, input_node.0.as_ffi()));6768let not_yet_marked = self.marked_for_visit.insert(input_node, ()).is_none();69if not_yet_marked {70self.queue.push_back(input_node);71}72}7374self.nodes_list.push(phys_node_info);75}7677assert!(self.queue.is_empty());78(self.nodes_list, self.edges)79}8081fn get_phys_node_info(82&self,83phys_node: &PhysNode,84phys_node_inputs: &mut Vec<PhysNodeKey>,85) -> PhysNodeInfo {86match phys_node.kind() {87PhysNodeKind::CallbackSink {88input,89function,90maintain_order,91chunk_size,92} => {93phys_node_inputs.push(input.node);9495let properties = PhysNodeProperties::CallbackSink {96callback_function: format_pl_smallstr!("{:?}", function),97maintain_order: *maintain_order,98chunk_size: *chunk_size,99};100101PhysNodeInfo {102title: properties.variant_name(),103properties,104..Default::default()105}106},107PhysNodeKind::DynamicSlice {108input,109offset,110length,111} => {112phys_node_inputs.push(input.node);113phys_node_inputs.push(offset.node);114phys_node_inputs.push(length.node);115116let properties = PhysNodeProperties::DynamicSlice;117118PhysNodeInfo {119title: properties.variant_name(),120properties,121..Default::default()122}123},124PhysNodeKind::FileSink {125target,126sink_options:127SinkOptions {128sync_on_close,129maintain_order,130mkdir,131},132file_type,133input,134cloud_options,135} => {136phys_node_inputs.push(input.node);137138let properties = PhysNodeProperties::FileSink {139target: match target {140SinkTarget::Path(p) => format_pl_smallstr!("Path({})", p.to_str()),141SinkTarget::Dyn(_) => PlSmallStr::from_static("DynWriteable"),142},143file_format: PlSmallStr::from_static(file_type.into()),144sync_on_close: *sync_on_close,145maintain_order: *maintain_order,146mkdir: *mkdir,147cloud_options: cloud_options.is_some(),148};149150PhysNodeInfo {151title: properties.variant_name(),152properties,153..Default::default()154}155},156PhysNodeKind::Filter { input, predicate } => {157phys_node_inputs.push(input.node);158159let properties = PhysNodeProperties::Filter {160predicate: format_pl_smallstr!("{}", predicate.display(self.expr_arena)),161};162163PhysNodeInfo {164title: properties.variant_name(),165properties,166..Default::default()167}168},169PhysNodeKind::GatherEvery { input, n, offset } => {170phys_node_inputs.push(input.node);171172let properties = PhysNodeProperties::GatherEvery {173n: (*n).try_into().unwrap(),174offset: (*offset).try_into().unwrap(),175};176177PhysNodeInfo {178title: properties.variant_name(),179properties,180..Default::default()181}182},183PhysNodeKind::GroupBy { input, key, aggs } => {184phys_node_inputs.push(input.node);185186let properties = PhysNodeProperties::GroupBy {187keys: expr_list(key, self.expr_arena),188aggs: expr_list(aggs, self.expr_arena),189};190191PhysNodeInfo {192title: properties.variant_name(),193properties,194..Default::default()195}196},197#[cfg(feature = "dynamic_group_by")]198PhysNodeKind::DynamicGroupBy {199input,200options,201aggs,202slice,203} => {204use polars_time::DynamicGroupOptions;205use polars_utils::IdxSize;206207phys_node_inputs.push(input.node);208209let DynamicGroupOptions {210index_column,211every,212period,213offset,214label,215include_boundaries,216closed_window,217start_by,218} = options;219220let properties = PhysNodeProperties::DynamicGroupBy {221index_column: index_column.clone(),222period: format_pl_smallstr!("{period}"),223every: format_pl_smallstr!("{every}"),224offset: format_pl_smallstr!("{offset}"),225start_by: PlSmallStr::from_static(start_by.into()),226label: PlSmallStr::from_static(label.into()),227include_boundaries: *include_boundaries,228closed_window: PlSmallStr::from_static(closed_window.into()),229aggs: expr_list(aggs, self.expr_arena),230slice: slice.map(|(o, l)| (IdxSize::into(o), IdxSize::into(l))),231};232233PhysNodeInfo {234title: properties.variant_name(),235properties,236..Default::default()237}238},239#[cfg(feature = "dynamic_group_by")]240PhysNodeKind::RollingGroupBy {241input,242index_column,243period,244offset,245closed,246slice,247aggs,248} => {249phys_node_inputs.push(input.node);250251let properties = PhysNodeProperties::RollingGroupBy {252index_column: index_column.clone(),253period: format_pl_smallstr!("{period}"),254offset: format_pl_smallstr!("{offset}"),255closed_window: PlSmallStr::from_static(closed.into()),256slice: slice.map(|(o, l)| (IdxSize::into(o), IdxSize::into(l))),257aggs: expr_list(aggs, self.expr_arena),258};259260PhysNodeInfo {261title: properties.variant_name(),262properties,263..Default::default()264}265},266PhysNodeKind::SortedGroupBy {267input,268key,269aggs,270slice,271} => {272phys_node_inputs.push(input.node);273274let properties = PhysNodeProperties::SortedGroupBy {275key: key.clone(),276aggs: expr_list(aggs, self.expr_arena),277slice: slice.map(|(o, l)| (IdxSize::into(o), IdxSize::into(l))),278};279280PhysNodeInfo {281title: properties.variant_name(),282properties,283..Default::default()284}285},286PhysNodeKind::InMemoryMap {287input,288map: _, // dyn DataFrameUdf289format_str,290} => {291phys_node_inputs.push(input.node);292293let properties = PhysNodeProperties::InMemoryMap {294format_str: format_str.as_deref().map_or(295PlSmallStr::from_static(296"error: prepare_visualization was not set during conversion",297),298PlSmallStr::from_str,299),300};301302PhysNodeInfo {303title: properties.variant_name(),304properties,305..Default::default()306}307},308PhysNodeKind::InMemorySink { input } => {309phys_node_inputs.push(input.node);310311let properties = PhysNodeProperties::InMemorySink;312313PhysNodeInfo {314title: properties.variant_name(),315properties,316..Default::default()317}318},319PhysNodeKind::InMemorySource { df } => {320let properties = PhysNodeProperties::InMemorySource {321n_rows: df.height().try_into().unwrap(),322schema_names: df.schema().iter_names_cloned().collect(),323};324325PhysNodeInfo {326title: properties.variant_name(),327properties,328..Default::default()329}330},331PhysNodeKind::InputIndependentSelect { selectors } => {332let properties = PhysNodeProperties::InputIndependentSelect {333selectors: expr_list(selectors, self.expr_arena),334};335336PhysNodeInfo {337title: properties.variant_name(),338properties,339..Default::default()340}341},342// Joins343PhysNodeKind::CrossJoin {344input_left,345input_right,346args,347} => {348phys_node_inputs.push(input_left.node);349phys_node_inputs.push(input_right.node);350351let JoinArgs {352how: _,353validation: _,354suffix,355slice: _,356nulls_equal: _,357coalesce: _,358maintain_order,359} = args;360361let properties = PhysNodeProperties::CrossJoin {362maintain_order: *maintain_order,363suffix: suffix.clone(),364};365366PhysNodeInfo {367title: properties.variant_name(),368properties,369..Default::default()370}371},372PhysNodeKind::EquiJoin {373input_left,374input_right,375left_on,376right_on,377args,378} => {379phys_node_inputs.push(input_left.node);380phys_node_inputs.push(input_right.node);381382let JoinArgs {383how,384validation,385suffix,386// Lowers to a separate node387slice: _,388nulls_equal,389coalesce,390maintain_order,391} = args;392393let properties = PhysNodeProperties::EquiJoin {394how: format_pl_smallstr!("{}", how),395left_on: expr_list(left_on, self.expr_arena),396right_on: expr_list(right_on, self.expr_arena),397nulls_equal: *nulls_equal,398coalesce: *coalesce,399maintain_order: *maintain_order,400validation: *validation,401suffix: suffix.clone(),402};403404PhysNodeInfo {405title: properties.variant_name(),406properties,407..Default::default()408}409},410PhysNodeKind::InMemoryJoin {411input_left,412input_right,413left_on,414right_on,415args:416JoinArgs {417how,418validation,419suffix,420slice,421nulls_equal,422coalesce,423maintain_order,424},425options,426} => {427phys_node_inputs.push(input_left.node);428phys_node_inputs.push(input_right.node);429430let properties = match how {431JoinType::AsOf(asof_options) => {432use polars_ops::frame::AsOfOptions;433434#[expect(unused_variables)]435let AsOfOptions {436strategy,437tolerance,438tolerance_str,439left_by,440right_by,441allow_eq,442check_sortedness,443} = asof_options.as_ref();444445assert_eq!(left_on.len(), 1);446assert_eq!(right_on.len(), 1);447448PhysNodeProperties::InMemoryAsOfJoin {449left_on: format_pl_smallstr!("{}", left_on[0].display(self.expr_arena)),450right_on: format_pl_smallstr!(451"{}",452right_on[0].display(self.expr_arena)453),454left_by: left_by.clone(),455right_by: right_by.clone(),456strategy: *strategy,457tolerance: tolerance.as_ref().map(|scalar| {458[459format_pl_smallstr!("{}", scalar.value()),460format_pl_smallstr!("{:?}", scalar.dtype()),461]462}),463suffix: suffix.clone(),464slice: convert_opt_slice(slice),465coalesce: *coalesce,466allow_eq: *allow_eq,467check_sortedness: *check_sortedness,468}469},470JoinType::IEJoin => {471use polars_ops::frame::IEJoinOptions;472473let Some(JoinTypeOptionsIR::IEJoin(IEJoinOptions {474operator1,475operator2,476})) = options477else {478unreachable!()479};480481PhysNodeProperties::InMemoryIEJoin {482left_on: expr_list(left_on, self.expr_arena),483right_on: expr_list(right_on, self.expr_arena),484inequality_operators: if let Some(operator2) = operator2 {485vec![*operator1, *operator2]486} else {487vec![*operator1]488},489suffix: suffix.clone(),490slice: convert_opt_slice(slice),491}492},493JoinType::Cross => unreachable!(),494_ => PhysNodeProperties::InMemoryJoin {495how: format_pl_smallstr!("{}", how),496left_on: expr_list(left_on, self.expr_arena),497right_on: expr_list(right_on, self.expr_arena),498nulls_equal: *nulls_equal,499coalesce: *coalesce,500maintain_order: *maintain_order,501validation: *validation,502suffix: suffix.clone(),503slice: convert_opt_slice(slice),504},505};506507PhysNodeInfo {508title: properties.variant_name(),509properties,510..Default::default()511}512},513PhysNodeKind::Map {514input,515map,516format_str,517} => {518phys_node_inputs.push(input.node);519520let properties = PhysNodeProperties::Map {521display_str: map.display_str(),522format_str: format_str.as_deref().map_or(523PlSmallStr::from_static(524"error: prepare_visualization was not set during conversion",525),526PlSmallStr::from_str,527),528};529530PhysNodeInfo {531title: properties.variant_name(),532properties,533..Default::default()534}535},536PhysNodeKind::MultiScan {537scan_sources,538file_reader_builder,539cloud_options: _,540file_projection_builder,541output_schema: _,542row_index,543pre_slice,544predicate,545predicate_file_skip_applied,546hive_parts,547include_file_paths,548cast_columns_policy: _,549missing_columns_policy: _,550forbid_extra_columns: _,551deletion_files,552table_statistics,553file_schema: _,554} => {555let pre_slice = pre_slice556.clone()557.map(|x| <(i64, usize)>::try_from(x).unwrap());558559let properties = PhysNodeProperties::MultiScan {560scan_type: file_reader_builder.reader_name().into(),561num_sources: scan_sources.len().try_into().unwrap(),562first_source: scan_sources563.first()564.map(|x| x.to_include_path_name().into()),565projected_file_columns: file_projection_builder566.projected_names()567.cloned()568.collect(),569file_projection_builder_type: PlSmallStr::from_static(570file_projection_builder.into(),571),572row_index_name: row_index.as_ref().map(|ri| ri.name.clone()),573#[allow(clippy::useless_conversion)]574row_index_offset: row_index.as_ref().map(|ri| ri.offset.into()),575pre_slice: convert_opt_slice(&pre_slice),576predicate: predicate577.as_ref()578.map(|e| format_pl_smallstr!("{}", e.display(self.expr_arena))),579predicate_file_skip_applied: *predicate_file_skip_applied,580has_table_statistics: table_statistics.is_some(),581include_file_paths: include_file_paths.clone(),582deletion_files_type: deletion_files583.as_ref()584.map(|x| PlSmallStr::from_static(x.into())),585hive_columns: hive_parts586.as_ref()587.map(|x| x.df().schema().iter_names_cloned().collect()),588};589590PhysNodeInfo {591title: properties.variant_name(),592properties,593..Default::default()594}595},596PhysNodeKind::Multiplexer { input } => {597phys_node_inputs.push(input.node);598599let properties = PhysNodeProperties::Multiplexer;600601PhysNodeInfo {602title: properties.variant_name(),603properties,604..Default::default()605}606},607PhysNodeKind::NegativeSlice {608input,609offset,610length,611} => {612phys_node_inputs.push(input.node);613614let properties = PhysNodeProperties::NegativeSlice {615offset: (*offset),616length: (*length).try_into().unwrap(),617};618619PhysNodeInfo {620title: properties.variant_name(),621properties,622..Default::default()623}624},625PhysNodeKind::OrderedUnion { inputs } => {626for input in inputs {627phys_node_inputs.push(input.node);628}629630let properties = PhysNodeProperties::OrderedUnion {631num_inputs: inputs.len().try_into().unwrap(),632};633634PhysNodeInfo {635title: properties.variant_name(),636properties,637..Default::default()638}639},640PhysNodeKind::PartitionedSink {641input,642base_path,643file_path_cb,644sink_options:645SinkOptions {646sync_on_close,647maintain_order,648mkdir,649},650variant,651file_type,652cloud_options: _,653per_partition_sort_by,654finish_callback,655} => {656phys_node_inputs.push(input.node);657658let (659partition_variant_max_size,660partition_variant_key_exprs,661partition_variant_include_key,662) = match variant {663PartitionVariantIR::ByKey {664key_exprs,665include_key,666}667| PartitionVariantIR::Parted {668key_exprs,669include_key,670} => (671None,672Some(expr_list(key_exprs, self.expr_arena)),673Some(*include_key),674),675#[allow(clippy::useless_conversion)]676PartitionVariantIR::MaxSize(max_size) => (Some((*max_size).into()), None, None),677};678679let (680per_partition_sort_exprs,681per_partition_sort_descending,682per_partition_sort_nulls_last,683) = per_partition_sort_by684.as_ref()685.map_or((None, None, None), |x| {686let (a, (b, c)): (Vec<_>, (Vec<_>, Vec<_>)) = x687.iter()688.map(|x| {689(690format_pl_smallstr!("{}", x.expr.display(self.expr_arena)),691(x.descending, x.nulls_last),692)693})694.unzip();695696(Some(a), Some(b), Some(c))697});698699let properties = PhysNodeProperties::PartitionSink {700base_path: base_path.to_str().into(),701file_path_callback: file_path_cb.as_ref().map(|x| x.display_str()),702partition_variant: PlSmallStr::from_static(variant.into()),703partition_variant_max_size,704partition_variant_key_exprs,705partition_variant_include_key,706file_type: PlSmallStr::from_static(file_type.into()),707per_partition_sort_exprs,708per_partition_sort_descending,709per_partition_sort_nulls_last,710finish_callback: finish_callback.as_ref().map(|x| x.display_str()),711sync_on_close: *sync_on_close,712maintain_order: *maintain_order,713mkdir: *mkdir,714};715716PhysNodeInfo {717title: properties.variant_name(),718properties,719..Default::default()720}721},722PhysNodeKind::FileSink2 {723input,724options:725FileSinkOptions {726target,727file_format,728unified_sink_args:729UnifiedSinkArgs {730mkdir,731maintain_order,732sync_on_close,733cloud_options,734},735},736} => {737phys_node_inputs.push(input.node);738739let properties = PhysNodeProperties::FileSink {740target: match target {741SinkTarget::Path(p) => format_pl_smallstr!("Path({})", p.to_str()),742SinkTarget::Dyn(_) => PlSmallStr::from_static("DynWriteable"),743},744file_format: PlSmallStr::from_static(file_format.as_ref().into()),745sync_on_close: *sync_on_close,746maintain_order: *maintain_order,747mkdir: *mkdir,748cloud_options: cloud_options.is_some(),749};750751PhysNodeInfo {752title: properties.variant_name(),753properties,754..Default::default()755}756},757PhysNodeKind::PartitionedSink2 {758input,759options:760PartitionedSinkOptionsIR {761base_path,762file_path_provider,763partition_strategy,764finish_callback: _,765file_format,766unified_sink_args:767UnifiedSinkArgs {768mkdir,769maintain_order,770sync_on_close,771cloud_options,772},773max_rows_per_file,774approximate_bytes_per_file,775},776} => {777phys_node_inputs.push(input.node);778779let mut partition_key_exprs: Option<Vec<PlSmallStr>> = None;780let mut include_keys_: Option<bool> = None;781let mut per_partition_sort_by_: Option<&[SortColumnIR]> = None;782783match partition_strategy {784PartitionStrategyIR::Keyed {785keys,786include_keys,787keys_pre_grouped: _,788per_partition_sort_by,789} => {790partition_key_exprs = Some(expr_list(keys, self.expr_arena));791include_keys_ = Some(*include_keys);792per_partition_sort_by_ = Some(per_partition_sort_by.as_slice());793},794PartitionStrategyIR::FileSize => {},795}796797let (798per_partition_sort_exprs,799per_partition_sort_descending,800per_partition_sort_nulls_last,801) = per_partition_sort_by_802.as_ref()803.map_or((None, None, None), |x| {804let (a, (b, c)): (Vec<_>, (Vec<_>, Vec<_>)) = x805.iter()806.map(|x| {807(808format_pl_smallstr!("{}", x.expr.display(self.expr_arena)),809(x.descending, x.nulls_last),810)811})812.unzip();813814(Some(a), Some(b), Some(c))815});816817let properties = PhysNodeProperties::PartitionSink2 {818base_path: base_path.to_str().into(),819file_path_provider: file_path_provider.clone(),820file_format: PlSmallStr::from_static(file_format.as_ref().into()),821partition_strategy: PlSmallStr::from_static(partition_strategy.into()),822partition_key_exprs,823include_keys: include_keys_,824per_partition_sort_exprs,825per_partition_sort_descending,826per_partition_sort_nulls_last,827mkdir: *mkdir,828maintain_order: *maintain_order,829sync_on_close: *sync_on_close,830cloud_options: cloud_options.is_some(),831max_rows_per_file: *max_rows_per_file,832approximate_bytes_per_file: *approximate_bytes_per_file,833};834835PhysNodeInfo {836title: properties.variant_name(),837properties,838..Default::default()839}840},841PhysNodeKind::PeakMinMax { input, is_peak_max } => {842phys_node_inputs.push(input.node);843844let properties = if *is_peak_max {845PhysNodeProperties::PeakMax846} else {847PhysNodeProperties::PeakMin848};849850PhysNodeInfo {851title: properties.variant_name(),852properties,853..Default::default()854}855},856PhysNodeKind::Reduce { input, exprs } => {857phys_node_inputs.push(input.node);858859let properties = PhysNodeProperties::Reduce {860exprs: expr_list(exprs, self.expr_arena),861};862863PhysNodeInfo {864title: properties.variant_name(),865properties,866..Default::default()867}868},869PhysNodeKind::Repeat { value, repeats } => {870phys_node_inputs.push(value.node);871phys_node_inputs.push(repeats.node);872873let properties = PhysNodeProperties::Repeat;874875PhysNodeInfo {876title: properties.variant_name(),877properties,878..Default::default()879}880},881PhysNodeKind::Rle(input) => {882phys_node_inputs.push(input.node);883884let properties = PhysNodeProperties::Rle;885886PhysNodeInfo {887title: properties.variant_name(),888properties,889..Default::default()890}891},892PhysNodeKind::RleId(input) => {893phys_node_inputs.push(input.node);894895let properties = PhysNodeProperties::RleId;896897PhysNodeInfo {898title: properties.variant_name(),899properties,900..Default::default()901}902},903PhysNodeKind::Select {904input,905selectors,906extend_original,907} => {908phys_node_inputs.push(input.node);909910let properties = PhysNodeProperties::Select {911selectors: expr_list(selectors, self.expr_arena),912extend_original: *extend_original,913};914915PhysNodeInfo {916title: properties.variant_name(),917properties,918..Default::default()919}920},921PhysNodeKind::Shift {922input,923offset,924fill,925} => {926phys_node_inputs.push(input.node);927phys_node_inputs.push(offset.node);928929if let Some(fill) = fill {930phys_node_inputs.push(fill.node);931}932933let properties = PhysNodeProperties::Shift {934has_fill: fill.is_some(),935};936937PhysNodeInfo {938title: properties.variant_name(),939properties,940..Default::default()941}942},943PhysNodeKind::SimpleProjection { input, columns } => {944phys_node_inputs.push(input.node);945946let properties = PhysNodeProperties::SimpleProjection {947columns: columns.clone(),948};949950PhysNodeInfo {951title: properties.variant_name(),952properties,953..Default::default()954}955},956PhysNodeKind::SinkMultiple { sinks } => {957for node in sinks {958phys_node_inputs.push(*node);959}960961let properties = PhysNodeProperties::SinkMultiple {962num_sinks: sinks.len().try_into().unwrap(),963};964965PhysNodeInfo {966title: properties.variant_name(),967properties,968..Default::default()969}970},971PhysNodeKind::Sort {972input,973by_column,974slice,975sort_options:976SortMultipleOptions {977descending,978nulls_last,979multithreaded,980maintain_order,981limit,982},983} => {984phys_node_inputs.push(input.node);985986let properties = PhysNodeProperties::Sort {987by_exprs: expr_list(by_column, self.expr_arena),988slice: convert_opt_slice(slice),989descending: descending.clone(),990nulls_last: nulls_last.clone(),991multithreaded: *multithreaded,992maintain_order: *maintain_order,993#[allow(clippy::useless_conversion)]994limit: limit.map(|x| x.into()),995};996997PhysNodeInfo {998title: properties.variant_name(),999properties,1000..Default::default()1001}1002},1003PhysNodeKind::StreamingSlice {1004input,1005offset,1006length,1007} => {1008phys_node_inputs.push(input.node);10091010let properties = PhysNodeProperties::Slice {1011offset: (*offset).try_into().unwrap(),1012length: (*length).try_into().unwrap(),1013};10141015PhysNodeInfo {1016title: properties.variant_name(),1017properties,1018..Default::default()1019}1020},1021PhysNodeKind::TopK {1022input,1023k,1024by_column,1025reverse,1026nulls_last,1027} => {1028phys_node_inputs.push(input.node);1029phys_node_inputs.push(k.node);10301031let properties = PhysNodeProperties::TopK {1032by_exprs: expr_list(by_column, self.expr_arena),1033reverse: reverse.clone(),1034nulls_last: nulls_last.clone(),1035};10361037PhysNodeInfo {1038title: properties.variant_name(),1039properties,1040..Default::default()1041}1042},1043PhysNodeKind::WithRowIndex {1044input,1045name,1046offset,1047} => {1048phys_node_inputs.push(input.node);10491050let properties = PhysNodeProperties::WithRowIndex {1051name: name.clone(),1052#[allow(clippy::useless_conversion)]1053offset: offset.map(|x| x.into()),1054};10551056PhysNodeInfo {1057title: properties.variant_name(),1058properties,1059..Default::default()1060}1061},1062PhysNodeKind::Zip {1063inputs,1064zip_behavior,1065} => {1066for input in inputs {1067phys_node_inputs.push(input.node);1068}10691070let properties = PhysNodeProperties::Zip {1071num_inputs: inputs.len().try_into().unwrap(),1072zip_behavior: *zip_behavior,1073};10741075PhysNodeInfo {1076title: properties.variant_name(),1077properties,1078..Default::default()1079}1080},1081#[cfg(feature = "cum_agg")]1082PhysNodeKind::CumAgg { input, kind } => {1083phys_node_inputs.push(input.node);10841085let properties = PhysNodeProperties::CumAgg {1086kind: format_pl_smallstr!("{:?}", kind),1087};10881089PhysNodeInfo {1090title: properties.variant_name(),1091properties,1092..Default::default()1093}1094},1095#[cfg(feature = "ewma")]1096PhysNodeKind::EwmMean { input, options }1097| PhysNodeKind::EwmVar { input, options }1098| PhysNodeKind::EwmStd { input, options } => {1099phys_node_inputs.push(input.node);11001101let polars_ops::series::EWMOptions {1102alpha,1103adjust,1104bias,1105min_periods,1106ignore_nulls,1107} = options;11081109let properties = PhysNodeProperties::Ewm {1110variant: PlSmallStr::from_static(phys_node.kind().into()),1111alpha: *alpha,1112adjust: *adjust,1113bias: *bias,1114min_periods: *min_periods,1115ignore_nulls: *ignore_nulls,1116};11171118PhysNodeInfo {1119title: properties.variant_name(),1120properties,1121..Default::default()1122}1123},1124#[cfg(feature = "semi_anti_join")]1125PhysNodeKind::SemiAntiJoin {1126input_left,1127input_right,1128left_on,1129right_on,1130args,1131output_bool,1132} => {1133phys_node_inputs.push(input_left.node);1134phys_node_inputs.push(input_right.node);11351136let properties = PhysNodeProperties::SemiAntiJoin {1137left_on: expr_list(left_on, self.expr_arena),1138right_on: expr_list(right_on, self.expr_arena),1139nulls_equal: args.nulls_equal,1140output_as_bool: *output_bool,1141};11421143PhysNodeInfo {1144title: properties.variant_name(),1145properties,1146..Default::default()1147}1148},1149#[cfg(feature = "merge_sorted")]1150PhysNodeKind::MergeSorted {1151input_left,1152input_right,1153} => {1154phys_node_inputs.push(input_left.node);1155phys_node_inputs.push(input_right.node);11561157let properties = PhysNodeProperties::MergeSorted;11581159PhysNodeInfo {1160title: properties.variant_name(),1161properties,1162..Default::default()1163}1164},1165#[cfg(feature = "python")]1166PhysNodeKind::PythonScan {1167options:1168polars_plan::plans::PythonOptions {1169scan_fn: _,1170schema,1171output_schema: _,1172with_columns,1173python_source,1174n_rows,1175predicate,1176validate_schema,1177is_pure,1178},1179} => {1180use polars_plan::plans::PythonPredicate;11811182let properties = PhysNodeProperties::PythonScan {1183scan_source_type: python_source.clone(),1184n_rows: n_rows.map(|x| x.try_into().unwrap()),1185projection: with_columns.as_deref().map(list_str_cloned),1186predicate: match predicate {1187PythonPredicate::None => None,1188PythonPredicate::PyArrow(s) => Some(s.into()),1189PythonPredicate::Polars(p) => {1190Some(format_pl_smallstr!("{}", p.display(self.expr_arena)))1191},1192},1193schema_names: schema.iter_names_cloned().collect(),1194is_pure: *is_pure,1195validate_schema: *validate_schema,1196};11971198PhysNodeInfo {1199title: properties.variant_name(),1200properties,1201..Default::default()1202}1203},1204}1205}1206}12071208impl PhysNodeProperties {1209fn variant_name(&self) -> PlSmallStr {1210PlSmallStr::from_static(<&'static str>::from(self))1211}1212}12131214fn list_str_cloned<I, T>(iter: I) -> Vec<PlSmallStr>1215where1216I: IntoIterator<Item = T>,1217T: AsRef<str>,1218{1219iter.into_iter()1220.map(|x| PlSmallStr::from_str(x.as_ref()))1221.collect()1222}12231224fn convert_opt_slice<T, U>(slice: &Option<(T, U)>) -> Option<(i64, u64)>1225where1226T: Copy + TryInto<i64>,1227U: Copy + TryInto<u64>,1228<T as TryInto<i64>>::Error: std::fmt::Debug,1229<U as TryInto<u64>>::Error: std::fmt::Debug,1230{1231slice.map(|(offset, len)| (offset.try_into().unwrap(), len.try_into().unwrap()))1232}12331234fn expr_list(exprs: &[ExprIR], expr_arena: &Arena<AExpr>) -> Vec<PlSmallStr> {1235exprs1236.iter()1237.map(|e| format_pl_smallstr!("{}", e.display(expr_arena)))1238.collect()1239}124012411242