1use rudb_common::{Error, Field, LogicalType, Result, Value};
4
5use crate::expr::{Arm, ColumnBinding, Expr, SortKey};
6use crate::node::Node;
7use crate::{ExprRef, NodeRef, Slice, StrRef, ValueRef};
8
9#[derive(Debug, Clone)]
27pub struct Plan {
28 nodes: Vec<Node>,
29 exprs: Vec<Expr>,
30 types: Vec<LogicalType>,
32 values: Vec<Value>,
33 strings: Vec<String>,
34 expr_lists: Vec<ExprRef>,
35 name_lists: Vec<StrRef>,
36 fields: Vec<Field>,
37 sort_keys: Vec<SortKey>,
38 arms: Vec<Arm>,
39 rows: Vec<Slice>,
40 root: NodeRef,
41}
42
43impl Default for Plan {
44 fn default() -> Self {
45 Self::new()
46 }
47}
48
49impl Plan {
50 #[must_use]
52 pub fn new() -> Self {
53 let mut plan = Self::without_nodes();
54 plan.add_node(Node::Dummy);
55 plan
56 }
57
58 pub(crate) fn without_nodes() -> Self {
64 Self {
65 nodes: Vec::new(),
66 exprs: Vec::new(),
67 types: Vec::new(),
68 values: Vec::new(),
69 strings: Vec::new(),
70 expr_lists: Vec::new(),
71 name_lists: Vec::new(),
72 fields: Vec::new(),
73 sort_keys: Vec::new(),
74 arms: Vec::new(),
75 rows: Vec::new(),
76 root: 0,
77 }
78 }
79
80 #[must_use]
82 pub fn root(&self) -> NodeRef {
83 self.root
84 }
85
86 pub fn set_root(&mut self, node: NodeRef) {
88 self.root = node;
89 }
90
91 #[must_use]
93 pub fn node_count(&self) -> usize {
94 self.nodes.len()
95 }
96
97 #[must_use]
99 pub fn expr_count(&self) -> usize {
100 self.exprs.len()
101 }
102
103 pub fn add_node(&mut self, node: Node) -> NodeRef {
108 push(&mut self.nodes, node)
109 }
110
111 pub fn add_expr(&mut self, expr: Expr, ty: LogicalType) -> ExprRef {
113 self.types.push(ty);
114 push(&mut self.exprs, expr)
115 }
116
117 pub fn add_value(&mut self, value: Value) -> ValueRef {
119 push(&mut self.values, value)
120 }
121
122 pub fn add_constant(&mut self, value: Value) -> ExprRef {
128 let ty = value.logical_type();
129 let reference = self.add_value(value);
130 self.add_expr(Expr::Constant(reference), ty)
131 }
132
133 pub fn intern(&mut self, text: &str) -> StrRef {
145 if let Some(found) = self.strings.iter().position(|held| held == text) {
146 return u32::try_from(found).expect("a string table this large cannot be built");
147 }
148 push(&mut self.strings, text.to_string())
149 }
150
151 pub fn add_expr_list(&mut self, exprs: &[ExprRef]) -> Slice {
153 extend(&mut self.expr_lists, exprs.iter().copied())
154 }
155
156 pub fn add_name_list(&mut self, names: &[StrRef]) -> Slice {
158 extend(&mut self.name_lists, names.iter().copied())
159 }
160
161 pub fn add_fields(&mut self, fields: &[Field]) -> Slice {
163 extend(&mut self.fields, fields.iter().cloned())
164 }
165
166 pub fn add_sort_keys(&mut self, keys: &[SortKey]) -> Slice {
168 extend(&mut self.sort_keys, keys.iter().copied())
169 }
170
171 pub fn add_arms(&mut self, arms: &[Arm]) -> Slice {
173 extend(&mut self.arms, arms.iter().copied())
174 }
175
176 pub fn add_rows(&mut self, rows: &[Slice]) -> Slice {
178 extend(&mut self.rows, rows.iter().copied())
179 }
180
181 #[must_use]
191 pub fn node(&self, reference: NodeRef) -> &Node {
192 &self.nodes[reference as usize]
193 }
194
195 #[must_use]
201 pub fn expr(&self, reference: ExprRef) -> &Expr {
202 &self.exprs[reference as usize]
203 }
204
205 #[must_use]
211 pub fn expr_type(&self, reference: ExprRef) -> &LogicalType {
212 &self.types[reference as usize]
213 }
214
215 #[must_use]
221 pub fn value(&self, reference: ValueRef) -> &Value {
222 &self.values[reference as usize]
223 }
224
225 #[must_use]
231 pub fn string(&self, reference: StrRef) -> &str {
232 &self.strings[reference as usize]
233 }
234
235 #[must_use]
241 pub fn expr_list(&self, slice: Slice) -> &[ExprRef] {
242 &self.expr_lists[slice.range()]
243 }
244
245 #[must_use]
251 pub fn name_list(&self, slice: Slice) -> &[StrRef] {
252 &self.name_lists[slice.range()]
253 }
254
255 #[must_use]
261 pub fn field_list(&self, slice: Slice) -> &[Field] {
262 &self.fields[slice.range()]
263 }
264
265 #[must_use]
271 pub fn sort_key_list(&self, slice: Slice) -> &[SortKey] {
272 &self.sort_keys[slice.range()]
273 }
274
275 #[must_use]
281 pub fn arm_list(&self, slice: Slice) -> &[Arm] {
282 &self.arms[slice.range()]
283 }
284
285 #[must_use]
291 pub fn row_list(&self, slice: Slice) -> &[Slice] {
292 &self.rows[slice.range()]
293 }
294
295 pub fn rebind(&mut self, reference: ExprRef, binding: ColumnBinding) {
311 match &mut self.exprs[reference as usize] {
312 Expr::Column(held) => *held = binding,
313 other => panic!("expression {reference} is {other:?}, not a column"),
314 }
315 }
316
317 pub fn node_mut(&mut self, reference: NodeRef) -> &mut Node {
323 &mut self.nodes[reference as usize]
324 }
325
326 pub fn validate(&self) -> Result<()> {
348 if self.exprs.len() != self.types.len() {
349 return Err(Error::internal(format!(
350 "the plan has {} expressions and {} types",
351 self.exprs.len(),
352 self.types.len()
353 )));
354 }
355 if self.root as usize >= self.nodes.len() {
356 return Err(Error::internal(format!(
357 "the plan is rooted at node {} and has {} nodes",
358 self.root,
359 self.nodes.len()
360 )));
361 }
362 for index in 0..self.exprs.len() {
363 self.validate_expr(u32::try_from(index).expect("index came from a length"))?;
364 }
365 for index in 0..self.nodes.len() {
366 self.validate_node(u32::try_from(index).expect("index came from a length"))?;
367 }
368 Ok(())
369 }
370
371 fn validate_expr(&self, reference: ExprRef) -> Result<()> {
372 let fail = |what: &str| Err(Error::internal(format!("expression {reference} {what}")));
373 let backwards = |operand: ExprRef| -> Result<()> {
374 if operand < reference {
375 Ok(())
376 } else {
377 Err(Error::internal(format!(
378 "expression {reference} refers to expression {operand}, which is not behind it"
379 )))
380 }
381 };
382 match *self.expr(reference) {
383 Expr::Column(_) => {}
384 Expr::Constant(value) => {
385 if value as usize >= self.values.len() {
386 return fail("names a constant that is not in the value table");
387 }
388 let held = self.value(value);
391 if !held.is_null() && held.logical_type() != *self.expr_type(reference) {
392 return fail("is a constant whose type disagrees with the value it holds");
393 }
394 }
395 Expr::Cast { input, .. } => backwards(input)?,
396 Expr::Compare { left, right, .. } => {
397 backwards(left)?;
398 backwards(right)?;
399 if *self.expr_type(reference) != LogicalType::Boolean {
400 return fail("is a comparison that does not produce BOOLEAN");
401 }
402 }
403 Expr::Conjunction { children, .. } => {
404 if children.len < 2 {
405 return fail("is a conjunction with fewer than two operands");
406 }
407 for &child in self.checked_expr_list(children, reference)? {
408 backwards(child)?;
409 }
410 if *self.expr_type(reference) != LogicalType::Boolean {
411 return fail("is a conjunction that does not produce BOOLEAN");
412 }
413 }
414 Expr::Function { name, args } | Expr::Aggregate { name, args, .. } => {
415 if name as usize >= self.strings.len() {
416 return fail("names a function that is not in the string table");
417 }
418 for &arg in self.checked_expr_list(args, reference)? {
419 backwards(arg)?;
420 }
421 if let Expr::Aggregate { filter: Some(filter), .. } = *self.expr(reference) {
422 backwards(filter)?;
423 if *self.expr_type(filter) != LogicalType::Boolean {
424 return fail("has a FILTER that is not BOOLEAN");
425 }
426 }
427 }
428 Expr::Case { arms, otherwise } => {
429 if arms.is_empty() {
430 return fail("is a CASE with no arms");
431 }
432 let end = arms.start as usize + arms.len as usize;
433 if end > self.arms.len() {
434 return fail("names an arm run that is not in the pool");
435 }
436 for arm in self.arm_list(arms) {
437 backwards(arm.when)?;
438 backwards(arm.then)?;
439 if *self.expr_type(arm.when) != LogicalType::Boolean {
440 return fail("has a WHEN that is not BOOLEAN");
441 }
442 }
443 if let Some(otherwise) = otherwise {
444 backwards(otherwise)?;
445 }
446 }
447 }
448 Ok(())
449 }
450
451 fn validate_node(&self, reference: NodeRef) -> Result<()> {
452 let node = self.node(reference);
453 let fail = |what: &str| {
454 Err(Error::internal(format!("node {reference}, which is a {}, {what}", node.keyword())))
455 };
456 for child in node.children().into_iter().flatten() {
457 if child >= reference {
458 return Err(Error::internal(format!(
459 "node {reference} has child {child}, which is not behind it"
460 )));
461 }
462 }
463 match *node {
464 Node::Dummy | Node::CrossProduct { .. } => {}
465 Node::Get { catalog, schema, table, alias, columns, .. } => {
466 for name in [catalog, schema, table, alias] {
467 if name as usize >= self.strings.len() {
468 return fail("names a string that is not in the table");
469 }
470 }
471 self.checked_field_list(columns, reference)?;
472 }
473 Node::Values { columns, rows, .. } => {
474 let width = self.checked_field_list(columns, reference)?.len();
475 let end = rows.start as usize + rows.len as usize;
476 if end > self.rows.len() {
477 return fail("names a row run that is not in the pool");
478 }
479 for row in self.row_list(rows) {
480 if self.checked_expr_list(*row, reference)?.len() != width {
481 return fail("has a row whose length is not the number of columns");
482 }
483 }
484 }
485 Node::TableFunction { function, args, options, settings, columns, .. } => {
486 if function as usize >= self.strings.len() {
487 return fail("names a string that is not in the table");
488 }
489 self.checked_field_list(columns, reference)?;
490 self.checked_expr_list(args, reference)?;
491 if self.checked_expr_list(settings, reference)?.len() != options.len as usize {
492 return fail("has a named parameter with no value or a value with no name");
493 }
494 let end = options.start as usize + options.len as usize;
495 if end > self.name_lists.len() {
496 return fail("names a name run that is not in the pool");
497 }
498 for &name in self.name_list(options) {
499 if name as usize >= self.strings.len() {
500 return fail("names a parameter that is not in the string table");
501 }
502 }
503 }
504 Node::Fetch { args, columns, row, .. } => {
505 self.checked_field_list(columns, reference)?;
506 if self.checked_expr_list(args, reference)?.len() != 1 {
507 return fail("reads other than exactly one file, which no ordinal identifies");
508 }
509 self.checked_expr(row, reference)?;
510 if *self.expr_type(row) != LogicalType::BigInt {
511 return fail("takes its ordinals from an expression that is not BIGINT");
512 }
513 }
514 Node::TableFetch { catalog, schema, table, columns, row, .. } => {
515 for name in [catalog, schema, table] {
516 if name as usize >= self.strings.len() {
517 return fail("names a string that is not in the table");
518 }
519 }
520 self.checked_field_list(columns, reference)?;
521 self.checked_expr(row, reference)?;
522 if *self.expr_type(row) != LogicalType::BigInt {
523 return fail("takes its ordinals from an expression that is not BIGINT");
524 }
525 }
526 Node::Filter { predicate, .. } => {
527 self.checked_expr(predicate, reference)?;
528 if *self.expr_type(predicate) != LogicalType::Boolean {
529 return fail("filters on an expression that is not BOOLEAN");
530 }
531 }
532 Node::Project { exprs, names, .. } => {
533 let count = self.checked_expr_list(exprs, reference)?.len();
534 let end = names.start as usize + names.len as usize;
535 if end > self.name_lists.len() {
536 return fail("names a name run that is not in the pool");
537 }
538 if self.name_list(names).len() != count {
539 return fail("has a different number of names and expressions");
540 }
541 for &name in self.name_list(names) {
542 if name as usize >= self.strings.len() {
543 return fail("names an output name that is not in the string table");
544 }
545 }
546 }
547 Node::Aggregate { groups, aggregates, .. } => {
548 for &group in self.checked_expr_list(groups, reference)? {
549 if matches!(self.expr(group), Expr::Aggregate { .. }) {
550 return fail("groups by an aggregate");
551 }
552 }
553 for &aggregate in self.checked_expr_list(aggregates, reference)? {
554 if !matches!(self.expr(aggregate), Expr::Aggregate { .. }) {
555 return fail(
556 "has something in its aggregate list that is not an aggregate",
557 );
558 }
559 }
560 }
561 Node::Sort { keys, .. } | Node::TopN { keys, .. } => {
562 let end = keys.start as usize + keys.len as usize;
563 if end > self.sort_keys.len() {
564 return fail("names a sort key run that is not in the pool");
565 }
566 if keys.is_empty() {
567 return fail("sorts on nothing");
568 }
569 for key in self.sort_key_list(keys) {
570 self.checked_expr(key.expr, reference)?;
571 }
572 }
573 Node::Limit { .. } => {}
574 Node::Distinct { on, .. } => {
575 self.checked_expr_list(on, reference)?;
576 }
577 Node::Join { conditions, .. } => {
578 for &condition in self.checked_expr_list(conditions, reference)? {
579 if *self.expr_type(condition) != LogicalType::Boolean {
580 return fail("joins on a condition that is not BOOLEAN");
581 }
582 }
583 }
584 Node::SetOp { .. } => {}
585 }
586
587 for (expr, aggregate_allowed) in self.top_level_exprs(node) {
592 if aggregate_allowed {
593 if let Expr::Aggregate { args, filter, .. } = *self.expr(expr) {
594 let nested = self
595 .expr_list(args)
596 .iter()
597 .chain(filter.iter())
598 .any(|&child| self.reaches_an_aggregate(child));
599 if nested {
600 return fail("has an aggregate inside an aggregate");
601 }
602 continue;
603 }
604 }
605 if self.reaches_an_aggregate(expr) {
606 return fail("has an aggregate outside an aggregate list");
607 }
608 }
609 Ok(())
610 }
611
612 fn top_level_exprs(&self, node: &Node) -> Vec<(ExprRef, bool)> {
618 let plain = |list: &[ExprRef]| -> Vec<(ExprRef, bool)> {
619 list.iter().map(|&expr| (expr, false)).collect()
620 };
621 match *node {
622 Node::Get { .. }
623 | Node::Dummy
624 | Node::CrossProduct { .. }
625 | Node::SetOp { .. }
626 | Node::Limit { .. } => Vec::new(),
627 Node::Values { rows, .. } => {
628 self.row_list(rows).iter().flat_map(|row| plain(self.expr_list(*row))).collect()
629 }
630 Node::TableFunction { args, .. } => plain(self.expr_list(args)),
631 Node::Fetch { args, row, .. } => {
632 let mut held = plain(self.expr_list(args));
633 held.push((row, false));
634 held
635 }
636 Node::TableFetch { row, .. } => vec![(row, false)],
637 Node::Filter { predicate, .. } => vec![(predicate, false)],
638 Node::Project { exprs, .. } => plain(self.expr_list(exprs)),
639 Node::Aggregate { groups, aggregates, .. } => {
640 let mut all = plain(self.expr_list(groups));
641 all.extend(self.expr_list(aggregates).iter().map(|&expr| (expr, true)));
642 all
643 }
644 Node::Sort { keys, .. } | Node::TopN { keys, .. } => {
645 self.sort_key_list(keys).iter().map(|key| (key.expr, false)).collect()
646 }
647 Node::Distinct { on, .. } => plain(self.expr_list(on)),
648 Node::Join { conditions, .. } => plain(self.expr_list(conditions)),
649 }
650 }
651
652 fn reaches_an_aggregate(&self, reference: ExprRef) -> bool {
657 match *self.expr(reference) {
658 Expr::Aggregate { .. } => true,
659 Expr::Column(_) | Expr::Constant(_) => false,
660 Expr::Cast { input, .. } => self.reaches_an_aggregate(input),
661 Expr::Compare { left, right, .. } => {
662 self.reaches_an_aggregate(left) || self.reaches_an_aggregate(right)
663 }
664 Expr::Conjunction { children: list, .. } | Expr::Function { args: list, .. } => {
665 self.expr_list(list).iter().any(|&child| self.reaches_an_aggregate(child))
666 }
667 Expr::Case { arms, otherwise } => {
668 self.arm_list(arms).iter().any(|arm| {
669 self.reaches_an_aggregate(arm.when) || self.reaches_an_aggregate(arm.then)
670 }) || otherwise.is_some_and(|child| self.reaches_an_aggregate(child))
671 }
672 }
673 }
674
675 fn checked_expr(&self, reference: ExprRef, node: NodeRef) -> Result<()> {
676 if reference as usize >= self.exprs.len() {
677 return Err(Error::internal(format!(
678 "node {node} names expression {reference}, which is not in the arena"
679 )));
680 }
681 Ok(())
682 }
683
684 fn checked_expr_list(&self, slice: Slice, owner: u32) -> Result<&[ExprRef]> {
685 let end = slice.start as usize + slice.len as usize;
686 if end > self.expr_lists.len() {
687 return Err(Error::internal(format!(
688 "{owner} names an expression run that is not in the pool"
689 )));
690 }
691 let list = self.expr_list(slice);
692 for &reference in list {
693 if reference as usize >= self.exprs.len() {
694 return Err(Error::internal(format!(
695 "{owner} names expression {reference}, which is not in the arena"
696 )));
697 }
698 }
699 Ok(list)
700 }
701
702 fn checked_field_list(&self, slice: Slice, owner: u32) -> Result<&[Field]> {
703 let end = slice.start as usize + slice.len as usize;
704 if end > self.fields.len() {
705 return Err(Error::internal(format!(
706 "{owner} names a field run that is not in the pool"
707 )));
708 }
709 Ok(self.field_list(slice))
710 }
711}
712
713fn push<T>(pool: &mut Vec<T>, item: T) -> u32 {
720 let index = u32::try_from(pool.len()).expect("a plan arena cannot hold four billion entries");
721 pool.push(item);
722 index
723}
724
725fn extend<T>(pool: &mut Vec<T>, items: impl Iterator<Item = T>) -> Slice {
731 let start = u32::try_from(pool.len()).expect("a plan arena cannot hold four billion entries");
732 pool.extend(items);
733 let len =
734 u32::try_from(pool.len()).expect("a plan arena cannot hold four billion entries") - start;
735 Slice { start, len }
736}
737
738#[cfg(test)]
739mod tests {
740 use super::*;
741 use crate::expr::{ColumnBinding, CompareOp};
742
743 #[test]
744 fn a_fresh_plan_is_a_valid_plan() {
745 let plan = Plan::new();
746 assert_eq!(*plan.node(plan.root()), Node::Dummy);
747 plan.validate().expect("an empty plan is one row and no columns, which is legal");
748 }
749
750 #[test]
751 fn interning_the_same_string_twice_gives_the_same_reference() {
752 let mut plan = Plan::new();
753 let first = plan.intern("hits");
754 let second = plan.intern("hits");
755 let other = plan.intern("visits");
756 assert_eq!(first, second);
757 assert_ne!(first, other);
758 assert_eq!(plan.string(first), "hits");
759 }
760
761 #[test]
762 fn a_constant_takes_its_type_from_its_value() {
763 let mut plan = Plan::new();
764 let one = plan.add_constant(Value::Integer(1));
765 assert_eq!(*plan.expr_type(one), LogicalType::Integer);
766 plan.validate().expect("a constant that agrees with itself is valid");
767 }
768
769 #[test]
772 fn a_typed_null_is_allowed_to_disagree_with_its_value() {
773 let mut plan = Plan::new();
774 let null = plan.add_value(Value::Null);
775 plan.add_expr(Expr::Constant(null), LogicalType::Varchar);
776 plan.validate().expect("a typed null is the point of carrying types separately");
777 }
778
779 #[test]
780 fn a_constant_that_disagrees_with_its_value_is_caught() {
781 let mut plan = Plan::new();
782 let value = plan.add_value(Value::Integer(1));
783 plan.add_expr(Expr::Constant(value), LogicalType::Varchar);
784 let message = plan.validate().unwrap_err().to_string();
785 assert!(message.contains("disagrees"), "unhelpful message: {message}");
786 }
787
788 #[test]
789 fn a_filter_on_something_that_is_not_boolean_is_caught() {
790 let mut plan = Plan::new();
791 let one = plan.add_constant(Value::Integer(1));
792 let filter = plan.add_node(Node::Filter { input: 0, predicate: one });
793 plan.set_root(filter);
794 let message = plan.validate().unwrap_err().to_string();
795 assert!(message.contains("BOOLEAN"), "unhelpful message: {message}");
796 }
797
798 #[test]
799 fn a_projection_with_more_expressions_than_names_is_caught() {
800 let mut plan = Plan::new();
801 let one = plan.add_constant(Value::Integer(1));
802 let two = plan.add_constant(Value::Integer(2));
803 let exprs = plan.add_expr_list(&[one, two]);
804 let name = plan.intern("a");
805 let names = plan.add_name_list(&[name]);
806 let project = plan.add_node(Node::Project { input: 0, index: 1, exprs, names });
807 plan.set_root(project);
808 let message = plan.validate().unwrap_err().to_string();
809 assert!(message.contains("names and expressions"), "unhelpful message: {message}");
810 }
811
812 #[test]
813 fn a_ragged_values_is_caught() {
814 let mut plan = Plan::new();
815 let one = plan.add_constant(Value::Integer(1));
816 let two = plan.add_constant(Value::Integer(2));
817 let wide = plan.add_expr_list(&[one, two]);
818 let narrow = plan.add_expr_list(&[one]);
819 let rows = plan.add_rows(&[wide, narrow]);
820 let columns = plan.add_fields(&[
821 Field::new("a", LogicalType::Integer),
822 Field::new("b", LogicalType::Integer),
823 ]);
824 let values = plan.add_node(Node::Values { index: 0, columns, rows });
825 plan.set_root(values);
826 let message = plan.validate().unwrap_err().to_string();
827 assert!(message.contains("number of columns"), "unhelpful message: {message}");
828 }
829
830 #[test]
831 fn an_aggregate_outside_an_aggregate_list_is_caught() {
832 let mut plan = Plan::new();
833 let name = plan.intern("count_star");
834 let count = plan.add_expr(
835 Expr::Aggregate { name, args: Slice::EMPTY, distinct: false, filter: None },
836 LogicalType::BigInt,
837 );
838 let zero = plan.add_constant(Value::BigInt(0));
839 let compare = plan.add_expr(
840 Expr::Compare { op: CompareOp::Greater, left: count, right: zero },
841 LogicalType::Boolean,
842 );
843 let filter = plan.add_node(Node::Filter { input: 0, predicate: compare });
844 plan.set_root(filter);
845 let message = plan.validate().unwrap_err().to_string();
846 assert!(message.contains("aggregate outside"), "unhelpful message: {message}");
847 }
848
849 #[test]
850 fn an_aggregate_inside_an_aggregate_list_is_fine() {
851 let mut plan = Plan::new();
852 let name = plan.intern("count_star");
853 let count = plan.add_expr(
854 Expr::Aggregate { name, args: Slice::EMPTY, distinct: false, filter: None },
855 LogicalType::BigInt,
856 );
857 let aggregates = plan.add_expr_list(&[count]);
858 let aggregate =
859 plan.add_node(Node::Aggregate { input: 0, index: 1, groups: Slice::EMPTY, aggregates });
860 plan.set_root(aggregate);
861 plan.validate().expect("this is the one place an aggregate belongs");
862 }
863
864 #[test]
867 fn a_node_that_refers_to_itself_is_caught() {
868 let mut plan = Plan::new();
869 let filter = plan.add_node(Node::Filter { input: 0, predicate: 0 });
870 let one = plan.add_constant(Value::Boolean(true));
871 plan.nodes[filter as usize] = Node::Filter { input: filter, predicate: one };
872 plan.set_root(filter);
873 let message = plan.validate().unwrap_err().to_string();
874 assert!(message.contains("not behind it"), "unhelpful message: {message}");
875 }
876
877 #[test]
878 fn an_expression_that_refers_forwards_is_caught() {
879 let mut plan = Plan::new();
880 let left = plan.add_constant(Value::Integer(1));
881 let compare = plan.add_expr(
882 Expr::Compare { op: CompareOp::Equal, left, right: left },
883 LogicalType::Boolean,
884 );
885 plan.exprs[compare as usize] =
886 Expr::Compare { op: CompareOp::Equal, left, right: compare + 1 };
887 plan.add_constant(Value::Integer(2));
888 let message = plan.validate().unwrap_err().to_string();
889 assert!(message.contains("not behind it"), "unhelpful message: {message}");
890 }
891
892 #[test]
893 fn a_root_that_is_not_in_the_arena_is_caught() {
894 let mut plan = Plan::new();
895 plan.set_root(17);
896 let message = plan.validate().unwrap_err().to_string();
897 assert!(message.contains("rooted at node 17"), "unhelpful message: {message}");
898 }
899
900 #[test]
901 fn a_column_binding_is_two_numbers_and_nothing_else() {
902 let binding = ColumnBinding::new(3, 7);
903 assert_eq!(binding.table, 3);
904 assert_eq!(binding.column, 7);
905 assert_eq!(size_of::<ColumnBinding>(), 8);
906 }
907}