diff --git a/src/structural/mod.rs b/src/structural/mod.rs deleted file mode 100644 index eb38c9b4..00000000 --- a/src/structural/mod.rs +++ /dev/null @@ -1,438 +0,0 @@ -//! Structural types for unified value representation -//! -//! This module provides a unified type system that bridges the gap between -//! extension values and textify values, using proper lifetime management -//! with `Cow<'a, str>` for flexible string handling. - -use std::borrow::Cow; -use std::fmt; - -use crate::extensions::simple::{ExtensionFunction, SimpleExtensions}; - -/// A unified value type that can represent both extension arguments and textify values -/// with proper lifetime management -#[derive(Debug, Clone)] -pub enum StructuralValue<'a> { - /// String value with flexible ownership (borrowed or owned) - String(Cow<'a, str>), - /// Integer literal value - Integer(i64), - /// Float literal value - Float(f64), - /// Boolean literal value - Boolean(bool), - /// Field reference ($0, $1, etc.) - Reference(i32), - /// Function call or extension reference - Function(FunctionRef<'a>), - /// Table name reference - TableName(Vec>), - /// Tuple of multiple values - Tuple(Vec>), - /// List of values - List(Vec>), - /// Missing or invalid value with error description - Missing(Cow<'a, str>), - /// Enum value representation - Enum(Cow<'a, str>), -} - -/// Represents a function call or extension reference with resolution state -#[derive(Debug, Clone)] -pub enum FunctionRef<'a> { - /// Unresolved reference by name only - Unresolved(Cow<'a, str>), - /// Unresolved reference with anchor notation (e.g., "add#10") - UnresolvedWithAnchor { name: Cow<'a, str>, anchor: u32 }, - /// Unresolved reference with URI anchor (e.g., "add@1") - UnresolvedWithUri { name: Cow<'a, str>, uri_anchor: u32 }, - /// Unresolved reference with both anchors (e.g., "add#10@1") - UnresolvedWithBoth { - name: Cow<'a, str>, - anchor: u32, - uri_anchor: u32, - }, - /// Resolved to a simple extension function - Resolved { - name: Cow<'a, str>, - extension: &'a ExtensionFunction, - }, -} - -/// Named argument pair for function calls and relations -#[derive(Debug, Clone)] -pub struct NamedArg<'a> { - pub name: Cow<'a, str>, - pub value: StructuralValue<'a>, -} - -/// Arguments collection for relations and function calls -#[derive(Debug, Clone)] -pub struct Arguments<'a> { - /// Positional arguments - pub positional: Vec>, - /// Named arguments - pub named: Vec>, -} - -/// Column specification with type information -#[derive(Debug, Clone)] -pub enum ColumnSpec<'a> { - /// Named column with optional type (name or name:type) - Named { - name: Cow<'a, str>, - type_spec: Option>, - }, - /// Field reference ($0, $1, etc.) - Reference(i32), - /// Expression column - Expression(StructuralValue<'a>), -} - -impl<'a> StructuralValue<'a> { - /// Create a new string value from any string-like type - pub fn string(s: impl Into>) -> Self { - Self::String(s.into()) - } - - /// Create a new owned string value - pub fn owned_string(s: String) -> Self { - Self::String(Cow::Owned(s)) - } - - /// Create a new borrowed string value - pub fn borrowed_string(s: &'a str) -> Self { - Self::String(Cow::Borrowed(s)) - } - - /// Create a function reference from a name - pub fn function(name: impl Into>) -> Self { - Self::Function(FunctionRef::Unresolved(name.into())) - } - - /// Create a function reference with anchor - pub fn function_with_anchor(name: impl Into>, anchor: u32) -> Self { - Self::Function(FunctionRef::UnresolvedWithAnchor { - name: name.into(), - anchor, - }) - } - - /// Create a function reference with URI anchor - pub fn function_with_uri(name: impl Into>, uri_anchor: u32) -> Self { - Self::Function(FunctionRef::UnresolvedWithUri { - name: name.into(), - uri_anchor, - }) - } - - /// Create a function reference with both anchors - pub fn function_with_both(name: impl Into>, anchor: u32, uri_anchor: u32) -> Self { - Self::Function(FunctionRef::UnresolvedWithBoth { - name: name.into(), - anchor, - uri_anchor, - }) - } - - /// Try to resolve function references using simple extensions - pub fn resolve_functions(&mut self, extensions: &'a SimpleExtensions) -> Result<(), String> { - match self { - StructuralValue::Function(func_ref) => { - *func_ref = func_ref.try_resolve(extensions)?; - } - StructuralValue::Tuple(values) | StructuralValue::List(values) => { - for value in values { - value.resolve_functions(extensions)?; - } - } - _ => {} // Other types don't need resolution - } - Ok(()) - } - - /// Convert to an owned version (removes all borrowed references) - pub fn into_owned(self) -> StructuralValue<'static> { - match self { - StructuralValue::String(s) => StructuralValue::String(Cow::Owned(s.into_owned())), - StructuralValue::Integer(i) => StructuralValue::Integer(i), - StructuralValue::Float(f) => StructuralValue::Float(f), - StructuralValue::Boolean(b) => StructuralValue::Boolean(b), - StructuralValue::Reference(r) => StructuralValue::Reference(r), - StructuralValue::Function(f) => StructuralValue::Function(f.into_owned()), - StructuralValue::TableName(names) => StructuralValue::TableName( - names - .into_iter() - .map(|n| Cow::Owned(n.into_owned())) - .collect(), - ), - StructuralValue::Tuple(values) => { - StructuralValue::Tuple(values.into_iter().map(|v| v.into_owned()).collect()) - } - StructuralValue::List(values) => { - StructuralValue::List(values.into_iter().map(|v| v.into_owned()).collect()) - } - StructuralValue::Missing(msg) => StructuralValue::Missing(Cow::Owned(msg.into_owned())), - StructuralValue::Enum(e) => StructuralValue::Enum(Cow::Owned(e.into_owned())), - } - } -} - -impl<'a> FunctionRef<'a> { - /// Try to resolve this function reference using simple extensions - pub fn try_resolve(self, extensions: &'a SimpleExtensions) -> Result { - match self { - FunctionRef::Unresolved(name) => { - // Try to find by name in extensions - if let Some(ext_fn) = extensions.functions().find_by_name(&name) { - Ok(FunctionRef::Resolved { - name, - extension: ext_fn, - }) - } else { - // Keep unresolved - Ok(FunctionRef::Unresolved(name)) - } - } - FunctionRef::UnresolvedWithAnchor { name, anchor } => { - // Try to resolve by anchor - if let Some(ext_fn) = extensions.functions().get(anchor) { - Ok(FunctionRef::Resolved { - name, - extension: ext_fn, - }) - } else { - Ok(FunctionRef::UnresolvedWithAnchor { name, anchor }) - } - } - // TODO: Add resolution logic for URI anchors and combined anchors - other => Ok(other), - } - } - - /// Convert to owned version - pub fn into_owned(self) -> FunctionRef<'static> { - match self { - FunctionRef::Unresolved(name) => FunctionRef::Unresolved(Cow::Owned(name.into_owned())), - FunctionRef::UnresolvedWithAnchor { name, anchor } => { - FunctionRef::UnresolvedWithAnchor { - name: Cow::Owned(name.into_owned()), - anchor, - } - } - FunctionRef::UnresolvedWithUri { name, uri_anchor } => FunctionRef::UnresolvedWithUri { - name: Cow::Owned(name.into_owned()), - uri_anchor, - }, - FunctionRef::UnresolvedWithBoth { - name, - anchor, - uri_anchor, - } => FunctionRef::UnresolvedWithBoth { - name: Cow::Owned(name.into_owned()), - anchor, - uri_anchor, - }, - FunctionRef::Resolved { name, extension: _ } => { - // Can't preserve the reference in owned version, convert to unresolved - FunctionRef::Unresolved(Cow::Owned(name.into_owned())) - } - } - } - - /// Get the function name regardless of resolution state - pub fn name(&self) -> &str { - match self { - FunctionRef::Unresolved(name) => name, - FunctionRef::UnresolvedWithAnchor { name, .. } => name, - FunctionRef::UnresolvedWithUri { name, .. } => name, - FunctionRef::UnresolvedWithBoth { name, .. } => name, - FunctionRef::Resolved { name, .. } => name, - } - } -} - -impl<'a> Arguments<'a> { - /// Create new empty arguments - pub fn new() -> Self { - Self { - positional: Vec::new(), - named: Vec::new(), - } - } - - /// Add a positional argument - pub fn add_positional(&mut self, value: StructuralValue<'a>) { - self.positional.push(value); - } - - /// Add a named argument - pub fn add_named(&mut self, name: impl Into>, value: StructuralValue<'a>) { - self.named.push(NamedArg { - name: name.into(), - value, - }); - } - - /// Check if arguments are empty - pub fn is_empty(&self) -> bool { - self.positional.is_empty() && self.named.is_empty() - } - - /// Resolve all function references in arguments - pub fn resolve_functions(&mut self, extensions: &'a SimpleExtensions) -> Result<(), String> { - for value in &mut self.positional { - value.resolve_functions(extensions)?; - } - for arg in &mut self.named { - arg.value.resolve_functions(extensions)?; - } - Ok(()) - } -} - -impl<'a> Default for Arguments<'a> { - fn default() -> Self { - Self::new() - } -} - -impl<'a> ColumnSpec<'a> { - /// Create a named column - pub fn named(name: impl Into>) -> Self { - Self::Named { - name: name.into(), - type_spec: None, - } - } - - /// Create a named column with type - pub fn named_with_type( - name: impl Into>, - type_spec: impl Into>, - ) -> Self { - Self::Named { - name: name.into(), - type_spec: Some(type_spec.into()), - } - } - - /// Create a reference column - pub fn reference(index: i32) -> Self { - Self::Reference(index) - } - - /// Create an expression column - pub fn expression(expr: StructuralValue<'a>) -> Self { - Self::Expression(expr) - } -} - -// Display implementations for debugging and textification -impl<'a> fmt::Display for StructuralValue<'a> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - StructuralValue::String(s) => write!(f, "'{}'", s), - StructuralValue::Integer(i) => write!(f, "{}", i), - StructuralValue::Float(fl) => write!(f, "{}", fl), - StructuralValue::Boolean(b) => write!(f, "{}", b), - StructuralValue::Reference(r) => write!(f, "${}", r), - StructuralValue::Function(func) => write!(f, "{}", func), - StructuralValue::TableName(names) => { - write!( - f, - "{}", - names - .iter() - .map(|n| n.as_ref()) - .collect::>() - .join(".") - ) - } - StructuralValue::Tuple(values) => { - write!( - f, - "({})", - values - .iter() - .map(|v| v.to_string()) - .collect::>() - .join(", ") - ) - } - StructuralValue::List(values) => { - write!( - f, - "[{}]", - values - .iter() - .map(|v| v.to_string()) - .collect::>() - .join(", ") - ) - } - StructuralValue::Missing(msg) => write!(f, "!{{{}}}", msg), - StructuralValue::Enum(e) => write!(f, "&{}", e), - } - } -} - -impl<'a> fmt::Display for FunctionRef<'a> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - FunctionRef::Unresolved(name) => write!(f, "{}", name), - FunctionRef::UnresolvedWithAnchor { name, anchor } => write!(f, "{}#{}", name, anchor), - FunctionRef::UnresolvedWithUri { name, uri_anchor } => { - write!(f, "{}@{}", name, uri_anchor) - } - FunctionRef::UnresolvedWithBoth { - name, - anchor, - uri_anchor, - } => { - write!(f, "{}#{}@{}", name, anchor, uri_anchor) - } - FunctionRef::Resolved { name, .. } => write!(f, "{}", name), - } - } -} - -impl<'a> fmt::Display for NamedArg<'a> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}={}", self.name, self.value) - } -} - -impl<'a> fmt::Display for Arguments<'a> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut parts = Vec::new(); - - // Add positional arguments - for value in &self.positional { - parts.push(value.to_string()); - } - - // Add named arguments - for arg in &self.named { - parts.push(arg.to_string()); - } - - write!(f, "{}", parts.join(", ")) - } -} - -impl<'a> fmt::Display for ColumnSpec<'a> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - ColumnSpec::Named { name, type_spec } => { - if let Some(type_spec) = type_spec { - write!(f, "{}:{}", name, type_spec) - } else { - write!(f, "{}", name) - } - } - ColumnSpec::Reference(r) => write!(f, "${}", r), - ColumnSpec::Expression(expr) => write!(f, "{}", expr), - } - } -} diff --git a/src/textify/rels.rs b/src/textify/rels.rs index 72a89b18..81f4ea15 100644 --- a/src/textify/rels.rs +++ b/src/textify/rels.rs @@ -158,9 +158,6 @@ fn schema_to_values<'a>(schema: &'a NamedStruct) -> Vec> { .flatten(); let mut names = schema.names.iter(); - // let field_count = schema.r#struct.as_ref().map(|s| s.types.len()).unwrap_or(0); - // let name_count = schema.names.len(); - let mut values = Vec::new(); loop { let field = fields.next(); @@ -292,7 +289,65 @@ impl Textify for Relation<'_> { } } -impl Relation<'_> { +fn read_columns<'a>(rel: &'a ReadRel) -> Vec> { + match rel.base_schema { + Some(ref schema) => schema_to_values(schema), + None => { + let err = + PlanError::unimplemented("ReadRel", Some("base_schema"), "Base schema is required"); + vec![Value::Missing(err)] + } + } +} + +pub fn get_emit(rel: Option<&RelCommon>) -> Option<&EmitKind> { + rel.as_ref().and_then(|c| c.emit_kind.as_ref()) +} + +fn join_output_columns( + join_type: join_rel::JoinType, + left_columns: usize, + right_columns: usize, +) -> Vec> { + let total_columns = match join_type { + // Inner, Left, Right, Outer joins output columns from both sides + join_rel::JoinType::Inner + | join_rel::JoinType::Left + | join_rel::JoinType::Right + | join_rel::JoinType::Outer => left_columns + right_columns, + + // Left semi/anti joins only output columns from the left side + join_rel::JoinType::LeftSemi | join_rel::JoinType::LeftAnti => left_columns, + + // Right semi/anti joins output columns from the right side + join_rel::JoinType::RightSemi | join_rel::JoinType::RightAnti => right_columns, + + // Single joins behave like semi joins + join_rel::JoinType::LeftSingle => left_columns, + join_rel::JoinType::RightSingle => right_columns, + + // Mark joins output base columns plus one mark column + join_rel::JoinType::LeftMark => left_columns + 1, + join_rel::JoinType::RightMark => right_columns + 1, + + // Unspecified - fallback to all columns + join_rel::JoinType::Unspecified => left_columns + right_columns, + }; + + // Output is always a contiguous range starting from $0 + (0..total_columns) + .map(|i| Value::Reference(i as i32)) + .collect() +} + +impl<'a> Relation<'a> { + pub fn emitted(&self) -> usize { + match self.emit { + Some(EmitKind::Emit(e)) => e.output_mapping.len(), + Some(EmitKind::Direct(_)) | None => self.columns.len(), + } + } + /// Write the single header line for this relation, e.g. `Filter[$0 => $0]`. /// Does not write a trailing newline; callers are responsible for any /// newline that follows (either from an addendum or from the next child). @@ -322,19 +377,62 @@ impl Relation<'_> { } Ok(()) } -} -impl<'a> Relation<'a> { - pub fn emitted(&self) -> usize { - match self.emit { - Some(EmitKind::Emit(e)) => e.output_mapping.len(), - Some(EmitKind::Direct(_)) => self.columns.len(), - None => self.columns.len(), + /// Convert a vector of relation references into their structured form. + /// + /// Returns a list of children (with None for ones missing), and a count of input columns. + pub fn convert_children( + refs: Vec>, + ctx: &S, + ) -> (Vec>>, usize) { + let mut children = vec![]; + let mut inputs = 0; + + for maybe_rel in refs { + match maybe_rel { + Some(rel) => { + let child = Relation::from_rel(rel, ctx); + inputs += child.emitted(); + children.push(Some(child)); + } + None => children.push(None), + } + } + + (children, inputs) + } + + pub fn from_rel(rel: &'a Rel, ctx: &S) -> Self { + match rel.rel_type.as_ref() { + Some(RelType::Read(r)) => Relation::from_read(r, ctx), + Some(RelType::Filter(r)) => Relation::from_filter(r, ctx), + Some(RelType::Project(r)) => Relation::from_project(r, ctx), + Some(RelType::Aggregate(r)) => Relation::from_aggregate(r, ctx), + Some(RelType::Sort(r)) => Relation::from_sort(r, ctx), + Some(RelType::Fetch(r)) => Relation::from_fetch(r, ctx), + Some(RelType::Join(r)) => Relation::from_join(r, ctx), + Some(RelType::ExtensionLeaf(r)) => Relation::from_extension_leaf(r, ctx), + Some(RelType::ExtensionSingle(r)) => Relation::from_extension_single(r, ctx), + Some(RelType::ExtensionMulti(r)) => Relation::from_extension_multi(r, ctx), + _ => { + let name = rel.name(); + let token = ctx.failure(FormatError::Format(PlanError::unimplemented( + "Rel", + Some(name), + format!("{name} is not yet supported in the text format"), + ))); + Relation { + name: Cow::Owned(format!("{token}")), + arguments: None, + columns: vec![], + emit: None, + addenda: AddendumLines::none(), + children: vec![], + } + } } } -} -impl<'a> Relation<'a> { fn from_read(rel: &'a ReadRel, ctx: &S) -> Self { let columns = read_columns(rel); let emit = rel.common.as_ref().and_then(|c| c.emit_kind.as_ref()); @@ -421,50 +519,7 @@ impl<'a> Relation<'a> { } } } -} - -fn read_columns<'a>(rel: &'a ReadRel) -> Vec> { - match rel.base_schema { - Some(ref schema) => schema_to_values(schema), - None => { - let err = - PlanError::unimplemented("ReadRel", Some("base_schema"), "Base schema is required"); - vec![Value::Missing(err)] - } - } -} - -pub fn get_emit(rel: Option<&RelCommon>) -> Option<&EmitKind> { - rel.as_ref().and_then(|c| c.emit_kind.as_ref()) -} -impl<'a> Relation<'a> { - /// Convert a vector of relation references into their structured form. - /// - /// Returns a list of children (with None for ones missing), and a count of input columns. - pub fn convert_children( - refs: Vec>, - ctx: &S, - ) -> (Vec>>, usize) { - let mut children = vec![]; - let mut inputs = 0; - - for maybe_rel in refs { - match maybe_rel { - Some(rel) => { - let child = Relation::from_rel(rel, ctx); - inputs += child.emitted(); - children.push(Some(child)); - } - None => children.push(None), - } - } - - (children, inputs) - } -} - -impl<'a> Relation<'a> { fn from_filter(rel: &'a FilterRel, ctx: &S) -> Self { let condition = rel .condition @@ -494,13 +549,10 @@ impl<'a> Relation<'a> { fn from_project(rel: &'a ProjectRel, ctx: &S) -> Self { let (children, input_columns) = Relation::convert_children(vec![rel.input.as_deref()], ctx); - let mut columns: Vec = vec![]; - for i in 0..input_columns { - columns.push(Value::Reference(i as i32)); - } - for expr in &rel.expressions { - columns.push(Value::Expression(expr)); - } + let columns: Vec = (0..input_columns) + .map(|i| Value::Reference(i as i32)) + .chain(rel.expressions.iter().map(Value::Expression)) + .collect(); Relation { name: Cow::Borrowed("Project"), @@ -512,65 +564,34 @@ impl<'a> Relation<'a> { } } - pub fn from_rel(rel: &'a Rel, ctx: &S) -> Self { - match rel.rel_type.as_ref() { - Some(RelType::Read(r)) => Relation::from_read(r, ctx), - Some(RelType::Filter(r)) => Relation::from_filter(r, ctx), - Some(RelType::Project(r)) => Relation::from_project(r, ctx), - Some(RelType::Aggregate(r)) => Relation::from_aggregate(r, ctx), - Some(RelType::Sort(r)) => Relation::from_sort(r, ctx), - Some(RelType::Fetch(r)) => Relation::from_fetch(r, ctx), - Some(RelType::Join(r)) => Relation::from_join(r, ctx), - Some(RelType::ExtensionLeaf(r)) => Relation::from_extension_leaf(r, ctx), - Some(RelType::ExtensionSingle(r)) => Relation::from_extension_single(r, ctx), - Some(RelType::ExtensionMulti(r)) => Relation::from_extension_multi(r, ctx), - _ => { - let name = rel.name(); - let token = ctx.failure(FormatError::Format(PlanError::unimplemented( - "Rel", - Some(name), - format!("{name} is not yet supported in the text format"), - ))); - Relation { - name: Cow::Owned(format!("{token}")), - arguments: None, - columns: vec![], - emit: None, - addenda: AddendumLines::none(), - children: vec![], - } - } - } - } - fn from_extension_leaf(rel: &'a ExtensionLeafRel, ctx: &S) -> Self { - let detail_ref = rel.detail.as_ref().map(AnyRef::from); - let decoded = match detail_ref { - Some(d) => ctx.extension_registry().decode(d), - None => Err(ExtensionError::MissingDetail), - }; + let decoded = rel + .detail + .as_ref() + .map_or(Err(ExtensionError::MissingDetail), |d| { + ctx.extension_registry().decode(AnyRef::from(d)) + }); Relation::from_extension("ExtensionLeaf", decoded, vec![], ctx) } fn from_extension_single(rel: &'a ExtensionSingleRel, ctx: &S) -> Self { - let detail_ref = rel.detail.as_ref().map(AnyRef::from); - let decoded = match detail_ref { - Some(d) => ctx.extension_registry().decode(d), - None => Err(ExtensionError::MissingDetail), - }; + let decoded = rel + .detail + .as_ref() + .map_or(Err(ExtensionError::MissingDetail), |d| { + ctx.extension_registry().decode(AnyRef::from(d)) + }); Relation::from_extension("ExtensionSingle", decoded, vec![rel.input.as_deref()], ctx) } fn from_extension_multi(rel: &'a ExtensionMultiRel, ctx: &S) -> Self { - let detail_ref = rel.detail.as_ref().map(AnyRef::from); - let decoded = match detail_ref { - Some(d) => ctx.extension_registry().decode(d), - None => Err(ExtensionError::MissingDetail), - }; - let mut child_refs: Vec> = vec![]; - for input in &rel.inputs { - child_refs.push(Some(input)); - } + let decoded = rel + .detail + .as_ref() + .map_or(Err(ExtensionError::MissingDetail), |d| { + ctx.extension_registry().decode(AnyRef::from(d)) + }); + let child_refs: Vec> = rel.inputs.iter().map(Some).collect(); Relation::from_extension("ExtensionMulti", decoded, child_refs, ctx) } @@ -583,21 +604,24 @@ impl<'a> Relation<'a> { match decoded { Ok((name, args)) => { let (children, _) = Relation::convert_children(child_refs, ctx); - let mut positional = vec![]; - for value in args.positional { - positional.push(Value::ExtensionArgument(value)); - } - let mut named = vec![]; - for (key, value) in args.named { - named.push(NamedArg { + let positional = args + .positional + .into_iter() + .map(Value::ExtensionArgument) + .collect(); + let named = args + .named + .into_iter() + .map(|(key, value)| NamedArg { name: Cow::Owned(key), value: Value::ExtensionArgument(value), - }); - } - let mut columns = vec![]; - for col in args.output_columns { - columns.push(Value::ExtColumn(col)); - } + }) + .collect(); + let columns = args + .output_columns + .into_iter() + .map(Value::ExtColumn) + .collect(); Relation { name: Cow::Owned(format!("{}:{}", ext_type, name)), arguments: Some(Arguments { positional, named }), @@ -741,58 +765,7 @@ impl<'a> Relation<'a> { } (expression_list, grouping_sets) } -} - -impl Textify for RelRoot { - fn name() -> &'static str { - "RelRoot" - } - fn textify(&self, ctx: &S, w: &mut W) -> fmt::Result { - let names = self.names.iter().map(|n| Name(n)).collect::>(); - - write!( - w, - "{}Root[{}]", - ctx.indent(), - ctx.separated(names.iter(), ", ") - )?; - let child_scope = ctx.push_indent(); - for child in self.input.iter() { - writeln!(w)?; - child.textify(&child_scope, w)?; - } - - Ok(()) - } -} - -impl Textify for PlanRelType { - fn name() -> &'static str { - "PlanRelType" - } - - fn textify(&self, ctx: &S, w: &mut W) -> fmt::Result { - match self { - PlanRelType::Rel(rel) => rel.textify(ctx, w), - PlanRelType::Root(root) => root.textify(ctx, w), - } - } -} - -impl Textify for PlanRel { - fn name() -> &'static str { - "PlanRel" - } - - /// Write the relation as a string. Inputs are ignored - those are handled - /// separately. - fn textify(&self, ctx: &S, w: &mut W) -> fmt::Result { - write!(w, "{}", ctx.expect(self.rel_type.as_ref())) - } -} - -impl<'a> Relation<'a> { fn from_sort(rel: &'a SortRel, ctx: &S) -> Self { let (children, input_columns) = Relation::convert_children(vec![rel.input.as_deref()], ctx); let mut positional = vec![]; @@ -803,16 +776,14 @@ impl<'a> Relation<'a> { positional, named: vec![], }); - // The columns are the direct outputs of this relation (before emit) - let mut col_values = vec![]; - for i in 0..input_columns { - col_values.push(Value::Reference(i as i32)); - } + let columns: Vec = (0..input_columns) + .map(|i| Value::Reference(i as i32)) + .collect(); let emit = get_emit(rel.common.as_ref()); Relation { name: Cow::Borrowed("Sort"), arguments, - columns: col_values, + columns, emit, addenda: AddendumLines::from_advanced_extension(ctx, rel.advanced_extension.as_ref()), children, @@ -873,45 +844,7 @@ impl<'a> Relation<'a> { children, } } -} - -fn join_output_columns( - join_type: join_rel::JoinType, - left_columns: usize, - right_columns: usize, -) -> Vec> { - let total_columns = match join_type { - // Inner, Left, Right, Outer joins output columns from both sides - join_rel::JoinType::Inner - | join_rel::JoinType::Left - | join_rel::JoinType::Right - | join_rel::JoinType::Outer => left_columns + right_columns, - - // Left semi/anti joins only output columns from the left side - join_rel::JoinType::LeftSemi | join_rel::JoinType::LeftAnti => left_columns, - - // Right semi/anti joins output columns from the right side - join_rel::JoinType::RightSemi | join_rel::JoinType::RightAnti => right_columns, - - // Single joins behave like semi joins - join_rel::JoinType::LeftSingle => left_columns, - join_rel::JoinType::RightSingle => right_columns, - - // Mark joins output base columns plus one mark column - join_rel::JoinType::LeftMark => left_columns + 1, - join_rel::JoinType::RightMark => right_columns + 1, - - // Unspecified - fallback to all columns - join_rel::JoinType::Unspecified => left_columns + right_columns, - }; - - // Output is always a contiguous range starting from $0 - (0..total_columns) - .map(|i| Value::Reference(i as i32)) - .collect() -} -impl<'a> Relation<'a> { fn from_join(rel: &'a JoinRel, ctx: &S) -> Self { let (children, _total_columns) = Relation::convert_children(vec![rel.left.as_deref(), rel.right.as_deref()], ctx); @@ -986,6 +919,55 @@ impl<'a> Relation<'a> { } } +impl Textify for RelRoot { + fn name() -> &'static str { + "RelRoot" + } + + fn textify(&self, ctx: &S, w: &mut W) -> fmt::Result { + let names = self.names.iter().map(|n| Name(n)).collect::>(); + + write!( + w, + "{}Root[{}]", + ctx.indent(), + ctx.separated(names.iter(), ", ") + )?; + let child_scope = ctx.push_indent(); + for child in self.input.iter() { + writeln!(w)?; + child.textify(&child_scope, w)?; + } + + Ok(()) + } +} + +impl Textify for PlanRelType { + fn name() -> &'static str { + "PlanRelType" + } + + fn textify(&self, ctx: &S, w: &mut W) -> fmt::Result { + match self { + PlanRelType::Rel(rel) => rel.textify(ctx, w), + PlanRelType::Root(root) => root.textify(ctx, w), + } + } +} + +impl Textify for PlanRel { + fn name() -> &'static str { + "PlanRel" + } + + /// Write the relation as a string. Inputs are ignored - those are handled + /// separately. + fn textify(&self, ctx: &S, w: &mut W) -> fmt::Result { + write!(w, "{}", ctx.expect(self.rel_type.as_ref())) + } +} + impl<'a> From<&'a SortField> for Value<'a> { fn from(sf: &'a SortField) -> Self { let field = match &sf.expr { diff --git a/src/textify/types.rs b/src/textify/types.rs index 360ca050..2eac35df 100644 --- a/src/textify/types.rs +++ b/src/textify/types.rs @@ -601,101 +601,6 @@ impl Textify for proto::Type { } } -// /// A schema is a named struct with a list of fields. -// /// -// /// This outputs the names and types of the fields in the struct, -// /// comma-separated. -// /// -// /// Assumes that the struct is not nullable, that the type variation reference -// /// is 0, and that the names and fields match up; otherwise, pushes errors. -// /// -// /// Names and fields are output without any bracketing; bring your own -// /// bracketing. -// pub struct Schema<'a>(pub &'a proto::NamedStruct); - -// impl<'a> Textify for Schema<'a> { -// fn name() -> &'static str { -// "Schema" -// } - -// fn textify(&self, ctx: &S, w: &mut W) -> fmt::Result { -// let mut fields = self -// .0 -// .r#struct -// .as_ref() -// .map(|s| s.types.iter()) -// .into_iter() -// .flatten(); -// let mut names = self.0.names.iter(); - -// let field_count = self.0.r#struct.as_ref().map(|s| s.types.len()).unwrap_or(0); -// let name_count = self.0.names.len(); - -// if field_count != name_count { -// ctx.push_error( -// TextifyError::invalid( -// "Schema", -// NONSPECIFIC, -// format!( -// "Field count ({}) does not match name count ({})", -// field_count, name_count -// ), -// ) -// .into(), -// ); -// } - -// write!(w, "[")?; -// let mut first = true; -// loop { -// let field = fields.next(); -// let name = names.next().map(|n| Name(n)); -// if field.is_none() && name.is_none() { -// break; -// } - -// if first { -// first = false; -// } else { -// write!(w, ", ")?; -// } - -// write!(w, "{}:{}", ctx.expect(name.as_ref()), ctx.expect(field))?; -// } -// write!(w, "]")?; - -// let s = match &self.0.r#struct { -// None => return Ok(()), -// Some(s) => s, -// }; - -// if s.nullability() != Nullability::Required { -// ctx.push_error( -// TextifyError::invalid( -// "Schema", -// Some("nullabilility"), -// "Expected schema to be Nullability::Required", -// ) -// .into(), -// ); -// s.nullability().textify(ctx, w)?; -// } -// if s.type_variation_reference != 0 { -// ctx.push_error( -// TextifyError::invalid( -// "Schema", -// Some("type_variation_reference"), -// "Expected schema to have type_variation_reference 0", -// ) -// .into(), -// ); -// TypeVariation(s.type_variation_reference).textify(ctx, w)?; -// } - -// Ok(()) -// } -// } - #[cfg(test)] mod tests {