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