1use std::{
2 ffi::{CString, NulError},
3 fmt::Display,
4 io::{Read, Seek},
5 ops::ControlFlow,
6 os::raw::{c_char, c_void},
7 ptr::{null, null_mut},
8};
9
10use std::fmt::Debug;
11
12use tempfile::Builder;
13
14include!("bindings.rs");
15
16pub struct PgSocket {
17 socket: i32,
18}
19
20pub enum PgSocketPollResult {
21 Timeout,
22 Error(String),
23}
24
25impl Display for PgSocketPollResult {
26 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
27 match self {
28 PgSocketPollResult::Timeout => write!(f, "Timeout"),
29 PgSocketPollResult::Error(s) => write!(f, "Error: {}", s),
30 }
31 }
32}
33
34impl PgSocket {
35 pub fn poll(
36 &self,
37 read: bool,
38 write: bool,
39 timeout: Option<f64>,
40 ) -> Result<(), PgSocketPollResult> {
41 unsafe {
42 let timeout_ms = match timeout {
43 Some(t) => PQgetCurrentTimeUSec() + (t * 1000000.0) as i64,
44 None => -1,
45 };
46
47 match PQsocketPoll(self.socket, read.into(), write.into(), timeout_ms) {
48 a if a > 0 => Ok(()),
49 0 => Err(PgSocketPollResult::Timeout),
50 _ => Err(PgSocketPollResult::Error(
51 std::io::Error::last_os_error().to_string(),
52 )),
53 }
54 }
55 }
56}
57pub struct PgConn {
58 conn: *mut PGconn,
59}
60
61unsafe impl Send for PgConn {}
62
63unsafe impl Sync for PgConn {}
64
65pub struct PgResult {
66 res: *mut PGresult,
67}
68
69pub struct PgNotify {
70 notify: *mut PGnotify,
71}
72
73impl PgNotify {
74 pub fn relname(&self) -> String {
75 unsafe {
76 let s = (*self.notify).relname;
77 std::ffi::CStr::from_ptr(s).to_string_lossy().into_owned()
78 }
79 }
80
81 pub fn be_pid(&self) -> i32 {
82 unsafe { (*self.notify).be_pid }
83 }
84
85 pub fn extra(&self) -> String {
86 unsafe {
87 let s = (*self.notify).extra;
88 std::ffi::CStr::from_ptr(s).to_string_lossy().into_owned()
89 }
90 }
91}
92
93impl Drop for PgConn {
94 fn drop(&mut self) {
95 unsafe {
96 PQfinish(self.conn);
97 }
98 }
99}
100
101impl Drop for PgNotify {
102 fn drop(&mut self) {
103 unsafe {
104 PQfreemem(self.notify as *mut c_void);
105 }
106 }
107}
108
109impl Drop for PgResult {
110 fn drop(&mut self) {
111 unsafe {
112 PQclear(self.res);
113 }
114 }
115}
116
117impl PgConn {
118 pub fn connect_db_env_vars() -> Result<PgConn, NulError> {
122 Self::connect_db("")
123 }
124
125 pub fn connect_db(s: &str) -> Result<PgConn, NulError> {
126 unsafe {
127 let conninfo = std::ffi::CString::new(s)?;
128 let conn = PQconnectdb(conninfo.as_ptr());
129 Ok(PgConn { conn })
130 }
131 }
132
133 pub fn status(&self) -> ConnStatusType {
134 unsafe { PQstatus(self.conn) }
135 }
136
137 pub fn exec(&self, query: &str) -> Result<PgResult, NulError> {
138 unsafe {
139 let c_query = std::ffi::CString::new(query)?;
140 let res = PQexec(self.conn, c_query.as_ptr());
141 Ok(PgResult { res })
142 }
143 }
144
145 pub fn exec_file(&self, file_path: &str) -> Result<PgResult, NulError> {
146 let content = std::fs::read_to_string(file_path).expect("Failed to read file.");
147 self.exec(&content)
148 }
149
150 pub fn exec_params(
155 &self,
156 query: &str,
157 param_values: &[Option<&str>],
158 ) -> Result<PgResult, NulError> {
159 unsafe {
160 let c_query = CString::new(query)?;
161
162 let c_params = param_values
163 .iter()
164 .map(|v| v.map(CString::new).transpose())
165 .collect::<Result<Vec<_>, NulError>>()?;
166
167 let ptrs: Vec<*const c_char> = c_params
168 .iter()
169 .map(|v| match v {
170 Some(c) => c.as_ptr(),
171 None => null(),
172 })
173 .collect();
174
175 let res = PQexecParams(
176 self.conn,
177 c_query.as_ptr(),
178 ptrs.len() as i32,
179 null(),
180 ptrs.as_ptr(),
181 null(),
182 null(),
183 0,
184 );
185
186 Ok(PgResult { res })
187 }
188 }
189
190 pub fn prepare(&self, stmt_name: &str, query: &str) -> Result<PgResult, NulError> {
193 unsafe {
194 let c_stmt_name = CString::new(stmt_name)?;
195 let c_query = CString::new(query)?;
196
197 let res = PQprepare(self.conn, c_stmt_name.as_ptr(), c_query.as_ptr(), 0, null());
198
199 Ok(PgResult { res })
200 }
201 }
202
203 pub fn exec_prepared(
207 &self,
208 stmt_name: &str,
209 param_values: &[Option<&str>],
210 ) -> Result<PgResult, NulError> {
211 unsafe {
212 let c_stmt_name = CString::new(stmt_name)?;
213
214 let c_params = param_values
215 .iter()
216 .map(|v| v.map(CString::new).transpose())
217 .collect::<Result<Vec<_>, NulError>>()?;
218
219 let ptrs: Vec<*const c_char> = c_params
220 .iter()
221 .map(|v| match v {
222 Some(c) => c.as_ptr(),
223 None => null(),
224 })
225 .collect();
226
227 let res = PQexecPrepared(
228 self.conn,
229 c_stmt_name.as_ptr(),
230 ptrs.len() as i32,
231 ptrs.as_ptr(),
232 null(),
233 null(),
234 0,
235 );
236
237 Ok(PgResult { res })
238 }
239 }
240
241 pub fn describe_prepared(&self, stmt_name: &str) -> Result<PgResult, NulError> {
245 unsafe {
246 let c_stmt_name = CString::new(stmt_name)?;
247 let res = PQdescribePrepared(self.conn, c_stmt_name.as_ptr());
248 Ok(PgResult { res })
249 }
250 }
251
252 pub fn describe_portal(&self, portal_name: &str) -> Result<PgResult, NulError> {
256 unsafe {
257 let c_portal_name = CString::new(portal_name)?;
258 let res = PQdescribePortal(self.conn, c_portal_name.as_ptr());
259 Ok(PgResult { res })
260 }
261 }
262
263 pub fn close_prepared(&self, stmt_name: &str) -> Result<PgResult, NulError> {
266 unsafe {
267 let c_stmt_name = CString::new(stmt_name)?;
268 let res = PQclosePrepared(self.conn, c_stmt_name.as_ptr());
269 Ok(PgResult { res })
270 }
271 }
272
273 pub fn trace(&mut self, file: &str) {
274 unsafe {
275 let c_file = std::ffi::CString::new(file).unwrap();
276 let mode = std::ffi::CString::new("w").unwrap();
277 let fp = fopen(c_file.as_ptr(), mode.as_ptr());
278 PQtrace(self.conn, fp);
279 assert_eq!(fflush(fp), 0);
280 }
281 }
282
283 pub fn untrace(&mut self) {
284 unsafe {
285 PQuntrace(self.conn);
286 }
287 }
288
289 pub fn socket(&self) -> PgSocket {
290 unsafe {
291 PgSocket {
292 socket: PQsocket(self.conn),
293 }
294 }
295 }
296
297 pub fn consume_input(&mut self) -> Result<(), String> {
298 unsafe {
299 if PQconsumeInput(self.conn) == 0 {
300 Err(self.error_message())
301 } else {
302 Ok(())
303 }
304 }
305 }
306
307 pub fn notifies(&mut self) -> Option<PgNotify> {
308 unsafe {
309 let notify = PQnotifies(self.conn);
310 if notify.is_null() {
311 None
312 } else {
313 Some(PgNotify { notify })
314 }
315 }
316 }
317
318 pub fn error_message(&self) -> String {
319 unsafe {
320 let s = PQerrorMessage(self.conn);
321 if s.is_null() {
322 "".to_string()
323 } else {
324 std::ffi::CStr::from_ptr(s).to_string_lossy().into_owned()
325 }
326 }
327 }
328
329 pub fn notify(&mut self, channel: &str, payload: Option<&str>) -> Result<PgResult, NulError> {
330 let query = match payload {
331 Some(p) => format!("NOTIFY {}, '{}';", channel, p),
332 None => format!("NOTIFY {};", channel),
333 };
334 self.exec(&query)
335 }
336
337 pub fn listen(&mut self, channel: &str) -> Result<PgResult, NulError> {
338 let query = format!("LISTEN {};", channel);
339 self.exec(&query)
340 }
341
342 extern "C" fn ffi_notice_processor<F>(arg: *mut c_void, data: *const c_char)
347 where
348 F: FnMut(String),
349 {
350 unsafe {
351 let s = std::ffi::CStr::from_ptr(data)
352 .to_string_lossy()
353 .into_owned();
354
355 let f = &mut *(arg as *mut F);
356
357 f(s);
358 }
359 }
360
361 pub fn set_notice_processor<F>(&mut self, proc: F) -> Box<F>
362 where
363 F: FnMut(String),
364 {
365 unsafe {
366 let mut b = Box::new(proc);
367 let a = b.as_mut() as *mut F as *mut c_void;
368 PQsetNoticeProcessor(self.conn, Some(Self::ffi_notice_processor::<F>), a);
369 b
370 }
371 }
372
373 extern "C" fn ffi_notice_receiver<F>(arg: *mut c_void, data: *const PGresult)
374 where
375 F: FnMut(PgResult),
376 {
377 unsafe {
378 let s = PgResult {
379 res: data as *mut PGresult,
380 };
381
382 let f = &mut *(arg as *mut F);
383
384 f(s);
385 }
386 }
387
388 pub fn set_notice_receiver<F>(&mut self, proc: F) -> Box<F>
392 where
393 F: FnMut(PgResult),
394 {
395 unsafe {
396 let mut b = Box::new(proc);
397 let a = b.as_mut() as *mut F as *mut c_void;
398 PQsetNoticeReceiver(self.conn, Some(Self::ffi_notice_receiver::<F>), a);
399 b
400 }
401 }
402
403 pub fn listen_loop<F, T>(&mut self, timeout_sec: Option<f64>, proc: F) -> Vec<T>
404 where
405 F: Fn(usize, PgNotify) -> ControlFlow<(), Option<T>>,
406 {
407 let mut recvs = Vec::new();
408
409 let mut count = 0;
410
411 loop {
412 match self.socket().poll(true, false, timeout_sec) {
413 Ok(()) => {
414 self.consume_input().expect("Failed to consume input.");
415
416 while let Some(notify) = self.notifies() {
417 match proc(count, notify) {
418 ControlFlow::Continue(Some(p)) => recvs.push(p),
419 ControlFlow::Break(()) => {
420 break;
421 }
422 _ => {} }
424 self.consume_input().expect("Failed to consume input.");
425 count += 1;
426 }
427 }
428 Err(_e) => break,
429 }
430 }
431
432 recvs
433 }
434}
435
436impl PgResult {
437 pub fn status(&self) -> ExecStatusType {
438 unsafe { PQresultStatus(self.res) }
439 }
440
441 pub fn cmd_status(&mut self) -> String {
442 unsafe {
443 let s = PQcmdStatus(self.res);
444 std::ffi::CStr::from_ptr(s).to_string_lossy().into_owned()
445 }
446 }
447
448 pub fn error_message(&self) -> String {
449 unsafe {
450 let s = PQresultErrorMessage(self.res);
451 std::ffi::CStr::from_ptr(s).to_string_lossy().into_owned()
452 }
453 }
454
455 pub fn error_field(&self, field_code: u8) -> Option<String> {
456 unsafe {
457 let s = PQresultErrorField(self.res, field_code.into());
458 if s.is_null() {
459 None
460 } else {
461 Some(std::ffi::CStr::from_ptr(s).to_string_lossy().into_owned())
462 }
463 }
464 }
465
466 pub fn get_value<T>(&self, row: i32, col: i32) -> Option<T>
467 where
468 T: std::str::FromStr,
469 {
470 unsafe {
471 let s = PQgetvalue(self.res, row, col);
472 if s.is_null() {
473 None
474 } else {
475 let s = std::ffi::CStr::from_ptr(s).to_string_lossy().into_owned();
476 match s.parse::<T>() {
477 Ok(v) => Some(v),
478 Err(_) => None,
479 }
480 }
481 }
482 }
483
484 pub fn ntuples(&self) -> i32 {
485 unsafe { PQntuples(self.res) }
486 }
487
488 pub fn print(
491 &self,
492 filename: &str,
493 header: bool,
494 align: bool,
495 fieldsep: &str,
496 standard: bool,
497 html3: bool,
498 expanded: bool,
499 pager: bool,
500 ) {
501 unsafe {
502 let sep = CString::new(fieldsep).unwrap();
503
504 let printopt = PQprintOpt {
505 header: header.into(),
506 align: align.into(),
507 fieldSep: sep.as_ptr() as *mut c_char,
508 tableOpt: null_mut(),
509 caption: null_mut(),
510 standard: standard.into(),
511 html3: html3.into(),
512 expanded: expanded.into(),
513 pager: pager.into(),
514 fieldName: null_mut(),
515 };
516
517 let fp = fopen(
518 CString::new(filename).unwrap().as_ptr(),
519 CString::new("w").unwrap().as_ptr(),
520 );
521
522 PQprint(fp, self.res, &printopt);
523
524 assert_eq!(fflush(fp), 0);
525 assert_eq!(fclose(fp), 0);
526 }
527 }
528
529 pub fn get_value_raw(&self, row: i32, col: i32) -> String {
532 unsafe {
533 let s = PQgetvalue(self.res, row, col);
534 if s.is_null() {
535 "".to_string()
536 } else {
537 std::ffi::CStr::from_ptr(s).to_string_lossy().into_owned()
538 }
539 }
540 }
541}
542
543impl Display for PgResult {
544 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
545 let mut temp_file = Builder::new()
546 .prefix("pg-res-")
547 .suffix(".json")
548 .tempfile()
549 .unwrap();
550
551 let temp_path = temp_file.path().to_path_buf();
552
553 self.print(
554 temp_path.as_path().to_str().unwrap(),
555 true,
556 true,
557 "|",
558 true,
559 false,
560 false,
561 false,
562 );
563
564 let mut s = String::new();
565 temp_file
566 .seek(std::io::SeekFrom::Start(0))
567 .expect("Failed to seek to start of temp file.");
568 temp_file
569 .read_to_string(&mut s)
570 .expect("Failed to read temp file.");
571
572 write!(f, "{}", s)
573 }
574}