1use super::{PortalBindingContext, SessionPortalTableDependencies};
10use crate::{
11 plan::{QueryPlan, RelationalPlan, SourcePlan},
12 registry::FunctionKind,
13 SQLError,
14};
15
16fn collect_graph_function_dependency(
17 name: &str,
18 binding: Option<&crate::ast::FunctionBinding>,
19 args: &[crate::ScalarExpr],
20 dependencies: &mut SessionPortalTableDependencies,
21) {
22 if binding.is_some_and(|binding| !binding.builtin) {
23 return;
24 }
25 let name = crate::semantics::builtin_function_dispatch_name(name);
26 let argument = match crate::registry::lookup(&name) {
27 Some(
28 FunctionKind::GraphPagerank
29 | FunctionKind::GraphHits
30 | FunctionKind::GraphBetweenness
31 | FunctionKind::GraphTraverse
32 | FunctionKind::GraphNeighbors
33 | FunctionKind::TraverseMatch
34 | FunctionKind::TemporalTraverse
35 | FunctionKind::GraphEdges,
36 ) => Some(0),
37 Some(FunctionKind::RPQ) => Some(2),
38 Some(FunctionKind::DeepPredict | FunctionKind::DeepLearn) => {
39 dependencies.graphs = None;
41 dependencies.graph_catalog = true;
42 return;
43 }
44 Some(
45 FunctionKind::GraphCreate
46 | FunctionKind::GraphDrop
47 | FunctionKind::GraphExists
48 | FunctionKind::GraphLabelCreate
49 | FunctionKind::GraphLabelDrop
50 | FunctionKind::GraphAlter,
51 ) => {
52 dependencies.graph_catalog = true;
53 return;
54 }
55 _ if name == "cypher" => Some(0),
56 _ if name == "graph_join" => {
57 dependencies.graphs = None;
58 dependencies.graph_catalog = true;
59 return;
60 }
61 _ => None,
62 };
63 let Some(argument) = argument else {
64 return;
65 };
66 dependencies.graph_catalog = true;
67 if let Some(crate::ScalarExpr::Literal(uqa_core::Value::Str(graph))) = args.get(argument) {
68 dependencies.insert_graph(graph.clone());
69 } else {
70 dependencies.graphs = None;
73 }
74}
75
76pub fn session_portal_table_dependencies(
77 inputs: &PortalBindingContext<'_>,
78 query: &QueryPlan,
79) -> Result<SessionPortalTableDependencies, SQLError> {
80 let mut dependencies = SessionPortalTableDependencies::empty();
81 collect_session_portal_query_dependencies(
82 inputs,
83 query,
84 &mut dependencies,
85 &mut std::collections::BTreeSet::new(),
86 &mut std::collections::BTreeSet::new(),
87 )?;
88 Ok(dependencies)
89}
90
91pub fn collect_session_portal_query_dependencies(
92 inputs: &PortalBindingContext<'_>,
93 query: &QueryPlan,
94 dependencies: &mut SessionPortalTableDependencies,
95 visiting_views: &mut std::collections::BTreeSet<String>,
96 visiting_routines: &mut std::collections::BTreeSet<String>,
97) -> Result<(), SQLError> {
98 if dependencies.is_all() {
99 return Ok(());
100 }
101 for cte in &query.ctes {
102 collect_session_portal_cte_dependencies(
103 inputs,
104 &cte.body,
105 dependencies,
106 visiting_views,
107 visiting_routines,
108 )?;
109 }
110 collect_session_portal_relational_dependencies(
111 inputs,
112 &query.root,
113 dependencies,
114 visiting_views,
115 visiting_routines,
116 )?;
117
118 let mut plan = crate::plan::UnifiedPlan::Query(Box::new(query.clone()));
119 let mut routines = Vec::new();
120 plan.rewrite_scalar_expressions(&mut |expression| {
121 if let crate::ScalarExpr::Func {
122 name,
123 binding,
124 args,
125 ..
126 } = expression
127 {
128 collect_graph_function_dependency(name, binding.as_ref(), args, dependencies);
129 routines.push((name.clone(), binding.clone()));
130 }
131 });
132 for (name, binding) in routines {
133 collect_session_portal_routine_dependencies(
134 inputs,
135 &name,
136 binding.as_ref(),
137 dependencies,
138 visiting_views,
139 visiting_routines,
140 )?;
141 if dependencies.is_all() {
142 break;
143 }
144 }
145 Ok(())
146}
147
148fn collect_session_portal_cte_dependencies(
149 inputs: &PortalBindingContext<'_>,
150 body: &crate::plan::CtePlanBody,
151 dependencies: &mut SessionPortalTableDependencies,
152 visiting_views: &mut std::collections::BTreeSet<String>,
153 visiting_routines: &mut std::collections::BTreeSet<String>,
154) -> Result<(), SQLError> {
155 match body {
156 crate::plan::CtePlanBody::Query(query) => collect_session_portal_query_dependencies(
157 inputs,
158 query,
159 dependencies,
160 visiting_views,
161 visiting_routines,
162 ),
163 crate::plan::CtePlanBody::Command(command) => {
164 if let Some(target) = command.mutation_target() {
165 collect_session_portal_relation_dependencies(
166 inputs,
167 target,
168 true,
169 dependencies,
170 visiting_views,
171 visiting_routines,
172 )?;
173 }
174 for cte in command.ctes() {
175 collect_session_portal_cte_dependencies(
176 inputs,
177 &cte.body,
178 dependencies,
179 visiting_views,
180 visiting_routines,
181 )?;
182 }
183 for query in command.query_inputs() {
184 collect_session_portal_query_dependencies(
185 inputs,
186 query,
187 dependencies,
188 visiting_views,
189 visiting_routines,
190 )?;
191 }
192 if let Some(source) = command.source_input() {
193 collect_session_portal_source_dependencies(
194 inputs,
195 source,
196 dependencies,
197 visiting_views,
198 visiting_routines,
199 )?;
200 }
201 Ok(())
202 }
203 }
204}
205
206pub fn collect_session_portal_relational_dependencies(
207 inputs: &PortalBindingContext<'_>,
208 plan: &RelationalPlan,
209 dependencies: &mut SessionPortalTableDependencies,
210 visiting_views: &mut std::collections::BTreeSet<String>,
211 visiting_routines: &mut std::collections::BTreeSet<String>,
212) -> Result<(), SQLError> {
213 match plan {
214 RelationalPlan::QueryBlock(block) => {
215 if let Some(source) = block.from.as_ref() {
216 collect_session_portal_source_dependencies(
217 inputs,
218 source,
219 dependencies,
220 visiting_views,
221 visiting_routines,
222 )?;
223 }
224 for subquery in &block.subqueries {
225 collect_session_portal_query_dependencies(
226 inputs,
227 subquery,
228 dependencies,
229 visiting_views,
230 visiting_routines,
231 )?;
232 }
233 }
234 RelationalPlan::SetOp {
235 left,
236 right,
237 subqueries,
238 ..
239 } => {
240 collect_session_portal_query_dependencies(
241 inputs,
242 left,
243 dependencies,
244 visiting_views,
245 visiting_routines,
246 )?;
247 collect_session_portal_query_dependencies(
248 inputs,
249 right,
250 dependencies,
251 visiting_views,
252 visiting_routines,
253 )?;
254 for subquery in subqueries {
255 collect_session_portal_query_dependencies(
256 inputs,
257 subquery,
258 dependencies,
259 visiting_views,
260 visiting_routines,
261 )?;
262 }
263 }
264 RelationalPlan::Values { subqueries, .. } => {
265 for subquery in subqueries {
266 collect_session_portal_query_dependencies(
267 inputs,
268 subquery,
269 dependencies,
270 visiting_views,
271 visiting_routines,
272 )?;
273 }
274 }
275 }
276 Ok(())
277}
278
279fn collect_session_portal_relation_dependencies(
280 inputs: &PortalBindingContext<'_>,
281 name: &str,
282 include_descendants: bool,
283 dependencies: &mut SessionPortalTableDependencies,
284 visiting_views: &mut std::collections::BTreeSet<String>,
285 visiting_routines: &mut std::collections::BTreeSet<String>,
286) -> Result<(), SQLError> {
287 if let Some(table) = inputs
288 .catalog
289 .try_resolve_table_name(name)
290 .map_err(|error| {
291 SQLError::Internal(format!(
292 "resolve cursor dependency relation `{name}`: {error}"
293 ))
294 })?
295 {
296 for table in inputs
297 .catalog
298 .hierarchy_scan_tables(&table, include_descendants)?
299 {
300 dependencies.insert(
301 uqa_core::RelationIdentity::from_legacy_name(&table).map_err(|error| {
302 SQLError::Internal(format!(
303 "resolve cursor dependency identity `{table}`: {error}"
304 ))
305 })?,
306 );
307 }
308 return Ok(());
309 }
310 if crate::binding::view_dependencies::canonical_virtual_relation_reference(name).is_some() {
311 dependencies.tables = None;
312 dependencies.graph_catalog = true;
313 return Ok(());
314 }
315 if let Some(relation) = inputs.catalog.resolve_age_label_relation_name(name)? {
316 let relation =
317 uqa_core::RelationIdentity::from_legacy_name(&relation).map_err(SQLError::Internal)?;
318 dependencies.insert_graph(relation.schema);
319 return Ok(());
320 }
321 let key = name.to_ascii_lowercase();
322 if !visiting_views.insert(key.clone()) {
323 return Ok(());
324 }
325 if let Some(view) = inputs.catalog.view_plan(name)? {
326 collect_session_portal_query_dependencies(
327 inputs,
328 &view,
329 dependencies,
330 visiting_views,
331 visiting_routines,
332 )?;
333 }
334 visiting_views.remove(&key);
335 Ok(())
336}
337
338pub fn collect_session_portal_source_dependencies(
339 inputs: &PortalBindingContext<'_>,
340 source: &SourcePlan,
341 dependencies: &mut SessionPortalTableDependencies,
342 visiting_views: &mut std::collections::BTreeSet<String>,
343 visiting_routines: &mut std::collections::BTreeSet<String>,
344) -> Result<(), SQLError> {
345 match source {
346 SourcePlan::Table {
347 name,
348 include_descendants,
349 ..
350 } => collect_session_portal_relation_dependencies(
351 inputs,
352 name,
353 *include_descendants,
354 dependencies,
355 visiting_views,
356 visiting_routines,
357 ),
358 SourcePlan::Join { left, right, .. } => {
359 collect_session_portal_source_dependencies(
360 inputs,
361 left,
362 dependencies,
363 visiting_views,
364 visiting_routines,
365 )?;
366 collect_session_portal_source_dependencies(
367 inputs,
368 right,
369 dependencies,
370 visiting_views,
371 visiting_routines,
372 )
373 }
374 SourcePlan::Subquery { body, .. } => collect_session_portal_query_dependencies(
375 inputs,
376 body,
377 dependencies,
378 visiting_views,
379 visiting_routines,
380 ),
381 SourcePlan::Function {
382 name,
383 binding,
384 relations,
385 args,
386 ..
387 } => {
388 collect_graph_function_dependency(name, binding.as_ref(), args, dependencies);
389 collect_session_portal_function_dependencies(
390 inputs,
391 name,
392 binding.as_ref(),
393 relations.as_ref(),
394 dependencies,
395 visiting_views,
396 visiting_routines,
397 )
398 }
399 SourcePlan::FunctionGroup { functions, .. } => {
400 for function in functions {
401 collect_graph_function_dependency(
402 &function.name,
403 function.binding.as_ref(),
404 &function.args,
405 dependencies,
406 );
407 collect_session_portal_function_dependencies(
408 inputs,
409 &function.name,
410 function.binding.as_ref(),
411 function.relations.as_ref(),
412 dependencies,
413 visiting_views,
414 visiting_routines,
415 )?;
416 }
417 Ok(())
418 }
419 SourcePlan::Values { .. } => Ok(()),
420 }
421}
422
423pub fn collect_session_portal_function_dependencies(
424 inputs: &PortalBindingContext<'_>,
425 name: &str,
426 binding: Option<&crate::ast::FunctionBinding>,
427 relations: Option<&crate::ast::OperatorJoinRelations>,
428 dependencies: &mut SessionPortalTableDependencies,
429 visiting_views: &mut std::collections::BTreeSet<String>,
430 visiting_routines: &mut std::collections::BTreeSet<String>,
431) -> Result<(), SQLError> {
432 if let Some(relations) = relations {
433 for relation in [&relations.left, &relations.right] {
434 collect_session_portal_function_relation_dependency(inputs, relation, dependencies)?;
435 }
436 }
437 collect_session_portal_routine_dependencies(
438 inputs,
439 name,
440 binding,
441 dependencies,
442 visiting_views,
443 visiting_routines,
444 )
445}
446
447pub fn collect_session_portal_function_relation_dependency(
448 inputs: &PortalBindingContext<'_>,
449 name: &str,
450 dependencies: &mut SessionPortalTableDependencies,
451) -> Result<(), SQLError> {
452 let Some(table) = inputs
453 .catalog
454 .try_resolve_table_name(name)
455 .map_err(|error| {
456 SQLError::Internal(format!(
457 "resolve cursor table-function relation `{name}`: {error}"
458 ))
459 })?
460 else {
461 return Ok(());
462 };
463 dependencies.insert(
464 uqa_core::RelationIdentity::from_legacy_name(&table).map_err(|error| {
465 SQLError::Internal(format!(
466 "resolve cursor table-function relation identity `{table}`: {error}"
467 ))
468 })?,
469 );
470 Ok(())
471}
472
473pub fn collect_session_portal_routine_dependencies(
474 inputs: &PortalBindingContext<'_>,
475 name: &str,
476 binding: Option<&crate::ast::FunctionBinding>,
477 dependencies: &mut SessionPortalTableDependencies,
478 visiting_views: &mut std::collections::BTreeSet<String>,
479 visiting_routines: &mut std::collections::BTreeSet<String>,
480) -> Result<(), SQLError> {
481 if binding.is_some_and(|binding| binding.builtin) {
482 return Ok(());
483 }
484 let overloads = match binding {
485 Some(binding) => inputs
486 .routines
487 .lookup_bound_sql_functions_by_binding(binding),
488 None => inputs
489 .routines
490 .lookup_visible_sql_functions_for_analysis(name)?,
491 };
492 let Some(overloads) = overloads else {
493 return Ok(());
494 };
495 for function in overloads {
496 if function.def.is_procedure
497 || binding.is_some_and(|binding| {
498 crate::routines::routine_signature_types(&function.def) != binding.argument_types
499 })
500 {
501 continue;
502 }
503 let signature = crate::routines::routine_signature_types(&function.def).join(",");
504 let key = format!("{}({signature})", function.def.name);
505 if !visiting_routines.insert(key.clone()) {
506 continue;
507 }
508 match &function.compiled {
509 crate::routines::CompiledFunctionBody::SQL(plans) => {
510 for plan in plans {
511 match plan {
512 crate::plan::UnifiedPlan::Query(query) => {
513 collect_session_portal_query_dependencies(
514 inputs,
515 query,
516 dependencies,
517 visiting_views,
518 visiting_routines,
519 )?;
520 }
521 crate::plan::UnifiedPlan::Command(_) => {
522 *dependencies = SessionPortalTableDependencies::all();
523 }
524 }
525 if dependencies.is_all() {
526 break;
527 }
528 }
529 }
530 crate::routines::CompiledFunctionBody::PLpgSQL(_) => {
531 *dependencies = SessionPortalTableDependencies::all();
532 }
533 }
534 visiting_routines.remove(&key);
535 if dependencies.is_all() {
536 break;
537 }
538 }
539 Ok(())
540}