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