Skip to main content

linkrs_query/binder/
validation.rs

1//! Validation result types produced by the Binder.
2//!
3//! These types were originally defined in the `validator` crate but are now
4//! produced by the Binder during the binding process (binding = validation).
5
6use std::collections::HashMap;
7use std::sync::Arc;
8
9use linkrs_core::types::expr::contextual::ContextualExpression;
10use linkrs_core::types::semantic::AliasType;
11
12use linkrs_core::types::Span;
13
14/// Verified statement wrapper.
15///
16/// Contains both the original AST and the validation information collected
17/// during binding.
18#[derive(Debug, Clone)]
19pub struct ValidatedStatement {
20    pub ast: Arc<crate::parser::ast::stmt::Ast>,
21    pub validation_info: ValidationInfo,
22}
23
24impl ValidatedStatement {
25    pub fn new(ast: Arc<crate::parser::ast::stmt::Ast>, validation_info: ValidationInfo) -> Self {
26        Self {
27            ast,
28            validation_info,
29        }
30    }
31
32    pub fn stmt(&self) -> &crate::parser::ast::Stmt {
33        &self.ast.stmt
34    }
35
36    pub fn statement_type(&self) -> &'static str {
37        self.ast.stmt.kind()
38    }
39
40    pub fn alias_map(&self) -> &HashMap<String, AliasType> {
41        &self.validation_info.alias_map
42    }
43
44    pub fn expr_context(
45        &self,
46    ) -> &Arc<linkrs_core::types::expr::expression_context::ExpressionAnalysisContext> {
47        &self.ast.expr_context
48    }
49}
50
51/// Validation information collected during binding.
52#[derive(Debug, Clone, Default)]
53pub struct ValidationInfo {
54    pub alias_map: HashMap<String, AliasType>,
55    pub path_analysis: Vec<PathAnalysis>,
56    pub optimization_hints: Vec<OptimizationHint>,
57    pub variable_definitions: HashMap<String, Span>,
58    pub validated_clauses: Vec<ClauseKind>,
59    pub semantic_info: SemanticInfo,
60}
61
62impl ValidationInfo {
63    pub fn new() -> Self {
64        Self::default()
65    }
66
67    pub fn add_alias(&mut self, name: String, alias_type: AliasType) {
68        self.alias_map.insert(name, alias_type);
69    }
70
71    pub fn add_path_analysis(&mut self, analysis: PathAnalysis) {
72        self.path_analysis.push(analysis);
73    }
74
75    pub fn add_optimization_hint(&mut self, hint: OptimizationHint) {
76        self.optimization_hints.push(hint);
77    }
78
79    pub fn get_alias_type(&self, name: &str) -> Option<&AliasType> {
80        self.alias_map.get(name)
81    }
82
83    pub fn is_node_variable(&self, name: &str) -> bool {
84        matches!(
85            self.alias_map.get(name),
86            Some(AliasType::Node) | Some(AliasType::NodeList)
87        )
88    }
89
90    pub fn is_edge_variable(&self, name: &str) -> bool {
91        matches!(
92            self.alias_map.get(name),
93            Some(AliasType::Edge) | Some(AliasType::EdgeList)
94        )
95    }
96}
97
98#[derive(Debug, Clone)]
99pub struct PathAnalysis {
100    pub alias: Option<String>,
101    pub node_count: usize,
102    pub edge_count: usize,
103    pub has_direction: bool,
104    pub min_hops: Option<usize>,
105    pub max_hops: Option<usize>,
106    pub variables: Vec<String>,
107    pub labels: Vec<String>,
108    pub edge_types: Vec<String>,
109}
110
111impl PathAnalysis {
112    pub fn new() -> Self {
113        Self {
114            alias: None,
115            node_count: 0,
116            edge_count: 0,
117            has_direction: true,
118            min_hops: None,
119            max_hops: None,
120            variables: Vec::new(),
121            labels: Vec::new(),
122            edge_types: Vec::new(),
123        }
124    }
125}
126
127impl Default for PathAnalysis {
128    fn default() -> Self {
129        Self::new()
130    }
131}
132
133#[derive(Debug, Clone)]
134pub enum OptimizationHint {
135    UseIndexScan {
136        table: String,
137        column: String,
138        condition: ContextualExpression,
139    },
140    LimitResults {
141        reason: String,
142        suggested_limit: usize,
143    },
144    PreFilter {
145        condition: ContextualExpression,
146        selectivity: f64,
147    },
148    JoinOrder {
149        optimal_order: Vec<String>,
150        estimated_cost: f64,
151    },
152    PerformanceWarning {
153        message: String,
154        severity: HintSeverity,
155    },
156}
157
158#[derive(Debug, Clone, Copy, PartialEq, Eq)]
159pub enum HintSeverity {
160    Info,
161    Warning,
162    Critical,
163}
164
165#[derive(Debug, Clone, Copy, PartialEq, Eq)]
166pub enum ClauseKind {
167    Match,
168    Where,
169    Return,
170    OrderBy,
171    Limit,
172    Skip,
173    With,
174    Unwind,
175    Create,
176    Delete,
177    Set,
178    Remove,
179    Yield,
180    Go,
181    Over,
182    From,
183}
184
185#[derive(Debug, Clone, Default)]
186pub struct SemanticInfo {
187    pub referenced_tags: Vec<String>,
188    pub referenced_edges: Vec<String>,
189    pub referenced_properties: Vec<String>,
190    pub used_variables: Vec<String>,
191    pub defined_variables: Vec<String>,
192    pub aggregate_calls: Vec<AggregateCallInfo>,
193    pub output_fields: Vec<String>,
194    pub ordering_fields: Vec<String>,
195    pub pagination_offset: Option<usize>,
196    pub pagination_limit: Option<usize>,
197    pub query_type: Option<String>,
198    pub query_complexity: Option<usize>,
199    pub space_name: Option<String>,
200    pub referenced_schemas: Vec<String>,
201}
202
203#[derive(Debug, Clone)]
204pub struct AggregateCallInfo {
205    pub function_name: String,
206    pub arguments: Vec<ContextualExpression>,
207    pub distinct: bool,
208    pub alias: Option<String>,
209}
210
211/// Cypher clause types.
212#[derive(Debug, Clone, PartialEq, Eq, Copy, Hash)]
213pub enum CypherClauseKind {
214    Match,
215    Where,
216    Return,
217    With,
218    Unwind,
219    Yield,
220    OrderBy,
221    Pagination,
222}
223
224impl CypherClauseKind {
225    pub fn as_str(&self) -> &'static str {
226        match self {
227            Self::Match => "MATCH",
228            Self::Where => "WHERE",
229            Self::Return => "RETURN",
230            Self::With => "WITH",
231            Self::Unwind => "UNWIND",
232            Self::Yield => "YIELD",
233            Self::OrderBy => "ORDER BY",
234            Self::Pagination => "PAGINATION",
235        }
236    }
237}
238
239// ── Clause contexts (used by planners) ───────────────────────────────────────
240
241use linkrs_core::types::OrderDirection;
242use linkrs_core::YieldColumn;
243
244#[derive(Debug, Clone)]
245pub struct WhereClauseContext {
246    pub filter: Option<ContextualExpression>,
247    pub aliases_available: HashMap<String, AliasType>,
248    pub aliases_generated: HashMap<String, AliasType>,
249}
250
251#[derive(Debug, Clone)]
252pub struct OrderByClauseContext {
253    pub indexed_order_factors: Vec<(usize, OrderDirection)>,
254}
255
256#[derive(Debug, Clone)]
257pub struct PaginationContext {
258    pub skip: i64,
259    pub limit: i64,
260}
261
262#[derive(Debug, Clone)]
263pub struct YieldClauseContext {
264    pub yield_columns: Vec<YieldColumn>,
265    pub aliases_available: HashMap<String, AliasType>,
266    pub aliases_generated: HashMap<String, AliasType>,
267    pub distinct: bool,
268    pub has_agg: bool,
269    pub group_keys: Vec<ContextualExpression>,
270    pub group_items: Vec<ContextualExpression>,
271    pub need_gen_project: bool,
272    pub agg_output_column_names: Vec<String>,
273    pub proj_output_column_names: Vec<String>,
274    pub filter_condition: Option<ContextualExpression>,
275    pub skip: Option<usize>,
276    pub limit: Option<usize>,
277}
278
279#[derive(Debug, Clone)]
280pub struct WithClauseContext {
281    pub yield_clause: YieldClauseContext,
282    pub aliases_available: HashMap<String, AliasType>,
283    pub aliases_generated: HashMap<String, AliasType>,
284    pub where_clause: Option<WhereClauseContext>,
285    pub pagination: Option<PaginationContext>,
286    pub order_by: Option<OrderByClauseContext>,
287    pub distinct: bool,
288}