1use radixdb_core::{DataType, Error, LogicalTypeRef, Result};
18use radixdb_sql::ast::*;
19
20pub use super::output_contract::{
21 BoundOutputColumn, NavigationOutputBinding, OutputBindingHost, QueryOutputColumn,
22};
23use super::types::parse_data_type;
24
25#[doc(hidden)]
27pub trait OutputBindingExt: OutputBindingHost {
28 fn describe_query_output(&self, sql: &str) -> Result<Option<Vec<QueryOutputColumn>>> {
31 let statements =
32 radixdb_sql::parse_sql(sql).map_err(|error| Error::Parse(error.to_string()))?;
33 let [Statement::Select(select)] = statements.as_slice() else {
34 return Ok(None);
35 };
36 self.bind_select_output(select, &[], 0).map(|columns| {
37 Some(
38 columns
39 .into_iter()
40 .map(|column| QueryOutputColumn {
41 name: column.name,
42 type_name: column.type_name,
43 data_type: column.data_type,
44 logical_type: column.logical_type,
45 nullable: column.nullable,
46 })
47 .collect(),
48 )
49 })
50 }
51
52 fn bind_scalar_output(&self, expression: &Expression) -> Result<(DataType, bool)> {
56 let (data_type, _, _, nullable) = self.bind_scalar_output_metadata(expression)?;
57 Ok((data_type, nullable))
58 }
59
60 fn bind_scalar_output_metadata(
64 &self,
65 expression: &Expression,
66 ) -> Result<(DataType, LogicalTypeRef, String, bool)> {
67 let (data_type, logical_type, type_name) = self
68 .bind_expression_output_type(expression, &[], &[], &[], 0)?
69 .ok_or_else(|| {
70 Error::NotSupported(
71 "scalar expression has no bind-time type; add an explicit CAST".to_string(),
72 )
73 })?;
74 Ok((
75 data_type,
76 logical_type,
77 type_name,
78 self.bind_expression_nullable(expression, &[], &[], &[], 0)?,
79 ))
80 }
81
82 fn bind_returning_output(
84 &self,
85 table_name: &str,
86 qualifier: &str,
87 returning: &[Expression],
88 ) -> Result<Vec<BoundOutputColumn>> {
89 let schema = self.output_binding_table_schema(table_name)?;
90 let scope = schema
91 .columns
92 .iter()
93 .map(|column| BoundOutputColumn {
94 name: column.name.clone(),
95 qualifier: Some(qualifier.to_lowercase()),
96 data_type: column.data_type,
97 logical_type: column.logical_type(),
98 type_name: column.formatted_data_type(),
99 nullable: column.nullable,
100 })
101 .collect::<Vec<_>>();
102 let mut output = Vec::new();
103 for (ordinal, expression) in returning.iter().enumerate() {
104 match expression {
105 Expression::Star(_) => output.extend(scope.iter().cloned().map(|mut column| {
106 column.qualifier = None;
107 column
108 })),
109 Expression::QualifiedStar(star)
110 if star.qualifier.eq_ignore_ascii_case(qualifier) =>
111 {
112 output.extend(scope.iter().cloned().map(|mut column| {
113 column.qualifier = None;
114 column
115 }));
116 }
117 Expression::QualifiedStar(star) => {
118 return Err(Error::InvalidArgument(format!(
119 "RETURNING cannot bind qualified star '{}.*' for table '{table_name}'",
120 star.qualifier
121 )));
122 }
123 expression => {
124 let (data_type, logical_type, type_name) = self
125 .bind_expression_output_type(expression, &scope, &[], &[], 0)?
126 .ok_or_else(|| {
127 Error::NotSupported(format!(
128 "RETURNING column {} has no bind-time type; add an explicit CAST",
129 ordinal + 1
130 ))
131 })?;
132 output.push(BoundOutputColumn {
133 name: Self::bound_expression_name(expression, ordinal),
134 qualifier: None,
135 data_type,
136 logical_type,
137 type_name,
138 nullable: self.bind_expression_nullable(expression, &scope, &[], &[], 0)?,
139 });
140 }
141 }
142 }
143 Ok(output)
144 }
145
146 fn bind_select_output(
147 &self,
148 select: &SelectStatement,
149 inherited_ctes: &[(String, Vec<BoundOutputColumn>)],
150 depth: usize,
151 ) -> Result<Vec<BoundOutputColumn>> {
152 if depth > 32 {
153 return Err(Error::NotSupported(
154 "CTAS metadata binding exceeded 32 nested query levels".to_string(),
155 ));
156 }
157
158 let mut ctes = inherited_ctes.to_vec();
159 if let Some(with) = &select.with {
160 for cte in &with.ctes {
161 let mut columns = self.bind_select_output(&cte.query, &ctes, depth + 1)?;
162 if !cte.column_names.is_empty() {
163 if cte.column_names.len() != columns.len() {
164 return Err(Error::InvalidArgument(format!(
165 "CTE '{}' declares {} columns but query returns {}",
166 cte.name.value,
167 cte.column_names.len(),
168 columns.len()
169 )));
170 }
171 for (column, alias) in columns.iter_mut().zip(&cte.column_names) {
172 column.name = alias.value.to_string();
173 }
174 }
175 ctes.push((cte.name.value_lower.to_string(), columns));
176 }
177 }
178
179 let scope = match select.table_expr.as_deref() {
180 Some(source) => self.bind_table_source(source, &ctes, depth + 1)?,
181 None => Vec::new(),
182 };
183 let navigation = self.output_binding_navigation(select)?;
184 let mut output = Vec::new();
185 for (ordinal, expression) in select.columns.iter().enumerate() {
186 match expression {
187 Expression::Star(_) => output.extend(scope.iter().cloned().map(|mut column| {
188 column.qualifier = None;
189 column
190 })),
191 Expression::QualifiedStar(star) => {
192 let qualifier = star.qualifier.to_lowercase();
193 let before = output.len();
194 output.extend(
195 scope
196 .iter()
197 .filter(|column| column.qualifier.as_deref() == Some(&qualifier))
198 .cloned()
199 .map(|mut column| {
200 column.qualifier = None;
201 column
202 }),
203 );
204 if output.len() == before {
205 return Err(Error::InvalidArgument(format!(
206 "CTAS cannot bind qualified star '{}.*'",
207 star.qualifier
208 )));
209 }
210 }
211 _ => {
212 let (data_type, logical_type, type_name) = self
213 .bind_expression_output_type(
214 expression,
215 &scope,
216 &ctes,
217 &navigation,
218 depth + 1,
219 )?
220 .ok_or_else(|| {
221 Error::NotSupported(format!(
222 "CTAS output column {} has no bind-time type; add an explicit CAST",
223 ordinal + 1
224 ))
225 })?;
226 output.push(BoundOutputColumn {
227 name: Self::bound_expression_name(expression, ordinal),
228 qualifier: None,
229 data_type,
230 logical_type,
231 type_name,
232 nullable: self.bind_expression_nullable(
233 expression,
234 &scope,
235 &ctes,
236 &navigation,
237 depth + 1,
238 )?,
239 });
240 }
241 }
242 }
243
244 for set_operation in &select.set_operations {
245 let right = self.bind_select_output(&set_operation.right, &ctes, depth + 1)?;
246 if right.len() != output.len() {
247 return Err(Error::InvalidArgument(
248 "set-operation branches have different column counts".to_string(),
249 ));
250 }
251 for (left, right) in output.iter_mut().zip(right) {
252 if left.logical_type != right.logical_type {
253 let merged = match (left.logical_type, right.logical_type) {
254 (LogicalTypeRef::Builtin(left), LogicalTypeRef::Builtin(right)) => {
255 Self::merge_bound_types(left, right)?
256 }
257 _ => {
258 return Err(Error::InvalidArgument(
259 "set-operation branches have different external types".to_string(),
260 ))
261 }
262 };
263 left.data_type = merged;
264 left.logical_type = LogicalTypeRef::Builtin(merged);
265 left.type_name = merged.to_string();
266 }
267 left.nullable |= right.nullable;
268 }
269 }
270 Ok(output)
271 }
272
273 fn bind_table_source(
274 &self,
275 source: &Expression,
276 ctes: &[(String, Vec<BoundOutputColumn>)],
277 depth: usize,
278 ) -> Result<Vec<BoundOutputColumn>> {
279 match source {
280 Expression::TableSource(table) => {
281 let name = table.name.value_lower.to_string();
282 if let Some((_, columns)) = ctes.iter().rev().find(|(cte, _)| cte == &name) {
283 return Ok(Self::qualify_bound_columns(
284 columns.clone(),
285 table
286 .alias
287 .as_ref()
288 .map_or(&name, |alias| alias.value_lower.as_str()),
289 ));
290 }
291 if let Ok(schema) = self.output_binding_table_schema(&name) {
292 let qualifier = table
293 .alias
294 .as_ref()
295 .map_or(name.as_str(), |alias| alias.value_lower.as_str());
296 return Ok(schema
297 .columns
298 .iter()
299 .map(|column| BoundOutputColumn {
300 name: column.name.clone(),
301 qualifier: Some(qualifier.to_string()),
302 data_type: column.data_type,
303 logical_type: column.logical_type(),
304 type_name: column.formatted_data_type(),
305 nullable: column.nullable,
306 })
307 .collect());
308 }
309 if let Some(view) = self.output_binding_view(&name)? {
310 let statements = radixdb_sql::parse_sql(&view.query).map_err(|error| {
311 Error::Parse(format!(
312 "cannot bind view '{}': {}",
313 view.original_name, error
314 ))
315 })?;
316 let [Statement::Select(query)] = statements.as_slice() else {
317 return Err(Error::Parse(format!(
318 "view '{}' does not contain one SELECT",
319 view.original_name
320 )));
321 };
322 let columns = self.bind_select_output(query, ctes, depth + 1)?;
323 let qualifier = table
324 .alias
325 .as_ref()
326 .map_or(name.as_str(), |alias| alias.value_lower.as_str());
327 return Ok(Self::qualify_bound_columns(columns, qualifier));
328 }
329 Err(Error::TableNotFound(name))
330 }
331 Expression::JoinSource(join) => {
332 let mut left = self.bind_table_source(&join.left, ctes, depth + 1)?;
333 let mut right = self.bind_table_source(&join.right, ctes, depth + 1)?;
334
335 let join_type = join.join_type.to_ascii_uppercase();
342 if join_type.contains("RIGHT") || join_type.contains("FULL") {
343 left.iter_mut().for_each(|column| column.nullable = true);
344 }
345 if join_type.contains("LEFT") || join_type.contains("FULL") {
346 right.iter_mut().for_each(|column| column.nullable = true);
347 }
348
349 left.extend(right);
350 Ok(left)
351 }
352 Expression::SubquerySource(subquery) => {
353 let columns = self.bind_select_output(&subquery.subquery, ctes, depth + 1)?;
354 let qualifier = subquery
355 .alias
356 .as_ref()
357 .map(|alias| alias.value_lower.as_str())
358 .unwrap_or("subquery");
359 Ok(Self::qualify_bound_columns(columns, qualifier))
360 }
361 Expression::CteReference(reference) => {
362 let name = reference.name.value_lower.as_str();
363 let columns = ctes
364 .iter()
365 .rev()
366 .find(|(cte, _)| cte == name)
367 .map(|(_, columns)| columns.clone())
368 .ok_or_else(|| Error::TableNotFound(name.to_string()))?;
369 let qualifier = reference
370 .alias
371 .as_ref()
372 .map_or(name, |alias| alias.value_lower.as_str());
373 Ok(Self::qualify_bound_columns(columns, qualifier))
374 }
375 Expression::ValuesSource(values) => {
376 let width = values.rows.first().map_or(0, Vec::len);
377 if values.rows.iter().any(|row| row.len() != width) {
378 return Err(Error::InvalidArgument(
379 "VALUES rows have different column counts".to_string(),
380 ));
381 }
382 let mut types = vec![None; width];
383 for row in &values.rows {
384 for (index, expression) in row.iter().enumerate() {
385 if let Some(data_type) =
386 self.bind_expression_type(expression, &[], ctes, &[], depth + 1)?
387 {
388 types[index] = Some(match types[index] {
389 Some(current) => Self::merge_bound_types(current, data_type)?,
390 None => data_type,
391 });
392 }
393 }
394 }
395 let qualifier = values
396 .alias
397 .as_ref()
398 .map(|alias| alias.value_lower.to_string());
399 types
400 .into_iter()
401 .enumerate()
402 .map(|(index, data_type)| {
403 Ok(BoundOutputColumn {
404 name: values.column_aliases.get(index).map_or_else(
405 || format!("column{}", index + 1),
406 |alias| alias.value.to_string(),
407 ),
408 qualifier: qualifier.clone(),
409 nullable: values.rows.iter().any(|row| {
410 row.get(index).is_some_and(|value| {
411 matches!(value, Expression::NullLiteral(_))
412 })
413 }),
414 data_type: data_type.ok_or_else(|| {
415 Error::NotSupported(format!(
416 "VALUES column {} has no bind-time type",
417 index + 1
418 ))
419 })?,
420 logical_type: LogicalTypeRef::Builtin(data_type.ok_or_else(|| {
421 Error::NotSupported(format!(
422 "VALUES column {} has no bind-time type",
423 index + 1
424 ))
425 })?),
426 type_name: data_type
427 .map(|data_type| data_type.to_string())
428 .unwrap_or_else(|| "NULL".to_string()),
429 })
430 })
431 .collect()
432 }
433 _ => Err(Error::NotSupported(format!(
434 "CTAS metadata binding does not support table source {source}"
435 ))),
436 }
437 }
438
439 fn bind_expression_output_type(
440 &self,
441 expression: &Expression,
442 scope: &[BoundOutputColumn],
443 ctes: &[(String, Vec<BoundOutputColumn>)],
444 navigation: &[NavigationOutputBinding],
445 depth: usize,
446 ) -> Result<Option<(DataType, LogicalTypeRef, String)>> {
447 let direct = match expression {
448 Expression::Identifier(identifier) => {
449 let matches = scope
450 .iter()
451 .filter(|column| column.name.eq_ignore_ascii_case(&identifier.value_lower))
452 .collect::<Vec<_>>();
453 match matches.as_slice() {
454 [column] => Some((
455 column.data_type,
456 column.logical_type,
457 column.type_name.clone(),
458 )),
459 [] => return Err(Error::ColumnNotFound(identifier.value.to_string())),
460 _ => {
461 return Err(Error::InvalidArgument(format!(
462 "ambiguous CTAS column '{}'",
463 identifier.value
464 )))
465 }
466 }
467 }
468 Expression::QualifiedIdentifier(identifier) => scope
469 .iter()
470 .find(|column| {
471 column.qualifier.as_deref() == Some(identifier.qualifier.value_lower.as_str())
472 && column
473 .name
474 .eq_ignore_ascii_case(&identifier.name.value_lower)
475 })
476 .map(|column| {
477 (
478 column.data_type,
479 column.logical_type,
480 column.type_name.clone(),
481 )
482 }),
483 Expression::Aliased(alias) => {
484 return self.bind_expression_output_type(
485 &alias.expression,
486 scope,
487 ctes,
488 navigation,
489 depth + 1,
490 )
491 }
492 Expression::Cast(cast) => Some(self.output_binding_type_name(&cast.type_name)?),
493 Expression::FunctionCall(function)
494 if !self.output_binding_functions().exists(&function.function) =>
495 {
496 let argument_types = function
497 .arguments
498 .iter()
499 .map(|argument| {
500 self.bind_expression_logical_type(
501 argument,
502 scope,
503 ctes,
504 navigation,
505 depth + 1,
506 )
507 })
508 .collect::<Result<Vec<_>>>()?;
509 self.output_binding_stored_function(&function.function, &argument_types)?
510 .map(|(data_type, logical_type, type_name, _)| {
511 (data_type, logical_type, type_name)
512 })
513 }
514 _ => None,
515 };
516 if direct.is_some() {
517 return Ok(direct);
518 }
519 Ok(self
520 .bind_expression_type(expression, scope, ctes, navigation, depth)?
521 .map(|data_type| {
522 (
523 data_type,
524 LogicalTypeRef::Builtin(data_type),
525 data_type.to_string(),
526 )
527 }))
528 }
529
530 fn bind_expression_type(
531 &self,
532 expression: &Expression,
533 scope: &[BoundOutputColumn],
534 ctes: &[(String, Vec<BoundOutputColumn>)],
535 navigation: &[NavigationOutputBinding],
536 depth: usize,
537 ) -> Result<Option<DataType>> {
538 use radixdb_sql::ast::{InfixOperator, PrefixOperator};
539 let bound = match expression {
540 Expression::Identifier(identifier) => {
541 let matches: Vec<_> = scope
542 .iter()
543 .filter(|column| column.name.eq_ignore_ascii_case(&identifier.value_lower))
544 .collect();
545 match matches.as_slice() {
546 [column] => Some(column.data_type),
547 [] => return Err(Error::ColumnNotFound(identifier.value.to_string())),
548 _ => {
549 return Err(Error::InvalidArgument(format!(
550 "ambiguous CTAS column '{}'",
551 identifier.value
552 )))
553 }
554 }
555 }
556 Expression::QualifiedIdentifier(identifier) => navigation
557 .iter()
558 .find(|path| {
559 path.display_path
560 .as_str()
561 .eq_ignore_ascii_case(&identifier.to_string())
562 })
563 .map(|path| path.terminal_type)
564 .or_else(|| {
565 scope
566 .iter()
567 .find(|column| {
568 column.qualifier.as_deref()
569 == Some(identifier.qualifier.value_lower.as_str())
570 && column
571 .name
572 .eq_ignore_ascii_case(&identifier.name.value_lower)
573 })
574 .map(|column| column.data_type)
575 })
576 .ok_or_else(|| {
577 Error::ColumnNotFound(format!(
578 "{}.{}",
579 identifier.qualifier.value, identifier.name.value
580 ))
581 })
582 .map(Some)?,
583 Expression::IntegerLiteral(_) => Some(DataType::Integer),
584 Expression::FloatLiteral(_) => Some(DataType::Float),
585 Expression::StringLiteral(literal) => Some(match literal.type_hint.as_deref() {
586 Some(type_hint) => parse_data_type(type_hint)?,
587 None => DataType::Text,
588 }),
589 Expression::BooleanLiteral(_) => Some(DataType::Boolean),
590 Expression::NullLiteral(_) => None,
591 Expression::Aliased(alias) => {
592 return self.bind_expression_type(
593 &alias.expression,
594 scope,
595 ctes,
596 navigation,
597 depth + 1,
598 )
599 }
600 Expression::Cast(cast) => Some(parse_data_type(&cast.type_name)?),
601 Expression::Prefix(prefix) => match prefix.op_type {
602 PrefixOperator::Not => Some(DataType::Boolean),
603 _ => {
604 self.bind_expression_type(&prefix.right, scope, ctes, navigation, depth + 1)?
605 }
606 },
607 Expression::Infix(infix) => match infix.op_type {
608 InfixOperator::Equal
609 | InfixOperator::NotEqual
610 | InfixOperator::LessThan
611 | InfixOperator::LessEqual
612 | InfixOperator::GreaterThan
613 | InfixOperator::GreaterEqual
614 | InfixOperator::And
615 | InfixOperator::Or
616 | InfixOperator::Xor
617 | InfixOperator::Like
618 | InfixOperator::ILike
619 | InfixOperator::NotLike
620 | InfixOperator::NotILike
621 | InfixOperator::Glob
622 | InfixOperator::NotGlob
623 | InfixOperator::Regexp
624 | InfixOperator::NotRegexp
625 | InfixOperator::Is
626 | InfixOperator::IsNot
627 | InfixOperator::IsDistinctFrom
628 | InfixOperator::IsNotDistinctFrom => Some(DataType::Boolean),
629 InfixOperator::Concat | InfixOperator::JsonAccessText => Some(DataType::Text),
630 InfixOperator::JsonAccess => Some(DataType::Json),
631 InfixOperator::VectorDistance => Some(DataType::Float),
632 InfixOperator::BitwiseAnd
633 | InfixOperator::BitwiseOr
634 | InfixOperator::BitwiseXor
635 | InfixOperator::LeftShift
636 | InfixOperator::RightShift => Some(DataType::Integer),
637 InfixOperator::Add
638 | InfixOperator::Subtract
639 | InfixOperator::Multiply
640 | InfixOperator::Divide
641 | InfixOperator::Modulo => {
642 let left =
643 self.bind_expression_type(&infix.left, scope, ctes, navigation, depth + 1)?;
644 let right = self.bind_expression_type(
645 &infix.right,
646 scope,
647 ctes,
648 navigation,
649 depth + 1,
650 )?;
651 match (left, right) {
652 (Some(left), Some(right)) => Some(Self::merge_bound_types(left, right)?),
653 (left, right) => left.or(right),
654 }
655 }
656 InfixOperator::Index | InfixOperator::Other => None,
657 },
658 Expression::FunctionCall(function) => {
659 self.bind_function_return_type(function, scope, ctes, navigation, depth + 1)?
660 }
661 Expression::Window(window) => self.bind_function_return_type(
662 &window.function,
663 scope,
664 ctes,
665 navigation,
666 depth + 1,
667 )?,
668 Expression::Case(case) => {
669 let mut result_type = case
670 .when_clauses
671 .iter()
672 .map(|clause| {
673 self.bind_expression_type(
674 &clause.then_result,
675 scope,
676 ctes,
677 navigation,
678 depth + 1,
679 )
680 })
681 .collect::<Result<Vec<_>>>()?
682 .into_iter()
683 .flatten()
684 .next();
685 for clause in &case.when_clauses {
686 if let Some(next) = self.bind_expression_type(
687 &clause.then_result,
688 scope,
689 ctes,
690 navigation,
691 depth + 1,
692 )? {
693 result_type = Some(match result_type {
694 Some(current) => Self::merge_bound_types(current, next)?,
695 None => next,
696 });
697 }
698 }
699 if let Some(else_value) = &case.else_value {
700 if let Some(next) =
701 self.bind_expression_type(else_value, scope, ctes, navigation, depth + 1)?
702 {
703 result_type = Some(match result_type {
704 Some(current) => Self::merge_bound_types(current, next)?,
705 None => next,
706 });
707 }
708 }
709 result_type
710 }
711 Expression::ScalarSubquery(subquery) => self
712 .bind_select_output(&subquery.subquery, ctes, depth + 1)?
713 .first()
714 .map(|column| column.data_type),
715 Expression::Exists(_)
716 | Expression::AllAny(_)
717 | Expression::In(_)
718 | Expression::InHashSet(_)
719 | Expression::Between(_)
720 | Expression::Like(_) => Some(DataType::Boolean),
721 _ => None,
722 };
723 Ok(bound)
724 }
725
726 fn bind_expression_logical_type(
727 &self,
728 expression: &Expression,
729 scope: &[BoundOutputColumn],
730 ctes: &[(String, Vec<BoundOutputColumn>)],
731 navigation: &[NavigationOutputBinding],
732 depth: usize,
733 ) -> Result<Option<LogicalTypeRef>> {
734 match expression {
735 Expression::NullLiteral(_) => Ok(None),
736 Expression::Identifier(identifier) => {
737 let matches = scope
738 .iter()
739 .filter(|column| column.name.eq_ignore_ascii_case(&identifier.value_lower))
740 .collect::<Vec<_>>();
741 match matches.as_slice() {
742 [column] => Ok(Some(column.logical_type)),
743 [] => Err(Error::ColumnNotFound(identifier.value.to_string())),
744 _ => Err(Error::InvalidArgument(format!(
745 "ambiguous column '{}'",
746 identifier.value
747 ))),
748 }
749 }
750 Expression::QualifiedIdentifier(identifier) => scope
751 .iter()
752 .find(|column| {
753 column.qualifier.as_deref() == Some(identifier.qualifier.value_lower.as_str())
754 && column
755 .name
756 .eq_ignore_ascii_case(&identifier.name.value_lower)
757 })
758 .map(|column| Some(column.logical_type))
759 .ok_or_else(|| Error::ColumnNotFound(identifier.to_string())),
760 Expression::Aliased(alias) => self.bind_expression_logical_type(
761 &alias.expression,
762 scope,
763 ctes,
764 navigation,
765 depth + 1,
766 ),
767 Expression::Cast(cast) => self
768 .output_binding_type_name(&cast.type_name)
769 .map(|(_, logical_type, _)| Some(logical_type)),
770 Expression::FunctionCall(function)
771 if !self.output_binding_functions().exists(&function.function) =>
772 {
773 let argument_types = function
774 .arguments
775 .iter()
776 .map(|argument| {
777 self.bind_expression_logical_type(
778 argument,
779 scope,
780 ctes,
781 navigation,
782 depth + 1,
783 )
784 })
785 .collect::<Result<Vec<_>>>()?;
786 self.output_binding_stored_function(&function.function, &argument_types)
787 .and_then(|bound| {
788 bound
789 .map(|(_, logical, _, _)| logical)
790 .map(Some)
791 .ok_or_else(|| {
792 Error::InvalidArgument(format!(
793 "unknown function {}",
794 function.function
795 ))
796 })
797 })
798 }
799 _ => self
800 .bind_expression_type(expression, scope, ctes, navigation, depth)
801 .map(|value| value.map(LogicalTypeRef::Builtin)),
802 }
803 }
804
805 fn bind_expression_nullable(
806 &self,
807 expression: &Expression,
808 scope: &[BoundOutputColumn],
809 ctes: &[(String, Vec<BoundOutputColumn>)],
810 navigation: &[NavigationOutputBinding],
811 depth: usize,
812 ) -> Result<bool> {
813 let nullable = match expression {
814 Expression::Identifier(identifier) => scope
815 .iter()
816 .find(|column| column.name.eq_ignore_ascii_case(&identifier.value_lower))
817 .is_none_or(|column| column.nullable),
818 Expression::QualifiedIdentifier(identifier) => navigation
819 .iter()
820 .find(|path| {
821 path.display_path
822 .as_str()
823 .eq_ignore_ascii_case(&identifier.to_string())
824 })
825 .map(|path| path.nullable)
826 .or_else(|| {
827 scope
828 .iter()
829 .find(|column| {
830 column.qualifier.as_deref()
831 == Some(identifier.qualifier.value_lower.as_str())
832 && column
833 .name
834 .eq_ignore_ascii_case(&identifier.name.value_lower)
835 })
836 .map(|column| column.nullable)
837 })
838 .unwrap_or(true),
839 Expression::Aliased(alias) => {
840 return self.bind_expression_nullable(
841 &alias.expression,
842 scope,
843 ctes,
844 navigation,
845 depth,
846 )
847 }
848 Expression::FunctionCall(function)
849 if !self.output_binding_functions().exists(&function.function) =>
850 {
851 let argument_types = function
852 .arguments
853 .iter()
854 .map(|argument| {
855 self.bind_expression_logical_type(
856 argument,
857 scope,
858 ctes,
859 navigation,
860 depth + 1,
861 )
862 })
863 .collect::<Result<Vec<_>>>()?;
864 self.output_binding_stored_function(&function.function, &argument_types)?
865 .is_none_or(|(_, _, _, nullable)| nullable)
866 }
867 Expression::IntegerLiteral(_)
868 | Expression::FloatLiteral(_)
869 | Expression::StringLiteral(_)
870 | Expression::BooleanLiteral(_) => false,
871 Expression::NullLiteral(_) => true,
872 _ => true,
873 };
874 Ok(nullable)
875 }
876
877 fn bind_function_return_type(
878 &self,
879 function: &FunctionCall,
880 scope: &[BoundOutputColumn],
881 ctes: &[(String, Vec<BoundOutputColumn>)],
882 navigation: &[NavigationOutputBinding],
883 depth: usize,
884 ) -> Result<Option<DataType>> {
885 use radixdb_functions::{FunctionDataType, FunctionReturnRule};
886 let Some(info) = self.output_binding_functions().get_info(&function.function) else {
887 let argument_types = function
888 .arguments
889 .iter()
890 .map(|argument| {
891 self.bind_expression_logical_type(argument, scope, ctes, navigation, depth + 1)
892 })
893 .collect::<Result<Vec<_>>>()?;
894 return self
895 .output_binding_stored_function(&function.function, &argument_types)
896 .and_then(|bound| {
897 bound
898 .map(|(data_type, _, _, _)| data_type)
899 .map(Some)
900 .ok_or_else(|| {
901 Error::InvalidArgument(format!(
902 "unknown function {}",
903 function.function
904 ))
905 })
906 });
907 };
908 let direct = match info.signature.return_type {
909 FunctionDataType::Integer => Some(DataType::Integer),
910 FunctionDataType::Float => Some(DataType::Float),
911 FunctionDataType::String => Some(DataType::Text),
912 FunctionDataType::Boolean => Some(DataType::Boolean),
913 FunctionDataType::Timestamp | FunctionDataType::Time | FunctionDataType::DateTime => {
914 Some(DataType::Timestamp)
915 }
916 FunctionDataType::Date => Some(DataType::Date),
917 FunctionDataType::Json => Some(DataType::Json),
918 FunctionDataType::Vector => Some(DataType::Vector),
919 FunctionDataType::Any | FunctionDataType::Unknown => None,
920 };
921 if direct.is_some() {
922 return Ok(direct);
923 }
924 let argument_indices: Vec<usize> = match &info.signature.return_rule {
925 FunctionReturnRule::Declared => return Ok(None),
926 FunctionReturnRule::AllArguments => (0..function.arguments.len()).collect(),
927 FunctionReturnRule::Arguments(arguments) => arguments.clone(),
928 };
929 let mut inferred = None;
930 for index in argument_indices {
931 let Some(argument) = function.arguments.get(index) else {
932 continue;
933 };
934 if matches!(argument, Expression::Star(_)) {
935 continue;
936 }
937 if let Some(data_type) =
938 self.bind_expression_type(argument, scope, ctes, navigation, depth + 1)?
939 {
940 inferred = Some(match inferred {
941 Some(current) => Self::merge_bound_types(current, data_type)?,
942 None => data_type,
943 });
944 }
945 }
946 Ok(inferred)
947 }
948
949 fn merge_bound_types(left: DataType, right: DataType) -> Result<DataType> {
950 if left == right {
951 return Ok(left);
952 }
953 if matches!(
954 left,
955 DataType::Integer | DataType::Float | DataType::Decimal
956 ) && matches!(
957 right,
958 DataType::Integer | DataType::Float | DataType::Decimal
959 ) {
960 return Ok(if left == DataType::Decimal || right == DataType::Decimal {
961 DataType::Decimal
962 } else if left == DataType::Float || right == DataType::Float {
963 DataType::Float
964 } else {
965 DataType::Integer
966 });
967 }
968 Err(Error::Type(format!(
969 "CTAS expression has incompatible bind-time types {left:?} and {right:?}"
970 )))
971 }
972
973 fn qualify_bound_columns(
974 mut columns: Vec<BoundOutputColumn>,
975 qualifier: &str,
976 ) -> Vec<BoundOutputColumn> {
977 let qualifier = qualifier.to_lowercase();
978 for column in &mut columns {
979 column.qualifier = Some(qualifier.clone());
980 }
981 columns
982 }
983
984 fn bound_expression_name(expression: &Expression, ordinal: usize) -> String {
985 match expression {
986 Expression::Identifier(identifier) => identifier.value.to_string(),
987 Expression::QualifiedIdentifier(identifier) => identifier.name.value.to_string(),
988 Expression::Aliased(alias) => alias.alias.value.to_string(),
989 Expression::FunctionCall(function) => function.function.to_string(),
990 Expression::Cast(cast) => format!("cast_{}", cast.type_name),
991 _ => format!("column{}", ordinal + 1),
992 }
993 }
994}
995
996impl<T: OutputBindingHost + ?Sized> OutputBindingExt for T {}