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.lines().filter_map(|l| if l.starts_with("WARNING") { Some(l.trim().into()) } else { None }).collect();
38        let mut tables: HashSet<Table> = HashSet::new();
39        let aliases = protobuf.aliases.clone();
40        let cte_names: HashSet<String> = HashSet::from_iter(protobuf.cte_names.to_owned());
41        let mut functions: HashSet<Function> = HashSet::new();
42        let mut filter_columns: HashSet<FilterColumn> = HashSet::new();
43        let truncated_query = protobuf.truncated_query.to_owned();
44        let statement_types = protobuf.statement_types.clone();
45
46        for table in &protobuf.tables {
47            tables.insert(Table::from(table));
48        }
49
50        for function in &protobuf.functions {
51            functions.insert(Function::from(function));
52        }
53
54        for filter_column in &protobuf.filter_columns {
55            filter_columns.insert(FilterColumn::from(filter_column));
56        }
57
58        Self {
59            protobuf,
60            warnings,
61            tables: Vec::from_iter(tables),
62            aliases,
63            cte_names: Vec::from_iter(cte_names),
64            functions: Vec::from_iter(functions),
65            filter_columns: Vec::from_iter(filter_columns),
66            truncated_query,
67            statement_types,
68        }
69    }
70
71    /// Returns all referenced tables in the query
72    pub fn tables(&self) -> Vec<String> {
73        let mut tables = HashSet::new();
74        self.tables.iter().for_each(|table| {
75            tables.insert(table.name.clone());
76        });
77        Vec::from_iter(tables)
78    }
79
80    /// Returns only tables that were selected from
81    pub fn select_tables(&self) -> Vec<String> {
82        self.tables
83            .iter()
84            .filter_map(|table| match &table.context {
85                Context::Select => Some(table.name.to_string()),
86                _ => None,
87            })
88            .collect()
89    }
90
91    /// Returns only tables that were modified by the query
92    pub fn dml_tables(&self) -> Vec<String> {
93        self.tables
94            .iter()
95            .filter_map(|table| match &table.context {
96                Context::Dml => Some(table.name.to_string()),
97                _ => None,
98            })
99            .collect()
100    }
101
102    /// Returns only tables that were modified by DDL statements
103    pub fn ddl_tables(&self) -> Vec<String> {
104        self.tables
105            .iter()
106            .filter_map(|table| match &table.context {
107                Context::Ddl => Some(table.name.to_string()),
108                _ => None,
109            })
110            .collect()
111    }
112
113    /// Returns all function references
114    pub fn functions(&self) -> Vec<String> {
115        let mut functions = HashSet::new();
116        self.functions.iter().for_each(|f| {
117            functions.insert(f.name.to_string());
118        });
119        Vec::from_iter(functions)
120    }
121
122    /// Returns DDL functions
123    pub fn ddl_functions(&self) -> Vec<String> {
124        self.functions
125            .iter()
126            .filter_map(|function| match &function.context {
127                Context::Ddl => Some(function.name.to_string()),
128                _ => None,
129            })
130            .collect()
131    }
132
133    /// Returns functions that were called
134    pub fn call_functions(&self) -> Vec<String> {
135        self.functions
136            .iter()
137            .filter_map(|function| match &function.context {
138                Context::Call => Some(function.name.to_string()),
139                _ => None,
140            })
141            .collect()
142    }
143
144    /// Returns all statement types in the query
145    pub fn statement_types(&self) -> Vec<&str> {
146        // Converts statement_types from Vec<String> to Vec<&str> for
147        // strict API compatibility with ParseResult.
148        self.statement_types.iter().map(AsRef::as_ref).collect()
149    }
150}
151
152#[derive(Debug, Eq, Hash, PartialEq)]
153pub struct Table {
154    pub name: String,
155    pub schema_name: String,
156    pub table_name: String,
157    pub context: Context,
158}
159
160impl From<&protobuf::summary_result::Table> for Table {
161    fn from(v: &protobuf::summary_result::Table) -> Self {
162        Self {
163            name: v.name.to_owned(),
164            schema_name: v.schema_name.to_owned(),
165            table_name: v.table_name.to_owned(),
166            context: Context::try_from(v.context).unwrap_or(Context::None),
167        }
168    }
169}
170
171#[derive(Debug, Eq, Hash, PartialEq)]
172pub struct Function {
173    pub name: String,
174    pub function_name: String,
175    pub schema_name: Option<String>,
176    pub context: Context,
177}
178
179impl From<&protobuf::summary_result::Function> for Function {
180    fn from(v: &protobuf::summary_result::Function) -> Self {
181        let schema_name = (!v.schema_name.is_empty()).then(|| v.schema_name.to_owned());
182
183        Function {
184            name: v.name.to_owned(),
185            function_name: v.function_name.to_owned(),
186            schema_name: schema_name,
187            context: Context::try_from(v.context).unwrap_or(Context::None),
188        }
189    }
190}
191
192#[derive(Debug, Eq, Hash, PartialEq, PartialOrd, Ord)]
193pub struct FilterColumn {
194    pub schema_name: Option<String>,
195    pub table_name: Option<String>,
196    pub column: String,
197}
198
199impl From<&protobuf::summary_result::FilterColumn> for FilterColumn {
200    fn from(v: &protobuf::summary_result::FilterColumn) -> Self {
201        let schema_name = (!v.schema_name.is_empty()).then(|| v.schema_name.to_owned());
202        let table_name = (!v.table_name.is_empty()).then(|| v.table_name.to_owned());
203        let column = v.column.to_owned();
204
205        Self { schema_name, table_name, column }
206    }
207}