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