1#![allow(clippy::result_large_err)]
9#![allow(clippy::cast_possible_truncation)]
11
12use sqlmodel_core::Error;
13use sqlmodel_core::error::TypeError;
14
15use super::oid;
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
19pub enum Format {
20 #[default]
22 Text,
23 Binary,
25}
26
27impl Format {
28 #[must_use]
30 pub const fn code(self) -> i16 {
31 match self {
32 Format::Text => 0,
33 Format::Binary => 1,
34 }
35 }
36
37 #[must_use]
39 pub const fn from_code(code: i16) -> Self {
40 match code {
41 1 => Format::Binary,
42 _ => Format::Text,
43 }
44 }
45}
46
47pub trait TextEncode {
49 fn encode_text(&self) -> String;
51}
52
53pub trait BinaryEncode {
55 fn encode_binary(&self, buf: &mut Vec<u8>);
57}
58
59pub trait Encode: TextEncode + BinaryEncode {
61 fn oid() -> u32;
63
64 fn encode(&self, format: Format, buf: &mut Vec<u8>) {
66 match format {
67 Format::Text => buf.extend(self.encode_text().as_bytes()),
68 Format::Binary => self.encode_binary(buf),
69 }
70 }
71}
72
73impl TextEncode for bool {
76 fn encode_text(&self) -> String {
77 if *self { "t" } else { "f" }.to_string()
78 }
79}
80
81impl BinaryEncode for bool {
82 fn encode_binary(&self, buf: &mut Vec<u8>) {
83 buf.push(u8::from(*self));
84 }
85}
86
87impl Encode for bool {
88 fn oid() -> u32 {
89 oid::BOOL
90 }
91}
92
93impl TextEncode for i8 {
96 fn encode_text(&self) -> String {
97 self.to_string()
98 }
99}
100
101impl BinaryEncode for i8 {
102 fn encode_binary(&self, buf: &mut Vec<u8>) {
103 buf.push(*self as u8);
104 }
105}
106
107impl TextEncode for i16 {
108 fn encode_text(&self) -> String {
109 self.to_string()
110 }
111}
112
113impl BinaryEncode for i16 {
114 fn encode_binary(&self, buf: &mut Vec<u8>) {
115 buf.extend_from_slice(&self.to_be_bytes());
116 }
117}
118
119impl Encode for i16 {
120 fn oid() -> u32 {
121 oid::INT2
122 }
123}
124
125impl TextEncode for i32 {
126 fn encode_text(&self) -> String {
127 self.to_string()
128 }
129}
130
131impl BinaryEncode for i32 {
132 fn encode_binary(&self, buf: &mut Vec<u8>) {
133 buf.extend_from_slice(&self.to_be_bytes());
134 }
135}
136
137impl Encode for i32 {
138 fn oid() -> u32 {
139 oid::INT4
140 }
141}
142
143impl TextEncode for i64 {
144 fn encode_text(&self) -> String {
145 self.to_string()
146 }
147}
148
149impl BinaryEncode for i64 {
150 fn encode_binary(&self, buf: &mut Vec<u8>) {
151 buf.extend_from_slice(&self.to_be_bytes());
152 }
153}
154
155impl Encode for i64 {
156 fn oid() -> u32 {
157 oid::INT8
158 }
159}
160
161impl TextEncode for u32 {
165 fn encode_text(&self) -> String {
166 i64::from(*self).to_string()
168 }
169}
170
171impl BinaryEncode for u32 {
172 fn encode_binary(&self, buf: &mut Vec<u8>) {
173 i64::from(*self).encode_binary(buf);
175 }
176}
177
178impl TextEncode for f32 {
181 fn encode_text(&self) -> String {
182 if self.is_nan() {
183 "NaN".to_string()
184 } else if self.is_infinite() {
185 if self.is_sign_positive() {
186 "Infinity".to_string()
187 } else {
188 "-Infinity".to_string()
189 }
190 } else {
191 self.to_string()
192 }
193 }
194}
195
196impl BinaryEncode for f32 {
197 fn encode_binary(&self, buf: &mut Vec<u8>) {
198 buf.extend_from_slice(&self.to_be_bytes());
199 }
200}
201
202impl Encode for f32 {
203 fn oid() -> u32 {
204 oid::FLOAT4
205 }
206}
207
208impl TextEncode for f64 {
209 fn encode_text(&self) -> String {
210 if self.is_nan() {
211 "NaN".to_string()
212 } else if self.is_infinite() {
213 if self.is_sign_positive() {
214 "Infinity".to_string()
215 } else {
216 "-Infinity".to_string()
217 }
218 } else {
219 self.to_string()
220 }
221 }
222}
223
224impl BinaryEncode for f64 {
225 fn encode_binary(&self, buf: &mut Vec<u8>) {
226 buf.extend_from_slice(&self.to_be_bytes());
227 }
228}
229
230impl Encode for f64 {
231 fn oid() -> u32 {
232 oid::FLOAT8
233 }
234}
235
236impl TextEncode for str {
239 fn encode_text(&self) -> String {
240 self.to_string()
241 }
242}
243
244impl BinaryEncode for str {
245 fn encode_binary(&self, buf: &mut Vec<u8>) {
246 buf.extend_from_slice(self.as_bytes());
247 }
248}
249
250impl TextEncode for String {
251 fn encode_text(&self) -> String {
252 self.clone()
253 }
254}
255
256impl BinaryEncode for String {
257 fn encode_binary(&self, buf: &mut Vec<u8>) {
258 buf.extend_from_slice(self.as_bytes());
259 }
260}
261
262impl Encode for String {
263 fn oid() -> u32 {
264 oid::TEXT
265 }
266}
267
268impl TextEncode for &str {
269 fn encode_text(&self) -> String {
270 (*self).to_string()
271 }
272}
273
274impl BinaryEncode for &str {
275 fn encode_binary(&self, buf: &mut Vec<u8>) {
276 buf.extend_from_slice(self.as_bytes());
277 }
278}
279
280impl TextEncode for [u8] {
283 fn encode_text(&self) -> String {
284 let mut s = String::with_capacity(2 + self.len() * 2);
286 s.push_str("\\x");
287 for byte in self {
288 s.push_str(&format!("{byte:02x}"));
289 }
290 s
291 }
292}
293
294impl BinaryEncode for [u8] {
295 fn encode_binary(&self, buf: &mut Vec<u8>) {
296 buf.extend_from_slice(self);
297 }
298}
299
300impl TextEncode for Vec<u8> {
301 fn encode_text(&self) -> String {
302 self.as_slice().encode_text()
303 }
304}
305
306impl BinaryEncode for Vec<u8> {
307 fn encode_binary(&self, buf: &mut Vec<u8>) {
308 buf.extend_from_slice(self);
309 }
310}
311
312impl Encode for Vec<u8> {
313 fn oid() -> u32 {
314 oid::BYTEA
315 }
316}
317
318impl TextEncode for [u8; 16] {
322 fn encode_text(&self) -> String {
323 format!(
324 "{:02x}{:02x}{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}-{:02x}{:02x}{:02x}{:02x}{:02x}{:02x}",
325 self[0],
326 self[1],
327 self[2],
328 self[3],
329 self[4],
330 self[5],
331 self[6],
332 self[7],
333 self[8],
334 self[9],
335 self[10],
336 self[11],
337 self[12],
338 self[13],
339 self[14],
340 self[15]
341 )
342 }
343}
344
345impl BinaryEncode for [u8; 16] {
346 fn encode_binary(&self, buf: &mut Vec<u8>) {
347 buf.extend_from_slice(self);
348 }
349}
350
351const PG_EPOCH_OFFSET_DAYS: i32 = 10_957; const PG_EPOCH_OFFSET_MICROS: i64 = 946_684_800_000_000; pub fn encode_date_days(days_since_unix: i32) -> i32 {
365 days_since_unix - PG_EPOCH_OFFSET_DAYS
366}
367
368pub fn encode_timestamp_micros(micros_since_unix: i64) -> i64 {
373 micros_since_unix - PG_EPOCH_OFFSET_MICROS
374}
375
376pub fn encode_time_micros(micros_since_midnight: i64) -> i64 {
378 micros_since_midnight
379}
380
381impl<T: TextEncode> TextEncode for Option<T> {
384 fn encode_text(&self) -> String {
385 match self {
386 Some(v) => v.encode_text(),
387 None => String::new(),
388 }
389 }
390}
391
392impl<T: BinaryEncode> BinaryEncode for Option<T> {
393 fn encode_binary(&self, buf: &mut Vec<u8>) {
394 if let Some(v) = self {
395 v.encode_binary(buf);
396 }
397 }
398}
399
400use sqlmodel_core::value::Value;
403
404pub fn encode_value(value: &Value, format: Format) -> Result<(Vec<u8>, u32), Error> {
408 let mut buf = Vec::new();
409 let type_oid = match value {
410 Value::Null => return Ok((vec![], oid::UNKNOWN)),
411 Value::Bool(v) => {
412 match format {
413 Format::Text => buf.extend(v.encode_text().as_bytes()),
414 Format::Binary => v.encode_binary(&mut buf),
415 }
416 oid::BOOL
417 }
418 Value::TinyInt(v) => {
419 match format {
420 Format::Text => buf.extend(v.encode_text().as_bytes()),
421 Format::Binary => {
422 i16::from(*v).encode_binary(&mut buf);
424 }
425 }
426 oid::INT2
427 }
428 Value::SmallInt(v) => {
429 match format {
430 Format::Text => buf.extend(v.encode_text().as_bytes()),
431 Format::Binary => v.encode_binary(&mut buf),
432 }
433 oid::INT2
434 }
435 Value::Int(v) => {
436 match format {
437 Format::Text => buf.extend(v.encode_text().as_bytes()),
438 Format::Binary => v.encode_binary(&mut buf),
439 }
440 oid::INT4
441 }
442 Value::BigInt(v) => {
443 match format {
444 Format::Text => buf.extend(v.encode_text().as_bytes()),
445 Format::Binary => v.encode_binary(&mut buf),
446 }
447 oid::INT8
448 }
449 Value::Float(v) => {
450 match format {
451 Format::Text => buf.extend(v.encode_text().as_bytes()),
452 Format::Binary => v.encode_binary(&mut buf),
453 }
454 oid::FLOAT4
455 }
456 Value::Double(v) => {
457 match format {
458 Format::Text => buf.extend(v.encode_text().as_bytes()),
459 Format::Binary => v.encode_binary(&mut buf),
460 }
461 oid::FLOAT8
462 }
463 Value::Decimal(v) => {
464 buf.extend(v.as_bytes());
465 oid::NUMERIC
466 }
467 Value::Text(v) => {
468 buf.extend(v.as_bytes());
469 oid::TEXT
470 }
471 Value::Bytes(v) => {
472 match format {
473 Format::Text => buf.extend(v.encode_text().as_bytes()),
474 Format::Binary => v.encode_binary(&mut buf),
475 }
476 oid::BYTEA
477 }
478 Value::Date(days) => {
479 match format {
480 Format::Text => {
481 let date = days_to_date_string(*days);
483 buf.extend(date.as_bytes());
484 }
485 Format::Binary => {
486 encode_date_days(*days).encode_binary(&mut buf);
487 }
488 }
489 oid::DATE
490 }
491 Value::Time(micros) => {
492 match format {
493 Format::Text => {
494 let time = micros_to_time_string(*micros);
495 buf.extend(time.as_bytes());
496 }
497 Format::Binary => {
498 micros.encode_binary(&mut buf);
499 }
500 }
501 oid::TIME
502 }
503 Value::Timestamp(micros) => {
504 match format {
505 Format::Text => {
506 let ts = micros_to_timestamp_string(*micros);
507 buf.extend(ts.as_bytes());
508 }
509 Format::Binary => {
510 encode_timestamp_micros(*micros).encode_binary(&mut buf);
511 }
512 }
513 oid::TIMESTAMP
514 }
515 Value::TimestampTz(micros) => {
516 match format {
517 Format::Text => {
518 let ts = micros_to_timestamp_string(*micros);
519 buf.extend(ts.as_bytes());
520 buf.extend(b"+00");
521 }
522 Format::Binary => {
523 encode_timestamp_micros(*micros).encode_binary(&mut buf);
524 }
525 }
526 oid::TIMESTAMPTZ
527 }
528 Value::Uuid(bytes) => {
529 match format {
530 Format::Text => buf.extend(bytes.encode_text().as_bytes()),
531 Format::Binary => bytes.encode_binary(&mut buf),
532 }
533 oid::UUID
534 }
535 Value::Json(json) => {
536 buf.extend(json.to_string().as_bytes());
537 oid::JSON
538 }
539 Value::Array(values) => {
540 return Err(Error::Type(TypeError {
541 expected: "scalar value",
542 actual: format!("array with {} elements", values.len()),
543 column: None,
544 rust_type: None,
545 }));
546 }
547 Value::Default => return Ok((vec![], oid::UNKNOWN)),
548 };
549
550 Ok((buf, type_oid))
551}
552
553#[allow(clippy::many_single_char_names)]
557fn days_to_date_string(days: i32) -> String {
558 let unix_epoch_jd = 2_440_588; let jd = unix_epoch_jd + i64::from(days);
562
563 let l = jd + 68_569;
565 let n = 4 * l / 146_097;
566 let l = l - (146_097 * n + 3) / 4;
567 let i = 4000 * (l + 1) / 1_461_001;
568 let l = l - 1461 * i / 4 + 31;
569 let j = 80 * l / 2447;
570 let d = l - 2447 * j / 80;
571 let l = j / 11;
572 let m = j + 2 - 12 * l;
573 let y = 100 * (n - 49) + i + l;
574
575 format!("{y:04}-{m:02}-{d:02}")
576}
577
578fn micros_to_time_string(micros: i64) -> String {
580 let total_secs = micros / 1_000_000;
581 let frac_micros = micros % 1_000_000;
582 let hours = total_secs / 3600;
583 let mins = (total_secs % 3600) / 60;
584 let secs = total_secs % 60;
585
586 if frac_micros == 0 {
587 format!("{hours:02}:{mins:02}:{secs:02}")
588 } else {
589 format!("{hours:02}:{mins:02}:{secs:02}.{frac_micros:06}")
590 }
591}
592
593fn micros_to_timestamp_string(micros: i64) -> String {
595 let days = micros / (86_400 * 1_000_000);
596 let day_micros = micros % (86_400 * 1_000_000);
597
598 let date = days_to_date_string(days as i32);
599 let time = micros_to_time_string(day_micros);
600
601 format!("{date} {time}")
602}
603
604#[cfg(test)]
605mod tests {
606 use super::*;
607
608 #[test]
609 fn test_bool_encoding() {
610 assert_eq!(true.encode_text(), "t");
611 assert_eq!(false.encode_text(), "f");
612
613 let mut buf = Vec::new();
614 true.encode_binary(&mut buf);
615 assert_eq!(buf, vec![1]);
616
617 buf.clear();
618 false.encode_binary(&mut buf);
619 assert_eq!(buf, vec![0]);
620 }
621
622 #[test]
623 fn test_integer_encoding() {
624 assert_eq!(42i32.encode_text(), "42");
625 assert_eq!((-100i64).encode_text(), "-100");
626
627 let mut buf = Vec::new();
628 42i32.encode_binary(&mut buf);
629 assert_eq!(buf, vec![0, 0, 0, 42]);
630
631 buf.clear();
632 256i32.encode_binary(&mut buf);
633 assert_eq!(buf, vec![0, 0, 1, 0]);
634 }
635
636 #[test]
637 fn test_float_encoding() {
638 assert_eq!(f64::NAN.encode_text(), "NaN");
639 assert_eq!(f64::INFINITY.encode_text(), "Infinity");
640 assert_eq!(f64::NEG_INFINITY.encode_text(), "-Infinity");
641 }
642
643 #[test]
644 fn test_bytea_encoding() {
645 let bytes = vec![0xDE, 0xAD, 0xBE, 0xEF];
646 assert_eq!(bytes.encode_text(), "\\xdeadbeef");
647 }
648
649 #[test]
650 fn test_uuid_encoding() {
651 let uuid: [u8; 16] = [
652 0x55, 0x06, 0x9c, 0x47, 0x86, 0x8b, 0x4a, 0x08, 0xa4, 0x7f, 0x36, 0x53, 0x26, 0x2b,
653 0xce, 0x35,
654 ];
655 assert_eq!(uuid.encode_text(), "55069c47-868b-4a08-a47f-3653262bce35");
656 }
657
658 #[test]
659 fn test_format_code() {
660 assert_eq!(Format::Text.code(), 0);
661 assert_eq!(Format::Binary.code(), 1);
662 assert_eq!(Format::from_code(0), Format::Text);
663 assert_eq!(Format::from_code(1), Format::Binary);
664 }
665}