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::Filter { predicate, .. } => {
515 self.checked_expr(predicate, reference)?;
516 if *self.expr_type(predicate) != LogicalType::Boolean {
517 return fail("filters on an expression that is not BOOLEAN");
518 }
519 }
520 Node::Project { exprs, names, .. } => {
521 let count = self.checked_expr_list(exprs, reference)?.len();
522 let end = names.start as usize + names.len as usize;
523 if end > self.name_lists.len() {
524 return fail("names a name run that is not in the pool");
525 }
526 if self.name_list(names).len() != count {
527 return fail("has a different number of names and expressions");
528 }
529 for &name in self.name_list(names) {
530 if name as usize >= self.strings.len() {
531 return fail("names an output name that is not in the string table");
532 }
533 }
534 }
535 Node::Aggregate { groups, aggregates, .. } => {
536 for &group in self.checked_expr_list(groups, reference)? {
537 if matches!(self.expr(group), Expr::Aggregate { .. }) {
538 return fail("groups by an aggregate");
539 }
540 }
541 for &aggregate in self.checked_expr_list(aggregates, reference)? {
542 if !matches!(self.expr(aggregate), Expr::Aggregate { .. }) {
543 return fail(
544 "has something in its aggregate list that is not an aggregate",
545 );
546 }
547 }
548 }
549 Node::Sort { keys, .. } | Node::TopN { keys, .. } => {
550 let end = keys.start as usize + keys.len as usize;
551 if end > self.sort_keys.len() {
552 return fail("names a sort key run that is not in the pool");
553 }
554 if keys.is_empty() {
555 return fail("sorts on nothing");
556 }
557 for key in self.sort_key_list(keys) {
558 self.checked_expr(key.expr, reference)?;
559 }
560 }
561 Node::Limit { .. } => {}
562 Node::Distinct { on, .. } => {
563 self.checked_expr_list(on, reference)?;
564 }
565 Node::Join { conditions, .. } => {
566 for &condition in self.checked_expr_list(conditions, reference)? {
567 if *self.expr_type(condition) != LogicalType::Boolean {
568 return fail("joins on a condition that is not BOOLEAN");
569 }
570 }
571 }
572 Node::SetOp { .. } => {}
573 }
574
575 for (expr, aggregate_allowed) in self.top_level_exprs(node) {
580 if aggregate_allowed {
581 if let Expr::Aggregate { args, filter, .. } = *self.expr(expr) {
582 let nested = self
583 .expr_list(args)
584 .iter()
585 .chain(filter.iter())
586 .any(|&child| self.reaches_an_aggregate(child));
587 if nested {
588 return fail("has an aggregate inside an aggregate");
589 }
590 continue;
591 }
592 }
593 if self.reaches_an_aggregate(expr) {
594 return fail("has an aggregate outside an aggregate list");
595 }
596 }
597 Ok(())
598 }
599
600 fn top_level_exprs(&self, node: &Node) -> Vec<(ExprRef, bool)> {
606 let plain = |list: &[ExprRef]| -> Vec<(ExprRef, bool)> {
607 list.iter().map(|&expr| (expr, false)).collect()
608 };
609 match *node {
610 Node::Get { .. }
611 | Node::Dummy
612 | Node::CrossProduct { .. }
613 | Node::SetOp { .. }
614 | Node::Limit { .. } => Vec::new(),
615 Node::Values { rows, .. } => {
616 self.row_list(rows).iter().flat_map(|row| plain(self.expr_list(*row))).collect()
617 }
618 Node::TableFunction { args, .. } => plain(self.expr_list(args)),
619 Node::Fetch { args, row, .. } => {
620 let mut held = plain(self.expr_list(args));
621 held.push((row, false));
622 held
623 }
624 Node::Filter { predicate, .. } => vec![(predicate, false)],
625 Node::Project { exprs, .. } => plain(self.expr_list(exprs)),
626 Node::Aggregate { groups, aggregates, .. } => {
627 let mut all = plain(self.expr_list(groups));
628 all.extend(self.expr_list(aggregates).iter().map(|&expr| (expr, true)));
629 all
630 }
631 Node::Sort { keys, .. } | Node::TopN { keys, .. } => {
632 self.sort_key_list(keys).iter().map(|key| (key.expr, false)).collect()
633 }
634 Node::Distinct { on, .. } => plain(self.expr_list(on)),
635 Node::Join { conditions, .. } => plain(self.expr_list(conditions)),
636 }
637 }
638
639 fn reaches_an_aggregate(&self, reference: ExprRef) -> bool {
644 match *self.expr(reference) {
645 Expr::Aggregate { .. } => true,
646 Expr::Column(_) | Expr::Constant(_) => false,
647 Expr::Cast { input, .. } => self.reaches_an_aggregate(input),
648 Expr::Compare { left, right, .. } => {
649 self.reaches_an_aggregate(left) || self.reaches_an_aggregate(right)
650 }
651 Expr::Conjunction { children: list, .. } | Expr::Function { args: list, .. } => {
652 self.expr_list(list).iter().any(|&child| self.reaches_an_aggregate(child))
653 }
654 Expr::Case { arms, otherwise } => {
655 self.arm_list(arms).iter().any(|arm| {
656 self.reaches_an_aggregate(arm.when) || self.reaches_an_aggregate(arm.then)
657 }) || otherwise.is_some_and(|child| self.reaches_an_aggregate(child))
658 }
659 }
660 }
661
662 fn checked_expr(&self, reference: ExprRef, node: NodeRef) -> Result<()> {
663 if reference as usize >= self.exprs.len() {
664 return Err(Error::internal(format!(
665 "node {node} names expression {reference}, which is not in the arena"
666 )));
667 }
668 Ok(())
669 }
670
671 fn checked_expr_list(&self, slice: Slice, owner: u32) -> Result<&[ExprRef]> {
672 let end = slice.start as usize + slice.len as usize;
673 if end > self.expr_lists.len() {
674 return Err(Error::internal(format!(
675 "{owner} names an expression run that is not in the pool"
676 )));
677 }
678 let list = self.expr_list(slice);
679 for &reference in list {
680 if reference as usize >= self.exprs.len() {
681 return Err(Error::internal(format!(
682 "{owner} names expression {reference}, which is not in the arena"
683 )));
684 }
685 }
686 Ok(list)
687 }
688
689 fn checked_field_list(&self, slice: Slice, owner: u32) -> Result<&[Field]> {
690 let end = slice.start as usize + slice.len as usize;
691 if end > self.fields.len() {
692 return Err(Error::internal(format!(
693 "{owner} names a field run that is not in the pool"
694 )));
695 }
696 Ok(self.field_list(slice))
697 }
698}
699
700fn push<T>(pool: &mut Vec<T>, item: T) -> u32 {
707 let index = u32::try_from(pool.len()).expect("a plan arena cannot hold four billion entries");
708 pool.push(item);
709 index
710}
711
712fn extend<T>(pool: &mut Vec<T>, items: impl Iterator<Item = T>) -> Slice {
718 let start = u32::try_from(pool.len()).expect("a plan arena cannot hold four billion entries");
719 pool.extend(items);
720 let len =
721 u32::try_from(pool.len()).expect("a plan arena cannot hold four billion entries") - start;
722 Slice { start, len }
723}
724
725#[cfg(test)]
726mod tests {
727 use super::*;
728 use crate::expr::{ColumnBinding, CompareOp};
729
730 #[test]
731 fn a_fresh_plan_is_a_valid_plan() {
732 let plan = Plan::new();
733 assert_eq!(*plan.node(plan.root()), Node::Dummy);
734 plan.validate().expect("an empty plan is one row and no columns, which is legal");
735 }
736
737 #[test]
738 fn interning_the_same_string_twice_gives_the_same_reference() {
739 let mut plan = Plan::new();
740 let first = plan.intern("hits");
741 let second = plan.intern("hits");
742 let other = plan.intern("visits");
743 assert_eq!(first, second);
744 assert_ne!(first, other);
745 assert_eq!(plan.string(first), "hits");
746 }
747
748 #[test]
749 fn a_constant_takes_its_type_from_its_value() {
750 let mut plan = Plan::new();
751 let one = plan.add_constant(Value::Integer(1));
752 assert_eq!(*plan.expr_type(one), LogicalType::Integer);
753 plan.validate().expect("a constant that agrees with itself is valid");
754 }
755
756 #[test]
759 fn a_typed_null_is_allowed_to_disagree_with_its_value() {
760 let mut plan = Plan::new();
761 let null = plan.add_value(Value::Null);
762 plan.add_expr(Expr::Constant(null), LogicalType::Varchar);
763 plan.validate().expect("a typed null is the point of carrying types separately");
764 }
765
766 #[test]
767 fn a_constant_that_disagrees_with_its_value_is_caught() {
768 let mut plan = Plan::new();
769 let value = plan.add_value(Value::Integer(1));
770 plan.add_expr(Expr::Constant(value), LogicalType::Varchar);
771 let message = plan.validate().unwrap_err().to_string();
772 assert!(message.contains("disagrees"), "unhelpful message: {message}");
773 }
774
775 #[test]
776 fn a_filter_on_something_that_is_not_boolean_is_caught() {
777 let mut plan = Plan::new();
778 let one = plan.add_constant(Value::Integer(1));
779 let filter = plan.add_node(Node::Filter { input: 0, predicate: one });
780 plan.set_root(filter);
781 let message = plan.validate().unwrap_err().to_string();
782 assert!(message.contains("BOOLEAN"), "unhelpful message: {message}");
783 }
784
785 #[test]
786 fn a_projection_with_more_expressions_than_names_is_caught() {
787 let mut plan = Plan::new();
788 let one = plan.add_constant(Value::Integer(1));
789 let two = plan.add_constant(Value::Integer(2));
790 let exprs = plan.add_expr_list(&[one, two]);
791 let name = plan.intern("a");
792 let names = plan.add_name_list(&[name]);
793 let project = plan.add_node(Node::Project { input: 0, index: 1, exprs, names });
794 plan.set_root(project);
795 let message = plan.validate().unwrap_err().to_string();
796 assert!(message.contains("names and expressions"), "unhelpful message: {message}");
797 }
798
799 #[test]
800 fn a_ragged_values_is_caught() {
801 let mut plan = Plan::new();
802 let one = plan.add_constant(Value::Integer(1));
803 let two = plan.add_constant(Value::Integer(2));
804 let wide = plan.add_expr_list(&[one, two]);
805 let narrow = plan.add_expr_list(&[one]);
806 let rows = plan.add_rows(&[wide, narrow]);
807 let columns = plan.add_fields(&[
808 Field::new("a", LogicalType::Integer),
809 Field::new("b", LogicalType::Integer),
810 ]);
811 let values = plan.add_node(Node::Values { index: 0, columns, rows });
812 plan.set_root(values);
813 let message = plan.validate().unwrap_err().to_string();
814 assert!(message.contains("number of columns"), "unhelpful message: {message}");
815 }
816
817 #[test]
818 fn an_aggregate_outside_an_aggregate_list_is_caught() {
819 let mut plan = Plan::new();
820 let name = plan.intern("count_star");
821 let count = plan.add_expr(
822 Expr::Aggregate { name, args: Slice::EMPTY, distinct: false, filter: None },
823 LogicalType::BigInt,
824 );
825 let zero = plan.add_constant(Value::BigInt(0));
826 let compare = plan.add_expr(
827 Expr::Compare { op: CompareOp::Greater, left: count, right: zero },
828 LogicalType::Boolean,
829 );
830 let filter = plan.add_node(Node::Filter { input: 0, predicate: compare });
831 plan.set_root(filter);
832 let message = plan.validate().unwrap_err().to_string();
833 assert!(message.contains("aggregate outside"), "unhelpful message: {message}");
834 }
835
836 #[test]
837 fn an_aggregate_inside_an_aggregate_list_is_fine() {
838 let mut plan = Plan::new();
839 let name = plan.intern("count_star");
840 let count = plan.add_expr(
841 Expr::Aggregate { name, args: Slice::EMPTY, distinct: false, filter: None },
842 LogicalType::BigInt,
843 );
844 let aggregates = plan.add_expr_list(&[count]);
845 let aggregate =
846 plan.add_node(Node::Aggregate { input: 0, index: 1, groups: Slice::EMPTY, aggregates });
847 plan.set_root(aggregate);
848 plan.validate().expect("this is the one place an aggregate belongs");
849 }
850
851 #[test]
854 fn a_node_that_refers_to_itself_is_caught() {
855 let mut plan = Plan::new();
856 let filter = plan.add_node(Node::Filter { input: 0, predicate: 0 });
857 let one = plan.add_constant(Value::Boolean(true));
858 plan.nodes[filter as usize] = Node::Filter { input: filter, predicate: one };
859 plan.set_root(filter);
860 let message = plan.validate().unwrap_err().to_string();
861 assert!(message.contains("not behind it"), "unhelpful message: {message}");
862 }
863
864 #[test]
865 fn an_expression_that_refers_forwards_is_caught() {
866 let mut plan = Plan::new();
867 let left = plan.add_constant(Value::Integer(1));
868 let compare = plan.add_expr(
869 Expr::Compare { op: CompareOp::Equal, left, right: left },
870 LogicalType::Boolean,
871 );
872 plan.exprs[compare as usize] =
873 Expr::Compare { op: CompareOp::Equal, left, right: compare + 1 };
874 plan.add_constant(Value::Integer(2));
875 let message = plan.validate().unwrap_err().to_string();
876 assert!(message.contains("not behind it"), "unhelpful message: {message}");
877 }
878
879 #[test]
880 fn a_root_that_is_not_in_the_arena_is_caught() {
881 let mut plan = Plan::new();
882 plan.set_root(17);
883 let message = plan.validate().unwrap_err().to_string();
884 assert!(message.contains("rooted at node 17"), "unhelpful message: {message}");
885 }
886
887 #[test]
888 fn a_column_binding_is_two_numbers_and_nothing_else() {
889 let binding = ColumnBinding::new(3, 7);
890 assert_eq!(binding.table, 3);
891 assert_eq!(binding.column, 7);
892 assert_eq!(size_of::<ColumnBinding>(), 8);
893 }
894}