1mod config;
2mod connection;
3mod decode;
4mod encode;
5mod error;
6mod pool;
7pub mod protocol;
8mod queryer;
9mod row;
10mod transaction;
11pub mod types;
12
13pub use config::{ConnectParams, PoolConfig};
14pub use connection::Connection;
15pub use decode::FromSql;
16pub use encode::{BindIter, ToSql};
17pub use error::{DbError, PgError};
18pub use pg_derive::FromRow;
19pub use pool::{Pool, PooledConnection};
20pub use queryer::{FromRow, Queryer, RowStream};
21pub use row::Row;
22pub use transaction::Transaction;
23
24pub type Result<T> = std::result::Result<T, PgError>;
25
26#[cfg(test)]
27mod tests {
28 use std::str::FromStr;
29
30 use crate::{types::*, *};
31
32 #[test]
33 fn test_to_sql_i32() {
34 let val: i32 = 42;
35 let bytes = val.to_sql().unwrap();
36 assert_eq!(bytes, vec![0, 0, 0, 42]);
37 assert_eq!(val.pg_type().oid, INT4OID);
38 }
39
40 #[test]
41 fn test_to_sql_i64() {
42 let val: i64 = 1234567890;
43 let bytes = val.to_sql().unwrap();
44 assert_eq!(bytes, vec![0, 0, 0, 0, 73, 150, 2, 210]);
45 assert_eq!(val.pg_type().oid, INT8OID);
46 }
47
48 #[test]
49 fn test_to_sql_bool() {
50 let t = true;
51 let f = false;
52 assert_eq!(t.to_sql().unwrap(), vec![1]);
53 assert_eq!(f.to_sql().unwrap(), vec![0]);
54 assert_eq!(t.pg_type().oid, BOOLOID);
55 }
56
57 #[test]
58 fn test_to_sql_string() {
59 let s = "hello".to_string();
60 assert_eq!(s.to_sql().unwrap(), b"hello");
61 assert_eq!(s.pg_type().oid, TEXTOID);
62 }
63
64 #[test]
65 fn test_to_sql_vec_i32() {
66 let v = vec![1i32, 2, 3];
67 let bytes = v.to_sql().unwrap();
68 assert!(bytes.len() > 20);
69 assert_eq!(v.pg_type().oid, INT4_ARRAY_OID);
70 }
71
72 #[test]
73 fn test_to_sql_vec_u8_bytea() {
74 let v: Vec<u8> = vec![0xde, 0xad, 0xbe, 0xef];
75 assert_eq!(v.to_sql().unwrap(), vec![0xde, 0xad, 0xbe, 0xef]);
76 assert_eq!(v.pg_type().oid, BYTEAOID);
77 }
78
79 #[test]
80 fn test_to_sql_slice_u8_bytea() {
81 let b: &[u8] = &[0xca, 0xfe, 0xba, 0xbe];
82 assert_eq!(b.to_sql().unwrap(), vec![0xca, 0xfe, 0xba, 0xbe]);
83 assert_eq!(b.pg_type().oid, BYTEAOID);
84 }
85
86 #[test]
87 fn test_to_sql_slice_i32_array() {
88 let arr: &[i32] = &[10, 20];
89 let bytes = arr.to_sql().unwrap();
90 assert!(bytes.len() > 20);
91 assert_eq!(arr.pg_type().oid, INT4_ARRAY_OID);
92 }
93
94 #[test]
95 fn test_to_sql_option_some() {
96 let val: Option<i32> = Some(42);
97 assert_eq!(val.to_sql().unwrap(), vec![0, 0, 0, 42]);
98 }
99
100 #[test]
101 fn test_bind_iter_i32() {
102 let data = vec![1i32, 2, 3];
103 let bi = BindIter::new(data.into_iter(), &INT4);
104 let bytes = bi.to_sql().unwrap();
105 assert!(bytes.len() > 20);
106 assert_eq!(bi.pg_type().oid, INT4_ARRAY_OID);
107
108 let dim_count = i32::from_be_bytes([bytes[12], bytes[13], bytes[14], bytes[15]]);
109 assert_eq!(dim_count, 3);
110 }
111
112 #[test]
113 fn test_bind_iter_uuid() {
114 let u1 = uuid::Uuid::from_str("550e8400-e29b-41d4-a716-446655440000").unwrap();
115 let u2 = uuid::Uuid::from_str("6ba7b810-9dad-11d1-80b4-00c04fd430c8").unwrap();
116 let bi = BindIter::new(vec![u1, u2].into_iter(), &UUID);
117 let bytes = bi.to_sql().unwrap();
118 let dim_count = i32::from_be_bytes([bytes[12], bytes[13], bytes[14], bytes[15]]);
119 assert_eq!(dim_count, 2);
120 assert_eq!(bi.pg_type().oid, UUID_ARRAY_OID);
121 }
122
123 #[test]
124 fn test_bind_iter_empty() {
125 let empty: Vec<i32> = vec![];
126 let bi = BindIter::new(empty.into_iter(), &INT4);
127 let bytes = bi.to_sql().unwrap();
128 let dim_count = i32::from_be_bytes([bytes[12], bytes[13], bytes[14], bytes[15]]);
129 assert_eq!(dim_count, 0);
130 assert_eq!(bytes.len(), 20); }
132
133 #[test]
134 fn test_bind_iter_same_as_collected_vec() {
135 let data = vec![10i32, 20, 30];
136 let vec_encoded = data.to_sql().unwrap();
137 let bi_encoded = BindIter::new(data.clone().into_iter(), &INT4).to_sql().unwrap();
138 assert_eq!(vec_encoded, bi_encoded);
139 }
140
141 #[test]
142 fn test_to_sql_option_none() {
143 let val: Option<i32> = None;
144 assert_eq!(val.to_sql().unwrap(), Vec::<u8>::new());
145 }
146
147 #[test]
148 fn test_from_sql_i32() {
149 let val = i32::from_sql(INT4OID, &[0, 0, 0, 42]).unwrap();
150 assert_eq!(val, 42);
151 }
152
153 #[test]
154 fn test_from_sql_i64() {
155 let val = i64::from_sql(INT8OID, &[0, 0, 0, 0, 73, 150, 2, 210]).unwrap();
156 assert_eq!(val, 1234567890);
157 }
158
159 #[test]
160 fn test_from_sql_bool() {
161 let t = bool::from_sql(BOOLOID, &[1]).unwrap();
162 let f = bool::from_sql(BOOLOID, &[0]).unwrap();
163 assert!(t);
164 assert!(!f);
165 }
166
167 #[test]
168 fn test_from_sql_string() {
169 let s = String::from_sql(TEXTOID, b"hello").unwrap();
170 assert_eq!(s, "hello");
171 }
172
173 #[test]
174 fn test_from_sql_option() {
175 let some: Option<i32> = Option::from_sql(INT4OID, &[0, 0, 0, 42]).unwrap();
176 assert_eq!(some, Some(42));
177
178 let none: Option<i32> = Option::from_sql(INT4OID, &[]).unwrap();
179 assert_eq!(none, None);
180 }
181
182 #[test]
183 fn test_connect_params_parse() {
184 let params = ConnectParams::parse("host=localhost port=5432 user=test dbname=mydb").unwrap();
185 assert_eq!(params.host, "localhost");
186 assert_eq!(params.port, 5432);
187 assert_eq!(params.user, "test");
188 assert_eq!(params.dbname, Some("mydb".to_string()));
189 }
190
191 #[test]
192 fn test_connect_params_requires_user() {
193 let result = ConnectParams::parse("host=localhost");
194 assert!(result.is_err());
195 }
196
197 #[test]
198 fn test_connect_params_defaults() {
199 let params = ConnectParams::parse("user=test").unwrap();
200 assert_eq!(params.host, "localhost");
201 assert_eq!(params.port, 5432);
202 }
203
204 #[test]
205 fn test_base64_roundtrip() {
206 let data = b"SCRAM test data \x00\x01\x02";
207 let encoded = protocol::base64_encode(data);
208 let decoded = protocol::base64_decode(&encoded).unwrap();
209 assert_eq!(data, &decoded[..]);
210 }
211
212 #[test]
213 fn test_pool_config_default() {
214 let cfg = PoolConfig::default();
215 assert_eq!(cfg.min_connections, 0);
216 assert_eq!(cfg.max_connections, 10);
217 }
218
219 #[test]
220 fn test_from_sql_timestamptz() {
221 use chrono::{DateTime, Utc};
222 let pg_epoch = DateTime::from_timestamp(946684800, 0).unwrap();
223 let micros = 0i64.to_be_bytes();
224 let dt = DateTime::<Utc>::from_sql(TIMESTAMPTZOID, µs).unwrap();
225 assert_eq!(dt, pg_epoch);
226
227 let one_second: i64 = 1_000_000;
228 let dt2 = DateTime::<Utc>::from_sql(TIMESTAMPTZOID, &one_second.to_be_bytes()).unwrap();
229 assert_eq!(dt2, pg_epoch + chrono::TimeDelta::seconds(1));
230 }
231
232 #[test]
233 fn test_to_sql_timestamptz_overflow() {
234 use chrono::{NaiveDate, TimeDelta};
235 let big_dur = TimeDelta::microseconds(i64::MAX);
237 let pg_epoch = NaiveDate::from_ymd_opt(2000, 1, 1)
238 .unwrap()
239 .and_hms_opt(0, 0, 0)
240 .unwrap()
241 .and_utc();
242 if let Some(far) = pg_epoch.checked_add_signed(big_dur) {
243 let result = far.to_sql();
244 assert!(result.is_err(), "expected overflow error for extreme date");
245 match result {
246 Err(PgError::Encode(msg)) => assert!(msg.contains("out of range"), "msg: {}", msg),
247 _ => panic!("expected Encode error, got {:?}", result),
248 }
249 }
250 }
252
253 #[test]
254 fn test_to_sql_timestamptz_normal() {
255 use chrono::DateTime;
256 let dt = DateTime::from_timestamp(0, 0).unwrap();
257 let result = dt.to_sql().unwrap();
258 let pg_epoch = DateTime::from_timestamp(946684800, 0).unwrap();
259 let expected_micros: i64 = (dt - pg_epoch).num_microseconds().unwrap();
260 assert_eq!(result, expected_micros.to_be_bytes().to_vec());
261 }
262
263 #[test]
264 fn test_int2_array_oid() {
265 let v = vec![1i16, 2, 3];
266 assert_eq!(v.pg_type().oid, INT2_ARRAY_OID);
267 assert_ne!(v.pg_type().oid, INT4_ARRAY_OID);
268 }
269
270 #[test]
271 fn test_element_to_array_int2() {
272 let arr = crate::types::element_to_array(&crate::types::INT2);
273 assert_eq!(arr.oid, INT2_ARRAY_OID);
274 }
275
276 #[test]
277 fn test_element_to_array_int4() {
278 let arr = crate::types::element_to_array(&crate::types::INT4);
279 assert_eq!(arr.oid, INT4_ARRAY_OID);
280 }
281
282 #[test]
283 fn test_bind_iter_int2() {
284 let data = vec![1i16, 2, 3];
285 let bi = BindIter::new(data.into_iter(), &INT2);
286 let bytes = bi.to_sql().unwrap();
287 assert_eq!(bi.pg_type().oid, INT2_ARRAY_OID);
288
289 let elem_oid = u32::from_be_bytes([bytes[8], bytes[9], bytes[10], bytes[11]]);
290 assert_eq!(elem_oid, INT2OID);
291 }
292
293 #[test]
294 fn test_vec_i16_encoding() {
295 let v: Vec<i16> = vec![1, 2, 3];
296 let bytes = v.to_sql().unwrap();
297
298 let num_dims = i32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]);
299 assert_eq!(num_dims, 1);
300
301 let elem_oid = u32::from_be_bytes([bytes[8], bytes[9], bytes[10], bytes[11]]);
302 assert_eq!(elem_oid, INT2OID, "element OID should be INT2 (21)");
303 }
304
305 #[test]
306 fn test_parse_command_tag_insert() {
307 fn parse_tag(tag: &str) -> u64 {
312 let mut a = 0u64;
313 if let Some(n) = tag.rsplit(' ').next().and_then(|s| s.parse::<u64>().ok()) {
314 a = n;
315 }
316 a
317 }
318
319 assert_eq!(parse_tag("INSERT 0 1"), 1);
320 assert_eq!(parse_tag("UPDATE 5"), 5);
321 assert_eq!(parse_tag("DELETE 3"), 3);
322 assert_eq!(parse_tag("SELECT 42"), 42);
323 assert_eq!(parse_tag("INSERT 0 0"), 0);
324 assert_eq!(parse_tag("CREATE TABLE"), 0);
325 }
326
327 #[test]
328 fn test_from_sql_array_empty() {
329 let empty_array = vec![
330 0i32.to_be_bytes(), 0i32.to_be_bytes(), 0i32.to_be_bytes(), 0i32.to_be_bytes(), 0i32.to_be_bytes(), ]
336 .concat();
337 let result = Vec::<i32>::from_sql(INT4_ARRAY_OID, &empty_array).unwrap();
338 assert!(result.is_empty());
339 }
340
341 #[test]
342 fn test_from_sql_array_with_nulls() {
343 let elem_count = 3i32;
345 let mut buf = Vec::new();
346 buf.extend_from_slice(&1i32.to_be_bytes()); buf.extend_from_slice(&1i32.to_be_bytes()); buf.extend_from_slice(&INT4OID.to_be_bytes()); buf.extend_from_slice(&elem_count.to_be_bytes()); buf.extend_from_slice(&1i32.to_be_bytes()); buf.extend_from_slice(&4i32.to_be_bytes());
353 buf.extend_from_slice(&1i32.to_be_bytes());
354 buf.extend_from_slice(&(-1i32).to_be_bytes());
356 buf.extend_from_slice(&4i32.to_be_bytes());
358 buf.extend_from_slice(&3i32.to_be_bytes());
359
360 let result = Vec::<i32>::from_sql(INT4_ARRAY_OID, &buf).unwrap();
361 assert_eq!(result, vec![1, 3]);
362 }
363
364 #[test]
365 fn test_from_sql_array_short_buffer() {
366 let result = Vec::<i32>::from_sql(INT4_ARRAY_OID, &[]);
367 assert!(result.is_err());
368 match result {
369 Err(PgError::Decode(msg)) => assert!(msg.contains("buffer too short")),
370 _ => panic!("expected Decode error"),
371 }
372 }
373
374 #[test]
375 fn test_from_sql_bool_empty() {
376 let result = bool::from_sql(BOOLOID, &[]);
377 assert!(result.is_err());
378 }
379
380 #[test]
381 fn test_from_sql_i32_empty() {
382 let result = i32::from_sql(INT4OID, &[]);
383 assert!(result.is_err());
384 }
385
386 #[test]
387 fn test_to_sql_empty_vec_i32() {
388 let v: Vec<i32> = vec![];
389 let bytes = v.to_sql().unwrap();
390 assert!(bytes.len() >= 20);
391 let dim_count = i32::from_be_bytes([bytes[12], bytes[13], bytes[14], bytes[15]]);
392 assert_eq!(dim_count, 0);
393 }
394
395 #[test]
396 fn test_to_sql_empty_slice_i32() {
397 let v: &[i32] = &[];
398 let bytes = v.to_sql().unwrap();
399 let dim_count = i32::from_be_bytes([bytes[12], bytes[13], bytes[14], bytes[15]]);
400 assert_eq!(dim_count, 0);
401 }
402
403 #[test]
404 fn test_from_sql_timestamptz_negative() {
405 use chrono::{DateTime, TimeDelta, Utc};
406 let pg_epoch = DateTime::from_timestamp(946684800, 0).unwrap();
407 let negative_micros = (-1_000_000i64).to_be_bytes();
408 let dt = DateTime::<Utc>::from_sql(TIMESTAMPTZOID, &negative_micros).unwrap();
409 assert_eq!(dt, pg_epoch - TimeDelta::seconds(1));
410 }
411
412 #[test]
413 fn test_from_sql_uuid() {
414 use uuid::Uuid;
415 let u = Uuid::from_str("550e8400-e29b-41d4-a716-446655440000").unwrap();
416 let bytes = u.as_bytes();
417 let result = Uuid::from_sql(UUIDOID, &bytes).unwrap();
418 assert_eq!(result, u);
419 }
420
421 #[test]
422 fn test_from_sql_uuid_short() {
423 use uuid::Uuid;
424 let result = Uuid::from_sql(UUIDOID, &[0; 4]);
425 assert!(result.is_err());
426 }
427
428 #[test]
429 fn test_to_sql_multiple_types_in_vec() {
430 let v = vec![1i32, 2, 3];
431 let bytes = v.to_sql().unwrap();
432 let dim_count = i32::from_be_bytes([bytes[12], bytes[13], bytes[14], bytes[15]]);
433 assert_eq!(dim_count, 3);
434
435 let mut offset = 20usize;
437 for expected in [1i32, 2, 3] {
438 let len = i32::from_be_bytes([bytes[offset], bytes[offset + 1], bytes[offset + 2], bytes[offset + 3]]);
439 assert_eq!(len, 4);
440 offset += 4;
441 let val = i32::from_be_bytes([bytes[offset], bytes[offset + 1], bytes[offset + 2], bytes[offset + 3]]);
442 assert_eq!(val, expected);
443 offset += 4;
444 }
445 }
446
447 #[test]
448 fn test_option_to_sql_pg_type() {
449 let some: Option<i32> = Some(42);
450 let none: Option<i32> = None;
451 assert_eq!(some.pg_type().oid, INT4OID);
452 assert_eq!(none.pg_type().oid, TEXTOID);
454 }
455
456 #[test]
457 fn test_bind_iter_i32_full_roundtrip() {
458 let data = vec![100i32, 200, 300];
459 let vec_encoded = data.to_sql().unwrap();
460 let bind_encoded = BindIter::new(data.clone().into_iter(), &INT4).to_sql().unwrap();
461 assert_eq!(vec_encoded, bind_encoded);
462 assert_eq!(bind_encoded.len(), 20 + 3 * (4 + 4));
463 }
464
465 #[test]
466 fn test_bind_iter_uuid_full_roundtrip() {
467 use uuid::Uuid;
468 let data = vec![
469 Uuid::from_str("550e8400-e29b-41d4-a716-446655440000").unwrap(),
470 Uuid::from_str("6ba7b810-9dad-11d1-80b4-00c04fd430c8").unwrap(),
471 ];
472 let vec_encoded = data.to_sql().unwrap();
473 let bind_encoded = BindIter::new(data.into_iter(), &UUID).to_sql().unwrap();
474 assert_eq!(vec_encoded, bind_encoded);
475 }
476
477 #[test]
478 fn test_option_in_vec_to_sql() {
479 let v: Vec<Option<i32>> = vec![Some(1), None, Some(3)];
480 let bytes = v.to_sql().unwrap();
481 assert!(bytes.len() > 20);
482 }
483
484 #[test]
485 fn test_from_sql_string_invalid_utf8() {
486 let result = String::from_sql(TEXTOID, &[0xff, 0xfe, 0xfd]);
487 assert!(result.is_err());
488 }
489
490 #[test]
491 fn test_pg_type_array_of() {
492 let arr_type = crate::types::PgType::array_of(&crate::types::INT2);
493 assert_eq!(arr_type.oid, INT2_ARRAY_OID);
494
495 let arr_type = crate::types::PgType::array_of(&crate::types::UUID);
496 assert_eq!(arr_type.oid, UUID_ARRAY_OID);
497
498 let arr_type = crate::types::PgType::array_of(&crate::types::TEXT);
499 assert_eq!(arr_type.oid, TEXT_ARRAY_OID);
500 }
501
502 #[test]
503 fn test_pool_config_edge_cases() {
504 let cfg = PoolConfig {
505 min_connections: 0,
506 max_connections: 1,
507 ..PoolConfig::default()
508 };
509 assert_eq!(cfg.max_connections, 1);
510
511 let cfg = PoolConfig {
512 min_connections: 5,
513 max_connections: 5,
514 ..PoolConfig::default()
515 };
516 assert_eq!(cfg.min_connections, cfg.max_connections);
517 }
518}