1pub mod columns;
10mod enforcement;
11
12use crate::catalog::roles::RoleReference;
13use std::collections::{BTreeMap, BTreeSet};
14
15use crate::plan::{QueryBlockPlan, QueryPlan, RelationalPlan, SourcePlan};
16use crate::SQLError;
17use crate::ScalarExpr;
18
19pub mod context;
20use context::PrivilegeScope as CteScope;
21use enforcement::ensure_required_select;
22
23#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
24struct BaseColumn {
25 table: String,
26 column: String,
27}
28
29#[derive(Clone, Debug)]
30struct OutputColumn {
31 name: String,
32 sources: BTreeSet<BaseColumn>,
33}
34
35#[derive(Clone, Default)]
36struct SourceLineage {
37 output: Vec<OutputColumn>,
38 qualifiers: BTreeMap<String, Vec<OutputColumn>>,
39 system_qualifiers: BTreeMap<String, BTreeSet<String>>,
40 tables: BTreeMap<String, BTreeSet<String>>,
41}
42
43impl SourceLineage {
44 fn has_unqualified(&self, column: &str) -> bool {
45 self.output.iter().any(|output| output.name == column)
46 }
47
48 fn has_qualifier(&self, qualifier: &str) -> bool {
49 self.qualifiers.contains_key(qualifier) || self.system_qualifiers.contains_key(qualifier)
50 }
51
52 fn require_unqualified(&self, column: &str, required: &mut BTreeSet<BaseColumn>) {
53 for output in self.output.iter().filter(|output| output.name == column) {
54 required.extend(output.sources.iter().cloned());
55 }
56 }
57
58 fn require_qualified(
59 &self,
60 qualifier: &str,
61 column: &str,
62 required: &mut BTreeSet<BaseColumn>,
63 ) {
64 if let Some(columns) = self.qualifiers.get(qualifier) {
65 for output in columns.iter().filter(|output| output.name == column) {
66 required.extend(output.sources.iter().cloned());
67 }
68 }
69 }
70
71 fn require_all(&self, required: &mut BTreeSet<BaseColumn>) {
72 for output in &self.output {
73 required.extend(output.sources.iter().cloned());
74 }
75 }
76
77 fn require_qualified_all(&self, qualifier: &str, required: &mut BTreeSet<BaseColumn>) {
78 if let Some(columns) = self.qualifiers.get(qualifier) {
79 for output in columns {
80 required.extend(output.sources.iter().cloned());
81 }
82 }
83 }
84
85 fn require_unqualified_system(&self, column: &str, required: &mut BTreeSet<BaseColumn>) {
86 for table in self.system_qualifiers.values().flatten() {
87 required.insert(BaseColumn {
88 table: table.clone(),
89 column: column.to_string(),
90 });
91 }
92 }
93
94 fn require_qualified_system(
95 &self,
96 qualifier: &str,
97 column: &str,
98 required: &mut BTreeSet<BaseColumn>,
99 ) {
100 if let Some(tables) = self.system_qualifiers.get(qualifier) {
101 required.extend(tables.iter().cloned().map(|table| BaseColumn {
102 table,
103 column: column.to_string(),
104 }));
105 }
106 }
107
108 fn include_tables(&mut self, other: &Self) {
109 for (table, columns) in &other.tables {
110 self.tables
111 .entry(table.clone())
112 .or_default()
113 .extend(columns.iter().cloned());
114 }
115 }
116}
117
118fn opaque_lineage(columns: Vec<String>, qualifier: Option<String>) -> SourceLineage {
119 let output = columns
120 .into_iter()
121 .map(|name| OutputColumn {
122 name,
123 sources: BTreeSet::new(),
124 })
125 .collect::<Vec<_>>();
126 let qualifiers = qualifier.map_or_else(BTreeMap::new, |qualifier| {
127 BTreeMap::from([(qualifier, output.clone())])
128 });
129 SourceLineage {
130 output,
131 qualifiers,
132 system_qualifiers: BTreeMap::new(),
133 tables: BTreeMap::new(),
134 }
135}
136
137fn is_system_column(column: &str) -> bool {
138 matches!(
139 column,
140 "ctid" | "xmin" | "cmin" | "xmax" | "cmax" | "tableoid"
141 )
142}
143
144fn rename_output_columns(output: &mut [OutputColumn], aliases: &[String]) {
145 for (column, alias) in output.iter_mut().zip(aliases) {
146 column.name.clone_from(alias);
147 }
148}
149
150fn table_lineage(
151 name: &str,
152 qualifier: &str,
153 alias: Option<&str>,
154 column_aliases: &[String],
155 ctes: &CteScope<'_>,
156) -> Result<SourceLineage, SQLError> {
157 if ctes.is_visible_cte(name) {
158 let columns = ctes
159 .materialized_for_scan(name)
160 .or_else(|| {
161 ctes.deferred_reference(name)
162 .and_then(|cte| cte.body.query())
163 .and_then(crate::semantics::projection::query_plan_output_columns)
164 })
165 .unwrap_or_default();
166 let mut output = opaque_lineage(columns, Some(alias.unwrap_or(qualifier).to_string()));
167 rename_output_columns(&mut output.output, column_aliases);
168 for columns in output.qualifiers.values_mut() {
169 rename_output_columns(columns, column_aliases);
170 }
171 return Ok(output);
172 }
173 let catalog = ctes.catalog;
174 let resolution = ctes.resolution.clone();
175 let Some(relation) = catalog.relation(&resolution, name)? else {
176 return Ok(SourceLineage::default());
177 };
178 let canonical = relation.canonical;
179 let columns = relation.columns;
180 let system_columns = relation.kind.has_system_columns();
181 let visible_qualifier = alias.unwrap_or(qualifier).to_string();
182 let mut output = columns
183 .iter()
184 .map(|column| OutputColumn {
185 name: column.clone(),
186 sources: BTreeSet::from([BaseColumn {
187 table: canonical.clone(),
188 column: column.clone(),
189 }]),
190 })
191 .collect::<Vec<_>>();
192 rename_output_columns(&mut output, column_aliases);
193 Ok(SourceLineage {
194 output: output.clone(),
195 qualifiers: BTreeMap::from([(visible_qualifier, output)]),
196 system_qualifiers: if system_columns {
197 BTreeMap::from([(
198 alias.unwrap_or(qualifier).to_string(),
199 BTreeSet::from([canonical.clone()]),
200 )])
201 } else {
202 BTreeMap::new()
203 },
204 tables: BTreeMap::from([(canonical, columns.into_iter().collect())]),
205 })
206}
207
208fn join_lineage(
209 left: SourceLineage,
210 right: SourceLineage,
211 using: Option<&crate::ast::JoinUsing>,
212 natural: bool,
213 alias: Option<&str>,
214 column_aliases: &[String],
215) -> SourceLineage {
216 let using_columns = using.map_or_else(
217 || {
218 if natural {
219 left.output
220 .iter()
221 .filter(|left| right.output.iter().any(|right| right.name == left.name))
222 .map(|column| column.name.clone())
223 .collect::<Vec<_>>()
224 } else {
225 Vec::new()
226 }
227 },
228 |using| using.columns.clone(),
229 );
230 let using_set = using_columns.iter().collect::<BTreeSet<_>>();
231 let mut output = Vec::new();
232 for column in &using_columns {
233 let mut sources = BTreeSet::new();
234 for candidate in left
235 .output
236 .iter()
237 .chain(&right.output)
238 .filter(|candidate| candidate.name == *column)
239 {
240 sources.extend(candidate.sources.iter().cloned());
241 }
242 output.push(OutputColumn {
243 name: column.clone(),
244 sources,
245 });
246 }
247 output.extend(
248 left.output
249 .iter()
250 .chain(&right.output)
251 .filter(|column| !using_set.contains(&column.name))
252 .cloned(),
253 );
254 let mut tables = left.tables.clone();
255 for (table, columns) in &right.tables {
256 tables
257 .entry(table.clone())
258 .or_default()
259 .extend(columns.iter().cloned());
260 }
261 let (qualifiers, system_qualifiers) = if let Some(alias) = alias {
262 rename_output_columns(&mut output, column_aliases);
263 (
264 BTreeMap::from([(alias.to_string(), output.clone())]),
265 BTreeMap::new(),
266 )
267 } else {
268 let mut qualifiers = left.qualifiers;
269 qualifiers.extend(right.qualifiers);
270 let mut system_qualifiers = left.system_qualifiers;
271 system_qualifiers.extend(right.system_qualifiers);
272 (qualifiers, system_qualifiers)
273 };
274 SourceLineage {
275 output,
276 qualifiers,
277 system_qualifiers,
278 tables,
279 }
280}
281
282fn collect_expression_columns(
283 expression: &ScalarExpr,
284 scopes: &[SourceLineage],
285 direct_star: bool,
286 required: &mut BTreeSet<BaseColumn>,
287) -> BTreeSet<usize> {
288 if direct_star {
289 match expression {
290 ScalarExpr::Star => {
291 if let Some(scope) = scopes.iter().find(|scope| !scope.output.is_empty()) {
292 scope.require_all(required);
293 }
294 return BTreeSet::new();
295 }
296 ScalarExpr::QualifiedStar(qualifier) => {
297 if let Some(scope) = scopes.iter().find(|scope| scope.has_qualifier(qualifier)) {
298 scope.require_qualified_all(qualifier, required);
299 }
300 return BTreeSet::new();
301 }
302 _ => {}
303 }
304 }
305 let mut subquery_ids = BTreeSet::new();
306 expression.visit(&mut |node| match node {
307 ScalarExpr::Column(column) => {
308 if let Some(scope) = scopes.iter().find(|scope| scope.has_unqualified(column)) {
309 scope.require_unqualified(column, required);
310 } else if is_system_column(column) {
311 if let Some(scope) = scopes
312 .iter()
313 .find(|scope| !scope.system_qualifiers.is_empty())
314 {
315 scope.require_unqualified_system(column, required);
316 }
317 }
318 }
319 ScalarExpr::QualifiedColumn {
320 qualifier, column, ..
321 } => {
322 if let Some(scope) = scopes.iter().find(|scope| scope.has_qualifier(qualifier)) {
323 scope.require_qualified(qualifier, column, required);
324 if is_system_column(column) {
325 scope.require_qualified_system(qualifier, column, required);
326 }
327 }
328 }
329 ScalarExpr::QualifiedStar(qualifier) => {
330 if let Some(scope) = scopes.iter().find(|scope| scope.has_qualifier(qualifier)) {
331 scope.require_qualified_all(qualifier, required);
332 }
333 }
334 ScalarExpr::ScalarSubquery(subquery)
335 | ScalarExpr::Exists { subquery, .. }
336 | ScalarExpr::InSubquery { subquery, .. } => {
337 subquery_ids.insert(*subquery);
338 }
339 _ => {}
340 });
341 subquery_ids
342}
343
344fn collect_expression_and_subqueries(
345 expression: &ScalarExpr,
346 scopes: &[SourceLineage],
347 direct_star: bool,
348 subqueries: &[QueryPlan],
349 ctes: &CteScope<'_>,
350 universe: &mut SourceLineage,
351 required: &mut BTreeSet<BaseColumn>,
352) -> Result<(), SQLError> {
353 for subquery in collect_expression_columns(expression, scopes, direct_star, required) {
354 let plan = subqueries.get(subquery).ok_or_else(|| {
355 SQLError::Internal(format!(
356 "column privilege analysis cannot resolve scalar subquery slot {subquery}"
357 ))
358 })?;
359 analyze_query_plan(plan, ctes, scopes, universe, required)?;
360 }
361 Ok(())
362}
363
364fn values_lineage(
365 rows: &[Vec<ScalarExpr>],
366 alias: Option<&str>,
367 column_aliases: &[String],
368) -> SourceLineage {
369 let columns = if column_aliases.is_empty() {
370 (1..=rows.first().map_or(0, Vec::len))
371 .map(|index| format!("column{index}"))
372 .collect()
373 } else {
374 column_aliases.to_vec()
375 };
376 opaque_lineage(columns, alias.map(str::to_string))
377}
378
379fn function_lineage(
380 name: &str,
381 output_name: &str,
382 alias: Option<&str>,
383 column_aliases: &[String],
384) -> SourceLineage {
385 let columns = if column_aliases.is_empty() {
386 vec![if output_name.is_empty() {
387 name.rsplit('.').next().unwrap_or(name).to_string()
388 } else {
389 output_name.to_string()
390 }]
391 } else {
392 column_aliases.to_vec()
393 };
394 let qualifier = alias.map_or_else(
395 || name.rsplit('.').next().unwrap_or(name).to_string(),
396 str::to_string,
397 );
398 opaque_lineage(columns, Some(qualifier))
399}
400
401fn analyze_join_source_lineage(
402 source: &SourcePlan,
403 outer_scopes: &[SourceLineage],
404 subqueries: &[QueryPlan],
405 ctes: &CteScope<'_>,
406 universe: &mut SourceLineage,
407 required: &mut BTreeSet<BaseColumn>,
408) -> Result<SourceLineage, SQLError> {
409 let SourcePlan::Join {
410 left,
411 right,
412 on,
413 using,
414 natural,
415 alias,
416 column_aliases,
417 ..
418 } = source
419 else {
420 unreachable!("join lineage helper requires a join source")
421 };
422 let left = analyze_source_lineage(left, outer_scopes, subqueries, ctes, universe, required)?;
423 let mut right_scopes = vec![left.clone()];
424 right_scopes.extend_from_slice(outer_scopes);
425 let right = analyze_source_lineage(right, &right_scopes, subqueries, ctes, universe, required)?;
426 let inputs = join_lineage(left.clone(), right.clone(), None, false, None, &[]);
427 let mut condition_scopes = vec![inputs];
428 condition_scopes.extend_from_slice(outer_scopes);
429 if let Some(on) = on {
430 collect_expression_and_subqueries(
431 on,
432 &condition_scopes,
433 false,
434 subqueries,
435 ctes,
436 universe,
437 required,
438 )?;
439 }
440 if let Some(using) = using {
441 for column in &using.columns {
442 left.require_unqualified(column, required);
443 right.require_unqualified(column, required);
444 }
445 } else if *natural {
446 for column in left
447 .output
448 .iter()
449 .filter(|left| right.output.iter().any(|right| right.name == left.name))
450 {
451 left.require_unqualified(&column.name, required);
452 right.require_unqualified(&column.name, required);
453 }
454 }
455 Ok(join_lineage(
456 left,
457 right,
458 using.as_ref(),
459 *natural,
460 alias.as_deref(),
461 column_aliases,
462 ))
463}
464
465fn analyze_function_group_lineage(
466 source: &SourcePlan,
467 outer_scopes: &[SourceLineage],
468 subqueries: &[QueryPlan],
469 ctes: &CteScope<'_>,
470 universe: &mut SourceLineage,
471 required: &mut BTreeSet<BaseColumn>,
472) -> Result<SourceLineage, SQLError> {
473 let SourcePlan::FunctionGroup {
474 functions,
475 alias,
476 column_aliases,
477 ..
478 } = source
479 else {
480 unreachable!("function-group lineage helper requires a function group")
481 };
482 for expression in functions.iter().flat_map(|function| &function.args) {
483 collect_expression_and_subqueries(
484 expression,
485 outer_scopes,
486 false,
487 subqueries,
488 ctes,
489 universe,
490 required,
491 )?;
492 }
493 let columns = if column_aliases.is_empty() {
494 functions
495 .iter()
496 .flat_map(|function| {
497 if function.column_aliases.is_empty() {
498 vec![function.output_name.clone()]
499 } else {
500 function.column_aliases.clone()
501 }
502 })
503 .collect()
504 } else {
505 column_aliases.clone()
506 };
507 Ok(opaque_lineage(columns, alias.clone()))
508}
509
510fn analyze_source_lineage(
511 source: &SourcePlan,
512 outer_scopes: &[SourceLineage],
513 subqueries: &[QueryPlan],
514 ctes: &CteScope<'_>,
515 universe: &mut SourceLineage,
516 required: &mut BTreeSet<BaseColumn>,
517) -> Result<SourceLineage, SQLError> {
518 match source {
519 SourcePlan::Table {
520 name,
521 qualifier,
522 alias,
523 column_aliases,
524 ..
525 } => table_lineage(name, qualifier, alias.as_deref(), column_aliases, ctes),
526 source @ SourcePlan::Join { .. } => {
527 analyze_join_source_lineage(source, outer_scopes, subqueries, ctes, universe, required)
528 }
529 SourcePlan::Values {
530 rows,
531 alias,
532 column_aliases,
533 ..
534 } => {
535 for expression in rows.iter().flatten() {
536 collect_expression_and_subqueries(
537 expression,
538 outer_scopes,
539 false,
540 subqueries,
541 ctes,
542 universe,
543 required,
544 )?;
545 }
546 Ok(values_lineage(rows, alias.as_deref(), column_aliases))
547 }
548 SourcePlan::Function {
549 name,
550 output_name,
551 args,
552 alias,
553 column_aliases,
554 ..
555 } => {
556 for expression in args {
557 collect_expression_and_subqueries(
558 expression,
559 outer_scopes,
560 false,
561 subqueries,
562 ctes,
563 universe,
564 required,
565 )?;
566 }
567 Ok(function_lineage(
568 name,
569 output_name,
570 alias.as_deref(),
571 column_aliases,
572 ))
573 }
574 source @ SourcePlan::FunctionGroup { .. } => analyze_function_group_lineage(
575 source,
576 outer_scopes,
577 subqueries,
578 ctes,
579 universe,
580 required,
581 ),
582 SourcePlan::Subquery {
583 body,
584 alias,
585 column_aliases,
586 } => {
587 analyze_query_plan(body, ctes, outer_scopes, universe, required)?;
588 let mut columns =
589 crate::semantics::projection::query_plan_output_columns(body).unwrap_or_default();
590 for (column, alias) in columns.iter_mut().zip(column_aliases) {
591 column.clone_from(alias);
592 }
593 Ok(opaque_lineage(columns, alias.clone()))
594 }
595 }
596}
597
598fn analyze_query_block(
599 block: &QueryBlockPlan,
600 source: Option<&SourcePlan>,
601 ctes: &CteScope<'_>,
602 outer_scopes: &[SourceLineage],
603 universe: &mut SourceLineage,
604 required: &mut BTreeSet<BaseColumn>,
605) -> Result<(), SQLError> {
606 let lineage = source.map_or_else(
607 || Ok(SourceLineage::default()),
608 |source| {
609 analyze_source_lineage(
610 source,
611 outer_scopes,
612 &block.subqueries,
613 ctes,
614 universe,
615 required,
616 )
617 },
618 )?;
619 universe.include_tables(&lineage);
620 let mut scopes = vec![lineage];
621 scopes.extend_from_slice(outer_scopes);
622 for column in block
623 .privilege_columns
624 .iter()
625 .filter(|_| ctes.include_authorization_only_columns)
626 {
627 let expression = column.qualifier().map_or_else(
628 || ScalarExpr::Column(column.column().to_string()),
629 |qualifier| ScalarExpr::qualified_column(qualifier, column.column()),
630 );
631 collect_expression_columns(&expression, &scopes, false, required);
632 }
633 for projection in &block.projections {
634 collect_expression_and_subqueries(
635 &projection.expr,
636 &scopes,
637 true,
638 &block.subqueries,
639 ctes,
640 universe,
641 required,
642 )?;
643 }
644 for expression in block
645 .r#where
646 .iter()
647 .chain(block.group_by.iter())
648 .chain(block.grouping_sets.iter().flatten())
649 .chain(block.having.iter())
650 .chain(block.order_by.iter().map(|order| &order.expr))
651 .chain(block.distinct_on.iter())
652 .chain(block.limit.iter())
653 .chain(block.offset.iter())
654 {
655 collect_expression_and_subqueries(
656 expression,
657 &scopes,
658 false,
659 &block.subqueries,
660 ctes,
661 universe,
662 required,
663 )?;
664 }
665 Ok(())
666}
667
668fn analyze_query_plan(
669 plan: &QueryPlan,
670 ctes: &CteScope<'_>,
671 outer_scopes: &[SourceLineage],
672 universe: &mut SourceLineage,
673 required: &mut BTreeSet<BaseColumn>,
674) -> Result<(), SQLError> {
675 let reachable = crate::semantics::reachable_plan_cte_names(plan);
676 let mut preceding_ctes = ctes.clone();
677 for cte in &plan.ctes {
678 if reachable.contains(&cte.name) {
679 let mut definition_ctes = preceding_ctes.clone();
680 if cte.recursive {
681 for local in &plan.ctes {
682 definition_ctes.insert_deferred(local.clone());
683 }
684 }
685 if let Some(query) = cte.body.query() {
686 analyze_query_plan(query, &definition_ctes, outer_scopes, universe, required)?;
687 }
688 }
689 preceding_ctes.insert_deferred(cte.clone());
690 }
691 let mut root_ctes = ctes.clone();
692 for cte in &plan.ctes {
693 root_ctes.insert_deferred(cte.clone());
694 }
695 match &plan.root {
696 RelationalPlan::QueryBlock(block) => analyze_query_block(
697 block,
698 block.from.as_ref(),
699 &root_ctes,
700 outer_scopes,
701 universe,
702 required,
703 ),
704 RelationalPlan::SetOp {
705 left,
706 right,
707 order_by,
708 limit,
709 offset,
710 subqueries,
711 ..
712 } => {
713 analyze_query_plan(left, &root_ctes, outer_scopes, universe, required)?;
714 analyze_query_plan(right, &root_ctes, outer_scopes, universe, required)?;
715 let result_scope = opaque_lineage(
716 crate::semantics::projection::query_plan_output_columns(left).unwrap_or_default(),
717 None,
718 );
719 let mut scopes = vec![result_scope];
720 scopes.extend_from_slice(outer_scopes);
721 for expression in order_by
722 .iter()
723 .map(|order| &order.expr)
724 .chain(limit.iter().map(Box::as_ref))
725 .chain(offset.iter().map(Box::as_ref))
726 {
727 collect_expression_and_subqueries(
728 expression, &scopes, false, subqueries, &root_ctes, universe, required,
729 )?;
730 }
731 Ok(())
732 }
733 RelationalPlan::Values { rows, subqueries } => {
734 for expression in rows.iter().flatten() {
735 collect_expression_and_subqueries(
736 expression,
737 outer_scopes,
738 false,
739 subqueries,
740 &root_ctes,
741 universe,
742 required,
743 )?;
744 }
745 Ok(())
746 }
747 }
748}
749
750pub fn ensure_select_privileges_for_query_block(
751 statement: &QueryBlockPlan,
752 source: &SourcePlan,
753 ctes: &CteScope<'_>,
754) -> Result<(), SQLError> {
755 let mut universe = SourceLineage::default();
756 let mut required = BTreeSet::new();
757 analyze_query_block(
758 statement,
759 Some(source),
760 ctes,
761 &[],
762 &mut universe,
763 &mut required,
764 )?;
765 ensure_required_select(&universe, &required, ctes)
766}
767
768pub fn ensure_select_privileges_for_source_expressions(
769 source: &SourcePlan,
770 expressions: &[&ScalarExpr],
771 ctes: &CteScope<'_>,
772) -> Result<(), SQLError> {
773 let (universe, required) = source_expression_dependencies(source, expressions, ctes)?;
774 ensure_required_select(&universe, &required, ctes)
775}
776
777fn source_expression_dependencies(
778 source: &SourcePlan,
779 expressions: &[&ScalarExpr],
780 ctes: &CteScope<'_>,
781) -> Result<(SourceLineage, BTreeSet<BaseColumn>), SQLError> {
782 let mut universe = SourceLineage::default();
783 let mut required = BTreeSet::new();
784 let lineage = analyze_source_lineage(
785 source,
786 &[],
787 &ctes.scalar_subqueries,
788 ctes,
789 &mut universe,
790 &mut required,
791 )?;
792 universe.include_tables(&lineage);
793 let scopes = [lineage.clone()];
794 for expression in expressions {
795 collect_expression_and_subqueries(
796 expression,
797 &scopes,
798 matches!(expression, ScalarExpr::QualifiedStar(_)),
799 &ctes.scalar_subqueries,
800 ctes,
801 &mut universe,
802 &mut required,
803 )?;
804 }
805 Ok((universe, required))
806}
807
808pub fn ensure_select_privileges_for_table_expressions(
809 table: &str,
810 qualifiers: &BTreeSet<String>,
811 expressions: &[&ScalarExpr],
812 subqueries: &[QueryPlan],
813 required_columns: &[String],
814 ctes: &CteScope<'_>,
815) -> Result<(), SQLError> {
816 let (universe, required) = table_expression_dependencies(
817 table,
818 qualifiers,
819 expressions,
820 subqueries,
821 required_columns,
822 ctes,
823 )?;
824 ensure_required_select(&universe, &required, ctes)
825}
826
827fn table_expression_dependencies(
828 table: &str,
829 qualifiers: &BTreeSet<String>,
830 expressions: &[&ScalarExpr],
831 subqueries: &[QueryPlan],
832 required_columns: &[String],
833 ctes: &CteScope<'_>,
834) -> Result<(SourceLineage, BTreeSet<BaseColumn>), SQLError> {
835 let catalog = ctes.catalog;
836 let mut resolution = ctes.resolution.clone();
837 resolution.set_lookup_mode(crate::catalog::resolution::RelationLookupMode::Bound);
838 let Some(relation) = catalog.relation(&resolution, table)? else {
839 return Err(SQLError::UnknownTable(table.to_string()));
840 };
841 let canonical = relation.canonical;
842 let columns = relation.columns;
843 let system_columns = relation.kind.has_system_columns();
844 let output = columns
845 .iter()
846 .map(|column| OutputColumn {
847 name: column.clone(),
848 sources: BTreeSet::from([BaseColumn {
849 table: canonical.clone(),
850 column: column.clone(),
851 }]),
852 })
853 .collect::<Vec<_>>();
854 let lineage = SourceLineage {
855 output: output.clone(),
856 qualifiers: qualifiers
857 .iter()
858 .map(|qualifier| (qualifier.clone(), output.clone()))
859 .collect(),
860 system_qualifiers: if system_columns {
861 qualifiers
862 .iter()
863 .map(|qualifier| (qualifier.clone(), BTreeSet::from([canonical.clone()])))
864 .collect()
865 } else {
866 BTreeMap::new()
867 },
868 tables: BTreeMap::from([(canonical.clone(), columns.into_iter().collect())]),
869 };
870 let mut universe = SourceLineage::default();
871 let mut required = required_columns
872 .iter()
873 .cloned()
874 .map(|column| BaseColumn {
875 table: canonical.clone(),
876 column,
877 })
878 .collect::<BTreeSet<_>>();
879 let scopes = [lineage.clone()];
880 for expression in expressions {
881 collect_expression_and_subqueries(
882 expression,
883 &scopes,
884 true,
885 subqueries,
886 ctes,
887 &mut universe,
888 &mut required,
889 )?;
890 }
891 if required.iter().any(|column| column.table == canonical) {
892 universe.include_tables(&lineage);
893 }
894 Ok((universe, required))
895}
896
897pub struct TargetSelectPrivilegeRequest<'a, 'expr> {
898 pub table: &'a str,
899 pub privilege_subject: Option<&'a RoleReference>,
900 pub target_qualifier: &'a str,
901 pub returning_aliases: &'a crate::ast::ReturningAliases,
902 pub expressions: &'a [&'expr ScalarExpr],
903 pub subqueries: &'a [QueryPlan],
904 pub required_columns: &'a [String],
905}