1use std::borrow::Cow;
5use std::convert::TryFrom;
6use std::fmt;
7
8use prost::UnknownEnumValue;
9use substrait::proto::aggregate_function::AggregationInvocation;
10use substrait::proto::sort_field::{SortDirection, SortKind};
11use substrait::proto::{
12 AggregateFunction, AggregationPhase, Expression, SortField, Type, join_rel, set_rel,
13};
14
15use super::types::Name;
16use super::{PlanError, Scope, Textify};
17use crate::extensions::{ExtensionColumn, ExtensionValue};
18
19pub trait ValueEnum {
22 fn as_enum_str(&self) -> Result<Cow<'static, str>, PlanError>;
23}
24
25#[derive(Debug, Clone)]
26pub struct NamedArg<'a> {
27 pub name: Cow<'a, str>,
28 pub value: Value<'a>,
29}
30
31#[derive(Debug, Clone)]
32pub enum Value<'a> {
33 TableName(Vec<Name<'a>>),
34 Field(Option<Name<'a>>, Option<&'a Type>),
35 Tuple(Vec<Value<'a>>),
36 Reference(i32),
37 Expression(&'a Expression),
38 AggregateFunction(&'a AggregateFunction),
39 Missing(PlanError),
41 Enum(Cow<'a, str>),
43 EmptyGroup,
44 Integer(i64),
45 ExtensionArgument(ExtensionValue),
47 ExtColumn(ExtensionColumn),
49}
50
51impl<'a> Value<'a> {
52 pub fn expect(maybe_value: Option<Self>, f: impl FnOnce() -> PlanError) -> Self {
53 match maybe_value {
54 Some(s) => s,
55 None => Value::Missing(f()),
56 }
57 }
58}
59
60impl<'a> From<Result<Vec<Name<'a>>, PlanError>> for Value<'a> {
61 fn from(token: Result<Vec<Name<'a>>, PlanError>) -> Self {
62 match token {
63 Ok(value) => Value::TableName(value),
64 Err(err) => Value::Missing(err),
65 }
66 }
67}
68
69impl<'a> Textify for Value<'a> {
70 fn name() -> &'static str {
71 "Value"
72 }
73
74 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
75 match self {
76 Value::TableName(names) => write!(w, "{}", ctx.separated(names, ".")),
77 Value::Field(name, typ) => {
78 write!(w, "{}:{}", ctx.expect(name.as_ref()), ctx.expect(*typ))
79 }
80 Value::Tuple(values) => write!(w, "({})", ctx.separated(values, ", ")),
81 Value::Reference(i) => write!(w, "${i}"),
83 Value::Expression(e) => write!(w, "{}", ctx.display(*e)),
84 Value::AggregateFunction(agg_fn) => agg_fn.textify(ctx, w),
85 Value::Missing(err) => write!(w, "{}", ctx.failure(err.clone())),
86 Value::Enum(res) => write!(w, "&{res}"),
87 Value::Integer(i) => write!(w, "{i}"),
88 Value::EmptyGroup => write!(w, "_"),
89 Value::ExtensionArgument(ev) => ev.textify(ctx, w),
90 Value::ExtColumn(ec) => ec.textify(ctx, w),
91 }
92 }
93}
94
95#[derive(Debug, Clone, Default)]
98pub struct Arguments<'a> {
99 pub positional: Vec<Value<'a>>,
101 pub named: Vec<NamedArg<'a>>,
103}
104
105impl<'a> Arguments<'a> {
106 pub fn new(positional: Vec<Value<'a>>, named: Vec<NamedArg<'a>>) -> Self {
107 Arguments { positional, named }
108 }
109}
110
111impl<'a> Textify for Arguments<'a> {
112 fn name() -> &'static str {
113 "Arguments"
114 }
115 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
116 if self.positional.is_empty() && self.named.is_empty() {
117 return write!(w, "_");
118 }
119
120 write!(w, "{}", ctx.separated(self.positional.iter(), ", "))?;
121 if !self.positional.is_empty() && !self.named.is_empty() {
122 write!(w, ", ")?;
123 }
124 write!(w, "{}", ctx.separated(self.named.iter(), ", "))
125 }
126}
127
128impl<'a> From<&'a SortField> for Value<'a> {
129 fn from(sf: &'a SortField) -> Self {
130 let field = match &sf.expr {
131 Some(expr) => Value::Expression(expr),
132 None => Value::Missing(PlanError::unimplemented(
133 "SortField",
134 Some("expr"),
135 "Missing expr",
136 )),
137 };
138 let direction = match &sf.sort_kind {
139 Some(kind) => Value::from(kind),
140 None => Value::Missing(PlanError::invalid(
141 "SortKind",
142 Some(Cow::Borrowed("sort_kind")),
143 "Missing sort_kind",
144 )),
145 };
146 Value::Tuple(vec![field, direction])
147 }
148}
149
150pub(crate) fn enum_str_value<'a>(result: Result<Cow<'static, str>, PlanError>) -> Value<'a> {
154 match result {
155 Ok(s) => Value::Enum(s),
156 Err(e) => Value::Missing(e),
157 }
158}
159
160pub(crate) fn decode_enum_field<'a, T>(
170 raw: i32,
171 message: &'static str,
172 field: &'static str,
173) -> Value<'a>
174where
175 T: TryFrom<i32> + ValueEnum,
176{
177 match T::try_from(raw) {
178 Ok(v) => enum_str_value(v.as_enum_str()),
179 Err(_) => Value::Missing(PlanError::invalid(
180 message,
181 Some(field),
182 format!("Unknown {message}.{field}: {raw}"),
183 )),
184 }
185}
186
187impl<'a, T: ValueEnum + ?Sized> From<&'a T> for Value<'a> {
188 fn from(enum_val: &'a T) -> Self {
189 enum_str_value(enum_val.as_enum_str())
190 }
191}
192
193impl ValueEnum for SortKind {
194 fn as_enum_str(&self) -> Result<Cow<'static, str>, PlanError> {
195 let d = match self {
196 &SortKind::Direction(d) => SortDirection::try_from(d),
197 SortKind::ComparisonFunctionReference(f) => {
198 return Err(PlanError::invalid(
199 "SortKind",
200 Some(Cow::Owned(format!("function reference{f}"))),
201 "SortKind::ComparisonFunctionReference unimplemented",
202 ));
203 }
204 };
205 let s = match d {
206 Err(UnknownEnumValue(d)) => {
207 return Err(PlanError::invalid(
208 "SortKind",
209 Some(Cow::Owned(format!("unknown variant: {d:?}"))),
210 "Unknown SortDirection",
211 ));
212 }
213 Ok(SortDirection::AscNullsFirst) => "AscNullsFirst",
214 Ok(SortDirection::AscNullsLast) => "AscNullsLast",
215 Ok(SortDirection::DescNullsFirst) => "DescNullsFirst",
216 Ok(SortDirection::DescNullsLast) => "DescNullsLast",
217 Ok(SortDirection::Clustered) => "Clustered",
218 Ok(SortDirection::Unspecified) => {
219 return Err(PlanError::invalid(
220 "SortKind",
221 Option::<Cow<str>>::None,
222 "Unspecified SortDirection",
223 ));
224 }
225 };
226 Ok(Cow::Borrowed(s))
227 }
228}
229
230impl ValueEnum for join_rel::JoinType {
231 fn as_enum_str(&self) -> Result<Cow<'static, str>, PlanError> {
232 let s = match self {
233 join_rel::JoinType::Unspecified => {
234 return Err(PlanError::invalid(
235 "JoinType",
236 Option::<Cow<str>>::None,
237 "Unspecified JoinType",
238 ));
239 }
240 join_rel::JoinType::Inner => "Inner",
241 join_rel::JoinType::Outer => "Outer",
242 join_rel::JoinType::Left => "Left",
243 join_rel::JoinType::Right => "Right",
244 join_rel::JoinType::LeftSemi => "LeftSemi",
245 join_rel::JoinType::RightSemi => "RightSemi",
246 join_rel::JoinType::LeftAnti => "LeftAnti",
247 join_rel::JoinType::RightAnti => "RightAnti",
248 join_rel::JoinType::LeftSingle => "LeftSingle",
249 join_rel::JoinType::RightSingle => "RightSingle",
250 join_rel::JoinType::LeftMark => "LeftMark",
251 join_rel::JoinType::RightMark => "RightMark",
252 };
253 Ok(Cow::Borrowed(s))
254 }
255}
256
257impl ValueEnum for set_rel::SetOp {
258 fn as_enum_str(&self) -> Result<Cow<'static, str>, PlanError> {
259 let s = match self {
260 set_rel::SetOp::Unspecified => {
261 return Err(PlanError::invalid(
262 "SetOp",
263 Option::<Cow<str>>::None,
264 "Unspecified SetOp",
265 ));
266 }
267 set_rel::SetOp::MinusPrimary => "MinusPrimary",
268 set_rel::SetOp::MinusPrimaryAll => "MinusPrimaryAll",
269 set_rel::SetOp::MinusMultiset => "MinusMultiset",
270 set_rel::SetOp::IntersectionPrimary => "IntersectionPrimary",
271 set_rel::SetOp::IntersectionMultiset => "IntersectionMultiset",
272 set_rel::SetOp::IntersectionMultisetAll => "IntersectionMultisetAll",
273 set_rel::SetOp::UnionDistinct => "UnionDistinct",
274 set_rel::SetOp::UnionAll => "UnionAll",
275 };
276 Ok(Cow::Borrowed(s))
277 }
278}
279
280impl ValueEnum for AggregationPhase {
281 fn as_enum_str(&self) -> Result<Cow<'static, str>, PlanError> {
282 let s = match self {
283 AggregationPhase::Unspecified => "Unspecified",
284 AggregationPhase::InitialToIntermediate => "InitialToIntermediate",
285 AggregationPhase::IntermediateToIntermediate => "IntermediateToIntermediate",
286 AggregationPhase::InitialToResult => "InitialToResult",
287 AggregationPhase::IntermediateToResult => "IntermediateToResult",
288 };
289 Ok(Cow::Borrowed(s))
290 }
291}
292
293impl ValueEnum for AggregationInvocation {
294 fn as_enum_str(&self) -> Result<Cow<'static, str>, PlanError> {
295 let s = match self {
296 AggregationInvocation::Unspecified => "Unspecified",
297 AggregationInvocation::All => "All",
298 AggregationInvocation::Distinct => "Distinct",
299 };
300 Ok(Cow::Borrowed(s))
301 }
302}
303
304impl<'a> Textify for NamedArg<'a> {
305 fn name() -> &'static str {
306 "NamedArg"
307 }
308 fn textify<S: Scope, W: fmt::Write>(&self, ctx: &S, w: &mut W) -> fmt::Result {
309 write!(w, "{}=", self.name)?;
310 self.value.textify(ctx, w)
311 }
312}
313
314#[cfg(test)]
315mod tests {
316 use super::*;
317 use crate::fixtures::TestContext;
318
319 #[test]
320 fn test_arguments_textify_positional_only() {
321 let ctx = TestContext::new();
322 let args = Arguments::new(vec![Value::Integer(42), Value::Integer(7)], vec![]);
323 let (result, errors) = ctx.textify(&args);
324 assert!(errors.is_empty(), "Expected no errors, got: {errors:?}");
325 assert_eq!(result, "42, 7");
326 }
327
328 #[test]
329 fn test_arguments_textify_named_only() {
330 let ctx = TestContext::new();
331 let args = Arguments::new(
332 vec![],
333 vec![
334 NamedArg {
335 name: Cow::Borrowed("limit"),
336 value: Value::Integer(10),
337 },
338 NamedArg {
339 name: Cow::Borrowed("offset"),
340 value: Value::Integer(5),
341 },
342 ],
343 );
344 let (result, errors) = ctx.textify(&args);
345 assert!(errors.is_empty(), "Expected no errors, got: {errors:?}");
346 assert_eq!(result, "limit=10, offset=5");
347 }
348
349 #[test]
350 fn test_arguments_textify_both() {
351 let ctx = TestContext::new();
352 let args = Arguments::new(
353 vec![Value::Integer(1)],
354 vec![NamedArg {
355 name: "foo".into(),
356 value: Value::Integer(2),
357 }],
358 );
359 let (result, errors) = ctx.textify(&args);
360 assert!(errors.is_empty(), "Expected no errors, got: {errors:?}");
361 assert_eq!(result, "1, foo=2");
362 }
363
364 #[test]
365 fn test_arguments_textify_empty() {
366 let ctx = TestContext::new();
367 let args = Arguments::new(vec![], vec![]);
368 let (result, errors) = ctx.textify(&args);
369 assert!(errors.is_empty(), "Expected no errors, got: {errors:?}");
370 assert_eq!(result, "_");
371 }
372
373 #[test]
374 fn test_named_arg_textify_error_token() {
375 let ctx = TestContext::new();
376 let named_arg = NamedArg {
377 name: "foo".into(),
378 value: Value::Missing(PlanError::invalid(
379 "my_enum",
380 Some(Cow::Borrowed("my_enum")),
381 Cow::Borrowed("my_enum"),
382 )),
383 };
384 let (result, errors) = ctx.textify(&named_arg);
385 assert!(result.contains("foo=!{my_enum}"), "Output: {result}");
387 assert!(!errors.is_empty(), "Expected error for error token");
389 }
390
391 #[test]
392 fn test_decode_enum_field_known_variant() {
393 let value =
394 decode_enum_field::<set_rel::SetOp>(set_rel::SetOp::UnionAll as i32, "SetRel", "op");
395 match value {
396 Value::Enum(s) => assert_eq!(s, "UnionAll"),
397 other => panic!("Expected Value::Enum, got {other:?}"),
398 }
399 }
400
401 #[test]
402 fn test_decode_enum_field_unknown_variant_names_field() {
403 let value = decode_enum_field::<set_rel::SetOp>(99, "SetRel", "op");
404 match value {
405 Value::Missing(err) => {
406 assert_eq!(err.message, "SetRel");
407 assert_eq!(err.lookup.as_deref(), Some("op"));
408 assert_eq!(err.description, "Unknown SetRel.op: 99");
411 }
412 other => panic!("Expected Value::Missing, got {other:?}"),
413 }
414 }
415}