1use std::ascii;
11use std::error::Error;
12use std::fmt::{self, Display, Formatter, Write as _};
13use std::time::SystemTime;
14
15use anyhow::{Context, bail};
16use itertools::Itertools;
17use md5::{Digest, Md5};
18use mz_ore::collections::CollectionExt;
19use mz_ore::retry::Retry;
20use mz_ore::str::StrExt;
21use mz_pgrepr::{Interval, Jsonb, Numeric, UInt2, UInt4, UInt8};
22use mz_postgres_util::query_prepared;
23use mz_repr::adt::range::Range;
24use mz_sql_parser::ast::{Raw, Statement};
25use postgres_array::Array;
26use regex::Regex;
27use tokio_postgres::error::DbError;
28use tokio_postgres::row::Row;
29use tokio_postgres::types::{FromSql, Type};
30
31use crate::action::{ControlFlow, Rewrite, State};
32use crate::parser::{FailSqlCommand, SqlCommand, SqlExpectedError, SqlOutput};
33
34pub async fn run_sql(mut cmd: SqlCommand, state: &mut State) -> Result<ControlFlow, anyhow::Error> {
35 use Statement::*;
36
37 state.rewrite_pos_start = cmd.expected_start;
38 state.rewrite_pos_end = cmd.expected_end;
39
40 let stmts = mz_sql_parser::parser::parse_statements(&cmd.query)
41 .with_context(|| format!("unable to parse SQL: {}", cmd.query))?;
42 if stmts.len() != 1 {
43 bail!("expected one statement, but got {}", stmts.len());
44 }
45 let stmt = stmts.into_element().ast;
46 if let SqlOutput::Full { expected_rows, .. } = &mut cmd.expected_output {
47 expected_rows.sort();
49 }
50
51 let should_retry = match &stmt {
52 Fetch(_) => false,
55 ExplainPlan(_) => false,
58 CreateConnection(_)
60 | CreateCluster(_)
61 | CreateClusterReplica(_)
62 | CreateDatabase(_)
63 | CreateSchema(_)
64 | CreateSource(_)
65 | CreateWebhookSource(_)
66 | CreateSink(_)
67 | CreateMaterializedView(_)
68 | CreateView(_)
69 | CreateTable(_)
70 | CreateTableFromSource(_)
71 | CreateIndex(_)
72 | CreateType(_)
73 | CreateRole(_)
74 | AlterObjectRename(_)
75 | AlterIndex(_)
76 | AlterSink(_)
77 | Discard(_)
78 | DropObjects(_)
79 | SetVariable(_) => false,
80 _ => true,
81 };
82
83 let query = &cmd.query;
84 print_query(query, Some(&stmt));
85 let expected_output = &cmd.expected_output;
86 let raw_output = cmd.raw_output;
87 state.error_line_count = 0;
88 state.error_string = "".to_string();
89 let (state, res) = match should_retry {
90 true => Retry::default()
91 .initial_backoff(state.initial_backoff)
92 .factor(state.backoff_factor)
93 .max_duration(state.timeout)
94 .max_tries(state.max_tries),
95 false => Retry::default().max_duration(state.timeout).max_tries(1),
96 }
97 .retry_async_with_state(state, |retry_state, state| async move {
98 let should_continue = retry_state.next_backoff.is_some() && should_retry;
104 let start = SystemTime::now();
105 match try_run_sql(state, query, expected_output, raw_output, should_continue).await {
106 Ok(()) => {
107 let now = SystemTime::now();
108 let epoch = SystemTime::UNIX_EPOCH;
109 let ts = now.duration_since(epoch).unwrap().as_secs_f64();
110 let delay = now.duration_since(start).unwrap().as_secs_f64();
111 println!("rows match; continuing at ts {ts}, took {delay}s");
112 (state, Ok(()))
113 }
114 Err(e) => {
115 if let Some(backoff) = retry_state.next_backoff {
116 if !backoff.is_zero()
117 && (retry_state.i == 0 || !mz_ore::env::is_var_truthy("CI"))
118 {
119 let error_string = format!("{:?}", e);
120 if error_string != state.error_string {
121 for _ in 0..state.error_line_count {
123 print!("\x1B[1A\x1B[2K");
124 }
125 state.error_line_count = 0;
126 state.error_string = error_string;
127 state.error_line_count = state.error_string.lines().count() + 1;
128 if state.error_string.ends_with('\n') {
129 print!("{}", state.error_string);
130 } else {
131 println!("{}", state.error_string);
132 state.error_line_count += 1;
133 }
134 println!(
135 "rows didn't match; sleeping to see if dataflow catches up 🕑 {:.0?}",
136 retry_state.next_backoff.unwrap_or_default()
137 );
138 }
139 }
140 } else {
141 for _ in 0..state.error_line_count {
142 print!("\x1B[1A\x1B[2K");
143 }
144 state.error_line_count = 0;
145 }
146 (state, Err(e))
147 }
148 }
149 })
150 .await;
151 if let Err(e) = res {
152 return Err(e);
153 }
154 if state.consistency_checks == super::consistency::Level::Statement {
155 super::consistency::run_consistency_checks(state).await?;
156 }
157
158 Ok(ControlFlow::Continue)
159}
160
161fn escape_for_parser(value: &str) -> String {
168 let mut out = String::with_capacity(value.len() + 2);
169 out.push('"');
170 for c in value.chars() {
171 match c {
172 '\\' => out.push_str("\\\\"),
173 '"' => out.push_str("\\\""),
174 '\n' => out.push_str("\\n"),
175 '\t' => out.push_str("\\t"),
176 '\r' => out.push_str("\\r"),
177 '\0' => out.push_str("\\0"),
178 c => out.push(c),
179 }
180 }
181 out.push('"');
182 out
183}
184
185fn rewrite_result(
186 state: &mut State,
187 columns: Vec<&str>,
188 content: Vec<Vec<String>>,
189 raw_output: bool,
190) -> Result<(), anyhow::Error> {
191 fn escape_line_start(line: String) -> String {
195 match line.chars().next() {
196 Some('$' | '>' | '!' | '?' | '#' | '\\') => format!("\\{line}"),
197 _ => line,
198 }
199 }
200
201 let mut buf = String::new();
202 if raw_output {
203 for row in content {
206 for value in row {
207 buf.push_str(&value);
208 if !value.ends_with('\n') {
209 buf.push('\n');
210 }
211 }
212 }
213 } else {
214 writeln!(buf, "{}", escape_line_start(columns.join(" ")))?;
215 writeln!(buf, "----")?;
216 for row in content {
217 let mut formatted_row = Vec::<String>::new();
218 for value in row {
219 if value.is_empty()
220 || value.contains(|x: char| char::is_ascii_whitespace(&x))
221 || value.contains('"')
222 || value.contains('\\')
223 {
224 formatted_row.push(escape_for_parser(&value));
225 } else {
226 formatted_row.push(value);
227 }
228 }
229 writeln!(buf, "{}", escape_line_start(formatted_row.join(" ")))?;
230 }
231 }
232 state.rewrites.push(Rewrite {
233 content: buf,
234 start: state.rewrite_pos_start,
235 end: state.rewrite_pos_end,
236 });
237
238 Ok(())
239}
240
241async fn try_run_sql(
242 state: &mut State,
243 query: &str,
244 expected_output: &SqlOutput,
245 raw_output: bool,
246 should_retry: bool,
247) -> Result<(), anyhow::Error> {
248 let stmt = state
249 .materialize
250 .pgclient
251 .prepare(query)
252 .await
253 .context("preparing query failed")?;
254
255 let query_with_timeout = tokio::time::timeout(
256 state.timeout.clone(),
257 query_prepared(&state.materialize.pgclient, &stmt, &[]),
258 )
259 .await;
260
261 if query_with_timeout.is_err() {
262 bail!("query timed out\n")
263 }
264
265 let rows: Vec<_> = query_with_timeout
266 .unwrap()
267 .context("executing query failed")?
268 .into_iter()
269 .map(|row| decode_row(state, row))
270 .collect::<Result<_, _>>()?;
271
272 let (mut actual, raw_actual): (Vec<_>, Vec<_>) = rows.into_iter().unzip();
273
274 let raw_actual: Option<Vec<_>> = if raw_actual.iter().any(|r| r.is_some()) {
275 Some(
278 actual
279 .iter()
280 .zip_eq(raw_actual)
281 .map(|(actual, unreplaced)| match unreplaced {
282 Some(raw_row) => raw_row,
283 None => actual.clone(),
284 })
285 .collect(),
286 )
287 } else {
288 None
289 };
290
291 actual.sort();
292 let actual_columns: Vec<_> = stmt.columns().iter().map(|c| c.name()).collect();
293
294 match expected_output {
295 SqlOutput::Full {
296 expected_rows,
297 column_names,
298 } => {
299 if let Some(column_names) = column_names {
300 if actual_columns.iter().ne(column_names) {
301 if state.rewrite_results && !should_retry {
302 rewrite_result(state, actual_columns, actual, raw_output)?;
303 return Ok(());
304 } else {
305 bail!(
306 "column name mismatch\nexpected: {:?}\nactual: {:?}\n",
307 column_names,
308 actual_columns
309 );
310 }
311 }
312 }
313 if &actual == expected_rows {
314 Ok(())
315 } else if state.rewrite_results && !should_retry {
316 rewrite_result(state, actual_columns, actual, raw_output)?;
317 Ok(())
318 } else {
319 let (mut left, mut right) = (0, 0);
320 let mut buf = String::new();
321 while let (Some(e), Some(a)) = (expected_rows.get(left), actual.get(right)) {
322 match e.cmp(a) {
323 std::cmp::Ordering::Less => {
324 writeln!(buf, "- {}", TestdriveRow(e)).unwrap();
325 left += 1;
326 }
327 std::cmp::Ordering::Equal => {
328 left += 1;
329 right += 1;
330 }
331 std::cmp::Ordering::Greater => {
332 writeln!(buf, "+ {}", TestdriveRow(a)).unwrap();
333 right += 1;
334 }
335 }
336 }
337 while let Some(e) = expected_rows.get(left) {
338 writeln!(buf, "- {}", TestdriveRow(e)).unwrap();
339 left += 1;
340 }
341 while let Some(a) = actual.get(right) {
342 writeln!(buf, "+ {}", TestdriveRow(a)).unwrap();
343 right += 1;
344 }
345 if state.rewrite_results && !should_retry {
346 rewrite_result(state, actual_columns, actual, raw_output)?;
347 Ok(())
348 } else if let Some(raw_actual) = raw_actual {
349 bail!(
350 "non-matching rows: expected:\n{:?}\ngot:\n{:?}\ngot raw rows:\n{:?}\nPoor diff:\n{}",
351 expected_rows,
352 actual,
353 raw_actual,
354 buf,
355 )
356 } else {
357 bail!(
358 "non-matching rows: expected:\n{:?}\ngot:\n{:?}\nPoor diff:\n{}",
359 expected_rows,
360 actual,
361 buf
362 )
363 }
364 }
365 }
366 SqlOutput::Hashed { num_values, md5 } => {
367 if &actual.len() != num_values {
368 bail!(
369 "wrong row count: expected:\n{:?}\ngot:\n{:?}\n",
370 num_values,
371 actual.len(),
372 )
373 } else {
374 let mut hasher = Md5::new();
375 for row in &actual {
376 for entry in row {
377 hasher.update(entry);
378 }
379 }
380 let actual = format!("{:x}", hasher.finalize());
381 if &actual != md5 {
382 bail!("wrong hash value: expected:{:?} got:{:?}", md5, actual)
383 } else {
384 Ok(())
385 }
386 }
387 }
388 }
389}
390
391#[derive(Clone)]
392enum ErrorMatcher {
393 Contains(String),
394 Exact(String),
395 Regex(Regex),
396 Timeout,
397}
398
399impl ErrorMatcher {
400 fn is_match(&self, err: &String) -> bool {
401 match self {
402 ErrorMatcher::Contains(s) => err.contains(s),
403 ErrorMatcher::Exact(s) => err == s,
404 ErrorMatcher::Regex(r) => r.is_match(err),
405 ErrorMatcher::Timeout => false,
409 }
410 }
411}
412
413impl fmt::Display for ErrorMatcher {
414 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
415 match self {
416 ErrorMatcher::Contains(s) => write!(f, "error containing {}", s.quoted()),
417 ErrorMatcher::Exact(s) => write!(f, "exact error {}", s.quoted()),
418 ErrorMatcher::Regex(s) => write!(f, "error matching regex {}", s.as_str().quoted()),
419 ErrorMatcher::Timeout => f.write_str("timeout"),
420 }
421 }
422}
423
424impl ErrorMatcher {
425 fn fmt_with_type(&self, type_: &str) -> String {
426 match self {
427 ErrorMatcher::Contains(s) => format!("{} containing {}", type_, s.quoted()),
428 ErrorMatcher::Exact(s) => format!("exact {} {}", type_, s.quoted()),
429 ErrorMatcher::Regex(s) => format!("{} matching regex {}", type_, s.as_str().quoted()),
430 ErrorMatcher::Timeout => "timeout".to_string(),
431 }
432 }
433}
434
435pub async fn run_fail_sql(
436 cmd: FailSqlCommand,
437 state: &mut State,
438) -> Result<ControlFlow, anyhow::Error> {
439 use Statement::{AlterSink, Commit, CreateConnection, Fetch, Rollback};
440
441 let stmts = mz_sql_parser::parser::parse_statements(&cmd.query)
442 .map_err(|e| format!("unable to parse SQL: {}: {}", cmd.query, e));
443
444 let stmt = match stmts {
447 Ok(s) => {
448 if s.len() != 1 {
449 bail!("expected one statement, but got {}", s.len());
450 }
451 Some(s.into_element().ast)
452 }
453 Err(_) => None,
454 };
455
456 let expected_error = match cmd.expected_error {
457 SqlExpectedError::Contains(s) => ErrorMatcher::Contains(s),
458 SqlExpectedError::Exact(s) => ErrorMatcher::Exact(s),
459 SqlExpectedError::Regex(s) => ErrorMatcher::Regex(s.parse()?),
460 SqlExpectedError::Timeout => ErrorMatcher::Timeout,
461 };
462 let expected_detail = cmd.expected_detail.map(ErrorMatcher::Contains);
463 let expected_hint = cmd.expected_hint.map(ErrorMatcher::Contains);
464
465 let query = &cmd.query;
466 print_query(query, stmt.as_ref());
467
468 let should_retry = match &stmt {
469 None => false,
471 Some(Commit(_)) | Some(Rollback(_)) => false,
475 Some(Fetch(_)) => false,
477 Some(AlterSink(_)) => false,
478 Some(CreateConnection(_)) => false,
479 Some(_) => true,
480 };
481
482 state.error_line_count = 0;
483 state.error_string = "".to_string();
484 let res = match should_retry {
485 true => Retry::default()
486 .initial_backoff(state.initial_backoff)
487 .factor(state.backoff_factor)
488 .max_duration(state.timeout)
489 .max_tries(state.max_tries),
490 false => Retry::default().max_duration(state.timeout).max_tries(1),
491 }
492 .retry_async_with_state_canceling(state, |retry_state, state| {
493 let expected_error = expected_error.clone();
494 let expected_detail = expected_detail.clone();
495 let expected_hint = expected_hint.clone();
496 async move {
497 match try_run_fail_sql(
498 state,
499 query,
500 &expected_error,
501 expected_detail.as_ref(),
502 expected_hint.as_ref(),
503 )
504 .await
505 {
506 Ok(()) => {
507 println!("query error matches; continuing");
508 (state, Ok(()))
509 }
510 Err(e) => {
511 if let Some(backoff) = retry_state.next_backoff {
512 if !backoff.is_zero() && (retry_state.i == 0 || !mz_ore::env::is_var_truthy("CI")) {
513 let error_string = format!("{:?}", e);
514 if error_string != state.error_string {
515 for _ in 0..state.error_line_count {
517 print!("\x1B[1A\x1B[2K");
518 }
519 state.error_line_count = 0;
520 state.error_string = error_string;
521 state.error_line_count = state.error_string.lines().count() + 1;
522 if state.error_string.ends_with('\n') {
523 print!("{}", state.error_string);
524 } else {
525 println!("{}", state.error_string);
526 state.error_line_count += 1;
527 }
528 println!("query error didn't match; sleeping to see if dataflow produces error shortly 🕑 {:.0?}", retry_state.next_backoff.unwrap_or_default());
529 }
530 }
531 } else {
532 for _ in 0..state.error_line_count {
533 print!("\x1B[1A\x1B[2K");
534 }
535 state.error_line_count = 0;
536 }
537 (state, Err(e))
538 }
539 }
540 }})
541 .await;
542
543 if let ErrorMatcher::Timeout = expected_error {
546 if let Err(e) = &res {
547 if e.is::<tokio::time::error::Elapsed>() {
548 println!("query timed out as expected");
549 return Ok(ControlFlow::Continue);
550 }
551 }
552 }
553
554 res?;
558 Ok(ControlFlow::Continue)
559}
560
561async fn try_run_fail_sql(
562 state: &State,
563 query: &str,
564 expected_error: &ErrorMatcher,
565 expected_detail: Option<&ErrorMatcher>,
566 expected_hint: Option<&ErrorMatcher>,
567) -> Result<(), anyhow::Error> {
568 #[allow(clippy::disallowed_methods)]
571 match state.materialize.pgclient.query(query, &[]).await {
572 Ok(_) => bail!("query succeeded, but expected {}", expected_error),
573 Err(err) => match err.source().and_then(|err| err.downcast_ref::<DbError>()) {
574 Some(err) => {
575 let mut err_string = err.message().to_string();
576 if let Some(regex) = &state.regex {
577 err_string = regex
578 .replace_all(&err_string, state.regex_replacement.as_str())
579 .to_string();
580 }
581 if !expected_error.is_match(&err_string) {
582 bail!("expected {}, got {}", expected_error, err_string.quoted());
583 }
584
585 let check_additional =
586 |extra: Option<&str>, matcher: Option<&ErrorMatcher>, type_| {
587 let extra = extra.map(|s| s.to_string());
588 match (extra, matcher) {
589 (Some(extra), Some(expected)) => {
590 if !expected.is_match(&extra) {
591 bail!(
592 "expected {}, got {}",
593 expected.fmt_with_type(type_),
594 extra.quoted()
595 );
596 }
597 }
598 (None, Some(expected)) => {
599 bail!("expected {}, but found none", expected.fmt_with_type(type_));
600 }
601 _ => {}
602 }
603 Ok(())
604 };
605
606 check_additional(err.detail(), expected_detail, "DETAIL")?;
607 check_additional(err.hint(), expected_hint, "HINT")?;
608
609 Ok(())
610 }
611 None => Err(err.into()),
612 },
613 }
614}
615
616pub fn print_query(query: &str, stmt: Option<&Statement<Raw>>) {
617 use Statement::*;
618 if let Some(CreateSecret(_)) = stmt {
619 println!(
620 "> CREATE SECRET [query truncated on purpose so as to not reveal the secret in the log]"
621 );
622 } else {
623 println!("> {}", query)
624 }
625}
626
627pub fn decode_row(
629 state: &State,
630 row: Row,
631) -> Result<(Vec<String>, Option<Vec<String>>), anyhow::Error> {
632 enum ArrayElement<T> {
633 Null,
634 NonNull(T),
635 }
636
637 impl<T> fmt::Display for ArrayElement<T>
638 where
639 T: fmt::Display,
640 {
641 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
642 match self {
643 ArrayElement::Null => f.write_str("NULL"),
644 ArrayElement::NonNull(t) => t.fmt(f),
645 }
646 }
647 }
648
649 impl<'a, T> FromSql<'a> for ArrayElement<T>
650 where
651 T: FromSql<'a>,
652 {
653 fn from_sql(
654 ty: &Type,
655 raw: &'a [u8],
656 ) -> Result<ArrayElement<T>, Box<dyn Error + Sync + Send>> {
657 T::from_sql(ty, raw).map(ArrayElement::NonNull)
658 }
659
660 fn from_sql_null(_: &Type) -> Result<ArrayElement<T>, Box<dyn Error + Sync + Send>> {
661 Ok(ArrayElement::Null)
662 }
663
664 fn accepts(ty: &Type) -> bool {
665 T::accepts(ty)
666 }
667 }
668
669 struct NumericStandardNotation(Numeric);
674
675 impl fmt::Display for NumericStandardNotation {
676 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
677 write!(f, "{}", self.0.0.0.to_standard_notation_string())
678 }
679 }
680
681 impl<'a> FromSql<'a> for NumericStandardNotation {
682 fn from_sql(
683 ty: &Type,
684 raw: &'a [u8],
685 ) -> Result<NumericStandardNotation, Box<dyn Error + Sync + Send>> {
686 Ok(NumericStandardNotation(Numeric::from_sql(ty, raw)?))
687 }
688
689 fn from_sql_null(
690 ty: &Type,
691 ) -> Result<NumericStandardNotation, Box<dyn Error + Sync + Send>> {
692 Ok(NumericStandardNotation(Numeric::from_sql_null(ty)?))
693 }
694
695 fn accepts(ty: &Type) -> bool {
696 Numeric::accepts(ty)
697 }
698 }
699
700 let mut out = vec![];
701 let mut raw_out = vec![];
702 for (i, col) in row.columns().iter().enumerate() {
703 let ty = col.type_();
704 let mut value: String = match *ty {
705 Type::ACLITEM => row.get::<_, Option<AclItem>>(i).map(|x| x.0),
706 Type::BOOL => row.get::<_, Option<bool>>(i).map(|x| x.to_string()),
707 Type::BPCHAR | Type::TEXT | Type::VARCHAR => row.get::<_, Option<String>>(i),
708 Type::TEXT_ARRAY => row
709 .get::<_, Option<Array<ArrayElement<String>>>>(i)
710 .map(|a| a.to_string()),
711 Type::BYTEA => row.get::<_, Option<Vec<u8>>>(i).map(|x| {
712 let s = x.into_iter().map(ascii::escape_default).flatten().collect();
713 String::from_utf8(s).unwrap()
714 }),
715 Type::CHAR => row.get::<_, Option<i8>>(i).map(|x| x.to_string()),
716 Type::INT2 => row.get::<_, Option<i16>>(i).map(|x| x.to_string()),
717 Type::INT4 => row.get::<_, Option<i32>>(i).map(|x| x.to_string()),
718 Type::INT8 => row.get::<_, Option<i64>>(i).map(|x| x.to_string()),
719 Type::OID => row.get::<_, Option<u32>>(i).map(|x| x.to_string()),
720 Type::NUMERIC => row
721 .get::<_, Option<NumericStandardNotation>>(i)
722 .map(|x| x.to_string()),
723 Type::FLOAT4 => row.get::<_, Option<f32>>(i).map(|x| x.to_string()),
724 Type::FLOAT8 => row.get::<_, Option<f64>>(i).map(|x| x.to_string()),
725 Type::TIMESTAMP => row
726 .get::<_, Option<chrono::NaiveDateTime>>(i)
727 .map(|x| x.to_string()),
728 Type::TIMESTAMPTZ => row
729 .get::<_, Option<chrono::DateTime<chrono::Utc>>>(i)
730 .map(|x| x.to_string()),
731 Type::DATE => row
732 .get::<_, Option<chrono::NaiveDate>>(i)
733 .map(|x| x.to_string()),
734 Type::TIME => row
735 .get::<_, Option<chrono::NaiveTime>>(i)
736 .map(|x| x.to_string()),
737 Type::INTERVAL => row.get::<_, Option<Interval>>(i).map(|x| x.to_string()),
738 Type::JSONB => row.get::<_, Option<Jsonb>>(i).map(|v| v.0.to_string()),
739 Type::UUID => row.get::<_, Option<uuid::Uuid>>(i).map(|v| v.to_string()),
740 Type::BOOL_ARRAY => row
741 .get::<_, Option<Array<ArrayElement<bool>>>>(i)
742 .map(|a| a.to_string()),
743 Type::INT2_ARRAY | Type::INT2_VECTOR => row
744 .get::<_, Option<Array<ArrayElement<i16>>>>(i)
745 .map(|a| a.to_string()),
746 Type::INT4_ARRAY => row
747 .get::<_, Option<Array<ArrayElement<i32>>>>(i)
748 .map(|a| a.to_string()),
749 Type::INT8_ARRAY => row
750 .get::<_, Option<Array<ArrayElement<i64>>>>(i)
751 .map(|a| a.to_string()),
752 Type::OID_ARRAY => row
753 .get::<_, Option<Array<ArrayElement<u32>>>>(i)
754 .map(|x| x.to_string()),
755 Type::NUMERIC_ARRAY => row
756 .get::<_, Option<Array<ArrayElement<NumericStandardNotation>>>>(i)
757 .map(|x| x.to_string()),
758 Type::FLOAT4_ARRAY => row
759 .get::<_, Option<Array<ArrayElement<f32>>>>(i)
760 .map(|x| x.to_string()),
761 Type::FLOAT8_ARRAY => row
762 .get::<_, Option<Array<ArrayElement<f64>>>>(i)
763 .map(|x| x.to_string()),
764 Type::TIMESTAMP_ARRAY => row
765 .get::<_, Option<Array<ArrayElement<chrono::NaiveDateTime>>>>(i)
766 .map(|x| x.to_string()),
767 Type::TIMESTAMPTZ_ARRAY => row
768 .get::<_, Option<Array<ArrayElement<chrono::DateTime<chrono::Utc>>>>>(i)
769 .map(|x| x.to_string()),
770 Type::DATE_ARRAY => row
771 .get::<_, Option<Array<ArrayElement<chrono::NaiveDate>>>>(i)
772 .map(|x| x.to_string()),
773 Type::TIME_ARRAY => row
774 .get::<_, Option<Array<ArrayElement<chrono::NaiveTime>>>>(i)
775 .map(|x| x.to_string()),
776 Type::INTERVAL_ARRAY => row
777 .get::<_, Option<Array<ArrayElement<Interval>>>>(i)
778 .map(|x| x.to_string()),
779 Type::JSONB_ARRAY => row
780 .get::<_, Option<Array<ArrayElement<Jsonb>>>>(i)
781 .map(|v| v.to_string()),
782 Type::UUID_ARRAY => row
783 .get::<_, Option<Array<ArrayElement<uuid::Uuid>>>>(i)
784 .map(|v| v.to_string()),
785 Type::INT4_RANGE => row.get::<_, Option<Range<i32>>>(i).map(|v| v.to_string()),
786 Type::INT4_RANGE_ARRAY => row
787 .get::<_, Option<Array<ArrayElement<Range<i32>>>>>(i)
788 .map(|v| v.to_string()),
789 Type::INT8_RANGE => row.get::<_, Option<Range<i64>>>(i).map(|v| v.to_string()),
790 Type::INT8_RANGE_ARRAY => row
791 .get::<_, Option<Array<ArrayElement<Range<i64>>>>>(i)
792 .map(|v| v.to_string()),
793 Type::NUM_RANGE => row
794 .get::<_, Option<Range<NumericStandardNotation>>>(i)
795 .map(|v| v.to_string()),
796 Type::NUM_RANGE_ARRAY => row
797 .get::<_, Option<Array<ArrayElement<Range<NumericStandardNotation>>>>>(i)
798 .map(|v| v.to_string()),
799 Type::DATE_RANGE => row
800 .get::<_, Option<Range<chrono::NaiveDate>>>(i)
801 .map(|v| v.to_string()),
802 Type::DATE_RANGE_ARRAY => row
803 .get::<_, Option<Array<ArrayElement<Range<chrono::NaiveDate>>>>>(i)
804 .map(|v| v.to_string()),
805 Type::TS_RANGE => row
806 .get::<_, Option<Range<chrono::NaiveDateTime>>>(i)
807 .map(|v| v.to_string()),
808 Type::TS_RANGE_ARRAY => row
809 .get::<_, Option<Array<ArrayElement<Range<chrono::NaiveDateTime>>>>>(i)
810 .map(|v| v.to_string()),
811 Type::TSTZ_RANGE => row
812 .get::<_, Option<Range<chrono::DateTime<chrono::Utc>>>>(i)
813 .map(|v| v.to_string()),
814 Type::TSTZ_RANGE_ARRAY => row
815 .get::<_, Option<Array<ArrayElement<Range<chrono::DateTime<chrono::Utc>>>>>>(i)
816 .map(|v| v.to_string()),
817 _ => match ty.oid() {
818 mz_pgrepr::oid::TYPE_UINT2_OID => {
819 row.get::<_, Option<UInt2>>(i).map(|x| x.0.to_string())
820 }
821 mz_pgrepr::oid::TYPE_UINT4_OID => {
822 row.get::<_, Option<UInt4>>(i).map(|x| x.0.to_string())
823 }
824 mz_pgrepr::oid::TYPE_UINT8_OID => {
825 row.get::<_, Option<UInt8>>(i).map(|x| x.0.to_string())
826 }
827 mz_pgrepr::oid::TYPE_MZ_TIMESTAMP_OID => {
828 row.get::<_, Option<MzTimestamp>>(i).map(|x| x.0)
829 }
830 _ => bail!("unsupported SQL type in testdrive: {:?}", ty),
831 },
832 }
833 .unwrap_or_else(|| "<null>".into());
834
835 raw_out.push(value.clone());
836 if let Some(regex) = &state.regex {
837 value = regex
838 .replace_all(&value, state.regex_replacement.as_str())
839 .to_string();
840 }
841
842 out.push(value);
843 }
844 let raw_out = if out != raw_out { Some(raw_out) } else { None };
845 Ok((out, raw_out))
846}
847
848struct MzTimestamp(String);
849
850impl<'a> FromSql<'a> for MzTimestamp {
851 fn from_sql(_: &Type, raw: &'a [u8]) -> Result<MzTimestamp, Box<dyn Error + Sync + Send>> {
852 Ok(MzTimestamp(std::str::from_utf8(raw)?.to_string()))
853 }
854
855 fn accepts(ty: &Type) -> bool {
856 ty.oid() == mz_pgrepr::oid::TYPE_MZ_TIMESTAMP_OID
857 }
858}
859
860#[allow(dead_code)]
861struct MzAclItem(String);
862
863impl<'a> FromSql<'a> for MzAclItem {
864 fn from_sql(_ty: &Type, raw: &'a [u8]) -> Result<Self, Box<dyn Error + Sync + Send>> {
865 Ok(MzAclItem(std::str::from_utf8(raw)?.to_string()))
866 }
867
868 fn accepts(ty: &Type) -> bool {
869 ty.oid() == mz_pgrepr::oid::TYPE_MZ_ACL_ITEM_OID
870 }
871}
872
873struct AclItem(String);
874
875impl<'a> FromSql<'a> for AclItem {
876 fn from_sql(_ty: &Type, raw: &'a [u8]) -> Result<Self, Box<dyn Error + Sync + Send>> {
877 Ok(AclItem(std::str::from_utf8(raw)?.to_string()))
878 }
879
880 fn accepts(ty: &Type) -> bool {
881 ty.oid() == 1033
882 }
883}
884
885struct TestdriveRow<'a>(&'a Vec<String>);
886
887impl Display for TestdriveRow<'_> {
888 fn fmt(&self, f: &mut Formatter) -> fmt::Result {
889 let mut cols = Vec::<String>::new();
890
891 for col_str in &self.0[0..self.0.len()] {
892 if col_str.contains(' ') || col_str.contains('"') || col_str.is_empty() {
893 cols.push(escape_for_parser(col_str));
894 } else {
895 cols.push(col_str.to_string());
896 }
897 }
898
899 write!(f, "{}", cols.join(" "))
900 }
901}