1use std::collections::HashMap;
54use sz_orm_model::Value;
55
56#[derive(Debug, Clone, PartialEq)]
58pub enum QueryError {
59 ColumnCountMismatch {
61 expected: usize,
63 actual: usize,
65 },
66 TypeMismatch {
68 column: std::borrow::Cow<'static, str>,
70 expected: &'static str,
72 },
73 MissingColumn {
75 column: &'static str,
77 },
78 Custom(String),
80}
81
82impl std::fmt::Display for QueryError {
83 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
84 match self {
85 QueryError::ColumnCountMismatch { expected, actual } => {
86 write!(f, "列数不匹配: 期望 {}, 实际 {}", expected, actual)
87 }
88 QueryError::TypeMismatch { column, expected } => {
89 write!(f, "列 {:?} 类型不匹配, 期望 {}", column, expected)
90 }
91 QueryError::MissingColumn { column } => {
92 write!(f, "缺少列: {}", column)
93 }
94 QueryError::Custom(msg) => write!(f, "{}", msg),
95 }
96 }
97}
98
99impl std::error::Error for QueryError {}
100
101#[derive(Debug, Clone)]
106pub struct RowDesc {
107 pub columns: Vec<String>,
109}
110
111impl RowDesc {
112 pub fn new(columns: Vec<String>) -> Self {
114 Self { columns }
115 }
116
117 pub fn len(&self) -> usize {
119 self.columns.len()
120 }
121
122 pub fn is_empty(&self) -> bool {
124 self.columns.is_empty()
125 }
126
127 pub fn index_of(&self, name: &str) -> Option<usize> {
129 self.columns.iter().position(|c| c == name)
130 }
131}
132
133pub trait Queryable: Sized {
138 fn from_values(values: Vec<Value>) -> Result<Self, QueryError>;
140
141 fn from_values_with_desc(values: Vec<Value>, desc: &RowDesc) -> Result<Self, QueryError> {
143 if values.len() != desc.len() {
144 return Err(QueryError::ColumnCountMismatch {
145 expected: desc.len(),
146 actual: values.len(),
147 });
148 }
149 Self::from_values(values)
150 }
151}
152
153pub trait FromRow: Sized {
158 fn from_row(row: HashMap<String, Value>) -> Result<Self, QueryError>;
160}
161
162impl Queryable for Value {
166 fn from_values(values: Vec<Value>) -> Result<Self, QueryError> {
167 if values.len() != 1 {
168 return Err(QueryError::ColumnCountMismatch {
169 expected: 1,
170 actual: values.len(),
171 });
172 }
173 Ok(values[0].clone())
174 }
175}
176
177impl Queryable for (Value, Value) {
179 fn from_values(values: Vec<Value>) -> Result<Self, QueryError> {
180 if values.len() != 2 {
181 return Err(QueryError::ColumnCountMismatch {
182 expected: 2,
183 actual: values.len(),
184 });
185 }
186 Ok((values[0].clone(), values[1].clone()))
187 }
188}
189
190impl Queryable for (Value, Value, Value) {
192 fn from_values(values: Vec<Value>) -> Result<Self, QueryError> {
193 if values.len() != 3 {
194 return Err(QueryError::ColumnCountMismatch {
195 expected: 3,
196 actual: values.len(),
197 });
198 }
199 Ok((values[0].clone(), values[1].clone(), values[2].clone()))
200 }
201}
202
203pub fn value_as_i64(v: &Value) -> Option<i64> {
207 v.as_i64()
208}
209
210pub fn value_as_f64(v: &Value) -> Option<f64> {
212 v.as_f64()
213}
214
215pub fn value_as_string(v: &Value) -> Option<String> {
217 v.as_str().map(|s| s.to_string())
218}
219
220pub fn value_as_bool(v: &Value) -> Option<bool> {
222 v.as_bool()
223}
224
225pub fn value_as_nullable_i64(v: &Value) -> Option<i64> {
227 if v.is_null() {
228 None
229 } else {
230 v.as_i64()
231 }
232}
233
234pub fn value_as_nullable_string(v: &Value) -> Option<String> {
236 if v.is_null() {
237 None
238 } else {
239 v.as_str().map(|s| s.to_string())
240 }
241}
242
243#[cfg(test)]
244mod tests {
245 use super::*;
246
247 #[derive(Debug, Default, PartialEq)]
250 struct UserRow {
251 id: i64,
252 name: String,
253 }
254
255 impl Queryable for UserRow {
256 fn from_values(values: Vec<Value>) -> Result<Self, QueryError> {
257 if values.len() != 2 {
258 return Err(QueryError::ColumnCountMismatch {
259 expected: 2,
260 actual: values.len(),
261 });
262 }
263 let id = values[0].as_i64().ok_or(QueryError::TypeMismatch {
264 column: "0".into(),
265 expected: "i64",
266 })?;
267 let name = values[1]
268 .as_str()
269 .ok_or(QueryError::TypeMismatch {
270 column: "1".into(),
271 expected: "String",
272 })?
273 .to_string();
274 Ok(UserRow { id, name })
275 }
276 }
277
278 impl FromRow for UserRow {
279 fn from_row(row: HashMap<String, Value>) -> Result<Self, QueryError> {
280 let id = row
281 .get("id")
282 .ok_or(QueryError::MissingColumn { column: "id" })?
283 .as_i64()
284 .ok_or(QueryError::TypeMismatch {
285 column: "id".into(),
286 expected: "i64",
287 })?;
288 let name = row
289 .get("name")
290 .ok_or(QueryError::MissingColumn { column: "name" })?
291 .as_str()
292 .ok_or(QueryError::TypeMismatch {
293 column: "name".into(),
294 expected: "String",
295 })?
296 .to_string();
297 Ok(UserRow { id, name })
298 }
299 }
300
301 #[test]
304 fn test_query_error_display() {
305 let e = QueryError::ColumnCountMismatch {
306 expected: 3,
307 actual: 2,
308 };
309 assert!(format!("{}", e).contains("3"));
310 assert!(format!("{}", e).contains("2"));
311
312 let e = QueryError::TypeMismatch {
313 column: "age".into(),
314 expected: "i64",
315 };
316 assert!(format!("{}", e).contains("age"));
317
318 let e = QueryError::MissingColumn { column: "id" };
319 assert!(format!("{}", e).contains("id"));
320
321 let e = QueryError::Custom("custom".into());
322 assert_eq!(format!("{}", e), "custom");
323 }
324
325 #[test]
328 fn test_row_desc_basic() {
329 let desc = RowDesc::new(vec!["id".into(), "name".into(), "age".into()]);
330 assert_eq!(desc.len(), 3);
331 assert!(!desc.is_empty());
332 assert_eq!(desc.index_of("name"), Some(1));
333 assert_eq!(desc.index_of("missing"), None);
334 }
335
336 #[test]
337 fn test_row_desc_empty() {
338 let desc = RowDesc::new(vec![]);
339 assert!(desc.is_empty());
340 assert_eq!(desc.len(), 0);
341 }
342
343 #[test]
346 fn test_user_row_from_values_success() {
347 let row =
348 UserRow::from_values(vec![Value::I64(42), Value::String("Alice".into())]).unwrap();
349 assert_eq!(row.id, 42);
350 assert_eq!(row.name, "Alice");
351 }
352
353 #[test]
354 fn test_user_row_from_values_count_mismatch() {
355 let result = UserRow::from_values(vec![Value::I64(42)]);
356 assert!(matches!(
357 result,
358 Err(QueryError::ColumnCountMismatch {
359 expected: 2,
360 actual: 1
361 })
362 ));
363 }
364
365 #[test]
366 fn test_user_row_from_values_type_mismatch() {
367 let result = UserRow::from_values(vec![
368 Value::String("not_an_int".into()),
369 Value::String("Alice".into()),
370 ]);
371 assert!(matches!(result, Err(QueryError::TypeMismatch { .. })));
372 }
373
374 #[test]
375 fn test_user_row_from_values_with_desc() {
376 let desc = RowDesc::new(vec!["id".into(), "name".into()]);
377 let row =
378 UserRow::from_values_with_desc(vec![Value::I64(1), Value::String("Bob".into())], &desc)
379 .unwrap();
380 assert_eq!(row.id, 1);
381 assert_eq!(row.name, "Bob");
382 }
383
384 #[test]
385 fn test_user_row_from_values_with_desc_mismatch() {
386 let desc = RowDesc::new(vec!["id".into(), "name".into(), "age".into()]);
387 let result =
388 UserRow::from_values_with_desc(vec![Value::I64(1), Value::String("Bob".into())], &desc);
389 assert!(matches!(
390 result,
391 Err(QueryError::ColumnCountMismatch { .. })
392 ));
393 }
394
395 #[test]
398 fn test_user_row_from_row_success() {
399 let mut map = HashMap::new();
400 map.insert("id".into(), Value::I64(99));
401 map.insert("name".into(), Value::String("Charlie".into()));
402 let row = UserRow::from_row(map).unwrap();
403 assert_eq!(row.id, 99);
404 assert_eq!(row.name, "Charlie");
405 }
406
407 #[test]
408 fn test_user_row_from_row_missing_column() {
409 let mut map = HashMap::new();
410 map.insert("id".into(), Value::I64(99));
411 let result = UserRow::from_row(map);
413 assert!(matches!(
414 result,
415 Err(QueryError::MissingColumn { column: "name" })
416 ));
417 }
418
419 #[test]
420 fn test_user_row_from_row_extra_columns_ignored() {
421 let mut map = HashMap::new();
422 map.insert("id".into(), Value::I64(1));
423 map.insert("name".into(), Value::String("X".into()));
424 map.insert("extra".into(), Value::String("ignored".into()));
425 let row = UserRow::from_row(map).unwrap();
426 assert_eq!(row.id, 1);
427 }
428
429 #[test]
432 fn test_value_queryable_single() {
433 let v = Value::from_values(vec![Value::I64(42)]).unwrap();
434 assert_eq!(v.as_i64(), Some(42));
435 }
436
437 #[test]
438 fn test_value_queryable_count_mismatch() {
439 let result = Value::from_values(vec![Value::I64(1), Value::I64(2)]);
440 assert!(matches!(
441 result,
442 Err(QueryError::ColumnCountMismatch { .. })
443 ));
444 }
445
446 #[test]
447 fn test_tuple_2_queryable() {
448 let (a, b) =
449 <(Value, Value)>::from_values(vec![Value::I64(1), Value::String("hello".into())])
450 .unwrap();
451 assert_eq!(a.as_i64(), Some(1));
452 assert_eq!(b.as_str(), Some("hello"));
453 }
454
455 #[test]
456 fn test_tuple_3_queryable() {
457 let (a, b, c) = <(Value, Value, Value)>::from_values(vec![
458 Value::I64(1),
459 Value::String("two".into()),
460 Value::F64(3.5),
461 ])
462 .unwrap();
463 assert_eq!(a.as_i64(), Some(1));
464 assert_eq!(b.as_str(), Some("two"));
465 assert_eq!(c.as_f64(), Some(3.5));
466 }
467
468 #[test]
471 fn test_value_helpers() {
472 assert_eq!(value_as_i64(&Value::I64(42)), Some(42));
473 assert_eq!(value_as_i64(&Value::String("42".into())), Some(42));
474 assert_eq!(value_as_f64(&Value::F64(3.5)), Some(3.5));
475 assert_eq!(
476 value_as_string(&Value::String("hi".into())),
477 Some("hi".into())
478 );
479 assert_eq!(value_as_bool(&Value::Bool(true)), Some(true));
480 }
481
482 #[test]
483 fn test_nullable_helpers() {
484 assert_eq!(value_as_nullable_i64(&Value::Null), None);
485 assert_eq!(value_as_nullable_i64(&Value::I64(42)), Some(42));
486 assert_eq!(value_as_nullable_string(&Value::Null), None);
487 assert_eq!(
488 value_as_nullable_string(&Value::String("hi".into())),
489 Some("hi".into())
490 );
491 }
492
493 #[test]
496 fn test_full_flow_queryable() {
497 let values = vec![Value::I64(1), Value::String("Alice".into())];
499 let row = UserRow::from_values(values).unwrap();
500 assert_eq!(
501 row,
502 UserRow {
503 id: 1,
504 name: "Alice".into()
505 }
506 );
507 }
508
509 #[test]
510 fn test_full_flow_from_row_with_extra_data() {
511 let mut map = HashMap::new();
513 map.insert("id".into(), Value::I64(7));
514 map.insert("name".into(), Value::String("Bob".into()));
515 map.insert("email".into(), Value::String("bob@example.com".into()));
516 map.insert("created_at".into(), Value::String("2026-01-01".into()));
517
518 let row = UserRow::from_row(map).unwrap();
519 assert_eq!(row.id, 7);
520 assert_eq!(row.name, "Bob");
521 }
522}