`COPY (SELECT ...) TO ' ' [WITH (...)]`
(trimmed: &str)
| 92 | |
| 93 | /// `COPY (SELECT ...) TO '<path>' [WITH (...)]` |
| 94 | fn parse_query_form(trimmed: &str) -> Result<NodedbStatement, SqlError> { |
| 95 | // Find the matching close-paren for the leading `(`. |
| 96 | let after_copy = trimmed["COPY ".len()..].trim_start(); |
| 97 | let close = find_matching_paren(after_copy).ok_or_else(|| SqlError::Parse { |
| 98 | detail: "COPY: unclosed parenthesis in query form".to_string(), |
| 99 | })?; |
| 100 | |
| 101 | let query = after_copy[1..close].trim().to_string(); |
| 102 | if query.is_empty() { |
| 103 | return Err(SqlError::Parse { |
| 104 | detail: "COPY: empty query in query form".to_string(), |
| 105 | }); |
| 106 | } |
| 107 | |
| 108 | // After the closing paren, expect " TO '<path>'" |
| 109 | let after_paren = after_copy[close + 1..].trim_start(); |
| 110 | let upper_after = after_paren.to_uppercase(); |
| 111 | if !upper_after.starts_with("TO ") { |
| 112 | return Err(SqlError::Parse { |
| 113 | detail: format!( |
| 114 | "COPY: expected TO after query, got: {}", |
| 115 | &after_paren[..after_paren.len().min(32)] |
| 116 | ), |
| 117 | }); |
| 118 | } |
| 119 | |
| 120 | let after_to = after_paren["TO ".len()..].trim_start(); |
| 121 | let (path, _rest, format, delimiter, header) = parse_path_and_opts(after_to)?; |
| 122 | |
| 123 | Ok(NodedbStatement::Misc(MiscStmt::CopyToFile { |
| 124 | source: CopyToSource::Query(query), |
| 125 | path, |
| 126 | format, |
| 127 | delimiter, |
| 128 | header, |
| 129 | })) |
| 130 | } |
| 131 | |
| 132 | type PathAndOpts<'a> = (String, &'a str, Option<CopyFormat>, Option<char>, bool); |
| 133 |
no test coverage detected