1use azul_css::{AzString, StringVec, U8Vec};
15
16#[repr(C, u8)]
20#[derive(Debug, Clone, PartialEq)]
21pub enum DbValue {
22 Null,
24 Integer(i64),
26 Real(f64),
28 Text(AzString),
30 Blob(U8Vec),
32}
33
34impl DbValue {
35 #[must_use] pub const fn is_null(&self) -> bool {
36 matches!(self, Self::Null)
37 }
38 #[must_use] pub const fn as_integer(&self) -> Option<i64> {
39 if let Self::Integer(i) = self {
40 Some(*i)
41 } else {
42 None
43 }
44 }
45 #[must_use] pub const fn as_real(&self) -> Option<f64> {
46 if let Self::Real(r) = self {
47 Some(*r)
48 } else {
49 None
50 }
51 }
52 #[must_use] pub const fn as_text(&self) -> Option<&AzString> {
53 if let Self::Text(t) = self {
54 Some(t)
55 } else {
56 None
57 }
58 }
59}
60
61impl_vec!(
62 DbValue,
63 DbValueVec,
64 DbValueVecDestructor,
65 DbValueVecDestructorType,
66 DbValueVecSlice,
67 OptionDbValue
68);
69impl_vec_debug!(DbValue, DbValueVec);
70impl_vec_clone!(DbValue, DbValueVec, DbValueVecDestructor);
71impl_vec_partialeq!(DbValue, DbValueVec);
72impl_option!(DbValue, OptionDbValue, copy = false, [Debug, Clone, PartialEq]);
73
74#[repr(C)]
78#[derive(Debug, Clone, PartialEq)]
79pub struct DbRows {
80 pub columns: StringVec,
82 pub values: DbValueVec,
84}
85
86impl DbRows {
87 #[must_use] pub fn num_columns(&self) -> usize {
89 self.columns.as_ref().len()
90 }
91 #[must_use] pub fn num_rows(&self) -> usize {
93 let cols = self.num_columns();
94 if cols == 0 {
95 0
96 } else {
97 self.values.as_ref().len() / cols
98 }
99 }
100 #[must_use] pub fn get(&self, row: usize, col: usize) -> Option<&DbValue> {
102 let cols = self.num_columns();
103 if col >= cols {
104 return None;
105 }
106 let idx = row.checked_mul(cols)?.checked_add(col)?;
110 self.values.as_ref().get(idx)
111 }
112}
113
114#[cfg(test)]
115mod tests {
116 use super::*;
117
118 #[test]
119 fn dbvalue_accessors() {
120 assert!(DbValue::Null.is_null());
121 assert_eq!(DbValue::Integer(7).as_integer(), Some(7));
122 assert_eq!(DbValue::Real(1.5).as_real(), Some(1.5));
123 assert_eq!(
124 DbValue::Text(AzString::from_const_str("hi")).as_text().map(AzString::as_str),
125 Some("hi")
126 );
127 assert_eq!(DbValue::Null.as_integer(), None);
129 assert!(!DbValue::Integer(0).is_null());
130 }
131
132 #[test]
133 fn dbrows_indexing() {
134 let columns = StringVec::from_vec(vec![
136 AzString::from_const_str("id"),
137 AzString::from_const_str("name"),
138 ]);
139 let values = DbValueVec::from_vec(vec![
140 DbValue::Integer(1),
141 DbValue::Text(AzString::from_const_str("alice")),
142 DbValue::Integer(2),
143 DbValue::Text(AzString::from_const_str("bob")),
144 ]);
145 let rows = DbRows { columns, values };
146
147 assert_eq!(rows.num_columns(), 2);
148 assert_eq!(rows.num_rows(), 2);
149 assert_eq!(rows.get(0, 0).and_then(DbValue::as_integer), Some(1));
150 assert_eq!(
151 rows.get(1, 1).and_then(|v| v.as_text()).map(AzString::as_str),
152 Some("bob")
153 );
154 assert!(rows.get(0, 2).is_none());
156 assert!(rows.get(2, 0).is_none());
157 }
158
159 #[test]
160 fn dbrows_empty() {
161 let rows = DbRows {
162 columns: StringVec::from_vec(vec![]),
163 values: DbValueVec::from_vec(vec![]),
164 };
165 assert_eq!(rows.num_columns(), 0);
166 assert_eq!(rows.num_rows(), 0);
167 assert!(rows.get(0, 0).is_none());
168 }
169}
170
171#[cfg(test)]
172mod autotest_generated {
173 use super::*;
180
181 fn cols(names: &[&'static str]) -> StringVec {
184 StringVec::from_vec(names.iter().copied().map(AzString::from_const_str).collect())
185 }
186
187 fn grid_2x2() -> DbRows {
189 DbRows {
190 columns: cols(&["a", "b"]),
191 values: DbValueVec::from_vec(vec![
192 DbValue::Integer(10),
193 DbValue::Integer(11),
194 DbValue::Integer(20),
195 DbValue::Integer(21),
196 ]),
197 }
198 }
199
200 fn all_variants() -> Vec<DbValue> {
202 vec![
203 DbValue::Null,
204 DbValue::Integer(0),
205 DbValue::Real(0.0),
206 DbValue::Text(AzString::from_const_str("t")),
207 DbValue::Blob(U8Vec::from_vec(vec![1, 2, 3])),
208 ]
209 }
210
211 #[test]
214 fn is_null_basic_true_false() {
215 assert!(DbValue::Null.is_null());
216 assert!(!DbValue::Integer(0).is_null());
217 }
218
219 #[test]
220 fn is_null_only_null_variant_is_null() {
221 for v in all_variants() {
222 let expect = matches!(v, DbValue::Null);
223 assert_eq!(v.is_null(), expect, "is_null mismatch for {v:?}");
224 }
225 assert!(!DbValue::Integer(i64::MIN).is_null());
227 assert!(!DbValue::Real(f64::NAN).is_null());
228 assert!(!DbValue::Blob(U8Vec::from_vec(vec![])).is_null());
229 }
230
231 #[test]
232 fn is_null_is_const_evaluable() {
233 const NULL_IS_NULL: bool = DbValue::Null.is_null();
236 const INT_IS_NULL: bool = DbValue::Integer(9).is_null();
237 const _: () = assert!(NULL_IS_NULL && !INT_IS_NULL);
238 }
239
240 #[test]
243 fn as_integer_extremes_round_trip() {
244 for &i in &[0i64, 1, -1, i64::MIN, i64::MIN + 1, i64::MAX - 1, i64::MAX] {
245 assert_eq!(DbValue::Integer(i).as_integer(), Some(i), "round-trip {i}");
246 }
247 }
248
249 #[test]
250 fn as_integer_wrong_variant_is_none() {
251 for v in all_variants() {
252 if matches!(v, DbValue::Integer(_)) {
253 continue;
254 }
255 assert_eq!(v.as_integer(), None, "expected None for {v:?}");
256 }
257 }
258
259 #[test]
260 fn as_integer_is_const_evaluable() {
261 const MIN_INT: Option<i64> = DbValue::Integer(i64::MIN).as_integer();
262 assert_eq!(MIN_INT, Some(i64::MIN));
263 }
264
265 #[test]
268 fn as_real_extremes_round_trip_bit_exact() {
269 for &r in &[
271 0.0f64,
272 -0.0,
273 1.0,
274 -1.0,
275 f64::MIN,
276 f64::MAX,
277 f64::MIN_POSITIVE,
278 f64::EPSILON,
279 f64::INFINITY,
280 f64::NEG_INFINITY,
281 ] {
282 assert_eq!(
283 DbValue::Real(r).as_real().map(f64::to_bits),
284 Some(r.to_bits()),
285 "bit-exact round-trip for {r:?}",
286 );
287 }
288 }
289
290 #[test]
291 fn as_real_nan_is_preserved_not_lost() {
292 let got = DbValue::Real(f64::NAN).as_real();
294 assert!(got.is_some());
295 assert!(got.unwrap().is_nan());
296 }
297
298 #[test]
299 fn as_real_negative_zero_keeps_sign() {
300 let got = DbValue::Real(-0.0).as_real().unwrap();
301 assert_eq!(got, 0.0);
302 assert!(got.is_sign_negative(), "-0.0 must stay negative-signed");
303 }
304
305 #[test]
306 fn as_real_wrong_variant_is_none() {
307 for v in all_variants() {
308 if matches!(v, DbValue::Real(_)) {
309 continue;
310 }
311 assert_eq!(v.as_real(), None, "expected None for {v:?}");
312 }
313 }
314
315 #[test]
318 fn as_text_empty_unicode_and_huge() {
319 assert_eq!(
321 DbValue::Text(AzString::from_const_str("")).as_text().map(AzString::as_str),
322 Some(""),
323 );
324 let tricky = "áé💥🔥\u{0}\u{FEFF}中文";
326 let v = DbValue::Text(AzString::from(tricky.to_string()));
327 assert_eq!(v.as_text().map(AzString::as_str), Some(tricky));
328 let huge = "x".repeat(200_000);
330 let v = DbValue::Text(AzString::from(huge.clone()));
331 assert_eq!(v.as_text().map(|s| s.as_str().len()), Some(huge.len()));
332 }
333
334 #[test]
335 fn as_text_wrong_variant_is_none() {
336 for v in all_variants() {
337 if matches!(v, DbValue::Text(_)) {
338 continue;
339 }
340 assert!(v.as_text().is_none(), "expected None for {v:?}");
341 }
342 }
343
344 #[test]
347 fn num_columns_empty_and_many() {
348 let empty = DbRows {
349 columns: StringVec::from_vec(vec![]),
350 values: DbValueVec::from_vec(vec![]),
351 };
352 assert_eq!(empty.num_columns(), 0);
353
354 let names: Vec<AzString> =
355 (0..1000).map(|_| AzString::from_const_str("c")).collect();
356 let wide = DbRows {
357 columns: StringVec::from_vec(names),
358 values: DbValueVec::from_vec(vec![]),
359 };
360 assert_eq!(wide.num_columns(), 1000);
361 }
362
363 #[test]
366 fn num_rows_zero_columns_never_divides_by_zero() {
367 let rows = DbRows {
370 columns: StringVec::from_vec(vec![]),
371 values: DbValueVec::from_vec(vec![DbValue::Null, DbValue::Integer(1)]),
372 };
373 assert_eq!(rows.num_rows(), 0);
374 assert!(rows.get(0, 0).is_none());
376 }
377
378 #[test]
379 fn num_rows_exact_and_ragged_truncates() {
380 assert_eq!(grid_2x2().num_rows(), 2);
382
383 let ragged = DbRows {
385 columns: cols(&["a", "b"]),
386 values: DbValueVec::from_vec(vec![
387 DbValue::Integer(1),
388 DbValue::Integer(2),
389 DbValue::Integer(3),
390 ]),
391 };
392 assert_eq!(ragged.num_rows(), 1);
393 assert!(ragged.get(1, 1).is_none());
395
396 let single = DbRows {
398 columns: cols(&["only"]),
399 values: DbValueVec::from_vec(vec![
400 DbValue::Integer(0),
401 DbValue::Integer(1),
402 DbValue::Integer(2),
403 ]),
404 };
405 assert_eq!(single.num_rows(), 3);
406 }
407
408 #[test]
411 fn get_zero_and_all_in_range_cells() {
412 let g = grid_2x2();
413 assert_eq!(g.get(0, 0).and_then(DbValue::as_integer), Some(10));
414 assert_eq!(g.get(0, 1).and_then(DbValue::as_integer), Some(11));
415 assert_eq!(g.get(1, 0).and_then(DbValue::as_integer), Some(20));
416 assert_eq!(g.get(1, 1).and_then(DbValue::as_integer), Some(21));
417 }
418
419 #[test]
420 fn get_out_of_range_column_is_none() {
421 let g = grid_2x2();
422 assert!(g.get(0, 2).is_none()); assert!(g.get(0, usize::MAX).is_none());
425 assert!(g.get(usize::MAX, usize::MAX).is_none());
427 }
428
429 #[test]
430 fn get_out_of_range_row_is_none() {
431 let g = grid_2x2();
432 assert!(g.get(2, 0).is_none());
433 assert!(g.get(1_000_000, 1).is_none());
434 }
435
436 #[test]
437 fn get_extreme_row_single_column_no_overflow() {
438 let single = DbRows {
441 columns: cols(&["only"]),
442 values: DbValueVec::from_vec(vec![DbValue::Integer(0)]),
443 };
444 assert!(single.get(usize::MAX, 0).is_none());
445 }
446
447 #[test]
448 fn get_on_empty_grid_is_none() {
449 let empty = DbRows {
450 columns: StringVec::from_vec(vec![]),
451 values: DbValueVec::from_vec(vec![]),
452 };
453 assert!(empty.get(0, 0).is_none());
454 assert!(empty.get(usize::MAX, 0).is_none());
455 }
456
457 #[test]
458 fn get_extreme_row_multi_column_never_yields_bogus_cell() {
459 let g = grid_2x2();
465 let outcome = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
466 g.get(usize::MAX, 0).cloned()
467 }));
468 match outcome {
469 Ok(v) => assert!(v.is_none(), "overflowed index must not map to a real cell, got {v:?}"),
470 Err(_) => { }
471 }
472 }
473
474 #[test]
475 fn get_largest_non_overflowing_row_is_none_not_a_panic() {
476 let row = usize::MAX / 2;
481 assert_eq!(
482 row.checked_mul(2).and_then(|x| x.checked_add(1)),
483 Some(usize::MAX),
484 "precondition: this row is the exact non-overflowing boundary",
485 );
486 let g = grid_2x2();
487 assert!(g.get(row, 0).is_none());
488 assert!(g.get(row, 1).is_none());
489 }
490
491 #[test]
494 fn eq_nan_never_equals_itself_but_survives_the_clone() {
495 let nan = DbValue::Real(f64::NAN);
497 let nan_clone = nan.clone();
498 assert_ne!(nan, nan_clone, "NaN must not compare equal to itself");
499
500 let v = DbValueVec::from_vec(vec![DbValue::Real(f64::NAN)]);
501 let v_clone = v.clone();
502 assert_ne!(v, v_clone, "a vec holding NaN is not equal to its own clone");
503 assert!(v_clone.as_slice()[0].as_real().unwrap().is_nan());
505 }
506
507 #[test]
508 fn eq_signed_zero_and_distinct_storage_classes() {
509 assert_eq!(DbValue::Real(0.0), DbValue::Real(-0.0));
511 assert_ne!(DbValue::Real(0.0).as_real().map(f64::to_bits), DbValue::Real(-0.0).as_real().map(f64::to_bits));
512
513 assert_ne!(DbValue::Integer(1), DbValue::Real(1.0));
515 assert_ne!(DbValue::Null, DbValue::Integer(0));
516 assert_ne!(DbValue::Text(AzString::from_const_str("1")), DbValue::Integer(1));
517 assert_ne!(
518 DbValue::Text(AzString::from_const_str("ab")),
519 DbValue::Blob(U8Vec::from_vec(vec![b'a', b'b'])),
520 "Text and Blob with identical bytes are still distinct storage classes",
521 );
522 assert_eq!(DbValue::Integer(i64::MIN), DbValue::Integer(i64::MIN));
524 }
525
526 #[test]
529 fn blob_is_opaque_to_every_getter_and_round_trips_bit_exact() {
530 let bytes: Vec<u8> = (0..=255u8).cycle().take(70_000).collect();
532 let b = DbValue::Blob(U8Vec::from_vec(bytes.clone()));
533
534 assert!(!b.is_null());
537 assert_eq!(b.as_integer(), None);
538 assert_eq!(b.as_real(), None);
539 assert!(b.as_text().is_none());
540
541 match b.clone() {
543 DbValue::Blob(v) => assert_eq!(v.as_slice(), bytes.as_slice()),
544 other => panic!("clone changed the variant: {other:?}"),
545 }
546
547 let empty = DbValue::Blob(U8Vec::from_vec(vec![]));
549 assert!(!empty.is_null());
550 assert_eq!(empty.as_integer(), None);
551 assert_ne!(empty, DbValue::Null);
552 }
553
554 fn extreme_values() -> Vec<DbValue> {
558 vec![
559 DbValue::Null,
560 DbValue::Integer(i64::MIN),
561 DbValue::Integer(i64::MAX),
562 DbValue::Real(f64::NEG_INFINITY),
563 DbValue::Real(f64::MIN_POSITIVE),
564 DbValue::Real(-0.0),
565 DbValue::Text(AzString::from(String::new())),
566 DbValue::Text(AzString::from("nul\u{0}\u{FEFF}émoji💥中文".to_string())),
567 DbValue::Blob(U8Vec::from_vec(vec![])),
568 DbValue::Blob(U8Vec::from_vec((0..=255u8).collect())),
569 ]
570 }
571
572 #[test]
573 fn dbvaluevec_round_trip_preserves_order_and_payloads() {
574 let original = extreme_values();
575 let encoded = DbValueVec::from_vec(original.clone());
576
577 assert_eq!(encoded.len(), original.len());
578 assert!(!encoded.is_empty());
579 assert_eq!(encoded.as_slice(), original.as_slice(), "decode(encode(v)) != v");
580
581 for (i, (got, want)) in encoded.as_slice().iter().zip(original.iter()).enumerate() {
583 assert_eq!(got, want, "cell {i} changed across the vec round-trip");
584 }
585
586 let decoded = DbValueVec::from_vec(original.clone()).into_library_owned_vec();
588 assert_eq!(decoded, original);
589 }
590
591 #[test]
592 fn dbvaluevec_empty_is_safe_to_read() {
593 for v in [DbValueVec::new(), DbValueVec::default(), DbValueVec::from_vec(vec![])] {
595 assert_eq!(v.len(), 0);
596 assert!(v.is_empty());
597 assert_eq!(v.as_slice(), &[] as &[DbValue]);
598 assert!(v.get(0).is_none());
599 assert!(v.get(usize::MAX).is_none());
600 assert!(v.c_get(0).is_none());
601 assert_eq!(v.iter().count(), 0);
602 }
603 }
604
605 #[test]
606 fn dbvaluevec_c_get_agrees_with_get_and_declines_out_of_range() {
607 let vals = extreme_values();
608 let v = DbValueVec::from_vec(vals.clone());
609 for (i, want) in vals.iter().enumerate() {
610 assert!(v.c_get(i).is_some(), "c_get({i}) should be Some");
611 assert_eq!(v.c_get(i).into_option().as_ref(), Some(want), "c_get({i})");
612 assert_eq!(v.get(i), Some(want), "get({i})");
613 }
614 assert!(v.c_get(vals.len()).is_none());
616 assert!(v.c_get(usize::MAX).is_none());
617 assert!(v.get(usize::MAX).is_none());
618 }
619
620 #[test]
621 fn option_dbvalue_round_trips_through_option() {
622 let inner = DbValue::Text(AzString::from("💥".to_string()));
623
624 let some: OptionDbValue = Some(inner.clone()).into();
625 assert!(some.is_some());
626 assert!(!some.is_none());
627 assert_eq!(some.as_ref(), Some(&inner));
628 assert_eq!(Option::<DbValue>::from(some.clone()), Some(inner.clone()));
629 assert_eq!(some.into_option(), Some(inner));
630
631 let none: OptionDbValue = Option::<DbValue>::None.into();
632 assert!(none.is_none());
633 assert_eq!(none.into_option(), None);
634 assert_eq!(OptionDbValue::default().into_option(), None);
635
636 let some_null: OptionDbValue = Some(DbValue::Null).into();
638 assert!(some_null.is_some());
639 assert_ne!(some_null, OptionDbValue::None);
640 }
641
642 #[test]
645 fn dbrows_clone_is_deep_and_outlives_the_original() {
646 let rows = DbRows {
647 columns: cols(&["id", "name"]),
648 values: DbValueVec::from_vec(vec![
649 DbValue::Integer(-1),
650 DbValue::Text(AzString::from("héllo 💥".to_string())),
651 ]),
652 };
653 let copy = rows.clone();
654 assert_eq!(copy, rows);
655
656 drop(rows);
659 assert_eq!(copy.num_columns(), 2);
660 assert_eq!(copy.num_rows(), 1);
661 assert_eq!(copy.get(0, 0).and_then(DbValue::as_integer), Some(-1));
662 assert_eq!(
663 copy.get(0, 1).and_then(|v| v.as_text()).map(AzString::as_str),
664 Some("héllo 💥"),
665 );
666
667 let copy2 = copy.clone();
669 drop(copy);
670 assert_eq!(
671 copy2.get(0, 1).and_then(|v| v.as_text()).map(AzString::as_str),
672 Some("héllo 💥"),
673 );
674 }
675
676 #[test]
677 fn dbrows_equality_is_structural() {
678 assert_eq!(grid_2x2(), grid_2x2());
679
680 let renamed = DbRows { columns: cols(&["a", "z"]), ..grid_2x2() };
682 assert_ne!(renamed, grid_2x2());
683
684 let reordered = DbRows {
686 columns: cols(&["a", "b"]),
687 values: DbValueVec::from_vec(vec![
688 DbValue::Integer(20),
689 DbValue::Integer(21),
690 DbValue::Integer(10),
691 DbValue::Integer(11),
692 ]),
693 };
694 assert_ne!(reordered, grid_2x2());
695
696 let reshaped = DbRows { columns: cols(&["a"]), ..grid_2x2() };
698 assert_ne!(reshaped, grid_2x2());
699 assert_eq!(reshaped.num_rows(), 4);
700 }
701
702 #[test]
703 fn dbrows_from_default_collections_is_an_empty_grid() {
704 let rows = DbRows { columns: StringVec::default(), values: DbValueVec::default() };
705 assert_eq!(rows.num_columns(), 0);
706 assert_eq!(rows.num_rows(), 0);
707 assert!(rows.get(0, 0).is_none());
708 assert!(rows.get(usize::MAX, usize::MAX).is_none());
709 assert_eq!(
710 rows,
711 DbRows {
712 columns: StringVec::from_vec(vec![]),
713 values: DbValueVec::from_vec(vec![]),
714 },
715 );
716 }
717
718 fn grid(cols_n: usize, len: usize) -> (DbRows, Vec<DbValue>) {
721 let names: Vec<AzString> = (0..cols_n).map(|_| AzString::from_const_str("c")).collect();
722 let cells: Vec<DbValue> = (0..len).map(|i| DbValue::Integer(i as i64)).collect();
723 let rows = DbRows {
724 columns: StringVec::from_vec(names),
725 values: DbValueVec::from_vec(cells.clone()),
726 };
727 (rows, cells)
728 }
729
730 #[test]
731 fn get_agrees_with_the_row_major_flat_index_for_every_shape() {
732 for cols_n in 1..=5usize {
733 for len in 0..=17usize {
734 let (rows, cells) = grid(cols_n, len);
735 assert_eq!(rows.num_columns(), cols_n);
736 assert_eq!(rows.num_rows(), len / cols_n, "cols={cols_n} len={len}");
737
738 for r in 0..rows.num_rows() {
740 for c in 0..cols_n {
741 assert_eq!(
742 rows.get(r, c),
743 Some(&cells[r * cols_n + c]),
744 "({r},{c}) cols={cols_n} len={len}",
745 );
746 }
747 }
748
749 let tail = rows.num_rows();
753 for c in 0..cols_n {
754 assert_eq!(
755 rows.get(tail, c),
756 cells.get(tail * cols_n + c),
757 "ragged tail ({tail},{c}) cols={cols_n} len={len}",
758 );
759 }
760
761 assert!(rows.get(0, cols_n).is_none());
763 assert!(rows.get(rows.num_rows().saturating_sub(1), cols_n).is_none());
764 }
765 }
766 }
767
768 #[test]
769 fn num_rows_times_num_columns_never_exceeds_the_cell_count() {
770 for cols_n in 0..=6usize {
771 for len in 0..=20usize {
772 let (rows, _) = grid(cols_n, len);
773 let (r, c) = (rows.num_rows(), rows.num_columns());
774
775 let covered = r.checked_mul(c).expect("row×col count must not overflow");
776 assert!(covered <= len, "claims {covered} cells but only {len} exist");
777 if c > 0 {
778 assert!(len - covered < c, "dropped a whole row: cols={c} len={len}");
780 }
781 }
782 }
783 }
784
785 #[test]
786 fn more_columns_than_values_yields_zero_rows_and_no_bogus_cells() {
787 let rows = DbRows {
788 columns: cols(&["a", "b", "c"]),
789 values: DbValueVec::from_vec(vec![DbValue::Integer(1), DbValue::Null]),
790 };
791 assert_eq!(rows.num_columns(), 3);
792 assert_eq!(rows.num_rows(), 0, "floor(2/3) == 0: no complete row exists");
793
794 assert_eq!(rows.get(0, 0).and_then(DbValue::as_integer), Some(1));
796 assert!(matches!(rows.get(0, 1), Some(DbValue::Null)));
797 assert!(rows.get(0, 2).is_none());
799 assert!(rows.get(1, 0).is_none());
800 }
801
802 #[test]
803 fn num_columns_counts_names_verbatim_including_duplicates_and_unicode() {
804 let rows = DbRows {
805 columns: StringVec::from_vec(vec![
806 AzString::from_const_str("dup"),
807 AzString::from_const_str("dup"), AzString::from_const_str(""), AzString::from("列💥\u{0}".to_string()),
810 ]),
811 values: DbValueVec::from_vec(vec![]),
812 };
813 assert_eq!(rows.num_columns(), 4);
814 assert_eq!(rows.num_rows(), 0);
815 assert!(rows.get(0, 3).is_none()); }
817}