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