Skip to main content

pg_query/
summary_result.rs

1use std::collections::HashMap;
2use std::collections::HashSet;
3use std::iter::FromIterator;
4use std::string::String;
5
6use crate::protobuf::summary_result::Context;
7use crate::*;
8
9/// Result from calling [summary].
10/// Where possible, this is API-compatible with [ParseResult].
11///
12/// The main distinction is that `summary` does truncation on the C side,
13/// whereas `parse` does it on the Rust side. This requires passing the
14/// maximum length ahead of time to `summary(query, max_length)`.
15///
16/// This means that `summary(query, max_length).truncated_query` is equivalent
17/// to `parse(query).truncate(max_length)`
18///
19/// For `tables`, `functions`, and `filter_columns`, `SummaryResult` stores
20/// more details than `ParseResult`, so the signatures have changed.
21/// However, the _functions_ that correspond to them should be equivalent.
22#[derive(Debug, PartialEq)]
23pub struct SummaryResult {
24    pub protobuf: protobuf::SummaryResult,
25    pub warnings: Vec<String>,
26    pub tables: Vec<Table>,
27    pub aliases: HashMap<String, String>,
28    pub cte_names: Vec<String>,
29    pub functions: Vec<Function>,
30    pub filter_columns: Vec<FilterColumn>,
31    pub truncated_query: String,
32    pub statement_types: Vec<String>,
33}
34
35impl SummaryResult {
36    pub fn new(protobuf: protobuf::SummaryResult, stderr: String) -> Self {
37        let warnings = stderr
38            .lines()
39            .filter_map(|l| {
40                if l.starts_with("WARNING") {
41                    Some(l.trim().into())
42                } else {
43                    None
44                }
45            })
46            .collect();
47        let mut tables: HashSet<Table> = HashSet::new();
48        let aliases = protobuf.aliases.clone();
49        let cte_names: HashSet<String> = HashSet::from_iter(protobuf.cte_names.to_owned());
50        let mut functions: HashSet<Function> = HashSet::new();
51        let mut filter_columns: HashSet<FilterColumn> = HashSet::new();
52        let truncated_query = protobuf.truncated_query.to_owned();
53        let statement_types = protobuf.statement_types.clone();
54
55        for table in &protobuf.tables {
56            tables.insert(Table::from(table));
57        }
58
59        for function in &protobuf.functions {
60            functions.insert(Function::from(function));
61        }
62
63        for filter_column in &protobuf.filter_columns {
64            filter_columns.insert(FilterColumn::from(filter_column));
65        }
66
67        Self {
68            protobuf,
69            warnings,
70            tables: Vec::from_iter(tables),
71            aliases,
72            cte_names: Vec::from_iter(cte_names),
73            functions: Vec::from_iter(functions),
74            filter_columns: Vec::from_iter(filter_columns),
75            truncated_query,
76            statement_types,
77        }
78    }
79
80    /// Returns all referenced tables in the query
81    pub fn tables(&self) -> Vec<String> {
82        let mut tables = HashSet::new();
83        self.tables.iter().for_each(|table| {
84            tables.insert(table.name.clone());
85        });
86        Vec::from_iter(tables)
87    }
88
89    /// Returns only tables that were selected from
90    pub fn select_tables(&self) -> Vec<String> {
91        self.tables
92            .iter()
93            .filter_map(|table| match &table.context {
94                Context::Select => Some(table.name.to_string()),
95                _ => None,
96            })
97            .collect()
98    }
99
100    /// Returns only tables that were modified by the query
101    pub fn dml_tables(&self) -> Vec<String> {
102        self.tables
103            .iter()
104            .filter_map(|table| match &table.context {
105                Context::Dml => Some(table.name.to_string()),
106                _ => None,
107            })
108            .collect()
109    }
110
111    /// Returns only tables that were modified by DDL statements
112    pub fn ddl_tables(&self) -> Vec<String> {
113        self.tables
114            .iter()
115            .filter_map(|table| match &table.context {
116                Context::Ddl => Some(table.name.to_string()),
117                _ => None,
118            })
119            .collect()
120    }
121
122    /// Returns all function references
123    pub fn functions(&self) -> Vec<String> {
124        let mut functions = HashSet::new();
125        self.functions.iter().for_each(|f| {
126            functions.insert(f.name.to_string());
127        });
128        Vec::from_iter(functions)
129    }
130
131    /// Returns DDL functions
132    pub fn ddl_functions(&self) -> Vec<String> {
133        self.functions
134            .iter()
135            .filter_map(|function| match &function.context {
136                Context::Ddl => Some(function.name.to_string()),
137                _ => None,
138            })
139            .collect()
140    }
141
142    /// Returns functions that were called
143    pub fn call_functions(&self) -> Vec<String> {
144        self.functions
145            .iter()
146            .filter_map(|function| match &function.context {
147                Context::Call => Some(function.name.to_string()),
148                _ => None,
149            })
150            .collect()
151    }
152
153    /// Returns all statement types in the query
154    pub fn statement_types(&self) -> Vec<&str> {
155        // Converts statement_types from Vec<String> to Vec<&str> for
156        // strict API compatibility with ParseResult.
157        self.statement_types.iter().map(AsRef::as_ref).collect()
158    }
159}
160
161#[derive(Debug, Eq, Hash, PartialEq)]
162pub struct Table {
163    pub name: String,
164    pub schema_name: String,
165    pub table_name: String,
166    pub context: Context,
167}
168
169impl From<&protobuf::summary_result::Table> for Table {
170    fn from(v: &protobuf::summary_result::Table) -> Self {
171        Self {
172            name: v.name.to_owned(),
173            schema_name: v.schema_name.to_owned(),
174            table_name: v.table_name.to_owned(),
175            context: Context::try_from(v.context).unwrap_or(Context::None),
176        }
177    }
178}
179
180#[derive(Debug, Eq, Hash, PartialEq)]
181pub struct Function {
182    pub name: String,
183    pub function_name: String,
184    pub schema_name: Option<String>,
185    pub context: Context,
186}
187
188impl From<&protobuf::summary_result::Function> for Function {
189    fn from(v: &protobuf::summary_result::Function) -> Self {
190        let schema_name = (!v.schema_name.is_empty()).then(|| v.schema_name.to_owned());
191
192        Function {
193            name: v.name.to_owned(),
194            function_name: v.function_name.to_owned(),
195            schema_name,
196            context: Context::try_from(v.context).unwrap_or(Context::None),
197        }
198    }
199}
200
201#[derive(Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
202pub struct FilterColumn {
203    pub schema_name: Option<String>,
204    pub table_name: Option<String>,
205    pub column: String,
206}
207
208impl From<&protobuf::summary_result::FilterColumn> for FilterColumn {
209    fn from(v: &protobuf::summary_result::FilterColumn) -> Self {
210        let schema_name = (!v.schema_name.is_empty()).then(|| v.schema_name.to_owned());
211        let table_name = (!v.table_name.is_empty()).then(|| v.table_name.to_owned());
212        let column = v.column.to_owned();
213
214        Self {
215            schema_name,
216            table_name,
217            column,
218        }
219    }
220}