Skip to main content

reifydb_evaluate/expression/
branch.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2026 ReifyDB
3
4use reifydb_core::value::column::{arith::is_family, view::group_by::common_key_type};
5use reifydb_value::{
6	error::{RuntimeErrorKind, TypeError},
7	fragment::Fragment,
8	value::value_type::ValueType,
9};
10
11use crate::Result;
12
13pub struct BranchLayout {
14	types: Vec<ValueType>,
15	open: Vec<bool>,
16	described: Vec<String>,
17}
18
19impl BranchLayout {
20	pub fn new<'a>(columns: impl IntoIterator<Item = (&'a str, ValueType, bool)>) -> Self {
21		let mut layout = Self {
22			types: Vec::new(),
23			open: Vec::new(),
24			described: Vec::new(),
25		};
26		for (name, ty, fits_any) in columns {
27			layout.described.push(describe(name, &ty));
28			layout.types.push(ty.inner_type().clone());
29			layout.open.push(fits_any);
30		}
31		layout
32	}
33
34	pub fn types(&self) -> &[ValueType] {
35		&self.types
36	}
37
38	pub fn admit<'a>(
39		&mut self,
40		columns: impl IntoIterator<Item = (&'a str, ValueType, bool)>,
41		fragment: &Fragment,
42	) -> Result<()> {
43		let incoming: Vec<(&str, ValueType, bool)> = columns.into_iter().collect();
44		let mut disagrees = incoming.len() != self.types.len();
45		if !disagrees {
46			for ((slot, open), (_, ty, fits_any)) in
47				self.types.iter_mut().zip(self.open.iter_mut()).zip(incoming.iter())
48			{
49				if *fits_any {
50					continue;
51				}
52				let ty = ty.inner_type();
53				if *open {
54					*slot = ty.clone();
55					*open = false;
56				} else if ty != slot {
57					match common_key_type(slot, ty) {
58						Some(common) => *slot = common,
59						None if (is_family(slot) && ty.is_floating_point())
60							|| (slot.is_floating_point() && is_family(ty)) =>
61						{
62							*slot = ValueType::Float8
63						}
64						None => {
65							disagrees = true;
66							break;
67						}
68					}
69				}
70			}
71		}
72
73		if disagrees {
74			return Err(TypeError::Runtime {
75				kind: RuntimeErrorKind::ConditionalBranchMismatch {
76					expected: self.described.clone(),
77					actual: incoming.iter().map(|(name, ty, _)| describe(name, ty)).collect(),
78					fragment: fragment.clone(),
79				},
80				message: "conditional branches produce different columns".to_string(),
81			}
82			.into());
83		}
84		Ok(())
85	}
86}
87
88fn describe(name: &str, ty: &ValueType) -> String {
89	format!("{}: {}", name, ty)
90}