1use std::{
4 collections::{HashMap, HashSet},
5 sync::Arc,
6};
7
8use crate::errors::ExecutorError;
9
10pub type CteResult = (vibesql_catalog::TableSchema, Arc<Vec<vibesql_storage::Row>>);
18
19pub fn execute_ctes<F>(
27 ctes: &[vibesql_ast::CommonTableExpr],
28 database: &vibesql_storage::Database,
29 executor: F,
30) -> Result<HashMap<String, CteResult>, ExecutorError>
31where
32 F: Fn(
33 &vibesql_ast::SelectStmt,
34 &HashMap<String, CteResult>,
35 ) -> Result<Vec<vibesql_storage::Row>, ExecutorError>,
36{
37 execute_ctes_with_memory_check(ctes, database, executor, |_| Ok(()))
39}
40
41pub(super) fn execute_ctes_with_memory_check<F, M>(
47 ctes: &[vibesql_ast::CommonTableExpr],
48 database: &vibesql_storage::Database,
49 executor: F,
50 memory_check: M,
51) -> Result<HashMap<String, CteResult>, ExecutorError>
52where
53 F: Fn(
54 &vibesql_ast::SelectStmt,
55 &HashMap<String, CteResult>,
56 ) -> Result<Vec<vibesql_storage::Row>, ExecutorError>,
57 M: Fn(usize) -> Result<(), ExecutorError>,
58{
59 let mut cte_results = HashMap::new();
60
61 for cte in ctes {
64 let is_recursive = cte.recursive || is_cte_self_referential(cte);
68 let rows = if is_recursive {
69 execute_recursive_cte(cte, &cte_results, database, &executor, &memory_check)?
71 } else {
72 executor(&cte.query, &cte_results)?
74 };
75
76 let estimated_size = super::helpers::estimate_result_size(&rows);
78 memory_check(estimated_size)?;
79
80 let schema = derive_cte_schema(cte, &rows, database, &cte_results)?;
82
83 cte_results.insert(cte.name.clone(), (schema, Arc::new(rows)));
85 }
86
87 Ok(cte_results)
88}
89
90pub(super) fn derive_cte_schema(
97 cte: &vibesql_ast::CommonTableExpr,
98 rows: &[vibesql_storage::Row],
99 database: &vibesql_storage::Database,
100 prior_ctes: &HashMap<String, CteResult>,
101) -> Result<vibesql_catalog::TableSchema, ExecutorError> {
102 if let Some(column_names) = &cte.columns {
104 if let Some(first_row) = rows.first() {
106 if first_row.values.len() != column_names.len() {
107 return Err(ExecutorError::UnsupportedFeature(format!(
108 "CTE column count mismatch: specified {} columns but query returned {}",
109 column_names.len(),
110 first_row.values.len()
111 )));
112 }
113
114 let columns = column_names
115 .iter()
116 .zip(&first_row.values)
117 .map(|(name, value)| {
118 let data_type = infer_type_from_value(value);
119 vibesql_catalog::ColumnSchema::new(name.clone(), data_type, true)
120 })
123 .collect();
124
125 Ok(cte_pseudo_schema(cte.name.clone(), columns))
126 } else {
127 let columns = column_names
129 .iter()
130 .map(|name| {
131 vibesql_catalog::ColumnSchema::new(
132 name.clone(),
133 vibesql_types::DataType::Varchar { max_length: Some(255) },
134 true,
135 )
136 })
137 .collect();
138
139 Ok(cte_pseudo_schema(cte.name.clone(), columns))
140 }
141 } else {
142 let mut columns: Vec<vibesql_catalog::ColumnSchema> = Vec::new();
149 let mut value_idx = 0usize;
150
151 for (i, item) in cte.query.select_list.iter().enumerate() {
152 let names: Vec<String> = match item {
154 vibesql_ast::SelectItem::Wildcard { .. }
155 | vibesql_ast::SelectItem::QualifiedWildcard { .. } => {
156 expand_wildcard_names(item, &cte.query, database, prior_ctes)
157 .unwrap_or_else(|| vec![format!("col{}", i)])
160 }
161 vibesql_ast::SelectItem::Expression { expr, alias, .. } => {
162 vec![if let Some(a) = alias {
163 a.clone()
164 } else {
165 match expr {
167 vibesql_ast::Expression::ColumnRef(col_id) => {
168 col_id.column_canonical().to_string()
169 }
170 _ => format!("col{}", i),
171 }
172 }]
173 }
174 };
175
176 for name in names {
177 let data_type = rows
179 .first()
180 .and_then(|first_row| first_row.values.get(value_idx))
181 .map(infer_type_from_value)
182 .unwrap_or(vibesql_types::DataType::Varchar { max_length: Some(255) });
183
184 columns.push(vibesql_catalog::ColumnSchema::new(name, data_type, true)); value_idx += 1;
186 }
187 }
188
189 if let Some(first_row) = rows.first() {
194 if columns.len() != first_row.values.len() {
195 return Ok(legacy_cte_schema(cte, rows));
196 }
197 }
198
199 Ok(cte_pseudo_schema(cte.name.clone(), columns))
200 }
201}
202
203fn cte_pseudo_schema(
213 name: String,
214 columns: Vec<vibesql_catalog::ColumnSchema>,
215) -> vibesql_catalog::TableSchema {
216 let mut schema = vibesql_catalog::TableSchema::new(name, columns);
217 schema.set_is_view(true);
218 schema
219}
220
221fn legacy_cte_schema(
225 cte: &vibesql_ast::CommonTableExpr,
226 rows: &[vibesql_storage::Row],
227) -> vibesql_catalog::TableSchema {
228 let columns = cte
229 .query
230 .select_list
231 .iter()
232 .enumerate()
233 .map(|(i, item)| {
234 let data_type = rows
235 .first()
236 .and_then(|first_row| first_row.values.get(i))
237 .map(infer_type_from_value)
238 .unwrap_or(vibesql_types::DataType::Varchar { max_length: Some(255) });
239
240 let col_name = match item {
241 vibesql_ast::SelectItem::Wildcard { .. }
242 | vibesql_ast::SelectItem::QualifiedWildcard { .. } => format!("col{}", i),
243 vibesql_ast::SelectItem::Expression { expr, alias, .. } => {
244 if let Some(a) = alias {
245 a.clone()
246 } else {
247 match expr {
248 vibesql_ast::Expression::ColumnRef(col_id) => {
249 col_id.column_canonical().to_string()
250 }
251 _ => format!("col{}", i),
252 }
253 }
254 }
255 };
256
257 vibesql_catalog::ColumnSchema::new(col_name, data_type, true) })
259 .collect();
260
261 cte_pseudo_schema(cte.name.clone(), columns)
262}
263
264struct WildcardColumn {
271 name: String,
272 hidden_for_star: bool,
273}
274
275struct WildcardSource {
278 qualifier: String,
279 columns: Vec<WildcardColumn>,
280}
281
282fn visible_columns(names: Vec<String>) -> Vec<WildcardColumn> {
284 names.into_iter().map(|name| WildcardColumn { name, hidden_for_star: false }).collect()
285}
286
287fn view_column_names(name: &str, database: &vibesql_storage::Database) -> Option<Vec<String>> {
295 let view = database.catalog.get_view(name)?;
296 if let Some(cols) = &view.columns {
297 return Some(cols.clone());
298 }
299 collect_select_list_columns(&view.query, database, &HashMap::new())
302}
303
304fn expand_wildcard_names(
310 item: &vibesql_ast::SelectItem,
311 stmt: &vibesql_ast::SelectStmt,
312 database: &vibesql_storage::Database,
313 prior_ctes: &HashMap<String, CteResult>,
314) -> Option<Vec<String>> {
315 match item {
316 vibesql_ast::SelectItem::Wildcard { alias } => {
317 if let Some(alias_names) = alias {
319 return Some(alias_names.clone());
320 }
321 let sources = collect_from_sources(stmt.from.as_ref()?, database, prior_ctes)?;
322 Some(
323 sources
324 .into_iter()
325 .flat_map(|s| s.columns)
326 .filter(|c| !c.hidden_for_star)
327 .map(|c| c.name)
328 .collect(),
329 )
330 }
331 vibesql_ast::SelectItem::QualifiedWildcard { qualifier, alias } => {
332 if let Some(alias_names) = alias {
333 return Some(alias_names.clone());
334 }
335 let sources = collect_from_sources(stmt.from.as_ref()?, database, prior_ctes)?;
336 sources
337 .into_iter()
338 .find(|s| s.qualifier.eq_ignore_ascii_case(qualifier))
339 .map(|s| s.columns.into_iter().map(|c| c.name).collect())
342 }
343 vibesql_ast::SelectItem::Expression { .. } => None,
344 }
345}
346
347fn collect_from_sources(
355 from: &vibesql_ast::FromClause,
356 database: &vibesql_storage::Database,
357 prior_ctes: &HashMap<String, CteResult>,
358) -> Option<Vec<WildcardSource>> {
359 match from {
360 vibesql_ast::FromClause::Table { name, alias, column_aliases, .. } => {
361 let base_columns: Vec<String> = if let Some((schema, _)) =
364 prior_ctes.get(name).or_else(|| {
365 prior_ctes.iter().find(|(k, _)| k.eq_ignore_ascii_case(name)).map(|(_, v)| v)
366 }) {
367 schema.columns.iter().map(|c| c.name.clone()).collect()
368 } else if let Some(table) = database.get_table(name) {
369 table.schema.columns.iter().map(|c| c.name.clone()).collect()
370 } else if let Some(cols) = view_column_names(name, database) {
371 cols
378 } else {
379 return None;
381 };
382
383 let columns = match column_aliases {
385 Some(aliases) if aliases.len() == base_columns.len() => aliases.clone(),
386 Some(_) => return None, None => base_columns,
388 };
389
390 let qualifier = alias.clone().unwrap_or_else(|| name.clone());
391 Some(vec![WildcardSource { qualifier, columns: visible_columns(columns) }])
392 }
393 vibesql_ast::FromClause::Join { left, right, natural, using_columns, alias, .. } => {
394 if alias.is_some() && (*natural || using_columns.is_some()) {
399 return None;
400 }
401
402 let mut sources = collect_from_sources(left, database, prior_ctes)?;
403 let mut right_sources = collect_from_sources(right, database, prior_ctes)?;
404
405 if *natural || using_columns.is_some() {
411 let shared: Vec<String> = if let Some(using) = using_columns {
412 using.clone()
413 } else {
414 let left_visible: Vec<&str> = sources
420 .iter()
421 .flat_map(|s| s.columns.iter())
422 .filter(|c| !c.hidden_for_star)
423 .map(|c| c.name.as_str())
424 .collect();
425 right_sources
426 .iter()
427 .flat_map(|s| s.columns.iter())
428 .filter(|c| !c.hidden_for_star)
429 .filter(|c| left_visible.iter().any(|l| l.eq_ignore_ascii_case(&c.name)))
430 .map(|c| c.name.clone())
431 .collect()
432 };
433
434 for source in &mut right_sources {
437 for col in &mut source.columns {
438 if shared.iter().any(|s| s.eq_ignore_ascii_case(&col.name)) {
439 col.hidden_for_star = true;
440 }
441 }
442 }
443 }
444
445 sources.extend(right_sources);
446 Some(sources)
447 }
448 vibesql_ast::FromClause::Subquery { query, alias, column_aliases } => {
449 let columns = if let Some(aliases) = column_aliases {
450 aliases.clone()
451 } else {
452 collect_select_list_columns(query, database, prior_ctes)?
453 };
454 Some(vec![WildcardSource {
455 qualifier: alias.clone(),
456 columns: visible_columns(columns),
457 }])
458 }
459 vibesql_ast::FromClause::Values { rows, alias, column_aliases } => {
460 let columns = if let Some(aliases) = column_aliases {
461 aliases.clone()
462 } else {
463 let first_row = rows.first()?;
464 (0..first_row.len()).map(|i| format!("col{}", i)).collect()
465 };
466 Some(vec![WildcardSource {
467 qualifier: alias.clone(),
468 columns: visible_columns(columns),
469 }])
470 }
471 }
472}
473
474pub(crate) fn try_static_select_columns(
486 stmt: &vibesql_ast::SelectStmt,
487 database: &vibesql_storage::Database,
488) -> Option<Vec<String>> {
489 collect_select_list_columns(stmt, database, &HashMap::new())
490}
491
492fn collect_select_list_columns(
497 stmt: &vibesql_ast::SelectStmt,
498 database: &vibesql_storage::Database,
499 prior_ctes: &HashMap<String, CteResult>,
500) -> Option<Vec<String>> {
501 if let Some(values_rows) = &stmt.values {
503 let first_row = values_rows.first()?;
504 return Some((0..first_row.len()).map(|i| format!("col{}", i)).collect());
505 }
506
507 let mut names = Vec::new();
508 for (i, item) in stmt.select_list.iter().enumerate() {
509 match item {
510 vibesql_ast::SelectItem::Wildcard { .. }
511 | vibesql_ast::SelectItem::QualifiedWildcard { .. } => {
512 names.extend(expand_wildcard_names(item, stmt, database, prior_ctes)?);
513 }
514 vibesql_ast::SelectItem::Expression { expr, alias, .. } => {
515 names.push(if let Some(a) = alias {
516 a.clone()
517 } else {
518 match expr {
519 vibesql_ast::Expression::ColumnRef(col_id) => {
520 col_id.column_canonical().to_string()
521 }
522 _ => format!("col{}", i),
523 }
524 });
525 }
526 }
527 }
528 Some(names)
529}
530
531fn execute_recursive_cte<F, M>(
551 cte: &vibesql_ast::CommonTableExpr,
552 cte_results: &HashMap<String, CteResult>,
553 database: &vibesql_storage::Database,
554 executor: &F,
555 memory_check: &M,
556) -> Result<Vec<vibesql_storage::Row>, ExecutorError>
557where
558 F: Fn(
559 &vibesql_ast::SelectStmt,
560 &HashMap<String, CteResult>,
561 ) -> Result<Vec<vibesql_storage::Row>, ExecutorError>,
562 M: Fn(usize) -> Result<(), ExecutorError>,
563{
564 use crate::limits::MAX_RECURSIVE_CTE_ITERATIONS;
565
566 let set_op = cte.query.set_operation.as_ref().ok_or_else(|| {
568 ExecutorError::UnsupportedFeature(format!(
569 "Recursive CTE '{}' must use UNION ALL",
570 cte.name
571 ))
572 })?;
573
574 if set_op.op != vibesql_ast::SetOperator::Union {
575 return Err(ExecutorError::UnsupportedFeature(format!(
576 "Recursive CTE '{}' must use UNION or UNION ALL (not INTERSECT or EXCEPT)",
577 cte.name
578 )));
579 }
580
581 let base_query = vibesql_ast::SelectStmt {
588 with_clause: cte.query.with_clause.clone(),
589 distinct: cte.query.distinct,
590 select_list: cte.query.select_list.clone(),
591 into_table: cte.query.into_table.clone(),
592 into_variables: cte.query.into_variables.clone(),
593 from: cte.query.from.clone(),
594 where_clause: cte.query.where_clause.clone(),
595 group_by: cte.query.group_by.clone(),
596 having: cte.query.having.clone(),
597 window_definitions: cte.query.window_definitions.clone(),
598 order_by: cte.query.order_by.clone(),
599 limit: cte.query.limit.clone(),
600 offset: cte.query.offset.clone(),
601 set_operation: None, values: cte.query.values.clone(),
603 };
604 let recursive_query = &set_op.right;
605
606 if crate::select::window::has_window_functions(&recursive_query.select_list) {
610 return Err(ExecutorError::SqliteCompatError(
611 "cannot use window functions in recursive queries".to_string(),
612 ));
613 }
614
615 if let (Some(base_count), Some(recursive_count)) =
620 (count_stmt_columns(&base_query), count_stmt_columns(recursive_query))
621 {
622 if base_count != recursive_count {
623 return Err(ExecutorError::UnsupportedFeature(
624 "SELECTs to the left and right of UNION ALL do not have the same number of result columns".to_string()
625 ));
626 }
627 }
628 let mut all_rows = executor(&base_query, cte_results)?;
632 let mut working_table = all_rows.clone();
633
634 let schema = derive_cte_schema(cte, &all_rows, database, cte_results)?;
638
639 let mut seen_rows: Option<HashSet<vibesql_storage::RowValues>> = if !set_op.all {
642 let mut seen = HashSet::with_capacity(all_rows.len());
643 for row in &all_rows {
644 seen.insert(row.values.clone());
645 }
646 Some(seen)
647 } else {
648 None
649 };
650
651 let mut depth = 0;
653 while !working_table.is_empty() && depth < MAX_RECURSIVE_CTE_ITERATIONS {
654 depth += 1;
655
656 let mut recursive_cte_results = cte_results.clone();
658 recursive_cte_results
659 .insert(cte.name.clone(), (schema.clone(), Arc::new(working_table.clone())));
660
661 let new_rows = executor(recursive_query, &recursive_cte_results)?;
663
664 if new_rows.is_empty() {
666 break;
667 }
668
669 if depth == 1 && !new_rows.is_empty() && !all_rows.is_empty() {
672 let base_col_count = all_rows[0].values.len();
673 let recursive_col_count = new_rows[0].values.len();
674 if base_col_count != recursive_col_count {
675 return Err(ExecutorError::UnsupportedFeature(
676 "SELECTs to the left and right of UNION ALL do not have the same number of result columns".to_string()
677 ));
678 }
679 }
680
681 let estimated_size = super::helpers::estimate_result_size(&new_rows);
683 memory_check(estimated_size)?;
684
685 let rows_to_add: Vec<vibesql_storage::Row> = if let Some(ref mut seen) = seen_rows {
687 new_rows.into_iter().filter(|row| seen.insert(row.values.clone())).collect()
689 } else {
690 new_rows
692 };
693
694 if rows_to_add.is_empty() {
696 break;
697 }
698
699 all_rows.extend(rows_to_add.clone());
701
702 working_table = rows_to_add;
704 }
705
706 if depth >= MAX_RECURSIVE_CTE_ITERATIONS {
708 return Err(ExecutorError::UnsupportedFeature(format!(
709 "Recursive CTE '{}' exceeded maximum iteration limit of {}",
710 cte.name, MAX_RECURSIVE_CTE_ITERATIONS
711 )));
712 }
713
714 Ok(all_rows)
715}
716
717fn count_explicit_columns(select_list: &[vibesql_ast::SelectItem]) -> Option<usize> {
722 let mut count = 0;
723 for item in select_list {
724 match item {
725 vibesql_ast::SelectItem::Expression { .. } => count += 1,
726 vibesql_ast::SelectItem::Wildcard { .. }
728 | vibesql_ast::SelectItem::QualifiedWildcard { .. } => {
729 return None;
730 }
731 }
732 }
733 Some(count)
734}
735
736fn count_stmt_columns(stmt: &vibesql_ast::SelectStmt) -> Option<usize> {
741 if let Some(values_rows) = &stmt.values {
743 return values_rows.first().map(|row| row.len());
744 }
745
746 count_explicit_columns(&stmt.select_list)
748}
749
750fn is_cte_self_referential(cte: &vibesql_ast::CommonTableExpr) -> bool {
756 let set_op = match &cte.query.set_operation {
758 Some(op) if op.op == vibesql_ast::SetOperator::Union => op,
759 _ => return false,
760 };
761
762 stmt_references_table(&set_op.right, &cte.name)
764}
765
766fn stmt_references_table(stmt: &vibesql_ast::SelectStmt, table_name: &str) -> bool {
768 if let Some(from) = &stmt.from {
770 if from_clause_references_table(from, table_name) {
771 return true;
772 }
773 }
774
775 if let Some(where_clause) = &stmt.where_clause {
777 if expr_references_table(where_clause, table_name) {
778 return true;
779 }
780 }
781
782 for item in &stmt.select_list {
784 if let vibesql_ast::SelectItem::Expression { expr, .. } = item {
785 if expr_references_table(expr, table_name) {
786 return true;
787 }
788 }
789 }
790
791 false
792}
793
794fn from_clause_references_table(from: &vibesql_ast::FromClause, table_name: &str) -> bool {
796 match from {
797 vibesql_ast::FromClause::Table { name, .. } => name.eq_ignore_ascii_case(table_name),
798 vibesql_ast::FromClause::Subquery { query, .. } => stmt_references_table(query, table_name),
799 vibesql_ast::FromClause::Join { left, right, condition, .. } => {
800 from_clause_references_table(left, table_name)
801 || from_clause_references_table(right, table_name)
802 || condition.as_ref().map_or(false, |c| expr_references_table(c, table_name))
803 }
804 vibesql_ast::FromClause::Values { .. } => false,
805 }
806}
807
808fn expr_references_table(expr: &vibesql_ast::Expression, table_name: &str) -> bool {
810 match expr {
811 vibesql_ast::Expression::ScalarSubquery(subquery) => {
812 stmt_references_table(subquery, table_name)
813 }
814 vibesql_ast::Expression::In { subquery, .. } => stmt_references_table(subquery, table_name),
815 vibesql_ast::Expression::Exists { subquery, .. } => {
816 stmt_references_table(subquery, table_name)
817 }
818 vibesql_ast::Expression::BinaryOp { left, right, .. } => {
819 expr_references_table(left, table_name) || expr_references_table(right, table_name)
820 }
821 vibesql_ast::Expression::UnaryOp { expr, .. } => expr_references_table(expr, table_name),
822 vibesql_ast::Expression::Function { args, .. } => {
823 args.iter().any(|arg| expr_references_table(arg, table_name))
824 }
825 vibesql_ast::Expression::AggregateFunction { args, filter, .. } => {
826 args.iter().any(|arg| expr_references_table(arg, table_name))
827 || filter.as_ref().map_or(false, |f| expr_references_table(f, table_name))
828 }
829 vibesql_ast::Expression::Case { operand, when_clauses, else_result, .. } => {
830 operand.as_ref().map_or(false, |o| expr_references_table(o, table_name))
831 || when_clauses.iter().any(|when| {
832 when.conditions.iter().any(|c| expr_references_table(c, table_name))
833 || expr_references_table(&when.result, table_name)
834 })
835 || else_result.as_ref().map_or(false, |e| expr_references_table(e, table_name))
836 }
837 vibesql_ast::Expression::Between { expr, low, high, .. } => {
838 expr_references_table(expr, table_name)
839 || expr_references_table(low, table_name)
840 || expr_references_table(high, table_name)
841 }
842 vibesql_ast::Expression::InList { expr, values, .. } => {
843 expr_references_table(expr, table_name)
844 || values.iter().any(|e| expr_references_table(e, table_name))
845 }
846 vibesql_ast::Expression::Cast { expr, .. }
847 | vibesql_ast::Expression::Collate { expr, .. } => expr_references_table(expr, table_name),
848 vibesql_ast::Expression::Conjunction(exprs)
849 | vibesql_ast::Expression::Disjunction(exprs) => {
850 exprs.iter().any(|e| expr_references_table(e, table_name))
851 }
852 vibesql_ast::Expression::QuantifiedComparison { expr, subquery, .. } => {
853 expr_references_table(expr, table_name) || stmt_references_table(subquery, table_name)
854 }
855 _ => false,
856 }
857}
858
859pub(super) fn infer_type_from_value(value: &vibesql_types::SqlValue) -> vibesql_types::DataType {
861 match value {
862 vibesql_types::SqlValue::Null => vibesql_types::DataType::Varchar { max_length: Some(255) }, vibesql_types::SqlValue::Integer(_) => vibesql_types::DataType::Integer,
864 vibesql_types::SqlValue::Varchar(_) => {
865 vibesql_types::DataType::Varchar { max_length: Some(255) }
866 }
867 vibesql_types::SqlValue::Character(_) => vibesql_types::DataType::Character { length: 1 },
868 vibesql_types::SqlValue::Boolean(_) => vibesql_types::DataType::Boolean,
869 vibesql_types::SqlValue::Float(_) => vibesql_types::DataType::Float { precision: 53 },
870 vibesql_types::SqlValue::Double(_) => vibesql_types::DataType::DoublePrecision,
871 vibesql_types::SqlValue::Numeric(_) => {
872 vibesql_types::DataType::Numeric { precision: 10, scale: 2 }
873 }
874 vibesql_types::SqlValue::Real(_) => vibesql_types::DataType::Real,
875 vibesql_types::SqlValue::Smallint(_) => vibesql_types::DataType::Smallint,
876 vibesql_types::SqlValue::Bigint(_) => vibesql_types::DataType::Bigint,
877 vibesql_types::SqlValue::Unsigned(_) => vibesql_types::DataType::Unsigned,
878 vibesql_types::SqlValue::Date(_) => vibesql_types::DataType::Date,
879 vibesql_types::SqlValue::Time(_) => vibesql_types::DataType::Time { with_timezone: false },
880 vibesql_types::SqlValue::Timestamp(_) => {
881 vibesql_types::DataType::Timestamp { with_timezone: false }
882 }
883 vibesql_types::SqlValue::Interval(_) => {
884 vibesql_types::DataType::Interval {
886 start_field: vibesql_types::IntervalField::Day,
887 end_field: None,
888 }
889 }
890 vibesql_types::SqlValue::Vector(v) => {
891 vibesql_types::DataType::Vector { dimensions: v.len() as u32 }
892 }
893 vibesql_types::SqlValue::Blob(_) => vibesql_types::DataType::BinaryLargeObject,
894 }
895}