nodedb_sql/resolver/scope.rs
1// SPDX-License-Identifier: Apache-2.0
2
3//! The column namespace an expression converts against.
4
5use crate::error::Result;
6use crate::resolver::columns::TableScope;
7
8/// The relations an identifier in this expression can name.
9#[derive(Debug, Clone, Copy)]
10pub enum ColumnScope<'a> {
11 /// No relation is in scope, so no identifier is checkable here.
12 ///
13 /// Used where an expression has no FROM clause behind it: stored DEFAULT
14 /// expressions, partial-index predicates, and the constant folders. A
15 /// column reference reaching one of those fails in constant folding as
16 /// `SqlError::Unsupported`, which is the correct answer there — the value
17 /// is not row-dependent.
18 Unchecked,
19 /// Identifiers resolve against these relations. One that resolves against
20 /// no relation, output alias, or synthetic column raises
21 /// `SqlError::UnknownColumn`.
22 Relations(&'a TableScope),
23}
24
25impl ColumnScope<'_> {
26 /// Reject `column`, optionally qualified by `table_ref`, when it names
27 /// nothing in this scope.
28 pub fn check_column(&self, table_ref: Option<&str>, column: &str) -> Result<()> {
29 match self {
30 Self::Unchecked => Ok(()),
31 Self::Relations(scope) => scope.check_name(table_ref, column),
32 }
33 }
34
35 /// Check `column`, optionally qualified by `table_ref`, and return the
36 /// qualifier its converted reference carries.
37 ///
38 /// Outside a join's input clauses the qualifier is kept as written. In
39 /// them, an unqualified column gets its owning relation's qualifier, so
40 /// it reads the `qualifier.column` key of the merged row.
41 pub fn column_qualifier(
42 &self,
43 table_ref: Option<&str>,
44 column: &str,
45 ) -> Result<Option<String>> {
46 match self {
47 Self::Unchecked => Ok(table_ref.map(str::to_owned)),
48 Self::Relations(scope) => match table_ref {
49 None => scope.bare_column_qualifier(column),
50 Some(table) => {
51 scope.check_name(Some(table), column)?;
52 Ok(scope.qualified_column_qualifier(table))
53 }
54 },
55 }
56 }
57
58 /// Whether an expression here is evaluated once per row of some relation.
59 ///
60 /// `Unchecked` stands behind stored DEFAULTs, index predicates, and the
61 /// constant folders, none of which iterate rows.
62 pub fn is_row_scope(&self) -> bool {
63 match self {
64 Self::Unchecked => false,
65 Self::Relations(scope) => scope.is_row_scope(),
66 }
67 }
68
69 /// Whether a sequence accessor resolves here although rows are in scope.
70 ///
71 /// Only a SELECT-list scope over a relation says yes; the Control Plane
72 /// evaluates that item per output row. `Unchecked` never iterates rows,
73 /// so the question does not arise there.
74 pub fn allows_cp_functions(&self) -> bool {
75 match self {
76 Self::Unchecked => false,
77 Self::Relations(scope) => scope.allows_cp_functions(),
78 }
79 }
80}