1#![forbid(unsafe_code)]
2#![warn(missing_docs)]
3
4mod cursor;
40mod error;
41pub mod middleware;
42mod migrator;
43pub mod params;
44mod pool;
45mod prepare;
46pub mod query;
47pub mod registry;
48mod row;
49pub mod schema;
50mod traits;
51mod value;
52mod warning;
53
54pub use cursor::Cursor;
55pub use error::OxiSqlError;
56#[cfg(feature = "tracing")]
57pub use middleware::TracingConnection;
58pub use middleware::{
59 ConnectionMetrics, LoggingConnection, MetricsConnection, MetricsSnapshot, RetryConnection,
60 RetryPolicy, RetryPredicate,
61};
62pub use migrator::{MigrationInfo, MigrationStatus, Migrator};
63pub use params::{bind_named_params, rewrite_named_params};
64pub use pool::ConnectionPool;
65pub use prepare::PreparedStatement;
66pub use query::{
67 BuiltQuery, DeleteBuilder, InsertBuilder, SelectBuilder, SortDirection, UpdateBuilder,
68};
69pub use registry::{SqlType, TypeRegistry};
70pub use row::{FromValue, Row, RowSet};
71pub use schema::{ColumnInfo, ForeignKeyInfo, IndexInfo, TableInfo, TableType};
72pub use traits::{Connection, ToSqlValue, Transaction};
73pub use value::{ArrayElementType, BorrowedValue, Value};
74pub use warning::{parse_warning_level, SqlWarning, SqlWarningLevel};
75
76#[cfg(test)]
79mod tests {
80 use super::*;
81
82 #[test]
83 fn value_display() {
84 assert_eq!(format!("{}", Value::Null), "NULL");
85 assert_eq!(format!("{}", Value::Bool(true)), "true");
86 assert_eq!(format!("{}", Value::I64(42)), "42");
87 assert_eq!(format!("{}", Value::F64(2.71)), "2.71");
88 assert_eq!(format!("{}", Value::Text("hello".into())), "hello");
89 assert_eq!(format!("{}", Value::Blob(vec![1, 2, 3])), "<blob:3 bytes>");
90 assert_eq!(format!("{}", Value::Decimal("123.456".into())), "123.456");
91 assert_eq!(
92 format!("{}", Value::Json(r#"{"key":"val"}"#.into())),
93 r#"{"key":"val"}"#
94 );
95 assert_eq!(
96 format!("{}", Value::Array(vec![Value::I64(1), Value::I64(2)])),
97 "[1, 2]"
98 );
99 }
100
101 #[test]
102 fn value_type_name() {
103 assert_eq!(Value::Null.type_name(), "Null");
104 assert_eq!(Value::Bool(true).type_name(), "Bool");
105 assert_eq!(Value::I64(1).type_name(), "I64");
106 assert_eq!(Value::F64(1.0).type_name(), "F64");
107 assert_eq!(Value::Text("x".into()).type_name(), "Text");
108 assert_eq!(Value::Blob(vec![]).type_name(), "Blob");
109 assert_eq!(Value::Timestamp(0).type_name(), "Timestamp");
110 assert_eq!(Value::Date(0).type_name(), "Date");
111 assert_eq!(Value::Time(0).type_name(), "Time");
112 assert_eq!(Value::Uuid(0).type_name(), "Uuid");
113 assert_eq!(Value::Json("{}".into()).type_name(), "Json");
114 assert_eq!(Value::Decimal("0".into()).type_name(), "Decimal");
115 assert_eq!(Value::Array(vec![]).type_name(), "Array");
116 }
117
118 #[test]
119 fn value_from_impls() {
120 assert_eq!(Value::from(true), Value::Bool(true));
121 assert_eq!(Value::from(42i32), Value::I64(42));
122 assert_eq!(Value::from(42i64), Value::I64(42));
123 assert_eq!(Value::from(2.71f64), Value::F64(2.71));
124 assert_eq!(Value::from("hello"), Value::Text("hello".into()));
125 assert_eq!(
126 Value::from("hello".to_string()),
127 Value::Text("hello".into())
128 );
129 assert_eq!(Value::from(vec![1u8, 2, 3]), Value::Blob(vec![1, 2, 3]));
130 assert_eq!(Value::from(None::<i64>), Value::Null);
131 assert_eq!(Value::from(Some(42i64)), Value::I64(42));
132 }
133
134 #[test]
135 fn value_partial_ord() {
136 assert!(Value::Null < Value::I64(0));
137 assert!(Value::I64(1) < Value::I64(2));
138 assert!(Value::Text("a".into()) < Value::Text("b".into()));
139 assert!(Value::Bool(false) < Value::Bool(true));
140 assert_eq!(Value::I64(1).partial_cmp(&Value::Text("1".into())), None);
142 }
143
144 #[test]
145 fn from_value_basic() {
146 assert_eq!(bool::from_value(&Value::Bool(true)).ok(), Some(true));
147 assert_eq!(i64::from_value(&Value::I64(42)).ok(), Some(42i64));
148 assert_eq!(i32::from_value(&Value::I64(42)).ok(), Some(42i32));
149 assert_eq!(f64::from_value(&Value::F64(2.71)).ok(), Some(2.71));
150 assert_eq!(
151 String::from_value(&Value::Text("hi".into())).ok(),
152 Some("hi".to_string())
153 );
154 assert_eq!(
155 Vec::<u8>::from_value(&Value::Blob(vec![1])).ok(),
156 Some(vec![1u8])
157 );
158 }
159
160 #[test]
161 fn from_value_option() {
162 assert_eq!(Option::<i64>::from_value(&Value::Null).ok(), Some(None));
163 assert_eq!(
164 Option::<i64>::from_value(&Value::I64(42)).ok(),
165 Some(Some(42))
166 );
167 }
168
169 #[test]
170 fn from_value_type_mismatch() {
171 assert!(i64::from_value(&Value::Text("x".into())).is_err());
172 assert!(bool::from_value(&Value::I64(1)).is_err());
173 assert!(String::from_value(&Value::I64(1)).is_err());
174 }
175
176 #[test]
177 fn row_try_get() {
178 let row = Row::new(
179 vec!["id".into(), "name".into(), "empty".into()],
180 vec![Value::I64(42), Value::Text("Alice".into()), Value::Null],
181 );
182 assert_eq!(row.try_get::<i64>("id").ok(), Some(42));
183 assert_eq!(
184 row.try_get::<String>("name").ok(),
185 Some("Alice".to_string())
186 );
187 assert_eq!(row.try_get::<Option<i64>>("empty").ok(), Some(None));
188 assert!(row.try_get::<i64>("nonexistent").is_err());
189 }
190
191 #[test]
192 fn row_column_count_and_is_null() {
193 let row = Row::new(
194 vec!["a".into(), "b".into()],
195 vec![Value::I64(1), Value::Null],
196 );
197 assert_eq!(row.column_count(), 2);
198 assert!(!row.is_null("a"));
199 assert!(row.is_null("b"));
200 assert!(!row.is_null("nonexistent"));
201 }
202
203 #[test]
204 fn row_display() {
205 let row = Row::new(
206 vec!["id".into(), "name".into()],
207 vec![Value::I64(1), Value::Text("Alice".into())],
208 );
209 assert_eq!(format!("{row}"), "{id: 1, name: Alice}");
210 }
211
212 #[test]
213 fn row_into_values() {
214 let row = Row::new(vec!["x".into()], vec![Value::I64(99)]);
215 let vals = row.into_values();
216 assert_eq!(vals, vec![Value::I64(99)]);
217 }
218
219 #[test]
220 fn row_o1_lookup() {
221 let cols: Vec<String> = (0..100).map(|i| format!("col{i}")).collect();
222 let vals: Vec<Value> = (0..100).map(Value::I64).collect();
223 let row = Row::new(cols, vals);
224 assert_eq!(row.try_get::<i64>("col0").ok(), Some(0));
225 assert_eq!(row.try_get::<i64>("col99").ok(), Some(99));
226 assert_eq!(row.try_get::<i64>("col50").ok(), Some(50));
227 assert!(row.try_get::<i64>("nonexistent").is_err());
228 }
229
230 #[test]
231 fn rowset_basic() {
232 let rows = vec![
233 Row::new(vec!["a".into()], vec![Value::I64(1)]),
234 Row::new(vec!["a".into()], vec![Value::I64(2)]),
235 ];
236 let rs = RowSet::from_rows(rows);
237 assert_eq!(rs.len(), 2);
238 assert_eq!(rs.column_count(), 1);
239 assert!(!rs.is_empty());
240 assert_eq!(rs.columns(), &["a".to_string()]);
241 }
242
243 #[test]
244 fn rowset_empty() {
245 let rs = RowSet::from_rows(vec![]);
246 assert!(rs.is_empty());
247 assert_eq!(rs.column_count(), 0);
248 }
249
250 #[test]
251 fn error_display() {
252 assert_eq!(
253 format!("{}", OxiSqlError::Parse("bad sql".into())),
254 "SQL parse error: bad sql"
255 );
256 assert_eq!(
257 format!("{}", OxiSqlError::ConstraintViolation("unique key".into())),
258 "constraint violation: unique key"
259 );
260 assert_eq!(
261 format!("{}", OxiSqlError::Timeout("5s".into())),
262 "timeout: 5s"
263 );
264 assert_eq!(
265 format!("{}", OxiSqlError::ConnectionPool("exhausted".into())),
266 "connection pool error: exhausted"
267 );
268 assert_eq!(
269 format!("{}", OxiSqlError::Migration("failed".into())),
270 "migration error: failed"
271 );
272 }
273
274 #[test]
275 fn uuid_display() {
276 assert_eq!(
278 format!("{}", Value::Uuid(0)),
279 "00000000-0000-0000-0000-000000000000"
280 );
281 }
282
283 #[test]
284 fn time_display() {
285 let us = (3600 + 2 * 60 + 3) * 1_000_000i64;
287 assert_eq!(format!("{}", Value::Time(us)), "01:02:03");
288 let us2 = us + 42;
290 assert_eq!(format!("{}", Value::Time(us2)), "01:02:03.000042");
291 }
292
293 #[test]
294 fn f64_from_i64_coercion() {
295 assert_eq!(f64::from_value(&Value::I64(42)).ok(), Some(42.0));
297 }
298
299 #[test]
300 fn string_from_json_and_decimal() {
301 assert_eq!(
303 String::from_value(&Value::Json(r#"{"a":1}"#.into())).ok(),
304 Some(r#"{"a":1}"#.to_string())
305 );
306 assert_eq!(
307 String::from_value(&Value::Decimal("1.23".into())).ok(),
308 Some("1.23".to_string())
309 );
310 }
311
312 #[test]
315 fn to_sql_value_i64() {
316 assert_eq!(42i64.to_value(), Value::I64(42));
317 assert_eq!((-1i64).to_value(), Value::I64(-1));
318 assert_eq!(i64::MAX.to_value(), Value::I64(i64::MAX));
319 }
320
321 #[test]
322 fn to_sql_value_i32() {
323 assert_eq!(100i32.to_value(), Value::I64(100));
324 assert_eq!((-5i32).to_value(), Value::I64(-5));
325 }
326
327 #[test]
328 fn to_sql_value_f64() {
329 assert_eq!(1.5f64.to_value(), Value::F64(1.5));
330 assert_eq!(0.0f64.to_value(), Value::F64(0.0));
331 }
332
333 #[test]
334 fn to_sql_value_bool() {
335 assert_eq!(true.to_value(), Value::Bool(true));
336 assert_eq!(false.to_value(), Value::Bool(false));
337 }
338
339 #[test]
340 fn to_sql_value_str() {
341 assert_eq!("hello".to_value(), Value::Text("hello".into()));
342 assert_eq!("".to_value(), Value::Text(String::new()));
343 }
344
345 #[test]
346 fn to_sql_value_string() {
347 assert_eq!("world".to_string().to_value(), Value::Text("world".into()));
348 }
349
350 #[test]
351 fn to_sql_value_bytes() {
352 assert_eq!(vec![1u8, 2, 3].to_value(), Value::Blob(vec![1, 2, 3]));
353 assert_eq!(Vec::<u8>::new().to_value(), Value::Blob(vec![]));
354 }
355
356 #[test]
357 fn to_sql_value_option_some_and_none() {
358 assert_eq!(Some(99i64).to_value(), Value::I64(99));
359 assert_eq!(None::<i64>.to_value(), Value::Null);
360 }
361
362 #[test]
363 fn to_sql_value_ref_passthrough() {
364 let n: i64 = 7;
366 assert_eq!(n.to_value(), Value::I64(7));
367 assert_eq!((&"txt").to_value(), Value::Text("txt".into()));
368 }
369
370 #[test]
373 fn from_value_i32_range_check() {
374 assert_eq!(i32::from_value(&Value::I64(100)).ok(), Some(100i32));
376 assert!(i32::from_value(&Value::I64(i64::MAX)).is_err());
378 assert!(i32::from_value(&Value::I64(i64::MIN)).is_err());
379 }
380
381 #[test]
382 fn from_value_uuid_as_string() {
383 let uuid_val = Value::Uuid(0u128);
385 let s = String::from_value(&uuid_val).expect("uuid as string");
386 assert!(s.contains('-'), "UUID string should contain hyphens: {s}");
387 assert_eq!(s, "00000000-0000-0000-0000-000000000000");
388 }
389
390 #[test]
391 fn from_value_uuid_nonzero_as_string() {
392 let uuid_val = Value::Uuid(0x0102_0304_0506_0708_090a_0b0c_0d0e_0f10u128);
394 let s = String::from_value(&uuid_val).expect("uuid as string");
395 assert!(s.contains('-'), "UUID string should contain hyphens: {s}");
396 }
397
398 #[test]
399 fn from_value_null_non_option_fails() {
400 assert!(i64::from_value(&Value::Null).is_err());
402 assert!(bool::from_value(&Value::Null).is_err());
403 assert!(String::from_value(&Value::Null).is_err());
404 assert!(f64::from_value(&Value::Null).is_err());
405 }
406
407 #[test]
408 fn from_value_u128_from_uuid() {
409 let val = Value::Uuid(12345678u128);
410 assert_eq!(u128::from_value(&val).ok(), Some(12345678u128));
411 }
412
413 #[test]
414 fn from_value_u128_wrong_type_fails() {
415 assert!(u128::from_value(&Value::I64(1)).is_err());
416 }
417
418 #[test]
419 fn from_value_blob_wrong_type_fails() {
420 assert!(Vec::<u8>::from_value(&Value::Text("x".into())).is_err());
421 }
422
423 #[test]
424 fn from_value_f64_from_i64_boundary() {
425 assert_eq!(f64::from_value(&Value::I64(0)).ok(), Some(0.0f64));
427 assert_eq!(f64::from_value(&Value::I64(-1)).ok(), Some(-1.0f64));
428 }
429
430 #[test]
433 fn select_builder_basic() {
434 let q = SelectBuilder::new()
435 .columns(&["id", "name"])
436 .from("users")
437 .build();
438 assert!(q.sql.contains("SELECT"));
439 assert!(q.sql.contains("users"));
440 assert!(q.sql.contains("id"));
441 assert!(q.sql.contains("name"));
442 }
443
444 #[test]
445 fn select_builder_with_where_eq() {
446 let q = SelectBuilder::new()
447 .from("users")
448 .where_eq("id", &42i64)
449 .build();
450 assert!(q.sql.contains("WHERE"));
451 assert!(!q.params.is_empty());
452 assert_eq!(q.params[0], Value::I64(42));
453 }
454
455 #[test]
456 fn insert_builder_basic() {
457 let q = InsertBuilder::new()
458 .into_table("users")
459 .column("name", &"Alice")
460 .column("age", &30i64)
461 .build();
462 assert!(q.sql.to_uppercase().contains("INSERT"));
463 assert!(q.sql.contains("users"));
464 assert_eq!(q.params.len(), 2);
465 }
466
467 #[test]
468 fn update_builder_basic() {
469 let q = UpdateBuilder::new()
470 .table("users")
471 .set("name", &"Bob")
472 .where_eq("id", &1i64)
473 .build();
474 assert!(q.sql.to_uppercase().contains("UPDATE"));
475 assert!(q.sql.contains("users"));
476 assert_eq!(q.params.len(), 2);
477 }
478
479 #[test]
480 fn delete_builder_basic() {
481 let q = DeleteBuilder::new()
482 .from("users")
483 .where_raw("id = 1")
484 .build();
485 assert!(q.sql.to_uppercase().contains("DELETE"));
486 assert!(q.sql.contains("users"));
487 }
488
489 #[test]
492 fn oxisql_error_display_all_variants() {
493 let cases: Vec<(OxiSqlError, &str)> = vec![
494 (OxiSqlError::NotConnected, "not connected"),
495 (OxiSqlError::Execution("oops".into()), "oops"),
496 (OxiSqlError::Timeout("30s".into()), "30s"),
497 (OxiSqlError::Parse("bad sql".into()), "bad sql"),
498 (
499 OxiSqlError::ConstraintViolation("unique key".into()),
500 "unique key",
501 ),
502 (OxiSqlError::ConnectionPool("exhausted".into()), "exhausted"),
503 (OxiSqlError::Migration("failed".into()), "failed"),
504 (OxiSqlError::Other("something".into()), "something"),
505 (
506 OxiSqlError::TypeMismatch {
507 expected: "I64",
508 got: "Text",
509 },
510 "i64",
511 ),
512 ];
513 for (err, fragment) in cases {
514 let s = err.to_string().to_lowercase();
515 assert!(
516 !s.is_empty(),
517 "error should display non-empty string for {err:?}"
518 );
519 assert!(
520 s.contains(fragment),
521 "expected fragment '{fragment}' not found in error display: '{s}'"
522 );
523 }
524 }
525
526 #[test]
529 fn type_registry_lookup_standard_types() {
530 let reg = TypeRegistry::new();
531 assert!(reg.lookup("INTEGER").is_some());
532 assert!(reg.lookup("TEXT").is_some());
533 assert!(reg.lookup("BOOLEAN").is_some());
534 assert!(reg.lookup("UUID").is_some());
535 assert!(reg.lookup("TIMESTAMP").is_some());
536 assert!(reg.lookup("DATE").is_some());
537 assert!(reg.lookup("TIME").is_some());
538 assert!(reg.lookup("JSON").is_some());
539 assert!(reg.lookup("DECIMAL").is_some());
540 assert!(reg.lookup("NONEXISTENT_TYPE").is_none());
541 }
542
543 #[test]
544 fn type_registry_lookup_case_insensitive() {
545 let reg = TypeRegistry::new();
546 assert!(reg.lookup("integer").is_some());
547 assert!(reg.lookup("Integer").is_some());
548 assert!(reg.lookup("TEXT").is_some());
549 assert!(reg.lookup("text").is_some());
550 }
551
552 #[test]
553 fn type_registry_lookup_aliases() {
554 let reg = TypeRegistry::new();
555 assert_eq!(reg.lookup("INT"), Some(&SqlType::Integer));
557 assert_eq!(reg.lookup("INT4"), Some(&SqlType::Integer));
558 assert_eq!(reg.lookup("BIGINT"), Some(&SqlType::BigInt));
560 assert_eq!(reg.lookup("INT8"), Some(&SqlType::BigInt));
561 assert_eq!(reg.lookup("SMALLINT"), Some(&SqlType::SmallInt));
563 assert_eq!(reg.lookup("INT2"), Some(&SqlType::SmallInt));
564 assert_eq!(reg.lookup("FLOAT"), Some(&SqlType::Float));
566 assert_eq!(reg.lookup("REAL"), Some(&SqlType::Float));
567 assert_eq!(reg.lookup("DOUBLE"), Some(&SqlType::Double));
569 assert_eq!(reg.lookup("FLOAT8"), Some(&SqlType::Double));
570 assert_eq!(reg.lookup("NUMERIC"), Some(&SqlType::Decimal));
572 assert_eq!(reg.lookup("BOOL"), Some(&SqlType::Boolean));
574 assert_eq!(reg.lookup("JSONB"), Some(&SqlType::Json));
576 assert_eq!(reg.lookup("BYTEA"), Some(&SqlType::Blob));
578 assert_eq!(reg.lookup("BLOB"), Some(&SqlType::Blob));
579 assert_eq!(reg.lookup("TIMESTAMPTZ"), Some(&SqlType::Timestamp));
581 assert_eq!(reg.lookup("TIMETZ"), Some(&SqlType::Time));
583 }
584
585 #[test]
586 fn type_registry_default_values() {
587 let reg = TypeRegistry::new();
588 assert_eq!(reg.default_value_for("INTEGER"), Value::I64(0));
589 assert_eq!(reg.default_value_for("BIGINT"), Value::I64(0));
590 assert_eq!(reg.default_value_for("SMALLINT"), Value::I64(0));
591 assert_eq!(reg.default_value_for("TEXT"), Value::Text(String::new()));
592 assert_eq!(reg.default_value_for("VARCHAR"), Value::Text(String::new()));
593 assert_eq!(reg.default_value_for("BOOLEAN"), Value::Bool(false));
594 assert_eq!(reg.default_value_for("TIMESTAMP"), Value::Timestamp(0));
595 assert_eq!(reg.default_value_for("DATE"), Value::Date(0));
596 assert_eq!(reg.default_value_for("TIME"), Value::Time(0));
597 assert_eq!(reg.default_value_for("UUID"), Value::Uuid(0));
598 assert_eq!(reg.default_value_for("JSON"), Value::Json("{}".into()));
599 assert_eq!(reg.default_value_for("DECIMAL"), Value::Decimal("0".into()));
600 assert_eq!(reg.default_value_for("BYTEA"), Value::Blob(Vec::new()));
601 assert_eq!(reg.default_value_for("UNKNOWN_TYPE"), Value::Null);
603 }
604
605 #[test]
606 fn type_registry_register_custom() {
607 let mut reg = TypeRegistry::new();
608 reg.register("MY_CUSTOM_TYPE", SqlType::Text);
609 assert!(reg.lookup("MY_CUSTOM_TYPE").is_some());
610 assert!(reg.lookup("my_custom_type").is_some());
612 }
613
614 #[test]
615 fn type_registry_register_overrides_existing() {
616 let mut reg = TypeRegistry::new();
617 reg.register("INTEGER", SqlType::BigInt);
619 assert_eq!(reg.lookup("INTEGER"), Some(&SqlType::BigInt));
620 }
621
622 #[test]
623 fn type_registry_default() {
624 let reg = TypeRegistry::default();
626 assert!(reg.lookup("TEXT").is_some());
627 }
628
629 #[test]
630 fn value_matches_type_basic() {
631 assert!(TypeRegistry::value_matches_type(
632 &Value::I64(1),
633 &SqlType::Integer
634 ));
635 assert!(TypeRegistry::value_matches_type(
636 &Value::I64(1),
637 &SqlType::BigInt
638 ));
639 assert!(TypeRegistry::value_matches_type(
640 &Value::I64(1),
641 &SqlType::SmallInt
642 ));
643 assert!(TypeRegistry::value_matches_type(
644 &Value::F64(1.0),
645 &SqlType::Float
646 ));
647 assert!(TypeRegistry::value_matches_type(
648 &Value::F64(1.0),
649 &SqlType::Double
650 ));
651 assert!(TypeRegistry::value_matches_type(
652 &Value::Text("x".into()),
653 &SqlType::Text
654 ));
655 assert!(TypeRegistry::value_matches_type(
656 &Value::Text("x".into()),
657 &SqlType::VarChar(Some(255))
658 ));
659 assert!(TypeRegistry::value_matches_type(
660 &Value::Bool(true),
661 &SqlType::Boolean
662 ));
663 assert!(TypeRegistry::value_matches_type(
664 &Value::Uuid(0),
665 &SqlType::Uuid
666 ));
667 assert!(TypeRegistry::value_matches_type(
668 &Value::Json("{}".into()),
669 &SqlType::Json
670 ));
671 assert!(TypeRegistry::value_matches_type(
672 &Value::Decimal("1.5".into()),
673 &SqlType::Decimal
674 ));
675 assert!(TypeRegistry::value_matches_type(
676 &Value::Blob(vec![]),
677 &SqlType::Blob
678 ));
679 assert!(TypeRegistry::value_matches_type(
680 &Value::Timestamp(0),
681 &SqlType::Timestamp
682 ));
683 assert!(TypeRegistry::value_matches_type(
684 &Value::Date(0),
685 &SqlType::Date
686 ));
687 assert!(TypeRegistry::value_matches_type(
688 &Value::Time(0),
689 &SqlType::Time
690 ));
691 assert!(TypeRegistry::value_matches_type(
692 &Value::Array(vec![]),
693 &SqlType::Array(Box::new(SqlType::Integer))
694 ));
695 }
696
697 #[test]
698 fn value_matches_type_mismatches() {
699 assert!(!TypeRegistry::value_matches_type(
700 &Value::Text("x".into()),
701 &SqlType::Integer
702 ));
703 assert!(!TypeRegistry::value_matches_type(
704 &Value::I64(1),
705 &SqlType::Text
706 ));
707 assert!(!TypeRegistry::value_matches_type(
708 &Value::Bool(true),
709 &SqlType::Integer
710 ));
711 assert!(!TypeRegistry::value_matches_type(
712 &Value::F64(1.0),
713 &SqlType::Integer
714 ));
715 }
716
717 #[test]
718 fn value_matches_type_null_valid_for_any() {
719 assert!(TypeRegistry::value_matches_type(
721 &Value::Null,
722 &SqlType::Integer
723 ));
724 assert!(TypeRegistry::value_matches_type(
725 &Value::Null,
726 &SqlType::Text
727 ));
728 assert!(TypeRegistry::value_matches_type(
729 &Value::Null,
730 &SqlType::Boolean
731 ));
732 assert!(TypeRegistry::value_matches_type(
733 &Value::Null,
734 &SqlType::Uuid
735 ));
736 }
737
738 #[test]
739 fn sql_type_as_sql_name() {
740 assert_eq!(SqlType::Integer.as_sql_name(), "INTEGER");
741 assert_eq!(SqlType::BigInt.as_sql_name(), "BIGINT");
742 assert_eq!(SqlType::SmallInt.as_sql_name(), "SMALLINT");
743 assert_eq!(SqlType::Float.as_sql_name(), "REAL");
744 assert_eq!(SqlType::Double.as_sql_name(), "DOUBLE PRECISION");
745 assert_eq!(SqlType::Decimal.as_sql_name(), "DECIMAL");
746 assert_eq!(SqlType::Text.as_sql_name(), "TEXT");
747 assert_eq!(SqlType::VarChar(None).as_sql_name(), "VARCHAR");
748 assert_eq!(SqlType::VarChar(Some(255)).as_sql_name(), "VARCHAR(255)");
749 assert_eq!(SqlType::Blob.as_sql_name(), "BYTEA");
750 assert_eq!(SqlType::Boolean.as_sql_name(), "BOOLEAN");
751 assert_eq!(SqlType::Timestamp.as_sql_name(), "TIMESTAMP");
752 assert_eq!(SqlType::Date.as_sql_name(), "DATE");
753 assert_eq!(SqlType::Time.as_sql_name(), "TIME");
754 assert_eq!(SqlType::Uuid.as_sql_name(), "UUID");
755 assert_eq!(SqlType::Json.as_sql_name(), "JSON");
756 assert_eq!(
757 SqlType::Array(Box::new(SqlType::Integer)).as_sql_name(),
758 "INTEGER[]"
759 );
760 assert_eq!(SqlType::Unknown("MYTYPE".into()).as_sql_name(), "MYTYPE");
761 }
762
763 #[test]
764 fn sql_type_default_value_array_and_unknown() {
765 assert_eq!(
766 SqlType::Array(Box::new(SqlType::Integer)).default_value(),
767 Value::Array(Vec::new())
768 );
769 assert_eq!(
770 SqlType::Unknown("CUSTOM".into()).default_value(),
771 Value::Null
772 );
773 }
774
775 #[test]
778 fn cursor_basic_traversal() {
779 let rows = vec![
780 Row::new(vec!["id".into()], vec![Value::I64(1)]),
781 Row::new(vec!["id".into()], vec![Value::I64(2)]),
782 ];
783 let mut cursor = Cursor::new(rows);
784 assert_eq!(cursor.len(), 2);
785 assert_eq!(cursor.remaining(), 2);
786 assert!(!cursor.is_empty());
787
788 let r1 = cursor.advance().expect("first row");
789 assert_eq!(r1.try_get::<i64>("id").unwrap(), 1);
790 assert_eq!(cursor.position(), 1);
791 assert_eq!(cursor.remaining(), 1);
792
793 let r2 = cursor.advance().expect("second row");
794 assert_eq!(r2.try_get::<i64>("id").unwrap(), 2);
795 assert_eq!(cursor.position(), 2);
796 assert_eq!(cursor.remaining(), 0);
797
798 assert!(cursor.advance().is_none());
799 }
800
801 #[test]
802 fn cursor_peek_does_not_advance() {
803 let rows = vec![Row::new(vec!["x".into()], vec![Value::I64(42)])];
804 let cursor = Cursor::new(rows);
805 let peeked = cursor.peek().expect("peek first row");
806 assert_eq!(peeked.try_get::<i64>("x").unwrap(), 42);
807 assert_eq!(cursor.position(), 0);
809 }
810
811 #[test]
812 fn cursor_reset() {
813 let rows = vec![Row::new(vec!["x".into()], vec![Value::I64(1)])];
814 let mut cursor = Cursor::new(rows);
815 cursor.advance();
816 assert_eq!(cursor.position(), 1);
817 cursor.reset();
818 assert_eq!(cursor.position(), 0);
819 assert_eq!(cursor.remaining(), 1);
820 assert!(cursor.advance().is_some());
821 }
822
823 #[test]
824 fn cursor_skip_by() {
825 let rows: Vec<Row> = (0..5)
826 .map(|i| Row::new(vec!["n".into()], vec![Value::I64(i)]))
827 .collect();
828 let mut cursor = Cursor::new(rows);
829 cursor.skip_by(3);
830 assert_eq!(cursor.position(), 3);
831 assert_eq!(cursor.remaining(), 2);
832 cursor.skip_by(100);
834 assert_eq!(cursor.position(), 5);
835 assert_eq!(cursor.remaining(), 0);
836 assert!(cursor.advance().is_none());
837 }
838
839 #[test]
840 fn cursor_into_rows_recovers_all() {
841 let rows = vec![
842 Row::new(vec!["v".into()], vec![Value::I64(10)]),
843 Row::new(vec!["v".into()], vec![Value::I64(20)]),
844 ];
845 let mut cursor = Cursor::new(rows.clone());
846 cursor.advance(); let recovered = cursor.into_rows();
848 assert_eq!(recovered.len(), 2);
850 }
851
852 #[test]
853 fn cursor_empty() {
854 let mut cursor = Cursor::new(vec![]);
855 assert!(cursor.is_empty());
856 assert_eq!(cursor.len(), 0);
857 assert_eq!(cursor.remaining(), 0);
858 assert!(cursor.peek().is_none());
859 assert!(cursor.advance().is_none());
860 }
861
862 #[test]
863 fn cursor_iterator_yields_owned_rows() {
864 let rows: Vec<Row> = (1..=3)
865 .map(|i| Row::new(vec!["i".into()], vec![Value::I64(i)]))
866 .collect();
867 let cursor = Cursor::new(rows);
868 let collected: Vec<Row> = cursor.collect();
869 assert_eq!(collected.len(), 3);
870 assert_eq!(collected[0].try_get::<i64>("i").unwrap(), 1);
871 assert_eq!(collected[2].try_get::<i64>("i").unwrap(), 3);
872 }
873
874 #[test]
875 fn cursor_iterator_and_advance_independent() {
876 let rows = vec![
878 Row::new(vec!["k".into()], vec![Value::I64(10)]),
879 Row::new(vec!["k".into()], vec![Value::I64(20)]),
880 Row::new(vec!["k".into()], vec![Value::I64(30)]),
881 ];
882 let mut cursor = Cursor::new(rows);
883 let owned = Iterator::next(&mut cursor).expect("first via Iterator");
885 assert_eq!(owned.try_get::<i64>("k").unwrap(), 10);
886 let borrowed = cursor.advance().expect("second via advance");
888 assert_eq!(borrowed.try_get::<i64>("k").unwrap(), 20);
889 assert_eq!(cursor.remaining(), 1);
891 }
892}