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