|
| 1 | +use clickhouse::Row; |
| 2 | +use serde::{Deserialize, Serialize}; |
| 3 | + |
| 4 | +#[derive(Row, Deserialize, Debug)] |
| 5 | +struct PersonName<'a> { |
| 6 | + name: &'a str, |
| 7 | +} |
| 8 | + |
| 9 | +#[derive(Row, Deserialize, Debug)] |
| 10 | +struct PersonInfo { |
| 11 | + name: String, |
| 12 | + age: u32, |
| 13 | +} |
| 14 | + |
| 15 | +#[tokio::test] |
| 16 | +async fn verify_raw_query_basic_functionality() { |
| 17 | + let client = prepare_database!(); |
| 18 | + |
| 19 | + // The key test: verify that ? characters don't cause binding errors |
| 20 | + let result = client |
| 21 | + .query_raw("SELECT 1 WHERE 'test?' = 'test?'") |
| 22 | + .fetch_bytes("TSV") |
| 23 | + .unwrap(); |
| 24 | + |
| 25 | + let mut data = Vec::new(); |
| 26 | + let mut cursor = result; |
| 27 | + while let Some(chunk) = cursor.next().await.unwrap() { |
| 28 | + data.extend_from_slice(&chunk); |
| 29 | + } |
| 30 | + let response = String::from_utf8(data).unwrap(); |
| 31 | + |
| 32 | + // Should return "1\n" - proving the query executed successfully |
| 33 | + assert_eq!(response.trim(), "1"); |
| 34 | + |
| 35 | + // Contrast: regular query with ? should fail |
| 36 | + let regular_result = client |
| 37 | + .query("SELECT 1 WHERE 'test?' = 'test?'") |
| 38 | + .fetch_bytes("TSV"); |
| 39 | + |
| 40 | + // This should fail because ? is treated as a bind parameter |
| 41 | + assert!(regular_result.is_err()); |
| 42 | + if let Err(error) = regular_result { |
| 43 | + let error_msg = error.to_string(); |
| 44 | + assert!(error_msg.contains("unbound")); |
| 45 | + } |
| 46 | +} |
| 47 | + |
| 48 | +#[tokio::test] |
| 49 | +async fn fetch_with_single_field_struct() { |
| 50 | + let client = prepare_database!(); |
| 51 | + |
| 52 | + client |
| 53 | + .query("CREATE TABLE test_users(name String) ENGINE = Memory") |
| 54 | + .execute() |
| 55 | + .await |
| 56 | + .unwrap(); |
| 57 | + |
| 58 | + client |
| 59 | + .query_raw("INSERT INTO test_users VALUES ('Alice?'), ('Bob??'), ('Charlie???')") |
| 60 | + .execute() |
| 61 | + .await |
| 62 | + .unwrap(); |
| 63 | + |
| 64 | + // Test raw query with struct fetching |
| 65 | + let sql = "SELECT name FROM test_users ORDER BY name"; |
| 66 | + |
| 67 | + let mut cursor = client.query_raw(sql).fetch::<PersonName<'_>>().unwrap(); |
| 68 | + |
| 69 | + let mut names = Vec::new(); |
| 70 | + while let Some(PersonName { name }) = cursor.next().await.unwrap() { |
| 71 | + names.push(name.to_string()); |
| 72 | + } |
| 73 | + |
| 74 | + assert_eq!(names, vec!["Alice?", "Bob??", "Charlie???"]); |
| 75 | +} |
| 76 | + |
| 77 | +#[tokio::test] |
| 78 | +async fn fetch_with_multi_field_struct() { |
| 79 | + let client = prepare_database!(); |
| 80 | + |
| 81 | + // Create a test table |
| 82 | + client |
| 83 | + .query("CREATE TABLE test_persons(name String, age UInt32) ENGINE = Memory") |
| 84 | + .execute() |
| 85 | + .await |
| 86 | + .unwrap(); |
| 87 | + |
| 88 | + // Insert test data with question marks in names |
| 89 | + client |
| 90 | + .query_raw("INSERT INTO test_persons VALUES ('What?', 25), ('How??', 30), ('Why???', 35)") |
| 91 | + .execute() |
| 92 | + .await |
| 93 | + .unwrap(); |
| 94 | + |
| 95 | + // Test raw query with multi-field struct |
| 96 | + let sql = "SELECT name, age FROM test_persons ORDER BY age"; |
| 97 | + |
| 98 | + let mut cursor = client.query_raw(sql).fetch::<PersonInfo>().unwrap(); |
| 99 | + |
| 100 | + let mut persons = Vec::new(); |
| 101 | + while let Some(person) = cursor.next().await.unwrap() { |
| 102 | + persons.push((person.name.clone(), person.age)); |
| 103 | + } |
| 104 | + |
| 105 | + assert_eq!( |
| 106 | + persons, |
| 107 | + vec![ |
| 108 | + ("What?".to_string(), 25), |
| 109 | + ("How??".to_string(), 30), |
| 110 | + ("Why???".to_string(), 35) |
| 111 | + ] |
| 112 | + ); |
| 113 | +} |
| 114 | + |
| 115 | +#[tokio::test] |
| 116 | +async fn compare_raw_vs_regular_query_with_structs() { |
| 117 | + let client = prepare_database!(); |
| 118 | + |
| 119 | + client |
| 120 | + .query("CREATE TABLE test_comparison(name String) ENGINE = Memory") |
| 121 | + .execute() |
| 122 | + .await |
| 123 | + .unwrap(); |
| 124 | + |
| 125 | + client |
| 126 | + .query_raw("INSERT INTO test_comparison VALUES ('Alice?')") |
| 127 | + .execute() |
| 128 | + .await |
| 129 | + .unwrap(); |
| 130 | + |
| 131 | + // Regular query with ? should fail due to unbound parameter |
| 132 | + let regular_result = client |
| 133 | + .query("SELECT name FROM test_comparison WHERE name = 'Alice?'") |
| 134 | + .fetch::<PersonName<'_>>(); |
| 135 | + |
| 136 | + assert!(regular_result.is_err()); |
| 137 | + if let Err(error) = regular_result { |
| 138 | + let error_msg = error.to_string(); |
| 139 | + assert!(error_msg.contains("unbound")); |
| 140 | + } |
| 141 | + |
| 142 | + // Raw query with ? should succeed ) |
| 143 | + let raw_result = client |
| 144 | + .query_raw("SELECT name FROM test_comparison WHERE name = 'Alice?'") |
| 145 | + .fetch::<PersonName<'_>>() |
| 146 | + .unwrap(); |
| 147 | + |
| 148 | + let mut names = Vec::new(); |
| 149 | + let mut cursor = raw_result; |
| 150 | + while let Some(PersonName { name }) = cursor.next().await.unwrap() { |
| 151 | + names.push(name.to_string()); |
| 152 | + } |
| 153 | + |
| 154 | + assert_eq!(names, vec!["Alice?"]); |
| 155 | +} |
| 156 | + |
| 157 | +#[tokio::test] |
| 158 | +async fn mixed_question_mark() { |
| 159 | + let client = prepare_database!(); |
| 160 | + |
| 161 | + // Test various question mark patterns with bytes fetch to avoid format issues |
| 162 | + let patterns = vec![ |
| 163 | + ("SELECT 1 WHERE 'test?' = 'test?'", "?"), |
| 164 | + ("SELECT 2 WHERE 'test??' = 'test??'", "??"), |
| 165 | + ("SELECT 3 WHERE 'test???' = 'test???'", "???"), |
| 166 | + ( |
| 167 | + "SELECT 4 WHERE 'What? How?? Why???' = 'What? How?? Why???'", |
| 168 | + "mixed", |
| 169 | + ), |
| 170 | + ]; |
| 171 | + |
| 172 | + for (sql, pattern_type) in patterns { |
| 173 | + let result = client.query_raw(sql).fetch_bytes("TSV").unwrap(); |
| 174 | + |
| 175 | + let mut data = Vec::new(); |
| 176 | + let mut cursor = result; |
| 177 | + while let Some(chunk) = cursor.next().await.unwrap() { |
| 178 | + data.extend_from_slice(&chunk); |
| 179 | + } |
| 180 | + let response = String::from_utf8(data).unwrap(); |
| 181 | + |
| 182 | + // Should return the expected number |
| 183 | + assert!( |
| 184 | + !response.trim().is_empty(), |
| 185 | + "Query should return data for pattern: {}", |
| 186 | + pattern_type |
| 187 | + ); |
| 188 | + } |
| 189 | +} |
| 190 | + |
| 191 | +#[tokio::test] |
| 192 | +async fn question_marks_in_comments() { |
| 193 | + let client = prepare_database!(); |
| 194 | + |
| 195 | + // Test question marks in SQL comments - should work without binding |
| 196 | + let result = client |
| 197 | + .query_raw("SELECT 1 /* What? How?? Why??? */ WHERE 1=1") |
| 198 | + .fetch_bytes("TSV") |
| 199 | + .unwrap(); |
| 200 | + |
| 201 | + let mut data = Vec::new(); |
| 202 | + let mut cursor = result; |
| 203 | + while let Some(chunk) = cursor.next().await.unwrap() { |
| 204 | + data.extend_from_slice(&chunk); |
| 205 | + } |
| 206 | + let response = String::from_utf8(data).unwrap(); |
| 207 | + |
| 208 | + assert_eq!(response.trim(), "1"); |
| 209 | +} |
| 210 | + |
| 211 | +#[tokio::test] |
| 212 | +async fn contrast_with_regular_query() { |
| 213 | + let client = prepare_database!(); |
| 214 | + |
| 215 | + // This should fail with regular query because of unbound parameter |
| 216 | + let result = client |
| 217 | + .query("SELECT 1 WHERE 'test?' = 'test?'") |
| 218 | + .fetch_bytes("TSV"); |
| 219 | + |
| 220 | + // Regular query should fail due to unbound ? |
| 221 | + assert!(result.is_err()); |
| 222 | + if let Err(error) = result { |
| 223 | + let error_msg = error.to_string(); |
| 224 | + assert!(error_msg.contains("unbound")); |
| 225 | + } |
| 226 | + |
| 227 | + // But raw query should succeed |
| 228 | + let raw_result = client |
| 229 | + .query_raw("SELECT 1 WHERE 'test?' = 'test?'") |
| 230 | + .fetch_bytes("TSV") |
| 231 | + .unwrap(); |
| 232 | + |
| 233 | + let mut data = Vec::new(); |
| 234 | + let mut cursor = raw_result; |
| 235 | + while let Some(chunk) = cursor.next().await.unwrap() { |
| 236 | + data.extend_from_slice(&chunk); |
| 237 | + } |
| 238 | + let response = String::from_utf8(data).unwrap(); |
| 239 | + |
| 240 | + assert_eq!(response.trim(), "1"); |
| 241 | +} |
| 242 | + |
| 243 | +#[tokio::test] |
| 244 | +async fn complex_sql_with_question_marks() { |
| 245 | + use clickhouse::Row; |
| 246 | + use serde::{Deserialize, Serialize}; |
| 247 | + |
| 248 | + #[derive(Debug, Row, Serialize, Deserialize)] |
| 249 | + struct TestResult { |
| 250 | + question: String, |
| 251 | + confusion: String, |
| 252 | + bewilderment: String, |
| 253 | + answer: String, |
| 254 | + } |
| 255 | + |
| 256 | + let client = prepare_database!(); |
| 257 | + |
| 258 | + // Test a more complex SQL query with question marks in various contexts |
| 259 | + let sql = r#" |
| 260 | + SELECT |
| 261 | + 'What is this?' as question, |
| 262 | + 'How does this work??' as confusion, |
| 263 | + 'Why would you do this???' as bewilderment, |
| 264 | + CASE |
| 265 | + WHEN 1=1 THEN 'Yes?' |
| 266 | + ELSE 'No??' |
| 267 | + END as answer |
| 268 | + WHERE 'test?' LIKE '%?' |
| 269 | + "#; |
| 270 | + |
| 271 | + let result = client.query_raw(sql).fetch_one::<TestResult>().await; |
| 272 | + |
| 273 | + assert!(result.is_ok()); |
| 274 | + let row = result.unwrap(); |
| 275 | + assert_eq!(row.question, "What is this?"); |
| 276 | + assert_eq!(row.confusion, "How does this work??"); |
| 277 | + assert_eq!(row.bewilderment, "Why would you do this???"); |
| 278 | + assert_eq!(row.answer, "Yes?"); |
| 279 | +} |
| 280 | + |
| 281 | +#[tokio::test] |
| 282 | +async fn query_raw_preserves_exact_sql() { |
| 283 | + let client = prepare_database!(); |
| 284 | +//check client |
| 285 | + |
| 286 | + // Test that raw query preserves the exact SQL including whitespace and formatting |
| 287 | + let sql = "SELECT 1 WHERE 'test?' = 'test?' "; |
| 288 | + |
| 289 | + let result = client.query_raw(sql).fetch_bytes("TSV").unwrap(); |
| 290 | + |
| 291 | + let mut data = Vec::new(); |
| 292 | + let mut cursor = result; |
| 293 | + while let Some(chunk) = cursor.next().await.unwrap() { |
| 294 | + data.extend_from_slice(&chunk); |
| 295 | + } |
| 296 | + let response = String::from_utf8(data).unwrap(); |
| 297 | + |
| 298 | + assert_eq!(response.trim(), "1"); |
| 299 | +} |
0 commit comments