| 2449 | /// ``` |
| 2450 | #[expect(clippy::needless_pass_by_value)] |
| 2451 | pub fn fill_null( |
| 2452 | &self, |
| 2453 | value: ScalarValue, |
| 2454 | columns: Vec<String>, |
| 2455 | ) -> Result<DataFrame> { |
| 2456 | let cols = if columns.is_empty() { |
| 2457 | self.logical_plan() |
| 2458 | .schema() |
| 2459 | .fields() |
| 2460 | .iter() |
| 2461 | .map(Arc::clone) |
| 2462 | .collect() |
| 2463 | } else { |
| 2464 | self.find_columns(&columns)? |
| 2465 | }; |
| 2466 | |
| 2467 | // Create projections for each column |
| 2468 | let projections = self |
| 2469 | .logical_plan() |
| 2470 | .schema() |
| 2471 | .fields() |
| 2472 | .iter() |
| 2473 | .map(|field| { |
| 2474 | if cols.contains(field) { |
| 2475 | // Try to cast fill value to column type. If the cast fails, fallback to the original column. |
| 2476 | match value.clone().cast_to(field.data_type()) { |
| 2477 | Ok(fill_value) => Expr::Alias(Alias { |
| 2478 | expr: Box::new(Expr::ScalarFunction(ScalarFunction { |
| 2479 | func: coalesce(), |
| 2480 | args: vec![col(field.name()), lit(fill_value)], |
| 2481 | })), |
| 2482 | relation: None, |
| 2483 | name: field.name().to_string(), |
| 2484 | metadata: None, |
| 2485 | }), |
| 2486 | Err(_) => col(field.name()), |
| 2487 | } |
| 2488 | } else { |
| 2489 | col(field.name()) |
| 2490 | } |
| 2491 | }) |
| 2492 | .collect::<Vec<_>>(); |
| 2493 | |
| 2494 | self.clone().select(projections) |
| 2495 | } |
| 2496 | |
| 2497 | // Helper to find columns from names |
| 2498 | fn find_columns(&self, names: &[String]) -> Result<Vec<FieldRef>> { |