1use std::cmp::Ordering;
2use std::collections::VecDeque;
3
4use crate::*;
5
6#[derive(Debug)]
7enum TruncationAttr {
8 TargetList,
9 WhereClause,
10 ValuesLists,
11 CTEQuery,
12 Cols,
13}
14
15#[derive(Debug)]
16struct PossibleTruncation {
17 attr: TruncationAttr,
18 node: NodeMut,
19 depth: i32,
20 length: i32,
21}
22
23pub fn truncate(protobuf: &protobuf::ParseResult, max_length: usize) -> Result<String> {
24 let mut output = protobuf.deparse()?;
25 if output.len() <= max_length {
26 return Ok(output);
27 }
28
29 unsafe {
38 let mut protobuf = protobuf.clone();
39 let mut truncations: VecDeque<PossibleTruncation> = VecDeque::new();
40 for (node, depth, _context) in protobuf.nodes_mut().into_iter() {
41 match node {
42 NodeMut::SelectStmt(s) => {
43 let s = s.as_mut().ok_or(Error::InvalidPointer)?;
44 if !s.target_list.is_empty() {
45 truncations.push_back(PossibleTruncation {
46 attr: TruncationAttr::TargetList,
47 node,
48 depth,
49 length: select_target_list_len(s.target_list.clone())?,
50 });
51 }
52 if let Some(clause) = s.where_clause.as_ref() {
53 truncations.push_back(PossibleTruncation {
54 attr: TruncationAttr::WhereClause,
55 node,
56 depth,
57 length: where_clause_len((*clause).clone())?,
58 });
59 }
60 if !s.values_lists.is_empty() {
61 truncations.push_back(PossibleTruncation {
62 attr: TruncationAttr::ValuesLists,
63 node,
64 depth,
65 length: select_values_lists_len(s.values_lists.clone())?,
66 });
67 }
68 }
69 NodeMut::UpdateStmt(s) => {
70 let s = s.as_mut().ok_or(Error::InvalidPointer)?;
71 if !s.target_list.is_empty() {
72 truncations.push_back(PossibleTruncation {
73 attr: TruncationAttr::TargetList,
74 node,
75 depth,
76 length: update_target_list_len(s.target_list.clone())?,
77 });
78 }
79 if let Some(clause) = s.where_clause.as_ref() {
80 truncations.push_back(PossibleTruncation {
81 attr: TruncationAttr::WhereClause,
82 node,
83 depth,
84 length: where_clause_len((*clause).clone())?,
85 });
86 }
87 }
88 NodeMut::DeleteStmt(s) => {
89 let s = s.as_mut().ok_or(Error::InvalidPointer)?;
90 if let Some(clause) = s.where_clause.as_ref() {
91 truncations.push_back(PossibleTruncation {
92 attr: TruncationAttr::WhereClause,
93 node,
94 depth,
95 length: where_clause_len((*clause).clone())?,
96 });
97 }
98 }
99 NodeMut::CopyStmt(s) => {
100 let s = s.as_mut().ok_or(Error::InvalidPointer)?;
101 if let Some(clause) = s.where_clause.as_ref() {
102 truncations.push_back(PossibleTruncation {
103 attr: TruncationAttr::WhereClause,
104 node,
105 depth,
106 length: where_clause_len((*clause).clone())?,
107 });
108 }
109 }
110 NodeMut::InsertStmt(s) => {
111 let s = s.as_mut().ok_or(Error::InvalidPointer)?;
112 if !s.cols.is_empty() {
113 truncations.push_back(PossibleTruncation { attr: TruncationAttr::Cols, node, depth, length: cols_len(s.cols.clone())? });
114 }
115 }
116 NodeMut::IndexStmt(s) => {
117 let s = s.as_mut().ok_or(Error::InvalidPointer)?;
118 if let Some(clause) = s.where_clause.as_ref() {
119 truncations.push_back(PossibleTruncation {
120 attr: TruncationAttr::WhereClause,
121 node,
122 depth,
123 length: where_clause_len((*clause).clone())?,
124 });
125 }
126 }
127 NodeMut::RuleStmt(s) => {
128 let s = s.as_mut().ok_or(Error::InvalidPointer)?;
129 if let Some(clause) = s.where_clause.as_ref() {
130 truncations.push_back(PossibleTruncation {
131 attr: TruncationAttr::WhereClause,
132 node,
133 depth,
134 length: where_clause_len((*clause).clone())?,
135 });
136 }
137 }
138 NodeMut::CommonTableExpr(s) => {
139 let s = s.as_mut().ok_or(Error::InvalidPointer)?;
140 if let Some(cte) = s.ctequery.as_ref() {
141 truncations.push_back(PossibleTruncation {
142 attr: TruncationAttr::CTEQuery,
143 node,
144 depth: depth + 1,
145 length: cte.deparse()?.len() as i32,
146 });
147 }
148 }
149 NodeMut::InferClause(s) => {
150 let s = s.as_mut().ok_or(Error::InvalidPointer)?;
151 if let Some(clause) = s.where_clause.as_ref() {
152 truncations.push_back(PossibleTruncation {
153 attr: TruncationAttr::WhereClause,
154 node,
155 depth,
156 length: where_clause_len((*clause).clone())?,
157 });
158 }
159 }
160 NodeMut::OnConflictClause(s) => {
161 let s = s.as_mut().ok_or(Error::InvalidPointer)?;
162 if !s.target_list.is_empty() {
163 truncations.push_back(PossibleTruncation {
164 attr: TruncationAttr::TargetList,
165 node,
166 depth,
167 length: update_target_list_len(s.target_list.clone())?,
168 });
169 }
170 if let Some(clause) = s.where_clause.as_ref() {
171 truncations.push_back(PossibleTruncation {
172 attr: TruncationAttr::WhereClause,
173 node,
174 depth,
175 length: where_clause_len((*clause).clone())?,
176 });
177 }
178 }
179 _ => (),
180 }
181 }
182
183 truncations.make_contiguous().sort_by(|a, b| match a.depth.cmp(&b.depth).reverse() {
184 Ordering::Equal => a.length.cmp(&b.length).reverse(),
185 other => other,
186 });
187
188 while let Some(truncation) = truncations.pop_front() {
189 match (truncation.node, truncation.attr) {
190 (NodeMut::SelectStmt(s), TruncationAttr::TargetList) => {
191 let s = s.as_mut().ok_or(Error::InvalidPointer)?;
192 s.target_list = vec![dummy_target()];
193 }
194 (NodeMut::SelectStmt(s), TruncationAttr::WhereClause) => {
195 let s = s.as_mut().ok_or(Error::InvalidPointer)?;
196 s.where_clause = Some(dummy_column());
197 }
198 (NodeMut::SelectStmt(s), TruncationAttr::ValuesLists) => {
199 let s = s.as_mut().ok_or(Error::InvalidPointer)?;
200 s.values_lists = vec![Node { node: Some(NodeEnum::List(protobuf::List { items: vec![*dummy_column()] })) }]
201 }
202 (NodeMut::UpdateStmt(s), TruncationAttr::TargetList) => {
203 let s = s.as_mut().ok_or(Error::InvalidPointer)?;
204 s.target_list = vec![dummy_target()];
205 }
206 (NodeMut::UpdateStmt(s), TruncationAttr::WhereClause) => {
207 let s = s.as_mut().ok_or(Error::InvalidPointer)?;
208 s.where_clause = Some(dummy_column());
209 }
210 (NodeMut::DeleteStmt(s), TruncationAttr::WhereClause) => {
211 let s = s.as_mut().ok_or(Error::InvalidPointer)?;
212 s.where_clause = Some(dummy_column());
213 }
214 (NodeMut::CopyStmt(s), TruncationAttr::WhereClause) => {
215 let s = s.as_mut().ok_or(Error::InvalidPointer)?;
216 s.where_clause = Some(dummy_column());
217 }
218 (NodeMut::InsertStmt(s), TruncationAttr::Cols) => {
219 let s = s.as_mut().ok_or(Error::InvalidPointer)?;
220 s.cols = vec![dummy_target()];
221 }
222 (NodeMut::IndexStmt(s), TruncationAttr::WhereClause) => {
223 let s = s.as_mut().ok_or(Error::InvalidPointer)?;
224 s.where_clause = Some(dummy_column());
225 }
226 (NodeMut::RuleStmt(s), TruncationAttr::WhereClause) => {
227 let s = s.as_mut().ok_or(Error::InvalidPointer)?;
228 s.where_clause = Some(dummy_column());
229 }
230 (NodeMut::CommonTableExpr(s), TruncationAttr::CTEQuery) => {
231 let s = s.as_mut().ok_or(Error::InvalidPointer)?;
232 let old = std::mem::replace(&mut s.ctequery, Some(dummy_select(vec![], Some(dummy_column()), vec![])));
233 if let Some(s) = old {
234 let node = s.node.ok_or(Error::InvalidPointer)?;
235 truncations.retain(|t| t.node.to_enum().unwrap() != node);
236 }
237 }
238 (NodeMut::InferClause(s), TruncationAttr::WhereClause) => {
239 let s = s.as_mut().ok_or(Error::InvalidPointer)?;
240 s.where_clause = Some(dummy_column());
241 }
242 (NodeMut::OnConflictClause(s), TruncationAttr::TargetList) => {
243 let s = s.as_mut().ok_or(Error::InvalidPointer)?;
244 s.target_list = vec![dummy_target()];
245 }
246 (NodeMut::OnConflictClause(s), TruncationAttr::WhereClause) => {
247 let s = s.as_mut().ok_or(Error::InvalidPointer)?;
248 s.where_clause = Some(dummy_column());
249 }
250 _ => panic!("unimplemented truncation"),
251 }
252 output = protobuf.deparse()?;
253 output = output.replace("SELECT WHERE \"…\"", "...").replace("\"…\"", "...");
254 output = output.replace("SELECT ... AS ...", "SELECT ...");
256 if output.len() <= max_length {
257 return Ok(output);
258 }
259 }
260 }
261
262 Ok(format!("{}...", truncate_str(&output, max_length - 3)))
264}
265
266fn truncate_str(string: &str, max_chars: usize) -> &str {
268 match string.char_indices().nth(max_chars) {
269 None => string,
270 Some((idx, _)) => &string[..idx],
271 }
272}
273
274fn select_target_list_len(nodes: Vec<Node>) -> Result<i32> {
275 let fragment = dummy_select(nodes, None, vec![]).deparse()?;
276 Ok(fragment.len() as i32 - 7) }
278
279fn select_values_lists_len(nodes: Vec<Node>) -> Result<i32> {
280 let fragment = dummy_select(vec![], None, nodes).deparse()?;
281 Ok(fragment.len() as i32 - 7) }
283
284fn update_target_list_len(nodes: Vec<Node>) -> Result<i32> {
285 let fragment = dummy_update(nodes).deparse()?;
286 Ok(fragment.len() as i32 - 13) }
288
289fn where_clause_len(node: Box<Node>) -> Result<i32> {
290 let fragment = dummy_select(vec![], Some(node), vec![]).deparse()?;
291 Ok(fragment.len() as i32 - 13) }
293
294fn cols_len(nodes: Vec<Node>) -> Result<i32> {
295 let fragment = dummy_insert(nodes).deparse()?;
296 Ok(fragment.len() as i32 - 31) }
298
299fn dummy_column() -> Box<Node> {
300 Box::new(Node {
301 node: Some(NodeEnum::ColumnRef(protobuf::ColumnRef {
302 location: 0,
303 fields: vec![Node { node: Some(NodeEnum::String(protobuf::String { sval: "…".to_string() })) }],
304 })),
305 })
306}
307
308fn dummy_target() -> Node {
309 Node {
310 node: Some(NodeEnum::ResTarget(Box::new(protobuf::ResTarget {
311 name: "…".to_string(),
312 location: 0,
313 indirection: vec![],
314 val: Some(dummy_column()),
315 }))),
316 }
317}
318
319fn dummy_select(target_list: Vec<Node>, where_clause: Option<Box<Node>>, values_lists: Vec<Node>) -> Box<Node> {
320 Box::new(Node {
321 node: Some(NodeEnum::SelectStmt(Box::new(protobuf::SelectStmt {
322 distinct_clause: vec![],
323 into_clause: None,
324 target_list,
325 from_clause: vec![],
326 where_clause,
327 group_clause: vec![],
328 having_clause: None,
329 window_clause: vec![],
330 values_lists,
331 sort_clause: vec![],
332 limit_offset: None,
333 limit_count: None,
334 limit_option: 1,
335 locking_clause: vec![],
336 with_clause: None,
337 op: 1,
338 all: false,
339 larg: None,
340 rarg: None,
341 group_distinct: false,
342 }))),
343 })
344}
345
346fn dummy_insert(cols: Vec<Node>) -> Box<Node> {
347 Box::new(Node {
348 node: Some(NodeEnum::InsertStmt(Box::new(protobuf::InsertStmt {
349 relation: Some(protobuf::RangeVar {
350 catalogname: "".to_string(),
351 schemaname: "".to_string(),
352 relname: "x".to_string(),
353 inh: true,
354 relpersistence: "p".to_string(),
355 alias: None,
356 location: 0,
357 }),
358 cols,
359 select_stmt: None,
360 on_conflict_clause: None,
361 returning_clause: None,
362 with_clause: None,
363 r#override: 1,
364 }))),
365 })
366}
367
368fn dummy_update(target_list: Vec<Node>) -> Box<Node> {
369 Box::new(Node {
370 node: Some(NodeEnum::UpdateStmt(Box::new(protobuf::UpdateStmt {
371 relation: Some(protobuf::RangeVar {
372 catalogname: "".to_string(),
373 schemaname: "".to_string(),
374 relname: "x".to_string(),
375 inh: true,
376 relpersistence: "p".to_string(),
377 alias: None,
378 location: 0,
379 }),
380 from_clause: vec![],
381 target_list,
382 where_clause: None,
383 returning_clause: None,
384 with_clause: None,
385 }))),
386 })
387}