1use bytes::{Buf, BytesMut};
27use tokio_postgres::types::{FromSql, IsNull, ToSql, Type};
28use tokio_postgres::Row as PgRow;
29
30use oxisql_core::{Row, Value};
31
32use crate::error::PgError;
33
34struct RawNumeric(Vec<u8>);
46
47impl<'a> FromSql<'a> for RawNumeric {
48 fn from_sql(
49 _ty: &Type,
50 raw: &'a [u8],
51 ) -> Result<Self, Box<dyn std::error::Error + Sync + Send>> {
52 Ok(RawNumeric(raw.to_vec()))
53 }
54
55 fn accepts(ty: &Type) -> bool {
56 *ty == Type::NUMERIC
57 }
58}
59
60fn decode_pg_numeric(raw: &[u8]) -> Result<String, PgError> {
65 if raw.len() < 8 {
66 return Err(PgError::TypeConversion(format!(
67 "NUMERIC binary too short: {} bytes",
68 raw.len()
69 )));
70 }
71
72 let mut buf = raw;
73
74 let ndigits = buf.get_u16() as usize;
75 let weight = buf.get_i16(); let sign = buf.get_u16();
77 let dscale = buf.get_u16() as usize;
78
79 const SIGN_NAN: u16 = 0xC000;
80 const SIGN_NEG: u16 = 0x4000;
81
82 if sign == SIGN_NAN {
83 return Ok("NaN".to_string());
84 }
85
86 let mut digits: Vec<u16> = Vec::with_capacity(ndigits);
88 for _ in 0..ndigits {
89 if buf.remaining() < 2 {
90 return Err(PgError::TypeConversion(
91 "NUMERIC binary truncated while reading digit groups".to_string(),
92 ));
93 }
94 digits.push(buf.get_u16());
95 }
96
97 let mut integer_part = String::new();
107 let mut fraction_part = String::new();
108
109 let int_groups = (weight + 1).max(0) as usize;
111
112 let int_digit_iter = (0..int_groups).map(|i| if i < ndigits { digits[i] } else { 0 });
115 for (i, digit) in int_digit_iter.enumerate() {
116 if i == 0 {
117 integer_part.push_str(&digit.to_string());
119 } else {
120 integer_part.push_str(&format!("{digit:04}"));
122 }
123 }
124
125 if integer_part.is_empty() {
126 integer_part.push('0');
127 }
128
129 if dscale > 0 {
132 let mut remaining_scale = dscale;
133 let frac_groups_needed = dscale.div_ceil(4);
134
135 for i in 0..frac_groups_needed {
136 let digit_idx = int_groups + i;
137 let digit = if digit_idx < ndigits {
138 digits[digit_idx]
139 } else {
140 0
141 };
142 let group_str = format!("{digit:04}");
143 let take = remaining_scale.min(4);
145 fraction_part.push_str(&group_str[..take]);
146 remaining_scale = remaining_scale.saturating_sub(4);
147 }
148 }
149
150 let mut result = if sign == SIGN_NEG {
151 format!("-{integer_part}")
152 } else {
153 integer_part
154 };
155
156 if !fraction_part.is_empty() {
157 result.push('.');
158 result.push_str(&fraction_part);
159 }
160
161 Ok(result)
162}
163
164struct RawInterval(Vec<u8>);
173
174impl<'a> FromSql<'a> for RawInterval {
175 fn from_sql(
176 _ty: &Type,
177 raw: &'a [u8],
178 ) -> Result<Self, Box<dyn std::error::Error + Sync + Send>> {
179 Ok(RawInterval(raw.to_vec()))
180 }
181
182 fn accepts(ty: &Type) -> bool {
183 *ty == Type::INTERVAL
184 }
185}
186
187fn decode_pg_interval(raw: &[u8]) -> Result<String, PgError> {
191 if raw.len() < 16 {
192 return Err(PgError::TypeConversion(format!(
193 "INTERVAL binary too short: {} bytes",
194 raw.len()
195 )));
196 }
197 let mut buf = raw;
198 let micros = buf.get_i64();
199 let days = buf.get_i32();
200 let months = buf.get_i32();
201
202 let years = months / 12;
203 let rem_months = months % 12;
204 let hours = micros / 3_600_000_000;
205 let rem_micros = micros % 3_600_000_000;
206 let minutes = rem_micros / 60_000_000;
207 let rem_micros2 = rem_micros % 60_000_000;
208 let secs = rem_micros2 / 1_000_000;
209 let frac_micros = (rem_micros2 % 1_000_000).unsigned_abs();
210
211 let mut parts: Vec<String> = Vec::new();
212 if years != 0 {
213 parts.push(format!("{years}Y"));
214 }
215 if rem_months != 0 {
216 parts.push(format!("{rem_months}M"));
217 }
218 if days != 0 {
219 parts.push(format!("{days}D"));
220 }
221
222 let has_time = hours != 0 || minutes != 0 || secs != 0 || frac_micros != 0;
223 if has_time {
224 if frac_micros > 0 {
225 parts.push(format!(
226 "{hours:02}:{minutes:02}:{secs:02}.{frac_micros:06}"
227 ));
228 } else {
229 parts.push(format!("{hours:02}:{minutes:02}:{secs:02}"));
230 }
231 }
232
233 if parts.is_empty() {
234 Ok("00:00:00".to_string())
235 } else {
236 Ok(parts.join(" "))
237 }
238}
239
240#[derive(Debug)]
248pub enum OwnedParam {
249 Null,
251 Bool(bool),
253 I64(i64),
255 F64(f64),
257 Text(String),
259 Blob(Vec<u8>),
261}
262
263impl ToSql for OwnedParam {
264 fn to_sql(
265 &self,
266 ty: &Type,
267 out: &mut BytesMut,
268 ) -> Result<IsNull, Box<dyn std::error::Error + Sync + Send>> {
269 match self {
270 OwnedParam::Null => Ok(IsNull::Yes),
271 OwnedParam::Bool(v) => v.to_sql(ty, out),
272 OwnedParam::I64(v) => {
273 if *ty == Type::INT4 {
274 let v4 = i32::try_from(*v)
275 .map_err(|e| Box::new(e) as Box<dyn std::error::Error + Sync + Send>)?;
276 v4.to_sql(ty, out)
277 } else if *ty == Type::INT2 {
278 let v2 = i16::try_from(*v)
279 .map_err(|e| Box::new(e) as Box<dyn std::error::Error + Sync + Send>)?;
280 v2.to_sql(ty, out)
281 } else {
282 v.to_sql(ty, out)
283 }
284 }
285 OwnedParam::F64(v) => {
286 if *ty == Type::FLOAT4 {
287 #[allow(clippy::cast_possible_truncation)]
288 let v4 = *v as f32;
289 v4.to_sql(ty, out)
290 } else {
291 v.to_sql(ty, out)
292 }
293 }
294 OwnedParam::Text(v) => v.to_sql(ty, out),
295 OwnedParam::Blob(v) => v.to_sql(ty, out),
296 }
297 }
298
299 fn accepts(_ty: &Type) -> bool
300 where
301 Self: Sized,
302 {
303 true
306 }
307
308 tokio_postgres::types::to_sql_checked!();
309}
310
311pub fn value_to_param(v: &Value) -> OwnedParam {
317 match v {
318 Value::Null => OwnedParam::Null,
319 Value::Bool(b) => OwnedParam::Bool(*b),
320 Value::I64(n) => OwnedParam::I64(*n),
321 Value::F64(f) => OwnedParam::F64(*f),
322 Value::Text(s) => OwnedParam::Text(s.clone()),
323 Value::Blob(b) => OwnedParam::Blob(b.clone()),
324 Value::Timestamp(us) => {
326 let secs = us / 1_000_000;
327 let frac = (us % 1_000_000).unsigned_abs();
328 OwnedParam::Text(format!("{secs}.{frac:06}"))
329 }
330 Value::Date(days) => OwnedParam::Text(format!("{days}")),
331 Value::Time(us) => {
332 let total_secs = us / 1_000_000;
333 let hours = total_secs / 3600;
334 let mins = (total_secs % 3600) / 60;
335 let secs = total_secs % 60;
336 let frac = (us % 1_000_000).unsigned_abs();
337 if frac == 0 {
338 OwnedParam::Text(format!("{hours:02}:{mins:02}:{secs:02}"))
339 } else {
340 OwnedParam::Text(format!("{hours:02}:{mins:02}:{secs:02}.{frac:06}"))
341 }
342 }
343 Value::Uuid(u) => OwnedParam::Text(format!("{}", Value::Uuid(*u))),
344 Value::Json(s) => OwnedParam::Text(s.clone()),
345 Value::Decimal(s) => OwnedParam::Text(s.clone()),
346 Value::Array(vals) => {
347 let items: Vec<String> = vals.iter().map(|v| format!("{v}")).collect();
349 OwnedParam::Text(format!("{{{}}}", items.join(",")))
350 }
351 }
352}
353
354pub fn pg_row_to_row(pg_row: PgRow) -> Result<Row, PgError> {
362 let cols = pg_row.columns();
363 let mut names = Vec::with_capacity(cols.len());
364 let mut values = Vec::with_capacity(cols.len());
365
366 for (i, col) in cols.iter().enumerate() {
367 names.push(col.name().to_string());
368 let value = extract_value(&pg_row, i, col.type_())?;
369 values.push(value);
370 }
371
372 Ok(Row::new(names, values))
373}
374
375fn extract_value(row: &PgRow, idx: usize, ty: &Type) -> Result<Value, PgError> {
376 match *ty {
377 Type::BOOL => {
378 let v: Option<bool> = row
379 .try_get(idx)
380 .map_err(|e| PgError::TypeConversion(e.to_string()))?;
381 Ok(v.map(Value::Bool).unwrap_or(Value::Null))
382 }
383 Type::INT2 => {
384 let v: Option<i16> = row
385 .try_get(idx)
386 .map_err(|e| PgError::TypeConversion(e.to_string()))?;
387 Ok(v.map(|n| Value::I64(i64::from(n))).unwrap_or(Value::Null))
388 }
389 Type::INT4 => {
390 let v: Option<i32> = row
391 .try_get(idx)
392 .map_err(|e| PgError::TypeConversion(e.to_string()))?;
393 Ok(v.map(|n| Value::I64(i64::from(n))).unwrap_or(Value::Null))
394 }
395 Type::INT8 => {
396 let v: Option<i64> = row
397 .try_get(idx)
398 .map_err(|e| PgError::TypeConversion(e.to_string()))?;
399 Ok(v.map(Value::I64).unwrap_or(Value::Null))
400 }
401 Type::FLOAT4 => {
402 let v: Option<f32> = row
403 .try_get(idx)
404 .map_err(|e| PgError::TypeConversion(e.to_string()))?;
405 Ok(v.map(|f| Value::F64(f64::from(f))).unwrap_or(Value::Null))
406 }
407 Type::FLOAT8 => {
408 let v: Option<f64> = row
409 .try_get(idx)
410 .map_err(|e| PgError::TypeConversion(e.to_string()))?;
411 Ok(v.map(Value::F64).unwrap_or(Value::Null))
412 }
413 Type::BYTEA => {
414 let v: Option<Vec<u8>> = row
415 .try_get(idx)
416 .map_err(|e| PgError::TypeConversion(e.to_string()))?;
417 Ok(v.map(Value::Blob).unwrap_or(Value::Null))
418 }
419 Type::JSON | Type::JSONB => {
421 let v: Option<String> = row
422 .try_get(idx)
423 .map_err(|e| PgError::TypeConversion(e.to_string()))?;
424 Ok(v.map(Value::Json).unwrap_or(Value::Null))
425 }
426 Type::DATE => {
434 let v: Option<time::Date> = row
435 .try_get(idx)
436 .map_err(|e| PgError::TypeConversion(e.to_string()))?;
437 Ok(v.map(|d| {
438 const UNIX_EPOCH_JDN: i32 = 2_440_588; Value::Date(d.to_julian_day() - UNIX_EPOCH_JDN)
440 })
441 .unwrap_or(Value::Null))
442 }
443 Type::TIMESTAMP => {
445 let v: Option<time::PrimitiveDateTime> = row
446 .try_get(idx)
447 .map_err(|e| PgError::TypeConversion(e.to_string()))?;
448 Ok(v.map(|dt| {
449 let us = dt.assume_utc().unix_timestamp_nanos() / 1_000;
451 #[allow(clippy::cast_possible_truncation)]
452 Value::Timestamp(us as i64)
453 })
454 .unwrap_or(Value::Null))
455 }
456 Type::TIMESTAMPTZ => {
458 let v: Option<time::OffsetDateTime> = row
459 .try_get(idx)
460 .map_err(|e| PgError::TypeConversion(e.to_string()))?;
461 Ok(v.map(|dt| {
462 let us = dt.unix_timestamp_nanos() / 1_000;
463 #[allow(clippy::cast_possible_truncation)]
464 Value::Timestamp(us as i64)
465 })
466 .unwrap_or(Value::Null))
467 }
468 Type::TIME => {
470 let v: Option<time::Time> = row
471 .try_get(idx)
472 .map_err(|e| PgError::TypeConversion(e.to_string()))?;
473 Ok(v.map(|t| {
474 let (h, m, s, ns) = t.as_hms_nano();
475 let us = i64::from(h) * 3_600_000_000
476 + i64::from(m) * 60_000_000
477 + i64::from(s) * 1_000_000
478 + i64::from(ns) / 1_000;
479 Value::Time(us)
480 })
481 .unwrap_or(Value::Null))
482 }
483 Type::UUID => {
485 let v: Option<uuid::Uuid> = row
486 .try_get(idx)
487 .map_err(|e| PgError::TypeConversion(e.to_string()))?;
488 Ok(v.map(|u| Value::Uuid(u.as_u128())).unwrap_or(Value::Null))
489 }
490 Type::NUMERIC => {
492 let v: Option<RawNumeric> = row
493 .try_get(idx)
494 .map_err(|e| PgError::TypeConversion(e.to_string()))?;
495 match v {
496 None => Ok(Value::Null),
497 Some(raw) => {
498 let s = decode_pg_numeric(&raw.0)?;
499 Ok(Value::Decimal(s))
500 }
501 }
502 }
503 Type::INTERVAL => {
505 let v: Option<RawInterval> = row
506 .try_get(idx)
507 .map_err(|e| PgError::TypeConversion(e.to_string()))?;
508 match v {
509 None => Ok(Value::Null),
510 Some(raw) => {
511 let s = decode_pg_interval(&raw.0)?;
512 Ok(Value::Text(s))
513 }
514 }
515 }
516 Type::BOOL_ARRAY => {
518 let v: Option<Vec<Option<bool>>> = row
519 .try_get(idx)
520 .map_err(|e| PgError::TypeConversion(e.to_string()))?;
521 Ok(v.map(|items| {
522 Value::Array(
523 items
524 .into_iter()
525 .map(|opt| opt.map(Value::Bool).unwrap_or(Value::Null))
526 .collect(),
527 )
528 })
529 .unwrap_or(Value::Null))
530 }
531 Type::INT2_ARRAY => {
532 let v: Option<Vec<Option<i16>>> = row
533 .try_get(idx)
534 .map_err(|e| PgError::TypeConversion(e.to_string()))?;
535 Ok(v.map(|items| {
536 Value::Array(
537 items
538 .into_iter()
539 .map(|opt| opt.map(|n| Value::I64(i64::from(n))).unwrap_or(Value::Null))
540 .collect(),
541 )
542 })
543 .unwrap_or(Value::Null))
544 }
545 Type::INT4_ARRAY => {
546 let v: Option<Vec<Option<i32>>> = row
547 .try_get(idx)
548 .map_err(|e| PgError::TypeConversion(e.to_string()))?;
549 Ok(v.map(|items| {
550 Value::Array(
551 items
552 .into_iter()
553 .map(|opt| opt.map(|n| Value::I64(i64::from(n))).unwrap_or(Value::Null))
554 .collect(),
555 )
556 })
557 .unwrap_or(Value::Null))
558 }
559 Type::INT8_ARRAY => {
560 let v: Option<Vec<Option<i64>>> = row
561 .try_get(idx)
562 .map_err(|e| PgError::TypeConversion(e.to_string()))?;
563 Ok(v.map(|items| {
564 Value::Array(
565 items
566 .into_iter()
567 .map(|opt| opt.map(Value::I64).unwrap_or(Value::Null))
568 .collect(),
569 )
570 })
571 .unwrap_or(Value::Null))
572 }
573 Type::FLOAT4_ARRAY => {
574 let v: Option<Vec<Option<f32>>> = row
575 .try_get(idx)
576 .map_err(|e| PgError::TypeConversion(e.to_string()))?;
577 Ok(v.map(|items| {
578 Value::Array(
579 items
580 .into_iter()
581 .map(|opt| opt.map(|f| Value::F64(f64::from(f))).unwrap_or(Value::Null))
582 .collect(),
583 )
584 })
585 .unwrap_or(Value::Null))
586 }
587 Type::FLOAT8_ARRAY => {
588 let v: Option<Vec<Option<f64>>> = row
589 .try_get(idx)
590 .map_err(|e| PgError::TypeConversion(e.to_string()))?;
591 Ok(v.map(|items| {
592 Value::Array(
593 items
594 .into_iter()
595 .map(|opt| opt.map(Value::F64).unwrap_or(Value::Null))
596 .collect(),
597 )
598 })
599 .unwrap_or(Value::Null))
600 }
601 Type::TEXT_ARRAY | Type::VARCHAR_ARRAY => {
602 let v: Option<Vec<Option<String>>> = row
603 .try_get(idx)
604 .map_err(|e| PgError::TypeConversion(e.to_string()))?;
605 Ok(v.map(|items| {
606 Value::Array(
607 items
608 .into_iter()
609 .map(|opt| opt.map(Value::Text).unwrap_or(Value::Null))
610 .collect(),
611 )
612 })
613 .unwrap_or(Value::Null))
614 }
615 ref other => {
617 if <String as FromSql>::accepts(other) {
618 let v: Option<String> = row
619 .try_get(idx)
620 .map_err(|e| PgError::TypeConversion(e.to_string()))?;
621 Ok(v.map(Value::Text).unwrap_or(Value::Null))
622 } else {
623 Ok(Value::Text(format!("<opaque:{}>", other.name())))
625 }
626 }
627 }
628}