Skip to main content

uqa_sql/semantics/
privileges.rs

1//
2// Unified Query Algebra
3//
4// Copyright (c) 2023-2026 Cognica, Inc.
5//
6
7//! Semantic base-column privilege analysis for query sources.
8
9mod 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}